Devicetree
 help / color / mirror / Atom feed
* [PATCH 6/9] ARM: dts: wheat: Drop MTD partitioning from DT
From: Marek Vasut @ 2018-05-22 12:02 UTC (permalink / raw)
  To: linux-arm-kernel
  Cc: devicetree, Geert Uytterhoeven, Wolfram Sang, linux-renesas-soc,
	Laurent Pinchart, Simon Horman, Marek Vasut
In-Reply-To: <20180522120257.13232-1-marek.vasut+renesas@gmail.com>

Drop the MTD partitioning from DT, since it does not describe HW
and to give way to a more flexible kernel command line partition
passing.

To retain the original partitioning, assure you have enabled
CONFIG_MTD_CMDLINE_PARTS in your kernel config and add the
following to your kernel command line:

  mtdparts=spi0.0:256k@0(loader),4096k(user),-(flash)

Signed-off-by: Marek Vasut <marek.vasut+renesas@gmail.com>
Cc: Geert Uytterhoeven <geert+renesas@glider.be>
Cc: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Cc: Simon Horman <horms+renesas@verge.net.au>
Cc: Wolfram Sang <wsa@the-dreams.de>
Cc: linux-renesas-soc@vger.kernel.org
---
 arch/arm/boot/dts/r8a7792-wheat.dts | 21 ---------------------
 1 file changed, 21 deletions(-)

diff --git a/arch/arm/boot/dts/r8a7792-wheat.dts b/arch/arm/boot/dts/r8a7792-wheat.dts
index db01de7a3811..93f78716225a 100644
--- a/arch/arm/boot/dts/r8a7792-wheat.dts
+++ b/arch/arm/boot/dts/r8a7792-wheat.dts
@@ -217,27 +217,6 @@
 		spi-cpol;
 		spi-cpha;
 		m25p,fast-read;
-
-		partitions {
-			compatible = "fixed-partitions";
-			#address-cells = <1>;
-			#size-cells = <1>;
-
-			partition@0 {
-				label = "loader";
-				reg = <0x00000000 0x00040000>;
-				read-only;
-			};
-			partition@40000 {
-				label = "user";
-				reg = <0x00040000 0x00400000>;
-				read-only;
-			};
-			partition@440000 {
-				label = "flash";
-				reg = <0x00440000 0x03bc0000>;
-			};
-		};
 	};
 };
 
-- 
2.16.2

^ permalink raw reply related

* [PATCH 7/9] ARM: dts: gose: Drop MTD partitioning from DT
From: Marek Vasut @ 2018-05-22 12:02 UTC (permalink / raw)
  To: linux-arm-kernel
  Cc: devicetree, Geert Uytterhoeven, Wolfram Sang, linux-renesas-soc,
	Laurent Pinchart, Simon Horman, Marek Vasut
In-Reply-To: <20180522120257.13232-1-marek.vasut+renesas@gmail.com>

Drop the MTD partitioning from DT, since it does not describe HW
and to give way to a more flexible kernel command line partition
passing.

To retain the original partitioning, assure you have enabled
CONFIG_MTD_CMDLINE_PARTS in your kernel config and add the
following to your kernel command line:

  mtdparts=spi0.0:256k@0(loader),4096k(user),-(flash)

Signed-off-by: Marek Vasut <marek.vasut+renesas@gmail.com>
Cc: Geert Uytterhoeven <geert+renesas@glider.be>
Cc: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Cc: Simon Horman <horms+renesas@verge.net.au>
Cc: Wolfram Sang <wsa@the-dreams.de>
Cc: linux-renesas-soc@vger.kernel.org
---
 arch/arm/boot/dts/r8a7793-gose.dts | 21 ---------------------
 1 file changed, 21 deletions(-)

diff --git a/arch/arm/boot/dts/r8a7793-gose.dts b/arch/arm/boot/dts/r8a7793-gose.dts
index aa209f6e5d71..b2dd54fbe40c 100644
--- a/arch/arm/boot/dts/r8a7793-gose.dts
+++ b/arch/arm/boot/dts/r8a7793-gose.dts
@@ -676,27 +676,6 @@
 		spi-cpol;
 		spi-cpha;
 		m25p,fast-read;
-
-		partitions {
-			compatible = "fixed-partitions";
-			#address-cells = <1>;
-			#size-cells = <1>;
-
-			partition@0 {
-				label = "loader";
-				reg = <0x00000000 0x00040000>;
-				read-only;
-			};
-			partition@40000 {
-				label = "user";
-				reg = <0x00040000 0x00400000>;
-				read-only;
-			};
-			partition@440000 {
-				label = "flash";
-				reg = <0x00440000 0x03bc0000>;
-			};
-		};
 	};
 };
 
-- 
2.16.2

^ permalink raw reply related

* [PATCH 8/9] ARM: dts: alt: Drop MTD partitioning from DT
From: Marek Vasut @ 2018-05-22 12:02 UTC (permalink / raw)
  To: linux-arm-kernel
  Cc: devicetree, Geert Uytterhoeven, Wolfram Sang, linux-renesas-soc,
	Laurent Pinchart, Simon Horman, Marek Vasut
In-Reply-To: <20180522120257.13232-1-marek.vasut+renesas@gmail.com>

Drop the MTD partitioning from DT, since it does not describe HW
and to give way to a more flexible kernel command line partition
passing.

To retain the original partitioning, assure you have enabled
CONFIG_MTD_CMDLINE_PARTS in your kernel config and add the
following to your kernel command line:

  mtdparts=spi0.0:256k@0(loader),256k(system),-(user)

Signed-off-by: Marek Vasut <marek.vasut+renesas@gmail.com>
Cc: Geert Uytterhoeven <geert+renesas@glider.be>
Cc: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Cc: Simon Horman <horms+renesas@verge.net.au>
Cc: Wolfram Sang <wsa@the-dreams.de>
Cc: linux-renesas-soc@vger.kernel.org
---
 arch/arm/boot/dts/r8a7794-alt.dts | 21 ---------------------
 1 file changed, 21 deletions(-)

diff --git a/arch/arm/boot/dts/r8a7794-alt.dts b/arch/arm/boot/dts/r8a7794-alt.dts
index e17027532941..1d044ed598f3 100644
--- a/arch/arm/boot/dts/r8a7794-alt.dts
+++ b/arch/arm/boot/dts/r8a7794-alt.dts
@@ -419,26 +419,5 @@
 		spi-cpol;
 		spi-cpha;
 		m25p,fast-read;
-
-		partitions {
-			compatible = "fixed-partitions";
-			#address-cells = <1>;
-			#size-cells = <1>;
-
-			partition@0 {
-				label = "loader";
-				reg = <0x00000000 0x00040000>;
-				read-only;
-			};
-			partition@40000 {
-				label = "system";
-				reg = <0x00040000 0x00040000>;
-				read-only;
-			};
-			partition@80000 {
-				label = "user";
-				reg = <0x00080000 0x03f80000>;
-			};
-		};
 	};
 };
-- 
2.16.2

^ permalink raw reply related

* [PATCH 9/9] ARM: dts: silk: Drop MTD partitioning from DT
From: Marek Vasut @ 2018-05-22 12:02 UTC (permalink / raw)
  To: linux-arm-kernel
  Cc: devicetree, Geert Uytterhoeven, Wolfram Sang, linux-renesas-soc,
	Laurent Pinchart, Simon Horman, Marek Vasut
In-Reply-To: <20180522120257.13232-1-marek.vasut+renesas@gmail.com>

Drop the MTD partitioning from DT, since it does not describe HW
and to give way to a more flexible kernel command line partition
passing.

To retain the original partitioning, assure you have enabled
CONFIG_MTD_CMDLINE_PARTS in your kernel config and add the
following to your kernel command line:

  mtdparts=spi0.0:256k@0(loader),4096k(user),-(flash)

Signed-off-by: Marek Vasut <marek.vasut+renesas@gmail.com>
Cc: Geert Uytterhoeven <geert+renesas@glider.be>
Cc: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Cc: Simon Horman <horms+renesas@verge.net.au>
Cc: Wolfram Sang <wsa@the-dreams.de>
Cc: linux-renesas-soc@vger.kernel.org
---
 arch/arm/boot/dts/r8a7794-silk.dts | 21 ---------------------
 1 file changed, 21 deletions(-)

diff --git a/arch/arm/boot/dts/r8a7794-silk.dts b/arch/arm/boot/dts/r8a7794-silk.dts
index 7808aaee6644..b8163a0e2190 100644
--- a/arch/arm/boot/dts/r8a7794-silk.dts
+++ b/arch/arm/boot/dts/r8a7794-silk.dts
@@ -444,27 +444,6 @@
 		spi-cpol;
 		spi-cpha;
 		m25p,fast-read;
-
-		partitions {
-			compatible = "fixed-partitions";
-			#address-cells = <1>;
-			#size-cells = <1>;
-
-			partition@0 {
-				label = "loader";
-				reg = <0x00000000 0x00040000>;
-				read-only;
-			};
-			partition@40000 {
-				label = "user";
-				reg = <0x00040000 0x00400000>;
-				read-only;
-			};
-			partition@440000 {
-				label = "flash";
-				reg = <0x00440000 0x03bc0000>;
-			};
-		};
 	};
 };
 
-- 
2.16.2

^ permalink raw reply related

* [RESEND PATCH 0/5] mtd: rawnand: add NVIDIA Tegra NAND flash support
From: Stefan Agner @ 2018-05-22 12:07 UTC (permalink / raw)
  To: boris.brezillon, dwmw2, computersforpeace, marek.vasut, robh+dt,
	mark.rutland, thierry.reding, mturquette, sboyd
  Cc: dev, miquel.raynal, richard, marcel, krzk, digetx,
	benjamin.lindqvist, jonathanh, pdeschrijver, pgaikwad, mirza.krak,
	linux-mtd, linux-tegra, devicetree, linux-kernel, linux-clk,
	Stefan Agner

[resending, this time with mailing lists in cc]

This picks up an older patchset written by Lucas Stach which
adds raw NAND flash support for Tegra 2.
http://lists.infradead.org/pipermail/linux-mtd/2015-November/063031.html

The driver has been reworked to implement the ->exec_op callback.
Some smaller changes and bug fixes have been applied too, but I did
not keep track of them. Since the original patchset has been posted
some years back already, I guess review needs to be done from scratch
anyway.

--
Stefan

Lucas Stach (4):
  mtd: rawnand: tegra: add devicetree binding
  clk: tegra20: init NDFLASH clock to sensible rate
  ARM: tegra: add Tegra20 NAND flash controller node
  ARM: tegra: enable NAND flash on Colibri T20

Stefan Agner (1):
  mtd: rawnand: add NVIDIA Tegra NAND Flash controller driver

 .../bindings/mtd/nvidia,tegra20-nand.txt      |  29 +
 MAINTAINERS                                   |   7 +
 arch/arm/boot/dts/tegra20-colibri-512.dtsi    |   7 +
 arch/arm/boot/dts/tegra20.dtsi                |  13 +
 drivers/clk/tegra/clk-tegra20.c               |   1 +
 drivers/mtd/nand/raw/Kconfig                  |   6 +
 drivers/mtd/nand/raw/Makefile                 |   1 +
 drivers/mtd/nand/raw/tegra_nand.c             | 915 ++++++++++++++++++
 8 files changed, 979 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/mtd/nvidia,tegra20-nand.txt
 create mode 100644 drivers/mtd/nand/raw/tegra_nand.c

-- 
2.17.0

^ permalink raw reply

* [RESEND PATCH 1/5] mtd: rawnand: tegra: add devicetree binding
From: Stefan Agner @ 2018-05-22 12:07 UTC (permalink / raw)
  To: boris.brezillon, dwmw2, computersforpeace, marek.vasut, robh+dt,
	mark.rutland, thierry.reding, mturquette, sboyd
  Cc: dev, miquel.raynal, richard, marcel, krzk, digetx,
	benjamin.lindqvist, jonathanh, pdeschrijver, pgaikwad, mirza.krak,
	linux-mtd, linux-tegra, devicetree, linux-kernel, linux-clk,
	Stefan Agner
In-Reply-To: <cover.1526990589.git.stefan@agner.ch>

From: Lucas Stach <dev@lynxeye.de>

This adds the devicetree binding for the Tegra 2 NAND flash
controller.

Signed-off-by: Lucas Stach <dev@lynxeye.de>
Signed-off-by: Stefan Agner <stefan@agner.ch>
---
 .../bindings/mtd/nvidia,tegra20-nand.txt      | 29 +++++++++++++++++++
 1 file changed, 29 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/mtd/nvidia,tegra20-nand.txt

diff --git a/Documentation/devicetree/bindings/mtd/nvidia,tegra20-nand.txt b/Documentation/devicetree/bindings/mtd/nvidia,tegra20-nand.txt
new file mode 100644
index 000000000000..522d442937a9
--- /dev/null
+++ b/Documentation/devicetree/bindings/mtd/nvidia,tegra20-nand.txt
@@ -0,0 +1,29 @@
+NVIDIA Tegra NAND Flash controller
+
+Required properties:
+- compatible: Must be one of:
+  - "nvidia,tegra20-nand"
+- reg: MMIO address range
+- interrupts: interrupt output of the NFC controller
+- clocks: Must contain an entry for each entry in clock-names.
+  See ../clocks/clock-bindings.txt for details.
+- clock-names: Must include the following entries:
+  - nand
+- resets: Must contain an entry for each entry in reset-names.
+  See ../reset/reset.txt for details.
+- reset-names: Must include the following entries:
+  - nand
+
+Optional properties:
+- nvidia,wp-gpios: GPIO used to disable write protection of the flash
+
+  Example:
+	nand@70008000 {
+		compatible = "nvidia,tegra20-nand";
+		reg = <0x70008000 0x100>;
+		interrupts = <GIC_SPI 24 IRQ_TYPE_LEVEL_HIGH>;
+		clocks = <&tegra_car TEGRA20_CLK_NDFLASH>;
+		clock-names = "nand";
+		resets = <&tegra_car 13>;
+		reset-names = "nand";
+	};
-- 
2.17.0

^ permalink raw reply related

* [RESEND PATCH 2/5] mtd: rawnand: add NVIDIA Tegra NAND Flash controller driver
From: Stefan Agner @ 2018-05-22 12:07 UTC (permalink / raw)
  To: boris.brezillon, dwmw2, computersforpeace, marek.vasut, robh+dt,
	mark.rutland, thierry.reding, mturquette, sboyd
  Cc: dev, miquel.raynal, richard, marcel, krzk, digetx,
	benjamin.lindqvist, jonathanh, pdeschrijver, pgaikwad, mirza.krak,
	linux-mtd, linux-tegra, devicetree, linux-kernel, linux-clk,
	Stefan Agner
In-Reply-To: <cover.1526990589.git.stefan@agner.ch>

Add support for the NAND flash controller found on NVIDIA
Tegra 2 SoCs. This implementation does not make use of the
command queue feature. Regular operations/data transfers are
done in PIO mode. Page read/writes with hardware ECC make
use of the DMA for data transfer.

Signed-off-by: Lucas Stach <dev@lynxeye.de>
Signed-off-by: Stefan Agner <stefan@agner.ch>
---
 MAINTAINERS                       |   7 +
 drivers/mtd/nand/raw/Kconfig      |   6 +
 drivers/mtd/nand/raw/Makefile     |   1 +
 drivers/mtd/nand/raw/tegra_nand.c | 915 ++++++++++++++++++++++++++++++
 4 files changed, 929 insertions(+)
 create mode 100644 drivers/mtd/nand/raw/tegra_nand.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 58b9861ccf99..a65739681279 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -13844,6 +13844,13 @@ M:	Laxman Dewangan <ldewangan@nvidia.com>
 S:	Supported
 F:	drivers/input/keyboard/tegra-kbc.c
 
+TEGRA NAND DRIVER
+M:	Stefan Agner <stefan@agner.ch>
+M:	Lucas Stach <dev@lynxeye.de>
+S:	Maintained
+F:	Documentation/devicetree/bindings/mtd/nvidia,tegra20-nand.txt
+F:	drivers/mtd/nand/tegra_nand.c
+
 TEGRA PWM DRIVER
 M:	Thierry Reding <thierry.reding@gmail.com>
 S:	Supported
diff --git a/drivers/mtd/nand/raw/Kconfig b/drivers/mtd/nand/raw/Kconfig
index 19a2b283fbbe..bd56264233ca 100644
--- a/drivers/mtd/nand/raw/Kconfig
+++ b/drivers/mtd/nand/raw/Kconfig
@@ -534,4 +534,10 @@ config MTD_NAND_MTK
 	  Enables support for NAND controller on MTK SoCs.
 	  This controller is found on mt27xx, mt81xx, mt65xx SoCs.
 
+config MTD_NAND_TEGRA
+	tristate "Support for NAND on NVIDIA Tegra"
+	depends on ARCH_TEGRA
+	help
+	  Enables support for NAND flash on NVIDIA Tegra SoC based boards.
+
 endif # MTD_NAND
diff --git a/drivers/mtd/nand/raw/Makefile b/drivers/mtd/nand/raw/Makefile
index 165b7ef9e9a1..d5a5f9832b88 100644
--- a/drivers/mtd/nand/raw/Makefile
+++ b/drivers/mtd/nand/raw/Makefile
@@ -56,6 +56,7 @@ obj-$(CONFIG_MTD_NAND_HISI504)	        += hisi504_nand.o
 obj-$(CONFIG_MTD_NAND_BRCMNAND)		+= brcmnand/
 obj-$(CONFIG_MTD_NAND_QCOM)		+= qcom_nandc.o
 obj-$(CONFIG_MTD_NAND_MTK)		+= mtk_ecc.o mtk_nand.o
+obj-$(CONFIG_MTD_NAND_TEGRA)		+= tegra_nand.o
 
 nand-objs := nand_base.o nand_bbt.o nand_timings.o nand_ids.o
 nand-objs += nand_amd.o
diff --git a/drivers/mtd/nand/raw/tegra_nand.c b/drivers/mtd/nand/raw/tegra_nand.c
new file mode 100644
index 000000000000..fa236e683fb8
--- /dev/null
+++ b/drivers/mtd/nand/raw/tegra_nand.c
@@ -0,0 +1,915 @@
+/*
+ * Copyright (C) 2018 Stefan Agner <stefan@agner.ch>
+ * Copyright (C) 2014-2015 Lucas Stach <dev@lynxeye.de>
+ * Copyright (C) 2012 Avionic Design GmbH
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/clk.h>
+#include <linux/completion.h>
+#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/err.h>
+#include <linux/gpio/consumer.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/mtd/partitions.h>
+#include <linux/mtd/rawnand.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/reset.h>
+
+#define CMD					0x00
+#define   CMD_GO				(1 << 31)
+#define   CMD_CLE				(1 << 30)
+#define   CMD_ALE				(1 << 29)
+#define   CMD_PIO				(1 << 28)
+#define   CMD_TX				(1 << 27)
+#define   CMD_RX				(1 << 26)
+#define   CMD_SEC_CMD				(1 << 25)
+#define   CMD_AFT_DAT				(1 << 24)
+#define   CMD_TRANS_SIZE(x)			(((x - 1) & 0xf) << 20)
+#define   CMD_A_VALID				(1 << 19)
+#define   CMD_B_VALID				(1 << 18)
+#define   CMD_RD_STATUS_CHK			(1 << 17)
+#define   CMD_RBSY_CHK				(1 << 16)
+#define   CMD_CE(x)				(1 << (8 + ((x) & 0x7)))
+#define   CMD_CLE_SIZE(x)			(((x - 1) & 0x3) << 4)
+#define   CMD_ALE_SIZE(x)			(((x - 1) & 0xf) << 0)
+
+#define STATUS					0x04
+
+#define ISR					0x08
+#define   ISR_CORRFAIL_ERR			(1 << 24)
+#define   ISR_UND				(1 << 7)
+#define   ISR_OVR				(1 << 6)
+#define   ISR_CMD_DONE				(1 << 5)
+#define   ISR_ECC_ERR				(1 << 4)
+
+#define IER					0x0c
+#define   IER_ERR_TRIG_VAL(x)			(((x) & 0xf) << 16)
+#define   IER_UND				(1 << 7)
+#define   IER_OVR				(1 << 6)
+#define   IER_CMD_DONE				(1 << 5)
+#define   IER_ECC_ERR				(1 << 4)
+#define   IER_GIE				(1 << 0)
+
+#define CFG					0x10
+#define   CFG_HW_ECC				(1 << 31)
+#define   CFG_ECC_SEL				(1 << 30)
+#define   CFG_ERR_COR				(1 << 29)
+#define   CFG_PIPE_EN				(1 << 28)
+#define   CFG_TVAL_4				(0 << 24)
+#define   CFG_TVAL_6				(1 << 24)
+#define   CFG_TVAL_8				(2 << 24)
+#define   CFG_SKIP_SPARE			(1 << 23)
+#define   CFG_BUS_WIDTH_8			(0 << 21)
+#define   CFG_BUS_WIDTH_16			(1 << 21)
+#define   CFG_COM_BSY				(1 << 20)
+#define   CFG_PS_256				(0 << 16)
+#define   CFG_PS_512				(1 << 16)
+#define   CFG_PS_1024				(2 << 16)
+#define   CFG_PS_2048				(3 << 16)
+#define   CFG_PS_4096				(4 << 16)
+#define   CFG_SKIP_SPARE_SIZE_4			(0 << 14)
+#define   CFG_SKIP_SPARE_SIZE_8			(1 << 14)
+#define   CFG_SKIP_SPARE_SIZE_12		(2 << 14)
+#define   CFG_SKIP_SPARE_SIZE_16		(3 << 14)
+#define   CFG_TAG_BYTE_SIZE(x)			((x) & 0xff)
+
+#define TIMING_1				0x14
+#define   TIMING_TRP_RESP(x)			(((x) & 0xf) << 28)
+#define   TIMING_TWB(x)				(((x) & 0xf) << 24)
+#define   TIMING_TCR_TAR_TRR(x)			(((x) & 0xf) << 20)
+#define   TIMING_TWHR(x)			(((x) & 0xf) << 16)
+#define   TIMING_TCS(x)				(((x) & 0x3) << 14)
+#define   TIMING_TWH(x)				(((x) & 0x3) << 12)
+#define   TIMING_TWP(x)				(((x) & 0xf) <<  8)
+#define   TIMING_TRH(x)				(((x) & 0xf) <<  4)
+#define   TIMING_TRP(x)				(((x) & 0xf) <<  0)
+
+#define RESP					0x18
+
+#define TIMING_2				0x1c
+#define   TIMING_TADL(x)			((x) & 0xf)
+
+#define CMD_1					0x20
+#define CMD_2					0x24
+#define ADDR_1					0x28
+#define ADDR_2					0x2c
+
+#define DMA_CTRL				0x30
+#define   DMA_CTRL_GO				(1 << 31)
+#define   DMA_CTRL_IN				(0 << 30)
+#define   DMA_CTRL_OUT				(1 << 30)
+#define   DMA_CTRL_PERF_EN			(1 << 29)
+#define   DMA_CTRL_IE_DONE			(1 << 28)
+#define   DMA_CTRL_REUSE			(1 << 27)
+#define   DMA_CTRL_BURST_1			(2 << 24)
+#define   DMA_CTRL_BURST_4			(3 << 24)
+#define   DMA_CTRL_BURST_8			(4 << 24)
+#define   DMA_CTRL_BURST_16			(5 << 24)
+#define   DMA_CTRL_IS_DONE			(1 << 20)
+#define   DMA_CTRL_EN_A				(1 <<  2)
+#define   DMA_CTRL_EN_B				(1 <<  1)
+
+#define DMA_CFG_A				0x34
+#define DMA_CFG_B				0x38
+
+#define FIFO_CTRL				0x3c
+#define   FIFO_CTRL_CLR_ALL			(1 << 3)
+
+#define DATA_PTR				0x40
+#define TAG_PTR					0x44
+#define ECC_PTR					0x48
+
+#define DEC_STATUS				0x4c
+#define   DEC_STATUS_A_ECC_FAIL			(1 << 1)
+#define   DEC_STATUS_ERR_COUNT_MASK		0x00ff0000
+#define   DEC_STATUS_ERR_COUNT_SHIFT		16
+
+#define HWSTATUS_CMD				0x50
+#define HWSTATUS_MASK				0x54
+#define   HWSTATUS_RDSTATUS_MASK(x)		(((x) & 0xff) << 24)
+#define   HWSTATUS_RDSTATUS_VALUE(x)		(((x) & 0xff) << 16)
+#define   HWSTATUS_RBSY_MASK(x)			(((x) & 0xff) << 8)
+#define   HWSTATUS_RBSY_VALUE(x)		(((x) & 0xff) << 0)
+
+#define DEC_STAT_RESULT				0xd0
+#define DEC_STAT_BUF				0xd4
+#define   DEC_STAT_BUF_CORR_SEC_FLAG_MASK	0x00ff0000
+#define   DEC_STAT_BUF_CORR_SEC_FLAG_SHIFT	16
+#define   DEC_STAT_BUF_MAX_CORR_CNT_MASK	0x00001f00
+#define   DEC_STAT_BUF_MAX_CORR_CNT_SHIFT	8
+
+struct tegra_nand {
+	void __iomem *regs;
+	struct clk *clk;
+	struct gpio_desc *wp_gpio;
+
+	struct nand_chip chip;
+	struct device *dev;
+
+	struct completion command_complete;
+	struct completion dma_complete;
+	bool last_read_error;
+
+	dma_addr_t data_dma;
+	void *data_buf;
+	dma_addr_t oob_dma;
+	void *oob_buf;
+
+	int cur_chip;
+};
+
+static inline struct tegra_nand *to_tegra_nand(struct mtd_info *mtd)
+{
+	struct nand_chip *chip = mtd_to_nand(mtd);
+
+	return nand_get_controller_data(chip);
+}
+
+static int tegra_nand_ooblayout_16_ecc(struct mtd_info *mtd, int section,
+				       struct mtd_oob_region *oobregion)
+{
+	if (section > 0)
+		return -ERANGE;
+
+	oobregion->offset = 4;
+	oobregion->length = 4;
+
+	return 0;
+}
+
+static int tegra_nand_ooblayout_16_free(struct mtd_info *mtd, int section,
+					struct mtd_oob_region *oobregion)
+{
+	if (section > 0)
+		return -ERANGE;
+
+	oobregion->offset = 8;
+	oobregion->length = 8;
+
+	return 0;
+}
+
+static const struct mtd_ooblayout_ops tegra_nand_oob_16_ops = {
+	.ecc = tegra_nand_ooblayout_16_ecc,
+	.free = tegra_nand_ooblayout_16_free,
+};
+
+static int tegra_nand_ooblayout_64_ecc(struct mtd_info *mtd, int section,
+				       struct mtd_oob_region *oobregion)
+{
+	if (section > 0)
+		return -ERANGE;
+
+	oobregion->offset = 4;
+	oobregion->length = 36;
+
+	return 0;
+}
+
+static int tegra_nand_ooblayout_64_free(struct mtd_info *mtd, int section,
+					struct mtd_oob_region *oobregion)
+{
+	if (section > 0)
+		return -ERANGE;
+
+	oobregion->offset = 40;
+	oobregion->length = 24;
+
+	return 0;
+}
+
+static const struct mtd_ooblayout_ops tegra_nand_oob_64_ops = {
+	.ecc = tegra_nand_ooblayout_64_ecc,
+	.free = tegra_nand_ooblayout_64_free,
+};
+
+static int tegra_nand_ooblayout_128_ecc(struct mtd_info *mtd, int section,
+				       struct mtd_oob_region *oobregion)
+{
+	if (section > 0)
+		return -ERANGE;
+
+	oobregion->offset = 4;
+	oobregion->length = 72;
+
+	return 0;
+}
+
+static int tegra_nand_ooblayout_128_free(struct mtd_info *mtd, int section,
+					struct mtd_oob_region *oobregion)
+{
+	if (section > 0)
+		return -ERANGE;
+
+	oobregion->offset = 76;
+	oobregion->length = 52;
+
+	return 0;
+}
+
+static const struct mtd_ooblayout_ops tegra_nand_oob_128_ops = {
+	.ecc = tegra_nand_ooblayout_128_ecc,
+	.free = tegra_nand_ooblayout_128_free,
+};
+
+static int tegra_nand_ooblayout_224_ecc(struct mtd_info *mtd, int section,
+				       struct mtd_oob_region *oobregion)
+{
+	if (section > 0)
+		return -ERANGE;
+
+	oobregion->offset = 4;
+	oobregion->length = 144;
+
+	return 0;
+}
+
+static int tegra_nand_ooblayout_224_free(struct mtd_info *mtd, int section,
+					struct mtd_oob_region *oobregion)
+{
+	if (section > 0)
+		return -ERANGE;
+
+	oobregion->offset = 148;
+	oobregion->length = 76;
+
+	return 0;
+}
+
+static const struct mtd_ooblayout_ops tegra_nand_oob_224_ops = {
+	.ecc = tegra_nand_ooblayout_224_ecc,
+	.free = tegra_nand_ooblayout_224_free,
+};
+
+static irqreturn_t tegra_nand_irq(int irq, void *data)
+{
+	struct tegra_nand *nand = data;
+	u32 isr, dma;
+
+	isr = readl(nand->regs + ISR);
+	dma = readl(nand->regs + DMA_CTRL);
+	dev_dbg(nand->dev, "isr %08x\n", isr);
+
+	if (!isr && !(dma & DMA_CTRL_IS_DONE))
+		return IRQ_NONE;
+
+	if (isr & ISR_CORRFAIL_ERR)
+		nand->last_read_error = true;
+
+	if (isr & ISR_CMD_DONE)
+		complete(&nand->command_complete);
+
+	if (isr & ISR_UND)
+		dev_dbg(nand->dev, "FIFO underrun\n");
+
+	if (isr & ISR_OVR)
+		dev_dbg(nand->dev, "FIFO overrun\n");
+
+	/* handle DMA interrupts */
+	if (dma & DMA_CTRL_IS_DONE) {
+		writel(dma, nand->regs + DMA_CTRL);
+		complete(&nand->dma_complete);
+	}
+
+	/* clear interrupts */
+	writel(isr, nand->regs + ISR);
+
+	return IRQ_HANDLED;
+}
+
+static int tegra_nand_cmd(struct nand_chip *chip,
+			 const struct nand_subop *subop)
+{
+	const struct nand_op_instr *instr;
+	const struct nand_op_instr *instr_data_in = NULL;
+	struct mtd_info *mtd = nand_to_mtd(chip);
+	struct tegra_nand *nand = to_tegra_nand(mtd);
+	unsigned int op_id = -1, trfr_in_sz = 0, trfr_out_sz = 0, offset = 0;
+	bool first_cmd = true;
+	bool force8bit;
+	u32 cmd = 0;
+	u32 value;
+
+	for (op_id = 0; op_id < subop->ninstrs; op_id++) {
+		unsigned int naddrs, i;
+		const u8 *addrs;
+		u32 addr1 = 0, addr2 = 0;
+
+		instr = &subop->instrs[op_id];
+
+		switch (instr->type) {
+		case NAND_OP_CMD_INSTR:
+			if (first_cmd) {
+				cmd |= CMD_CLE;
+				writel(instr->ctx.cmd.opcode, nand->regs + CMD_1);
+			} else {
+				cmd |= CMD_SEC_CMD;
+				writel(instr->ctx.cmd.opcode, nand->regs + CMD_2);
+			}
+			first_cmd = false;
+			break;
+		case NAND_OP_ADDR_INSTR:
+			offset = nand_subop_get_addr_start_off(subop, op_id);
+			naddrs = nand_subop_get_num_addr_cyc(subop, op_id);
+			addrs = &instr->ctx.addr.addrs[offset];
+
+			cmd |= CMD_ALE | CMD_ALE_SIZE(naddrs);
+			for (i = 0; i < min_t(unsigned int, 4, naddrs); i++)
+				addr1 |= *addrs++ << (8 * i);
+			naddrs -= i;
+			for (i = 0; i < min_t(unsigned int, 4, naddrs); i++)
+				addr2 |= *addrs++ << (8 * i);
+			writel(addr1, nand->regs + ADDR_1);
+			writel(addr2, nand->regs + ADDR_2);
+			break;
+
+		case NAND_OP_DATA_IN_INSTR:
+			trfr_in_sz = nand_subop_get_data_len(subop, op_id);
+			offset = nand_subop_get_data_start_off(subop, op_id);
+
+			cmd |= CMD_TRANS_SIZE(trfr_in_sz) | CMD_PIO | CMD_RX | CMD_A_VALID;
+
+			instr_data_in = instr;
+			break;
+
+		case NAND_OP_DATA_OUT_INSTR:
+			trfr_out_sz = nand_subop_get_data_len(subop, op_id);
+			offset = nand_subop_get_data_start_off(subop, op_id);
+			trfr_out_sz = min_t(size_t, trfr_out_sz, 4);
+
+			cmd |= CMD_TRANS_SIZE(trfr_out_sz) | CMD_PIO | CMD_TX | CMD_A_VALID;
+
+			memcpy(&value, instr->ctx.data.buf.out + offset, trfr_out_sz);
+			writel(value, nand->regs + RESP);
+
+			break;
+		case NAND_OP_WAITRDY_INSTR:
+			cmd |= CMD_RBSY_CHK;
+			break;
+
+		}
+	}
+
+
+	cmd |= CMD_GO | CMD_CE(nand->cur_chip);
+	writel(cmd, nand->regs + CMD);
+	wait_for_completion(&nand->command_complete);
+
+	if (instr_data_in) {
+		u32 value;
+		size_t n = min_t(size_t, trfr_in_sz, 4);
+
+		value = readl(nand->regs + RESP);
+		memcpy(instr_data_in->ctx.data.buf.in + offset, &value, n);
+	}
+
+	return 0;
+}
+
+static const struct nand_op_parser tegra_nand_op_parser = NAND_OP_PARSER(
+	NAND_OP_PARSER_PATTERN(tegra_nand_cmd,
+		NAND_OP_PARSER_PAT_CMD_ELEM(true),
+		NAND_OP_PARSER_PAT_ADDR_ELEM(true, 8),
+		NAND_OP_PARSER_PAT_CMD_ELEM(true),
+		NAND_OP_PARSER_PAT_WAITRDY_ELEM(true)),
+	NAND_OP_PARSER_PATTERN(tegra_nand_cmd,
+		NAND_OP_PARSER_PAT_DATA_OUT_ELEM(false, 4)),
+	NAND_OP_PARSER_PATTERN(tegra_nand_cmd,
+		NAND_OP_PARSER_PAT_CMD_ELEM(true),
+		NAND_OP_PARSER_PAT_ADDR_ELEM(true, 8),
+		NAND_OP_PARSER_PAT_CMD_ELEM(true),
+		NAND_OP_PARSER_PAT_WAITRDY_ELEM(true),
+		NAND_OP_PARSER_PAT_DATA_IN_ELEM(true, 4)),
+	);
+
+static int tegra_nand_exec_op(struct nand_chip *chip,
+			     const struct nand_operation *op,
+			     bool check_only)
+{
+	return nand_op_parser_exec_op(chip, &tegra_nand_op_parser, op,
+				      check_only);
+}
+static void tegra_nand_select_chip(struct mtd_info *mtd, int chip)
+{
+	struct tegra_nand *nand = to_tegra_nand(mtd);
+
+	nand->cur_chip = chip;
+}
+
+static u32 tegra_nand_fill_address(struct mtd_info *mtd, struct nand_chip *chip,
+				   int page)
+{
+	struct tegra_nand *nand = to_tegra_nand(mtd);
+
+	/* Lower 16-bits are column, always 0 */
+	writel(page << 16, nand->regs + ADDR_1);
+
+	if (chip->options & NAND_ROW_ADDR_3) {
+		writel(page >> 16, nand->regs + ADDR_2);
+		return 5;
+	}
+
+	return 4;
+}
+
+static int tegra_nand_read_page(struct mtd_info *mtd, struct nand_chip *chip,
+				uint8_t *buf, int oob_required, int page)
+{
+	struct tegra_nand *nand = to_tegra_nand(mtd);
+	u32 value, addrs;
+
+	writel(NAND_CMD_READ0, nand->regs + CMD_1);
+	writel(NAND_CMD_READSTART, nand->regs + CMD_2);
+
+	addrs = tegra_nand_fill_address(mtd, chip, page);
+
+	value = readl(nand->regs + CFG);
+	value |= CFG_HW_ECC | CFG_ERR_COR;
+	writel(value, nand->regs + CFG);
+
+	writel(mtd->writesize - 1, nand->regs + DMA_CFG_A);
+	writel(nand->data_dma, nand->regs + DATA_PTR);
+
+	if (oob_required) {
+		writel(mtd_ooblayout_count_freebytes(mtd) - 1,
+		       nand->regs + DMA_CFG_B);
+		writel(nand->oob_dma, nand->regs + TAG_PTR);
+	} else {
+		writel(0, nand->regs + DMA_CFG_B);
+		writel(0, nand->regs + TAG_PTR);
+	}
+
+	value = DMA_CTRL_GO | DMA_CTRL_IN | DMA_CTRL_PERF_EN |
+		DMA_CTRL_REUSE | DMA_CTRL_IE_DONE | DMA_CTRL_IS_DONE |
+		DMA_CTRL_BURST_8 | DMA_CTRL_EN_A;
+	if (oob_required)
+		value |= DMA_CTRL_EN_B;
+	writel(value, nand->regs + DMA_CTRL);
+
+	value = CMD_CLE | CMD_ALE | CMD_ALE_SIZE(addrs) | CMD_SEC_CMD |
+		CMD_RBSY_CHK | CMD_GO | CMD_RX | CMD_TRANS_SIZE(9) |
+		CMD_A_VALID | CMD_CE(nand->cur_chip);
+	if (oob_required)
+		value |= CMD_B_VALID;
+	writel(value, nand->regs + CMD);
+
+	wait_for_completion(&nand->command_complete);
+	wait_for_completion(&nand->dma_complete);
+
+	if (oob_required) {
+		struct mtd_oob_region oobregion;
+
+		mtd_ooblayout_free(mtd, 0, &oobregion);
+		memcpy(chip->oob_poi, nand->oob_buf + oobregion.offset,
+		       mtd_ooblayout_count_freebytes(mtd));
+	}
+	memcpy(buf, nand->data_buf, mtd->writesize);
+
+	value = readl(nand->regs + CFG);
+	value &= ~(CFG_HW_ECC | CFG_ERR_COR);
+	writel(value, nand->regs + CFG);
+
+	value = readl(nand->regs + DEC_STATUS);
+	if (value & DEC_STATUS_A_ECC_FAIL) {
+		/*
+		 * The ECC isn't smart enough to figure out if a page is
+		 * completely erased and flags an error in this case. So we
+		 * check the read data here to figure out if it's a legitimate
+		 * error or a false positive.
+		 */
+		int i, err;
+		int flips_threshold = chip->ecc.strength / 2;
+		int max_bitflips = 0;
+
+		for (i = 0; i < chip->ecc.steps; i++) {
+			u8 *data = buf + (chip->ecc.size * i);
+			err = nand_check_erased_ecc_chunk(data, chip->ecc.size,
+							  NULL, 0,
+							  NULL, 0,
+							  flips_threshold);
+			if (err < 0)
+				return err;
+
+			max_bitflips += max_bitflips;
+		}
+
+		return max_bitflips;
+	}
+
+	if (nand->last_read_error) {
+		int max_corr_cnt, corr_sec_flag;
+
+		value = readl(nand->regs + DEC_STAT_BUF);
+		corr_sec_flag = (value & DEC_STAT_BUF_CORR_SEC_FLAG_MASK) >>
+				DEC_STAT_BUF_CORR_SEC_FLAG_SHIFT;
+		max_corr_cnt = (value & DEC_STAT_BUF_MAX_CORR_CNT_MASK) >>
+			       DEC_STAT_BUF_MAX_CORR_CNT_SHIFT;
+
+		/*
+		 * The value returned in the register is the maximum of
+		 * bitflips encountered in any of the ECC regions. As there is
+		 * no way to get the number of bitflips in a specific regions
+		 * we are not able to deliver correct stats but instead
+		 * overestimate the number of corrected bitflips by assuming
+		 * that all regions where errors have been corrected
+		 * encountered the maximum number of bitflips.
+		 */
+		mtd->ecc_stats.corrected += max_corr_cnt * hweight8(corr_sec_flag);
+		nand->last_read_error = false;
+		return value;
+	}
+
+	return 0;
+}
+
+static int tegra_nand_write_page(struct mtd_info *mtd, struct nand_chip *chip,
+				 const uint8_t *buf, int oob_required, int page)
+{
+	struct tegra_nand *nand = to_tegra_nand(mtd);
+	u32 value, addrs;
+
+	writel(NAND_CMD_SEQIN, nand->regs + CMD_1);
+	writel(NAND_CMD_PAGEPROG, nand->regs + CMD_2);
+
+	addrs = tegra_nand_fill_address(mtd, chip, page);
+
+	value = readl(nand->regs + CFG);
+	value |= CFG_HW_ECC | CFG_ERR_COR;
+	writel(value, nand->regs + CFG);
+
+	memcpy(nand->data_buf, buf, mtd->writesize);
+
+	writel(mtd->writesize - 1, nand->regs + DMA_CFG_A);
+	writel(nand->data_dma, nand->regs + DATA_PTR);
+
+	if (oob_required) {
+		struct mtd_oob_region oobregion;
+
+		mtd_ooblayout_free(mtd, 0, &oobregion);
+		memcpy(nand->oob_buf, chip->oob_poi + oobregion.offset,
+		       mtd_ooblayout_count_freebytes(mtd));
+		writel(mtd_ooblayout_count_freebytes(mtd) - 1,
+		       nand->regs + DMA_CFG_B);
+		writel(nand->oob_dma, nand->regs + TAG_PTR);
+	} else {
+		writel(0, nand->regs + DMA_CFG_B);
+		writel(0, nand->regs + TAG_PTR);
+	}
+
+	value = DMA_CTRL_GO | DMA_CTRL_OUT | DMA_CTRL_PERF_EN |
+		DMA_CTRL_IE_DONE | DMA_CTRL_IS_DONE |
+		DMA_CTRL_BURST_8 | DMA_CTRL_EN_A;
+	if (oob_required)
+		value |= DMA_CTRL_EN_B;
+	writel(value, nand->regs + DMA_CTRL);
+
+	value = CMD_CLE | CMD_ALE | CMD_ALE_SIZE(addrs) | CMD_SEC_CMD |
+		CMD_AFT_DAT | CMD_RBSY_CHK | CMD_GO | CMD_TX | CMD_A_VALID |
+		CMD_TRANS_SIZE(9) | CMD_CE(nand->cur_chip);
+	if (oob_required)
+		value |= CMD_B_VALID;
+	writel(value, nand->regs + CMD);
+
+	wait_for_completion(&nand->command_complete);
+	wait_for_completion(&nand->dma_complete);
+
+	value = readl(nand->regs + CFG);
+	value &= ~(CFG_HW_ECC | CFG_ERR_COR);
+	writel(value, nand->regs + CFG);
+
+	return 0;
+}
+
+static void tegra_nand_setup_timing(struct tegra_nand *nand, int mode)
+{
+	/*
+	 * The period (and all other timings in this function) is in ps,
+	 * so need to take care here to avoid integer overflows.
+	 */
+	unsigned int rate = clk_get_rate(nand->clk) / 1000000;
+	unsigned int period = DIV_ROUND_UP(1000000, rate);
+	const struct nand_sdr_timings *timings;
+	u32 val, reg = 0;
+
+	timings = onfi_async_timing_mode_to_sdr_timings(mode);
+
+	val = DIV_ROUND_UP(max3(timings->tAR_min, timings->tRR_min,
+				timings->tRC_min), period);
+	if (val > 2)
+		val -= 3;
+	reg |= TIMING_TCR_TAR_TRR(val);
+
+	val = DIV_ROUND_UP(max(max(timings->tCS_min, timings->tCH_min),
+				   max(timings->tALS_min, timings->tALH_min)),
+			   period);
+	if (val > 1)
+		val -= 2;
+	reg |= TIMING_TCS(val);
+
+	val = DIV_ROUND_UP(max(timings->tRP_min, timings->tREA_max) + 6000,
+			   period);
+	reg |= TIMING_TRP(val) | TIMING_TRP_RESP(val);
+
+	reg |= TIMING_TWB(DIV_ROUND_UP(timings->tWB_max, period));
+	reg |= TIMING_TWHR(DIV_ROUND_UP(timings->tWHR_min, period));
+	reg |= TIMING_TWH(DIV_ROUND_UP(timings->tWH_min, period));
+	reg |= TIMING_TWP(DIV_ROUND_UP(timings->tWP_min, period));
+	reg |= TIMING_TRH(DIV_ROUND_UP(timings->tRHW_min, period));
+
+	writel(reg, nand->regs + TIMING_1);
+
+	val = DIV_ROUND_UP(timings->tADL_min, period);
+	if (val > 2)
+		val -= 3;
+	reg = TIMING_TADL(val);
+
+	writel(reg, nand->regs + TIMING_2);
+}
+
+static void tegra_nand_setup_chiptiming(struct tegra_nand *nand)
+{
+	struct nand_chip *chip = &nand->chip;
+	int mode;
+
+	mode = onfi_get_async_timing_mode(chip);
+	if (mode == ONFI_TIMING_MODE_UNKNOWN)
+		mode = chip->onfi_timing_mode_default;
+	else
+		mode = fls(mode);
+
+	tegra_nand_setup_timing(nand, mode);
+}
+
+static int tegra_nand_probe(struct platform_device *pdev)
+{
+	struct reset_control *rst;
+	struct tegra_nand *nand;
+	struct nand_chip *chip;
+	struct mtd_info *mtd;
+	struct resource *res;
+	unsigned long value;
+	int irq, err = 0;
+
+	nand = devm_kzalloc(&pdev->dev, sizeof(*nand), GFP_KERNEL);
+	if (!nand)
+		return -ENOMEM;
+
+	nand->dev = &pdev->dev;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	nand->regs = devm_ioremap_resource(&pdev->dev, res);
+	if (IS_ERR(nand->regs))
+		return PTR_ERR(nand->regs);
+
+	irq = platform_get_irq(pdev, 0);
+	err = devm_request_irq(&pdev->dev, irq, tegra_nand_irq, 0,
+			       dev_name(&pdev->dev), nand);
+	if (err)
+		return err;
+
+	rst = devm_reset_control_get(&pdev->dev, "nand");
+	if (IS_ERR(rst))
+		return PTR_ERR(rst);
+
+	nand->clk = devm_clk_get(&pdev->dev, "nand");
+	if (IS_ERR(nand->clk))
+		return PTR_ERR(nand->clk);
+
+	nand->wp_gpio = gpiod_get_optional(&pdev->dev, "wp-gpios",
+					   GPIOD_OUT_HIGH);
+	if (IS_ERR(nand->wp_gpio))
+		return PTR_ERR(nand->wp_gpio);
+
+	err = clk_prepare_enable(nand->clk);
+	if (err)
+		return err;
+
+	reset_control_assert(rst);
+	udelay(2);
+	reset_control_deassert(rst);
+
+	value = HWSTATUS_RDSTATUS_MASK(1) | HWSTATUS_RDSTATUS_VALUE(0) |
+		HWSTATUS_RBSY_MASK(NAND_STATUS_READY) |
+		HWSTATUS_RBSY_VALUE(NAND_STATUS_READY);
+	writel(NAND_CMD_STATUS, nand->regs + HWSTATUS_CMD);
+	writel(value, nand->regs + HWSTATUS_MASK);
+
+	init_completion(&nand->command_complete);
+	init_completion(&nand->dma_complete);
+
+	/* clear interrupts */
+	value = readl(nand->regs + ISR);
+	writel(value, nand->regs + ISR);
+
+	writel(DMA_CTRL_IS_DONE, nand->regs + DMA_CTRL);
+
+	/* enable interrupts */
+	value = IER_UND | IER_OVR | IER_CMD_DONE | IER_ECC_ERR | IER_GIE;
+	writel(value, nand->regs + IER);
+
+	/* reset config */
+	writel(0, nand->regs + CFG);
+
+	chip = &nand->chip;
+	mtd = nand_to_mtd(chip);
+
+	mtd->dev.parent = &pdev->dev;
+	mtd->name = "tegra_nand";
+	mtd->owner = THIS_MODULE;
+
+	nand_set_flash_node(chip, pdev->dev.of_node);
+	nand_set_controller_data(chip, nand);
+
+	chip->options = NAND_NO_SUBPAGE_WRITE;
+	chip->exec_op = tegra_nand_exec_op;
+	chip->select_chip = tegra_nand_select_chip;
+	tegra_nand_setup_timing(nand, 0);
+
+	err = nand_scan_ident(mtd, 1, NULL);
+	if (err)
+		goto err_disable_clk;
+
+	if (chip->bbt_options & NAND_BBT_USE_FLASH)
+		chip->bbt_options |= NAND_BBT_NO_OOB;
+
+	nand->data_buf = dmam_alloc_coherent(&pdev->dev, mtd->writesize,
+					    &nand->data_dma, GFP_KERNEL);
+	if (!nand->data_buf) {
+		err = -ENOMEM;
+		goto err_disable_clk;
+	}
+
+	nand->oob_buf = dmam_alloc_coherent(&pdev->dev, mtd->oobsize,
+					    &nand->oob_dma, GFP_KERNEL);
+	if (!nand->oob_buf) {
+		err = -ENOMEM;
+		goto err_disable_clk;
+	}
+
+	chip->ecc.mode = NAND_ECC_HW;
+	chip->ecc.size = 512;
+	chip->ecc.read_page = tegra_nand_read_page;
+	chip->ecc.write_page = tegra_nand_write_page;
+
+	value = readl(nand->regs + CFG);
+	value |= CFG_PIPE_EN | CFG_SKIP_SPARE | CFG_SKIP_SPARE_SIZE_4 |
+		 CFG_TAG_BYTE_SIZE(mtd_ooblayout_count_freebytes(mtd) - 1);
+
+	if (chip->options & NAND_BUSWIDTH_16)
+		value |= CFG_BUS_WIDTH_16;
+
+	switch (mtd->oobsize) {
+	case 16:
+		mtd_set_ooblayout(mtd, &tegra_nand_oob_16_ops);
+		chip->ecc.strength = 1;
+		chip->ecc.bytes = 4;
+		break;
+	case 64:
+		mtd_set_ooblayout(mtd, &tegra_nand_oob_64_ops);
+		chip->ecc.strength = 8;
+		chip->ecc.bytes = 18;
+		value |= CFG_ECC_SEL | CFG_TVAL_8;
+		break;
+	case 128:
+		mtd_set_ooblayout(mtd, &tegra_nand_oob_128_ops);
+		chip->ecc.strength = 8;
+		chip->ecc.bytes = 18;
+		value |= CFG_ECC_SEL | CFG_TVAL_8;
+		break;
+	case 224:
+		mtd_set_ooblayout(mtd, &tegra_nand_oob_224_ops);
+		chip->ecc.strength = 8;
+		chip->ecc.bytes = 18;
+		value |= CFG_ECC_SEL | CFG_TVAL_8;
+		break;
+	default:
+		dev_err(&pdev->dev, "unhandled OOB size %d\n", mtd->oobsize);
+		err = -ENODEV;
+		goto err_disable_clk;
+	}
+
+	switch (mtd->writesize) {
+	case 256:
+		value |= CFG_PS_256;
+		break;
+	case 512:
+		value |= CFG_PS_512;
+		break;
+	case 1024:
+		value |= CFG_PS_1024;
+		break;
+	case 2048:
+		value |= CFG_PS_2048;
+		break;
+	case 4096:
+		value |= CFG_PS_4096;
+		break;
+	default:
+		dev_err(&pdev->dev, "unhandled writesize %d\n", mtd->writesize);
+		err = -ENODEV;
+		goto err_disable_clk;
+	}
+
+	writel(value, nand->regs + CFG);
+
+	tegra_nand_setup_chiptiming(nand);
+
+	err = nand_scan_tail(mtd);
+	if (err)
+		goto err_disable_clk;
+
+	err = mtd_device_register(mtd, NULL, 0);
+	if (err)
+		goto err_cleanup_nand;
+
+	platform_set_drvdata(pdev, nand);
+
+	return 0;
+
+err_cleanup_nand:
+	nand_cleanup(chip);
+err_disable_clk:
+	clk_disable_unprepare(nand->clk);
+	return err;
+}
+
+static int tegra_nand_remove(struct platform_device *pdev)
+{
+	struct tegra_nand *nand = platform_get_drvdata(pdev);
+
+	nand_release(nand_to_mtd(&nand->chip));
+
+	clk_disable_unprepare(nand->clk);
+
+	return 0;
+}
+
+static const struct of_device_id tegra_nand_of_match[] = {
+	{ .compatible = "nvidia,tegra20-nand" },
+	{ /* sentinel */ }
+};
+
+static struct platform_driver tegra_nand_driver = {
+	.driver = {
+		.name = "tegra-nand",
+		.of_match_table = tegra_nand_of_match,
+	},
+	.probe = tegra_nand_probe,
+	.remove = tegra_nand_remove,
+};
+module_platform_driver(tegra_nand_driver);
+
+MODULE_DESCRIPTION("NVIDIA Tegra NAND driver");
+MODULE_AUTHOR("Thierry Reding <thierry.reding@nvidia.com>");
+MODULE_AUTHOR("Lucas Stach <dev@lynxeye.de>");
+MODULE_AUTHOR("Stefan Agner <stefan@agner.ch>");
+MODULE_LICENSE("GPL v2");
+MODULE_DEVICE_TABLE(of, tegra_nand_of_match);
-- 
2.17.0

^ permalink raw reply related

* [RESEND PATCH 3/5] clk: tegra20: init NDFLASH clock to sensible rate
From: Stefan Agner @ 2018-05-22 12:07 UTC (permalink / raw)
  To: boris.brezillon, dwmw2, computersforpeace, marek.vasut, robh+dt,
	mark.rutland, thierry.reding, mturquette, sboyd
  Cc: dev, miquel.raynal, richard, marcel, krzk, digetx,
	benjamin.lindqvist, jonathanh, pdeschrijver, pgaikwad, mirza.krak,
	linux-mtd, linux-tegra, devicetree, linux-kernel, linux-clk,
	Stefan Agner
In-Reply-To: <cover.1526990589.git.stefan@agner.ch>

From: Lucas Stach <dev@lynxeye.de>

Set up the NAND Flash controller clock to run at 150MHz
instead of the rate set by the bootloader. This is a
conservative rate which also yields good performance.

Signed-off-by: Lucas Stach <dev@lynxeye.de>
Signed-off-by: Stefan Agner <stefan@agner.ch>
---
 drivers/clk/tegra/clk-tegra20.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/drivers/clk/tegra/clk-tegra20.c b/drivers/clk/tegra/clk-tegra20.c
index 0ee56dd04cec..dff8c425cd28 100644
--- a/drivers/clk/tegra/clk-tegra20.c
+++ b/drivers/clk/tegra/clk-tegra20.c
@@ -1049,6 +1049,7 @@ static struct tegra_clk_init_table init_table[] __initdata = {
 	{ TEGRA20_CLK_GR2D, TEGRA20_CLK_PLL_C, 300000000, 0 },
 	{ TEGRA20_CLK_GR3D, TEGRA20_CLK_PLL_C, 300000000, 0 },
 	{ TEGRA20_CLK_VDE, TEGRA20_CLK_CLK_MAX, 300000000, 0 },
+	{ TEGRA20_CLK_NDFLASH, TEGRA20_CLK_PLL_P, 150000000, 0 },
 	/* must be the last entry */
 	{ TEGRA20_CLK_CLK_MAX, TEGRA20_CLK_CLK_MAX, 0, 0 },
 };
-- 
2.17.0

^ permalink raw reply related

* [RESEND PATCH 4/5] ARM: tegra: add Tegra20 NAND flash controller node
From: Stefan Agner @ 2018-05-22 12:07 UTC (permalink / raw)
  To: boris.brezillon, dwmw2, computersforpeace, marek.vasut, robh+dt,
	mark.rutland, thierry.reding, mturquette, sboyd
  Cc: dev, miquel.raynal, richard, marcel, krzk, digetx,
	benjamin.lindqvist, jonathanh, pdeschrijver, pgaikwad, mirza.krak,
	linux-mtd, linux-tegra, devicetree, linux-kernel, linux-clk,
	Stefan Agner
In-Reply-To: <cover.1526990589.git.stefan@agner.ch>

From: Lucas Stach <dev@lynxeye.de>

Add basic controller description to be extended
by individual boards.

Signed-off-by: Lucas Stach <dev@lynxeye.de>
Signed-off-by: Stefan Agner <stefan@agner.ch>
---
 arch/arm/boot/dts/tegra20.dtsi | 13 +++++++++++++
 1 file changed, 13 insertions(+)

diff --git a/arch/arm/boot/dts/tegra20.dtsi b/arch/arm/boot/dts/tegra20.dtsi
index 0a7136462a1a..5772d0800ad7 100644
--- a/arch/arm/boot/dts/tegra20.dtsi
+++ b/arch/arm/boot/dts/tegra20.dtsi
@@ -425,6 +425,19 @@
 		status = "disabled";
 	};
 
+	nand: nand@70008000 {
+		compatible = "nvidia,tegra20-nand";
+		reg = <0x70008000 0x100>;
+		interrupts = <GIC_SPI 24 IRQ_TYPE_LEVEL_HIGH>;
+		clocks = <&tegra_car TEGRA20_CLK_NDFLASH>;
+		clock-names = "nand";
+		resets = <&tegra_car 13>;
+		reset-names = "nand";
+		status = "disabled";
+		#address-cells = <1>;
+		#size-cells = <1>;
+	};
+
 	pwm: pwm@7000a000 {
 		compatible = "nvidia,tegra20-pwm";
 		reg = <0x7000a000 0x100>;
-- 
2.17.0

^ permalink raw reply related

* [RESEND PATCH 5/5] ARM: tegra: enable NAND flash on Colibri T20
From: Stefan Agner @ 2018-05-22 12:07 UTC (permalink / raw)
  To: boris.brezillon, dwmw2, computersforpeace, marek.vasut, robh+dt,
	mark.rutland, thierry.reding, mturquette, sboyd
  Cc: dev, miquel.raynal, richard, marcel, krzk, digetx,
	benjamin.lindqvist, jonathanh, pdeschrijver, pgaikwad, mirza.krak,
	linux-mtd, linux-tegra, devicetree, linux-kernel, linux-clk,
	Stefan Agner
In-Reply-To: <cover.1526990589.git.stefan@agner.ch>

From: Lucas Stach <dev@lynxeye.de>

This enables the on-module ONFI conformant NAND flash.

Signed-off-by: Lucas Stach <dev@lynxeye.de>
Signed-off-by: Stefan Agner <stefan@agner.ch>
---
 arch/arm/boot/dts/tegra20-colibri-512.dtsi | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/arch/arm/boot/dts/tegra20-colibri-512.dtsi b/arch/arm/boot/dts/tegra20-colibri-512.dtsi
index 5c202b3e3bb1..6e5dd31c4036 100644
--- a/arch/arm/boot/dts/tegra20-colibri-512.dtsi
+++ b/arch/arm/boot/dts/tegra20-colibri-512.dtsi
@@ -462,6 +462,13 @@
 		};
 	};
 
+	nand@70008000 {
+		status = "okay";
+		nand-bus-width = <8>;
+		nand-on-flash-bbt;
+		wp-gpios = <&gpio TEGRA_GPIO(S, 0) GPIO_ACTIVE_HIGH>;
+	};
+
 	usb@c5004000 {
 		status = "okay";
 		nvidia,phy-reset-gpio = <&gpio TEGRA_GPIO(V, 1)
-- 
2.17.0

^ permalink raw reply related

* Re: [RESEND PATCH 2/5] mtd: rawnand: add NVIDIA Tegra NAND Flash controller driver
From: Dmitry Osipenko @ 2018-05-22 12:16 UTC (permalink / raw)
  To: Stefan Agner, boris.brezillon, dwmw2, computersforpeace,
	marek.vasut, robh+dt, mark.rutland, thierry.reding, mturquette,
	sboyd
  Cc: dev, miquel.raynal, richard, marcel, krzk, benjamin.lindqvist,
	jonathanh, pdeschrijver, pgaikwad, mirza.krak, linux-mtd,
	linux-tegra, devicetree, linux-kernel, linux-clk
In-Reply-To: <86fdf19ec92b732709732fb60199f16488b4b727.1526990589.git.stefan@agner.ch>

[Re-sending the review I made before of the series RESEND]

> Hello Stefan,
> 
> I don't have expertise to review the actual NAND-related driver logic, so I only
> reviewed the basics. The driver code looks good to me, though I've couple minor
> comments.
> 
> On 21.05.2018 03:16, Stefan Agner wrote:
>> Add support for the NAND flash controller found on NVIDIA
>> Tegra 2 SoCs. This implementation does not make use of the
>> command queue feature. Regular operations/data transfers are
>> done in PIO mode. Page read/writes with hardware ECC make
>> use of the DMA for data transfer.
>> 
>> Signed-off-by: Lucas Stach <dev@lynxeye.de>
>> Signed-off-by: Stefan Agner <stefan@agner.ch>
>> ---
>>  MAINTAINERS                       |   7 +
>>  drivers/mtd/nand/raw/Kconfig      |   6 +
>>  drivers/mtd/nand/raw/Makefile     |   1 +
>>  drivers/mtd/nand/raw/tegra_nand.c | 915 ++++++++++++++++++++++++++++++
>>  4 files changed, 929 insertions(+)
>>  create mode 100644 drivers/mtd/nand/raw/tegra_nand.c
>> 
>> diff --git a/MAINTAINERS b/MAINTAINERS
>> index 58b9861ccf99..a65739681279 100644
>> --- a/MAINTAINERS
>> +++ b/MAINTAINERS
>> @@ -13844,6 +13844,13 @@ M:	Laxman Dewangan <ldewangan@nvidia.com>
>>  S:	Supported
>>  F:	drivers/input/keyboard/tegra-kbc.c
>>  
>> +TEGRA NAND DRIVER
>> +M:	Stefan Agner <stefan@agner.ch>
>> +M:	Lucas Stach <dev@lynxeye.de>
>> +S:	Maintained
>> +F:	Documentation/devicetree/bindings/mtd/nvidia,tegra20-nand.txt
>> +F:	drivers/mtd/nand/tegra_nand.c
>> +
>>  TEGRA PWM DRIVER
>>  M:	Thierry Reding <thierry.reding@gmail.com>
>>  S:	Supported
>> diff --git a/drivers/mtd/nand/raw/Kconfig b/drivers/mtd/nand/raw/Kconfig
>> index 19a2b283fbbe..bd56264233ca 100644
>> --- a/drivers/mtd/nand/raw/Kconfig
>> +++ b/drivers/mtd/nand/raw/Kconfig
>> @@ -534,4 +534,10 @@ config MTD_NAND_MTK
>>  	  Enables support for NAND controller on MTK SoCs.
>>  	  This controller is found on mt27xx, mt81xx, mt65xx SoCs.
>>  
>> +config MTD_NAND_TEGRA
>> +	tristate "Support for NAND on NVIDIA Tegra"
>> +	depends on ARCH_TEGRA
>> +	help
>> +	  Enables support for NAND flash on NVIDIA Tegra SoC based boards.
>> +
>>  endif # MTD_NAND
>> diff --git a/drivers/mtd/nand/raw/Makefile b/drivers/mtd/nand/raw/Makefile
>> index 165b7ef9e9a1..d5a5f9832b88 100644
>> --- a/drivers/mtd/nand/raw/Makefile
>> +++ b/drivers/mtd/nand/raw/Makefile
>> @@ -56,6 +56,7 @@ obj-$(CONFIG_MTD_NAND_HISI504)	        += hisi504_nand.o
>>  obj-$(CONFIG_MTD_NAND_BRCMNAND)		+= brcmnand/
>>  obj-$(CONFIG_MTD_NAND_QCOM)		+= qcom_nandc.o
>>  obj-$(CONFIG_MTD_NAND_MTK)		+= mtk_ecc.o mtk_nand.o
>> +obj-$(CONFIG_MTD_NAND_TEGRA)		+= tegra_nand.o
>>  
>>  nand-objs := nand_base.o nand_bbt.o nand_timings.o nand_ids.o
>>  nand-objs += nand_amd.o
>> diff --git a/drivers/mtd/nand/raw/tegra_nand.c b/drivers/mtd/nand/raw/tegra_nand.c
>> new file mode 100644
>> index 000000000000..fa236e683fb8
>> --- /dev/null
>> +++ b/drivers/mtd/nand/raw/tegra_nand.c
>> @@ -0,0 +1,915 @@
>> +/*
>> + * Copyright (C) 2018 Stefan Agner <stefan@agner.ch>
>> + * Copyright (C) 2014-2015 Lucas Stach <dev@lynxeye.de>
>> + * Copyright (C) 2012 Avionic Design GmbH
>> + *
>> + * This program is free software; you can redistribute it and/or modify
>> + * it under the terms of the GNU General Public License version 2 as
>> + * published by the Free Software Foundation.
>> + */
>> +
>> +#include <linux/clk.h>
>> +#include <linux/completion.h>
>> +#include <linux/delay.h>
>> +#include <linux/dma-mapping.h>
>> +#include <linux/err.h>
>> +#include <linux/gpio/consumer.h>
>> +#include <linux/interrupt.h>
>> +#include <linux/io.h>
>> +#include <linux/module.h>
>> +#include <linux/mtd/partitions.h>
>> +#include <linux/mtd/rawnand.h>
>> +#include <linux/of.h>
>> +#include <linux/platform_device.h>> +#include <linux/reset.h>
>> +
>> +#define CMD					0x00
>> +#define   CMD_GO				(1 << 31)
>> +#define   CMD_CLE				(1 << 30)
>> +#define   CMD_ALE				(1 << 29)
>> +#define   CMD_PIO				(1 << 28)
>> +#define   CMD_TX				(1 << 27)
>> +#define   CMD_RX				(1 << 26)
>> +#define   CMD_SEC_CMD				(1 << 25)
>> +#define   CMD_AFT_DAT				(1 << 24)
>> +#define   CMD_TRANS_SIZE(x)			(((x - 1) & 0xf) << 20)
>> +#define   CMD_A_VALID				(1 << 19)
>> +#define   CMD_B_VALID				(1 << 18)
>> +#define   CMD_RD_STATUS_CHK			(1 << 17)
>> +#define   CMD_RBSY_CHK				(1 << 16)
>> +#define   CMD_CE(x)				(1 << (8 + ((x) & 0x7)))
>> +#define   CMD_CLE_SIZE(x)			(((x - 1) & 0x3) << 4)
>> +#define   CMD_ALE_SIZE(x)			(((x - 1) & 0xf) << 0)
>> +
>> +#define STATUS					0x04
>> +
>> +#define ISR					0x08
>> +#define   ISR_CORRFAIL_ERR			(1 << 24)
>> +#define   ISR_UND				(1 << 7)
>> +#define   ISR_OVR				(1 << 6)
>> +#define   ISR_CMD_DONE				(1 << 5)
>> +#define   ISR_ECC_ERR				(1 << 4)
>> +
>> +#define IER					0x0c
>> +#define   IER_ERR_TRIG_VAL(x)			(((x) & 0xf) << 16)
>> +#define   IER_UND				(1 << 7)
>> +#define   IER_OVR				(1 << 6)
>> +#define   IER_CMD_DONE				(1 << 5)
>> +#define   IER_ECC_ERR				(1 << 4)
>> +#define   IER_GIE				(1 << 0)
>> +
>> +#define CFG					0x10
>> +#define   CFG_HW_ECC				(1 << 31)
>> +#define   CFG_ECC_SEL				(1 << 30)
>> +#define   CFG_ERR_COR				(1 << 29)
>> +#define   CFG_PIPE_EN				(1 << 28)
>> +#define   CFG_TVAL_4				(0 << 24)
>> +#define   CFG_TVAL_6				(1 << 24)
>> +#define   CFG_TVAL_8				(2 << 24)
>> +#define   CFG_SKIP_SPARE			(1 << 23)
>> +#define   CFG_BUS_WIDTH_8			(0 << 21)
>> +#define   CFG_BUS_WIDTH_16			(1 << 21)
>> +#define   CFG_COM_BSY				(1 << 20)
>> +#define   CFG_PS_256				(0 << 16)
>> +#define   CFG_PS_512				(1 << 16)
>> +#define   CFG_PS_1024				(2 << 16)
>> +#define   CFG_PS_2048				(3 << 16)
>> +#define   CFG_PS_4096				(4 << 16)
>> +#define   CFG_SKIP_SPARE_SIZE_4			(0 << 14)
>> +#define   CFG_SKIP_SPARE_SIZE_8			(1 << 14)
>> +#define   CFG_SKIP_SPARE_SIZE_12		(2 << 14)
>> +#define   CFG_SKIP_SPARE_SIZE_16		(3 << 14)
>> +#define   CFG_TAG_BYTE_SIZE(x)			((x) & 0xff)
>> +
>> +#define TIMING_1				0x14
>> +#define   TIMING_TRP_RESP(x)			(((x) & 0xf) << 28)
>> +#define   TIMING_TWB(x)				(((x) & 0xf) << 24)
>> +#define   TIMING_TCR_TAR_TRR(x)			(((x) & 0xf) << 20)
>> +#define   TIMING_TWHR(x)			(((x) & 0xf) << 16)
>> +#define   TIMING_TCS(x)				(((x) & 0x3) << 14)
>> +#define   TIMING_TWH(x)				(((x) & 0x3) << 12)
>> +#define   TIMING_TWP(x)				(((x) & 0xf) <<  8)
>> +#define   TIMING_TRH(x)				(((x) & 0xf) <<  4)
>> +#define   TIMING_TRP(x)				(((x) & 0xf) <<  0)
>> +
>> +#define RESP					0x18
>> +
>> +#define TIMING_2				0x1c
>> +#define   TIMING_TADL(x)			((x) & 0xf)
>> +
>> +#define CMD_1					0x20
>> +#define CMD_2					0x24
>> +#define ADDR_1					0x28
>> +#define ADDR_2					0x2c
>> +
>> +#define DMA_CTRL				0x30
>> +#define   DMA_CTRL_GO				(1 << 31)
>> +#define   DMA_CTRL_IN				(0 << 30)
>> +#define   DMA_CTRL_OUT				(1 << 30)
>> +#define   DMA_CTRL_PERF_EN			(1 << 29)
>> +#define   DMA_CTRL_IE_DONE			(1 << 28)
>> +#define   DMA_CTRL_REUSE			(1 << 27)
>> +#define   DMA_CTRL_BURST_1			(2 << 24)
>> +#define   DMA_CTRL_BURST_4			(3 << 24)
>> +#define   DMA_CTRL_BURST_8			(4 << 24)
>> +#define   DMA_CTRL_BURST_16			(5 << 24)
>> +#define   DMA_CTRL_IS_DONE			(1 << 20)
>> +#define   DMA_CTRL_EN_A				(1 <<  2)
>> +#define   DMA_CTRL_EN_B				(1 <<  1)
>> +
>> +#define DMA_CFG_A				0x34
>> +#define DMA_CFG_B				0x38
>> +
>> +#define FIFO_CTRL				0x3c
>> +#define   FIFO_CTRL_CLR_ALL			(1 << 3)
>> +
>> +#define DATA_PTR				0x40
>> +#define TAG_PTR					0x44
>> +#define ECC_PTR					0x48
>> +
>> +#define DEC_STATUS				0x4c
>> +#define   DEC_STATUS_A_ECC_FAIL			(1 << 1)
>> +#define   DEC_STATUS_ERR_COUNT_MASK		0x00ff0000
>> +#define   DEC_STATUS_ERR_COUNT_SHIFT		16
>> +
>> +#define HWSTATUS_CMD				0x50
>> +#define HWSTATUS_MASK				0x54
>> +#define   HWSTATUS_RDSTATUS_MASK(x)		(((x) & 0xff) << 24)
>> +#define   HWSTATUS_RDSTATUS_VALUE(x)		(((x) & 0xff) << 16)
>> +#define   HWSTATUS_RBSY_MASK(x)			(((x) & 0xff) << 8)
>> +#define   HWSTATUS_RBSY_VALUE(x)		(((x) & 0xff) << 0)
>> +
>> +#define DEC_STAT_RESULT				0xd0
>> +#define DEC_STAT_BUF				0xd4
>> +#define   DEC_STAT_BUF_CORR_SEC_FLAG_MASK	0x00ff0000
>> +#define   DEC_STAT_BUF_CORR_SEC_FLAG_SHIFT	16
>> +#define   DEC_STAT_BUF_MAX_CORR_CNT_MASK	0x00001f00
>> +#define   DEC_STAT_BUF_MAX_CORR_CNT_SHIFT	8
>> +
>> +struct tegra_nand {
>> +	void __iomem *regs;
>> +	struct clk *clk;
>> +	struct gpio_desc *wp_gpio;
>> +
>> +	struct nand_chip chip;
>> +	struct device *dev;
>> +
>> +	struct completion command_complete;
>> +	struct completion dma_complete;
>> +	bool last_read_error;
>> +
>> +	dma_addr_t data_dma;
>> +	void *data_buf;
>> +	dma_addr_t oob_dma;
>> +	void *oob_buf;
>> +
>> +	int cur_chip;
>> +};
>> +
>> +static inline struct tegra_nand *to_tegra_nand(struct mtd_info *mtd)
>> +{
>> +	struct nand_chip *chip = mtd_to_nand(mtd);
>> +
>> +	return nand_get_controller_data(chip);
>> +}
>> +
>> +static int tegra_nand_ooblayout_16_ecc(struct mtd_info *mtd, int section,
>> +				       struct mtd_oob_region *oobregion)
>> +{
>> +	if (section > 0)
>> +		return -ERANGE;
>> +
>> +	oobregion->offset = 4;
>> +	oobregion->length = 4;
>> +
>> +	return 0;
>> +}
>> +
>> +static int tegra_nand_ooblayout_16_free(struct mtd_info *mtd, int section,
>> +					struct mtd_oob_region *oobregion)
>> +{
>> +	if (section > 0)
>> +		return -ERANGE;
>> +
>> +	oobregion->offset = 8;
>> +	oobregion->length = 8;
>> +
>> +	return 0;
>> +}
>> +
>> +static const struct mtd_ooblayout_ops tegra_nand_oob_16_ops = {
>> +	.ecc = tegra_nand_ooblayout_16_ecc,
>> +	.free = tegra_nand_ooblayout_16_free,
>> +};
>> +
>> +static int tegra_nand_ooblayout_64_ecc(struct mtd_info *mtd, int section,
>> +				       struct mtd_oob_region *oobregion)
>> +{
>> +	if (section > 0)
>> +		return -ERANGE;
>> +
>> +	oobregion->offset = 4;
>> +	oobregion->length = 36;
>> +
>> +	return 0;
>> +}
>> +
>> +static int tegra_nand_ooblayout_64_free(struct mtd_info *mtd, int section,
>> +					struct mtd_oob_region *oobregion)
>> +{
>> +	if (section > 0)
>> +		return -ERANGE;
>> +
>> +	oobregion->offset = 40;
>> +	oobregion->length = 24;
>> +
>> +	return 0;
>> +}
>> +
>> +static const struct mtd_ooblayout_ops tegra_nand_oob_64_ops = {
>> +	.ecc = tegra_nand_ooblayout_64_ecc,
>> +	.free = tegra_nand_ooblayout_64_free,
>> +};
>> +
>> +static int tegra_nand_ooblayout_128_ecc(struct mtd_info *mtd, int section,
>> +				       struct mtd_oob_region *oobregion)
>> +{
>> +	if (section > 0)
>> +		return -ERANGE;
>> +
>> +	oobregion->offset = 4;
>> +	oobregion->length = 72;
>> +
>> +	return 0;
>> +}
>> +
>> +static int tegra_nand_ooblayout_128_free(struct mtd_info *mtd, int section,
>> +					struct mtd_oob_region *oobregion)
>> +{
>> +	if (section > 0)
>> +		return -ERANGE;
>> +
>> +	oobregion->offset = 76;
>> +	oobregion->length = 52;
>> +
>> +	return 0;
>> +}
>> +
>> +static const struct mtd_ooblayout_ops tegra_nand_oob_128_ops = {
>> +	.ecc = tegra_nand_ooblayout_128_ecc,
>> +	.free = tegra_nand_ooblayout_128_free,
>> +};
>> +
>> +static int tegra_nand_ooblayout_224_ecc(struct mtd_info *mtd, int section,
>> +				       struct mtd_oob_region *oobregion)
>> +{
>> +	if (section > 0)
>> +		return -ERANGE;
>> +
>> +	oobregion->offset = 4;
>> +	oobregion->length = 144;
>> +
>> +	return 0;
>> +}
>> +
>> +static int tegra_nand_ooblayout_224_free(struct mtd_info *mtd, int section,
>> +					struct mtd_oob_region *oobregion)
>> +{
>> +	if (section > 0)
>> +		return -ERANGE;
>> +
>> +	oobregion->offset = 148;
>> +	oobregion->length = 76;
>> +
>> +	return 0;
>> +}
>> +
>> +static const struct mtd_ooblayout_ops tegra_nand_oob_224_ops = {
>> +	.ecc = tegra_nand_ooblayout_224_ecc,
>> +	.free = tegra_nand_ooblayout_224_free,
>> +};
>> +
>> +static irqreturn_t tegra_nand_irq(int irq, void *data)
>> +{
>> +	struct tegra_nand *nand = data;
>> +	u32 isr, dma;
>> +
>> +	isr = readl(nand->regs + ISR);
>> +	dma = readl(nand->regs + DMA_CTRL);
> 
> You could use readl_relaxed() here.
> 
>> +	dev_dbg(nand->dev, "isr %08x\n", isr);
>> +
>> +	if (!isr && !(dma & DMA_CTRL_IS_DONE))
>> +		return IRQ_NONE;
>> +
>> +	if (isr & ISR_CORRFAIL_ERR)
>> +		nand->last_read_error = true;
>> +
>> +	if (isr & ISR_CMD_DONE)
>> +		complete(&nand->command_complete);
>> +
>> +	if (isr & ISR_UND)
>> +		dev_dbg(nand->dev, "FIFO underrun\n");
>> +
>> +	if (isr & ISR_OVR)
>> +		dev_dbg(nand->dev, "FIFO overrun\n");
>> +
>> +	/* handle DMA interrupts */
>> +	if (dma & DMA_CTRL_IS_DONE) {
>> +		writel(dma, nand->regs + DMA_CTRL);
>> +		complete(&nand->dma_complete);
>> +	}
>> +
>> +	/* clear interrupts */
>> +	writel(isr, nand->regs + ISR);
>> +
>> +	return IRQ_HANDLED;
>> +}
>> +
>> +static int tegra_nand_cmd(struct nand_chip *chip,
>> +			 const struct nand_subop *subop)
>> +{
>> +	const struct nand_op_instr *instr;
>> +	const struct nand_op_instr *instr_data_in = NULL;
>> +	struct mtd_info *mtd = nand_to_mtd(chip);
>> +	struct tegra_nand *nand = to_tegra_nand(mtd);
>> +	unsigned int op_id = -1, trfr_in_sz = 0, trfr_out_sz = 0, offset = 0;
>> +	bool first_cmd = true;
>> +	bool force8bit;
> 
> The force8bit variable isn't used anywhere in the code, so compiler should warn
> you about the "unused variable", is it the case?
> 
>> +	u32 cmd = 0;
>> +	u32 value;
>> +
> 
> The op_id=-1 above is probably because the loop below was:
> 
> 	while (++op_id < subop->ninstrs) {}
> 
> Both variants are fine, but the for-loop is a bit more explicit. You could omit
> the above op_id variable initialization for consistency.
> 
>> +	for (op_id = 0; op_id < subop->ninstrs; op_id++) {
>> +		unsigned int naddrs, i;
>> +		const u8 *addrs;
>> +		u32 addr1 = 0, addr2 = 0;
>> +
>> +		instr = &subop->instrs[op_id];
>> +
>> +		switch (instr->type) {
>> +		case NAND_OP_CMD_INSTR:
>> +			if (first_cmd) {
>> +				cmd |= CMD_CLE;
>> +				writel(instr->ctx.cmd.opcode, nand->regs + CMD_1);
>> +			} else {
>> +				cmd |= CMD_SEC_CMD;
>> +				writel(instr->ctx.cmd.opcode, nand->regs + CMD_2);
>> +			}
>> +			first_cmd = false;
>> +			break;
>> +		case NAND_OP_ADDR_INSTR:
>> +			offset = nand_subop_get_addr_start_off(subop, op_id);
>> +			naddrs = nand_subop_get_num_addr_cyc(subop, op_id);
>> +			addrs = &instr->ctx.addr.addrs[offset];
>> +
>> +			cmd |= CMD_ALE | CMD_ALE_SIZE(naddrs);
>> +			for (i = 0; i < min_t(unsigned int, 4, naddrs); i++)
>> +				addr1 |= *addrs++ << (8 * i);
>> +			naddrs -= i;
>> +			for (i = 0; i < min_t(unsigned int, 4, naddrs); i++)
>> +				addr2 |= *addrs++ << (8 * i);
>> +			writel(addr1, nand->regs + ADDR_1);
>> +			writel(addr2, nand->regs + ADDR_2);
>> +			break;
>> +
>> +		case NAND_OP_DATA_IN_INSTR:
>> +			trfr_in_sz = nand_subop_get_data_len(subop, op_id);
>> +			offset = nand_subop_get_data_start_off(subop, op_id);
>> +
>> +			cmd |= CMD_TRANS_SIZE(trfr_in_sz) | CMD_PIO | CMD_RX | CMD_A_VALID;
>> +
>> +			instr_data_in = instr;
>> +			break;
>> +
>> +		case NAND_OP_DATA_OUT_INSTR:
>> +			trfr_out_sz = nand_subop_get_data_len(subop, op_id);
>> +			offset = nand_subop_get_data_start_off(subop, op_id);
>> +			trfr_out_sz = min_t(size_t, trfr_out_sz, 4);
>> +
>> +			cmd |= CMD_TRANS_SIZE(trfr_out_sz) | CMD_PIO | CMD_TX | CMD_A_VALID;
>> +
>> +			memcpy(&value, instr->ctx.data.buf.out + offset, trfr_out_sz)> +			writel(value, nand->regs + RESP);
> 
> Note:
> 
> The memcpy + readl / writel won't work with a big-endian kernel,
> cpu_to_[bl]e32() should be applied in a such cases.
> 
> Tegra's I2C driver had a similar memcpy-case issue, it was corrected during the
> attempt to get BE kernel support for Tegra. But that attempt was quite long time
> ago and Tegra maintainers were not very excited to have to do more testing work
> for each kernel release and hence Tegra is LE-only as of today.
> 
>> +
>> +			break;
>> +		case NAND_OP_WAITRDY_INSTR:
>> +			cmd |= CMD_RBSY_CHK;
>> +			break;
>> +
>> +		}
>> +	}
>> +
>> +
>> +	cmd |= CMD_GO | CMD_CE(nand->cur_chip);
>> +	writel(cmd, nand->regs + CMD);
>> +	wait_for_completion(&nand->command_complete);
>> +
>> +	if (instr_data_in) {
>> +		u32 value;
>> +		size_t n = min_t(size_t, trfr_in_sz, 4);
>> +
>> +		value = readl(nand->regs + RESP);
>> +		memcpy(instr_data_in->ctx.data.buf.in + offset, &value, n);
>> +	}
>> +
>> +	return 0;
>> +}
>> +
>> +static const struct nand_op_parser tegra_nand_op_parser = NAND_OP_PARSER(
>> +	NAND_OP_PARSER_PATTERN(tegra_nand_cmd,
>> +		NAND_OP_PARSER_PAT_CMD_ELEM(true),
>> +		NAND_OP_PARSER_PAT_ADDR_ELEM(true, 8),
>> +		NAND_OP_PARSER_PAT_CMD_ELEM(true),
>> +		NAND_OP_PARSER_PAT_WAITRDY_ELEM(true)),
>> +	NAND_OP_PARSER_PATTERN(tegra_nand_cmd,
>> +		NAND_OP_PARSER_PAT_DATA_OUT_ELEM(false, 4)),
>> +	NAND_OP_PARSER_PATTERN(tegra_nand_cmd,
>> +		NAND_OP_PARSER_PAT_CMD_ELEM(true),
>> +		NAND_OP_PARSER_PAT_ADDR_ELEM(true, 8),
>> +		NAND_OP_PARSER_PAT_CMD_ELEM(true),
>> +		NAND_OP_PARSER_PAT_WAITRDY_ELEM(true),
>> +		NAND_OP_PARSER_PAT_DATA_IN_ELEM(true, 4)),
>> +	);
>> +
>> +static int tegra_nand_exec_op(struct nand_chip *chip,
>> +			     const struct nand_operation *op,
>> +			     bool check_only)
>> +{
>> +	return nand_op_parser_exec_op(chip, &tegra_nand_op_parser, op,
>> +				      check_only);
>> +}
>> +static void tegra_nand_select_chip(struct mtd_info *mtd, int chip)
>> +{
>> +	struct tegra_nand *nand = to_tegra_nand(mtd);
>> +
>> +	nand->cur_chip = chip;
>> +}
>> +
>> +static u32 tegra_nand_fill_address(struct mtd_info *mtd, struct nand_chip *chip,
>> +				   int page)
>> +{
>> +	struct tegra_nand *nand = to_tegra_nand(mtd);
>> +
>> +	/* Lower 16-bits are column, always 0 */
>> +	writel(page << 16, nand->regs + ADDR_1);
>> +
>> +	if (chip->options & NAND_ROW_ADDR_3) {
>> +		writel(page >> 16, nand->regs + ADDR_2);
>> +		return 5;
>> +	}
>> +
>> +	return 4;
>> +}
>> +
>> +static int tegra_nand_read_page(struct mtd_info *mtd, struct nand_chip *chip,
>> +				uint8_t *buf, int oob_required, int page)
>> +{
>> +	struct tegra_nand *nand = to_tegra_nand(mtd);
>> +	u32 value, addrs;
>> +
>> +	writel(NAND_CMD_READ0, nand->regs + CMD_1);
>> +	writel(NAND_CMD_READSTART, nand->regs + CMD_2);
>> +
>> +	addrs = tegra_nand_fill_address(mtd, chip, page);
>> +
>> +	value = readl(nand->regs + CFG);
>> +	value |= CFG_HW_ECC | CFG_ERR_COR;
>> +	writel(value, nand->regs + CFG);
>> +
>> +	writel(mtd->writesize - 1, nand->regs + DMA_CFG_A);
>> +	writel(nand->data_dma, nand->regs + DATA_PTR);
>> +
>> +	if (oob_required) {
>> +		writel(mtd_ooblayout_count_freebytes(mtd) - 1,
>> +		       nand->regs + DMA_CFG_B);
>> +		writel(nand->oob_dma, nand->regs + TAG_PTR);
>> +	} else {
>> +		writel(0, nand->regs + DMA_CFG_B);
>> +		writel(0, nand->regs + TAG_PTR);
>> +	}
>> +
>> +	value = DMA_CTRL_GO | DMA_CTRL_IN | DMA_CTRL_PERF_EN |
>> +		DMA_CTRL_REUSE | DMA_CTRL_IE_DONE | DMA_CTRL_IS_DONE |
>> +		DMA_CTRL_BURST_8 | DMA_CTRL_EN_A;
> 
> Wouldn't be more efficient to set DMA burst to 16 words? The writesize seems
> always aligned to at least 64 words.
> 
>> +	if (oob_required)
>> +		value |= DMA_CTRL_EN_B;
>> +	writel(value, nand->regs + DMA_CTRL);
>> +
>> +	value = CMD_CLE | CMD_ALE | CMD_ALE_SIZE(addrs) | CMD_SEC_CMD |
>> +		CMD_RBSY_CHK | CMD_GO | CMD_RX | CMD_TRANS_SIZE(9) |
>> +		CMD_A_VALID | CMD_CE(nand->cur_chip);
>> +	if (oob_required)
>> +		value |= CMD_B_VALID;
>> +	writel(value, nand->regs + CMD);
>> +
>> +	wait_for_completion(&nand->command_complete);
>> +	wait_for_completion(&nand->dma_complete);
>> +
>> +	if (oob_required) {
>> +		struct mtd_oob_region oobregion;
>> +
>> +		mtd_ooblayout_free(mtd, 0, &oobregion);
>> +		memcpy(chip->oob_poi, nand->oob_buf + oobregion.offset,
>> +		       mtd_ooblayout_count_freebytes(mtd));
>> +	}
>> +	memcpy(buf, nand->data_buf, mtd->writesize);
>> +
>> +	value = readl(nand->regs + CFG);
>> +	value &= ~(CFG_HW_ECC | CFG_ERR_COR);
>> +	writel(value, nand->regs + CFG);
>> +
>> +	value = readl(nand->regs + DEC_STATUS);
>> +	if (value & DEC_STATUS_A_ECC_FAIL) {
>> +		/*
>> +		 * The ECC isn't smart enough to figure out if a page is
>> +		 * completely erased and flags an error in this case. So we
>> +		 * check the read data here to figure out if it's a legitimate
>> +		 * error or a false positive.
>> +		 */
>> +		int i, err;
>> +		int flips_threshold = chip->ecc.strength / 2;
>> +		int max_bitflips = 0;
>> +
>> +		for (i = 0; i < chip->ecc.steps; i++) {
>> +			u8 *data = buf + (chip->ecc.size * i);
>> +			err = nand_check_erased_ecc_chunk(data, chip->ecc.size,
>> +							  NULL, 0,
>> +							  NULL, 0,
>> +							  flips_threshold);
>> +			if (err < 0)
>> +				return err;
>> +
>> +			max_bitflips += max_bitflips;
>> +		}
>> +
>> +		return max_bitflips;
>> +	}
>> +
>> +	if (nand->last_read_error) {
>> +		int max_corr_cnt, corr_sec_flag;
>> +
>> +		value = readl(nand->regs + DEC_STAT_BUF);
>> +		corr_sec_flag = (value & DEC_STAT_BUF_CORR_SEC_FLAG_MASK) >>
>> +				DEC_STAT_BUF_CORR_SEC_FLAG_SHIFT;
>> +		max_corr_cnt = (value & DEC_STAT_BUF_MAX_CORR_CNT_MASK) >>
>> +			       DEC_STAT_BUF_MAX_CORR_CNT_SHIFT;
>> +
>> +		/*
>> +		 * The value returned in the register is the maximum of
>> +		 * bitflips encountered in any of the ECC regions. As there is
>> +		 * no way to get the number of bitflips in a specific regions
>> +		 * we are not able to deliver correct stats but instead
>> +		 * overestimate the number of corrected bitflips by assuming
>> +		 * that all regions where errors have been corrected
>> +		 * encountered the maximum number of bitflips.
>> +		 */
>> +		mtd->ecc_stats.corrected += max_corr_cnt * hweight8(corr_sec_flag);
>> +		nand->last_read_error = false;
>> +		return value;
>> +	}
>> +
>> +	return 0;
>> +}
>> +
>> +static int tegra_nand_write_page(struct mtd_info *mtd, struct nand_chip *chip,
>> +				 const uint8_t *buf, int oob_required, int page)
>> +{
>> +	struct tegra_nand *nand = to_tegra_nand(mtd);
>> +	u32 value, addrs;
>> +
>> +	writel(NAND_CMD_SEQIN, nand->regs + CMD_1);
>> +	writel(NAND_CMD_PAGEPROG, nand->regs + CMD_2);
>> +
>> +	addrs = tegra_nand_fill_address(mtd, chip, page);
>> +
>> +	value = readl(nand->regs + CFG);
>> +	value |= CFG_HW_ECC | CFG_ERR_COR;
>> +	writel(value, nand->regs + CFG);
>> +
>> +	memcpy(nand->data_buf, buf, mtd->writesize);
>> +
>> +	writel(mtd->writesize - 1, nand->regs + DMA_CFG_A);
>> +	writel(nand->data_dma, nand->regs + DATA_PTR);
>> +
>> +	if (oob_required) {
>> +		struct mtd_oob_region oobregion;
>> +
>> +		mtd_ooblayout_free(mtd, 0, &oobregion);
>> +		memcpy(nand->oob_buf, chip->oob_poi + oobregion.offset,
>> +		       mtd_ooblayout_count_freebytes(mtd));
>> +		writel(mtd_ooblayout_count_freebytes(mtd) - 1,
>> +		       nand->regs + DMA_CFG_B);
>> +		writel(nand->oob_dma, nand->regs + TAG_PTR);
>> +	} else {
>> +		writel(0, nand->regs + DMA_CFG_B);
>> +		writel(0, nand->regs + TAG_PTR);
>> +	}
>> +
>> +	value = DMA_CTRL_GO | DMA_CTRL_OUT | DMA_CTRL_PERF_EN |
>> +		DMA_CTRL_IE_DONE | DMA_CTRL_IS_DONE |
>> +		DMA_CTRL_BURST_8 | DMA_CTRL_EN_A;
>> +	if (oob_required)
>> +		value |= DMA_CTRL_EN_B;
>> +	writel(value, nand->regs + DMA_CTRL);
>> +
>> +	value = CMD_CLE | CMD_ALE | CMD_ALE_SIZE(addrs) | CMD_SEC_CMD |
>> +		CMD_AFT_DAT | CMD_RBSY_CHK | CMD_GO | CMD_TX | CMD_A_VALID |
>> +		CMD_TRANS_SIZE(9) | CMD_CE(nand->cur_chip);
>> +	if (oob_required)
>> +		value |= CMD_B_VALID;
>> +	writel(value, nand->regs + CMD);
>> +
>> +	wait_for_completion(&nand->command_complete);
>> +	wait_for_completion(&nand->dma_complete);
>> +
>> +	value = readl(nand->regs + CFG);
>> +	value &= ~(CFG_HW_ECC | CFG_ERR_COR);
>> +	writel(value, nand->regs + CFG);
>> +
>> +	return 0;
>> +}
>> +
>> +static void tegra_nand_setup_timing(struct tegra_nand *nand, int mode)
>> +{
>> +	/*
>> +	 * The period (and all other timings in this function) is in ps,
>> +	 * so need to take care here to avoid integer overflows.
>> +	 */
>> +	unsigned int rate = clk_get_rate(nand->clk) / 1000000;
>> +	unsigned int period = DIV_ROUND_UP(1000000, rate);
>> +	const struct nand_sdr_timings *timings;
>> +	u32 val, reg = 0;
>> +
>> +	timings = onfi_async_timing_mode_to_sdr_timings(mode);
>> +
>> +	val = DIV_ROUND_UP(max3(timings->tAR_min, timings->tRR_min,
>> +				timings->tRC_min), period);
>> +	if (val > 2)
>> +		val -= 3;
>> +	reg |= TIMING_TCR_TAR_TRR(val);
>> +
>> +	val = DIV_ROUND_UP(max(max(timings->tCS_min, timings->tCH_min),
>> +				   max(timings->tALS_min, timings->tALH_min)),
>> +			   period);
>> +	if (val > 1)
>> +		val -= 2;
>> +	reg |= TIMING_TCS(val);
>> +
>> +	val = DIV_ROUND_UP(max(timings->tRP_min, timings->tREA_max) + 6000,
>> +			   period);
>> +	reg |= TIMING_TRP(val) | TIMING_TRP_RESP(val);
>> +
>> +	reg |= TIMING_TWB(DIV_ROUND_UP(timings->tWB_max, period));
>> +	reg |= TIMING_TWHR(DIV_ROUND_UP(timings->tWHR_min, period));
>> +	reg |= TIMING_TWH(DIV_ROUND_UP(timings->tWH_min, period));
>> +	reg |= TIMING_TWP(DIV_ROUND_UP(timings->tWP_min, period));
>> +	reg |= TIMING_TRH(DIV_ROUND_UP(timings->tRHW_min, period));
>> +
>> +	writel(reg, nand->regs + TIMING_1);
>> +
>> +	val = DIV_ROUND_UP(timings->tADL_min, period);
>> +	if (val > 2)
>> +		val -= 3;
>> +	reg = TIMING_TADL(val);
>> +
>> +	writel(reg, nand->regs + TIMING_2);
>> +}
>> +
>> +static void tegra_nand_setup_chiptiming(struct tegra_nand *nand)
>> +{
>> +	struct nand_chip *chip = &nand->chip;
>> +	int mode;
>> +
>> +	mode = onfi_get_async_timing_mode(chip);
>> +	if (mode == ONFI_TIMING_MODE_UNKNOWN)
>> +		mode = chip->onfi_timing_mode_default;
>> +	else
>> +		mode = fls(mode);
>> +
>> +	tegra_nand_setup_timing(nand, mode);
>> +}
>> +
>> +static int tegra_nand_probe(struct platform_device *pdev)
>> +{
>> +	struct reset_control *rst;
>> +	struct tegra_nand *nand;
>> +	struct nand_chip *chip;
>> +	struct mtd_info *mtd;
>> +	struct resource *res;
>> +	unsigned long value;
>> +	int irq, err = 0;
>> +
>> +	nand = devm_kzalloc(&pdev->dev, sizeof(*nand), GFP_KERNEL);
>> +	if (!nand)
>> +		return -ENOMEM;
>> +
>> +	nand->dev = &pdev->dev;
>> +
>> +	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
>> +	nand->regs = devm_ioremap_resource(&pdev->dev, res);
>> +	if (IS_ERR(nand->regs))
>> +		return PTR_ERR(nand->regs);
>> +
>> +	irq = platform_get_irq(pdev, 0);
>> +	err = devm_request_irq(&pdev->dev, irq, tegra_nand_irq, 0,
>> +			       dev_name(&pdev->dev), nand);
>> +	if (err)
>> +		return err;
>> +
>> +	rst = devm_reset_control_get(&pdev->dev, "nand");
>> +	if (IS_ERR(rst))
>> +		return PTR_ERR(rst);
>> +
>> +	nand->clk = devm_clk_get(&pdev->dev, "nand");
>> +	if (IS_ERR(nand->clk))
>> +		return PTR_ERR(nand->clk);
>> +
>> +	nand->wp_gpio = gpiod_get_optional(&pdev->dev, "wp-gpios",
>> +					   GPIOD_OUT_HIGH);
>> +	if (IS_ERR(nand->wp_gpio))
>> +		return PTR_ERR(nand->wp_gpio);
>> +
>> +	err = clk_prepare_enable(nand->clk);
>> +	if (err)
>> +		return err;
>> +
>> +	reset_control_assert(rst);
>> +	udelay(2);
>> +	reset_control_deassert(rst);
> 
> You could use the reset_control_reset() here, though it uses the 1 usec delay
> instead of 2, but I think it shouldn't really matter.
> 
>> +
>> +	value = HWSTATUS_RDSTATUS_MASK(1) | HWSTATUS_RDSTATUS_VALUE(0) |
>> +		HWSTATUS_RBSY_MASK(NAND_STATUS_READY) |
>> +		HWSTATUS_RBSY_VALUE(NAND_STATUS_READY);
>> +	writel(NAND_CMD_STATUS, nand->regs + HWSTATUS_CMD);
>> +	writel(value, nand->regs + HWSTATUS_MASK);
>> +
>> +	init_completion(&nand->command_complete);
>> +	init_completion(&nand->dma_complete);
>> +
>> +	/* clear interrupts */
>> +	value = readl(nand->regs + ISR);
>> +	writel(value, nand->regs + ISR);
> 
> Isn't a set ISR bit means that NAND HW asserts interrupt line? If yes, then you
> may want to move devm_request_irq() after resetting the HW state, to be on a
> safe side.
> 
>> +
>> +	writel(DMA_CTRL_IS_DONE, nand->regs + DMA_CTRL);
>> +
>> +	/* enable interrupts */
>> +	value = IER_UND | IER_OVR | IER_CMD_DONE | IER_ECC_ERR | IER_GIE;
>> +	writel(value, nand->regs + IER);
>> +
>> +	/* reset config */
>> +	writel(0, nand->regs + CFG);
> 
> Is this "reset config" really needed? It is overwritten below in the code.
> 
> 
> Also, maybe you could factor out the HW reset/init stuff into a respective
> tegra_nand_hw_reset() / tegra_nand_hw_init(), just for consistency.
> 
>> +
>> +	chip = &nand->chip;
>> +	mtd = nand_to_mtd(chip);
>> +
>> +	mtd->dev.parent = &pdev->dev;
>> +	mtd->name = "tegra_nand";
>> +	mtd->owner = THIS_MODULE;
>> +
>> +	nand_set_flash_node(chip, pdev->dev.of_node);
>> +	nand_set_controller_data(chip, nand);
>> +
>> +	chip->options = NAND_NO_SUBPAGE_WRITE;
>> +	chip->exec_op = tegra_nand_exec_op;
>> +	chip->select_chip = tegra_nand_select_chip;
>> +	tegra_nand_setup_timing(nand, 0);
>> +
>> +	err = nand_scan_ident(mtd, 1, NULL);
>> +	if (err)
>> +		goto err_disable_clk;
>> +
>> +	if (chip->bbt_options & NAND_BBT_USE_FLASH)
>> +		chip->bbt_options |= NAND_BBT_NO_OOB;
>> +
>> +	nand->data_buf = dmam_alloc_coherent(&pdev->dev, mtd->writesize,
>> +					    &nand->data_dma, GFP_KERNEL);
>> +	if (!nand->data_buf) {
>> +		err = -ENOMEM;
>> +		goto err_disable_clk;
>> +	}
>> +
>> +	nand->oob_buf = dmam_alloc_coherent(&pdev->dev, mtd->oobsize,
>> +					    &nand->oob_dma, GFP_KERNEL);
>> +	if (!nand->oob_buf) {
>> +		err = -ENOMEM;
>> +		goto err_disable_clk;
>> +	}
>> +
>> +	chip->ecc.mode = NAND_ECC_HW;
>> +	chip->ecc.size = 512;
>> +	chip->ecc.read_page = tegra_nand_read_page;
>> +	chip->ecc.write_page = tegra_nand_write_page;
>> +
>> +	value = readl(nand->regs + CFG);
>> +	value |= CFG_PIPE_EN | CFG_SKIP_SPARE | CFG_SKIP_SPARE_SIZE_4 |
>> +		 CFG_TAG_BYTE_SIZE(mtd_ooblayout_count_freebytes(mtd) - 1);
>> +
>> +	if (chip->options & NAND_BUSWIDTH_16)
>> +		value |= CFG_BUS_WIDTH_16;
>> +
>> +	switch (mtd->oobsize) {
>> +	case 16:
>> +		mtd_set_ooblayout(mtd, &tegra_nand_oob_16_ops);
>> +		chip->ecc.strength = 1;
>> +		chip->ecc.bytes = 4;
>> +		break;
>> +	case 64:
>> +		mtd_set_ooblayout(mtd, &tegra_nand_oob_64_ops);
>> +		chip->ecc.strength = 8;
>> +		chip->ecc.bytes = 18;
>> +		value |= CFG_ECC_SEL | CFG_TVAL_8;
>> +		break;
>> +	case 128:
>> +		mtd_set_ooblayout(mtd, &tegra_nand_oob_128_ops);
>> +		chip->ecc.strength = 8;
>> +		chip->ecc.bytes = 18;
>> +		value |= CFG_ECC_SEL | CFG_TVAL_8;
>> +		break;
>> +	case 224:
>> +		mtd_set_ooblayout(mtd, &tegra_nand_oob_224_ops);
>> +		chip->ecc.strength = 8;
>> +		chip->ecc.bytes = 18;
>> +		value |= CFG_ECC_SEL | CFG_TVAL_8;
>> +		break;
>> +	default:
>> +		dev_err(&pdev->dev, "unhandled OOB size %d\n", mtd->oobsize);
>> +		err = -ENODEV;
>> +		goto err_disable_clk;
>> +	}
>> +
>> +	switch (mtd->writesize) {
>> +	case 256:
>> +		value |= CFG_PS_256;
>> +		break;
>> +	case 512:
>> +		value |= CFG_PS_512;
>> +		break;
>> +	case 1024:
>> +		value |= CFG_PS_1024;
>> +		break;
>> +	case 2048:
>> +		value |= CFG_PS_2048;
>> +		break;
>> +	case 4096:
>> +		value |= CFG_PS_4096;
>> +		break;
>> +	default:
>> +		dev_err(&pdev->dev, "unhandled writesize %d\n", mtd->writesize);
>> +		err = -ENODEV;
>> +		goto err_disable_clk;
>> +	}
>> +
>> +	writel(value, nand->regs + CFG);
>> +
>> +	tegra_nand_setup_chiptiming(nand);
>> +
>> +	err = nand_scan_tail(mtd);
>> +	if (err)
>> +		goto err_disable_clk;
>> +
>> +	err = mtd_device_register(mtd, NULL, 0);
>> +	if (err)
>> +		goto err_cleanup_nand;
>> +
>> +	platform_set_drvdata(pdev, nand);
>> +
>> +	return 0;
>> +
>> +err_cleanup_nand:
>> +	nand_cleanup(chip);
>> +err_disable_clk:
>> +	clk_disable_unprepare(nand->clk);
>> +	return err;
>> +}
>> +
>> +static int tegra_nand_remove(struct platform_device *pdev)
>> +{
>> +	struct tegra_nand *nand = platform_get_drvdata(pdev);
>> +
>> +	nand_release(nand_to_mtd(&nand->chip));
>> +
>> +	clk_disable_unprepare(nand->clk);
>> +
>> +	return 0;
>> +}
>> +
>> +static const struct of_device_id tegra_nand_of_match[] = {
>> +	{ .compatible = "nvidia,tegra20-nand" },
>> +	{ /* sentinel */ }
>> +};
>> +
>> +static struct platform_driver tegra_nand_driver = {
>> +	.driver = {
>> +		.name = "tegra-nand",
>> +		.of_match_table = tegra_nand_of_match,
>> +	},
>> +	.probe = tegra_nand_probe,
>> +	.remove = tegra_nand_remove,
>> +};
>> +module_platform_driver(tegra_nand_driver);
>> +
>> +MODULE_DESCRIPTION("NVIDIA Tegra NAND driver");
>> +MODULE_AUTHOR("Thierry Reding <thierry.reding@nvidia.com>");
>> +MODULE_AUTHOR("Lucas Stach <dev@lynxeye.de>");
>> +MODULE_AUTHOR("Stefan Agner <stefan@agner.ch>");
>> +MODULE_LICENSE("GPL v2");
>> +MODULE_DEVICE_TABLE(of, tegra_nand_of_match);
>> 

^ permalink raw reply

* Re: [PATCH v7 2/2] leds: lm3601x: Introduce the lm3601x LED driver
From: Dan Murphy @ 2018-05-22 12:17 UTC (permalink / raw)
  To: Andy Shevchenko
  Cc: Jacek Anaszewski, Rob Herring, Mark Rutland, devicetree,
	Linux Kernel Mailing List, Linux LED Subsystem
In-Reply-To: <CAHp75Vf_KaRDJU83d8o1Sy1v+oztucJSYBEK9=cqNw6FQspFvg@mail.gmail.com>

Andy

On 05/21/2018 06:05 PM, Andy Shevchenko wrote:
> On Tue, May 22, 2018 at 12:44 AM, Dan Murphy <dmurphy@ti.com> wrote:
> 
> 
>>>> +    child = device_get_next_child_node(&led->client->dev, child);
>>>> +    if (!child) {
>>>> +        dev_err(&led->client->dev, "No LED Child node\n");
>>>> +        return ret;
>>>> +    }
>>>> +
>>>> +    ret = fwnode_property_read_u32(child, "reg", &led->led_mode);
>>>> +    if (ret) {
>>>> +        dev_err(&led->client->dev, "reg DT property missing\n");
>>>> +        goto out_err;
>>>> +    }
>>>> +
>>>> +    if (led->led_mode > LM3601X_LED_TORCH ||
>>>> +        led->led_mode < LM3601X_LED_IR) {
>>>> +        dev_warn(&led->client->dev, "Invalid led mode requested\n");
>>>> +        ret = -EINVAL;
>>>> +        goto out_err;
>>>> +    }
>>>> +
>>>> +    ret = fwnode_property_read_string(child, "label", &name);
>>>> +    if (ret) {
>>>> +        if (led->led_mode == LM3601X_LED_TORCH)
>>>> +            name = "torch";
>>>> +        else
>>>> +            name = "infrared";
>>>> +    }
>>>> +
>>>> +    snprintf(led->led_name, sizeof(led->led_name),
>>>> +        "%s:%s", node->name, name);
>>>
>>> Reading once again my recent explanation regarding this I realized
>>> that I didn't provide clear conclusion, which is: we no longer
>>> use child node name for LED class device name if label is absent.
>>> (apart from that - you're using parent DT node now, i.e.
>>> led-controller).
>>>
>>> Please follow what was done for drivers/leds/leds-cr0014114.c.
>>
>> Hmmm.  If this is calling dev->of_node->name to store the name will this
>> work in non-DT configurations?
> 
> I didn't found this kind of use in linux-next, perhaps I missed something?
> 

Linux next has a different version then what I found doing a search for the driver.

I will change the code to match what is in linux-next.

Dan

> In the driver Jacek referred to I found, though, use of of_node, which
> at some point should be changed to fwnode.
> 
> For now you can fill that if you want to using something like this
> (IIRC it should work):
> 
> if (is_of_node(fwnode))
>  ...->of_node = to_of_node(...);
> 
>> I have not dug to deeply into the fwnode code to find out how the nodes
>> get populated.  So my question may not even be valid.
> 


-- 
------------------
Dan Murphy

^ permalink raw reply

* Re: [RESEND PATCH 2/5] mtd: rawnand: add NVIDIA Tegra NAND Flash controller driver
From: Stefan Agner @ 2018-05-22 12:19 UTC (permalink / raw)
  To: Dmitry Osipenko
  Cc: boris.brezillon, dwmw2, computersforpeace, marek.vasut, robh+dt,
	mark.rutland, thierry.reding, mturquette, sboyd, dev,
	miquel.raynal, richard, marcel, krzk, benjamin.lindqvist,
	jonathanh, pdeschrijver, pgaikwad, mirza.krak, linux-mtd,
	linux-tegra, devicetree, linux-kernel, linux-clk
In-Reply-To: <86fdf19ec92b732709732fb60199f16488b4b727.1526990589.git.stefan@agner.ch>

[review sent to my first patch sent off-ml, moving to ml thread]

On 21.05.2018 16:05, Dmitry Osipenko wrote:
> Hello Stefan,
> 
> I don't have expertise to review the actual NAND-related driver logic, so I only
> reviewed the basics. The driver code looks good to me, though I've couple minor
> comments.
> 
> On 21.05.2018 03:16, Stefan Agner wrote:
>> Add support for the NAND flash controller found on NVIDIA
>> Tegra 2 SoCs. This implementation does not make use of the
>> command queue feature. Regular operations/data transfers are
>> done in PIO mode. Page read/writes with hardware ECC make
>> use of the DMA for data transfer.
>>
>> Signed-off-by: Lucas Stach <dev@lynxeye.de>
>> Signed-off-by: Stefan Agner <stefan@agner.ch>
>> ---
>>  MAINTAINERS                       |   7 +
>>  drivers/mtd/nand/raw/Kconfig      |   6 +
>>  drivers/mtd/nand/raw/Makefile     |   1 +
>>  drivers/mtd/nand/raw/tegra_nand.c | 915 ++++++++++++++++++++++++++++++
>>  4 files changed, 929 insertions(+)
>>  create mode 100644 drivers/mtd/nand/raw/tegra_nand.c
>>
>> diff --git a/MAINTAINERS b/MAINTAINERS
>> index 58b9861ccf99..a65739681279 100644
>> --- a/MAINTAINERS
>> +++ b/MAINTAINERS
>> @@ -13844,6 +13844,13 @@ M:	Laxman Dewangan <ldewangan@nvidia.com>
>>  S:	Supported
>>  F:	drivers/input/keyboard/tegra-kbc.c
>>
>> +TEGRA NAND DRIVER
>> +M:	Stefan Agner <stefan@agner.ch>
>> +M:	Lucas Stach <dev@lynxeye.de>
>> +S:	Maintained
>> +F:	Documentation/devicetree/bindings/mtd/nvidia,tegra20-nand.txt
>> +F:	drivers/mtd/nand/tegra_nand.c
>> +
>>  TEGRA PWM DRIVER
>>  M:	Thierry Reding <thierry.reding@gmail.com>
>>  S:	Supported
>> diff --git a/drivers/mtd/nand/raw/Kconfig b/drivers/mtd/nand/raw/Kconfig
>> index 19a2b283fbbe..bd56264233ca 100644
>> --- a/drivers/mtd/nand/raw/Kconfig
>> +++ b/drivers/mtd/nand/raw/Kconfig
>> @@ -534,4 +534,10 @@ config MTD_NAND_MTK
>>  	  Enables support for NAND controller on MTK SoCs.
>>  	  This controller is found on mt27xx, mt81xx, mt65xx SoCs.
>>
>> +config MTD_NAND_TEGRA
>> +	tristate "Support for NAND on NVIDIA Tegra"
>> +	depends on ARCH_TEGRA
>> +	help
>> +	  Enables support for NAND flash on NVIDIA Tegra SoC based boards.
>> +
>>  endif # MTD_NAND
>> diff --git a/drivers/mtd/nand/raw/Makefile b/drivers/mtd/nand/raw/Makefile
>> index 165b7ef9e9a1..d5a5f9832b88 100644
>> --- a/drivers/mtd/nand/raw/Makefile
>> +++ b/drivers/mtd/nand/raw/Makefile
>> @@ -56,6 +56,7 @@ obj-$(CONFIG_MTD_NAND_HISI504)	        += hisi504_nand.o
>>  obj-$(CONFIG_MTD_NAND_BRCMNAND)		+= brcmnand/
>>  obj-$(CONFIG_MTD_NAND_QCOM)		+= qcom_nandc.o
>>  obj-$(CONFIG_MTD_NAND_MTK)		+= mtk_ecc.o mtk_nand.o
>> +obj-$(CONFIG_MTD_NAND_TEGRA)		+= tegra_nand.o
>>
>>  nand-objs := nand_base.o nand_bbt.o nand_timings.o nand_ids.o
>>  nand-objs += nand_amd.o
>> diff --git a/drivers/mtd/nand/raw/tegra_nand.c b/drivers/mtd/nand/raw/tegra_nand.c
>> new file mode 100644
>> index 000000000000..fa236e683fb8
>> --- /dev/null
>> +++ b/drivers/mtd/nand/raw/tegra_nand.c
>> @@ -0,0 +1,915 @@
>> +/*
>> + * Copyright (C) 2018 Stefan Agner <stefan@agner.ch>
>> + * Copyright (C) 2014-2015 Lucas Stach <dev@lynxeye.de>
>> + * Copyright (C) 2012 Avionic Design GmbH
>> + *
>> + * This program is free software; you can redistribute it and/or modify
>> + * it under the terms of the GNU General Public License version 2 as
>> + * published by the Free Software Foundation.
>> + */
>> +
>> +#include <linux/clk.h>
>> +#include <linux/completion.h>
>> +#include <linux/delay.h>
>> +#include <linux/dma-mapping.h>
>> +#include <linux/err.h>
>> +#include <linux/gpio/consumer.h>
>> +#include <linux/interrupt.h>
>> +#include <linux/io.h>
>> +#include <linux/module.h>
>> +#include <linux/mtd/partitions.h>
>> +#include <linux/mtd/rawnand.h>
>> +#include <linux/of.h>
>> +#include <linux/platform_device.h>> +#include <linux/reset.h>
>> +
>> +#define CMD					0x00
>> +#define   CMD_GO				(1 << 31)
>> +#define   CMD_CLE				(1 << 30)
>> +#define   CMD_ALE				(1 << 29)
>> +#define   CMD_PIO				(1 << 28)
>> +#define   CMD_TX				(1 << 27)
>> +#define   CMD_RX				(1 << 26)
>> +#define   CMD_SEC_CMD				(1 << 25)
>> +#define   CMD_AFT_DAT				(1 << 24)
>> +#define   CMD_TRANS_SIZE(x)			(((x - 1) & 0xf) << 20)
>> +#define   CMD_A_VALID				(1 << 19)
>> +#define   CMD_B_VALID				(1 << 18)
>> +#define   CMD_RD_STATUS_CHK			(1 << 17)
>> +#define   CMD_RBSY_CHK				(1 << 16)
>> +#define   CMD_CE(x)				(1 << (8 + ((x) & 0x7)))
>> +#define   CMD_CLE_SIZE(x)			(((x - 1) & 0x3) << 4)
>> +#define   CMD_ALE_SIZE(x)			(((x - 1) & 0xf) << 0)
>> +
>> +#define STATUS					0x04
>> +
>> +#define ISR					0x08
>> +#define   ISR_CORRFAIL_ERR			(1 << 24)
>> +#define   ISR_UND				(1 << 7)
>> +#define   ISR_OVR				(1 << 6)
>> +#define   ISR_CMD_DONE				(1 << 5)
>> +#define   ISR_ECC_ERR				(1 << 4)
>> +
>> +#define IER					0x0c
>> +#define   IER_ERR_TRIG_VAL(x)			(((x) & 0xf) << 16)
>> +#define   IER_UND				(1 << 7)
>> +#define   IER_OVR				(1 << 6)
>> +#define   IER_CMD_DONE				(1 << 5)
>> +#define   IER_ECC_ERR				(1 << 4)
>> +#define   IER_GIE				(1 << 0)
>> +
>> +#define CFG					0x10
>> +#define   CFG_HW_ECC				(1 << 31)
>> +#define   CFG_ECC_SEL				(1 << 30)
>> +#define   CFG_ERR_COR				(1 << 29)
>> +#define   CFG_PIPE_EN				(1 << 28)
>> +#define   CFG_TVAL_4				(0 << 24)
>> +#define   CFG_TVAL_6				(1 << 24)
>> +#define   CFG_TVAL_8				(2 << 24)
>> +#define   CFG_SKIP_SPARE			(1 << 23)
>> +#define   CFG_BUS_WIDTH_8			(0 << 21)
>> +#define   CFG_BUS_WIDTH_16			(1 << 21)
>> +#define   CFG_COM_BSY				(1 << 20)
>> +#define   CFG_PS_256				(0 << 16)
>> +#define   CFG_PS_512				(1 << 16)
>> +#define   CFG_PS_1024				(2 << 16)
>> +#define   CFG_PS_2048				(3 << 16)
>> +#define   CFG_PS_4096				(4 << 16)
>> +#define   CFG_SKIP_SPARE_SIZE_4			(0 << 14)
>> +#define   CFG_SKIP_SPARE_SIZE_8			(1 << 14)
>> +#define   CFG_SKIP_SPARE_SIZE_12		(2 << 14)
>> +#define   CFG_SKIP_SPARE_SIZE_16		(3 << 14)
>> +#define   CFG_TAG_BYTE_SIZE(x)			((x) & 0xff)
>> +
>> +#define TIMING_1				0x14
>> +#define   TIMING_TRP_RESP(x)			(((x) & 0xf) << 28)
>> +#define   TIMING_TWB(x)				(((x) & 0xf) << 24)
>> +#define   TIMING_TCR_TAR_TRR(x)			(((x) & 0xf) << 20)
>> +#define   TIMING_TWHR(x)			(((x) & 0xf) << 16)
>> +#define   TIMING_TCS(x)				(((x) & 0x3) << 14)
>> +#define   TIMING_TWH(x)				(((x) & 0x3) << 12)
>> +#define   TIMING_TWP(x)				(((x) & 0xf) <<  8)
>> +#define   TIMING_TRH(x)				(((x) & 0xf) <<  4)
>> +#define   TIMING_TRP(x)				(((x) & 0xf) <<  0)
>> +
>> +#define RESP					0x18
>> +
>> +#define TIMING_2				0x1c
>> +#define   TIMING_TADL(x)			((x) & 0xf)
>> +
>> +#define CMD_1					0x20
>> +#define CMD_2					0x24
>> +#define ADDR_1					0x28
>> +#define ADDR_2					0x2c
>> +
>> +#define DMA_CTRL				0x30
>> +#define   DMA_CTRL_GO				(1 << 31)
>> +#define   DMA_CTRL_IN				(0 << 30)
>> +#define   DMA_CTRL_OUT				(1 << 30)
>> +#define   DMA_CTRL_PERF_EN			(1 << 29)
>> +#define   DMA_CTRL_IE_DONE			(1 << 28)
>> +#define   DMA_CTRL_REUSE			(1 << 27)
>> +#define   DMA_CTRL_BURST_1			(2 << 24)
>> +#define   DMA_CTRL_BURST_4			(3 << 24)
>> +#define   DMA_CTRL_BURST_8			(4 << 24)
>> +#define   DMA_CTRL_BURST_16			(5 << 24)
>> +#define   DMA_CTRL_IS_DONE			(1 << 20)
>> +#define   DMA_CTRL_EN_A				(1 <<  2)
>> +#define   DMA_CTRL_EN_B				(1 <<  1)
>> +
>> +#define DMA_CFG_A				0x34
>> +#define DMA_CFG_B				0x38
>> +
>> +#define FIFO_CTRL				0x3c
>> +#define   FIFO_CTRL_CLR_ALL			(1 << 3)
>> +
>> +#define DATA_PTR				0x40
>> +#define TAG_PTR					0x44
>> +#define ECC_PTR					0x48
>> +
>> +#define DEC_STATUS				0x4c
>> +#define   DEC_STATUS_A_ECC_FAIL			(1 << 1)
>> +#define   DEC_STATUS_ERR_COUNT_MASK		0x00ff0000
>> +#define   DEC_STATUS_ERR_COUNT_SHIFT		16
>> +
>> +#define HWSTATUS_CMD				0x50
>> +#define HWSTATUS_MASK				0x54
>> +#define   HWSTATUS_RDSTATUS_MASK(x)		(((x) & 0xff) << 24)
>> +#define   HWSTATUS_RDSTATUS_VALUE(x)		(((x) & 0xff) << 16)
>> +#define   HWSTATUS_RBSY_MASK(x)			(((x) & 0xff) << 8)
>> +#define   HWSTATUS_RBSY_VALUE(x)		(((x) & 0xff) << 0)
>> +
>> +#define DEC_STAT_RESULT				0xd0
>> +#define DEC_STAT_BUF				0xd4
>> +#define   DEC_STAT_BUF_CORR_SEC_FLAG_MASK	0x00ff0000
>> +#define   DEC_STAT_BUF_CORR_SEC_FLAG_SHIFT	16
>> +#define   DEC_STAT_BUF_MAX_CORR_CNT_MASK	0x00001f00
>> +#define   DEC_STAT_BUF_MAX_CORR_CNT_SHIFT	8
>> +
>> +struct tegra_nand {
>> +	void __iomem *regs;
>> +	struct clk *clk;
>> +	struct gpio_desc *wp_gpio;
>> +
>> +	struct nand_chip chip;
>> +	struct device *dev;
>> +
>> +	struct completion command_complete;
>> +	struct completion dma_complete;
>> +	bool last_read_error;
>> +
>> +	dma_addr_t data_dma;
>> +	void *data_buf;
>> +	dma_addr_t oob_dma;
>> +	void *oob_buf;
>> +
>> +	int cur_chip;
>> +};
>> +
>> +static inline struct tegra_nand *to_tegra_nand(struct mtd_info *mtd)
>> +{
>> +	struct nand_chip *chip = mtd_to_nand(mtd);
>> +
>> +	return nand_get_controller_data(chip);
>> +}
>> +
>> +static int tegra_nand_ooblayout_16_ecc(struct mtd_info *mtd, int section,
>> +				       struct mtd_oob_region *oobregion)
>> +{
>> +	if (section > 0)
>> +		return -ERANGE;
>> +
>> +	oobregion->offset = 4;
>> +	oobregion->length = 4;
>> +
>> +	return 0;
>> +}
>> +
>> +static int tegra_nand_ooblayout_16_free(struct mtd_info *mtd, int section,
>> +					struct mtd_oob_region *oobregion)
>> +{
>> +	if (section > 0)
>> +		return -ERANGE;
>> +
>> +	oobregion->offset = 8;
>> +	oobregion->length = 8;
>> +
>> +	return 0;
>> +}
>> +
>> +static const struct mtd_ooblayout_ops tegra_nand_oob_16_ops = {
>> +	.ecc = tegra_nand_ooblayout_16_ecc,
>> +	.free = tegra_nand_ooblayout_16_free,
>> +};
>> +
>> +static int tegra_nand_ooblayout_64_ecc(struct mtd_info *mtd, int section,
>> +				       struct mtd_oob_region *oobregion)
>> +{
>> +	if (section > 0)
>> +		return -ERANGE;
>> +
>> +	oobregion->offset = 4;
>> +	oobregion->length = 36;
>> +
>> +	return 0;
>> +}
>> +
>> +static int tegra_nand_ooblayout_64_free(struct mtd_info *mtd, int section,
>> +					struct mtd_oob_region *oobregion)
>> +{
>> +	if (section > 0)
>> +		return -ERANGE;
>> +
>> +	oobregion->offset = 40;
>> +	oobregion->length = 24;
>> +
>> +	return 0;
>> +}
>> +
>> +static const struct mtd_ooblayout_ops tegra_nand_oob_64_ops = {
>> +	.ecc = tegra_nand_ooblayout_64_ecc,
>> +	.free = tegra_nand_ooblayout_64_free,
>> +};
>> +
>> +static int tegra_nand_ooblayout_128_ecc(struct mtd_info *mtd, int section,
>> +				       struct mtd_oob_region *oobregion)
>> +{
>> +	if (section > 0)
>> +		return -ERANGE;
>> +
>> +	oobregion->offset = 4;
>> +	oobregion->length = 72;
>> +
>> +	return 0;
>> +}
>> +
>> +static int tegra_nand_ooblayout_128_free(struct mtd_info *mtd, int section,
>> +					struct mtd_oob_region *oobregion)
>> +{
>> +	if (section > 0)
>> +		return -ERANGE;
>> +
>> +	oobregion->offset = 76;
>> +	oobregion->length = 52;
>> +
>> +	return 0;
>> +}
>> +
>> +static const struct mtd_ooblayout_ops tegra_nand_oob_128_ops = {
>> +	.ecc = tegra_nand_ooblayout_128_ecc,
>> +	.free = tegra_nand_ooblayout_128_free,
>> +};
>> +
>> +static int tegra_nand_ooblayout_224_ecc(struct mtd_info *mtd, int section,
>> +				       struct mtd_oob_region *oobregion)
>> +{
>> +	if (section > 0)
>> +		return -ERANGE;
>> +
>> +	oobregion->offset = 4;
>> +	oobregion->length = 144;
>> +
>> +	return 0;
>> +}
>> +
>> +static int tegra_nand_ooblayout_224_free(struct mtd_info *mtd, int section,
>> +					struct mtd_oob_region *oobregion)
>> +{
>> +	if (section > 0)
>> +		return -ERANGE;
>> +
>> +	oobregion->offset = 148;
>> +	oobregion->length = 76;
>> +
>> +	return 0;
>> +}
>> +
>> +static const struct mtd_ooblayout_ops tegra_nand_oob_224_ops = {
>> +	.ecc = tegra_nand_ooblayout_224_ecc,
>> +	.free = tegra_nand_ooblayout_224_free,
>> +};
>> +
>> +static irqreturn_t tegra_nand_irq(int irq, void *data)
>> +{
>> +	struct tegra_nand *nand = data;
>> +	u32 isr, dma;
>> +
>> +	isr = readl(nand->regs + ISR);
>> +	dma = readl(nand->regs + DMA_CTRL);
> 
> You could use readl_relaxed() here.
> 
>> +	dev_dbg(nand->dev, "isr %08x\n", isr);
>> +
>> +	if (!isr && !(dma & DMA_CTRL_IS_DONE))
>> +		return IRQ_NONE;
>> +
>> +	if (isr & ISR_CORRFAIL_ERR)
>> +		nand->last_read_error = true;
>> +
>> +	if (isr & ISR_CMD_DONE)
>> +		complete(&nand->command_complete);
>> +
>> +	if (isr & ISR_UND)
>> +		dev_dbg(nand->dev, "FIFO underrun\n");
>> +
>> +	if (isr & ISR_OVR)
>> +		dev_dbg(nand->dev, "FIFO overrun\n");
>> +
>> +	/* handle DMA interrupts */
>> +	if (dma & DMA_CTRL_IS_DONE) {
>> +		writel(dma, nand->regs + DMA_CTRL);
>> +		complete(&nand->dma_complete);
>> +	}
>> +
>> +	/* clear interrupts */
>> +	writel(isr, nand->regs + ISR);
>> +
>> +	return IRQ_HANDLED;
>> +}
>> +
>> +static int tegra_nand_cmd(struct nand_chip *chip,
>> +			 const struct nand_subop *subop)
>> +{
>> +	const struct nand_op_instr *instr;
>> +	const struct nand_op_instr *instr_data_in = NULL;
>> +	struct mtd_info *mtd = nand_to_mtd(chip);
>> +	struct tegra_nand *nand = to_tegra_nand(mtd);
>> +	unsigned int op_id = -1, trfr_in_sz = 0, trfr_out_sz = 0, offset = 0;
>> +	bool first_cmd = true;
>> +	bool force8bit;
> 
> The force8bit variable isn't used anywhere in the code, so compiler should warn
> you about the "unused variable", is it the case?
> 
>> +	u32 cmd = 0;
>> +	u32 value;
>> +
> 
> The op_id=-1 above is probably because the loop below was:
> 
> 	while (++op_id < subop->ninstrs) {}
> 
> Both variants are fine, but the for-loop is a bit more explicit. You could omit
> the above op_id variable initialization for consistency.
> 

Agreed, will do the for loop variant.

>> +	for (op_id = 0; op_id < subop->ninstrs; op_id++) {
>> +		unsigned int naddrs, i;
>> +		const u8 *addrs;
>> +		u32 addr1 = 0, addr2 = 0;
>> +
>> +		instr = &subop->instrs[op_id];
>> +
>> +		switch (instr->type) {
>> +		case NAND_OP_CMD_INSTR:
>> +			if (first_cmd) {
>> +				cmd |= CMD_CLE;
>> +				writel(instr->ctx.cmd.opcode, nand->regs + CMD_1);
>> +			} else {
>> +				cmd |= CMD_SEC_CMD;
>> +				writel(instr->ctx.cmd.opcode, nand->regs + CMD_2);
>> +			}
>> +			first_cmd = false;
>> +			break;
>> +		case NAND_OP_ADDR_INSTR:
>> +			offset = nand_subop_get_addr_start_off(subop, op_id);
>> +			naddrs = nand_subop_get_num_addr_cyc(subop, op_id);
>> +			addrs = &instr->ctx.addr.addrs[offset];
>> +
>> +			cmd |= CMD_ALE | CMD_ALE_SIZE(naddrs);
>> +			for (i = 0; i < min_t(unsigned int, 4, naddrs); i++)
>> +				addr1 |= *addrs++ << (8 * i);
>> +			naddrs -= i;
>> +			for (i = 0; i < min_t(unsigned int, 4, naddrs); i++)
>> +				addr2 |= *addrs++ << (8 * i);
>> +			writel(addr1, nand->regs + ADDR_1);
>> +			writel(addr2, nand->regs + ADDR_2);
>> +			break;
>> +
>> +		case NAND_OP_DATA_IN_INSTR:
>> +			trfr_in_sz = nand_subop_get_data_len(subop, op_id);
>> +			offset = nand_subop_get_data_start_off(subop, op_id);
>> +
>> +			cmd |= CMD_TRANS_SIZE(trfr_in_sz) | CMD_PIO | CMD_RX | CMD_A_VALID;
>> +
>> +			instr_data_in = instr;
>> +			break;
>> +
>> +		case NAND_OP_DATA_OUT_INSTR:
>> +			trfr_out_sz = nand_subop_get_data_len(subop, op_id);
>> +			offset = nand_subop_get_data_start_off(subop, op_id);
>> +			trfr_out_sz = min_t(size_t, trfr_out_sz, 4);
>> +
>> +			cmd |= CMD_TRANS_SIZE(trfr_out_sz) | CMD_PIO | CMD_TX | CMD_A_VALID;
>> +
>> +			memcpy(&value, instr->ctx.data.buf.out + offset, trfr_out_sz)> +			writel(value, nand->regs + RESP);
> 
> Note:
> 
> The memcpy + readl / writel won't work with a big-endian kernel,
> cpu_to_[bl]e32() should be applied in a such cases.
> 
> Tegra's I2C driver had a similar memcpy-case issue, it was corrected during the
> attempt to get BE kernel support for Tegra. But that attempt was quite long time
> ago and Tegra maintainers were not very excited to have to do more testing work
> for each kernel release and hence Tegra is LE-only as of today.
> 
>> +
>> +			break;
>> +		case NAND_OP_WAITRDY_INSTR:
>> +			cmd |= CMD_RBSY_CHK;
>> +			break;
>> +
>> +		}
>> +	}
>> +
>> +
>> +	cmd |= CMD_GO | CMD_CE(nand->cur_chip);
>> +	writel(cmd, nand->regs + CMD);
>> +	wait_for_completion(&nand->command_complete);
>> +
>> +	if (instr_data_in) {
>> +		u32 value;
>> +		size_t n = min_t(size_t, trfr_in_sz, 4);
>> +
>> +		value = readl(nand->regs + RESP);
>> +		memcpy(instr_data_in->ctx.data.buf.in + offset, &value, n);
>> +	}
>> +
>> +	return 0;
>> +}
>> +
>> +static const struct nand_op_parser tegra_nand_op_parser = NAND_OP_PARSER(
>> +	NAND_OP_PARSER_PATTERN(tegra_nand_cmd,
>> +		NAND_OP_PARSER_PAT_CMD_ELEM(true),
>> +		NAND_OP_PARSER_PAT_ADDR_ELEM(true, 8),
>> +		NAND_OP_PARSER_PAT_CMD_ELEM(true),
>> +		NAND_OP_PARSER_PAT_WAITRDY_ELEM(true)),
>> +	NAND_OP_PARSER_PATTERN(tegra_nand_cmd,
>> +		NAND_OP_PARSER_PAT_DATA_OUT_ELEM(false, 4)),
>> +	NAND_OP_PARSER_PATTERN(tegra_nand_cmd,
>> +		NAND_OP_PARSER_PAT_CMD_ELEM(true),
>> +		NAND_OP_PARSER_PAT_ADDR_ELEM(true, 8),
>> +		NAND_OP_PARSER_PAT_CMD_ELEM(true),
>> +		NAND_OP_PARSER_PAT_WAITRDY_ELEM(true),
>> +		NAND_OP_PARSER_PAT_DATA_IN_ELEM(true, 4)),
>> +	);
>> +
>> +static int tegra_nand_exec_op(struct nand_chip *chip,
>> +			     const struct nand_operation *op,
>> +			     bool check_only)
>> +{
>> +	return nand_op_parser_exec_op(chip, &tegra_nand_op_parser, op,
>> +				      check_only);
>> +}
>> +static void tegra_nand_select_chip(struct mtd_info *mtd, int chip)
>> +{
>> +	struct tegra_nand *nand = to_tegra_nand(mtd);
>> +
>> +	nand->cur_chip = chip;
>> +}
>> +
>> +static u32 tegra_nand_fill_address(struct mtd_info *mtd, struct nand_chip *chip,
>> +				   int page)
>> +{
>> +	struct tegra_nand *nand = to_tegra_nand(mtd);
>> +
>> +	/* Lower 16-bits are column, always 0 */
>> +	writel(page << 16, nand->regs + ADDR_1);
>> +
>> +	if (chip->options & NAND_ROW_ADDR_3) {
>> +		writel(page >> 16, nand->regs + ADDR_2);
>> +		return 5;
>> +	}
>> +
>> +	return 4;
>> +}
>> +
>> +static int tegra_nand_read_page(struct mtd_info *mtd, struct nand_chip *chip,
>> +				uint8_t *buf, int oob_required, int page)
>> +{
>> +	struct tegra_nand *nand = to_tegra_nand(mtd);
>> +	u32 value, addrs;
>> +
>> +	writel(NAND_CMD_READ0, nand->regs + CMD_1);
>> +	writel(NAND_CMD_READSTART, nand->regs + CMD_2);
>> +
>> +	addrs = tegra_nand_fill_address(mtd, chip, page);
>> +
>> +	value = readl(nand->regs + CFG);
>> +	value |= CFG_HW_ECC | CFG_ERR_COR;
>> +	writel(value, nand->regs + CFG);
>> +
>> +	writel(mtd->writesize - 1, nand->regs + DMA_CFG_A);
>> +	writel(nand->data_dma, nand->regs + DATA_PTR);
>> +
>> +	if (oob_required) {
>> +		writel(mtd_ooblayout_count_freebytes(mtd) - 1,
>> +		       nand->regs + DMA_CFG_B);
>> +		writel(nand->oob_dma, nand->regs + TAG_PTR);
>> +	} else {
>> +		writel(0, nand->regs + DMA_CFG_B);
>> +		writel(0, nand->regs + TAG_PTR);
>> +	}
>> +
>> +	value = DMA_CTRL_GO | DMA_CTRL_IN | DMA_CTRL_PERF_EN |
>> +		DMA_CTRL_REUSE | DMA_CTRL_IE_DONE | DMA_CTRL_IS_DONE |
>> +		DMA_CTRL_BURST_8 | DMA_CTRL_EN_A;
> 
> Wouldn't be more efficient to set DMA burst to 16 words? The writesize seems
> always aligned to at least 64 words.
> 

Hm, haven't tested 16 words, 8 was the setting Lucas used.

Are you sure this is only about write size? Not sure, but isn't the ECC
area also DMA'd? On Colibri we use RS with t=8, hence 144 bytes parity,
so this would be properly aligned non the less...

>> +	if (oob_required)
>> +		value |= DMA_CTRL_EN_B;
>> +	writel(value, nand->regs + DMA_CTRL);
>> +
>> +	value = CMD_CLE | CMD_ALE | CMD_ALE_SIZE(addrs) | CMD_SEC_CMD |
>> +		CMD_RBSY_CHK | CMD_GO | CMD_RX | CMD_TRANS_SIZE(9) |
>> +		CMD_A_VALID | CMD_CE(nand->cur_chip);
>> +	if (oob_required)
>> +		value |= CMD_B_VALID;
>> +	writel(value, nand->regs + CMD);
>> +
>> +	wait_for_completion(&nand->command_complete);
>> +	wait_for_completion(&nand->dma_complete);
>> +
>> +	if (oob_required) {
>> +		struct mtd_oob_region oobregion;
>> +
>> +		mtd_ooblayout_free(mtd, 0, &oobregion);
>> +		memcpy(chip->oob_poi, nand->oob_buf + oobregion.offset,
>> +		       mtd_ooblayout_count_freebytes(mtd));
>> +	}
>> +	memcpy(buf, nand->data_buf, mtd->writesize);
>> +
>> +	value = readl(nand->regs + CFG);
>> +	value &= ~(CFG_HW_ECC | CFG_ERR_COR);
>> +	writel(value, nand->regs + CFG);
>> +
>> +	value = readl(nand->regs + DEC_STATUS);
>> +	if (value & DEC_STATUS_A_ECC_FAIL) {
>> +		/*
>> +		 * The ECC isn't smart enough to figure out if a page is
>> +		 * completely erased and flags an error in this case. So we
>> +		 * check the read data here to figure out if it's a legitimate
>> +		 * error or a false positive.
>> +		 */
>> +		int i, err;
>> +		int flips_threshold = chip->ecc.strength / 2;
>> +		int max_bitflips = 0;
>> +
>> +		for (i = 0; i < chip->ecc.steps; i++) {
>> +			u8 *data = buf + (chip->ecc.size * i);
>> +			err = nand_check_erased_ecc_chunk(data, chip->ecc.size,
>> +							  NULL, 0,
>> +							  NULL, 0,
>> +							  flips_threshold);
>> +			if (err < 0)
>> +				return err;
>> +
>> +			max_bitflips += max_bitflips;
>> +		}
>> +
>> +		return max_bitflips;
>> +	}
>> +
>> +	if (nand->last_read_error) {
>> +		int max_corr_cnt, corr_sec_flag;
>> +
>> +		value = readl(nand->regs + DEC_STAT_BUF);
>> +		corr_sec_flag = (value & DEC_STAT_BUF_CORR_SEC_FLAG_MASK) >>
>> +				DEC_STAT_BUF_CORR_SEC_FLAG_SHIFT;
>> +		max_corr_cnt = (value & DEC_STAT_BUF_MAX_CORR_CNT_MASK) >>
>> +			       DEC_STAT_BUF_MAX_CORR_CNT_SHIFT;
>> +
>> +		/*
>> +		 * The value returned in the register is the maximum of
>> +		 * bitflips encountered in any of the ECC regions. As there is
>> +		 * no way to get the number of bitflips in a specific regions
>> +		 * we are not able to deliver correct stats but instead
>> +		 * overestimate the number of corrected bitflips by assuming
>> +		 * that all regions where errors have been corrected
>> +		 * encountered the maximum number of bitflips.
>> +		 */
>> +		mtd->ecc_stats.corrected += max_corr_cnt * hweight8(corr_sec_flag);
>> +		nand->last_read_error = false;
>> +		return value;
>> +	}
>> +
>> +	return 0;
>> +}
>> +
>> +static int tegra_nand_write_page(struct mtd_info *mtd, struct nand_chip *chip,
>> +				 const uint8_t *buf, int oob_required, int page)
>> +{
>> +	struct tegra_nand *nand = to_tegra_nand(mtd);
>> +	u32 value, addrs;
>> +
>> +	writel(NAND_CMD_SEQIN, nand->regs + CMD_1);
>> +	writel(NAND_CMD_PAGEPROG, nand->regs + CMD_2);
>> +
>> +	addrs = tegra_nand_fill_address(mtd, chip, page);
>> +
>> +	value = readl(nand->regs + CFG);
>> +	value |= CFG_HW_ECC | CFG_ERR_COR;
>> +	writel(value, nand->regs + CFG);
>> +
>> +	memcpy(nand->data_buf, buf, mtd->writesize);
>> +
>> +	writel(mtd->writesize - 1, nand->regs + DMA_CFG_A);
>> +	writel(nand->data_dma, nand->regs + DATA_PTR);
>> +
>> +	if (oob_required) {
>> +		struct mtd_oob_region oobregion;
>> +
>> +		mtd_ooblayout_free(mtd, 0, &oobregion);
>> +		memcpy(nand->oob_buf, chip->oob_poi + oobregion.offset,
>> +		       mtd_ooblayout_count_freebytes(mtd));
>> +		writel(mtd_ooblayout_count_freebytes(mtd) - 1,
>> +		       nand->regs + DMA_CFG_B);
>> +		writel(nand->oob_dma, nand->regs + TAG_PTR);
>> +	} else {
>> +		writel(0, nand->regs + DMA_CFG_B);
>> +		writel(0, nand->regs + TAG_PTR);
>> +	}
>> +
>> +	value = DMA_CTRL_GO | DMA_CTRL_OUT | DMA_CTRL_PERF_EN |
>> +		DMA_CTRL_IE_DONE | DMA_CTRL_IS_DONE |
>> +		DMA_CTRL_BURST_8 | DMA_CTRL_EN_A;
>> +	if (oob_required)
>> +		value |= DMA_CTRL_EN_B;
>> +	writel(value, nand->regs + DMA_CTRL);
>> +
>> +	value = CMD_CLE | CMD_ALE | CMD_ALE_SIZE(addrs) | CMD_SEC_CMD |
>> +		CMD_AFT_DAT | CMD_RBSY_CHK | CMD_GO | CMD_TX | CMD_A_VALID |
>> +		CMD_TRANS_SIZE(9) | CMD_CE(nand->cur_chip);
>> +	if (oob_required)
>> +		value |= CMD_B_VALID;
>> +	writel(value, nand->regs + CMD);
>> +
>> +	wait_for_completion(&nand->command_complete);
>> +	wait_for_completion(&nand->dma_complete);
>> +
>> +	value = readl(nand->regs + CFG);
>> +	value &= ~(CFG_HW_ECC | CFG_ERR_COR);
>> +	writel(value, nand->regs + CFG);
>> +
>> +	return 0;
>> +}
>> +
>> +static void tegra_nand_setup_timing(struct tegra_nand *nand, int mode)
>> +{
>> +	/*
>> +	 * The period (and all other timings in this function) is in ps,
>> +	 * so need to take care here to avoid integer overflows.
>> +	 */
>> +	unsigned int rate = clk_get_rate(nand->clk) / 1000000;
>> +	unsigned int period = DIV_ROUND_UP(1000000, rate);
>> +	const struct nand_sdr_timings *timings;
>> +	u32 val, reg = 0;
>> +
>> +	timings = onfi_async_timing_mode_to_sdr_timings(mode);
>> +
>> +	val = DIV_ROUND_UP(max3(timings->tAR_min, timings->tRR_min,
>> +				timings->tRC_min), period);
>> +	if (val > 2)
>> +		val -= 3;
>> +	reg |= TIMING_TCR_TAR_TRR(val);
>> +
>> +	val = DIV_ROUND_UP(max(max(timings->tCS_min, timings->tCH_min),
>> +				   max(timings->tALS_min, timings->tALH_min)),
>> +			   period);
>> +	if (val > 1)
>> +		val -= 2;
>> +	reg |= TIMING_TCS(val);
>> +
>> +	val = DIV_ROUND_UP(max(timings->tRP_min, timings->tREA_max) + 6000,
>> +			   period);
>> +	reg |= TIMING_TRP(val) | TIMING_TRP_RESP(val);
>> +
>> +	reg |= TIMING_TWB(DIV_ROUND_UP(timings->tWB_max, period));
>> +	reg |= TIMING_TWHR(DIV_ROUND_UP(timings->tWHR_min, period));
>> +	reg |= TIMING_TWH(DIV_ROUND_UP(timings->tWH_min, period));
>> +	reg |= TIMING_TWP(DIV_ROUND_UP(timings->tWP_min, period));
>> +	reg |= TIMING_TRH(DIV_ROUND_UP(timings->tRHW_min, period));
>> +
>> +	writel(reg, nand->regs + TIMING_1);
>> +
>> +	val = DIV_ROUND_UP(timings->tADL_min, period);
>> +	if (val > 2)
>> +		val -= 3;
>> +	reg = TIMING_TADL(val);
>> +
>> +	writel(reg, nand->regs + TIMING_2);
>> +}
>> +
>> +static void tegra_nand_setup_chiptiming(struct tegra_nand *nand)
>> +{
>> +	struct nand_chip *chip = &nand->chip;
>> +	int mode;
>> +
>> +	mode = onfi_get_async_timing_mode(chip);
>> +	if (mode == ONFI_TIMING_MODE_UNKNOWN)
>> +		mode = chip->onfi_timing_mode_default;
>> +	else
>> +		mode = fls(mode);
>> +
>> +	tegra_nand_setup_timing(nand, mode);
>> +}
>> +
>> +static int tegra_nand_probe(struct platform_device *pdev)
>> +{
>> +	struct reset_control *rst;
>> +	struct tegra_nand *nand;
>> +	struct nand_chip *chip;
>> +	struct mtd_info *mtd;
>> +	struct resource *res;
>> +	unsigned long value;
>> +	int irq, err = 0;
>> +
>> +	nand = devm_kzalloc(&pdev->dev, sizeof(*nand), GFP_KERNEL);
>> +	if (!nand)
>> +		return -ENOMEM;
>> +
>> +	nand->dev = &pdev->dev;
>> +
>> +	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
>> +	nand->regs = devm_ioremap_resource(&pdev->dev, res);
>> +	if (IS_ERR(nand->regs))
>> +		return PTR_ERR(nand->regs);
>> +
>> +	irq = platform_get_irq(pdev, 0);
>> +	err = devm_request_irq(&pdev->dev, irq, tegra_nand_irq, 0,
>> +			       dev_name(&pdev->dev), nand);
>> +	if (err)
>> +		return err;
>> +
>> +	rst = devm_reset_control_get(&pdev->dev, "nand");
>> +	if (IS_ERR(rst))
>> +		return PTR_ERR(rst);
>> +
>> +	nand->clk = devm_clk_get(&pdev->dev, "nand");
>> +	if (IS_ERR(nand->clk))
>> +		return PTR_ERR(nand->clk);
>> +
>> +	nand->wp_gpio = gpiod_get_optional(&pdev->dev, "wp-gpios",
>> +					   GPIOD_OUT_HIGH);
>> +	if (IS_ERR(nand->wp_gpio))
>> +		return PTR_ERR(nand->wp_gpio);
>> +
>> +	err = clk_prepare_enable(nand->clk);
>> +	if (err)
>> +		return err;
>> +
>> +	reset_control_assert(rst);
>> +	udelay(2);
>> +	reset_control_deassert(rst);
> 
> You could use the reset_control_reset() here, though it uses the 1 usec delay
> instead of 2, but I think it shouldn't really matter.
> 
>> +
>> +	value = HWSTATUS_RDSTATUS_MASK(1) | HWSTATUS_RDSTATUS_VALUE(0) |
>> +		HWSTATUS_RBSY_MASK(NAND_STATUS_READY) |
>> +		HWSTATUS_RBSY_VALUE(NAND_STATUS_READY);
>> +	writel(NAND_CMD_STATUS, nand->regs + HWSTATUS_CMD);
>> +	writel(value, nand->regs + HWSTATUS_MASK);
>> +
>> +	init_completion(&nand->command_complete);
>> +	init_completion(&nand->dma_complete);
>> +
>> +	/* clear interrupts */
>> +	value = readl(nand->regs + ISR);
>> +	writel(value, nand->regs + ISR);
> 
> Isn't a set ISR bit means that NAND HW asserts interrupt line? If yes, then you
> may want to move devm_request_irq() after resetting the HW state, to be on a
> safe side.
> 

Agreed.

>> +
>> +	writel(DMA_CTRL_IS_DONE, nand->regs + DMA_CTRL);
>> +
>> +	/* enable interrupts */
>> +	value = IER_UND | IER_OVR | IER_CMD_DONE | IER_ECC_ERR | IER_GIE;
>> +	writel(value, nand->regs + IER);
>> +
>> +	/* reset config */
>> +	writel(0, nand->regs + CFG);
> 
> Is this "reset config" really needed? It is overwritten below in the code.
> 

We already use the NAND peripheral in nand_scan_ident, and then properly
set it up later. I think it is better to have a known config state
during this phase...

> 
> Also, maybe you could factor out the HW reset/init stuff into a respective
> tegra_nand_hw_reset() / tegra_nand_hw_init(), just for consistency.
> 

I will consider that.

Thanks for your review!

--
Stefan

>> +
>> +	chip = &nand->chip;
>> +	mtd = nand_to_mtd(chip);
>> +
>> +	mtd->dev.parent = &pdev->dev;
>> +	mtd->name = "tegra_nand";
>> +	mtd->owner = THIS_MODULE;
>> +
>> +	nand_set_flash_node(chip, pdev->dev.of_node);
>> +	nand_set_controller_data(chip, nand);
>> +
>> +	chip->options = NAND_NO_SUBPAGE_WRITE;
>> +	chip->exec_op = tegra_nand_exec_op;
>> +	chip->select_chip = tegra_nand_select_chip;
>> +	tegra_nand_setup_timing(nand, 0);
>> +
>> +	err = nand_scan_ident(mtd, 1, NULL);
>> +	if (err)
>> +		goto err_disable_clk;
>> +
>> +	if (chip->bbt_options & NAND_BBT_USE_FLASH)
>> +		chip->bbt_options |= NAND_BBT_NO_OOB;
>> +
>> +	nand->data_buf = dmam_alloc_coherent(&pdev->dev, mtd->writesize,
>> +					    &nand->data_dma, GFP_KERNEL);
>> +	if (!nand->data_buf) {
>> +		err = -ENOMEM;
>> +		goto err_disable_clk;
>> +	}
>> +
>> +	nand->oob_buf = dmam_alloc_coherent(&pdev->dev, mtd->oobsize,
>> +					    &nand->oob_dma, GFP_KERNEL);
>> +	if (!nand->oob_buf) {
>> +		err = -ENOMEM;
>> +		goto err_disable_clk;
>> +	}
>> +
>> +	chip->ecc.mode = NAND_ECC_HW;
>> +	chip->ecc.size = 512;
>> +	chip->ecc.read_page = tegra_nand_read_page;
>> +	chip->ecc.write_page = tegra_nand_write_page;
>> +
>> +	value = readl(nand->regs + CFG);
>> +	value |= CFG_PIPE_EN | CFG_SKIP_SPARE | CFG_SKIP_SPARE_SIZE_4 |
>> +		 CFG_TAG_BYTE_SIZE(mtd_ooblayout_count_freebytes(mtd) - 1);
>> +
>> +	if (chip->options & NAND_BUSWIDTH_16)
>> +		value |= CFG_BUS_WIDTH_16;
>> +
>> +	switch (mtd->oobsize) {
>> +	case 16:
>> +		mtd_set_ooblayout(mtd, &tegra_nand_oob_16_ops);
>> +		chip->ecc.strength = 1;
>> +		chip->ecc.bytes = 4;
>> +		break;
>> +	case 64:
>> +		mtd_set_ooblayout(mtd, &tegra_nand_oob_64_ops);
>> +		chip->ecc.strength = 8;
>> +		chip->ecc.bytes = 18;
>> +		value |= CFG_ECC_SEL | CFG_TVAL_8;
>> +		break;
>> +	case 128:
>> +		mtd_set_ooblayout(mtd, &tegra_nand_oob_128_ops);
>> +		chip->ecc.strength = 8;
>> +		chip->ecc.bytes = 18;
>> +		value |= CFG_ECC_SEL | CFG_TVAL_8;
>> +		break;
>> +	case 224:
>> +		mtd_set_ooblayout(mtd, &tegra_nand_oob_224_ops);
>> +		chip->ecc.strength = 8;
>> +		chip->ecc.bytes = 18;
>> +		value |= CFG_ECC_SEL | CFG_TVAL_8;
>> +		break;
>> +	default:
>> +		dev_err(&pdev->dev, "unhandled OOB size %d\n", mtd->oobsize);
>> +		err = -ENODEV;
>> +		goto err_disable_clk;
>> +	}
>> +
>> +	switch (mtd->writesize) {
>> +	case 256:
>> +		value |= CFG_PS_256;
>> +		break;
>> +	case 512:
>> +		value |= CFG_PS_512;
>> +		break;
>> +	case 1024:
>> +		value |= CFG_PS_1024;
>> +		break;
>> +	case 2048:
>> +		value |= CFG_PS_2048;
>> +		break;
>> +	case 4096:
>> +		value |= CFG_PS_4096;
>> +		break;
>> +	default:
>> +		dev_err(&pdev->dev, "unhandled writesize %d\n", mtd->writesize);
>> +		err = -ENODEV;
>> +		goto err_disable_clk;
>> +	}
>> +
>> +	writel(value, nand->regs + CFG);
>> +
>> +	tegra_nand_setup_chiptiming(nand);
>> +
>> +	err = nand_scan_tail(mtd);
>> +	if (err)
>> +		goto err_disable_clk;
>> +
>> +	err = mtd_device_register(mtd, NULL, 0);
>> +	if (err)
>> +		goto err_cleanup_nand;
>> +
>> +	platform_set_drvdata(pdev, nand);
>> +
>> +	return 0;
>> +
>> +err_cleanup_nand:
>> +	nand_cleanup(chip);
>> +err_disable_clk:
>> +	clk_disable_unprepare(nand->clk);
>> +	return err;
>> +}
>> +
>> +static int tegra_nand_remove(struct platform_device *pdev)
>> +{
>> +	struct tegra_nand *nand = platform_get_drvdata(pdev);
>> +
>> +	nand_release(nand_to_mtd(&nand->chip));
>> +
>> +	clk_disable_unprepare(nand->clk);
>> +
>> +	return 0;
>> +}
>> +
>> +static const struct of_device_id tegra_nand_of_match[] = {
>> +	{ .compatible = "nvidia,tegra20-nand" },
>> +	{ /* sentinel */ }
>> +};
>> +
>> +static struct platform_driver tegra_nand_driver = {
>> +	.driver = {
>> +		.name = "tegra-nand",
>> +		.of_match_table = tegra_nand_of_match,
>> +	},
>> +	.probe = tegra_nand_probe,
>> +	.remove = tegra_nand_remove,
>> +};
>> +module_platform_driver(tegra_nand_driver);
>> +
>> +MODULE_DESCRIPTION("NVIDIA Tegra NAND driver");
>> +MODULE_AUTHOR("Thierry Reding <thierry.reding@nvidia.com>");
>> +MODULE_AUTHOR("Lucas Stach <dev@lynxeye.de>");
>> +MODULE_AUTHOR("Stefan Agner <stefan@agner.ch>");
>> +MODULE_LICENSE("GPL v2");
>> +MODULE_DEVICE_TABLE(of, tegra_nand_of_match);
>>

^ permalink raw reply

* Re: [PATCH v7 5/9] iio: adc: at91-sama5d2_adc: add support for position and pressure channels
From: Ludovic Desroches @ 2018-05-22 12:22 UTC (permalink / raw)
  To: Eugen Hristev
  Cc: jic23, ludovic.desroches, alexandre.belloni, linux-arm-kernel,
	devicetree, linux-kernel, linux-iio, linux-input, nicolas.ferre,
	dmitry.torokhov, robh
In-Reply-To: <1526975559-18966-6-git-send-email-eugen.hristev@microchip.com>

On Tue, May 22, 2018 at 10:52:35AM +0300, Eugen Hristev wrote:
> This implements the support for position and pressure for the included
> touchscreen support in the SAMA5D2 SOC ADC block.
> Two position channels are added and one for pressure.
> They can be read in raw format, or through a buffer.
> A normal use case is for a consumer driver to register a callback buffer
> for these channels.
> When the touchscreen channels are in the active scan mask,
> the driver will start the touchscreen sampling and push the data to the
> buffer.
> 
> Some parts of this patch are based on initial original work by
> Mohamed Jamsheeth Hajanajubudeen and Bandaru Venkateswara Swamy
> 
> Signed-off-by: Eugen Hristev <eugen.hristev@microchip.com>
Acked-by: Ludovic Desroches <ludovic.desroches@microchip.com>

> ---
> Changes in v6:
>  - fixed a crash when issuing buffer enable from sysfs, if no trigger was
> previously configured. This is because now the driver can work in software
> buffer mode (to connect the callback buffer). So, when trying to enable the
> buffer, check if we are going indeed to a triggered mode or not. If not, do
> not allow buffer to be started (we do not have the right trigger).
> It's in buffer_postenable and predisable.
> 
> Changes in v4:
>  - use return value of at91_adc_configure_touch
>  - rewrote some part of the read_info_raw according to Jonathan's
> suggestion
> 
> Changes in v3:
>  - prefix macros with AT91_SAMA5D2
>  - reworked the x_pos and y_pos functions into a single one with two
> additional wrappers
>  - reworked pressure report to have it grow naturally and not top down
>  - fixed some checks regarding IIO_VOLTAGE as suggested
>  - added a comment explaining some code in trigger handling
>  - reworked the frequency get handler to use the saved value instead of
> reading it from the hardware.
>  - added comment on deffered work queueing
>  - pulled out INFO_RAW function into a separate utility function as suggested
>  - added iio_dev ops structure at all times . The functions are needed in
> case we do not have a hardware trigger attached, but we want to use the
> consumer touchscreen driver, thus a callback buffer is attached. Then we still
> need to have buffer preenable and postdisable to configure the touch IRQs (etc.)
> 
> Changes in v2:
>  - the support is now based on callback buffer.
> 
>  drivers/iio/adc/at91-sama5d2_adc.c | 609 +++++++++++++++++++++++++++++++++----
>  1 file changed, 551 insertions(+), 58 deletions(-)
> 
> diff --git a/drivers/iio/adc/at91-sama5d2_adc.c b/drivers/iio/adc/at91-sama5d2_adc.c
> index 8729d65..58c4c2b 100644
> --- a/drivers/iio/adc/at91-sama5d2_adc.c
> +++ b/drivers/iio/adc/at91-sama5d2_adc.c
> @@ -102,14 +102,26 @@
>  #define AT91_SAMA5D2_LCDR	0x20
>  /* Interrupt Enable Register */
>  #define AT91_SAMA5D2_IER	0x24
> +/* Interrupt Enable Register - TS X measurement ready */
> +#define AT91_SAMA5D2_IER_XRDY   BIT(20)
> +/* Interrupt Enable Register - TS Y measurement ready */
> +#define AT91_SAMA5D2_IER_YRDY   BIT(21)
> +/* Interrupt Enable Register - TS pressure measurement ready */
> +#define AT91_SAMA5D2_IER_PRDY   BIT(22)
>  /* Interrupt Enable Register - general overrun error */
>  #define AT91_SAMA5D2_IER_GOVRE BIT(25)
> +/* Interrupt Enable Register - Pen detect */
> +#define AT91_SAMA5D2_IER_PEN    BIT(29)
> +/* Interrupt Enable Register - No pen detect */
> +#define AT91_SAMA5D2_IER_NOPEN  BIT(30)
>  /* Interrupt Disable Register */
>  #define AT91_SAMA5D2_IDR	0x28
>  /* Interrupt Mask Register */
>  #define AT91_SAMA5D2_IMR	0x2c
>  /* Interrupt Status Register */
>  #define AT91_SAMA5D2_ISR	0x30
> +/* Interrupt Status Register - Pen touching sense status */
> +#define AT91_SAMA5D2_ISR_PENS   BIT(31)
>  /* Last Channel Trigger Mode Register */
>  #define AT91_SAMA5D2_LCTMR	0x34
>  /* Last Channel Compare Window Register */
> @@ -131,8 +143,38 @@
>  #define AT91_SAMA5D2_CDR0	0x50
>  /* Analog Control Register */
>  #define AT91_SAMA5D2_ACR	0x94
> +/* Analog Control Register - Pen detect sensitivity mask */
> +#define AT91_SAMA5D2_ACR_PENDETSENS_MASK        GENMASK(1, 0)
> +
>  /* Touchscreen Mode Register */
>  #define AT91_SAMA5D2_TSMR	0xb0
> +/* Touchscreen Mode Register - No touch mode */
> +#define AT91_SAMA5D2_TSMR_TSMODE_NONE           0
> +/* Touchscreen Mode Register - 4 wire screen, no pressure measurement */
> +#define AT91_SAMA5D2_TSMR_TSMODE_4WIRE_NO_PRESS 1
> +/* Touchscreen Mode Register - 4 wire screen, pressure measurement */
> +#define AT91_SAMA5D2_TSMR_TSMODE_4WIRE_PRESS    2
> +/* Touchscreen Mode Register - 5 wire screen */
> +#define AT91_SAMA5D2_TSMR_TSMODE_5WIRE          3
> +/* Touchscreen Mode Register - Average samples mask */
> +#define AT91_SAMA5D2_TSMR_TSAV_MASK             GENMASK(5, 4)
> +/* Touchscreen Mode Register - Average samples */
> +#define AT91_SAMA5D2_TSMR_TSAV(x)               ((x) << 4)
> +/* Touchscreen Mode Register - Touch/trigger frequency ratio mask */
> +#define AT91_SAMA5D2_TSMR_TSFREQ_MASK           GENMASK(11, 8)
> +/* Touchscreen Mode Register - Touch/trigger frequency ratio */
> +#define AT91_SAMA5D2_TSMR_TSFREQ(x)             ((x) << 8)
> +/* Touchscreen Mode Register - Pen Debounce Time mask */
> +#define AT91_SAMA5D2_TSMR_PENDBC_MASK           GENMASK(31, 28)
> +/* Touchscreen Mode Register - Pen Debounce Time */
> +#define AT91_SAMA5D2_TSMR_PENDBC(x)            ((x) << 28)
> +/* Touchscreen Mode Register - No DMA for touch measurements */
> +#define AT91_SAMA5D2_TSMR_NOTSDMA               BIT(22)
> +/* Touchscreen Mode Register - Disable pen detection */
> +#define AT91_SAMA5D2_TSMR_PENDET_DIS            (0 << 24)
> +/* Touchscreen Mode Register - Enable pen detection */
> +#define AT91_SAMA5D2_TSMR_PENDET_ENA            BIT(24)
> +
>  /* Touchscreen X Position Register */
>  #define AT91_SAMA5D2_XPOSR	0xb4
>  /* Touchscreen Y Position Register */
> @@ -151,6 +193,12 @@
>  #define AT91_SAMA5D2_TRGR_TRGMOD_EXT_TRIG_FALL 2
>  /* Trigger Mode external trigger any edge */
>  #define AT91_SAMA5D2_TRGR_TRGMOD_EXT_TRIG_ANY 3
> +/* Trigger Mode internal periodic */
> +#define AT91_SAMA5D2_TRGR_TRGMOD_PERIODIC 5
> +/* Trigger Mode - trigger period mask */
> +#define AT91_SAMA5D2_TRGR_TRGPER_MASK           GENMASK(31, 16)
> +/* Trigger Mode - trigger period */
> +#define AT91_SAMA5D2_TRGR_TRGPER(x)             ((x) << 16)
>  
>  /* Correction Select Register */
>  #define AT91_SAMA5D2_COSR	0xd0
> @@ -169,6 +217,22 @@
>  #define AT91_SAMA5D2_SINGLE_CHAN_CNT 12
>  #define AT91_SAMA5D2_DIFF_CHAN_CNT 6
>  
> +#define AT91_SAMA5D2_TIMESTAMP_CHAN_IDX (AT91_SAMA5D2_SINGLE_CHAN_CNT + \
> +					 AT91_SAMA5D2_DIFF_CHAN_CNT + 1)
> +
> +#define AT91_SAMA5D2_TOUCH_X_CHAN_IDX (AT91_SAMA5D2_SINGLE_CHAN_CNT + \
> +					 AT91_SAMA5D2_DIFF_CHAN_CNT * 2)
> +#define AT91_SAMA5D2_TOUCH_Y_CHAN_IDX   (AT91_SAMA5D2_TOUCH_X_CHAN_IDX + 1)
> +#define AT91_SAMA5D2_TOUCH_P_CHAN_IDX   (AT91_SAMA5D2_TOUCH_Y_CHAN_IDX + 1)
> +#define AT91_SAMA5D2_MAX_CHAN_IDX	AT91_SAMA5D2_TOUCH_P_CHAN_IDX
> +
> +#define AT91_SAMA5D2_TOUCH_SAMPLE_PERIOD_US          2000    /* 2ms */
> +#define AT91_SAMA5D2_TOUCH_PEN_DETECT_DEBOUNCE_US    200
> +
> +#define AT91_SAMA5D2_XYZ_MASK		GENMASK(11, 0)
> +
> +#define AT91_SAMA5D2_MAX_POS_BITS			12
> +
>  /*
>   * Maximum number of bytes to hold conversion from all channels
>   * without the timestamp.
> @@ -222,6 +286,37 @@
>  		.indexed = 1,						\
>  	}
>  
> +#define AT91_SAMA5D2_CHAN_TOUCH(num, name, mod)				\
> +	{								\
> +		.type = IIO_POSITIONRELATIVE,				\
> +		.modified = 1,						\
> +		.channel = num,						\
> +		.channel2 = mod,					\
> +		.scan_index = num,					\
> +		.scan_type = {						\
> +			.sign = 'u',					\
> +			.realbits = 12,					\
> +			.storagebits = 16,				\
> +		},							\
> +		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),		\
> +		.info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ),\
> +		.datasheet_name = name,					\
> +	}
> +#define AT91_SAMA5D2_CHAN_PRESSURE(num, name)				\
> +	{								\
> +		.type = IIO_PRESSURE,					\
> +		.channel = num,						\
> +		.scan_index = num,					\
> +		.scan_type = {						\
> +			.sign = 'u',					\
> +			.realbits = 12,					\
> +			.storagebits = 16,				\
> +		},							\
> +		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),		\
> +		.info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ),\
> +		.datasheet_name = name,					\
> +	}
> +
>  #define at91_adc_readl(st, reg)		readl_relaxed(st->base + reg)
>  #define at91_adc_writel(st, reg, val)	writel_relaxed(val, st->base + reg)
>  
> @@ -260,6 +355,22 @@ struct at91_adc_dma {
>  	s64				dma_ts;
>  };
>  
> +/**
> + * at91_adc_touch - at91-sama5d2 touchscreen information struct
> + * @sample_period_val:		the value for periodic trigger interval
> + * @touching:			is the pen touching the screen or not
> + * @x_pos:			temporary placeholder for pressure computation
> + * @channels_bitmask:		bitmask with the touchscreen channels enabled
> + * @workq:			workqueue for buffer data pushing
> + */
> +struct at91_adc_touch {
> +	u16				sample_period_val;
> +	bool				touching;
> +	u16				x_pos;
> +	unsigned long			channels_bitmask;
> +	struct work_struct		workq;
> +};
> +
>  struct at91_adc_state {
>  	void __iomem			*base;
>  	int				irq;
> @@ -267,6 +378,7 @@ struct at91_adc_state {
>  	struct regulator		*reg;
>  	struct regulator		*vref;
>  	int				vref_uv;
> +	unsigned int			current_sample_rate;
>  	struct iio_trigger		*trig;
>  	const struct at91_adc_trigger	*selected_trig;
>  	const struct iio_chan_spec	*chan;
> @@ -275,6 +387,7 @@ struct at91_adc_state {
>  	struct at91_adc_soc_info	soc_info;
>  	wait_queue_head_t		wq_data_available;
>  	struct at91_adc_dma		dma_st;
> +	struct at91_adc_touch		touch_st;
>  	u16				buffer[AT91_BUFFER_MAX_HWORDS];
>  	/*
>  	 * lock to prevent concurrent 'single conversion' requests through
> @@ -329,8 +442,10 @@ static const struct iio_chan_spec at91_adc_channels[] = {
>  	AT91_SAMA5D2_CHAN_DIFF(6, 7, 0x68),
>  	AT91_SAMA5D2_CHAN_DIFF(8, 9, 0x70),
>  	AT91_SAMA5D2_CHAN_DIFF(10, 11, 0x78),
> -	IIO_CHAN_SOFT_TIMESTAMP(AT91_SAMA5D2_SINGLE_CHAN_CNT
> -				+ AT91_SAMA5D2_DIFF_CHAN_CNT + 1),
> +	IIO_CHAN_SOFT_TIMESTAMP(AT91_SAMA5D2_TIMESTAMP_CHAN_IDX),
> +	AT91_SAMA5D2_CHAN_TOUCH(AT91_SAMA5D2_TOUCH_X_CHAN_IDX, "x", IIO_MOD_X),
> +	AT91_SAMA5D2_CHAN_TOUCH(AT91_SAMA5D2_TOUCH_Y_CHAN_IDX, "y", IIO_MOD_Y),
> +	AT91_SAMA5D2_CHAN_PRESSURE(AT91_SAMA5D2_TOUCH_P_CHAN_IDX, "pressure"),
>  };
>  
>  static int at91_adc_chan_xlate(struct iio_dev *indio_dev, int chan)
> @@ -354,6 +469,160 @@ at91_adc_chan_get(struct iio_dev *indio_dev, int chan)
>  	return indio_dev->channels + index;
>  }
>  
> +static inline int at91_adc_of_xlate(struct iio_dev *indio_dev,
> +				    const struct of_phandle_args *iiospec)
> +{
> +	return at91_adc_chan_xlate(indio_dev, iiospec->args[0]);
> +}
> +
> +static int at91_adc_configure_touch(struct at91_adc_state *st, bool state)
> +{
> +	u32 clk_khz = st->current_sample_rate / 1000;
> +	int i = 0;
> +	u16 pendbc;
> +	u32 tsmr, acr;
> +
> +	if (!state) {
> +		/* disabling touch IRQs and setting mode to no touch enabled */
> +		at91_adc_writel(st, AT91_SAMA5D2_IDR,
> +				AT91_SAMA5D2_IER_PEN | AT91_SAMA5D2_IER_NOPEN);
> +		at91_adc_writel(st, AT91_SAMA5D2_TSMR, 0);
> +		return 0;
> +	}
> +	/*
> +	 * debounce time is in microseconds, we need it in milliseconds to
> +	 * multiply with kilohertz, so, divide by 1000, but after the multiply.
> +	 * round up to make sure pendbc is at least 1
> +	 */
> +	pendbc = round_up(AT91_SAMA5D2_TOUCH_PEN_DETECT_DEBOUNCE_US *
> +			  clk_khz / 1000, 1);
> +
> +	/* get the required exponent */
> +	while (pendbc >> i++)
> +		;
> +
> +	pendbc = i;
> +
> +	tsmr = AT91_SAMA5D2_TSMR_TSMODE_4WIRE_PRESS;
> +
> +	tsmr |= AT91_SAMA5D2_TSMR_TSAV(2) & AT91_SAMA5D2_TSMR_TSAV_MASK;
> +	tsmr |= AT91_SAMA5D2_TSMR_PENDBC(pendbc) &
> +		AT91_SAMA5D2_TSMR_PENDBC_MASK;
> +	tsmr |= AT91_SAMA5D2_TSMR_NOTSDMA;
> +	tsmr |= AT91_SAMA5D2_TSMR_PENDET_ENA;
> +	tsmr |= AT91_SAMA5D2_TSMR_TSFREQ(2) & AT91_SAMA5D2_TSMR_TSFREQ_MASK;
> +
> +	at91_adc_writel(st, AT91_SAMA5D2_TSMR, tsmr);
> +
> +	acr =  at91_adc_readl(st, AT91_SAMA5D2_ACR);
> +	acr &= ~AT91_SAMA5D2_ACR_PENDETSENS_MASK;
> +	acr |= 0x02 & AT91_SAMA5D2_ACR_PENDETSENS_MASK;
> +	at91_adc_writel(st, AT91_SAMA5D2_ACR, acr);
> +
> +	/* Sample Period Time = (TRGPER + 1) / ADCClock */
> +	st->touch_st.sample_period_val =
> +				 round_up((AT91_SAMA5D2_TOUCH_SAMPLE_PERIOD_US *
> +				 clk_khz / 1000) - 1, 1);
> +	/* enable pen detect IRQ */
> +	at91_adc_writel(st, AT91_SAMA5D2_IER, AT91_SAMA5D2_IER_PEN);
> +
> +	return 0;
> +}
> +
> +static u16 at91_adc_touch_pos(struct at91_adc_state *st, int reg)
> +{
> +	u32 val;
> +	u32 scale, result, pos;
> +
> +	/*
> +	 * to obtain the actual position we must divide by scale
> +	 * and multiply with max, where
> +	 * max = 2^AT91_SAMA5D2_MAX_POS_BITS - 1
> +	 */
> +	/* first half of register is the x or y, second half is the scale */
> +	val = at91_adc_readl(st, reg);
> +	if (!val)
> +		dev_dbg(&iio_priv_to_dev(st)->dev, "pos is 0\n");
> +
> +	pos = val & AT91_SAMA5D2_XYZ_MASK;
> +	result = (pos << AT91_SAMA5D2_MAX_POS_BITS) - pos;
> +	scale = (val >> 16) & AT91_SAMA5D2_XYZ_MASK;
> +	if (scale == 0) {
> +		dev_err(&iio_priv_to_dev(st)->dev, "scale is 0\n");
> +		return 0;
> +	}
> +	result /= scale;
> +
> +	return result;
> +}
> +
> +static u16 at91_adc_touch_x_pos(struct at91_adc_state *st)
> +{
> +	st->touch_st.x_pos = at91_adc_touch_pos(st, AT91_SAMA5D2_XPOSR);
> +	return st->touch_st.x_pos;
> +}
> +
> +static u16 at91_adc_touch_y_pos(struct at91_adc_state *st)
> +{
> +	return at91_adc_touch_pos(st, AT91_SAMA5D2_YPOSR);
> +}
> +
> +static u16 at91_adc_touch_pressure(struct at91_adc_state *st)
> +{
> +	u32 val;
> +	u32 z1, z2;
> +	u32 pres;
> +	u32 rxp = 1;
> +	u32 factor = 1000;
> +
> +	/* calculate the pressure */
> +	val = at91_adc_readl(st, AT91_SAMA5D2_PRESSR);
> +	z1 = val & AT91_SAMA5D2_XYZ_MASK;
> +	z2 = (val >> 16) & AT91_SAMA5D2_XYZ_MASK;
> +
> +	if (z1 != 0)
> +		pres = rxp * (st->touch_st.x_pos * factor / 1024) *
> +			(z2 * factor / z1 - factor) /
> +			factor;
> +	else
> +		pres = 0xFFFF;       /* no pen contact */
> +
> +	/*
> +	 * The pressure from device grows down, minimum is 0xFFFF, maximum 0x0.
> +	 * We compute it this way, but let's return it in the expected way,
> +	 * growing from 0 to 0xFFFF.
> +	 */
> +	return 0xFFFF - pres;
> +}
> +
> +static int at91_adc_read_position(struct at91_adc_state *st, int chan, u16 *val)
> +{
> +	*val = 0;
> +	if (!st->touch_st.touching)
> +		return -ENODATA;
> +	if (chan == AT91_SAMA5D2_TOUCH_X_CHAN_IDX)
> +		*val = at91_adc_touch_x_pos(st);
> +	else if (chan == AT91_SAMA5D2_TOUCH_Y_CHAN_IDX)
> +		*val = at91_adc_touch_y_pos(st);
> +	else
> +		return -ENODATA;
> +
> +	return IIO_VAL_INT;
> +}
> +
> +static int at91_adc_read_pressure(struct at91_adc_state *st, int chan, u16 *val)
> +{
> +	*val = 0;
> +	if (!st->touch_st.touching)
> +		return -ENODATA;
> +	if (chan == AT91_SAMA5D2_TOUCH_P_CHAN_IDX)
> +		*val = at91_adc_touch_pressure(st);
> +	else
> +		return -ENODATA;
> +
> +	return IIO_VAL_INT;
> +}
> +
>  static int at91_adc_configure_trigger(struct iio_trigger *trig, bool state)
>  {
>  	struct iio_dev *indio = iio_trigger_get_drvdata(trig);
> @@ -375,6 +644,11 @@ static int at91_adc_configure_trigger(struct iio_trigger *trig, bool state)
>  
>  		if (!chan)
>  			continue;
> +		/* these channel types cannot be handled by this trigger */
> +		if (chan->type == IIO_POSITIONRELATIVE ||
> +		    chan->type == IIO_PRESSURE)
> +			continue;
> +
>  		if (state) {
>  			at91_adc_writel(st, AT91_SAMA5D2_CHER,
>  					BIT(chan->channel));
> @@ -520,7 +794,20 @@ static int at91_adc_dma_start(struct iio_dev *indio_dev)
>  static int at91_adc_buffer_postenable(struct iio_dev *indio_dev)
>  {
>  	int ret;
> +	struct at91_adc_state *st = iio_priv(indio_dev);
>  
> +	/* check if we are enabling triggered buffer or the touchscreen */
> +	if (bitmap_subset(indio_dev->active_scan_mask,
> +			  &st->touch_st.channels_bitmask,
> +			  AT91_SAMA5D2_MAX_CHAN_IDX + 1)) {
> +		/* touchscreen enabling */
> +		return at91_adc_configure_touch(st, true);
> +	}
> +	/* if we are not in triggered mode, we cannot enable the buffer. */
> +	if (!(indio_dev->currentmode & INDIO_ALL_TRIGGERED_MODES))
> +		return -EINVAL;
> +
> +	/* we continue with the triggered buffer */
>  	ret = at91_adc_dma_start(indio_dev);
>  	if (ret) {
>  		dev_err(&indio_dev->dev, "buffer postenable failed\n");
> @@ -536,6 +823,18 @@ static int at91_adc_buffer_predisable(struct iio_dev *indio_dev)
>  	int ret;
>  	u8 bit;
>  
> +	/* check if we are disabling triggered buffer or the touchscreen */
> +	if (bitmap_subset(indio_dev->active_scan_mask,
> +			  &st->touch_st.channels_bitmask,
> +			  AT91_SAMA5D2_MAX_CHAN_IDX + 1)) {
> +		/* touchscreen disable */
> +		return at91_adc_configure_touch(st, false);
> +	}
> +	/* if we are not in triggered mode, nothing to do here */
> +	if (!(indio_dev->currentmode & INDIO_ALL_TRIGGERED_MODES))
> +		return -EINVAL;
> +
> +	/* continue with the triggered buffer */
>  	ret = iio_triggered_buffer_predisable(indio_dev);
>  	if (ret < 0)
>  		dev_err(&indio_dev->dev, "buffer predisable failed\n");
> @@ -558,6 +857,10 @@ static int at91_adc_buffer_predisable(struct iio_dev *indio_dev)
>  
>  		if (!chan)
>  			continue;
> +		/* these channel types are virtual, no need to do anything */
> +		if (chan->type == IIO_POSITIONRELATIVE ||
> +		    chan->type == IIO_PRESSURE)
> +			continue;
>  		if (st->dma_st.dma_chan)
>  			at91_adc_readl(st, chan->address);
>  	}
> @@ -622,7 +925,22 @@ static void at91_adc_trigger_handler_nodma(struct iio_dev *indio_dev,
>  
>  		if (!chan)
>  			continue;
> -		st->buffer[i] = at91_adc_readl(st, chan->address);
> +		/*
> +		 * Our external trigger only supports the voltage channels.
> +		 * In case someone requested a different type of channel
> +		 * just put zeroes to buffer.
> +		 * This should not happen because we check the scan mode
> +		 * and scan mask when we enable the buffer, and we don't allow
> +		 * the buffer to start with a mixed mask (voltage and something
> +		 * else).
> +		 * Thus, emit a warning.
> +		 */
> +		if (chan->type == IIO_VOLTAGE) {
> +			st->buffer[i] = at91_adc_readl(st, chan->address);
> +		} else {
> +			st->buffer[i] = 0;
> +			WARN(true, "This trigger cannot handle this type of channel");
> +		}
>  		i++;
>  	}
>  	iio_push_to_buffers_with_timestamp(indio_dev, st->buffer,
> @@ -688,9 +1006,20 @@ static irqreturn_t at91_adc_trigger_handler(int irq, void *p)
>  
>  static int at91_adc_buffer_init(struct iio_dev *indio)
>  {
> -	return devm_iio_triggered_buffer_setup(&indio->dev, indio,
> +	struct at91_adc_state *st = iio_priv(indio);
> +
> +	if (st->selected_trig->hw_trig) {
> +		return devm_iio_triggered_buffer_setup(&indio->dev, indio,
>  			&iio_pollfunc_store_time,
>  			&at91_adc_trigger_handler, &at91_buffer_setup_ops);
> +	}
> +	/*
> +	 * we need to prepare the buffer ops in case we will get
> +	 * another buffer attached (like a callback buffer for the touchscreen)
> +	 */
> +	indio->setup_ops = &at91_buffer_setup_ops;
> +
> +	return 0;
>  }
>  
>  static unsigned at91_adc_startup_time(unsigned startup_time_min,
> @@ -736,19 +1065,83 @@ static void at91_adc_setup_samp_freq(struct at91_adc_state *st, unsigned freq)
>  
>  	dev_dbg(&indio_dev->dev, "freq: %u, startup: %u, prescal: %u\n",
>  		freq, startup, prescal);
> +	st->current_sample_rate = freq;
>  }
>  
> -static unsigned at91_adc_get_sample_freq(struct at91_adc_state *st)
> +static inline unsigned at91_adc_get_sample_freq(struct at91_adc_state *st)
>  {
> -	unsigned f_adc, f_per = clk_get_rate(st->per_clk);
> -	unsigned mr, prescal;
> +	return st->current_sample_rate;
> +}
>  
> -	mr = at91_adc_readl(st, AT91_SAMA5D2_MR);
> -	prescal = (mr >> AT91_SAMA5D2_MR_PRESCAL_OFFSET)
> -		  & AT91_SAMA5D2_MR_PRESCAL_MAX;
> -	f_adc = f_per / (2 * (prescal + 1));
> +static void at91_adc_touch_data_handler(struct iio_dev *indio_dev)
> +{
> +	struct at91_adc_state *st = iio_priv(indio_dev);
> +	u8 bit;
> +	u16 val;
> +	int i = 0;
>  
> -	return f_adc;
> +	for_each_set_bit(bit, indio_dev->active_scan_mask,
> +			 AT91_SAMA5D2_MAX_CHAN_IDX + 1) {
> +		struct iio_chan_spec const *chan =
> +					 at91_adc_chan_get(indio_dev, bit);
> +
> +		if (chan->type == IIO_POSITIONRELATIVE)
> +			at91_adc_read_position(st, chan->channel, &val);
> +		else if (chan->type == IIO_PRESSURE)
> +			at91_adc_read_pressure(st, chan->channel, &val);
> +		else
> +			continue;
> +		st->buffer[i] = val;
> +		i++;
> +	}
> +	/*
> +	 * Schedule work to push to buffers.
> +	 * This is intended to push to the callback buffer that another driver
> +	 * registered. We are still in a handler from our IRQ. If we push
> +	 * directly, it means the other driver has it's callback called
> +	 * from our IRQ context. Which is something we better avoid.
> +	 * Let's schedule it after our IRQ is completed.
> +	 */
> +	schedule_work(&st->touch_st.workq);
> +}
> +
> +static void at91_adc_pen_detect_interrupt(struct at91_adc_state *st)
> +{
> +	at91_adc_writel(st, AT91_SAMA5D2_IDR, AT91_SAMA5D2_IER_PEN);
> +	at91_adc_writel(st, AT91_SAMA5D2_IER, AT91_SAMA5D2_IER_NOPEN |
> +			AT91_SAMA5D2_IER_XRDY | AT91_SAMA5D2_IER_YRDY |
> +			AT91_SAMA5D2_IER_PRDY);
> +	at91_adc_writel(st, AT91_SAMA5D2_TRGR,
> +			AT91_SAMA5D2_TRGR_TRGMOD_PERIODIC |
> +			AT91_SAMA5D2_TRGR_TRGPER(st->touch_st.sample_period_val));
> +	st->touch_st.touching = true;
> +}
> +
> +static void at91_adc_no_pen_detect_interrupt(struct at91_adc_state *st)
> +{
> +	struct iio_dev *indio_dev = iio_priv_to_dev(st);
> +
> +	at91_adc_writel(st, AT91_SAMA5D2_TRGR,
> +			AT91_SAMA5D2_TRGR_TRGMOD_NO_TRIGGER);
> +	at91_adc_writel(st, AT91_SAMA5D2_IDR, AT91_SAMA5D2_IER_NOPEN |
> +			AT91_SAMA5D2_IER_XRDY | AT91_SAMA5D2_IER_YRDY |
> +			AT91_SAMA5D2_IER_PRDY);
> +	st->touch_st.touching = false;
> +
> +	at91_adc_touch_data_handler(indio_dev);
> +
> +	at91_adc_writel(st, AT91_SAMA5D2_IER, AT91_SAMA5D2_IER_PEN);
> +}
> +
> +static void at91_adc_workq_handler(struct work_struct *workq)
> +{
> +	struct at91_adc_touch *touch_st = container_of(workq,
> +					struct at91_adc_touch, workq);
> +	struct at91_adc_state *st = container_of(touch_st,
> +					struct at91_adc_state, touch_st);
> +	struct iio_dev *indio_dev = iio_priv_to_dev(st);
> +
> +	iio_push_to_buffers(indio_dev, st->buffer);
>  }
>  
>  static irqreturn_t at91_adc_interrupt(int irq, void *private)
> @@ -757,17 +1150,39 @@ static irqreturn_t at91_adc_interrupt(int irq, void *private)
>  	struct at91_adc_state *st = iio_priv(indio);
>  	u32 status = at91_adc_readl(st, AT91_SAMA5D2_ISR);
>  	u32 imr = at91_adc_readl(st, AT91_SAMA5D2_IMR);
> +	u32 rdy_mask = AT91_SAMA5D2_IER_XRDY | AT91_SAMA5D2_IER_YRDY |
> +			AT91_SAMA5D2_IER_PRDY;
>  
>  	if (!(status & imr))
>  		return IRQ_NONE;
> -
> -	if (iio_buffer_enabled(indio) && !st->dma_st.dma_chan) {
> +	if (status & AT91_SAMA5D2_IER_PEN) {
> +		/* pen detected IRQ */
> +		at91_adc_pen_detect_interrupt(st);
> +	} else if ((status & AT91_SAMA5D2_IER_NOPEN)) {
> +		/* nopen detected IRQ */
> +		at91_adc_no_pen_detect_interrupt(st);
> +	} else if ((status & AT91_SAMA5D2_ISR_PENS) &&
> +		   ((status & rdy_mask) == rdy_mask)) {
> +		/* periodic trigger IRQ - during pen sense */
> +		at91_adc_touch_data_handler(indio);
> +	} else if (status & AT91_SAMA5D2_ISR_PENS) {
> +		/*
> +		 * touching, but the measurements are not ready yet.
> +		 * read and ignore.
> +		 */
> +		status = at91_adc_readl(st, AT91_SAMA5D2_XPOSR);
> +		status = at91_adc_readl(st, AT91_SAMA5D2_YPOSR);
> +		status = at91_adc_readl(st, AT91_SAMA5D2_PRESSR);
> +	} else if (iio_buffer_enabled(indio) && !st->dma_st.dma_chan) {
> +		/* triggered buffer without DMA */
>  		disable_irq_nosync(irq);
>  		iio_trigger_poll(indio->trig);
>  	} else if (iio_buffer_enabled(indio) && st->dma_st.dma_chan) {
> +		/* triggered buffer with DMA - should not happen */
>  		disable_irq_nosync(irq);
>  		WARN(true, "Unexpected irq occurred\n");
>  	} else if (!iio_buffer_enabled(indio)) {
> +		/* software requested conversion */
>  		st->conversion_value = at91_adc_readl(st, st->chan->address);
>  		st->conversion_done = true;
>  		wake_up_interruptible(&st->wq_data_available);
> @@ -775,58 +1190,97 @@ static irqreturn_t at91_adc_interrupt(int irq, void *private)
>  	return IRQ_HANDLED;
>  }
>  
> -static int at91_adc_read_raw(struct iio_dev *indio_dev,
> -			     struct iio_chan_spec const *chan,
> -			     int *val, int *val2, long mask)
> +static int at91_adc_read_info_raw(struct iio_dev *indio_dev,
> +				  struct iio_chan_spec const *chan, int *val)
>  {
>  	struct at91_adc_state *st = iio_priv(indio_dev);
>  	u32 cor = 0;
>  	int ret;
>  
> -	switch (mask) {
> -	case IIO_CHAN_INFO_RAW:
> -		/* we cannot use software trigger if hw trigger enabled */
> +	/*
> +	 * Keep in mind that we cannot use software trigger or touchscreen
> +	 * if external trigger is enabled
> +	 */
> +	if (chan->type == IIO_POSITIONRELATIVE) {
>  		ret = iio_device_claim_direct_mode(indio_dev);
>  		if (ret)
>  			return ret;
>  		mutex_lock(&st->lock);
>  
> -		st->chan = chan;
> +		ret = at91_adc_read_position(st, chan->channel,
> +					     (u16 *)val);
> +		mutex_unlock(&st->lock);
> +		iio_device_release_direct_mode(indio_dev);
>  
> -		if (chan->differential)
> -			cor = (BIT(chan->channel) | BIT(chan->channel2)) <<
> -			      AT91_SAMA5D2_COR_DIFF_OFFSET;
> -
> -		at91_adc_writel(st, AT91_SAMA5D2_COR, cor);
> -		at91_adc_writel(st, AT91_SAMA5D2_CHER, BIT(chan->channel));
> -		at91_adc_writel(st, AT91_SAMA5D2_IER, BIT(chan->channel));
> -		at91_adc_writel(st, AT91_SAMA5D2_CR, AT91_SAMA5D2_CR_START);
> -
> -		ret = wait_event_interruptible_timeout(st->wq_data_available,
> -						       st->conversion_done,
> -						       msecs_to_jiffies(1000));
> -		if (ret == 0)
> -			ret = -ETIMEDOUT;
> -
> -		if (ret > 0) {
> -			*val = st->conversion_value;
> -			if (chan->scan_type.sign == 's')
> -				*val = sign_extend32(*val, 11);
> -			ret = IIO_VAL_INT;
> -			st->conversion_done = false;
> -		}
> +		return ret;
> +	}
> +	if (chan->type == IIO_PRESSURE) {
> +		ret = iio_device_claim_direct_mode(indio_dev);
> +		if (ret)
> +			return ret;
> +		mutex_lock(&st->lock);
>  
> -		at91_adc_writel(st, AT91_SAMA5D2_IDR, BIT(chan->channel));
> -		at91_adc_writel(st, AT91_SAMA5D2_CHDR, BIT(chan->channel));
> +		ret = at91_adc_read_pressure(st, chan->channel,
> +					     (u16 *)val);
> +		mutex_unlock(&st->lock);
> +		iio_device_release_direct_mode(indio_dev);
>  
> -		/* Needed to ACK the DRDY interruption */
> -		at91_adc_readl(st, AT91_SAMA5D2_LCDR);
> +		return ret;
> +	}
>  
> -		mutex_unlock(&st->lock);
> +	/* in this case we have a voltage channel */
>  
> -		iio_device_release_direct_mode(indio_dev);
> +	ret = iio_device_claim_direct_mode(indio_dev);
> +	if (ret)
>  		return ret;
> +	mutex_lock(&st->lock);
> +
> +	st->chan = chan;
> +
> +	if (chan->differential)
> +		cor = (BIT(chan->channel) | BIT(chan->channel2)) <<
> +		      AT91_SAMA5D2_COR_DIFF_OFFSET;
> +
> +	at91_adc_writel(st, AT91_SAMA5D2_COR, cor);
> +	at91_adc_writel(st, AT91_SAMA5D2_CHER, BIT(chan->channel));
> +	at91_adc_writel(st, AT91_SAMA5D2_IER, BIT(chan->channel));
> +	at91_adc_writel(st, AT91_SAMA5D2_CR, AT91_SAMA5D2_CR_START);
> +
> +	ret = wait_event_interruptible_timeout(st->wq_data_available,
> +					       st->conversion_done,
> +					       msecs_to_jiffies(1000));
> +	if (ret == 0)
> +		ret = -ETIMEDOUT;
> +
> +	if (ret > 0) {
> +		*val = st->conversion_value;
> +		if (chan->scan_type.sign == 's')
> +			*val = sign_extend32(*val, 11);
> +		ret = IIO_VAL_INT;
> +		st->conversion_done = false;
> +	}
> +
> +	at91_adc_writel(st, AT91_SAMA5D2_IDR, BIT(chan->channel));
> +	at91_adc_writel(st, AT91_SAMA5D2_CHDR, BIT(chan->channel));
> +
> +	/* Needed to ACK the DRDY interruption */
> +	at91_adc_readl(st, AT91_SAMA5D2_LCDR);
> +
> +	mutex_unlock(&st->lock);
> +
> +	iio_device_release_direct_mode(indio_dev);
> +	return ret;
> +}
> +
> +static int at91_adc_read_raw(struct iio_dev *indio_dev,
> +			     struct iio_chan_spec const *chan,
> +			     int *val, int *val2, long mask)
> +{
> +	struct at91_adc_state *st = iio_priv(indio_dev);
>  
> +	switch (mask) {
> +	case IIO_CHAN_INFO_RAW:
> +		return at91_adc_read_info_raw(indio_dev, chan, val);
>  	case IIO_CHAN_INFO_SCALE:
>  		*val = st->vref_uv / 1000;
>  		if (chan->differential)
> @@ -974,9 +1428,29 @@ static int at91_adc_set_watermark(struct iio_dev *indio_dev, unsigned int val)
>  	return 0;
>  }
>  
> +static int at91_adc_update_scan_mode(struct iio_dev *indio_dev,
> +				     const unsigned long *scan_mask)
> +{
> +	struct at91_adc_state *st = iio_priv(indio_dev);
> +
> +	if (bitmap_subset(scan_mask, &st->touch_st.channels_bitmask,
> +			  AT91_SAMA5D2_MAX_CHAN_IDX + 1))
> +		return 0;
> +	/*
> +	 * if the new bitmap is a combination of touchscreen and regular
> +	 * channels, then we are not fine
> +	 */
> +	if (bitmap_intersects(&st->touch_st.channels_bitmask, scan_mask,
> +			      AT91_SAMA5D2_MAX_CHAN_IDX + 1))
> +		return -EINVAL;
> +	return 0;
> +}
> +
>  static const struct iio_info at91_adc_info = {
>  	.read_raw = &at91_adc_read_raw,
>  	.write_raw = &at91_adc_write_raw,
> +	.update_scan_mode = &at91_adc_update_scan_mode,
> +	.of_xlate = &at91_adc_of_xlate,
>  	.hwfifo_set_watermark = &at91_adc_set_watermark,
>  };
>  
> @@ -1044,13 +1518,20 @@ static int at91_adc_probe(struct platform_device *pdev)
>  
>  	indio_dev->dev.parent = &pdev->dev;
>  	indio_dev->name = dev_name(&pdev->dev);
> -	indio_dev->modes = INDIO_DIRECT_MODE;
> +	indio_dev->modes = INDIO_DIRECT_MODE | INDIO_BUFFER_SOFTWARE;
>  	indio_dev->info = &at91_adc_info;
>  	indio_dev->channels = at91_adc_channels;
>  	indio_dev->num_channels = ARRAY_SIZE(at91_adc_channels);
>  
>  	st = iio_priv(indio_dev);
>  
> +	bitmap_set(&st->touch_st.channels_bitmask,
> +		   AT91_SAMA5D2_TOUCH_X_CHAN_IDX, 1);
> +	bitmap_set(&st->touch_st.channels_bitmask,
> +		   AT91_SAMA5D2_TOUCH_Y_CHAN_IDX, 1);
> +	bitmap_set(&st->touch_st.channels_bitmask,
> +		   AT91_SAMA5D2_TOUCH_P_CHAN_IDX, 1);
> +
>  	ret = of_property_read_u32(pdev->dev.of_node,
>  				   "atmel,min-sample-rate-hz",
>  				   &st->soc_info.min_sample_rate);
> @@ -1100,6 +1581,7 @@ static int at91_adc_probe(struct platform_device *pdev)
>  
>  	init_waitqueue_head(&st->wq_data_available);
>  	mutex_init(&st->lock);
> +	INIT_WORK(&st->touch_st.workq, at91_adc_workq_handler);
>  
>  	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
>  	if (!res)
> @@ -1159,13 +1641,13 @@ static int at91_adc_probe(struct platform_device *pdev)
>  
>  	platform_set_drvdata(pdev, indio_dev);
>  
> -	if (st->selected_trig->hw_trig) {
> -		ret = at91_adc_buffer_init(indio_dev);
> -		if (ret < 0) {
> -			dev_err(&pdev->dev, "couldn't initialize the buffer.\n");
> -			goto per_clk_disable_unprepare;
> -		}
> +	ret = at91_adc_buffer_init(indio_dev);
> +	if (ret < 0) {
> +		dev_err(&pdev->dev, "couldn't initialize the buffer.\n");
> +		goto per_clk_disable_unprepare;
> +	}
>  
> +	if (st->selected_trig->hw_trig) {
>  		ret = at91_adc_trigger_init(indio_dev);
>  		if (ret < 0) {
>  			dev_err(&pdev->dev, "couldn't setup the triggers.\n");
> @@ -1272,9 +1754,20 @@ static __maybe_unused int at91_adc_resume(struct device *dev)
>  	at91_adc_hw_init(st);
>  
>  	/* reconfiguring trigger hardware state */
> -	if (iio_buffer_enabled(indio_dev))
> -		at91_adc_configure_trigger(st->trig, true);
> +	if (!iio_buffer_enabled(indio_dev))
> +		return 0;
> +
> +	/* check if we are enabling triggered buffer or the touchscreen */
> +	if (bitmap_subset(indio_dev->active_scan_mask,
> +			  &st->touch_st.channels_bitmask,
> +			  AT91_SAMA5D2_MAX_CHAN_IDX + 1)) {
> +		/* touchscreen enabling */
> +		return at91_adc_configure_touch(st, true);
> +	} else {
> +		return at91_adc_configure_trigger(st->trig, true);
> +	}
>  
> +	/* not needed but more explicit */
>  	return 0;
>  
>  vref_disable_resume:
> -- 
> 2.7.4
> 

^ permalink raw reply

* Re: [PATCH v7 7/9] dt-bindings: iio: adc: at91-sama5d2_adc: add channel specific consumer info
From: Ludovic Desroches @ 2018-05-22 12:23 UTC (permalink / raw)
  To: Eugen Hristev
  Cc: jic23, ludovic.desroches, alexandre.belloni, linux-arm-kernel,
	devicetree, linux-kernel, linux-iio, linux-input, nicolas.ferre,
	dmitry.torokhov, robh
In-Reply-To: <1526975559-18966-8-git-send-email-eugen.hristev@microchip.com>

On Tue, May 22, 2018 at 10:52:37AM +0300, Eugen Hristev wrote:
> Added defines for channel consumer device-tree binding
> 
> Signed-off-by: Eugen Hristev <eugen.hristev@microchip.com>
> Reviewed-by: Rob Herring <robh@kernel.org>
Acked-by: Ludovic Desroches <ludovic.desroches@microchip.com>

> ---
>  .../devicetree/bindings/iio/adc/at91-sama5d2_adc.txt     |  9 +++++++++
>  include/dt-bindings/iio/adc/at91-sama5d2_adc.h           | 16 ++++++++++++++++
>  2 files changed, 25 insertions(+)
>  create mode 100644 include/dt-bindings/iio/adc/at91-sama5d2_adc.h
> 
> diff --git a/Documentation/devicetree/bindings/iio/adc/at91-sama5d2_adc.txt b/Documentation/devicetree/bindings/iio/adc/at91-sama5d2_adc.txt
> index 6469a4c..4a3c1d4 100644
> --- a/Documentation/devicetree/bindings/iio/adc/at91-sama5d2_adc.txt
> +++ b/Documentation/devicetree/bindings/iio/adc/at91-sama5d2_adc.txt
> @@ -21,6 +21,14 @@ Optional properties:
>    - dmas: Phandle to dma channel for the ADC.
>    - dma-names: Must be "rx" when dmas property is being used.
>    See ../../dma/dma.txt for details.
> +  - #io-channel-cells: in case consumer drivers are attached, this must be 1.
> +  See <Documentation/devicetree/bindings/iio/iio-bindings.txt> for details.
> +
> +Properties for consumer drivers:
> +  - Consumer drivers can be connected to this producer device, as specified
> +  in <Documentation/devicetree/bindings/iio/iio-bindings.txt>
> +  - Channels exposed are specified in:
> +  <dt-bindings/iio/adc/at91-sama5d2_adc.txt>
>  
>  Example:
>  
> @@ -38,4 +46,5 @@ adc: adc@fc030000 {
>  	atmel,trigger-edge-type = <IRQ_TYPE_EDGE_BOTH>;
>  	dmas = <&dma0 (AT91_XDMAC_DT_MEM_IF(0) | AT91_XDMAC_DT_PER_IF(1) | AT91_XDMAC_DT_PERID(25))>;
>  	dma-names = "rx";
> +	#io-channel-cells = <1>;
>  }
> diff --git a/include/dt-bindings/iio/adc/at91-sama5d2_adc.h b/include/dt-bindings/iio/adc/at91-sama5d2_adc.h
> new file mode 100644
> index 0000000..70f99db
> --- /dev/null
> +++ b/include/dt-bindings/iio/adc/at91-sama5d2_adc.h
> @@ -0,0 +1,16 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * This header provides constants for configuring the AT91 SAMA5D2 ADC
> + */
> +
> +#ifndef _DT_BINDINGS_IIO_ADC_AT91_SAMA5D2_ADC_H
> +#define _DT_BINDINGS_IIO_ADC_AT91_SAMA5D2_ADC_H
> +
> +/* X relative position channel index */
> +#define AT91_SAMA5D2_ADC_X_CHANNEL		24
> +/* Y relative position channel index */
> +#define AT91_SAMA5D2_ADC_Y_CHANNEL		25
> +/* pressure channel index */
> +#define AT91_SAMA5D2_ADC_P_CHANNEL		26
> +
> +#endif
> -- 
> 2.7.4
> 

^ permalink raw reply

* Re: [PATCH 1/2] clk: imx6ul: add GPIO clock gates
From: Stefan Wahren @ 2018-05-22 12:25 UTC (permalink / raw)
  To: michael, robh+dt, fabio.estevam, mark.rutland, Anson Huang,
	matteo.lisi, shawnguo, kernel, mturquette, sboyd
  Cc: linux-clk, Linux-imx, devicetree, linux-kernel, linux-arm-kernel
In-Reply-To: <1526959560-6014-1-git-send-email-Anson.Huang@nxp.com>

Hi Anson,

> Anson Huang <Anson.Huang@nxp.com> hat am 22. Mai 2018 um 05:25 geschrieben:
> 
> 
> i.MX6UL has GPIO clock gates in CCM CCGR, add
> them into clock tree for clock management.
> 
> Signed-off-by: Anson Huang <Anson.Huang@nxp.com>
> ---
>  drivers/clk/imx/clk-imx6ul.c             |  5 +++++
>  include/dt-bindings/clock/imx6ul-clock.h | 31 ++++++++++++++++++-------------
>  2 files changed, 23 insertions(+), 13 deletions(-)
> 
> diff --git a/drivers/clk/imx/clk-imx6ul.c b/drivers/clk/imx/clk-imx6ul.c
> index ba563ba..3ea2d97 100644
> --- a/drivers/clk/imx/clk-imx6ul.c
> +++ b/drivers/clk/imx/clk-imx6ul.c
> @@ -360,6 +360,7 @@ static void __init imx6ul_clocks_init(struct device_node *ccm_node)
>  	clks[IMX6UL_CLK_UART2_SERIAL]	= imx_clk_gate2("uart2_serial",	"uart_podf",	base + 0x68,	28);
>  	if (clk_on_imx6ull())
>  		clks[IMX6UL_CLK_AIPSTZ3]	= imx_clk_gate2("aips_tz3",	"ahb",		 base + 0x80,	18);
> +	clks[IMX6UL_CLK_GPIO2]		= imx_clk_gate2("gpio2",	"ipg",		base + 0x68,	30);
>  
>  	/* CCGR1 */
>  	clks[IMX6UL_CLK_ECSPI1]		= imx_clk_gate2("ecspi1",	"ecspi_podf",	base + 0x6c,	0);
> @@ -376,6 +377,8 @@ static void __init imx6ul_clocks_init(struct device_node *ccm_node)
>  	clks[IMX6UL_CLK_GPT1_SERIAL]	= imx_clk_gate2("gpt1_serial",	"perclk",	base + 0x6c,	22);
>  	clks[IMX6UL_CLK_UART4_IPG]	= imx_clk_gate2("uart4_ipg",	"ipg",		base + 0x6c,	24);
>  	clks[IMX6UL_CLK_UART4_SERIAL]	= imx_clk_gate2("uart4_serial",	"uart_podf",	base + 0x6c,	24);
> +	clks[IMX6UL_CLK_GPIO1]		= imx_clk_gate2("gpio1",	"ipg",		base + 0x6c,	26);
> +	clks[IMX6UL_CLK_GPIO5]		= imx_clk_gate2("gpio5",	"ipg",		base + 0x6c,	30);
>  
>  	/* CCGR2 */
>  	if (clk_on_imx6ull()) {
> @@ -389,6 +392,7 @@ static void __init imx6ul_clocks_init(struct device_node *ccm_node)
>  	clks[IMX6UL_CLK_I2C3]		= imx_clk_gate2("i2c3",		"perclk",	base + 0x70,	10);
>  	clks[IMX6UL_CLK_OCOTP]		= imx_clk_gate2("ocotp",	"ipg",		base + 0x70,	12);
>  	clks[IMX6UL_CLK_IOMUXC]		= imx_clk_gate2("iomuxc",	"lcdif_podf",	base + 0x70,	14);
> +	clks[IMX6UL_CLK_GPIO3]		= imx_clk_gate2("gpio3",	"ipg",		base + 0x70,	26);
>  	clks[IMX6UL_CLK_LCDIF_APB]	= imx_clk_gate2("lcdif_apb",	"axi",		base + 0x70,	28);
>  	clks[IMX6UL_CLK_PXP]		= imx_clk_gate2("pxp",		"axi",		base + 0x70,	30);
>  
> @@ -405,6 +409,7 @@ static void __init imx6ul_clocks_init(struct device_node *ccm_node)
>  	clks[IMX6UL_CLK_UART6_IPG]	= imx_clk_gate2("uart6_ipg",	"ipg",		base + 0x74,	6);
>  	clks[IMX6UL_CLK_UART6_SERIAL]	= imx_clk_gate2("uart6_serial",	"uart_podf",	base + 0x74,	6);
>  	clks[IMX6UL_CLK_LCDIF_PIX]	= imx_clk_gate2("lcdif_pix",	"lcdif_podf",	base + 0x74,	10);
> +	clks[IMX6UL_CLK_GPIO4]		= imx_clk_gate2("gpio4",	"ipg",		base + 0x74,	12);
>  	clks[IMX6UL_CLK_QSPI]		= imx_clk_gate2("qspi1",	"qspi1_podf",	base + 0x74,	14);
>  	clks[IMX6UL_CLK_WDOG1]		= imx_clk_gate2("wdog1",	"ipg",		base + 0x74,	16);
>  	clks[IMX6UL_CLK_MMDC_P0_FAST]	= imx_clk_gate("mmdc_p0_fast", "mmdc_podf", base + 0x74,	20);
> diff --git a/include/dt-bindings/clock/imx6ul-clock.h b/include/dt-bindings/clock/imx6ul-clock.h
> index 9564597..1291328 100644
> --- a/include/dt-bindings/clock/imx6ul-clock.h
> +++ b/include/dt-bindings/clock/imx6ul-clock.h
> @@ -242,20 +242,25 @@
>  #define IMX6UL_CLK_CKO2_PODF		229
>  #define IMX6UL_CLK_CKO2			230
>  #define IMX6UL_CLK_CKO			231
> +#define IMX6UL_CLK_GPIO1		232
> +#define IMX6UL_CLK_GPIO2		233
> +#define IMX6UL_CLK_GPIO3		234
> +#define IMX6UL_CLK_GPIO4		235
> +#define IMX6UL_CLK_GPIO5		236

this change looks like a breakage of devicetree ABI. You are changing the mean of the existing clock IDs on i.MX6ULL, which probably regress the combination of older DTBs with newer kernel.

>  
>  /* For i.MX6ULL */
> -#define IMX6ULL_CLK_ESAI_PRED		232
> -#define IMX6ULL_CLK_ESAI_PODF		233
> -#define IMX6ULL_CLK_ESAI_EXTAL		234
> -#define IMX6ULL_CLK_ESAI_MEM		235
> -#define IMX6ULL_CLK_ESAI_IPG		236
> -#define IMX6ULL_CLK_DCP_CLK		237
> -#define IMX6ULL_CLK_EPDC_PRE_SEL	238
> -#define IMX6ULL_CLK_EPDC_SEL		239
> -#define IMX6ULL_CLK_EPDC_PODF		240
> -#define IMX6ULL_CLK_EPDC_ACLK		241
> -#define IMX6ULL_CLK_EPDC_PIX		242
> -#define IMX6ULL_CLK_ESAI_SEL		243
> -#define IMX6UL_CLK_END			244
> +#define IMX6ULL_CLK_ESAI_PRED		237
> +#define IMX6ULL_CLK_ESAI_PODF		238
> +#define IMX6ULL_CLK_ESAI_EXTAL		239
> +#define IMX6ULL_CLK_ESAI_MEM		240
> +#define IMX6ULL_CLK_ESAI_IPG		241
> +#define IMX6ULL_CLK_DCP_CLK		242
> +#define IMX6ULL_CLK_EPDC_PRE_SEL	243
> +#define IMX6ULL_CLK_EPDC_SEL		244
> +#define IMX6ULL_CLK_EPDC_PODF		245
> +#define IMX6ULL_CLK_EPDC_ACLK		246
> +#define IMX6ULL_CLK_EPDC_PIX		247
> +#define IMX6ULL_CLK_ESAI_SEL		248
> +#define IMX6UL_CLK_END			249
>  
>  #endif /* __DT_BINDINGS_CLOCK_IMX6UL_H */
> -- 
> 2.7.4
> 
> 
> _______________________________________________
> linux-arm-kernel mailing list
> linux-arm-kernel@lists.infradead.org
> http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply

* Re: [PATCH 1/9] ARM: shmobile: defconfig: Enable MTD command line partition parsing
From: Wolfram Sang @ 2018-05-22 12:32 UTC (permalink / raw)
  To: Marek Vasut
  Cc: devicetree, Geert Uytterhoeven, linux-renesas-soc,
	Laurent Pinchart, Simon Horman, linux-arm-kernel, Marek Vasut
In-Reply-To: <20180522120257.13232-1-marek.vasut+renesas@gmail.com>


[-- Attachment #1.1: Type: text/plain, Size: 813 bytes --]

On Tue, May 22, 2018 at 02:02:49PM +0200, Marek Vasut wrote:
> In preparation for removing MTD partitioning from the DTs and moving
> it over to kernel command line partition parsing, enable the support
> for kernel command line MTD partition parsing.
> 
> The argument for not having MTD partitions in the DT is the same as
> for not having hard drive partitions in DT, neither describes the
> hardware itself, so it shouldn't be in the DT. Furthermore, kernel
> command line MTD partition passing allows greater flexibility in
> case someone decided to repartition the flash, which is well in the
> realm of possibility with these systems.
> 
> Signed-off-by: Marek Vasut <marek.vasut+renesas@gmail.com>

Makes a lot of sense to me.

Acked-by: Wolfram Sang <wsa+renesas@sang-engineering.com>


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

[-- Attachment #2: Type: text/plain, Size: 176 bytes --]

_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply

* Re: [PATCH 2/9] ARM: dts: lager: Drop MTD partitioning from DT
From: Wolfram Sang @ 2018-05-22 12:34 UTC (permalink / raw)
  To: Marek Vasut
  Cc: devicetree, Geert Uytterhoeven, linux-renesas-soc,
	Laurent Pinchart, Simon Horman, linux-arm-kernel, Marek Vasut
In-Reply-To: <20180522120257.13232-2-marek.vasut+renesas@gmail.com>


[-- Attachment #1.1: Type: text/plain, Size: 814 bytes --]

On Tue, May 22, 2018 at 02:02:50PM +0200, Marek Vasut wrote:
> Drop the MTD partitioning from DT, since it does not describe HW
> and to give way to a more flexible kernel command line partition
> passing.
> 
> To retain the original partitioning, assure you have enabled
> CONFIG_MTD_CMDLINE_PARTS in your kernel config and add the
> following to your kernel command line:
> 
>   mtdparts=spi0.0:256k@0(loader),4096k(user),-(flash)
> 
> Signed-off-by: Marek Vasut <marek.vasut+renesas@gmail.com>
> Cc: Geert Uytterhoeven <geert+renesas@glider.be>
> Cc: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> Cc: Simon Horman <horms+renesas@verge.net.au>
> Cc: Wolfram Sang <wsa@the-dreams.de>
> Cc: linux-renesas-soc@vger.kernel.org

Acked-by: Wolfram Sang <wsa+renesas@sang-engineering.com>


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

[-- Attachment #2: Type: text/plain, Size: 176 bytes --]

_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply

* Re: [PATCH 3/9] ARM: dts: stout: Drop MTD partitioning from DT
From: Wolfram Sang @ 2018-05-22 12:34 UTC (permalink / raw)
  To: Marek Vasut
  Cc: devicetree, Geert Uytterhoeven, linux-renesas-soc,
	Laurent Pinchart, Simon Horman, linux-arm-kernel, Marek Vasut
In-Reply-To: <20180522120257.13232-3-marek.vasut+renesas@gmail.com>


[-- Attachment #1.1: Type: text/plain, Size: 830 bytes --]

On Tue, May 22, 2018 at 02:02:51PM +0200, Marek Vasut wrote:
> Drop the MTD partitioning from DT, since it does not describe HW
> and to give way to a more flexible kernel command line partition
> passing.
> 
> To retain the original partitioning, assure you have enabled
> CONFIG_MTD_CMDLINE_PARTS in your kernel config and add the
> following to your kernel command line:
> 
>   mtdparts=spi0.0:512k@0(loader),256k(uboot),256k(uboot-env),-(flash)
> 
> Signed-off-by: Marek Vasut <marek.vasut+renesas@gmail.com>
> Cc: Geert Uytterhoeven <geert+renesas@glider.be>
> Cc: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> Cc: Simon Horman <horms+renesas@verge.net.au>
> Cc: Wolfram Sang <wsa@the-dreams.de>
> Cc: linux-renesas-soc@vger.kernel.org

Acked-by: Wolfram Sang <wsa+renesas@sang-engineering.com>


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

[-- Attachment #2: Type: text/plain, Size: 176 bytes --]

_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply

* Re: [PATCH 4/9] ARM: dts: koelsch: Drop MTD partitioning from DT
From: Wolfram Sang @ 2018-05-22 12:34 UTC (permalink / raw)
  To: Marek Vasut
  Cc: devicetree, Geert Uytterhoeven, linux-renesas-soc,
	Laurent Pinchart, Simon Horman, linux-arm-kernel, Marek Vasut
In-Reply-To: <20180522120257.13232-4-marek.vasut+renesas@gmail.com>


[-- Attachment #1.1: Type: text/plain, Size: 814 bytes --]

On Tue, May 22, 2018 at 02:02:52PM +0200, Marek Vasut wrote:
> Drop the MTD partitioning from DT, since it does not describe HW
> and to give way to a more flexible kernel command line partition
> passing.
> 
> To retain the original partitioning, assure you have enabled
> CONFIG_MTD_CMDLINE_PARTS in your kernel config and add the
> following to your kernel command line:
> 
>   mtdparts=spi0.0:512k@0(loader),5632k(user),-(flash)
> 
> Signed-off-by: Marek Vasut <marek.vasut+renesas@gmail.com>
> Cc: Geert Uytterhoeven <geert+renesas@glider.be>
> Cc: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> Cc: Simon Horman <horms+renesas@verge.net.au>
> Cc: Wolfram Sang <wsa@the-dreams.de>
> Cc: linux-renesas-soc@vger.kernel.org

Acked-by: Wolfram Sang <wsa+renesas@sang-engineering.com>


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

[-- Attachment #2: Type: text/plain, Size: 176 bytes --]

_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply

* Re: [PATCH 5/9] ARM: dts: porter: Drop MTD partitioning from DT
From: Wolfram Sang @ 2018-05-22 12:34 UTC (permalink / raw)
  To: Marek Vasut
  Cc: devicetree, Geert Uytterhoeven, linux-renesas-soc,
	Laurent Pinchart, Simon Horman, linux-arm-kernel, Marek Vasut
In-Reply-To: <20180522120257.13232-5-marek.vasut+renesas@gmail.com>


[-- Attachment #1.1: Type: text/plain, Size: 825 bytes --]

On Tue, May 22, 2018 at 02:02:53PM +0200, Marek Vasut wrote:
> Drop the MTD partitioning from DT, since it does not describe HW
> and to give way to a more flexible kernel command line partition
> passing.
> 
> To retain the original partitioning, assure you have enabled
> CONFIG_MTD_CMDLINE_PARTS in your kernel config and add the
> following to your kernel command line:
> 
>   mtdparts=spi0.0:256k@0(loader_prg),4096k(user_prg),-(flash_fs)
> 
> Signed-off-by: Marek Vasut <marek.vasut+renesas@gmail.com>
> Cc: Geert Uytterhoeven <geert+renesas@glider.be>
> Cc: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> Cc: Simon Horman <horms+renesas@verge.net.au>
> Cc: Wolfram Sang <wsa@the-dreams.de>
> Cc: linux-renesas-soc@vger.kernel.org

Acked-by: Wolfram Sang <wsa+renesas@sang-engineering.com>


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

[-- Attachment #2: Type: text/plain, Size: 176 bytes --]

_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply

* Re: [PATCH 6/9] ARM: dts: wheat: Drop MTD partitioning from DT
From: Wolfram Sang @ 2018-05-22 12:34 UTC (permalink / raw)
  To: Marek Vasut
  Cc: devicetree, Geert Uytterhoeven, linux-renesas-soc,
	Laurent Pinchart, Simon Horman, linux-arm-kernel, Marek Vasut
In-Reply-To: <20180522120257.13232-6-marek.vasut+renesas@gmail.com>


[-- Attachment #1.1: Type: text/plain, Size: 814 bytes --]

On Tue, May 22, 2018 at 02:02:54PM +0200, Marek Vasut wrote:
> Drop the MTD partitioning from DT, since it does not describe HW
> and to give way to a more flexible kernel command line partition
> passing.
> 
> To retain the original partitioning, assure you have enabled
> CONFIG_MTD_CMDLINE_PARTS in your kernel config and add the
> following to your kernel command line:
> 
>   mtdparts=spi0.0:256k@0(loader),4096k(user),-(flash)
> 
> Signed-off-by: Marek Vasut <marek.vasut+renesas@gmail.com>
> Cc: Geert Uytterhoeven <geert+renesas@glider.be>
> Cc: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> Cc: Simon Horman <horms+renesas@verge.net.au>
> Cc: Wolfram Sang <wsa@the-dreams.de>
> Cc: linux-renesas-soc@vger.kernel.org

Acked-by: Wolfram Sang <wsa+renesas@sang-engineering.com>


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

[-- Attachment #2: Type: text/plain, Size: 176 bytes --]

_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply

* Re: [PATCH 7/9] ARM: dts: gose: Drop MTD partitioning from DT
From: Wolfram Sang @ 2018-05-22 12:34 UTC (permalink / raw)
  To: Marek Vasut
  Cc: devicetree, Geert Uytterhoeven, linux-renesas-soc,
	Laurent Pinchart, Simon Horman, linux-arm-kernel, Marek Vasut
In-Reply-To: <20180522120257.13232-7-marek.vasut+renesas@gmail.com>


[-- Attachment #1.1: Type: text/plain, Size: 814 bytes --]

On Tue, May 22, 2018 at 02:02:55PM +0200, Marek Vasut wrote:
> Drop the MTD partitioning from DT, since it does not describe HW
> and to give way to a more flexible kernel command line partition
> passing.
> 
> To retain the original partitioning, assure you have enabled
> CONFIG_MTD_CMDLINE_PARTS in your kernel config and add the
> following to your kernel command line:
> 
>   mtdparts=spi0.0:256k@0(loader),4096k(user),-(flash)
> 
> Signed-off-by: Marek Vasut <marek.vasut+renesas@gmail.com>
> Cc: Geert Uytterhoeven <geert+renesas@glider.be>
> Cc: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> Cc: Simon Horman <horms+renesas@verge.net.au>
> Cc: Wolfram Sang <wsa@the-dreams.de>
> Cc: linux-renesas-soc@vger.kernel.org

Acked-by: Wolfram Sang <wsa+renesas@sang-engineering.com>


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

[-- Attachment #2: Type: text/plain, Size: 176 bytes --]

_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply

* Re: [PATCH 8/9] ARM: dts: alt: Drop MTD partitioning from DT
From: Wolfram Sang @ 2018-05-22 12:34 UTC (permalink / raw)
  To: Marek Vasut
  Cc: devicetree, Geert Uytterhoeven, linux-renesas-soc,
	Laurent Pinchart, Simon Horman, linux-arm-kernel, Marek Vasut
In-Reply-To: <20180522120257.13232-8-marek.vasut+renesas@gmail.com>


[-- Attachment #1.1: Type: text/plain, Size: 814 bytes --]

On Tue, May 22, 2018 at 02:02:56PM +0200, Marek Vasut wrote:
> Drop the MTD partitioning from DT, since it does not describe HW
> and to give way to a more flexible kernel command line partition
> passing.
> 
> To retain the original partitioning, assure you have enabled
> CONFIG_MTD_CMDLINE_PARTS in your kernel config and add the
> following to your kernel command line:
> 
>   mtdparts=spi0.0:256k@0(loader),256k(system),-(user)
> 
> Signed-off-by: Marek Vasut <marek.vasut+renesas@gmail.com>
> Cc: Geert Uytterhoeven <geert+renesas@glider.be>
> Cc: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> Cc: Simon Horman <horms+renesas@verge.net.au>
> Cc: Wolfram Sang <wsa@the-dreams.de>
> Cc: linux-renesas-soc@vger.kernel.org

Acked-by: Wolfram Sang <wsa+renesas@sang-engineering.com>


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

[-- Attachment #2: Type: text/plain, Size: 176 bytes --]

_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply

* Re: [PATCH 9/9] ARM: dts: silk: Drop MTD partitioning from DT
From: Wolfram Sang @ 2018-05-22 12:34 UTC (permalink / raw)
  To: Marek Vasut
  Cc: devicetree, Geert Uytterhoeven, linux-renesas-soc,
	Laurent Pinchart, Simon Horman, linux-arm-kernel, Marek Vasut
In-Reply-To: <20180522120257.13232-9-marek.vasut+renesas@gmail.com>


[-- Attachment #1.1: Type: text/plain, Size: 814 bytes --]

On Tue, May 22, 2018 at 02:02:57PM +0200, Marek Vasut wrote:
> Drop the MTD partitioning from DT, since it does not describe HW
> and to give way to a more flexible kernel command line partition
> passing.
> 
> To retain the original partitioning, assure you have enabled
> CONFIG_MTD_CMDLINE_PARTS in your kernel config and add the
> following to your kernel command line:
> 
>   mtdparts=spi0.0:256k@0(loader),4096k(user),-(flash)
> 
> Signed-off-by: Marek Vasut <marek.vasut+renesas@gmail.com>
> Cc: Geert Uytterhoeven <geert+renesas@glider.be>
> Cc: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> Cc: Simon Horman <horms+renesas@verge.net.au>
> Cc: Wolfram Sang <wsa@the-dreams.de>
> Cc: linux-renesas-soc@vger.kernel.org

Acked-by: Wolfram Sang <wsa+renesas@sang-engineering.com>


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

[-- Attachment #2: Type: text/plain, Size: 176 bytes --]

_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply


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