Merge tag 'mtd/for-4.17' of git://git.infradead.org/linux-mtd
authorLinus Torvalds <torvalds@linux-foundation.org>
Fri, 6 Apr 2018 19:15:41 +0000 (12:15 -0700)
committerLinus Torvalds <torvalds@linux-foundation.org>
Fri, 6 Apr 2018 19:15:41 +0000 (12:15 -0700)
Pull MTD updates from Boris Brezillon:
 "MTD Core:
   - Remove support for asynchronous erase (not implemented by any of
     the existing drivers anyway)
   - Remove Cyrille from the list of SPI NOR and MTD maintainers
   - Fix kernel doc headers
   - Allow users to define the partitions parsers they want to test
     through a DT property (compatible of the partitions subnode)
   - Remove the bfin-async-flash driver (the only architecture using it
     has been removed)
   - Fix pagetest test
   - Add extra checks in mtd_erase()
   - Simplify the MTD partition creation logic and get rid of
     mtd_add_device_partitions()

  MTD Drivers:
   - Add endianness information to the physmap DT binding
   - Add Eon EN29LV400A IDs to JEDEC probe logic
   - Use %*ph where appropriate

  SPI NOR Drivers:
   - Make fsl-quaspi assign different names to MTD devices connected to
     the same QSPI controller
   - Remove an unneeded driver.bus assigned in the fsl-qspi driver

  NAND Core:
   - Prepare arrival of the SPI NAND subsystem by implementing a generic
     (interface-agnostic) layer to ease manipulation of NAND devices
   - Move onenand code base to the drivers/mtd/nand/ dir
   - Rework timing mode selection
   - Provide a generic way for NAND chip drivers to flag a specific
     GET/SET FEATURE operation as supported/unsupported
   - Stop embedding ONFI/JEDEC param page in nand_chip

  NAND Drivers:
   - Rework/cleanup of the mxc driver
   - Various cleanups in the vf610 driver
   - Migrate the fsmc and vf610 to ->exec_op()
   - Get rid of the pxa driver (replaced by marvell_nand)
   - Support ->setup_data_interface() in the GPMI driver
   - Fix probe error path in several drivers
   - Remove support for unused hw_syndrome mode in sunxi_nand
   - Various minor improvements"

* tag 'mtd/for-4.17' of git://git.infradead.org/linux-mtd: (89 commits)
  dt-bindings: fsl-quadspi: Add the example of two SPI NOR
  mtd: fsl-quadspi: Distinguish the mtd device names
  mtd: nand: Fix some function description mismatches in core.c
  mtd: fsl-quadspi: Remove unneeded driver.bus assignment
  mtd: rawnand: marvell: Rename ->ecc_clk into ->core_clk
  mtd: rawnand: s3c2410: enhance the probe function error path
  mtd: rawnand: tango: fix probe function error path
  mtd: rawnand: sh_flctl: fix the probe function error path
  mtd: rawnand: omap2: fix the probe function error path
  mtd: rawnand: mxc: fix probe function error path
  mtd: rawnand: denali: fix probe function error path
  mtd: rawnand: davinci: fix probe function error path
  mtd: rawnand: cafe: fix probe function error path
  mtd: rawnand: brcmnand: fix probe function error path
  mtd: rawnand: sunxi: Stop supporting ECC_HW_SYNDROME mode
  mtd: rawnand: marvell: Fix clock resource by adding a register clock
  mtd: ftl: Use DIV_ROUND_UP()
  mtd: Fix some function description mismatches in mtdcore.c
  mtd: physmap_of: update struct map_info's swap as per map requirement
  dt-bindings: mtd-physmap: Add endianness supports
  ...

1  2 
.mailmap
Documentation/driver-api/gpio/drivers-on-gpio.rst
MAINTAINERS
drivers/mtd/chips/jedec_probe.c
drivers/mtd/mtdchar.c
drivers/mtd/nand/raw/atmel/pmecc.c
drivers/mtd/nand/raw/fsl_ifc_nand.c
drivers/staging/mt29f_spinand/mt29f_spinand.c

diff --cc .mailmap
Simple merge
index 0194838,0000000..7da0c1d
mode 100644,000000..100644
--- /dev/null
@@@ -1,97 -1,0 +1,97 @@@
- - gpio-nand: drivers/mtd/nand/gpio.c is used to connect a NAND flash chip to
-   a set of simple GPIO lines: RDY, NCE, ALE, CLE, NWP. It interacts with the
 +============================
 +Subsystem drivers using GPIO
 +============================
 +
 +Note that standard kernel drivers exist for common GPIO tasks and will provide
 +the right in-kernel and userspace APIs/ABIs for the job, and that these
 +drivers can quite easily interconnect with other kernel subsystems using
 +hardware descriptions such as device tree or ACPI:
 +
 +- leds-gpio: drivers/leds/leds-gpio.c will handle LEDs connected to  GPIO
 +  lines, giving you the LED sysfs interface
 +
 +- ledtrig-gpio: drivers/leds/trigger/ledtrig-gpio.c will provide a LED trigger,
 +  i.e. a LED will turn on/off in response to a GPIO line going high or low
 +  (and that LED may in turn use the leds-gpio as per above).
 +
 +- gpio-keys: drivers/input/keyboard/gpio_keys.c is used when your GPIO line
 +  can generate interrupts in response to a key press. Also supports debounce.
 +
 +- gpio-keys-polled: drivers/input/keyboard/gpio_keys_polled.c is used when your
 +  GPIO line cannot generate interrupts, so it needs to be periodically polled
 +  by a timer.
 +
 +- gpio_mouse: drivers/input/mouse/gpio_mouse.c is used to provide a mouse with
 +  up to three buttons by simply using GPIOs and no mouse port. You can cut the
 +  mouse cable and connect the wires to GPIO lines or solder a mouse connector
 +  to the lines for a more permanent solution of this type.
 +
 +- gpio-beeper: drivers/input/misc/gpio-beeper.c is used to provide a beep from
 +  an external speaker connected to a GPIO line.
 +
 +- extcon-gpio: drivers/extcon/extcon-gpio.c is used when you need to read an
 +  external connector status, such as a headset line for an audio driver or an
 +  HDMI connector. It will provide a better userspace sysfs interface than GPIO.
 +
 +- restart-gpio: drivers/power/reset/gpio-restart.c is used to restart/reboot
 +  the system by pulling a GPIO line and will register a restart handler so
 +  userspace can issue the right system call to restart the system.
 +
 +- poweroff-gpio: drivers/power/reset/gpio-poweroff.c is used to power the
 +  system down by pulling a GPIO line and will register a pm_power_off()
 +  callback so that userspace can issue the right system call to power down the
 +  system.
 +
 +- gpio-gate-clock: drivers/clk/clk-gpio.c is used to control a gated clock
 +  (off/on) that uses a GPIO, and integrated with the clock subsystem.
 +
 +- i2c-gpio: drivers/i2c/busses/i2c-gpio.c is used to drive an I2C bus
 +  (two wires, SDA and SCL lines) by hammering (bitbang) two GPIO lines. It will
 +  appear as any other I2C bus to the system and makes it possible to connect
 +  drivers for the I2C devices on the bus like any other I2C bus driver.
 +
 +- spi_gpio: drivers/spi/spi-gpio.c is used to drive an SPI bus (variable number
 +  of wires, at least SCK and optionally MISO, MOSI and chip select lines) using
 +  GPIO hammering (bitbang). It will appear as any other SPI bus on the system
 +  and makes it possible to connect drivers for SPI devices on the bus like
 +  any other SPI bus driver. For example any MMC/SD card can then be connected
 +  to this SPI by using the mmc_spi host from the MMC/SD card subsystem.
 +
 +- w1-gpio: drivers/w1/masters/w1-gpio.c is used to drive a one-wire bus using
 +  a GPIO line, integrating with the W1 subsystem and handling devices on
 +  the bus like any other W1 device.
 +
 +- gpio-fan: drivers/hwmon/gpio-fan.c is used to control a fan for cooling the
 +  system, connected to a GPIO line (and optionally a GPIO alarm line),
 +  presenting all the right in-kernel and sysfs interfaces to make your system
 +  not overheat.
 +
 +- gpio-regulator: drivers/regulator/gpio-regulator.c is used to control a
 +  regulator providing a certain voltage by pulling a GPIO line, integrating
 +  with the regulator subsystem and giving you all the right interfaces.
 +
 +- gpio-wdt: drivers/watchdog/gpio_wdt.c is used to provide a watchdog timer
 +  that will periodically "ping" a hardware connected to a GPIO line by toggling
 +  it from 1-to-0-to-1. If that hardware does not receive its "ping"
 +  periodically, it will reset the system.
 +
++- gpio-nand: drivers/mtd/nand/raw/gpio.c is used to connect a NAND flash chip
++  to a set of simple GPIO lines: RDY, NCE, ALE, CLE, NWP. It interacts with the
 +  NAND flash MTD subsystem and provides chip access and partition parsing like
 +  any other NAND driving hardware.
 +
 +- ps2-gpio: drivers/input/serio/ps2-gpio.c is used to drive a PS/2 (IBM) serio
 +  bus, data and clock line, by bit banging two GPIO lines. It will appear as
 +  any other serio bus to the system and makes it possible to connect drivers
 +  for e.g. keyboards and other PS/2 protocol based devices.
 +
 +Apart from this there are special GPIO drivers in subsystems like MMC/SD to
 +read card detect and write protect GPIO lines, and in the TTY serial subsystem
 +to emulate MCTRL (modem control) signals CTS/RTS by using two GPIO lines. The
 +MTD NOR flash has add-ons for extra GPIO lines too, though the address bus is
 +usually connected directly to the flash.
 +
 +Use those instead of talking directly to the GPIOs using sysfs; they integrate
 +with kernel frameworks better than your userspace code could. Needless to say,
 +just using the appropriate kernel drivers will simplify and speed up your
 +embedded hacking in particular by providing ready-made components.
diff --cc MAINTAINERS
@@@ -4091,10 -4116,10 +4091,10 @@@ DENALI NAND DRIVE
  M:    Masahiro Yamada <yamada.masahiro@socionext.com>
  L:    linux-mtd@lists.infradead.org
  S:    Supported
- F:    drivers/mtd/nand/denali*
+ F:    drivers/mtd/nand/raw/denali*
  
  DESIGNWARE USB2 DRD IP DRIVER
 -M:    John Youn <johnyoun@synopsys.com>
 +M:    Minas Harutyunyan <hminas@synopsys.com>
  L:    linux-usb@vger.kernel.org
  T:    git git://git.kernel.org/pub/scm/linux/kernel/git/balbi/usb.git
  S:    Maintained
Simple merge
Simple merge
index 0000000,9de29c9..555a74e
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,1012 +1,1012 @@@
 -      return user->cache.cfg & PMECC_LOOKUP_TABLE_SIZE_1024 ? 1024 : 512;
+ /*
+  * Copyright 2017 ATMEL
+  * Copyright 2017 Free Electrons
+  *
+  * Author: Boris Brezillon <boris.brezillon@free-electrons.com>
+  *
+  * Derived from the atmel_nand.c driver which contained the following
+  * copyrights:
+  *
+  *   Copyright 2003 Rick Bronson
+  *
+  *   Derived from drivers/mtd/nand/autcpu12.c (removed in v3.8)
+  *    Copyright 2001 Thomas Gleixner (gleixner@autronix.de)
+  *
+  *   Derived from drivers/mtd/spia.c (removed in v3.8)
+  *    Copyright 2000 Steven J. Hill (sjhill@cotw.com)
+  *
+  *   Add Hardware ECC support for AT91SAM9260 / AT91SAM9263
+  *    Richard Genoud (richard.genoud@gmail.com), Adeneo Copyright 2007
+  *
+  *   Derived from Das U-Boot source code
+  *    (u-boot-1.1.5/board/atmel/at91sam9263ek/nand.c)
+  *      Copyright 2006 ATMEL Rousset, Lacressonniere Nicolas
+  *
+  *   Add Programmable Multibit ECC support for various AT91 SoC
+  *    Copyright 2012 ATMEL, Hong Xu
+  *
+  *   Add Nand Flash Controller support for SAMA5 SoC
+  *    Copyright 2013 ATMEL, Josh Wu (josh.wu@atmel.com)
+  *
+  * 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.
+  *
+  * The PMECC is an hardware assisted BCH engine, which means part of the
+  * ECC algorithm is left to the software. The hardware/software repartition
+  * is explained in the "PMECC Controller Functional Description" chapter in
+  * Atmel datasheets, and some of the functions in this file are directly
+  * implementing the algorithms described in the "Software Implementation"
+  * sub-section.
+  *
+  * TODO: it seems that the software BCH implementation in lib/bch.c is already
+  * providing some of the logic we are implementing here. It would be smart
+  * to expose the needed lib/bch.c helpers/functions and re-use them here.
+  */
+ #include <linux/genalloc.h>
+ #include <linux/iopoll.h>
+ #include <linux/module.h>
+ #include <linux/mtd/rawnand.h>
+ #include <linux/of_irq.h>
+ #include <linux/of_platform.h>
+ #include <linux/platform_device.h>
+ #include <linux/slab.h>
+ #include "pmecc.h"
+ /* Galois field dimension */
+ #define PMECC_GF_DIMENSION_13                 13
+ #define PMECC_GF_DIMENSION_14                 14
+ /* Primitive Polynomial used by PMECC */
+ #define PMECC_GF_13_PRIMITIVE_POLY            0x201b
+ #define PMECC_GF_14_PRIMITIVE_POLY            0x4443
+ #define PMECC_LOOKUP_TABLE_SIZE_512           0x2000
+ #define PMECC_LOOKUP_TABLE_SIZE_1024          0x4000
+ /* Time out value for reading PMECC status register */
+ #define PMECC_MAX_TIMEOUT_MS                  100
+ /* PMECC Register Definitions */
+ #define ATMEL_PMECC_CFG                               0x0
+ #define PMECC_CFG_BCH_STRENGTH(x)             (x)
+ #define PMECC_CFG_BCH_STRENGTH_MASK           GENMASK(2, 0)
+ #define PMECC_CFG_SECTOR512                   (0 << 4)
+ #define PMECC_CFG_SECTOR1024                  (1 << 4)
+ #define PMECC_CFG_NSECTORS(x)                 ((fls(x) - 1) << 8)
+ #define PMECC_CFG_READ_OP                     (0 << 12)
+ #define PMECC_CFG_WRITE_OP                    (1 << 12)
+ #define PMECC_CFG_SPARE_ENABLE                        BIT(16)
+ #define PMECC_CFG_AUTO_ENABLE                 BIT(20)
+ #define ATMEL_PMECC_SAREA                     0x4
+ #define ATMEL_PMECC_SADDR                     0x8
+ #define ATMEL_PMECC_EADDR                     0xc
+ #define ATMEL_PMECC_CLK                               0x10
+ #define PMECC_CLK_133MHZ                      (2 << 0)
+ #define ATMEL_PMECC_CTRL                      0x14
+ #define PMECC_CTRL_RST                                BIT(0)
+ #define PMECC_CTRL_DATA                               BIT(1)
+ #define PMECC_CTRL_USER                               BIT(2)
+ #define PMECC_CTRL_ENABLE                     BIT(4)
+ #define PMECC_CTRL_DISABLE                    BIT(5)
+ #define ATMEL_PMECC_SR                                0x18
+ #define PMECC_SR_BUSY                         BIT(0)
+ #define PMECC_SR_ENABLE                               BIT(4)
+ #define ATMEL_PMECC_IER                               0x1c
+ #define ATMEL_PMECC_IDR                               0x20
+ #define ATMEL_PMECC_IMR                               0x24
+ #define ATMEL_PMECC_ISR                               0x28
+ #define PMECC_ERROR_INT                               BIT(0)
+ #define ATMEL_PMECC_ECC(sector, n)            \
+       ((((sector) + 1) * 0x40) + (n))
+ #define ATMEL_PMECC_REM(sector, n)            \
+       ((((sector) + 1) * 0x40) + ((n) * 4) + 0x200)
+ /* PMERRLOC Register Definitions */
+ #define ATMEL_PMERRLOC_ELCFG                  0x0
+ #define PMERRLOC_ELCFG_SECTOR_512             (0 << 0)
+ #define PMERRLOC_ELCFG_SECTOR_1024            (1 << 0)
+ #define PMERRLOC_ELCFG_NUM_ERRORS(n)          ((n) << 16)
+ #define ATMEL_PMERRLOC_ELPRIM                 0x4
+ #define ATMEL_PMERRLOC_ELEN                   0x8
+ #define ATMEL_PMERRLOC_ELDIS                  0xc
+ #define PMERRLOC_DISABLE                      BIT(0)
+ #define ATMEL_PMERRLOC_ELSR                   0x10
+ #define PMERRLOC_ELSR_BUSY                    BIT(0)
+ #define ATMEL_PMERRLOC_ELIER                  0x14
+ #define ATMEL_PMERRLOC_ELIDR                  0x18
+ #define ATMEL_PMERRLOC_ELIMR                  0x1c
+ #define ATMEL_PMERRLOC_ELISR                  0x20
+ #define PMERRLOC_ERR_NUM_MASK                 GENMASK(12, 8)
+ #define PMERRLOC_CALC_DONE                    BIT(0)
+ #define ATMEL_PMERRLOC_SIGMA(x)                       (((x) * 0x4) + 0x28)
+ #define ATMEL_PMERRLOC_EL(offs, x)            (((x) * 0x4) + (offs))
+ struct atmel_pmecc_gf_tables {
+       u16 *alpha_to;
+       u16 *index_of;
+ };
+ struct atmel_pmecc_caps {
+       const int *strengths;
+       int nstrengths;
+       int el_offset;
+       bool correct_erased_chunks;
+ };
+ struct atmel_pmecc {
+       struct device *dev;
+       const struct atmel_pmecc_caps *caps;
+       struct {
+               void __iomem *base;
+               void __iomem *errloc;
+       } regs;
+       struct mutex lock;
+ };
+ struct atmel_pmecc_user_conf_cache {
+       u32 cfg;
+       u32 sarea;
+       u32 saddr;
+       u32 eaddr;
+ };
+ struct atmel_pmecc_user {
+       struct atmel_pmecc_user_conf_cache cache;
+       struct atmel_pmecc *pmecc;
+       const struct atmel_pmecc_gf_tables *gf_tables;
+       int eccbytes;
+       s16 *partial_syn;
+       s16 *si;
+       s16 *lmu;
+       s16 *smu;
+       s32 *mu;
+       s32 *dmu;
+       s32 *delta;
+       u32 isr;
+ };
+ static DEFINE_MUTEX(pmecc_gf_tables_lock);
+ static const struct atmel_pmecc_gf_tables *pmecc_gf_tables_512;
+ static const struct atmel_pmecc_gf_tables *pmecc_gf_tables_1024;
+ static inline int deg(unsigned int poly)
+ {
+       /* polynomial degree is the most-significant bit index */
+       return fls(poly) - 1;
+ }
+ static int atmel_pmecc_build_gf_tables(int mm, unsigned int poly,
+                                      struct atmel_pmecc_gf_tables *gf_tables)
+ {
+       unsigned int i, x = 1;
+       const unsigned int k = BIT(deg(poly));
+       unsigned int nn = BIT(mm) - 1;
+       /* primitive polynomial must be of degree m */
+       if (k != (1u << mm))
+               return -EINVAL;
+       for (i = 0; i < nn; i++) {
+               gf_tables->alpha_to[i] = x;
+               gf_tables->index_of[x] = i;
+               if (i && (x == 1))
+                       /* polynomial is not primitive (a^i=1 with 0<i<2^m-1) */
+                       return -EINVAL;
+               x <<= 1;
+               if (x & k)
+                       x ^= poly;
+       }
+       gf_tables->alpha_to[nn] = 1;
+       gf_tables->index_of[0] = 0;
+       return 0;
+ }
+ static const struct atmel_pmecc_gf_tables *
+ atmel_pmecc_create_gf_tables(const struct atmel_pmecc_user_req *req)
+ {
+       struct atmel_pmecc_gf_tables *gf_tables;
+       unsigned int poly, degree, table_size;
+       int ret;
+       if (req->ecc.sectorsize == 512) {
+               degree = PMECC_GF_DIMENSION_13;
+               poly = PMECC_GF_13_PRIMITIVE_POLY;
+               table_size = PMECC_LOOKUP_TABLE_SIZE_512;
+       } else {
+               degree = PMECC_GF_DIMENSION_14;
+               poly = PMECC_GF_14_PRIMITIVE_POLY;
+               table_size = PMECC_LOOKUP_TABLE_SIZE_1024;
+       }
+       gf_tables = kzalloc(sizeof(*gf_tables) +
+                           (2 * table_size * sizeof(u16)),
+                           GFP_KERNEL);
+       if (!gf_tables)
+               return ERR_PTR(-ENOMEM);
+       gf_tables->alpha_to = (void *)(gf_tables + 1);
+       gf_tables->index_of = gf_tables->alpha_to + table_size;
+       ret = atmel_pmecc_build_gf_tables(degree, poly, gf_tables);
+       if (ret) {
+               kfree(gf_tables);
+               return ERR_PTR(ret);
+       }
+       return gf_tables;
+ }
+ static const struct atmel_pmecc_gf_tables *
+ atmel_pmecc_get_gf_tables(const struct atmel_pmecc_user_req *req)
+ {
+       const struct atmel_pmecc_gf_tables **gf_tables, *ret;
+       mutex_lock(&pmecc_gf_tables_lock);
+       if (req->ecc.sectorsize == 512)
+               gf_tables = &pmecc_gf_tables_512;
+       else
+               gf_tables = &pmecc_gf_tables_1024;
+       ret = *gf_tables;
+       if (!ret) {
+               ret = atmel_pmecc_create_gf_tables(req);
+               if (!IS_ERR(ret))
+                       *gf_tables = ret;
+       }
+       mutex_unlock(&pmecc_gf_tables_lock);
+       return ret;
+ }
+ static int atmel_pmecc_prepare_user_req(struct atmel_pmecc *pmecc,
+                                       struct atmel_pmecc_user_req *req)
+ {
+       int i, max_eccbytes, eccbytes = 0, eccstrength = 0;
+       if (req->pagesize <= 0 || req->oobsize <= 0 || req->ecc.bytes <= 0)
+               return -EINVAL;
+       if (req->ecc.ooboffset >= 0 &&
+           req->ecc.ooboffset + req->ecc.bytes > req->oobsize)
+               return -EINVAL;
+       if (req->ecc.sectorsize == ATMEL_PMECC_SECTOR_SIZE_AUTO) {
+               if (req->ecc.strength != ATMEL_PMECC_MAXIMIZE_ECC_STRENGTH)
+                       return -EINVAL;
+               if (req->pagesize > 512)
+                       req->ecc.sectorsize = 1024;
+               else
+                       req->ecc.sectorsize = 512;
+       }
+       if (req->ecc.sectorsize != 512 && req->ecc.sectorsize != 1024)
+               return -EINVAL;
+       if (req->pagesize % req->ecc.sectorsize)
+               return -EINVAL;
+       req->ecc.nsectors = req->pagesize / req->ecc.sectorsize;
+       max_eccbytes = req->ecc.bytes;
+       for (i = 0; i < pmecc->caps->nstrengths; i++) {
+               int nbytes, strength = pmecc->caps->strengths[i];
+               if (req->ecc.strength != ATMEL_PMECC_MAXIMIZE_ECC_STRENGTH &&
+                   strength < req->ecc.strength)
+                       continue;
+               nbytes = DIV_ROUND_UP(strength * fls(8 * req->ecc.sectorsize),
+                                     8);
+               nbytes *= req->ecc.nsectors;
+               if (nbytes > max_eccbytes)
+                       break;
+               eccstrength = strength;
+               eccbytes = nbytes;
+               if (req->ecc.strength != ATMEL_PMECC_MAXIMIZE_ECC_STRENGTH)
+                       break;
+       }
+       if (!eccstrength)
+               return -EINVAL;
+       req->ecc.bytes = eccbytes;
+       req->ecc.strength = eccstrength;
+       if (req->ecc.ooboffset < 0)
+               req->ecc.ooboffset = req->oobsize - eccbytes;
+       return 0;
+ }
+ struct atmel_pmecc_user *
+ atmel_pmecc_create_user(struct atmel_pmecc *pmecc,
+                       struct atmel_pmecc_user_req *req)
+ {
+       struct atmel_pmecc_user *user;
+       const struct atmel_pmecc_gf_tables *gf_tables;
+       int strength, size, ret;
+       ret = atmel_pmecc_prepare_user_req(pmecc, req);
+       if (ret)
+               return ERR_PTR(ret);
+       size = sizeof(*user);
+       size = ALIGN(size, sizeof(u16));
+       /* Reserve space for partial_syn, si and smu */
+       size += ((2 * req->ecc.strength) + 1) * sizeof(u16) *
+               (2 + req->ecc.strength + 2);
+       /* Reserve space for lmu. */
+       size += (req->ecc.strength + 1) * sizeof(u16);
+       /* Reserve space for mu, dmu and delta. */
+       size = ALIGN(size, sizeof(s32));
+       size += (req->ecc.strength + 1) * sizeof(s32) * 3;
+       user = kzalloc(size, GFP_KERNEL);
+       if (!user)
+               return ERR_PTR(-ENOMEM);
+       user->pmecc = pmecc;
+       user->partial_syn = (s16 *)PTR_ALIGN(user + 1, sizeof(u16));
+       user->si = user->partial_syn + ((2 * req->ecc.strength) + 1);
+       user->lmu = user->si + ((2 * req->ecc.strength) + 1);
+       user->smu = user->lmu + (req->ecc.strength + 1);
+       user->mu = (s32 *)PTR_ALIGN(user->smu +
+                                   (((2 * req->ecc.strength) + 1) *
+                                    (req->ecc.strength + 2)),
+                                   sizeof(s32));
+       user->dmu = user->mu + req->ecc.strength + 1;
+       user->delta = user->dmu + req->ecc.strength + 1;
+       gf_tables = atmel_pmecc_get_gf_tables(req);
+       if (IS_ERR(gf_tables)) {
+               kfree(user);
+               return ERR_CAST(gf_tables);
+       }
+       user->gf_tables = gf_tables;
+       user->eccbytes = req->ecc.bytes / req->ecc.nsectors;
+       for (strength = 0; strength < pmecc->caps->nstrengths; strength++) {
+               if (pmecc->caps->strengths[strength] == req->ecc.strength)
+                       break;
+       }
+       user->cache.cfg = PMECC_CFG_BCH_STRENGTH(strength) |
+                         PMECC_CFG_NSECTORS(req->ecc.nsectors);
+       if (req->ecc.sectorsize == 1024)
+               user->cache.cfg |= PMECC_CFG_SECTOR1024;
+       user->cache.sarea = req->oobsize - 1;
+       user->cache.saddr = req->ecc.ooboffset;
+       user->cache.eaddr = req->ecc.ooboffset + req->ecc.bytes - 1;
+       return user;
+ }
+ EXPORT_SYMBOL_GPL(atmel_pmecc_create_user);
+ void atmel_pmecc_destroy_user(struct atmel_pmecc_user *user)
+ {
+       kfree(user);
+ }
+ EXPORT_SYMBOL_GPL(atmel_pmecc_destroy_user);
+ static int get_strength(struct atmel_pmecc_user *user)
+ {
+       const int *strengths = user->pmecc->caps->strengths;
+       return strengths[user->cache.cfg & PMECC_CFG_BCH_STRENGTH_MASK];
+ }
+ static int get_sectorsize(struct atmel_pmecc_user *user)
+ {
++      return user->cache.cfg & PMECC_CFG_SECTOR1024 ? 1024 : 512;
+ }
+ static void atmel_pmecc_gen_syndrome(struct atmel_pmecc_user *user, int sector)
+ {
+       int strength = get_strength(user);
+       u32 value;
+       int i;
+       /* Fill odd syndromes */
+       for (i = 0; i < strength; i++) {
+               value = readl_relaxed(user->pmecc->regs.base +
+                                     ATMEL_PMECC_REM(sector, i / 2));
+               if (i & 1)
+                       value >>= 16;
+               user->partial_syn[(2 * i) + 1] = value;
+       }
+ }
+ static void atmel_pmecc_substitute(struct atmel_pmecc_user *user)
+ {
+       int degree = get_sectorsize(user) == 512 ? 13 : 14;
+       int cw_len = BIT(degree) - 1;
+       int strength = get_strength(user);
+       s16 *alpha_to = user->gf_tables->alpha_to;
+       s16 *index_of = user->gf_tables->index_of;
+       s16 *partial_syn = user->partial_syn;
+       s16 *si;
+       int i, j;
+       /*
+        * si[] is a table that holds the current syndrome value,
+        * an element of that table belongs to the field
+        */
+       si = user->si;
+       memset(&si[1], 0, sizeof(s16) * ((2 * strength) - 1));
+       /* Computation 2t syndromes based on S(x) */
+       /* Odd syndromes */
+       for (i = 1; i < 2 * strength; i += 2) {
+               for (j = 0; j < degree; j++) {
+                       if (partial_syn[i] & BIT(j))
+                               si[i] = alpha_to[i * j] ^ si[i];
+               }
+       }
+       /* Even syndrome = (Odd syndrome) ** 2 */
+       for (i = 2, j = 1; j <= strength; i = ++j << 1) {
+               if (si[j] == 0) {
+                       si[i] = 0;
+               } else {
+                       s16 tmp;
+                       tmp = index_of[si[j]];
+                       tmp = (tmp * 2) % cw_len;
+                       si[i] = alpha_to[tmp];
+               }
+       }
+ }
+ static void atmel_pmecc_get_sigma(struct atmel_pmecc_user *user)
+ {
+       s16 *lmu = user->lmu;
+       s16 *si = user->si;
+       s32 *mu = user->mu;
+       s32 *dmu = user->dmu;
+       s32 *delta = user->delta;
+       int degree = get_sectorsize(user) == 512 ? 13 : 14;
+       int cw_len = BIT(degree) - 1;
+       int strength = get_strength(user);
+       int num = 2 * strength + 1;
+       s16 *index_of = user->gf_tables->index_of;
+       s16 *alpha_to = user->gf_tables->alpha_to;
+       int i, j, k;
+       u32 dmu_0_count, tmp;
+       s16 *smu = user->smu;
+       /* index of largest delta */
+       int ro;
+       int largest;
+       int diff;
+       dmu_0_count = 0;
+       /* First Row */
+       /* Mu */
+       mu[0] = -1;
+       memset(smu, 0, sizeof(s16) * num);
+       smu[0] = 1;
+       /* discrepancy set to 1 */
+       dmu[0] = 1;
+       /* polynom order set to 0 */
+       lmu[0] = 0;
+       delta[0] = (mu[0] * 2 - lmu[0]) >> 1;
+       /* Second Row */
+       /* Mu */
+       mu[1] = 0;
+       /* Sigma(x) set to 1 */
+       memset(&smu[num], 0, sizeof(s16) * num);
+       smu[num] = 1;
+       /* discrepancy set to S1 */
+       dmu[1] = si[1];
+       /* polynom order set to 0 */
+       lmu[1] = 0;
+       delta[1] = (mu[1] * 2 - lmu[1]) >> 1;
+       /* Init the Sigma(x) last row */
+       memset(&smu[(strength + 1) * num], 0, sizeof(s16) * num);
+       for (i = 1; i <= strength; i++) {
+               mu[i + 1] = i << 1;
+               /* Begin Computing Sigma (Mu+1) and L(mu) */
+               /* check if discrepancy is set to 0 */
+               if (dmu[i] == 0) {
+                       dmu_0_count++;
+                       tmp = ((strength - (lmu[i] >> 1) - 1) / 2);
+                       if ((strength - (lmu[i] >> 1) - 1) & 0x1)
+                               tmp += 2;
+                       else
+                               tmp += 1;
+                       if (dmu_0_count == tmp) {
+                               for (j = 0; j <= (lmu[i] >> 1) + 1; j++)
+                                       smu[(strength + 1) * num + j] =
+                                                       smu[i * num + j];
+                               lmu[strength + 1] = lmu[i];
+                               return;
+                       }
+                       /* copy polynom */
+                       for (j = 0; j <= lmu[i] >> 1; j++)
+                               smu[(i + 1) * num + j] = smu[i * num + j];
+                       /* copy previous polynom order to the next */
+                       lmu[i + 1] = lmu[i];
+               } else {
+                       ro = 0;
+                       largest = -1;
+                       /* find largest delta with dmu != 0 */
+                       for (j = 0; j < i; j++) {
+                               if ((dmu[j]) && (delta[j] > largest)) {
+                                       largest = delta[j];
+                                       ro = j;
+                               }
+                       }
+                       /* compute difference */
+                       diff = (mu[i] - mu[ro]);
+                       /* Compute degree of the new smu polynomial */
+                       if ((lmu[i] >> 1) > ((lmu[ro] >> 1) + diff))
+                               lmu[i + 1] = lmu[i];
+                       else
+                               lmu[i + 1] = ((lmu[ro] >> 1) + diff) * 2;
+                       /* Init smu[i+1] with 0 */
+                       for (k = 0; k < num; k++)
+                               smu[(i + 1) * num + k] = 0;
+                       /* Compute smu[i+1] */
+                       for (k = 0; k <= lmu[ro] >> 1; k++) {
+                               s16 a, b, c;
+                               if (!(smu[ro * num + k] && dmu[i]))
+                                       continue;
+                               a = index_of[dmu[i]];
+                               b = index_of[dmu[ro]];
+                               c = index_of[smu[ro * num + k]];
+                               tmp = a + (cw_len - b) + c;
+                               a = alpha_to[tmp % cw_len];
+                               smu[(i + 1) * num + (k + diff)] = a;
+                       }
+                       for (k = 0; k <= lmu[i] >> 1; k++)
+                               smu[(i + 1) * num + k] ^= smu[i * num + k];
+               }
+               /* End Computing Sigma (Mu+1) and L(mu) */
+               /* In either case compute delta */
+               delta[i + 1] = (mu[i + 1] * 2 - lmu[i + 1]) >> 1;
+               /* Do not compute discrepancy for the last iteration */
+               if (i >= strength)
+                       continue;
+               for (k = 0; k <= (lmu[i + 1] >> 1); k++) {
+                       tmp = 2 * (i - 1);
+                       if (k == 0) {
+                               dmu[i + 1] = si[tmp + 3];
+                       } else if (smu[(i + 1) * num + k] && si[tmp + 3 - k]) {
+                               s16 a, b, c;
+                               a = index_of[smu[(i + 1) * num + k]];
+                               b = si[2 * (i - 1) + 3 - k];
+                               c = index_of[b];
+                               tmp = a + c;
+                               tmp %= cw_len;
+                               dmu[i + 1] = alpha_to[tmp] ^ dmu[i + 1];
+                       }
+               }
+       }
+ }
+ static int atmel_pmecc_err_location(struct atmel_pmecc_user *user)
+ {
+       int sector_size = get_sectorsize(user);
+       int degree = sector_size == 512 ? 13 : 14;
+       struct atmel_pmecc *pmecc = user->pmecc;
+       int strength = get_strength(user);
+       int ret, roots_nbr, i, err_nbr = 0;
+       int num = (2 * strength) + 1;
+       s16 *smu = user->smu;
+       u32 val;
+       writel(PMERRLOC_DISABLE, pmecc->regs.errloc + ATMEL_PMERRLOC_ELDIS);
+       for (i = 0; i <= user->lmu[strength + 1] >> 1; i++) {
+               writel_relaxed(smu[(strength + 1) * num + i],
+                              pmecc->regs.errloc + ATMEL_PMERRLOC_SIGMA(i));
+               err_nbr++;
+       }
+       val = (err_nbr - 1) << 16;
+       if (sector_size == 1024)
+               val |= 1;
+       writel(val, pmecc->regs.errloc + ATMEL_PMERRLOC_ELCFG);
+       writel((sector_size * 8) + (degree * strength),
+              pmecc->regs.errloc + ATMEL_PMERRLOC_ELEN);
+       ret = readl_relaxed_poll_timeout(pmecc->regs.errloc +
+                                        ATMEL_PMERRLOC_ELISR,
+                                        val, val & PMERRLOC_CALC_DONE, 0,
+                                        PMECC_MAX_TIMEOUT_MS * 1000);
+       if (ret) {
+               dev_err(pmecc->dev,
+                       "PMECC: Timeout to calculate error location.\n");
+               return ret;
+       }
+       roots_nbr = (val & PMERRLOC_ERR_NUM_MASK) >> 8;
+       /* Number of roots == degree of smu hence <= cap */
+       if (roots_nbr == user->lmu[strength + 1] >> 1)
+               return err_nbr - 1;
+       /*
+        * Number of roots does not match the degree of smu
+        * unable to correct error.
+        */
+       return -EBADMSG;
+ }
+ int atmel_pmecc_correct_sector(struct atmel_pmecc_user *user, int sector,
+                              void *data, void *ecc)
+ {
+       struct atmel_pmecc *pmecc = user->pmecc;
+       int sectorsize = get_sectorsize(user);
+       int eccbytes = user->eccbytes;
+       int i, nerrors;
+       if (!(user->isr & BIT(sector)))
+               return 0;
+       atmel_pmecc_gen_syndrome(user, sector);
+       atmel_pmecc_substitute(user);
+       atmel_pmecc_get_sigma(user);
+       nerrors = atmel_pmecc_err_location(user);
+       if (nerrors < 0)
+               return nerrors;
+       for (i = 0; i < nerrors; i++) {
+               const char *area;
+               int byte, bit;
+               u32 errpos;
+               u8 *ptr;
+               errpos = readl_relaxed(pmecc->regs.errloc +
+                               ATMEL_PMERRLOC_EL(pmecc->caps->el_offset, i));
+               errpos--;
+               byte = errpos / 8;
+               bit = errpos % 8;
+               if (byte < sectorsize) {
+                       ptr = data + byte;
+                       area = "data";
+               } else if (byte < sectorsize + eccbytes) {
+                       ptr = ecc + byte - sectorsize;
+                       area = "ECC";
+               } else {
+                       dev_dbg(pmecc->dev,
+                               "Invalid errpos value (%d, max is %d)\n",
+                               errpos, (sectorsize + eccbytes) * 8);
+                       return -EINVAL;
+               }
+               dev_dbg(pmecc->dev,
+                       "Bit flip in %s area, byte %d: 0x%02x -> 0x%02x\n",
+                       area, byte, *ptr, (unsigned int)(*ptr ^ BIT(bit)));
+               *ptr ^= BIT(bit);
+       }
+       return nerrors;
+ }
+ EXPORT_SYMBOL_GPL(atmel_pmecc_correct_sector);
+ bool atmel_pmecc_correct_erased_chunks(struct atmel_pmecc_user *user)
+ {
+       return user->pmecc->caps->correct_erased_chunks;
+ }
+ EXPORT_SYMBOL_GPL(atmel_pmecc_correct_erased_chunks);
+ void atmel_pmecc_get_generated_eccbytes(struct atmel_pmecc_user *user,
+                                       int sector, void *ecc)
+ {
+       struct atmel_pmecc *pmecc = user->pmecc;
+       u8 *ptr = ecc;
+       int i;
+       for (i = 0; i < user->eccbytes; i++)
+               ptr[i] = readb_relaxed(pmecc->regs.base +
+                                      ATMEL_PMECC_ECC(sector, i));
+ }
+ EXPORT_SYMBOL_GPL(atmel_pmecc_get_generated_eccbytes);
+ void atmel_pmecc_reset(struct atmel_pmecc *pmecc)
+ {
+       writel(PMECC_CTRL_RST, pmecc->regs.base + ATMEL_PMECC_CTRL);
+       writel(PMECC_CTRL_DISABLE, pmecc->regs.base + ATMEL_PMECC_CTRL);
+ }
+ EXPORT_SYMBOL_GPL(atmel_pmecc_reset);
+ int atmel_pmecc_enable(struct atmel_pmecc_user *user, int op)
+ {
+       struct atmel_pmecc *pmecc = user->pmecc;
+       u32 cfg;
+       if (op != NAND_ECC_READ && op != NAND_ECC_WRITE) {
+               dev_err(pmecc->dev, "Bad ECC operation!");
+               return -EINVAL;
+       }
+       mutex_lock(&user->pmecc->lock);
+       cfg = user->cache.cfg;
+       if (op == NAND_ECC_WRITE)
+               cfg |= PMECC_CFG_WRITE_OP;
+       else
+               cfg |= PMECC_CFG_AUTO_ENABLE;
+       writel(cfg, pmecc->regs.base + ATMEL_PMECC_CFG);
+       writel(user->cache.sarea, pmecc->regs.base + ATMEL_PMECC_SAREA);
+       writel(user->cache.saddr, pmecc->regs.base + ATMEL_PMECC_SADDR);
+       writel(user->cache.eaddr, pmecc->regs.base + ATMEL_PMECC_EADDR);
+       writel(PMECC_CTRL_ENABLE, pmecc->regs.base + ATMEL_PMECC_CTRL);
+       writel(PMECC_CTRL_DATA, pmecc->regs.base + ATMEL_PMECC_CTRL);
+       return 0;
+ }
+ EXPORT_SYMBOL_GPL(atmel_pmecc_enable);
+ void atmel_pmecc_disable(struct atmel_pmecc_user *user)
+ {
+       atmel_pmecc_reset(user->pmecc);
+       mutex_unlock(&user->pmecc->lock);
+ }
+ EXPORT_SYMBOL_GPL(atmel_pmecc_disable);
+ int atmel_pmecc_wait_rdy(struct atmel_pmecc_user *user)
+ {
+       struct atmel_pmecc *pmecc = user->pmecc;
+       u32 status;
+       int ret;
+       ret = readl_relaxed_poll_timeout(pmecc->regs.base +
+                                        ATMEL_PMECC_SR,
+                                        status, !(status & PMECC_SR_BUSY), 0,
+                                        PMECC_MAX_TIMEOUT_MS * 1000);
+       if (ret) {
+               dev_err(pmecc->dev,
+                       "Timeout while waiting for PMECC ready.\n");
+               return ret;
+       }
+       user->isr = readl_relaxed(pmecc->regs.base + ATMEL_PMECC_ISR);
+       return 0;
+ }
+ EXPORT_SYMBOL_GPL(atmel_pmecc_wait_rdy);
+ static struct atmel_pmecc *atmel_pmecc_create(struct platform_device *pdev,
+                                       const struct atmel_pmecc_caps *caps,
+                                       int pmecc_res_idx, int errloc_res_idx)
+ {
+       struct device *dev = &pdev->dev;
+       struct atmel_pmecc *pmecc;
+       struct resource *res;
+       pmecc = devm_kzalloc(dev, sizeof(*pmecc), GFP_KERNEL);
+       if (!pmecc)
+               return ERR_PTR(-ENOMEM);
+       pmecc->caps = caps;
+       pmecc->dev = dev;
+       mutex_init(&pmecc->lock);
+       res = platform_get_resource(pdev, IORESOURCE_MEM, pmecc_res_idx);
+       pmecc->regs.base = devm_ioremap_resource(dev, res);
+       if (IS_ERR(pmecc->regs.base))
+               return ERR_CAST(pmecc->regs.base);
+       res = platform_get_resource(pdev, IORESOURCE_MEM, errloc_res_idx);
+       pmecc->regs.errloc = devm_ioremap_resource(dev, res);
+       if (IS_ERR(pmecc->regs.errloc))
+               return ERR_CAST(pmecc->regs.errloc);
+       /* Disable all interrupts before registering the PMECC handler. */
+       writel(0xffffffff, pmecc->regs.base + ATMEL_PMECC_IDR);
+       atmel_pmecc_reset(pmecc);
+       return pmecc;
+ }
+ static void devm_atmel_pmecc_put(struct device *dev, void *res)
+ {
+       struct atmel_pmecc **pmecc = res;
+       put_device((*pmecc)->dev);
+ }
+ static struct atmel_pmecc *atmel_pmecc_get_by_node(struct device *userdev,
+                                                  struct device_node *np)
+ {
+       struct platform_device *pdev;
+       struct atmel_pmecc *pmecc, **ptr;
+       pdev = of_find_device_by_node(np);
+       if (!pdev || !platform_get_drvdata(pdev))
+               return ERR_PTR(-EPROBE_DEFER);
+       ptr = devres_alloc(devm_atmel_pmecc_put, sizeof(*ptr), GFP_KERNEL);
+       if (!ptr)
+               return ERR_PTR(-ENOMEM);
+       get_device(&pdev->dev);
+       pmecc = platform_get_drvdata(pdev);
+       *ptr = pmecc;
+       devres_add(userdev, ptr);
+       return pmecc;
+ }
+ static const int atmel_pmecc_strengths[] = { 2, 4, 8, 12, 24, 32 };
+ static struct atmel_pmecc_caps at91sam9g45_caps = {
+       .strengths = atmel_pmecc_strengths,
+       .nstrengths = 5,
+       .el_offset = 0x8c,
+ };
+ static struct atmel_pmecc_caps sama5d4_caps = {
+       .strengths = atmel_pmecc_strengths,
+       .nstrengths = 5,
+       .el_offset = 0x8c,
+       .correct_erased_chunks = true,
+ };
+ static struct atmel_pmecc_caps sama5d2_caps = {
+       .strengths = atmel_pmecc_strengths,
+       .nstrengths = 6,
+       .el_offset = 0xac,
+       .correct_erased_chunks = true,
+ };
+ static const struct of_device_id atmel_pmecc_legacy_match[] = {
+       { .compatible = "atmel,sama5d4-nand", &sama5d4_caps },
+       { .compatible = "atmel,sama5d2-nand", &sama5d2_caps },
+       { /* sentinel */ }
+ };
+ struct atmel_pmecc *devm_atmel_pmecc_get(struct device *userdev)
+ {
+       struct atmel_pmecc *pmecc;
+       struct device_node *np;
+       if (!userdev)
+               return ERR_PTR(-EINVAL);
+       if (!userdev->of_node)
+               return NULL;
+       np = of_parse_phandle(userdev->of_node, "ecc-engine", 0);
+       if (np) {
+               pmecc = atmel_pmecc_get_by_node(userdev, np);
+               of_node_put(np);
+       } else {
+               /*
+                * Support old DT bindings: in this case the PMECC iomem
+                * resources are directly defined in the user pdev at position
+                * 1 and 2. Extract all relevant information from there.
+                */
+               struct platform_device *pdev = to_platform_device(userdev);
+               const struct atmel_pmecc_caps *caps;
+               const struct of_device_id *match;
+               /* No PMECC engine available. */
+               if (!of_property_read_bool(userdev->of_node,
+                                          "atmel,has-pmecc"))
+                       return NULL;
+               caps = &at91sam9g45_caps;
+               /* Find the caps associated to the NAND dev node. */
+               match = of_match_node(atmel_pmecc_legacy_match,
+                                     userdev->of_node);
+               if (match && match->data)
+                       caps = match->data;
+               pmecc = atmel_pmecc_create(pdev, caps, 1, 2);
+       }
+       return pmecc;
+ }
+ EXPORT_SYMBOL(devm_atmel_pmecc_get);
+ static const struct of_device_id atmel_pmecc_match[] = {
+       { .compatible = "atmel,at91sam9g45-pmecc", &at91sam9g45_caps },
+       { .compatible = "atmel,sama5d4-pmecc", &sama5d4_caps },
+       { .compatible = "atmel,sama5d2-pmecc", &sama5d2_caps },
+       { /* sentinel */ }
+ };
+ MODULE_DEVICE_TABLE(of, atmel_pmecc_match);
+ static int atmel_pmecc_probe(struct platform_device *pdev)
+ {
+       struct device *dev = &pdev->dev;
+       const struct atmel_pmecc_caps *caps;
+       struct atmel_pmecc *pmecc;
+       caps = of_device_get_match_data(&pdev->dev);
+       if (!caps) {
+               dev_err(dev, "Invalid caps\n");
+               return -EINVAL;
+       }
+       pmecc = atmel_pmecc_create(pdev, caps, 0, 1);
+       if (IS_ERR(pmecc))
+               return PTR_ERR(pmecc);
+       platform_set_drvdata(pdev, pmecc);
+       return 0;
+ }
+ static struct platform_driver atmel_pmecc_driver = {
+       .driver = {
+               .name = "atmel-pmecc",
+               .of_match_table = of_match_ptr(atmel_pmecc_match),
+       },
+       .probe = atmel_pmecc_probe,
+ };
+ module_platform_driver(atmel_pmecc_driver);
+ MODULE_LICENSE("GPL");
+ MODULE_AUTHOR("Boris Brezillon <boris.brezillon@free-electrons.com>");
+ MODULE_DESCRIPTION("PMECC engine driver");
+ MODULE_ALIAS("platform:atmel_pmecc");
index 0000000,7ca678f..61aae02
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,1117 +1,1111 @@@
 -                        u32 *eccstat, unsigned int bufnum)
+ /*
+  * Freescale Integrated Flash Controller NAND driver
+  *
+  * Copyright 2011-2012 Freescale Semiconductor, Inc
+  *
+  * Author: Dipen Dudhat <Dipen.Dudhat@freescale.com>
+  *
+  * This program is free software; you can redistribute it and/or modify
+  * it under the terms of the GNU General Public License as published by
+  * the Free Software Foundation; either version 2 of the License, or
+  * (at your option) any later version.
+  *
+  * This program is distributed in the hope that it will be useful,
+  * but WITHOUT ANY WARRANTY; without even the implied warranty of
+  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  * GNU General Public License for more details.
+  *
+  * You should have received a copy of the GNU General Public License
+  * along with this program; if not, write to the Free Software
+  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+  */
+ #include <linux/module.h>
+ #include <linux/types.h>
+ #include <linux/kernel.h>
+ #include <linux/of_address.h>
+ #include <linux/slab.h>
+ #include <linux/mtd/mtd.h>
+ #include <linux/mtd/rawnand.h>
+ #include <linux/mtd/partitions.h>
+ #include <linux/mtd/nand_ecc.h>
+ #include <linux/fsl_ifc.h>
+ #define ERR_BYTE              0xFF /* Value returned for read
+                                       bytes when read failed  */
+ #define IFC_TIMEOUT_MSECS     500  /* Maximum number of mSecs to wait
+                                       for IFC NAND Machine    */
+ struct fsl_ifc_ctrl;
+ /* mtd information per set */
+ struct fsl_ifc_mtd {
+       struct nand_chip chip;
+       struct fsl_ifc_ctrl *ctrl;
+       struct device *dev;
+       int bank;               /* Chip select bank number              */
+       unsigned int bufnum_mask; /* bufnum = page & bufnum_mask */
+       u8 __iomem *vbase;      /* Chip select base virtual address     */
+ };
+ /* overview of the fsl ifc controller */
+ struct fsl_ifc_nand_ctrl {
+       struct nand_hw_control controller;
+       struct fsl_ifc_mtd *chips[FSL_IFC_BANK_COUNT];
+       void __iomem *addr;     /* Address of assigned IFC buffer       */
+       unsigned int page;      /* Last page written to / read from     */
+       unsigned int read_bytes;/* Number of bytes read during command  */
+       unsigned int column;    /* Saved column from SEQIN              */
+       unsigned int index;     /* Pointer to next byte to 'read'       */
+       unsigned int oob;       /* Non zero if operating on OOB data    */
+       unsigned int eccread;   /* Non zero for a full-page ECC read    */
+       unsigned int counter;   /* counter for the initializations      */
+       unsigned int max_bitflips;  /* Saved during READ0 cmd           */
+ };
+ static struct fsl_ifc_nand_ctrl *ifc_nand_ctrl;
+ /*
+  * Generic flash bbt descriptors
+  */
+ static u8 bbt_pattern[] = {'B', 'b', 't', '0' };
+ static u8 mirror_pattern[] = {'1', 't', 'b', 'B' };
+ 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 = 2, /* 0 on 8-bit small page */
+       .len = 4,
+       .veroffs = 6,
+       .maxblocks = 4,
+       .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 = 2, /* 0 on 8-bit small page */
+       .len = 4,
+       .veroffs = 6,
+       .maxblocks = 4,
+       .pattern = mirror_pattern,
+ };
+ static int fsl_ifc_ooblayout_ecc(struct mtd_info *mtd, int section,
+                                struct mtd_oob_region *oobregion)
+ {
+       struct nand_chip *chip = mtd_to_nand(mtd);
+       if (section)
+               return -ERANGE;
+       oobregion->offset = 8;
+       oobregion->length = chip->ecc.total;
+       return 0;
+ }
+ static int fsl_ifc_ooblayout_free(struct mtd_info *mtd, int section,
+                                 struct mtd_oob_region *oobregion)
+ {
+       struct nand_chip *chip = mtd_to_nand(mtd);
+       if (section > 1)
+               return -ERANGE;
+       if (mtd->writesize == 512 &&
+           !(chip->options & NAND_BUSWIDTH_16)) {
+               if (!section) {
+                       oobregion->offset = 0;
+                       oobregion->length = 5;
+               } else {
+                       oobregion->offset = 6;
+                       oobregion->length = 2;
+               }
+               return 0;
+       }
+       if (!section) {
+               oobregion->offset = 2;
+               oobregion->length = 6;
+       } else {
+               oobregion->offset = chip->ecc.total + 8;
+               oobregion->length = mtd->oobsize - oobregion->offset;
+       }
+       return 0;
+ }
+ static const struct mtd_ooblayout_ops fsl_ifc_ooblayout_ops = {
+       .ecc = fsl_ifc_ooblayout_ecc,
+       .free = fsl_ifc_ooblayout_free,
+ };
+ /*
+  * Set up the IFC hardware block and page address fields, and the ifc nand
+  * structure addr field to point to the correct IFC buffer in memory
+  */
+ static void set_addr(struct mtd_info *mtd, int column, int page_addr, int oob)
+ {
+       struct nand_chip *chip = mtd_to_nand(mtd);
+       struct fsl_ifc_mtd *priv = nand_get_controller_data(chip);
+       struct fsl_ifc_ctrl *ctrl = priv->ctrl;
+       struct fsl_ifc_runtime __iomem *ifc = ctrl->rregs;
+       int buf_num;
+       ifc_nand_ctrl->page = page_addr;
+       /* Program ROW0/COL0 */
+       ifc_out32(page_addr, &ifc->ifc_nand.row0);
+       ifc_out32((oob ? IFC_NAND_COL_MS : 0) | column, &ifc->ifc_nand.col0);
+       buf_num = page_addr & priv->bufnum_mask;
+       ifc_nand_ctrl->addr = priv->vbase + buf_num * (mtd->writesize * 2);
+       ifc_nand_ctrl->index = column;
+       /* for OOB data point to the second half of the buffer */
+       if (oob)
+               ifc_nand_ctrl->index += mtd->writesize;
+ }
+ /* returns nonzero if entire page is blank */
+ static int check_read_ecc(struct mtd_info *mtd, struct fsl_ifc_ctrl *ctrl,
 -      u32 reg = eccstat[bufnum / 4];
 -      int errors;
 -
 -      errors = (reg >> ((3 - bufnum % 4) * 8)) & 15;
 -
 -      return errors;
++                        u32 eccstat, unsigned int bufnum)
+ {
 -      u32 eccstat[4];
++      return  (eccstat >> ((3 - bufnum % 4) * 8)) & 15;
+ }
+ /*
+  * execute IFC NAND command and wait for it to complete
+  */
+ static void fsl_ifc_run_command(struct mtd_info *mtd)
+ {
+       struct nand_chip *chip = mtd_to_nand(mtd);
+       struct fsl_ifc_mtd *priv = nand_get_controller_data(chip);
+       struct fsl_ifc_ctrl *ctrl = priv->ctrl;
+       struct fsl_ifc_nand_ctrl *nctrl = ifc_nand_ctrl;
+       struct fsl_ifc_runtime __iomem *ifc = ctrl->rregs;
 -              int sector = bufnum * chip->ecc.steps;
 -              int sector_end = sector + chip->ecc.steps - 1;
++      u32 eccstat;
+       int i;
+       /* set the chip select for NAND Transaction */
+       ifc_out32(priv->bank << IFC_NAND_CSEL_SHIFT,
+                 &ifc->ifc_nand.nand_csel);
+       dev_vdbg(priv->dev,
+                       "%s: fir0=%08x fcr0=%08x\n",
+                       __func__,
+                       ifc_in32(&ifc->ifc_nand.nand_fir0),
+                       ifc_in32(&ifc->ifc_nand.nand_fcr0));
+       ctrl->nand_stat = 0;
+       /* start read/write seq */
+       ifc_out32(IFC_NAND_SEQ_STRT_FIR_STRT, &ifc->ifc_nand.nandseq_strt);
+       /* wait for command complete flag or timeout */
+       wait_event_timeout(ctrl->nand_wait, ctrl->nand_stat,
+                          msecs_to_jiffies(IFC_TIMEOUT_MSECS));
+       /* ctrl->nand_stat will be updated from IRQ context */
+       if (!ctrl->nand_stat)
+               dev_err(priv->dev, "Controller is not responding\n");
+       if (ctrl->nand_stat & IFC_NAND_EVTER_STAT_FTOER)
+               dev_err(priv->dev, "NAND Flash Timeout Error\n");
+       if (ctrl->nand_stat & IFC_NAND_EVTER_STAT_WPER)
+               dev_err(priv->dev, "NAND Flash Write Protect Error\n");
+       nctrl->max_bitflips = 0;
+       if (nctrl->eccread) {
+               int errors;
+               int bufnum = nctrl->page & priv->bufnum_mask;
 -              if (ctrl->version >= FSL_IFC_VERSION_2_0_0)
 -                      eccstat_regs = ifc->ifc_nand.v2_nand_eccstat;
 -              else
 -                      eccstat_regs = ifc->ifc_nand.v1_nand_eccstat;
++              int sector_start = bufnum * chip->ecc.steps;
++              int sector_end = sector_start + chip->ecc.steps - 1;
+               __be32 *eccstat_regs;
 -              for (i = sector / 4; i <= sector_end / 4; i++)
 -                      eccstat[i] = ifc_in32(&eccstat_regs[i]);
++              eccstat_regs = ifc->ifc_nand.nand_eccstat;
++              eccstat = ifc_in32(&eccstat_regs[sector_start / 4]);
 -              for (i = sector; i <= sector_end; i++) {
++              for (i = sector_start; i <= sector_end; i++) {
++                      if (i != sector_start && !(i % 4))
++                              eccstat = ifc_in32(&eccstat_regs[i / 4]);
 -
+                       errors = check_read_ecc(mtd, ctrl, eccstat, i);
+                       if (errors == 15) {
+                               /*
+                                * Uncorrectable error.
+                                * We'll check for blank pages later.
+                                *
+                                * We disable ECCER reporting due to...
+                                * erratum IFC-A002770 -- so report it now if we
+                                * see an uncorrectable error in ECCSTAT.
+                                */
+                               ctrl->nand_stat |= IFC_NAND_EVTER_STAT_ECCER;
+                               continue;
+                       }
+                       mtd->ecc_stats.corrected += errors;
+                       nctrl->max_bitflips = max_t(unsigned int,
+                                                   nctrl->max_bitflips,
+                                                   errors);
+               }
+               nctrl->eccread = 0;
+       }
+ }
+ static void fsl_ifc_do_read(struct nand_chip *chip,
+                           int oob,
+                           struct mtd_info *mtd)
+ {
+       struct fsl_ifc_mtd *priv = nand_get_controller_data(chip);
+       struct fsl_ifc_ctrl *ctrl = priv->ctrl;
+       struct fsl_ifc_runtime __iomem *ifc = ctrl->rregs;
+       /* Program FIR/IFC_NAND_FCR0 for Small/Large page */
+       if (mtd->writesize > 512) {
+               ifc_out32((IFC_FIR_OP_CW0 << IFC_NAND_FIR0_OP0_SHIFT) |
+                         (IFC_FIR_OP_CA0 << IFC_NAND_FIR0_OP1_SHIFT) |
+                         (IFC_FIR_OP_RA0 << IFC_NAND_FIR0_OP2_SHIFT) |
+                         (IFC_FIR_OP_CMD1 << IFC_NAND_FIR0_OP3_SHIFT) |
+                         (IFC_FIR_OP_RBCD << IFC_NAND_FIR0_OP4_SHIFT),
+                         &ifc->ifc_nand.nand_fir0);
+               ifc_out32(0x0, &ifc->ifc_nand.nand_fir1);
+               ifc_out32((NAND_CMD_READ0 << IFC_NAND_FCR0_CMD0_SHIFT) |
+                         (NAND_CMD_READSTART << IFC_NAND_FCR0_CMD1_SHIFT),
+                         &ifc->ifc_nand.nand_fcr0);
+       } else {
+               ifc_out32((IFC_FIR_OP_CW0 << IFC_NAND_FIR0_OP0_SHIFT) |
+                         (IFC_FIR_OP_CA0 << IFC_NAND_FIR0_OP1_SHIFT) |
+                         (IFC_FIR_OP_RA0  << IFC_NAND_FIR0_OP2_SHIFT) |
+                         (IFC_FIR_OP_RBCD << IFC_NAND_FIR0_OP3_SHIFT),
+                         &ifc->ifc_nand.nand_fir0);
+               ifc_out32(0x0, &ifc->ifc_nand.nand_fir1);
+               if (oob)
+                       ifc_out32(NAND_CMD_READOOB <<
+                                 IFC_NAND_FCR0_CMD0_SHIFT,
+                                 &ifc->ifc_nand.nand_fcr0);
+               else
+                       ifc_out32(NAND_CMD_READ0 <<
+                                 IFC_NAND_FCR0_CMD0_SHIFT,
+                                 &ifc->ifc_nand.nand_fcr0);
+       }
+ }
+ /* cmdfunc send commands to the IFC NAND Machine */
+ static void fsl_ifc_cmdfunc(struct mtd_info *mtd, unsigned int command,
+                            int column, int page_addr) {
+       struct nand_chip *chip = mtd_to_nand(mtd);
+       struct fsl_ifc_mtd *priv = nand_get_controller_data(chip);
+       struct fsl_ifc_ctrl *ctrl = priv->ctrl;
+       struct fsl_ifc_runtime __iomem *ifc = ctrl->rregs;
+       /* clear the read buffer */
+       ifc_nand_ctrl->read_bytes = 0;
+       if (command != NAND_CMD_PAGEPROG)
+               ifc_nand_ctrl->index = 0;
+       switch (command) {
+       /* READ0 read the entire buffer to use hardware ECC. */
+       case NAND_CMD_READ0:
+               ifc_out32(0, &ifc->ifc_nand.nand_fbcr);
+               set_addr(mtd, 0, page_addr, 0);
+               ifc_nand_ctrl->read_bytes = mtd->writesize + mtd->oobsize;
+               ifc_nand_ctrl->index += column;
+               if (chip->ecc.mode == NAND_ECC_HW)
+                       ifc_nand_ctrl->eccread = 1;
+               fsl_ifc_do_read(chip, 0, mtd);
+               fsl_ifc_run_command(mtd);
+               return;
+       /* READOOB reads only the OOB because no ECC is performed. */
+       case NAND_CMD_READOOB:
+               ifc_out32(mtd->oobsize - column, &ifc->ifc_nand.nand_fbcr);
+               set_addr(mtd, column, page_addr, 1);
+               ifc_nand_ctrl->read_bytes = mtd->writesize + mtd->oobsize;
+               fsl_ifc_do_read(chip, 1, mtd);
+               fsl_ifc_run_command(mtd);
+               return;
+       case NAND_CMD_READID:
+       case NAND_CMD_PARAM: {
+               int timing = IFC_FIR_OP_RB;
+               if (command == NAND_CMD_PARAM)
+                       timing = IFC_FIR_OP_RBCD;
+               ifc_out32((IFC_FIR_OP_CW0 << IFC_NAND_FIR0_OP0_SHIFT) |
+                         (IFC_FIR_OP_UA  << IFC_NAND_FIR0_OP1_SHIFT) |
+                         (timing << IFC_NAND_FIR0_OP2_SHIFT),
+                         &ifc->ifc_nand.nand_fir0);
+               ifc_out32(command << IFC_NAND_FCR0_CMD0_SHIFT,
+                         &ifc->ifc_nand.nand_fcr0);
+               ifc_out32(column, &ifc->ifc_nand.row3);
+               /*
+                * although currently it's 8 bytes for READID, we always read
+                * the maximum 256 bytes(for PARAM)
+                */
+               ifc_out32(256, &ifc->ifc_nand.nand_fbcr);
+               ifc_nand_ctrl->read_bytes = 256;
+               set_addr(mtd, 0, 0, 0);
+               fsl_ifc_run_command(mtd);
+               return;
+       }
+       /* ERASE1 stores the block and page address */
+       case NAND_CMD_ERASE1:
+               set_addr(mtd, 0, page_addr, 0);
+               return;
+       /* ERASE2 uses the block and page address from ERASE1 */
+       case NAND_CMD_ERASE2:
+               ifc_out32((IFC_FIR_OP_CW0 << IFC_NAND_FIR0_OP0_SHIFT) |
+                         (IFC_FIR_OP_RA0 << IFC_NAND_FIR0_OP1_SHIFT) |
+                         (IFC_FIR_OP_CMD1 << IFC_NAND_FIR0_OP2_SHIFT),
+                         &ifc->ifc_nand.nand_fir0);
+               ifc_out32((NAND_CMD_ERASE1 << IFC_NAND_FCR0_CMD0_SHIFT) |
+                         (NAND_CMD_ERASE2 << IFC_NAND_FCR0_CMD1_SHIFT),
+                         &ifc->ifc_nand.nand_fcr0);
+               ifc_out32(0, &ifc->ifc_nand.nand_fbcr);
+               ifc_nand_ctrl->read_bytes = 0;
+               fsl_ifc_run_command(mtd);
+               return;
+       /* SEQIN sets up the addr buffer and all registers except the length */
+       case NAND_CMD_SEQIN: {
+               u32 nand_fcr0;
+               ifc_nand_ctrl->column = column;
+               ifc_nand_ctrl->oob = 0;
+               if (mtd->writesize > 512) {
+                       nand_fcr0 =
+                               (NAND_CMD_SEQIN << IFC_NAND_FCR0_CMD0_SHIFT) |
+                               (NAND_CMD_STATUS << IFC_NAND_FCR0_CMD1_SHIFT) |
+                               (NAND_CMD_PAGEPROG << IFC_NAND_FCR0_CMD2_SHIFT);
+                       ifc_out32(
+                               (IFC_FIR_OP_CW0 << IFC_NAND_FIR0_OP0_SHIFT) |
+                               (IFC_FIR_OP_CA0 << IFC_NAND_FIR0_OP1_SHIFT) |
+                               (IFC_FIR_OP_RA0 << IFC_NAND_FIR0_OP2_SHIFT) |
+                               (IFC_FIR_OP_WBCD << IFC_NAND_FIR0_OP3_SHIFT) |
+                               (IFC_FIR_OP_CMD2 << IFC_NAND_FIR0_OP4_SHIFT),
+                               &ifc->ifc_nand.nand_fir0);
+                       ifc_out32(
+                               (IFC_FIR_OP_CW1 << IFC_NAND_FIR1_OP5_SHIFT) |
+                               (IFC_FIR_OP_RDSTAT << IFC_NAND_FIR1_OP6_SHIFT) |
+                               (IFC_FIR_OP_NOP << IFC_NAND_FIR1_OP7_SHIFT),
+                               &ifc->ifc_nand.nand_fir1);
+               } else {
+                       nand_fcr0 = ((NAND_CMD_PAGEPROG <<
+                                       IFC_NAND_FCR0_CMD1_SHIFT) |
+                                   (NAND_CMD_SEQIN <<
+                                       IFC_NAND_FCR0_CMD2_SHIFT) |
+                                   (NAND_CMD_STATUS <<
+                                       IFC_NAND_FCR0_CMD3_SHIFT));
+                       ifc_out32(
+                               (IFC_FIR_OP_CW0 << IFC_NAND_FIR0_OP0_SHIFT) |
+                               (IFC_FIR_OP_CMD2 << IFC_NAND_FIR0_OP1_SHIFT) |
+                               (IFC_FIR_OP_CA0 << IFC_NAND_FIR0_OP2_SHIFT) |
+                               (IFC_FIR_OP_RA0 << IFC_NAND_FIR0_OP3_SHIFT) |
+                               (IFC_FIR_OP_WBCD << IFC_NAND_FIR0_OP4_SHIFT),
+                               &ifc->ifc_nand.nand_fir0);
+                       ifc_out32(
+                               (IFC_FIR_OP_CMD1 << IFC_NAND_FIR1_OP5_SHIFT) |
+                               (IFC_FIR_OP_CW3 << IFC_NAND_FIR1_OP6_SHIFT) |
+                               (IFC_FIR_OP_RDSTAT << IFC_NAND_FIR1_OP7_SHIFT) |
+                               (IFC_FIR_OP_NOP << IFC_NAND_FIR1_OP8_SHIFT),
+                               &ifc->ifc_nand.nand_fir1);
+                       if (column >= mtd->writesize)
+                               nand_fcr0 |=
+                               NAND_CMD_READOOB << IFC_NAND_FCR0_CMD0_SHIFT;
+                       else
+                               nand_fcr0 |=
+                               NAND_CMD_READ0 << IFC_NAND_FCR0_CMD0_SHIFT;
+               }
+               if (column >= mtd->writesize) {
+                       /* OOB area --> READOOB */
+                       column -= mtd->writesize;
+                       ifc_nand_ctrl->oob = 1;
+               }
+               ifc_out32(nand_fcr0, &ifc->ifc_nand.nand_fcr0);
+               set_addr(mtd, column, page_addr, ifc_nand_ctrl->oob);
+               return;
+       }
+       /* PAGEPROG reuses all of the setup from SEQIN and adds the length */
+       case NAND_CMD_PAGEPROG: {
+               if (ifc_nand_ctrl->oob) {
+                       ifc_out32(ifc_nand_ctrl->index -
+                                 ifc_nand_ctrl->column,
+                                 &ifc->ifc_nand.nand_fbcr);
+               } else {
+                       ifc_out32(0, &ifc->ifc_nand.nand_fbcr);
+               }
+               fsl_ifc_run_command(mtd);
+               return;
+       }
+       case NAND_CMD_STATUS: {
+               void __iomem *addr;
+               ifc_out32((IFC_FIR_OP_CW0 << IFC_NAND_FIR0_OP0_SHIFT) |
+                         (IFC_FIR_OP_RB << IFC_NAND_FIR0_OP1_SHIFT),
+                         &ifc->ifc_nand.nand_fir0);
+               ifc_out32(NAND_CMD_STATUS << IFC_NAND_FCR0_CMD0_SHIFT,
+                         &ifc->ifc_nand.nand_fcr0);
+               ifc_out32(1, &ifc->ifc_nand.nand_fbcr);
+               set_addr(mtd, 0, 0, 0);
+               ifc_nand_ctrl->read_bytes = 1;
+               fsl_ifc_run_command(mtd);
+               /*
+                * The chip always seems to report that it is
+                * write-protected, even when it is not.
+                */
+               addr = ifc_nand_ctrl->addr;
+               if (chip->options & NAND_BUSWIDTH_16)
+                       ifc_out16(ifc_in16(addr) | (NAND_STATUS_WP), addr);
+               else
+                       ifc_out8(ifc_in8(addr) | (NAND_STATUS_WP), addr);
+               return;
+       }
+       case NAND_CMD_RESET:
+               ifc_out32(IFC_FIR_OP_CW0 << IFC_NAND_FIR0_OP0_SHIFT,
+                         &ifc->ifc_nand.nand_fir0);
+               ifc_out32(NAND_CMD_RESET << IFC_NAND_FCR0_CMD0_SHIFT,
+                         &ifc->ifc_nand.nand_fcr0);
+               fsl_ifc_run_command(mtd);
+               return;
+       default:
+               dev_err(priv->dev, "%s: error, unsupported command 0x%x.\n",
+                                       __func__, command);
+       }
+ }
+ static void fsl_ifc_select_chip(struct mtd_info *mtd, int chip)
+ {
+       /* The hardware does not seem to support multiple
+        * chips per bank.
+        */
+ }
+ /*
+  * Write buf to the IFC NAND Controller Data Buffer
+  */
+ static void fsl_ifc_write_buf(struct mtd_info *mtd, const u8 *buf, int len)
+ {
+       struct nand_chip *chip = mtd_to_nand(mtd);
+       struct fsl_ifc_mtd *priv = nand_get_controller_data(chip);
+       unsigned int bufsize = mtd->writesize + mtd->oobsize;
+       if (len <= 0) {
+               dev_err(priv->dev, "%s: len %d bytes", __func__, len);
+               return;
+       }
+       if ((unsigned int)len > bufsize - ifc_nand_ctrl->index) {
+               dev_err(priv->dev,
+                       "%s: beyond end of buffer (%d requested, %u available)\n",
+                       __func__, len, bufsize - ifc_nand_ctrl->index);
+               len = bufsize - ifc_nand_ctrl->index;
+       }
+       memcpy_toio(ifc_nand_ctrl->addr + ifc_nand_ctrl->index, buf, len);
+       ifc_nand_ctrl->index += len;
+ }
+ /*
+  * Read a byte from either the IFC hardware buffer
+  * read function for 8-bit buswidth
+  */
+ static uint8_t fsl_ifc_read_byte(struct mtd_info *mtd)
+ {
+       struct nand_chip *chip = mtd_to_nand(mtd);
+       struct fsl_ifc_mtd *priv = nand_get_controller_data(chip);
+       unsigned int offset;
+       /*
+        * If there are still bytes in the IFC buffer, then use the
+        * next byte.
+        */
+       if (ifc_nand_ctrl->index < ifc_nand_ctrl->read_bytes) {
+               offset = ifc_nand_ctrl->index++;
+               return ifc_in8(ifc_nand_ctrl->addr + offset);
+       }
+       dev_err(priv->dev, "%s: beyond end of buffer\n", __func__);
+       return ERR_BYTE;
+ }
+ /*
+  * Read two bytes from the IFC hardware buffer
+  * read function for 16-bit buswith
+  */
+ static uint8_t fsl_ifc_read_byte16(struct mtd_info *mtd)
+ {
+       struct nand_chip *chip = mtd_to_nand(mtd);
+       struct fsl_ifc_mtd *priv = nand_get_controller_data(chip);
+       uint16_t data;
+       /*
+        * If there are still bytes in the IFC buffer, then use the
+        * next byte.
+        */
+       if (ifc_nand_ctrl->index < ifc_nand_ctrl->read_bytes) {
+               data = ifc_in16(ifc_nand_ctrl->addr + ifc_nand_ctrl->index);
+               ifc_nand_ctrl->index += 2;
+               return (uint8_t) data;
+       }
+       dev_err(priv->dev, "%s: beyond end of buffer\n", __func__);
+       return ERR_BYTE;
+ }
+ /*
+  * Read from the IFC Controller Data Buffer
+  */
+ static void fsl_ifc_read_buf(struct mtd_info *mtd, u8 *buf, int len)
+ {
+       struct nand_chip *chip = mtd_to_nand(mtd);
+       struct fsl_ifc_mtd *priv = nand_get_controller_data(chip);
+       int avail;
+       if (len < 0) {
+               dev_err(priv->dev, "%s: len %d bytes", __func__, len);
+               return;
+       }
+       avail = min((unsigned int)len,
+                       ifc_nand_ctrl->read_bytes - ifc_nand_ctrl->index);
+       memcpy_fromio(buf, ifc_nand_ctrl->addr + ifc_nand_ctrl->index, avail);
+       ifc_nand_ctrl->index += avail;
+       if (len > avail)
+               dev_err(priv->dev,
+                       "%s: beyond end of buffer (%d requested, %d available)\n",
+                       __func__, len, avail);
+ }
+ /*
+  * This function is called after Program and Erase Operations to
+  * check for success or failure.
+  */
+ static int fsl_ifc_wait(struct mtd_info *mtd, struct nand_chip *chip)
+ {
+       struct fsl_ifc_mtd *priv = nand_get_controller_data(chip);
+       struct fsl_ifc_ctrl *ctrl = priv->ctrl;
+       struct fsl_ifc_runtime __iomem *ifc = ctrl->rregs;
+       u32 nand_fsr;
++      int status;
+       /* Use READ_STATUS command, but wait for the device to be ready */
+       ifc_out32((IFC_FIR_OP_CW0 << IFC_NAND_FIR0_OP0_SHIFT) |
+                 (IFC_FIR_OP_RDSTAT << IFC_NAND_FIR0_OP1_SHIFT),
+                 &ifc->ifc_nand.nand_fir0);
+       ifc_out32(NAND_CMD_STATUS << IFC_NAND_FCR0_CMD0_SHIFT,
+                 &ifc->ifc_nand.nand_fcr0);
+       ifc_out32(1, &ifc->ifc_nand.nand_fbcr);
+       set_addr(mtd, 0, 0, 0);
+       ifc_nand_ctrl->read_bytes = 1;
+       fsl_ifc_run_command(mtd);
+       nand_fsr = ifc_in32(&ifc->ifc_nand.nand_fsr);
 -      return nand_fsr | NAND_STATUS_WP;
++      status = nand_fsr >> 24;
+       /*
+        * The chip always seems to report that it is
+        * write-protected, even when it is not.
+        */
++      return status | NAND_STATUS_WP;
+ }
+ /*
+  * The controller does not check for bitflips in erased pages,
+  * therefore software must check instead.
+  */
+ static int check_erased_page(struct nand_chip *chip, u8 *buf)
+ {
+       struct mtd_info *mtd = nand_to_mtd(chip);
+       u8 *ecc = chip->oob_poi;
+       const int ecc_size = chip->ecc.bytes;
+       const int pkt_size = chip->ecc.size;
+       int i, res, bitflips = 0;
+       struct mtd_oob_region oobregion = { };
+       mtd_ooblayout_ecc(mtd, 0, &oobregion);
+       ecc += oobregion.offset;
+       for (i = 0; i < chip->ecc.steps; ++i) {
+               res = nand_check_erased_ecc_chunk(buf, pkt_size, ecc, ecc_size,
+                                                 NULL, 0,
+                                                 chip->ecc.strength);
+               if (res < 0)
+                       mtd->ecc_stats.failed++;
+               else
+                       mtd->ecc_stats.corrected += res;
+               bitflips = max(res, bitflips);
+               buf += pkt_size;
+               ecc += ecc_size;
+       }
+       return bitflips;
+ }
+ static int fsl_ifc_read_page(struct mtd_info *mtd, struct nand_chip *chip,
+                            uint8_t *buf, int oob_required, int page)
+ {
+       struct fsl_ifc_mtd *priv = nand_get_controller_data(chip);
+       struct fsl_ifc_ctrl *ctrl = priv->ctrl;
+       struct fsl_ifc_nand_ctrl *nctrl = ifc_nand_ctrl;
+       nand_read_page_op(chip, page, 0, buf, mtd->writesize);
+       if (oob_required)
+               fsl_ifc_read_buf(mtd, chip->oob_poi, mtd->oobsize);
+       if (ctrl->nand_stat & IFC_NAND_EVTER_STAT_ECCER) {
+               if (!oob_required)
+                       fsl_ifc_read_buf(mtd, chip->oob_poi, mtd->oobsize);
+               return check_erased_page(chip, buf);
+       }
+       if (ctrl->nand_stat != IFC_NAND_EVTER_STAT_OPC)
+               mtd->ecc_stats.failed++;
+       return nctrl->max_bitflips;
+ }
+ /* ECC will be calculated automatically, and errors will be detected in
+  * waitfunc.
+  */
+ static int fsl_ifc_write_page(struct mtd_info *mtd, struct nand_chip *chip,
+                              const uint8_t *buf, int oob_required, int page)
+ {
+       nand_prog_page_begin_op(chip, page, 0, buf, mtd->writesize);
+       fsl_ifc_write_buf(mtd, chip->oob_poi, mtd->oobsize);
+       return nand_prog_page_end_op(chip);
+ }
+ static int fsl_ifc_chip_init_tail(struct mtd_info *mtd)
+ {
+       struct nand_chip *chip = mtd_to_nand(mtd);
+       struct fsl_ifc_mtd *priv = nand_get_controller_data(chip);
+       dev_dbg(priv->dev, "%s: nand->numchips = %d\n", __func__,
+                                                       chip->numchips);
+       dev_dbg(priv->dev, "%s: nand->chipsize = %lld\n", __func__,
+                                                       chip->chipsize);
+       dev_dbg(priv->dev, "%s: nand->pagemask = %8x\n", __func__,
+                                                       chip->pagemask);
+       dev_dbg(priv->dev, "%s: nand->chip_delay = %d\n", __func__,
+                                                       chip->chip_delay);
+       dev_dbg(priv->dev, "%s: nand->badblockpos = %d\n", __func__,
+                                                       chip->badblockpos);
+       dev_dbg(priv->dev, "%s: nand->chip_shift = %d\n", __func__,
+                                                       chip->chip_shift);
+       dev_dbg(priv->dev, "%s: nand->page_shift = %d\n", __func__,
+                                                       chip->page_shift);
+       dev_dbg(priv->dev, "%s: nand->phys_erase_shift = %d\n", __func__,
+                                                       chip->phys_erase_shift);
+       dev_dbg(priv->dev, "%s: nand->ecc.mode = %d\n", __func__,
+                                                       chip->ecc.mode);
+       dev_dbg(priv->dev, "%s: nand->ecc.steps = %d\n", __func__,
+                                                       chip->ecc.steps);
+       dev_dbg(priv->dev, "%s: nand->ecc.bytes = %d\n", __func__,
+                                                       chip->ecc.bytes);
+       dev_dbg(priv->dev, "%s: nand->ecc.total = %d\n", __func__,
+                                                       chip->ecc.total);
+       dev_dbg(priv->dev, "%s: mtd->ooblayout = %p\n", __func__,
+                                                       mtd->ooblayout);
+       dev_dbg(priv->dev, "%s: mtd->flags = %08x\n", __func__, mtd->flags);
+       dev_dbg(priv->dev, "%s: mtd->size = %lld\n", __func__, mtd->size);
+       dev_dbg(priv->dev, "%s: mtd->erasesize = %d\n", __func__,
+                                                       mtd->erasesize);
+       dev_dbg(priv->dev, "%s: mtd->writesize = %d\n", __func__,
+                                                       mtd->writesize);
+       dev_dbg(priv->dev, "%s: mtd->oobsize = %d\n", __func__,
+                                                       mtd->oobsize);
+       return 0;
+ }
+ static void fsl_ifc_sram_init(struct fsl_ifc_mtd *priv)
+ {
+       struct fsl_ifc_ctrl *ctrl = priv->ctrl;
+       struct fsl_ifc_runtime __iomem *ifc_runtime = ctrl->rregs;
+       struct fsl_ifc_global __iomem *ifc_global = ctrl->gregs;
+       uint32_t csor = 0, csor_8k = 0, csor_ext = 0;
+       uint32_t cs = priv->bank;
+       /* Save CSOR and CSOR_ext */
+       csor = ifc_in32(&ifc_global->csor_cs[cs].csor);
+       csor_ext = ifc_in32(&ifc_global->csor_cs[cs].csor_ext);
+       /* chage PageSize 8K and SpareSize 1K*/
+       csor_8k = (csor & ~(CSOR_NAND_PGS_MASK)) | 0x0018C000;
+       ifc_out32(csor_8k, &ifc_global->csor_cs[cs].csor);
+       ifc_out32(0x0000400, &ifc_global->csor_cs[cs].csor_ext);
+       /* READID */
+       ifc_out32((IFC_FIR_OP_CW0 << IFC_NAND_FIR0_OP0_SHIFT) |
+                   (IFC_FIR_OP_UA  << IFC_NAND_FIR0_OP1_SHIFT) |
+                   (IFC_FIR_OP_RB << IFC_NAND_FIR0_OP2_SHIFT),
+                   &ifc_runtime->ifc_nand.nand_fir0);
+       ifc_out32(NAND_CMD_READID << IFC_NAND_FCR0_CMD0_SHIFT,
+                   &ifc_runtime->ifc_nand.nand_fcr0);
+       ifc_out32(0x0, &ifc_runtime->ifc_nand.row3);
+       ifc_out32(0x0, &ifc_runtime->ifc_nand.nand_fbcr);
+       /* Program ROW0/COL0 */
+       ifc_out32(0x0, &ifc_runtime->ifc_nand.row0);
+       ifc_out32(0x0, &ifc_runtime->ifc_nand.col0);
+       /* set the chip select for NAND Transaction */
+       ifc_out32(cs << IFC_NAND_CSEL_SHIFT,
+               &ifc_runtime->ifc_nand.nand_csel);
+       /* start read seq */
+       ifc_out32(IFC_NAND_SEQ_STRT_FIR_STRT,
+               &ifc_runtime->ifc_nand.nandseq_strt);
+       /* wait for command complete flag or timeout */
+       wait_event_timeout(ctrl->nand_wait, ctrl->nand_stat,
+                          msecs_to_jiffies(IFC_TIMEOUT_MSECS));
+       if (ctrl->nand_stat != IFC_NAND_EVTER_STAT_OPC)
+               pr_err("fsl-ifc: Failed to Initialise SRAM\n");
+       /* Restore CSOR and CSOR_ext */
+       ifc_out32(csor, &ifc_global->csor_cs[cs].csor);
+       ifc_out32(csor_ext, &ifc_global->csor_cs[cs].csor_ext);
+ }
+ static int fsl_ifc_chip_init(struct fsl_ifc_mtd *priv)
+ {
+       struct fsl_ifc_ctrl *ctrl = priv->ctrl;
+       struct fsl_ifc_global __iomem *ifc_global = ctrl->gregs;
+       struct fsl_ifc_runtime __iomem *ifc_runtime = ctrl->rregs;
+       struct nand_chip *chip = &priv->chip;
+       struct mtd_info *mtd = nand_to_mtd(&priv->chip);
+       u32 csor;
+       /* Fill in fsl_ifc_mtd structure */
+       mtd->dev.parent = priv->dev;
+       nand_set_flash_node(chip, priv->dev->of_node);
+       /* fill in nand_chip structure */
+       /* set up function call table */
+       if ((ifc_in32(&ifc_global->cspr_cs[priv->bank].cspr))
+               & CSPR_PORT_SIZE_16)
+               chip->read_byte = fsl_ifc_read_byte16;
+       else
+               chip->read_byte = fsl_ifc_read_byte;
+       chip->write_buf = fsl_ifc_write_buf;
+       chip->read_buf = fsl_ifc_read_buf;
+       chip->select_chip = fsl_ifc_select_chip;
+       chip->cmdfunc = fsl_ifc_cmdfunc;
+       chip->waitfunc = fsl_ifc_wait;
+       chip->set_features = nand_get_set_features_notsupp;
+       chip->get_features = nand_get_set_features_notsupp;
+       chip->bbt_td = &bbt_main_descr;
+       chip->bbt_md = &bbt_mirror_descr;
+       ifc_out32(0x0, &ifc_runtime->ifc_nand.ncfgr);
+       /* set up nand options */
+       chip->bbt_options = NAND_BBT_USE_FLASH;
+       chip->options = NAND_NO_SUBPAGE_WRITE;
+       if (ifc_in32(&ifc_global->cspr_cs[priv->bank].cspr)
+               & CSPR_PORT_SIZE_16) {
+               chip->read_byte = fsl_ifc_read_byte16;
+               chip->options |= NAND_BUSWIDTH_16;
+       } else {
+               chip->read_byte = fsl_ifc_read_byte;
+       }
+       chip->controller = &ifc_nand_ctrl->controller;
+       nand_set_controller_data(chip, priv);
+       chip->ecc.read_page = fsl_ifc_read_page;
+       chip->ecc.write_page = fsl_ifc_write_page;
+       csor = ifc_in32(&ifc_global->csor_cs[priv->bank].csor);
+       switch (csor & CSOR_NAND_PGS_MASK) {
+       case CSOR_NAND_PGS_512:
+               if (!(chip->options & NAND_BUSWIDTH_16)) {
+                       /* Avoid conflict with bad block marker */
+                       bbt_main_descr.offs = 0;
+                       bbt_mirror_descr.offs = 0;
+               }
+               priv->bufnum_mask = 15;
+               break;
+       case CSOR_NAND_PGS_2K:
+               priv->bufnum_mask = 3;
+               break;
+       case CSOR_NAND_PGS_4K:
+               priv->bufnum_mask = 1;
+               break;
+       case CSOR_NAND_PGS_8K:
+               priv->bufnum_mask = 0;
+               break;
+       default:
+               dev_err(priv->dev, "bad csor %#x: bad page size\n", csor);
+               return -ENODEV;
+       }
+       /* Must also set CSOR_NAND_ECC_ENC_EN if DEC_EN set */
+       if (csor & CSOR_NAND_ECC_DEC_EN) {
+               chip->ecc.mode = NAND_ECC_HW;
+               mtd_set_ooblayout(mtd, &fsl_ifc_ooblayout_ops);
+               /* Hardware generates ECC per 512 Bytes */
+               chip->ecc.size = 512;
+               if ((csor & CSOR_NAND_ECC_MODE_MASK) == CSOR_NAND_ECC_MODE_4) {
+                       chip->ecc.bytes = 8;
+                       chip->ecc.strength = 4;
+               } else {
+                       chip->ecc.bytes = 16;
+                       chip->ecc.strength = 8;
+               }
+       } else {
+               chip->ecc.mode = NAND_ECC_SOFT;
+               chip->ecc.algo = NAND_ECC_HAMMING;
+       }
+       if (ctrl->version >= FSL_IFC_VERSION_1_1_0)
+               fsl_ifc_sram_init(priv);
+       /*
+        * As IFC version 2.0.0 has 16KB of internal SRAM as compared to older
+        * versions which had 8KB. Hence bufnum mask needs to be updated.
+        */
+       if (ctrl->version >= FSL_IFC_VERSION_2_0_0)
+               priv->bufnum_mask = (priv->bufnum_mask * 2) + 1;
+       return 0;
+ }
+ static int fsl_ifc_chip_remove(struct fsl_ifc_mtd *priv)
+ {
+       struct mtd_info *mtd = nand_to_mtd(&priv->chip);
+       nand_release(mtd);
+       kfree(mtd->name);
+       if (priv->vbase)
+               iounmap(priv->vbase);
+       ifc_nand_ctrl->chips[priv->bank] = NULL;
+       return 0;
+ }
+ static int match_bank(struct fsl_ifc_global __iomem *ifc_global, int bank,
+                     phys_addr_t addr)
+ {
+       u32 cspr = ifc_in32(&ifc_global->cspr_cs[bank].cspr);
+       if (!(cspr & CSPR_V))
+               return 0;
+       if ((cspr & CSPR_MSEL) != CSPR_MSEL_NAND)
+               return 0;
+       return (cspr & CSPR_BA) == convert_ifc_address(addr);
+ }
+ static DEFINE_MUTEX(fsl_ifc_nand_mutex);
+ static int fsl_ifc_nand_probe(struct platform_device *dev)
+ {
+       struct fsl_ifc_runtime __iomem *ifc;
+       struct fsl_ifc_mtd *priv;
+       struct resource res;
+       static const char *part_probe_types[]
+               = { "cmdlinepart", "RedBoot", "ofpart", NULL };
+       int ret;
+       int bank;
+       struct device_node *node = dev->dev.of_node;
+       struct mtd_info *mtd;
+       if (!fsl_ifc_ctrl_dev || !fsl_ifc_ctrl_dev->rregs)
+               return -ENODEV;
+       ifc = fsl_ifc_ctrl_dev->rregs;
+       /* get, allocate and map the memory resource */
+       ret = of_address_to_resource(node, 0, &res);
+       if (ret) {
+               dev_err(&dev->dev, "%s: failed to get resource\n", __func__);
+               return ret;
+       }
+       /* find which chip select it is connected to */
+       for (bank = 0; bank < fsl_ifc_ctrl_dev->banks; bank++) {
+               if (match_bank(fsl_ifc_ctrl_dev->gregs, bank, res.start))
+                       break;
+       }
+       if (bank >= fsl_ifc_ctrl_dev->banks) {
+               dev_err(&dev->dev, "%s: address did not match any chip selects\n",
+                       __func__);
+               return -ENODEV;
+       }
+       priv = devm_kzalloc(&dev->dev, sizeof(*priv), GFP_KERNEL);
+       if (!priv)
+               return -ENOMEM;
+       mutex_lock(&fsl_ifc_nand_mutex);
+       if (!fsl_ifc_ctrl_dev->nand) {
+               ifc_nand_ctrl = kzalloc(sizeof(*ifc_nand_ctrl), GFP_KERNEL);
+               if (!ifc_nand_ctrl) {
+                       mutex_unlock(&fsl_ifc_nand_mutex);
+                       return -ENOMEM;
+               }
+               ifc_nand_ctrl->read_bytes = 0;
+               ifc_nand_ctrl->index = 0;
+               ifc_nand_ctrl->addr = NULL;
+               fsl_ifc_ctrl_dev->nand = ifc_nand_ctrl;
+               nand_hw_control_init(&ifc_nand_ctrl->controller);
+       } else {
+               ifc_nand_ctrl = fsl_ifc_ctrl_dev->nand;
+       }
+       mutex_unlock(&fsl_ifc_nand_mutex);
+       ifc_nand_ctrl->chips[bank] = priv;
+       priv->bank = bank;
+       priv->ctrl = fsl_ifc_ctrl_dev;
+       priv->dev = &dev->dev;
+       priv->vbase = ioremap(res.start, resource_size(&res));
+       if (!priv->vbase) {
+               dev_err(priv->dev, "%s: failed to map chip region\n", __func__);
+               ret = -ENOMEM;
+               goto err;
+       }
+       dev_set_drvdata(priv->dev, priv);
+       ifc_out32(IFC_NAND_EVTER_EN_OPC_EN |
+                 IFC_NAND_EVTER_EN_FTOER_EN |
+                 IFC_NAND_EVTER_EN_WPER_EN,
+                 &ifc->ifc_nand.nand_evter_en);
+       /* enable NAND Machine Interrupts */
+       ifc_out32(IFC_NAND_EVTER_INTR_OPCIR_EN |
+                 IFC_NAND_EVTER_INTR_FTOERIR_EN |
+                 IFC_NAND_EVTER_INTR_WPERIR_EN,
+                 &ifc->ifc_nand.nand_evter_intr_en);
+       mtd = nand_to_mtd(&priv->chip);
+       mtd->name = kasprintf(GFP_KERNEL, "%llx.flash", (u64)res.start);
+       if (!mtd->name) {
+               ret = -ENOMEM;
+               goto err;
+       }
+       ret = fsl_ifc_chip_init(priv);
+       if (ret)
+               goto err;
+       ret = nand_scan_ident(mtd, 1, NULL);
+       if (ret)
+               goto err;
+       ret = fsl_ifc_chip_init_tail(mtd);
+       if (ret)
+               goto err;
+       ret = nand_scan_tail(mtd);
+       if (ret)
+               goto err;
+       /* First look for RedBoot table or partitions on the command
+        * line, these take precedence over device tree information */
+       mtd_device_parse_register(mtd, part_probe_types, NULL, NULL, 0);
+       dev_info(priv->dev, "IFC NAND device at 0x%llx, bank %d\n",
+                (unsigned long long)res.start, priv->bank);
+       return 0;
+ err:
+       fsl_ifc_chip_remove(priv);
+       return ret;
+ }
+ static int fsl_ifc_nand_remove(struct platform_device *dev)
+ {
+       struct fsl_ifc_mtd *priv = dev_get_drvdata(&dev->dev);
+       fsl_ifc_chip_remove(priv);
+       mutex_lock(&fsl_ifc_nand_mutex);
+       ifc_nand_ctrl->counter--;
+       if (!ifc_nand_ctrl->counter) {
+               fsl_ifc_ctrl_dev->nand = NULL;
+               kfree(ifc_nand_ctrl);
+       }
+       mutex_unlock(&fsl_ifc_nand_mutex);
+       return 0;
+ }
+ static const struct of_device_id fsl_ifc_nand_match[] = {
+       {
+               .compatible = "fsl,ifc-nand",
+       },
+       {}
+ };
+ MODULE_DEVICE_TABLE(of, fsl_ifc_nand_match);
+ static struct platform_driver fsl_ifc_nand_driver = {
+       .driver = {
+               .name   = "fsl,ifc-nand",
+               .of_match_table = fsl_ifc_nand_match,
+       },
+       .probe       = fsl_ifc_nand_probe,
+       .remove      = fsl_ifc_nand_remove,
+ };
+ module_platform_driver(fsl_ifc_nand_driver);
+ MODULE_LICENSE("GPL");
+ MODULE_AUTHOR("Freescale");
+ MODULE_DESCRIPTION("Freescale Integrated Flash Controller MTD NAND driver");