diff mbox series

[RFC] mtd: nand: add support for ts72xx

Message ID 20230927141532.25525-1-nikita.shubin@maquefel.me
State New
Headers show
Series [RFC] mtd: nand: add support for ts72xx | expand

Commit Message

Nikita Shubin Sept. 27, 2023, 2:15 p.m. UTC
Technologic Systems has it's own nand controller implementation in CPLD.

Signed-off-by: Nikita Shubin <nikita.shubin@maquefel.me>
---
Hello Miquèl.

Can you please take a look on it as RFC, so no need for the whole series spinning ?

I've got rid of all legacy stuff i think, however:

- look's like i me ts7250 is missing READCACHE and i couldn't find any docs 
  for this controller, so as legacy had no READCACHE, i mark it as non-supported
- legacy wait implementation had no delays - only timeout and if i set 
  readb_poll_timeout() to something reasonable i experience speed degradation 
  on mtd_speedtest, can it be left as 0 ?

---
 drivers/mtd/nand/raw/Kconfig                  |   7 +
 drivers/mtd/nand/raw/Makefile                 |   1 +
 .../nand/raw/technologic-nand-controller.c    | 233 ++++++++++++++++++
 3 files changed, 241 insertions(+)
 create mode 100644 drivers/mtd/nand/raw/technologic-nand-controller.c

Comments

Miquel Raynal Sept. 27, 2023, 3:29 p.m. UTC | #1
Hi Nikita,

nikita.shubin@maquefel.me wrote on Wed, 27 Sep 2023 17:15:25 +0300:

> Technologic Systems has it's own nand controller implementation in CPLD.
> 
> Signed-off-by: Nikita Shubin <nikita.shubin@maquefel.me>
> ---
> Hello Miquèl.
> 
> Can you please take a look on it as RFC, so no need for the whole series spinning ?
> 
> I've got rid of all legacy stuff i think, however:

I am glad you did, thanks a lot!

> - look's like i me ts7250 is missing READCACHE and i couldn't find any docs 
>   for this controller, so as legacy had no READCACHE, i mark it as non-supported

What NAND device do you use?

The controller clearly supports it, as it just forwards whatever
addrs/cmd/data cycle you input. However we are trying to fix the cache
reads support which is unstable. If you use a NAND that is non-JEDEC
and non-ONFI then please apply this and you will no longer be bothered
by it:
https://lore.kernel.org/linux-mtd/20230926132725.5d570e1b@xps-13/T/#md7e5e944a6a08e24f4f1e20068a49f94794ab945

If you are using a Micron chip that specifies not supporting ECC
correction together with sequential cache reads, then it's a problem
that we will solve soon (you can hardcode
chip->controller->supported_op.cont_read to 0);

Otherwise we can discuss it.

> - legacy wait implementation had no delays - only timeout and if i set 
>   readb_poll_timeout() to something reasonable i experience speed degradation 
>   on mtd_speedtest, can it be left as 0 ?

Looks like the legacy implementation used cond_resched(). The delay
needs to be observed before first checking for the status, it's a delay
that is described in the spec, if you get the status before you might
just not see a consistent value. I believe that is worth taking into
account in your implementation below (don't wait then delay, it's
not relevant). Can you share the values you've tried and the
performances you've got?

Thanks,
Miquèl
Nikita Shubin Sept. 30, 2023, 4:06 p.m. UTC | #2
Hello Miquèl!

On Wed, 2023-09-27 at 17:29 +0200, Miquel Raynal wrote:
> Hi Nikita,
> 
> nikita.shubin@maquefel.me wrote on Wed, 27 Sep 2023 17:15:25 +0300:
> 
> > Technologic Systems has it's own nand controller implementation in
> > CPLD.
> > 
> > Signed-off-by: Nikita Shubin <nikita.shubin@maquefel.me>
> > ---
> > Hello Miquèl.
> > 
> > Can you please take a look on it as RFC, so no need for the whole
> > series spinning ?
> > 
> > I've got rid of all legacy stuff i think, however:
> 
> I am glad you did, thanks a lot!
> 
> > - look's like i me ts7250 is missing READCACHE and i couldn't find
> > any docs 
> >   for this controller, so as legacy had no READCACHE, i mark it as
> > non-supported
> 
> What NAND device do you use?
> 
> The controller clearly supports it, as it just forwards whatever
> addrs/cmd/data cycle you input. However we are trying to fix the
> cache
> reads support which is unstable. If you use a NAND that is non-JEDEC
> and non-ONFI then please apply this and you will no longer be
> bothered
> by it:
> https://lore.kernel.org/linux-mtd/20230926132725.5d570e1b@xps-13/T/#md7e5e944a6a08e24f4f1e20068a49f94794ab945
> 

You are totally right - controller and chip are different entities, i
somehow forgot about it:

```
# modprobe technologic-nand-controller
nand: device found, Manufacturer ID: 0xec, Chip ID: 0xf1
nand: Samsung NAND 128MiB 3,3V 8-bit
nand: 128 MiB, SLC, erase size: 128 KiB, page size: 2048, OOB size: 64
Scanning device for bad blocks
Bad eraseblock 137 at 0x000001120000
Bad eraseblock 310 at 0x0000026c0000
3 fixed-partitions partitions found on MTD device 60000000.nand-
controller
Creating 3 MTD partitions on "60000000.nand-controller":
0x000000000000-0x000000020000 : "TS-BOOTROM"
0x000000020000-0x000007d20000 : "Linux"
0x000007d20000-0x000008000000 : "RedBoot"
```

This looks like Samsung K9F1G08U0D or K9F1G08U0B.

And the patch above totally solves my issues with READCACHE - thank
you!

> If you are using a Micron chip that specifies not supporting ECC
> correction together with sequential cache reads, then it's a problem
> that we will solve soon (you can hardcode
> chip->controller->supported_op.cont_read to 0);
> 
> Otherwise we can discuss it.
> 
> > - legacy wait implementation had no delays - only timeout and if i
> > set 
> >   readb_poll_timeout() to something reasonable i experience speed
> > degradation 
> >   on mtd_speedtest, can it be left as 0 ?
> 
> Looks like the legacy implementation used cond_resched(). The delay
> needs to be observed before first checking for the status, it's a
> delay
> that is described in the spec, if you get the status before you might
> just not see a consistent value. I believe that is worth taking into
> account in your implementation below (don't wait then delay, it's
> not relevant). Can you share the values you've tried and the
> performances you've got?

The numbers are pretty stable, so indeed legacy is a bit faster,
followed up by new one with zero interval.

Legacy speedtest (old version of nand controller):

```
# insmod mtd_speedtest.ko dev=1

=================================================
mtd_speedtest: MTD device: 1
mtd_speedtest: MTD device size 131072000, eraseblock size 131072, page
size 2048, count of eraseblocks 1000, pages per eraseblock 64, OOB size
64
mtd_test: scanning for bad eraseblocks
mtd_test: block 136 is bad
mtd_test: block 309 is bad
mtd_test: scanned 1000 eraseblocks, 2 are bad
mtd_speedtest: testing eraseblock write speed
mtd_speedtest: eraseblock write speed is 3793 KiB/s
mtd_speedtest: testing eraseblock read speed
mtd_speedtest: eraseblock read speed is 3567 KiB/s
mtd_speedtest: testing page write speed
mtd_speedtest: page write speed is 3682 KiB/s
mtd_speedtest: testing page read speed
mtd_speedtest: page read speed is 3488 KiB/s
mtd_speedtest: testing 2 page write speed
mtd_speedtest: 2 page write speed is 3724 KiB/s
mtd_speedtest: testing 2 page read speed
mtd_speedtest: 2 page read speed is 3521 KiB/s
mtd_speedtest: Testing erase speed
mtd_speedtest: erase speed is 198709 KiB/s
mtd_speedtest: Testing 2x multi-block erase speed
mtd_speedtest: 2x multi-block erase speed is 200638 KiB/s
mtd_speedtest: Testing 4x multi-block erase speed
mtd_speedtest: 4x multi-block erase speed is 201545 KiB/s
mtd_speedtest: Testing 8x multi-block erase speed
mtd_speedtest: 8x multi-block erase speed is 202341 KiB/s
mtd_speedtest: Testing 16x multi-block erase speed
mtd_speedtest: 16x multi-block erase speed is 202909 KiB/s
mtd_speedtest: Testing 32x multi-block erase speed
mtd_speedtest: 32x multi-block erase speed is 203095 KiB/s
mtd_speedtest: Testing 64x multi-block erase speed
mtd_speedtest: 64x multi-block erase speed is 203482 KiB/s
mtd_speedtest: finished
=================================================
```

New version with interval zero:

```
# insmod mtd_speedtest.ko dev=1

=================================================
mtd_speedtest: MTD device: 1
mtd_speedtest: MTD device size 131072000, eraseblock size 131072, page
size 2048, count of eraseblocks 1000, pages per eraseblock 64, OOB size
64
mtd_test: scanning for bad eraseblocks
mtd_test: block 136 is bad
mtd_test: block 309 is bad
mtd_test: scanned 1000 eraseblocks, 2 are bad
mtd_speedtest: testing eraseblock write speed
mtd_speedtest: eraseblock write speed is 3685 KiB/s
mtd_speedtest: testing eraseblock read speed
mtd_speedtest: eraseblock read speed is 3517 KiB/s
mtd_speedtest: testing page write speed
mtd_speedtest: page write speed is 3592 KiB/s
mtd_speedtest: testing page read speed
mtd_speedtest: page read speed is 3444 KiB/s
mtd_speedtest: testing 2 page write speed
mtd_speedtest: 2 page write speed is 3608 KiB/s
mtd_speedtest: testing 2 page read speed
mtd_speedtest: 2 page read speed is 3475 KiB/s
mtd_speedtest: Testing erase speed
mtd_speedtest: erase speed is 194499 KiB/s
mtd_speedtest: Testing 2x multi-block erase speed
mtd_speedtest: 2x multi-block erase speed is 196316 KiB/s
mtd_speedtest: Testing 4x multi-block erase speed
mtd_speedtest: 4x multi-block erase speed is 198305 KiB/s
mtd_speedtest: Testing 8x multi-block erase speed
mtd_speedtest: 8x multi-block erase speed is 199263 KiB/s
mtd_speedtest: Testing 16x multi-block erase speed
mtd_speedtest: 16x multi-block erase speed is 199548 KiB/s
mtd_speedtest: Testing 32x multi-block erase speed
mtd_speedtest: 32x multi-block erase speed is 200221 KiB/s
mtd_speedtest: Testing 64x multi-block erase speed
mtd_speedtest: 64x multi-block erase speed is 200579 KiB/s
mtd_speedtest: finished
=================================================
```

New version with interval 100:

```
# insmod mtd_speedtest.ko dev=1

=================================================
mtd_speedtest: MTD device: 1
mtd_speedtest: MTD device size 131072000, eraseblock size 131072, page
size 2048, count of eraseblocks 1000, pages per eraseblock 64, OOB size
64
mtd_test: scanning for bad eraseblocks
mtd_test: block 136 is bad
mtd_test: block 309 is bad
mtd_test: scanned 1000 eraseblocks, 2 are bad
mtd_speedtest: testing eraseblock write speed
mtd_speedtest: eraseblock write speed is 2722 KiB/s
mtd_speedtest: testing eraseblock read speed
mtd_speedtest: eraseblock read speed is 2175 KiB/s
mtd_speedtest: testing page write speed
mtd_speedtest: page write speed is 2598 KiB/s
mtd_speedtest: testing page read speed
mtd_speedtest: page read speed is 2070 KiB/s
mtd_speedtest: testing 2 page write speed
mtd_speedtest: 2 page write speed is 2627 KiB/s
mtd_speedtest: testing 2 page read speed
mtd_speedtest: 2 page read speed is 2106 KiB/s
mtd_speedtest: Testing erase speed
mtd_speedtest: erase speed is 175851 KiB/s
mtd_speedtest: Testing 2x multi-block erase speed
mtd_speedtest: 2x multi-block erase speed is 181582 KiB/s
mtd_speedtest: Testing 4x multi-block erase speed
mtd_speedtest: 4x multi-block erase speed is 181579 KiB/s
mtd_speedtest: Testing 8x multi-block erase speed
mtd_speedtest: 8x multi-block erase speed is 182735 KiB/s
mtd_speedtest: Testing 16x multi-block erase speed
mtd_speedtest: 16x multi-block erase speed is 183589 KiB/s
mtd_speedtest: Testing 32x multi-block erase speed
mtd_speedtest: 32x multi-block erase speed is 183003 KiB/s
mtd_speedtest: Testing 64x multi-block erase speed
mtd_speedtest: 64x multi-block erase speed is 183460 KiB/s
mtd_speedtest: finished
=================================================
```

Also providing version with zero interval and "if (instr->delay_ns)"
dropped - it's optional as far i understood:

```
# insmod mtd_speedtest.ko dev=1

=================================================
mtd_speedtest: MTD device: 1
mtd_speedtest: MTD device size 131072000, eraseblock size 131072, page
size 2048, count of eraseblocks 1000, pages per eraseblock 64, OOB size
64
mtd_test: scanning for bad eraseblocks
mtd_test: block 136 is bad
mtd_test: block 309 is bad
mtd_test: scanned 1000 eraseblocks, 2 are bad
mtd_speedtest: testing eraseblock write speed
mtd_speedtest: eraseblock write speed is 3695 KiB/s
mtd_speedtest: testing eraseblock read speed
mtd_speedtest: eraseblock read speed is 3532 KiB/s
mtd_speedtest: testing page write speed
mtd_speedtest: page write speed is 3593 KiB/s
mtd_speedtest: testing page read speed
mtd_speedtest: page read speed is 3457 KiB/s
mtd_speedtest: testing 2 page write speed
mtd_speedtest: 2 page write speed is 3640 KiB/s
mtd_speedtest: testing 2 page read speed
mtd_speedtest: 2 page read speed is 3488 KiB/s
mtd_speedtest: Testing erase speed
mtd_speedtest: erase speed is 195451 KiB/s
mtd_speedtest: Testing 2x multi-block erase speed
mtd_speedtest: 2x multi-block erase speed is 198538 KiB/s
mtd_speedtest: Testing 4x multi-block erase speed
mtd_speedtest: 4x multi-block erase speed is 199509 KiB/s
mtd_speedtest: Testing 8x multi-block erase speed
mtd_speedtest: 8x multi-block erase speed is 199871 KiB/s
mtd_speedtest: Testing 16x multi-block erase speed
mtd_speedtest: 16x multi-block erase speed is 200075 KiB/s
mtd_speedtest: Testing 32x multi-block erase speed
mtd_speedtest: 32x multi-block erase speed is 200683 KiB/s
mtd_speedtest: Testing 64x multi-block erase speed
mtd_speedtest: 64x multi-block erase speed is 200850 KiB/s
mtd_speedtest: finished
=================================================

```


> 
> Thanks,
> Miquèl
>
Miquel Raynal Oct. 2, 2023, 9:30 a.m. UTC | #3
Hi Nikita,

> ```
> # modprobe technologic-nand-controller
> nand: device found, Manufacturer ID: 0xec, Chip ID: 0xf1
> nand: Samsung NAND 128MiB 3,3V 8-bit
> nand: 128 MiB, SLC, erase size: 128 KiB, page size: 2048, OOB size: 64
> Scanning device for bad blocks
> Bad eraseblock 137 at 0x000001120000
> Bad eraseblock 310 at 0x0000026c0000
> 3 fixed-partitions partitions found on MTD device 60000000.nand-
> controller
> Creating 3 MTD partitions on "60000000.nand-controller":
> 0x000000000000-0x000000020000 : "TS-BOOTROM"
> 0x000000020000-0x000007d20000 : "Linux"
> 0x000007d20000-0x000008000000 : "RedBoot"
> ```
> 
> This looks like Samsung K9F1G08U0D or K9F1G08U0B.
> 
> And the patch above totally solves my issues with READCACHE - thank
> you!
> 

Great!

> > If you are using a Micron chip that specifies not supporting ECC
> > correction together with sequential cache reads, then it's a problem
> > that we will solve soon (you can hardcode
> > chip->controller->supported_op.cont_read to 0);
> > 
> > Otherwise we can discuss it.
> >   
> > > - legacy wait implementation had no delays - only timeout and if i
> > > set 
> > >   readb_poll_timeout() to something reasonable i experience speed
> > > degradation 
> > >   on mtd_speedtest, can it be left as 0 ?  
> > 
> > Looks like the legacy implementation used cond_resched(). The delay
> > needs to be observed before first checking for the status, it's a
> > delay
> > that is described in the spec, if you get the status before you might
> > just not see a consistent value. I believe that is worth taking into
> > account in your implementation below (don't wait then delay, it's
> > not relevant). Can you share the values you've tried and the
> > performances you've got?  
> 
> The numbers are pretty stable, so indeed legacy is a bit faster,
> followed up by new one with zero interval.

...

> Legacy speedtest (old version of nand controller):

> mtd_speedtest: eraseblock write speed is 3793 KiB/s
> mtd_speedtest: eraseblock read speed is 3567 KiB/s
> mtd_speedtest: page write speed is 3682 KiB/s
> mtd_speedtest: page read speed is 3488 KiB/s
> mtd_speedtest: 2 page write speed is 3724 KiB/s
> mtd_speedtest: 2 page read speed is 3521 KiB/s
> mtd_speedtest: erase speed is 198709 KiB/s

...
 
> New version with interval zero:

> mtd_speedtest: eraseblock write speed is 3685 KiB/s
> mtd_speedtest: eraseblock read speed is 3517 KiB/s
> mtd_speedtest: page write speed is 3592 KiB/s
> mtd_speedtest: page read speed is 3444 KiB/s
> mtd_speedtest: 2 page write speed is 3608 KiB/s
> mtd_speedtest: 2 page read speed is 3475 KiB/s
> mtd_speedtest: erase speed is 194499 KiB/s

Looks almost the same as above, I believe the difference is just noise
between measurements.

...

> New version with interval 100:

> mtd_speedtest: eraseblock write speed is 2722 KiB/s
> mtd_speedtest: eraseblock read speed is 2175 KiB/s
> mtd_speedtest: page write speed is 2598 KiB/s
> mtd_speedtest: page read speed is 2070 KiB/s
> mtd_speedtest: 2 page write speed is 2627 KiB/s
> mtd_speedtest: 2 page read speed is 2106 KiB/s
> mtd_speedtest: erase speed is 175851 KiB/s

...

> Also providing version with zero interval and "if (instr->delay_ns)"
> dropped - it's optional as far i understood:

It's not optional, sorry for the wrong comment, please keep it as it
is, knowing that it will be null after a wait_rdy.

> mtd_speedtest: eraseblock write speed is 3695 KiB/s
> mtd_speedtest: eraseblock read speed is 3532 KiB/s
> mtd_speedtest: page write speed is 3593 KiB/s
> mtd_speedtest: page read speed is 3457 KiB/s
> mtd_speedtest: 2 page write speed is 3640 KiB/s
> mtd_speedtest: 2 page read speed is 3488 KiB/s
> mtd_speedtest: erase speed is 195451 KiB/s

Thanks,
Miquèl
diff mbox series

Patch

diff --git a/drivers/mtd/nand/raw/Kconfig b/drivers/mtd/nand/raw/Kconfig
index cbf8ae85e1ae..3937c10dea1c 100644
--- a/drivers/mtd/nand/raw/Kconfig
+++ b/drivers/mtd/nand/raw/Kconfig
@@ -449,6 +449,13 @@  config MTD_NAND_RENESAS
 	  Enables support for the NAND controller found on Renesas R-Car
 	  Gen3 and RZ/N1 SoC families.
 
+config MTD_NAND_TS72XX
+	tristate "ts72xx NAND controller"
+	depends on ARCH_EP93XX && HAS_IOMEM
+	help
+	  Enables support for NAND controller on ts72xx SBCs.
+	  This is a legacy driver based on gen_nand.
+
 comment "Misc"
 
 config MTD_SM_COMMON
diff --git a/drivers/mtd/nand/raw/Makefile b/drivers/mtd/nand/raw/Makefile
index 25120a4afada..44d8f5b7cfd2 100644
--- a/drivers/mtd/nand/raw/Makefile
+++ b/drivers/mtd/nand/raw/Makefile
@@ -23,6 +23,7 @@  omap2_nand-objs := omap2.o
 obj-$(CONFIG_MTD_NAND_OMAP2) 		+= omap2_nand.o
 obj-$(CONFIG_MTD_NAND_OMAP_BCH_BUILD)	+= omap_elm.o
 obj-$(CONFIG_MTD_NAND_MARVELL)		+= marvell_nand.o
+obj-$(CONFIG_MTD_NAND_TS72XX)		+= technologic-nand-controller.o
 obj-$(CONFIG_MTD_NAND_PLATFORM)		+= plat_nand.o
 obj-$(CONFIG_MTD_NAND_PASEMI)		+= pasemi_nand.o
 obj-$(CONFIG_MTD_NAND_ORION)		+= orion_nand.o
diff --git a/drivers/mtd/nand/raw/technologic-nand-controller.c b/drivers/mtd/nand/raw/technologic-nand-controller.c
new file mode 100644
index 000000000000..4751955f26ee
--- /dev/null
+++ b/drivers/mtd/nand/raw/technologic-nand-controller.c
@@ -0,0 +1,233 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Technologic Systems TS72xx NAND controller driver
+ *
+ * Copyright (C) 2023 Nikita Shubin <nikita.shubin@maquefel.me>
+ *
+ * Derived from: plat_nand.c
+ *  Author: Vitaly Wool <vitalywool@gmail.com>
+ */
+
+#include <linux/bits.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/platnand.h>
+
+#define TS72XX_NAND_CONTROL_ADDR_LINE	BIT(22)	/* 0xN0400000 */
+#define TS72XX_NAND_BUSY_ADDR_LINE	BIT(23)	/* 0xN0800000 */
+
+#define TS72XX_NAND_ALE			BIT(0)
+#define TS72XX_NAND_CLE			BIT(1)
+#define TS72XX_NAND_NCE			BIT(2)
+
+#define TS72XX_NAND_CTRL_CLE		(TS72XX_NAND_NCE | TS72XX_NAND_CLE)
+#define TS72XX_NAND_CTRL_ALE		(TS72XX_NAND_NCE | TS72XX_NAND_ALE)
+
+struct ts72xx_nand_data {
+	struct nand_controller	controller;
+	struct nand_chip	chip;
+	void __iomem		*base;
+	void __iomem		*ctrl;
+	void __iomem		*busy;
+};
+
+static inline struct ts72xx_nand_data *chip_to_ts72xx(struct nand_chip *chip)
+{
+	return container_of(chip, struct ts72xx_nand_data, chip);
+}
+
+static int ts72xx_nand_attach_chip(struct nand_chip *chip)
+{
+	switch (chip->ecc.engine_type) {
+	case NAND_ECC_ENGINE_TYPE_SOFT:
+		if (chip->ecc.algo == NAND_ECC_ALGO_UNKNOWN)
+			chip->ecc.algo = NAND_ECC_ALGO_HAMMING;
+		chip->ecc.algo = NAND_ECC_ALGO_HAMMING;
+		break;
+	case NAND_ECC_ENGINE_TYPE_ON_HOST:
+		return -EINVAL;
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+static void ts72xx_nand_ctrl(struct nand_chip *chip, u8 value)
+{
+	struct ts72xx_nand_data *data = chip_to_ts72xx(chip);
+	unsigned char bits = ioread8(data->ctrl) & ~GENMASK(2, 0);
+
+	iowrite8(bits | value, data->ctrl);
+}
+
+static int ts72xx_nand_exec_instr(struct nand_chip *chip,
+				const struct nand_op_instr *instr)
+{
+	struct ts72xx_nand_data *data = chip_to_ts72xx(chip);
+	unsigned int i, timeout_us;
+	u32 status;
+	int ret;
+
+	switch (instr->type) {
+	case NAND_OP_CMD_INSTR:
+		ts72xx_nand_ctrl(chip, TS72XX_NAND_CTRL_CLE);
+		iowrite8(instr->ctx.cmd.opcode, data->base);
+		ts72xx_nand_ctrl(chip, TS72XX_NAND_NCE);
+		break;
+
+	case NAND_OP_ADDR_INSTR:
+		ts72xx_nand_ctrl(chip, TS72XX_NAND_CTRL_ALE);
+		for (i = 0; i < instr->ctx.addr.naddrs; i++)
+			iowrite8(instr->ctx.addr.addrs[i], data->base);
+		ts72xx_nand_ctrl(chip, TS72XX_NAND_NCE);
+		break;
+
+	case NAND_OP_DATA_IN_INSTR:
+		ioread8_rep(data->base, instr->ctx.data.buf.in, instr->ctx.data.len);
+		break;
+
+	case NAND_OP_DATA_OUT_INSTR:
+		iowrite8_rep(data->base, instr->ctx.data.buf.in, instr->ctx.data.len);
+		break;
+
+	case NAND_OP_WAITRDY_INSTR:
+		timeout_us = instr->ctx.waitrdy.timeout_ms * 1000;
+		ret = readb_poll_timeout(data->busy, status, status & BIT(5), 0, timeout_us);
+		if (ret)
+			return ret;
+
+		break;
+	}
+
+	if (instr->delay_ns)
+		ndelay(instr->delay_ns);
+
+	return 0;
+}
+
+static int ts72xx_nand_exec_op(struct nand_chip *chip,
+			       const struct nand_operation *op, bool check_only)
+{
+	const struct nand_op_instr *instr = NULL;
+	unsigned int i, op_id;
+	int ret;
+
+	if (check_only) {
+		for (op_id = 0; op_id < op->ninstrs; op_id++) {
+			instr = &op->instrs[op_id];
+			if (instr->type == NAND_OP_CMD_INSTR &&
+			    (instr->ctx.cmd.opcode == NAND_CMD_READCACHEEND ||
+			     instr->ctx.cmd.opcode == NAND_CMD_READCACHESEQ))
+				return -EOPNOTSUPP;
+		}
+
+		return 0;
+	}
+
+	for (i = 0; i < op->ninstrs; i++) {
+		ret = ts72xx_nand_exec_instr(chip, &op->instrs[i]);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+static const struct nand_controller_ops ts72xx_nand_ops = {
+	.attach_chip = ts72xx_nand_attach_chip,
+	.exec_op = ts72xx_nand_exec_op,
+};
+
+static int ts72xx_nand_probe(struct platform_device *pdev)
+{
+	struct ts72xx_nand_data *data;
+	struct fwnode_handle *child;
+	struct mtd_info *mtd;
+	int err;
+
+	data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+
+	nand_controller_init(&data->controller);
+	data->controller.ops = &ts72xx_nand_ops;
+	data->chip.controller = &data->controller;
+
+	data->base = devm_platform_ioremap_resource(pdev, 0);
+	if (IS_ERR(data->base))
+		return PTR_ERR(data->base);
+	data->ctrl = data->base + TS72XX_NAND_CONTROL_ADDR_LINE;
+	data->busy = data->base + TS72XX_NAND_BUSY_ADDR_LINE;
+
+	child = fwnode_get_next_child_node(dev_fwnode(&pdev->dev), NULL);
+	if (!child)
+		return dev_err_probe(&pdev->dev, -ENXIO,
+				"ts72xx controller node should have exactly one child\n");
+
+	nand_set_flash_node(&data->chip, to_of_node(child));
+	mtd = nand_to_mtd(&data->chip);
+	mtd->dev.parent = &pdev->dev;
+	platform_set_drvdata(pdev, data);
+
+	/*
+	 * This driver assumes that the default ECC engine should be TYPE_SOFT.
+	 * Set ->engine_type before registering the NAND devices in order to
+	 * provide a driver specific default value.
+	 */
+	data->chip.ecc.engine_type = NAND_ECC_ENGINE_TYPE_SOFT;
+
+	/* Scan to find existence of the device */
+	err = nand_scan(&data->chip, 1);
+	if (err)
+		goto err_handle_put;
+
+	err = mtd_device_parse_register(mtd, NULL, NULL, NULL, 0);
+	if (err)
+		goto err_clean_nand;
+
+	return 0;
+
+err_clean_nand:
+	nand_cleanup(&data->chip);
+err_handle_put:
+	fwnode_handle_put(child);
+	return err;
+}
+
+static void ts72xx_nand_remove(struct platform_device *pdev)
+{
+	struct ts72xx_nand_data *data = platform_get_drvdata(pdev);
+	struct nand_chip *chip = &data->chip;
+	int ret;
+
+	ret = mtd_device_unregister(nand_to_mtd(chip));
+	WARN_ON(ret);
+	nand_cleanup(chip);
+}
+
+static const struct of_device_id ts72xx_id_table[] = {
+	{ .compatible = "technologic,ts7200-nand" },
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, ts72xx_id_table);
+
+static struct platform_driver ts72xx_nand_driver = {
+	.driver = {
+		.name = "ts72xx-nand",
+		.of_match_table = ts72xx_id_table,
+	},
+	.probe = ts72xx_nand_probe,
+	.remove_new = ts72xx_nand_remove,
+};
+module_platform_driver(ts72xx_nand_driver);
+
+MODULE_AUTHOR("Nikita Shubin <nikita.shubin@maquefel.me");
+MODULE_DESCRIPTION("Technologic Systems TS72xx NAND controller driver");
+MODULE_LICENSE("GPL");