All of lore.kernel.org
 help / color / mirror / Atom feed
* [RFC 00/12] Marvell NAND controller rework with ->exec_op()
@ 2017-10-18 14:36 ` Miquel Raynal
  0 siblings, 0 replies; 75+ messages in thread
From: Miquel Raynal @ 2017-10-18 14:36 UTC (permalink / raw)
  To: Andrew Lunn, bcm-kernel-feedback-list, Boris Brezillon,
	Brian Norris, Catalin Marinas, Chen-Yu Tsai, Cyrille Pitchen,
	Daniel Mack, David Woodhouse, devel, devicetree, Ezequiel Garcia,
	Greg Kroah-Hartman, Gregory Clement, Han Xu, Haojian Zhuang,
	Jason Cooper, Josh Wu, Kamal Dasu, linux-arm-kernel,
	linux-kernel, linux-mediatek, linux-mtd, Marc Gonzalez,
	Marek Vasut, Mark Rutland, Masahiro Yamada, Matthias Brugger,
	Maxime Ripard, Maxim Levitsky, Richard Weinberger,
	Robert Jarzmik, Rob Herring, Russell King, Sebastian Hesselbarth,
	Stefan Agner, Sylvain Lemieux, Vladimir Zapolskiy, Wenyou Yang,
	Will Deacon
  Cc: Thomas Petazzoni, Antoine Tenart, Miquel Raynal, Igor Grinberg,
	Nadav Haklai, Ofer Heifetz, Neta Zur Hershkovits, Hanna Hawa

Hi,

This series adds the implementation of the NAND framework ->exec_op()
interface with all the related hooks and helpers. The reasons for adding
it are explained in details in the commit log "mtd: nand: add
 ->exec_op() implementation", but mostly it will ease later expansion of
the framework, as well as the implementation of new vendor specific
commands, and should also ease driver development.

The series contains as well the reworked NAND controller driver from
Marvell.

A lot of comments are written to explain how to use the new API and the
Marvell driver shows how to implement it. A proper external
documentation is being written and will later be submitted.

It also changes the device tree NAND node definition for all platforms
referring to the Marvell driver to use the new bindings. They are more
hierarchical and fit the real organization of the hardware, by having
NAND partitions that are part of NAND chip nodes, themselves part of
the NAND controller node.

See the commit log of "mtd: nand: add reworked Marvell NAND controller
driver" for details about why a completely new driver is needed.

As this series changes several core functions and also directly touches
multiple controller drivers, it would be great to have reviews and tests
from the concerned maintainers. Only PXA3xx SoCs are not supported yet
(explaining the RFC) as having a local board to test is really needed.

Thank you,
Miquèl


Boris Brezillon (2):
  mtd: nand: provide several helpers to do common NAND operations
  mtd: nand: force drivers to explicitly send READ/PROG commands

Miquel Raynal (10):
  mtd: nand: use a static data_interface in the nand_chip structure
  mtd: nand: add ->exec_op() implementation
  dt-bindings: mtd: add Marvell NAND controller documentation
  mtd: nand: add reworked Marvell NAND controller driver
  ARM: dts: armada-370-xp: use reworked NAND controller driver
  ARM: dts: armada-375: use reworked NAND controller driver
  ARM: dts: armada-38x: use reworked NAND controller driver
  ARM: dts: armada-39x: use reworked NAND controller driver
  ARM: dts: pxa: use reworked NAND controller driver
  ARM64: dts: marvell: use reworked NAND controller driver on Armada
    7K/8K

 .../devicetree/bindings/mtd/marvell-nand.txt       |   95 +
 arch/arm/boot/dts/armada-370-db.dts                |   56 +-
 arch/arm/boot/dts/armada-370-dlink-dns327l.dts     |  119 +-
 arch/arm/boot/dts/armada-370-mirabox.dts           |   50 +-
 arch/arm/boot/dts/armada-370-netgear-rn102.dts     |   89 +-
 arch/arm/boot/dts/armada-370-netgear-rn104.dts     |   89 +-
 arch/arm/boot/dts/armada-370-rd.dts                |   51 +-
 arch/arm/boot/dts/armada-370-seagate-nas-xbay.dtsi |   63 +-
 arch/arm/boot/dts/armada-370-xp.dtsi               |    6 +-
 arch/arm/boot/dts/armada-375-db.dts                |   49 +-
 arch/arm/boot/dts/armada-375.dtsi                  |    6 +-
 arch/arm/boot/dts/armada-385-db-ap.dts             |   68 +-
 arch/arm/boot/dts/armada-385-linksys-caiman.dts    |  129 +-
 arch/arm/boot/dts/armada-385-linksys-cobra.dts     |  129 +-
 arch/arm/boot/dts/armada-385-linksys-rango.dts     |  141 +-
 arch/arm/boot/dts/armada-385-linksys-shelby.dts    |  129 +-
 arch/arm/boot/dts/armada-385-linksys.dtsi          |   15 +-
 arch/arm/boot/dts/armada-388-db.dts                |   54 +-
 arch/arm/boot/dts/armada-38x.dtsi                  |    6 +-
 arch/arm/boot/dts/armada-390-db.dts                |   65 +-
 arch/arm/boot/dts/armada-395-gp.dts                |   73 +-
 arch/arm/boot/dts/armada-398-db.dts                |   59 +-
 arch/arm/boot/dts/armada-39x.dtsi                  |    6 +-
 arch/arm/boot/dts/armada-xp-db-dxbc2.dts           |    1 -
 arch/arm/boot/dts/armada-xp-db-xc3-24g4xg.dts      |    1 -
 arch/arm/boot/dts/armada-xp-db.dts                 |    1 -
 arch/arm/boot/dts/armada-xp-gp.dts                 |    1 -
 arch/arm/boot/dts/armada-xp-lenovo-ix4-300d.dts    |    1 -
 arch/arm/boot/dts/armada-xp-linksys-mamba.dts      |  155 +-
 arch/arm/boot/dts/armada-xp-netgear-rn2120.dts     |   89 +-
 arch/arm/boot/dts/pxa3xx.dtsi                      |    6 +-
 arch/arm64/boot/dts/marvell/armada-7040-db.dts     |   47 +-
 .../boot/dts/marvell/armada-cp110-master.dtsi      |    6 +-
 .../arm64/boot/dts/marvell/armada-cp110-slave.dtsi |    6 +-
 drivers/mtd/nand/Kconfig                           |   11 +
 drivers/mtd/nand/Makefile                          |    1 +
 drivers/mtd/nand/atmel/nand-controller.c           |    9 +-
 drivers/mtd/nand/bf5xx_nand.c                      |    6 +-
 drivers/mtd/nand/brcmnand/brcmnand.c               |   20 +-
 drivers/mtd/nand/cafe_nand.c                       |   20 +-
 drivers/mtd/nand/denali.c                          |  109 +-
 drivers/mtd/nand/diskonchip.c                      |    4 +-
 drivers/mtd/nand/docg4.c                           |   19 +-
 drivers/mtd/nand/fsl_elbc_nand.c                   |    6 +-
 drivers/mtd/nand/fsl_ifc_nand.c                    |    6 +-
 drivers/mtd/nand/fsmc_nand.c                       |    5 +-
 drivers/mtd/nand/gpmi-nand/gpmi-nand.c             |   76 +-
 drivers/mtd/nand/hisi504_nand.c                    |    9 +-
 drivers/mtd/nand/jz4740_nand.c                     |   16 +-
 drivers/mtd/nand/lpc32xx_mlc.c                     |    7 +-
 drivers/mtd/nand/lpc32xx_slc.c                     |   33 +-
 drivers/mtd/nand/marvell_nand.c                    | 2384 ++++++++++++++++++++
 drivers/mtd/nand/mtk_nand.c                        |   21 +-
 drivers/mtd/nand/nand_base.c                       | 2020 ++++++++++++++---
 drivers/mtd/nand/nand_hynix.c                      |  106 +-
 drivers/mtd/nand/nand_micron.c                     |   50 +-
 drivers/mtd/nand/nand_timings.c                    |   23 +-
 drivers/mtd/nand/omap2.c                           |    8 +-
 drivers/mtd/nand/pxa3xx_nand.c                     |    8 +-
 drivers/mtd/nand/qcom_nandc.c                      |   16 +-
 drivers/mtd/nand/r852.c                            |   11 +-
 drivers/mtd/nand/sh_flctl.c                        |    6 +-
 drivers/mtd/nand/sunxi_nand.c                      |   97 +-
 drivers/mtd/nand/tango_nand.c                      |   27 +-
 drivers/mtd/nand/tmio_nand.c                       |    5 +-
 drivers/mtd/nand/vf610_nfc.c                       |    6 +-
 drivers/staging/mt29f_spinand/mt29f_spinand.c      |    7 +-
 include/linux/mtd/rawnand.h                        |  391 +++-
 68 files changed, 5980 insertions(+), 1424 deletions(-)
 create mode 100644 Documentation/devicetree/bindings/mtd/marvell-nand.txt
 create mode 100644 drivers/mtd/nand/marvell_nand.c

-- 
2.11.0

^ permalink raw reply	[flat|nested] 75+ messages in thread

* [RFC 00/12] Marvell NAND controller rework with ->exec_op()
@ 2017-10-18 14:36 ` Miquel Raynal
  0 siblings, 0 replies; 75+ messages in thread
From: Miquel Raynal @ 2017-10-18 14:36 UTC (permalink / raw)
  To: Andrew Lunn, bcm-kernel-feedback-list, Boris Brezillon,
	Brian Norris, Catalin Marinas, Chen-Yu Tsai, Cyrille Pitchen,
	Daniel Mack, David Woodhouse, devel, devicetree, Ezequiel Garcia,
	Greg Kroah-Hartman, Gregory Clement, Han Xu, Haojian Zhuang,
	Jason Cooper, Josh Wu, Kamal Dasu, linux-arm-kernel,
	linux-kernel, linux-mediatek, linux-mtd
  Cc: Hanna Hawa, Antoine Tenart, Nadav Haklai, Neta Zur Hershkovits,
	Igor Grinberg, Miquel Raynal, Ofer Heifetz

Hi,

This series adds the implementation of the NAND framework ->exec_op()
interface with all the related hooks and helpers. The reasons for adding
it are explained in details in the commit log "mtd: nand: add
 ->exec_op() implementation", but mostly it will ease later expansion of
the framework, as well as the implementation of new vendor specific
commands, and should also ease driver development.

The series contains as well the reworked NAND controller driver from
Marvell.

A lot of comments are written to explain how to use the new API and the
Marvell driver shows how to implement it. A proper external
documentation is being written and will later be submitted.

It also changes the device tree NAND node definition for all platforms
referring to the Marvell driver to use the new bindings. They are more
hierarchical and fit the real organization of the hardware, by having
NAND partitions that are part of NAND chip nodes, themselves part of
the NAND controller node.

See the commit log of "mtd: nand: add reworked Marvell NAND controller
driver" for details about why a completely new driver is needed.

As this series changes several core functions and also directly touches
multiple controller drivers, it would be great to have reviews and tests
from the concerned maintainers. Only PXA3xx SoCs are not supported yet
(explaining the RFC) as having a local board to test is really needed.

Thank you,
Miquèl


Boris Brezillon (2):
  mtd: nand: provide several helpers to do common NAND operations
  mtd: nand: force drivers to explicitly send READ/PROG commands

Miquel Raynal (10):
  mtd: nand: use a static data_interface in the nand_chip structure
  mtd: nand: add ->exec_op() implementation
  dt-bindings: mtd: add Marvell NAND controller documentation
  mtd: nand: add reworked Marvell NAND controller driver
  ARM: dts: armada-370-xp: use reworked NAND controller driver
  ARM: dts: armada-375: use reworked NAND controller driver
  ARM: dts: armada-38x: use reworked NAND controller driver
  ARM: dts: armada-39x: use reworked NAND controller driver
  ARM: dts: pxa: use reworked NAND controller driver
  ARM64: dts: marvell: use reworked NAND controller driver on Armada
    7K/8K

 .../devicetree/bindings/mtd/marvell-nand.txt       |   95 +
 arch/arm/boot/dts/armada-370-db.dts                |   56 +-
 arch/arm/boot/dts/armada-370-dlink-dns327l.dts     |  119 +-
 arch/arm/boot/dts/armada-370-mirabox.dts           |   50 +-
 arch/arm/boot/dts/armada-370-netgear-rn102.dts     |   89 +-
 arch/arm/boot/dts/armada-370-netgear-rn104.dts     |   89 +-
 arch/arm/boot/dts/armada-370-rd.dts                |   51 +-
 arch/arm/boot/dts/armada-370-seagate-nas-xbay.dtsi |   63 +-
 arch/arm/boot/dts/armada-370-xp.dtsi               |    6 +-
 arch/arm/boot/dts/armada-375-db.dts                |   49 +-
 arch/arm/boot/dts/armada-375.dtsi                  |    6 +-
 arch/arm/boot/dts/armada-385-db-ap.dts             |   68 +-
 arch/arm/boot/dts/armada-385-linksys-caiman.dts    |  129 +-
 arch/arm/boot/dts/armada-385-linksys-cobra.dts     |  129 +-
 arch/arm/boot/dts/armada-385-linksys-rango.dts     |  141 +-
 arch/arm/boot/dts/armada-385-linksys-shelby.dts    |  129 +-
 arch/arm/boot/dts/armada-385-linksys.dtsi          |   15 +-
 arch/arm/boot/dts/armada-388-db.dts                |   54 +-
 arch/arm/boot/dts/armada-38x.dtsi                  |    6 +-
 arch/arm/boot/dts/armada-390-db.dts                |   65 +-
 arch/arm/boot/dts/armada-395-gp.dts                |   73 +-
 arch/arm/boot/dts/armada-398-db.dts                |   59 +-
 arch/arm/boot/dts/armada-39x.dtsi                  |    6 +-
 arch/arm/boot/dts/armada-xp-db-dxbc2.dts           |    1 -
 arch/arm/boot/dts/armada-xp-db-xc3-24g4xg.dts      |    1 -
 arch/arm/boot/dts/armada-xp-db.dts                 |    1 -
 arch/arm/boot/dts/armada-xp-gp.dts                 |    1 -
 arch/arm/boot/dts/armada-xp-lenovo-ix4-300d.dts    |    1 -
 arch/arm/boot/dts/armada-xp-linksys-mamba.dts      |  155 +-
 arch/arm/boot/dts/armada-xp-netgear-rn2120.dts     |   89 +-
 arch/arm/boot/dts/pxa3xx.dtsi                      |    6 +-
 arch/arm64/boot/dts/marvell/armada-7040-db.dts     |   47 +-
 .../boot/dts/marvell/armada-cp110-master.dtsi      |    6 +-
 .../arm64/boot/dts/marvell/armada-cp110-slave.dtsi |    6 +-
 drivers/mtd/nand/Kconfig                           |   11 +
 drivers/mtd/nand/Makefile                          |    1 +
 drivers/mtd/nand/atmel/nand-controller.c           |    9 +-
 drivers/mtd/nand/bf5xx_nand.c                      |    6 +-
 drivers/mtd/nand/brcmnand/brcmnand.c               |   20 +-
 drivers/mtd/nand/cafe_nand.c                       |   20 +-
 drivers/mtd/nand/denali.c                          |  109 +-
 drivers/mtd/nand/diskonchip.c                      |    4 +-
 drivers/mtd/nand/docg4.c                           |   19 +-
 drivers/mtd/nand/fsl_elbc_nand.c                   |    6 +-
 drivers/mtd/nand/fsl_ifc_nand.c                    |    6 +-
 drivers/mtd/nand/fsmc_nand.c                       |    5 +-
 drivers/mtd/nand/gpmi-nand/gpmi-nand.c             |   76 +-
 drivers/mtd/nand/hisi504_nand.c                    |    9 +-
 drivers/mtd/nand/jz4740_nand.c                     |   16 +-
 drivers/mtd/nand/lpc32xx_mlc.c                     |    7 +-
 drivers/mtd/nand/lpc32xx_slc.c                     |   33 +-
 drivers/mtd/nand/marvell_nand.c                    | 2384 ++++++++++++++++++++
 drivers/mtd/nand/mtk_nand.c                        |   21 +-
 drivers/mtd/nand/nand_base.c                       | 2020 ++++++++++++++---
 drivers/mtd/nand/nand_hynix.c                      |  106 +-
 drivers/mtd/nand/nand_micron.c                     |   50 +-
 drivers/mtd/nand/nand_timings.c                    |   23 +-
 drivers/mtd/nand/omap2.c                           |    8 +-
 drivers/mtd/nand/pxa3xx_nand.c                     |    8 +-
 drivers/mtd/nand/qcom_nandc.c                      |   16 +-
 drivers/mtd/nand/r852.c                            |   11 +-
 drivers/mtd/nand/sh_flctl.c                        |    6 +-
 drivers/mtd/nand/sunxi_nand.c                      |   97 +-
 drivers/mtd/nand/tango_nand.c                      |   27 +-
 drivers/mtd/nand/tmio_nand.c                       |    5 +-
 drivers/mtd/nand/vf610_nfc.c                       |    6 +-
 drivers/staging/mt29f_spinand/mt29f_spinand.c      |    7 +-
 include/linux/mtd/rawnand.h                        |  391 +++-
 68 files changed, 5980 insertions(+), 1424 deletions(-)
 create mode 100644 Documentation/devicetree/bindings/mtd/marvell-nand.txt
 create mode 100644 drivers/mtd/nand/marvell_nand.c

-- 
2.11.0

_______________________________________________
devel mailing list
devel@linuxdriverproject.org
http://driverdev.linuxdriverproject.org/mailman/listinfo/driverdev-devel

^ permalink raw reply	[flat|nested] 75+ messages in thread

* [RFC 00/12] Marvell NAND controller rework with ->exec_op()
@ 2017-10-18 14:36 ` Miquel Raynal
  0 siblings, 0 replies; 75+ messages in thread
From: Miquel Raynal @ 2017-10-18 14:36 UTC (permalink / raw)
  To: linux-arm-kernel

Hi,

This series adds the implementation of the NAND framework ->exec_op()
interface with all the related hooks and helpers. The reasons for adding
it are explained in details in the commit log "mtd: nand: add
 ->exec_op() implementation", but mostly it will ease later expansion of
the framework, as well as the implementation of new vendor specific
commands, and should also ease driver development.

The series contains as well the reworked NAND controller driver from
Marvell.

A lot of comments are written to explain how to use the new API and the
Marvell driver shows how to implement it. A proper external
documentation is being written and will later be submitted.

It also changes the device tree NAND node definition for all platforms
referring to the Marvell driver to use the new bindings. They are more
hierarchical and fit the real organization of the hardware, by having
NAND partitions that are part of NAND chip nodes, themselves part of
the NAND controller node.

See the commit log of "mtd: nand: add reworked Marvell NAND controller
driver" for details about why a completely new driver is needed.

As this series changes several core functions and also directly touches
multiple controller drivers, it would be great to have reviews and tests
from the concerned maintainers. Only PXA3xx SoCs are not supported yet
(explaining the RFC) as having a local board to test is really needed.

Thank you,
Miqu?l


Boris Brezillon (2):
  mtd: nand: provide several helpers to do common NAND operations
  mtd: nand: force drivers to explicitly send READ/PROG commands

Miquel Raynal (10):
  mtd: nand: use a static data_interface in the nand_chip structure
  mtd: nand: add ->exec_op() implementation
  dt-bindings: mtd: add Marvell NAND controller documentation
  mtd: nand: add reworked Marvell NAND controller driver
  ARM: dts: armada-370-xp: use reworked NAND controller driver
  ARM: dts: armada-375: use reworked NAND controller driver
  ARM: dts: armada-38x: use reworked NAND controller driver
  ARM: dts: armada-39x: use reworked NAND controller driver
  ARM: dts: pxa: use reworked NAND controller driver
  ARM64: dts: marvell: use reworked NAND controller driver on Armada
    7K/8K

 .../devicetree/bindings/mtd/marvell-nand.txt       |   95 +
 arch/arm/boot/dts/armada-370-db.dts                |   56 +-
 arch/arm/boot/dts/armada-370-dlink-dns327l.dts     |  119 +-
 arch/arm/boot/dts/armada-370-mirabox.dts           |   50 +-
 arch/arm/boot/dts/armada-370-netgear-rn102.dts     |   89 +-
 arch/arm/boot/dts/armada-370-netgear-rn104.dts     |   89 +-
 arch/arm/boot/dts/armada-370-rd.dts                |   51 +-
 arch/arm/boot/dts/armada-370-seagate-nas-xbay.dtsi |   63 +-
 arch/arm/boot/dts/armada-370-xp.dtsi               |    6 +-
 arch/arm/boot/dts/armada-375-db.dts                |   49 +-
 arch/arm/boot/dts/armada-375.dtsi                  |    6 +-
 arch/arm/boot/dts/armada-385-db-ap.dts             |   68 +-
 arch/arm/boot/dts/armada-385-linksys-caiman.dts    |  129 +-
 arch/arm/boot/dts/armada-385-linksys-cobra.dts     |  129 +-
 arch/arm/boot/dts/armada-385-linksys-rango.dts     |  141 +-
 arch/arm/boot/dts/armada-385-linksys-shelby.dts    |  129 +-
 arch/arm/boot/dts/armada-385-linksys.dtsi          |   15 +-
 arch/arm/boot/dts/armada-388-db.dts                |   54 +-
 arch/arm/boot/dts/armada-38x.dtsi                  |    6 +-
 arch/arm/boot/dts/armada-390-db.dts                |   65 +-
 arch/arm/boot/dts/armada-395-gp.dts                |   73 +-
 arch/arm/boot/dts/armada-398-db.dts                |   59 +-
 arch/arm/boot/dts/armada-39x.dtsi                  |    6 +-
 arch/arm/boot/dts/armada-xp-db-dxbc2.dts           |    1 -
 arch/arm/boot/dts/armada-xp-db-xc3-24g4xg.dts      |    1 -
 arch/arm/boot/dts/armada-xp-db.dts                 |    1 -
 arch/arm/boot/dts/armada-xp-gp.dts                 |    1 -
 arch/arm/boot/dts/armada-xp-lenovo-ix4-300d.dts    |    1 -
 arch/arm/boot/dts/armada-xp-linksys-mamba.dts      |  155 +-
 arch/arm/boot/dts/armada-xp-netgear-rn2120.dts     |   89 +-
 arch/arm/boot/dts/pxa3xx.dtsi                      |    6 +-
 arch/arm64/boot/dts/marvell/armada-7040-db.dts     |   47 +-
 .../boot/dts/marvell/armada-cp110-master.dtsi      |    6 +-
 .../arm64/boot/dts/marvell/armada-cp110-slave.dtsi |    6 +-
 drivers/mtd/nand/Kconfig                           |   11 +
 drivers/mtd/nand/Makefile                          |    1 +
 drivers/mtd/nand/atmel/nand-controller.c           |    9 +-
 drivers/mtd/nand/bf5xx_nand.c                      |    6 +-
 drivers/mtd/nand/brcmnand/brcmnand.c               |   20 +-
 drivers/mtd/nand/cafe_nand.c                       |   20 +-
 drivers/mtd/nand/denali.c                          |  109 +-
 drivers/mtd/nand/diskonchip.c                      |    4 +-
 drivers/mtd/nand/docg4.c                           |   19 +-
 drivers/mtd/nand/fsl_elbc_nand.c                   |    6 +-
 drivers/mtd/nand/fsl_ifc_nand.c                    |    6 +-
 drivers/mtd/nand/fsmc_nand.c                       |    5 +-
 drivers/mtd/nand/gpmi-nand/gpmi-nand.c             |   76 +-
 drivers/mtd/nand/hisi504_nand.c                    |    9 +-
 drivers/mtd/nand/jz4740_nand.c                     |   16 +-
 drivers/mtd/nand/lpc32xx_mlc.c                     |    7 +-
 drivers/mtd/nand/lpc32xx_slc.c                     |   33 +-
 drivers/mtd/nand/marvell_nand.c                    | 2384 ++++++++++++++++++++
 drivers/mtd/nand/mtk_nand.c                        |   21 +-
 drivers/mtd/nand/nand_base.c                       | 2020 ++++++++++++++---
 drivers/mtd/nand/nand_hynix.c                      |  106 +-
 drivers/mtd/nand/nand_micron.c                     |   50 +-
 drivers/mtd/nand/nand_timings.c                    |   23 +-
 drivers/mtd/nand/omap2.c                           |    8 +-
 drivers/mtd/nand/pxa3xx_nand.c                     |    8 +-
 drivers/mtd/nand/qcom_nandc.c                      |   16 +-
 drivers/mtd/nand/r852.c                            |   11 +-
 drivers/mtd/nand/sh_flctl.c                        |    6 +-
 drivers/mtd/nand/sunxi_nand.c                      |   97 +-
 drivers/mtd/nand/tango_nand.c                      |   27 +-
 drivers/mtd/nand/tmio_nand.c                       |    5 +-
 drivers/mtd/nand/vf610_nfc.c                       |    6 +-
 drivers/staging/mt29f_spinand/mt29f_spinand.c      |    7 +-
 include/linux/mtd/rawnand.h                        |  391 +++-
 68 files changed, 5980 insertions(+), 1424 deletions(-)
 create mode 100644 Documentation/devicetree/bindings/mtd/marvell-nand.txt
 create mode 100644 drivers/mtd/nand/marvell_nand.c

-- 
2.11.0

^ permalink raw reply	[flat|nested] 75+ messages in thread

* [RFC 01/12] mtd: nand: provide several helpers to do common NAND operations
  2017-10-18 14:36 ` Miquel Raynal
  (?)
@ 2017-10-18 14:36   ` Miquel Raynal
  -1 siblings, 0 replies; 75+ messages in thread
From: Miquel Raynal @ 2017-10-18 14:36 UTC (permalink / raw)
  To: Andrew Lunn, bcm-kernel-feedback-list, Boris Brezillon,
	Brian Norris, Catalin Marinas, Chen-Yu Tsai, Cyrille Pitchen,
	Daniel Mack, David Woodhouse, devel, devicetree, Ezequiel Garcia,
	Greg Kroah-Hartman, Gregory Clement, Han Xu, Haojian Zhuang,
	Jason Cooper, Josh Wu, Kamal Dasu, linux-arm-kernel,
	linux-kernel, linux-mediatek, linux-mtd, Marc Gonzalez,
	Marek Vasut, Mark Rutland, Masahiro Yamada, Matthias Brugger,
	Maxime Ripard, Maxim Levitsky, Richard Weinberger,
	Robert Jarzmik, Rob Herring, Russell King, Sebastian Hesselbarth,
	Stefan Agner, Sylvain Lemieux, Vladimir Zapolskiy, Wenyou Yang,
	Will Deacon
  Cc: Thomas Petazzoni, Antoine Tenart, Miquel Raynal, Igor Grinberg,
	Nadav Haklai, Ofer Heifetz, Neta Zur Hershkovits, Hanna Hawa

From: Boris Brezillon <boris.brezillon@free-electrons.com>

This is part of the process of removing direct calls to ->cmdfunc()
outside of the core in order to introduce a better interface to execute
NAND operations.

Here we provide several helpers and make use of them to remove all
direct calls to ->cmdfunc(). This way, we can easily modify those
helpers to make use of the new ->exec_op() interface when available.

Signed-off-by: Boris Brezillon <boris.brezillon@free-electrons.com>
[miquel.raynal@free-electrons.com: rebased and fixed some conflicts]
Signed-off-by: Miquel Raynal <miquel.raynal@free-electrons.com>
---
 drivers/mtd/nand/atmel/nand-controller.c |   2 +-
 drivers/mtd/nand/brcmnand/brcmnand.c     |   9 +-
 drivers/mtd/nand/cafe_nand.c             |  14 +-
 drivers/mtd/nand/denali.c                |   2 +-
 drivers/mtd/nand/diskonchip.c            |   4 +-
 drivers/mtd/nand/docg4.c                 |   7 +-
 drivers/mtd/nand/fsmc_nand.c             |   5 +-
 drivers/mtd/nand/gpmi-nand/gpmi-nand.c   |  56 +-
 drivers/mtd/nand/hisi504_nand.c          |   3 +-
 drivers/mtd/nand/jz4740_nand.c           |  16 +-
 drivers/mtd/nand/lpc32xx_mlc.c           |   2 +-
 drivers/mtd/nand/lpc32xx_slc.c           |  22 +-
 drivers/mtd/nand/mtk_nand.c              |  11 +-
 drivers/mtd/nand/nand_base.c             | 956 ++++++++++++++++++++++++++-----
 drivers/mtd/nand/nand_hynix.c            |  17 +-
 drivers/mtd/nand/nand_micron.c           |  17 +-
 drivers/mtd/nand/omap2.c                 |   8 +-
 drivers/mtd/nand/pxa3xx_nand.c           |   8 +-
 drivers/mtd/nand/qcom_nandc.c            |  16 +-
 drivers/mtd/nand/r852.c                  |  11 +-
 drivers/mtd/nand/sunxi_nand.c            |  67 +--
 drivers/mtd/nand/tango_nand.c            |  26 +-
 drivers/mtd/nand/tmio_nand.c             |   5 +-
 include/linux/mtd/rawnand.h              |  25 +
 24 files changed, 943 insertions(+), 366 deletions(-)

diff --git a/drivers/mtd/nand/atmel/nand-controller.c b/drivers/mtd/nand/atmel/nand-controller.c
index 29182160bb5f..0b2c82727b24 100644
--- a/drivers/mtd/nand/atmel/nand-controller.c
+++ b/drivers/mtd/nand/atmel/nand-controller.c
@@ -1000,7 +1000,7 @@ static int atmel_hsmc_nand_pmecc_read_pg(struct nand_chip *chip, u8 *buf,
 	 * to the non-optimized one.
 	 */
 	if (nand->activecs->rb.type != ATMEL_NAND_NATIVE_RB) {
-		chip->cmdfunc(mtd, NAND_CMD_READ0, 0x00, page);
+		nand_read_page_op(chip, page, 0, NULL, 0);
 
 		return atmel_nand_pmecc_read_pg(chip, buf, oob_required, page,
 						raw);
diff --git a/drivers/mtd/nand/brcmnand/brcmnand.c b/drivers/mtd/nand/brcmnand/brcmnand.c
index e0eb51d8c012..3f441096a14c 100644
--- a/drivers/mtd/nand/brcmnand/brcmnand.c
+++ b/drivers/mtd/nand/brcmnand/brcmnand.c
@@ -1071,7 +1071,7 @@ static void brcmnand_wp(struct mtd_info *mtd, int wp)
 			return;
 
 		brcmnand_set_wp(ctrl, wp);
-		chip->cmdfunc(mtd, NAND_CMD_STATUS, -1, -1);
+		nand_status_op(chip, NULL);
 		/* NAND_STATUS_WP 0x00 = protected, 0x80 = not protected */
 		ret = bcmnand_ctrl_poll_status(ctrl,
 					       NAND_CTRL_RDY |
@@ -1453,7 +1453,7 @@ static uint8_t brcmnand_read_byte(struct mtd_info *mtd)
 
 		/* At FC_BYTES boundary, switch to next column */
 		if (host->last_byte > 0 && offs == 0)
-			chip->cmdfunc(mtd, NAND_CMD_RNDOUT, addr, -1);
+			nand_change_read_column_op(chip, addr, NULL, 0, false);
 
 		ret = ctrl->flash_cache[offs];
 		break;
@@ -1689,7 +1689,7 @@ static int brcmstb_nand_verify_erased_page(struct mtd_info *mtd,
 	sas = mtd->oobsize / chip->ecc.steps;
 
 	/* read without ecc for verification */
-	chip->cmdfunc(mtd, NAND_CMD_READ0, 0x00, page);
+	nand_read_page_op(chip, page, 0, NULL, 0);
 	ret = chip->ecc.read_page_raw(mtd, chip, buf, true, page);
 	if (ret)
 		return ret;
@@ -2369,12 +2369,11 @@ static int brcmnand_resume(struct device *dev)
 
 	list_for_each_entry(host, &ctrl->host_list, node) {
 		struct nand_chip *chip = &host->chip;
-		struct mtd_info *mtd = nand_to_mtd(chip);
 
 		brcmnand_save_restore_cs_config(host, 1);
 
 		/* Reset the chip, required by some chips after power-up */
-		chip->cmdfunc(mtd, NAND_CMD_RESET, -1, -1);
+		nand_reset_op(chip);
 	}
 
 	return 0;
diff --git a/drivers/mtd/nand/cafe_nand.c b/drivers/mtd/nand/cafe_nand.c
index bc558c438a57..95c2cfa68b66 100644
--- a/drivers/mtd/nand/cafe_nand.c
+++ b/drivers/mtd/nand/cafe_nand.c
@@ -353,23 +353,15 @@ static void cafe_nand_bug(struct mtd_info *mtd)
 static int cafe_nand_write_oob(struct mtd_info *mtd,
 			       struct nand_chip *chip, int page)
 {
-	int status = 0;
-
-	chip->cmdfunc(mtd, NAND_CMD_SEQIN, mtd->writesize, page);
-	chip->write_buf(mtd, chip->oob_poi, mtd->oobsize);
-	chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1);
-	status = chip->waitfunc(mtd, chip);
-
-	return status & NAND_STATUS_FAIL ? -EIO : 0;
+	return nand_prog_page_op(chip, page, mtd->writesize, chip->oob_poi,
+				 mtd->oobsize);
 }
 
 /* Don't use -- use nand_read_oob_std for now */
 static int cafe_nand_read_oob(struct mtd_info *mtd, struct nand_chip *chip,
 			      int page)
 {
-	chip->cmdfunc(mtd, NAND_CMD_READOOB, 0, page);
-	chip->read_buf(mtd, chip->oob_poi, mtd->oobsize);
-	return 0;
+	return nand_read_oob_op(chip, page, 0, chip->oob_poi, mtd->oobsize);
 }
 /**
  * cafe_nand_read_page_syndrome - [REPLACEABLE] hardware ecc syndrome based page read
diff --git a/drivers/mtd/nand/denali.c b/drivers/mtd/nand/denali.c
index 5124f8ae8c04..f90f93a8fb08 100644
--- a/drivers/mtd/nand/denali.c
+++ b/drivers/mtd/nand/denali.c
@@ -951,7 +951,7 @@ static int denali_erase(struct mtd_info *mtd, int page)
 	irq_status = denali_wait_for_irq(denali,
 					 INTR__ERASE_COMP | INTR__ERASE_FAIL);
 
-	return irq_status & INTR__ERASE_COMP ? 0 : NAND_STATUS_FAIL;
+	return irq_status & INTR__ERASE_FAIL ? -EIO : 0;
 }
 
 static int denali_setup_data_interface(struct mtd_info *mtd, int chipnr,
diff --git a/drivers/mtd/nand/diskonchip.c b/drivers/mtd/nand/diskonchip.c
index 72671dc52e2e..6bc93ea66f50 100644
--- a/drivers/mtd/nand/diskonchip.c
+++ b/drivers/mtd/nand/diskonchip.c
@@ -448,7 +448,7 @@ static int doc200x_wait(struct mtd_info *mtd, struct nand_chip *this)
 	int status;
 
 	DoC_WaitReady(doc);
-	this->cmdfunc(mtd, NAND_CMD_STATUS, -1, -1);
+	nand_status_op(this, NULL);
 	DoC_WaitReady(doc);
 	status = (int)this->read_byte(mtd);
 
@@ -595,7 +595,7 @@ static void doc2001plus_select_chip(struct mtd_info *mtd, int chip)
 
 	/* Assert ChipEnable and deassert WriteProtect */
 	WriteDOC((DOC_FLASH_CE), docptr, Mplus_FlashSelect);
-	this->cmdfunc(mtd, NAND_CMD_RESET, -1, -1);
+	nand_reset_op(this);
 
 	doc->curchip = chip;
 	doc->curfloor = floor;
diff --git a/drivers/mtd/nand/docg4.c b/drivers/mtd/nand/docg4.c
index 2436cbc71662..45c01b4b34c7 100644
--- a/drivers/mtd/nand/docg4.c
+++ b/drivers/mtd/nand/docg4.c
@@ -900,6 +900,7 @@ static int docg4_erase_block(struct mtd_info *mtd, int page)
 	struct docg4_priv *doc = nand_get_controller_data(nand);
 	void __iomem *docptr = doc->virtadr;
 	uint16_t g4_page;
+	int status;
 
 	dev_dbg(doc->dev, "%s: page %04x\n", __func__, page);
 
@@ -939,7 +940,11 @@ static int docg4_erase_block(struct mtd_info *mtd, int page)
 	poll_status(doc);
 	write_nop(docptr);
 
-	return nand->waitfunc(mtd, nand);
+	status = nand->waitfunc(mtd, nand);
+	if (status < 0)
+		return status;
+
+	return status & NAND_STATUS_FAIL ? -EIO : 0;
 }
 
 static int write_page(struct mtd_info *mtd, struct nand_chip *nand,
diff --git a/drivers/mtd/nand/fsmc_nand.c b/drivers/mtd/nand/fsmc_nand.c
index eac15d9bf49e..b44e5c6545e0 100644
--- a/drivers/mtd/nand/fsmc_nand.c
+++ b/drivers/mtd/nand/fsmc_nand.c
@@ -697,7 +697,7 @@ static int fsmc_read_page_hwecc(struct mtd_info *mtd, struct nand_chip *chip,
 	unsigned int max_bitflips = 0;
 
 	for (i = 0, s = 0; s < eccsteps; s++, i += eccbytes, p += eccsize) {
-		chip->cmdfunc(mtd, NAND_CMD_READ0, s * eccsize, page);
+		nand_read_page_op(chip, page, s * eccsize, NULL, 0);
 		chip->ecc.hwctl(mtd, NAND_ECC_READ);
 		chip->read_buf(mtd, p, eccsize);
 
@@ -720,8 +720,7 @@ static int fsmc_read_page_hwecc(struct mtd_info *mtd, struct nand_chip *chip,
 			if (chip->options & NAND_BUSWIDTH_16)
 				len = roundup(len, 2);
 
-			chip->cmdfunc(mtd, NAND_CMD_READOOB, off, page);
-			chip->read_buf(mtd, oob + j, len);
+			nand_read_oob_op(chip, page, off, oob + j, len);
 			j += len;
 		}
 
diff --git a/drivers/mtd/nand/gpmi-nand/gpmi-nand.c b/drivers/mtd/nand/gpmi-nand/gpmi-nand.c
index 50f8d4a1b983..c8ceaecd8065 100644
--- a/drivers/mtd/nand/gpmi-nand/gpmi-nand.c
+++ b/drivers/mtd/nand/gpmi-nand/gpmi-nand.c
@@ -1097,8 +1097,8 @@ static int gpmi_ecc_read_page(struct mtd_info *mtd, struct nand_chip *chip,
 			eccbytes = DIV_ROUND_UP(offset + eccbits, 8);
 			offset /= 8;
 			eccbytes -= offset;
-			chip->cmdfunc(mtd, NAND_CMD_RNDOUT, offset, -1);
-			chip->read_buf(mtd, eccbuf, eccbytes);
+			nand_change_read_column_op(chip, offset, eccbuf,
+						   eccbytes, false);
 
 			/*
 			 * ECC data are not byte aligned and we may have
@@ -1411,7 +1411,7 @@ static int gpmi_ecc_read_oob(struct mtd_info *mtd, struct nand_chip *chip,
 	memset(chip->oob_poi, ~0, mtd->oobsize);
 
 	/* Read out the conventional OOB. */
-	chip->cmdfunc(mtd, NAND_CMD_READ0, mtd->writesize, page);
+	nand_read_page_op(chip, page, mtd->writesize, NULL, 0);
 	chip->read_buf(mtd, chip->oob_poi, mtd->oobsize);
 
 	/*
@@ -1421,7 +1421,7 @@ static int gpmi_ecc_read_oob(struct mtd_info *mtd, struct nand_chip *chip,
 	 */
 	if (GPMI_IS_MX23(this)) {
 		/* Read the block mark into the first byte of the OOB buffer. */
-		chip->cmdfunc(mtd, NAND_CMD_READ0, 0, page);
+		nand_read_page_op(chip, page, 0, NULL, 0);
 		chip->oob_poi[0] = chip->read_byte(mtd);
 	}
 
@@ -1432,7 +1432,6 @@ static int
 gpmi_ecc_write_oob(struct mtd_info *mtd, struct nand_chip *chip, int page)
 {
 	struct mtd_oob_region of = { };
-	int status = 0;
 
 	/* Do we have available oob area? */
 	mtd_ooblayout_free(mtd, 0, &of);
@@ -1442,12 +1441,8 @@ gpmi_ecc_write_oob(struct mtd_info *mtd, struct nand_chip *chip, int page)
 	if (!nand_is_slc(chip))
 		return -EPERM;
 
-	chip->cmdfunc(mtd, NAND_CMD_SEQIN, mtd->writesize + of.offset, page);
-	chip->write_buf(mtd, chip->oob_poi + of.offset, of.length);
-	chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1);
-
-	status = chip->waitfunc(mtd, chip);
-	return status & NAND_STATUS_FAIL ? -EIO : 0;
+	return nand_prog_page_op(chip, page, mtd->writesize + of.offset,
+				 chip->oob_poi + of.offset, of.length);
 }
 
 /*
@@ -1630,7 +1625,7 @@ static int gpmi_ecc_write_page_raw(struct mtd_info *mtd,
 static int gpmi_ecc_read_oob_raw(struct mtd_info *mtd, struct nand_chip *chip,
 				 int page)
 {
-	chip->cmdfunc(mtd, NAND_CMD_READ0, 0, page);
+	nand_read_page_op(chip, page, 0, NULL, 0);
 
 	return gpmi_ecc_read_page_raw(mtd, chip, NULL, 1, page);
 }
@@ -1638,7 +1633,7 @@ static int gpmi_ecc_read_oob_raw(struct mtd_info *mtd, struct nand_chip *chip,
 static int gpmi_ecc_write_oob_raw(struct mtd_info *mtd, struct nand_chip *chip,
 				 int page)
 {
-	chip->cmdfunc(mtd, NAND_CMD_SEQIN, 0, page);
+	nand_prog_page_begin_op(chip, page, 0, NULL, 0);
 
 	return gpmi_ecc_write_page_raw(mtd, chip, NULL, 1, page);
 }
@@ -1649,7 +1644,7 @@ static int gpmi_block_markbad(struct mtd_info *mtd, loff_t ofs)
 	struct gpmi_nand_data *this = nand_get_controller_data(chip);
 	int ret = 0;
 	uint8_t *block_mark;
-	int column, page, status, chipnr;
+	int column, page, chipnr;
 
 	chipnr = (int)(ofs >> chip->chip_shift);
 	chip->select_chip(mtd, chipnr);
@@ -1663,13 +1658,7 @@ static int gpmi_block_markbad(struct mtd_info *mtd, loff_t ofs)
 	/* Shift to get page */
 	page = (int)(ofs >> chip->page_shift);
 
-	chip->cmdfunc(mtd, NAND_CMD_SEQIN, column, page);
-	chip->write_buf(mtd, block_mark, 1);
-	chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1);
-
-	status = chip->waitfunc(mtd, chip);
-	if (status & NAND_STATUS_FAIL)
-		ret = -EIO;
+	ret = nand_prog_page_op(chip, page, column, block_mark, 1);
 
 	chip->select_chip(mtd, -1);
 
@@ -1737,7 +1726,7 @@ static int mx23_check_transcription_stamp(struct gpmi_nand_data *this)
 		 * Read the NCB fingerprint. The fingerprint is four bytes long
 		 * and starts in the 12th byte of the page.
 		 */
-		chip->cmdfunc(mtd, NAND_CMD_READ0, 12, page);
+		nand_read_page_op(chip, page, 12, NULL, 0);
 		chip->read_buf(mtd, buffer, strlen(fingerprint));
 
 		/* Look for the fingerprint. */
@@ -1797,17 +1786,10 @@ static int mx23_write_transcription_stamp(struct gpmi_nand_data *this)
 	dev_dbg(dev, "Erasing the search area...\n");
 
 	for (block = 0; block < search_area_size_in_blocks; block++) {
-		/* Compute the page address. */
-		page = block * block_size_in_pages;
-
 		/* Erase this block. */
 		dev_dbg(dev, "\tErasing block 0x%x\n", block);
-		chip->cmdfunc(mtd, NAND_CMD_ERASE1, -1, page);
-		chip->cmdfunc(mtd, NAND_CMD_ERASE2, -1, -1);
-
-		/* Wait for the erase to finish. */
-		status = chip->waitfunc(mtd, chip);
-		if (status & NAND_STATUS_FAIL)
+		status = nand_erase_op(chip, block);
+		if (status)
 			dev_err(dev, "[%s] Erase failed.\n", __func__);
 	}
 
@@ -1823,13 +1805,11 @@ static int mx23_write_transcription_stamp(struct gpmi_nand_data *this)
 
 		/* Write the first page of the current stride. */
 		dev_dbg(dev, "Writing an NCB fingerprint in page 0x%x\n", page);
-		chip->cmdfunc(mtd, NAND_CMD_SEQIN, 0x00, page);
-		chip->ecc.write_page_raw(mtd, chip, buffer, 0, page);
-		chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1);
 
-		/* Wait for the write to finish. */
-		status = chip->waitfunc(mtd, chip);
-		if (status & NAND_STATUS_FAIL)
+		nand_prog_page_begin_op(chip, page, 0, NULL, 0);
+		chip->ecc.write_page_raw(mtd, chip, buffer, 0, page);
+		status = nand_prog_page_end_op(chip);
+		if (status)
 			dev_err(dev, "[%s] Write failed.\n", __func__);
 	}
 
@@ -1884,7 +1864,7 @@ static int mx23_boot_init(struct gpmi_nand_data  *this)
 
 		/* Send the command to read the conventional block mark. */
 		chip->select_chip(mtd, chipnr);
-		chip->cmdfunc(mtd, NAND_CMD_READ0, mtd->writesize, page);
+		nand_read_page_op(chip, page, mtd->writesize, NULL, 0);
 		block_mark = chip->read_byte(mtd);
 		chip->select_chip(mtd, -1);
 
diff --git a/drivers/mtd/nand/hisi504_nand.c b/drivers/mtd/nand/hisi504_nand.c
index 0897261c3e17..184d765c8bbe 100644
--- a/drivers/mtd/nand/hisi504_nand.c
+++ b/drivers/mtd/nand/hisi504_nand.c
@@ -574,8 +574,7 @@ static int hisi_nand_read_oob(struct mtd_info *mtd, struct nand_chip *chip,
 {
 	struct hinfc_host *host = nand_get_controller_data(chip);
 
-	chip->cmdfunc(mtd, NAND_CMD_READOOB, 0, page);
-	chip->read_buf(mtd, chip->oob_poi, mtd->oobsize);
+	nand_read_oob_op(chip, page, 0, chip->oob_poi, mtd->oobsize);
 
 	if (host->irq_status & HINFC504_INTS_UE) {
 		host->irq_status = 0;
diff --git a/drivers/mtd/nand/jz4740_nand.c b/drivers/mtd/nand/jz4740_nand.c
index ad827d4af3e9..613b00a9604b 100644
--- a/drivers/mtd/nand/jz4740_nand.c
+++ b/drivers/mtd/nand/jz4740_nand.c
@@ -313,6 +313,7 @@ static int jz_nand_detect_bank(struct platform_device *pdev,
 	uint32_t ctrl;
 	struct nand_chip *chip = &nand->chip;
 	struct mtd_info *mtd = nand_to_mtd(chip);
+	u8 id[2];
 
 	/* Request I/O resource. */
 	sprintf(res_name, "bank%d", bank);
@@ -335,17 +336,16 @@ static int jz_nand_detect_bank(struct platform_device *pdev,
 
 		/* Retrieve the IDs from the first chip. */
 		chip->select_chip(mtd, 0);
-		chip->cmdfunc(mtd, NAND_CMD_RESET, -1, -1);
-		chip->cmdfunc(mtd, NAND_CMD_READID, 0x00, -1);
-		*nand_maf_id = chip->read_byte(mtd);
-		*nand_dev_id = chip->read_byte(mtd);
+		nand_reset_op(chip);
+		nand_readid_op(chip, 0, id, sizeof(id));
+		*nand_maf_id = id[0];
+		*nand_dev_id = id[1];
 	} else {
 		/* Detect additional chip. */
 		chip->select_chip(mtd, chipnr);
-		chip->cmdfunc(mtd, NAND_CMD_RESET, -1, -1);
-		chip->cmdfunc(mtd, NAND_CMD_READID, 0x00, -1);
-		if (*nand_maf_id != chip->read_byte(mtd)
-		 || *nand_dev_id != chip->read_byte(mtd)) {
+		nand_reset_op(chip);
+		nand_readid_op(chip, 0, id, sizeof(id));
+		if (*nand_maf_id != id[0] || *nand_dev_id != id[1]) {
 			ret = -ENODEV;
 			goto notfound_id;
 		}
diff --git a/drivers/mtd/nand/lpc32xx_mlc.c b/drivers/mtd/nand/lpc32xx_mlc.c
index c3bb358ef01e..0723ded232ff 100644
--- a/drivers/mtd/nand/lpc32xx_mlc.c
+++ b/drivers/mtd/nand/lpc32xx_mlc.c
@@ -461,7 +461,7 @@ static int lpc32xx_read_page(struct mtd_info *mtd, struct nand_chip *chip,
 	}
 
 	/* Writing Command and Address */
-	chip->cmdfunc(mtd, NAND_CMD_READ0, 0, page);
+	nand_read_page_op(chip, page, 0, NULL, 0);
 
 	/* For all sub-pages */
 	for (i = 0; i < host->mlcsubpages; i++) {
diff --git a/drivers/mtd/nand/lpc32xx_slc.c b/drivers/mtd/nand/lpc32xx_slc.c
index b61f28a1554d..2b96c281b1a2 100644
--- a/drivers/mtd/nand/lpc32xx_slc.c
+++ b/drivers/mtd/nand/lpc32xx_slc.c
@@ -399,10 +399,7 @@ static void lpc32xx_nand_write_buf(struct mtd_info *mtd, const uint8_t *buf, int
 static int lpc32xx_nand_read_oob_syndrome(struct mtd_info *mtd,
 					  struct nand_chip *chip, int page)
 {
-	chip->cmdfunc(mtd, NAND_CMD_READOOB, 0, page);
-	chip->read_buf(mtd, chip->oob_poi, mtd->oobsize);
-
-	return 0;
+	return nand_read_oob_op(chip, page, 0, chip->oob_poi, mtd->oobsize);
 }
 
 /*
@@ -411,17 +408,8 @@ static int lpc32xx_nand_read_oob_syndrome(struct mtd_info *mtd,
 static int lpc32xx_nand_write_oob_syndrome(struct mtd_info *mtd,
 	struct nand_chip *chip, int page)
 {
-	int status;
-
-	chip->cmdfunc(mtd, NAND_CMD_SEQIN, mtd->writesize, page);
-	chip->write_buf(mtd, chip->oob_poi, mtd->oobsize);
-
-	/* Send command to program the OOB data */
-	chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1);
-
-	status = chip->waitfunc(mtd, chip);
-
-	return status & NAND_STATUS_FAIL ? -EIO : 0;
+	return nand_prog_page_op(chip, page, mtd->writesize, chip->oob_poi,
+				 mtd->oobsize);
 }
 
 /*
@@ -632,7 +620,7 @@ static int lpc32xx_nand_read_page_syndrome(struct mtd_info *mtd,
 	uint8_t *oobecc, tmpecc[LPC32XX_ECC_SAVE_SIZE];
 
 	/* Issue read command */
-	chip->cmdfunc(mtd, NAND_CMD_READ0, 0, page);
+	nand_read_page_op(chip, page, 0, NULL, 0);
 
 	/* Read data and oob, calculate ECC */
 	status = lpc32xx_xfer(mtd, buf, chip->ecc.steps, 1);
@@ -675,7 +663,7 @@ static int lpc32xx_nand_read_page_raw_syndrome(struct mtd_info *mtd,
 					       int page)
 {
 	/* Issue read command */
-	chip->cmdfunc(mtd, NAND_CMD_READ0, 0, page);
+	nand_read_page_op(chip, page, 0, NULL, 0);
 
 	/* Raw reads can just use the FIFO interface */
 	chip->read_buf(mtd, buf, chip->ecc.size * chip->ecc.steps);
diff --git a/drivers/mtd/nand/mtk_nand.c b/drivers/mtd/nand/mtk_nand.c
index d86a7d131cc0..1a3fcb65f478 100644
--- a/drivers/mtd/nand/mtk_nand.c
+++ b/drivers/mtd/nand/mtk_nand.c
@@ -834,16 +834,13 @@ static int mtk_nfc_write_oob_std(struct mtd_info *mtd, struct nand_chip *chip,
 {
 	int ret;
 
-	chip->cmdfunc(mtd, NAND_CMD_SEQIN, 0x00, page);
+	nand_prog_page_begin_op(chip, page, 0, NULL, 0);
 
 	ret = mtk_nfc_write_page_raw(mtd, chip, NULL, 1, page);
 	if (ret < 0)
 		return -EIO;
 
-	chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1);
-	ret = chip->waitfunc(mtd, chip);
-
-	return ret & NAND_STATUS_FAIL ? -EIO : 0;
+	return nand_prog_page_end_op(chip);
 }
 
 static int mtk_nfc_update_ecc_stats(struct mtd_info *mtd, u8 *buf, u32 sectors)
@@ -1016,7 +1013,7 @@ static int mtk_nfc_read_page_raw(struct mtd_info *mtd, struct nand_chip *chip,
 static int mtk_nfc_read_oob_std(struct mtd_info *mtd, struct nand_chip *chip,
 				int page)
 {
-	chip->cmdfunc(mtd, NAND_CMD_READ0, 0, page);
+	nand_read_page_op(chip, page, 0, NULL, 0);
 
 	return mtk_nfc_read_page_raw(mtd, chip, NULL, 1, page);
 }
@@ -1556,7 +1553,7 @@ static int mtk_nfc_resume(struct device *dev)
 		mtd = nand_to_mtd(nand);
 		for (i = 0; i < chip->nsels; i++) {
 			nand->select_chip(mtd, i);
-			nand->cmdfunc(mtd, NAND_CMD_RESET, -1, -1);
+			nand_reset_op(nand);
 		}
 	}
 
diff --git a/drivers/mtd/nand/nand_base.c b/drivers/mtd/nand/nand_base.c
index c63e4a88a653..f7dd7d5a84b3 100644
--- a/drivers/mtd/nand/nand_base.c
+++ b/drivers/mtd/nand/nand_base.c
@@ -561,14 +561,19 @@ static int nand_block_markbad_lowlevel(struct mtd_info *mtd, loff_t ofs)
 static int nand_check_wp(struct mtd_info *mtd)
 {
 	struct nand_chip *chip = mtd_to_nand(mtd);
+	u8 status;
+	int ret;
 
 	/* Broken xD cards report WP despite being writable */
 	if (chip->options & NAND_BROKEN_XD)
 		return 0;
 
 	/* Check the WP bit */
-	chip->cmdfunc(mtd, NAND_CMD_STATUS, -1, -1);
-	return (chip->read_byte(mtd) & NAND_STATUS_WP) ? 0 : 1;
+	ret = nand_status_op(chip, &status);
+	if (ret)
+		return ret;
+
+	return status & NAND_STATUS_WP ? 0 : 1;
 }
 
 /**
@@ -667,10 +672,17 @@ EXPORT_SYMBOL_GPL(nand_wait_ready);
 static void nand_wait_status_ready(struct mtd_info *mtd, unsigned long timeo)
 {
 	register struct nand_chip *chip = mtd_to_nand(mtd);
+	int ret;
 
 	timeo = jiffies + msecs_to_jiffies(timeo);
 	do {
-		if ((chip->read_byte(mtd) & NAND_STATUS_READY))
+		u8 status;
+
+		ret = nand_read_data_op(chip, &status, sizeof(status), true);
+		if (ret)
+			return;
+
+		if (status & NAND_STATUS_READY)
 			break;
 		touch_softlockup_watchdog();
 	} while (time_before(jiffies, timeo));
@@ -1014,7 +1026,15 @@ static void panic_nand_wait(struct mtd_info *mtd, struct nand_chip *chip,
 			if (chip->dev_ready(mtd))
 				break;
 		} else {
-			if (chip->read_byte(mtd) & NAND_STATUS_READY)
+			int ret;
+			u8 status;
+
+			ret = nand_read_data_op(chip, &status, sizeof(status),
+						true);
+			if (ret)
+				return;
+
+			if (status & NAND_STATUS_READY)
 				break;
 		}
 		mdelay(1);
@@ -1031,8 +1051,9 @@ static void panic_nand_wait(struct mtd_info *mtd, struct nand_chip *chip,
 static int nand_wait(struct mtd_info *mtd, struct nand_chip *chip)
 {
 
-	int status;
 	unsigned long timeo = 400;
+	u8 status;
+	int ret;
 
 	/*
 	 * Apply this short delay always to ensure that we do wait tWB in any
@@ -1040,7 +1061,9 @@ static int nand_wait(struct mtd_info *mtd, struct nand_chip *chip)
 	 */
 	ndelay(100);
 
-	chip->cmdfunc(mtd, NAND_CMD_STATUS, -1, -1);
+	ret = nand_status_op(chip, NULL);
+	if (ret)
+		return ret;
 
 	if (in_interrupt() || oops_in_progress)
 		panic_nand_wait(mtd, chip, timeo);
@@ -1051,14 +1074,22 @@ static int nand_wait(struct mtd_info *mtd, struct nand_chip *chip)
 				if (chip->dev_ready(mtd))
 					break;
 			} else {
-				if (chip->read_byte(mtd) & NAND_STATUS_READY)
+				ret = nand_read_data_op(chip, &status,
+							sizeof(status), true);
+				if (ret)
+					return ret;
+
+				if (status & NAND_STATUS_READY)
 					break;
 			}
 			cond_resched();
 		} while (time_before(jiffies, timeo));
 	}
 
-	status = (int)chip->read_byte(mtd);
+	ret = nand_read_data_op(chip, &status, sizeof(status), true);
+	if (ret)
+		return ret;
+
 	/* This can happen if in case of timeout or buggy dev_ready */
 	WARN_ON(!(status & NAND_STATUS_READY));
 	return status;
@@ -1213,6 +1244,492 @@ static void nand_release_data_interface(struct nand_chip *chip)
 }
 
 /**
+ * nand_read_page_op - Do a READ PAGE operation
+ * @chip: The NAND chip
+ * @page: page to read
+ * @offset_in_page: offset within the page
+ * @buf: buffer used to store the data
+ * @len: length of the buffer
+ *
+ * This function issues a READ PAGE operation.
+ * This function does not select/unselect the CS line.
+ *
+ * Returns 0 for success or negative error code otherwise
+ */
+int nand_read_page_op(struct nand_chip *chip, unsigned int page,
+		      unsigned int column, void *buf, unsigned int len)
+{
+	struct mtd_info *mtd = nand_to_mtd(chip);
+
+	if (len && !buf)
+		return -EINVAL;
+
+	if (column + len > mtd->writesize + mtd->oobsize)
+		return -EINVAL;
+
+	chip->cmdfunc(mtd, NAND_CMD_READ0, column, page);
+	if (len)
+		chip->read_buf(mtd, buf, len);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(nand_read_page_op);
+
+/**
+ * nand_read_param_page_op - Do a READ PARAMETER PAGE operation
+ * @chip: The NAND chip
+ * @page: parameter page to read
+ * @buf: buffer used to store the data
+ * @len: length of the buffer
+ *
+ * This function issues a READ PARAMETER PAGE operation.
+ * This function does not select/unselect the CS line.
+ *
+ * Returns 0 for success or negative error code otherwise
+ */
+static int nand_read_param_page_op(struct nand_chip *chip, u8 page, void *buf,
+				   unsigned int len)
+{
+	struct mtd_info *mtd = nand_to_mtd(chip);
+	unsigned int i;
+	u8 *p = buf;
+
+	if (len && !buf)
+		return -EINVAL;
+
+	chip->cmdfunc(mtd, NAND_CMD_PARAM, page, -1);
+	for (i = 0; i < len; i++)
+		p[i] = chip->read_byte(mtd);
+
+	return 0;
+}
+
+/**
+ * nand_change_read_column_op - Do a CHANGE READ COLUMN operation
+ * @chip: The NAND chip
+ * @offset_in_page: offset within the page
+ * @buf: buffer used to store the data
+ * @len: length of the buffer
+ * @force_8bit: force 8-bit bus access
+ *
+ * This function issues a CHANGE READ COLUMN operation.
+ * This function does not select/unselect the CS line.
+ *
+ * Returns 0 for success or negative error code otherwise
+ */
+int nand_change_read_column_op(struct nand_chip *chip, unsigned int column,
+			       void *buf, unsigned int len, bool force_8bit)
+{
+	struct mtd_info *mtd = nand_to_mtd(chip);
+
+	if (len && !buf)
+		return -EINVAL;
+
+	if (column + len > mtd->writesize + mtd->oobsize)
+		return -EINVAL;
+
+	chip->cmdfunc(mtd, NAND_CMD_RNDOUT, column, -1);
+	if (len)
+		chip->read_buf(mtd, buf, len);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(nand_change_read_column_op);
+
+/**
+ * nand_read_oob_op - Do a READ OOB operation
+ * @chip: The NAND chip
+ * @page: page to read
+ * @offset_in_page: offset within the page
+ * @buf: buffer used to store the data
+ * @len: length of the buffer
+ *
+ * This function issues a READ OOB operation.
+ * This function does not select/unselect the CS line.
+ *
+ * Returns 0 for success or negative error code otherwise
+ */
+int nand_read_oob_op(struct nand_chip *chip, unsigned int page,
+		     unsigned int column, void *buf, unsigned int len)
+{
+	struct mtd_info *mtd = nand_to_mtd(chip);
+
+	if (len && !buf)
+		return -EINVAL;
+
+	if (column + len > mtd->oobsize)
+		return -EINVAL;
+
+	chip->cmdfunc(mtd, NAND_CMD_READOOB, column, page);
+	if (len)
+		chip->read_buf(mtd, buf, len);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(nand_read_oob_op);
+
+/**
+ * nand_prog_page_begin_op - starts a PROG PAGE operation
+ * @chip: The NAND chip
+ * @page: page to write
+ * @offset_in_page: offset within the page
+ * @buf: buffer containing the data to write to the page
+ * @len: length of the buffer
+ *
+ * This function issues the first half of a PROG PAGE operation.
+ * This function does not select/unselect the CS line.
+ *
+ * Returns 0 for success or negative error code otherwise
+ */
+int nand_prog_page_begin_op(struct nand_chip *chip, unsigned int page,
+			    unsigned int column, const void *buf,
+			    unsigned int len)
+{
+	struct mtd_info *mtd = nand_to_mtd(chip);
+
+	if (len && !buf)
+		return -EINVAL;
+
+	if (column + len > mtd->writesize + mtd->oobsize)
+		return -EINVAL;
+
+	chip->cmdfunc(mtd, NAND_CMD_SEQIN, column, page);
+
+	if (buf)
+		chip->write_buf(mtd, buf, len);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(nand_prog_page_begin_op);
+
+/**
+ * nand_prog_page_end_op - ends a PROG PAGE operation
+ * @chip: The NAND chip
+ *
+ * This function issues the second half of a PROG PAGE operation.
+ * This function does not select/unselect the CS line.
+ *
+ * Returns 0 for success or negative error code otherwise
+ */
+int nand_prog_page_end_op(struct nand_chip *chip)
+{
+	struct mtd_info *mtd = nand_to_mtd(chip);
+	int status;
+
+	chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1);
+
+	status = chip->waitfunc(mtd, chip);
+	if (status & NAND_STATUS_FAIL)
+		return -EIO;
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(nand_prog_page_end_op);
+
+/**
+ * nand_prog_page_op - Do a full PROG PAGE operation
+ * @chip: The NAND chip
+ * @page: page to write
+ * @offset_in_page: offset within the page
+ * @buf: buffer containing the data to write to the page
+ * @len: length of the buffer
+ *
+ * This function issues a full PROG PAGE operation.
+ * This function does not select/unselect the CS line.
+ *
+ * Returns 0 for success or negative error code otherwise
+ */
+int nand_prog_page_op(struct nand_chip *chip, unsigned int page,
+		      unsigned int column, const void *buf, unsigned int len)
+{
+	struct mtd_info *mtd = nand_to_mtd(chip);
+	int status;
+
+	if (!len || !buf)
+		return -EINVAL;
+
+	if (column + len > mtd->writesize + mtd->oobsize)
+		return -EINVAL;
+
+	chip->cmdfunc(mtd, NAND_CMD_SEQIN, column, page);
+	chip->write_buf(mtd, buf, len);
+	chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1);
+
+	status = chip->waitfunc(mtd, chip);
+	if (status & NAND_STATUS_FAIL)
+		return -EIO;
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(nand_prog_page_op);
+
+/**
+ * nand_change_write_column_op - Do a CHANGE WRITE COLUMN operation
+ * @chip: The NAND chip
+ * @offset_in_page: offset within the page
+ * @buf: buffer containing the data to send to the NAND
+ * @len: length of the buffer
+ * @force_8bit: force 8-bit bus access
+ *
+ * This function issues a CHANGE WRITE COLUMN operation.
+ * This function does not select/unselect the CS line.
+ *
+ * Returns 0 for success or negative error code otherwise
+ */
+int nand_change_write_column_op(struct nand_chip *chip, unsigned int column,
+				const void *buf, unsigned int len,
+				bool force_8bit)
+{
+	struct mtd_info *mtd = nand_to_mtd(chip);
+
+	if (len && !buf)
+		return -EINVAL;
+
+	if (column + len > mtd->writesize + mtd->oobsize)
+		return -EINVAL;
+
+	chip->cmdfunc(mtd, NAND_CMD_RNDIN, column, -1);
+	if (len)
+		chip->write_buf(mtd, buf, len);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(nand_change_write_column_op);
+
+/**
+ * nand_readid_op - Do a READID operation
+ * @chip: The NAND chip
+ * @addr: address cycle to pass after the READID command
+ * @buf: buffer used to store the ID
+ * @len: length of the buffer
+ *
+ * This function sends a READID command and reads back the ID returned by the
+ * NAND.
+ * This function does not select/unselect the CS line.
+ *
+ * Returns 0 for success or negative error code otherwise
+ */
+int nand_readid_op(struct nand_chip *chip, u8 addr,
+		   void *buf, unsigned int len)
+{
+	struct mtd_info *mtd = nand_to_mtd(chip);
+	unsigned int i;
+	u8 *id = buf;
+
+	if (!len || !buf)
+		return -EINVAL;
+
+	chip->cmdfunc(mtd, NAND_CMD_READID, addr, -1);
+
+	for (i = 0; i < len; i++)
+		id[i] = chip->read_byte(mtd);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(nand_readid_op);
+
+/**
+ * nand_status_op - Do a STATUS operation
+ * @chip: The NAND chip
+ * @status: out variable to store the NAND status
+ *
+ * This function sends a STATUS command and reads back the status returned by
+ * the NAND.
+ * This function does not select/unselect the CS line.
+ *
+ * Returns 0 for success or negative error code otherwise
+ */
+int nand_status_op(struct nand_chip *chip, u8 *status)
+{
+	struct mtd_info *mtd = nand_to_mtd(chip);
+
+	chip->cmdfunc(mtd, NAND_CMD_STATUS, -1, -1);
+	if (status)
+		*status = chip->read_byte(mtd);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(nand_status_op);
+
+/**
+ * nand_erase_op - Do an erase operation
+ * @chip: The NAND chip
+ * @eraseblock: block to erase
+ *
+ * This function sends an ERASE command and waits for the NAND to be ready
+ * before returning.
+ * This function does not select/unselect the CS line.
+ *
+ * Returns 0 for success or negative error code otherwise
+ */
+int nand_erase_op(struct nand_chip *chip, unsigned int eraseblock)
+{
+	struct mtd_info *mtd = nand_to_mtd(chip);
+	unsigned int page = eraseblock <<
+			    (chip->phys_erase_shift - chip->page_shift);
+	int status;
+
+	chip->cmdfunc(mtd, NAND_CMD_ERASE1, -1, page);
+	chip->cmdfunc(mtd, NAND_CMD_ERASE2, -1, -1);
+
+	status = chip->waitfunc(mtd, chip);
+	if (status < 0)
+		return status;
+
+	if (status & NAND_STATUS_FAIL)
+		return -EIO;
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(nand_erase_op);
+
+/**
+ * nand_set_features_op - Do a SET FEATURES operation
+ * @chip: The NAND chip
+ * @feature: feature id
+ * @data: 4 bytes of data
+ *
+ * This function sends a SET FEATURES command and waits for the NAND to be
+ * ready before returning.
+ * This function does not select/unselect the CS line.
+ *
+ * Returns 0 for success or negative error code otherwise
+ */
+static int nand_set_features_op(struct nand_chip *chip, u8 feature,
+				const void *data)
+{
+	struct mtd_info *mtd = nand_to_mtd(chip);
+	const u8 *params = data;
+	int i, status;
+
+	chip->cmdfunc(mtd, NAND_CMD_SET_FEATURES, feature, -1);
+	for (i = 0; i < ONFI_SUBFEATURE_PARAM_LEN; ++i)
+		chip->write_byte(mtd, params[i]);
+
+	status = chip->waitfunc(mtd, chip);
+	if (status & NAND_STATUS_FAIL)
+		return -EIO;
+
+	return 0;
+}
+
+/**
+ * nand_get_features_op - Do a GET FEATURES operation
+ * @chip: The NAND chip
+ * @feature: feature id
+ * @data: 4 bytes of data
+ *
+ * This function sends a GET FEATURES command and waits for the NAND to be
+ * ready before returning.
+ * This function does not select/unselect the CS line.
+ *
+ * Returns 0 for success or negative error code otherwise
+ */
+static int nand_get_features_op(struct nand_chip *chip, u8 feature,
+				void *data)
+{
+	struct mtd_info *mtd = nand_to_mtd(chip);
+	u8 *params = data;
+	int i;
+
+	chip->cmdfunc(mtd, NAND_CMD_GET_FEATURES, feature, -1);
+	for (i = 0; i < ONFI_SUBFEATURE_PARAM_LEN; ++i)
+		params[i] = chip->read_byte(mtd);
+
+	return 0;
+}
+
+/**
+ * nand_reset_op - Do a reset operation
+ * @chip: The NAND chip
+ *
+ * This function sends a RESET command and waits for the NAND to be ready
+ * before returning.
+ * This function does not select/unselect the CS line.
+ *
+ * Returns 0 for success or negative error code otherwise
+ */
+int nand_reset_op(struct nand_chip *chip)
+{
+	struct mtd_info *mtd = nand_to_mtd(chip);
+
+	chip->cmdfunc(mtd, NAND_CMD_RESET, -1, -1);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(nand_reset_op);
+
+/**
+ * nand_read_data_op - Read data from the NAND
+ * @chip: The NAND chip
+ * @buf: buffer used to store the data
+ * @len: length of the buffer
+ * @force_8bit: force 8-bit bus access
+ *
+ * This function does a raw data read on the bus. Usually used after launching
+ * another NAND operation like nand_read_page_op().
+ * This function does not select/unselect the CS line.
+ *
+ * Returns 0 for success or negative error code otherwise
+ */
+int nand_read_data_op(struct nand_chip *chip, void *buf, unsigned int len,
+		      bool force_8bit)
+{
+	struct mtd_info *mtd = nand_to_mtd(chip);
+
+	if (!len || !buf)
+		return -EINVAL;
+
+	if (force_8bit) {
+		u8 *p = buf;
+		unsigned int i;
+
+		for (i = 0; i < len; i++)
+			p[i] = chip->read_byte(mtd);
+	} else {
+		chip->read_buf(mtd, buf, len);
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(nand_read_data_op);
+
+/**
+ * nand_write_data_op - Write data from the NAND
+ * @chip: The NAND chip
+ * @buf: buffer containing the data to send on the bus
+ * @len: length of the buffer
+ * @force_8bit: force 8-bit bus access
+ *
+ * This function does a raw data write on the bus. Usually used after launching
+ * another NAND operation like nand_write_page_begin_op().
+ * This function does not select/unselect the CS line.
+ *
+ * Returns 0 for success or negative error code otherwise
+ */
+int nand_write_data_op(struct nand_chip *chip, const void *buf,
+		       unsigned int len, bool force_8bit)
+{
+	struct mtd_info *mtd = nand_to_mtd(chip);
+
+	if (!len || !buf)
+		return -EINVAL;
+
+	if (force_8bit) {
+		const u8 *p = buf;
+		unsigned int i;
+
+		for (i = 0; i < len; i++)
+			chip->write_byte(mtd, p[i]);
+	} else {
+		chip->write_buf(mtd, buf, len);
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(nand_write_data_op);
+
+/**
  * nand_reset - Reset and initialize a NAND device
  * @chip: The NAND chip
  * @chipnr: Internal die id
@@ -1233,7 +1750,7 @@ int nand_reset(struct nand_chip *chip, int chipnr)
 	 * interface settings, hence this weird ->select_chip() dance.
 	 */
 	chip->select_chip(mtd, chipnr);
-	chip->cmdfunc(mtd, NAND_CMD_RESET, -1, -1);
+	nand_reset_op(chip);
 	chip->select_chip(mtd, -1);
 
 	chip->select_chip(mtd, chipnr);
@@ -1389,9 +1906,19 @@ EXPORT_SYMBOL(nand_check_erased_ecc_chunk);
 int nand_read_page_raw(struct mtd_info *mtd, struct nand_chip *chip,
 		       uint8_t *buf, int oob_required, int page)
 {
-	chip->read_buf(mtd, buf, mtd->writesize);
-	if (oob_required)
-		chip->read_buf(mtd, chip->oob_poi, mtd->oobsize);
+	int ret;
+
+	ret = nand_read_data_op(chip, buf, mtd->writesize, false);
+	if (ret)
+		return ret;
+
+	if (oob_required) {
+		ret = nand_read_data_op(chip, chip->oob_poi, mtd->oobsize,
+					false);
+		if (ret)
+			return ret;
+	}
+
 	return 0;
 }
 EXPORT_SYMBOL(nand_read_page_raw);
@@ -1413,29 +1940,46 @@ static int nand_read_page_raw_syndrome(struct mtd_info *mtd,
 	int eccsize = chip->ecc.size;
 	int eccbytes = chip->ecc.bytes;
 	uint8_t *oob = chip->oob_poi;
-	int steps, size;
+	int steps, size, ret;
 
 	for (steps = chip->ecc.steps; steps > 0; steps--) {
-		chip->read_buf(mtd, buf, eccsize);
+		ret = nand_read_data_op(chip, buf, eccsize, false);
+		if (ret)
+			return ret;
+
 		buf += eccsize;
 
 		if (chip->ecc.prepad) {
-			chip->read_buf(mtd, oob, chip->ecc.prepad);
+			ret = nand_read_data_op(chip, oob, chip->ecc.prepad,
+						false);
+			if (ret)
+				return ret;
+
 			oob += chip->ecc.prepad;
 		}
 
-		chip->read_buf(mtd, oob, eccbytes);
+		ret = nand_read_data_op(chip, oob, eccbytes, false);
+		if (ret)
+			return ret;
+
 		oob += eccbytes;
 
 		if (chip->ecc.postpad) {
-			chip->read_buf(mtd, oob, chip->ecc.postpad);
+			ret = nand_read_data_op(chip, oob, chip->ecc.postpad,
+						false);
+			if (ret)
+				return ret;
+
 			oob += chip->ecc.postpad;
 		}
 	}
 
 	size = mtd->oobsize - (oob - chip->oob_poi);
-	if (size)
-		chip->read_buf(mtd, oob, size);
+	if (size) {
+		ret = nand_read_data_op(chip, oob, size, false);
+		if (ret)
+			return ret;
+	}
 
 	return 0;
 }
@@ -1524,7 +2068,7 @@ static int nand_read_subpage(struct mtd_info *mtd, struct nand_chip *chip,
 		chip->cmdfunc(mtd, NAND_CMD_RNDOUT, data_col_addr, -1);
 
 	p = bufpoi + data_col_addr;
-	chip->read_buf(mtd, p, datafrag_len);
+	nand_read_data_op(chip, p, datafrag_len, false);
 
 	/* Calculate ECC */
 	for (i = 0; i < eccfrag_len ; i += chip->ecc.bytes, p += chip->ecc.size)
@@ -1542,8 +2086,11 @@ static int nand_read_subpage(struct mtd_info *mtd, struct nand_chip *chip,
 		gaps = 1;
 
 	if (gaps) {
-		chip->cmdfunc(mtd, NAND_CMD_RNDOUT, mtd->writesize, -1);
-		chip->read_buf(mtd, chip->oob_poi, mtd->oobsize);
+		ret = nand_change_read_column_op(chip, mtd->writesize,
+						 chip->oob_poi, mtd->oobsize,
+						 false);
+		if (ret)
+			return ret;
 	} else {
 		/*
 		 * Send the command to read the particular ECC bytes take care
@@ -1557,9 +2104,12 @@ static int nand_read_subpage(struct mtd_info *mtd, struct nand_chip *chip,
 		    (busw - 1))
 			aligned_len++;
 
-		chip->cmdfunc(mtd, NAND_CMD_RNDOUT,
-			      mtd->writesize + aligned_pos, -1);
-		chip->read_buf(mtd, &chip->oob_poi[aligned_pos], aligned_len);
+		ret = nand_change_read_column_op(chip,
+						 mtd->writesize + aligned_pos,
+						 &chip->oob_poi[aligned_pos],
+						 aligned_len, false);
+		if (ret)
+			return ret;
 	}
 
 	ret = mtd_ooblayout_get_eccbytes(mtd, chip->buffers->ecccode,
@@ -1616,10 +2166,17 @@ static int nand_read_page_hwecc(struct mtd_info *mtd, struct nand_chip *chip,
 
 	for (i = 0; eccsteps; eccsteps--, i += eccbytes, p += eccsize) {
 		chip->ecc.hwctl(mtd, NAND_ECC_READ);
-		chip->read_buf(mtd, p, eccsize);
+
+		ret = nand_read_data_op(chip, p, eccsize, false);
+		if (ret)
+			return ret;
+
 		chip->ecc.calculate(mtd, p, &ecc_calc[i]);
 	}
-	chip->read_buf(mtd, chip->oob_poi, mtd->oobsize);
+
+	ret = nand_read_data_op(chip, chip->oob_poi, mtd->oobsize, false);
+	if (ret)
+		return ret;
 
 	ret = mtd_ooblayout_get_eccbytes(mtd, ecc_code, chip->oob_poi, 0,
 					 chip->ecc.total);
@@ -1678,9 +2235,11 @@ static int nand_read_page_hwecc_oob_first(struct mtd_info *mtd,
 	unsigned int max_bitflips = 0;
 
 	/* Read the OOB area first */
-	chip->cmdfunc(mtd, NAND_CMD_READOOB, 0, page);
-	chip->read_buf(mtd, chip->oob_poi, mtd->oobsize);
-	chip->cmdfunc(mtd, NAND_CMD_READ0, 0, page);
+	ret = nand_read_oob_op(chip, page, 0, chip->oob_poi, mtd->oobsize);
+	if (ret)
+		return ret;
+
+	nand_read_page_op(chip, page, 0, NULL, 0);
 
 	ret = mtd_ooblayout_get_eccbytes(mtd, ecc_code, chip->oob_poi, 0,
 					 chip->ecc.total);
@@ -1691,7 +2250,11 @@ static int nand_read_page_hwecc_oob_first(struct mtd_info *mtd,
 		int stat;
 
 		chip->ecc.hwctl(mtd, NAND_ECC_READ);
-		chip->read_buf(mtd, p, eccsize);
+
+		ret = nand_read_data_op(chip, p, eccsize, false);
+		if (ret)
+			return ret;
+
 		chip->ecc.calculate(mtd, p, &ecc_calc[i]);
 
 		stat = chip->ecc.correct(mtd, p, &ecc_code[i], NULL);
@@ -1728,7 +2291,7 @@ static int nand_read_page_hwecc_oob_first(struct mtd_info *mtd,
 static int nand_read_page_syndrome(struct mtd_info *mtd, struct nand_chip *chip,
 				   uint8_t *buf, int oob_required, int page)
 {
-	int i, eccsize = chip->ecc.size;
+	int ret, i, eccsize = chip->ecc.size;
 	int eccbytes = chip->ecc.bytes;
 	int eccsteps = chip->ecc.steps;
 	int eccpadbytes = eccbytes + chip->ecc.prepad + chip->ecc.postpad;
@@ -1740,21 +2303,36 @@ static int nand_read_page_syndrome(struct mtd_info *mtd, struct nand_chip *chip,
 		int stat;
 
 		chip->ecc.hwctl(mtd, NAND_ECC_READ);
-		chip->read_buf(mtd, p, eccsize);
+
+		ret = nand_read_data_op(chip, p, eccsize, false);
+		if (ret)
+			return ret;
 
 		if (chip->ecc.prepad) {
-			chip->read_buf(mtd, oob, chip->ecc.prepad);
+			ret = nand_read_data_op(chip, oob, chip->ecc.prepad,
+						false);
+			if (ret)
+				return ret;
+
 			oob += chip->ecc.prepad;
 		}
 
 		chip->ecc.hwctl(mtd, NAND_ECC_READSYN);
-		chip->read_buf(mtd, oob, eccbytes);
+
+		ret = nand_read_data_op(chip, oob, eccbytes, false);
+		if (ret)
+			return ret;
+
 		stat = chip->ecc.correct(mtd, p, oob, NULL);
 
 		oob += eccbytes;
 
 		if (chip->ecc.postpad) {
-			chip->read_buf(mtd, oob, chip->ecc.postpad);
+			ret = nand_read_data_op(chip, oob, chip->ecc.postpad,
+						false);
+			if (ret)
+				return ret;
+
 			oob += chip->ecc.postpad;
 		}
 
@@ -1778,8 +2356,11 @@ static int nand_read_page_syndrome(struct mtd_info *mtd, struct nand_chip *chip,
 
 	/* Calculate remaining oob bytes */
 	i = mtd->oobsize - (oob - chip->oob_poi);
-	if (i)
-		chip->read_buf(mtd, oob, i);
+	if (i) {
+		ret = nand_read_data_op(chip, oob, i, false);
+		if (ret)
+			return ret;
+	}
 
 	return max_bitflips;
 }
@@ -1901,7 +2482,7 @@ static int nand_do_read_ops(struct mtd_info *mtd, loff_t from,
 
 read_retry:
 			if (nand_standard_page_accessors(&chip->ecc))
-				chip->cmdfunc(mtd, NAND_CMD_READ0, 0x00, page);
+				nand_read_page_op(chip, page, 0, NULL, 0);
 
 			/*
 			 * Now read the page into the buffer.  Absent an error,
@@ -2060,9 +2641,7 @@ static int nand_read(struct mtd_info *mtd, loff_t from, size_t len,
  */
 int nand_read_oob_std(struct mtd_info *mtd, struct nand_chip *chip, int page)
 {
-	chip->cmdfunc(mtd, NAND_CMD_READOOB, 0, page);
-	chip->read_buf(mtd, chip->oob_poi, mtd->oobsize);
-	return 0;
+	return nand_read_oob_op(chip, page, 0, chip->oob_poi, mtd->oobsize);
 }
 EXPORT_SYMBOL(nand_read_oob_std);
 
@@ -2080,25 +2659,39 @@ int nand_read_oob_syndrome(struct mtd_info *mtd, struct nand_chip *chip,
 	int chunk = chip->ecc.bytes + chip->ecc.prepad + chip->ecc.postpad;
 	int eccsize = chip->ecc.size;
 	uint8_t *bufpoi = chip->oob_poi;
-	int i, toread, sndrnd = 0, pos;
+	int i, toread, sndrnd = 0, pos, ret;
 
-	chip->cmdfunc(mtd, NAND_CMD_READ0, chip->ecc.size, page);
+	nand_read_page_op(chip, page, chip->ecc.size, NULL, 0);
 	for (i = 0; i < chip->ecc.steps; i++) {
 		if (sndrnd) {
+			int ret;
+
 			pos = eccsize + i * (eccsize + chunk);
 			if (mtd->writesize > 512)
-				chip->cmdfunc(mtd, NAND_CMD_RNDOUT, pos, -1);
+				ret = nand_change_read_column_op(
+					chip, pos, NULL, 0, false);
 			else
-				chip->cmdfunc(mtd, NAND_CMD_READ0, pos, page);
+				ret = nand_read_page_op(chip, page, pos, NULL,
+							0);
+
+			if (ret)
+				return ret;
 		} else
 			sndrnd = 1;
 		toread = min_t(int, length, chunk);
-		chip->read_buf(mtd, bufpoi, toread);
+
+		ret = nand_read_data_op(chip, bufpoi, toread, false);
+		if (ret)
+			return ret;
+
 		bufpoi += toread;
 		length -= toread;
 	}
-	if (length > 0)
-		chip->read_buf(mtd, bufpoi, length);
+	if (length > 0) {
+		ret = nand_read_data_op(chip, bufpoi, length, false);
+		if (ret)
+			return ret;
+	}
 
 	return 0;
 }
@@ -2112,18 +2705,8 @@ EXPORT_SYMBOL(nand_read_oob_syndrome);
  */
 int nand_write_oob_std(struct mtd_info *mtd, struct nand_chip *chip, int page)
 {
-	int status = 0;
-	const uint8_t *buf = chip->oob_poi;
-	int length = mtd->oobsize;
-
-	chip->cmdfunc(mtd, NAND_CMD_SEQIN, mtd->writesize, page);
-	chip->write_buf(mtd, buf, length);
-	/* Send command to program the OOB data */
-	chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1);
-
-	status = chip->waitfunc(mtd, chip);
-
-	return status & NAND_STATUS_FAIL ? -EIO : 0;
+	return nand_prog_page_op(chip, page, mtd->writesize, chip->oob_poi,
+				 mtd->oobsize);
 }
 EXPORT_SYMBOL(nand_write_oob_std);
 
@@ -2139,7 +2722,7 @@ int nand_write_oob_syndrome(struct mtd_info *mtd, struct nand_chip *chip,
 {
 	int chunk = chip->ecc.bytes + chip->ecc.prepad + chip->ecc.postpad;
 	int eccsize = chip->ecc.size, length = mtd->oobsize;
-	int i, len, pos, status = 0, sndcmd = 0, steps = chip->ecc.steps;
+	int ret, i, len, pos, sndcmd = 0, steps = chip->ecc.steps;
 	const uint8_t *bufpoi = chip->oob_poi;
 
 	/*
@@ -2153,7 +2736,7 @@ int nand_write_oob_syndrome(struct mtd_info *mtd, struct nand_chip *chip,
 	} else
 		pos = eccsize;
 
-	chip->cmdfunc(mtd, NAND_CMD_SEQIN, pos, page);
+	nand_prog_page_begin_op(chip, page, pos, NULL, 0);
 	for (i = 0; i < steps; i++) {
 		if (sndcmd) {
 			if (mtd->writesize <= 512) {
@@ -2162,28 +2745,39 @@ int nand_write_oob_syndrome(struct mtd_info *mtd, struct nand_chip *chip,
 				len = eccsize;
 				while (len > 0) {
 					int num = min_t(int, len, 4);
-					chip->write_buf(mtd, (uint8_t *)&fill,
-							num);
+
+					ret = nand_write_data_op(chip, &fill,
+								 num, false);
+					if (ret)
+						return ret;
+
 					len -= num;
 				}
 			} else {
 				pos = eccsize + i * (eccsize + chunk);
-				chip->cmdfunc(mtd, NAND_CMD_RNDIN, pos, -1);
+				ret = nand_change_write_column_op(
+					chip, pos, NULL, 0, false);
+				if (ret)
+					return ret;
 			}
 		} else
 			sndcmd = 1;
 		len = min_t(int, length, chunk);
-		chip->write_buf(mtd, bufpoi, len);
+
+		ret = nand_write_data_op(chip, bufpoi, len, false);
+		if (ret)
+			return ret;
+
 		bufpoi += len;
 		length -= len;
 	}
-	if (length > 0)
-		chip->write_buf(mtd, bufpoi, length);
-
-	chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1);
-	status = chip->waitfunc(mtd, chip);
+	if (length > 0) {
+		ret = nand_write_data_op(chip, bufpoi, length, false);
+		if (ret)
+			return ret;
+	}
 
-	return status & NAND_STATUS_FAIL ? -EIO : 0;
+	return nand_prog_page_end_op(chip);
 }
 EXPORT_SYMBOL(nand_write_oob_syndrome);
 
@@ -2335,9 +2929,18 @@ static int nand_read_oob(struct mtd_info *mtd, loff_t from,
 int nand_write_page_raw(struct mtd_info *mtd, struct nand_chip *chip,
 			const uint8_t *buf, int oob_required, int page)
 {
-	chip->write_buf(mtd, buf, mtd->writesize);
-	if (oob_required)
-		chip->write_buf(mtd, chip->oob_poi, mtd->oobsize);
+	int ret;
+
+	ret = nand_write_data_op(chip, buf, mtd->writesize, false);
+	if (ret)
+		return ret;
+
+	if (oob_required) {
+		ret = nand_write_data_op(chip, chip->oob_poi, mtd->oobsize,
+					 false);
+		if (ret)
+			return ret;
+	}
 
 	return 0;
 }
@@ -2361,29 +2964,46 @@ static int nand_write_page_raw_syndrome(struct mtd_info *mtd,
 	int eccsize = chip->ecc.size;
 	int eccbytes = chip->ecc.bytes;
 	uint8_t *oob = chip->oob_poi;
-	int steps, size;
+	int steps, size, ret;
 
 	for (steps = chip->ecc.steps; steps > 0; steps--) {
-		chip->write_buf(mtd, buf, eccsize);
+		ret = nand_write_data_op(chip, buf, eccsize, false);
+		if (ret)
+			return ret;
+
 		buf += eccsize;
 
 		if (chip->ecc.prepad) {
-			chip->write_buf(mtd, oob, chip->ecc.prepad);
+			ret = nand_write_data_op(chip, oob, chip->ecc.prepad,
+						 false);
+			if (ret)
+				return ret;
+
 			oob += chip->ecc.prepad;
 		}
 
-		chip->write_buf(mtd, oob, eccbytes);
+		ret = nand_write_data_op(chip, oob, eccbytes, false);
+		if (ret)
+			return ret;
+
 		oob += eccbytes;
 
 		if (chip->ecc.postpad) {
-			chip->write_buf(mtd, oob, chip->ecc.postpad);
+			ret = nand_write_data_op(chip, oob, chip->ecc.postpad,
+						 false);
+			if (ret)
+				return ret;
+
 			oob += chip->ecc.postpad;
 		}
 	}
 
 	size = mtd->oobsize - (oob - chip->oob_poi);
-	if (size)
-		chip->write_buf(mtd, oob, size);
+	if (size) {
+		ret = nand_write_data_op(chip, oob, size, false);
+		if (ret)
+			return ret;
+	}
 
 	return 0;
 }
@@ -2437,7 +3057,11 @@ static int nand_write_page_hwecc(struct mtd_info *mtd, struct nand_chip *chip,
 
 	for (i = 0; eccsteps; eccsteps--, i += eccbytes, p += eccsize) {
 		chip->ecc.hwctl(mtd, NAND_ECC_WRITE);
-		chip->write_buf(mtd, p, eccsize);
+
+		ret = nand_write_data_op(chip, p, eccsize, false);
+		if (ret)
+			return ret;
+
 		chip->ecc.calculate(mtd, p, &ecc_calc[i]);
 	}
 
@@ -2446,7 +3070,9 @@ static int nand_write_page_hwecc(struct mtd_info *mtd, struct nand_chip *chip,
 	if (ret)
 		return ret;
 
-	chip->write_buf(mtd, chip->oob_poi, mtd->oobsize);
+	ret = nand_write_data_op(chip, chip->oob_poi, mtd->oobsize, false);
+	if (ret)
+		return ret;
 
 	return 0;
 }
@@ -2482,7 +3108,9 @@ static int nand_write_subpage_hwecc(struct mtd_info *mtd,
 		chip->ecc.hwctl(mtd, NAND_ECC_WRITE);
 
 		/* write data (untouched subpages already masked by 0xFF) */
-		chip->write_buf(mtd, buf, ecc_size);
+		ret = nand_write_data_op(chip, buf, ecc_size, false);
+		if (ret)
+			return ret;
 
 		/* mask ECC of un-touched subpages by padding 0xFF */
 		if ((step < start_step) || (step > end_step))
@@ -2509,7 +3137,9 @@ static int nand_write_subpage_hwecc(struct mtd_info *mtd,
 		return ret;
 
 	/* write OOB buffer to NAND device */
-	chip->write_buf(mtd, chip->oob_poi, mtd->oobsize);
+	ret = nand_write_data_op(chip, chip->oob_poi, mtd->oobsize, false);
+	if (ret)
+		return ret;
 
 	return 0;
 }
@@ -2536,31 +3166,49 @@ static int nand_write_page_syndrome(struct mtd_info *mtd,
 	int eccsteps = chip->ecc.steps;
 	const uint8_t *p = buf;
 	uint8_t *oob = chip->oob_poi;
+	int ret;
 
 	for (i = 0; eccsteps; eccsteps--, i += eccbytes, p += eccsize) {
-
 		chip->ecc.hwctl(mtd, NAND_ECC_WRITE);
-		chip->write_buf(mtd, p, eccsize);
+
+		ret = nand_write_data_op(chip, p, eccsize, false);
+		if (ret)
+			return ret;
 
 		if (chip->ecc.prepad) {
-			chip->write_buf(mtd, oob, chip->ecc.prepad);
+			ret = nand_write_data_op(chip, oob, chip->ecc.prepad,
+						 false);
+			if (ret)
+				return ret;
+
 			oob += chip->ecc.prepad;
 		}
 
 		chip->ecc.calculate(mtd, p, oob);
-		chip->write_buf(mtd, oob, eccbytes);
+
+		ret = nand_write_data_op(chip, oob, eccbytes, false);
+		if (ret)
+			return ret;
+
 		oob += eccbytes;
 
 		if (chip->ecc.postpad) {
-			chip->write_buf(mtd, oob, chip->ecc.postpad);
+			ret = nand_write_data_op(chip, oob, chip->ecc.postpad,
+						 false);
+			if (ret)
+				return ret;
+
 			oob += chip->ecc.postpad;
 		}
 	}
 
 	/* Calculate remaining oob bytes */
 	i = mtd->oobsize - (oob - chip->oob_poi);
-	if (i)
-		chip->write_buf(mtd, oob, i);
+	if (i) {
+		ret = nand_write_data_op(chip, oob, i, false);
+		if (ret)
+			return ret;
+	}
 
 	return 0;
 }
@@ -2588,8 +3236,11 @@ static int nand_write_page(struct mtd_info *mtd, struct nand_chip *chip,
 	else
 		subpage = 0;
 
-	if (nand_standard_page_accessors(&chip->ecc))
-		chip->cmdfunc(mtd, NAND_CMD_SEQIN, 0x00, page);
+	if (nand_standard_page_accessors(&chip->ecc)) {
+		status = nand_prog_page_begin_op(chip, page, 0, NULL, 0);
+		if (status)
+			return status;
+	}
 
 	if (unlikely(raw))
 		status = chip->ecc.write_page_raw(mtd, chip, buf,
@@ -2604,13 +3255,8 @@ static int nand_write_page(struct mtd_info *mtd, struct nand_chip *chip,
 	if (status < 0)
 		return status;
 
-	if (nand_standard_page_accessors(&chip->ecc)) {
-		chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1);
-
-		status = chip->waitfunc(mtd, chip);
-		if (status & NAND_STATUS_FAIL)
-			return -EIO;
-	}
+	if (nand_standard_page_accessors(&chip->ecc))
+		return nand_prog_page_end_op(chip);
 
 	return 0;
 }
@@ -2981,11 +3627,12 @@ static int nand_write_oob(struct mtd_info *mtd, loff_t to,
 static int single_erase(struct mtd_info *mtd, int page)
 {
 	struct nand_chip *chip = mtd_to_nand(mtd);
+	unsigned int eraseblock;
+
 	/* Send commands to erase a block */
-	chip->cmdfunc(mtd, NAND_CMD_ERASE1, -1, page);
-	chip->cmdfunc(mtd, NAND_CMD_ERASE2, -1, -1);
+	eraseblock = page >> (chip->phys_erase_shift - chip->page_shift);
 
-	return chip->waitfunc(mtd, chip);
+	return nand_erase_op(chip, eraseblock);
 }
 
 /**
@@ -3069,7 +3716,7 @@ int nand_erase_nand(struct mtd_info *mtd, struct erase_info *instr,
 		status = chip->erase(mtd, page & chip->pagemask);
 
 		/* See if block erase succeeded */
-		if (status & NAND_STATUS_FAIL) {
+		if (status) {
 			pr_debug("%s: failed erase, page 0x%08x\n",
 					__func__, page);
 			instr->state = MTD_ERASE_FAILED;
@@ -3212,22 +3859,12 @@ static int nand_max_bad_blocks(struct mtd_info *mtd, loff_t ofs, size_t len)
 static int nand_onfi_set_features(struct mtd_info *mtd, struct nand_chip *chip,
 			int addr, uint8_t *subfeature_param)
 {
-	int status;
-	int i;
-
 	if (!chip->onfi_version ||
 	    !(le16_to_cpu(chip->onfi_params.opt_cmd)
 	      & ONFI_OPT_CMD_SET_GET_FEATURES))
 		return -EINVAL;
 
-	chip->cmdfunc(mtd, NAND_CMD_SET_FEATURES, addr, -1);
-	for (i = 0; i < ONFI_SUBFEATURE_PARAM_LEN; ++i)
-		chip->write_byte(mtd, subfeature_param[i]);
-
-	status = chip->waitfunc(mtd, chip);
-	if (status & NAND_STATUS_FAIL)
-		return -EIO;
-	return 0;
+	return nand_set_features_op(chip, addr, subfeature_param);
 }
 
 /**
@@ -3240,17 +3877,12 @@ static int nand_onfi_set_features(struct mtd_info *mtd, struct nand_chip *chip,
 static int nand_onfi_get_features(struct mtd_info *mtd, struct nand_chip *chip,
 			int addr, uint8_t *subfeature_param)
 {
-	int i;
-
 	if (!chip->onfi_version ||
 	    !(le16_to_cpu(chip->onfi_params.opt_cmd)
 	      & ONFI_OPT_CMD_SET_GET_FEATURES))
 		return -EINVAL;
 
-	chip->cmdfunc(mtd, NAND_CMD_GET_FEATURES, addr, -1);
-	for (i = 0; i < ONFI_SUBFEATURE_PARAM_LEN; ++i)
-		*subfeature_param++ = chip->read_byte(mtd);
-	return 0;
+	return nand_get_features_op(chip, addr, subfeature_param);
 }
 
 /**
@@ -3393,12 +4025,11 @@ static u16 onfi_crc16(u16 crc, u8 const *p, size_t len)
 static int nand_flash_detect_ext_param_page(struct nand_chip *chip,
 					    struct nand_onfi_params *p)
 {
-	struct mtd_info *mtd = nand_to_mtd(chip);
 	struct onfi_ext_param_page *ep;
 	struct onfi_ext_section *s;
 	struct onfi_ext_ecc_info *ecc;
 	uint8_t *cursor;
-	int ret = -EINVAL;
+	int ret;
 	int len;
 	int i;
 
@@ -3408,14 +4039,18 @@ static int nand_flash_detect_ext_param_page(struct nand_chip *chip,
 		return -ENOMEM;
 
 	/* Send our own NAND_CMD_PARAM. */
-	chip->cmdfunc(mtd, NAND_CMD_PARAM, 0, -1);
+	ret = nand_read_param_page_op(chip, 0, NULL, 0);
+	if (ret)
+		return ret;
 
 	/* Use the Change Read Column command to skip the ONFI param pages. */
-	chip->cmdfunc(mtd, NAND_CMD_RNDOUT,
-			sizeof(*p) * p->num_of_param_pages , -1);
+	ret = nand_change_read_column_op(chip,
+					 sizeof(*p) * p->num_of_param_pages,
+					 ep, len, true);
+	if (ret)
+		return ret;
 
-	/* Read out the Extended Parameter Page. */
-	chip->read_buf(mtd, (uint8_t *)ep, len);
+	ret = -EINVAL;
 	if ((onfi_crc16(ONFI_CRC_BASE, ((uint8_t *)ep) + 2, len - 2)
 		!= le16_to_cpu(ep->crc))) {
 		pr_debug("fail in the CRC.\n");
@@ -3468,19 +4103,23 @@ static int nand_flash_detect_onfi(struct nand_chip *chip)
 {
 	struct mtd_info *mtd = nand_to_mtd(chip);
 	struct nand_onfi_params *p = &chip->onfi_params;
-	int i, j;
-	int val;
+	char id[4];
+	int i, ret, val;
 
 	/* Try ONFI for unknown chip or LP */
-	chip->cmdfunc(mtd, NAND_CMD_READID, 0x20, -1);
-	if (chip->read_byte(mtd) != 'O' || chip->read_byte(mtd) != 'N' ||
-		chip->read_byte(mtd) != 'F' || chip->read_byte(mtd) != 'I')
+	nand_readid_op(chip, 0x20, id, sizeof(id));
+	if (strncmp(id, "ONFI", 4))
 		return 0;
 
-	chip->cmdfunc(mtd, NAND_CMD_PARAM, 0, -1);
+	ret = nand_read_param_page_op(chip, 0, NULL, 0);
+	if (ret)
+		return ret;
+
 	for (i = 0; i < 3; i++) {
-		for (j = 0; j < sizeof(*p); j++)
-			((uint8_t *)p)[j] = chip->read_byte(mtd);
+		ret = nand_read_data_op(chip, p, sizeof(*p), true);
+		if (ret)
+			return ret;
+
 		if (onfi_crc16(ONFI_CRC_BASE, (uint8_t *)p, 254) ==
 				le16_to_cpu(p->crc)) {
 			break;
@@ -3571,20 +4210,22 @@ static int nand_flash_detect_jedec(struct nand_chip *chip)
 	struct mtd_info *mtd = nand_to_mtd(chip);
 	struct nand_jedec_params *p = &chip->jedec_params;
 	struct jedec_ecc_info *ecc;
-	int val;
-	int i, j;
+	char id[5];
+	int i, val, ret;
 
 	/* Try JEDEC for unknown chip or LP */
-	chip->cmdfunc(mtd, NAND_CMD_READID, 0x40, -1);
-	if (chip->read_byte(mtd) != 'J' || chip->read_byte(mtd) != 'E' ||
-		chip->read_byte(mtd) != 'D' || chip->read_byte(mtd) != 'E' ||
-		chip->read_byte(mtd) != 'C')
+	nand_readid_op(chip, 0x40, id, sizeof(id));
+	if (strncmp(id, "JEDEC", sizeof(id)))
 		return 0;
 
-	chip->cmdfunc(mtd, NAND_CMD_PARAM, 0x40, -1);
+	ret = nand_read_param_page_op(chip, 0x40, NULL, 0);
+	if (ret)
+		return ret;
+
 	for (i = 0; i < 3; i++) {
-		for (j = 0; j < sizeof(*p); j++)
-			((uint8_t *)p)[j] = chip->read_byte(mtd);
+		ret = nand_read_data_op(chip, p, sizeof(*p), true);
+		if (ret)
+			return ret;
 
 		if (onfi_crc16(ONFI_CRC_BASE, (uint8_t *)p, 510) ==
 				le16_to_cpu(p->crc))
@@ -3864,7 +4505,6 @@ static int nand_detect(struct nand_chip *chip, struct nand_flash_dev *type)
 	const struct nand_manufacturer *manufacturer;
 	struct mtd_info *mtd = nand_to_mtd(chip);
 	int busw;
-	int i;
 	u8 *id_data = chip->id.data;
 	u8 maf_id, dev_id;
 
@@ -3878,11 +4518,11 @@ static int nand_detect(struct nand_chip *chip, struct nand_flash_dev *type)
 	chip->select_chip(mtd, 0);
 
 	/* Send the command for reading device ID */
-	chip->cmdfunc(mtd, NAND_CMD_READID, 0x00, -1);
+	nand_readid_op(chip, 0, id_data, 2);
 
 	/* Read manufacturer and device IDs */
-	maf_id = chip->read_byte(mtd);
-	dev_id = chip->read_byte(mtd);
+	maf_id = id_data[0];
+	dev_id = id_data[1];
 
 	/*
 	 * Try again to make sure, as some systems the bus-hold or other
@@ -3891,11 +4531,8 @@ static int nand_detect(struct nand_chip *chip, struct nand_flash_dev *type)
 	 * not match, ignore the device completely.
 	 */
 
-	chip->cmdfunc(mtd, NAND_CMD_READID, 0x00, -1);
-
 	/* Read entire ID string */
-	for (i = 0; i < ARRAY_SIZE(chip->id.data); i++)
-		id_data[i] = chip->read_byte(mtd);
+	nand_readid_op(chip, 0, id_data, sizeof(chip->id.data));
 
 	if (id_data[0] != maf_id || id_data[1] != dev_id) {
 		pr_info("second ID read did not match %02x,%02x against %02x,%02x\n",
@@ -4222,15 +4859,16 @@ int nand_scan_ident(struct mtd_info *mtd, int maxchips,
 
 	/* Check for a chip array */
 	for (i = 1; i < maxchips; i++) {
+		u8 id[2];
+
 		/* See comment in nand_get_flash_type for reset */
 		nand_reset(chip, i);
 
 		chip->select_chip(mtd, i);
 		/* Send the command for reading device ID */
-		chip->cmdfunc(mtd, NAND_CMD_READID, 0x00, -1);
+		nand_readid_op(chip, 0, id, sizeof(id));
 		/* Read manufacturer and device IDs */
-		if (nand_maf_id != chip->read_byte(mtd) ||
-		    nand_dev_id != chip->read_byte(mtd)) {
+		if (nand_maf_id != id[0] || nand_dev_id != id[1]) {
 			chip->select_chip(mtd, -1);
 			break;
 		}
diff --git a/drivers/mtd/nand/nand_hynix.c b/drivers/mtd/nand/nand_hynix.c
index 985751eda317..04e3ab7a476c 100644
--- a/drivers/mtd/nand/nand_hynix.c
+++ b/drivers/mtd/nand/nand_hynix.c
@@ -67,15 +67,11 @@ struct hynix_read_retry_otp {
 
 static bool hynix_nand_has_valid_jedecid(struct nand_chip *chip)
 {
-	struct mtd_info *mtd = nand_to_mtd(chip);
-	u8 jedecid[6] = { };
-	int i = 0;
+	u8 jedecid[5] = { };
 
-	chip->cmdfunc(mtd, NAND_CMD_READID, 0x40, -1);
-	for (i = 0; i < 5; i++)
-		jedecid[i] = chip->read_byte(mtd);
+	nand_readid_op(chip, 0x40, jedecid, sizeof(jedecid));
 
-	return !strcmp("JEDEC", jedecid);
+	return !strncmp("JEDEC", jedecid, sizeof(jedecid));
 }
 
 static int hynix_nand_setup_read_retry(struct mtd_info *mtd, int retry_mode)
@@ -175,7 +171,7 @@ static int hynix_read_rr_otp(struct nand_chip *chip,
 	struct mtd_info *mtd = nand_to_mtd(chip);
 	int i;
 
-	chip->cmdfunc(mtd, NAND_CMD_RESET, -1, -1);
+	nand_reset_op(chip);
 
 	chip->cmdfunc(mtd, NAND_HYNIX_CMD_SET_PARAMS, -1, -1);
 
@@ -195,11 +191,10 @@ static int hynix_read_rr_otp(struct nand_chip *chip,
 	chip->cmdfunc(mtd, 0x19, -1, -1);
 
 	/* Now read the page */
-	chip->cmdfunc(mtd, NAND_CMD_READ0, 0x0, info->page);
-	chip->read_buf(mtd, buf, info->size);
+	nand_read_page_op(chip, info->page, 0, buf, info->size);
 
 	/* Put everything back to normal */
-	chip->cmdfunc(mtd, NAND_CMD_RESET, -1, -1);
+	nand_reset_op(chip);
 	chip->cmdfunc(mtd, NAND_HYNIX_CMD_SET_PARAMS, 0x38, -1);
 	chip->write_byte(mtd, 0x0);
 	chip->cmdfunc(mtd, NAND_HYNIX_CMD_APPLY_PARAMS, -1, -1);
diff --git a/drivers/mtd/nand/nand_micron.c b/drivers/mtd/nand/nand_micron.c
index abf6a3c376e8..3934539a7468 100644
--- a/drivers/mtd/nand/nand_micron.c
+++ b/drivers/mtd/nand/nand_micron.c
@@ -137,8 +137,6 @@ micron_nand_read_page_on_die_ecc(struct mtd_info *mtd, struct nand_chip *chip,
 	else if (status & NAND_STATUS_WRITE_RECOMMENDED)
 		max_bitflips = chip->ecc.strength;
 
-	chip->cmdfunc(mtd, NAND_CMD_READ0, -1, -1);
-
 	nand_read_page_raw(mtd, chip, buf, oob_required, page);
 
 	micron_nand_on_die_ecc_setup(chip, false);
@@ -155,10 +153,7 @@ micron_nand_write_page_on_die_ecc(struct mtd_info *mtd, struct nand_chip *chip,
 
 	micron_nand_on_die_ecc_setup(chip, true);
 
-	chip->cmdfunc(mtd, NAND_CMD_SEQIN, 0x00, page);
-	nand_write_page_raw(mtd, chip, buf, oob_required, page);
-	chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1);
-	status = chip->waitfunc(mtd, chip);
+	status = nand_write_page_raw(mtd, chip, buf, oob_required, page);
 
 	micron_nand_on_die_ecc_setup(chip, false);
 
@@ -171,7 +166,6 @@ micron_nand_read_page_raw_on_die_ecc(struct mtd_info *mtd,
 				     uint8_t *buf, int oob_required,
 				     int page)
 {
-	chip->cmdfunc(mtd, NAND_CMD_READ0, 0x00, page);
 	nand_read_page_raw(mtd, chip, buf, oob_required, page);
 
 	return 0;
@@ -183,14 +177,7 @@ micron_nand_write_page_raw_on_die_ecc(struct mtd_info *mtd,
 				      const uint8_t *buf, int oob_required,
 				      int page)
 {
-	int status;
-
-	chip->cmdfunc(mtd, NAND_CMD_SEQIN, 0x00, page);
-	nand_write_page_raw(mtd, chip, buf, oob_required, page);
-	chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1);
-	status = chip->waitfunc(mtd, chip);
-
-	return status & NAND_STATUS_FAIL ? -EIO : 0;
+	return nand_write_page_raw(mtd, chip, buf, oob_required, page);
 }
 
 enum {
diff --git a/drivers/mtd/nand/omap2.c b/drivers/mtd/nand/omap2.c
index 54540c8fa1a2..41e124777579 100644
--- a/drivers/mtd/nand/omap2.c
+++ b/drivers/mtd/nand/omap2.c
@@ -1538,10 +1538,10 @@ static int omap_read_page_bch(struct mtd_info *mtd, struct nand_chip *chip,
 	chip->read_buf(mtd, buf, mtd->writesize);
 
 	/* Read oob bytes */
-	chip->cmdfunc(mtd, NAND_CMD_RNDOUT,
-		      mtd->writesize + BADBLOCK_MARKER_LENGTH, -1);
-	chip->read_buf(mtd, chip->oob_poi + BADBLOCK_MARKER_LENGTH,
-		       chip->ecc.total);
+	nand_change_read_column_op(chip,
+				   mtd->writesize + BADBLOCK_MARKER_LENGTH,
+				   chip->oob_poi + BADBLOCK_MARKER_LENGTH,
+				   chip->ecc.total, false);
 
 	/* Calculate ecc bytes */
 	chip->ecc.calculate(mtd, buf, ecc_calc);
diff --git a/drivers/mtd/nand/pxa3xx_nand.c b/drivers/mtd/nand/pxa3xx_nand.c
index 90b9a9ccbe60..28bcdf64c1fc 100644
--- a/drivers/mtd/nand/pxa3xx_nand.c
+++ b/drivers/mtd/nand/pxa3xx_nand.c
@@ -520,15 +520,13 @@ static int pxa3xx_nand_init_timings_compat(struct pxa3xx_nand_host *host,
 	struct nand_chip *chip = &host->chip;
 	struct pxa3xx_nand_info *info = host->info_data;
 	const struct pxa3xx_nand_flash *f = NULL;
-	struct mtd_info *mtd = nand_to_mtd(&host->chip);
 	int i, id, ntypes;
+	u8 idbuf[2];
 
 	ntypes = ARRAY_SIZE(builtin_flash_types);
 
-	chip->cmdfunc(mtd, NAND_CMD_READID, 0x00, -1);
-
-	id = chip->read_byte(mtd);
-	id |= chip->read_byte(mtd) << 0x8;
+	nand_readid_op(chip, 0, idbuf, sizeof(idbuf));
+	id = idbuf[0] | (idbuf[1] << 8);
 
 	for (i = 0; i < ntypes; i++) {
 		f = &builtin_flash_types[i];
diff --git a/drivers/mtd/nand/qcom_nandc.c b/drivers/mtd/nand/qcom_nandc.c
index 2656c1ac5646..e34313ecd903 100644
--- a/drivers/mtd/nand/qcom_nandc.c
+++ b/drivers/mtd/nand/qcom_nandc.c
@@ -1990,7 +1990,7 @@ static int qcom_nandc_write_oob(struct mtd_info *mtd, struct nand_chip *chip,
 	struct nand_ecc_ctrl *ecc = &chip->ecc;
 	u8 *oob = chip->oob_poi;
 	int data_size, oob_size;
-	int ret, status = 0;
+	int ret;
 
 	host->use_ecc = true;
 
@@ -2027,11 +2027,7 @@ static int qcom_nandc_write_oob(struct mtd_info *mtd, struct nand_chip *chip,
 		return -EIO;
 	}
 
-	chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1);
-
-	status = chip->waitfunc(mtd, chip);
-
-	return status & NAND_STATUS_FAIL ? -EIO : 0;
+	return nand_prog_page_end_op(chip);
 }
 
 static int qcom_nandc_block_bad(struct mtd_info *mtd, loff_t ofs)
@@ -2081,7 +2077,7 @@ static int qcom_nandc_block_markbad(struct mtd_info *mtd, loff_t ofs)
 	struct qcom_nand_host *host = to_qcom_nand_host(chip);
 	struct qcom_nand_controller *nandc = get_qcom_nand_controller(chip);
 	struct nand_ecc_ctrl *ecc = &chip->ecc;
-	int page, ret, status = 0;
+	int page, ret;
 
 	clear_read_regs(nandc);
 	clear_bam_transaction(nandc);
@@ -2114,11 +2110,7 @@ static int qcom_nandc_block_markbad(struct mtd_info *mtd, loff_t ofs)
 		return -EIO;
 	}
 
-	chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1);
-
-	status = chip->waitfunc(mtd, chip);
-
-	return status & NAND_STATUS_FAIL ? -EIO : 0;
+	return nand_prog_page_end_op(chip);
 }
 
 /*
diff --git a/drivers/mtd/nand/r852.c b/drivers/mtd/nand/r852.c
index fc9287af4614..595635b9e9de 100644
--- a/drivers/mtd/nand/r852.c
+++ b/drivers/mtd/nand/r852.c
@@ -364,7 +364,7 @@ static int r852_wait(struct mtd_info *mtd, struct nand_chip *chip)
 	struct r852_device *dev = nand_get_controller_data(chip);
 
 	unsigned long timeout;
-	int status;
+	u8 status;
 
 	timeout = jiffies + (chip->state == FL_ERASING ?
 		msecs_to_jiffies(400) : msecs_to_jiffies(20));
@@ -373,8 +373,7 @@ static int r852_wait(struct mtd_info *mtd, struct nand_chip *chip)
 		if (chip->dev_ready(mtd))
 			break;
 
-	chip->cmdfunc(mtd, NAND_CMD_STATUS, -1, -1);
-	status = (int)chip->read_byte(mtd);
+	nand_status_op(chip, &status);
 
 	/* Unfortunelly, no way to send detailed error status... */
 	if (dev->dma_error) {
@@ -522,9 +521,7 @@ static int r852_ecc_correct(struct mtd_info *mtd, uint8_t *dat,
 static int r852_read_oob(struct mtd_info *mtd, struct nand_chip *chip,
 			     int page)
 {
-	chip->cmdfunc(mtd, NAND_CMD_READOOB, 0, page);
-	chip->read_buf(mtd, chip->oob_poi, mtd->oobsize);
-	return 0;
+	return nand_read_oob_op(chip, page, 0, chip->oob_poi, mtd->oobsize);
 }
 
 /*
@@ -1046,7 +1043,7 @@ static int r852_resume(struct device *device)
 	if (dev->card_registred) {
 		r852_engine_enable(dev);
 		dev->chip->select_chip(mtd, 0);
-		dev->chip->cmdfunc(mtd, NAND_CMD_RESET, -1, -1);
+		nand_reset_op(dev->chip);
 		dev->chip->select_chip(mtd, -1);
 	}
 
diff --git a/drivers/mtd/nand/sunxi_nand.c b/drivers/mtd/nand/sunxi_nand.c
index 82244be3e766..230d037d05d2 100644
--- a/drivers/mtd/nand/sunxi_nand.c
+++ b/drivers/mtd/nand/sunxi_nand.c
@@ -958,12 +958,12 @@ static int sunxi_nfc_hw_ecc_read_chunk(struct mtd_info *mtd,
 	int ret;
 
 	if (*cur_off != data_off)
-		nand->cmdfunc(mtd, NAND_CMD_RNDOUT, data_off, -1);
+		nand_change_read_column_op(nand, data_off, NULL, 0, false);
 
 	sunxi_nfc_randomizer_read_buf(mtd, NULL, ecc->size, false, page);
 
 	if (data_off + ecc->size != oob_off)
-		nand->cmdfunc(mtd, NAND_CMD_RNDOUT, oob_off, -1);
+		nand_change_read_column_op(nand, oob_off, NULL, 0, false);
 
 	ret = sunxi_nfc_wait_cmd_fifo_empty(nfc);
 	if (ret)
@@ -991,16 +991,15 @@ static int sunxi_nfc_hw_ecc_read_chunk(struct mtd_info *mtd,
 		 * Re-read the data with the randomizer disabled to identify
 		 * bitflips in erased pages.
 		 */
-		if (nand->options & NAND_NEED_SCRAMBLING) {
-			nand->cmdfunc(mtd, NAND_CMD_RNDOUT, data_off, -1);
-			nand->read_buf(mtd, data, ecc->size);
-		} else {
+		if (nand->options & NAND_NEED_SCRAMBLING)
+			nand_change_read_column_op(nand, data_off, data,
+						   ecc->size, false);
+		else
 			memcpy_fromio(data, nfc->regs + NFC_RAM0_BASE,
 				      ecc->size);
-		}
 
-		nand->cmdfunc(mtd, NAND_CMD_RNDOUT, oob_off, -1);
-		nand->read_buf(mtd, oob, ecc->bytes + 4);
+		nand_change_read_column_op(nand, oob_off, oob, ecc->bytes + 4,
+					   false);
 
 		ret = nand_check_erased_ecc_chunk(data,	ecc->size,
 						  oob, ecc->bytes + 4,
@@ -1011,7 +1010,8 @@ static int sunxi_nfc_hw_ecc_read_chunk(struct mtd_info *mtd,
 		memcpy_fromio(data, nfc->regs + NFC_RAM0_BASE, ecc->size);
 
 		if (oob_required) {
-			nand->cmdfunc(mtd, NAND_CMD_RNDOUT, oob_off, -1);
+			nand_change_read_column_op(nand, oob_off, NULL, 0,
+						   false);
 			sunxi_nfc_randomizer_read_buf(mtd, oob, ecc->bytes + 4,
 						      true, page);
 
@@ -1038,8 +1038,8 @@ static void sunxi_nfc_hw_ecc_read_extra_oob(struct mtd_info *mtd,
 		return;
 
 	if (!cur_off || *cur_off != offset)
-		nand->cmdfunc(mtd, NAND_CMD_RNDOUT,
-			      offset + mtd->writesize, -1);
+		nand_change_read_column_op(nand, mtd->writesize, NULL, 0,
+					   false);
 
 	if (!randomize)
 		sunxi_nfc_read_buf(mtd, oob + offset, len);
@@ -1116,9 +1116,9 @@ static int sunxi_nfc_hw_ecc_read_chunks_dma(struct mtd_info *mtd, uint8_t *buf,
 
 		if (oob_required && !erased) {
 			/* TODO: use DMA to retrieve OOB */
-			nand->cmdfunc(mtd, NAND_CMD_RNDOUT,
-				      mtd->writesize + oob_off, -1);
-			nand->read_buf(mtd, oob, ecc->bytes + 4);
+			nand_change_read_column_op(nand,
+						   mtd->writesize + oob_off,
+						   oob, ecc->bytes + 4, false);
 
 			sunxi_nfc_hw_ecc_get_prot_oob_bytes(mtd, oob, i,
 							    !i, page);
@@ -1143,18 +1143,17 @@ static int sunxi_nfc_hw_ecc_read_chunks_dma(struct mtd_info *mtd, uint8_t *buf,
 			/*
 			 * Re-read the data with the randomizer disabled to
 			 * identify bitflips in erased pages.
+			 * TODO: use DMA to read page in raw mode
 			 */
-			if (randomized) {
-				/* TODO: use DMA to read page in raw mode */
-				nand->cmdfunc(mtd, NAND_CMD_RNDOUT,
-					      data_off, -1);
-				nand->read_buf(mtd, data, ecc->size);
-			}
+			if (randomized)
+				nand_change_read_column_op(nand, data_off,
+							   data, ecc->size
+							   false);
 
 			/* TODO: use DMA to retrieve OOB */
-			nand->cmdfunc(mtd, NAND_CMD_RNDOUT,
-				      mtd->writesize + oob_off, -1);
-			nand->read_buf(mtd, oob, ecc->bytes + 4);
+			nand_change_read_column_op(nand,
+						   mtd->writesize + oob_off,
+						   oob, ecc->bytes + 4, false);
 
 			ret = nand_check_erased_ecc_chunk(data,	ecc->size,
 							  oob, ecc->bytes + 4,
@@ -1187,12 +1186,12 @@ static int sunxi_nfc_hw_ecc_write_chunk(struct mtd_info *mtd,
 	int ret;
 
 	if (data_off != *cur_off)
-		nand->cmdfunc(mtd, NAND_CMD_RNDIN, data_off, -1);
+		nand_change_write_column_op(nand, data_off, NULL, 0, false);
 
 	sunxi_nfc_randomizer_write_buf(mtd, data, ecc->size, false, page);
 
 	if (data_off + ecc->size != oob_off)
-		nand->cmdfunc(mtd, NAND_CMD_RNDIN, oob_off, -1);
+		nand_change_write_column_op(nand, oob_off, NULL, 0, false);
 
 	ret = sunxi_nfc_wait_cmd_fifo_empty(nfc);
 	if (ret)
@@ -1228,8 +1227,8 @@ static void sunxi_nfc_hw_ecc_write_extra_oob(struct mtd_info *mtd,
 		return;
 
 	if (!cur_off || *cur_off != offset)
-		nand->cmdfunc(mtd, NAND_CMD_RNDIN,
-			      offset + mtd->writesize, -1);
+		nand_change_write_column_op(nand, offset + mtd->writesize,
+					    NULL, 0, false);
 
 	sunxi_nfc_randomizer_write_buf(mtd, oob + offset, len, false, page);
 
@@ -1540,7 +1539,7 @@ static int sunxi_nfc_hw_common_ecc_read_oob(struct mtd_info *mtd,
 					    struct nand_chip *chip,
 					    int page)
 {
-	chip->cmdfunc(mtd, NAND_CMD_READ0, 0, page);
+	nand_read_page_op(chip, page, 0, NULL, 0);
 
 	chip->pagebuf = -1;
 
@@ -1551,9 +1550,9 @@ static int sunxi_nfc_hw_common_ecc_write_oob(struct mtd_info *mtd,
 					     struct nand_chip *chip,
 					     int page)
 {
-	int ret, status;
+	int ret;
 
-	chip->cmdfunc(mtd, NAND_CMD_SEQIN, 0, page);
+	nand_prog_page_begin_op(chip, page, 0, NULL, 0);
 
 	chip->pagebuf = -1;
 
@@ -1563,11 +1562,7 @@ static int sunxi_nfc_hw_common_ecc_write_oob(struct mtd_info *mtd,
 		return ret;
 
 	/* Send command to program the OOB data */
-	chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1);
-
-	status = chip->waitfunc(mtd, chip);
-
-	return status & NAND_STATUS_FAIL ? -EIO : 0;
+	return nand_prog_page_end_op(chip);
 }
 
 static const s32 tWB_lut[] = {6, 12, 16, 20};
diff --git a/drivers/mtd/nand/tango_nand.c b/drivers/mtd/nand/tango_nand.c
index 766906f03943..97a300b46b1d 100644
--- a/drivers/mtd/nand/tango_nand.c
+++ b/drivers/mtd/nand/tango_nand.c
@@ -329,7 +329,7 @@ static void aux_read(struct nand_chip *chip, u8 **buf, int len, int *pos)
 
 	if (!*buf) {
 		/* skip over "len" bytes */
-		chip->cmdfunc(mtd, NAND_CMD_RNDOUT, *pos, -1);
+		nand_change_read_column_op(chip, *pos, NULL, 0, false);
 	} else {
 		tango_read_buf(mtd, *buf, len);
 		*buf += len;
@@ -344,7 +344,7 @@ static void aux_write(struct nand_chip *chip, const u8 **buf, int len, int *pos)
 
 	if (!*buf) {
 		/* skip over "len" bytes */
-		chip->cmdfunc(mtd, NAND_CMD_RNDIN, *pos, -1);
+		nand_change_write_column_op(chip, *pos, NULL, 0, false);
 	} else {
 		tango_write_buf(mtd, *buf, len);
 		*buf += len;
@@ -427,7 +427,7 @@ static void raw_write(struct nand_chip *chip, const u8 *buf, const u8 *oob)
 static int tango_read_page_raw(struct mtd_info *mtd, struct nand_chip *chip,
 			       u8 *buf, int oob_required, int page)
 {
-	chip->cmdfunc(mtd, NAND_CMD_READ0, 0, page);
+	nand_read_page_op(chip, page, 0, NULL, 0);
 	raw_read(chip, buf, chip->oob_poi);
 	return 0;
 }
@@ -435,23 +435,15 @@ static int tango_read_page_raw(struct mtd_info *mtd, struct nand_chip *chip,
 static int tango_write_page_raw(struct mtd_info *mtd, struct nand_chip *chip,
 				const u8 *buf, int oob_required, int page)
 {
-	int status;
-
-	chip->cmdfunc(mtd, NAND_CMD_SEQIN, 0, page);
+	nand_prog_page_begin_op(chip, page, 0, NULL, 0);
 	raw_write(chip, buf, chip->oob_poi);
-	chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1);
-
-	status = chip->waitfunc(mtd, chip);
-	if (status & NAND_STATUS_FAIL)
-		return -EIO;
-
-	return 0;
+	return nand_prog_page_end_op(chip);
 }
 
 static int tango_read_oob(struct mtd_info *mtd, struct nand_chip *chip,
 			  int page)
 {
-	chip->cmdfunc(mtd, NAND_CMD_READ0, 0, page);
+	nand_read_page_op(chip, page, 0, NULL, 0);
 	raw_read(chip, NULL, chip->oob_poi);
 	return 0;
 }
@@ -459,11 +451,9 @@ static int tango_read_oob(struct mtd_info *mtd, struct nand_chip *chip,
 static int tango_write_oob(struct mtd_info *mtd, struct nand_chip *chip,
 			   int page)
 {
-	chip->cmdfunc(mtd, NAND_CMD_SEQIN, 0, page);
+	nand_prog_page_begin_op(chip, page, 0, NULL, 0);
 	raw_write(chip, NULL, chip->oob_poi);
-	chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1);
-	chip->waitfunc(mtd, chip);
-	return 0;
+	return nand_prog_page_end_op(chip);
 }
 
 static int oob_ecc(struct mtd_info *mtd, int idx, struct mtd_oob_region *res)
diff --git a/drivers/mtd/nand/tmio_nand.c b/drivers/mtd/nand/tmio_nand.c
index 84dbf32332e1..ae3583b092fb 100644
--- a/drivers/mtd/nand/tmio_nand.c
+++ b/drivers/mtd/nand/tmio_nand.c
@@ -192,6 +192,7 @@ tmio_nand_wait(struct mtd_info *mtd, struct nand_chip *nand_chip)
 {
 	struct tmio_nand *tmio = mtd_to_tmio(mtd);
 	long timeout;
+	u8 status;
 
 	/* enable RDYREQ interrupt */
 	tmio_iowrite8(0x0f, tmio->fcr + FCR_ISR);
@@ -212,8 +213,8 @@ tmio_nand_wait(struct mtd_info *mtd, struct nand_chip *nand_chip)
 		dev_warn(&tmio->dev->dev, "timeout waiting for interrupt\n");
 	}
 
-	nand_chip->cmdfunc(mtd, NAND_CMD_STATUS, -1, -1);
-	return nand_chip->read_byte(mtd);
+	nand_status_op(chip, &status)
+	return status;
 }
 
 /*
diff --git a/include/linux/mtd/rawnand.h b/include/linux/mtd/rawnand.h
index 749bb08c4772..8b8acde72465 100644
--- a/include/linux/mtd/rawnand.h
+++ b/include/linux/mtd/rawnand.h
@@ -1316,6 +1316,31 @@ int nand_write_page_raw(struct mtd_info *mtd, struct nand_chip *chip,
 /* Reset and initialize a NAND device */
 int nand_reset(struct nand_chip *chip, int chipnr);
 
+/* NAND operation helpers */
+int nand_reset_op(struct nand_chip *chip);
+int nand_readid_op(struct nand_chip *chip, u8 addr, void *buf,
+		   unsigned int len);
+int nand_status_op(struct nand_chip *chip, u8 *status);
+int nand_erase_op(struct nand_chip *chip, unsigned int eraseblock);
+int nand_read_page_op(struct nand_chip *chip, unsigned int page,
+		      unsigned int column, void *buf, unsigned int len);
+int nand_change_read_column_op(struct nand_chip *chip, unsigned int column,
+			       void *buf, unsigned int len, bool force_8bit);
+int nand_read_oob_op(struct nand_chip *chip, unsigned int page,
+		     unsigned int column, void *buf, unsigned int len);
+int nand_prog_page_begin_op(struct nand_chip *chip, unsigned int page,
+			    unsigned int column, const void *buf,
+			    unsigned int len);
+int nand_prog_page_end_op(struct nand_chip *chip);
+int nand_prog_page_op(struct nand_chip *chip, unsigned int page,
+		      unsigned int column, const void *buf, unsigned int len);
+int nand_change_write_column_op(struct nand_chip *chip, unsigned int column,
+				const void *buf, unsigned int len, bool force_8bit);
+int nand_read_data_op(struct nand_chip *chip, void *buf, unsigned int len,
+		      bool force_8bits);
+int nand_write_data_op(struct nand_chip *chip, const void *buf,
+		       unsigned int len, bool force_8bits);
+
 /* Free resources held by the NAND device */
 void nand_cleanup(struct nand_chip *chip);
 
-- 
2.11.0

^ permalink raw reply related	[flat|nested] 75+ messages in thread

* [RFC 01/12] mtd: nand: provide several helpers to do common NAND operations
@ 2017-10-18 14:36   ` Miquel Raynal
  0 siblings, 0 replies; 75+ messages in thread
From: Miquel Raynal @ 2017-10-18 14:36 UTC (permalink / raw)
  To: Andrew Lunn, bcm-kernel-feedback-list, Boris Brezillon,
	Brian Norris, Catalin Marinas, Chen-Yu Tsai, Cyrille Pitchen,
	Daniel Mack, David Woodhouse, devel, devicetree, Ezequiel Garcia,
	Greg Kroah-Hartman, Gregory Clement, Han Xu, Haojian Zhuang,
	Jason Cooper, Josh Wu, Kamal Dasu, linux-arm-kernel,
	linux-kernel, linux-mediatek, linux-mtd
  Cc: Hanna Hawa, Antoine Tenart, Nadav Haklai, Neta Zur Hershkovits,
	Igor Grinberg, Miquel Raynal, Ofer Heifetz

From: Boris Brezillon <boris.brezillon@free-electrons.com>

This is part of the process of removing direct calls to ->cmdfunc()
outside of the core in order to introduce a better interface to execute
NAND operations.

Here we provide several helpers and make use of them to remove all
direct calls to ->cmdfunc(). This way, we can easily modify those
helpers to make use of the new ->exec_op() interface when available.

Signed-off-by: Boris Brezillon <boris.brezillon@free-electrons.com>
[miquel.raynal@free-electrons.com: rebased and fixed some conflicts]
Signed-off-by: Miquel Raynal <miquel.raynal@free-electrons.com>
---
 drivers/mtd/nand/atmel/nand-controller.c |   2 +-
 drivers/mtd/nand/brcmnand/brcmnand.c     |   9 +-
 drivers/mtd/nand/cafe_nand.c             |  14 +-
 drivers/mtd/nand/denali.c                |   2 +-
 drivers/mtd/nand/diskonchip.c            |   4 +-
 drivers/mtd/nand/docg4.c                 |   7 +-
 drivers/mtd/nand/fsmc_nand.c             |   5 +-
 drivers/mtd/nand/gpmi-nand/gpmi-nand.c   |  56 +-
 drivers/mtd/nand/hisi504_nand.c          |   3 +-
 drivers/mtd/nand/jz4740_nand.c           |  16 +-
 drivers/mtd/nand/lpc32xx_mlc.c           |   2 +-
 drivers/mtd/nand/lpc32xx_slc.c           |  22 +-
 drivers/mtd/nand/mtk_nand.c              |  11 +-
 drivers/mtd/nand/nand_base.c             | 956 ++++++++++++++++++++++++++-----
 drivers/mtd/nand/nand_hynix.c            |  17 +-
 drivers/mtd/nand/nand_micron.c           |  17 +-
 drivers/mtd/nand/omap2.c                 |   8 +-
 drivers/mtd/nand/pxa3xx_nand.c           |   8 +-
 drivers/mtd/nand/qcom_nandc.c            |  16 +-
 drivers/mtd/nand/r852.c                  |  11 +-
 drivers/mtd/nand/sunxi_nand.c            |  67 +--
 drivers/mtd/nand/tango_nand.c            |  26 +-
 drivers/mtd/nand/tmio_nand.c             |   5 +-
 include/linux/mtd/rawnand.h              |  25 +
 24 files changed, 943 insertions(+), 366 deletions(-)

diff --git a/drivers/mtd/nand/atmel/nand-controller.c b/drivers/mtd/nand/atmel/nand-controller.c
index 29182160bb5f..0b2c82727b24 100644
--- a/drivers/mtd/nand/atmel/nand-controller.c
+++ b/drivers/mtd/nand/atmel/nand-controller.c
@@ -1000,7 +1000,7 @@ static int atmel_hsmc_nand_pmecc_read_pg(struct nand_chip *chip, u8 *buf,
 	 * to the non-optimized one.
 	 */
 	if (nand->activecs->rb.type != ATMEL_NAND_NATIVE_RB) {
-		chip->cmdfunc(mtd, NAND_CMD_READ0, 0x00, page);
+		nand_read_page_op(chip, page, 0, NULL, 0);
 
 		return atmel_nand_pmecc_read_pg(chip, buf, oob_required, page,
 						raw);
diff --git a/drivers/mtd/nand/brcmnand/brcmnand.c b/drivers/mtd/nand/brcmnand/brcmnand.c
index e0eb51d8c012..3f441096a14c 100644
--- a/drivers/mtd/nand/brcmnand/brcmnand.c
+++ b/drivers/mtd/nand/brcmnand/brcmnand.c
@@ -1071,7 +1071,7 @@ static void brcmnand_wp(struct mtd_info *mtd, int wp)
 			return;
 
 		brcmnand_set_wp(ctrl, wp);
-		chip->cmdfunc(mtd, NAND_CMD_STATUS, -1, -1);
+		nand_status_op(chip, NULL);
 		/* NAND_STATUS_WP 0x00 = protected, 0x80 = not protected */
 		ret = bcmnand_ctrl_poll_status(ctrl,
 					       NAND_CTRL_RDY |
@@ -1453,7 +1453,7 @@ static uint8_t brcmnand_read_byte(struct mtd_info *mtd)
 
 		/* At FC_BYTES boundary, switch to next column */
 		if (host->last_byte > 0 && offs == 0)
-			chip->cmdfunc(mtd, NAND_CMD_RNDOUT, addr, -1);
+			nand_change_read_column_op(chip, addr, NULL, 0, false);
 
 		ret = ctrl->flash_cache[offs];
 		break;
@@ -1689,7 +1689,7 @@ static int brcmstb_nand_verify_erased_page(struct mtd_info *mtd,
 	sas = mtd->oobsize / chip->ecc.steps;
 
 	/* read without ecc for verification */
-	chip->cmdfunc(mtd, NAND_CMD_READ0, 0x00, page);
+	nand_read_page_op(chip, page, 0, NULL, 0);
 	ret = chip->ecc.read_page_raw(mtd, chip, buf, true, page);
 	if (ret)
 		return ret;
@@ -2369,12 +2369,11 @@ static int brcmnand_resume(struct device *dev)
 
 	list_for_each_entry(host, &ctrl->host_list, node) {
 		struct nand_chip *chip = &host->chip;
-		struct mtd_info *mtd = nand_to_mtd(chip);
 
 		brcmnand_save_restore_cs_config(host, 1);
 
 		/* Reset the chip, required by some chips after power-up */
-		chip->cmdfunc(mtd, NAND_CMD_RESET, -1, -1);
+		nand_reset_op(chip);
 	}
 
 	return 0;
diff --git a/drivers/mtd/nand/cafe_nand.c b/drivers/mtd/nand/cafe_nand.c
index bc558c438a57..95c2cfa68b66 100644
--- a/drivers/mtd/nand/cafe_nand.c
+++ b/drivers/mtd/nand/cafe_nand.c
@@ -353,23 +353,15 @@ static void cafe_nand_bug(struct mtd_info *mtd)
 static int cafe_nand_write_oob(struct mtd_info *mtd,
 			       struct nand_chip *chip, int page)
 {
-	int status = 0;
-
-	chip->cmdfunc(mtd, NAND_CMD_SEQIN, mtd->writesize, page);
-	chip->write_buf(mtd, chip->oob_poi, mtd->oobsize);
-	chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1);
-	status = chip->waitfunc(mtd, chip);
-
-	return status & NAND_STATUS_FAIL ? -EIO : 0;
+	return nand_prog_page_op(chip, page, mtd->writesize, chip->oob_poi,
+				 mtd->oobsize);
 }
 
 /* Don't use -- use nand_read_oob_std for now */
 static int cafe_nand_read_oob(struct mtd_info *mtd, struct nand_chip *chip,
 			      int page)
 {
-	chip->cmdfunc(mtd, NAND_CMD_READOOB, 0, page);
-	chip->read_buf(mtd, chip->oob_poi, mtd->oobsize);
-	return 0;
+	return nand_read_oob_op(chip, page, 0, chip->oob_poi, mtd->oobsize);
 }
 /**
  * cafe_nand_read_page_syndrome - [REPLACEABLE] hardware ecc syndrome based page read
diff --git a/drivers/mtd/nand/denali.c b/drivers/mtd/nand/denali.c
index 5124f8ae8c04..f90f93a8fb08 100644
--- a/drivers/mtd/nand/denali.c
+++ b/drivers/mtd/nand/denali.c
@@ -951,7 +951,7 @@ static int denali_erase(struct mtd_info *mtd, int page)
 	irq_status = denali_wait_for_irq(denali,
 					 INTR__ERASE_COMP | INTR__ERASE_FAIL);
 
-	return irq_status & INTR__ERASE_COMP ? 0 : NAND_STATUS_FAIL;
+	return irq_status & INTR__ERASE_FAIL ? -EIO : 0;
 }
 
 static int denali_setup_data_interface(struct mtd_info *mtd, int chipnr,
diff --git a/drivers/mtd/nand/diskonchip.c b/drivers/mtd/nand/diskonchip.c
index 72671dc52e2e..6bc93ea66f50 100644
--- a/drivers/mtd/nand/diskonchip.c
+++ b/drivers/mtd/nand/diskonchip.c
@@ -448,7 +448,7 @@ static int doc200x_wait(struct mtd_info *mtd, struct nand_chip *this)
 	int status;
 
 	DoC_WaitReady(doc);
-	this->cmdfunc(mtd, NAND_CMD_STATUS, -1, -1);
+	nand_status_op(this, NULL);
 	DoC_WaitReady(doc);
 	status = (int)this->read_byte(mtd);
 
@@ -595,7 +595,7 @@ static void doc2001plus_select_chip(struct mtd_info *mtd, int chip)
 
 	/* Assert ChipEnable and deassert WriteProtect */
 	WriteDOC((DOC_FLASH_CE), docptr, Mplus_FlashSelect);
-	this->cmdfunc(mtd, NAND_CMD_RESET, -1, -1);
+	nand_reset_op(this);
 
 	doc->curchip = chip;
 	doc->curfloor = floor;
diff --git a/drivers/mtd/nand/docg4.c b/drivers/mtd/nand/docg4.c
index 2436cbc71662..45c01b4b34c7 100644
--- a/drivers/mtd/nand/docg4.c
+++ b/drivers/mtd/nand/docg4.c
@@ -900,6 +900,7 @@ static int docg4_erase_block(struct mtd_info *mtd, int page)
 	struct docg4_priv *doc = nand_get_controller_data(nand);
 	void __iomem *docptr = doc->virtadr;
 	uint16_t g4_page;
+	int status;
 
 	dev_dbg(doc->dev, "%s: page %04x\n", __func__, page);
 
@@ -939,7 +940,11 @@ static int docg4_erase_block(struct mtd_info *mtd, int page)
 	poll_status(doc);
 	write_nop(docptr);
 
-	return nand->waitfunc(mtd, nand);
+	status = nand->waitfunc(mtd, nand);
+	if (status < 0)
+		return status;
+
+	return status & NAND_STATUS_FAIL ? -EIO : 0;
 }
 
 static int write_page(struct mtd_info *mtd, struct nand_chip *nand,
diff --git a/drivers/mtd/nand/fsmc_nand.c b/drivers/mtd/nand/fsmc_nand.c
index eac15d9bf49e..b44e5c6545e0 100644
--- a/drivers/mtd/nand/fsmc_nand.c
+++ b/drivers/mtd/nand/fsmc_nand.c
@@ -697,7 +697,7 @@ static int fsmc_read_page_hwecc(struct mtd_info *mtd, struct nand_chip *chip,
 	unsigned int max_bitflips = 0;
 
 	for (i = 0, s = 0; s < eccsteps; s++, i += eccbytes, p += eccsize) {
-		chip->cmdfunc(mtd, NAND_CMD_READ0, s * eccsize, page);
+		nand_read_page_op(chip, page, s * eccsize, NULL, 0);
 		chip->ecc.hwctl(mtd, NAND_ECC_READ);
 		chip->read_buf(mtd, p, eccsize);
 
@@ -720,8 +720,7 @@ static int fsmc_read_page_hwecc(struct mtd_info *mtd, struct nand_chip *chip,
 			if (chip->options & NAND_BUSWIDTH_16)
 				len = roundup(len, 2);
 
-			chip->cmdfunc(mtd, NAND_CMD_READOOB, off, page);
-			chip->read_buf(mtd, oob + j, len);
+			nand_read_oob_op(chip, page, off, oob + j, len);
 			j += len;
 		}
 
diff --git a/drivers/mtd/nand/gpmi-nand/gpmi-nand.c b/drivers/mtd/nand/gpmi-nand/gpmi-nand.c
index 50f8d4a1b983..c8ceaecd8065 100644
--- a/drivers/mtd/nand/gpmi-nand/gpmi-nand.c
+++ b/drivers/mtd/nand/gpmi-nand/gpmi-nand.c
@@ -1097,8 +1097,8 @@ static int gpmi_ecc_read_page(struct mtd_info *mtd, struct nand_chip *chip,
 			eccbytes = DIV_ROUND_UP(offset + eccbits, 8);
 			offset /= 8;
 			eccbytes -= offset;
-			chip->cmdfunc(mtd, NAND_CMD_RNDOUT, offset, -1);
-			chip->read_buf(mtd, eccbuf, eccbytes);
+			nand_change_read_column_op(chip, offset, eccbuf,
+						   eccbytes, false);
 
 			/*
 			 * ECC data are not byte aligned and we may have
@@ -1411,7 +1411,7 @@ static int gpmi_ecc_read_oob(struct mtd_info *mtd, struct nand_chip *chip,
 	memset(chip->oob_poi, ~0, mtd->oobsize);
 
 	/* Read out the conventional OOB. */
-	chip->cmdfunc(mtd, NAND_CMD_READ0, mtd->writesize, page);
+	nand_read_page_op(chip, page, mtd->writesize, NULL, 0);
 	chip->read_buf(mtd, chip->oob_poi, mtd->oobsize);
 
 	/*
@@ -1421,7 +1421,7 @@ static int gpmi_ecc_read_oob(struct mtd_info *mtd, struct nand_chip *chip,
 	 */
 	if (GPMI_IS_MX23(this)) {
 		/* Read the block mark into the first byte of the OOB buffer. */
-		chip->cmdfunc(mtd, NAND_CMD_READ0, 0, page);
+		nand_read_page_op(chip, page, 0, NULL, 0);
 		chip->oob_poi[0] = chip->read_byte(mtd);
 	}
 
@@ -1432,7 +1432,6 @@ static int
 gpmi_ecc_write_oob(struct mtd_info *mtd, struct nand_chip *chip, int page)
 {
 	struct mtd_oob_region of = { };
-	int status = 0;
 
 	/* Do we have available oob area? */
 	mtd_ooblayout_free(mtd, 0, &of);
@@ -1442,12 +1441,8 @@ gpmi_ecc_write_oob(struct mtd_info *mtd, struct nand_chip *chip, int page)
 	if (!nand_is_slc(chip))
 		return -EPERM;
 
-	chip->cmdfunc(mtd, NAND_CMD_SEQIN, mtd->writesize + of.offset, page);
-	chip->write_buf(mtd, chip->oob_poi + of.offset, of.length);
-	chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1);
-
-	status = chip->waitfunc(mtd, chip);
-	return status & NAND_STATUS_FAIL ? -EIO : 0;
+	return nand_prog_page_op(chip, page, mtd->writesize + of.offset,
+				 chip->oob_poi + of.offset, of.length);
 }
 
 /*
@@ -1630,7 +1625,7 @@ static int gpmi_ecc_write_page_raw(struct mtd_info *mtd,
 static int gpmi_ecc_read_oob_raw(struct mtd_info *mtd, struct nand_chip *chip,
 				 int page)
 {
-	chip->cmdfunc(mtd, NAND_CMD_READ0, 0, page);
+	nand_read_page_op(chip, page, 0, NULL, 0);
 
 	return gpmi_ecc_read_page_raw(mtd, chip, NULL, 1, page);
 }
@@ -1638,7 +1633,7 @@ static int gpmi_ecc_read_oob_raw(struct mtd_info *mtd, struct nand_chip *chip,
 static int gpmi_ecc_write_oob_raw(struct mtd_info *mtd, struct nand_chip *chip,
 				 int page)
 {
-	chip->cmdfunc(mtd, NAND_CMD_SEQIN, 0, page);
+	nand_prog_page_begin_op(chip, page, 0, NULL, 0);
 
 	return gpmi_ecc_write_page_raw(mtd, chip, NULL, 1, page);
 }
@@ -1649,7 +1644,7 @@ static int gpmi_block_markbad(struct mtd_info *mtd, loff_t ofs)
 	struct gpmi_nand_data *this = nand_get_controller_data(chip);
 	int ret = 0;
 	uint8_t *block_mark;
-	int column, page, status, chipnr;
+	int column, page, chipnr;
 
 	chipnr = (int)(ofs >> chip->chip_shift);
 	chip->select_chip(mtd, chipnr);
@@ -1663,13 +1658,7 @@ static int gpmi_block_markbad(struct mtd_info *mtd, loff_t ofs)
 	/* Shift to get page */
 	page = (int)(ofs >> chip->page_shift);
 
-	chip->cmdfunc(mtd, NAND_CMD_SEQIN, column, page);
-	chip->write_buf(mtd, block_mark, 1);
-	chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1);
-
-	status = chip->waitfunc(mtd, chip);
-	if (status & NAND_STATUS_FAIL)
-		ret = -EIO;
+	ret = nand_prog_page_op(chip, page, column, block_mark, 1);
 
 	chip->select_chip(mtd, -1);
 
@@ -1737,7 +1726,7 @@ static int mx23_check_transcription_stamp(struct gpmi_nand_data *this)
 		 * Read the NCB fingerprint. The fingerprint is four bytes long
 		 * and starts in the 12th byte of the page.
 		 */
-		chip->cmdfunc(mtd, NAND_CMD_READ0, 12, page);
+		nand_read_page_op(chip, page, 12, NULL, 0);
 		chip->read_buf(mtd, buffer, strlen(fingerprint));
 
 		/* Look for the fingerprint. */
@@ -1797,17 +1786,10 @@ static int mx23_write_transcription_stamp(struct gpmi_nand_data *this)
 	dev_dbg(dev, "Erasing the search area...\n");
 
 	for (block = 0; block < search_area_size_in_blocks; block++) {
-		/* Compute the page address. */
-		page = block * block_size_in_pages;
-
 		/* Erase this block. */
 		dev_dbg(dev, "\tErasing block 0x%x\n", block);
-		chip->cmdfunc(mtd, NAND_CMD_ERASE1, -1, page);
-		chip->cmdfunc(mtd, NAND_CMD_ERASE2, -1, -1);
-
-		/* Wait for the erase to finish. */
-		status = chip->waitfunc(mtd, chip);
-		if (status & NAND_STATUS_FAIL)
+		status = nand_erase_op(chip, block);
+		if (status)
 			dev_err(dev, "[%s] Erase failed.\n", __func__);
 	}
 
@@ -1823,13 +1805,11 @@ static int mx23_write_transcription_stamp(struct gpmi_nand_data *this)
 
 		/* Write the first page of the current stride. */
 		dev_dbg(dev, "Writing an NCB fingerprint in page 0x%x\n", page);
-		chip->cmdfunc(mtd, NAND_CMD_SEQIN, 0x00, page);
-		chip->ecc.write_page_raw(mtd, chip, buffer, 0, page);
-		chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1);
 
-		/* Wait for the write to finish. */
-		status = chip->waitfunc(mtd, chip);
-		if (status & NAND_STATUS_FAIL)
+		nand_prog_page_begin_op(chip, page, 0, NULL, 0);
+		chip->ecc.write_page_raw(mtd, chip, buffer, 0, page);
+		status = nand_prog_page_end_op(chip);
+		if (status)
 			dev_err(dev, "[%s] Write failed.\n", __func__);
 	}
 
@@ -1884,7 +1864,7 @@ static int mx23_boot_init(struct gpmi_nand_data  *this)
 
 		/* Send the command to read the conventional block mark. */
 		chip->select_chip(mtd, chipnr);
-		chip->cmdfunc(mtd, NAND_CMD_READ0, mtd->writesize, page);
+		nand_read_page_op(chip, page, mtd->writesize, NULL, 0);
 		block_mark = chip->read_byte(mtd);
 		chip->select_chip(mtd, -1);
 
diff --git a/drivers/mtd/nand/hisi504_nand.c b/drivers/mtd/nand/hisi504_nand.c
index 0897261c3e17..184d765c8bbe 100644
--- a/drivers/mtd/nand/hisi504_nand.c
+++ b/drivers/mtd/nand/hisi504_nand.c
@@ -574,8 +574,7 @@ static int hisi_nand_read_oob(struct mtd_info *mtd, struct nand_chip *chip,
 {
 	struct hinfc_host *host = nand_get_controller_data(chip);
 
-	chip->cmdfunc(mtd, NAND_CMD_READOOB, 0, page);
-	chip->read_buf(mtd, chip->oob_poi, mtd->oobsize);
+	nand_read_oob_op(chip, page, 0, chip->oob_poi, mtd->oobsize);
 
 	if (host->irq_status & HINFC504_INTS_UE) {
 		host->irq_status = 0;
diff --git a/drivers/mtd/nand/jz4740_nand.c b/drivers/mtd/nand/jz4740_nand.c
index ad827d4af3e9..613b00a9604b 100644
--- a/drivers/mtd/nand/jz4740_nand.c
+++ b/drivers/mtd/nand/jz4740_nand.c
@@ -313,6 +313,7 @@ static int jz_nand_detect_bank(struct platform_device *pdev,
 	uint32_t ctrl;
 	struct nand_chip *chip = &nand->chip;
 	struct mtd_info *mtd = nand_to_mtd(chip);
+	u8 id[2];
 
 	/* Request I/O resource. */
 	sprintf(res_name, "bank%d", bank);
@@ -335,17 +336,16 @@ static int jz_nand_detect_bank(struct platform_device *pdev,
 
 		/* Retrieve the IDs from the first chip. */
 		chip->select_chip(mtd, 0);
-		chip->cmdfunc(mtd, NAND_CMD_RESET, -1, -1);
-		chip->cmdfunc(mtd, NAND_CMD_READID, 0x00, -1);
-		*nand_maf_id = chip->read_byte(mtd);
-		*nand_dev_id = chip->read_byte(mtd);
+		nand_reset_op(chip);
+		nand_readid_op(chip, 0, id, sizeof(id));
+		*nand_maf_id = id[0];
+		*nand_dev_id = id[1];
 	} else {
 		/* Detect additional chip. */
 		chip->select_chip(mtd, chipnr);
-		chip->cmdfunc(mtd, NAND_CMD_RESET, -1, -1);
-		chip->cmdfunc(mtd, NAND_CMD_READID, 0x00, -1);
-		if (*nand_maf_id != chip->read_byte(mtd)
-		 || *nand_dev_id != chip->read_byte(mtd)) {
+		nand_reset_op(chip);
+		nand_readid_op(chip, 0, id, sizeof(id));
+		if (*nand_maf_id != id[0] || *nand_dev_id != id[1]) {
 			ret = -ENODEV;
 			goto notfound_id;
 		}
diff --git a/drivers/mtd/nand/lpc32xx_mlc.c b/drivers/mtd/nand/lpc32xx_mlc.c
index c3bb358ef01e..0723ded232ff 100644
--- a/drivers/mtd/nand/lpc32xx_mlc.c
+++ b/drivers/mtd/nand/lpc32xx_mlc.c
@@ -461,7 +461,7 @@ static int lpc32xx_read_page(struct mtd_info *mtd, struct nand_chip *chip,
 	}
 
 	/* Writing Command and Address */
-	chip->cmdfunc(mtd, NAND_CMD_READ0, 0, page);
+	nand_read_page_op(chip, page, 0, NULL, 0);
 
 	/* For all sub-pages */
 	for (i = 0; i < host->mlcsubpages; i++) {
diff --git a/drivers/mtd/nand/lpc32xx_slc.c b/drivers/mtd/nand/lpc32xx_slc.c
index b61f28a1554d..2b96c281b1a2 100644
--- a/drivers/mtd/nand/lpc32xx_slc.c
+++ b/drivers/mtd/nand/lpc32xx_slc.c
@@ -399,10 +399,7 @@ static void lpc32xx_nand_write_buf(struct mtd_info *mtd, const uint8_t *buf, int
 static int lpc32xx_nand_read_oob_syndrome(struct mtd_info *mtd,
 					  struct nand_chip *chip, int page)
 {
-	chip->cmdfunc(mtd, NAND_CMD_READOOB, 0, page);
-	chip->read_buf(mtd, chip->oob_poi, mtd->oobsize);
-
-	return 0;
+	return nand_read_oob_op(chip, page, 0, chip->oob_poi, mtd->oobsize);
 }
 
 /*
@@ -411,17 +408,8 @@ static int lpc32xx_nand_read_oob_syndrome(struct mtd_info *mtd,
 static int lpc32xx_nand_write_oob_syndrome(struct mtd_info *mtd,
 	struct nand_chip *chip, int page)
 {
-	int status;
-
-	chip->cmdfunc(mtd, NAND_CMD_SEQIN, mtd->writesize, page);
-	chip->write_buf(mtd, chip->oob_poi, mtd->oobsize);
-
-	/* Send command to program the OOB data */
-	chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1);
-
-	status = chip->waitfunc(mtd, chip);
-
-	return status & NAND_STATUS_FAIL ? -EIO : 0;
+	return nand_prog_page_op(chip, page, mtd->writesize, chip->oob_poi,
+				 mtd->oobsize);
 }
 
 /*
@@ -632,7 +620,7 @@ static int lpc32xx_nand_read_page_syndrome(struct mtd_info *mtd,
 	uint8_t *oobecc, tmpecc[LPC32XX_ECC_SAVE_SIZE];
 
 	/* Issue read command */
-	chip->cmdfunc(mtd, NAND_CMD_READ0, 0, page);
+	nand_read_page_op(chip, page, 0, NULL, 0);
 
 	/* Read data and oob, calculate ECC */
 	status = lpc32xx_xfer(mtd, buf, chip->ecc.steps, 1);
@@ -675,7 +663,7 @@ static int lpc32xx_nand_read_page_raw_syndrome(struct mtd_info *mtd,
 					       int page)
 {
 	/* Issue read command */
-	chip->cmdfunc(mtd, NAND_CMD_READ0, 0, page);
+	nand_read_page_op(chip, page, 0, NULL, 0);
 
 	/* Raw reads can just use the FIFO interface */
 	chip->read_buf(mtd, buf, chip->ecc.size * chip->ecc.steps);
diff --git a/drivers/mtd/nand/mtk_nand.c b/drivers/mtd/nand/mtk_nand.c
index d86a7d131cc0..1a3fcb65f478 100644
--- a/drivers/mtd/nand/mtk_nand.c
+++ b/drivers/mtd/nand/mtk_nand.c
@@ -834,16 +834,13 @@ static int mtk_nfc_write_oob_std(struct mtd_info *mtd, struct nand_chip *chip,
 {
 	int ret;
 
-	chip->cmdfunc(mtd, NAND_CMD_SEQIN, 0x00, page);
+	nand_prog_page_begin_op(chip, page, 0, NULL, 0);
 
 	ret = mtk_nfc_write_page_raw(mtd, chip, NULL, 1, page);
 	if (ret < 0)
 		return -EIO;
 
-	chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1);
-	ret = chip->waitfunc(mtd, chip);
-
-	return ret & NAND_STATUS_FAIL ? -EIO : 0;
+	return nand_prog_page_end_op(chip);
 }
 
 static int mtk_nfc_update_ecc_stats(struct mtd_info *mtd, u8 *buf, u32 sectors)
@@ -1016,7 +1013,7 @@ static int mtk_nfc_read_page_raw(struct mtd_info *mtd, struct nand_chip *chip,
 static int mtk_nfc_read_oob_std(struct mtd_info *mtd, struct nand_chip *chip,
 				int page)
 {
-	chip->cmdfunc(mtd, NAND_CMD_READ0, 0, page);
+	nand_read_page_op(chip, page, 0, NULL, 0);
 
 	return mtk_nfc_read_page_raw(mtd, chip, NULL, 1, page);
 }
@@ -1556,7 +1553,7 @@ static int mtk_nfc_resume(struct device *dev)
 		mtd = nand_to_mtd(nand);
 		for (i = 0; i < chip->nsels; i++) {
 			nand->select_chip(mtd, i);
-			nand->cmdfunc(mtd, NAND_CMD_RESET, -1, -1);
+			nand_reset_op(nand);
 		}
 	}
 
diff --git a/drivers/mtd/nand/nand_base.c b/drivers/mtd/nand/nand_base.c
index c63e4a88a653..f7dd7d5a84b3 100644
--- a/drivers/mtd/nand/nand_base.c
+++ b/drivers/mtd/nand/nand_base.c
@@ -561,14 +561,19 @@ static int nand_block_markbad_lowlevel(struct mtd_info *mtd, loff_t ofs)
 static int nand_check_wp(struct mtd_info *mtd)
 {
 	struct nand_chip *chip = mtd_to_nand(mtd);
+	u8 status;
+	int ret;
 
 	/* Broken xD cards report WP despite being writable */
 	if (chip->options & NAND_BROKEN_XD)
 		return 0;
 
 	/* Check the WP bit */
-	chip->cmdfunc(mtd, NAND_CMD_STATUS, -1, -1);
-	return (chip->read_byte(mtd) & NAND_STATUS_WP) ? 0 : 1;
+	ret = nand_status_op(chip, &status);
+	if (ret)
+		return ret;
+
+	return status & NAND_STATUS_WP ? 0 : 1;
 }
 
 /**
@@ -667,10 +672,17 @@ EXPORT_SYMBOL_GPL(nand_wait_ready);
 static void nand_wait_status_ready(struct mtd_info *mtd, unsigned long timeo)
 {
 	register struct nand_chip *chip = mtd_to_nand(mtd);
+	int ret;
 
 	timeo = jiffies + msecs_to_jiffies(timeo);
 	do {
-		if ((chip->read_byte(mtd) & NAND_STATUS_READY))
+		u8 status;
+
+		ret = nand_read_data_op(chip, &status, sizeof(status), true);
+		if (ret)
+			return;
+
+		if (status & NAND_STATUS_READY)
 			break;
 		touch_softlockup_watchdog();
 	} while (time_before(jiffies, timeo));
@@ -1014,7 +1026,15 @@ static void panic_nand_wait(struct mtd_info *mtd, struct nand_chip *chip,
 			if (chip->dev_ready(mtd))
 				break;
 		} else {
-			if (chip->read_byte(mtd) & NAND_STATUS_READY)
+			int ret;
+			u8 status;
+
+			ret = nand_read_data_op(chip, &status, sizeof(status),
+						true);
+			if (ret)
+				return;
+
+			if (status & NAND_STATUS_READY)
 				break;
 		}
 		mdelay(1);
@@ -1031,8 +1051,9 @@ static void panic_nand_wait(struct mtd_info *mtd, struct nand_chip *chip,
 static int nand_wait(struct mtd_info *mtd, struct nand_chip *chip)
 {
 
-	int status;
 	unsigned long timeo = 400;
+	u8 status;
+	int ret;
 
 	/*
 	 * Apply this short delay always to ensure that we do wait tWB in any
@@ -1040,7 +1061,9 @@ static int nand_wait(struct mtd_info *mtd, struct nand_chip *chip)
 	 */
 	ndelay(100);
 
-	chip->cmdfunc(mtd, NAND_CMD_STATUS, -1, -1);
+	ret = nand_status_op(chip, NULL);
+	if (ret)
+		return ret;
 
 	if (in_interrupt() || oops_in_progress)
 		panic_nand_wait(mtd, chip, timeo);
@@ -1051,14 +1074,22 @@ static int nand_wait(struct mtd_info *mtd, struct nand_chip *chip)
 				if (chip->dev_ready(mtd))
 					break;
 			} else {
-				if (chip->read_byte(mtd) & NAND_STATUS_READY)
+				ret = nand_read_data_op(chip, &status,
+							sizeof(status), true);
+				if (ret)
+					return ret;
+
+				if (status & NAND_STATUS_READY)
 					break;
 			}
 			cond_resched();
 		} while (time_before(jiffies, timeo));
 	}
 
-	status = (int)chip->read_byte(mtd);
+	ret = nand_read_data_op(chip, &status, sizeof(status), true);
+	if (ret)
+		return ret;
+
 	/* This can happen if in case of timeout or buggy dev_ready */
 	WARN_ON(!(status & NAND_STATUS_READY));
 	return status;
@@ -1213,6 +1244,492 @@ static void nand_release_data_interface(struct nand_chip *chip)
 }
 
 /**
+ * nand_read_page_op - Do a READ PAGE operation
+ * @chip: The NAND chip
+ * @page: page to read
+ * @offset_in_page: offset within the page
+ * @buf: buffer used to store the data
+ * @len: length of the buffer
+ *
+ * This function issues a READ PAGE operation.
+ * This function does not select/unselect the CS line.
+ *
+ * Returns 0 for success or negative error code otherwise
+ */
+int nand_read_page_op(struct nand_chip *chip, unsigned int page,
+		      unsigned int column, void *buf, unsigned int len)
+{
+	struct mtd_info *mtd = nand_to_mtd(chip);
+
+	if (len && !buf)
+		return -EINVAL;
+
+	if (column + len > mtd->writesize + mtd->oobsize)
+		return -EINVAL;
+
+	chip->cmdfunc(mtd, NAND_CMD_READ0, column, page);
+	if (len)
+		chip->read_buf(mtd, buf, len);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(nand_read_page_op);
+
+/**
+ * nand_read_param_page_op - Do a READ PARAMETER PAGE operation
+ * @chip: The NAND chip
+ * @page: parameter page to read
+ * @buf: buffer used to store the data
+ * @len: length of the buffer
+ *
+ * This function issues a READ PARAMETER PAGE operation.
+ * This function does not select/unselect the CS line.
+ *
+ * Returns 0 for success or negative error code otherwise
+ */
+static int nand_read_param_page_op(struct nand_chip *chip, u8 page, void *buf,
+				   unsigned int len)
+{
+	struct mtd_info *mtd = nand_to_mtd(chip);
+	unsigned int i;
+	u8 *p = buf;
+
+	if (len && !buf)
+		return -EINVAL;
+
+	chip->cmdfunc(mtd, NAND_CMD_PARAM, page, -1);
+	for (i = 0; i < len; i++)
+		p[i] = chip->read_byte(mtd);
+
+	return 0;
+}
+
+/**
+ * nand_change_read_column_op - Do a CHANGE READ COLUMN operation
+ * @chip: The NAND chip
+ * @offset_in_page: offset within the page
+ * @buf: buffer used to store the data
+ * @len: length of the buffer
+ * @force_8bit: force 8-bit bus access
+ *
+ * This function issues a CHANGE READ COLUMN operation.
+ * This function does not select/unselect the CS line.
+ *
+ * Returns 0 for success or negative error code otherwise
+ */
+int nand_change_read_column_op(struct nand_chip *chip, unsigned int column,
+			       void *buf, unsigned int len, bool force_8bit)
+{
+	struct mtd_info *mtd = nand_to_mtd(chip);
+
+	if (len && !buf)
+		return -EINVAL;
+
+	if (column + len > mtd->writesize + mtd->oobsize)
+		return -EINVAL;
+
+	chip->cmdfunc(mtd, NAND_CMD_RNDOUT, column, -1);
+	if (len)
+		chip->read_buf(mtd, buf, len);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(nand_change_read_column_op);
+
+/**
+ * nand_read_oob_op - Do a READ OOB operation
+ * @chip: The NAND chip
+ * @page: page to read
+ * @offset_in_page: offset within the page
+ * @buf: buffer used to store the data
+ * @len: length of the buffer
+ *
+ * This function issues a READ OOB operation.
+ * This function does not select/unselect the CS line.
+ *
+ * Returns 0 for success or negative error code otherwise
+ */
+int nand_read_oob_op(struct nand_chip *chip, unsigned int page,
+		     unsigned int column, void *buf, unsigned int len)
+{
+	struct mtd_info *mtd = nand_to_mtd(chip);
+
+	if (len && !buf)
+		return -EINVAL;
+
+	if (column + len > mtd->oobsize)
+		return -EINVAL;
+
+	chip->cmdfunc(mtd, NAND_CMD_READOOB, column, page);
+	if (len)
+		chip->read_buf(mtd, buf, len);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(nand_read_oob_op);
+
+/**
+ * nand_prog_page_begin_op - starts a PROG PAGE operation
+ * @chip: The NAND chip
+ * @page: page to write
+ * @offset_in_page: offset within the page
+ * @buf: buffer containing the data to write to the page
+ * @len: length of the buffer
+ *
+ * This function issues the first half of a PROG PAGE operation.
+ * This function does not select/unselect the CS line.
+ *
+ * Returns 0 for success or negative error code otherwise
+ */
+int nand_prog_page_begin_op(struct nand_chip *chip, unsigned int page,
+			    unsigned int column, const void *buf,
+			    unsigned int len)
+{
+	struct mtd_info *mtd = nand_to_mtd(chip);
+
+	if (len && !buf)
+		return -EINVAL;
+
+	if (column + len > mtd->writesize + mtd->oobsize)
+		return -EINVAL;
+
+	chip->cmdfunc(mtd, NAND_CMD_SEQIN, column, page);
+
+	if (buf)
+		chip->write_buf(mtd, buf, len);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(nand_prog_page_begin_op);
+
+/**
+ * nand_prog_page_end_op - ends a PROG PAGE operation
+ * @chip: The NAND chip
+ *
+ * This function issues the second half of a PROG PAGE operation.
+ * This function does not select/unselect the CS line.
+ *
+ * Returns 0 for success or negative error code otherwise
+ */
+int nand_prog_page_end_op(struct nand_chip *chip)
+{
+	struct mtd_info *mtd = nand_to_mtd(chip);
+	int status;
+
+	chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1);
+
+	status = chip->waitfunc(mtd, chip);
+	if (status & NAND_STATUS_FAIL)
+		return -EIO;
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(nand_prog_page_end_op);
+
+/**
+ * nand_prog_page_op - Do a full PROG PAGE operation
+ * @chip: The NAND chip
+ * @page: page to write
+ * @offset_in_page: offset within the page
+ * @buf: buffer containing the data to write to the page
+ * @len: length of the buffer
+ *
+ * This function issues a full PROG PAGE operation.
+ * This function does not select/unselect the CS line.
+ *
+ * Returns 0 for success or negative error code otherwise
+ */
+int nand_prog_page_op(struct nand_chip *chip, unsigned int page,
+		      unsigned int column, const void *buf, unsigned int len)
+{
+	struct mtd_info *mtd = nand_to_mtd(chip);
+	int status;
+
+	if (!len || !buf)
+		return -EINVAL;
+
+	if (column + len > mtd->writesize + mtd->oobsize)
+		return -EINVAL;
+
+	chip->cmdfunc(mtd, NAND_CMD_SEQIN, column, page);
+	chip->write_buf(mtd, buf, len);
+	chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1);
+
+	status = chip->waitfunc(mtd, chip);
+	if (status & NAND_STATUS_FAIL)
+		return -EIO;
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(nand_prog_page_op);
+
+/**
+ * nand_change_write_column_op - Do a CHANGE WRITE COLUMN operation
+ * @chip: The NAND chip
+ * @offset_in_page: offset within the page
+ * @buf: buffer containing the data to send to the NAND
+ * @len: length of the buffer
+ * @force_8bit: force 8-bit bus access
+ *
+ * This function issues a CHANGE WRITE COLUMN operation.
+ * This function does not select/unselect the CS line.
+ *
+ * Returns 0 for success or negative error code otherwise
+ */
+int nand_change_write_column_op(struct nand_chip *chip, unsigned int column,
+				const void *buf, unsigned int len,
+				bool force_8bit)
+{
+	struct mtd_info *mtd = nand_to_mtd(chip);
+
+	if (len && !buf)
+		return -EINVAL;
+
+	if (column + len > mtd->writesize + mtd->oobsize)
+		return -EINVAL;
+
+	chip->cmdfunc(mtd, NAND_CMD_RNDIN, column, -1);
+	if (len)
+		chip->write_buf(mtd, buf, len);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(nand_change_write_column_op);
+
+/**
+ * nand_readid_op - Do a READID operation
+ * @chip: The NAND chip
+ * @addr: address cycle to pass after the READID command
+ * @buf: buffer used to store the ID
+ * @len: length of the buffer
+ *
+ * This function sends a READID command and reads back the ID returned by the
+ * NAND.
+ * This function does not select/unselect the CS line.
+ *
+ * Returns 0 for success or negative error code otherwise
+ */
+int nand_readid_op(struct nand_chip *chip, u8 addr,
+		   void *buf, unsigned int len)
+{
+	struct mtd_info *mtd = nand_to_mtd(chip);
+	unsigned int i;
+	u8 *id = buf;
+
+	if (!len || !buf)
+		return -EINVAL;
+
+	chip->cmdfunc(mtd, NAND_CMD_READID, addr, -1);
+
+	for (i = 0; i < len; i++)
+		id[i] = chip->read_byte(mtd);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(nand_readid_op);
+
+/**
+ * nand_status_op - Do a STATUS operation
+ * @chip: The NAND chip
+ * @status: out variable to store the NAND status
+ *
+ * This function sends a STATUS command and reads back the status returned by
+ * the NAND.
+ * This function does not select/unselect the CS line.
+ *
+ * Returns 0 for success or negative error code otherwise
+ */
+int nand_status_op(struct nand_chip *chip, u8 *status)
+{
+	struct mtd_info *mtd = nand_to_mtd(chip);
+
+	chip->cmdfunc(mtd, NAND_CMD_STATUS, -1, -1);
+	if (status)
+		*status = chip->read_byte(mtd);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(nand_status_op);
+
+/**
+ * nand_erase_op - Do an erase operation
+ * @chip: The NAND chip
+ * @eraseblock: block to erase
+ *
+ * This function sends an ERASE command and waits for the NAND to be ready
+ * before returning.
+ * This function does not select/unselect the CS line.
+ *
+ * Returns 0 for success or negative error code otherwise
+ */
+int nand_erase_op(struct nand_chip *chip, unsigned int eraseblock)
+{
+	struct mtd_info *mtd = nand_to_mtd(chip);
+	unsigned int page = eraseblock <<
+			    (chip->phys_erase_shift - chip->page_shift);
+	int status;
+
+	chip->cmdfunc(mtd, NAND_CMD_ERASE1, -1, page);
+	chip->cmdfunc(mtd, NAND_CMD_ERASE2, -1, -1);
+
+	status = chip->waitfunc(mtd, chip);
+	if (status < 0)
+		return status;
+
+	if (status & NAND_STATUS_FAIL)
+		return -EIO;
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(nand_erase_op);
+
+/**
+ * nand_set_features_op - Do a SET FEATURES operation
+ * @chip: The NAND chip
+ * @feature: feature id
+ * @data: 4 bytes of data
+ *
+ * This function sends a SET FEATURES command and waits for the NAND to be
+ * ready before returning.
+ * This function does not select/unselect the CS line.
+ *
+ * Returns 0 for success or negative error code otherwise
+ */
+static int nand_set_features_op(struct nand_chip *chip, u8 feature,
+				const void *data)
+{
+	struct mtd_info *mtd = nand_to_mtd(chip);
+	const u8 *params = data;
+	int i, status;
+
+	chip->cmdfunc(mtd, NAND_CMD_SET_FEATURES, feature, -1);
+	for (i = 0; i < ONFI_SUBFEATURE_PARAM_LEN; ++i)
+		chip->write_byte(mtd, params[i]);
+
+	status = chip->waitfunc(mtd, chip);
+	if (status & NAND_STATUS_FAIL)
+		return -EIO;
+
+	return 0;
+}
+
+/**
+ * nand_get_features_op - Do a GET FEATURES operation
+ * @chip: The NAND chip
+ * @feature: feature id
+ * @data: 4 bytes of data
+ *
+ * This function sends a GET FEATURES command and waits for the NAND to be
+ * ready before returning.
+ * This function does not select/unselect the CS line.
+ *
+ * Returns 0 for success or negative error code otherwise
+ */
+static int nand_get_features_op(struct nand_chip *chip, u8 feature,
+				void *data)
+{
+	struct mtd_info *mtd = nand_to_mtd(chip);
+	u8 *params = data;
+	int i;
+
+	chip->cmdfunc(mtd, NAND_CMD_GET_FEATURES, feature, -1);
+	for (i = 0; i < ONFI_SUBFEATURE_PARAM_LEN; ++i)
+		params[i] = chip->read_byte(mtd);
+
+	return 0;
+}
+
+/**
+ * nand_reset_op - Do a reset operation
+ * @chip: The NAND chip
+ *
+ * This function sends a RESET command and waits for the NAND to be ready
+ * before returning.
+ * This function does not select/unselect the CS line.
+ *
+ * Returns 0 for success or negative error code otherwise
+ */
+int nand_reset_op(struct nand_chip *chip)
+{
+	struct mtd_info *mtd = nand_to_mtd(chip);
+
+	chip->cmdfunc(mtd, NAND_CMD_RESET, -1, -1);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(nand_reset_op);
+
+/**
+ * nand_read_data_op - Read data from the NAND
+ * @chip: The NAND chip
+ * @buf: buffer used to store the data
+ * @len: length of the buffer
+ * @force_8bit: force 8-bit bus access
+ *
+ * This function does a raw data read on the bus. Usually used after launching
+ * another NAND operation like nand_read_page_op().
+ * This function does not select/unselect the CS line.
+ *
+ * Returns 0 for success or negative error code otherwise
+ */
+int nand_read_data_op(struct nand_chip *chip, void *buf, unsigned int len,
+		      bool force_8bit)
+{
+	struct mtd_info *mtd = nand_to_mtd(chip);
+
+	if (!len || !buf)
+		return -EINVAL;
+
+	if (force_8bit) {
+		u8 *p = buf;
+		unsigned int i;
+
+		for (i = 0; i < len; i++)
+			p[i] = chip->read_byte(mtd);
+	} else {
+		chip->read_buf(mtd, buf, len);
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(nand_read_data_op);
+
+/**
+ * nand_write_data_op - Write data from the NAND
+ * @chip: The NAND chip
+ * @buf: buffer containing the data to send on the bus
+ * @len: length of the buffer
+ * @force_8bit: force 8-bit bus access
+ *
+ * This function does a raw data write on the bus. Usually used after launching
+ * another NAND operation like nand_write_page_begin_op().
+ * This function does not select/unselect the CS line.
+ *
+ * Returns 0 for success or negative error code otherwise
+ */
+int nand_write_data_op(struct nand_chip *chip, const void *buf,
+		       unsigned int len, bool force_8bit)
+{
+	struct mtd_info *mtd = nand_to_mtd(chip);
+
+	if (!len || !buf)
+		return -EINVAL;
+
+	if (force_8bit) {
+		const u8 *p = buf;
+		unsigned int i;
+
+		for (i = 0; i < len; i++)
+			chip->write_byte(mtd, p[i]);
+	} else {
+		chip->write_buf(mtd, buf, len);
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(nand_write_data_op);
+
+/**
  * nand_reset - Reset and initialize a NAND device
  * @chip: The NAND chip
  * @chipnr: Internal die id
@@ -1233,7 +1750,7 @@ int nand_reset(struct nand_chip *chip, int chipnr)
 	 * interface settings, hence this weird ->select_chip() dance.
 	 */
 	chip->select_chip(mtd, chipnr);
-	chip->cmdfunc(mtd, NAND_CMD_RESET, -1, -1);
+	nand_reset_op(chip);
 	chip->select_chip(mtd, -1);
 
 	chip->select_chip(mtd, chipnr);
@@ -1389,9 +1906,19 @@ EXPORT_SYMBOL(nand_check_erased_ecc_chunk);
 int nand_read_page_raw(struct mtd_info *mtd, struct nand_chip *chip,
 		       uint8_t *buf, int oob_required, int page)
 {
-	chip->read_buf(mtd, buf, mtd->writesize);
-	if (oob_required)
-		chip->read_buf(mtd, chip->oob_poi, mtd->oobsize);
+	int ret;
+
+	ret = nand_read_data_op(chip, buf, mtd->writesize, false);
+	if (ret)
+		return ret;
+
+	if (oob_required) {
+		ret = nand_read_data_op(chip, chip->oob_poi, mtd->oobsize,
+					false);
+		if (ret)
+			return ret;
+	}
+
 	return 0;
 }
 EXPORT_SYMBOL(nand_read_page_raw);
@@ -1413,29 +1940,46 @@ static int nand_read_page_raw_syndrome(struct mtd_info *mtd,
 	int eccsize = chip->ecc.size;
 	int eccbytes = chip->ecc.bytes;
 	uint8_t *oob = chip->oob_poi;
-	int steps, size;
+	int steps, size, ret;
 
 	for (steps = chip->ecc.steps; steps > 0; steps--) {
-		chip->read_buf(mtd, buf, eccsize);
+		ret = nand_read_data_op(chip, buf, eccsize, false);
+		if (ret)
+			return ret;
+
 		buf += eccsize;
 
 		if (chip->ecc.prepad) {
-			chip->read_buf(mtd, oob, chip->ecc.prepad);
+			ret = nand_read_data_op(chip, oob, chip->ecc.prepad,
+						false);
+			if (ret)
+				return ret;
+
 			oob += chip->ecc.prepad;
 		}
 
-		chip->read_buf(mtd, oob, eccbytes);
+		ret = nand_read_data_op(chip, oob, eccbytes, false);
+		if (ret)
+			return ret;
+
 		oob += eccbytes;
 
 		if (chip->ecc.postpad) {
-			chip->read_buf(mtd, oob, chip->ecc.postpad);
+			ret = nand_read_data_op(chip, oob, chip->ecc.postpad,
+						false);
+			if (ret)
+				return ret;
+
 			oob += chip->ecc.postpad;
 		}
 	}
 
 	size = mtd->oobsize - (oob - chip->oob_poi);
-	if (size)
-		chip->read_buf(mtd, oob, size);
+	if (size) {
+		ret = nand_read_data_op(chip, oob, size, false);
+		if (ret)
+			return ret;
+	}
 
 	return 0;
 }
@@ -1524,7 +2068,7 @@ static int nand_read_subpage(struct mtd_info *mtd, struct nand_chip *chip,
 		chip->cmdfunc(mtd, NAND_CMD_RNDOUT, data_col_addr, -1);
 
 	p = bufpoi + data_col_addr;
-	chip->read_buf(mtd, p, datafrag_len);
+	nand_read_data_op(chip, p, datafrag_len, false);
 
 	/* Calculate ECC */
 	for (i = 0; i < eccfrag_len ; i += chip->ecc.bytes, p += chip->ecc.size)
@@ -1542,8 +2086,11 @@ static int nand_read_subpage(struct mtd_info *mtd, struct nand_chip *chip,
 		gaps = 1;
 
 	if (gaps) {
-		chip->cmdfunc(mtd, NAND_CMD_RNDOUT, mtd->writesize, -1);
-		chip->read_buf(mtd, chip->oob_poi, mtd->oobsize);
+		ret = nand_change_read_column_op(chip, mtd->writesize,
+						 chip->oob_poi, mtd->oobsize,
+						 false);
+		if (ret)
+			return ret;
 	} else {
 		/*
 		 * Send the command to read the particular ECC bytes take care
@@ -1557,9 +2104,12 @@ static int nand_read_subpage(struct mtd_info *mtd, struct nand_chip *chip,
 		    (busw - 1))
 			aligned_len++;
 
-		chip->cmdfunc(mtd, NAND_CMD_RNDOUT,
-			      mtd->writesize + aligned_pos, -1);
-		chip->read_buf(mtd, &chip->oob_poi[aligned_pos], aligned_len);
+		ret = nand_change_read_column_op(chip,
+						 mtd->writesize + aligned_pos,
+						 &chip->oob_poi[aligned_pos],
+						 aligned_len, false);
+		if (ret)
+			return ret;
 	}
 
 	ret = mtd_ooblayout_get_eccbytes(mtd, chip->buffers->ecccode,
@@ -1616,10 +2166,17 @@ static int nand_read_page_hwecc(struct mtd_info *mtd, struct nand_chip *chip,
 
 	for (i = 0; eccsteps; eccsteps--, i += eccbytes, p += eccsize) {
 		chip->ecc.hwctl(mtd, NAND_ECC_READ);
-		chip->read_buf(mtd, p, eccsize);
+
+		ret = nand_read_data_op(chip, p, eccsize, false);
+		if (ret)
+			return ret;
+
 		chip->ecc.calculate(mtd, p, &ecc_calc[i]);
 	}
-	chip->read_buf(mtd, chip->oob_poi, mtd->oobsize);
+
+	ret = nand_read_data_op(chip, chip->oob_poi, mtd->oobsize, false);
+	if (ret)
+		return ret;
 
 	ret = mtd_ooblayout_get_eccbytes(mtd, ecc_code, chip->oob_poi, 0,
 					 chip->ecc.total);
@@ -1678,9 +2235,11 @@ static int nand_read_page_hwecc_oob_first(struct mtd_info *mtd,
 	unsigned int max_bitflips = 0;
 
 	/* Read the OOB area first */
-	chip->cmdfunc(mtd, NAND_CMD_READOOB, 0, page);
-	chip->read_buf(mtd, chip->oob_poi, mtd->oobsize);
-	chip->cmdfunc(mtd, NAND_CMD_READ0, 0, page);
+	ret = nand_read_oob_op(chip, page, 0, chip->oob_poi, mtd->oobsize);
+	if (ret)
+		return ret;
+
+	nand_read_page_op(chip, page, 0, NULL, 0);
 
 	ret = mtd_ooblayout_get_eccbytes(mtd, ecc_code, chip->oob_poi, 0,
 					 chip->ecc.total);
@@ -1691,7 +2250,11 @@ static int nand_read_page_hwecc_oob_first(struct mtd_info *mtd,
 		int stat;
 
 		chip->ecc.hwctl(mtd, NAND_ECC_READ);
-		chip->read_buf(mtd, p, eccsize);
+
+		ret = nand_read_data_op(chip, p, eccsize, false);
+		if (ret)
+			return ret;
+
 		chip->ecc.calculate(mtd, p, &ecc_calc[i]);
 
 		stat = chip->ecc.correct(mtd, p, &ecc_code[i], NULL);
@@ -1728,7 +2291,7 @@ static int nand_read_page_hwecc_oob_first(struct mtd_info *mtd,
 static int nand_read_page_syndrome(struct mtd_info *mtd, struct nand_chip *chip,
 				   uint8_t *buf, int oob_required, int page)
 {
-	int i, eccsize = chip->ecc.size;
+	int ret, i, eccsize = chip->ecc.size;
 	int eccbytes = chip->ecc.bytes;
 	int eccsteps = chip->ecc.steps;
 	int eccpadbytes = eccbytes + chip->ecc.prepad + chip->ecc.postpad;
@@ -1740,21 +2303,36 @@ static int nand_read_page_syndrome(struct mtd_info *mtd, struct nand_chip *chip,
 		int stat;
 
 		chip->ecc.hwctl(mtd, NAND_ECC_READ);
-		chip->read_buf(mtd, p, eccsize);
+
+		ret = nand_read_data_op(chip, p, eccsize, false);
+		if (ret)
+			return ret;
 
 		if (chip->ecc.prepad) {
-			chip->read_buf(mtd, oob, chip->ecc.prepad);
+			ret = nand_read_data_op(chip, oob, chip->ecc.prepad,
+						false);
+			if (ret)
+				return ret;
+
 			oob += chip->ecc.prepad;
 		}
 
 		chip->ecc.hwctl(mtd, NAND_ECC_READSYN);
-		chip->read_buf(mtd, oob, eccbytes);
+
+		ret = nand_read_data_op(chip, oob, eccbytes, false);
+		if (ret)
+			return ret;
+
 		stat = chip->ecc.correct(mtd, p, oob, NULL);
 
 		oob += eccbytes;
 
 		if (chip->ecc.postpad) {
-			chip->read_buf(mtd, oob, chip->ecc.postpad);
+			ret = nand_read_data_op(chip, oob, chip->ecc.postpad,
+						false);
+			if (ret)
+				return ret;
+
 			oob += chip->ecc.postpad;
 		}
 
@@ -1778,8 +2356,11 @@ static int nand_read_page_syndrome(struct mtd_info *mtd, struct nand_chip *chip,
 
 	/* Calculate remaining oob bytes */
 	i = mtd->oobsize - (oob - chip->oob_poi);
-	if (i)
-		chip->read_buf(mtd, oob, i);
+	if (i) {
+		ret = nand_read_data_op(chip, oob, i, false);
+		if (ret)
+			return ret;
+	}
 
 	return max_bitflips;
 }
@@ -1901,7 +2482,7 @@ static int nand_do_read_ops(struct mtd_info *mtd, loff_t from,
 
 read_retry:
 			if (nand_standard_page_accessors(&chip->ecc))
-				chip->cmdfunc(mtd, NAND_CMD_READ0, 0x00, page);
+				nand_read_page_op(chip, page, 0, NULL, 0);
 
 			/*
 			 * Now read the page into the buffer.  Absent an error,
@@ -2060,9 +2641,7 @@ static int nand_read(struct mtd_info *mtd, loff_t from, size_t len,
  */
 int nand_read_oob_std(struct mtd_info *mtd, struct nand_chip *chip, int page)
 {
-	chip->cmdfunc(mtd, NAND_CMD_READOOB, 0, page);
-	chip->read_buf(mtd, chip->oob_poi, mtd->oobsize);
-	return 0;
+	return nand_read_oob_op(chip, page, 0, chip->oob_poi, mtd->oobsize);
 }
 EXPORT_SYMBOL(nand_read_oob_std);
 
@@ -2080,25 +2659,39 @@ int nand_read_oob_syndrome(struct mtd_info *mtd, struct nand_chip *chip,
 	int chunk = chip->ecc.bytes + chip->ecc.prepad + chip->ecc.postpad;
 	int eccsize = chip->ecc.size;
 	uint8_t *bufpoi = chip->oob_poi;
-	int i, toread, sndrnd = 0, pos;
+	int i, toread, sndrnd = 0, pos, ret;
 
-	chip->cmdfunc(mtd, NAND_CMD_READ0, chip->ecc.size, page);
+	nand_read_page_op(chip, page, chip->ecc.size, NULL, 0);
 	for (i = 0; i < chip->ecc.steps; i++) {
 		if (sndrnd) {
+			int ret;
+
 			pos = eccsize + i * (eccsize + chunk);
 			if (mtd->writesize > 512)
-				chip->cmdfunc(mtd, NAND_CMD_RNDOUT, pos, -1);
+				ret = nand_change_read_column_op(
+					chip, pos, NULL, 0, false);
 			else
-				chip->cmdfunc(mtd, NAND_CMD_READ0, pos, page);
+				ret = nand_read_page_op(chip, page, pos, NULL,
+							0);
+
+			if (ret)
+				return ret;
 		} else
 			sndrnd = 1;
 		toread = min_t(int, length, chunk);
-		chip->read_buf(mtd, bufpoi, toread);
+
+		ret = nand_read_data_op(chip, bufpoi, toread, false);
+		if (ret)
+			return ret;
+
 		bufpoi += toread;
 		length -= toread;
 	}
-	if (length > 0)
-		chip->read_buf(mtd, bufpoi, length);
+	if (length > 0) {
+		ret = nand_read_data_op(chip, bufpoi, length, false);
+		if (ret)
+			return ret;
+	}
 
 	return 0;
 }
@@ -2112,18 +2705,8 @@ EXPORT_SYMBOL(nand_read_oob_syndrome);
  */
 int nand_write_oob_std(struct mtd_info *mtd, struct nand_chip *chip, int page)
 {
-	int status = 0;
-	const uint8_t *buf = chip->oob_poi;
-	int length = mtd->oobsize;
-
-	chip->cmdfunc(mtd, NAND_CMD_SEQIN, mtd->writesize, page);
-	chip->write_buf(mtd, buf, length);
-	/* Send command to program the OOB data */
-	chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1);
-
-	status = chip->waitfunc(mtd, chip);
-
-	return status & NAND_STATUS_FAIL ? -EIO : 0;
+	return nand_prog_page_op(chip, page, mtd->writesize, chip->oob_poi,
+				 mtd->oobsize);
 }
 EXPORT_SYMBOL(nand_write_oob_std);
 
@@ -2139,7 +2722,7 @@ int nand_write_oob_syndrome(struct mtd_info *mtd, struct nand_chip *chip,
 {
 	int chunk = chip->ecc.bytes + chip->ecc.prepad + chip->ecc.postpad;
 	int eccsize = chip->ecc.size, length = mtd->oobsize;
-	int i, len, pos, status = 0, sndcmd = 0, steps = chip->ecc.steps;
+	int ret, i, len, pos, sndcmd = 0, steps = chip->ecc.steps;
 	const uint8_t *bufpoi = chip->oob_poi;
 
 	/*
@@ -2153,7 +2736,7 @@ int nand_write_oob_syndrome(struct mtd_info *mtd, struct nand_chip *chip,
 	} else
 		pos = eccsize;
 
-	chip->cmdfunc(mtd, NAND_CMD_SEQIN, pos, page);
+	nand_prog_page_begin_op(chip, page, pos, NULL, 0);
 	for (i = 0; i < steps; i++) {
 		if (sndcmd) {
 			if (mtd->writesize <= 512) {
@@ -2162,28 +2745,39 @@ int nand_write_oob_syndrome(struct mtd_info *mtd, struct nand_chip *chip,
 				len = eccsize;
 				while (len > 0) {
 					int num = min_t(int, len, 4);
-					chip->write_buf(mtd, (uint8_t *)&fill,
-							num);
+
+					ret = nand_write_data_op(chip, &fill,
+								 num, false);
+					if (ret)
+						return ret;
+
 					len -= num;
 				}
 			} else {
 				pos = eccsize + i * (eccsize + chunk);
-				chip->cmdfunc(mtd, NAND_CMD_RNDIN, pos, -1);
+				ret = nand_change_write_column_op(
+					chip, pos, NULL, 0, false);
+				if (ret)
+					return ret;
 			}
 		} else
 			sndcmd = 1;
 		len = min_t(int, length, chunk);
-		chip->write_buf(mtd, bufpoi, len);
+
+		ret = nand_write_data_op(chip, bufpoi, len, false);
+		if (ret)
+			return ret;
+
 		bufpoi += len;
 		length -= len;
 	}
-	if (length > 0)
-		chip->write_buf(mtd, bufpoi, length);
-
-	chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1);
-	status = chip->waitfunc(mtd, chip);
+	if (length > 0) {
+		ret = nand_write_data_op(chip, bufpoi, length, false);
+		if (ret)
+			return ret;
+	}
 
-	return status & NAND_STATUS_FAIL ? -EIO : 0;
+	return nand_prog_page_end_op(chip);
 }
 EXPORT_SYMBOL(nand_write_oob_syndrome);
 
@@ -2335,9 +2929,18 @@ static int nand_read_oob(struct mtd_info *mtd, loff_t from,
 int nand_write_page_raw(struct mtd_info *mtd, struct nand_chip *chip,
 			const uint8_t *buf, int oob_required, int page)
 {
-	chip->write_buf(mtd, buf, mtd->writesize);
-	if (oob_required)
-		chip->write_buf(mtd, chip->oob_poi, mtd->oobsize);
+	int ret;
+
+	ret = nand_write_data_op(chip, buf, mtd->writesize, false);
+	if (ret)
+		return ret;
+
+	if (oob_required) {
+		ret = nand_write_data_op(chip, chip->oob_poi, mtd->oobsize,
+					 false);
+		if (ret)
+			return ret;
+	}
 
 	return 0;
 }
@@ -2361,29 +2964,46 @@ static int nand_write_page_raw_syndrome(struct mtd_info *mtd,
 	int eccsize = chip->ecc.size;
 	int eccbytes = chip->ecc.bytes;
 	uint8_t *oob = chip->oob_poi;
-	int steps, size;
+	int steps, size, ret;
 
 	for (steps = chip->ecc.steps; steps > 0; steps--) {
-		chip->write_buf(mtd, buf, eccsize);
+		ret = nand_write_data_op(chip, buf, eccsize, false);
+		if (ret)
+			return ret;
+
 		buf += eccsize;
 
 		if (chip->ecc.prepad) {
-			chip->write_buf(mtd, oob, chip->ecc.prepad);
+			ret = nand_write_data_op(chip, oob, chip->ecc.prepad,
+						 false);
+			if (ret)
+				return ret;
+
 			oob += chip->ecc.prepad;
 		}
 
-		chip->write_buf(mtd, oob, eccbytes);
+		ret = nand_write_data_op(chip, oob, eccbytes, false);
+		if (ret)
+			return ret;
+
 		oob += eccbytes;
 
 		if (chip->ecc.postpad) {
-			chip->write_buf(mtd, oob, chip->ecc.postpad);
+			ret = nand_write_data_op(chip, oob, chip->ecc.postpad,
+						 false);
+			if (ret)
+				return ret;
+
 			oob += chip->ecc.postpad;
 		}
 	}
 
 	size = mtd->oobsize - (oob - chip->oob_poi);
-	if (size)
-		chip->write_buf(mtd, oob, size);
+	if (size) {
+		ret = nand_write_data_op(chip, oob, size, false);
+		if (ret)
+			return ret;
+	}
 
 	return 0;
 }
@@ -2437,7 +3057,11 @@ static int nand_write_page_hwecc(struct mtd_info *mtd, struct nand_chip *chip,
 
 	for (i = 0; eccsteps; eccsteps--, i += eccbytes, p += eccsize) {
 		chip->ecc.hwctl(mtd, NAND_ECC_WRITE);
-		chip->write_buf(mtd, p, eccsize);
+
+		ret = nand_write_data_op(chip, p, eccsize, false);
+		if (ret)
+			return ret;
+
 		chip->ecc.calculate(mtd, p, &ecc_calc[i]);
 	}
 
@@ -2446,7 +3070,9 @@ static int nand_write_page_hwecc(struct mtd_info *mtd, struct nand_chip *chip,
 	if (ret)
 		return ret;
 
-	chip->write_buf(mtd, chip->oob_poi, mtd->oobsize);
+	ret = nand_write_data_op(chip, chip->oob_poi, mtd->oobsize, false);
+	if (ret)
+		return ret;
 
 	return 0;
 }
@@ -2482,7 +3108,9 @@ static int nand_write_subpage_hwecc(struct mtd_info *mtd,
 		chip->ecc.hwctl(mtd, NAND_ECC_WRITE);
 
 		/* write data (untouched subpages already masked by 0xFF) */
-		chip->write_buf(mtd, buf, ecc_size);
+		ret = nand_write_data_op(chip, buf, ecc_size, false);
+		if (ret)
+			return ret;
 
 		/* mask ECC of un-touched subpages by padding 0xFF */
 		if ((step < start_step) || (step > end_step))
@@ -2509,7 +3137,9 @@ static int nand_write_subpage_hwecc(struct mtd_info *mtd,
 		return ret;
 
 	/* write OOB buffer to NAND device */
-	chip->write_buf(mtd, chip->oob_poi, mtd->oobsize);
+	ret = nand_write_data_op(chip, chip->oob_poi, mtd->oobsize, false);
+	if (ret)
+		return ret;
 
 	return 0;
 }
@@ -2536,31 +3166,49 @@ static int nand_write_page_syndrome(struct mtd_info *mtd,
 	int eccsteps = chip->ecc.steps;
 	const uint8_t *p = buf;
 	uint8_t *oob = chip->oob_poi;
+	int ret;
 
 	for (i = 0; eccsteps; eccsteps--, i += eccbytes, p += eccsize) {
-
 		chip->ecc.hwctl(mtd, NAND_ECC_WRITE);
-		chip->write_buf(mtd, p, eccsize);
+
+		ret = nand_write_data_op(chip, p, eccsize, false);
+		if (ret)
+			return ret;
 
 		if (chip->ecc.prepad) {
-			chip->write_buf(mtd, oob, chip->ecc.prepad);
+			ret = nand_write_data_op(chip, oob, chip->ecc.prepad,
+						 false);
+			if (ret)
+				return ret;
+
 			oob += chip->ecc.prepad;
 		}
 
 		chip->ecc.calculate(mtd, p, oob);
-		chip->write_buf(mtd, oob, eccbytes);
+
+		ret = nand_write_data_op(chip, oob, eccbytes, false);
+		if (ret)
+			return ret;
+
 		oob += eccbytes;
 
 		if (chip->ecc.postpad) {
-			chip->write_buf(mtd, oob, chip->ecc.postpad);
+			ret = nand_write_data_op(chip, oob, chip->ecc.postpad,
+						 false);
+			if (ret)
+				return ret;
+
 			oob += chip->ecc.postpad;
 		}
 	}
 
 	/* Calculate remaining oob bytes */
 	i = mtd->oobsize - (oob - chip->oob_poi);
-	if (i)
-		chip->write_buf(mtd, oob, i);
+	if (i) {
+		ret = nand_write_data_op(chip, oob, i, false);
+		if (ret)
+			return ret;
+	}
 
 	return 0;
 }
@@ -2588,8 +3236,11 @@ static int nand_write_page(struct mtd_info *mtd, struct nand_chip *chip,
 	else
 		subpage = 0;
 
-	if (nand_standard_page_accessors(&chip->ecc))
-		chip->cmdfunc(mtd, NAND_CMD_SEQIN, 0x00, page);
+	if (nand_standard_page_accessors(&chip->ecc)) {
+		status = nand_prog_page_begin_op(chip, page, 0, NULL, 0);
+		if (status)
+			return status;
+	}
 
 	if (unlikely(raw))
 		status = chip->ecc.write_page_raw(mtd, chip, buf,
@@ -2604,13 +3255,8 @@ static int nand_write_page(struct mtd_info *mtd, struct nand_chip *chip,
 	if (status < 0)
 		return status;
 
-	if (nand_standard_page_accessors(&chip->ecc)) {
-		chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1);
-
-		status = chip->waitfunc(mtd, chip);
-		if (status & NAND_STATUS_FAIL)
-			return -EIO;
-	}
+	if (nand_standard_page_accessors(&chip->ecc))
+		return nand_prog_page_end_op(chip);
 
 	return 0;
 }
@@ -2981,11 +3627,12 @@ static int nand_write_oob(struct mtd_info *mtd, loff_t to,
 static int single_erase(struct mtd_info *mtd, int page)
 {
 	struct nand_chip *chip = mtd_to_nand(mtd);
+	unsigned int eraseblock;
+
 	/* Send commands to erase a block */
-	chip->cmdfunc(mtd, NAND_CMD_ERASE1, -1, page);
-	chip->cmdfunc(mtd, NAND_CMD_ERASE2, -1, -1);
+	eraseblock = page >> (chip->phys_erase_shift - chip->page_shift);
 
-	return chip->waitfunc(mtd, chip);
+	return nand_erase_op(chip, eraseblock);
 }
 
 /**
@@ -3069,7 +3716,7 @@ int nand_erase_nand(struct mtd_info *mtd, struct erase_info *instr,
 		status = chip->erase(mtd, page & chip->pagemask);
 
 		/* See if block erase succeeded */
-		if (status & NAND_STATUS_FAIL) {
+		if (status) {
 			pr_debug("%s: failed erase, page 0x%08x\n",
 					__func__, page);
 			instr->state = MTD_ERASE_FAILED;
@@ -3212,22 +3859,12 @@ static int nand_max_bad_blocks(struct mtd_info *mtd, loff_t ofs, size_t len)
 static int nand_onfi_set_features(struct mtd_info *mtd, struct nand_chip *chip,
 			int addr, uint8_t *subfeature_param)
 {
-	int status;
-	int i;
-
 	if (!chip->onfi_version ||
 	    !(le16_to_cpu(chip->onfi_params.opt_cmd)
 	      & ONFI_OPT_CMD_SET_GET_FEATURES))
 		return -EINVAL;
 
-	chip->cmdfunc(mtd, NAND_CMD_SET_FEATURES, addr, -1);
-	for (i = 0; i < ONFI_SUBFEATURE_PARAM_LEN; ++i)
-		chip->write_byte(mtd, subfeature_param[i]);
-
-	status = chip->waitfunc(mtd, chip);
-	if (status & NAND_STATUS_FAIL)
-		return -EIO;
-	return 0;
+	return nand_set_features_op(chip, addr, subfeature_param);
 }
 
 /**
@@ -3240,17 +3877,12 @@ static int nand_onfi_set_features(struct mtd_info *mtd, struct nand_chip *chip,
 static int nand_onfi_get_features(struct mtd_info *mtd, struct nand_chip *chip,
 			int addr, uint8_t *subfeature_param)
 {
-	int i;
-
 	if (!chip->onfi_version ||
 	    !(le16_to_cpu(chip->onfi_params.opt_cmd)
 	      & ONFI_OPT_CMD_SET_GET_FEATURES))
 		return -EINVAL;
 
-	chip->cmdfunc(mtd, NAND_CMD_GET_FEATURES, addr, -1);
-	for (i = 0; i < ONFI_SUBFEATURE_PARAM_LEN; ++i)
-		*subfeature_param++ = chip->read_byte(mtd);
-	return 0;
+	return nand_get_features_op(chip, addr, subfeature_param);
 }
 
 /**
@@ -3393,12 +4025,11 @@ static u16 onfi_crc16(u16 crc, u8 const *p, size_t len)
 static int nand_flash_detect_ext_param_page(struct nand_chip *chip,
 					    struct nand_onfi_params *p)
 {
-	struct mtd_info *mtd = nand_to_mtd(chip);
 	struct onfi_ext_param_page *ep;
 	struct onfi_ext_section *s;
 	struct onfi_ext_ecc_info *ecc;
 	uint8_t *cursor;
-	int ret = -EINVAL;
+	int ret;
 	int len;
 	int i;
 
@@ -3408,14 +4039,18 @@ static int nand_flash_detect_ext_param_page(struct nand_chip *chip,
 		return -ENOMEM;
 
 	/* Send our own NAND_CMD_PARAM. */
-	chip->cmdfunc(mtd, NAND_CMD_PARAM, 0, -1);
+	ret = nand_read_param_page_op(chip, 0, NULL, 0);
+	if (ret)
+		return ret;
 
 	/* Use the Change Read Column command to skip the ONFI param pages. */
-	chip->cmdfunc(mtd, NAND_CMD_RNDOUT,
-			sizeof(*p) * p->num_of_param_pages , -1);
+	ret = nand_change_read_column_op(chip,
+					 sizeof(*p) * p->num_of_param_pages,
+					 ep, len, true);
+	if (ret)
+		return ret;
 
-	/* Read out the Extended Parameter Page. */
-	chip->read_buf(mtd, (uint8_t *)ep, len);
+	ret = -EINVAL;
 	if ((onfi_crc16(ONFI_CRC_BASE, ((uint8_t *)ep) + 2, len - 2)
 		!= le16_to_cpu(ep->crc))) {
 		pr_debug("fail in the CRC.\n");
@@ -3468,19 +4103,23 @@ static int nand_flash_detect_onfi(struct nand_chip *chip)
 {
 	struct mtd_info *mtd = nand_to_mtd(chip);
 	struct nand_onfi_params *p = &chip->onfi_params;
-	int i, j;
-	int val;
+	char id[4];
+	int i, ret, val;
 
 	/* Try ONFI for unknown chip or LP */
-	chip->cmdfunc(mtd, NAND_CMD_READID, 0x20, -1);
-	if (chip->read_byte(mtd) != 'O' || chip->read_byte(mtd) != 'N' ||
-		chip->read_byte(mtd) != 'F' || chip->read_byte(mtd) != 'I')
+	nand_readid_op(chip, 0x20, id, sizeof(id));
+	if (strncmp(id, "ONFI", 4))
 		return 0;
 
-	chip->cmdfunc(mtd, NAND_CMD_PARAM, 0, -1);
+	ret = nand_read_param_page_op(chip, 0, NULL, 0);
+	if (ret)
+		return ret;
+
 	for (i = 0; i < 3; i++) {
-		for (j = 0; j < sizeof(*p); j++)
-			((uint8_t *)p)[j] = chip->read_byte(mtd);
+		ret = nand_read_data_op(chip, p, sizeof(*p), true);
+		if (ret)
+			return ret;
+
 		if (onfi_crc16(ONFI_CRC_BASE, (uint8_t *)p, 254) ==
 				le16_to_cpu(p->crc)) {
 			break;
@@ -3571,20 +4210,22 @@ static int nand_flash_detect_jedec(struct nand_chip *chip)
 	struct mtd_info *mtd = nand_to_mtd(chip);
 	struct nand_jedec_params *p = &chip->jedec_params;
 	struct jedec_ecc_info *ecc;
-	int val;
-	int i, j;
+	char id[5];
+	int i, val, ret;
 
 	/* Try JEDEC for unknown chip or LP */
-	chip->cmdfunc(mtd, NAND_CMD_READID, 0x40, -1);
-	if (chip->read_byte(mtd) != 'J' || chip->read_byte(mtd) != 'E' ||
-		chip->read_byte(mtd) != 'D' || chip->read_byte(mtd) != 'E' ||
-		chip->read_byte(mtd) != 'C')
+	nand_readid_op(chip, 0x40, id, sizeof(id));
+	if (strncmp(id, "JEDEC", sizeof(id)))
 		return 0;
 
-	chip->cmdfunc(mtd, NAND_CMD_PARAM, 0x40, -1);
+	ret = nand_read_param_page_op(chip, 0x40, NULL, 0);
+	if (ret)
+		return ret;
+
 	for (i = 0; i < 3; i++) {
-		for (j = 0; j < sizeof(*p); j++)
-			((uint8_t *)p)[j] = chip->read_byte(mtd);
+		ret = nand_read_data_op(chip, p, sizeof(*p), true);
+		if (ret)
+			return ret;
 
 		if (onfi_crc16(ONFI_CRC_BASE, (uint8_t *)p, 510) ==
 				le16_to_cpu(p->crc))
@@ -3864,7 +4505,6 @@ static int nand_detect(struct nand_chip *chip, struct nand_flash_dev *type)
 	const struct nand_manufacturer *manufacturer;
 	struct mtd_info *mtd = nand_to_mtd(chip);
 	int busw;
-	int i;
 	u8 *id_data = chip->id.data;
 	u8 maf_id, dev_id;
 
@@ -3878,11 +4518,11 @@ static int nand_detect(struct nand_chip *chip, struct nand_flash_dev *type)
 	chip->select_chip(mtd, 0);
 
 	/* Send the command for reading device ID */
-	chip->cmdfunc(mtd, NAND_CMD_READID, 0x00, -1);
+	nand_readid_op(chip, 0, id_data, 2);
 
 	/* Read manufacturer and device IDs */
-	maf_id = chip->read_byte(mtd);
-	dev_id = chip->read_byte(mtd);
+	maf_id = id_data[0];
+	dev_id = id_data[1];
 
 	/*
 	 * Try again to make sure, as some systems the bus-hold or other
@@ -3891,11 +4531,8 @@ static int nand_detect(struct nand_chip *chip, struct nand_flash_dev *type)
 	 * not match, ignore the device completely.
 	 */
 
-	chip->cmdfunc(mtd, NAND_CMD_READID, 0x00, -1);
-
 	/* Read entire ID string */
-	for (i = 0; i < ARRAY_SIZE(chip->id.data); i++)
-		id_data[i] = chip->read_byte(mtd);
+	nand_readid_op(chip, 0, id_data, sizeof(chip->id.data));
 
 	if (id_data[0] != maf_id || id_data[1] != dev_id) {
 		pr_info("second ID read did not match %02x,%02x against %02x,%02x\n",
@@ -4222,15 +4859,16 @@ int nand_scan_ident(struct mtd_info *mtd, int maxchips,
 
 	/* Check for a chip array */
 	for (i = 1; i < maxchips; i++) {
+		u8 id[2];
+
 		/* See comment in nand_get_flash_type for reset */
 		nand_reset(chip, i);
 
 		chip->select_chip(mtd, i);
 		/* Send the command for reading device ID */
-		chip->cmdfunc(mtd, NAND_CMD_READID, 0x00, -1);
+		nand_readid_op(chip, 0, id, sizeof(id));
 		/* Read manufacturer and device IDs */
-		if (nand_maf_id != chip->read_byte(mtd) ||
-		    nand_dev_id != chip->read_byte(mtd)) {
+		if (nand_maf_id != id[0] || nand_dev_id != id[1]) {
 			chip->select_chip(mtd, -1);
 			break;
 		}
diff --git a/drivers/mtd/nand/nand_hynix.c b/drivers/mtd/nand/nand_hynix.c
index 985751eda317..04e3ab7a476c 100644
--- a/drivers/mtd/nand/nand_hynix.c
+++ b/drivers/mtd/nand/nand_hynix.c
@@ -67,15 +67,11 @@ struct hynix_read_retry_otp {
 
 static bool hynix_nand_has_valid_jedecid(struct nand_chip *chip)
 {
-	struct mtd_info *mtd = nand_to_mtd(chip);
-	u8 jedecid[6] = { };
-	int i = 0;
+	u8 jedecid[5] = { };
 
-	chip->cmdfunc(mtd, NAND_CMD_READID, 0x40, -1);
-	for (i = 0; i < 5; i++)
-		jedecid[i] = chip->read_byte(mtd);
+	nand_readid_op(chip, 0x40, jedecid, sizeof(jedecid));
 
-	return !strcmp("JEDEC", jedecid);
+	return !strncmp("JEDEC", jedecid, sizeof(jedecid));
 }
 
 static int hynix_nand_setup_read_retry(struct mtd_info *mtd, int retry_mode)
@@ -175,7 +171,7 @@ static int hynix_read_rr_otp(struct nand_chip *chip,
 	struct mtd_info *mtd = nand_to_mtd(chip);
 	int i;
 
-	chip->cmdfunc(mtd, NAND_CMD_RESET, -1, -1);
+	nand_reset_op(chip);
 
 	chip->cmdfunc(mtd, NAND_HYNIX_CMD_SET_PARAMS, -1, -1);
 
@@ -195,11 +191,10 @@ static int hynix_read_rr_otp(struct nand_chip *chip,
 	chip->cmdfunc(mtd, 0x19, -1, -1);
 
 	/* Now read the page */
-	chip->cmdfunc(mtd, NAND_CMD_READ0, 0x0, info->page);
-	chip->read_buf(mtd, buf, info->size);
+	nand_read_page_op(chip, info->page, 0, buf, info->size);
 
 	/* Put everything back to normal */
-	chip->cmdfunc(mtd, NAND_CMD_RESET, -1, -1);
+	nand_reset_op(chip);
 	chip->cmdfunc(mtd, NAND_HYNIX_CMD_SET_PARAMS, 0x38, -1);
 	chip->write_byte(mtd, 0x0);
 	chip->cmdfunc(mtd, NAND_HYNIX_CMD_APPLY_PARAMS, -1, -1);
diff --git a/drivers/mtd/nand/nand_micron.c b/drivers/mtd/nand/nand_micron.c
index abf6a3c376e8..3934539a7468 100644
--- a/drivers/mtd/nand/nand_micron.c
+++ b/drivers/mtd/nand/nand_micron.c
@@ -137,8 +137,6 @@ micron_nand_read_page_on_die_ecc(struct mtd_info *mtd, struct nand_chip *chip,
 	else if (status & NAND_STATUS_WRITE_RECOMMENDED)
 		max_bitflips = chip->ecc.strength;
 
-	chip->cmdfunc(mtd, NAND_CMD_READ0, -1, -1);
-
 	nand_read_page_raw(mtd, chip, buf, oob_required, page);
 
 	micron_nand_on_die_ecc_setup(chip, false);
@@ -155,10 +153,7 @@ micron_nand_write_page_on_die_ecc(struct mtd_info *mtd, struct nand_chip *chip,
 
 	micron_nand_on_die_ecc_setup(chip, true);
 
-	chip->cmdfunc(mtd, NAND_CMD_SEQIN, 0x00, page);
-	nand_write_page_raw(mtd, chip, buf, oob_required, page);
-	chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1);
-	status = chip->waitfunc(mtd, chip);
+	status = nand_write_page_raw(mtd, chip, buf, oob_required, page);
 
 	micron_nand_on_die_ecc_setup(chip, false);
 
@@ -171,7 +166,6 @@ micron_nand_read_page_raw_on_die_ecc(struct mtd_info *mtd,
 				     uint8_t *buf, int oob_required,
 				     int page)
 {
-	chip->cmdfunc(mtd, NAND_CMD_READ0, 0x00, page);
 	nand_read_page_raw(mtd, chip, buf, oob_required, page);
 
 	return 0;
@@ -183,14 +177,7 @@ micron_nand_write_page_raw_on_die_ecc(struct mtd_info *mtd,
 				      const uint8_t *buf, int oob_required,
 				      int page)
 {
-	int status;
-
-	chip->cmdfunc(mtd, NAND_CMD_SEQIN, 0x00, page);
-	nand_write_page_raw(mtd, chip, buf, oob_required, page);
-	chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1);
-	status = chip->waitfunc(mtd, chip);
-
-	return status & NAND_STATUS_FAIL ? -EIO : 0;
+	return nand_write_page_raw(mtd, chip, buf, oob_required, page);
 }
 
 enum {
diff --git a/drivers/mtd/nand/omap2.c b/drivers/mtd/nand/omap2.c
index 54540c8fa1a2..41e124777579 100644
--- a/drivers/mtd/nand/omap2.c
+++ b/drivers/mtd/nand/omap2.c
@@ -1538,10 +1538,10 @@ static int omap_read_page_bch(struct mtd_info *mtd, struct nand_chip *chip,
 	chip->read_buf(mtd, buf, mtd->writesize);
 
 	/* Read oob bytes */
-	chip->cmdfunc(mtd, NAND_CMD_RNDOUT,
-		      mtd->writesize + BADBLOCK_MARKER_LENGTH, -1);
-	chip->read_buf(mtd, chip->oob_poi + BADBLOCK_MARKER_LENGTH,
-		       chip->ecc.total);
+	nand_change_read_column_op(chip,
+				   mtd->writesize + BADBLOCK_MARKER_LENGTH,
+				   chip->oob_poi + BADBLOCK_MARKER_LENGTH,
+				   chip->ecc.total, false);
 
 	/* Calculate ecc bytes */
 	chip->ecc.calculate(mtd, buf, ecc_calc);
diff --git a/drivers/mtd/nand/pxa3xx_nand.c b/drivers/mtd/nand/pxa3xx_nand.c
index 90b9a9ccbe60..28bcdf64c1fc 100644
--- a/drivers/mtd/nand/pxa3xx_nand.c
+++ b/drivers/mtd/nand/pxa3xx_nand.c
@@ -520,15 +520,13 @@ static int pxa3xx_nand_init_timings_compat(struct pxa3xx_nand_host *host,
 	struct nand_chip *chip = &host->chip;
 	struct pxa3xx_nand_info *info = host->info_data;
 	const struct pxa3xx_nand_flash *f = NULL;
-	struct mtd_info *mtd = nand_to_mtd(&host->chip);
 	int i, id, ntypes;
+	u8 idbuf[2];
 
 	ntypes = ARRAY_SIZE(builtin_flash_types);
 
-	chip->cmdfunc(mtd, NAND_CMD_READID, 0x00, -1);
-
-	id = chip->read_byte(mtd);
-	id |= chip->read_byte(mtd) << 0x8;
+	nand_readid_op(chip, 0, idbuf, sizeof(idbuf));
+	id = idbuf[0] | (idbuf[1] << 8);
 
 	for (i = 0; i < ntypes; i++) {
 		f = &builtin_flash_types[i];
diff --git a/drivers/mtd/nand/qcom_nandc.c b/drivers/mtd/nand/qcom_nandc.c
index 2656c1ac5646..e34313ecd903 100644
--- a/drivers/mtd/nand/qcom_nandc.c
+++ b/drivers/mtd/nand/qcom_nandc.c
@@ -1990,7 +1990,7 @@ static int qcom_nandc_write_oob(struct mtd_info *mtd, struct nand_chip *chip,
 	struct nand_ecc_ctrl *ecc = &chip->ecc;
 	u8 *oob = chip->oob_poi;
 	int data_size, oob_size;
-	int ret, status = 0;
+	int ret;
 
 	host->use_ecc = true;
 
@@ -2027,11 +2027,7 @@ static int qcom_nandc_write_oob(struct mtd_info *mtd, struct nand_chip *chip,
 		return -EIO;
 	}
 
-	chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1);
-
-	status = chip->waitfunc(mtd, chip);
-
-	return status & NAND_STATUS_FAIL ? -EIO : 0;
+	return nand_prog_page_end_op(chip);
 }
 
 static int qcom_nandc_block_bad(struct mtd_info *mtd, loff_t ofs)
@@ -2081,7 +2077,7 @@ static int qcom_nandc_block_markbad(struct mtd_info *mtd, loff_t ofs)
 	struct qcom_nand_host *host = to_qcom_nand_host(chip);
 	struct qcom_nand_controller *nandc = get_qcom_nand_controller(chip);
 	struct nand_ecc_ctrl *ecc = &chip->ecc;
-	int page, ret, status = 0;
+	int page, ret;
 
 	clear_read_regs(nandc);
 	clear_bam_transaction(nandc);
@@ -2114,11 +2110,7 @@ static int qcom_nandc_block_markbad(struct mtd_info *mtd, loff_t ofs)
 		return -EIO;
 	}
 
-	chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1);
-
-	status = chip->waitfunc(mtd, chip);
-
-	return status & NAND_STATUS_FAIL ? -EIO : 0;
+	return nand_prog_page_end_op(chip);
 }
 
 /*
diff --git a/drivers/mtd/nand/r852.c b/drivers/mtd/nand/r852.c
index fc9287af4614..595635b9e9de 100644
--- a/drivers/mtd/nand/r852.c
+++ b/drivers/mtd/nand/r852.c
@@ -364,7 +364,7 @@ static int r852_wait(struct mtd_info *mtd, struct nand_chip *chip)
 	struct r852_device *dev = nand_get_controller_data(chip);
 
 	unsigned long timeout;
-	int status;
+	u8 status;
 
 	timeout = jiffies + (chip->state == FL_ERASING ?
 		msecs_to_jiffies(400) : msecs_to_jiffies(20));
@@ -373,8 +373,7 @@ static int r852_wait(struct mtd_info *mtd, struct nand_chip *chip)
 		if (chip->dev_ready(mtd))
 			break;
 
-	chip->cmdfunc(mtd, NAND_CMD_STATUS, -1, -1);
-	status = (int)chip->read_byte(mtd);
+	nand_status_op(chip, &status);
 
 	/* Unfortunelly, no way to send detailed error status... */
 	if (dev->dma_error) {
@@ -522,9 +521,7 @@ static int r852_ecc_correct(struct mtd_info *mtd, uint8_t *dat,
 static int r852_read_oob(struct mtd_info *mtd, struct nand_chip *chip,
 			     int page)
 {
-	chip->cmdfunc(mtd, NAND_CMD_READOOB, 0, page);
-	chip->read_buf(mtd, chip->oob_poi, mtd->oobsize);
-	return 0;
+	return nand_read_oob_op(chip, page, 0, chip->oob_poi, mtd->oobsize);
 }
 
 /*
@@ -1046,7 +1043,7 @@ static int r852_resume(struct device *device)
 	if (dev->card_registred) {
 		r852_engine_enable(dev);
 		dev->chip->select_chip(mtd, 0);
-		dev->chip->cmdfunc(mtd, NAND_CMD_RESET, -1, -1);
+		nand_reset_op(dev->chip);
 		dev->chip->select_chip(mtd, -1);
 	}
 
diff --git a/drivers/mtd/nand/sunxi_nand.c b/drivers/mtd/nand/sunxi_nand.c
index 82244be3e766..230d037d05d2 100644
--- a/drivers/mtd/nand/sunxi_nand.c
+++ b/drivers/mtd/nand/sunxi_nand.c
@@ -958,12 +958,12 @@ static int sunxi_nfc_hw_ecc_read_chunk(struct mtd_info *mtd,
 	int ret;
 
 	if (*cur_off != data_off)
-		nand->cmdfunc(mtd, NAND_CMD_RNDOUT, data_off, -1);
+		nand_change_read_column_op(nand, data_off, NULL, 0, false);
 
 	sunxi_nfc_randomizer_read_buf(mtd, NULL, ecc->size, false, page);
 
 	if (data_off + ecc->size != oob_off)
-		nand->cmdfunc(mtd, NAND_CMD_RNDOUT, oob_off, -1);
+		nand_change_read_column_op(nand, oob_off, NULL, 0, false);
 
 	ret = sunxi_nfc_wait_cmd_fifo_empty(nfc);
 	if (ret)
@@ -991,16 +991,15 @@ static int sunxi_nfc_hw_ecc_read_chunk(struct mtd_info *mtd,
 		 * Re-read the data with the randomizer disabled to identify
 		 * bitflips in erased pages.
 		 */
-		if (nand->options & NAND_NEED_SCRAMBLING) {
-			nand->cmdfunc(mtd, NAND_CMD_RNDOUT, data_off, -1);
-			nand->read_buf(mtd, data, ecc->size);
-		} else {
+		if (nand->options & NAND_NEED_SCRAMBLING)
+			nand_change_read_column_op(nand, data_off, data,
+						   ecc->size, false);
+		else
 			memcpy_fromio(data, nfc->regs + NFC_RAM0_BASE,
 				      ecc->size);
-		}
 
-		nand->cmdfunc(mtd, NAND_CMD_RNDOUT, oob_off, -1);
-		nand->read_buf(mtd, oob, ecc->bytes + 4);
+		nand_change_read_column_op(nand, oob_off, oob, ecc->bytes + 4,
+					   false);
 
 		ret = nand_check_erased_ecc_chunk(data,	ecc->size,
 						  oob, ecc->bytes + 4,
@@ -1011,7 +1010,8 @@ static int sunxi_nfc_hw_ecc_read_chunk(struct mtd_info *mtd,
 		memcpy_fromio(data, nfc->regs + NFC_RAM0_BASE, ecc->size);
 
 		if (oob_required) {
-			nand->cmdfunc(mtd, NAND_CMD_RNDOUT, oob_off, -1);
+			nand_change_read_column_op(nand, oob_off, NULL, 0,
+						   false);
 			sunxi_nfc_randomizer_read_buf(mtd, oob, ecc->bytes + 4,
 						      true, page);
 
@@ -1038,8 +1038,8 @@ static void sunxi_nfc_hw_ecc_read_extra_oob(struct mtd_info *mtd,
 		return;
 
 	if (!cur_off || *cur_off != offset)
-		nand->cmdfunc(mtd, NAND_CMD_RNDOUT,
-			      offset + mtd->writesize, -1);
+		nand_change_read_column_op(nand, mtd->writesize, NULL, 0,
+					   false);
 
 	if (!randomize)
 		sunxi_nfc_read_buf(mtd, oob + offset, len);
@@ -1116,9 +1116,9 @@ static int sunxi_nfc_hw_ecc_read_chunks_dma(struct mtd_info *mtd, uint8_t *buf,
 
 		if (oob_required && !erased) {
 			/* TODO: use DMA to retrieve OOB */
-			nand->cmdfunc(mtd, NAND_CMD_RNDOUT,
-				      mtd->writesize + oob_off, -1);
-			nand->read_buf(mtd, oob, ecc->bytes + 4);
+			nand_change_read_column_op(nand,
+						   mtd->writesize + oob_off,
+						   oob, ecc->bytes + 4, false);
 
 			sunxi_nfc_hw_ecc_get_prot_oob_bytes(mtd, oob, i,
 							    !i, page);
@@ -1143,18 +1143,17 @@ static int sunxi_nfc_hw_ecc_read_chunks_dma(struct mtd_info *mtd, uint8_t *buf,
 			/*
 			 * Re-read the data with the randomizer disabled to
 			 * identify bitflips in erased pages.
+			 * TODO: use DMA to read page in raw mode
 			 */
-			if (randomized) {
-				/* TODO: use DMA to read page in raw mode */
-				nand->cmdfunc(mtd, NAND_CMD_RNDOUT,
-					      data_off, -1);
-				nand->read_buf(mtd, data, ecc->size);
-			}
+			if (randomized)
+				nand_change_read_column_op(nand, data_off,
+							   data, ecc->size
+							   false);
 
 			/* TODO: use DMA to retrieve OOB */
-			nand->cmdfunc(mtd, NAND_CMD_RNDOUT,
-				      mtd->writesize + oob_off, -1);
-			nand->read_buf(mtd, oob, ecc->bytes + 4);
+			nand_change_read_column_op(nand,
+						   mtd->writesize + oob_off,
+						   oob, ecc->bytes + 4, false);
 
 			ret = nand_check_erased_ecc_chunk(data,	ecc->size,
 							  oob, ecc->bytes + 4,
@@ -1187,12 +1186,12 @@ static int sunxi_nfc_hw_ecc_write_chunk(struct mtd_info *mtd,
 	int ret;
 
 	if (data_off != *cur_off)
-		nand->cmdfunc(mtd, NAND_CMD_RNDIN, data_off, -1);
+		nand_change_write_column_op(nand, data_off, NULL, 0, false);
 
 	sunxi_nfc_randomizer_write_buf(mtd, data, ecc->size, false, page);
 
 	if (data_off + ecc->size != oob_off)
-		nand->cmdfunc(mtd, NAND_CMD_RNDIN, oob_off, -1);
+		nand_change_write_column_op(nand, oob_off, NULL, 0, false);
 
 	ret = sunxi_nfc_wait_cmd_fifo_empty(nfc);
 	if (ret)
@@ -1228,8 +1227,8 @@ static void sunxi_nfc_hw_ecc_write_extra_oob(struct mtd_info *mtd,
 		return;
 
 	if (!cur_off || *cur_off != offset)
-		nand->cmdfunc(mtd, NAND_CMD_RNDIN,
-			      offset + mtd->writesize, -1);
+		nand_change_write_column_op(nand, offset + mtd->writesize,
+					    NULL, 0, false);
 
 	sunxi_nfc_randomizer_write_buf(mtd, oob + offset, len, false, page);
 
@@ -1540,7 +1539,7 @@ static int sunxi_nfc_hw_common_ecc_read_oob(struct mtd_info *mtd,
 					    struct nand_chip *chip,
 					    int page)
 {
-	chip->cmdfunc(mtd, NAND_CMD_READ0, 0, page);
+	nand_read_page_op(chip, page, 0, NULL, 0);
 
 	chip->pagebuf = -1;
 
@@ -1551,9 +1550,9 @@ static int sunxi_nfc_hw_common_ecc_write_oob(struct mtd_info *mtd,
 					     struct nand_chip *chip,
 					     int page)
 {
-	int ret, status;
+	int ret;
 
-	chip->cmdfunc(mtd, NAND_CMD_SEQIN, 0, page);
+	nand_prog_page_begin_op(chip, page, 0, NULL, 0);
 
 	chip->pagebuf = -1;
 
@@ -1563,11 +1562,7 @@ static int sunxi_nfc_hw_common_ecc_write_oob(struct mtd_info *mtd,
 		return ret;
 
 	/* Send command to program the OOB data */
-	chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1);
-
-	status = chip->waitfunc(mtd, chip);
-
-	return status & NAND_STATUS_FAIL ? -EIO : 0;
+	return nand_prog_page_end_op(chip);
 }
 
 static const s32 tWB_lut[] = {6, 12, 16, 20};
diff --git a/drivers/mtd/nand/tango_nand.c b/drivers/mtd/nand/tango_nand.c
index 766906f03943..97a300b46b1d 100644
--- a/drivers/mtd/nand/tango_nand.c
+++ b/drivers/mtd/nand/tango_nand.c
@@ -329,7 +329,7 @@ static void aux_read(struct nand_chip *chip, u8 **buf, int len, int *pos)
 
 	if (!*buf) {
 		/* skip over "len" bytes */
-		chip->cmdfunc(mtd, NAND_CMD_RNDOUT, *pos, -1);
+		nand_change_read_column_op(chip, *pos, NULL, 0, false);
 	} else {
 		tango_read_buf(mtd, *buf, len);
 		*buf += len;
@@ -344,7 +344,7 @@ static void aux_write(struct nand_chip *chip, const u8 **buf, int len, int *pos)
 
 	if (!*buf) {
 		/* skip over "len" bytes */
-		chip->cmdfunc(mtd, NAND_CMD_RNDIN, *pos, -1);
+		nand_change_write_column_op(chip, *pos, NULL, 0, false);
 	} else {
 		tango_write_buf(mtd, *buf, len);
 		*buf += len;
@@ -427,7 +427,7 @@ static void raw_write(struct nand_chip *chip, const u8 *buf, const u8 *oob)
 static int tango_read_page_raw(struct mtd_info *mtd, struct nand_chip *chip,
 			       u8 *buf, int oob_required, int page)
 {
-	chip->cmdfunc(mtd, NAND_CMD_READ0, 0, page);
+	nand_read_page_op(chip, page, 0, NULL, 0);
 	raw_read(chip, buf, chip->oob_poi);
 	return 0;
 }
@@ -435,23 +435,15 @@ static int tango_read_page_raw(struct mtd_info *mtd, struct nand_chip *chip,
 static int tango_write_page_raw(struct mtd_info *mtd, struct nand_chip *chip,
 				const u8 *buf, int oob_required, int page)
 {
-	int status;
-
-	chip->cmdfunc(mtd, NAND_CMD_SEQIN, 0, page);
+	nand_prog_page_begin_op(chip, page, 0, NULL, 0);
 	raw_write(chip, buf, chip->oob_poi);
-	chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1);
-
-	status = chip->waitfunc(mtd, chip);
-	if (status & NAND_STATUS_FAIL)
-		return -EIO;
-
-	return 0;
+	return nand_prog_page_end_op(chip);
 }
 
 static int tango_read_oob(struct mtd_info *mtd, struct nand_chip *chip,
 			  int page)
 {
-	chip->cmdfunc(mtd, NAND_CMD_READ0, 0, page);
+	nand_read_page_op(chip, page, 0, NULL, 0);
 	raw_read(chip, NULL, chip->oob_poi);
 	return 0;
 }
@@ -459,11 +451,9 @@ static int tango_read_oob(struct mtd_info *mtd, struct nand_chip *chip,
 static int tango_write_oob(struct mtd_info *mtd, struct nand_chip *chip,
 			   int page)
 {
-	chip->cmdfunc(mtd, NAND_CMD_SEQIN, 0, page);
+	nand_prog_page_begin_op(chip, page, 0, NULL, 0);
 	raw_write(chip, NULL, chip->oob_poi);
-	chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1);
-	chip->waitfunc(mtd, chip);
-	return 0;
+	return nand_prog_page_end_op(chip);
 }
 
 static int oob_ecc(struct mtd_info *mtd, int idx, struct mtd_oob_region *res)
diff --git a/drivers/mtd/nand/tmio_nand.c b/drivers/mtd/nand/tmio_nand.c
index 84dbf32332e1..ae3583b092fb 100644
--- a/drivers/mtd/nand/tmio_nand.c
+++ b/drivers/mtd/nand/tmio_nand.c
@@ -192,6 +192,7 @@ tmio_nand_wait(struct mtd_info *mtd, struct nand_chip *nand_chip)
 {
 	struct tmio_nand *tmio = mtd_to_tmio(mtd);
 	long timeout;
+	u8 status;
 
 	/* enable RDYREQ interrupt */
 	tmio_iowrite8(0x0f, tmio->fcr + FCR_ISR);
@@ -212,8 +213,8 @@ tmio_nand_wait(struct mtd_info *mtd, struct nand_chip *nand_chip)
 		dev_warn(&tmio->dev->dev, "timeout waiting for interrupt\n");
 	}
 
-	nand_chip->cmdfunc(mtd, NAND_CMD_STATUS, -1, -1);
-	return nand_chip->read_byte(mtd);
+	nand_status_op(chip, &status)
+	return status;
 }
 
 /*
diff --git a/include/linux/mtd/rawnand.h b/include/linux/mtd/rawnand.h
index 749bb08c4772..8b8acde72465 100644
--- a/include/linux/mtd/rawnand.h
+++ b/include/linux/mtd/rawnand.h
@@ -1316,6 +1316,31 @@ int nand_write_page_raw(struct mtd_info *mtd, struct nand_chip *chip,
 /* Reset and initialize a NAND device */
 int nand_reset(struct nand_chip *chip, int chipnr);
 
+/* NAND operation helpers */
+int nand_reset_op(struct nand_chip *chip);
+int nand_readid_op(struct nand_chip *chip, u8 addr, void *buf,
+		   unsigned int len);
+int nand_status_op(struct nand_chip *chip, u8 *status);
+int nand_erase_op(struct nand_chip *chip, unsigned int eraseblock);
+int nand_read_page_op(struct nand_chip *chip, unsigned int page,
+		      unsigned int column, void *buf, unsigned int len);
+int nand_change_read_column_op(struct nand_chip *chip, unsigned int column,
+			       void *buf, unsigned int len, bool force_8bit);
+int nand_read_oob_op(struct nand_chip *chip, unsigned int page,
+		     unsigned int column, void *buf, unsigned int len);
+int nand_prog_page_begin_op(struct nand_chip *chip, unsigned int page,
+			    unsigned int column, const void *buf,
+			    unsigned int len);
+int nand_prog_page_end_op(struct nand_chip *chip);
+int nand_prog_page_op(struct nand_chip *chip, unsigned int page,
+		      unsigned int column, const void *buf, unsigned int len);
+int nand_change_write_column_op(struct nand_chip *chip, unsigned int column,
+				const void *buf, unsigned int len, bool force_8bit);
+int nand_read_data_op(struct nand_chip *chip, void *buf, unsigned int len,
+		      bool force_8bits);
+int nand_write_data_op(struct nand_chip *chip, const void *buf,
+		       unsigned int len, bool force_8bits);
+
 /* Free resources held by the NAND device */
 void nand_cleanup(struct nand_chip *chip);
 
-- 
2.11.0

^ permalink raw reply related	[flat|nested] 75+ messages in thread

* [RFC 01/12] mtd: nand: provide several helpers to do common NAND operations
@ 2017-10-18 14:36   ` Miquel Raynal
  0 siblings, 0 replies; 75+ messages in thread
From: Miquel Raynal @ 2017-10-18 14:36 UTC (permalink / raw)
  To: linux-arm-kernel

From: Boris Brezillon <boris.brezillon@free-electrons.com>

This is part of the process of removing direct calls to ->cmdfunc()
outside of the core in order to introduce a better interface to execute
NAND operations.

Here we provide several helpers and make use of them to remove all
direct calls to ->cmdfunc(). This way, we can easily modify those
helpers to make use of the new ->exec_op() interface when available.

Signed-off-by: Boris Brezillon <boris.brezillon@free-electrons.com>
[miquel.raynal at free-electrons.com: rebased and fixed some conflicts]
Signed-off-by: Miquel Raynal <miquel.raynal@free-electrons.com>
---
 drivers/mtd/nand/atmel/nand-controller.c |   2 +-
 drivers/mtd/nand/brcmnand/brcmnand.c     |   9 +-
 drivers/mtd/nand/cafe_nand.c             |  14 +-
 drivers/mtd/nand/denali.c                |   2 +-
 drivers/mtd/nand/diskonchip.c            |   4 +-
 drivers/mtd/nand/docg4.c                 |   7 +-
 drivers/mtd/nand/fsmc_nand.c             |   5 +-
 drivers/mtd/nand/gpmi-nand/gpmi-nand.c   |  56 +-
 drivers/mtd/nand/hisi504_nand.c          |   3 +-
 drivers/mtd/nand/jz4740_nand.c           |  16 +-
 drivers/mtd/nand/lpc32xx_mlc.c           |   2 +-
 drivers/mtd/nand/lpc32xx_slc.c           |  22 +-
 drivers/mtd/nand/mtk_nand.c              |  11 +-
 drivers/mtd/nand/nand_base.c             | 956 ++++++++++++++++++++++++++-----
 drivers/mtd/nand/nand_hynix.c            |  17 +-
 drivers/mtd/nand/nand_micron.c           |  17 +-
 drivers/mtd/nand/omap2.c                 |   8 +-
 drivers/mtd/nand/pxa3xx_nand.c           |   8 +-
 drivers/mtd/nand/qcom_nandc.c            |  16 +-
 drivers/mtd/nand/r852.c                  |  11 +-
 drivers/mtd/nand/sunxi_nand.c            |  67 +--
 drivers/mtd/nand/tango_nand.c            |  26 +-
 drivers/mtd/nand/tmio_nand.c             |   5 +-
 include/linux/mtd/rawnand.h              |  25 +
 24 files changed, 943 insertions(+), 366 deletions(-)

diff --git a/drivers/mtd/nand/atmel/nand-controller.c b/drivers/mtd/nand/atmel/nand-controller.c
index 29182160bb5f..0b2c82727b24 100644
--- a/drivers/mtd/nand/atmel/nand-controller.c
+++ b/drivers/mtd/nand/atmel/nand-controller.c
@@ -1000,7 +1000,7 @@ static int atmel_hsmc_nand_pmecc_read_pg(struct nand_chip *chip, u8 *buf,
 	 * to the non-optimized one.
 	 */
 	if (nand->activecs->rb.type != ATMEL_NAND_NATIVE_RB) {
-		chip->cmdfunc(mtd, NAND_CMD_READ0, 0x00, page);
+		nand_read_page_op(chip, page, 0, NULL, 0);
 
 		return atmel_nand_pmecc_read_pg(chip, buf, oob_required, page,
 						raw);
diff --git a/drivers/mtd/nand/brcmnand/brcmnand.c b/drivers/mtd/nand/brcmnand/brcmnand.c
index e0eb51d8c012..3f441096a14c 100644
--- a/drivers/mtd/nand/brcmnand/brcmnand.c
+++ b/drivers/mtd/nand/brcmnand/brcmnand.c
@@ -1071,7 +1071,7 @@ static void brcmnand_wp(struct mtd_info *mtd, int wp)
 			return;
 
 		brcmnand_set_wp(ctrl, wp);
-		chip->cmdfunc(mtd, NAND_CMD_STATUS, -1, -1);
+		nand_status_op(chip, NULL);
 		/* NAND_STATUS_WP 0x00 = protected, 0x80 = not protected */
 		ret = bcmnand_ctrl_poll_status(ctrl,
 					       NAND_CTRL_RDY |
@@ -1453,7 +1453,7 @@ static uint8_t brcmnand_read_byte(struct mtd_info *mtd)
 
 		/* At FC_BYTES boundary, switch to next column */
 		if (host->last_byte > 0 && offs == 0)
-			chip->cmdfunc(mtd, NAND_CMD_RNDOUT, addr, -1);
+			nand_change_read_column_op(chip, addr, NULL, 0, false);
 
 		ret = ctrl->flash_cache[offs];
 		break;
@@ -1689,7 +1689,7 @@ static int brcmstb_nand_verify_erased_page(struct mtd_info *mtd,
 	sas = mtd->oobsize / chip->ecc.steps;
 
 	/* read without ecc for verification */
-	chip->cmdfunc(mtd, NAND_CMD_READ0, 0x00, page);
+	nand_read_page_op(chip, page, 0, NULL, 0);
 	ret = chip->ecc.read_page_raw(mtd, chip, buf, true, page);
 	if (ret)
 		return ret;
@@ -2369,12 +2369,11 @@ static int brcmnand_resume(struct device *dev)
 
 	list_for_each_entry(host, &ctrl->host_list, node) {
 		struct nand_chip *chip = &host->chip;
-		struct mtd_info *mtd = nand_to_mtd(chip);
 
 		brcmnand_save_restore_cs_config(host, 1);
 
 		/* Reset the chip, required by some chips after power-up */
-		chip->cmdfunc(mtd, NAND_CMD_RESET, -1, -1);
+		nand_reset_op(chip);
 	}
 
 	return 0;
diff --git a/drivers/mtd/nand/cafe_nand.c b/drivers/mtd/nand/cafe_nand.c
index bc558c438a57..95c2cfa68b66 100644
--- a/drivers/mtd/nand/cafe_nand.c
+++ b/drivers/mtd/nand/cafe_nand.c
@@ -353,23 +353,15 @@ static void cafe_nand_bug(struct mtd_info *mtd)
 static int cafe_nand_write_oob(struct mtd_info *mtd,
 			       struct nand_chip *chip, int page)
 {
-	int status = 0;
-
-	chip->cmdfunc(mtd, NAND_CMD_SEQIN, mtd->writesize, page);
-	chip->write_buf(mtd, chip->oob_poi, mtd->oobsize);
-	chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1);
-	status = chip->waitfunc(mtd, chip);
-
-	return status & NAND_STATUS_FAIL ? -EIO : 0;
+	return nand_prog_page_op(chip, page, mtd->writesize, chip->oob_poi,
+				 mtd->oobsize);
 }
 
 /* Don't use -- use nand_read_oob_std for now */
 static int cafe_nand_read_oob(struct mtd_info *mtd, struct nand_chip *chip,
 			      int page)
 {
-	chip->cmdfunc(mtd, NAND_CMD_READOOB, 0, page);
-	chip->read_buf(mtd, chip->oob_poi, mtd->oobsize);
-	return 0;
+	return nand_read_oob_op(chip, page, 0, chip->oob_poi, mtd->oobsize);
 }
 /**
  * cafe_nand_read_page_syndrome - [REPLACEABLE] hardware ecc syndrome based page read
diff --git a/drivers/mtd/nand/denali.c b/drivers/mtd/nand/denali.c
index 5124f8ae8c04..f90f93a8fb08 100644
--- a/drivers/mtd/nand/denali.c
+++ b/drivers/mtd/nand/denali.c
@@ -951,7 +951,7 @@ static int denali_erase(struct mtd_info *mtd, int page)
 	irq_status = denali_wait_for_irq(denali,
 					 INTR__ERASE_COMP | INTR__ERASE_FAIL);
 
-	return irq_status & INTR__ERASE_COMP ? 0 : NAND_STATUS_FAIL;
+	return irq_status & INTR__ERASE_FAIL ? -EIO : 0;
 }
 
 static int denali_setup_data_interface(struct mtd_info *mtd, int chipnr,
diff --git a/drivers/mtd/nand/diskonchip.c b/drivers/mtd/nand/diskonchip.c
index 72671dc52e2e..6bc93ea66f50 100644
--- a/drivers/mtd/nand/diskonchip.c
+++ b/drivers/mtd/nand/diskonchip.c
@@ -448,7 +448,7 @@ static int doc200x_wait(struct mtd_info *mtd, struct nand_chip *this)
 	int status;
 
 	DoC_WaitReady(doc);
-	this->cmdfunc(mtd, NAND_CMD_STATUS, -1, -1);
+	nand_status_op(this, NULL);
 	DoC_WaitReady(doc);
 	status = (int)this->read_byte(mtd);
 
@@ -595,7 +595,7 @@ static void doc2001plus_select_chip(struct mtd_info *mtd, int chip)
 
 	/* Assert ChipEnable and deassert WriteProtect */
 	WriteDOC((DOC_FLASH_CE), docptr, Mplus_FlashSelect);
-	this->cmdfunc(mtd, NAND_CMD_RESET, -1, -1);
+	nand_reset_op(this);
 
 	doc->curchip = chip;
 	doc->curfloor = floor;
diff --git a/drivers/mtd/nand/docg4.c b/drivers/mtd/nand/docg4.c
index 2436cbc71662..45c01b4b34c7 100644
--- a/drivers/mtd/nand/docg4.c
+++ b/drivers/mtd/nand/docg4.c
@@ -900,6 +900,7 @@ static int docg4_erase_block(struct mtd_info *mtd, int page)
 	struct docg4_priv *doc = nand_get_controller_data(nand);
 	void __iomem *docptr = doc->virtadr;
 	uint16_t g4_page;
+	int status;
 
 	dev_dbg(doc->dev, "%s: page %04x\n", __func__, page);
 
@@ -939,7 +940,11 @@ static int docg4_erase_block(struct mtd_info *mtd, int page)
 	poll_status(doc);
 	write_nop(docptr);
 
-	return nand->waitfunc(mtd, nand);
+	status = nand->waitfunc(mtd, nand);
+	if (status < 0)
+		return status;
+
+	return status & NAND_STATUS_FAIL ? -EIO : 0;
 }
 
 static int write_page(struct mtd_info *mtd, struct nand_chip *nand,
diff --git a/drivers/mtd/nand/fsmc_nand.c b/drivers/mtd/nand/fsmc_nand.c
index eac15d9bf49e..b44e5c6545e0 100644
--- a/drivers/mtd/nand/fsmc_nand.c
+++ b/drivers/mtd/nand/fsmc_nand.c
@@ -697,7 +697,7 @@ static int fsmc_read_page_hwecc(struct mtd_info *mtd, struct nand_chip *chip,
 	unsigned int max_bitflips = 0;
 
 	for (i = 0, s = 0; s < eccsteps; s++, i += eccbytes, p += eccsize) {
-		chip->cmdfunc(mtd, NAND_CMD_READ0, s * eccsize, page);
+		nand_read_page_op(chip, page, s * eccsize, NULL, 0);
 		chip->ecc.hwctl(mtd, NAND_ECC_READ);
 		chip->read_buf(mtd, p, eccsize);
 
@@ -720,8 +720,7 @@ static int fsmc_read_page_hwecc(struct mtd_info *mtd, struct nand_chip *chip,
 			if (chip->options & NAND_BUSWIDTH_16)
 				len = roundup(len, 2);
 
-			chip->cmdfunc(mtd, NAND_CMD_READOOB, off, page);
-			chip->read_buf(mtd, oob + j, len);
+			nand_read_oob_op(chip, page, off, oob + j, len);
 			j += len;
 		}
 
diff --git a/drivers/mtd/nand/gpmi-nand/gpmi-nand.c b/drivers/mtd/nand/gpmi-nand/gpmi-nand.c
index 50f8d4a1b983..c8ceaecd8065 100644
--- a/drivers/mtd/nand/gpmi-nand/gpmi-nand.c
+++ b/drivers/mtd/nand/gpmi-nand/gpmi-nand.c
@@ -1097,8 +1097,8 @@ static int gpmi_ecc_read_page(struct mtd_info *mtd, struct nand_chip *chip,
 			eccbytes = DIV_ROUND_UP(offset + eccbits, 8);
 			offset /= 8;
 			eccbytes -= offset;
-			chip->cmdfunc(mtd, NAND_CMD_RNDOUT, offset, -1);
-			chip->read_buf(mtd, eccbuf, eccbytes);
+			nand_change_read_column_op(chip, offset, eccbuf,
+						   eccbytes, false);
 
 			/*
 			 * ECC data are not byte aligned and we may have
@@ -1411,7 +1411,7 @@ static int gpmi_ecc_read_oob(struct mtd_info *mtd, struct nand_chip *chip,
 	memset(chip->oob_poi, ~0, mtd->oobsize);
 
 	/* Read out the conventional OOB. */
-	chip->cmdfunc(mtd, NAND_CMD_READ0, mtd->writesize, page);
+	nand_read_page_op(chip, page, mtd->writesize, NULL, 0);
 	chip->read_buf(mtd, chip->oob_poi, mtd->oobsize);
 
 	/*
@@ -1421,7 +1421,7 @@ static int gpmi_ecc_read_oob(struct mtd_info *mtd, struct nand_chip *chip,
 	 */
 	if (GPMI_IS_MX23(this)) {
 		/* Read the block mark into the first byte of the OOB buffer. */
-		chip->cmdfunc(mtd, NAND_CMD_READ0, 0, page);
+		nand_read_page_op(chip, page, 0, NULL, 0);
 		chip->oob_poi[0] = chip->read_byte(mtd);
 	}
 
@@ -1432,7 +1432,6 @@ static int
 gpmi_ecc_write_oob(struct mtd_info *mtd, struct nand_chip *chip, int page)
 {
 	struct mtd_oob_region of = { };
-	int status = 0;
 
 	/* Do we have available oob area? */
 	mtd_ooblayout_free(mtd, 0, &of);
@@ -1442,12 +1441,8 @@ gpmi_ecc_write_oob(struct mtd_info *mtd, struct nand_chip *chip, int page)
 	if (!nand_is_slc(chip))
 		return -EPERM;
 
-	chip->cmdfunc(mtd, NAND_CMD_SEQIN, mtd->writesize + of.offset, page);
-	chip->write_buf(mtd, chip->oob_poi + of.offset, of.length);
-	chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1);
-
-	status = chip->waitfunc(mtd, chip);
-	return status & NAND_STATUS_FAIL ? -EIO : 0;
+	return nand_prog_page_op(chip, page, mtd->writesize + of.offset,
+				 chip->oob_poi + of.offset, of.length);
 }
 
 /*
@@ -1630,7 +1625,7 @@ static int gpmi_ecc_write_page_raw(struct mtd_info *mtd,
 static int gpmi_ecc_read_oob_raw(struct mtd_info *mtd, struct nand_chip *chip,
 				 int page)
 {
-	chip->cmdfunc(mtd, NAND_CMD_READ0, 0, page);
+	nand_read_page_op(chip, page, 0, NULL, 0);
 
 	return gpmi_ecc_read_page_raw(mtd, chip, NULL, 1, page);
 }
@@ -1638,7 +1633,7 @@ static int gpmi_ecc_read_oob_raw(struct mtd_info *mtd, struct nand_chip *chip,
 static int gpmi_ecc_write_oob_raw(struct mtd_info *mtd, struct nand_chip *chip,
 				 int page)
 {
-	chip->cmdfunc(mtd, NAND_CMD_SEQIN, 0, page);
+	nand_prog_page_begin_op(chip, page, 0, NULL, 0);
 
 	return gpmi_ecc_write_page_raw(mtd, chip, NULL, 1, page);
 }
@@ -1649,7 +1644,7 @@ static int gpmi_block_markbad(struct mtd_info *mtd, loff_t ofs)
 	struct gpmi_nand_data *this = nand_get_controller_data(chip);
 	int ret = 0;
 	uint8_t *block_mark;
-	int column, page, status, chipnr;
+	int column, page, chipnr;
 
 	chipnr = (int)(ofs >> chip->chip_shift);
 	chip->select_chip(mtd, chipnr);
@@ -1663,13 +1658,7 @@ static int gpmi_block_markbad(struct mtd_info *mtd, loff_t ofs)
 	/* Shift to get page */
 	page = (int)(ofs >> chip->page_shift);
 
-	chip->cmdfunc(mtd, NAND_CMD_SEQIN, column, page);
-	chip->write_buf(mtd, block_mark, 1);
-	chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1);
-
-	status = chip->waitfunc(mtd, chip);
-	if (status & NAND_STATUS_FAIL)
-		ret = -EIO;
+	ret = nand_prog_page_op(chip, page, column, block_mark, 1);
 
 	chip->select_chip(mtd, -1);
 
@@ -1737,7 +1726,7 @@ static int mx23_check_transcription_stamp(struct gpmi_nand_data *this)
 		 * Read the NCB fingerprint. The fingerprint is four bytes long
 		 * and starts in the 12th byte of the page.
 		 */
-		chip->cmdfunc(mtd, NAND_CMD_READ0, 12, page);
+		nand_read_page_op(chip, page, 12, NULL, 0);
 		chip->read_buf(mtd, buffer, strlen(fingerprint));
 
 		/* Look for the fingerprint. */
@@ -1797,17 +1786,10 @@ static int mx23_write_transcription_stamp(struct gpmi_nand_data *this)
 	dev_dbg(dev, "Erasing the search area...\n");
 
 	for (block = 0; block < search_area_size_in_blocks; block++) {
-		/* Compute the page address. */
-		page = block * block_size_in_pages;
-
 		/* Erase this block. */
 		dev_dbg(dev, "\tErasing block 0x%x\n", block);
-		chip->cmdfunc(mtd, NAND_CMD_ERASE1, -1, page);
-		chip->cmdfunc(mtd, NAND_CMD_ERASE2, -1, -1);
-
-		/* Wait for the erase to finish. */
-		status = chip->waitfunc(mtd, chip);
-		if (status & NAND_STATUS_FAIL)
+		status = nand_erase_op(chip, block);
+		if (status)
 			dev_err(dev, "[%s] Erase failed.\n", __func__);
 	}
 
@@ -1823,13 +1805,11 @@ static int mx23_write_transcription_stamp(struct gpmi_nand_data *this)
 
 		/* Write the first page of the current stride. */
 		dev_dbg(dev, "Writing an NCB fingerprint in page 0x%x\n", page);
-		chip->cmdfunc(mtd, NAND_CMD_SEQIN, 0x00, page);
-		chip->ecc.write_page_raw(mtd, chip, buffer, 0, page);
-		chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1);
 
-		/* Wait for the write to finish. */
-		status = chip->waitfunc(mtd, chip);
-		if (status & NAND_STATUS_FAIL)
+		nand_prog_page_begin_op(chip, page, 0, NULL, 0);
+		chip->ecc.write_page_raw(mtd, chip, buffer, 0, page);
+		status = nand_prog_page_end_op(chip);
+		if (status)
 			dev_err(dev, "[%s] Write failed.\n", __func__);
 	}
 
@@ -1884,7 +1864,7 @@ static int mx23_boot_init(struct gpmi_nand_data  *this)
 
 		/* Send the command to read the conventional block mark. */
 		chip->select_chip(mtd, chipnr);
-		chip->cmdfunc(mtd, NAND_CMD_READ0, mtd->writesize, page);
+		nand_read_page_op(chip, page, mtd->writesize, NULL, 0);
 		block_mark = chip->read_byte(mtd);
 		chip->select_chip(mtd, -1);
 
diff --git a/drivers/mtd/nand/hisi504_nand.c b/drivers/mtd/nand/hisi504_nand.c
index 0897261c3e17..184d765c8bbe 100644
--- a/drivers/mtd/nand/hisi504_nand.c
+++ b/drivers/mtd/nand/hisi504_nand.c
@@ -574,8 +574,7 @@ static int hisi_nand_read_oob(struct mtd_info *mtd, struct nand_chip *chip,
 {
 	struct hinfc_host *host = nand_get_controller_data(chip);
 
-	chip->cmdfunc(mtd, NAND_CMD_READOOB, 0, page);
-	chip->read_buf(mtd, chip->oob_poi, mtd->oobsize);
+	nand_read_oob_op(chip, page, 0, chip->oob_poi, mtd->oobsize);
 
 	if (host->irq_status & HINFC504_INTS_UE) {
 		host->irq_status = 0;
diff --git a/drivers/mtd/nand/jz4740_nand.c b/drivers/mtd/nand/jz4740_nand.c
index ad827d4af3e9..613b00a9604b 100644
--- a/drivers/mtd/nand/jz4740_nand.c
+++ b/drivers/mtd/nand/jz4740_nand.c
@@ -313,6 +313,7 @@ static int jz_nand_detect_bank(struct platform_device *pdev,
 	uint32_t ctrl;
 	struct nand_chip *chip = &nand->chip;
 	struct mtd_info *mtd = nand_to_mtd(chip);
+	u8 id[2];
 
 	/* Request I/O resource. */
 	sprintf(res_name, "bank%d", bank);
@@ -335,17 +336,16 @@ static int jz_nand_detect_bank(struct platform_device *pdev,
 
 		/* Retrieve the IDs from the first chip. */
 		chip->select_chip(mtd, 0);
-		chip->cmdfunc(mtd, NAND_CMD_RESET, -1, -1);
-		chip->cmdfunc(mtd, NAND_CMD_READID, 0x00, -1);
-		*nand_maf_id = chip->read_byte(mtd);
-		*nand_dev_id = chip->read_byte(mtd);
+		nand_reset_op(chip);
+		nand_readid_op(chip, 0, id, sizeof(id));
+		*nand_maf_id = id[0];
+		*nand_dev_id = id[1];
 	} else {
 		/* Detect additional chip. */
 		chip->select_chip(mtd, chipnr);
-		chip->cmdfunc(mtd, NAND_CMD_RESET, -1, -1);
-		chip->cmdfunc(mtd, NAND_CMD_READID, 0x00, -1);
-		if (*nand_maf_id != chip->read_byte(mtd)
-		 || *nand_dev_id != chip->read_byte(mtd)) {
+		nand_reset_op(chip);
+		nand_readid_op(chip, 0, id, sizeof(id));
+		if (*nand_maf_id != id[0] || *nand_dev_id != id[1]) {
 			ret = -ENODEV;
 			goto notfound_id;
 		}
diff --git a/drivers/mtd/nand/lpc32xx_mlc.c b/drivers/mtd/nand/lpc32xx_mlc.c
index c3bb358ef01e..0723ded232ff 100644
--- a/drivers/mtd/nand/lpc32xx_mlc.c
+++ b/drivers/mtd/nand/lpc32xx_mlc.c
@@ -461,7 +461,7 @@ static int lpc32xx_read_page(struct mtd_info *mtd, struct nand_chip *chip,
 	}
 
 	/* Writing Command and Address */
-	chip->cmdfunc(mtd, NAND_CMD_READ0, 0, page);
+	nand_read_page_op(chip, page, 0, NULL, 0);
 
 	/* For all sub-pages */
 	for (i = 0; i < host->mlcsubpages; i++) {
diff --git a/drivers/mtd/nand/lpc32xx_slc.c b/drivers/mtd/nand/lpc32xx_slc.c
index b61f28a1554d..2b96c281b1a2 100644
--- a/drivers/mtd/nand/lpc32xx_slc.c
+++ b/drivers/mtd/nand/lpc32xx_slc.c
@@ -399,10 +399,7 @@ static void lpc32xx_nand_write_buf(struct mtd_info *mtd, const uint8_t *buf, int
 static int lpc32xx_nand_read_oob_syndrome(struct mtd_info *mtd,
 					  struct nand_chip *chip, int page)
 {
-	chip->cmdfunc(mtd, NAND_CMD_READOOB, 0, page);
-	chip->read_buf(mtd, chip->oob_poi, mtd->oobsize);
-
-	return 0;
+	return nand_read_oob_op(chip, page, 0, chip->oob_poi, mtd->oobsize);
 }
 
 /*
@@ -411,17 +408,8 @@ static int lpc32xx_nand_read_oob_syndrome(struct mtd_info *mtd,
 static int lpc32xx_nand_write_oob_syndrome(struct mtd_info *mtd,
 	struct nand_chip *chip, int page)
 {
-	int status;
-
-	chip->cmdfunc(mtd, NAND_CMD_SEQIN, mtd->writesize, page);
-	chip->write_buf(mtd, chip->oob_poi, mtd->oobsize);
-
-	/* Send command to program the OOB data */
-	chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1);
-
-	status = chip->waitfunc(mtd, chip);
-
-	return status & NAND_STATUS_FAIL ? -EIO : 0;
+	return nand_prog_page_op(chip, page, mtd->writesize, chip->oob_poi,
+				 mtd->oobsize);
 }
 
 /*
@@ -632,7 +620,7 @@ static int lpc32xx_nand_read_page_syndrome(struct mtd_info *mtd,
 	uint8_t *oobecc, tmpecc[LPC32XX_ECC_SAVE_SIZE];
 
 	/* Issue read command */
-	chip->cmdfunc(mtd, NAND_CMD_READ0, 0, page);
+	nand_read_page_op(chip, page, 0, NULL, 0);
 
 	/* Read data and oob, calculate ECC */
 	status = lpc32xx_xfer(mtd, buf, chip->ecc.steps, 1);
@@ -675,7 +663,7 @@ static int lpc32xx_nand_read_page_raw_syndrome(struct mtd_info *mtd,
 					       int page)
 {
 	/* Issue read command */
-	chip->cmdfunc(mtd, NAND_CMD_READ0, 0, page);
+	nand_read_page_op(chip, page, 0, NULL, 0);
 
 	/* Raw reads can just use the FIFO interface */
 	chip->read_buf(mtd, buf, chip->ecc.size * chip->ecc.steps);
diff --git a/drivers/mtd/nand/mtk_nand.c b/drivers/mtd/nand/mtk_nand.c
index d86a7d131cc0..1a3fcb65f478 100644
--- a/drivers/mtd/nand/mtk_nand.c
+++ b/drivers/mtd/nand/mtk_nand.c
@@ -834,16 +834,13 @@ static int mtk_nfc_write_oob_std(struct mtd_info *mtd, struct nand_chip *chip,
 {
 	int ret;
 
-	chip->cmdfunc(mtd, NAND_CMD_SEQIN, 0x00, page);
+	nand_prog_page_begin_op(chip, page, 0, NULL, 0);
 
 	ret = mtk_nfc_write_page_raw(mtd, chip, NULL, 1, page);
 	if (ret < 0)
 		return -EIO;
 
-	chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1);
-	ret = chip->waitfunc(mtd, chip);
-
-	return ret & NAND_STATUS_FAIL ? -EIO : 0;
+	return nand_prog_page_end_op(chip);
 }
 
 static int mtk_nfc_update_ecc_stats(struct mtd_info *mtd, u8 *buf, u32 sectors)
@@ -1016,7 +1013,7 @@ static int mtk_nfc_read_page_raw(struct mtd_info *mtd, struct nand_chip *chip,
 static int mtk_nfc_read_oob_std(struct mtd_info *mtd, struct nand_chip *chip,
 				int page)
 {
-	chip->cmdfunc(mtd, NAND_CMD_READ0, 0, page);
+	nand_read_page_op(chip, page, 0, NULL, 0);
 
 	return mtk_nfc_read_page_raw(mtd, chip, NULL, 1, page);
 }
@@ -1556,7 +1553,7 @@ static int mtk_nfc_resume(struct device *dev)
 		mtd = nand_to_mtd(nand);
 		for (i = 0; i < chip->nsels; i++) {
 			nand->select_chip(mtd, i);
-			nand->cmdfunc(mtd, NAND_CMD_RESET, -1, -1);
+			nand_reset_op(nand);
 		}
 	}
 
diff --git a/drivers/mtd/nand/nand_base.c b/drivers/mtd/nand/nand_base.c
index c63e4a88a653..f7dd7d5a84b3 100644
--- a/drivers/mtd/nand/nand_base.c
+++ b/drivers/mtd/nand/nand_base.c
@@ -561,14 +561,19 @@ static int nand_block_markbad_lowlevel(struct mtd_info *mtd, loff_t ofs)
 static int nand_check_wp(struct mtd_info *mtd)
 {
 	struct nand_chip *chip = mtd_to_nand(mtd);
+	u8 status;
+	int ret;
 
 	/* Broken xD cards report WP despite being writable */
 	if (chip->options & NAND_BROKEN_XD)
 		return 0;
 
 	/* Check the WP bit */
-	chip->cmdfunc(mtd, NAND_CMD_STATUS, -1, -1);
-	return (chip->read_byte(mtd) & NAND_STATUS_WP) ? 0 : 1;
+	ret = nand_status_op(chip, &status);
+	if (ret)
+		return ret;
+
+	return status & NAND_STATUS_WP ? 0 : 1;
 }
 
 /**
@@ -667,10 +672,17 @@ EXPORT_SYMBOL_GPL(nand_wait_ready);
 static void nand_wait_status_ready(struct mtd_info *mtd, unsigned long timeo)
 {
 	register struct nand_chip *chip = mtd_to_nand(mtd);
+	int ret;
 
 	timeo = jiffies + msecs_to_jiffies(timeo);
 	do {
-		if ((chip->read_byte(mtd) & NAND_STATUS_READY))
+		u8 status;
+
+		ret = nand_read_data_op(chip, &status, sizeof(status), true);
+		if (ret)
+			return;
+
+		if (status & NAND_STATUS_READY)
 			break;
 		touch_softlockup_watchdog();
 	} while (time_before(jiffies, timeo));
@@ -1014,7 +1026,15 @@ static void panic_nand_wait(struct mtd_info *mtd, struct nand_chip *chip,
 			if (chip->dev_ready(mtd))
 				break;
 		} else {
-			if (chip->read_byte(mtd) & NAND_STATUS_READY)
+			int ret;
+			u8 status;
+
+			ret = nand_read_data_op(chip, &status, sizeof(status),
+						true);
+			if (ret)
+				return;
+
+			if (status & NAND_STATUS_READY)
 				break;
 		}
 		mdelay(1);
@@ -1031,8 +1051,9 @@ static void panic_nand_wait(struct mtd_info *mtd, struct nand_chip *chip,
 static int nand_wait(struct mtd_info *mtd, struct nand_chip *chip)
 {
 
-	int status;
 	unsigned long timeo = 400;
+	u8 status;
+	int ret;
 
 	/*
 	 * Apply this short delay always to ensure that we do wait tWB in any
@@ -1040,7 +1061,9 @@ static int nand_wait(struct mtd_info *mtd, struct nand_chip *chip)
 	 */
 	ndelay(100);
 
-	chip->cmdfunc(mtd, NAND_CMD_STATUS, -1, -1);
+	ret = nand_status_op(chip, NULL);
+	if (ret)
+		return ret;
 
 	if (in_interrupt() || oops_in_progress)
 		panic_nand_wait(mtd, chip, timeo);
@@ -1051,14 +1074,22 @@ static int nand_wait(struct mtd_info *mtd, struct nand_chip *chip)
 				if (chip->dev_ready(mtd))
 					break;
 			} else {
-				if (chip->read_byte(mtd) & NAND_STATUS_READY)
+				ret = nand_read_data_op(chip, &status,
+							sizeof(status), true);
+				if (ret)
+					return ret;
+
+				if (status & NAND_STATUS_READY)
 					break;
 			}
 			cond_resched();
 		} while (time_before(jiffies, timeo));
 	}
 
-	status = (int)chip->read_byte(mtd);
+	ret = nand_read_data_op(chip, &status, sizeof(status), true);
+	if (ret)
+		return ret;
+
 	/* This can happen if in case of timeout or buggy dev_ready */
 	WARN_ON(!(status & NAND_STATUS_READY));
 	return status;
@@ -1213,6 +1244,492 @@ static void nand_release_data_interface(struct nand_chip *chip)
 }
 
 /**
+ * nand_read_page_op - Do a READ PAGE operation
+ * @chip: The NAND chip
+ * @page: page to read
+ * @offset_in_page: offset within the page
+ * @buf: buffer used to store the data
+ * @len: length of the buffer
+ *
+ * This function issues a READ PAGE operation.
+ * This function does not select/unselect the CS line.
+ *
+ * Returns 0 for success or negative error code otherwise
+ */
+int nand_read_page_op(struct nand_chip *chip, unsigned int page,
+		      unsigned int column, void *buf, unsigned int len)
+{
+	struct mtd_info *mtd = nand_to_mtd(chip);
+
+	if (len && !buf)
+		return -EINVAL;
+
+	if (column + len > mtd->writesize + mtd->oobsize)
+		return -EINVAL;
+
+	chip->cmdfunc(mtd, NAND_CMD_READ0, column, page);
+	if (len)
+		chip->read_buf(mtd, buf, len);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(nand_read_page_op);
+
+/**
+ * nand_read_param_page_op - Do a READ PARAMETER PAGE operation
+ * @chip: The NAND chip
+ * @page: parameter page to read
+ * @buf: buffer used to store the data
+ * @len: length of the buffer
+ *
+ * This function issues a READ PARAMETER PAGE operation.
+ * This function does not select/unselect the CS line.
+ *
+ * Returns 0 for success or negative error code otherwise
+ */
+static int nand_read_param_page_op(struct nand_chip *chip, u8 page, void *buf,
+				   unsigned int len)
+{
+	struct mtd_info *mtd = nand_to_mtd(chip);
+	unsigned int i;
+	u8 *p = buf;
+
+	if (len && !buf)
+		return -EINVAL;
+
+	chip->cmdfunc(mtd, NAND_CMD_PARAM, page, -1);
+	for (i = 0; i < len; i++)
+		p[i] = chip->read_byte(mtd);
+
+	return 0;
+}
+
+/**
+ * nand_change_read_column_op - Do a CHANGE READ COLUMN operation
+ * @chip: The NAND chip
+ * @offset_in_page: offset within the page
+ * @buf: buffer used to store the data
+ * @len: length of the buffer
+ * @force_8bit: force 8-bit bus access
+ *
+ * This function issues a CHANGE READ COLUMN operation.
+ * This function does not select/unselect the CS line.
+ *
+ * Returns 0 for success or negative error code otherwise
+ */
+int nand_change_read_column_op(struct nand_chip *chip, unsigned int column,
+			       void *buf, unsigned int len, bool force_8bit)
+{
+	struct mtd_info *mtd = nand_to_mtd(chip);
+
+	if (len && !buf)
+		return -EINVAL;
+
+	if (column + len > mtd->writesize + mtd->oobsize)
+		return -EINVAL;
+
+	chip->cmdfunc(mtd, NAND_CMD_RNDOUT, column, -1);
+	if (len)
+		chip->read_buf(mtd, buf, len);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(nand_change_read_column_op);
+
+/**
+ * nand_read_oob_op - Do a READ OOB operation
+ * @chip: The NAND chip
+ * @page: page to read
+ * @offset_in_page: offset within the page
+ * @buf: buffer used to store the data
+ * @len: length of the buffer
+ *
+ * This function issues a READ OOB operation.
+ * This function does not select/unselect the CS line.
+ *
+ * Returns 0 for success or negative error code otherwise
+ */
+int nand_read_oob_op(struct nand_chip *chip, unsigned int page,
+		     unsigned int column, void *buf, unsigned int len)
+{
+	struct mtd_info *mtd = nand_to_mtd(chip);
+
+	if (len && !buf)
+		return -EINVAL;
+
+	if (column + len > mtd->oobsize)
+		return -EINVAL;
+
+	chip->cmdfunc(mtd, NAND_CMD_READOOB, column, page);
+	if (len)
+		chip->read_buf(mtd, buf, len);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(nand_read_oob_op);
+
+/**
+ * nand_prog_page_begin_op - starts a PROG PAGE operation
+ * @chip: The NAND chip
+ * @page: page to write
+ * @offset_in_page: offset within the page
+ * @buf: buffer containing the data to write to the page
+ * @len: length of the buffer
+ *
+ * This function issues the first half of a PROG PAGE operation.
+ * This function does not select/unselect the CS line.
+ *
+ * Returns 0 for success or negative error code otherwise
+ */
+int nand_prog_page_begin_op(struct nand_chip *chip, unsigned int page,
+			    unsigned int column, const void *buf,
+			    unsigned int len)
+{
+	struct mtd_info *mtd = nand_to_mtd(chip);
+
+	if (len && !buf)
+		return -EINVAL;
+
+	if (column + len > mtd->writesize + mtd->oobsize)
+		return -EINVAL;
+
+	chip->cmdfunc(mtd, NAND_CMD_SEQIN, column, page);
+
+	if (buf)
+		chip->write_buf(mtd, buf, len);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(nand_prog_page_begin_op);
+
+/**
+ * nand_prog_page_end_op - ends a PROG PAGE operation
+ * @chip: The NAND chip
+ *
+ * This function issues the second half of a PROG PAGE operation.
+ * This function does not select/unselect the CS line.
+ *
+ * Returns 0 for success or negative error code otherwise
+ */
+int nand_prog_page_end_op(struct nand_chip *chip)
+{
+	struct mtd_info *mtd = nand_to_mtd(chip);
+	int status;
+
+	chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1);
+
+	status = chip->waitfunc(mtd, chip);
+	if (status & NAND_STATUS_FAIL)
+		return -EIO;
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(nand_prog_page_end_op);
+
+/**
+ * nand_prog_page_op - Do a full PROG PAGE operation
+ * @chip: The NAND chip
+ * @page: page to write
+ * @offset_in_page: offset within the page
+ * @buf: buffer containing the data to write to the page
+ * @len: length of the buffer
+ *
+ * This function issues a full PROG PAGE operation.
+ * This function does not select/unselect the CS line.
+ *
+ * Returns 0 for success or negative error code otherwise
+ */
+int nand_prog_page_op(struct nand_chip *chip, unsigned int page,
+		      unsigned int column, const void *buf, unsigned int len)
+{
+	struct mtd_info *mtd = nand_to_mtd(chip);
+	int status;
+
+	if (!len || !buf)
+		return -EINVAL;
+
+	if (column + len > mtd->writesize + mtd->oobsize)
+		return -EINVAL;
+
+	chip->cmdfunc(mtd, NAND_CMD_SEQIN, column, page);
+	chip->write_buf(mtd, buf, len);
+	chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1);
+
+	status = chip->waitfunc(mtd, chip);
+	if (status & NAND_STATUS_FAIL)
+		return -EIO;
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(nand_prog_page_op);
+
+/**
+ * nand_change_write_column_op - Do a CHANGE WRITE COLUMN operation
+ * @chip: The NAND chip
+ * @offset_in_page: offset within the page
+ * @buf: buffer containing the data to send to the NAND
+ * @len: length of the buffer
+ * @force_8bit: force 8-bit bus access
+ *
+ * This function issues a CHANGE WRITE COLUMN operation.
+ * This function does not select/unselect the CS line.
+ *
+ * Returns 0 for success or negative error code otherwise
+ */
+int nand_change_write_column_op(struct nand_chip *chip, unsigned int column,
+				const void *buf, unsigned int len,
+				bool force_8bit)
+{
+	struct mtd_info *mtd = nand_to_mtd(chip);
+
+	if (len && !buf)
+		return -EINVAL;
+
+	if (column + len > mtd->writesize + mtd->oobsize)
+		return -EINVAL;
+
+	chip->cmdfunc(mtd, NAND_CMD_RNDIN, column, -1);
+	if (len)
+		chip->write_buf(mtd, buf, len);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(nand_change_write_column_op);
+
+/**
+ * nand_readid_op - Do a READID operation
+ * @chip: The NAND chip
+ * @addr: address cycle to pass after the READID command
+ * @buf: buffer used to store the ID
+ * @len: length of the buffer
+ *
+ * This function sends a READID command and reads back the ID returned by the
+ * NAND.
+ * This function does not select/unselect the CS line.
+ *
+ * Returns 0 for success or negative error code otherwise
+ */
+int nand_readid_op(struct nand_chip *chip, u8 addr,
+		   void *buf, unsigned int len)
+{
+	struct mtd_info *mtd = nand_to_mtd(chip);
+	unsigned int i;
+	u8 *id = buf;
+
+	if (!len || !buf)
+		return -EINVAL;
+
+	chip->cmdfunc(mtd, NAND_CMD_READID, addr, -1);
+
+	for (i = 0; i < len; i++)
+		id[i] = chip->read_byte(mtd);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(nand_readid_op);
+
+/**
+ * nand_status_op - Do a STATUS operation
+ * @chip: The NAND chip
+ * @status: out variable to store the NAND status
+ *
+ * This function sends a STATUS command and reads back the status returned by
+ * the NAND.
+ * This function does not select/unselect the CS line.
+ *
+ * Returns 0 for success or negative error code otherwise
+ */
+int nand_status_op(struct nand_chip *chip, u8 *status)
+{
+	struct mtd_info *mtd = nand_to_mtd(chip);
+
+	chip->cmdfunc(mtd, NAND_CMD_STATUS, -1, -1);
+	if (status)
+		*status = chip->read_byte(mtd);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(nand_status_op);
+
+/**
+ * nand_erase_op - Do an erase operation
+ * @chip: The NAND chip
+ * @eraseblock: block to erase
+ *
+ * This function sends an ERASE command and waits for the NAND to be ready
+ * before returning.
+ * This function does not select/unselect the CS line.
+ *
+ * Returns 0 for success or negative error code otherwise
+ */
+int nand_erase_op(struct nand_chip *chip, unsigned int eraseblock)
+{
+	struct mtd_info *mtd = nand_to_mtd(chip);
+	unsigned int page = eraseblock <<
+			    (chip->phys_erase_shift - chip->page_shift);
+	int status;
+
+	chip->cmdfunc(mtd, NAND_CMD_ERASE1, -1, page);
+	chip->cmdfunc(mtd, NAND_CMD_ERASE2, -1, -1);
+
+	status = chip->waitfunc(mtd, chip);
+	if (status < 0)
+		return status;
+
+	if (status & NAND_STATUS_FAIL)
+		return -EIO;
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(nand_erase_op);
+
+/**
+ * nand_set_features_op - Do a SET FEATURES operation
+ * @chip: The NAND chip
+ * @feature: feature id
+ * @data: 4 bytes of data
+ *
+ * This function sends a SET FEATURES command and waits for the NAND to be
+ * ready before returning.
+ * This function does not select/unselect the CS line.
+ *
+ * Returns 0 for success or negative error code otherwise
+ */
+static int nand_set_features_op(struct nand_chip *chip, u8 feature,
+				const void *data)
+{
+	struct mtd_info *mtd = nand_to_mtd(chip);
+	const u8 *params = data;
+	int i, status;
+
+	chip->cmdfunc(mtd, NAND_CMD_SET_FEATURES, feature, -1);
+	for (i = 0; i < ONFI_SUBFEATURE_PARAM_LEN; ++i)
+		chip->write_byte(mtd, params[i]);
+
+	status = chip->waitfunc(mtd, chip);
+	if (status & NAND_STATUS_FAIL)
+		return -EIO;
+
+	return 0;
+}
+
+/**
+ * nand_get_features_op - Do a GET FEATURES operation
+ * @chip: The NAND chip
+ * @feature: feature id
+ * @data: 4 bytes of data
+ *
+ * This function sends a GET FEATURES command and waits for the NAND to be
+ * ready before returning.
+ * This function does not select/unselect the CS line.
+ *
+ * Returns 0 for success or negative error code otherwise
+ */
+static int nand_get_features_op(struct nand_chip *chip, u8 feature,
+				void *data)
+{
+	struct mtd_info *mtd = nand_to_mtd(chip);
+	u8 *params = data;
+	int i;
+
+	chip->cmdfunc(mtd, NAND_CMD_GET_FEATURES, feature, -1);
+	for (i = 0; i < ONFI_SUBFEATURE_PARAM_LEN; ++i)
+		params[i] = chip->read_byte(mtd);
+
+	return 0;
+}
+
+/**
+ * nand_reset_op - Do a reset operation
+ * @chip: The NAND chip
+ *
+ * This function sends a RESET command and waits for the NAND to be ready
+ * before returning.
+ * This function does not select/unselect the CS line.
+ *
+ * Returns 0 for success or negative error code otherwise
+ */
+int nand_reset_op(struct nand_chip *chip)
+{
+	struct mtd_info *mtd = nand_to_mtd(chip);
+
+	chip->cmdfunc(mtd, NAND_CMD_RESET, -1, -1);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(nand_reset_op);
+
+/**
+ * nand_read_data_op - Read data from the NAND
+ * @chip: The NAND chip
+ * @buf: buffer used to store the data
+ * @len: length of the buffer
+ * @force_8bit: force 8-bit bus access
+ *
+ * This function does a raw data read on the bus. Usually used after launching
+ * another NAND operation like nand_read_page_op().
+ * This function does not select/unselect the CS line.
+ *
+ * Returns 0 for success or negative error code otherwise
+ */
+int nand_read_data_op(struct nand_chip *chip, void *buf, unsigned int len,
+		      bool force_8bit)
+{
+	struct mtd_info *mtd = nand_to_mtd(chip);
+
+	if (!len || !buf)
+		return -EINVAL;
+
+	if (force_8bit) {
+		u8 *p = buf;
+		unsigned int i;
+
+		for (i = 0; i < len; i++)
+			p[i] = chip->read_byte(mtd);
+	} else {
+		chip->read_buf(mtd, buf, len);
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(nand_read_data_op);
+
+/**
+ * nand_write_data_op - Write data from the NAND
+ * @chip: The NAND chip
+ * @buf: buffer containing the data to send on the bus
+ * @len: length of the buffer
+ * @force_8bit: force 8-bit bus access
+ *
+ * This function does a raw data write on the bus. Usually used after launching
+ * another NAND operation like nand_write_page_begin_op().
+ * This function does not select/unselect the CS line.
+ *
+ * Returns 0 for success or negative error code otherwise
+ */
+int nand_write_data_op(struct nand_chip *chip, const void *buf,
+		       unsigned int len, bool force_8bit)
+{
+	struct mtd_info *mtd = nand_to_mtd(chip);
+
+	if (!len || !buf)
+		return -EINVAL;
+
+	if (force_8bit) {
+		const u8 *p = buf;
+		unsigned int i;
+
+		for (i = 0; i < len; i++)
+			chip->write_byte(mtd, p[i]);
+	} else {
+		chip->write_buf(mtd, buf, len);
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(nand_write_data_op);
+
+/**
  * nand_reset - Reset and initialize a NAND device
  * @chip: The NAND chip
  * @chipnr: Internal die id
@@ -1233,7 +1750,7 @@ int nand_reset(struct nand_chip *chip, int chipnr)
 	 * interface settings, hence this weird ->select_chip() dance.
 	 */
 	chip->select_chip(mtd, chipnr);
-	chip->cmdfunc(mtd, NAND_CMD_RESET, -1, -1);
+	nand_reset_op(chip);
 	chip->select_chip(mtd, -1);
 
 	chip->select_chip(mtd, chipnr);
@@ -1389,9 +1906,19 @@ EXPORT_SYMBOL(nand_check_erased_ecc_chunk);
 int nand_read_page_raw(struct mtd_info *mtd, struct nand_chip *chip,
 		       uint8_t *buf, int oob_required, int page)
 {
-	chip->read_buf(mtd, buf, mtd->writesize);
-	if (oob_required)
-		chip->read_buf(mtd, chip->oob_poi, mtd->oobsize);
+	int ret;
+
+	ret = nand_read_data_op(chip, buf, mtd->writesize, false);
+	if (ret)
+		return ret;
+
+	if (oob_required) {
+		ret = nand_read_data_op(chip, chip->oob_poi, mtd->oobsize,
+					false);
+		if (ret)
+			return ret;
+	}
+
 	return 0;
 }
 EXPORT_SYMBOL(nand_read_page_raw);
@@ -1413,29 +1940,46 @@ static int nand_read_page_raw_syndrome(struct mtd_info *mtd,
 	int eccsize = chip->ecc.size;
 	int eccbytes = chip->ecc.bytes;
 	uint8_t *oob = chip->oob_poi;
-	int steps, size;
+	int steps, size, ret;
 
 	for (steps = chip->ecc.steps; steps > 0; steps--) {
-		chip->read_buf(mtd, buf, eccsize);
+		ret = nand_read_data_op(chip, buf, eccsize, false);
+		if (ret)
+			return ret;
+
 		buf += eccsize;
 
 		if (chip->ecc.prepad) {
-			chip->read_buf(mtd, oob, chip->ecc.prepad);
+			ret = nand_read_data_op(chip, oob, chip->ecc.prepad,
+						false);
+			if (ret)
+				return ret;
+
 			oob += chip->ecc.prepad;
 		}
 
-		chip->read_buf(mtd, oob, eccbytes);
+		ret = nand_read_data_op(chip, oob, eccbytes, false);
+		if (ret)
+			return ret;
+
 		oob += eccbytes;
 
 		if (chip->ecc.postpad) {
-			chip->read_buf(mtd, oob, chip->ecc.postpad);
+			ret = nand_read_data_op(chip, oob, chip->ecc.postpad,
+						false);
+			if (ret)
+				return ret;
+
 			oob += chip->ecc.postpad;
 		}
 	}
 
 	size = mtd->oobsize - (oob - chip->oob_poi);
-	if (size)
-		chip->read_buf(mtd, oob, size);
+	if (size) {
+		ret = nand_read_data_op(chip, oob, size, false);
+		if (ret)
+			return ret;
+	}
 
 	return 0;
 }
@@ -1524,7 +2068,7 @@ static int nand_read_subpage(struct mtd_info *mtd, struct nand_chip *chip,
 		chip->cmdfunc(mtd, NAND_CMD_RNDOUT, data_col_addr, -1);
 
 	p = bufpoi + data_col_addr;
-	chip->read_buf(mtd, p, datafrag_len);
+	nand_read_data_op(chip, p, datafrag_len, false);
 
 	/* Calculate ECC */
 	for (i = 0; i < eccfrag_len ; i += chip->ecc.bytes, p += chip->ecc.size)
@@ -1542,8 +2086,11 @@ static int nand_read_subpage(struct mtd_info *mtd, struct nand_chip *chip,
 		gaps = 1;
 
 	if (gaps) {
-		chip->cmdfunc(mtd, NAND_CMD_RNDOUT, mtd->writesize, -1);
-		chip->read_buf(mtd, chip->oob_poi, mtd->oobsize);
+		ret = nand_change_read_column_op(chip, mtd->writesize,
+						 chip->oob_poi, mtd->oobsize,
+						 false);
+		if (ret)
+			return ret;
 	} else {
 		/*
 		 * Send the command to read the particular ECC bytes take care
@@ -1557,9 +2104,12 @@ static int nand_read_subpage(struct mtd_info *mtd, struct nand_chip *chip,
 		    (busw - 1))
 			aligned_len++;
 
-		chip->cmdfunc(mtd, NAND_CMD_RNDOUT,
-			      mtd->writesize + aligned_pos, -1);
-		chip->read_buf(mtd, &chip->oob_poi[aligned_pos], aligned_len);
+		ret = nand_change_read_column_op(chip,
+						 mtd->writesize + aligned_pos,
+						 &chip->oob_poi[aligned_pos],
+						 aligned_len, false);
+		if (ret)
+			return ret;
 	}
 
 	ret = mtd_ooblayout_get_eccbytes(mtd, chip->buffers->ecccode,
@@ -1616,10 +2166,17 @@ static int nand_read_page_hwecc(struct mtd_info *mtd, struct nand_chip *chip,
 
 	for (i = 0; eccsteps; eccsteps--, i += eccbytes, p += eccsize) {
 		chip->ecc.hwctl(mtd, NAND_ECC_READ);
-		chip->read_buf(mtd, p, eccsize);
+
+		ret = nand_read_data_op(chip, p, eccsize, false);
+		if (ret)
+			return ret;
+
 		chip->ecc.calculate(mtd, p, &ecc_calc[i]);
 	}
-	chip->read_buf(mtd, chip->oob_poi, mtd->oobsize);
+
+	ret = nand_read_data_op(chip, chip->oob_poi, mtd->oobsize, false);
+	if (ret)
+		return ret;
 
 	ret = mtd_ooblayout_get_eccbytes(mtd, ecc_code, chip->oob_poi, 0,
 					 chip->ecc.total);
@@ -1678,9 +2235,11 @@ static int nand_read_page_hwecc_oob_first(struct mtd_info *mtd,
 	unsigned int max_bitflips = 0;
 
 	/* Read the OOB area first */
-	chip->cmdfunc(mtd, NAND_CMD_READOOB, 0, page);
-	chip->read_buf(mtd, chip->oob_poi, mtd->oobsize);
-	chip->cmdfunc(mtd, NAND_CMD_READ0, 0, page);
+	ret = nand_read_oob_op(chip, page, 0, chip->oob_poi, mtd->oobsize);
+	if (ret)
+		return ret;
+
+	nand_read_page_op(chip, page, 0, NULL, 0);
 
 	ret = mtd_ooblayout_get_eccbytes(mtd, ecc_code, chip->oob_poi, 0,
 					 chip->ecc.total);
@@ -1691,7 +2250,11 @@ static int nand_read_page_hwecc_oob_first(struct mtd_info *mtd,
 		int stat;
 
 		chip->ecc.hwctl(mtd, NAND_ECC_READ);
-		chip->read_buf(mtd, p, eccsize);
+
+		ret = nand_read_data_op(chip, p, eccsize, false);
+		if (ret)
+			return ret;
+
 		chip->ecc.calculate(mtd, p, &ecc_calc[i]);
 
 		stat = chip->ecc.correct(mtd, p, &ecc_code[i], NULL);
@@ -1728,7 +2291,7 @@ static int nand_read_page_hwecc_oob_first(struct mtd_info *mtd,
 static int nand_read_page_syndrome(struct mtd_info *mtd, struct nand_chip *chip,
 				   uint8_t *buf, int oob_required, int page)
 {
-	int i, eccsize = chip->ecc.size;
+	int ret, i, eccsize = chip->ecc.size;
 	int eccbytes = chip->ecc.bytes;
 	int eccsteps = chip->ecc.steps;
 	int eccpadbytes = eccbytes + chip->ecc.prepad + chip->ecc.postpad;
@@ -1740,21 +2303,36 @@ static int nand_read_page_syndrome(struct mtd_info *mtd, struct nand_chip *chip,
 		int stat;
 
 		chip->ecc.hwctl(mtd, NAND_ECC_READ);
-		chip->read_buf(mtd, p, eccsize);
+
+		ret = nand_read_data_op(chip, p, eccsize, false);
+		if (ret)
+			return ret;
 
 		if (chip->ecc.prepad) {
-			chip->read_buf(mtd, oob, chip->ecc.prepad);
+			ret = nand_read_data_op(chip, oob, chip->ecc.prepad,
+						false);
+			if (ret)
+				return ret;
+
 			oob += chip->ecc.prepad;
 		}
 
 		chip->ecc.hwctl(mtd, NAND_ECC_READSYN);
-		chip->read_buf(mtd, oob, eccbytes);
+
+		ret = nand_read_data_op(chip, oob, eccbytes, false);
+		if (ret)
+			return ret;
+
 		stat = chip->ecc.correct(mtd, p, oob, NULL);
 
 		oob += eccbytes;
 
 		if (chip->ecc.postpad) {
-			chip->read_buf(mtd, oob, chip->ecc.postpad);
+			ret = nand_read_data_op(chip, oob, chip->ecc.postpad,
+						false);
+			if (ret)
+				return ret;
+
 			oob += chip->ecc.postpad;
 		}
 
@@ -1778,8 +2356,11 @@ static int nand_read_page_syndrome(struct mtd_info *mtd, struct nand_chip *chip,
 
 	/* Calculate remaining oob bytes */
 	i = mtd->oobsize - (oob - chip->oob_poi);
-	if (i)
-		chip->read_buf(mtd, oob, i);
+	if (i) {
+		ret = nand_read_data_op(chip, oob, i, false);
+		if (ret)
+			return ret;
+	}
 
 	return max_bitflips;
 }
@@ -1901,7 +2482,7 @@ static int nand_do_read_ops(struct mtd_info *mtd, loff_t from,
 
 read_retry:
 			if (nand_standard_page_accessors(&chip->ecc))
-				chip->cmdfunc(mtd, NAND_CMD_READ0, 0x00, page);
+				nand_read_page_op(chip, page, 0, NULL, 0);
 
 			/*
 			 * Now read the page into the buffer.  Absent an error,
@@ -2060,9 +2641,7 @@ static int nand_read(struct mtd_info *mtd, loff_t from, size_t len,
  */
 int nand_read_oob_std(struct mtd_info *mtd, struct nand_chip *chip, int page)
 {
-	chip->cmdfunc(mtd, NAND_CMD_READOOB, 0, page);
-	chip->read_buf(mtd, chip->oob_poi, mtd->oobsize);
-	return 0;
+	return nand_read_oob_op(chip, page, 0, chip->oob_poi, mtd->oobsize);
 }
 EXPORT_SYMBOL(nand_read_oob_std);
 
@@ -2080,25 +2659,39 @@ int nand_read_oob_syndrome(struct mtd_info *mtd, struct nand_chip *chip,
 	int chunk = chip->ecc.bytes + chip->ecc.prepad + chip->ecc.postpad;
 	int eccsize = chip->ecc.size;
 	uint8_t *bufpoi = chip->oob_poi;
-	int i, toread, sndrnd = 0, pos;
+	int i, toread, sndrnd = 0, pos, ret;
 
-	chip->cmdfunc(mtd, NAND_CMD_READ0, chip->ecc.size, page);
+	nand_read_page_op(chip, page, chip->ecc.size, NULL, 0);
 	for (i = 0; i < chip->ecc.steps; i++) {
 		if (sndrnd) {
+			int ret;
+
 			pos = eccsize + i * (eccsize + chunk);
 			if (mtd->writesize > 512)
-				chip->cmdfunc(mtd, NAND_CMD_RNDOUT, pos, -1);
+				ret = nand_change_read_column_op(
+					chip, pos, NULL, 0, false);
 			else
-				chip->cmdfunc(mtd, NAND_CMD_READ0, pos, page);
+				ret = nand_read_page_op(chip, page, pos, NULL,
+							0);
+
+			if (ret)
+				return ret;
 		} else
 			sndrnd = 1;
 		toread = min_t(int, length, chunk);
-		chip->read_buf(mtd, bufpoi, toread);
+
+		ret = nand_read_data_op(chip, bufpoi, toread, false);
+		if (ret)
+			return ret;
+
 		bufpoi += toread;
 		length -= toread;
 	}
-	if (length > 0)
-		chip->read_buf(mtd, bufpoi, length);
+	if (length > 0) {
+		ret = nand_read_data_op(chip, bufpoi, length, false);
+		if (ret)
+			return ret;
+	}
 
 	return 0;
 }
@@ -2112,18 +2705,8 @@ EXPORT_SYMBOL(nand_read_oob_syndrome);
  */
 int nand_write_oob_std(struct mtd_info *mtd, struct nand_chip *chip, int page)
 {
-	int status = 0;
-	const uint8_t *buf = chip->oob_poi;
-	int length = mtd->oobsize;
-
-	chip->cmdfunc(mtd, NAND_CMD_SEQIN, mtd->writesize, page);
-	chip->write_buf(mtd, buf, length);
-	/* Send command to program the OOB data */
-	chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1);
-
-	status = chip->waitfunc(mtd, chip);
-
-	return status & NAND_STATUS_FAIL ? -EIO : 0;
+	return nand_prog_page_op(chip, page, mtd->writesize, chip->oob_poi,
+				 mtd->oobsize);
 }
 EXPORT_SYMBOL(nand_write_oob_std);
 
@@ -2139,7 +2722,7 @@ int nand_write_oob_syndrome(struct mtd_info *mtd, struct nand_chip *chip,
 {
 	int chunk = chip->ecc.bytes + chip->ecc.prepad + chip->ecc.postpad;
 	int eccsize = chip->ecc.size, length = mtd->oobsize;
-	int i, len, pos, status = 0, sndcmd = 0, steps = chip->ecc.steps;
+	int ret, i, len, pos, sndcmd = 0, steps = chip->ecc.steps;
 	const uint8_t *bufpoi = chip->oob_poi;
 
 	/*
@@ -2153,7 +2736,7 @@ int nand_write_oob_syndrome(struct mtd_info *mtd, struct nand_chip *chip,
 	} else
 		pos = eccsize;
 
-	chip->cmdfunc(mtd, NAND_CMD_SEQIN, pos, page);
+	nand_prog_page_begin_op(chip, page, pos, NULL, 0);
 	for (i = 0; i < steps; i++) {
 		if (sndcmd) {
 			if (mtd->writesize <= 512) {
@@ -2162,28 +2745,39 @@ int nand_write_oob_syndrome(struct mtd_info *mtd, struct nand_chip *chip,
 				len = eccsize;
 				while (len > 0) {
 					int num = min_t(int, len, 4);
-					chip->write_buf(mtd, (uint8_t *)&fill,
-							num);
+
+					ret = nand_write_data_op(chip, &fill,
+								 num, false);
+					if (ret)
+						return ret;
+
 					len -= num;
 				}
 			} else {
 				pos = eccsize + i * (eccsize + chunk);
-				chip->cmdfunc(mtd, NAND_CMD_RNDIN, pos, -1);
+				ret = nand_change_write_column_op(
+					chip, pos, NULL, 0, false);
+				if (ret)
+					return ret;
 			}
 		} else
 			sndcmd = 1;
 		len = min_t(int, length, chunk);
-		chip->write_buf(mtd, bufpoi, len);
+
+		ret = nand_write_data_op(chip, bufpoi, len, false);
+		if (ret)
+			return ret;
+
 		bufpoi += len;
 		length -= len;
 	}
-	if (length > 0)
-		chip->write_buf(mtd, bufpoi, length);
-
-	chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1);
-	status = chip->waitfunc(mtd, chip);
+	if (length > 0) {
+		ret = nand_write_data_op(chip, bufpoi, length, false);
+		if (ret)
+			return ret;
+	}
 
-	return status & NAND_STATUS_FAIL ? -EIO : 0;
+	return nand_prog_page_end_op(chip);
 }
 EXPORT_SYMBOL(nand_write_oob_syndrome);
 
@@ -2335,9 +2929,18 @@ static int nand_read_oob(struct mtd_info *mtd, loff_t from,
 int nand_write_page_raw(struct mtd_info *mtd, struct nand_chip *chip,
 			const uint8_t *buf, int oob_required, int page)
 {
-	chip->write_buf(mtd, buf, mtd->writesize);
-	if (oob_required)
-		chip->write_buf(mtd, chip->oob_poi, mtd->oobsize);
+	int ret;
+
+	ret = nand_write_data_op(chip, buf, mtd->writesize, false);
+	if (ret)
+		return ret;
+
+	if (oob_required) {
+		ret = nand_write_data_op(chip, chip->oob_poi, mtd->oobsize,
+					 false);
+		if (ret)
+			return ret;
+	}
 
 	return 0;
 }
@@ -2361,29 +2964,46 @@ static int nand_write_page_raw_syndrome(struct mtd_info *mtd,
 	int eccsize = chip->ecc.size;
 	int eccbytes = chip->ecc.bytes;
 	uint8_t *oob = chip->oob_poi;
-	int steps, size;
+	int steps, size, ret;
 
 	for (steps = chip->ecc.steps; steps > 0; steps--) {
-		chip->write_buf(mtd, buf, eccsize);
+		ret = nand_write_data_op(chip, buf, eccsize, false);
+		if (ret)
+			return ret;
+
 		buf += eccsize;
 
 		if (chip->ecc.prepad) {
-			chip->write_buf(mtd, oob, chip->ecc.prepad);
+			ret = nand_write_data_op(chip, oob, chip->ecc.prepad,
+						 false);
+			if (ret)
+				return ret;
+
 			oob += chip->ecc.prepad;
 		}
 
-		chip->write_buf(mtd, oob, eccbytes);
+		ret = nand_write_data_op(chip, oob, eccbytes, false);
+		if (ret)
+			return ret;
+
 		oob += eccbytes;
 
 		if (chip->ecc.postpad) {
-			chip->write_buf(mtd, oob, chip->ecc.postpad);
+			ret = nand_write_data_op(chip, oob, chip->ecc.postpad,
+						 false);
+			if (ret)
+				return ret;
+
 			oob += chip->ecc.postpad;
 		}
 	}
 
 	size = mtd->oobsize - (oob - chip->oob_poi);
-	if (size)
-		chip->write_buf(mtd, oob, size);
+	if (size) {
+		ret = nand_write_data_op(chip, oob, size, false);
+		if (ret)
+			return ret;
+	}
 
 	return 0;
 }
@@ -2437,7 +3057,11 @@ static int nand_write_page_hwecc(struct mtd_info *mtd, struct nand_chip *chip,
 
 	for (i = 0; eccsteps; eccsteps--, i += eccbytes, p += eccsize) {
 		chip->ecc.hwctl(mtd, NAND_ECC_WRITE);
-		chip->write_buf(mtd, p, eccsize);
+
+		ret = nand_write_data_op(chip, p, eccsize, false);
+		if (ret)
+			return ret;
+
 		chip->ecc.calculate(mtd, p, &ecc_calc[i]);
 	}
 
@@ -2446,7 +3070,9 @@ static int nand_write_page_hwecc(struct mtd_info *mtd, struct nand_chip *chip,
 	if (ret)
 		return ret;
 
-	chip->write_buf(mtd, chip->oob_poi, mtd->oobsize);
+	ret = nand_write_data_op(chip, chip->oob_poi, mtd->oobsize, false);
+	if (ret)
+		return ret;
 
 	return 0;
 }
@@ -2482,7 +3108,9 @@ static int nand_write_subpage_hwecc(struct mtd_info *mtd,
 		chip->ecc.hwctl(mtd, NAND_ECC_WRITE);
 
 		/* write data (untouched subpages already masked by 0xFF) */
-		chip->write_buf(mtd, buf, ecc_size);
+		ret = nand_write_data_op(chip, buf, ecc_size, false);
+		if (ret)
+			return ret;
 
 		/* mask ECC of un-touched subpages by padding 0xFF */
 		if ((step < start_step) || (step > end_step))
@@ -2509,7 +3137,9 @@ static int nand_write_subpage_hwecc(struct mtd_info *mtd,
 		return ret;
 
 	/* write OOB buffer to NAND device */
-	chip->write_buf(mtd, chip->oob_poi, mtd->oobsize);
+	ret = nand_write_data_op(chip, chip->oob_poi, mtd->oobsize, false);
+	if (ret)
+		return ret;
 
 	return 0;
 }
@@ -2536,31 +3166,49 @@ static int nand_write_page_syndrome(struct mtd_info *mtd,
 	int eccsteps = chip->ecc.steps;
 	const uint8_t *p = buf;
 	uint8_t *oob = chip->oob_poi;
+	int ret;
 
 	for (i = 0; eccsteps; eccsteps--, i += eccbytes, p += eccsize) {
-
 		chip->ecc.hwctl(mtd, NAND_ECC_WRITE);
-		chip->write_buf(mtd, p, eccsize);
+
+		ret = nand_write_data_op(chip, p, eccsize, false);
+		if (ret)
+			return ret;
 
 		if (chip->ecc.prepad) {
-			chip->write_buf(mtd, oob, chip->ecc.prepad);
+			ret = nand_write_data_op(chip, oob, chip->ecc.prepad,
+						 false);
+			if (ret)
+				return ret;
+
 			oob += chip->ecc.prepad;
 		}
 
 		chip->ecc.calculate(mtd, p, oob);
-		chip->write_buf(mtd, oob, eccbytes);
+
+		ret = nand_write_data_op(chip, oob, eccbytes, false);
+		if (ret)
+			return ret;
+
 		oob += eccbytes;
 
 		if (chip->ecc.postpad) {
-			chip->write_buf(mtd, oob, chip->ecc.postpad);
+			ret = nand_write_data_op(chip, oob, chip->ecc.postpad,
+						 false);
+			if (ret)
+				return ret;
+
 			oob += chip->ecc.postpad;
 		}
 	}
 
 	/* Calculate remaining oob bytes */
 	i = mtd->oobsize - (oob - chip->oob_poi);
-	if (i)
-		chip->write_buf(mtd, oob, i);
+	if (i) {
+		ret = nand_write_data_op(chip, oob, i, false);
+		if (ret)
+			return ret;
+	}
 
 	return 0;
 }
@@ -2588,8 +3236,11 @@ static int nand_write_page(struct mtd_info *mtd, struct nand_chip *chip,
 	else
 		subpage = 0;
 
-	if (nand_standard_page_accessors(&chip->ecc))
-		chip->cmdfunc(mtd, NAND_CMD_SEQIN, 0x00, page);
+	if (nand_standard_page_accessors(&chip->ecc)) {
+		status = nand_prog_page_begin_op(chip, page, 0, NULL, 0);
+		if (status)
+			return status;
+	}
 
 	if (unlikely(raw))
 		status = chip->ecc.write_page_raw(mtd, chip, buf,
@@ -2604,13 +3255,8 @@ static int nand_write_page(struct mtd_info *mtd, struct nand_chip *chip,
 	if (status < 0)
 		return status;
 
-	if (nand_standard_page_accessors(&chip->ecc)) {
-		chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1);
-
-		status = chip->waitfunc(mtd, chip);
-		if (status & NAND_STATUS_FAIL)
-			return -EIO;
-	}
+	if (nand_standard_page_accessors(&chip->ecc))
+		return nand_prog_page_end_op(chip);
 
 	return 0;
 }
@@ -2981,11 +3627,12 @@ static int nand_write_oob(struct mtd_info *mtd, loff_t to,
 static int single_erase(struct mtd_info *mtd, int page)
 {
 	struct nand_chip *chip = mtd_to_nand(mtd);
+	unsigned int eraseblock;
+
 	/* Send commands to erase a block */
-	chip->cmdfunc(mtd, NAND_CMD_ERASE1, -1, page);
-	chip->cmdfunc(mtd, NAND_CMD_ERASE2, -1, -1);
+	eraseblock = page >> (chip->phys_erase_shift - chip->page_shift);
 
-	return chip->waitfunc(mtd, chip);
+	return nand_erase_op(chip, eraseblock);
 }
 
 /**
@@ -3069,7 +3716,7 @@ int nand_erase_nand(struct mtd_info *mtd, struct erase_info *instr,
 		status = chip->erase(mtd, page & chip->pagemask);
 
 		/* See if block erase succeeded */
-		if (status & NAND_STATUS_FAIL) {
+		if (status) {
 			pr_debug("%s: failed erase, page 0x%08x\n",
 					__func__, page);
 			instr->state = MTD_ERASE_FAILED;
@@ -3212,22 +3859,12 @@ static int nand_max_bad_blocks(struct mtd_info *mtd, loff_t ofs, size_t len)
 static int nand_onfi_set_features(struct mtd_info *mtd, struct nand_chip *chip,
 			int addr, uint8_t *subfeature_param)
 {
-	int status;
-	int i;
-
 	if (!chip->onfi_version ||
 	    !(le16_to_cpu(chip->onfi_params.opt_cmd)
 	      & ONFI_OPT_CMD_SET_GET_FEATURES))
 		return -EINVAL;
 
-	chip->cmdfunc(mtd, NAND_CMD_SET_FEATURES, addr, -1);
-	for (i = 0; i < ONFI_SUBFEATURE_PARAM_LEN; ++i)
-		chip->write_byte(mtd, subfeature_param[i]);
-
-	status = chip->waitfunc(mtd, chip);
-	if (status & NAND_STATUS_FAIL)
-		return -EIO;
-	return 0;
+	return nand_set_features_op(chip, addr, subfeature_param);
 }
 
 /**
@@ -3240,17 +3877,12 @@ static int nand_onfi_set_features(struct mtd_info *mtd, struct nand_chip *chip,
 static int nand_onfi_get_features(struct mtd_info *mtd, struct nand_chip *chip,
 			int addr, uint8_t *subfeature_param)
 {
-	int i;
-
 	if (!chip->onfi_version ||
 	    !(le16_to_cpu(chip->onfi_params.opt_cmd)
 	      & ONFI_OPT_CMD_SET_GET_FEATURES))
 		return -EINVAL;
 
-	chip->cmdfunc(mtd, NAND_CMD_GET_FEATURES, addr, -1);
-	for (i = 0; i < ONFI_SUBFEATURE_PARAM_LEN; ++i)
-		*subfeature_param++ = chip->read_byte(mtd);
-	return 0;
+	return nand_get_features_op(chip, addr, subfeature_param);
 }
 
 /**
@@ -3393,12 +4025,11 @@ static u16 onfi_crc16(u16 crc, u8 const *p, size_t len)
 static int nand_flash_detect_ext_param_page(struct nand_chip *chip,
 					    struct nand_onfi_params *p)
 {
-	struct mtd_info *mtd = nand_to_mtd(chip);
 	struct onfi_ext_param_page *ep;
 	struct onfi_ext_section *s;
 	struct onfi_ext_ecc_info *ecc;
 	uint8_t *cursor;
-	int ret = -EINVAL;
+	int ret;
 	int len;
 	int i;
 
@@ -3408,14 +4039,18 @@ static int nand_flash_detect_ext_param_page(struct nand_chip *chip,
 		return -ENOMEM;
 
 	/* Send our own NAND_CMD_PARAM. */
-	chip->cmdfunc(mtd, NAND_CMD_PARAM, 0, -1);
+	ret = nand_read_param_page_op(chip, 0, NULL, 0);
+	if (ret)
+		return ret;
 
 	/* Use the Change Read Column command to skip the ONFI param pages. */
-	chip->cmdfunc(mtd, NAND_CMD_RNDOUT,
-			sizeof(*p) * p->num_of_param_pages , -1);
+	ret = nand_change_read_column_op(chip,
+					 sizeof(*p) * p->num_of_param_pages,
+					 ep, len, true);
+	if (ret)
+		return ret;
 
-	/* Read out the Extended Parameter Page. */
-	chip->read_buf(mtd, (uint8_t *)ep, len);
+	ret = -EINVAL;
 	if ((onfi_crc16(ONFI_CRC_BASE, ((uint8_t *)ep) + 2, len - 2)
 		!= le16_to_cpu(ep->crc))) {
 		pr_debug("fail in the CRC.\n");
@@ -3468,19 +4103,23 @@ static int nand_flash_detect_onfi(struct nand_chip *chip)
 {
 	struct mtd_info *mtd = nand_to_mtd(chip);
 	struct nand_onfi_params *p = &chip->onfi_params;
-	int i, j;
-	int val;
+	char id[4];
+	int i, ret, val;
 
 	/* Try ONFI for unknown chip or LP */
-	chip->cmdfunc(mtd, NAND_CMD_READID, 0x20, -1);
-	if (chip->read_byte(mtd) != 'O' || chip->read_byte(mtd) != 'N' ||
-		chip->read_byte(mtd) != 'F' || chip->read_byte(mtd) != 'I')
+	nand_readid_op(chip, 0x20, id, sizeof(id));
+	if (strncmp(id, "ONFI", 4))
 		return 0;
 
-	chip->cmdfunc(mtd, NAND_CMD_PARAM, 0, -1);
+	ret = nand_read_param_page_op(chip, 0, NULL, 0);
+	if (ret)
+		return ret;
+
 	for (i = 0; i < 3; i++) {
-		for (j = 0; j < sizeof(*p); j++)
-			((uint8_t *)p)[j] = chip->read_byte(mtd);
+		ret = nand_read_data_op(chip, p, sizeof(*p), true);
+		if (ret)
+			return ret;
+
 		if (onfi_crc16(ONFI_CRC_BASE, (uint8_t *)p, 254) ==
 				le16_to_cpu(p->crc)) {
 			break;
@@ -3571,20 +4210,22 @@ static int nand_flash_detect_jedec(struct nand_chip *chip)
 	struct mtd_info *mtd = nand_to_mtd(chip);
 	struct nand_jedec_params *p = &chip->jedec_params;
 	struct jedec_ecc_info *ecc;
-	int val;
-	int i, j;
+	char id[5];
+	int i, val, ret;
 
 	/* Try JEDEC for unknown chip or LP */
-	chip->cmdfunc(mtd, NAND_CMD_READID, 0x40, -1);
-	if (chip->read_byte(mtd) != 'J' || chip->read_byte(mtd) != 'E' ||
-		chip->read_byte(mtd) != 'D' || chip->read_byte(mtd) != 'E' ||
-		chip->read_byte(mtd) != 'C')
+	nand_readid_op(chip, 0x40, id, sizeof(id));
+	if (strncmp(id, "JEDEC", sizeof(id)))
 		return 0;
 
-	chip->cmdfunc(mtd, NAND_CMD_PARAM, 0x40, -1);
+	ret = nand_read_param_page_op(chip, 0x40, NULL, 0);
+	if (ret)
+		return ret;
+
 	for (i = 0; i < 3; i++) {
-		for (j = 0; j < sizeof(*p); j++)
-			((uint8_t *)p)[j] = chip->read_byte(mtd);
+		ret = nand_read_data_op(chip, p, sizeof(*p), true);
+		if (ret)
+			return ret;
 
 		if (onfi_crc16(ONFI_CRC_BASE, (uint8_t *)p, 510) ==
 				le16_to_cpu(p->crc))
@@ -3864,7 +4505,6 @@ static int nand_detect(struct nand_chip *chip, struct nand_flash_dev *type)
 	const struct nand_manufacturer *manufacturer;
 	struct mtd_info *mtd = nand_to_mtd(chip);
 	int busw;
-	int i;
 	u8 *id_data = chip->id.data;
 	u8 maf_id, dev_id;
 
@@ -3878,11 +4518,11 @@ static int nand_detect(struct nand_chip *chip, struct nand_flash_dev *type)
 	chip->select_chip(mtd, 0);
 
 	/* Send the command for reading device ID */
-	chip->cmdfunc(mtd, NAND_CMD_READID, 0x00, -1);
+	nand_readid_op(chip, 0, id_data, 2);
 
 	/* Read manufacturer and device IDs */
-	maf_id = chip->read_byte(mtd);
-	dev_id = chip->read_byte(mtd);
+	maf_id = id_data[0];
+	dev_id = id_data[1];
 
 	/*
 	 * Try again to make sure, as some systems the bus-hold or other
@@ -3891,11 +4531,8 @@ static int nand_detect(struct nand_chip *chip, struct nand_flash_dev *type)
 	 * not match, ignore the device completely.
 	 */
 
-	chip->cmdfunc(mtd, NAND_CMD_READID, 0x00, -1);
-
 	/* Read entire ID string */
-	for (i = 0; i < ARRAY_SIZE(chip->id.data); i++)
-		id_data[i] = chip->read_byte(mtd);
+	nand_readid_op(chip, 0, id_data, sizeof(chip->id.data));
 
 	if (id_data[0] != maf_id || id_data[1] != dev_id) {
 		pr_info("second ID read did not match %02x,%02x against %02x,%02x\n",
@@ -4222,15 +4859,16 @@ int nand_scan_ident(struct mtd_info *mtd, int maxchips,
 
 	/* Check for a chip array */
 	for (i = 1; i < maxchips; i++) {
+		u8 id[2];
+
 		/* See comment in nand_get_flash_type for reset */
 		nand_reset(chip, i);
 
 		chip->select_chip(mtd, i);
 		/* Send the command for reading device ID */
-		chip->cmdfunc(mtd, NAND_CMD_READID, 0x00, -1);
+		nand_readid_op(chip, 0, id, sizeof(id));
 		/* Read manufacturer and device IDs */
-		if (nand_maf_id != chip->read_byte(mtd) ||
-		    nand_dev_id != chip->read_byte(mtd)) {
+		if (nand_maf_id != id[0] || nand_dev_id != id[1]) {
 			chip->select_chip(mtd, -1);
 			break;
 		}
diff --git a/drivers/mtd/nand/nand_hynix.c b/drivers/mtd/nand/nand_hynix.c
index 985751eda317..04e3ab7a476c 100644
--- a/drivers/mtd/nand/nand_hynix.c
+++ b/drivers/mtd/nand/nand_hynix.c
@@ -67,15 +67,11 @@ struct hynix_read_retry_otp {
 
 static bool hynix_nand_has_valid_jedecid(struct nand_chip *chip)
 {
-	struct mtd_info *mtd = nand_to_mtd(chip);
-	u8 jedecid[6] = { };
-	int i = 0;
+	u8 jedecid[5] = { };
 
-	chip->cmdfunc(mtd, NAND_CMD_READID, 0x40, -1);
-	for (i = 0; i < 5; i++)
-		jedecid[i] = chip->read_byte(mtd);
+	nand_readid_op(chip, 0x40, jedecid, sizeof(jedecid));
 
-	return !strcmp("JEDEC", jedecid);
+	return !strncmp("JEDEC", jedecid, sizeof(jedecid));
 }
 
 static int hynix_nand_setup_read_retry(struct mtd_info *mtd, int retry_mode)
@@ -175,7 +171,7 @@ static int hynix_read_rr_otp(struct nand_chip *chip,
 	struct mtd_info *mtd = nand_to_mtd(chip);
 	int i;
 
-	chip->cmdfunc(mtd, NAND_CMD_RESET, -1, -1);
+	nand_reset_op(chip);
 
 	chip->cmdfunc(mtd, NAND_HYNIX_CMD_SET_PARAMS, -1, -1);
 
@@ -195,11 +191,10 @@ static int hynix_read_rr_otp(struct nand_chip *chip,
 	chip->cmdfunc(mtd, 0x19, -1, -1);
 
 	/* Now read the page */
-	chip->cmdfunc(mtd, NAND_CMD_READ0, 0x0, info->page);
-	chip->read_buf(mtd, buf, info->size);
+	nand_read_page_op(chip, info->page, 0, buf, info->size);
 
 	/* Put everything back to normal */
-	chip->cmdfunc(mtd, NAND_CMD_RESET, -1, -1);
+	nand_reset_op(chip);
 	chip->cmdfunc(mtd, NAND_HYNIX_CMD_SET_PARAMS, 0x38, -1);
 	chip->write_byte(mtd, 0x0);
 	chip->cmdfunc(mtd, NAND_HYNIX_CMD_APPLY_PARAMS, -1, -1);
diff --git a/drivers/mtd/nand/nand_micron.c b/drivers/mtd/nand/nand_micron.c
index abf6a3c376e8..3934539a7468 100644
--- a/drivers/mtd/nand/nand_micron.c
+++ b/drivers/mtd/nand/nand_micron.c
@@ -137,8 +137,6 @@ micron_nand_read_page_on_die_ecc(struct mtd_info *mtd, struct nand_chip *chip,
 	else if (status & NAND_STATUS_WRITE_RECOMMENDED)
 		max_bitflips = chip->ecc.strength;
 
-	chip->cmdfunc(mtd, NAND_CMD_READ0, -1, -1);
-
 	nand_read_page_raw(mtd, chip, buf, oob_required, page);
 
 	micron_nand_on_die_ecc_setup(chip, false);
@@ -155,10 +153,7 @@ micron_nand_write_page_on_die_ecc(struct mtd_info *mtd, struct nand_chip *chip,
 
 	micron_nand_on_die_ecc_setup(chip, true);
 
-	chip->cmdfunc(mtd, NAND_CMD_SEQIN, 0x00, page);
-	nand_write_page_raw(mtd, chip, buf, oob_required, page);
-	chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1);
-	status = chip->waitfunc(mtd, chip);
+	status = nand_write_page_raw(mtd, chip, buf, oob_required, page);
 
 	micron_nand_on_die_ecc_setup(chip, false);
 
@@ -171,7 +166,6 @@ micron_nand_read_page_raw_on_die_ecc(struct mtd_info *mtd,
 				     uint8_t *buf, int oob_required,
 				     int page)
 {
-	chip->cmdfunc(mtd, NAND_CMD_READ0, 0x00, page);
 	nand_read_page_raw(mtd, chip, buf, oob_required, page);
 
 	return 0;
@@ -183,14 +177,7 @@ micron_nand_write_page_raw_on_die_ecc(struct mtd_info *mtd,
 				      const uint8_t *buf, int oob_required,
 				      int page)
 {
-	int status;
-
-	chip->cmdfunc(mtd, NAND_CMD_SEQIN, 0x00, page);
-	nand_write_page_raw(mtd, chip, buf, oob_required, page);
-	chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1);
-	status = chip->waitfunc(mtd, chip);
-
-	return status & NAND_STATUS_FAIL ? -EIO : 0;
+	return nand_write_page_raw(mtd, chip, buf, oob_required, page);
 }
 
 enum {
diff --git a/drivers/mtd/nand/omap2.c b/drivers/mtd/nand/omap2.c
index 54540c8fa1a2..41e124777579 100644
--- a/drivers/mtd/nand/omap2.c
+++ b/drivers/mtd/nand/omap2.c
@@ -1538,10 +1538,10 @@ static int omap_read_page_bch(struct mtd_info *mtd, struct nand_chip *chip,
 	chip->read_buf(mtd, buf, mtd->writesize);
 
 	/* Read oob bytes */
-	chip->cmdfunc(mtd, NAND_CMD_RNDOUT,
-		      mtd->writesize + BADBLOCK_MARKER_LENGTH, -1);
-	chip->read_buf(mtd, chip->oob_poi + BADBLOCK_MARKER_LENGTH,
-		       chip->ecc.total);
+	nand_change_read_column_op(chip,
+				   mtd->writesize + BADBLOCK_MARKER_LENGTH,
+				   chip->oob_poi + BADBLOCK_MARKER_LENGTH,
+				   chip->ecc.total, false);
 
 	/* Calculate ecc bytes */
 	chip->ecc.calculate(mtd, buf, ecc_calc);
diff --git a/drivers/mtd/nand/pxa3xx_nand.c b/drivers/mtd/nand/pxa3xx_nand.c
index 90b9a9ccbe60..28bcdf64c1fc 100644
--- a/drivers/mtd/nand/pxa3xx_nand.c
+++ b/drivers/mtd/nand/pxa3xx_nand.c
@@ -520,15 +520,13 @@ static int pxa3xx_nand_init_timings_compat(struct pxa3xx_nand_host *host,
 	struct nand_chip *chip = &host->chip;
 	struct pxa3xx_nand_info *info = host->info_data;
 	const struct pxa3xx_nand_flash *f = NULL;
-	struct mtd_info *mtd = nand_to_mtd(&host->chip);
 	int i, id, ntypes;
+	u8 idbuf[2];
 
 	ntypes = ARRAY_SIZE(builtin_flash_types);
 
-	chip->cmdfunc(mtd, NAND_CMD_READID, 0x00, -1);
-
-	id = chip->read_byte(mtd);
-	id |= chip->read_byte(mtd) << 0x8;
+	nand_readid_op(chip, 0, idbuf, sizeof(idbuf));
+	id = idbuf[0] | (idbuf[1] << 8);
 
 	for (i = 0; i < ntypes; i++) {
 		f = &builtin_flash_types[i];
diff --git a/drivers/mtd/nand/qcom_nandc.c b/drivers/mtd/nand/qcom_nandc.c
index 2656c1ac5646..e34313ecd903 100644
--- a/drivers/mtd/nand/qcom_nandc.c
+++ b/drivers/mtd/nand/qcom_nandc.c
@@ -1990,7 +1990,7 @@ static int qcom_nandc_write_oob(struct mtd_info *mtd, struct nand_chip *chip,
 	struct nand_ecc_ctrl *ecc = &chip->ecc;
 	u8 *oob = chip->oob_poi;
 	int data_size, oob_size;
-	int ret, status = 0;
+	int ret;
 
 	host->use_ecc = true;
 
@@ -2027,11 +2027,7 @@ static int qcom_nandc_write_oob(struct mtd_info *mtd, struct nand_chip *chip,
 		return -EIO;
 	}
 
-	chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1);
-
-	status = chip->waitfunc(mtd, chip);
-
-	return status & NAND_STATUS_FAIL ? -EIO : 0;
+	return nand_prog_page_end_op(chip);
 }
 
 static int qcom_nandc_block_bad(struct mtd_info *mtd, loff_t ofs)
@@ -2081,7 +2077,7 @@ static int qcom_nandc_block_markbad(struct mtd_info *mtd, loff_t ofs)
 	struct qcom_nand_host *host = to_qcom_nand_host(chip);
 	struct qcom_nand_controller *nandc = get_qcom_nand_controller(chip);
 	struct nand_ecc_ctrl *ecc = &chip->ecc;
-	int page, ret, status = 0;
+	int page, ret;
 
 	clear_read_regs(nandc);
 	clear_bam_transaction(nandc);
@@ -2114,11 +2110,7 @@ static int qcom_nandc_block_markbad(struct mtd_info *mtd, loff_t ofs)
 		return -EIO;
 	}
 
-	chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1);
-
-	status = chip->waitfunc(mtd, chip);
-
-	return status & NAND_STATUS_FAIL ? -EIO : 0;
+	return nand_prog_page_end_op(chip);
 }
 
 /*
diff --git a/drivers/mtd/nand/r852.c b/drivers/mtd/nand/r852.c
index fc9287af4614..595635b9e9de 100644
--- a/drivers/mtd/nand/r852.c
+++ b/drivers/mtd/nand/r852.c
@@ -364,7 +364,7 @@ static int r852_wait(struct mtd_info *mtd, struct nand_chip *chip)
 	struct r852_device *dev = nand_get_controller_data(chip);
 
 	unsigned long timeout;
-	int status;
+	u8 status;
 
 	timeout = jiffies + (chip->state == FL_ERASING ?
 		msecs_to_jiffies(400) : msecs_to_jiffies(20));
@@ -373,8 +373,7 @@ static int r852_wait(struct mtd_info *mtd, struct nand_chip *chip)
 		if (chip->dev_ready(mtd))
 			break;
 
-	chip->cmdfunc(mtd, NAND_CMD_STATUS, -1, -1);
-	status = (int)chip->read_byte(mtd);
+	nand_status_op(chip, &status);
 
 	/* Unfortunelly, no way to send detailed error status... */
 	if (dev->dma_error) {
@@ -522,9 +521,7 @@ static int r852_ecc_correct(struct mtd_info *mtd, uint8_t *dat,
 static int r852_read_oob(struct mtd_info *mtd, struct nand_chip *chip,
 			     int page)
 {
-	chip->cmdfunc(mtd, NAND_CMD_READOOB, 0, page);
-	chip->read_buf(mtd, chip->oob_poi, mtd->oobsize);
-	return 0;
+	return nand_read_oob_op(chip, page, 0, chip->oob_poi, mtd->oobsize);
 }
 
 /*
@@ -1046,7 +1043,7 @@ static int r852_resume(struct device *device)
 	if (dev->card_registred) {
 		r852_engine_enable(dev);
 		dev->chip->select_chip(mtd, 0);
-		dev->chip->cmdfunc(mtd, NAND_CMD_RESET, -1, -1);
+		nand_reset_op(dev->chip);
 		dev->chip->select_chip(mtd, -1);
 	}
 
diff --git a/drivers/mtd/nand/sunxi_nand.c b/drivers/mtd/nand/sunxi_nand.c
index 82244be3e766..230d037d05d2 100644
--- a/drivers/mtd/nand/sunxi_nand.c
+++ b/drivers/mtd/nand/sunxi_nand.c
@@ -958,12 +958,12 @@ static int sunxi_nfc_hw_ecc_read_chunk(struct mtd_info *mtd,
 	int ret;
 
 	if (*cur_off != data_off)
-		nand->cmdfunc(mtd, NAND_CMD_RNDOUT, data_off, -1);
+		nand_change_read_column_op(nand, data_off, NULL, 0, false);
 
 	sunxi_nfc_randomizer_read_buf(mtd, NULL, ecc->size, false, page);
 
 	if (data_off + ecc->size != oob_off)
-		nand->cmdfunc(mtd, NAND_CMD_RNDOUT, oob_off, -1);
+		nand_change_read_column_op(nand, oob_off, NULL, 0, false);
 
 	ret = sunxi_nfc_wait_cmd_fifo_empty(nfc);
 	if (ret)
@@ -991,16 +991,15 @@ static int sunxi_nfc_hw_ecc_read_chunk(struct mtd_info *mtd,
 		 * Re-read the data with the randomizer disabled to identify
 		 * bitflips in erased pages.
 		 */
-		if (nand->options & NAND_NEED_SCRAMBLING) {
-			nand->cmdfunc(mtd, NAND_CMD_RNDOUT, data_off, -1);
-			nand->read_buf(mtd, data, ecc->size);
-		} else {
+		if (nand->options & NAND_NEED_SCRAMBLING)
+			nand_change_read_column_op(nand, data_off, data,
+						   ecc->size, false);
+		else
 			memcpy_fromio(data, nfc->regs + NFC_RAM0_BASE,
 				      ecc->size);
-		}
 
-		nand->cmdfunc(mtd, NAND_CMD_RNDOUT, oob_off, -1);
-		nand->read_buf(mtd, oob, ecc->bytes + 4);
+		nand_change_read_column_op(nand, oob_off, oob, ecc->bytes + 4,
+					   false);
 
 		ret = nand_check_erased_ecc_chunk(data,	ecc->size,
 						  oob, ecc->bytes + 4,
@@ -1011,7 +1010,8 @@ static int sunxi_nfc_hw_ecc_read_chunk(struct mtd_info *mtd,
 		memcpy_fromio(data, nfc->regs + NFC_RAM0_BASE, ecc->size);
 
 		if (oob_required) {
-			nand->cmdfunc(mtd, NAND_CMD_RNDOUT, oob_off, -1);
+			nand_change_read_column_op(nand, oob_off, NULL, 0,
+						   false);
 			sunxi_nfc_randomizer_read_buf(mtd, oob, ecc->bytes + 4,
 						      true, page);
 
@@ -1038,8 +1038,8 @@ static void sunxi_nfc_hw_ecc_read_extra_oob(struct mtd_info *mtd,
 		return;
 
 	if (!cur_off || *cur_off != offset)
-		nand->cmdfunc(mtd, NAND_CMD_RNDOUT,
-			      offset + mtd->writesize, -1);
+		nand_change_read_column_op(nand, mtd->writesize, NULL, 0,
+					   false);
 
 	if (!randomize)
 		sunxi_nfc_read_buf(mtd, oob + offset, len);
@@ -1116,9 +1116,9 @@ static int sunxi_nfc_hw_ecc_read_chunks_dma(struct mtd_info *mtd, uint8_t *buf,
 
 		if (oob_required && !erased) {
 			/* TODO: use DMA to retrieve OOB */
-			nand->cmdfunc(mtd, NAND_CMD_RNDOUT,
-				      mtd->writesize + oob_off, -1);
-			nand->read_buf(mtd, oob, ecc->bytes + 4);
+			nand_change_read_column_op(nand,
+						   mtd->writesize + oob_off,
+						   oob, ecc->bytes + 4, false);
 
 			sunxi_nfc_hw_ecc_get_prot_oob_bytes(mtd, oob, i,
 							    !i, page);
@@ -1143,18 +1143,17 @@ static int sunxi_nfc_hw_ecc_read_chunks_dma(struct mtd_info *mtd, uint8_t *buf,
 			/*
 			 * Re-read the data with the randomizer disabled to
 			 * identify bitflips in erased pages.
+			 * TODO: use DMA to read page in raw mode
 			 */
-			if (randomized) {
-				/* TODO: use DMA to read page in raw mode */
-				nand->cmdfunc(mtd, NAND_CMD_RNDOUT,
-					      data_off, -1);
-				nand->read_buf(mtd, data, ecc->size);
-			}
+			if (randomized)
+				nand_change_read_column_op(nand, data_off,
+							   data, ecc->size
+							   false);
 
 			/* TODO: use DMA to retrieve OOB */
-			nand->cmdfunc(mtd, NAND_CMD_RNDOUT,
-				      mtd->writesize + oob_off, -1);
-			nand->read_buf(mtd, oob, ecc->bytes + 4);
+			nand_change_read_column_op(nand,
+						   mtd->writesize + oob_off,
+						   oob, ecc->bytes + 4, false);
 
 			ret = nand_check_erased_ecc_chunk(data,	ecc->size,
 							  oob, ecc->bytes + 4,
@@ -1187,12 +1186,12 @@ static int sunxi_nfc_hw_ecc_write_chunk(struct mtd_info *mtd,
 	int ret;
 
 	if (data_off != *cur_off)
-		nand->cmdfunc(mtd, NAND_CMD_RNDIN, data_off, -1);
+		nand_change_write_column_op(nand, data_off, NULL, 0, false);
 
 	sunxi_nfc_randomizer_write_buf(mtd, data, ecc->size, false, page);
 
 	if (data_off + ecc->size != oob_off)
-		nand->cmdfunc(mtd, NAND_CMD_RNDIN, oob_off, -1);
+		nand_change_write_column_op(nand, oob_off, NULL, 0, false);
 
 	ret = sunxi_nfc_wait_cmd_fifo_empty(nfc);
 	if (ret)
@@ -1228,8 +1227,8 @@ static void sunxi_nfc_hw_ecc_write_extra_oob(struct mtd_info *mtd,
 		return;
 
 	if (!cur_off || *cur_off != offset)
-		nand->cmdfunc(mtd, NAND_CMD_RNDIN,
-			      offset + mtd->writesize, -1);
+		nand_change_write_column_op(nand, offset + mtd->writesize,
+					    NULL, 0, false);
 
 	sunxi_nfc_randomizer_write_buf(mtd, oob + offset, len, false, page);
 
@@ -1540,7 +1539,7 @@ static int sunxi_nfc_hw_common_ecc_read_oob(struct mtd_info *mtd,
 					    struct nand_chip *chip,
 					    int page)
 {
-	chip->cmdfunc(mtd, NAND_CMD_READ0, 0, page);
+	nand_read_page_op(chip, page, 0, NULL, 0);
 
 	chip->pagebuf = -1;
 
@@ -1551,9 +1550,9 @@ static int sunxi_nfc_hw_common_ecc_write_oob(struct mtd_info *mtd,
 					     struct nand_chip *chip,
 					     int page)
 {
-	int ret, status;
+	int ret;
 
-	chip->cmdfunc(mtd, NAND_CMD_SEQIN, 0, page);
+	nand_prog_page_begin_op(chip, page, 0, NULL, 0);
 
 	chip->pagebuf = -1;
 
@@ -1563,11 +1562,7 @@ static int sunxi_nfc_hw_common_ecc_write_oob(struct mtd_info *mtd,
 		return ret;
 
 	/* Send command to program the OOB data */
-	chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1);
-
-	status = chip->waitfunc(mtd, chip);
-
-	return status & NAND_STATUS_FAIL ? -EIO : 0;
+	return nand_prog_page_end_op(chip);
 }
 
 static const s32 tWB_lut[] = {6, 12, 16, 20};
diff --git a/drivers/mtd/nand/tango_nand.c b/drivers/mtd/nand/tango_nand.c
index 766906f03943..97a300b46b1d 100644
--- a/drivers/mtd/nand/tango_nand.c
+++ b/drivers/mtd/nand/tango_nand.c
@@ -329,7 +329,7 @@ static void aux_read(struct nand_chip *chip, u8 **buf, int len, int *pos)
 
 	if (!*buf) {
 		/* skip over "len" bytes */
-		chip->cmdfunc(mtd, NAND_CMD_RNDOUT, *pos, -1);
+		nand_change_read_column_op(chip, *pos, NULL, 0, false);
 	} else {
 		tango_read_buf(mtd, *buf, len);
 		*buf += len;
@@ -344,7 +344,7 @@ static void aux_write(struct nand_chip *chip, const u8 **buf, int len, int *pos)
 
 	if (!*buf) {
 		/* skip over "len" bytes */
-		chip->cmdfunc(mtd, NAND_CMD_RNDIN, *pos, -1);
+		nand_change_write_column_op(chip, *pos, NULL, 0, false);
 	} else {
 		tango_write_buf(mtd, *buf, len);
 		*buf += len;
@@ -427,7 +427,7 @@ static void raw_write(struct nand_chip *chip, const u8 *buf, const u8 *oob)
 static int tango_read_page_raw(struct mtd_info *mtd, struct nand_chip *chip,
 			       u8 *buf, int oob_required, int page)
 {
-	chip->cmdfunc(mtd, NAND_CMD_READ0, 0, page);
+	nand_read_page_op(chip, page, 0, NULL, 0);
 	raw_read(chip, buf, chip->oob_poi);
 	return 0;
 }
@@ -435,23 +435,15 @@ static int tango_read_page_raw(struct mtd_info *mtd, struct nand_chip *chip,
 static int tango_write_page_raw(struct mtd_info *mtd, struct nand_chip *chip,
 				const u8 *buf, int oob_required, int page)
 {
-	int status;
-
-	chip->cmdfunc(mtd, NAND_CMD_SEQIN, 0, page);
+	nand_prog_page_begin_op(chip, page, 0, NULL, 0);
 	raw_write(chip, buf, chip->oob_poi);
-	chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1);
-
-	status = chip->waitfunc(mtd, chip);
-	if (status & NAND_STATUS_FAIL)
-		return -EIO;
-
-	return 0;
+	return nand_prog_page_end_op(chip);
 }
 
 static int tango_read_oob(struct mtd_info *mtd, struct nand_chip *chip,
 			  int page)
 {
-	chip->cmdfunc(mtd, NAND_CMD_READ0, 0, page);
+	nand_read_page_op(chip, page, 0, NULL, 0);
 	raw_read(chip, NULL, chip->oob_poi);
 	return 0;
 }
@@ -459,11 +451,9 @@ static int tango_read_oob(struct mtd_info *mtd, struct nand_chip *chip,
 static int tango_write_oob(struct mtd_info *mtd, struct nand_chip *chip,
 			   int page)
 {
-	chip->cmdfunc(mtd, NAND_CMD_SEQIN, 0, page);
+	nand_prog_page_begin_op(chip, page, 0, NULL, 0);
 	raw_write(chip, NULL, chip->oob_poi);
-	chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1);
-	chip->waitfunc(mtd, chip);
-	return 0;
+	return nand_prog_page_end_op(chip);
 }
 
 static int oob_ecc(struct mtd_info *mtd, int idx, struct mtd_oob_region *res)
diff --git a/drivers/mtd/nand/tmio_nand.c b/drivers/mtd/nand/tmio_nand.c
index 84dbf32332e1..ae3583b092fb 100644
--- a/drivers/mtd/nand/tmio_nand.c
+++ b/drivers/mtd/nand/tmio_nand.c
@@ -192,6 +192,7 @@ tmio_nand_wait(struct mtd_info *mtd, struct nand_chip *nand_chip)
 {
 	struct tmio_nand *tmio = mtd_to_tmio(mtd);
 	long timeout;
+	u8 status;
 
 	/* enable RDYREQ interrupt */
 	tmio_iowrite8(0x0f, tmio->fcr + FCR_ISR);
@@ -212,8 +213,8 @@ tmio_nand_wait(struct mtd_info *mtd, struct nand_chip *nand_chip)
 		dev_warn(&tmio->dev->dev, "timeout waiting for interrupt\n");
 	}
 
-	nand_chip->cmdfunc(mtd, NAND_CMD_STATUS, -1, -1);
-	return nand_chip->read_byte(mtd);
+	nand_status_op(chip, &status)
+	return status;
 }
 
 /*
diff --git a/include/linux/mtd/rawnand.h b/include/linux/mtd/rawnand.h
index 749bb08c4772..8b8acde72465 100644
--- a/include/linux/mtd/rawnand.h
+++ b/include/linux/mtd/rawnand.h
@@ -1316,6 +1316,31 @@ int nand_write_page_raw(struct mtd_info *mtd, struct nand_chip *chip,
 /* Reset and initialize a NAND device */
 int nand_reset(struct nand_chip *chip, int chipnr);
 
+/* NAND operation helpers */
+int nand_reset_op(struct nand_chip *chip);
+int nand_readid_op(struct nand_chip *chip, u8 addr, void *buf,
+		   unsigned int len);
+int nand_status_op(struct nand_chip *chip, u8 *status);
+int nand_erase_op(struct nand_chip *chip, unsigned int eraseblock);
+int nand_read_page_op(struct nand_chip *chip, unsigned int page,
+		      unsigned int column, void *buf, unsigned int len);
+int nand_change_read_column_op(struct nand_chip *chip, unsigned int column,
+			       void *buf, unsigned int len, bool force_8bit);
+int nand_read_oob_op(struct nand_chip *chip, unsigned int page,
+		     unsigned int column, void *buf, unsigned int len);
+int nand_prog_page_begin_op(struct nand_chip *chip, unsigned int page,
+			    unsigned int column, const void *buf,
+			    unsigned int len);
+int nand_prog_page_end_op(struct nand_chip *chip);
+int nand_prog_page_op(struct nand_chip *chip, unsigned int page,
+		      unsigned int column, const void *buf, unsigned int len);
+int nand_change_write_column_op(struct nand_chip *chip, unsigned int column,
+				const void *buf, unsigned int len, bool force_8bit);
+int nand_read_data_op(struct nand_chip *chip, void *buf, unsigned int len,
+		      bool force_8bits);
+int nand_write_data_op(struct nand_chip *chip, const void *buf,
+		       unsigned int len, bool force_8bits);
+
 /* Free resources held by the NAND device */
 void nand_cleanup(struct nand_chip *chip);
 
-- 
2.11.0

^ permalink raw reply related	[flat|nested] 75+ messages in thread

* [RFC 02/12] mtd: nand: force drivers to explicitly send READ/PROG commands
  2017-10-18 14:36 ` Miquel Raynal
  (?)
@ 2017-10-18 14:36   ` Miquel Raynal
  -1 siblings, 0 replies; 75+ messages in thread
From: Miquel Raynal @ 2017-10-18 14:36 UTC (permalink / raw)
  To: Andrew Lunn, bcm-kernel-feedback-list, Boris Brezillon,
	Brian Norris, Catalin Marinas, Chen-Yu Tsai, Cyrille Pitchen,
	Daniel Mack, David Woodhouse, devel, devicetree, Ezequiel Garcia,
	Greg Kroah-Hartman, Gregory Clement, Han Xu, Haojian Zhuang,
	Jason Cooper, Josh Wu, Kamal Dasu, linux-arm-kernel,
	linux-kernel, linux-mediatek, linux-mtd, Marc Gonzalez,
	Marek Vasut, Mark Rutland, Masahiro Yamada, Matthias Brugger,
	Maxime Ripard, Maxim Levitsky, Richard Weinberger,
	Robert Jarzmik, Rob Herring, Russell King, Sebastian Hesselbarth,
	Stefan Agner, Sylvain Lemieux, Vladimir Zapolskiy, Wenyou Yang,
	Will Deacon
  Cc: Thomas Petazzoni, Antoine Tenart, Miquel Raynal, Igor Grinberg,
	Nadav Haklai, Ofer Heifetz, Neta Zur Hershkovits, Hanna Hawa

From: Boris Brezillon <boris.brezillon@free-electrons.com>

The core currently send the READ0 and SEQIN+PAGEPROG commands in
nand_do_read/write_ops(). This is inconsistent with
->read/write_oob[_raw]() hooks behavior which are expected to send
these commands.

There's already a flag (NAND_ECC_CUSTOM_PAGE_ACCESS) to inform the core
that a specific controller wants to send the READ/SEQIN+PAGEPROG
commands on its own, but it's an opt-in flag, and existing drivers are
unlikely to be updated to pass it.

Moreover, some controllers cannot dissociate the READ/PAGEPROG commands
from the associated data transfer and ECC engine activation, and
developers have to hack things in their ->cmdfunc() implementation to
handle such complex cases, or have to accept the perf penalty of sending
twice the same command.
To address this problem we are planning on adding a new interface which
is passed all information about a NAND operation (including the amount
of data to transfer) and replacing all calls to ->cmdfunc() to calls to
this new ->exec_op() hook. But, in order to do that, we need to have all
->cmdfunc() calls placed near their associated ->read/write_buf/byte()
calls.

Modify the core and relevant drivers to make NAND_ECC_CUSTOM_PAGE_ACCESS
the default case, and remove this flag.

Signed-off-by: Boris Brezillon <boris.brezillon@free-electrons.com>
[miquel.raynal@free-electrons.com: rebased and fixed some conflicts]
Signed-off-by: Miquel Raynal <miquel.raynal@free-electrons.com>
---
 drivers/mtd/nand/atmel/nand-controller.c      |  7 ++-
 drivers/mtd/nand/bf5xx_nand.c                 |  6 ++-
 drivers/mtd/nand/brcmnand/brcmnand.c          | 13 +++--
 drivers/mtd/nand/cafe_nand.c                  |  6 +--
 drivers/mtd/nand/denali.c                     | 14 +++++-
 drivers/mtd/nand/docg4.c                      | 12 +++--
 drivers/mtd/nand/fsl_elbc_nand.c              |  6 +--
 drivers/mtd/nand/fsl_ifc_nand.c               |  6 +--
 drivers/mtd/nand/gpmi-nand/gpmi-nand.c        | 30 ++++++------
 drivers/mtd/nand/hisi504_nand.c               |  6 +--
 drivers/mtd/nand/lpc32xx_mlc.c                |  5 +-
 drivers/mtd/nand/lpc32xx_slc.c                | 11 +++--
 drivers/mtd/nand/mtk_nand.c                   | 10 ++--
 drivers/mtd/nand/nand_base.c                  | 68 +++++++++------------------
 drivers/mtd/nand/nand_micron.c                |  1 -
 drivers/mtd/nand/sh_flctl.c                   |  6 +--
 drivers/mtd/nand/sunxi_nand.c                 | 34 +++++++++-----
 drivers/mtd/nand/tango_nand.c                 |  1 -
 drivers/mtd/nand/vf610_nfc.c                  |  6 +--
 drivers/staging/mt29f_spinand/mt29f_spinand.c |  7 ++-
 include/linux/mtd/rawnand.h                   | 11 -----
 21 files changed, 142 insertions(+), 124 deletions(-)

diff --git a/drivers/mtd/nand/atmel/nand-controller.c b/drivers/mtd/nand/atmel/nand-controller.c
index 0b2c82727b24..6add993ea1e0 100644
--- a/drivers/mtd/nand/atmel/nand-controller.c
+++ b/drivers/mtd/nand/atmel/nand-controller.c
@@ -841,6 +841,8 @@ static int atmel_nand_pmecc_write_pg(struct nand_chip *chip, const u8 *buf,
 	struct atmel_nand *nand = to_atmel_nand(chip);
 	int ret;
 
+	nand_prog_page_begin_op(chip, page, 0, NULL, 0);
+
 	ret = atmel_nand_pmecc_enable(chip, NAND_ECC_WRITE, raw);
 	if (ret)
 		return ret;
@@ -857,7 +859,7 @@ static int atmel_nand_pmecc_write_pg(struct nand_chip *chip, const u8 *buf,
 
 	atmel_nand_write_buf(mtd, chip->oob_poi, mtd->oobsize);
 
-	return 0;
+	return nand_prog_page_end_op(chip);
 }
 
 static int atmel_nand_pmecc_write_page(struct mtd_info *mtd,
@@ -881,6 +883,8 @@ static int atmel_nand_pmecc_read_pg(struct nand_chip *chip, u8 *buf,
 	struct mtd_info *mtd = nand_to_mtd(chip);
 	int ret;
 
+	nand_read_page_op(chip, page, 0, NULL, 0);
+
 	ret = atmel_nand_pmecc_enable(chip, NAND_ECC_READ, raw);
 	if (ret)
 		return ret;
@@ -1178,7 +1182,6 @@ static int atmel_hsmc_nand_ecc_init(struct atmel_nand *nand)
 	chip->ecc.write_page = atmel_hsmc_nand_pmecc_write_page;
 	chip->ecc.read_page_raw = atmel_hsmc_nand_pmecc_read_page_raw;
 	chip->ecc.write_page_raw = atmel_hsmc_nand_pmecc_write_page_raw;
-	chip->ecc.options |= NAND_ECC_CUSTOM_PAGE_ACCESS;
 
 	return 0;
 }
diff --git a/drivers/mtd/nand/bf5xx_nand.c b/drivers/mtd/nand/bf5xx_nand.c
index 5655dca6ce43..87bbd177b3e5 100644
--- a/drivers/mtd/nand/bf5xx_nand.c
+++ b/drivers/mtd/nand/bf5xx_nand.c
@@ -572,6 +572,8 @@ static void bf5xx_nand_dma_write_buf(struct mtd_info *mtd,
 static int bf5xx_nand_read_page_raw(struct mtd_info *mtd, struct nand_chip *chip,
 		uint8_t *buf, int oob_required, int page)
 {
+	nand_read_page_op(chip, page, 0, NULL, 0);
+
 	bf5xx_nand_read_buf(mtd, buf, mtd->writesize);
 	bf5xx_nand_read_buf(mtd, chip->oob_poi, mtd->oobsize);
 
@@ -582,10 +584,10 @@ static int bf5xx_nand_write_page_raw(struct mtd_info *mtd,
 		struct nand_chip *chip,	const uint8_t *buf, int oob_required,
 		int page)
 {
-	bf5xx_nand_write_buf(mtd, buf, mtd->writesize);
+	nand_prog_page_begin_op(chip, page, 0, buf, mtd->writesize);
 	bf5xx_nand_write_buf(mtd, chip->oob_poi, mtd->oobsize);
 
-	return 0;
+	return nand_prog_page_end_op(chip);
 }
 
 /*
diff --git a/drivers/mtd/nand/brcmnand/brcmnand.c b/drivers/mtd/nand/brcmnand/brcmnand.c
index 3f441096a14c..e6879d4d53ca 100644
--- a/drivers/mtd/nand/brcmnand/brcmnand.c
+++ b/drivers/mtd/nand/brcmnand/brcmnand.c
@@ -1689,7 +1689,6 @@ static int brcmstb_nand_verify_erased_page(struct mtd_info *mtd,
 	sas = mtd->oobsize / chip->ecc.steps;
 
 	/* read without ecc for verification */
-	nand_read_page_op(chip, page, 0, NULL, 0);
 	ret = chip->ecc.read_page_raw(mtd, chip, buf, true, page);
 	if (ret)
 		return ret;
@@ -1793,6 +1792,8 @@ static int brcmnand_read_page(struct mtd_info *mtd, struct nand_chip *chip,
 	struct brcmnand_host *host = nand_get_controller_data(chip);
 	u8 *oob = oob_required ? (u8 *)chip->oob_poi : NULL;
 
+	nand_read_page_op(chip, page, 0, NULL, 0);
+
 	return brcmnand_read(mtd, chip, host->last_addr,
 			mtd->writesize >> FC_SHIFT, (u32 *)buf, oob);
 }
@@ -1804,6 +1805,8 @@ static int brcmnand_read_page_raw(struct mtd_info *mtd, struct nand_chip *chip,
 	u8 *oob = oob_required ? (u8 *)chip->oob_poi : NULL;
 	int ret;
 
+	nand_read_page_op(chip, page, 0, NULL, 0);
+
 	brcmnand_set_ecc_enabled(host, 0);
 	ret = brcmnand_read(mtd, chip, host->last_addr,
 			mtd->writesize >> FC_SHIFT, (u32 *)buf, oob);
@@ -1909,8 +1912,10 @@ static int brcmnand_write_page(struct mtd_info *mtd, struct nand_chip *chip,
 	struct brcmnand_host *host = nand_get_controller_data(chip);
 	void *oob = oob_required ? chip->oob_poi : NULL;
 
+	nand_prog_page_begin_op(chip, page, 0, NULL, 0);
 	brcmnand_write(mtd, chip, host->last_addr, (const u32 *)buf, oob);
-	return 0;
+
+	return nand_prog_page_end_op(chip);
 }
 
 static int brcmnand_write_page_raw(struct mtd_info *mtd,
@@ -1920,10 +1925,12 @@ static int brcmnand_write_page_raw(struct mtd_info *mtd,
 	struct brcmnand_host *host = nand_get_controller_data(chip);
 	void *oob = oob_required ? chip->oob_poi : NULL;
 
+	nand_prog_page_begin_op(chip, page, 0, NULL, 0);
 	brcmnand_set_ecc_enabled(host, 0);
 	brcmnand_write(mtd, chip, host->last_addr, (const u32 *)buf, oob);
 	brcmnand_set_ecc_enabled(host, 1);
-	return 0;
+
+	return nand_prog_page_end_op(chip);
 }
 
 static int brcmnand_write_oob(struct mtd_info *mtd, struct nand_chip *chip,
diff --git a/drivers/mtd/nand/cafe_nand.c b/drivers/mtd/nand/cafe_nand.c
index 95c2cfa68b66..de36762e3058 100644
--- a/drivers/mtd/nand/cafe_nand.c
+++ b/drivers/mtd/nand/cafe_nand.c
@@ -383,7 +383,7 @@ static int cafe_nand_read_page(struct mtd_info *mtd, struct nand_chip *chip,
 		     cafe_readl(cafe, NAND_ECC_RESULT),
 		     cafe_readl(cafe, NAND_ECC_SYN01));
 
-	chip->read_buf(mtd, buf, mtd->writesize);
+	nand_read_page_op(chip, page, 0, buf, mtd->writesize);
 	chip->read_buf(mtd, chip->oob_poi, mtd->oobsize);
 
 	if (checkecc && cafe_readl(cafe, NAND_ECC_RESULT) & (1<<18)) {
@@ -541,13 +541,13 @@ static int cafe_nand_write_page_lowlevel(struct mtd_info *mtd,
 {
 	struct cafe_priv *cafe = nand_get_controller_data(chip);
 
-	chip->write_buf(mtd, buf, mtd->writesize);
+	nand_prog_page_begin_op(chip, page, 0, buf, mtd->writesize);
 	chip->write_buf(mtd, chip->oob_poi, mtd->oobsize);
 
 	/* Set up ECC autogeneration */
 	cafe->ctl2 |= (1<<30);
 
-	return 0;
+	return nand_prog_page_end_op(chip);
 }
 
 static int cafe_nand_block_bad(struct mtd_info *mtd, loff_t ofs)
diff --git a/drivers/mtd/nand/denali.c b/drivers/mtd/nand/denali.c
index f90f93a8fb08..e5f38359f6df 100644
--- a/drivers/mtd/nand/denali.c
+++ b/drivers/mtd/nand/denali.c
@@ -550,11 +550,14 @@ static int denali_pio_read(struct denali_nand_info *denali, void *buf,
 static int denali_pio_write(struct denali_nand_info *denali,
 			    const void *buf, size_t size, int page, int raw)
 {
+	struct nand_chip *chip = &denali->nand;
 	u32 addr = DENALI_MAP01 | DENALI_BANK(denali) | page;
 	const uint32_t *buf32 = (uint32_t *)buf;
 	uint32_t irq_status;
 	int i;
 
+	nand_prog_page_begin_op(chip, page, 0, NULL, 0);
+
 	denali_reset_irq(denali);
 
 	for (i = 0; i < size / 4; i++)
@@ -580,6 +583,7 @@ static int denali_pio_xfer(struct denali_nand_info *denali, void *buf,
 static int denali_dma_xfer(struct denali_nand_info *denali, void *buf,
 			   size_t size, int page, int raw, int write)
 {
+	struct nand_chip *chip = &denali->nand;
 	dma_addr_t dma_addr;
 	uint32_t irq_mask, irq_status, ecc_err_mask;
 	enum dma_data_direction dir = write ? DMA_TO_DEVICE : DMA_FROM_DEVICE;
@@ -625,7 +629,10 @@ static int denali_dma_xfer(struct denali_nand_info *denali, void *buf,
 	if (irq_status & INTR__ERASED_PAGE)
 		memset(buf, 0xff, size);
 
-	return ret;
+	if (ret)
+		return ret;
+
+	return nand_prog_page_end_op(chip);
 }
 
 static int denali_data_xfer(struct denali_nand_info *denali, void *buf,
@@ -790,6 +797,8 @@ static int denali_write_oob(struct mtd_info *mtd, struct nand_chip *chip,
 	struct denali_nand_info *denali = mtd_to_denali(mtd);
 	int status;
 
+	nand_read_page_op(chip, page, 0, NULL, 0);
+
 	denali_reset_irq(denali);
 
 	denali_oob_xfer(mtd, chip, page, 1);
@@ -846,6 +855,8 @@ static int denali_write_page_raw(struct mtd_info *mtd, struct nand_chip *chip,
 	size_t size = writesize + oobsize;
 	int i, pos, len;
 
+	nand_read_page_op(chip, page, 0, NULL, 0);
+
 	/*
 	 * Fill the buffer with 0xff first except the full page transfer.
 	 * This simplifies the logic.
@@ -1359,7 +1370,6 @@ int denali_init(struct denali_nand_info *denali)
 		chip->read_buf = denali_read_buf;
 		chip->write_buf = denali_write_buf;
 	}
-	chip->ecc.options |= NAND_ECC_CUSTOM_PAGE_ACCESS;
 	chip->ecc.read_page = denali_read_page;
 	chip->ecc.read_page_raw = denali_read_page_raw;
 	chip->ecc.write_page = denali_write_page;
diff --git a/drivers/mtd/nand/docg4.c b/drivers/mtd/nand/docg4.c
index 45c01b4b34c7..156fdf683b2f 100644
--- a/drivers/mtd/nand/docg4.c
+++ b/drivers/mtd/nand/docg4.c
@@ -785,6 +785,8 @@ static int read_page(struct mtd_info *mtd, struct nand_chip *nand,
 
 	dev_dbg(doc->dev, "%s: page %08x\n", __func__, page);
 
+	nand_read_page_op(nand, page, 0, NULL, 0);
+
 	writew(DOC_ECCCONF0_READ_MODE |
 	       DOC_ECCCONF0_ECC_ENABLE |
 	       DOC_ECCCONF0_UNKNOWN |
@@ -948,7 +950,7 @@ static int docg4_erase_block(struct mtd_info *mtd, int page)
 }
 
 static int write_page(struct mtd_info *mtd, struct nand_chip *nand,
-		       const uint8_t *buf, bool use_ecc)
+		      const uint8_t *buf, int page, bool use_ecc)
 {
 	struct docg4_priv *doc = nand_get_controller_data(nand);
 	void __iomem *docptr = doc->virtadr;
@@ -956,6 +958,8 @@ static int write_page(struct mtd_info *mtd, struct nand_chip *nand,
 
 	dev_dbg(doc->dev, "%s...\n", __func__);
 
+	nand_prog_page_begin_op(nand, page, 0, NULL, 0);
+
 	writew(DOC_ECCCONF0_ECC_ENABLE |
 	       DOC_ECCCONF0_UNKNOWN |
 	       DOCG4_BCH_SIZE,
@@ -1000,19 +1004,19 @@ static int write_page(struct mtd_info *mtd, struct nand_chip *nand,
 	writew(0, docptr + DOC_DATAEND);
 	write_nop(docptr);
 
-	return 0;
+	return nand_prog_page_end_op(nand);
 }
 
 static int docg4_write_page_raw(struct mtd_info *mtd, struct nand_chip *nand,
 				const uint8_t *buf, int oob_required, int page)
 {
-	return write_page(mtd, nand, buf, false);
+	return write_page(mtd, nand, buf, page, false);
 }
 
 static int docg4_write_page(struct mtd_info *mtd, struct nand_chip *nand,
 			     const uint8_t *buf, int oob_required, int page)
 {
-	return write_page(mtd, nand, buf, true);
+	return write_page(mtd, nand, buf, page, true);
 }
 
 static int docg4_write_oob(struct mtd_info *mtd, struct nand_chip *nand,
diff --git a/drivers/mtd/nand/fsl_elbc_nand.c b/drivers/mtd/nand/fsl_elbc_nand.c
index 17db2f90aa2c..a24c308a0b8a 100644
--- a/drivers/mtd/nand/fsl_elbc_nand.c
+++ b/drivers/mtd/nand/fsl_elbc_nand.c
@@ -713,7 +713,7 @@ static int fsl_elbc_read_page(struct mtd_info *mtd, struct nand_chip *chip,
 	struct fsl_lbc_ctrl *ctrl = priv->ctrl;
 	struct fsl_elbc_fcm_ctrl *elbc_fcm_ctrl = ctrl->nand;
 
-	fsl_elbc_read_buf(mtd, buf, mtd->writesize);
+	nand_read_page_op(chip, page, 0, buf, mtd->writesize);
 	if (oob_required)
 		fsl_elbc_read_buf(mtd, chip->oob_poi, mtd->oobsize);
 
@@ -729,10 +729,10 @@ static int fsl_elbc_read_page(struct mtd_info *mtd, struct nand_chip *chip,
 static int fsl_elbc_write_page(struct mtd_info *mtd, struct nand_chip *chip,
 				const uint8_t *buf, int oob_required, int page)
 {
-	fsl_elbc_write_buf(mtd, buf, mtd->writesize);
+	nand_prog_page_begin_op(chip, page, 0, buf, mtd->writesize);
 	fsl_elbc_write_buf(mtd, chip->oob_poi, mtd->oobsize);
 
-	return 0;
+	return nand_prog_page_end_op(chip);
 }
 
 /* ECC will be calculated automatically, and errors will be detected in
diff --git a/drivers/mtd/nand/fsl_ifc_nand.c b/drivers/mtd/nand/fsl_ifc_nand.c
index 9e03bac7f34c..857038fe874f 100644
--- a/drivers/mtd/nand/fsl_ifc_nand.c
+++ b/drivers/mtd/nand/fsl_ifc_nand.c
@@ -688,7 +688,7 @@ static int fsl_ifc_read_page(struct mtd_info *mtd, struct nand_chip *chip,
 	struct fsl_ifc_ctrl *ctrl = priv->ctrl;
 	struct fsl_ifc_nand_ctrl *nctrl = ifc_nand_ctrl;
 
-	fsl_ifc_read_buf(mtd, buf, mtd->writesize);
+	nand_read_page_op(chip, page, 0, buf, mtd->writesize);
 	if (oob_required)
 		fsl_ifc_read_buf(mtd, chip->oob_poi, mtd->oobsize);
 
@@ -711,10 +711,10 @@ static int fsl_ifc_read_page(struct mtd_info *mtd, struct nand_chip *chip,
 static int fsl_ifc_write_page(struct mtd_info *mtd, struct nand_chip *chip,
 			       const uint8_t *buf, int oob_required, int page)
 {
-	fsl_ifc_write_buf(mtd, buf, mtd->writesize);
+	nand_prog_page_begin_op(chip, page, 0, buf, mtd->writesize);
 	fsl_ifc_write_buf(mtd, chip->oob_poi, mtd->oobsize);
 
-	return 0;
+	return nand_prog_page_end_op(chip);
 }
 
 static int fsl_ifc_chip_init_tail(struct mtd_info *mtd)
diff --git a/drivers/mtd/nand/gpmi-nand/gpmi-nand.c b/drivers/mtd/nand/gpmi-nand/gpmi-nand.c
index c8ceaecd8065..3f2b903158c1 100644
--- a/drivers/mtd/nand/gpmi-nand/gpmi-nand.c
+++ b/drivers/mtd/nand/gpmi-nand/gpmi-nand.c
@@ -1043,6 +1043,8 @@ static int gpmi_ecc_read_page(struct mtd_info *mtd, struct nand_chip *chip,
 	unsigned int  max_bitflips = 0;
 	int           ret;
 
+	nand_read_page_op(chip, page, 0, NULL, 0);
+
 	dev_dbg(this->dev, "page number is : %d\n", page);
 	ret = read_page_prepare(this, buf, nfc_geo->payload_size,
 					this->payload_virt, this->payload_phys,
@@ -1220,12 +1222,13 @@ static int gpmi_ecc_read_subpage(struct mtd_info *mtd, struct nand_chip *chip,
 	meta = geo->metadata_size;
 	if (first) {
 		col = meta + (size + ecc_parity_size) * first;
-		chip->cmdfunc(mtd, NAND_CMD_RNDOUT, col, -1);
 
 		meta = 0;
 		buf = buf + first * size;
 	}
 
+	nand_read_page_op(chip, page, col, NULL, 0);
+
 	/* Save the old environment */
 	r1_old = r1_new = readl(bch_regs + HW_BCH_FLASH0LAYOUT0);
 	r2_old = r2_new = readl(bch_regs + HW_BCH_FLASH0LAYOUT1);
@@ -1277,6 +1280,9 @@ static int gpmi_ecc_write_page(struct mtd_info *mtd, struct nand_chip *chip,
 	int        ret;
 
 	dev_dbg(this->dev, "ecc write page.\n");
+
+	nand_prog_page_begin_op(chip, page, 0, NULL, 0);
+
 	if (this->swap_block_mark) {
 		/*
 		 * If control arrives here, we're doing block mark swapping.
@@ -1338,7 +1344,10 @@ static int gpmi_ecc_write_page(struct mtd_info *mtd, struct nand_chip *chip,
 				payload_virt, payload_phys);
 	}
 
-	return 0;
+	if (ret)
+		return ret;
+
+	return nand_prog_page_end_op(chip);
 }
 
 /*
@@ -1472,8 +1481,8 @@ static int gpmi_ecc_read_page_raw(struct mtd_info *mtd,
 	uint8_t *oob = chip->oob_poi;
 	int step;
 
-	chip->read_buf(mtd, tmp_buf,
-		       mtd->writesize + mtd->oobsize);
+	nand_read_page_op(chip, page, 0, tmp_buf,
+			  mtd->writesize + mtd->oobsize);
 
 	/*
 	 * If required, swap the bad block marker and the data stored in the
@@ -1617,24 +1626,19 @@ static int gpmi_ecc_write_page_raw(struct mtd_info *mtd,
 		tmp_buf[mtd->writesize] = swap;
 	}
 
-	chip->write_buf(mtd, tmp_buf, mtd->writesize + mtd->oobsize);
-
-	return 0;
+	return nand_prog_page_op(chip, page, 0, tmp_buf,
+				 mtd->writesize + mtd->oobsize);
 }
 
 static int gpmi_ecc_read_oob_raw(struct mtd_info *mtd, struct nand_chip *chip,
 				 int page)
 {
-	nand_read_page_op(chip, page, 0, NULL, 0);
-
 	return gpmi_ecc_read_page_raw(mtd, chip, NULL, 1, page);
 }
 
 static int gpmi_ecc_write_oob_raw(struct mtd_info *mtd, struct nand_chip *chip,
 				 int page)
 {
-	nand_prog_page_begin_op(chip, page, 0, NULL, 0);
-
 	return gpmi_ecc_write_page_raw(mtd, chip, NULL, 1, page);
 }
 
@@ -1806,9 +1810,7 @@ static int mx23_write_transcription_stamp(struct gpmi_nand_data *this)
 		/* Write the first page of the current stride. */
 		dev_dbg(dev, "Writing an NCB fingerprint in page 0x%x\n", page);
 
-		nand_prog_page_begin_op(chip, page, 0, NULL, 0);
-		chip->ecc.write_page_raw(mtd, chip, buffer, 0, page);
-		status = nand_prog_page_end_op(chip);
+		status = chip->ecc.write_page_raw(mtd, chip, buffer, 0, page);
 		if (status)
 			dev_err(dev, "[%s] Write failed.\n", __func__);
 	}
diff --git a/drivers/mtd/nand/hisi504_nand.c b/drivers/mtd/nand/hisi504_nand.c
index 184d765c8bbe..cb862793ab6d 100644
--- a/drivers/mtd/nand/hisi504_nand.c
+++ b/drivers/mtd/nand/hisi504_nand.c
@@ -544,7 +544,7 @@ static int hisi_nand_read_page_hwecc(struct mtd_info *mtd,
 	int max_bitflips = 0, stat = 0, stat_max = 0, status_ecc;
 	int stat_1, stat_2;
 
-	chip->read_buf(mtd, buf, mtd->writesize);
+	nand_read_page_op(chip, page, 0, buf, mtd->writesize);
 	chip->read_buf(mtd, chip->oob_poi, mtd->oobsize);
 
 	/* errors which can not be corrected by ECC */
@@ -589,11 +589,11 @@ static int hisi_nand_write_page_hwecc(struct mtd_info *mtd,
 		struct nand_chip *chip, const uint8_t *buf, int oob_required,
 		int page)
 {
-	chip->write_buf(mtd, buf, mtd->writesize);
+	nand_prog_page_begin_op(chip, page, 0, buf, mtd->writesize);
 	if (oob_required)
 		chip->write_buf(mtd, chip->oob_poi, mtd->oobsize);
 
-	return 0;
+	return nand_prog_page_end_op(chip);
 }
 
 static void hisi_nfc_host_init(struct hinfc_host *host)
diff --git a/drivers/mtd/nand/lpc32xx_mlc.c b/drivers/mtd/nand/lpc32xx_mlc.c
index 0723ded232ff..fcab1900a764 100644
--- a/drivers/mtd/nand/lpc32xx_mlc.c
+++ b/drivers/mtd/nand/lpc32xx_mlc.c
@@ -522,6 +522,8 @@ static int lpc32xx_write_page_lowlevel(struct mtd_info *mtd,
 		memcpy(dma_buf, buf, mtd->writesize);
 	}
 
+	nand_prog_page_begin_op(chip, page, 0, NULL, 0);
+
 	for (i = 0; i < host->mlcsubpages; i++) {
 		/* Start Encode */
 		writeb(0x00, MLC_ECC_ENC_REG(host->io_base));
@@ -550,7 +552,8 @@ static int lpc32xx_write_page_lowlevel(struct mtd_info *mtd,
 		/* Wait for Controller Ready */
 		lpc32xx_waitfunc_controller(mtd, chip);
 	}
-	return 0;
+
+	return nand_prog_page_end_op(chip);
 }
 
 static int lpc32xx_read_oob(struct mtd_info *mtd, struct nand_chip *chip,
diff --git a/drivers/mtd/nand/lpc32xx_slc.c b/drivers/mtd/nand/lpc32xx_slc.c
index 2b96c281b1a2..5f7cc6da0a7f 100644
--- a/drivers/mtd/nand/lpc32xx_slc.c
+++ b/drivers/mtd/nand/lpc32xx_slc.c
@@ -686,6 +686,8 @@ static int lpc32xx_nand_write_page_syndrome(struct mtd_info *mtd,
 	uint8_t *pb;
 	int error;
 
+	nand_prog_page_begin_op(chip, page, 0, NULL, 0);
+
 	/* Write data, calculate ECC on outbound data */
 	error = lpc32xx_xfer(mtd, (uint8_t *)buf, chip->ecc.steps, 0);
 	if (error)
@@ -704,7 +706,8 @@ static int lpc32xx_nand_write_page_syndrome(struct mtd_info *mtd,
 
 	/* Write ECC data to device */
 	chip->write_buf(mtd, chip->oob_poi, mtd->oobsize);
-	return 0;
+
+	return nand_prog_page_end_op(chip);
 }
 
 /*
@@ -717,9 +720,11 @@ static int lpc32xx_nand_write_page_raw_syndrome(struct mtd_info *mtd,
 						int oob_required, int page)
 {
 	/* Raw writes can just use the FIFO interface */
-	chip->write_buf(mtd, buf, chip->ecc.size * chip->ecc.steps);
+	nand_prog_page_begin_op(chip, page, 0, buf,
+				chip->ecc.size * chip->ecc.steps);
 	chip->write_buf(mtd, chip->oob_poi, mtd->oobsize);
-	return 0;
+
+	return nand_prog_page_end_op(chip);
 }
 
 static int lpc32xx_nand_dma_setup(struct lpc32xx_nand_host *host)
diff --git a/drivers/mtd/nand/mtk_nand.c b/drivers/mtd/nand/mtk_nand.c
index 1a3fcb65f478..005dabfc7f37 100644
--- a/drivers/mtd/nand/mtk_nand.c
+++ b/drivers/mtd/nand/mtk_nand.c
@@ -761,6 +761,8 @@ static int mtk_nfc_write_page(struct mtd_info *mtd, struct nand_chip *chip,
 	u32 reg;
 	int ret;
 
+	nand_prog_page_begin_op(chip, page, 0, NULL, 0);
+
 	if (!raw) {
 		/* OOB => FDM: from register,  ECC: from HW */
 		reg = nfi_readw(nfc, NFI_CNFG) | CNFG_AUTO_FMT_EN;
@@ -794,7 +796,10 @@ static int mtk_nfc_write_page(struct mtd_info *mtd, struct nand_chip *chip,
 	if (!raw)
 		mtk_ecc_disable(nfc->ecc);
 
-	return ret;
+	if (ret)
+		return ret;
+
+	return nand_prog_page_end_op(chip);
 }
 
 static int mtk_nfc_write_page_hwecc(struct mtd_info *mtd,
@@ -889,8 +894,7 @@ static int mtk_nfc_read_subpage(struct mtd_info *mtd, struct nand_chip *chip,
 	len = sectors * chip->ecc.size + (raw ? sectors * spare : 0);
 	buf = bufpoi + start * chip->ecc.size;
 
-	if (column != 0)
-		chip->cmdfunc(mtd, NAND_CMD_RNDOUT, column, -1);
+	nand_read_page_op(chip, page, column, NULL, 0);
 
 	addr = dma_map_single(nfc->dev, buf, len, DMA_FROM_DEVICE);
 	rc = dma_mapping_error(nfc->dev, addr);
diff --git a/drivers/mtd/nand/nand_base.c b/drivers/mtd/nand/nand_base.c
index f7dd7d5a84b3..318595c29053 100644
--- a/drivers/mtd/nand/nand_base.c
+++ b/drivers/mtd/nand/nand_base.c
@@ -1908,7 +1908,7 @@ int nand_read_page_raw(struct mtd_info *mtd, struct nand_chip *chip,
 {
 	int ret;
 
-	ret = nand_read_data_op(chip, buf, mtd->writesize, false);
+	ret = nand_read_page_op(chip, page, 0, buf, mtd->writesize);
 	if (ret)
 		return ret;
 
@@ -1942,6 +1942,10 @@ static int nand_read_page_raw_syndrome(struct mtd_info *mtd,
 	uint8_t *oob = chip->oob_poi;
 	int steps, size, ret;
 
+	ret = nand_read_page_op(chip, page, 0, NULL, 0);
+	if (ret)
+		return ret;
+
 	for (steps = chip->ecc.steps; steps > 0; steps--) {
 		ret = nand_read_data_op(chip, buf, eccsize, false);
 		if (ret)
@@ -2064,11 +2068,10 @@ static int nand_read_subpage(struct mtd_info *mtd, struct nand_chip *chip,
 
 	data_col_addr = start_step * chip->ecc.size;
 	/* If we read not a page aligned data */
-	if (data_col_addr != 0)
-		chip->cmdfunc(mtd, NAND_CMD_RNDOUT, data_col_addr, -1);
-
 	p = bufpoi + data_col_addr;
-	nand_read_data_op(chip, p, datafrag_len, false);
+	ret = nand_read_page_op(chip, page, data_col_addr, p, datafrag_len);
+	if (ret)
+		return ret;
 
 	/* Calculate ECC */
 	for (i = 0; i < eccfrag_len ; i += chip->ecc.bytes, p += chip->ecc.size)
@@ -2164,6 +2167,10 @@ static int nand_read_page_hwecc(struct mtd_info *mtd, struct nand_chip *chip,
 	uint8_t *ecc_code = chip->buffers->ecccode;
 	unsigned int max_bitflips = 0;
 
+	ret = nand_read_page_op(chip, page, 0, NULL, 0);
+	if (ret)
+		return ret;
+
 	for (i = 0; eccsteps; eccsteps--, i += eccbytes, p += eccsize) {
 		chip->ecc.hwctl(mtd, NAND_ECC_READ);
 
@@ -2299,6 +2306,10 @@ static int nand_read_page_syndrome(struct mtd_info *mtd, struct nand_chip *chip,
 	uint8_t *oob = chip->oob_poi;
 	unsigned int max_bitflips = 0;
 
+	ret = nand_read_page_op(chip, page, 0, NULL, 0);
+	if (ret)
+		return ret;
+
 	for (i = 0; eccsteps; eccsteps--, i += eccbytes, p += eccsize) {
 		int stat;
 
@@ -2481,9 +2492,6 @@ static int nand_do_read_ops(struct mtd_info *mtd, loff_t from,
 						 __func__, buf);
 
 read_retry:
-			if (nand_standard_page_accessors(&chip->ecc))
-				nand_read_page_op(chip, page, 0, NULL, 0);
-
 			/*
 			 * Now read the page into the buffer.  Absent an error,
 			 * the read methods return max bitflips per ecc step.
@@ -2931,7 +2939,7 @@ int nand_write_page_raw(struct mtd_info *mtd, struct nand_chip *chip,
 {
 	int ret;
 
-	ret = nand_write_data_op(chip, buf, mtd->writesize, false);
+	ret = nand_prog_page_begin_op(chip, page, 0, buf, mtd->writesize);
 	if (ret)
 		return ret;
 
@@ -2942,7 +2950,7 @@ int nand_write_page_raw(struct mtd_info *mtd, struct nand_chip *chip,
 			return ret;
 	}
 
-	return 0;
+	return nand_prog_page_end_op(chip);
 }
 EXPORT_SYMBOL(nand_write_page_raw);
 
@@ -2966,6 +2974,10 @@ static int nand_write_page_raw_syndrome(struct mtd_info *mtd,
 	uint8_t *oob = chip->oob_poi;
 	int steps, size, ret;
 
+	ret = nand_prog_page_begin_op(chip, page, 0, NULL, 0);
+	if (ret)
+		return ret;
+
 	for (steps = chip->ecc.steps; steps > 0; steps--) {
 		ret = nand_write_data_op(chip, buf, eccsize, false);
 		if (ret)
@@ -3005,7 +3017,7 @@ static int nand_write_page_raw_syndrome(struct mtd_info *mtd,
 			return ret;
 	}
 
-	return 0;
+	return nand_prog_page_end_op(chip);
 }
 /**
  * nand_write_page_swecc - [REPLACEABLE] software ECC based page write function
@@ -3236,12 +3248,6 @@ static int nand_write_page(struct mtd_info *mtd, struct nand_chip *chip,
 	else
 		subpage = 0;
 
-	if (nand_standard_page_accessors(&chip->ecc)) {
-		status = nand_prog_page_begin_op(chip, page, 0, NULL, 0);
-		if (status)
-			return status;
-	}
-
 	if (unlikely(raw))
 		status = chip->ecc.write_page_raw(mtd, chip, buf,
 						  oob_required, page);
@@ -3255,9 +3261,6 @@ static int nand_write_page(struct mtd_info *mtd, struct nand_chip *chip,
 	if (status < 0)
 		return status;
 
-	if (nand_standard_page_accessors(&chip->ecc))
-		return nand_prog_page_end_op(chip);
-
 	return 0;
 }
 
@@ -5235,26 +5238,6 @@ static bool nand_ecc_strength_good(struct mtd_info *mtd)
 	return corr >= ds_corr && ecc->strength >= chip->ecc_strength_ds;
 }
 
-static bool invalid_ecc_page_accessors(struct nand_chip *chip)
-{
-	struct nand_ecc_ctrl *ecc = &chip->ecc;
-
-	if (nand_standard_page_accessors(ecc))
-		return false;
-
-	/*
-	 * NAND_ECC_CUSTOM_PAGE_ACCESS flag is set, make sure the NAND
-	 * controller driver implements all the page accessors because
-	 * default helpers are not suitable when the core does not
-	 * send the READ0/PAGEPROG commands.
-	 */
-	return (!ecc->read_page || !ecc->write_page ||
-		!ecc->read_page_raw || !ecc->write_page_raw ||
-		(NAND_HAS_SUBPAGE_READ(chip) && !ecc->read_subpage) ||
-		(NAND_HAS_SUBPAGE_WRITE(chip) && !ecc->write_subpage &&
-		 ecc->hwctl && ecc->calculate));
-}
-
 /**
  * nand_scan_tail - [NAND Interface] Scan for the NAND device
  * @mtd: MTD device structure
@@ -5276,11 +5259,6 @@ int nand_scan_tail(struct mtd_info *mtd)
 		return -EINVAL;
 	}
 
-	if (invalid_ecc_page_accessors(chip)) {
-		pr_err("Invalid ECC page accessors setup\n");
-		return -EINVAL;
-	}
-
 	if (!(chip->options & NAND_OWN_BUFFERS)) {
 		nbuf = kzalloc(sizeof(*nbuf), GFP_KERNEL);
 		if (!nbuf)
diff --git a/drivers/mtd/nand/nand_micron.c b/drivers/mtd/nand/nand_micron.c
index 3934539a7468..543352380ffa 100644
--- a/drivers/mtd/nand/nand_micron.c
+++ b/drivers/mtd/nand/nand_micron.c
@@ -272,7 +272,6 @@ static int micron_nand_init(struct nand_chip *chip)
 			return -EINVAL;
 		}
 
-		chip->ecc.options = NAND_ECC_CUSTOM_PAGE_ACCESS;
 		chip->ecc.bytes = 8;
 		chip->ecc.size = 512;
 		chip->ecc.strength = 4;
diff --git a/drivers/mtd/nand/sh_flctl.c b/drivers/mtd/nand/sh_flctl.c
index 3c5008a4f5f3..c4e7755448e6 100644
--- a/drivers/mtd/nand/sh_flctl.c
+++ b/drivers/mtd/nand/sh_flctl.c
@@ -614,7 +614,7 @@ static void set_cmd_regs(struct mtd_info *mtd, uint32_t cmd, uint32_t flcmcdr_va
 static int flctl_read_page_hwecc(struct mtd_info *mtd, struct nand_chip *chip,
 				uint8_t *buf, int oob_required, int page)
 {
-	chip->read_buf(mtd, buf, mtd->writesize);
+	nand_read_page_op(chip, page, 0, buf, mtd->writesize);
 	if (oob_required)
 		chip->read_buf(mtd, chip->oob_poi, mtd->oobsize);
 	return 0;
@@ -624,9 +624,9 @@ static int flctl_write_page_hwecc(struct mtd_info *mtd, struct nand_chip *chip,
 				  const uint8_t *buf, int oob_required,
 				  int page)
 {
-	chip->write_buf(mtd, buf, mtd->writesize);
+	nand_prog_page_begin_op(chip, page, 0, buf, mtd->writesize);
 	chip->write_buf(mtd, chip->oob_poi, mtd->oobsize);
-	return 0;
+	return nand_prog_page_end_op(chip);
 }
 
 static void execmd_read_page_sector(struct mtd_info *mtd, int page_addr)
diff --git a/drivers/mtd/nand/sunxi_nand.c b/drivers/mtd/nand/sunxi_nand.c
index 230d037d05d2..2354410d8c48 100644
--- a/drivers/mtd/nand/sunxi_nand.c
+++ b/drivers/mtd/nand/sunxi_nand.c
@@ -1245,6 +1245,8 @@ static int sunxi_nfc_hw_ecc_read_page(struct mtd_info *mtd,
 	int ret, i, cur_off = 0;
 	bool raw_mode = false;
 
+	nand_read_page_op(chip, page, 0, NULL, 0);
+
 	sunxi_nfc_hw_ecc_enable(mtd);
 
 	for (i = 0; i < ecc->steps; i++) {
@@ -1278,14 +1280,14 @@ static int sunxi_nfc_hw_ecc_read_page_dma(struct mtd_info *mtd,
 {
 	int ret;
 
+	nand_read_page_op(chip, page, 0, NULL, 0);
+
 	ret = sunxi_nfc_hw_ecc_read_chunks_dma(mtd, buf, oob_required, page,
 					       chip->ecc.steps);
 	if (ret >= 0)
 		return ret;
 
 	/* Fallback to PIO mode */
-	chip->cmdfunc(mtd, NAND_CMD_RNDOUT, 0, -1);
-
 	return sunxi_nfc_hw_ecc_read_page(mtd, chip, buf, oob_required, page);
 }
 
@@ -1298,6 +1300,8 @@ static int sunxi_nfc_hw_ecc_read_subpage(struct mtd_info *mtd,
 	int ret, i, cur_off = 0;
 	unsigned int max_bitflips = 0;
 
+	nand_read_page_op(chip, page, 0, NULL, 0);
+
 	sunxi_nfc_hw_ecc_enable(mtd);
 
 	for (i = data_offs / ecc->size;
@@ -1329,13 +1333,13 @@ static int sunxi_nfc_hw_ecc_read_subpage_dma(struct mtd_info *mtd,
 	int nchunks = DIV_ROUND_UP(data_offs + readlen, chip->ecc.size);
 	int ret;
 
+	nand_read_page_op(chip, page, 0, NULL, 0);
+
 	ret = sunxi_nfc_hw_ecc_read_chunks_dma(mtd, buf, false, page, nchunks);
 	if (ret >= 0)
 		return ret;
 
 	/* Fallback to PIO mode */
-	chip->cmdfunc(mtd, NAND_CMD_RNDOUT, 0, -1);
-
 	return sunxi_nfc_hw_ecc_read_subpage(mtd, chip, data_offs, readlen,
 					     buf, page);
 }
@@ -1348,6 +1352,8 @@ static int sunxi_nfc_hw_ecc_write_page(struct mtd_info *mtd,
 	struct nand_ecc_ctrl *ecc = &chip->ecc;
 	int ret, i, cur_off = 0;
 
+	nand_prog_page_begin_op(chip, page, 0, NULL, 0);
+
 	sunxi_nfc_hw_ecc_enable(mtd);
 
 	for (i = 0; i < ecc->steps; i++) {
@@ -1369,7 +1375,7 @@ static int sunxi_nfc_hw_ecc_write_page(struct mtd_info *mtd,
 
 	sunxi_nfc_hw_ecc_disable(mtd);
 
-	return 0;
+	return nand_prog_page_end_op(chip);
 }
 
 static int sunxi_nfc_hw_ecc_write_subpage(struct mtd_info *mtd,
@@ -1381,6 +1387,8 @@ static int sunxi_nfc_hw_ecc_write_subpage(struct mtd_info *mtd,
 	struct nand_ecc_ctrl *ecc = &chip->ecc;
 	int ret, i, cur_off = 0;
 
+	nand_prog_page_begin_op(chip, page, 0, NULL, 0);
+
 	sunxi_nfc_hw_ecc_enable(mtd);
 
 	for (i = data_offs / ecc->size;
@@ -1399,7 +1407,7 @@ static int sunxi_nfc_hw_ecc_write_subpage(struct mtd_info *mtd,
 
 	sunxi_nfc_hw_ecc_disable(mtd);
 
-	return 0;
+	return nand_prog_page_end_op(chip);
 }
 
 static int sunxi_nfc_hw_ecc_write_page_dma(struct mtd_info *mtd,
@@ -1429,6 +1437,8 @@ static int sunxi_nfc_hw_ecc_write_page_dma(struct mtd_info *mtd,
 		sunxi_nfc_hw_ecc_set_prot_oob_bytes(mtd, oob, i, !i, page);
 	}
 
+	nand_prog_page_begin_op(chip, page, 0, NULL, 0);
+
 	sunxi_nfc_hw_ecc_enable(mtd);
 	sunxi_nfc_randomizer_config(mtd, page, false);
 	sunxi_nfc_randomizer_enable(mtd);
@@ -1459,7 +1469,7 @@ static int sunxi_nfc_hw_ecc_write_page_dma(struct mtd_info *mtd,
 		sunxi_nfc_hw_ecc_write_extra_oob(mtd, chip->oob_poi,
 						 NULL, page);
 
-	return 0;
+	return nand_prog_page_end_op(chip);
 
 pio_fallback:
 	return sunxi_nfc_hw_ecc_write_page(mtd, chip, buf, oob_required, page);
@@ -1475,6 +1485,8 @@ static int sunxi_nfc_hw_syndrome_ecc_read_page(struct mtd_info *mtd,
 	int ret, i, cur_off = 0;
 	bool raw_mode = false;
 
+	nand_read_page_op(chip, page, 0, NULL, 0);
+
 	sunxi_nfc_hw_ecc_enable(mtd);
 
 	for (i = 0; i < ecc->steps; i++) {
@@ -1511,6 +1523,8 @@ static int sunxi_nfc_hw_syndrome_ecc_write_page(struct mtd_info *mtd,
 	struct nand_ecc_ctrl *ecc = &chip->ecc;
 	int ret, i, cur_off = 0;
 
+	nand_prog_page_begin_op(chip, page, 0, NULL, 0);
+
 	sunxi_nfc_hw_ecc_enable(mtd);
 
 	for (i = 0; i < ecc->steps; i++) {
@@ -1532,15 +1546,13 @@ static int sunxi_nfc_hw_syndrome_ecc_write_page(struct mtd_info *mtd,
 
 	sunxi_nfc_hw_ecc_disable(mtd);
 
-	return 0;
+	return nand_prog_page_end_op(chip);
 }
 
 static int sunxi_nfc_hw_common_ecc_read_oob(struct mtd_info *mtd,
 					    struct nand_chip *chip,
 					    int page)
 {
-	nand_read_page_op(chip, page, 0, NULL, 0);
-
 	chip->pagebuf = -1;
 
 	return chip->ecc.read_page(mtd, chip, chip->buffers->databuf, 1, page);
@@ -1552,8 +1564,6 @@ static int sunxi_nfc_hw_common_ecc_write_oob(struct mtd_info *mtd,
 {
 	int ret;
 
-	nand_prog_page_begin_op(chip, page, 0, NULL, 0);
-
 	chip->pagebuf = -1;
 
 	memset(chip->buffers->databuf, 0xff, mtd->writesize);
diff --git a/drivers/mtd/nand/tango_nand.c b/drivers/mtd/nand/tango_nand.c
index 97a300b46b1d..c5bee00b7f5e 100644
--- a/drivers/mtd/nand/tango_nand.c
+++ b/drivers/mtd/nand/tango_nand.c
@@ -580,7 +580,6 @@ static int chip_init(struct device *dev, struct device_node *np)
 	ecc->write_page = tango_write_page;
 	ecc->read_oob = tango_read_oob;
 	ecc->write_oob = tango_write_oob;
-	ecc->options = NAND_ECC_CUSTOM_PAGE_ACCESS;
 
 	err = nand_scan_tail(mtd);
 	if (err)
diff --git a/drivers/mtd/nand/vf610_nfc.c b/drivers/mtd/nand/vf610_nfc.c
index 8037d4b48a05..80d31a58e558 100644
--- a/drivers/mtd/nand/vf610_nfc.c
+++ b/drivers/mtd/nand/vf610_nfc.c
@@ -560,7 +560,7 @@ static int vf610_nfc_read_page(struct mtd_info *mtd, struct nand_chip *chip,
 	int eccsize = chip->ecc.size;
 	int stat;
 
-	vf610_nfc_read_buf(mtd, buf, eccsize);
+	nand_read_page_op(chip, page, 0, buf, eccsize);
 	if (oob_required)
 		vf610_nfc_read_buf(mtd, chip->oob_poi, mtd->oobsize);
 
@@ -580,7 +580,7 @@ static int vf610_nfc_write_page(struct mtd_info *mtd, struct nand_chip *chip,
 {
 	struct vf610_nfc *nfc = mtd_to_nfc(mtd);
 
-	vf610_nfc_write_buf(mtd, buf, mtd->writesize);
+	nand_prog_page_begin_op(chip, page, 0, buf, mtd->writesize);
 	if (oob_required)
 		vf610_nfc_write_buf(mtd, chip->oob_poi, mtd->oobsize);
 
@@ -588,7 +588,7 @@ static int vf610_nfc_write_page(struct mtd_info *mtd, struct nand_chip *chip,
 	nfc->use_hw_ecc = true;
 	nfc->write_sz = mtd->writesize + mtd->oobsize;
 
-	return 0;
+	return nand_prog_page_end_op(chip);
 }
 
 static const struct of_device_id vf610_nfc_dt_ids[] = {
diff --git a/drivers/staging/mt29f_spinand/mt29f_spinand.c b/drivers/staging/mt29f_spinand/mt29f_spinand.c
index 87595c594b12..e396075dd508 100644
--- a/drivers/staging/mt29f_spinand/mt29f_spinand.c
+++ b/drivers/staging/mt29f_spinand/mt29f_spinand.c
@@ -636,9 +636,12 @@ static int spinand_write_page_hwecc(struct mtd_info *mtd,
 	int eccsize = chip->ecc.size;
 	int eccsteps = chip->ecc.steps;
 
+	nand_prog_page_begin_op(chip, page, 0, NULL, 0);
+
 	enable_hw_ecc = 1;
 	chip->write_buf(mtd, p, eccsize * eccsteps);
-	return 0;
+
+	return nand_prog_page_end_op(chip);
 }
 
 static int spinand_read_page_hwecc(struct mtd_info *mtd, struct nand_chip *chip,
@@ -653,7 +656,7 @@ static int spinand_read_page_hwecc(struct mtd_info *mtd, struct nand_chip *chip,
 
 	enable_read_hw_ecc = 1;
 
-	chip->read_buf(mtd, p, eccsize * eccsteps);
+	nand_read_page_op(chip, page, 0, p, eccsize * eccsteps);
 	if (oob_required)
 		chip->read_buf(mtd, chip->oob_poi, mtd->oobsize);
 
diff --git a/include/linux/mtd/rawnand.h b/include/linux/mtd/rawnand.h
index 8b8acde72465..04f4cfbe6c09 100644
--- a/include/linux/mtd/rawnand.h
+++ b/include/linux/mtd/rawnand.h
@@ -133,12 +133,6 @@ enum nand_ecc_algo {
  */
 #define NAND_ECC_GENERIC_ERASED_CHECK	BIT(0)
 #define NAND_ECC_MAXIMIZE		BIT(1)
-/*
- * If your controller already sends the required NAND commands when
- * reading or writing a page, then the framework is not supposed to
- * send READ0 and SEQIN/PAGEPROG respectively.
- */
-#define NAND_ECC_CUSTOM_PAGE_ACCESS	BIT(2)
 
 /* Bit mask for flags passed to do_nand_read_ecc */
 #define NAND_GET_DEVICE		0x80
@@ -602,11 +596,6 @@ struct nand_ecc_ctrl {
 			int page);
 };
 
-static inline int nand_standard_page_accessors(struct nand_ecc_ctrl *ecc)
-{
-	return !(ecc->options & NAND_ECC_CUSTOM_PAGE_ACCESS);
-}
-
 /**
  * struct nand_buffers - buffer structure for read/write
  * @ecccalc:	buffer pointer for calculated ECC, size is oobsize.
-- 
2.11.0

^ permalink raw reply related	[flat|nested] 75+ messages in thread

* [RFC 02/12] mtd: nand: force drivers to explicitly send READ/PROG commands
@ 2017-10-18 14:36   ` Miquel Raynal
  0 siblings, 0 replies; 75+ messages in thread
From: Miquel Raynal @ 2017-10-18 14:36 UTC (permalink / raw)
  To: Andrew Lunn, bcm-kernel-feedback-list, Boris Brezillon,
	Brian Norris, Catalin Marinas, Chen-Yu Tsai, Cyrille Pitchen,
	Daniel Mack, David Woodhouse, devel, devicetree, Ezequiel Garcia,
	Greg Kroah-Hartman, Gregory Clement, Han Xu, Haojian Zhuang,
	Jason Cooper, Josh Wu, Kamal Dasu, linux-arm-kernel,
	linux-kernel, linux-mediatek, linux-mtd
  Cc: Hanna Hawa, Antoine Tenart, Nadav Haklai, Neta Zur Hershkovits,
	Igor Grinberg, Miquel Raynal, Ofer Heifetz

From: Boris Brezillon <boris.brezillon@free-electrons.com>

The core currently send the READ0 and SEQIN+PAGEPROG commands in
nand_do_read/write_ops(). This is inconsistent with
->read/write_oob[_raw]() hooks behavior which are expected to send
these commands.

There's already a flag (NAND_ECC_CUSTOM_PAGE_ACCESS) to inform the core
that a specific controller wants to send the READ/SEQIN+PAGEPROG
commands on its own, but it's an opt-in flag, and existing drivers are
unlikely to be updated to pass it.

Moreover, some controllers cannot dissociate the READ/PAGEPROG commands
from the associated data transfer and ECC engine activation, and
developers have to hack things in their ->cmdfunc() implementation to
handle such complex cases, or have to accept the perf penalty of sending
twice the same command.
To address this problem we are planning on adding a new interface which
is passed all information about a NAND operation (including the amount
of data to transfer) and replacing all calls to ->cmdfunc() to calls to
this new ->exec_op() hook. But, in order to do that, we need to have all
->cmdfunc() calls placed near their associated ->read/write_buf/byte()
calls.

Modify the core and relevant drivers to make NAND_ECC_CUSTOM_PAGE_ACCESS
the default case, and remove this flag.

Signed-off-by: Boris Brezillon <boris.brezillon@free-electrons.com>
[miquel.raynal@free-electrons.com: rebased and fixed some conflicts]
Signed-off-by: Miquel Raynal <miquel.raynal@free-electrons.com>
---
 drivers/mtd/nand/atmel/nand-controller.c      |  7 ++-
 drivers/mtd/nand/bf5xx_nand.c                 |  6 ++-
 drivers/mtd/nand/brcmnand/brcmnand.c          | 13 +++--
 drivers/mtd/nand/cafe_nand.c                  |  6 +--
 drivers/mtd/nand/denali.c                     | 14 +++++-
 drivers/mtd/nand/docg4.c                      | 12 +++--
 drivers/mtd/nand/fsl_elbc_nand.c              |  6 +--
 drivers/mtd/nand/fsl_ifc_nand.c               |  6 +--
 drivers/mtd/nand/gpmi-nand/gpmi-nand.c        | 30 ++++++------
 drivers/mtd/nand/hisi504_nand.c               |  6 +--
 drivers/mtd/nand/lpc32xx_mlc.c                |  5 +-
 drivers/mtd/nand/lpc32xx_slc.c                | 11 +++--
 drivers/mtd/nand/mtk_nand.c                   | 10 ++--
 drivers/mtd/nand/nand_base.c                  | 68 +++++++++------------------
 drivers/mtd/nand/nand_micron.c                |  1 -
 drivers/mtd/nand/sh_flctl.c                   |  6 +--
 drivers/mtd/nand/sunxi_nand.c                 | 34 +++++++++-----
 drivers/mtd/nand/tango_nand.c                 |  1 -
 drivers/mtd/nand/vf610_nfc.c                  |  6 +--
 drivers/staging/mt29f_spinand/mt29f_spinand.c |  7 ++-
 include/linux/mtd/rawnand.h                   | 11 -----
 21 files changed, 142 insertions(+), 124 deletions(-)

diff --git a/drivers/mtd/nand/atmel/nand-controller.c b/drivers/mtd/nand/atmel/nand-controller.c
index 0b2c82727b24..6add993ea1e0 100644
--- a/drivers/mtd/nand/atmel/nand-controller.c
+++ b/drivers/mtd/nand/atmel/nand-controller.c
@@ -841,6 +841,8 @@ static int atmel_nand_pmecc_write_pg(struct nand_chip *chip, const u8 *buf,
 	struct atmel_nand *nand = to_atmel_nand(chip);
 	int ret;
 
+	nand_prog_page_begin_op(chip, page, 0, NULL, 0);
+
 	ret = atmel_nand_pmecc_enable(chip, NAND_ECC_WRITE, raw);
 	if (ret)
 		return ret;
@@ -857,7 +859,7 @@ static int atmel_nand_pmecc_write_pg(struct nand_chip *chip, const u8 *buf,
 
 	atmel_nand_write_buf(mtd, chip->oob_poi, mtd->oobsize);
 
-	return 0;
+	return nand_prog_page_end_op(chip);
 }
 
 static int atmel_nand_pmecc_write_page(struct mtd_info *mtd,
@@ -881,6 +883,8 @@ static int atmel_nand_pmecc_read_pg(struct nand_chip *chip, u8 *buf,
 	struct mtd_info *mtd = nand_to_mtd(chip);
 	int ret;
 
+	nand_read_page_op(chip, page, 0, NULL, 0);
+
 	ret = atmel_nand_pmecc_enable(chip, NAND_ECC_READ, raw);
 	if (ret)
 		return ret;
@@ -1178,7 +1182,6 @@ static int atmel_hsmc_nand_ecc_init(struct atmel_nand *nand)
 	chip->ecc.write_page = atmel_hsmc_nand_pmecc_write_page;
 	chip->ecc.read_page_raw = atmel_hsmc_nand_pmecc_read_page_raw;
 	chip->ecc.write_page_raw = atmel_hsmc_nand_pmecc_write_page_raw;
-	chip->ecc.options |= NAND_ECC_CUSTOM_PAGE_ACCESS;
 
 	return 0;
 }
diff --git a/drivers/mtd/nand/bf5xx_nand.c b/drivers/mtd/nand/bf5xx_nand.c
index 5655dca6ce43..87bbd177b3e5 100644
--- a/drivers/mtd/nand/bf5xx_nand.c
+++ b/drivers/mtd/nand/bf5xx_nand.c
@@ -572,6 +572,8 @@ static void bf5xx_nand_dma_write_buf(struct mtd_info *mtd,
 static int bf5xx_nand_read_page_raw(struct mtd_info *mtd, struct nand_chip *chip,
 		uint8_t *buf, int oob_required, int page)
 {
+	nand_read_page_op(chip, page, 0, NULL, 0);
+
 	bf5xx_nand_read_buf(mtd, buf, mtd->writesize);
 	bf5xx_nand_read_buf(mtd, chip->oob_poi, mtd->oobsize);
 
@@ -582,10 +584,10 @@ static int bf5xx_nand_write_page_raw(struct mtd_info *mtd,
 		struct nand_chip *chip,	const uint8_t *buf, int oob_required,
 		int page)
 {
-	bf5xx_nand_write_buf(mtd, buf, mtd->writesize);
+	nand_prog_page_begin_op(chip, page, 0, buf, mtd->writesize);
 	bf5xx_nand_write_buf(mtd, chip->oob_poi, mtd->oobsize);
 
-	return 0;
+	return nand_prog_page_end_op(chip);
 }
 
 /*
diff --git a/drivers/mtd/nand/brcmnand/brcmnand.c b/drivers/mtd/nand/brcmnand/brcmnand.c
index 3f441096a14c..e6879d4d53ca 100644
--- a/drivers/mtd/nand/brcmnand/brcmnand.c
+++ b/drivers/mtd/nand/brcmnand/brcmnand.c
@@ -1689,7 +1689,6 @@ static int brcmstb_nand_verify_erased_page(struct mtd_info *mtd,
 	sas = mtd->oobsize / chip->ecc.steps;
 
 	/* read without ecc for verification */
-	nand_read_page_op(chip, page, 0, NULL, 0);
 	ret = chip->ecc.read_page_raw(mtd, chip, buf, true, page);
 	if (ret)
 		return ret;
@@ -1793,6 +1792,8 @@ static int brcmnand_read_page(struct mtd_info *mtd, struct nand_chip *chip,
 	struct brcmnand_host *host = nand_get_controller_data(chip);
 	u8 *oob = oob_required ? (u8 *)chip->oob_poi : NULL;
 
+	nand_read_page_op(chip, page, 0, NULL, 0);
+
 	return brcmnand_read(mtd, chip, host->last_addr,
 			mtd->writesize >> FC_SHIFT, (u32 *)buf, oob);
 }
@@ -1804,6 +1805,8 @@ static int brcmnand_read_page_raw(struct mtd_info *mtd, struct nand_chip *chip,
 	u8 *oob = oob_required ? (u8 *)chip->oob_poi : NULL;
 	int ret;
 
+	nand_read_page_op(chip, page, 0, NULL, 0);
+
 	brcmnand_set_ecc_enabled(host, 0);
 	ret = brcmnand_read(mtd, chip, host->last_addr,
 			mtd->writesize >> FC_SHIFT, (u32 *)buf, oob);
@@ -1909,8 +1912,10 @@ static int brcmnand_write_page(struct mtd_info *mtd, struct nand_chip *chip,
 	struct brcmnand_host *host = nand_get_controller_data(chip);
 	void *oob = oob_required ? chip->oob_poi : NULL;
 
+	nand_prog_page_begin_op(chip, page, 0, NULL, 0);
 	brcmnand_write(mtd, chip, host->last_addr, (const u32 *)buf, oob);
-	return 0;
+
+	return nand_prog_page_end_op(chip);
 }
 
 static int brcmnand_write_page_raw(struct mtd_info *mtd,
@@ -1920,10 +1925,12 @@ static int brcmnand_write_page_raw(struct mtd_info *mtd,
 	struct brcmnand_host *host = nand_get_controller_data(chip);
 	void *oob = oob_required ? chip->oob_poi : NULL;
 
+	nand_prog_page_begin_op(chip, page, 0, NULL, 0);
 	brcmnand_set_ecc_enabled(host, 0);
 	brcmnand_write(mtd, chip, host->last_addr, (const u32 *)buf, oob);
 	brcmnand_set_ecc_enabled(host, 1);
-	return 0;
+
+	return nand_prog_page_end_op(chip);
 }
 
 static int brcmnand_write_oob(struct mtd_info *mtd, struct nand_chip *chip,
diff --git a/drivers/mtd/nand/cafe_nand.c b/drivers/mtd/nand/cafe_nand.c
index 95c2cfa68b66..de36762e3058 100644
--- a/drivers/mtd/nand/cafe_nand.c
+++ b/drivers/mtd/nand/cafe_nand.c
@@ -383,7 +383,7 @@ static int cafe_nand_read_page(struct mtd_info *mtd, struct nand_chip *chip,
 		     cafe_readl(cafe, NAND_ECC_RESULT),
 		     cafe_readl(cafe, NAND_ECC_SYN01));
 
-	chip->read_buf(mtd, buf, mtd->writesize);
+	nand_read_page_op(chip, page, 0, buf, mtd->writesize);
 	chip->read_buf(mtd, chip->oob_poi, mtd->oobsize);
 
 	if (checkecc && cafe_readl(cafe, NAND_ECC_RESULT) & (1<<18)) {
@@ -541,13 +541,13 @@ static int cafe_nand_write_page_lowlevel(struct mtd_info *mtd,
 {
 	struct cafe_priv *cafe = nand_get_controller_data(chip);
 
-	chip->write_buf(mtd, buf, mtd->writesize);
+	nand_prog_page_begin_op(chip, page, 0, buf, mtd->writesize);
 	chip->write_buf(mtd, chip->oob_poi, mtd->oobsize);
 
 	/* Set up ECC autogeneration */
 	cafe->ctl2 |= (1<<30);
 
-	return 0;
+	return nand_prog_page_end_op(chip);
 }
 
 static int cafe_nand_block_bad(struct mtd_info *mtd, loff_t ofs)
diff --git a/drivers/mtd/nand/denali.c b/drivers/mtd/nand/denali.c
index f90f93a8fb08..e5f38359f6df 100644
--- a/drivers/mtd/nand/denali.c
+++ b/drivers/mtd/nand/denali.c
@@ -550,11 +550,14 @@ static int denali_pio_read(struct denali_nand_info *denali, void *buf,
 static int denali_pio_write(struct denali_nand_info *denali,
 			    const void *buf, size_t size, int page, int raw)
 {
+	struct nand_chip *chip = &denali->nand;
 	u32 addr = DENALI_MAP01 | DENALI_BANK(denali) | page;
 	const uint32_t *buf32 = (uint32_t *)buf;
 	uint32_t irq_status;
 	int i;
 
+	nand_prog_page_begin_op(chip, page, 0, NULL, 0);
+
 	denali_reset_irq(denali);
 
 	for (i = 0; i < size / 4; i++)
@@ -580,6 +583,7 @@ static int denali_pio_xfer(struct denali_nand_info *denali, void *buf,
 static int denali_dma_xfer(struct denali_nand_info *denali, void *buf,
 			   size_t size, int page, int raw, int write)
 {
+	struct nand_chip *chip = &denali->nand;
 	dma_addr_t dma_addr;
 	uint32_t irq_mask, irq_status, ecc_err_mask;
 	enum dma_data_direction dir = write ? DMA_TO_DEVICE : DMA_FROM_DEVICE;
@@ -625,7 +629,10 @@ static int denali_dma_xfer(struct denali_nand_info *denali, void *buf,
 	if (irq_status & INTR__ERASED_PAGE)
 		memset(buf, 0xff, size);
 
-	return ret;
+	if (ret)
+		return ret;
+
+	return nand_prog_page_end_op(chip);
 }
 
 static int denali_data_xfer(struct denali_nand_info *denali, void *buf,
@@ -790,6 +797,8 @@ static int denali_write_oob(struct mtd_info *mtd, struct nand_chip *chip,
 	struct denali_nand_info *denali = mtd_to_denali(mtd);
 	int status;
 
+	nand_read_page_op(chip, page, 0, NULL, 0);
+
 	denali_reset_irq(denali);
 
 	denali_oob_xfer(mtd, chip, page, 1);
@@ -846,6 +855,8 @@ static int denali_write_page_raw(struct mtd_info *mtd, struct nand_chip *chip,
 	size_t size = writesize + oobsize;
 	int i, pos, len;
 
+	nand_read_page_op(chip, page, 0, NULL, 0);
+
 	/*
 	 * Fill the buffer with 0xff first except the full page transfer.
 	 * This simplifies the logic.
@@ -1359,7 +1370,6 @@ int denali_init(struct denali_nand_info *denali)
 		chip->read_buf = denali_read_buf;
 		chip->write_buf = denali_write_buf;
 	}
-	chip->ecc.options |= NAND_ECC_CUSTOM_PAGE_ACCESS;
 	chip->ecc.read_page = denali_read_page;
 	chip->ecc.read_page_raw = denali_read_page_raw;
 	chip->ecc.write_page = denali_write_page;
diff --git a/drivers/mtd/nand/docg4.c b/drivers/mtd/nand/docg4.c
index 45c01b4b34c7..156fdf683b2f 100644
--- a/drivers/mtd/nand/docg4.c
+++ b/drivers/mtd/nand/docg4.c
@@ -785,6 +785,8 @@ static int read_page(struct mtd_info *mtd, struct nand_chip *nand,
 
 	dev_dbg(doc->dev, "%s: page %08x\n", __func__, page);
 
+	nand_read_page_op(nand, page, 0, NULL, 0);
+
 	writew(DOC_ECCCONF0_READ_MODE |
 	       DOC_ECCCONF0_ECC_ENABLE |
 	       DOC_ECCCONF0_UNKNOWN |
@@ -948,7 +950,7 @@ static int docg4_erase_block(struct mtd_info *mtd, int page)
 }
 
 static int write_page(struct mtd_info *mtd, struct nand_chip *nand,
-		       const uint8_t *buf, bool use_ecc)
+		      const uint8_t *buf, int page, bool use_ecc)
 {
 	struct docg4_priv *doc = nand_get_controller_data(nand);
 	void __iomem *docptr = doc->virtadr;
@@ -956,6 +958,8 @@ static int write_page(struct mtd_info *mtd, struct nand_chip *nand,
 
 	dev_dbg(doc->dev, "%s...\n", __func__);
 
+	nand_prog_page_begin_op(nand, page, 0, NULL, 0);
+
 	writew(DOC_ECCCONF0_ECC_ENABLE |
 	       DOC_ECCCONF0_UNKNOWN |
 	       DOCG4_BCH_SIZE,
@@ -1000,19 +1004,19 @@ static int write_page(struct mtd_info *mtd, struct nand_chip *nand,
 	writew(0, docptr + DOC_DATAEND);
 	write_nop(docptr);
 
-	return 0;
+	return nand_prog_page_end_op(nand);
 }
 
 static int docg4_write_page_raw(struct mtd_info *mtd, struct nand_chip *nand,
 				const uint8_t *buf, int oob_required, int page)
 {
-	return write_page(mtd, nand, buf, false);
+	return write_page(mtd, nand, buf, page, false);
 }
 
 static int docg4_write_page(struct mtd_info *mtd, struct nand_chip *nand,
 			     const uint8_t *buf, int oob_required, int page)
 {
-	return write_page(mtd, nand, buf, true);
+	return write_page(mtd, nand, buf, page, true);
 }
 
 static int docg4_write_oob(struct mtd_info *mtd, struct nand_chip *nand,
diff --git a/drivers/mtd/nand/fsl_elbc_nand.c b/drivers/mtd/nand/fsl_elbc_nand.c
index 17db2f90aa2c..a24c308a0b8a 100644
--- a/drivers/mtd/nand/fsl_elbc_nand.c
+++ b/drivers/mtd/nand/fsl_elbc_nand.c
@@ -713,7 +713,7 @@ static int fsl_elbc_read_page(struct mtd_info *mtd, struct nand_chip *chip,
 	struct fsl_lbc_ctrl *ctrl = priv->ctrl;
 	struct fsl_elbc_fcm_ctrl *elbc_fcm_ctrl = ctrl->nand;
 
-	fsl_elbc_read_buf(mtd, buf, mtd->writesize);
+	nand_read_page_op(chip, page, 0, buf, mtd->writesize);
 	if (oob_required)
 		fsl_elbc_read_buf(mtd, chip->oob_poi, mtd->oobsize);
 
@@ -729,10 +729,10 @@ static int fsl_elbc_read_page(struct mtd_info *mtd, struct nand_chip *chip,
 static int fsl_elbc_write_page(struct mtd_info *mtd, struct nand_chip *chip,
 				const uint8_t *buf, int oob_required, int page)
 {
-	fsl_elbc_write_buf(mtd, buf, mtd->writesize);
+	nand_prog_page_begin_op(chip, page, 0, buf, mtd->writesize);
 	fsl_elbc_write_buf(mtd, chip->oob_poi, mtd->oobsize);
 
-	return 0;
+	return nand_prog_page_end_op(chip);
 }
 
 /* ECC will be calculated automatically, and errors will be detected in
diff --git a/drivers/mtd/nand/fsl_ifc_nand.c b/drivers/mtd/nand/fsl_ifc_nand.c
index 9e03bac7f34c..857038fe874f 100644
--- a/drivers/mtd/nand/fsl_ifc_nand.c
+++ b/drivers/mtd/nand/fsl_ifc_nand.c
@@ -688,7 +688,7 @@ static int fsl_ifc_read_page(struct mtd_info *mtd, struct nand_chip *chip,
 	struct fsl_ifc_ctrl *ctrl = priv->ctrl;
 	struct fsl_ifc_nand_ctrl *nctrl = ifc_nand_ctrl;
 
-	fsl_ifc_read_buf(mtd, buf, mtd->writesize);
+	nand_read_page_op(chip, page, 0, buf, mtd->writesize);
 	if (oob_required)
 		fsl_ifc_read_buf(mtd, chip->oob_poi, mtd->oobsize);
 
@@ -711,10 +711,10 @@ static int fsl_ifc_read_page(struct mtd_info *mtd, struct nand_chip *chip,
 static int fsl_ifc_write_page(struct mtd_info *mtd, struct nand_chip *chip,
 			       const uint8_t *buf, int oob_required, int page)
 {
-	fsl_ifc_write_buf(mtd, buf, mtd->writesize);
+	nand_prog_page_begin_op(chip, page, 0, buf, mtd->writesize);
 	fsl_ifc_write_buf(mtd, chip->oob_poi, mtd->oobsize);
 
-	return 0;
+	return nand_prog_page_end_op(chip);
 }
 
 static int fsl_ifc_chip_init_tail(struct mtd_info *mtd)
diff --git a/drivers/mtd/nand/gpmi-nand/gpmi-nand.c b/drivers/mtd/nand/gpmi-nand/gpmi-nand.c
index c8ceaecd8065..3f2b903158c1 100644
--- a/drivers/mtd/nand/gpmi-nand/gpmi-nand.c
+++ b/drivers/mtd/nand/gpmi-nand/gpmi-nand.c
@@ -1043,6 +1043,8 @@ static int gpmi_ecc_read_page(struct mtd_info *mtd, struct nand_chip *chip,
 	unsigned int  max_bitflips = 0;
 	int           ret;
 
+	nand_read_page_op(chip, page, 0, NULL, 0);
+
 	dev_dbg(this->dev, "page number is : %d\n", page);
 	ret = read_page_prepare(this, buf, nfc_geo->payload_size,
 					this->payload_virt, this->payload_phys,
@@ -1220,12 +1222,13 @@ static int gpmi_ecc_read_subpage(struct mtd_info *mtd, struct nand_chip *chip,
 	meta = geo->metadata_size;
 	if (first) {
 		col = meta + (size + ecc_parity_size) * first;
-		chip->cmdfunc(mtd, NAND_CMD_RNDOUT, col, -1);
 
 		meta = 0;
 		buf = buf + first * size;
 	}
 
+	nand_read_page_op(chip, page, col, NULL, 0);
+
 	/* Save the old environment */
 	r1_old = r1_new = readl(bch_regs + HW_BCH_FLASH0LAYOUT0);
 	r2_old = r2_new = readl(bch_regs + HW_BCH_FLASH0LAYOUT1);
@@ -1277,6 +1280,9 @@ static int gpmi_ecc_write_page(struct mtd_info *mtd, struct nand_chip *chip,
 	int        ret;
 
 	dev_dbg(this->dev, "ecc write page.\n");
+
+	nand_prog_page_begin_op(chip, page, 0, NULL, 0);
+
 	if (this->swap_block_mark) {
 		/*
 		 * If control arrives here, we're doing block mark swapping.
@@ -1338,7 +1344,10 @@ static int gpmi_ecc_write_page(struct mtd_info *mtd, struct nand_chip *chip,
 				payload_virt, payload_phys);
 	}
 
-	return 0;
+	if (ret)
+		return ret;
+
+	return nand_prog_page_end_op(chip);
 }
 
 /*
@@ -1472,8 +1481,8 @@ static int gpmi_ecc_read_page_raw(struct mtd_info *mtd,
 	uint8_t *oob = chip->oob_poi;
 	int step;
 
-	chip->read_buf(mtd, tmp_buf,
-		       mtd->writesize + mtd->oobsize);
+	nand_read_page_op(chip, page, 0, tmp_buf,
+			  mtd->writesize + mtd->oobsize);
 
 	/*
 	 * If required, swap the bad block marker and the data stored in the
@@ -1617,24 +1626,19 @@ static int gpmi_ecc_write_page_raw(struct mtd_info *mtd,
 		tmp_buf[mtd->writesize] = swap;
 	}
 
-	chip->write_buf(mtd, tmp_buf, mtd->writesize + mtd->oobsize);
-
-	return 0;
+	return nand_prog_page_op(chip, page, 0, tmp_buf,
+				 mtd->writesize + mtd->oobsize);
 }
 
 static int gpmi_ecc_read_oob_raw(struct mtd_info *mtd, struct nand_chip *chip,
 				 int page)
 {
-	nand_read_page_op(chip, page, 0, NULL, 0);
-
 	return gpmi_ecc_read_page_raw(mtd, chip, NULL, 1, page);
 }
 
 static int gpmi_ecc_write_oob_raw(struct mtd_info *mtd, struct nand_chip *chip,
 				 int page)
 {
-	nand_prog_page_begin_op(chip, page, 0, NULL, 0);
-
 	return gpmi_ecc_write_page_raw(mtd, chip, NULL, 1, page);
 }
 
@@ -1806,9 +1810,7 @@ static int mx23_write_transcription_stamp(struct gpmi_nand_data *this)
 		/* Write the first page of the current stride. */
 		dev_dbg(dev, "Writing an NCB fingerprint in page 0x%x\n", page);
 
-		nand_prog_page_begin_op(chip, page, 0, NULL, 0);
-		chip->ecc.write_page_raw(mtd, chip, buffer, 0, page);
-		status = nand_prog_page_end_op(chip);
+		status = chip->ecc.write_page_raw(mtd, chip, buffer, 0, page);
 		if (status)
 			dev_err(dev, "[%s] Write failed.\n", __func__);
 	}
diff --git a/drivers/mtd/nand/hisi504_nand.c b/drivers/mtd/nand/hisi504_nand.c
index 184d765c8bbe..cb862793ab6d 100644
--- a/drivers/mtd/nand/hisi504_nand.c
+++ b/drivers/mtd/nand/hisi504_nand.c
@@ -544,7 +544,7 @@ static int hisi_nand_read_page_hwecc(struct mtd_info *mtd,
 	int max_bitflips = 0, stat = 0, stat_max = 0, status_ecc;
 	int stat_1, stat_2;
 
-	chip->read_buf(mtd, buf, mtd->writesize);
+	nand_read_page_op(chip, page, 0, buf, mtd->writesize);
 	chip->read_buf(mtd, chip->oob_poi, mtd->oobsize);
 
 	/* errors which can not be corrected by ECC */
@@ -589,11 +589,11 @@ static int hisi_nand_write_page_hwecc(struct mtd_info *mtd,
 		struct nand_chip *chip, const uint8_t *buf, int oob_required,
 		int page)
 {
-	chip->write_buf(mtd, buf, mtd->writesize);
+	nand_prog_page_begin_op(chip, page, 0, buf, mtd->writesize);
 	if (oob_required)
 		chip->write_buf(mtd, chip->oob_poi, mtd->oobsize);
 
-	return 0;
+	return nand_prog_page_end_op(chip);
 }
 
 static void hisi_nfc_host_init(struct hinfc_host *host)
diff --git a/drivers/mtd/nand/lpc32xx_mlc.c b/drivers/mtd/nand/lpc32xx_mlc.c
index 0723ded232ff..fcab1900a764 100644
--- a/drivers/mtd/nand/lpc32xx_mlc.c
+++ b/drivers/mtd/nand/lpc32xx_mlc.c
@@ -522,6 +522,8 @@ static int lpc32xx_write_page_lowlevel(struct mtd_info *mtd,
 		memcpy(dma_buf, buf, mtd->writesize);
 	}
 
+	nand_prog_page_begin_op(chip, page, 0, NULL, 0);
+
 	for (i = 0; i < host->mlcsubpages; i++) {
 		/* Start Encode */
 		writeb(0x00, MLC_ECC_ENC_REG(host->io_base));
@@ -550,7 +552,8 @@ static int lpc32xx_write_page_lowlevel(struct mtd_info *mtd,
 		/* Wait for Controller Ready */
 		lpc32xx_waitfunc_controller(mtd, chip);
 	}
-	return 0;
+
+	return nand_prog_page_end_op(chip);
 }
 
 static int lpc32xx_read_oob(struct mtd_info *mtd, struct nand_chip *chip,
diff --git a/drivers/mtd/nand/lpc32xx_slc.c b/drivers/mtd/nand/lpc32xx_slc.c
index 2b96c281b1a2..5f7cc6da0a7f 100644
--- a/drivers/mtd/nand/lpc32xx_slc.c
+++ b/drivers/mtd/nand/lpc32xx_slc.c
@@ -686,6 +686,8 @@ static int lpc32xx_nand_write_page_syndrome(struct mtd_info *mtd,
 	uint8_t *pb;
 	int error;
 
+	nand_prog_page_begin_op(chip, page, 0, NULL, 0);
+
 	/* Write data, calculate ECC on outbound data */
 	error = lpc32xx_xfer(mtd, (uint8_t *)buf, chip->ecc.steps, 0);
 	if (error)
@@ -704,7 +706,8 @@ static int lpc32xx_nand_write_page_syndrome(struct mtd_info *mtd,
 
 	/* Write ECC data to device */
 	chip->write_buf(mtd, chip->oob_poi, mtd->oobsize);
-	return 0;
+
+	return nand_prog_page_end_op(chip);
 }
 
 /*
@@ -717,9 +720,11 @@ static int lpc32xx_nand_write_page_raw_syndrome(struct mtd_info *mtd,
 						int oob_required, int page)
 {
 	/* Raw writes can just use the FIFO interface */
-	chip->write_buf(mtd, buf, chip->ecc.size * chip->ecc.steps);
+	nand_prog_page_begin_op(chip, page, 0, buf,
+				chip->ecc.size * chip->ecc.steps);
 	chip->write_buf(mtd, chip->oob_poi, mtd->oobsize);
-	return 0;
+
+	return nand_prog_page_end_op(chip);
 }
 
 static int lpc32xx_nand_dma_setup(struct lpc32xx_nand_host *host)
diff --git a/drivers/mtd/nand/mtk_nand.c b/drivers/mtd/nand/mtk_nand.c
index 1a3fcb65f478..005dabfc7f37 100644
--- a/drivers/mtd/nand/mtk_nand.c
+++ b/drivers/mtd/nand/mtk_nand.c
@@ -761,6 +761,8 @@ static int mtk_nfc_write_page(struct mtd_info *mtd, struct nand_chip *chip,
 	u32 reg;
 	int ret;
 
+	nand_prog_page_begin_op(chip, page, 0, NULL, 0);
+
 	if (!raw) {
 		/* OOB => FDM: from register,  ECC: from HW */
 		reg = nfi_readw(nfc, NFI_CNFG) | CNFG_AUTO_FMT_EN;
@@ -794,7 +796,10 @@ static int mtk_nfc_write_page(struct mtd_info *mtd, struct nand_chip *chip,
 	if (!raw)
 		mtk_ecc_disable(nfc->ecc);
 
-	return ret;
+	if (ret)
+		return ret;
+
+	return nand_prog_page_end_op(chip);
 }
 
 static int mtk_nfc_write_page_hwecc(struct mtd_info *mtd,
@@ -889,8 +894,7 @@ static int mtk_nfc_read_subpage(struct mtd_info *mtd, struct nand_chip *chip,
 	len = sectors * chip->ecc.size + (raw ? sectors * spare : 0);
 	buf = bufpoi + start * chip->ecc.size;
 
-	if (column != 0)
-		chip->cmdfunc(mtd, NAND_CMD_RNDOUT, column, -1);
+	nand_read_page_op(chip, page, column, NULL, 0);
 
 	addr = dma_map_single(nfc->dev, buf, len, DMA_FROM_DEVICE);
 	rc = dma_mapping_error(nfc->dev, addr);
diff --git a/drivers/mtd/nand/nand_base.c b/drivers/mtd/nand/nand_base.c
index f7dd7d5a84b3..318595c29053 100644
--- a/drivers/mtd/nand/nand_base.c
+++ b/drivers/mtd/nand/nand_base.c
@@ -1908,7 +1908,7 @@ int nand_read_page_raw(struct mtd_info *mtd, struct nand_chip *chip,
 {
 	int ret;
 
-	ret = nand_read_data_op(chip, buf, mtd->writesize, false);
+	ret = nand_read_page_op(chip, page, 0, buf, mtd->writesize);
 	if (ret)
 		return ret;
 
@@ -1942,6 +1942,10 @@ static int nand_read_page_raw_syndrome(struct mtd_info *mtd,
 	uint8_t *oob = chip->oob_poi;
 	int steps, size, ret;
 
+	ret = nand_read_page_op(chip, page, 0, NULL, 0);
+	if (ret)
+		return ret;
+
 	for (steps = chip->ecc.steps; steps > 0; steps--) {
 		ret = nand_read_data_op(chip, buf, eccsize, false);
 		if (ret)
@@ -2064,11 +2068,10 @@ static int nand_read_subpage(struct mtd_info *mtd, struct nand_chip *chip,
 
 	data_col_addr = start_step * chip->ecc.size;
 	/* If we read not a page aligned data */
-	if (data_col_addr != 0)
-		chip->cmdfunc(mtd, NAND_CMD_RNDOUT, data_col_addr, -1);
-
 	p = bufpoi + data_col_addr;
-	nand_read_data_op(chip, p, datafrag_len, false);
+	ret = nand_read_page_op(chip, page, data_col_addr, p, datafrag_len);
+	if (ret)
+		return ret;
 
 	/* Calculate ECC */
 	for (i = 0; i < eccfrag_len ; i += chip->ecc.bytes, p += chip->ecc.size)
@@ -2164,6 +2167,10 @@ static int nand_read_page_hwecc(struct mtd_info *mtd, struct nand_chip *chip,
 	uint8_t *ecc_code = chip->buffers->ecccode;
 	unsigned int max_bitflips = 0;
 
+	ret = nand_read_page_op(chip, page, 0, NULL, 0);
+	if (ret)
+		return ret;
+
 	for (i = 0; eccsteps; eccsteps--, i += eccbytes, p += eccsize) {
 		chip->ecc.hwctl(mtd, NAND_ECC_READ);
 
@@ -2299,6 +2306,10 @@ static int nand_read_page_syndrome(struct mtd_info *mtd, struct nand_chip *chip,
 	uint8_t *oob = chip->oob_poi;
 	unsigned int max_bitflips = 0;
 
+	ret = nand_read_page_op(chip, page, 0, NULL, 0);
+	if (ret)
+		return ret;
+
 	for (i = 0; eccsteps; eccsteps--, i += eccbytes, p += eccsize) {
 		int stat;
 
@@ -2481,9 +2492,6 @@ static int nand_do_read_ops(struct mtd_info *mtd, loff_t from,
 						 __func__, buf);
 
 read_retry:
-			if (nand_standard_page_accessors(&chip->ecc))
-				nand_read_page_op(chip, page, 0, NULL, 0);
-
 			/*
 			 * Now read the page into the buffer.  Absent an error,
 			 * the read methods return max bitflips per ecc step.
@@ -2931,7 +2939,7 @@ int nand_write_page_raw(struct mtd_info *mtd, struct nand_chip *chip,
 {
 	int ret;
 
-	ret = nand_write_data_op(chip, buf, mtd->writesize, false);
+	ret = nand_prog_page_begin_op(chip, page, 0, buf, mtd->writesize);
 	if (ret)
 		return ret;
 
@@ -2942,7 +2950,7 @@ int nand_write_page_raw(struct mtd_info *mtd, struct nand_chip *chip,
 			return ret;
 	}
 
-	return 0;
+	return nand_prog_page_end_op(chip);
 }
 EXPORT_SYMBOL(nand_write_page_raw);
 
@@ -2966,6 +2974,10 @@ static int nand_write_page_raw_syndrome(struct mtd_info *mtd,
 	uint8_t *oob = chip->oob_poi;
 	int steps, size, ret;
 
+	ret = nand_prog_page_begin_op(chip, page, 0, NULL, 0);
+	if (ret)
+		return ret;
+
 	for (steps = chip->ecc.steps; steps > 0; steps--) {
 		ret = nand_write_data_op(chip, buf, eccsize, false);
 		if (ret)
@@ -3005,7 +3017,7 @@ static int nand_write_page_raw_syndrome(struct mtd_info *mtd,
 			return ret;
 	}
 
-	return 0;
+	return nand_prog_page_end_op(chip);
 }
 /**
  * nand_write_page_swecc - [REPLACEABLE] software ECC based page write function
@@ -3236,12 +3248,6 @@ static int nand_write_page(struct mtd_info *mtd, struct nand_chip *chip,
 	else
 		subpage = 0;
 
-	if (nand_standard_page_accessors(&chip->ecc)) {
-		status = nand_prog_page_begin_op(chip, page, 0, NULL, 0);
-		if (status)
-			return status;
-	}
-
 	if (unlikely(raw))
 		status = chip->ecc.write_page_raw(mtd, chip, buf,
 						  oob_required, page);
@@ -3255,9 +3261,6 @@ static int nand_write_page(struct mtd_info *mtd, struct nand_chip *chip,
 	if (status < 0)
 		return status;
 
-	if (nand_standard_page_accessors(&chip->ecc))
-		return nand_prog_page_end_op(chip);
-
 	return 0;
 }
 
@@ -5235,26 +5238,6 @@ static bool nand_ecc_strength_good(struct mtd_info *mtd)
 	return corr >= ds_corr && ecc->strength >= chip->ecc_strength_ds;
 }
 
-static bool invalid_ecc_page_accessors(struct nand_chip *chip)
-{
-	struct nand_ecc_ctrl *ecc = &chip->ecc;
-
-	if (nand_standard_page_accessors(ecc))
-		return false;
-
-	/*
-	 * NAND_ECC_CUSTOM_PAGE_ACCESS flag is set, make sure the NAND
-	 * controller driver implements all the page accessors because
-	 * default helpers are not suitable when the core does not
-	 * send the READ0/PAGEPROG commands.
-	 */
-	return (!ecc->read_page || !ecc->write_page ||
-		!ecc->read_page_raw || !ecc->write_page_raw ||
-		(NAND_HAS_SUBPAGE_READ(chip) && !ecc->read_subpage) ||
-		(NAND_HAS_SUBPAGE_WRITE(chip) && !ecc->write_subpage &&
-		 ecc->hwctl && ecc->calculate));
-}
-
 /**
  * nand_scan_tail - [NAND Interface] Scan for the NAND device
  * @mtd: MTD device structure
@@ -5276,11 +5259,6 @@ int nand_scan_tail(struct mtd_info *mtd)
 		return -EINVAL;
 	}
 
-	if (invalid_ecc_page_accessors(chip)) {
-		pr_err("Invalid ECC page accessors setup\n");
-		return -EINVAL;
-	}
-
 	if (!(chip->options & NAND_OWN_BUFFERS)) {
 		nbuf = kzalloc(sizeof(*nbuf), GFP_KERNEL);
 		if (!nbuf)
diff --git a/drivers/mtd/nand/nand_micron.c b/drivers/mtd/nand/nand_micron.c
index 3934539a7468..543352380ffa 100644
--- a/drivers/mtd/nand/nand_micron.c
+++ b/drivers/mtd/nand/nand_micron.c
@@ -272,7 +272,6 @@ static int micron_nand_init(struct nand_chip *chip)
 			return -EINVAL;
 		}
 
-		chip->ecc.options = NAND_ECC_CUSTOM_PAGE_ACCESS;
 		chip->ecc.bytes = 8;
 		chip->ecc.size = 512;
 		chip->ecc.strength = 4;
diff --git a/drivers/mtd/nand/sh_flctl.c b/drivers/mtd/nand/sh_flctl.c
index 3c5008a4f5f3..c4e7755448e6 100644
--- a/drivers/mtd/nand/sh_flctl.c
+++ b/drivers/mtd/nand/sh_flctl.c
@@ -614,7 +614,7 @@ static void set_cmd_regs(struct mtd_info *mtd, uint32_t cmd, uint32_t flcmcdr_va
 static int flctl_read_page_hwecc(struct mtd_info *mtd, struct nand_chip *chip,
 				uint8_t *buf, int oob_required, int page)
 {
-	chip->read_buf(mtd, buf, mtd->writesize);
+	nand_read_page_op(chip, page, 0, buf, mtd->writesize);
 	if (oob_required)
 		chip->read_buf(mtd, chip->oob_poi, mtd->oobsize);
 	return 0;
@@ -624,9 +624,9 @@ static int flctl_write_page_hwecc(struct mtd_info *mtd, struct nand_chip *chip,
 				  const uint8_t *buf, int oob_required,
 				  int page)
 {
-	chip->write_buf(mtd, buf, mtd->writesize);
+	nand_prog_page_begin_op(chip, page, 0, buf, mtd->writesize);
 	chip->write_buf(mtd, chip->oob_poi, mtd->oobsize);
-	return 0;
+	return nand_prog_page_end_op(chip);
 }
 
 static void execmd_read_page_sector(struct mtd_info *mtd, int page_addr)
diff --git a/drivers/mtd/nand/sunxi_nand.c b/drivers/mtd/nand/sunxi_nand.c
index 230d037d05d2..2354410d8c48 100644
--- a/drivers/mtd/nand/sunxi_nand.c
+++ b/drivers/mtd/nand/sunxi_nand.c
@@ -1245,6 +1245,8 @@ static int sunxi_nfc_hw_ecc_read_page(struct mtd_info *mtd,
 	int ret, i, cur_off = 0;
 	bool raw_mode = false;
 
+	nand_read_page_op(chip, page, 0, NULL, 0);
+
 	sunxi_nfc_hw_ecc_enable(mtd);
 
 	for (i = 0; i < ecc->steps; i++) {
@@ -1278,14 +1280,14 @@ static int sunxi_nfc_hw_ecc_read_page_dma(struct mtd_info *mtd,
 {
 	int ret;
 
+	nand_read_page_op(chip, page, 0, NULL, 0);
+
 	ret = sunxi_nfc_hw_ecc_read_chunks_dma(mtd, buf, oob_required, page,
 					       chip->ecc.steps);
 	if (ret >= 0)
 		return ret;
 
 	/* Fallback to PIO mode */
-	chip->cmdfunc(mtd, NAND_CMD_RNDOUT, 0, -1);
-
 	return sunxi_nfc_hw_ecc_read_page(mtd, chip, buf, oob_required, page);
 }
 
@@ -1298,6 +1300,8 @@ static int sunxi_nfc_hw_ecc_read_subpage(struct mtd_info *mtd,
 	int ret, i, cur_off = 0;
 	unsigned int max_bitflips = 0;
 
+	nand_read_page_op(chip, page, 0, NULL, 0);
+
 	sunxi_nfc_hw_ecc_enable(mtd);
 
 	for (i = data_offs / ecc->size;
@@ -1329,13 +1333,13 @@ static int sunxi_nfc_hw_ecc_read_subpage_dma(struct mtd_info *mtd,
 	int nchunks = DIV_ROUND_UP(data_offs + readlen, chip->ecc.size);
 	int ret;
 
+	nand_read_page_op(chip, page, 0, NULL, 0);
+
 	ret = sunxi_nfc_hw_ecc_read_chunks_dma(mtd, buf, false, page, nchunks);
 	if (ret >= 0)
 		return ret;
 
 	/* Fallback to PIO mode */
-	chip->cmdfunc(mtd, NAND_CMD_RNDOUT, 0, -1);
-
 	return sunxi_nfc_hw_ecc_read_subpage(mtd, chip, data_offs, readlen,
 					     buf, page);
 }
@@ -1348,6 +1352,8 @@ static int sunxi_nfc_hw_ecc_write_page(struct mtd_info *mtd,
 	struct nand_ecc_ctrl *ecc = &chip->ecc;
 	int ret, i, cur_off = 0;
 
+	nand_prog_page_begin_op(chip, page, 0, NULL, 0);
+
 	sunxi_nfc_hw_ecc_enable(mtd);
 
 	for (i = 0; i < ecc->steps; i++) {
@@ -1369,7 +1375,7 @@ static int sunxi_nfc_hw_ecc_write_page(struct mtd_info *mtd,
 
 	sunxi_nfc_hw_ecc_disable(mtd);
 
-	return 0;
+	return nand_prog_page_end_op(chip);
 }
 
 static int sunxi_nfc_hw_ecc_write_subpage(struct mtd_info *mtd,
@@ -1381,6 +1387,8 @@ static int sunxi_nfc_hw_ecc_write_subpage(struct mtd_info *mtd,
 	struct nand_ecc_ctrl *ecc = &chip->ecc;
 	int ret, i, cur_off = 0;
 
+	nand_prog_page_begin_op(chip, page, 0, NULL, 0);
+
 	sunxi_nfc_hw_ecc_enable(mtd);
 
 	for (i = data_offs / ecc->size;
@@ -1399,7 +1407,7 @@ static int sunxi_nfc_hw_ecc_write_subpage(struct mtd_info *mtd,
 
 	sunxi_nfc_hw_ecc_disable(mtd);
 
-	return 0;
+	return nand_prog_page_end_op(chip);
 }
 
 static int sunxi_nfc_hw_ecc_write_page_dma(struct mtd_info *mtd,
@@ -1429,6 +1437,8 @@ static int sunxi_nfc_hw_ecc_write_page_dma(struct mtd_info *mtd,
 		sunxi_nfc_hw_ecc_set_prot_oob_bytes(mtd, oob, i, !i, page);
 	}
 
+	nand_prog_page_begin_op(chip, page, 0, NULL, 0);
+
 	sunxi_nfc_hw_ecc_enable(mtd);
 	sunxi_nfc_randomizer_config(mtd, page, false);
 	sunxi_nfc_randomizer_enable(mtd);
@@ -1459,7 +1469,7 @@ static int sunxi_nfc_hw_ecc_write_page_dma(struct mtd_info *mtd,
 		sunxi_nfc_hw_ecc_write_extra_oob(mtd, chip->oob_poi,
 						 NULL, page);
 
-	return 0;
+	return nand_prog_page_end_op(chip);
 
 pio_fallback:
 	return sunxi_nfc_hw_ecc_write_page(mtd, chip, buf, oob_required, page);
@@ -1475,6 +1485,8 @@ static int sunxi_nfc_hw_syndrome_ecc_read_page(struct mtd_info *mtd,
 	int ret, i, cur_off = 0;
 	bool raw_mode = false;
 
+	nand_read_page_op(chip, page, 0, NULL, 0);
+
 	sunxi_nfc_hw_ecc_enable(mtd);
 
 	for (i = 0; i < ecc->steps; i++) {
@@ -1511,6 +1523,8 @@ static int sunxi_nfc_hw_syndrome_ecc_write_page(struct mtd_info *mtd,
 	struct nand_ecc_ctrl *ecc = &chip->ecc;
 	int ret, i, cur_off = 0;
 
+	nand_prog_page_begin_op(chip, page, 0, NULL, 0);
+
 	sunxi_nfc_hw_ecc_enable(mtd);
 
 	for (i = 0; i < ecc->steps; i++) {
@@ -1532,15 +1546,13 @@ static int sunxi_nfc_hw_syndrome_ecc_write_page(struct mtd_info *mtd,
 
 	sunxi_nfc_hw_ecc_disable(mtd);
 
-	return 0;
+	return nand_prog_page_end_op(chip);
 }
 
 static int sunxi_nfc_hw_common_ecc_read_oob(struct mtd_info *mtd,
 					    struct nand_chip *chip,
 					    int page)
 {
-	nand_read_page_op(chip, page, 0, NULL, 0);
-
 	chip->pagebuf = -1;
 
 	return chip->ecc.read_page(mtd, chip, chip->buffers->databuf, 1, page);
@@ -1552,8 +1564,6 @@ static int sunxi_nfc_hw_common_ecc_write_oob(struct mtd_info *mtd,
 {
 	int ret;
 
-	nand_prog_page_begin_op(chip, page, 0, NULL, 0);
-
 	chip->pagebuf = -1;
 
 	memset(chip->buffers->databuf, 0xff, mtd->writesize);
diff --git a/drivers/mtd/nand/tango_nand.c b/drivers/mtd/nand/tango_nand.c
index 97a300b46b1d..c5bee00b7f5e 100644
--- a/drivers/mtd/nand/tango_nand.c
+++ b/drivers/mtd/nand/tango_nand.c
@@ -580,7 +580,6 @@ static int chip_init(struct device *dev, struct device_node *np)
 	ecc->write_page = tango_write_page;
 	ecc->read_oob = tango_read_oob;
 	ecc->write_oob = tango_write_oob;
-	ecc->options = NAND_ECC_CUSTOM_PAGE_ACCESS;
 
 	err = nand_scan_tail(mtd);
 	if (err)
diff --git a/drivers/mtd/nand/vf610_nfc.c b/drivers/mtd/nand/vf610_nfc.c
index 8037d4b48a05..80d31a58e558 100644
--- a/drivers/mtd/nand/vf610_nfc.c
+++ b/drivers/mtd/nand/vf610_nfc.c
@@ -560,7 +560,7 @@ static int vf610_nfc_read_page(struct mtd_info *mtd, struct nand_chip *chip,
 	int eccsize = chip->ecc.size;
 	int stat;
 
-	vf610_nfc_read_buf(mtd, buf, eccsize);
+	nand_read_page_op(chip, page, 0, buf, eccsize);
 	if (oob_required)
 		vf610_nfc_read_buf(mtd, chip->oob_poi, mtd->oobsize);
 
@@ -580,7 +580,7 @@ static int vf610_nfc_write_page(struct mtd_info *mtd, struct nand_chip *chip,
 {
 	struct vf610_nfc *nfc = mtd_to_nfc(mtd);
 
-	vf610_nfc_write_buf(mtd, buf, mtd->writesize);
+	nand_prog_page_begin_op(chip, page, 0, buf, mtd->writesize);
 	if (oob_required)
 		vf610_nfc_write_buf(mtd, chip->oob_poi, mtd->oobsize);
 
@@ -588,7 +588,7 @@ static int vf610_nfc_write_page(struct mtd_info *mtd, struct nand_chip *chip,
 	nfc->use_hw_ecc = true;
 	nfc->write_sz = mtd->writesize + mtd->oobsize;
 
-	return 0;
+	return nand_prog_page_end_op(chip);
 }
 
 static const struct of_device_id vf610_nfc_dt_ids[] = {
diff --git a/drivers/staging/mt29f_spinand/mt29f_spinand.c b/drivers/staging/mt29f_spinand/mt29f_spinand.c
index 87595c594b12..e396075dd508 100644
--- a/drivers/staging/mt29f_spinand/mt29f_spinand.c
+++ b/drivers/staging/mt29f_spinand/mt29f_spinand.c
@@ -636,9 +636,12 @@ static int spinand_write_page_hwecc(struct mtd_info *mtd,
 	int eccsize = chip->ecc.size;
 	int eccsteps = chip->ecc.steps;
 
+	nand_prog_page_begin_op(chip, page, 0, NULL, 0);
+
 	enable_hw_ecc = 1;
 	chip->write_buf(mtd, p, eccsize * eccsteps);
-	return 0;
+
+	return nand_prog_page_end_op(chip);
 }
 
 static int spinand_read_page_hwecc(struct mtd_info *mtd, struct nand_chip *chip,
@@ -653,7 +656,7 @@ static int spinand_read_page_hwecc(struct mtd_info *mtd, struct nand_chip *chip,
 
 	enable_read_hw_ecc = 1;
 
-	chip->read_buf(mtd, p, eccsize * eccsteps);
+	nand_read_page_op(chip, page, 0, p, eccsize * eccsteps);
 	if (oob_required)
 		chip->read_buf(mtd, chip->oob_poi, mtd->oobsize);
 
diff --git a/include/linux/mtd/rawnand.h b/include/linux/mtd/rawnand.h
index 8b8acde72465..04f4cfbe6c09 100644
--- a/include/linux/mtd/rawnand.h
+++ b/include/linux/mtd/rawnand.h
@@ -133,12 +133,6 @@ enum nand_ecc_algo {
  */
 #define NAND_ECC_GENERIC_ERASED_CHECK	BIT(0)
 #define NAND_ECC_MAXIMIZE		BIT(1)
-/*
- * If your controller already sends the required NAND commands when
- * reading or writing a page, then the framework is not supposed to
- * send READ0 and SEQIN/PAGEPROG respectively.
- */
-#define NAND_ECC_CUSTOM_PAGE_ACCESS	BIT(2)
 
 /* Bit mask for flags passed to do_nand_read_ecc */
 #define NAND_GET_DEVICE		0x80
@@ -602,11 +596,6 @@ struct nand_ecc_ctrl {
 			int page);
 };
 
-static inline int nand_standard_page_accessors(struct nand_ecc_ctrl *ecc)
-{
-	return !(ecc->options & NAND_ECC_CUSTOM_PAGE_ACCESS);
-}
-
 /**
  * struct nand_buffers - buffer structure for read/write
  * @ecccalc:	buffer pointer for calculated ECC, size is oobsize.
-- 
2.11.0

^ permalink raw reply related	[flat|nested] 75+ messages in thread

* [RFC 02/12] mtd: nand: force drivers to explicitly send READ/PROG commands
@ 2017-10-18 14:36   ` Miquel Raynal
  0 siblings, 0 replies; 75+ messages in thread
From: Miquel Raynal @ 2017-10-18 14:36 UTC (permalink / raw)
  To: linux-arm-kernel

From: Boris Brezillon <boris.brezillon@free-electrons.com>

The core currently send the READ0 and SEQIN+PAGEPROG commands in
nand_do_read/write_ops(). This is inconsistent with
->read/write_oob[_raw]() hooks behavior which are expected to send
these commands.

There's already a flag (NAND_ECC_CUSTOM_PAGE_ACCESS) to inform the core
that a specific controller wants to send the READ/SEQIN+PAGEPROG
commands on its own, but it's an opt-in flag, and existing drivers are
unlikely to be updated to pass it.

Moreover, some controllers cannot dissociate the READ/PAGEPROG commands
from the associated data transfer and ECC engine activation, and
developers have to hack things in their ->cmdfunc() implementation to
handle such complex cases, or have to accept the perf penalty of sending
twice the same command.
To address this problem we are planning on adding a new interface which
is passed all information about a NAND operation (including the amount
of data to transfer) and replacing all calls to ->cmdfunc() to calls to
this new ->exec_op() hook. But, in order to do that, we need to have all
->cmdfunc() calls placed near their associated ->read/write_buf/byte()
calls.

Modify the core and relevant drivers to make NAND_ECC_CUSTOM_PAGE_ACCESS
the default case, and remove this flag.

Signed-off-by: Boris Brezillon <boris.brezillon@free-electrons.com>
[miquel.raynal at free-electrons.com: rebased and fixed some conflicts]
Signed-off-by: Miquel Raynal <miquel.raynal@free-electrons.com>
---
 drivers/mtd/nand/atmel/nand-controller.c      |  7 ++-
 drivers/mtd/nand/bf5xx_nand.c                 |  6 ++-
 drivers/mtd/nand/brcmnand/brcmnand.c          | 13 +++--
 drivers/mtd/nand/cafe_nand.c                  |  6 +--
 drivers/mtd/nand/denali.c                     | 14 +++++-
 drivers/mtd/nand/docg4.c                      | 12 +++--
 drivers/mtd/nand/fsl_elbc_nand.c              |  6 +--
 drivers/mtd/nand/fsl_ifc_nand.c               |  6 +--
 drivers/mtd/nand/gpmi-nand/gpmi-nand.c        | 30 ++++++------
 drivers/mtd/nand/hisi504_nand.c               |  6 +--
 drivers/mtd/nand/lpc32xx_mlc.c                |  5 +-
 drivers/mtd/nand/lpc32xx_slc.c                | 11 +++--
 drivers/mtd/nand/mtk_nand.c                   | 10 ++--
 drivers/mtd/nand/nand_base.c                  | 68 +++++++++------------------
 drivers/mtd/nand/nand_micron.c                |  1 -
 drivers/mtd/nand/sh_flctl.c                   |  6 +--
 drivers/mtd/nand/sunxi_nand.c                 | 34 +++++++++-----
 drivers/mtd/nand/tango_nand.c                 |  1 -
 drivers/mtd/nand/vf610_nfc.c                  |  6 +--
 drivers/staging/mt29f_spinand/mt29f_spinand.c |  7 ++-
 include/linux/mtd/rawnand.h                   | 11 -----
 21 files changed, 142 insertions(+), 124 deletions(-)

diff --git a/drivers/mtd/nand/atmel/nand-controller.c b/drivers/mtd/nand/atmel/nand-controller.c
index 0b2c82727b24..6add993ea1e0 100644
--- a/drivers/mtd/nand/atmel/nand-controller.c
+++ b/drivers/mtd/nand/atmel/nand-controller.c
@@ -841,6 +841,8 @@ static int atmel_nand_pmecc_write_pg(struct nand_chip *chip, const u8 *buf,
 	struct atmel_nand *nand = to_atmel_nand(chip);
 	int ret;
 
+	nand_prog_page_begin_op(chip, page, 0, NULL, 0);
+
 	ret = atmel_nand_pmecc_enable(chip, NAND_ECC_WRITE, raw);
 	if (ret)
 		return ret;
@@ -857,7 +859,7 @@ static int atmel_nand_pmecc_write_pg(struct nand_chip *chip, const u8 *buf,
 
 	atmel_nand_write_buf(mtd, chip->oob_poi, mtd->oobsize);
 
-	return 0;
+	return nand_prog_page_end_op(chip);
 }
 
 static int atmel_nand_pmecc_write_page(struct mtd_info *mtd,
@@ -881,6 +883,8 @@ static int atmel_nand_pmecc_read_pg(struct nand_chip *chip, u8 *buf,
 	struct mtd_info *mtd = nand_to_mtd(chip);
 	int ret;
 
+	nand_read_page_op(chip, page, 0, NULL, 0);
+
 	ret = atmel_nand_pmecc_enable(chip, NAND_ECC_READ, raw);
 	if (ret)
 		return ret;
@@ -1178,7 +1182,6 @@ static int atmel_hsmc_nand_ecc_init(struct atmel_nand *nand)
 	chip->ecc.write_page = atmel_hsmc_nand_pmecc_write_page;
 	chip->ecc.read_page_raw = atmel_hsmc_nand_pmecc_read_page_raw;
 	chip->ecc.write_page_raw = atmel_hsmc_nand_pmecc_write_page_raw;
-	chip->ecc.options |= NAND_ECC_CUSTOM_PAGE_ACCESS;
 
 	return 0;
 }
diff --git a/drivers/mtd/nand/bf5xx_nand.c b/drivers/mtd/nand/bf5xx_nand.c
index 5655dca6ce43..87bbd177b3e5 100644
--- a/drivers/mtd/nand/bf5xx_nand.c
+++ b/drivers/mtd/nand/bf5xx_nand.c
@@ -572,6 +572,8 @@ static void bf5xx_nand_dma_write_buf(struct mtd_info *mtd,
 static int bf5xx_nand_read_page_raw(struct mtd_info *mtd, struct nand_chip *chip,
 		uint8_t *buf, int oob_required, int page)
 {
+	nand_read_page_op(chip, page, 0, NULL, 0);
+
 	bf5xx_nand_read_buf(mtd, buf, mtd->writesize);
 	bf5xx_nand_read_buf(mtd, chip->oob_poi, mtd->oobsize);
 
@@ -582,10 +584,10 @@ static int bf5xx_nand_write_page_raw(struct mtd_info *mtd,
 		struct nand_chip *chip,	const uint8_t *buf, int oob_required,
 		int page)
 {
-	bf5xx_nand_write_buf(mtd, buf, mtd->writesize);
+	nand_prog_page_begin_op(chip, page, 0, buf, mtd->writesize);
 	bf5xx_nand_write_buf(mtd, chip->oob_poi, mtd->oobsize);
 
-	return 0;
+	return nand_prog_page_end_op(chip);
 }
 
 /*
diff --git a/drivers/mtd/nand/brcmnand/brcmnand.c b/drivers/mtd/nand/brcmnand/brcmnand.c
index 3f441096a14c..e6879d4d53ca 100644
--- a/drivers/mtd/nand/brcmnand/brcmnand.c
+++ b/drivers/mtd/nand/brcmnand/brcmnand.c
@@ -1689,7 +1689,6 @@ static int brcmstb_nand_verify_erased_page(struct mtd_info *mtd,
 	sas = mtd->oobsize / chip->ecc.steps;
 
 	/* read without ecc for verification */
-	nand_read_page_op(chip, page, 0, NULL, 0);
 	ret = chip->ecc.read_page_raw(mtd, chip, buf, true, page);
 	if (ret)
 		return ret;
@@ -1793,6 +1792,8 @@ static int brcmnand_read_page(struct mtd_info *mtd, struct nand_chip *chip,
 	struct brcmnand_host *host = nand_get_controller_data(chip);
 	u8 *oob = oob_required ? (u8 *)chip->oob_poi : NULL;
 
+	nand_read_page_op(chip, page, 0, NULL, 0);
+
 	return brcmnand_read(mtd, chip, host->last_addr,
 			mtd->writesize >> FC_SHIFT, (u32 *)buf, oob);
 }
@@ -1804,6 +1805,8 @@ static int brcmnand_read_page_raw(struct mtd_info *mtd, struct nand_chip *chip,
 	u8 *oob = oob_required ? (u8 *)chip->oob_poi : NULL;
 	int ret;
 
+	nand_read_page_op(chip, page, 0, NULL, 0);
+
 	brcmnand_set_ecc_enabled(host, 0);
 	ret = brcmnand_read(mtd, chip, host->last_addr,
 			mtd->writesize >> FC_SHIFT, (u32 *)buf, oob);
@@ -1909,8 +1912,10 @@ static int brcmnand_write_page(struct mtd_info *mtd, struct nand_chip *chip,
 	struct brcmnand_host *host = nand_get_controller_data(chip);
 	void *oob = oob_required ? chip->oob_poi : NULL;
 
+	nand_prog_page_begin_op(chip, page, 0, NULL, 0);
 	brcmnand_write(mtd, chip, host->last_addr, (const u32 *)buf, oob);
-	return 0;
+
+	return nand_prog_page_end_op(chip);
 }
 
 static int brcmnand_write_page_raw(struct mtd_info *mtd,
@@ -1920,10 +1925,12 @@ static int brcmnand_write_page_raw(struct mtd_info *mtd,
 	struct brcmnand_host *host = nand_get_controller_data(chip);
 	void *oob = oob_required ? chip->oob_poi : NULL;
 
+	nand_prog_page_begin_op(chip, page, 0, NULL, 0);
 	brcmnand_set_ecc_enabled(host, 0);
 	brcmnand_write(mtd, chip, host->last_addr, (const u32 *)buf, oob);
 	brcmnand_set_ecc_enabled(host, 1);
-	return 0;
+
+	return nand_prog_page_end_op(chip);
 }
 
 static int brcmnand_write_oob(struct mtd_info *mtd, struct nand_chip *chip,
diff --git a/drivers/mtd/nand/cafe_nand.c b/drivers/mtd/nand/cafe_nand.c
index 95c2cfa68b66..de36762e3058 100644
--- a/drivers/mtd/nand/cafe_nand.c
+++ b/drivers/mtd/nand/cafe_nand.c
@@ -383,7 +383,7 @@ static int cafe_nand_read_page(struct mtd_info *mtd, struct nand_chip *chip,
 		     cafe_readl(cafe, NAND_ECC_RESULT),
 		     cafe_readl(cafe, NAND_ECC_SYN01));
 
-	chip->read_buf(mtd, buf, mtd->writesize);
+	nand_read_page_op(chip, page, 0, buf, mtd->writesize);
 	chip->read_buf(mtd, chip->oob_poi, mtd->oobsize);
 
 	if (checkecc && cafe_readl(cafe, NAND_ECC_RESULT) & (1<<18)) {
@@ -541,13 +541,13 @@ static int cafe_nand_write_page_lowlevel(struct mtd_info *mtd,
 {
 	struct cafe_priv *cafe = nand_get_controller_data(chip);
 
-	chip->write_buf(mtd, buf, mtd->writesize);
+	nand_prog_page_begin_op(chip, page, 0, buf, mtd->writesize);
 	chip->write_buf(mtd, chip->oob_poi, mtd->oobsize);
 
 	/* Set up ECC autogeneration */
 	cafe->ctl2 |= (1<<30);
 
-	return 0;
+	return nand_prog_page_end_op(chip);
 }
 
 static int cafe_nand_block_bad(struct mtd_info *mtd, loff_t ofs)
diff --git a/drivers/mtd/nand/denali.c b/drivers/mtd/nand/denali.c
index f90f93a8fb08..e5f38359f6df 100644
--- a/drivers/mtd/nand/denali.c
+++ b/drivers/mtd/nand/denali.c
@@ -550,11 +550,14 @@ static int denali_pio_read(struct denali_nand_info *denali, void *buf,
 static int denali_pio_write(struct denali_nand_info *denali,
 			    const void *buf, size_t size, int page, int raw)
 {
+	struct nand_chip *chip = &denali->nand;
 	u32 addr = DENALI_MAP01 | DENALI_BANK(denali) | page;
 	const uint32_t *buf32 = (uint32_t *)buf;
 	uint32_t irq_status;
 	int i;
 
+	nand_prog_page_begin_op(chip, page, 0, NULL, 0);
+
 	denali_reset_irq(denali);
 
 	for (i = 0; i < size / 4; i++)
@@ -580,6 +583,7 @@ static int denali_pio_xfer(struct denali_nand_info *denali, void *buf,
 static int denali_dma_xfer(struct denali_nand_info *denali, void *buf,
 			   size_t size, int page, int raw, int write)
 {
+	struct nand_chip *chip = &denali->nand;
 	dma_addr_t dma_addr;
 	uint32_t irq_mask, irq_status, ecc_err_mask;
 	enum dma_data_direction dir = write ? DMA_TO_DEVICE : DMA_FROM_DEVICE;
@@ -625,7 +629,10 @@ static int denali_dma_xfer(struct denali_nand_info *denali, void *buf,
 	if (irq_status & INTR__ERASED_PAGE)
 		memset(buf, 0xff, size);
 
-	return ret;
+	if (ret)
+		return ret;
+
+	return nand_prog_page_end_op(chip);
 }
 
 static int denali_data_xfer(struct denali_nand_info *denali, void *buf,
@@ -790,6 +797,8 @@ static int denali_write_oob(struct mtd_info *mtd, struct nand_chip *chip,
 	struct denali_nand_info *denali = mtd_to_denali(mtd);
 	int status;
 
+	nand_read_page_op(chip, page, 0, NULL, 0);
+
 	denali_reset_irq(denali);
 
 	denali_oob_xfer(mtd, chip, page, 1);
@@ -846,6 +855,8 @@ static int denali_write_page_raw(struct mtd_info *mtd, struct nand_chip *chip,
 	size_t size = writesize + oobsize;
 	int i, pos, len;
 
+	nand_read_page_op(chip, page, 0, NULL, 0);
+
 	/*
 	 * Fill the buffer with 0xff first except the full page transfer.
 	 * This simplifies the logic.
@@ -1359,7 +1370,6 @@ int denali_init(struct denali_nand_info *denali)
 		chip->read_buf = denali_read_buf;
 		chip->write_buf = denali_write_buf;
 	}
-	chip->ecc.options |= NAND_ECC_CUSTOM_PAGE_ACCESS;
 	chip->ecc.read_page = denali_read_page;
 	chip->ecc.read_page_raw = denali_read_page_raw;
 	chip->ecc.write_page = denali_write_page;
diff --git a/drivers/mtd/nand/docg4.c b/drivers/mtd/nand/docg4.c
index 45c01b4b34c7..156fdf683b2f 100644
--- a/drivers/mtd/nand/docg4.c
+++ b/drivers/mtd/nand/docg4.c
@@ -785,6 +785,8 @@ static int read_page(struct mtd_info *mtd, struct nand_chip *nand,
 
 	dev_dbg(doc->dev, "%s: page %08x\n", __func__, page);
 
+	nand_read_page_op(nand, page, 0, NULL, 0);
+
 	writew(DOC_ECCCONF0_READ_MODE |
 	       DOC_ECCCONF0_ECC_ENABLE |
 	       DOC_ECCCONF0_UNKNOWN |
@@ -948,7 +950,7 @@ static int docg4_erase_block(struct mtd_info *mtd, int page)
 }
 
 static int write_page(struct mtd_info *mtd, struct nand_chip *nand,
-		       const uint8_t *buf, bool use_ecc)
+		      const uint8_t *buf, int page, bool use_ecc)
 {
 	struct docg4_priv *doc = nand_get_controller_data(nand);
 	void __iomem *docptr = doc->virtadr;
@@ -956,6 +958,8 @@ static int write_page(struct mtd_info *mtd, struct nand_chip *nand,
 
 	dev_dbg(doc->dev, "%s...\n", __func__);
 
+	nand_prog_page_begin_op(nand, page, 0, NULL, 0);
+
 	writew(DOC_ECCCONF0_ECC_ENABLE |
 	       DOC_ECCCONF0_UNKNOWN |
 	       DOCG4_BCH_SIZE,
@@ -1000,19 +1004,19 @@ static int write_page(struct mtd_info *mtd, struct nand_chip *nand,
 	writew(0, docptr + DOC_DATAEND);
 	write_nop(docptr);
 
-	return 0;
+	return nand_prog_page_end_op(nand);
 }
 
 static int docg4_write_page_raw(struct mtd_info *mtd, struct nand_chip *nand,
 				const uint8_t *buf, int oob_required, int page)
 {
-	return write_page(mtd, nand, buf, false);
+	return write_page(mtd, nand, buf, page, false);
 }
 
 static int docg4_write_page(struct mtd_info *mtd, struct nand_chip *nand,
 			     const uint8_t *buf, int oob_required, int page)
 {
-	return write_page(mtd, nand, buf, true);
+	return write_page(mtd, nand, buf, page, true);
 }
 
 static int docg4_write_oob(struct mtd_info *mtd, struct nand_chip *nand,
diff --git a/drivers/mtd/nand/fsl_elbc_nand.c b/drivers/mtd/nand/fsl_elbc_nand.c
index 17db2f90aa2c..a24c308a0b8a 100644
--- a/drivers/mtd/nand/fsl_elbc_nand.c
+++ b/drivers/mtd/nand/fsl_elbc_nand.c
@@ -713,7 +713,7 @@ static int fsl_elbc_read_page(struct mtd_info *mtd, struct nand_chip *chip,
 	struct fsl_lbc_ctrl *ctrl = priv->ctrl;
 	struct fsl_elbc_fcm_ctrl *elbc_fcm_ctrl = ctrl->nand;
 
-	fsl_elbc_read_buf(mtd, buf, mtd->writesize);
+	nand_read_page_op(chip, page, 0, buf, mtd->writesize);
 	if (oob_required)
 		fsl_elbc_read_buf(mtd, chip->oob_poi, mtd->oobsize);
 
@@ -729,10 +729,10 @@ static int fsl_elbc_read_page(struct mtd_info *mtd, struct nand_chip *chip,
 static int fsl_elbc_write_page(struct mtd_info *mtd, struct nand_chip *chip,
 				const uint8_t *buf, int oob_required, int page)
 {
-	fsl_elbc_write_buf(mtd, buf, mtd->writesize);
+	nand_prog_page_begin_op(chip, page, 0, buf, mtd->writesize);
 	fsl_elbc_write_buf(mtd, chip->oob_poi, mtd->oobsize);
 
-	return 0;
+	return nand_prog_page_end_op(chip);
 }
 
 /* ECC will be calculated automatically, and errors will be detected in
diff --git a/drivers/mtd/nand/fsl_ifc_nand.c b/drivers/mtd/nand/fsl_ifc_nand.c
index 9e03bac7f34c..857038fe874f 100644
--- a/drivers/mtd/nand/fsl_ifc_nand.c
+++ b/drivers/mtd/nand/fsl_ifc_nand.c
@@ -688,7 +688,7 @@ static int fsl_ifc_read_page(struct mtd_info *mtd, struct nand_chip *chip,
 	struct fsl_ifc_ctrl *ctrl = priv->ctrl;
 	struct fsl_ifc_nand_ctrl *nctrl = ifc_nand_ctrl;
 
-	fsl_ifc_read_buf(mtd, buf, mtd->writesize);
+	nand_read_page_op(chip, page, 0, buf, mtd->writesize);
 	if (oob_required)
 		fsl_ifc_read_buf(mtd, chip->oob_poi, mtd->oobsize);
 
@@ -711,10 +711,10 @@ static int fsl_ifc_read_page(struct mtd_info *mtd, struct nand_chip *chip,
 static int fsl_ifc_write_page(struct mtd_info *mtd, struct nand_chip *chip,
 			       const uint8_t *buf, int oob_required, int page)
 {
-	fsl_ifc_write_buf(mtd, buf, mtd->writesize);
+	nand_prog_page_begin_op(chip, page, 0, buf, mtd->writesize);
 	fsl_ifc_write_buf(mtd, chip->oob_poi, mtd->oobsize);
 
-	return 0;
+	return nand_prog_page_end_op(chip);
 }
 
 static int fsl_ifc_chip_init_tail(struct mtd_info *mtd)
diff --git a/drivers/mtd/nand/gpmi-nand/gpmi-nand.c b/drivers/mtd/nand/gpmi-nand/gpmi-nand.c
index c8ceaecd8065..3f2b903158c1 100644
--- a/drivers/mtd/nand/gpmi-nand/gpmi-nand.c
+++ b/drivers/mtd/nand/gpmi-nand/gpmi-nand.c
@@ -1043,6 +1043,8 @@ static int gpmi_ecc_read_page(struct mtd_info *mtd, struct nand_chip *chip,
 	unsigned int  max_bitflips = 0;
 	int           ret;
 
+	nand_read_page_op(chip, page, 0, NULL, 0);
+
 	dev_dbg(this->dev, "page number is : %d\n", page);
 	ret = read_page_prepare(this, buf, nfc_geo->payload_size,
 					this->payload_virt, this->payload_phys,
@@ -1220,12 +1222,13 @@ static int gpmi_ecc_read_subpage(struct mtd_info *mtd, struct nand_chip *chip,
 	meta = geo->metadata_size;
 	if (first) {
 		col = meta + (size + ecc_parity_size) * first;
-		chip->cmdfunc(mtd, NAND_CMD_RNDOUT, col, -1);
 
 		meta = 0;
 		buf = buf + first * size;
 	}
 
+	nand_read_page_op(chip, page, col, NULL, 0);
+
 	/* Save the old environment */
 	r1_old = r1_new = readl(bch_regs + HW_BCH_FLASH0LAYOUT0);
 	r2_old = r2_new = readl(bch_regs + HW_BCH_FLASH0LAYOUT1);
@@ -1277,6 +1280,9 @@ static int gpmi_ecc_write_page(struct mtd_info *mtd, struct nand_chip *chip,
 	int        ret;
 
 	dev_dbg(this->dev, "ecc write page.\n");
+
+	nand_prog_page_begin_op(chip, page, 0, NULL, 0);
+
 	if (this->swap_block_mark) {
 		/*
 		 * If control arrives here, we're doing block mark swapping.
@@ -1338,7 +1344,10 @@ static int gpmi_ecc_write_page(struct mtd_info *mtd, struct nand_chip *chip,
 				payload_virt, payload_phys);
 	}
 
-	return 0;
+	if (ret)
+		return ret;
+
+	return nand_prog_page_end_op(chip);
 }
 
 /*
@@ -1472,8 +1481,8 @@ static int gpmi_ecc_read_page_raw(struct mtd_info *mtd,
 	uint8_t *oob = chip->oob_poi;
 	int step;
 
-	chip->read_buf(mtd, tmp_buf,
-		       mtd->writesize + mtd->oobsize);
+	nand_read_page_op(chip, page, 0, tmp_buf,
+			  mtd->writesize + mtd->oobsize);
 
 	/*
 	 * If required, swap the bad block marker and the data stored in the
@@ -1617,24 +1626,19 @@ static int gpmi_ecc_write_page_raw(struct mtd_info *mtd,
 		tmp_buf[mtd->writesize] = swap;
 	}
 
-	chip->write_buf(mtd, tmp_buf, mtd->writesize + mtd->oobsize);
-
-	return 0;
+	return nand_prog_page_op(chip, page, 0, tmp_buf,
+				 mtd->writesize + mtd->oobsize);
 }
 
 static int gpmi_ecc_read_oob_raw(struct mtd_info *mtd, struct nand_chip *chip,
 				 int page)
 {
-	nand_read_page_op(chip, page, 0, NULL, 0);
-
 	return gpmi_ecc_read_page_raw(mtd, chip, NULL, 1, page);
 }
 
 static int gpmi_ecc_write_oob_raw(struct mtd_info *mtd, struct nand_chip *chip,
 				 int page)
 {
-	nand_prog_page_begin_op(chip, page, 0, NULL, 0);
-
 	return gpmi_ecc_write_page_raw(mtd, chip, NULL, 1, page);
 }
 
@@ -1806,9 +1810,7 @@ static int mx23_write_transcription_stamp(struct gpmi_nand_data *this)
 		/* Write the first page of the current stride. */
 		dev_dbg(dev, "Writing an NCB fingerprint in page 0x%x\n", page);
 
-		nand_prog_page_begin_op(chip, page, 0, NULL, 0);
-		chip->ecc.write_page_raw(mtd, chip, buffer, 0, page);
-		status = nand_prog_page_end_op(chip);
+		status = chip->ecc.write_page_raw(mtd, chip, buffer, 0, page);
 		if (status)
 			dev_err(dev, "[%s] Write failed.\n", __func__);
 	}
diff --git a/drivers/mtd/nand/hisi504_nand.c b/drivers/mtd/nand/hisi504_nand.c
index 184d765c8bbe..cb862793ab6d 100644
--- a/drivers/mtd/nand/hisi504_nand.c
+++ b/drivers/mtd/nand/hisi504_nand.c
@@ -544,7 +544,7 @@ static int hisi_nand_read_page_hwecc(struct mtd_info *mtd,
 	int max_bitflips = 0, stat = 0, stat_max = 0, status_ecc;
 	int stat_1, stat_2;
 
-	chip->read_buf(mtd, buf, mtd->writesize);
+	nand_read_page_op(chip, page, 0, buf, mtd->writesize);
 	chip->read_buf(mtd, chip->oob_poi, mtd->oobsize);
 
 	/* errors which can not be corrected by ECC */
@@ -589,11 +589,11 @@ static int hisi_nand_write_page_hwecc(struct mtd_info *mtd,
 		struct nand_chip *chip, const uint8_t *buf, int oob_required,
 		int page)
 {
-	chip->write_buf(mtd, buf, mtd->writesize);
+	nand_prog_page_begin_op(chip, page, 0, buf, mtd->writesize);
 	if (oob_required)
 		chip->write_buf(mtd, chip->oob_poi, mtd->oobsize);
 
-	return 0;
+	return nand_prog_page_end_op(chip);
 }
 
 static void hisi_nfc_host_init(struct hinfc_host *host)
diff --git a/drivers/mtd/nand/lpc32xx_mlc.c b/drivers/mtd/nand/lpc32xx_mlc.c
index 0723ded232ff..fcab1900a764 100644
--- a/drivers/mtd/nand/lpc32xx_mlc.c
+++ b/drivers/mtd/nand/lpc32xx_mlc.c
@@ -522,6 +522,8 @@ static int lpc32xx_write_page_lowlevel(struct mtd_info *mtd,
 		memcpy(dma_buf, buf, mtd->writesize);
 	}
 
+	nand_prog_page_begin_op(chip, page, 0, NULL, 0);
+
 	for (i = 0; i < host->mlcsubpages; i++) {
 		/* Start Encode */
 		writeb(0x00, MLC_ECC_ENC_REG(host->io_base));
@@ -550,7 +552,8 @@ static int lpc32xx_write_page_lowlevel(struct mtd_info *mtd,
 		/* Wait for Controller Ready */
 		lpc32xx_waitfunc_controller(mtd, chip);
 	}
-	return 0;
+
+	return nand_prog_page_end_op(chip);
 }
 
 static int lpc32xx_read_oob(struct mtd_info *mtd, struct nand_chip *chip,
diff --git a/drivers/mtd/nand/lpc32xx_slc.c b/drivers/mtd/nand/lpc32xx_slc.c
index 2b96c281b1a2..5f7cc6da0a7f 100644
--- a/drivers/mtd/nand/lpc32xx_slc.c
+++ b/drivers/mtd/nand/lpc32xx_slc.c
@@ -686,6 +686,8 @@ static int lpc32xx_nand_write_page_syndrome(struct mtd_info *mtd,
 	uint8_t *pb;
 	int error;
 
+	nand_prog_page_begin_op(chip, page, 0, NULL, 0);
+
 	/* Write data, calculate ECC on outbound data */
 	error = lpc32xx_xfer(mtd, (uint8_t *)buf, chip->ecc.steps, 0);
 	if (error)
@@ -704,7 +706,8 @@ static int lpc32xx_nand_write_page_syndrome(struct mtd_info *mtd,
 
 	/* Write ECC data to device */
 	chip->write_buf(mtd, chip->oob_poi, mtd->oobsize);
-	return 0;
+
+	return nand_prog_page_end_op(chip);
 }
 
 /*
@@ -717,9 +720,11 @@ static int lpc32xx_nand_write_page_raw_syndrome(struct mtd_info *mtd,
 						int oob_required, int page)
 {
 	/* Raw writes can just use the FIFO interface */
-	chip->write_buf(mtd, buf, chip->ecc.size * chip->ecc.steps);
+	nand_prog_page_begin_op(chip, page, 0, buf,
+				chip->ecc.size * chip->ecc.steps);
 	chip->write_buf(mtd, chip->oob_poi, mtd->oobsize);
-	return 0;
+
+	return nand_prog_page_end_op(chip);
 }
 
 static int lpc32xx_nand_dma_setup(struct lpc32xx_nand_host *host)
diff --git a/drivers/mtd/nand/mtk_nand.c b/drivers/mtd/nand/mtk_nand.c
index 1a3fcb65f478..005dabfc7f37 100644
--- a/drivers/mtd/nand/mtk_nand.c
+++ b/drivers/mtd/nand/mtk_nand.c
@@ -761,6 +761,8 @@ static int mtk_nfc_write_page(struct mtd_info *mtd, struct nand_chip *chip,
 	u32 reg;
 	int ret;
 
+	nand_prog_page_begin_op(chip, page, 0, NULL, 0);
+
 	if (!raw) {
 		/* OOB => FDM: from register,  ECC: from HW */
 		reg = nfi_readw(nfc, NFI_CNFG) | CNFG_AUTO_FMT_EN;
@@ -794,7 +796,10 @@ static int mtk_nfc_write_page(struct mtd_info *mtd, struct nand_chip *chip,
 	if (!raw)
 		mtk_ecc_disable(nfc->ecc);
 
-	return ret;
+	if (ret)
+		return ret;
+
+	return nand_prog_page_end_op(chip);
 }
 
 static int mtk_nfc_write_page_hwecc(struct mtd_info *mtd,
@@ -889,8 +894,7 @@ static int mtk_nfc_read_subpage(struct mtd_info *mtd, struct nand_chip *chip,
 	len = sectors * chip->ecc.size + (raw ? sectors * spare : 0);
 	buf = bufpoi + start * chip->ecc.size;
 
-	if (column != 0)
-		chip->cmdfunc(mtd, NAND_CMD_RNDOUT, column, -1);
+	nand_read_page_op(chip, page, column, NULL, 0);
 
 	addr = dma_map_single(nfc->dev, buf, len, DMA_FROM_DEVICE);
 	rc = dma_mapping_error(nfc->dev, addr);
diff --git a/drivers/mtd/nand/nand_base.c b/drivers/mtd/nand/nand_base.c
index f7dd7d5a84b3..318595c29053 100644
--- a/drivers/mtd/nand/nand_base.c
+++ b/drivers/mtd/nand/nand_base.c
@@ -1908,7 +1908,7 @@ int nand_read_page_raw(struct mtd_info *mtd, struct nand_chip *chip,
 {
 	int ret;
 
-	ret = nand_read_data_op(chip, buf, mtd->writesize, false);
+	ret = nand_read_page_op(chip, page, 0, buf, mtd->writesize);
 	if (ret)
 		return ret;
 
@@ -1942,6 +1942,10 @@ static int nand_read_page_raw_syndrome(struct mtd_info *mtd,
 	uint8_t *oob = chip->oob_poi;
 	int steps, size, ret;
 
+	ret = nand_read_page_op(chip, page, 0, NULL, 0);
+	if (ret)
+		return ret;
+
 	for (steps = chip->ecc.steps; steps > 0; steps--) {
 		ret = nand_read_data_op(chip, buf, eccsize, false);
 		if (ret)
@@ -2064,11 +2068,10 @@ static int nand_read_subpage(struct mtd_info *mtd, struct nand_chip *chip,
 
 	data_col_addr = start_step * chip->ecc.size;
 	/* If we read not a page aligned data */
-	if (data_col_addr != 0)
-		chip->cmdfunc(mtd, NAND_CMD_RNDOUT, data_col_addr, -1);
-
 	p = bufpoi + data_col_addr;
-	nand_read_data_op(chip, p, datafrag_len, false);
+	ret = nand_read_page_op(chip, page, data_col_addr, p, datafrag_len);
+	if (ret)
+		return ret;
 
 	/* Calculate ECC */
 	for (i = 0; i < eccfrag_len ; i += chip->ecc.bytes, p += chip->ecc.size)
@@ -2164,6 +2167,10 @@ static int nand_read_page_hwecc(struct mtd_info *mtd, struct nand_chip *chip,
 	uint8_t *ecc_code = chip->buffers->ecccode;
 	unsigned int max_bitflips = 0;
 
+	ret = nand_read_page_op(chip, page, 0, NULL, 0);
+	if (ret)
+		return ret;
+
 	for (i = 0; eccsteps; eccsteps--, i += eccbytes, p += eccsize) {
 		chip->ecc.hwctl(mtd, NAND_ECC_READ);
 
@@ -2299,6 +2306,10 @@ static int nand_read_page_syndrome(struct mtd_info *mtd, struct nand_chip *chip,
 	uint8_t *oob = chip->oob_poi;
 	unsigned int max_bitflips = 0;
 
+	ret = nand_read_page_op(chip, page, 0, NULL, 0);
+	if (ret)
+		return ret;
+
 	for (i = 0; eccsteps; eccsteps--, i += eccbytes, p += eccsize) {
 		int stat;
 
@@ -2481,9 +2492,6 @@ static int nand_do_read_ops(struct mtd_info *mtd, loff_t from,
 						 __func__, buf);
 
 read_retry:
-			if (nand_standard_page_accessors(&chip->ecc))
-				nand_read_page_op(chip, page, 0, NULL, 0);
-
 			/*
 			 * Now read the page into the buffer.  Absent an error,
 			 * the read methods return max bitflips per ecc step.
@@ -2931,7 +2939,7 @@ int nand_write_page_raw(struct mtd_info *mtd, struct nand_chip *chip,
 {
 	int ret;
 
-	ret = nand_write_data_op(chip, buf, mtd->writesize, false);
+	ret = nand_prog_page_begin_op(chip, page, 0, buf, mtd->writesize);
 	if (ret)
 		return ret;
 
@@ -2942,7 +2950,7 @@ int nand_write_page_raw(struct mtd_info *mtd, struct nand_chip *chip,
 			return ret;
 	}
 
-	return 0;
+	return nand_prog_page_end_op(chip);
 }
 EXPORT_SYMBOL(nand_write_page_raw);
 
@@ -2966,6 +2974,10 @@ static int nand_write_page_raw_syndrome(struct mtd_info *mtd,
 	uint8_t *oob = chip->oob_poi;
 	int steps, size, ret;
 
+	ret = nand_prog_page_begin_op(chip, page, 0, NULL, 0);
+	if (ret)
+		return ret;
+
 	for (steps = chip->ecc.steps; steps > 0; steps--) {
 		ret = nand_write_data_op(chip, buf, eccsize, false);
 		if (ret)
@@ -3005,7 +3017,7 @@ static int nand_write_page_raw_syndrome(struct mtd_info *mtd,
 			return ret;
 	}
 
-	return 0;
+	return nand_prog_page_end_op(chip);
 }
 /**
  * nand_write_page_swecc - [REPLACEABLE] software ECC based page write function
@@ -3236,12 +3248,6 @@ static int nand_write_page(struct mtd_info *mtd, struct nand_chip *chip,
 	else
 		subpage = 0;
 
-	if (nand_standard_page_accessors(&chip->ecc)) {
-		status = nand_prog_page_begin_op(chip, page, 0, NULL, 0);
-		if (status)
-			return status;
-	}
-
 	if (unlikely(raw))
 		status = chip->ecc.write_page_raw(mtd, chip, buf,
 						  oob_required, page);
@@ -3255,9 +3261,6 @@ static int nand_write_page(struct mtd_info *mtd, struct nand_chip *chip,
 	if (status < 0)
 		return status;
 
-	if (nand_standard_page_accessors(&chip->ecc))
-		return nand_prog_page_end_op(chip);
-
 	return 0;
 }
 
@@ -5235,26 +5238,6 @@ static bool nand_ecc_strength_good(struct mtd_info *mtd)
 	return corr >= ds_corr && ecc->strength >= chip->ecc_strength_ds;
 }
 
-static bool invalid_ecc_page_accessors(struct nand_chip *chip)
-{
-	struct nand_ecc_ctrl *ecc = &chip->ecc;
-
-	if (nand_standard_page_accessors(ecc))
-		return false;
-
-	/*
-	 * NAND_ECC_CUSTOM_PAGE_ACCESS flag is set, make sure the NAND
-	 * controller driver implements all the page accessors because
-	 * default helpers are not suitable when the core does not
-	 * send the READ0/PAGEPROG commands.
-	 */
-	return (!ecc->read_page || !ecc->write_page ||
-		!ecc->read_page_raw || !ecc->write_page_raw ||
-		(NAND_HAS_SUBPAGE_READ(chip) && !ecc->read_subpage) ||
-		(NAND_HAS_SUBPAGE_WRITE(chip) && !ecc->write_subpage &&
-		 ecc->hwctl && ecc->calculate));
-}
-
 /**
  * nand_scan_tail - [NAND Interface] Scan for the NAND device
  * @mtd: MTD device structure
@@ -5276,11 +5259,6 @@ int nand_scan_tail(struct mtd_info *mtd)
 		return -EINVAL;
 	}
 
-	if (invalid_ecc_page_accessors(chip)) {
-		pr_err("Invalid ECC page accessors setup\n");
-		return -EINVAL;
-	}
-
 	if (!(chip->options & NAND_OWN_BUFFERS)) {
 		nbuf = kzalloc(sizeof(*nbuf), GFP_KERNEL);
 		if (!nbuf)
diff --git a/drivers/mtd/nand/nand_micron.c b/drivers/mtd/nand/nand_micron.c
index 3934539a7468..543352380ffa 100644
--- a/drivers/mtd/nand/nand_micron.c
+++ b/drivers/mtd/nand/nand_micron.c
@@ -272,7 +272,6 @@ static int micron_nand_init(struct nand_chip *chip)
 			return -EINVAL;
 		}
 
-		chip->ecc.options = NAND_ECC_CUSTOM_PAGE_ACCESS;
 		chip->ecc.bytes = 8;
 		chip->ecc.size = 512;
 		chip->ecc.strength = 4;
diff --git a/drivers/mtd/nand/sh_flctl.c b/drivers/mtd/nand/sh_flctl.c
index 3c5008a4f5f3..c4e7755448e6 100644
--- a/drivers/mtd/nand/sh_flctl.c
+++ b/drivers/mtd/nand/sh_flctl.c
@@ -614,7 +614,7 @@ static void set_cmd_regs(struct mtd_info *mtd, uint32_t cmd, uint32_t flcmcdr_va
 static int flctl_read_page_hwecc(struct mtd_info *mtd, struct nand_chip *chip,
 				uint8_t *buf, int oob_required, int page)
 {
-	chip->read_buf(mtd, buf, mtd->writesize);
+	nand_read_page_op(chip, page, 0, buf, mtd->writesize);
 	if (oob_required)
 		chip->read_buf(mtd, chip->oob_poi, mtd->oobsize);
 	return 0;
@@ -624,9 +624,9 @@ static int flctl_write_page_hwecc(struct mtd_info *mtd, struct nand_chip *chip,
 				  const uint8_t *buf, int oob_required,
 				  int page)
 {
-	chip->write_buf(mtd, buf, mtd->writesize);
+	nand_prog_page_begin_op(chip, page, 0, buf, mtd->writesize);
 	chip->write_buf(mtd, chip->oob_poi, mtd->oobsize);
-	return 0;
+	return nand_prog_page_end_op(chip);
 }
 
 static void execmd_read_page_sector(struct mtd_info *mtd, int page_addr)
diff --git a/drivers/mtd/nand/sunxi_nand.c b/drivers/mtd/nand/sunxi_nand.c
index 230d037d05d2..2354410d8c48 100644
--- a/drivers/mtd/nand/sunxi_nand.c
+++ b/drivers/mtd/nand/sunxi_nand.c
@@ -1245,6 +1245,8 @@ static int sunxi_nfc_hw_ecc_read_page(struct mtd_info *mtd,
 	int ret, i, cur_off = 0;
 	bool raw_mode = false;
 
+	nand_read_page_op(chip, page, 0, NULL, 0);
+
 	sunxi_nfc_hw_ecc_enable(mtd);
 
 	for (i = 0; i < ecc->steps; i++) {
@@ -1278,14 +1280,14 @@ static int sunxi_nfc_hw_ecc_read_page_dma(struct mtd_info *mtd,
 {
 	int ret;
 
+	nand_read_page_op(chip, page, 0, NULL, 0);
+
 	ret = sunxi_nfc_hw_ecc_read_chunks_dma(mtd, buf, oob_required, page,
 					       chip->ecc.steps);
 	if (ret >= 0)
 		return ret;
 
 	/* Fallback to PIO mode */
-	chip->cmdfunc(mtd, NAND_CMD_RNDOUT, 0, -1);
-
 	return sunxi_nfc_hw_ecc_read_page(mtd, chip, buf, oob_required, page);
 }
 
@@ -1298,6 +1300,8 @@ static int sunxi_nfc_hw_ecc_read_subpage(struct mtd_info *mtd,
 	int ret, i, cur_off = 0;
 	unsigned int max_bitflips = 0;
 
+	nand_read_page_op(chip, page, 0, NULL, 0);
+
 	sunxi_nfc_hw_ecc_enable(mtd);
 
 	for (i = data_offs / ecc->size;
@@ -1329,13 +1333,13 @@ static int sunxi_nfc_hw_ecc_read_subpage_dma(struct mtd_info *mtd,
 	int nchunks = DIV_ROUND_UP(data_offs + readlen, chip->ecc.size);
 	int ret;
 
+	nand_read_page_op(chip, page, 0, NULL, 0);
+
 	ret = sunxi_nfc_hw_ecc_read_chunks_dma(mtd, buf, false, page, nchunks);
 	if (ret >= 0)
 		return ret;
 
 	/* Fallback to PIO mode */
-	chip->cmdfunc(mtd, NAND_CMD_RNDOUT, 0, -1);
-
 	return sunxi_nfc_hw_ecc_read_subpage(mtd, chip, data_offs, readlen,
 					     buf, page);
 }
@@ -1348,6 +1352,8 @@ static int sunxi_nfc_hw_ecc_write_page(struct mtd_info *mtd,
 	struct nand_ecc_ctrl *ecc = &chip->ecc;
 	int ret, i, cur_off = 0;
 
+	nand_prog_page_begin_op(chip, page, 0, NULL, 0);
+
 	sunxi_nfc_hw_ecc_enable(mtd);
 
 	for (i = 0; i < ecc->steps; i++) {
@@ -1369,7 +1375,7 @@ static int sunxi_nfc_hw_ecc_write_page(struct mtd_info *mtd,
 
 	sunxi_nfc_hw_ecc_disable(mtd);
 
-	return 0;
+	return nand_prog_page_end_op(chip);
 }
 
 static int sunxi_nfc_hw_ecc_write_subpage(struct mtd_info *mtd,
@@ -1381,6 +1387,8 @@ static int sunxi_nfc_hw_ecc_write_subpage(struct mtd_info *mtd,
 	struct nand_ecc_ctrl *ecc = &chip->ecc;
 	int ret, i, cur_off = 0;
 
+	nand_prog_page_begin_op(chip, page, 0, NULL, 0);
+
 	sunxi_nfc_hw_ecc_enable(mtd);
 
 	for (i = data_offs / ecc->size;
@@ -1399,7 +1407,7 @@ static int sunxi_nfc_hw_ecc_write_subpage(struct mtd_info *mtd,
 
 	sunxi_nfc_hw_ecc_disable(mtd);
 
-	return 0;
+	return nand_prog_page_end_op(chip);
 }
 
 static int sunxi_nfc_hw_ecc_write_page_dma(struct mtd_info *mtd,
@@ -1429,6 +1437,8 @@ static int sunxi_nfc_hw_ecc_write_page_dma(struct mtd_info *mtd,
 		sunxi_nfc_hw_ecc_set_prot_oob_bytes(mtd, oob, i, !i, page);
 	}
 
+	nand_prog_page_begin_op(chip, page, 0, NULL, 0);
+
 	sunxi_nfc_hw_ecc_enable(mtd);
 	sunxi_nfc_randomizer_config(mtd, page, false);
 	sunxi_nfc_randomizer_enable(mtd);
@@ -1459,7 +1469,7 @@ static int sunxi_nfc_hw_ecc_write_page_dma(struct mtd_info *mtd,
 		sunxi_nfc_hw_ecc_write_extra_oob(mtd, chip->oob_poi,
 						 NULL, page);
 
-	return 0;
+	return nand_prog_page_end_op(chip);
 
 pio_fallback:
 	return sunxi_nfc_hw_ecc_write_page(mtd, chip, buf, oob_required, page);
@@ -1475,6 +1485,8 @@ static int sunxi_nfc_hw_syndrome_ecc_read_page(struct mtd_info *mtd,
 	int ret, i, cur_off = 0;
 	bool raw_mode = false;
 
+	nand_read_page_op(chip, page, 0, NULL, 0);
+
 	sunxi_nfc_hw_ecc_enable(mtd);
 
 	for (i = 0; i < ecc->steps; i++) {
@@ -1511,6 +1523,8 @@ static int sunxi_nfc_hw_syndrome_ecc_write_page(struct mtd_info *mtd,
 	struct nand_ecc_ctrl *ecc = &chip->ecc;
 	int ret, i, cur_off = 0;
 
+	nand_prog_page_begin_op(chip, page, 0, NULL, 0);
+
 	sunxi_nfc_hw_ecc_enable(mtd);
 
 	for (i = 0; i < ecc->steps; i++) {
@@ -1532,15 +1546,13 @@ static int sunxi_nfc_hw_syndrome_ecc_write_page(struct mtd_info *mtd,
 
 	sunxi_nfc_hw_ecc_disable(mtd);
 
-	return 0;
+	return nand_prog_page_end_op(chip);
 }
 
 static int sunxi_nfc_hw_common_ecc_read_oob(struct mtd_info *mtd,
 					    struct nand_chip *chip,
 					    int page)
 {
-	nand_read_page_op(chip, page, 0, NULL, 0);
-
 	chip->pagebuf = -1;
 
 	return chip->ecc.read_page(mtd, chip, chip->buffers->databuf, 1, page);
@@ -1552,8 +1564,6 @@ static int sunxi_nfc_hw_common_ecc_write_oob(struct mtd_info *mtd,
 {
 	int ret;
 
-	nand_prog_page_begin_op(chip, page, 0, NULL, 0);
-
 	chip->pagebuf = -1;
 
 	memset(chip->buffers->databuf, 0xff, mtd->writesize);
diff --git a/drivers/mtd/nand/tango_nand.c b/drivers/mtd/nand/tango_nand.c
index 97a300b46b1d..c5bee00b7f5e 100644
--- a/drivers/mtd/nand/tango_nand.c
+++ b/drivers/mtd/nand/tango_nand.c
@@ -580,7 +580,6 @@ static int chip_init(struct device *dev, struct device_node *np)
 	ecc->write_page = tango_write_page;
 	ecc->read_oob = tango_read_oob;
 	ecc->write_oob = tango_write_oob;
-	ecc->options = NAND_ECC_CUSTOM_PAGE_ACCESS;
 
 	err = nand_scan_tail(mtd);
 	if (err)
diff --git a/drivers/mtd/nand/vf610_nfc.c b/drivers/mtd/nand/vf610_nfc.c
index 8037d4b48a05..80d31a58e558 100644
--- a/drivers/mtd/nand/vf610_nfc.c
+++ b/drivers/mtd/nand/vf610_nfc.c
@@ -560,7 +560,7 @@ static int vf610_nfc_read_page(struct mtd_info *mtd, struct nand_chip *chip,
 	int eccsize = chip->ecc.size;
 	int stat;
 
-	vf610_nfc_read_buf(mtd, buf, eccsize);
+	nand_read_page_op(chip, page, 0, buf, eccsize);
 	if (oob_required)
 		vf610_nfc_read_buf(mtd, chip->oob_poi, mtd->oobsize);
 
@@ -580,7 +580,7 @@ static int vf610_nfc_write_page(struct mtd_info *mtd, struct nand_chip *chip,
 {
 	struct vf610_nfc *nfc = mtd_to_nfc(mtd);
 
-	vf610_nfc_write_buf(mtd, buf, mtd->writesize);
+	nand_prog_page_begin_op(chip, page, 0, buf, mtd->writesize);
 	if (oob_required)
 		vf610_nfc_write_buf(mtd, chip->oob_poi, mtd->oobsize);
 
@@ -588,7 +588,7 @@ static int vf610_nfc_write_page(struct mtd_info *mtd, struct nand_chip *chip,
 	nfc->use_hw_ecc = true;
 	nfc->write_sz = mtd->writesize + mtd->oobsize;
 
-	return 0;
+	return nand_prog_page_end_op(chip);
 }
 
 static const struct of_device_id vf610_nfc_dt_ids[] = {
diff --git a/drivers/staging/mt29f_spinand/mt29f_spinand.c b/drivers/staging/mt29f_spinand/mt29f_spinand.c
index 87595c594b12..e396075dd508 100644
--- a/drivers/staging/mt29f_spinand/mt29f_spinand.c
+++ b/drivers/staging/mt29f_spinand/mt29f_spinand.c
@@ -636,9 +636,12 @@ static int spinand_write_page_hwecc(struct mtd_info *mtd,
 	int eccsize = chip->ecc.size;
 	int eccsteps = chip->ecc.steps;
 
+	nand_prog_page_begin_op(chip, page, 0, NULL, 0);
+
 	enable_hw_ecc = 1;
 	chip->write_buf(mtd, p, eccsize * eccsteps);
-	return 0;
+
+	return nand_prog_page_end_op(chip);
 }
 
 static int spinand_read_page_hwecc(struct mtd_info *mtd, struct nand_chip *chip,
@@ -653,7 +656,7 @@ static int spinand_read_page_hwecc(struct mtd_info *mtd, struct nand_chip *chip,
 
 	enable_read_hw_ecc = 1;
 
-	chip->read_buf(mtd, p, eccsize * eccsteps);
+	nand_read_page_op(chip, page, 0, p, eccsize * eccsteps);
 	if (oob_required)
 		chip->read_buf(mtd, chip->oob_poi, mtd->oobsize);
 
diff --git a/include/linux/mtd/rawnand.h b/include/linux/mtd/rawnand.h
index 8b8acde72465..04f4cfbe6c09 100644
--- a/include/linux/mtd/rawnand.h
+++ b/include/linux/mtd/rawnand.h
@@ -133,12 +133,6 @@ enum nand_ecc_algo {
  */
 #define NAND_ECC_GENERIC_ERASED_CHECK	BIT(0)
 #define NAND_ECC_MAXIMIZE		BIT(1)
-/*
- * If your controller already sends the required NAND commands when
- * reading or writing a page, then the framework is not supposed to
- * send READ0 and SEQIN/PAGEPROG respectively.
- */
-#define NAND_ECC_CUSTOM_PAGE_ACCESS	BIT(2)
 
 /* Bit mask for flags passed to do_nand_read_ecc */
 #define NAND_GET_DEVICE		0x80
@@ -602,11 +596,6 @@ struct nand_ecc_ctrl {
 			int page);
 };
 
-static inline int nand_standard_page_accessors(struct nand_ecc_ctrl *ecc)
-{
-	return !(ecc->options & NAND_ECC_CUSTOM_PAGE_ACCESS);
-}
-
 /**
  * struct nand_buffers - buffer structure for read/write
  * @ecccalc:	buffer pointer for calculated ECC, size is oobsize.
-- 
2.11.0

^ permalink raw reply related	[flat|nested] 75+ messages in thread

* [RFC 03/12] mtd: nand: use a static data_interface in the nand_chip structure
  2017-10-18 14:36 ` Miquel Raynal
  (?)
@ 2017-10-18 14:36   ` Miquel Raynal
  -1 siblings, 0 replies; 75+ messages in thread
From: Miquel Raynal @ 2017-10-18 14:36 UTC (permalink / raw)
  To: Andrew Lunn, bcm-kernel-feedback-list, Boris Brezillon,
	Brian Norris, Catalin Marinas, Chen-Yu Tsai, Cyrille Pitchen,
	Daniel Mack, David Woodhouse, devel, devicetree, Ezequiel Garcia,
	Greg Kroah-Hartman, Gregory Clement, Han Xu, Haojian Zhuang,
	Jason Cooper, Josh Wu, Kamal Dasu, linux-arm-kernel,
	linux-kernel, linux-mediatek, linux-mtd, Marc Gonzalez,
	Marek Vasut, Mark Rutland, Masahiro Yamada, Matthias Brugger,
	Maxime Ripard, Maxim Levitsky, Richard Weinberger,
	Robert Jarzmik, Rob Herring, Russell King, Sebastian Hesselbarth,
	Stefan Agner, Sylvain Lemieux, Vladimir Zapolskiy, Wenyou Yang,
	Will Deacon
  Cc: Thomas Petazzoni, Antoine Tenart, Miquel Raynal, Igor Grinberg,
	Nadav Haklai, Ofer Heifetz, Neta Zur Hershkovits, Hanna Hawa

Change the data_interface field from the nand_chip structure, convert
the pointer to a static structure.

Also remove the nand_get_default_data_interface() function that become
useless and rename the onfi_init_data_interface() by
nand_fill_data_interface(), which is a more appropriate name because
applied timings are ONFI, no matter if the NAND actually is one.

This is needed before passing to ->exec_op() to avoid any race that
could lead to a panic (null pointer dereference) on the initialization
of the timings structure that will be used from the first reset
operation if the pointer to data_interface was not referenced yet.

Signed-off-by: Miquel Raynal <miquel.raynal@free-electrons.com>
---
 drivers/mtd/nand/nand_base.c    | 35 ++++++++++-------------------------
 drivers/mtd/nand/nand_timings.c | 23 ++++++-----------------
 include/linux/mtd/rawnand.h     |  9 ++-------
 3 files changed, 18 insertions(+), 49 deletions(-)

diff --git a/drivers/mtd/nand/nand_base.c b/drivers/mtd/nand/nand_base.c
index 318595c29053..bef20e06f0db 100644
--- a/drivers/mtd/nand/nand_base.c
+++ b/drivers/mtd/nand/nand_base.c
@@ -814,8 +814,8 @@ static void nand_ccs_delay(struct nand_chip *chip)
 	 * Wait tCCS_min if it is correctly defined, otherwise wait 500ns
 	 * (which should be safe for all NANDs).
 	 */
-	if (chip->data_interface && chip->data_interface->timings.sdr.tCCS_min)
-		ndelay(chip->data_interface->timings.sdr.tCCS_min / 1000);
+	if (chip->data_interface.timings.sdr.tCCS_min)
+		ndelay(chip->data_interface.timings.sdr.tCCS_min / 1000);
 	else
 		ndelay(500);
 }
@@ -1107,7 +1107,6 @@ static int nand_wait(struct mtd_info *mtd, struct nand_chip *chip)
 static int nand_reset_data_interface(struct nand_chip *chip, int chipnr)
 {
 	struct mtd_info *mtd = nand_to_mtd(chip);
-	const struct nand_data_interface *conf;
 	int ret;
 
 	if (!chip->setup_data_interface)
@@ -1127,8 +1126,8 @@ static int nand_reset_data_interface(struct nand_chip *chip, int chipnr)
 	 * timings to timing mode 0.
 	 */
 
-	conf = nand_get_default_data_interface();
-	ret = chip->setup_data_interface(mtd, chipnr, conf);
+	nand_fill_data_interface(chip, 0);
+	ret = chip->setup_data_interface(mtd, chipnr, &chip->data_interface);
 	if (ret)
 		pr_err("Failed to configure data interface to SDR timing mode 0\n");
 
@@ -1153,7 +1152,7 @@ static int nand_setup_data_interface(struct nand_chip *chip, int chipnr)
 	struct mtd_info *mtd = nand_to_mtd(chip);
 	int ret;
 
-	if (!chip->setup_data_interface || !chip->data_interface)
+	if (!chip->setup_data_interface)
 		return 0;
 
 	/*
@@ -1174,7 +1173,7 @@ static int nand_setup_data_interface(struct nand_chip *chip, int chipnr)
 			goto err;
 	}
 
-	ret = chip->setup_data_interface(mtd, chipnr, chip->data_interface);
+	ret = chip->setup_data_interface(mtd, chipnr, &chip->data_interface);
 err:
 	return ret;
 }
@@ -1214,21 +1213,16 @@ static int nand_init_data_interface(struct nand_chip *chip)
 		modes = GENMASK(chip->onfi_timing_mode_default, 0);
 	}
 
-	chip->data_interface = kzalloc(sizeof(*chip->data_interface),
-				       GFP_KERNEL);
-	if (!chip->data_interface)
-		return -ENOMEM;
 
 	for (mode = fls(modes) - 1; mode >= 0; mode--) {
-		ret = onfi_init_data_interface(chip, chip->data_interface,
-					       NAND_SDR_IFACE, mode);
+		ret = nand_fill_data_interface(chip, mode);
 		if (ret)
 			continue;
 
 		/* Pass -1 to only */
 		ret = chip->setup_data_interface(mtd,
 						 NAND_DATA_IFACE_CHECK_ONLY,
-						 chip->data_interface);
+						 &chip->data_interface);
 		if (!ret) {
 			chip->onfi_timing_mode_default = mode;
 			break;
@@ -1238,11 +1232,6 @@ static int nand_init_data_interface(struct nand_chip *chip)
 	return 0;
 }
 
-static void nand_release_data_interface(struct nand_chip *chip)
-{
-	kfree(chip->data_interface);
-}
-
 /**
  * nand_read_page_op - Do a READ PAGE operation
  * @chip: The NAND chip
@@ -5567,7 +5556,7 @@ int nand_scan_tail(struct mtd_info *mtd)
 		chip->select_chip(mtd, -1);
 
 		if (ret)
-			goto err_nand_data_iface_cleanup;
+			goto err_nand_manuf_cleanup;
 	}
 
 	/* Check, if we should skip the bad block table scan */
@@ -5577,12 +5566,10 @@ int nand_scan_tail(struct mtd_info *mtd)
 	/* Build bad block table */
 	ret = chip->scan_bbt(mtd);
 	if (ret)
-		goto err_nand_data_iface_cleanup;
+		goto err_nand_manuf_cleanup;
 
 	return 0;
 
-err_nand_data_iface_cleanup:
-	nand_release_data_interface(chip);
 
 err_nand_manuf_cleanup:
 	nand_manufacturer_cleanup(chip);
@@ -5641,8 +5628,6 @@ void nand_cleanup(struct nand_chip *chip)
 	    chip->ecc.algo == NAND_ECC_BCH)
 		nand_bch_free((struct nand_bch_control *)chip->ecc.priv);
 
-	nand_release_data_interface(chip);
-
 	/* Free bad block table memory */
 	kfree(chip->bbt);
 	if (!(chip->options & NAND_OWN_BUFFERS) && chip->buffers) {
diff --git a/drivers/mtd/nand/nand_timings.c b/drivers/mtd/nand/nand_timings.c
index 5d1533bcc5bd..745b6404c901 100644
--- a/drivers/mtd/nand/nand_timings.c
+++ b/drivers/mtd/nand/nand_timings.c
@@ -283,17 +283,17 @@ const struct nand_sdr_timings *onfi_async_timing_mode_to_sdr_timings(int mode)
 EXPORT_SYMBOL(onfi_async_timing_mode_to_sdr_timings);
 
 /**
- * onfi_init_data_interface - [NAND Interface] Initialize a data interface from
+ * nand_fill_data_interface - [NAND Interface] Initialize a data interface from
  * given ONFI mode
  * @iface: The data interface to be initialized
  * @mode: The ONFI timing mode
  */
-int onfi_init_data_interface(struct nand_chip *chip,
-			     struct nand_data_interface *iface,
-			     enum nand_data_interface_type type,
+int nand_fill_data_interface(struct nand_chip *chip,
 			     int timing_mode)
 {
-	if (type != NAND_SDR_IFACE)
+	struct nand_data_interface *iface = &chip->data_interface;
+
+	if (iface->type != NAND_SDR_IFACE)
 		return -EINVAL;
 
 	if (timing_mode < 0 || timing_mode >= ARRAY_SIZE(onfi_sdr_timings))
@@ -321,15 +321,4 @@ int onfi_init_data_interface(struct nand_chip *chip,
 
 	return 0;
 }
-EXPORT_SYMBOL(onfi_init_data_interface);
-
-/**
- * nand_get_default_data_interface - [NAND Interface] Retrieve NAND
- * data interface for mode 0. This is used as default timing after
- * reset.
- */
-const struct nand_data_interface *nand_get_default_data_interface(void)
-{
-	return &onfi_sdr_timings[0];
-}
-EXPORT_SYMBOL(nand_get_default_data_interface);
+EXPORT_SYMBOL(nand_fill_data_interface);
diff --git a/include/linux/mtd/rawnand.h b/include/linux/mtd/rawnand.h
index 04f4cfbe6c09..1acc26ed0e91 100644
--- a/include/linux/mtd/rawnand.h
+++ b/include/linux/mtd/rawnand.h
@@ -917,7 +917,7 @@ struct nand_chip {
 	u16 max_bb_per_die;
 	u32 blocks_per_die;
 
-	struct nand_data_interface *data_interface;
+	struct nand_data_interface data_interface;
 
 	int read_retries;
 
@@ -1214,10 +1214,7 @@ static inline int onfi_get_sync_timing_mode(struct nand_chip *chip)
 	return le16_to_cpu(chip->onfi_params.src_sync_timing_mode);
 }
 
-int onfi_init_data_interface(struct nand_chip *chip,
-			     struct nand_data_interface *iface,
-			     enum nand_data_interface_type type,
-			     int timing_mode);
+int nand_fill_data_interface(struct nand_chip *chip, int timing_mode);
 
 /*
  * Check if it is a SLC nand.
@@ -1258,8 +1255,6 @@ static inline int jedec_feature(struct nand_chip *chip)
 
 /* get timing characteristics from ONFI timing mode. */
 const struct nand_sdr_timings *onfi_async_timing_mode_to_sdr_timings(int mode);
-/* get data interface from ONFI timing mode 0, used after reset. */
-const struct nand_data_interface *nand_get_default_data_interface(void);
 
 int nand_check_erased_ecc_chunk(void *data, int datalen,
 				void *ecc, int ecclen,
-- 
2.11.0

^ permalink raw reply related	[flat|nested] 75+ messages in thread

* [RFC 03/12] mtd: nand: use a static data_interface in the nand_chip structure
@ 2017-10-18 14:36   ` Miquel Raynal
  0 siblings, 0 replies; 75+ messages in thread
From: Miquel Raynal @ 2017-10-18 14:36 UTC (permalink / raw)
  To: Andrew Lunn, bcm-kernel-feedback-list, Boris Brezillon,
	Brian Norris, Catalin Marinas, Chen-Yu Tsai, Cyrille Pitchen,
	Daniel Mack, David Woodhouse, devel, devicetree, Ezequiel Garcia,
	Greg Kroah-Hartman, Gregory Clement, Han Xu, Haojian Zhuang,
	Jason Cooper, Josh Wu, Kamal Dasu, linux-arm-kernel,
	linux-kernel, linux-mediatek, linux-mtd
  Cc: Hanna Hawa, Antoine Tenart, Nadav Haklai, Neta Zur Hershkovits,
	Igor Grinberg, Miquel Raynal, Ofer Heifetz

Change the data_interface field from the nand_chip structure, convert
the pointer to a static structure.

Also remove the nand_get_default_data_interface() function that become
useless and rename the onfi_init_data_interface() by
nand_fill_data_interface(), which is a more appropriate name because
applied timings are ONFI, no matter if the NAND actually is one.

This is needed before passing to ->exec_op() to avoid any race that
could lead to a panic (null pointer dereference) on the initialization
of the timings structure that will be used from the first reset
operation if the pointer to data_interface was not referenced yet.

Signed-off-by: Miquel Raynal <miquel.raynal@free-electrons.com>
---
 drivers/mtd/nand/nand_base.c    | 35 ++++++++++-------------------------
 drivers/mtd/nand/nand_timings.c | 23 ++++++-----------------
 include/linux/mtd/rawnand.h     |  9 ++-------
 3 files changed, 18 insertions(+), 49 deletions(-)

diff --git a/drivers/mtd/nand/nand_base.c b/drivers/mtd/nand/nand_base.c
index 318595c29053..bef20e06f0db 100644
--- a/drivers/mtd/nand/nand_base.c
+++ b/drivers/mtd/nand/nand_base.c
@@ -814,8 +814,8 @@ static void nand_ccs_delay(struct nand_chip *chip)
 	 * Wait tCCS_min if it is correctly defined, otherwise wait 500ns
 	 * (which should be safe for all NANDs).
 	 */
-	if (chip->data_interface && chip->data_interface->timings.sdr.tCCS_min)
-		ndelay(chip->data_interface->timings.sdr.tCCS_min / 1000);
+	if (chip->data_interface.timings.sdr.tCCS_min)
+		ndelay(chip->data_interface.timings.sdr.tCCS_min / 1000);
 	else
 		ndelay(500);
 }
@@ -1107,7 +1107,6 @@ static int nand_wait(struct mtd_info *mtd, struct nand_chip *chip)
 static int nand_reset_data_interface(struct nand_chip *chip, int chipnr)
 {
 	struct mtd_info *mtd = nand_to_mtd(chip);
-	const struct nand_data_interface *conf;
 	int ret;
 
 	if (!chip->setup_data_interface)
@@ -1127,8 +1126,8 @@ static int nand_reset_data_interface(struct nand_chip *chip, int chipnr)
 	 * timings to timing mode 0.
 	 */
 
-	conf = nand_get_default_data_interface();
-	ret = chip->setup_data_interface(mtd, chipnr, conf);
+	nand_fill_data_interface(chip, 0);
+	ret = chip->setup_data_interface(mtd, chipnr, &chip->data_interface);
 	if (ret)
 		pr_err("Failed to configure data interface to SDR timing mode 0\n");
 
@@ -1153,7 +1152,7 @@ static int nand_setup_data_interface(struct nand_chip *chip, int chipnr)
 	struct mtd_info *mtd = nand_to_mtd(chip);
 	int ret;
 
-	if (!chip->setup_data_interface || !chip->data_interface)
+	if (!chip->setup_data_interface)
 		return 0;
 
 	/*
@@ -1174,7 +1173,7 @@ static int nand_setup_data_interface(struct nand_chip *chip, int chipnr)
 			goto err;
 	}
 
-	ret = chip->setup_data_interface(mtd, chipnr, chip->data_interface);
+	ret = chip->setup_data_interface(mtd, chipnr, &chip->data_interface);
 err:
 	return ret;
 }
@@ -1214,21 +1213,16 @@ static int nand_init_data_interface(struct nand_chip *chip)
 		modes = GENMASK(chip->onfi_timing_mode_default, 0);
 	}
 
-	chip->data_interface = kzalloc(sizeof(*chip->data_interface),
-				       GFP_KERNEL);
-	if (!chip->data_interface)
-		return -ENOMEM;
 
 	for (mode = fls(modes) - 1; mode >= 0; mode--) {
-		ret = onfi_init_data_interface(chip, chip->data_interface,
-					       NAND_SDR_IFACE, mode);
+		ret = nand_fill_data_interface(chip, mode);
 		if (ret)
 			continue;
 
 		/* Pass -1 to only */
 		ret = chip->setup_data_interface(mtd,
 						 NAND_DATA_IFACE_CHECK_ONLY,
-						 chip->data_interface);
+						 &chip->data_interface);
 		if (!ret) {
 			chip->onfi_timing_mode_default = mode;
 			break;
@@ -1238,11 +1232,6 @@ static int nand_init_data_interface(struct nand_chip *chip)
 	return 0;
 }
 
-static void nand_release_data_interface(struct nand_chip *chip)
-{
-	kfree(chip->data_interface);
-}
-
 /**
  * nand_read_page_op - Do a READ PAGE operation
  * @chip: The NAND chip
@@ -5567,7 +5556,7 @@ int nand_scan_tail(struct mtd_info *mtd)
 		chip->select_chip(mtd, -1);
 
 		if (ret)
-			goto err_nand_data_iface_cleanup;
+			goto err_nand_manuf_cleanup;
 	}
 
 	/* Check, if we should skip the bad block table scan */
@@ -5577,12 +5566,10 @@ int nand_scan_tail(struct mtd_info *mtd)
 	/* Build bad block table */
 	ret = chip->scan_bbt(mtd);
 	if (ret)
-		goto err_nand_data_iface_cleanup;
+		goto err_nand_manuf_cleanup;
 
 	return 0;
 
-err_nand_data_iface_cleanup:
-	nand_release_data_interface(chip);
 
 err_nand_manuf_cleanup:
 	nand_manufacturer_cleanup(chip);
@@ -5641,8 +5628,6 @@ void nand_cleanup(struct nand_chip *chip)
 	    chip->ecc.algo == NAND_ECC_BCH)
 		nand_bch_free((struct nand_bch_control *)chip->ecc.priv);
 
-	nand_release_data_interface(chip);
-
 	/* Free bad block table memory */
 	kfree(chip->bbt);
 	if (!(chip->options & NAND_OWN_BUFFERS) && chip->buffers) {
diff --git a/drivers/mtd/nand/nand_timings.c b/drivers/mtd/nand/nand_timings.c
index 5d1533bcc5bd..745b6404c901 100644
--- a/drivers/mtd/nand/nand_timings.c
+++ b/drivers/mtd/nand/nand_timings.c
@@ -283,17 +283,17 @@ const struct nand_sdr_timings *onfi_async_timing_mode_to_sdr_timings(int mode)
 EXPORT_SYMBOL(onfi_async_timing_mode_to_sdr_timings);
 
 /**
- * onfi_init_data_interface - [NAND Interface] Initialize a data interface from
+ * nand_fill_data_interface - [NAND Interface] Initialize a data interface from
  * given ONFI mode
  * @iface: The data interface to be initialized
  * @mode: The ONFI timing mode
  */
-int onfi_init_data_interface(struct nand_chip *chip,
-			     struct nand_data_interface *iface,
-			     enum nand_data_interface_type type,
+int nand_fill_data_interface(struct nand_chip *chip,
 			     int timing_mode)
 {
-	if (type != NAND_SDR_IFACE)
+	struct nand_data_interface *iface = &chip->data_interface;
+
+	if (iface->type != NAND_SDR_IFACE)
 		return -EINVAL;
 
 	if (timing_mode < 0 || timing_mode >= ARRAY_SIZE(onfi_sdr_timings))
@@ -321,15 +321,4 @@ int onfi_init_data_interface(struct nand_chip *chip,
 
 	return 0;
 }
-EXPORT_SYMBOL(onfi_init_data_interface);
-
-/**
- * nand_get_default_data_interface - [NAND Interface] Retrieve NAND
- * data interface for mode 0. This is used as default timing after
- * reset.
- */
-const struct nand_data_interface *nand_get_default_data_interface(void)
-{
-	return &onfi_sdr_timings[0];
-}
-EXPORT_SYMBOL(nand_get_default_data_interface);
+EXPORT_SYMBOL(nand_fill_data_interface);
diff --git a/include/linux/mtd/rawnand.h b/include/linux/mtd/rawnand.h
index 04f4cfbe6c09..1acc26ed0e91 100644
--- a/include/linux/mtd/rawnand.h
+++ b/include/linux/mtd/rawnand.h
@@ -917,7 +917,7 @@ struct nand_chip {
 	u16 max_bb_per_die;
 	u32 blocks_per_die;
 
-	struct nand_data_interface *data_interface;
+	struct nand_data_interface data_interface;
 
 	int read_retries;
 
@@ -1214,10 +1214,7 @@ static inline int onfi_get_sync_timing_mode(struct nand_chip *chip)
 	return le16_to_cpu(chip->onfi_params.src_sync_timing_mode);
 }
 
-int onfi_init_data_interface(struct nand_chip *chip,
-			     struct nand_data_interface *iface,
-			     enum nand_data_interface_type type,
-			     int timing_mode);
+int nand_fill_data_interface(struct nand_chip *chip, int timing_mode);
 
 /*
  * Check if it is a SLC nand.
@@ -1258,8 +1255,6 @@ static inline int jedec_feature(struct nand_chip *chip)
 
 /* get timing characteristics from ONFI timing mode. */
 const struct nand_sdr_timings *onfi_async_timing_mode_to_sdr_timings(int mode);
-/* get data interface from ONFI timing mode 0, used after reset. */
-const struct nand_data_interface *nand_get_default_data_interface(void);
 
 int nand_check_erased_ecc_chunk(void *data, int datalen,
 				void *ecc, int ecclen,
-- 
2.11.0

^ permalink raw reply related	[flat|nested] 75+ messages in thread

* [RFC 03/12] mtd: nand: use a static data_interface in the nand_chip structure
@ 2017-10-18 14:36   ` Miquel Raynal
  0 siblings, 0 replies; 75+ messages in thread
From: Miquel Raynal @ 2017-10-18 14:36 UTC (permalink / raw)
  To: linux-arm-kernel

Change the data_interface field from the nand_chip structure, convert
the pointer to a static structure.

Also remove the nand_get_default_data_interface() function that become
useless and rename the onfi_init_data_interface() by
nand_fill_data_interface(), which is a more appropriate name because
applied timings are ONFI, no matter if the NAND actually is one.

This is needed before passing to ->exec_op() to avoid any race that
could lead to a panic (null pointer dereference) on the initialization
of the timings structure that will be used from the first reset
operation if the pointer to data_interface was not referenced yet.

Signed-off-by: Miquel Raynal <miquel.raynal@free-electrons.com>
---
 drivers/mtd/nand/nand_base.c    | 35 ++++++++++-------------------------
 drivers/mtd/nand/nand_timings.c | 23 ++++++-----------------
 include/linux/mtd/rawnand.h     |  9 ++-------
 3 files changed, 18 insertions(+), 49 deletions(-)

diff --git a/drivers/mtd/nand/nand_base.c b/drivers/mtd/nand/nand_base.c
index 318595c29053..bef20e06f0db 100644
--- a/drivers/mtd/nand/nand_base.c
+++ b/drivers/mtd/nand/nand_base.c
@@ -814,8 +814,8 @@ static void nand_ccs_delay(struct nand_chip *chip)
 	 * Wait tCCS_min if it is correctly defined, otherwise wait 500ns
 	 * (which should be safe for all NANDs).
 	 */
-	if (chip->data_interface && chip->data_interface->timings.sdr.tCCS_min)
-		ndelay(chip->data_interface->timings.sdr.tCCS_min / 1000);
+	if (chip->data_interface.timings.sdr.tCCS_min)
+		ndelay(chip->data_interface.timings.sdr.tCCS_min / 1000);
 	else
 		ndelay(500);
 }
@@ -1107,7 +1107,6 @@ static int nand_wait(struct mtd_info *mtd, struct nand_chip *chip)
 static int nand_reset_data_interface(struct nand_chip *chip, int chipnr)
 {
 	struct mtd_info *mtd = nand_to_mtd(chip);
-	const struct nand_data_interface *conf;
 	int ret;
 
 	if (!chip->setup_data_interface)
@@ -1127,8 +1126,8 @@ static int nand_reset_data_interface(struct nand_chip *chip, int chipnr)
 	 * timings to timing mode 0.
 	 */
 
-	conf = nand_get_default_data_interface();
-	ret = chip->setup_data_interface(mtd, chipnr, conf);
+	nand_fill_data_interface(chip, 0);
+	ret = chip->setup_data_interface(mtd, chipnr, &chip->data_interface);
 	if (ret)
 		pr_err("Failed to configure data interface to SDR timing mode 0\n");
 
@@ -1153,7 +1152,7 @@ static int nand_setup_data_interface(struct nand_chip *chip, int chipnr)
 	struct mtd_info *mtd = nand_to_mtd(chip);
 	int ret;
 
-	if (!chip->setup_data_interface || !chip->data_interface)
+	if (!chip->setup_data_interface)
 		return 0;
 
 	/*
@@ -1174,7 +1173,7 @@ static int nand_setup_data_interface(struct nand_chip *chip, int chipnr)
 			goto err;
 	}
 
-	ret = chip->setup_data_interface(mtd, chipnr, chip->data_interface);
+	ret = chip->setup_data_interface(mtd, chipnr, &chip->data_interface);
 err:
 	return ret;
 }
@@ -1214,21 +1213,16 @@ static int nand_init_data_interface(struct nand_chip *chip)
 		modes = GENMASK(chip->onfi_timing_mode_default, 0);
 	}
 
-	chip->data_interface = kzalloc(sizeof(*chip->data_interface),
-				       GFP_KERNEL);
-	if (!chip->data_interface)
-		return -ENOMEM;
 
 	for (mode = fls(modes) - 1; mode >= 0; mode--) {
-		ret = onfi_init_data_interface(chip, chip->data_interface,
-					       NAND_SDR_IFACE, mode);
+		ret = nand_fill_data_interface(chip, mode);
 		if (ret)
 			continue;
 
 		/* Pass -1 to only */
 		ret = chip->setup_data_interface(mtd,
 						 NAND_DATA_IFACE_CHECK_ONLY,
-						 chip->data_interface);
+						 &chip->data_interface);
 		if (!ret) {
 			chip->onfi_timing_mode_default = mode;
 			break;
@@ -1238,11 +1232,6 @@ static int nand_init_data_interface(struct nand_chip *chip)
 	return 0;
 }
 
-static void nand_release_data_interface(struct nand_chip *chip)
-{
-	kfree(chip->data_interface);
-}
-
 /**
  * nand_read_page_op - Do a READ PAGE operation
  * @chip: The NAND chip
@@ -5567,7 +5556,7 @@ int nand_scan_tail(struct mtd_info *mtd)
 		chip->select_chip(mtd, -1);
 
 		if (ret)
-			goto err_nand_data_iface_cleanup;
+			goto err_nand_manuf_cleanup;
 	}
 
 	/* Check, if we should skip the bad block table scan */
@@ -5577,12 +5566,10 @@ int nand_scan_tail(struct mtd_info *mtd)
 	/* Build bad block table */
 	ret = chip->scan_bbt(mtd);
 	if (ret)
-		goto err_nand_data_iface_cleanup;
+		goto err_nand_manuf_cleanup;
 
 	return 0;
 
-err_nand_data_iface_cleanup:
-	nand_release_data_interface(chip);
 
 err_nand_manuf_cleanup:
 	nand_manufacturer_cleanup(chip);
@@ -5641,8 +5628,6 @@ void nand_cleanup(struct nand_chip *chip)
 	    chip->ecc.algo == NAND_ECC_BCH)
 		nand_bch_free((struct nand_bch_control *)chip->ecc.priv);
 
-	nand_release_data_interface(chip);
-
 	/* Free bad block table memory */
 	kfree(chip->bbt);
 	if (!(chip->options & NAND_OWN_BUFFERS) && chip->buffers) {
diff --git a/drivers/mtd/nand/nand_timings.c b/drivers/mtd/nand/nand_timings.c
index 5d1533bcc5bd..745b6404c901 100644
--- a/drivers/mtd/nand/nand_timings.c
+++ b/drivers/mtd/nand/nand_timings.c
@@ -283,17 +283,17 @@ const struct nand_sdr_timings *onfi_async_timing_mode_to_sdr_timings(int mode)
 EXPORT_SYMBOL(onfi_async_timing_mode_to_sdr_timings);
 
 /**
- * onfi_init_data_interface - [NAND Interface] Initialize a data interface from
+ * nand_fill_data_interface - [NAND Interface] Initialize a data interface from
  * given ONFI mode
  * @iface: The data interface to be initialized
  * @mode: The ONFI timing mode
  */
-int onfi_init_data_interface(struct nand_chip *chip,
-			     struct nand_data_interface *iface,
-			     enum nand_data_interface_type type,
+int nand_fill_data_interface(struct nand_chip *chip,
 			     int timing_mode)
 {
-	if (type != NAND_SDR_IFACE)
+	struct nand_data_interface *iface = &chip->data_interface;
+
+	if (iface->type != NAND_SDR_IFACE)
 		return -EINVAL;
 
 	if (timing_mode < 0 || timing_mode >= ARRAY_SIZE(onfi_sdr_timings))
@@ -321,15 +321,4 @@ int onfi_init_data_interface(struct nand_chip *chip,
 
 	return 0;
 }
-EXPORT_SYMBOL(onfi_init_data_interface);
-
-/**
- * nand_get_default_data_interface - [NAND Interface] Retrieve NAND
- * data interface for mode 0. This is used as default timing after
- * reset.
- */
-const struct nand_data_interface *nand_get_default_data_interface(void)
-{
-	return &onfi_sdr_timings[0];
-}
-EXPORT_SYMBOL(nand_get_default_data_interface);
+EXPORT_SYMBOL(nand_fill_data_interface);
diff --git a/include/linux/mtd/rawnand.h b/include/linux/mtd/rawnand.h
index 04f4cfbe6c09..1acc26ed0e91 100644
--- a/include/linux/mtd/rawnand.h
+++ b/include/linux/mtd/rawnand.h
@@ -917,7 +917,7 @@ struct nand_chip {
 	u16 max_bb_per_die;
 	u32 blocks_per_die;
 
-	struct nand_data_interface *data_interface;
+	struct nand_data_interface data_interface;
 
 	int read_retries;
 
@@ -1214,10 +1214,7 @@ static inline int onfi_get_sync_timing_mode(struct nand_chip *chip)
 	return le16_to_cpu(chip->onfi_params.src_sync_timing_mode);
 }
 
-int onfi_init_data_interface(struct nand_chip *chip,
-			     struct nand_data_interface *iface,
-			     enum nand_data_interface_type type,
-			     int timing_mode);
+int nand_fill_data_interface(struct nand_chip *chip, int timing_mode);
 
 /*
  * Check if it is a SLC nand.
@@ -1258,8 +1255,6 @@ static inline int jedec_feature(struct nand_chip *chip)
 
 /* get timing characteristics from ONFI timing mode. */
 const struct nand_sdr_timings *onfi_async_timing_mode_to_sdr_timings(int mode);
-/* get data interface from ONFI timing mode 0, used after reset. */
-const struct nand_data_interface *nand_get_default_data_interface(void);
 
 int nand_check_erased_ecc_chunk(void *data, int datalen,
 				void *ecc, int ecclen,
-- 
2.11.0

^ permalink raw reply related	[flat|nested] 75+ messages in thread

* [RFC 04/12] mtd: nand: add ->exec_op() implementation
  2017-10-18 14:36 ` Miquel Raynal
  (?)
@ 2017-10-18 14:36   ` Miquel Raynal
  -1 siblings, 0 replies; 75+ messages in thread
From: Miquel Raynal @ 2017-10-18 14:36 UTC (permalink / raw)
  To: Andrew Lunn, bcm-kernel-feedback-list, Boris Brezillon,
	Brian Norris, Catalin Marinas, Chen-Yu Tsai, Cyrille Pitchen,
	Daniel Mack, David Woodhouse, devel, devicetree, Ezequiel Garcia,
	Greg Kroah-Hartman, Gregory Clement, Han Xu, Haojian Zhuang,
	Jason Cooper, Josh Wu, Kamal Dasu, linux-arm-kernel,
	linux-kernel, linux-mediatek, linux-mtd, Marc Gonzalez,
	Marek Vasut, Mark Rutland, Masahiro Yamada, Matthias Brugger,
	Maxime Ripard, Maxim Levitsky, Richard Weinberger,
	Robert Jarzmik, Rob Herring, Russell King, Sebastian Hesselbarth,
	Stefan Agner, Sylvain Lemieux, Vladimir Zapolskiy, Wenyou Yang,
	Will Deacon
  Cc: Thomas Petazzoni, Antoine Tenart, Miquel Raynal, Igor Grinberg,
	Nadav Haklai, Ofer Heifetz, Neta Zur Hershkovits, Hanna Hawa

Introduce the new way to control the NAND controller drivers by
implementing the ->exec_op() core helpers and allowing new drivers to
use it instead of relying on ->cmd_ctrl(), ->cmdfunc() and
->read/write_byte/word/buf().

The logic is now to send to the controller driver a list of
instructions. The driver shall use the parser added by this commit to
get the matching hook if any. The instructions may be split by the
parser in order to comply with the controller constraints filled in an
array of supported patterns.

Various helpers are also added to ease NAND controller drivers writing.

This new interface should really ease the support of new vendor specific
instructions.

Suggested-by: Boris Brezillon <boris.brezillon@free-electrons.com>
Signed-off-by: Miquel Raynal <miquel.raynal@free-electrons.com>
---
 drivers/mtd/nand/denali.c      |  93 +++-
 drivers/mtd/nand/nand_base.c   | 949 +++++++++++++++++++++++++++++++++++++++--
 drivers/mtd/nand/nand_hynix.c  |  91 +++-
 drivers/mtd/nand/nand_micron.c |  32 +-
 include/linux/mtd/rawnand.h    | 366 +++++++++++++++-
 5 files changed, 1448 insertions(+), 83 deletions(-)

diff --git a/drivers/mtd/nand/denali.c b/drivers/mtd/nand/denali.c
index e5f38359f6df..8f0f18d9d9cf 100644
--- a/drivers/mtd/nand/denali.c
+++ b/drivers/mtd/nand/denali.c
@@ -652,8 +652,6 @@ static void denali_oob_xfer(struct mtd_info *mtd, struct nand_chip *chip,
 			    int page, int write)
 {
 	struct denali_nand_info *denali = mtd_to_denali(mtd);
-	unsigned int start_cmd = write ? NAND_CMD_SEQIN : NAND_CMD_READ0;
-	unsigned int rnd_cmd = write ? NAND_CMD_RNDIN : NAND_CMD_RNDOUT;
 	int writesize = mtd->writesize;
 	int oobsize = mtd->oobsize;
 	uint8_t *bufpoi = chip->oob_poi;
@@ -665,11 +663,22 @@ static void denali_oob_xfer(struct mtd_info *mtd, struct nand_chip *chip,
 	int i, pos, len;
 
 	/* BBM at the beginning of the OOB area */
-	chip->cmdfunc(mtd, start_cmd, writesize, page);
-	if (write)
-		chip->write_buf(mtd, bufpoi, oob_skip);
-	else
-		chip->read_buf(mtd, bufpoi, oob_skip);
+	if (chip->exec_op) {
+		if (write)
+			nand_prog_page_begin_op(chip, page, writesize, bufpoi,
+						oob_skip);
+		else
+			nand_read_page_op(chip, page, writesize, bufpoi,
+					  oob_skip);
+	} else {
+		if (write) {
+			chip->cmdfunc(mtd, NAND_CMD_SEQIN, writesize, page);
+			chip->write_buf(mtd, bufpoi, oob_skip);
+		} else {
+			chip->cmdfunc(mtd, NAND_CMD_READ0, writesize, page);
+			chip->read_buf(mtd, bufpoi, oob_skip);
+		}
+	}
 	bufpoi += oob_skip;
 
 	/* OOB ECC */
@@ -682,30 +691,67 @@ static void denali_oob_xfer(struct mtd_info *mtd, struct nand_chip *chip,
 		else if (pos + len > writesize)
 			len = writesize - pos;
 
-		chip->cmdfunc(mtd, rnd_cmd, pos, -1);
-		if (write)
-			chip->write_buf(mtd, bufpoi, len);
-		else
-			chip->read_buf(mtd, bufpoi, len);
-		bufpoi += len;
-		if (len < ecc_bytes) {
-			len = ecc_bytes - len;
-			chip->cmdfunc(mtd, rnd_cmd, writesize + oob_skip, -1);
+		if (chip->exec_op) {
 			if (write)
-				chip->write_buf(mtd, bufpoi, len);
+				nand_change_write_column_op(
+					chip, pos, bufpoi, len, false);
 			else
+				nand_change_read_column_op(
+					chip, pos, bufpoi, len, false);
+		} else {
+			if (write) {
+				chip->cmdfunc(mtd, NAND_CMD_RNDIN, pos, -1);
+				chip->write_buf(mtd, bufpoi, len);
+			} else {
+				chip->cmdfunc(mtd, NAND_CMD_RNDOUT, pos, -1);
 				chip->read_buf(mtd, bufpoi, len);
+			}
+		}
+		bufpoi += len;
+		if (len < ecc_bytes) {
+			len = ecc_bytes - len;
+			if (chip->exec_op) {
+				if (write)
+					nand_change_write_column_op(
+						chip, writesize + oob_skip,
+						bufpoi, len, false);
+				else
+					nand_change_read_column_op(
+						chip, writesize + oob_skip,
+						bufpoi, len, false);
+			} else {
+				if (write) {
+					chip->cmdfunc(mtd, NAND_CMD_RNDIN,
+						      writesize + oob_skip, -1);
+					chip->write_buf(mtd, bufpoi, len);
+				} else {
+					chip->cmdfunc(mtd, NAND_CMD_RNDOUT,
+						      writesize + oob_skip, -1);
+					chip->read_buf(mtd, bufpoi, len);
+				}
+			}
 			bufpoi += len;
 		}
 	}
 
 	/* OOB free */
 	len = oobsize - (bufpoi - chip->oob_poi);
-	chip->cmdfunc(mtd, rnd_cmd, size - len, -1);
-	if (write)
-		chip->write_buf(mtd, bufpoi, len);
-	else
-		chip->read_buf(mtd, bufpoi, len);
+	if (chip->exec_op) {
+		if (write)
+			nand_change_write_column_op(chip, size - len, bufpoi,
+						    len, false);
+		else
+			nand_change_read_column_op(chip, size - len, bufpoi,
+						   len, false);
+	} else {
+		if (write) {
+			chip->cmdfunc(mtd, NAND_CMD_RNDIN, size - len, -1);
+			chip->write_buf(mtd, bufpoi, len);
+		} else {
+			chip->cmdfunc(mtd, NAND_CMD_RNDOUT, size - len, -1);
+			chip->read_buf(mtd, bufpoi, len);
+		}
+	}
 }
 
 static int denali_read_page_raw(struct mtd_info *mtd, struct nand_chip *chip,
@@ -803,6 +849,9 @@ static int denali_write_oob(struct mtd_info *mtd, struct nand_chip *chip,
 
 	denali_oob_xfer(mtd, chip, page, 1);
 
+	if (chip->exec_op)
+		return nand_prog_page_end_op(chip);
+
 	chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1);
 	status = chip->waitfunc(mtd, chip);
 
diff --git a/drivers/mtd/nand/nand_base.c b/drivers/mtd/nand/nand_base.c
index bef20e06f0db..737f19bd2f83 100644
--- a/drivers/mtd/nand/nand_base.c
+++ b/drivers/mtd/nand/nand_base.c
@@ -814,7 +814,7 @@ static void nand_ccs_delay(struct nand_chip *chip)
 	 * Wait tCCS_min if it is correctly defined, otherwise wait 500ns
 	 * (which should be safe for all NANDs).
 	 */
-	if (chip->data_interface.timings.sdr.tCCS_min)
+	if (&chip->data_interface.timings.sdr.tCCS_min)
 		ndelay(chip->data_interface.timings.sdr.tCCS_min / 1000);
 	else
 		ndelay(500);
@@ -1233,6 +1233,118 @@ static int nand_init_data_interface(struct nand_chip *chip)
 }
 
 /**
+ * nand_fill_column_cycles - fill the column fields on an address array
+ * @chip: The NAND chip
+ * @addrs: Array of address cycles to fill
+ * @offset_in_page: The offset in the page
+ *
+ * Fills the first or the two first bytes of the @addrs field depending
+ * on the NAND bus width and the page size.
+ */
+int nand_fill_column_cycles(struct nand_chip *chip, u8 *addrs,
+			    unsigned int offset_in_page)
+{
+	struct mtd_info *mtd = nand_to_mtd(chip);
+
+	/*
+	 * The offset in page is expressed in bytes, if the NAND bus is 16-bit
+	 * wide, then it must be divided by 2.
+	 */
+	if (chip->options & NAND_BUSWIDTH_16) {
+		if (offset_in_page % 2) {
+			WARN_ON(true);
+			return -EINVAL;
+		}
+
+		offset_in_page /= 2;
+	}
+
+	addrs[0] = offset_in_page;
+
+	/* Small pages use 1 cycle for the columns, while large page need 2 */
+	if (mtd->writesize <= 512)
+		return 1;
+
+	addrs[1] = offset_in_page >> 8;
+
+	return 2;
+}
+EXPORT_SYMBOL_GPL(nand_fill_column_cycles);
+
+static int nand_sp_exec_read_page_op(struct nand_chip *chip, unsigned int page,
+				     unsigned int offset_in_page, void *buf,
+				     unsigned int len)
+{
+	struct mtd_info *mtd = nand_to_mtd(chip);
+	const struct nand_sdr_timings *sdr =
+		nand_get_sdr_timings(&chip->data_interface);
+	u8 addrs[4];
+	struct nand_op_instr instrs[] = {
+		NAND_OP_CMD(NAND_CMD_READ0, 0),
+		NAND_OP_ADDR(3, addrs, sdr->tWB_max),
+		NAND_OP_WAIT_RDY(sdr->tR_max, sdr->tRR_min),
+		NAND_OP_DATA_IN(len, buf, 0),
+	};
+	struct nand_operation op = NAND_OPERATION(instrs);
+
+	/* Drop the DATA_OUT instruction if len is set to 0. */
+	if (!len)
+		op.ninstrs--;
+
+	if (offset_in_page >= mtd->writesize)
+		instrs[0].cmd.opcode = NAND_CMD_READOOB;
+	else if (offset_in_page >= 256)
+		instrs[0].cmd.opcode = NAND_CMD_READ1;
+
+	if (nand_fill_column_cycles(chip, addrs, offset_in_page) < 0)
+		return -EINVAL;
+
+	addrs[1] = page;
+	addrs[2] = page >> 8;
+
+	if (chip->options & NAND_ROW_ADDR_3) {
+		addrs[3] = page >> 16;
+		instrs[1].addr.naddrs++;
+	}
+
+	return nand_exec_op(chip, &op);
+}
+
+static int nand_lp_exec_read_page_op(struct nand_chip *chip, unsigned int page,
+				     unsigned int offset_in_page, void *buf,
+				     unsigned int len)
+{
+	const struct nand_sdr_timings *sdr =
+		nand_get_sdr_timings(&chip->data_interface);
+	u8 addrs[5];
+	struct nand_op_instr instrs[] = {
+		NAND_OP_CMD(NAND_CMD_READ0, 0),
+		NAND_OP_ADDR(4, addrs, 0),
+		NAND_OP_CMD(NAND_CMD_READSTART, sdr->tWB_max),
+		NAND_OP_WAIT_RDY(sdr->tR_max, sdr->tRR_min),
+		NAND_OP_DATA_IN(len, buf, 0),
+	};
+	struct nand_operation op = NAND_OPERATION(instrs);
+
+	/* Drop the DATA_IN instruction if len is set to 0. */
+	if (!len)
+		op.ninstrs--;
+
+	if (nand_fill_column_cycles(chip, addrs, offset_in_page))
+		return -EINVAL;
+
+	addrs[2] = page;
+	addrs[3] = page >> 8;
+
+	if (chip->options & NAND_ROW_ADDR_3) {
+		addrs[4] = page >> 16;
+		instrs[1].addr.naddrs++;
+	}
+
+	return nand_exec_op(chip, &op);
+}
+
+/**
  * nand_read_page_op - Do a READ PAGE operation
  * @chip: The NAND chip
  * @page: page to read
@@ -1246,17 +1358,26 @@ static int nand_init_data_interface(struct nand_chip *chip)
  * Returns 0 for success or negative error code otherwise
  */
 int nand_read_page_op(struct nand_chip *chip, unsigned int page,
-		      unsigned int column, void *buf, unsigned int len)
+		      unsigned int offset_in_page, void *buf, unsigned int len)
 {
 	struct mtd_info *mtd = nand_to_mtd(chip);
 
 	if (len && !buf)
 		return -EINVAL;
 
-	if (column + len > mtd->writesize + mtd->oobsize)
+	if (offset_in_page + len > mtd->writesize + mtd->oobsize)
 		return -EINVAL;
 
-	chip->cmdfunc(mtd, NAND_CMD_READ0, column, page);
+	if (chip->exec_op) {
+		if (mtd->writesize > 512)
+			return nand_lp_exec_read_page_op(
+				chip, page, offset_in_page, buf, len);
+
+		return nand_sp_exec_read_page_op(chip, page, offset_in_page,
+						 buf, len);
+	}
+
+	chip->cmdfunc(mtd, NAND_CMD_READ0, offset_in_page, page);
 	if (len)
 		chip->read_buf(mtd, buf, len);
 
@@ -1286,6 +1407,25 @@ static int nand_read_param_page_op(struct nand_chip *chip, u8 page, void *buf,
 	if (len && !buf)
 		return -EINVAL;
 
+	if (chip->exec_op) {
+		const struct nand_sdr_timings *sdr =
+			nand_get_sdr_timings(&chip->data_interface);
+		struct nand_op_instr instrs[] = {
+			NAND_OP_CMD(NAND_CMD_PARAM, 0),
+			NAND_OP_ADDR(1, &page, sdr->tWB_max),
+			NAND_OP_WAIT_RDY(sdr->tR_max, sdr->tRR_min),
+			NAND_OP_8BIT_DATA_IN(len, buf, 0),
+		};
+		struct nand_operation op =
+			NAND_OPERATION(instrs);
+
+		/* Drop the DATA_IN instruction if len is set to 0. */
+		if (!len)
+			op.ninstrs--;
+
+		return nand_exec_op(chip, &op);
+	}
+
 	chip->cmdfunc(mtd, NAND_CMD_PARAM, page, -1);
 	for (i = 0; i < len; i++)
 		p[i] = chip->read_byte(mtd);
@@ -1306,18 +1446,48 @@ static int nand_read_param_page_op(struct nand_chip *chip, u8 page, void *buf,
  *
  * Returns 0 for success or negative error code otherwise
  */
-int nand_change_read_column_op(struct nand_chip *chip, unsigned int column,
-			       void *buf, unsigned int len, bool force_8bit)
+int nand_change_read_column_op(struct nand_chip *chip,
+			       unsigned int offset_in_page, void *buf,
+			       unsigned int len, bool force_8bit)
 {
 	struct mtd_info *mtd = nand_to_mtd(chip);
 
 	if (len && !buf)
 		return -EINVAL;
 
-	if (column + len > mtd->writesize + mtd->oobsize)
+	if (offset_in_page + len > mtd->writesize + mtd->oobsize)
 		return -EINVAL;
 
-	chip->cmdfunc(mtd, NAND_CMD_RNDOUT, column, -1);
+	/* Small page NANDs do not support column change. */
+	if (mtd->writesize <= 512)
+		return -ENOTSUPP;
+
+	if (chip->exec_op) {
+		const struct nand_sdr_timings *sdr =
+			nand_get_sdr_timings(&chip->data_interface);
+		u8 addrs[2] = {};
+		struct nand_op_instr instrs[] = {
+			NAND_OP_CMD(NAND_CMD_RNDOUT, 0),
+			NAND_OP_ADDR(2, addrs, 0),
+			NAND_OP_CMD(NAND_CMD_RNDOUTSTART, sdr->tCCS_min),
+			NAND_OP_DATA_IN(len, buf, 0),
+		};
+		struct nand_operation op =
+			NAND_OPERATION(instrs);
+
+		if (nand_fill_column_cycles(chip, addrs, offset_in_page) < 0)
+			return -EINVAL;
+
+		/* Drop the DATA_IN instruction if len is set to 0. */
+		if (!len)
+			op.ninstrs--;
+
+		instrs[3].data.force_8bit = force_8bit;
+
+		return nand_exec_op(chip, &op);
+	}
+
+	chip->cmdfunc(mtd, NAND_CMD_RNDOUT, offset_in_page, -1);
 	if (len)
 		chip->read_buf(mtd, buf, len);
 
@@ -1339,17 +1509,28 @@ EXPORT_SYMBOL_GPL(nand_change_read_column_op);
  * Returns 0 for success or negative error code otherwise
  */
 int nand_read_oob_op(struct nand_chip *chip, unsigned int page,
-		     unsigned int column, void *buf, unsigned int len)
+		     unsigned int offset_in_page, void *buf, unsigned int len)
 {
 	struct mtd_info *mtd = nand_to_mtd(chip);
 
 	if (len && !buf)
 		return -EINVAL;
 
-	if (column + len > mtd->oobsize)
+	if (offset_in_page + len > mtd->oobsize)
 		return -EINVAL;
 
-	chip->cmdfunc(mtd, NAND_CMD_READOOB, column, page);
+	if (chip->exec_op) {
+		offset_in_page += mtd->writesize;
+
+		if (mtd->writesize > 512)
+			return nand_lp_exec_read_page_op(
+				chip, page, offset_in_page, buf, len);
+
+		return nand_sp_exec_read_page_op(chip, page, offset_in_page,
+						 buf, len);
+	}
+
+	chip->cmdfunc(mtd, NAND_CMD_READOOB, offset_in_page, page);
 	if (len)
 		chip->read_buf(mtd, buf, len);
 
@@ -1357,6 +1538,62 @@ int nand_read_oob_op(struct nand_chip *chip, unsigned int page,
 }
 EXPORT_SYMBOL_GPL(nand_read_oob_op);
 
+static int nand_exec_prog_page_op(struct nand_chip *chip, unsigned int page,
+				  unsigned int offset_in_page, const void *buf,
+				  unsigned int len, bool prog)
+{
+	struct mtd_info *mtd = nand_to_mtd(chip);
+	const struct nand_sdr_timings *sdr =
+		nand_get_sdr_timings(&chip->data_interface);
+	u8 addrs[5] = {};
+	struct nand_op_instr instrs[] = {
+		/*
+		 * Pointer command will be adjusted if we're dealing
+		 * with a small page NAND.
+		 */
+		NAND_OP_CMD(NAND_CMD_READ0, 0),
+		NAND_OP_CMD(NAND_CMD_SEQIN, 0),
+		NAND_OP_ADDR(0, addrs, sdr->tADL_min),
+		NAND_OP_DATA_OUT(len, buf, 0),
+		NAND_OP_CMD(NAND_CMD_PAGEPROG, sdr->tWB_max),
+		NAND_OP_WAIT_RDY(sdr->tPROG_max, 0),
+	};
+	struct nand_operation op = NAND_OPERATION(instrs);
+	int naddrs = nand_fill_column_cycles(chip, addrs, offset_in_page);
+
+	if (naddrs < 0)
+		return naddrs;
+
+	addrs[naddrs++] = page;
+	addrs[naddrs++] = page >> 8;
+	if (chip->options & NAND_ROW_ADDR_3)
+		addrs[naddrs++] = page >> 16;
+
+	instrs[2].addr.naddrs = naddrs;
+
+	/* Drop the lasts instructions if we're not programming the page. */
+	if (!prog) {
+		op.ninstrs -= 2;
+		/* Also drop the DATA_OUT instruction if empty */
+		if (!len)
+			op.ninstrs--;
+	}
+
+	if (mtd->writesize <= 512) {
+		/* Small pages need some more tweaking */
+		if (offset_in_page >= mtd->writesize)
+			instrs[0].cmd.opcode = NAND_CMD_READOOB;
+		else if (offset_in_page >= 256)
+			instrs[0].cmd.opcode = NAND_CMD_READ1;
+	} else {
+		/* Drop the first command if dealing with large pages */
+		op.instrs++;
+		op.ninstrs--;
+	}
+
+	return nand_exec_op(chip, &op);
+}
+
 /**
  * nand_prog_page_begin_op - starts a PROG PAGE operation
  * @chip: The NAND chip
@@ -1371,7 +1608,7 @@ EXPORT_SYMBOL_GPL(nand_read_oob_op);
  * Returns 0 for success or negative error code otherwise
  */
 int nand_prog_page_begin_op(struct nand_chip *chip, unsigned int page,
-			    unsigned int column, const void *buf,
+			    unsigned int offset_in_page, const void *buf,
 			    unsigned int len)
 {
 	struct mtd_info *mtd = nand_to_mtd(chip);
@@ -1379,10 +1616,14 @@ int nand_prog_page_begin_op(struct nand_chip *chip, unsigned int page,
 	if (len && !buf)
 		return -EINVAL;
 
-	if (column + len > mtd->writesize + mtd->oobsize)
+	if (offset_in_page + len > mtd->writesize + mtd->oobsize)
 		return -EINVAL;
 
-	chip->cmdfunc(mtd, NAND_CMD_SEQIN, column, page);
+	if (chip->exec_op)
+		return nand_exec_prog_page_op(
+			chip, page, offset_in_page, buf, len, false);
+
+	chip->cmdfunc(mtd, NAND_CMD_SEQIN, offset_in_page, page);
 
 	if (buf)
 		chip->write_buf(mtd, buf, len);
@@ -1405,6 +1646,19 @@ int nand_prog_page_end_op(struct nand_chip *chip)
 	struct mtd_info *mtd = nand_to_mtd(chip);
 	int status;
 
+	if (chip->exec_op) {
+		const struct nand_sdr_timings *sdr =
+			nand_get_sdr_timings(&chip->data_interface);
+		struct nand_op_instr instrs[] = {
+			NAND_OP_CMD(NAND_CMD_PAGEPROG, sdr->tWB_max),
+			NAND_OP_WAIT_RDY(sdr->tPROG_max, 0),
+		};
+		struct nand_operation op =
+			NAND_OPERATION(instrs);
+
+		return nand_exec_op(chip, &op);
+	}
+
 	chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1);
 
 	status = chip->waitfunc(mtd, chip);
@@ -1429,7 +1683,8 @@ EXPORT_SYMBOL_GPL(nand_prog_page_end_op);
  * Returns 0 for success or negative error code otherwise
  */
 int nand_prog_page_op(struct nand_chip *chip, unsigned int page,
-		      unsigned int column, const void *buf, unsigned int len)
+		      unsigned int offset_in_page, const void *buf,
+		      unsigned int len)
 {
 	struct mtd_info *mtd = nand_to_mtd(chip);
 	int status;
@@ -1437,10 +1692,14 @@ int nand_prog_page_op(struct nand_chip *chip, unsigned int page,
 	if (!len || !buf)
 		return -EINVAL;
 
-	if (column + len > mtd->writesize + mtd->oobsize)
+	if (offset_in_page + len > mtd->writesize + mtd->oobsize)
 		return -EINVAL;
 
-	chip->cmdfunc(mtd, NAND_CMD_SEQIN, column, page);
+	if (chip->exec_op)
+		return nand_exec_prog_page_op(
+			chip, page, offset_in_page, buf, len, true);
+
+	chip->cmdfunc(mtd, NAND_CMD_SEQIN, offset_in_page, page);
 	chip->write_buf(mtd, buf, len);
 	chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1);
 
@@ -1465,7 +1724,8 @@ EXPORT_SYMBOL_GPL(nand_prog_page_op);
  *
  * Returns 0 for success or negative error code otherwise
  */
-int nand_change_write_column_op(struct nand_chip *chip, unsigned int column,
+int nand_change_write_column_op(struct nand_chip *chip,
+				unsigned int offset_in_page,
 				const void *buf, unsigned int len,
 				bool force_8bit)
 {
@@ -1474,10 +1734,38 @@ int nand_change_write_column_op(struct nand_chip *chip, unsigned int column,
 	if (len && !buf)
 		return -EINVAL;
 
-	if (column + len > mtd->writesize + mtd->oobsize)
+	if (offset_in_page + len > mtd->writesize + mtd->oobsize)
 		return -EINVAL;
 
-	chip->cmdfunc(mtd, NAND_CMD_RNDIN, column, -1);
+	/* Small page NANDs do not support column change. */
+	if (mtd->writesize <= 512)
+		return -ENOTSUPP;
+
+	if (chip->exec_op) {
+		const struct nand_sdr_timings *sdr =
+			nand_get_sdr_timings(&chip->data_interface);
+		u8 addrs[2];
+		struct nand_op_instr instrs[] = {
+			NAND_OP_CMD(NAND_CMD_RNDIN, 0),
+			NAND_OP_ADDR(2, addrs, sdr->tCCS_min),
+			NAND_OP_DATA_OUT(len, buf, 0),
+		};
+		struct nand_operation op =
+			NAND_OPERATION(instrs);
+
+		if (nand_fill_column_cycles(chip, addrs, offset_in_page) < 0)
+			return -EINVAL;
+
+		instrs[2].data.force_8bit = force_8bit;
+
+		/* Drop the DATA_OUT instruction if len is set to 0. */
+		if (!len)
+			op.ninstrs--;
+
+		return nand_exec_op(chip, &op);
+	}
+
+	chip->cmdfunc(mtd, NAND_CMD_RNDIN, offset_in_page, -1);
 	if (len)
 		chip->write_buf(mtd, buf, len);
 
@@ -1508,6 +1796,24 @@ int nand_readid_op(struct nand_chip *chip, u8 addr,
 	if (!len || !buf)
 		return -EINVAL;
 
+	if (chip->exec_op) {
+		const struct nand_sdr_timings *sdr =
+			nand_get_sdr_timings(&chip->data_interface);
+		struct nand_op_instr instrs[] = {
+			NAND_OP_CMD(NAND_CMD_READID, 0),
+			NAND_OP_ADDR(1, &addr, sdr->tADL_min),
+			NAND_OP_8BIT_DATA_IN(len, buf, 0),
+		};
+		struct nand_operation op =
+			NAND_OPERATION(instrs);
+
+		/* Drop the DATA_IN instruction if len is set to 0. */
+		if (!len)
+			op.ninstrs--;
+
+		return nand_exec_op(chip, &op);
+	}
+
 	chip->cmdfunc(mtd, NAND_CMD_READID, addr, -1);
 
 	for (i = 0; i < len; i++)
@@ -1532,6 +1838,22 @@ int nand_status_op(struct nand_chip *chip, u8 *status)
 {
 	struct mtd_info *mtd = nand_to_mtd(chip);
 
+	if (chip->exec_op) {
+		const struct nand_sdr_timings *sdr =
+			nand_get_sdr_timings(&chip->data_interface);
+		struct nand_op_instr instrs[] = {
+			NAND_OP_CMD(NAND_CMD_STATUS, sdr->tADL_min),
+			NAND_OP_8BIT_DATA_IN(1, status, 0),
+		};
+		struct nand_operation op =
+			NAND_OPERATION(instrs);
+
+		if (!status)
+			op.ninstrs--;
+
+		return nand_exec_op(chip, &op);
+	}
+
 	chip->cmdfunc(mtd, NAND_CMD_STATUS, -1, -1);
 	if (status)
 		*status = chip->read_byte(mtd);
@@ -1558,6 +1880,25 @@ int nand_erase_op(struct nand_chip *chip, unsigned int eraseblock)
 			    (chip->phys_erase_shift - chip->page_shift);
 	int status;
 
+	if (chip->exec_op) {
+		const struct nand_sdr_timings *sdr =
+			nand_get_sdr_timings(&chip->data_interface);
+		u8 addrs[3] = {	page, page >> 8, page >> 16 };
+		struct nand_op_instr instrs[] = {
+			NAND_OP_CMD(NAND_CMD_ERASE1, 0),
+			NAND_OP_ADDR(2, addrs, 0),
+			NAND_OP_CMD(NAND_CMD_ERASE2, sdr->tWB_max),
+			NAND_OP_WAIT_RDY(sdr->tBERS_max, 0),
+		};
+		struct nand_operation op =
+			NAND_OPERATION(instrs);
+
+		if (chip->options & NAND_ROW_ADDR_3)
+			instrs[1].addr.naddrs++;
+
+		return nand_exec_op(chip, &op);
+	}
+
 	chip->cmdfunc(mtd, NAND_CMD_ERASE1, -1, page);
 	chip->cmdfunc(mtd, NAND_CMD_ERASE2, -1, -1);
 
@@ -1591,6 +1932,22 @@ static int nand_set_features_op(struct nand_chip *chip, u8 feature,
 	const u8 *params = data;
 	int i, status;
 
+	if (chip->exec_op) {
+		const struct nand_sdr_timings *sdr =
+			nand_get_sdr_timings(&chip->data_interface);
+		struct nand_op_instr instrs[] = {
+			NAND_OP_CMD(NAND_CMD_SET_FEATURES, 0),
+			NAND_OP_ADDR(1, &feature, sdr->tADL_min),
+			NAND_OP_8BIT_DATA_OUT(ONFI_SUBFEATURE_PARAM_LEN, data,
+					      sdr->tWB_max),
+			NAND_OP_WAIT_RDY(sdr->tFEAT_max, 0),
+		};
+		struct nand_operation op =
+			NAND_OPERATION(instrs);
+
+		return nand_exec_op(chip, &op);
+	}
+
 	chip->cmdfunc(mtd, NAND_CMD_SET_FEATURES, feature, -1);
 	for (i = 0; i < ONFI_SUBFEATURE_PARAM_LEN; ++i)
 		chip->write_byte(mtd, params[i]);
@@ -1621,6 +1978,22 @@ static int nand_get_features_op(struct nand_chip *chip, u8 feature,
 	u8 *params = data;
 	int i;
 
+	if (chip->exec_op) {
+		const struct nand_sdr_timings *sdr =
+			nand_get_sdr_timings(&chip->data_interface);
+		struct nand_op_instr instrs[] = {
+			NAND_OP_CMD(NAND_CMD_GET_FEATURES, 0),
+			NAND_OP_ADDR(1, &feature, sdr->tWB_max),
+			NAND_OP_WAIT_RDY(sdr->tFEAT_max, sdr->tRR_min),
+			NAND_OP_8BIT_DATA_IN(ONFI_SUBFEATURE_PARAM_LEN,
+					     data, 0),
+		};
+		struct nand_operation op =
+			NAND_OPERATION(instrs);
+
+		return nand_exec_op(chip, &op);
+	}
+
 	chip->cmdfunc(mtd, NAND_CMD_GET_FEATURES, feature, -1);
 	for (i = 0; i < ONFI_SUBFEATURE_PARAM_LEN; ++i)
 		params[i] = chip->read_byte(mtd);
@@ -1642,6 +2015,19 @@ int nand_reset_op(struct nand_chip *chip)
 {
 	struct mtd_info *mtd = nand_to_mtd(chip);
 
+	if (chip->exec_op) {
+		const struct nand_sdr_timings *sdr =
+			nand_get_sdr_timings(&chip->data_interface);
+		struct nand_op_instr instrs[] = {
+			NAND_OP_CMD(NAND_CMD_RESET, sdr->tWB_max),
+			NAND_OP_WAIT_RDY(sdr->tRST_max, 0),
+		};
+		struct nand_operation op =
+			NAND_OPERATION(instrs);
+
+		return nand_exec_op(chip, &op);
+	}
+
 	chip->cmdfunc(mtd, NAND_CMD_RESET, -1, -1);
 
 	return 0;
@@ -1669,6 +2055,18 @@ int nand_read_data_op(struct nand_chip *chip, void *buf, unsigned int len,
 	if (!len || !buf)
 		return -EINVAL;
 
+	if (chip->exec_op) {
+		struct nand_op_instr instrs[] = {
+			NAND_OP_DATA_IN(len, buf, 0),
+		};
+		struct nand_operation op =
+			NAND_OPERATION(instrs);
+
+		instrs[0].data.force_8bit = force_8bit;
+
+		return nand_exec_op(chip, &op);
+	}
+
 	if (force_8bit) {
 		u8 *p = buf;
 		unsigned int i;
@@ -1704,6 +2102,18 @@ int nand_write_data_op(struct nand_chip *chip, const void *buf,
 	if (!len || !buf)
 		return -EINVAL;
 
+	if (chip->exec_op) {
+		struct nand_op_instr instrs[] = {
+			NAND_OP_DATA_OUT(len, buf, 0),
+		};
+		struct nand_operation op =
+			NAND_OPERATION(instrs);
+
+		instrs[0].data.force_8bit = force_8bit;
+
+		return nand_exec_op(chip, &op);
+	}
+
 	if (force_8bit) {
 		const u8 *p = buf;
 		unsigned int i;
@@ -1719,6 +2129,471 @@ int nand_write_data_op(struct nand_chip *chip, const void *buf,
 EXPORT_SYMBOL_GPL(nand_write_data_op);
 
 /**
+ * struct nand_op_parser_ctx - Context used by the parser
+ * @instrs: array of all the instructions that must be addressed
+ * @ninstrs: length of the @instrs array
+ * @instr_idx: index of the instruction in the @instrs array that matches the
+ *	       first instruction of the subop structure
+ * @instr_start_off: offset at which the first instruction of the subop
+ *		     structure must start if it is and address or a data
+ *		     instruction
+ *
+ * This structure is used by the core to handle splitting lengthy instructions
+ * into sub-operations.
+ */
+struct nand_op_parser_ctx {
+	const struct nand_op_instr *instrs;
+	unsigned int ninstrs;
+	unsigned int instr_idx;
+	unsigned int instr_start_off;
+	struct nand_subop subop;
+};
+
+/**
+ * nand_op_parser_must_split_instr - Checks if an instruction must be split
+ * @pat: the parser pattern that match
+ * @instr: the instruction array to check
+ * @start_offset: the offset from which to start in the first instruction of the
+ *		  @instr array
+ *
+ * Some NAND controllers are limited and cannot send X address cycles with a
+ * unique operation, or cannot read/write more than Y bytes at the same time.
+ * In this case, reduce the scope of this set of operation, and trigger another
+ * instruction with the rest.
+ *
+ * Returns true if the array of instruction must be split, false otherwise.
+ * The @start_offset parameter is also updated to the offset at which the first
+ * instruction of the next bundle of instruction must start (if an address or a
+ * data instruction).
+ */
+static bool
+nand_op_parser_must_split_instr(const struct nand_op_parser_pattern_elem *pat,
+				const struct nand_op_instr *instr,
+				unsigned int *start_offset)
+{
+	switch (pat->type) {
+	case NAND_OP_ADDR_INSTR:
+		if (!pat->addr.maxcycles)
+			break;
+
+		if (instr->addr.naddrs - *start_offset > pat->addr.maxcycles) {
+			*start_offset += pat->addr.maxcycles;
+			return true;
+		}
+		break;
+
+	case NAND_OP_DATA_IN_INSTR:
+	case NAND_OP_DATA_OUT_INSTR:
+		if (!pat->data.maxlen)
+			break;
+
+		if (instr->data.len - *start_offset > pat->data.maxlen) {
+			*start_offset += pat->data.maxlen;
+			return true;
+		}
+		break;
+
+	default:
+		break;
+	}
+
+	return false;
+}
+
+/**
+ * nand_op_parser_match_pat - Checks a pattern
+ * @pat: the parser pattern to check if it matches
+ * @ctx: the context structure to match with the pattern @pat
+ *
+ * Check if *one* given pattern matches the given sequence of instructions
+ */
+static bool
+nand_op_parser_match_pat(const struct nand_op_parser_pattern *pat,
+			 struct nand_op_parser_ctx *ctx)
+{
+	unsigned int i, j, boundary_off = ctx->instr_start_off;
+
+	ctx->subop.ninstrs = 0;
+
+	for (i = ctx->instr_idx, j = 0; i < ctx->ninstrs && j < pat->nelems;) {
+		const struct nand_op_instr *instr = &ctx->instrs[i];
+
+		/*
+		 * The pattern instruction does not match the operation
+		 * instruction. If the instruction is marked optional in the
+		 * pattern definition, we skip the pattern element and continue
+		 * to the next one. If the element is mandatory, there's no
+		 * match and we can return false directly.
+		 */
+		if (instr->type != pat->elems[j].type) {
+			if (!pat->elems[j].optional)
+				return false;
+
+			j++;
+			continue;
+		}
+
+		/*
+		 * Now check the pattern element constraints. If the pattern is
+		 * not able to handle the whole instruction in a single step,
+		 * we'll have to break it down into several instructions.
+		 * The *boudary_off value comes back updated to point to the
+		 * limit between the split instruction (the end of the original
+		 * chunk, the start of new next one).
+		 */
+		if (nand_op_parser_must_split_instr(&pat->elems[j], instr,
+						    &boundary_off)) {
+			ctx->subop.ninstrs++;
+			j++;
+			break;
+		}
+
+		ctx->subop.ninstrs++;
+		i++;
+		j++;
+		boundary_off = 0;
+	}
+
+	/*
+	 * This can happen if all instructions of a pattern are optional.
+	 * Still, if there's not at least one instruction handled by this
+	 * pattern, this is not a match, and we should try the next one (if
+	 * any).
+	 */
+	if (!ctx->subop.ninstrs)
+		return false;
+
+	/*
+	 * We had a match on the pattern head, but the pattern may be longer
+	 * than the instructions we're asked to execute. We need to make sure
+	 * there's no mandatory elements in the pattern tail.
+	 *
+	 * The case where all the operations of a pattern have been checked but
+	 * the number of instructions is bigger is handled right after this by
+	 * returning true on the pattern match, which will order the execution
+	 * of the subset of instructions later defined, while updating the
+	 * context ids to the next chunk of instructions.
+	 */
+	for (; j < pat->nelems; j++) {
+		if (!pat->elems[j].optional)
+			return false;
+	}
+
+	/*
+	 * We have a match: update the ctx and return true. The subop structure
+	 * will be used by the pattern's ->exec() function.
+	 */
+	ctx->subop.instrs = &ctx->instrs[ctx->instr_idx];
+	ctx->subop.first_instr_start_off = ctx->instr_start_off;
+	ctx->subop.last_instr_end_off = boundary_off;
+
+	/*
+	 * Update the pointers so the calling function will be able to recall
+	 * this one with a new subset of instructions.
+	 *
+	 * In the case where the last operation of this set is split, point to
+	 * the last unfinished job, knowing the starting offset.
+	 */
+	ctx->instr_idx = i;
+	ctx->instr_start_off = boundary_off;
+
+	return true;
+}
+
+#if IS_ENABLED(CONFIG_DYNAMIC_DEBUG) || defined(DEBUG)
+static void nand_op_parser_trace(const struct nand_op_parser_ctx *ctx,
+				 bool success)
+{
+	const struct nand_op_instr *instr;
+	bool in_subop = false;
+	char *is_in =  "    ->", *is_out = "      ", *prefix;
+	char *buf;
+	unsigned int len, off = 0;
+	int i, j;
+
+	if (success)
+		pr_debug("executing subop:\n");
+	else
+		pr_debug("pattern not found:\n");
+
+	for (i = 0; i < ctx->ninstrs; i++) {
+		instr = &ctx->instrs[i];
+
+		/*
+		 * ctx->instr_idx is not reliable because it may already had
+		 * been updated by the parser. Use pointers comparison instead.
+		 */
+		if (instr == &ctx->subop.instrs[0])
+			in_subop = true;
+
+		if (in_subop)
+			prefix = is_in;
+		else
+			prefix = is_out;
+
+		switch (instr->type) {
+		case NAND_OP_CMD_INSTR:
+			pr_debug("%sCMD      [0x%02x]\n", prefix,
+				 instr->cmd.opcode);
+			break;
+		case NAND_OP_ADDR_INSTR:
+			/*
+			 * A log line is much less than 50 bytes, plus 5 bytes
+			 * per address cycle to display.
+			 */
+			len = 50 + 5 * instr->addr.naddrs;
+			buf = kmalloc(len, GFP_KERNEL);
+			if (!buf)
+				return;
+			memset(buf, 0, len);
+			off += snprintf(buf, len, "ADDR     [%d cyc:",
+					instr->addr.naddrs);
+			for (j = 0; j < instr->addr.naddrs; j++)
+				off += snprintf(&buf[off], len - off, " 0x%02x",
+						instr->addr.addrs[j]);
+			pr_debug("%s%s]\n", prefix, buf);
+			break;
+		case NAND_OP_DATA_IN_INSTR:
+			pr_debug("%sDATA_IN  [%d B%s]\n", prefix,
+				 instr->data.len,
+				 instr->data.force_8bit ? ", force 8-bit" : "");
+			break;
+		case NAND_OP_DATA_OUT_INSTR:
+			pr_debug("%sDATA_OUT [%d B%s]\n", prefix,
+				 instr->data.len,
+				 instr->data.force_8bit ? ", force 8-bit" : "");
+			break;
+		case NAND_OP_WAITRDY_INSTR:
+			pr_debug("%sWAITRDY  [max %d ms]\n", prefix,
+				 instr->waitrdy.timeout_ms);
+			break;
+		}
+
+		if (instr == &ctx->subop.instrs[ctx->subop.ninstrs - 1])
+			in_subop = false;
+	}
+}
+#else
+static void nand_op_parser_trace(const struct nand_op_parser_ctx *ctx,
+				 bool success)
+{
+	/* NOP */
+}
+#endif
+
+/**
+ * nand_op_parser_exec_op - exec_op parser
+ * @chip: the NAND chip
+ * @parser: the parser to use given by the controller driver
+ * @op: the NAND operation to address
+ * @check_only: flag asking if the entire operation could be handled
+ *
+ * Function that must be called by each driver that implement the "exec_op API"
+ * in their own ->exec_op() implementation.
+ *
+ * The function iterates on all the instructions asked and make use of internal
+ * parsers to find matches between the instruction list and the handled patterns
+ * filled by the controller drivers inside the @parser structure. If needed, the
+ * instructions could be split into sub-operations and be executed sequentially.
+ */
+int nand_op_parser_exec_op(struct nand_chip *chip,
+			   const struct nand_op_parser *parser,
+			   const struct nand_operation *op, bool check_only)
+{
+	struct nand_op_parser_ctx ctx = {
+		.instrs = op->instrs,
+		.ninstrs = op->ninstrs,
+	};
+	unsigned int i;
+
+	while (ctx.instr_idx < op->ninstrs) {
+		bool pattern_found = false;
+		int ret;
+
+		for (i = 0; i < parser->npatterns; i++) {
+			const struct nand_op_parser_pattern *pattern;
+
+			pattern = &parser->patterns[i];
+			if (!nand_op_parser_match_pat(pattern, &ctx))
+				continue;
+
+			nand_op_parser_trace(&ctx, true);
+
+			if (check_only)
+				break;
+
+			ret = pattern->exec(chip, &ctx.subop);
+			if (ret)
+				return ret;
+
+			pattern_found = true;
+		}
+
+		if (!pattern_found) {
+			nand_op_parser_trace(&ctx, false);
+			return -ENOTSUPP;
+		}
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(nand_op_parser_exec_op);
+
+static bool nand_instr_is_data(const struct nand_op_instr *instr)
+{
+	return instr && (instr->type == NAND_OP_DATA_IN_INSTR ||
+			 instr->type == NAND_OP_DATA_OUT_INSTR);
+}
+
+static bool nand_subop_instr_is_valid(const struct nand_subop *subop,
+				      unsigned int op_id)
+{
+	return subop && op_id < subop->ninstrs;
+}
+
+static int nand_subop_get_start_off(const struct nand_subop *subop,
+				    unsigned int op_id)
+{
+	if (op_id)
+		return 0;
+
+	return subop->first_instr_start_off;
+}
+
+/**
+ * nand_subop_get_addr_start_off - Get the start offset in an address array
+ * @subop: The entire sub-operation
+ * @op_id: Index of the instruction inside the sub-operation
+ *
+ * Instructions arrays may be split by the parser between instructions,
+ * and also in the middle of an address instruction if the number of cycles
+ * to assert in one operation is not supported by the controller.
+ *
+ * For this, instead of using the first index of the ->addr.addrs field from the
+ * address instruction, the NAND controller driver must use this helper that
+ * will either return 0 if the index does not point to the first instruction of
+ * the sub-operation, or the offset of the next starting offset inside the
+ * address cycles.
+ *
+ * Returns the offset of the first address cycle to assert from the pointed
+ * address instruction.
+ */
+int nand_subop_get_addr_start_off(const struct nand_subop *subop,
+				  unsigned int op_id)
+{
+	if (!nand_subop_instr_is_valid(subop, op_id) ||
+	    subop->instrs[op_id].type != NAND_OP_ADDR_INSTR)
+		return -EINVAL;
+
+	return nand_subop_get_start_off(subop, op_id);
+}
+EXPORT_SYMBOL_GPL(nand_subop_get_addr_start_off);
+
+/**
+ * nand_subop_get_num_addr_cyc - Get the remaining address cycles to assert
+ * @subop: The entire sub-operation
+ * @op_id: Index of the instruction inside the sub-operation
+ *
+ * Instructions arrays may be split by the parser between instructions,
+ * and also in the middle of an address instruction if the number of cycles
+ * to assert in one operation is not supported by the controller.
+ *
+ * For this, instead of using the ->addr.naddrs fields of the address
+ * instruction, the NAND controller driver must use this helper that will
+ * return the actual number of cycles to assert between the first and last
+ * offset asked for this particular instruction.
+ *
+ * Returns the number of address cycles to assert from the pointed address
+ * instruction.
+ */
+int nand_subop_get_num_addr_cyc(const struct nand_subop *subop,
+				unsigned int op_id)
+{
+	int start_off, end_off;
+
+	if (!nand_subop_instr_is_valid(subop, op_id) ||
+	    subop->instrs[op_id].type != NAND_OP_ADDR_INSTR)
+		return -EINVAL;
+
+	start_off = nand_subop_get_addr_start_off(subop, op_id);
+
+	if (op_id == subop->ninstrs - 1 &&
+	    subop->last_instr_end_off)
+		end_off = subop->last_instr_end_off;
+	else
+		end_off = subop->instrs[op_id].addr.naddrs;
+
+	return end_off - start_off;
+}
+EXPORT_SYMBOL_GPL(nand_subop_get_num_addr_cyc);
+
+/**
+ * nand_subop_get_data_start_off - Get the start offset in a data array
+ * @subop: The entire sub-operation
+ * @op_id: Index of the instruction inside the sub-operation
+ *
+ * Instructions arrays may be split by the parser between instructions,
+ * and also in the middle of a data instruction if the number of bytes to access
+ * in one operation is greater that the controller limit.
+ *
+ * For this, instead of using the first index of the ->data.buf field from the
+ * data instruction, the NAND controller driver must use this helper that
+ * will either return 0 if the index does not point to the first instruction of
+ * the sub-operation, or the offset of the next starting offset inside the
+ * data buffer.
+ *
+ * Returns the data offset inside the pointed data instruction buffer from which
+ * to start.
+ */
+int nand_subop_get_data_start_off(const struct nand_subop *subop,
+				  unsigned int op_id)
+{
+	if (!nand_subop_instr_is_valid(subop, op_id) ||
+	    !nand_instr_is_data(&subop->instrs[op_id]))
+		return -EINVAL;
+
+	return nand_subop_get_start_off(subop, op_id);
+}
+EXPORT_SYMBOL_GPL(nand_subop_get_data_start_off);
+
+/**
+ * nand_subop_get_data_len - Get the number of bytes to retrieve
+ * @subop: The entire sub-operation
+ * @op_id: Index of the instruction inside the sub-operation
+ *
+ * Instructions arrays may be split by the parser between instructions,
+ * and also in the middle of a data instruction if the number of bytes to access
+ * in one operation is greater that the controller limit.
+ *
+ * For this, instead of using the ->data.len field from the data instruction,
+ * the NAND controller driver must use this helper that will return the actual
+ * length of data to move between the first and last offset asked for this
+ * particular instruction.
+ *
+ * Returns the length of the data to move from the pointed data instruction.
+ */
+int nand_subop_get_data_len(const struct nand_subop *subop,
+			    unsigned int op_id)
+{
+	int start_off = 0, end_off;
+
+	if (!nand_subop_instr_is_valid(subop, op_id) ||
+	    !nand_instr_is_data(&subop->instrs[op_id]))
+		return -EINVAL;
+
+	start_off = nand_subop_get_data_start_off(subop, op_id);
+
+	if (op_id == subop->ninstrs - 1 &&
+	    subop->last_instr_end_off)
+		end_off = subop->last_instr_end_off;
+	else
+		end_off = subop->instrs[op_id].data.len;
+
+	return end_off - start_off;
+}
+EXPORT_SYMBOL_GPL(nand_subop_get_data_len);
+
+/**
  * nand_reset - Reset and initialize a NAND device
  * @chip: The NAND chip
  * @chipnr: Internal die id
@@ -3940,7 +4815,7 @@ static void nand_set_defaults(struct nand_chip *chip)
 		chip->chip_delay = 20;
 
 	/* check, if a user supplied command function given */
-	if (chip->cmdfunc == NULL)
+	if (chip->cmdfunc == NULL && !chip->exec_op)
 		chip->cmdfunc = nand_command;
 
 	/* check, if a user supplied wait function given */
@@ -4823,15 +5698,35 @@ int nand_scan_ident(struct mtd_info *mtd, int maxchips,
 	if (!mtd->name && mtd->dev.parent)
 		mtd->name = dev_name(mtd->dev.parent);
 
-	if ((!chip->cmdfunc || !chip->select_chip) && !chip->cmd_ctrl) {
+	/*
+	 * ->cmdfunc() is legacy and will only be used if ->exec_op() is not
+	 * populated.
+	 */
+	if (chip->exec_op) {
 		/*
-		 * Default functions assigned for chip_select() and
-		 * cmdfunc() both expect cmd_ctrl() to be populated,
-		 * so we need to check that that's the case
+		 * The implementation of ->exec_op() heavily relies on timings
+		 * to be accessible through the nand_data_interface structure.
+		 * Thus, the ->setup_data_interface() hook must be provided. The
+		 * controller driver will be noticed of delays it must apply
+		 * after each ->exec_op() instruction (if any) through the
+		 * .delay_ns field. The driver will then choose to handle the
+		 * delays manually if the controller cannot do it natively.
 		 */
-		pr_err("chip.cmd_ctrl() callback is not provided");
-		return -EINVAL;
+		if (!chip->setup_data_interface) {
+			pr_err("->setup_data_interface() should be provided\n");
+			return -EINVAL;
+		}
+	} else {
+		/*
+		 * Default functions assigned for ->cmdfunc() and
+		 * ->select_chip() both expect ->cmd_ctrl() to be populated.
+		 */
+		if ((!chip->cmdfunc || !chip->select_chip) && !chip->cmd_ctrl) {
+			pr_err("->cmd_ctrl() should be provided\n");
+			return -EINVAL;
+		}
 	}
+
 	/* Set the default functions */
 	nand_set_defaults(chip);
 
diff --git a/drivers/mtd/nand/nand_hynix.c b/drivers/mtd/nand/nand_hynix.c
index 04e3ab7a476c..81c382f24513 100644
--- a/drivers/mtd/nand/nand_hynix.c
+++ b/drivers/mtd/nand/nand_hynix.c
@@ -74,19 +74,36 @@ static bool hynix_nand_has_valid_jedecid(struct nand_chip *chip)
 	return !strncmp("JEDEC", jedecid, sizeof(jedecid));
 }
 
+static int hynix_nand_cmd_op(struct nand_chip *chip, u8 cmd)
+{
+	struct mtd_info *mtd = nand_to_mtd(chip);
+
+	if (chip->exec_op) {
+		struct nand_op_instr instrs[] = {
+			NAND_OP_CMD(cmd, 0),
+		};
+		struct nand_operation op = NAND_OPERATION(instrs);
+
+		return nand_exec_op(chip, &op);
+	}
+
+	chip->cmdfunc(mtd, cmd, -1, -1);
+
+	return 0;
+}
+
 static int hynix_nand_setup_read_retry(struct mtd_info *mtd, int retry_mode)
 {
 	struct nand_chip *chip = mtd_to_nand(mtd);
 	struct hynix_nand *hynix = nand_get_manufacturer_data(chip);
 	const u8 *values;
-	int status;
 	int i;
 
 	values = hynix->read_retry->values +
 		 (retry_mode * hynix->read_retry->nregs);
 
 	/* Enter 'Set Hynix Parameters' mode */
-	chip->cmdfunc(mtd, NAND_HYNIX_CMD_SET_PARAMS, -1, -1);
+	hynix_nand_cmd_op(chip, NAND_HYNIX_CMD_SET_PARAMS);
 
 	/*
 	 * Configure the NAND in the requested read-retry mode.
@@ -101,16 +118,17 @@ static int hynix_nand_setup_read_retry(struct mtd_info *mtd, int retry_mode)
 		int column = hynix->read_retry->regs[i];
 
 		column |= column << 8;
-		chip->cmdfunc(mtd, NAND_CMD_NONE, column, -1);
-		chip->write_byte(mtd, values[i]);
+		if (chip->exec_op) {
+			nand_change_write_column_op(chip, column,
+						    &values[i], 1, true);
+		} else {
+			chip->cmdfunc(mtd, NAND_CMD_NONE, column, -1);
+			chip->write_byte(mtd, values[i]);
+		}
 	}
 
 	/* Apply the new settings. */
-	chip->cmdfunc(mtd, NAND_HYNIX_CMD_APPLY_PARAMS, -1, -1);
-
-	status = chip->waitfunc(mtd, chip);
-	if (status & NAND_STATUS_FAIL)
-		return -EIO;
+	hynix_nand_cmd_op(chip, NAND_HYNIX_CMD_APPLY_PARAMS);
 
 	return 0;
 }
@@ -173,32 +191,65 @@ static int hynix_read_rr_otp(struct nand_chip *chip,
 
 	nand_reset_op(chip);
 
-	chip->cmdfunc(mtd, NAND_HYNIX_CMD_SET_PARAMS, -1, -1);
+	hynix_nand_cmd_op(chip, NAND_HYNIX_CMD_SET_PARAMS);
 
 	for (i = 0; i < info->nregs; i++) {
 		int column = info->regs[i];
 
 		column |= column << 8;
-		chip->cmdfunc(mtd, NAND_CMD_NONE, column, -1);
-		chip->write_byte(mtd, info->values[i]);
+		if (chip->exec_op) {
+			nand_change_write_column_op(chip, column,
+						    &info->values[i], 1, true);
+		} else {
+			chip->cmdfunc(mtd, NAND_CMD_NONE, column, -1);
+			chip->write_byte(mtd, info->values[i]);
+		}
 	}
 
-	chip->cmdfunc(mtd, NAND_HYNIX_CMD_APPLY_PARAMS, -1, -1);
+	hynix_nand_cmd_op(chip, NAND_HYNIX_CMD_APPLY_PARAMS);
 
 	/* Sequence to enter OTP mode? */
-	chip->cmdfunc(mtd, 0x17, -1, -1);
-	chip->cmdfunc(mtd, 0x04, -1, -1);
-	chip->cmdfunc(mtd, 0x19, -1, -1);
+	if (chip->exec_op) {
+		struct nand_op_instr instrs[] = {
+			NAND_OP_CMD(0x17, 0),
+			NAND_OP_CMD(0x04, 0),
+			NAND_OP_CMD(0x19, 0),
+		};
+		struct nand_operation op = NAND_OPERATION(instrs);
+
+		nand_exec_op(chip, &op);
+	} else {
+		chip->cmdfunc(mtd, 0x17, -1, -1);
+		chip->cmdfunc(mtd, 0x04, -1, -1);
+		chip->cmdfunc(mtd, 0x19, -1, -1);
+	}
 
 	/* Now read the page */
 	nand_read_page_op(chip, info->page, 0, buf, info->size);
 
 	/* Put everything back to normal */
 	nand_reset_op(chip);
-	chip->cmdfunc(mtd, NAND_HYNIX_CMD_SET_PARAMS, 0x38, -1);
-	chip->write_byte(mtd, 0x0);
-	chip->cmdfunc(mtd, NAND_HYNIX_CMD_APPLY_PARAMS, -1, -1);
-	chip->cmdfunc(mtd, NAND_CMD_READ0, 0x0, -1);
+	if (chip->exec_op) {
+		const struct nand_sdr_timings *sdr =
+			nand_get_sdr_timings(&chip->data_interface);
+		u8 byte = 0;
+		u8 addr = 0x38;
+		struct nand_op_instr instrs[] = {
+			NAND_OP_CMD(NAND_HYNIX_CMD_SET_PARAMS, 0),
+			NAND_OP_ADDR(1, &addr, sdr->tCCS_min),
+			NAND_OP_8BIT_DATA_OUT(1, &byte, 0),
+			NAND_OP_CMD(NAND_HYNIX_CMD_APPLY_PARAMS, 0),
+			NAND_OP_CMD(NAND_CMD_READ0, 0),
+		};
+		struct nand_operation op = NAND_OPERATION(instrs);
+
+		nand_exec_op(chip, &op);
+	} else {
+		chip->cmdfunc(mtd, NAND_HYNIX_CMD_SET_PARAMS, 0x38, -1);
+		chip->write_byte(mtd, 0x0);
+		chip->cmdfunc(mtd, NAND_HYNIX_CMD_APPLY_PARAMS, -1, -1);
+		chip->cmdfunc(mtd, NAND_CMD_READ0, 0x0, -1);
+	}
 
 	return 0;
 }
diff --git a/drivers/mtd/nand/nand_micron.c b/drivers/mtd/nand/nand_micron.c
index 543352380ffa..109d8003e33d 100644
--- a/drivers/mtd/nand/nand_micron.c
+++ b/drivers/mtd/nand/nand_micron.c
@@ -117,14 +117,38 @@ micron_nand_read_page_on_die_ecc(struct mtd_info *mtd, struct nand_chip *chip,
 				 uint8_t *buf, int oob_required,
 				 int page)
 {
-	int status;
+	u8 status;
 	int max_bitflips = 0;
 
 	micron_nand_on_die_ecc_setup(chip, true);
 
-	chip->cmdfunc(mtd, NAND_CMD_READ0, 0x00, page);
-	chip->cmdfunc(mtd, NAND_CMD_STATUS, -1, -1);
-	status = chip->read_byte(mtd);
+	if (chip->exec_op) {
+		u8 addrs[5] = {};
+		struct nand_op_instr instrs[] = {
+			NAND_OP_CMD(NAND_CMD_READ0, 0),
+			NAND_OP_ADDR(4, addrs, 0),
+			NAND_OP_CMD(NAND_CMD_STATUS, 0),
+			NAND_OP_8BIT_DATA_IN(1, &status, 0),
+		};
+		struct nand_operation op = NAND_OPERATION(instrs);
+
+		if (nand_fill_column_cycles(chip, addrs, 0))
+			return -EINVAL;
+
+		addrs[2] = page;
+		addrs[3] = page >> 8;
+		if (chip->options & NAND_ROW_ADDR_3) {
+			addrs[4] = page >> 16;
+			instrs[1].addr.naddrs++;
+		}
+
+		nand_exec_op(chip, &op);
+	} else {
+		chip->cmdfunc(mtd, NAND_CMD_READ0, 0x00, page);
+		chip->cmdfunc(mtd, NAND_CMD_STATUS, -1, -1);
+		status = chip->read_byte(mtd);
+	}
+
 	if (status & NAND_STATUS_FAIL)
 		mtd->ecc_stats.failed++;
 	/*
diff --git a/include/linux/mtd/rawnand.h b/include/linux/mtd/rawnand.h
index 1acc26ed0e91..302f231df65e 100644
--- a/include/linux/mtd/rawnand.h
+++ b/include/linux/mtd/rawnand.h
@@ -751,6 +751,337 @@ struct nand_manufacturer_ops {
 };
 
 /**
+ * struct nand_op_cmd_instr - Definition of a command instruction
+ * @opcode: the command to assert in one cycle
+ */
+struct nand_op_cmd_instr {
+	u8 opcode;
+};
+
+/**
+ * struct nand_op_addr_instr - Definition of an address instruction
+ * @naddrs: length of the @addrs array
+ * @addrs: array containing the address cycles to assert
+ */
+struct nand_op_addr_instr {
+	unsigned int naddrs;
+	const u8 *addrs;
+};
+
+/**
+ * struct nand_op_data_instr - Definition of a data instruction
+ * @len: number of data bytes to move
+ * @in: buffer to fill when reading from the NAND chip
+ * @out: buffer to read from when writing to the NAND chip
+ * @force_8bit: force 8-bit access
+ *
+ * Please note that "in" and "out" are inverted from the ONFI specification
+ * and are from the controller perspective, so a "in" is a read from the NAND
+ * chip while a "out" is a write to the NAND chip.
+ */
+struct nand_op_data_instr {
+	unsigned int len;
+	union {
+		void *in;
+		const void *out;
+	};
+	bool force_8bit;
+};
+
+/**
+ * struct nand_op_waitrdy_instr - Definition of a wait ready instruction
+ * @timeout_ms: maximum delay while waiting for the ready/busy pin in ms
+ */
+struct nand_op_waitrdy_instr {
+	unsigned int timeout_ms;
+};
+
+/**
+ * enum nand_op_instr_type - Enumeration of all the instructions
+ *
+ * Please note that data instructions are separated into DATA_IN and DATA_OUT
+ * instructions.
+ */
+enum nand_op_instr_type {
+	NAND_OP_CMD_INSTR,
+	NAND_OP_ADDR_INSTR,
+	NAND_OP_DATA_IN_INSTR,
+	NAND_OP_DATA_OUT_INSTR,
+	NAND_OP_WAITRDY_INSTR,
+};
+
+/**
+ * struct nand_op_instr - Generic definition of and instruction
+ * @type: an enumeration of the instruction type
+ * @cmd/@addr/@data/@waitrdy: the actual instruction to refer depending on the
+ *			      value of @type
+ * @delay_ns: delay to apply by the controller after the instruction has been
+ *	      actually executed (most of them are directly handled by the
+ *	      controllers once the timings negociation has been done)
+ */
+struct nand_op_instr {
+	enum nand_op_instr_type type;
+	union {
+		struct nand_op_cmd_instr cmd;
+		struct nand_op_addr_instr addr;
+		struct nand_op_data_instr data;
+		struct nand_op_waitrdy_instr waitrdy;
+	};
+	unsigned int delay_ns;
+};
+
+/*
+ * Special handling must be done for the WAITRDY timeout parameter as it usually
+ * is either tPROG (after a prog), tR (before a read), tRST (during a reset) or
+ * tBERS (during an erase) which all of them are u64 values that cannot be
+ * divided by usual kernel macros and must be handled with the special
+ * DIV_ROUND_UP_ULL() macro.
+ */
+#define PSEC_TO_NSEC(x) DIV_ROUND_UP(x, 10^3)
+#define PSEC_TO_MSEC(x) DIV_ROUND_UP_ULL(x, 10^9)
+
+#define NAND_OP_CMD(id, delay_ps)					\
+	{								\
+		.type = NAND_OP_CMD_INSTR,				\
+		.cmd.opcode = id,					\
+		.delay_ns = PSEC_TO_NSEC(delay_ps),			\
+	}
+
+#define NAND_OP_ADDR(ncycles, cycles, delay_ps)				\
+	{								\
+		.type = NAND_OP_ADDR_INSTR,				\
+		.addr = {						\
+			.naddrs = ncycles,				\
+			.addrs = cycles,				\
+		},							\
+		.delay_ns = PSEC_TO_NSEC(delay_ps),			\
+	}
+
+#define NAND_OP_DATA_IN(l, buf, delay_ps)				\
+	{								\
+		.type = NAND_OP_DATA_IN_INSTR,				\
+		.data = {						\
+			.len = l,					\
+			.in = buf,					\
+			.force_8bit = false,				\
+		},							\
+		.delay_ns = PSEC_TO_NSEC(delay_ps),			\
+	}
+
+#define NAND_OP_DATA_OUT(l, buf, delay_ps)				\
+	{								\
+		.type = NAND_OP_DATA_OUT_INSTR,				\
+		.data = {						\
+			.len = l,					\
+			.out = buf,					\
+			.force_8bit = false,				\
+		},							\
+		.delay_ns = PSEC_TO_NSEC(delay_ps),			\
+	}
+
+#define NAND_OP_8BIT_DATA_IN(l, buf, delay_ps)				\
+	{								\
+		.type = NAND_OP_DATA_IN_INSTR,				\
+		.data = {						\
+			.len = l,					\
+			.in = buf,					\
+			.force_8bit = true,				\
+		},							\
+		.delay_ns = PSEC_TO_NSEC(delay_ps),			\
+	}
+
+#define NAND_OP_8BIT_DATA_OUT(l, buf, delay_ps)				\
+	{								\
+		.type = NAND_OP_DATA_OUT_INSTR,				\
+		.data = {						\
+			.len = l,					\
+			.out = buf,					\
+			.force_8bit = true,				\
+		},							\
+		.delay_ns = PSEC_TO_NSEC(delay_ps),			\
+	}
+
+#define NAND_OP_WAIT_RDY(tout_ps, delay_ps)				\
+	{								\
+		.type = NAND_OP_WAITRDY_INSTR,				\
+		.waitrdy.timeout_ms = PSEC_TO_MSEC(tout_ps),		\
+		.delay_ns = PSEC_TO_NSEC(delay_ps),			\
+	}
+
+/**
+ * struct nand_subop - a sub operation
+ * @instrs: array of instructions
+ * @ninstrs: length of the @instrs array
+ * @first_instr_start_off: offset to start from for the first instruction
+ *			   of the sub-operation
+ * @last_instr_end_off: offset to end at (excluded) for the last instruction
+ *			of the sub-operation
+ *
+ * When an operation cannot be handled as is by the NAND controller, it will
+ * be split by the parser and the remaining pieces will be handled as
+ * sub-operations.
+ */
+struct nand_subop {
+	const struct nand_op_instr *instrs;
+	unsigned int ninstrs;
+	/*
+	 * Specific offset for the first and the last instructions of the subop.
+	 * Applies for the address cycles in the case of address, or for data
+	 * offset in the case of data transfers. Otherwise, it is irrelevant.
+	 */
+	unsigned int first_instr_start_off;
+	unsigned int last_instr_end_off;
+};
+
+int nand_subop_get_addr_start_off(const struct nand_subop *subop,
+				  unsigned int op_id);
+int nand_subop_get_num_addr_cyc(const struct nand_subop *subop,
+				unsigned int op_id);
+int nand_subop_get_data_start_off(const struct nand_subop *subop,
+				  unsigned int op_id);
+int nand_subop_get_data_len(const struct nand_subop *subop,
+			    unsigned int op_id);
+
+/**
+ * struct nand_op_parser_addr_constraints - Constraints for address instructions
+ * @maxcycles: maximum number of cycles that the controller can assert by
+ *	       instruction
+ */
+struct nand_op_parser_addr_constraints {
+	unsigned int maxcycles;
+};
+
+/**
+ * struct nand_op_parser_data_constraints - Constraints for data instructions
+ * @maxlen: maximum data length that the controller can handle with one
+ *	    instruction
+ */
+struct nand_op_parser_data_constraints {
+	unsigned int maxlen;
+};
+
+/**
+ * struct nand_op_parser_pattern_elem - One element of a pattern
+ * @type: the instructuction type
+ * @optional: if this element of the pattern is optional or mandatory
+ * @addr/@data: address or data constraint (number of cycles or data length)
+ */
+struct nand_op_parser_pattern_elem {
+	enum nand_op_instr_type type;
+	bool optional;
+	union {
+		struct nand_op_parser_addr_constraints addr;
+		struct nand_op_parser_data_constraints data;
+	};
+};
+
+#define NAND_OP_PARSER_PAT_CMD_ELEM(_opt)			\
+	{							\
+		.type = NAND_OP_CMD_INSTR,			\
+		.optional = _opt,				\
+	}
+
+#define NAND_OP_PARSER_PAT_ADDR_ELEM(_opt, _maxcycles)		\
+	{							\
+		.type = NAND_OP_ADDR_INSTR,			\
+		.optional = _opt,				\
+		.addr.maxcycles = _maxcycles,			\
+	}
+
+#define NAND_OP_PARSER_PAT_DATA_IN_ELEM(_opt, _maxlen)		\
+	{							\
+		.type = NAND_OP_DATA_IN_INSTR,			\
+		.optional = _opt,				\
+		.data.maxlen = _maxlen,				\
+	}
+
+#define NAND_OP_PARSER_PAT_DATA_OUT_ELEM(_opt, _maxlen)		\
+	{							\
+		.type = NAND_OP_DATA_OUT_INSTR,			\
+		.optional = _opt,				\
+		.data.maxlen = _maxlen,				\
+	}
+
+#define NAND_OP_PARSER_PAT_WAITRDY_ELEM(_opt)			\
+	{							\
+		.type = NAND_OP_WAITRDY_INSTR,			\
+		.optional = _opt,				\
+	}
+
+/**
+ * struct nand_op_parser_pattern - A complete pattern
+ * @elems: array of pattern elements
+ * @nelems: number of pattern elements in @elems array
+ * @exec: the function that will actually execute this pattern, written in the
+ *	  controller driver
+ *
+ * This is a complete pattern that is a list of elements, each one reprensenting
+ * one instruction with its constraints. Controller drivers must declare as much
+ * patterns as they support and give the list of the supported patterns (created
+ * with the help of the following macro) at the call to nand_op_parser_exec_op
+ * which shall be the main thing to do in the driver implementation of
+ * ->exec_op(). Once there is a match between the pattern and an operation, the
+ * core calls the @exec function to actually do the operation.
+ */
+struct nand_op_parser_pattern {
+	const struct nand_op_parser_pattern_elem *elems;
+	unsigned int nelems;
+	int (*exec)(struct nand_chip *chip, const struct nand_subop *subop);
+};
+
+#define NAND_OP_PARSER_PATTERN(_exec, ...)							\
+	{											\
+		.exec = _exec,									\
+		.elems = (struct nand_op_parser_pattern_elem[]) { __VA_ARGS__ },		\
+		.nelems = sizeof((struct nand_op_parser_pattern_elem[]) { __VA_ARGS__ }) /	\
+			  sizeof(struct nand_op_parser_pattern_elem),				\
+	}
+
+/**
+ * struct nand_op_parser - The actual parser
+ * @patterns: array of patterns
+ * @npatterns: length of the @patterns array
+ *
+ * The actual parser structure wich is an array of supported patterns.
+ */
+struct nand_op_parser {
+	const struct nand_op_parser_pattern *patterns;
+	unsigned int npatterns;
+};
+
+#define NAND_OP_PARSER(...)									\
+	{											\
+		.patterns = (struct nand_op_parser_pattern[]) { __VA_ARGS__ },			\
+		.npatterns = sizeof((struct nand_op_parser_pattern[]) { __VA_ARGS__ }) /	\
+			     sizeof(struct nand_op_parser_pattern),				\
+	}
+
+/**
+ * struct nand_operation - The actual operation
+ * @instrs: array of instructions to execute
+ * @ninstrs: length of the @instrs array
+ *
+ * The actual operation structure that will be given to the parser.
+ */
+struct nand_operation {
+	const struct nand_op_instr *instrs;
+	unsigned int ninstrs;
+};
+
+#define NAND_OPERATION(_instrs)					\
+	{							\
+		.instrs = _instrs,				\
+		.ninstrs = ARRAY_SIZE(_instrs),			\
+	}
+
+int nand_fill_column_cycles(struct nand_chip *chip, u8 *addrs,
+			    unsigned int offset_in_page);
+
+int nand_op_parser_exec_op(struct nand_chip *chip,
+			   const struct nand_op_parser *parser,
+			   const struct nand_operation *op, bool check_only);
+
+/**
  * struct nand_chip - NAND Private Flash Chip Data
  * @mtd:		MTD device registered to the MTD framework
  * @IO_ADDR_R:		[BOARDSPECIFIC] address to read the 8 I/O lines of the
@@ -885,6 +1216,9 @@ struct nand_chip {
 	int (*setup_data_interface)(struct mtd_info *mtd, int chipnr,
 				    const struct nand_data_interface *conf);
 
+	int (*exec_op)(struct nand_chip *chip,
+		       const struct nand_operation *op,
+		       bool check_only);
 
 	int chip_delay;
 	unsigned int options;
@@ -945,6 +1279,15 @@ struct nand_chip {
 	} manufacturer;
 };
 
+static inline int nand_exec_op(struct nand_chip *chip,
+			       const struct nand_operation *op)
+{
+	if (!chip->exec_op)
+		return -ENOTSUPP;
+
+	return chip->exec_op(chip, op, false);
+}
+
 extern const struct mtd_ooblayout_ops nand_ooblayout_sp_ops;
 extern const struct mtd_ooblayout_ops nand_ooblayout_lp_ops;
 
@@ -1307,23 +1650,26 @@ int nand_readid_op(struct nand_chip *chip, u8 addr, void *buf,
 int nand_status_op(struct nand_chip *chip, u8 *status);
 int nand_erase_op(struct nand_chip *chip, unsigned int eraseblock);
 int nand_read_page_op(struct nand_chip *chip, unsigned int page,
-		      unsigned int column, void *buf, unsigned int len);
-int nand_change_read_column_op(struct nand_chip *chip, unsigned int column,
-			       void *buf, unsigned int len, bool force_8bit);
+		      unsigned int offset_in_page, void *buf, unsigned int len);
+int nand_change_read_column_op(struct nand_chip *chip,
+			       unsigned int offset_in_page, void *buf,
+			       unsigned int len, bool force_8bit);
 int nand_read_oob_op(struct nand_chip *chip, unsigned int page,
-		     unsigned int column, void *buf, unsigned int len);
+		     unsigned int offset_in_page, void *buf, unsigned int len);
 int nand_prog_page_begin_op(struct nand_chip *chip, unsigned int page,
-			    unsigned int column, const void *buf,
+			    unsigned int offset_in_page, const void *buf,
 			    unsigned int len);
 int nand_prog_page_end_op(struct nand_chip *chip);
 int nand_prog_page_op(struct nand_chip *chip, unsigned int page,
-		      unsigned int column, const void *buf, unsigned int len);
-int nand_change_write_column_op(struct nand_chip *chip, unsigned int column,
-				const void *buf, unsigned int len, bool force_8bit);
+		      unsigned int offset_in_page, const void *buf,
+		      unsigned int len);
+int nand_change_write_column_op(struct nand_chip *chip,
+				unsigned int offset_in_page, const void *buf,
+				unsigned int len, bool force_8bit);
 int nand_read_data_op(struct nand_chip *chip, void *buf, unsigned int len,
-		      bool force_8bits);
+		      bool force_8bit);
 int nand_write_data_op(struct nand_chip *chip, const void *buf,
-		       unsigned int len, bool force_8bits);
+		       unsigned int len, bool force_8bit);
 
 /* Free resources held by the NAND device */
 void nand_cleanup(struct nand_chip *chip);
-- 
2.11.0

^ permalink raw reply related	[flat|nested] 75+ messages in thread

* [RFC 04/12] mtd: nand: add ->exec_op() implementation
@ 2017-10-18 14:36   ` Miquel Raynal
  0 siblings, 0 replies; 75+ messages in thread
From: Miquel Raynal @ 2017-10-18 14:36 UTC (permalink / raw)
  To: Andrew Lunn, bcm-kernel-feedback-list, Boris Brezillon,
	Brian Norris, Catalin Marinas, Chen-Yu Tsai, Cyrille Pitchen,
	Daniel Mack, David Woodhouse, devel, devicetree, Ezequiel Garcia,
	Greg Kroah-Hartman, Gregory Clement, Han Xu, Haojian Zhuang,
	Jason Cooper, Josh Wu, Kamal Dasu, linux-arm-kernel,
	linux-kernel, linux-mediatek, linux-mtd
  Cc: Hanna Hawa, Antoine Tenart, Nadav Haklai, Neta Zur Hershkovits,
	Igor Grinberg, Miquel Raynal, Ofer Heifetz

Introduce the new way to control the NAND controller drivers by
implementing the ->exec_op() core helpers and allowing new drivers to
use it instead of relying on ->cmd_ctrl(), ->cmdfunc() and
->read/write_byte/word/buf().

The logic is now to send to the controller driver a list of
instructions. The driver shall use the parser added by this commit to
get the matching hook if any. The instructions may be split by the
parser in order to comply with the controller constraints filled in an
array of supported patterns.

Various helpers are also added to ease NAND controller drivers writing.

This new interface should really ease the support of new vendor specific
instructions.

Suggested-by: Boris Brezillon <boris.brezillon@free-electrons.com>
Signed-off-by: Miquel Raynal <miquel.raynal@free-electrons.com>
---
 drivers/mtd/nand/denali.c      |  93 +++-
 drivers/mtd/nand/nand_base.c   | 949 +++++++++++++++++++++++++++++++++++++++--
 drivers/mtd/nand/nand_hynix.c  |  91 +++-
 drivers/mtd/nand/nand_micron.c |  32 +-
 include/linux/mtd/rawnand.h    | 366 +++++++++++++++-
 5 files changed, 1448 insertions(+), 83 deletions(-)

diff --git a/drivers/mtd/nand/denali.c b/drivers/mtd/nand/denali.c
index e5f38359f6df..8f0f18d9d9cf 100644
--- a/drivers/mtd/nand/denali.c
+++ b/drivers/mtd/nand/denali.c
@@ -652,8 +652,6 @@ static void denali_oob_xfer(struct mtd_info *mtd, struct nand_chip *chip,
 			    int page, int write)
 {
 	struct denali_nand_info *denali = mtd_to_denali(mtd);
-	unsigned int start_cmd = write ? NAND_CMD_SEQIN : NAND_CMD_READ0;
-	unsigned int rnd_cmd = write ? NAND_CMD_RNDIN : NAND_CMD_RNDOUT;
 	int writesize = mtd->writesize;
 	int oobsize = mtd->oobsize;
 	uint8_t *bufpoi = chip->oob_poi;
@@ -665,11 +663,22 @@ static void denali_oob_xfer(struct mtd_info *mtd, struct nand_chip *chip,
 	int i, pos, len;
 
 	/* BBM at the beginning of the OOB area */
-	chip->cmdfunc(mtd, start_cmd, writesize, page);
-	if (write)
-		chip->write_buf(mtd, bufpoi, oob_skip);
-	else
-		chip->read_buf(mtd, bufpoi, oob_skip);
+	if (chip->exec_op) {
+		if (write)
+			nand_prog_page_begin_op(chip, page, writesize, bufpoi,
+						oob_skip);
+		else
+			nand_read_page_op(chip, page, writesize, bufpoi,
+					  oob_skip);
+	} else {
+		if (write) {
+			chip->cmdfunc(mtd, NAND_CMD_SEQIN, writesize, page);
+			chip->write_buf(mtd, bufpoi, oob_skip);
+		} else {
+			chip->cmdfunc(mtd, NAND_CMD_READ0, writesize, page);
+			chip->read_buf(mtd, bufpoi, oob_skip);
+		}
+	}
 	bufpoi += oob_skip;
 
 	/* OOB ECC */
@@ -682,30 +691,67 @@ static void denali_oob_xfer(struct mtd_info *mtd, struct nand_chip *chip,
 		else if (pos + len > writesize)
 			len = writesize - pos;
 
-		chip->cmdfunc(mtd, rnd_cmd, pos, -1);
-		if (write)
-			chip->write_buf(mtd, bufpoi, len);
-		else
-			chip->read_buf(mtd, bufpoi, len);
-		bufpoi += len;
-		if (len < ecc_bytes) {
-			len = ecc_bytes - len;
-			chip->cmdfunc(mtd, rnd_cmd, writesize + oob_skip, -1);
+		if (chip->exec_op) {
 			if (write)
-				chip->write_buf(mtd, bufpoi, len);
+				nand_change_write_column_op(
+					chip, pos, bufpoi, len, false);
 			else
+				nand_change_read_column_op(
+					chip, pos, bufpoi, len, false);
+		} else {
+			if (write) {
+				chip->cmdfunc(mtd, NAND_CMD_RNDIN, pos, -1);
+				chip->write_buf(mtd, bufpoi, len);
+			} else {
+				chip->cmdfunc(mtd, NAND_CMD_RNDOUT, pos, -1);
 				chip->read_buf(mtd, bufpoi, len);
+			}
+		}
+		bufpoi += len;
+		if (len < ecc_bytes) {
+			len = ecc_bytes - len;
+			if (chip->exec_op) {
+				if (write)
+					nand_change_write_column_op(
+						chip, writesize + oob_skip,
+						bufpoi, len, false);
+				else
+					nand_change_read_column_op(
+						chip, writesize + oob_skip,
+						bufpoi, len, false);
+			} else {
+				if (write) {
+					chip->cmdfunc(mtd, NAND_CMD_RNDIN,
+						      writesize + oob_skip, -1);
+					chip->write_buf(mtd, bufpoi, len);
+				} else {
+					chip->cmdfunc(mtd, NAND_CMD_RNDOUT,
+						      writesize + oob_skip, -1);
+					chip->read_buf(mtd, bufpoi, len);
+				}
+			}
 			bufpoi += len;
 		}
 	}
 
 	/* OOB free */
 	len = oobsize - (bufpoi - chip->oob_poi);
-	chip->cmdfunc(mtd, rnd_cmd, size - len, -1);
-	if (write)
-		chip->write_buf(mtd, bufpoi, len);
-	else
-		chip->read_buf(mtd, bufpoi, len);
+	if (chip->exec_op) {
+		if (write)
+			nand_change_write_column_op(chip, size - len, bufpoi,
+						    len, false);
+		else
+			nand_change_read_column_op(chip, size - len, bufpoi,
+						   len, false);
+	} else {
+		if (write) {
+			chip->cmdfunc(mtd, NAND_CMD_RNDIN, size - len, -1);
+			chip->write_buf(mtd, bufpoi, len);
+		} else {
+			chip->cmdfunc(mtd, NAND_CMD_RNDOUT, size - len, -1);
+			chip->read_buf(mtd, bufpoi, len);
+		}
+	}
 }
 
 static int denali_read_page_raw(struct mtd_info *mtd, struct nand_chip *chip,
@@ -803,6 +849,9 @@ static int denali_write_oob(struct mtd_info *mtd, struct nand_chip *chip,
 
 	denali_oob_xfer(mtd, chip, page, 1);
 
+	if (chip->exec_op)
+		return nand_prog_page_end_op(chip);
+
 	chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1);
 	status = chip->waitfunc(mtd, chip);
 
diff --git a/drivers/mtd/nand/nand_base.c b/drivers/mtd/nand/nand_base.c
index bef20e06f0db..737f19bd2f83 100644
--- a/drivers/mtd/nand/nand_base.c
+++ b/drivers/mtd/nand/nand_base.c
@@ -814,7 +814,7 @@ static void nand_ccs_delay(struct nand_chip *chip)
 	 * Wait tCCS_min if it is correctly defined, otherwise wait 500ns
 	 * (which should be safe for all NANDs).
 	 */
-	if (chip->data_interface.timings.sdr.tCCS_min)
+	if (&chip->data_interface.timings.sdr.tCCS_min)
 		ndelay(chip->data_interface.timings.sdr.tCCS_min / 1000);
 	else
 		ndelay(500);
@@ -1233,6 +1233,118 @@ static int nand_init_data_interface(struct nand_chip *chip)
 }
 
 /**
+ * nand_fill_column_cycles - fill the column fields on an address array
+ * @chip: The NAND chip
+ * @addrs: Array of address cycles to fill
+ * @offset_in_page: The offset in the page
+ *
+ * Fills the first or the two first bytes of the @addrs field depending
+ * on the NAND bus width and the page size.
+ */
+int nand_fill_column_cycles(struct nand_chip *chip, u8 *addrs,
+			    unsigned int offset_in_page)
+{
+	struct mtd_info *mtd = nand_to_mtd(chip);
+
+	/*
+	 * The offset in page is expressed in bytes, if the NAND bus is 16-bit
+	 * wide, then it must be divided by 2.
+	 */
+	if (chip->options & NAND_BUSWIDTH_16) {
+		if (offset_in_page % 2) {
+			WARN_ON(true);
+			return -EINVAL;
+		}
+
+		offset_in_page /= 2;
+	}
+
+	addrs[0] = offset_in_page;
+
+	/* Small pages use 1 cycle for the columns, while large page need 2 */
+	if (mtd->writesize <= 512)
+		return 1;
+
+	addrs[1] = offset_in_page >> 8;
+
+	return 2;
+}
+EXPORT_SYMBOL_GPL(nand_fill_column_cycles);
+
+static int nand_sp_exec_read_page_op(struct nand_chip *chip, unsigned int page,
+				     unsigned int offset_in_page, void *buf,
+				     unsigned int len)
+{
+	struct mtd_info *mtd = nand_to_mtd(chip);
+	const struct nand_sdr_timings *sdr =
+		nand_get_sdr_timings(&chip->data_interface);
+	u8 addrs[4];
+	struct nand_op_instr instrs[] = {
+		NAND_OP_CMD(NAND_CMD_READ0, 0),
+		NAND_OP_ADDR(3, addrs, sdr->tWB_max),
+		NAND_OP_WAIT_RDY(sdr->tR_max, sdr->tRR_min),
+		NAND_OP_DATA_IN(len, buf, 0),
+	};
+	struct nand_operation op = NAND_OPERATION(instrs);
+
+	/* Drop the DATA_OUT instruction if len is set to 0. */
+	if (!len)
+		op.ninstrs--;
+
+	if (offset_in_page >= mtd->writesize)
+		instrs[0].cmd.opcode = NAND_CMD_READOOB;
+	else if (offset_in_page >= 256)
+		instrs[0].cmd.opcode = NAND_CMD_READ1;
+
+	if (nand_fill_column_cycles(chip, addrs, offset_in_page) < 0)
+		return -EINVAL;
+
+	addrs[1] = page;
+	addrs[2] = page >> 8;
+
+	if (chip->options & NAND_ROW_ADDR_3) {
+		addrs[3] = page >> 16;
+		instrs[1].addr.naddrs++;
+	}
+
+	return nand_exec_op(chip, &op);
+}
+
+static int nand_lp_exec_read_page_op(struct nand_chip *chip, unsigned int page,
+				     unsigned int offset_in_page, void *buf,
+				     unsigned int len)
+{
+	const struct nand_sdr_timings *sdr =
+		nand_get_sdr_timings(&chip->data_interface);
+	u8 addrs[5];
+	struct nand_op_instr instrs[] = {
+		NAND_OP_CMD(NAND_CMD_READ0, 0),
+		NAND_OP_ADDR(4, addrs, 0),
+		NAND_OP_CMD(NAND_CMD_READSTART, sdr->tWB_max),
+		NAND_OP_WAIT_RDY(sdr->tR_max, sdr->tRR_min),
+		NAND_OP_DATA_IN(len, buf, 0),
+	};
+	struct nand_operation op = NAND_OPERATION(instrs);
+
+	/* Drop the DATA_IN instruction if len is set to 0. */
+	if (!len)
+		op.ninstrs--;
+
+	if (nand_fill_column_cycles(chip, addrs, offset_in_page))
+		return -EINVAL;
+
+	addrs[2] = page;
+	addrs[3] = page >> 8;
+
+	if (chip->options & NAND_ROW_ADDR_3) {
+		addrs[4] = page >> 16;
+		instrs[1].addr.naddrs++;
+	}
+
+	return nand_exec_op(chip, &op);
+}
+
+/**
  * nand_read_page_op - Do a READ PAGE operation
  * @chip: The NAND chip
  * @page: page to read
@@ -1246,17 +1358,26 @@ static int nand_init_data_interface(struct nand_chip *chip)
  * Returns 0 for success or negative error code otherwise
  */
 int nand_read_page_op(struct nand_chip *chip, unsigned int page,
-		      unsigned int column, void *buf, unsigned int len)
+		      unsigned int offset_in_page, void *buf, unsigned int len)
 {
 	struct mtd_info *mtd = nand_to_mtd(chip);
 
 	if (len && !buf)
 		return -EINVAL;
 
-	if (column + len > mtd->writesize + mtd->oobsize)
+	if (offset_in_page + len > mtd->writesize + mtd->oobsize)
 		return -EINVAL;
 
-	chip->cmdfunc(mtd, NAND_CMD_READ0, column, page);
+	if (chip->exec_op) {
+		if (mtd->writesize > 512)
+			return nand_lp_exec_read_page_op(
+				chip, page, offset_in_page, buf, len);
+
+		return nand_sp_exec_read_page_op(chip, page, offset_in_page,
+						 buf, len);
+	}
+
+	chip->cmdfunc(mtd, NAND_CMD_READ0, offset_in_page, page);
 	if (len)
 		chip->read_buf(mtd, buf, len);
 
@@ -1286,6 +1407,25 @@ static int nand_read_param_page_op(struct nand_chip *chip, u8 page, void *buf,
 	if (len && !buf)
 		return -EINVAL;
 
+	if (chip->exec_op) {
+		const struct nand_sdr_timings *sdr =
+			nand_get_sdr_timings(&chip->data_interface);
+		struct nand_op_instr instrs[] = {
+			NAND_OP_CMD(NAND_CMD_PARAM, 0),
+			NAND_OP_ADDR(1, &page, sdr->tWB_max),
+			NAND_OP_WAIT_RDY(sdr->tR_max, sdr->tRR_min),
+			NAND_OP_8BIT_DATA_IN(len, buf, 0),
+		};
+		struct nand_operation op =
+			NAND_OPERATION(instrs);
+
+		/* Drop the DATA_IN instruction if len is set to 0. */
+		if (!len)
+			op.ninstrs--;
+
+		return nand_exec_op(chip, &op);
+	}
+
 	chip->cmdfunc(mtd, NAND_CMD_PARAM, page, -1);
 	for (i = 0; i < len; i++)
 		p[i] = chip->read_byte(mtd);
@@ -1306,18 +1446,48 @@ static int nand_read_param_page_op(struct nand_chip *chip, u8 page, void *buf,
  *
  * Returns 0 for success or negative error code otherwise
  */
-int nand_change_read_column_op(struct nand_chip *chip, unsigned int column,
-			       void *buf, unsigned int len, bool force_8bit)
+int nand_change_read_column_op(struct nand_chip *chip,
+			       unsigned int offset_in_page, void *buf,
+			       unsigned int len, bool force_8bit)
 {
 	struct mtd_info *mtd = nand_to_mtd(chip);
 
 	if (len && !buf)
 		return -EINVAL;
 
-	if (column + len > mtd->writesize + mtd->oobsize)
+	if (offset_in_page + len > mtd->writesize + mtd->oobsize)
 		return -EINVAL;
 
-	chip->cmdfunc(mtd, NAND_CMD_RNDOUT, column, -1);
+	/* Small page NANDs do not support column change. */
+	if (mtd->writesize <= 512)
+		return -ENOTSUPP;
+
+	if (chip->exec_op) {
+		const struct nand_sdr_timings *sdr =
+			nand_get_sdr_timings(&chip->data_interface);
+		u8 addrs[2] = {};
+		struct nand_op_instr instrs[] = {
+			NAND_OP_CMD(NAND_CMD_RNDOUT, 0),
+			NAND_OP_ADDR(2, addrs, 0),
+			NAND_OP_CMD(NAND_CMD_RNDOUTSTART, sdr->tCCS_min),
+			NAND_OP_DATA_IN(len, buf, 0),
+		};
+		struct nand_operation op =
+			NAND_OPERATION(instrs);
+
+		if (nand_fill_column_cycles(chip, addrs, offset_in_page) < 0)
+			return -EINVAL;
+
+		/* Drop the DATA_IN instruction if len is set to 0. */
+		if (!len)
+			op.ninstrs--;
+
+		instrs[3].data.force_8bit = force_8bit;
+
+		return nand_exec_op(chip, &op);
+	}
+
+	chip->cmdfunc(mtd, NAND_CMD_RNDOUT, offset_in_page, -1);
 	if (len)
 		chip->read_buf(mtd, buf, len);
 
@@ -1339,17 +1509,28 @@ EXPORT_SYMBOL_GPL(nand_change_read_column_op);
  * Returns 0 for success or negative error code otherwise
  */
 int nand_read_oob_op(struct nand_chip *chip, unsigned int page,
-		     unsigned int column, void *buf, unsigned int len)
+		     unsigned int offset_in_page, void *buf, unsigned int len)
 {
 	struct mtd_info *mtd = nand_to_mtd(chip);
 
 	if (len && !buf)
 		return -EINVAL;
 
-	if (column + len > mtd->oobsize)
+	if (offset_in_page + len > mtd->oobsize)
 		return -EINVAL;
 
-	chip->cmdfunc(mtd, NAND_CMD_READOOB, column, page);
+	if (chip->exec_op) {
+		offset_in_page += mtd->writesize;
+
+		if (mtd->writesize > 512)
+			return nand_lp_exec_read_page_op(
+				chip, page, offset_in_page, buf, len);
+
+		return nand_sp_exec_read_page_op(chip, page, offset_in_page,
+						 buf, len);
+	}
+
+	chip->cmdfunc(mtd, NAND_CMD_READOOB, offset_in_page, page);
 	if (len)
 		chip->read_buf(mtd, buf, len);
 
@@ -1357,6 +1538,62 @@ int nand_read_oob_op(struct nand_chip *chip, unsigned int page,
 }
 EXPORT_SYMBOL_GPL(nand_read_oob_op);
 
+static int nand_exec_prog_page_op(struct nand_chip *chip, unsigned int page,
+				  unsigned int offset_in_page, const void *buf,
+				  unsigned int len, bool prog)
+{
+	struct mtd_info *mtd = nand_to_mtd(chip);
+	const struct nand_sdr_timings *sdr =
+		nand_get_sdr_timings(&chip->data_interface);
+	u8 addrs[5] = {};
+	struct nand_op_instr instrs[] = {
+		/*
+		 * Pointer command will be adjusted if we're dealing
+		 * with a small page NAND.
+		 */
+		NAND_OP_CMD(NAND_CMD_READ0, 0),
+		NAND_OP_CMD(NAND_CMD_SEQIN, 0),
+		NAND_OP_ADDR(0, addrs, sdr->tADL_min),
+		NAND_OP_DATA_OUT(len, buf, 0),
+		NAND_OP_CMD(NAND_CMD_PAGEPROG, sdr->tWB_max),
+		NAND_OP_WAIT_RDY(sdr->tPROG_max, 0),
+	};
+	struct nand_operation op = NAND_OPERATION(instrs);
+	int naddrs = nand_fill_column_cycles(chip, addrs, offset_in_page);
+
+	if (naddrs < 0)
+		return naddrs;
+
+	addrs[naddrs++] = page;
+	addrs[naddrs++] = page >> 8;
+	if (chip->options & NAND_ROW_ADDR_3)
+		addrs[naddrs++] = page >> 16;
+
+	instrs[2].addr.naddrs = naddrs;
+
+	/* Drop the lasts instructions if we're not programming the page. */
+	if (!prog) {
+		op.ninstrs -= 2;
+		/* Also drop the DATA_OUT instruction if empty */
+		if (!len)
+			op.ninstrs--;
+	}
+
+	if (mtd->writesize <= 512) {
+		/* Small pages need some more tweaking */
+		if (offset_in_page >= mtd->writesize)
+			instrs[0].cmd.opcode = NAND_CMD_READOOB;
+		else if (offset_in_page >= 256)
+			instrs[0].cmd.opcode = NAND_CMD_READ1;
+	} else {
+		/* Drop the first command if dealing with large pages */
+		op.instrs++;
+		op.ninstrs--;
+	}
+
+	return nand_exec_op(chip, &op);
+}
+
 /**
  * nand_prog_page_begin_op - starts a PROG PAGE operation
  * @chip: The NAND chip
@@ -1371,7 +1608,7 @@ EXPORT_SYMBOL_GPL(nand_read_oob_op);
  * Returns 0 for success or negative error code otherwise
  */
 int nand_prog_page_begin_op(struct nand_chip *chip, unsigned int page,
-			    unsigned int column, const void *buf,
+			    unsigned int offset_in_page, const void *buf,
 			    unsigned int len)
 {
 	struct mtd_info *mtd = nand_to_mtd(chip);
@@ -1379,10 +1616,14 @@ int nand_prog_page_begin_op(struct nand_chip *chip, unsigned int page,
 	if (len && !buf)
 		return -EINVAL;
 
-	if (column + len > mtd->writesize + mtd->oobsize)
+	if (offset_in_page + len > mtd->writesize + mtd->oobsize)
 		return -EINVAL;
 
-	chip->cmdfunc(mtd, NAND_CMD_SEQIN, column, page);
+	if (chip->exec_op)
+		return nand_exec_prog_page_op(
+			chip, page, offset_in_page, buf, len, false);
+
+	chip->cmdfunc(mtd, NAND_CMD_SEQIN, offset_in_page, page);
 
 	if (buf)
 		chip->write_buf(mtd, buf, len);
@@ -1405,6 +1646,19 @@ int nand_prog_page_end_op(struct nand_chip *chip)
 	struct mtd_info *mtd = nand_to_mtd(chip);
 	int status;
 
+	if (chip->exec_op) {
+		const struct nand_sdr_timings *sdr =
+			nand_get_sdr_timings(&chip->data_interface);
+		struct nand_op_instr instrs[] = {
+			NAND_OP_CMD(NAND_CMD_PAGEPROG, sdr->tWB_max),
+			NAND_OP_WAIT_RDY(sdr->tPROG_max, 0),
+		};
+		struct nand_operation op =
+			NAND_OPERATION(instrs);
+
+		return nand_exec_op(chip, &op);
+	}
+
 	chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1);
 
 	status = chip->waitfunc(mtd, chip);
@@ -1429,7 +1683,8 @@ EXPORT_SYMBOL_GPL(nand_prog_page_end_op);
  * Returns 0 for success or negative error code otherwise
  */
 int nand_prog_page_op(struct nand_chip *chip, unsigned int page,
-		      unsigned int column, const void *buf, unsigned int len)
+		      unsigned int offset_in_page, const void *buf,
+		      unsigned int len)
 {
 	struct mtd_info *mtd = nand_to_mtd(chip);
 	int status;
@@ -1437,10 +1692,14 @@ int nand_prog_page_op(struct nand_chip *chip, unsigned int page,
 	if (!len || !buf)
 		return -EINVAL;
 
-	if (column + len > mtd->writesize + mtd->oobsize)
+	if (offset_in_page + len > mtd->writesize + mtd->oobsize)
 		return -EINVAL;
 
-	chip->cmdfunc(mtd, NAND_CMD_SEQIN, column, page);
+	if (chip->exec_op)
+		return nand_exec_prog_page_op(
+			chip, page, offset_in_page, buf, len, true);
+
+	chip->cmdfunc(mtd, NAND_CMD_SEQIN, offset_in_page, page);
 	chip->write_buf(mtd, buf, len);
 	chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1);
 
@@ -1465,7 +1724,8 @@ EXPORT_SYMBOL_GPL(nand_prog_page_op);
  *
  * Returns 0 for success or negative error code otherwise
  */
-int nand_change_write_column_op(struct nand_chip *chip, unsigned int column,
+int nand_change_write_column_op(struct nand_chip *chip,
+				unsigned int offset_in_page,
 				const void *buf, unsigned int len,
 				bool force_8bit)
 {
@@ -1474,10 +1734,38 @@ int nand_change_write_column_op(struct nand_chip *chip, unsigned int column,
 	if (len && !buf)
 		return -EINVAL;
 
-	if (column + len > mtd->writesize + mtd->oobsize)
+	if (offset_in_page + len > mtd->writesize + mtd->oobsize)
 		return -EINVAL;
 
-	chip->cmdfunc(mtd, NAND_CMD_RNDIN, column, -1);
+	/* Small page NANDs do not support column change. */
+	if (mtd->writesize <= 512)
+		return -ENOTSUPP;
+
+	if (chip->exec_op) {
+		const struct nand_sdr_timings *sdr =
+			nand_get_sdr_timings(&chip->data_interface);
+		u8 addrs[2];
+		struct nand_op_instr instrs[] = {
+			NAND_OP_CMD(NAND_CMD_RNDIN, 0),
+			NAND_OP_ADDR(2, addrs, sdr->tCCS_min),
+			NAND_OP_DATA_OUT(len, buf, 0),
+		};
+		struct nand_operation op =
+			NAND_OPERATION(instrs);
+
+		if (nand_fill_column_cycles(chip, addrs, offset_in_page) < 0)
+			return -EINVAL;
+
+		instrs[2].data.force_8bit = force_8bit;
+
+		/* Drop the DATA_OUT instruction if len is set to 0. */
+		if (!len)
+			op.ninstrs--;
+
+		return nand_exec_op(chip, &op);
+	}
+
+	chip->cmdfunc(mtd, NAND_CMD_RNDIN, offset_in_page, -1);
 	if (len)
 		chip->write_buf(mtd, buf, len);
 
@@ -1508,6 +1796,24 @@ int nand_readid_op(struct nand_chip *chip, u8 addr,
 	if (!len || !buf)
 		return -EINVAL;
 
+	if (chip->exec_op) {
+		const struct nand_sdr_timings *sdr =
+			nand_get_sdr_timings(&chip->data_interface);
+		struct nand_op_instr instrs[] = {
+			NAND_OP_CMD(NAND_CMD_READID, 0),
+			NAND_OP_ADDR(1, &addr, sdr->tADL_min),
+			NAND_OP_8BIT_DATA_IN(len, buf, 0),
+		};
+		struct nand_operation op =
+			NAND_OPERATION(instrs);
+
+		/* Drop the DATA_IN instruction if len is set to 0. */
+		if (!len)
+			op.ninstrs--;
+
+		return nand_exec_op(chip, &op);
+	}
+
 	chip->cmdfunc(mtd, NAND_CMD_READID, addr, -1);
 
 	for (i = 0; i < len; i++)
@@ -1532,6 +1838,22 @@ int nand_status_op(struct nand_chip *chip, u8 *status)
 {
 	struct mtd_info *mtd = nand_to_mtd(chip);
 
+	if (chip->exec_op) {
+		const struct nand_sdr_timings *sdr =
+			nand_get_sdr_timings(&chip->data_interface);
+		struct nand_op_instr instrs[] = {
+			NAND_OP_CMD(NAND_CMD_STATUS, sdr->tADL_min),
+			NAND_OP_8BIT_DATA_IN(1, status, 0),
+		};
+		struct nand_operation op =
+			NAND_OPERATION(instrs);
+
+		if (!status)
+			op.ninstrs--;
+
+		return nand_exec_op(chip, &op);
+	}
+
 	chip->cmdfunc(mtd, NAND_CMD_STATUS, -1, -1);
 	if (status)
 		*status = chip->read_byte(mtd);
@@ -1558,6 +1880,25 @@ int nand_erase_op(struct nand_chip *chip, unsigned int eraseblock)
 			    (chip->phys_erase_shift - chip->page_shift);
 	int status;
 
+	if (chip->exec_op) {
+		const struct nand_sdr_timings *sdr =
+			nand_get_sdr_timings(&chip->data_interface);
+		u8 addrs[3] = {	page, page >> 8, page >> 16 };
+		struct nand_op_instr instrs[] = {
+			NAND_OP_CMD(NAND_CMD_ERASE1, 0),
+			NAND_OP_ADDR(2, addrs, 0),
+			NAND_OP_CMD(NAND_CMD_ERASE2, sdr->tWB_max),
+			NAND_OP_WAIT_RDY(sdr->tBERS_max, 0),
+		};
+		struct nand_operation op =
+			NAND_OPERATION(instrs);
+
+		if (chip->options & NAND_ROW_ADDR_3)
+			instrs[1].addr.naddrs++;
+
+		return nand_exec_op(chip, &op);
+	}
+
 	chip->cmdfunc(mtd, NAND_CMD_ERASE1, -1, page);
 	chip->cmdfunc(mtd, NAND_CMD_ERASE2, -1, -1);
 
@@ -1591,6 +1932,22 @@ static int nand_set_features_op(struct nand_chip *chip, u8 feature,
 	const u8 *params = data;
 	int i, status;
 
+	if (chip->exec_op) {
+		const struct nand_sdr_timings *sdr =
+			nand_get_sdr_timings(&chip->data_interface);
+		struct nand_op_instr instrs[] = {
+			NAND_OP_CMD(NAND_CMD_SET_FEATURES, 0),
+			NAND_OP_ADDR(1, &feature, sdr->tADL_min),
+			NAND_OP_8BIT_DATA_OUT(ONFI_SUBFEATURE_PARAM_LEN, data,
+					      sdr->tWB_max),
+			NAND_OP_WAIT_RDY(sdr->tFEAT_max, 0),
+		};
+		struct nand_operation op =
+			NAND_OPERATION(instrs);
+
+		return nand_exec_op(chip, &op);
+	}
+
 	chip->cmdfunc(mtd, NAND_CMD_SET_FEATURES, feature, -1);
 	for (i = 0; i < ONFI_SUBFEATURE_PARAM_LEN; ++i)
 		chip->write_byte(mtd, params[i]);
@@ -1621,6 +1978,22 @@ static int nand_get_features_op(struct nand_chip *chip, u8 feature,
 	u8 *params = data;
 	int i;
 
+	if (chip->exec_op) {
+		const struct nand_sdr_timings *sdr =
+			nand_get_sdr_timings(&chip->data_interface);
+		struct nand_op_instr instrs[] = {
+			NAND_OP_CMD(NAND_CMD_GET_FEATURES, 0),
+			NAND_OP_ADDR(1, &feature, sdr->tWB_max),
+			NAND_OP_WAIT_RDY(sdr->tFEAT_max, sdr->tRR_min),
+			NAND_OP_8BIT_DATA_IN(ONFI_SUBFEATURE_PARAM_LEN,
+					     data, 0),
+		};
+		struct nand_operation op =
+			NAND_OPERATION(instrs);
+
+		return nand_exec_op(chip, &op);
+	}
+
 	chip->cmdfunc(mtd, NAND_CMD_GET_FEATURES, feature, -1);
 	for (i = 0; i < ONFI_SUBFEATURE_PARAM_LEN; ++i)
 		params[i] = chip->read_byte(mtd);
@@ -1642,6 +2015,19 @@ int nand_reset_op(struct nand_chip *chip)
 {
 	struct mtd_info *mtd = nand_to_mtd(chip);
 
+	if (chip->exec_op) {
+		const struct nand_sdr_timings *sdr =
+			nand_get_sdr_timings(&chip->data_interface);
+		struct nand_op_instr instrs[] = {
+			NAND_OP_CMD(NAND_CMD_RESET, sdr->tWB_max),
+			NAND_OP_WAIT_RDY(sdr->tRST_max, 0),
+		};
+		struct nand_operation op =
+			NAND_OPERATION(instrs);
+
+		return nand_exec_op(chip, &op);
+	}
+
 	chip->cmdfunc(mtd, NAND_CMD_RESET, -1, -1);
 
 	return 0;
@@ -1669,6 +2055,18 @@ int nand_read_data_op(struct nand_chip *chip, void *buf, unsigned int len,
 	if (!len || !buf)
 		return -EINVAL;
 
+	if (chip->exec_op) {
+		struct nand_op_instr instrs[] = {
+			NAND_OP_DATA_IN(len, buf, 0),
+		};
+		struct nand_operation op =
+			NAND_OPERATION(instrs);
+
+		instrs[0].data.force_8bit = force_8bit;
+
+		return nand_exec_op(chip, &op);
+	}
+
 	if (force_8bit) {
 		u8 *p = buf;
 		unsigned int i;
@@ -1704,6 +2102,18 @@ int nand_write_data_op(struct nand_chip *chip, const void *buf,
 	if (!len || !buf)
 		return -EINVAL;
 
+	if (chip->exec_op) {
+		struct nand_op_instr instrs[] = {
+			NAND_OP_DATA_OUT(len, buf, 0),
+		};
+		struct nand_operation op =
+			NAND_OPERATION(instrs);
+
+		instrs[0].data.force_8bit = force_8bit;
+
+		return nand_exec_op(chip, &op);
+	}
+
 	if (force_8bit) {
 		const u8 *p = buf;
 		unsigned int i;
@@ -1719,6 +2129,471 @@ int nand_write_data_op(struct nand_chip *chip, const void *buf,
 EXPORT_SYMBOL_GPL(nand_write_data_op);
 
 /**
+ * struct nand_op_parser_ctx - Context used by the parser
+ * @instrs: array of all the instructions that must be addressed
+ * @ninstrs: length of the @instrs array
+ * @instr_idx: index of the instruction in the @instrs array that matches the
+ *	       first instruction of the subop structure
+ * @instr_start_off: offset at which the first instruction of the subop
+ *		     structure must start if it is and address or a data
+ *		     instruction
+ *
+ * This structure is used by the core to handle splitting lengthy instructions
+ * into sub-operations.
+ */
+struct nand_op_parser_ctx {
+	const struct nand_op_instr *instrs;
+	unsigned int ninstrs;
+	unsigned int instr_idx;
+	unsigned int instr_start_off;
+	struct nand_subop subop;
+};
+
+/**
+ * nand_op_parser_must_split_instr - Checks if an instruction must be split
+ * @pat: the parser pattern that match
+ * @instr: the instruction array to check
+ * @start_offset: the offset from which to start in the first instruction of the
+ *		  @instr array
+ *
+ * Some NAND controllers are limited and cannot send X address cycles with a
+ * unique operation, or cannot read/write more than Y bytes at the same time.
+ * In this case, reduce the scope of this set of operation, and trigger another
+ * instruction with the rest.
+ *
+ * Returns true if the array of instruction must be split, false otherwise.
+ * The @start_offset parameter is also updated to the offset at which the first
+ * instruction of the next bundle of instruction must start (if an address or a
+ * data instruction).
+ */
+static bool
+nand_op_parser_must_split_instr(const struct nand_op_parser_pattern_elem *pat,
+				const struct nand_op_instr *instr,
+				unsigned int *start_offset)
+{
+	switch (pat->type) {
+	case NAND_OP_ADDR_INSTR:
+		if (!pat->addr.maxcycles)
+			break;
+
+		if (instr->addr.naddrs - *start_offset > pat->addr.maxcycles) {
+			*start_offset += pat->addr.maxcycles;
+			return true;
+		}
+		break;
+
+	case NAND_OP_DATA_IN_INSTR:
+	case NAND_OP_DATA_OUT_INSTR:
+		if (!pat->data.maxlen)
+			break;
+
+		if (instr->data.len - *start_offset > pat->data.maxlen) {
+			*start_offset += pat->data.maxlen;
+			return true;
+		}
+		break;
+
+	default:
+		break;
+	}
+
+	return false;
+}
+
+/**
+ * nand_op_parser_match_pat - Checks a pattern
+ * @pat: the parser pattern to check if it matches
+ * @ctx: the context structure to match with the pattern @pat
+ *
+ * Check if *one* given pattern matches the given sequence of instructions
+ */
+static bool
+nand_op_parser_match_pat(const struct nand_op_parser_pattern *pat,
+			 struct nand_op_parser_ctx *ctx)
+{
+	unsigned int i, j, boundary_off = ctx->instr_start_off;
+
+	ctx->subop.ninstrs = 0;
+
+	for (i = ctx->instr_idx, j = 0; i < ctx->ninstrs && j < pat->nelems;) {
+		const struct nand_op_instr *instr = &ctx->instrs[i];
+
+		/*
+		 * The pattern instruction does not match the operation
+		 * instruction. If the instruction is marked optional in the
+		 * pattern definition, we skip the pattern element and continue
+		 * to the next one. If the element is mandatory, there's no
+		 * match and we can return false directly.
+		 */
+		if (instr->type != pat->elems[j].type) {
+			if (!pat->elems[j].optional)
+				return false;
+
+			j++;
+			continue;
+		}
+
+		/*
+		 * Now check the pattern element constraints. If the pattern is
+		 * not able to handle the whole instruction in a single step,
+		 * we'll have to break it down into several instructions.
+		 * The *boudary_off value comes back updated to point to the
+		 * limit between the split instruction (the end of the original
+		 * chunk, the start of new next one).
+		 */
+		if (nand_op_parser_must_split_instr(&pat->elems[j], instr,
+						    &boundary_off)) {
+			ctx->subop.ninstrs++;
+			j++;
+			break;
+		}
+
+		ctx->subop.ninstrs++;
+		i++;
+		j++;
+		boundary_off = 0;
+	}
+
+	/*
+	 * This can happen if all instructions of a pattern are optional.
+	 * Still, if there's not at least one instruction handled by this
+	 * pattern, this is not a match, and we should try the next one (if
+	 * any).
+	 */
+	if (!ctx->subop.ninstrs)
+		return false;
+
+	/*
+	 * We had a match on the pattern head, but the pattern may be longer
+	 * than the instructions we're asked to execute. We need to make sure
+	 * there's no mandatory elements in the pattern tail.
+	 *
+	 * The case where all the operations of a pattern have been checked but
+	 * the number of instructions is bigger is handled right after this by
+	 * returning true on the pattern match, which will order the execution
+	 * of the subset of instructions later defined, while updating the
+	 * context ids to the next chunk of instructions.
+	 */
+	for (; j < pat->nelems; j++) {
+		if (!pat->elems[j].optional)
+			return false;
+	}
+
+	/*
+	 * We have a match: update the ctx and return true. The subop structure
+	 * will be used by the pattern's ->exec() function.
+	 */
+	ctx->subop.instrs = &ctx->instrs[ctx->instr_idx];
+	ctx->subop.first_instr_start_off = ctx->instr_start_off;
+	ctx->subop.last_instr_end_off = boundary_off;
+
+	/*
+	 * Update the pointers so the calling function will be able to recall
+	 * this one with a new subset of instructions.
+	 *
+	 * In the case where the last operation of this set is split, point to
+	 * the last unfinished job, knowing the starting offset.
+	 */
+	ctx->instr_idx = i;
+	ctx->instr_start_off = boundary_off;
+
+	return true;
+}
+
+#if IS_ENABLED(CONFIG_DYNAMIC_DEBUG) || defined(DEBUG)
+static void nand_op_parser_trace(const struct nand_op_parser_ctx *ctx,
+				 bool success)
+{
+	const struct nand_op_instr *instr;
+	bool in_subop = false;
+	char *is_in =  "    ->", *is_out = "      ", *prefix;
+	char *buf;
+	unsigned int len, off = 0;
+	int i, j;
+
+	if (success)
+		pr_debug("executing subop:\n");
+	else
+		pr_debug("pattern not found:\n");
+
+	for (i = 0; i < ctx->ninstrs; i++) {
+		instr = &ctx->instrs[i];
+
+		/*
+		 * ctx->instr_idx is not reliable because it may already had
+		 * been updated by the parser. Use pointers comparison instead.
+		 */
+		if (instr == &ctx->subop.instrs[0])
+			in_subop = true;
+
+		if (in_subop)
+			prefix = is_in;
+		else
+			prefix = is_out;
+
+		switch (instr->type) {
+		case NAND_OP_CMD_INSTR:
+			pr_debug("%sCMD      [0x%02x]\n", prefix,
+				 instr->cmd.opcode);
+			break;
+		case NAND_OP_ADDR_INSTR:
+			/*
+			 * A log line is much less than 50 bytes, plus 5 bytes
+			 * per address cycle to display.
+			 */
+			len = 50 + 5 * instr->addr.naddrs;
+			buf = kmalloc(len, GFP_KERNEL);
+			if (!buf)
+				return;
+			memset(buf, 0, len);
+			off += snprintf(buf, len, "ADDR     [%d cyc:",
+					instr->addr.naddrs);
+			for (j = 0; j < instr->addr.naddrs; j++)
+				off += snprintf(&buf[off], len - off, " 0x%02x",
+						instr->addr.addrs[j]);
+			pr_debug("%s%s]\n", prefix, buf);
+			break;
+		case NAND_OP_DATA_IN_INSTR:
+			pr_debug("%sDATA_IN  [%d B%s]\n", prefix,
+				 instr->data.len,
+				 instr->data.force_8bit ? ", force 8-bit" : "");
+			break;
+		case NAND_OP_DATA_OUT_INSTR:
+			pr_debug("%sDATA_OUT [%d B%s]\n", prefix,
+				 instr->data.len,
+				 instr->data.force_8bit ? ", force 8-bit" : "");
+			break;
+		case NAND_OP_WAITRDY_INSTR:
+			pr_debug("%sWAITRDY  [max %d ms]\n", prefix,
+				 instr->waitrdy.timeout_ms);
+			break;
+		}
+
+		if (instr == &ctx->subop.instrs[ctx->subop.ninstrs - 1])
+			in_subop = false;
+	}
+}
+#else
+static void nand_op_parser_trace(const struct nand_op_parser_ctx *ctx,
+				 bool success)
+{
+	/* NOP */
+}
+#endif
+
+/**
+ * nand_op_parser_exec_op - exec_op parser
+ * @chip: the NAND chip
+ * @parser: the parser to use given by the controller driver
+ * @op: the NAND operation to address
+ * @check_only: flag asking if the entire operation could be handled
+ *
+ * Function that must be called by each driver that implement the "exec_op API"
+ * in their own ->exec_op() implementation.
+ *
+ * The function iterates on all the instructions asked and make use of internal
+ * parsers to find matches between the instruction list and the handled patterns
+ * filled by the controller drivers inside the @parser structure. If needed, the
+ * instructions could be split into sub-operations and be executed sequentially.
+ */
+int nand_op_parser_exec_op(struct nand_chip *chip,
+			   const struct nand_op_parser *parser,
+			   const struct nand_operation *op, bool check_only)
+{
+	struct nand_op_parser_ctx ctx = {
+		.instrs = op->instrs,
+		.ninstrs = op->ninstrs,
+	};
+	unsigned int i;
+
+	while (ctx.instr_idx < op->ninstrs) {
+		bool pattern_found = false;
+		int ret;
+
+		for (i = 0; i < parser->npatterns; i++) {
+			const struct nand_op_parser_pattern *pattern;
+
+			pattern = &parser->patterns[i];
+			if (!nand_op_parser_match_pat(pattern, &ctx))
+				continue;
+
+			nand_op_parser_trace(&ctx, true);
+
+			if (check_only)
+				break;
+
+			ret = pattern->exec(chip, &ctx.subop);
+			if (ret)
+				return ret;
+
+			pattern_found = true;
+		}
+
+		if (!pattern_found) {
+			nand_op_parser_trace(&ctx, false);
+			return -ENOTSUPP;
+		}
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(nand_op_parser_exec_op);
+
+static bool nand_instr_is_data(const struct nand_op_instr *instr)
+{
+	return instr && (instr->type == NAND_OP_DATA_IN_INSTR ||
+			 instr->type == NAND_OP_DATA_OUT_INSTR);
+}
+
+static bool nand_subop_instr_is_valid(const struct nand_subop *subop,
+				      unsigned int op_id)
+{
+	return subop && op_id < subop->ninstrs;
+}
+
+static int nand_subop_get_start_off(const struct nand_subop *subop,
+				    unsigned int op_id)
+{
+	if (op_id)
+		return 0;
+
+	return subop->first_instr_start_off;
+}
+
+/**
+ * nand_subop_get_addr_start_off - Get the start offset in an address array
+ * @subop: The entire sub-operation
+ * @op_id: Index of the instruction inside the sub-operation
+ *
+ * Instructions arrays may be split by the parser between instructions,
+ * and also in the middle of an address instruction if the number of cycles
+ * to assert in one operation is not supported by the controller.
+ *
+ * For this, instead of using the first index of the ->addr.addrs field from the
+ * address instruction, the NAND controller driver must use this helper that
+ * will either return 0 if the index does not point to the first instruction of
+ * the sub-operation, or the offset of the next starting offset inside the
+ * address cycles.
+ *
+ * Returns the offset of the first address cycle to assert from the pointed
+ * address instruction.
+ */
+int nand_subop_get_addr_start_off(const struct nand_subop *subop,
+				  unsigned int op_id)
+{
+	if (!nand_subop_instr_is_valid(subop, op_id) ||
+	    subop->instrs[op_id].type != NAND_OP_ADDR_INSTR)
+		return -EINVAL;
+
+	return nand_subop_get_start_off(subop, op_id);
+}
+EXPORT_SYMBOL_GPL(nand_subop_get_addr_start_off);
+
+/**
+ * nand_subop_get_num_addr_cyc - Get the remaining address cycles to assert
+ * @subop: The entire sub-operation
+ * @op_id: Index of the instruction inside the sub-operation
+ *
+ * Instructions arrays may be split by the parser between instructions,
+ * and also in the middle of an address instruction if the number of cycles
+ * to assert in one operation is not supported by the controller.
+ *
+ * For this, instead of using the ->addr.naddrs fields of the address
+ * instruction, the NAND controller driver must use this helper that will
+ * return the actual number of cycles to assert between the first and last
+ * offset asked for this particular instruction.
+ *
+ * Returns the number of address cycles to assert from the pointed address
+ * instruction.
+ */
+int nand_subop_get_num_addr_cyc(const struct nand_subop *subop,
+				unsigned int op_id)
+{
+	int start_off, end_off;
+
+	if (!nand_subop_instr_is_valid(subop, op_id) ||
+	    subop->instrs[op_id].type != NAND_OP_ADDR_INSTR)
+		return -EINVAL;
+
+	start_off = nand_subop_get_addr_start_off(subop, op_id);
+
+	if (op_id == subop->ninstrs - 1 &&
+	    subop->last_instr_end_off)
+		end_off = subop->last_instr_end_off;
+	else
+		end_off = subop->instrs[op_id].addr.naddrs;
+
+	return end_off - start_off;
+}
+EXPORT_SYMBOL_GPL(nand_subop_get_num_addr_cyc);
+
+/**
+ * nand_subop_get_data_start_off - Get the start offset in a data array
+ * @subop: The entire sub-operation
+ * @op_id: Index of the instruction inside the sub-operation
+ *
+ * Instructions arrays may be split by the parser between instructions,
+ * and also in the middle of a data instruction if the number of bytes to access
+ * in one operation is greater that the controller limit.
+ *
+ * For this, instead of using the first index of the ->data.buf field from the
+ * data instruction, the NAND controller driver must use this helper that
+ * will either return 0 if the index does not point to the first instruction of
+ * the sub-operation, or the offset of the next starting offset inside the
+ * data buffer.
+ *
+ * Returns the data offset inside the pointed data instruction buffer from which
+ * to start.
+ */
+int nand_subop_get_data_start_off(const struct nand_subop *subop,
+				  unsigned int op_id)
+{
+	if (!nand_subop_instr_is_valid(subop, op_id) ||
+	    !nand_instr_is_data(&subop->instrs[op_id]))
+		return -EINVAL;
+
+	return nand_subop_get_start_off(subop, op_id);
+}
+EXPORT_SYMBOL_GPL(nand_subop_get_data_start_off);
+
+/**
+ * nand_subop_get_data_len - Get the number of bytes to retrieve
+ * @subop: The entire sub-operation
+ * @op_id: Index of the instruction inside the sub-operation
+ *
+ * Instructions arrays may be split by the parser between instructions,
+ * and also in the middle of a data instruction if the number of bytes to access
+ * in one operation is greater that the controller limit.
+ *
+ * For this, instead of using the ->data.len field from the data instruction,
+ * the NAND controller driver must use this helper that will return the actual
+ * length of data to move between the first and last offset asked for this
+ * particular instruction.
+ *
+ * Returns the length of the data to move from the pointed data instruction.
+ */
+int nand_subop_get_data_len(const struct nand_subop *subop,
+			    unsigned int op_id)
+{
+	int start_off = 0, end_off;
+
+	if (!nand_subop_instr_is_valid(subop, op_id) ||
+	    !nand_instr_is_data(&subop->instrs[op_id]))
+		return -EINVAL;
+
+	start_off = nand_subop_get_data_start_off(subop, op_id);
+
+	if (op_id == subop->ninstrs - 1 &&
+	    subop->last_instr_end_off)
+		end_off = subop->last_instr_end_off;
+	else
+		end_off = subop->instrs[op_id].data.len;
+
+	return end_off - start_off;
+}
+EXPORT_SYMBOL_GPL(nand_subop_get_data_len);
+
+/**
  * nand_reset - Reset and initialize a NAND device
  * @chip: The NAND chip
  * @chipnr: Internal die id
@@ -3940,7 +4815,7 @@ static void nand_set_defaults(struct nand_chip *chip)
 		chip->chip_delay = 20;
 
 	/* check, if a user supplied command function given */
-	if (chip->cmdfunc == NULL)
+	if (chip->cmdfunc == NULL && !chip->exec_op)
 		chip->cmdfunc = nand_command;
 
 	/* check, if a user supplied wait function given */
@@ -4823,15 +5698,35 @@ int nand_scan_ident(struct mtd_info *mtd, int maxchips,
 	if (!mtd->name && mtd->dev.parent)
 		mtd->name = dev_name(mtd->dev.parent);
 
-	if ((!chip->cmdfunc || !chip->select_chip) && !chip->cmd_ctrl) {
+	/*
+	 * ->cmdfunc() is legacy and will only be used if ->exec_op() is not
+	 * populated.
+	 */
+	if (chip->exec_op) {
 		/*
-		 * Default functions assigned for chip_select() and
-		 * cmdfunc() both expect cmd_ctrl() to be populated,
-		 * so we need to check that that's the case
+		 * The implementation of ->exec_op() heavily relies on timings
+		 * to be accessible through the nand_data_interface structure.
+		 * Thus, the ->setup_data_interface() hook must be provided. The
+		 * controller driver will be noticed of delays it must apply
+		 * after each ->exec_op() instruction (if any) through the
+		 * .delay_ns field. The driver will then choose to handle the
+		 * delays manually if the controller cannot do it natively.
 		 */
-		pr_err("chip.cmd_ctrl() callback is not provided");
-		return -EINVAL;
+		if (!chip->setup_data_interface) {
+			pr_err("->setup_data_interface() should be provided\n");
+			return -EINVAL;
+		}
+	} else {
+		/*
+		 * Default functions assigned for ->cmdfunc() and
+		 * ->select_chip() both expect ->cmd_ctrl() to be populated.
+		 */
+		if ((!chip->cmdfunc || !chip->select_chip) && !chip->cmd_ctrl) {
+			pr_err("->cmd_ctrl() should be provided\n");
+			return -EINVAL;
+		}
 	}
+
 	/* Set the default functions */
 	nand_set_defaults(chip);
 
diff --git a/drivers/mtd/nand/nand_hynix.c b/drivers/mtd/nand/nand_hynix.c
index 04e3ab7a476c..81c382f24513 100644
--- a/drivers/mtd/nand/nand_hynix.c
+++ b/drivers/mtd/nand/nand_hynix.c
@@ -74,19 +74,36 @@ static bool hynix_nand_has_valid_jedecid(struct nand_chip *chip)
 	return !strncmp("JEDEC", jedecid, sizeof(jedecid));
 }
 
+static int hynix_nand_cmd_op(struct nand_chip *chip, u8 cmd)
+{
+	struct mtd_info *mtd = nand_to_mtd(chip);
+
+	if (chip->exec_op) {
+		struct nand_op_instr instrs[] = {
+			NAND_OP_CMD(cmd, 0),
+		};
+		struct nand_operation op = NAND_OPERATION(instrs);
+
+		return nand_exec_op(chip, &op);
+	}
+
+	chip->cmdfunc(mtd, cmd, -1, -1);
+
+	return 0;
+}
+
 static int hynix_nand_setup_read_retry(struct mtd_info *mtd, int retry_mode)
 {
 	struct nand_chip *chip = mtd_to_nand(mtd);
 	struct hynix_nand *hynix = nand_get_manufacturer_data(chip);
 	const u8 *values;
-	int status;
 	int i;
 
 	values = hynix->read_retry->values +
 		 (retry_mode * hynix->read_retry->nregs);
 
 	/* Enter 'Set Hynix Parameters' mode */
-	chip->cmdfunc(mtd, NAND_HYNIX_CMD_SET_PARAMS, -1, -1);
+	hynix_nand_cmd_op(chip, NAND_HYNIX_CMD_SET_PARAMS);
 
 	/*
 	 * Configure the NAND in the requested read-retry mode.
@@ -101,16 +118,17 @@ static int hynix_nand_setup_read_retry(struct mtd_info *mtd, int retry_mode)
 		int column = hynix->read_retry->regs[i];
 
 		column |= column << 8;
-		chip->cmdfunc(mtd, NAND_CMD_NONE, column, -1);
-		chip->write_byte(mtd, values[i]);
+		if (chip->exec_op) {
+			nand_change_write_column_op(chip, column,
+						    &values[i], 1, true);
+		} else {
+			chip->cmdfunc(mtd, NAND_CMD_NONE, column, -1);
+			chip->write_byte(mtd, values[i]);
+		}
 	}
 
 	/* Apply the new settings. */
-	chip->cmdfunc(mtd, NAND_HYNIX_CMD_APPLY_PARAMS, -1, -1);
-
-	status = chip->waitfunc(mtd, chip);
-	if (status & NAND_STATUS_FAIL)
-		return -EIO;
+	hynix_nand_cmd_op(chip, NAND_HYNIX_CMD_APPLY_PARAMS);
 
 	return 0;
 }
@@ -173,32 +191,65 @@ static int hynix_read_rr_otp(struct nand_chip *chip,
 
 	nand_reset_op(chip);
 
-	chip->cmdfunc(mtd, NAND_HYNIX_CMD_SET_PARAMS, -1, -1);
+	hynix_nand_cmd_op(chip, NAND_HYNIX_CMD_SET_PARAMS);
 
 	for (i = 0; i < info->nregs; i++) {
 		int column = info->regs[i];
 
 		column |= column << 8;
-		chip->cmdfunc(mtd, NAND_CMD_NONE, column, -1);
-		chip->write_byte(mtd, info->values[i]);
+		if (chip->exec_op) {
+			nand_change_write_column_op(chip, column,
+						    &info->values[i], 1, true);
+		} else {
+			chip->cmdfunc(mtd, NAND_CMD_NONE, column, -1);
+			chip->write_byte(mtd, info->values[i]);
+		}
 	}
 
-	chip->cmdfunc(mtd, NAND_HYNIX_CMD_APPLY_PARAMS, -1, -1);
+	hynix_nand_cmd_op(chip, NAND_HYNIX_CMD_APPLY_PARAMS);
 
 	/* Sequence to enter OTP mode? */
-	chip->cmdfunc(mtd, 0x17, -1, -1);
-	chip->cmdfunc(mtd, 0x04, -1, -1);
-	chip->cmdfunc(mtd, 0x19, -1, -1);
+	if (chip->exec_op) {
+		struct nand_op_instr instrs[] = {
+			NAND_OP_CMD(0x17, 0),
+			NAND_OP_CMD(0x04, 0),
+			NAND_OP_CMD(0x19, 0),
+		};
+		struct nand_operation op = NAND_OPERATION(instrs);
+
+		nand_exec_op(chip, &op);
+	} else {
+		chip->cmdfunc(mtd, 0x17, -1, -1);
+		chip->cmdfunc(mtd, 0x04, -1, -1);
+		chip->cmdfunc(mtd, 0x19, -1, -1);
+	}
 
 	/* Now read the page */
 	nand_read_page_op(chip, info->page, 0, buf, info->size);
 
 	/* Put everything back to normal */
 	nand_reset_op(chip);
-	chip->cmdfunc(mtd, NAND_HYNIX_CMD_SET_PARAMS, 0x38, -1);
-	chip->write_byte(mtd, 0x0);
-	chip->cmdfunc(mtd, NAND_HYNIX_CMD_APPLY_PARAMS, -1, -1);
-	chip->cmdfunc(mtd, NAND_CMD_READ0, 0x0, -1);
+	if (chip->exec_op) {
+		const struct nand_sdr_timings *sdr =
+			nand_get_sdr_timings(&chip->data_interface);
+		u8 byte = 0;
+		u8 addr = 0x38;
+		struct nand_op_instr instrs[] = {
+			NAND_OP_CMD(NAND_HYNIX_CMD_SET_PARAMS, 0),
+			NAND_OP_ADDR(1, &addr, sdr->tCCS_min),
+			NAND_OP_8BIT_DATA_OUT(1, &byte, 0),
+			NAND_OP_CMD(NAND_HYNIX_CMD_APPLY_PARAMS, 0),
+			NAND_OP_CMD(NAND_CMD_READ0, 0),
+		};
+		struct nand_operation op = NAND_OPERATION(instrs);
+
+		nand_exec_op(chip, &op);
+	} else {
+		chip->cmdfunc(mtd, NAND_HYNIX_CMD_SET_PARAMS, 0x38, -1);
+		chip->write_byte(mtd, 0x0);
+		chip->cmdfunc(mtd, NAND_HYNIX_CMD_APPLY_PARAMS, -1, -1);
+		chip->cmdfunc(mtd, NAND_CMD_READ0, 0x0, -1);
+	}
 
 	return 0;
 }
diff --git a/drivers/mtd/nand/nand_micron.c b/drivers/mtd/nand/nand_micron.c
index 543352380ffa..109d8003e33d 100644
--- a/drivers/mtd/nand/nand_micron.c
+++ b/drivers/mtd/nand/nand_micron.c
@@ -117,14 +117,38 @@ micron_nand_read_page_on_die_ecc(struct mtd_info *mtd, struct nand_chip *chip,
 				 uint8_t *buf, int oob_required,
 				 int page)
 {
-	int status;
+	u8 status;
 	int max_bitflips = 0;
 
 	micron_nand_on_die_ecc_setup(chip, true);
 
-	chip->cmdfunc(mtd, NAND_CMD_READ0, 0x00, page);
-	chip->cmdfunc(mtd, NAND_CMD_STATUS, -1, -1);
-	status = chip->read_byte(mtd);
+	if (chip->exec_op) {
+		u8 addrs[5] = {};
+		struct nand_op_instr instrs[] = {
+			NAND_OP_CMD(NAND_CMD_READ0, 0),
+			NAND_OP_ADDR(4, addrs, 0),
+			NAND_OP_CMD(NAND_CMD_STATUS, 0),
+			NAND_OP_8BIT_DATA_IN(1, &status, 0),
+		};
+		struct nand_operation op = NAND_OPERATION(instrs);
+
+		if (nand_fill_column_cycles(chip, addrs, 0))
+			return -EINVAL;
+
+		addrs[2] = page;
+		addrs[3] = page >> 8;
+		if (chip->options & NAND_ROW_ADDR_3) {
+			addrs[4] = page >> 16;
+			instrs[1].addr.naddrs++;
+		}
+
+		nand_exec_op(chip, &op);
+	} else {
+		chip->cmdfunc(mtd, NAND_CMD_READ0, 0x00, page);
+		chip->cmdfunc(mtd, NAND_CMD_STATUS, -1, -1);
+		status = chip->read_byte(mtd);
+	}
+
 	if (status & NAND_STATUS_FAIL)
 		mtd->ecc_stats.failed++;
 	/*
diff --git a/include/linux/mtd/rawnand.h b/include/linux/mtd/rawnand.h
index 1acc26ed0e91..302f231df65e 100644
--- a/include/linux/mtd/rawnand.h
+++ b/include/linux/mtd/rawnand.h
@@ -751,6 +751,337 @@ struct nand_manufacturer_ops {
 };
 
 /**
+ * struct nand_op_cmd_instr - Definition of a command instruction
+ * @opcode: the command to assert in one cycle
+ */
+struct nand_op_cmd_instr {
+	u8 opcode;
+};
+
+/**
+ * struct nand_op_addr_instr - Definition of an address instruction
+ * @naddrs: length of the @addrs array
+ * @addrs: array containing the address cycles to assert
+ */
+struct nand_op_addr_instr {
+	unsigned int naddrs;
+	const u8 *addrs;
+};
+
+/**
+ * struct nand_op_data_instr - Definition of a data instruction
+ * @len: number of data bytes to move
+ * @in: buffer to fill when reading from the NAND chip
+ * @out: buffer to read from when writing to the NAND chip
+ * @force_8bit: force 8-bit access
+ *
+ * Please note that "in" and "out" are inverted from the ONFI specification
+ * and are from the controller perspective, so a "in" is a read from the NAND
+ * chip while a "out" is a write to the NAND chip.
+ */
+struct nand_op_data_instr {
+	unsigned int len;
+	union {
+		void *in;
+		const void *out;
+	};
+	bool force_8bit;
+};
+
+/**
+ * struct nand_op_waitrdy_instr - Definition of a wait ready instruction
+ * @timeout_ms: maximum delay while waiting for the ready/busy pin in ms
+ */
+struct nand_op_waitrdy_instr {
+	unsigned int timeout_ms;
+};
+
+/**
+ * enum nand_op_instr_type - Enumeration of all the instructions
+ *
+ * Please note that data instructions are separated into DATA_IN and DATA_OUT
+ * instructions.
+ */
+enum nand_op_instr_type {
+	NAND_OP_CMD_INSTR,
+	NAND_OP_ADDR_INSTR,
+	NAND_OP_DATA_IN_INSTR,
+	NAND_OP_DATA_OUT_INSTR,
+	NAND_OP_WAITRDY_INSTR,
+};
+
+/**
+ * struct nand_op_instr - Generic definition of and instruction
+ * @type: an enumeration of the instruction type
+ * @cmd/@addr/@data/@waitrdy: the actual instruction to refer depending on the
+ *			      value of @type
+ * @delay_ns: delay to apply by the controller after the instruction has been
+ *	      actually executed (most of them are directly handled by the
+ *	      controllers once the timings negociation has been done)
+ */
+struct nand_op_instr {
+	enum nand_op_instr_type type;
+	union {
+		struct nand_op_cmd_instr cmd;
+		struct nand_op_addr_instr addr;
+		struct nand_op_data_instr data;
+		struct nand_op_waitrdy_instr waitrdy;
+	};
+	unsigned int delay_ns;
+};
+
+/*
+ * Special handling must be done for the WAITRDY timeout parameter as it usually
+ * is either tPROG (after a prog), tR (before a read), tRST (during a reset) or
+ * tBERS (during an erase) which all of them are u64 values that cannot be
+ * divided by usual kernel macros and must be handled with the special
+ * DIV_ROUND_UP_ULL() macro.
+ */
+#define PSEC_TO_NSEC(x) DIV_ROUND_UP(x, 10^3)
+#define PSEC_TO_MSEC(x) DIV_ROUND_UP_ULL(x, 10^9)
+
+#define NAND_OP_CMD(id, delay_ps)					\
+	{								\
+		.type = NAND_OP_CMD_INSTR,				\
+		.cmd.opcode = id,					\
+		.delay_ns = PSEC_TO_NSEC(delay_ps),			\
+	}
+
+#define NAND_OP_ADDR(ncycles, cycles, delay_ps)				\
+	{								\
+		.type = NAND_OP_ADDR_INSTR,				\
+		.addr = {						\
+			.naddrs = ncycles,				\
+			.addrs = cycles,				\
+		},							\
+		.delay_ns = PSEC_TO_NSEC(delay_ps),			\
+	}
+
+#define NAND_OP_DATA_IN(l, buf, delay_ps)				\
+	{								\
+		.type = NAND_OP_DATA_IN_INSTR,				\
+		.data = {						\
+			.len = l,					\
+			.in = buf,					\
+			.force_8bit = false,				\
+		},							\
+		.delay_ns = PSEC_TO_NSEC(delay_ps),			\
+	}
+
+#define NAND_OP_DATA_OUT(l, buf, delay_ps)				\
+	{								\
+		.type = NAND_OP_DATA_OUT_INSTR,				\
+		.data = {						\
+			.len = l,					\
+			.out = buf,					\
+			.force_8bit = false,				\
+		},							\
+		.delay_ns = PSEC_TO_NSEC(delay_ps),			\
+	}
+
+#define NAND_OP_8BIT_DATA_IN(l, buf, delay_ps)				\
+	{								\
+		.type = NAND_OP_DATA_IN_INSTR,				\
+		.data = {						\
+			.len = l,					\
+			.in = buf,					\
+			.force_8bit = true,				\
+		},							\
+		.delay_ns = PSEC_TO_NSEC(delay_ps),			\
+	}
+
+#define NAND_OP_8BIT_DATA_OUT(l, buf, delay_ps)				\
+	{								\
+		.type = NAND_OP_DATA_OUT_INSTR,				\
+		.data = {						\
+			.len = l,					\
+			.out = buf,					\
+			.force_8bit = true,				\
+		},							\
+		.delay_ns = PSEC_TO_NSEC(delay_ps),			\
+	}
+
+#define NAND_OP_WAIT_RDY(tout_ps, delay_ps)				\
+	{								\
+		.type = NAND_OP_WAITRDY_INSTR,				\
+		.waitrdy.timeout_ms = PSEC_TO_MSEC(tout_ps),		\
+		.delay_ns = PSEC_TO_NSEC(delay_ps),			\
+	}
+
+/**
+ * struct nand_subop - a sub operation
+ * @instrs: array of instructions
+ * @ninstrs: length of the @instrs array
+ * @first_instr_start_off: offset to start from for the first instruction
+ *			   of the sub-operation
+ * @last_instr_end_off: offset to end at (excluded) for the last instruction
+ *			of the sub-operation
+ *
+ * When an operation cannot be handled as is by the NAND controller, it will
+ * be split by the parser and the remaining pieces will be handled as
+ * sub-operations.
+ */
+struct nand_subop {
+	const struct nand_op_instr *instrs;
+	unsigned int ninstrs;
+	/*
+	 * Specific offset for the first and the last instructions of the subop.
+	 * Applies for the address cycles in the case of address, or for data
+	 * offset in the case of data transfers. Otherwise, it is irrelevant.
+	 */
+	unsigned int first_instr_start_off;
+	unsigned int last_instr_end_off;
+};
+
+int nand_subop_get_addr_start_off(const struct nand_subop *subop,
+				  unsigned int op_id);
+int nand_subop_get_num_addr_cyc(const struct nand_subop *subop,
+				unsigned int op_id);
+int nand_subop_get_data_start_off(const struct nand_subop *subop,
+				  unsigned int op_id);
+int nand_subop_get_data_len(const struct nand_subop *subop,
+			    unsigned int op_id);
+
+/**
+ * struct nand_op_parser_addr_constraints - Constraints for address instructions
+ * @maxcycles: maximum number of cycles that the controller can assert by
+ *	       instruction
+ */
+struct nand_op_parser_addr_constraints {
+	unsigned int maxcycles;
+};
+
+/**
+ * struct nand_op_parser_data_constraints - Constraints for data instructions
+ * @maxlen: maximum data length that the controller can handle with one
+ *	    instruction
+ */
+struct nand_op_parser_data_constraints {
+	unsigned int maxlen;
+};
+
+/**
+ * struct nand_op_parser_pattern_elem - One element of a pattern
+ * @type: the instructuction type
+ * @optional: if this element of the pattern is optional or mandatory
+ * @addr/@data: address or data constraint (number of cycles or data length)
+ */
+struct nand_op_parser_pattern_elem {
+	enum nand_op_instr_type type;
+	bool optional;
+	union {
+		struct nand_op_parser_addr_constraints addr;
+		struct nand_op_parser_data_constraints data;
+	};
+};
+
+#define NAND_OP_PARSER_PAT_CMD_ELEM(_opt)			\
+	{							\
+		.type = NAND_OP_CMD_INSTR,			\
+		.optional = _opt,				\
+	}
+
+#define NAND_OP_PARSER_PAT_ADDR_ELEM(_opt, _maxcycles)		\
+	{							\
+		.type = NAND_OP_ADDR_INSTR,			\
+		.optional = _opt,				\
+		.addr.maxcycles = _maxcycles,			\
+	}
+
+#define NAND_OP_PARSER_PAT_DATA_IN_ELEM(_opt, _maxlen)		\
+	{							\
+		.type = NAND_OP_DATA_IN_INSTR,			\
+		.optional = _opt,				\
+		.data.maxlen = _maxlen,				\
+	}
+
+#define NAND_OP_PARSER_PAT_DATA_OUT_ELEM(_opt, _maxlen)		\
+	{							\
+		.type = NAND_OP_DATA_OUT_INSTR,			\
+		.optional = _opt,				\
+		.data.maxlen = _maxlen,				\
+	}
+
+#define NAND_OP_PARSER_PAT_WAITRDY_ELEM(_opt)			\
+	{							\
+		.type = NAND_OP_WAITRDY_INSTR,			\
+		.optional = _opt,				\
+	}
+
+/**
+ * struct nand_op_parser_pattern - A complete pattern
+ * @elems: array of pattern elements
+ * @nelems: number of pattern elements in @elems array
+ * @exec: the function that will actually execute this pattern, written in the
+ *	  controller driver
+ *
+ * This is a complete pattern that is a list of elements, each one reprensenting
+ * one instruction with its constraints. Controller drivers must declare as much
+ * patterns as they support and give the list of the supported patterns (created
+ * with the help of the following macro) at the call to nand_op_parser_exec_op
+ * which shall be the main thing to do in the driver implementation of
+ * ->exec_op(). Once there is a match between the pattern and an operation, the
+ * core calls the @exec function to actually do the operation.
+ */
+struct nand_op_parser_pattern {
+	const struct nand_op_parser_pattern_elem *elems;
+	unsigned int nelems;
+	int (*exec)(struct nand_chip *chip, const struct nand_subop *subop);
+};
+
+#define NAND_OP_PARSER_PATTERN(_exec, ...)							\
+	{											\
+		.exec = _exec,									\
+		.elems = (struct nand_op_parser_pattern_elem[]) { __VA_ARGS__ },		\
+		.nelems = sizeof((struct nand_op_parser_pattern_elem[]) { __VA_ARGS__ }) /	\
+			  sizeof(struct nand_op_parser_pattern_elem),				\
+	}
+
+/**
+ * struct nand_op_parser - The actual parser
+ * @patterns: array of patterns
+ * @npatterns: length of the @patterns array
+ *
+ * The actual parser structure wich is an array of supported patterns.
+ */
+struct nand_op_parser {
+	const struct nand_op_parser_pattern *patterns;
+	unsigned int npatterns;
+};
+
+#define NAND_OP_PARSER(...)									\
+	{											\
+		.patterns = (struct nand_op_parser_pattern[]) { __VA_ARGS__ },			\
+		.npatterns = sizeof((struct nand_op_parser_pattern[]) { __VA_ARGS__ }) /	\
+			     sizeof(struct nand_op_parser_pattern),				\
+	}
+
+/**
+ * struct nand_operation - The actual operation
+ * @instrs: array of instructions to execute
+ * @ninstrs: length of the @instrs array
+ *
+ * The actual operation structure that will be given to the parser.
+ */
+struct nand_operation {
+	const struct nand_op_instr *instrs;
+	unsigned int ninstrs;
+};
+
+#define NAND_OPERATION(_instrs)					\
+	{							\
+		.instrs = _instrs,				\
+		.ninstrs = ARRAY_SIZE(_instrs),			\
+	}
+
+int nand_fill_column_cycles(struct nand_chip *chip, u8 *addrs,
+			    unsigned int offset_in_page);
+
+int nand_op_parser_exec_op(struct nand_chip *chip,
+			   const struct nand_op_parser *parser,
+			   const struct nand_operation *op, bool check_only);
+
+/**
  * struct nand_chip - NAND Private Flash Chip Data
  * @mtd:		MTD device registered to the MTD framework
  * @IO_ADDR_R:		[BOARDSPECIFIC] address to read the 8 I/O lines of the
@@ -885,6 +1216,9 @@ struct nand_chip {
 	int (*setup_data_interface)(struct mtd_info *mtd, int chipnr,
 				    const struct nand_data_interface *conf);
 
+	int (*exec_op)(struct nand_chip *chip,
+		       const struct nand_operation *op,
+		       bool check_only);
 
 	int chip_delay;
 	unsigned int options;
@@ -945,6 +1279,15 @@ struct nand_chip {
 	} manufacturer;
 };
 
+static inline int nand_exec_op(struct nand_chip *chip,
+			       const struct nand_operation *op)
+{
+	if (!chip->exec_op)
+		return -ENOTSUPP;
+
+	return chip->exec_op(chip, op, false);
+}
+
 extern const struct mtd_ooblayout_ops nand_ooblayout_sp_ops;
 extern const struct mtd_ooblayout_ops nand_ooblayout_lp_ops;
 
@@ -1307,23 +1650,26 @@ int nand_readid_op(struct nand_chip *chip, u8 addr, void *buf,
 int nand_status_op(struct nand_chip *chip, u8 *status);
 int nand_erase_op(struct nand_chip *chip, unsigned int eraseblock);
 int nand_read_page_op(struct nand_chip *chip, unsigned int page,
-		      unsigned int column, void *buf, unsigned int len);
-int nand_change_read_column_op(struct nand_chip *chip, unsigned int column,
-			       void *buf, unsigned int len, bool force_8bit);
+		      unsigned int offset_in_page, void *buf, unsigned int len);
+int nand_change_read_column_op(struct nand_chip *chip,
+			       unsigned int offset_in_page, void *buf,
+			       unsigned int len, bool force_8bit);
 int nand_read_oob_op(struct nand_chip *chip, unsigned int page,
-		     unsigned int column, void *buf, unsigned int len);
+		     unsigned int offset_in_page, void *buf, unsigned int len);
 int nand_prog_page_begin_op(struct nand_chip *chip, unsigned int page,
-			    unsigned int column, const void *buf,
+			    unsigned int offset_in_page, const void *buf,
 			    unsigned int len);
 int nand_prog_page_end_op(struct nand_chip *chip);
 int nand_prog_page_op(struct nand_chip *chip, unsigned int page,
-		      unsigned int column, const void *buf, unsigned int len);
-int nand_change_write_column_op(struct nand_chip *chip, unsigned int column,
-				const void *buf, unsigned int len, bool force_8bit);
+		      unsigned int offset_in_page, const void *buf,
+		      unsigned int len);
+int nand_change_write_column_op(struct nand_chip *chip,
+				unsigned int offset_in_page, const void *buf,
+				unsigned int len, bool force_8bit);
 int nand_read_data_op(struct nand_chip *chip, void *buf, unsigned int len,
-		      bool force_8bits);
+		      bool force_8bit);
 int nand_write_data_op(struct nand_chip *chip, const void *buf,
-		       unsigned int len, bool force_8bits);
+		       unsigned int len, bool force_8bit);
 
 /* Free resources held by the NAND device */
 void nand_cleanup(struct nand_chip *chip);
-- 
2.11.0

^ permalink raw reply related	[flat|nested] 75+ messages in thread

* [RFC 04/12] mtd: nand: add ->exec_op() implementation
@ 2017-10-18 14:36   ` Miquel Raynal
  0 siblings, 0 replies; 75+ messages in thread
From: Miquel Raynal @ 2017-10-18 14:36 UTC (permalink / raw)
  To: linux-arm-kernel

Introduce the new way to control the NAND controller drivers by
implementing the ->exec_op() core helpers and allowing new drivers to
use it instead of relying on ->cmd_ctrl(), ->cmdfunc() and
->read/write_byte/word/buf().

The logic is now to send to the controller driver a list of
instructions. The driver shall use the parser added by this commit to
get the matching hook if any. The instructions may be split by the
parser in order to comply with the controller constraints filled in an
array of supported patterns.

Various helpers are also added to ease NAND controller drivers writing.

This new interface should really ease the support of new vendor specific
instructions.

Suggested-by: Boris Brezillon <boris.brezillon@free-electrons.com>
Signed-off-by: Miquel Raynal <miquel.raynal@free-electrons.com>
---
 drivers/mtd/nand/denali.c      |  93 +++-
 drivers/mtd/nand/nand_base.c   | 949 +++++++++++++++++++++++++++++++++++++++--
 drivers/mtd/nand/nand_hynix.c  |  91 +++-
 drivers/mtd/nand/nand_micron.c |  32 +-
 include/linux/mtd/rawnand.h    | 366 +++++++++++++++-
 5 files changed, 1448 insertions(+), 83 deletions(-)

diff --git a/drivers/mtd/nand/denali.c b/drivers/mtd/nand/denali.c
index e5f38359f6df..8f0f18d9d9cf 100644
--- a/drivers/mtd/nand/denali.c
+++ b/drivers/mtd/nand/denali.c
@@ -652,8 +652,6 @@ static void denali_oob_xfer(struct mtd_info *mtd, struct nand_chip *chip,
 			    int page, int write)
 {
 	struct denali_nand_info *denali = mtd_to_denali(mtd);
-	unsigned int start_cmd = write ? NAND_CMD_SEQIN : NAND_CMD_READ0;
-	unsigned int rnd_cmd = write ? NAND_CMD_RNDIN : NAND_CMD_RNDOUT;
 	int writesize = mtd->writesize;
 	int oobsize = mtd->oobsize;
 	uint8_t *bufpoi = chip->oob_poi;
@@ -665,11 +663,22 @@ static void denali_oob_xfer(struct mtd_info *mtd, struct nand_chip *chip,
 	int i, pos, len;
 
 	/* BBM at the beginning of the OOB area */
-	chip->cmdfunc(mtd, start_cmd, writesize, page);
-	if (write)
-		chip->write_buf(mtd, bufpoi, oob_skip);
-	else
-		chip->read_buf(mtd, bufpoi, oob_skip);
+	if (chip->exec_op) {
+		if (write)
+			nand_prog_page_begin_op(chip, page, writesize, bufpoi,
+						oob_skip);
+		else
+			nand_read_page_op(chip, page, writesize, bufpoi,
+					  oob_skip);
+	} else {
+		if (write) {
+			chip->cmdfunc(mtd, NAND_CMD_SEQIN, writesize, page);
+			chip->write_buf(mtd, bufpoi, oob_skip);
+		} else {
+			chip->cmdfunc(mtd, NAND_CMD_READ0, writesize, page);
+			chip->read_buf(mtd, bufpoi, oob_skip);
+		}
+	}
 	bufpoi += oob_skip;
 
 	/* OOB ECC */
@@ -682,30 +691,67 @@ static void denali_oob_xfer(struct mtd_info *mtd, struct nand_chip *chip,
 		else if (pos + len > writesize)
 			len = writesize - pos;
 
-		chip->cmdfunc(mtd, rnd_cmd, pos, -1);
-		if (write)
-			chip->write_buf(mtd, bufpoi, len);
-		else
-			chip->read_buf(mtd, bufpoi, len);
-		bufpoi += len;
-		if (len < ecc_bytes) {
-			len = ecc_bytes - len;
-			chip->cmdfunc(mtd, rnd_cmd, writesize + oob_skip, -1);
+		if (chip->exec_op) {
 			if (write)
-				chip->write_buf(mtd, bufpoi, len);
+				nand_change_write_column_op(
+					chip, pos, bufpoi, len, false);
 			else
+				nand_change_read_column_op(
+					chip, pos, bufpoi, len, false);
+		} else {
+			if (write) {
+				chip->cmdfunc(mtd, NAND_CMD_RNDIN, pos, -1);
+				chip->write_buf(mtd, bufpoi, len);
+			} else {
+				chip->cmdfunc(mtd, NAND_CMD_RNDOUT, pos, -1);
 				chip->read_buf(mtd, bufpoi, len);
+			}
+		}
+		bufpoi += len;
+		if (len < ecc_bytes) {
+			len = ecc_bytes - len;
+			if (chip->exec_op) {
+				if (write)
+					nand_change_write_column_op(
+						chip, writesize + oob_skip,
+						bufpoi, len, false);
+				else
+					nand_change_read_column_op(
+						chip, writesize + oob_skip,
+						bufpoi, len, false);
+			} else {
+				if (write) {
+					chip->cmdfunc(mtd, NAND_CMD_RNDIN,
+						      writesize + oob_skip, -1);
+					chip->write_buf(mtd, bufpoi, len);
+				} else {
+					chip->cmdfunc(mtd, NAND_CMD_RNDOUT,
+						      writesize + oob_skip, -1);
+					chip->read_buf(mtd, bufpoi, len);
+				}
+			}
 			bufpoi += len;
 		}
 	}
 
 	/* OOB free */
 	len = oobsize - (bufpoi - chip->oob_poi);
-	chip->cmdfunc(mtd, rnd_cmd, size - len, -1);
-	if (write)
-		chip->write_buf(mtd, bufpoi, len);
-	else
-		chip->read_buf(mtd, bufpoi, len);
+	if (chip->exec_op) {
+		if (write)
+			nand_change_write_column_op(chip, size - len, bufpoi,
+						    len, false);
+		else
+			nand_change_read_column_op(chip, size - len, bufpoi,
+						   len, false);
+	} else {
+		if (write) {
+			chip->cmdfunc(mtd, NAND_CMD_RNDIN, size - len, -1);
+			chip->write_buf(mtd, bufpoi, len);
+		} else {
+			chip->cmdfunc(mtd, NAND_CMD_RNDOUT, size - len, -1);
+			chip->read_buf(mtd, bufpoi, len);
+		}
+	}
 }
 
 static int denali_read_page_raw(struct mtd_info *mtd, struct nand_chip *chip,
@@ -803,6 +849,9 @@ static int denali_write_oob(struct mtd_info *mtd, struct nand_chip *chip,
 
 	denali_oob_xfer(mtd, chip, page, 1);
 
+	if (chip->exec_op)
+		return nand_prog_page_end_op(chip);
+
 	chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1);
 	status = chip->waitfunc(mtd, chip);
 
diff --git a/drivers/mtd/nand/nand_base.c b/drivers/mtd/nand/nand_base.c
index bef20e06f0db..737f19bd2f83 100644
--- a/drivers/mtd/nand/nand_base.c
+++ b/drivers/mtd/nand/nand_base.c
@@ -814,7 +814,7 @@ static void nand_ccs_delay(struct nand_chip *chip)
 	 * Wait tCCS_min if it is correctly defined, otherwise wait 500ns
 	 * (which should be safe for all NANDs).
 	 */
-	if (chip->data_interface.timings.sdr.tCCS_min)
+	if (&chip->data_interface.timings.sdr.tCCS_min)
 		ndelay(chip->data_interface.timings.sdr.tCCS_min / 1000);
 	else
 		ndelay(500);
@@ -1233,6 +1233,118 @@ static int nand_init_data_interface(struct nand_chip *chip)
 }
 
 /**
+ * nand_fill_column_cycles - fill the column fields on an address array
+ * @chip: The NAND chip
+ * @addrs: Array of address cycles to fill
+ * @offset_in_page: The offset in the page
+ *
+ * Fills the first or the two first bytes of the @addrs field depending
+ * on the NAND bus width and the page size.
+ */
+int nand_fill_column_cycles(struct nand_chip *chip, u8 *addrs,
+			    unsigned int offset_in_page)
+{
+	struct mtd_info *mtd = nand_to_mtd(chip);
+
+	/*
+	 * The offset in page is expressed in bytes, if the NAND bus is 16-bit
+	 * wide, then it must be divided by 2.
+	 */
+	if (chip->options & NAND_BUSWIDTH_16) {
+		if (offset_in_page % 2) {
+			WARN_ON(true);
+			return -EINVAL;
+		}
+
+		offset_in_page /= 2;
+	}
+
+	addrs[0] = offset_in_page;
+
+	/* Small pages use 1 cycle for the columns, while large page need 2 */
+	if (mtd->writesize <= 512)
+		return 1;
+
+	addrs[1] = offset_in_page >> 8;
+
+	return 2;
+}
+EXPORT_SYMBOL_GPL(nand_fill_column_cycles);
+
+static int nand_sp_exec_read_page_op(struct nand_chip *chip, unsigned int page,
+				     unsigned int offset_in_page, void *buf,
+				     unsigned int len)
+{
+	struct mtd_info *mtd = nand_to_mtd(chip);
+	const struct nand_sdr_timings *sdr =
+		nand_get_sdr_timings(&chip->data_interface);
+	u8 addrs[4];
+	struct nand_op_instr instrs[] = {
+		NAND_OP_CMD(NAND_CMD_READ0, 0),
+		NAND_OP_ADDR(3, addrs, sdr->tWB_max),
+		NAND_OP_WAIT_RDY(sdr->tR_max, sdr->tRR_min),
+		NAND_OP_DATA_IN(len, buf, 0),
+	};
+	struct nand_operation op = NAND_OPERATION(instrs);
+
+	/* Drop the DATA_OUT instruction if len is set to 0. */
+	if (!len)
+		op.ninstrs--;
+
+	if (offset_in_page >= mtd->writesize)
+		instrs[0].cmd.opcode = NAND_CMD_READOOB;
+	else if (offset_in_page >= 256)
+		instrs[0].cmd.opcode = NAND_CMD_READ1;
+
+	if (nand_fill_column_cycles(chip, addrs, offset_in_page) < 0)
+		return -EINVAL;
+
+	addrs[1] = page;
+	addrs[2] = page >> 8;
+
+	if (chip->options & NAND_ROW_ADDR_3) {
+		addrs[3] = page >> 16;
+		instrs[1].addr.naddrs++;
+	}
+
+	return nand_exec_op(chip, &op);
+}
+
+static int nand_lp_exec_read_page_op(struct nand_chip *chip, unsigned int page,
+				     unsigned int offset_in_page, void *buf,
+				     unsigned int len)
+{
+	const struct nand_sdr_timings *sdr =
+		nand_get_sdr_timings(&chip->data_interface);
+	u8 addrs[5];
+	struct nand_op_instr instrs[] = {
+		NAND_OP_CMD(NAND_CMD_READ0, 0),
+		NAND_OP_ADDR(4, addrs, 0),
+		NAND_OP_CMD(NAND_CMD_READSTART, sdr->tWB_max),
+		NAND_OP_WAIT_RDY(sdr->tR_max, sdr->tRR_min),
+		NAND_OP_DATA_IN(len, buf, 0),
+	};
+	struct nand_operation op = NAND_OPERATION(instrs);
+
+	/* Drop the DATA_IN instruction if len is set to 0. */
+	if (!len)
+		op.ninstrs--;
+
+	if (nand_fill_column_cycles(chip, addrs, offset_in_page))
+		return -EINVAL;
+
+	addrs[2] = page;
+	addrs[3] = page >> 8;
+
+	if (chip->options & NAND_ROW_ADDR_3) {
+		addrs[4] = page >> 16;
+		instrs[1].addr.naddrs++;
+	}
+
+	return nand_exec_op(chip, &op);
+}
+
+/**
  * nand_read_page_op - Do a READ PAGE operation
  * @chip: The NAND chip
  * @page: page to read
@@ -1246,17 +1358,26 @@ static int nand_init_data_interface(struct nand_chip *chip)
  * Returns 0 for success or negative error code otherwise
  */
 int nand_read_page_op(struct nand_chip *chip, unsigned int page,
-		      unsigned int column, void *buf, unsigned int len)
+		      unsigned int offset_in_page, void *buf, unsigned int len)
 {
 	struct mtd_info *mtd = nand_to_mtd(chip);
 
 	if (len && !buf)
 		return -EINVAL;
 
-	if (column + len > mtd->writesize + mtd->oobsize)
+	if (offset_in_page + len > mtd->writesize + mtd->oobsize)
 		return -EINVAL;
 
-	chip->cmdfunc(mtd, NAND_CMD_READ0, column, page);
+	if (chip->exec_op) {
+		if (mtd->writesize > 512)
+			return nand_lp_exec_read_page_op(
+				chip, page, offset_in_page, buf, len);
+
+		return nand_sp_exec_read_page_op(chip, page, offset_in_page,
+						 buf, len);
+	}
+
+	chip->cmdfunc(mtd, NAND_CMD_READ0, offset_in_page, page);
 	if (len)
 		chip->read_buf(mtd, buf, len);
 
@@ -1286,6 +1407,25 @@ static int nand_read_param_page_op(struct nand_chip *chip, u8 page, void *buf,
 	if (len && !buf)
 		return -EINVAL;
 
+	if (chip->exec_op) {
+		const struct nand_sdr_timings *sdr =
+			nand_get_sdr_timings(&chip->data_interface);
+		struct nand_op_instr instrs[] = {
+			NAND_OP_CMD(NAND_CMD_PARAM, 0),
+			NAND_OP_ADDR(1, &page, sdr->tWB_max),
+			NAND_OP_WAIT_RDY(sdr->tR_max, sdr->tRR_min),
+			NAND_OP_8BIT_DATA_IN(len, buf, 0),
+		};
+		struct nand_operation op =
+			NAND_OPERATION(instrs);
+
+		/* Drop the DATA_IN instruction if len is set to 0. */
+		if (!len)
+			op.ninstrs--;
+
+		return nand_exec_op(chip, &op);
+	}
+
 	chip->cmdfunc(mtd, NAND_CMD_PARAM, page, -1);
 	for (i = 0; i < len; i++)
 		p[i] = chip->read_byte(mtd);
@@ -1306,18 +1446,48 @@ static int nand_read_param_page_op(struct nand_chip *chip, u8 page, void *buf,
  *
  * Returns 0 for success or negative error code otherwise
  */
-int nand_change_read_column_op(struct nand_chip *chip, unsigned int column,
-			       void *buf, unsigned int len, bool force_8bit)
+int nand_change_read_column_op(struct nand_chip *chip,
+			       unsigned int offset_in_page, void *buf,
+			       unsigned int len, bool force_8bit)
 {
 	struct mtd_info *mtd = nand_to_mtd(chip);
 
 	if (len && !buf)
 		return -EINVAL;
 
-	if (column + len > mtd->writesize + mtd->oobsize)
+	if (offset_in_page + len > mtd->writesize + mtd->oobsize)
 		return -EINVAL;
 
-	chip->cmdfunc(mtd, NAND_CMD_RNDOUT, column, -1);
+	/* Small page NANDs do not support column change. */
+	if (mtd->writesize <= 512)
+		return -ENOTSUPP;
+
+	if (chip->exec_op) {
+		const struct nand_sdr_timings *sdr =
+			nand_get_sdr_timings(&chip->data_interface);
+		u8 addrs[2] = {};
+		struct nand_op_instr instrs[] = {
+			NAND_OP_CMD(NAND_CMD_RNDOUT, 0),
+			NAND_OP_ADDR(2, addrs, 0),
+			NAND_OP_CMD(NAND_CMD_RNDOUTSTART, sdr->tCCS_min),
+			NAND_OP_DATA_IN(len, buf, 0),
+		};
+		struct nand_operation op =
+			NAND_OPERATION(instrs);
+
+		if (nand_fill_column_cycles(chip, addrs, offset_in_page) < 0)
+			return -EINVAL;
+
+		/* Drop the DATA_IN instruction if len is set to 0. */
+		if (!len)
+			op.ninstrs--;
+
+		instrs[3].data.force_8bit = force_8bit;
+
+		return nand_exec_op(chip, &op);
+	}
+
+	chip->cmdfunc(mtd, NAND_CMD_RNDOUT, offset_in_page, -1);
 	if (len)
 		chip->read_buf(mtd, buf, len);
 
@@ -1339,17 +1509,28 @@ EXPORT_SYMBOL_GPL(nand_change_read_column_op);
  * Returns 0 for success or negative error code otherwise
  */
 int nand_read_oob_op(struct nand_chip *chip, unsigned int page,
-		     unsigned int column, void *buf, unsigned int len)
+		     unsigned int offset_in_page, void *buf, unsigned int len)
 {
 	struct mtd_info *mtd = nand_to_mtd(chip);
 
 	if (len && !buf)
 		return -EINVAL;
 
-	if (column + len > mtd->oobsize)
+	if (offset_in_page + len > mtd->oobsize)
 		return -EINVAL;
 
-	chip->cmdfunc(mtd, NAND_CMD_READOOB, column, page);
+	if (chip->exec_op) {
+		offset_in_page += mtd->writesize;
+
+		if (mtd->writesize > 512)
+			return nand_lp_exec_read_page_op(
+				chip, page, offset_in_page, buf, len);
+
+		return nand_sp_exec_read_page_op(chip, page, offset_in_page,
+						 buf, len);
+	}
+
+	chip->cmdfunc(mtd, NAND_CMD_READOOB, offset_in_page, page);
 	if (len)
 		chip->read_buf(mtd, buf, len);
 
@@ -1357,6 +1538,62 @@ int nand_read_oob_op(struct nand_chip *chip, unsigned int page,
 }
 EXPORT_SYMBOL_GPL(nand_read_oob_op);
 
+static int nand_exec_prog_page_op(struct nand_chip *chip, unsigned int page,
+				  unsigned int offset_in_page, const void *buf,
+				  unsigned int len, bool prog)
+{
+	struct mtd_info *mtd = nand_to_mtd(chip);
+	const struct nand_sdr_timings *sdr =
+		nand_get_sdr_timings(&chip->data_interface);
+	u8 addrs[5] = {};
+	struct nand_op_instr instrs[] = {
+		/*
+		 * Pointer command will be adjusted if we're dealing
+		 * with a small page NAND.
+		 */
+		NAND_OP_CMD(NAND_CMD_READ0, 0),
+		NAND_OP_CMD(NAND_CMD_SEQIN, 0),
+		NAND_OP_ADDR(0, addrs, sdr->tADL_min),
+		NAND_OP_DATA_OUT(len, buf, 0),
+		NAND_OP_CMD(NAND_CMD_PAGEPROG, sdr->tWB_max),
+		NAND_OP_WAIT_RDY(sdr->tPROG_max, 0),
+	};
+	struct nand_operation op = NAND_OPERATION(instrs);
+	int naddrs = nand_fill_column_cycles(chip, addrs, offset_in_page);
+
+	if (naddrs < 0)
+		return naddrs;
+
+	addrs[naddrs++] = page;
+	addrs[naddrs++] = page >> 8;
+	if (chip->options & NAND_ROW_ADDR_3)
+		addrs[naddrs++] = page >> 16;
+
+	instrs[2].addr.naddrs = naddrs;
+
+	/* Drop the lasts instructions if we're not programming the page. */
+	if (!prog) {
+		op.ninstrs -= 2;
+		/* Also drop the DATA_OUT instruction if empty */
+		if (!len)
+			op.ninstrs--;
+	}
+
+	if (mtd->writesize <= 512) {
+		/* Small pages need some more tweaking */
+		if (offset_in_page >= mtd->writesize)
+			instrs[0].cmd.opcode = NAND_CMD_READOOB;
+		else if (offset_in_page >= 256)
+			instrs[0].cmd.opcode = NAND_CMD_READ1;
+	} else {
+		/* Drop the first command if dealing with large pages */
+		op.instrs++;
+		op.ninstrs--;
+	}
+
+	return nand_exec_op(chip, &op);
+}
+
 /**
  * nand_prog_page_begin_op - starts a PROG PAGE operation
  * @chip: The NAND chip
@@ -1371,7 +1608,7 @@ EXPORT_SYMBOL_GPL(nand_read_oob_op);
  * Returns 0 for success or negative error code otherwise
  */
 int nand_prog_page_begin_op(struct nand_chip *chip, unsigned int page,
-			    unsigned int column, const void *buf,
+			    unsigned int offset_in_page, const void *buf,
 			    unsigned int len)
 {
 	struct mtd_info *mtd = nand_to_mtd(chip);
@@ -1379,10 +1616,14 @@ int nand_prog_page_begin_op(struct nand_chip *chip, unsigned int page,
 	if (len && !buf)
 		return -EINVAL;
 
-	if (column + len > mtd->writesize + mtd->oobsize)
+	if (offset_in_page + len > mtd->writesize + mtd->oobsize)
 		return -EINVAL;
 
-	chip->cmdfunc(mtd, NAND_CMD_SEQIN, column, page);
+	if (chip->exec_op)
+		return nand_exec_prog_page_op(
+			chip, page, offset_in_page, buf, len, false);
+
+	chip->cmdfunc(mtd, NAND_CMD_SEQIN, offset_in_page, page);
 
 	if (buf)
 		chip->write_buf(mtd, buf, len);
@@ -1405,6 +1646,19 @@ int nand_prog_page_end_op(struct nand_chip *chip)
 	struct mtd_info *mtd = nand_to_mtd(chip);
 	int status;
 
+	if (chip->exec_op) {
+		const struct nand_sdr_timings *sdr =
+			nand_get_sdr_timings(&chip->data_interface);
+		struct nand_op_instr instrs[] = {
+			NAND_OP_CMD(NAND_CMD_PAGEPROG, sdr->tWB_max),
+			NAND_OP_WAIT_RDY(sdr->tPROG_max, 0),
+		};
+		struct nand_operation op =
+			NAND_OPERATION(instrs);
+
+		return nand_exec_op(chip, &op);
+	}
+
 	chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1);
 
 	status = chip->waitfunc(mtd, chip);
@@ -1429,7 +1683,8 @@ EXPORT_SYMBOL_GPL(nand_prog_page_end_op);
  * Returns 0 for success or negative error code otherwise
  */
 int nand_prog_page_op(struct nand_chip *chip, unsigned int page,
-		      unsigned int column, const void *buf, unsigned int len)
+		      unsigned int offset_in_page, const void *buf,
+		      unsigned int len)
 {
 	struct mtd_info *mtd = nand_to_mtd(chip);
 	int status;
@@ -1437,10 +1692,14 @@ int nand_prog_page_op(struct nand_chip *chip, unsigned int page,
 	if (!len || !buf)
 		return -EINVAL;
 
-	if (column + len > mtd->writesize + mtd->oobsize)
+	if (offset_in_page + len > mtd->writesize + mtd->oobsize)
 		return -EINVAL;
 
-	chip->cmdfunc(mtd, NAND_CMD_SEQIN, column, page);
+	if (chip->exec_op)
+		return nand_exec_prog_page_op(
+			chip, page, offset_in_page, buf, len, true);
+
+	chip->cmdfunc(mtd, NAND_CMD_SEQIN, offset_in_page, page);
 	chip->write_buf(mtd, buf, len);
 	chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1);
 
@@ -1465,7 +1724,8 @@ EXPORT_SYMBOL_GPL(nand_prog_page_op);
  *
  * Returns 0 for success or negative error code otherwise
  */
-int nand_change_write_column_op(struct nand_chip *chip, unsigned int column,
+int nand_change_write_column_op(struct nand_chip *chip,
+				unsigned int offset_in_page,
 				const void *buf, unsigned int len,
 				bool force_8bit)
 {
@@ -1474,10 +1734,38 @@ int nand_change_write_column_op(struct nand_chip *chip, unsigned int column,
 	if (len && !buf)
 		return -EINVAL;
 
-	if (column + len > mtd->writesize + mtd->oobsize)
+	if (offset_in_page + len > mtd->writesize + mtd->oobsize)
 		return -EINVAL;
 
-	chip->cmdfunc(mtd, NAND_CMD_RNDIN, column, -1);
+	/* Small page NANDs do not support column change. */
+	if (mtd->writesize <= 512)
+		return -ENOTSUPP;
+
+	if (chip->exec_op) {
+		const struct nand_sdr_timings *sdr =
+			nand_get_sdr_timings(&chip->data_interface);
+		u8 addrs[2];
+		struct nand_op_instr instrs[] = {
+			NAND_OP_CMD(NAND_CMD_RNDIN, 0),
+			NAND_OP_ADDR(2, addrs, sdr->tCCS_min),
+			NAND_OP_DATA_OUT(len, buf, 0),
+		};
+		struct nand_operation op =
+			NAND_OPERATION(instrs);
+
+		if (nand_fill_column_cycles(chip, addrs, offset_in_page) < 0)
+			return -EINVAL;
+
+		instrs[2].data.force_8bit = force_8bit;
+
+		/* Drop the DATA_OUT instruction if len is set to 0. */
+		if (!len)
+			op.ninstrs--;
+
+		return nand_exec_op(chip, &op);
+	}
+
+	chip->cmdfunc(mtd, NAND_CMD_RNDIN, offset_in_page, -1);
 	if (len)
 		chip->write_buf(mtd, buf, len);
 
@@ -1508,6 +1796,24 @@ int nand_readid_op(struct nand_chip *chip, u8 addr,
 	if (!len || !buf)
 		return -EINVAL;
 
+	if (chip->exec_op) {
+		const struct nand_sdr_timings *sdr =
+			nand_get_sdr_timings(&chip->data_interface);
+		struct nand_op_instr instrs[] = {
+			NAND_OP_CMD(NAND_CMD_READID, 0),
+			NAND_OP_ADDR(1, &addr, sdr->tADL_min),
+			NAND_OP_8BIT_DATA_IN(len, buf, 0),
+		};
+		struct nand_operation op =
+			NAND_OPERATION(instrs);
+
+		/* Drop the DATA_IN instruction if len is set to 0. */
+		if (!len)
+			op.ninstrs--;
+
+		return nand_exec_op(chip, &op);
+	}
+
 	chip->cmdfunc(mtd, NAND_CMD_READID, addr, -1);
 
 	for (i = 0; i < len; i++)
@@ -1532,6 +1838,22 @@ int nand_status_op(struct nand_chip *chip, u8 *status)
 {
 	struct mtd_info *mtd = nand_to_mtd(chip);
 
+	if (chip->exec_op) {
+		const struct nand_sdr_timings *sdr =
+			nand_get_sdr_timings(&chip->data_interface);
+		struct nand_op_instr instrs[] = {
+			NAND_OP_CMD(NAND_CMD_STATUS, sdr->tADL_min),
+			NAND_OP_8BIT_DATA_IN(1, status, 0),
+		};
+		struct nand_operation op =
+			NAND_OPERATION(instrs);
+
+		if (!status)
+			op.ninstrs--;
+
+		return nand_exec_op(chip, &op);
+	}
+
 	chip->cmdfunc(mtd, NAND_CMD_STATUS, -1, -1);
 	if (status)
 		*status = chip->read_byte(mtd);
@@ -1558,6 +1880,25 @@ int nand_erase_op(struct nand_chip *chip, unsigned int eraseblock)
 			    (chip->phys_erase_shift - chip->page_shift);
 	int status;
 
+	if (chip->exec_op) {
+		const struct nand_sdr_timings *sdr =
+			nand_get_sdr_timings(&chip->data_interface);
+		u8 addrs[3] = {	page, page >> 8, page >> 16 };
+		struct nand_op_instr instrs[] = {
+			NAND_OP_CMD(NAND_CMD_ERASE1, 0),
+			NAND_OP_ADDR(2, addrs, 0),
+			NAND_OP_CMD(NAND_CMD_ERASE2, sdr->tWB_max),
+			NAND_OP_WAIT_RDY(sdr->tBERS_max, 0),
+		};
+		struct nand_operation op =
+			NAND_OPERATION(instrs);
+
+		if (chip->options & NAND_ROW_ADDR_3)
+			instrs[1].addr.naddrs++;
+
+		return nand_exec_op(chip, &op);
+	}
+
 	chip->cmdfunc(mtd, NAND_CMD_ERASE1, -1, page);
 	chip->cmdfunc(mtd, NAND_CMD_ERASE2, -1, -1);
 
@@ -1591,6 +1932,22 @@ static int nand_set_features_op(struct nand_chip *chip, u8 feature,
 	const u8 *params = data;
 	int i, status;
 
+	if (chip->exec_op) {
+		const struct nand_sdr_timings *sdr =
+			nand_get_sdr_timings(&chip->data_interface);
+		struct nand_op_instr instrs[] = {
+			NAND_OP_CMD(NAND_CMD_SET_FEATURES, 0),
+			NAND_OP_ADDR(1, &feature, sdr->tADL_min),
+			NAND_OP_8BIT_DATA_OUT(ONFI_SUBFEATURE_PARAM_LEN, data,
+					      sdr->tWB_max),
+			NAND_OP_WAIT_RDY(sdr->tFEAT_max, 0),
+		};
+		struct nand_operation op =
+			NAND_OPERATION(instrs);
+
+		return nand_exec_op(chip, &op);
+	}
+
 	chip->cmdfunc(mtd, NAND_CMD_SET_FEATURES, feature, -1);
 	for (i = 0; i < ONFI_SUBFEATURE_PARAM_LEN; ++i)
 		chip->write_byte(mtd, params[i]);
@@ -1621,6 +1978,22 @@ static int nand_get_features_op(struct nand_chip *chip, u8 feature,
 	u8 *params = data;
 	int i;
 
+	if (chip->exec_op) {
+		const struct nand_sdr_timings *sdr =
+			nand_get_sdr_timings(&chip->data_interface);
+		struct nand_op_instr instrs[] = {
+			NAND_OP_CMD(NAND_CMD_GET_FEATURES, 0),
+			NAND_OP_ADDR(1, &feature, sdr->tWB_max),
+			NAND_OP_WAIT_RDY(sdr->tFEAT_max, sdr->tRR_min),
+			NAND_OP_8BIT_DATA_IN(ONFI_SUBFEATURE_PARAM_LEN,
+					     data, 0),
+		};
+		struct nand_operation op =
+			NAND_OPERATION(instrs);
+
+		return nand_exec_op(chip, &op);
+	}
+
 	chip->cmdfunc(mtd, NAND_CMD_GET_FEATURES, feature, -1);
 	for (i = 0; i < ONFI_SUBFEATURE_PARAM_LEN; ++i)
 		params[i] = chip->read_byte(mtd);
@@ -1642,6 +2015,19 @@ int nand_reset_op(struct nand_chip *chip)
 {
 	struct mtd_info *mtd = nand_to_mtd(chip);
 
+	if (chip->exec_op) {
+		const struct nand_sdr_timings *sdr =
+			nand_get_sdr_timings(&chip->data_interface);
+		struct nand_op_instr instrs[] = {
+			NAND_OP_CMD(NAND_CMD_RESET, sdr->tWB_max),
+			NAND_OP_WAIT_RDY(sdr->tRST_max, 0),
+		};
+		struct nand_operation op =
+			NAND_OPERATION(instrs);
+
+		return nand_exec_op(chip, &op);
+	}
+
 	chip->cmdfunc(mtd, NAND_CMD_RESET, -1, -1);
 
 	return 0;
@@ -1669,6 +2055,18 @@ int nand_read_data_op(struct nand_chip *chip, void *buf, unsigned int len,
 	if (!len || !buf)
 		return -EINVAL;
 
+	if (chip->exec_op) {
+		struct nand_op_instr instrs[] = {
+			NAND_OP_DATA_IN(len, buf, 0),
+		};
+		struct nand_operation op =
+			NAND_OPERATION(instrs);
+
+		instrs[0].data.force_8bit = force_8bit;
+
+		return nand_exec_op(chip, &op);
+	}
+
 	if (force_8bit) {
 		u8 *p = buf;
 		unsigned int i;
@@ -1704,6 +2102,18 @@ int nand_write_data_op(struct nand_chip *chip, const void *buf,
 	if (!len || !buf)
 		return -EINVAL;
 
+	if (chip->exec_op) {
+		struct nand_op_instr instrs[] = {
+			NAND_OP_DATA_OUT(len, buf, 0),
+		};
+		struct nand_operation op =
+			NAND_OPERATION(instrs);
+
+		instrs[0].data.force_8bit = force_8bit;
+
+		return nand_exec_op(chip, &op);
+	}
+
 	if (force_8bit) {
 		const u8 *p = buf;
 		unsigned int i;
@@ -1719,6 +2129,471 @@ int nand_write_data_op(struct nand_chip *chip, const void *buf,
 EXPORT_SYMBOL_GPL(nand_write_data_op);
 
 /**
+ * struct nand_op_parser_ctx - Context used by the parser
+ * @instrs: array of all the instructions that must be addressed
+ * @ninstrs: length of the @instrs array
+ * @instr_idx: index of the instruction in the @instrs array that matches the
+ *	       first instruction of the subop structure
+ * @instr_start_off: offset at which the first instruction of the subop
+ *		     structure must start if it is and address or a data
+ *		     instruction
+ *
+ * This structure is used by the core to handle splitting lengthy instructions
+ * into sub-operations.
+ */
+struct nand_op_parser_ctx {
+	const struct nand_op_instr *instrs;
+	unsigned int ninstrs;
+	unsigned int instr_idx;
+	unsigned int instr_start_off;
+	struct nand_subop subop;
+};
+
+/**
+ * nand_op_parser_must_split_instr - Checks if an instruction must be split
+ * @pat: the parser pattern that match
+ * @instr: the instruction array to check
+ * @start_offset: the offset from which to start in the first instruction of the
+ *		  @instr array
+ *
+ * Some NAND controllers are limited and cannot send X address cycles with a
+ * unique operation, or cannot read/write more than Y bytes at the same time.
+ * In this case, reduce the scope of this set of operation, and trigger another
+ * instruction with the rest.
+ *
+ * Returns true if the array of instruction must be split, false otherwise.
+ * The @start_offset parameter is also updated to the offset at which the first
+ * instruction of the next bundle of instruction must start (if an address or a
+ * data instruction).
+ */
+static bool
+nand_op_parser_must_split_instr(const struct nand_op_parser_pattern_elem *pat,
+				const struct nand_op_instr *instr,
+				unsigned int *start_offset)
+{
+	switch (pat->type) {
+	case NAND_OP_ADDR_INSTR:
+		if (!pat->addr.maxcycles)
+			break;
+
+		if (instr->addr.naddrs - *start_offset > pat->addr.maxcycles) {
+			*start_offset += pat->addr.maxcycles;
+			return true;
+		}
+		break;
+
+	case NAND_OP_DATA_IN_INSTR:
+	case NAND_OP_DATA_OUT_INSTR:
+		if (!pat->data.maxlen)
+			break;
+
+		if (instr->data.len - *start_offset > pat->data.maxlen) {
+			*start_offset += pat->data.maxlen;
+			return true;
+		}
+		break;
+
+	default:
+		break;
+	}
+
+	return false;
+}
+
+/**
+ * nand_op_parser_match_pat - Checks a pattern
+ * @pat: the parser pattern to check if it matches
+ * @ctx: the context structure to match with the pattern @pat
+ *
+ * Check if *one* given pattern matches the given sequence of instructions
+ */
+static bool
+nand_op_parser_match_pat(const struct nand_op_parser_pattern *pat,
+			 struct nand_op_parser_ctx *ctx)
+{
+	unsigned int i, j, boundary_off = ctx->instr_start_off;
+
+	ctx->subop.ninstrs = 0;
+
+	for (i = ctx->instr_idx, j = 0; i < ctx->ninstrs && j < pat->nelems;) {
+		const struct nand_op_instr *instr = &ctx->instrs[i];
+
+		/*
+		 * The pattern instruction does not match the operation
+		 * instruction. If the instruction is marked optional in the
+		 * pattern definition, we skip the pattern element and continue
+		 * to the next one. If the element is mandatory, there's no
+		 * match and we can return false directly.
+		 */
+		if (instr->type != pat->elems[j].type) {
+			if (!pat->elems[j].optional)
+				return false;
+
+			j++;
+			continue;
+		}
+
+		/*
+		 * Now check the pattern element constraints. If the pattern is
+		 * not able to handle the whole instruction in a single step,
+		 * we'll have to break it down into several instructions.
+		 * The *boudary_off value comes back updated to point to the
+		 * limit between the split instruction (the end of the original
+		 * chunk, the start of new next one).
+		 */
+		if (nand_op_parser_must_split_instr(&pat->elems[j], instr,
+						    &boundary_off)) {
+			ctx->subop.ninstrs++;
+			j++;
+			break;
+		}
+
+		ctx->subop.ninstrs++;
+		i++;
+		j++;
+		boundary_off = 0;
+	}
+
+	/*
+	 * This can happen if all instructions of a pattern are optional.
+	 * Still, if there's not at least one instruction handled by this
+	 * pattern, this is not a match, and we should try the next one (if
+	 * any).
+	 */
+	if (!ctx->subop.ninstrs)
+		return false;
+
+	/*
+	 * We had a match on the pattern head, but the pattern may be longer
+	 * than the instructions we're asked to execute. We need to make sure
+	 * there's no mandatory elements in the pattern tail.
+	 *
+	 * The case where all the operations of a pattern have been checked but
+	 * the number of instructions is bigger is handled right after this by
+	 * returning true on the pattern match, which will order the execution
+	 * of the subset of instructions later defined, while updating the
+	 * context ids to the next chunk of instructions.
+	 */
+	for (; j < pat->nelems; j++) {
+		if (!pat->elems[j].optional)
+			return false;
+	}
+
+	/*
+	 * We have a match: update the ctx and return true. The subop structure
+	 * will be used by the pattern's ->exec() function.
+	 */
+	ctx->subop.instrs = &ctx->instrs[ctx->instr_idx];
+	ctx->subop.first_instr_start_off = ctx->instr_start_off;
+	ctx->subop.last_instr_end_off = boundary_off;
+
+	/*
+	 * Update the pointers so the calling function will be able to recall
+	 * this one with a new subset of instructions.
+	 *
+	 * In the case where the last operation of this set is split, point to
+	 * the last unfinished job, knowing the starting offset.
+	 */
+	ctx->instr_idx = i;
+	ctx->instr_start_off = boundary_off;
+
+	return true;
+}
+
+#if IS_ENABLED(CONFIG_DYNAMIC_DEBUG) || defined(DEBUG)
+static void nand_op_parser_trace(const struct nand_op_parser_ctx *ctx,
+				 bool success)
+{
+	const struct nand_op_instr *instr;
+	bool in_subop = false;
+	char *is_in =  "    ->", *is_out = "      ", *prefix;
+	char *buf;
+	unsigned int len, off = 0;
+	int i, j;
+
+	if (success)
+		pr_debug("executing subop:\n");
+	else
+		pr_debug("pattern not found:\n");
+
+	for (i = 0; i < ctx->ninstrs; i++) {
+		instr = &ctx->instrs[i];
+
+		/*
+		 * ctx->instr_idx is not reliable because it may already had
+		 * been updated by the parser. Use pointers comparison instead.
+		 */
+		if (instr == &ctx->subop.instrs[0])
+			in_subop = true;
+
+		if (in_subop)
+			prefix = is_in;
+		else
+			prefix = is_out;
+
+		switch (instr->type) {
+		case NAND_OP_CMD_INSTR:
+			pr_debug("%sCMD      [0x%02x]\n", prefix,
+				 instr->cmd.opcode);
+			break;
+		case NAND_OP_ADDR_INSTR:
+			/*
+			 * A log line is much less than 50 bytes, plus 5 bytes
+			 * per address cycle to display.
+			 */
+			len = 50 + 5 * instr->addr.naddrs;
+			buf = kmalloc(len, GFP_KERNEL);
+			if (!buf)
+				return;
+			memset(buf, 0, len);
+			off += snprintf(buf, len, "ADDR     [%d cyc:",
+					instr->addr.naddrs);
+			for (j = 0; j < instr->addr.naddrs; j++)
+				off += snprintf(&buf[off], len - off, " 0x%02x",
+						instr->addr.addrs[j]);
+			pr_debug("%s%s]\n", prefix, buf);
+			break;
+		case NAND_OP_DATA_IN_INSTR:
+			pr_debug("%sDATA_IN  [%d B%s]\n", prefix,
+				 instr->data.len,
+				 instr->data.force_8bit ? ", force 8-bit" : "");
+			break;
+		case NAND_OP_DATA_OUT_INSTR:
+			pr_debug("%sDATA_OUT [%d B%s]\n", prefix,
+				 instr->data.len,
+				 instr->data.force_8bit ? ", force 8-bit" : "");
+			break;
+		case NAND_OP_WAITRDY_INSTR:
+			pr_debug("%sWAITRDY  [max %d ms]\n", prefix,
+				 instr->waitrdy.timeout_ms);
+			break;
+		}
+
+		if (instr == &ctx->subop.instrs[ctx->subop.ninstrs - 1])
+			in_subop = false;
+	}
+}
+#else
+static void nand_op_parser_trace(const struct nand_op_parser_ctx *ctx,
+				 bool success)
+{
+	/* NOP */
+}
+#endif
+
+/**
+ * nand_op_parser_exec_op - exec_op parser
+ * @chip: the NAND chip
+ * @parser: the parser to use given by the controller driver
+ * @op: the NAND operation to address
+ * @check_only: flag asking if the entire operation could be handled
+ *
+ * Function that must be called by each driver that implement the "exec_op API"
+ * in their own ->exec_op() implementation.
+ *
+ * The function iterates on all the instructions asked and make use of internal
+ * parsers to find matches between the instruction list and the handled patterns
+ * filled by the controller drivers inside the @parser structure. If needed, the
+ * instructions could be split into sub-operations and be executed sequentially.
+ */
+int nand_op_parser_exec_op(struct nand_chip *chip,
+			   const struct nand_op_parser *parser,
+			   const struct nand_operation *op, bool check_only)
+{
+	struct nand_op_parser_ctx ctx = {
+		.instrs = op->instrs,
+		.ninstrs = op->ninstrs,
+	};
+	unsigned int i;
+
+	while (ctx.instr_idx < op->ninstrs) {
+		bool pattern_found = false;
+		int ret;
+
+		for (i = 0; i < parser->npatterns; i++) {
+			const struct nand_op_parser_pattern *pattern;
+
+			pattern = &parser->patterns[i];
+			if (!nand_op_parser_match_pat(pattern, &ctx))
+				continue;
+
+			nand_op_parser_trace(&ctx, true);
+
+			if (check_only)
+				break;
+
+			ret = pattern->exec(chip, &ctx.subop);
+			if (ret)
+				return ret;
+
+			pattern_found = true;
+		}
+
+		if (!pattern_found) {
+			nand_op_parser_trace(&ctx, false);
+			return -ENOTSUPP;
+		}
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(nand_op_parser_exec_op);
+
+static bool nand_instr_is_data(const struct nand_op_instr *instr)
+{
+	return instr && (instr->type == NAND_OP_DATA_IN_INSTR ||
+			 instr->type == NAND_OP_DATA_OUT_INSTR);
+}
+
+static bool nand_subop_instr_is_valid(const struct nand_subop *subop,
+				      unsigned int op_id)
+{
+	return subop && op_id < subop->ninstrs;
+}
+
+static int nand_subop_get_start_off(const struct nand_subop *subop,
+				    unsigned int op_id)
+{
+	if (op_id)
+		return 0;
+
+	return subop->first_instr_start_off;
+}
+
+/**
+ * nand_subop_get_addr_start_off - Get the start offset in an address array
+ * @subop: The entire sub-operation
+ * @op_id: Index of the instruction inside the sub-operation
+ *
+ * Instructions arrays may be split by the parser between instructions,
+ * and also in the middle of an address instruction if the number of cycles
+ * to assert in one operation is not supported by the controller.
+ *
+ * For this, instead of using the first index of the ->addr.addrs field from the
+ * address instruction, the NAND controller driver must use this helper that
+ * will either return 0 if the index does not point to the first instruction of
+ * the sub-operation, or the offset of the next starting offset inside the
+ * address cycles.
+ *
+ * Returns the offset of the first address cycle to assert from the pointed
+ * address instruction.
+ */
+int nand_subop_get_addr_start_off(const struct nand_subop *subop,
+				  unsigned int op_id)
+{
+	if (!nand_subop_instr_is_valid(subop, op_id) ||
+	    subop->instrs[op_id].type != NAND_OP_ADDR_INSTR)
+		return -EINVAL;
+
+	return nand_subop_get_start_off(subop, op_id);
+}
+EXPORT_SYMBOL_GPL(nand_subop_get_addr_start_off);
+
+/**
+ * nand_subop_get_num_addr_cyc - Get the remaining address cycles to assert
+ * @subop: The entire sub-operation
+ * @op_id: Index of the instruction inside the sub-operation
+ *
+ * Instructions arrays may be split by the parser between instructions,
+ * and also in the middle of an address instruction if the number of cycles
+ * to assert in one operation is not supported by the controller.
+ *
+ * For this, instead of using the ->addr.naddrs fields of the address
+ * instruction, the NAND controller driver must use this helper that will
+ * return the actual number of cycles to assert between the first and last
+ * offset asked for this particular instruction.
+ *
+ * Returns the number of address cycles to assert from the pointed address
+ * instruction.
+ */
+int nand_subop_get_num_addr_cyc(const struct nand_subop *subop,
+				unsigned int op_id)
+{
+	int start_off, end_off;
+
+	if (!nand_subop_instr_is_valid(subop, op_id) ||
+	    subop->instrs[op_id].type != NAND_OP_ADDR_INSTR)
+		return -EINVAL;
+
+	start_off = nand_subop_get_addr_start_off(subop, op_id);
+
+	if (op_id == subop->ninstrs - 1 &&
+	    subop->last_instr_end_off)
+		end_off = subop->last_instr_end_off;
+	else
+		end_off = subop->instrs[op_id].addr.naddrs;
+
+	return end_off - start_off;
+}
+EXPORT_SYMBOL_GPL(nand_subop_get_num_addr_cyc);
+
+/**
+ * nand_subop_get_data_start_off - Get the start offset in a data array
+ * @subop: The entire sub-operation
+ * @op_id: Index of the instruction inside the sub-operation
+ *
+ * Instructions arrays may be split by the parser between instructions,
+ * and also in the middle of a data instruction if the number of bytes to access
+ * in one operation is greater that the controller limit.
+ *
+ * For this, instead of using the first index of the ->data.buf field from the
+ * data instruction, the NAND controller driver must use this helper that
+ * will either return 0 if the index does not point to the first instruction of
+ * the sub-operation, or the offset of the next starting offset inside the
+ * data buffer.
+ *
+ * Returns the data offset inside the pointed data instruction buffer from which
+ * to start.
+ */
+int nand_subop_get_data_start_off(const struct nand_subop *subop,
+				  unsigned int op_id)
+{
+	if (!nand_subop_instr_is_valid(subop, op_id) ||
+	    !nand_instr_is_data(&subop->instrs[op_id]))
+		return -EINVAL;
+
+	return nand_subop_get_start_off(subop, op_id);
+}
+EXPORT_SYMBOL_GPL(nand_subop_get_data_start_off);
+
+/**
+ * nand_subop_get_data_len - Get the number of bytes to retrieve
+ * @subop: The entire sub-operation
+ * @op_id: Index of the instruction inside the sub-operation
+ *
+ * Instructions arrays may be split by the parser between instructions,
+ * and also in the middle of a data instruction if the number of bytes to access
+ * in one operation is greater that the controller limit.
+ *
+ * For this, instead of using the ->data.len field from the data instruction,
+ * the NAND controller driver must use this helper that will return the actual
+ * length of data to move between the first and last offset asked for this
+ * particular instruction.
+ *
+ * Returns the length of the data to move from the pointed data instruction.
+ */
+int nand_subop_get_data_len(const struct nand_subop *subop,
+			    unsigned int op_id)
+{
+	int start_off = 0, end_off;
+
+	if (!nand_subop_instr_is_valid(subop, op_id) ||
+	    !nand_instr_is_data(&subop->instrs[op_id]))
+		return -EINVAL;
+
+	start_off = nand_subop_get_data_start_off(subop, op_id);
+
+	if (op_id == subop->ninstrs - 1 &&
+	    subop->last_instr_end_off)
+		end_off = subop->last_instr_end_off;
+	else
+		end_off = subop->instrs[op_id].data.len;
+
+	return end_off - start_off;
+}
+EXPORT_SYMBOL_GPL(nand_subop_get_data_len);
+
+/**
  * nand_reset - Reset and initialize a NAND device
  * @chip: The NAND chip
  * @chipnr: Internal die id
@@ -3940,7 +4815,7 @@ static void nand_set_defaults(struct nand_chip *chip)
 		chip->chip_delay = 20;
 
 	/* check, if a user supplied command function given */
-	if (chip->cmdfunc == NULL)
+	if (chip->cmdfunc == NULL && !chip->exec_op)
 		chip->cmdfunc = nand_command;
 
 	/* check, if a user supplied wait function given */
@@ -4823,15 +5698,35 @@ int nand_scan_ident(struct mtd_info *mtd, int maxchips,
 	if (!mtd->name && mtd->dev.parent)
 		mtd->name = dev_name(mtd->dev.parent);
 
-	if ((!chip->cmdfunc || !chip->select_chip) && !chip->cmd_ctrl) {
+	/*
+	 * ->cmdfunc() is legacy and will only be used if ->exec_op() is not
+	 * populated.
+	 */
+	if (chip->exec_op) {
 		/*
-		 * Default functions assigned for chip_select() and
-		 * cmdfunc() both expect cmd_ctrl() to be populated,
-		 * so we need to check that that's the case
+		 * The implementation of ->exec_op() heavily relies on timings
+		 * to be accessible through the nand_data_interface structure.
+		 * Thus, the ->setup_data_interface() hook must be provided. The
+		 * controller driver will be noticed of delays it must apply
+		 * after each ->exec_op() instruction (if any) through the
+		 * .delay_ns field. The driver will then choose to handle the
+		 * delays manually if the controller cannot do it natively.
 		 */
-		pr_err("chip.cmd_ctrl() callback is not provided");
-		return -EINVAL;
+		if (!chip->setup_data_interface) {
+			pr_err("->setup_data_interface() should be provided\n");
+			return -EINVAL;
+		}
+	} else {
+		/*
+		 * Default functions assigned for ->cmdfunc() and
+		 * ->select_chip() both expect ->cmd_ctrl() to be populated.
+		 */
+		if ((!chip->cmdfunc || !chip->select_chip) && !chip->cmd_ctrl) {
+			pr_err("->cmd_ctrl() should be provided\n");
+			return -EINVAL;
+		}
 	}
+
 	/* Set the default functions */
 	nand_set_defaults(chip);
 
diff --git a/drivers/mtd/nand/nand_hynix.c b/drivers/mtd/nand/nand_hynix.c
index 04e3ab7a476c..81c382f24513 100644
--- a/drivers/mtd/nand/nand_hynix.c
+++ b/drivers/mtd/nand/nand_hynix.c
@@ -74,19 +74,36 @@ static bool hynix_nand_has_valid_jedecid(struct nand_chip *chip)
 	return !strncmp("JEDEC", jedecid, sizeof(jedecid));
 }
 
+static int hynix_nand_cmd_op(struct nand_chip *chip, u8 cmd)
+{
+	struct mtd_info *mtd = nand_to_mtd(chip);
+
+	if (chip->exec_op) {
+		struct nand_op_instr instrs[] = {
+			NAND_OP_CMD(cmd, 0),
+		};
+		struct nand_operation op = NAND_OPERATION(instrs);
+
+		return nand_exec_op(chip, &op);
+	}
+
+	chip->cmdfunc(mtd, cmd, -1, -1);
+
+	return 0;
+}
+
 static int hynix_nand_setup_read_retry(struct mtd_info *mtd, int retry_mode)
 {
 	struct nand_chip *chip = mtd_to_nand(mtd);
 	struct hynix_nand *hynix = nand_get_manufacturer_data(chip);
 	const u8 *values;
-	int status;
 	int i;
 
 	values = hynix->read_retry->values +
 		 (retry_mode * hynix->read_retry->nregs);
 
 	/* Enter 'Set Hynix Parameters' mode */
-	chip->cmdfunc(mtd, NAND_HYNIX_CMD_SET_PARAMS, -1, -1);
+	hynix_nand_cmd_op(chip, NAND_HYNIX_CMD_SET_PARAMS);
 
 	/*
 	 * Configure the NAND in the requested read-retry mode.
@@ -101,16 +118,17 @@ static int hynix_nand_setup_read_retry(struct mtd_info *mtd, int retry_mode)
 		int column = hynix->read_retry->regs[i];
 
 		column |= column << 8;
-		chip->cmdfunc(mtd, NAND_CMD_NONE, column, -1);
-		chip->write_byte(mtd, values[i]);
+		if (chip->exec_op) {
+			nand_change_write_column_op(chip, column,
+						    &values[i], 1, true);
+		} else {
+			chip->cmdfunc(mtd, NAND_CMD_NONE, column, -1);
+			chip->write_byte(mtd, values[i]);
+		}
 	}
 
 	/* Apply the new settings. */
-	chip->cmdfunc(mtd, NAND_HYNIX_CMD_APPLY_PARAMS, -1, -1);
-
-	status = chip->waitfunc(mtd, chip);
-	if (status & NAND_STATUS_FAIL)
-		return -EIO;
+	hynix_nand_cmd_op(chip, NAND_HYNIX_CMD_APPLY_PARAMS);
 
 	return 0;
 }
@@ -173,32 +191,65 @@ static int hynix_read_rr_otp(struct nand_chip *chip,
 
 	nand_reset_op(chip);
 
-	chip->cmdfunc(mtd, NAND_HYNIX_CMD_SET_PARAMS, -1, -1);
+	hynix_nand_cmd_op(chip, NAND_HYNIX_CMD_SET_PARAMS);
 
 	for (i = 0; i < info->nregs; i++) {
 		int column = info->regs[i];
 
 		column |= column << 8;
-		chip->cmdfunc(mtd, NAND_CMD_NONE, column, -1);
-		chip->write_byte(mtd, info->values[i]);
+		if (chip->exec_op) {
+			nand_change_write_column_op(chip, column,
+						    &info->values[i], 1, true);
+		} else {
+			chip->cmdfunc(mtd, NAND_CMD_NONE, column, -1);
+			chip->write_byte(mtd, info->values[i]);
+		}
 	}
 
-	chip->cmdfunc(mtd, NAND_HYNIX_CMD_APPLY_PARAMS, -1, -1);
+	hynix_nand_cmd_op(chip, NAND_HYNIX_CMD_APPLY_PARAMS);
 
 	/* Sequence to enter OTP mode? */
-	chip->cmdfunc(mtd, 0x17, -1, -1);
-	chip->cmdfunc(mtd, 0x04, -1, -1);
-	chip->cmdfunc(mtd, 0x19, -1, -1);
+	if (chip->exec_op) {
+		struct nand_op_instr instrs[] = {
+			NAND_OP_CMD(0x17, 0),
+			NAND_OP_CMD(0x04, 0),
+			NAND_OP_CMD(0x19, 0),
+		};
+		struct nand_operation op = NAND_OPERATION(instrs);
+
+		nand_exec_op(chip, &op);
+	} else {
+		chip->cmdfunc(mtd, 0x17, -1, -1);
+		chip->cmdfunc(mtd, 0x04, -1, -1);
+		chip->cmdfunc(mtd, 0x19, -1, -1);
+	}
 
 	/* Now read the page */
 	nand_read_page_op(chip, info->page, 0, buf, info->size);
 
 	/* Put everything back to normal */
 	nand_reset_op(chip);
-	chip->cmdfunc(mtd, NAND_HYNIX_CMD_SET_PARAMS, 0x38, -1);
-	chip->write_byte(mtd, 0x0);
-	chip->cmdfunc(mtd, NAND_HYNIX_CMD_APPLY_PARAMS, -1, -1);
-	chip->cmdfunc(mtd, NAND_CMD_READ0, 0x0, -1);
+	if (chip->exec_op) {
+		const struct nand_sdr_timings *sdr =
+			nand_get_sdr_timings(&chip->data_interface);
+		u8 byte = 0;
+		u8 addr = 0x38;
+		struct nand_op_instr instrs[] = {
+			NAND_OP_CMD(NAND_HYNIX_CMD_SET_PARAMS, 0),
+			NAND_OP_ADDR(1, &addr, sdr->tCCS_min),
+			NAND_OP_8BIT_DATA_OUT(1, &byte, 0),
+			NAND_OP_CMD(NAND_HYNIX_CMD_APPLY_PARAMS, 0),
+			NAND_OP_CMD(NAND_CMD_READ0, 0),
+		};
+		struct nand_operation op = NAND_OPERATION(instrs);
+
+		nand_exec_op(chip, &op);
+	} else {
+		chip->cmdfunc(mtd, NAND_HYNIX_CMD_SET_PARAMS, 0x38, -1);
+		chip->write_byte(mtd, 0x0);
+		chip->cmdfunc(mtd, NAND_HYNIX_CMD_APPLY_PARAMS, -1, -1);
+		chip->cmdfunc(mtd, NAND_CMD_READ0, 0x0, -1);
+	}
 
 	return 0;
 }
diff --git a/drivers/mtd/nand/nand_micron.c b/drivers/mtd/nand/nand_micron.c
index 543352380ffa..109d8003e33d 100644
--- a/drivers/mtd/nand/nand_micron.c
+++ b/drivers/mtd/nand/nand_micron.c
@@ -117,14 +117,38 @@ micron_nand_read_page_on_die_ecc(struct mtd_info *mtd, struct nand_chip *chip,
 				 uint8_t *buf, int oob_required,
 				 int page)
 {
-	int status;
+	u8 status;
 	int max_bitflips = 0;
 
 	micron_nand_on_die_ecc_setup(chip, true);
 
-	chip->cmdfunc(mtd, NAND_CMD_READ0, 0x00, page);
-	chip->cmdfunc(mtd, NAND_CMD_STATUS, -1, -1);
-	status = chip->read_byte(mtd);
+	if (chip->exec_op) {
+		u8 addrs[5] = {};
+		struct nand_op_instr instrs[] = {
+			NAND_OP_CMD(NAND_CMD_READ0, 0),
+			NAND_OP_ADDR(4, addrs, 0),
+			NAND_OP_CMD(NAND_CMD_STATUS, 0),
+			NAND_OP_8BIT_DATA_IN(1, &status, 0),
+		};
+		struct nand_operation op = NAND_OPERATION(instrs);
+
+		if (nand_fill_column_cycles(chip, addrs, 0))
+			return -EINVAL;
+
+		addrs[2] = page;
+		addrs[3] = page >> 8;
+		if (chip->options & NAND_ROW_ADDR_3) {
+			addrs[4] = page >> 16;
+			instrs[1].addr.naddrs++;
+		}
+
+		nand_exec_op(chip, &op);
+	} else {
+		chip->cmdfunc(mtd, NAND_CMD_READ0, 0x00, page);
+		chip->cmdfunc(mtd, NAND_CMD_STATUS, -1, -1);
+		status = chip->read_byte(mtd);
+	}
+
 	if (status & NAND_STATUS_FAIL)
 		mtd->ecc_stats.failed++;
 	/*
diff --git a/include/linux/mtd/rawnand.h b/include/linux/mtd/rawnand.h
index 1acc26ed0e91..302f231df65e 100644
--- a/include/linux/mtd/rawnand.h
+++ b/include/linux/mtd/rawnand.h
@@ -751,6 +751,337 @@ struct nand_manufacturer_ops {
 };
 
 /**
+ * struct nand_op_cmd_instr - Definition of a command instruction
+ * @opcode: the command to assert in one cycle
+ */
+struct nand_op_cmd_instr {
+	u8 opcode;
+};
+
+/**
+ * struct nand_op_addr_instr - Definition of an address instruction
+ * @naddrs: length of the @addrs array
+ * @addrs: array containing the address cycles to assert
+ */
+struct nand_op_addr_instr {
+	unsigned int naddrs;
+	const u8 *addrs;
+};
+
+/**
+ * struct nand_op_data_instr - Definition of a data instruction
+ * @len: number of data bytes to move
+ * @in: buffer to fill when reading from the NAND chip
+ * @out: buffer to read from when writing to the NAND chip
+ * @force_8bit: force 8-bit access
+ *
+ * Please note that "in" and "out" are inverted from the ONFI specification
+ * and are from the controller perspective, so a "in" is a read from the NAND
+ * chip while a "out" is a write to the NAND chip.
+ */
+struct nand_op_data_instr {
+	unsigned int len;
+	union {
+		void *in;
+		const void *out;
+	};
+	bool force_8bit;
+};
+
+/**
+ * struct nand_op_waitrdy_instr - Definition of a wait ready instruction
+ * @timeout_ms: maximum delay while waiting for the ready/busy pin in ms
+ */
+struct nand_op_waitrdy_instr {
+	unsigned int timeout_ms;
+};
+
+/**
+ * enum nand_op_instr_type - Enumeration of all the instructions
+ *
+ * Please note that data instructions are separated into DATA_IN and DATA_OUT
+ * instructions.
+ */
+enum nand_op_instr_type {
+	NAND_OP_CMD_INSTR,
+	NAND_OP_ADDR_INSTR,
+	NAND_OP_DATA_IN_INSTR,
+	NAND_OP_DATA_OUT_INSTR,
+	NAND_OP_WAITRDY_INSTR,
+};
+
+/**
+ * struct nand_op_instr - Generic definition of and instruction
+ * @type: an enumeration of the instruction type
+ * @cmd/@addr/@data/@waitrdy: the actual instruction to refer depending on the
+ *			      value of @type
+ * @delay_ns: delay to apply by the controller after the instruction has been
+ *	      actually executed (most of them are directly handled by the
+ *	      controllers once the timings negociation has been done)
+ */
+struct nand_op_instr {
+	enum nand_op_instr_type type;
+	union {
+		struct nand_op_cmd_instr cmd;
+		struct nand_op_addr_instr addr;
+		struct nand_op_data_instr data;
+		struct nand_op_waitrdy_instr waitrdy;
+	};
+	unsigned int delay_ns;
+};
+
+/*
+ * Special handling must be done for the WAITRDY timeout parameter as it usually
+ * is either tPROG (after a prog), tR (before a read), tRST (during a reset) or
+ * tBERS (during an erase) which all of them are u64 values that cannot be
+ * divided by usual kernel macros and must be handled with the special
+ * DIV_ROUND_UP_ULL() macro.
+ */
+#define PSEC_TO_NSEC(x) DIV_ROUND_UP(x, 10^3)
+#define PSEC_TO_MSEC(x) DIV_ROUND_UP_ULL(x, 10^9)
+
+#define NAND_OP_CMD(id, delay_ps)					\
+	{								\
+		.type = NAND_OP_CMD_INSTR,				\
+		.cmd.opcode = id,					\
+		.delay_ns = PSEC_TO_NSEC(delay_ps),			\
+	}
+
+#define NAND_OP_ADDR(ncycles, cycles, delay_ps)				\
+	{								\
+		.type = NAND_OP_ADDR_INSTR,				\
+		.addr = {						\
+			.naddrs = ncycles,				\
+			.addrs = cycles,				\
+		},							\
+		.delay_ns = PSEC_TO_NSEC(delay_ps),			\
+	}
+
+#define NAND_OP_DATA_IN(l, buf, delay_ps)				\
+	{								\
+		.type = NAND_OP_DATA_IN_INSTR,				\
+		.data = {						\
+			.len = l,					\
+			.in = buf,					\
+			.force_8bit = false,				\
+		},							\
+		.delay_ns = PSEC_TO_NSEC(delay_ps),			\
+	}
+
+#define NAND_OP_DATA_OUT(l, buf, delay_ps)				\
+	{								\
+		.type = NAND_OP_DATA_OUT_INSTR,				\
+		.data = {						\
+			.len = l,					\
+			.out = buf,					\
+			.force_8bit = false,				\
+		},							\
+		.delay_ns = PSEC_TO_NSEC(delay_ps),			\
+	}
+
+#define NAND_OP_8BIT_DATA_IN(l, buf, delay_ps)				\
+	{								\
+		.type = NAND_OP_DATA_IN_INSTR,				\
+		.data = {						\
+			.len = l,					\
+			.in = buf,					\
+			.force_8bit = true,				\
+		},							\
+		.delay_ns = PSEC_TO_NSEC(delay_ps),			\
+	}
+
+#define NAND_OP_8BIT_DATA_OUT(l, buf, delay_ps)				\
+	{								\
+		.type = NAND_OP_DATA_OUT_INSTR,				\
+		.data = {						\
+			.len = l,					\
+			.out = buf,					\
+			.force_8bit = true,				\
+		},							\
+		.delay_ns = PSEC_TO_NSEC(delay_ps),			\
+	}
+
+#define NAND_OP_WAIT_RDY(tout_ps, delay_ps)				\
+	{								\
+		.type = NAND_OP_WAITRDY_INSTR,				\
+		.waitrdy.timeout_ms = PSEC_TO_MSEC(tout_ps),		\
+		.delay_ns = PSEC_TO_NSEC(delay_ps),			\
+	}
+
+/**
+ * struct nand_subop - a sub operation
+ * @instrs: array of instructions
+ * @ninstrs: length of the @instrs array
+ * @first_instr_start_off: offset to start from for the first instruction
+ *			   of the sub-operation
+ * @last_instr_end_off: offset to end at (excluded) for the last instruction
+ *			of the sub-operation
+ *
+ * When an operation cannot be handled as is by the NAND controller, it will
+ * be split by the parser and the remaining pieces will be handled as
+ * sub-operations.
+ */
+struct nand_subop {
+	const struct nand_op_instr *instrs;
+	unsigned int ninstrs;
+	/*
+	 * Specific offset for the first and the last instructions of the subop.
+	 * Applies for the address cycles in the case of address, or for data
+	 * offset in the case of data transfers. Otherwise, it is irrelevant.
+	 */
+	unsigned int first_instr_start_off;
+	unsigned int last_instr_end_off;
+};
+
+int nand_subop_get_addr_start_off(const struct nand_subop *subop,
+				  unsigned int op_id);
+int nand_subop_get_num_addr_cyc(const struct nand_subop *subop,
+				unsigned int op_id);
+int nand_subop_get_data_start_off(const struct nand_subop *subop,
+				  unsigned int op_id);
+int nand_subop_get_data_len(const struct nand_subop *subop,
+			    unsigned int op_id);
+
+/**
+ * struct nand_op_parser_addr_constraints - Constraints for address instructions
+ * @maxcycles: maximum number of cycles that the controller can assert by
+ *	       instruction
+ */
+struct nand_op_parser_addr_constraints {
+	unsigned int maxcycles;
+};
+
+/**
+ * struct nand_op_parser_data_constraints - Constraints for data instructions
+ * @maxlen: maximum data length that the controller can handle with one
+ *	    instruction
+ */
+struct nand_op_parser_data_constraints {
+	unsigned int maxlen;
+};
+
+/**
+ * struct nand_op_parser_pattern_elem - One element of a pattern
+ * @type: the instructuction type
+ * @optional: if this element of the pattern is optional or mandatory
+ * @addr/@data: address or data constraint (number of cycles or data length)
+ */
+struct nand_op_parser_pattern_elem {
+	enum nand_op_instr_type type;
+	bool optional;
+	union {
+		struct nand_op_parser_addr_constraints addr;
+		struct nand_op_parser_data_constraints data;
+	};
+};
+
+#define NAND_OP_PARSER_PAT_CMD_ELEM(_opt)			\
+	{							\
+		.type = NAND_OP_CMD_INSTR,			\
+		.optional = _opt,				\
+	}
+
+#define NAND_OP_PARSER_PAT_ADDR_ELEM(_opt, _maxcycles)		\
+	{							\
+		.type = NAND_OP_ADDR_INSTR,			\
+		.optional = _opt,				\
+		.addr.maxcycles = _maxcycles,			\
+	}
+
+#define NAND_OP_PARSER_PAT_DATA_IN_ELEM(_opt, _maxlen)		\
+	{							\
+		.type = NAND_OP_DATA_IN_INSTR,			\
+		.optional = _opt,				\
+		.data.maxlen = _maxlen,				\
+	}
+
+#define NAND_OP_PARSER_PAT_DATA_OUT_ELEM(_opt, _maxlen)		\
+	{							\
+		.type = NAND_OP_DATA_OUT_INSTR,			\
+		.optional = _opt,				\
+		.data.maxlen = _maxlen,				\
+	}
+
+#define NAND_OP_PARSER_PAT_WAITRDY_ELEM(_opt)			\
+	{							\
+		.type = NAND_OP_WAITRDY_INSTR,			\
+		.optional = _opt,				\
+	}
+
+/**
+ * struct nand_op_parser_pattern - A complete pattern
+ * @elems: array of pattern elements
+ * @nelems: number of pattern elements in @elems array
+ * @exec: the function that will actually execute this pattern, written in the
+ *	  controller driver
+ *
+ * This is a complete pattern that is a list of elements, each one reprensenting
+ * one instruction with its constraints. Controller drivers must declare as much
+ * patterns as they support and give the list of the supported patterns (created
+ * with the help of the following macro) at the call to nand_op_parser_exec_op
+ * which shall be the main thing to do in the driver implementation of
+ * ->exec_op(). Once there is a match between the pattern and an operation, the
+ * core calls the @exec function to actually do the operation.
+ */
+struct nand_op_parser_pattern {
+	const struct nand_op_parser_pattern_elem *elems;
+	unsigned int nelems;
+	int (*exec)(struct nand_chip *chip, const struct nand_subop *subop);
+};
+
+#define NAND_OP_PARSER_PATTERN(_exec, ...)							\
+	{											\
+		.exec = _exec,									\
+		.elems = (struct nand_op_parser_pattern_elem[]) { __VA_ARGS__ },		\
+		.nelems = sizeof((struct nand_op_parser_pattern_elem[]) { __VA_ARGS__ }) /	\
+			  sizeof(struct nand_op_parser_pattern_elem),				\
+	}
+
+/**
+ * struct nand_op_parser - The actual parser
+ * @patterns: array of patterns
+ * @npatterns: length of the @patterns array
+ *
+ * The actual parser structure wich is an array of supported patterns.
+ */
+struct nand_op_parser {
+	const struct nand_op_parser_pattern *patterns;
+	unsigned int npatterns;
+};
+
+#define NAND_OP_PARSER(...)									\
+	{											\
+		.patterns = (struct nand_op_parser_pattern[]) { __VA_ARGS__ },			\
+		.npatterns = sizeof((struct nand_op_parser_pattern[]) { __VA_ARGS__ }) /	\
+			     sizeof(struct nand_op_parser_pattern),				\
+	}
+
+/**
+ * struct nand_operation - The actual operation
+ * @instrs: array of instructions to execute
+ * @ninstrs: length of the @instrs array
+ *
+ * The actual operation structure that will be given to the parser.
+ */
+struct nand_operation {
+	const struct nand_op_instr *instrs;
+	unsigned int ninstrs;
+};
+
+#define NAND_OPERATION(_instrs)					\
+	{							\
+		.instrs = _instrs,				\
+		.ninstrs = ARRAY_SIZE(_instrs),			\
+	}
+
+int nand_fill_column_cycles(struct nand_chip *chip, u8 *addrs,
+			    unsigned int offset_in_page);
+
+int nand_op_parser_exec_op(struct nand_chip *chip,
+			   const struct nand_op_parser *parser,
+			   const struct nand_operation *op, bool check_only);
+
+/**
  * struct nand_chip - NAND Private Flash Chip Data
  * @mtd:		MTD device registered to the MTD framework
  * @IO_ADDR_R:		[BOARDSPECIFIC] address to read the 8 I/O lines of the
@@ -885,6 +1216,9 @@ struct nand_chip {
 	int (*setup_data_interface)(struct mtd_info *mtd, int chipnr,
 				    const struct nand_data_interface *conf);
 
+	int (*exec_op)(struct nand_chip *chip,
+		       const struct nand_operation *op,
+		       bool check_only);
 
 	int chip_delay;
 	unsigned int options;
@@ -945,6 +1279,15 @@ struct nand_chip {
 	} manufacturer;
 };
 
+static inline int nand_exec_op(struct nand_chip *chip,
+			       const struct nand_operation *op)
+{
+	if (!chip->exec_op)
+		return -ENOTSUPP;
+
+	return chip->exec_op(chip, op, false);
+}
+
 extern const struct mtd_ooblayout_ops nand_ooblayout_sp_ops;
 extern const struct mtd_ooblayout_ops nand_ooblayout_lp_ops;
 
@@ -1307,23 +1650,26 @@ int nand_readid_op(struct nand_chip *chip, u8 addr, void *buf,
 int nand_status_op(struct nand_chip *chip, u8 *status);
 int nand_erase_op(struct nand_chip *chip, unsigned int eraseblock);
 int nand_read_page_op(struct nand_chip *chip, unsigned int page,
-		      unsigned int column, void *buf, unsigned int len);
-int nand_change_read_column_op(struct nand_chip *chip, unsigned int column,
-			       void *buf, unsigned int len, bool force_8bit);
+		      unsigned int offset_in_page, void *buf, unsigned int len);
+int nand_change_read_column_op(struct nand_chip *chip,
+			       unsigned int offset_in_page, void *buf,
+			       unsigned int len, bool force_8bit);
 int nand_read_oob_op(struct nand_chip *chip, unsigned int page,
-		     unsigned int column, void *buf, unsigned int len);
+		     unsigned int offset_in_page, void *buf, unsigned int len);
 int nand_prog_page_begin_op(struct nand_chip *chip, unsigned int page,
-			    unsigned int column, const void *buf,
+			    unsigned int offset_in_page, const void *buf,
 			    unsigned int len);
 int nand_prog_page_end_op(struct nand_chip *chip);
 int nand_prog_page_op(struct nand_chip *chip, unsigned int page,
-		      unsigned int column, const void *buf, unsigned int len);
-int nand_change_write_column_op(struct nand_chip *chip, unsigned int column,
-				const void *buf, unsigned int len, bool force_8bit);
+		      unsigned int offset_in_page, const void *buf,
+		      unsigned int len);
+int nand_change_write_column_op(struct nand_chip *chip,
+				unsigned int offset_in_page, const void *buf,
+				unsigned int len, bool force_8bit);
 int nand_read_data_op(struct nand_chip *chip, void *buf, unsigned int len,
-		      bool force_8bits);
+		      bool force_8bit);
 int nand_write_data_op(struct nand_chip *chip, const void *buf,
-		       unsigned int len, bool force_8bits);
+		       unsigned int len, bool force_8bit);
 
 /* Free resources held by the NAND device */
 void nand_cleanup(struct nand_chip *chip);
-- 
2.11.0

^ permalink raw reply related	[flat|nested] 75+ messages in thread

* [RFC 05/12] dt-bindings: mtd: add Marvell NAND controller documentation
  2017-10-18 14:36 ` Miquel Raynal
  (?)
@ 2017-10-18 14:36   ` Miquel Raynal
  -1 siblings, 0 replies; 75+ messages in thread
From: Miquel Raynal @ 2017-10-18 14:36 UTC (permalink / raw)
  To: Andrew Lunn, bcm-kernel-feedback-list, Boris Brezillon,
	Brian Norris, Catalin Marinas, Chen-Yu Tsai, Cyrille Pitchen,
	Daniel Mack, David Woodhouse, devel, devicetree, Ezequiel Garcia,
	Greg Kroah-Hartman, Gregory Clement, Han Xu, Haojian Zhuang,
	Jason Cooper, Josh Wu, Kamal Dasu, linux-arm-kernel,
	linux-kernel, linux-mediatek, linux-mtd, Marc Gonzalez,
	Marek Vasut, Mark Rutland, Masahiro Yamada, Matthias Brugger,
	Maxime Ripard, Maxim Levitsky, Richard Weinberger,
	Robert Jarzmik, Rob Herring, Russell King, Sebastian Hesselbarth,
	Stefan Agner, Sylvain Lemieux, Vladimir Zapolskiy, Wenyou Yang,
	Will Deacon
  Cc: Thomas Petazzoni, Antoine Tenart, Miquel Raynal, Igor Grinberg,
	Nadav Haklai, Ofer Heifetz, Neta Zur Hershkovits, Hanna Hawa

Document the bindings for the legacy and the new bindings relative to
Marvell NAND controller driver rework.

Signed-off-by: Miquel Raynal <miquel.raynal@free-electrons.com>
---
 .../devicetree/bindings/mtd/marvell-nand.txt       | 95 ++++++++++++++++++++++
 1 file changed, 95 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/mtd/marvell-nand.txt

diff --git a/Documentation/devicetree/bindings/mtd/marvell-nand.txt b/Documentation/devicetree/bindings/mtd/marvell-nand.txt
new file mode 100644
index 000000000000..ea99f426c03f
--- /dev/null
+++ b/Documentation/devicetree/bindings/mtd/marvell-nand.txt
@@ -0,0 +1,95 @@
+Marvell NAND Flash Controller (NFC)
+
+Required properties:
+C'est faux, t'en a rajouté un y a pas longtps :).
+Je conseille de mettre ça sous forme de liste, genre
+
+- compatible: can be one of the following:
+    * "marvell,armada-8k-nand-controller"
+    * "marvell,armada370-nand-controller"
+    * "marvell,pxa3xx-nand-controller"
+    * "marvell,armada-8k-nand" (deprecated)
+    * "marvell,armada370-nand" (deprecated)
+    * "marvell,pxa3xx-nand" (deprecated)
+- reg: shall contain registers location and length for data and reg.
+- #address-cells: shall be set to 1. Encode the nand CS.
+- #size-cells: shall be set to 0.
+- interrupts: shall define the nand controller interrupt.
+- clocks: shall reference nand controller clocks.
+- marvell,system-controller: Set to retrieve the syscon node that handles
+  NAND controller related registers (only required with the
+  "marvell,armada-8k-nand[-controller]" compatibles).
+
+Optional properties:
+- dmas: shall reference DMA channel associated to the NAND controller.
+- dma-names: shall be "rxtx".
+
+Optional children nodes:
+Children nodes represent the available NAND chips.
+
+Required properties:
+- reg: shall contain the native Chip Select ids (0-3)
+- marvell,rb: shall contain the native Ready/Busy ids (0-1)
+
+Optional properties:
+- marvell,nand-keep-config: orders the driver not to take the timings
+  from the core and leaving them completely untouched. Bootloader
+  timings will then be used.
+- marvell,nand-enable-arbiter: only useful for PXA platforms, will
+  enable bus arbiter between NFC and DFI bus (must be enabled for
+  NFC operation)
+- nand-on-flash-bbt: speed up the boot process by not discovering all
+  the bad blocks at each boot and reading directly an on flash table.
+- nand-ecc-mode: one of the supported ECC modes ("none", "soft",
+  "hw"). If not specified, hardware ECC will be used.
+- nand-ecc-algo: algorithm to use if previous choice was "soft"
+  ("hamming" or "bch). This property may be added for hardware ECC for
+  clarification but will be ignored by the driver because ECC mode is
+  chosen depending on the page size and the strength required by the
+  NAND chip. This value may be overwritten with the nand-ecc-strength
+  property.
+- nand-ecc-strength: desired ECC strength.
+- nand-ecc-step-size: indication on the ECC step size. This has no
+  effect and will be ignored by the driver when using hardware
+  ECC. Because Marvell's NAND flash controller does use fixed strength
+  (1-bit for Hamming, 16-bit for BCH), the step size will shrink or
+  grown in order to fit the required strength and the value
+  updated. Step sizes are not completely random for all and follow
+  certain patterns described in AN-379, "Marvell SoC NFC ECC".
+
+See Documentation/devicetree/bindings/mtd/nand.txt for more details on
+generic bindings.
+
+
+Example:
+nand_controller: nand-controller@d0000 {
+	compatible = "marvell,armada370-nand-controller";
+	reg = <0xd0000 0x54>;
+	#address-cells = <1>;
+	#size-cells = <0>;
+	interrupts = <GIC_SPI 84 IRQ_TYPE_LEVEL_HIGH>;
+	clocks = <&coredivclk 0>;
+	status = "okay";
+
+	nand@0 {
+		reg = <0>;
+		marvell,rb = <0>;
+		nand-ecc-mode = "hw";
+		marvell,nand-keep-config;
+		marvell,nand-enable-arbiter;
+		nand-on-flash-bbt;
+		nand-ecc-strength = <4>;
+		nand-ecc-step-size = <512>;
+
+		partitions {
+			compatible = "fixed-partitions";
+			#address-cells = <1>;
+			#size-cells = <1>;
+
+			partition@0 {
+				label = "Rootfs";
+				reg = <0x00000000 0x40000000>;
+			};
+		};
+	};
+};
-- 
2.11.0

^ permalink raw reply related	[flat|nested] 75+ messages in thread

* [RFC 05/12] dt-bindings: mtd: add Marvell NAND controller documentation
@ 2017-10-18 14:36   ` Miquel Raynal
  0 siblings, 0 replies; 75+ messages in thread
From: Miquel Raynal @ 2017-10-18 14:36 UTC (permalink / raw)
  To: Andrew Lunn, bcm-kernel-feedback-list, Boris Brezillon,
	Brian Norris, Catalin Marinas, Chen-Yu Tsai, Cyrille Pitchen,
	Daniel Mack, David Woodhouse, devel, devicetree, Ezequiel Garcia,
	Greg Kroah-Hartman, Gregory Clement, Han Xu, Haojian Zhuang,
	Jason Cooper, Josh Wu, Kamal Dasu, linux-arm-kernel,
	linux-kernel, linux-mediatek, linux-mtd
  Cc: Hanna Hawa, Antoine Tenart, Nadav Haklai, Neta Zur Hershkovits,
	Igor Grinberg, Miquel Raynal, Ofer Heifetz

Document the bindings for the legacy and the new bindings relative to
Marvell NAND controller driver rework.

Signed-off-by: Miquel Raynal <miquel.raynal@free-electrons.com>
---
 .../devicetree/bindings/mtd/marvell-nand.txt       | 95 ++++++++++++++++++++++
 1 file changed, 95 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/mtd/marvell-nand.txt

diff --git a/Documentation/devicetree/bindings/mtd/marvell-nand.txt b/Documentation/devicetree/bindings/mtd/marvell-nand.txt
new file mode 100644
index 000000000000..ea99f426c03f
--- /dev/null
+++ b/Documentation/devicetree/bindings/mtd/marvell-nand.txt
@@ -0,0 +1,95 @@
+Marvell NAND Flash Controller (NFC)
+
+Required properties:
+C'est faux, t'en a rajouté un y a pas longtps :).
+Je conseille de mettre ça sous forme de liste, genre
+
+- compatible: can be one of the following:
+    * "marvell,armada-8k-nand-controller"
+    * "marvell,armada370-nand-controller"
+    * "marvell,pxa3xx-nand-controller"
+    * "marvell,armada-8k-nand" (deprecated)
+    * "marvell,armada370-nand" (deprecated)
+    * "marvell,pxa3xx-nand" (deprecated)
+- reg: shall contain registers location and length for data and reg.
+- #address-cells: shall be set to 1. Encode the nand CS.
+- #size-cells: shall be set to 0.
+- interrupts: shall define the nand controller interrupt.
+- clocks: shall reference nand controller clocks.
+- marvell,system-controller: Set to retrieve the syscon node that handles
+  NAND controller related registers (only required with the
+  "marvell,armada-8k-nand[-controller]" compatibles).
+
+Optional properties:
+- dmas: shall reference DMA channel associated to the NAND controller.
+- dma-names: shall be "rxtx".
+
+Optional children nodes:
+Children nodes represent the available NAND chips.
+
+Required properties:
+- reg: shall contain the native Chip Select ids (0-3)
+- marvell,rb: shall contain the native Ready/Busy ids (0-1)
+
+Optional properties:
+- marvell,nand-keep-config: orders the driver not to take the timings
+  from the core and leaving them completely untouched. Bootloader
+  timings will then be used.
+- marvell,nand-enable-arbiter: only useful for PXA platforms, will
+  enable bus arbiter between NFC and DFI bus (must be enabled for
+  NFC operation)
+- nand-on-flash-bbt: speed up the boot process by not discovering all
+  the bad blocks at each boot and reading directly an on flash table.
+- nand-ecc-mode: one of the supported ECC modes ("none", "soft",
+  "hw"). If not specified, hardware ECC will be used.
+- nand-ecc-algo: algorithm to use if previous choice was "soft"
+  ("hamming" or "bch). This property may be added for hardware ECC for
+  clarification but will be ignored by the driver because ECC mode is
+  chosen depending on the page size and the strength required by the
+  NAND chip. This value may be overwritten with the nand-ecc-strength
+  property.
+- nand-ecc-strength: desired ECC strength.
+- nand-ecc-step-size: indication on the ECC step size. This has no
+  effect and will be ignored by the driver when using hardware
+  ECC. Because Marvell's NAND flash controller does use fixed strength
+  (1-bit for Hamming, 16-bit for BCH), the step size will shrink or
+  grown in order to fit the required strength and the value
+  updated. Step sizes are not completely random for all and follow
+  certain patterns described in AN-379, "Marvell SoC NFC ECC".
+
+See Documentation/devicetree/bindings/mtd/nand.txt for more details on
+generic bindings.
+
+
+Example:
+nand_controller: nand-controller@d0000 {
+	compatible = "marvell,armada370-nand-controller";
+	reg = <0xd0000 0x54>;
+	#address-cells = <1>;
+	#size-cells = <0>;
+	interrupts = <GIC_SPI 84 IRQ_TYPE_LEVEL_HIGH>;
+	clocks = <&coredivclk 0>;
+	status = "okay";
+
+	nand@0 {
+		reg = <0>;
+		marvell,rb = <0>;
+		nand-ecc-mode = "hw";
+		marvell,nand-keep-config;
+		marvell,nand-enable-arbiter;
+		nand-on-flash-bbt;
+		nand-ecc-strength = <4>;
+		nand-ecc-step-size = <512>;
+
+		partitions {
+			compatible = "fixed-partitions";
+			#address-cells = <1>;
+			#size-cells = <1>;
+
+			partition@0 {
+				label = "Rootfs";
+				reg = <0x00000000 0x40000000>;
+			};
+		};
+	};
+};
-- 
2.11.0

_______________________________________________
devel mailing list
devel@linuxdriverproject.org
http://driverdev.linuxdriverproject.org/mailman/listinfo/driverdev-devel

^ permalink raw reply related	[flat|nested] 75+ messages in thread

* [RFC 05/12] dt-bindings: mtd: add Marvell NAND controller documentation
@ 2017-10-18 14:36   ` Miquel Raynal
  0 siblings, 0 replies; 75+ messages in thread
From: Miquel Raynal @ 2017-10-18 14:36 UTC (permalink / raw)
  To: linux-arm-kernel

Document the bindings for the legacy and the new bindings relative to
Marvell NAND controller driver rework.

Signed-off-by: Miquel Raynal <miquel.raynal@free-electrons.com>
---
 .../devicetree/bindings/mtd/marvell-nand.txt       | 95 ++++++++++++++++++++++
 1 file changed, 95 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/mtd/marvell-nand.txt

diff --git a/Documentation/devicetree/bindings/mtd/marvell-nand.txt b/Documentation/devicetree/bindings/mtd/marvell-nand.txt
new file mode 100644
index 000000000000..ea99f426c03f
--- /dev/null
+++ b/Documentation/devicetree/bindings/mtd/marvell-nand.txt
@@ -0,0 +1,95 @@
+Marvell NAND Flash Controller (NFC)
+
+Required properties:
+C'est faux, t'en a rajout? un y a pas longtps :).
+Je conseille de mettre ?a sous forme de liste, genre
+
+- compatible: can be one of the following:
+    * "marvell,armada-8k-nand-controller"
+    * "marvell,armada370-nand-controller"
+    * "marvell,pxa3xx-nand-controller"
+    * "marvell,armada-8k-nand" (deprecated)
+    * "marvell,armada370-nand" (deprecated)
+    * "marvell,pxa3xx-nand" (deprecated)
+- reg: shall contain registers location and length for data and reg.
+- #address-cells: shall be set to 1. Encode the nand CS.
+- #size-cells: shall be set to 0.
+- interrupts: shall define the nand controller interrupt.
+- clocks: shall reference nand controller clocks.
+- marvell,system-controller: Set to retrieve the syscon node that handles
+  NAND controller related registers (only required with the
+  "marvell,armada-8k-nand[-controller]" compatibles).
+
+Optional properties:
+- dmas: shall reference DMA channel associated to the NAND controller.
+- dma-names: shall be "rxtx".
+
+Optional children nodes:
+Children nodes represent the available NAND chips.
+
+Required properties:
+- reg: shall contain the native Chip Select ids (0-3)
+- marvell,rb: shall contain the native Ready/Busy ids (0-1)
+
+Optional properties:
+- marvell,nand-keep-config: orders the driver not to take the timings
+  from the core and leaving them completely untouched. Bootloader
+  timings will then be used.
+- marvell,nand-enable-arbiter: only useful for PXA platforms, will
+  enable bus arbiter between NFC and DFI bus (must be enabled for
+  NFC operation)
+- nand-on-flash-bbt: speed up the boot process by not discovering all
+  the bad blocks at each boot and reading directly an on flash table.
+- nand-ecc-mode: one of the supported ECC modes ("none", "soft",
+  "hw"). If not specified, hardware ECC will be used.
+- nand-ecc-algo: algorithm to use if previous choice was "soft"
+  ("hamming" or "bch). This property may be added for hardware ECC for
+  clarification but will be ignored by the driver because ECC mode is
+  chosen depending on the page size and the strength required by the
+  NAND chip. This value may be overwritten with the nand-ecc-strength
+  property.
+- nand-ecc-strength: desired ECC strength.
+- nand-ecc-step-size: indication on the ECC step size. This has no
+  effect and will be ignored by the driver when using hardware
+  ECC. Because Marvell's NAND flash controller does use fixed strength
+  (1-bit for Hamming, 16-bit for BCH), the step size will shrink or
+  grown in order to fit the required strength and the value
+  updated. Step sizes are not completely random for all and follow
+  certain patterns described in AN-379, "Marvell SoC NFC ECC".
+
+See Documentation/devicetree/bindings/mtd/nand.txt for more details on
+generic bindings.
+
+
+Example:
+nand_controller: nand-controller at d0000 {
+	compatible = "marvell,armada370-nand-controller";
+	reg = <0xd0000 0x54>;
+	#address-cells = <1>;
+	#size-cells = <0>;
+	interrupts = <GIC_SPI 84 IRQ_TYPE_LEVEL_HIGH>;
+	clocks = <&coredivclk 0>;
+	status = "okay";
+
+	nand at 0 {
+		reg = <0>;
+		marvell,rb = <0>;
+		nand-ecc-mode = "hw";
+		marvell,nand-keep-config;
+		marvell,nand-enable-arbiter;
+		nand-on-flash-bbt;
+		nand-ecc-strength = <4>;
+		nand-ecc-step-size = <512>;
+
+		partitions {
+			compatible = "fixed-partitions";
+			#address-cells = <1>;
+			#size-cells = <1>;
+
+			partition at 0 {
+				label = "Rootfs";
+				reg = <0x00000000 0x40000000>;
+			};
+		};
+	};
+};
-- 
2.11.0

^ permalink raw reply related	[flat|nested] 75+ messages in thread

* [RFC 06/12] mtd: nand: add reworked Marvell NAND controller driver
  2017-10-18 14:36 ` Miquel Raynal
  (?)
@ 2017-10-18 14:36   ` Miquel Raynal
  -1 siblings, 0 replies; 75+ messages in thread
From: Miquel Raynal @ 2017-10-18 14:36 UTC (permalink / raw)
  To: Andrew Lunn, bcm-kernel-feedback-list, Boris Brezillon,
	Brian Norris, Catalin Marinas, Chen-Yu Tsai, Cyrille Pitchen,
	Daniel Mack, David Woodhouse, devel, devicetree, Ezequiel Garcia,
	Greg Kroah-Hartman, Gregory Clement, Han Xu, Haojian Zhuang,
	Jason Cooper, Josh Wu, Kamal Dasu, linux-arm-kernel,
	linux-kernel, linux-mediatek, linux-mtd, Marc Gonzalez,
	Marek Vasut, Mark Rutland, Masahiro Yamada, Matthias Brugger,
	Maxime Ripard, Maxim Levitsky, Richard Weinberger,
	Robert Jarzmik, Rob Herring, Russell King, Sebastian Hesselbarth,
	Stefan Agner, Sylvain Lemieux, Vladimir Zapolskiy, Wenyou Yang,
	Will Deacon
  Cc: Thomas Petazzoni, Antoine Tenart, Miquel Raynal, Igor Grinberg,
	Nadav Haklai, Ofer Heifetz, Neta Zur Hershkovits, Hanna Hawa

Add marvell_nand driver which aims at replacing the existing pxa3xx_nand
driver.

The new driver intends to be easier to understand and follows the brand
new NAND framework rules by implementing hooks for every pattern the
controller might support and referencing them inside a parser object
that will be given to the core at each ->exec_op() call.

Raw accessors are implemented, useful to test/debug memory/filesystem
corruptions. Userspace binaries contained in the mtd-utils package may
now be used and their output trusted.

Timings may not be kept from the bootloader anymore, the timings used
for instance in U-Boot were not optimal and it supposed to have NAND
support (and initialized) in the bootloader.

Thanks to the improved timings, implementation of ONFI mode 5 support
(with EDO managed by adding a delay on data sampling), merging the
commands together and optimizing writes in the command registers, the
new driver may achieve faster throughputs in both directions.
Measurements show an improvement of about +23% read throughput and +24%
write throughput. These measurements have been done with an
Armada-385-DB-AP (4kiB NAND pages forced in 4-bit strength BCH ECC
correction) using the userspace tool 'flash_speed' from the MTD test
suite.

Besides these important topics, the new driver addresses several
unsolved known issues in the old driver which:
- did not work with ECC soft neither with ECC none ;
- relied on naked read/write (which is unchanged) while the NFCv1
  embedded in the pxa3xx platforms do not implement it, so several
  NAND commands did not actually ever work without any notice (like
  reading the ONFI PARAM_PAGE or SET/GET_FEATURES) ;
- wrote the OOB data correctly, but was not able to read it correctly
  past the first OOB data chunk ;
- did not displayed ECC bytes ;
- used device tree bindings that did not allow more than one NAND chip,
  and did not allow to choose the correct chip select if not
  incrementing from 0. Plus, the Ready/Busy line used had to be 0.

Old device tree bindings are still supported but deprecated. A more
hierarchical view has to be used to keep the controller and the NAND
chip structures clearly separated both inside the device tree and also
in the driver code.

Signed-off-by: Miquel Raynal <miquel.raynal@free-electrons.com>
---
 drivers/mtd/nand/Kconfig        |   11 +
 drivers/mtd/nand/Makefile       |    1 +
 drivers/mtd/nand/marvell_nand.c | 2384 +++++++++++++++++++++++++++++++++++++++
 3 files changed, 2396 insertions(+)
 create mode 100644 drivers/mtd/nand/marvell_nand.c

diff --git a/drivers/mtd/nand/Kconfig b/drivers/mtd/nand/Kconfig
index bb48aafed9a2..8a64a18b9d26 100644
--- a/drivers/mtd/nand/Kconfig
+++ b/drivers/mtd/nand/Kconfig
@@ -323,6 +323,17 @@ config MTD_NAND_PXA3xx
 	  platforms (XP, 370, 375, 38x, 39x) and 64-bit Armada
 	  platforms (7K, 8K) (NFCv2).
 
+config MTD_NAND_MARVELL
+	tristate "NAND controller support on Marvell boards"
+	depends on PXA3xx || ARCH_MMP || PLAT_ORION || ARCH_MVEBU || \
+		   (COMPILE_TEST && HAS_IOMEM)
+	help
+	  This enables the NAND flash controller driver for Marvell boards,
+	  including:
+	  - PXA3xx processors (NFCv1)
+	  - 32-bit Armada platforms (XP, 37x, 38x, 39x) (NFCv2)
+	  - 64-bit Aramda platforms (7k, 8k) (NFCv2)
+
 config MTD_NAND_SLC_LPC32XX
 	tristate "NXP LPC32xx SLC Controller"
 	depends on ARCH_LPC32XX
diff --git a/drivers/mtd/nand/Makefile b/drivers/mtd/nand/Makefile
index ade5fc4c3819..828332dfe18c 100644
--- a/drivers/mtd/nand/Makefile
+++ b/drivers/mtd/nand/Makefile
@@ -31,6 +31,7 @@ obj-$(CONFIG_MTD_NAND_OMAP2) 		+= omap2_nand.o
 obj-$(CONFIG_MTD_NAND_OMAP_BCH_BUILD)	+= omap_elm.o
 obj-$(CONFIG_MTD_NAND_CM_X270)		+= cmx270_nand.o
 obj-$(CONFIG_MTD_NAND_PXA3xx)		+= pxa3xx_nand.o
+obj-$(CONFIG_MTD_NAND_MARVELL)		+= marvell_nand.o
 obj-$(CONFIG_MTD_NAND_TMIO)		+= tmio_nand.o
 obj-$(CONFIG_MTD_NAND_PLATFORM)		+= plat_nand.o
 obj-$(CONFIG_MTD_NAND_PASEMI)		+= pasemi_nand.o
diff --git a/drivers/mtd/nand/marvell_nand.c b/drivers/mtd/nand/marvell_nand.c
new file mode 100644
index 000000000000..557817a6f4b6
--- /dev/null
+++ b/drivers/mtd/nand/marvell_nand.c
@@ -0,0 +1,2384 @@
+/*
+ * Marvell NAND flash controller driver
+ *
+ * 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.
+ *
+ * Copyright (C) 2017 Marvell
+ * Author: Miquel RAYNAL <miquel.raynal@free-electrons.com>
+ */
+
+#include <linux/module.h>
+#include <linux/clk.h>
+#include <linux/mtd/rawnand.h>
+#include <linux/of_platform.h>
+#include <linux/iopoll.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+#include <linux/mfd/syscon.h>
+#include <linux/regmap.h>
+#include <asm/unaligned.h>
+
+/* Data FIFO granularity, FIFO reads/writes must be a multiple of this length */
+#define FIFO_DEPTH		8
+#define FIFO_REP(x)		(x / sizeof(u32))
+#define FIFO_DEPTH_32		FIFO_REP(FIFO_DEPTH)
+/* NFCv2 does not support transfers of larger chunks at a time */
+#define MAX_CHUNK_SIZE		2112
+/* Polling is done at a pace of POLL_PERIOD us until POLL_TIMEOUT is reached */
+#define POLL_PERIOD		0
+#define POLL_TIMEOUT		100000
+/* Interrupt maximum wait period in ms */
+#define IRQ_TIMEOUT		1000
+/* Latency in clock cycles between SoC pins and NFC logic */
+#define MIN_RD_DEL_CNT		3
+/* Maximum number of contiguous address cycles */
+#define MAX_ADDRESS_CYC		7
+/* System control register and bit to enable NAND on some SoCs */
+#define GENCONF_SOC_DEVICE_MUX	0x208
+#define GENCONF_SOC_DEVICE_MUX_NFC_EN BIT(0)
+
+/* NAND controller data flash control register */
+#define NDCR			0x00
+/* NAND interface timing parameter 0 register */
+#define NDTR0			0x04
+/* NAND interface timing parameter 1 register */
+#define NDTR1			0x0C
+/* NAND controller status register */
+#define NDSR			0x14
+/* NAND ECC control register */
+#define NDECCCTRL		0x28
+/* NAND controller data buffer register */
+#define NDDB			0x40
+/* NAND controller command buffer 0 register */
+#define NDCB0			0x48
+/* NAND controller command buffer 1 register */
+#define NDCB1			0x4C
+/* NAND controller command buffer 2 register */
+#define NDCB2			0x50
+/* NAND controller command buffer 3 register */
+#define NDCB3			0x54
+
+/* Data flash control register bitfields */
+#define NDCR_ALL_INT		GENMASK(11, 0)
+#define NDCR_CS1_CMDDM		BIT(7)
+#define NDCR_CS0_CMDDM		BIT(8)
+#define NDCR_RDYM		BIT(11)
+#define NDCR_ND_ARB_EN		BIT(12)
+#define NDCR_RA_START		BIT(15)
+#define NDCR_RD_ID_CNT(x)	((x & 0x7) << 16)
+#define NDCR_PAGE_SZ(x)		(x >= 2048 ? BIT(24) : 0)
+#define NDCR_DWIDTH_M		BIT(26)
+#define NDCR_DWIDTH_C		BIT(27)
+#define NDCR_ND_RUN		BIT(28)
+#define NDCR_ECC_EN		BIT(30)
+#define NDCR_SPARE_EN		BIT(31)
+
+/* NAND interface timing parameter registers bitfields */
+#define NDTR0_TRP(x)		((min_t(unsigned int, x, 0xF) & 0x7) << 0)
+#define NDTR0_TRH(x)		(min_t(unsigned int, x, 0x7) << 3)
+#define NDTR0_ETRP(x)		((min_t(unsigned int, x, 0xF) & 0x8) << 3)
+#define NDTR0_SEL_NRE_EDGE	BIT(7)
+#define NDTR0_TWP(x)		(min_t(unsigned int, x, 0x7) << 8)
+#define NDTR0_TWH(x)		(min_t(unsigned int, x, 0x7) << 11)
+#define NDTR0_TCS(x)		(min_t(unsigned int, x, 0x7) << 16)
+#define NDTR0_TCH(x)		(min_t(unsigned int, x, 0x7) << 19)
+#define NDTR0_RD_CNT_DEL(x)	(min_t(unsigned int, x, 0xF) << 22)
+#define NDTR0_SELCNTR		BIT(26)
+#define NDTR0_TADL(x)		(min_t(unsigned int, x, 0x1F) << 27)
+
+#define NDTR1_TAR(x)		(min_t(unsigned int, x, 0xF) << 0)
+#define NDTR1_TWHR(x)		(min_t(unsigned int, x, 0xF) << 4)
+#define NDTR1_TRHW(x)		(min_t(unsigned int, x / 16, 0x3) << 8)
+#define NDTR1_PRESCALE		BIT(14)
+#define NDTR1_WAIT_MODE		BIT(15)
+#define NDTR1_TR(x)		(min_t(unsigned int, x, 0xFFFF) << 16)
+
+/* NAND controller status register bitfields */
+#define NDSR_WRCMDREQ		BIT(0)
+#define NDSR_RDDREQ		BIT(1)
+#define NDSR_WRDREQ		BIT(2)
+#define NDSR_CORERR		BIT(3)
+#define NDSR_UNCERR		BIT(4)
+#define NDSR_CMDD(cs)		BIT(8 - cs)
+#define NDSR_RDY(rb)		BIT(11 + rb)
+#define NDSR_ERRCNT(x)		((x >> 16) & 0x1F)
+
+/* NAND ECC control register bitfields */
+#define NDECCTRL_BCH_EN		BIT(0)
+
+/* NAND controller command buffer registers bitfields */
+#define NDCB0_CMD1(x)		((x & 0xFF) << 0)
+#define NDCB0_CMD2(x)		((x & 0xFF) << 8)
+#define NDCB0_ADDR_CYC(x)	((x & 0x7) << 16)
+#define NDCB0_DBC		BIT(19)
+#define NDCB0_CMD_TYPE(x)	((x & 0x7) << 21)
+#define NDCB0_CSEL		BIT(24)
+#define NDCB0_RDY_BYP		BIT(27)
+#define NDCB0_LEN_OVRD		BIT(28)
+#define NDCB0_CMD_XTYPE(x)	((x & 0x7) << 29)
+
+#define NDCB1_COLS(x)		((x & 0xFFFF) << 0)
+#define NDCB1_ADDRS(x)		(x << 16)
+
+#define NDCB2_ADDR5(x)		(((x >> 16) & 0xFF) << 0)
+
+#define NDCB3_ADDR6(x)		((x & 0xFF) << 16)
+#define NDCB3_ADDR7(x)		((x & 0xFF) << 24)
+
+/* NAND controller command buffer 0 register 'type' and 'xtype' fields */
+#define TYPE_READ		0
+#define TYPE_WRITE		1
+#define TYPE_ERASE		2
+#define TYPE_READ_ID		3
+#define TYPE_STATUS		4
+#define TYPE_RESET		5
+#define TYPE_NAKED_CMD		6
+#define TYPE_NAKED_ADDR		7
+#define TYPE_MASK		7
+#define XTYPE_MONOLITHIC_RW	0
+#define XTYPE_LAST_NAKED_RW	1
+#define XTYPE_FINAL_COMMAND	3
+#define XTYPE_READ		4
+#define XTYPE_WRITE_DISPATCH	4
+#define XTYPE_NAKED_RW		5
+#define XTYPE_COMMAND_DISPATCH	6
+#define XTYPE_MASK		7
+
+/*
+ * Marvell ECC engine works differently than the others, in order to limit the
+ * size of the IP, hardware engineers choose to set a fixed strength at 16 bits
+ * per subpage, and depending on a the desired strength needed by the NAND chip,
+ * a particular layout mixing data/spare/ecc is defined, with a possible last
+ * chunk smaller that the others.
+ *
+ * @writesize:		Full page size on which the layout applies
+ * @chunk:		Desired ECC chunk size on which the layout applies
+ * @strength:		Desired ECC strength (per chunk size bytes) on which the
+ *			layout applies
+ * @full_chunk_cnt:	Number of full-sized chunks, which is the number of
+ *			repetitions of the pattern:
+ *			(data_bytes + spare_bytes + ecc_bytes).
+ * @data_bytes:		Number of data bytes per chunk
+ * @spare_bytes:	Number of spare bytes per chunk
+ * @ecc_bytes:		Number of ecc bytes per chunk
+ * @last_chunk_cnt:	If there is a last chunk with a different size than
+ *			the first ones, the next fields may not be empty
+ * @last_data_bytes:	Number of data bytes in the last chunk
+ * @last_spare_bytes:	Number of spare bytes in the last chunk
+ * @last_ecc_bytes:	Number of ecc bytes in the last chunk
+ */
+struct marvell_hw_ecc_layout {
+	/* Constraints */
+	int writesize;
+	int chunk;
+	int strength;
+	/* Corresponding layout */
+	int full_chunk_cnt;
+	int data_bytes;
+	int spare_bytes;
+	int ecc_bytes;
+	int last_chunk_cnt;
+	int last_data_bytes;
+	int last_spare_bytes;
+	int last_ecc_bytes;
+};
+
+#define MARVELL_LAYOUT(ws, dc, ds, fcc, db, sb, eb, lcc, ldb, lsb, leb) \
+	{								\
+		.writesize = ws,					\
+		.chunk = dc,						\
+		.strength = ds,						\
+		.full_chunk_cnt = fcc,					\
+		.data_bytes = db,					\
+		.spare_bytes = sb,					\
+		.ecc_bytes = eb,					\
+		.last_chunk_cnt = lcc,					\
+		.last_data_bytes = ldb,					\
+		.last_spare_bytes = lsb,				\
+		.last_ecc_bytes = leb,					\
+	}
+
+/* Layouts explained in AN-379_Marvell_SoC_NFC_ECC */
+static const struct marvell_hw_ecc_layout marvell_nfc_layouts[] = {
+	MARVELL_LAYOUT(  512,   512,  1,  1,  512,  0,  6,  0,  0,  0,  0),
+	MARVELL_LAYOUT( 2048,   512,  1,  1, 2048, 40, 24,  0,  0,  0,  0),
+	MARVELL_LAYOUT( 2048,   512,  4,  1, 2048, 32, 30,  0,  0,  0,  0),
+	MARVELL_LAYOUT( 4096,   512,  4,  2, 2048, 32, 30,  0,  0,  0,  0),
+	MARVELL_LAYOUT( 4096,   512,  8,  4, 1024,  0, 30,  1,  0, 64, 30),
+};
+
+/*
+ * The Nand Flash Controller has up to 4 CE and 2 RB pins. The CE selection
+ * is made by a field in NDCB0 register, and in another field in NDCB2 register.
+ * The datasheet describes the logic with an error: ADDR5 field is once
+ * declared at the beginning of NDCB2, and another time at its end. Because the
+ * ADDR5 field of NDCB2 may be used by other bytes, it would be more logical
+ * to use the last bit of this field instead of the first ones.
+ *
+ * @ndcb0_csel:		Value of the NDCB0 register with or without the flag
+ *			selecting the wanted CE lane. This is set once when
+ *			the Device Tree is probed.
+ * @rb:			Ready/Busy pin for the flash chip
+ */
+struct marvell_nand_chip_sel {
+	u32 ndcb0_csel;
+	unsigned int rb;
+};
+
+/*
+ * NAND chip structure: stores NAND chip device related information
+ *
+ * @chip:		Base NAND chip structure
+ * @node:		Used to store NAND chips into a list
+ * @layout		NAND layout when using hardware ECC
+ * @ndtr0		Timing registers 0 value for this NAND chip
+ * @ndtr1		Timing registers 1 value for this NAND chip
+ * @selected:		Current active CS
+ * @nsels:		Number of CS lines required by the NAND chip
+ * @sels:		Array of CS lines descriptions
+ */
+struct marvell_nand_chip {
+	struct nand_chip chip;
+	struct list_head node;
+	const struct marvell_hw_ecc_layout *layout;
+	u32 ndtr0;
+	u32 ndtr1;
+	int addr_cyc;
+	int selected;
+	unsigned int nsels;
+	struct marvell_nand_chip_sel sels[0];
+};
+
+static inline struct marvell_nand_chip *to_marvell_nand(struct nand_chip *chip)
+{
+	return container_of(chip, struct marvell_nand_chip, chip);
+}
+
+static inline struct marvell_nand_chip_sel *to_nand_sel(struct marvell_nand_chip
+							*nand)
+{
+	return &nand->sels[nand->selected];
+}
+
+enum marvell_nfc_variant {
+	MARVELL_NFC_VARIANT_PXA3XX,
+	MARVELL_NFC_VARIANT_ARMADA370,
+	MARVELL_NFC_VARIANT_ARMADA_8K,
+};
+
+/*
+ * NAND controller capabilities for distinction between compatible strings
+ *
+ * @variant:		Board type
+ * @max_cs_nb:		Number of Chip Select lines available
+ * @max_rb_nb:		Number of Ready/Busy lines available
+ * @legacy_of_bindings	Indicates if DT parsing must be done using the old
+ *			fashion way
+ */
+struct marvell_nfc_caps {
+	enum marvell_nfc_variant variant;
+	unsigned int max_cs_nb;
+	unsigned int max_rb_nb;
+	bool legacy_of_bindings;
+};
+
+/*
+ * NAND controller structure: stores Marvell NAND controller information
+ *
+ * @controller:		Base controller structure
+ * @dev:		Parent device (used to print error messages)
+ * @regs:		NAND controller registers
+ * @ecc_clk:		ECC block clock, two times the NAND controller clock
+ * @complete:		Completion object to wait for NAND controller events
+ * @assigned_cs:	Bitmask describing already assigned CS lines
+ * @chips:		List containing all the NAND chips attached to
+ *			this NAND controller
+ * @caps:		NAND controller capabilities for each compatible string
+ * @buf:		Controller local buffer to store a part of the read
+ *			buffer when the read operation was not 8 bytes aligned
+ *			as is the FIFO.
+ * @buf_pos:		Position in the 'buf' buffer
+ */
+struct marvell_nfc {
+	struct nand_hw_control controller;
+	struct device *dev;
+	void __iomem *regs;
+	struct clk *ecc_clk;
+	struct completion complete;
+	unsigned long assigned_cs;
+	struct list_head chips;
+	const struct marvell_nfc_caps *caps;
+
+	/*
+	 * Buffer handling: @buf will be accessed byte-per-byter but also
+	 * int-per-int when exchanging data with the NAND controller FIFO,
+	 * 32-bit alignment is then required.
+	 */
+	u8 buf[FIFO_DEPTH] __aligned(sizeof(u32));
+	int buf_pos;
+};
+
+static inline struct marvell_nfc *to_marvell_nfc(struct nand_hw_control *ctrl)
+{
+	return container_of(ctrl, struct marvell_nfc, controller);
+}
+
+/*
+ * NAND controller timings expressed in NAND Controller clock cycles
+ *
+ * @tRP:		ND_nRE pulse width
+ * @tRH:		ND_nRE high duration
+ * @tWP:		ND_nWE pulse time
+ * @tWH:		ND_nWE high duration
+ * @tCS:		Enable signal setup time
+ * @tCH:		Enable signal hold time
+ * @tADL:		Address to write data delay
+ * @tAR:		ND_ALE low to ND_nRE low delay
+ * @tWHR:		ND_nWE high to ND_nRE low for status read
+ * @tRHW:		ND_nRE high duration, read to write delay
+ * @tR:			ND_nWE high to ND_nRE low for read
+ */
+struct marvell_nfc_timings {
+	/* NDTR0 fields */
+	unsigned int tRP;
+	unsigned int tRH;
+	unsigned int tWP;
+	unsigned int tWH;
+	unsigned int tCS;
+	unsigned int tCH;
+	unsigned int tADL;
+	/* NDTR1 fields */
+	unsigned int tAR;
+	unsigned int tWHR;
+	unsigned int tRHW;
+	unsigned int tR;
+};
+
+/*
+ * Derives a duration in numbers of clock cycles.
+ *
+ * @ps: Duration in pico-seconds
+ * @period_ns:  Clock period in nano-seconds
+ *
+ * Convert the duration in nano-seconds, then divide by the period and
+ * return the number of clock periods.
+ */
+#define TO_CYCLES(ps, period_ns) (DIV_ROUND_UP(ps / 1000, period_ns))
+
+/*
+ * NAND driver structure filled during the parsing of the ->exec_op() subop
+ * subset of instructions.
+ *
+ * @ndcb:		Array for the values of the NDCBx registers
+ * @cle_ale_delay_ns:	Optional delay after the last CMD or ADDR cycle
+ * @rdy_timeout_ms:	Timeout for waits on Ready/Busy pin
+ * @rdy_delay_ns:	Optional delay after waiting for the RB pin
+ * @data_delay_ns:	Optional delay after the data xfer
+ * @data_instr_idx:	Index of the data instruction in the subop
+ * @data_instr:		Pointer to the data instruction in the subop
+ */
+struct marvell_nfc_op {
+	u32 ndcb[4];
+	unsigned int cle_ale_delay_ns;
+	unsigned int rdy_timeout_ms;
+	unsigned int rdy_delay_ns;
+	unsigned int data_delay_ns;
+	unsigned int data_instr_idx;
+	const struct nand_op_instr *data_instr;
+};
+
+/*
+ * Internal helper to conditionnally apply a delay (from the above structure,
+ * most of the time).
+ */
+static void cond_delay(unsigned int ns)
+{
+	if (!ns)
+		return;
+
+	if (ns < 10000)
+		ndelay(ns);
+	else
+		udelay(DIV_ROUND_UP(ns, 1000));
+}
+
+/*
+ * Internal helper to mimic core functions whithout having to distinguish if
+ * this is the first read operation on the page or not and hence choose the
+ * right function.
+ */
+int read_page_data(struct nand_chip *chip, unsigned int page,
+		   unsigned int column, void *buf, unsigned int len)
+{
+	if (!column)
+		return nand_read_page_op(chip, page, column, buf, len);
+	else
+		return nand_change_read_column_op(chip, column, buf, len,
+						  false);
+}
+
+/*
+ * The controller has many flags that could generate interrupts, most of them
+ * are disabled and polling is used. For the very slow signals, using interrupts
+ * may relax the CPU charge.
+ */
+static void marvell_nfc_disable_int(struct marvell_nfc *nfc, u32 int_mask)
+{
+	u32 reg;
+
+	/* Writing 1 disables the interrupt */
+	reg = readl_relaxed(nfc->regs + NDCR);
+	writel_relaxed(reg | int_mask, nfc->regs + NDCR);
+}
+
+static void marvell_nfc_enable_int(struct marvell_nfc *nfc, u32 int_mask)
+{
+	u32 reg;
+
+	/* Writing 0 enables the interrupt */
+	reg = readl_relaxed(nfc->regs + NDCR);
+	writel_relaxed(reg & ~int_mask, nfc->regs + NDCR);
+}
+
+static void marvell_nfc_clear_int(struct marvell_nfc *nfc, u32 int_mask)
+{
+	writel_relaxed(int_mask, nfc->regs + NDSR);
+}
+
+/*
+ * The core may ask the controller to use only 8-bit accesses while usually
+ * using 16-bit accesses. Later function may blindly call this one with a
+ * boolean to indicate if 8-bit accesses must be enabled of disabled without
+ * knowing if 16-bit accesses are actually in use.
+ */
+static void marvell_nfc_force_byte_access(struct nand_chip *chip,
+					  bool force_8bit)
+{
+	struct marvell_nfc *nfc = to_marvell_nfc(chip->controller);
+	u32 ndcr;
+
+	if (!(chip->options & NAND_BUSWIDTH_16))
+		return;
+
+	ndcr = readl_relaxed(nfc->regs + NDCR);
+
+	if (force_8bit)
+		ndcr &= ~(NDCR_DWIDTH_M | NDCR_DWIDTH_C);
+	else
+		ndcr |= NDCR_DWIDTH_M | NDCR_DWIDTH_C;
+
+	writel_relaxed(ndcr, nfc->regs + NDCR);
+}
+
+/*
+ * Any time a command has to be sent to the controller, the following sequence
+ * has to be followed:
+ * - call marvell_nfc_prepare_cmd()
+ *      -> activate the ND_RUN bit that will kind of 'start a job'
+ *      -> wait the signal indicating the NFC is waiting for a command
+ * - send the command (cmd and address cycles)
+ * - enventually send or receive the data
+ * - call marvell_nfc_end_cmd() with the corresponding flag
+ *      -> wait the flag to be triggered or cancel the job with a timeout
+ *
+ * The following functions are helpers to do this job and keep in the
+ * specialized functions the code that really does the operations.
+ */
+static int marvell_nfc_prepare_cmd(struct nand_chip *chip)
+{
+	struct marvell_nfc *nfc = to_marvell_nfc(chip->controller);
+	u32 ndcr, val;
+	int ret;
+
+	/* Deassert ND_RUN and clear NDSR before issuing any command */
+	ndcr = readl_relaxed(nfc->regs + NDCR);
+	writel_relaxed(ndcr & ~NDCR_ND_RUN, nfc->regs + NDCR);
+	writel_relaxed(readl_relaxed(nfc->regs + NDSR), nfc->regs + NDSR);
+
+	/* Assert ND_RUN bit and wait the NFC to be ready */
+	writel_relaxed(ndcr | NDCR_ND_RUN, nfc->regs + NDCR);
+	ret = readl_relaxed_poll_timeout(nfc->regs + NDSR, val,
+					 val & NDSR_WRCMDREQ,
+					 POLL_PERIOD, POLL_TIMEOUT);
+	if (ret) {
+		dev_err(nfc->dev, "Timeout on WRCMDRE\n");
+		return -ETIMEDOUT;
+	}
+
+	/* Command may be written, clear WRCMDREQ status bit */
+	writel_relaxed(NDSR_WRCMDREQ, nfc->regs + NDSR);
+
+	return 0;
+}
+
+static void marvell_nfc_send_cmd(struct nand_chip *chip,
+				 struct marvell_nfc_op *nfc_op)
+{
+	struct marvell_nand_chip *marvell_nand = to_marvell_nand(chip);
+	struct marvell_nfc *nfc = to_marvell_nfc(chip->controller);
+
+	writel_relaxed(to_nand_sel(marvell_nand)->ndcb0_csel | nfc_op->ndcb[0],
+		       nfc->regs + NDCB0);
+	writel_relaxed(nfc_op->ndcb[1], nfc->regs + NDCB0);
+	writel(nfc_op->ndcb[2], nfc->regs + NDCB0);
+
+	/*
+	 * Write NDCB0 four times only if LEN_OVRD is set or if ADDR6 or ADDR7
+	 * fields are used.
+	 */
+	if (nfc_op->ndcb[0] & NDCB0_LEN_OVRD ||
+	    (nfc_op->ndcb[0] & NDCB0_ADDR_CYC(6)) == NDCB0_ADDR_CYC(6))
+		writel(nfc_op->ndcb[3], nfc->regs + NDCB0);
+}
+
+static int marvell_nfc_wait_ndrun(struct nand_chip *chip)
+{
+	struct marvell_nfc *nfc = to_marvell_nfc(chip->controller);
+	u32 val;
+	int ret;
+
+	/*
+	 * The command is being processed, wait for the ND_RUN bit to be
+	 * cleared by the NFC. If not, we must clear it by hand.
+	 */
+	ret = readl_relaxed_poll_timeout(nfc->regs + NDCR, val,
+					 (val & NDCR_ND_RUN) == 0,
+					 POLL_PERIOD, POLL_TIMEOUT);
+	if (ret) {
+		dev_err(nfc->dev, "Timeout on NAND controller run mode\n");
+		writel_relaxed(readl_relaxed(nfc->regs + NDCR) & ~NDCR_ND_RUN,
+			       nfc->regs + NDCR);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int marvell_nfc_end_cmd(struct nand_chip *chip, int flag,
+			       const char *label)
+{
+	struct marvell_nfc *nfc = to_marvell_nfc(chip->controller);
+	u32 val;
+	int ret;
+
+	ret = readl_relaxed_poll_timeout(nfc->regs + NDSR, val,
+					 val & flag,
+					 POLL_PERIOD, POLL_TIMEOUT);
+	if (ret) {
+		dev_err(nfc->dev, "Timeout on %s\n", label);
+		return ret;
+	}
+
+	writel_relaxed(flag, nfc->regs + NDSR);
+
+	return 0;
+}
+
+static int marvell_nfc_wait_cmdd(struct nand_chip *chip)
+{
+	struct marvell_nand_chip *marvell_nand = to_marvell_nand(chip);
+	int cs_flag = NDSR_CMDD(to_nand_sel(marvell_nand)->ndcb0_csel);
+
+	return marvell_nfc_end_cmd(chip, cs_flag, "CMDD");
+}
+
+static int marvell_nfc_wait_op(struct nand_chip *chip, unsigned int timeout_ms)
+{
+	struct marvell_nfc *nfc = to_marvell_nfc(chip->controller);
+	int ret;
+
+	/* Timeout is expressed in ms */
+	if (!timeout_ms)
+		timeout_ms = IRQ_TIMEOUT;
+
+	init_completion(&nfc->complete);
+
+	marvell_nfc_enable_int(nfc, NDCR_RDYM);
+	ret = wait_for_completion_timeout(&nfc->complete,
+					  msecs_to_jiffies(timeout_ms));
+	marvell_nfc_disable_int(nfc, NDCR_RDYM);
+	marvell_nfc_clear_int(nfc, NDSR_RDY(0) | NDSR_RDY(1));
+
+	if (!ret)
+		dev_err(nfc->dev, "Timeout waiting for RB signal\n");
+
+	return ret ? 0 : -ETIMEDOUT;
+}
+
+static void marvell_nfc_select_chip(struct mtd_info *mtd, int chip)
+{
+	struct nand_chip *nand = mtd_to_nand(mtd);
+	struct marvell_nand_chip *marvell_nand = to_marvell_nand(nand);
+	struct marvell_nfc *nfc = to_marvell_nfc(nand->controller);
+	u32 ndcr;
+
+	if (chip < 0 || chip >= marvell_nand->nsels)
+		return;
+
+	if (chip == marvell_nand->selected)
+		return;
+
+	/*
+	 * Do not change the timing registers when using the DT property
+	 * marvell,nand-keep-config; in that case ->ndtr0 and ->ndtr1 from the
+	 * marvell_nand structure are supposedly empty.
+	 */
+	if (marvell_nand->ndtr0 && marvell_nand->ndtr1) {
+		writel_relaxed(marvell_nand->ndtr0, nfc->regs + NDTR0);
+		writel_relaxed(marvell_nand->ndtr1, nfc->regs + NDTR1);
+	}
+
+	ndcr = readl_relaxed(nfc->regs + NDCR);
+
+	/* Ensure controller is not blocked in operation */
+	ndcr &= ~NDCR_ND_RUN;
+
+	/* Adapt bus width */
+	if (nand->options & NAND_BUSWIDTH_16)
+		ndcr |= NDCR_DWIDTH_M | NDCR_DWIDTH_C;
+
+	/*
+	 * Page size as seen by the controller, either 512B or 2kiB. This size
+	 * will be the reference for the controller when using LEN_OVRD.
+	 */
+	ndcr |= NDCR_PAGE_SZ(mtd->writesize);
+
+	/* Update the control register */
+	writel_relaxed(ndcr,  nfc->regs + NDCR);
+
+	/* Also reset the interrupt status register */
+	marvell_nfc_clear_int(nfc, NDCR_ALL_INT);
+
+	marvell_nand->selected = chip;
+}
+
+static irqreturn_t marvell_nfc_isr(int irq, void *dev_id)
+{
+	struct marvell_nfc *nfc = dev_id;
+	u32 st = readl_relaxed(nfc->regs + NDSR);
+	u32 ien = (~readl_relaxed(nfc->regs + NDCR)) & NDCR_ALL_INT;
+
+	/*
+	 * RDY interrupt mask is one bit in NDCR while there are two status
+	 * bit in NDSR (RDY[cs0/cs2] and RDY[cs1/cs3]).
+	 */
+	if (st & NDSR_RDY(1))
+		st |= NDSR_RDY(0);
+
+	if (!(st & ien))
+		return IRQ_NONE;
+
+	marvell_nfc_disable_int(nfc, st & NDCR_ALL_INT);
+
+	complete(&nfc->complete);
+
+	return IRQ_HANDLED;
+}
+
+/* HW ECC related functions */
+static void marvell_nfc_hw_ecc_enable(struct nand_chip *chip)
+{
+	struct marvell_nfc *nfc = to_marvell_nfc(chip->controller);
+	u32 ndcr = readl_relaxed(nfc->regs + NDCR);
+
+	if (!(ndcr & NDCR_ECC_EN)) {
+		writel_relaxed(ndcr | NDCR_ECC_EN | NDCR_SPARE_EN,
+			       nfc->regs + NDCR);
+
+		/*
+		 * When enabling BCH, set threshold to 0 to always know the
+		 * number of corrected bitflips.
+		 */
+		if (chip->ecc.algo == NAND_ECC_BCH)
+			writel_relaxed(NDECCTRL_BCH_EN, nfc->regs + NDECCCTRL);
+	}
+}
+
+static void marvell_nfc_hw_ecc_disable(struct nand_chip *chip)
+{
+	struct marvell_nfc *nfc = to_marvell_nfc(chip->controller);
+	u32 ndcr = readl_relaxed(nfc->regs + NDCR);
+
+	if (ndcr & NDCR_ECC_EN) {
+		writel_relaxed(ndcr & ~(NDCR_ECC_EN | NDCR_SPARE_EN),
+			       nfc->regs + NDCR);
+		if (chip->ecc.algo == NAND_ECC_BCH)
+			writel_relaxed(0, nfc->regs + NDECCCTRL);
+	}
+}
+
+static void marvell_nfc_hw_ecc_correct(struct nand_chip *chip,
+				       u8 *data, int data_len,
+				       u8 *oob, int oob_len,
+				       unsigned int *max_bitflips)
+{
+	struct mtd_info *mtd = nand_to_mtd(chip);
+	struct marvell_nfc *nfc = to_marvell_nfc(chip->controller);
+	int bf = 0;
+	u32 ndsr;
+
+	ndsr = readl_relaxed(nfc->regs + NDSR);
+
+	/* Check uncorrectable error flag */
+	if (ndsr & NDSR_UNCERR) {
+		writel_relaxed(ndsr, nfc->regs + NDSR);
+
+		/*
+		 * Blank pages (all 0xFF) with no ECC are recognized as bad
+		 * because hardware ECC engine expects non-empty ECC values
+		 * in that case, so whenever an uncorrectable error occurs,
+		 * check if the page is actually blank or not.
+		 *
+		 * It is important to check the emptyness only on oob_len,
+		 * which only covers the spare bytes because after a read with
+		 * ECC enabled, the ECC bytes in the buffer have been set by the
+		 * ECC engine, so they are not 0xFF.
+		 */
+		if (!data)
+			data_len = 0;
+		if (!oob)
+			oob_len = 0;
+		bf = nand_check_erased_ecc_chunk(data, data_len, NULL, 0,
+						 oob, oob_len,
+						 chip->ecc.strength);
+		if (bf < 0) {
+			mtd->ecc_stats.failed++;
+			return;
+		}
+	}
+
+	/* Check correctable error flag */
+	if (ndsr & NDSR_CORERR) {
+		writel_relaxed(ndsr, nfc->regs + NDSR);
+
+		if (chip->ecc.algo == NAND_ECC_BCH)
+			bf = NDSR_ERRCNT(ndsr);
+		else
+			bf = 1;
+	}
+
+	/*
+	 * Derive max_bitflips either from the number of bitflips detected by
+	 * the hardware ECC engine or by nand_check_erased_ecc_chunk().
+	 */
+	mtd->ecc_stats.corrected += bf;
+	*max_bitflips = max_t(unsigned int, *max_bitflips, bf);
+}
+
+/* Reads with HW ECC */
+static int marvell_nfc_hw_ecc_hmg_read_page(struct mtd_info *mtd,
+					    struct nand_chip *chip,
+					    u8 *buf, int oob_required,
+					    int page)
+{
+	struct marvell_nand_chip *marvell_nand = to_marvell_nand(chip);
+	struct marvell_nfc *nfc = to_marvell_nfc(chip->controller);
+	const struct marvell_hw_ecc_layout *lt = to_marvell_nand(chip)->layout;
+	int max_bitflips = 0, ret = 0, i;
+	u8 *data, *oob;
+	struct marvell_nfc_op nfc_op = {
+		.ndcb[0] = NDCB0_CMD_TYPE(TYPE_READ) |
+			   NDCB0_CMD_XTYPE(XTYPE_MONOLITHIC_RW) |
+			   NDCB0_ADDR_CYC(marvell_nand->addr_cyc) |
+			   NDCB0_DBC |
+			   NDCB0_CMD1(NAND_CMD_READ0) |
+			   NDCB0_CMD2(NAND_CMD_READSTART),
+		.ndcb[1] = NDCB1_ADDRS(page),
+		.ndcb[2] = NDCB2_ADDR5(page),
+	};
+
+	/*
+	 * With Hamming, OOB is not fully used (and thus not read entirely), not
+	 * expected bytes could show up at the end of the OOB buffer if not
+	 * explicitly erased.
+	 */
+	if (oob_required)
+		memset(chip->oob_poi, 0xFF, mtd->oobsize);
+
+	ret = marvell_nfc_prepare_cmd(chip);
+	if (ret)
+		return ret;
+
+	marvell_nfc_hw_ecc_enable(chip);
+
+	data = buf;
+	oob = chip->oob_poi;
+
+	/*
+	 * Reading spare area is mandatory when using HW ECC or read operation
+	 * will trigger uncorrectable ECC errors, but do not read ECC here.
+	 */
+	marvell_nfc_send_cmd(chip, &nfc_op);
+
+	marvell_nfc_end_cmd(chip, NDSR_RDDREQ,
+			    "RDDREQ while draining FIFO (data)");
+
+	/* Read the data... */
+	if (data)
+		ioread32_rep(nfc->regs + NDDB, data, FIFO_REP(lt->data_bytes));
+	else
+		for (i = 0; i < FIFO_REP(lt->data_bytes); i++)
+			ioread32_rep(nfc->regs + NDDB, nfc->buf, FIFO_DEPTH_32);
+
+	/* ...then the spare bytes */
+	ioread32_rep(nfc->regs + NDDB, oob, FIFO_REP(lt->spare_bytes));
+
+	marvell_nfc_hw_ecc_correct(chip, data, lt->data_bytes,
+				   oob, lt->spare_bytes, &max_bitflips);
+
+	marvell_nfc_hw_ecc_disable(chip);
+
+	if (oob_required) {
+		/* Read ECC bytes without ECC enabled */
+		nand_read_page_op(chip, page,
+				  lt->data_bytes + lt->spare_bytes,
+				  oob + lt->spare_bytes, lt->ecc_bytes);
+	}
+
+	return max_bitflips;
+}
+
+static void marvell_nfc_hw_ecc_bch_read_chunk(struct nand_chip *chip, int chunk,
+					      u8 *data, int data_len,
+					      u8 *oob, int oob_len,
+					      int oob_required, int page)
+{
+	struct marvell_nand_chip *marvell_nand = to_marvell_nand(chip);
+	struct marvell_nfc *nfc = to_marvell_nfc(chip->controller);
+	const struct marvell_hw_ecc_layout *lt = to_marvell_nand(chip)->layout;
+	int i, j;
+	struct marvell_nfc_op nfc_op = {
+		.ndcb[0] = NDCB0_CMD_TYPE(TYPE_READ) |
+			   NDCB0_ADDR_CYC(marvell_nand->addr_cyc) |
+			   NDCB0_LEN_OVRD,
+		.ndcb[1] = NDCB1_ADDRS(page),
+		.ndcb[2] = NDCB2_ADDR5(page),
+	};
+
+	/*
+	 * Reading spare area is mandatory when using HW ECC or read operation
+	 * will trigger uncorrectable ECC errors, but do not read ECC here.
+	 */
+	nfc_op.ndcb[3] = data_len + oob_len;
+
+	if (marvell_nfc_prepare_cmd(chip))
+		return;
+
+	if (chunk == 0)
+		nfc_op.ndcb[0] |= NDCB0_DBC |
+				  NDCB0_CMD1(NAND_CMD_READ0) |
+				  NDCB0_CMD2(NAND_CMD_READSTART);
+
+	/*
+	 * Trigger the naked read operation only on the last chunk.
+	 * Otherwise, use monolithic read.
+	 */
+	if ((lt->last_chunk_cnt && chunk == lt->full_chunk_cnt) ||
+	    (!lt->last_chunk_cnt && chunk == lt->full_chunk_cnt - 1))
+		nfc_op.ndcb[0] |= NDCB0_CMD_XTYPE(XTYPE_LAST_NAKED_RW);
+	else
+		nfc_op.ndcb[0] |= NDCB0_CMD_XTYPE(XTYPE_MONOLITHIC_RW);
+
+	marvell_nfc_send_cmd(chip, &nfc_op);
+
+	/*
+	 * According to the datasheet, when reading from NDDB
+	 * with BCH enabled, after each 32 bytes reads, we
+	 * have to make sure that the NDSR.RDDREQ bit is set.
+	 *
+	 * Drain the FIFO, 8 32-bit reads at a time, and skip
+	 * the polling on the last read.
+	 *
+	 * Length is a multiple of 32 bytes, hence it is a multiple of 8 too.
+	 *
+	 */
+	for (i = 0; i < data_len; i += FIFO_DEPTH * sizeof(u32)) {
+		marvell_nfc_end_cmd(chip, NDSR_RDDREQ,
+				    "RDDREQ while draining FIFO (data)");
+		if (data) {
+			ioread32_rep(nfc->regs + NDDB, data,
+				     FIFO_DEPTH_32 * sizeof(u32));
+			data += FIFO_DEPTH * sizeof(u32);
+		} else {
+			for (j = 0; j < sizeof(u32); j++)
+				ioread32_rep(nfc->regs + NDDB, nfc->buf,
+					     FIFO_DEPTH_32);
+		}
+	}
+
+	for (i = 0; i < oob_len; i += FIFO_DEPTH * 4) {
+		marvell_nfc_end_cmd(chip, NDSR_RDDREQ,
+				    "RDDREQ while draining FIFO (OOB)");
+		ioread32_rep(nfc->regs + NDDB, oob, FIFO_DEPTH_32 * 4);
+		oob += FIFO_DEPTH * 4;
+	}
+}
+
+static int marvell_nfc_hw_ecc_bch_read_page(struct mtd_info *mtd,
+					    struct nand_chip *chip,
+					    u8 *buf, int oob_required,
+					    int page)
+{
+	const struct marvell_hw_ecc_layout *lt = to_marvell_nand(chip)->layout;
+	int nchunks = lt->full_chunk_cnt + lt->last_chunk_cnt;
+	int max_bitflips = 0;
+	u8 *data, *oob;
+	int chunk, data_len, oob_len, ecc_len;
+	/* Following sizes are used to read the ECC bytes after ECC operation */
+	int fixed_oob_size = lt->spare_bytes + lt->ecc_bytes;
+	int fixed_chunk_size = lt->data_bytes + fixed_oob_size;
+
+	/*
+	 * With BCH, OOB is not fully used (and thus not read entirely), not
+	 * expected bytes could show up at the end of the OOB buffer if not
+	 * explicitly erased.
+	 */
+	if (oob_required)
+		memset(chip->oob_poi, 0xFF, mtd->oobsize);
+
+	marvell_nfc_hw_ecc_enable(chip);
+
+	for (chunk = 0; chunk < nchunks; chunk++) {
+		if (chunk == 0) {
+			/* Init pointers to iterate through the chunks */
+			if (buf)
+				data = buf;
+			else
+				data = NULL;
+			oob = chip->oob_poi;
+		} else {
+			/* Update pointers */
+			if (data)
+				data += lt->data_bytes;
+			oob += (lt->spare_bytes + lt->ecc_bytes + 2);
+		}
+
+		/* Update length */
+		if (chunk < lt->full_chunk_cnt) {
+			data_len = lt->data_bytes;
+			oob_len = lt->spare_bytes;
+			ecc_len = lt->ecc_bytes;
+		} else {
+			data_len = lt->last_data_bytes;
+			oob_len = lt->last_spare_bytes;
+			ecc_len = lt->last_ecc_bytes;
+		}
+
+		/* Read the chunk and detect number of bitflips */
+		marvell_nfc_hw_ecc_bch_read_chunk(chip, chunk, data, data_len,
+						  oob, oob_len, oob_required,
+						  page);
+
+		marvell_nfc_hw_ecc_correct(chip, data, data_len,
+					   oob, oob_len, &max_bitflips);
+	}
+
+	marvell_nfc_hw_ecc_disable(chip);
+
+	if (!oob_required)
+		return max_bitflips;
+
+	/* Read ECC bytes without ECC enabled */
+	for (chunk = 0; chunk < lt->full_chunk_cnt; chunk++)
+		read_page_data(chip, page,
+			       ((chunk + 1) * (fixed_chunk_size)) -
+			       lt->ecc_bytes,
+			       chip->oob_poi + ((chunk + 1) *
+						(fixed_oob_size + 2) -
+						(lt->ecc_bytes + 2)),
+			       lt->ecc_bytes);
+
+	if (lt->last_chunk_cnt)
+		read_page_data(chip, page,
+			       (lt->full_chunk_cnt * fixed_chunk_size) +
+			       lt->last_data_bytes + lt->last_spare_bytes,
+			       chip->oob_poi + (lt->full_chunk_cnt *
+						(fixed_oob_size + 2)) +
+			       lt->last_spare_bytes,
+			       lt->last_ecc_bytes);
+
+	return max_bitflips;
+}
+
+static int marvell_nfc_hw_ecc_read_oob(struct mtd_info *mtd,
+				       struct nand_chip *chip, int page)
+{
+	return chip->ecc.read_page(mtd, chip, NULL, true, page);
+}
+
+/* Raw reads with HW ECC */
+static int marvell_nfc_hw_ecc_read_page_raw(struct mtd_info *mtd,
+					    struct nand_chip *chip, u8 *buf,
+					    int oob_required, int page)
+{
+	const struct marvell_hw_ecc_layout *lt = to_marvell_nand(chip)->layout;
+	int chunk_size = lt->data_bytes + lt->spare_bytes + lt->ecc_bytes;
+	u8 *oob = chip->oob_poi;
+	int chunk;
+
+	if (oob_required)
+		memset(chip->oob_poi, 0xFF, mtd->oobsize);
+
+	for (chunk = 0; chunk < lt->full_chunk_cnt; chunk++) {
+		read_page_data(chip, page, chunk * chunk_size, buf,
+			       lt->data_bytes);
+		buf += lt->data_bytes;
+
+		if (oob_required) {
+			nand_read_data_op(chip, oob, lt->spare_bytes +
+					  lt->ecc_bytes, false);
+			/* Pad user data with 2 bytes when using BCH (30B) */
+			oob += lt->spare_bytes + lt->ecc_bytes + 2;
+		}
+	}
+
+	if (!lt->last_chunk_cnt)
+		return 0;
+
+	read_page_data(chip, page, chunk * chunk_size, buf,
+		       lt->last_data_bytes);
+	if (oob_required)
+		nand_read_data_op(chip, oob, lt->last_spare_bytes +
+				  lt->last_ecc_bytes, false);
+
+	return 0;
+}
+
+static int marvell_nfc_hw_ecc_read_oob_raw(struct mtd_info *mtd,
+					   struct nand_chip *chip, int page)
+{
+	const struct marvell_hw_ecc_layout *lt = to_marvell_nand(chip)->layout;
+	u8 *oob = chip->oob_poi;
+	int chunk_size = lt->data_bytes + lt->spare_bytes + lt->ecc_bytes;
+	int chunk;
+
+	for (chunk = 0; chunk < lt->full_chunk_cnt; chunk++) {
+		/* Move NAND pointer to the next chunk of OOB data */
+		read_page_data(chip, page,
+			       chunk * chunk_size + lt->data_bytes,
+			       oob, lt->spare_bytes + lt->ecc_bytes);
+		/* Pad user data with 2 bytes when using BCH (30B) */
+		oob += lt->spare_bytes + lt->ecc_bytes + 2;
+	}
+
+	if (lt->last_chunk_cnt)
+		nand_read_data_op(chip, oob,
+				  lt->last_spare_bytes + lt->last_ecc_bytes,
+				  false);
+
+	return 0;
+}
+
+/* Writes with HW ECC */
+static int marvell_nfc_hw_ecc_hmg_write_page(struct mtd_info *mtd,
+					     struct nand_chip *chip,
+					     const u8 *buf,
+					     int oob_required, int page)
+{
+	struct marvell_nand_chip *marvell_nand = to_marvell_nand(chip);
+	struct marvell_nfc *nfc = to_marvell_nfc(chip->controller);
+	const struct marvell_hw_ecc_layout *lt = to_marvell_nand(chip)->layout;
+	const u8 *data = buf, *oob = chip->oob_poi;
+	int ret, i;
+	struct marvell_nfc_op nfc_op = {
+		.ndcb[0] = NDCB0_CMD_TYPE(TYPE_WRITE) |
+			   NDCB0_CMD_XTYPE(XTYPE_MONOLITHIC_RW) |
+			   NDCB0_ADDR_CYC(marvell_nand->addr_cyc) |
+			   NDCB0_CMD1(NAND_CMD_SEQIN),
+		.ndcb[1] = NDCB1_ADDRS(page),
+		.ndcb[2] = NDCB2_ADDR5(page),
+	};
+
+	ret = marvell_nfc_prepare_cmd(chip);
+	if (ret)
+		return ret;
+
+	marvell_nfc_hw_ecc_enable(chip);
+
+		marvell_nfc_send_cmd(chip, &nfc_op);
+
+	marvell_nfc_end_cmd(chip, NDSR_WRDREQ,
+			    "WRDREQ while loading FIFO (data)");
+
+	/* Write the data. If buf is empty, write empty bytes (0xFF) */
+	if (data) {
+		iowrite32_rep(nfc->regs + NDDB, data, FIFO_REP(lt->data_bytes));
+	} else {
+		data = nfc->buf;
+		memset(nfc->buf, 0xFF, FIFO_DEPTH);
+		for (i = 0; i < FIFO_REP(lt->data_bytes) / FIFO_DEPTH_32; i++)
+			iowrite32_rep(nfc->regs + NDDB, data, FIFO_DEPTH_32);
+	}
+
+	/* Then write the OOB data */
+	iowrite32_rep(nfc->regs + NDDB, oob, FIFO_REP(lt->spare_bytes));
+
+	ret = marvell_nfc_wait_op(chip,
+				  chip->data_interface.timings.sdr.tPROG_max);
+
+	marvell_nfc_hw_ecc_disable(chip);
+
+	if (ret & NAND_STATUS_FAIL)
+		return -EIO;
+
+	return 0;
+}
+
+static void marvell_nfc_hw_ecc_bch_write_chunk(struct nand_chip *chip,
+					       int chunk, const u8 *data,
+					       u8 *oob, int oob_required,
+					       int page)
+{
+	struct marvell_nand_chip *marvell_nand = to_marvell_nand(chip);
+	struct marvell_nfc *nfc = to_marvell_nfc(chip->controller);
+	const struct marvell_hw_ecc_layout *lt = to_marvell_nand(chip)->layout;
+	int data_len, oob_len, i;
+	struct marvell_nfc_op nfc_op = {
+		.ndcb[0] = NDCB0_CMD_TYPE(TYPE_WRITE) | NDCB0_LEN_OVRD,
+	};
+
+	/* OOB area to write is only spare area when using HW ECC */
+	if (chunk < lt->full_chunk_cnt) {
+		data_len = lt->data_bytes;
+		oob_len = lt->spare_bytes;
+	} else {
+		data_len = lt->last_data_bytes;
+		oob_len = lt->last_spare_bytes;
+	}
+
+	nfc_op.ndcb[3] = data_len + oob_len;
+
+	/*
+	 * First operation dispatches the CMD_SEQIN command, issue the address
+	 * cycles and asks for the first chunk of data.
+	 * Last operation dispatches the PAGEPROG command and also asks for the
+	 * last chunk of data.
+	 * All operations in the middle (if any) will issue a naked write and
+	 * also ask for data.
+	 */
+	if (chunk == 0) {
+		nfc_op.ndcb[0] |= NDCB0_CMD_XTYPE(XTYPE_WRITE_DISPATCH) |
+				  NDCB0_ADDR_CYC(marvell_nand->addr_cyc) |
+				  NDCB0_CMD1(NAND_CMD_SEQIN);
+		nfc_op.ndcb[1] |= NDCB1_ADDRS(page);
+		nfc_op.ndcb[2] |= NDCB2_ADDR5(page);
+	} else if (
+		(lt->last_chunk_cnt && (chunk == lt->full_chunk_cnt)) ||
+		(!lt->last_chunk_cnt &&	(chunk == lt->full_chunk_cnt - 1))) {
+		nfc_op.ndcb[0] |= NDCB0_CMD2(NAND_CMD_PAGEPROG) | NDCB0_DBC |
+				  NDCB0_CMD_XTYPE(XTYPE_LAST_NAKED_RW);
+	} else {
+		nfc_op.ndcb[0] |= NDCB0_CMD_XTYPE(XTYPE_NAKED_RW);
+	}
+
+	/*
+	 * If this is the first chunk, the previous command also embedded
+	 * the write operation, no need to repeat it.
+	 */
+	if (marvell_nfc_prepare_cmd(chip))
+		return;
+
+	marvell_nfc_send_cmd(chip, &nfc_op);
+
+	marvell_nfc_end_cmd(chip, NDSR_WRDREQ,
+			    "WRDREQ while loading FIFO (data)");
+
+	/* Effectively write the data to the data buffer */
+	if (data) {
+		data += chunk * lt->data_bytes;
+		iowrite32_rep(nfc->regs + NDDB, data, FIFO_REP(data_len));
+	} else {
+		memset(nfc->buf, 0xFF, FIFO_DEPTH);
+		data = nfc->buf;
+		for (i = 0; i < FIFO_REP(data_len) / FIFO_DEPTH_32; i++)
+			iowrite32_rep(nfc->regs + NDDB, data, FIFO_DEPTH_32);
+	}
+
+	/* Pad user data with 2 bytes when using BCH (30B) */
+	oob += chunk * lt->spare_bytes;
+	iowrite32_rep(nfc->regs + NDDB, oob, FIFO_REP(oob_len));
+}
+
+static int marvell_nfc_hw_ecc_bch_write_page(struct mtd_info *mtd,
+					     struct nand_chip *chip,
+					     const u8 *buf,
+					     int oob_required, int page)
+{
+	const struct marvell_hw_ecc_layout *lt = to_marvell_nand(chip)->layout;
+	int nchunks = lt->full_chunk_cnt + lt->last_chunk_cnt;
+	int chunk, ret;
+
+	marvell_nfc_hw_ecc_enable(chip);
+
+	for (chunk = 0; chunk < nchunks; chunk++) {
+		marvell_nfc_hw_ecc_bch_write_chunk(chip, chunk, buf,
+						   chip->oob_poi,
+						   oob_required, page);
+		/*
+		 * Waiting only for CMDD or PAGED is not enough, ECC are
+		 * partially written. No flag is set once the operation is
+		 * really finished but the ND_RUN bit is cleared, so wait for it
+		 * before stepping into the next command.
+		 */
+		marvell_nfc_wait_ndrun(chip);
+	}
+
+	ret = marvell_nfc_wait_op(chip,
+				  chip->data_interface.timings.sdr.tPROG_max);
+
+	marvell_nfc_hw_ecc_disable(chip);
+
+	if (ret & NAND_STATUS_FAIL)
+		return -EIO;
+
+	return 0;
+}
+
+static int marvell_nfc_hw_ecc_write_oob(struct mtd_info *mtd,
+					struct nand_chip *chip, int page)
+{
+	return chip->ecc.write_page(mtd, chip, NULL, true, page);
+}
+
+/* Raw writes with HW ECC */
+static int marvell_nfc_hw_ecc_write_page_raw(struct mtd_info *mtd,
+					     struct nand_chip *chip,
+					     const u8 *buf,
+					     int oob_required, int page)
+{
+	const struct marvell_hw_ecc_layout *lt = to_marvell_nand(chip)->layout;
+	int nchunks = lt->full_chunk_cnt + lt->last_chunk_cnt;
+	int oob_size = lt->spare_bytes + lt->ecc_bytes;
+	int last_oob_size = lt->last_spare_bytes + lt->last_ecc_bytes;
+	int chunk;
+
+	nand_prog_page_begin_op(chip, page, 0, NULL, 0);
+
+	for (chunk = 0; chunk < nchunks; chunk++) {
+		/*
+		 * OOB are not 8-bytes aligned anyway so change the column
+		 * at each cycle
+		 */
+		nand_change_write_column_op(chip, chunk * (lt->data_bytes +
+							   oob_size),
+					    NULL, 0, false);
+
+		if (chunk < lt->full_chunk_cnt)
+			nand_write_data_op(chip, buf + (chunk * lt->data_bytes),
+					   lt->data_bytes, false);
+		else
+			nand_write_data_op(chip, buf + (chunk * lt->data_bytes),
+					   lt->last_data_bytes, false);
+
+		if (!oob_required)
+			continue;
+
+		if (chunk < lt->full_chunk_cnt)
+			nand_write_data_op(chip, chip->oob_poi +
+					   (chunk * (oob_size + 2)),
+					   oob_size, false);
+		else
+			nand_write_data_op(chip, chip->oob_poi +
+					   (chunk * (oob_size + 2)),
+					   last_oob_size, false);
+	}
+
+	return nand_prog_page_end_op(chip);
+}
+
+static int marvell_nfc_hw_ecc_write_oob_raw(struct mtd_info *mtd,
+					    struct nand_chip *chip, int page)
+{
+	const struct marvell_hw_ecc_layout *lt = to_marvell_nand(chip)->layout;
+	int chunk_size = lt->data_bytes + lt->spare_bytes + lt->ecc_bytes;
+	int nchunks = lt->full_chunk_cnt + lt->last_chunk_cnt;
+	int oob_size = lt->spare_bytes + lt->ecc_bytes;
+	int last_oob_size = lt->last_spare_bytes + lt->last_ecc_bytes;
+	int chunk;
+
+	nand_prog_page_begin_op(chip, page, 0, NULL, 0);
+
+	for (chunk = 0; chunk < nchunks; chunk++) {
+		nand_change_write_column_op(chip, lt->data_bytes +
+					    (chunk * chunk_size), NULL, 0,
+					    false);
+
+		if (chunk < lt->full_chunk_cnt)
+			nand_write_data_op(chip, chip->oob_poi +
+					   (chunk * (oob_size + 2)),
+					   oob_size, false);
+		else
+			nand_write_data_op(chip, chip->oob_poi +
+					   (chunk * (oob_size + 2)),
+					   last_oob_size, false);
+	}
+
+	return nand_prog_page_end_op(chip);
+}
+
+/* NAND framework ->exec_op() hooks and related helpers */
+static void marvell_nfc_parse_instructions(const struct nand_subop *subop,
+					   struct marvell_nfc_op *nfc_op)
+{
+	const struct nand_op_instr *instr = NULL;
+	bool first_cmd = true;
+	unsigned int op_id;
+	int i;
+
+	/* Reset the input structure as most of its fields will be OR'ed */
+	memset(nfc_op, 0, sizeof(struct marvell_nfc_op));
+
+	for (op_id = 0; op_id < subop->ninstrs; op_id++) {
+		unsigned int offset, naddrs;
+		const u8 *addrs;
+		int len = nand_subop_get_data_len(subop, op_id);
+
+		instr = &subop->instrs[op_id];
+
+		switch (instr->type) {
+		case NAND_OP_CMD_INSTR:
+			pr_debug("  ->CMD      [0x%02x]\n", instr->cmd.opcode);
+
+			if (first_cmd)
+				nfc_op->ndcb[0] |=
+					NDCB0_CMD1(instr->cmd.opcode);
+			else
+				nfc_op->ndcb[0] |=
+					NDCB0_CMD2(instr->cmd.opcode) |
+					NDCB0_DBC;
+
+			nfc_op->cle_ale_delay_ns = instr->delay_ns;
+			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->addr.addrs[offset];
+
+			pr_debug("  ->ADDR     [%d cyc]", naddrs);
+
+			nfc_op->ndcb[0] |= NDCB0_ADDR_CYC(naddrs);
+
+			for (i = 0; i < min_t(unsigned int, 4, naddrs); i++)
+				nfc_op->ndcb[1] |= addrs[i] << (8 * i);
+
+			if (naddrs >= 5)
+				nfc_op->ndcb[2] |= NDCB2_ADDR5(addrs[5]);
+			if (naddrs >= 6)
+				nfc_op->ndcb[3] |= NDCB3_ADDR6(addrs[6]);
+			if (naddrs == 7)
+				nfc_op->ndcb[3] |= NDCB3_ADDR7(addrs[7]);
+
+			nfc_op->cle_ale_delay_ns = instr->delay_ns;
+			break;
+
+		case NAND_OP_DATA_IN_INSTR:
+			pr_debug("  ->DATA_IN  [%d B%s]\n", len,
+				 instr->data.force_8bit ? ", force 8-bit" : "");
+
+			nfc_op->data_instr = instr;
+			nfc_op->data_instr_idx = op_id;
+			nfc_op->ndcb[0] |=
+				NDCB0_CMD_TYPE(TYPE_READ) |
+				NDCB0_CMD_XTYPE(XTYPE_MONOLITHIC_RW) |
+				NDCB0_LEN_OVRD;
+			nfc_op->ndcb[3] |= round_up(len, FIFO_DEPTH);
+			nfc_op->data_delay_ns = instr->delay_ns;
+			break;
+
+		case NAND_OP_DATA_OUT_INSTR:
+			pr_debug("  ->DATA_OUT [%d B%s]\n", len,
+				 instr->data.force_8bit ? ", force 8-bit" : "");
+
+			nfc_op->data_instr = instr;
+			nfc_op->data_instr_idx = op_id;
+			nfc_op->ndcb[0] |=
+				NDCB0_CMD_TYPE(TYPE_WRITE) |
+				NDCB0_CMD_XTYPE(XTYPE_MONOLITHIC_RW) |
+				NDCB0_LEN_OVRD;
+			nfc_op->ndcb[3] |= round_up(len, FIFO_DEPTH);
+			nfc_op->data_delay_ns = instr->delay_ns;
+			break;
+
+		case NAND_OP_WAITRDY_INSTR:
+			pr_debug("  ->WAITRDY  [max %d ms]\n",
+				 instr->waitrdy.timeout_ms);
+
+			nfc_op->rdy_timeout_ms = instr->waitrdy.timeout_ms;
+			nfc_op->rdy_delay_ns = instr->delay_ns;
+			break;
+		}
+	}
+}
+
+static void marvell_nfc_xfer_data(struct nand_chip *chip,
+				  const struct nand_subop *subop,
+				  struct marvell_nfc_op *nfc_op)
+{
+	struct marvell_nfc *nfc = to_marvell_nfc(chip->controller);
+	unsigned int op_id = nfc_op->data_instr_idx;
+	int len = nand_subop_get_data_len(subop, op_id);
+	int offset = nand_subop_get_data_start_off(subop, op_id);
+	int last_len = len % FIFO_DEPTH;
+	int last_full_offset = round_down(len, FIFO_DEPTH);
+	const struct nand_op_instr *instr = nfc_op->data_instr;
+	u8 *in;
+	const u8 *out;
+	int i;
+
+	if (instr->data.force_8bit)
+		marvell_nfc_force_byte_access(chip, true);
+
+	if (instr->type == NAND_OP_DATA_IN_INSTR) {
+		in = &((u8 *)instr->data.in)[offset];
+
+		for (i = 0; i < last_full_offset; i += FIFO_DEPTH)
+			ioread32_rep(nfc->regs + NDDB,
+				     &((u32 *)in)[i / sizeof(u32)],
+				     FIFO_DEPTH_32);
+
+		if (last_len) {
+			ioread32_rep(nfc->regs + NDDB, nfc->buf, FIFO_DEPTH_32);
+			memcpy(&in[last_full_offset], nfc->buf, last_len);
+		}
+	} else {
+		out = &((const u8 *)instr->data.out)[offset];
+
+		for (i = 0; i < last_full_offset; i += FIFO_DEPTH)
+			iowrite32_rep(nfc->regs + NDDB,
+				      &((u32 *)out)[i / sizeof(u32)],
+				      FIFO_DEPTH_32);
+
+		if (last_len) {
+			memcpy(nfc->buf, &out[last_full_offset], last_len);
+			iowrite32_rep(nfc->regs + NDDB, nfc->buf,
+				      FIFO_DEPTH_32);
+		}
+	}
+
+	if (instr->data.force_8bit)
+		marvell_nfc_force_byte_access(chip, false);
+}
+
+static int marvell_nfc_monolithic_access_exec(struct nand_chip *chip,
+					      const struct nand_subop *subop)
+{
+	struct marvell_nfc_op nfc_op;
+	bool reading;
+	int ret;
+
+	marvell_nfc_parse_instructions(subop, &nfc_op);
+	reading = nfc_op.data_instr->type == NAND_OP_DATA_IN_INSTR;
+
+	ret = marvell_nfc_prepare_cmd(chip);
+	if (ret)
+		return ret;
+
+	marvell_nfc_send_cmd(chip, &nfc_op);
+	ret = marvell_nfc_end_cmd(chip, NDSR_RDDREQ | NDSR_WRDREQ,
+				  "RDDREQ/WRDREQ while draining raw data");
+	cond_delay(nfc_op.cle_ale_delay_ns);
+
+	if (reading) {
+		if (nfc_op.rdy_timeout_ms)
+			ret = marvell_nfc_wait_op(chip, nfc_op.rdy_timeout_ms);
+		cond_delay(nfc_op.rdy_delay_ns);
+	}
+
+	if (ret)
+		return ret;
+
+	marvell_nfc_xfer_data(chip, subop, &nfc_op);
+	ret = marvell_nfc_wait_cmdd(chip);
+	cond_delay(nfc_op.data_delay_ns);
+
+	if (!reading) {
+		if (nfc_op.rdy_timeout_ms)
+			ret = marvell_nfc_wait_op(chip, nfc_op.rdy_timeout_ms);
+		cond_delay(nfc_op.rdy_delay_ns);
+	}
+
+	return ret;
+}
+
+static int marvell_nfc_reset_cmd_type_exec(struct nand_chip *chip,
+					   const struct nand_subop *subop)
+{
+	struct marvell_nfc_op nfc_op;
+	int ret;
+
+	marvell_nfc_parse_instructions(subop, &nfc_op);
+	nfc_op.ndcb[0] |= NDCB0_CMD_TYPE(TYPE_RESET);
+
+	ret = marvell_nfc_prepare_cmd(chip);
+	if (ret)
+		return ret;
+
+	marvell_nfc_send_cmd(chip, &nfc_op);
+	ret = marvell_nfc_wait_ndrun(chip);
+	cond_delay(nfc_op.cle_ale_delay_ns);
+
+	if (nfc_op.rdy_timeout_ms)
+		ret = marvell_nfc_wait_op(chip, nfc_op.rdy_timeout_ms);
+	cond_delay(nfc_op.rdy_delay_ns);
+
+	return ret;
+}
+
+static int marvell_nfc_erase_cmd_type_exec(struct nand_chip *chip,
+					   const struct nand_subop *subop)
+{
+	struct marvell_nfc_op nfc_op;
+	int ret;
+
+	marvell_nfc_parse_instructions(subop, &nfc_op);
+	nfc_op.ndcb[0] |= NDCB0_CMD_TYPE(TYPE_ERASE);
+
+	ret = marvell_nfc_prepare_cmd(chip);
+	if (ret)
+		return ret;
+
+	marvell_nfc_send_cmd(chip, &nfc_op);
+	ret = marvell_nfc_wait_ndrun(chip);
+	cond_delay(nfc_op.cle_ale_delay_ns);
+
+	if (nfc_op.rdy_timeout_ms)
+		ret = marvell_nfc_wait_op(chip, nfc_op.rdy_timeout_ms);
+	cond_delay(nfc_op.rdy_delay_ns);
+
+	return ret;
+}
+
+static int marvell_nfc_naked_access_exec(struct nand_chip *chip,
+					 const struct nand_subop *subop)
+{
+	struct marvell_nfc_op nfc_op;
+	int ret;
+
+	marvell_nfc_parse_instructions(subop, &nfc_op);
+
+	/*
+	 * Naked access are different in that they need to be flagged as naked
+	 * by the controller. Reset the controller registers fields that inform
+	 * on the type and refill them according to the ongoing operation.
+	 */
+	nfc_op.ndcb[0] &= ~(NDCB0_CMD_TYPE(TYPE_MASK) |
+			    NDCB0_CMD_XTYPE(XTYPE_MASK));
+	switch (subop->instrs[0].type) {
+	case NAND_OP_CMD_INSTR:
+		nfc_op.ndcb[0] |= NDCB0_CMD_TYPE(TYPE_NAKED_CMD);
+		break;
+	case NAND_OP_ADDR_INSTR:
+		nfc_op.ndcb[0] |= NDCB0_CMD_TYPE(TYPE_NAKED_ADDR);
+		break;
+	case NAND_OP_DATA_IN_INSTR:
+		nfc_op.ndcb[0] |= NDCB0_CMD_TYPE(TYPE_READ) |
+				  NDCB0_CMD_XTYPE(XTYPE_LAST_NAKED_RW);
+		break;
+	case NAND_OP_DATA_OUT_INSTR:
+		nfc_op.ndcb[0] |= NDCB0_CMD_TYPE(TYPE_WRITE) |
+				  NDCB0_CMD_XTYPE(XTYPE_LAST_NAKED_RW);
+		break;
+	default:
+		/* This should never happen */
+		break;
+	}
+
+	ret = marvell_nfc_prepare_cmd(chip);
+	if (ret)
+		return ret;
+
+	marvell_nfc_send_cmd(chip, &nfc_op);
+
+	if (!nfc_op.data_instr) {
+		ret = marvell_nfc_wait_ndrun(chip);
+		cond_delay(nfc_op.cle_ale_delay_ns);
+		return ret;
+	}
+
+	ret = marvell_nfc_end_cmd(chip, NDSR_RDDREQ | NDSR_WRDREQ,
+				  "RDDREQ/WRDREQ while draining raw data");
+	if (ret)
+		return ret;
+
+	marvell_nfc_xfer_data(chip, subop, &nfc_op);
+	ret = marvell_nfc_wait_cmdd(chip);
+	cond_delay(nfc_op.data_delay_ns);
+
+	return ret;
+}
+
+static int marvell_nfc_naked_waitrdy_exec(struct nand_chip *chip,
+					  const struct nand_subop *subop)
+{
+	struct marvell_nfc_op nfc_op;
+	int ret;
+
+	marvell_nfc_parse_instructions(subop, &nfc_op);
+
+	ret = marvell_nfc_wait_op(chip, nfc_op.rdy_timeout_ms);
+	cond_delay(nfc_op.rdy_delay_ns);
+
+	return ret;
+}
+
+static const struct nand_op_parser marvell_nfc_op_parser = NAND_OP_PARSER(
+	/* Monolithic read/write */
+	NAND_OP_PARSER_PATTERN(
+		marvell_nfc_monolithic_access_exec,
+		NAND_OP_PARSER_PAT_CMD_ELEM(false),
+		NAND_OP_PARSER_PAT_ADDR_ELEM(true, MAX_ADDRESS_CYC),
+		NAND_OP_PARSER_PAT_CMD_ELEM(true),
+		NAND_OP_PARSER_PAT_WAITRDY_ELEM(true),
+		NAND_OP_PARSER_PAT_DATA_IN_ELEM(false, MAX_CHUNK_SIZE)),
+	NAND_OP_PARSER_PATTERN(
+		marvell_nfc_monolithic_access_exec,
+		NAND_OP_PARSER_PAT_CMD_ELEM(false),
+		NAND_OP_PARSER_PAT_ADDR_ELEM(false, MAX_ADDRESS_CYC),
+		NAND_OP_PARSER_PAT_DATA_OUT_ELEM(false, MAX_CHUNK_SIZE),
+		NAND_OP_PARSER_PAT_CMD_ELEM(true),
+		NAND_OP_PARSER_PAT_WAITRDY_ELEM(true)),
+	/* Isolated commands (reset, erase, begin prog,...) */
+	NAND_OP_PARSER_PATTERN(
+		marvell_nfc_erase_cmd_type_exec,
+		NAND_OP_PARSER_PAT_CMD_ELEM(false),
+		NAND_OP_PARSER_PAT_ADDR_ELEM(false, MAX_ADDRESS_CYC),
+		NAND_OP_PARSER_PAT_CMD_ELEM(false),
+		NAND_OP_PARSER_PAT_WAITRDY_ELEM(true)),
+	NAND_OP_PARSER_PATTERN(
+		marvell_nfc_reset_cmd_type_exec,
+		NAND_OP_PARSER_PAT_CMD_ELEM(false),
+		NAND_OP_PARSER_PAT_WAITRDY_ELEM(false)),
+	/* Naked commands */
+	NAND_OP_PARSER_PATTERN(
+		marvell_nfc_naked_access_exec,
+		NAND_OP_PARSER_PAT_CMD_ELEM(false)),
+	NAND_OP_PARSER_PATTERN(
+		marvell_nfc_naked_access_exec,
+		NAND_OP_PARSER_PAT_ADDR_ELEM(false, MAX_ADDRESS_CYC)),
+	NAND_OP_PARSER_PATTERN(
+		marvell_nfc_naked_access_exec,
+		NAND_OP_PARSER_PAT_DATA_IN_ELEM(false, MAX_CHUNK_SIZE)),
+	NAND_OP_PARSER_PATTERN(
+		marvell_nfc_naked_access_exec,
+		NAND_OP_PARSER_PAT_DATA_OUT_ELEM(false, MAX_CHUNK_SIZE)),
+	NAND_OP_PARSER_PATTERN(
+		marvell_nfc_naked_waitrdy_exec,
+		NAND_OP_PARSER_PAT_WAITRDY_ELEM(false)),
+	);
+
+static int marvell_nfc_exec_op(struct nand_chip *chip,
+			       const struct nand_operation *op,
+			       bool check_only)
+{
+	return nand_op_parser_exec_op(chip, &marvell_nfc_op_parser,
+				      op, check_only);
+}
+
+/*
+ * HW ECC layouts, identical to old pxa3xx_nand driver,
+ * to be fully backward compatible.
+ */
+static int marvell_nand_ooblayout_ecc(struct mtd_info *mtd, int section,
+				      struct mtd_oob_region *oobregion)
+{
+	struct nand_chip *chip = mtd_to_nand(mtd);
+	const struct marvell_hw_ecc_layout *lt = to_marvell_nand(chip)->layout;
+	int nchunks = lt->full_chunk_cnt;
+
+	if (section >= nchunks)
+		return -ERANGE;
+
+	oobregion->offset = ((lt->spare_bytes + lt->ecc_bytes) * section) +
+		lt->spare_bytes;
+	oobregion->length = lt->ecc_bytes;
+
+	return 0;
+}
+
+static int marvell_nand_ooblayout_free(struct mtd_info *mtd, int section,
+				       struct mtd_oob_region *oobregion)
+{
+	struct nand_chip *chip = mtd_to_nand(mtd);
+	const struct marvell_hw_ecc_layout *lt = to_marvell_nand(chip)->layout;
+	int nchunks = lt->full_chunk_cnt;
+
+	if (section >= nchunks)
+		return -ERANGE;
+
+	if (!lt->spare_bytes)
+		return 0;
+
+	oobregion->offset = section * (lt->spare_bytes + lt->ecc_bytes);
+	oobregion->length = lt->spare_bytes;
+	if (!section) {
+		/*
+		 * Bootrom looks in bytes 0 & 5 for bad blocks for the
+		 * 4KB page / 4bit BCH combination.
+		 */
+		if (mtd->writesize == 4096 && lt->data_bytes == 2048) {
+			oobregion->offset += 6;
+			oobregion->length -= 6;
+		} else {
+			oobregion->offset += 2;
+			oobregion->length -= 2;
+		}
+	}
+
+	return 0;
+}
+
+static const struct mtd_ooblayout_ops marvell_nand_ooblayout_ops = {
+	.ecc = marvell_nand_ooblayout_ecc,
+	.free = marvell_nand_ooblayout_free,
+};
+
+static int marvell_nand_hw_ecc_ctrl_init(struct mtd_info *mtd,
+					 struct nand_ecc_ctrl *ecc,
+					 struct device_node *np)
+{
+	struct nand_chip *chip = mtd_to_nand(mtd);
+	struct marvell_nfc *nfc = to_marvell_nfc(chip->controller);
+	const struct marvell_hw_ecc_layout *l;
+	int i;
+
+	to_marvell_nand(chip)->layout = NULL;
+	for (i = 0; i < ARRAY_SIZE(marvell_nfc_layouts); i++) {
+		l = &marvell_nfc_layouts[i];
+		if (mtd->writesize == l->writesize &&
+		    ecc->size == l->chunk && ecc->strength == l->strength) {
+			to_marvell_nand(chip)->layout = l;
+			break;
+		}
+	}
+
+	if (!to_marvell_nand(chip)->layout) {
+		dev_err(nfc->dev,
+			"ECC strength %d at page size %d is not supported\n",
+			ecc->strength, mtd->writesize);
+		return -ENOTSUPP;
+	}
+
+	mtd_set_ooblayout(mtd, &marvell_nand_ooblayout_ops);
+	ecc->steps = l->full_chunk_cnt + l->last_chunk_cnt;
+	ecc->size = l->data_bytes;
+
+	if (ecc->strength == 1) {
+		chip->ecc.algo = NAND_ECC_HAMMING;
+		ecc->read_page = marvell_nfc_hw_ecc_hmg_read_page;
+		ecc->write_page = marvell_nfc_hw_ecc_hmg_write_page;
+	} else {
+		chip->ecc.algo = NAND_ECC_BCH;
+		ecc->read_page = marvell_nfc_hw_ecc_bch_read_page;
+		ecc->write_page = marvell_nfc_hw_ecc_bch_write_page;
+		ecc->strength = 16;
+	}
+
+	ecc->read_oob = marvell_nfc_hw_ecc_read_oob;
+	ecc->write_oob = marvell_nfc_hw_ecc_write_oob;
+
+	ecc->read_page_raw = marvell_nfc_hw_ecc_read_page_raw;
+	ecc->write_page_raw = marvell_nfc_hw_ecc_write_page_raw;
+	ecc->read_oob_raw = marvell_nfc_hw_ecc_read_oob_raw;
+	ecc->write_oob_raw = marvell_nfc_hw_ecc_write_oob_raw;
+
+	return 0;
+}
+
+static int marvell_nand_ecc_init(struct mtd_info *mtd,
+				 struct nand_ecc_ctrl *ecc,
+				 struct device_node *np)
+{
+	struct nand_chip *chip = mtd_to_nand(mtd);
+	int ret;
+
+	if (!ecc->size)
+		ecc->size = chip->ecc_step_ds;
+
+	if (!ecc->strength)
+		ecc->strength = chip->ecc_strength_ds;
+
+	if (!ecc->size || !ecc->strength)
+		return -EINVAL;
+
+	switch (ecc->mode) {
+	case NAND_ECC_HW:
+		ret = marvell_nand_hw_ecc_ctrl_init(mtd, ecc, np);
+		if (ret)
+			return ret;
+		break;
+	case NAND_ECC_NONE:
+		chip->ecc.algo = 0;
+	case NAND_ECC_SOFT:
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static u8 bbt_pattern[] = {'M', 'V', 'B', 'b', 't', '0' };
+static u8 bbt_mirror_pattern[] = {'1', 't', 'b', 'B', 'V', 'M' };
+
+static struct nand_bbt_descr bbt_main_descr = {
+	.options = NAND_BBT_LASTBLOCK | NAND_BBT_CREATE | NAND_BBT_WRITE |
+		   NAND_BBT_2BIT | NAND_BBT_VERSION,
+	.offs =	8,
+	.len = 6,
+	.veroffs = 14,
+	.maxblocks = 8,	/* Last 8 blocks in each chip */
+	.pattern = bbt_pattern
+};
+
+static struct nand_bbt_descr bbt_mirror_descr = {
+	.options = NAND_BBT_LASTBLOCK | NAND_BBT_CREATE | NAND_BBT_WRITE |
+		   NAND_BBT_2BIT | NAND_BBT_VERSION,
+	.offs =	8,
+	.len = 6,
+	.veroffs = 14,
+	.maxblocks = 8,	/* Last 8 blocks in each chip */
+	.pattern = bbt_mirror_pattern
+};
+
+static int marvell_nfc_setup_data_interface(struct mtd_info *mtd, int chipnr,
+					    const struct nand_data_interface
+					    *conf)
+{
+	struct nand_chip *chip = mtd_to_nand(mtd);
+	struct marvell_nand_chip *marvell_nand = to_marvell_nand(chip);
+	struct marvell_nfc *nfc = to_marvell_nfc(chip->controller);
+	unsigned int period_ns = 1000000000 / clk_get_rate(nfc->ecc_clk) * 2;
+	const struct nand_sdr_timings *sdr;
+	struct marvell_nfc_timings nfc_tmg;
+	int read_delay;
+
+	sdr = nand_get_sdr_timings(conf);
+	if (IS_ERR(sdr))
+		return PTR_ERR(sdr);
+
+	/*
+	 * SDR timings are given in pico-seconds while NFC timings must be
+	 * expressed in NAND controller clock cycles, which is half of the
+	 * frequency of the accessible ECC clock retrieved by clk_get_rate().
+	 * This is not written anywhere in the datasheet but was observed
+	 * with an oscilloscope.
+	 *
+	 * NFC datasheet gives equations from which thoses calculations
+	 * are derived, they tend to be slightly more restrictives than the
+	 * given core timings and may improve the overall speed.
+	 */
+	nfc_tmg.tRP = TO_CYCLES(DIV_ROUND_UP(sdr->tRC_min, 2), period_ns) - 1;
+	nfc_tmg.tRH = nfc_tmg.tRP;
+	nfc_tmg.tWP = TO_CYCLES(DIV_ROUND_UP(sdr->tWC_min, 2), period_ns) - 1;
+	nfc_tmg.tWH = nfc_tmg.tWP;
+	nfc_tmg.tCS = TO_CYCLES(sdr->tCS_min, period_ns);
+	nfc_tmg.tCH = TO_CYCLES(sdr->tCH_min, period_ns) - 1;
+	nfc_tmg.tADL = TO_CYCLES(sdr->tADL_min, period_ns);
+	/*
+	 * Read delay is the time of propagation from SoC pins to NFC internal
+	 * logic. With non-EDO timings, this is MIN_RD_DEL_CNT clock cycles. In
+	 * EDO mode, an additional delay of tRH must be taken into account so
+	 * the data is sampled on the falling edge instead of the rising edge.
+	 */
+	read_delay = sdr->tRC_min >= 30000 ?
+		MIN_RD_DEL_CNT : MIN_RD_DEL_CNT + nfc_tmg.tRH;
+
+	nfc_tmg.tAR = TO_CYCLES(sdr->tAR_min, period_ns);
+	/*
+	 * tWHR and tRHW are supposed to be read to write delays (and vice
+	 * versa) but in some cases, ie. when doing a change column, they must
+	 * be greater than that to be sure tCCS delay is respected.
+	 */
+	nfc_tmg.tWHR = TO_CYCLES(max_t(int, sdr->tWHR_min, sdr->tCCS_min),
+				 period_ns) - 2,
+	nfc_tmg.tRHW = TO_CYCLES(max_t(int, sdr->tRHW_min, sdr->tCCS_min),
+				 period_ns);
+
+	/* Use WAIT_MODE (wait for RB line) instead of only relying on delays */
+	nfc_tmg.tR = TO_CYCLES(sdr->tWB_max, period_ns);
+
+	if (chipnr < 0)
+		return 0;
+
+	marvell_nand->ndtr0 =
+		NDTR0_TRP(nfc_tmg.tRP) |
+		NDTR0_TRH(nfc_tmg.tRH) |
+		NDTR0_ETRP(nfc_tmg.tRP) |
+		NDTR0_TWP(nfc_tmg.tWP) |
+		NDTR0_TWH(nfc_tmg.tWH) |
+		NDTR0_TCS(nfc_tmg.tCS) |
+		NDTR0_TCH(nfc_tmg.tCH) |
+		NDTR0_RD_CNT_DEL(read_delay) |
+		NDTR0_SELCNTR |
+		NDTR0_TADL(nfc_tmg.tADL);
+
+	marvell_nand->ndtr1 =
+		NDTR1_TAR(nfc_tmg.tAR) |
+		NDTR1_TWHR(nfc_tmg.tWHR) |
+		NDTR1_TRHW(nfc_tmg.tRHW) |
+		NDTR1_WAIT_MODE |
+		NDTR1_TR(nfc_tmg.tR);
+
+	writel_relaxed(marvell_nand->ndtr0, nfc->regs + NDTR0);
+	writel_relaxed(marvell_nand->ndtr1, nfc->regs + NDTR1);
+
+	return 0;
+}
+
+static int marvell_nand_chip_init(struct device *dev, struct marvell_nfc *nfc,
+				  struct device_node *np)
+{
+	struct marvell_nand_chip *marvell_nand;
+	struct mtd_info *mtd;
+	struct nand_chip *chip;
+	int nsels, ret, i;
+	u32 cs, rb;
+
+	/*
+	 * The legacy "num-cs" property indicates the number of CS on the only
+	 * chip connected to the controller (legacy bindings does not support
+	 * more than one chip). CS are only incremented one by one while the RB
+	 * pin is always the #0.
+	 *
+	 * When not using legacy bindings, a couple of "reg" and "marvell,rb"
+	 * properties must be filled. For each chip, expressed as a subnode,
+	 * "reg" points to the CS lines and "marvell,rb" to the RB line.
+	 */
+	if (nfc->caps->legacy_of_bindings) {
+		if (!of_get_property(np, "num-cs", &nsels)) {
+			dev_err(dev, "missing num-cs property\n");
+			return -EINVAL;
+		}
+	} else {
+		if (!of_get_property(np, "reg", &nsels)) {
+			dev_err(dev, "missing reg property\n");
+			return -EINVAL;
+		}
+	}
+
+	nsels /= sizeof(u32);
+	if (!nsels) {
+		dev_err(dev, "invalid reg property size\n");
+		return -EINVAL;
+	}
+
+	/* Alloc the nand chip structure */
+	marvell_nand = devm_kzalloc(dev, sizeof(*marvell_nand) +
+				    (nsels *
+				     sizeof(struct marvell_nand_chip_sel)),
+				    GFP_KERNEL);
+	if (!marvell_nand) {
+		dev_err(dev, "could not allocate chip structure\n");
+		return -ENOMEM;
+	}
+
+	marvell_nand->nsels = nsels;
+	marvell_nand->selected = -1;
+
+	for (i = 0; i < nsels; i++) {
+		if (nfc->caps->legacy_of_bindings) {
+			/*
+			 * Legacy bindings use the CS lines in natural
+			 * order (0, 1, ...)
+			 */
+			cs = i;
+		} else {
+			/* Retrieve CS id */
+			ret = of_property_read_u32_index(np, "reg", i, &cs);
+			if (ret) {
+				dev_err(dev, "could not retrieve reg property: %d\n",
+					ret);
+				return ret;
+			}
+		}
+
+		if (cs >= nfc->caps->max_cs_nb) {
+			dev_err(dev, "invalid reg value: %u (max CS = %d)\n",
+				cs, nfc->caps->max_cs_nb);
+			return -EINVAL;
+		}
+
+		if (test_and_set_bit(cs, &nfc->assigned_cs)) {
+			dev_err(dev, "CS %d already assigned\n", cs);
+			return -EINVAL;
+		}
+
+		/*
+		 * The cs variable represents the chip select id, which must be
+		 * converted in bit fields for NDCB0 and NDCB2 to select the
+		 * right chip. Unfortunately, due to a lack of information on
+		 * the subject and incoherent documentation, the user should not
+		 * use CS1 and CS3 at all as asserting them is not supported in
+		 * a reliable way (due to multiplexing inside ADDR5 field).
+		 */
+		switch (cs) {
+		case 0:
+		case 2:
+			marvell_nand->sels[i].ndcb0_csel = 0;
+			break;
+		case 1:
+		case 3:
+			marvell_nand->sels[i].ndcb0_csel = NDCB0_CSEL;
+			break;
+		default:
+			return -EINVAL;
+		}
+
+		/* Retrieve RB id */
+		if (nfc->caps->legacy_of_bindings) {
+			/* Legacy bindings always use RB #0 */
+			rb = 0;
+		} else {
+			ret = of_property_read_u32_index(np, "marvell,rb", i,
+							 &rb);
+			if (ret) {
+				dev_err(dev,
+					"could not retrieve RB property: %d\n",
+					ret);
+				return ret;
+			}
+		}
+
+		if (rb >= nfc->caps->max_rb_nb) {
+			dev_err(dev, "invalid reg value: %u (max RB = %d)\n",
+				rb, nfc->caps->max_rb_nb);
+			return -EINVAL;
+		}
+
+		marvell_nand->sels[i].rb = rb;
+	}
+
+	chip = &marvell_nand->chip;
+	chip->controller = &nfc->controller;
+	nand_set_flash_node(chip, np);
+
+	chip->exec_op = marvell_nfc_exec_op;
+	chip->select_chip = marvell_nfc_select_chip;
+	if (!of_get_property(np, "marvell,nand-keep-config", NULL))
+		chip->setup_data_interface = marvell_nfc_setup_data_interface;
+
+	mtd = nand_to_mtd(chip);
+	mtd->dev.parent = dev;
+
+	/*
+	 * Default to HW ECC engine mode. If the nand-ecc-mode property is given
+	 * in the DT node, this entry will be overwritten in nand_scan_ident().
+	 */
+	chip->ecc.mode = NAND_ECC_HW;
+
+	ret = nand_scan_ident(mtd, marvell_nand->nsels, NULL);
+	if (ret) {
+		dev_err(dev, "could not identify the nand chip\n");
+		return ret;
+	}
+
+	if (chip->bbt_options & NAND_BBT_USE_FLASH) {
+		/*
+		 * We'll use a bad block table stored in-flash and don't
+		 * allow writing the bad block marker to the flash.
+		 */
+		chip->bbt_options |= NAND_BBT_NO_OOB_BBM;
+		chip->bbt_td = &bbt_main_descr;
+		chip->bbt_md = &bbt_mirror_descr;
+	}
+
+	/*
+	 * With RA_START bit set in NDCR, columns takes two address cycles. This
+	 * means addressing a chip with more than 256 pages needs a fifth
+	 * address cycle. Addressing a chip using CS 2 or 3 should also needs
+	 * this additional cycle but due to insistance in the documentation and
+	 * lack of hardware to test this situation, this case has been dropped
+	 * and is not supported by this driver.
+	 */
+	marvell_nand->addr_cyc = 4;
+	if (chip->options & NAND_ROW_ADDR_3)
+		marvell_nand->addr_cyc = 5;
+
+	ret = marvell_nand_ecc_init(mtd, &chip->ecc, np);
+	if (ret) {
+		dev_err(dev, "ECC init failed: %d\n", ret);
+		return ret;
+	}
+
+	if (chip->ecc.mode == NAND_ECC_HW) {
+		/*
+		 * Subpage write not available with hardware ECC, prohibit also
+		 * subpage read as in userspace subpage acces would still be
+		 * allowed and subpage write, if used, would lead to numerous
+		 * uncorrectable ECC errors.
+		 */
+		chip->options |= NAND_NO_SUBPAGE_WRITE;
+	}
+
+	ret = nand_scan_tail(mtd);
+	if (ret) {
+		dev_err(dev, "nand_scan_tail failed: %d\n", ret);
+		return ret;
+	}
+
+	ret = mtd_device_register(mtd, NULL, 0);
+	if (ret) {
+		dev_err(dev, "failed to register mtd device: %d\n", ret);
+		nand_release(mtd);
+		return ret;
+	}
+
+	list_add_tail(&marvell_nand->node, &nfc->chips);
+
+	return 0;
+}
+
+static int marvell_nand_chips_init(struct device *dev, struct marvell_nfc *nfc)
+{
+	struct device_node *np = dev->of_node;
+	struct device_node *nand_np;
+	int nchips = of_get_child_count(np);
+	int max_cs = nfc->caps->max_cs_nb;
+	int ret;
+
+	if (nchips > max_cs) {
+		dev_err(dev, "too many NAND chips: %d (max = %d CS)\n", nchips,
+			max_cs);
+		return -EINVAL;
+	}
+
+	/*
+	 * Legacy bindings do not use child nodes to exhibit NAND chip
+	 * properties and layout. Instead, NAND properties are mixed with the
+	 * controller's and a single subnode presents the memory layout.
+	 */
+	if (nfc->caps->legacy_of_bindings) {
+		ret = marvell_nand_chip_init(dev, nfc, np);
+		return ret;
+	}
+
+	for_each_child_of_node(np, nand_np) {
+		ret = marvell_nand_chip_init(dev, nfc, nand_np);
+		if (ret) {
+			of_node_put(nand_np);
+			return ret;
+		}
+	}
+
+	return 0;
+}
+
+static void marvell_nand_chips_cleanup(struct marvell_nfc *nfc)
+{
+	struct marvell_nand_chip *entry, *temp;
+
+	list_for_each_entry_safe(entry, temp, &nfc->chips, node) {
+		nand_release(nand_to_mtd(&entry->chip));
+		list_del(&entry->node);
+	}
+}
+
+static int marvell_nfc_init(struct marvell_nfc *nfc)
+{
+	struct device_node *np = nfc->dev->of_node;
+	u32 enable_arbiter = 0;
+
+	/*
+	 * Some SoCs like A7k/A8k need to enable manually the NAND
+	 * controller to avoid being bootloader dependent. This is done
+	 * through the use of a single bit in the System Functions registers.
+	 */
+	if (nfc->caps->variant == MARVELL_NFC_VARIANT_ARMADA_8K) {
+		struct regmap *sysctrl_base = syscon_regmap_lookup_by_phandle(
+			np, "marvell,system-controller");
+		u32 reg;
+
+		if (IS_ERR(sysctrl_base))
+			return PTR_ERR(sysctrl_base);
+
+		regmap_read(sysctrl_base, GENCONF_SOC_DEVICE_MUX, &reg);
+		reg |= GENCONF_SOC_DEVICE_MUX_NFC_EN;
+		regmap_write(sysctrl_base, GENCONF_SOC_DEVICE_MUX, reg);
+	}
+
+	/* For PXA only, but other SoCs have this bit marked reserved */
+	if (of_get_property(np, "marvell,nand-enable-arbiter", NULL))
+		enable_arbiter = NDCR_ND_ARB_EN;
+
+	/*
+	 * ECC operations and interruptions are only enabled when specifically
+	 * needed. ECC shall not be activated in the early stages (fails probe)
+	 */
+	writel_relaxed(NDCR_RA_START | NDCR_ALL_INT | enable_arbiter,
+		       nfc->regs + NDCR);
+	writel_relaxed(0xFFFFFFFF, nfc->regs + NDSR);
+	writel_relaxed(0, nfc->regs + NDECCCTRL);
+
+	return 0;
+}
+
+static int marvell_nfc_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct resource *r;
+	struct marvell_nfc *nfc;
+	int ret;
+	int irq;
+
+	nfc = devm_kzalloc(&pdev->dev, sizeof(struct marvell_nfc),
+			   GFP_KERNEL);
+	if (!nfc)
+		return -ENOMEM;
+
+	nfc->dev = dev;
+	nand_hw_control_init(&nfc->controller);
+	INIT_LIST_HEAD(&nfc->chips);
+
+	r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	nfc->regs = devm_ioremap_resource(dev, r);
+	if (IS_ERR(nfc->regs))
+		return PTR_ERR(nfc->regs);
+
+	irq = platform_get_irq(pdev, 0);
+	if (irq < 0) {
+		dev_err(dev, "failed to retrieve irq\n");
+		return irq;
+	}
+
+	nfc->ecc_clk = devm_clk_get(&pdev->dev, NULL);
+	if (IS_ERR(nfc->ecc_clk))
+		return PTR_ERR(nfc->ecc_clk);
+
+	ret = clk_prepare_enable(nfc->ecc_clk);
+	if (ret)
+		return ret;
+
+	marvell_nfc_disable_int(nfc, NDCR_ALL_INT);
+	marvell_nfc_clear_int(nfc, NDCR_ALL_INT);
+	ret = devm_request_irq(dev, irq, marvell_nfc_isr,
+			       0, "marvell-nfc", nfc);
+	if (ret)
+		goto out_clk_unprepare;
+
+	/* Get NAND controller capabilities */
+	if (pdev->id_entry)
+		nfc->caps = (void *)pdev->id_entry->driver_data;
+	else
+		nfc->caps = of_device_get_match_data(&pdev->dev);
+
+	if (!nfc->caps) {
+		dev_err(dev, "Could not retrieve NFC caps\n");
+		ret = -EINVAL;
+		goto out_clk_unprepare;
+	}
+
+	/* Init the controller and then probe the chips */
+	ret = marvell_nfc_init(nfc);
+	if (ret)
+		goto out_clk_unprepare;
+
+	platform_set_drvdata(pdev, nfc);
+
+	ret = marvell_nand_chips_init(dev, nfc);
+	if (ret)
+		goto out_clk_unprepare;
+
+	return 0;
+
+out_clk_unprepare:
+	clk_disable_unprepare(nfc->ecc_clk);
+
+	return ret;
+}
+
+static int marvell_nfc_remove(struct platform_device *pdev)
+{
+	struct marvell_nfc *nfc = platform_get_drvdata(pdev);
+
+	marvell_nand_chips_cleanup(nfc);
+
+	clk_disable_unprepare(nfc->ecc_clk);
+
+	return 0;
+}
+
+static const struct marvell_nfc_caps marvell_armada_8k_nfc_caps = {
+	.variant = MARVELL_NFC_VARIANT_ARMADA_8K,
+	.max_cs_nb = 4,
+	.max_rb_nb = 2,
+};
+
+static const struct marvell_nfc_caps marvell_armada370_nfc_caps = {
+	.variant = MARVELL_NFC_VARIANT_ARMADA370,
+	.max_cs_nb = 4,
+	.max_rb_nb = 2,
+};
+
+static const struct marvell_nfc_caps marvell_pxa3xx_nfc_caps = {
+	.variant = MARVELL_NFC_VARIANT_PXA3XX,
+	.max_cs_nb = 2,
+	.max_rb_nb = 1,
+};
+
+static const struct marvell_nfc_caps marvell_armada_8k_nfc_legacy_caps = {
+	.variant = MARVELL_NFC_VARIANT_ARMADA_8K,
+	.max_cs_nb = 4,
+	.max_rb_nb = 2,
+	.legacy_of_bindings = true,
+};
+
+static const struct marvell_nfc_caps marvell_armada370_nfc_legacy_caps = {
+	.variant = MARVELL_NFC_VARIANT_ARMADA370,
+	.max_cs_nb = 4,
+	.max_rb_nb = 2,
+	.legacy_of_bindings = true,
+};
+
+static const struct marvell_nfc_caps marvell_pxa3xx_nfc_legacy_caps = {
+	.variant = MARVELL_NFC_VARIANT_PXA3XX,
+	.max_cs_nb = 2,
+	.max_rb_nb = 1,
+	.legacy_of_bindings = true,
+};
+
+static const struct of_device_id marvell_nfc_of_ids[] = {
+	{
+		.compatible = "marvell,armada-8k-nand-controller",
+		.data = &marvell_armada_8k_nfc_caps,
+	},
+	{
+		.compatible = "marvell,armada370-nand-controller",
+		.data = &marvell_armada370_nfc_caps,
+	},
+	{
+		.compatible = "marvell,pxa3xx-nand-controller",
+		.data = &marvell_pxa3xx_nfc_caps,
+	},
+	/* Support for old/deprecated bindings: */
+	{
+		.compatible = "marvell,armada-8k-nand",
+		.data = &marvell_armada_8k_nfc_legacy_caps,
+	},
+	{
+		.compatible = "marvell,armada370-nand",
+		.data = &marvell_armada370_nfc_legacy_caps,
+	},
+	{
+		.compatible = "marvell,pxa3xx-nand",
+		.data = &marvell_pxa3xx_nfc_legacy_caps,
+	},
+	{ /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, marvell_nand_match);
+
+static struct platform_driver marvell_nfc_driver = {
+	.driver	= {
+		.name		= "marvell-nfc",
+		.of_match_table = marvell_nfc_of_ids,
+	},
+	.probe	= marvell_nfc_probe,
+	.remove	= marvell_nfc_remove,
+};
+module_platform_driver(marvell_nfc_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Marvell NAND controller driver");
-- 
2.11.0

^ permalink raw reply related	[flat|nested] 75+ messages in thread

* [RFC 06/12] mtd: nand: add reworked Marvell NAND controller driver
@ 2017-10-18 14:36   ` Miquel Raynal
  0 siblings, 0 replies; 75+ messages in thread
From: Miquel Raynal @ 2017-10-18 14:36 UTC (permalink / raw)
  To: Andrew Lunn, bcm-kernel-feedback-list, Boris Brezillon,
	Brian Norris, Catalin Marinas, Chen-Yu Tsai, Cyrille Pitchen,
	Daniel Mack, David Woodhouse, devel, devicetree, Ezequiel Garcia,
	Greg Kroah-Hartman, Gregory Clement, Han Xu, Haojian Zhuang,
	Jason Cooper, Josh Wu, Kamal Dasu, linux-arm-kernel,
	linux-kernel, linux-mediatek, linux-mtd
  Cc: Hanna Hawa, Antoine Tenart, Nadav Haklai, Neta Zur Hershkovits,
	Igor Grinberg, Miquel Raynal, Ofer Heifetz

Add marvell_nand driver which aims at replacing the existing pxa3xx_nand
driver.

The new driver intends to be easier to understand and follows the brand
new NAND framework rules by implementing hooks for every pattern the
controller might support and referencing them inside a parser object
that will be given to the core at each ->exec_op() call.

Raw accessors are implemented, useful to test/debug memory/filesystem
corruptions. Userspace binaries contained in the mtd-utils package may
now be used and their output trusted.

Timings may not be kept from the bootloader anymore, the timings used
for instance in U-Boot were not optimal and it supposed to have NAND
support (and initialized) in the bootloader.

Thanks to the improved timings, implementation of ONFI mode 5 support
(with EDO managed by adding a delay on data sampling), merging the
commands together and optimizing writes in the command registers, the
new driver may achieve faster throughputs in both directions.
Measurements show an improvement of about +23% read throughput and +24%
write throughput. These measurements have been done with an
Armada-385-DB-AP (4kiB NAND pages forced in 4-bit strength BCH ECC
correction) using the userspace tool 'flash_speed' from the MTD test
suite.

Besides these important topics, the new driver addresses several
unsolved known issues in the old driver which:
- did not work with ECC soft neither with ECC none ;
- relied on naked read/write (which is unchanged) while the NFCv1
  embedded in the pxa3xx platforms do not implement it, so several
  NAND commands did not actually ever work without any notice (like
  reading the ONFI PARAM_PAGE or SET/GET_FEATURES) ;
- wrote the OOB data correctly, but was not able to read it correctly
  past the first OOB data chunk ;
- did not displayed ECC bytes ;
- used device tree bindings that did not allow more than one NAND chip,
  and did not allow to choose the correct chip select if not
  incrementing from 0. Plus, the Ready/Busy line used had to be 0.

Old device tree bindings are still supported but deprecated. A more
hierarchical view has to be used to keep the controller and the NAND
chip structures clearly separated both inside the device tree and also
in the driver code.

Signed-off-by: Miquel Raynal <miquel.raynal@free-electrons.com>
---
 drivers/mtd/nand/Kconfig        |   11 +
 drivers/mtd/nand/Makefile       |    1 +
 drivers/mtd/nand/marvell_nand.c | 2384 +++++++++++++++++++++++++++++++++++++++
 3 files changed, 2396 insertions(+)
 create mode 100644 drivers/mtd/nand/marvell_nand.c

diff --git a/drivers/mtd/nand/Kconfig b/drivers/mtd/nand/Kconfig
index bb48aafed9a2..8a64a18b9d26 100644
--- a/drivers/mtd/nand/Kconfig
+++ b/drivers/mtd/nand/Kconfig
@@ -323,6 +323,17 @@ config MTD_NAND_PXA3xx
 	  platforms (XP, 370, 375, 38x, 39x) and 64-bit Armada
 	  platforms (7K, 8K) (NFCv2).
 
+config MTD_NAND_MARVELL
+	tristate "NAND controller support on Marvell boards"
+	depends on PXA3xx || ARCH_MMP || PLAT_ORION || ARCH_MVEBU || \
+		   (COMPILE_TEST && HAS_IOMEM)
+	help
+	  This enables the NAND flash controller driver for Marvell boards,
+	  including:
+	  - PXA3xx processors (NFCv1)
+	  - 32-bit Armada platforms (XP, 37x, 38x, 39x) (NFCv2)
+	  - 64-bit Aramda platforms (7k, 8k) (NFCv2)
+
 config MTD_NAND_SLC_LPC32XX
 	tristate "NXP LPC32xx SLC Controller"
 	depends on ARCH_LPC32XX
diff --git a/drivers/mtd/nand/Makefile b/drivers/mtd/nand/Makefile
index ade5fc4c3819..828332dfe18c 100644
--- a/drivers/mtd/nand/Makefile
+++ b/drivers/mtd/nand/Makefile
@@ -31,6 +31,7 @@ obj-$(CONFIG_MTD_NAND_OMAP2) 		+= omap2_nand.o
 obj-$(CONFIG_MTD_NAND_OMAP_BCH_BUILD)	+= omap_elm.o
 obj-$(CONFIG_MTD_NAND_CM_X270)		+= cmx270_nand.o
 obj-$(CONFIG_MTD_NAND_PXA3xx)		+= pxa3xx_nand.o
+obj-$(CONFIG_MTD_NAND_MARVELL)		+= marvell_nand.o
 obj-$(CONFIG_MTD_NAND_TMIO)		+= tmio_nand.o
 obj-$(CONFIG_MTD_NAND_PLATFORM)		+= plat_nand.o
 obj-$(CONFIG_MTD_NAND_PASEMI)		+= pasemi_nand.o
diff --git a/drivers/mtd/nand/marvell_nand.c b/drivers/mtd/nand/marvell_nand.c
new file mode 100644
index 000000000000..557817a6f4b6
--- /dev/null
+++ b/drivers/mtd/nand/marvell_nand.c
@@ -0,0 +1,2384 @@
+/*
+ * Marvell NAND flash controller driver
+ *
+ * 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.
+ *
+ * Copyright (C) 2017 Marvell
+ * Author: Miquel RAYNAL <miquel.raynal@free-electrons.com>
+ */
+
+#include <linux/module.h>
+#include <linux/clk.h>
+#include <linux/mtd/rawnand.h>
+#include <linux/of_platform.h>
+#include <linux/iopoll.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+#include <linux/mfd/syscon.h>
+#include <linux/regmap.h>
+#include <asm/unaligned.h>
+
+/* Data FIFO granularity, FIFO reads/writes must be a multiple of this length */
+#define FIFO_DEPTH		8
+#define FIFO_REP(x)		(x / sizeof(u32))
+#define FIFO_DEPTH_32		FIFO_REP(FIFO_DEPTH)
+/* NFCv2 does not support transfers of larger chunks at a time */
+#define MAX_CHUNK_SIZE		2112
+/* Polling is done at a pace of POLL_PERIOD us until POLL_TIMEOUT is reached */
+#define POLL_PERIOD		0
+#define POLL_TIMEOUT		100000
+/* Interrupt maximum wait period in ms */
+#define IRQ_TIMEOUT		1000
+/* Latency in clock cycles between SoC pins and NFC logic */
+#define MIN_RD_DEL_CNT		3
+/* Maximum number of contiguous address cycles */
+#define MAX_ADDRESS_CYC		7
+/* System control register and bit to enable NAND on some SoCs */
+#define GENCONF_SOC_DEVICE_MUX	0x208
+#define GENCONF_SOC_DEVICE_MUX_NFC_EN BIT(0)
+
+/* NAND controller data flash control register */
+#define NDCR			0x00
+/* NAND interface timing parameter 0 register */
+#define NDTR0			0x04
+/* NAND interface timing parameter 1 register */
+#define NDTR1			0x0C
+/* NAND controller status register */
+#define NDSR			0x14
+/* NAND ECC control register */
+#define NDECCCTRL		0x28
+/* NAND controller data buffer register */
+#define NDDB			0x40
+/* NAND controller command buffer 0 register */
+#define NDCB0			0x48
+/* NAND controller command buffer 1 register */
+#define NDCB1			0x4C
+/* NAND controller command buffer 2 register */
+#define NDCB2			0x50
+/* NAND controller command buffer 3 register */
+#define NDCB3			0x54
+
+/* Data flash control register bitfields */
+#define NDCR_ALL_INT		GENMASK(11, 0)
+#define NDCR_CS1_CMDDM		BIT(7)
+#define NDCR_CS0_CMDDM		BIT(8)
+#define NDCR_RDYM		BIT(11)
+#define NDCR_ND_ARB_EN		BIT(12)
+#define NDCR_RA_START		BIT(15)
+#define NDCR_RD_ID_CNT(x)	((x & 0x7) << 16)
+#define NDCR_PAGE_SZ(x)		(x >= 2048 ? BIT(24) : 0)
+#define NDCR_DWIDTH_M		BIT(26)
+#define NDCR_DWIDTH_C		BIT(27)
+#define NDCR_ND_RUN		BIT(28)
+#define NDCR_ECC_EN		BIT(30)
+#define NDCR_SPARE_EN		BIT(31)
+
+/* NAND interface timing parameter registers bitfields */
+#define NDTR0_TRP(x)		((min_t(unsigned int, x, 0xF) & 0x7) << 0)
+#define NDTR0_TRH(x)		(min_t(unsigned int, x, 0x7) << 3)
+#define NDTR0_ETRP(x)		((min_t(unsigned int, x, 0xF) & 0x8) << 3)
+#define NDTR0_SEL_NRE_EDGE	BIT(7)
+#define NDTR0_TWP(x)		(min_t(unsigned int, x, 0x7) << 8)
+#define NDTR0_TWH(x)		(min_t(unsigned int, x, 0x7) << 11)
+#define NDTR0_TCS(x)		(min_t(unsigned int, x, 0x7) << 16)
+#define NDTR0_TCH(x)		(min_t(unsigned int, x, 0x7) << 19)
+#define NDTR0_RD_CNT_DEL(x)	(min_t(unsigned int, x, 0xF) << 22)
+#define NDTR0_SELCNTR		BIT(26)
+#define NDTR0_TADL(x)		(min_t(unsigned int, x, 0x1F) << 27)
+
+#define NDTR1_TAR(x)		(min_t(unsigned int, x, 0xF) << 0)
+#define NDTR1_TWHR(x)		(min_t(unsigned int, x, 0xF) << 4)
+#define NDTR1_TRHW(x)		(min_t(unsigned int, x / 16, 0x3) << 8)
+#define NDTR1_PRESCALE		BIT(14)
+#define NDTR1_WAIT_MODE		BIT(15)
+#define NDTR1_TR(x)		(min_t(unsigned int, x, 0xFFFF) << 16)
+
+/* NAND controller status register bitfields */
+#define NDSR_WRCMDREQ		BIT(0)
+#define NDSR_RDDREQ		BIT(1)
+#define NDSR_WRDREQ		BIT(2)
+#define NDSR_CORERR		BIT(3)
+#define NDSR_UNCERR		BIT(4)
+#define NDSR_CMDD(cs)		BIT(8 - cs)
+#define NDSR_RDY(rb)		BIT(11 + rb)
+#define NDSR_ERRCNT(x)		((x >> 16) & 0x1F)
+
+/* NAND ECC control register bitfields */
+#define NDECCTRL_BCH_EN		BIT(0)
+
+/* NAND controller command buffer registers bitfields */
+#define NDCB0_CMD1(x)		((x & 0xFF) << 0)
+#define NDCB0_CMD2(x)		((x & 0xFF) << 8)
+#define NDCB0_ADDR_CYC(x)	((x & 0x7) << 16)
+#define NDCB0_DBC		BIT(19)
+#define NDCB0_CMD_TYPE(x)	((x & 0x7) << 21)
+#define NDCB0_CSEL		BIT(24)
+#define NDCB0_RDY_BYP		BIT(27)
+#define NDCB0_LEN_OVRD		BIT(28)
+#define NDCB0_CMD_XTYPE(x)	((x & 0x7) << 29)
+
+#define NDCB1_COLS(x)		((x & 0xFFFF) << 0)
+#define NDCB1_ADDRS(x)		(x << 16)
+
+#define NDCB2_ADDR5(x)		(((x >> 16) & 0xFF) << 0)
+
+#define NDCB3_ADDR6(x)		((x & 0xFF) << 16)
+#define NDCB3_ADDR7(x)		((x & 0xFF) << 24)
+
+/* NAND controller command buffer 0 register 'type' and 'xtype' fields */
+#define TYPE_READ		0
+#define TYPE_WRITE		1
+#define TYPE_ERASE		2
+#define TYPE_READ_ID		3
+#define TYPE_STATUS		4
+#define TYPE_RESET		5
+#define TYPE_NAKED_CMD		6
+#define TYPE_NAKED_ADDR		7
+#define TYPE_MASK		7
+#define XTYPE_MONOLITHIC_RW	0
+#define XTYPE_LAST_NAKED_RW	1
+#define XTYPE_FINAL_COMMAND	3
+#define XTYPE_READ		4
+#define XTYPE_WRITE_DISPATCH	4
+#define XTYPE_NAKED_RW		5
+#define XTYPE_COMMAND_DISPATCH	6
+#define XTYPE_MASK		7
+
+/*
+ * Marvell ECC engine works differently than the others, in order to limit the
+ * size of the IP, hardware engineers choose to set a fixed strength at 16 bits
+ * per subpage, and depending on a the desired strength needed by the NAND chip,
+ * a particular layout mixing data/spare/ecc is defined, with a possible last
+ * chunk smaller that the others.
+ *
+ * @writesize:		Full page size on which the layout applies
+ * @chunk:		Desired ECC chunk size on which the layout applies
+ * @strength:		Desired ECC strength (per chunk size bytes) on which the
+ *			layout applies
+ * @full_chunk_cnt:	Number of full-sized chunks, which is the number of
+ *			repetitions of the pattern:
+ *			(data_bytes + spare_bytes + ecc_bytes).
+ * @data_bytes:		Number of data bytes per chunk
+ * @spare_bytes:	Number of spare bytes per chunk
+ * @ecc_bytes:		Number of ecc bytes per chunk
+ * @last_chunk_cnt:	If there is a last chunk with a different size than
+ *			the first ones, the next fields may not be empty
+ * @last_data_bytes:	Number of data bytes in the last chunk
+ * @last_spare_bytes:	Number of spare bytes in the last chunk
+ * @last_ecc_bytes:	Number of ecc bytes in the last chunk
+ */
+struct marvell_hw_ecc_layout {
+	/* Constraints */
+	int writesize;
+	int chunk;
+	int strength;
+	/* Corresponding layout */
+	int full_chunk_cnt;
+	int data_bytes;
+	int spare_bytes;
+	int ecc_bytes;
+	int last_chunk_cnt;
+	int last_data_bytes;
+	int last_spare_bytes;
+	int last_ecc_bytes;
+};
+
+#define MARVELL_LAYOUT(ws, dc, ds, fcc, db, sb, eb, lcc, ldb, lsb, leb) \
+	{								\
+		.writesize = ws,					\
+		.chunk = dc,						\
+		.strength = ds,						\
+		.full_chunk_cnt = fcc,					\
+		.data_bytes = db,					\
+		.spare_bytes = sb,					\
+		.ecc_bytes = eb,					\
+		.last_chunk_cnt = lcc,					\
+		.last_data_bytes = ldb,					\
+		.last_spare_bytes = lsb,				\
+		.last_ecc_bytes = leb,					\
+	}
+
+/* Layouts explained in AN-379_Marvell_SoC_NFC_ECC */
+static const struct marvell_hw_ecc_layout marvell_nfc_layouts[] = {
+	MARVELL_LAYOUT(  512,   512,  1,  1,  512,  0,  6,  0,  0,  0,  0),
+	MARVELL_LAYOUT( 2048,   512,  1,  1, 2048, 40, 24,  0,  0,  0,  0),
+	MARVELL_LAYOUT( 2048,   512,  4,  1, 2048, 32, 30,  0,  0,  0,  0),
+	MARVELL_LAYOUT( 4096,   512,  4,  2, 2048, 32, 30,  0,  0,  0,  0),
+	MARVELL_LAYOUT( 4096,   512,  8,  4, 1024,  0, 30,  1,  0, 64, 30),
+};
+
+/*
+ * The Nand Flash Controller has up to 4 CE and 2 RB pins. The CE selection
+ * is made by a field in NDCB0 register, and in another field in NDCB2 register.
+ * The datasheet describes the logic with an error: ADDR5 field is once
+ * declared at the beginning of NDCB2, and another time at its end. Because the
+ * ADDR5 field of NDCB2 may be used by other bytes, it would be more logical
+ * to use the last bit of this field instead of the first ones.
+ *
+ * @ndcb0_csel:		Value of the NDCB0 register with or without the flag
+ *			selecting the wanted CE lane. This is set once when
+ *			the Device Tree is probed.
+ * @rb:			Ready/Busy pin for the flash chip
+ */
+struct marvell_nand_chip_sel {
+	u32 ndcb0_csel;
+	unsigned int rb;
+};
+
+/*
+ * NAND chip structure: stores NAND chip device related information
+ *
+ * @chip:		Base NAND chip structure
+ * @node:		Used to store NAND chips into a list
+ * @layout		NAND layout when using hardware ECC
+ * @ndtr0		Timing registers 0 value for this NAND chip
+ * @ndtr1		Timing registers 1 value for this NAND chip
+ * @selected:		Current active CS
+ * @nsels:		Number of CS lines required by the NAND chip
+ * @sels:		Array of CS lines descriptions
+ */
+struct marvell_nand_chip {
+	struct nand_chip chip;
+	struct list_head node;
+	const struct marvell_hw_ecc_layout *layout;
+	u32 ndtr0;
+	u32 ndtr1;
+	int addr_cyc;
+	int selected;
+	unsigned int nsels;
+	struct marvell_nand_chip_sel sels[0];
+};
+
+static inline struct marvell_nand_chip *to_marvell_nand(struct nand_chip *chip)
+{
+	return container_of(chip, struct marvell_nand_chip, chip);
+}
+
+static inline struct marvell_nand_chip_sel *to_nand_sel(struct marvell_nand_chip
+							*nand)
+{
+	return &nand->sels[nand->selected];
+}
+
+enum marvell_nfc_variant {
+	MARVELL_NFC_VARIANT_PXA3XX,
+	MARVELL_NFC_VARIANT_ARMADA370,
+	MARVELL_NFC_VARIANT_ARMADA_8K,
+};
+
+/*
+ * NAND controller capabilities for distinction between compatible strings
+ *
+ * @variant:		Board type
+ * @max_cs_nb:		Number of Chip Select lines available
+ * @max_rb_nb:		Number of Ready/Busy lines available
+ * @legacy_of_bindings	Indicates if DT parsing must be done using the old
+ *			fashion way
+ */
+struct marvell_nfc_caps {
+	enum marvell_nfc_variant variant;
+	unsigned int max_cs_nb;
+	unsigned int max_rb_nb;
+	bool legacy_of_bindings;
+};
+
+/*
+ * NAND controller structure: stores Marvell NAND controller information
+ *
+ * @controller:		Base controller structure
+ * @dev:		Parent device (used to print error messages)
+ * @regs:		NAND controller registers
+ * @ecc_clk:		ECC block clock, two times the NAND controller clock
+ * @complete:		Completion object to wait for NAND controller events
+ * @assigned_cs:	Bitmask describing already assigned CS lines
+ * @chips:		List containing all the NAND chips attached to
+ *			this NAND controller
+ * @caps:		NAND controller capabilities for each compatible string
+ * @buf:		Controller local buffer to store a part of the read
+ *			buffer when the read operation was not 8 bytes aligned
+ *			as is the FIFO.
+ * @buf_pos:		Position in the 'buf' buffer
+ */
+struct marvell_nfc {
+	struct nand_hw_control controller;
+	struct device *dev;
+	void __iomem *regs;
+	struct clk *ecc_clk;
+	struct completion complete;
+	unsigned long assigned_cs;
+	struct list_head chips;
+	const struct marvell_nfc_caps *caps;
+
+	/*
+	 * Buffer handling: @buf will be accessed byte-per-byter but also
+	 * int-per-int when exchanging data with the NAND controller FIFO,
+	 * 32-bit alignment is then required.
+	 */
+	u8 buf[FIFO_DEPTH] __aligned(sizeof(u32));
+	int buf_pos;
+};
+
+static inline struct marvell_nfc *to_marvell_nfc(struct nand_hw_control *ctrl)
+{
+	return container_of(ctrl, struct marvell_nfc, controller);
+}
+
+/*
+ * NAND controller timings expressed in NAND Controller clock cycles
+ *
+ * @tRP:		ND_nRE pulse width
+ * @tRH:		ND_nRE high duration
+ * @tWP:		ND_nWE pulse time
+ * @tWH:		ND_nWE high duration
+ * @tCS:		Enable signal setup time
+ * @tCH:		Enable signal hold time
+ * @tADL:		Address to write data delay
+ * @tAR:		ND_ALE low to ND_nRE low delay
+ * @tWHR:		ND_nWE high to ND_nRE low for status read
+ * @tRHW:		ND_nRE high duration, read to write delay
+ * @tR:			ND_nWE high to ND_nRE low for read
+ */
+struct marvell_nfc_timings {
+	/* NDTR0 fields */
+	unsigned int tRP;
+	unsigned int tRH;
+	unsigned int tWP;
+	unsigned int tWH;
+	unsigned int tCS;
+	unsigned int tCH;
+	unsigned int tADL;
+	/* NDTR1 fields */
+	unsigned int tAR;
+	unsigned int tWHR;
+	unsigned int tRHW;
+	unsigned int tR;
+};
+
+/*
+ * Derives a duration in numbers of clock cycles.
+ *
+ * @ps: Duration in pico-seconds
+ * @period_ns:  Clock period in nano-seconds
+ *
+ * Convert the duration in nano-seconds, then divide by the period and
+ * return the number of clock periods.
+ */
+#define TO_CYCLES(ps, period_ns) (DIV_ROUND_UP(ps / 1000, period_ns))
+
+/*
+ * NAND driver structure filled during the parsing of the ->exec_op() subop
+ * subset of instructions.
+ *
+ * @ndcb:		Array for the values of the NDCBx registers
+ * @cle_ale_delay_ns:	Optional delay after the last CMD or ADDR cycle
+ * @rdy_timeout_ms:	Timeout for waits on Ready/Busy pin
+ * @rdy_delay_ns:	Optional delay after waiting for the RB pin
+ * @data_delay_ns:	Optional delay after the data xfer
+ * @data_instr_idx:	Index of the data instruction in the subop
+ * @data_instr:		Pointer to the data instruction in the subop
+ */
+struct marvell_nfc_op {
+	u32 ndcb[4];
+	unsigned int cle_ale_delay_ns;
+	unsigned int rdy_timeout_ms;
+	unsigned int rdy_delay_ns;
+	unsigned int data_delay_ns;
+	unsigned int data_instr_idx;
+	const struct nand_op_instr *data_instr;
+};
+
+/*
+ * Internal helper to conditionnally apply a delay (from the above structure,
+ * most of the time).
+ */
+static void cond_delay(unsigned int ns)
+{
+	if (!ns)
+		return;
+
+	if (ns < 10000)
+		ndelay(ns);
+	else
+		udelay(DIV_ROUND_UP(ns, 1000));
+}
+
+/*
+ * Internal helper to mimic core functions whithout having to distinguish if
+ * this is the first read operation on the page or not and hence choose the
+ * right function.
+ */
+int read_page_data(struct nand_chip *chip, unsigned int page,
+		   unsigned int column, void *buf, unsigned int len)
+{
+	if (!column)
+		return nand_read_page_op(chip, page, column, buf, len);
+	else
+		return nand_change_read_column_op(chip, column, buf, len,
+						  false);
+}
+
+/*
+ * The controller has many flags that could generate interrupts, most of them
+ * are disabled and polling is used. For the very slow signals, using interrupts
+ * may relax the CPU charge.
+ */
+static void marvell_nfc_disable_int(struct marvell_nfc *nfc, u32 int_mask)
+{
+	u32 reg;
+
+	/* Writing 1 disables the interrupt */
+	reg = readl_relaxed(nfc->regs + NDCR);
+	writel_relaxed(reg | int_mask, nfc->regs + NDCR);
+}
+
+static void marvell_nfc_enable_int(struct marvell_nfc *nfc, u32 int_mask)
+{
+	u32 reg;
+
+	/* Writing 0 enables the interrupt */
+	reg = readl_relaxed(nfc->regs + NDCR);
+	writel_relaxed(reg & ~int_mask, nfc->regs + NDCR);
+}
+
+static void marvell_nfc_clear_int(struct marvell_nfc *nfc, u32 int_mask)
+{
+	writel_relaxed(int_mask, nfc->regs + NDSR);
+}
+
+/*
+ * The core may ask the controller to use only 8-bit accesses while usually
+ * using 16-bit accesses. Later function may blindly call this one with a
+ * boolean to indicate if 8-bit accesses must be enabled of disabled without
+ * knowing if 16-bit accesses are actually in use.
+ */
+static void marvell_nfc_force_byte_access(struct nand_chip *chip,
+					  bool force_8bit)
+{
+	struct marvell_nfc *nfc = to_marvell_nfc(chip->controller);
+	u32 ndcr;
+
+	if (!(chip->options & NAND_BUSWIDTH_16))
+		return;
+
+	ndcr = readl_relaxed(nfc->regs + NDCR);
+
+	if (force_8bit)
+		ndcr &= ~(NDCR_DWIDTH_M | NDCR_DWIDTH_C);
+	else
+		ndcr |= NDCR_DWIDTH_M | NDCR_DWIDTH_C;
+
+	writel_relaxed(ndcr, nfc->regs + NDCR);
+}
+
+/*
+ * Any time a command has to be sent to the controller, the following sequence
+ * has to be followed:
+ * - call marvell_nfc_prepare_cmd()
+ *      -> activate the ND_RUN bit that will kind of 'start a job'
+ *      -> wait the signal indicating the NFC is waiting for a command
+ * - send the command (cmd and address cycles)
+ * - enventually send or receive the data
+ * - call marvell_nfc_end_cmd() with the corresponding flag
+ *      -> wait the flag to be triggered or cancel the job with a timeout
+ *
+ * The following functions are helpers to do this job and keep in the
+ * specialized functions the code that really does the operations.
+ */
+static int marvell_nfc_prepare_cmd(struct nand_chip *chip)
+{
+	struct marvell_nfc *nfc = to_marvell_nfc(chip->controller);
+	u32 ndcr, val;
+	int ret;
+
+	/* Deassert ND_RUN and clear NDSR before issuing any command */
+	ndcr = readl_relaxed(nfc->regs + NDCR);
+	writel_relaxed(ndcr & ~NDCR_ND_RUN, nfc->regs + NDCR);
+	writel_relaxed(readl_relaxed(nfc->regs + NDSR), nfc->regs + NDSR);
+
+	/* Assert ND_RUN bit and wait the NFC to be ready */
+	writel_relaxed(ndcr | NDCR_ND_RUN, nfc->regs + NDCR);
+	ret = readl_relaxed_poll_timeout(nfc->regs + NDSR, val,
+					 val & NDSR_WRCMDREQ,
+					 POLL_PERIOD, POLL_TIMEOUT);
+	if (ret) {
+		dev_err(nfc->dev, "Timeout on WRCMDRE\n");
+		return -ETIMEDOUT;
+	}
+
+	/* Command may be written, clear WRCMDREQ status bit */
+	writel_relaxed(NDSR_WRCMDREQ, nfc->regs + NDSR);
+
+	return 0;
+}
+
+static void marvell_nfc_send_cmd(struct nand_chip *chip,
+				 struct marvell_nfc_op *nfc_op)
+{
+	struct marvell_nand_chip *marvell_nand = to_marvell_nand(chip);
+	struct marvell_nfc *nfc = to_marvell_nfc(chip->controller);
+
+	writel_relaxed(to_nand_sel(marvell_nand)->ndcb0_csel | nfc_op->ndcb[0],
+		       nfc->regs + NDCB0);
+	writel_relaxed(nfc_op->ndcb[1], nfc->regs + NDCB0);
+	writel(nfc_op->ndcb[2], nfc->regs + NDCB0);
+
+	/*
+	 * Write NDCB0 four times only if LEN_OVRD is set or if ADDR6 or ADDR7
+	 * fields are used.
+	 */
+	if (nfc_op->ndcb[0] & NDCB0_LEN_OVRD ||
+	    (nfc_op->ndcb[0] & NDCB0_ADDR_CYC(6)) == NDCB0_ADDR_CYC(6))
+		writel(nfc_op->ndcb[3], nfc->regs + NDCB0);
+}
+
+static int marvell_nfc_wait_ndrun(struct nand_chip *chip)
+{
+	struct marvell_nfc *nfc = to_marvell_nfc(chip->controller);
+	u32 val;
+	int ret;
+
+	/*
+	 * The command is being processed, wait for the ND_RUN bit to be
+	 * cleared by the NFC. If not, we must clear it by hand.
+	 */
+	ret = readl_relaxed_poll_timeout(nfc->regs + NDCR, val,
+					 (val & NDCR_ND_RUN) == 0,
+					 POLL_PERIOD, POLL_TIMEOUT);
+	if (ret) {
+		dev_err(nfc->dev, "Timeout on NAND controller run mode\n");
+		writel_relaxed(readl_relaxed(nfc->regs + NDCR) & ~NDCR_ND_RUN,
+			       nfc->regs + NDCR);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int marvell_nfc_end_cmd(struct nand_chip *chip, int flag,
+			       const char *label)
+{
+	struct marvell_nfc *nfc = to_marvell_nfc(chip->controller);
+	u32 val;
+	int ret;
+
+	ret = readl_relaxed_poll_timeout(nfc->regs + NDSR, val,
+					 val & flag,
+					 POLL_PERIOD, POLL_TIMEOUT);
+	if (ret) {
+		dev_err(nfc->dev, "Timeout on %s\n", label);
+		return ret;
+	}
+
+	writel_relaxed(flag, nfc->regs + NDSR);
+
+	return 0;
+}
+
+static int marvell_nfc_wait_cmdd(struct nand_chip *chip)
+{
+	struct marvell_nand_chip *marvell_nand = to_marvell_nand(chip);
+	int cs_flag = NDSR_CMDD(to_nand_sel(marvell_nand)->ndcb0_csel);
+
+	return marvell_nfc_end_cmd(chip, cs_flag, "CMDD");
+}
+
+static int marvell_nfc_wait_op(struct nand_chip *chip, unsigned int timeout_ms)
+{
+	struct marvell_nfc *nfc = to_marvell_nfc(chip->controller);
+	int ret;
+
+	/* Timeout is expressed in ms */
+	if (!timeout_ms)
+		timeout_ms = IRQ_TIMEOUT;
+
+	init_completion(&nfc->complete);
+
+	marvell_nfc_enable_int(nfc, NDCR_RDYM);
+	ret = wait_for_completion_timeout(&nfc->complete,
+					  msecs_to_jiffies(timeout_ms));
+	marvell_nfc_disable_int(nfc, NDCR_RDYM);
+	marvell_nfc_clear_int(nfc, NDSR_RDY(0) | NDSR_RDY(1));
+
+	if (!ret)
+		dev_err(nfc->dev, "Timeout waiting for RB signal\n");
+
+	return ret ? 0 : -ETIMEDOUT;
+}
+
+static void marvell_nfc_select_chip(struct mtd_info *mtd, int chip)
+{
+	struct nand_chip *nand = mtd_to_nand(mtd);
+	struct marvell_nand_chip *marvell_nand = to_marvell_nand(nand);
+	struct marvell_nfc *nfc = to_marvell_nfc(nand->controller);
+	u32 ndcr;
+
+	if (chip < 0 || chip >= marvell_nand->nsels)
+		return;
+
+	if (chip == marvell_nand->selected)
+		return;
+
+	/*
+	 * Do not change the timing registers when using the DT property
+	 * marvell,nand-keep-config; in that case ->ndtr0 and ->ndtr1 from the
+	 * marvell_nand structure are supposedly empty.
+	 */
+	if (marvell_nand->ndtr0 && marvell_nand->ndtr1) {
+		writel_relaxed(marvell_nand->ndtr0, nfc->regs + NDTR0);
+		writel_relaxed(marvell_nand->ndtr1, nfc->regs + NDTR1);
+	}
+
+	ndcr = readl_relaxed(nfc->regs + NDCR);
+
+	/* Ensure controller is not blocked in operation */
+	ndcr &= ~NDCR_ND_RUN;
+
+	/* Adapt bus width */
+	if (nand->options & NAND_BUSWIDTH_16)
+		ndcr |= NDCR_DWIDTH_M | NDCR_DWIDTH_C;
+
+	/*
+	 * Page size as seen by the controller, either 512B or 2kiB. This size
+	 * will be the reference for the controller when using LEN_OVRD.
+	 */
+	ndcr |= NDCR_PAGE_SZ(mtd->writesize);
+
+	/* Update the control register */
+	writel_relaxed(ndcr,  nfc->regs + NDCR);
+
+	/* Also reset the interrupt status register */
+	marvell_nfc_clear_int(nfc, NDCR_ALL_INT);
+
+	marvell_nand->selected = chip;
+}
+
+static irqreturn_t marvell_nfc_isr(int irq, void *dev_id)
+{
+	struct marvell_nfc *nfc = dev_id;
+	u32 st = readl_relaxed(nfc->regs + NDSR);
+	u32 ien = (~readl_relaxed(nfc->regs + NDCR)) & NDCR_ALL_INT;
+
+	/*
+	 * RDY interrupt mask is one bit in NDCR while there are two status
+	 * bit in NDSR (RDY[cs0/cs2] and RDY[cs1/cs3]).
+	 */
+	if (st & NDSR_RDY(1))
+		st |= NDSR_RDY(0);
+
+	if (!(st & ien))
+		return IRQ_NONE;
+
+	marvell_nfc_disable_int(nfc, st & NDCR_ALL_INT);
+
+	complete(&nfc->complete);
+
+	return IRQ_HANDLED;
+}
+
+/* HW ECC related functions */
+static void marvell_nfc_hw_ecc_enable(struct nand_chip *chip)
+{
+	struct marvell_nfc *nfc = to_marvell_nfc(chip->controller);
+	u32 ndcr = readl_relaxed(nfc->regs + NDCR);
+
+	if (!(ndcr & NDCR_ECC_EN)) {
+		writel_relaxed(ndcr | NDCR_ECC_EN | NDCR_SPARE_EN,
+			       nfc->regs + NDCR);
+
+		/*
+		 * When enabling BCH, set threshold to 0 to always know the
+		 * number of corrected bitflips.
+		 */
+		if (chip->ecc.algo == NAND_ECC_BCH)
+			writel_relaxed(NDECCTRL_BCH_EN, nfc->regs + NDECCCTRL);
+	}
+}
+
+static void marvell_nfc_hw_ecc_disable(struct nand_chip *chip)
+{
+	struct marvell_nfc *nfc = to_marvell_nfc(chip->controller);
+	u32 ndcr = readl_relaxed(nfc->regs + NDCR);
+
+	if (ndcr & NDCR_ECC_EN) {
+		writel_relaxed(ndcr & ~(NDCR_ECC_EN | NDCR_SPARE_EN),
+			       nfc->regs + NDCR);
+		if (chip->ecc.algo == NAND_ECC_BCH)
+			writel_relaxed(0, nfc->regs + NDECCCTRL);
+	}
+}
+
+static void marvell_nfc_hw_ecc_correct(struct nand_chip *chip,
+				       u8 *data, int data_len,
+				       u8 *oob, int oob_len,
+				       unsigned int *max_bitflips)
+{
+	struct mtd_info *mtd = nand_to_mtd(chip);
+	struct marvell_nfc *nfc = to_marvell_nfc(chip->controller);
+	int bf = 0;
+	u32 ndsr;
+
+	ndsr = readl_relaxed(nfc->regs + NDSR);
+
+	/* Check uncorrectable error flag */
+	if (ndsr & NDSR_UNCERR) {
+		writel_relaxed(ndsr, nfc->regs + NDSR);
+
+		/*
+		 * Blank pages (all 0xFF) with no ECC are recognized as bad
+		 * because hardware ECC engine expects non-empty ECC values
+		 * in that case, so whenever an uncorrectable error occurs,
+		 * check if the page is actually blank or not.
+		 *
+		 * It is important to check the emptyness only on oob_len,
+		 * which only covers the spare bytes because after a read with
+		 * ECC enabled, the ECC bytes in the buffer have been set by the
+		 * ECC engine, so they are not 0xFF.
+		 */
+		if (!data)
+			data_len = 0;
+		if (!oob)
+			oob_len = 0;
+		bf = nand_check_erased_ecc_chunk(data, data_len, NULL, 0,
+						 oob, oob_len,
+						 chip->ecc.strength);
+		if (bf < 0) {
+			mtd->ecc_stats.failed++;
+			return;
+		}
+	}
+
+	/* Check correctable error flag */
+	if (ndsr & NDSR_CORERR) {
+		writel_relaxed(ndsr, nfc->regs + NDSR);
+
+		if (chip->ecc.algo == NAND_ECC_BCH)
+			bf = NDSR_ERRCNT(ndsr);
+		else
+			bf = 1;
+	}
+
+	/*
+	 * Derive max_bitflips either from the number of bitflips detected by
+	 * the hardware ECC engine or by nand_check_erased_ecc_chunk().
+	 */
+	mtd->ecc_stats.corrected += bf;
+	*max_bitflips = max_t(unsigned int, *max_bitflips, bf);
+}
+
+/* Reads with HW ECC */
+static int marvell_nfc_hw_ecc_hmg_read_page(struct mtd_info *mtd,
+					    struct nand_chip *chip,
+					    u8 *buf, int oob_required,
+					    int page)
+{
+	struct marvell_nand_chip *marvell_nand = to_marvell_nand(chip);
+	struct marvell_nfc *nfc = to_marvell_nfc(chip->controller);
+	const struct marvell_hw_ecc_layout *lt = to_marvell_nand(chip)->layout;
+	int max_bitflips = 0, ret = 0, i;
+	u8 *data, *oob;
+	struct marvell_nfc_op nfc_op = {
+		.ndcb[0] = NDCB0_CMD_TYPE(TYPE_READ) |
+			   NDCB0_CMD_XTYPE(XTYPE_MONOLITHIC_RW) |
+			   NDCB0_ADDR_CYC(marvell_nand->addr_cyc) |
+			   NDCB0_DBC |
+			   NDCB0_CMD1(NAND_CMD_READ0) |
+			   NDCB0_CMD2(NAND_CMD_READSTART),
+		.ndcb[1] = NDCB1_ADDRS(page),
+		.ndcb[2] = NDCB2_ADDR5(page),
+	};
+
+	/*
+	 * With Hamming, OOB is not fully used (and thus not read entirely), not
+	 * expected bytes could show up at the end of the OOB buffer if not
+	 * explicitly erased.
+	 */
+	if (oob_required)
+		memset(chip->oob_poi, 0xFF, mtd->oobsize);
+
+	ret = marvell_nfc_prepare_cmd(chip);
+	if (ret)
+		return ret;
+
+	marvell_nfc_hw_ecc_enable(chip);
+
+	data = buf;
+	oob = chip->oob_poi;
+
+	/*
+	 * Reading spare area is mandatory when using HW ECC or read operation
+	 * will trigger uncorrectable ECC errors, but do not read ECC here.
+	 */
+	marvell_nfc_send_cmd(chip, &nfc_op);
+
+	marvell_nfc_end_cmd(chip, NDSR_RDDREQ,
+			    "RDDREQ while draining FIFO (data)");
+
+	/* Read the data... */
+	if (data)
+		ioread32_rep(nfc->regs + NDDB, data, FIFO_REP(lt->data_bytes));
+	else
+		for (i = 0; i < FIFO_REP(lt->data_bytes); i++)
+			ioread32_rep(nfc->regs + NDDB, nfc->buf, FIFO_DEPTH_32);
+
+	/* ...then the spare bytes */
+	ioread32_rep(nfc->regs + NDDB, oob, FIFO_REP(lt->spare_bytes));
+
+	marvell_nfc_hw_ecc_correct(chip, data, lt->data_bytes,
+				   oob, lt->spare_bytes, &max_bitflips);
+
+	marvell_nfc_hw_ecc_disable(chip);
+
+	if (oob_required) {
+		/* Read ECC bytes without ECC enabled */
+		nand_read_page_op(chip, page,
+				  lt->data_bytes + lt->spare_bytes,
+				  oob + lt->spare_bytes, lt->ecc_bytes);
+	}
+
+	return max_bitflips;
+}
+
+static void marvell_nfc_hw_ecc_bch_read_chunk(struct nand_chip *chip, int chunk,
+					      u8 *data, int data_len,
+					      u8 *oob, int oob_len,
+					      int oob_required, int page)
+{
+	struct marvell_nand_chip *marvell_nand = to_marvell_nand(chip);
+	struct marvell_nfc *nfc = to_marvell_nfc(chip->controller);
+	const struct marvell_hw_ecc_layout *lt = to_marvell_nand(chip)->layout;
+	int i, j;
+	struct marvell_nfc_op nfc_op = {
+		.ndcb[0] = NDCB0_CMD_TYPE(TYPE_READ) |
+			   NDCB0_ADDR_CYC(marvell_nand->addr_cyc) |
+			   NDCB0_LEN_OVRD,
+		.ndcb[1] = NDCB1_ADDRS(page),
+		.ndcb[2] = NDCB2_ADDR5(page),
+	};
+
+	/*
+	 * Reading spare area is mandatory when using HW ECC or read operation
+	 * will trigger uncorrectable ECC errors, but do not read ECC here.
+	 */
+	nfc_op.ndcb[3] = data_len + oob_len;
+
+	if (marvell_nfc_prepare_cmd(chip))
+		return;
+
+	if (chunk == 0)
+		nfc_op.ndcb[0] |= NDCB0_DBC |
+				  NDCB0_CMD1(NAND_CMD_READ0) |
+				  NDCB0_CMD2(NAND_CMD_READSTART);
+
+	/*
+	 * Trigger the naked read operation only on the last chunk.
+	 * Otherwise, use monolithic read.
+	 */
+	if ((lt->last_chunk_cnt && chunk == lt->full_chunk_cnt) ||
+	    (!lt->last_chunk_cnt && chunk == lt->full_chunk_cnt - 1))
+		nfc_op.ndcb[0] |= NDCB0_CMD_XTYPE(XTYPE_LAST_NAKED_RW);
+	else
+		nfc_op.ndcb[0] |= NDCB0_CMD_XTYPE(XTYPE_MONOLITHIC_RW);
+
+	marvell_nfc_send_cmd(chip, &nfc_op);
+
+	/*
+	 * According to the datasheet, when reading from NDDB
+	 * with BCH enabled, after each 32 bytes reads, we
+	 * have to make sure that the NDSR.RDDREQ bit is set.
+	 *
+	 * Drain the FIFO, 8 32-bit reads at a time, and skip
+	 * the polling on the last read.
+	 *
+	 * Length is a multiple of 32 bytes, hence it is a multiple of 8 too.
+	 *
+	 */
+	for (i = 0; i < data_len; i += FIFO_DEPTH * sizeof(u32)) {
+		marvell_nfc_end_cmd(chip, NDSR_RDDREQ,
+				    "RDDREQ while draining FIFO (data)");
+		if (data) {
+			ioread32_rep(nfc->regs + NDDB, data,
+				     FIFO_DEPTH_32 * sizeof(u32));
+			data += FIFO_DEPTH * sizeof(u32);
+		} else {
+			for (j = 0; j < sizeof(u32); j++)
+				ioread32_rep(nfc->regs + NDDB, nfc->buf,
+					     FIFO_DEPTH_32);
+		}
+	}
+
+	for (i = 0; i < oob_len; i += FIFO_DEPTH * 4) {
+		marvell_nfc_end_cmd(chip, NDSR_RDDREQ,
+				    "RDDREQ while draining FIFO (OOB)");
+		ioread32_rep(nfc->regs + NDDB, oob, FIFO_DEPTH_32 * 4);
+		oob += FIFO_DEPTH * 4;
+	}
+}
+
+static int marvell_nfc_hw_ecc_bch_read_page(struct mtd_info *mtd,
+					    struct nand_chip *chip,
+					    u8 *buf, int oob_required,
+					    int page)
+{
+	const struct marvell_hw_ecc_layout *lt = to_marvell_nand(chip)->layout;
+	int nchunks = lt->full_chunk_cnt + lt->last_chunk_cnt;
+	int max_bitflips = 0;
+	u8 *data, *oob;
+	int chunk, data_len, oob_len, ecc_len;
+	/* Following sizes are used to read the ECC bytes after ECC operation */
+	int fixed_oob_size = lt->spare_bytes + lt->ecc_bytes;
+	int fixed_chunk_size = lt->data_bytes + fixed_oob_size;
+
+	/*
+	 * With BCH, OOB is not fully used (and thus not read entirely), not
+	 * expected bytes could show up at the end of the OOB buffer if not
+	 * explicitly erased.
+	 */
+	if (oob_required)
+		memset(chip->oob_poi, 0xFF, mtd->oobsize);
+
+	marvell_nfc_hw_ecc_enable(chip);
+
+	for (chunk = 0; chunk < nchunks; chunk++) {
+		if (chunk == 0) {
+			/* Init pointers to iterate through the chunks */
+			if (buf)
+				data = buf;
+			else
+				data = NULL;
+			oob = chip->oob_poi;
+		} else {
+			/* Update pointers */
+			if (data)
+				data += lt->data_bytes;
+			oob += (lt->spare_bytes + lt->ecc_bytes + 2);
+		}
+
+		/* Update length */
+		if (chunk < lt->full_chunk_cnt) {
+			data_len = lt->data_bytes;
+			oob_len = lt->spare_bytes;
+			ecc_len = lt->ecc_bytes;
+		} else {
+			data_len = lt->last_data_bytes;
+			oob_len = lt->last_spare_bytes;
+			ecc_len = lt->last_ecc_bytes;
+		}
+
+		/* Read the chunk and detect number of bitflips */
+		marvell_nfc_hw_ecc_bch_read_chunk(chip, chunk, data, data_len,
+						  oob, oob_len, oob_required,
+						  page);
+
+		marvell_nfc_hw_ecc_correct(chip, data, data_len,
+					   oob, oob_len, &max_bitflips);
+	}
+
+	marvell_nfc_hw_ecc_disable(chip);
+
+	if (!oob_required)
+		return max_bitflips;
+
+	/* Read ECC bytes without ECC enabled */
+	for (chunk = 0; chunk < lt->full_chunk_cnt; chunk++)
+		read_page_data(chip, page,
+			       ((chunk + 1) * (fixed_chunk_size)) -
+			       lt->ecc_bytes,
+			       chip->oob_poi + ((chunk + 1) *
+						(fixed_oob_size + 2) -
+						(lt->ecc_bytes + 2)),
+			       lt->ecc_bytes);
+
+	if (lt->last_chunk_cnt)
+		read_page_data(chip, page,
+			       (lt->full_chunk_cnt * fixed_chunk_size) +
+			       lt->last_data_bytes + lt->last_spare_bytes,
+			       chip->oob_poi + (lt->full_chunk_cnt *
+						(fixed_oob_size + 2)) +
+			       lt->last_spare_bytes,
+			       lt->last_ecc_bytes);
+
+	return max_bitflips;
+}
+
+static int marvell_nfc_hw_ecc_read_oob(struct mtd_info *mtd,
+				       struct nand_chip *chip, int page)
+{
+	return chip->ecc.read_page(mtd, chip, NULL, true, page);
+}
+
+/* Raw reads with HW ECC */
+static int marvell_nfc_hw_ecc_read_page_raw(struct mtd_info *mtd,
+					    struct nand_chip *chip, u8 *buf,
+					    int oob_required, int page)
+{
+	const struct marvell_hw_ecc_layout *lt = to_marvell_nand(chip)->layout;
+	int chunk_size = lt->data_bytes + lt->spare_bytes + lt->ecc_bytes;
+	u8 *oob = chip->oob_poi;
+	int chunk;
+
+	if (oob_required)
+		memset(chip->oob_poi, 0xFF, mtd->oobsize);
+
+	for (chunk = 0; chunk < lt->full_chunk_cnt; chunk++) {
+		read_page_data(chip, page, chunk * chunk_size, buf,
+			       lt->data_bytes);
+		buf += lt->data_bytes;
+
+		if (oob_required) {
+			nand_read_data_op(chip, oob, lt->spare_bytes +
+					  lt->ecc_bytes, false);
+			/* Pad user data with 2 bytes when using BCH (30B) */
+			oob += lt->spare_bytes + lt->ecc_bytes + 2;
+		}
+	}
+
+	if (!lt->last_chunk_cnt)
+		return 0;
+
+	read_page_data(chip, page, chunk * chunk_size, buf,
+		       lt->last_data_bytes);
+	if (oob_required)
+		nand_read_data_op(chip, oob, lt->last_spare_bytes +
+				  lt->last_ecc_bytes, false);
+
+	return 0;
+}
+
+static int marvell_nfc_hw_ecc_read_oob_raw(struct mtd_info *mtd,
+					   struct nand_chip *chip, int page)
+{
+	const struct marvell_hw_ecc_layout *lt = to_marvell_nand(chip)->layout;
+	u8 *oob = chip->oob_poi;
+	int chunk_size = lt->data_bytes + lt->spare_bytes + lt->ecc_bytes;
+	int chunk;
+
+	for (chunk = 0; chunk < lt->full_chunk_cnt; chunk++) {
+		/* Move NAND pointer to the next chunk of OOB data */
+		read_page_data(chip, page,
+			       chunk * chunk_size + lt->data_bytes,
+			       oob, lt->spare_bytes + lt->ecc_bytes);
+		/* Pad user data with 2 bytes when using BCH (30B) */
+		oob += lt->spare_bytes + lt->ecc_bytes + 2;
+	}
+
+	if (lt->last_chunk_cnt)
+		nand_read_data_op(chip, oob,
+				  lt->last_spare_bytes + lt->last_ecc_bytes,
+				  false);
+
+	return 0;
+}
+
+/* Writes with HW ECC */
+static int marvell_nfc_hw_ecc_hmg_write_page(struct mtd_info *mtd,
+					     struct nand_chip *chip,
+					     const u8 *buf,
+					     int oob_required, int page)
+{
+	struct marvell_nand_chip *marvell_nand = to_marvell_nand(chip);
+	struct marvell_nfc *nfc = to_marvell_nfc(chip->controller);
+	const struct marvell_hw_ecc_layout *lt = to_marvell_nand(chip)->layout;
+	const u8 *data = buf, *oob = chip->oob_poi;
+	int ret, i;
+	struct marvell_nfc_op nfc_op = {
+		.ndcb[0] = NDCB0_CMD_TYPE(TYPE_WRITE) |
+			   NDCB0_CMD_XTYPE(XTYPE_MONOLITHIC_RW) |
+			   NDCB0_ADDR_CYC(marvell_nand->addr_cyc) |
+			   NDCB0_CMD1(NAND_CMD_SEQIN),
+		.ndcb[1] = NDCB1_ADDRS(page),
+		.ndcb[2] = NDCB2_ADDR5(page),
+	};
+
+	ret = marvell_nfc_prepare_cmd(chip);
+	if (ret)
+		return ret;
+
+	marvell_nfc_hw_ecc_enable(chip);
+
+		marvell_nfc_send_cmd(chip, &nfc_op);
+
+	marvell_nfc_end_cmd(chip, NDSR_WRDREQ,
+			    "WRDREQ while loading FIFO (data)");
+
+	/* Write the data. If buf is empty, write empty bytes (0xFF) */
+	if (data) {
+		iowrite32_rep(nfc->regs + NDDB, data, FIFO_REP(lt->data_bytes));
+	} else {
+		data = nfc->buf;
+		memset(nfc->buf, 0xFF, FIFO_DEPTH);
+		for (i = 0; i < FIFO_REP(lt->data_bytes) / FIFO_DEPTH_32; i++)
+			iowrite32_rep(nfc->regs + NDDB, data, FIFO_DEPTH_32);
+	}
+
+	/* Then write the OOB data */
+	iowrite32_rep(nfc->regs + NDDB, oob, FIFO_REP(lt->spare_bytes));
+
+	ret = marvell_nfc_wait_op(chip,
+				  chip->data_interface.timings.sdr.tPROG_max);
+
+	marvell_nfc_hw_ecc_disable(chip);
+
+	if (ret & NAND_STATUS_FAIL)
+		return -EIO;
+
+	return 0;
+}
+
+static void marvell_nfc_hw_ecc_bch_write_chunk(struct nand_chip *chip,
+					       int chunk, const u8 *data,
+					       u8 *oob, int oob_required,
+					       int page)
+{
+	struct marvell_nand_chip *marvell_nand = to_marvell_nand(chip);
+	struct marvell_nfc *nfc = to_marvell_nfc(chip->controller);
+	const struct marvell_hw_ecc_layout *lt = to_marvell_nand(chip)->layout;
+	int data_len, oob_len, i;
+	struct marvell_nfc_op nfc_op = {
+		.ndcb[0] = NDCB0_CMD_TYPE(TYPE_WRITE) | NDCB0_LEN_OVRD,
+	};
+
+	/* OOB area to write is only spare area when using HW ECC */
+	if (chunk < lt->full_chunk_cnt) {
+		data_len = lt->data_bytes;
+		oob_len = lt->spare_bytes;
+	} else {
+		data_len = lt->last_data_bytes;
+		oob_len = lt->last_spare_bytes;
+	}
+
+	nfc_op.ndcb[3] = data_len + oob_len;
+
+	/*
+	 * First operation dispatches the CMD_SEQIN command, issue the address
+	 * cycles and asks for the first chunk of data.
+	 * Last operation dispatches the PAGEPROG command and also asks for the
+	 * last chunk of data.
+	 * All operations in the middle (if any) will issue a naked write and
+	 * also ask for data.
+	 */
+	if (chunk == 0) {
+		nfc_op.ndcb[0] |= NDCB0_CMD_XTYPE(XTYPE_WRITE_DISPATCH) |
+				  NDCB0_ADDR_CYC(marvell_nand->addr_cyc) |
+				  NDCB0_CMD1(NAND_CMD_SEQIN);
+		nfc_op.ndcb[1] |= NDCB1_ADDRS(page);
+		nfc_op.ndcb[2] |= NDCB2_ADDR5(page);
+	} else if (
+		(lt->last_chunk_cnt && (chunk == lt->full_chunk_cnt)) ||
+		(!lt->last_chunk_cnt &&	(chunk == lt->full_chunk_cnt - 1))) {
+		nfc_op.ndcb[0] |= NDCB0_CMD2(NAND_CMD_PAGEPROG) | NDCB0_DBC |
+				  NDCB0_CMD_XTYPE(XTYPE_LAST_NAKED_RW);
+	} else {
+		nfc_op.ndcb[0] |= NDCB0_CMD_XTYPE(XTYPE_NAKED_RW);
+	}
+
+	/*
+	 * If this is the first chunk, the previous command also embedded
+	 * the write operation, no need to repeat it.
+	 */
+	if (marvell_nfc_prepare_cmd(chip))
+		return;
+
+	marvell_nfc_send_cmd(chip, &nfc_op);
+
+	marvell_nfc_end_cmd(chip, NDSR_WRDREQ,
+			    "WRDREQ while loading FIFO (data)");
+
+	/* Effectively write the data to the data buffer */
+	if (data) {
+		data += chunk * lt->data_bytes;
+		iowrite32_rep(nfc->regs + NDDB, data, FIFO_REP(data_len));
+	} else {
+		memset(nfc->buf, 0xFF, FIFO_DEPTH);
+		data = nfc->buf;
+		for (i = 0; i < FIFO_REP(data_len) / FIFO_DEPTH_32; i++)
+			iowrite32_rep(nfc->regs + NDDB, data, FIFO_DEPTH_32);
+	}
+
+	/* Pad user data with 2 bytes when using BCH (30B) */
+	oob += chunk * lt->spare_bytes;
+	iowrite32_rep(nfc->regs + NDDB, oob, FIFO_REP(oob_len));
+}
+
+static int marvell_nfc_hw_ecc_bch_write_page(struct mtd_info *mtd,
+					     struct nand_chip *chip,
+					     const u8 *buf,
+					     int oob_required, int page)
+{
+	const struct marvell_hw_ecc_layout *lt = to_marvell_nand(chip)->layout;
+	int nchunks = lt->full_chunk_cnt + lt->last_chunk_cnt;
+	int chunk, ret;
+
+	marvell_nfc_hw_ecc_enable(chip);
+
+	for (chunk = 0; chunk < nchunks; chunk++) {
+		marvell_nfc_hw_ecc_bch_write_chunk(chip, chunk, buf,
+						   chip->oob_poi,
+						   oob_required, page);
+		/*
+		 * Waiting only for CMDD or PAGED is not enough, ECC are
+		 * partially written. No flag is set once the operation is
+		 * really finished but the ND_RUN bit is cleared, so wait for it
+		 * before stepping into the next command.
+		 */
+		marvell_nfc_wait_ndrun(chip);
+	}
+
+	ret = marvell_nfc_wait_op(chip,
+				  chip->data_interface.timings.sdr.tPROG_max);
+
+	marvell_nfc_hw_ecc_disable(chip);
+
+	if (ret & NAND_STATUS_FAIL)
+		return -EIO;
+
+	return 0;
+}
+
+static int marvell_nfc_hw_ecc_write_oob(struct mtd_info *mtd,
+					struct nand_chip *chip, int page)
+{
+	return chip->ecc.write_page(mtd, chip, NULL, true, page);
+}
+
+/* Raw writes with HW ECC */
+static int marvell_nfc_hw_ecc_write_page_raw(struct mtd_info *mtd,
+					     struct nand_chip *chip,
+					     const u8 *buf,
+					     int oob_required, int page)
+{
+	const struct marvell_hw_ecc_layout *lt = to_marvell_nand(chip)->layout;
+	int nchunks = lt->full_chunk_cnt + lt->last_chunk_cnt;
+	int oob_size = lt->spare_bytes + lt->ecc_bytes;
+	int last_oob_size = lt->last_spare_bytes + lt->last_ecc_bytes;
+	int chunk;
+
+	nand_prog_page_begin_op(chip, page, 0, NULL, 0);
+
+	for (chunk = 0; chunk < nchunks; chunk++) {
+		/*
+		 * OOB are not 8-bytes aligned anyway so change the column
+		 * at each cycle
+		 */
+		nand_change_write_column_op(chip, chunk * (lt->data_bytes +
+							   oob_size),
+					    NULL, 0, false);
+
+		if (chunk < lt->full_chunk_cnt)
+			nand_write_data_op(chip, buf + (chunk * lt->data_bytes),
+					   lt->data_bytes, false);
+		else
+			nand_write_data_op(chip, buf + (chunk * lt->data_bytes),
+					   lt->last_data_bytes, false);
+
+		if (!oob_required)
+			continue;
+
+		if (chunk < lt->full_chunk_cnt)
+			nand_write_data_op(chip, chip->oob_poi +
+					   (chunk * (oob_size + 2)),
+					   oob_size, false);
+		else
+			nand_write_data_op(chip, chip->oob_poi +
+					   (chunk * (oob_size + 2)),
+					   last_oob_size, false);
+	}
+
+	return nand_prog_page_end_op(chip);
+}
+
+static int marvell_nfc_hw_ecc_write_oob_raw(struct mtd_info *mtd,
+					    struct nand_chip *chip, int page)
+{
+	const struct marvell_hw_ecc_layout *lt = to_marvell_nand(chip)->layout;
+	int chunk_size = lt->data_bytes + lt->spare_bytes + lt->ecc_bytes;
+	int nchunks = lt->full_chunk_cnt + lt->last_chunk_cnt;
+	int oob_size = lt->spare_bytes + lt->ecc_bytes;
+	int last_oob_size = lt->last_spare_bytes + lt->last_ecc_bytes;
+	int chunk;
+
+	nand_prog_page_begin_op(chip, page, 0, NULL, 0);
+
+	for (chunk = 0; chunk < nchunks; chunk++) {
+		nand_change_write_column_op(chip, lt->data_bytes +
+					    (chunk * chunk_size), NULL, 0,
+					    false);
+
+		if (chunk < lt->full_chunk_cnt)
+			nand_write_data_op(chip, chip->oob_poi +
+					   (chunk * (oob_size + 2)),
+					   oob_size, false);
+		else
+			nand_write_data_op(chip, chip->oob_poi +
+					   (chunk * (oob_size + 2)),
+					   last_oob_size, false);
+	}
+
+	return nand_prog_page_end_op(chip);
+}
+
+/* NAND framework ->exec_op() hooks and related helpers */
+static void marvell_nfc_parse_instructions(const struct nand_subop *subop,
+					   struct marvell_nfc_op *nfc_op)
+{
+	const struct nand_op_instr *instr = NULL;
+	bool first_cmd = true;
+	unsigned int op_id;
+	int i;
+
+	/* Reset the input structure as most of its fields will be OR'ed */
+	memset(nfc_op, 0, sizeof(struct marvell_nfc_op));
+
+	for (op_id = 0; op_id < subop->ninstrs; op_id++) {
+		unsigned int offset, naddrs;
+		const u8 *addrs;
+		int len = nand_subop_get_data_len(subop, op_id);
+
+		instr = &subop->instrs[op_id];
+
+		switch (instr->type) {
+		case NAND_OP_CMD_INSTR:
+			pr_debug("  ->CMD      [0x%02x]\n", instr->cmd.opcode);
+
+			if (first_cmd)
+				nfc_op->ndcb[0] |=
+					NDCB0_CMD1(instr->cmd.opcode);
+			else
+				nfc_op->ndcb[0] |=
+					NDCB0_CMD2(instr->cmd.opcode) |
+					NDCB0_DBC;
+
+			nfc_op->cle_ale_delay_ns = instr->delay_ns;
+			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->addr.addrs[offset];
+
+			pr_debug("  ->ADDR     [%d cyc]", naddrs);
+
+			nfc_op->ndcb[0] |= NDCB0_ADDR_CYC(naddrs);
+
+			for (i = 0; i < min_t(unsigned int, 4, naddrs); i++)
+				nfc_op->ndcb[1] |= addrs[i] << (8 * i);
+
+			if (naddrs >= 5)
+				nfc_op->ndcb[2] |= NDCB2_ADDR5(addrs[5]);
+			if (naddrs >= 6)
+				nfc_op->ndcb[3] |= NDCB3_ADDR6(addrs[6]);
+			if (naddrs == 7)
+				nfc_op->ndcb[3] |= NDCB3_ADDR7(addrs[7]);
+
+			nfc_op->cle_ale_delay_ns = instr->delay_ns;
+			break;
+
+		case NAND_OP_DATA_IN_INSTR:
+			pr_debug("  ->DATA_IN  [%d B%s]\n", len,
+				 instr->data.force_8bit ? ", force 8-bit" : "");
+
+			nfc_op->data_instr = instr;
+			nfc_op->data_instr_idx = op_id;
+			nfc_op->ndcb[0] |=
+				NDCB0_CMD_TYPE(TYPE_READ) |
+				NDCB0_CMD_XTYPE(XTYPE_MONOLITHIC_RW) |
+				NDCB0_LEN_OVRD;
+			nfc_op->ndcb[3] |= round_up(len, FIFO_DEPTH);
+			nfc_op->data_delay_ns = instr->delay_ns;
+			break;
+
+		case NAND_OP_DATA_OUT_INSTR:
+			pr_debug("  ->DATA_OUT [%d B%s]\n", len,
+				 instr->data.force_8bit ? ", force 8-bit" : "");
+
+			nfc_op->data_instr = instr;
+			nfc_op->data_instr_idx = op_id;
+			nfc_op->ndcb[0] |=
+				NDCB0_CMD_TYPE(TYPE_WRITE) |
+				NDCB0_CMD_XTYPE(XTYPE_MONOLITHIC_RW) |
+				NDCB0_LEN_OVRD;
+			nfc_op->ndcb[3] |= round_up(len, FIFO_DEPTH);
+			nfc_op->data_delay_ns = instr->delay_ns;
+			break;
+
+		case NAND_OP_WAITRDY_INSTR:
+			pr_debug("  ->WAITRDY  [max %d ms]\n",
+				 instr->waitrdy.timeout_ms);
+
+			nfc_op->rdy_timeout_ms = instr->waitrdy.timeout_ms;
+			nfc_op->rdy_delay_ns = instr->delay_ns;
+			break;
+		}
+	}
+}
+
+static void marvell_nfc_xfer_data(struct nand_chip *chip,
+				  const struct nand_subop *subop,
+				  struct marvell_nfc_op *nfc_op)
+{
+	struct marvell_nfc *nfc = to_marvell_nfc(chip->controller);
+	unsigned int op_id = nfc_op->data_instr_idx;
+	int len = nand_subop_get_data_len(subop, op_id);
+	int offset = nand_subop_get_data_start_off(subop, op_id);
+	int last_len = len % FIFO_DEPTH;
+	int last_full_offset = round_down(len, FIFO_DEPTH);
+	const struct nand_op_instr *instr = nfc_op->data_instr;
+	u8 *in;
+	const u8 *out;
+	int i;
+
+	if (instr->data.force_8bit)
+		marvell_nfc_force_byte_access(chip, true);
+
+	if (instr->type == NAND_OP_DATA_IN_INSTR) {
+		in = &((u8 *)instr->data.in)[offset];
+
+		for (i = 0; i < last_full_offset; i += FIFO_DEPTH)
+			ioread32_rep(nfc->regs + NDDB,
+				     &((u32 *)in)[i / sizeof(u32)],
+				     FIFO_DEPTH_32);
+
+		if (last_len) {
+			ioread32_rep(nfc->regs + NDDB, nfc->buf, FIFO_DEPTH_32);
+			memcpy(&in[last_full_offset], nfc->buf, last_len);
+		}
+	} else {
+		out = &((const u8 *)instr->data.out)[offset];
+
+		for (i = 0; i < last_full_offset; i += FIFO_DEPTH)
+			iowrite32_rep(nfc->regs + NDDB,
+				      &((u32 *)out)[i / sizeof(u32)],
+				      FIFO_DEPTH_32);
+
+		if (last_len) {
+			memcpy(nfc->buf, &out[last_full_offset], last_len);
+			iowrite32_rep(nfc->regs + NDDB, nfc->buf,
+				      FIFO_DEPTH_32);
+		}
+	}
+
+	if (instr->data.force_8bit)
+		marvell_nfc_force_byte_access(chip, false);
+}
+
+static int marvell_nfc_monolithic_access_exec(struct nand_chip *chip,
+					      const struct nand_subop *subop)
+{
+	struct marvell_nfc_op nfc_op;
+	bool reading;
+	int ret;
+
+	marvell_nfc_parse_instructions(subop, &nfc_op);
+	reading = nfc_op.data_instr->type == NAND_OP_DATA_IN_INSTR;
+
+	ret = marvell_nfc_prepare_cmd(chip);
+	if (ret)
+		return ret;
+
+	marvell_nfc_send_cmd(chip, &nfc_op);
+	ret = marvell_nfc_end_cmd(chip, NDSR_RDDREQ | NDSR_WRDREQ,
+				  "RDDREQ/WRDREQ while draining raw data");
+	cond_delay(nfc_op.cle_ale_delay_ns);
+
+	if (reading) {
+		if (nfc_op.rdy_timeout_ms)
+			ret = marvell_nfc_wait_op(chip, nfc_op.rdy_timeout_ms);
+		cond_delay(nfc_op.rdy_delay_ns);
+	}
+
+	if (ret)
+		return ret;
+
+	marvell_nfc_xfer_data(chip, subop, &nfc_op);
+	ret = marvell_nfc_wait_cmdd(chip);
+	cond_delay(nfc_op.data_delay_ns);
+
+	if (!reading) {
+		if (nfc_op.rdy_timeout_ms)
+			ret = marvell_nfc_wait_op(chip, nfc_op.rdy_timeout_ms);
+		cond_delay(nfc_op.rdy_delay_ns);
+	}
+
+	return ret;
+}
+
+static int marvell_nfc_reset_cmd_type_exec(struct nand_chip *chip,
+					   const struct nand_subop *subop)
+{
+	struct marvell_nfc_op nfc_op;
+	int ret;
+
+	marvell_nfc_parse_instructions(subop, &nfc_op);
+	nfc_op.ndcb[0] |= NDCB0_CMD_TYPE(TYPE_RESET);
+
+	ret = marvell_nfc_prepare_cmd(chip);
+	if (ret)
+		return ret;
+
+	marvell_nfc_send_cmd(chip, &nfc_op);
+	ret = marvell_nfc_wait_ndrun(chip);
+	cond_delay(nfc_op.cle_ale_delay_ns);
+
+	if (nfc_op.rdy_timeout_ms)
+		ret = marvell_nfc_wait_op(chip, nfc_op.rdy_timeout_ms);
+	cond_delay(nfc_op.rdy_delay_ns);
+
+	return ret;
+}
+
+static int marvell_nfc_erase_cmd_type_exec(struct nand_chip *chip,
+					   const struct nand_subop *subop)
+{
+	struct marvell_nfc_op nfc_op;
+	int ret;
+
+	marvell_nfc_parse_instructions(subop, &nfc_op);
+	nfc_op.ndcb[0] |= NDCB0_CMD_TYPE(TYPE_ERASE);
+
+	ret = marvell_nfc_prepare_cmd(chip);
+	if (ret)
+		return ret;
+
+	marvell_nfc_send_cmd(chip, &nfc_op);
+	ret = marvell_nfc_wait_ndrun(chip);
+	cond_delay(nfc_op.cle_ale_delay_ns);
+
+	if (nfc_op.rdy_timeout_ms)
+		ret = marvell_nfc_wait_op(chip, nfc_op.rdy_timeout_ms);
+	cond_delay(nfc_op.rdy_delay_ns);
+
+	return ret;
+}
+
+static int marvell_nfc_naked_access_exec(struct nand_chip *chip,
+					 const struct nand_subop *subop)
+{
+	struct marvell_nfc_op nfc_op;
+	int ret;
+
+	marvell_nfc_parse_instructions(subop, &nfc_op);
+
+	/*
+	 * Naked access are different in that they need to be flagged as naked
+	 * by the controller. Reset the controller registers fields that inform
+	 * on the type and refill them according to the ongoing operation.
+	 */
+	nfc_op.ndcb[0] &= ~(NDCB0_CMD_TYPE(TYPE_MASK) |
+			    NDCB0_CMD_XTYPE(XTYPE_MASK));
+	switch (subop->instrs[0].type) {
+	case NAND_OP_CMD_INSTR:
+		nfc_op.ndcb[0] |= NDCB0_CMD_TYPE(TYPE_NAKED_CMD);
+		break;
+	case NAND_OP_ADDR_INSTR:
+		nfc_op.ndcb[0] |= NDCB0_CMD_TYPE(TYPE_NAKED_ADDR);
+		break;
+	case NAND_OP_DATA_IN_INSTR:
+		nfc_op.ndcb[0] |= NDCB0_CMD_TYPE(TYPE_READ) |
+				  NDCB0_CMD_XTYPE(XTYPE_LAST_NAKED_RW);
+		break;
+	case NAND_OP_DATA_OUT_INSTR:
+		nfc_op.ndcb[0] |= NDCB0_CMD_TYPE(TYPE_WRITE) |
+				  NDCB0_CMD_XTYPE(XTYPE_LAST_NAKED_RW);
+		break;
+	default:
+		/* This should never happen */
+		break;
+	}
+
+	ret = marvell_nfc_prepare_cmd(chip);
+	if (ret)
+		return ret;
+
+	marvell_nfc_send_cmd(chip, &nfc_op);
+
+	if (!nfc_op.data_instr) {
+		ret = marvell_nfc_wait_ndrun(chip);
+		cond_delay(nfc_op.cle_ale_delay_ns);
+		return ret;
+	}
+
+	ret = marvell_nfc_end_cmd(chip, NDSR_RDDREQ | NDSR_WRDREQ,
+				  "RDDREQ/WRDREQ while draining raw data");
+	if (ret)
+		return ret;
+
+	marvell_nfc_xfer_data(chip, subop, &nfc_op);
+	ret = marvell_nfc_wait_cmdd(chip);
+	cond_delay(nfc_op.data_delay_ns);
+
+	return ret;
+}
+
+static int marvell_nfc_naked_waitrdy_exec(struct nand_chip *chip,
+					  const struct nand_subop *subop)
+{
+	struct marvell_nfc_op nfc_op;
+	int ret;
+
+	marvell_nfc_parse_instructions(subop, &nfc_op);
+
+	ret = marvell_nfc_wait_op(chip, nfc_op.rdy_timeout_ms);
+	cond_delay(nfc_op.rdy_delay_ns);
+
+	return ret;
+}
+
+static const struct nand_op_parser marvell_nfc_op_parser = NAND_OP_PARSER(
+	/* Monolithic read/write */
+	NAND_OP_PARSER_PATTERN(
+		marvell_nfc_monolithic_access_exec,
+		NAND_OP_PARSER_PAT_CMD_ELEM(false),
+		NAND_OP_PARSER_PAT_ADDR_ELEM(true, MAX_ADDRESS_CYC),
+		NAND_OP_PARSER_PAT_CMD_ELEM(true),
+		NAND_OP_PARSER_PAT_WAITRDY_ELEM(true),
+		NAND_OP_PARSER_PAT_DATA_IN_ELEM(false, MAX_CHUNK_SIZE)),
+	NAND_OP_PARSER_PATTERN(
+		marvell_nfc_monolithic_access_exec,
+		NAND_OP_PARSER_PAT_CMD_ELEM(false),
+		NAND_OP_PARSER_PAT_ADDR_ELEM(false, MAX_ADDRESS_CYC),
+		NAND_OP_PARSER_PAT_DATA_OUT_ELEM(false, MAX_CHUNK_SIZE),
+		NAND_OP_PARSER_PAT_CMD_ELEM(true),
+		NAND_OP_PARSER_PAT_WAITRDY_ELEM(true)),
+	/* Isolated commands (reset, erase, begin prog,...) */
+	NAND_OP_PARSER_PATTERN(
+		marvell_nfc_erase_cmd_type_exec,
+		NAND_OP_PARSER_PAT_CMD_ELEM(false),
+		NAND_OP_PARSER_PAT_ADDR_ELEM(false, MAX_ADDRESS_CYC),
+		NAND_OP_PARSER_PAT_CMD_ELEM(false),
+		NAND_OP_PARSER_PAT_WAITRDY_ELEM(true)),
+	NAND_OP_PARSER_PATTERN(
+		marvell_nfc_reset_cmd_type_exec,
+		NAND_OP_PARSER_PAT_CMD_ELEM(false),
+		NAND_OP_PARSER_PAT_WAITRDY_ELEM(false)),
+	/* Naked commands */
+	NAND_OP_PARSER_PATTERN(
+		marvell_nfc_naked_access_exec,
+		NAND_OP_PARSER_PAT_CMD_ELEM(false)),
+	NAND_OP_PARSER_PATTERN(
+		marvell_nfc_naked_access_exec,
+		NAND_OP_PARSER_PAT_ADDR_ELEM(false, MAX_ADDRESS_CYC)),
+	NAND_OP_PARSER_PATTERN(
+		marvell_nfc_naked_access_exec,
+		NAND_OP_PARSER_PAT_DATA_IN_ELEM(false, MAX_CHUNK_SIZE)),
+	NAND_OP_PARSER_PATTERN(
+		marvell_nfc_naked_access_exec,
+		NAND_OP_PARSER_PAT_DATA_OUT_ELEM(false, MAX_CHUNK_SIZE)),
+	NAND_OP_PARSER_PATTERN(
+		marvell_nfc_naked_waitrdy_exec,
+		NAND_OP_PARSER_PAT_WAITRDY_ELEM(false)),
+	);
+
+static int marvell_nfc_exec_op(struct nand_chip *chip,
+			       const struct nand_operation *op,
+			       bool check_only)
+{
+	return nand_op_parser_exec_op(chip, &marvell_nfc_op_parser,
+				      op, check_only);
+}
+
+/*
+ * HW ECC layouts, identical to old pxa3xx_nand driver,
+ * to be fully backward compatible.
+ */
+static int marvell_nand_ooblayout_ecc(struct mtd_info *mtd, int section,
+				      struct mtd_oob_region *oobregion)
+{
+	struct nand_chip *chip = mtd_to_nand(mtd);
+	const struct marvell_hw_ecc_layout *lt = to_marvell_nand(chip)->layout;
+	int nchunks = lt->full_chunk_cnt;
+
+	if (section >= nchunks)
+		return -ERANGE;
+
+	oobregion->offset = ((lt->spare_bytes + lt->ecc_bytes) * section) +
+		lt->spare_bytes;
+	oobregion->length = lt->ecc_bytes;
+
+	return 0;
+}
+
+static int marvell_nand_ooblayout_free(struct mtd_info *mtd, int section,
+				       struct mtd_oob_region *oobregion)
+{
+	struct nand_chip *chip = mtd_to_nand(mtd);
+	const struct marvell_hw_ecc_layout *lt = to_marvell_nand(chip)->layout;
+	int nchunks = lt->full_chunk_cnt;
+
+	if (section >= nchunks)
+		return -ERANGE;
+
+	if (!lt->spare_bytes)
+		return 0;
+
+	oobregion->offset = section * (lt->spare_bytes + lt->ecc_bytes);
+	oobregion->length = lt->spare_bytes;
+	if (!section) {
+		/*
+		 * Bootrom looks in bytes 0 & 5 for bad blocks for the
+		 * 4KB page / 4bit BCH combination.
+		 */
+		if (mtd->writesize == 4096 && lt->data_bytes == 2048) {
+			oobregion->offset += 6;
+			oobregion->length -= 6;
+		} else {
+			oobregion->offset += 2;
+			oobregion->length -= 2;
+		}
+	}
+
+	return 0;
+}
+
+static const struct mtd_ooblayout_ops marvell_nand_ooblayout_ops = {
+	.ecc = marvell_nand_ooblayout_ecc,
+	.free = marvell_nand_ooblayout_free,
+};
+
+static int marvell_nand_hw_ecc_ctrl_init(struct mtd_info *mtd,
+					 struct nand_ecc_ctrl *ecc,
+					 struct device_node *np)
+{
+	struct nand_chip *chip = mtd_to_nand(mtd);
+	struct marvell_nfc *nfc = to_marvell_nfc(chip->controller);
+	const struct marvell_hw_ecc_layout *l;
+	int i;
+
+	to_marvell_nand(chip)->layout = NULL;
+	for (i = 0; i < ARRAY_SIZE(marvell_nfc_layouts); i++) {
+		l = &marvell_nfc_layouts[i];
+		if (mtd->writesize == l->writesize &&
+		    ecc->size == l->chunk && ecc->strength == l->strength) {
+			to_marvell_nand(chip)->layout = l;
+			break;
+		}
+	}
+
+	if (!to_marvell_nand(chip)->layout) {
+		dev_err(nfc->dev,
+			"ECC strength %d at page size %d is not supported\n",
+			ecc->strength, mtd->writesize);
+		return -ENOTSUPP;
+	}
+
+	mtd_set_ooblayout(mtd, &marvell_nand_ooblayout_ops);
+	ecc->steps = l->full_chunk_cnt + l->last_chunk_cnt;
+	ecc->size = l->data_bytes;
+
+	if (ecc->strength == 1) {
+		chip->ecc.algo = NAND_ECC_HAMMING;
+		ecc->read_page = marvell_nfc_hw_ecc_hmg_read_page;
+		ecc->write_page = marvell_nfc_hw_ecc_hmg_write_page;
+	} else {
+		chip->ecc.algo = NAND_ECC_BCH;
+		ecc->read_page = marvell_nfc_hw_ecc_bch_read_page;
+		ecc->write_page = marvell_nfc_hw_ecc_bch_write_page;
+		ecc->strength = 16;
+	}
+
+	ecc->read_oob = marvell_nfc_hw_ecc_read_oob;
+	ecc->write_oob = marvell_nfc_hw_ecc_write_oob;
+
+	ecc->read_page_raw = marvell_nfc_hw_ecc_read_page_raw;
+	ecc->write_page_raw = marvell_nfc_hw_ecc_write_page_raw;
+	ecc->read_oob_raw = marvell_nfc_hw_ecc_read_oob_raw;
+	ecc->write_oob_raw = marvell_nfc_hw_ecc_write_oob_raw;
+
+	return 0;
+}
+
+static int marvell_nand_ecc_init(struct mtd_info *mtd,
+				 struct nand_ecc_ctrl *ecc,
+				 struct device_node *np)
+{
+	struct nand_chip *chip = mtd_to_nand(mtd);
+	int ret;
+
+	if (!ecc->size)
+		ecc->size = chip->ecc_step_ds;
+
+	if (!ecc->strength)
+		ecc->strength = chip->ecc_strength_ds;
+
+	if (!ecc->size || !ecc->strength)
+		return -EINVAL;
+
+	switch (ecc->mode) {
+	case NAND_ECC_HW:
+		ret = marvell_nand_hw_ecc_ctrl_init(mtd, ecc, np);
+		if (ret)
+			return ret;
+		break;
+	case NAND_ECC_NONE:
+		chip->ecc.algo = 0;
+	case NAND_ECC_SOFT:
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static u8 bbt_pattern[] = {'M', 'V', 'B', 'b', 't', '0' };
+static u8 bbt_mirror_pattern[] = {'1', 't', 'b', 'B', 'V', 'M' };
+
+static struct nand_bbt_descr bbt_main_descr = {
+	.options = NAND_BBT_LASTBLOCK | NAND_BBT_CREATE | NAND_BBT_WRITE |
+		   NAND_BBT_2BIT | NAND_BBT_VERSION,
+	.offs =	8,
+	.len = 6,
+	.veroffs = 14,
+	.maxblocks = 8,	/* Last 8 blocks in each chip */
+	.pattern = bbt_pattern
+};
+
+static struct nand_bbt_descr bbt_mirror_descr = {
+	.options = NAND_BBT_LASTBLOCK | NAND_BBT_CREATE | NAND_BBT_WRITE |
+		   NAND_BBT_2BIT | NAND_BBT_VERSION,
+	.offs =	8,
+	.len = 6,
+	.veroffs = 14,
+	.maxblocks = 8,	/* Last 8 blocks in each chip */
+	.pattern = bbt_mirror_pattern
+};
+
+static int marvell_nfc_setup_data_interface(struct mtd_info *mtd, int chipnr,
+					    const struct nand_data_interface
+					    *conf)
+{
+	struct nand_chip *chip = mtd_to_nand(mtd);
+	struct marvell_nand_chip *marvell_nand = to_marvell_nand(chip);
+	struct marvell_nfc *nfc = to_marvell_nfc(chip->controller);
+	unsigned int period_ns = 1000000000 / clk_get_rate(nfc->ecc_clk) * 2;
+	const struct nand_sdr_timings *sdr;
+	struct marvell_nfc_timings nfc_tmg;
+	int read_delay;
+
+	sdr = nand_get_sdr_timings(conf);
+	if (IS_ERR(sdr))
+		return PTR_ERR(sdr);
+
+	/*
+	 * SDR timings are given in pico-seconds while NFC timings must be
+	 * expressed in NAND controller clock cycles, which is half of the
+	 * frequency of the accessible ECC clock retrieved by clk_get_rate().
+	 * This is not written anywhere in the datasheet but was observed
+	 * with an oscilloscope.
+	 *
+	 * NFC datasheet gives equations from which thoses calculations
+	 * are derived, they tend to be slightly more restrictives than the
+	 * given core timings and may improve the overall speed.
+	 */
+	nfc_tmg.tRP = TO_CYCLES(DIV_ROUND_UP(sdr->tRC_min, 2), period_ns) - 1;
+	nfc_tmg.tRH = nfc_tmg.tRP;
+	nfc_tmg.tWP = TO_CYCLES(DIV_ROUND_UP(sdr->tWC_min, 2), period_ns) - 1;
+	nfc_tmg.tWH = nfc_tmg.tWP;
+	nfc_tmg.tCS = TO_CYCLES(sdr->tCS_min, period_ns);
+	nfc_tmg.tCH = TO_CYCLES(sdr->tCH_min, period_ns) - 1;
+	nfc_tmg.tADL = TO_CYCLES(sdr->tADL_min, period_ns);
+	/*
+	 * Read delay is the time of propagation from SoC pins to NFC internal
+	 * logic. With non-EDO timings, this is MIN_RD_DEL_CNT clock cycles. In
+	 * EDO mode, an additional delay of tRH must be taken into account so
+	 * the data is sampled on the falling edge instead of the rising edge.
+	 */
+	read_delay = sdr->tRC_min >= 30000 ?
+		MIN_RD_DEL_CNT : MIN_RD_DEL_CNT + nfc_tmg.tRH;
+
+	nfc_tmg.tAR = TO_CYCLES(sdr->tAR_min, period_ns);
+	/*
+	 * tWHR and tRHW are supposed to be read to write delays (and vice
+	 * versa) but in some cases, ie. when doing a change column, they must
+	 * be greater than that to be sure tCCS delay is respected.
+	 */
+	nfc_tmg.tWHR = TO_CYCLES(max_t(int, sdr->tWHR_min, sdr->tCCS_min),
+				 period_ns) - 2,
+	nfc_tmg.tRHW = TO_CYCLES(max_t(int, sdr->tRHW_min, sdr->tCCS_min),
+				 period_ns);
+
+	/* Use WAIT_MODE (wait for RB line) instead of only relying on delays */
+	nfc_tmg.tR = TO_CYCLES(sdr->tWB_max, period_ns);
+
+	if (chipnr < 0)
+		return 0;
+
+	marvell_nand->ndtr0 =
+		NDTR0_TRP(nfc_tmg.tRP) |
+		NDTR0_TRH(nfc_tmg.tRH) |
+		NDTR0_ETRP(nfc_tmg.tRP) |
+		NDTR0_TWP(nfc_tmg.tWP) |
+		NDTR0_TWH(nfc_tmg.tWH) |
+		NDTR0_TCS(nfc_tmg.tCS) |
+		NDTR0_TCH(nfc_tmg.tCH) |
+		NDTR0_RD_CNT_DEL(read_delay) |
+		NDTR0_SELCNTR |
+		NDTR0_TADL(nfc_tmg.tADL);
+
+	marvell_nand->ndtr1 =
+		NDTR1_TAR(nfc_tmg.tAR) |
+		NDTR1_TWHR(nfc_tmg.tWHR) |
+		NDTR1_TRHW(nfc_tmg.tRHW) |
+		NDTR1_WAIT_MODE |
+		NDTR1_TR(nfc_tmg.tR);
+
+	writel_relaxed(marvell_nand->ndtr0, nfc->regs + NDTR0);
+	writel_relaxed(marvell_nand->ndtr1, nfc->regs + NDTR1);
+
+	return 0;
+}
+
+static int marvell_nand_chip_init(struct device *dev, struct marvell_nfc *nfc,
+				  struct device_node *np)
+{
+	struct marvell_nand_chip *marvell_nand;
+	struct mtd_info *mtd;
+	struct nand_chip *chip;
+	int nsels, ret, i;
+	u32 cs, rb;
+
+	/*
+	 * The legacy "num-cs" property indicates the number of CS on the only
+	 * chip connected to the controller (legacy bindings does not support
+	 * more than one chip). CS are only incremented one by one while the RB
+	 * pin is always the #0.
+	 *
+	 * When not using legacy bindings, a couple of "reg" and "marvell,rb"
+	 * properties must be filled. For each chip, expressed as a subnode,
+	 * "reg" points to the CS lines and "marvell,rb" to the RB line.
+	 */
+	if (nfc->caps->legacy_of_bindings) {
+		if (!of_get_property(np, "num-cs", &nsels)) {
+			dev_err(dev, "missing num-cs property\n");
+			return -EINVAL;
+		}
+	} else {
+		if (!of_get_property(np, "reg", &nsels)) {
+			dev_err(dev, "missing reg property\n");
+			return -EINVAL;
+		}
+	}
+
+	nsels /= sizeof(u32);
+	if (!nsels) {
+		dev_err(dev, "invalid reg property size\n");
+		return -EINVAL;
+	}
+
+	/* Alloc the nand chip structure */
+	marvell_nand = devm_kzalloc(dev, sizeof(*marvell_nand) +
+				    (nsels *
+				     sizeof(struct marvell_nand_chip_sel)),
+				    GFP_KERNEL);
+	if (!marvell_nand) {
+		dev_err(dev, "could not allocate chip structure\n");
+		return -ENOMEM;
+	}
+
+	marvell_nand->nsels = nsels;
+	marvell_nand->selected = -1;
+
+	for (i = 0; i < nsels; i++) {
+		if (nfc->caps->legacy_of_bindings) {
+			/*
+			 * Legacy bindings use the CS lines in natural
+			 * order (0, 1, ...)
+			 */
+			cs = i;
+		} else {
+			/* Retrieve CS id */
+			ret = of_property_read_u32_index(np, "reg", i, &cs);
+			if (ret) {
+				dev_err(dev, "could not retrieve reg property: %d\n",
+					ret);
+				return ret;
+			}
+		}
+
+		if (cs >= nfc->caps->max_cs_nb) {
+			dev_err(dev, "invalid reg value: %u (max CS = %d)\n",
+				cs, nfc->caps->max_cs_nb);
+			return -EINVAL;
+		}
+
+		if (test_and_set_bit(cs, &nfc->assigned_cs)) {
+			dev_err(dev, "CS %d already assigned\n", cs);
+			return -EINVAL;
+		}
+
+		/*
+		 * The cs variable represents the chip select id, which must be
+		 * converted in bit fields for NDCB0 and NDCB2 to select the
+		 * right chip. Unfortunately, due to a lack of information on
+		 * the subject and incoherent documentation, the user should not
+		 * use CS1 and CS3 at all as asserting them is not supported in
+		 * a reliable way (due to multiplexing inside ADDR5 field).
+		 */
+		switch (cs) {
+		case 0:
+		case 2:
+			marvell_nand->sels[i].ndcb0_csel = 0;
+			break;
+		case 1:
+		case 3:
+			marvell_nand->sels[i].ndcb0_csel = NDCB0_CSEL;
+			break;
+		default:
+			return -EINVAL;
+		}
+
+		/* Retrieve RB id */
+		if (nfc->caps->legacy_of_bindings) {
+			/* Legacy bindings always use RB #0 */
+			rb = 0;
+		} else {
+			ret = of_property_read_u32_index(np, "marvell,rb", i,
+							 &rb);
+			if (ret) {
+				dev_err(dev,
+					"could not retrieve RB property: %d\n",
+					ret);
+				return ret;
+			}
+		}
+
+		if (rb >= nfc->caps->max_rb_nb) {
+			dev_err(dev, "invalid reg value: %u (max RB = %d)\n",
+				rb, nfc->caps->max_rb_nb);
+			return -EINVAL;
+		}
+
+		marvell_nand->sels[i].rb = rb;
+	}
+
+	chip = &marvell_nand->chip;
+	chip->controller = &nfc->controller;
+	nand_set_flash_node(chip, np);
+
+	chip->exec_op = marvell_nfc_exec_op;
+	chip->select_chip = marvell_nfc_select_chip;
+	if (!of_get_property(np, "marvell,nand-keep-config", NULL))
+		chip->setup_data_interface = marvell_nfc_setup_data_interface;
+
+	mtd = nand_to_mtd(chip);
+	mtd->dev.parent = dev;
+
+	/*
+	 * Default to HW ECC engine mode. If the nand-ecc-mode property is given
+	 * in the DT node, this entry will be overwritten in nand_scan_ident().
+	 */
+	chip->ecc.mode = NAND_ECC_HW;
+
+	ret = nand_scan_ident(mtd, marvell_nand->nsels, NULL);
+	if (ret) {
+		dev_err(dev, "could not identify the nand chip\n");
+		return ret;
+	}
+
+	if (chip->bbt_options & NAND_BBT_USE_FLASH) {
+		/*
+		 * We'll use a bad block table stored in-flash and don't
+		 * allow writing the bad block marker to the flash.
+		 */
+		chip->bbt_options |= NAND_BBT_NO_OOB_BBM;
+		chip->bbt_td = &bbt_main_descr;
+		chip->bbt_md = &bbt_mirror_descr;
+	}
+
+	/*
+	 * With RA_START bit set in NDCR, columns takes two address cycles. This
+	 * means addressing a chip with more than 256 pages needs a fifth
+	 * address cycle. Addressing a chip using CS 2 or 3 should also needs
+	 * this additional cycle but due to insistance in the documentation and
+	 * lack of hardware to test this situation, this case has been dropped
+	 * and is not supported by this driver.
+	 */
+	marvell_nand->addr_cyc = 4;
+	if (chip->options & NAND_ROW_ADDR_3)
+		marvell_nand->addr_cyc = 5;
+
+	ret = marvell_nand_ecc_init(mtd, &chip->ecc, np);
+	if (ret) {
+		dev_err(dev, "ECC init failed: %d\n", ret);
+		return ret;
+	}
+
+	if (chip->ecc.mode == NAND_ECC_HW) {
+		/*
+		 * Subpage write not available with hardware ECC, prohibit also
+		 * subpage read as in userspace subpage acces would still be
+		 * allowed and subpage write, if used, would lead to numerous
+		 * uncorrectable ECC errors.
+		 */
+		chip->options |= NAND_NO_SUBPAGE_WRITE;
+	}
+
+	ret = nand_scan_tail(mtd);
+	if (ret) {
+		dev_err(dev, "nand_scan_tail failed: %d\n", ret);
+		return ret;
+	}
+
+	ret = mtd_device_register(mtd, NULL, 0);
+	if (ret) {
+		dev_err(dev, "failed to register mtd device: %d\n", ret);
+		nand_release(mtd);
+		return ret;
+	}
+
+	list_add_tail(&marvell_nand->node, &nfc->chips);
+
+	return 0;
+}
+
+static int marvell_nand_chips_init(struct device *dev, struct marvell_nfc *nfc)
+{
+	struct device_node *np = dev->of_node;
+	struct device_node *nand_np;
+	int nchips = of_get_child_count(np);
+	int max_cs = nfc->caps->max_cs_nb;
+	int ret;
+
+	if (nchips > max_cs) {
+		dev_err(dev, "too many NAND chips: %d (max = %d CS)\n", nchips,
+			max_cs);
+		return -EINVAL;
+	}
+
+	/*
+	 * Legacy bindings do not use child nodes to exhibit NAND chip
+	 * properties and layout. Instead, NAND properties are mixed with the
+	 * controller's and a single subnode presents the memory layout.
+	 */
+	if (nfc->caps->legacy_of_bindings) {
+		ret = marvell_nand_chip_init(dev, nfc, np);
+		return ret;
+	}
+
+	for_each_child_of_node(np, nand_np) {
+		ret = marvell_nand_chip_init(dev, nfc, nand_np);
+		if (ret) {
+			of_node_put(nand_np);
+			return ret;
+		}
+	}
+
+	return 0;
+}
+
+static void marvell_nand_chips_cleanup(struct marvell_nfc *nfc)
+{
+	struct marvell_nand_chip *entry, *temp;
+
+	list_for_each_entry_safe(entry, temp, &nfc->chips, node) {
+		nand_release(nand_to_mtd(&entry->chip));
+		list_del(&entry->node);
+	}
+}
+
+static int marvell_nfc_init(struct marvell_nfc *nfc)
+{
+	struct device_node *np = nfc->dev->of_node;
+	u32 enable_arbiter = 0;
+
+	/*
+	 * Some SoCs like A7k/A8k need to enable manually the NAND
+	 * controller to avoid being bootloader dependent. This is done
+	 * through the use of a single bit in the System Functions registers.
+	 */
+	if (nfc->caps->variant == MARVELL_NFC_VARIANT_ARMADA_8K) {
+		struct regmap *sysctrl_base = syscon_regmap_lookup_by_phandle(
+			np, "marvell,system-controller");
+		u32 reg;
+
+		if (IS_ERR(sysctrl_base))
+			return PTR_ERR(sysctrl_base);
+
+		regmap_read(sysctrl_base, GENCONF_SOC_DEVICE_MUX, &reg);
+		reg |= GENCONF_SOC_DEVICE_MUX_NFC_EN;
+		regmap_write(sysctrl_base, GENCONF_SOC_DEVICE_MUX, reg);
+	}
+
+	/* For PXA only, but other SoCs have this bit marked reserved */
+	if (of_get_property(np, "marvell,nand-enable-arbiter", NULL))
+		enable_arbiter = NDCR_ND_ARB_EN;
+
+	/*
+	 * ECC operations and interruptions are only enabled when specifically
+	 * needed. ECC shall not be activated in the early stages (fails probe)
+	 */
+	writel_relaxed(NDCR_RA_START | NDCR_ALL_INT | enable_arbiter,
+		       nfc->regs + NDCR);
+	writel_relaxed(0xFFFFFFFF, nfc->regs + NDSR);
+	writel_relaxed(0, nfc->regs + NDECCCTRL);
+
+	return 0;
+}
+
+static int marvell_nfc_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct resource *r;
+	struct marvell_nfc *nfc;
+	int ret;
+	int irq;
+
+	nfc = devm_kzalloc(&pdev->dev, sizeof(struct marvell_nfc),
+			   GFP_KERNEL);
+	if (!nfc)
+		return -ENOMEM;
+
+	nfc->dev = dev;
+	nand_hw_control_init(&nfc->controller);
+	INIT_LIST_HEAD(&nfc->chips);
+
+	r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	nfc->regs = devm_ioremap_resource(dev, r);
+	if (IS_ERR(nfc->regs))
+		return PTR_ERR(nfc->regs);
+
+	irq = platform_get_irq(pdev, 0);
+	if (irq < 0) {
+		dev_err(dev, "failed to retrieve irq\n");
+		return irq;
+	}
+
+	nfc->ecc_clk = devm_clk_get(&pdev->dev, NULL);
+	if (IS_ERR(nfc->ecc_clk))
+		return PTR_ERR(nfc->ecc_clk);
+
+	ret = clk_prepare_enable(nfc->ecc_clk);
+	if (ret)
+		return ret;
+
+	marvell_nfc_disable_int(nfc, NDCR_ALL_INT);
+	marvell_nfc_clear_int(nfc, NDCR_ALL_INT);
+	ret = devm_request_irq(dev, irq, marvell_nfc_isr,
+			       0, "marvell-nfc", nfc);
+	if (ret)
+		goto out_clk_unprepare;
+
+	/* Get NAND controller capabilities */
+	if (pdev->id_entry)
+		nfc->caps = (void *)pdev->id_entry->driver_data;
+	else
+		nfc->caps = of_device_get_match_data(&pdev->dev);
+
+	if (!nfc->caps) {
+		dev_err(dev, "Could not retrieve NFC caps\n");
+		ret = -EINVAL;
+		goto out_clk_unprepare;
+	}
+
+	/* Init the controller and then probe the chips */
+	ret = marvell_nfc_init(nfc);
+	if (ret)
+		goto out_clk_unprepare;
+
+	platform_set_drvdata(pdev, nfc);
+
+	ret = marvell_nand_chips_init(dev, nfc);
+	if (ret)
+		goto out_clk_unprepare;
+
+	return 0;
+
+out_clk_unprepare:
+	clk_disable_unprepare(nfc->ecc_clk);
+
+	return ret;
+}
+
+static int marvell_nfc_remove(struct platform_device *pdev)
+{
+	struct marvell_nfc *nfc = platform_get_drvdata(pdev);
+
+	marvell_nand_chips_cleanup(nfc);
+
+	clk_disable_unprepare(nfc->ecc_clk);
+
+	return 0;
+}
+
+static const struct marvell_nfc_caps marvell_armada_8k_nfc_caps = {
+	.variant = MARVELL_NFC_VARIANT_ARMADA_8K,
+	.max_cs_nb = 4,
+	.max_rb_nb = 2,
+};
+
+static const struct marvell_nfc_caps marvell_armada370_nfc_caps = {
+	.variant = MARVELL_NFC_VARIANT_ARMADA370,
+	.max_cs_nb = 4,
+	.max_rb_nb = 2,
+};
+
+static const struct marvell_nfc_caps marvell_pxa3xx_nfc_caps = {
+	.variant = MARVELL_NFC_VARIANT_PXA3XX,
+	.max_cs_nb = 2,
+	.max_rb_nb = 1,
+};
+
+static const struct marvell_nfc_caps marvell_armada_8k_nfc_legacy_caps = {
+	.variant = MARVELL_NFC_VARIANT_ARMADA_8K,
+	.max_cs_nb = 4,
+	.max_rb_nb = 2,
+	.legacy_of_bindings = true,
+};
+
+static const struct marvell_nfc_caps marvell_armada370_nfc_legacy_caps = {
+	.variant = MARVELL_NFC_VARIANT_ARMADA370,
+	.max_cs_nb = 4,
+	.max_rb_nb = 2,
+	.legacy_of_bindings = true,
+};
+
+static const struct marvell_nfc_caps marvell_pxa3xx_nfc_legacy_caps = {
+	.variant = MARVELL_NFC_VARIANT_PXA3XX,
+	.max_cs_nb = 2,
+	.max_rb_nb = 1,
+	.legacy_of_bindings = true,
+};
+
+static const struct of_device_id marvell_nfc_of_ids[] = {
+	{
+		.compatible = "marvell,armada-8k-nand-controller",
+		.data = &marvell_armada_8k_nfc_caps,
+	},
+	{
+		.compatible = "marvell,armada370-nand-controller",
+		.data = &marvell_armada370_nfc_caps,
+	},
+	{
+		.compatible = "marvell,pxa3xx-nand-controller",
+		.data = &marvell_pxa3xx_nfc_caps,
+	},
+	/* Support for old/deprecated bindings: */
+	{
+		.compatible = "marvell,armada-8k-nand",
+		.data = &marvell_armada_8k_nfc_legacy_caps,
+	},
+	{
+		.compatible = "marvell,armada370-nand",
+		.data = &marvell_armada370_nfc_legacy_caps,
+	},
+	{
+		.compatible = "marvell,pxa3xx-nand",
+		.data = &marvell_pxa3xx_nfc_legacy_caps,
+	},
+	{ /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, marvell_nand_match);
+
+static struct platform_driver marvell_nfc_driver = {
+	.driver	= {
+		.name		= "marvell-nfc",
+		.of_match_table = marvell_nfc_of_ids,
+	},
+	.probe	= marvell_nfc_probe,
+	.remove	= marvell_nfc_remove,
+};
+module_platform_driver(marvell_nfc_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Marvell NAND controller driver");
-- 
2.11.0

^ permalink raw reply related	[flat|nested] 75+ messages in thread

* [RFC 06/12] mtd: nand: add reworked Marvell NAND controller driver
@ 2017-10-18 14:36   ` Miquel Raynal
  0 siblings, 0 replies; 75+ messages in thread
From: Miquel Raynal @ 2017-10-18 14:36 UTC (permalink / raw)
  To: linux-arm-kernel

Add marvell_nand driver which aims at replacing the existing pxa3xx_nand
driver.

The new driver intends to be easier to understand and follows the brand
new NAND framework rules by implementing hooks for every pattern the
controller might support and referencing them inside a parser object
that will be given to the core at each ->exec_op() call.

Raw accessors are implemented, useful to test/debug memory/filesystem
corruptions. Userspace binaries contained in the mtd-utils package may
now be used and their output trusted.

Timings may not be kept from the bootloader anymore, the timings used
for instance in U-Boot were not optimal and it supposed to have NAND
support (and initialized) in the bootloader.

Thanks to the improved timings, implementation of ONFI mode 5 support
(with EDO managed by adding a delay on data sampling), merging the
commands together and optimizing writes in the command registers, the
new driver may achieve faster throughputs in both directions.
Measurements show an improvement of about +23% read throughput and +24%
write throughput. These measurements have been done with an
Armada-385-DB-AP (4kiB NAND pages forced in 4-bit strength BCH ECC
correction) using the userspace tool 'flash_speed' from the MTD test
suite.

Besides these important topics, the new driver addresses several
unsolved known issues in the old driver which:
- did not work with ECC soft neither with ECC none ;
- relied on naked read/write (which is unchanged) while the NFCv1
  embedded in the pxa3xx platforms do not implement it, so several
  NAND commands did not actually ever work without any notice (like
  reading the ONFI PARAM_PAGE or SET/GET_FEATURES) ;
- wrote the OOB data correctly, but was not able to read it correctly
  past the first OOB data chunk ;
- did not displayed ECC bytes ;
- used device tree bindings that did not allow more than one NAND chip,
  and did not allow to choose the correct chip select if not
  incrementing from 0. Plus, the Ready/Busy line used had to be 0.

Old device tree bindings are still supported but deprecated. A more
hierarchical view has to be used to keep the controller and the NAND
chip structures clearly separated both inside the device tree and also
in the driver code.

Signed-off-by: Miquel Raynal <miquel.raynal@free-electrons.com>
---
 drivers/mtd/nand/Kconfig        |   11 +
 drivers/mtd/nand/Makefile       |    1 +
 drivers/mtd/nand/marvell_nand.c | 2384 +++++++++++++++++++++++++++++++++++++++
 3 files changed, 2396 insertions(+)
 create mode 100644 drivers/mtd/nand/marvell_nand.c

diff --git a/drivers/mtd/nand/Kconfig b/drivers/mtd/nand/Kconfig
index bb48aafed9a2..8a64a18b9d26 100644
--- a/drivers/mtd/nand/Kconfig
+++ b/drivers/mtd/nand/Kconfig
@@ -323,6 +323,17 @@ config MTD_NAND_PXA3xx
 	  platforms (XP, 370, 375, 38x, 39x) and 64-bit Armada
 	  platforms (7K, 8K) (NFCv2).
 
+config MTD_NAND_MARVELL
+	tristate "NAND controller support on Marvell boards"
+	depends on PXA3xx || ARCH_MMP || PLAT_ORION || ARCH_MVEBU || \
+		   (COMPILE_TEST && HAS_IOMEM)
+	help
+	  This enables the NAND flash controller driver for Marvell boards,
+	  including:
+	  - PXA3xx processors (NFCv1)
+	  - 32-bit Armada platforms (XP, 37x, 38x, 39x) (NFCv2)
+	  - 64-bit Aramda platforms (7k, 8k) (NFCv2)
+
 config MTD_NAND_SLC_LPC32XX
 	tristate "NXP LPC32xx SLC Controller"
 	depends on ARCH_LPC32XX
diff --git a/drivers/mtd/nand/Makefile b/drivers/mtd/nand/Makefile
index ade5fc4c3819..828332dfe18c 100644
--- a/drivers/mtd/nand/Makefile
+++ b/drivers/mtd/nand/Makefile
@@ -31,6 +31,7 @@ obj-$(CONFIG_MTD_NAND_OMAP2) 		+= omap2_nand.o
 obj-$(CONFIG_MTD_NAND_OMAP_BCH_BUILD)	+= omap_elm.o
 obj-$(CONFIG_MTD_NAND_CM_X270)		+= cmx270_nand.o
 obj-$(CONFIG_MTD_NAND_PXA3xx)		+= pxa3xx_nand.o
+obj-$(CONFIG_MTD_NAND_MARVELL)		+= marvell_nand.o
 obj-$(CONFIG_MTD_NAND_TMIO)		+= tmio_nand.o
 obj-$(CONFIG_MTD_NAND_PLATFORM)		+= plat_nand.o
 obj-$(CONFIG_MTD_NAND_PASEMI)		+= pasemi_nand.o
diff --git a/drivers/mtd/nand/marvell_nand.c b/drivers/mtd/nand/marvell_nand.c
new file mode 100644
index 000000000000..557817a6f4b6
--- /dev/null
+++ b/drivers/mtd/nand/marvell_nand.c
@@ -0,0 +1,2384 @@
+/*
+ * Marvell NAND flash controller driver
+ *
+ * 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.
+ *
+ * Copyright (C) 2017 Marvell
+ * Author: Miquel RAYNAL <miquel.raynal@free-electrons.com>
+ */
+
+#include <linux/module.h>
+#include <linux/clk.h>
+#include <linux/mtd/rawnand.h>
+#include <linux/of_platform.h>
+#include <linux/iopoll.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+#include <linux/mfd/syscon.h>
+#include <linux/regmap.h>
+#include <asm/unaligned.h>
+
+/* Data FIFO granularity, FIFO reads/writes must be a multiple of this length */
+#define FIFO_DEPTH		8
+#define FIFO_REP(x)		(x / sizeof(u32))
+#define FIFO_DEPTH_32		FIFO_REP(FIFO_DEPTH)
+/* NFCv2 does not support transfers of larger chunks at a time */
+#define MAX_CHUNK_SIZE		2112
+/* Polling is done at a pace of POLL_PERIOD us until POLL_TIMEOUT is reached */
+#define POLL_PERIOD		0
+#define POLL_TIMEOUT		100000
+/* Interrupt maximum wait period in ms */
+#define IRQ_TIMEOUT		1000
+/* Latency in clock cycles between SoC pins and NFC logic */
+#define MIN_RD_DEL_CNT		3
+/* Maximum number of contiguous address cycles */
+#define MAX_ADDRESS_CYC		7
+/* System control register and bit to enable NAND on some SoCs */
+#define GENCONF_SOC_DEVICE_MUX	0x208
+#define GENCONF_SOC_DEVICE_MUX_NFC_EN BIT(0)
+
+/* NAND controller data flash control register */
+#define NDCR			0x00
+/* NAND interface timing parameter 0 register */
+#define NDTR0			0x04
+/* NAND interface timing parameter 1 register */
+#define NDTR1			0x0C
+/* NAND controller status register */
+#define NDSR			0x14
+/* NAND ECC control register */
+#define NDECCCTRL		0x28
+/* NAND controller data buffer register */
+#define NDDB			0x40
+/* NAND controller command buffer 0 register */
+#define NDCB0			0x48
+/* NAND controller command buffer 1 register */
+#define NDCB1			0x4C
+/* NAND controller command buffer 2 register */
+#define NDCB2			0x50
+/* NAND controller command buffer 3 register */
+#define NDCB3			0x54
+
+/* Data flash control register bitfields */
+#define NDCR_ALL_INT		GENMASK(11, 0)
+#define NDCR_CS1_CMDDM		BIT(7)
+#define NDCR_CS0_CMDDM		BIT(8)
+#define NDCR_RDYM		BIT(11)
+#define NDCR_ND_ARB_EN		BIT(12)
+#define NDCR_RA_START		BIT(15)
+#define NDCR_RD_ID_CNT(x)	((x & 0x7) << 16)
+#define NDCR_PAGE_SZ(x)		(x >= 2048 ? BIT(24) : 0)
+#define NDCR_DWIDTH_M		BIT(26)
+#define NDCR_DWIDTH_C		BIT(27)
+#define NDCR_ND_RUN		BIT(28)
+#define NDCR_ECC_EN		BIT(30)
+#define NDCR_SPARE_EN		BIT(31)
+
+/* NAND interface timing parameter registers bitfields */
+#define NDTR0_TRP(x)		((min_t(unsigned int, x, 0xF) & 0x7) << 0)
+#define NDTR0_TRH(x)		(min_t(unsigned int, x, 0x7) << 3)
+#define NDTR0_ETRP(x)		((min_t(unsigned int, x, 0xF) & 0x8) << 3)
+#define NDTR0_SEL_NRE_EDGE	BIT(7)
+#define NDTR0_TWP(x)		(min_t(unsigned int, x, 0x7) << 8)
+#define NDTR0_TWH(x)		(min_t(unsigned int, x, 0x7) << 11)
+#define NDTR0_TCS(x)		(min_t(unsigned int, x, 0x7) << 16)
+#define NDTR0_TCH(x)		(min_t(unsigned int, x, 0x7) << 19)
+#define NDTR0_RD_CNT_DEL(x)	(min_t(unsigned int, x, 0xF) << 22)
+#define NDTR0_SELCNTR		BIT(26)
+#define NDTR0_TADL(x)		(min_t(unsigned int, x, 0x1F) << 27)
+
+#define NDTR1_TAR(x)		(min_t(unsigned int, x, 0xF) << 0)
+#define NDTR1_TWHR(x)		(min_t(unsigned int, x, 0xF) << 4)
+#define NDTR1_TRHW(x)		(min_t(unsigned int, x / 16, 0x3) << 8)
+#define NDTR1_PRESCALE		BIT(14)
+#define NDTR1_WAIT_MODE		BIT(15)
+#define NDTR1_TR(x)		(min_t(unsigned int, x, 0xFFFF) << 16)
+
+/* NAND controller status register bitfields */
+#define NDSR_WRCMDREQ		BIT(0)
+#define NDSR_RDDREQ		BIT(1)
+#define NDSR_WRDREQ		BIT(2)
+#define NDSR_CORERR		BIT(3)
+#define NDSR_UNCERR		BIT(4)
+#define NDSR_CMDD(cs)		BIT(8 - cs)
+#define NDSR_RDY(rb)		BIT(11 + rb)
+#define NDSR_ERRCNT(x)		((x >> 16) & 0x1F)
+
+/* NAND ECC control register bitfields */
+#define NDECCTRL_BCH_EN		BIT(0)
+
+/* NAND controller command buffer registers bitfields */
+#define NDCB0_CMD1(x)		((x & 0xFF) << 0)
+#define NDCB0_CMD2(x)		((x & 0xFF) << 8)
+#define NDCB0_ADDR_CYC(x)	((x & 0x7) << 16)
+#define NDCB0_DBC		BIT(19)
+#define NDCB0_CMD_TYPE(x)	((x & 0x7) << 21)
+#define NDCB0_CSEL		BIT(24)
+#define NDCB0_RDY_BYP		BIT(27)
+#define NDCB0_LEN_OVRD		BIT(28)
+#define NDCB0_CMD_XTYPE(x)	((x & 0x7) << 29)
+
+#define NDCB1_COLS(x)		((x & 0xFFFF) << 0)
+#define NDCB1_ADDRS(x)		(x << 16)
+
+#define NDCB2_ADDR5(x)		(((x >> 16) & 0xFF) << 0)
+
+#define NDCB3_ADDR6(x)		((x & 0xFF) << 16)
+#define NDCB3_ADDR7(x)		((x & 0xFF) << 24)
+
+/* NAND controller command buffer 0 register 'type' and 'xtype' fields */
+#define TYPE_READ		0
+#define TYPE_WRITE		1
+#define TYPE_ERASE		2
+#define TYPE_READ_ID		3
+#define TYPE_STATUS		4
+#define TYPE_RESET		5
+#define TYPE_NAKED_CMD		6
+#define TYPE_NAKED_ADDR		7
+#define TYPE_MASK		7
+#define XTYPE_MONOLITHIC_RW	0
+#define XTYPE_LAST_NAKED_RW	1
+#define XTYPE_FINAL_COMMAND	3
+#define XTYPE_READ		4
+#define XTYPE_WRITE_DISPATCH	4
+#define XTYPE_NAKED_RW		5
+#define XTYPE_COMMAND_DISPATCH	6
+#define XTYPE_MASK		7
+
+/*
+ * Marvell ECC engine works differently than the others, in order to limit the
+ * size of the IP, hardware engineers choose to set a fixed strength at 16 bits
+ * per subpage, and depending on a the desired strength needed by the NAND chip,
+ * a particular layout mixing data/spare/ecc is defined, with a possible last
+ * chunk smaller that the others.
+ *
+ * @writesize:		Full page size on which the layout applies
+ * @chunk:		Desired ECC chunk size on which the layout applies
+ * @strength:		Desired ECC strength (per chunk size bytes) on which the
+ *			layout applies
+ * @full_chunk_cnt:	Number of full-sized chunks, which is the number of
+ *			repetitions of the pattern:
+ *			(data_bytes + spare_bytes + ecc_bytes).
+ * @data_bytes:		Number of data bytes per chunk
+ * @spare_bytes:	Number of spare bytes per chunk
+ * @ecc_bytes:		Number of ecc bytes per chunk
+ * @last_chunk_cnt:	If there is a last chunk with a different size than
+ *			the first ones, the next fields may not be empty
+ * @last_data_bytes:	Number of data bytes in the last chunk
+ * @last_spare_bytes:	Number of spare bytes in the last chunk
+ * @last_ecc_bytes:	Number of ecc bytes in the last chunk
+ */
+struct marvell_hw_ecc_layout {
+	/* Constraints */
+	int writesize;
+	int chunk;
+	int strength;
+	/* Corresponding layout */
+	int full_chunk_cnt;
+	int data_bytes;
+	int spare_bytes;
+	int ecc_bytes;
+	int last_chunk_cnt;
+	int last_data_bytes;
+	int last_spare_bytes;
+	int last_ecc_bytes;
+};
+
+#define MARVELL_LAYOUT(ws, dc, ds, fcc, db, sb, eb, lcc, ldb, lsb, leb) \
+	{								\
+		.writesize = ws,					\
+		.chunk = dc,						\
+		.strength = ds,						\
+		.full_chunk_cnt = fcc,					\
+		.data_bytes = db,					\
+		.spare_bytes = sb,					\
+		.ecc_bytes = eb,					\
+		.last_chunk_cnt = lcc,					\
+		.last_data_bytes = ldb,					\
+		.last_spare_bytes = lsb,				\
+		.last_ecc_bytes = leb,					\
+	}
+
+/* Layouts explained in AN-379_Marvell_SoC_NFC_ECC */
+static const struct marvell_hw_ecc_layout marvell_nfc_layouts[] = {
+	MARVELL_LAYOUT(  512,   512,  1,  1,  512,  0,  6,  0,  0,  0,  0),
+	MARVELL_LAYOUT( 2048,   512,  1,  1, 2048, 40, 24,  0,  0,  0,  0),
+	MARVELL_LAYOUT( 2048,   512,  4,  1, 2048, 32, 30,  0,  0,  0,  0),
+	MARVELL_LAYOUT( 4096,   512,  4,  2, 2048, 32, 30,  0,  0,  0,  0),
+	MARVELL_LAYOUT( 4096,   512,  8,  4, 1024,  0, 30,  1,  0, 64, 30),
+};
+
+/*
+ * The Nand Flash Controller has up to 4 CE and 2 RB pins. The CE selection
+ * is made by a field in NDCB0 register, and in another field in NDCB2 register.
+ * The datasheet describes the logic with an error: ADDR5 field is once
+ * declared at the beginning of NDCB2, and another time@its end. Because the
+ * ADDR5 field of NDCB2 may be used by other bytes, it would be more logical
+ * to use the last bit of this field instead of the first ones.
+ *
+ * @ndcb0_csel:		Value of the NDCB0 register with or without the flag
+ *			selecting the wanted CE lane. This is set once when
+ *			the Device Tree is probed.
+ * @rb:			Ready/Busy pin for the flash chip
+ */
+struct marvell_nand_chip_sel {
+	u32 ndcb0_csel;
+	unsigned int rb;
+};
+
+/*
+ * NAND chip structure: stores NAND chip device related information
+ *
+ * @chip:		Base NAND chip structure
+ * @node:		Used to store NAND chips into a list
+ * @layout		NAND layout when using hardware ECC
+ * @ndtr0		Timing registers 0 value for this NAND chip
+ * @ndtr1		Timing registers 1 value for this NAND chip
+ * @selected:		Current active CS
+ * @nsels:		Number of CS lines required by the NAND chip
+ * @sels:		Array of CS lines descriptions
+ */
+struct marvell_nand_chip {
+	struct nand_chip chip;
+	struct list_head node;
+	const struct marvell_hw_ecc_layout *layout;
+	u32 ndtr0;
+	u32 ndtr1;
+	int addr_cyc;
+	int selected;
+	unsigned int nsels;
+	struct marvell_nand_chip_sel sels[0];
+};
+
+static inline struct marvell_nand_chip *to_marvell_nand(struct nand_chip *chip)
+{
+	return container_of(chip, struct marvell_nand_chip, chip);
+}
+
+static inline struct marvell_nand_chip_sel *to_nand_sel(struct marvell_nand_chip
+							*nand)
+{
+	return &nand->sels[nand->selected];
+}
+
+enum marvell_nfc_variant {
+	MARVELL_NFC_VARIANT_PXA3XX,
+	MARVELL_NFC_VARIANT_ARMADA370,
+	MARVELL_NFC_VARIANT_ARMADA_8K,
+};
+
+/*
+ * NAND controller capabilities for distinction between compatible strings
+ *
+ * @variant:		Board type
+ * @max_cs_nb:		Number of Chip Select lines available
+ * @max_rb_nb:		Number of Ready/Busy lines available
+ * @legacy_of_bindings	Indicates if DT parsing must be done using the old
+ *			fashion way
+ */
+struct marvell_nfc_caps {
+	enum marvell_nfc_variant variant;
+	unsigned int max_cs_nb;
+	unsigned int max_rb_nb;
+	bool legacy_of_bindings;
+};
+
+/*
+ * NAND controller structure: stores Marvell NAND controller information
+ *
+ * @controller:		Base controller structure
+ * @dev:		Parent device (used to print error messages)
+ * @regs:		NAND controller registers
+ * @ecc_clk:		ECC block clock, two times the NAND controller clock
+ * @complete:		Completion object to wait for NAND controller events
+ * @assigned_cs:	Bitmask describing already assigned CS lines
+ * @chips:		List containing all the NAND chips attached to
+ *			this NAND controller
+ * @caps:		NAND controller capabilities for each compatible string
+ * @buf:		Controller local buffer to store a part of the read
+ *			buffer when the read operation was not 8 bytes aligned
+ *			as is the FIFO.
+ * @buf_pos:		Position in the 'buf' buffer
+ */
+struct marvell_nfc {
+	struct nand_hw_control controller;
+	struct device *dev;
+	void __iomem *regs;
+	struct clk *ecc_clk;
+	struct completion complete;
+	unsigned long assigned_cs;
+	struct list_head chips;
+	const struct marvell_nfc_caps *caps;
+
+	/*
+	 * Buffer handling: @buf will be accessed byte-per-byter but also
+	 * int-per-int when exchanging data with the NAND controller FIFO,
+	 * 32-bit alignment is then required.
+	 */
+	u8 buf[FIFO_DEPTH] __aligned(sizeof(u32));
+	int buf_pos;
+};
+
+static inline struct marvell_nfc *to_marvell_nfc(struct nand_hw_control *ctrl)
+{
+	return container_of(ctrl, struct marvell_nfc, controller);
+}
+
+/*
+ * NAND controller timings expressed in NAND Controller clock cycles
+ *
+ * @tRP:		ND_nRE pulse width
+ * @tRH:		ND_nRE high duration
+ * @tWP:		ND_nWE pulse time
+ * @tWH:		ND_nWE high duration
+ * @tCS:		Enable signal setup time
+ * @tCH:		Enable signal hold time
+ * @tADL:		Address to write data delay
+ * @tAR:		ND_ALE low to ND_nRE low delay
+ * @tWHR:		ND_nWE high to ND_nRE low for status read
+ * @tRHW:		ND_nRE high duration, read to write delay
+ * @tR:			ND_nWE high to ND_nRE low for read
+ */
+struct marvell_nfc_timings {
+	/* NDTR0 fields */
+	unsigned int tRP;
+	unsigned int tRH;
+	unsigned int tWP;
+	unsigned int tWH;
+	unsigned int tCS;
+	unsigned int tCH;
+	unsigned int tADL;
+	/* NDTR1 fields */
+	unsigned int tAR;
+	unsigned int tWHR;
+	unsigned int tRHW;
+	unsigned int tR;
+};
+
+/*
+ * Derives a duration in numbers of clock cycles.
+ *
+ * @ps: Duration in pico-seconds
+ * @period_ns:  Clock period in nano-seconds
+ *
+ * Convert the duration in nano-seconds, then divide by the period and
+ * return the number of clock periods.
+ */
+#define TO_CYCLES(ps, period_ns) (DIV_ROUND_UP(ps / 1000, period_ns))
+
+/*
+ * NAND driver structure filled during the parsing of the ->exec_op() subop
+ * subset of instructions.
+ *
+ * @ndcb:		Array for the values of the NDCBx registers
+ * @cle_ale_delay_ns:	Optional delay after the last CMD or ADDR cycle
+ * @rdy_timeout_ms:	Timeout for waits on Ready/Busy pin
+ * @rdy_delay_ns:	Optional delay after waiting for the RB pin
+ * @data_delay_ns:	Optional delay after the data xfer
+ * @data_instr_idx:	Index of the data instruction in the subop
+ * @data_instr:		Pointer to the data instruction in the subop
+ */
+struct marvell_nfc_op {
+	u32 ndcb[4];
+	unsigned int cle_ale_delay_ns;
+	unsigned int rdy_timeout_ms;
+	unsigned int rdy_delay_ns;
+	unsigned int data_delay_ns;
+	unsigned int data_instr_idx;
+	const struct nand_op_instr *data_instr;
+};
+
+/*
+ * Internal helper to conditionnally apply a delay (from the above structure,
+ * most of the time).
+ */
+static void cond_delay(unsigned int ns)
+{
+	if (!ns)
+		return;
+
+	if (ns < 10000)
+		ndelay(ns);
+	else
+		udelay(DIV_ROUND_UP(ns, 1000));
+}
+
+/*
+ * Internal helper to mimic core functions whithout having to distinguish if
+ * this is the first read operation on the page or not and hence choose the
+ * right function.
+ */
+int read_page_data(struct nand_chip *chip, unsigned int page,
+		   unsigned int column, void *buf, unsigned int len)
+{
+	if (!column)
+		return nand_read_page_op(chip, page, column, buf, len);
+	else
+		return nand_change_read_column_op(chip, column, buf, len,
+						  false);
+}
+
+/*
+ * The controller has many flags that could generate interrupts, most of them
+ * are disabled and polling is used. For the very slow signals, using interrupts
+ * may relax the CPU charge.
+ */
+static void marvell_nfc_disable_int(struct marvell_nfc *nfc, u32 int_mask)
+{
+	u32 reg;
+
+	/* Writing 1 disables the interrupt */
+	reg = readl_relaxed(nfc->regs + NDCR);
+	writel_relaxed(reg | int_mask, nfc->regs + NDCR);
+}
+
+static void marvell_nfc_enable_int(struct marvell_nfc *nfc, u32 int_mask)
+{
+	u32 reg;
+
+	/* Writing 0 enables the interrupt */
+	reg = readl_relaxed(nfc->regs + NDCR);
+	writel_relaxed(reg & ~int_mask, nfc->regs + NDCR);
+}
+
+static void marvell_nfc_clear_int(struct marvell_nfc *nfc, u32 int_mask)
+{
+	writel_relaxed(int_mask, nfc->regs + NDSR);
+}
+
+/*
+ * The core may ask the controller to use only 8-bit accesses while usually
+ * using 16-bit accesses. Later function may blindly call this one with a
+ * boolean to indicate if 8-bit accesses must be enabled of disabled without
+ * knowing if 16-bit accesses are actually in use.
+ */
+static void marvell_nfc_force_byte_access(struct nand_chip *chip,
+					  bool force_8bit)
+{
+	struct marvell_nfc *nfc = to_marvell_nfc(chip->controller);
+	u32 ndcr;
+
+	if (!(chip->options & NAND_BUSWIDTH_16))
+		return;
+
+	ndcr = readl_relaxed(nfc->regs + NDCR);
+
+	if (force_8bit)
+		ndcr &= ~(NDCR_DWIDTH_M | NDCR_DWIDTH_C);
+	else
+		ndcr |= NDCR_DWIDTH_M | NDCR_DWIDTH_C;
+
+	writel_relaxed(ndcr, nfc->regs + NDCR);
+}
+
+/*
+ * Any time a command has to be sent to the controller, the following sequence
+ * has to be followed:
+ * - call marvell_nfc_prepare_cmd()
+ *      -> activate the ND_RUN bit that will kind of 'start a job'
+ *      -> wait the signal indicating the NFC is waiting for a command
+ * - send the command (cmd and address cycles)
+ * - enventually send or receive the data
+ * - call marvell_nfc_end_cmd() with the corresponding flag
+ *      -> wait the flag to be triggered or cancel the job with a timeout
+ *
+ * The following functions are helpers to do this job and keep in the
+ * specialized functions the code that really does the operations.
+ */
+static int marvell_nfc_prepare_cmd(struct nand_chip *chip)
+{
+	struct marvell_nfc *nfc = to_marvell_nfc(chip->controller);
+	u32 ndcr, val;
+	int ret;
+
+	/* Deassert ND_RUN and clear NDSR before issuing any command */
+	ndcr = readl_relaxed(nfc->regs + NDCR);
+	writel_relaxed(ndcr & ~NDCR_ND_RUN, nfc->regs + NDCR);
+	writel_relaxed(readl_relaxed(nfc->regs + NDSR), nfc->regs + NDSR);
+
+	/* Assert ND_RUN bit and wait the NFC to be ready */
+	writel_relaxed(ndcr | NDCR_ND_RUN, nfc->regs + NDCR);
+	ret = readl_relaxed_poll_timeout(nfc->regs + NDSR, val,
+					 val & NDSR_WRCMDREQ,
+					 POLL_PERIOD, POLL_TIMEOUT);
+	if (ret) {
+		dev_err(nfc->dev, "Timeout on WRCMDRE\n");
+		return -ETIMEDOUT;
+	}
+
+	/* Command may be written, clear WRCMDREQ status bit */
+	writel_relaxed(NDSR_WRCMDREQ, nfc->regs + NDSR);
+
+	return 0;
+}
+
+static void marvell_nfc_send_cmd(struct nand_chip *chip,
+				 struct marvell_nfc_op *nfc_op)
+{
+	struct marvell_nand_chip *marvell_nand = to_marvell_nand(chip);
+	struct marvell_nfc *nfc = to_marvell_nfc(chip->controller);
+
+	writel_relaxed(to_nand_sel(marvell_nand)->ndcb0_csel | nfc_op->ndcb[0],
+		       nfc->regs + NDCB0);
+	writel_relaxed(nfc_op->ndcb[1], nfc->regs + NDCB0);
+	writel(nfc_op->ndcb[2], nfc->regs + NDCB0);
+
+	/*
+	 * Write NDCB0 four times only if LEN_OVRD is set or if ADDR6 or ADDR7
+	 * fields are used.
+	 */
+	if (nfc_op->ndcb[0] & NDCB0_LEN_OVRD ||
+	    (nfc_op->ndcb[0] & NDCB0_ADDR_CYC(6)) == NDCB0_ADDR_CYC(6))
+		writel(nfc_op->ndcb[3], nfc->regs + NDCB0);
+}
+
+static int marvell_nfc_wait_ndrun(struct nand_chip *chip)
+{
+	struct marvell_nfc *nfc = to_marvell_nfc(chip->controller);
+	u32 val;
+	int ret;
+
+	/*
+	 * The command is being processed, wait for the ND_RUN bit to be
+	 * cleared by the NFC. If not, we must clear it by hand.
+	 */
+	ret = readl_relaxed_poll_timeout(nfc->regs + NDCR, val,
+					 (val & NDCR_ND_RUN) == 0,
+					 POLL_PERIOD, POLL_TIMEOUT);
+	if (ret) {
+		dev_err(nfc->dev, "Timeout on NAND controller run mode\n");
+		writel_relaxed(readl_relaxed(nfc->regs + NDCR) & ~NDCR_ND_RUN,
+			       nfc->regs + NDCR);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int marvell_nfc_end_cmd(struct nand_chip *chip, int flag,
+			       const char *label)
+{
+	struct marvell_nfc *nfc = to_marvell_nfc(chip->controller);
+	u32 val;
+	int ret;
+
+	ret = readl_relaxed_poll_timeout(nfc->regs + NDSR, val,
+					 val & flag,
+					 POLL_PERIOD, POLL_TIMEOUT);
+	if (ret) {
+		dev_err(nfc->dev, "Timeout on %s\n", label);
+		return ret;
+	}
+
+	writel_relaxed(flag, nfc->regs + NDSR);
+
+	return 0;
+}
+
+static int marvell_nfc_wait_cmdd(struct nand_chip *chip)
+{
+	struct marvell_nand_chip *marvell_nand = to_marvell_nand(chip);
+	int cs_flag = NDSR_CMDD(to_nand_sel(marvell_nand)->ndcb0_csel);
+
+	return marvell_nfc_end_cmd(chip, cs_flag, "CMDD");
+}
+
+static int marvell_nfc_wait_op(struct nand_chip *chip, unsigned int timeout_ms)
+{
+	struct marvell_nfc *nfc = to_marvell_nfc(chip->controller);
+	int ret;
+
+	/* Timeout is expressed in ms */
+	if (!timeout_ms)
+		timeout_ms = IRQ_TIMEOUT;
+
+	init_completion(&nfc->complete);
+
+	marvell_nfc_enable_int(nfc, NDCR_RDYM);
+	ret = wait_for_completion_timeout(&nfc->complete,
+					  msecs_to_jiffies(timeout_ms));
+	marvell_nfc_disable_int(nfc, NDCR_RDYM);
+	marvell_nfc_clear_int(nfc, NDSR_RDY(0) | NDSR_RDY(1));
+
+	if (!ret)
+		dev_err(nfc->dev, "Timeout waiting for RB signal\n");
+
+	return ret ? 0 : -ETIMEDOUT;
+}
+
+static void marvell_nfc_select_chip(struct mtd_info *mtd, int chip)
+{
+	struct nand_chip *nand = mtd_to_nand(mtd);
+	struct marvell_nand_chip *marvell_nand = to_marvell_nand(nand);
+	struct marvell_nfc *nfc = to_marvell_nfc(nand->controller);
+	u32 ndcr;
+
+	if (chip < 0 || chip >= marvell_nand->nsels)
+		return;
+
+	if (chip == marvell_nand->selected)
+		return;
+
+	/*
+	 * Do not change the timing registers when using the DT property
+	 * marvell,nand-keep-config; in that case ->ndtr0 and ->ndtr1 from the
+	 * marvell_nand structure are supposedly empty.
+	 */
+	if (marvell_nand->ndtr0 && marvell_nand->ndtr1) {
+		writel_relaxed(marvell_nand->ndtr0, nfc->regs + NDTR0);
+		writel_relaxed(marvell_nand->ndtr1, nfc->regs + NDTR1);
+	}
+
+	ndcr = readl_relaxed(nfc->regs + NDCR);
+
+	/* Ensure controller is not blocked in operation */
+	ndcr &= ~NDCR_ND_RUN;
+
+	/* Adapt bus width */
+	if (nand->options & NAND_BUSWIDTH_16)
+		ndcr |= NDCR_DWIDTH_M | NDCR_DWIDTH_C;
+
+	/*
+	 * Page size as seen by the controller, either 512B or 2kiB. This size
+	 * will be the reference for the controller when using LEN_OVRD.
+	 */
+	ndcr |= NDCR_PAGE_SZ(mtd->writesize);
+
+	/* Update the control register */
+	writel_relaxed(ndcr,  nfc->regs + NDCR);
+
+	/* Also reset the interrupt status register */
+	marvell_nfc_clear_int(nfc, NDCR_ALL_INT);
+
+	marvell_nand->selected = chip;
+}
+
+static irqreturn_t marvell_nfc_isr(int irq, void *dev_id)
+{
+	struct marvell_nfc *nfc = dev_id;
+	u32 st = readl_relaxed(nfc->regs + NDSR);
+	u32 ien = (~readl_relaxed(nfc->regs + NDCR)) & NDCR_ALL_INT;
+
+	/*
+	 * RDY interrupt mask is one bit in NDCR while there are two status
+	 * bit in NDSR (RDY[cs0/cs2] and RDY[cs1/cs3]).
+	 */
+	if (st & NDSR_RDY(1))
+		st |= NDSR_RDY(0);
+
+	if (!(st & ien))
+		return IRQ_NONE;
+
+	marvell_nfc_disable_int(nfc, st & NDCR_ALL_INT);
+
+	complete(&nfc->complete);
+
+	return IRQ_HANDLED;
+}
+
+/* HW ECC related functions */
+static void marvell_nfc_hw_ecc_enable(struct nand_chip *chip)
+{
+	struct marvell_nfc *nfc = to_marvell_nfc(chip->controller);
+	u32 ndcr = readl_relaxed(nfc->regs + NDCR);
+
+	if (!(ndcr & NDCR_ECC_EN)) {
+		writel_relaxed(ndcr | NDCR_ECC_EN | NDCR_SPARE_EN,
+			       nfc->regs + NDCR);
+
+		/*
+		 * When enabling BCH, set threshold to 0 to always know the
+		 * number of corrected bitflips.
+		 */
+		if (chip->ecc.algo == NAND_ECC_BCH)
+			writel_relaxed(NDECCTRL_BCH_EN, nfc->regs + NDECCCTRL);
+	}
+}
+
+static void marvell_nfc_hw_ecc_disable(struct nand_chip *chip)
+{
+	struct marvell_nfc *nfc = to_marvell_nfc(chip->controller);
+	u32 ndcr = readl_relaxed(nfc->regs + NDCR);
+
+	if (ndcr & NDCR_ECC_EN) {
+		writel_relaxed(ndcr & ~(NDCR_ECC_EN | NDCR_SPARE_EN),
+			       nfc->regs + NDCR);
+		if (chip->ecc.algo == NAND_ECC_BCH)
+			writel_relaxed(0, nfc->regs + NDECCCTRL);
+	}
+}
+
+static void marvell_nfc_hw_ecc_correct(struct nand_chip *chip,
+				       u8 *data, int data_len,
+				       u8 *oob, int oob_len,
+				       unsigned int *max_bitflips)
+{
+	struct mtd_info *mtd = nand_to_mtd(chip);
+	struct marvell_nfc *nfc = to_marvell_nfc(chip->controller);
+	int bf = 0;
+	u32 ndsr;
+
+	ndsr = readl_relaxed(nfc->regs + NDSR);
+
+	/* Check uncorrectable error flag */
+	if (ndsr & NDSR_UNCERR) {
+		writel_relaxed(ndsr, nfc->regs + NDSR);
+
+		/*
+		 * Blank pages (all 0xFF) with no ECC are recognized as bad
+		 * because hardware ECC engine expects non-empty ECC values
+		 * in that case, so whenever an uncorrectable error occurs,
+		 * check if the page is actually blank or not.
+		 *
+		 * It is important to check the emptyness only on oob_len,
+		 * which only covers the spare bytes because after a read with
+		 * ECC enabled, the ECC bytes in the buffer have been set by the
+		 * ECC engine, so they are not 0xFF.
+		 */
+		if (!data)
+			data_len = 0;
+		if (!oob)
+			oob_len = 0;
+		bf = nand_check_erased_ecc_chunk(data, data_len, NULL, 0,
+						 oob, oob_len,
+						 chip->ecc.strength);
+		if (bf < 0) {
+			mtd->ecc_stats.failed++;
+			return;
+		}
+	}
+
+	/* Check correctable error flag */
+	if (ndsr & NDSR_CORERR) {
+		writel_relaxed(ndsr, nfc->regs + NDSR);
+
+		if (chip->ecc.algo == NAND_ECC_BCH)
+			bf = NDSR_ERRCNT(ndsr);
+		else
+			bf = 1;
+	}
+
+	/*
+	 * Derive max_bitflips either from the number of bitflips detected by
+	 * the hardware ECC engine or by nand_check_erased_ecc_chunk().
+	 */
+	mtd->ecc_stats.corrected += bf;
+	*max_bitflips = max_t(unsigned int, *max_bitflips, bf);
+}
+
+/* Reads with HW ECC */
+static int marvell_nfc_hw_ecc_hmg_read_page(struct mtd_info *mtd,
+					    struct nand_chip *chip,
+					    u8 *buf, int oob_required,
+					    int page)
+{
+	struct marvell_nand_chip *marvell_nand = to_marvell_nand(chip);
+	struct marvell_nfc *nfc = to_marvell_nfc(chip->controller);
+	const struct marvell_hw_ecc_layout *lt = to_marvell_nand(chip)->layout;
+	int max_bitflips = 0, ret = 0, i;
+	u8 *data, *oob;
+	struct marvell_nfc_op nfc_op = {
+		.ndcb[0] = NDCB0_CMD_TYPE(TYPE_READ) |
+			   NDCB0_CMD_XTYPE(XTYPE_MONOLITHIC_RW) |
+			   NDCB0_ADDR_CYC(marvell_nand->addr_cyc) |
+			   NDCB0_DBC |
+			   NDCB0_CMD1(NAND_CMD_READ0) |
+			   NDCB0_CMD2(NAND_CMD_READSTART),
+		.ndcb[1] = NDCB1_ADDRS(page),
+		.ndcb[2] = NDCB2_ADDR5(page),
+	};
+
+	/*
+	 * With Hamming, OOB is not fully used (and thus not read entirely), not
+	 * expected bytes could show up at the end of the OOB buffer if not
+	 * explicitly erased.
+	 */
+	if (oob_required)
+		memset(chip->oob_poi, 0xFF, mtd->oobsize);
+
+	ret = marvell_nfc_prepare_cmd(chip);
+	if (ret)
+		return ret;
+
+	marvell_nfc_hw_ecc_enable(chip);
+
+	data = buf;
+	oob = chip->oob_poi;
+
+	/*
+	 * Reading spare area is mandatory when using HW ECC or read operation
+	 * will trigger uncorrectable ECC errors, but do not read ECC here.
+	 */
+	marvell_nfc_send_cmd(chip, &nfc_op);
+
+	marvell_nfc_end_cmd(chip, NDSR_RDDREQ,
+			    "RDDREQ while draining FIFO (data)");
+
+	/* Read the data... */
+	if (data)
+		ioread32_rep(nfc->regs + NDDB, data, FIFO_REP(lt->data_bytes));
+	else
+		for (i = 0; i < FIFO_REP(lt->data_bytes); i++)
+			ioread32_rep(nfc->regs + NDDB, nfc->buf, FIFO_DEPTH_32);
+
+	/* ...then the spare bytes */
+	ioread32_rep(nfc->regs + NDDB, oob, FIFO_REP(lt->spare_bytes));
+
+	marvell_nfc_hw_ecc_correct(chip, data, lt->data_bytes,
+				   oob, lt->spare_bytes, &max_bitflips);
+
+	marvell_nfc_hw_ecc_disable(chip);
+
+	if (oob_required) {
+		/* Read ECC bytes without ECC enabled */
+		nand_read_page_op(chip, page,
+				  lt->data_bytes + lt->spare_bytes,
+				  oob + lt->spare_bytes, lt->ecc_bytes);
+	}
+
+	return max_bitflips;
+}
+
+static void marvell_nfc_hw_ecc_bch_read_chunk(struct nand_chip *chip, int chunk,
+					      u8 *data, int data_len,
+					      u8 *oob, int oob_len,
+					      int oob_required, int page)
+{
+	struct marvell_nand_chip *marvell_nand = to_marvell_nand(chip);
+	struct marvell_nfc *nfc = to_marvell_nfc(chip->controller);
+	const struct marvell_hw_ecc_layout *lt = to_marvell_nand(chip)->layout;
+	int i, j;
+	struct marvell_nfc_op nfc_op = {
+		.ndcb[0] = NDCB0_CMD_TYPE(TYPE_READ) |
+			   NDCB0_ADDR_CYC(marvell_nand->addr_cyc) |
+			   NDCB0_LEN_OVRD,
+		.ndcb[1] = NDCB1_ADDRS(page),
+		.ndcb[2] = NDCB2_ADDR5(page),
+	};
+
+	/*
+	 * Reading spare area is mandatory when using HW ECC or read operation
+	 * will trigger uncorrectable ECC errors, but do not read ECC here.
+	 */
+	nfc_op.ndcb[3] = data_len + oob_len;
+
+	if (marvell_nfc_prepare_cmd(chip))
+		return;
+
+	if (chunk == 0)
+		nfc_op.ndcb[0] |= NDCB0_DBC |
+				  NDCB0_CMD1(NAND_CMD_READ0) |
+				  NDCB0_CMD2(NAND_CMD_READSTART);
+
+	/*
+	 * Trigger the naked read operation only on the last chunk.
+	 * Otherwise, use monolithic read.
+	 */
+	if ((lt->last_chunk_cnt && chunk == lt->full_chunk_cnt) ||
+	    (!lt->last_chunk_cnt && chunk == lt->full_chunk_cnt - 1))
+		nfc_op.ndcb[0] |= NDCB0_CMD_XTYPE(XTYPE_LAST_NAKED_RW);
+	else
+		nfc_op.ndcb[0] |= NDCB0_CMD_XTYPE(XTYPE_MONOLITHIC_RW);
+
+	marvell_nfc_send_cmd(chip, &nfc_op);
+
+	/*
+	 * According to the datasheet, when reading from NDDB
+	 * with BCH enabled, after each 32 bytes reads, we
+	 * have to make sure that the NDSR.RDDREQ bit is set.
+	 *
+	 * Drain the FIFO, 8 32-bit reads at a time, and skip
+	 * the polling on the last read.
+	 *
+	 * Length is a multiple of 32 bytes, hence it is a multiple of 8 too.
+	 *
+	 */
+	for (i = 0; i < data_len; i += FIFO_DEPTH * sizeof(u32)) {
+		marvell_nfc_end_cmd(chip, NDSR_RDDREQ,
+				    "RDDREQ while draining FIFO (data)");
+		if (data) {
+			ioread32_rep(nfc->regs + NDDB, data,
+				     FIFO_DEPTH_32 * sizeof(u32));
+			data += FIFO_DEPTH * sizeof(u32);
+		} else {
+			for (j = 0; j < sizeof(u32); j++)
+				ioread32_rep(nfc->regs + NDDB, nfc->buf,
+					     FIFO_DEPTH_32);
+		}
+	}
+
+	for (i = 0; i < oob_len; i += FIFO_DEPTH * 4) {
+		marvell_nfc_end_cmd(chip, NDSR_RDDREQ,
+				    "RDDREQ while draining FIFO (OOB)");
+		ioread32_rep(nfc->regs + NDDB, oob, FIFO_DEPTH_32 * 4);
+		oob += FIFO_DEPTH * 4;
+	}
+}
+
+static int marvell_nfc_hw_ecc_bch_read_page(struct mtd_info *mtd,
+					    struct nand_chip *chip,
+					    u8 *buf, int oob_required,
+					    int page)
+{
+	const struct marvell_hw_ecc_layout *lt = to_marvell_nand(chip)->layout;
+	int nchunks = lt->full_chunk_cnt + lt->last_chunk_cnt;
+	int max_bitflips = 0;
+	u8 *data, *oob;
+	int chunk, data_len, oob_len, ecc_len;
+	/* Following sizes are used to read the ECC bytes after ECC operation */
+	int fixed_oob_size = lt->spare_bytes + lt->ecc_bytes;
+	int fixed_chunk_size = lt->data_bytes + fixed_oob_size;
+
+	/*
+	 * With BCH, OOB is not fully used (and thus not read entirely), not
+	 * expected bytes could show up at the end of the OOB buffer if not
+	 * explicitly erased.
+	 */
+	if (oob_required)
+		memset(chip->oob_poi, 0xFF, mtd->oobsize);
+
+	marvell_nfc_hw_ecc_enable(chip);
+
+	for (chunk = 0; chunk < nchunks; chunk++) {
+		if (chunk == 0) {
+			/* Init pointers to iterate through the chunks */
+			if (buf)
+				data = buf;
+			else
+				data = NULL;
+			oob = chip->oob_poi;
+		} else {
+			/* Update pointers */
+			if (data)
+				data += lt->data_bytes;
+			oob += (lt->spare_bytes + lt->ecc_bytes + 2);
+		}
+
+		/* Update length */
+		if (chunk < lt->full_chunk_cnt) {
+			data_len = lt->data_bytes;
+			oob_len = lt->spare_bytes;
+			ecc_len = lt->ecc_bytes;
+		} else {
+			data_len = lt->last_data_bytes;
+			oob_len = lt->last_spare_bytes;
+			ecc_len = lt->last_ecc_bytes;
+		}
+
+		/* Read the chunk and detect number of bitflips */
+		marvell_nfc_hw_ecc_bch_read_chunk(chip, chunk, data, data_len,
+						  oob, oob_len, oob_required,
+						  page);
+
+		marvell_nfc_hw_ecc_correct(chip, data, data_len,
+					   oob, oob_len, &max_bitflips);
+	}
+
+	marvell_nfc_hw_ecc_disable(chip);
+
+	if (!oob_required)
+		return max_bitflips;
+
+	/* Read ECC bytes without ECC enabled */
+	for (chunk = 0; chunk < lt->full_chunk_cnt; chunk++)
+		read_page_data(chip, page,
+			       ((chunk + 1) * (fixed_chunk_size)) -
+			       lt->ecc_bytes,
+			       chip->oob_poi + ((chunk + 1) *
+						(fixed_oob_size + 2) -
+						(lt->ecc_bytes + 2)),
+			       lt->ecc_bytes);
+
+	if (lt->last_chunk_cnt)
+		read_page_data(chip, page,
+			       (lt->full_chunk_cnt * fixed_chunk_size) +
+			       lt->last_data_bytes + lt->last_spare_bytes,
+			       chip->oob_poi + (lt->full_chunk_cnt *
+						(fixed_oob_size + 2)) +
+			       lt->last_spare_bytes,
+			       lt->last_ecc_bytes);
+
+	return max_bitflips;
+}
+
+static int marvell_nfc_hw_ecc_read_oob(struct mtd_info *mtd,
+				       struct nand_chip *chip, int page)
+{
+	return chip->ecc.read_page(mtd, chip, NULL, true, page);
+}
+
+/* Raw reads with HW ECC */
+static int marvell_nfc_hw_ecc_read_page_raw(struct mtd_info *mtd,
+					    struct nand_chip *chip, u8 *buf,
+					    int oob_required, int page)
+{
+	const struct marvell_hw_ecc_layout *lt = to_marvell_nand(chip)->layout;
+	int chunk_size = lt->data_bytes + lt->spare_bytes + lt->ecc_bytes;
+	u8 *oob = chip->oob_poi;
+	int chunk;
+
+	if (oob_required)
+		memset(chip->oob_poi, 0xFF, mtd->oobsize);
+
+	for (chunk = 0; chunk < lt->full_chunk_cnt; chunk++) {
+		read_page_data(chip, page, chunk * chunk_size, buf,
+			       lt->data_bytes);
+		buf += lt->data_bytes;
+
+		if (oob_required) {
+			nand_read_data_op(chip, oob, lt->spare_bytes +
+					  lt->ecc_bytes, false);
+			/* Pad user data with 2 bytes when using BCH (30B) */
+			oob += lt->spare_bytes + lt->ecc_bytes + 2;
+		}
+	}
+
+	if (!lt->last_chunk_cnt)
+		return 0;
+
+	read_page_data(chip, page, chunk * chunk_size, buf,
+		       lt->last_data_bytes);
+	if (oob_required)
+		nand_read_data_op(chip, oob, lt->last_spare_bytes +
+				  lt->last_ecc_bytes, false);
+
+	return 0;
+}
+
+static int marvell_nfc_hw_ecc_read_oob_raw(struct mtd_info *mtd,
+					   struct nand_chip *chip, int page)
+{
+	const struct marvell_hw_ecc_layout *lt = to_marvell_nand(chip)->layout;
+	u8 *oob = chip->oob_poi;
+	int chunk_size = lt->data_bytes + lt->spare_bytes + lt->ecc_bytes;
+	int chunk;
+
+	for (chunk = 0; chunk < lt->full_chunk_cnt; chunk++) {
+		/* Move NAND pointer to the next chunk of OOB data */
+		read_page_data(chip, page,
+			       chunk * chunk_size + lt->data_bytes,
+			       oob, lt->spare_bytes + lt->ecc_bytes);
+		/* Pad user data with 2 bytes when using BCH (30B) */
+		oob += lt->spare_bytes + lt->ecc_bytes + 2;
+	}
+
+	if (lt->last_chunk_cnt)
+		nand_read_data_op(chip, oob,
+				  lt->last_spare_bytes + lt->last_ecc_bytes,
+				  false);
+
+	return 0;
+}
+
+/* Writes with HW ECC */
+static int marvell_nfc_hw_ecc_hmg_write_page(struct mtd_info *mtd,
+					     struct nand_chip *chip,
+					     const u8 *buf,
+					     int oob_required, int page)
+{
+	struct marvell_nand_chip *marvell_nand = to_marvell_nand(chip);
+	struct marvell_nfc *nfc = to_marvell_nfc(chip->controller);
+	const struct marvell_hw_ecc_layout *lt = to_marvell_nand(chip)->layout;
+	const u8 *data = buf, *oob = chip->oob_poi;
+	int ret, i;
+	struct marvell_nfc_op nfc_op = {
+		.ndcb[0] = NDCB0_CMD_TYPE(TYPE_WRITE) |
+			   NDCB0_CMD_XTYPE(XTYPE_MONOLITHIC_RW) |
+			   NDCB0_ADDR_CYC(marvell_nand->addr_cyc) |
+			   NDCB0_CMD1(NAND_CMD_SEQIN),
+		.ndcb[1] = NDCB1_ADDRS(page),
+		.ndcb[2] = NDCB2_ADDR5(page),
+	};
+
+	ret = marvell_nfc_prepare_cmd(chip);
+	if (ret)
+		return ret;
+
+	marvell_nfc_hw_ecc_enable(chip);
+
+		marvell_nfc_send_cmd(chip, &nfc_op);
+
+	marvell_nfc_end_cmd(chip, NDSR_WRDREQ,
+			    "WRDREQ while loading FIFO (data)");
+
+	/* Write the data. If buf is empty, write empty bytes (0xFF) */
+	if (data) {
+		iowrite32_rep(nfc->regs + NDDB, data, FIFO_REP(lt->data_bytes));
+	} else {
+		data = nfc->buf;
+		memset(nfc->buf, 0xFF, FIFO_DEPTH);
+		for (i = 0; i < FIFO_REP(lt->data_bytes) / FIFO_DEPTH_32; i++)
+			iowrite32_rep(nfc->regs + NDDB, data, FIFO_DEPTH_32);
+	}
+
+	/* Then write the OOB data */
+	iowrite32_rep(nfc->regs + NDDB, oob, FIFO_REP(lt->spare_bytes));
+
+	ret = marvell_nfc_wait_op(chip,
+				  chip->data_interface.timings.sdr.tPROG_max);
+
+	marvell_nfc_hw_ecc_disable(chip);
+
+	if (ret & NAND_STATUS_FAIL)
+		return -EIO;
+
+	return 0;
+}
+
+static void marvell_nfc_hw_ecc_bch_write_chunk(struct nand_chip *chip,
+					       int chunk, const u8 *data,
+					       u8 *oob, int oob_required,
+					       int page)
+{
+	struct marvell_nand_chip *marvell_nand = to_marvell_nand(chip);
+	struct marvell_nfc *nfc = to_marvell_nfc(chip->controller);
+	const struct marvell_hw_ecc_layout *lt = to_marvell_nand(chip)->layout;
+	int data_len, oob_len, i;
+	struct marvell_nfc_op nfc_op = {
+		.ndcb[0] = NDCB0_CMD_TYPE(TYPE_WRITE) | NDCB0_LEN_OVRD,
+	};
+
+	/* OOB area to write is only spare area when using HW ECC */
+	if (chunk < lt->full_chunk_cnt) {
+		data_len = lt->data_bytes;
+		oob_len = lt->spare_bytes;
+	} else {
+		data_len = lt->last_data_bytes;
+		oob_len = lt->last_spare_bytes;
+	}
+
+	nfc_op.ndcb[3] = data_len + oob_len;
+
+	/*
+	 * First operation dispatches the CMD_SEQIN command, issue the address
+	 * cycles and asks for the first chunk of data.
+	 * Last operation dispatches the PAGEPROG command and also asks for the
+	 * last chunk of data.
+	 * All operations in the middle (if any) will issue a naked write and
+	 * also ask for data.
+	 */
+	if (chunk == 0) {
+		nfc_op.ndcb[0] |= NDCB0_CMD_XTYPE(XTYPE_WRITE_DISPATCH) |
+				  NDCB0_ADDR_CYC(marvell_nand->addr_cyc) |
+				  NDCB0_CMD1(NAND_CMD_SEQIN);
+		nfc_op.ndcb[1] |= NDCB1_ADDRS(page);
+		nfc_op.ndcb[2] |= NDCB2_ADDR5(page);
+	} else if (
+		(lt->last_chunk_cnt && (chunk == lt->full_chunk_cnt)) ||
+		(!lt->last_chunk_cnt &&	(chunk == lt->full_chunk_cnt - 1))) {
+		nfc_op.ndcb[0] |= NDCB0_CMD2(NAND_CMD_PAGEPROG) | NDCB0_DBC |
+				  NDCB0_CMD_XTYPE(XTYPE_LAST_NAKED_RW);
+	} else {
+		nfc_op.ndcb[0] |= NDCB0_CMD_XTYPE(XTYPE_NAKED_RW);
+	}
+
+	/*
+	 * If this is the first chunk, the previous command also embedded
+	 * the write operation, no need to repeat it.
+	 */
+	if (marvell_nfc_prepare_cmd(chip))
+		return;
+
+	marvell_nfc_send_cmd(chip, &nfc_op);
+
+	marvell_nfc_end_cmd(chip, NDSR_WRDREQ,
+			    "WRDREQ while loading FIFO (data)");
+
+	/* Effectively write the data to the data buffer */
+	if (data) {
+		data += chunk * lt->data_bytes;
+		iowrite32_rep(nfc->regs + NDDB, data, FIFO_REP(data_len));
+	} else {
+		memset(nfc->buf, 0xFF, FIFO_DEPTH);
+		data = nfc->buf;
+		for (i = 0; i < FIFO_REP(data_len) / FIFO_DEPTH_32; i++)
+			iowrite32_rep(nfc->regs + NDDB, data, FIFO_DEPTH_32);
+	}
+
+	/* Pad user data with 2 bytes when using BCH (30B) */
+	oob += chunk * lt->spare_bytes;
+	iowrite32_rep(nfc->regs + NDDB, oob, FIFO_REP(oob_len));
+}
+
+static int marvell_nfc_hw_ecc_bch_write_page(struct mtd_info *mtd,
+					     struct nand_chip *chip,
+					     const u8 *buf,
+					     int oob_required, int page)
+{
+	const struct marvell_hw_ecc_layout *lt = to_marvell_nand(chip)->layout;
+	int nchunks = lt->full_chunk_cnt + lt->last_chunk_cnt;
+	int chunk, ret;
+
+	marvell_nfc_hw_ecc_enable(chip);
+
+	for (chunk = 0; chunk < nchunks; chunk++) {
+		marvell_nfc_hw_ecc_bch_write_chunk(chip, chunk, buf,
+						   chip->oob_poi,
+						   oob_required, page);
+		/*
+		 * Waiting only for CMDD or PAGED is not enough, ECC are
+		 * partially written. No flag is set once the operation is
+		 * really finished but the ND_RUN bit is cleared, so wait for it
+		 * before stepping into the next command.
+		 */
+		marvell_nfc_wait_ndrun(chip);
+	}
+
+	ret = marvell_nfc_wait_op(chip,
+				  chip->data_interface.timings.sdr.tPROG_max);
+
+	marvell_nfc_hw_ecc_disable(chip);
+
+	if (ret & NAND_STATUS_FAIL)
+		return -EIO;
+
+	return 0;
+}
+
+static int marvell_nfc_hw_ecc_write_oob(struct mtd_info *mtd,
+					struct nand_chip *chip, int page)
+{
+	return chip->ecc.write_page(mtd, chip, NULL, true, page);
+}
+
+/* Raw writes with HW ECC */
+static int marvell_nfc_hw_ecc_write_page_raw(struct mtd_info *mtd,
+					     struct nand_chip *chip,
+					     const u8 *buf,
+					     int oob_required, int page)
+{
+	const struct marvell_hw_ecc_layout *lt = to_marvell_nand(chip)->layout;
+	int nchunks = lt->full_chunk_cnt + lt->last_chunk_cnt;
+	int oob_size = lt->spare_bytes + lt->ecc_bytes;
+	int last_oob_size = lt->last_spare_bytes + lt->last_ecc_bytes;
+	int chunk;
+
+	nand_prog_page_begin_op(chip, page, 0, NULL, 0);
+
+	for (chunk = 0; chunk < nchunks; chunk++) {
+		/*
+		 * OOB are not 8-bytes aligned anyway so change the column
+		 *@each cycle
+		 */
+		nand_change_write_column_op(chip, chunk * (lt->data_bytes +
+							   oob_size),
+					    NULL, 0, false);
+
+		if (chunk < lt->full_chunk_cnt)
+			nand_write_data_op(chip, buf + (chunk * lt->data_bytes),
+					   lt->data_bytes, false);
+		else
+			nand_write_data_op(chip, buf + (chunk * lt->data_bytes),
+					   lt->last_data_bytes, false);
+
+		if (!oob_required)
+			continue;
+
+		if (chunk < lt->full_chunk_cnt)
+			nand_write_data_op(chip, chip->oob_poi +
+					   (chunk * (oob_size + 2)),
+					   oob_size, false);
+		else
+			nand_write_data_op(chip, chip->oob_poi +
+					   (chunk * (oob_size + 2)),
+					   last_oob_size, false);
+	}
+
+	return nand_prog_page_end_op(chip);
+}
+
+static int marvell_nfc_hw_ecc_write_oob_raw(struct mtd_info *mtd,
+					    struct nand_chip *chip, int page)
+{
+	const struct marvell_hw_ecc_layout *lt = to_marvell_nand(chip)->layout;
+	int chunk_size = lt->data_bytes + lt->spare_bytes + lt->ecc_bytes;
+	int nchunks = lt->full_chunk_cnt + lt->last_chunk_cnt;
+	int oob_size = lt->spare_bytes + lt->ecc_bytes;
+	int last_oob_size = lt->last_spare_bytes + lt->last_ecc_bytes;
+	int chunk;
+
+	nand_prog_page_begin_op(chip, page, 0, NULL, 0);
+
+	for (chunk = 0; chunk < nchunks; chunk++) {
+		nand_change_write_column_op(chip, lt->data_bytes +
+					    (chunk * chunk_size), NULL, 0,
+					    false);
+
+		if (chunk < lt->full_chunk_cnt)
+			nand_write_data_op(chip, chip->oob_poi +
+					   (chunk * (oob_size + 2)),
+					   oob_size, false);
+		else
+			nand_write_data_op(chip, chip->oob_poi +
+					   (chunk * (oob_size + 2)),
+					   last_oob_size, false);
+	}
+
+	return nand_prog_page_end_op(chip);
+}
+
+/* NAND framework ->exec_op() hooks and related helpers */
+static void marvell_nfc_parse_instructions(const struct nand_subop *subop,
+					   struct marvell_nfc_op *nfc_op)
+{
+	const struct nand_op_instr *instr = NULL;
+	bool first_cmd = true;
+	unsigned int op_id;
+	int i;
+
+	/* Reset the input structure as most of its fields will be OR'ed */
+	memset(nfc_op, 0, sizeof(struct marvell_nfc_op));
+
+	for (op_id = 0; op_id < subop->ninstrs; op_id++) {
+		unsigned int offset, naddrs;
+		const u8 *addrs;
+		int len = nand_subop_get_data_len(subop, op_id);
+
+		instr = &subop->instrs[op_id];
+
+		switch (instr->type) {
+		case NAND_OP_CMD_INSTR:
+			pr_debug("  ->CMD      [0x%02x]\n", instr->cmd.opcode);
+
+			if (first_cmd)
+				nfc_op->ndcb[0] |=
+					NDCB0_CMD1(instr->cmd.opcode);
+			else
+				nfc_op->ndcb[0] |=
+					NDCB0_CMD2(instr->cmd.opcode) |
+					NDCB0_DBC;
+
+			nfc_op->cle_ale_delay_ns = instr->delay_ns;
+			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->addr.addrs[offset];
+
+			pr_debug("  ->ADDR     [%d cyc]", naddrs);
+
+			nfc_op->ndcb[0] |= NDCB0_ADDR_CYC(naddrs);
+
+			for (i = 0; i < min_t(unsigned int, 4, naddrs); i++)
+				nfc_op->ndcb[1] |= addrs[i] << (8 * i);
+
+			if (naddrs >= 5)
+				nfc_op->ndcb[2] |= NDCB2_ADDR5(addrs[5]);
+			if (naddrs >= 6)
+				nfc_op->ndcb[3] |= NDCB3_ADDR6(addrs[6]);
+			if (naddrs == 7)
+				nfc_op->ndcb[3] |= NDCB3_ADDR7(addrs[7]);
+
+			nfc_op->cle_ale_delay_ns = instr->delay_ns;
+			break;
+
+		case NAND_OP_DATA_IN_INSTR:
+			pr_debug("  ->DATA_IN  [%d B%s]\n", len,
+				 instr->data.force_8bit ? ", force 8-bit" : "");
+
+			nfc_op->data_instr = instr;
+			nfc_op->data_instr_idx = op_id;
+			nfc_op->ndcb[0] |=
+				NDCB0_CMD_TYPE(TYPE_READ) |
+				NDCB0_CMD_XTYPE(XTYPE_MONOLITHIC_RW) |
+				NDCB0_LEN_OVRD;
+			nfc_op->ndcb[3] |= round_up(len, FIFO_DEPTH);
+			nfc_op->data_delay_ns = instr->delay_ns;
+			break;
+
+		case NAND_OP_DATA_OUT_INSTR:
+			pr_debug("  ->DATA_OUT [%d B%s]\n", len,
+				 instr->data.force_8bit ? ", force 8-bit" : "");
+
+			nfc_op->data_instr = instr;
+			nfc_op->data_instr_idx = op_id;
+			nfc_op->ndcb[0] |=
+				NDCB0_CMD_TYPE(TYPE_WRITE) |
+				NDCB0_CMD_XTYPE(XTYPE_MONOLITHIC_RW) |
+				NDCB0_LEN_OVRD;
+			nfc_op->ndcb[3] |= round_up(len, FIFO_DEPTH);
+			nfc_op->data_delay_ns = instr->delay_ns;
+			break;
+
+		case NAND_OP_WAITRDY_INSTR:
+			pr_debug("  ->WAITRDY  [max %d ms]\n",
+				 instr->waitrdy.timeout_ms);
+
+			nfc_op->rdy_timeout_ms = instr->waitrdy.timeout_ms;
+			nfc_op->rdy_delay_ns = instr->delay_ns;
+			break;
+		}
+	}
+}
+
+static void marvell_nfc_xfer_data(struct nand_chip *chip,
+				  const struct nand_subop *subop,
+				  struct marvell_nfc_op *nfc_op)
+{
+	struct marvell_nfc *nfc = to_marvell_nfc(chip->controller);
+	unsigned int op_id = nfc_op->data_instr_idx;
+	int len = nand_subop_get_data_len(subop, op_id);
+	int offset = nand_subop_get_data_start_off(subop, op_id);
+	int last_len = len % FIFO_DEPTH;
+	int last_full_offset = round_down(len, FIFO_DEPTH);
+	const struct nand_op_instr *instr = nfc_op->data_instr;
+	u8 *in;
+	const u8 *out;
+	int i;
+
+	if (instr->data.force_8bit)
+		marvell_nfc_force_byte_access(chip, true);
+
+	if (instr->type == NAND_OP_DATA_IN_INSTR) {
+		in = &((u8 *)instr->data.in)[offset];
+
+		for (i = 0; i < last_full_offset; i += FIFO_DEPTH)
+			ioread32_rep(nfc->regs + NDDB,
+				     &((u32 *)in)[i / sizeof(u32)],
+				     FIFO_DEPTH_32);
+
+		if (last_len) {
+			ioread32_rep(nfc->regs + NDDB, nfc->buf, FIFO_DEPTH_32);
+			memcpy(&in[last_full_offset], nfc->buf, last_len);
+		}
+	} else {
+		out = &((const u8 *)instr->data.out)[offset];
+
+		for (i = 0; i < last_full_offset; i += FIFO_DEPTH)
+			iowrite32_rep(nfc->regs + NDDB,
+				      &((u32 *)out)[i / sizeof(u32)],
+				      FIFO_DEPTH_32);
+
+		if (last_len) {
+			memcpy(nfc->buf, &out[last_full_offset], last_len);
+			iowrite32_rep(nfc->regs + NDDB, nfc->buf,
+				      FIFO_DEPTH_32);
+		}
+	}
+
+	if (instr->data.force_8bit)
+		marvell_nfc_force_byte_access(chip, false);
+}
+
+static int marvell_nfc_monolithic_access_exec(struct nand_chip *chip,
+					      const struct nand_subop *subop)
+{
+	struct marvell_nfc_op nfc_op;
+	bool reading;
+	int ret;
+
+	marvell_nfc_parse_instructions(subop, &nfc_op);
+	reading = nfc_op.data_instr->type == NAND_OP_DATA_IN_INSTR;
+
+	ret = marvell_nfc_prepare_cmd(chip);
+	if (ret)
+		return ret;
+
+	marvell_nfc_send_cmd(chip, &nfc_op);
+	ret = marvell_nfc_end_cmd(chip, NDSR_RDDREQ | NDSR_WRDREQ,
+				  "RDDREQ/WRDREQ while draining raw data");
+	cond_delay(nfc_op.cle_ale_delay_ns);
+
+	if (reading) {
+		if (nfc_op.rdy_timeout_ms)
+			ret = marvell_nfc_wait_op(chip, nfc_op.rdy_timeout_ms);
+		cond_delay(nfc_op.rdy_delay_ns);
+	}
+
+	if (ret)
+		return ret;
+
+	marvell_nfc_xfer_data(chip, subop, &nfc_op);
+	ret = marvell_nfc_wait_cmdd(chip);
+	cond_delay(nfc_op.data_delay_ns);
+
+	if (!reading) {
+		if (nfc_op.rdy_timeout_ms)
+			ret = marvell_nfc_wait_op(chip, nfc_op.rdy_timeout_ms);
+		cond_delay(nfc_op.rdy_delay_ns);
+	}
+
+	return ret;
+}
+
+static int marvell_nfc_reset_cmd_type_exec(struct nand_chip *chip,
+					   const struct nand_subop *subop)
+{
+	struct marvell_nfc_op nfc_op;
+	int ret;
+
+	marvell_nfc_parse_instructions(subop, &nfc_op);
+	nfc_op.ndcb[0] |= NDCB0_CMD_TYPE(TYPE_RESET);
+
+	ret = marvell_nfc_prepare_cmd(chip);
+	if (ret)
+		return ret;
+
+	marvell_nfc_send_cmd(chip, &nfc_op);
+	ret = marvell_nfc_wait_ndrun(chip);
+	cond_delay(nfc_op.cle_ale_delay_ns);
+
+	if (nfc_op.rdy_timeout_ms)
+		ret = marvell_nfc_wait_op(chip, nfc_op.rdy_timeout_ms);
+	cond_delay(nfc_op.rdy_delay_ns);
+
+	return ret;
+}
+
+static int marvell_nfc_erase_cmd_type_exec(struct nand_chip *chip,
+					   const struct nand_subop *subop)
+{
+	struct marvell_nfc_op nfc_op;
+	int ret;
+
+	marvell_nfc_parse_instructions(subop, &nfc_op);
+	nfc_op.ndcb[0] |= NDCB0_CMD_TYPE(TYPE_ERASE);
+
+	ret = marvell_nfc_prepare_cmd(chip);
+	if (ret)
+		return ret;
+
+	marvell_nfc_send_cmd(chip, &nfc_op);
+	ret = marvell_nfc_wait_ndrun(chip);
+	cond_delay(nfc_op.cle_ale_delay_ns);
+
+	if (nfc_op.rdy_timeout_ms)
+		ret = marvell_nfc_wait_op(chip, nfc_op.rdy_timeout_ms);
+	cond_delay(nfc_op.rdy_delay_ns);
+
+	return ret;
+}
+
+static int marvell_nfc_naked_access_exec(struct nand_chip *chip,
+					 const struct nand_subop *subop)
+{
+	struct marvell_nfc_op nfc_op;
+	int ret;
+
+	marvell_nfc_parse_instructions(subop, &nfc_op);
+
+	/*
+	 * Naked access are different in that they need to be flagged as naked
+	 * by the controller. Reset the controller registers fields that inform
+	 * on the type and refill them according to the ongoing operation.
+	 */
+	nfc_op.ndcb[0] &= ~(NDCB0_CMD_TYPE(TYPE_MASK) |
+			    NDCB0_CMD_XTYPE(XTYPE_MASK));
+	switch (subop->instrs[0].type) {
+	case NAND_OP_CMD_INSTR:
+		nfc_op.ndcb[0] |= NDCB0_CMD_TYPE(TYPE_NAKED_CMD);
+		break;
+	case NAND_OP_ADDR_INSTR:
+		nfc_op.ndcb[0] |= NDCB0_CMD_TYPE(TYPE_NAKED_ADDR);
+		break;
+	case NAND_OP_DATA_IN_INSTR:
+		nfc_op.ndcb[0] |= NDCB0_CMD_TYPE(TYPE_READ) |
+				  NDCB0_CMD_XTYPE(XTYPE_LAST_NAKED_RW);
+		break;
+	case NAND_OP_DATA_OUT_INSTR:
+		nfc_op.ndcb[0] |= NDCB0_CMD_TYPE(TYPE_WRITE) |
+				  NDCB0_CMD_XTYPE(XTYPE_LAST_NAKED_RW);
+		break;
+	default:
+		/* This should never happen */
+		break;
+	}
+
+	ret = marvell_nfc_prepare_cmd(chip);
+	if (ret)
+		return ret;
+
+	marvell_nfc_send_cmd(chip, &nfc_op);
+
+	if (!nfc_op.data_instr) {
+		ret = marvell_nfc_wait_ndrun(chip);
+		cond_delay(nfc_op.cle_ale_delay_ns);
+		return ret;
+	}
+
+	ret = marvell_nfc_end_cmd(chip, NDSR_RDDREQ | NDSR_WRDREQ,
+				  "RDDREQ/WRDREQ while draining raw data");
+	if (ret)
+		return ret;
+
+	marvell_nfc_xfer_data(chip, subop, &nfc_op);
+	ret = marvell_nfc_wait_cmdd(chip);
+	cond_delay(nfc_op.data_delay_ns);
+
+	return ret;
+}
+
+static int marvell_nfc_naked_waitrdy_exec(struct nand_chip *chip,
+					  const struct nand_subop *subop)
+{
+	struct marvell_nfc_op nfc_op;
+	int ret;
+
+	marvell_nfc_parse_instructions(subop, &nfc_op);
+
+	ret = marvell_nfc_wait_op(chip, nfc_op.rdy_timeout_ms);
+	cond_delay(nfc_op.rdy_delay_ns);
+
+	return ret;
+}
+
+static const struct nand_op_parser marvell_nfc_op_parser = NAND_OP_PARSER(
+	/* Monolithic read/write */
+	NAND_OP_PARSER_PATTERN(
+		marvell_nfc_monolithic_access_exec,
+		NAND_OP_PARSER_PAT_CMD_ELEM(false),
+		NAND_OP_PARSER_PAT_ADDR_ELEM(true, MAX_ADDRESS_CYC),
+		NAND_OP_PARSER_PAT_CMD_ELEM(true),
+		NAND_OP_PARSER_PAT_WAITRDY_ELEM(true),
+		NAND_OP_PARSER_PAT_DATA_IN_ELEM(false, MAX_CHUNK_SIZE)),
+	NAND_OP_PARSER_PATTERN(
+		marvell_nfc_monolithic_access_exec,
+		NAND_OP_PARSER_PAT_CMD_ELEM(false),
+		NAND_OP_PARSER_PAT_ADDR_ELEM(false, MAX_ADDRESS_CYC),
+		NAND_OP_PARSER_PAT_DATA_OUT_ELEM(false, MAX_CHUNK_SIZE),
+		NAND_OP_PARSER_PAT_CMD_ELEM(true),
+		NAND_OP_PARSER_PAT_WAITRDY_ELEM(true)),
+	/* Isolated commands (reset, erase, begin prog,...) */
+	NAND_OP_PARSER_PATTERN(
+		marvell_nfc_erase_cmd_type_exec,
+		NAND_OP_PARSER_PAT_CMD_ELEM(false),
+		NAND_OP_PARSER_PAT_ADDR_ELEM(false, MAX_ADDRESS_CYC),
+		NAND_OP_PARSER_PAT_CMD_ELEM(false),
+		NAND_OP_PARSER_PAT_WAITRDY_ELEM(true)),
+	NAND_OP_PARSER_PATTERN(
+		marvell_nfc_reset_cmd_type_exec,
+		NAND_OP_PARSER_PAT_CMD_ELEM(false),
+		NAND_OP_PARSER_PAT_WAITRDY_ELEM(false)),
+	/* Naked commands */
+	NAND_OP_PARSER_PATTERN(
+		marvell_nfc_naked_access_exec,
+		NAND_OP_PARSER_PAT_CMD_ELEM(false)),
+	NAND_OP_PARSER_PATTERN(
+		marvell_nfc_naked_access_exec,
+		NAND_OP_PARSER_PAT_ADDR_ELEM(false, MAX_ADDRESS_CYC)),
+	NAND_OP_PARSER_PATTERN(
+		marvell_nfc_naked_access_exec,
+		NAND_OP_PARSER_PAT_DATA_IN_ELEM(false, MAX_CHUNK_SIZE)),
+	NAND_OP_PARSER_PATTERN(
+		marvell_nfc_naked_access_exec,
+		NAND_OP_PARSER_PAT_DATA_OUT_ELEM(false, MAX_CHUNK_SIZE)),
+	NAND_OP_PARSER_PATTERN(
+		marvell_nfc_naked_waitrdy_exec,
+		NAND_OP_PARSER_PAT_WAITRDY_ELEM(false)),
+	);
+
+static int marvell_nfc_exec_op(struct nand_chip *chip,
+			       const struct nand_operation *op,
+			       bool check_only)
+{
+	return nand_op_parser_exec_op(chip, &marvell_nfc_op_parser,
+				      op, check_only);
+}
+
+/*
+ * HW ECC layouts, identical to old pxa3xx_nand driver,
+ * to be fully backward compatible.
+ */
+static int marvell_nand_ooblayout_ecc(struct mtd_info *mtd, int section,
+				      struct mtd_oob_region *oobregion)
+{
+	struct nand_chip *chip = mtd_to_nand(mtd);
+	const struct marvell_hw_ecc_layout *lt = to_marvell_nand(chip)->layout;
+	int nchunks = lt->full_chunk_cnt;
+
+	if (section >= nchunks)
+		return -ERANGE;
+
+	oobregion->offset = ((lt->spare_bytes + lt->ecc_bytes) * section) +
+		lt->spare_bytes;
+	oobregion->length = lt->ecc_bytes;
+
+	return 0;
+}
+
+static int marvell_nand_ooblayout_free(struct mtd_info *mtd, int section,
+				       struct mtd_oob_region *oobregion)
+{
+	struct nand_chip *chip = mtd_to_nand(mtd);
+	const struct marvell_hw_ecc_layout *lt = to_marvell_nand(chip)->layout;
+	int nchunks = lt->full_chunk_cnt;
+
+	if (section >= nchunks)
+		return -ERANGE;
+
+	if (!lt->spare_bytes)
+		return 0;
+
+	oobregion->offset = section * (lt->spare_bytes + lt->ecc_bytes);
+	oobregion->length = lt->spare_bytes;
+	if (!section) {
+		/*
+		 * Bootrom looks in bytes 0 & 5 for bad blocks for the
+		 * 4KB page / 4bit BCH combination.
+		 */
+		if (mtd->writesize == 4096 && lt->data_bytes == 2048) {
+			oobregion->offset += 6;
+			oobregion->length -= 6;
+		} else {
+			oobregion->offset += 2;
+			oobregion->length -= 2;
+		}
+	}
+
+	return 0;
+}
+
+static const struct mtd_ooblayout_ops marvell_nand_ooblayout_ops = {
+	.ecc = marvell_nand_ooblayout_ecc,
+	.free = marvell_nand_ooblayout_free,
+};
+
+static int marvell_nand_hw_ecc_ctrl_init(struct mtd_info *mtd,
+					 struct nand_ecc_ctrl *ecc,
+					 struct device_node *np)
+{
+	struct nand_chip *chip = mtd_to_nand(mtd);
+	struct marvell_nfc *nfc = to_marvell_nfc(chip->controller);
+	const struct marvell_hw_ecc_layout *l;
+	int i;
+
+	to_marvell_nand(chip)->layout = NULL;
+	for (i = 0; i < ARRAY_SIZE(marvell_nfc_layouts); i++) {
+		l = &marvell_nfc_layouts[i];
+		if (mtd->writesize == l->writesize &&
+		    ecc->size == l->chunk && ecc->strength == l->strength) {
+			to_marvell_nand(chip)->layout = l;
+			break;
+		}
+	}
+
+	if (!to_marvell_nand(chip)->layout) {
+		dev_err(nfc->dev,
+			"ECC strength %d at page size %d is not supported\n",
+			ecc->strength, mtd->writesize);
+		return -ENOTSUPP;
+	}
+
+	mtd_set_ooblayout(mtd, &marvell_nand_ooblayout_ops);
+	ecc->steps = l->full_chunk_cnt + l->last_chunk_cnt;
+	ecc->size = l->data_bytes;
+
+	if (ecc->strength == 1) {
+		chip->ecc.algo = NAND_ECC_HAMMING;
+		ecc->read_page = marvell_nfc_hw_ecc_hmg_read_page;
+		ecc->write_page = marvell_nfc_hw_ecc_hmg_write_page;
+	} else {
+		chip->ecc.algo = NAND_ECC_BCH;
+		ecc->read_page = marvell_nfc_hw_ecc_bch_read_page;
+		ecc->write_page = marvell_nfc_hw_ecc_bch_write_page;
+		ecc->strength = 16;
+	}
+
+	ecc->read_oob = marvell_nfc_hw_ecc_read_oob;
+	ecc->write_oob = marvell_nfc_hw_ecc_write_oob;
+
+	ecc->read_page_raw = marvell_nfc_hw_ecc_read_page_raw;
+	ecc->write_page_raw = marvell_nfc_hw_ecc_write_page_raw;
+	ecc->read_oob_raw = marvell_nfc_hw_ecc_read_oob_raw;
+	ecc->write_oob_raw = marvell_nfc_hw_ecc_write_oob_raw;
+
+	return 0;
+}
+
+static int marvell_nand_ecc_init(struct mtd_info *mtd,
+				 struct nand_ecc_ctrl *ecc,
+				 struct device_node *np)
+{
+	struct nand_chip *chip = mtd_to_nand(mtd);
+	int ret;
+
+	if (!ecc->size)
+		ecc->size = chip->ecc_step_ds;
+
+	if (!ecc->strength)
+		ecc->strength = chip->ecc_strength_ds;
+
+	if (!ecc->size || !ecc->strength)
+		return -EINVAL;
+
+	switch (ecc->mode) {
+	case NAND_ECC_HW:
+		ret = marvell_nand_hw_ecc_ctrl_init(mtd, ecc, np);
+		if (ret)
+			return ret;
+		break;
+	case NAND_ECC_NONE:
+		chip->ecc.algo = 0;
+	case NAND_ECC_SOFT:
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static u8 bbt_pattern[] = {'M', 'V', 'B', 'b', 't', '0' };
+static u8 bbt_mirror_pattern[] = {'1', 't', 'b', 'B', 'V', 'M' };
+
+static struct nand_bbt_descr bbt_main_descr = {
+	.options = NAND_BBT_LASTBLOCK | NAND_BBT_CREATE | NAND_BBT_WRITE |
+		   NAND_BBT_2BIT | NAND_BBT_VERSION,
+	.offs =	8,
+	.len = 6,
+	.veroffs = 14,
+	.maxblocks = 8,	/* Last 8 blocks in each chip */
+	.pattern = bbt_pattern
+};
+
+static struct nand_bbt_descr bbt_mirror_descr = {
+	.options = NAND_BBT_LASTBLOCK | NAND_BBT_CREATE | NAND_BBT_WRITE |
+		   NAND_BBT_2BIT | NAND_BBT_VERSION,
+	.offs =	8,
+	.len = 6,
+	.veroffs = 14,
+	.maxblocks = 8,	/* Last 8 blocks in each chip */
+	.pattern = bbt_mirror_pattern
+};
+
+static int marvell_nfc_setup_data_interface(struct mtd_info *mtd, int chipnr,
+					    const struct nand_data_interface
+					    *conf)
+{
+	struct nand_chip *chip = mtd_to_nand(mtd);
+	struct marvell_nand_chip *marvell_nand = to_marvell_nand(chip);
+	struct marvell_nfc *nfc = to_marvell_nfc(chip->controller);
+	unsigned int period_ns = 1000000000 / clk_get_rate(nfc->ecc_clk) * 2;
+	const struct nand_sdr_timings *sdr;
+	struct marvell_nfc_timings nfc_tmg;
+	int read_delay;
+
+	sdr = nand_get_sdr_timings(conf);
+	if (IS_ERR(sdr))
+		return PTR_ERR(sdr);
+
+	/*
+	 * SDR timings are given in pico-seconds while NFC timings must be
+	 * expressed in NAND controller clock cycles, which is half of the
+	 * frequency of the accessible ECC clock retrieved by clk_get_rate().
+	 * This is not written anywhere in the datasheet but was observed
+	 * with an oscilloscope.
+	 *
+	 * NFC datasheet gives equations from which thoses calculations
+	 * are derived, they tend to be slightly more restrictives than the
+	 * given core timings and may improve the overall speed.
+	 */
+	nfc_tmg.tRP = TO_CYCLES(DIV_ROUND_UP(sdr->tRC_min, 2), period_ns) - 1;
+	nfc_tmg.tRH = nfc_tmg.tRP;
+	nfc_tmg.tWP = TO_CYCLES(DIV_ROUND_UP(sdr->tWC_min, 2), period_ns) - 1;
+	nfc_tmg.tWH = nfc_tmg.tWP;
+	nfc_tmg.tCS = TO_CYCLES(sdr->tCS_min, period_ns);
+	nfc_tmg.tCH = TO_CYCLES(sdr->tCH_min, period_ns) - 1;
+	nfc_tmg.tADL = TO_CYCLES(sdr->tADL_min, period_ns);
+	/*
+	 * Read delay is the time of propagation from SoC pins to NFC internal
+	 * logic. With non-EDO timings, this is MIN_RD_DEL_CNT clock cycles. In
+	 * EDO mode, an additional delay of tRH must be taken into account so
+	 * the data is sampled on the falling edge instead of the rising edge.
+	 */
+	read_delay = sdr->tRC_min >= 30000 ?
+		MIN_RD_DEL_CNT : MIN_RD_DEL_CNT + nfc_tmg.tRH;
+
+	nfc_tmg.tAR = TO_CYCLES(sdr->tAR_min, period_ns);
+	/*
+	 * tWHR and tRHW are supposed to be read to write delays (and vice
+	 * versa) but in some cases, ie. when doing a change column, they must
+	 * be greater than that to be sure tCCS delay is respected.
+	 */
+	nfc_tmg.tWHR = TO_CYCLES(max_t(int, sdr->tWHR_min, sdr->tCCS_min),
+				 period_ns) - 2,
+	nfc_tmg.tRHW = TO_CYCLES(max_t(int, sdr->tRHW_min, sdr->tCCS_min),
+				 period_ns);
+
+	/* Use WAIT_MODE (wait for RB line) instead of only relying on delays */
+	nfc_tmg.tR = TO_CYCLES(sdr->tWB_max, period_ns);
+
+	if (chipnr < 0)
+		return 0;
+
+	marvell_nand->ndtr0 =
+		NDTR0_TRP(nfc_tmg.tRP) |
+		NDTR0_TRH(nfc_tmg.tRH) |
+		NDTR0_ETRP(nfc_tmg.tRP) |
+		NDTR0_TWP(nfc_tmg.tWP) |
+		NDTR0_TWH(nfc_tmg.tWH) |
+		NDTR0_TCS(nfc_tmg.tCS) |
+		NDTR0_TCH(nfc_tmg.tCH) |
+		NDTR0_RD_CNT_DEL(read_delay) |
+		NDTR0_SELCNTR |
+		NDTR0_TADL(nfc_tmg.tADL);
+
+	marvell_nand->ndtr1 =
+		NDTR1_TAR(nfc_tmg.tAR) |
+		NDTR1_TWHR(nfc_tmg.tWHR) |
+		NDTR1_TRHW(nfc_tmg.tRHW) |
+		NDTR1_WAIT_MODE |
+		NDTR1_TR(nfc_tmg.tR);
+
+	writel_relaxed(marvell_nand->ndtr0, nfc->regs + NDTR0);
+	writel_relaxed(marvell_nand->ndtr1, nfc->regs + NDTR1);
+
+	return 0;
+}
+
+static int marvell_nand_chip_init(struct device *dev, struct marvell_nfc *nfc,
+				  struct device_node *np)
+{
+	struct marvell_nand_chip *marvell_nand;
+	struct mtd_info *mtd;
+	struct nand_chip *chip;
+	int nsels, ret, i;
+	u32 cs, rb;
+
+	/*
+	 * The legacy "num-cs" property indicates the number of CS on the only
+	 * chip connected to the controller (legacy bindings does not support
+	 * more than one chip). CS are only incremented one by one while the RB
+	 * pin is always the #0.
+	 *
+	 * When not using legacy bindings, a couple of "reg" and "marvell,rb"
+	 * properties must be filled. For each chip, expressed as a subnode,
+	 * "reg" points to the CS lines and "marvell,rb" to the RB line.
+	 */
+	if (nfc->caps->legacy_of_bindings) {
+		if (!of_get_property(np, "num-cs", &nsels)) {
+			dev_err(dev, "missing num-cs property\n");
+			return -EINVAL;
+		}
+	} else {
+		if (!of_get_property(np, "reg", &nsels)) {
+			dev_err(dev, "missing reg property\n");
+			return -EINVAL;
+		}
+	}
+
+	nsels /= sizeof(u32);
+	if (!nsels) {
+		dev_err(dev, "invalid reg property size\n");
+		return -EINVAL;
+	}
+
+	/* Alloc the nand chip structure */
+	marvell_nand = devm_kzalloc(dev, sizeof(*marvell_nand) +
+				    (nsels *
+				     sizeof(struct marvell_nand_chip_sel)),
+				    GFP_KERNEL);
+	if (!marvell_nand) {
+		dev_err(dev, "could not allocate chip structure\n");
+		return -ENOMEM;
+	}
+
+	marvell_nand->nsels = nsels;
+	marvell_nand->selected = -1;
+
+	for (i = 0; i < nsels; i++) {
+		if (nfc->caps->legacy_of_bindings) {
+			/*
+			 * Legacy bindings use the CS lines in natural
+			 * order (0, 1, ...)
+			 */
+			cs = i;
+		} else {
+			/* Retrieve CS id */
+			ret = of_property_read_u32_index(np, "reg", i, &cs);
+			if (ret) {
+				dev_err(dev, "could not retrieve reg property: %d\n",
+					ret);
+				return ret;
+			}
+		}
+
+		if (cs >= nfc->caps->max_cs_nb) {
+			dev_err(dev, "invalid reg value: %u (max CS = %d)\n",
+				cs, nfc->caps->max_cs_nb);
+			return -EINVAL;
+		}
+
+		if (test_and_set_bit(cs, &nfc->assigned_cs)) {
+			dev_err(dev, "CS %d already assigned\n", cs);
+			return -EINVAL;
+		}
+
+		/*
+		 * The cs variable represents the chip select id, which must be
+		 * converted in bit fields for NDCB0 and NDCB2 to select the
+		 * right chip. Unfortunately, due to a lack of information on
+		 * the subject and incoherent documentation, the user should not
+		 * use CS1 and CS3 at all as asserting them is not supported in
+		 * a reliable way (due to multiplexing inside ADDR5 field).
+		 */
+		switch (cs) {
+		case 0:
+		case 2:
+			marvell_nand->sels[i].ndcb0_csel = 0;
+			break;
+		case 1:
+		case 3:
+			marvell_nand->sels[i].ndcb0_csel = NDCB0_CSEL;
+			break;
+		default:
+			return -EINVAL;
+		}
+
+		/* Retrieve RB id */
+		if (nfc->caps->legacy_of_bindings) {
+			/* Legacy bindings always use RB #0 */
+			rb = 0;
+		} else {
+			ret = of_property_read_u32_index(np, "marvell,rb", i,
+							 &rb);
+			if (ret) {
+				dev_err(dev,
+					"could not retrieve RB property: %d\n",
+					ret);
+				return ret;
+			}
+		}
+
+		if (rb >= nfc->caps->max_rb_nb) {
+			dev_err(dev, "invalid reg value: %u (max RB = %d)\n",
+				rb, nfc->caps->max_rb_nb);
+			return -EINVAL;
+		}
+
+		marvell_nand->sels[i].rb = rb;
+	}
+
+	chip = &marvell_nand->chip;
+	chip->controller = &nfc->controller;
+	nand_set_flash_node(chip, np);
+
+	chip->exec_op = marvell_nfc_exec_op;
+	chip->select_chip = marvell_nfc_select_chip;
+	if (!of_get_property(np, "marvell,nand-keep-config", NULL))
+		chip->setup_data_interface = marvell_nfc_setup_data_interface;
+
+	mtd = nand_to_mtd(chip);
+	mtd->dev.parent = dev;
+
+	/*
+	 * Default to HW ECC engine mode. If the nand-ecc-mode property is given
+	 * in the DT node, this entry will be overwritten in nand_scan_ident().
+	 */
+	chip->ecc.mode = NAND_ECC_HW;
+
+	ret = nand_scan_ident(mtd, marvell_nand->nsels, NULL);
+	if (ret) {
+		dev_err(dev, "could not identify the nand chip\n");
+		return ret;
+	}
+
+	if (chip->bbt_options & NAND_BBT_USE_FLASH) {
+		/*
+		 * We'll use a bad block table stored in-flash and don't
+		 * allow writing the bad block marker to the flash.
+		 */
+		chip->bbt_options |= NAND_BBT_NO_OOB_BBM;
+		chip->bbt_td = &bbt_main_descr;
+		chip->bbt_md = &bbt_mirror_descr;
+	}
+
+	/*
+	 * With RA_START bit set in NDCR, columns takes two address cycles. This
+	 * means addressing a chip with more than 256 pages needs a fifth
+	 * address cycle. Addressing a chip using CS 2 or 3 should also needs
+	 * this additional cycle but due to insistance in the documentation and
+	 * lack of hardware to test this situation, this case has been dropped
+	 * and is not supported by this driver.
+	 */
+	marvell_nand->addr_cyc = 4;
+	if (chip->options & NAND_ROW_ADDR_3)
+		marvell_nand->addr_cyc = 5;
+
+	ret = marvell_nand_ecc_init(mtd, &chip->ecc, np);
+	if (ret) {
+		dev_err(dev, "ECC init failed: %d\n", ret);
+		return ret;
+	}
+
+	if (chip->ecc.mode == NAND_ECC_HW) {
+		/*
+		 * Subpage write not available with hardware ECC, prohibit also
+		 * subpage read as in userspace subpage acces would still be
+		 * allowed and subpage write, if used, would lead to numerous
+		 * uncorrectable ECC errors.
+		 */
+		chip->options |= NAND_NO_SUBPAGE_WRITE;
+	}
+
+	ret = nand_scan_tail(mtd);
+	if (ret) {
+		dev_err(dev, "nand_scan_tail failed: %d\n", ret);
+		return ret;
+	}
+
+	ret = mtd_device_register(mtd, NULL, 0);
+	if (ret) {
+		dev_err(dev, "failed to register mtd device: %d\n", ret);
+		nand_release(mtd);
+		return ret;
+	}
+
+	list_add_tail(&marvell_nand->node, &nfc->chips);
+
+	return 0;
+}
+
+static int marvell_nand_chips_init(struct device *dev, struct marvell_nfc *nfc)
+{
+	struct device_node *np = dev->of_node;
+	struct device_node *nand_np;
+	int nchips = of_get_child_count(np);
+	int max_cs = nfc->caps->max_cs_nb;
+	int ret;
+
+	if (nchips > max_cs) {
+		dev_err(dev, "too many NAND chips: %d (max = %d CS)\n", nchips,
+			max_cs);
+		return -EINVAL;
+	}
+
+	/*
+	 * Legacy bindings do not use child nodes to exhibit NAND chip
+	 * properties and layout. Instead, NAND properties are mixed with the
+	 * controller's and a single subnode presents the memory layout.
+	 */
+	if (nfc->caps->legacy_of_bindings) {
+		ret = marvell_nand_chip_init(dev, nfc, np);
+		return ret;
+	}
+
+	for_each_child_of_node(np, nand_np) {
+		ret = marvell_nand_chip_init(dev, nfc, nand_np);
+		if (ret) {
+			of_node_put(nand_np);
+			return ret;
+		}
+	}
+
+	return 0;
+}
+
+static void marvell_nand_chips_cleanup(struct marvell_nfc *nfc)
+{
+	struct marvell_nand_chip *entry, *temp;
+
+	list_for_each_entry_safe(entry, temp, &nfc->chips, node) {
+		nand_release(nand_to_mtd(&entry->chip));
+		list_del(&entry->node);
+	}
+}
+
+static int marvell_nfc_init(struct marvell_nfc *nfc)
+{
+	struct device_node *np = nfc->dev->of_node;
+	u32 enable_arbiter = 0;
+
+	/*
+	 * Some SoCs like A7k/A8k need to enable manually the NAND
+	 * controller to avoid being bootloader dependent. This is done
+	 * through the use of a single bit in the System Functions registers.
+	 */
+	if (nfc->caps->variant == MARVELL_NFC_VARIANT_ARMADA_8K) {
+		struct regmap *sysctrl_base = syscon_regmap_lookup_by_phandle(
+			np, "marvell,system-controller");
+		u32 reg;
+
+		if (IS_ERR(sysctrl_base))
+			return PTR_ERR(sysctrl_base);
+
+		regmap_read(sysctrl_base, GENCONF_SOC_DEVICE_MUX, &reg);
+		reg |= GENCONF_SOC_DEVICE_MUX_NFC_EN;
+		regmap_write(sysctrl_base, GENCONF_SOC_DEVICE_MUX, reg);
+	}
+
+	/* For PXA only, but other SoCs have this bit marked reserved */
+	if (of_get_property(np, "marvell,nand-enable-arbiter", NULL))
+		enable_arbiter = NDCR_ND_ARB_EN;
+
+	/*
+	 * ECC operations and interruptions are only enabled when specifically
+	 * needed. ECC shall not be activated in the early stages (fails probe)
+	 */
+	writel_relaxed(NDCR_RA_START | NDCR_ALL_INT | enable_arbiter,
+		       nfc->regs + NDCR);
+	writel_relaxed(0xFFFFFFFF, nfc->regs + NDSR);
+	writel_relaxed(0, nfc->regs + NDECCCTRL);
+
+	return 0;
+}
+
+static int marvell_nfc_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct resource *r;
+	struct marvell_nfc *nfc;
+	int ret;
+	int irq;
+
+	nfc = devm_kzalloc(&pdev->dev, sizeof(struct marvell_nfc),
+			   GFP_KERNEL);
+	if (!nfc)
+		return -ENOMEM;
+
+	nfc->dev = dev;
+	nand_hw_control_init(&nfc->controller);
+	INIT_LIST_HEAD(&nfc->chips);
+
+	r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	nfc->regs = devm_ioremap_resource(dev, r);
+	if (IS_ERR(nfc->regs))
+		return PTR_ERR(nfc->regs);
+
+	irq = platform_get_irq(pdev, 0);
+	if (irq < 0) {
+		dev_err(dev, "failed to retrieve irq\n");
+		return irq;
+	}
+
+	nfc->ecc_clk = devm_clk_get(&pdev->dev, NULL);
+	if (IS_ERR(nfc->ecc_clk))
+		return PTR_ERR(nfc->ecc_clk);
+
+	ret = clk_prepare_enable(nfc->ecc_clk);
+	if (ret)
+		return ret;
+
+	marvell_nfc_disable_int(nfc, NDCR_ALL_INT);
+	marvell_nfc_clear_int(nfc, NDCR_ALL_INT);
+	ret = devm_request_irq(dev, irq, marvell_nfc_isr,
+			       0, "marvell-nfc", nfc);
+	if (ret)
+		goto out_clk_unprepare;
+
+	/* Get NAND controller capabilities */
+	if (pdev->id_entry)
+		nfc->caps = (void *)pdev->id_entry->driver_data;
+	else
+		nfc->caps = of_device_get_match_data(&pdev->dev);
+
+	if (!nfc->caps) {
+		dev_err(dev, "Could not retrieve NFC caps\n");
+		ret = -EINVAL;
+		goto out_clk_unprepare;
+	}
+
+	/* Init the controller and then probe the chips */
+	ret = marvell_nfc_init(nfc);
+	if (ret)
+		goto out_clk_unprepare;
+
+	platform_set_drvdata(pdev, nfc);
+
+	ret = marvell_nand_chips_init(dev, nfc);
+	if (ret)
+		goto out_clk_unprepare;
+
+	return 0;
+
+out_clk_unprepare:
+	clk_disable_unprepare(nfc->ecc_clk);
+
+	return ret;
+}
+
+static int marvell_nfc_remove(struct platform_device *pdev)
+{
+	struct marvell_nfc *nfc = platform_get_drvdata(pdev);
+
+	marvell_nand_chips_cleanup(nfc);
+
+	clk_disable_unprepare(nfc->ecc_clk);
+
+	return 0;
+}
+
+static const struct marvell_nfc_caps marvell_armada_8k_nfc_caps = {
+	.variant = MARVELL_NFC_VARIANT_ARMADA_8K,
+	.max_cs_nb = 4,
+	.max_rb_nb = 2,
+};
+
+static const struct marvell_nfc_caps marvell_armada370_nfc_caps = {
+	.variant = MARVELL_NFC_VARIANT_ARMADA370,
+	.max_cs_nb = 4,
+	.max_rb_nb = 2,
+};
+
+static const struct marvell_nfc_caps marvell_pxa3xx_nfc_caps = {
+	.variant = MARVELL_NFC_VARIANT_PXA3XX,
+	.max_cs_nb = 2,
+	.max_rb_nb = 1,
+};
+
+static const struct marvell_nfc_caps marvell_armada_8k_nfc_legacy_caps = {
+	.variant = MARVELL_NFC_VARIANT_ARMADA_8K,
+	.max_cs_nb = 4,
+	.max_rb_nb = 2,
+	.legacy_of_bindings = true,
+};
+
+static const struct marvell_nfc_caps marvell_armada370_nfc_legacy_caps = {
+	.variant = MARVELL_NFC_VARIANT_ARMADA370,
+	.max_cs_nb = 4,
+	.max_rb_nb = 2,
+	.legacy_of_bindings = true,
+};
+
+static const struct marvell_nfc_caps marvell_pxa3xx_nfc_legacy_caps = {
+	.variant = MARVELL_NFC_VARIANT_PXA3XX,
+	.max_cs_nb = 2,
+	.max_rb_nb = 1,
+	.legacy_of_bindings = true,
+};
+
+static const struct of_device_id marvell_nfc_of_ids[] = {
+	{
+		.compatible = "marvell,armada-8k-nand-controller",
+		.data = &marvell_armada_8k_nfc_caps,
+	},
+	{
+		.compatible = "marvell,armada370-nand-controller",
+		.data = &marvell_armada370_nfc_caps,
+	},
+	{
+		.compatible = "marvell,pxa3xx-nand-controller",
+		.data = &marvell_pxa3xx_nfc_caps,
+	},
+	/* Support for old/deprecated bindings: */
+	{
+		.compatible = "marvell,armada-8k-nand",
+		.data = &marvell_armada_8k_nfc_legacy_caps,
+	},
+	{
+		.compatible = "marvell,armada370-nand",
+		.data = &marvell_armada370_nfc_legacy_caps,
+	},
+	{
+		.compatible = "marvell,pxa3xx-nand",
+		.data = &marvell_pxa3xx_nfc_legacy_caps,
+	},
+	{ /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, marvell_nand_match);
+
+static struct platform_driver marvell_nfc_driver = {
+	.driver	= {
+		.name		= "marvell-nfc",
+		.of_match_table = marvell_nfc_of_ids,
+	},
+	.probe	= marvell_nfc_probe,
+	.remove	= marvell_nfc_remove,
+};
+module_platform_driver(marvell_nfc_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Marvell NAND controller driver");
-- 
2.11.0

^ permalink raw reply related	[flat|nested] 75+ messages in thread

* [RFC 07/12] ARM: dts: armada-370-xp: use reworked NAND controller driver
  2017-10-18 14:36 ` Miquel Raynal
  (?)
@ 2017-10-18 14:36   ` Miquel Raynal
  -1 siblings, 0 replies; 75+ messages in thread
From: Miquel Raynal @ 2017-10-18 14:36 UTC (permalink / raw)
  To: Andrew Lunn, bcm-kernel-feedback-list, Boris Brezillon,
	Brian Norris, Catalin Marinas, Chen-Yu Tsai, Cyrille Pitchen,
	Daniel Mack, David Woodhouse, devel, devicetree, Ezequiel Garcia,
	Greg Kroah-Hartman, Gregory Clement, Han Xu, Haojian Zhuang,
	Jason Cooper, Josh Wu, Kamal Dasu, linux-arm-kernel,
	linux-kernel, linux-mediatek, linux-mtd, Marc Gonzalez,
	Marek Vasut, Mark Rutland, Masahiro Yamada, Matthias Brugger,
	Maxime Ripard, Maxim Levitsky, Richard Weinberger,
	Robert Jarzmik, Rob Herring, Russell King, Sebastian Hesselbarth,
	Stefan Agner, Sylvain Lemieux, Vladimir Zapolskiy, Wenyou Yang,
	Will Deacon
  Cc: Thomas Petazzoni, Antoine Tenart, Miquel Raynal, Igor Grinberg,
	Nadav Haklai, Ofer Heifetz, Neta Zur Hershkovits, Hanna Hawa

Change the bindings to use the reworked Marvell NAND controller driver.
Also adapt the nand controller node organization to distinguish which
property is relevant for the controller, and which one is NAND chip
specific. Expose the partitions as a subnode of the NAND chip.

Remove the 'marvell,nand-enable-arbiter' property, inefective with
Armada boards.

Signed-off-by: Miquel Raynal <miquel.raynal@free-electrons.com>
---
 arch/arm/boot/dts/armada-370-db.dts                |  56 ++++----
 arch/arm/boot/dts/armada-370-dlink-dns327l.dts     | 119 ++++++++--------
 arch/arm/boot/dts/armada-370-mirabox.dts           |  50 ++++---
 arch/arm/boot/dts/armada-370-netgear-rn102.dts     |  89 ++++++------
 arch/arm/boot/dts/armada-370-netgear-rn104.dts     |  89 ++++++------
 arch/arm/boot/dts/armada-370-rd.dts                |  51 ++++---
 arch/arm/boot/dts/armada-370-seagate-nas-xbay.dtsi |  63 +++++----
 arch/arm/boot/dts/armada-370-xp.dtsi               |   6 +-
 arch/arm/boot/dts/armada-xp-db-dxbc2.dts           |   1 -
 arch/arm/boot/dts/armada-xp-db-xc3-24g4xg.dts      |   1 -
 arch/arm/boot/dts/armada-xp-db.dts                 |   1 -
 arch/arm/boot/dts/armada-xp-gp.dts                 |   1 -
 arch/arm/boot/dts/armada-xp-lenovo-ix4-300d.dts    |   1 -
 arch/arm/boot/dts/armada-xp-linksys-mamba.dts      | 155 +++++++++++----------
 arch/arm/boot/dts/armada-xp-netgear-rn2120.dts     |  89 ++++++------
 15 files changed, 420 insertions(+), 352 deletions(-)

diff --git a/arch/arm/boot/dts/armada-370-db.dts b/arch/arm/boot/dts/armada-370-db.dts
index c4eef7323367..76c433c29731 100644
--- a/arch/arm/boot/dts/armada-370-db.dts
+++ b/arch/arm/boot/dts/armada-370-db.dts
@@ -142,33 +142,6 @@
 			usb@51000 {
 				status = "okay";
 			};
-
-			nand@d0000 {
-				status = "okay";
-				num-cs = <1>;
-				marvell,nand-keep-config;
-				marvell,nand-enable-arbiter;
-				nand-on-flash-bbt;
-
-				partitions {
-					compatible = "fixed-partitions";
-					#address-cells = <1>;
-					#size-cells = <1>;
-
-					partition@0 {
-						label = "U-Boot";
-						reg = <0 0x800000>;
-					};
-					partition@800000 {
-						label = "Linux";
-						reg = <0x800000 0x800000>;
-					};
-					partition@1000000 {
-						label = "Filesystem";
-						reg = <0x1000000 0x3f000000>;
-					};
-				};
-			};
 		};
 	};
 
@@ -276,3 +249,32 @@
 	};
 };
 
+&nand_controller {
+	status = "okay";
+
+	nand@0 {
+		reg = <0>;
+		marvell,rb = <0>;
+		marvell,nand-keep-config;
+		nand-on-flash-bbt;
+
+		partitions {
+			compatible = "fixed-partitions";
+			#address-cells = <1>;
+			#size-cells = <1>;
+
+			partition@0 {
+				label = "U-Boot";
+				reg = <0 0x800000>;
+			};
+			partition@800000 {
+				label = "Linux";
+				reg = <0x800000 0x800000>;
+			};
+			partition@1000000 {
+				label = "Filesystem";
+				reg = <0x1000000 0x3f000000>;
+			};
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/armada-370-dlink-dns327l.dts b/arch/arm/boot/dts/armada-370-dlink-dns327l.dts
index db7f3aa38670..0cc3285a40b3 100644
--- a/arch/arm/boot/dts/armada-370-dlink-dns327l.dts
+++ b/arch/arm/boot/dts/armada-370-dlink-dns327l.dts
@@ -81,61 +81,6 @@
 			usb@50000 {
 				status = "okay";
 			};
-
-			nand@d0000 {
-				status = "okay";
-				num-cs = <1>;
-				marvell,nand-keep-config;
-				marvell,nand-enable-arbiter;
-				nand-on-flash-bbt;
-				nand-ecc-strength = <4>;
-				nand-ecc-step-size = <512>;
-
-				partition@0 {
-					label = "u-boot";
-					/* 1.0 MiB */
-					reg = <0x0000000 0x100000>;
-					read-only;
-				};
-
-				partition@100000 {
-					label = "u-boot-env";
-					/* 128 KiB */
-					reg = <0x100000 0x20000>;
-					read-only;
-				};
-
-				partition@120000 {
-					label = "uImage";
-					/* 7 MiB */
-					reg = <0x120000 0x700000>;
-				};
-
-				partition@820000 {
-					label = "ubifs";
-					/* ~ 84 MiB */
-					reg = <0x820000 0x54e0000>;
-				};
-
-				/* Hardcoded into stock bootloader */
-				partition@5d00000 {
-					label = "failsafe-uImage";
-					/* 5 MiB */
-					reg = <0x5d00000 0x500000>;
-				};
-
-				partition@6200000 {
-					label = "failsafe-fs";
-					/* 29 MiB */
-					reg = <0x6200000 0x1d00000>;
-				};
-
-				partition@7f00000 {
-					label = "bbt";
-					/* 1 MiB for BBT */
-					reg = <0x7f00000 0x100000>;
-				};
-			};
 		};
 	};
 
@@ -356,3 +301,67 @@
 	clock-frequency = <100000>;
 	status = "okay";
 };
+
+&nand_controller {
+	status = "okay";
+
+	nand@0 {
+		reg = <0>;
+		marvell,rb = <0>;
+		marvell,nand-keep-config;
+		nand-on-flash-bbt;
+		nand-ecc-strength = <4>;
+		nand-ecc-step-size = <512>;
+
+		partitions {
+			compatible = "fixed-partitions";
+			#address-cells = <1>;
+			#size-cells = <1>;
+
+			partition@0 {
+				label = "u-boot";
+				/* 1.0 MiB */
+				reg = <0x0000000 0x100000>;
+				read-only;
+			};
+
+			partition@100000 {
+				label = "u-boot-env";
+				/* 128 KiB */
+				reg = <0x100000 0x20000>;
+				read-only;
+			};
+
+			partition@120000 {
+				label = "uImage";
+				/* 7 MiB */
+				reg = <0x120000 0x700000>;
+			};
+
+			partition@820000 {
+				label = "ubifs";
+				/* ~ 84 MiB */
+				reg = <0x820000 0x54e0000>;
+			};
+
+			/* Hardcoded into stock bootloader */
+			partition@5d00000 {
+				label = "failsafe-uImage";
+				/* 5 MiB */
+				reg = <0x5d00000 0x500000>;
+			};
+
+			partition@6200000 {
+				label = "failsafe-fs";
+				/* 29 MiB */
+				reg = <0x6200000 0x1d00000>;
+			};
+
+			partition@7f00000 {
+				label = "bbt";
+				/* 1 MiB for BBT */
+				reg = <0x7f00000 0x100000>;
+			};
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/armada-370-mirabox.dts b/arch/arm/boot/dts/armada-370-mirabox.dts
index 702f58c9642d..9e13a83cf057 100644
--- a/arch/arm/boot/dts/armada-370-mirabox.dts
+++ b/arch/arm/boot/dts/armada-370-mirabox.dts
@@ -145,27 +145,6 @@
 					reg = <0x25>;
 				};
 			};
-
-			nand@d0000 {
-				status = "okay";
-				num-cs = <1>;
-				marvell,nand-keep-config;
-				marvell,nand-enable-arbiter;
-				nand-on-flash-bbt;
-
-				partition@0 {
-					label = "U-Boot";
-					reg = <0 0x400000>;
-				};
-				partition@400000 {
-					label = "Linux";
-					reg = <0x400000 0x400000>;
-				};
-				partition@800000 {
-					label = "Filesystem";
-					reg = <0x800000 0x3f800000>;
-				};
-			};
 		};
 	};
 };
@@ -210,3 +189,32 @@
 	};
 };
 
+&nand_controller {
+	status = "okay";
+
+	nand@0 {
+		reg = <0>;
+		marvell,rb = <0>;
+		marvell,nand-keep-config;
+		nand-on-flash-bbt;
+
+		partitions {
+			compatible = "fixed-partitions";
+			#address-cells = <1>;
+			#size-cells = <1>;
+
+			partition@0 {
+				label = "U-Boot";
+				reg = <0 0x400000>;
+			};
+			partition@400000 {
+				label = "Linux";
+				reg = <0x400000 0x400000>;
+			};
+			partition@800000 {
+				label = "Filesystem";
+				reg = <0x800000 0x3f800000>;
+			};
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/armada-370-netgear-rn102.dts b/arch/arm/boot/dts/armada-370-netgear-rn102.dts
index b1a96e95e921..e4fb1d7cf009 100644
--- a/arch/arm/boot/dts/armada-370-netgear-rn102.dts
+++ b/arch/arm/boot/dts/armada-370-netgear-rn102.dts
@@ -118,46 +118,6 @@
 					pwm_polarity = <0>;
 				};
 			};
-
-			nand@d0000 {
-				status = "okay";
-				num-cs = <1>;
-				marvell,nand-keep-config;
-				marvell,nand-enable-arbiter;
-				nand-on-flash-bbt;
-
-				/* Use Hardware BCH ECC */
-				nand-ecc-strength = <4>;
-				nand-ecc-step-size = <512>;
-
-				partition@0 {
-					label = "u-boot";
-					reg = <0x0000000 0x180000>;  /* 1.5MB */
-					read-only;
-				};
-
-				partition@180000 {
-					label = "u-boot-env";
-					reg = <0x180000 0x20000>;    /* 128KB */
-					read-only;
-				};
-
-				partition@200000 {
-					label = "uImage";
-					reg = <0x0200000 0x600000>;    /* 6MB */
-				};
-
-				partition@800000 {
-					label = "minirootfs";
-					reg = <0x0800000 0x400000>;    /* 4MB */
-				};
-
-				/* Last MB is for the BBT, i.e. not writable */
-				partition@c00000 {
-					label = "ubifs";
-					reg = <0x0c00000 0x7400000>; /* 116MB */
-				};
-			};
 		};
 	};
 
@@ -301,3 +261,52 @@
 		marvell,function = "gpio";
 	};
 };
+
+&nand_controller {
+	status = "okay";
+
+	nand@0 {
+		reg = <0>;
+		marvell,rb = <0>;
+		marvell,nand-keep-config;
+		nand-on-flash-bbt;
+
+		/* Use Hardware BCH ECC */
+		nand-ecc-strength = <4>;
+		nand-ecc-step-size = <512>;
+
+		partitions {
+			compatible = "fixed-partitions";
+			#address-cells = <1>;
+			#size-cells = <1>;
+
+			partition@0 {
+				label = "u-boot";
+				reg = <0x0000000 0x180000>;  /* 1.5MB */
+				read-only;
+			};
+
+			partition@180000 {
+				label = "u-boot-env";
+				reg = <0x180000 0x20000>;    /* 128KB */
+				read-only;
+			};
+
+			partition@200000 {
+				label = "uImage";
+				reg = <0x0200000 0x600000>;    /* 6MB */
+			};
+
+			partition@800000 {
+				label = "minirootfs";
+				reg = <0x0800000 0x400000>;    /* 4MB */
+			};
+
+			/* Last MB is for the BBT, i.e. not writable */
+			partition@c00000 {
+				label = "ubifs";
+				reg = <0x0c00000 0x7400000>; /* 116MB */
+			};
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/armada-370-netgear-rn104.dts b/arch/arm/boot/dts/armada-370-netgear-rn104.dts
index d67e7aa42b54..f639838ce27d 100644
--- a/arch/arm/boot/dts/armada-370-netgear-rn104.dts
+++ b/arch/arm/boot/dts/armada-370-netgear-rn104.dts
@@ -127,46 +127,6 @@
 					reg = <0x23>;
 				};
 			};
-
-			nand@d0000 {
-				status = "okay";
-				num-cs = <1>;
-				marvell,nand-keep-config;
-				marvell,nand-enable-arbiter;
-				nand-on-flash-bbt;
-
-				/* Use Hardware BCH ECC */
-				nand-ecc-strength = <4>;
-				nand-ecc-step-size = <512>;
-
-				partition@0 {
-					label = "u-boot";
-					reg = <0x0000000 0x180000>;  /* 1.5MB */
-					read-only;
-				};
-
-				partition@180000 {
-					label = "u-boot-env";
-					reg = <0x180000 0x20000>;    /* 128KB */
-					read-only;
-				};
-
-				partition@200000 {
-					label = "uImage";
-					reg = <0x0200000 0x600000>;    /* 6MB */
-				};
-
-				partition@800000 {
-					label = "minirootfs";
-					reg = <0x0800000 0x400000>;    /* 4MB */
-				};
-
-				/* Last MB is for the BBT, i.e. not writable */
-				partition@c00000 {
-					label = "ubifs";
-					reg = <0x0c00000 0x7400000>; /* 116MB */
-				};
-			};
 		};
 	};
 
@@ -313,3 +273,52 @@
 		marvell,function = "gpio";
 	};
 };
+
+&nand_controller {
+	status = "okay";
+
+	nand@0 {
+		reg = <0>;
+		marvell,rb = <0>;
+		marvell,nand-keep-config;
+		nand-on-flash-bbt;
+
+		/* Use Hardware BCH ECC */
+		nand-ecc-strength = <4>;
+		nand-ecc-step-size = <512>;
+
+		partitions {
+			compatible = "fixed-partitions";
+			#address-cells = <1>;
+			#size-cells = <1>;
+
+			partition@0 {
+				label = "u-boot";
+				reg = <0x0000000 0x180000>;  /* 1.5MB */
+				read-only;
+			};
+
+			partition@180000 {
+				label = "u-boot-env";
+				reg = <0x180000 0x20000>;    /* 128KB */
+				read-only;
+			};
+
+			partition@200000 {
+				label = "uImage";
+				reg = <0x0200000 0x600000>;    /* 6MB */
+			};
+
+			partition@800000 {
+				label = "minirootfs";
+				reg = <0x0800000 0x400000>;    /* 4MB */
+			};
+
+			/* Last MB is for the BBT, i.e. not writable */
+			partition@c00000 {
+				label = "ubifs";
+				reg = <0x0c00000 0x7400000>; /* 116MB */
+			};
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/armada-370-rd.dts b/arch/arm/boot/dts/armada-370-rd.dts
index 8b2fa9a49967..d949cc6da1d6 100644
--- a/arch/arm/boot/dts/armada-370-rd.dts
+++ b/arch/arm/boot/dts/armada-370-rd.dts
@@ -148,27 +148,6 @@
 					default-state = "keep";
 				};
 			};
-
-			nand@d0000 {
-				status = "okay";
-				num-cs = <1>;
-				marvell,nand-keep-config;
-				marvell,nand-enable-arbiter;
-				nand-on-flash-bbt;
-
-				partition@0 {
-					label = "U-Boot";
-					reg = <0 0x800000>;
-				};
-				partition@800000 {
-					label = "Linux";
-					reg = <0x800000 0x800000>;
-				};
-				partition@1000000 {
-					label = "Filesystem";
-					reg = <0x1000000 0x3f000000>;
-				};
-			};
 		};
 	};
 
@@ -293,3 +272,33 @@
 		marvell,function = "gpio";
 	};
 };
+
+&nand_controller {
+	status = "okay";
+
+	nand@0 {
+		reg = <0>;
+		marvell,rb = <0>;
+		marvell,nand-keep-config;
+		nand-on-flash-bbt;
+
+		partitions {
+			compatible = "fixed-partitions";
+			#address-cells = <1>;
+			#size-cells = <1>;
+
+			partition@0 {
+				label = "U-Boot";
+				reg = <0 0x800000>;
+			};
+			partition@800000 {
+				label = "Linux";
+				reg = <0x800000 0x800000>;
+			};
+			partition@1000000 {
+				label = "Filesystem";
+				reg = <0x1000000 0x3f000000>;
+			};
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/armada-370-seagate-nas-xbay.dtsi b/arch/arm/boot/dts/armada-370-seagate-nas-xbay.dtsi
index e9a5b952afc0..8f4b556e8fbf 100644
--- a/arch/arm/boot/dts/armada-370-seagate-nas-xbay.dtsi
+++ b/arch/arm/boot/dts/armada-370-seagate-nas-xbay.dtsi
@@ -69,33 +69,6 @@
 					interrupts = <110>;
 				};
 			};
-
-			nand@d0000 {
-				status = "okay";
-				num-cs = <1>;
-				marvell,nand-keep-config;
-				marvell,nand-enable-arbiter;
-				nand-on-flash-bbt;
-				nand-ecc-strength = <4>;
-				nand-ecc-step-size = <512>;
-
-				partition@0 {
-					label = "u-boot";
-					reg = <0x0 0x300000>;
-				};
-				partition@300000 {
-					label = "device-tree";
-					reg = <0x300000 0x20000>;
-				};
-				partition@320000 {
-					label = "linux";
-					reg = <0x320000 0x2000000>;
-				};
-				partition@2320000 {
-					label = "rootfs";
-					reg = <0x2320000 0xdce0000>;
-				};
-			};
 		};
 
 	};
@@ -230,3 +203,39 @@
 		marvell,function = "gpio";
 	};
 };
+
+&nand_controller {
+	status = "okay";
+
+	nand@0 {
+		reg = <0>;
+		marvell,rb = <0>;
+		marvell,nand-keep-config;
+		nand-on-flash-bbt;
+		nand-ecc-strength = <4>;
+		nand-ecc-step-size = <512>;
+
+		partitions {
+			compatible = "fixed-partitions";
+			#address-cells = <1>;
+			#size-cells = <1>;
+
+			partition@0 {
+				label = "u-boot";
+				reg = <0x0 0x300000>;
+			};
+			partition@300000 {
+				label = "device-tree";
+				reg = <0x300000 0x20000>;
+			};
+			partition@320000 {
+				label = "linux";
+				reg = <0x320000 0x2000000>;
+			};
+			partition@2320000 {
+				label = "rootfs";
+				reg = <0x2320000 0xdce0000>;
+			};
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/armada-370-xp.dtsi b/arch/arm/boot/dts/armada-370-xp.dtsi
index 09495e87b038..b6bead81c438 100644
--- a/arch/arm/boot/dts/armada-370-xp.dtsi
+++ b/arch/arm/boot/dts/armada-370-xp.dtsi
@@ -281,11 +281,11 @@
 				status = "disabled";
 			};
 
-			nand: nand@d0000 {
-				compatible = "marvell,armada370-nand";
+			nand_controller: nand-controller@d0000 {
+				compatible = "marvell,armada370-nand-controller";
 				reg = <0xd0000 0x54>;
 				#address-cells = <1>;
-				#size-cells = <1>;
+				#size-cells = <0>;
 				interrupts = <113>;
 				clocks = <&coredivclk 0>;
 				status = "disabled";
diff --git a/arch/arm/boot/dts/armada-xp-db-dxbc2.dts b/arch/arm/boot/dts/armada-xp-db-dxbc2.dts
index 1b1ff17fdd9c..ba3ecf3d2481 100644
--- a/arch/arm/boot/dts/armada-xp-db-dxbc2.dts
+++ b/arch/arm/boot/dts/armada-xp-db-dxbc2.dts
@@ -109,7 +109,6 @@
 	status = "okay";
 	num-cs = <1>;
 	marvell,nand-keep-config;
-	marvell,nand-enable-arbiter;
 	nand-on-flash-bbt;
 	nand-ecc-strength = <4>;
 	nand-ecc-step-size = <512>;
diff --git a/arch/arm/boot/dts/armada-xp-db-xc3-24g4xg.dts b/arch/arm/boot/dts/armada-xp-db-xc3-24g4xg.dts
index 06fce35d7491..42f939dd9fdc 100644
--- a/arch/arm/boot/dts/armada-xp-db-xc3-24g4xg.dts
+++ b/arch/arm/boot/dts/armada-xp-db-xc3-24g4xg.dts
@@ -108,7 +108,6 @@
 	status = "okay";
 	num-cs = <1>;
 	marvell,nand-keep-config;
-	marvell,nand-enable-arbiter;
 	nand-on-flash-bbt;
 	nand-ecc-strength = <4>;
 	nand-ecc-step-size = <512>;
diff --git a/arch/arm/boot/dts/armada-xp-db.dts b/arch/arm/boot/dts/armada-xp-db.dts
index 065282c21789..311d6af25118 100644
--- a/arch/arm/boot/dts/armada-xp-db.dts
+++ b/arch/arm/boot/dts/armada-xp-db.dts
@@ -184,7 +184,6 @@
 				status = "okay";
 				num-cs = <1>;
 				marvell,nand-keep-config;
-				marvell,nand-enable-arbiter;
 				nand-on-flash-bbt;
 
 				partitions {
diff --git a/arch/arm/boot/dts/armada-xp-gp.dts b/arch/arm/boot/dts/armada-xp-gp.dts
index ac9eab8ac186..a3053d176262 100644
--- a/arch/arm/boot/dts/armada-xp-gp.dts
+++ b/arch/arm/boot/dts/armada-xp-gp.dts
@@ -201,7 +201,6 @@
 				status = "okay";
 				num-cs = <1>;
 				marvell,nand-keep-config;
-				marvell,nand-enable-arbiter;
 				nand-on-flash-bbt;
 			};
 		};
diff --git a/arch/arm/boot/dts/armada-xp-lenovo-ix4-300d.dts b/arch/arm/boot/dts/armada-xp-lenovo-ix4-300d.dts
index ce0afba1ce58..a0ace4c92a8d 100644
--- a/arch/arm/boot/dts/armada-xp-lenovo-ix4-300d.dts
+++ b/arch/arm/boot/dts/armada-xp-lenovo-ix4-300d.dts
@@ -122,7 +122,6 @@
 				status = "okay";
 				num-cs = <1>;
 				marvell,nand-keep-config;
-				marvell,nand-enable-arbiter;
 				nand-on-flash-bbt;
 
 				partitions {
diff --git a/arch/arm/boot/dts/armada-xp-linksys-mamba.dts b/arch/arm/boot/dts/armada-xp-linksys-mamba.dts
index 6d705f518254..3ec10054d5a3 100644
--- a/arch/arm/boot/dts/armada-xp-linksys-mamba.dts
+++ b/arch/arm/boot/dts/armada-xp-linksys-mamba.dts
@@ -196,79 +196,6 @@
 			bm@c8000 {
 				status = "okay";
 			};
-
-			nand@d0000 {
-				status = "okay";
-				num-cs = <1>;
-				marvell,nand-keep-config;
-				marvell,nand-enable-arbiter;
-				nand-on-flash-bbt;
-				nand-ecc-strength = <4>;
-				nand-ecc-step-size = <512>;
-
-				partition@0 {
-					label = "u-boot";
-					reg = <0x0000000 0x100000>;  /* 1MB */
-					read-only;
-				};
-
-				partition@100000 {
-					label = "u_env";
-					reg = <0x100000 0x40000>;    /* 256KB */
-				};
-
-				partition@140000 {
-					label = "s_env";
-					reg = <0x140000 0x40000>;    /* 256KB */
-				};
-
-				partition@900000 {
-					label = "devinfo";
-					reg = <0x900000 0x100000>;   /* 1MB */
-					read-only;
-				};
-
-				/* kernel1 overlaps with rootfs1 by design */
-				partition@a00000 {
-					label = "kernel1";
-					reg = <0xa00000 0x2800000>;  /* 40MB */
-				};
-
-				partition@d00000 {
-					label = "rootfs1";
-					reg = <0xd00000 0x2500000>;  /* 37MB */
-				};
-
-				/* kernel2 overlaps with rootfs2 by design */
-				partition@3200000 {
-					label = "kernel2";
-					reg = <0x3200000 0x2800000>; /* 40MB */
-				};
-
-				partition@3500000 {
-					label = "rootfs2";
-					reg = <0x3500000 0x2500000>; /* 37MB */
-				};
-
-				/*
-				 * 38MB, last MB is for the BBT, not writable
-				 */
-				partition@5a00000 {
-					label = "syscfg";
-					reg = <0x5a00000 0x2600000>;
-				};
-
-				/*
-				 * Unused area between "s_env" and "devinfo".
-				 * Moved here because otherwise the renumbered
-				 * partitions would break the bootloader
-				 * supplied bootargs
-				 */
-				partition@180000 {
-					label = "unused_area";
-					reg = <0x180000 0x780000>;   /* 7.5MB */
-				};
-			};
 		};
 
 		bm-bppi {
@@ -465,3 +392,85 @@
 		};
 	};
 };
+
+&nand_controller {
+	status = "okay";
+
+	nand@0 {
+		reg = <0>;
+		marvell,rb = <0>;
+		marvell,nand-keep-config;
+		nand-on-flash-bbt;
+		nand-ecc-strength = <4>;
+		nand-ecc-step-size = <512>;
+
+		partitions {
+			compatible = "fixed-partitions";
+			#address-cells = <1>;
+			#size-cells = <1>;
+
+			partition@0 {
+				label = "u-boot";
+				reg = <0x0000000 0x100000>;  /* 1MB */
+				read-only;
+			};
+
+			partition@100000 {
+				label = "u_env";
+				reg = <0x100000 0x40000>;    /* 256KB */
+			};
+
+			partition@140000 {
+				label = "s_env";
+				reg = <0x140000 0x40000>;    /* 256KB */
+			};
+
+			partition@900000 {
+				label = "devinfo";
+				reg = <0x900000 0x100000>;   /* 1MB */
+				read-only;
+			};
+
+			/* kernel1 overlaps with rootfs1 by design */
+			partition@a00000 {
+				label = "kernel1";
+				reg = <0xa00000 0x2800000>;  /* 40MB */
+			};
+
+			partition@d00000 {
+				label = "rootfs1";
+				reg = <0xd00000 0x2500000>;  /* 37MB */
+			};
+
+			/* kernel2 overlaps with rootfs2 by design */
+			partition@3200000 {
+				label = "kernel2";
+				reg = <0x3200000 0x2800000>; /* 40MB */
+			};
+
+			partition@3500000 {
+				label = "rootfs2";
+				reg = <0x3500000 0x2500000>; /* 37MB */
+			};
+
+			/*
+			 * 38MB, last MB is for the BBT, not writable
+			 */
+			partition@5a00000 {
+				label = "syscfg";
+				reg = <0x5a00000 0x2600000>;
+			};
+
+			/*
+			 * Unused area between "s_env" and "devinfo".
+			 * Moved here because otherwise the renumbered
+			 * partitions would break the bootloader
+			 * supplied bootargs
+			 */
+			partition@180000 {
+				label = "unused_area";
+				reg = <0x180000 0x780000>;   /* 7.5MB */
+			};
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/armada-xp-netgear-rn2120.dts b/arch/arm/boot/dts/armada-xp-netgear-rn2120.dts
index 40c6fe21e720..0027f34b3666 100644
--- a/arch/arm/boot/dts/armada-xp-netgear-rn2120.dts
+++ b/arch/arm/boot/dts/armada-xp-netgear-rn2120.dts
@@ -154,46 +154,6 @@
 				nr-ports = <2>;
 				status = "okay";
 			};
-
-			nand@d0000 {
-				status = "okay";
-				num-cs = <1>;
-				marvell,nand-keep-config;
-				marvell,nand-enable-arbiter;
-				nand-on-flash-bbt;
-
-				/* Use Hardware BCH ECC */
-				nand-ecc-strength = <4>;
-				nand-ecc-step-size = <512>;
-
-				partition@0 {
-					label = "u-boot";
-					reg = <0x0000000 0x180000>;  /* 1.5MB */
-					read-only;
-				};
-
-				partition@180000 {
-					label = "u-boot-env";
-					reg = <0x180000 0x20000>;    /* 128KB */
-					read-only;
-				};
-
-				partition@200000 {
-					label = "uImage";
-					reg = <0x0200000 0x600000>;    /* 6MB */
-				};
-
-				partition@800000 {
-					label = "minirootfs";
-					reg = <0x0800000 0x400000>;    /* 4MB */
-				};
-
-				/* Last MB is for the BBT, i.e. not writable */
-				partition@c00000 {
-					label = "ubifs";
-					reg = <0x0c00000 0x7400000>; /* 116MB */
-				};
-			};
 		};
 	};
 
@@ -382,3 +342,52 @@
 		marvell,function = "gpio";
 	};
 };
+
+&nand_controller {
+	status = "okay";
+
+	nand@0 {
+		reg = <0>;
+		marvell,rb = <0>;
+		marvell,nand-keep-config;
+		nand-on-flash-bbt;
+
+		/* Use Hardware BCH ECC */
+		nand-ecc-strength = <4>;
+		nand-ecc-step-size = <512>;
+
+		partitions {
+			compatible = "fixed-partitions";
+			#address-cells = <1>;
+			#size-cells = <1>;
+
+			partition@0 {
+				label = "u-boot";
+				reg = <0x0000000 0x180000>;  /* 1.5MB */
+				read-only;
+			};
+
+			partition@180000 {
+				label = "u-boot-env";
+				reg = <0x180000 0x20000>;    /* 128KB */
+				read-only;
+			};
+
+			partition@200000 {
+				label = "uImage";
+				reg = <0x0200000 0x600000>;    /* 6MB */
+			};
+
+			partition@800000 {
+				label = "minirootfs";
+				reg = <0x0800000 0x400000>;    /* 4MB */
+			};
+
+			/* Last MB is for the BBT, i.e. not writable */
+			partition@c00000 {
+				label = "ubifs";
+				reg = <0x0c00000 0x7400000>; /* 116MB */
+			};
+		};
+	};
+};
-- 
2.11.0

^ permalink raw reply related	[flat|nested] 75+ messages in thread

* [RFC 07/12] ARM: dts: armada-370-xp: use reworked NAND controller driver
@ 2017-10-18 14:36   ` Miquel Raynal
  0 siblings, 0 replies; 75+ messages in thread
From: Miquel Raynal @ 2017-10-18 14:36 UTC (permalink / raw)
  To: Andrew Lunn, bcm-kernel-feedback-list, Boris Brezillon,
	Brian Norris, Catalin Marinas, Chen-Yu Tsai, Cyrille Pitchen,
	Daniel Mack, David Woodhouse, devel, devicetree, Ezequiel Garcia,
	Greg Kroah-Hartman, Gregory Clement, Han Xu, Haojian Zhuang,
	Jason Cooper, Josh Wu, Kamal Dasu, linux-arm-kernel,
	linux-kernel, linux-mediatek, linux-mtd
  Cc: Hanna Hawa, Antoine Tenart, Nadav Haklai, Neta Zur Hershkovits,
	Igor Grinberg, Miquel Raynal, Ofer Heifetz

Change the bindings to use the reworked Marvell NAND controller driver.
Also adapt the nand controller node organization to distinguish which
property is relevant for the controller, and which one is NAND chip
specific. Expose the partitions as a subnode of the NAND chip.

Remove the 'marvell,nand-enable-arbiter' property, inefective with
Armada boards.

Signed-off-by: Miquel Raynal <miquel.raynal@free-electrons.com>
---
 arch/arm/boot/dts/armada-370-db.dts                |  56 ++++----
 arch/arm/boot/dts/armada-370-dlink-dns327l.dts     | 119 ++++++++--------
 arch/arm/boot/dts/armada-370-mirabox.dts           |  50 ++++---
 arch/arm/boot/dts/armada-370-netgear-rn102.dts     |  89 ++++++------
 arch/arm/boot/dts/armada-370-netgear-rn104.dts     |  89 ++++++------
 arch/arm/boot/dts/armada-370-rd.dts                |  51 ++++---
 arch/arm/boot/dts/armada-370-seagate-nas-xbay.dtsi |  63 +++++----
 arch/arm/boot/dts/armada-370-xp.dtsi               |   6 +-
 arch/arm/boot/dts/armada-xp-db-dxbc2.dts           |   1 -
 arch/arm/boot/dts/armada-xp-db-xc3-24g4xg.dts      |   1 -
 arch/arm/boot/dts/armada-xp-db.dts                 |   1 -
 arch/arm/boot/dts/armada-xp-gp.dts                 |   1 -
 arch/arm/boot/dts/armada-xp-lenovo-ix4-300d.dts    |   1 -
 arch/arm/boot/dts/armada-xp-linksys-mamba.dts      | 155 +++++++++++----------
 arch/arm/boot/dts/armada-xp-netgear-rn2120.dts     |  89 ++++++------
 15 files changed, 420 insertions(+), 352 deletions(-)

diff --git a/arch/arm/boot/dts/armada-370-db.dts b/arch/arm/boot/dts/armada-370-db.dts
index c4eef7323367..76c433c29731 100644
--- a/arch/arm/boot/dts/armada-370-db.dts
+++ b/arch/arm/boot/dts/armada-370-db.dts
@@ -142,33 +142,6 @@
 			usb@51000 {
 				status = "okay";
 			};
-
-			nand@d0000 {
-				status = "okay";
-				num-cs = <1>;
-				marvell,nand-keep-config;
-				marvell,nand-enable-arbiter;
-				nand-on-flash-bbt;
-
-				partitions {
-					compatible = "fixed-partitions";
-					#address-cells = <1>;
-					#size-cells = <1>;
-
-					partition@0 {
-						label = "U-Boot";
-						reg = <0 0x800000>;
-					};
-					partition@800000 {
-						label = "Linux";
-						reg = <0x800000 0x800000>;
-					};
-					partition@1000000 {
-						label = "Filesystem";
-						reg = <0x1000000 0x3f000000>;
-					};
-				};
-			};
 		};
 	};
 
@@ -276,3 +249,32 @@
 	};
 };
 
+&nand_controller {
+	status = "okay";
+
+	nand@0 {
+		reg = <0>;
+		marvell,rb = <0>;
+		marvell,nand-keep-config;
+		nand-on-flash-bbt;
+
+		partitions {
+			compatible = "fixed-partitions";
+			#address-cells = <1>;
+			#size-cells = <1>;
+
+			partition@0 {
+				label = "U-Boot";
+				reg = <0 0x800000>;
+			};
+			partition@800000 {
+				label = "Linux";
+				reg = <0x800000 0x800000>;
+			};
+			partition@1000000 {
+				label = "Filesystem";
+				reg = <0x1000000 0x3f000000>;
+			};
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/armada-370-dlink-dns327l.dts b/arch/arm/boot/dts/armada-370-dlink-dns327l.dts
index db7f3aa38670..0cc3285a40b3 100644
--- a/arch/arm/boot/dts/armada-370-dlink-dns327l.dts
+++ b/arch/arm/boot/dts/armada-370-dlink-dns327l.dts
@@ -81,61 +81,6 @@
 			usb@50000 {
 				status = "okay";
 			};
-
-			nand@d0000 {
-				status = "okay";
-				num-cs = <1>;
-				marvell,nand-keep-config;
-				marvell,nand-enable-arbiter;
-				nand-on-flash-bbt;
-				nand-ecc-strength = <4>;
-				nand-ecc-step-size = <512>;
-
-				partition@0 {
-					label = "u-boot";
-					/* 1.0 MiB */
-					reg = <0x0000000 0x100000>;
-					read-only;
-				};
-
-				partition@100000 {
-					label = "u-boot-env";
-					/* 128 KiB */
-					reg = <0x100000 0x20000>;
-					read-only;
-				};
-
-				partition@120000 {
-					label = "uImage";
-					/* 7 MiB */
-					reg = <0x120000 0x700000>;
-				};
-
-				partition@820000 {
-					label = "ubifs";
-					/* ~ 84 MiB */
-					reg = <0x820000 0x54e0000>;
-				};
-
-				/* Hardcoded into stock bootloader */
-				partition@5d00000 {
-					label = "failsafe-uImage";
-					/* 5 MiB */
-					reg = <0x5d00000 0x500000>;
-				};
-
-				partition@6200000 {
-					label = "failsafe-fs";
-					/* 29 MiB */
-					reg = <0x6200000 0x1d00000>;
-				};
-
-				partition@7f00000 {
-					label = "bbt";
-					/* 1 MiB for BBT */
-					reg = <0x7f00000 0x100000>;
-				};
-			};
 		};
 	};
 
@@ -356,3 +301,67 @@
 	clock-frequency = <100000>;
 	status = "okay";
 };
+
+&nand_controller {
+	status = "okay";
+
+	nand@0 {
+		reg = <0>;
+		marvell,rb = <0>;
+		marvell,nand-keep-config;
+		nand-on-flash-bbt;
+		nand-ecc-strength = <4>;
+		nand-ecc-step-size = <512>;
+
+		partitions {
+			compatible = "fixed-partitions";
+			#address-cells = <1>;
+			#size-cells = <1>;
+
+			partition@0 {
+				label = "u-boot";
+				/* 1.0 MiB */
+				reg = <0x0000000 0x100000>;
+				read-only;
+			};
+
+			partition@100000 {
+				label = "u-boot-env";
+				/* 128 KiB */
+				reg = <0x100000 0x20000>;
+				read-only;
+			};
+
+			partition@120000 {
+				label = "uImage";
+				/* 7 MiB */
+				reg = <0x120000 0x700000>;
+			};
+
+			partition@820000 {
+				label = "ubifs";
+				/* ~ 84 MiB */
+				reg = <0x820000 0x54e0000>;
+			};
+
+			/* Hardcoded into stock bootloader */
+			partition@5d00000 {
+				label = "failsafe-uImage";
+				/* 5 MiB */
+				reg = <0x5d00000 0x500000>;
+			};
+
+			partition@6200000 {
+				label = "failsafe-fs";
+				/* 29 MiB */
+				reg = <0x6200000 0x1d00000>;
+			};
+
+			partition@7f00000 {
+				label = "bbt";
+				/* 1 MiB for BBT */
+				reg = <0x7f00000 0x100000>;
+			};
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/armada-370-mirabox.dts b/arch/arm/boot/dts/armada-370-mirabox.dts
index 702f58c9642d..9e13a83cf057 100644
--- a/arch/arm/boot/dts/armada-370-mirabox.dts
+++ b/arch/arm/boot/dts/armada-370-mirabox.dts
@@ -145,27 +145,6 @@
 					reg = <0x25>;
 				};
 			};
-
-			nand@d0000 {
-				status = "okay";
-				num-cs = <1>;
-				marvell,nand-keep-config;
-				marvell,nand-enable-arbiter;
-				nand-on-flash-bbt;
-
-				partition@0 {
-					label = "U-Boot";
-					reg = <0 0x400000>;
-				};
-				partition@400000 {
-					label = "Linux";
-					reg = <0x400000 0x400000>;
-				};
-				partition@800000 {
-					label = "Filesystem";
-					reg = <0x800000 0x3f800000>;
-				};
-			};
 		};
 	};
 };
@@ -210,3 +189,32 @@
 	};
 };
 
+&nand_controller {
+	status = "okay";
+
+	nand@0 {
+		reg = <0>;
+		marvell,rb = <0>;
+		marvell,nand-keep-config;
+		nand-on-flash-bbt;
+
+		partitions {
+			compatible = "fixed-partitions";
+			#address-cells = <1>;
+			#size-cells = <1>;
+
+			partition@0 {
+				label = "U-Boot";
+				reg = <0 0x400000>;
+			};
+			partition@400000 {
+				label = "Linux";
+				reg = <0x400000 0x400000>;
+			};
+			partition@800000 {
+				label = "Filesystem";
+				reg = <0x800000 0x3f800000>;
+			};
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/armada-370-netgear-rn102.dts b/arch/arm/boot/dts/armada-370-netgear-rn102.dts
index b1a96e95e921..e4fb1d7cf009 100644
--- a/arch/arm/boot/dts/armada-370-netgear-rn102.dts
+++ b/arch/arm/boot/dts/armada-370-netgear-rn102.dts
@@ -118,46 +118,6 @@
 					pwm_polarity = <0>;
 				};
 			};
-
-			nand@d0000 {
-				status = "okay";
-				num-cs = <1>;
-				marvell,nand-keep-config;
-				marvell,nand-enable-arbiter;
-				nand-on-flash-bbt;
-
-				/* Use Hardware BCH ECC */
-				nand-ecc-strength = <4>;
-				nand-ecc-step-size = <512>;
-
-				partition@0 {
-					label = "u-boot";
-					reg = <0x0000000 0x180000>;  /* 1.5MB */
-					read-only;
-				};
-
-				partition@180000 {
-					label = "u-boot-env";
-					reg = <0x180000 0x20000>;    /* 128KB */
-					read-only;
-				};
-
-				partition@200000 {
-					label = "uImage";
-					reg = <0x0200000 0x600000>;    /* 6MB */
-				};
-
-				partition@800000 {
-					label = "minirootfs";
-					reg = <0x0800000 0x400000>;    /* 4MB */
-				};
-
-				/* Last MB is for the BBT, i.e. not writable */
-				partition@c00000 {
-					label = "ubifs";
-					reg = <0x0c00000 0x7400000>; /* 116MB */
-				};
-			};
 		};
 	};
 
@@ -301,3 +261,52 @@
 		marvell,function = "gpio";
 	};
 };
+
+&nand_controller {
+	status = "okay";
+
+	nand@0 {
+		reg = <0>;
+		marvell,rb = <0>;
+		marvell,nand-keep-config;
+		nand-on-flash-bbt;
+
+		/* Use Hardware BCH ECC */
+		nand-ecc-strength = <4>;
+		nand-ecc-step-size = <512>;
+
+		partitions {
+			compatible = "fixed-partitions";
+			#address-cells = <1>;
+			#size-cells = <1>;
+
+			partition@0 {
+				label = "u-boot";
+				reg = <0x0000000 0x180000>;  /* 1.5MB */
+				read-only;
+			};
+
+			partition@180000 {
+				label = "u-boot-env";
+				reg = <0x180000 0x20000>;    /* 128KB */
+				read-only;
+			};
+
+			partition@200000 {
+				label = "uImage";
+				reg = <0x0200000 0x600000>;    /* 6MB */
+			};
+
+			partition@800000 {
+				label = "minirootfs";
+				reg = <0x0800000 0x400000>;    /* 4MB */
+			};
+
+			/* Last MB is for the BBT, i.e. not writable */
+			partition@c00000 {
+				label = "ubifs";
+				reg = <0x0c00000 0x7400000>; /* 116MB */
+			};
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/armada-370-netgear-rn104.dts b/arch/arm/boot/dts/armada-370-netgear-rn104.dts
index d67e7aa42b54..f639838ce27d 100644
--- a/arch/arm/boot/dts/armada-370-netgear-rn104.dts
+++ b/arch/arm/boot/dts/armada-370-netgear-rn104.dts
@@ -127,46 +127,6 @@
 					reg = <0x23>;
 				};
 			};
-
-			nand@d0000 {
-				status = "okay";
-				num-cs = <1>;
-				marvell,nand-keep-config;
-				marvell,nand-enable-arbiter;
-				nand-on-flash-bbt;
-
-				/* Use Hardware BCH ECC */
-				nand-ecc-strength = <4>;
-				nand-ecc-step-size = <512>;
-
-				partition@0 {
-					label = "u-boot";
-					reg = <0x0000000 0x180000>;  /* 1.5MB */
-					read-only;
-				};
-
-				partition@180000 {
-					label = "u-boot-env";
-					reg = <0x180000 0x20000>;    /* 128KB */
-					read-only;
-				};
-
-				partition@200000 {
-					label = "uImage";
-					reg = <0x0200000 0x600000>;    /* 6MB */
-				};
-
-				partition@800000 {
-					label = "minirootfs";
-					reg = <0x0800000 0x400000>;    /* 4MB */
-				};
-
-				/* Last MB is for the BBT, i.e. not writable */
-				partition@c00000 {
-					label = "ubifs";
-					reg = <0x0c00000 0x7400000>; /* 116MB */
-				};
-			};
 		};
 	};
 
@@ -313,3 +273,52 @@
 		marvell,function = "gpio";
 	};
 };
+
+&nand_controller {
+	status = "okay";
+
+	nand@0 {
+		reg = <0>;
+		marvell,rb = <0>;
+		marvell,nand-keep-config;
+		nand-on-flash-bbt;
+
+		/* Use Hardware BCH ECC */
+		nand-ecc-strength = <4>;
+		nand-ecc-step-size = <512>;
+
+		partitions {
+			compatible = "fixed-partitions";
+			#address-cells = <1>;
+			#size-cells = <1>;
+
+			partition@0 {
+				label = "u-boot";
+				reg = <0x0000000 0x180000>;  /* 1.5MB */
+				read-only;
+			};
+
+			partition@180000 {
+				label = "u-boot-env";
+				reg = <0x180000 0x20000>;    /* 128KB */
+				read-only;
+			};
+
+			partition@200000 {
+				label = "uImage";
+				reg = <0x0200000 0x600000>;    /* 6MB */
+			};
+
+			partition@800000 {
+				label = "minirootfs";
+				reg = <0x0800000 0x400000>;    /* 4MB */
+			};
+
+			/* Last MB is for the BBT, i.e. not writable */
+			partition@c00000 {
+				label = "ubifs";
+				reg = <0x0c00000 0x7400000>; /* 116MB */
+			};
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/armada-370-rd.dts b/arch/arm/boot/dts/armada-370-rd.dts
index 8b2fa9a49967..d949cc6da1d6 100644
--- a/arch/arm/boot/dts/armada-370-rd.dts
+++ b/arch/arm/boot/dts/armada-370-rd.dts
@@ -148,27 +148,6 @@
 					default-state = "keep";
 				};
 			};
-
-			nand@d0000 {
-				status = "okay";
-				num-cs = <1>;
-				marvell,nand-keep-config;
-				marvell,nand-enable-arbiter;
-				nand-on-flash-bbt;
-
-				partition@0 {
-					label = "U-Boot";
-					reg = <0 0x800000>;
-				};
-				partition@800000 {
-					label = "Linux";
-					reg = <0x800000 0x800000>;
-				};
-				partition@1000000 {
-					label = "Filesystem";
-					reg = <0x1000000 0x3f000000>;
-				};
-			};
 		};
 	};
 
@@ -293,3 +272,33 @@
 		marvell,function = "gpio";
 	};
 };
+
+&nand_controller {
+	status = "okay";
+
+	nand@0 {
+		reg = <0>;
+		marvell,rb = <0>;
+		marvell,nand-keep-config;
+		nand-on-flash-bbt;
+
+		partitions {
+			compatible = "fixed-partitions";
+			#address-cells = <1>;
+			#size-cells = <1>;
+
+			partition@0 {
+				label = "U-Boot";
+				reg = <0 0x800000>;
+			};
+			partition@800000 {
+				label = "Linux";
+				reg = <0x800000 0x800000>;
+			};
+			partition@1000000 {
+				label = "Filesystem";
+				reg = <0x1000000 0x3f000000>;
+			};
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/armada-370-seagate-nas-xbay.dtsi b/arch/arm/boot/dts/armada-370-seagate-nas-xbay.dtsi
index e9a5b952afc0..8f4b556e8fbf 100644
--- a/arch/arm/boot/dts/armada-370-seagate-nas-xbay.dtsi
+++ b/arch/arm/boot/dts/armada-370-seagate-nas-xbay.dtsi
@@ -69,33 +69,6 @@
 					interrupts = <110>;
 				};
 			};
-
-			nand@d0000 {
-				status = "okay";
-				num-cs = <1>;
-				marvell,nand-keep-config;
-				marvell,nand-enable-arbiter;
-				nand-on-flash-bbt;
-				nand-ecc-strength = <4>;
-				nand-ecc-step-size = <512>;
-
-				partition@0 {
-					label = "u-boot";
-					reg = <0x0 0x300000>;
-				};
-				partition@300000 {
-					label = "device-tree";
-					reg = <0x300000 0x20000>;
-				};
-				partition@320000 {
-					label = "linux";
-					reg = <0x320000 0x2000000>;
-				};
-				partition@2320000 {
-					label = "rootfs";
-					reg = <0x2320000 0xdce0000>;
-				};
-			};
 		};
 
 	};
@@ -230,3 +203,39 @@
 		marvell,function = "gpio";
 	};
 };
+
+&nand_controller {
+	status = "okay";
+
+	nand@0 {
+		reg = <0>;
+		marvell,rb = <0>;
+		marvell,nand-keep-config;
+		nand-on-flash-bbt;
+		nand-ecc-strength = <4>;
+		nand-ecc-step-size = <512>;
+
+		partitions {
+			compatible = "fixed-partitions";
+			#address-cells = <1>;
+			#size-cells = <1>;
+
+			partition@0 {
+				label = "u-boot";
+				reg = <0x0 0x300000>;
+			};
+			partition@300000 {
+				label = "device-tree";
+				reg = <0x300000 0x20000>;
+			};
+			partition@320000 {
+				label = "linux";
+				reg = <0x320000 0x2000000>;
+			};
+			partition@2320000 {
+				label = "rootfs";
+				reg = <0x2320000 0xdce0000>;
+			};
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/armada-370-xp.dtsi b/arch/arm/boot/dts/armada-370-xp.dtsi
index 09495e87b038..b6bead81c438 100644
--- a/arch/arm/boot/dts/armada-370-xp.dtsi
+++ b/arch/arm/boot/dts/armada-370-xp.dtsi
@@ -281,11 +281,11 @@
 				status = "disabled";
 			};
 
-			nand: nand@d0000 {
-				compatible = "marvell,armada370-nand";
+			nand_controller: nand-controller@d0000 {
+				compatible = "marvell,armada370-nand-controller";
 				reg = <0xd0000 0x54>;
 				#address-cells = <1>;
-				#size-cells = <1>;
+				#size-cells = <0>;
 				interrupts = <113>;
 				clocks = <&coredivclk 0>;
 				status = "disabled";
diff --git a/arch/arm/boot/dts/armada-xp-db-dxbc2.dts b/arch/arm/boot/dts/armada-xp-db-dxbc2.dts
index 1b1ff17fdd9c..ba3ecf3d2481 100644
--- a/arch/arm/boot/dts/armada-xp-db-dxbc2.dts
+++ b/arch/arm/boot/dts/armada-xp-db-dxbc2.dts
@@ -109,7 +109,6 @@
 	status = "okay";
 	num-cs = <1>;
 	marvell,nand-keep-config;
-	marvell,nand-enable-arbiter;
 	nand-on-flash-bbt;
 	nand-ecc-strength = <4>;
 	nand-ecc-step-size = <512>;
diff --git a/arch/arm/boot/dts/armada-xp-db-xc3-24g4xg.dts b/arch/arm/boot/dts/armada-xp-db-xc3-24g4xg.dts
index 06fce35d7491..42f939dd9fdc 100644
--- a/arch/arm/boot/dts/armada-xp-db-xc3-24g4xg.dts
+++ b/arch/arm/boot/dts/armada-xp-db-xc3-24g4xg.dts
@@ -108,7 +108,6 @@
 	status = "okay";
 	num-cs = <1>;
 	marvell,nand-keep-config;
-	marvell,nand-enable-arbiter;
 	nand-on-flash-bbt;
 	nand-ecc-strength = <4>;
 	nand-ecc-step-size = <512>;
diff --git a/arch/arm/boot/dts/armada-xp-db.dts b/arch/arm/boot/dts/armada-xp-db.dts
index 065282c21789..311d6af25118 100644
--- a/arch/arm/boot/dts/armada-xp-db.dts
+++ b/arch/arm/boot/dts/armada-xp-db.dts
@@ -184,7 +184,6 @@
 				status = "okay";
 				num-cs = <1>;
 				marvell,nand-keep-config;
-				marvell,nand-enable-arbiter;
 				nand-on-flash-bbt;
 
 				partitions {
diff --git a/arch/arm/boot/dts/armada-xp-gp.dts b/arch/arm/boot/dts/armada-xp-gp.dts
index ac9eab8ac186..a3053d176262 100644
--- a/arch/arm/boot/dts/armada-xp-gp.dts
+++ b/arch/arm/boot/dts/armada-xp-gp.dts
@@ -201,7 +201,6 @@
 				status = "okay";
 				num-cs = <1>;
 				marvell,nand-keep-config;
-				marvell,nand-enable-arbiter;
 				nand-on-flash-bbt;
 			};
 		};
diff --git a/arch/arm/boot/dts/armada-xp-lenovo-ix4-300d.dts b/arch/arm/boot/dts/armada-xp-lenovo-ix4-300d.dts
index ce0afba1ce58..a0ace4c92a8d 100644
--- a/arch/arm/boot/dts/armada-xp-lenovo-ix4-300d.dts
+++ b/arch/arm/boot/dts/armada-xp-lenovo-ix4-300d.dts
@@ -122,7 +122,6 @@
 				status = "okay";
 				num-cs = <1>;
 				marvell,nand-keep-config;
-				marvell,nand-enable-arbiter;
 				nand-on-flash-bbt;
 
 				partitions {
diff --git a/arch/arm/boot/dts/armada-xp-linksys-mamba.dts b/arch/arm/boot/dts/armada-xp-linksys-mamba.dts
index 6d705f518254..3ec10054d5a3 100644
--- a/arch/arm/boot/dts/armada-xp-linksys-mamba.dts
+++ b/arch/arm/boot/dts/armada-xp-linksys-mamba.dts
@@ -196,79 +196,6 @@
 			bm@c8000 {
 				status = "okay";
 			};
-
-			nand@d0000 {
-				status = "okay";
-				num-cs = <1>;
-				marvell,nand-keep-config;
-				marvell,nand-enable-arbiter;
-				nand-on-flash-bbt;
-				nand-ecc-strength = <4>;
-				nand-ecc-step-size = <512>;
-
-				partition@0 {
-					label = "u-boot";
-					reg = <0x0000000 0x100000>;  /* 1MB */
-					read-only;
-				};
-
-				partition@100000 {
-					label = "u_env";
-					reg = <0x100000 0x40000>;    /* 256KB */
-				};
-
-				partition@140000 {
-					label = "s_env";
-					reg = <0x140000 0x40000>;    /* 256KB */
-				};
-
-				partition@900000 {
-					label = "devinfo";
-					reg = <0x900000 0x100000>;   /* 1MB */
-					read-only;
-				};
-
-				/* kernel1 overlaps with rootfs1 by design */
-				partition@a00000 {
-					label = "kernel1";
-					reg = <0xa00000 0x2800000>;  /* 40MB */
-				};
-
-				partition@d00000 {
-					label = "rootfs1";
-					reg = <0xd00000 0x2500000>;  /* 37MB */
-				};
-
-				/* kernel2 overlaps with rootfs2 by design */
-				partition@3200000 {
-					label = "kernel2";
-					reg = <0x3200000 0x2800000>; /* 40MB */
-				};
-
-				partition@3500000 {
-					label = "rootfs2";
-					reg = <0x3500000 0x2500000>; /* 37MB */
-				};
-
-				/*
-				 * 38MB, last MB is for the BBT, not writable
-				 */
-				partition@5a00000 {
-					label = "syscfg";
-					reg = <0x5a00000 0x2600000>;
-				};
-
-				/*
-				 * Unused area between "s_env" and "devinfo".
-				 * Moved here because otherwise the renumbered
-				 * partitions would break the bootloader
-				 * supplied bootargs
-				 */
-				partition@180000 {
-					label = "unused_area";
-					reg = <0x180000 0x780000>;   /* 7.5MB */
-				};
-			};
 		};
 
 		bm-bppi {
@@ -465,3 +392,85 @@
 		};
 	};
 };
+
+&nand_controller {
+	status = "okay";
+
+	nand@0 {
+		reg = <0>;
+		marvell,rb = <0>;
+		marvell,nand-keep-config;
+		nand-on-flash-bbt;
+		nand-ecc-strength = <4>;
+		nand-ecc-step-size = <512>;
+
+		partitions {
+			compatible = "fixed-partitions";
+			#address-cells = <1>;
+			#size-cells = <1>;
+
+			partition@0 {
+				label = "u-boot";
+				reg = <0x0000000 0x100000>;  /* 1MB */
+				read-only;
+			};
+
+			partition@100000 {
+				label = "u_env";
+				reg = <0x100000 0x40000>;    /* 256KB */
+			};
+
+			partition@140000 {
+				label = "s_env";
+				reg = <0x140000 0x40000>;    /* 256KB */
+			};
+
+			partition@900000 {
+				label = "devinfo";
+				reg = <0x900000 0x100000>;   /* 1MB */
+				read-only;
+			};
+
+			/* kernel1 overlaps with rootfs1 by design */
+			partition@a00000 {
+				label = "kernel1";
+				reg = <0xa00000 0x2800000>;  /* 40MB */
+			};
+
+			partition@d00000 {
+				label = "rootfs1";
+				reg = <0xd00000 0x2500000>;  /* 37MB */
+			};
+
+			/* kernel2 overlaps with rootfs2 by design */
+			partition@3200000 {
+				label = "kernel2";
+				reg = <0x3200000 0x2800000>; /* 40MB */
+			};
+
+			partition@3500000 {
+				label = "rootfs2";
+				reg = <0x3500000 0x2500000>; /* 37MB */
+			};
+
+			/*
+			 * 38MB, last MB is for the BBT, not writable
+			 */
+			partition@5a00000 {
+				label = "syscfg";
+				reg = <0x5a00000 0x2600000>;
+			};
+
+			/*
+			 * Unused area between "s_env" and "devinfo".
+			 * Moved here because otherwise the renumbered
+			 * partitions would break the bootloader
+			 * supplied bootargs
+			 */
+			partition@180000 {
+				label = "unused_area";
+				reg = <0x180000 0x780000>;   /* 7.5MB */
+			};
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/armada-xp-netgear-rn2120.dts b/arch/arm/boot/dts/armada-xp-netgear-rn2120.dts
index 40c6fe21e720..0027f34b3666 100644
--- a/arch/arm/boot/dts/armada-xp-netgear-rn2120.dts
+++ b/arch/arm/boot/dts/armada-xp-netgear-rn2120.dts
@@ -154,46 +154,6 @@
 				nr-ports = <2>;
 				status = "okay";
 			};
-
-			nand@d0000 {
-				status = "okay";
-				num-cs = <1>;
-				marvell,nand-keep-config;
-				marvell,nand-enable-arbiter;
-				nand-on-flash-bbt;
-
-				/* Use Hardware BCH ECC */
-				nand-ecc-strength = <4>;
-				nand-ecc-step-size = <512>;
-
-				partition@0 {
-					label = "u-boot";
-					reg = <0x0000000 0x180000>;  /* 1.5MB */
-					read-only;
-				};
-
-				partition@180000 {
-					label = "u-boot-env";
-					reg = <0x180000 0x20000>;    /* 128KB */
-					read-only;
-				};
-
-				partition@200000 {
-					label = "uImage";
-					reg = <0x0200000 0x600000>;    /* 6MB */
-				};
-
-				partition@800000 {
-					label = "minirootfs";
-					reg = <0x0800000 0x400000>;    /* 4MB */
-				};
-
-				/* Last MB is for the BBT, i.e. not writable */
-				partition@c00000 {
-					label = "ubifs";
-					reg = <0x0c00000 0x7400000>; /* 116MB */
-				};
-			};
 		};
 	};
 
@@ -382,3 +342,52 @@
 		marvell,function = "gpio";
 	};
 };
+
+&nand_controller {
+	status = "okay";
+
+	nand@0 {
+		reg = <0>;
+		marvell,rb = <0>;
+		marvell,nand-keep-config;
+		nand-on-flash-bbt;
+
+		/* Use Hardware BCH ECC */
+		nand-ecc-strength = <4>;
+		nand-ecc-step-size = <512>;
+
+		partitions {
+			compatible = "fixed-partitions";
+			#address-cells = <1>;
+			#size-cells = <1>;
+
+			partition@0 {
+				label = "u-boot";
+				reg = <0x0000000 0x180000>;  /* 1.5MB */
+				read-only;
+			};
+
+			partition@180000 {
+				label = "u-boot-env";
+				reg = <0x180000 0x20000>;    /* 128KB */
+				read-only;
+			};
+
+			partition@200000 {
+				label = "uImage";
+				reg = <0x0200000 0x600000>;    /* 6MB */
+			};
+
+			partition@800000 {
+				label = "minirootfs";
+				reg = <0x0800000 0x400000>;    /* 4MB */
+			};
+
+			/* Last MB is for the BBT, i.e. not writable */
+			partition@c00000 {
+				label = "ubifs";
+				reg = <0x0c00000 0x7400000>; /* 116MB */
+			};
+		};
+	};
+};
-- 
2.11.0

^ permalink raw reply related	[flat|nested] 75+ messages in thread

* [RFC 07/12] ARM: dts: armada-370-xp: use reworked NAND controller driver
@ 2017-10-18 14:36   ` Miquel Raynal
  0 siblings, 0 replies; 75+ messages in thread
From: Miquel Raynal @ 2017-10-18 14:36 UTC (permalink / raw)
  To: linux-arm-kernel

Change the bindings to use the reworked Marvell NAND controller driver.
Also adapt the nand controller node organization to distinguish which
property is relevant for the controller, and which one is NAND chip
specific. Expose the partitions as a subnode of the NAND chip.

Remove the 'marvell,nand-enable-arbiter' property, inefective with
Armada boards.

Signed-off-by: Miquel Raynal <miquel.raynal@free-electrons.com>
---
 arch/arm/boot/dts/armada-370-db.dts                |  56 ++++----
 arch/arm/boot/dts/armada-370-dlink-dns327l.dts     | 119 ++++++++--------
 arch/arm/boot/dts/armada-370-mirabox.dts           |  50 ++++---
 arch/arm/boot/dts/armada-370-netgear-rn102.dts     |  89 ++++++------
 arch/arm/boot/dts/armada-370-netgear-rn104.dts     |  89 ++++++------
 arch/arm/boot/dts/armada-370-rd.dts                |  51 ++++---
 arch/arm/boot/dts/armada-370-seagate-nas-xbay.dtsi |  63 +++++----
 arch/arm/boot/dts/armada-370-xp.dtsi               |   6 +-
 arch/arm/boot/dts/armada-xp-db-dxbc2.dts           |   1 -
 arch/arm/boot/dts/armada-xp-db-xc3-24g4xg.dts      |   1 -
 arch/arm/boot/dts/armada-xp-db.dts                 |   1 -
 arch/arm/boot/dts/armada-xp-gp.dts                 |   1 -
 arch/arm/boot/dts/armada-xp-lenovo-ix4-300d.dts    |   1 -
 arch/arm/boot/dts/armada-xp-linksys-mamba.dts      | 155 +++++++++++----------
 arch/arm/boot/dts/armada-xp-netgear-rn2120.dts     |  89 ++++++------
 15 files changed, 420 insertions(+), 352 deletions(-)

diff --git a/arch/arm/boot/dts/armada-370-db.dts b/arch/arm/boot/dts/armada-370-db.dts
index c4eef7323367..76c433c29731 100644
--- a/arch/arm/boot/dts/armada-370-db.dts
+++ b/arch/arm/boot/dts/armada-370-db.dts
@@ -142,33 +142,6 @@
 			usb at 51000 {
 				status = "okay";
 			};
-
-			nand at d0000 {
-				status = "okay";
-				num-cs = <1>;
-				marvell,nand-keep-config;
-				marvell,nand-enable-arbiter;
-				nand-on-flash-bbt;
-
-				partitions {
-					compatible = "fixed-partitions";
-					#address-cells = <1>;
-					#size-cells = <1>;
-
-					partition at 0 {
-						label = "U-Boot";
-						reg = <0 0x800000>;
-					};
-					partition at 800000 {
-						label = "Linux";
-						reg = <0x800000 0x800000>;
-					};
-					partition at 1000000 {
-						label = "Filesystem";
-						reg = <0x1000000 0x3f000000>;
-					};
-				};
-			};
 		};
 	};
 
@@ -276,3 +249,32 @@
 	};
 };
 
+&nand_controller {
+	status = "okay";
+
+	nand at 0 {
+		reg = <0>;
+		marvell,rb = <0>;
+		marvell,nand-keep-config;
+		nand-on-flash-bbt;
+
+		partitions {
+			compatible = "fixed-partitions";
+			#address-cells = <1>;
+			#size-cells = <1>;
+
+			partition at 0 {
+				label = "U-Boot";
+				reg = <0 0x800000>;
+			};
+			partition at 800000 {
+				label = "Linux";
+				reg = <0x800000 0x800000>;
+			};
+			partition at 1000000 {
+				label = "Filesystem";
+				reg = <0x1000000 0x3f000000>;
+			};
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/armada-370-dlink-dns327l.dts b/arch/arm/boot/dts/armada-370-dlink-dns327l.dts
index db7f3aa38670..0cc3285a40b3 100644
--- a/arch/arm/boot/dts/armada-370-dlink-dns327l.dts
+++ b/arch/arm/boot/dts/armada-370-dlink-dns327l.dts
@@ -81,61 +81,6 @@
 			usb at 50000 {
 				status = "okay";
 			};
-
-			nand at d0000 {
-				status = "okay";
-				num-cs = <1>;
-				marvell,nand-keep-config;
-				marvell,nand-enable-arbiter;
-				nand-on-flash-bbt;
-				nand-ecc-strength = <4>;
-				nand-ecc-step-size = <512>;
-
-				partition at 0 {
-					label = "u-boot";
-					/* 1.0 MiB */
-					reg = <0x0000000 0x100000>;
-					read-only;
-				};
-
-				partition at 100000 {
-					label = "u-boot-env";
-					/* 128 KiB */
-					reg = <0x100000 0x20000>;
-					read-only;
-				};
-
-				partition at 120000 {
-					label = "uImage";
-					/* 7 MiB */
-					reg = <0x120000 0x700000>;
-				};
-
-				partition at 820000 {
-					label = "ubifs";
-					/* ~ 84 MiB */
-					reg = <0x820000 0x54e0000>;
-				};
-
-				/* Hardcoded into stock bootloader */
-				partition at 5d00000 {
-					label = "failsafe-uImage";
-					/* 5 MiB */
-					reg = <0x5d00000 0x500000>;
-				};
-
-				partition at 6200000 {
-					label = "failsafe-fs";
-					/* 29 MiB */
-					reg = <0x6200000 0x1d00000>;
-				};
-
-				partition at 7f00000 {
-					label = "bbt";
-					/* 1 MiB for BBT */
-					reg = <0x7f00000 0x100000>;
-				};
-			};
 		};
 	};
 
@@ -356,3 +301,67 @@
 	clock-frequency = <100000>;
 	status = "okay";
 };
+
+&nand_controller {
+	status = "okay";
+
+	nand at 0 {
+		reg = <0>;
+		marvell,rb = <0>;
+		marvell,nand-keep-config;
+		nand-on-flash-bbt;
+		nand-ecc-strength = <4>;
+		nand-ecc-step-size = <512>;
+
+		partitions {
+			compatible = "fixed-partitions";
+			#address-cells = <1>;
+			#size-cells = <1>;
+
+			partition at 0 {
+				label = "u-boot";
+				/* 1.0 MiB */
+				reg = <0x0000000 0x100000>;
+				read-only;
+			};
+
+			partition at 100000 {
+				label = "u-boot-env";
+				/* 128 KiB */
+				reg = <0x100000 0x20000>;
+				read-only;
+			};
+
+			partition at 120000 {
+				label = "uImage";
+				/* 7 MiB */
+				reg = <0x120000 0x700000>;
+			};
+
+			partition at 820000 {
+				label = "ubifs";
+				/* ~ 84 MiB */
+				reg = <0x820000 0x54e0000>;
+			};
+
+			/* Hardcoded into stock bootloader */
+			partition at 5d00000 {
+				label = "failsafe-uImage";
+				/* 5 MiB */
+				reg = <0x5d00000 0x500000>;
+			};
+
+			partition at 6200000 {
+				label = "failsafe-fs";
+				/* 29 MiB */
+				reg = <0x6200000 0x1d00000>;
+			};
+
+			partition at 7f00000 {
+				label = "bbt";
+				/* 1 MiB for BBT */
+				reg = <0x7f00000 0x100000>;
+			};
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/armada-370-mirabox.dts b/arch/arm/boot/dts/armada-370-mirabox.dts
index 702f58c9642d..9e13a83cf057 100644
--- a/arch/arm/boot/dts/armada-370-mirabox.dts
+++ b/arch/arm/boot/dts/armada-370-mirabox.dts
@@ -145,27 +145,6 @@
 					reg = <0x25>;
 				};
 			};
-
-			nand at d0000 {
-				status = "okay";
-				num-cs = <1>;
-				marvell,nand-keep-config;
-				marvell,nand-enable-arbiter;
-				nand-on-flash-bbt;
-
-				partition at 0 {
-					label = "U-Boot";
-					reg = <0 0x400000>;
-				};
-				partition at 400000 {
-					label = "Linux";
-					reg = <0x400000 0x400000>;
-				};
-				partition at 800000 {
-					label = "Filesystem";
-					reg = <0x800000 0x3f800000>;
-				};
-			};
 		};
 	};
 };
@@ -210,3 +189,32 @@
 	};
 };
 
+&nand_controller {
+	status = "okay";
+
+	nand at 0 {
+		reg = <0>;
+		marvell,rb = <0>;
+		marvell,nand-keep-config;
+		nand-on-flash-bbt;
+
+		partitions {
+			compatible = "fixed-partitions";
+			#address-cells = <1>;
+			#size-cells = <1>;
+
+			partition at 0 {
+				label = "U-Boot";
+				reg = <0 0x400000>;
+			};
+			partition at 400000 {
+				label = "Linux";
+				reg = <0x400000 0x400000>;
+			};
+			partition at 800000 {
+				label = "Filesystem";
+				reg = <0x800000 0x3f800000>;
+			};
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/armada-370-netgear-rn102.dts b/arch/arm/boot/dts/armada-370-netgear-rn102.dts
index b1a96e95e921..e4fb1d7cf009 100644
--- a/arch/arm/boot/dts/armada-370-netgear-rn102.dts
+++ b/arch/arm/boot/dts/armada-370-netgear-rn102.dts
@@ -118,46 +118,6 @@
 					pwm_polarity = <0>;
 				};
 			};
-
-			nand at d0000 {
-				status = "okay";
-				num-cs = <1>;
-				marvell,nand-keep-config;
-				marvell,nand-enable-arbiter;
-				nand-on-flash-bbt;
-
-				/* Use Hardware BCH ECC */
-				nand-ecc-strength = <4>;
-				nand-ecc-step-size = <512>;
-
-				partition at 0 {
-					label = "u-boot";
-					reg = <0x0000000 0x180000>;  /* 1.5MB */
-					read-only;
-				};
-
-				partition at 180000 {
-					label = "u-boot-env";
-					reg = <0x180000 0x20000>;    /* 128KB */
-					read-only;
-				};
-
-				partition at 200000 {
-					label = "uImage";
-					reg = <0x0200000 0x600000>;    /* 6MB */
-				};
-
-				partition at 800000 {
-					label = "minirootfs";
-					reg = <0x0800000 0x400000>;    /* 4MB */
-				};
-
-				/* Last MB is for the BBT, i.e. not writable */
-				partition at c00000 {
-					label = "ubifs";
-					reg = <0x0c00000 0x7400000>; /* 116MB */
-				};
-			};
 		};
 	};
 
@@ -301,3 +261,52 @@
 		marvell,function = "gpio";
 	};
 };
+
+&nand_controller {
+	status = "okay";
+
+	nand at 0 {
+		reg = <0>;
+		marvell,rb = <0>;
+		marvell,nand-keep-config;
+		nand-on-flash-bbt;
+
+		/* Use Hardware BCH ECC */
+		nand-ecc-strength = <4>;
+		nand-ecc-step-size = <512>;
+
+		partitions {
+			compatible = "fixed-partitions";
+			#address-cells = <1>;
+			#size-cells = <1>;
+
+			partition at 0 {
+				label = "u-boot";
+				reg = <0x0000000 0x180000>;  /* 1.5MB */
+				read-only;
+			};
+
+			partition at 180000 {
+				label = "u-boot-env";
+				reg = <0x180000 0x20000>;    /* 128KB */
+				read-only;
+			};
+
+			partition at 200000 {
+				label = "uImage";
+				reg = <0x0200000 0x600000>;    /* 6MB */
+			};
+
+			partition at 800000 {
+				label = "minirootfs";
+				reg = <0x0800000 0x400000>;    /* 4MB */
+			};
+
+			/* Last MB is for the BBT, i.e. not writable */
+			partition at c00000 {
+				label = "ubifs";
+				reg = <0x0c00000 0x7400000>; /* 116MB */
+			};
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/armada-370-netgear-rn104.dts b/arch/arm/boot/dts/armada-370-netgear-rn104.dts
index d67e7aa42b54..f639838ce27d 100644
--- a/arch/arm/boot/dts/armada-370-netgear-rn104.dts
+++ b/arch/arm/boot/dts/armada-370-netgear-rn104.dts
@@ -127,46 +127,6 @@
 					reg = <0x23>;
 				};
 			};
-
-			nand at d0000 {
-				status = "okay";
-				num-cs = <1>;
-				marvell,nand-keep-config;
-				marvell,nand-enable-arbiter;
-				nand-on-flash-bbt;
-
-				/* Use Hardware BCH ECC */
-				nand-ecc-strength = <4>;
-				nand-ecc-step-size = <512>;
-
-				partition at 0 {
-					label = "u-boot";
-					reg = <0x0000000 0x180000>;  /* 1.5MB */
-					read-only;
-				};
-
-				partition at 180000 {
-					label = "u-boot-env";
-					reg = <0x180000 0x20000>;    /* 128KB */
-					read-only;
-				};
-
-				partition at 200000 {
-					label = "uImage";
-					reg = <0x0200000 0x600000>;    /* 6MB */
-				};
-
-				partition at 800000 {
-					label = "minirootfs";
-					reg = <0x0800000 0x400000>;    /* 4MB */
-				};
-
-				/* Last MB is for the BBT, i.e. not writable */
-				partition at c00000 {
-					label = "ubifs";
-					reg = <0x0c00000 0x7400000>; /* 116MB */
-				};
-			};
 		};
 	};
 
@@ -313,3 +273,52 @@
 		marvell,function = "gpio";
 	};
 };
+
+&nand_controller {
+	status = "okay";
+
+	nand at 0 {
+		reg = <0>;
+		marvell,rb = <0>;
+		marvell,nand-keep-config;
+		nand-on-flash-bbt;
+
+		/* Use Hardware BCH ECC */
+		nand-ecc-strength = <4>;
+		nand-ecc-step-size = <512>;
+
+		partitions {
+			compatible = "fixed-partitions";
+			#address-cells = <1>;
+			#size-cells = <1>;
+
+			partition at 0 {
+				label = "u-boot";
+				reg = <0x0000000 0x180000>;  /* 1.5MB */
+				read-only;
+			};
+
+			partition at 180000 {
+				label = "u-boot-env";
+				reg = <0x180000 0x20000>;    /* 128KB */
+				read-only;
+			};
+
+			partition at 200000 {
+				label = "uImage";
+				reg = <0x0200000 0x600000>;    /* 6MB */
+			};
+
+			partition at 800000 {
+				label = "minirootfs";
+				reg = <0x0800000 0x400000>;    /* 4MB */
+			};
+
+			/* Last MB is for the BBT, i.e. not writable */
+			partition at c00000 {
+				label = "ubifs";
+				reg = <0x0c00000 0x7400000>; /* 116MB */
+			};
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/armada-370-rd.dts b/arch/arm/boot/dts/armada-370-rd.dts
index 8b2fa9a49967..d949cc6da1d6 100644
--- a/arch/arm/boot/dts/armada-370-rd.dts
+++ b/arch/arm/boot/dts/armada-370-rd.dts
@@ -148,27 +148,6 @@
 					default-state = "keep";
 				};
 			};
-
-			nand at d0000 {
-				status = "okay";
-				num-cs = <1>;
-				marvell,nand-keep-config;
-				marvell,nand-enable-arbiter;
-				nand-on-flash-bbt;
-
-				partition at 0 {
-					label = "U-Boot";
-					reg = <0 0x800000>;
-				};
-				partition at 800000 {
-					label = "Linux";
-					reg = <0x800000 0x800000>;
-				};
-				partition at 1000000 {
-					label = "Filesystem";
-					reg = <0x1000000 0x3f000000>;
-				};
-			};
 		};
 	};
 
@@ -293,3 +272,33 @@
 		marvell,function = "gpio";
 	};
 };
+
+&nand_controller {
+	status = "okay";
+
+	nand at 0 {
+		reg = <0>;
+		marvell,rb = <0>;
+		marvell,nand-keep-config;
+		nand-on-flash-bbt;
+
+		partitions {
+			compatible = "fixed-partitions";
+			#address-cells = <1>;
+			#size-cells = <1>;
+
+			partition at 0 {
+				label = "U-Boot";
+				reg = <0 0x800000>;
+			};
+			partition at 800000 {
+				label = "Linux";
+				reg = <0x800000 0x800000>;
+			};
+			partition at 1000000 {
+				label = "Filesystem";
+				reg = <0x1000000 0x3f000000>;
+			};
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/armada-370-seagate-nas-xbay.dtsi b/arch/arm/boot/dts/armada-370-seagate-nas-xbay.dtsi
index e9a5b952afc0..8f4b556e8fbf 100644
--- a/arch/arm/boot/dts/armada-370-seagate-nas-xbay.dtsi
+++ b/arch/arm/boot/dts/armada-370-seagate-nas-xbay.dtsi
@@ -69,33 +69,6 @@
 					interrupts = <110>;
 				};
 			};
-
-			nand at d0000 {
-				status = "okay";
-				num-cs = <1>;
-				marvell,nand-keep-config;
-				marvell,nand-enable-arbiter;
-				nand-on-flash-bbt;
-				nand-ecc-strength = <4>;
-				nand-ecc-step-size = <512>;
-
-				partition at 0 {
-					label = "u-boot";
-					reg = <0x0 0x300000>;
-				};
-				partition at 300000 {
-					label = "device-tree";
-					reg = <0x300000 0x20000>;
-				};
-				partition at 320000 {
-					label = "linux";
-					reg = <0x320000 0x2000000>;
-				};
-				partition at 2320000 {
-					label = "rootfs";
-					reg = <0x2320000 0xdce0000>;
-				};
-			};
 		};
 
 	};
@@ -230,3 +203,39 @@
 		marvell,function = "gpio";
 	};
 };
+
+&nand_controller {
+	status = "okay";
+
+	nand at 0 {
+		reg = <0>;
+		marvell,rb = <0>;
+		marvell,nand-keep-config;
+		nand-on-flash-bbt;
+		nand-ecc-strength = <4>;
+		nand-ecc-step-size = <512>;
+
+		partitions {
+			compatible = "fixed-partitions";
+			#address-cells = <1>;
+			#size-cells = <1>;
+
+			partition at 0 {
+				label = "u-boot";
+				reg = <0x0 0x300000>;
+			};
+			partition at 300000 {
+				label = "device-tree";
+				reg = <0x300000 0x20000>;
+			};
+			partition at 320000 {
+				label = "linux";
+				reg = <0x320000 0x2000000>;
+			};
+			partition at 2320000 {
+				label = "rootfs";
+				reg = <0x2320000 0xdce0000>;
+			};
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/armada-370-xp.dtsi b/arch/arm/boot/dts/armada-370-xp.dtsi
index 09495e87b038..b6bead81c438 100644
--- a/arch/arm/boot/dts/armada-370-xp.dtsi
+++ b/arch/arm/boot/dts/armada-370-xp.dtsi
@@ -281,11 +281,11 @@
 				status = "disabled";
 			};
 
-			nand: nand at d0000 {
-				compatible = "marvell,armada370-nand";
+			nand_controller: nand-controller at d0000 {
+				compatible = "marvell,armada370-nand-controller";
 				reg = <0xd0000 0x54>;
 				#address-cells = <1>;
-				#size-cells = <1>;
+				#size-cells = <0>;
 				interrupts = <113>;
 				clocks = <&coredivclk 0>;
 				status = "disabled";
diff --git a/arch/arm/boot/dts/armada-xp-db-dxbc2.dts b/arch/arm/boot/dts/armada-xp-db-dxbc2.dts
index 1b1ff17fdd9c..ba3ecf3d2481 100644
--- a/arch/arm/boot/dts/armada-xp-db-dxbc2.dts
+++ b/arch/arm/boot/dts/armada-xp-db-dxbc2.dts
@@ -109,7 +109,6 @@
 	status = "okay";
 	num-cs = <1>;
 	marvell,nand-keep-config;
-	marvell,nand-enable-arbiter;
 	nand-on-flash-bbt;
 	nand-ecc-strength = <4>;
 	nand-ecc-step-size = <512>;
diff --git a/arch/arm/boot/dts/armada-xp-db-xc3-24g4xg.dts b/arch/arm/boot/dts/armada-xp-db-xc3-24g4xg.dts
index 06fce35d7491..42f939dd9fdc 100644
--- a/arch/arm/boot/dts/armada-xp-db-xc3-24g4xg.dts
+++ b/arch/arm/boot/dts/armada-xp-db-xc3-24g4xg.dts
@@ -108,7 +108,6 @@
 	status = "okay";
 	num-cs = <1>;
 	marvell,nand-keep-config;
-	marvell,nand-enable-arbiter;
 	nand-on-flash-bbt;
 	nand-ecc-strength = <4>;
 	nand-ecc-step-size = <512>;
diff --git a/arch/arm/boot/dts/armada-xp-db.dts b/arch/arm/boot/dts/armada-xp-db.dts
index 065282c21789..311d6af25118 100644
--- a/arch/arm/boot/dts/armada-xp-db.dts
+++ b/arch/arm/boot/dts/armada-xp-db.dts
@@ -184,7 +184,6 @@
 				status = "okay";
 				num-cs = <1>;
 				marvell,nand-keep-config;
-				marvell,nand-enable-arbiter;
 				nand-on-flash-bbt;
 
 				partitions {
diff --git a/arch/arm/boot/dts/armada-xp-gp.dts b/arch/arm/boot/dts/armada-xp-gp.dts
index ac9eab8ac186..a3053d176262 100644
--- a/arch/arm/boot/dts/armada-xp-gp.dts
+++ b/arch/arm/boot/dts/armada-xp-gp.dts
@@ -201,7 +201,6 @@
 				status = "okay";
 				num-cs = <1>;
 				marvell,nand-keep-config;
-				marvell,nand-enable-arbiter;
 				nand-on-flash-bbt;
 			};
 		};
diff --git a/arch/arm/boot/dts/armada-xp-lenovo-ix4-300d.dts b/arch/arm/boot/dts/armada-xp-lenovo-ix4-300d.dts
index ce0afba1ce58..a0ace4c92a8d 100644
--- a/arch/arm/boot/dts/armada-xp-lenovo-ix4-300d.dts
+++ b/arch/arm/boot/dts/armada-xp-lenovo-ix4-300d.dts
@@ -122,7 +122,6 @@
 				status = "okay";
 				num-cs = <1>;
 				marvell,nand-keep-config;
-				marvell,nand-enable-arbiter;
 				nand-on-flash-bbt;
 
 				partitions {
diff --git a/arch/arm/boot/dts/armada-xp-linksys-mamba.dts b/arch/arm/boot/dts/armada-xp-linksys-mamba.dts
index 6d705f518254..3ec10054d5a3 100644
--- a/arch/arm/boot/dts/armada-xp-linksys-mamba.dts
+++ b/arch/arm/boot/dts/armada-xp-linksys-mamba.dts
@@ -196,79 +196,6 @@
 			bm at c8000 {
 				status = "okay";
 			};
-
-			nand at d0000 {
-				status = "okay";
-				num-cs = <1>;
-				marvell,nand-keep-config;
-				marvell,nand-enable-arbiter;
-				nand-on-flash-bbt;
-				nand-ecc-strength = <4>;
-				nand-ecc-step-size = <512>;
-
-				partition at 0 {
-					label = "u-boot";
-					reg = <0x0000000 0x100000>;  /* 1MB */
-					read-only;
-				};
-
-				partition at 100000 {
-					label = "u_env";
-					reg = <0x100000 0x40000>;    /* 256KB */
-				};
-
-				partition at 140000 {
-					label = "s_env";
-					reg = <0x140000 0x40000>;    /* 256KB */
-				};
-
-				partition at 900000 {
-					label = "devinfo";
-					reg = <0x900000 0x100000>;   /* 1MB */
-					read-only;
-				};
-
-				/* kernel1 overlaps with rootfs1 by design */
-				partition at a00000 {
-					label = "kernel1";
-					reg = <0xa00000 0x2800000>;  /* 40MB */
-				};
-
-				partition at d00000 {
-					label = "rootfs1";
-					reg = <0xd00000 0x2500000>;  /* 37MB */
-				};
-
-				/* kernel2 overlaps with rootfs2 by design */
-				partition at 3200000 {
-					label = "kernel2";
-					reg = <0x3200000 0x2800000>; /* 40MB */
-				};
-
-				partition at 3500000 {
-					label = "rootfs2";
-					reg = <0x3500000 0x2500000>; /* 37MB */
-				};
-
-				/*
-				 * 38MB, last MB is for the BBT, not writable
-				 */
-				partition at 5a00000 {
-					label = "syscfg";
-					reg = <0x5a00000 0x2600000>;
-				};
-
-				/*
-				 * Unused area between "s_env" and "devinfo".
-				 * Moved here because otherwise the renumbered
-				 * partitions would break the bootloader
-				 * supplied bootargs
-				 */
-				partition at 180000 {
-					label = "unused_area";
-					reg = <0x180000 0x780000>;   /* 7.5MB */
-				};
-			};
 		};
 
 		bm-bppi {
@@ -465,3 +392,85 @@
 		};
 	};
 };
+
+&nand_controller {
+	status = "okay";
+
+	nand at 0 {
+		reg = <0>;
+		marvell,rb = <0>;
+		marvell,nand-keep-config;
+		nand-on-flash-bbt;
+		nand-ecc-strength = <4>;
+		nand-ecc-step-size = <512>;
+
+		partitions {
+			compatible = "fixed-partitions";
+			#address-cells = <1>;
+			#size-cells = <1>;
+
+			partition at 0 {
+				label = "u-boot";
+				reg = <0x0000000 0x100000>;  /* 1MB */
+				read-only;
+			};
+
+			partition at 100000 {
+				label = "u_env";
+				reg = <0x100000 0x40000>;    /* 256KB */
+			};
+
+			partition at 140000 {
+				label = "s_env";
+				reg = <0x140000 0x40000>;    /* 256KB */
+			};
+
+			partition at 900000 {
+				label = "devinfo";
+				reg = <0x900000 0x100000>;   /* 1MB */
+				read-only;
+			};
+
+			/* kernel1 overlaps with rootfs1 by design */
+			partition at a00000 {
+				label = "kernel1";
+				reg = <0xa00000 0x2800000>;  /* 40MB */
+			};
+
+			partition at d00000 {
+				label = "rootfs1";
+				reg = <0xd00000 0x2500000>;  /* 37MB */
+			};
+
+			/* kernel2 overlaps with rootfs2 by design */
+			partition at 3200000 {
+				label = "kernel2";
+				reg = <0x3200000 0x2800000>; /* 40MB */
+			};
+
+			partition at 3500000 {
+				label = "rootfs2";
+				reg = <0x3500000 0x2500000>; /* 37MB */
+			};
+
+			/*
+			 * 38MB, last MB is for the BBT, not writable
+			 */
+			partition at 5a00000 {
+				label = "syscfg";
+				reg = <0x5a00000 0x2600000>;
+			};
+
+			/*
+			 * Unused area between "s_env" and "devinfo".
+			 * Moved here because otherwise the renumbered
+			 * partitions would break the bootloader
+			 * supplied bootargs
+			 */
+			partition at 180000 {
+				label = "unused_area";
+				reg = <0x180000 0x780000>;   /* 7.5MB */
+			};
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/armada-xp-netgear-rn2120.dts b/arch/arm/boot/dts/armada-xp-netgear-rn2120.dts
index 40c6fe21e720..0027f34b3666 100644
--- a/arch/arm/boot/dts/armada-xp-netgear-rn2120.dts
+++ b/arch/arm/boot/dts/armada-xp-netgear-rn2120.dts
@@ -154,46 +154,6 @@
 				nr-ports = <2>;
 				status = "okay";
 			};
-
-			nand at d0000 {
-				status = "okay";
-				num-cs = <1>;
-				marvell,nand-keep-config;
-				marvell,nand-enable-arbiter;
-				nand-on-flash-bbt;
-
-				/* Use Hardware BCH ECC */
-				nand-ecc-strength = <4>;
-				nand-ecc-step-size = <512>;
-
-				partition at 0 {
-					label = "u-boot";
-					reg = <0x0000000 0x180000>;  /* 1.5MB */
-					read-only;
-				};
-
-				partition at 180000 {
-					label = "u-boot-env";
-					reg = <0x180000 0x20000>;    /* 128KB */
-					read-only;
-				};
-
-				partition at 200000 {
-					label = "uImage";
-					reg = <0x0200000 0x600000>;    /* 6MB */
-				};
-
-				partition at 800000 {
-					label = "minirootfs";
-					reg = <0x0800000 0x400000>;    /* 4MB */
-				};
-
-				/* Last MB is for the BBT, i.e. not writable */
-				partition at c00000 {
-					label = "ubifs";
-					reg = <0x0c00000 0x7400000>; /* 116MB */
-				};
-			};
 		};
 	};
 
@@ -382,3 +342,52 @@
 		marvell,function = "gpio";
 	};
 };
+
+&nand_controller {
+	status = "okay";
+
+	nand at 0 {
+		reg = <0>;
+		marvell,rb = <0>;
+		marvell,nand-keep-config;
+		nand-on-flash-bbt;
+
+		/* Use Hardware BCH ECC */
+		nand-ecc-strength = <4>;
+		nand-ecc-step-size = <512>;
+
+		partitions {
+			compatible = "fixed-partitions";
+			#address-cells = <1>;
+			#size-cells = <1>;
+
+			partition at 0 {
+				label = "u-boot";
+				reg = <0x0000000 0x180000>;  /* 1.5MB */
+				read-only;
+			};
+
+			partition at 180000 {
+				label = "u-boot-env";
+				reg = <0x180000 0x20000>;    /* 128KB */
+				read-only;
+			};
+
+			partition at 200000 {
+				label = "uImage";
+				reg = <0x0200000 0x600000>;    /* 6MB */
+			};
+
+			partition at 800000 {
+				label = "minirootfs";
+				reg = <0x0800000 0x400000>;    /* 4MB */
+			};
+
+			/* Last MB is for the BBT, i.e. not writable */
+			partition at c00000 {
+				label = "ubifs";
+				reg = <0x0c00000 0x7400000>; /* 116MB */
+			};
+		};
+	};
+};
-- 
2.11.0

^ permalink raw reply related	[flat|nested] 75+ messages in thread

* [RFC 08/12] ARM: dts: armada-375: use reworked NAND controller driver
  2017-10-18 14:36 ` Miquel Raynal
  (?)
@ 2017-10-18 14:36   ` Miquel Raynal
  -1 siblings, 0 replies; 75+ messages in thread
From: Miquel Raynal @ 2017-10-18 14:36 UTC (permalink / raw)
  To: Andrew Lunn, bcm-kernel-feedback-list, Boris Brezillon,
	Brian Norris, Catalin Marinas, Chen-Yu Tsai, Cyrille Pitchen,
	Daniel Mack, David Woodhouse, devel, devicetree, Ezequiel Garcia,
	Greg Kroah-Hartman, Gregory Clement, Han Xu, Haojian Zhuang,
	Jason Cooper, Josh Wu, Kamal Dasu, linux-arm-kernel,
	linux-kernel, linux-mediatek, linux-mtd, Marc Gonzalez,
	Marek Vasut, Mark Rutland, Masahiro Yamada, Matthias Brugger,
	Maxime Ripard, Maxim Levitsky, Richard Weinberger,
	Robert Jarzmik, Rob Herring, Russell King, Sebastian Hesselbarth,
	Stefan Agner, Sylvain Lemieux, Vladimir Zapolskiy, Wenyou Yang,
	Will Deacon
  Cc: Thomas Petazzoni, Antoine Tenart, Miquel Raynal, Igor Grinberg,
	Nadav Haklai, Ofer Heifetz, Neta Zur Hershkovits, Hanna Hawa

Change the bindings to use the reworked Marvell NAND controller driver.
Also adapt the nand controller node organization to distinguish which
property is relevant for the controller, and which one is NAND chip
specific. Expose the partitions as a subnode of the NAND chip.

Remove the 'marvell,nand-enable-arbiter' property, inefective with
Armada boards.

Signed-off-by: Miquel Raynal <miquel.raynal@free-electrons.com>
---
 arch/arm/boot/dts/armada-375-db.dts | 49 ++++++++++++++++++++++---------------
 arch/arm/boot/dts/armada-375.dtsi   |  6 ++---
 2 files changed, 32 insertions(+), 23 deletions(-)

diff --git a/arch/arm/boot/dts/armada-375-db.dts b/arch/arm/boot/dts/armada-375-db.dts
index bcdbb8ba1d65..42277a554063 100644
--- a/arch/arm/boot/dts/armada-375-db.dts
+++ b/arch/arm/boot/dts/armada-375-db.dts
@@ -140,28 +140,37 @@
 	nr-ports = <2>;
 };
 
-&nand {
+&nand_controller {
+	status = "okay";
 	pinctrl-0 = <&nand_pins>;
 	pinctrl-names = "default";
-	status = "okay";
-	num-cs = <1>;
-	marvell,nand-keep-config;
-	marvell,nand-enable-arbiter;
-	nand-on-flash-bbt;
-	nand-ecc-strength = <4>;
-	nand-ecc-step-size = <512>;
-
-	partition@0 {
-		label = "U-Boot";
-		reg = <0 0x800000>;
-	};
-	partition@800000 {
-		label = "Linux";
-		reg = <0x800000 0x800000>;
-	};
-	partition@1000000 {
-		label = "Filesystem";
-		reg = <0x1000000 0x3f000000>;
+
+	nand@0 {
+		reg = <0>;
+		marvell,rb = <0>;
+		marvell,nand-keep-config;
+		nand-on-flash-bbt;
+		nand-ecc-strength = <4>;
+		nand-ecc-step-size = <512>;
+
+		partitions {
+			compatible = "fixed-partitions";
+			#address-cells = <1>;
+			#size-cells = <1>;
+
+			partition@0 {
+				label = "U-Boot";
+				reg = <0 0x800000>;
+			};
+			partition@800000 {
+				label = "Linux";
+				reg = <0x800000 0x800000>;
+			};
+			partition@1000000 {
+				label = "Filesystem";
+				reg = <0x1000000 0x3f000000>;
+			};
+		};
 	};
 };
 
diff --git a/arch/arm/boot/dts/armada-375.dtsi b/arch/arm/boot/dts/armada-375.dtsi
index 7225c7ce9a8d..db66b5f29834 100644
--- a/arch/arm/boot/dts/armada-375.dtsi
+++ b/arch/arm/boot/dts/armada-375.dtsi
@@ -539,11 +539,11 @@
 				status = "disabled";
 			};
 
-			nand: nand@d0000 {
-				compatible = "marvell,armada370-nand";
+			nand_controller: nand-controller@d0000 {
+				compatible = "marvell,armada370-nand-controller";
 				reg = <0xd0000 0x54>;
 				#address-cells = <1>;
-				#size-cells = <1>;
+				#size-cells = <0>;
 				interrupts = <GIC_SPI 84 IRQ_TYPE_LEVEL_HIGH>;
 				clocks = <&gateclk 11>;
 				status = "disabled";
-- 
2.11.0

^ permalink raw reply related	[flat|nested] 75+ messages in thread

* [RFC 08/12] ARM: dts: armada-375: use reworked NAND controller driver
@ 2017-10-18 14:36   ` Miquel Raynal
  0 siblings, 0 replies; 75+ messages in thread
From: Miquel Raynal @ 2017-10-18 14:36 UTC (permalink / raw)
  To: Andrew Lunn, bcm-kernel-feedback-list, Boris Brezillon,
	Brian Norris, Catalin Marinas, Chen-Yu Tsai, Cyrille Pitchen,
	Daniel Mack, David Woodhouse, devel, devicetree, Ezequiel Garcia,
	Greg Kroah-Hartman, Gregory Clement, Han Xu, Haojian Zhuang,
	Jason Cooper, Josh Wu, Kamal Dasu, linux-arm-kernel,
	linux-kernel, linux-mediatek, linux-mtd
  Cc: Hanna Hawa, Antoine Tenart, Nadav Haklai, Neta Zur Hershkovits,
	Igor Grinberg, Miquel Raynal, Ofer Heifetz

Change the bindings to use the reworked Marvell NAND controller driver.
Also adapt the nand controller node organization to distinguish which
property is relevant for the controller, and which one is NAND chip
specific. Expose the partitions as a subnode of the NAND chip.

Remove the 'marvell,nand-enable-arbiter' property, inefective with
Armada boards.

Signed-off-by: Miquel Raynal <miquel.raynal@free-electrons.com>
---
 arch/arm/boot/dts/armada-375-db.dts | 49 ++++++++++++++++++++++---------------
 arch/arm/boot/dts/armada-375.dtsi   |  6 ++---
 2 files changed, 32 insertions(+), 23 deletions(-)

diff --git a/arch/arm/boot/dts/armada-375-db.dts b/arch/arm/boot/dts/armada-375-db.dts
index bcdbb8ba1d65..42277a554063 100644
--- a/arch/arm/boot/dts/armada-375-db.dts
+++ b/arch/arm/boot/dts/armada-375-db.dts
@@ -140,28 +140,37 @@
 	nr-ports = <2>;
 };
 
-&nand {
+&nand_controller {
+	status = "okay";
 	pinctrl-0 = <&nand_pins>;
 	pinctrl-names = "default";
-	status = "okay";
-	num-cs = <1>;
-	marvell,nand-keep-config;
-	marvell,nand-enable-arbiter;
-	nand-on-flash-bbt;
-	nand-ecc-strength = <4>;
-	nand-ecc-step-size = <512>;
-
-	partition@0 {
-		label = "U-Boot";
-		reg = <0 0x800000>;
-	};
-	partition@800000 {
-		label = "Linux";
-		reg = <0x800000 0x800000>;
-	};
-	partition@1000000 {
-		label = "Filesystem";
-		reg = <0x1000000 0x3f000000>;
+
+	nand@0 {
+		reg = <0>;
+		marvell,rb = <0>;
+		marvell,nand-keep-config;
+		nand-on-flash-bbt;
+		nand-ecc-strength = <4>;
+		nand-ecc-step-size = <512>;
+
+		partitions {
+			compatible = "fixed-partitions";
+			#address-cells = <1>;
+			#size-cells = <1>;
+
+			partition@0 {
+				label = "U-Boot";
+				reg = <0 0x800000>;
+			};
+			partition@800000 {
+				label = "Linux";
+				reg = <0x800000 0x800000>;
+			};
+			partition@1000000 {
+				label = "Filesystem";
+				reg = <0x1000000 0x3f000000>;
+			};
+		};
 	};
 };
 
diff --git a/arch/arm/boot/dts/armada-375.dtsi b/arch/arm/boot/dts/armada-375.dtsi
index 7225c7ce9a8d..db66b5f29834 100644
--- a/arch/arm/boot/dts/armada-375.dtsi
+++ b/arch/arm/boot/dts/armada-375.dtsi
@@ -539,11 +539,11 @@
 				status = "disabled";
 			};
 
-			nand: nand@d0000 {
-				compatible = "marvell,armada370-nand";
+			nand_controller: nand-controller@d0000 {
+				compatible = "marvell,armada370-nand-controller";
 				reg = <0xd0000 0x54>;
 				#address-cells = <1>;
-				#size-cells = <1>;
+				#size-cells = <0>;
 				interrupts = <GIC_SPI 84 IRQ_TYPE_LEVEL_HIGH>;
 				clocks = <&gateclk 11>;
 				status = "disabled";
-- 
2.11.0

^ permalink raw reply related	[flat|nested] 75+ messages in thread

* [RFC 08/12] ARM: dts: armada-375: use reworked NAND controller driver
@ 2017-10-18 14:36   ` Miquel Raynal
  0 siblings, 0 replies; 75+ messages in thread
From: Miquel Raynal @ 2017-10-18 14:36 UTC (permalink / raw)
  To: linux-arm-kernel

Change the bindings to use the reworked Marvell NAND controller driver.
Also adapt the nand controller node organization to distinguish which
property is relevant for the controller, and which one is NAND chip
specific. Expose the partitions as a subnode of the NAND chip.

Remove the 'marvell,nand-enable-arbiter' property, inefective with
Armada boards.

Signed-off-by: Miquel Raynal <miquel.raynal@free-electrons.com>
---
 arch/arm/boot/dts/armada-375-db.dts | 49 ++++++++++++++++++++++---------------
 arch/arm/boot/dts/armada-375.dtsi   |  6 ++---
 2 files changed, 32 insertions(+), 23 deletions(-)

diff --git a/arch/arm/boot/dts/armada-375-db.dts b/arch/arm/boot/dts/armada-375-db.dts
index bcdbb8ba1d65..42277a554063 100644
--- a/arch/arm/boot/dts/armada-375-db.dts
+++ b/arch/arm/boot/dts/armada-375-db.dts
@@ -140,28 +140,37 @@
 	nr-ports = <2>;
 };
 
-&nand {
+&nand_controller {
+	status = "okay";
 	pinctrl-0 = <&nand_pins>;
 	pinctrl-names = "default";
-	status = "okay";
-	num-cs = <1>;
-	marvell,nand-keep-config;
-	marvell,nand-enable-arbiter;
-	nand-on-flash-bbt;
-	nand-ecc-strength = <4>;
-	nand-ecc-step-size = <512>;
-
-	partition at 0 {
-		label = "U-Boot";
-		reg = <0 0x800000>;
-	};
-	partition at 800000 {
-		label = "Linux";
-		reg = <0x800000 0x800000>;
-	};
-	partition at 1000000 {
-		label = "Filesystem";
-		reg = <0x1000000 0x3f000000>;
+
+	nand at 0 {
+		reg = <0>;
+		marvell,rb = <0>;
+		marvell,nand-keep-config;
+		nand-on-flash-bbt;
+		nand-ecc-strength = <4>;
+		nand-ecc-step-size = <512>;
+
+		partitions {
+			compatible = "fixed-partitions";
+			#address-cells = <1>;
+			#size-cells = <1>;
+
+			partition at 0 {
+				label = "U-Boot";
+				reg = <0 0x800000>;
+			};
+			partition at 800000 {
+				label = "Linux";
+				reg = <0x800000 0x800000>;
+			};
+			partition at 1000000 {
+				label = "Filesystem";
+				reg = <0x1000000 0x3f000000>;
+			};
+		};
 	};
 };
 
diff --git a/arch/arm/boot/dts/armada-375.dtsi b/arch/arm/boot/dts/armada-375.dtsi
index 7225c7ce9a8d..db66b5f29834 100644
--- a/arch/arm/boot/dts/armada-375.dtsi
+++ b/arch/arm/boot/dts/armada-375.dtsi
@@ -539,11 +539,11 @@
 				status = "disabled";
 			};
 
-			nand: nand at d0000 {
-				compatible = "marvell,armada370-nand";
+			nand_controller: nand-controller at d0000 {
+				compatible = "marvell,armada370-nand-controller";
 				reg = <0xd0000 0x54>;
 				#address-cells = <1>;
-				#size-cells = <1>;
+				#size-cells = <0>;
 				interrupts = <GIC_SPI 84 IRQ_TYPE_LEVEL_HIGH>;
 				clocks = <&gateclk 11>;
 				status = "disabled";
-- 
2.11.0

^ permalink raw reply related	[flat|nested] 75+ messages in thread

* [RFC 09/12] ARM: dts: armada-38x: use reworked NAND controller driver
  2017-10-18 14:36 ` Miquel Raynal
  (?)
@ 2017-10-18 14:36   ` Miquel Raynal
  -1 siblings, 0 replies; 75+ messages in thread
From: Miquel Raynal @ 2017-10-18 14:36 UTC (permalink / raw)
  To: Andrew Lunn, bcm-kernel-feedback-list, Boris Brezillon,
	Brian Norris, Catalin Marinas, Chen-Yu Tsai, Cyrille Pitchen,
	Daniel Mack, David Woodhouse, devel, devicetree, Ezequiel Garcia,
	Greg Kroah-Hartman, Gregory Clement, Han Xu, Haojian Zhuang,
	Jason Cooper, Josh Wu, Kamal Dasu, linux-arm-kernel,
	linux-kernel, linux-mediatek, linux-mtd, Marc Gonzalez,
	Marek Vasut, Mark Rutland, Masahiro Yamada, Matthias Brugger,
	Maxime Ripard, Maxim Levitsky, Richard Weinberger,
	Robert Jarzmik, Rob Herring, Russell King, Sebastian Hesselbarth,
	Stefan Agner, Sylvain Lemieux, Vladimir Zapolskiy, Wenyou Yang,
	Will Deacon
  Cc: Thomas Petazzoni, Antoine Tenart, Miquel Raynal, Igor Grinberg,
	Nadav Haklai, Ofer Heifetz, Neta Zur Hershkovits, Hanna Hawa

Change the bindings to use the reworked Marvell NAND controller driver.
Also adapt the nand controller node organization to distinguish which
property is relevant for the controller, and which one is NAND chip
specific. Expose the partitions as a subnode of the NAND chip.

Remove the 'marvell,nand-enable-arbiter' property, inefective with
Armada boards.

Signed-off-by: Miquel Raynal <miquel.raynal@free-electrons.com>
---
 arch/arm/boot/dts/armada-385-db-ap.dts          |  68 ++++++------
 arch/arm/boot/dts/armada-385-linksys-caiman.dts | 129 +++++++++++-----------
 arch/arm/boot/dts/armada-385-linksys-cobra.dts  | 129 +++++++++++-----------
 arch/arm/boot/dts/armada-385-linksys-rango.dts  | 141 ++++++++++++------------
 arch/arm/boot/dts/armada-385-linksys-shelby.dts | 129 +++++++++++-----------
 arch/arm/boot/dts/armada-385-linksys.dtsi       |  15 ++-
 arch/arm/boot/dts/armada-388-db.dts             |  54 +++++----
 arch/arm/boot/dts/armada-38x.dtsi               |   6 +-
 8 files changed, 353 insertions(+), 318 deletions(-)

diff --git a/arch/arm/boot/dts/armada-385-db-ap.dts b/arch/arm/boot/dts/armada-385-db-ap.dts
index 25d2d720dc0e..625691196acb 100644
--- a/arch/arm/boot/dts/armada-385-db-ap.dts
+++ b/arch/arm/boot/dts/armada-385-db-ap.dts
@@ -166,39 +166,6 @@
 				status = "okay";
 			};
 
-			nfc: flash@d0000 {
-				status = "okay";
-				num-cs = <1>;
-				nand-ecc-strength = <4>;
-				nand-ecc-step-size = <512>;
-				marvell,nand-keep-config;
-				marvell,nand-enable-arbiter;
-				nand-on-flash-bbt;
-
-				partitions {
-					compatible = "fixed-partitions";
-					#address-cells = <1>;
-					#size-cells = <1>;
-
-					partition@0 {
-						label = "U-Boot";
-						reg = <0x00000000 0x00800000>;
-						read-only;
-					};
-
-					partition@800000 {
-						label = "uImage";
-						reg = <0x00800000 0x00400000>;
-						read-only;
-					};
-
-					partition@c00000 {
-						label = "Root";
-						reg = <0x00c00000 0x3f400000>;
-					};
-				};
-			};
-
 			usb3@f0000 {
 				status = "okay";
 				usb-phy = <&usb3_phy>;
@@ -263,3 +230,38 @@
 		spi-max-frequency = <54000000>;
 	};
 };
+
+&nand_controller {
+	status = "okay";
+
+	nand@0 {
+		reg = <0>;
+		marvell,rb = <0>;
+		nand-on-flash-bbt;
+		nand-ecc-strength = <4>;
+		nand-ecc-step-size = <512>;
+
+		partitions {
+			compatible = "fixed-partitions";
+			#address-cells = <1>;
+			#size-cells = <1>;
+
+			partition@0 {
+				label = "U-Boot";
+				reg = <0x00000000 0x00800000>;
+				read-only;
+			};
+
+			partition@800000 {
+				label = "uImage";
+				reg = <0x00800000 0x00400000>;
+				read-only;
+			};
+
+			partition@c00000 {
+				label = "Root";
+				reg = <0x00c00000 0x3f400000>;
+			};
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/armada-385-linksys-caiman.dts b/arch/arm/boot/dts/armada-385-linksys-caiman.dts
index ee669ae61011..e769bcf7a9d1 100644
--- a/arch/arm/boot/dts/armada-385-linksys-caiman.dts
+++ b/arch/arm/boot/dts/armada-385-linksys-caiman.dts
@@ -105,67 +105,72 @@
 
 &nand {
 	/* 128MiB */
-
-	partition@0 {
-		label = "u-boot";
-		reg = <0x0000000 0x200000>;  /* 2MiB */
-		read-only;
-	};
-
-	partition@100000 {
-		label = "u_env";
-		reg = <0x200000 0x40000>;    /* 256KiB */
-	};
-
-	partition@140000 {
-		label = "s_env";
-		reg = <0x240000 0x40000>;    /* 256KiB */
-	};
-
-	partition@900000 {
-		label = "devinfo";
-		reg = <0x900000 0x100000>;   /* 1MiB */
-		read-only;
-	};
-
-	/* kernel1 overlaps with rootfs1 by design */
-	partition@a00000 {
-		label = "kernel1";
-		reg = <0xa00000 0x2800000>;  /* 40MiB */
-	};
-
-	partition@1000000 {
-		label = "rootfs1";
-		reg = <0x1000000 0x2200000>;  /* 34MiB */
-	};
-
-	/* kernel2 overlaps with rootfs2 by design */
-	partition@3200000 {
-		label = "kernel2";
-		reg = <0x3200000 0x2800000>; /* 40MiB */
-	};
-
-	partition@3800000 {
-		label = "rootfs2";
-		reg = <0x3800000 0x2200000>; /* 34MiB */
-	};
-
-	/*
-	 * 38MiB, last MiB is for the BBT, not writable
-	 */
-	partition@5a00000 {
-		label = "syscfg";
-		reg = <0x5a00000 0x2600000>;
-	};
-
-	/*
-	 * Unused area between "s_env" and "devinfo".
-	 * Moved here because otherwise the renumbered
-	 * partitions would break the bootloader
-	 * supplied bootargs
-	 */
-	partition@180000 {
-		label = "unused_area";
-		reg = <0x280000 0x680000>;   /* 6.5MiB */
+	partitions {
+		compatible = "fixed-partitions";
+		#address-cells = <1>;
+		#size-cells = <1>;
+
+		partition@0 {
+			label = "u-boot";
+			reg = <0x0000000 0x200000>;  /* 2MiB */
+			read-only;
+		};
+
+		partition@100000 {
+			label = "u_env";
+			reg = <0x200000 0x40000>;    /* 256KiB */
+		};
+
+		partition@140000 {
+			label = "s_env";
+			reg = <0x240000 0x40000>;    /* 256KiB */
+		};
+
+		partition@900000 {
+			label = "devinfo";
+			reg = <0x900000 0x100000>;   /* 1MiB */
+			read-only;
+		};
+
+		/* kernel1 overlaps with rootfs1 by design */
+		partition@a00000 {
+			label = "kernel1";
+			reg = <0xa00000 0x2800000>;  /* 40MiB */
+		};
+
+		partition@1000000 {
+			label = "rootfs1";
+			reg = <0x1000000 0x2200000>;  /* 34MiB */
+		};
+
+		/* kernel2 overlaps with rootfs2 by design */
+		partition@3200000 {
+			label = "kernel2";
+			reg = <0x3200000 0x2800000>; /* 40MiB */
+		};
+
+		partition@3800000 {
+			label = "rootfs2";
+			reg = <0x3800000 0x2200000>; /* 34MiB */
+		};
+
+		/*
+		 * 38MiB, last MiB is for the BBT, not writable
+		 */
+		partition@5a00000 {
+			label = "syscfg";
+			reg = <0x5a00000 0x2600000>;
+		};
+
+		/*
+		 * Unused area between "s_env" and "devinfo".
+		 * Moved here because otherwise the renumbered
+		 * partitions would break the bootloader
+		 * supplied bootargs
+		 */
+		partition@180000 {
+			label = "unused_area";
+			reg = <0x280000 0x680000>;   /* 6.5MiB */
+		};
 	};
 };
diff --git a/arch/arm/boot/dts/armada-385-linksys-cobra.dts b/arch/arm/boot/dts/armada-385-linksys-cobra.dts
index 5169ca89c55a..690be217838c 100644
--- a/arch/arm/boot/dts/armada-385-linksys-cobra.dts
+++ b/arch/arm/boot/dts/armada-385-linksys-cobra.dts
@@ -105,67 +105,72 @@
 
 &nand {
 	/* 128MiB */
-
-	partition@0 {
-		label = "u-boot";
-		reg = <0x0000000 0x200000>;  /* 2MiB */
-		read-only;
-	};
-
-	partition@100000 {
-		label = "u_env";
-		reg = <0x200000 0x40000>;    /* 256KiB */
-	};
-
-	partition@140000 {
-		label = "s_env";
-		reg = <0x240000 0x40000>;    /* 256KiB */
-	};
-
-	partition@900000 {
-		label = "devinfo";
-		reg = <0x900000 0x100000>;   /* 1MiB */
-		read-only;
-	};
-
-	/* kernel1 overlaps with rootfs1 by design */
-	partition@a00000 {
-		label = "kernel1";
-		reg = <0xa00000 0x2800000>;  /* 40MiB */
-	};
-
-	partition@1000000 {
-		label = "rootfs1";
-		reg = <0x1000000 0x2200000>;  /* 34MiB */
-	};
-
-	/* kernel2 overlaps with rootfs2 by design */
-	partition@3200000 {
-		label = "kernel2";
-		reg = <0x3200000 0x2800000>; /* 40MiB */
-	};
-
-	partition@3800000 {
-		label = "rootfs2";
-		reg = <0x3800000 0x2200000>; /* 34MiB */
-	};
-
-	/*
-	 * 38MiB, last MiB is for the BBT, not writable
-	 */
-	partition@5a00000 {
-		label = "syscfg";
-		reg = <0x5a00000 0x2600000>;
-	};
-
-	/*
-	 * Unused area between "s_env" and "devinfo".
-	 * Moved here because otherwise the renumbered
-	 * partitions would break the bootloader
-	 * supplied bootargs
-	 */
-	partition@180000 {
-		label = "unused_area";
-		reg = <0x280000 0x680000>;   /* 6.5MiB */
+	partitions {
+		compatible = "fixed-partitions";
+		#address-cells = <1>;
+		#size-cells = <1>;
+
+		partition@0 {
+			label = "u-boot";
+			reg = <0x0000000 0x200000>;  /* 2MiB */
+			read-only;
+		};
+
+		partition@100000 {
+			label = "u_env";
+			reg = <0x200000 0x40000>;    /* 256KiB */
+		};
+
+		partition@140000 {
+			label = "s_env";
+			reg = <0x240000 0x40000>;    /* 256KiB */
+		};
+
+		partition@900000 {
+			label = "devinfo";
+			reg = <0x900000 0x100000>;   /* 1MiB */
+			read-only;
+		};
+
+		/* kernel1 overlaps with rootfs1 by design */
+		partition@a00000 {
+			label = "kernel1";
+			reg = <0xa00000 0x2800000>;  /* 40MiB */
+		};
+
+		partition@1000000 {
+			label = "rootfs1";
+			reg = <0x1000000 0x2200000>;  /* 34MiB */
+		};
+
+		/* kernel2 overlaps with rootfs2 by design */
+		partition@3200000 {
+			label = "kernel2";
+			reg = <0x3200000 0x2800000>; /* 40MiB */
+		};
+
+		partition@3800000 {
+			label = "rootfs2";
+			reg = <0x3800000 0x2200000>; /* 34MiB */
+		};
+
+		/*
+		 * 38MiB, last MiB is for the BBT, not writable
+		 */
+		partition@5a00000 {
+			label = "syscfg";
+			reg = <0x5a00000 0x2600000>;
+		};
+
+		/*
+		 * Unused area between "s_env" and "devinfo".
+		 * Moved here because otherwise the renumbered
+		 * partitions would break the bootloader
+		 * supplied bootargs
+		 */
+		partition@180000 {
+			label = "unused_area";
+			reg = <0x280000 0x680000>;   /* 6.5MiB */
+		};
 	};
 };
diff --git a/arch/arm/boot/dts/armada-385-linksys-rango.dts b/arch/arm/boot/dts/armada-385-linksys-rango.dts
index da8a0f3d432b..93570c6bfe85 100644
--- a/arch/arm/boot/dts/armada-385-linksys-rango.dts
+++ b/arch/arm/boot/dts/armada-385-linksys-rango.dts
@@ -113,74 +113,79 @@
 
 &nand {
 	/* AMD/Spansion S34ML02G2 256MiB, OEM Layout */
-
-	partition@0 {
-		label = "u-boot";
-		reg = <0x0000000 0x200000>;  /* 2MiB */
-		read-only;
-	};
-
-	partition@200000 {
-		label = "u_env";
-		reg = <0x200000 0x20000>;    /* 128KiB */
-	};
-
-	partition@220000 {
-		label = "s_env";
-		reg = <0x220000 0x40000>;    /* 256KiB */
-	};
-
-	partition@7e0000 {
-		label = "devinfo";
-		reg = <0x7e0000 0x40000>;   /* 256KiB */
-		read-only;
-	};
-
-	partition@820000 {
-		label = "sysdiag";
-		reg = <0x820000 0x1e0000>;   /* 1920KiB */
-		read-only;
-	};
-
-	/* kernel1 overlaps with rootfs1 by design */
-	partition@a00000 {
-		label = "kernel1";
-		reg = <0xa00000 0x5000000>;  /* 80MiB */
-	};
-
-	partition@1000000 {
-		label = "rootfs1";
-		reg = <0x1000000 0x4a00000>;  /* 74MiB */
-	};
-
-	/* kernel2 overlaps with rootfs2 by design */
-	partition@5a00000 {
-		label = "kernel2";
-		reg = <0x5a00000 0x5000000>; /* 80MiB */
-	};
-
-	partition@6000000 {
-		label = "rootfs2";
-		reg = <0x6000000 0x4a00000>; /* 74MiB */
-	};
-
-	/*
-	 * 86MiB, last MiB is for the BBT, not writable
-	 */
-	partition@aa00000 {
-		label = "syscfg";
-		reg = <0xaa00000 0x5600000>;
-	};
-
-	/*
-	 * Unused area between "s_env" and "devinfo".
-	 * Moved here because otherwise the renumbered
-	 * partitions would break the bootloader
-	 * supplied bootargs
-	 */
-	partition@180000 {
-		label = "unused_area";
-		reg = <0x260000 0x5c0000>;   /* 5.75MiB */
+	partitions {
+		compatible = "fixed-partitions";
+		#address-cells = <1>;
+		#size-cells = <1>;
+
+		partition@0 {
+			label = "u-boot";
+			reg = <0x0000000 0x200000>;  /* 2MiB */
+			read-only;
+		};
+
+		partition@200000 {
+			label = "u_env";
+			reg = <0x200000 0x20000>;    /* 128KiB */
+		};
+
+		partition@220000 {
+			label = "s_env";
+			reg = <0x220000 0x40000>;    /* 256KiB */
+		};
+
+		partition@7e0000 {
+			label = "devinfo";
+			reg = <0x7e0000 0x40000>;   /* 256KiB */
+			read-only;
+		};
+
+		partition@820000 {
+			label = "sysdiag";
+			reg = <0x820000 0x1e0000>;   /* 1920KiB */
+			read-only;
+		};
+
+		/* kernel1 overlaps with rootfs1 by design */
+		partition@a00000 {
+			label = "kernel1";
+			reg = <0xa00000 0x5000000>;  /* 80MiB */
+		};
+
+		partition@1000000 {
+			label = "rootfs1";
+			reg = <0x1000000 0x4a00000>;  /* 74MiB */
+		};
+
+		/* kernel2 overlaps with rootfs2 by design */
+		partition@5a00000 {
+			label = "kernel2";
+			reg = <0x5a00000 0x5000000>; /* 80MiB */
+		};
+
+		partition@6000000 {
+			label = "rootfs2";
+			reg = <0x6000000 0x4a00000>; /* 74MiB */
+		};
+
+		/*
+		 * 86MiB, last MiB is for the BBT, not writable
+		 */
+		partition@aa00000 {
+			label = "syscfg";
+			reg = <0xaa00000 0x5600000>;
+		};
+
+		/*
+		 * Unused area between "s_env" and "devinfo".
+		 * Moved here because otherwise the renumbered
+		 * partitions would break the bootloader
+		 * supplied bootargs
+		 */
+		partition@180000 {
+			label = "unused_area";
+			reg = <0x260000 0x5c0000>;   /* 5.75MiB */
+		};
 	};
 };
 
diff --git a/arch/arm/boot/dts/armada-385-linksys-shelby.dts b/arch/arm/boot/dts/armada-385-linksys-shelby.dts
index 94aa35bc0bff..4694556700ff 100644
--- a/arch/arm/boot/dts/armada-385-linksys-shelby.dts
+++ b/arch/arm/boot/dts/armada-385-linksys-shelby.dts
@@ -105,67 +105,72 @@
 
 &nand {
 	/* 128MiB */
-
-	partition@0 {
-		label = "u-boot";
-		reg = <0x0000000 0x200000>;  /* 2MiB */
-		read-only;
-	};
-
-	partition@100000 {
-		label = "u_env";
-		reg = <0x200000 0x40000>;    /* 256KiB */
-	};
-
-	partition@140000 {
-		label = "s_env";
-		reg = <0x240000 0x40000>;    /* 256KiB */
-	};
-
-	partition@900000 {
-		label = "devinfo";
-		reg = <0x900000 0x100000>;   /* 1MiB */
-		read-only;
-	};
-
-	/* kernel1 overlaps with rootfs1 by design */
-	partition@a00000 {
-		label = "kernel1";
-		reg = <0xa00000 0x2800000>;  /* 40MiB */
-	};
-
-	partition@1000000 {
-		label = "rootfs1";
-		reg = <0x1000000 0x2200000>;  /* 34MiB */
-	};
-
-	/* kernel2 overlaps with rootfs2 by design */
-	partition@3200000 {
-		label = "kernel2";
-		reg = <0x3200000 0x2800000>; /* 40MiB */
-	};
-
-	partition@3800000 {
-		label = "rootfs2";
-		reg = <0x3800000 0x2200000>; /* 34MiB */
-	};
-
-	/*
-	 * 38MiB, last MiB is for the BBT, not writable
-	 */
-	partition@5a00000 {
-		label = "syscfg";
-		reg = <0x5a00000 0x2600000>;
-	};
-
-	/*
-	 * Unused area between "s_env" and "devinfo".
-	 * Moved here because otherwise the renumbered
-	 * partitions would break the bootloader
-	 * supplied bootargs
-	 */
-	partition@180000 {
-		label = "unused_area";
-		reg = <0x280000 0x680000>;   /* 6.5MiB */
+	partitions {
+		compatible = "fixed-partitions";
+		#address-cells = <1>;
+		#size-cells = <1>;
+
+		partition@0 {
+			label = "u-boot";
+			reg = <0x0000000 0x200000>;  /* 2MiB */
+			read-only;
+		};
+
+		partition@100000 {
+			label = "u_env";
+			reg = <0x200000 0x40000>;    /* 256KiB */
+		};
+
+		partition@140000 {
+			label = "s_env";
+			reg = <0x240000 0x40000>;    /* 256KiB */
+		};
+
+		partition@900000 {
+			label = "devinfo";
+			reg = <0x900000 0x100000>;   /* 1MiB */
+			read-only;
+		};
+
+		/* kernel1 overlaps with rootfs1 by design */
+		partition@a00000 {
+			label = "kernel1";
+			reg = <0xa00000 0x2800000>;  /* 40MiB */
+		};
+
+		partition@1000000 {
+			label = "rootfs1";
+			reg = <0x1000000 0x2200000>;  /* 34MiB */
+		};
+
+		/* kernel2 overlaps with rootfs2 by design */
+		partition@3200000 {
+			label = "kernel2";
+			reg = <0x3200000 0x2800000>; /* 40MiB */
+		};
+
+		partition@3800000 {
+			label = "rootfs2";
+			reg = <0x3800000 0x2200000>; /* 34MiB */
+		};
+
+		/*
+		 * 38MiB, last MiB is for the BBT, not writable
+		 */
+		partition@5a00000 {
+			label = "syscfg";
+			reg = <0x5a00000 0x2600000>;
+		};
+
+		/*
+		 * Unused area between "s_env" and "devinfo".
+		 * Moved here because otherwise the renumbered
+		 * partitions would break the bootloader
+		 * supplied bootargs
+		 */
+		partition@180000 {
+			label = "unused_area";
+			reg = <0x280000 0x680000>;   /* 6.5MiB */
+		};
 	};
 };
diff --git a/arch/arm/boot/dts/armada-385-linksys.dtsi b/arch/arm/boot/dts/armada-385-linksys.dtsi
index e1f355ffc8f7..358b17e921bb 100644
--- a/arch/arm/boot/dts/armada-385-linksys.dtsi
+++ b/arch/arm/boot/dts/armada-385-linksys.dtsi
@@ -169,13 +169,18 @@
 	};
 };
 
-&nand {
+&nand_controller {
 	/* 128MiB or 256MiB */
 	status = "okay";
-	num-cs = <1>;
-	marvell,nand-keep-config;
-	marvell,nand-enable-arbiter;
-	nand-on-flash-bbt;
+	#address-cells = <1>;
+	#size-cells = <0>;
+
+	nand: nand@0 {
+		reg = <0>;
+		marvell,rb = <0>;
+		marvell,nand-keep-config;
+		nand-on-flash-bbt;
+	};
 };
 
 &mdio {
diff --git a/arch/arm/boot/dts/armada-388-db.dts b/arch/arm/boot/dts/armada-388-db.dts
index a4ec1fa37529..0c020d7afcc1 100644
--- a/arch/arm/boot/dts/armada-388-db.dts
+++ b/arch/arm/boot/dts/armada-388-db.dts
@@ -128,29 +128,6 @@
 				status = "okay";
 			};
 
-			flash@d0000 {
-				status = "okay";
-				num-cs = <1>;
-				marvell,nand-keep-config;
-				marvell,nand-enable-arbiter;
-				nand-on-flash-bbt;
-				nand-ecc-strength = <4>;
-				nand-ecc-step-size = <512>;
-
-				partition@0 {
-					label = "U-Boot";
-					reg = <0 0x800000>;
-				};
-				partition@800000 {
-					label = "Linux";
-					reg = <0x800000 0x800000>;
-				};
-				partition@1000000 {
-					label = "Filesystem";
-					reg = <0x1000000 0x3f000000>;
-				};
-			};
-
 			sdhci@d8000 {
 				broken-cd;
 				wp-inverted;
@@ -202,3 +179,34 @@
 	};
 };
 
+&nand_controller {
+	status = "okay";
+
+	nand@0 {
+		reg = <0>;
+		marvell,rb = <0>;
+		marvell,nand-keep-config;
+		nand-on-flash-bbt;
+		nand-ecc-strength = <4>;
+		nand-ecc-step-size = <512>;
+
+		partitions {
+			compatible = "fixed-partitions";
+			#address-cells = <1>;
+			#size-cells = <1>;
+
+			partition@0 {
+				label = "U-Boot";
+				reg = <0 0x800000>;
+			};
+			partition@800000 {
+				label = "Linux";
+				reg = <0x800000 0x800000>;
+			};
+			partition@1000000 {
+				label = "Filesystem";
+				reg = <0x1000000 0x3f000000>;
+			};
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/armada-38x.dtsi b/arch/arm/boot/dts/armada-38x.dtsi
index 7ff0811e61db..764fcf08ef53 100644
--- a/arch/arm/boot/dts/armada-38x.dtsi
+++ b/arch/arm/boot/dts/armada-38x.dtsi
@@ -583,11 +583,11 @@
 				status = "okay";
 			};
 
-			nand: flash@d0000 {
-				compatible = "marvell,armada370-nand";
+			nand_controller: nand-controller@d0000 {
+				compatible = "marvell,armada370-nand-controller";
 				reg = <0xd0000 0x54>;
 				#address-cells = <1>;
-				#size-cells = <1>;
+				#size-cells = <0>;
 				interrupts = <GIC_SPI 84 IRQ_TYPE_LEVEL_HIGH>;
 				clocks = <&coredivclk 0>;
 				status = "disabled";
-- 
2.11.0

^ permalink raw reply related	[flat|nested] 75+ messages in thread

* [RFC 09/12] ARM: dts: armada-38x: use reworked NAND controller driver
@ 2017-10-18 14:36   ` Miquel Raynal
  0 siblings, 0 replies; 75+ messages in thread
From: Miquel Raynal @ 2017-10-18 14:36 UTC (permalink / raw)
  To: Andrew Lunn, bcm-kernel-feedback-list, Boris Brezillon,
	Brian Norris, Catalin Marinas, Chen-Yu Tsai, Cyrille Pitchen,
	Daniel Mack, David Woodhouse, devel, devicetree, Ezequiel Garcia,
	Greg Kroah-Hartman, Gregory Clement, Han Xu, Haojian Zhuang,
	Jason Cooper, Josh Wu, Kamal Dasu, linux-arm-kernel,
	linux-kernel, linux-mediatek, linux-mtd
  Cc: Hanna Hawa, Antoine Tenart, Nadav Haklai, Neta Zur Hershkovits,
	Igor Grinberg, Miquel Raynal, Ofer Heifetz

Change the bindings to use the reworked Marvell NAND controller driver.
Also adapt the nand controller node organization to distinguish which
property is relevant for the controller, and which one is NAND chip
specific. Expose the partitions as a subnode of the NAND chip.

Remove the 'marvell,nand-enable-arbiter' property, inefective with
Armada boards.

Signed-off-by: Miquel Raynal <miquel.raynal@free-electrons.com>
---
 arch/arm/boot/dts/armada-385-db-ap.dts          |  68 ++++++------
 arch/arm/boot/dts/armada-385-linksys-caiman.dts | 129 +++++++++++-----------
 arch/arm/boot/dts/armada-385-linksys-cobra.dts  | 129 +++++++++++-----------
 arch/arm/boot/dts/armada-385-linksys-rango.dts  | 141 ++++++++++++------------
 arch/arm/boot/dts/armada-385-linksys-shelby.dts | 129 +++++++++++-----------
 arch/arm/boot/dts/armada-385-linksys.dtsi       |  15 ++-
 arch/arm/boot/dts/armada-388-db.dts             |  54 +++++----
 arch/arm/boot/dts/armada-38x.dtsi               |   6 +-
 8 files changed, 353 insertions(+), 318 deletions(-)

diff --git a/arch/arm/boot/dts/armada-385-db-ap.dts b/arch/arm/boot/dts/armada-385-db-ap.dts
index 25d2d720dc0e..625691196acb 100644
--- a/arch/arm/boot/dts/armada-385-db-ap.dts
+++ b/arch/arm/boot/dts/armada-385-db-ap.dts
@@ -166,39 +166,6 @@
 				status = "okay";
 			};
 
-			nfc: flash@d0000 {
-				status = "okay";
-				num-cs = <1>;
-				nand-ecc-strength = <4>;
-				nand-ecc-step-size = <512>;
-				marvell,nand-keep-config;
-				marvell,nand-enable-arbiter;
-				nand-on-flash-bbt;
-
-				partitions {
-					compatible = "fixed-partitions";
-					#address-cells = <1>;
-					#size-cells = <1>;
-
-					partition@0 {
-						label = "U-Boot";
-						reg = <0x00000000 0x00800000>;
-						read-only;
-					};
-
-					partition@800000 {
-						label = "uImage";
-						reg = <0x00800000 0x00400000>;
-						read-only;
-					};
-
-					partition@c00000 {
-						label = "Root";
-						reg = <0x00c00000 0x3f400000>;
-					};
-				};
-			};
-
 			usb3@f0000 {
 				status = "okay";
 				usb-phy = <&usb3_phy>;
@@ -263,3 +230,38 @@
 		spi-max-frequency = <54000000>;
 	};
 };
+
+&nand_controller {
+	status = "okay";
+
+	nand@0 {
+		reg = <0>;
+		marvell,rb = <0>;
+		nand-on-flash-bbt;
+		nand-ecc-strength = <4>;
+		nand-ecc-step-size = <512>;
+
+		partitions {
+			compatible = "fixed-partitions";
+			#address-cells = <1>;
+			#size-cells = <1>;
+
+			partition@0 {
+				label = "U-Boot";
+				reg = <0x00000000 0x00800000>;
+				read-only;
+			};
+
+			partition@800000 {
+				label = "uImage";
+				reg = <0x00800000 0x00400000>;
+				read-only;
+			};
+
+			partition@c00000 {
+				label = "Root";
+				reg = <0x00c00000 0x3f400000>;
+			};
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/armada-385-linksys-caiman.dts b/arch/arm/boot/dts/armada-385-linksys-caiman.dts
index ee669ae61011..e769bcf7a9d1 100644
--- a/arch/arm/boot/dts/armada-385-linksys-caiman.dts
+++ b/arch/arm/boot/dts/armada-385-linksys-caiman.dts
@@ -105,67 +105,72 @@
 
 &nand {
 	/* 128MiB */
-
-	partition@0 {
-		label = "u-boot";
-		reg = <0x0000000 0x200000>;  /* 2MiB */
-		read-only;
-	};
-
-	partition@100000 {
-		label = "u_env";
-		reg = <0x200000 0x40000>;    /* 256KiB */
-	};
-
-	partition@140000 {
-		label = "s_env";
-		reg = <0x240000 0x40000>;    /* 256KiB */
-	};
-
-	partition@900000 {
-		label = "devinfo";
-		reg = <0x900000 0x100000>;   /* 1MiB */
-		read-only;
-	};
-
-	/* kernel1 overlaps with rootfs1 by design */
-	partition@a00000 {
-		label = "kernel1";
-		reg = <0xa00000 0x2800000>;  /* 40MiB */
-	};
-
-	partition@1000000 {
-		label = "rootfs1";
-		reg = <0x1000000 0x2200000>;  /* 34MiB */
-	};
-
-	/* kernel2 overlaps with rootfs2 by design */
-	partition@3200000 {
-		label = "kernel2";
-		reg = <0x3200000 0x2800000>; /* 40MiB */
-	};
-
-	partition@3800000 {
-		label = "rootfs2";
-		reg = <0x3800000 0x2200000>; /* 34MiB */
-	};
-
-	/*
-	 * 38MiB, last MiB is for the BBT, not writable
-	 */
-	partition@5a00000 {
-		label = "syscfg";
-		reg = <0x5a00000 0x2600000>;
-	};
-
-	/*
-	 * Unused area between "s_env" and "devinfo".
-	 * Moved here because otherwise the renumbered
-	 * partitions would break the bootloader
-	 * supplied bootargs
-	 */
-	partition@180000 {
-		label = "unused_area";
-		reg = <0x280000 0x680000>;   /* 6.5MiB */
+	partitions {
+		compatible = "fixed-partitions";
+		#address-cells = <1>;
+		#size-cells = <1>;
+
+		partition@0 {
+			label = "u-boot";
+			reg = <0x0000000 0x200000>;  /* 2MiB */
+			read-only;
+		};
+
+		partition@100000 {
+			label = "u_env";
+			reg = <0x200000 0x40000>;    /* 256KiB */
+		};
+
+		partition@140000 {
+			label = "s_env";
+			reg = <0x240000 0x40000>;    /* 256KiB */
+		};
+
+		partition@900000 {
+			label = "devinfo";
+			reg = <0x900000 0x100000>;   /* 1MiB */
+			read-only;
+		};
+
+		/* kernel1 overlaps with rootfs1 by design */
+		partition@a00000 {
+			label = "kernel1";
+			reg = <0xa00000 0x2800000>;  /* 40MiB */
+		};
+
+		partition@1000000 {
+			label = "rootfs1";
+			reg = <0x1000000 0x2200000>;  /* 34MiB */
+		};
+
+		/* kernel2 overlaps with rootfs2 by design */
+		partition@3200000 {
+			label = "kernel2";
+			reg = <0x3200000 0x2800000>; /* 40MiB */
+		};
+
+		partition@3800000 {
+			label = "rootfs2";
+			reg = <0x3800000 0x2200000>; /* 34MiB */
+		};
+
+		/*
+		 * 38MiB, last MiB is for the BBT, not writable
+		 */
+		partition@5a00000 {
+			label = "syscfg";
+			reg = <0x5a00000 0x2600000>;
+		};
+
+		/*
+		 * Unused area between "s_env" and "devinfo".
+		 * Moved here because otherwise the renumbered
+		 * partitions would break the bootloader
+		 * supplied bootargs
+		 */
+		partition@180000 {
+			label = "unused_area";
+			reg = <0x280000 0x680000>;   /* 6.5MiB */
+		};
 	};
 };
diff --git a/arch/arm/boot/dts/armada-385-linksys-cobra.dts b/arch/arm/boot/dts/armada-385-linksys-cobra.dts
index 5169ca89c55a..690be217838c 100644
--- a/arch/arm/boot/dts/armada-385-linksys-cobra.dts
+++ b/arch/arm/boot/dts/armada-385-linksys-cobra.dts
@@ -105,67 +105,72 @@
 
 &nand {
 	/* 128MiB */
-
-	partition@0 {
-		label = "u-boot";
-		reg = <0x0000000 0x200000>;  /* 2MiB */
-		read-only;
-	};
-
-	partition@100000 {
-		label = "u_env";
-		reg = <0x200000 0x40000>;    /* 256KiB */
-	};
-
-	partition@140000 {
-		label = "s_env";
-		reg = <0x240000 0x40000>;    /* 256KiB */
-	};
-
-	partition@900000 {
-		label = "devinfo";
-		reg = <0x900000 0x100000>;   /* 1MiB */
-		read-only;
-	};
-
-	/* kernel1 overlaps with rootfs1 by design */
-	partition@a00000 {
-		label = "kernel1";
-		reg = <0xa00000 0x2800000>;  /* 40MiB */
-	};
-
-	partition@1000000 {
-		label = "rootfs1";
-		reg = <0x1000000 0x2200000>;  /* 34MiB */
-	};
-
-	/* kernel2 overlaps with rootfs2 by design */
-	partition@3200000 {
-		label = "kernel2";
-		reg = <0x3200000 0x2800000>; /* 40MiB */
-	};
-
-	partition@3800000 {
-		label = "rootfs2";
-		reg = <0x3800000 0x2200000>; /* 34MiB */
-	};
-
-	/*
-	 * 38MiB, last MiB is for the BBT, not writable
-	 */
-	partition@5a00000 {
-		label = "syscfg";
-		reg = <0x5a00000 0x2600000>;
-	};
-
-	/*
-	 * Unused area between "s_env" and "devinfo".
-	 * Moved here because otherwise the renumbered
-	 * partitions would break the bootloader
-	 * supplied bootargs
-	 */
-	partition@180000 {
-		label = "unused_area";
-		reg = <0x280000 0x680000>;   /* 6.5MiB */
+	partitions {
+		compatible = "fixed-partitions";
+		#address-cells = <1>;
+		#size-cells = <1>;
+
+		partition@0 {
+			label = "u-boot";
+			reg = <0x0000000 0x200000>;  /* 2MiB */
+			read-only;
+		};
+
+		partition@100000 {
+			label = "u_env";
+			reg = <0x200000 0x40000>;    /* 256KiB */
+		};
+
+		partition@140000 {
+			label = "s_env";
+			reg = <0x240000 0x40000>;    /* 256KiB */
+		};
+
+		partition@900000 {
+			label = "devinfo";
+			reg = <0x900000 0x100000>;   /* 1MiB */
+			read-only;
+		};
+
+		/* kernel1 overlaps with rootfs1 by design */
+		partition@a00000 {
+			label = "kernel1";
+			reg = <0xa00000 0x2800000>;  /* 40MiB */
+		};
+
+		partition@1000000 {
+			label = "rootfs1";
+			reg = <0x1000000 0x2200000>;  /* 34MiB */
+		};
+
+		/* kernel2 overlaps with rootfs2 by design */
+		partition@3200000 {
+			label = "kernel2";
+			reg = <0x3200000 0x2800000>; /* 40MiB */
+		};
+
+		partition@3800000 {
+			label = "rootfs2";
+			reg = <0x3800000 0x2200000>; /* 34MiB */
+		};
+
+		/*
+		 * 38MiB, last MiB is for the BBT, not writable
+		 */
+		partition@5a00000 {
+			label = "syscfg";
+			reg = <0x5a00000 0x2600000>;
+		};
+
+		/*
+		 * Unused area between "s_env" and "devinfo".
+		 * Moved here because otherwise the renumbered
+		 * partitions would break the bootloader
+		 * supplied bootargs
+		 */
+		partition@180000 {
+			label = "unused_area";
+			reg = <0x280000 0x680000>;   /* 6.5MiB */
+		};
 	};
 };
diff --git a/arch/arm/boot/dts/armada-385-linksys-rango.dts b/arch/arm/boot/dts/armada-385-linksys-rango.dts
index da8a0f3d432b..93570c6bfe85 100644
--- a/arch/arm/boot/dts/armada-385-linksys-rango.dts
+++ b/arch/arm/boot/dts/armada-385-linksys-rango.dts
@@ -113,74 +113,79 @@
 
 &nand {
 	/* AMD/Spansion S34ML02G2 256MiB, OEM Layout */
-
-	partition@0 {
-		label = "u-boot";
-		reg = <0x0000000 0x200000>;  /* 2MiB */
-		read-only;
-	};
-
-	partition@200000 {
-		label = "u_env";
-		reg = <0x200000 0x20000>;    /* 128KiB */
-	};
-
-	partition@220000 {
-		label = "s_env";
-		reg = <0x220000 0x40000>;    /* 256KiB */
-	};
-
-	partition@7e0000 {
-		label = "devinfo";
-		reg = <0x7e0000 0x40000>;   /* 256KiB */
-		read-only;
-	};
-
-	partition@820000 {
-		label = "sysdiag";
-		reg = <0x820000 0x1e0000>;   /* 1920KiB */
-		read-only;
-	};
-
-	/* kernel1 overlaps with rootfs1 by design */
-	partition@a00000 {
-		label = "kernel1";
-		reg = <0xa00000 0x5000000>;  /* 80MiB */
-	};
-
-	partition@1000000 {
-		label = "rootfs1";
-		reg = <0x1000000 0x4a00000>;  /* 74MiB */
-	};
-
-	/* kernel2 overlaps with rootfs2 by design */
-	partition@5a00000 {
-		label = "kernel2";
-		reg = <0x5a00000 0x5000000>; /* 80MiB */
-	};
-
-	partition@6000000 {
-		label = "rootfs2";
-		reg = <0x6000000 0x4a00000>; /* 74MiB */
-	};
-
-	/*
-	 * 86MiB, last MiB is for the BBT, not writable
-	 */
-	partition@aa00000 {
-		label = "syscfg";
-		reg = <0xaa00000 0x5600000>;
-	};
-
-	/*
-	 * Unused area between "s_env" and "devinfo".
-	 * Moved here because otherwise the renumbered
-	 * partitions would break the bootloader
-	 * supplied bootargs
-	 */
-	partition@180000 {
-		label = "unused_area";
-		reg = <0x260000 0x5c0000>;   /* 5.75MiB */
+	partitions {
+		compatible = "fixed-partitions";
+		#address-cells = <1>;
+		#size-cells = <1>;
+
+		partition@0 {
+			label = "u-boot";
+			reg = <0x0000000 0x200000>;  /* 2MiB */
+			read-only;
+		};
+
+		partition@200000 {
+			label = "u_env";
+			reg = <0x200000 0x20000>;    /* 128KiB */
+		};
+
+		partition@220000 {
+			label = "s_env";
+			reg = <0x220000 0x40000>;    /* 256KiB */
+		};
+
+		partition@7e0000 {
+			label = "devinfo";
+			reg = <0x7e0000 0x40000>;   /* 256KiB */
+			read-only;
+		};
+
+		partition@820000 {
+			label = "sysdiag";
+			reg = <0x820000 0x1e0000>;   /* 1920KiB */
+			read-only;
+		};
+
+		/* kernel1 overlaps with rootfs1 by design */
+		partition@a00000 {
+			label = "kernel1";
+			reg = <0xa00000 0x5000000>;  /* 80MiB */
+		};
+
+		partition@1000000 {
+			label = "rootfs1";
+			reg = <0x1000000 0x4a00000>;  /* 74MiB */
+		};
+
+		/* kernel2 overlaps with rootfs2 by design */
+		partition@5a00000 {
+			label = "kernel2";
+			reg = <0x5a00000 0x5000000>; /* 80MiB */
+		};
+
+		partition@6000000 {
+			label = "rootfs2";
+			reg = <0x6000000 0x4a00000>; /* 74MiB */
+		};
+
+		/*
+		 * 86MiB, last MiB is for the BBT, not writable
+		 */
+		partition@aa00000 {
+			label = "syscfg";
+			reg = <0xaa00000 0x5600000>;
+		};
+
+		/*
+		 * Unused area between "s_env" and "devinfo".
+		 * Moved here because otherwise the renumbered
+		 * partitions would break the bootloader
+		 * supplied bootargs
+		 */
+		partition@180000 {
+			label = "unused_area";
+			reg = <0x260000 0x5c0000>;   /* 5.75MiB */
+		};
 	};
 };
 
diff --git a/arch/arm/boot/dts/armada-385-linksys-shelby.dts b/arch/arm/boot/dts/armada-385-linksys-shelby.dts
index 94aa35bc0bff..4694556700ff 100644
--- a/arch/arm/boot/dts/armada-385-linksys-shelby.dts
+++ b/arch/arm/boot/dts/armada-385-linksys-shelby.dts
@@ -105,67 +105,72 @@
 
 &nand {
 	/* 128MiB */
-
-	partition@0 {
-		label = "u-boot";
-		reg = <0x0000000 0x200000>;  /* 2MiB */
-		read-only;
-	};
-
-	partition@100000 {
-		label = "u_env";
-		reg = <0x200000 0x40000>;    /* 256KiB */
-	};
-
-	partition@140000 {
-		label = "s_env";
-		reg = <0x240000 0x40000>;    /* 256KiB */
-	};
-
-	partition@900000 {
-		label = "devinfo";
-		reg = <0x900000 0x100000>;   /* 1MiB */
-		read-only;
-	};
-
-	/* kernel1 overlaps with rootfs1 by design */
-	partition@a00000 {
-		label = "kernel1";
-		reg = <0xa00000 0x2800000>;  /* 40MiB */
-	};
-
-	partition@1000000 {
-		label = "rootfs1";
-		reg = <0x1000000 0x2200000>;  /* 34MiB */
-	};
-
-	/* kernel2 overlaps with rootfs2 by design */
-	partition@3200000 {
-		label = "kernel2";
-		reg = <0x3200000 0x2800000>; /* 40MiB */
-	};
-
-	partition@3800000 {
-		label = "rootfs2";
-		reg = <0x3800000 0x2200000>; /* 34MiB */
-	};
-
-	/*
-	 * 38MiB, last MiB is for the BBT, not writable
-	 */
-	partition@5a00000 {
-		label = "syscfg";
-		reg = <0x5a00000 0x2600000>;
-	};
-
-	/*
-	 * Unused area between "s_env" and "devinfo".
-	 * Moved here because otherwise the renumbered
-	 * partitions would break the bootloader
-	 * supplied bootargs
-	 */
-	partition@180000 {
-		label = "unused_area";
-		reg = <0x280000 0x680000>;   /* 6.5MiB */
+	partitions {
+		compatible = "fixed-partitions";
+		#address-cells = <1>;
+		#size-cells = <1>;
+
+		partition@0 {
+			label = "u-boot";
+			reg = <0x0000000 0x200000>;  /* 2MiB */
+			read-only;
+		};
+
+		partition@100000 {
+			label = "u_env";
+			reg = <0x200000 0x40000>;    /* 256KiB */
+		};
+
+		partition@140000 {
+			label = "s_env";
+			reg = <0x240000 0x40000>;    /* 256KiB */
+		};
+
+		partition@900000 {
+			label = "devinfo";
+			reg = <0x900000 0x100000>;   /* 1MiB */
+			read-only;
+		};
+
+		/* kernel1 overlaps with rootfs1 by design */
+		partition@a00000 {
+			label = "kernel1";
+			reg = <0xa00000 0x2800000>;  /* 40MiB */
+		};
+
+		partition@1000000 {
+			label = "rootfs1";
+			reg = <0x1000000 0x2200000>;  /* 34MiB */
+		};
+
+		/* kernel2 overlaps with rootfs2 by design */
+		partition@3200000 {
+			label = "kernel2";
+			reg = <0x3200000 0x2800000>; /* 40MiB */
+		};
+
+		partition@3800000 {
+			label = "rootfs2";
+			reg = <0x3800000 0x2200000>; /* 34MiB */
+		};
+
+		/*
+		 * 38MiB, last MiB is for the BBT, not writable
+		 */
+		partition@5a00000 {
+			label = "syscfg";
+			reg = <0x5a00000 0x2600000>;
+		};
+
+		/*
+		 * Unused area between "s_env" and "devinfo".
+		 * Moved here because otherwise the renumbered
+		 * partitions would break the bootloader
+		 * supplied bootargs
+		 */
+		partition@180000 {
+			label = "unused_area";
+			reg = <0x280000 0x680000>;   /* 6.5MiB */
+		};
 	};
 };
diff --git a/arch/arm/boot/dts/armada-385-linksys.dtsi b/arch/arm/boot/dts/armada-385-linksys.dtsi
index e1f355ffc8f7..358b17e921bb 100644
--- a/arch/arm/boot/dts/armada-385-linksys.dtsi
+++ b/arch/arm/boot/dts/armada-385-linksys.dtsi
@@ -169,13 +169,18 @@
 	};
 };
 
-&nand {
+&nand_controller {
 	/* 128MiB or 256MiB */
 	status = "okay";
-	num-cs = <1>;
-	marvell,nand-keep-config;
-	marvell,nand-enable-arbiter;
-	nand-on-flash-bbt;
+	#address-cells = <1>;
+	#size-cells = <0>;
+
+	nand: nand@0 {
+		reg = <0>;
+		marvell,rb = <0>;
+		marvell,nand-keep-config;
+		nand-on-flash-bbt;
+	};
 };
 
 &mdio {
diff --git a/arch/arm/boot/dts/armada-388-db.dts b/arch/arm/boot/dts/armada-388-db.dts
index a4ec1fa37529..0c020d7afcc1 100644
--- a/arch/arm/boot/dts/armada-388-db.dts
+++ b/arch/arm/boot/dts/armada-388-db.dts
@@ -128,29 +128,6 @@
 				status = "okay";
 			};
 
-			flash@d0000 {
-				status = "okay";
-				num-cs = <1>;
-				marvell,nand-keep-config;
-				marvell,nand-enable-arbiter;
-				nand-on-flash-bbt;
-				nand-ecc-strength = <4>;
-				nand-ecc-step-size = <512>;
-
-				partition@0 {
-					label = "U-Boot";
-					reg = <0 0x800000>;
-				};
-				partition@800000 {
-					label = "Linux";
-					reg = <0x800000 0x800000>;
-				};
-				partition@1000000 {
-					label = "Filesystem";
-					reg = <0x1000000 0x3f000000>;
-				};
-			};
-
 			sdhci@d8000 {
 				broken-cd;
 				wp-inverted;
@@ -202,3 +179,34 @@
 	};
 };
 
+&nand_controller {
+	status = "okay";
+
+	nand@0 {
+		reg = <0>;
+		marvell,rb = <0>;
+		marvell,nand-keep-config;
+		nand-on-flash-bbt;
+		nand-ecc-strength = <4>;
+		nand-ecc-step-size = <512>;
+
+		partitions {
+			compatible = "fixed-partitions";
+			#address-cells = <1>;
+			#size-cells = <1>;
+
+			partition@0 {
+				label = "U-Boot";
+				reg = <0 0x800000>;
+			};
+			partition@800000 {
+				label = "Linux";
+				reg = <0x800000 0x800000>;
+			};
+			partition@1000000 {
+				label = "Filesystem";
+				reg = <0x1000000 0x3f000000>;
+			};
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/armada-38x.dtsi b/arch/arm/boot/dts/armada-38x.dtsi
index 7ff0811e61db..764fcf08ef53 100644
--- a/arch/arm/boot/dts/armada-38x.dtsi
+++ b/arch/arm/boot/dts/armada-38x.dtsi
@@ -583,11 +583,11 @@
 				status = "okay";
 			};
 
-			nand: flash@d0000 {
-				compatible = "marvell,armada370-nand";
+			nand_controller: nand-controller@d0000 {
+				compatible = "marvell,armada370-nand-controller";
 				reg = <0xd0000 0x54>;
 				#address-cells = <1>;
-				#size-cells = <1>;
+				#size-cells = <0>;
 				interrupts = <GIC_SPI 84 IRQ_TYPE_LEVEL_HIGH>;
 				clocks = <&coredivclk 0>;
 				status = "disabled";
-- 
2.11.0

^ permalink raw reply related	[flat|nested] 75+ messages in thread

* [RFC 09/12] ARM: dts: armada-38x: use reworked NAND controller driver
@ 2017-10-18 14:36   ` Miquel Raynal
  0 siblings, 0 replies; 75+ messages in thread
From: Miquel Raynal @ 2017-10-18 14:36 UTC (permalink / raw)
  To: linux-arm-kernel

Change the bindings to use the reworked Marvell NAND controller driver.
Also adapt the nand controller node organization to distinguish which
property is relevant for the controller, and which one is NAND chip
specific. Expose the partitions as a subnode of the NAND chip.

Remove the 'marvell,nand-enable-arbiter' property, inefective with
Armada boards.

Signed-off-by: Miquel Raynal <miquel.raynal@free-electrons.com>
---
 arch/arm/boot/dts/armada-385-db-ap.dts          |  68 ++++++------
 arch/arm/boot/dts/armada-385-linksys-caiman.dts | 129 +++++++++++-----------
 arch/arm/boot/dts/armada-385-linksys-cobra.dts  | 129 +++++++++++-----------
 arch/arm/boot/dts/armada-385-linksys-rango.dts  | 141 ++++++++++++------------
 arch/arm/boot/dts/armada-385-linksys-shelby.dts | 129 +++++++++++-----------
 arch/arm/boot/dts/armada-385-linksys.dtsi       |  15 ++-
 arch/arm/boot/dts/armada-388-db.dts             |  54 +++++----
 arch/arm/boot/dts/armada-38x.dtsi               |   6 +-
 8 files changed, 353 insertions(+), 318 deletions(-)

diff --git a/arch/arm/boot/dts/armada-385-db-ap.dts b/arch/arm/boot/dts/armada-385-db-ap.dts
index 25d2d720dc0e..625691196acb 100644
--- a/arch/arm/boot/dts/armada-385-db-ap.dts
+++ b/arch/arm/boot/dts/armada-385-db-ap.dts
@@ -166,39 +166,6 @@
 				status = "okay";
 			};
 
-			nfc: flash at d0000 {
-				status = "okay";
-				num-cs = <1>;
-				nand-ecc-strength = <4>;
-				nand-ecc-step-size = <512>;
-				marvell,nand-keep-config;
-				marvell,nand-enable-arbiter;
-				nand-on-flash-bbt;
-
-				partitions {
-					compatible = "fixed-partitions";
-					#address-cells = <1>;
-					#size-cells = <1>;
-
-					partition at 0 {
-						label = "U-Boot";
-						reg = <0x00000000 0x00800000>;
-						read-only;
-					};
-
-					partition at 800000 {
-						label = "uImage";
-						reg = <0x00800000 0x00400000>;
-						read-only;
-					};
-
-					partition at c00000 {
-						label = "Root";
-						reg = <0x00c00000 0x3f400000>;
-					};
-				};
-			};
-
 			usb3 at f0000 {
 				status = "okay";
 				usb-phy = <&usb3_phy>;
@@ -263,3 +230,38 @@
 		spi-max-frequency = <54000000>;
 	};
 };
+
+&nand_controller {
+	status = "okay";
+
+	nand at 0 {
+		reg = <0>;
+		marvell,rb = <0>;
+		nand-on-flash-bbt;
+		nand-ecc-strength = <4>;
+		nand-ecc-step-size = <512>;
+
+		partitions {
+			compatible = "fixed-partitions";
+			#address-cells = <1>;
+			#size-cells = <1>;
+
+			partition at 0 {
+				label = "U-Boot";
+				reg = <0x00000000 0x00800000>;
+				read-only;
+			};
+
+			partition at 800000 {
+				label = "uImage";
+				reg = <0x00800000 0x00400000>;
+				read-only;
+			};
+
+			partition at c00000 {
+				label = "Root";
+				reg = <0x00c00000 0x3f400000>;
+			};
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/armada-385-linksys-caiman.dts b/arch/arm/boot/dts/armada-385-linksys-caiman.dts
index ee669ae61011..e769bcf7a9d1 100644
--- a/arch/arm/boot/dts/armada-385-linksys-caiman.dts
+++ b/arch/arm/boot/dts/armada-385-linksys-caiman.dts
@@ -105,67 +105,72 @@
 
 &nand {
 	/* 128MiB */
-
-	partition at 0 {
-		label = "u-boot";
-		reg = <0x0000000 0x200000>;  /* 2MiB */
-		read-only;
-	};
-
-	partition at 100000 {
-		label = "u_env";
-		reg = <0x200000 0x40000>;    /* 256KiB */
-	};
-
-	partition at 140000 {
-		label = "s_env";
-		reg = <0x240000 0x40000>;    /* 256KiB */
-	};
-
-	partition at 900000 {
-		label = "devinfo";
-		reg = <0x900000 0x100000>;   /* 1MiB */
-		read-only;
-	};
-
-	/* kernel1 overlaps with rootfs1 by design */
-	partition at a00000 {
-		label = "kernel1";
-		reg = <0xa00000 0x2800000>;  /* 40MiB */
-	};
-
-	partition at 1000000 {
-		label = "rootfs1";
-		reg = <0x1000000 0x2200000>;  /* 34MiB */
-	};
-
-	/* kernel2 overlaps with rootfs2 by design */
-	partition at 3200000 {
-		label = "kernel2";
-		reg = <0x3200000 0x2800000>; /* 40MiB */
-	};
-
-	partition at 3800000 {
-		label = "rootfs2";
-		reg = <0x3800000 0x2200000>; /* 34MiB */
-	};
-
-	/*
-	 * 38MiB, last MiB is for the BBT, not writable
-	 */
-	partition at 5a00000 {
-		label = "syscfg";
-		reg = <0x5a00000 0x2600000>;
-	};
-
-	/*
-	 * Unused area between "s_env" and "devinfo".
-	 * Moved here because otherwise the renumbered
-	 * partitions would break the bootloader
-	 * supplied bootargs
-	 */
-	partition at 180000 {
-		label = "unused_area";
-		reg = <0x280000 0x680000>;   /* 6.5MiB */
+	partitions {
+		compatible = "fixed-partitions";
+		#address-cells = <1>;
+		#size-cells = <1>;
+
+		partition at 0 {
+			label = "u-boot";
+			reg = <0x0000000 0x200000>;  /* 2MiB */
+			read-only;
+		};
+
+		partition at 100000 {
+			label = "u_env";
+			reg = <0x200000 0x40000>;    /* 256KiB */
+		};
+
+		partition at 140000 {
+			label = "s_env";
+			reg = <0x240000 0x40000>;    /* 256KiB */
+		};
+
+		partition at 900000 {
+			label = "devinfo";
+			reg = <0x900000 0x100000>;   /* 1MiB */
+			read-only;
+		};
+
+		/* kernel1 overlaps with rootfs1 by design */
+		partition at a00000 {
+			label = "kernel1";
+			reg = <0xa00000 0x2800000>;  /* 40MiB */
+		};
+
+		partition at 1000000 {
+			label = "rootfs1";
+			reg = <0x1000000 0x2200000>;  /* 34MiB */
+		};
+
+		/* kernel2 overlaps with rootfs2 by design */
+		partition at 3200000 {
+			label = "kernel2";
+			reg = <0x3200000 0x2800000>; /* 40MiB */
+		};
+
+		partition at 3800000 {
+			label = "rootfs2";
+			reg = <0x3800000 0x2200000>; /* 34MiB */
+		};
+
+		/*
+		 * 38MiB, last MiB is for the BBT, not writable
+		 */
+		partition at 5a00000 {
+			label = "syscfg";
+			reg = <0x5a00000 0x2600000>;
+		};
+
+		/*
+		 * Unused area between "s_env" and "devinfo".
+		 * Moved here because otherwise the renumbered
+		 * partitions would break the bootloader
+		 * supplied bootargs
+		 */
+		partition at 180000 {
+			label = "unused_area";
+			reg = <0x280000 0x680000>;   /* 6.5MiB */
+		};
 	};
 };
diff --git a/arch/arm/boot/dts/armada-385-linksys-cobra.dts b/arch/arm/boot/dts/armada-385-linksys-cobra.dts
index 5169ca89c55a..690be217838c 100644
--- a/arch/arm/boot/dts/armada-385-linksys-cobra.dts
+++ b/arch/arm/boot/dts/armada-385-linksys-cobra.dts
@@ -105,67 +105,72 @@
 
 &nand {
 	/* 128MiB */
-
-	partition at 0 {
-		label = "u-boot";
-		reg = <0x0000000 0x200000>;  /* 2MiB */
-		read-only;
-	};
-
-	partition at 100000 {
-		label = "u_env";
-		reg = <0x200000 0x40000>;    /* 256KiB */
-	};
-
-	partition at 140000 {
-		label = "s_env";
-		reg = <0x240000 0x40000>;    /* 256KiB */
-	};
-
-	partition at 900000 {
-		label = "devinfo";
-		reg = <0x900000 0x100000>;   /* 1MiB */
-		read-only;
-	};
-
-	/* kernel1 overlaps with rootfs1 by design */
-	partition at a00000 {
-		label = "kernel1";
-		reg = <0xa00000 0x2800000>;  /* 40MiB */
-	};
-
-	partition at 1000000 {
-		label = "rootfs1";
-		reg = <0x1000000 0x2200000>;  /* 34MiB */
-	};
-
-	/* kernel2 overlaps with rootfs2 by design */
-	partition at 3200000 {
-		label = "kernel2";
-		reg = <0x3200000 0x2800000>; /* 40MiB */
-	};
-
-	partition at 3800000 {
-		label = "rootfs2";
-		reg = <0x3800000 0x2200000>; /* 34MiB */
-	};
-
-	/*
-	 * 38MiB, last MiB is for the BBT, not writable
-	 */
-	partition at 5a00000 {
-		label = "syscfg";
-		reg = <0x5a00000 0x2600000>;
-	};
-
-	/*
-	 * Unused area between "s_env" and "devinfo".
-	 * Moved here because otherwise the renumbered
-	 * partitions would break the bootloader
-	 * supplied bootargs
-	 */
-	partition at 180000 {
-		label = "unused_area";
-		reg = <0x280000 0x680000>;   /* 6.5MiB */
+	partitions {
+		compatible = "fixed-partitions";
+		#address-cells = <1>;
+		#size-cells = <1>;
+
+		partition at 0 {
+			label = "u-boot";
+			reg = <0x0000000 0x200000>;  /* 2MiB */
+			read-only;
+		};
+
+		partition at 100000 {
+			label = "u_env";
+			reg = <0x200000 0x40000>;    /* 256KiB */
+		};
+
+		partition at 140000 {
+			label = "s_env";
+			reg = <0x240000 0x40000>;    /* 256KiB */
+		};
+
+		partition at 900000 {
+			label = "devinfo";
+			reg = <0x900000 0x100000>;   /* 1MiB */
+			read-only;
+		};
+
+		/* kernel1 overlaps with rootfs1 by design */
+		partition at a00000 {
+			label = "kernel1";
+			reg = <0xa00000 0x2800000>;  /* 40MiB */
+		};
+
+		partition at 1000000 {
+			label = "rootfs1";
+			reg = <0x1000000 0x2200000>;  /* 34MiB */
+		};
+
+		/* kernel2 overlaps with rootfs2 by design */
+		partition at 3200000 {
+			label = "kernel2";
+			reg = <0x3200000 0x2800000>; /* 40MiB */
+		};
+
+		partition at 3800000 {
+			label = "rootfs2";
+			reg = <0x3800000 0x2200000>; /* 34MiB */
+		};
+
+		/*
+		 * 38MiB, last MiB is for the BBT, not writable
+		 */
+		partition at 5a00000 {
+			label = "syscfg";
+			reg = <0x5a00000 0x2600000>;
+		};
+
+		/*
+		 * Unused area between "s_env" and "devinfo".
+		 * Moved here because otherwise the renumbered
+		 * partitions would break the bootloader
+		 * supplied bootargs
+		 */
+		partition at 180000 {
+			label = "unused_area";
+			reg = <0x280000 0x680000>;   /* 6.5MiB */
+		};
 	};
 };
diff --git a/arch/arm/boot/dts/armada-385-linksys-rango.dts b/arch/arm/boot/dts/armada-385-linksys-rango.dts
index da8a0f3d432b..93570c6bfe85 100644
--- a/arch/arm/boot/dts/armada-385-linksys-rango.dts
+++ b/arch/arm/boot/dts/armada-385-linksys-rango.dts
@@ -113,74 +113,79 @@
 
 &nand {
 	/* AMD/Spansion S34ML02G2 256MiB, OEM Layout */
-
-	partition at 0 {
-		label = "u-boot";
-		reg = <0x0000000 0x200000>;  /* 2MiB */
-		read-only;
-	};
-
-	partition at 200000 {
-		label = "u_env";
-		reg = <0x200000 0x20000>;    /* 128KiB */
-	};
-
-	partition at 220000 {
-		label = "s_env";
-		reg = <0x220000 0x40000>;    /* 256KiB */
-	};
-
-	partition at 7e0000 {
-		label = "devinfo";
-		reg = <0x7e0000 0x40000>;   /* 256KiB */
-		read-only;
-	};
-
-	partition at 820000 {
-		label = "sysdiag";
-		reg = <0x820000 0x1e0000>;   /* 1920KiB */
-		read-only;
-	};
-
-	/* kernel1 overlaps with rootfs1 by design */
-	partition at a00000 {
-		label = "kernel1";
-		reg = <0xa00000 0x5000000>;  /* 80MiB */
-	};
-
-	partition at 1000000 {
-		label = "rootfs1";
-		reg = <0x1000000 0x4a00000>;  /* 74MiB */
-	};
-
-	/* kernel2 overlaps with rootfs2 by design */
-	partition at 5a00000 {
-		label = "kernel2";
-		reg = <0x5a00000 0x5000000>; /* 80MiB */
-	};
-
-	partition at 6000000 {
-		label = "rootfs2";
-		reg = <0x6000000 0x4a00000>; /* 74MiB */
-	};
-
-	/*
-	 * 86MiB, last MiB is for the BBT, not writable
-	 */
-	partition at aa00000 {
-		label = "syscfg";
-		reg = <0xaa00000 0x5600000>;
-	};
-
-	/*
-	 * Unused area between "s_env" and "devinfo".
-	 * Moved here because otherwise the renumbered
-	 * partitions would break the bootloader
-	 * supplied bootargs
-	 */
-	partition at 180000 {
-		label = "unused_area";
-		reg = <0x260000 0x5c0000>;   /* 5.75MiB */
+	partitions {
+		compatible = "fixed-partitions";
+		#address-cells = <1>;
+		#size-cells = <1>;
+
+		partition at 0 {
+			label = "u-boot";
+			reg = <0x0000000 0x200000>;  /* 2MiB */
+			read-only;
+		};
+
+		partition at 200000 {
+			label = "u_env";
+			reg = <0x200000 0x20000>;    /* 128KiB */
+		};
+
+		partition at 220000 {
+			label = "s_env";
+			reg = <0x220000 0x40000>;    /* 256KiB */
+		};
+
+		partition at 7e0000 {
+			label = "devinfo";
+			reg = <0x7e0000 0x40000>;   /* 256KiB */
+			read-only;
+		};
+
+		partition at 820000 {
+			label = "sysdiag";
+			reg = <0x820000 0x1e0000>;   /* 1920KiB */
+			read-only;
+		};
+
+		/* kernel1 overlaps with rootfs1 by design */
+		partition at a00000 {
+			label = "kernel1";
+			reg = <0xa00000 0x5000000>;  /* 80MiB */
+		};
+
+		partition at 1000000 {
+			label = "rootfs1";
+			reg = <0x1000000 0x4a00000>;  /* 74MiB */
+		};
+
+		/* kernel2 overlaps with rootfs2 by design */
+		partition at 5a00000 {
+			label = "kernel2";
+			reg = <0x5a00000 0x5000000>; /* 80MiB */
+		};
+
+		partition at 6000000 {
+			label = "rootfs2";
+			reg = <0x6000000 0x4a00000>; /* 74MiB */
+		};
+
+		/*
+		 * 86MiB, last MiB is for the BBT, not writable
+		 */
+		partition at aa00000 {
+			label = "syscfg";
+			reg = <0xaa00000 0x5600000>;
+		};
+
+		/*
+		 * Unused area between "s_env" and "devinfo".
+		 * Moved here because otherwise the renumbered
+		 * partitions would break the bootloader
+		 * supplied bootargs
+		 */
+		partition at 180000 {
+			label = "unused_area";
+			reg = <0x260000 0x5c0000>;   /* 5.75MiB */
+		};
 	};
 };
 
diff --git a/arch/arm/boot/dts/armada-385-linksys-shelby.dts b/arch/arm/boot/dts/armada-385-linksys-shelby.dts
index 94aa35bc0bff..4694556700ff 100644
--- a/arch/arm/boot/dts/armada-385-linksys-shelby.dts
+++ b/arch/arm/boot/dts/armada-385-linksys-shelby.dts
@@ -105,67 +105,72 @@
 
 &nand {
 	/* 128MiB */
-
-	partition at 0 {
-		label = "u-boot";
-		reg = <0x0000000 0x200000>;  /* 2MiB */
-		read-only;
-	};
-
-	partition at 100000 {
-		label = "u_env";
-		reg = <0x200000 0x40000>;    /* 256KiB */
-	};
-
-	partition at 140000 {
-		label = "s_env";
-		reg = <0x240000 0x40000>;    /* 256KiB */
-	};
-
-	partition at 900000 {
-		label = "devinfo";
-		reg = <0x900000 0x100000>;   /* 1MiB */
-		read-only;
-	};
-
-	/* kernel1 overlaps with rootfs1 by design */
-	partition at a00000 {
-		label = "kernel1";
-		reg = <0xa00000 0x2800000>;  /* 40MiB */
-	};
-
-	partition at 1000000 {
-		label = "rootfs1";
-		reg = <0x1000000 0x2200000>;  /* 34MiB */
-	};
-
-	/* kernel2 overlaps with rootfs2 by design */
-	partition at 3200000 {
-		label = "kernel2";
-		reg = <0x3200000 0x2800000>; /* 40MiB */
-	};
-
-	partition at 3800000 {
-		label = "rootfs2";
-		reg = <0x3800000 0x2200000>; /* 34MiB */
-	};
-
-	/*
-	 * 38MiB, last MiB is for the BBT, not writable
-	 */
-	partition at 5a00000 {
-		label = "syscfg";
-		reg = <0x5a00000 0x2600000>;
-	};
-
-	/*
-	 * Unused area between "s_env" and "devinfo".
-	 * Moved here because otherwise the renumbered
-	 * partitions would break the bootloader
-	 * supplied bootargs
-	 */
-	partition at 180000 {
-		label = "unused_area";
-		reg = <0x280000 0x680000>;   /* 6.5MiB */
+	partitions {
+		compatible = "fixed-partitions";
+		#address-cells = <1>;
+		#size-cells = <1>;
+
+		partition at 0 {
+			label = "u-boot";
+			reg = <0x0000000 0x200000>;  /* 2MiB */
+			read-only;
+		};
+
+		partition at 100000 {
+			label = "u_env";
+			reg = <0x200000 0x40000>;    /* 256KiB */
+		};
+
+		partition at 140000 {
+			label = "s_env";
+			reg = <0x240000 0x40000>;    /* 256KiB */
+		};
+
+		partition at 900000 {
+			label = "devinfo";
+			reg = <0x900000 0x100000>;   /* 1MiB */
+			read-only;
+		};
+
+		/* kernel1 overlaps with rootfs1 by design */
+		partition at a00000 {
+			label = "kernel1";
+			reg = <0xa00000 0x2800000>;  /* 40MiB */
+		};
+
+		partition at 1000000 {
+			label = "rootfs1";
+			reg = <0x1000000 0x2200000>;  /* 34MiB */
+		};
+
+		/* kernel2 overlaps with rootfs2 by design */
+		partition at 3200000 {
+			label = "kernel2";
+			reg = <0x3200000 0x2800000>; /* 40MiB */
+		};
+
+		partition at 3800000 {
+			label = "rootfs2";
+			reg = <0x3800000 0x2200000>; /* 34MiB */
+		};
+
+		/*
+		 * 38MiB, last MiB is for the BBT, not writable
+		 */
+		partition at 5a00000 {
+			label = "syscfg";
+			reg = <0x5a00000 0x2600000>;
+		};
+
+		/*
+		 * Unused area between "s_env" and "devinfo".
+		 * Moved here because otherwise the renumbered
+		 * partitions would break the bootloader
+		 * supplied bootargs
+		 */
+		partition at 180000 {
+			label = "unused_area";
+			reg = <0x280000 0x680000>;   /* 6.5MiB */
+		};
 	};
 };
diff --git a/arch/arm/boot/dts/armada-385-linksys.dtsi b/arch/arm/boot/dts/armada-385-linksys.dtsi
index e1f355ffc8f7..358b17e921bb 100644
--- a/arch/arm/boot/dts/armada-385-linksys.dtsi
+++ b/arch/arm/boot/dts/armada-385-linksys.dtsi
@@ -169,13 +169,18 @@
 	};
 };
 
-&nand {
+&nand_controller {
 	/* 128MiB or 256MiB */
 	status = "okay";
-	num-cs = <1>;
-	marvell,nand-keep-config;
-	marvell,nand-enable-arbiter;
-	nand-on-flash-bbt;
+	#address-cells = <1>;
+	#size-cells = <0>;
+
+	nand: nand at 0 {
+		reg = <0>;
+		marvell,rb = <0>;
+		marvell,nand-keep-config;
+		nand-on-flash-bbt;
+	};
 };
 
 &mdio {
diff --git a/arch/arm/boot/dts/armada-388-db.dts b/arch/arm/boot/dts/armada-388-db.dts
index a4ec1fa37529..0c020d7afcc1 100644
--- a/arch/arm/boot/dts/armada-388-db.dts
+++ b/arch/arm/boot/dts/armada-388-db.dts
@@ -128,29 +128,6 @@
 				status = "okay";
 			};
 
-			flash at d0000 {
-				status = "okay";
-				num-cs = <1>;
-				marvell,nand-keep-config;
-				marvell,nand-enable-arbiter;
-				nand-on-flash-bbt;
-				nand-ecc-strength = <4>;
-				nand-ecc-step-size = <512>;
-
-				partition at 0 {
-					label = "U-Boot";
-					reg = <0 0x800000>;
-				};
-				partition at 800000 {
-					label = "Linux";
-					reg = <0x800000 0x800000>;
-				};
-				partition at 1000000 {
-					label = "Filesystem";
-					reg = <0x1000000 0x3f000000>;
-				};
-			};
-
 			sdhci at d8000 {
 				broken-cd;
 				wp-inverted;
@@ -202,3 +179,34 @@
 	};
 };
 
+&nand_controller {
+	status = "okay";
+
+	nand at 0 {
+		reg = <0>;
+		marvell,rb = <0>;
+		marvell,nand-keep-config;
+		nand-on-flash-bbt;
+		nand-ecc-strength = <4>;
+		nand-ecc-step-size = <512>;
+
+		partitions {
+			compatible = "fixed-partitions";
+			#address-cells = <1>;
+			#size-cells = <1>;
+
+			partition at 0 {
+				label = "U-Boot";
+				reg = <0 0x800000>;
+			};
+			partition at 800000 {
+				label = "Linux";
+				reg = <0x800000 0x800000>;
+			};
+			partition at 1000000 {
+				label = "Filesystem";
+				reg = <0x1000000 0x3f000000>;
+			};
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/armada-38x.dtsi b/arch/arm/boot/dts/armada-38x.dtsi
index 7ff0811e61db..764fcf08ef53 100644
--- a/arch/arm/boot/dts/armada-38x.dtsi
+++ b/arch/arm/boot/dts/armada-38x.dtsi
@@ -583,11 +583,11 @@
 				status = "okay";
 			};
 
-			nand: flash at d0000 {
-				compatible = "marvell,armada370-nand";
+			nand_controller: nand-controller at d0000 {
+				compatible = "marvell,armada370-nand-controller";
 				reg = <0xd0000 0x54>;
 				#address-cells = <1>;
-				#size-cells = <1>;
+				#size-cells = <0>;
 				interrupts = <GIC_SPI 84 IRQ_TYPE_LEVEL_HIGH>;
 				clocks = <&coredivclk 0>;
 				status = "disabled";
-- 
2.11.0

^ permalink raw reply related	[flat|nested] 75+ messages in thread

* [RFC 10/12] ARM: dts: armada-39x: use reworked NAND controller driver
  2017-10-18 14:36 ` Miquel Raynal
  (?)
@ 2017-10-18 14:36   ` Miquel Raynal
  -1 siblings, 0 replies; 75+ messages in thread
From: Miquel Raynal @ 2017-10-18 14:36 UTC (permalink / raw)
  To: Andrew Lunn, bcm-kernel-feedback-list, Boris Brezillon,
	Brian Norris, Catalin Marinas, Chen-Yu Tsai, Cyrille Pitchen,
	Daniel Mack, David Woodhouse, devel, devicetree, Ezequiel Garcia,
	Greg Kroah-Hartman, Gregory Clement, Han Xu, Haojian Zhuang,
	Jason Cooper, Josh Wu, Kamal Dasu, linux-arm-kernel,
	linux-kernel, linux-mediatek, linux-mtd, Marc Gonzalez,
	Marek Vasut, Mark Rutland, Masahiro Yamada, Matthias Brugger,
	Maxime Ripard, Maxim Levitsky, Richard Weinberger,
	Robert Jarzmik, Rob Herring, Russell King, Sebastian Hesselbarth,
	Stefan Agner, Sylvain Lemieux, Vladimir Zapolskiy, Wenyou Yang,
	Will Deacon
  Cc: Thomas Petazzoni, Antoine Tenart, Miquel Raynal, Igor Grinberg,
	Nadav Haklai, Ofer Heifetz, Neta Zur Hershkovits, Hanna Hawa

Change the bindings to use the reworked Marvell NAND controller driver.
Also adapt the nand controller node organization to distinguish which
property is relevant for the controller, and which one is NAND chip
specific. Expose the partitions as a subnode of the NAND chip.

Remove the 'marvell,nand-enable-arbiter' property, inefective with
Armada boards.

Signed-off-by: Miquel Raynal <miquel.raynal@free-electrons.com>
---
 arch/arm/boot/dts/armada-390-db.dts | 65 +++++++++++++++++----------------
 arch/arm/boot/dts/armada-395-gp.dts | 73 +++++++++++++++++++------------------
 arch/arm/boot/dts/armada-398-db.dts | 59 +++++++++++++++++-------------
 arch/arm/boot/dts/armada-39x.dtsi   |  6 +--
 4 files changed, 109 insertions(+), 94 deletions(-)

diff --git a/arch/arm/boot/dts/armada-390-db.dts b/arch/arm/boot/dts/armada-390-db.dts
index c718a5242595..5a8b05da80f2 100644
--- a/arch/arm/boot/dts/armada-390-db.dts
+++ b/arch/arm/boot/dts/armada-390-db.dts
@@ -86,37 +86,6 @@
 				status = "okay";
 			};
 
-			flash@d0000 {
-				status = "okay";
-				pinctrl-0 = <&nand_pins>;
-				pinctrl-names = "default";
-				num-cs = <1>;
-				marvell,nand-keep-config;
-				marvell,nand-enable-arbiter;
-				nand-on-flash-bbt;
-				nand-ecc-strength = <8>;
-				nand-ecc-step-size = <512>;
-
-				partitions {
-					compatible = "fixed-partitions";
-					#address-cells = <1>;
-					#size-cells = <1>;
-
-					partition@0 {
-						label = "U-Boot";
-						reg = <0 0x800000>;
-					};
-					partition@800000 {
-						label = "Linux";
-						reg = <0x800000 0x800000>;
-					};
-					partition@1000000 {
-						label = "Filesystem";
-						reg = <0x1000000 0x3f000000>;
-					};
-				};
-			};
-
 			/* CON98 */
 			usb3@f8000 {
 				status = "okay";
@@ -173,3 +142,37 @@
 		};
 	};
 };
+
+&nand_controller {
+	status = "okay";
+	pinctrl-0 = <&nand_pins>;
+	pinctrl-names = "default";
+
+	nand@0 {
+		reg = <0>;
+		marvell,rb = <0>;
+		marvell,nand-keep-config;
+		nand-on-flash-bbt;
+		nand-ecc-strength = <8>;
+		nand-ecc-step-size = <512>;
+
+		partitions {
+			compatible = "fixed-partitions";
+			#address-cells = <1>;
+			#size-cells = <1>;
+
+			partition@0 {
+				label = "U-Boot";
+				reg = <0 0x800000>;
+			};
+			partition@800000 {
+				label = "Linux";
+				reg = <0x800000 0x800000>;
+			};
+			partition@1000000 {
+				label = "Filesystem";
+				reg = <0x1000000 0x3f000000>;
+			};
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/armada-395-gp.dts b/arch/arm/boot/dts/armada-395-gp.dts
index ef491b524fd6..6261bc6ea3d0 100644
--- a/arch/arm/boot/dts/armada-395-gp.dts
+++ b/arch/arm/boot/dts/armada-395-gp.dts
@@ -88,41 +88,6 @@
 				status = "okay";
 			};
 
-			flash@d0000 {
-				status = "okay";
-				pinctrl-0 = <&nand_pins>;
-				pinctrl-names = "default";
-				num-cs = <1>;
-				marvell,nand-keep-config;
-				marvell,nand-enable-arbiter;
-				nand-on-flash-bbt;
-				nand-ecc-strength = <4>;
-				nand-ecc-step-size = <512>;
-
-				partitions {
-					compatible = "fixed-partitions";
-					#address-cells = <1>;
-					#size-cells = <1>;
-
-					partition@0 {
-						label = "U-Boot";
-						reg = <0x00000000 0x00600000>;
-						read-only;
-					};
-
-					partition@800000 {
-						label = "uImage";
-						reg = <0x00600000 0x00400000>;
-						read-only;
-					};
-
-					partition@1000000 {
-						label = "Root";
-						reg = <0x00a00000 0x3f600000>;
-					};
-				};
-			};
-
 			/* CON18 */
 			sdhci@d8000 {
 				clock-frequency = <200000000>;
@@ -161,3 +126,41 @@
 		};
 	};
 };
+
+&nand_controller {
+	status = "okay";
+	pinctrl-0 = <&nand_pins>;
+	pinctrl-names = "default";
+
+	nand@0 {
+		reg = <0>;
+		marvell,rb = <0>;
+		marvell,nand-keep-config;
+		nand-on-flash-bbt;
+		nand-ecc-strength = <4>;
+		nand-ecc-step-size = <512>;
+
+		partitions {
+			compatible = "fixed-partitions";
+			#address-cells = <1>;
+			#size-cells = <1>;
+
+			partition@0 {
+				label = "U-Boot";
+				reg = <0x00000000 0x00600000>;
+				read-only;
+			};
+
+			partition@800000 {
+				label = "uImage";
+				reg = <0x00600000 0x00400000>;
+				read-only;
+			};
+
+			partition@1000000 {
+				label = "Root";
+				reg = <0x00a00000 0x3f600000>;
+			};
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/armada-398-db.dts b/arch/arm/boot/dts/armada-398-db.dts
index f0e0379f7619..f8ff3d161f84 100644
--- a/arch/arm/boot/dts/armada-398-db.dts
+++ b/arch/arm/boot/dts/armada-398-db.dts
@@ -88,31 +88,6 @@
 				status = "okay";
 			};
 
-			flash@d0000 {
-				status = "okay";
-				pinctrl-0 = <&nand_pins>;
-				pinctrl-names = "default";
-				num-cs = <1>;
-				marvell,nand-keep-config;
-				marvell,nand-enable-arbiter;
-				nand-on-flash-bbt;
-				nand-ecc-strength = <8>;
-				nand-ecc-step-size = <512>;
-
-				partition@0 {
-					label = "U-Boot";
-					reg = <0 0x800000>;
-				};
-				partition@800000 {
-					label = "Linux";
-					reg = <0x800000 0x800000>;
-				};
-				partition@1000000 {
-					label = "Filesystem";
-					reg = <0x1000000 0x3f000000>;
-				};
-			};
-
 			usb3@f8000 {
 				status = "okay";
 			};
@@ -159,3 +134,37 @@
 		};
 	};
 };
+
+&nand_controller {
+	status = "okay";
+	pinctrl-0 = <&nand_pins>;
+	pinctrl-names = "default";
+
+	nand@0 {
+		reg = <0>;
+		marvell,rb = <0>;
+		marvell,nand-keep-config;
+		nand-on-flash-bbt;
+		nand-ecc-strength = <8>;
+		nand-ecc-step-size = <512>;
+
+		partitions {
+			compatible = "fixed-partitions";
+			#address-cells = <1>;
+			#size-cells = <1>;
+
+			partition@0 {
+				label = "U-Boot";
+				reg = <0 0x800000>;
+			};
+			partition@800000 {
+				label = "Linux";
+				reg = <0x800000 0x800000>;
+			};
+			partition@1000000 {
+				label = "Filesystem";
+				reg = <0x1000000 0x3f000000>;
+			};
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/armada-39x.dtsi b/arch/arm/boot/dts/armada-39x.dtsi
index ea657071e278..58299c97cf9f 100644
--- a/arch/arm/boot/dts/armada-39x.dtsi
+++ b/arch/arm/boot/dts/armada-39x.dtsi
@@ -404,11 +404,11 @@
 				interrupts = <GIC_SPI 21 IRQ_TYPE_LEVEL_HIGH>;
 			};
 
-			flash@d0000 {
-				compatible = "marvell,armada370-nand";
+			nand_controller: nand-controller@d0000 {
+				compatible = "marvell,armada370-nand-controller";
 				reg = <0xd0000 0x54>;
 				#address-cells = <1>;
-				#size-cells = <1>;
+				#size-cells = <0>;
 				interrupts = <GIC_SPI 84 IRQ_TYPE_LEVEL_HIGH>;
 				clocks = <&coredivclk 0>;
 				status = "disabled";
-- 
2.11.0

^ permalink raw reply related	[flat|nested] 75+ messages in thread

* [RFC 10/12] ARM: dts: armada-39x: use reworked NAND controller driver
@ 2017-10-18 14:36   ` Miquel Raynal
  0 siblings, 0 replies; 75+ messages in thread
From: Miquel Raynal @ 2017-10-18 14:36 UTC (permalink / raw)
  To: Andrew Lunn, bcm-kernel-feedback-list, Boris Brezillon,
	Brian Norris, Catalin Marinas, Chen-Yu Tsai, Cyrille Pitchen,
	Daniel Mack, David Woodhouse, devel, devicetree, Ezequiel Garcia,
	Greg Kroah-Hartman, Gregory Clement, Han Xu, Haojian Zhuang,
	Jason Cooper, Josh Wu, Kamal Dasu, linux-arm-kernel,
	linux-kernel, linux-mediatek, linux-mtd
  Cc: Hanna Hawa, Antoine Tenart, Nadav Haklai, Neta Zur Hershkovits,
	Igor Grinberg, Miquel Raynal, Ofer Heifetz

Change the bindings to use the reworked Marvell NAND controller driver.
Also adapt the nand controller node organization to distinguish which
property is relevant for the controller, and which one is NAND chip
specific. Expose the partitions as a subnode of the NAND chip.

Remove the 'marvell,nand-enable-arbiter' property, inefective with
Armada boards.

Signed-off-by: Miquel Raynal <miquel.raynal@free-electrons.com>
---
 arch/arm/boot/dts/armada-390-db.dts | 65 +++++++++++++++++----------------
 arch/arm/boot/dts/armada-395-gp.dts | 73 +++++++++++++++++++------------------
 arch/arm/boot/dts/armada-398-db.dts | 59 +++++++++++++++++-------------
 arch/arm/boot/dts/armada-39x.dtsi   |  6 +--
 4 files changed, 109 insertions(+), 94 deletions(-)

diff --git a/arch/arm/boot/dts/armada-390-db.dts b/arch/arm/boot/dts/armada-390-db.dts
index c718a5242595..5a8b05da80f2 100644
--- a/arch/arm/boot/dts/armada-390-db.dts
+++ b/arch/arm/boot/dts/armada-390-db.dts
@@ -86,37 +86,6 @@
 				status = "okay";
 			};
 
-			flash@d0000 {
-				status = "okay";
-				pinctrl-0 = <&nand_pins>;
-				pinctrl-names = "default";
-				num-cs = <1>;
-				marvell,nand-keep-config;
-				marvell,nand-enable-arbiter;
-				nand-on-flash-bbt;
-				nand-ecc-strength = <8>;
-				nand-ecc-step-size = <512>;
-
-				partitions {
-					compatible = "fixed-partitions";
-					#address-cells = <1>;
-					#size-cells = <1>;
-
-					partition@0 {
-						label = "U-Boot";
-						reg = <0 0x800000>;
-					};
-					partition@800000 {
-						label = "Linux";
-						reg = <0x800000 0x800000>;
-					};
-					partition@1000000 {
-						label = "Filesystem";
-						reg = <0x1000000 0x3f000000>;
-					};
-				};
-			};
-
 			/* CON98 */
 			usb3@f8000 {
 				status = "okay";
@@ -173,3 +142,37 @@
 		};
 	};
 };
+
+&nand_controller {
+	status = "okay";
+	pinctrl-0 = <&nand_pins>;
+	pinctrl-names = "default";
+
+	nand@0 {
+		reg = <0>;
+		marvell,rb = <0>;
+		marvell,nand-keep-config;
+		nand-on-flash-bbt;
+		nand-ecc-strength = <8>;
+		nand-ecc-step-size = <512>;
+
+		partitions {
+			compatible = "fixed-partitions";
+			#address-cells = <1>;
+			#size-cells = <1>;
+
+			partition@0 {
+				label = "U-Boot";
+				reg = <0 0x800000>;
+			};
+			partition@800000 {
+				label = "Linux";
+				reg = <0x800000 0x800000>;
+			};
+			partition@1000000 {
+				label = "Filesystem";
+				reg = <0x1000000 0x3f000000>;
+			};
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/armada-395-gp.dts b/arch/arm/boot/dts/armada-395-gp.dts
index ef491b524fd6..6261bc6ea3d0 100644
--- a/arch/arm/boot/dts/armada-395-gp.dts
+++ b/arch/arm/boot/dts/armada-395-gp.dts
@@ -88,41 +88,6 @@
 				status = "okay";
 			};
 
-			flash@d0000 {
-				status = "okay";
-				pinctrl-0 = <&nand_pins>;
-				pinctrl-names = "default";
-				num-cs = <1>;
-				marvell,nand-keep-config;
-				marvell,nand-enable-arbiter;
-				nand-on-flash-bbt;
-				nand-ecc-strength = <4>;
-				nand-ecc-step-size = <512>;
-
-				partitions {
-					compatible = "fixed-partitions";
-					#address-cells = <1>;
-					#size-cells = <1>;
-
-					partition@0 {
-						label = "U-Boot";
-						reg = <0x00000000 0x00600000>;
-						read-only;
-					};
-
-					partition@800000 {
-						label = "uImage";
-						reg = <0x00600000 0x00400000>;
-						read-only;
-					};
-
-					partition@1000000 {
-						label = "Root";
-						reg = <0x00a00000 0x3f600000>;
-					};
-				};
-			};
-
 			/* CON18 */
 			sdhci@d8000 {
 				clock-frequency = <200000000>;
@@ -161,3 +126,41 @@
 		};
 	};
 };
+
+&nand_controller {
+	status = "okay";
+	pinctrl-0 = <&nand_pins>;
+	pinctrl-names = "default";
+
+	nand@0 {
+		reg = <0>;
+		marvell,rb = <0>;
+		marvell,nand-keep-config;
+		nand-on-flash-bbt;
+		nand-ecc-strength = <4>;
+		nand-ecc-step-size = <512>;
+
+		partitions {
+			compatible = "fixed-partitions";
+			#address-cells = <1>;
+			#size-cells = <1>;
+
+			partition@0 {
+				label = "U-Boot";
+				reg = <0x00000000 0x00600000>;
+				read-only;
+			};
+
+			partition@800000 {
+				label = "uImage";
+				reg = <0x00600000 0x00400000>;
+				read-only;
+			};
+
+			partition@1000000 {
+				label = "Root";
+				reg = <0x00a00000 0x3f600000>;
+			};
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/armada-398-db.dts b/arch/arm/boot/dts/armada-398-db.dts
index f0e0379f7619..f8ff3d161f84 100644
--- a/arch/arm/boot/dts/armada-398-db.dts
+++ b/arch/arm/boot/dts/armada-398-db.dts
@@ -88,31 +88,6 @@
 				status = "okay";
 			};
 
-			flash@d0000 {
-				status = "okay";
-				pinctrl-0 = <&nand_pins>;
-				pinctrl-names = "default";
-				num-cs = <1>;
-				marvell,nand-keep-config;
-				marvell,nand-enable-arbiter;
-				nand-on-flash-bbt;
-				nand-ecc-strength = <8>;
-				nand-ecc-step-size = <512>;
-
-				partition@0 {
-					label = "U-Boot";
-					reg = <0 0x800000>;
-				};
-				partition@800000 {
-					label = "Linux";
-					reg = <0x800000 0x800000>;
-				};
-				partition@1000000 {
-					label = "Filesystem";
-					reg = <0x1000000 0x3f000000>;
-				};
-			};
-
 			usb3@f8000 {
 				status = "okay";
 			};
@@ -159,3 +134,37 @@
 		};
 	};
 };
+
+&nand_controller {
+	status = "okay";
+	pinctrl-0 = <&nand_pins>;
+	pinctrl-names = "default";
+
+	nand@0 {
+		reg = <0>;
+		marvell,rb = <0>;
+		marvell,nand-keep-config;
+		nand-on-flash-bbt;
+		nand-ecc-strength = <8>;
+		nand-ecc-step-size = <512>;
+
+		partitions {
+			compatible = "fixed-partitions";
+			#address-cells = <1>;
+			#size-cells = <1>;
+
+			partition@0 {
+				label = "U-Boot";
+				reg = <0 0x800000>;
+			};
+			partition@800000 {
+				label = "Linux";
+				reg = <0x800000 0x800000>;
+			};
+			partition@1000000 {
+				label = "Filesystem";
+				reg = <0x1000000 0x3f000000>;
+			};
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/armada-39x.dtsi b/arch/arm/boot/dts/armada-39x.dtsi
index ea657071e278..58299c97cf9f 100644
--- a/arch/arm/boot/dts/armada-39x.dtsi
+++ b/arch/arm/boot/dts/armada-39x.dtsi
@@ -404,11 +404,11 @@
 				interrupts = <GIC_SPI 21 IRQ_TYPE_LEVEL_HIGH>;
 			};
 
-			flash@d0000 {
-				compatible = "marvell,armada370-nand";
+			nand_controller: nand-controller@d0000 {
+				compatible = "marvell,armada370-nand-controller";
 				reg = <0xd0000 0x54>;
 				#address-cells = <1>;
-				#size-cells = <1>;
+				#size-cells = <0>;
 				interrupts = <GIC_SPI 84 IRQ_TYPE_LEVEL_HIGH>;
 				clocks = <&coredivclk 0>;
 				status = "disabled";
-- 
2.11.0

^ permalink raw reply related	[flat|nested] 75+ messages in thread

* [RFC 10/12] ARM: dts: armada-39x: use reworked NAND controller driver
@ 2017-10-18 14:36   ` Miquel Raynal
  0 siblings, 0 replies; 75+ messages in thread
From: Miquel Raynal @ 2017-10-18 14:36 UTC (permalink / raw)
  To: linux-arm-kernel

Change the bindings to use the reworked Marvell NAND controller driver.
Also adapt the nand controller node organization to distinguish which
property is relevant for the controller, and which one is NAND chip
specific. Expose the partitions as a subnode of the NAND chip.

Remove the 'marvell,nand-enable-arbiter' property, inefective with
Armada boards.

Signed-off-by: Miquel Raynal <miquel.raynal@free-electrons.com>
---
 arch/arm/boot/dts/armada-390-db.dts | 65 +++++++++++++++++----------------
 arch/arm/boot/dts/armada-395-gp.dts | 73 +++++++++++++++++++------------------
 arch/arm/boot/dts/armada-398-db.dts | 59 +++++++++++++++++-------------
 arch/arm/boot/dts/armada-39x.dtsi   |  6 +--
 4 files changed, 109 insertions(+), 94 deletions(-)

diff --git a/arch/arm/boot/dts/armada-390-db.dts b/arch/arm/boot/dts/armada-390-db.dts
index c718a5242595..5a8b05da80f2 100644
--- a/arch/arm/boot/dts/armada-390-db.dts
+++ b/arch/arm/boot/dts/armada-390-db.dts
@@ -86,37 +86,6 @@
 				status = "okay";
 			};
 
-			flash at d0000 {
-				status = "okay";
-				pinctrl-0 = <&nand_pins>;
-				pinctrl-names = "default";
-				num-cs = <1>;
-				marvell,nand-keep-config;
-				marvell,nand-enable-arbiter;
-				nand-on-flash-bbt;
-				nand-ecc-strength = <8>;
-				nand-ecc-step-size = <512>;
-
-				partitions {
-					compatible = "fixed-partitions";
-					#address-cells = <1>;
-					#size-cells = <1>;
-
-					partition at 0 {
-						label = "U-Boot";
-						reg = <0 0x800000>;
-					};
-					partition at 800000 {
-						label = "Linux";
-						reg = <0x800000 0x800000>;
-					};
-					partition at 1000000 {
-						label = "Filesystem";
-						reg = <0x1000000 0x3f000000>;
-					};
-				};
-			};
-
 			/* CON98 */
 			usb3 at f8000 {
 				status = "okay";
@@ -173,3 +142,37 @@
 		};
 	};
 };
+
+&nand_controller {
+	status = "okay";
+	pinctrl-0 = <&nand_pins>;
+	pinctrl-names = "default";
+
+	nand at 0 {
+		reg = <0>;
+		marvell,rb = <0>;
+		marvell,nand-keep-config;
+		nand-on-flash-bbt;
+		nand-ecc-strength = <8>;
+		nand-ecc-step-size = <512>;
+
+		partitions {
+			compatible = "fixed-partitions";
+			#address-cells = <1>;
+			#size-cells = <1>;
+
+			partition at 0 {
+				label = "U-Boot";
+				reg = <0 0x800000>;
+			};
+			partition at 800000 {
+				label = "Linux";
+				reg = <0x800000 0x800000>;
+			};
+			partition at 1000000 {
+				label = "Filesystem";
+				reg = <0x1000000 0x3f000000>;
+			};
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/armada-395-gp.dts b/arch/arm/boot/dts/armada-395-gp.dts
index ef491b524fd6..6261bc6ea3d0 100644
--- a/arch/arm/boot/dts/armada-395-gp.dts
+++ b/arch/arm/boot/dts/armada-395-gp.dts
@@ -88,41 +88,6 @@
 				status = "okay";
 			};
 
-			flash at d0000 {
-				status = "okay";
-				pinctrl-0 = <&nand_pins>;
-				pinctrl-names = "default";
-				num-cs = <1>;
-				marvell,nand-keep-config;
-				marvell,nand-enable-arbiter;
-				nand-on-flash-bbt;
-				nand-ecc-strength = <4>;
-				nand-ecc-step-size = <512>;
-
-				partitions {
-					compatible = "fixed-partitions";
-					#address-cells = <1>;
-					#size-cells = <1>;
-
-					partition at 0 {
-						label = "U-Boot";
-						reg = <0x00000000 0x00600000>;
-						read-only;
-					};
-
-					partition at 800000 {
-						label = "uImage";
-						reg = <0x00600000 0x00400000>;
-						read-only;
-					};
-
-					partition at 1000000 {
-						label = "Root";
-						reg = <0x00a00000 0x3f600000>;
-					};
-				};
-			};
-
 			/* CON18 */
 			sdhci at d8000 {
 				clock-frequency = <200000000>;
@@ -161,3 +126,41 @@
 		};
 	};
 };
+
+&nand_controller {
+	status = "okay";
+	pinctrl-0 = <&nand_pins>;
+	pinctrl-names = "default";
+
+	nand at 0 {
+		reg = <0>;
+		marvell,rb = <0>;
+		marvell,nand-keep-config;
+		nand-on-flash-bbt;
+		nand-ecc-strength = <4>;
+		nand-ecc-step-size = <512>;
+
+		partitions {
+			compatible = "fixed-partitions";
+			#address-cells = <1>;
+			#size-cells = <1>;
+
+			partition at 0 {
+				label = "U-Boot";
+				reg = <0x00000000 0x00600000>;
+				read-only;
+			};
+
+			partition at 800000 {
+				label = "uImage";
+				reg = <0x00600000 0x00400000>;
+				read-only;
+			};
+
+			partition at 1000000 {
+				label = "Root";
+				reg = <0x00a00000 0x3f600000>;
+			};
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/armada-398-db.dts b/arch/arm/boot/dts/armada-398-db.dts
index f0e0379f7619..f8ff3d161f84 100644
--- a/arch/arm/boot/dts/armada-398-db.dts
+++ b/arch/arm/boot/dts/armada-398-db.dts
@@ -88,31 +88,6 @@
 				status = "okay";
 			};
 
-			flash at d0000 {
-				status = "okay";
-				pinctrl-0 = <&nand_pins>;
-				pinctrl-names = "default";
-				num-cs = <1>;
-				marvell,nand-keep-config;
-				marvell,nand-enable-arbiter;
-				nand-on-flash-bbt;
-				nand-ecc-strength = <8>;
-				nand-ecc-step-size = <512>;
-
-				partition at 0 {
-					label = "U-Boot";
-					reg = <0 0x800000>;
-				};
-				partition at 800000 {
-					label = "Linux";
-					reg = <0x800000 0x800000>;
-				};
-				partition at 1000000 {
-					label = "Filesystem";
-					reg = <0x1000000 0x3f000000>;
-				};
-			};
-
 			usb3 at f8000 {
 				status = "okay";
 			};
@@ -159,3 +134,37 @@
 		};
 	};
 };
+
+&nand_controller {
+	status = "okay";
+	pinctrl-0 = <&nand_pins>;
+	pinctrl-names = "default";
+
+	nand at 0 {
+		reg = <0>;
+		marvell,rb = <0>;
+		marvell,nand-keep-config;
+		nand-on-flash-bbt;
+		nand-ecc-strength = <8>;
+		nand-ecc-step-size = <512>;
+
+		partitions {
+			compatible = "fixed-partitions";
+			#address-cells = <1>;
+			#size-cells = <1>;
+
+			partition at 0 {
+				label = "U-Boot";
+				reg = <0 0x800000>;
+			};
+			partition at 800000 {
+				label = "Linux";
+				reg = <0x800000 0x800000>;
+			};
+			partition at 1000000 {
+				label = "Filesystem";
+				reg = <0x1000000 0x3f000000>;
+			};
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/armada-39x.dtsi b/arch/arm/boot/dts/armada-39x.dtsi
index ea657071e278..58299c97cf9f 100644
--- a/arch/arm/boot/dts/armada-39x.dtsi
+++ b/arch/arm/boot/dts/armada-39x.dtsi
@@ -404,11 +404,11 @@
 				interrupts = <GIC_SPI 21 IRQ_TYPE_LEVEL_HIGH>;
 			};
 
-			flash at d0000 {
-				compatible = "marvell,armada370-nand";
+			nand_controller: nand-controller at d0000 {
+				compatible = "marvell,armada370-nand-controller";
 				reg = <0xd0000 0x54>;
 				#address-cells = <1>;
-				#size-cells = <1>;
+				#size-cells = <0>;
 				interrupts = <GIC_SPI 84 IRQ_TYPE_LEVEL_HIGH>;
 				clocks = <&coredivclk 0>;
 				status = "disabled";
-- 
2.11.0

^ permalink raw reply related	[flat|nested] 75+ messages in thread

* [RFC 11/12] ARM: dts: pxa: use reworked NAND controller driver
  2017-10-18 14:36 ` Miquel Raynal
  (?)
@ 2017-10-18 14:36   ` Miquel Raynal
  -1 siblings, 0 replies; 75+ messages in thread
From: Miquel Raynal @ 2017-10-18 14:36 UTC (permalink / raw)
  To: Andrew Lunn, bcm-kernel-feedback-list, Boris Brezillon,
	Brian Norris, Catalin Marinas, Chen-Yu Tsai, Cyrille Pitchen,
	Daniel Mack, David Woodhouse, devel, devicetree, Ezequiel Garcia,
	Greg Kroah-Hartman, Gregory Clement, Han Xu, Haojian Zhuang,
	Jason Cooper, Josh Wu, Kamal Dasu, linux-arm-kernel,
	linux-kernel, linux-mediatek, linux-mtd, Marc Gonzalez,
	Marek Vasut, Mark Rutland, Masahiro Yamada, Matthias Brugger,
	Maxime Ripard, Maxim Levitsky, Richard Weinberger,
	Robert Jarzmik, Rob Herring, Russell King, Sebastian Hesselbarth,
	Stefan Agner, Sylvain Lemieux, Vladimir Zapolskiy, Wenyou Yang,
	Will Deacon
  Cc: Thomas Petazzoni, Antoine Tenart, Miquel Raynal, Igor Grinberg,
	Nadav Haklai, Ofer Heifetz, Neta Zur Hershkovits, Hanna Hawa

Change the bindings to use the reworked Marvell NAND controller driver.
Also adapt the nand controller node organization to distinguish which
property is relevant for the controller, and which one is NAND chip
specific. Expose the partitions as a subnode of the NAND chip.

Signed-off-by: Miquel Raynal <miquel.raynal@free-electrons.com>
---
 arch/arm/boot/dts/pxa3xx.dtsi | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/arch/arm/boot/dts/pxa3xx.dtsi b/arch/arm/boot/dts/pxa3xx.dtsi
index 7a0cc4ea819a..e7cde732bf88 100644
--- a/arch/arm/boot/dts/pxa3xx.dtsi
+++ b/arch/arm/boot/dts/pxa3xx.dtsi
@@ -116,15 +116,15 @@
 			status = "disabled";
 		};
 
-		nand0: nand@43100000 {
-			compatible = "marvell,pxa3xx-nand";
+		nand_controller: nand-controller@43100000 {
+			compatible = "marvell,pxa3xx-nand-controller";
 			reg = <0x43100000 90>;
 			interrupts = <45>;
 			clocks = <&clks CLK_NAND>;
 			dmas = <&pdma 97 3>;
 			dma-names = "data";
 			#address-cells = <1>;
-			#size-cells = <1>;	
+			#size-cells = <0>;
 			status = "disabled";
 		};
 
-- 
2.11.0

^ permalink raw reply related	[flat|nested] 75+ messages in thread

* [RFC 11/12] ARM: dts: pxa: use reworked NAND controller driver
@ 2017-10-18 14:36   ` Miquel Raynal
  0 siblings, 0 replies; 75+ messages in thread
From: Miquel Raynal @ 2017-10-18 14:36 UTC (permalink / raw)
  To: Andrew Lunn, bcm-kernel-feedback-list, Boris Brezillon,
	Brian Norris, Catalin Marinas, Chen-Yu Tsai, Cyrille Pitchen,
	Daniel Mack, David Woodhouse, devel, devicetree, Ezequiel Garcia,
	Greg Kroah-Hartman, Gregory Clement, Han Xu, Haojian Zhuang,
	Jason Cooper, Josh Wu, Kamal Dasu, linux-arm-kernel,
	linux-kernel, linux-mediatek, linux-mtd
  Cc: Hanna Hawa, Antoine Tenart, Nadav Haklai, Neta Zur Hershkovits,
	Igor Grinberg, Miquel Raynal, Ofer Heifetz

Change the bindings to use the reworked Marvell NAND controller driver.
Also adapt the nand controller node organization to distinguish which
property is relevant for the controller, and which one is NAND chip
specific. Expose the partitions as a subnode of the NAND chip.

Signed-off-by: Miquel Raynal <miquel.raynal@free-electrons.com>
---
 arch/arm/boot/dts/pxa3xx.dtsi | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/arch/arm/boot/dts/pxa3xx.dtsi b/arch/arm/boot/dts/pxa3xx.dtsi
index 7a0cc4ea819a..e7cde732bf88 100644
--- a/arch/arm/boot/dts/pxa3xx.dtsi
+++ b/arch/arm/boot/dts/pxa3xx.dtsi
@@ -116,15 +116,15 @@
 			status = "disabled";
 		};
 
-		nand0: nand@43100000 {
-			compatible = "marvell,pxa3xx-nand";
+		nand_controller: nand-controller@43100000 {
+			compatible = "marvell,pxa3xx-nand-controller";
 			reg = <0x43100000 90>;
 			interrupts = <45>;
 			clocks = <&clks CLK_NAND>;
 			dmas = <&pdma 97 3>;
 			dma-names = "data";
 			#address-cells = <1>;
-			#size-cells = <1>;	
+			#size-cells = <0>;
 			status = "disabled";
 		};
 
-- 
2.11.0

^ permalink raw reply related	[flat|nested] 75+ messages in thread

* [RFC 11/12] ARM: dts: pxa: use reworked NAND controller driver
@ 2017-10-18 14:36   ` Miquel Raynal
  0 siblings, 0 replies; 75+ messages in thread
From: Miquel Raynal @ 2017-10-18 14:36 UTC (permalink / raw)
  To: linux-arm-kernel

Change the bindings to use the reworked Marvell NAND controller driver.
Also adapt the nand controller node organization to distinguish which
property is relevant for the controller, and which one is NAND chip
specific. Expose the partitions as a subnode of the NAND chip.

Signed-off-by: Miquel Raynal <miquel.raynal@free-electrons.com>
---
 arch/arm/boot/dts/pxa3xx.dtsi | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/arch/arm/boot/dts/pxa3xx.dtsi b/arch/arm/boot/dts/pxa3xx.dtsi
index 7a0cc4ea819a..e7cde732bf88 100644
--- a/arch/arm/boot/dts/pxa3xx.dtsi
+++ b/arch/arm/boot/dts/pxa3xx.dtsi
@@ -116,15 +116,15 @@
 			status = "disabled";
 		};
 
-		nand0: nand at 43100000 {
-			compatible = "marvell,pxa3xx-nand";
+		nand_controller: nand-controller at 43100000 {
+			compatible = "marvell,pxa3xx-nand-controller";
 			reg = <0x43100000 90>;
 			interrupts = <45>;
 			clocks = <&clks CLK_NAND>;
 			dmas = <&pdma 97 3>;
 			dma-names = "data";
 			#address-cells = <1>;
-			#size-cells = <1>;	
+			#size-cells = <0>;
 			status = "disabled";
 		};
 
-- 
2.11.0

^ permalink raw reply related	[flat|nested] 75+ messages in thread

* [RFC 12/12] ARM64: dts: marvell: use reworked NAND controller driver on Armada 7K/8K
  2017-10-18 14:36 ` Miquel Raynal
  (?)
@ 2017-10-18 14:36   ` Miquel Raynal
  -1 siblings, 0 replies; 75+ messages in thread
From: Miquel Raynal @ 2017-10-18 14:36 UTC (permalink / raw)
  To: Andrew Lunn, bcm-kernel-feedback-list, Boris Brezillon,
	Brian Norris, Catalin Marinas, Chen-Yu Tsai, Cyrille Pitchen,
	Daniel Mack, David Woodhouse, devel, devicetree, Ezequiel Garcia,
	Greg Kroah-Hartman, Gregory Clement, Han Xu, Haojian Zhuang,
	Jason Cooper, Josh Wu, Kamal Dasu, linux-arm-kernel,
	linux-kernel, linux-mediatek, linux-mtd, Marc Gonzalez,
	Marek Vasut, Mark Rutland, Masahiro Yamada, Matthias Brugger,
	Maxime Ripard, Maxim Levitsky, Richard Weinberger,
	Robert Jarzmik, Rob Herring, Russell King, Sebastian Hesselbarth,
	Stefan Agner, Sylvain Lemieux, Vladimir Zapolskiy, Wenyou Yang,
	Will Deacon
  Cc: Thomas Petazzoni, Antoine Tenart, Miquel Raynal, Igor Grinberg,
	Nadav Haklai, Ofer Heifetz, Neta Zur Hershkovits, Hanna Hawa

Change the bindings to use the reworked Marvell NAND controller driver.
Also adapt the nand controller node organization to distinguish which
property is relevant for the controller, and which one is NAND chip
specific. Expose the partitions as a subnode of the NAND chip.

Remove the 'marvell,nand-enable-arbiter' property, inefective with
Armada boards.

Signed-off-by: Miquel Raynal <miquel.raynal@free-electrons.com>
---
 arch/arm64/boot/dts/marvell/armada-7040-db.dts     | 47 +++++++++++++---------
 .../boot/dts/marvell/armada-cp110-master.dtsi      |  6 +--
 .../arm64/boot/dts/marvell/armada-cp110-slave.dtsi |  6 +--
 3 files changed, 34 insertions(+), 25 deletions(-)

diff --git a/arch/arm64/boot/dts/marvell/armada-7040-db.dts b/arch/arm64/boot/dts/marvell/armada-7040-db.dts
index 3cb4e81f6415..cd0290613eea 100644
--- a/arch/arm64/boot/dts/marvell/armada-7040-db.dts
+++ b/arch/arm64/boot/dts/marvell/armada-7040-db.dts
@@ -144,32 +144,41 @@
 	};
 };
 
-&cpm_nand {
+&cpm_nand_controller {
 	/*
 	 * SPI on CPM and NAND have common pins on this board. We can
-	 * use only one at a time. To enable the NAND (whihch will
+	 * use only one at a time. To enable the NAND (which will
 	 * disable the SPI), the "status = "okay";" line have to be
 	 * added here.
 	 */
-	num-cs = <1>;
 	pinctrl-0 = <&nand_pins>, <&nand_rb>;
 	pinctrl-names = "default";
-	nand-ecc-strength = <4>;
-	nand-ecc-step-size = <512>;
-	marvell,nand-enable-arbiter;
-	nand-on-flash-bbt;
-
-	partition@0 {
-		label = "U-Boot";
-		reg = <0 0x200000>;
-	};
-	partition@200000 {
-		label = "Linux";
-		reg = <0x200000 0xe00000>;
-	};
-	partition@1000000 {
-		label = "Filesystem";
-		reg = <0x1000000 0x3f000000>;
+
+	nand@0 {
+		reg = <0>;
+		marvell,rb = <0>;
+		nand-on-flash-bbt;
+		nand-ecc-strength = <4>;
+		nand-ecc-step-size = <512>;
+
+		partitions {
+
+			partition@0 {
+				label = "U-Boot";
+				reg = <0 0x200000>;
+			};
+
+			partition@200000 {
+				label = "Linux";
+				reg = <0x200000 0xe00000>;
+			};
+
+			partition@1000000 {
+				label = "Filesystem";
+				reg = <0x1000000 0x3f000000>;
+			};
+
+		};
 	};
 };
 
diff --git a/arch/arm64/boot/dts/marvell/armada-cp110-master.dtsi b/arch/arm64/boot/dts/marvell/armada-cp110-master.dtsi
index f203f7300194..c209f2f93b6c 100644
--- a/arch/arm64/boot/dts/marvell/armada-cp110-master.dtsi
+++ b/arch/arm64/boot/dts/marvell/armada-cp110-master.dtsi
@@ -268,14 +268,14 @@
 				status = "disabled";
 			};
 
-			cpm_nand: nand@720000 {
+			cpm_nand_controller: nand@720000 {
 				/*
 				 * Due to the limiation of the pin available
 				 * this controller is only usable on the CPM
 				 * for A7K and on the CPS for A8K.
 				 */
-				compatible = "marvell,armada-8k-nand",
-					     "marvell,armada370-nand";
+				compatible = "marvell,armada-8k-nand-controller",
+					     "marvell,armada370-nand-controller";
 				reg = <0x720000 0x54>;
 				#address-cells = <1>;
 				#size-cells = <1>;
diff --git a/arch/arm64/boot/dts/marvell/armada-cp110-slave.dtsi b/arch/arm64/boot/dts/marvell/armada-cp110-slave.dtsi
index 7d78767d3024..22789485d77e 100644
--- a/arch/arm64/boot/dts/marvell/armada-cp110-slave.dtsi
+++ b/arch/arm64/boot/dts/marvell/armada-cp110-slave.dtsi
@@ -269,14 +269,14 @@
 				status = "disabled";
 			};
 
-			cps_nand: nand@720000 {
+			cps_nand_controller: nand@720000 {
 				/*
 				 * Due to the limiation of the pin available
 				 * this controller is only usable on the CPM
 				 * for A7K and on the CPS for A8K.
 				 */
-				compatible = "marvell,armada370-nand",
-					     "marvell,armada370-nand";
+				compatible = "marvell,armada-8k-nand-controller",
+					     "marvell,armada370-nand-controller";
 				reg = <0x720000 0x54>;
 				#address-cells = <1>;
 				#size-cells = <1>;
-- 
2.11.0

^ permalink raw reply related	[flat|nested] 75+ messages in thread

* [RFC 12/12] ARM64: dts: marvell: use reworked NAND controller driver on Armada 7K/8K
@ 2017-10-18 14:36   ` Miquel Raynal
  0 siblings, 0 replies; 75+ messages in thread
From: Miquel Raynal @ 2017-10-18 14:36 UTC (permalink / raw)
  To: Andrew Lunn, bcm-kernel-feedback-list, Boris Brezillon,
	Brian Norris, Catalin Marinas, Chen-Yu Tsai, Cyrille Pitchen,
	Daniel Mack, David Woodhouse, devel, devicetree, Ezequiel Garcia,
	Greg Kroah-Hartman, Gregory Clement, Han Xu, Haojian Zhuang,
	Jason Cooper, Josh Wu, Kamal Dasu
  Cc: Thomas Petazzoni, Antoine Tenart, Miquel Raynal, Igor Grinberg,
	Nadav Haklai, Ofer Heifetz, Neta Zur Hershkovits, Hanna Hawa

Change the bindings to use the reworked Marvell NAND controller driver.
Also adapt the nand controller node organization to distinguish which
property is relevant for the controller, and which one is NAND chip
specific. Expose the partitions as a subnode of the NAND chip.

Remove the 'marvell,nand-enable-arbiter' property, inefective with
Armada boards.

Signed-off-by: Miquel Raynal <miquel.raynal@free-electrons.com>
---
 arch/arm64/boot/dts/marvell/armada-7040-db.dts     | 47 +++++++++++++---------
 .../boot/dts/marvell/armada-cp110-master.dtsi      |  6 +--
 .../arm64/boot/dts/marvell/armada-cp110-slave.dtsi |  6 +--
 3 files changed, 34 insertions(+), 25 deletions(-)

diff --git a/arch/arm64/boot/dts/marvell/armada-7040-db.dts b/arch/arm64/boot/dts/marvell/armada-7040-db.dts
index 3cb4e81f6415..cd0290613eea 100644
--- a/arch/arm64/boot/dts/marvell/armada-7040-db.dts
+++ b/arch/arm64/boot/dts/marvell/armada-7040-db.dts
@@ -144,32 +144,41 @@
 	};
 };
 
-&cpm_nand {
+&cpm_nand_controller {
 	/*
 	 * SPI on CPM and NAND have common pins on this board. We can
-	 * use only one at a time. To enable the NAND (whihch will
+	 * use only one at a time. To enable the NAND (which will
 	 * disable the SPI), the "status = "okay";" line have to be
 	 * added here.
 	 */
-	num-cs = <1>;
 	pinctrl-0 = <&nand_pins>, <&nand_rb>;
 	pinctrl-names = "default";
-	nand-ecc-strength = <4>;
-	nand-ecc-step-size = <512>;
-	marvell,nand-enable-arbiter;
-	nand-on-flash-bbt;
-
-	partition@0 {
-		label = "U-Boot";
-		reg = <0 0x200000>;
-	};
-	partition@200000 {
-		label = "Linux";
-		reg = <0x200000 0xe00000>;
-	};
-	partition@1000000 {
-		label = "Filesystem";
-		reg = <0x1000000 0x3f000000>;
+
+	nand@0 {
+		reg = <0>;
+		marvell,rb = <0>;
+		nand-on-flash-bbt;
+		nand-ecc-strength = <4>;
+		nand-ecc-step-size = <512>;
+
+		partitions {
+
+			partition@0 {
+				label = "U-Boot";
+				reg = <0 0x200000>;
+			};
+
+			partition@200000 {
+				label = "Linux";
+				reg = <0x200000 0xe00000>;
+			};
+
+			partition@1000000 {
+				label = "Filesystem";
+				reg = <0x1000000 0x3f000000>;
+			};
+
+		};
 	};
 };
 
diff --git a/arch/arm64/boot/dts/marvell/armada-cp110-master.dtsi b/arch/arm64/boot/dts/marvell/armada-cp110-master.dtsi
index f203f7300194..c209f2f93b6c 100644
--- a/arch/arm64/boot/dts/marvell/armada-cp110-master.dtsi
+++ b/arch/arm64/boot/dts/marvell/armada-cp110-master.dtsi
@@ -268,14 +268,14 @@
 				status = "disabled";
 			};
 
-			cpm_nand: nand@720000 {
+			cpm_nand_controller: nand@720000 {
 				/*
 				 * Due to the limiation of the pin available
 				 * this controller is only usable on the CPM
 				 * for A7K and on the CPS for A8K.
 				 */
-				compatible = "marvell,armada-8k-nand",
-					     "marvell,armada370-nand";
+				compatible = "marvell,armada-8k-nand-controller",
+					     "marvell,armada370-nand-controller";
 				reg = <0x720000 0x54>;
 				#address-cells = <1>;
 				#size-cells = <1>;
diff --git a/arch/arm64/boot/dts/marvell/armada-cp110-slave.dtsi b/arch/arm64/boot/dts/marvell/armada-cp110-slave.dtsi
index 7d78767d3024..22789485d77e 100644
--- a/arch/arm64/boot/dts/marvell/armada-cp110-slave.dtsi
+++ b/arch/arm64/boot/dts/marvell/armada-cp110-slave.dtsi
@@ -269,14 +269,14 @@
 				status = "disabled";
 			};
 
-			cps_nand: nand@720000 {
+			cps_nand_controller: nand@720000 {
 				/*
 				 * Due to the limiation of the pin available
 				 * this controller is only usable on the CPM
 				 * for A7K and on the CPS for A8K.
 				 */
-				compatible = "marvell,armada370-nand",
-					     "marvell,armada370-nand";
+				compatible = "marvell,armada-8k-nand-controller",
+					     "marvell,armada370-nand-controller";
 				reg = <0x720000 0x54>;
 				#address-cells = <1>;
 				#size-cells = <1>;
-- 
2.11.0

^ permalink raw reply related	[flat|nested] 75+ messages in thread

* [RFC 12/12] ARM64: dts: marvell: use reworked NAND controller driver on Armada 7K/8K
  2017-10-18 14:36 ` Miquel Raynal
                   ` (12 preceding siblings ...)
  (?)
@ 2017-10-18 14:36 ` Miquel Raynal
  -1 siblings, 0 replies; 75+ messages in thread
From: Miquel Raynal @ 2017-10-18 14:36 UTC (permalink / raw)
  To: Andrew Lunn, bcm-kernel-feedback-list, Boris Brezillon,
	Brian Norris, Catalin Marinas, Chen-Yu Tsai, Cyrille Pitchen,
	Daniel Mack, David Woodhouse, devel, devicetree, Ezequiel Garcia,
	Greg Kroah-Hartman, Gregory Clement, Han Xu, Haojian Zhuang,
	Jason Cooper, Josh Wu, Kamal Dasu, linux-arm-kernel,
	linux-kernel, linux-mediatek, linux-mtd
  Cc: Hanna Hawa, Antoine Tenart, Nadav Haklai, Neta Zur Hershkovits,
	Igor Grinberg, Miquel Raynal, Ofer Heifetz

Change the bindings to use the reworked Marvell NAND controller driver.
Also adapt the nand controller node organization to distinguish which
property is relevant for the controller, and which one is NAND chip
specific. Expose the partitions as a subnode of the NAND chip.

Remove the 'marvell,nand-enable-arbiter' property, inefective with
Armada boards.

Signed-off-by: Miquel Raynal <miquel.raynal@free-electrons.com>
---
 arch/arm64/boot/dts/marvell/armada-7040-db.dts     | 47 +++++++++++++---------
 .../boot/dts/marvell/armada-cp110-master.dtsi      |  6 +--
 .../arm64/boot/dts/marvell/armada-cp110-slave.dtsi |  6 +--
 3 files changed, 34 insertions(+), 25 deletions(-)

diff --git a/arch/arm64/boot/dts/marvell/armada-7040-db.dts b/arch/arm64/boot/dts/marvell/armada-7040-db.dts
index 3cb4e81f6415..cd0290613eea 100644
--- a/arch/arm64/boot/dts/marvell/armada-7040-db.dts
+++ b/arch/arm64/boot/dts/marvell/armada-7040-db.dts
@@ -144,32 +144,41 @@
 	};
 };
 
-&cpm_nand {
+&cpm_nand_controller {
 	/*
 	 * SPI on CPM and NAND have common pins on this board. We can
-	 * use only one at a time. To enable the NAND (whihch will
+	 * use only one at a time. To enable the NAND (which will
 	 * disable the SPI), the "status = "okay";" line have to be
 	 * added here.
 	 */
-	num-cs = <1>;
 	pinctrl-0 = <&nand_pins>, <&nand_rb>;
 	pinctrl-names = "default";
-	nand-ecc-strength = <4>;
-	nand-ecc-step-size = <512>;
-	marvell,nand-enable-arbiter;
-	nand-on-flash-bbt;
-
-	partition@0 {
-		label = "U-Boot";
-		reg = <0 0x200000>;
-	};
-	partition@200000 {
-		label = "Linux";
-		reg = <0x200000 0xe00000>;
-	};
-	partition@1000000 {
-		label = "Filesystem";
-		reg = <0x1000000 0x3f000000>;
+
+	nand@0 {
+		reg = <0>;
+		marvell,rb = <0>;
+		nand-on-flash-bbt;
+		nand-ecc-strength = <4>;
+		nand-ecc-step-size = <512>;
+
+		partitions {
+
+			partition@0 {
+				label = "U-Boot";
+				reg = <0 0x200000>;
+			};
+
+			partition@200000 {
+				label = "Linux";
+				reg = <0x200000 0xe00000>;
+			};
+
+			partition@1000000 {
+				label = "Filesystem";
+				reg = <0x1000000 0x3f000000>;
+			};
+
+		};
 	};
 };
 
diff --git a/arch/arm64/boot/dts/marvell/armada-cp110-master.dtsi b/arch/arm64/boot/dts/marvell/armada-cp110-master.dtsi
index f203f7300194..c209f2f93b6c 100644
--- a/arch/arm64/boot/dts/marvell/armada-cp110-master.dtsi
+++ b/arch/arm64/boot/dts/marvell/armada-cp110-master.dtsi
@@ -268,14 +268,14 @@
 				status = "disabled";
 			};
 
-			cpm_nand: nand@720000 {
+			cpm_nand_controller: nand@720000 {
 				/*
 				 * Due to the limiation of the pin available
 				 * this controller is only usable on the CPM
 				 * for A7K and on the CPS for A8K.
 				 */
-				compatible = "marvell,armada-8k-nand",
-					     "marvell,armada370-nand";
+				compatible = "marvell,armada-8k-nand-controller",
+					     "marvell,armada370-nand-controller";
 				reg = <0x720000 0x54>;
 				#address-cells = <1>;
 				#size-cells = <1>;
diff --git a/arch/arm64/boot/dts/marvell/armada-cp110-slave.dtsi b/arch/arm64/boot/dts/marvell/armada-cp110-slave.dtsi
index 7d78767d3024..22789485d77e 100644
--- a/arch/arm64/boot/dts/marvell/armada-cp110-slave.dtsi
+++ b/arch/arm64/boot/dts/marvell/armada-cp110-slave.dtsi
@@ -269,14 +269,14 @@
 				status = "disabled";
 			};
 
-			cps_nand: nand@720000 {
+			cps_nand_controller: nand@720000 {
 				/*
 				 * Due to the limiation of the pin available
 				 * this controller is only usable on the CPM
 				 * for A7K and on the CPS for A8K.
 				 */
-				compatible = "marvell,armada370-nand",
-					     "marvell,armada370-nand";
+				compatible = "marvell,armada-8k-nand-controller",
+					     "marvell,armada370-nand-controller";
 				reg = <0x720000 0x54>;
 				#address-cells = <1>;
 				#size-cells = <1>;
-- 
2.11.0

^ permalink raw reply related	[flat|nested] 75+ messages in thread

* [RFC 12/12] ARM64: dts: marvell: use reworked NAND controller driver on Armada 7K/8K
       [not found] ` <20171018143629.29302-1-miquel.raynal-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8@public.gmane.org>
@ 2017-10-18 14:36   ` Miquel Raynal
  0 siblings, 0 replies; 75+ messages in thread
From: Miquel Raynal @ 2017-10-18 14:36 UTC (permalink / raw)
  To: Andrew Lunn, bcm-kernel-feedback-list-dY08KVG/lbpWk0Htik3J/w,
	Boris Brezillon, Brian Norris, Catalin Marinas, Chen-Yu Tsai,
	Cyrille Pitchen, Daniel Mack, David Woodhouse,
	devel-gWbeCf7V1WCQmaza687I9mD2FQJk+8+b,
	devicetree-u79uwXL29TY76Z2rM5mHXA, Ezequiel Garcia,
	Greg Kroah-Hartman, Gregory Clement, Han Xu, Haojian Zhuang,
	Jason Cooper, Josh Wu, Kamal Dasu,
	linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	linux-mediatek-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
	linux-mtd-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r
  Cc: Thomas Petazzoni, Hanna Hawa, Antoine Tenart, Nadav Haklai,
	Neta Zur Hershkovits, Igor Grinberg, Miquel Raynal, Ofer Heifetz

Change the bindings to use the reworked Marvell NAND controller driver.
Also adapt the nand controller node organization to distinguish which
property is relevant for the controller, and which one is NAND chip
specific. Expose the partitions as a subnode of the NAND chip.

Remove the 'marvell,nand-enable-arbiter' property, inefective with
Armada boards.

Signed-off-by: Miquel Raynal <miquel.raynal-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8@public.gmane.org>
---
 arch/arm64/boot/dts/marvell/armada-7040-db.dts     | 47 +++++++++++++---------
 .../boot/dts/marvell/armada-cp110-master.dtsi      |  6 +--
 .../arm64/boot/dts/marvell/armada-cp110-slave.dtsi |  6 +--
 3 files changed, 34 insertions(+), 25 deletions(-)

diff --git a/arch/arm64/boot/dts/marvell/armada-7040-db.dts b/arch/arm64/boot/dts/marvell/armada-7040-db.dts
index 3cb4e81f6415..cd0290613eea 100644
--- a/arch/arm64/boot/dts/marvell/armada-7040-db.dts
+++ b/arch/arm64/boot/dts/marvell/armada-7040-db.dts
@@ -144,32 +144,41 @@
 	};
 };
 
-&cpm_nand {
+&cpm_nand_controller {
 	/*
 	 * SPI on CPM and NAND have common pins on this board. We can
-	 * use only one at a time. To enable the NAND (whihch will
+	 * use only one at a time. To enable the NAND (which will
 	 * disable the SPI), the "status = "okay";" line have to be
 	 * added here.
 	 */
-	num-cs = <1>;
 	pinctrl-0 = <&nand_pins>, <&nand_rb>;
 	pinctrl-names = "default";
-	nand-ecc-strength = <4>;
-	nand-ecc-step-size = <512>;
-	marvell,nand-enable-arbiter;
-	nand-on-flash-bbt;
-
-	partition@0 {
-		label = "U-Boot";
-		reg = <0 0x200000>;
-	};
-	partition@200000 {
-		label = "Linux";
-		reg = <0x200000 0xe00000>;
-	};
-	partition@1000000 {
-		label = "Filesystem";
-		reg = <0x1000000 0x3f000000>;
+
+	nand@0 {
+		reg = <0>;
+		marvell,rb = <0>;
+		nand-on-flash-bbt;
+		nand-ecc-strength = <4>;
+		nand-ecc-step-size = <512>;
+
+		partitions {
+
+			partition@0 {
+				label = "U-Boot";
+				reg = <0 0x200000>;
+			};
+
+			partition@200000 {
+				label = "Linux";
+				reg = <0x200000 0xe00000>;
+			};
+
+			partition@1000000 {
+				label = "Filesystem";
+				reg = <0x1000000 0x3f000000>;
+			};
+
+		};
 	};
 };
 
diff --git a/arch/arm64/boot/dts/marvell/armada-cp110-master.dtsi b/arch/arm64/boot/dts/marvell/armada-cp110-master.dtsi
index f203f7300194..c209f2f93b6c 100644
--- a/arch/arm64/boot/dts/marvell/armada-cp110-master.dtsi
+++ b/arch/arm64/boot/dts/marvell/armada-cp110-master.dtsi
@@ -268,14 +268,14 @@
 				status = "disabled";
 			};
 
-			cpm_nand: nand@720000 {
+			cpm_nand_controller: nand@720000 {
 				/*
 				 * Due to the limiation of the pin available
 				 * this controller is only usable on the CPM
 				 * for A7K and on the CPS for A8K.
 				 */
-				compatible = "marvell,armada-8k-nand",
-					     "marvell,armada370-nand";
+				compatible = "marvell,armada-8k-nand-controller",
+					     "marvell,armada370-nand-controller";
 				reg = <0x720000 0x54>;
 				#address-cells = <1>;
 				#size-cells = <1>;
diff --git a/arch/arm64/boot/dts/marvell/armada-cp110-slave.dtsi b/arch/arm64/boot/dts/marvell/armada-cp110-slave.dtsi
index 7d78767d3024..22789485d77e 100644
--- a/arch/arm64/boot/dts/marvell/armada-cp110-slave.dtsi
+++ b/arch/arm64/boot/dts/marvell/armada-cp110-slave.dtsi
@@ -269,14 +269,14 @@
 				status = "disabled";
 			};
 
-			cps_nand: nand@720000 {
+			cps_nand_controller: nand@720000 {
 				/*
 				 * Due to the limiation of the pin available
 				 * this controller is only usable on the CPM
 				 * for A7K and on the CPS for A8K.
 				 */
-				compatible = "marvell,armada370-nand",
-					     "marvell,armada370-nand";
+				compatible = "marvell,armada-8k-nand-controller",
+					     "marvell,armada370-nand-controller";
 				reg = <0x720000 0x54>;
 				#address-cells = <1>;
 				#size-cells = <1>;
-- 
2.11.0

^ permalink raw reply related	[flat|nested] 75+ messages in thread

* [RFC 12/12] ARM64: dts: marvell: use reworked NAND controller driver on Armada 7K/8K
  2017-10-18 14:36 ` Miquel Raynal
                   ` (14 preceding siblings ...)
  (?)
@ 2017-10-18 14:36 ` Miquel Raynal
  -1 siblings, 0 replies; 75+ messages in thread
From: Miquel Raynal @ 2017-10-18 14:36 UTC (permalink / raw)
  To: Andrew Lunn, bcm-kernel-feedback-list, Boris Brezillon,
	Brian Norris, Catalin Marinas, Chen-Yu Tsai, Cyrille Pitchen,
	Daniel Mack, David Woodhouse, devel, devicetree, Ezequiel Garcia,
	Greg Kroah-Hartman, Gregory Clement, Han Xu, Haojian Zhuang,
	Jason Cooper, Josh Wu, Kamal Dasu, linux-arm-kernel,
	linux-kernel, linux-mediatek, linux-mtd
  Cc: Thomas Petazzoni, Hanna Hawa, Antoine Tenart, Nadav Haklai,
	Neta Zur Hershkovits, Igor Grinberg, Miquel Raynal, Ofer Heifetz

Change the bindings to use the reworked Marvell NAND controller driver.
Also adapt the nand controller node organization to distinguish which
property is relevant for the controller, and which one is NAND chip
specific. Expose the partitions as a subnode of the NAND chip.

Remove the 'marvell,nand-enable-arbiter' property, inefective with
Armada boards.

Signed-off-by: Miquel Raynal <miquel.raynal@free-electrons.com>
---
 arch/arm64/boot/dts/marvell/armada-7040-db.dts     | 47 +++++++++++++---------
 .../boot/dts/marvell/armada-cp110-master.dtsi      |  6 +--
 .../arm64/boot/dts/marvell/armada-cp110-slave.dtsi |  6 +--
 3 files changed, 34 insertions(+), 25 deletions(-)

diff --git a/arch/arm64/boot/dts/marvell/armada-7040-db.dts b/arch/arm64/boot/dts/marvell/armada-7040-db.dts
index 3cb4e81f6415..cd0290613eea 100644
--- a/arch/arm64/boot/dts/marvell/armada-7040-db.dts
+++ b/arch/arm64/boot/dts/marvell/armada-7040-db.dts
@@ -144,32 +144,41 @@
 	};
 };
 
-&cpm_nand {
+&cpm_nand_controller {
 	/*
 	 * SPI on CPM and NAND have common pins on this board. We can
-	 * use only one at a time. To enable the NAND (whihch will
+	 * use only one at a time. To enable the NAND (which will
 	 * disable the SPI), the "status = "okay";" line have to be
 	 * added here.
 	 */
-	num-cs = <1>;
 	pinctrl-0 = <&nand_pins>, <&nand_rb>;
 	pinctrl-names = "default";
-	nand-ecc-strength = <4>;
-	nand-ecc-step-size = <512>;
-	marvell,nand-enable-arbiter;
-	nand-on-flash-bbt;
-
-	partition@0 {
-		label = "U-Boot";
-		reg = <0 0x200000>;
-	};
-	partition@200000 {
-		label = "Linux";
-		reg = <0x200000 0xe00000>;
-	};
-	partition@1000000 {
-		label = "Filesystem";
-		reg = <0x1000000 0x3f000000>;
+
+	nand@0 {
+		reg = <0>;
+		marvell,rb = <0>;
+		nand-on-flash-bbt;
+		nand-ecc-strength = <4>;
+		nand-ecc-step-size = <512>;
+
+		partitions {
+
+			partition@0 {
+				label = "U-Boot";
+				reg = <0 0x200000>;
+			};
+
+			partition@200000 {
+				label = "Linux";
+				reg = <0x200000 0xe00000>;
+			};
+
+			partition@1000000 {
+				label = "Filesystem";
+				reg = <0x1000000 0x3f000000>;
+			};
+
+		};
 	};
 };
 
diff --git a/arch/arm64/boot/dts/marvell/armada-cp110-master.dtsi b/arch/arm64/boot/dts/marvell/armada-cp110-master.dtsi
index f203f7300194..c209f2f93b6c 100644
--- a/arch/arm64/boot/dts/marvell/armada-cp110-master.dtsi
+++ b/arch/arm64/boot/dts/marvell/armada-cp110-master.dtsi
@@ -268,14 +268,14 @@
 				status = "disabled";
 			};
 
-			cpm_nand: nand@720000 {
+			cpm_nand_controller: nand@720000 {
 				/*
 				 * Due to the limiation of the pin available
 				 * this controller is only usable on the CPM
 				 * for A7K and on the CPS for A8K.
 				 */
-				compatible = "marvell,armada-8k-nand",
-					     "marvell,armada370-nand";
+				compatible = "marvell,armada-8k-nand-controller",
+					     "marvell,armada370-nand-controller";
 				reg = <0x720000 0x54>;
 				#address-cells = <1>;
 				#size-cells = <1>;
diff --git a/arch/arm64/boot/dts/marvell/armada-cp110-slave.dtsi b/arch/arm64/boot/dts/marvell/armada-cp110-slave.dtsi
index 7d78767d3024..22789485d77e 100644
--- a/arch/arm64/boot/dts/marvell/armada-cp110-slave.dtsi
+++ b/arch/arm64/boot/dts/marvell/armada-cp110-slave.dtsi
@@ -269,14 +269,14 @@
 				status = "disabled";
 			};
 
-			cps_nand: nand@720000 {
+			cps_nand_controller: nand@720000 {
 				/*
 				 * Due to the limiation of the pin available
 				 * this controller is only usable on the CPM
 				 * for A7K and on the CPS for A8K.
 				 */
-				compatible = "marvell,armada370-nand",
-					     "marvell,armada370-nand";
+				compatible = "marvell,armada-8k-nand-controller",
+					     "marvell,armada370-nand-controller";
 				reg = <0x720000 0x54>;
 				#address-cells = <1>;
 				#size-cells = <1>;
-- 
2.11.0


______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/

^ permalink raw reply related	[flat|nested] 75+ messages in thread

* [RFC 12/12] ARM64: dts: marvell: use reworked NAND controller driver on Armada 7K/8K
@ 2017-10-18 14:36   ` Miquel Raynal
  0 siblings, 0 replies; 75+ messages in thread
From: Miquel Raynal @ 2017-10-18 14:36 UTC (permalink / raw)
  To: linux-arm-kernel

Change the bindings to use the reworked Marvell NAND controller driver.
Also adapt the nand controller node organization to distinguish which
property is relevant for the controller, and which one is NAND chip
specific. Expose the partitions as a subnode of the NAND chip.

Remove the 'marvell,nand-enable-arbiter' property, inefective with
Armada boards.

Signed-off-by: Miquel Raynal <miquel.raynal@free-electrons.com>
---
 arch/arm64/boot/dts/marvell/armada-7040-db.dts     | 47 +++++++++++++---------
 .../boot/dts/marvell/armada-cp110-master.dtsi      |  6 +--
 .../arm64/boot/dts/marvell/armada-cp110-slave.dtsi |  6 +--
 3 files changed, 34 insertions(+), 25 deletions(-)

diff --git a/arch/arm64/boot/dts/marvell/armada-7040-db.dts b/arch/arm64/boot/dts/marvell/armada-7040-db.dts
index 3cb4e81f6415..cd0290613eea 100644
--- a/arch/arm64/boot/dts/marvell/armada-7040-db.dts
+++ b/arch/arm64/boot/dts/marvell/armada-7040-db.dts
@@ -144,32 +144,41 @@
 	};
 };
 
-&cpm_nand {
+&cpm_nand_controller {
 	/*
 	 * SPI on CPM and NAND have common pins on this board. We can
-	 * use only one at a time. To enable the NAND (whihch will
+	 * use only one at a time. To enable the NAND (which will
 	 * disable the SPI), the "status = "okay";" line have to be
 	 * added here.
 	 */
-	num-cs = <1>;
 	pinctrl-0 = <&nand_pins>, <&nand_rb>;
 	pinctrl-names = "default";
-	nand-ecc-strength = <4>;
-	nand-ecc-step-size = <512>;
-	marvell,nand-enable-arbiter;
-	nand-on-flash-bbt;
-
-	partition at 0 {
-		label = "U-Boot";
-		reg = <0 0x200000>;
-	};
-	partition at 200000 {
-		label = "Linux";
-		reg = <0x200000 0xe00000>;
-	};
-	partition at 1000000 {
-		label = "Filesystem";
-		reg = <0x1000000 0x3f000000>;
+
+	nand at 0 {
+		reg = <0>;
+		marvell,rb = <0>;
+		nand-on-flash-bbt;
+		nand-ecc-strength = <4>;
+		nand-ecc-step-size = <512>;
+
+		partitions {
+
+			partition at 0 {
+				label = "U-Boot";
+				reg = <0 0x200000>;
+			};
+
+			partition at 200000 {
+				label = "Linux";
+				reg = <0x200000 0xe00000>;
+			};
+
+			partition at 1000000 {
+				label = "Filesystem";
+				reg = <0x1000000 0x3f000000>;
+			};
+
+		};
 	};
 };
 
diff --git a/arch/arm64/boot/dts/marvell/armada-cp110-master.dtsi b/arch/arm64/boot/dts/marvell/armada-cp110-master.dtsi
index f203f7300194..c209f2f93b6c 100644
--- a/arch/arm64/boot/dts/marvell/armada-cp110-master.dtsi
+++ b/arch/arm64/boot/dts/marvell/armada-cp110-master.dtsi
@@ -268,14 +268,14 @@
 				status = "disabled";
 			};
 
-			cpm_nand: nand at 720000 {
+			cpm_nand_controller: nand at 720000 {
 				/*
 				 * Due to the limiation of the pin available
 				 * this controller is only usable on the CPM
 				 * for A7K and on the CPS for A8K.
 				 */
-				compatible = "marvell,armada-8k-nand",
-					     "marvell,armada370-nand";
+				compatible = "marvell,armada-8k-nand-controller",
+					     "marvell,armada370-nand-controller";
 				reg = <0x720000 0x54>;
 				#address-cells = <1>;
 				#size-cells = <1>;
diff --git a/arch/arm64/boot/dts/marvell/armada-cp110-slave.dtsi b/arch/arm64/boot/dts/marvell/armada-cp110-slave.dtsi
index 7d78767d3024..22789485d77e 100644
--- a/arch/arm64/boot/dts/marvell/armada-cp110-slave.dtsi
+++ b/arch/arm64/boot/dts/marvell/armada-cp110-slave.dtsi
@@ -269,14 +269,14 @@
 				status = "disabled";
 			};
 
-			cps_nand: nand at 720000 {
+			cps_nand_controller: nand at 720000 {
 				/*
 				 * Due to the limiation of the pin available
 				 * this controller is only usable on the CPM
 				 * for A7K and on the CPS for A8K.
 				 */
-				compatible = "marvell,armada370-nand",
-					     "marvell,armada370-nand";
+				compatible = "marvell,armada-8k-nand-controller",
+					     "marvell,armada370-nand-controller";
 				reg = <0x720000 0x54>;
 				#address-cells = <1>;
 				#size-cells = <1>;
-- 
2.11.0

^ permalink raw reply related	[flat|nested] 75+ messages in thread

* Re: [RFC 03/12] mtd: nand: use a static data_interface in the nand_chip structure
  2017-10-18 14:36   ` Miquel Raynal
  (?)
@ 2017-10-18 17:02     ` Boris Brezillon
  -1 siblings, 0 replies; 75+ messages in thread
From: Boris Brezillon @ 2017-10-18 17:02 UTC (permalink / raw)
  To: Miquel Raynal
  Cc: Mark Rutland, Andrew Lunn, Catalin Marinas, Hanna Hawa,
	Will Deacon, Stefan Agner, Nadav Haklai, Masahiro Yamada,
	linux-mtd, Matthias Brugger, Robert Jarzmik, devel,
	Maxim Levitsky, Kamal Dasu, Josh Wu, Russell King, Marek Vasut,
	Chen-Yu Tsai, bcm-kernel-feedback-list, Sebastian Hesselbarth,
	Ezequiel Garcia, Sylvain Lemieux, Marc Gonzalez

On Wed, 18 Oct 2017 16:36:20 +0200
Miquel Raynal <miquel.raynal@free-electrons.com> wrote:

> Change the data_interface field from the nand_chip structure, convert
> the pointer to a static structure.

You mean "embed a nand_data_interface object". Not sure the static
specifier can be used to describe what you do.

> 
> Also remove the nand_get_default_data_interface() function that become
> useless and rename the onfi_init_data_interface() by
> nand_fill_data_interface(),

I know I'm the one who suggested to rename this function, but I'm not
so sure it's a good idea in the end, and I don't like the new prototype
where you drop the iface_type parameter.

> which is a more appropriate name because
> applied timings are ONFI, no matter if the NAND actually is one.

Well, this is actually a good reason to keep the onfi_ prefix. A NAND
driver can decide that its NAND is close to an existing timing mode and
re-use this onfi_init_data_interface(), but maybe someday we'll have
drivers filling the nand_data_interface object with their own
(non-ONFI) timings.

> 
> This is needed before passing to ->exec_op() to avoid any race that

It's not really a race, it's just that you need some timings during
NAND detection, and the core is currently creating the
nand_data_interface object after the detection step (in
nand_scan_tail()).

> could lead to a panic (null pointer dereference) on the initialization
> of the timings structure that will be used from the first reset
> operation if the pointer to data_interface was not referenced yet.
> 
> Signed-off-by: Miquel Raynal <miquel.raynal@free-electrons.com>
> ---
>  drivers/mtd/nand/nand_base.c    | 35 ++++++++++-------------------------
>  drivers/mtd/nand/nand_timings.c | 23 ++++++-----------------
>  include/linux/mtd/rawnand.h     |  9 ++-------
>  3 files changed, 18 insertions(+), 49 deletions(-)
> 
> diff --git a/drivers/mtd/nand/nand_base.c b/drivers/mtd/nand/nand_base.c
> index 318595c29053..bef20e06f0db 100644
> --- a/drivers/mtd/nand/nand_base.c
> +++ b/drivers/mtd/nand/nand_base.c
> @@ -814,8 +814,8 @@ static void nand_ccs_delay(struct nand_chip *chip)
>  	 * Wait tCCS_min if it is correctly defined, otherwise wait 500ns
>  	 * (which should be safe for all NANDs).
>  	 */
> -	if (chip->data_interface && chip->data_interface->timings.sdr.tCCS_min)
> -		ndelay(chip->data_interface->timings.sdr.tCCS_min / 1000);
> +	if (chip->data_interface.timings.sdr.tCCS_min)
> +		ndelay(chip->data_interface.timings.sdr.tCCS_min / 1000);
>  	else
>  		ndelay(500);
>  }
> @@ -1107,7 +1107,6 @@ static int nand_wait(struct mtd_info *mtd, struct nand_chip *chip)
>  static int nand_reset_data_interface(struct nand_chip *chip, int chipnr)
>  {
>  	struct mtd_info *mtd = nand_to_mtd(chip);
> -	const struct nand_data_interface *conf;
>  	int ret;
>  
>  	if (!chip->setup_data_interface)
> @@ -1127,8 +1126,8 @@ static int nand_reset_data_interface(struct nand_chip *chip, int chipnr)
>  	 * timings to timing mode 0.
>  	 */
>  
> -	conf = nand_get_default_data_interface();
> -	ret = chip->setup_data_interface(mtd, chipnr, conf);
> +	nand_fill_data_interface(chip, 0);
> +	ret = chip->setup_data_interface(mtd, chipnr, &chip->data_interface);
>  	if (ret)
>  		pr_err("Failed to configure data interface to SDR timing mode 0\n");
>  

So now you're stuck in timing mode 0 as soon as you have nand_reset()
called.

Actually, what we should do is:

1/ at the beginning of nand_scan_ident(), call
   onfi_init_data_interface(chip, &chip->data_interface, NAND_SDR_IFACE, 0)
   (or a wrapper that does that) so that data_interface is initialized with
   the appropriate timings for reset/detection operations
2/ after detection, find the best iface type and timing mode we can use
   (what's currently done in nand_init_data_interface())
3/ in nand_reset(), you should save what's in chip->data_interface, in a
   local variable, then apply SDR+timing-mode-0 and finally restore the
   previous settings (the one you stored in your local variable) after
   the RESET op.

This way you're guaranteed to always end up with the best data iface
settings after a RESET.

> @@ -1153,7 +1152,7 @@ static int nand_setup_data_interface(struct nand_chip *chip, int chipnr)
>  	struct mtd_info *mtd = nand_to_mtd(chip);
>  	int ret;
>  
> -	if (!chip->setup_data_interface || !chip->data_interface)
> +	if (!chip->setup_data_interface)
>  		return 0;
>  
>  	/*
> @@ -1174,7 +1173,7 @@ static int nand_setup_data_interface(struct nand_chip *chip, int chipnr)
>  			goto err;
>  	}
>  
> -	ret = chip->setup_data_interface(mtd, chipnr, chip->data_interface);
> +	ret = chip->setup_data_interface(mtd, chipnr, &chip->data_interface);
>  err:
>  	return ret;
>  }
> @@ -1214,21 +1213,16 @@ static int nand_init_data_interface(struct nand_chip *chip)
>  		modes = GENMASK(chip->onfi_timing_mode_default, 0);
>  	}
>  
> -	chip->data_interface = kzalloc(sizeof(*chip->data_interface),
> -				       GFP_KERNEL);
> -	if (!chip->data_interface)
> -		return -ENOMEM;
>  
>  	for (mode = fls(modes) - 1; mode >= 0; mode--) {
> -		ret = onfi_init_data_interface(chip, chip->data_interface,
> -					       NAND_SDR_IFACE, mode);
> +		ret = nand_fill_data_interface(chip, mode);
>  		if (ret)
>  			continue;
>  
>  		/* Pass -1 to only */
>  		ret = chip->setup_data_interface(mtd,
>  						 NAND_DATA_IFACE_CHECK_ONLY,
> -						 chip->data_interface);
> +						 &chip->data_interface);
>  		if (!ret) {
>  			chip->onfi_timing_mode_default = mode;
>  			break;
> @@ -1238,11 +1232,6 @@ static int nand_init_data_interface(struct nand_chip *chip)
>  	return 0;
>  }
>  
> -static void nand_release_data_interface(struct nand_chip *chip)
> -{
> -	kfree(chip->data_interface);
> -}
> -
>  /**
>   * nand_read_page_op - Do a READ PAGE operation
>   * @chip: The NAND chip
> @@ -5567,7 +5556,7 @@ int nand_scan_tail(struct mtd_info *mtd)
>  		chip->select_chip(mtd, -1);
>  
>  		if (ret)
> -			goto err_nand_data_iface_cleanup;
> +			goto err_nand_manuf_cleanup;
>  	}
>  
>  	/* Check, if we should skip the bad block table scan */
> @@ -5577,12 +5566,10 @@ int nand_scan_tail(struct mtd_info *mtd)
>  	/* Build bad block table */
>  	ret = chip->scan_bbt(mtd);
>  	if (ret)
> -		goto err_nand_data_iface_cleanup;
> +		goto err_nand_manuf_cleanup;
>  
>  	return 0;
>  
> -err_nand_data_iface_cleanup:
> -	nand_release_data_interface(chip);
>  
>  err_nand_manuf_cleanup:
>  	nand_manufacturer_cleanup(chip);
> @@ -5641,8 +5628,6 @@ void nand_cleanup(struct nand_chip *chip)
>  	    chip->ecc.algo == NAND_ECC_BCH)
>  		nand_bch_free((struct nand_bch_control *)chip->ecc.priv);
>  
> -	nand_release_data_interface(chip);
> -
>  	/* Free bad block table memory */
>  	kfree(chip->bbt);
>  	if (!(chip->options & NAND_OWN_BUFFERS) && chip->buffers) {
> diff --git a/drivers/mtd/nand/nand_timings.c b/drivers/mtd/nand/nand_timings.c
> index 5d1533bcc5bd..745b6404c901 100644
> --- a/drivers/mtd/nand/nand_timings.c
> +++ b/drivers/mtd/nand/nand_timings.c
> @@ -283,17 +283,17 @@ const struct nand_sdr_timings *onfi_async_timing_mode_to_sdr_timings(int mode)
>  EXPORT_SYMBOL(onfi_async_timing_mode_to_sdr_timings);
>  
>  /**
> - * onfi_init_data_interface - [NAND Interface] Initialize a data interface from
> + * nand_fill_data_interface - [NAND Interface] Initialize a data interface from
>   * given ONFI mode
>   * @iface: The data interface to be initialized
>   * @mode: The ONFI timing mode
>   */
> -int onfi_init_data_interface(struct nand_chip *chip,
> -			     struct nand_data_interface *iface,
> -			     enum nand_data_interface_type type,
> +int nand_fill_data_interface(struct nand_chip *chip,
>  			     int timing_mode)
>  {
> -	if (type != NAND_SDR_IFACE)
> +	struct nand_data_interface *iface = &chip->data_interface;
> +
> +	if (iface->type != NAND_SDR_IFACE)
>  		return -EINVAL;
>  
>  	if (timing_mode < 0 || timing_mode >= ARRAY_SIZE(onfi_sdr_timings))
> @@ -321,15 +321,4 @@ int onfi_init_data_interface(struct nand_chip *chip,
>  
>  	return 0;
>  }
> -EXPORT_SYMBOL(onfi_init_data_interface);
> -
> -/**
> - * nand_get_default_data_interface - [NAND Interface] Retrieve NAND
> - * data interface for mode 0. This is used as default timing after
> - * reset.
> - */
> -const struct nand_data_interface *nand_get_default_data_interface(void)
> -{
> -	return &onfi_sdr_timings[0];
> -}
> -EXPORT_SYMBOL(nand_get_default_data_interface);
> +EXPORT_SYMBOL(nand_fill_data_interface);
> diff --git a/include/linux/mtd/rawnand.h b/include/linux/mtd/rawnand.h
> index 04f4cfbe6c09..1acc26ed0e91 100644
> --- a/include/linux/mtd/rawnand.h
> +++ b/include/linux/mtd/rawnand.h
> @@ -917,7 +917,7 @@ struct nand_chip {
>  	u16 max_bb_per_die;
>  	u32 blocks_per_die;
>  
> -	struct nand_data_interface *data_interface;
> +	struct nand_data_interface data_interface;
>  
>  	int read_retries;
>  
> @@ -1214,10 +1214,7 @@ static inline int onfi_get_sync_timing_mode(struct nand_chip *chip)
>  	return le16_to_cpu(chip->onfi_params.src_sync_timing_mode);
>  }
>  
> -int onfi_init_data_interface(struct nand_chip *chip,
> -			     struct nand_data_interface *iface,
> -			     enum nand_data_interface_type type,
> -			     int timing_mode);
> +int nand_fill_data_interface(struct nand_chip *chip, int timing_mode);
>  
>  /*
>   * Check if it is a SLC nand.
> @@ -1258,8 +1255,6 @@ static inline int jedec_feature(struct nand_chip *chip)
>  
>  /* get timing characteristics from ONFI timing mode. */
>  const struct nand_sdr_timings *onfi_async_timing_mode_to_sdr_timings(int mode);
> -/* get data interface from ONFI timing mode 0, used after reset. */
> -const struct nand_data_interface *nand_get_default_data_interface(void);
>  
>  int nand_check_erased_ecc_chunk(void *data, int datalen,
>  				void *ecc, int ecclen,

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [RFC 03/12] mtd: nand: use a static data_interface in the nand_chip structure
@ 2017-10-18 17:02     ` Boris Brezillon
  0 siblings, 0 replies; 75+ messages in thread
From: Boris Brezillon @ 2017-10-18 17:02 UTC (permalink / raw)
  To: Miquel Raynal
  Cc: Andrew Lunn, bcm-kernel-feedback-list, Brian Norris,
	Catalin Marinas, Chen-Yu Tsai, Cyrille Pitchen, Daniel Mack,
	David Woodhouse, devel, devicetree, Ezequiel Garcia,
	Greg Kroah-Hartman, Gregory Clement, Han Xu, Haojian Zhuang,
	Jason Cooper, Josh Wu, Kamal Dasu, linux-arm-kernel,
	linux-kernel, linux-mediatek, linux-mtd, Marc Gonzalez,
	Marek Vasut, Mark Rutland, Masahiro Yamada, Matthias Brugger,
	Maxime Ripard, Maxim Levitsky, Richard Weinberger,
	Robert Jarzmik, Rob Herring, Russell King, Sebastian Hesselbarth,
	Stefan Agner, Sylvain Lemieux, Vladimir Zapolskiy, Wenyou Yang,
	Will Deacon, Thomas Petazzoni, Antoine Tenart, Igor Grinberg,
	Nadav Haklai, Ofer Heifetz, Neta Zur Hershkovits, Hanna Hawa

On Wed, 18 Oct 2017 16:36:20 +0200
Miquel Raynal <miquel.raynal@free-electrons.com> wrote:

> Change the data_interface field from the nand_chip structure, convert
> the pointer to a static structure.

You mean "embed a nand_data_interface object". Not sure the static
specifier can be used to describe what you do.

> 
> Also remove the nand_get_default_data_interface() function that become
> useless and rename the onfi_init_data_interface() by
> nand_fill_data_interface(),

I know I'm the one who suggested to rename this function, but I'm not
so sure it's a good idea in the end, and I don't like the new prototype
where you drop the iface_type parameter.

> which is a more appropriate name because
> applied timings are ONFI, no matter if the NAND actually is one.

Well, this is actually a good reason to keep the onfi_ prefix. A NAND
driver can decide that its NAND is close to an existing timing mode and
re-use this onfi_init_data_interface(), but maybe someday we'll have
drivers filling the nand_data_interface object with their own
(non-ONFI) timings.

> 
> This is needed before passing to ->exec_op() to avoid any race that

It's not really a race, it's just that you need some timings during
NAND detection, and the core is currently creating the
nand_data_interface object after the detection step (in
nand_scan_tail()).

> could lead to a panic (null pointer dereference) on the initialization
> of the timings structure that will be used from the first reset
> operation if the pointer to data_interface was not referenced yet.
> 
> Signed-off-by: Miquel Raynal <miquel.raynal@free-electrons.com>
> ---
>  drivers/mtd/nand/nand_base.c    | 35 ++++++++++-------------------------
>  drivers/mtd/nand/nand_timings.c | 23 ++++++-----------------
>  include/linux/mtd/rawnand.h     |  9 ++-------
>  3 files changed, 18 insertions(+), 49 deletions(-)
> 
> diff --git a/drivers/mtd/nand/nand_base.c b/drivers/mtd/nand/nand_base.c
> index 318595c29053..bef20e06f0db 100644
> --- a/drivers/mtd/nand/nand_base.c
> +++ b/drivers/mtd/nand/nand_base.c
> @@ -814,8 +814,8 @@ static void nand_ccs_delay(struct nand_chip *chip)
>  	 * Wait tCCS_min if it is correctly defined, otherwise wait 500ns
>  	 * (which should be safe for all NANDs).
>  	 */
> -	if (chip->data_interface && chip->data_interface->timings.sdr.tCCS_min)
> -		ndelay(chip->data_interface->timings.sdr.tCCS_min / 1000);
> +	if (chip->data_interface.timings.sdr.tCCS_min)
> +		ndelay(chip->data_interface.timings.sdr.tCCS_min / 1000);
>  	else
>  		ndelay(500);
>  }
> @@ -1107,7 +1107,6 @@ static int nand_wait(struct mtd_info *mtd, struct nand_chip *chip)
>  static int nand_reset_data_interface(struct nand_chip *chip, int chipnr)
>  {
>  	struct mtd_info *mtd = nand_to_mtd(chip);
> -	const struct nand_data_interface *conf;
>  	int ret;
>  
>  	if (!chip->setup_data_interface)
> @@ -1127,8 +1126,8 @@ static int nand_reset_data_interface(struct nand_chip *chip, int chipnr)
>  	 * timings to timing mode 0.
>  	 */
>  
> -	conf = nand_get_default_data_interface();
> -	ret = chip->setup_data_interface(mtd, chipnr, conf);
> +	nand_fill_data_interface(chip, 0);
> +	ret = chip->setup_data_interface(mtd, chipnr, &chip->data_interface);
>  	if (ret)
>  		pr_err("Failed to configure data interface to SDR timing mode 0\n");
>  

So now you're stuck in timing mode 0 as soon as you have nand_reset()
called.

Actually, what we should do is:

1/ at the beginning of nand_scan_ident(), call
   onfi_init_data_interface(chip, &chip->data_interface, NAND_SDR_IFACE, 0)
   (or a wrapper that does that) so that data_interface is initialized with
   the appropriate timings for reset/detection operations
2/ after detection, find the best iface type and timing mode we can use
   (what's currently done in nand_init_data_interface())
3/ in nand_reset(), you should save what's in chip->data_interface, in a
   local variable, then apply SDR+timing-mode-0 and finally restore the
   previous settings (the one you stored in your local variable) after
   the RESET op.

This way you're guaranteed to always end up with the best data iface
settings after a RESET.

> @@ -1153,7 +1152,7 @@ static int nand_setup_data_interface(struct nand_chip *chip, int chipnr)
>  	struct mtd_info *mtd = nand_to_mtd(chip);
>  	int ret;
>  
> -	if (!chip->setup_data_interface || !chip->data_interface)
> +	if (!chip->setup_data_interface)
>  		return 0;
>  
>  	/*
> @@ -1174,7 +1173,7 @@ static int nand_setup_data_interface(struct nand_chip *chip, int chipnr)
>  			goto err;
>  	}
>  
> -	ret = chip->setup_data_interface(mtd, chipnr, chip->data_interface);
> +	ret = chip->setup_data_interface(mtd, chipnr, &chip->data_interface);
>  err:
>  	return ret;
>  }
> @@ -1214,21 +1213,16 @@ static int nand_init_data_interface(struct nand_chip *chip)
>  		modes = GENMASK(chip->onfi_timing_mode_default, 0);
>  	}
>  
> -	chip->data_interface = kzalloc(sizeof(*chip->data_interface),
> -				       GFP_KERNEL);
> -	if (!chip->data_interface)
> -		return -ENOMEM;
>  
>  	for (mode = fls(modes) - 1; mode >= 0; mode--) {
> -		ret = onfi_init_data_interface(chip, chip->data_interface,
> -					       NAND_SDR_IFACE, mode);
> +		ret = nand_fill_data_interface(chip, mode);
>  		if (ret)
>  			continue;
>  
>  		/* Pass -1 to only */
>  		ret = chip->setup_data_interface(mtd,
>  						 NAND_DATA_IFACE_CHECK_ONLY,
> -						 chip->data_interface);
> +						 &chip->data_interface);
>  		if (!ret) {
>  			chip->onfi_timing_mode_default = mode;
>  			break;
> @@ -1238,11 +1232,6 @@ static int nand_init_data_interface(struct nand_chip *chip)
>  	return 0;
>  }
>  
> -static void nand_release_data_interface(struct nand_chip *chip)
> -{
> -	kfree(chip->data_interface);
> -}
> -
>  /**
>   * nand_read_page_op - Do a READ PAGE operation
>   * @chip: The NAND chip
> @@ -5567,7 +5556,7 @@ int nand_scan_tail(struct mtd_info *mtd)
>  		chip->select_chip(mtd, -1);
>  
>  		if (ret)
> -			goto err_nand_data_iface_cleanup;
> +			goto err_nand_manuf_cleanup;
>  	}
>  
>  	/* Check, if we should skip the bad block table scan */
> @@ -5577,12 +5566,10 @@ int nand_scan_tail(struct mtd_info *mtd)
>  	/* Build bad block table */
>  	ret = chip->scan_bbt(mtd);
>  	if (ret)
> -		goto err_nand_data_iface_cleanup;
> +		goto err_nand_manuf_cleanup;
>  
>  	return 0;
>  
> -err_nand_data_iface_cleanup:
> -	nand_release_data_interface(chip);
>  
>  err_nand_manuf_cleanup:
>  	nand_manufacturer_cleanup(chip);
> @@ -5641,8 +5628,6 @@ void nand_cleanup(struct nand_chip *chip)
>  	    chip->ecc.algo == NAND_ECC_BCH)
>  		nand_bch_free((struct nand_bch_control *)chip->ecc.priv);
>  
> -	nand_release_data_interface(chip);
> -
>  	/* Free bad block table memory */
>  	kfree(chip->bbt);
>  	if (!(chip->options & NAND_OWN_BUFFERS) && chip->buffers) {
> diff --git a/drivers/mtd/nand/nand_timings.c b/drivers/mtd/nand/nand_timings.c
> index 5d1533bcc5bd..745b6404c901 100644
> --- a/drivers/mtd/nand/nand_timings.c
> +++ b/drivers/mtd/nand/nand_timings.c
> @@ -283,17 +283,17 @@ const struct nand_sdr_timings *onfi_async_timing_mode_to_sdr_timings(int mode)
>  EXPORT_SYMBOL(onfi_async_timing_mode_to_sdr_timings);
>  
>  /**
> - * onfi_init_data_interface - [NAND Interface] Initialize a data interface from
> + * nand_fill_data_interface - [NAND Interface] Initialize a data interface from
>   * given ONFI mode
>   * @iface: The data interface to be initialized
>   * @mode: The ONFI timing mode
>   */
> -int onfi_init_data_interface(struct nand_chip *chip,
> -			     struct nand_data_interface *iface,
> -			     enum nand_data_interface_type type,
> +int nand_fill_data_interface(struct nand_chip *chip,
>  			     int timing_mode)
>  {
> -	if (type != NAND_SDR_IFACE)
> +	struct nand_data_interface *iface = &chip->data_interface;
> +
> +	if (iface->type != NAND_SDR_IFACE)
>  		return -EINVAL;
>  
>  	if (timing_mode < 0 || timing_mode >= ARRAY_SIZE(onfi_sdr_timings))
> @@ -321,15 +321,4 @@ int onfi_init_data_interface(struct nand_chip *chip,
>  
>  	return 0;
>  }
> -EXPORT_SYMBOL(onfi_init_data_interface);
> -
> -/**
> - * nand_get_default_data_interface - [NAND Interface] Retrieve NAND
> - * data interface for mode 0. This is used as default timing after
> - * reset.
> - */
> -const struct nand_data_interface *nand_get_default_data_interface(void)
> -{
> -	return &onfi_sdr_timings[0];
> -}
> -EXPORT_SYMBOL(nand_get_default_data_interface);
> +EXPORT_SYMBOL(nand_fill_data_interface);
> diff --git a/include/linux/mtd/rawnand.h b/include/linux/mtd/rawnand.h
> index 04f4cfbe6c09..1acc26ed0e91 100644
> --- a/include/linux/mtd/rawnand.h
> +++ b/include/linux/mtd/rawnand.h
> @@ -917,7 +917,7 @@ struct nand_chip {
>  	u16 max_bb_per_die;
>  	u32 blocks_per_die;
>  
> -	struct nand_data_interface *data_interface;
> +	struct nand_data_interface data_interface;
>  
>  	int read_retries;
>  
> @@ -1214,10 +1214,7 @@ static inline int onfi_get_sync_timing_mode(struct nand_chip *chip)
>  	return le16_to_cpu(chip->onfi_params.src_sync_timing_mode);
>  }
>  
> -int onfi_init_data_interface(struct nand_chip *chip,
> -			     struct nand_data_interface *iface,
> -			     enum nand_data_interface_type type,
> -			     int timing_mode);
> +int nand_fill_data_interface(struct nand_chip *chip, int timing_mode);
>  
>  /*
>   * Check if it is a SLC nand.
> @@ -1258,8 +1255,6 @@ static inline int jedec_feature(struct nand_chip *chip)
>  
>  /* get timing characteristics from ONFI timing mode. */
>  const struct nand_sdr_timings *onfi_async_timing_mode_to_sdr_timings(int mode);
> -/* get data interface from ONFI timing mode 0, used after reset. */
> -const struct nand_data_interface *nand_get_default_data_interface(void);
>  
>  int nand_check_erased_ecc_chunk(void *data, int datalen,
>  				void *ecc, int ecclen,

^ permalink raw reply	[flat|nested] 75+ messages in thread

* [RFC 03/12] mtd: nand: use a static data_interface in the nand_chip structure
@ 2017-10-18 17:02     ` Boris Brezillon
  0 siblings, 0 replies; 75+ messages in thread
From: Boris Brezillon @ 2017-10-18 17:02 UTC (permalink / raw)
  To: linux-arm-kernel

On Wed, 18 Oct 2017 16:36:20 +0200
Miquel Raynal <miquel.raynal@free-electrons.com> wrote:

> Change the data_interface field from the nand_chip structure, convert
> the pointer to a static structure.

You mean "embed a nand_data_interface object". Not sure the static
specifier can be used to describe what you do.

> 
> Also remove the nand_get_default_data_interface() function that become
> useless and rename the onfi_init_data_interface() by
> nand_fill_data_interface(),

I know I'm the one who suggested to rename this function, but I'm not
so sure it's a good idea in the end, and I don't like the new prototype
where you drop the iface_type parameter.

> which is a more appropriate name because
> applied timings are ONFI, no matter if the NAND actually is one.

Well, this is actually a good reason to keep the onfi_ prefix. A NAND
driver can decide that its NAND is close to an existing timing mode and
re-use this onfi_init_data_interface(), but maybe someday we'll have
drivers filling the nand_data_interface object with their own
(non-ONFI) timings.

> 
> This is needed before passing to ->exec_op() to avoid any race that

It's not really a race, it's just that you need some timings during
NAND detection, and the core is currently creating the
nand_data_interface object after the detection step (in
nand_scan_tail()).

> could lead to a panic (null pointer dereference) on the initialization
> of the timings structure that will be used from the first reset
> operation if the pointer to data_interface was not referenced yet.
> 
> Signed-off-by: Miquel Raynal <miquel.raynal@free-electrons.com>
> ---
>  drivers/mtd/nand/nand_base.c    | 35 ++++++++++-------------------------
>  drivers/mtd/nand/nand_timings.c | 23 ++++++-----------------
>  include/linux/mtd/rawnand.h     |  9 ++-------
>  3 files changed, 18 insertions(+), 49 deletions(-)
> 
> diff --git a/drivers/mtd/nand/nand_base.c b/drivers/mtd/nand/nand_base.c
> index 318595c29053..bef20e06f0db 100644
> --- a/drivers/mtd/nand/nand_base.c
> +++ b/drivers/mtd/nand/nand_base.c
> @@ -814,8 +814,8 @@ static void nand_ccs_delay(struct nand_chip *chip)
>  	 * Wait tCCS_min if it is correctly defined, otherwise wait 500ns
>  	 * (which should be safe for all NANDs).
>  	 */
> -	if (chip->data_interface && chip->data_interface->timings.sdr.tCCS_min)
> -		ndelay(chip->data_interface->timings.sdr.tCCS_min / 1000);
> +	if (chip->data_interface.timings.sdr.tCCS_min)
> +		ndelay(chip->data_interface.timings.sdr.tCCS_min / 1000);
>  	else
>  		ndelay(500);
>  }
> @@ -1107,7 +1107,6 @@ static int nand_wait(struct mtd_info *mtd, struct nand_chip *chip)
>  static int nand_reset_data_interface(struct nand_chip *chip, int chipnr)
>  {
>  	struct mtd_info *mtd = nand_to_mtd(chip);
> -	const struct nand_data_interface *conf;
>  	int ret;
>  
>  	if (!chip->setup_data_interface)
> @@ -1127,8 +1126,8 @@ static int nand_reset_data_interface(struct nand_chip *chip, int chipnr)
>  	 * timings to timing mode 0.
>  	 */
>  
> -	conf = nand_get_default_data_interface();
> -	ret = chip->setup_data_interface(mtd, chipnr, conf);
> +	nand_fill_data_interface(chip, 0);
> +	ret = chip->setup_data_interface(mtd, chipnr, &chip->data_interface);
>  	if (ret)
>  		pr_err("Failed to configure data interface to SDR timing mode 0\n");
>  

So now you're stuck in timing mode 0 as soon as you have nand_reset()
called.

Actually, what we should do is:

1/ at the beginning of nand_scan_ident(), call
   onfi_init_data_interface(chip, &chip->data_interface, NAND_SDR_IFACE, 0)
   (or a wrapper that does that) so that data_interface is initialized with
   the appropriate timings for reset/detection operations
2/ after detection, find the best iface type and timing mode we can use
   (what's currently done in nand_init_data_interface())
3/ in nand_reset(), you should save what's in chip->data_interface, in a
   local variable, then apply SDR+timing-mode-0 and finally restore the
   previous settings (the one you stored in your local variable) after
   the RESET op.

This way you're guaranteed to always end up with the best data iface
settings after a RESET.

> @@ -1153,7 +1152,7 @@ static int nand_setup_data_interface(struct nand_chip *chip, int chipnr)
>  	struct mtd_info *mtd = nand_to_mtd(chip);
>  	int ret;
>  
> -	if (!chip->setup_data_interface || !chip->data_interface)
> +	if (!chip->setup_data_interface)
>  		return 0;
>  
>  	/*
> @@ -1174,7 +1173,7 @@ static int nand_setup_data_interface(struct nand_chip *chip, int chipnr)
>  			goto err;
>  	}
>  
> -	ret = chip->setup_data_interface(mtd, chipnr, chip->data_interface);
> +	ret = chip->setup_data_interface(mtd, chipnr, &chip->data_interface);
>  err:
>  	return ret;
>  }
> @@ -1214,21 +1213,16 @@ static int nand_init_data_interface(struct nand_chip *chip)
>  		modes = GENMASK(chip->onfi_timing_mode_default, 0);
>  	}
>  
> -	chip->data_interface = kzalloc(sizeof(*chip->data_interface),
> -				       GFP_KERNEL);
> -	if (!chip->data_interface)
> -		return -ENOMEM;
>  
>  	for (mode = fls(modes) - 1; mode >= 0; mode--) {
> -		ret = onfi_init_data_interface(chip, chip->data_interface,
> -					       NAND_SDR_IFACE, mode);
> +		ret = nand_fill_data_interface(chip, mode);
>  		if (ret)
>  			continue;
>  
>  		/* Pass -1 to only */
>  		ret = chip->setup_data_interface(mtd,
>  						 NAND_DATA_IFACE_CHECK_ONLY,
> -						 chip->data_interface);
> +						 &chip->data_interface);
>  		if (!ret) {
>  			chip->onfi_timing_mode_default = mode;
>  			break;
> @@ -1238,11 +1232,6 @@ static int nand_init_data_interface(struct nand_chip *chip)
>  	return 0;
>  }
>  
> -static void nand_release_data_interface(struct nand_chip *chip)
> -{
> -	kfree(chip->data_interface);
> -}
> -
>  /**
>   * nand_read_page_op - Do a READ PAGE operation
>   * @chip: The NAND chip
> @@ -5567,7 +5556,7 @@ int nand_scan_tail(struct mtd_info *mtd)
>  		chip->select_chip(mtd, -1);
>  
>  		if (ret)
> -			goto err_nand_data_iface_cleanup;
> +			goto err_nand_manuf_cleanup;
>  	}
>  
>  	/* Check, if we should skip the bad block table scan */
> @@ -5577,12 +5566,10 @@ int nand_scan_tail(struct mtd_info *mtd)
>  	/* Build bad block table */
>  	ret = chip->scan_bbt(mtd);
>  	if (ret)
> -		goto err_nand_data_iface_cleanup;
> +		goto err_nand_manuf_cleanup;
>  
>  	return 0;
>  
> -err_nand_data_iface_cleanup:
> -	nand_release_data_interface(chip);
>  
>  err_nand_manuf_cleanup:
>  	nand_manufacturer_cleanup(chip);
> @@ -5641,8 +5628,6 @@ void nand_cleanup(struct nand_chip *chip)
>  	    chip->ecc.algo == NAND_ECC_BCH)
>  		nand_bch_free((struct nand_bch_control *)chip->ecc.priv);
>  
> -	nand_release_data_interface(chip);
> -
>  	/* Free bad block table memory */
>  	kfree(chip->bbt);
>  	if (!(chip->options & NAND_OWN_BUFFERS) && chip->buffers) {
> diff --git a/drivers/mtd/nand/nand_timings.c b/drivers/mtd/nand/nand_timings.c
> index 5d1533bcc5bd..745b6404c901 100644
> --- a/drivers/mtd/nand/nand_timings.c
> +++ b/drivers/mtd/nand/nand_timings.c
> @@ -283,17 +283,17 @@ const struct nand_sdr_timings *onfi_async_timing_mode_to_sdr_timings(int mode)
>  EXPORT_SYMBOL(onfi_async_timing_mode_to_sdr_timings);
>  
>  /**
> - * onfi_init_data_interface - [NAND Interface] Initialize a data interface from
> + * nand_fill_data_interface - [NAND Interface] Initialize a data interface from
>   * given ONFI mode
>   * @iface: The data interface to be initialized
>   * @mode: The ONFI timing mode
>   */
> -int onfi_init_data_interface(struct nand_chip *chip,
> -			     struct nand_data_interface *iface,
> -			     enum nand_data_interface_type type,
> +int nand_fill_data_interface(struct nand_chip *chip,
>  			     int timing_mode)
>  {
> -	if (type != NAND_SDR_IFACE)
> +	struct nand_data_interface *iface = &chip->data_interface;
> +
> +	if (iface->type != NAND_SDR_IFACE)
>  		return -EINVAL;
>  
>  	if (timing_mode < 0 || timing_mode >= ARRAY_SIZE(onfi_sdr_timings))
> @@ -321,15 +321,4 @@ int onfi_init_data_interface(struct nand_chip *chip,
>  
>  	return 0;
>  }
> -EXPORT_SYMBOL(onfi_init_data_interface);
> -
> -/**
> - * nand_get_default_data_interface - [NAND Interface] Retrieve NAND
> - * data interface for mode 0. This is used as default timing after
> - * reset.
> - */
> -const struct nand_data_interface *nand_get_default_data_interface(void)
> -{
> -	return &onfi_sdr_timings[0];
> -}
> -EXPORT_SYMBOL(nand_get_default_data_interface);
> +EXPORT_SYMBOL(nand_fill_data_interface);
> diff --git a/include/linux/mtd/rawnand.h b/include/linux/mtd/rawnand.h
> index 04f4cfbe6c09..1acc26ed0e91 100644
> --- a/include/linux/mtd/rawnand.h
> +++ b/include/linux/mtd/rawnand.h
> @@ -917,7 +917,7 @@ struct nand_chip {
>  	u16 max_bb_per_die;
>  	u32 blocks_per_die;
>  
> -	struct nand_data_interface *data_interface;
> +	struct nand_data_interface data_interface;
>  
>  	int read_retries;
>  
> @@ -1214,10 +1214,7 @@ static inline int onfi_get_sync_timing_mode(struct nand_chip *chip)
>  	return le16_to_cpu(chip->onfi_params.src_sync_timing_mode);
>  }
>  
> -int onfi_init_data_interface(struct nand_chip *chip,
> -			     struct nand_data_interface *iface,
> -			     enum nand_data_interface_type type,
> -			     int timing_mode);
> +int nand_fill_data_interface(struct nand_chip *chip, int timing_mode);
>  
>  /*
>   * Check if it is a SLC nand.
> @@ -1258,8 +1255,6 @@ static inline int jedec_feature(struct nand_chip *chip)
>  
>  /* get timing characteristics from ONFI timing mode. */
>  const struct nand_sdr_timings *onfi_async_timing_mode_to_sdr_timings(int mode);
> -/* get data interface from ONFI timing mode 0, used after reset. */
> -const struct nand_data_interface *nand_get_default_data_interface(void);
>  
>  int nand_check_erased_ecc_chunk(void *data, int datalen,
>  				void *ecc, int ecclen,

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [RFC 04/12] mtd: nand: add ->exec_op() implementation
  2017-10-18 14:36   ` Miquel Raynal
  (?)
@ 2017-10-18 21:57     ` Boris Brezillon
  -1 siblings, 0 replies; 75+ messages in thread
From: Boris Brezillon @ 2017-10-18 21:57 UTC (permalink / raw)
  To: Miquel Raynal
  Cc: Mark Rutland, Andrew Lunn, Catalin Marinas, Hanna Hawa,
	Will Deacon, Stefan Agner, Nadav Haklai, Masahiro Yamada,
	linux-mtd, Matthias Brugger, Robert Jarzmik, devel,
	Maxim Levitsky, Kamal Dasu, Josh Wu, Russell King, Marek Vasut,
	Chen-Yu Tsai, bcm-kernel-feedback-list, Sebastian Hesselbarth,
	Ezequiel Garcia, Sylvain Lemieux, Marc Gonzalez

On Wed, 18 Oct 2017 16:36:21 +0200
Miquel Raynal <miquel.raynal@free-electrons.com> wrote:

> Introduce the new way to control the NAND controller drivers by
> implementing the ->exec_op() core helpers and allowing new drivers to
> use it instead of relying on ->cmd_ctrl(), ->cmdfunc() and
> ->read/write_byte/word/buf().

"
Introduce a new interface to instruct NAND controllers to send specific
NAND operations. The new interface takes the form of a single method
called ->exec_op(). This method is designed to replace ->cmd_ctrl(),
->cmdfunc() and ->read/write_byte/word/buf() hooks.
"

> 
> The logic is now to send to the controller driver a list of
> instructions.

"
->exec_op() is passed a set of instructions describing the operation to
execute. Each instruction has a type (ADDR, CMD, CYCLE, WAITRDY) and
delay. The type is directly matching the description of NAND commands
in various NAND datasheet and standards (ONFI, JEDEC), the delay is
here to help simple controllers wait enough time between each
instruction. Advanced controllers with integrated timings control can
ignore these delays. 
"

> The driver shall use the parser added by this commit to
> get the matching hook if any.

That's true only for advanced controllers. For those who are executing
each instruction individually (like the gpio-nand driver, or any old
style driver where each ADDR/CMD/DATA cycle is controlled
independently) this is not needed, and who add extra complexity for no
real gain.

> The instructions may be split by the
> parser in order to comply with the controller constraints filled in an
> array of supported patterns.

Given only this description it's hard to tell what this parser is and
why it's needed. I think a real example who be helpful to better
understand what this is.

> 
> Various helpers are also added to ease NAND controller drivers writing.
> 
> This new interface should really ease the support of new vendor specific
> instructions.

Actually, it's more than easing support for vendor specific operations,
it allows the core to check whether the NAND controller will be able to
execute a specific operation or not, which before that was impossible.
It doesn't mean all controllers will magically support all kind of NAND
operations, but at least we'll know when it doesn't.

> 
> Suggested-by: Boris Brezillon <boris.brezillon@free-electrons.com>
> Signed-off-by: Miquel Raynal <miquel.raynal@free-electrons.com>
> ---
>  drivers/mtd/nand/denali.c      |  93 +++-
>  drivers/mtd/nand/nand_base.c   | 949 +++++++++++++++++++++++++++++++++++++++--
>  drivers/mtd/nand/nand_hynix.c  |  91 +++-
>  drivers/mtd/nand/nand_micron.c |  32 +-
>  include/linux/mtd/rawnand.h    | 366 +++++++++++++++-
>  5 files changed, 1448 insertions(+), 83 deletions(-)
> 
> diff --git a/drivers/mtd/nand/denali.c b/drivers/mtd/nand/denali.c
> index e5f38359f6df..8f0f18d9d9cf 100644
> --- a/drivers/mtd/nand/denali.c
> +++ b/drivers/mtd/nand/denali.c
> @@ -652,8 +652,6 @@ static void denali_oob_xfer(struct mtd_info *mtd, struct nand_chip *chip,
>  			    int page, int write)
>  {
>  	struct denali_nand_info *denali = mtd_to_denali(mtd);
> -	unsigned int start_cmd = write ? NAND_CMD_SEQIN : NAND_CMD_READ0;
> -	unsigned int rnd_cmd = write ? NAND_CMD_RNDIN : NAND_CMD_RNDOUT;
>  	int writesize = mtd->writesize;
>  	int oobsize = mtd->oobsize;
>  	uint8_t *bufpoi = chip->oob_poi;
> @@ -665,11 +663,22 @@ static void denali_oob_xfer(struct mtd_info *mtd, struct nand_chip *chip,
>  	int i, pos, len;
>  
>  	/* BBM at the beginning of the OOB area */
> -	chip->cmdfunc(mtd, start_cmd, writesize, page);
> -	if (write)
> -		chip->write_buf(mtd, bufpoi, oob_skip);
> -	else
> -		chip->read_buf(mtd, bufpoi, oob_skip);
> +	if (chip->exec_op) {
> +		if (write)
> +			nand_prog_page_begin_op(chip, page, writesize, bufpoi,
> +						oob_skip);
> +		else
> +			nand_read_page_op(chip, page, writesize, bufpoi,
> +					  oob_skip);
> +	} else {
> +		if (write) {
> +			chip->cmdfunc(mtd, NAND_CMD_SEQIN, writesize, page);
> +			chip->write_buf(mtd, bufpoi, oob_skip);
> +		} else {
> +			chip->cmdfunc(mtd, NAND_CMD_READ0, writesize, page);
> +			chip->read_buf(mtd, bufpoi, oob_skip);
> +		}
> +	}

Why do we have to keep the ->cmdfunc() logic? nand_prog_page_xxx()
already abstracts this for us, right?

>  	bufpoi += oob_skip;
>  
>  	/* OOB ECC */
> @@ -682,30 +691,67 @@ static void denali_oob_xfer(struct mtd_info *mtd, struct nand_chip *chip,
>  		else if (pos + len > writesize)
>  			len = writesize - pos;
>  
> -		chip->cmdfunc(mtd, rnd_cmd, pos, -1);
> -		if (write)
> -			chip->write_buf(mtd, bufpoi, len);
> -		else
> -			chip->read_buf(mtd, bufpoi, len);
> -		bufpoi += len;
> -		if (len < ecc_bytes) {
> -			len = ecc_bytes - len;
> -			chip->cmdfunc(mtd, rnd_cmd, writesize + oob_skip, -1);
> +		if (chip->exec_op) {
>  			if (write)
> -				chip->write_buf(mtd, bufpoi, len);
> +				nand_change_write_column_op(
> +					chip, pos, bufpoi, len, false);

Nitpick, but can you try to align things like that:

				nand_change_write_column_op(chip, pos,
							    bufpoi, len,
							    false);

>  			else
> +				nand_change_read_column_op(
> +					chip, pos, bufpoi, len, false);

Ditto.

> +		} else {
> +			if (write) {
> +				chip->cmdfunc(mtd, NAND_CMD_RNDIN, pos, -1);
> +				chip->write_buf(mtd, bufpoi, len);
> +			} else {
> +				chip->cmdfunc(mtd, NAND_CMD_RNDOUT, pos, -1);
>  				chip->read_buf(mtd, bufpoi, len);
> +			}
> +		}

Smae here: I don't think we need to support both ->cmdfunc() and
nand_change_read/write_column().

> +		bufpoi += len;
> +		if (len < ecc_bytes) {
> +			len = ecc_bytes - len;
> +			if (chip->exec_op) {
> +				if (write)
> +					nand_change_write_column_op(
> +						chip, writesize + oob_skip,
> +						bufpoi, len, false);
> +				else
> +					nand_change_read_column_op(
> +						chip, writesize + oob_skip,
> +						bufpoi, len, false);
> +			} else {
> +				if (write) {
> +					chip->cmdfunc(mtd, NAND_CMD_RNDIN,
> +						      writesize + oob_skip, -1);
> +					chip->write_buf(mtd, bufpoi, len);
> +				} else {
> +					chip->cmdfunc(mtd, NAND_CMD_RNDOUT,
> +						      writesize + oob_skip, -1);
> +					chip->read_buf(mtd, bufpoi, len);
> +				}
> +			.

Ditto.

>  			bufpoi += len;
>  		}
>  	}
>  
>  	/* OOB free */
>  	len = oobsize - (bufpoi - chip->oob_poi);
> -	chip->cmdfunc(mtd, rnd_cmd, size - len, -1);
> -	if (write)
> -		chip->write_buf(mtd, bufpoi, len);
> -	else
> -		chip->read_buf(mtd, bufpoi, len);
> +	if (chip->exec_op) {
> +		if (write)
> +			nand_change_write_column_op(chip, size - len, bufpoi,
> +						    len, false);
> +		else
> +			nand_change_read_column_op(chip, size - len, bufpoi,
> +						   len, false);
> +	} else {
> +		if (write) {
> +			chip->cmdfunc(mtd, NAND_CMD_RNDIN, size - len, -1);
> +			chip->write_buf(mtd, bufpoi, len);
> +		} else {
> +			chip->cmdfunc(mtd, NAND_CMD_RNDOUT, size - len, -1);
> +			chip->read_buf(mtd, bufpoi, len);
> +		}
> +	}
>  }
>  
>  static int denali_read_page_raw(struct mtd_info *mtd, struct nand_chip *chip,
> @@ -803,6 +849,9 @@ static int denali_write_oob(struct mtd_info *mtd, struct nand_chip *chip,
>  
>  	denali_oob_xfer(mtd, chip, page, 1);
>  
> +	if (chip->exec_op)
> +		return nand_prog_page_end_op(chip);
> +
>  	chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1);
>  	status = chip->waitfunc(mtd, chip);

All denali changes in this patch should actually be moved to patch 1.

>  
> diff --git a/drivers/mtd/nand/nand_base.c b/drivers/mtd/nand/nand_base.c
> index bef20e06f0db..737f19bd2f83 100644
> --- a/drivers/mtd/nand/nand_base.c
> +++ b/drivers/mtd/nand/nand_base.c
> @@ -814,7 +814,7 @@ static void nand_ccs_delay(struct nand_chip *chip)
>  	 * Wait tCCS_min if it is correctly defined, otherwise wait 500ns
>  	 * (which should be safe for all NANDs).
>  	 */
> -	if (chip->data_interface.timings.sdr.tCCS_min)
> +	if (&chip->data_interface.timings.sdr.tCCS_min)

This condition is always true. I think this should be replaced by:

	if (chip->setup_data_interface)

And BTW, it should go in patch 3.

>  		ndelay(chip->data_interface.timings.sdr.tCCS_min / 1000);
>  	else
>  		ndelay(500);
> @@ -1233,6 +1233,118 @@ static int nand_init_data_interface(struct nand_chip *chip)
>  }
>  
>  /**
> + * nand_fill_column_cycles - fill the column fields on an address array
> + * @chip: The NAND chip
> + * @addrs: Array of address cycles to fill
> + * @offset_in_page: The offset in the page
> + *
> + * Fills the first or the two first bytes of the @addrs field depending
> + * on the NAND bus width and the page size.
> + */
> +int nand_fill_column_cycles(struct nand_chip *chip, u8 *addrs,
> +			    unsigned int offset_in_page)
> +{
> +	struct mtd_info *mtd = nand_to_mtd(chip);
> +
> +	/*
> +	 * The offset in page is expressed in bytes, if the NAND bus is 16-bit
> +	 * wide, then it must be divided by 2.
> +	 */
> +	if (chip->options & NAND_BUSWIDTH_16) {
> +		if (offset_in_page % 2) {
> +			WARN_ON(true);
> +			return -EINVAL;
> +		}
> +
> +		offset_in_page /= 2;
> +	}
> +
> +	addrs[0] = offset_in_page;
> +
> +	/* Small pages use 1 cycle for the columns, while large page need 2 */
> +	if (mtd->writesize <= 512)
> +		return 1;
> +
> +	addrs[1] = offset_in_page >> 8;
> +
> +	return 2;
> +}
> +EXPORT_SYMBOL_GPL(nand_fill_column_cycles);
> +
> +static int nand_sp_exec_read_page_op(struct nand_chip *chip, unsigned int page,
> +				     unsigned int offset_in_page, void *buf,
> +				     unsigned int len)
> +{
> +	struct mtd_info *mtd = nand_to_mtd(chip);
> +	const struct nand_sdr_timings *sdr =
> +		nand_get_sdr_timings(&chip->data_interface);
> +	u8 addrs[4];
> +	struct nand_op_instr instrs[] = {
> +		NAND_OP_CMD(NAND_CMD_READ0, 0),
> +		NAND_OP_ADDR(3, addrs, sdr->tWB_max),
> +		NAND_OP_WAIT_RDY(sdr->tR_max, sdr->tRR_min),
> +		NAND_OP_DATA_IN(len, buf, 0),
> +	};
> +	struct nand_operation op = NAND_OPERATION(instrs);
> +
> +	/* Drop the DATA_OUT instruction if len is set to 0. */
> +	if (!len)
> +		op.ninstrs--;
> +
> +	if (offset_in_page >= mtd->writesize)
> +		instrs[0].cmd.opcode = NAND_CMD_READOOB;
> +	else if (offset_in_page >= 256)
> +		instrs[0].cmd.opcode = NAND_CMD_READ1;
> +
> +	if (nand_fill_column_cycles(chip, addrs, offset_in_page) < 0)

Why not returning the retcode of nand_fill_column_cycles() directly?

	ret = nand_fill_column_cycles(chip, addrs, offset_in_page)
	if (ret < 0)
		return ret;

> +		return -EINVAL;
> +
> +	addrs[1] = page;
> +	addrs[2] = page >> 8;
> +
> +	if (chip->options & NAND_ROW_ADDR_3) {
> +		addrs[3] = page >> 16;
> +		instrs[1].addr.naddrs++;
> +	}
> +
> +	return nand_exec_op(chip, &op);
> +}
> +
> +static int nand_lp_exec_read_page_op(struct nand_chip *chip, unsigned int page,
> +				     unsigned int offset_in_page, void *buf,
> +				     unsigned int len)
> +{
> +	const struct nand_sdr_timings *sdr =
> +		nand_get_sdr_timings(&chip->data_interface);
> +	u8 addrs[5];
> +	struct nand_op_instr instrs[] = {
> +		NAND_OP_CMD(NAND_CMD_READ0, 0),
> +		NAND_OP_ADDR(4, addrs, 0),
> +		NAND_OP_CMD(NAND_CMD_READSTART, sdr->tWB_max),
> +		NAND_OP_WAIT_RDY(sdr->tR_max, sdr->tRR_min),
> +		NAND_OP_DATA_IN(len, buf, 0),
> +	};
> +	struct nand_operation op = NAND_OPERATION(instrs);
> +
> +	/* Drop the DATA_IN instruction if len is set to 0. */
> +	if (!len)
> +		op.ninstrs--;
> +
> +	if (nand_fill_column_cycles(chip, addrs, offset_in_page))

Should be

	if (nand_fill_column_cycles(chip, addrs, offset_in_page) < 0)

> +		return -EINVAL;
> +
> +	addrs[2] = page;
> +	addrs[3] = page >> 8;
> +
> +	if (chip->options & NAND_ROW_ADDR_3) {
> +		addrs[4] = page >> 16;
> +		instrs[1].addr.naddrs++;
> +	}
> +
> +	return nand_exec_op(chip, &op);
> +}
> +
> +/**
>   * nand_read_page_op - Do a READ PAGE operation
>   * @chip: The NAND chip
>   * @page: page to read
> @@ -1246,17 +1358,26 @@ static int nand_init_data_interface(struct nand_chip *chip)
>   * Returns 0 for success or negative error code otherwise
>   */
>  int nand_read_page_op(struct nand_chip *chip, unsigned int page,
> -		      unsigned int column, void *buf, unsigned int len)
> +		      unsigned int offset_in_page, void *buf, unsigned int len)

You should change the parameter name in patch 1 since this function is
introduced there.

>  {
>  	struct mtd_info *mtd = nand_to_mtd(chip);
>  
>  	if (len && !buf)
>  		return -EINVAL;
>  
> -	if (column + len > mtd->writesize + mtd->oobsize)
> +	if (offset_in_page + len > mtd->writesize + mtd->oobsize)
>  		return -EINVAL;
>  
> -	chip->cmdfunc(mtd, NAND_CMD_READ0, column, page);
> +	if (chip->exec_op) {
> +		if (mtd->writesize > 512)
> +			return nand_lp_exec_read_page_op(
> +				chip, page, offset_in_page, buf, len);
> +
> +		return nand_sp_exec_read_page_op(chip, page, offset_in_page,
> +						 buf, len);
> +	}
> +
> +	chip->cmdfunc(mtd, NAND_CMD_READ0, offset_in_page, page);
>  	if (len)
>  		chip->read_buf(mtd, buf, len);
>  
> @@ -1286,6 +1407,25 @@ static int nand_read_param_page_op(struct nand_chip *chip, u8 page, void *buf,
>  	if (len && !buf)
>  		return -EINVAL;
>  
> +	if (chip->exec_op) {
> +		const struct nand_sdr_timings *sdr =
> +			nand_get_sdr_timings(&chip->data_interface);
> +		struct nand_op_instr instrs[] = {
> +			NAND_OP_CMD(NAND_CMD_PARAM, 0),
> +			NAND_OP_ADDR(1, &page, sdr->tWB_max),
> +			NAND_OP_WAIT_RDY(sdr->tR_max, sdr->tRR_min),
> +			NAND_OP_8BIT_DATA_IN(len, buf, 0),
> +		};
> +		struct nand_operation op =
> +			NAND_OPERATION(instrs);
> +
> +		/* Drop the DATA_IN instruction if len is set to 0. */
> +		if (!len)
> +			op.ninstrs--;
> +
> +		return nand_exec_op(chip, &op);
> +	}
> +
>  	chip->cmdfunc(mtd, NAND_CMD_PARAM, page, -1);
>  	for (i = 0; i < len; i++)
>  		p[i] = chip->read_byte(mtd);

[...]

> +
>  /**
>   * nand_prog_page_begin_op - starts a PROG PAGE operation
>   * @chip: The NAND chip
> @@ -1371,7 +1608,7 @@ EXPORT_SYMBOL_GPL(nand_read_oob_op);
>   * Returns 0 for success or negative error code otherwise
>   */
>  int nand_prog_page_begin_op(struct nand_chip *chip, unsigned int page,
> -			    unsigned int column, const void *buf,
> +			    unsigned int offset_in_page, const void *buf,
>  			    unsigned int len)
>  {
>  	struct mtd_info *mtd = nand_to_mtd(chip);
> @@ -1379,10 +1616,14 @@ int nand_prog_page_begin_op(struct nand_chip *chip, unsigned int page,
>  	if (len && !buf)
>  		return -EINVAL;
>  
> -	if (column + len > mtd->writesize + mtd->oobsize)
> +	if (offset_in_page + len > mtd->writesize + mtd->oobsize)
>  		return -EINVAL;
>  
> -	chip->cmdfunc(mtd, NAND_CMD_SEQIN, column, page);
> +	if (chip->exec_op)
> +		return nand_exec_prog_page_op(
> +			chip, page, offset_in_page, buf, len, false);

Param alignment please.

> +
> +	chip->cmdfunc(mtd, NAND_CMD_SEQIN, offset_in_page, page);
>  
>  	if (buf)
>  		chip->write_buf(mtd, buf, len);
> @@ -1405,6 +1646,19 @@ int nand_prog_page_end_op(struct nand_chip *chip)
>  	struct mtd_info *mtd = nand_to_mtd(chip);
>  	int status;
>  
> +	if (chip->exec_op) {
> +		const struct nand_sdr_timings *sdr =
> +			nand_get_sdr_timings(&chip->data_interface);
> +		struct nand_op_instr instrs[] = {
> +			NAND_OP_CMD(NAND_CMD_PAGEPROG, sdr->tWB_max),
> +			NAND_OP_WAIT_RDY(sdr->tPROG_max, 0),
> +		};
> +		struct nand_operation op =
> +			NAND_OPERATION(instrs);
> +
> +		return nand_exec_op(chip, &op);
> +	}
> +
>  	chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1);
>  
>  	status = chip->waitfunc(mtd, chip);
> @@ -1429,7 +1683,8 @@ EXPORT_SYMBOL_GPL(nand_prog_page_end_op);
>   * Returns 0 for success or negative error code otherwise
>   */
>  int nand_prog_page_op(struct nand_chip *chip, unsigned int page,
> -		      unsigned int column, const void *buf, unsigned int len)
> +		      unsigned int offset_in_page, const void *buf,
> +		      unsigned int len)
>  {
>  	struct mtd_info *mtd = nand_to_mtd(chip);
>  	int status;
> @@ -1437,10 +1692,14 @@ int nand_prog_page_op(struct nand_chip *chip, unsigned int page,
>  	if (!len || !buf)
>  		return -EINVAL;
>  
> -	if (column + len > mtd->writesize + mtd->oobsize)
> +	if (offset_in_page + len > mtd->writesize + mtd->oobsize)
>  		return -EINVAL;
>  
> -	chip->cmdfunc(mtd, NAND_CMD_SEQIN, column, page);
> +	if (chip->exec_op)
> +		return nand_exec_prog_page_op(
> +			chip, page, offset_in_page, buf, len, true);
> +
> +	chip->cmdfunc(mtd, NAND_CMD_SEQIN, offset_in_page, page);
>  	chip->write_buf(mtd, buf, len);
>  	chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1);
>  
> @@ -1465,7 +1724,8 @@ EXPORT_SYMBOL_GPL(nand_prog_page_op);
>   *
>   * Returns 0 for success or negative error code otherwise
>   */
> -int nand_change_write_column_op(struct nand_chip *chip, unsigned int column,
> +int nand_change_write_column_op(struct nand_chip *chip,
> +				unsigned int offset_in_page,
>  				const void *buf, unsigned int len,
>  				bool force_8bit)
>  {
> @@ -1474,10 +1734,38 @@ int nand_change_write_column_op(struct nand_chip *chip, unsigned int column,
>  	if (len && !buf)
>  		return -EINVAL;
>  
> -	if (column + len > mtd->writesize + mtd->oobsize)
> +	if (offset_in_page + len > mtd->writesize + mtd->oobsize)
>  		return -EINVAL;
>  
> -	chip->cmdfunc(mtd, NAND_CMD_RNDIN, column, -1);
> +	/* Small page NANDs do not support column change. */
> +	if (mtd->writesize <= 512)
> +		return -ENOTSUPP;
> +
> +	if (chip->exec_op) {
> +		const struct nand_sdr_timings *sdr =
> +			nand_get_sdr_timings(&chip->data_interface);
> +		u8 addrs[2];
> +		struct nand_op_instr instrs[] = {
> +			NAND_OP_CMD(NAND_CMD_RNDIN, 0),
> +			NAND_OP_ADDR(2, addrs, sdr->tCCS_min),
> +			NAND_OP_DATA_OUT(len, buf, 0),
> +		};
> +		struct nand_operation op =
> +			NAND_OPERATION(instrs);
> +
> +		if (nand_fill_column_cycles(chip, addrs, offset_in_page) < 0)
> +			return -EINVAL;
> +
> +		instrs[2].data.force_8bit = force_8bit;
> +
> +		/* Drop the DATA_OUT instruction if len is set to 0. */
> +		if (!len)
> +			op.ninstrs--;
> +
> +		return nand_exec_op(chip, &op);
> +	}
> +
> +	chip->cmdfunc(mtd, NAND_CMD_RNDIN, offset_in_page, -1);
>  	if (len)
>  		chip->write_buf(mtd, buf, len);
>  
> @@ -1508,6 +1796,24 @@ int nand_readid_op(struct nand_chip *chip, u8 addr,
>  	if (!len || !buf)
>  		return -EINVAL;
>  
> +	if (chip->exec_op) {
> +		const struct nand_sdr_timings *sdr =
> +			nand_get_sdr_timings(&chip->data_interface);
> +		struct nand_op_instr instrs[] = {
> +			NAND_OP_CMD(NAND_CMD_READID, 0),
> +			NAND_OP_ADDR(1, &addr, sdr->tADL_min),
> +			NAND_OP_8BIT_DATA_IN(len, buf, 0),
> +		};
> +		struct nand_operation op =
> +			NAND_OPERATION(instrs);
> +
> +		/* Drop the DATA_IN instruction if len is set to 0. */
> +		if (!len)
> +			op.ninstrs--;
> +
> +		return nand_exec_op(chip, &op);
> +	}
> +
>  	chip->cmdfunc(mtd, NAND_CMD_READID, addr, -1);
>  
>  	for (i = 0; i < len; i++)
> @@ -1532,6 +1838,22 @@ int nand_status_op(struct nand_chip *chip, u8 *status)
>  {
>  	struct mtd_info *mtd = nand_to_mtd(chip);
>  
> +	if (chip->exec_op) {
> +		const struct nand_sdr_timings *sdr =
> +			nand_get_sdr_timings(&chip->data_interface);
> +		struct nand_op_instr instrs[] = {
> +			NAND_OP_CMD(NAND_CMD_STATUS, sdr->tADL_min),
> +			NAND_OP_8BIT_DATA_IN(1, status, 0),
> +		};
> +		struct nand_operation op =
> +			NAND_OPERATION(instrs);
> +
> +		if (!status)
> +			op.ninstrs--;
> +
> +		return nand_exec_op(chip, &op);
> +	}
> +
>  	chip->cmdfunc(mtd, NAND_CMD_STATUS, -1, -1);
>  	if (status)
>  		*status = chip->read_byte(mtd);
> @@ -1558,6 +1880,25 @@ int nand_erase_op(struct nand_chip *chip, unsigned int eraseblock)
>  			    (chip->phys_erase_shift - chip->page_shift);
>  	int status;
>  
> +	if (chip->exec_op) {
> +		const struct nand_sdr_timings *sdr =
> +			nand_get_sdr_timings(&chip->data_interface);
> +		u8 addrs[3] = {	page, page >> 8, page >> 16 };
> +		struct nand_op_instr instrs[] = {
> +			NAND_OP_CMD(NAND_CMD_ERASE1, 0),
> +			NAND_OP_ADDR(2, addrs, 0),
> +			NAND_OP_CMD(NAND_CMD_ERASE2, sdr->tWB_max),
> +			NAND_OP_WAIT_RDY(sdr->tBERS_max, 0),
> +		};
> +		struct nand_operation op =
> +			NAND_OPERATION(instrs);
> +
> +		if (chip->options & NAND_ROW_ADDR_3)
> +			instrs[1].addr.naddrs++;
> +
> +		return nand_exec_op(chip, &op);
> +	}
> +
>  	chip->cmdfunc(mtd, NAND_CMD_ERASE1, -1, page);
>  	chip->cmdfunc(mtd, NAND_CMD_ERASE2, -1, -1);
>  
> @@ -1591,6 +1932,22 @@ static int nand_set_features_op(struct nand_chip *chip, u8 feature,
>  	const u8 *params = data;
>  	int i, status;
>  
> +	if (chip->exec_op) {
> +		const struct nand_sdr_timings *sdr =
> +			nand_get_sdr_timings(&chip->data_interface);
> +		struct nand_op_instr instrs[] = {
> +			NAND_OP_CMD(NAND_CMD_SET_FEATURES, 0),
> +			NAND_OP_ADDR(1, &feature, sdr->tADL_min),
> +			NAND_OP_8BIT_DATA_OUT(ONFI_SUBFEATURE_PARAM_LEN, data,
> +					      sdr->tWB_max),
> +			NAND_OP_WAIT_RDY(sdr->tFEAT_max, 0),
> +		};
> +		struct nand_operation op =
> +			NAND_OPERATION(instrs);
> +
> +		return nand_exec_op(chip, &op);
> +	}
> +
>  	chip->cmdfunc(mtd, NAND_CMD_SET_FEATURES, feature, -1);
>  	for (i = 0; i < ONFI_SUBFEATURE_PARAM_LEN; ++i)
>  		chip->write_byte(mtd, params[i]);
> @@ -1621,6 +1978,22 @@ static int nand_get_features_op(struct nand_chip *chip, u8 feature,
>  	u8 *params = data;
>  	int i;
>  
> +	if (chip->exec_op) {
> +		const struct nand_sdr_timings *sdr =
> +			nand_get_sdr_timings(&chip->data_interface);
> +		struct nand_op_instr instrs[] = {
> +			NAND_OP_CMD(NAND_CMD_GET_FEATURES, 0),
> +			NAND_OP_ADDR(1, &feature, sdr->tWB_max),
> +			NAND_OP_WAIT_RDY(sdr->tFEAT_max, sdr->tRR_min),
> +			NAND_OP_8BIT_DATA_IN(ONFI_SUBFEATURE_PARAM_LEN,
> +					     data, 0),
> +		};
> +		struct nand_operation op =
> +			NAND_OPERATION(instrs);
> +
> +		return nand_exec_op(chip, &op);
> +	}
> +
>  	chip->cmdfunc(mtd, NAND_CMD_GET_FEATURES, feature, -1);
>  	for (i = 0; i < ONFI_SUBFEATURE_PARAM_LEN; ++i)
>  		params[i] = chip->read_byte(mtd);
> @@ -1642,6 +2015,19 @@ int nand_reset_op(struct nand_chip *chip)
>  {
>  	struct mtd_info *mtd = nand_to_mtd(chip);
>  
> +	if (chip->exec_op) {
> +		const struct nand_sdr_timings *sdr =
> +			nand_get_sdr_timings(&chip->data_interface);
> +		struct nand_op_instr instrs[] = {
> +			NAND_OP_CMD(NAND_CMD_RESET, sdr->tWB_max),
> +			NAND_OP_WAIT_RDY(sdr->tRST_max, 0),
> +		};
> +		struct nand_operation op =
> +			NAND_OPERATION(instrs);
> +
> +		return nand_exec_op(chip, &op);
> +	}
> +
>  	chip->cmdfunc(mtd, NAND_CMD_RESET, -1, -1);
>  
>  	return 0;
> @@ -1669,6 +2055,18 @@ int nand_read_data_op(struct nand_chip *chip, void *buf, unsigned int len,
>  	if (!len || !buf)
>  		return -EINVAL;
>  
> +	if (chip->exec_op) {
> +		struct nand_op_instr instrs[] = {
> +			NAND_OP_DATA_IN(len, buf, 0),
> +		};
> +		struct nand_operation op =
> +			NAND_OPERATION(instrs);
> +
> +		instrs[0].data.force_8bit = force_8bit;
> +
> +		return nand_exec_op(chip, &op);
> +	}
> +
>  	if (force_8bit) {
>  		u8 *p = buf;
>  		unsigned int i;
> @@ -1704,6 +2102,18 @@ int nand_write_data_op(struct nand_chip *chip, const void *buf,
>  	if (!len || !buf)
>  		return -EINVAL;
>  
> +	if (chip->exec_op) {
> +		struct nand_op_instr instrs[] = {
> +			NAND_OP_DATA_OUT(len, buf, 0),
> +		};
> +		struct nand_operation op =
> +			NAND_OPERATION(instrs);
> +
> +		instrs[0].data.force_8bit = force_8bit;
> +
> +		return nand_exec_op(chip, &op);
> +	}
> +
>  	if (force_8bit) {
>  		const u8 *p = buf;
>  		unsigned int i;
> @@ -1719,6 +2129,471 @@ int nand_write_data_op(struct nand_chip *chip, const void *buf,
>  EXPORT_SYMBOL_GPL(nand_write_data_op);
>  
>  /**
> + * struct nand_op_parser_ctx - Context used by the parser
> + * @instrs: array of all the instructions that must be addressed
> + * @ninstrs: length of the @instrs array
> + * @instr_idx: index of the instruction in the @instrs array that matches the
> + *	       first instruction of the subop structure
> + * @instr_start_off: offset at which the first instruction of the subop
> + *		     structure must start if it is and address or a data
> + *		     instruction
> + *
> + * This structure is used by the core to handle splitting lengthy instructions
> + * into sub-operations.
> + */
> +struct nand_op_parser_ctx {
> +	const struct nand_op_instr *instrs;
> +	unsigned int ninstrs;
> +	unsigned int instr_idx;
> +	unsigned int instr_start_off;
> +	struct nand_subop subop;
> +};
> +
> +/**
> + * nand_op_parser_must_split_instr - Checks if an instruction must be split
> + * @pat: the parser pattern that match
> + * @instr: the instruction array to check
> + * @start_offset: the offset from which to start in the first instruction of the
> + *		  @instr array
> + *
> + * Some NAND controllers are limited and cannot send X address cycles with a
> + * unique operation, or cannot read/write more than Y bytes at the same time.
> + * In this case, reduce the scope of this set of operation, and trigger another
> + * instruction with the rest.

"
In this case, split the instruction that does not fit in a single
controller-operation into two or more chunks.
"

> + *
> + * Returns true if the array of instruction must be split, false otherwise.

s/array of//

> + * The @start_offset parameter is also updated to the offset at which the first
> + * instruction of the next bundle of instruction must start (if an address or a

"
The @start_offset parameter is also updated to the offset at which the
the next bundle of instructions must start ...
"

> + * data instruction).
> + */
> +static bool
> +nand_op_parser_must_split_instr(const struct nand_op_parser_pattern_elem *pat,
> +				const struct nand_op_instr *instr,
> +				unsigned int *start_offset)
> +{
> +	switch (pat->type) {
> +	case NAND_OP_ADDR_INSTR:
> +		if (!pat->addr.maxcycles)
> +			break;
> +
> +		if (instr->addr.naddrs - *start_offset > pat->addr.maxcycles) {
> +			*start_offset += pat->addr.maxcycles;
> +			return true;
> +		}
> +		break;
> +
> +	case NAND_OP_DATA_IN_INSTR:
> +	case NAND_OP_DATA_OUT_INSTR:
> +		if (!pat->data.maxlen)
> +			break;
> +
> +		if (instr->data.len - *start_offset > pat->data.maxlen) {
> +			*start_offset += pat->data.maxlen;
> +			return true;
> +		}
> +		break;
> +
> +	default:
> +		break;
> +	}
> +
> +	return false;
> +}
> +
> +/**
> + * nand_op_parser_match_pat - Checks a pattern
> + * @pat: the parser pattern to check if it matches
> + * @ctx: the context structure to match with the pattern @pat
> + *
> + * Check if *one* given pattern matches the given sequence of instructions
> + */
> +static bool
> +nand_op_parser_match_pat(const struct nand_op_parser_pattern *pat,
> +			 struct nand_op_parser_ctx *ctx)
> +{
> +	unsigned int i, j, boundary_off = ctx->instr_start_off;
> +
> +	ctx->subop.ninstrs = 0;
> +
> +	for (i = ctx->instr_idx, j = 0; i < ctx->ninstrs && j < pat->nelems;) {
> +		const struct nand_op_instr *instr = &ctx->instrs[i];
> +
> +		/*
> +		 * The pattern instruction does not match the operation
> +		 * instruction. If the instruction is marked optional in the
> +		 * pattern definition, we skip the pattern element and continue
> +		 * to the next one. If the element is mandatory, there's no
> +		 * match and we can return false directly.
> +		 */
> +		if (instr->type != pat->elems[j].type) {
> +			if (!pat->elems[j].optional)
> +				return false;
> +
> +			j++;
> +			continue;
> +		}
> +
> +		/*
> +		 * Now check the pattern element constraints. If the pattern is
> +		 * not able to handle the whole instruction in a single step,
> +		 * we'll have to break it down into several instructions.
> +		 * The *boudary_off value comes back updated to point to the
> +		 * limit between the split instruction (the end of the original
> +		 * chunk, the start of new next one).
> +		 */
> +		if (nand_op_parser_must_split_instr(&pat->elems[j], instr,
> +						    &boundary_off)) {
> +			ctx->subop.ninstrs++;
> +			j++;
> +			break;
> +		}
> +
> +		ctx->subop.ninstrs++;
> +		i++;
> +		j++;
> +		boundary_off = 0;
> +	}
> +
> +	/*
> +	 * This can happen if all instructions of a pattern are optional.
> +	 * Still, if there's not at least one instruction handled by this
> +	 * pattern, this is not a match, and we should try the next one (if
> +	 * any).
> +	 */
> +	if (!ctx->subop.ninstrs)
> +		return false;
> +
> +	/*
> +	 * We had a match on the pattern head, but the pattern may be longer
> +	 * than the instructions we're asked to execute. We need to make sure
> +	 * there's no mandatory elements in the pattern tail.
> +	 *
> +	 * The case where all the operations of a pattern have been checked but
> +	 * the number of instructions is bigger is handled right after this by
> +	 * returning true on the pattern match, which will order the execution
> +	 * of the subset of instructions later defined, while updating the
> +	 * context ids to the next chunk of instructions.
> +	 */
> +	for (; j < pat->nelems; j++) {
> +		if (!pat->elems[j].optional)
> +			return false;
> +	}
> +
> +	/*
> +	 * We have a match: update the ctx and return true. The subop structure
> +	 * will be used by the pattern's ->exec() function.
> +	 */
> +	ctx->subop.instrs = &ctx->instrs[ctx->instr_idx];
> +	ctx->subop.first_instr_start_off = ctx->instr_start_off;
> +	ctx->subop.last_instr_end_off = boundary_off;
> +
> +	/*
> +	 * Update the pointers so the calling function will be able to recall
> +	 * this one with a new subset of instructions.
> +	 *
> +	 * In the case where the last operation of this set is split, point to
> +	 * the last unfinished job, knowing the starting offset.
> +	 */
> +	ctx->instr_idx = i;
> +	ctx->instr_start_off = boundary_off;
> +
> +	return true;
> +}
> +
> +#if IS_ENABLED(CONFIG_DYNAMIC_DEBUG) || defined(DEBUG)
> +static void nand_op_parser_trace(const struct nand_op_parser_ctx *ctx,
> +				 bool success)
> +{
> +	const struct nand_op_instr *instr;
> +	bool in_subop = false;
> +	char *is_in =  "    ->", *is_out = "      ", *prefix;
> +	char *buf;
> +	unsigned int len, off = 0;
> +	int i, j;
> +
> +	if (success)
> +		pr_debug("executing subop:\n");
> +	else
> +		pr_debug("pattern not found:\n");
> +
> +	for (i = 0; i < ctx->ninstrs; i++) {
> +		instr = &ctx->instrs[i];
> +
> +		/*
> +		 * ctx->instr_idx is not reliable because it may already had

									 have

> +		 * been updated by the parser. Use pointers comparison instead.
> +		 */
> +		if (instr == &ctx->subop.instrs[0])
> +			in_subop = true;

It seems in_subop is only used to select the prefix, so you can just
get rid of it and do

		if (instr == &ctx->subop.instrs[0])
			prefix = "    ->";

BTW, if success is false, you should not even try to change the prefix,
right?

> +
> +		if (in_subop)
> +			prefix = is_in;
> +		else
> +			prefix = is_out;
> +
> +		switch (instr->type) {
> +		case NAND_OP_CMD_INSTR:
> +			pr_debug("%sCMD      [0x%02x]\n", prefix,
> +				 instr->cmd.opcode);
> +			break;
> +		case NAND_OP_ADDR_INSTR:
> +			/*
> +			 * A log line is much less than 50 bytes, plus 5 bytes
> +			 * per address cycle to display.
> +			 */
> +			len = 50 + 5 * instr->addr.naddrs;
> +			buf = kmalloc(len, GFP_KERNEL);
> +			if (!buf)
> +				return;
> +			memset(buf, 0, len);
> +			off += snprintf(buf, len, "ADDR     [%d cyc:",
> +					instr->addr.naddrs);
> +			for (j = 0; j < instr->addr.naddrs; j++)
> +				off += snprintf(&buf[off], len - off, " 0x%02x",
> +						instr->addr.addrs[j]);
> +			pr_debug("%s%s]\n", prefix, buf);
> +			break;
> +		case NAND_OP_DATA_IN_INSTR:
> +			pr_debug("%sDATA_IN  [%d B%s]\n", prefix,
> +				 instr->data.len,
> +				 instr->data.force_8bit ? ", force 8-bit" : "");
> +			break;
> +		case NAND_OP_DATA_OUT_INSTR:
> +			pr_debug("%sDATA_OUT [%d B%s]\n", prefix,
> +				 instr->data.len,
> +				 instr->data.force_8bit ? ", force 8-bit" : "");
> +			break;
> +		case NAND_OP_WAITRDY_INSTR:
> +			pr_debug("%sWAITRDY  [max %d ms]\n", prefix,
> +				 instr->waitrdy.timeout_ms);
> +			break;
> +		}
> +
> +		if (instr == &ctx->subop.instrs[ctx->subop.ninstrs - 1])
> +			in_subop = false;

		if (instr == &ctx->subop.instrs[ctx->subop.ninstrs - 1])
			prefix = "      ";

and initialize prefix to "      " at the beginning of the function.

> +	}
> +}
> +#else
> +static void nand_op_parser_trace(const struct nand_op_parser_ctx *ctx,
> +				 bool success)
> +{
> +	/* NOP */
> +}
> +#endif
> +
> +/**
> + * nand_op_parser_exec_op - exec_op parser
> + * @chip: the NAND chip
> + * @parser: the parser to use given by the controller driver
> + * @op: the NAND operation to address
> + * @check_only: flag asking if the entire operation could be handled
> + *
> + * Function that must be called by each driver that implement the "exec_op API"
> + * in their own ->exec_op() implementation.
> + *
> + * The function iterates on all the instructions asked and make use of internal
> + * parsers to find matches between the instruction list and the handled patterns
> + * filled by the controller drivers inside the @parser structure. If needed, the
> + * instructions could be split into sub-operations and be executed sequentially.
> + */
> +int nand_op_parser_exec_op(struct nand_chip *chip,
> +			   const struct nand_op_parser *parser,
> +			   const struct nand_operation *op, bool check_only)
> +{
> +	struct nand_op_parser_ctx ctx = {
> +		.instrs = op->instrs,
> +		.ninstrs = op->ninstrs,
> +	};
> +	unsigned int i;
> +
> +	while (ctx.instr_idx < op->ninstrs) {
> +		bool pattern_found = false;
> +		int ret;
> +
> +		for (i = 0; i < parser->npatterns; i++) {
> +			const struct nand_op_parser_pattern *pattern;
> +
> +			pattern = &parser->patterns[i];
> +			if (!nand_op_parser_match_pat(pattern, &ctx))
> +				continue;
> +
> +			nand_op_parser_trace(&ctx, true);
> +

pattern_found should be set to true here. But I'm not even sure you
really need this variable.

> +			if (check_only)
> +				break;
> +
> +			ret = pattern->exec(chip, &ctx.subop);
> +			if (ret)
> +				return ret;
> +
> +			pattern_found = true;
> +		}
> +
> +		if (!pattern_found) {

Just test

		if (i == parser->npatterns) {

and you should be good.

> +			nand_op_parser_trace(&ctx, false);
> +			return -ENOTSUPP;
> +		}
> +	}
> +
> +	return 0;
> +}
> +EXPORT_SYMBOL_GPL(nand_op_parser_exec_op);
> +
> +static bool nand_instr_is_data(const struct nand_op_instr *instr)
> +{
> +	return instr && (instr->type == NAND_OP_DATA_IN_INSTR ||
> +			 instr->type == NAND_OP_DATA_OUT_INSTR);
> +}
> +
> +static bool nand_subop_instr_is_valid(const struct nand_subop *subop,
> +				      unsigned int op_id)

s/op_id/instr_idx/

> +{
> +	return subop && op_id < subop->ninstrs;
> +}
> +
> +static int nand_subop_get_start_off(const struct nand_subop *subop,
> +				    unsigned int op_id)

Ditto.

> +{
> +	if (op_id)
> +		return 0;
> +
> +	return subop->first_instr_start_off;
> +}
> +
> +/**
> + * nand_subop_get_addr_start_off - Get the start offset in an address array
> + * @subop: The entire sub-operation
> + * @op_id: Index of the instruction inside the sub-operation

instr_idx.

> + *
> + * Instructions arrays may be split by the parser between instructions,
> + * and also in the middle of an address instruction if the number of cycles
> + * to assert in one operation is not supported by the controller.
> + *
> + * For this, instead of using the first index of the ->addr.addrs field from the
> + * address instruction, the NAND controller driver must use this helper that
> + * will either return 0 if the index does not point to the first instruction of
> + * the sub-operation, or the offset of the next starting offset inside the
> + * address cycles.

I think you can drop this paragraph which IMO brings more confusion to
what this function does.

> + *
> + * Returns the offset of the first address cycle to assert from the pointed
> + * address instruction.

This part is definitely clearer.

> + */
> +int nand_subop_get_addr_start_off(const struct nand_subop *subop,
> +				  unsigned int op_id)
> +{
> +	if (!nand_subop_instr_is_valid(subop, op_id) ||
> +	    subop->instrs[op_id].type != NAND_OP_ADDR_INSTR)
> +		return -EINVAL;
> +
> +	return nand_subop_get_start_off(subop, op_id);
> +}
> +EXPORT_SYMBOL_GPL(nand_subop_get_addr_start_off);
> +
> +/**
> + * nand_subop_get_num_addr_cyc - Get the remaining address cycles to assert
> + * @subop: The entire sub-operation
> + * @op_id: Index of the instruction inside the sub-operation

instr_idx

> + *
> + * Instructions arrays may be split by the parser between instructions,
> + * and also in the middle of an address instruction if the number of cycles
> + * to assert in one operation is not supported by the controller.
> + *
> + * For this, instead of using the ->addr.naddrs fields of the address
> + * instruction, the NAND controller driver must use this helper that will
> + * return the actual number of cycles to assert between the first and last
> + * offset asked for this particular instruction.
> + *
> + * Returns the number of address cycles to assert from the pointed address
> + * instruction.
> + */
> +int nand_subop_get_num_addr_cyc(const struct nand_subop *subop,
> +				unsigned int op_id)
> +{
> +	int start_off, end_off;
> +
> +	if (!nand_subop_instr_is_valid(subop, op_id) ||
> +	    subop->instrs[op_id].type != NAND_OP_ADDR_INSTR)
> +		return -EINVAL;
> +
> +	start_off = nand_subop_get_addr_start_off(subop, op_id);
> +
> +	if (op_id == subop->ninstrs - 1 &&
> +	    subop->last_instr_end_off)
> +		end_off = subop->last_instr_end_off;
> +	else
> +		end_off = subop->instrs[op_id].addr.naddrs;
> +
> +	return end_off - start_off;
> +}
> +EXPORT_SYMBOL_GPL(nand_subop_get_num_addr_cyc);
> +
> +/**
> + * nand_subop_get_data_start_off - Get the start offset in a data array
> + * @subop: The entire sub-operation
> + * @op_id: Index of the instruction inside the sub-operation
> + *
> + * Instructions arrays may be split by the parser between instructions,
> + * and also in the middle of a data instruction if the number of bytes to access
> + * in one operation is greater that the controller limit.
> + *
> + * For this, instead of using the first index of the ->data.buf field from the
> + * data instruction, the NAND controller driver must use this helper that
> + * will either return 0 if the index does not point to the first instruction of
> + * the sub-operation, or the offset of the next starting offset inside the
> + * data buffer.

Same as for nand_subop_get_addr_start_off(), the explanation is
confusing. I think we can drop it.

> + *
> + * Returns the data offset inside the pointed data instruction buffer from which
> + * to start.
> + */
> +int nand_subop_get_data_start_off(const struct nand_subop *subop,
> +				  unsigned int op_id)
> +{
> +	if (!nand_subop_instr_is_valid(subop, op_id) ||
> +	    !nand_instr_is_data(&subop->instrs[op_id]))
> +		return -EINVAL;
> +
> +	return nand_subop_get_start_off(subop, op_id);
> +}
> +EXPORT_SYMBOL_GPL(nand_subop_get_data_start_off);
> +
> +/**
> + * nand_subop_get_data_len - Get the number of bytes to retrieve
> + * @subop: The entire sub-operation
> + * @op_id: Index of the instruction inside the sub-operation
> + *
> + * Instructions arrays may be split by the parser between instructions,
> + * and also in the middle of a data instruction if the number of bytes to access
> + * in one operation is greater that the controller limit.
> + *
> + * For this, instead of using the ->data.len field from the data instruction,
> + * the NAND controller driver must use this helper that will return the actual
> + * length of data to move between the first and last offset asked for this
> + * particular instruction.
> + *
> + * Returns the length of the data to move from the pointed data instruction.
> + */
> +int nand_subop_get_data_len(const struct nand_subop *subop,
> +			    unsigned int op_id)
> +{
> +	int start_off = 0, end_off;
> +
> +	if (!nand_subop_instr_is_valid(subop, op_id) ||
> +	    !nand_instr_is_data(&subop->instrs[op_id]))
> +		return -EINVAL;
> +
> +	start_off = nand_subop_get_data_start_off(subop, op_id);
> +
> +	if (op_id == subop->ninstrs - 1 &&
> +	    subop->last_instr_end_off)
> +		end_off = subop->last_instr_end_off;
> +	else
> +		end_off = subop->instrs[op_id].data.len;
> +
> +	return end_off - start_off;
> +}
> +EXPORT_SYMBOL_GPL(nand_subop_get_data_len);
> +
> +/**
>   * nand_reset - Reset and initialize a NAND device
>   * @chip: The NAND chip
>   * @chipnr: Internal die id
> @@ -3940,7 +4815,7 @@ static void nand_set_defaults(struct nand_chip *chip)
>  		chip->chip_delay = 20;
>  
>  	/* check, if a user supplied command function given */
> -	if (chip->cmdfunc == NULL)
> +	if (chip->cmdfunc == NULL && !chip->exec_op)
>  		chip->cmdfunc = nand_command;
>  
>  	/* check, if a user supplied wait function given */
> @@ -4823,15 +5698,35 @@ int nand_scan_ident(struct mtd_info *mtd, int maxchips,
>  	if (!mtd->name && mtd->dev.parent)
>  		mtd->name = dev_name(mtd->dev.parent);
>  
> -	if ((!chip->cmdfunc || !chip->select_chip) && !chip->cmd_ctrl) {
> +	/*
> +	 * ->cmdfunc() is legacy and will only be used if ->exec_op() is not
> +	 * populated.
> +	 */
> +	if (chip->exec_op) {
>  		/*
> -		 * Default functions assigned for chip_select() and
> -		 * cmdfunc() both expect cmd_ctrl() to be populated,
> -		 * so we need to check that that's the case
> +		 * The implementation of ->exec_op() heavily relies on timings
> +		 * to be accessible through the nand_data_interface structure.
> +		 * Thus, the ->setup_data_interface() hook must be provided. The
> +		 * controller driver will be noticed of delays it must apply
> +		 * after each ->exec_op() instruction (if any) through the
> +		 * .delay_ns field. The driver will then choose to handle the
> +		 * delays manually if the controller cannot do it natively.
>  		 */
> -		pr_err("chip.cmd_ctrl() callback is not provided");
> -		return -EINVAL;
> +		if (!chip->setup_data_interface) {
> +			pr_err("->setup_data_interface() should be provided\n");
> +			return -EINVAL;
> +		}
> +	} else {
> +		/*
> +		 * Default functions assigned for ->cmdfunc() and
> +		 * ->select_chip() both expect ->cmd_ctrl() to be populated.
> +		 */
> +		if ((!chip->cmdfunc || !chip->select_chip) && !chip->cmd_ctrl) {
> +			pr_err("->cmd_ctrl() should be provided\n");
> +			return -EINVAL;
> +		}
>  	}
> +
>  	/* Set the default functions */
>  	nand_set_defaults(chip);
>  
> diff --git a/drivers/mtd/nand/nand_hynix.c b/drivers/mtd/nand/nand_hynix.c
> index 04e3ab7a476c..81c382f24513 100644
> --- a/drivers/mtd/nand/nand_hynix.c
> +++ b/drivers/mtd/nand/nand_hynix.c
> @@ -74,19 +74,36 @@ static bool hynix_nand_has_valid_jedecid(struct nand_chip *chip)
>  	return !strncmp("JEDEC", jedecid, sizeof(jedecid));
>  }
>  
> +static int hynix_nand_cmd_op(struct nand_chip *chip, u8 cmd)

Maybe you can introduce the helper in patch 1

> +{
> +	struct mtd_info *mtd = nand_to_mtd(chip);
> +
> +	if (chip->exec_op) {
> +		struct nand_op_instr instrs[] = {
> +			NAND_OP_CMD(cmd, 0),
> +		};
> +		struct nand_operation op = NAND_OPERATION(instrs);
> +
> +		return nand_exec_op(chip, &op);

And then ass this block of code in this patch.

> +	}
> +
> +	chip->cmdfunc(mtd, cmd, -1, -1);
> +
> +	return 0;
> +}
> +
>  static int hynix_nand_setup_read_retry(struct mtd_info *mtd, int retry_mode)
>  {
>  	struct nand_chip *chip = mtd_to_nand(mtd);
>  	struct hynix_nand *hynix = nand_get_manufacturer_data(chip);
>  	const u8 *values;
> -	int status;
>  	int i;
>  
>  	values = hynix->read_retry->values +
>  		 (retry_mode * hynix->read_retry->nregs);
>  
>  	/* Enter 'Set Hynix Parameters' mode */
> -	chip->cmdfunc(mtd, NAND_HYNIX_CMD_SET_PARAMS, -1, -1);
> +	hynix_nand_cmd_op(chip, NAND_HYNIX_CMD_SET_PARAMS);
>  
>  	/*
>  	 * Configure the NAND in the requested read-retry mode.
> @@ -101,16 +118,17 @@ static int hynix_nand_setup_read_retry(struct mtd_info *mtd, int retry_mode)
>  		int column = hynix->read_retry->regs[i];
>  
>  		column |= column << 8;
> -		chip->cmdfunc(mtd, NAND_CMD_NONE, column, -1);
> -		chip->write_byte(mtd, values[i]);
> +		if (chip->exec_op) {
> +			nand_change_write_column_op(chip, column,
> +						    &values[i], 1, true);

This is not a nand_change_write_column_op() op, here the cmd cycle is
set to NAND_CMD_NONE. You should have your own operation defined to
handle this sequence.

> +		} else {
> +			chip->cmdfunc(mtd, NAND_CMD_NONE, column, -1);
> +			chip->write_byte(mtd, values[i]);
> +		}
>  	}
>  
>  	/* Apply the new settings. */
> -	chip->cmdfunc(mtd, NAND_HYNIX_CMD_APPLY_PARAMS, -1, -1);
> -
> -	status = chip->waitfunc(mtd, chip);
> -	if (status & NAND_STATUS_FAIL)
> -		return -EIO;
> +	hynix_nand_cmd_op(chip, NAND_HYNIX_CMD_APPLY_PARAMS);

No, it's wrong, you miss the WAITRDY instruction to be compatible
with what was done before.

>  
>  	return 0;
>  }
> @@ -173,32 +191,65 @@ static int hynix_read_rr_otp(struct nand_chip *chip,
>  
>  	nand_reset_op(chip);
>  
> -	chip->cmdfunc(mtd, NAND_HYNIX_CMD_SET_PARAMS, -1, -1);
> +	hynix_nand_cmd_op(chip, NAND_HYNIX_CMD_SET_PARAMS);
>  
>  	for (i = 0; i < info->nregs; i++) {
>  		int column = info->regs[i];
>  
>  		column |= column << 8;
> -		chip->cmdfunc(mtd, NAND_CMD_NONE, column, -1);
> -		chip->write_byte(mtd, info->values[i]);
> +		if (chip->exec_op) {
> +			nand_change_write_column_op(chip, column,
> +						    &info->values[i], 1, true);
> +		} else {
> +			chip->cmdfunc(mtd, NAND_CMD_NONE, column, -1);
> +			chip->write_byte(mtd, info->values[i]);
> +		}

Same comments apply here.

>  	}
>  
> -	chip->cmdfunc(mtd, NAND_HYNIX_CMD_APPLY_PARAMS, -1, -1);
> +	hynix_nand_cmd_op(chip, NAND_HYNIX_CMD_APPLY_PARAMS);
>  
>  	/* Sequence to enter OTP mode? */
> -	chip->cmdfunc(mtd, 0x17, -1, -1);
> -	chip->cmdfunc(mtd, 0x04, -1, -1);
> -	chip->cmdfunc(mtd, 0x19, -1, -1);
> +	if (chip->exec_op) {
> +		struct nand_op_instr instrs[] = {
> +			NAND_OP_CMD(0x17, 0),
> +			NAND_OP_CMD(0x04, 0),
> +			NAND_OP_CMD(0x19, 0),
> +		};
> +		struct nand_operation op = NAND_OPERATION(instrs);
> +
> +		nand_exec_op(chip, &op);
> +	} else {
> +		chip->cmdfunc(mtd, 0x17, -1, -1);
> +		chip->cmdfunc(mtd, 0x04, -1, -1);
> +		chip->cmdfunc(mtd, 0x19, -1, -1);
> +	}

Why not creating a hynix_nand_enter_otp_mode_op() for that? 

>  
>  	/* Now read the page */
>  	nand_read_page_op(chip, info->page, 0, buf, info->size);
>  
>  	/* Put everything back to normal */
>  	nand_reset_op(chip);
> -	chip->cmdfunc(mtd, NAND_HYNIX_CMD_SET_PARAMS, 0x38, -1);
> -	chip->write_byte(mtd, 0x0);
> -	chip->cmdfunc(mtd, NAND_HYNIX_CMD_APPLY_PARAMS, -1, -1);
> -	chip->cmdfunc(mtd, NAND_CMD_READ0, 0x0, -1);
> +	if (chip->exec_op) {
> +		const struct nand_sdr_timings *sdr =
> +			nand_get_sdr_timings(&chip->data_interface);
> +		u8 byte = 0;
> +		u8 addr = 0x38;
> +		struct nand_op_instr instrs[] = {
> +			NAND_OP_CMD(NAND_HYNIX_CMD_SET_PARAMS, 0),
> +			NAND_OP_ADDR(1, &addr, sdr->tCCS_min),
> +			NAND_OP_8BIT_DATA_OUT(1, &byte, 0),
> +			NAND_OP_CMD(NAND_HYNIX_CMD_APPLY_PARAMS, 0),
> +			NAND_OP_CMD(NAND_CMD_READ0, 0),
> +		};
> +		struct nand_operation op = NAND_OPERATION(instrs);
> +
> +		nand_exec_op(chip, &op);
> +	} else {
> +		chip->cmdfunc(mtd, NAND_HYNIX_CMD_SET_PARAMS, 0x38, -1);
> +		chip->write_byte(mtd, 0x0);
> +		chip->cmdfunc(mtd, NAND_HYNIX_CMD_APPLY_PARAMS, -1, -1);
> +		chip->cmdfunc(mtd, NAND_CMD_READ0, 0x0, -1);
> +	}

Same here.

>  
>  	return 0;
>  }
> diff --git a/drivers/mtd/nand/nand_micron.c b/drivers/mtd/nand/nand_micron.c
> index 543352380ffa..109d8003e33d 100644
> --- a/drivers/mtd/nand/nand_micron.c
> +++ b/drivers/mtd/nand/nand_micron.c
> @@ -117,14 +117,38 @@ micron_nand_read_page_on_die_ecc(struct mtd_info *mtd, struct nand_chip *chip,
>  				 uint8_t *buf, int oob_required,
>  				 int page)
>  {
> -	int status;
> +	u8 status;
>  	int max_bitflips = 0;
>  
>  	micron_nand_on_die_ecc_setup(chip, true);
>  
> -	chip->cmdfunc(mtd, NAND_CMD_READ0, 0x00, page);
> -	chip->cmdfunc(mtd, NAND_CMD_STATUS, -1, -1);
> -	status = chip->read_byte(mtd);
> +	if (chip->exec_op) {
> +		u8 addrs[5] = {};
> +		struct nand_op_instr instrs[] = {
> +			NAND_OP_CMD(NAND_CMD_READ0, 0),
> +			NAND_OP_ADDR(4, addrs, 0),
> +			NAND_OP_CMD(NAND_CMD_STATUS, 0),
> +			NAND_OP_8BIT_DATA_IN(1, &status, 0),
> +		};
> +		struct nand_operation op = NAND_OPERATION(instrs);
> +
> +		if (nand_fill_column_cycles(chip, addrs, 0))

		if (nand_fill_column_cycles(chip, addrs, 0) < 0)

> +			return -EINVAL;
> +
> +		addrs[2] = page;
> +		addrs[3] = page >> 8;
> +		if (chip->options & NAND_ROW_ADDR_3) {
> +			addrs[4] = page >> 16;
> +			instrs[1].addr.naddrs++;
> +		}
> +
> +		nand_exec_op(chip, &op);
> +	} else {
> +		chip->cmdfunc(mtd, NAND_CMD_READ0, 0x00, page);
> +		chip->cmdfunc(mtd, NAND_CMD_STATUS, -1, -1);
> +		status = chip->read_byte(mtd);
> +	}
> +
>  	if (status & NAND_STATUS_FAIL)
>  		mtd->ecc_stats.failed++;
>  	/*
> diff --git a/include/linux/mtd/rawnand.h b/include/linux/mtd/rawnand.h
> index 1acc26ed0e91..302f231df65e 100644
> --- a/include/linux/mtd/rawnand.h
> +++ b/include/linux/mtd/rawnand.h
> @@ -751,6 +751,337 @@ struct nand_manufacturer_ops {
>  };
>  
>  /**
> + * struct nand_op_cmd_instr - Definition of a command instruction
> + * @opcode: the command to assert in one cycle
> + */
> +struct nand_op_cmd_instr {
> +	u8 opcode;
> +};
> +
> +/**
> + * struct nand_op_addr_instr - Definition of an address instruction
> + * @naddrs: length of the @addrs array
> + * @addrs: array containing the address cycles to assert
> + */
> +struct nand_op_addr_instr {
> +	unsigned int naddrs;
> +	const u8 *addrs;
> +};
> +
> +/**
> + * struct nand_op_data_instr - Definition of a data instruction
> + * @len: number of data bytes to move
> + * @in: buffer to fill when reading from the NAND chip
> + * @out: buffer to read from when writing to the NAND chip
> + * @force_8bit: force 8-bit access
> + *
> + * Please note that "in" and "out" are inverted from the ONFI specification
> + * and are from the controller perspective, so a "in" is a read from the NAND
> + * chip while a "out" is a write to the NAND chip.
> + */
> +struct nand_op_data_instr {
> +	unsigned int len;
> +	union {
> +		void *in;
> +		const void *out;
> +	};
> +	bool force_8bit;
> +};
> +
> +/**
> + * struct nand_op_waitrdy_instr - Definition of a wait ready instruction
> + * @timeout_ms: maximum delay while waiting for the ready/busy pin in ms
> + */
> +struct nand_op_waitrdy_instr {
> +	unsigned int timeout_ms;
> +};
> +
> +/**
> + * enum nand_op_instr_type - Enumeration of all the instructions

*of all instruction types

> + *
> + * Please note that data instructions are separated into DATA_IN and DATA_OUT
> + * instructions.
> + */
> +enum nand_op_instr_type {
> +	NAND_OP_CMD_INSTR,
> +	NAND_OP_ADDR_INSTR,
> +	NAND_OP_DATA_IN_INSTR,
> +	NAND_OP_DATA_OUT_INSTR,
> +	NAND_OP_WAITRDY_INSTR,
> +};
> +
> +/**
> + * struct nand_op_instr - Generic definition of and instruction

s/and/an/

> + * @type: an enumeration of the instruction type
> + * @cmd/@addr/@data/@waitrdy: the actual instruction to refer depending on the
> + *			      value of @type

"
extra data associated to the instruction. You'll have to use
the appropriate element depending on @type"

> + * @delay_ns: delay to apply by the controller after the instruction has been
> + *	      actually executed (most of them are directly handled by the
> + *	      controllers once the timings negociation has been done)
> + */
> +struct nand_op_instr {
> +	enum nand_op_instr_type type;
> +	union {
> +		struct nand_op_cmd_instr cmd;
> +		struct nand_op_addr_instr addr;
> +		struct nand_op_data_instr data;
> +		struct nand_op_waitrdy_instr waitrdy;
> +	};
> +	unsigned int delay_ns;
> +};
> +
> +/*
> + * Special handling must be done for the WAITRDY timeout parameter as it usually
> + * is either tPROG (after a prog), tR (before a read), tRST (during a reset) or
> + * tBERS (during an erase) which all of them are u64 values that cannot be
> + * divided by usual kernel macros and must be handled with the special
> + * DIV_ROUND_UP_ULL() macro.
> + */
> +#define PSEC_TO_NSEC(x) DIV_ROUND_UP(x, 10^3)
> +#define PSEC_TO_MSEC(x) DIV_ROUND_UP_ULL(x, 10^9)

Hm, that's a bit of a mess to let each macro decide which converter
they should use, because we don't know at this level whether the timing
is stored in an u64 or u32. How about doing the conversion in the
call-sites instead, because there you should know what you manipulate.

Something like

	NAND_OP_CMD(FOO, PSEC_TO_NSEC_UL(sdr->tXX))
	NAND_OP_WAITRDY(PSEC_TO_MSEC_ULL(sdr->tXX),
			PSEC_TO_NSEC_UL(sdr->tYY))



> +
> +#define NAND_OP_CMD(id, delay_ps)					\
> +	{								\
> +		.type = NAND_OP_CMD_INSTR,				\
> +		.cmd.opcode = id,					\
> +		.delay_ns = PSEC_TO_NSEC(delay_ps),			\
> +	}
> +
> +#define NAND_OP_ADDR(ncycles, cycles, delay_ps)				\
> +	{								\
> +		.type = NAND_OP_ADDR_INSTR,				\
> +		.addr = {						\
> +			.naddrs = ncycles,				\
> +			.addrs = cycles,				\
> +		},							\
> +		.delay_ns = PSEC_TO_NSEC(delay_ps),			\
> +	}
> +
> +#define NAND_OP_DATA_IN(l, buf, delay_ps)				\
> +	{								\
> +		.type = NAND_OP_DATA_IN_INSTR,				\
> +		.data = {						\
> +			.len = l,					\
> +			.in = buf,					\
> +			.force_8bit = false,				\
> +		},							\
> +		.delay_ns = PSEC_TO_NSEC(delay_ps),			\
> +	}
> +
> +#define NAND_OP_DATA_OUT(l, buf, delay_ps)				\
> +	{								\
> +		.type = NAND_OP_DATA_OUT_INSTR,				\
> +		.data = {						\
> +			.len = l,					\
> +			.out = buf,					\
> +			.force_8bit = false,				\
> +		},							\
> +		.delay_ns = PSEC_TO_NSEC(delay_ps),			\
> +	}
> +
> +#define NAND_OP_8BIT_DATA_IN(l, buf, delay_ps)				\
> +	{								\
> +		.type = NAND_OP_DATA_IN_INSTR,				\
> +		.data = {						\
> +			.len = l,					\
> +			.in = buf,					\
> +			.force_8bit = true,				\
> +		},							\
> +		.delay_ns = PSEC_TO_NSEC(delay_ps),			\
> +	}
> +
> +#define NAND_OP_8BIT_DATA_OUT(l, buf, delay_ps)				\
> +	{								\
> +		.type = NAND_OP_DATA_OUT_INSTR,				\
> +		.data = {						\
> +			.len = l,					\
> +			.out = buf,					\
> +			.force_8bit = true,				\
> +		},							\
> +		.delay_ns = PSEC_TO_NSEC(delay_ps),			\
> +	}
> +
> +#define NAND_OP_WAIT_RDY(tout_ps, delay_ps)				\
> +	{								\
> +		.type = NAND_OP_WAITRDY_INSTR,				\
> +		.waitrdy.timeout_ms = PSEC_TO_MSEC(tout_ps),		\
> +		.delay_ns = PSEC_TO_NSEC(delay_ps),			\
> +	}
> +
> +/**
> + * struct nand_subop - a sub operation
> + * @instrs: array of instructions
> + * @ninstrs: length of the @instrs array
> + * @first_instr_start_off: offset to start from for the first instruction
> + *			   of the sub-operation
> + * @last_instr_end_off: offset to end at (excluded) for the last instruction
> + *			of the sub-operation
> + *
> + * When an operation cannot be handled as is by the NAND controller, it will
> + * be split by the parser and the remaining pieces will be handled as
> + * sub-operations.
> + */
> +struct nand_subop {
> +	const struct nand_op_instr *instrs;
> +	unsigned int ninstrs;
> +	/*
> +	 * Specific offset for the first and the last instructions of the subop.
> +	 * Applies for the address cycles in the case of address, or for data
> +	 * offset in the case of data transfers. Otherwise, it is irrelevant.
> +	 */

Can you move that in the kernel header doc?

> +	unsigned int first_instr_start_off;
> +	unsigned int last_instr_end_off;
> +};
> +
> +int nand_subop_get_addr_start_off(const struct nand_subop *subop,
> +				  unsigned int op_id);
> +int nand_subop_get_num_addr_cyc(const struct nand_subop *subop,
> +				unsigned int op_id);
> +int nand_subop_get_data_start_off(const struct nand_subop *subop,
> +				  unsigned int op_id);
> +int nand_subop_get_data_len(const struct nand_subop *subop,
> +			    unsigned int op_id);
> +
> +/**
> + * struct nand_op_parser_addr_constraints - Constraints for address instructions
> + * @maxcycles: maximum number of cycles that the controller can assert by
> + *	       instruction
> + */
> +struct nand_op_parser_addr_constraints {
> +	unsigned int maxcycles;
> +};
> +
> +/**
> + * struct nand_op_parser_data_constraints - Constraints for data instructions
> + * @maxlen: maximum data length that the controller can handle with one
> + *	    instruction
> + */
> +struct nand_op_parser_data_constraints {
> +	unsigned int maxlen;

At some point we should probably add minlen and alignment fields, but
let's keep that for later.

> +};
> +
> +/**
> + * struct nand_op_parser_pattern_elem - One element of a pattern
> + * @type: the instructuction type
> + * @optional: if this element of the pattern is optional or mandatory
> + * @addr/@data: address or data constraint (number of cycles or data length)
> + */
> +struct nand_op_parser_pattern_elem {
> +	enum nand_op_instr_type type;
> +	bool optional;
> +	union {
> +		struct nand_op_parser_addr_constraints addr;
> +		struct nand_op_parser_data_constraints data;
> +	};
> +};
> +
> +#define NAND_OP_PARSER_PAT_CMD_ELEM(_opt)			\
> +	{							\
> +		.type = NAND_OP_CMD_INSTR,			\
> +		.optional = _opt,				\
> +	}
> +
> +#define NAND_OP_PARSER_PAT_ADDR_ELEM(_opt, _maxcycles)		\
> +	{							\
> +		.type = NAND_OP_ADDR_INSTR,			\
> +		.optional = _opt,				\
> +		.addr.maxcycles = _maxcycles,			\
> +	}
> +
> +#define NAND_OP_PARSER_PAT_DATA_IN_ELEM(_opt, _maxlen)		\
> +	{							\
> +		.type = NAND_OP_DATA_IN_INSTR,			\
> +		.optional = _opt,				\
> +		.data.maxlen = _maxlen,				\
> +	}
> +
> +#define NAND_OP_PARSER_PAT_DATA_OUT_ELEM(_opt, _maxlen)		\
> +	{							\
> +		.type = NAND_OP_DATA_OUT_INSTR,			\
> +		.optional = _opt,				\
> +		.data.maxlen = _maxlen,				\
> +	}
> +
> +#define NAND_OP_PARSER_PAT_WAITRDY_ELEM(_opt)			\
> +	{							\
> +		.type = NAND_OP_WAITRDY_INSTR,			\
> +		.optional = _opt,				\
> +	}
> +
> +/**
> + * struct nand_op_parser_pattern - A complete pattern
> + * @elems: array of pattern elements
> + * @nelems: number of pattern elements in @elems array
> + * @exec: the function that will actually execute this pattern, written in the
> + *	  controller driver
> + *
> + * This is a complete pattern that is a list of elements, each one reprensenting
> + * one instruction with its constraints. Controller drivers must declare as much
> + * patterns as they support and give the list of the supported patterns (created
> + * with the help of the following macro) at the call to nand_op_parser_exec_op

s/at the call to nand_op_parser_exec_op/when calling
nand_op_parser_exec_op()/

> + * which shall be the main thing to do in the driver implementation of
> + * ->exec_op().

I'd be more lax than that. Yes the parser is the preferred approach
when you deal with a complex controller. But for simple controllers
it's not.

> Once there is a match between the pattern and an operation, the
> + * core calls the @exec function to actually do the operation.

Not necessarily. The plan is still to ask the controller which
operation it supports before actually executing them. So, in this case
(when check_only param is true), nand_op_parser_exec_op() will never
call ->exec(), it will just make sure the operation can be handled with
the provided patterns.

> + */
> +struct nand_op_parser_pattern {
> +	const struct nand_op_parser_pattern_elem *elems;
> +	unsigned int nelems;
> +	int (*exec)(struct nand_chip *chip, const struct nand_subop *subop);
> +};
> +
> +#define NAND_OP_PARSER_PATTERN(_exec, ...)							\
> +	{											\
> +		.exec = _exec,									\
> +		.elems = (struct nand_op_parser_pattern_elem[]) { __VA_ARGS__ },		\
> +		.nelems = sizeof((struct nand_op_parser_pattern_elem[]) { __VA_ARGS__ }) /	\
> +			  sizeof(struct nand_op_parser_pattern_elem),				\
> +	}
> +
> +/**
> + * struct nand_op_parser - The actual parser
> + * @patterns: array of patterns
> + * @npatterns: length of the @patterns array
> + *
> + * The actual parser structure wich is an array of supported patterns.

It's worth mentioning that patterns will be tested in their declaration
order, and the first match will be taken, so it's important to oder
patterns appropriately so that simple/inefficient patterns are placed
at the end of the list. Usually, this is where you put single
instruction patterns.

> + */
> +struct nand_op_parser {
> +	const struct nand_op_parser_pattern *patterns;
> +	unsigned int npatterns;
> +};
> +
> +#define NAND_OP_PARSER(...)									\
> +	{											\
> +		.patterns = (struct nand_op_parser_pattern[]) { __VA_ARGS__ },			\
> +		.npatterns = sizeof((struct nand_op_parser_pattern[]) { __VA_ARGS__ }) /	\
> +			     sizeof(struct nand_op_parser_pattern),				\
> +	}
> +
> +/**
> + * struct nand_operation - The actual operation
> + * @instrs: array of instructions to execute
> + * @ninstrs: length of the @instrs array
> + *
> + * The actual operation structure that will be given to the parser.

Not only the parser, it will be passed to chip->exep_op() as well.

> + */
> +struct nand_operation {
> +	const struct nand_op_instr *instrs;
> +	unsigned int ninstrs;
> +};
> +
> +#define NAND_OPERATION(_instrs)					\
> +	{							\
> +		.instrs = _instrs,				\
> +		.ninstrs = ARRAY_SIZE(_instrs),			\
> +	}
> +
> +int nand_fill_column_cycles(struct nand_chip *chip, u8 *addrs,
> +			    unsigned int offset_in_page);
> +
> +int nand_op_parser_exec_op(struct nand_chip *chip,
> +			   const struct nand_op_parser *parser,
> +			   const struct nand_operation *op, bool check_only);
> +
> +/**
>   * struct nand_chip - NAND Private Flash Chip Data
>   * @mtd:		MTD device registered to the MTD framework
>   * @IO_ADDR_R:		[BOARDSPECIFIC] address to read the 8 I/O lines of the
> @@ -885,6 +1216,9 @@ struct nand_chip {
>  	int (*setup_data_interface)(struct mtd_info *mtd, int chipnr,
>  				    const struct nand_data_interface *conf);
>  
> +	int (*exec_op)(struct nand_chip *chip,
> +		       const struct nand_operation *op,
> +		       bool check_only);
>  
>  	int chip_delay;
>  	unsigned int options;
> @@ -945,6 +1279,15 @@ struct nand_chip {
>  	} manufacturer;
>  };
>  
> +static inline int nand_exec_op(struct nand_chip *chip,
> +			       const struct nand_operation *op)
> +{
> +	if (!chip->exec_op)
> +		return -ENOTSUPP;
> +
> +	return chip->exec_op(chip, op, false);
> +}
> +
>  extern const struct mtd_ooblayout_ops nand_ooblayout_sp_ops;
>  extern const struct mtd_ooblayout_ops nand_ooblayout_lp_ops;
>  
> @@ -1307,23 +1650,26 @@ int nand_readid_op(struct nand_chip *chip, u8 addr, void *buf,
>  int nand_status_op(struct nand_chip *chip, u8 *status);
>  int nand_erase_op(struct nand_chip *chip, unsigned int eraseblock);
>  int nand_read_page_op(struct nand_chip *chip, unsigned int page,
> -		      unsigned int column, void *buf, unsigned int len);
> -int nand_change_read_column_op(struct nand_chip *chip, unsigned int column,
> -			       void *buf, unsigned int len, bool force_8bit);
> +		      unsigned int offset_in_page, void *buf, unsigned int len);
> +int nand_change_read_column_op(struct nand_chip *chip,
> +			       unsigned int offset_in_page, void *buf,
> +			       unsigned int len, bool force_8bit);
>  int nand_read_oob_op(struct nand_chip *chip, unsigned int page,
> -		     unsigned int column, void *buf, unsigned int len);
> +		     unsigned int offset_in_page, void *buf, unsigned int len);
>  int nand_prog_page_begin_op(struct nand_chip *chip, unsigned int page,
> -			    unsigned int column, const void *buf,
> +			    unsigned int offset_in_page, const void *buf,
>  			    unsigned int len);
>  int nand_prog_page_end_op(struct nand_chip *chip);
>  int nand_prog_page_op(struct nand_chip *chip, unsigned int page,
> -		      unsigned int column, const void *buf, unsigned int len);
> -int nand_change_write_column_op(struct nand_chip *chip, unsigned int column,
> -				const void *buf, unsigned int len, bool force_8bit);
> +		      unsigned int offset_in_page, const void *buf,
> +		      unsigned int len);
> +int nand_change_write_column_op(struct nand_chip *chip,
> +				unsigned int offset_in_page, const void *buf,
> +				unsigned int len, bool force_8bit);
>  int nand_read_data_op(struct nand_chip *chip, void *buf, unsigned int len,
> -		      bool force_8bits);
> +		      bool force_8bit);
>  int nand_write_data_op(struct nand_chip *chip, const void *buf,
> -		       unsigned int len, bool force_8bits);
> +		       unsigned int len, bool force_8bit);
>  
>  /* Free resources held by the NAND device */
>  void nand_cleanup(struct nand_chip *chip);

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [RFC 04/12] mtd: nand: add ->exec_op() implementation
@ 2017-10-18 21:57     ` Boris Brezillon
  0 siblings, 0 replies; 75+ messages in thread
From: Boris Brezillon @ 2017-10-18 21:57 UTC (permalink / raw)
  To: Miquel Raynal
  Cc: Andrew Lunn, bcm-kernel-feedback-list, Brian Norris,
	Catalin Marinas, Chen-Yu Tsai, Cyrille Pitchen, Daniel Mack,
	David Woodhouse, devel, devicetree, Ezequiel Garcia,
	Greg Kroah-Hartman, Gregory Clement, Han Xu, Haojian Zhuang,
	Jason Cooper, Josh Wu, Kamal Dasu, linux-arm-kernel,
	linux-kernel, linux-mediatek, linux-mtd, Marc Gonzalez,
	Marek Vasut, Mark Rutland, Masahiro Yamada, Matthias Brugger,
	Maxime Ripard, Maxim Levitsky, Richard Weinberger,
	Robert Jarzmik, Rob Herring, Russell King, Sebastian Hesselbarth,
	Stefan Agner, Sylvain Lemieux, Vladimir Zapolskiy, Wenyou Yang,
	Will Deacon, Thomas Petazzoni, Antoine Tenart, Igor Grinberg,
	Nadav Haklai, Ofer Heifetz, Neta Zur Hershkovits, Hanna Hawa

On Wed, 18 Oct 2017 16:36:21 +0200
Miquel Raynal <miquel.raynal@free-electrons.com> wrote:

> Introduce the new way to control the NAND controller drivers by
> implementing the ->exec_op() core helpers and allowing new drivers to
> use it instead of relying on ->cmd_ctrl(), ->cmdfunc() and
> ->read/write_byte/word/buf().

"
Introduce a new interface to instruct NAND controllers to send specific
NAND operations. The new interface takes the form of a single method
called ->exec_op(). This method is designed to replace ->cmd_ctrl(),
->cmdfunc() and ->read/write_byte/word/buf() hooks.
"

> 
> The logic is now to send to the controller driver a list of
> instructions.

"
->exec_op() is passed a set of instructions describing the operation to
execute. Each instruction has a type (ADDR, CMD, CYCLE, WAITRDY) and
delay. The type is directly matching the description of NAND commands
in various NAND datasheet and standards (ONFI, JEDEC), the delay is
here to help simple controllers wait enough time between each
instruction. Advanced controllers with integrated timings control can
ignore these delays. 
"

> The driver shall use the parser added by this commit to
> get the matching hook if any.

That's true only for advanced controllers. For those who are executing
each instruction individually (like the gpio-nand driver, or any old
style driver where each ADDR/CMD/DATA cycle is controlled
independently) this is not needed, and who add extra complexity for no
real gain.

> The instructions may be split by the
> parser in order to comply with the controller constraints filled in an
> array of supported patterns.

Given only this description it's hard to tell what this parser is and
why it's needed. I think a real example who be helpful to better
understand what this is.

> 
> Various helpers are also added to ease NAND controller drivers writing.
> 
> This new interface should really ease the support of new vendor specific
> instructions.

Actually, it's more than easing support for vendor specific operations,
it allows the core to check whether the NAND controller will be able to
execute a specific operation or not, which before that was impossible.
It doesn't mean all controllers will magically support all kind of NAND
operations, but at least we'll know when it doesn't.

> 
> Suggested-by: Boris Brezillon <boris.brezillon@free-electrons.com>
> Signed-off-by: Miquel Raynal <miquel.raynal@free-electrons.com>
> ---
>  drivers/mtd/nand/denali.c      |  93 +++-
>  drivers/mtd/nand/nand_base.c   | 949 +++++++++++++++++++++++++++++++++++++++--
>  drivers/mtd/nand/nand_hynix.c  |  91 +++-
>  drivers/mtd/nand/nand_micron.c |  32 +-
>  include/linux/mtd/rawnand.h    | 366 +++++++++++++++-
>  5 files changed, 1448 insertions(+), 83 deletions(-)
> 
> diff --git a/drivers/mtd/nand/denali.c b/drivers/mtd/nand/denali.c
> index e5f38359f6df..8f0f18d9d9cf 100644
> --- a/drivers/mtd/nand/denali.c
> +++ b/drivers/mtd/nand/denali.c
> @@ -652,8 +652,6 @@ static void denali_oob_xfer(struct mtd_info *mtd, struct nand_chip *chip,
>  			    int page, int write)
>  {
>  	struct denali_nand_info *denali = mtd_to_denali(mtd);
> -	unsigned int start_cmd = write ? NAND_CMD_SEQIN : NAND_CMD_READ0;
> -	unsigned int rnd_cmd = write ? NAND_CMD_RNDIN : NAND_CMD_RNDOUT;
>  	int writesize = mtd->writesize;
>  	int oobsize = mtd->oobsize;
>  	uint8_t *bufpoi = chip->oob_poi;
> @@ -665,11 +663,22 @@ static void denali_oob_xfer(struct mtd_info *mtd, struct nand_chip *chip,
>  	int i, pos, len;
>  
>  	/* BBM at the beginning of the OOB area */
> -	chip->cmdfunc(mtd, start_cmd, writesize, page);
> -	if (write)
> -		chip->write_buf(mtd, bufpoi, oob_skip);
> -	else
> -		chip->read_buf(mtd, bufpoi, oob_skip);
> +	if (chip->exec_op) {
> +		if (write)
> +			nand_prog_page_begin_op(chip, page, writesize, bufpoi,
> +						oob_skip);
> +		else
> +			nand_read_page_op(chip, page, writesize, bufpoi,
> +					  oob_skip);
> +	} else {
> +		if (write) {
> +			chip->cmdfunc(mtd, NAND_CMD_SEQIN, writesize, page);
> +			chip->write_buf(mtd, bufpoi, oob_skip);
> +		} else {
> +			chip->cmdfunc(mtd, NAND_CMD_READ0, writesize, page);
> +			chip->read_buf(mtd, bufpoi, oob_skip);
> +		}
> +	}

Why do we have to keep the ->cmdfunc() logic? nand_prog_page_xxx()
already abstracts this for us, right?

>  	bufpoi += oob_skip;
>  
>  	/* OOB ECC */
> @@ -682,30 +691,67 @@ static void denali_oob_xfer(struct mtd_info *mtd, struct nand_chip *chip,
>  		else if (pos + len > writesize)
>  			len = writesize - pos;
>  
> -		chip->cmdfunc(mtd, rnd_cmd, pos, -1);
> -		if (write)
> -			chip->write_buf(mtd, bufpoi, len);
> -		else
> -			chip->read_buf(mtd, bufpoi, len);
> -		bufpoi += len;
> -		if (len < ecc_bytes) {
> -			len = ecc_bytes - len;
> -			chip->cmdfunc(mtd, rnd_cmd, writesize + oob_skip, -1);
> +		if (chip->exec_op) {
>  			if (write)
> -				chip->write_buf(mtd, bufpoi, len);
> +				nand_change_write_column_op(
> +					chip, pos, bufpoi, len, false);

Nitpick, but can you try to align things like that:

				nand_change_write_column_op(chip, pos,
							    bufpoi, len,
							    false);

>  			else
> +				nand_change_read_column_op(
> +					chip, pos, bufpoi, len, false);

Ditto.

> +		} else {
> +			if (write) {
> +				chip->cmdfunc(mtd, NAND_CMD_RNDIN, pos, -1);
> +				chip->write_buf(mtd, bufpoi, len);
> +			} else {
> +				chip->cmdfunc(mtd, NAND_CMD_RNDOUT, pos, -1);
>  				chip->read_buf(mtd, bufpoi, len);
> +			}
> +		}

Smae here: I don't think we need to support both ->cmdfunc() and
nand_change_read/write_column().

> +		bufpoi += len;
> +		if (len < ecc_bytes) {
> +			len = ecc_bytes - len;
> +			if (chip->exec_op) {
> +				if (write)
> +					nand_change_write_column_op(
> +						chip, writesize + oob_skip,
> +						bufpoi, len, false);
> +				else
> +					nand_change_read_column_op(
> +						chip, writesize + oob_skip,
> +						bufpoi, len, false);
> +			} else {
> +				if (write) {
> +					chip->cmdfunc(mtd, NAND_CMD_RNDIN,
> +						      writesize + oob_skip, -1);
> +					chip->write_buf(mtd, bufpoi, len);
> +				} else {
> +					chip->cmdfunc(mtd, NAND_CMD_RNDOUT,
> +						      writesize + oob_skip, -1);
> +					chip->read_buf(mtd, bufpoi, len);
> +				}
> +			.

Ditto.

>  			bufpoi += len;
>  		}
>  	}
>  
>  	/* OOB free */
>  	len = oobsize - (bufpoi - chip->oob_poi);
> -	chip->cmdfunc(mtd, rnd_cmd, size - len, -1);
> -	if (write)
> -		chip->write_buf(mtd, bufpoi, len);
> -	else
> -		chip->read_buf(mtd, bufpoi, len);
> +	if (chip->exec_op) {
> +		if (write)
> +			nand_change_write_column_op(chip, size - len, bufpoi,
> +						    len, false);
> +		else
> +			nand_change_read_column_op(chip, size - len, bufpoi,
> +						   len, false);
> +	} else {
> +		if (write) {
> +			chip->cmdfunc(mtd, NAND_CMD_RNDIN, size - len, -1);
> +			chip->write_buf(mtd, bufpoi, len);
> +		} else {
> +			chip->cmdfunc(mtd, NAND_CMD_RNDOUT, size - len, -1);
> +			chip->read_buf(mtd, bufpoi, len);
> +		}
> +	}
>  }
>  
>  static int denali_read_page_raw(struct mtd_info *mtd, struct nand_chip *chip,
> @@ -803,6 +849,9 @@ static int denali_write_oob(struct mtd_info *mtd, struct nand_chip *chip,
>  
>  	denali_oob_xfer(mtd, chip, page, 1);
>  
> +	if (chip->exec_op)
> +		return nand_prog_page_end_op(chip);
> +
>  	chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1);
>  	status = chip->waitfunc(mtd, chip);

All denali changes in this patch should actually be moved to patch 1.

>  
> diff --git a/drivers/mtd/nand/nand_base.c b/drivers/mtd/nand/nand_base.c
> index bef20e06f0db..737f19bd2f83 100644
> --- a/drivers/mtd/nand/nand_base.c
> +++ b/drivers/mtd/nand/nand_base.c
> @@ -814,7 +814,7 @@ static void nand_ccs_delay(struct nand_chip *chip)
>  	 * Wait tCCS_min if it is correctly defined, otherwise wait 500ns
>  	 * (which should be safe for all NANDs).
>  	 */
> -	if (chip->data_interface.timings.sdr.tCCS_min)
> +	if (&chip->data_interface.timings.sdr.tCCS_min)

This condition is always true. I think this should be replaced by:

	if (chip->setup_data_interface)

And BTW, it should go in patch 3.

>  		ndelay(chip->data_interface.timings.sdr.tCCS_min / 1000);
>  	else
>  		ndelay(500);
> @@ -1233,6 +1233,118 @@ static int nand_init_data_interface(struct nand_chip *chip)
>  }
>  
>  /**
> + * nand_fill_column_cycles - fill the column fields on an address array
> + * @chip: The NAND chip
> + * @addrs: Array of address cycles to fill
> + * @offset_in_page: The offset in the page
> + *
> + * Fills the first or the two first bytes of the @addrs field depending
> + * on the NAND bus width and the page size.
> + */
> +int nand_fill_column_cycles(struct nand_chip *chip, u8 *addrs,
> +			    unsigned int offset_in_page)
> +{
> +	struct mtd_info *mtd = nand_to_mtd(chip);
> +
> +	/*
> +	 * The offset in page is expressed in bytes, if the NAND bus is 16-bit
> +	 * wide, then it must be divided by 2.
> +	 */
> +	if (chip->options & NAND_BUSWIDTH_16) {
> +		if (offset_in_page % 2) {
> +			WARN_ON(true);
> +			return -EINVAL;
> +		}
> +
> +		offset_in_page /= 2;
> +	}
> +
> +	addrs[0] = offset_in_page;
> +
> +	/* Small pages use 1 cycle for the columns, while large page need 2 */
> +	if (mtd->writesize <= 512)
> +		return 1;
> +
> +	addrs[1] = offset_in_page >> 8;
> +
> +	return 2;
> +}
> +EXPORT_SYMBOL_GPL(nand_fill_column_cycles);
> +
> +static int nand_sp_exec_read_page_op(struct nand_chip *chip, unsigned int page,
> +				     unsigned int offset_in_page, void *buf,
> +				     unsigned int len)
> +{
> +	struct mtd_info *mtd = nand_to_mtd(chip);
> +	const struct nand_sdr_timings *sdr =
> +		nand_get_sdr_timings(&chip->data_interface);
> +	u8 addrs[4];
> +	struct nand_op_instr instrs[] = {
> +		NAND_OP_CMD(NAND_CMD_READ0, 0),
> +		NAND_OP_ADDR(3, addrs, sdr->tWB_max),
> +		NAND_OP_WAIT_RDY(sdr->tR_max, sdr->tRR_min),
> +		NAND_OP_DATA_IN(len, buf, 0),
> +	};
> +	struct nand_operation op = NAND_OPERATION(instrs);
> +
> +	/* Drop the DATA_OUT instruction if len is set to 0. */
> +	if (!len)
> +		op.ninstrs--;
> +
> +	if (offset_in_page >= mtd->writesize)
> +		instrs[0].cmd.opcode = NAND_CMD_READOOB;
> +	else if (offset_in_page >= 256)
> +		instrs[0].cmd.opcode = NAND_CMD_READ1;
> +
> +	if (nand_fill_column_cycles(chip, addrs, offset_in_page) < 0)

Why not returning the retcode of nand_fill_column_cycles() directly?

	ret = nand_fill_column_cycles(chip, addrs, offset_in_page)
	if (ret < 0)
		return ret;

> +		return -EINVAL;
> +
> +	addrs[1] = page;
> +	addrs[2] = page >> 8;
> +
> +	if (chip->options & NAND_ROW_ADDR_3) {
> +		addrs[3] = page >> 16;
> +		instrs[1].addr.naddrs++;
> +	}
> +
> +	return nand_exec_op(chip, &op);
> +}
> +
> +static int nand_lp_exec_read_page_op(struct nand_chip *chip, unsigned int page,
> +				     unsigned int offset_in_page, void *buf,
> +				     unsigned int len)
> +{
> +	const struct nand_sdr_timings *sdr =
> +		nand_get_sdr_timings(&chip->data_interface);
> +	u8 addrs[5];
> +	struct nand_op_instr instrs[] = {
> +		NAND_OP_CMD(NAND_CMD_READ0, 0),
> +		NAND_OP_ADDR(4, addrs, 0),
> +		NAND_OP_CMD(NAND_CMD_READSTART, sdr->tWB_max),
> +		NAND_OP_WAIT_RDY(sdr->tR_max, sdr->tRR_min),
> +		NAND_OP_DATA_IN(len, buf, 0),
> +	};
> +	struct nand_operation op = NAND_OPERATION(instrs);
> +
> +	/* Drop the DATA_IN instruction if len is set to 0. */
> +	if (!len)
> +		op.ninstrs--;
> +
> +	if (nand_fill_column_cycles(chip, addrs, offset_in_page))

Should be

	if (nand_fill_column_cycles(chip, addrs, offset_in_page) < 0)

> +		return -EINVAL;
> +
> +	addrs[2] = page;
> +	addrs[3] = page >> 8;
> +
> +	if (chip->options & NAND_ROW_ADDR_3) {
> +		addrs[4] = page >> 16;
> +		instrs[1].addr.naddrs++;
> +	}
> +
> +	return nand_exec_op(chip, &op);
> +}
> +
> +/**
>   * nand_read_page_op - Do a READ PAGE operation
>   * @chip: The NAND chip
>   * @page: page to read
> @@ -1246,17 +1358,26 @@ static int nand_init_data_interface(struct nand_chip *chip)
>   * Returns 0 for success or negative error code otherwise
>   */
>  int nand_read_page_op(struct nand_chip *chip, unsigned int page,
> -		      unsigned int column, void *buf, unsigned int len)
> +		      unsigned int offset_in_page, void *buf, unsigned int len)

You should change the parameter name in patch 1 since this function is
introduced there.

>  {
>  	struct mtd_info *mtd = nand_to_mtd(chip);
>  
>  	if (len && !buf)
>  		return -EINVAL;
>  
> -	if (column + len > mtd->writesize + mtd->oobsize)
> +	if (offset_in_page + len > mtd->writesize + mtd->oobsize)
>  		return -EINVAL;
>  
> -	chip->cmdfunc(mtd, NAND_CMD_READ0, column, page);
> +	if (chip->exec_op) {
> +		if (mtd->writesize > 512)
> +			return nand_lp_exec_read_page_op(
> +				chip, page, offset_in_page, buf, len);
> +
> +		return nand_sp_exec_read_page_op(chip, page, offset_in_page,
> +						 buf, len);
> +	}
> +
> +	chip->cmdfunc(mtd, NAND_CMD_READ0, offset_in_page, page);
>  	if (len)
>  		chip->read_buf(mtd, buf, len);
>  
> @@ -1286,6 +1407,25 @@ static int nand_read_param_page_op(struct nand_chip *chip, u8 page, void *buf,
>  	if (len && !buf)
>  		return -EINVAL;
>  
> +	if (chip->exec_op) {
> +		const struct nand_sdr_timings *sdr =
> +			nand_get_sdr_timings(&chip->data_interface);
> +		struct nand_op_instr instrs[] = {
> +			NAND_OP_CMD(NAND_CMD_PARAM, 0),
> +			NAND_OP_ADDR(1, &page, sdr->tWB_max),
> +			NAND_OP_WAIT_RDY(sdr->tR_max, sdr->tRR_min),
> +			NAND_OP_8BIT_DATA_IN(len, buf, 0),
> +		};
> +		struct nand_operation op =
> +			NAND_OPERATION(instrs);
> +
> +		/* Drop the DATA_IN instruction if len is set to 0. */
> +		if (!len)
> +			op.ninstrs--;
> +
> +		return nand_exec_op(chip, &op);
> +	}
> +
>  	chip->cmdfunc(mtd, NAND_CMD_PARAM, page, -1);
>  	for (i = 0; i < len; i++)
>  		p[i] = chip->read_byte(mtd);

[...]

> +
>  /**
>   * nand_prog_page_begin_op - starts a PROG PAGE operation
>   * @chip: The NAND chip
> @@ -1371,7 +1608,7 @@ EXPORT_SYMBOL_GPL(nand_read_oob_op);
>   * Returns 0 for success or negative error code otherwise
>   */
>  int nand_prog_page_begin_op(struct nand_chip *chip, unsigned int page,
> -			    unsigned int column, const void *buf,
> +			    unsigned int offset_in_page, const void *buf,
>  			    unsigned int len)
>  {
>  	struct mtd_info *mtd = nand_to_mtd(chip);
> @@ -1379,10 +1616,14 @@ int nand_prog_page_begin_op(struct nand_chip *chip, unsigned int page,
>  	if (len && !buf)
>  		return -EINVAL;
>  
> -	if (column + len > mtd->writesize + mtd->oobsize)
> +	if (offset_in_page + len > mtd->writesize + mtd->oobsize)
>  		return -EINVAL;
>  
> -	chip->cmdfunc(mtd, NAND_CMD_SEQIN, column, page);
> +	if (chip->exec_op)
> +		return nand_exec_prog_page_op(
> +			chip, page, offset_in_page, buf, len, false);

Param alignment please.

> +
> +	chip->cmdfunc(mtd, NAND_CMD_SEQIN, offset_in_page, page);
>  
>  	if (buf)
>  		chip->write_buf(mtd, buf, len);
> @@ -1405,6 +1646,19 @@ int nand_prog_page_end_op(struct nand_chip *chip)
>  	struct mtd_info *mtd = nand_to_mtd(chip);
>  	int status;
>  
> +	if (chip->exec_op) {
> +		const struct nand_sdr_timings *sdr =
> +			nand_get_sdr_timings(&chip->data_interface);
> +		struct nand_op_instr instrs[] = {
> +			NAND_OP_CMD(NAND_CMD_PAGEPROG, sdr->tWB_max),
> +			NAND_OP_WAIT_RDY(sdr->tPROG_max, 0),
> +		};
> +		struct nand_operation op =
> +			NAND_OPERATION(instrs);
> +
> +		return nand_exec_op(chip, &op);
> +	}
> +
>  	chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1);
>  
>  	status = chip->waitfunc(mtd, chip);
> @@ -1429,7 +1683,8 @@ EXPORT_SYMBOL_GPL(nand_prog_page_end_op);
>   * Returns 0 for success or negative error code otherwise
>   */
>  int nand_prog_page_op(struct nand_chip *chip, unsigned int page,
> -		      unsigned int column, const void *buf, unsigned int len)
> +		      unsigned int offset_in_page, const void *buf,
> +		      unsigned int len)
>  {
>  	struct mtd_info *mtd = nand_to_mtd(chip);
>  	int status;
> @@ -1437,10 +1692,14 @@ int nand_prog_page_op(struct nand_chip *chip, unsigned int page,
>  	if (!len || !buf)
>  		return -EINVAL;
>  
> -	if (column + len > mtd->writesize + mtd->oobsize)
> +	if (offset_in_page + len > mtd->writesize + mtd->oobsize)
>  		return -EINVAL;
>  
> -	chip->cmdfunc(mtd, NAND_CMD_SEQIN, column, page);
> +	if (chip->exec_op)
> +		return nand_exec_prog_page_op(
> +			chip, page, offset_in_page, buf, len, true);
> +
> +	chip->cmdfunc(mtd, NAND_CMD_SEQIN, offset_in_page, page);
>  	chip->write_buf(mtd, buf, len);
>  	chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1);
>  
> @@ -1465,7 +1724,8 @@ EXPORT_SYMBOL_GPL(nand_prog_page_op);
>   *
>   * Returns 0 for success or negative error code otherwise
>   */
> -int nand_change_write_column_op(struct nand_chip *chip, unsigned int column,
> +int nand_change_write_column_op(struct nand_chip *chip,
> +				unsigned int offset_in_page,
>  				const void *buf, unsigned int len,
>  				bool force_8bit)
>  {
> @@ -1474,10 +1734,38 @@ int nand_change_write_column_op(struct nand_chip *chip, unsigned int column,
>  	if (len && !buf)
>  		return -EINVAL;
>  
> -	if (column + len > mtd->writesize + mtd->oobsize)
> +	if (offset_in_page + len > mtd->writesize + mtd->oobsize)
>  		return -EINVAL;
>  
> -	chip->cmdfunc(mtd, NAND_CMD_RNDIN, column, -1);
> +	/* Small page NANDs do not support column change. */
> +	if (mtd->writesize <= 512)
> +		return -ENOTSUPP;
> +
> +	if (chip->exec_op) {
> +		const struct nand_sdr_timings *sdr =
> +			nand_get_sdr_timings(&chip->data_interface);
> +		u8 addrs[2];
> +		struct nand_op_instr instrs[] = {
> +			NAND_OP_CMD(NAND_CMD_RNDIN, 0),
> +			NAND_OP_ADDR(2, addrs, sdr->tCCS_min),
> +			NAND_OP_DATA_OUT(len, buf, 0),
> +		};
> +		struct nand_operation op =
> +			NAND_OPERATION(instrs);
> +
> +		if (nand_fill_column_cycles(chip, addrs, offset_in_page) < 0)
> +			return -EINVAL;
> +
> +		instrs[2].data.force_8bit = force_8bit;
> +
> +		/* Drop the DATA_OUT instruction if len is set to 0. */
> +		if (!len)
> +			op.ninstrs--;
> +
> +		return nand_exec_op(chip, &op);
> +	}
> +
> +	chip->cmdfunc(mtd, NAND_CMD_RNDIN, offset_in_page, -1);
>  	if (len)
>  		chip->write_buf(mtd, buf, len);
>  
> @@ -1508,6 +1796,24 @@ int nand_readid_op(struct nand_chip *chip, u8 addr,
>  	if (!len || !buf)
>  		return -EINVAL;
>  
> +	if (chip->exec_op) {
> +		const struct nand_sdr_timings *sdr =
> +			nand_get_sdr_timings(&chip->data_interface);
> +		struct nand_op_instr instrs[] = {
> +			NAND_OP_CMD(NAND_CMD_READID, 0),
> +			NAND_OP_ADDR(1, &addr, sdr->tADL_min),
> +			NAND_OP_8BIT_DATA_IN(len, buf, 0),
> +		};
> +		struct nand_operation op =
> +			NAND_OPERATION(instrs);
> +
> +		/* Drop the DATA_IN instruction if len is set to 0. */
> +		if (!len)
> +			op.ninstrs--;
> +
> +		return nand_exec_op(chip, &op);
> +	}
> +
>  	chip->cmdfunc(mtd, NAND_CMD_READID, addr, -1);
>  
>  	for (i = 0; i < len; i++)
> @@ -1532,6 +1838,22 @@ int nand_status_op(struct nand_chip *chip, u8 *status)
>  {
>  	struct mtd_info *mtd = nand_to_mtd(chip);
>  
> +	if (chip->exec_op) {
> +		const struct nand_sdr_timings *sdr =
> +			nand_get_sdr_timings(&chip->data_interface);
> +		struct nand_op_instr instrs[] = {
> +			NAND_OP_CMD(NAND_CMD_STATUS, sdr->tADL_min),
> +			NAND_OP_8BIT_DATA_IN(1, status, 0),
> +		};
> +		struct nand_operation op =
> +			NAND_OPERATION(instrs);
> +
> +		if (!status)
> +			op.ninstrs--;
> +
> +		return nand_exec_op(chip, &op);
> +	}
> +
>  	chip->cmdfunc(mtd, NAND_CMD_STATUS, -1, -1);
>  	if (status)
>  		*status = chip->read_byte(mtd);
> @@ -1558,6 +1880,25 @@ int nand_erase_op(struct nand_chip *chip, unsigned int eraseblock)
>  			    (chip->phys_erase_shift - chip->page_shift);
>  	int status;
>  
> +	if (chip->exec_op) {
> +		const struct nand_sdr_timings *sdr =
> +			nand_get_sdr_timings(&chip->data_interface);
> +		u8 addrs[3] = {	page, page >> 8, page >> 16 };
> +		struct nand_op_instr instrs[] = {
> +			NAND_OP_CMD(NAND_CMD_ERASE1, 0),
> +			NAND_OP_ADDR(2, addrs, 0),
> +			NAND_OP_CMD(NAND_CMD_ERASE2, sdr->tWB_max),
> +			NAND_OP_WAIT_RDY(sdr->tBERS_max, 0),
> +		};
> +		struct nand_operation op =
> +			NAND_OPERATION(instrs);
> +
> +		if (chip->options & NAND_ROW_ADDR_3)
> +			instrs[1].addr.naddrs++;
> +
> +		return nand_exec_op(chip, &op);
> +	}
> +
>  	chip->cmdfunc(mtd, NAND_CMD_ERASE1, -1, page);
>  	chip->cmdfunc(mtd, NAND_CMD_ERASE2, -1, -1);
>  
> @@ -1591,6 +1932,22 @@ static int nand_set_features_op(struct nand_chip *chip, u8 feature,
>  	const u8 *params = data;
>  	int i, status;
>  
> +	if (chip->exec_op) {
> +		const struct nand_sdr_timings *sdr =
> +			nand_get_sdr_timings(&chip->data_interface);
> +		struct nand_op_instr instrs[] = {
> +			NAND_OP_CMD(NAND_CMD_SET_FEATURES, 0),
> +			NAND_OP_ADDR(1, &feature, sdr->tADL_min),
> +			NAND_OP_8BIT_DATA_OUT(ONFI_SUBFEATURE_PARAM_LEN, data,
> +					      sdr->tWB_max),
> +			NAND_OP_WAIT_RDY(sdr->tFEAT_max, 0),
> +		};
> +		struct nand_operation op =
> +			NAND_OPERATION(instrs);
> +
> +		return nand_exec_op(chip, &op);
> +	}
> +
>  	chip->cmdfunc(mtd, NAND_CMD_SET_FEATURES, feature, -1);
>  	for (i = 0; i < ONFI_SUBFEATURE_PARAM_LEN; ++i)
>  		chip->write_byte(mtd, params[i]);
> @@ -1621,6 +1978,22 @@ static int nand_get_features_op(struct nand_chip *chip, u8 feature,
>  	u8 *params = data;
>  	int i;
>  
> +	if (chip->exec_op) {
> +		const struct nand_sdr_timings *sdr =
> +			nand_get_sdr_timings(&chip->data_interface);
> +		struct nand_op_instr instrs[] = {
> +			NAND_OP_CMD(NAND_CMD_GET_FEATURES, 0),
> +			NAND_OP_ADDR(1, &feature, sdr->tWB_max),
> +			NAND_OP_WAIT_RDY(sdr->tFEAT_max, sdr->tRR_min),
> +			NAND_OP_8BIT_DATA_IN(ONFI_SUBFEATURE_PARAM_LEN,
> +					     data, 0),
> +		};
> +		struct nand_operation op =
> +			NAND_OPERATION(instrs);
> +
> +		return nand_exec_op(chip, &op);
> +	}
> +
>  	chip->cmdfunc(mtd, NAND_CMD_GET_FEATURES, feature, -1);
>  	for (i = 0; i < ONFI_SUBFEATURE_PARAM_LEN; ++i)
>  		params[i] = chip->read_byte(mtd);
> @@ -1642,6 +2015,19 @@ int nand_reset_op(struct nand_chip *chip)
>  {
>  	struct mtd_info *mtd = nand_to_mtd(chip);
>  
> +	if (chip->exec_op) {
> +		const struct nand_sdr_timings *sdr =
> +			nand_get_sdr_timings(&chip->data_interface);
> +		struct nand_op_instr instrs[] = {
> +			NAND_OP_CMD(NAND_CMD_RESET, sdr->tWB_max),
> +			NAND_OP_WAIT_RDY(sdr->tRST_max, 0),
> +		};
> +		struct nand_operation op =
> +			NAND_OPERATION(instrs);
> +
> +		return nand_exec_op(chip, &op);
> +	}
> +
>  	chip->cmdfunc(mtd, NAND_CMD_RESET, -1, -1);
>  
>  	return 0;
> @@ -1669,6 +2055,18 @@ int nand_read_data_op(struct nand_chip *chip, void *buf, unsigned int len,
>  	if (!len || !buf)
>  		return -EINVAL;
>  
> +	if (chip->exec_op) {
> +		struct nand_op_instr instrs[] = {
> +			NAND_OP_DATA_IN(len, buf, 0),
> +		};
> +		struct nand_operation op =
> +			NAND_OPERATION(instrs);
> +
> +		instrs[0].data.force_8bit = force_8bit;
> +
> +		return nand_exec_op(chip, &op);
> +	}
> +
>  	if (force_8bit) {
>  		u8 *p = buf;
>  		unsigned int i;
> @@ -1704,6 +2102,18 @@ int nand_write_data_op(struct nand_chip *chip, const void *buf,
>  	if (!len || !buf)
>  		return -EINVAL;
>  
> +	if (chip->exec_op) {
> +		struct nand_op_instr instrs[] = {
> +			NAND_OP_DATA_OUT(len, buf, 0),
> +		};
> +		struct nand_operation op =
> +			NAND_OPERATION(instrs);
> +
> +		instrs[0].data.force_8bit = force_8bit;
> +
> +		return nand_exec_op(chip, &op);
> +	}
> +
>  	if (force_8bit) {
>  		const u8 *p = buf;
>  		unsigned int i;
> @@ -1719,6 +2129,471 @@ int nand_write_data_op(struct nand_chip *chip, const void *buf,
>  EXPORT_SYMBOL_GPL(nand_write_data_op);
>  
>  /**
> + * struct nand_op_parser_ctx - Context used by the parser
> + * @instrs: array of all the instructions that must be addressed
> + * @ninstrs: length of the @instrs array
> + * @instr_idx: index of the instruction in the @instrs array that matches the
> + *	       first instruction of the subop structure
> + * @instr_start_off: offset at which the first instruction of the subop
> + *		     structure must start if it is and address or a data
> + *		     instruction
> + *
> + * This structure is used by the core to handle splitting lengthy instructions
> + * into sub-operations.
> + */
> +struct nand_op_parser_ctx {
> +	const struct nand_op_instr *instrs;
> +	unsigned int ninstrs;
> +	unsigned int instr_idx;
> +	unsigned int instr_start_off;
> +	struct nand_subop subop;
> +};
> +
> +/**
> + * nand_op_parser_must_split_instr - Checks if an instruction must be split
> + * @pat: the parser pattern that match
> + * @instr: the instruction array to check
> + * @start_offset: the offset from which to start in the first instruction of the
> + *		  @instr array
> + *
> + * Some NAND controllers are limited and cannot send X address cycles with a
> + * unique operation, or cannot read/write more than Y bytes at the same time.
> + * In this case, reduce the scope of this set of operation, and trigger another
> + * instruction with the rest.

"
In this case, split the instruction that does not fit in a single
controller-operation into two or more chunks.
"

> + *
> + * Returns true if the array of instruction must be split, false otherwise.

s/array of//

> + * The @start_offset parameter is also updated to the offset at which the first
> + * instruction of the next bundle of instruction must start (if an address or a

"
The @start_offset parameter is also updated to the offset at which the
the next bundle of instructions must start ...
"

> + * data instruction).
> + */
> +static bool
> +nand_op_parser_must_split_instr(const struct nand_op_parser_pattern_elem *pat,
> +				const struct nand_op_instr *instr,
> +				unsigned int *start_offset)
> +{
> +	switch (pat->type) {
> +	case NAND_OP_ADDR_INSTR:
> +		if (!pat->addr.maxcycles)
> +			break;
> +
> +		if (instr->addr.naddrs - *start_offset > pat->addr.maxcycles) {
> +			*start_offset += pat->addr.maxcycles;
> +			return true;
> +		}
> +		break;
> +
> +	case NAND_OP_DATA_IN_INSTR:
> +	case NAND_OP_DATA_OUT_INSTR:
> +		if (!pat->data.maxlen)
> +			break;
> +
> +		if (instr->data.len - *start_offset > pat->data.maxlen) {
> +			*start_offset += pat->data.maxlen;
> +			return true;
> +		}
> +		break;
> +
> +	default:
> +		break;
> +	}
> +
> +	return false;
> +}
> +
> +/**
> + * nand_op_parser_match_pat - Checks a pattern
> + * @pat: the parser pattern to check if it matches
> + * @ctx: the context structure to match with the pattern @pat
> + *
> + * Check if *one* given pattern matches the given sequence of instructions
> + */
> +static bool
> +nand_op_parser_match_pat(const struct nand_op_parser_pattern *pat,
> +			 struct nand_op_parser_ctx *ctx)
> +{
> +	unsigned int i, j, boundary_off = ctx->instr_start_off;
> +
> +	ctx->subop.ninstrs = 0;
> +
> +	for (i = ctx->instr_idx, j = 0; i < ctx->ninstrs && j < pat->nelems;) {
> +		const struct nand_op_instr *instr = &ctx->instrs[i];
> +
> +		/*
> +		 * The pattern instruction does not match the operation
> +		 * instruction. If the instruction is marked optional in the
> +		 * pattern definition, we skip the pattern element and continue
> +		 * to the next one. If the element is mandatory, there's no
> +		 * match and we can return false directly.
> +		 */
> +		if (instr->type != pat->elems[j].type) {
> +			if (!pat->elems[j].optional)
> +				return false;
> +
> +			j++;
> +			continue;
> +		}
> +
> +		/*
> +		 * Now check the pattern element constraints. If the pattern is
> +		 * not able to handle the whole instruction in a single step,
> +		 * we'll have to break it down into several instructions.
> +		 * The *boudary_off value comes back updated to point to the
> +		 * limit between the split instruction (the end of the original
> +		 * chunk, the start of new next one).
> +		 */
> +		if (nand_op_parser_must_split_instr(&pat->elems[j], instr,
> +						    &boundary_off)) {
> +			ctx->subop.ninstrs++;
> +			j++;
> +			break;
> +		}
> +
> +		ctx->subop.ninstrs++;
> +		i++;
> +		j++;
> +		boundary_off = 0;
> +	}
> +
> +	/*
> +	 * This can happen if all instructions of a pattern are optional.
> +	 * Still, if there's not at least one instruction handled by this
> +	 * pattern, this is not a match, and we should try the next one (if
> +	 * any).
> +	 */
> +	if (!ctx->subop.ninstrs)
> +		return false;
> +
> +	/*
> +	 * We had a match on the pattern head, but the pattern may be longer
> +	 * than the instructions we're asked to execute. We need to make sure
> +	 * there's no mandatory elements in the pattern tail.
> +	 *
> +	 * The case where all the operations of a pattern have been checked but
> +	 * the number of instructions is bigger is handled right after this by
> +	 * returning true on the pattern match, which will order the execution
> +	 * of the subset of instructions later defined, while updating the
> +	 * context ids to the next chunk of instructions.
> +	 */
> +	for (; j < pat->nelems; j++) {
> +		if (!pat->elems[j].optional)
> +			return false;
> +	}
> +
> +	/*
> +	 * We have a match: update the ctx and return true. The subop structure
> +	 * will be used by the pattern's ->exec() function.
> +	 */
> +	ctx->subop.instrs = &ctx->instrs[ctx->instr_idx];
> +	ctx->subop.first_instr_start_off = ctx->instr_start_off;
> +	ctx->subop.last_instr_end_off = boundary_off;
> +
> +	/*
> +	 * Update the pointers so the calling function will be able to recall
> +	 * this one with a new subset of instructions.
> +	 *
> +	 * In the case where the last operation of this set is split, point to
> +	 * the last unfinished job, knowing the starting offset.
> +	 */
> +	ctx->instr_idx = i;
> +	ctx->instr_start_off = boundary_off;
> +
> +	return true;
> +}
> +
> +#if IS_ENABLED(CONFIG_DYNAMIC_DEBUG) || defined(DEBUG)
> +static void nand_op_parser_trace(const struct nand_op_parser_ctx *ctx,
> +				 bool success)
> +{
> +	const struct nand_op_instr *instr;
> +	bool in_subop = false;
> +	char *is_in =  "    ->", *is_out = "      ", *prefix;
> +	char *buf;
> +	unsigned int len, off = 0;
> +	int i, j;
> +
> +	if (success)
> +		pr_debug("executing subop:\n");
> +	else
> +		pr_debug("pattern not found:\n");
> +
> +	for (i = 0; i < ctx->ninstrs; i++) {
> +		instr = &ctx->instrs[i];
> +
> +		/*
> +		 * ctx->instr_idx is not reliable because it may already had

									 have

> +		 * been updated by the parser. Use pointers comparison instead.
> +		 */
> +		if (instr == &ctx->subop.instrs[0])
> +			in_subop = true;

It seems in_subop is only used to select the prefix, so you can just
get rid of it and do

		if (instr == &ctx->subop.instrs[0])
			prefix = "    ->";

BTW, if success is false, you should not even try to change the prefix,
right?

> +
> +		if (in_subop)
> +			prefix = is_in;
> +		else
> +			prefix = is_out;
> +
> +		switch (instr->type) {
> +		case NAND_OP_CMD_INSTR:
> +			pr_debug("%sCMD      [0x%02x]\n", prefix,
> +				 instr->cmd.opcode);
> +			break;
> +		case NAND_OP_ADDR_INSTR:
> +			/*
> +			 * A log line is much less than 50 bytes, plus 5 bytes
> +			 * per address cycle to display.
> +			 */
> +			len = 50 + 5 * instr->addr.naddrs;
> +			buf = kmalloc(len, GFP_KERNEL);
> +			if (!buf)
> +				return;
> +			memset(buf, 0, len);
> +			off += snprintf(buf, len, "ADDR     [%d cyc:",
> +					instr->addr.naddrs);
> +			for (j = 0; j < instr->addr.naddrs; j++)
> +				off += snprintf(&buf[off], len - off, " 0x%02x",
> +						instr->addr.addrs[j]);
> +			pr_debug("%s%s]\n", prefix, buf);
> +			break;
> +		case NAND_OP_DATA_IN_INSTR:
> +			pr_debug("%sDATA_IN  [%d B%s]\n", prefix,
> +				 instr->data.len,
> +				 instr->data.force_8bit ? ", force 8-bit" : "");
> +			break;
> +		case NAND_OP_DATA_OUT_INSTR:
> +			pr_debug("%sDATA_OUT [%d B%s]\n", prefix,
> +				 instr->data.len,
> +				 instr->data.force_8bit ? ", force 8-bit" : "");
> +			break;
> +		case NAND_OP_WAITRDY_INSTR:
> +			pr_debug("%sWAITRDY  [max %d ms]\n", prefix,
> +				 instr->waitrdy.timeout_ms);
> +			break;
> +		}
> +
> +		if (instr == &ctx->subop.instrs[ctx->subop.ninstrs - 1])
> +			in_subop = false;

		if (instr == &ctx->subop.instrs[ctx->subop.ninstrs - 1])
			prefix = "      ";

and initialize prefix to "      " at the beginning of the function.

> +	}
> +}
> +#else
> +static void nand_op_parser_trace(const struct nand_op_parser_ctx *ctx,
> +				 bool success)
> +{
> +	/* NOP */
> +}
> +#endif
> +
> +/**
> + * nand_op_parser_exec_op - exec_op parser
> + * @chip: the NAND chip
> + * @parser: the parser to use given by the controller driver
> + * @op: the NAND operation to address
> + * @check_only: flag asking if the entire operation could be handled
> + *
> + * Function that must be called by each driver that implement the "exec_op API"
> + * in their own ->exec_op() implementation.
> + *
> + * The function iterates on all the instructions asked and make use of internal
> + * parsers to find matches between the instruction list and the handled patterns
> + * filled by the controller drivers inside the @parser structure. If needed, the
> + * instructions could be split into sub-operations and be executed sequentially.
> + */
> +int nand_op_parser_exec_op(struct nand_chip *chip,
> +			   const struct nand_op_parser *parser,
> +			   const struct nand_operation *op, bool check_only)
> +{
> +	struct nand_op_parser_ctx ctx = {
> +		.instrs = op->instrs,
> +		.ninstrs = op->ninstrs,
> +	};
> +	unsigned int i;
> +
> +	while (ctx.instr_idx < op->ninstrs) {
> +		bool pattern_found = false;
> +		int ret;
> +
> +		for (i = 0; i < parser->npatterns; i++) {
> +			const struct nand_op_parser_pattern *pattern;
> +
> +			pattern = &parser->patterns[i];
> +			if (!nand_op_parser_match_pat(pattern, &ctx))
> +				continue;
> +
> +			nand_op_parser_trace(&ctx, true);
> +

pattern_found should be set to true here. But I'm not even sure you
really need this variable.

> +			if (check_only)
> +				break;
> +
> +			ret = pattern->exec(chip, &ctx.subop);
> +			if (ret)
> +				return ret;
> +
> +			pattern_found = true;
> +		}
> +
> +		if (!pattern_found) {

Just test

		if (i == parser->npatterns) {

and you should be good.

> +			nand_op_parser_trace(&ctx, false);
> +			return -ENOTSUPP;
> +		}
> +	}
> +
> +	return 0;
> +}
> +EXPORT_SYMBOL_GPL(nand_op_parser_exec_op);
> +
> +static bool nand_instr_is_data(const struct nand_op_instr *instr)
> +{
> +	return instr && (instr->type == NAND_OP_DATA_IN_INSTR ||
> +			 instr->type == NAND_OP_DATA_OUT_INSTR);
> +}
> +
> +static bool nand_subop_instr_is_valid(const struct nand_subop *subop,
> +				      unsigned int op_id)

s/op_id/instr_idx/

> +{
> +	return subop && op_id < subop->ninstrs;
> +}
> +
> +static int nand_subop_get_start_off(const struct nand_subop *subop,
> +				    unsigned int op_id)

Ditto.

> +{
> +	if (op_id)
> +		return 0;
> +
> +	return subop->first_instr_start_off;
> +}
> +
> +/**
> + * nand_subop_get_addr_start_off - Get the start offset in an address array
> + * @subop: The entire sub-operation
> + * @op_id: Index of the instruction inside the sub-operation

instr_idx.

> + *
> + * Instructions arrays may be split by the parser between instructions,
> + * and also in the middle of an address instruction if the number of cycles
> + * to assert in one operation is not supported by the controller.
> + *
> + * For this, instead of using the first index of the ->addr.addrs field from the
> + * address instruction, the NAND controller driver must use this helper that
> + * will either return 0 if the index does not point to the first instruction of
> + * the sub-operation, or the offset of the next starting offset inside the
> + * address cycles.

I think you can drop this paragraph which IMO brings more confusion to
what this function does.

> + *
> + * Returns the offset of the first address cycle to assert from the pointed
> + * address instruction.

This part is definitely clearer.

> + */
> +int nand_subop_get_addr_start_off(const struct nand_subop *subop,
> +				  unsigned int op_id)
> +{
> +	if (!nand_subop_instr_is_valid(subop, op_id) ||
> +	    subop->instrs[op_id].type != NAND_OP_ADDR_INSTR)
> +		return -EINVAL;
> +
> +	return nand_subop_get_start_off(subop, op_id);
> +}
> +EXPORT_SYMBOL_GPL(nand_subop_get_addr_start_off);
> +
> +/**
> + * nand_subop_get_num_addr_cyc - Get the remaining address cycles to assert
> + * @subop: The entire sub-operation
> + * @op_id: Index of the instruction inside the sub-operation

instr_idx

> + *
> + * Instructions arrays may be split by the parser between instructions,
> + * and also in the middle of an address instruction if the number of cycles
> + * to assert in one operation is not supported by the controller.
> + *
> + * For this, instead of using the ->addr.naddrs fields of the address
> + * instruction, the NAND controller driver must use this helper that will
> + * return the actual number of cycles to assert between the first and last
> + * offset asked for this particular instruction.
> + *
> + * Returns the number of address cycles to assert from the pointed address
> + * instruction.
> + */
> +int nand_subop_get_num_addr_cyc(const struct nand_subop *subop,
> +				unsigned int op_id)
> +{
> +	int start_off, end_off;
> +
> +	if (!nand_subop_instr_is_valid(subop, op_id) ||
> +	    subop->instrs[op_id].type != NAND_OP_ADDR_INSTR)
> +		return -EINVAL;
> +
> +	start_off = nand_subop_get_addr_start_off(subop, op_id);
> +
> +	if (op_id == subop->ninstrs - 1 &&
> +	    subop->last_instr_end_off)
> +		end_off = subop->last_instr_end_off;
> +	else
> +		end_off = subop->instrs[op_id].addr.naddrs;
> +
> +	return end_off - start_off;
> +}
> +EXPORT_SYMBOL_GPL(nand_subop_get_num_addr_cyc);
> +
> +/**
> + * nand_subop_get_data_start_off - Get the start offset in a data array
> + * @subop: The entire sub-operation
> + * @op_id: Index of the instruction inside the sub-operation
> + *
> + * Instructions arrays may be split by the parser between instructions,
> + * and also in the middle of a data instruction if the number of bytes to access
> + * in one operation is greater that the controller limit.
> + *
> + * For this, instead of using the first index of the ->data.buf field from the
> + * data instruction, the NAND controller driver must use this helper that
> + * will either return 0 if the index does not point to the first instruction of
> + * the sub-operation, or the offset of the next starting offset inside the
> + * data buffer.

Same as for nand_subop_get_addr_start_off(), the explanation is
confusing. I think we can drop it.

> + *
> + * Returns the data offset inside the pointed data instruction buffer from which
> + * to start.
> + */
> +int nand_subop_get_data_start_off(const struct nand_subop *subop,
> +				  unsigned int op_id)
> +{
> +	if (!nand_subop_instr_is_valid(subop, op_id) ||
> +	    !nand_instr_is_data(&subop->instrs[op_id]))
> +		return -EINVAL;
> +
> +	return nand_subop_get_start_off(subop, op_id);
> +}
> +EXPORT_SYMBOL_GPL(nand_subop_get_data_start_off);
> +
> +/**
> + * nand_subop_get_data_len - Get the number of bytes to retrieve
> + * @subop: The entire sub-operation
> + * @op_id: Index of the instruction inside the sub-operation
> + *
> + * Instructions arrays may be split by the parser between instructions,
> + * and also in the middle of a data instruction if the number of bytes to access
> + * in one operation is greater that the controller limit.
> + *
> + * For this, instead of using the ->data.len field from the data instruction,
> + * the NAND controller driver must use this helper that will return the actual
> + * length of data to move between the first and last offset asked for this
> + * particular instruction.
> + *
> + * Returns the length of the data to move from the pointed data instruction.
> + */
> +int nand_subop_get_data_len(const struct nand_subop *subop,
> +			    unsigned int op_id)
> +{
> +	int start_off = 0, end_off;
> +
> +	if (!nand_subop_instr_is_valid(subop, op_id) ||
> +	    !nand_instr_is_data(&subop->instrs[op_id]))
> +		return -EINVAL;
> +
> +	start_off = nand_subop_get_data_start_off(subop, op_id);
> +
> +	if (op_id == subop->ninstrs - 1 &&
> +	    subop->last_instr_end_off)
> +		end_off = subop->last_instr_end_off;
> +	else
> +		end_off = subop->instrs[op_id].data.len;
> +
> +	return end_off - start_off;
> +}
> +EXPORT_SYMBOL_GPL(nand_subop_get_data_len);
> +
> +/**
>   * nand_reset - Reset and initialize a NAND device
>   * @chip: The NAND chip
>   * @chipnr: Internal die id
> @@ -3940,7 +4815,7 @@ static void nand_set_defaults(struct nand_chip *chip)
>  		chip->chip_delay = 20;
>  
>  	/* check, if a user supplied command function given */
> -	if (chip->cmdfunc == NULL)
> +	if (chip->cmdfunc == NULL && !chip->exec_op)
>  		chip->cmdfunc = nand_command;
>  
>  	/* check, if a user supplied wait function given */
> @@ -4823,15 +5698,35 @@ int nand_scan_ident(struct mtd_info *mtd, int maxchips,
>  	if (!mtd->name && mtd->dev.parent)
>  		mtd->name = dev_name(mtd->dev.parent);
>  
> -	if ((!chip->cmdfunc || !chip->select_chip) && !chip->cmd_ctrl) {
> +	/*
> +	 * ->cmdfunc() is legacy and will only be used if ->exec_op() is not
> +	 * populated.
> +	 */
> +	if (chip->exec_op) {
>  		/*
> -		 * Default functions assigned for chip_select() and
> -		 * cmdfunc() both expect cmd_ctrl() to be populated,
> -		 * so we need to check that that's the case
> +		 * The implementation of ->exec_op() heavily relies on timings
> +		 * to be accessible through the nand_data_interface structure.
> +		 * Thus, the ->setup_data_interface() hook must be provided. The
> +		 * controller driver will be noticed of delays it must apply
> +		 * after each ->exec_op() instruction (if any) through the
> +		 * .delay_ns field. The driver will then choose to handle the
> +		 * delays manually if the controller cannot do it natively.
>  		 */
> -		pr_err("chip.cmd_ctrl() callback is not provided");
> -		return -EINVAL;
> +		if (!chip->setup_data_interface) {
> +			pr_err("->setup_data_interface() should be provided\n");
> +			return -EINVAL;
> +		}
> +	} else {
> +		/*
> +		 * Default functions assigned for ->cmdfunc() and
> +		 * ->select_chip() both expect ->cmd_ctrl() to be populated.
> +		 */
> +		if ((!chip->cmdfunc || !chip->select_chip) && !chip->cmd_ctrl) {
> +			pr_err("->cmd_ctrl() should be provided\n");
> +			return -EINVAL;
> +		}
>  	}
> +
>  	/* Set the default functions */
>  	nand_set_defaults(chip);
>  
> diff --git a/drivers/mtd/nand/nand_hynix.c b/drivers/mtd/nand/nand_hynix.c
> index 04e3ab7a476c..81c382f24513 100644
> --- a/drivers/mtd/nand/nand_hynix.c
> +++ b/drivers/mtd/nand/nand_hynix.c
> @@ -74,19 +74,36 @@ static bool hynix_nand_has_valid_jedecid(struct nand_chip *chip)
>  	return !strncmp("JEDEC", jedecid, sizeof(jedecid));
>  }
>  
> +static int hynix_nand_cmd_op(struct nand_chip *chip, u8 cmd)

Maybe you can introduce the helper in patch 1

> +{
> +	struct mtd_info *mtd = nand_to_mtd(chip);
> +
> +	if (chip->exec_op) {
> +		struct nand_op_instr instrs[] = {
> +			NAND_OP_CMD(cmd, 0),
> +		};
> +		struct nand_operation op = NAND_OPERATION(instrs);
> +
> +		return nand_exec_op(chip, &op);

And then ass this block of code in this patch.

> +	}
> +
> +	chip->cmdfunc(mtd, cmd, -1, -1);
> +
> +	return 0;
> +}
> +
>  static int hynix_nand_setup_read_retry(struct mtd_info *mtd, int retry_mode)
>  {
>  	struct nand_chip *chip = mtd_to_nand(mtd);
>  	struct hynix_nand *hynix = nand_get_manufacturer_data(chip);
>  	const u8 *values;
> -	int status;
>  	int i;
>  
>  	values = hynix->read_retry->values +
>  		 (retry_mode * hynix->read_retry->nregs);
>  
>  	/* Enter 'Set Hynix Parameters' mode */
> -	chip->cmdfunc(mtd, NAND_HYNIX_CMD_SET_PARAMS, -1, -1);
> +	hynix_nand_cmd_op(chip, NAND_HYNIX_CMD_SET_PARAMS);
>  
>  	/*
>  	 * Configure the NAND in the requested read-retry mode.
> @@ -101,16 +118,17 @@ static int hynix_nand_setup_read_retry(struct mtd_info *mtd, int retry_mode)
>  		int column = hynix->read_retry->regs[i];
>  
>  		column |= column << 8;
> -		chip->cmdfunc(mtd, NAND_CMD_NONE, column, -1);
> -		chip->write_byte(mtd, values[i]);
> +		if (chip->exec_op) {
> +			nand_change_write_column_op(chip, column,
> +						    &values[i], 1, true);

This is not a nand_change_write_column_op() op, here the cmd cycle is
set to NAND_CMD_NONE. You should have your own operation defined to
handle this sequence.

> +		} else {
> +			chip->cmdfunc(mtd, NAND_CMD_NONE, column, -1);
> +			chip->write_byte(mtd, values[i]);
> +		}
>  	}
>  
>  	/* Apply the new settings. */
> -	chip->cmdfunc(mtd, NAND_HYNIX_CMD_APPLY_PARAMS, -1, -1);
> -
> -	status = chip->waitfunc(mtd, chip);
> -	if (status & NAND_STATUS_FAIL)
> -		return -EIO;
> +	hynix_nand_cmd_op(chip, NAND_HYNIX_CMD_APPLY_PARAMS);

No, it's wrong, you miss the WAITRDY instruction to be compatible
with what was done before.

>  
>  	return 0;
>  }
> @@ -173,32 +191,65 @@ static int hynix_read_rr_otp(struct nand_chip *chip,
>  
>  	nand_reset_op(chip);
>  
> -	chip->cmdfunc(mtd, NAND_HYNIX_CMD_SET_PARAMS, -1, -1);
> +	hynix_nand_cmd_op(chip, NAND_HYNIX_CMD_SET_PARAMS);
>  
>  	for (i = 0; i < info->nregs; i++) {
>  		int column = info->regs[i];
>  
>  		column |= column << 8;
> -		chip->cmdfunc(mtd, NAND_CMD_NONE, column, -1);
> -		chip->write_byte(mtd, info->values[i]);
> +		if (chip->exec_op) {
> +			nand_change_write_column_op(chip, column,
> +						    &info->values[i], 1, true);
> +		} else {
> +			chip->cmdfunc(mtd, NAND_CMD_NONE, column, -1);
> +			chip->write_byte(mtd, info->values[i]);
> +		}

Same comments apply here.

>  	}
>  
> -	chip->cmdfunc(mtd, NAND_HYNIX_CMD_APPLY_PARAMS, -1, -1);
> +	hynix_nand_cmd_op(chip, NAND_HYNIX_CMD_APPLY_PARAMS);
>  
>  	/* Sequence to enter OTP mode? */
> -	chip->cmdfunc(mtd, 0x17, -1, -1);
> -	chip->cmdfunc(mtd, 0x04, -1, -1);
> -	chip->cmdfunc(mtd, 0x19, -1, -1);
> +	if (chip->exec_op) {
> +		struct nand_op_instr instrs[] = {
> +			NAND_OP_CMD(0x17, 0),
> +			NAND_OP_CMD(0x04, 0),
> +			NAND_OP_CMD(0x19, 0),
> +		};
> +		struct nand_operation op = NAND_OPERATION(instrs);
> +
> +		nand_exec_op(chip, &op);
> +	} else {
> +		chip->cmdfunc(mtd, 0x17, -1, -1);
> +		chip->cmdfunc(mtd, 0x04, -1, -1);
> +		chip->cmdfunc(mtd, 0x19, -1, -1);
> +	}

Why not creating a hynix_nand_enter_otp_mode_op() for that? 

>  
>  	/* Now read the page */
>  	nand_read_page_op(chip, info->page, 0, buf, info->size);
>  
>  	/* Put everything back to normal */
>  	nand_reset_op(chip);
> -	chip->cmdfunc(mtd, NAND_HYNIX_CMD_SET_PARAMS, 0x38, -1);
> -	chip->write_byte(mtd, 0x0);
> -	chip->cmdfunc(mtd, NAND_HYNIX_CMD_APPLY_PARAMS, -1, -1);
> -	chip->cmdfunc(mtd, NAND_CMD_READ0, 0x0, -1);
> +	if (chip->exec_op) {
> +		const struct nand_sdr_timings *sdr =
> +			nand_get_sdr_timings(&chip->data_interface);
> +		u8 byte = 0;
> +		u8 addr = 0x38;
> +		struct nand_op_instr instrs[] = {
> +			NAND_OP_CMD(NAND_HYNIX_CMD_SET_PARAMS, 0),
> +			NAND_OP_ADDR(1, &addr, sdr->tCCS_min),
> +			NAND_OP_8BIT_DATA_OUT(1, &byte, 0),
> +			NAND_OP_CMD(NAND_HYNIX_CMD_APPLY_PARAMS, 0),
> +			NAND_OP_CMD(NAND_CMD_READ0, 0),
> +		};
> +		struct nand_operation op = NAND_OPERATION(instrs);
> +
> +		nand_exec_op(chip, &op);
> +	} else {
> +		chip->cmdfunc(mtd, NAND_HYNIX_CMD_SET_PARAMS, 0x38, -1);
> +		chip->write_byte(mtd, 0x0);
> +		chip->cmdfunc(mtd, NAND_HYNIX_CMD_APPLY_PARAMS, -1, -1);
> +		chip->cmdfunc(mtd, NAND_CMD_READ0, 0x0, -1);
> +	}

Same here.

>  
>  	return 0;
>  }
> diff --git a/drivers/mtd/nand/nand_micron.c b/drivers/mtd/nand/nand_micron.c
> index 543352380ffa..109d8003e33d 100644
> --- a/drivers/mtd/nand/nand_micron.c
> +++ b/drivers/mtd/nand/nand_micron.c
> @@ -117,14 +117,38 @@ micron_nand_read_page_on_die_ecc(struct mtd_info *mtd, struct nand_chip *chip,
>  				 uint8_t *buf, int oob_required,
>  				 int page)
>  {
> -	int status;
> +	u8 status;
>  	int max_bitflips = 0;
>  
>  	micron_nand_on_die_ecc_setup(chip, true);
>  
> -	chip->cmdfunc(mtd, NAND_CMD_READ0, 0x00, page);
> -	chip->cmdfunc(mtd, NAND_CMD_STATUS, -1, -1);
> -	status = chip->read_byte(mtd);
> +	if (chip->exec_op) {
> +		u8 addrs[5] = {};
> +		struct nand_op_instr instrs[] = {
> +			NAND_OP_CMD(NAND_CMD_READ0, 0),
> +			NAND_OP_ADDR(4, addrs, 0),
> +			NAND_OP_CMD(NAND_CMD_STATUS, 0),
> +			NAND_OP_8BIT_DATA_IN(1, &status, 0),
> +		};
> +		struct nand_operation op = NAND_OPERATION(instrs);
> +
> +		if (nand_fill_column_cycles(chip, addrs, 0))

		if (nand_fill_column_cycles(chip, addrs, 0) < 0)

> +			return -EINVAL;
> +
> +		addrs[2] = page;
> +		addrs[3] = page >> 8;
> +		if (chip->options & NAND_ROW_ADDR_3) {
> +			addrs[4] = page >> 16;
> +			instrs[1].addr.naddrs++;
> +		}
> +
> +		nand_exec_op(chip, &op);
> +	} else {
> +		chip->cmdfunc(mtd, NAND_CMD_READ0, 0x00, page);
> +		chip->cmdfunc(mtd, NAND_CMD_STATUS, -1, -1);
> +		status = chip->read_byte(mtd);
> +	}
> +
>  	if (status & NAND_STATUS_FAIL)
>  		mtd->ecc_stats.failed++;
>  	/*
> diff --git a/include/linux/mtd/rawnand.h b/include/linux/mtd/rawnand.h
> index 1acc26ed0e91..302f231df65e 100644
> --- a/include/linux/mtd/rawnand.h
> +++ b/include/linux/mtd/rawnand.h
> @@ -751,6 +751,337 @@ struct nand_manufacturer_ops {
>  };
>  
>  /**
> + * struct nand_op_cmd_instr - Definition of a command instruction
> + * @opcode: the command to assert in one cycle
> + */
> +struct nand_op_cmd_instr {
> +	u8 opcode;
> +};
> +
> +/**
> + * struct nand_op_addr_instr - Definition of an address instruction
> + * @naddrs: length of the @addrs array
> + * @addrs: array containing the address cycles to assert
> + */
> +struct nand_op_addr_instr {
> +	unsigned int naddrs;
> +	const u8 *addrs;
> +};
> +
> +/**
> + * struct nand_op_data_instr - Definition of a data instruction
> + * @len: number of data bytes to move
> + * @in: buffer to fill when reading from the NAND chip
> + * @out: buffer to read from when writing to the NAND chip
> + * @force_8bit: force 8-bit access
> + *
> + * Please note that "in" and "out" are inverted from the ONFI specification
> + * and are from the controller perspective, so a "in" is a read from the NAND
> + * chip while a "out" is a write to the NAND chip.
> + */
> +struct nand_op_data_instr {
> +	unsigned int len;
> +	union {
> +		void *in;
> +		const void *out;
> +	};
> +	bool force_8bit;
> +};
> +
> +/**
> + * struct nand_op_waitrdy_instr - Definition of a wait ready instruction
> + * @timeout_ms: maximum delay while waiting for the ready/busy pin in ms
> + */
> +struct nand_op_waitrdy_instr {
> +	unsigned int timeout_ms;
> +};
> +
> +/**
> + * enum nand_op_instr_type - Enumeration of all the instructions

*of all instruction types

> + *
> + * Please note that data instructions are separated into DATA_IN and DATA_OUT
> + * instructions.
> + */
> +enum nand_op_instr_type {
> +	NAND_OP_CMD_INSTR,
> +	NAND_OP_ADDR_INSTR,
> +	NAND_OP_DATA_IN_INSTR,
> +	NAND_OP_DATA_OUT_INSTR,
> +	NAND_OP_WAITRDY_INSTR,
> +};
> +
> +/**
> + * struct nand_op_instr - Generic definition of and instruction

s/and/an/

> + * @type: an enumeration of the instruction type
> + * @cmd/@addr/@data/@waitrdy: the actual instruction to refer depending on the
> + *			      value of @type

"
extra data associated to the instruction. You'll have to use
the appropriate element depending on @type"

> + * @delay_ns: delay to apply by the controller after the instruction has been
> + *	      actually executed (most of them are directly handled by the
> + *	      controllers once the timings negociation has been done)
> + */
> +struct nand_op_instr {
> +	enum nand_op_instr_type type;
> +	union {
> +		struct nand_op_cmd_instr cmd;
> +		struct nand_op_addr_instr addr;
> +		struct nand_op_data_instr data;
> +		struct nand_op_waitrdy_instr waitrdy;
> +	};
> +	unsigned int delay_ns;
> +};
> +
> +/*
> + * Special handling must be done for the WAITRDY timeout parameter as it usually
> + * is either tPROG (after a prog), tR (before a read), tRST (during a reset) or
> + * tBERS (during an erase) which all of them are u64 values that cannot be
> + * divided by usual kernel macros and must be handled with the special
> + * DIV_ROUND_UP_ULL() macro.
> + */
> +#define PSEC_TO_NSEC(x) DIV_ROUND_UP(x, 10^3)
> +#define PSEC_TO_MSEC(x) DIV_ROUND_UP_ULL(x, 10^9)

Hm, that's a bit of a mess to let each macro decide which converter
they should use, because we don't know at this level whether the timing
is stored in an u64 or u32. How about doing the conversion in the
call-sites instead, because there you should know what you manipulate.

Something like

	NAND_OP_CMD(FOO, PSEC_TO_NSEC_UL(sdr->tXX))
	NAND_OP_WAITRDY(PSEC_TO_MSEC_ULL(sdr->tXX),
			PSEC_TO_NSEC_UL(sdr->tYY))



> +
> +#define NAND_OP_CMD(id, delay_ps)					\
> +	{								\
> +		.type = NAND_OP_CMD_INSTR,				\
> +		.cmd.opcode = id,					\
> +		.delay_ns = PSEC_TO_NSEC(delay_ps),			\
> +	}
> +
> +#define NAND_OP_ADDR(ncycles, cycles, delay_ps)				\
> +	{								\
> +		.type = NAND_OP_ADDR_INSTR,				\
> +		.addr = {						\
> +			.naddrs = ncycles,				\
> +			.addrs = cycles,				\
> +		},							\
> +		.delay_ns = PSEC_TO_NSEC(delay_ps),			\
> +	}
> +
> +#define NAND_OP_DATA_IN(l, buf, delay_ps)				\
> +	{								\
> +		.type = NAND_OP_DATA_IN_INSTR,				\
> +		.data = {						\
> +			.len = l,					\
> +			.in = buf,					\
> +			.force_8bit = false,				\
> +		},							\
> +		.delay_ns = PSEC_TO_NSEC(delay_ps),			\
> +	}
> +
> +#define NAND_OP_DATA_OUT(l, buf, delay_ps)				\
> +	{								\
> +		.type = NAND_OP_DATA_OUT_INSTR,				\
> +		.data = {						\
> +			.len = l,					\
> +			.out = buf,					\
> +			.force_8bit = false,				\
> +		},							\
> +		.delay_ns = PSEC_TO_NSEC(delay_ps),			\
> +	}
> +
> +#define NAND_OP_8BIT_DATA_IN(l, buf, delay_ps)				\
> +	{								\
> +		.type = NAND_OP_DATA_IN_INSTR,				\
> +		.data = {						\
> +			.len = l,					\
> +			.in = buf,					\
> +			.force_8bit = true,				\
> +		},							\
> +		.delay_ns = PSEC_TO_NSEC(delay_ps),			\
> +	}
> +
> +#define NAND_OP_8BIT_DATA_OUT(l, buf, delay_ps)				\
> +	{								\
> +		.type = NAND_OP_DATA_OUT_INSTR,				\
> +		.data = {						\
> +			.len = l,					\
> +			.out = buf,					\
> +			.force_8bit = true,				\
> +		},							\
> +		.delay_ns = PSEC_TO_NSEC(delay_ps),			\
> +	}
> +
> +#define NAND_OP_WAIT_RDY(tout_ps, delay_ps)				\
> +	{								\
> +		.type = NAND_OP_WAITRDY_INSTR,				\
> +		.waitrdy.timeout_ms = PSEC_TO_MSEC(tout_ps),		\
> +		.delay_ns = PSEC_TO_NSEC(delay_ps),			\
> +	}
> +
> +/**
> + * struct nand_subop - a sub operation
> + * @instrs: array of instructions
> + * @ninstrs: length of the @instrs array
> + * @first_instr_start_off: offset to start from for the first instruction
> + *			   of the sub-operation
> + * @last_instr_end_off: offset to end at (excluded) for the last instruction
> + *			of the sub-operation
> + *
> + * When an operation cannot be handled as is by the NAND controller, it will
> + * be split by the parser and the remaining pieces will be handled as
> + * sub-operations.
> + */
> +struct nand_subop {
> +	const struct nand_op_instr *instrs;
> +	unsigned int ninstrs;
> +	/*
> +	 * Specific offset for the first and the last instructions of the subop.
> +	 * Applies for the address cycles in the case of address, or for data
> +	 * offset in the case of data transfers. Otherwise, it is irrelevant.
> +	 */

Can you move that in the kernel header doc?

> +	unsigned int first_instr_start_off;
> +	unsigned int last_instr_end_off;
> +};
> +
> +int nand_subop_get_addr_start_off(const struct nand_subop *subop,
> +				  unsigned int op_id);
> +int nand_subop_get_num_addr_cyc(const struct nand_subop *subop,
> +				unsigned int op_id);
> +int nand_subop_get_data_start_off(const struct nand_subop *subop,
> +				  unsigned int op_id);
> +int nand_subop_get_data_len(const struct nand_subop *subop,
> +			    unsigned int op_id);
> +
> +/**
> + * struct nand_op_parser_addr_constraints - Constraints for address instructions
> + * @maxcycles: maximum number of cycles that the controller can assert by
> + *	       instruction
> + */
> +struct nand_op_parser_addr_constraints {
> +	unsigned int maxcycles;
> +};
> +
> +/**
> + * struct nand_op_parser_data_constraints - Constraints for data instructions
> + * @maxlen: maximum data length that the controller can handle with one
> + *	    instruction
> + */
> +struct nand_op_parser_data_constraints {
> +	unsigned int maxlen;

At some point we should probably add minlen and alignment fields, but
let's keep that for later.

> +};
> +
> +/**
> + * struct nand_op_parser_pattern_elem - One element of a pattern
> + * @type: the instructuction type
> + * @optional: if this element of the pattern is optional or mandatory
> + * @addr/@data: address or data constraint (number of cycles or data length)
> + */
> +struct nand_op_parser_pattern_elem {
> +	enum nand_op_instr_type type;
> +	bool optional;
> +	union {
> +		struct nand_op_parser_addr_constraints addr;
> +		struct nand_op_parser_data_constraints data;
> +	};
> +};
> +
> +#define NAND_OP_PARSER_PAT_CMD_ELEM(_opt)			\
> +	{							\
> +		.type = NAND_OP_CMD_INSTR,			\
> +		.optional = _opt,				\
> +	}
> +
> +#define NAND_OP_PARSER_PAT_ADDR_ELEM(_opt, _maxcycles)		\
> +	{							\
> +		.type = NAND_OP_ADDR_INSTR,			\
> +		.optional = _opt,				\
> +		.addr.maxcycles = _maxcycles,			\
> +	}
> +
> +#define NAND_OP_PARSER_PAT_DATA_IN_ELEM(_opt, _maxlen)		\
> +	{							\
> +		.type = NAND_OP_DATA_IN_INSTR,			\
> +		.optional = _opt,				\
> +		.data.maxlen = _maxlen,				\
> +	}
> +
> +#define NAND_OP_PARSER_PAT_DATA_OUT_ELEM(_opt, _maxlen)		\
> +	{							\
> +		.type = NAND_OP_DATA_OUT_INSTR,			\
> +		.optional = _opt,				\
> +		.data.maxlen = _maxlen,				\
> +	}
> +
> +#define NAND_OP_PARSER_PAT_WAITRDY_ELEM(_opt)			\
> +	{							\
> +		.type = NAND_OP_WAITRDY_INSTR,			\
> +		.optional = _opt,				\
> +	}
> +
> +/**
> + * struct nand_op_parser_pattern - A complete pattern
> + * @elems: array of pattern elements
> + * @nelems: number of pattern elements in @elems array
> + * @exec: the function that will actually execute this pattern, written in the
> + *	  controller driver
> + *
> + * This is a complete pattern that is a list of elements, each one reprensenting
> + * one instruction with its constraints. Controller drivers must declare as much
> + * patterns as they support and give the list of the supported patterns (created
> + * with the help of the following macro) at the call to nand_op_parser_exec_op

s/at the call to nand_op_parser_exec_op/when calling
nand_op_parser_exec_op()/

> + * which shall be the main thing to do in the driver implementation of
> + * ->exec_op().

I'd be more lax than that. Yes the parser is the preferred approach
when you deal with a complex controller. But for simple controllers
it's not.

> Once there is a match between the pattern and an operation, the
> + * core calls the @exec function to actually do the operation.

Not necessarily. The plan is still to ask the controller which
operation it supports before actually executing them. So, in this case
(when check_only param is true), nand_op_parser_exec_op() will never
call ->exec(), it will just make sure the operation can be handled with
the provided patterns.

> + */
> +struct nand_op_parser_pattern {
> +	const struct nand_op_parser_pattern_elem *elems;
> +	unsigned int nelems;
> +	int (*exec)(struct nand_chip *chip, const struct nand_subop *subop);
> +};
> +
> +#define NAND_OP_PARSER_PATTERN(_exec, ...)							\
> +	{											\
> +		.exec = _exec,									\
> +		.elems = (struct nand_op_parser_pattern_elem[]) { __VA_ARGS__ },		\
> +		.nelems = sizeof((struct nand_op_parser_pattern_elem[]) { __VA_ARGS__ }) /	\
> +			  sizeof(struct nand_op_parser_pattern_elem),				\
> +	}
> +
> +/**
> + * struct nand_op_parser - The actual parser
> + * @patterns: array of patterns
> + * @npatterns: length of the @patterns array
> + *
> + * The actual parser structure wich is an array of supported patterns.

It's worth mentioning that patterns will be tested in their declaration
order, and the first match will be taken, so it's important to oder
patterns appropriately so that simple/inefficient patterns are placed
at the end of the list. Usually, this is where you put single
instruction patterns.

> + */
> +struct nand_op_parser {
> +	const struct nand_op_parser_pattern *patterns;
> +	unsigned int npatterns;
> +};
> +
> +#define NAND_OP_PARSER(...)									\
> +	{											\
> +		.patterns = (struct nand_op_parser_pattern[]) { __VA_ARGS__ },			\
> +		.npatterns = sizeof((struct nand_op_parser_pattern[]) { __VA_ARGS__ }) /	\
> +			     sizeof(struct nand_op_parser_pattern),				\
> +	}
> +
> +/**
> + * struct nand_operation - The actual operation
> + * @instrs: array of instructions to execute
> + * @ninstrs: length of the @instrs array
> + *
> + * The actual operation structure that will be given to the parser.

Not only the parser, it will be passed to chip->exep_op() as well.

> + */
> +struct nand_operation {
> +	const struct nand_op_instr *instrs;
> +	unsigned int ninstrs;
> +};
> +
> +#define NAND_OPERATION(_instrs)					\
> +	{							\
> +		.instrs = _instrs,				\
> +		.ninstrs = ARRAY_SIZE(_instrs),			\
> +	}
> +
> +int nand_fill_column_cycles(struct nand_chip *chip, u8 *addrs,
> +			    unsigned int offset_in_page);
> +
> +int nand_op_parser_exec_op(struct nand_chip *chip,
> +			   const struct nand_op_parser *parser,
> +			   const struct nand_operation *op, bool check_only);
> +
> +/**
>   * struct nand_chip - NAND Private Flash Chip Data
>   * @mtd:		MTD device registered to the MTD framework
>   * @IO_ADDR_R:		[BOARDSPECIFIC] address to read the 8 I/O lines of the
> @@ -885,6 +1216,9 @@ struct nand_chip {
>  	int (*setup_data_interface)(struct mtd_info *mtd, int chipnr,
>  				    const struct nand_data_interface *conf);
>  
> +	int (*exec_op)(struct nand_chip *chip,
> +		       const struct nand_operation *op,
> +		       bool check_only);
>  
>  	int chip_delay;
>  	unsigned int options;
> @@ -945,6 +1279,15 @@ struct nand_chip {
>  	} manufacturer;
>  };
>  
> +static inline int nand_exec_op(struct nand_chip *chip,
> +			       const struct nand_operation *op)
> +{
> +	if (!chip->exec_op)
> +		return -ENOTSUPP;
> +
> +	return chip->exec_op(chip, op, false);
> +}
> +
>  extern const struct mtd_ooblayout_ops nand_ooblayout_sp_ops;
>  extern const struct mtd_ooblayout_ops nand_ooblayout_lp_ops;
>  
> @@ -1307,23 +1650,26 @@ int nand_readid_op(struct nand_chip *chip, u8 addr, void *buf,
>  int nand_status_op(struct nand_chip *chip, u8 *status);
>  int nand_erase_op(struct nand_chip *chip, unsigned int eraseblock);
>  int nand_read_page_op(struct nand_chip *chip, unsigned int page,
> -		      unsigned int column, void *buf, unsigned int len);
> -int nand_change_read_column_op(struct nand_chip *chip, unsigned int column,
> -			       void *buf, unsigned int len, bool force_8bit);
> +		      unsigned int offset_in_page, void *buf, unsigned int len);
> +int nand_change_read_column_op(struct nand_chip *chip,
> +			       unsigned int offset_in_page, void *buf,
> +			       unsigned int len, bool force_8bit);
>  int nand_read_oob_op(struct nand_chip *chip, unsigned int page,
> -		     unsigned int column, void *buf, unsigned int len);
> +		     unsigned int offset_in_page, void *buf, unsigned int len);
>  int nand_prog_page_begin_op(struct nand_chip *chip, unsigned int page,
> -			    unsigned int column, const void *buf,
> +			    unsigned int offset_in_page, const void *buf,
>  			    unsigned int len);
>  int nand_prog_page_end_op(struct nand_chip *chip);
>  int nand_prog_page_op(struct nand_chip *chip, unsigned int page,
> -		      unsigned int column, const void *buf, unsigned int len);
> -int nand_change_write_column_op(struct nand_chip *chip, unsigned int column,
> -				const void *buf, unsigned int len, bool force_8bit);
> +		      unsigned int offset_in_page, const void *buf,
> +		      unsigned int len);
> +int nand_change_write_column_op(struct nand_chip *chip,
> +				unsigned int offset_in_page, const void *buf,
> +				unsigned int len, bool force_8bit);
>  int nand_read_data_op(struct nand_chip *chip, void *buf, unsigned int len,
> -		      bool force_8bits);
> +		      bool force_8bit);
>  int nand_write_data_op(struct nand_chip *chip, const void *buf,
> -		       unsigned int len, bool force_8bits);
> +		       unsigned int len, bool force_8bit);
>  
>  /* Free resources held by the NAND device */
>  void nand_cleanup(struct nand_chip *chip);

^ permalink raw reply	[flat|nested] 75+ messages in thread

* [RFC 04/12] mtd: nand: add ->exec_op() implementation
@ 2017-10-18 21:57     ` Boris Brezillon
  0 siblings, 0 replies; 75+ messages in thread
From: Boris Brezillon @ 2017-10-18 21:57 UTC (permalink / raw)
  To: linux-arm-kernel

On Wed, 18 Oct 2017 16:36:21 +0200
Miquel Raynal <miquel.raynal@free-electrons.com> wrote:

> Introduce the new way to control the NAND controller drivers by
> implementing the ->exec_op() core helpers and allowing new drivers to
> use it instead of relying on ->cmd_ctrl(), ->cmdfunc() and
> ->read/write_byte/word/buf().

"
Introduce a new interface to instruct NAND controllers to send specific
NAND operations. The new interface takes the form of a single method
called ->exec_op(). This method is designed to replace ->cmd_ctrl(),
->cmdfunc() and ->read/write_byte/word/buf() hooks.
"

> 
> The logic is now to send to the controller driver a list of
> instructions.

"
->exec_op() is passed a set of instructions describing the operation to
execute. Each instruction has a type (ADDR, CMD, CYCLE, WAITRDY) and
delay. The type is directly matching the description of NAND commands
in various NAND datasheet and standards (ONFI, JEDEC), the delay is
here to help simple controllers wait enough time between each
instruction. Advanced controllers with integrated timings control can
ignore these delays. 
"

> The driver shall use the parser added by this commit to
> get the matching hook if any.

That's true only for advanced controllers. For those who are executing
each instruction individually (like the gpio-nand driver, or any old
style driver where each ADDR/CMD/DATA cycle is controlled
independently) this is not needed, and who add extra complexity for no
real gain.

> The instructions may be split by the
> parser in order to comply with the controller constraints filled in an
> array of supported patterns.

Given only this description it's hard to tell what this parser is and
why it's needed. I think a real example who be helpful to better
understand what this is.

> 
> Various helpers are also added to ease NAND controller drivers writing.
> 
> This new interface should really ease the support of new vendor specific
> instructions.

Actually, it's more than easing support for vendor specific operations,
it allows the core to check whether the NAND controller will be able to
execute a specific operation or not, which before that was impossible.
It doesn't mean all controllers will magically support all kind of NAND
operations, but at least we'll know when it doesn't.

> 
> Suggested-by: Boris Brezillon <boris.brezillon@free-electrons.com>
> Signed-off-by: Miquel Raynal <miquel.raynal@free-electrons.com>
> ---
>  drivers/mtd/nand/denali.c      |  93 +++-
>  drivers/mtd/nand/nand_base.c   | 949 +++++++++++++++++++++++++++++++++++++++--
>  drivers/mtd/nand/nand_hynix.c  |  91 +++-
>  drivers/mtd/nand/nand_micron.c |  32 +-
>  include/linux/mtd/rawnand.h    | 366 +++++++++++++++-
>  5 files changed, 1448 insertions(+), 83 deletions(-)
> 
> diff --git a/drivers/mtd/nand/denali.c b/drivers/mtd/nand/denali.c
> index e5f38359f6df..8f0f18d9d9cf 100644
> --- a/drivers/mtd/nand/denali.c
> +++ b/drivers/mtd/nand/denali.c
> @@ -652,8 +652,6 @@ static void denali_oob_xfer(struct mtd_info *mtd, struct nand_chip *chip,
>  			    int page, int write)
>  {
>  	struct denali_nand_info *denali = mtd_to_denali(mtd);
> -	unsigned int start_cmd = write ? NAND_CMD_SEQIN : NAND_CMD_READ0;
> -	unsigned int rnd_cmd = write ? NAND_CMD_RNDIN : NAND_CMD_RNDOUT;
>  	int writesize = mtd->writesize;
>  	int oobsize = mtd->oobsize;
>  	uint8_t *bufpoi = chip->oob_poi;
> @@ -665,11 +663,22 @@ static void denali_oob_xfer(struct mtd_info *mtd, struct nand_chip *chip,
>  	int i, pos, len;
>  
>  	/* BBM at the beginning of the OOB area */
> -	chip->cmdfunc(mtd, start_cmd, writesize, page);
> -	if (write)
> -		chip->write_buf(mtd, bufpoi, oob_skip);
> -	else
> -		chip->read_buf(mtd, bufpoi, oob_skip);
> +	if (chip->exec_op) {
> +		if (write)
> +			nand_prog_page_begin_op(chip, page, writesize, bufpoi,
> +						oob_skip);
> +		else
> +			nand_read_page_op(chip, page, writesize, bufpoi,
> +					  oob_skip);
> +	} else {
> +		if (write) {
> +			chip->cmdfunc(mtd, NAND_CMD_SEQIN, writesize, page);
> +			chip->write_buf(mtd, bufpoi, oob_skip);
> +		} else {
> +			chip->cmdfunc(mtd, NAND_CMD_READ0, writesize, page);
> +			chip->read_buf(mtd, bufpoi, oob_skip);
> +		}
> +	}

Why do we have to keep the ->cmdfunc() logic? nand_prog_page_xxx()
already abstracts this for us, right?

>  	bufpoi += oob_skip;
>  
>  	/* OOB ECC */
> @@ -682,30 +691,67 @@ static void denali_oob_xfer(struct mtd_info *mtd, struct nand_chip *chip,
>  		else if (pos + len > writesize)
>  			len = writesize - pos;
>  
> -		chip->cmdfunc(mtd, rnd_cmd, pos, -1);
> -		if (write)
> -			chip->write_buf(mtd, bufpoi, len);
> -		else
> -			chip->read_buf(mtd, bufpoi, len);
> -		bufpoi += len;
> -		if (len < ecc_bytes) {
> -			len = ecc_bytes - len;
> -			chip->cmdfunc(mtd, rnd_cmd, writesize + oob_skip, -1);
> +		if (chip->exec_op) {
>  			if (write)
> -				chip->write_buf(mtd, bufpoi, len);
> +				nand_change_write_column_op(
> +					chip, pos, bufpoi, len, false);

Nitpick, but can you try to align things like that:

				nand_change_write_column_op(chip, pos,
							    bufpoi, len,
							    false);

>  			else
> +				nand_change_read_column_op(
> +					chip, pos, bufpoi, len, false);

Ditto.

> +		} else {
> +			if (write) {
> +				chip->cmdfunc(mtd, NAND_CMD_RNDIN, pos, -1);
> +				chip->write_buf(mtd, bufpoi, len);
> +			} else {
> +				chip->cmdfunc(mtd, NAND_CMD_RNDOUT, pos, -1);
>  				chip->read_buf(mtd, bufpoi, len);
> +			}
> +		}

Smae here: I don't think we need to support both ->cmdfunc() and
nand_change_read/write_column().

> +		bufpoi += len;
> +		if (len < ecc_bytes) {
> +			len = ecc_bytes - len;
> +			if (chip->exec_op) {
> +				if (write)
> +					nand_change_write_column_op(
> +						chip, writesize + oob_skip,
> +						bufpoi, len, false);
> +				else
> +					nand_change_read_column_op(
> +						chip, writesize + oob_skip,
> +						bufpoi, len, false);
> +			} else {
> +				if (write) {
> +					chip->cmdfunc(mtd, NAND_CMD_RNDIN,
> +						      writesize + oob_skip, -1);
> +					chip->write_buf(mtd, bufpoi, len);
> +				} else {
> +					chip->cmdfunc(mtd, NAND_CMD_RNDOUT,
> +						      writesize + oob_skip, -1);
> +					chip->read_buf(mtd, bufpoi, len);
> +				}
> +			.

Ditto.

>  			bufpoi += len;
>  		}
>  	}
>  
>  	/* OOB free */
>  	len = oobsize - (bufpoi - chip->oob_poi);
> -	chip->cmdfunc(mtd, rnd_cmd, size - len, -1);
> -	if (write)
> -		chip->write_buf(mtd, bufpoi, len);
> -	else
> -		chip->read_buf(mtd, bufpoi, len);
> +	if (chip->exec_op) {
> +		if (write)
> +			nand_change_write_column_op(chip, size - len, bufpoi,
> +						    len, false);
> +		else
> +			nand_change_read_column_op(chip, size - len, bufpoi,
> +						   len, false);
> +	} else {
> +		if (write) {
> +			chip->cmdfunc(mtd, NAND_CMD_RNDIN, size - len, -1);
> +			chip->write_buf(mtd, bufpoi, len);
> +		} else {
> +			chip->cmdfunc(mtd, NAND_CMD_RNDOUT, size - len, -1);
> +			chip->read_buf(mtd, bufpoi, len);
> +		}
> +	}
>  }
>  
>  static int denali_read_page_raw(struct mtd_info *mtd, struct nand_chip *chip,
> @@ -803,6 +849,9 @@ static int denali_write_oob(struct mtd_info *mtd, struct nand_chip *chip,
>  
>  	denali_oob_xfer(mtd, chip, page, 1);
>  
> +	if (chip->exec_op)
> +		return nand_prog_page_end_op(chip);
> +
>  	chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1);
>  	status = chip->waitfunc(mtd, chip);

All denali changes in this patch should actually be moved to patch 1.

>  
> diff --git a/drivers/mtd/nand/nand_base.c b/drivers/mtd/nand/nand_base.c
> index bef20e06f0db..737f19bd2f83 100644
> --- a/drivers/mtd/nand/nand_base.c
> +++ b/drivers/mtd/nand/nand_base.c
> @@ -814,7 +814,7 @@ static void nand_ccs_delay(struct nand_chip *chip)
>  	 * Wait tCCS_min if it is correctly defined, otherwise wait 500ns
>  	 * (which should be safe for all NANDs).
>  	 */
> -	if (chip->data_interface.timings.sdr.tCCS_min)
> +	if (&chip->data_interface.timings.sdr.tCCS_min)

This condition is always true. I think this should be replaced by:

	if (chip->setup_data_interface)

And BTW, it should go in patch 3.

>  		ndelay(chip->data_interface.timings.sdr.tCCS_min / 1000);
>  	else
>  		ndelay(500);
> @@ -1233,6 +1233,118 @@ static int nand_init_data_interface(struct nand_chip *chip)
>  }
>  
>  /**
> + * nand_fill_column_cycles - fill the column fields on an address array
> + * @chip: The NAND chip
> + * @addrs: Array of address cycles to fill
> + * @offset_in_page: The offset in the page
> + *
> + * Fills the first or the two first bytes of the @addrs field depending
> + * on the NAND bus width and the page size.
> + */
> +int nand_fill_column_cycles(struct nand_chip *chip, u8 *addrs,
> +			    unsigned int offset_in_page)
> +{
> +	struct mtd_info *mtd = nand_to_mtd(chip);
> +
> +	/*
> +	 * The offset in page is expressed in bytes, if the NAND bus is 16-bit
> +	 * wide, then it must be divided by 2.
> +	 */
> +	if (chip->options & NAND_BUSWIDTH_16) {
> +		if (offset_in_page % 2) {
> +			WARN_ON(true);
> +			return -EINVAL;
> +		}
> +
> +		offset_in_page /= 2;
> +	}
> +
> +	addrs[0] = offset_in_page;
> +
> +	/* Small pages use 1 cycle for the columns, while large page need 2 */
> +	if (mtd->writesize <= 512)
> +		return 1;
> +
> +	addrs[1] = offset_in_page >> 8;
> +
> +	return 2;
> +}
> +EXPORT_SYMBOL_GPL(nand_fill_column_cycles);
> +
> +static int nand_sp_exec_read_page_op(struct nand_chip *chip, unsigned int page,
> +				     unsigned int offset_in_page, void *buf,
> +				     unsigned int len)
> +{
> +	struct mtd_info *mtd = nand_to_mtd(chip);
> +	const struct nand_sdr_timings *sdr =
> +		nand_get_sdr_timings(&chip->data_interface);
> +	u8 addrs[4];
> +	struct nand_op_instr instrs[] = {
> +		NAND_OP_CMD(NAND_CMD_READ0, 0),
> +		NAND_OP_ADDR(3, addrs, sdr->tWB_max),
> +		NAND_OP_WAIT_RDY(sdr->tR_max, sdr->tRR_min),
> +		NAND_OP_DATA_IN(len, buf, 0),
> +	};
> +	struct nand_operation op = NAND_OPERATION(instrs);
> +
> +	/* Drop the DATA_OUT instruction if len is set to 0. */
> +	if (!len)
> +		op.ninstrs--;
> +
> +	if (offset_in_page >= mtd->writesize)
> +		instrs[0].cmd.opcode = NAND_CMD_READOOB;
> +	else if (offset_in_page >= 256)
> +		instrs[0].cmd.opcode = NAND_CMD_READ1;
> +
> +	if (nand_fill_column_cycles(chip, addrs, offset_in_page) < 0)

Why not returning the retcode of nand_fill_column_cycles() directly?

	ret = nand_fill_column_cycles(chip, addrs, offset_in_page)
	if (ret < 0)
		return ret;

> +		return -EINVAL;
> +
> +	addrs[1] = page;
> +	addrs[2] = page >> 8;
> +
> +	if (chip->options & NAND_ROW_ADDR_3) {
> +		addrs[3] = page >> 16;
> +		instrs[1].addr.naddrs++;
> +	}
> +
> +	return nand_exec_op(chip, &op);
> +}
> +
> +static int nand_lp_exec_read_page_op(struct nand_chip *chip, unsigned int page,
> +				     unsigned int offset_in_page, void *buf,
> +				     unsigned int len)
> +{
> +	const struct nand_sdr_timings *sdr =
> +		nand_get_sdr_timings(&chip->data_interface);
> +	u8 addrs[5];
> +	struct nand_op_instr instrs[] = {
> +		NAND_OP_CMD(NAND_CMD_READ0, 0),
> +		NAND_OP_ADDR(4, addrs, 0),
> +		NAND_OP_CMD(NAND_CMD_READSTART, sdr->tWB_max),
> +		NAND_OP_WAIT_RDY(sdr->tR_max, sdr->tRR_min),
> +		NAND_OP_DATA_IN(len, buf, 0),
> +	};
> +	struct nand_operation op = NAND_OPERATION(instrs);
> +
> +	/* Drop the DATA_IN instruction if len is set to 0. */
> +	if (!len)
> +		op.ninstrs--;
> +
> +	if (nand_fill_column_cycles(chip, addrs, offset_in_page))

Should be

	if (nand_fill_column_cycles(chip, addrs, offset_in_page) < 0)

> +		return -EINVAL;
> +
> +	addrs[2] = page;
> +	addrs[3] = page >> 8;
> +
> +	if (chip->options & NAND_ROW_ADDR_3) {
> +		addrs[4] = page >> 16;
> +		instrs[1].addr.naddrs++;
> +	}
> +
> +	return nand_exec_op(chip, &op);
> +}
> +
> +/**
>   * nand_read_page_op - Do a READ PAGE operation
>   * @chip: The NAND chip
>   * @page: page to read
> @@ -1246,17 +1358,26 @@ static int nand_init_data_interface(struct nand_chip *chip)
>   * Returns 0 for success or negative error code otherwise
>   */
>  int nand_read_page_op(struct nand_chip *chip, unsigned int page,
> -		      unsigned int column, void *buf, unsigned int len)
> +		      unsigned int offset_in_page, void *buf, unsigned int len)

You should change the parameter name in patch 1 since this function is
introduced there.

>  {
>  	struct mtd_info *mtd = nand_to_mtd(chip);
>  
>  	if (len && !buf)
>  		return -EINVAL;
>  
> -	if (column + len > mtd->writesize + mtd->oobsize)
> +	if (offset_in_page + len > mtd->writesize + mtd->oobsize)
>  		return -EINVAL;
>  
> -	chip->cmdfunc(mtd, NAND_CMD_READ0, column, page);
> +	if (chip->exec_op) {
> +		if (mtd->writesize > 512)
> +			return nand_lp_exec_read_page_op(
> +				chip, page, offset_in_page, buf, len);
> +
> +		return nand_sp_exec_read_page_op(chip, page, offset_in_page,
> +						 buf, len);
> +	}
> +
> +	chip->cmdfunc(mtd, NAND_CMD_READ0, offset_in_page, page);
>  	if (len)
>  		chip->read_buf(mtd, buf, len);
>  
> @@ -1286,6 +1407,25 @@ static int nand_read_param_page_op(struct nand_chip *chip, u8 page, void *buf,
>  	if (len && !buf)
>  		return -EINVAL;
>  
> +	if (chip->exec_op) {
> +		const struct nand_sdr_timings *sdr =
> +			nand_get_sdr_timings(&chip->data_interface);
> +		struct nand_op_instr instrs[] = {
> +			NAND_OP_CMD(NAND_CMD_PARAM, 0),
> +			NAND_OP_ADDR(1, &page, sdr->tWB_max),
> +			NAND_OP_WAIT_RDY(sdr->tR_max, sdr->tRR_min),
> +			NAND_OP_8BIT_DATA_IN(len, buf, 0),
> +		};
> +		struct nand_operation op =
> +			NAND_OPERATION(instrs);
> +
> +		/* Drop the DATA_IN instruction if len is set to 0. */
> +		if (!len)
> +			op.ninstrs--;
> +
> +		return nand_exec_op(chip, &op);
> +	}
> +
>  	chip->cmdfunc(mtd, NAND_CMD_PARAM, page, -1);
>  	for (i = 0; i < len; i++)
>  		p[i] = chip->read_byte(mtd);

[...]

> +
>  /**
>   * nand_prog_page_begin_op - starts a PROG PAGE operation
>   * @chip: The NAND chip
> @@ -1371,7 +1608,7 @@ EXPORT_SYMBOL_GPL(nand_read_oob_op);
>   * Returns 0 for success or negative error code otherwise
>   */
>  int nand_prog_page_begin_op(struct nand_chip *chip, unsigned int page,
> -			    unsigned int column, const void *buf,
> +			    unsigned int offset_in_page, const void *buf,
>  			    unsigned int len)
>  {
>  	struct mtd_info *mtd = nand_to_mtd(chip);
> @@ -1379,10 +1616,14 @@ int nand_prog_page_begin_op(struct nand_chip *chip, unsigned int page,
>  	if (len && !buf)
>  		return -EINVAL;
>  
> -	if (column + len > mtd->writesize + mtd->oobsize)
> +	if (offset_in_page + len > mtd->writesize + mtd->oobsize)
>  		return -EINVAL;
>  
> -	chip->cmdfunc(mtd, NAND_CMD_SEQIN, column, page);
> +	if (chip->exec_op)
> +		return nand_exec_prog_page_op(
> +			chip, page, offset_in_page, buf, len, false);

Param alignment please.

> +
> +	chip->cmdfunc(mtd, NAND_CMD_SEQIN, offset_in_page, page);
>  
>  	if (buf)
>  		chip->write_buf(mtd, buf, len);
> @@ -1405,6 +1646,19 @@ int nand_prog_page_end_op(struct nand_chip *chip)
>  	struct mtd_info *mtd = nand_to_mtd(chip);
>  	int status;
>  
> +	if (chip->exec_op) {
> +		const struct nand_sdr_timings *sdr =
> +			nand_get_sdr_timings(&chip->data_interface);
> +		struct nand_op_instr instrs[] = {
> +			NAND_OP_CMD(NAND_CMD_PAGEPROG, sdr->tWB_max),
> +			NAND_OP_WAIT_RDY(sdr->tPROG_max, 0),
> +		};
> +		struct nand_operation op =
> +			NAND_OPERATION(instrs);
> +
> +		return nand_exec_op(chip, &op);
> +	}
> +
>  	chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1);
>  
>  	status = chip->waitfunc(mtd, chip);
> @@ -1429,7 +1683,8 @@ EXPORT_SYMBOL_GPL(nand_prog_page_end_op);
>   * Returns 0 for success or negative error code otherwise
>   */
>  int nand_prog_page_op(struct nand_chip *chip, unsigned int page,
> -		      unsigned int column, const void *buf, unsigned int len)
> +		      unsigned int offset_in_page, const void *buf,
> +		      unsigned int len)
>  {
>  	struct mtd_info *mtd = nand_to_mtd(chip);
>  	int status;
> @@ -1437,10 +1692,14 @@ int nand_prog_page_op(struct nand_chip *chip, unsigned int page,
>  	if (!len || !buf)
>  		return -EINVAL;
>  
> -	if (column + len > mtd->writesize + mtd->oobsize)
> +	if (offset_in_page + len > mtd->writesize + mtd->oobsize)
>  		return -EINVAL;
>  
> -	chip->cmdfunc(mtd, NAND_CMD_SEQIN, column, page);
> +	if (chip->exec_op)
> +		return nand_exec_prog_page_op(
> +			chip, page, offset_in_page, buf, len, true);
> +
> +	chip->cmdfunc(mtd, NAND_CMD_SEQIN, offset_in_page, page);
>  	chip->write_buf(mtd, buf, len);
>  	chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1);
>  
> @@ -1465,7 +1724,8 @@ EXPORT_SYMBOL_GPL(nand_prog_page_op);
>   *
>   * Returns 0 for success or negative error code otherwise
>   */
> -int nand_change_write_column_op(struct nand_chip *chip, unsigned int column,
> +int nand_change_write_column_op(struct nand_chip *chip,
> +				unsigned int offset_in_page,
>  				const void *buf, unsigned int len,
>  				bool force_8bit)
>  {
> @@ -1474,10 +1734,38 @@ int nand_change_write_column_op(struct nand_chip *chip, unsigned int column,
>  	if (len && !buf)
>  		return -EINVAL;
>  
> -	if (column + len > mtd->writesize + mtd->oobsize)
> +	if (offset_in_page + len > mtd->writesize + mtd->oobsize)
>  		return -EINVAL;
>  
> -	chip->cmdfunc(mtd, NAND_CMD_RNDIN, column, -1);
> +	/* Small page NANDs do not support column change. */
> +	if (mtd->writesize <= 512)
> +		return -ENOTSUPP;
> +
> +	if (chip->exec_op) {
> +		const struct nand_sdr_timings *sdr =
> +			nand_get_sdr_timings(&chip->data_interface);
> +		u8 addrs[2];
> +		struct nand_op_instr instrs[] = {
> +			NAND_OP_CMD(NAND_CMD_RNDIN, 0),
> +			NAND_OP_ADDR(2, addrs, sdr->tCCS_min),
> +			NAND_OP_DATA_OUT(len, buf, 0),
> +		};
> +		struct nand_operation op =
> +			NAND_OPERATION(instrs);
> +
> +		if (nand_fill_column_cycles(chip, addrs, offset_in_page) < 0)
> +			return -EINVAL;
> +
> +		instrs[2].data.force_8bit = force_8bit;
> +
> +		/* Drop the DATA_OUT instruction if len is set to 0. */
> +		if (!len)
> +			op.ninstrs--;
> +
> +		return nand_exec_op(chip, &op);
> +	}
> +
> +	chip->cmdfunc(mtd, NAND_CMD_RNDIN, offset_in_page, -1);
>  	if (len)
>  		chip->write_buf(mtd, buf, len);
>  
> @@ -1508,6 +1796,24 @@ int nand_readid_op(struct nand_chip *chip, u8 addr,
>  	if (!len || !buf)
>  		return -EINVAL;
>  
> +	if (chip->exec_op) {
> +		const struct nand_sdr_timings *sdr =
> +			nand_get_sdr_timings(&chip->data_interface);
> +		struct nand_op_instr instrs[] = {
> +			NAND_OP_CMD(NAND_CMD_READID, 0),
> +			NAND_OP_ADDR(1, &addr, sdr->tADL_min),
> +			NAND_OP_8BIT_DATA_IN(len, buf, 0),
> +		};
> +		struct nand_operation op =
> +			NAND_OPERATION(instrs);
> +
> +		/* Drop the DATA_IN instruction if len is set to 0. */
> +		if (!len)
> +			op.ninstrs--;
> +
> +		return nand_exec_op(chip, &op);
> +	}
> +
>  	chip->cmdfunc(mtd, NAND_CMD_READID, addr, -1);
>  
>  	for (i = 0; i < len; i++)
> @@ -1532,6 +1838,22 @@ int nand_status_op(struct nand_chip *chip, u8 *status)
>  {
>  	struct mtd_info *mtd = nand_to_mtd(chip);
>  
> +	if (chip->exec_op) {
> +		const struct nand_sdr_timings *sdr =
> +			nand_get_sdr_timings(&chip->data_interface);
> +		struct nand_op_instr instrs[] = {
> +			NAND_OP_CMD(NAND_CMD_STATUS, sdr->tADL_min),
> +			NAND_OP_8BIT_DATA_IN(1, status, 0),
> +		};
> +		struct nand_operation op =
> +			NAND_OPERATION(instrs);
> +
> +		if (!status)
> +			op.ninstrs--;
> +
> +		return nand_exec_op(chip, &op);
> +	}
> +
>  	chip->cmdfunc(mtd, NAND_CMD_STATUS, -1, -1);
>  	if (status)
>  		*status = chip->read_byte(mtd);
> @@ -1558,6 +1880,25 @@ int nand_erase_op(struct nand_chip *chip, unsigned int eraseblock)
>  			    (chip->phys_erase_shift - chip->page_shift);
>  	int status;
>  
> +	if (chip->exec_op) {
> +		const struct nand_sdr_timings *sdr =
> +			nand_get_sdr_timings(&chip->data_interface);
> +		u8 addrs[3] = {	page, page >> 8, page >> 16 };
> +		struct nand_op_instr instrs[] = {
> +			NAND_OP_CMD(NAND_CMD_ERASE1, 0),
> +			NAND_OP_ADDR(2, addrs, 0),
> +			NAND_OP_CMD(NAND_CMD_ERASE2, sdr->tWB_max),
> +			NAND_OP_WAIT_RDY(sdr->tBERS_max, 0),
> +		};
> +		struct nand_operation op =
> +			NAND_OPERATION(instrs);
> +
> +		if (chip->options & NAND_ROW_ADDR_3)
> +			instrs[1].addr.naddrs++;
> +
> +		return nand_exec_op(chip, &op);
> +	}
> +
>  	chip->cmdfunc(mtd, NAND_CMD_ERASE1, -1, page);
>  	chip->cmdfunc(mtd, NAND_CMD_ERASE2, -1, -1);
>  
> @@ -1591,6 +1932,22 @@ static int nand_set_features_op(struct nand_chip *chip, u8 feature,
>  	const u8 *params = data;
>  	int i, status;
>  
> +	if (chip->exec_op) {
> +		const struct nand_sdr_timings *sdr =
> +			nand_get_sdr_timings(&chip->data_interface);
> +		struct nand_op_instr instrs[] = {
> +			NAND_OP_CMD(NAND_CMD_SET_FEATURES, 0),
> +			NAND_OP_ADDR(1, &feature, sdr->tADL_min),
> +			NAND_OP_8BIT_DATA_OUT(ONFI_SUBFEATURE_PARAM_LEN, data,
> +					      sdr->tWB_max),
> +			NAND_OP_WAIT_RDY(sdr->tFEAT_max, 0),
> +		};
> +		struct nand_operation op =
> +			NAND_OPERATION(instrs);
> +
> +		return nand_exec_op(chip, &op);
> +	}
> +
>  	chip->cmdfunc(mtd, NAND_CMD_SET_FEATURES, feature, -1);
>  	for (i = 0; i < ONFI_SUBFEATURE_PARAM_LEN; ++i)
>  		chip->write_byte(mtd, params[i]);
> @@ -1621,6 +1978,22 @@ static int nand_get_features_op(struct nand_chip *chip, u8 feature,
>  	u8 *params = data;
>  	int i;
>  
> +	if (chip->exec_op) {
> +		const struct nand_sdr_timings *sdr =
> +			nand_get_sdr_timings(&chip->data_interface);
> +		struct nand_op_instr instrs[] = {
> +			NAND_OP_CMD(NAND_CMD_GET_FEATURES, 0),
> +			NAND_OP_ADDR(1, &feature, sdr->tWB_max),
> +			NAND_OP_WAIT_RDY(sdr->tFEAT_max, sdr->tRR_min),
> +			NAND_OP_8BIT_DATA_IN(ONFI_SUBFEATURE_PARAM_LEN,
> +					     data, 0),
> +		};
> +		struct nand_operation op =
> +			NAND_OPERATION(instrs);
> +
> +		return nand_exec_op(chip, &op);
> +	}
> +
>  	chip->cmdfunc(mtd, NAND_CMD_GET_FEATURES, feature, -1);
>  	for (i = 0; i < ONFI_SUBFEATURE_PARAM_LEN; ++i)
>  		params[i] = chip->read_byte(mtd);
> @@ -1642,6 +2015,19 @@ int nand_reset_op(struct nand_chip *chip)
>  {
>  	struct mtd_info *mtd = nand_to_mtd(chip);
>  
> +	if (chip->exec_op) {
> +		const struct nand_sdr_timings *sdr =
> +			nand_get_sdr_timings(&chip->data_interface);
> +		struct nand_op_instr instrs[] = {
> +			NAND_OP_CMD(NAND_CMD_RESET, sdr->tWB_max),
> +			NAND_OP_WAIT_RDY(sdr->tRST_max, 0),
> +		};
> +		struct nand_operation op =
> +			NAND_OPERATION(instrs);
> +
> +		return nand_exec_op(chip, &op);
> +	}
> +
>  	chip->cmdfunc(mtd, NAND_CMD_RESET, -1, -1);
>  
>  	return 0;
> @@ -1669,6 +2055,18 @@ int nand_read_data_op(struct nand_chip *chip, void *buf, unsigned int len,
>  	if (!len || !buf)
>  		return -EINVAL;
>  
> +	if (chip->exec_op) {
> +		struct nand_op_instr instrs[] = {
> +			NAND_OP_DATA_IN(len, buf, 0),
> +		};
> +		struct nand_operation op =
> +			NAND_OPERATION(instrs);
> +
> +		instrs[0].data.force_8bit = force_8bit;
> +
> +		return nand_exec_op(chip, &op);
> +	}
> +
>  	if (force_8bit) {
>  		u8 *p = buf;
>  		unsigned int i;
> @@ -1704,6 +2102,18 @@ int nand_write_data_op(struct nand_chip *chip, const void *buf,
>  	if (!len || !buf)
>  		return -EINVAL;
>  
> +	if (chip->exec_op) {
> +		struct nand_op_instr instrs[] = {
> +			NAND_OP_DATA_OUT(len, buf, 0),
> +		};
> +		struct nand_operation op =
> +			NAND_OPERATION(instrs);
> +
> +		instrs[0].data.force_8bit = force_8bit;
> +
> +		return nand_exec_op(chip, &op);
> +	}
> +
>  	if (force_8bit) {
>  		const u8 *p = buf;
>  		unsigned int i;
> @@ -1719,6 +2129,471 @@ int nand_write_data_op(struct nand_chip *chip, const void *buf,
>  EXPORT_SYMBOL_GPL(nand_write_data_op);
>  
>  /**
> + * struct nand_op_parser_ctx - Context used by the parser
> + * @instrs: array of all the instructions that must be addressed
> + * @ninstrs: length of the @instrs array
> + * @instr_idx: index of the instruction in the @instrs array that matches the
> + *	       first instruction of the subop structure
> + * @instr_start_off: offset at which the first instruction of the subop
> + *		     structure must start if it is and address or a data
> + *		     instruction
> + *
> + * This structure is used by the core to handle splitting lengthy instructions
> + * into sub-operations.
> + */
> +struct nand_op_parser_ctx {
> +	const struct nand_op_instr *instrs;
> +	unsigned int ninstrs;
> +	unsigned int instr_idx;
> +	unsigned int instr_start_off;
> +	struct nand_subop subop;
> +};
> +
> +/**
> + * nand_op_parser_must_split_instr - Checks if an instruction must be split
> + * @pat: the parser pattern that match
> + * @instr: the instruction array to check
> + * @start_offset: the offset from which to start in the first instruction of the
> + *		  @instr array
> + *
> + * Some NAND controllers are limited and cannot send X address cycles with a
> + * unique operation, or cannot read/write more than Y bytes at the same time.
> + * In this case, reduce the scope of this set of operation, and trigger another
> + * instruction with the rest.

"
In this case, split the instruction that does not fit in a single
controller-operation into two or more chunks.
"

> + *
> + * Returns true if the array of instruction must be split, false otherwise.

s/array of//

> + * The @start_offset parameter is also updated to the offset at which the first
> + * instruction of the next bundle of instruction must start (if an address or a

"
The @start_offset parameter is also updated to the offset at which the
the next bundle of instructions must start ...
"

> + * data instruction).
> + */
> +static bool
> +nand_op_parser_must_split_instr(const struct nand_op_parser_pattern_elem *pat,
> +				const struct nand_op_instr *instr,
> +				unsigned int *start_offset)
> +{
> +	switch (pat->type) {
> +	case NAND_OP_ADDR_INSTR:
> +		if (!pat->addr.maxcycles)
> +			break;
> +
> +		if (instr->addr.naddrs - *start_offset > pat->addr.maxcycles) {
> +			*start_offset += pat->addr.maxcycles;
> +			return true;
> +		}
> +		break;
> +
> +	case NAND_OP_DATA_IN_INSTR:
> +	case NAND_OP_DATA_OUT_INSTR:
> +		if (!pat->data.maxlen)
> +			break;
> +
> +		if (instr->data.len - *start_offset > pat->data.maxlen) {
> +			*start_offset += pat->data.maxlen;
> +			return true;
> +		}
> +		break;
> +
> +	default:
> +		break;
> +	}
> +
> +	return false;
> +}
> +
> +/**
> + * nand_op_parser_match_pat - Checks a pattern
> + * @pat: the parser pattern to check if it matches
> + * @ctx: the context structure to match with the pattern @pat
> + *
> + * Check if *one* given pattern matches the given sequence of instructions
> + */
> +static bool
> +nand_op_parser_match_pat(const struct nand_op_parser_pattern *pat,
> +			 struct nand_op_parser_ctx *ctx)
> +{
> +	unsigned int i, j, boundary_off = ctx->instr_start_off;
> +
> +	ctx->subop.ninstrs = 0;
> +
> +	for (i = ctx->instr_idx, j = 0; i < ctx->ninstrs && j < pat->nelems;) {
> +		const struct nand_op_instr *instr = &ctx->instrs[i];
> +
> +		/*
> +		 * The pattern instruction does not match the operation
> +		 * instruction. If the instruction is marked optional in the
> +		 * pattern definition, we skip the pattern element and continue
> +		 * to the next one. If the element is mandatory, there's no
> +		 * match and we can return false directly.
> +		 */
> +		if (instr->type != pat->elems[j].type) {
> +			if (!pat->elems[j].optional)
> +				return false;
> +
> +			j++;
> +			continue;
> +		}
> +
> +		/*
> +		 * Now check the pattern element constraints. If the pattern is
> +		 * not able to handle the whole instruction in a single step,
> +		 * we'll have to break it down into several instructions.
> +		 * The *boudary_off value comes back updated to point to the
> +		 * limit between the split instruction (the end of the original
> +		 * chunk, the start of new next one).
> +		 */
> +		if (nand_op_parser_must_split_instr(&pat->elems[j], instr,
> +						    &boundary_off)) {
> +			ctx->subop.ninstrs++;
> +			j++;
> +			break;
> +		}
> +
> +		ctx->subop.ninstrs++;
> +		i++;
> +		j++;
> +		boundary_off = 0;
> +	}
> +
> +	/*
> +	 * This can happen if all instructions of a pattern are optional.
> +	 * Still, if there's not at least one instruction handled by this
> +	 * pattern, this is not a match, and we should try the next one (if
> +	 * any).
> +	 */
> +	if (!ctx->subop.ninstrs)
> +		return false;
> +
> +	/*
> +	 * We had a match on the pattern head, but the pattern may be longer
> +	 * than the instructions we're asked to execute. We need to make sure
> +	 * there's no mandatory elements in the pattern tail.
> +	 *
> +	 * The case where all the operations of a pattern have been checked but
> +	 * the number of instructions is bigger is handled right after this by
> +	 * returning true on the pattern match, which will order the execution
> +	 * of the subset of instructions later defined, while updating the
> +	 * context ids to the next chunk of instructions.
> +	 */
> +	for (; j < pat->nelems; j++) {
> +		if (!pat->elems[j].optional)
> +			return false;
> +	}
> +
> +	/*
> +	 * We have a match: update the ctx and return true. The subop structure
> +	 * will be used by the pattern's ->exec() function.
> +	 */
> +	ctx->subop.instrs = &ctx->instrs[ctx->instr_idx];
> +	ctx->subop.first_instr_start_off = ctx->instr_start_off;
> +	ctx->subop.last_instr_end_off = boundary_off;
> +
> +	/*
> +	 * Update the pointers so the calling function will be able to recall
> +	 * this one with a new subset of instructions.
> +	 *
> +	 * In the case where the last operation of this set is split, point to
> +	 * the last unfinished job, knowing the starting offset.
> +	 */
> +	ctx->instr_idx = i;
> +	ctx->instr_start_off = boundary_off;
> +
> +	return true;
> +}
> +
> +#if IS_ENABLED(CONFIG_DYNAMIC_DEBUG) || defined(DEBUG)
> +static void nand_op_parser_trace(const struct nand_op_parser_ctx *ctx,
> +				 bool success)
> +{
> +	const struct nand_op_instr *instr;
> +	bool in_subop = false;
> +	char *is_in =  "    ->", *is_out = "      ", *prefix;
> +	char *buf;
> +	unsigned int len, off = 0;
> +	int i, j;
> +
> +	if (success)
> +		pr_debug("executing subop:\n");
> +	else
> +		pr_debug("pattern not found:\n");
> +
> +	for (i = 0; i < ctx->ninstrs; i++) {
> +		instr = &ctx->instrs[i];
> +
> +		/*
> +		 * ctx->instr_idx is not reliable because it may already had

									 have

> +		 * been updated by the parser. Use pointers comparison instead.
> +		 */
> +		if (instr == &ctx->subop.instrs[0])
> +			in_subop = true;

It seems in_subop is only used to select the prefix, so you can just
get rid of it and do

		if (instr == &ctx->subop.instrs[0])
			prefix = "    ->";

BTW, if success is false, you should not even try to change the prefix,
right?

> +
> +		if (in_subop)
> +			prefix = is_in;
> +		else
> +			prefix = is_out;
> +
> +		switch (instr->type) {
> +		case NAND_OP_CMD_INSTR:
> +			pr_debug("%sCMD      [0x%02x]\n", prefix,
> +				 instr->cmd.opcode);
> +			break;
> +		case NAND_OP_ADDR_INSTR:
> +			/*
> +			 * A log line is much less than 50 bytes, plus 5 bytes
> +			 * per address cycle to display.
> +			 */
> +			len = 50 + 5 * instr->addr.naddrs;
> +			buf = kmalloc(len, GFP_KERNEL);
> +			if (!buf)
> +				return;
> +			memset(buf, 0, len);
> +			off += snprintf(buf, len, "ADDR     [%d cyc:",
> +					instr->addr.naddrs);
> +			for (j = 0; j < instr->addr.naddrs; j++)
> +				off += snprintf(&buf[off], len - off, " 0x%02x",
> +						instr->addr.addrs[j]);
> +			pr_debug("%s%s]\n", prefix, buf);
> +			break;
> +		case NAND_OP_DATA_IN_INSTR:
> +			pr_debug("%sDATA_IN  [%d B%s]\n", prefix,
> +				 instr->data.len,
> +				 instr->data.force_8bit ? ", force 8-bit" : "");
> +			break;
> +		case NAND_OP_DATA_OUT_INSTR:
> +			pr_debug("%sDATA_OUT [%d B%s]\n", prefix,
> +				 instr->data.len,
> +				 instr->data.force_8bit ? ", force 8-bit" : "");
> +			break;
> +		case NAND_OP_WAITRDY_INSTR:
> +			pr_debug("%sWAITRDY  [max %d ms]\n", prefix,
> +				 instr->waitrdy.timeout_ms);
> +			break;
> +		}
> +
> +		if (instr == &ctx->subop.instrs[ctx->subop.ninstrs - 1])
> +			in_subop = false;

		if (instr == &ctx->subop.instrs[ctx->subop.ninstrs - 1])
			prefix = "      ";

and initialize prefix to "      " at the beginning of the function.

> +	}
> +}
> +#else
> +static void nand_op_parser_trace(const struct nand_op_parser_ctx *ctx,
> +				 bool success)
> +{
> +	/* NOP */
> +}
> +#endif
> +
> +/**
> + * nand_op_parser_exec_op - exec_op parser
> + * @chip: the NAND chip
> + * @parser: the parser to use given by the controller driver
> + * @op: the NAND operation to address
> + * @check_only: flag asking if the entire operation could be handled
> + *
> + * Function that must be called by each driver that implement the "exec_op API"
> + * in their own ->exec_op() implementation.
> + *
> + * The function iterates on all the instructions asked and make use of internal
> + * parsers to find matches between the instruction list and the handled patterns
> + * filled by the controller drivers inside the @parser structure. If needed, the
> + * instructions could be split into sub-operations and be executed sequentially.
> + */
> +int nand_op_parser_exec_op(struct nand_chip *chip,
> +			   const struct nand_op_parser *parser,
> +			   const struct nand_operation *op, bool check_only)
> +{
> +	struct nand_op_parser_ctx ctx = {
> +		.instrs = op->instrs,
> +		.ninstrs = op->ninstrs,
> +	};
> +	unsigned int i;
> +
> +	while (ctx.instr_idx < op->ninstrs) {
> +		bool pattern_found = false;
> +		int ret;
> +
> +		for (i = 0; i < parser->npatterns; i++) {
> +			const struct nand_op_parser_pattern *pattern;
> +
> +			pattern = &parser->patterns[i];
> +			if (!nand_op_parser_match_pat(pattern, &ctx))
> +				continue;
> +
> +			nand_op_parser_trace(&ctx, true);
> +

pattern_found should be set to true here. But I'm not even sure you
really need this variable.

> +			if (check_only)
> +				break;
> +
> +			ret = pattern->exec(chip, &ctx.subop);
> +			if (ret)
> +				return ret;
> +
> +			pattern_found = true;
> +		}
> +
> +		if (!pattern_found) {

Just test

		if (i == parser->npatterns) {

and you should be good.

> +			nand_op_parser_trace(&ctx, false);
> +			return -ENOTSUPP;
> +		}
> +	}
> +
> +	return 0;
> +}
> +EXPORT_SYMBOL_GPL(nand_op_parser_exec_op);
> +
> +static bool nand_instr_is_data(const struct nand_op_instr *instr)
> +{
> +	return instr && (instr->type == NAND_OP_DATA_IN_INSTR ||
> +			 instr->type == NAND_OP_DATA_OUT_INSTR);
> +}
> +
> +static bool nand_subop_instr_is_valid(const struct nand_subop *subop,
> +				      unsigned int op_id)

s/op_id/instr_idx/

> +{
> +	return subop && op_id < subop->ninstrs;
> +}
> +
> +static int nand_subop_get_start_off(const struct nand_subop *subop,
> +				    unsigned int op_id)

Ditto.

> +{
> +	if (op_id)
> +		return 0;
> +
> +	return subop->first_instr_start_off;
> +}
> +
> +/**
> + * nand_subop_get_addr_start_off - Get the start offset in an address array
> + * @subop: The entire sub-operation
> + * @op_id: Index of the instruction inside the sub-operation

instr_idx.

> + *
> + * Instructions arrays may be split by the parser between instructions,
> + * and also in the middle of an address instruction if the number of cycles
> + * to assert in one operation is not supported by the controller.
> + *
> + * For this, instead of using the first index of the ->addr.addrs field from the
> + * address instruction, the NAND controller driver must use this helper that
> + * will either return 0 if the index does not point to the first instruction of
> + * the sub-operation, or the offset of the next starting offset inside the
> + * address cycles.

I think you can drop this paragraph which IMO brings more confusion to
what this function does.

> + *
> + * Returns the offset of the first address cycle to assert from the pointed
> + * address instruction.

This part is definitely clearer.

> + */
> +int nand_subop_get_addr_start_off(const struct nand_subop *subop,
> +				  unsigned int op_id)
> +{
> +	if (!nand_subop_instr_is_valid(subop, op_id) ||
> +	    subop->instrs[op_id].type != NAND_OP_ADDR_INSTR)
> +		return -EINVAL;
> +
> +	return nand_subop_get_start_off(subop, op_id);
> +}
> +EXPORT_SYMBOL_GPL(nand_subop_get_addr_start_off);
> +
> +/**
> + * nand_subop_get_num_addr_cyc - Get the remaining address cycles to assert
> + * @subop: The entire sub-operation
> + * @op_id: Index of the instruction inside the sub-operation

instr_idx

> + *
> + * Instructions arrays may be split by the parser between instructions,
> + * and also in the middle of an address instruction if the number of cycles
> + * to assert in one operation is not supported by the controller.
> + *
> + * For this, instead of using the ->addr.naddrs fields of the address
> + * instruction, the NAND controller driver must use this helper that will
> + * return the actual number of cycles to assert between the first and last
> + * offset asked for this particular instruction.
> + *
> + * Returns the number of address cycles to assert from the pointed address
> + * instruction.
> + */
> +int nand_subop_get_num_addr_cyc(const struct nand_subop *subop,
> +				unsigned int op_id)
> +{
> +	int start_off, end_off;
> +
> +	if (!nand_subop_instr_is_valid(subop, op_id) ||
> +	    subop->instrs[op_id].type != NAND_OP_ADDR_INSTR)
> +		return -EINVAL;
> +
> +	start_off = nand_subop_get_addr_start_off(subop, op_id);
> +
> +	if (op_id == subop->ninstrs - 1 &&
> +	    subop->last_instr_end_off)
> +		end_off = subop->last_instr_end_off;
> +	else
> +		end_off = subop->instrs[op_id].addr.naddrs;
> +
> +	return end_off - start_off;
> +}
> +EXPORT_SYMBOL_GPL(nand_subop_get_num_addr_cyc);
> +
> +/**
> + * nand_subop_get_data_start_off - Get the start offset in a data array
> + * @subop: The entire sub-operation
> + * @op_id: Index of the instruction inside the sub-operation
> + *
> + * Instructions arrays may be split by the parser between instructions,
> + * and also in the middle of a data instruction if the number of bytes to access
> + * in one operation is greater that the controller limit.
> + *
> + * For this, instead of using the first index of the ->data.buf field from the
> + * data instruction, the NAND controller driver must use this helper that
> + * will either return 0 if the index does not point to the first instruction of
> + * the sub-operation, or the offset of the next starting offset inside the
> + * data buffer.

Same as for nand_subop_get_addr_start_off(), the explanation is
confusing. I think we can drop it.

> + *
> + * Returns the data offset inside the pointed data instruction buffer from which
> + * to start.
> + */
> +int nand_subop_get_data_start_off(const struct nand_subop *subop,
> +				  unsigned int op_id)
> +{
> +	if (!nand_subop_instr_is_valid(subop, op_id) ||
> +	    !nand_instr_is_data(&subop->instrs[op_id]))
> +		return -EINVAL;
> +
> +	return nand_subop_get_start_off(subop, op_id);
> +}
> +EXPORT_SYMBOL_GPL(nand_subop_get_data_start_off);
> +
> +/**
> + * nand_subop_get_data_len - Get the number of bytes to retrieve
> + * @subop: The entire sub-operation
> + * @op_id: Index of the instruction inside the sub-operation
> + *
> + * Instructions arrays may be split by the parser between instructions,
> + * and also in the middle of a data instruction if the number of bytes to access
> + * in one operation is greater that the controller limit.
> + *
> + * For this, instead of using the ->data.len field from the data instruction,
> + * the NAND controller driver must use this helper that will return the actual
> + * length of data to move between the first and last offset asked for this
> + * particular instruction.
> + *
> + * Returns the length of the data to move from the pointed data instruction.
> + */
> +int nand_subop_get_data_len(const struct nand_subop *subop,
> +			    unsigned int op_id)
> +{
> +	int start_off = 0, end_off;
> +
> +	if (!nand_subop_instr_is_valid(subop, op_id) ||
> +	    !nand_instr_is_data(&subop->instrs[op_id]))
> +		return -EINVAL;
> +
> +	start_off = nand_subop_get_data_start_off(subop, op_id);
> +
> +	if (op_id == subop->ninstrs - 1 &&
> +	    subop->last_instr_end_off)
> +		end_off = subop->last_instr_end_off;
> +	else
> +		end_off = subop->instrs[op_id].data.len;
> +
> +	return end_off - start_off;
> +}
> +EXPORT_SYMBOL_GPL(nand_subop_get_data_len);
> +
> +/**
>   * nand_reset - Reset and initialize a NAND device
>   * @chip: The NAND chip
>   * @chipnr: Internal die id
> @@ -3940,7 +4815,7 @@ static void nand_set_defaults(struct nand_chip *chip)
>  		chip->chip_delay = 20;
>  
>  	/* check, if a user supplied command function given */
> -	if (chip->cmdfunc == NULL)
> +	if (chip->cmdfunc == NULL && !chip->exec_op)
>  		chip->cmdfunc = nand_command;
>  
>  	/* check, if a user supplied wait function given */
> @@ -4823,15 +5698,35 @@ int nand_scan_ident(struct mtd_info *mtd, int maxchips,
>  	if (!mtd->name && mtd->dev.parent)
>  		mtd->name = dev_name(mtd->dev.parent);
>  
> -	if ((!chip->cmdfunc || !chip->select_chip) && !chip->cmd_ctrl) {
> +	/*
> +	 * ->cmdfunc() is legacy and will only be used if ->exec_op() is not
> +	 * populated.
> +	 */
> +	if (chip->exec_op) {
>  		/*
> -		 * Default functions assigned for chip_select() and
> -		 * cmdfunc() both expect cmd_ctrl() to be populated,
> -		 * so we need to check that that's the case
> +		 * The implementation of ->exec_op() heavily relies on timings
> +		 * to be accessible through the nand_data_interface structure.
> +		 * Thus, the ->setup_data_interface() hook must be provided. The
> +		 * controller driver will be noticed of delays it must apply
> +		 * after each ->exec_op() instruction (if any) through the
> +		 * .delay_ns field. The driver will then choose to handle the
> +		 * delays manually if the controller cannot do it natively.
>  		 */
> -		pr_err("chip.cmd_ctrl() callback is not provided");
> -		return -EINVAL;
> +		if (!chip->setup_data_interface) {
> +			pr_err("->setup_data_interface() should be provided\n");
> +			return -EINVAL;
> +		}
> +	} else {
> +		/*
> +		 * Default functions assigned for ->cmdfunc() and
> +		 * ->select_chip() both expect ->cmd_ctrl() to be populated.
> +		 */
> +		if ((!chip->cmdfunc || !chip->select_chip) && !chip->cmd_ctrl) {
> +			pr_err("->cmd_ctrl() should be provided\n");
> +			return -EINVAL;
> +		}
>  	}
> +
>  	/* Set the default functions */
>  	nand_set_defaults(chip);
>  
> diff --git a/drivers/mtd/nand/nand_hynix.c b/drivers/mtd/nand/nand_hynix.c
> index 04e3ab7a476c..81c382f24513 100644
> --- a/drivers/mtd/nand/nand_hynix.c
> +++ b/drivers/mtd/nand/nand_hynix.c
> @@ -74,19 +74,36 @@ static bool hynix_nand_has_valid_jedecid(struct nand_chip *chip)
>  	return !strncmp("JEDEC", jedecid, sizeof(jedecid));
>  }
>  
> +static int hynix_nand_cmd_op(struct nand_chip *chip, u8 cmd)

Maybe you can introduce the helper in patch 1

> +{
> +	struct mtd_info *mtd = nand_to_mtd(chip);
> +
> +	if (chip->exec_op) {
> +		struct nand_op_instr instrs[] = {
> +			NAND_OP_CMD(cmd, 0),
> +		};
> +		struct nand_operation op = NAND_OPERATION(instrs);
> +
> +		return nand_exec_op(chip, &op);

And then ass this block of code in this patch.

> +	}
> +
> +	chip->cmdfunc(mtd, cmd, -1, -1);
> +
> +	return 0;
> +}
> +
>  static int hynix_nand_setup_read_retry(struct mtd_info *mtd, int retry_mode)
>  {
>  	struct nand_chip *chip = mtd_to_nand(mtd);
>  	struct hynix_nand *hynix = nand_get_manufacturer_data(chip);
>  	const u8 *values;
> -	int status;
>  	int i;
>  
>  	values = hynix->read_retry->values +
>  		 (retry_mode * hynix->read_retry->nregs);
>  
>  	/* Enter 'Set Hynix Parameters' mode */
> -	chip->cmdfunc(mtd, NAND_HYNIX_CMD_SET_PARAMS, -1, -1);
> +	hynix_nand_cmd_op(chip, NAND_HYNIX_CMD_SET_PARAMS);
>  
>  	/*
>  	 * Configure the NAND in the requested read-retry mode.
> @@ -101,16 +118,17 @@ static int hynix_nand_setup_read_retry(struct mtd_info *mtd, int retry_mode)
>  		int column = hynix->read_retry->regs[i];
>  
>  		column |= column << 8;
> -		chip->cmdfunc(mtd, NAND_CMD_NONE, column, -1);
> -		chip->write_byte(mtd, values[i]);
> +		if (chip->exec_op) {
> +			nand_change_write_column_op(chip, column,
> +						    &values[i], 1, true);

This is not a nand_change_write_column_op() op, here the cmd cycle is
set to NAND_CMD_NONE. You should have your own operation defined to
handle this sequence.

> +		} else {
> +			chip->cmdfunc(mtd, NAND_CMD_NONE, column, -1);
> +			chip->write_byte(mtd, values[i]);
> +		}
>  	}
>  
>  	/* Apply the new settings. */
> -	chip->cmdfunc(mtd, NAND_HYNIX_CMD_APPLY_PARAMS, -1, -1);
> -
> -	status = chip->waitfunc(mtd, chip);
> -	if (status & NAND_STATUS_FAIL)
> -		return -EIO;
> +	hynix_nand_cmd_op(chip, NAND_HYNIX_CMD_APPLY_PARAMS);

No, it's wrong, you miss the WAITRDY instruction to be compatible
with what was done before.

>  
>  	return 0;
>  }
> @@ -173,32 +191,65 @@ static int hynix_read_rr_otp(struct nand_chip *chip,
>  
>  	nand_reset_op(chip);
>  
> -	chip->cmdfunc(mtd, NAND_HYNIX_CMD_SET_PARAMS, -1, -1);
> +	hynix_nand_cmd_op(chip, NAND_HYNIX_CMD_SET_PARAMS);
>  
>  	for (i = 0; i < info->nregs; i++) {
>  		int column = info->regs[i];
>  
>  		column |= column << 8;
> -		chip->cmdfunc(mtd, NAND_CMD_NONE, column, -1);
> -		chip->write_byte(mtd, info->values[i]);
> +		if (chip->exec_op) {
> +			nand_change_write_column_op(chip, column,
> +						    &info->values[i], 1, true);
> +		} else {
> +			chip->cmdfunc(mtd, NAND_CMD_NONE, column, -1);
> +			chip->write_byte(mtd, info->values[i]);
> +		}

Same comments apply here.

>  	}
>  
> -	chip->cmdfunc(mtd, NAND_HYNIX_CMD_APPLY_PARAMS, -1, -1);
> +	hynix_nand_cmd_op(chip, NAND_HYNIX_CMD_APPLY_PARAMS);
>  
>  	/* Sequence to enter OTP mode? */
> -	chip->cmdfunc(mtd, 0x17, -1, -1);
> -	chip->cmdfunc(mtd, 0x04, -1, -1);
> -	chip->cmdfunc(mtd, 0x19, -1, -1);
> +	if (chip->exec_op) {
> +		struct nand_op_instr instrs[] = {
> +			NAND_OP_CMD(0x17, 0),
> +			NAND_OP_CMD(0x04, 0),
> +			NAND_OP_CMD(0x19, 0),
> +		};
> +		struct nand_operation op = NAND_OPERATION(instrs);
> +
> +		nand_exec_op(chip, &op);
> +	} else {
> +		chip->cmdfunc(mtd, 0x17, -1, -1);
> +		chip->cmdfunc(mtd, 0x04, -1, -1);
> +		chip->cmdfunc(mtd, 0x19, -1, -1);
> +	}

Why not creating a hynix_nand_enter_otp_mode_op() for that? 

>  
>  	/* Now read the page */
>  	nand_read_page_op(chip, info->page, 0, buf, info->size);
>  
>  	/* Put everything back to normal */
>  	nand_reset_op(chip);
> -	chip->cmdfunc(mtd, NAND_HYNIX_CMD_SET_PARAMS, 0x38, -1);
> -	chip->write_byte(mtd, 0x0);
> -	chip->cmdfunc(mtd, NAND_HYNIX_CMD_APPLY_PARAMS, -1, -1);
> -	chip->cmdfunc(mtd, NAND_CMD_READ0, 0x0, -1);
> +	if (chip->exec_op) {
> +		const struct nand_sdr_timings *sdr =
> +			nand_get_sdr_timings(&chip->data_interface);
> +		u8 byte = 0;
> +		u8 addr = 0x38;
> +		struct nand_op_instr instrs[] = {
> +			NAND_OP_CMD(NAND_HYNIX_CMD_SET_PARAMS, 0),
> +			NAND_OP_ADDR(1, &addr, sdr->tCCS_min),
> +			NAND_OP_8BIT_DATA_OUT(1, &byte, 0),
> +			NAND_OP_CMD(NAND_HYNIX_CMD_APPLY_PARAMS, 0),
> +			NAND_OP_CMD(NAND_CMD_READ0, 0),
> +		};
> +		struct nand_operation op = NAND_OPERATION(instrs);
> +
> +		nand_exec_op(chip, &op);
> +	} else {
> +		chip->cmdfunc(mtd, NAND_HYNIX_CMD_SET_PARAMS, 0x38, -1);
> +		chip->write_byte(mtd, 0x0);
> +		chip->cmdfunc(mtd, NAND_HYNIX_CMD_APPLY_PARAMS, -1, -1);
> +		chip->cmdfunc(mtd, NAND_CMD_READ0, 0x0, -1);
> +	}

Same here.

>  
>  	return 0;
>  }
> diff --git a/drivers/mtd/nand/nand_micron.c b/drivers/mtd/nand/nand_micron.c
> index 543352380ffa..109d8003e33d 100644
> --- a/drivers/mtd/nand/nand_micron.c
> +++ b/drivers/mtd/nand/nand_micron.c
> @@ -117,14 +117,38 @@ micron_nand_read_page_on_die_ecc(struct mtd_info *mtd, struct nand_chip *chip,
>  				 uint8_t *buf, int oob_required,
>  				 int page)
>  {
> -	int status;
> +	u8 status;
>  	int max_bitflips = 0;
>  
>  	micron_nand_on_die_ecc_setup(chip, true);
>  
> -	chip->cmdfunc(mtd, NAND_CMD_READ0, 0x00, page);
> -	chip->cmdfunc(mtd, NAND_CMD_STATUS, -1, -1);
> -	status = chip->read_byte(mtd);
> +	if (chip->exec_op) {
> +		u8 addrs[5] = {};
> +		struct nand_op_instr instrs[] = {
> +			NAND_OP_CMD(NAND_CMD_READ0, 0),
> +			NAND_OP_ADDR(4, addrs, 0),
> +			NAND_OP_CMD(NAND_CMD_STATUS, 0),
> +			NAND_OP_8BIT_DATA_IN(1, &status, 0),
> +		};
> +		struct nand_operation op = NAND_OPERATION(instrs);
> +
> +		if (nand_fill_column_cycles(chip, addrs, 0))

		if (nand_fill_column_cycles(chip, addrs, 0) < 0)

> +			return -EINVAL;
> +
> +		addrs[2] = page;
> +		addrs[3] = page >> 8;
> +		if (chip->options & NAND_ROW_ADDR_3) {
> +			addrs[4] = page >> 16;
> +			instrs[1].addr.naddrs++;
> +		}
> +
> +		nand_exec_op(chip, &op);
> +	} else {
> +		chip->cmdfunc(mtd, NAND_CMD_READ0, 0x00, page);
> +		chip->cmdfunc(mtd, NAND_CMD_STATUS, -1, -1);
> +		status = chip->read_byte(mtd);
> +	}
> +
>  	if (status & NAND_STATUS_FAIL)
>  		mtd->ecc_stats.failed++;
>  	/*
> diff --git a/include/linux/mtd/rawnand.h b/include/linux/mtd/rawnand.h
> index 1acc26ed0e91..302f231df65e 100644
> --- a/include/linux/mtd/rawnand.h
> +++ b/include/linux/mtd/rawnand.h
> @@ -751,6 +751,337 @@ struct nand_manufacturer_ops {
>  };
>  
>  /**
> + * struct nand_op_cmd_instr - Definition of a command instruction
> + * @opcode: the command to assert in one cycle
> + */
> +struct nand_op_cmd_instr {
> +	u8 opcode;
> +};
> +
> +/**
> + * struct nand_op_addr_instr - Definition of an address instruction
> + * @naddrs: length of the @addrs array
> + * @addrs: array containing the address cycles to assert
> + */
> +struct nand_op_addr_instr {
> +	unsigned int naddrs;
> +	const u8 *addrs;
> +};
> +
> +/**
> + * struct nand_op_data_instr - Definition of a data instruction
> + * @len: number of data bytes to move
> + * @in: buffer to fill when reading from the NAND chip
> + * @out: buffer to read from when writing to the NAND chip
> + * @force_8bit: force 8-bit access
> + *
> + * Please note that "in" and "out" are inverted from the ONFI specification
> + * and are from the controller perspective, so a "in" is a read from the NAND
> + * chip while a "out" is a write to the NAND chip.
> + */
> +struct nand_op_data_instr {
> +	unsigned int len;
> +	union {
> +		void *in;
> +		const void *out;
> +	};
> +	bool force_8bit;
> +};
> +
> +/**
> + * struct nand_op_waitrdy_instr - Definition of a wait ready instruction
> + * @timeout_ms: maximum delay while waiting for the ready/busy pin in ms
> + */
> +struct nand_op_waitrdy_instr {
> +	unsigned int timeout_ms;
> +};
> +
> +/**
> + * enum nand_op_instr_type - Enumeration of all the instructions

*of all instruction types

> + *
> + * Please note that data instructions are separated into DATA_IN and DATA_OUT
> + * instructions.
> + */
> +enum nand_op_instr_type {
> +	NAND_OP_CMD_INSTR,
> +	NAND_OP_ADDR_INSTR,
> +	NAND_OP_DATA_IN_INSTR,
> +	NAND_OP_DATA_OUT_INSTR,
> +	NAND_OP_WAITRDY_INSTR,
> +};
> +
> +/**
> + * struct nand_op_instr - Generic definition of and instruction

s/and/an/

> + * @type: an enumeration of the instruction type
> + * @cmd/@addr/@data/@waitrdy: the actual instruction to refer depending on the
> + *			      value of @type

"
extra data associated to the instruction. You'll have to use
the appropriate element depending on @type"

> + * @delay_ns: delay to apply by the controller after the instruction has been
> + *	      actually executed (most of them are directly handled by the
> + *	      controllers once the timings negociation has been done)
> + */
> +struct nand_op_instr {
> +	enum nand_op_instr_type type;
> +	union {
> +		struct nand_op_cmd_instr cmd;
> +		struct nand_op_addr_instr addr;
> +		struct nand_op_data_instr data;
> +		struct nand_op_waitrdy_instr waitrdy;
> +	};
> +	unsigned int delay_ns;
> +};
> +
> +/*
> + * Special handling must be done for the WAITRDY timeout parameter as it usually
> + * is either tPROG (after a prog), tR (before a read), tRST (during a reset) or
> + * tBERS (during an erase) which all of them are u64 values that cannot be
> + * divided by usual kernel macros and must be handled with the special
> + * DIV_ROUND_UP_ULL() macro.
> + */
> +#define PSEC_TO_NSEC(x) DIV_ROUND_UP(x, 10^3)
> +#define PSEC_TO_MSEC(x) DIV_ROUND_UP_ULL(x, 10^9)

Hm, that's a bit of a mess to let each macro decide which converter
they should use, because we don't know at this level whether the timing
is stored in an u64 or u32. How about doing the conversion in the
call-sites instead, because there you should know what you manipulate.

Something like

	NAND_OP_CMD(FOO, PSEC_TO_NSEC_UL(sdr->tXX))
	NAND_OP_WAITRDY(PSEC_TO_MSEC_ULL(sdr->tXX),
			PSEC_TO_NSEC_UL(sdr->tYY))



> +
> +#define NAND_OP_CMD(id, delay_ps)					\
> +	{								\
> +		.type = NAND_OP_CMD_INSTR,				\
> +		.cmd.opcode = id,					\
> +		.delay_ns = PSEC_TO_NSEC(delay_ps),			\
> +	}
> +
> +#define NAND_OP_ADDR(ncycles, cycles, delay_ps)				\
> +	{								\
> +		.type = NAND_OP_ADDR_INSTR,				\
> +		.addr = {						\
> +			.naddrs = ncycles,				\
> +			.addrs = cycles,				\
> +		},							\
> +		.delay_ns = PSEC_TO_NSEC(delay_ps),			\
> +	}
> +
> +#define NAND_OP_DATA_IN(l, buf, delay_ps)				\
> +	{								\
> +		.type = NAND_OP_DATA_IN_INSTR,				\
> +		.data = {						\
> +			.len = l,					\
> +			.in = buf,					\
> +			.force_8bit = false,				\
> +		},							\
> +		.delay_ns = PSEC_TO_NSEC(delay_ps),			\
> +	}
> +
> +#define NAND_OP_DATA_OUT(l, buf, delay_ps)				\
> +	{								\
> +		.type = NAND_OP_DATA_OUT_INSTR,				\
> +		.data = {						\
> +			.len = l,					\
> +			.out = buf,					\
> +			.force_8bit = false,				\
> +		},							\
> +		.delay_ns = PSEC_TO_NSEC(delay_ps),			\
> +	}
> +
> +#define NAND_OP_8BIT_DATA_IN(l, buf, delay_ps)				\
> +	{								\
> +		.type = NAND_OP_DATA_IN_INSTR,				\
> +		.data = {						\
> +			.len = l,					\
> +			.in = buf,					\
> +			.force_8bit = true,				\
> +		},							\
> +		.delay_ns = PSEC_TO_NSEC(delay_ps),			\
> +	}
> +
> +#define NAND_OP_8BIT_DATA_OUT(l, buf, delay_ps)				\
> +	{								\
> +		.type = NAND_OP_DATA_OUT_INSTR,				\
> +		.data = {						\
> +			.len = l,					\
> +			.out = buf,					\
> +			.force_8bit = true,				\
> +		},							\
> +		.delay_ns = PSEC_TO_NSEC(delay_ps),			\
> +	}
> +
> +#define NAND_OP_WAIT_RDY(tout_ps, delay_ps)				\
> +	{								\
> +		.type = NAND_OP_WAITRDY_INSTR,				\
> +		.waitrdy.timeout_ms = PSEC_TO_MSEC(tout_ps),		\
> +		.delay_ns = PSEC_TO_NSEC(delay_ps),			\
> +	}
> +
> +/**
> + * struct nand_subop - a sub operation
> + * @instrs: array of instructions
> + * @ninstrs: length of the @instrs array
> + * @first_instr_start_off: offset to start from for the first instruction
> + *			   of the sub-operation
> + * @last_instr_end_off: offset to end at (excluded) for the last instruction
> + *			of the sub-operation
> + *
> + * When an operation cannot be handled as is by the NAND controller, it will
> + * be split by the parser and the remaining pieces will be handled as
> + * sub-operations.
> + */
> +struct nand_subop {
> +	const struct nand_op_instr *instrs;
> +	unsigned int ninstrs;
> +	/*
> +	 * Specific offset for the first and the last instructions of the subop.
> +	 * Applies for the address cycles in the case of address, or for data
> +	 * offset in the case of data transfers. Otherwise, it is irrelevant.
> +	 */

Can you move that in the kernel header doc?

> +	unsigned int first_instr_start_off;
> +	unsigned int last_instr_end_off;
> +};
> +
> +int nand_subop_get_addr_start_off(const struct nand_subop *subop,
> +				  unsigned int op_id);
> +int nand_subop_get_num_addr_cyc(const struct nand_subop *subop,
> +				unsigned int op_id);
> +int nand_subop_get_data_start_off(const struct nand_subop *subop,
> +				  unsigned int op_id);
> +int nand_subop_get_data_len(const struct nand_subop *subop,
> +			    unsigned int op_id);
> +
> +/**
> + * struct nand_op_parser_addr_constraints - Constraints for address instructions
> + * @maxcycles: maximum number of cycles that the controller can assert by
> + *	       instruction
> + */
> +struct nand_op_parser_addr_constraints {
> +	unsigned int maxcycles;
> +};
> +
> +/**
> + * struct nand_op_parser_data_constraints - Constraints for data instructions
> + * @maxlen: maximum data length that the controller can handle with one
> + *	    instruction
> + */
> +struct nand_op_parser_data_constraints {
> +	unsigned int maxlen;

At some point we should probably add minlen and alignment fields, but
let's keep that for later.

> +};
> +
> +/**
> + * struct nand_op_parser_pattern_elem - One element of a pattern
> + * @type: the instructuction type
> + * @optional: if this element of the pattern is optional or mandatory
> + * @addr/@data: address or data constraint (number of cycles or data length)
> + */
> +struct nand_op_parser_pattern_elem {
> +	enum nand_op_instr_type type;
> +	bool optional;
> +	union {
> +		struct nand_op_parser_addr_constraints addr;
> +		struct nand_op_parser_data_constraints data;
> +	};
> +};
> +
> +#define NAND_OP_PARSER_PAT_CMD_ELEM(_opt)			\
> +	{							\
> +		.type = NAND_OP_CMD_INSTR,			\
> +		.optional = _opt,				\
> +	}
> +
> +#define NAND_OP_PARSER_PAT_ADDR_ELEM(_opt, _maxcycles)		\
> +	{							\
> +		.type = NAND_OP_ADDR_INSTR,			\
> +		.optional = _opt,				\
> +		.addr.maxcycles = _maxcycles,			\
> +	}
> +
> +#define NAND_OP_PARSER_PAT_DATA_IN_ELEM(_opt, _maxlen)		\
> +	{							\
> +		.type = NAND_OP_DATA_IN_INSTR,			\
> +		.optional = _opt,				\
> +		.data.maxlen = _maxlen,				\
> +	}
> +
> +#define NAND_OP_PARSER_PAT_DATA_OUT_ELEM(_opt, _maxlen)		\
> +	{							\
> +		.type = NAND_OP_DATA_OUT_INSTR,			\
> +		.optional = _opt,				\
> +		.data.maxlen = _maxlen,				\
> +	}
> +
> +#define NAND_OP_PARSER_PAT_WAITRDY_ELEM(_opt)			\
> +	{							\
> +		.type = NAND_OP_WAITRDY_INSTR,			\
> +		.optional = _opt,				\
> +	}
> +
> +/**
> + * struct nand_op_parser_pattern - A complete pattern
> + * @elems: array of pattern elements
> + * @nelems: number of pattern elements in @elems array
> + * @exec: the function that will actually execute this pattern, written in the
> + *	  controller driver
> + *
> + * This is a complete pattern that is a list of elements, each one reprensenting
> + * one instruction with its constraints. Controller drivers must declare as much
> + * patterns as they support and give the list of the supported patterns (created
> + * with the help of the following macro) at the call to nand_op_parser_exec_op

s/at the call to nand_op_parser_exec_op/when calling
nand_op_parser_exec_op()/

> + * which shall be the main thing to do in the driver implementation of
> + * ->exec_op().

I'd be more lax than that. Yes the parser is the preferred approach
when you deal with a complex controller. But for simple controllers
it's not.

> Once there is a match between the pattern and an operation, the
> + * core calls the @exec function to actually do the operation.

Not necessarily. The plan is still to ask the controller which
operation it supports before actually executing them. So, in this case
(when check_only param is true), nand_op_parser_exec_op() will never
call ->exec(), it will just make sure the operation can be handled with
the provided patterns.

> + */
> +struct nand_op_parser_pattern {
> +	const struct nand_op_parser_pattern_elem *elems;
> +	unsigned int nelems;
> +	int (*exec)(struct nand_chip *chip, const struct nand_subop *subop);
> +};
> +
> +#define NAND_OP_PARSER_PATTERN(_exec, ...)							\
> +	{											\
> +		.exec = _exec,									\
> +		.elems = (struct nand_op_parser_pattern_elem[]) { __VA_ARGS__ },		\
> +		.nelems = sizeof((struct nand_op_parser_pattern_elem[]) { __VA_ARGS__ }) /	\
> +			  sizeof(struct nand_op_parser_pattern_elem),				\
> +	}
> +
> +/**
> + * struct nand_op_parser - The actual parser
> + * @patterns: array of patterns
> + * @npatterns: length of the @patterns array
> + *
> + * The actual parser structure wich is an array of supported patterns.

It's worth mentioning that patterns will be tested in their declaration
order, and the first match will be taken, so it's important to oder
patterns appropriately so that simple/inefficient patterns are placed
at the end of the list. Usually, this is where you put single
instruction patterns.

> + */
> +struct nand_op_parser {
> +	const struct nand_op_parser_pattern *patterns;
> +	unsigned int npatterns;
> +};
> +
> +#define NAND_OP_PARSER(...)									\
> +	{											\
> +		.patterns = (struct nand_op_parser_pattern[]) { __VA_ARGS__ },			\
> +		.npatterns = sizeof((struct nand_op_parser_pattern[]) { __VA_ARGS__ }) /	\
> +			     sizeof(struct nand_op_parser_pattern),				\
> +	}
> +
> +/**
> + * struct nand_operation - The actual operation
> + * @instrs: array of instructions to execute
> + * @ninstrs: length of the @instrs array
> + *
> + * The actual operation structure that will be given to the parser.

Not only the parser, it will be passed to chip->exep_op() as well.

> + */
> +struct nand_operation {
> +	const struct nand_op_instr *instrs;
> +	unsigned int ninstrs;
> +};
> +
> +#define NAND_OPERATION(_instrs)					\
> +	{							\
> +		.instrs = _instrs,				\
> +		.ninstrs = ARRAY_SIZE(_instrs),			\
> +	}
> +
> +int nand_fill_column_cycles(struct nand_chip *chip, u8 *addrs,
> +			    unsigned int offset_in_page);
> +
> +int nand_op_parser_exec_op(struct nand_chip *chip,
> +			   const struct nand_op_parser *parser,
> +			   const struct nand_operation *op, bool check_only);
> +
> +/**
>   * struct nand_chip - NAND Private Flash Chip Data
>   * @mtd:		MTD device registered to the MTD framework
>   * @IO_ADDR_R:		[BOARDSPECIFIC] address to read the 8 I/O lines of the
> @@ -885,6 +1216,9 @@ struct nand_chip {
>  	int (*setup_data_interface)(struct mtd_info *mtd, int chipnr,
>  				    const struct nand_data_interface *conf);
>  
> +	int (*exec_op)(struct nand_chip *chip,
> +		       const struct nand_operation *op,
> +		       bool check_only);
>  
>  	int chip_delay;
>  	unsigned int options;
> @@ -945,6 +1279,15 @@ struct nand_chip {
>  	} manufacturer;
>  };
>  
> +static inline int nand_exec_op(struct nand_chip *chip,
> +			       const struct nand_operation *op)
> +{
> +	if (!chip->exec_op)
> +		return -ENOTSUPP;
> +
> +	return chip->exec_op(chip, op, false);
> +}
> +
>  extern const struct mtd_ooblayout_ops nand_ooblayout_sp_ops;
>  extern const struct mtd_ooblayout_ops nand_ooblayout_lp_ops;
>  
> @@ -1307,23 +1650,26 @@ int nand_readid_op(struct nand_chip *chip, u8 addr, void *buf,
>  int nand_status_op(struct nand_chip *chip, u8 *status);
>  int nand_erase_op(struct nand_chip *chip, unsigned int eraseblock);
>  int nand_read_page_op(struct nand_chip *chip, unsigned int page,
> -		      unsigned int column, void *buf, unsigned int len);
> -int nand_change_read_column_op(struct nand_chip *chip, unsigned int column,
> -			       void *buf, unsigned int len, bool force_8bit);
> +		      unsigned int offset_in_page, void *buf, unsigned int len);
> +int nand_change_read_column_op(struct nand_chip *chip,
> +			       unsigned int offset_in_page, void *buf,
> +			       unsigned int len, bool force_8bit);
>  int nand_read_oob_op(struct nand_chip *chip, unsigned int page,
> -		     unsigned int column, void *buf, unsigned int len);
> +		     unsigned int offset_in_page, void *buf, unsigned int len);
>  int nand_prog_page_begin_op(struct nand_chip *chip, unsigned int page,
> -			    unsigned int column, const void *buf,
> +			    unsigned int offset_in_page, const void *buf,
>  			    unsigned int len);
>  int nand_prog_page_end_op(struct nand_chip *chip);
>  int nand_prog_page_op(struct nand_chip *chip, unsigned int page,
> -		      unsigned int column, const void *buf, unsigned int len);
> -int nand_change_write_column_op(struct nand_chip *chip, unsigned int column,
> -				const void *buf, unsigned int len, bool force_8bit);
> +		      unsigned int offset_in_page, const void *buf,
> +		      unsigned int len);
> +int nand_change_write_column_op(struct nand_chip *chip,
> +				unsigned int offset_in_page, const void *buf,
> +				unsigned int len, bool force_8bit);
>  int nand_read_data_op(struct nand_chip *chip, void *buf, unsigned int len,
> -		      bool force_8bits);
> +		      bool force_8bit);
>  int nand_write_data_op(struct nand_chip *chip, const void *buf,
> -		       unsigned int len, bool force_8bits);
> +		       unsigned int len, bool force_8bit);
>  
>  /* Free resources held by the NAND device */
>  void nand_cleanup(struct nand_chip *chip);

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [RFC 05/12] dt-bindings: mtd: add Marvell NAND controller documentation
  2017-10-18 14:36   ` Miquel Raynal
  (?)
@ 2017-10-18 22:01     ` Boris Brezillon
  -1 siblings, 0 replies; 75+ messages in thread
From: Boris Brezillon @ 2017-10-18 22:01 UTC (permalink / raw)
  To: Miquel Raynal
  Cc: Mark Rutland, Andrew Lunn, Catalin Marinas, Hanna Hawa,
	Will Deacon, Stefan Agner, Nadav Haklai, Masahiro Yamada,
	linux-mtd, Matthias Brugger, Robert Jarzmik, devel,
	Maxim Levitsky, Kamal Dasu, Josh Wu, Russell King, Marek Vasut,
	Chen-Yu Tsai, bcm-kernel-feedback-list, Sebastian Hesselbarth,
	Ezequiel Garcia, Sylvain Lemieux, Marc Gonzalez

On Wed, 18 Oct 2017 16:36:22 +0200
Miquel Raynal <miquel.raynal@free-electrons.com> wrote:

> Document the bindings for the legacy and the new bindings relative to
> Marvell NAND controller driver rework.
> 
> Signed-off-by: Miquel Raynal <miquel.raynal@free-electrons.com>
> ---
>  .../devicetree/bindings/mtd/marvell-nand.txt       | 95 ++++++++++++++++++++++
>  1 file changed, 95 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/mtd/marvell-nand.txt
> 
> diff --git a/Documentation/devicetree/bindings/mtd/marvell-nand.txt b/Documentation/devicetree/bindings/mtd/marvell-nand.txt
> new file mode 100644
> index 000000000000..ea99f426c03f
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/mtd/marvell-nand.txt
> @@ -0,0 +1,95 @@
> +Marvell NAND Flash Controller (NFC)
> +
> +Required properties:
> +C'est faux, t'en a rajouté un y a pas longtps :).
> +Je conseille de mettre ça sous forme de liste, genre

Oops! French words slipped in :-).

> +
> +- compatible: can be one of the following:
> +    * "marvell,armada-8k-nand-controller"
> +    * "marvell,armada370-nand-controller"
> +    * "marvell,pxa3xx-nand-controller"
> +    * "marvell,armada-8k-nand" (deprecated)
> +    * "marvell,armada370-nand" (deprecated)
> +    * "marvell,pxa3xx-nand" (deprecated)
> +- reg: shall contain registers location and length for data and reg.
> +- #address-cells: shall be set to 1. Encode the nand CS.
> +- #size-cells: shall be set to 0.
> +- interrupts: shall define the nand controller interrupt.
> +- clocks: shall reference nand controller clocks.
> +- marvell,system-controller: Set to retrieve the syscon node that handles
> +  NAND controller related registers (only required with the
> +  "marvell,armada-8k-nand[-controller]" compatibles).
> +
> +Optional properties:
> +- dmas: shall reference DMA channel associated to the NAND controller.
> +- dma-names: shall be "rxtx".
> +
> +Optional children nodes:
> +Children nodes represent the available NAND chips.
> +
> +Required properties:
> +- reg: shall contain the native Chip Select ids (0-3)
> +- marvell,rb: shall contain the native Ready/Busy ids (0-1)
> +
> +Optional properties:
> +- marvell,nand-keep-config: orders the driver not to take the timings
> +  from the core and leaving them completely untouched. Bootloader
> +  timings will then be used.
> +- marvell,nand-enable-arbiter: only useful for PXA platforms, will
> +  enable bus arbiter between NFC and DFI bus (must be enabled for
> +  NFC operation)
> +- nand-on-flash-bbt: speed up the boot process by not discovering all
> +  the bad blocks at each boot and reading directly an on flash table.
> +- nand-ecc-mode: one of the supported ECC modes ("none", "soft",
> +  "hw"). If not specified, hardware ECC will be used.
> +- nand-ecc-algo: algorithm to use if previous choice was "soft"
> +  ("hamming" or "bch). This property may be added for hardware ECC for
> +  clarification but will be ignored by the driver because ECC mode is
> +  chosen depending on the page size and the strength required by the
> +  NAND chip. This value may be overwritten with the nand-ecc-strength
> +  property.
> +- nand-ecc-strength: desired ECC strength.
> +- nand-ecc-step-size: indication on the ECC step size. This has no
> +  effect and will be ignored by the driver when using hardware
> +  ECC. Because Marvell's NAND flash controller does use fixed strength
> +  (1-bit for Hamming, 16-bit for BCH), the step size will shrink or
> +  grown in order to fit the required strength and the value
> +  updated. Step sizes are not completely random for all and follow
> +  certain patterns described in AN-379, "Marvell SoC NFC ECC".
> +
> +See Documentation/devicetree/bindings/mtd/nand.txt for more details on
> +generic bindings.
> +
> +
> +Example:
> +nand_controller: nand-controller@d0000 {
> +	compatible = "marvell,armada370-nand-controller";
> +	reg = <0xd0000 0x54>;
> +	#address-cells = <1>;
> +	#size-cells = <0>;
> +	interrupts = <GIC_SPI 84 IRQ_TYPE_LEVEL_HIGH>;
> +	clocks = <&coredivclk 0>;
> +	status = "okay";
> +
> +	nand@0 {
> +		reg = <0>;
> +		marvell,rb = <0>;
> +		nand-ecc-mode = "hw";
> +		marvell,nand-keep-config;
> +		marvell,nand-enable-arbiter;
> +		nand-on-flash-bbt;
> +		nand-ecc-strength = <4>;
> +		nand-ecc-step-size = <512>;
> +
> +		partitions {
> +			compatible = "fixed-partitions";
> +			#address-cells = <1>;
> +			#size-cells = <1>;
> +
> +			partition@0 {
> +				label = "Rootfs";
> +				reg = <0x00000000 0x40000000>;
> +			};
> +		};
> +	};
> +};

_______________________________________________
devel mailing list
devel@linuxdriverproject.org
http://driverdev.linuxdriverproject.org/mailman/listinfo/driverdev-devel

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [RFC 05/12] dt-bindings: mtd: add Marvell NAND controller documentation
@ 2017-10-18 22:01     ` Boris Brezillon
  0 siblings, 0 replies; 75+ messages in thread
From: Boris Brezillon @ 2017-10-18 22:01 UTC (permalink / raw)
  To: Miquel Raynal
  Cc: Andrew Lunn, bcm-kernel-feedback-list, Brian Norris,
	Catalin Marinas, Chen-Yu Tsai, Cyrille Pitchen, Daniel Mack,
	David Woodhouse, devel, devicetree, Ezequiel Garcia,
	Greg Kroah-Hartman, Gregory Clement, Han Xu, Haojian Zhuang,
	Jason Cooper, Josh Wu, Kamal Dasu, linux-arm-kernel,
	linux-kernel, linux-mediatek, linux-mtd, Marc Gonzalez,
	Marek Vasut, Mark Rutland, Masahiro Yamada, Matthias Brugger,
	Maxime Ripard, Maxim Levitsky, Richard Weinberger,
	Robert Jarzmik, Rob Herring, Russell King, Sebastian Hesselbarth,
	Stefan Agner, Sylvain Lemieux, Vladimir Zapolskiy, Wenyou Yang,
	Will Deacon, Thomas Petazzoni, Antoine Tenart, Igor Grinberg,
	Nadav Haklai, Ofer Heifetz, Neta Zur Hershkovits, Hanna Hawa

On Wed, 18 Oct 2017 16:36:22 +0200
Miquel Raynal <miquel.raynal@free-electrons.com> wrote:

> Document the bindings for the legacy and the new bindings relative to
> Marvell NAND controller driver rework.
> 
> Signed-off-by: Miquel Raynal <miquel.raynal@free-electrons.com>
> ---
>  .../devicetree/bindings/mtd/marvell-nand.txt       | 95 ++++++++++++++++++++++
>  1 file changed, 95 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/mtd/marvell-nand.txt
> 
> diff --git a/Documentation/devicetree/bindings/mtd/marvell-nand.txt b/Documentation/devicetree/bindings/mtd/marvell-nand.txt
> new file mode 100644
> index 000000000000..ea99f426c03f
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/mtd/marvell-nand.txt
> @@ -0,0 +1,95 @@
> +Marvell NAND Flash Controller (NFC)
> +
> +Required properties:
> +C'est faux, t'en a rajouté un y a pas longtps :).
> +Je conseille de mettre ça sous forme de liste, genre

Oops! French words slipped in :-).

> +
> +- compatible: can be one of the following:
> +    * "marvell,armada-8k-nand-controller"
> +    * "marvell,armada370-nand-controller"
> +    * "marvell,pxa3xx-nand-controller"
> +    * "marvell,armada-8k-nand" (deprecated)
> +    * "marvell,armada370-nand" (deprecated)
> +    * "marvell,pxa3xx-nand" (deprecated)
> +- reg: shall contain registers location and length for data and reg.
> +- #address-cells: shall be set to 1. Encode the nand CS.
> +- #size-cells: shall be set to 0.
> +- interrupts: shall define the nand controller interrupt.
> +- clocks: shall reference nand controller clocks.
> +- marvell,system-controller: Set to retrieve the syscon node that handles
> +  NAND controller related registers (only required with the
> +  "marvell,armada-8k-nand[-controller]" compatibles).
> +
> +Optional properties:
> +- dmas: shall reference DMA channel associated to the NAND controller.
> +- dma-names: shall be "rxtx".
> +
> +Optional children nodes:
> +Children nodes represent the available NAND chips.
> +
> +Required properties:
> +- reg: shall contain the native Chip Select ids (0-3)
> +- marvell,rb: shall contain the native Ready/Busy ids (0-1)
> +
> +Optional properties:
> +- marvell,nand-keep-config: orders the driver not to take the timings
> +  from the core and leaving them completely untouched. Bootloader
> +  timings will then be used.
> +- marvell,nand-enable-arbiter: only useful for PXA platforms, will
> +  enable bus arbiter between NFC and DFI bus (must be enabled for
> +  NFC operation)
> +- nand-on-flash-bbt: speed up the boot process by not discovering all
> +  the bad blocks at each boot and reading directly an on flash table.
> +- nand-ecc-mode: one of the supported ECC modes ("none", "soft",
> +  "hw"). If not specified, hardware ECC will be used.
> +- nand-ecc-algo: algorithm to use if previous choice was "soft"
> +  ("hamming" or "bch). This property may be added for hardware ECC for
> +  clarification but will be ignored by the driver because ECC mode is
> +  chosen depending on the page size and the strength required by the
> +  NAND chip. This value may be overwritten with the nand-ecc-strength
> +  property.
> +- nand-ecc-strength: desired ECC strength.
> +- nand-ecc-step-size: indication on the ECC step size. This has no
> +  effect and will be ignored by the driver when using hardware
> +  ECC. Because Marvell's NAND flash controller does use fixed strength
> +  (1-bit for Hamming, 16-bit for BCH), the step size will shrink or
> +  grown in order to fit the required strength and the value
> +  updated. Step sizes are not completely random for all and follow
> +  certain patterns described in AN-379, "Marvell SoC NFC ECC".
> +
> +See Documentation/devicetree/bindings/mtd/nand.txt for more details on
> +generic bindings.
> +
> +
> +Example:
> +nand_controller: nand-controller@d0000 {
> +	compatible = "marvell,armada370-nand-controller";
> +	reg = <0xd0000 0x54>;
> +	#address-cells = <1>;
> +	#size-cells = <0>;
> +	interrupts = <GIC_SPI 84 IRQ_TYPE_LEVEL_HIGH>;
> +	clocks = <&coredivclk 0>;
> +	status = "okay";
> +
> +	nand@0 {
> +		reg = <0>;
> +		marvell,rb = <0>;
> +		nand-ecc-mode = "hw";
> +		marvell,nand-keep-config;
> +		marvell,nand-enable-arbiter;
> +		nand-on-flash-bbt;
> +		nand-ecc-strength = <4>;
> +		nand-ecc-step-size = <512>;
> +
> +		partitions {
> +			compatible = "fixed-partitions";
> +			#address-cells = <1>;
> +			#size-cells = <1>;
> +
> +			partition@0 {
> +				label = "Rootfs";
> +				reg = <0x00000000 0x40000000>;
> +			};
> +		};
> +	};
> +};

^ permalink raw reply	[flat|nested] 75+ messages in thread

* [RFC 05/12] dt-bindings: mtd: add Marvell NAND controller documentation
@ 2017-10-18 22:01     ` Boris Brezillon
  0 siblings, 0 replies; 75+ messages in thread
From: Boris Brezillon @ 2017-10-18 22:01 UTC (permalink / raw)
  To: linux-arm-kernel

On Wed, 18 Oct 2017 16:36:22 +0200
Miquel Raynal <miquel.raynal@free-electrons.com> wrote:

> Document the bindings for the legacy and the new bindings relative to
> Marvell NAND controller driver rework.
> 
> Signed-off-by: Miquel Raynal <miquel.raynal@free-electrons.com>
> ---
>  .../devicetree/bindings/mtd/marvell-nand.txt       | 95 ++++++++++++++++++++++
>  1 file changed, 95 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/mtd/marvell-nand.txt
> 
> diff --git a/Documentation/devicetree/bindings/mtd/marvell-nand.txt b/Documentation/devicetree/bindings/mtd/marvell-nand.txt
> new file mode 100644
> index 000000000000..ea99f426c03f
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/mtd/marvell-nand.txt
> @@ -0,0 +1,95 @@
> +Marvell NAND Flash Controller (NFC)
> +
> +Required properties:
> +C'est faux, t'en a rajout? un y a pas longtps :).
> +Je conseille de mettre ?a sous forme de liste, genre

Oops! French words slipped in :-).

> +
> +- compatible: can be one of the following:
> +    * "marvell,armada-8k-nand-controller"
> +    * "marvell,armada370-nand-controller"
> +    * "marvell,pxa3xx-nand-controller"
> +    * "marvell,armada-8k-nand" (deprecated)
> +    * "marvell,armada370-nand" (deprecated)
> +    * "marvell,pxa3xx-nand" (deprecated)
> +- reg: shall contain registers location and length for data and reg.
> +- #address-cells: shall be set to 1. Encode the nand CS.
> +- #size-cells: shall be set to 0.
> +- interrupts: shall define the nand controller interrupt.
> +- clocks: shall reference nand controller clocks.
> +- marvell,system-controller: Set to retrieve the syscon node that handles
> +  NAND controller related registers (only required with the
> +  "marvell,armada-8k-nand[-controller]" compatibles).
> +
> +Optional properties:
> +- dmas: shall reference DMA channel associated to the NAND controller.
> +- dma-names: shall be "rxtx".
> +
> +Optional children nodes:
> +Children nodes represent the available NAND chips.
> +
> +Required properties:
> +- reg: shall contain the native Chip Select ids (0-3)
> +- marvell,rb: shall contain the native Ready/Busy ids (0-1)
> +
> +Optional properties:
> +- marvell,nand-keep-config: orders the driver not to take the timings
> +  from the core and leaving them completely untouched. Bootloader
> +  timings will then be used.
> +- marvell,nand-enable-arbiter: only useful for PXA platforms, will
> +  enable bus arbiter between NFC and DFI bus (must be enabled for
> +  NFC operation)
> +- nand-on-flash-bbt: speed up the boot process by not discovering all
> +  the bad blocks at each boot and reading directly an on flash table.
> +- nand-ecc-mode: one of the supported ECC modes ("none", "soft",
> +  "hw"). If not specified, hardware ECC will be used.
> +- nand-ecc-algo: algorithm to use if previous choice was "soft"
> +  ("hamming" or "bch). This property may be added for hardware ECC for
> +  clarification but will be ignored by the driver because ECC mode is
> +  chosen depending on the page size and the strength required by the
> +  NAND chip. This value may be overwritten with the nand-ecc-strength
> +  property.
> +- nand-ecc-strength: desired ECC strength.
> +- nand-ecc-step-size: indication on the ECC step size. This has no
> +  effect and will be ignored by the driver when using hardware
> +  ECC. Because Marvell's NAND flash controller does use fixed strength
> +  (1-bit for Hamming, 16-bit for BCH), the step size will shrink or
> +  grown in order to fit the required strength and the value
> +  updated. Step sizes are not completely random for all and follow
> +  certain patterns described in AN-379, "Marvell SoC NFC ECC".
> +
> +See Documentation/devicetree/bindings/mtd/nand.txt for more details on
> +generic bindings.
> +
> +
> +Example:
> +nand_controller: nand-controller at d0000 {
> +	compatible = "marvell,armada370-nand-controller";
> +	reg = <0xd0000 0x54>;
> +	#address-cells = <1>;
> +	#size-cells = <0>;
> +	interrupts = <GIC_SPI 84 IRQ_TYPE_LEVEL_HIGH>;
> +	clocks = <&coredivclk 0>;
> +	status = "okay";
> +
> +	nand at 0 {
> +		reg = <0>;
> +		marvell,rb = <0>;
> +		nand-ecc-mode = "hw";
> +		marvell,nand-keep-config;
> +		marvell,nand-enable-arbiter;
> +		nand-on-flash-bbt;
> +		nand-ecc-strength = <4>;
> +		nand-ecc-step-size = <512>;
> +
> +		partitions {
> +			compatible = "fixed-partitions";
> +			#address-cells = <1>;
> +			#size-cells = <1>;
> +
> +			partition at 0 {
> +				label = "Rootfs";
> +				reg = <0x00000000 0x40000000>;
> +			};
> +		};
> +	};
> +};

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [RFC 00/12] Marvell NAND controller rework with ->exec_op()
  2017-10-18 14:36 ` Miquel Raynal
  (?)
@ 2017-10-18 22:29   ` Boris Brezillon
  -1 siblings, 0 replies; 75+ messages in thread
From: Boris Brezillon @ 2017-10-18 22:29 UTC (permalink / raw)
  To: Miquel Raynal
  Cc: Mark Rutland, Andrew Lunn, Catalin Marinas, Hanna Hawa,
	Will Deacon, Stefan Agner, Nadav Haklai, Masahiro Yamada,
	linux-mtd, Matthias Brugger, Robert Jarzmik, devel,
	Maxim Levitsky, Kamal Dasu, Josh Wu, Russell King, Marek Vasut,
	Chen-Yu Tsai, bcm-kernel-feedback-list, Sebastian Hesselbarth,
	Ezequiel Garcia, Sylvain Lemieux, Marc Gonzalez

Hi Miquel,

On Wed, 18 Oct 2017 16:36:17 +0200
Miquel Raynal <miquel.raynal@free-electrons.com> wrote:

> Hi,
> 
> This series adds the implementation of the NAND framework ->exec_op()
> interface with all the related hooks and helpers. The reasons for adding
> it are explained in details in the commit log "mtd: nand: add
>  ->exec_op() implementation", but mostly it will ease later expansion of  
> the framework, as well as the implementation of new vendor specific
> commands, and should also ease driver development.

Still need to review patch 6, but I'd like to say I'm really happy to
see that happen, finally. Hopefully that will help us make the NAND
framework a bit easier to maintain and allow us to support advanced
NAND features without having to patch all NAND controller drivers every
time we add a new vendor specific operation. I also have in mind a few
NAND controller drivers that have been rejected because they were not
fitting in the ->cmd_ctrl() approach and I didn't want to add more
drivers with their own partially implemented/broken ->cmdfunc() method.

Kudos for the work you've done, and I hope we'll get reviews from other
active NAND driver maintainers/contributors. To all NAND driver
maintainers: note that the long term goal is to get rid of
->cmd_ctrl()/cmdfunc()/read/write_buf/byte/word() entirely and replace
them with a single ->exec_op() hook. Please let us know if you need
extra things that are not yet exposed in nand_op_instr/nand_op_parser
or if you would like things to be done differently.

Thanks,

Boris

> 
> The series contains as well the reworked NAND controller driver from
> Marvell.
> 
> A lot of comments are written to explain how to use the new API and the
> Marvell driver shows how to implement it. A proper external
> documentation is being written and will later be submitted.
> 
> It also changes the device tree NAND node definition for all platforms
> referring to the Marvell driver to use the new bindings. They are more
> hierarchical and fit the real organization of the hardware, by having
> NAND partitions that are part of NAND chip nodes, themselves part of
> the NAND controller node.
> 
> See the commit log of "mtd: nand: add reworked Marvell NAND controller
> driver" for details about why a completely new driver is needed.
> 
> As this series changes several core functions and also directly touches
> multiple controller drivers, it would be great to have reviews and tests
> from the concerned maintainers. Only PXA3xx SoCs are not supported yet
> (explaining the RFC) as having a local board to test is really needed.
> 
> Thank you,
> Miquèl
> 
> 
> Boris Brezillon (2):
>   mtd: nand: provide several helpers to do common NAND operations
>   mtd: nand: force drivers to explicitly send READ/PROG commands
> 
> Miquel Raynal (10):
>   mtd: nand: use a static data_interface in the nand_chip structure
>   mtd: nand: add ->exec_op() implementation
>   dt-bindings: mtd: add Marvell NAND controller documentation
>   mtd: nand: add reworked Marvell NAND controller driver
>   ARM: dts: armada-370-xp: use reworked NAND controller driver
>   ARM: dts: armada-375: use reworked NAND controller driver
>   ARM: dts: armada-38x: use reworked NAND controller driver
>   ARM: dts: armada-39x: use reworked NAND controller driver
>   ARM: dts: pxa: use reworked NAND controller driver
>   ARM64: dts: marvell: use reworked NAND controller driver on Armada
>     7K/8K
> 
>  .../devicetree/bindings/mtd/marvell-nand.txt       |   95 +
>  arch/arm/boot/dts/armada-370-db.dts                |   56 +-
>  arch/arm/boot/dts/armada-370-dlink-dns327l.dts     |  119 +-
>  arch/arm/boot/dts/armada-370-mirabox.dts           |   50 +-
>  arch/arm/boot/dts/armada-370-netgear-rn102.dts     |   89 +-
>  arch/arm/boot/dts/armada-370-netgear-rn104.dts     |   89 +-
>  arch/arm/boot/dts/armada-370-rd.dts                |   51 +-
>  arch/arm/boot/dts/armada-370-seagate-nas-xbay.dtsi |   63 +-
>  arch/arm/boot/dts/armada-370-xp.dtsi               |    6 +-
>  arch/arm/boot/dts/armada-375-db.dts                |   49 +-
>  arch/arm/boot/dts/armada-375.dtsi                  |    6 +-
>  arch/arm/boot/dts/armada-385-db-ap.dts             |   68 +-
>  arch/arm/boot/dts/armada-385-linksys-caiman.dts    |  129 +-
>  arch/arm/boot/dts/armada-385-linksys-cobra.dts     |  129 +-
>  arch/arm/boot/dts/armada-385-linksys-rango.dts     |  141 +-
>  arch/arm/boot/dts/armada-385-linksys-shelby.dts    |  129 +-
>  arch/arm/boot/dts/armada-385-linksys.dtsi          |   15 +-
>  arch/arm/boot/dts/armada-388-db.dts                |   54 +-
>  arch/arm/boot/dts/armada-38x.dtsi                  |    6 +-
>  arch/arm/boot/dts/armada-390-db.dts                |   65 +-
>  arch/arm/boot/dts/armada-395-gp.dts                |   73 +-
>  arch/arm/boot/dts/armada-398-db.dts                |   59 +-
>  arch/arm/boot/dts/armada-39x.dtsi                  |    6 +-
>  arch/arm/boot/dts/armada-xp-db-dxbc2.dts           |    1 -
>  arch/arm/boot/dts/armada-xp-db-xc3-24g4xg.dts      |    1 -
>  arch/arm/boot/dts/armada-xp-db.dts                 |    1 -
>  arch/arm/boot/dts/armada-xp-gp.dts                 |    1 -
>  arch/arm/boot/dts/armada-xp-lenovo-ix4-300d.dts    |    1 -
>  arch/arm/boot/dts/armada-xp-linksys-mamba.dts      |  155 +-
>  arch/arm/boot/dts/armada-xp-netgear-rn2120.dts     |   89 +-
>  arch/arm/boot/dts/pxa3xx.dtsi                      |    6 +-
>  arch/arm64/boot/dts/marvell/armada-7040-db.dts     |   47 +-
>  .../boot/dts/marvell/armada-cp110-master.dtsi      |    6 +-
>  .../arm64/boot/dts/marvell/armada-cp110-slave.dtsi |    6 +-
>  drivers/mtd/nand/Kconfig                           |   11 +
>  drivers/mtd/nand/Makefile                          |    1 +
>  drivers/mtd/nand/atmel/nand-controller.c           |    9 +-
>  drivers/mtd/nand/bf5xx_nand.c                      |    6 +-
>  drivers/mtd/nand/brcmnand/brcmnand.c               |   20 +-
>  drivers/mtd/nand/cafe_nand.c                       |   20 +-
>  drivers/mtd/nand/denali.c                          |  109 +-
>  drivers/mtd/nand/diskonchip.c                      |    4 +-
>  drivers/mtd/nand/docg4.c                           |   19 +-
>  drivers/mtd/nand/fsl_elbc_nand.c                   |    6 +-
>  drivers/mtd/nand/fsl_ifc_nand.c                    |    6 +-
>  drivers/mtd/nand/fsmc_nand.c                       |    5 +-
>  drivers/mtd/nand/gpmi-nand/gpmi-nand.c             |   76 +-
>  drivers/mtd/nand/hisi504_nand.c                    |    9 +-
>  drivers/mtd/nand/jz4740_nand.c                     |   16 +-
>  drivers/mtd/nand/lpc32xx_mlc.c                     |    7 +-
>  drivers/mtd/nand/lpc32xx_slc.c                     |   33 +-
>  drivers/mtd/nand/marvell_nand.c                    | 2384 ++++++++++++++++++++
>  drivers/mtd/nand/mtk_nand.c                        |   21 +-
>  drivers/mtd/nand/nand_base.c                       | 2020 ++++++++++++++---
>  drivers/mtd/nand/nand_hynix.c                      |  106 +-
>  drivers/mtd/nand/nand_micron.c                     |   50 +-
>  drivers/mtd/nand/nand_timings.c                    |   23 +-
>  drivers/mtd/nand/omap2.c                           |    8 +-
>  drivers/mtd/nand/pxa3xx_nand.c                     |    8 +-
>  drivers/mtd/nand/qcom_nandc.c                      |   16 +-
>  drivers/mtd/nand/r852.c                            |   11 +-
>  drivers/mtd/nand/sh_flctl.c                        |    6 +-
>  drivers/mtd/nand/sunxi_nand.c                      |   97 +-
>  drivers/mtd/nand/tango_nand.c                      |   27 +-
>  drivers/mtd/nand/tmio_nand.c                       |    5 +-
>  drivers/mtd/nand/vf610_nfc.c                       |    6 +-
>  drivers/staging/mt29f_spinand/mt29f_spinand.c      |    7 +-
>  include/linux/mtd/rawnand.h                        |  391 +++-
>  68 files changed, 5980 insertions(+), 1424 deletions(-)
>  create mode 100644 Documentation/devicetree/bindings/mtd/marvell-nand.txt
>  create mode 100644 drivers/mtd/nand/marvell_nand.c
> 

_______________________________________________
devel mailing list
devel@linuxdriverproject.org
http://driverdev.linuxdriverproject.org/mailman/listinfo/driverdev-devel

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [RFC 00/12] Marvell NAND controller rework with ->exec_op()
@ 2017-10-18 22:29   ` Boris Brezillon
  0 siblings, 0 replies; 75+ messages in thread
From: Boris Brezillon @ 2017-10-18 22:29 UTC (permalink / raw)
  To: Miquel Raynal
  Cc: Andrew Lunn, bcm-kernel-feedback-list, Brian Norris,
	Catalin Marinas, Chen-Yu Tsai, Cyrille Pitchen, Daniel Mack,
	David Woodhouse, devel, devicetree, Ezequiel Garcia,
	Greg Kroah-Hartman, Gregory Clement, Han Xu, Haojian Zhuang,
	Jason Cooper, Josh Wu, Kamal Dasu, linux-arm-kernel,
	linux-kernel, linux-mediatek, linux-mtd, Marc Gonzalez,
	Marek Vasut, Mark Rutland, Masahiro Yamada, Matthias Brugger,
	Maxime Ripard, Maxim Levitsky, Richard Weinberger,
	Robert Jarzmik, Rob Herring, Russell King, Sebastian Hesselbarth,
	Stefan Agner, Sylvain Lemieux, Vladimir Zapolskiy, Wenyou Yang,
	Will Deacon, Thomas Petazzoni, Antoine Tenart, Igor Grinberg,
	Nadav Haklai, Ofer Heifetz, Neta Zur Hershkovits, Hanna Hawa

Hi Miquel,

On Wed, 18 Oct 2017 16:36:17 +0200
Miquel Raynal <miquel.raynal@free-electrons.com> wrote:

> Hi,
> 
> This series adds the implementation of the NAND framework ->exec_op()
> interface with all the related hooks and helpers. The reasons for adding
> it are explained in details in the commit log "mtd: nand: add
>  ->exec_op() implementation", but mostly it will ease later expansion of  
> the framework, as well as the implementation of new vendor specific
> commands, and should also ease driver development.

Still need to review patch 6, but I'd like to say I'm really happy to
see that happen, finally. Hopefully that will help us make the NAND
framework a bit easier to maintain and allow us to support advanced
NAND features without having to patch all NAND controller drivers every
time we add a new vendor specific operation. I also have in mind a few
NAND controller drivers that have been rejected because they were not
fitting in the ->cmd_ctrl() approach and I didn't want to add more
drivers with their own partially implemented/broken ->cmdfunc() method.

Kudos for the work you've done, and I hope we'll get reviews from other
active NAND driver maintainers/contributors. To all NAND driver
maintainers: note that the long term goal is to get rid of
->cmd_ctrl()/cmdfunc()/read/write_buf/byte/word() entirely and replace
them with a single ->exec_op() hook. Please let us know if you need
extra things that are not yet exposed in nand_op_instr/nand_op_parser
or if you would like things to be done differently.

Thanks,

Boris

> 
> The series contains as well the reworked NAND controller driver from
> Marvell.
> 
> A lot of comments are written to explain how to use the new API and the
> Marvell driver shows how to implement it. A proper external
> documentation is being written and will later be submitted.
> 
> It also changes the device tree NAND node definition for all platforms
> referring to the Marvell driver to use the new bindings. They are more
> hierarchical and fit the real organization of the hardware, by having
> NAND partitions that are part of NAND chip nodes, themselves part of
> the NAND controller node.
> 
> See the commit log of "mtd: nand: add reworked Marvell NAND controller
> driver" for details about why a completely new driver is needed.
> 
> As this series changes several core functions and also directly touches
> multiple controller drivers, it would be great to have reviews and tests
> from the concerned maintainers. Only PXA3xx SoCs are not supported yet
> (explaining the RFC) as having a local board to test is really needed.
> 
> Thank you,
> Miquèl
> 
> 
> Boris Brezillon (2):
>   mtd: nand: provide several helpers to do common NAND operations
>   mtd: nand: force drivers to explicitly send READ/PROG commands
> 
> Miquel Raynal (10):
>   mtd: nand: use a static data_interface in the nand_chip structure
>   mtd: nand: add ->exec_op() implementation
>   dt-bindings: mtd: add Marvell NAND controller documentation
>   mtd: nand: add reworked Marvell NAND controller driver
>   ARM: dts: armada-370-xp: use reworked NAND controller driver
>   ARM: dts: armada-375: use reworked NAND controller driver
>   ARM: dts: armada-38x: use reworked NAND controller driver
>   ARM: dts: armada-39x: use reworked NAND controller driver
>   ARM: dts: pxa: use reworked NAND controller driver
>   ARM64: dts: marvell: use reworked NAND controller driver on Armada
>     7K/8K
> 
>  .../devicetree/bindings/mtd/marvell-nand.txt       |   95 +
>  arch/arm/boot/dts/armada-370-db.dts                |   56 +-
>  arch/arm/boot/dts/armada-370-dlink-dns327l.dts     |  119 +-
>  arch/arm/boot/dts/armada-370-mirabox.dts           |   50 +-
>  arch/arm/boot/dts/armada-370-netgear-rn102.dts     |   89 +-
>  arch/arm/boot/dts/armada-370-netgear-rn104.dts     |   89 +-
>  arch/arm/boot/dts/armada-370-rd.dts                |   51 +-
>  arch/arm/boot/dts/armada-370-seagate-nas-xbay.dtsi |   63 +-
>  arch/arm/boot/dts/armada-370-xp.dtsi               |    6 +-
>  arch/arm/boot/dts/armada-375-db.dts                |   49 +-
>  arch/arm/boot/dts/armada-375.dtsi                  |    6 +-
>  arch/arm/boot/dts/armada-385-db-ap.dts             |   68 +-
>  arch/arm/boot/dts/armada-385-linksys-caiman.dts    |  129 +-
>  arch/arm/boot/dts/armada-385-linksys-cobra.dts     |  129 +-
>  arch/arm/boot/dts/armada-385-linksys-rango.dts     |  141 +-
>  arch/arm/boot/dts/armada-385-linksys-shelby.dts    |  129 +-
>  arch/arm/boot/dts/armada-385-linksys.dtsi          |   15 +-
>  arch/arm/boot/dts/armada-388-db.dts                |   54 +-
>  arch/arm/boot/dts/armada-38x.dtsi                  |    6 +-
>  arch/arm/boot/dts/armada-390-db.dts                |   65 +-
>  arch/arm/boot/dts/armada-395-gp.dts                |   73 +-
>  arch/arm/boot/dts/armada-398-db.dts                |   59 +-
>  arch/arm/boot/dts/armada-39x.dtsi                  |    6 +-
>  arch/arm/boot/dts/armada-xp-db-dxbc2.dts           |    1 -
>  arch/arm/boot/dts/armada-xp-db-xc3-24g4xg.dts      |    1 -
>  arch/arm/boot/dts/armada-xp-db.dts                 |    1 -
>  arch/arm/boot/dts/armada-xp-gp.dts                 |    1 -
>  arch/arm/boot/dts/armada-xp-lenovo-ix4-300d.dts    |    1 -
>  arch/arm/boot/dts/armada-xp-linksys-mamba.dts      |  155 +-
>  arch/arm/boot/dts/armada-xp-netgear-rn2120.dts     |   89 +-
>  arch/arm/boot/dts/pxa3xx.dtsi                      |    6 +-
>  arch/arm64/boot/dts/marvell/armada-7040-db.dts     |   47 +-
>  .../boot/dts/marvell/armada-cp110-master.dtsi      |    6 +-
>  .../arm64/boot/dts/marvell/armada-cp110-slave.dtsi |    6 +-
>  drivers/mtd/nand/Kconfig                           |   11 +
>  drivers/mtd/nand/Makefile                          |    1 +
>  drivers/mtd/nand/atmel/nand-controller.c           |    9 +-
>  drivers/mtd/nand/bf5xx_nand.c                      |    6 +-
>  drivers/mtd/nand/brcmnand/brcmnand.c               |   20 +-
>  drivers/mtd/nand/cafe_nand.c                       |   20 +-
>  drivers/mtd/nand/denali.c                          |  109 +-
>  drivers/mtd/nand/diskonchip.c                      |    4 +-
>  drivers/mtd/nand/docg4.c                           |   19 +-
>  drivers/mtd/nand/fsl_elbc_nand.c                   |    6 +-
>  drivers/mtd/nand/fsl_ifc_nand.c                    |    6 +-
>  drivers/mtd/nand/fsmc_nand.c                       |    5 +-
>  drivers/mtd/nand/gpmi-nand/gpmi-nand.c             |   76 +-
>  drivers/mtd/nand/hisi504_nand.c                    |    9 +-
>  drivers/mtd/nand/jz4740_nand.c                     |   16 +-
>  drivers/mtd/nand/lpc32xx_mlc.c                     |    7 +-
>  drivers/mtd/nand/lpc32xx_slc.c                     |   33 +-
>  drivers/mtd/nand/marvell_nand.c                    | 2384 ++++++++++++++++++++
>  drivers/mtd/nand/mtk_nand.c                        |   21 +-
>  drivers/mtd/nand/nand_base.c                       | 2020 ++++++++++++++---
>  drivers/mtd/nand/nand_hynix.c                      |  106 +-
>  drivers/mtd/nand/nand_micron.c                     |   50 +-
>  drivers/mtd/nand/nand_timings.c                    |   23 +-
>  drivers/mtd/nand/omap2.c                           |    8 +-
>  drivers/mtd/nand/pxa3xx_nand.c                     |    8 +-
>  drivers/mtd/nand/qcom_nandc.c                      |   16 +-
>  drivers/mtd/nand/r852.c                            |   11 +-
>  drivers/mtd/nand/sh_flctl.c                        |    6 +-
>  drivers/mtd/nand/sunxi_nand.c                      |   97 +-
>  drivers/mtd/nand/tango_nand.c                      |   27 +-
>  drivers/mtd/nand/tmio_nand.c                       |    5 +-
>  drivers/mtd/nand/vf610_nfc.c                       |    6 +-
>  drivers/staging/mt29f_spinand/mt29f_spinand.c      |    7 +-
>  include/linux/mtd/rawnand.h                        |  391 +++-
>  68 files changed, 5980 insertions(+), 1424 deletions(-)
>  create mode 100644 Documentation/devicetree/bindings/mtd/marvell-nand.txt
>  create mode 100644 drivers/mtd/nand/marvell_nand.c
> 

^ permalink raw reply	[flat|nested] 75+ messages in thread

* [RFC 00/12] Marvell NAND controller rework with ->exec_op()
@ 2017-10-18 22:29   ` Boris Brezillon
  0 siblings, 0 replies; 75+ messages in thread
From: Boris Brezillon @ 2017-10-18 22:29 UTC (permalink / raw)
  To: linux-arm-kernel

Hi Miquel,

On Wed, 18 Oct 2017 16:36:17 +0200
Miquel Raynal <miquel.raynal@free-electrons.com> wrote:

> Hi,
> 
> This series adds the implementation of the NAND framework ->exec_op()
> interface with all the related hooks and helpers. The reasons for adding
> it are explained in details in the commit log "mtd: nand: add
>  ->exec_op() implementation", but mostly it will ease later expansion of  
> the framework, as well as the implementation of new vendor specific
> commands, and should also ease driver development.

Still need to review patch 6, but I'd like to say I'm really happy to
see that happen, finally. Hopefully that will help us make the NAND
framework a bit easier to maintain and allow us to support advanced
NAND features without having to patch all NAND controller drivers every
time we add a new vendor specific operation. I also have in mind a few
NAND controller drivers that have been rejected because they were not
fitting in the ->cmd_ctrl() approach and I didn't want to add more
drivers with their own partially implemented/broken ->cmdfunc() method.

Kudos for the work you've done, and I hope we'll get reviews from other
active NAND driver maintainers/contributors. To all NAND driver
maintainers: note that the long term goal is to get rid of
->cmd_ctrl()/cmdfunc()/read/write_buf/byte/word() entirely and replace
them with a single ->exec_op() hook. Please let us know if you need
extra things that are not yet exposed in nand_op_instr/nand_op_parser
or if you would like things to be done differently.

Thanks,

Boris

> 
> The series contains as well the reworked NAND controller driver from
> Marvell.
> 
> A lot of comments are written to explain how to use the new API and the
> Marvell driver shows how to implement it. A proper external
> documentation is being written and will later be submitted.
> 
> It also changes the device tree NAND node definition for all platforms
> referring to the Marvell driver to use the new bindings. They are more
> hierarchical and fit the real organization of the hardware, by having
> NAND partitions that are part of NAND chip nodes, themselves part of
> the NAND controller node.
> 
> See the commit log of "mtd: nand: add reworked Marvell NAND controller
> driver" for details about why a completely new driver is needed.
> 
> As this series changes several core functions and also directly touches
> multiple controller drivers, it would be great to have reviews and tests
> from the concerned maintainers. Only PXA3xx SoCs are not supported yet
> (explaining the RFC) as having a local board to test is really needed.
> 
> Thank you,
> Miqu?l
> 
> 
> Boris Brezillon (2):
>   mtd: nand: provide several helpers to do common NAND operations
>   mtd: nand: force drivers to explicitly send READ/PROG commands
> 
> Miquel Raynal (10):
>   mtd: nand: use a static data_interface in the nand_chip structure
>   mtd: nand: add ->exec_op() implementation
>   dt-bindings: mtd: add Marvell NAND controller documentation
>   mtd: nand: add reworked Marvell NAND controller driver
>   ARM: dts: armada-370-xp: use reworked NAND controller driver
>   ARM: dts: armada-375: use reworked NAND controller driver
>   ARM: dts: armada-38x: use reworked NAND controller driver
>   ARM: dts: armada-39x: use reworked NAND controller driver
>   ARM: dts: pxa: use reworked NAND controller driver
>   ARM64: dts: marvell: use reworked NAND controller driver on Armada
>     7K/8K
> 
>  .../devicetree/bindings/mtd/marvell-nand.txt       |   95 +
>  arch/arm/boot/dts/armada-370-db.dts                |   56 +-
>  arch/arm/boot/dts/armada-370-dlink-dns327l.dts     |  119 +-
>  arch/arm/boot/dts/armada-370-mirabox.dts           |   50 +-
>  arch/arm/boot/dts/armada-370-netgear-rn102.dts     |   89 +-
>  arch/arm/boot/dts/armada-370-netgear-rn104.dts     |   89 +-
>  arch/arm/boot/dts/armada-370-rd.dts                |   51 +-
>  arch/arm/boot/dts/armada-370-seagate-nas-xbay.dtsi |   63 +-
>  arch/arm/boot/dts/armada-370-xp.dtsi               |    6 +-
>  arch/arm/boot/dts/armada-375-db.dts                |   49 +-
>  arch/arm/boot/dts/armada-375.dtsi                  |    6 +-
>  arch/arm/boot/dts/armada-385-db-ap.dts             |   68 +-
>  arch/arm/boot/dts/armada-385-linksys-caiman.dts    |  129 +-
>  arch/arm/boot/dts/armada-385-linksys-cobra.dts     |  129 +-
>  arch/arm/boot/dts/armada-385-linksys-rango.dts     |  141 +-
>  arch/arm/boot/dts/armada-385-linksys-shelby.dts    |  129 +-
>  arch/arm/boot/dts/armada-385-linksys.dtsi          |   15 +-
>  arch/arm/boot/dts/armada-388-db.dts                |   54 +-
>  arch/arm/boot/dts/armada-38x.dtsi                  |    6 +-
>  arch/arm/boot/dts/armada-390-db.dts                |   65 +-
>  arch/arm/boot/dts/armada-395-gp.dts                |   73 +-
>  arch/arm/boot/dts/armada-398-db.dts                |   59 +-
>  arch/arm/boot/dts/armada-39x.dtsi                  |    6 +-
>  arch/arm/boot/dts/armada-xp-db-dxbc2.dts           |    1 -
>  arch/arm/boot/dts/armada-xp-db-xc3-24g4xg.dts      |    1 -
>  arch/arm/boot/dts/armada-xp-db.dts                 |    1 -
>  arch/arm/boot/dts/armada-xp-gp.dts                 |    1 -
>  arch/arm/boot/dts/armada-xp-lenovo-ix4-300d.dts    |    1 -
>  arch/arm/boot/dts/armada-xp-linksys-mamba.dts      |  155 +-
>  arch/arm/boot/dts/armada-xp-netgear-rn2120.dts     |   89 +-
>  arch/arm/boot/dts/pxa3xx.dtsi                      |    6 +-
>  arch/arm64/boot/dts/marvell/armada-7040-db.dts     |   47 +-
>  .../boot/dts/marvell/armada-cp110-master.dtsi      |    6 +-
>  .../arm64/boot/dts/marvell/armada-cp110-slave.dtsi |    6 +-
>  drivers/mtd/nand/Kconfig                           |   11 +
>  drivers/mtd/nand/Makefile                          |    1 +
>  drivers/mtd/nand/atmel/nand-controller.c           |    9 +-
>  drivers/mtd/nand/bf5xx_nand.c                      |    6 +-
>  drivers/mtd/nand/brcmnand/brcmnand.c               |   20 +-
>  drivers/mtd/nand/cafe_nand.c                       |   20 +-
>  drivers/mtd/nand/denali.c                          |  109 +-
>  drivers/mtd/nand/diskonchip.c                      |    4 +-
>  drivers/mtd/nand/docg4.c                           |   19 +-
>  drivers/mtd/nand/fsl_elbc_nand.c                   |    6 +-
>  drivers/mtd/nand/fsl_ifc_nand.c                    |    6 +-
>  drivers/mtd/nand/fsmc_nand.c                       |    5 +-
>  drivers/mtd/nand/gpmi-nand/gpmi-nand.c             |   76 +-
>  drivers/mtd/nand/hisi504_nand.c                    |    9 +-
>  drivers/mtd/nand/jz4740_nand.c                     |   16 +-
>  drivers/mtd/nand/lpc32xx_mlc.c                     |    7 +-
>  drivers/mtd/nand/lpc32xx_slc.c                     |   33 +-
>  drivers/mtd/nand/marvell_nand.c                    | 2384 ++++++++++++++++++++
>  drivers/mtd/nand/mtk_nand.c                        |   21 +-
>  drivers/mtd/nand/nand_base.c                       | 2020 ++++++++++++++---
>  drivers/mtd/nand/nand_hynix.c                      |  106 +-
>  drivers/mtd/nand/nand_micron.c                     |   50 +-
>  drivers/mtd/nand/nand_timings.c                    |   23 +-
>  drivers/mtd/nand/omap2.c                           |    8 +-
>  drivers/mtd/nand/pxa3xx_nand.c                     |    8 +-
>  drivers/mtd/nand/qcom_nandc.c                      |   16 +-
>  drivers/mtd/nand/r852.c                            |   11 +-
>  drivers/mtd/nand/sh_flctl.c                        |    6 +-
>  drivers/mtd/nand/sunxi_nand.c                      |   97 +-
>  drivers/mtd/nand/tango_nand.c                      |   27 +-
>  drivers/mtd/nand/tmio_nand.c                       |    5 +-
>  drivers/mtd/nand/vf610_nfc.c                       |    6 +-
>  drivers/staging/mt29f_spinand/mt29f_spinand.c      |    7 +-
>  include/linux/mtd/rawnand.h                        |  391 +++-
>  68 files changed, 5980 insertions(+), 1424 deletions(-)
>  create mode 100644 Documentation/devicetree/bindings/mtd/marvell-nand.txt
>  create mode 100644 drivers/mtd/nand/marvell_nand.c
> 

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [RFC 06/12] mtd: nand: add reworked Marvell NAND controller driver
  2017-10-18 14:36   ` Miquel Raynal
  (?)
@ 2017-10-19  7:18     ` Boris Brezillon
  -1 siblings, 0 replies; 75+ messages in thread
From: Boris Brezillon @ 2017-10-19  7:18 UTC (permalink / raw)
  To: Miquel Raynal
  Cc: Mark Rutland, Andrew Lunn, Catalin Marinas, Hanna Hawa,
	Will Deacon, Stefan Agner, Nadav Haklai, Masahiro Yamada,
	linux-mtd, Matthias Brugger, Robert Jarzmik, devel,
	Maxim Levitsky, Kamal Dasu, Josh Wu, Russell King, Marek Vasut,
	Chen-Yu Tsai, bcm-kernel-feedback-list, Sebastian Hesselbarth,
	Ezequiel Garcia, Sylvain Lemieux, Marc Gonzalez

On Wed, 18 Oct 2017 16:36:23 +0200
Miquel Raynal <miquel.raynal@free-electrons.com> wrote:


> +
> +enum marvell_nfc_variant {
> +	MARVELL_NFC_VARIANT_PXA3XX,
> +	MARVELL_NFC_VARIANT_ARMADA370,
> +	MARVELL_NFC_VARIANT_ARMADA_8K,
> +};
> +
> +/*
> + * NAND controller capabilities for distinction between compatible strings
> + *
> + * @variant:		Board type
> + * @max_cs_nb:		Number of Chip Select lines available
> + * @max_rb_nb:		Number of Ready/Busy lines available
> + * @legacy_of_bindings	Indicates if DT parsing must be done using the old
> + *			fashion way
> + */
> +struct marvell_nfc_caps {
> +	enum marvell_nfc_variant variant;

Do you really need this variant field. It seems you're only using it to
detect if you should MUX the NAND pins using the Special Function
registers on armada8k. Maybe you should just have:

	bool need_system_controller;

and set it to true for the "armada8k" compatible.

> +	unsigned int max_cs_nb;
> +	unsigned int max_rb_nb;
> +	bool legacy_of_bindings;
> +};
> +

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [RFC 06/12] mtd: nand: add reworked Marvell NAND controller driver
@ 2017-10-19  7:18     ` Boris Brezillon
  0 siblings, 0 replies; 75+ messages in thread
From: Boris Brezillon @ 2017-10-19  7:18 UTC (permalink / raw)
  To: Miquel Raynal
  Cc: Andrew Lunn, bcm-kernel-feedback-list, Brian Norris,
	Catalin Marinas, Chen-Yu Tsai, Cyrille Pitchen, Daniel Mack,
	David Woodhouse, devel, devicetree, Ezequiel Garcia,
	Greg Kroah-Hartman, Gregory Clement, Han Xu, Haojian Zhuang,
	Jason Cooper, Josh Wu, Kamal Dasu, linux-arm-kernel,
	linux-kernel, linux-mediatek, linux-mtd, Marc Gonzalez,
	Marek Vasut, Mark Rutland, Masahiro Yamada, Matthias Brugger,
	Maxime Ripard, Maxim Levitsky, Richard Weinberger,
	Robert Jarzmik, Rob Herring, Russell King, Sebastian Hesselbarth,
	Stefan Agner, Sylvain Lemieux, Vladimir Zapolskiy, Wenyou Yang,
	Will Deacon, Thomas Petazzoni, Antoine Tenart, Igor Grinberg,
	Nadav Haklai, Ofer Heifetz, Neta Zur Hershkovits, Hanna Hawa

On Wed, 18 Oct 2017 16:36:23 +0200
Miquel Raynal <miquel.raynal@free-electrons.com> wrote:


> +
> +enum marvell_nfc_variant {
> +	MARVELL_NFC_VARIANT_PXA3XX,
> +	MARVELL_NFC_VARIANT_ARMADA370,
> +	MARVELL_NFC_VARIANT_ARMADA_8K,
> +};
> +
> +/*
> + * NAND controller capabilities for distinction between compatible strings
> + *
> + * @variant:		Board type
> + * @max_cs_nb:		Number of Chip Select lines available
> + * @max_rb_nb:		Number of Ready/Busy lines available
> + * @legacy_of_bindings	Indicates if DT parsing must be done using the old
> + *			fashion way
> + */
> +struct marvell_nfc_caps {
> +	enum marvell_nfc_variant variant;

Do you really need this variant field. It seems you're only using it to
detect if you should MUX the NAND pins using the Special Function
registers on armada8k. Maybe you should just have:

	bool need_system_controller;

and set it to true for the "armada8k" compatible.

> +	unsigned int max_cs_nb;
> +	unsigned int max_rb_nb;
> +	bool legacy_of_bindings;
> +};
> +

^ permalink raw reply	[flat|nested] 75+ messages in thread

* [RFC 06/12] mtd: nand: add reworked Marvell NAND controller driver
@ 2017-10-19  7:18     ` Boris Brezillon
  0 siblings, 0 replies; 75+ messages in thread
From: Boris Brezillon @ 2017-10-19  7:18 UTC (permalink / raw)
  To: linux-arm-kernel

On Wed, 18 Oct 2017 16:36:23 +0200
Miquel Raynal <miquel.raynal@free-electrons.com> wrote:


> +
> +enum marvell_nfc_variant {
> +	MARVELL_NFC_VARIANT_PXA3XX,
> +	MARVELL_NFC_VARIANT_ARMADA370,
> +	MARVELL_NFC_VARIANT_ARMADA_8K,
> +};
> +
> +/*
> + * NAND controller capabilities for distinction between compatible strings
> + *
> + * @variant:		Board type
> + * @max_cs_nb:		Number of Chip Select lines available
> + * @max_rb_nb:		Number of Ready/Busy lines available
> + * @legacy_of_bindings	Indicates if DT parsing must be done using the old
> + *			fashion way
> + */
> +struct marvell_nfc_caps {
> +	enum marvell_nfc_variant variant;

Do you really need this variant field. It seems you're only using it to
detect if you should MUX the NAND pins using the Special Function
registers on armada8k. Maybe you should just have:

	bool need_system_controller;

and set it to true for the "armada8k" compatible.

> +	unsigned int max_cs_nb;
> +	unsigned int max_rb_nb;
> +	bool legacy_of_bindings;
> +};
> +

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [RFC 00/12] Marvell NAND controller rework with ->exec_op()
  2017-10-18 22:29   ` Boris Brezillon
  (?)
  (?)
@ 2017-10-19  8:47   ` Marc Gonzalez
  2017-10-19 11:30     ` Boris Brezillon
  -1 siblings, 1 reply; 75+ messages in thread
From: Marc Gonzalez @ 2017-10-19  8:47 UTC (permalink / raw)
  To: Boris Brezillon, Miquel Raynal
  Cc: Andrew Lunn, Brian Norris, Catalin Marinas, Chen-Yu Tsai,
	Cyrille Pitchen, Daniel Mack, David Woodhouse, Ezequiel Garcia,
	Greg Kroah-Hartman, Gregory Clement, Han Xu, Haojian Zhuang,
	Jason Cooper, Josh Wu, Kamal Dasu, Marek Vasut, Mark Rutland,
	Masahiro Yamada, Matthias Brugger, Maxime Ripard, Maxim Levitsky,
	Richard Weinberger, Robert Jarzmik, Rob Herring, Russell King,
	Sebastian Hesselbarth, Stefan Agner, Sylvain Lemieux,
	Vladimir Zapolskiy, Wenyou Yang, Will Deacon, Thomas Petazzoni,
	Antoine Tenart, Igor Grinberg, Nadav Haklai, Ofer Heifetz,
	Neta Zur Hershkovits, Hanna Hawa, linux-mtd

[ Dropping all mailing lists except linux-mtd ]

On 19/10/2017 00:29, Boris Brezillon wrote:

> Still need to review patch 6, but I'd like to say I'm really happy to
> see that happen, finally. Hopefully that will help us make the NAND
> framework a bit easier to maintain and allow us to support advanced
> NAND features without having to patch all NAND controller drivers every
> time we add a new vendor specific operation. I also have in mind a few
> NAND controller drivers that have been rejected because they were not
> fitting in the ->cmd_ctrl() approach and I didn't want to add more
> drivers with their own partially implemented/broken ->cmdfunc() method.
> 
> Kudos for the work you've done, and I hope we'll get reviews from other
> active NAND driver maintainers/contributors. To all NAND driver
> maintainers: note that the long term goal is to get rid of
> ->cmd_ctrl()/cmdfunc()/read/write_buf/byte/word() entirely and replace
> them with a single ->exec_op() hook. Please let us know if you need
> extra things that are not yet exposed in nand_op_instr/nand_op_parser
> or if you would like things to be done differently.

A long time ago, we discussed a limitation of the API for reads
and writes handling only a single page at a time. My controller
is able to "burst" 1-15 contiguous pages.

Does this rewrite help with such a feature, or is it orthogonal?

Regards.

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [RFC 00/12] Marvell NAND controller rework with ->exec_op()
  2017-10-19  8:47   ` Marc Gonzalez
@ 2017-10-19 11:30     ` Boris Brezillon
  0 siblings, 0 replies; 75+ messages in thread
From: Boris Brezillon @ 2017-10-19 11:30 UTC (permalink / raw)
  To: Marc Gonzalez
  Cc: Miquel Raynal, Andrew Lunn, Brian Norris, Catalin Marinas,
	Chen-Yu Tsai, Cyrille Pitchen, Daniel Mack, David Woodhouse,
	Ezequiel Garcia, Greg Kroah-Hartman, Gregory Clement, Han Xu,
	Haojian Zhuang, Jason Cooper, Josh Wu, Kamal Dasu, Marek Vasut,
	Mark Rutland, Masahiro Yamada, Matthias Brugger, Maxime Ripard,
	Maxim Levitsky, Richard Weinberger, Robert Jarzmik, Rob Herring,
	Russell King, Sebastian Hesselbarth, Stefan Agner,
	Sylvain Lemieux, Vladimir Zapolskiy, Wenyou Yang, Will Deacon,
	Thomas Petazzoni, Antoine Tenart, Igor Grinberg, Nadav Haklai,
	Ofer Heifetz, Neta Zur Hershkovits, Hanna Hawa, linux-mtd

Hi Marc,

On Thu, 19 Oct 2017 10:47:07 +0200
Marc Gonzalez <marc_gonzalez@sigmadesigns.com> wrote:

> [ Dropping all mailing lists except linux-mtd ]
> 
> On 19/10/2017 00:29, Boris Brezillon wrote:
> 
> > Still need to review patch 6, but I'd like to say I'm really happy to
> > see that happen, finally. Hopefully that will help us make the NAND
> > framework a bit easier to maintain and allow us to support advanced
> > NAND features without having to patch all NAND controller drivers every
> > time we add a new vendor specific operation. I also have in mind a few
> > NAND controller drivers that have been rejected because they were not
> > fitting in the ->cmd_ctrl() approach and I didn't want to add more
> > drivers with their own partially implemented/broken ->cmdfunc() method.
> > 
> > Kudos for the work you've done, and I hope we'll get reviews from other
> > active NAND driver maintainers/contributors. To all NAND driver
> > maintainers: note that the long term goal is to get rid of  
> > ->cmd_ctrl()/cmdfunc()/read/write_buf/byte/word() entirely and replace  
> > them with a single ->exec_op() hook. Please let us know if you need
> > extra things that are not yet exposed in nand_op_instr/nand_op_parser
> > or if you would like things to be done differently.  
> 
> A long time ago, we discussed a limitation of the API for reads
> and writes handling only a single page at a time. My controller
> is able to "burst" 1-15 contiguous pages.
> 
> Does this rewrite help with such a feature, or is it orthogonal?

It's kind of orthogonal. To do what you want to do we'd need to extend
the current nand_chip/nand_ecc_ctrl interfaces to pass the MTD request
directly to the NAND controller driver and let it detect when a burst
access makes sense. Right now this is not my priority, but yes, I've
already thought a bit about how this interface would look like (see
this presentation [1] page 31).

Regards,

Boris

[1]https://events.linuxfoundation.org/sites/events/files/slides/brezillon-nand-framework_0.pdf

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [RFC 02/12] mtd: nand: force drivers to explicitly send READ/PROG commands
  2017-10-18 14:36   ` Miquel Raynal
  (?)
@ 2017-10-20  9:29     ` Stefan Agner
  -1 siblings, 0 replies; 75+ messages in thread
From: Stefan Agner @ 2017-10-20  9:29 UTC (permalink / raw)
  To: Miquel Raynal
  Cc: Mark Rutland, Andrew Lunn, Catalin Marinas, Hanna Hawa,
	Will Deacon, Nadav Haklai, Masahiro Yamada, linux-mtd,
	Matthias Brugger, Robert Jarzmik, devel, Boris Brezillon,
	Maxim Levitsky, Kamal Dasu, Josh Wu, Russell King, Marek Vasut,
	Chen-Yu Tsai, bcm-kernel-feedback-list, Sebastian Hesselbarth,
	Ezequiel Garcia, Sylvain Lemieux, Marc

Hi Miquel,

Thanks for the work on this, happy to see the new interface is moving
forward. Some comments below.

On 2017-10-18 16:36, Miquel Raynal wrote:
> From: Boris Brezillon <boris.brezillon@free-electrons.com>
> 
> The core currently send the READ0 and SEQIN+PAGEPROG commands in
> nand_do_read/write_ops(). This is inconsistent with
> ->read/write_oob[_raw]() hooks behavior which are expected to send
> these commands.
> 
> There's already a flag (NAND_ECC_CUSTOM_PAGE_ACCESS) to inform the core
> that a specific controller wants to send the READ/SEQIN+PAGEPROG
> commands on its own, but it's an opt-in flag, and existing drivers are
> unlikely to be updated to pass it.
> 
> Moreover, some controllers cannot dissociate the READ/PAGEPROG commands
> from the associated data transfer and ECC engine activation, and
> developers have to hack things in their ->cmdfunc() implementation to
> handle such complex cases, or have to accept the perf penalty of sending
> twice the same command.
> To address this problem we are planning on adding a new interface which
> is passed all information about a NAND operation (including the amount
> of data to transfer) and replacing all calls to ->cmdfunc() to calls to
> this new ->exec_op() hook. But, in order to do that, we need to have all
> ->cmdfunc() calls placed near their associated ->read/write_buf/byte()
> calls.
> 
> Modify the core and relevant drivers to make NAND_ECC_CUSTOM_PAGE_ACCESS
> the default case, and remove this flag.
> 
> Signed-off-by: Boris Brezillon <boris.brezillon@free-electrons.com>
> [miquel.raynal@free-electrons.com: rebased and fixed some conflicts]
> Signed-off-by: Miquel Raynal <miquel.raynal@free-electrons.com>
> ---
>  drivers/mtd/nand/atmel/nand-controller.c      |  7 ++-
>  drivers/mtd/nand/bf5xx_nand.c                 |  6 ++-
>  drivers/mtd/nand/brcmnand/brcmnand.c          | 13 +++--
>  drivers/mtd/nand/cafe_nand.c                  |  6 +--
>  drivers/mtd/nand/denali.c                     | 14 +++++-
>  drivers/mtd/nand/docg4.c                      | 12 +++--
>  drivers/mtd/nand/fsl_elbc_nand.c              |  6 +--
>  drivers/mtd/nand/fsl_ifc_nand.c               |  6 +--
>  drivers/mtd/nand/gpmi-nand/gpmi-nand.c        | 30 ++++++------
>  drivers/mtd/nand/hisi504_nand.c               |  6 +--
>  drivers/mtd/nand/lpc32xx_mlc.c                |  5 +-
>  drivers/mtd/nand/lpc32xx_slc.c                | 11 +++--
>  drivers/mtd/nand/mtk_nand.c                   | 10 ++--
>  drivers/mtd/nand/nand_base.c                  | 68 +++++++++------------------
>  drivers/mtd/nand/nand_micron.c                |  1 -
>  drivers/mtd/nand/sh_flctl.c                   |  6 +--
>  drivers/mtd/nand/sunxi_nand.c                 | 34 +++++++++-----
>  drivers/mtd/nand/tango_nand.c                 |  1 -
>  drivers/mtd/nand/vf610_nfc.c                  |  6 +--
>  drivers/staging/mt29f_spinand/mt29f_spinand.c |  7 ++-
>  include/linux/mtd/rawnand.h                   | 11 -----
>  21 files changed, 142 insertions(+), 124 deletions(-)
> 

<snip>

> diff --git a/drivers/mtd/nand/gpmi-nand/gpmi-nand.c
> b/drivers/mtd/nand/gpmi-nand/gpmi-nand.c
> index c8ceaecd8065..3f2b903158c1 100644
> --- a/drivers/mtd/nand/gpmi-nand/gpmi-nand.c
> +++ b/drivers/mtd/nand/gpmi-nand/gpmi-nand.c
> @@ -1043,6 +1043,8 @@ static int gpmi_ecc_read_page(struct mtd_info
> *mtd, struct nand_chip *chip,
>  	unsigned int  max_bitflips = 0;
>  	int           ret;
>  
> +	nand_read_page_op(chip, page, 0, NULL, 0);
> +
>  	dev_dbg(this->dev, "page number is : %d\n", page);
>  	ret = read_page_prepare(this, buf, nfc_geo->payload_size,
>  					this->payload_virt, this->payload_phys,
> @@ -1220,12 +1222,13 @@ static int gpmi_ecc_read_subpage(struct
> mtd_info *mtd, struct nand_chip *chip,
>  	meta = geo->metadata_size;
>  	if (first) {
>  		col = meta + (size + ecc_parity_size) * first;
> -		chip->cmdfunc(mtd, NAND_CMD_RNDOUT, col, -1);

Shouldn't we add nand_change_read_column_op here?


>  
>  		meta = 0;
>  		buf = buf + first * size;
>  	}
>  
> +	nand_read_page_op(chip, page, col, NULL, 0);
> +
>  	/* Save the old environment */
>  	r1_old = r1_new = readl(bch_regs + HW_BCH_FLASH0LAYOUT0);
>  	r2_old = r2_new = readl(bch_regs + HW_BCH_FLASH0LAYOUT1);
> @@ -1277,6 +1280,9 @@ static int gpmi_ecc_write_page(struct mtd_info
> *mtd, struct nand_chip *chip,
>  	int        ret;
>  
>  	dev_dbg(this->dev, "ecc write page.\n");
> +
> +	nand_prog_page_begin_op(chip, page, 0, NULL, 0);
> +
>  	if (this->swap_block_mark) {
>  		/*
>  		 * If control arrives here, we're doing block mark swapping.
> @@ -1338,7 +1344,10 @@ static int gpmi_ecc_write_page(struct mtd_info
> *mtd, struct nand_chip *chip,
>  				payload_virt, payload_phys);
>  	}
>  
> -	return 0;
> +	if (ret)
> +		return ret;
> +
> +	return nand_prog_page_end_op(chip);
>  }
>  
>  /*
> @@ -1472,8 +1481,8 @@ static int gpmi_ecc_read_page_raw(struct mtd_info *mtd,
>  	uint8_t *oob = chip->oob_poi;
>  	int step;
>  
> -	chip->read_buf(mtd, tmp_buf,
> -		       mtd->writesize + mtd->oobsize);
> +	nand_read_page_op(chip, page, 0, tmp_buf,
> +			  mtd->writesize + mtd->oobsize);
>  
>  	/*
>  	 * If required, swap the bad block marker and the data stored in the
> @@ -1617,24 +1626,19 @@ static int gpmi_ecc_write_page_raw(struct mtd_info *mtd,
>  		tmp_buf[mtd->writesize] = swap;
>  	}
>  
> -	chip->write_buf(mtd, tmp_buf, mtd->writesize + mtd->oobsize);
> -
> -	return 0;
> +	return nand_prog_page_op(chip, page, 0, tmp_buf,
> +				 mtd->writesize + mtd->oobsize);
>  }
>  
>  static int gpmi_ecc_read_oob_raw(struct mtd_info *mtd, struct nand_chip *chip,
>  				 int page)
>  {
> -	nand_read_page_op(chip, page, 0, NULL, 0);
> -
>  	return gpmi_ecc_read_page_raw(mtd, chip, NULL, 1, page);
>  }
>  
>  static int gpmi_ecc_write_oob_raw(struct mtd_info *mtd, struct nand_chip *chip,
>  				 int page)
>  {
> -	nand_prog_page_begin_op(chip, page, 0, NULL, 0);
> -
>  	return gpmi_ecc_write_page_raw(mtd, chip, NULL, 1, page);
>  }
>  
> @@ -1806,9 +1810,7 @@ static int mx23_write_transcription_stamp(struct
> gpmi_nand_data *this)
>  		/* Write the first page of the current stride. */
>  		dev_dbg(dev, "Writing an NCB fingerprint in page 0x%x\n", page);
>  
> -		nand_prog_page_begin_op(chip, page, 0, NULL, 0);
> -		chip->ecc.write_page_raw(mtd, chip, buffer, 0, page);
> -		status = nand_prog_page_end_op(chip);
> +		status = chip->ecc.write_page_raw(mtd, chip, buffer, 0, page);
>  		if (status)
>  			dev_err(dev, "[%s] Write failed.\n", __func__);
>  	}

<snip>

> diff --git a/drivers/mtd/nand/vf610_nfc.c b/drivers/mtd/nand/vf610_nfc.c
> index 8037d4b48a05..80d31a58e558 100644
> --- a/drivers/mtd/nand/vf610_nfc.c
> +++ b/drivers/mtd/nand/vf610_nfc.c
> @@ -560,7 +560,7 @@ static int vf610_nfc_read_page(struct mtd_info
> *mtd, struct nand_chip *chip,
>  	int eccsize = chip->ecc.size;
>  	int stat;
>  
> -	vf610_nfc_read_buf(mtd, buf, eccsize);
> +	nand_read_page_op(chip, page, 0, buf, eccsize);
>  	if (oob_required)
>  		vf610_nfc_read_buf(mtd, chip->oob_poi, mtd->oobsize);
>  
> @@ -580,7 +580,7 @@ static int vf610_nfc_write_page(struct mtd_info
> *mtd, struct nand_chip *chip,
>  {
>  	struct vf610_nfc *nfc = mtd_to_nfc(mtd);
>  
> -	vf610_nfc_write_buf(mtd, buf, mtd->writesize);

We currently ignore NAND_CMD_SEQIN in ->cmdfunc anyway, so I think this
change would not even be necessary here.

This is a NAND controller which will benefit from the new interface. I
plan to convert the driver to the new interface, maybe I manage to do
that soon so we have a second driver making use of the new interface.


> +	nand_prog_page_begin_op(chip, page, 0, buf, mtd->writesize);
>  	if (oob_required)
>  		vf610_nfc_write_buf(mtd, chip->oob_poi, mtd->oobsize);
>  
> @@ -588,7 +588,7 @@ static int vf610_nfc_write_page(struct mtd_info
> *mtd, struct nand_chip *chip,
>  	nfc->use_hw_ecc = true;
>  	nfc->write_sz = mtd->writesize + mtd->oobsize;
>  
> -	return 0;
> +	return nand_prog_page_end_op(chip);
>  }
>  
>  static const struct of_device_id vf610_nfc_dt_ids[] = {

--
Stefan

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [RFC 02/12] mtd: nand: force drivers to explicitly send READ/PROG commands
@ 2017-10-20  9:29     ` Stefan Agner
  0 siblings, 0 replies; 75+ messages in thread
From: Stefan Agner @ 2017-10-20  9:29 UTC (permalink / raw)
  To: Miquel Raynal
  Cc: Andrew Lunn, bcm-kernel-feedback-list, Boris Brezillon,
	Brian Norris, Catalin Marinas, Chen-Yu Tsai, Cyrille Pitchen,
	Daniel Mack, David Woodhouse, devel, devicetree, Ezequiel Garcia,
	Greg Kroah-Hartman, Gregory Clement, Han Xu, Haojian Zhuang,
	Jason Cooper, Josh Wu, Kamal Dasu, linux-arm-kernel,
	linux-kernel, linux-mediatek, linux-mtd, Marc Gonzalez,
	Marek Vasut, Mark Rutland, Masahiro Yamada, Matthias Brugger,
	Maxime Ripard, Maxim Levitsky, Richard Weinberger,
	Robert Jarzmik, Rob Herring, Russell King, Sebastian Hesselbarth,
	Sylvain Lemieux, Vladimir Zapolskiy, Wenyou Yang, Will Deacon,
	Thomas Petazzoni, Antoine Tenart, Igor Grinberg, Nadav Haklai,
	Ofer Heifetz, Neta Zur Hershkovits, Hanna Hawa

Hi Miquel,

Thanks for the work on this, happy to see the new interface is moving
forward. Some comments below.

On 2017-10-18 16:36, Miquel Raynal wrote:
> From: Boris Brezillon <boris.brezillon@free-electrons.com>
> 
> The core currently send the READ0 and SEQIN+PAGEPROG commands in
> nand_do_read/write_ops(). This is inconsistent with
> ->read/write_oob[_raw]() hooks behavior which are expected to send
> these commands.
> 
> There's already a flag (NAND_ECC_CUSTOM_PAGE_ACCESS) to inform the core
> that a specific controller wants to send the READ/SEQIN+PAGEPROG
> commands on its own, but it's an opt-in flag, and existing drivers are
> unlikely to be updated to pass it.
> 
> Moreover, some controllers cannot dissociate the READ/PAGEPROG commands
> from the associated data transfer and ECC engine activation, and
> developers have to hack things in their ->cmdfunc() implementation to
> handle such complex cases, or have to accept the perf penalty of sending
> twice the same command.
> To address this problem we are planning on adding a new interface which
> is passed all information about a NAND operation (including the amount
> of data to transfer) and replacing all calls to ->cmdfunc() to calls to
> this new ->exec_op() hook. But, in order to do that, we need to have all
> ->cmdfunc() calls placed near their associated ->read/write_buf/byte()
> calls.
> 
> Modify the core and relevant drivers to make NAND_ECC_CUSTOM_PAGE_ACCESS
> the default case, and remove this flag.
> 
> Signed-off-by: Boris Brezillon <boris.brezillon@free-electrons.com>
> [miquel.raynal@free-electrons.com: rebased and fixed some conflicts]
> Signed-off-by: Miquel Raynal <miquel.raynal@free-electrons.com>
> ---
>  drivers/mtd/nand/atmel/nand-controller.c      |  7 ++-
>  drivers/mtd/nand/bf5xx_nand.c                 |  6 ++-
>  drivers/mtd/nand/brcmnand/brcmnand.c          | 13 +++--
>  drivers/mtd/nand/cafe_nand.c                  |  6 +--
>  drivers/mtd/nand/denali.c                     | 14 +++++-
>  drivers/mtd/nand/docg4.c                      | 12 +++--
>  drivers/mtd/nand/fsl_elbc_nand.c              |  6 +--
>  drivers/mtd/nand/fsl_ifc_nand.c               |  6 +--
>  drivers/mtd/nand/gpmi-nand/gpmi-nand.c        | 30 ++++++------
>  drivers/mtd/nand/hisi504_nand.c               |  6 +--
>  drivers/mtd/nand/lpc32xx_mlc.c                |  5 +-
>  drivers/mtd/nand/lpc32xx_slc.c                | 11 +++--
>  drivers/mtd/nand/mtk_nand.c                   | 10 ++--
>  drivers/mtd/nand/nand_base.c                  | 68 +++++++++------------------
>  drivers/mtd/nand/nand_micron.c                |  1 -
>  drivers/mtd/nand/sh_flctl.c                   |  6 +--
>  drivers/mtd/nand/sunxi_nand.c                 | 34 +++++++++-----
>  drivers/mtd/nand/tango_nand.c                 |  1 -
>  drivers/mtd/nand/vf610_nfc.c                  |  6 +--
>  drivers/staging/mt29f_spinand/mt29f_spinand.c |  7 ++-
>  include/linux/mtd/rawnand.h                   | 11 -----
>  21 files changed, 142 insertions(+), 124 deletions(-)
> 

<snip>

> diff --git a/drivers/mtd/nand/gpmi-nand/gpmi-nand.c
> b/drivers/mtd/nand/gpmi-nand/gpmi-nand.c
> index c8ceaecd8065..3f2b903158c1 100644
> --- a/drivers/mtd/nand/gpmi-nand/gpmi-nand.c
> +++ b/drivers/mtd/nand/gpmi-nand/gpmi-nand.c
> @@ -1043,6 +1043,8 @@ static int gpmi_ecc_read_page(struct mtd_info
> *mtd, struct nand_chip *chip,
>  	unsigned int  max_bitflips = 0;
>  	int           ret;
>  
> +	nand_read_page_op(chip, page, 0, NULL, 0);
> +
>  	dev_dbg(this->dev, "page number is : %d\n", page);
>  	ret = read_page_prepare(this, buf, nfc_geo->payload_size,
>  					this->payload_virt, this->payload_phys,
> @@ -1220,12 +1222,13 @@ static int gpmi_ecc_read_subpage(struct
> mtd_info *mtd, struct nand_chip *chip,
>  	meta = geo->metadata_size;
>  	if (first) {
>  		col = meta + (size + ecc_parity_size) * first;
> -		chip->cmdfunc(mtd, NAND_CMD_RNDOUT, col, -1);

Shouldn't we add nand_change_read_column_op here?


>  
>  		meta = 0;
>  		buf = buf + first * size;
>  	}
>  
> +	nand_read_page_op(chip, page, col, NULL, 0);
> +
>  	/* Save the old environment */
>  	r1_old = r1_new = readl(bch_regs + HW_BCH_FLASH0LAYOUT0);
>  	r2_old = r2_new = readl(bch_regs + HW_BCH_FLASH0LAYOUT1);
> @@ -1277,6 +1280,9 @@ static int gpmi_ecc_write_page(struct mtd_info
> *mtd, struct nand_chip *chip,
>  	int        ret;
>  
>  	dev_dbg(this->dev, "ecc write page.\n");
> +
> +	nand_prog_page_begin_op(chip, page, 0, NULL, 0);
> +
>  	if (this->swap_block_mark) {
>  		/*
>  		 * If control arrives here, we're doing block mark swapping.
> @@ -1338,7 +1344,10 @@ static int gpmi_ecc_write_page(struct mtd_info
> *mtd, struct nand_chip *chip,
>  				payload_virt, payload_phys);
>  	}
>  
> -	return 0;
> +	if (ret)
> +		return ret;
> +
> +	return nand_prog_page_end_op(chip);
>  }
>  
>  /*
> @@ -1472,8 +1481,8 @@ static int gpmi_ecc_read_page_raw(struct mtd_info *mtd,
>  	uint8_t *oob = chip->oob_poi;
>  	int step;
>  
> -	chip->read_buf(mtd, tmp_buf,
> -		       mtd->writesize + mtd->oobsize);
> +	nand_read_page_op(chip, page, 0, tmp_buf,
> +			  mtd->writesize + mtd->oobsize);
>  
>  	/*
>  	 * If required, swap the bad block marker and the data stored in the
> @@ -1617,24 +1626,19 @@ static int gpmi_ecc_write_page_raw(struct mtd_info *mtd,
>  		tmp_buf[mtd->writesize] = swap;
>  	}
>  
> -	chip->write_buf(mtd, tmp_buf, mtd->writesize + mtd->oobsize);
> -
> -	return 0;
> +	return nand_prog_page_op(chip, page, 0, tmp_buf,
> +				 mtd->writesize + mtd->oobsize);
>  }
>  
>  static int gpmi_ecc_read_oob_raw(struct mtd_info *mtd, struct nand_chip *chip,
>  				 int page)
>  {
> -	nand_read_page_op(chip, page, 0, NULL, 0);
> -
>  	return gpmi_ecc_read_page_raw(mtd, chip, NULL, 1, page);
>  }
>  
>  static int gpmi_ecc_write_oob_raw(struct mtd_info *mtd, struct nand_chip *chip,
>  				 int page)
>  {
> -	nand_prog_page_begin_op(chip, page, 0, NULL, 0);
> -
>  	return gpmi_ecc_write_page_raw(mtd, chip, NULL, 1, page);
>  }
>  
> @@ -1806,9 +1810,7 @@ static int mx23_write_transcription_stamp(struct
> gpmi_nand_data *this)
>  		/* Write the first page of the current stride. */
>  		dev_dbg(dev, "Writing an NCB fingerprint in page 0x%x\n", page);
>  
> -		nand_prog_page_begin_op(chip, page, 0, NULL, 0);
> -		chip->ecc.write_page_raw(mtd, chip, buffer, 0, page);
> -		status = nand_prog_page_end_op(chip);
> +		status = chip->ecc.write_page_raw(mtd, chip, buffer, 0, page);
>  		if (status)
>  			dev_err(dev, "[%s] Write failed.\n", __func__);
>  	}

<snip>

> diff --git a/drivers/mtd/nand/vf610_nfc.c b/drivers/mtd/nand/vf610_nfc.c
> index 8037d4b48a05..80d31a58e558 100644
> --- a/drivers/mtd/nand/vf610_nfc.c
> +++ b/drivers/mtd/nand/vf610_nfc.c
> @@ -560,7 +560,7 @@ static int vf610_nfc_read_page(struct mtd_info
> *mtd, struct nand_chip *chip,
>  	int eccsize = chip->ecc.size;
>  	int stat;
>  
> -	vf610_nfc_read_buf(mtd, buf, eccsize);
> +	nand_read_page_op(chip, page, 0, buf, eccsize);
>  	if (oob_required)
>  		vf610_nfc_read_buf(mtd, chip->oob_poi, mtd->oobsize);
>  
> @@ -580,7 +580,7 @@ static int vf610_nfc_write_page(struct mtd_info
> *mtd, struct nand_chip *chip,
>  {
>  	struct vf610_nfc *nfc = mtd_to_nfc(mtd);
>  
> -	vf610_nfc_write_buf(mtd, buf, mtd->writesize);

We currently ignore NAND_CMD_SEQIN in ->cmdfunc anyway, so I think this
change would not even be necessary here.

This is a NAND controller which will benefit from the new interface. I
plan to convert the driver to the new interface, maybe I manage to do
that soon so we have a second driver making use of the new interface.


> +	nand_prog_page_begin_op(chip, page, 0, buf, mtd->writesize);
>  	if (oob_required)
>  		vf610_nfc_write_buf(mtd, chip->oob_poi, mtd->oobsize);
>  
> @@ -588,7 +588,7 @@ static int vf610_nfc_write_page(struct mtd_info
> *mtd, struct nand_chip *chip,
>  	nfc->use_hw_ecc = true;
>  	nfc->write_sz = mtd->writesize + mtd->oobsize;
>  
> -	return 0;
> +	return nand_prog_page_end_op(chip);
>  }
>  
>  static const struct of_device_id vf610_nfc_dt_ids[] = {

--
Stefan

^ permalink raw reply	[flat|nested] 75+ messages in thread

* [RFC 02/12] mtd: nand: force drivers to explicitly send READ/PROG commands
@ 2017-10-20  9:29     ` Stefan Agner
  0 siblings, 0 replies; 75+ messages in thread
From: Stefan Agner @ 2017-10-20  9:29 UTC (permalink / raw)
  To: linux-arm-kernel

Hi Miquel,

Thanks for the work on this, happy to see the new interface is moving
forward. Some comments below.

On 2017-10-18 16:36, Miquel Raynal wrote:
> From: Boris Brezillon <boris.brezillon@free-electrons.com>
> 
> The core currently send the READ0 and SEQIN+PAGEPROG commands in
> nand_do_read/write_ops(). This is inconsistent with
> ->read/write_oob[_raw]() hooks behavior which are expected to send
> these commands.
> 
> There's already a flag (NAND_ECC_CUSTOM_PAGE_ACCESS) to inform the core
> that a specific controller wants to send the READ/SEQIN+PAGEPROG
> commands on its own, but it's an opt-in flag, and existing drivers are
> unlikely to be updated to pass it.
> 
> Moreover, some controllers cannot dissociate the READ/PAGEPROG commands
> from the associated data transfer and ECC engine activation, and
> developers have to hack things in their ->cmdfunc() implementation to
> handle such complex cases, or have to accept the perf penalty of sending
> twice the same command.
> To address this problem we are planning on adding a new interface which
> is passed all information about a NAND operation (including the amount
> of data to transfer) and replacing all calls to ->cmdfunc() to calls to
> this new ->exec_op() hook. But, in order to do that, we need to have all
> ->cmdfunc() calls placed near their associated ->read/write_buf/byte()
> calls.
> 
> Modify the core and relevant drivers to make NAND_ECC_CUSTOM_PAGE_ACCESS
> the default case, and remove this flag.
> 
> Signed-off-by: Boris Brezillon <boris.brezillon@free-electrons.com>
> [miquel.raynal at free-electrons.com: rebased and fixed some conflicts]
> Signed-off-by: Miquel Raynal <miquel.raynal@free-electrons.com>
> ---
>  drivers/mtd/nand/atmel/nand-controller.c      |  7 ++-
>  drivers/mtd/nand/bf5xx_nand.c                 |  6 ++-
>  drivers/mtd/nand/brcmnand/brcmnand.c          | 13 +++--
>  drivers/mtd/nand/cafe_nand.c                  |  6 +--
>  drivers/mtd/nand/denali.c                     | 14 +++++-
>  drivers/mtd/nand/docg4.c                      | 12 +++--
>  drivers/mtd/nand/fsl_elbc_nand.c              |  6 +--
>  drivers/mtd/nand/fsl_ifc_nand.c               |  6 +--
>  drivers/mtd/nand/gpmi-nand/gpmi-nand.c        | 30 ++++++------
>  drivers/mtd/nand/hisi504_nand.c               |  6 +--
>  drivers/mtd/nand/lpc32xx_mlc.c                |  5 +-
>  drivers/mtd/nand/lpc32xx_slc.c                | 11 +++--
>  drivers/mtd/nand/mtk_nand.c                   | 10 ++--
>  drivers/mtd/nand/nand_base.c                  | 68 +++++++++------------------
>  drivers/mtd/nand/nand_micron.c                |  1 -
>  drivers/mtd/nand/sh_flctl.c                   |  6 +--
>  drivers/mtd/nand/sunxi_nand.c                 | 34 +++++++++-----
>  drivers/mtd/nand/tango_nand.c                 |  1 -
>  drivers/mtd/nand/vf610_nfc.c                  |  6 +--
>  drivers/staging/mt29f_spinand/mt29f_spinand.c |  7 ++-
>  include/linux/mtd/rawnand.h                   | 11 -----
>  21 files changed, 142 insertions(+), 124 deletions(-)
> 

<snip>

> diff --git a/drivers/mtd/nand/gpmi-nand/gpmi-nand.c
> b/drivers/mtd/nand/gpmi-nand/gpmi-nand.c
> index c8ceaecd8065..3f2b903158c1 100644
> --- a/drivers/mtd/nand/gpmi-nand/gpmi-nand.c
> +++ b/drivers/mtd/nand/gpmi-nand/gpmi-nand.c
> @@ -1043,6 +1043,8 @@ static int gpmi_ecc_read_page(struct mtd_info
> *mtd, struct nand_chip *chip,
>  	unsigned int  max_bitflips = 0;
>  	int           ret;
>  
> +	nand_read_page_op(chip, page, 0, NULL, 0);
> +
>  	dev_dbg(this->dev, "page number is : %d\n", page);
>  	ret = read_page_prepare(this, buf, nfc_geo->payload_size,
>  					this->payload_virt, this->payload_phys,
> @@ -1220,12 +1222,13 @@ static int gpmi_ecc_read_subpage(struct
> mtd_info *mtd, struct nand_chip *chip,
>  	meta = geo->metadata_size;
>  	if (first) {
>  		col = meta + (size + ecc_parity_size) * first;
> -		chip->cmdfunc(mtd, NAND_CMD_RNDOUT, col, -1);

Shouldn't we add nand_change_read_column_op here?


>  
>  		meta = 0;
>  		buf = buf + first * size;
>  	}
>  
> +	nand_read_page_op(chip, page, col, NULL, 0);
> +
>  	/* Save the old environment */
>  	r1_old = r1_new = readl(bch_regs + HW_BCH_FLASH0LAYOUT0);
>  	r2_old = r2_new = readl(bch_regs + HW_BCH_FLASH0LAYOUT1);
> @@ -1277,6 +1280,9 @@ static int gpmi_ecc_write_page(struct mtd_info
> *mtd, struct nand_chip *chip,
>  	int        ret;
>  
>  	dev_dbg(this->dev, "ecc write page.\n");
> +
> +	nand_prog_page_begin_op(chip, page, 0, NULL, 0);
> +
>  	if (this->swap_block_mark) {
>  		/*
>  		 * If control arrives here, we're doing block mark swapping.
> @@ -1338,7 +1344,10 @@ static int gpmi_ecc_write_page(struct mtd_info
> *mtd, struct nand_chip *chip,
>  				payload_virt, payload_phys);
>  	}
>  
> -	return 0;
> +	if (ret)
> +		return ret;
> +
> +	return nand_prog_page_end_op(chip);
>  }
>  
>  /*
> @@ -1472,8 +1481,8 @@ static int gpmi_ecc_read_page_raw(struct mtd_info *mtd,
>  	uint8_t *oob = chip->oob_poi;
>  	int step;
>  
> -	chip->read_buf(mtd, tmp_buf,
> -		       mtd->writesize + mtd->oobsize);
> +	nand_read_page_op(chip, page, 0, tmp_buf,
> +			  mtd->writesize + mtd->oobsize);
>  
>  	/*
>  	 * If required, swap the bad block marker and the data stored in the
> @@ -1617,24 +1626,19 @@ static int gpmi_ecc_write_page_raw(struct mtd_info *mtd,
>  		tmp_buf[mtd->writesize] = swap;
>  	}
>  
> -	chip->write_buf(mtd, tmp_buf, mtd->writesize + mtd->oobsize);
> -
> -	return 0;
> +	return nand_prog_page_op(chip, page, 0, tmp_buf,
> +				 mtd->writesize + mtd->oobsize);
>  }
>  
>  static int gpmi_ecc_read_oob_raw(struct mtd_info *mtd, struct nand_chip *chip,
>  				 int page)
>  {
> -	nand_read_page_op(chip, page, 0, NULL, 0);
> -
>  	return gpmi_ecc_read_page_raw(mtd, chip, NULL, 1, page);
>  }
>  
>  static int gpmi_ecc_write_oob_raw(struct mtd_info *mtd, struct nand_chip *chip,
>  				 int page)
>  {
> -	nand_prog_page_begin_op(chip, page, 0, NULL, 0);
> -
>  	return gpmi_ecc_write_page_raw(mtd, chip, NULL, 1, page);
>  }
>  
> @@ -1806,9 +1810,7 @@ static int mx23_write_transcription_stamp(struct
> gpmi_nand_data *this)
>  		/* Write the first page of the current stride. */
>  		dev_dbg(dev, "Writing an NCB fingerprint in page 0x%x\n", page);
>  
> -		nand_prog_page_begin_op(chip, page, 0, NULL, 0);
> -		chip->ecc.write_page_raw(mtd, chip, buffer, 0, page);
> -		status = nand_prog_page_end_op(chip);
> +		status = chip->ecc.write_page_raw(mtd, chip, buffer, 0, page);
>  		if (status)
>  			dev_err(dev, "[%s] Write failed.\n", __func__);
>  	}

<snip>

> diff --git a/drivers/mtd/nand/vf610_nfc.c b/drivers/mtd/nand/vf610_nfc.c
> index 8037d4b48a05..80d31a58e558 100644
> --- a/drivers/mtd/nand/vf610_nfc.c
> +++ b/drivers/mtd/nand/vf610_nfc.c
> @@ -560,7 +560,7 @@ static int vf610_nfc_read_page(struct mtd_info
> *mtd, struct nand_chip *chip,
>  	int eccsize = chip->ecc.size;
>  	int stat;
>  
> -	vf610_nfc_read_buf(mtd, buf, eccsize);
> +	nand_read_page_op(chip, page, 0, buf, eccsize);
>  	if (oob_required)
>  		vf610_nfc_read_buf(mtd, chip->oob_poi, mtd->oobsize);
>  
> @@ -580,7 +580,7 @@ static int vf610_nfc_write_page(struct mtd_info
> *mtd, struct nand_chip *chip,
>  {
>  	struct vf610_nfc *nfc = mtd_to_nfc(mtd);
>  
> -	vf610_nfc_write_buf(mtd, buf, mtd->writesize);

We currently ignore NAND_CMD_SEQIN in ->cmdfunc anyway, so I think this
change would not even be necessary here.

This is a NAND controller which will benefit from the new interface. I
plan to convert the driver to the new interface, maybe I manage to do
that soon so we have a second driver making use of the new interface.


> +	nand_prog_page_begin_op(chip, page, 0, buf, mtd->writesize);
>  	if (oob_required)
>  		vf610_nfc_write_buf(mtd, chip->oob_poi, mtd->oobsize);
>  
> @@ -588,7 +588,7 @@ static int vf610_nfc_write_page(struct mtd_info
> *mtd, struct nand_chip *chip,
>  	nfc->use_hw_ecc = true;
>  	nfc->write_sz = mtd->writesize + mtd->oobsize;
>  
> -	return 0;
> +	return nand_prog_page_end_op(chip);
>  }
>  
>  static const struct of_device_id vf610_nfc_dt_ids[] = {

--
Stefan

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [RFC 02/12] mtd: nand: force drivers to explicitly send READ/PROG commands
  2017-10-20  9:29     ` Stefan Agner
  (?)
@ 2017-10-20 11:18       ` Boris Brezillon
  -1 siblings, 0 replies; 75+ messages in thread
From: Boris Brezillon @ 2017-10-20 11:18 UTC (permalink / raw)
  To: Stefan Agner
  Cc: Mark Rutland, Andrew Lunn, Catalin Marinas, Hanna Hawa,
	Will Deacon, Nadav Haklai, Masahiro Yamada, linux-mtd,
	Matthias Brugger, Robert Jarzmik, devel, Maxim Levitsky,
	Kamal Dasu, Josh Wu, Russell King, Marek Vasut, Chen-Yu Tsai,
	bcm-kernel-feedback-list, Sebastian Hesselbarth, Ezequiel Garcia,
	Sylvain Lemieux, Marc Gonzalez, devicetree

On Fri, 20 Oct 2017 11:29:18 +0200
Stefan Agner <stefan@agner.ch> wrote:

> Hi Miquel,
> 
> Thanks for the work on this, happy to see the new interface is moving
> forward. Some comments below.
> 
> On 2017-10-18 16:36, Miquel Raynal wrote:
> > From: Boris Brezillon <boris.brezillon@free-electrons.com>
> > 
> > The core currently send the READ0 and SEQIN+PAGEPROG commands in
> > nand_do_read/write_ops(). This is inconsistent with  
> > ->read/write_oob[_raw]() hooks behavior which are expected to send  
> > these commands.
> > 
> > There's already a flag (NAND_ECC_CUSTOM_PAGE_ACCESS) to inform the core
> > that a specific controller wants to send the READ/SEQIN+PAGEPROG
> > commands on its own, but it's an opt-in flag, and existing drivers are
> > unlikely to be updated to pass it.
> > 
> > Moreover, some controllers cannot dissociate the READ/PAGEPROG commands
> > from the associated data transfer and ECC engine activation, and
> > developers have to hack things in their ->cmdfunc() implementation to
> > handle such complex cases, or have to accept the perf penalty of sending
> > twice the same command.
> > To address this problem we are planning on adding a new interface which
> > is passed all information about a NAND operation (including the amount
> > of data to transfer) and replacing all calls to ->cmdfunc() to calls to
> > this new ->exec_op() hook. But, in order to do that, we need to have all  
> > ->cmdfunc() calls placed near their associated ->read/write_buf/byte()  
> > calls.
> > 
> > Modify the core and relevant drivers to make NAND_ECC_CUSTOM_PAGE_ACCESS
> > the default case, and remove this flag.
> > 
> > Signed-off-by: Boris Brezillon <boris.brezillon@free-electrons.com>
> > [miquel.raynal@free-electrons.com: rebased and fixed some conflicts]
> > Signed-off-by: Miquel Raynal <miquel.raynal@free-electrons.com>
> > ---
> >  drivers/mtd/nand/atmel/nand-controller.c      |  7 ++-
> >  drivers/mtd/nand/bf5xx_nand.c                 |  6 ++-
> >  drivers/mtd/nand/brcmnand/brcmnand.c          | 13 +++--
> >  drivers/mtd/nand/cafe_nand.c                  |  6 +--
> >  drivers/mtd/nand/denali.c                     | 14 +++++-
> >  drivers/mtd/nand/docg4.c                      | 12 +++--
> >  drivers/mtd/nand/fsl_elbc_nand.c              |  6 +--
> >  drivers/mtd/nand/fsl_ifc_nand.c               |  6 +--
> >  drivers/mtd/nand/gpmi-nand/gpmi-nand.c        | 30 ++++++------
> >  drivers/mtd/nand/hisi504_nand.c               |  6 +--
> >  drivers/mtd/nand/lpc32xx_mlc.c                |  5 +-
> >  drivers/mtd/nand/lpc32xx_slc.c                | 11 +++--
> >  drivers/mtd/nand/mtk_nand.c                   | 10 ++--
> >  drivers/mtd/nand/nand_base.c                  | 68 +++++++++------------------
> >  drivers/mtd/nand/nand_micron.c                |  1 -
> >  drivers/mtd/nand/sh_flctl.c                   |  6 +--
> >  drivers/mtd/nand/sunxi_nand.c                 | 34 +++++++++-----
> >  drivers/mtd/nand/tango_nand.c                 |  1 -
> >  drivers/mtd/nand/vf610_nfc.c                  |  6 +--
> >  drivers/staging/mt29f_spinand/mt29f_spinand.c |  7 ++-
> >  include/linux/mtd/rawnand.h                   | 11 -----
> >  21 files changed, 142 insertions(+), 124 deletions(-)
> >   
> 
> <snip>
> 
> > diff --git a/drivers/mtd/nand/gpmi-nand/gpmi-nand.c
> > b/drivers/mtd/nand/gpmi-nand/gpmi-nand.c
> > index c8ceaecd8065..3f2b903158c1 100644
> > --- a/drivers/mtd/nand/gpmi-nand/gpmi-nand.c
> > +++ b/drivers/mtd/nand/gpmi-nand/gpmi-nand.c
> > @@ -1043,6 +1043,8 @@ static int gpmi_ecc_read_page(struct mtd_info
> > *mtd, struct nand_chip *chip,
> >  	unsigned int  max_bitflips = 0;
> >  	int           ret;
> >  
> > +	nand_read_page_op(chip, page, 0, NULL, 0);
> > +
> >  	dev_dbg(this->dev, "page number is : %d\n", page);
> >  	ret = read_page_prepare(this, buf, nfc_geo->payload_size,
> >  					this->payload_virt, this->payload_phys,
> > @@ -1220,12 +1222,13 @@ static int gpmi_ecc_read_subpage(struct
> > mtd_info *mtd, struct nand_chip *chip,
> >  	meta = geo->metadata_size;
> >  	if (first) {
> >  		col = meta + (size + ecc_parity_size) * first;
> > -		chip->cmdfunc(mtd, NAND_CMD_RNDOUT, col, -1);  
> 
> Shouldn't we add nand_change_read_column_op here?
> 
> 
> >  
> >  		meta = 0;
> >  		buf = buf + first * size;
> >  	}
> >  
> > +	nand_read_page_op(chip, page, col, NULL, 0);
> > +
> >  	/* Save the old environment */
> >  	r1_old = r1_new = readl(bch_regs + HW_BCH_FLASH0LAYOUT0);
> >  	r2_old = r2_new = readl(bch_regs + HW_BCH_FLASH0LAYOUT1);
> > @@ -1277,6 +1280,9 @@ static int gpmi_ecc_write_page(struct mtd_info
> > *mtd, struct nand_chip *chip,
> >  	int        ret;
> >  
> >  	dev_dbg(this->dev, "ecc write page.\n");
> > +
> > +	nand_prog_page_begin_op(chip, page, 0, NULL, 0);
> > +
> >  	if (this->swap_block_mark) {
> >  		/*
> >  		 * If control arrives here, we're doing block mark swapping.
> > @@ -1338,7 +1344,10 @@ static int gpmi_ecc_write_page(struct mtd_info
> > *mtd, struct nand_chip *chip,
> >  				payload_virt, payload_phys);
> >  	}
> >  
> > -	return 0;
> > +	if (ret)
> > +		return ret;
> > +
> > +	return nand_prog_page_end_op(chip);
> >  }
> >  
> >  /*
> > @@ -1472,8 +1481,8 @@ static int gpmi_ecc_read_page_raw(struct mtd_info *mtd,
> >  	uint8_t *oob = chip->oob_poi;
> >  	int step;
> >  
> > -	chip->read_buf(mtd, tmp_buf,
> > -		       mtd->writesize + mtd->oobsize);
> > +	nand_read_page_op(chip, page, 0, tmp_buf,
> > +			  mtd->writesize + mtd->oobsize);
> >  
> >  	/*
> >  	 * If required, swap the bad block marker and the data stored in the
> > @@ -1617,24 +1626,19 @@ static int gpmi_ecc_write_page_raw(struct mtd_info *mtd,
> >  		tmp_buf[mtd->writesize] = swap;
> >  	}
> >  
> > -	chip->write_buf(mtd, tmp_buf, mtd->writesize + mtd->oobsize);
> > -
> > -	return 0;
> > +	return nand_prog_page_op(chip, page, 0, tmp_buf,
> > +				 mtd->writesize + mtd->oobsize);
> >  }
> >  
> >  static int gpmi_ecc_read_oob_raw(struct mtd_info *mtd, struct nand_chip *chip,
> >  				 int page)
> >  {
> > -	nand_read_page_op(chip, page, 0, NULL, 0);
> > -
> >  	return gpmi_ecc_read_page_raw(mtd, chip, NULL, 1, page);
> >  }
> >  
> >  static int gpmi_ecc_write_oob_raw(struct mtd_info *mtd, struct nand_chip *chip,
> >  				 int page)
> >  {
> > -	nand_prog_page_begin_op(chip, page, 0, NULL, 0);
> > -
> >  	return gpmi_ecc_write_page_raw(mtd, chip, NULL, 1, page);
> >  }
> >  
> > @@ -1806,9 +1810,7 @@ static int mx23_write_transcription_stamp(struct
> > gpmi_nand_data *this)
> >  		/* Write the first page of the current stride. */
> >  		dev_dbg(dev, "Writing an NCB fingerprint in page 0x%x\n", page);
> >  
> > -		nand_prog_page_begin_op(chip, page, 0, NULL, 0);
> > -		chip->ecc.write_page_raw(mtd, chip, buffer, 0, page);
> > -		status = nand_prog_page_end_op(chip);
> > +		status = chip->ecc.write_page_raw(mtd, chip, buffer, 0, page);
> >  		if (status)
> >  			dev_err(dev, "[%s] Write failed.\n", __func__);
> >  	}  
> 
> <snip>
> 
> > diff --git a/drivers/mtd/nand/vf610_nfc.c b/drivers/mtd/nand/vf610_nfc.c
> > index 8037d4b48a05..80d31a58e558 100644
> > --- a/drivers/mtd/nand/vf610_nfc.c
> > +++ b/drivers/mtd/nand/vf610_nfc.c
> > @@ -560,7 +560,7 @@ static int vf610_nfc_read_page(struct mtd_info
> > *mtd, struct nand_chip *chip,
> >  	int eccsize = chip->ecc.size;
> >  	int stat;
> >  
> > -	vf610_nfc_read_buf(mtd, buf, eccsize);
> > +	nand_read_page_op(chip, page, 0, buf, eccsize);
> >  	if (oob_required)
> >  		vf610_nfc_read_buf(mtd, chip->oob_poi, mtd->oobsize);
> >  
> > @@ -580,7 +580,7 @@ static int vf610_nfc_write_page(struct mtd_info
> > *mtd, struct nand_chip *chip,
> >  {
> >  	struct vf610_nfc *nfc = mtd_to_nfc(mtd);
> >  
> > -	vf610_nfc_write_buf(mtd, buf, mtd->writesize);  
> 
> We currently ignore NAND_CMD_SEQIN in ->cmdfunc anyway, so I think this
> change would not even be necessary here.

Well, the thing is, we're trying to keep the logic unchanged. Even if
for your driver ->cmdfunc(NAND_CMD_SEQIN) is a NOP, I'd prefer to be
consistent and still replace this ->write_buf() call by a
nand_prog_page_begin_op() one.

> 
> This is a NAND controller which will benefit from the new interface. I
> plan to convert the driver to the new interface, maybe I manage to do
> that soon so we have a second driver making use of the new interface.

Great!!! Let us know if you need any help.

Regards,

Boris

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [RFC 02/12] mtd: nand: force drivers to explicitly send READ/PROG commands
@ 2017-10-20 11:18       ` Boris Brezillon
  0 siblings, 0 replies; 75+ messages in thread
From: Boris Brezillon @ 2017-10-20 11:18 UTC (permalink / raw)
  To: Stefan Agner
  Cc: Miquel Raynal, Andrew Lunn, bcm-kernel-feedback-list,
	Brian Norris, Catalin Marinas, Chen-Yu Tsai, Cyrille Pitchen,
	Daniel Mack, David Woodhouse, devel, devicetree, Ezequiel Garcia,
	Greg Kroah-Hartman, Gregory Clement, Han Xu, Haojian Zhuang,
	Jason Cooper, Josh Wu, Kamal Dasu, linux-arm-kernel,
	linux-kernel, linux-mediatek, linux-mtd, Marc Gonzalez,
	Marek Vasut, Mark Rutland, Masahiro Yamada, Matthias Brugger,
	Maxime Ripard, Maxim Levitsky, Richard Weinberger,
	Robert Jarzmik, Rob Herring, Russell King, Sebastian Hesselbarth,
	Sylvain Lemieux, Vladimir Zapolskiy, Wenyou Yang, Will Deacon,
	Thomas Petazzoni, Antoine Tenart, Igor Grinberg, Nadav Haklai,
	Ofer Heifetz, Neta Zur Hershkovits, Hanna Hawa

On Fri, 20 Oct 2017 11:29:18 +0200
Stefan Agner <stefan@agner.ch> wrote:

> Hi Miquel,
> 
> Thanks for the work on this, happy to see the new interface is moving
> forward. Some comments below.
> 
> On 2017-10-18 16:36, Miquel Raynal wrote:
> > From: Boris Brezillon <boris.brezillon@free-electrons.com>
> > 
> > The core currently send the READ0 and SEQIN+PAGEPROG commands in
> > nand_do_read/write_ops(). This is inconsistent with  
> > ->read/write_oob[_raw]() hooks behavior which are expected to send  
> > these commands.
> > 
> > There's already a flag (NAND_ECC_CUSTOM_PAGE_ACCESS) to inform the core
> > that a specific controller wants to send the READ/SEQIN+PAGEPROG
> > commands on its own, but it's an opt-in flag, and existing drivers are
> > unlikely to be updated to pass it.
> > 
> > Moreover, some controllers cannot dissociate the READ/PAGEPROG commands
> > from the associated data transfer and ECC engine activation, and
> > developers have to hack things in their ->cmdfunc() implementation to
> > handle such complex cases, or have to accept the perf penalty of sending
> > twice the same command.
> > To address this problem we are planning on adding a new interface which
> > is passed all information about a NAND operation (including the amount
> > of data to transfer) and replacing all calls to ->cmdfunc() to calls to
> > this new ->exec_op() hook. But, in order to do that, we need to have all  
> > ->cmdfunc() calls placed near their associated ->read/write_buf/byte()  
> > calls.
> > 
> > Modify the core and relevant drivers to make NAND_ECC_CUSTOM_PAGE_ACCESS
> > the default case, and remove this flag.
> > 
> > Signed-off-by: Boris Brezillon <boris.brezillon@free-electrons.com>
> > [miquel.raynal@free-electrons.com: rebased and fixed some conflicts]
> > Signed-off-by: Miquel Raynal <miquel.raynal@free-electrons.com>
> > ---
> >  drivers/mtd/nand/atmel/nand-controller.c      |  7 ++-
> >  drivers/mtd/nand/bf5xx_nand.c                 |  6 ++-
> >  drivers/mtd/nand/brcmnand/brcmnand.c          | 13 +++--
> >  drivers/mtd/nand/cafe_nand.c                  |  6 +--
> >  drivers/mtd/nand/denali.c                     | 14 +++++-
> >  drivers/mtd/nand/docg4.c                      | 12 +++--
> >  drivers/mtd/nand/fsl_elbc_nand.c              |  6 +--
> >  drivers/mtd/nand/fsl_ifc_nand.c               |  6 +--
> >  drivers/mtd/nand/gpmi-nand/gpmi-nand.c        | 30 ++++++------
> >  drivers/mtd/nand/hisi504_nand.c               |  6 +--
> >  drivers/mtd/nand/lpc32xx_mlc.c                |  5 +-
> >  drivers/mtd/nand/lpc32xx_slc.c                | 11 +++--
> >  drivers/mtd/nand/mtk_nand.c                   | 10 ++--
> >  drivers/mtd/nand/nand_base.c                  | 68 +++++++++------------------
> >  drivers/mtd/nand/nand_micron.c                |  1 -
> >  drivers/mtd/nand/sh_flctl.c                   |  6 +--
> >  drivers/mtd/nand/sunxi_nand.c                 | 34 +++++++++-----
> >  drivers/mtd/nand/tango_nand.c                 |  1 -
> >  drivers/mtd/nand/vf610_nfc.c                  |  6 +--
> >  drivers/staging/mt29f_spinand/mt29f_spinand.c |  7 ++-
> >  include/linux/mtd/rawnand.h                   | 11 -----
> >  21 files changed, 142 insertions(+), 124 deletions(-)
> >   
> 
> <snip>
> 
> > diff --git a/drivers/mtd/nand/gpmi-nand/gpmi-nand.c
> > b/drivers/mtd/nand/gpmi-nand/gpmi-nand.c
> > index c8ceaecd8065..3f2b903158c1 100644
> > --- a/drivers/mtd/nand/gpmi-nand/gpmi-nand.c
> > +++ b/drivers/mtd/nand/gpmi-nand/gpmi-nand.c
> > @@ -1043,6 +1043,8 @@ static int gpmi_ecc_read_page(struct mtd_info
> > *mtd, struct nand_chip *chip,
> >  	unsigned int  max_bitflips = 0;
> >  	int           ret;
> >  
> > +	nand_read_page_op(chip, page, 0, NULL, 0);
> > +
> >  	dev_dbg(this->dev, "page number is : %d\n", page);
> >  	ret = read_page_prepare(this, buf, nfc_geo->payload_size,
> >  					this->payload_virt, this->payload_phys,
> > @@ -1220,12 +1222,13 @@ static int gpmi_ecc_read_subpage(struct
> > mtd_info *mtd, struct nand_chip *chip,
> >  	meta = geo->metadata_size;
> >  	if (first) {
> >  		col = meta + (size + ecc_parity_size) * first;
> > -		chip->cmdfunc(mtd, NAND_CMD_RNDOUT, col, -1);  
> 
> Shouldn't we add nand_change_read_column_op here?
> 
> 
> >  
> >  		meta = 0;
> >  		buf = buf + first * size;
> >  	}
> >  
> > +	nand_read_page_op(chip, page, col, NULL, 0);
> > +
> >  	/* Save the old environment */
> >  	r1_old = r1_new = readl(bch_regs + HW_BCH_FLASH0LAYOUT0);
> >  	r2_old = r2_new = readl(bch_regs + HW_BCH_FLASH0LAYOUT1);
> > @@ -1277,6 +1280,9 @@ static int gpmi_ecc_write_page(struct mtd_info
> > *mtd, struct nand_chip *chip,
> >  	int        ret;
> >  
> >  	dev_dbg(this->dev, "ecc write page.\n");
> > +
> > +	nand_prog_page_begin_op(chip, page, 0, NULL, 0);
> > +
> >  	if (this->swap_block_mark) {
> >  		/*
> >  		 * If control arrives here, we're doing block mark swapping.
> > @@ -1338,7 +1344,10 @@ static int gpmi_ecc_write_page(struct mtd_info
> > *mtd, struct nand_chip *chip,
> >  				payload_virt, payload_phys);
> >  	}
> >  
> > -	return 0;
> > +	if (ret)
> > +		return ret;
> > +
> > +	return nand_prog_page_end_op(chip);
> >  }
> >  
> >  /*
> > @@ -1472,8 +1481,8 @@ static int gpmi_ecc_read_page_raw(struct mtd_info *mtd,
> >  	uint8_t *oob = chip->oob_poi;
> >  	int step;
> >  
> > -	chip->read_buf(mtd, tmp_buf,
> > -		       mtd->writesize + mtd->oobsize);
> > +	nand_read_page_op(chip, page, 0, tmp_buf,
> > +			  mtd->writesize + mtd->oobsize);
> >  
> >  	/*
> >  	 * If required, swap the bad block marker and the data stored in the
> > @@ -1617,24 +1626,19 @@ static int gpmi_ecc_write_page_raw(struct mtd_info *mtd,
> >  		tmp_buf[mtd->writesize] = swap;
> >  	}
> >  
> > -	chip->write_buf(mtd, tmp_buf, mtd->writesize + mtd->oobsize);
> > -
> > -	return 0;
> > +	return nand_prog_page_op(chip, page, 0, tmp_buf,
> > +				 mtd->writesize + mtd->oobsize);
> >  }
> >  
> >  static int gpmi_ecc_read_oob_raw(struct mtd_info *mtd, struct nand_chip *chip,
> >  				 int page)
> >  {
> > -	nand_read_page_op(chip, page, 0, NULL, 0);
> > -
> >  	return gpmi_ecc_read_page_raw(mtd, chip, NULL, 1, page);
> >  }
> >  
> >  static int gpmi_ecc_write_oob_raw(struct mtd_info *mtd, struct nand_chip *chip,
> >  				 int page)
> >  {
> > -	nand_prog_page_begin_op(chip, page, 0, NULL, 0);
> > -
> >  	return gpmi_ecc_write_page_raw(mtd, chip, NULL, 1, page);
> >  }
> >  
> > @@ -1806,9 +1810,7 @@ static int mx23_write_transcription_stamp(struct
> > gpmi_nand_data *this)
> >  		/* Write the first page of the current stride. */
> >  		dev_dbg(dev, "Writing an NCB fingerprint in page 0x%x\n", page);
> >  
> > -		nand_prog_page_begin_op(chip, page, 0, NULL, 0);
> > -		chip->ecc.write_page_raw(mtd, chip, buffer, 0, page);
> > -		status = nand_prog_page_end_op(chip);
> > +		status = chip->ecc.write_page_raw(mtd, chip, buffer, 0, page);
> >  		if (status)
> >  			dev_err(dev, "[%s] Write failed.\n", __func__);
> >  	}  
> 
> <snip>
> 
> > diff --git a/drivers/mtd/nand/vf610_nfc.c b/drivers/mtd/nand/vf610_nfc.c
> > index 8037d4b48a05..80d31a58e558 100644
> > --- a/drivers/mtd/nand/vf610_nfc.c
> > +++ b/drivers/mtd/nand/vf610_nfc.c
> > @@ -560,7 +560,7 @@ static int vf610_nfc_read_page(struct mtd_info
> > *mtd, struct nand_chip *chip,
> >  	int eccsize = chip->ecc.size;
> >  	int stat;
> >  
> > -	vf610_nfc_read_buf(mtd, buf, eccsize);
> > +	nand_read_page_op(chip, page, 0, buf, eccsize);
> >  	if (oob_required)
> >  		vf610_nfc_read_buf(mtd, chip->oob_poi, mtd->oobsize);
> >  
> > @@ -580,7 +580,7 @@ static int vf610_nfc_write_page(struct mtd_info
> > *mtd, struct nand_chip *chip,
> >  {
> >  	struct vf610_nfc *nfc = mtd_to_nfc(mtd);
> >  
> > -	vf610_nfc_write_buf(mtd, buf, mtd->writesize);  
> 
> We currently ignore NAND_CMD_SEQIN in ->cmdfunc anyway, so I think this
> change would not even be necessary here.

Well, the thing is, we're trying to keep the logic unchanged. Even if
for your driver ->cmdfunc(NAND_CMD_SEQIN) is a NOP, I'd prefer to be
consistent and still replace this ->write_buf() call by a
nand_prog_page_begin_op() one.

> 
> This is a NAND controller which will benefit from the new interface. I
> plan to convert the driver to the new interface, maybe I manage to do
> that soon so we have a second driver making use of the new interface.

Great!!! Let us know if you need any help.

Regards,

Boris

^ permalink raw reply	[flat|nested] 75+ messages in thread

* [RFC 02/12] mtd: nand: force drivers to explicitly send READ/PROG commands
@ 2017-10-20 11:18       ` Boris Brezillon
  0 siblings, 0 replies; 75+ messages in thread
From: Boris Brezillon @ 2017-10-20 11:18 UTC (permalink / raw)
  To: linux-arm-kernel

On Fri, 20 Oct 2017 11:29:18 +0200
Stefan Agner <stefan@agner.ch> wrote:

> Hi Miquel,
> 
> Thanks for the work on this, happy to see the new interface is moving
> forward. Some comments below.
> 
> On 2017-10-18 16:36, Miquel Raynal wrote:
> > From: Boris Brezillon <boris.brezillon@free-electrons.com>
> > 
> > The core currently send the READ0 and SEQIN+PAGEPROG commands in
> > nand_do_read/write_ops(). This is inconsistent with  
> > ->read/write_oob[_raw]() hooks behavior which are expected to send  
> > these commands.
> > 
> > There's already a flag (NAND_ECC_CUSTOM_PAGE_ACCESS) to inform the core
> > that a specific controller wants to send the READ/SEQIN+PAGEPROG
> > commands on its own, but it's an opt-in flag, and existing drivers are
> > unlikely to be updated to pass it.
> > 
> > Moreover, some controllers cannot dissociate the READ/PAGEPROG commands
> > from the associated data transfer and ECC engine activation, and
> > developers have to hack things in their ->cmdfunc() implementation to
> > handle such complex cases, or have to accept the perf penalty of sending
> > twice the same command.
> > To address this problem we are planning on adding a new interface which
> > is passed all information about a NAND operation (including the amount
> > of data to transfer) and replacing all calls to ->cmdfunc() to calls to
> > this new ->exec_op() hook. But, in order to do that, we need to have all  
> > ->cmdfunc() calls placed near their associated ->read/write_buf/byte()  
> > calls.
> > 
> > Modify the core and relevant drivers to make NAND_ECC_CUSTOM_PAGE_ACCESS
> > the default case, and remove this flag.
> > 
> > Signed-off-by: Boris Brezillon <boris.brezillon@free-electrons.com>
> > [miquel.raynal at free-electrons.com: rebased and fixed some conflicts]
> > Signed-off-by: Miquel Raynal <miquel.raynal@free-electrons.com>
> > ---
> >  drivers/mtd/nand/atmel/nand-controller.c      |  7 ++-
> >  drivers/mtd/nand/bf5xx_nand.c                 |  6 ++-
> >  drivers/mtd/nand/brcmnand/brcmnand.c          | 13 +++--
> >  drivers/mtd/nand/cafe_nand.c                  |  6 +--
> >  drivers/mtd/nand/denali.c                     | 14 +++++-
> >  drivers/mtd/nand/docg4.c                      | 12 +++--
> >  drivers/mtd/nand/fsl_elbc_nand.c              |  6 +--
> >  drivers/mtd/nand/fsl_ifc_nand.c               |  6 +--
> >  drivers/mtd/nand/gpmi-nand/gpmi-nand.c        | 30 ++++++------
> >  drivers/mtd/nand/hisi504_nand.c               |  6 +--
> >  drivers/mtd/nand/lpc32xx_mlc.c                |  5 +-
> >  drivers/mtd/nand/lpc32xx_slc.c                | 11 +++--
> >  drivers/mtd/nand/mtk_nand.c                   | 10 ++--
> >  drivers/mtd/nand/nand_base.c                  | 68 +++++++++------------------
> >  drivers/mtd/nand/nand_micron.c                |  1 -
> >  drivers/mtd/nand/sh_flctl.c                   |  6 +--
> >  drivers/mtd/nand/sunxi_nand.c                 | 34 +++++++++-----
> >  drivers/mtd/nand/tango_nand.c                 |  1 -
> >  drivers/mtd/nand/vf610_nfc.c                  |  6 +--
> >  drivers/staging/mt29f_spinand/mt29f_spinand.c |  7 ++-
> >  include/linux/mtd/rawnand.h                   | 11 -----
> >  21 files changed, 142 insertions(+), 124 deletions(-)
> >   
> 
> <snip>
> 
> > diff --git a/drivers/mtd/nand/gpmi-nand/gpmi-nand.c
> > b/drivers/mtd/nand/gpmi-nand/gpmi-nand.c
> > index c8ceaecd8065..3f2b903158c1 100644
> > --- a/drivers/mtd/nand/gpmi-nand/gpmi-nand.c
> > +++ b/drivers/mtd/nand/gpmi-nand/gpmi-nand.c
> > @@ -1043,6 +1043,8 @@ static int gpmi_ecc_read_page(struct mtd_info
> > *mtd, struct nand_chip *chip,
> >  	unsigned int  max_bitflips = 0;
> >  	int           ret;
> >  
> > +	nand_read_page_op(chip, page, 0, NULL, 0);
> > +
> >  	dev_dbg(this->dev, "page number is : %d\n", page);
> >  	ret = read_page_prepare(this, buf, nfc_geo->payload_size,
> >  					this->payload_virt, this->payload_phys,
> > @@ -1220,12 +1222,13 @@ static int gpmi_ecc_read_subpage(struct
> > mtd_info *mtd, struct nand_chip *chip,
> >  	meta = geo->metadata_size;
> >  	if (first) {
> >  		col = meta + (size + ecc_parity_size) * first;
> > -		chip->cmdfunc(mtd, NAND_CMD_RNDOUT, col, -1);  
> 
> Shouldn't we add nand_change_read_column_op here?
> 
> 
> >  
> >  		meta = 0;
> >  		buf = buf + first * size;
> >  	}
> >  
> > +	nand_read_page_op(chip, page, col, NULL, 0);
> > +
> >  	/* Save the old environment */
> >  	r1_old = r1_new = readl(bch_regs + HW_BCH_FLASH0LAYOUT0);
> >  	r2_old = r2_new = readl(bch_regs + HW_BCH_FLASH0LAYOUT1);
> > @@ -1277,6 +1280,9 @@ static int gpmi_ecc_write_page(struct mtd_info
> > *mtd, struct nand_chip *chip,
> >  	int        ret;
> >  
> >  	dev_dbg(this->dev, "ecc write page.\n");
> > +
> > +	nand_prog_page_begin_op(chip, page, 0, NULL, 0);
> > +
> >  	if (this->swap_block_mark) {
> >  		/*
> >  		 * If control arrives here, we're doing block mark swapping.
> > @@ -1338,7 +1344,10 @@ static int gpmi_ecc_write_page(struct mtd_info
> > *mtd, struct nand_chip *chip,
> >  				payload_virt, payload_phys);
> >  	}
> >  
> > -	return 0;
> > +	if (ret)
> > +		return ret;
> > +
> > +	return nand_prog_page_end_op(chip);
> >  }
> >  
> >  /*
> > @@ -1472,8 +1481,8 @@ static int gpmi_ecc_read_page_raw(struct mtd_info *mtd,
> >  	uint8_t *oob = chip->oob_poi;
> >  	int step;
> >  
> > -	chip->read_buf(mtd, tmp_buf,
> > -		       mtd->writesize + mtd->oobsize);
> > +	nand_read_page_op(chip, page, 0, tmp_buf,
> > +			  mtd->writesize + mtd->oobsize);
> >  
> >  	/*
> >  	 * If required, swap the bad block marker and the data stored in the
> > @@ -1617,24 +1626,19 @@ static int gpmi_ecc_write_page_raw(struct mtd_info *mtd,
> >  		tmp_buf[mtd->writesize] = swap;
> >  	}
> >  
> > -	chip->write_buf(mtd, tmp_buf, mtd->writesize + mtd->oobsize);
> > -
> > -	return 0;
> > +	return nand_prog_page_op(chip, page, 0, tmp_buf,
> > +				 mtd->writesize + mtd->oobsize);
> >  }
> >  
> >  static int gpmi_ecc_read_oob_raw(struct mtd_info *mtd, struct nand_chip *chip,
> >  				 int page)
> >  {
> > -	nand_read_page_op(chip, page, 0, NULL, 0);
> > -
> >  	return gpmi_ecc_read_page_raw(mtd, chip, NULL, 1, page);
> >  }
> >  
> >  static int gpmi_ecc_write_oob_raw(struct mtd_info *mtd, struct nand_chip *chip,
> >  				 int page)
> >  {
> > -	nand_prog_page_begin_op(chip, page, 0, NULL, 0);
> > -
> >  	return gpmi_ecc_write_page_raw(mtd, chip, NULL, 1, page);
> >  }
> >  
> > @@ -1806,9 +1810,7 @@ static int mx23_write_transcription_stamp(struct
> > gpmi_nand_data *this)
> >  		/* Write the first page of the current stride. */
> >  		dev_dbg(dev, "Writing an NCB fingerprint in page 0x%x\n", page);
> >  
> > -		nand_prog_page_begin_op(chip, page, 0, NULL, 0);
> > -		chip->ecc.write_page_raw(mtd, chip, buffer, 0, page);
> > -		status = nand_prog_page_end_op(chip);
> > +		status = chip->ecc.write_page_raw(mtd, chip, buffer, 0, page);
> >  		if (status)
> >  			dev_err(dev, "[%s] Write failed.\n", __func__);
> >  	}  
> 
> <snip>
> 
> > diff --git a/drivers/mtd/nand/vf610_nfc.c b/drivers/mtd/nand/vf610_nfc.c
> > index 8037d4b48a05..80d31a58e558 100644
> > --- a/drivers/mtd/nand/vf610_nfc.c
> > +++ b/drivers/mtd/nand/vf610_nfc.c
> > @@ -560,7 +560,7 @@ static int vf610_nfc_read_page(struct mtd_info
> > *mtd, struct nand_chip *chip,
> >  	int eccsize = chip->ecc.size;
> >  	int stat;
> >  
> > -	vf610_nfc_read_buf(mtd, buf, eccsize);
> > +	nand_read_page_op(chip, page, 0, buf, eccsize);
> >  	if (oob_required)
> >  		vf610_nfc_read_buf(mtd, chip->oob_poi, mtd->oobsize);
> >  
> > @@ -580,7 +580,7 @@ static int vf610_nfc_write_page(struct mtd_info
> > *mtd, struct nand_chip *chip,
> >  {
> >  	struct vf610_nfc *nfc = mtd_to_nfc(mtd);
> >  
> > -	vf610_nfc_write_buf(mtd, buf, mtd->writesize);  
> 
> We currently ignore NAND_CMD_SEQIN in ->cmdfunc anyway, so I think this
> change would not even be necessary here.

Well, the thing is, we're trying to keep the logic unchanged. Even if
for your driver ->cmdfunc(NAND_CMD_SEQIN) is a NOP, I'd prefer to be
consistent and still replace this ->write_buf() call by a
nand_prog_page_begin_op() one.

> 
> This is a NAND controller which will benefit from the new interface. I
> plan to convert the driver to the new interface, maybe I manage to do
> that soon so we have a second driver making use of the new interface.

Great!!! Let us know if you need any help.

Regards,

Boris

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [RFC 05/12] dt-bindings: mtd: add Marvell NAND controller documentation
  2017-10-18 14:36   ` Miquel Raynal
  (?)
@ 2017-10-24 19:04     ` Rob Herring
  -1 siblings, 0 replies; 75+ messages in thread
From: Rob Herring @ 2017-10-24 19:04 UTC (permalink / raw)
  To: Miquel Raynal
  Cc: Mark Rutland, Andrew Lunn, Catalin Marinas, Hanna Hawa,
	Will Deacon, Stefan Agner, Nadav Haklai, Masahiro Yamada,
	linux-mtd, Matthias Brugger, Robert Jarzmik, devel,
	Boris Brezillon, Maxim Levitsky, Kamal Dasu, Josh Wu,
	Russell King, Marek Vasut, Chen-Yu Tsai,
	bcm-kernel-feedback-list, Sebastian Hesselbarth, Ezequiel Garcia,
	Sylvain Lemieux

On Wed, Oct 18, 2017 at 04:36:22PM +0200, Miquel Raynal wrote:
> Document the bindings for the legacy and the new bindings relative to
> Marvell NAND controller driver rework.
> 
> Signed-off-by: Miquel Raynal <miquel.raynal@free-electrons.com>
> ---
>  .../devicetree/bindings/mtd/marvell-nand.txt       | 95 ++++++++++++++++++++++
>  1 file changed, 95 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/mtd/marvell-nand.txt
> 
> diff --git a/Documentation/devicetree/bindings/mtd/marvell-nand.txt b/Documentation/devicetree/bindings/mtd/marvell-nand.txt
> new file mode 100644
> index 000000000000..ea99f426c03f
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/mtd/marvell-nand.txt
> @@ -0,0 +1,95 @@
> +Marvell NAND Flash Controller (NFC)
> +
> +Required properties:
> +C'est faux, t'en a rajouté un y a pas longtps :).
> +Je conseille de mettre ça sous forme de liste, genre

Humm.

> +
> +- compatible: can be one of the following:
> +    * "marvell,armada-8k-nand-controller"
> +    * "marvell,armada370-nand-controller"
> +    * "marvell,pxa3xx-nand-controller"
> +    * "marvell,armada-8k-nand" (deprecated)
> +    * "marvell,armada370-nand" (deprecated)
> +    * "marvell,pxa3xx-nand" (deprecated)
> +- reg: shall contain registers location and length for data and reg.

2 regions?

> +- #address-cells: shall be set to 1. Encode the nand CS.
> +- #size-cells: shall be set to 0.
> +- interrupts: shall define the nand controller interrupt.
> +- clocks: shall reference nand controller clocks.

How many clocks?

> +- marvell,system-controller: Set to retrieve the syscon node that handles
> +  NAND controller related registers (only required with the
> +  "marvell,armada-8k-nand[-controller]" compatibles).
> +
> +Optional properties:
> +- dmas: shall reference DMA channel associated to the NAND controller.
> +- dma-names: shall be "rxtx".
> +
> +Optional children nodes:
> +Children nodes represent the available NAND chips.
> +
> +Required properties:
> +- reg: shall contain the native Chip Select ids (0-3)
> +- marvell,rb: shall contain the native Ready/Busy ids (0-1)
> +
> +Optional properties:
> +- marvell,nand-keep-config: orders the driver not to take the timings
> +  from the core and leaving them completely untouched. Bootloader
> +  timings will then be used.
> +- marvell,nand-enable-arbiter: only useful for PXA platforms, will
> +  enable bus arbiter between NFC and DFI bus (must be enabled for
> +  NFC operation)

Why do you need this if it must be enabled?

> +- nand-on-flash-bbt: speed up the boot process by not discovering all
> +  the bad blocks at each boot and reading directly an on flash table.
> +- nand-ecc-mode: one of the supported ECC modes ("none", "soft",
> +  "hw"). If not specified, hardware ECC will be used.
> +- nand-ecc-algo: algorithm to use if previous choice was "soft"
> +  ("hamming" or "bch). This property may be added for hardware ECC for
> +  clarification but will be ignored by the driver because ECC mode is
> +  chosen depending on the page size and the strength required by the
> +  NAND chip. This value may be overwritten with the nand-ecc-strength
> +  property.
> +- nand-ecc-strength: desired ECC strength.
> +- nand-ecc-step-size: indication on the ECC step size. This has no
> +  effect and will be ignored by the driver when using hardware
> +  ECC. Because Marvell's NAND flash controller does use fixed strength
> +  (1-bit for Hamming, 16-bit for BCH), the step size will shrink or
> +  grown in order to fit the required strength and the value
> +  updated. Step sizes are not completely random for all and follow
> +  certain patterns described in AN-379, "Marvell SoC NFC ECC".

For standard properties, just reference nand.txt and add any 
constraints. Don't define what the property is again.

> +
> +See Documentation/devicetree/bindings/mtd/nand.txt for more details on
> +generic bindings.
> +
> +
> +Example:
> +nand_controller: nand-controller@d0000 {
> +	compatible = "marvell,armada370-nand-controller";
> +	reg = <0xd0000 0x54>;
> +	#address-cells = <1>;
> +	#size-cells = <0>;
> +	interrupts = <GIC_SPI 84 IRQ_TYPE_LEVEL_HIGH>;
> +	clocks = <&coredivclk 0>;
> +	status = "okay";

Don't show status in examples.

> +
> +	nand@0 {
> +		reg = <0>;
> +		marvell,rb = <0>;
> +		nand-ecc-mode = "hw";
> +		marvell,nand-keep-config;
> +		marvell,nand-enable-arbiter;
> +		nand-on-flash-bbt;
> +		nand-ecc-strength = <4>;
> +		nand-ecc-step-size = <512>;
> +
> +		partitions {
> +			compatible = "fixed-partitions";
> +			#address-cells = <1>;
> +			#size-cells = <1>;
> +
> +			partition@0 {
> +				label = "Rootfs";
> +				reg = <0x00000000 0x40000000>;
> +			};
> +		};
> +	};
> +};
> -- 
> 2.11.0
> 

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [RFC 05/12] dt-bindings: mtd: add Marvell NAND controller documentation
@ 2017-10-24 19:04     ` Rob Herring
  0 siblings, 0 replies; 75+ messages in thread
From: Rob Herring @ 2017-10-24 19:04 UTC (permalink / raw)
  To: Miquel Raynal
  Cc: Andrew Lunn, bcm-kernel-feedback-list, Boris Brezillon,
	Brian Norris, Catalin Marinas, Chen-Yu Tsai, Cyrille Pitchen,
	Daniel Mack, David Woodhouse, devel, devicetree, Ezequiel Garcia,
	Greg Kroah-Hartman, Gregory Clement, Han Xu, Haojian Zhuang,
	Jason Cooper, Josh Wu, Kamal Dasu, linux-arm-kernel,
	linux-kernel, linux-mediatek, linux-mtd, Marc Gonzalez,
	Marek Vasut, Mark Rutland, Masahiro Yamada, Matthias Brugger,
	Maxime Ripard, Maxim Levitsky, Richard Weinberger,
	Robert Jarzmik, Russell King, Sebastian Hesselbarth,
	Stefan Agner, Sylvain Lemieux, Vladimir Zapolskiy, Wenyou Yang,
	Will Deacon, Thomas Petazzoni, Antoine Tenart, Igor Grinberg,
	Nadav Haklai, Ofer Heifetz, Neta Zur Hershkovits, Hanna Hawa

On Wed, Oct 18, 2017 at 04:36:22PM +0200, Miquel Raynal wrote:
> Document the bindings for the legacy and the new bindings relative to
> Marvell NAND controller driver rework.
> 
> Signed-off-by: Miquel Raynal <miquel.raynal@free-electrons.com>
> ---
>  .../devicetree/bindings/mtd/marvell-nand.txt       | 95 ++++++++++++++++++++++
>  1 file changed, 95 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/mtd/marvell-nand.txt
> 
> diff --git a/Documentation/devicetree/bindings/mtd/marvell-nand.txt b/Documentation/devicetree/bindings/mtd/marvell-nand.txt
> new file mode 100644
> index 000000000000..ea99f426c03f
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/mtd/marvell-nand.txt
> @@ -0,0 +1,95 @@
> +Marvell NAND Flash Controller (NFC)
> +
> +Required properties:
> +C'est faux, t'en a rajouté un y a pas longtps :).
> +Je conseille de mettre ça sous forme de liste, genre

Humm.

> +
> +- compatible: can be one of the following:
> +    * "marvell,armada-8k-nand-controller"
> +    * "marvell,armada370-nand-controller"
> +    * "marvell,pxa3xx-nand-controller"
> +    * "marvell,armada-8k-nand" (deprecated)
> +    * "marvell,armada370-nand" (deprecated)
> +    * "marvell,pxa3xx-nand" (deprecated)
> +- reg: shall contain registers location and length for data and reg.

2 regions?

> +- #address-cells: shall be set to 1. Encode the nand CS.
> +- #size-cells: shall be set to 0.
> +- interrupts: shall define the nand controller interrupt.
> +- clocks: shall reference nand controller clocks.

How many clocks?

> +- marvell,system-controller: Set to retrieve the syscon node that handles
> +  NAND controller related registers (only required with the
> +  "marvell,armada-8k-nand[-controller]" compatibles).
> +
> +Optional properties:
> +- dmas: shall reference DMA channel associated to the NAND controller.
> +- dma-names: shall be "rxtx".
> +
> +Optional children nodes:
> +Children nodes represent the available NAND chips.
> +
> +Required properties:
> +- reg: shall contain the native Chip Select ids (0-3)
> +- marvell,rb: shall contain the native Ready/Busy ids (0-1)
> +
> +Optional properties:
> +- marvell,nand-keep-config: orders the driver not to take the timings
> +  from the core and leaving them completely untouched. Bootloader
> +  timings will then be used.
> +- marvell,nand-enable-arbiter: only useful for PXA platforms, will
> +  enable bus arbiter between NFC and DFI bus (must be enabled for
> +  NFC operation)

Why do you need this if it must be enabled?

> +- nand-on-flash-bbt: speed up the boot process by not discovering all
> +  the bad blocks at each boot and reading directly an on flash table.
> +- nand-ecc-mode: one of the supported ECC modes ("none", "soft",
> +  "hw"). If not specified, hardware ECC will be used.
> +- nand-ecc-algo: algorithm to use if previous choice was "soft"
> +  ("hamming" or "bch). This property may be added for hardware ECC for
> +  clarification but will be ignored by the driver because ECC mode is
> +  chosen depending on the page size and the strength required by the
> +  NAND chip. This value may be overwritten with the nand-ecc-strength
> +  property.
> +- nand-ecc-strength: desired ECC strength.
> +- nand-ecc-step-size: indication on the ECC step size. This has no
> +  effect and will be ignored by the driver when using hardware
> +  ECC. Because Marvell's NAND flash controller does use fixed strength
> +  (1-bit for Hamming, 16-bit for BCH), the step size will shrink or
> +  grown in order to fit the required strength and the value
> +  updated. Step sizes are not completely random for all and follow
> +  certain patterns described in AN-379, "Marvell SoC NFC ECC".

For standard properties, just reference nand.txt and add any 
constraints. Don't define what the property is again.

> +
> +See Documentation/devicetree/bindings/mtd/nand.txt for more details on
> +generic bindings.
> +
> +
> +Example:
> +nand_controller: nand-controller@d0000 {
> +	compatible = "marvell,armada370-nand-controller";
> +	reg = <0xd0000 0x54>;
> +	#address-cells = <1>;
> +	#size-cells = <0>;
> +	interrupts = <GIC_SPI 84 IRQ_TYPE_LEVEL_HIGH>;
> +	clocks = <&coredivclk 0>;
> +	status = "okay";

Don't show status in examples.

> +
> +	nand@0 {
> +		reg = <0>;
> +		marvell,rb = <0>;
> +		nand-ecc-mode = "hw";
> +		marvell,nand-keep-config;
> +		marvell,nand-enable-arbiter;
> +		nand-on-flash-bbt;
> +		nand-ecc-strength = <4>;
> +		nand-ecc-step-size = <512>;
> +
> +		partitions {
> +			compatible = "fixed-partitions";
> +			#address-cells = <1>;
> +			#size-cells = <1>;
> +
> +			partition@0 {
> +				label = "Rootfs";
> +				reg = <0x00000000 0x40000000>;
> +			};
> +		};
> +	};
> +};
> -- 
> 2.11.0
> 

^ permalink raw reply	[flat|nested] 75+ messages in thread

* [RFC 05/12] dt-bindings: mtd: add Marvell NAND controller documentation
@ 2017-10-24 19:04     ` Rob Herring
  0 siblings, 0 replies; 75+ messages in thread
From: Rob Herring @ 2017-10-24 19:04 UTC (permalink / raw)
  To: linux-arm-kernel

On Wed, Oct 18, 2017 at 04:36:22PM +0200, Miquel Raynal wrote:
> Document the bindings for the legacy and the new bindings relative to
> Marvell NAND controller driver rework.
> 
> Signed-off-by: Miquel Raynal <miquel.raynal@free-electrons.com>
> ---
>  .../devicetree/bindings/mtd/marvell-nand.txt       | 95 ++++++++++++++++++++++
>  1 file changed, 95 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/mtd/marvell-nand.txt
> 
> diff --git a/Documentation/devicetree/bindings/mtd/marvell-nand.txt b/Documentation/devicetree/bindings/mtd/marvell-nand.txt
> new file mode 100644
> index 000000000000..ea99f426c03f
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/mtd/marvell-nand.txt
> @@ -0,0 +1,95 @@
> +Marvell NAND Flash Controller (NFC)
> +
> +Required properties:
> +C'est faux, t'en a rajout? un y a pas longtps :).
> +Je conseille de mettre ?a sous forme de liste, genre

Humm.

> +
> +- compatible: can be one of the following:
> +    * "marvell,armada-8k-nand-controller"
> +    * "marvell,armada370-nand-controller"
> +    * "marvell,pxa3xx-nand-controller"
> +    * "marvell,armada-8k-nand" (deprecated)
> +    * "marvell,armada370-nand" (deprecated)
> +    * "marvell,pxa3xx-nand" (deprecated)
> +- reg: shall contain registers location and length for data and reg.

2 regions?

> +- #address-cells: shall be set to 1. Encode the nand CS.
> +- #size-cells: shall be set to 0.
> +- interrupts: shall define the nand controller interrupt.
> +- clocks: shall reference nand controller clocks.

How many clocks?

> +- marvell,system-controller: Set to retrieve the syscon node that handles
> +  NAND controller related registers (only required with the
> +  "marvell,armada-8k-nand[-controller]" compatibles).
> +
> +Optional properties:
> +- dmas: shall reference DMA channel associated to the NAND controller.
> +- dma-names: shall be "rxtx".
> +
> +Optional children nodes:
> +Children nodes represent the available NAND chips.
> +
> +Required properties:
> +- reg: shall contain the native Chip Select ids (0-3)
> +- marvell,rb: shall contain the native Ready/Busy ids (0-1)
> +
> +Optional properties:
> +- marvell,nand-keep-config: orders the driver not to take the timings
> +  from the core and leaving them completely untouched. Bootloader
> +  timings will then be used.
> +- marvell,nand-enable-arbiter: only useful for PXA platforms, will
> +  enable bus arbiter between NFC and DFI bus (must be enabled for
> +  NFC operation)

Why do you need this if it must be enabled?

> +- nand-on-flash-bbt: speed up the boot process by not discovering all
> +  the bad blocks at each boot and reading directly an on flash table.
> +- nand-ecc-mode: one of the supported ECC modes ("none", "soft",
> +  "hw"). If not specified, hardware ECC will be used.
> +- nand-ecc-algo: algorithm to use if previous choice was "soft"
> +  ("hamming" or "bch). This property may be added for hardware ECC for
> +  clarification but will be ignored by the driver because ECC mode is
> +  chosen depending on the page size and the strength required by the
> +  NAND chip. This value may be overwritten with the nand-ecc-strength
> +  property.
> +- nand-ecc-strength: desired ECC strength.
> +- nand-ecc-step-size: indication on the ECC step size. This has no
> +  effect and will be ignored by the driver when using hardware
> +  ECC. Because Marvell's NAND flash controller does use fixed strength
> +  (1-bit for Hamming, 16-bit for BCH), the step size will shrink or
> +  grown in order to fit the required strength and the value
> +  updated. Step sizes are not completely random for all and follow
> +  certain patterns described in AN-379, "Marvell SoC NFC ECC".

For standard properties, just reference nand.txt and add any 
constraints. Don't define what the property is again.

> +
> +See Documentation/devicetree/bindings/mtd/nand.txt for more details on
> +generic bindings.
> +
> +
> +Example:
> +nand_controller: nand-controller at d0000 {
> +	compatible = "marvell,armada370-nand-controller";
> +	reg = <0xd0000 0x54>;
> +	#address-cells = <1>;
> +	#size-cells = <0>;
> +	interrupts = <GIC_SPI 84 IRQ_TYPE_LEVEL_HIGH>;
> +	clocks = <&coredivclk 0>;
> +	status = "okay";

Don't show status in examples.

> +
> +	nand at 0 {
> +		reg = <0>;
> +		marvell,rb = <0>;
> +		nand-ecc-mode = "hw";
> +		marvell,nand-keep-config;
> +		marvell,nand-enable-arbiter;
> +		nand-on-flash-bbt;
> +		nand-ecc-strength = <4>;
> +		nand-ecc-step-size = <512>;
> +
> +		partitions {
> +			compatible = "fixed-partitions";
> +			#address-cells = <1>;
> +			#size-cells = <1>;
> +
> +			partition at 0 {
> +				label = "Rootfs";
> +				reg = <0x00000000 0x40000000>;
> +			};
> +		};
> +	};
> +};
> -- 
> 2.11.0
> 

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [RFC 03/12] mtd: nand: use a static data_interface in the nand_chip structure
  2017-10-18 17:02     ` Boris Brezillon
  (?)
  (?)
@ 2017-11-03 13:46     ` Miquel RAYNAL
  -1 siblings, 0 replies; 75+ messages in thread
From: Miquel RAYNAL @ 2017-11-03 13:46 UTC (permalink / raw)
  To: Boris Brezillon
  Cc: Brian Norris, Cyrille Pitchen, David Woodhouse, Gregory Clement,
	linux-mtd, Marek Vasut, Richard Weinberger, Thomas Petazzoni,
	Antoine Tenart, Nadav Haklai, Ofer Heifetz, Neta Zur Hershkovits,
	Hanna Hawa

Hi Boris,

> > Change the data_interface field from the nand_chip structure,
> > convert the pointer to a static structure.  
> 
> You mean "embed a nand_data_interface object". Not sure the static
> specifier can be used to describe what you do.

Ok.

> > Also remove the nand_get_default_data_interface() function that
> > become useless and rename the onfi_init_data_interface() by
> > nand_fill_data_interface(),  
> 
> I know I'm the one who suggested to rename this function, but I'm not
> so sure it's a good idea in the end, and I don't like the new
> prototype where you drop the iface_type parameter.

No problem, I will just rename it "onfi_fill_data_interface" because
there is no more real "initialization", it is just a dumb function
that fills the structure with the appropriate timings (I found the
name too close to "nand_init_data_interface" that really does
something). But ok, let's keep the iface_type parameter.

> 
> > which is a more appropriate name because
> > applied timings are ONFI, no matter if the NAND actually is one.  
> 
> Well, this is actually a good reason to keep the onfi_ prefix. A NAND
> driver can decide that its NAND is close to an existing timing mode
> and re-use this onfi_init_data_interface(), but maybe someday we'll
> have drivers filling the nand_data_interface object with their own
> (non-ONFI) timings.
> 
> > 
> > This is needed before passing to ->exec_op() to avoid any race
> > that  
> 
> It's not really a race, it's just that you need some timings during
> NAND detection, and the core is currently creating the
> nand_data_interface object after the detection step (in
> nand_scan_tail()).

I changed the commit message.


> > @@ -1107,7 +1107,6 @@ static int nand_wait(struct mtd_info *mtd,
> > struct nand_chip *chip) static int nand_reset_data_interface(struct
> > nand_chip *chip, int chipnr) {
> >  	struct mtd_info *mtd = nand_to_mtd(chip);
> > -	const struct nand_data_interface *conf;
> >  	int ret;
> >  
> >  	if (!chip->setup_data_interface)
> > @@ -1127,8 +1126,8 @@ static int nand_reset_data_interface(struct
> > nand_chip *chip, int chipnr)
> >  	 * timings to timing mode 0.
> >  	 */
> >  
> > -	conf = nand_get_default_data_interface();
> > -	ret = chip->setup_data_interface(mtd, chipnr, conf);
> > +	nand_fill_data_interface(chip, 0);
> > +	ret = chip->setup_data_interface(mtd, chipnr,
> > &chip->data_interface); if (ret)
> >  		pr_err("Failed to configure data interface to SDR
> > timing mode 0\n"); 
> 
> So now you're stuck in timing mode 0 as soon as you have nand_reset()
> called.
> 
> Actually, what we should do is:
> 
> 1/ at the beginning of nand_scan_ident(), call
>    onfi_init_data_interface(chip, &chip->data_interface,
> NAND_SDR_IFACE, 0) (or a wrapper that does that) so that
> data_interface is initialized with the appropriate timings for
> reset/detection operations 2/ after detection, find the best iface
> type and timing mode we can use (what's currently done in
> nand_init_data_interface()) 3/ in nand_reset(), you should save
> what's in chip->data_interface, in a local variable, then apply
> SDR+timing-mode-0 and finally restore the previous settings (the one
> you stored in your local variable) after the RESET op.
> 
> This way you're guaranteed to always end up with the best data iface
> settings after a RESET.

Clear explanation, this is corrected.

Thank you,
Miquèl

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [RFC 05/12] dt-bindings: mtd: add Marvell NAND controller documentation
  2017-10-24 19:04     ` Rob Herring
  (?)
@ 2017-11-06 13:24       ` Miquel RAYNAL
  -1 siblings, 0 replies; 75+ messages in thread
From: Miquel RAYNAL @ 2017-11-06 13:24 UTC (permalink / raw)
  To: Rob Herring
  Cc: Mark Rutland, Andrew Lunn, Catalin Marinas, Hanna Hawa,
	Will Deacon, Stefan Agner, Nadav Haklai, Masahiro Yamada,
	linux-mtd, Matthias Brugger, Robert Jarzmik, devel,
	Boris Brezillon, Maxim Levitsky, Kamal Dasu, Josh Wu,
	Russell King, Marek Vasut, Chen-Yu Tsai,
	bcm-kernel-feedback-list, Sebastian Hesselbarth, Ezequiel Garcia,
	Sylvain Lemieux

Hi Rob,

> > +Required properties:
> > +C'est faux, t'en a rajouté un y a pas longtps :).
> > +Je conseille de mettre ça sous forme de liste, genre  
> 
> Humm.

Oops!

> 
> > +
> > +- compatible: can be one of the following:
> > +    * "marvell,armada-8k-nand-controller"
> > +    * "marvell,armada370-nand-controller"
> > +    * "marvell,pxa3xx-nand-controller"
> > +    * "marvell,armada-8k-nand" (deprecated)
> > +    * "marvell,armada370-nand" (deprecated)
> > +    * "marvell,pxa3xx-nand" (deprecated)
> > +- reg: shall contain registers location and length for data and
> > reg.  
> 
> 2 regions?

Just one, rephrased.

> 
> > +- #address-cells: shall be set to 1. Encode the nand CS.
> > +- #size-cells: shall be set to 0.
> > +- interrupts: shall define the nand controller interrupt.
> > +- clocks: shall reference nand controller clocks.  
> 
> How many clocks?

Only one too: "reference the NAND controller clock".

> 
> > +- marvell,system-controller: Set to retrieve the syscon node that
> > handles
> > +  NAND controller related registers (only required with the
> > +  "marvell,armada-8k-nand[-controller]" compatibles).
> > +
> > +Optional properties:
> > +- dmas: shall reference DMA channel associated to the NAND
> > controller. +- dma-names: shall be "rxtx".
> > +
> > +Optional children nodes:
> > +Children nodes represent the available NAND chips.
> > +
> > +Required properties:
> > +- reg: shall contain the native Chip Select ids (0-3)
> > +- marvell,rb: shall contain the native Ready/Busy ids (0-1)
> > +
> > +Optional properties:
> > +- marvell,nand-keep-config: orders the driver not to take the
> > timings
> > +  from the core and leaving them completely untouched. Bootloader
> > +  timings will then be used.
> > +- marvell,nand-enable-arbiter: only useful for PXA platforms, will
> > +  enable bus arbiter between NFC and DFI bus (must be enabled for
> > +  NFC operation)  
> 
> Why do you need this if it must be enabled?

That is right, there is no more need for it, also removed it from the
driver, just knowing the board with the compatible string is enough.

> 
> > +- nand-on-flash-bbt: speed up the boot process by not discovering
> > all
> > +  the bad blocks at each boot and reading directly an on flash
> > table. +- nand-ecc-mode: one of the supported ECC modes ("none",
> > "soft",
> > +  "hw"). If not specified, hardware ECC will be used.
> > +- nand-ecc-algo: algorithm to use if previous choice was "soft"
> > +  ("hamming" or "bch). This property may be added for hardware ECC
> > for
> > +  clarification but will be ignored by the driver because ECC mode
> > is
> > +  chosen depending on the page size and the strength required by
> > the
> > +  NAND chip. This value may be overwritten with the
> > nand-ecc-strength
> > +  property.
> > +- nand-ecc-strength: desired ECC strength.
> > +- nand-ecc-step-size: indication on the ECC step size. This has no
> > +  effect and will be ignored by the driver when using hardware
> > +  ECC. Because Marvell's NAND flash controller does use fixed
> > strength
> > +  (1-bit for Hamming, 16-bit for BCH), the step size will shrink or
> > +  grown in order to fit the required strength and the value
> > +  updated. Step sizes are not completely random for all and follow
> > +  certain patterns described in AN-379, "Marvell SoC NFC ECC".  
> 
> For standard properties, just reference nand.txt and add any 
> constraints. Don't define what the property is again.

Ok.

> 
> > +
> > +See Documentation/devicetree/bindings/mtd/nand.txt for more
> > details on +generic bindings.
> > +
> > +
> > +Example:
> > +nand_controller: nand-controller@d0000 {
> > +	compatible = "marvell,armada370-nand-controller";
> > +	reg = <0xd0000 0x54>;
> > +	#address-cells = <1>;
> > +	#size-cells = <0>;
> > +	interrupts = <GIC_SPI 84 IRQ_TYPE_LEVEL_HIGH>;
> > +	clocks = <&coredivclk 0>;
> > +	status = "okay";  
> 
> Don't show status in examples.

Ok.

> 
> > +
> > +	nand@0 {
> > +		reg = <0>;
> > +		marvell,rb = <0>;
> > +		nand-ecc-mode = "hw";
> > +		marvell,nand-keep-config;
> > +		marvell,nand-enable-arbiter;
> > +		nand-on-flash-bbt;
> > +		nand-ecc-strength = <4>;
> > +		nand-ecc-step-size = <512>;
> > +
> > +		partitions {
> > +			compatible = "fixed-partitions";
> > +			#address-cells = <1>;
> > +			#size-cells = <1>;
> > +
> > +			partition@0 {
> > +				label = "Rootfs";
> > +				reg = <0x00000000 0x40000000>;
> > +			};
> > +		};
> > +	};
> > +};
> > -- 
> > 2.11.0
> >   

Thanks for the review,
Miquèl
_______________________________________________
devel mailing list
devel@linuxdriverproject.org
http://driverdev.linuxdriverproject.org/mailman/listinfo/driverdev-devel

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [RFC 05/12] dt-bindings: mtd: add Marvell NAND controller documentation
@ 2017-11-06 13:24       ` Miquel RAYNAL
  0 siblings, 0 replies; 75+ messages in thread
From: Miquel RAYNAL @ 2017-11-06 13:24 UTC (permalink / raw)
  To: Rob Herring
  Cc: Andrew Lunn, bcm-kernel-feedback-list, Boris Brezillon,
	Brian Norris, Catalin Marinas, Chen-Yu Tsai, Cyrille Pitchen,
	Daniel Mack, David Woodhouse, devel, devicetree, Ezequiel Garcia,
	Greg Kroah-Hartman, Gregory Clement, Han Xu, Haojian Zhuang,
	Jason Cooper, Josh Wu, Kamal Dasu, linux-arm-kernel,
	linux-kernel, linux-mediatek, linux-mtd, Marc Gonzalez,
	Marek Vasut, Mark Rutland, Masahiro Yamada, Matthias Brugger,
	Maxime Ripard, Maxim Levitsky, Richard Weinberger,
	Robert Jarzmik, Russell King, Sebastian Hesselbarth,
	Stefan Agner, Sylvain Lemieux, Vladimir Zapolskiy, Wenyou Yang,
	Will Deacon, Thomas Petazzoni, Antoine Tenart, Igor Grinberg,
	Nadav Haklai, Ofer Heifetz, Neta Zur Hershkovits, Hanna Hawa

Hi Rob,

> > +Required properties:
> > +C'est faux, t'en a rajouté un y a pas longtps :).
> > +Je conseille de mettre ça sous forme de liste, genre  
> 
> Humm.

Oops!

> 
> > +
> > +- compatible: can be one of the following:
> > +    * "marvell,armada-8k-nand-controller"
> > +    * "marvell,armada370-nand-controller"
> > +    * "marvell,pxa3xx-nand-controller"
> > +    * "marvell,armada-8k-nand" (deprecated)
> > +    * "marvell,armada370-nand" (deprecated)
> > +    * "marvell,pxa3xx-nand" (deprecated)
> > +- reg: shall contain registers location and length for data and
> > reg.  
> 
> 2 regions?

Just one, rephrased.

> 
> > +- #address-cells: shall be set to 1. Encode the nand CS.
> > +- #size-cells: shall be set to 0.
> > +- interrupts: shall define the nand controller interrupt.
> > +- clocks: shall reference nand controller clocks.  
> 
> How many clocks?

Only one too: "reference the NAND controller clock".

> 
> > +- marvell,system-controller: Set to retrieve the syscon node that
> > handles
> > +  NAND controller related registers (only required with the
> > +  "marvell,armada-8k-nand[-controller]" compatibles).
> > +
> > +Optional properties:
> > +- dmas: shall reference DMA channel associated to the NAND
> > controller. +- dma-names: shall be "rxtx".
> > +
> > +Optional children nodes:
> > +Children nodes represent the available NAND chips.
> > +
> > +Required properties:
> > +- reg: shall contain the native Chip Select ids (0-3)
> > +- marvell,rb: shall contain the native Ready/Busy ids (0-1)
> > +
> > +Optional properties:
> > +- marvell,nand-keep-config: orders the driver not to take the
> > timings
> > +  from the core and leaving them completely untouched. Bootloader
> > +  timings will then be used.
> > +- marvell,nand-enable-arbiter: only useful for PXA platforms, will
> > +  enable bus arbiter between NFC and DFI bus (must be enabled for
> > +  NFC operation)  
> 
> Why do you need this if it must be enabled?

That is right, there is no more need for it, also removed it from the
driver, just knowing the board with the compatible string is enough.

> 
> > +- nand-on-flash-bbt: speed up the boot process by not discovering
> > all
> > +  the bad blocks at each boot and reading directly an on flash
> > table. +- nand-ecc-mode: one of the supported ECC modes ("none",
> > "soft",
> > +  "hw"). If not specified, hardware ECC will be used.
> > +- nand-ecc-algo: algorithm to use if previous choice was "soft"
> > +  ("hamming" or "bch). This property may be added for hardware ECC
> > for
> > +  clarification but will be ignored by the driver because ECC mode
> > is
> > +  chosen depending on the page size and the strength required by
> > the
> > +  NAND chip. This value may be overwritten with the
> > nand-ecc-strength
> > +  property.
> > +- nand-ecc-strength: desired ECC strength.
> > +- nand-ecc-step-size: indication on the ECC step size. This has no
> > +  effect and will be ignored by the driver when using hardware
> > +  ECC. Because Marvell's NAND flash controller does use fixed
> > strength
> > +  (1-bit for Hamming, 16-bit for BCH), the step size will shrink or
> > +  grown in order to fit the required strength and the value
> > +  updated. Step sizes are not completely random for all and follow
> > +  certain patterns described in AN-379, "Marvell SoC NFC ECC".  
> 
> For standard properties, just reference nand.txt and add any 
> constraints. Don't define what the property is again.

Ok.

> 
> > +
> > +See Documentation/devicetree/bindings/mtd/nand.txt for more
> > details on +generic bindings.
> > +
> > +
> > +Example:
> > +nand_controller: nand-controller@d0000 {
> > +	compatible = "marvell,armada370-nand-controller";
> > +	reg = <0xd0000 0x54>;
> > +	#address-cells = <1>;
> > +	#size-cells = <0>;
> > +	interrupts = <GIC_SPI 84 IRQ_TYPE_LEVEL_HIGH>;
> > +	clocks = <&coredivclk 0>;
> > +	status = "okay";  
> 
> Don't show status in examples.

Ok.

> 
> > +
> > +	nand@0 {
> > +		reg = <0>;
> > +		marvell,rb = <0>;
> > +		nand-ecc-mode = "hw";
> > +		marvell,nand-keep-config;
> > +		marvell,nand-enable-arbiter;
> > +		nand-on-flash-bbt;
> > +		nand-ecc-strength = <4>;
> > +		nand-ecc-step-size = <512>;
> > +
> > +		partitions {
> > +			compatible = "fixed-partitions";
> > +			#address-cells = <1>;
> > +			#size-cells = <1>;
> > +
> > +			partition@0 {
> > +				label = "Rootfs";
> > +				reg = <0x00000000 0x40000000>;
> > +			};
> > +		};
> > +	};
> > +};
> > -- 
> > 2.11.0
> >   

Thanks for the review,
Miquèl

^ permalink raw reply	[flat|nested] 75+ messages in thread

* [RFC 05/12] dt-bindings: mtd: add Marvell NAND controller documentation
@ 2017-11-06 13:24       ` Miquel RAYNAL
  0 siblings, 0 replies; 75+ messages in thread
From: Miquel RAYNAL @ 2017-11-06 13:24 UTC (permalink / raw)
  To: linux-arm-kernel

Hi Rob,

> > +Required properties:
> > +C'est faux, t'en a rajout? un y a pas longtps :).
> > +Je conseille de mettre ?a sous forme de liste, genre  
> 
> Humm.

Oops!

> 
> > +
> > +- compatible: can be one of the following:
> > +    * "marvell,armada-8k-nand-controller"
> > +    * "marvell,armada370-nand-controller"
> > +    * "marvell,pxa3xx-nand-controller"
> > +    * "marvell,armada-8k-nand" (deprecated)
> > +    * "marvell,armada370-nand" (deprecated)
> > +    * "marvell,pxa3xx-nand" (deprecated)
> > +- reg: shall contain registers location and length for data and
> > reg.  
> 
> 2 regions?

Just one, rephrased.

> 
> > +- #address-cells: shall be set to 1. Encode the nand CS.
> > +- #size-cells: shall be set to 0.
> > +- interrupts: shall define the nand controller interrupt.
> > +- clocks: shall reference nand controller clocks.  
> 
> How many clocks?

Only one too: "reference the NAND controller clock".

> 
> > +- marvell,system-controller: Set to retrieve the syscon node that
> > handles
> > +  NAND controller related registers (only required with the
> > +  "marvell,armada-8k-nand[-controller]" compatibles).
> > +
> > +Optional properties:
> > +- dmas: shall reference DMA channel associated to the NAND
> > controller. +- dma-names: shall be "rxtx".
> > +
> > +Optional children nodes:
> > +Children nodes represent the available NAND chips.
> > +
> > +Required properties:
> > +- reg: shall contain the native Chip Select ids (0-3)
> > +- marvell,rb: shall contain the native Ready/Busy ids (0-1)
> > +
> > +Optional properties:
> > +- marvell,nand-keep-config: orders the driver not to take the
> > timings
> > +  from the core and leaving them completely untouched. Bootloader
> > +  timings will then be used.
> > +- marvell,nand-enable-arbiter: only useful for PXA platforms, will
> > +  enable bus arbiter between NFC and DFI bus (must be enabled for
> > +  NFC operation)  
> 
> Why do you need this if it must be enabled?

That is right, there is no more need for it, also removed it from the
driver, just knowing the board with the compatible string is enough.

> 
> > +- nand-on-flash-bbt: speed up the boot process by not discovering
> > all
> > +  the bad blocks at each boot and reading directly an on flash
> > table. +- nand-ecc-mode: one of the supported ECC modes ("none",
> > "soft",
> > +  "hw"). If not specified, hardware ECC will be used.
> > +- nand-ecc-algo: algorithm to use if previous choice was "soft"
> > +  ("hamming" or "bch). This property may be added for hardware ECC
> > for
> > +  clarification but will be ignored by the driver because ECC mode
> > is
> > +  chosen depending on the page size and the strength required by
> > the
> > +  NAND chip. This value may be overwritten with the
> > nand-ecc-strength
> > +  property.
> > +- nand-ecc-strength: desired ECC strength.
> > +- nand-ecc-step-size: indication on the ECC step size. This has no
> > +  effect and will be ignored by the driver when using hardware
> > +  ECC. Because Marvell's NAND flash controller does use fixed
> > strength
> > +  (1-bit for Hamming, 16-bit for BCH), the step size will shrink or
> > +  grown in order to fit the required strength and the value
> > +  updated. Step sizes are not completely random for all and follow
> > +  certain patterns described in AN-379, "Marvell SoC NFC ECC".  
> 
> For standard properties, just reference nand.txt and add any 
> constraints. Don't define what the property is again.

Ok.

> 
> > +
> > +See Documentation/devicetree/bindings/mtd/nand.txt for more
> > details on +generic bindings.
> > +
> > +
> > +Example:
> > +nand_controller: nand-controller at d0000 {
> > +	compatible = "marvell,armada370-nand-controller";
> > +	reg = <0xd0000 0x54>;
> > +	#address-cells = <1>;
> > +	#size-cells = <0>;
> > +	interrupts = <GIC_SPI 84 IRQ_TYPE_LEVEL_HIGH>;
> > +	clocks = <&coredivclk 0>;
> > +	status = "okay";  
> 
> Don't show status in examples.

Ok.

> 
> > +
> > +	nand at 0 {
> > +		reg = <0>;
> > +		marvell,rb = <0>;
> > +		nand-ecc-mode = "hw";
> > +		marvell,nand-keep-config;
> > +		marvell,nand-enable-arbiter;
> > +		nand-on-flash-bbt;
> > +		nand-ecc-strength = <4>;
> > +		nand-ecc-step-size = <512>;
> > +
> > +		partitions {
> > +			compatible = "fixed-partitions";
> > +			#address-cells = <1>;
> > +			#size-cells = <1>;
> > +
> > +			partition at 0 {
> > +				label = "Rootfs";
> > +				reg = <0x00000000 0x40000000>;
> > +			};
> > +		};
> > +	};
> > +};
> > -- 
> > 2.11.0
> >   

Thanks for the review,
Miqu?l

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [RFC 06/12] mtd: nand: add reworked Marvell NAND controller driver
  2017-10-19  7:18     ` Boris Brezillon
  (?)
  (?)
@ 2017-11-06 13:49     ` Miquel RAYNAL
  -1 siblings, 0 replies; 75+ messages in thread
From: Miquel RAYNAL @ 2017-11-06 13:49 UTC (permalink / raw)
  To: Boris Brezillon
  Cc: Brian Norris, David Woodhouse, Gregory Clement, linux-mtd,
	Marek Vasut, Richard Weinberger, Thomas Petazzoni,
	Antoine Tenart, Nadav Haklai, Ofer Heifetz, Neta Zur Hershkovits,
	Hanna Hawa

Hi Boris,

> > +enum marvell_nfc_variant {
> > +	MARVELL_NFC_VARIANT_PXA3XX,
> > +	MARVELL_NFC_VARIANT_ARMADA370,
> > +	MARVELL_NFC_VARIANT_ARMADA_8K,
> > +};
> > +
> > +/*
> > + * NAND controller capabilities for distinction between compatible
> > strings
> > + *
> > + * @variant:		Board type
> > + * @max_cs_nb:		Number of Chip Select lines available
> > + * @max_rb_nb:		Number of Ready/Busy lines available
> > + * @legacy_of_bindings	Indicates if DT parsing must be done
> > using the old
> > + *			fashion way
> > + */
> > +struct marvell_nfc_caps {
> > +	enum marvell_nfc_variant variant;  
> 
> Do you really need this variant field. It seems you're only using it
> to detect if you should MUX the NAND pins using the Special Function
> registers on armada8k. Maybe you should just have:
> 
> 	bool need_system_controller;
> 
> and set it to true for the "armada8k" compatible.

Ok. After Rob's review I also added a "bool need_arbiter" to avoid
using any "variant == PXA_VARIANT" in the code.

> 
> > +	unsigned int max_cs_nb;
> > +	unsigned int max_rb_nb;
> > +	bool legacy_of_bindings;
> > +};
> > +  

Thanks,
Miquèl

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [RFC 04/12] mtd: nand: add ->exec_op() implementation
  2017-10-18 21:57     ` Boris Brezillon
  (?)
  (?)
@ 2017-11-06 14:09     ` Miquel RAYNAL
  -1 siblings, 0 replies; 75+ messages in thread
From: Miquel RAYNAL @ 2017-11-06 14:09 UTC (permalink / raw)
  To: Boris Brezillon
  Cc: Brian Norris, Cyrille Pitchen, David Woodhouse, linux-mtd,
	Marek Vasut, Richard Weinberger, Thomas Petazzoni,
	Antoine Tenart, Nadav Haklai, Ofer Heifetz, Neta Zur Hershkovits,
	Hanna Hawa

Hi Boris,

> > Introduce the new way to control the NAND controller drivers by
> > implementing the ->exec_op() core helpers and allowing new drivers
> > to use it instead of relying on ->cmd_ctrl(), ->cmdfunc() and  
> > ->read/write_byte/word/buf().  
> 
> "
> Introduce a new interface to instruct NAND controllers to send
> specific NAND operations. The new interface takes the form of a
> single method called ->exec_op(). This method is designed to replace
> ->cmd_ctrl(), ->cmdfunc() and ->read/write_byte/word/buf() hooks.  
> "

Changed.

> 
> > 
> > The logic is now to send to the controller driver a list of
> > instructions.  
> 
> "
> ->exec_op() is passed a set of instructions describing the operation
> to execute. Each instruction has a type (ADDR, CMD, CYCLE, WAITRDY)
> and delay. The type is directly matching the description of NAND
> commands in various NAND datasheet and standards (ONFI, JEDEC), the
> delay is here to help simple controllers wait enough time between each
> instruction. Advanced controllers with integrated timings control can
> ignore these delays. 
> "

Changed with s/CYCLE/DATA/.

> 
> > The driver shall use the parser added by this commit to
> > get the matching hook if any.  
> 
> That's true only for advanced controllers. For those who are executing
> each instruction individually (like the gpio-nand driver, or any old
> style driver where each ADDR/CMD/DATA cycle is controlled
> independently) this is not needed, and who add extra complexity for no
> real gain.

Mention about advanced vs dump controllers added.

> 
> > The instructions may be split by the
> > parser in order to comply with the controller constraints filled in
> > an array of supported patterns.  
> 
> Given only this description it's hard to tell what this parser is and
> why it's needed. I think a real example who be helpful to better
> understand what this is.

Added an example.

> 
> > 
> > Various helpers are also added to ease NAND controller drivers
> > writing.
> > 
> > This new interface should really ease the support of new vendor
> > specific instructions.  
> 
> Actually, it's more than easing support for vendor specific
> operations, it allows the core to check whether the NAND controller
> will be able to execute a specific operation or not, which before
> that was impossible. It doesn't mean all controllers will magically
> support all kind of NAND operations, but at least we'll know when it
> doesn't.

Added a note about that.

> 
> > 
> > Suggested-by: Boris Brezillon <boris.brezillon@free-electrons.com>
> > Signed-off-by: Miquel Raynal <miquel.raynal@free-electrons.com>
> > ---
> >  drivers/mtd/nand/denali.c      |  93 +++-
> >  drivers/mtd/nand/nand_base.c   | 949
> > +++++++++++++++++++++++++++++++++++++++--
> > drivers/mtd/nand/nand_hynix.c  |  91 +++-
> > drivers/mtd/nand/nand_micron.c |  32 +-
> > include/linux/mtd/rawnand.h    | 366 +++++++++++++++- 5 files
> > changed, 1448 insertions(+), 83 deletions(-)
> > 
...
> > @@ -665,11 +663,22 @@ static void denali_oob_xfer(struct mtd_info
> > *mtd, struct nand_chip *chip, int i, pos, len;
> >  
> >  	/* BBM at the beginning of the OOB area */
> > -	chip->cmdfunc(mtd, start_cmd, writesize, page);
> > -	if (write)
> > -		chip->write_buf(mtd, bufpoi, oob_skip);
> > -	else
> > -		chip->read_buf(mtd, bufpoi, oob_skip);
> > +	if (chip->exec_op) {
> > +		if (write)
> > +			nand_prog_page_begin_op(chip, page,
> > writesize, bufpoi,
> > +						oob_skip);
> > +		else
> > +			nand_read_page_op(chip, page, writesize,
> > bufpoi,
> > +					  oob_skip);
> > +	} else {
> > +		if (write) {
> > +			chip->cmdfunc(mtd, NAND_CMD_SEQIN,
> > writesize, page);
> > +			chip->write_buf(mtd, bufpoi, oob_skip);
> > +		} else {
> > +			chip->cmdfunc(mtd, NAND_CMD_READ0,
> > writesize, page);
> > +			chip->read_buf(mtd, bufpoi, oob_skip);
> > +		}
> > +	}  
> 
> Why do we have to keep the ->cmdfunc() logic? nand_prog_page_xxx()
> already abstracts this for us, right?

Right! Changed here and after.

> 
> >  	bufpoi += oob_skip;
> >  
> >  	/* OOB ECC */
> > @@ -682,30 +691,67 @@ static void denali_oob_xfer(struct mtd_info
> > *mtd, struct nand_chip *chip, else if (pos + len > writesize)
> >  			len = writesize - pos;
> >  
> > -		chip->cmdfunc(mtd, rnd_cmd, pos, -1);
> > -		if (write)
> > -			chip->write_buf(mtd, bufpoi, len);
> > -		else
> > -			chip->read_buf(mtd, bufpoi, len);
> > -		bufpoi += len;
> > -		if (len < ecc_bytes) {
> > -			len = ecc_bytes - len;
> > -			chip->cmdfunc(mtd, rnd_cmd, writesize +
> > oob_skip, -1);
> > +		if (chip->exec_op) {
> >  			if (write)
> > -				chip->write_buf(mtd, bufpoi, len);
> > +				nand_change_write_column_op(
> > +					chip, pos, bufpoi, len,
> > false);  
> 
> Nitpick, but can you try to align things like that:
> 
> 				nand_change_write_column_op(chip, pos,
> 							    bufpoi,
> len, false);

Changed here and after.

> 
> >  			else
> > +				nand_change_read_column_op(
> > +					chip, pos, bufpoi, len,
> > false);  
> 
> Ditto.
> 
> > +		} else {
> > +			if (write) {
> > +				chip->cmdfunc(mtd, NAND_CMD_RNDIN,
> > pos, -1);
> > +				chip->write_buf(mtd, bufpoi, len);
> > +			} else {
> > +				chip->cmdfunc(mtd,
> > NAND_CMD_RNDOUT, pos, -1); chip->read_buf(mtd, bufpoi, len);
> > +			}
> > +		}  
> 
> Smae here: I don't think we need to support both ->cmdfunc() and
> nand_change_read/write_column().
> 
> > +		bufpoi += len;
> > +		if (len < ecc_bytes) {
> > +			len = ecc_bytes - len;
> > +			if (chip->exec_op) {
> > +				if (write)
> > +
> > nand_change_write_column_op(
> > +						chip, writesize +
> > oob_skip,
> > +						bufpoi, len,
> > false);
> > +				else
> > +					nand_change_read_column_op(
> > +						chip, writesize +
> > oob_skip,
> > +						bufpoi, len,
> > false);
> > +			} else {
> > +				if (write) {
> > +					chip->cmdfunc(mtd,
> > NAND_CMD_RNDIN,
> > +						      writesize +
> > oob_skip, -1);
> > +					chip->write_buf(mtd,
> > bufpoi, len);
> > +				} else {
> > +					chip->cmdfunc(mtd,
> > NAND_CMD_RNDOUT,
> > +						      writesize +
> > oob_skip, -1);
> > +					chip->read_buf(mtd,
> > bufpoi, len);
> > +				}
> > +			.  
> 
> Ditto.
> 
> >  			bufpoi += len;
> >  		}
> >  	}
> >  
> >  	/* OOB free */
> >  	len = oobsize - (bufpoi - chip->oob_poi);
> > -	chip->cmdfunc(mtd, rnd_cmd, size - len, -1);
> > -	if (write)
> > -		chip->write_buf(mtd, bufpoi, len);
> > -	else
> > -		chip->read_buf(mtd, bufpoi, len);
> > +	if (chip->exec_op) {
> > +		if (write)
> > +			nand_change_write_column_op(chip, size -
> > len, bufpoi,
> > +						    len, false);
> > +		else
> > +			nand_change_read_column_op(chip, size -
> > len, bufpoi,
> > +						   len, false);
> > +	} else {
> > +		if (write) {
> > +			chip->cmdfunc(mtd, NAND_CMD_RNDIN, size -
> > len, -1);
> > +			chip->write_buf(mtd, bufpoi, len);
> > +		} else {
> > +			chip->cmdfunc(mtd, NAND_CMD_RNDOUT, size -
> > len, -1);
> > +			chip->read_buf(mtd, bufpoi, len);
> > +		}
> > +	}
> >  }
> >  
> >  static int denali_read_page_raw(struct mtd_info *mtd, struct
> > nand_chip *chip, @@ -803,6 +849,9 @@ static int
> > denali_write_oob(struct mtd_info *mtd, struct nand_chip *chip, 
> >  	denali_oob_xfer(mtd, chip, page, 1);
> >  
> > +	if (chip->exec_op)
> > +		return nand_prog_page_end_op(chip);
> > +
> >  	chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1);
> >  	status = chip->waitfunc(mtd, chip);  
> 
> All denali changes in this patch should actually be moved to patch 1.

Ok.

> 
> >  
> > diff --git a/drivers/mtd/nand/nand_base.c
> > b/drivers/mtd/nand/nand_base.c index bef20e06f0db..737f19bd2f83
> > 100644 --- a/drivers/mtd/nand/nand_base.c
> > +++ b/drivers/mtd/nand/nand_base.c
> > @@ -814,7 +814,7 @@ static void nand_ccs_delay(struct nand_chip
> > *chip)
> >  	 * Wait tCCS_min if it is correctly defined, otherwise
> > wait 500ns
> >  	 * (which should be safe for all NANDs).
> >  	 */
> > -	if (chip->data_interface.timings.sdr.tCCS_min)
> > +	if (&chip->data_interface.timings.sdr.tCCS_min)  
> 
> This condition is always true. I think this should be replaced by:
> 
> 	if (chip->setup_data_interface)

Yes.

> 
> And BTW, it should go in patch 3.

Ok.

> 
> >  		ndelay(chip->data_interface.timings.sdr.tCCS_min /
> > 1000); else
> >  		ndelay(500);
> > @@ -1233,6 +1233,118 @@ static int nand_init_data_interface(struct
> > nand_chip *chip) }
> >  
> >  /**
> > + * nand_fill_column_cycles - fill the column fields on an address
> > array
> > + * @chip: The NAND chip
> > + * @addrs: Array of address cycles to fill
> > + * @offset_in_page: The offset in the page
> > + *
> > + * Fills the first or the two first bytes of the @addrs field
> > depending
> > + * on the NAND bus width and the page size.
> > + */
> > +int nand_fill_column_cycles(struct nand_chip *chip, u8 *addrs,
> > +			    unsigned int offset_in_page)
> > +{
> > +	struct mtd_info *mtd = nand_to_mtd(chip);
> > +
> > +	/*
> > +	 * The offset in page is expressed in bytes, if the NAND
> > bus is 16-bit
> > +	 * wide, then it must be divided by 2.
> > +	 */
> > +	if (chip->options & NAND_BUSWIDTH_16) {
> > +		if (offset_in_page % 2) {
> > +			WARN_ON(true);
> > +			return -EINVAL;
> > +		}
> > +
> > +		offset_in_page /= 2;
> > +	}
> > +
> > +	addrs[0] = offset_in_page;
> > +
> > +	/* Small pages use 1 cycle for the columns, while large
> > page need 2 */
> > +	if (mtd->writesize <= 512)
> > +		return 1;
> > +
> > +	addrs[1] = offset_in_page >> 8;
> > +
> > +	return 2;
> > +}
> > +EXPORT_SYMBOL_GPL(nand_fill_column_cycles);
> > +
> > +static int nand_sp_exec_read_page_op(struct nand_chip *chip,
> > unsigned int page,
> > +				     unsigned int offset_in_page,
> > void *buf,
> > +				     unsigned int len)
> > +{
> > +	struct mtd_info *mtd = nand_to_mtd(chip);
> > +	const struct nand_sdr_timings *sdr =
> > +		nand_get_sdr_timings(&chip->data_interface);
> > +	u8 addrs[4];
> > +	struct nand_op_instr instrs[] = {
> > +		NAND_OP_CMD(NAND_CMD_READ0, 0),
> > +		NAND_OP_ADDR(3, addrs, sdr->tWB_max),
> > +		NAND_OP_WAIT_RDY(sdr->tR_max, sdr->tRR_min),
> > +		NAND_OP_DATA_IN(len, buf, 0),
> > +	};
> > +	struct nand_operation op = NAND_OPERATION(instrs);
> > +
> > +	/* Drop the DATA_OUT instruction if len is set to 0. */
> > +	if (!len)
> > +		op.ninstrs--;
> > +
> > +	if (offset_in_page >= mtd->writesize)
> > +		instrs[0].cmd.opcode = NAND_CMD_READOOB;
> > +	else if (offset_in_page >= 256)
> > +		instrs[0].cmd.opcode = NAND_CMD_READ1;
> > +
> > +	if (nand_fill_column_cycles(chip, addrs, offset_in_page) <
> > 0)  
> 
> Why not returning the retcode of nand_fill_column_cycles() directly?
> 
> 	ret = nand_fill_column_cycles(chip, addrs, offset_in_page)
> 	if (ret < 0)
> 		return ret;

Changed.

> 
> > +		return -EINVAL;
> > +
> > +	addrs[1] = page;
> > +	addrs[2] = page >> 8;
> > +
> > +	if (chip->options & NAND_ROW_ADDR_3) {
> > +		addrs[3] = page >> 16;
> > +		instrs[1].addr.naddrs++;
> > +	}
> > +
> > +	return nand_exec_op(chip, &op);
> > +}
> > +
> > +static int nand_lp_exec_read_page_op(struct nand_chip *chip,
> > unsigned int page,
> > +				     unsigned int offset_in_page,
> > void *buf,
> > +				     unsigned int len)
> > +{
> > +	const struct nand_sdr_timings *sdr =
> > +		nand_get_sdr_timings(&chip->data_interface);
> > +	u8 addrs[5];
> > +	struct nand_op_instr instrs[] = {
> > +		NAND_OP_CMD(NAND_CMD_READ0, 0),
> > +		NAND_OP_ADDR(4, addrs, 0),
> > +		NAND_OP_CMD(NAND_CMD_READSTART, sdr->tWB_max),
> > +		NAND_OP_WAIT_RDY(sdr->tR_max, sdr->tRR_min),
> > +		NAND_OP_DATA_IN(len, buf, 0),
> > +	};
> > +	struct nand_operation op = NAND_OPERATION(instrs);
> > +
> > +	/* Drop the DATA_IN instruction if len is set to 0. */
> > +	if (!len)
> > +		op.ninstrs--;
> > +
> > +	if (nand_fill_column_cycles(chip, addrs, offset_in_page))  
> 
> Should be
> 
> 	if (nand_fill_column_cycles(chip, addrs, offset_in_page) < 0)

+ use of ret =, if (ret < 0)...

> 
> > +		return -EINVAL;
> > +
> > +	addrs[2] = page;
> > +	addrs[3] = page >> 8;
> > +
> > +	if (chip->options & NAND_ROW_ADDR_3) {
> > +		addrs[4] = page >> 16;
> > +		instrs[1].addr.naddrs++;
> > +	}
> > +
> > +	return nand_exec_op(chip, &op);
> > +}
> > +
> > +/**
> >   * nand_read_page_op - Do a READ PAGE operation
> >   * @chip: The NAND chip
> >   * @page: page to read
> > @@ -1246,17 +1358,26 @@ static int nand_init_data_interface(struct
> > nand_chip *chip)
> >   * Returns 0 for success or negative error code otherwise
> >   */
> >  int nand_read_page_op(struct nand_chip *chip, unsigned int page,
> > -		      unsigned int column, void *buf, unsigned int
> > len)
> > +		      unsigned int offset_in_page, void *buf,
> > unsigned int len)  
> 
> You should change the parameter name in patch 1 since this function is
> introduced there.

Ok, done in other places as well.

> 
> >  {
> >  	struct mtd_info *mtd = nand_to_mtd(chip);
> >  
> >  	if (len && !buf)
> >  		return -EINVAL;
> >  
> > -	if (column + len > mtd->writesize + mtd->oobsize)
> > +	if (offset_in_page + len > mtd->writesize + mtd->oobsize)
> >  		return -EINVAL;
> >  
> > -	chip->cmdfunc(mtd, NAND_CMD_READ0, column, page);
> > +	if (chip->exec_op) {
> > +		if (mtd->writesize > 512)
> > +			return nand_lp_exec_read_page_op(
> > +				chip, page, offset_in_page, buf,
> > len); +
> > +		return nand_sp_exec_read_page_op(chip, page,
> > offset_in_page,
> > +						 buf, len);
> > +	}
> > +
> > +	chip->cmdfunc(mtd, NAND_CMD_READ0, offset_in_page, page);
> >  	if (len)
> >  		chip->read_buf(mtd, buf, len);
> >  
> > @@ -1286,6 +1407,25 @@ static int nand_read_param_page_op(struct
> > nand_chip *chip, u8 page, void *buf, if (len && !buf)
> >  		return -EINVAL;
> >  
> > +	if (chip->exec_op) {
> > +		const struct nand_sdr_timings *sdr =
> > +
> > nand_get_sdr_timings(&chip->data_interface);
> > +		struct nand_op_instr instrs[] = {
> > +			NAND_OP_CMD(NAND_CMD_PARAM, 0),
> > +			NAND_OP_ADDR(1, &page, sdr->tWB_max),
> > +			NAND_OP_WAIT_RDY(sdr->tR_max,
> > sdr->tRR_min),
> > +			NAND_OP_8BIT_DATA_IN(len, buf, 0),
> > +		};
> > +		struct nand_operation op =
> > +			NAND_OPERATION(instrs);
> > +
> > +		/* Drop the DATA_IN instruction if len is set to
> > 0. */
> > +		if (!len)
> > +			op.ninstrs--;
> > +
> > +		return nand_exec_op(chip, &op);
> > +	}
> > +
> >  	chip->cmdfunc(mtd, NAND_CMD_PARAM, page, -1);
> >  	for (i = 0; i < len; i++)
> >  		p[i] = chip->read_byte(mtd);  
> 
> [...]
> 
> > +
> >  /**
> >   * nand_prog_page_begin_op - starts a PROG PAGE operation
> >   * @chip: The NAND chip
> > @@ -1371,7 +1608,7 @@ EXPORT_SYMBOL_GPL(nand_read_oob_op);
> >   * Returns 0 for success or negative error code otherwise
> >   */
> >  int nand_prog_page_begin_op(struct nand_chip *chip, unsigned int
> > page,
> > -			    unsigned int column, const void *buf,
> > +			    unsigned int offset_in_page, const
> > void *buf, unsigned int len)
> >  {
> >  	struct mtd_info *mtd = nand_to_mtd(chip);
> > @@ -1379,10 +1616,14 @@ int nand_prog_page_begin_op(struct
> > nand_chip *chip, unsigned int page, if (len && !buf)
> >  		return -EINVAL;
> >  
> > -	if (column + len > mtd->writesize + mtd->oobsize)
> > +	if (offset_in_page + len > mtd->writesize + mtd->oobsize)
> >  		return -EINVAL;
> >  
> > -	chip->cmdfunc(mtd, NAND_CMD_SEQIN, column, page);
> > +	if (chip->exec_op)
> > +		return nand_exec_prog_page_op(
> > +			chip, page, offset_in_page, buf, len,
> > false);  
> 
> Param alignment please.

Done

> 
> > +
> > +	chip->cmdfunc(mtd, NAND_CMD_SEQIN, offset_in_page, page);
> >  
> >  	if (buf)
> >  		chip->write_buf(mtd, buf, len);
> > @@ -1405,6 +1646,19 @@ int nand_prog_page_end_op(struct nand_chip
> > *chip) struct mtd_info *mtd = nand_to_mtd(chip);
> >  	int status;
> >  
> > +	if (chip->exec_op) {
> > +		const struct nand_sdr_timings *sdr =
> > +
> > nand_get_sdr_timings(&chip->data_interface);
> > +		struct nand_op_instr instrs[] = {
> > +			NAND_OP_CMD(NAND_CMD_PAGEPROG,
> > sdr->tWB_max),
> > +			NAND_OP_WAIT_RDY(sdr->tPROG_max, 0),
> > +		};
> > +		struct nand_operation op =
> > +			NAND_OPERATION(instrs);
> > +
> > +		return nand_exec_op(chip, &op);
> > +	}
> > +
> >  	chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1);
> >  
> >  	status = chip->waitfunc(mtd, chip);

[...]

> > +/**
> > + * nand_op_parser_must_split_instr - Checks if an instruction must
> > be split
> > + * @pat: the parser pattern that match
> > + * @instr: the instruction array to check
> > + * @start_offset: the offset from which to start in the first
> > instruction of the
> > + *		  @instr array
> > + *
> > + * Some NAND controllers are limited and cannot send X address
> > cycles with a
> > + * unique operation, or cannot read/write more than Y bytes at the
> > same time.
> > + * In this case, reduce the scope of this set of operation, and
> > trigger another
> > + * instruction with the rest.  
> 
> "
> In this case, split the instruction that does not fit in a single
> controller-operation into two or more chunks.
> "

Ok.

> 
> > + *
> > + * Returns true if the array of instruction must be split, false
> > otherwise.  
> 
> s/array of//

Right.

> 
> > + * The @start_offset parameter is also updated to the offset at
> > which the first
> > + * instruction of the next bundle of instruction must start (if an
> > address or a  
> 
> "
> The @start_offset parameter is also updated to the offset at which the
> the next bundle of instructions must start ...
> "

Ok.

> 
> > + * data instruction).
> > + */
> > +static bool
> > +nand_op_parser_must_split_instr(const struct
> > nand_op_parser_pattern_elem *pat,
> > +				const struct nand_op_instr *instr,
> > +				unsigned int *start_offset)
> > +{

[...]

> > +#if IS_ENABLED(CONFIG_DYNAMIC_DEBUG) || defined(DEBUG)
> > +static void nand_op_parser_trace(const struct nand_op_parser_ctx
> > *ctx,
> > +				 bool success)
> > +{
> > +	const struct nand_op_instr *instr;
> > +	bool in_subop = false;
> > +	char *is_in =  "    ->", *is_out = "      ", *prefix;
> > +	char *buf;
> > +	unsigned int len, off = 0;
> > +	int i, j;
> > +
> > +	if (success)
> > +		pr_debug("executing subop:\n");
> > +	else
> > +		pr_debug("pattern not found:\n");
> > +
> > +	for (i = 0; i < ctx->ninstrs; i++) {
> > +		instr = &ctx->instrs[i];
> > +
> > +		/*
> > +		 * ctx->instr_idx is not reliable because it may
> > already had  
> 
> 									 have

Corrected.

> 
> > +		 * been updated by the parser. Use pointers
> > comparison instead.
> > +		 */
> > +		if (instr == &ctx->subop.instrs[0])
> > +			in_subop = true;  
> 
> It seems in_subop is only used to select the prefix, so you can just
> get rid of it and do
> 
> 		if (instr == &ctx->subop.instrs[0])
> 			prefix = "    ->";

Ok, changed.

> 
> BTW, if success is false, you should not even try to change the
> prefix, right?

I do not agree, there is no point in not showing what bundle of
instructions failed!

> 
> > +
> > +		if (in_subop)
> > +			prefix = is_in;
> > +		else
> > +			prefix = is_out;
> > +
> > +		switch (instr->type) {
> > +		case NAND_OP_CMD_INSTR:
> > +			pr_debug("%sCMD      [0x%02x]\n", prefix,
> > +				 instr->cmd.opcode);
> > +			break;
> > +		case NAND_OP_ADDR_INSTR:
> > +			/*
> > +			 * A log line is much less than 50 bytes,
> > plus 5 bytes
> > +			 * per address cycle to display.
> > +			 */
> > +			len = 50 + 5 * instr->addr.naddrs;
> > +			buf = kmalloc(len, GFP_KERNEL);
> > +			if (!buf)
> > +				return;
> > +			memset(buf, 0, len);
> > +			off += snprintf(buf, len, "ADDR     [%d
> > cyc:",
> > +					instr->addr.naddrs);
> > +			for (j = 0; j < instr->addr.naddrs; j++)
> > +				off += snprintf(&buf[off], len -
> > off, " 0x%02x",
> > +
> > instr->addr.addrs[j]);
> > +			pr_debug("%s%s]\n", prefix, buf);
> > +			break;
> > +		case NAND_OP_DATA_IN_INSTR:
> > +			pr_debug("%sDATA_IN  [%d B%s]\n", prefix,
> > +				 instr->data.len,
> > +				 instr->data.force_8bit ? ", force
> > 8-bit" : "");
> > +			break;
> > +		case NAND_OP_DATA_OUT_INSTR:
> > +			pr_debug("%sDATA_OUT [%d B%s]\n", prefix,
> > +				 instr->data.len,
> > +				 instr->data.force_8bit ? ", force
> > 8-bit" : "");
> > +			break;
> > +		case NAND_OP_WAITRDY_INSTR:
> > +			pr_debug("%sWAITRDY  [max %d ms]\n",
> > prefix,
> > +				 instr->waitrdy.timeout_ms);
> > +			break;
> > +		}
> > +
> > +		if (instr == &ctx->subop.instrs[ctx->subop.ninstrs
> > - 1])
> > +			in_subop = false;  
> 
> 		if (instr == &ctx->subop.instrs[ctx->subop.ninstrs -
> 1]) prefix = "      ";
> 
> and initialize prefix to "      " at the beginning of the function.
> 
> > +	}
> > +}
> > +#else
> > +static void nand_op_parser_trace(const struct nand_op_parser_ctx
> > *ctx,
> > +				 bool success)
> > +{
> > +	/* NOP */
> > +}
> > +#endif
> > +
> > +/**
> > + * nand_op_parser_exec_op - exec_op parser
> > + * @chip: the NAND chip
> > + * @parser: the parser to use given by the controller driver
> > + * @op: the NAND operation to address
> > + * @check_only: flag asking if the entire operation could be
> > handled
> > + *
> > + * Function that must be called by each driver that implement the
> > "exec_op API"
> > + * in their own ->exec_op() implementation.
> > + *
> > + * The function iterates on all the instructions asked and make
> > use of internal
> > + * parsers to find matches between the instruction list and the
> > handled patterns
> > + * filled by the controller drivers inside the @parser structure.
> > If needed, the
> > + * instructions could be split into sub-operations and be executed
> > sequentially.
> > + */
> > +int nand_op_parser_exec_op(struct nand_chip *chip,
> > +			   const struct nand_op_parser *parser,
> > +			   const struct nand_operation *op, bool
> > check_only) +{
> > +	struct nand_op_parser_ctx ctx = {
> > +		.instrs = op->instrs,
> > +		.ninstrs = op->ninstrs,
> > +	};
> > +	unsigned int i;
> > +
> > +	while (ctx.instr_idx < op->ninstrs) {
> > +		bool pattern_found = false;
> > +		int ret;
> > +
> > +		for (i = 0; i < parser->npatterns; i++) {
> > +			const struct nand_op_parser_pattern
> > *pattern; +
> > +			pattern = &parser->patterns[i];
> > +			if (!nand_op_parser_match_pat(pattern,
> > &ctx))
> > +				continue;
> > +
> > +			nand_op_parser_trace(&ctx, true);
> > +  
> 
> pattern_found should be set to true here. But I'm not even sure you
> really need this variable.
> 
> > +			if (check_only)
> > +				break;
> > +
> > +			ret = pattern->exec(chip, &ctx.subop);
> > +			if (ret)
> > +				return ret;
> > +
> > +			pattern_found = true;
> > +		}
> > +
> > +		if (!pattern_found) {  
> 
> Just test
> 
> 		if (i == parser->npatterns) {
> 
> and you should be good.

Indeed it works.

> 
> > +			nand_op_parser_trace(&ctx, false);
> > +			return -ENOTSUPP;
> > +		}
> > +	}
> > +
> > +	return 0;
> > +}
> > +EXPORT_SYMBOL_GPL(nand_op_parser_exec_op);
> > +
> > +static bool nand_instr_is_data(const struct nand_op_instr *instr)
> > +{
> > +	return instr && (instr->type == NAND_OP_DATA_IN_INSTR ||
> > +			 instr->type == NAND_OP_DATA_OUT_INSTR);
> > +}
> > +
> > +static bool nand_subop_instr_is_valid(const struct nand_subop
> > *subop,
> > +				      unsigned int op_id)  
> 
> s/op_id/instr_idx/

Changed everywhere.

> 
> > +{
> > +	return subop && op_id < subop->ninstrs;
> > +}
> > +
> > +static int nand_subop_get_start_off(const struct nand_subop *subop,
> > +				    unsigned int op_id)  
> 
> Ditto.
> 
> > +{
> > +	if (op_id)
> > +		return 0;
> > +
> > +	return subop->first_instr_start_off;
> > +}
> > +
> > +/**
> > + * nand_subop_get_addr_start_off - Get the start offset in an
> > address array
> > + * @subop: The entire sub-operation
> > + * @op_id: Index of the instruction inside the sub-operation  
> 
> instr_idx.
> 
> > + *
> > + * Instructions arrays may be split by the parser between
> > instructions,
> > + * and also in the middle of an address instruction if the number
> > of cycles
> > + * to assert in one operation is not supported by the controller.
> > + *
> > + * For this, instead of using the first index of the ->addr.addrs
> > field from the
> > + * address instruction, the NAND controller driver must use this
> > helper that
> > + * will either return 0 if the index does not point to the first
> > instruction of
> > + * the sub-operation, or the offset of the next starting offset
> > inside the
> > + * address cycles.  
> 
> I think you can drop this paragraph which IMO brings more confusion to
> what this function does.
> 
> > + *
> > + * Returns the offset of the first address cycle to assert from
> > the pointed
> > + * address instruction.  
> 
> This part is definitely clearer.

Ok.

> 
> > + */
> > +int nand_subop_get_addr_start_off(const struct nand_subop *subop,
> > +				  unsigned int op_id)
> > +{
> > +	if (!nand_subop_instr_is_valid(subop, op_id) ||
> > +	    subop->instrs[op_id].type != NAND_OP_ADDR_INSTR)
> > +		return -EINVAL;
> > +
> > +	return nand_subop_get_start_off(subop, op_id);
> > +}
> > +EXPORT_SYMBOL_GPL(nand_subop_get_addr_start_off);
> > +
> > +/**
> > + * nand_subop_get_num_addr_cyc - Get the remaining address cycles
> > to assert
> > + * @subop: The entire sub-operation
> > + * @op_id: Index of the instruction inside the sub-operation  
> 
> instr_idx
> 
> > + *
> > + * Instructions arrays may be split by the parser between
> > instructions,
> > + * and also in the middle of an address instruction if the number
> > of cycles
> > + * to assert in one operation is not supported by the controller.
> > + *
> > + * For this, instead of using the ->addr.naddrs fields of the
> > address
> > + * instruction, the NAND controller driver must use this helper
> > that will
> > + * return the actual number of cycles to assert between the first
> > and last
> > + * offset asked for this particular instruction.
> > + *
> > + * Returns the number of address cycles to assert from the pointed
> > address
> > + * instruction.
> > + */
> > +int nand_subop_get_num_addr_cyc(const struct nand_subop *subop,
> > +				unsigned int op_id)
> > +{
> > +	int start_off, end_off;
> > +
> > +	if (!nand_subop_instr_is_valid(subop, op_id) ||
> > +	    subop->instrs[op_id].type != NAND_OP_ADDR_INSTR)
> > +		return -EINVAL;
> > +
> > +	start_off = nand_subop_get_addr_start_off(subop, op_id);
> > +
> > +	if (op_id == subop->ninstrs - 1 &&
> > +	    subop->last_instr_end_off)
> > +		end_off = subop->last_instr_end_off;
> > +	else
> > +		end_off = subop->instrs[op_id].addr.naddrs;
> > +
> > +	return end_off - start_off;
> > +}
> > +EXPORT_SYMBOL_GPL(nand_subop_get_num_addr_cyc);
> > +
> > +/**
> > + * nand_subop_get_data_start_off - Get the start offset in a data
> > array
> > + * @subop: The entire sub-operation
> > + * @op_id: Index of the instruction inside the sub-operation
> > + *
> > + * Instructions arrays may be split by the parser between
> > instructions,
> > + * and also in the middle of a data instruction if the number of
> > bytes to access
> > + * in one operation is greater that the controller limit.
> > + *
> > + * For this, instead of using the first index of the ->data.buf
> > field from the
> > + * data instruction, the NAND controller driver must use this
> > helper that
> > + * will either return 0 if the index does not point to the first
> > instruction of
> > + * the sub-operation, or the offset of the next starting offset
> > inside the
> > + * data buffer.  
> 
> Same as for nand_subop_get_addr_start_off(), the explanation is
> confusing. I think we can drop it.

Dropped then.

> 
> > + *
> > + * Returns the data offset inside the pointed data instruction
> > buffer from which
> > + * to start.
> > + */
> > +int nand_subop_get_data_start_off(const struct nand_subop *subop,
> > +				  unsigned int op_id)
> > +{
> > +	if (!nand_subop_instr_is_valid(subop, op_id) ||
> > +	    !nand_instr_is_data(&subop->instrs[op_id]))
> > +		return -EINVAL;
> > +
> > +	return nand_subop_get_start_off(subop, op_id);
> > +}
> > +EXPORT_SYMBOL_GPL(nand_subop_get_data_start_off);
> > +
> > +/**
> > + * nand_subop_get_data_len - Get the number of bytes to retrieve
> > + * @subop: The entire sub-operation
> > + * @op_id: Index of the instruction inside the sub-operation
> > + *
> > + * Instructions arrays may be split by the parser between
> > instructions,
> > + * and also in the middle of a data instruction if the number of
> > bytes to access
> > + * in one operation is greater that the controller limit.
> > + *
> > + * For this, instead of using the ->data.len field from the data
> > instruction,
> > + * the NAND controller driver must use this helper that will
> > return the actual
> > + * length of data to move between the first and last offset asked
> > for this
> > + * particular instruction.
> > + *
> > + * Returns the length of the data to move from the pointed data
> > instruction.
> > + */
> > +int nand_subop_get_data_len(const struct nand_subop *subop,
> > +			    unsigned int op_id)
> > +{
> > +	int start_off = 0, end_off;
> > +
> > +	if (!nand_subop_instr_is_valid(subop, op_id) ||
> > +	    !nand_instr_is_data(&subop->instrs[op_id]))
> > +		return -EINVAL;
> > +
> > +	start_off = nand_subop_get_data_start_off(subop, op_id);
> > +
> > +	if (op_id == subop->ninstrs - 1 &&
> > +	    subop->last_instr_end_off)
> > +		end_off = subop->last_instr_end_off;
> > +	else
> > +		end_off = subop->instrs[op_id].data.len;
> > +
> > +	return end_off - start_off;
> > +}
> > +EXPORT_SYMBOL_GPL(nand_subop_get_data_len);
> > +
> > +/**
> >   * nand_reset - Reset and initialize a NAND device
> >   * @chip: The NAND chip
> >   * @chipnr: Internal die id
> > @@ -3940,7 +4815,7 @@ static void nand_set_defaults(struct
> > nand_chip *chip) chip->chip_delay = 20;
> >  
> >  	/* check, if a user supplied command function given */
> > -	if (chip->cmdfunc == NULL)
> > +	if (chip->cmdfunc == NULL && !chip->exec_op)
> >  		chip->cmdfunc = nand_command;
> >  
> >  	/* check, if a user supplied wait function given */
> > @@ -4823,15 +5698,35 @@ int nand_scan_ident(struct mtd_info *mtd,
> > int maxchips, if (!mtd->name && mtd->dev.parent)
> >  		mtd->name = dev_name(mtd->dev.parent);
> >  
> > -	if ((!chip->cmdfunc || !chip->select_chip)
> > && !chip->cmd_ctrl) {
> > +	/*
> > +	 * ->cmdfunc() is legacy and will only be used if
> > ->exec_op() is not
> > +	 * populated.
> > +	 */
> > +	if (chip->exec_op) {
> >  		/*
> > -		 * Default functions assigned for chip_select() and
> > -		 * cmdfunc() both expect cmd_ctrl() to be
> > populated,
> > -		 * so we need to check that that's the case
> > +		 * The implementation of ->exec_op() heavily
> > relies on timings
> > +		 * to be accessible through the
> > nand_data_interface structure.
> > +		 * Thus, the ->setup_data_interface() hook must be
> > provided. The
> > +		 * controller driver will be noticed of delays it
> > must apply
> > +		 * after each ->exec_op() instruction (if any)
> > through the
> > +		 * .delay_ns field. The driver will then choose to
> > handle the
> > +		 * delays manually if the controller cannot do it
> > natively. */
> > -		pr_err("chip.cmd_ctrl() callback is not provided");
> > -		return -EINVAL;
> > +		if (!chip->setup_data_interface) {
> > +			pr_err("->setup_data_interface() should be
> > provided\n");
> > +			return -EINVAL;
> > +		}
> > +	} else {
> > +		/*
> > +		 * Default functions assigned for ->cmdfunc() and
> > +		 * ->select_chip() both expect ->cmd_ctrl() to be
> > populated.
> > +		 */
> > +		if ((!chip->cmdfunc || !chip->select_chip)
> > && !chip->cmd_ctrl) {
> > +			pr_err("->cmd_ctrl() should be
> > provided\n");
> > +			return -EINVAL;
> > +		}
> >  	}
> > +
> >  	/* Set the default functions */
> >  	nand_set_defaults(chip);
> >  
> > diff --git a/drivers/mtd/nand/nand_hynix.c
> > b/drivers/mtd/nand/nand_hynix.c index 04e3ab7a476c..81c382f24513
> > 100644 --- a/drivers/mtd/nand/nand_hynix.c
> > +++ b/drivers/mtd/nand/nand_hynix.c
> > @@ -74,19 +74,36 @@ static bool hynix_nand_has_valid_jedecid(struct
> > nand_chip *chip) return !strncmp("JEDEC", jedecid, sizeof(jedecid));
> >  }
> >  
> > +static int hynix_nand_cmd_op(struct nand_chip *chip, u8 cmd)  
> 
> Maybe you can introduce the helper in patch 1

Done.

> 
> > +{
> > +	struct mtd_info *mtd = nand_to_mtd(chip);
> > +
> > +	if (chip->exec_op) {
> > +		struct nand_op_instr instrs[] = {
> > +			NAND_OP_CMD(cmd, 0),
> > +		};
> > +		struct nand_operation op = NAND_OPERATION(instrs);
> > +
> > +		return nand_exec_op(chip, &op);  
> 
> And then ass this block of code in this patch.

Sure.

> 
> > +	}
> > +
> > +	chip->cmdfunc(mtd, cmd, -1, -1);
> > +
> > +	return 0;
> > +}
> > +
> >  static int hynix_nand_setup_read_retry(struct mtd_info *mtd, int
> > retry_mode) {
> >  	struct nand_chip *chip = mtd_to_nand(mtd);
> >  	struct hynix_nand *hynix =
> > nand_get_manufacturer_data(chip); const u8 *values;
> > -	int status;
> >  	int i;
> >  
> >  	values = hynix->read_retry->values +
> >  		 (retry_mode * hynix->read_retry->nregs);
> >  
> >  	/* Enter 'Set Hynix Parameters' mode */
> > -	chip->cmdfunc(mtd, NAND_HYNIX_CMD_SET_PARAMS, -1, -1);
> > +	hynix_nand_cmd_op(chip, NAND_HYNIX_CMD_SET_PARAMS);
> >  
> >  	/*
> >  	 * Configure the NAND in the requested read-retry mode.
> > @@ -101,16 +118,17 @@ static int hynix_nand_setup_read_retry(struct
> > mtd_info *mtd, int retry_mode) int column =
> > hynix->read_retry->regs[i]; 
> >  		column |= column << 8;
> > -		chip->cmdfunc(mtd, NAND_CMD_NONE, column, -1);
> > -		chip->write_byte(mtd, values[i]);
> > +		if (chip->exec_op) {
> > +			nand_change_write_column_op(chip, column,
> > +						    &values[i], 1,
> > true);  
> 
> This is not a nand_change_write_column_op() op, here the cmd cycle is
> set to NAND_CMD_NONE. You should have your own operation defined to
> handle this sequence.

That is right.
However, this is not handled correctly in nand_command_lp(), I will
send a patch very soon to fix it.

> 
> > +		} else {
> > +			chip->cmdfunc(mtd, NAND_CMD_NONE, column,
> > -1);
> > +			chip->write_byte(mtd, values[i]);
> > +		}
> >  	}
> >  
> >  	/* Apply the new settings. */
> > -	chip->cmdfunc(mtd, NAND_HYNIX_CMD_APPLY_PARAMS, -1, -1);
> > -
> > -	status = chip->waitfunc(mtd, chip);
> > -	if (status & NAND_STATUS_FAIL)
> > -		return -EIO;
> > +	hynix_nand_cmd_op(chip, NAND_HYNIX_CMD_APPLY_PARAMS);  
> 
> No, it's wrong, you miss the WAITRDY instruction to be compatible
> with what was done before.

Re-added.

> 
> >  
> >  	return 0;
> >  }
> > @@ -173,32 +191,65 @@ static int hynix_read_rr_otp(struct nand_chip
> > *chip, 
> >  	nand_reset_op(chip);
> >  
> > -	chip->cmdfunc(mtd, NAND_HYNIX_CMD_SET_PARAMS, -1, -1);
> > +	hynix_nand_cmd_op(chip, NAND_HYNIX_CMD_SET_PARAMS);
> >  
> >  	for (i = 0; i < info->nregs; i++) {
> >  		int column = info->regs[i];
> >  
> >  		column |= column << 8;
> > -		chip->cmdfunc(mtd, NAND_CMD_NONE, column, -1);
> > -		chip->write_byte(mtd, info->values[i]);
> > +		if (chip->exec_op) {
> > +			nand_change_write_column_op(chip, column,
> > +
> > &info->values[i], 1, true);
> > +		} else {
> > +			chip->cmdfunc(mtd, NAND_CMD_NONE, column,
> > -1);
> > +			chip->write_byte(mtd, info->values[i]);
> > +		}  
> 
> Same comments apply here.
> 
> >  	}
> >  
> > -	chip->cmdfunc(mtd, NAND_HYNIX_CMD_APPLY_PARAMS, -1, -1);
> > +	hynix_nand_cmd_op(chip, NAND_HYNIX_CMD_APPLY_PARAMS);
> >  
> >  	/* Sequence to enter OTP mode? */
> > -	chip->cmdfunc(mtd, 0x17, -1, -1);
> > -	chip->cmdfunc(mtd, 0x04, -1, -1);
> > -	chip->cmdfunc(mtd, 0x19, -1, -1);
> > +	if (chip->exec_op) {
> > +		struct nand_op_instr instrs[] = {
> > +			NAND_OP_CMD(0x17, 0),
> > +			NAND_OP_CMD(0x04, 0),
> > +			NAND_OP_CMD(0x19, 0),
> > +		};
> > +		struct nand_operation op = NAND_OPERATION(instrs);
> > +
> > +		nand_exec_op(chip, &op);
> > +	} else {
> > +		chip->cmdfunc(mtd, 0x17, -1, -1);
> > +		chip->cmdfunc(mtd, 0x04, -1, -1);
> > +		chip->cmdfunc(mtd, 0x19, -1, -1);
> > +	}  
> 
> Why not creating a hynix_nand_enter_otp_mode_op() for that?

I think this file needs some kind of rework and may really benefit
from ->exec_op() new API, but this series is maybe not the best place to
do it. Let's keep it this way for now.

> 
> >  
> >  	/* Now read the page */
> >  	nand_read_page_op(chip, info->page, 0, buf, info->size);
> >  
> >  	/* Put everything back to normal */
> >  	nand_reset_op(chip);
> > -	chip->cmdfunc(mtd, NAND_HYNIX_CMD_SET_PARAMS, 0x38, -1);
> > -	chip->write_byte(mtd, 0x0);
> > -	chip->cmdfunc(mtd, NAND_HYNIX_CMD_APPLY_PARAMS, -1, -1);
> > -	chip->cmdfunc(mtd, NAND_CMD_READ0, 0x0, -1);
> > +	if (chip->exec_op) {
> > +		const struct nand_sdr_timings *sdr =
> > +
> > nand_get_sdr_timings(&chip->data_interface);
> > +		u8 byte = 0;
> > +		u8 addr = 0x38;
> > +		struct nand_op_instr instrs[] = {
> > +			NAND_OP_CMD(NAND_HYNIX_CMD_SET_PARAMS, 0),
> > +			NAND_OP_ADDR(1, &addr, sdr->tCCS_min),
> > +			NAND_OP_8BIT_DATA_OUT(1, &byte, 0),
> > +			NAND_OP_CMD(NAND_HYNIX_CMD_APPLY_PARAMS,
> > 0),
> > +			NAND_OP_CMD(NAND_CMD_READ0, 0),
> > +		};
> > +		struct nand_operation op = NAND_OPERATION(instrs);
> > +
> > +		nand_exec_op(chip, &op);
> > +	} else {
> > +		chip->cmdfunc(mtd, NAND_HYNIX_CMD_SET_PARAMS,
> > 0x38, -1);
> > +		chip->write_byte(mtd, 0x0);
> > +		chip->cmdfunc(mtd, NAND_HYNIX_CMD_APPLY_PARAMS,
> > -1, -1);
> > +		chip->cmdfunc(mtd, NAND_CMD_READ0, 0x0, -1);
> > +	}  
> 
> Same here.
> 
> >  
> >  	return 0;
> >  }
> > diff --git a/drivers/mtd/nand/nand_micron.c
> > b/drivers/mtd/nand/nand_micron.c index 543352380ffa..109d8003e33d
> > 100644 --- a/drivers/mtd/nand/nand_micron.c
> > +++ b/drivers/mtd/nand/nand_micron.c
> > @@ -117,14 +117,38 @@ micron_nand_read_page_on_die_ecc(struct
> > mtd_info *mtd, struct nand_chip *chip, uint8_t *buf, int
> > oob_required, int page)
> >  {
> > -	int status;
> > +	u8 status;
> >  	int max_bitflips = 0;
> >  
> >  	micron_nand_on_die_ecc_setup(chip, true);
> >  
> > -	chip->cmdfunc(mtd, NAND_CMD_READ0, 0x00, page);
> > -	chip->cmdfunc(mtd, NAND_CMD_STATUS, -1, -1);
> > -	status = chip->read_byte(mtd);
> > +	if (chip->exec_op) {
> > +		u8 addrs[5] = {};
> > +		struct nand_op_instr instrs[] = {
> > +			NAND_OP_CMD(NAND_CMD_READ0, 0),
> > +			NAND_OP_ADDR(4, addrs, 0),
> > +			NAND_OP_CMD(NAND_CMD_STATUS, 0),
> > +			NAND_OP_8BIT_DATA_IN(1, &status, 0),
> > +		};
> > +		struct nand_operation op = NAND_OPERATION(instrs);
> > +
> > +		if (nand_fill_column_cycles(chip, addrs, 0))  
> 
> 		if (nand_fill_column_cycles(chip, addrs, 0) < 0)

Yes.

> 
> > +			return -EINVAL;
> > +
> > +		addrs[2] = page;
> > +		addrs[3] = page >> 8;
> > +		if (chip->options & NAND_ROW_ADDR_3) {
> > +			addrs[4] = page >> 16;
> > +			instrs[1].addr.naddrs++;
> > +		}
> > +
> > +		nand_exec_op(chip, &op);
> > +	} else {
> > +		chip->cmdfunc(mtd, NAND_CMD_READ0, 0x00, page);
> > +		chip->cmdfunc(mtd, NAND_CMD_STATUS, -1, -1);
> > +		status = chip->read_byte(mtd);
> > +	}
> > +
> >  	if (status & NAND_STATUS_FAIL)
> >  		mtd->ecc_stats.failed++;
> >  	/*
> > diff --git a/include/linux/mtd/rawnand.h
> > b/include/linux/mtd/rawnand.h index 1acc26ed0e91..302f231df65e
> > 100644 --- a/include/linux/mtd/rawnand.h
> > +++ b/include/linux/mtd/rawnand.h
> > @@ -751,6 +751,337 @@ struct nand_manufacturer_ops {
> >  };
> >  
> >  /**
> > + * struct nand_op_cmd_instr - Definition of a command instruction
> > + * @opcode: the command to assert in one cycle
> > + */
> > +struct nand_op_cmd_instr {
> > +	u8 opcode;
> > +};
> > +
> > +/**
> > + * struct nand_op_addr_instr - Definition of an address instruction
> > + * @naddrs: length of the @addrs array
> > + * @addrs: array containing the address cycles to assert
> > + */
> > +struct nand_op_addr_instr {
> > +	unsigned int naddrs;
> > +	const u8 *addrs;
> > +};
> > +
> > +/**
> > + * struct nand_op_data_instr - Definition of a data instruction
> > + * @len: number of data bytes to move
> > + * @in: buffer to fill when reading from the NAND chip
> > + * @out: buffer to read from when writing to the NAND chip
> > + * @force_8bit: force 8-bit access
> > + *
> > + * Please note that "in" and "out" are inverted from the ONFI
> > specification
> > + * and are from the controller perspective, so a "in" is a read
> > from the NAND
> > + * chip while a "out" is a write to the NAND chip.
> > + */
> > +struct nand_op_data_instr {
> > +	unsigned int len;
> > +	union {
> > +		void *in;
> > +		const void *out;
> > +	};
> > +	bool force_8bit;
> > +};
> > +
> > +/**
> > + * struct nand_op_waitrdy_instr - Definition of a wait ready
> > instruction
> > + * @timeout_ms: maximum delay while waiting for the ready/busy pin
> > in ms
> > + */
> > +struct nand_op_waitrdy_instr {
> > +	unsigned int timeout_ms;
> > +};
> > +
> > +/**
> > + * enum nand_op_instr_type - Enumeration of all the instructions  
> 
> *of all instruction types

Changed.

> 
> > + *
> > + * Please note that data instructions are separated into DATA_IN
> > and DATA_OUT
> > + * instructions.
> > + */
> > +enum nand_op_instr_type {
> > +	NAND_OP_CMD_INSTR,
> > +	NAND_OP_ADDR_INSTR,
> > +	NAND_OP_DATA_IN_INSTR,
> > +	NAND_OP_DATA_OUT_INSTR,
> > +	NAND_OP_WAITRDY_INSTR,
> > +};
> > +
> > +/**
> > + * struct nand_op_instr - Generic definition of and instruction  
> 
> s/and/an/

Corrected.

> 
> > + * @type: an enumeration of the instruction type
> > + * @cmd/@addr/@data/@waitrdy: the actual instruction to refer
> > depending on the
> > + *			      value of @type  
> 
> "
> extra data associated to the instruction. You'll have to use
> the appropriate element depending on @type"

Changed.

> 
> > + * @delay_ns: delay to apply by the controller after the
> > instruction has been
> > + *	      actually executed (most of them are directly
> > handled by the
> > + *	      controllers once the timings negociation has been
> > done)
> > + */
> > +struct nand_op_instr {
> > +	enum nand_op_instr_type type;
> > +	union {
> > +		struct nand_op_cmd_instr cmd;
> > +		struct nand_op_addr_instr addr;
> > +		struct nand_op_data_instr data;
> > +		struct nand_op_waitrdy_instr waitrdy;
> > +	};
> > +	unsigned int delay_ns;
> > +};
> > +
> > +/*
> > + * Special handling must be done for the WAITRDY timeout parameter
> > as it usually
> > + * is either tPROG (after a prog), tR (before a read), tRST
> > (during a reset) or
> > + * tBERS (during an erase) which all of them are u64 values that
> > cannot be
> > + * divided by usual kernel macros and must be handled with the
> > special
> > + * DIV_ROUND_UP_ULL() macro.
> > + */
> > +#define PSEC_TO_NSEC(x) DIV_ROUND_UP(x, 10^3)
> > +#define PSEC_TO_MSEC(x) DIV_ROUND_UP_ULL(x, 10^9)  
> 
> Hm, that's a bit of a mess to let each macro decide which converter
> they should use, because we don't know at this level whether the
> timing is stored in an u64 or u32. How about doing the conversion in
> the call-sites instead, because there you should know what you
> manipulate.
> 
> Something like
> 
> 	NAND_OP_CMD(FOO, PSEC_TO_NSEC_UL(sdr->tXX))
> 	NAND_OP_WAITRDY(PSEC_TO_MSEC_ULL(sdr->tXX),
> 			PSEC_TO_NSEC_UL(sdr->tYY))

That is a bit messy but okay. I have added to the core a "__DIVIDE"
macro that uses the size of the dividend to decide if the *_ULL version
must be used, so the drivers can safely use PSEC_TO_NSEC and
PSEC_TO_MSEC without knowing it the timings are stored in a u32 or a
u64.

> 
> 
> 
> > +
> > +#define NAND_OP_CMD(id,
> > delay_ps)					\
> > +
> > {								\
> > +		.type =
> > NAND_OP_CMD_INSTR,				\
> > +		.cmd.opcode =
> > id,					\
> > +		.delay_ns =
> > PSEC_TO_NSEC(delay_ps),			\
> > +	}
> > +
> > +#define NAND_OP_ADDR(ncycles, cycles,
> > delay_ps)				\
> > +
> > {								\
> > +		.type =
> > NAND_OP_ADDR_INSTR,				\
> > +		.addr =
> > {						\
> > +			.naddrs =
> > ncycles,				\
> > +			.addrs =
> > cycles,				\
> > +		},
> > \
> > +		.delay_ns =
> > PSEC_TO_NSEC(delay_ps),			\
> > +	}
> > +
> > +#define NAND_OP_DATA_IN(l, buf,
> > delay_ps)				\
> > +
> > {								\
> > +		.type =
> > NAND_OP_DATA_IN_INSTR,				\
> > +		.data =
> > {						\
> > +			.len =
> > l,					\
> > +			.in =
> > buf,					\
> > +			.force_8bit =
> > false,				\
> > +		},
> > \
> > +		.delay_ns =
> > PSEC_TO_NSEC(delay_ps),			\
> > +	}
> > +
> > +#define NAND_OP_DATA_OUT(l, buf,
> > delay_ps)				\
> > +
> > {								\
> > +		.type =
> > NAND_OP_DATA_OUT_INSTR,				\
> > +		.data =
> > {						\
> > +			.len =
> > l,					\
> > +			.out =
> > buf,					\
> > +			.force_8bit =
> > false,				\
> > +		},
> > \
> > +		.delay_ns =
> > PSEC_TO_NSEC(delay_ps),			\
> > +	}
> > +
> > +#define NAND_OP_8BIT_DATA_IN(l, buf,
> > delay_ps)				\
> > +
> > {								\
> > +		.type =
> > NAND_OP_DATA_IN_INSTR,				\
> > +		.data =
> > {						\
> > +			.len =
> > l,					\
> > +			.in =
> > buf,					\
> > +			.force_8bit =
> > true,				\
> > +		},
> > \
> > +		.delay_ns =
> > PSEC_TO_NSEC(delay_ps),			\
> > +	}
> > +
> > +#define NAND_OP_8BIT_DATA_OUT(l, buf,
> > delay_ps)				\
> > +
> > {								\
> > +		.type =
> > NAND_OP_DATA_OUT_INSTR,				\
> > +		.data =
> > {						\
> > +			.len =
> > l,					\
> > +			.out =
> > buf,					\
> > +			.force_8bit =
> > true,				\
> > +		},
> > \
> > +		.delay_ns =
> > PSEC_TO_NSEC(delay_ps),			\
> > +	}
> > +
> > +#define NAND_OP_WAIT_RDY(tout_ps,
> > delay_ps)				\
> > +
> > {								\
> > +		.type =
> > NAND_OP_WAITRDY_INSTR,				\
> > +		.waitrdy.timeout_ms =
> > PSEC_TO_MSEC(tout_ps),		\
> > +		.delay_ns =
> > PSEC_TO_NSEC(delay_ps),			\
> > +	}
> > +
> > +/**
> > + * struct nand_subop - a sub operation
> > + * @instrs: array of instructions
> > + * @ninstrs: length of the @instrs array
> > + * @first_instr_start_off: offset to start from for the first
> > instruction
> > + *			   of the sub-operation
> > + * @last_instr_end_off: offset to end at (excluded) for the last
> > instruction
> > + *			of the sub-operation
> > + *
> > + * When an operation cannot be handled as is by the NAND
> > controller, it will
> > + * be split by the parser and the remaining pieces will be handled
> > as
> > + * sub-operations.
> > + */
> > +struct nand_subop {
> > +	const struct nand_op_instr *instrs;
> > +	unsigned int ninstrs;
> > +	/*
> > +	 * Specific offset for the first and the last instructions
> > of the subop.
> > +	 * Applies for the address cycles in the case of address,
> > or for data
> > +	 * offset in the case of data transfers. Otherwise, it is
> > irrelevant.
> > +	 */  
> 
> Can you move that in the kernel header doc?

Sure.

> 
> > +	unsigned int first_instr_start_off;
> > +	unsigned int last_instr_end_off;
> > +};
> > +
> > +int nand_subop_get_addr_start_off(const struct nand_subop *subop,
> > +				  unsigned int op_id);
> > +int nand_subop_get_num_addr_cyc(const struct nand_subop *subop,
> > +				unsigned int op_id);
> > +int nand_subop_get_data_start_off(const struct nand_subop *subop,
> > +				  unsigned int op_id);
> > +int nand_subop_get_data_len(const struct nand_subop *subop,
> > +			    unsigned int op_id);
> > +
> > +/**
> > + * struct nand_op_parser_addr_constraints - Constraints for
> > address instructions
> > + * @maxcycles: maximum number of cycles that the controller can
> > assert by
> > + *	       instruction
> > + */
> > +struct nand_op_parser_addr_constraints {
> > +	unsigned int maxcycles;
> > +};
> > +
> > +/**
> > + * struct nand_op_parser_data_constraints - Constraints for data
> > instructions
> > + * @maxlen: maximum data length that the controller can handle
> > with one
> > + *	    instruction
> > + */
> > +struct nand_op_parser_data_constraints {
> > +	unsigned int maxlen;  
> 
> At some point we should probably add minlen and alignment fields, but
> let's keep that for later.
> 
> > +};
> > +
> > +/**
> > + * struct nand_op_parser_pattern_elem - One element of a pattern
> > + * @type: the instructuction type
> > + * @optional: if this element of the pattern is optional or
> > mandatory
> > + * @addr/@data: address or data constraint (number of cycles or
> > data length)
> > + */
> > +struct nand_op_parser_pattern_elem {
> > +	enum nand_op_instr_type type;
> > +	bool optional;
> > +	union {
> > +		struct nand_op_parser_addr_constraints addr;
> > +		struct nand_op_parser_data_constraints data;
> > +	};
> > +};
> > +
> > +#define NAND_OP_PARSER_PAT_CMD_ELEM(_opt)			\
> > +	{							\
> > +		.type = NAND_OP_CMD_INSTR,			\
> > +		.optional = _opt,				\
> > +	}
> > +
> > +#define NAND_OP_PARSER_PAT_ADDR_ELEM(_opt,
> > _maxcycles)		\
> > +	{							\
> > +		.type = NAND_OP_ADDR_INSTR,
> > \
> > +		.optional = _opt,				\
> > +		.addr.maxcycles =
> > _maxcycles,			\
> > +	}
> > +
> > +#define NAND_OP_PARSER_PAT_DATA_IN_ELEM(_opt,
> > _maxlen)		\
> > +	{							\
> > +		.type =
> > NAND_OP_DATA_IN_INSTR,			\
> > +		.optional = _opt,				\
> > +		.data.maxlen =
> > _maxlen,				\
> > +	}
> > +
> > +#define NAND_OP_PARSER_PAT_DATA_OUT_ELEM(_opt,
> > _maxlen)		\
> > +	{							\
> > +		.type =
> > NAND_OP_DATA_OUT_INSTR,			\
> > +		.optional = _opt,				\
> > +		.data.maxlen =
> > _maxlen,				\
> > +	}
> > +
> > +#define
> > NAND_OP_PARSER_PAT_WAITRDY_ELEM(_opt)			\
> > +	{							\
> > +		.type =
> > NAND_OP_WAITRDY_INSTR,			\
> > +		.optional = _opt,				\
> > +	}
> > +
> > +/**
> > + * struct nand_op_parser_pattern - A complete pattern
> > + * @elems: array of pattern elements
> > + * @nelems: number of pattern elements in @elems array
> > + * @exec: the function that will actually execute this pattern,
> > written in the
> > + *	  controller driver
> > + *
> > + * This is a complete pattern that is a list of elements, each one
> > reprensenting
> > + * one instruction with its constraints. Controller drivers must
> > declare as much
> > + * patterns as they support and give the list of the supported
> > patterns (created
> > + * with the help of the following macro) at the call to
> > nand_op_parser_exec_op  
> 
> s/at the call to nand_op_parser_exec_op/when calling
> nand_op_parser_exec_op()/

Ok.

> 
> > + * which shall be the main thing to do in the driver
> > implementation of
> > + * ->exec_op().  
> 
> I'd be more lax than that. Yes the parser is the preferred approach
> when you deal with a complex controller. But for simple controllers
> it's not.
> 
> > Once there is a match between the pattern and an operation, the
> > + * core calls the @exec function to actually do the operation.  
> 
> Not necessarily. The plan is still to ask the controller which
> operation it supports before actually executing them. So, in this case
> (when check_only param is true), nand_op_parser_exec_op() will never
> call ->exec(), it will just make sure the operation can be handled
> with the provided patterns.

I changed that §, see next version.

> 
> > + */
> > +struct nand_op_parser_pattern {
> > +	const struct nand_op_parser_pattern_elem *elems;
> > +	unsigned int nelems;
> > +	int (*exec)(struct nand_chip *chip, const struct
> > nand_subop *subop); +};
> > +
> > +#define
> > NAND_OP_PARSER_PATTERN(_exec, ...)
> > \
> > +
> > {
> > \
> > +		.exec =
> > _exec,
> > \
> > +		.elems = (struct nand_op_parser_pattern_elem[])
> > { __VA_ARGS__ },		\
> > +		.nelems = sizeof((struct
> > nand_op_parser_pattern_elem[]) { __VA_ARGS__ }) /	\
> > +			  sizeof(struct
> > nand_op_parser_pattern_elem),				\
> > +	}
> > +
> > +/**
> > + * struct nand_op_parser - The actual parser
> > + * @patterns: array of patterns
> > + * @npatterns: length of the @patterns array
> > + *
> > + * The actual parser structure wich is an array of supported
> > patterns.  
> 
> It's worth mentioning that patterns will be tested in their
> declaration order, and the first match will be taken, so it's
> important to oder patterns appropriately so that simple/inefficient
> patterns are placed at the end of the list. Usually, this is where
> you put single instruction patterns.

Nice explanation, added.

> 
> > + */
> > +struct nand_op_parser {
> > +	const struct nand_op_parser_pattern *patterns;
> > +	unsigned int npatterns;
> > +};
> > +
> > +#define
> > NAND_OP_PARSER(...)
> > \
> > +
> > {
> > \
> > +		.patterns = (struct nand_op_parser_pattern[])
> > { __VA_ARGS__ },			\
> > +		.npatterns = sizeof((struct
> > nand_op_parser_pattern[]) { __VA_ARGS__ }) /	\
> > +			     sizeof(struct
> > nand_op_parser_pattern),				\
> > +	}
> > +
> > +/**
> > + * struct nand_operation - The actual operation
> > + * @instrs: array of instructions to execute
> > + * @ninstrs: length of the @instrs array
> > + *
> > + * The actual operation structure that will be given to the
> > parser.  
> 
> Not only the parser, it will be passed to chip->exep_op() as well.

Yes.

> 
> > + */
> > +struct nand_operation {
> > +	const struct nand_op_instr *instrs;
> > +	unsigned int ninstrs;
> > +};
> > +
> > +#define
> > NAND_OPERATION(_instrs)					\
> > +	{							\
> > +		.instrs = _instrs,				\
> > +		.ninstrs =
> > ARRAY_SIZE(_instrs),			\
> > +	}
> > +
> > +int nand_fill_column_cycles(struct nand_chip *chip, u8 *addrs,
> > +			    unsigned int offset_in_page);
> > +
> > +int nand_op_parser_exec_op(struct nand_chip *chip,
> > +			   const struct nand_op_parser *parser,
> > +			   const struct nand_operation *op, bool
> > check_only); +
> > +/**
> >   * struct nand_chip - NAND Private Flash Chip Data
> >   * @mtd:		MTD device registered to the MTD framework
> >   * @IO_ADDR_R:		[BOARDSPECIFIC] address to read the
> > 8 I/O lines of the @@ -885,6 +1216,9 @@ struct nand_chip {
> >  	int (*setup_data_interface)(struct mtd_info *mtd, int
> > chipnr, const struct nand_data_interface *conf);
> >  
> > +	int (*exec_op)(struct nand_chip *chip,
> > +		       const struct nand_operation *op,
> > +		       bool check_only);
> >  
> >  	int chip_delay;
> >  	unsigned int options;
> > @@ -945,6 +1279,15 @@ struct nand_chip {
> >  	} manufacturer;
> >  };
> >  
> > +static inline int nand_exec_op(struct nand_chip *chip,
> > +			       const struct nand_operation *op)
> > +{
> > +	if (!chip->exec_op)
> > +		return -ENOTSUPP;
> > +
> > +	return chip->exec_op(chip, op, false);
> > +}
> > +
> >  extern const struct mtd_ooblayout_ops nand_ooblayout_sp_ops;
> >  extern const struct mtd_ooblayout_ops nand_ooblayout_lp_ops;
> >  
> > @@ -1307,23 +1650,26 @@ int nand_readid_op(struct nand_chip *chip,
> > u8 addr, void *buf, int nand_status_op(struct nand_chip *chip, u8
> > *status); int nand_erase_op(struct nand_chip *chip, unsigned int
> > eraseblock); int nand_read_page_op(struct nand_chip *chip, unsigned
> > int page,
> > -		      unsigned int column, void *buf, unsigned int
> > len); -int nand_change_read_column_op(struct nand_chip *chip,
> > unsigned int column,
> > -			       void *buf, unsigned int len, bool
> > force_8bit);
> > +		      unsigned int offset_in_page, void *buf,
> > unsigned int len); +int nand_change_read_column_op(struct nand_chip
> > *chip,
> > +			       unsigned int offset_in_page, void
> > *buf,
> > +			       unsigned int len, bool force_8bit);
> >  int nand_read_oob_op(struct nand_chip *chip, unsigned int page,
> > -		     unsigned int column, void *buf, unsigned int
> > len);
> > +		     unsigned int offset_in_page, void *buf,
> > unsigned int len); int nand_prog_page_begin_op(struct nand_chip
> > *chip, unsigned int page,
> > -			    unsigned int column, const void *buf,
> > +			    unsigned int offset_in_page, const
> > void *buf, unsigned int len);
> >  int nand_prog_page_end_op(struct nand_chip *chip);
> >  int nand_prog_page_op(struct nand_chip *chip, unsigned int page,
> > -		      unsigned int column, const void *buf,
> > unsigned int len); -int nand_change_write_column_op(struct
> > nand_chip *chip, unsigned int column,
> > -				const void *buf, unsigned int len,
> > bool force_8bit);
> > +		      unsigned int offset_in_page, const void *buf,
> > +		      unsigned int len);
> > +int nand_change_write_column_op(struct nand_chip *chip,
> > +				unsigned int offset_in_page, const
> > void *buf,
> > +				unsigned int len, bool force_8bit);
> >  int nand_read_data_op(struct nand_chip *chip, void *buf, unsigned
> > int len,
> > -		      bool force_8bits);
> > +		      bool force_8bit);
> >  int nand_write_data_op(struct nand_chip *chip, const void *buf,
> > -		       unsigned int len, bool force_8bits);
> > +		       unsigned int len, bool force_8bit);
> >  
> >  /* Free resources held by the NAND device */
> >  void nand_cleanup(struct nand_chip *chip);  
> 


Thanks for the detailed review,
Miquèl

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [RFC 02/12] mtd: nand: force drivers to explicitly send READ/PROG commands
  2017-10-20  9:29     ` Stefan Agner
                       ` (2 preceding siblings ...)
  (?)
@ 2017-11-06 15:02     ` Miquel RAYNAL
  -1 siblings, 0 replies; 75+ messages in thread
From: Miquel RAYNAL @ 2017-11-06 15:02 UTC (permalink / raw)
  To: Stefan Agner
  Cc: Boris Brezillon, Brian Norris, Cyrille Pitchen, David Woodhouse,
	Gregory Clement, linux-mtd, Marek Vasut, Richard Weinberger,
	Thomas Petazzoni, Antoine Tenart, Nadav Haklai, Ofer Heifetz,
	Neta Zur Hershkovits, Hanna Hawa

Hi Stefan,

> Thanks for the work on this, happy to see the new interface is moving
> forward. Some comments below.

Thank you!

> > diff --git a/drivers/mtd/nand/gpmi-nand/gpmi-nand.c
> > b/drivers/mtd/nand/gpmi-nand/gpmi-nand.c
> > index c8ceaecd8065..3f2b903158c1 100644
> > --- a/drivers/mtd/nand/gpmi-nand/gpmi-nand.c
> > +++ b/drivers/mtd/nand/gpmi-nand/gpmi-nand.c
> > @@ -1043,6 +1043,8 @@ static int gpmi_ecc_read_page(struct mtd_info
> > *mtd, struct nand_chip *chip,
> >  	unsigned int  max_bitflips = 0;
> >  	int           ret;
> >  
> > +	nand_read_page_op(chip, page, 0, NULL, 0);
> > +
> >  	dev_dbg(this->dev, "page number is : %d\n", page);
> >  	ret = read_page_prepare(this, buf, nfc_geo->payload_size,
> >  					this->payload_virt,
> > this->payload_phys, @@ -1220,12 +1222,13 @@ static int
> > gpmi_ecc_read_subpage(struct mtd_info *mtd, struct nand_chip *chip,
> >  	meta = geo->metadata_size;
> >  	if (first) {
> >  		col = meta + (size + ecc_parity_size) * first;
> > -		chip->cmdfunc(mtd, NAND_CMD_RNDOUT, col, -1);  
> 
> Shouldn't we add nand_change_read_column_op here?

The change column operation is done right after with
"nand_read_page_op()" that also set the page. I am pretty sure this
overall operation could be handled in a cleaner way though.

Could you test this driver with ->exec_op() implementation? If you do I
am interested to know if all goes well or not.

> 
> 
> >  
> >  		meta = 0;
> >  		buf = buf + first * size;
> >  	}
> >  
> > +	nand_read_page_op(chip, page, col, NULL, 0);
> > +
> >  	/* Save the old environment */
> >  	r1_old = r1_new = readl(bch_regs + HW_BCH_FLASH0LAYOUT0);
> >  	r2_old = r2_new = readl(bch_regs + HW_BCH_FLASH0LAYOUT1);

[...]

> 
> > diff --git a/drivers/mtd/nand/vf610_nfc.c
> > b/drivers/mtd/nand/vf610_nfc.c index 8037d4b48a05..80d31a58e558
> > 100644 --- a/drivers/mtd/nand/vf610_nfc.c
> > +++ b/drivers/mtd/nand/vf610_nfc.c
> > @@ -560,7 +560,7 @@ static int vf610_nfc_read_page(struct mtd_info
> > *mtd, struct nand_chip *chip,
> >  	int eccsize = chip->ecc.size;
> >  	int stat;
> >  
> > -	vf610_nfc_read_buf(mtd, buf, eccsize);
> > +	nand_read_page_op(chip, page, 0, buf, eccsize);
> >  	if (oob_required)
> >  		vf610_nfc_read_buf(mtd, chip->oob_poi,
> > mtd->oobsize); 
> > @@ -580,7 +580,7 @@ static int vf610_nfc_write_page(struct mtd_info
> > *mtd, struct nand_chip *chip,
> >  {
> >  	struct vf610_nfc *nfc = mtd_to_nfc(mtd);
> >  
> > -	vf610_nfc_write_buf(mtd, buf, mtd->writesize);  
> 
> We currently ignore NAND_CMD_SEQIN in ->cmdfunc anyway, so I think
> this change would not even be necessary here.
> 

Boris already answered this one, I will let it as is to remain
consistent but thanks for the remark.

> 
> This is a NAND controller which will benefit from the new interface. I
> plan to convert the driver to the new interface, maybe I manage to do
> that soon so we have a second driver making use of the new interface.

That would be great! I am writing the kernel-doc aside, I hope it will
be ready soon, but in any case do not hesitate if you need help.

Thank you,
Miquèl

^ permalink raw reply	[flat|nested] 75+ messages in thread

end of thread, other threads:[~2017-11-06 15:02 UTC | newest]

Thread overview: 75+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2017-10-18 14:36 [RFC 00/12] Marvell NAND controller rework with ->exec_op() Miquel Raynal
2017-10-18 14:36 ` Miquel Raynal
2017-10-18 14:36 ` Miquel Raynal
2017-10-18 14:36 ` [RFC 01/12] mtd: nand: provide several helpers to do common NAND operations Miquel Raynal
2017-10-18 14:36   ` Miquel Raynal
2017-10-18 14:36   ` Miquel Raynal
2017-10-18 14:36 ` [RFC 02/12] mtd: nand: force drivers to explicitly send READ/PROG commands Miquel Raynal
2017-10-18 14:36   ` Miquel Raynal
2017-10-18 14:36   ` Miquel Raynal
2017-10-20  9:29   ` Stefan Agner
2017-10-20  9:29     ` Stefan Agner
2017-10-20  9:29     ` Stefan Agner
2017-10-20 11:18     ` Boris Brezillon
2017-10-20 11:18       ` Boris Brezillon
2017-10-20 11:18       ` Boris Brezillon
2017-11-06 15:02     ` Miquel RAYNAL
2017-10-18 14:36 ` [RFC 03/12] mtd: nand: use a static data_interface in the nand_chip structure Miquel Raynal
2017-10-18 14:36   ` Miquel Raynal
2017-10-18 14:36   ` Miquel Raynal
2017-10-18 17:02   ` Boris Brezillon
2017-10-18 17:02     ` Boris Brezillon
2017-10-18 17:02     ` Boris Brezillon
2017-11-03 13:46     ` Miquel RAYNAL
2017-10-18 14:36 ` [RFC 04/12] mtd: nand: add ->exec_op() implementation Miquel Raynal
2017-10-18 14:36   ` Miquel Raynal
2017-10-18 14:36   ` Miquel Raynal
2017-10-18 21:57   ` Boris Brezillon
2017-10-18 21:57     ` Boris Brezillon
2017-10-18 21:57     ` Boris Brezillon
2017-11-06 14:09     ` Miquel RAYNAL
2017-10-18 14:36 ` [RFC 05/12] dt-bindings: mtd: add Marvell NAND controller documentation Miquel Raynal
2017-10-18 14:36   ` Miquel Raynal
2017-10-18 14:36   ` Miquel Raynal
2017-10-18 22:01   ` Boris Brezillon
2017-10-18 22:01     ` Boris Brezillon
2017-10-18 22:01     ` Boris Brezillon
2017-10-24 19:04   ` Rob Herring
2017-10-24 19:04     ` Rob Herring
2017-10-24 19:04     ` Rob Herring
2017-11-06 13:24     ` Miquel RAYNAL
2017-11-06 13:24       ` Miquel RAYNAL
2017-11-06 13:24       ` Miquel RAYNAL
2017-10-18 14:36 ` [RFC 06/12] mtd: nand: add reworked Marvell NAND controller driver Miquel Raynal
2017-10-18 14:36   ` Miquel Raynal
2017-10-18 14:36   ` Miquel Raynal
2017-10-19  7:18   ` Boris Brezillon
2017-10-19  7:18     ` Boris Brezillon
2017-10-19  7:18     ` Boris Brezillon
2017-11-06 13:49     ` Miquel RAYNAL
2017-10-18 14:36 ` [RFC 07/12] ARM: dts: armada-370-xp: use reworked " Miquel Raynal
2017-10-18 14:36   ` Miquel Raynal
2017-10-18 14:36   ` Miquel Raynal
2017-10-18 14:36 ` [RFC 08/12] ARM: dts: armada-375: " Miquel Raynal
2017-10-18 14:36   ` Miquel Raynal
2017-10-18 14:36   ` Miquel Raynal
2017-10-18 14:36 ` [RFC 09/12] ARM: dts: armada-38x: " Miquel Raynal
2017-10-18 14:36   ` Miquel Raynal
2017-10-18 14:36   ` Miquel Raynal
2017-10-18 14:36 ` [RFC 10/12] ARM: dts: armada-39x: " Miquel Raynal
2017-10-18 14:36   ` Miquel Raynal
2017-10-18 14:36   ` Miquel Raynal
2017-10-18 14:36 ` [RFC 11/12] ARM: dts: pxa: " Miquel Raynal
2017-10-18 14:36   ` Miquel Raynal
2017-10-18 14:36   ` Miquel Raynal
2017-10-18 14:36 ` [RFC 12/12] ARM64: dts: marvell: use reworked NAND controller driver on Armada 7K/8K Miquel Raynal
2017-10-18 14:36 ` Miquel Raynal
2017-10-18 14:36   ` Miquel Raynal
2017-10-18 14:36   ` Miquel Raynal
2017-10-18 14:36 ` Miquel Raynal
     [not found] ` <20171018143629.29302-1-miquel.raynal-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8@public.gmane.org>
2017-10-18 14:36   ` Miquel Raynal
2017-10-18 22:29 ` [RFC 00/12] Marvell NAND controller rework with ->exec_op() Boris Brezillon
2017-10-18 22:29   ` Boris Brezillon
2017-10-18 22:29   ` Boris Brezillon
2017-10-19  8:47   ` Marc Gonzalez
2017-10-19 11:30     ` Boris Brezillon

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.