diff mbox series

[LEDE-DEV,PATCHv2,3/4] kernel/4.4: add generic spi-nand framework

Message ID 20170903054359.4118-1-hackpascal@gmail.com
State Superseded
Headers show
Series [LEDE-DEV,PATCHv2,1/4] mac80211: enable use of GPI9 of ath9k | expand

Commit Message

Weijie Gao Sept. 3, 2017, 5:43 a.m. UTC
From: Weijie Gao <hackpascal@gmail.com>

This patch adds generic SPI-NAND framework for linux-4.4.

Files come from patches of target pistachio, but have lots of modifications
to add full support for GD5F series.

Signed-off-by: Weijie Gao <hackpascal@gmail.com>
---
 target/linux/generic/config-4.4                    |   2 +
 .../generic/files/drivers/mtd/spi-nand/Kconfig     |  17 +
 .../generic/files/drivers/mtd/spi-nand/Makefile    |   2 +
 .../files/drivers/mtd/spi-nand/spi-nand-base.c     | 588 ++++++++++++++++
 .../files/drivers/mtd/spi-nand/spi-nand-device.c   | 761 +++++++++++++++++++++
 .../generic/files/include/linux/mtd/spi-nand.h     |  56 ++
 ...length-of-ID-before-reading-bits-per-cell.patch |  33 +
 ...-Add-JEDEC-manufacturer-ID-for-Gigadevice.patch |  35 +
 .../454-mtd-Introduce-SPI-NAND-framework.patch     |  20 +
 9 files changed, 1514 insertions(+)
 create mode 100644 target/linux/generic/files/drivers/mtd/spi-nand/Kconfig
 create mode 100644 target/linux/generic/files/drivers/mtd/spi-nand/Makefile
 create mode 100644 target/linux/generic/files/drivers/mtd/spi-nand/spi-nand-base.c
 create mode 100644 target/linux/generic/files/drivers/mtd/spi-nand/spi-nand-device.c
 create mode 100644 target/linux/generic/files/include/linux/mtd/spi-nand.h
 create mode 100644 target/linux/generic/pending-4.4/452-mtd-nand-Check-length-of-ID-before-reading-bits-per-cell.patch
 create mode 100644 target/linux/generic/pending-4.4/453-mtd-nand-Add-JEDEC-manufacturer-ID-for-Gigadevice.patch
 create mode 100644 target/linux/generic/pending-4.4/454-mtd-Introduce-SPI-NAND-framework.patch

Comments

Weijie Gao Sept. 3, 2017, 1:42 p.m. UTC | #1
GD5F1GQ4UAYIG
datasheet: https://www.endrich.com/fm/2/GD5F1GQ4UAYIG.pdf

2017-09-03 19:27 GMT+08:00 Tom Psyborg <pozega.tomislav@gmail.com>:
> which devices you tested this on?
>
> On 3 September 2017 at 07:43, hackpascal <hackpascal@gmail.com> wrote:
>>
>> From: Weijie Gao <hackpascal@gmail.com>
>>
>> This patch adds generic SPI-NAND framework for linux-4.4.
>>
>> Files come from patches of target pistachio, but have lots of
>> modifications
>> to add full support for GD5F series.
>>
>> Signed-off-by: Weijie Gao <hackpascal@gmail.com>
>> ---
>>  target/linux/generic/config-4.4                    |   2 +
>>  .../generic/files/drivers/mtd/spi-nand/Kconfig     |  17 +
>>  .../generic/files/drivers/mtd/spi-nand/Makefile    |   2 +
>>  .../files/drivers/mtd/spi-nand/spi-nand-base.c     | 588 ++++++++++++++++
>>  .../files/drivers/mtd/spi-nand/spi-nand-device.c   | 761
>> +++++++++++++++++++++
>>  .../generic/files/include/linux/mtd/spi-nand.h     |  56 ++
>>  ...length-of-ID-before-reading-bits-per-cell.patch |  33 +
>>  ...-Add-JEDEC-manufacturer-ID-for-Gigadevice.patch |  35 +
>>  .../454-mtd-Introduce-SPI-NAND-framework.patch     |  20 +
>>  9 files changed, 1514 insertions(+)
>>  create mode 100644
>> target/linux/generic/files/drivers/mtd/spi-nand/Kconfig
>>  create mode 100644
>> target/linux/generic/files/drivers/mtd/spi-nand/Makefile
>>  create mode 100644
>> target/linux/generic/files/drivers/mtd/spi-nand/spi-nand-base.c
>>  create mode 100644
>> target/linux/generic/files/drivers/mtd/spi-nand/spi-nand-device.c
>>  create mode 100644
>> target/linux/generic/files/include/linux/mtd/spi-nand.h
>>  create mode 100644
>> target/linux/generic/pending-4.4/452-mtd-nand-Check-length-of-ID-before-reading-bits-per-cell.patch
>>  create mode 100644
>> target/linux/generic/pending-4.4/453-mtd-nand-Add-JEDEC-manufacturer-ID-for-Gigadevice.patch
>>  create mode 100644
>> target/linux/generic/pending-4.4/454-mtd-Introduce-SPI-NAND-framework.patch
>>
>> diff --git a/target/linux/generic/config-4.4
>> b/target/linux/generic/config-4.4
>> index 1c0af9597f..0fd7c1d49c 100644
>> --- a/target/linux/generic/config-4.4
>> +++ b/target/linux/generic/config-4.4
>> @@ -2369,6 +2369,8 @@ CONFIG_MTD_ROOTFS_ROOT_DEV=y
>>  # CONFIG_MTD_SLRAM is not set
>>  # CONFIG_MTD_SM_COMMON is not set
>>  # CONFIG_MTD_SPINAND_MT29F is not set
>> +# CONFIG_MTD_SPI_NAND is not set
>> +# CONFIG_MTD_SPI_NAND_DEVICES is not set
>>  # CONFIG_MTD_SPI_NOR is not set
>>  # CONFIG_MTD_SPI_NOR_USE_4K_SECTORS is not set
>>  CONFIG_MTD_SPLIT=y
>> diff --git a/target/linux/generic/files/drivers/mtd/spi-nand/Kconfig
>> b/target/linux/generic/files/drivers/mtd/spi-nand/Kconfig
>> new file mode 100644
>> index 0000000000..ab6bb6c7fa
>> --- /dev/null
>> +++ b/target/linux/generic/files/drivers/mtd/spi-nand/Kconfig
>> @@ -0,0 +1,17 @@
>> +menuconfig MTD_SPI_NAND
>> +       tristate "SPI NAND device support"
>> +       depends on MTD
>> +       select MTD_NAND
>> +       help
>> +         This is the framework for the SPI NAND.
>> +
>> +if MTD_SPI_NAND
>> +
>> +config MTD_SPI_NAND_DEVICES
>> +       tristate "Support for SPI NAND devices"
>> +       default y
>> +       depends on MTD_SPI_NAND
>> +       help
>> +         Select this option if you require support for SPI NAND devices.
>> +
>> +endif # MTD_SPI_NAND
>> diff --git a/target/linux/generic/files/drivers/mtd/spi-nand/Makefile
>> b/target/linux/generic/files/drivers/mtd/spi-nand/Makefile
>> new file mode 100644
>> index 0000000000..6e460d1814
>> --- /dev/null
>> +++ b/target/linux/generic/files/drivers/mtd/spi-nand/Makefile
>> @@ -0,0 +1,2 @@
>> +obj-$(CONFIG_MTD_SPI_NAND)             += spi-nand-base.o
>> +obj-$(CONFIG_MTD_SPI_NAND_DEVICES)     += spi-nand-device.o
>> diff --git
>> a/target/linux/generic/files/drivers/mtd/spi-nand/spi-nand-base.c
>> b/target/linux/generic/files/drivers/mtd/spi-nand/spi-nand-base.c
>> new file mode 100644
>> index 0000000000..07dad5397a
>> --- /dev/null
>> +++ b/target/linux/generic/files/drivers/mtd/spi-nand/spi-nand-base.c
>> @@ -0,0 +1,588 @@
>> +/*
>> + * Copyright (C) 2014 Imagination Technologies Ltd.
>> + *
>> + * 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; version 2 of the License.
>> + *
>> + * Notes:
>> + * 1. Erase and program operations need to call write_enable() first,
>> + *    to clear the enable bit. This bit is cleared automatically after
>> + *    the erase or program operation.
>> + *
>> + */
>> +
>> +#include <linux/device.h>
>> +#include <linux/err.h>
>> +#include <linux/errno.h>
>> +#include <linux/kernel.h>
>> +#include <linux/module.h>
>> +#include <linux/mtd/nand.h>
>> +#include <linux/mtd/mtd.h>
>> +#include <linux/mtd/partitions.h>
>> +#include <linux/mtd/spi-nand.h>
>> +#include <linux/of.h>
>> +#include <linux/slab.h>
>> +
>> +/* Registers common to all devices */
>> +#define SPI_NAND_LOCK_REG              0xa0
>> +#define SPI_NAND_PROT_UNLOCK_ALL       0x0
>> +
>> +#define SPI_NAND_FEATURE_REG           0xb0
>> +#define SPI_NAND_ECC_EN                        BIT(4)
>> +#define SPI_NAND_QUAD_EN               BIT(0)
>> +
>> +#define SPI_NAND_STATUS_REG            0xc0
>> +#define SPI_NAND_STATUS_REG_ECC_MASK   0x3
>> +#define SPI_NAND_STATUS_REG_ECC_SHIFT  4
>> +#define SPI_NAND_STATUS_REG_PROG_FAIL  BIT(3)
>> +#define SPI_NAND_STATUS_REG_ERASE_FAIL BIT(2)
>> +#define SPI_NAND_STATUS_REG_WREN       BIT(1)
>> +#define SPI_NAND_STATUS_REG_BUSY       BIT(0)
>> +
>> +#define SPI_NAND_CMD_BUF_LEN           8
>> +
>> +/* Rewind and fill the buffer with 0xff */
>> +static void spi_nand_clear_buffer(struct spi_nand *snand)
>> +{
>> +       snand->buf_start = 0;
>> +       memset(snand->data_buf, 0xff, snand->buf_size);
>> +}
>> +
>> +static int spi_nand_enable_ecc(struct spi_nand *snand)
>> +{
>> +       int ret;
>> +
>> +       ret = snand->read_reg(snand, SPI_NAND_FEATURE_REG, snand->buf);
>> +       if (ret)
>> +               return ret;
>> +
>> +       snand->buf[0] |= SPI_NAND_ECC_EN;
>> +       ret = snand->write_reg(snand, SPI_NAND_FEATURE_REG, snand->buf);
>> +       if (ret)
>> +               return ret;
>> +       snand->ecc = true;
>> +
>> +       return 0;
>> +}
>> +
>> +static int spi_nand_disable_ecc(struct spi_nand *snand)
>> +{
>> +       int ret;
>> +
>> +       ret = snand->read_reg(snand, SPI_NAND_FEATURE_REG, snand->buf);
>> +       if (ret)
>> +               return ret;
>> +
>> +       snand->buf[0] &= ~SPI_NAND_ECC_EN;
>> +       ret = snand->write_reg(snand, SPI_NAND_FEATURE_REG, snand->buf);
>> +       if (ret)
>> +               return ret;
>> +       snand->ecc = false;
>> +
>> +       return 0;
>> +}
>> +
>> +static int spi_nand_enable_quad(struct spi_nand *snand)
>> +{
>> +       int ret;
>> +
>> +       ret = snand->read_reg(snand, SPI_NAND_FEATURE_REG, snand->buf);
>> +       if (ret)
>> +               return ret;
>> +
>> +       snand->buf[0] |= SPI_NAND_QUAD_EN;
>> +       ret = snand->write_reg(snand, SPI_NAND_FEATURE_REG, snand->buf);
>> +       if (ret)
>> +               return ret;
>> +
>> +       return 0;
>> +}
>> +/*
>> + * Wait until the status register busy bit is cleared.
>> + * Returns a negatie errno on error or time out, and a non-negative
>> status
>> + * value if the device is ready.
>> + */
>> +static int spi_nand_wait_till_ready(struct spi_nand *snand)
>> +{
>> +       unsigned long deadline = jiffies + msecs_to_jiffies(100);
>> +       bool timeout = false;
>> +       int ret;
>> +
>> +       /*
>> +        * Perhaps we should set a different timeout for each
>> +        * operation (reset, read, write, erase).
>> +        */
>> +       while (!timeout) {
>> +               if (time_after_eq(jiffies, deadline))
>> +                       timeout = true;
>> +
>> +               ret = snand->read_reg(snand, SPI_NAND_STATUS_REG,
>> snand->buf);
>> +               if (ret < 0) {
>> +                       dev_err(snand->dev, "error reading status
>> register\n");
>> +                       return ret;
>> +               } else if (!(snand->buf[0] & SPI_NAND_STATUS_REG_BUSY)) {
>> +                       return snand->buf[0];
>> +               }
>> +
>> +               cond_resched();
>> +       }
>> +
>> +       dev_err(snand->dev, "operation timed out\n");
>> +
>> +       return -ETIMEDOUT;
>> +}
>> +
>> +static int spi_nand_reset(struct spi_nand *snand)
>> +{
>> +       int ret;
>> +
>> +       ret = snand->reset(snand);
>> +       if (ret < 0) {
>> +               dev_err(snand->dev, "reset command failed\n");
>> +               return ret;
>> +       }
>> +
>> +       /*
>> +        * The NAND core won't wait after a device reset, so we need
>> +        * to do that here.
>> +        */
>> +       ret = spi_nand_wait_till_ready(snand);
>> +       if (ret < 0)
>> +               return ret;
>> +       return 0;
>> +}
>> +
>> +static int spi_nand_status(struct spi_nand *snand)
>> +{
>> +       int ret, status;
>> +
>> +       ret = snand->read_reg(snand, SPI_NAND_STATUS_REG, snand->buf);
>> +       if (ret < 0) {
>> +               dev_err(snand->dev, "error reading status register\n");
>> +               return ret;
>> +       }
>> +       status = snand->buf[0];
>> +
>> +       /* Convert this into standard NAND_STATUS values */
>> +       if (status & SPI_NAND_STATUS_REG_BUSY)
>> +               snand->buf[0] = 0;
>> +       else
>> +               snand->buf[0] = NAND_STATUS_READY;
>> +
>> +       if (status & SPI_NAND_STATUS_REG_PROG_FAIL ||
>> +           status & SPI_NAND_STATUS_REG_ERASE_FAIL)
>> +               snand->buf[0] |= NAND_STATUS_FAIL;
>> +
>> +       /*
>> +        * Since we unlock the entire device at initialization,
>> unconditionally
>> +        * set the WP bit to indicate it's not protected.
>> +        */
>> +       snand->buf[0] |= NAND_STATUS_WP;
>> +       return 0;
>> +}
>> +
>> +static int spi_nand_erase(struct spi_nand *snand, int page_addr)
>> +{
>> +       int ret;
>> +
>> +       ret = snand->write_enable(snand);
>> +       if (ret < 0) {
>> +               dev_err(snand->dev, "write enable command failed\n");
>> +               return ret;
>> +       }
>> +
>> +       ret = snand->block_erase(snand, page_addr);
>> +       if (ret < 0) {
>> +               dev_err(snand->dev, "block erase command failed\n");
>> +               return ret;
>> +       }
>> +
>> +       return 0;
>> +}
>> +
>> +static int spi_nand_write(struct spi_nand *snand)
>> +{
>> +       int ret;
>> +
>> +       /* Enable quad mode */
>> +       ret = spi_nand_enable_quad(snand);
>> +       if (ret) {
>> +               dev_err(snand->dev, "error %d enabling quad mode\n", ret);
>> +               return ret;
>> +       }
>> +       /* Store the page to cache */
>> +       ret = snand->store_cache(snand, 0, snand->buf_size,
>> snand->data_buf);
>> +       if (ret < 0) {
>> +               dev_err(snand->dev, "error %d storing page 0x%x to
>> cache\n",
>> +                       ret, snand->page_addr);
>> +               return ret;
>> +       }
>> +
>> +       ret = snand->write_enable(snand);
>> +       if (ret < 0) {
>> +               dev_err(snand->dev, "write enable command failed\n");
>> +               return ret;
>> +       }
>> +
>> +       /* Get page from the device cache into our internal buffer */
>> +       ret = snand->write_page(snand, snand->page_addr);
>> +       if (ret < 0) {
>> +               dev_err(snand->dev, "error %d reading page 0x%x from
>> cache\n",
>> +                       ret, snand->page_addr);
>> +               return ret;
>> +       }
>> +
>> +       return 0;
>> +}
>> +
>> +static int spi_nand_read_id(struct spi_nand *snand)
>> +{
>> +       int ret;
>> +
>> +       ret = snand->read_id(snand, snand->data_buf);
>> +       if (ret < 0) {
>> +               dev_err(snand->dev, "error %d reading ID\n", ret);
>> +               return ret;
>> +       }
>> +       return 0;
>> +}
>> +
>> +static int spi_nand_read_page(struct spi_nand *snand, unsigned int
>> page_addr,
>> +                             unsigned int page_offset, size_t length)
>> +{
>> +       unsigned int corrected = 0, ecc_error = 0;
>> +       int ret;
>> +
>> +       /* Load a page into the cache register */
>> +       ret = snand->load_page(snand, page_addr);
>> +       if (ret < 0) {
>> +               dev_err(snand->dev, "error %d loading page 0x%x to
>> cache\n",
>> +                       ret, page_addr);
>> +               return ret;
>> +       }
>> +
>> +       ret = spi_nand_wait_till_ready(snand);
>> +       if (ret < 0)
>> +               return ret;
>> +
>> +       if (snand->ecc) {
>> +               snand->get_ecc_status(ret, &corrected, &ecc_error);
>> +               snand->bitflips = corrected;
>> +
>> +               /*
>> +                * If there's an ECC error, print a message and notify MTD
>> +                * about it. Then complete the read, to load actual data
>> on
>> +                * the buffer (instead of the status result).
>> +                */
>> +               if (ecc_error) {
>> +                       dev_err(snand->dev,
>> +                               "internal ECC error reading page 0x%x\n",
>> +                               page_addr);
>> +                       snand->mtd.ecc_stats.failed++;
>> +               } else {
>> +                       snand->mtd.ecc_stats.corrected += corrected;
>> +               }
>> +       }
>> +
>> +       /* Enable quad mode */
>> +       ret = spi_nand_enable_quad(snand);
>> +       if (ret) {
>> +               dev_err(snand->dev, "error %d enabling quad mode\n", ret);
>> +               return ret;
>> +       }
>> +       /* Get page from the device cache into our internal buffer */
>> +       ret = snand->read_cache(snand, page_offset, length,
>> snand->data_buf);
>> +       if (ret < 0) {
>> +               dev_err(snand->dev, "error %d reading page 0x%x from
>> cache\n",
>> +                       ret, page_addr);
>> +               return ret;
>> +       }
>> +       return 0;
>> +}
>> +
>> +static u8 spi_nand_read_byte(struct mtd_info *mtd)
>> +{
>> +       struct nand_chip *chip = mtd->priv;
>> +       struct spi_nand *snand = chip->priv;
>> +       char val = 0xff;
>> +
>> +       if (snand->buf_start < snand->buf_size)
>> +               val = snand->data_buf[snand->buf_start++];
>> +       return val;
>> +}
>> +
>> +static void spi_nand_write_buf(struct mtd_info *mtd, const u8 *buf, int
>> len)
>> +{
>> +       struct nand_chip *chip = mtd->priv;
>> +       struct spi_nand *snand = chip->priv;
>> +       size_t n = min_t(size_t, len, snand->buf_size - snand->buf_start);
>> +
>> +       memcpy(snand->data_buf + snand->buf_start, buf, n);
>> +       snand->buf_start += n;
>> +}
>> +
>> +static void spi_nand_read_buf(struct mtd_info *mtd, u8 *buf, int len)
>> +{
>> +       struct nand_chip *chip = mtd->priv;
>> +       struct spi_nand *snand = chip->priv;
>> +       size_t n = min_t(size_t, len, snand->buf_size - snand->buf_start);
>> +
>> +       memcpy(buf, snand->data_buf + snand->buf_start, n);
>> +       snand->buf_start += n;
>> +}
>> +
>> +static int spi_nand_write_page_hwecc(struct mtd_info *mtd,
>> +               struct nand_chip *chip, const uint8_t *buf, int
>> oob_required,
>> +               int page)
>> +{
>> +       chip->write_buf(mtd, buf, mtd->writesize);
>> +       chip->write_buf(mtd, chip->oob_poi, mtd->oobsize);
>> +
>> +       return 0;
>> +}
>> +
>> +static int spi_nand_read_page_hwecc(struct mtd_info *mtd,
>> +               struct nand_chip *chip, uint8_t *buf, int oob_required,
>> +               int page)
>> +{
>> +       struct spi_nand *snand = chip->priv;
>> +
>> +       chip->read_buf(mtd, buf, mtd->writesize);
>> +       chip->read_buf(mtd, chip->oob_poi, mtd->oobsize);
>> +
>> +       return snand->bitflips;
>> +}
>> +
>> +static int spi_nand_waitfunc(struct mtd_info *mtd, struct nand_chip
>> *chip)
>> +{
>> +       struct spi_nand *snand = chip->priv;
>> +       int ret;
>> +
>> +       ret = spi_nand_wait_till_ready(snand);
>> +
>> +       if (ret < 0) {
>> +               return NAND_STATUS_FAIL;
>> +       } else if (ret & SPI_NAND_STATUS_REG_PROG_FAIL) {
>> +               dev_err(snand->dev, "page program failed\n");
>> +               return NAND_STATUS_FAIL;
>> +       } else if (ret & SPI_NAND_STATUS_REG_ERASE_FAIL) {
>> +               dev_err(snand->dev, "block erase failed\n");
>> +               return NAND_STATUS_FAIL;
>> +       }
>> +
>> +       return NAND_STATUS_READY;
>> +}
>> +
>> +static void spi_nand_cmdfunc(struct mtd_info *mtd, unsigned int command,
>> +                            int column, int page_addr)
>> +{
>> +       struct nand_chip *chip = mtd->priv;
>> +       struct spi_nand *snand = chip->priv;
>> +
>> +       /*
>> +        * In case there's any unsupported command, let's make sure
>> +        * we don't keep garbage around in the buffer.
>> +        */
>> +       if (command != NAND_CMD_PAGEPROG) {
>> +               spi_nand_clear_buffer(snand);
>> +               snand->page_addr = 0;
>> +       }
>> +
>> +       switch (command) {
>> +       case NAND_CMD_READ0:
>> +               spi_nand_read_page(snand, page_addr, 0, mtd->writesize);
>> +               break;
>> +       case NAND_CMD_READOOB:
>> +               spi_nand_disable_ecc(snand);
>> +               spi_nand_read_page(snand, page_addr, mtd->writesize,
>> +                                  mtd->oobsize);
>> +               spi_nand_enable_ecc(snand);
>> +               break;
>> +       case NAND_CMD_READID:
>> +               spi_nand_read_id(snand);
>> +               break;
>> +       case NAND_CMD_ERASE1:
>> +               spi_nand_erase(snand, page_addr);
>> +               break;
>> +       case NAND_CMD_ERASE2:
>> +               /* There's nothing to do here, as the erase is one-step */
>> +               break;
>> +       case NAND_CMD_SEQIN:
>> +               snand->buf_start = column;
>> +               snand->page_addr = page_addr;
>> +               break;
>> +       case NAND_CMD_PAGEPROG:
>> +               spi_nand_write(snand);
>> +               break;
>> +       case NAND_CMD_STATUS:
>> +               spi_nand_status(snand);
>> +               break;
>> +       case NAND_CMD_RESET:
>> +               spi_nand_reset(snand);
>> +               break;
>> +       default:
>> +               dev_err(&mtd->dev, "unknown command 0x%x\n", command);
>> +       }
>> +}
>> +
>> +static void spi_nand_select_chip(struct mtd_info *mtd, int chip)
>> +{
>> +       /* We need this to override the default */
>> +}
>> +
>> +static bool spi_nand_get_oob_layout(struct mtd_info *mtd, struct
>> nand_ecclayout **ooblayout)
>> +{
>> +       struct nand_chip *chip = mtd->priv;
>> +       struct spi_nand *snand = chip->priv;
>> +       u8 id[0x24];    /* Maximum for GD5F */
>> +       struct nand_ecclayout *new_ooblayout;
>> +
>> +       spi_nand_clear_buffer(snand);
>> +       snand->page_addr = 0;
>> +
>> +       /* Send the command for reading device ID */
>> +       spi_nand_read_id(snand);
>> +
>> +       /* Read ID bytes */
>> +       spi_nand_read_buf(mtd, id, sizeof (id));
>> +
>> +       /* Get OOB layout */
>> +       new_ooblayout = spi_nand_post_probe(id, sizeof (id));
>> +
>> +       if (new_ooblayout && ooblayout)
>> +               *ooblayout = new_ooblayout;
>> +
>> +       return new_ooblayout != NULL;
>> +}
>> +
>> +int spi_nand_check(struct spi_nand *snand)
>> +{
>> +       if (!snand->dev)
>> +               return -ENODEV;
>> +       if (!snand->read_cache)
>> +               return -ENODEV;
>> +       if (!snand->load_page)
>> +               return -ENODEV;
>> +       if (!snand->store_cache)
>> +               return -ENODEV;
>> +       if (!snand->write_page)
>> +               return -ENODEV;
>> +       if (!snand->write_reg)
>> +               return -ENODEV;
>> +       if (!snand->read_reg)
>> +               return -ENODEV;
>> +       if (!snand->block_erase)
>> +               return -ENODEV;
>> +       if (!snand->reset)
>> +               return -ENODEV;
>> +       if (!snand->write_enable)
>> +               return -ENODEV;
>> +       if (!snand->write_disable)
>> +               return -ENODEV;
>> +       if (!snand->get_ecc_status)
>> +               return -ENODEV;
>> +       return 0;
>> +}
>> +
>> +int spi_nand_register(struct spi_nand *snand, struct nand_flash_dev
>> *flash_ids)
>> +{
>> +       struct nand_chip *chip = &snand->nand_chip;
>> +       struct mtd_info *mtd = &snand->mtd;
>> +       int ret;
>> +
>> +       /* Let's check all the hooks are in-place so we don't panic later
>> */
>> +       ret = spi_nand_check(snand);
>> +       if (ret)
>> +               return ret;
>> +
>> +       chip->priv = snand;
>> +       chip->read_buf  = spi_nand_read_buf;
>> +       chip->write_buf = spi_nand_write_buf;
>> +       chip->read_byte = spi_nand_read_byte;
>> +       chip->cmdfunc   = spi_nand_cmdfunc;
>> +       chip->waitfunc  = spi_nand_waitfunc;
>> +       chip->select_chip = spi_nand_select_chip;
>> +       chip->options |= NAND_NO_SUBPAGE_WRITE;
>> +       chip->bits_per_cell = 1;
>> +
>> +       chip->ecc.read_page     = spi_nand_read_page_hwecc;
>> +       chip->ecc.write_page    = spi_nand_write_page_hwecc;
>> +       chip->ecc.mode          = NAND_ECC_HW;
>> +
>> +       if (!mtd->name)
>> +               mtd->name = dev_name(snand->dev);
>> +       mtd->owner = THIS_MODULE;
>> +       mtd->priv = chip;
>> +       mtd->type = MTD_NANDFLASH;
>> +       mtd->flags = MTD_CAP_NANDFLASH;
>> +
>> +       /* Allocate buffer to be used to read/write the internal registers
>> */
>> +       snand->buf = kmalloc(SPI_NAND_CMD_BUF_LEN, GFP_KERNEL);
>> +       if (!snand->buf)
>> +               return -ENOMEM;
>> +
>> +       /* This is enabled at device power up but we'd better make sure */
>> +       ret = spi_nand_enable_ecc(snand);
>> +       if (ret)
>> +               return ret;
>> +
>> +       /* Preallocate buffer for flash identification (NAND_CMD_READID)
>> */
>> +       snand->buf_size = SPI_NAND_CMD_BUF_LEN;
>> +       snand->data_buf = kmalloc(snand->buf_size, GFP_KERNEL);
>> +
>> +       ret = nand_scan_ident(mtd, 1, flash_ids);
>> +       if (ret)
>> +               return ret;
>> +
>> +       /*
>> +        * SPI NAND has on-die ECC, which means we can correct as much as
>> +        * we are required to. This must be done after identification of
>> +        * the device.
>> +        */
>> +       chip->ecc.strength = chip->ecc_strength_ds;
>> +       chip->ecc.size = chip->ecc_step_ds;
>> +
>> +       /* Re-check manufacturer and device IDs to get proper OOB layout
>> */
>> +       if (!spi_nand_get_oob_layout(mtd, &chip->ecc.layout)) {
>> +               dev_err(snand->dev, "OOB layout not found\n");
>> +               return -EINVAL;
>> +       }
>> +
>> +       /*
>> +        * Unlock all the device before calling nand_scan_tail. This is
>> needed
>> +        * in case the in-flash bad block table needs to be created.
>> +        * We could override __nand_unlock(), but since it's not currently
>> used
>> +        * by the NAND core we call this explicitly.
>> +        */
>> +       snand->buf[0] = SPI_NAND_PROT_UNLOCK_ALL;
>> +       ret = snand->write_reg(snand, SPI_NAND_LOCK_REG, snand->buf);
>> +       if (ret)
>> +               return ret;
>> +
>> +       /* Free the buffer and allocate a good one, to fit a page plus OOB
>> */
>> +       kfree(snand->data_buf);
>> +
>> +       snand->buf_size = mtd->writesize + mtd->oobsize;
>> +       snand->data_buf = kmalloc(snand->buf_size, GFP_KERNEL);
>> +       if (!snand->data_buf)
>> +               return -ENOMEM;
>> +
>> +       ret = nand_scan_tail(mtd);
>> +       if (ret)
>> +               return ret;
>> +
>> +       return mtd_device_register(mtd, NULL, 0);
>> +}
>> +EXPORT_SYMBOL_GPL(spi_nand_register);
>> +
>> +void spi_nand_unregister(struct spi_nand *snand)
>> +{
>> +       kfree(snand->buf);
>> +       kfree(snand->data_buf);
>> +}
>> +EXPORT_SYMBOL_GPL(spi_nand_unregister);
>> +
>> +MODULE_AUTHOR("Ezequiel Garcia <ezequiel.garcia@imgtec.com>");
>> +MODULE_DESCRIPTION("Framework for SPI NAND");
>> +MODULE_LICENSE("GPL v2");
>> diff --git
>> a/target/linux/generic/files/drivers/mtd/spi-nand/spi-nand-device.c
>> b/target/linux/generic/files/drivers/mtd/spi-nand/spi-nand-device.c
>> new file mode 100644
>> index 0000000000..9fb793493b
>> --- /dev/null
>> +++ b/target/linux/generic/files/drivers/mtd/spi-nand/spi-nand-device.c
>> @@ -0,0 +1,761 @@
>> +/*
>> + * Copyright (C) 2014 Imagination Technologies Ltd.
>> + * Copyright (C) 2017 Weijie Gao <hackpascal@gmail.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; version 2 of the License.
>> + *
>> + * Notes:
>> + * 1. We avoid using a stack-allocated buffer for SPI messages. Using
>> + *    a kmalloced buffer is probably better, given we shouldn't assume
>> + *    any particular usage by SPI core.
>> + */
>> +
>> +#include <linux/device.h>
>> +#include <linux/err.h>
>> +#include <linux/errno.h>
>> +#include <linux/module.h>
>> +#include <linux/mtd/mtd.h>
>> +#include <linux/mtd/partitions.h>
>> +#include <linux/mtd/spi-nand.h>
>> +#include <linux/sizes.h>
>> +#include <linux/spi/spi.h>
>> +
>> +/* SPI NAND commands */
>> +#define        SPI_NAND_WRITE_ENABLE           0x06
>> +#define        SPI_NAND_WRITE_DISABLE          0x04
>> +#define        SPI_NAND_GET_FEATURE            0x0f
>> +#define        SPI_NAND_SET_FEATURE            0x1f
>> +#define        SPI_NAND_PAGE_READ              0x13
>> +#define        SPI_NAND_READ_CACHE             0x03
>> +#define        SPI_NAND_FAST_READ_CACHE        0x0b
>> +#define        SPI_NAND_READ_CACHE_X2          0x3b
>> +#define        SPI_NAND_READ_CACHE_X4          0x6b
>> +#define        SPI_NAND_READ_CACHE_DUAL_IO     0xbb
>> +#define        SPI_NAND_READ_CACHE_QUAD_IO     0xeb
>> +#define        SPI_NAND_READ_ID                0x9f
>> +#define        SPI_NAND_PROGRAM_LOAD           0x02
>> +#define        SPI_NAND_PROGRAM_LOAD4          0x32
>> +#define        SPI_NAND_PROGRAM_EXEC           0x10
>> +#define        SPI_NAND_PROGRAM_LOAD_RANDOM    0x84
>> +#define        SPI_NAND_PROGRAM_LOAD_RANDOM4   0xc4
>> +#define        SPI_NAND_BLOCK_ERASE            0xd8
>> +#define        SPI_NAND_RESET                  0xff
>> +
>> +#define SPI_NAND_GD5F_READID_LEN       0x24
>> +
>> +#define SPI_NAND_GD5F_ECC_MASK         (BIT(0) | BIT(1) | BIT(2))
>> +#define SPI_NAND_GD5F_ECC_UNCORR       (BIT(0) | BIT(1) | BIT(2))
>> +#define SPI_NAND_GD5F_ECC_SHIFT                4
>> +
>> +/* Used for GD5FxGQ4UAYIG */
>> +static struct nand_ecclayout gd25_oob_64_layout = {
>> +       .eccbytes = 16,
>> +       .eccpos = {
>> +               12, 13, 14, 15, 28, 29, 30, 31,
>> +               44, 45, 46, 47, 60, 61, 62, 63
>> +       },
>> +       /* Not including spare regions that are not ECC-ed */
>> +       .oobavail = 32,
>> +       .oobfree = {
>> +               {
>> +                       .offset = 4,
>> +                       .length = 8
>> +               }, {
>> +                       .offset = 20,
>> +                       .length = 8
>> +               }, {
>> +                       .offset = 36,
>> +                       .length = 8
>> +               }, {
>> +                       .offset = 52,
>> +                       .length = 8
>> +               }
>> +       }
>> +};
>> +
>> +/* Used for GD5FxGQ4UAY with "SNFI" on ID addr. 0x20 */
>> +static struct nand_ecclayout gd25_snfi_oob_64_layout = {
>> +       .eccbytes = 32,
>> +       .eccpos = {
>> +               8, 9, 10, 11, 12, 13, 14, 15,
>> +               24, 25, 26, 27, 28, 29, 30, 31,
>> +               40, 41, 42, 43, 44, 45, 46, 47,
>> +               56, 57, 58, 59, 60, 61, 62, 63
>> +       },
>> +       /* Not including spare regions that are not ECC-ed */
>> +       .oobavail = 32,
>> +       .oobfree = {
>> +               {
>> +                       .offset = 4,
>> +                       .length = 4
>> +               }, {
>> +                       .offset = 20,
>> +                       .length = 4
>> +               }, {
>> +                       .offset = 36,
>> +                       .length = 4
>> +               }, {
>> +                       .offset = 52,
>> +                       .length = 4
>> +               }
>> +       }
>> +};
>> +
>> +static struct nand_ecclayout gd25_oob_128_layout = {
>> +       .eccbytes = 64,
>> +       .eccpos = {
>> +               64, 65, 66, 67, 68, 69, 70, 71,
>> +               72, 73, 74, 75, 76, 77, 78, 79,
>> +               80, 81, 82, 83, 84, 85, 86, 87,
>> +               88, 89, 90, 91, 92, 93, 94, 95,
>> +               96, 97, 98, 99, 100, 101, 102, 103,
>> +               104, 105, 106, 107, 108, 109, 110, 111,
>> +               112, 113, 114, 115, 116, 117, 118, 119,
>> +               120, 121, 122, 123, 124, 125, 126, 127
>> +       },
>> +       .oobavail = 63,
>> +       .oobfree = {
>> +               {
>> +                       .offset = 1,
>> +                       .length = 63,
>> +               }
>> +       },
>> +};
>> +
>> +static struct nand_ecclayout gd25_oob_256_layout = {
>> +       .eccbytes = 128,
>> +       .eccpos = {
>> +               128, 129, 130, 131, 132, 133, 134, 135,
>> +               136, 137, 138, 139, 140, 141, 142, 143,
>> +               144, 145, 146, 147, 148, 149, 150, 151,
>> +               152, 153, 154, 155, 156, 157, 158, 159,
>> +               160, 161, 162, 163, 164, 165, 166, 167,
>> +               168, 169, 170, 171, 172, 173, 174, 175,
>> +               176, 177, 178, 179, 180, 181, 182, 183,
>> +               184, 185, 186, 187, 188, 189, 190, 191,
>> +               192, 193, 194, 195, 196, 197, 198, 199,
>> +               200, 201, 202, 203, 204, 205, 206, 207,
>> +               208, 209, 210, 211, 212, 213, 214, 215,
>> +               216, 217, 218, 219, 220, 221, 222, 223,
>> +               224, 225, 226, 227, 228, 229, 230, 231,
>> +               232, 233, 234, 235, 236, 237, 238, 239,
>> +               240, 241, 242, 243, 244, 245, 246, 247,
>> +               248, 249, 250, 251, 252, 253, 254, 255
>> +       },
>> +       .oobavail = 127,
>> +       .oobfree = {
>> +               {
>> +                       .offset = 1,
>> +                       .length = 127,
>> +               }
>> +       },
>> +};
>> +
>> +static struct nand_flash_dev spi_nand_flash_ids[] = {
>> +       {
>> +               .name = "GD5F1GQ4UA",
>> +               .id = { NAND_MFR_GIGADEVICE, 0xf1 },
>> +               .chipsize = 128,
>> +               .pagesize = SZ_2K,
>> +               .erasesize = SZ_128K,
>> +               .id_len = 2,
>> +               .oobsize = 64,
>> +               .ecc.strength_ds = 8,
>> +               .ecc.step_ds = 512,
>> +       },
>> +       {
>> +               .name = "GD5F1GQ4RA",
>> +               .id = { NAND_MFR_GIGADEVICE, 0xe1 },
>> +               .chipsize = 128,
>> +               .pagesize = SZ_2K,
>> +               .erasesize = SZ_128K,
>> +               .id_len = 2,
>> +               .oobsize = 64,
>> +               .ecc.strength_ds = 8,
>> +               .ecc.step_ds = 512,
>> +       },
>> +       {
>> +               .name = "GD5F1GQ4UB",
>> +               .id = { NAND_MFR_GIGADEVICE, 0xd1 },
>> +               .chipsize = 128,
>> +               .pagesize = SZ_2K,
>> +               .erasesize = SZ_128K,
>> +               .id_len = 2,
>> +               .oobsize = 128,
>> +               .ecc.strength_ds = 8,
>> +               .ecc.step_ds = 512,
>> +       },
>> +       {
>> +               .name = "GD5F1GQ4RB",
>> +               .id = { NAND_MFR_GIGADEVICE, 0xc1 },
>> +               .chipsize = 128,
>> +               .pagesize = SZ_2K,
>> +               .erasesize = SZ_128K,
>> +               .id_len = 2,
>> +               .oobsize = 128,
>> +               .ecc.strength_ds = 8,
>> +               .ecc.step_ds = 512,
>> +       },
>> +       {
>> +               .name = "GD5F1GQ4UC",
>> +               .id = { NAND_MFR_GIGADEVICE, 0xb1 },
>> +               .chipsize = 128,
>> +               .pagesize = SZ_2K,
>> +               .erasesize = SZ_128K,
>> +               .id_len = 2,
>> +               .oobsize = 128,
>> +               .ecc.strength_ds = 8,
>> +               .ecc.step_ds = 512,
>> +       },
>> +       {
>> +               .name = "GD5F1GQ4RC",
>> +               .id = { NAND_MFR_GIGADEVICE, 0xa1 },
>> +               .chipsize = 128,
>> +               .pagesize = SZ_2K,
>> +               .erasesize = SZ_128K,
>> +               .id_len = 2,
>> +               .oobsize = 128,
>> +               .ecc.strength_ds = 8,
>> +               .ecc.step_ds = 512,
>> +       },
>> +       {
>> +               .name = "GD5F2GQ4UA",
>> +               .id = { NAND_MFR_GIGADEVICE, 0xf2 },
>> +               .chipsize = 256,
>> +               .pagesize = SZ_2K,
>> +               .erasesize = SZ_128K,
>> +               .id_len = 2,
>> +               .oobsize = 64,
>> +               .ecc.strength_ds = 8,
>> +               .ecc.step_ds = 512,
>> +       },
>> +       {
>> +               .name = "GD5F2GQ4RA",
>> +               .id = { NAND_MFR_GIGADEVICE, 0xe2 },
>> +               .chipsize = 256,
>> +               .pagesize = SZ_2K,
>> +               .erasesize = SZ_128K,
>> +               .id_len = 2,
>> +               .oobsize = 64,
>> +               .ecc.strength_ds = 8,
>> +               .ecc.step_ds = 512,
>> +       },
>> +       {
>> +               .name = "GD5F2GQ4UB",
>> +               .id = { NAND_MFR_GIGADEVICE, 0xd2 },
>> +               .chipsize = 256,
>> +               .pagesize = SZ_2K,
>> +               .erasesize = SZ_128K,
>> +               .id_len = 2,
>> +               .oobsize = 128,
>> +               .ecc.strength_ds = 8,
>> +               .ecc.step_ds = 512,
>> +       },
>> +       {
>> +               .name = "GD5F2GQ4RB",
>> +               .id = { NAND_MFR_GIGADEVICE, 0xc2 },
>> +               .chipsize = 256,
>> +               .pagesize = SZ_2K,
>> +               .erasesize = SZ_128K,
>> +               .id_len = 2,
>> +               .oobsize = 128,
>> +               .ecc.strength_ds = 8,
>> +               .ecc.step_ds = 512,
>> +       },
>> +       {
>> +               .name = "GD5F2GQ4UC",
>> +               .id = { NAND_MFR_GIGADEVICE, 0xb2 },
>> +               .chipsize = 256,
>> +               .pagesize = SZ_2K,
>> +               .erasesize = SZ_128K,
>> +               .id_len = 2,
>> +               .oobsize = 128,
>> +               .ecc.strength_ds = 8,
>> +               .ecc.step_ds = 512,
>> +       },
>> +       {
>> +               .name = "GD5F2GQ4RC",
>> +               .id = { NAND_MFR_GIGADEVICE, 0xa2 },
>> +               .chipsize = 256,
>> +               .pagesize = SZ_2K,
>> +               .erasesize = SZ_128K,
>> +               .id_len = 2,
>> +               .oobsize = 128,
>> +               .ecc.strength_ds = 8,
>> +               .ecc.step_ds = 512,
>> +       },
>> +       {
>> +               .name = "GD5F4GQ4UA",
>> +               .id = { NAND_MFR_GIGADEVICE, 0xf4 },
>> +               .chipsize = 512,
>> +               .pagesize = SZ_2K,
>> +               .erasesize = SZ_128K,
>> +               .id_len = 2,
>> +               .oobsize = 64,
>> +               .ecc.strength_ds = 8,
>> +               .ecc.step_ds = 512,
>> +       },
>> +       {
>> +               .name = "GD5F4GQ4RA",
>> +               .id = { NAND_MFR_GIGADEVICE, 0xe4 },
>> +               .chipsize = 512,
>> +               .pagesize = SZ_2K,
>> +               .erasesize = SZ_128K,
>> +               .id_len = 2,
>> +               .oobsize = 64,
>> +               .ecc.strength_ds = 8,
>> +               .ecc.step_ds = 512,
>> +       },
>> +       {
>> +               .name = "GD5F4GQ4UB",
>> +               .id = { NAND_MFR_GIGADEVICE, 0xd4 },
>> +               .chipsize = 512,
>> +               .pagesize = SZ_4K,
>> +               .erasesize = SZ_256K,
>> +               .id_len = 2,
>> +               .oobsize = 256,
>> +               .ecc.strength_ds = 8,
>> +               .ecc.step_ds = 512,
>> +       },
>> +       {
>> +               .name = "GD5F4GQ4RB",
>> +               .id = { NAND_MFR_GIGADEVICE, 0xc4 },
>> +               .chipsize = 512,
>> +               .pagesize = SZ_4K,
>> +               .erasesize = SZ_256K,
>> +               .id_len = 2,
>> +               .oobsize = 256,
>> +               .ecc.strength_ds = 8,
>> +               .ecc.step_ds = 512,
>> +       },
>> +       {
>> +               .name = "GD5F4GQ4UC",
>> +               .id = { NAND_MFR_GIGADEVICE, 0xb4 },
>> +               .chipsize = 512,
>> +               .pagesize = SZ_4K,
>> +               .erasesize = SZ_256K,
>> +               .id_len = 2,
>> +               .oobsize = 256,
>> +               .ecc.strength_ds = 8,
>> +               .ecc.step_ds = 512,
>> +       },
>> +       {
>> +               .name = "GD5F4GQ4RC",
>> +               .id = { NAND_MFR_GIGADEVICE, 0xa4 },
>> +               .chipsize = 512,
>> +               .pagesize = SZ_4K,
>> +               .erasesize = SZ_256K,
>> +               .id_len = 2,
>> +               .oobsize = 256,
>> +               .ecc.strength_ds = 8,
>> +               .ecc.step_ds = 512,
>> +       },
>> +};
>> +
>> +enum spi_nand_device_variant {
>> +       SPI_NAND_GENERIC,
>> +       SPI_NAND_GD5F,
>> +};
>> +
>> +struct spi_nand_device_cmd {
>> +
>> +       /*
>> +        * Command and address. I/O errors have been observed if a
>> +        * separate spi_transfer is used for command and address,
>> +        * so keep them together.
>> +        */
>> +       u32 n_cmd;
>> +       u8 cmd[5];
>> +
>> +       /* Tx data */
>> +       u32 n_tx;
>> +       u8 *tx_buf;
>> +
>> +       /* Rx data */
>> +       u32 n_rx;
>> +       u8 *rx_buf;
>> +       u8 rx_nbits;
>> +       u8 tx_nbits;
>> +};
>> +
>> +struct spi_nand_device {
>> +       struct spi_nand spi_nand;
>> +       struct spi_device *spi;
>> +
>> +       struct spi_nand_device_cmd cmd;
>> +};
>> +
>> +static int spi_nand_send_command(struct spi_device *spi,
>> +                                struct spi_nand_device_cmd *cmd)
>> +{
>> +       struct spi_message message;
>> +       struct spi_transfer x[2];
>> +
>> +       if (!cmd->n_cmd) {
>> +               dev_err(&spi->dev, "cannot send an empty command\n");
>> +               return -EINVAL;
>> +       }
>> +
>> +       if (cmd->n_tx && cmd->n_rx) {
>> +               dev_err(&spi->dev, "cannot send and receive data at the
>> same time\n");
>> +               return -EINVAL;
>> +       }
>> +
>> +       spi_message_init(&message);
>> +       memset(x, 0, sizeof(x));
>> +
>> +       /* Command and address */
>> +       x[0].len = cmd->n_cmd;
>> +       x[0].tx_buf = cmd->cmd;
>> +       x[0].tx_nbits = cmd->tx_nbits;
>> +       spi_message_add_tail(&x[0], &message);
>> +
>> +       /* Data to be transmitted */
>> +       if (cmd->n_tx) {
>> +               x[1].len = cmd->n_tx;
>> +               x[1].tx_buf = cmd->tx_buf;
>> +               x[1].tx_nbits = cmd->tx_nbits;
>> +               spi_message_add_tail(&x[1], &message);
>> +       }
>> +
>> +       /* Data to be received */
>> +       if (cmd->n_rx) {
>> +               x[1].len = cmd->n_rx;
>> +               x[1].rx_buf = cmd->rx_buf;
>> +               x[1].rx_nbits = cmd->rx_nbits;
>> +               spi_message_add_tail(&x[1], &message);
>> +       }
>> +
>> +       return spi_sync(spi, &message);
>> +}
>> +
>> +static int spi_nand_device_reset(struct spi_nand *snand)
>> +{
>> +       struct spi_nand_device *snand_dev = snand->priv;
>> +       struct spi_nand_device_cmd *cmd = &snand_dev->cmd;
>> +
>> +       memset(cmd, 0, sizeof(struct spi_nand_device_cmd));
>> +       cmd->n_cmd = 1;
>> +       cmd->cmd[0] = SPI_NAND_RESET;
>> +
>> +       dev_dbg(snand->dev, "%s\n", __func__);
>> +
>> +       return spi_nand_send_command(snand_dev->spi, cmd);
>> +}
>> +
>> +static int spi_nand_device_read_reg(struct spi_nand *snand, u8 opcode, u8
>> *buf)
>> +{
>> +       struct spi_nand_device *snand_dev = snand->priv;
>> +       struct spi_nand_device_cmd *cmd = &snand_dev->cmd;
>> +
>> +       memset(cmd, 0, sizeof(struct spi_nand_device_cmd));
>> +       cmd->n_cmd = 2;
>> +       cmd->cmd[0] = SPI_NAND_GET_FEATURE;
>> +       cmd->cmd[1] = opcode;
>> +       cmd->n_rx = 1;
>> +       cmd->rx_buf = buf;
>> +
>> +       dev_dbg(snand->dev, "%s: reg 0%x\n", __func__, opcode);
>> +
>> +       return spi_nand_send_command(snand_dev->spi, cmd);
>> +}
>> +
>> +static int spi_nand_device_write_reg(struct spi_nand *snand, u8 opcode,
>> u8 *buf)
>> +{
>> +       struct spi_nand_device *snand_dev = snand->priv;
>> +       struct spi_nand_device_cmd *cmd = &snand_dev->cmd;
>> +
>> +       memset(cmd, 0, sizeof(struct spi_nand_device_cmd));
>> +       cmd->n_cmd = 2;
>> +       cmd->cmd[0] = SPI_NAND_SET_FEATURE;
>> +       cmd->cmd[1] = opcode;
>> +       cmd->n_tx = 1;
>> +       cmd->tx_buf = buf;
>> +
>> +       dev_dbg(snand->dev, "%s: reg 0%x\n", __func__, opcode);
>> +
>> +       return spi_nand_send_command(snand_dev->spi, cmd);
>> +}
>> +
>> +static int spi_nand_device_write_enable(struct spi_nand *snand)
>> +{
>> +       struct spi_nand_device *snand_dev = snand->priv;
>> +       struct spi_nand_device_cmd *cmd = &snand_dev->cmd;
>> +
>> +       memset(cmd, 0, sizeof(struct spi_nand_device_cmd));
>> +       cmd->n_cmd = 1;
>> +       cmd->cmd[0] = SPI_NAND_WRITE_ENABLE;
>> +
>> +       dev_dbg(snand->dev, "%s\n", __func__);
>> +
>> +       return spi_nand_send_command(snand_dev->spi, cmd);
>> +}
>> +
>> +static int spi_nand_device_write_disable(struct spi_nand *snand)
>> +{
>> +       struct spi_nand_device *snand_dev = snand->priv;
>> +       struct spi_nand_device_cmd *cmd = &snand_dev->cmd;
>> +
>> +       memset(cmd, 0, sizeof(struct spi_nand_device_cmd));
>> +       cmd->n_cmd = 1;
>> +       cmd->cmd[0] = SPI_NAND_WRITE_DISABLE;
>> +
>> +       dev_dbg(snand->dev, "%s\n", __func__);
>> +
>> +       return spi_nand_send_command(snand_dev->spi, cmd);
>> +}
>> +
>> +static int spi_nand_device_write_page(struct spi_nand *snand,
>> +                                     unsigned int page_addr)
>> +{
>> +       struct spi_nand_device *snand_dev = snand->priv;
>> +       struct spi_nand_device_cmd *cmd = &snand_dev->cmd;
>> +
>> +       memset(cmd, 0, sizeof(struct spi_nand_device_cmd));
>> +       cmd->n_cmd = 4;
>> +       cmd->cmd[0] = SPI_NAND_PROGRAM_EXEC;
>> +       cmd->cmd[1] = (u8)((page_addr & 0xff0000) >> 16);
>> +       cmd->cmd[2] = (u8)((page_addr & 0xff00) >> 8);
>> +       cmd->cmd[3] = (u8)(page_addr & 0xff);
>> +
>> +       dev_dbg(snand->dev, "%s: page 0x%x\n", __func__, page_addr);
>> +
>> +       return spi_nand_send_command(snand_dev->spi, cmd);
>> +}
>> +
>> +static int spi_nand_device_store_cache(struct spi_nand *snand,
>> +                                      unsigned int page_offset, size_t
>> length,
>> +                                      u8 *write_buf)
>> +{
>> +       struct spi_nand_device *snand_dev = snand->priv;
>> +       struct spi_nand_device_cmd *cmd = &snand_dev->cmd;
>> +       struct spi_device *spi = snand_dev->spi;
>> +
>> +       memset(cmd, 0, sizeof(struct spi_nand_device_cmd));
>> +       cmd->n_cmd = 3;
>> +       cmd->cmd[0] = spi->mode & SPI_TX_QUAD ? SPI_NAND_PROGRAM_LOAD4 :
>> +                       SPI_NAND_PROGRAM_LOAD;
>> +       cmd->cmd[1] = (u8)((page_offset & 0xff00) >> 8);
>> +       cmd->cmd[2] = (u8)(page_offset & 0xff);
>> +       cmd->n_tx = length;
>> +       cmd->tx_buf = write_buf;
>> +       cmd->tx_nbits = spi->mode & SPI_TX_QUAD ? 4 : 1;
>> +
>> +       dev_dbg(snand->dev, "%s: offset 0x%x\n", __func__, page_offset);
>> +
>> +       return spi_nand_send_command(snand_dev->spi, cmd);
>> +}
>> +
>> +static int spi_nand_device_load_page(struct spi_nand *snand,
>> +                                    unsigned int page_addr)
>> +{
>> +       struct spi_nand_device *snand_dev = snand->priv;
>> +       struct spi_nand_device_cmd *cmd = &snand_dev->cmd;
>> +
>> +       memset(cmd, 0, sizeof(struct spi_nand_device_cmd));
>> +       cmd->n_cmd = 4;
>> +       cmd->cmd[0] = SPI_NAND_PAGE_READ;
>> +       cmd->cmd[1] = (u8)((page_addr & 0xff0000) >> 16);
>> +       cmd->cmd[2] = (u8)((page_addr & 0xff00) >> 8);
>> +       cmd->cmd[3] = (u8)(page_addr & 0xff);
>> +
>> +       dev_dbg(snand->dev, "%s: page 0x%x\n", __func__, page_addr);
>> +
>> +       return spi_nand_send_command(snand_dev->spi, cmd);
>> +}
>> +
>> +static int spi_nand_device_read_cache(struct spi_nand *snand,
>> +                                     unsigned int page_offset, size_t
>> length,
>> +                                     u8 *read_buf)
>> +{
>> +       struct spi_nand_device *snand_dev = snand->priv;
>> +       struct spi_nand_device_cmd *cmd = &snand_dev->cmd;
>> +       struct spi_device *spi = snand_dev->spi;
>> +
>> +       memset(cmd, 0, sizeof(struct spi_nand_device_cmd));
>> +       cmd->n_cmd = 4;
>> +       cmd->cmd[0] = (spi->mode & SPI_RX_QUAD) ? SPI_NAND_READ_CACHE_X4 :
>> +                       ((spi->mode & SPI_RX_DUAL) ?
>> SPI_NAND_READ_CACHE_X2 :
>> +                       SPI_NAND_READ_CACHE);
>> +       cmd->cmd[1] = (u8)((page_offset & 0xff00) >> 8);
>> +       cmd->cmd[2] = (u8)(page_offset & 0xff);
>> +       cmd->cmd[3] = 0; /* dummy byte */
>> +       cmd->n_rx = length;
>> +       cmd->rx_buf = read_buf;
>> +       cmd->rx_nbits = (spi->mode & SPI_RX_QUAD) ? 4 :
>> +                       ((spi->mode & SPI_RX_DUAL) ? 2 : 1);
>> +
>> +       dev_dbg(snand->dev, "%s: offset 0x%x\n", __func__, page_offset);
>> +
>> +       return spi_nand_send_command(snand_dev->spi, cmd);
>> +}
>> +
>> +static int spi_nand_device_block_erase(struct spi_nand *snand,
>> +                                      unsigned int page_addr)
>> +{
>> +       struct spi_nand_device *snand_dev = snand->priv;
>> +       struct spi_nand_device_cmd *cmd = &snand_dev->cmd;
>> +
>> +       memset(cmd, 0, sizeof(struct spi_nand_device_cmd));
>> +       cmd->n_cmd = 4;
>> +       cmd->cmd[0] = SPI_NAND_BLOCK_ERASE;
>> +       cmd->cmd[1] = (u8)((page_addr & 0xff0000) >> 16);
>> +       cmd->cmd[2] = (u8)((page_addr & 0xff00) >> 8);
>> +       cmd->cmd[3] = (u8)(page_addr & 0xff);
>> +
>> +       dev_dbg(snand->dev, "%s: block 0x%x\n", __func__, page_addr);
>> +
>> +       return spi_nand_send_command(snand_dev->spi, cmd);
>> +}
>> +
>> +static int spi_nand_gd5f_read_id(struct spi_nand *snand, u8 *buf)
>> +{
>> +       struct spi_nand_device *snand_dev = snand->priv;
>> +       struct spi_nand_device_cmd *cmd = &snand_dev->cmd;
>> +
>> +       memset(cmd, 0, sizeof(struct spi_nand_device_cmd));
>> +       cmd->n_cmd = 2;
>> +       cmd->cmd[0] = SPI_NAND_READ_ID;
>> +       cmd->cmd[1] = 0; /* dummy byte */
>> +       cmd->n_rx = SPI_NAND_GD5F_READID_LEN;
>> +       cmd->rx_buf = buf;
>> +
>> +       dev_dbg(snand->dev, "%s\n", __func__);
>> +
>> +       return spi_nand_send_command(snand_dev->spi, cmd);
>> +}
>> +
>> +static void spi_nand_gd5f_ecc_status(unsigned int status,
>> +                                    unsigned int *corrected,
>> +                                    unsigned int *ecc_error)
>> +{
>> +       unsigned int ecc_status = (status >> SPI_NAND_GD5F_ECC_SHIFT) &
>> +                                            SPI_NAND_GD5F_ECC_MASK;
>> +
>> +       *ecc_error = (ecc_status == SPI_NAND_GD5F_ECC_UNCORR) ? 1 : 0;
>> +       if (*ecc_error == 0)
>> +               *corrected = (ecc_status > 1) ? (2 + ecc_status) : 0;
>> +}
>> +
>> +struct nand_ecclayout *spi_nand_post_probe(u8 *id, int len)
>> +{
>> +       int i;
>> +       struct nand_flash_dev *nfd = NULL;
>> +
>> +       if (len < 2)
>> +               return NULL;
>> +
>> +       for (i = 0; i < ARRAY_SIZE(spi_nand_flash_ids); i++) {
>> +               if (spi_nand_flash_ids[i].id[0] == id[0] &&
>> +                   spi_nand_flash_ids[i].id[1] == id[1]) {
>> +                       nfd = &spi_nand_flash_ids[i];
>> +                       break;
>> +               }
>> +       }
>> +
>> +       if (!nfd)
>> +               return NULL;
>> +
>> +       switch (id[0])
>> +       {
>> +       case NAND_MFR_GIGADEVICE:
>> +               switch (nfd->oobsize) {
>> +               case 64:
>> +                       if (id[0x20] == 'S' &&
>> +                           id[0x21] == 'N' &&
>> +                           id[0x22] == 'F' &&
>> +                           id[0x23] == 'I')
>> +                               return &gd25_snfi_oob_64_layout;
>> +                       else
>> +                               return &gd25_oob_64_layout;
>> +               case 128:
>> +                       return &gd25_oob_128_layout;
>> +               case 256:
>> +                       return &gd25_oob_256_layout;
>> +               }
>> +       }
>> +
>> +       return NULL;
>> +}
>> +
>> +static int spi_nand_device_probe(struct spi_device *spi)
>> +{
>> +       enum spi_nand_device_variant variant;
>> +       struct spi_nand_device *priv;
>> +       struct spi_nand *snand;
>> +       int ret;
>> +
>> +       priv = devm_kzalloc(&spi->dev, sizeof(*priv), GFP_KERNEL);
>> +       if (!priv)
>> +               return -ENOMEM;
>> +
>> +       snand = &priv->spi_nand;
>> +
>> +       snand->read_cache = spi_nand_device_read_cache;
>> +       snand->load_page = spi_nand_device_load_page;
>> +       snand->store_cache = spi_nand_device_store_cache;
>> +       snand->write_page = spi_nand_device_write_page;
>> +       snand->write_reg = spi_nand_device_write_reg;
>> +       snand->read_reg = spi_nand_device_read_reg;
>> +       snand->block_erase = spi_nand_device_block_erase;
>> +       snand->reset = spi_nand_device_reset;
>> +       snand->write_enable = spi_nand_device_write_enable;
>> +       snand->write_disable = spi_nand_device_write_disable;
>> +       snand->dev = &spi->dev;
>> +       snand->priv = priv;
>> +
>> +       /* This'll mean we won't need to specify any specific compatible
>> string
>> +        * for a given device, and instead just support spi-nand.
>> +        */
>> +       variant = spi_get_device_id(spi)->driver_data;
>> +       switch (variant) {
>> +       case SPI_NAND_GD5F:
>> +               snand->read_id = spi_nand_gd5f_read_id;
>> +               snand->get_ecc_status = spi_nand_gd5f_ecc_status;
>> +               break;
>> +       default:
>> +               dev_err(snand->dev, "unknown device\n");
>> +               return -ENODEV;
>> +       }
>> +
>> +       spi_set_drvdata(spi, snand);
>> +       priv->spi = spi;
>> +
>> +       ret = spi_nand_register(snand, spi_nand_flash_ids);
>> +       if (ret)
>> +               return ret;
>> +       return 0;
>> +}
>> +
>> +static int spi_nand_device_remove(struct spi_device *spi)
>> +{
>> +       struct spi_nand *snand = spi_get_drvdata(spi);
>> +
>> +       spi_nand_unregister(snand);
>> +
>> +       return 0;
>> +}
>> +
>> +const struct spi_device_id spi_nand_id_table[] = {
>> +       { "spi-nand", SPI_NAND_GENERIC },
>> +       { "gd5f", SPI_NAND_GD5F },
>> +       { },
>> +};
>> +MODULE_DEVICE_TABLE(spi, spi_nand_id_table);
>> +
>> +static struct spi_driver spi_nand_device_driver = {
>> +       .driver = {
>> +               .name   = "spi_nand_device",
>> +               .owner  = THIS_MODULE,
>> +       },
>> +       .id_table = spi_nand_id_table,
>> +       .probe  = spi_nand_device_probe,
>> +       .remove = spi_nand_device_remove,
>> +};
>> +module_spi_driver(spi_nand_device_driver);
>> +
>> +MODULE_AUTHOR("Ezequiel Garcia <ezequiel.garcia@imgtec.com>");
>> +MODULE_DESCRIPTION("SPI NAND device support");
>> +MODULE_LICENSE("GPL v2");
>> diff --git a/target/linux/generic/files/include/linux/mtd/spi-nand.h
>> b/target/linux/generic/files/include/linux/mtd/spi-nand.h
>> new file mode 100644
>> index 0000000000..5fcc98e7bb
>> --- /dev/null
>> +++ b/target/linux/generic/files/include/linux/mtd/spi-nand.h
>> @@ -0,0 +1,56 @@
>> +/*
>> + * Copyright (C) 2014 Imagination Technologies Ltd.
>> + *
>> + * 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; version 2 of the License.
>> + */
>> +
>> +#ifndef __LINUX_MTD_SPI_NAND_H
>> +#define __LINUX_MTD_SPI_NAND_H
>> +
>> +#include <linux/mtd/mtd.h>
>> +#include <linux/mtd/nand.h>
>> +
>> +struct spi_nand {
>> +       struct nand_chip        nand_chip;
>> +       struct mtd_info         mtd;
>> +       struct device           *dev;
>> +       const char              *name;
>> +
>> +       u8                      *buf, *data_buf;
>> +       size_t                  buf_size;
>> +       off_t                   buf_start;
>> +       unsigned int            page_addr;
>> +       unsigned int            bitflips;
>> +       bool                    ecc;
>> +
>> +       int (*reset)(struct spi_nand *snand);
>> +       int (*read_id)(struct spi_nand *snand, u8 *buf);
>> +
>> +       int (*write_disable)(struct spi_nand *snand);
>> +       int (*write_enable)(struct spi_nand *snand);
>> +
>> +       int (*read_reg)(struct spi_nand *snand, u8 opcode, u8 *buf);
>> +       int (*write_reg)(struct spi_nand *snand, u8 opcode, u8 *buf);
>> +       void (*get_ecc_status)(unsigned int status,
>> +                              unsigned int *corrected,
>> +                              unsigned int *ecc_errors);
>> +
>> +       int (*store_cache)(struct spi_nand *snand, unsigned int
>> page_offset,
>> +                          size_t length, u8 *write_buf);
>> +       int (*write_page)(struct spi_nand *snand, unsigned int page_addr);
>> +       int (*load_page)(struct spi_nand *snand, unsigned int page_addr);
>> +       int (*read_cache)(struct spi_nand *snand, unsigned int
>> page_offset,
>> +                         size_t length, u8 *read_buf);
>> +       int (*block_erase)(struct spi_nand *snand, unsigned int
>> page_addr);
>> +
>> +       void *priv;
>> +};
>> +
>> +struct nand_ecclayout *spi_nand_post_probe(u8 *id, int len);
>> +
>> +int spi_nand_register(struct spi_nand *snand, struct nand_flash_dev
>> *flash_ids);
>> +void spi_nand_unregister(struct spi_nand *snand);
>> +
>> +#endif
>> diff --git
>> a/target/linux/generic/pending-4.4/452-mtd-nand-Check-length-of-ID-before-reading-bits-per-cell.patch
>> b/target/linux/generic/pending-4.4/452-mtd-nand-Check-length-of-ID-before-reading-bits-per-cell.patch
>> new file mode 100644
>> index 0000000000..60da4d6459
>> --- /dev/null
>> +++
>> b/target/linux/generic/pending-4.4/452-mtd-nand-Check-length-of-ID-before-reading-bits-per-cell.patch
>> @@ -0,0 +1,33 @@
>> +From 42ebff638003be18fab503b37de4ad7853244e95 Mon Sep 17 00:00:00 2001
>> +From: Ezequiel Garcia <ezequiel.garcia@imgtec.com>
>> +Date: Sat, 25 Feb 2017 15:58:22 +0000
>> +Subject: mtd: nand: Check length of ID before reading bits per cell
>> +
>> +The table-based NAND identification currently reads the number
>> +of bits per cell from the 3rd byte of the extended ID. This is done
>> +for the so-called 'full ID' devices; i.e. devices that have a known
>> +length ID.
>> +
>> +However, if the ID length is shorter than three, there's no 3rd byte,
>> +and so it's wrong to read the bits per cell from there. Fix this by
>> +adding a check for the ID length.
>> +
>> +(picked from
>> http://lists.infradead.org/pipermail/linux-mtd/2014-December/056764.html)
>> +
>> +Signed-off-by: Ezequiel Garcia <ezequiel.garcia@imgtec.com>
>> +---
>> + drivers/mtd/nand/nand_base.c | 3 ++-
>> + 1 file changed, 2 insertions(+), 1 deletion(-)
>> +
>> +--- a/drivers/mtd/nand/nand_base.c
>> ++++ b/drivers/mtd/nand/nand_base.c
>> +@@ -3758,7 +3758,8 @@ static bool find_full_id_nand(struct mtd
>> +               mtd->erasesize = type->erasesize;
>> +               mtd->oobsize = type->oobsize;
>> +
>> +-              chip->bits_per_cell = nand_get_bits_per_cell(id_data[2]);
>> ++              if (type->id_len > 2)
>> ++                      chip->bits_per_cell =
>> nand_get_bits_per_cell(id_data[2]);
>> +               chip->chipsize = (uint64_t)type->chipsize << 20;
>> +               chip->options |= type->options;
>> +               chip->ecc_strength_ds = NAND_ECC_STRENGTH(type);
>> diff --git
>> a/target/linux/generic/pending-4.4/453-mtd-nand-Add-JEDEC-manufacturer-ID-for-Gigadevice.patch
>> b/target/linux/generic/pending-4.4/453-mtd-nand-Add-JEDEC-manufacturer-ID-for-Gigadevice.patch
>> new file mode 100644
>> index 0000000000..70b311be70
>> --- /dev/null
>> +++
>> b/target/linux/generic/pending-4.4/453-mtd-nand-Add-JEDEC-manufacturer-ID-for-Gigadevice.patch
>> @@ -0,0 +1,35 @@
>> +From a4bc33b205fd9b1db862f1e45173dba57b0fa57f Mon Sep 17 00:00:00 2001
>> +From: Ezequiel Garcia <ezequiel.garcia@imgtec.com>
>> +Date: Sat, 25 Feb 2017 15:43:09 +0000
>> +Subject: mtd: nand: Add JEDEC manufacturer ID for Gigadevice
>> +
>> +This commit adds Gigadevice to the list of manufacturer ID and name
>> strings.
>> +
>> +(picked from
>> http://lists.infradead.org/pipermail/linux-mtd/2014-December/056765.html)
>> +
>> +Signed-off-by: Ezequiel Garcia <ezequiel.garcia@imgtec.com>
>> +---
>> + drivers/mtd/nand/nand_ids.c | 1 +
>> + include/linux/mtd/nand.h    | 1 +
>> + 2 files changed, 2 insertions(+)
>> +
>> +--- a/drivers/mtd/nand/nand_ids.c
>> ++++ b/drivers/mtd/nand/nand_ids.c
>> +@@ -181,6 +181,7 @@ struct nand_manufacturers nand_manuf_ids
>> +       {NAND_MFR_SANDISK, "SanDisk"},
>> +       {NAND_MFR_INTEL, "Intel"},
>> +       {NAND_MFR_ATO, "ATO"},
>> ++      {NAND_MFR_GIGADEVICE, "Gigadevice"},
>> +       {0x0, "Unknown"}
>> + };
>> +
>> +--- a/include/linux/mtd/nand.h
>> ++++ b/include/linux/mtd/nand.h
>> +@@ -736,6 +736,7 @@ static inline void nand_set_controller_d
>> + #define NAND_MFR_SANDISK      0x45
>> + #define NAND_MFR_INTEL                0x89
>> + #define NAND_MFR_ATO          0x9b
>> ++#define NAND_MFR_GIGADEVICE   0xc8
>> +
>> + /* The maximum expected count of bytes in the NAND ID sequence */
>> + #define NAND_MAX_ID_LEN 8
>> diff --git
>> a/target/linux/generic/pending-4.4/454-mtd-Introduce-SPI-NAND-framework.patch
>> b/target/linux/generic/pending-4.4/454-mtd-Introduce-SPI-NAND-framework.patch
>> new file mode 100644
>> index 0000000000..18c703026b
>> --- /dev/null
>> +++
>> b/target/linux/generic/pending-4.4/454-mtd-Introduce-SPI-NAND-framework.patch
>> @@ -0,0 +1,20 @@
>> +--- a/drivers/mtd/Kconfig
>> ++++ b/drivers/mtd/Kconfig
>> +@@ -369,6 +369,8 @@ source "drivers/mtd/onenand/Kconfig"
>> +
>> + source "drivers/mtd/lpddr/Kconfig"
>> +
>> ++source "drivers/mtd/spi-nand/Kconfig"
>> ++
>> + source "drivers/mtd/spi-nor/Kconfig"
>> +
>> + source "drivers/mtd/ubi/Kconfig"
>> +--- a/drivers/mtd/Makefile
>> ++++ b/drivers/mtd/Makefile
>> +@@ -35,5 +35,6 @@ inftl-objs           := inftlcore.o inftlmount.o
>> +
>> + obj-y         += chips/ lpddr/ maps/ devices/ nand/ onenand/ tests/
>> +
>> ++obj-$(CONFIG_MTD_SPI_NAND)    += spi-nand/
>> + obj-$(CONFIG_MTD_SPI_NOR)     += spi-nor/
>> + obj-$(CONFIG_MTD_UBI)         += ubi/
>> --
>> 2.11.0
>>
>>
>> _______________________________________________
>> Lede-dev mailing list
>> Lede-dev@lists.infradead.org
>> http://lists.infradead.org/mailman/listinfo/lede-dev
>
>
Michael Yartys via Lede-dev Sept. 3, 2017, 5:56 p.m. UTC | #2
The sender domain has a DMARC Reject/Quarantine policy which disallows
sending mailing list messages using the original "From" header.

To mitigate this problem, the original message has been wrapped
automatically by the mailing list software.
On Sunday, September 3, 2017 1:43:59 PM CEST hackpascal wrote:
> From: Weijie Gao <hackpascal@gmail.com>
> 
> This patch adds generic SPI-NAND framework for linux-4.4.
> 
> Files come from patches of target pistachio, but have lots of modifications
> to add full support for GD5F series.
> 
> Signed-off-by: Weijie Gao <hackpascal@gmail.com>
> ---
Hm, from what I know multiple "generic" SPI-NAND driver/framework exist.

The current favourite of linux-mtd seems to be from Micron:
<http://lists.infradead.org/pipermail/linux-mtd/2017-April/073649.html>
(Altought, development has sadly stalled as well ;( )
<http://lists.infradead.org/pipermail/linux-mtd/2017-April/073681.html>

Is this correct? Or was there a recent attempt at upstreaming "this"
older framework too that I can't find?

Note:
The kernel had some support for spinand since v3.13 via the
staging "mt29f_spinand" driver. I had some success with it
on the RT-AC58U. All I needed there was to add a custom 
definition for the Winbond W25N01GV chip [0] and enable
CONFIG_MTD_SPINAND_MT29F=y
CONFIG_MTD_SPINAND_ONDIEECC=y

Maybe you too can get away with something similar to this?
Until linux-mtd _finally_ knows what they want to merge.

Regards,
Christian

[0] <https://github.com/lede-project/source/blob/master/target/linux/ipq806x/patches-4.9/104-mtd-nand-add-Winbond-manufacturer-and-chip.patch>
Weijie Gao Sept. 4, 2017, 5:48 a.m. UTC | #3
Yes. If I search "spi-nand linux" in Google, Micron's framework is the
first result.
However, Micron's patches are based on higher version of kernel, which
require lots of modifications to port to linux-4.4.
I choose the framework from LEDE's
target/linux/pistachio/patches-4.9/, which requires only few changes.

I've read nearly all datasheets from GigaDevice, Micron, Winbond,
Macronix, and other manufacturers. I found that their chips are not
full compatiable with each other.
For example, the ECC status bits definition. GigaDevice uses one
status to indicate the unrecoverable bits while Winbond uses two
statuses to indicate that.
And stacked die selection command is different between Micron and
Winbond. And Mircon's chip has its own plane selection bit.

Even chips of the same manufacturer, the page size/OOB size and layout
can be different. You can see differences of GigaDevice's chips in
this patch.

So, the mt29f_spinand is not enough, it's designed only for Micron's chips.

The framework I chose from
<https://github.com/lede-project/source/tree/master/target/linux/pistachio/patches-4.9/413-mtd-Introduce-SPI-NAND-framework.patch>
<https://github.com/lede-project/source/tree/master/target/linux/pistachio/patches-4.9/414-mtd-spi-nand-Support-Gigadevice-GD5F.patch>
has two layers, one for generic nand operation and one for
manufacturer-specific operations, such as the ecc status check.

I think this is a good practice.

Sincerely,
Weijie

2017-09-04 1:56 GMT+08:00 Christian Lamparter via Lede-dev
<lede-dev@lists.infradead.org>:
> The sender domain has a DMARC Reject/Quarantine policy which disallows
> sending mailing list messages using the original "From" header.
>
> To mitigate this problem, the original message has been wrapped
> automatically by the mailing list software.
>
> ---------- 已转发邮件 ----------
> From: Christian Lamparter <chunkeey@googlemail.com>
> To: lede-dev@lists.infradead.org
> Cc: hackpascal <hackpascal@gmail.com>
> Bcc:
> Date: Sun, 03 Sep 2017 19:56:22 +0200
> Subject: Re: [LEDE-DEV] [PATCHv2 3/4] kernel/4.4: add generic spi-nand framework
> On Sunday, September 3, 2017 1:43:59 PM CEST hackpascal wrote:
>> From: Weijie Gao <hackpascal@gmail.com>
>>
>> This patch adds generic SPI-NAND framework for linux-4.4.
>>
>> Files come from patches of target pistachio, but have lots of modifications
>> to add full support for GD5F series.
>>
>> Signed-off-by: Weijie Gao <hackpascal@gmail.com>
>> ---
> Hm, from what I know multiple "generic" SPI-NAND driver/framework exist.
>
> The current favourite of linux-mtd seems to be from Micron:
> <http://lists.infradead.org/pipermail/linux-mtd/2017-April/073649.html>
> (Altought, development has sadly stalled as well ;( )
> <http://lists.infradead.org/pipermail/linux-mtd/2017-April/073681.html>
>
> Is this correct? Or was there a recent attempt at upstreaming "this"
> older framework too that I can't find?
>
> Note:
> The kernel had some support for spinand since v3.13 via the
> staging "mt29f_spinand" driver. I had some success with it
> on the RT-AC58U. All I needed there was to add a custom
> definition for the Winbond W25N01GV chip [0] and enable
> CONFIG_MTD_SPINAND_MT29F=y
> CONFIG_MTD_SPINAND_ONDIEECC=y
>
> Maybe you too can get away with something similar to this?
> Until linux-mtd _finally_ knows what they want to merge.
>
> Regards,
> Christian
>
> [0] <https://github.com/lede-project/source/blob/master/target/linux/ipq806x/patches-4.9/104-mtd-nand-add-Winbond-manufacturer-and-chip.patch>
>
>
> _______________________________________________
> Lede-dev mailing list
> Lede-dev@lists.infradead.org
> http://lists.infradead.org/mailman/listinfo/lede-dev
>
Michael Yartys via Lede-dev Sept. 5, 2017, 7:25 p.m. UTC | #4
The sender domain has a DMARC Reject/Quarantine policy which disallows
sending mailing list messages using the original "From" header.

To mitigate this problem, the original message has been wrapped
automatically by the mailing list software.
On Monday, September 4, 2017 1:48:38 PM CEST Weijie Gao wrote:
> Yes. If I search "spi-nand linux" in Google, Micron's framework is the
> first result.
> However, Micron's patches are based on higher version of kernel, which
> require lots of modifications to port to linux-4.4.
> I choose the framework from LEDE's
> target/linux/pistachio/patches-4.9/, which requires only few changes.

Yes exactly this is my concern: all the porting of of out-of-tree patches.
linux-4.4 has only about five to six more month left before the patches
will run out:
<https://www.kernel.org/category/releases.html> (Projected EOL: Feb, 2018).
So question is, what will ar71xx do in six month? Will it be updated to 4.9?
Or will it be ported to the upcoming 4.14? (I don't know?) Would you be 
willing to stick around and be prepared to revisit the patch in this case too?

> I've read nearly all datasheets from GigaDevice, Micron, Winbond,
> Macronix, and other manufacturers. I found that their chips are not
> full compatiable with each other.
> For example, the ECC status bits definition. GigaDevice uses one
> status to indicate the unrecoverable bits while Winbond uses two
> statuses to indicate that.

> And stacked die selection command is different between Micron and
> Winbond. And Mircon's chip has its own plane selection bit.
> 
> Even chips of the same manufacturer, the page size/OOB size and layout
> can be different. You can see differences of GigaDevice's chips in
> this patch.
> 
> So, the mt29f_spinand is not enough, it's designed only for
> Micron's chips.
A micron engineer put it like this:
<http://lists.infradead.org/pipermail/linux-mtd/2014-November/056531.html>

" As far as I've seen from the datasheets I have available (SPI NAND
GigaDevice 1Gb/2Gb/4Gb, Micron 1Gb/2Gb/4Gb), the command sequencing
is the same for read/write/erase operations (read: enable ECC, read to cache,
wait, read from cache, check for errors, disable ECC; write: enable ECC,
write enable, program load, program execute, wait, verify, disable ECC;
erase: write enable, erase block, verify).

Regarding the stream of bytes for each command, the problematic
commands are the read from cache ones (read, fast read, read x2,
read x4, read dual read quad) that have different command format
(additional dummy bytes, a plane select bit to be set).
Other differences are in the structure of the protection and status registers
(some of them use more ECC and protection bits according to the size
of the chip). Also, each has a different ECC layout "

The funny thing is, that a micron engineer made a patch back in the day
that had support for both the MT29F and your GD5F with
<http://lkml.iu.edu/hypermail/linux/kernel/1501.0/03747.html>
From the look of it, it seems easy to support the GD5F that way...

So much so, that "this unification habbit" was picked up by imgtec
for the pistachio!

Just look at this pull request from an ImgTec engineer titled:
"Add Imagination's Pistachio SoC and (Ci40) Marduk platform support" for
openwrt project:
<https://github.com/openwrt/openwrt/pull/201/files#diff-b70d21c394349496f5f6a7e6b5a05a7c>

Look at the target/linux/pistachio/patches-4.4/0001-mtd-nand-Introduce-SPI-NAND-framework.patch
commit message:

"5. mtd: spi-nand: Support common SPI NAND devices
This commit uses the recently introduced SPI NAND framework to support
Micron MT29F and Gigadevice GD5F serial NAND devices. These two families
of devices are fairly similar and so they are supported with only minimal
differences."

> The framework I chose from
> <https://github.com/lede-project/source/tree/master/target/linux/pistachio/patches-4.9/413-mtd-Introduce-SPI-NAND-framework.patch>
> <https://github.com/lede-project/source/tree/master/target/linux/pistachio/patches-4.9/414-mtd-spi-nand-Support-Gigadevice-GD5F.patch>
> has two layers, one for generic nand operation and one for
> manufacturer-specific operations, such as the ecc status check.
> 
> I think this is a good practice.
Depends on how ar71xx will play out. For now, assume let's assume that
ar71xx is updated to 4.9. Then these patches would have to be moved to
either ar71xx's kernel patch directory: target/linux/ar71xx/patches-4.9
Or someone would have to *move* the existing 4.9 version out of 
target/linux/pistachio/patches-4.9 and into target/linux/generic/patches-4.9.
(Otherwise, quilt will complain because of the duplicated patches).

Regards,
Christian
Weijie Gao Sept. 6, 2017, 12:54 p.m. UTC | #5
Yes, I have to admit operations of GigaDevice's chips are the same.
All the chips have the same read/write/erase command sequences.
Differences are plane selection bits (Micron), die selection
(Micron/Winbond), buffered read/continuous read (Winbond) and ECC
status bits.
These differences can be abstracted to different functions chosen by
the flash id. So this won't be a problem.

As for the patches, may be I can submit patches to both generic-4.4
and generic-4.9, and remove patches from pistachio.
So this won't be a problem when ar71xx updates linux-4.9.

Sincerely,
Weijie


2017-09-06 3:25 GMT+08:00 Christian Lamparter via Lede-dev
<lede-dev@lists.infradead.org>:
> The sender domain has a DMARC Reject/Quarantine policy which disallows
> sending mailing list messages using the original "From" header.
>
> To mitigate this problem, the original message has been wrapped
> automatically by the mailing list software.
>
>
> ---------- 已转发邮件 ----------
> From: Christian Lamparter <chunkeey@googlemail.com>
> To: Weijie Gao <hackpascal@gmail.com>
> Cc: LEDE Development List <lede-dev@lists.infradead.org>
> Bcc:
> Date: Tue, 05 Sep 2017 21:24:48 +0200
> Subject: Re: [LEDE-DEV] [PATCHv2 3/4] kernel/4.4: add generic spi-nand framework
> On Monday, September 4, 2017 1:48:38 PM CEST Weijie Gao wrote:
>> Yes. If I search "spi-nand linux" in Google, Micron's framework is the
>> first result.
>> However, Micron's patches are based on higher version of kernel, which
>> require lots of modifications to port to linux-4.4.
>> I choose the framework from LEDE's
>> target/linux/pistachio/patches-4.9/, which requires only few changes.
>
> Yes exactly this is my concern: all the porting of of out-of-tree patches.
> linux-4.4 has only about five to six more month left before the patches
> will run out:
> <https://www.kernel.org/category/releases.html> (Projected EOL: Feb, 2018).
> So question is, what will ar71xx do in six month? Will it be updated to 4.9?
> Or will it be ported to the upcoming 4.14? (I don't know?) Would you be
> willing to stick around and be prepared to revisit the patch in this case too?
>
>> I've read nearly all datasheets from GigaDevice, Micron, Winbond,
>> Macronix, and other manufacturers. I found that their chips are not
>> full compatiable with each other.
>> For example, the ECC status bits definition. GigaDevice uses one
>> status to indicate the unrecoverable bits while Winbond uses two
>> statuses to indicate that.
>
>> And stacked die selection command is different between Micron and
>> Winbond. And Mircon's chip has its own plane selection bit.
>>
>> Even chips of the same manufacturer, the page size/OOB size and layout
>> can be different. You can see differences of GigaDevice's chips in
>> this patch.
>>
>> So, the mt29f_spinand is not enough, it's designed only for
>> Micron's chips.
> A micron engineer put it like this:
> <http://lists.infradead.org/pipermail/linux-mtd/2014-November/056531.html>
>
> " As far as I've seen from the datasheets I have available (SPI NAND
> GigaDevice 1Gb/2Gb/4Gb, Micron 1Gb/2Gb/4Gb), the command sequencing
> is the same for read/write/erase operations (read: enable ECC, read to cache,
> wait, read from cache, check for errors, disable ECC; write: enable ECC,
> write enable, program load, program execute, wait, verify, disable ECC;
> erase: write enable, erase block, verify).
>
> Regarding the stream of bytes for each command, the problematic
> commands are the read from cache ones (read, fast read, read x2,
> read x4, read dual read quad) that have different command format
> (additional dummy bytes, a plane select bit to be set).
> Other differences are in the structure of the protection and status registers
> (some of them use more ECC and protection bits according to the size
> of the chip). Also, each has a different ECC layout "
>
> The funny thing is, that a micron engineer made a patch back in the day
> that had support for both the MT29F and your GD5F with
> <http://lkml.iu.edu/hypermail/linux/kernel/1501.0/03747.html>
> From the look of it, it seems easy to support the GD5F that way...
>
> So much so, that "this unification habbit" was picked up by imgtec
> for the pistachio!
>
> Just look at this pull request from an ImgTec engineer titled:
> "Add Imagination's Pistachio SoC and (Ci40) Marduk platform support" for
> openwrt project:
> <https://github.com/openwrt/openwrt/pull/201/files#diff-b70d21c394349496f5f6a7e6b5a05a7c>
>
> Look at the target/linux/pistachio/patches-4.4/0001-mtd-nand-Introduce-SPI-NAND-framework.patch
> commit message:
>
> "5. mtd: spi-nand: Support common SPI NAND devices
> This commit uses the recently introduced SPI NAND framework to support
> Micron MT29F and Gigadevice GD5F serial NAND devices. These two families
> of devices are fairly similar and so they are supported with only minimal
> differences."
>
>> The framework I chose from
>> <https://github.com/lede-project/source/tree/master/target/linux/pistachio/patches-4.9/413-mtd-Introduce-SPI-NAND-framework.patch>
>> <https://github.com/lede-project/source/tree/master/target/linux/pistachio/patches-4.9/414-mtd-spi-nand-Support-Gigadevice-GD5F.patch>
>> has two layers, one for generic nand operation and one for
>> manufacturer-specific operations, such as the ecc status check.
>>
>> I think this is a good practice.
> Depends on how ar71xx will play out. For now, assume let's assume that
> ar71xx is updated to 4.9. Then these patches would have to be moved to
> either ar71xx's kernel patch directory: target/linux/ar71xx/patches-4.9
> Or someone would have to *move* the existing 4.9 version out of
> target/linux/pistachio/patches-4.9 and into target/linux/generic/patches-4.9.
> (Otherwise, quilt will complain because of the duplicated patches).
>
> Regards,
> Christian
>
>
> _______________________________________________
> Lede-dev mailing list
> Lede-dev@lists.infradead.org
> http://lists.infradead.org/mailman/listinfo/lede-dev
>
Felix Fietkau Sept. 10, 2017, 5:25 a.m. UTC | #6
On 2017-09-06 18:24, Weijie Gao wrote:
> Yes, I have to admit operations of GigaDevice's chips are the same.
> All the chips have the same read/write/erase command sequences.
> Differences are plane selection bits (Micron), die selection
> (Micron/Winbond), buffered read/continuous read (Winbond) and ECC
> status bits.
> These differences can be abstracted to different functions chosen by
> the flash id. So this won't be a problem.
> 
> As for the patches, may be I can submit patches to both generic-4.4
> and generic-4.9, and remove patches from pistachio.
> So this won't be a problem when ar71xx updates linux-4.9.
I think it's a bad idea to integrate new code that is based on an old
framework rejected by upstream. Even if it's more work, I strongly
suggest using the new code by Micron.
If there's no way around using the old code for now, it should
definitely not be placed in generic/ because this could potentially lead
to even more people picking it up, thus creating long term porting issues.

- Felix
Weijie Gao Sept. 10, 2017, 4:27 p.m. UTC | #7
ok. I'll be waiting until the framework merged into the upstream kernel.

2017-09-10 13:25 GMT+08:00 Felix Fietkau <nbd@nbd.name>:
> On 2017-09-06 18:24, Weijie Gao wrote:
>> Yes, I have to admit operations of GigaDevice's chips are the same.
>> All the chips have the same read/write/erase command sequences.
>> Differences are plane selection bits (Micron), die selection
>> (Micron/Winbond), buffered read/continuous read (Winbond) and ECC
>> status bits.
>> These differences can be abstracted to different functions chosen by
>> the flash id. So this won't be a problem.
>>
>> As for the patches, may be I can submit patches to both generic-4.4
>> and generic-4.9, and remove patches from pistachio.
>> So this won't be a problem when ar71xx updates linux-4.9.
> I think it's a bad idea to integrate new code that is based on an old
> framework rejected by upstream. Even if it's more work, I strongly
> suggest using the new code by Micron.
> If there's no way around using the old code for now, it should
> definitely not be placed in generic/ because this could potentially lead
> to even more people picking it up, thus creating long term porting issues.
>
> - Felix
diff mbox series

Patch

diff --git a/target/linux/generic/config-4.4 b/target/linux/generic/config-4.4
index 1c0af9597f..0fd7c1d49c 100644
--- a/target/linux/generic/config-4.4
+++ b/target/linux/generic/config-4.4
@@ -2369,6 +2369,8 @@  CONFIG_MTD_ROOTFS_ROOT_DEV=y
 # CONFIG_MTD_SLRAM is not set
 # CONFIG_MTD_SM_COMMON is not set
 # CONFIG_MTD_SPINAND_MT29F is not set
+# CONFIG_MTD_SPI_NAND is not set
+# CONFIG_MTD_SPI_NAND_DEVICES is not set
 # CONFIG_MTD_SPI_NOR is not set
 # CONFIG_MTD_SPI_NOR_USE_4K_SECTORS is not set
 CONFIG_MTD_SPLIT=y
diff --git a/target/linux/generic/files/drivers/mtd/spi-nand/Kconfig b/target/linux/generic/files/drivers/mtd/spi-nand/Kconfig
new file mode 100644
index 0000000000..ab6bb6c7fa
--- /dev/null
+++ b/target/linux/generic/files/drivers/mtd/spi-nand/Kconfig
@@ -0,0 +1,17 @@ 
+menuconfig MTD_SPI_NAND
+	tristate "SPI NAND device support"
+	depends on MTD
+	select MTD_NAND
+	help
+	  This is the framework for the SPI NAND.
+
+if MTD_SPI_NAND
+
+config MTD_SPI_NAND_DEVICES
+	tristate "Support for SPI NAND devices"
+	default y
+	depends on MTD_SPI_NAND
+	help
+	  Select this option if you require support for SPI NAND devices.
+
+endif # MTD_SPI_NAND
diff --git a/target/linux/generic/files/drivers/mtd/spi-nand/Makefile b/target/linux/generic/files/drivers/mtd/spi-nand/Makefile
new file mode 100644
index 0000000000..6e460d1814
--- /dev/null
+++ b/target/linux/generic/files/drivers/mtd/spi-nand/Makefile
@@ -0,0 +1,2 @@ 
+obj-$(CONFIG_MTD_SPI_NAND)		+= spi-nand-base.o
+obj-$(CONFIG_MTD_SPI_NAND_DEVICES)     += spi-nand-device.o
diff --git a/target/linux/generic/files/drivers/mtd/spi-nand/spi-nand-base.c b/target/linux/generic/files/drivers/mtd/spi-nand/spi-nand-base.c
new file mode 100644
index 0000000000..07dad5397a
--- /dev/null
+++ b/target/linux/generic/files/drivers/mtd/spi-nand/spi-nand-base.c
@@ -0,0 +1,588 @@ 
+/*
+ * Copyright (C) 2014 Imagination Technologies Ltd.
+ *
+ * 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; version 2 of the License.
+ *
+ * Notes:
+ * 1. Erase and program operations need to call write_enable() first,
+ *    to clear the enable bit. This bit is cleared automatically after
+ *    the erase or program operation.
+ *
+ */
+
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mtd/nand.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/partitions.h>
+#include <linux/mtd/spi-nand.h>
+#include <linux/of.h>
+#include <linux/slab.h>
+
+/* Registers common to all devices */
+#define SPI_NAND_LOCK_REG		0xa0
+#define SPI_NAND_PROT_UNLOCK_ALL	0x0
+
+#define SPI_NAND_FEATURE_REG		0xb0
+#define SPI_NAND_ECC_EN			BIT(4)
+#define SPI_NAND_QUAD_EN		BIT(0)
+
+#define SPI_NAND_STATUS_REG		0xc0
+#define SPI_NAND_STATUS_REG_ECC_MASK	0x3
+#define SPI_NAND_STATUS_REG_ECC_SHIFT	4
+#define SPI_NAND_STATUS_REG_PROG_FAIL	BIT(3)
+#define SPI_NAND_STATUS_REG_ERASE_FAIL	BIT(2)
+#define SPI_NAND_STATUS_REG_WREN	BIT(1)
+#define SPI_NAND_STATUS_REG_BUSY	BIT(0)
+
+#define SPI_NAND_CMD_BUF_LEN		8
+
+/* Rewind and fill the buffer with 0xff */
+static void spi_nand_clear_buffer(struct spi_nand *snand)
+{
+	snand->buf_start = 0;
+	memset(snand->data_buf, 0xff, snand->buf_size);
+}
+
+static int spi_nand_enable_ecc(struct spi_nand *snand)
+{
+	int ret;
+
+	ret = snand->read_reg(snand, SPI_NAND_FEATURE_REG, snand->buf);
+	if (ret)
+		return ret;
+
+	snand->buf[0] |= SPI_NAND_ECC_EN;
+	ret = snand->write_reg(snand, SPI_NAND_FEATURE_REG, snand->buf);
+	if (ret)
+		return ret;
+	snand->ecc = true;
+
+	return 0;
+}
+
+static int spi_nand_disable_ecc(struct spi_nand *snand)
+{
+	int ret;
+
+	ret = snand->read_reg(snand, SPI_NAND_FEATURE_REG, snand->buf);
+	if (ret)
+		return ret;
+
+	snand->buf[0] &= ~SPI_NAND_ECC_EN;
+	ret = snand->write_reg(snand, SPI_NAND_FEATURE_REG, snand->buf);
+	if (ret)
+		return ret;
+	snand->ecc = false;
+
+	return 0;
+}
+
+static int spi_nand_enable_quad(struct spi_nand *snand)
+{
+	int ret;
+
+	ret = snand->read_reg(snand, SPI_NAND_FEATURE_REG, snand->buf);
+	if (ret)
+		return ret;
+
+	snand->buf[0] |= SPI_NAND_QUAD_EN;
+	ret = snand->write_reg(snand, SPI_NAND_FEATURE_REG, snand->buf);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+/*
+ * Wait until the status register busy bit is cleared.
+ * Returns a negatie errno on error or time out, and a non-negative status
+ * value if the device is ready.
+ */
+static int spi_nand_wait_till_ready(struct spi_nand *snand)
+{
+	unsigned long deadline = jiffies + msecs_to_jiffies(100);
+	bool timeout = false;
+	int ret;
+
+	/*
+	 * Perhaps we should set a different timeout for each
+	 * operation (reset, read, write, erase).
+	 */
+	while (!timeout) {
+		if (time_after_eq(jiffies, deadline))
+			timeout = true;
+
+		ret = snand->read_reg(snand, SPI_NAND_STATUS_REG, snand->buf);
+		if (ret < 0) {
+			dev_err(snand->dev, "error reading status register\n");
+			return ret;
+		} else if (!(snand->buf[0] & SPI_NAND_STATUS_REG_BUSY)) {
+			return snand->buf[0];
+		}
+
+		cond_resched();
+	}
+
+	dev_err(snand->dev, "operation timed out\n");
+
+	return -ETIMEDOUT;
+}
+
+static int spi_nand_reset(struct spi_nand *snand)
+{
+	int ret;
+
+	ret = snand->reset(snand);
+	if (ret < 0) {
+		dev_err(snand->dev, "reset command failed\n");
+		return ret;
+	}
+
+	/*
+	 * The NAND core won't wait after a device reset, so we need
+	 * to do that here.
+	 */
+	ret = spi_nand_wait_till_ready(snand);
+	if (ret < 0)
+		return ret;
+	return 0;
+}
+
+static int spi_nand_status(struct spi_nand *snand)
+{
+	int ret, status;
+
+	ret = snand->read_reg(snand, SPI_NAND_STATUS_REG, snand->buf);
+	if (ret < 0) {
+		dev_err(snand->dev, "error reading status register\n");
+		return ret;
+	}
+	status = snand->buf[0];
+
+	/* Convert this into standard NAND_STATUS values */
+	if (status & SPI_NAND_STATUS_REG_BUSY)
+		snand->buf[0] = 0;
+	else
+		snand->buf[0] = NAND_STATUS_READY;
+
+	if (status & SPI_NAND_STATUS_REG_PROG_FAIL ||
+	    status & SPI_NAND_STATUS_REG_ERASE_FAIL)
+		snand->buf[0] |= NAND_STATUS_FAIL;
+
+	/*
+	 * Since we unlock the entire device at initialization, unconditionally
+	 * set the WP bit to indicate it's not protected.
+	 */
+	snand->buf[0] |= NAND_STATUS_WP;
+	return 0;
+}
+
+static int spi_nand_erase(struct spi_nand *snand, int page_addr)
+{
+	int ret;
+
+	ret = snand->write_enable(snand);
+	if (ret < 0) {
+		dev_err(snand->dev, "write enable command failed\n");
+		return ret;
+	}
+
+	ret = snand->block_erase(snand, page_addr);
+	if (ret < 0) {
+		dev_err(snand->dev, "block erase command failed\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+static int spi_nand_write(struct spi_nand *snand)
+{
+	int ret;
+
+	/* Enable quad mode */
+	ret = spi_nand_enable_quad(snand);
+	if (ret) {
+		dev_err(snand->dev, "error %d enabling quad mode\n", ret);
+		return ret;
+	}
+	/* Store the page to cache */
+	ret = snand->store_cache(snand, 0, snand->buf_size, snand->data_buf);
+	if (ret < 0) {
+		dev_err(snand->dev, "error %d storing page 0x%x to cache\n",
+			ret, snand->page_addr);
+		return ret;
+	}
+
+	ret = snand->write_enable(snand);
+	if (ret < 0) {
+		dev_err(snand->dev, "write enable command failed\n");
+		return ret;
+	}
+
+	/* Get page from the device cache into our internal buffer */
+	ret = snand->write_page(snand, snand->page_addr);
+	if (ret < 0) {
+		dev_err(snand->dev, "error %d reading page 0x%x from cache\n",
+			ret, snand->page_addr);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int spi_nand_read_id(struct spi_nand *snand)
+{
+	int ret;
+
+	ret = snand->read_id(snand, snand->data_buf);
+	if (ret < 0) {
+		dev_err(snand->dev, "error %d reading ID\n", ret);
+		return ret;
+	}
+	return 0;
+}
+
+static int spi_nand_read_page(struct spi_nand *snand, unsigned int page_addr,
+			      unsigned int page_offset, size_t length)
+{
+	unsigned int corrected = 0, ecc_error = 0;
+	int ret;
+
+	/* Load a page into the cache register */
+	ret = snand->load_page(snand, page_addr);
+	if (ret < 0) {
+		dev_err(snand->dev, "error %d loading page 0x%x to cache\n",
+			ret, page_addr);
+		return ret;
+	}
+
+	ret = spi_nand_wait_till_ready(snand);
+	if (ret < 0)
+		return ret;
+
+	if (snand->ecc) {
+		snand->get_ecc_status(ret, &corrected, &ecc_error);
+		snand->bitflips = corrected;
+
+		/*
+		 * If there's an ECC error, print a message and notify MTD
+		 * about it. Then complete the read, to load actual data on
+		 * the buffer (instead of the status result).
+		 */
+		if (ecc_error) {
+			dev_err(snand->dev,
+				"internal ECC error reading page 0x%x\n",
+				page_addr);
+			snand->mtd.ecc_stats.failed++;
+		} else {
+			snand->mtd.ecc_stats.corrected += corrected;
+		}
+	}
+
+	/* Enable quad mode */
+	ret = spi_nand_enable_quad(snand);
+	if (ret) {
+		dev_err(snand->dev, "error %d enabling quad mode\n", ret);
+		return ret;
+	}
+	/* Get page from the device cache into our internal buffer */
+	ret = snand->read_cache(snand, page_offset, length, snand->data_buf);
+	if (ret < 0) {
+		dev_err(snand->dev, "error %d reading page 0x%x from cache\n",
+			ret, page_addr);
+		return ret;
+	}
+	return 0;
+}
+
+static u8 spi_nand_read_byte(struct mtd_info *mtd)
+{
+	struct nand_chip *chip = mtd->priv;
+	struct spi_nand *snand = chip->priv;
+	char val = 0xff;
+
+	if (snand->buf_start < snand->buf_size)
+		val = snand->data_buf[snand->buf_start++];
+	return val;
+}
+
+static void spi_nand_write_buf(struct mtd_info *mtd, const u8 *buf, int len)
+{
+	struct nand_chip *chip = mtd->priv;
+	struct spi_nand *snand = chip->priv;
+	size_t n = min_t(size_t, len, snand->buf_size - snand->buf_start);
+
+	memcpy(snand->data_buf + snand->buf_start, buf, n);
+	snand->buf_start += n;
+}
+
+static void spi_nand_read_buf(struct mtd_info *mtd, u8 *buf, int len)
+{
+	struct nand_chip *chip = mtd->priv;
+	struct spi_nand *snand = chip->priv;
+	size_t n = min_t(size_t, len, snand->buf_size - snand->buf_start);
+
+	memcpy(buf, snand->data_buf + snand->buf_start, n);
+	snand->buf_start += n;
+}
+
+static int spi_nand_write_page_hwecc(struct mtd_info *mtd,
+		struct nand_chip *chip, const uint8_t *buf, int oob_required,
+		int page)
+{
+	chip->write_buf(mtd, buf, mtd->writesize);
+	chip->write_buf(mtd, chip->oob_poi, mtd->oobsize);
+
+	return 0;
+}
+
+static int spi_nand_read_page_hwecc(struct mtd_info *mtd,
+		struct nand_chip *chip, uint8_t *buf, int oob_required,
+		int page)
+{
+	struct spi_nand *snand = chip->priv;
+
+	chip->read_buf(mtd, buf, mtd->writesize);
+	chip->read_buf(mtd, chip->oob_poi, mtd->oobsize);
+
+	return snand->bitflips;
+}
+
+static int spi_nand_waitfunc(struct mtd_info *mtd, struct nand_chip *chip)
+{
+	struct spi_nand *snand = chip->priv;
+	int ret;
+
+	ret = spi_nand_wait_till_ready(snand);
+
+	if (ret < 0) {
+		return NAND_STATUS_FAIL;
+	} else if (ret & SPI_NAND_STATUS_REG_PROG_FAIL) {
+		dev_err(snand->dev, "page program failed\n");
+		return NAND_STATUS_FAIL;
+	} else if (ret & SPI_NAND_STATUS_REG_ERASE_FAIL) {
+		dev_err(snand->dev, "block erase failed\n");
+		return NAND_STATUS_FAIL;
+	}
+
+	return NAND_STATUS_READY;
+}
+
+static void spi_nand_cmdfunc(struct mtd_info *mtd, unsigned int command,
+			     int column, int page_addr)
+{
+	struct nand_chip *chip = mtd->priv;
+	struct spi_nand *snand = chip->priv;
+
+	/*
+	 * In case there's any unsupported command, let's make sure
+	 * we don't keep garbage around in the buffer.
+	 */
+	if (command != NAND_CMD_PAGEPROG) {
+		spi_nand_clear_buffer(snand);
+		snand->page_addr = 0;
+	}
+
+	switch (command) {
+	case NAND_CMD_READ0:
+		spi_nand_read_page(snand, page_addr, 0, mtd->writesize);
+		break;
+	case NAND_CMD_READOOB:
+		spi_nand_disable_ecc(snand);
+		spi_nand_read_page(snand, page_addr, mtd->writesize,
+				   mtd->oobsize);
+		spi_nand_enable_ecc(snand);
+		break;
+	case NAND_CMD_READID:
+		spi_nand_read_id(snand);
+		break;
+	case NAND_CMD_ERASE1:
+		spi_nand_erase(snand, page_addr);
+		break;
+	case NAND_CMD_ERASE2:
+		/* There's nothing to do here, as the erase is one-step */
+		break;
+	case NAND_CMD_SEQIN:
+		snand->buf_start = column;
+		snand->page_addr = page_addr;
+		break;
+	case NAND_CMD_PAGEPROG:
+		spi_nand_write(snand);
+		break;
+	case NAND_CMD_STATUS:
+		spi_nand_status(snand);
+		break;
+	case NAND_CMD_RESET:
+		spi_nand_reset(snand);
+		break;
+	default:
+		dev_err(&mtd->dev, "unknown command 0x%x\n", command);
+	}
+}
+
+static void spi_nand_select_chip(struct mtd_info *mtd, int chip)
+{
+	/* We need this to override the default */
+}
+
+static bool spi_nand_get_oob_layout(struct mtd_info *mtd, struct nand_ecclayout **ooblayout)
+{
+	struct nand_chip *chip = mtd->priv;
+	struct spi_nand *snand = chip->priv;
+	u8 id[0x24];	/* Maximum for GD5F */
+	struct nand_ecclayout *new_ooblayout;
+
+	spi_nand_clear_buffer(snand);
+	snand->page_addr = 0;
+
+	/* Send the command for reading device ID */
+	spi_nand_read_id(snand);
+
+	/* Read ID bytes */
+	spi_nand_read_buf(mtd, id, sizeof (id));
+
+	/* Get OOB layout */
+	new_ooblayout = spi_nand_post_probe(id, sizeof (id));
+
+	if (new_ooblayout && ooblayout)
+		*ooblayout = new_ooblayout;
+
+	return new_ooblayout != NULL;
+}
+
+int spi_nand_check(struct spi_nand *snand)
+{
+	if (!snand->dev)
+		return -ENODEV;
+	if (!snand->read_cache)
+		return -ENODEV;
+	if (!snand->load_page)
+		return -ENODEV;
+	if (!snand->store_cache)
+		return -ENODEV;
+	if (!snand->write_page)
+		return -ENODEV;
+	if (!snand->write_reg)
+		return -ENODEV;
+	if (!snand->read_reg)
+		return -ENODEV;
+	if (!snand->block_erase)
+		return -ENODEV;
+	if (!snand->reset)
+		return -ENODEV;
+	if (!snand->write_enable)
+		return -ENODEV;
+	if (!snand->write_disable)
+		return -ENODEV;
+	if (!snand->get_ecc_status)
+		return -ENODEV;
+	return 0;
+}
+
+int spi_nand_register(struct spi_nand *snand, struct nand_flash_dev *flash_ids)
+{
+	struct nand_chip *chip = &snand->nand_chip;
+	struct mtd_info *mtd = &snand->mtd;
+	int ret;
+
+	/* Let's check all the hooks are in-place so we don't panic later */
+	ret = spi_nand_check(snand);
+	if (ret)
+		return ret;
+
+	chip->priv = snand;
+	chip->read_buf	= spi_nand_read_buf;
+	chip->write_buf	= spi_nand_write_buf;
+	chip->read_byte	= spi_nand_read_byte;
+	chip->cmdfunc	= spi_nand_cmdfunc;
+	chip->waitfunc	= spi_nand_waitfunc;
+	chip->select_chip = spi_nand_select_chip;
+	chip->options |= NAND_NO_SUBPAGE_WRITE;
+	chip->bits_per_cell = 1;
+
+	chip->ecc.read_page	= spi_nand_read_page_hwecc;
+	chip->ecc.write_page	= spi_nand_write_page_hwecc;
+	chip->ecc.mode		= NAND_ECC_HW;
+
+	if (!mtd->name)
+		mtd->name = dev_name(snand->dev);
+	mtd->owner = THIS_MODULE;
+	mtd->priv = chip;
+	mtd->type = MTD_NANDFLASH;
+	mtd->flags = MTD_CAP_NANDFLASH;
+
+	/* Allocate buffer to be used to read/write the internal registers */
+	snand->buf = kmalloc(SPI_NAND_CMD_BUF_LEN, GFP_KERNEL);
+	if (!snand->buf)
+		return -ENOMEM;
+
+	/* This is enabled at device power up but we'd better make sure */
+	ret = spi_nand_enable_ecc(snand);
+	if (ret)
+		return ret;
+
+	/* Preallocate buffer for flash identification (NAND_CMD_READID) */
+	snand->buf_size = SPI_NAND_CMD_BUF_LEN;
+	snand->data_buf = kmalloc(snand->buf_size, GFP_KERNEL);
+
+	ret = nand_scan_ident(mtd, 1, flash_ids);
+	if (ret)
+		return ret;
+
+	/*
+	 * SPI NAND has on-die ECC, which means we can correct as much as
+	 * we are required to. This must be done after identification of
+	 * the device.
+	 */
+	chip->ecc.strength = chip->ecc_strength_ds;
+	chip->ecc.size = chip->ecc_step_ds;
+
+	/* Re-check manufacturer and device IDs to get proper OOB layout */
+	if (!spi_nand_get_oob_layout(mtd, &chip->ecc.layout)) {
+		dev_err(snand->dev, "OOB layout not found\n");
+		return -EINVAL;
+	}
+
+	/*
+	 * Unlock all the device before calling nand_scan_tail. This is needed
+	 * in case the in-flash bad block table needs to be created.
+	 * We could override __nand_unlock(), but since it's not currently used
+	 * by the NAND core we call this explicitly.
+	 */
+	snand->buf[0] = SPI_NAND_PROT_UNLOCK_ALL;
+	ret = snand->write_reg(snand, SPI_NAND_LOCK_REG, snand->buf);
+	if (ret)
+		return ret;
+
+	/* Free the buffer and allocate a good one, to fit a page plus OOB */
+	kfree(snand->data_buf);
+
+	snand->buf_size = mtd->writesize + mtd->oobsize;
+	snand->data_buf = kmalloc(snand->buf_size, GFP_KERNEL);
+	if (!snand->data_buf)
+		return -ENOMEM;
+
+	ret = nand_scan_tail(mtd);
+	if (ret)
+		return ret;
+
+	return mtd_device_register(mtd, NULL, 0);
+}
+EXPORT_SYMBOL_GPL(spi_nand_register);
+
+void spi_nand_unregister(struct spi_nand *snand)
+{
+	kfree(snand->buf);
+	kfree(snand->data_buf);
+}
+EXPORT_SYMBOL_GPL(spi_nand_unregister);
+
+MODULE_AUTHOR("Ezequiel Garcia <ezequiel.garcia@imgtec.com>");
+MODULE_DESCRIPTION("Framework for SPI NAND");
+MODULE_LICENSE("GPL v2");
diff --git a/target/linux/generic/files/drivers/mtd/spi-nand/spi-nand-device.c b/target/linux/generic/files/drivers/mtd/spi-nand/spi-nand-device.c
new file mode 100644
index 0000000000..9fb793493b
--- /dev/null
+++ b/target/linux/generic/files/drivers/mtd/spi-nand/spi-nand-device.c
@@ -0,0 +1,761 @@ 
+/*
+ * Copyright (C) 2014 Imagination Technologies Ltd.
+ * Copyright (C) 2017 Weijie Gao <hackpascal@gmail.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; version 2 of the License.
+ *
+ * Notes:
+ * 1. We avoid using a stack-allocated buffer for SPI messages. Using
+ *    a kmalloced buffer is probably better, given we shouldn't assume
+ *    any particular usage by SPI core.
+ */
+
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/errno.h>
+#include <linux/module.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/partitions.h>
+#include <linux/mtd/spi-nand.h>
+#include <linux/sizes.h>
+#include <linux/spi/spi.h>
+
+/* SPI NAND commands */
+#define	SPI_NAND_WRITE_ENABLE		0x06
+#define	SPI_NAND_WRITE_DISABLE		0x04
+#define	SPI_NAND_GET_FEATURE		0x0f
+#define	SPI_NAND_SET_FEATURE		0x1f
+#define	SPI_NAND_PAGE_READ		0x13
+#define	SPI_NAND_READ_CACHE		0x03
+#define	SPI_NAND_FAST_READ_CACHE	0x0b
+#define	SPI_NAND_READ_CACHE_X2		0x3b
+#define	SPI_NAND_READ_CACHE_X4		0x6b
+#define	SPI_NAND_READ_CACHE_DUAL_IO	0xbb
+#define	SPI_NAND_READ_CACHE_QUAD_IO	0xeb
+#define	SPI_NAND_READ_ID		0x9f
+#define	SPI_NAND_PROGRAM_LOAD		0x02
+#define	SPI_NAND_PROGRAM_LOAD4		0x32
+#define	SPI_NAND_PROGRAM_EXEC		0x10
+#define	SPI_NAND_PROGRAM_LOAD_RANDOM	0x84
+#define	SPI_NAND_PROGRAM_LOAD_RANDOM4	0xc4
+#define	SPI_NAND_BLOCK_ERASE		0xd8
+#define	SPI_NAND_RESET			0xff
+
+#define SPI_NAND_GD5F_READID_LEN	0x24
+
+#define SPI_NAND_GD5F_ECC_MASK		(BIT(0) | BIT(1) | BIT(2))
+#define SPI_NAND_GD5F_ECC_UNCORR	(BIT(0) | BIT(1) | BIT(2))
+#define SPI_NAND_GD5F_ECC_SHIFT		4
+
+/* Used for GD5FxGQ4UAYIG */
+static struct nand_ecclayout gd25_oob_64_layout = {
+	.eccbytes = 16,
+	.eccpos = {
+		12, 13, 14, 15, 28, 29, 30, 31,
+		44, 45, 46, 47, 60, 61, 62, 63
+	},
+	/* Not including spare regions that are not ECC-ed */
+	.oobavail = 32,
+	.oobfree = {
+		{
+			.offset = 4,
+			.length = 8
+		}, {
+			.offset = 20,
+			.length = 8
+		}, {
+			.offset = 36,
+			.length = 8
+		}, {
+			.offset = 52,
+			.length = 8
+		}
+	}
+};
+
+/* Used for GD5FxGQ4UAY with "SNFI" on ID addr. 0x20 */
+static struct nand_ecclayout gd25_snfi_oob_64_layout = {
+	.eccbytes = 32,
+	.eccpos = {
+		8, 9, 10, 11, 12, 13, 14, 15,
+		24, 25, 26, 27, 28, 29, 30, 31,
+		40, 41, 42, 43, 44, 45, 46, 47,
+		56, 57, 58, 59, 60, 61, 62, 63
+	},
+	/* Not including spare regions that are not ECC-ed */
+	.oobavail = 32,
+	.oobfree = {
+		{
+			.offset = 4,
+			.length = 4
+		}, {
+			.offset = 20,
+			.length = 4
+		}, {
+			.offset = 36,
+			.length = 4
+		}, {
+			.offset = 52,
+			.length = 4
+		}
+	}
+};
+
+static struct nand_ecclayout gd25_oob_128_layout = {
+	.eccbytes = 64,
+	.eccpos = {
+		64, 65, 66, 67, 68, 69, 70, 71,
+		72, 73, 74, 75, 76, 77, 78, 79,
+		80, 81, 82, 83, 84, 85, 86, 87,
+		88, 89, 90, 91, 92, 93, 94, 95,
+		96, 97, 98, 99, 100, 101, 102, 103,
+		104, 105, 106, 107, 108, 109, 110, 111,
+		112, 113, 114, 115, 116, 117, 118, 119,
+		120, 121, 122, 123, 124, 125, 126, 127
+	},
+	.oobavail = 63,
+	.oobfree = {
+		{
+			.offset = 1,
+			.length = 63,
+		}
+	},
+};
+
+static struct nand_ecclayout gd25_oob_256_layout = {
+	.eccbytes = 128,
+	.eccpos = {
+		128, 129, 130, 131, 132, 133, 134, 135,
+		136, 137, 138, 139, 140, 141, 142, 143,
+		144, 145, 146, 147, 148, 149, 150, 151,
+		152, 153, 154, 155, 156, 157, 158, 159,
+		160, 161, 162, 163, 164, 165, 166, 167,
+		168, 169, 170, 171, 172, 173, 174, 175,
+		176, 177, 178, 179, 180, 181, 182, 183,
+		184, 185, 186, 187, 188, 189, 190, 191,
+		192, 193, 194, 195, 196, 197, 198, 199,
+		200, 201, 202, 203, 204, 205, 206, 207,
+		208, 209, 210, 211, 212, 213, 214, 215,
+		216, 217, 218, 219, 220, 221, 222, 223,
+		224, 225, 226, 227, 228, 229, 230, 231,
+		232, 233, 234, 235, 236, 237, 238, 239,
+		240, 241, 242, 243, 244, 245, 246, 247,
+		248, 249, 250, 251, 252, 253, 254, 255
+	},
+	.oobavail = 127,
+	.oobfree = {
+		{
+			.offset = 1,
+			.length = 127,
+		}
+	},
+};
+
+static struct nand_flash_dev spi_nand_flash_ids[] = {
+	{
+		.name = "GD5F1GQ4UA",
+		.id = { NAND_MFR_GIGADEVICE, 0xf1 },
+		.chipsize = 128,
+		.pagesize = SZ_2K,
+		.erasesize = SZ_128K,
+		.id_len = 2,
+		.oobsize = 64,
+		.ecc.strength_ds = 8,
+		.ecc.step_ds = 512,
+	},
+	{
+		.name = "GD5F1GQ4RA",
+		.id = { NAND_MFR_GIGADEVICE, 0xe1 },
+		.chipsize = 128,
+		.pagesize = SZ_2K,
+		.erasesize = SZ_128K,
+		.id_len = 2,
+		.oobsize = 64,
+		.ecc.strength_ds = 8,
+		.ecc.step_ds = 512,
+	},
+	{
+		.name = "GD5F1GQ4UB",
+		.id = { NAND_MFR_GIGADEVICE, 0xd1 },
+		.chipsize = 128,
+		.pagesize = SZ_2K,
+		.erasesize = SZ_128K,
+		.id_len = 2,
+		.oobsize = 128,
+		.ecc.strength_ds = 8,
+		.ecc.step_ds = 512,
+	},
+	{
+		.name = "GD5F1GQ4RB",
+		.id = { NAND_MFR_GIGADEVICE, 0xc1 },
+		.chipsize = 128,
+		.pagesize = SZ_2K,
+		.erasesize = SZ_128K,
+		.id_len = 2,
+		.oobsize = 128,
+		.ecc.strength_ds = 8,
+		.ecc.step_ds = 512,
+	},
+	{
+		.name = "GD5F1GQ4UC",
+		.id = { NAND_MFR_GIGADEVICE, 0xb1 },
+		.chipsize = 128,
+		.pagesize = SZ_2K,
+		.erasesize = SZ_128K,
+		.id_len = 2,
+		.oobsize = 128,
+		.ecc.strength_ds = 8,
+		.ecc.step_ds = 512,
+	},
+	{
+		.name = "GD5F1GQ4RC",
+		.id = { NAND_MFR_GIGADEVICE, 0xa1 },
+		.chipsize = 128,
+		.pagesize = SZ_2K,
+		.erasesize = SZ_128K,
+		.id_len = 2,
+		.oobsize = 128,
+		.ecc.strength_ds = 8,
+		.ecc.step_ds = 512,
+	},
+	{
+		.name = "GD5F2GQ4UA",
+		.id = { NAND_MFR_GIGADEVICE, 0xf2 },
+		.chipsize = 256,
+		.pagesize = SZ_2K,
+		.erasesize = SZ_128K,
+		.id_len = 2,
+		.oobsize = 64,
+		.ecc.strength_ds = 8,
+		.ecc.step_ds = 512,
+	},
+	{
+		.name = "GD5F2GQ4RA",
+		.id = { NAND_MFR_GIGADEVICE, 0xe2 },
+		.chipsize = 256,
+		.pagesize = SZ_2K,
+		.erasesize = SZ_128K,
+		.id_len = 2,
+		.oobsize = 64,
+		.ecc.strength_ds = 8,
+		.ecc.step_ds = 512,
+	},
+	{
+		.name = "GD5F2GQ4UB",
+		.id = { NAND_MFR_GIGADEVICE, 0xd2 },
+		.chipsize = 256,
+		.pagesize = SZ_2K,
+		.erasesize = SZ_128K,
+		.id_len = 2,
+		.oobsize = 128,
+		.ecc.strength_ds = 8,
+		.ecc.step_ds = 512,
+	},
+	{
+		.name = "GD5F2GQ4RB",
+		.id = { NAND_MFR_GIGADEVICE, 0xc2 },
+		.chipsize = 256,
+		.pagesize = SZ_2K,
+		.erasesize = SZ_128K,
+		.id_len = 2,
+		.oobsize = 128,
+		.ecc.strength_ds = 8,
+		.ecc.step_ds = 512,
+	},
+	{
+		.name = "GD5F2GQ4UC",
+		.id = { NAND_MFR_GIGADEVICE, 0xb2 },
+		.chipsize = 256,
+		.pagesize = SZ_2K,
+		.erasesize = SZ_128K,
+		.id_len = 2,
+		.oobsize = 128,
+		.ecc.strength_ds = 8,
+		.ecc.step_ds = 512,
+	},
+	{
+		.name = "GD5F2GQ4RC",
+		.id = { NAND_MFR_GIGADEVICE, 0xa2 },
+		.chipsize = 256,
+		.pagesize = SZ_2K,
+		.erasesize = SZ_128K,
+		.id_len = 2,
+		.oobsize = 128,
+		.ecc.strength_ds = 8,
+		.ecc.step_ds = 512,
+	},
+	{
+		.name = "GD5F4GQ4UA",
+		.id = { NAND_MFR_GIGADEVICE, 0xf4 },
+		.chipsize = 512,
+		.pagesize = SZ_2K,
+		.erasesize = SZ_128K,
+		.id_len = 2,
+		.oobsize = 64,
+		.ecc.strength_ds = 8,
+		.ecc.step_ds = 512,
+	},
+	{
+		.name = "GD5F4GQ4RA",
+		.id = { NAND_MFR_GIGADEVICE, 0xe4 },
+		.chipsize = 512,
+		.pagesize = SZ_2K,
+		.erasesize = SZ_128K,
+		.id_len = 2,
+		.oobsize = 64,
+		.ecc.strength_ds = 8,
+		.ecc.step_ds = 512,
+	},
+	{
+		.name = "GD5F4GQ4UB",
+		.id = { NAND_MFR_GIGADEVICE, 0xd4 },
+		.chipsize = 512,
+		.pagesize = SZ_4K,
+		.erasesize = SZ_256K,
+		.id_len = 2,
+		.oobsize = 256,
+		.ecc.strength_ds = 8,
+		.ecc.step_ds = 512,
+	},
+	{
+		.name = "GD5F4GQ4RB",
+		.id = { NAND_MFR_GIGADEVICE, 0xc4 },
+		.chipsize = 512,
+		.pagesize = SZ_4K,
+		.erasesize = SZ_256K,
+		.id_len = 2,
+		.oobsize = 256,
+		.ecc.strength_ds = 8,
+		.ecc.step_ds = 512,
+	},
+	{
+		.name = "GD5F4GQ4UC",
+		.id = { NAND_MFR_GIGADEVICE, 0xb4 },
+		.chipsize = 512,
+		.pagesize = SZ_4K,
+		.erasesize = SZ_256K,
+		.id_len = 2,
+		.oobsize = 256,
+		.ecc.strength_ds = 8,
+		.ecc.step_ds = 512,
+	},
+	{
+		.name = "GD5F4GQ4RC",
+		.id = { NAND_MFR_GIGADEVICE, 0xa4 },
+		.chipsize = 512,
+		.pagesize = SZ_4K,
+		.erasesize = SZ_256K,
+		.id_len = 2,
+		.oobsize = 256,
+		.ecc.strength_ds = 8,
+		.ecc.step_ds = 512,
+	},
+};
+
+enum spi_nand_device_variant {
+	SPI_NAND_GENERIC,
+	SPI_NAND_GD5F,
+};
+
+struct spi_nand_device_cmd {
+
+	/*
+	 * Command and address. I/O errors have been observed if a
+	 * separate spi_transfer is used for command and address,
+	 * so keep them together.
+	 */
+	u32 n_cmd;
+	u8 cmd[5];
+
+	/* Tx data */
+	u32 n_tx;
+	u8 *tx_buf;
+
+	/* Rx data */
+	u32 n_rx;
+	u8 *rx_buf;
+	u8 rx_nbits;
+	u8 tx_nbits;
+};
+
+struct spi_nand_device {
+	struct spi_nand	spi_nand;
+	struct spi_device *spi;
+
+	struct spi_nand_device_cmd cmd;
+};
+
+static int spi_nand_send_command(struct spi_device *spi,
+				 struct spi_nand_device_cmd *cmd)
+{
+	struct spi_message message;
+	struct spi_transfer x[2];
+
+	if (!cmd->n_cmd) {
+		dev_err(&spi->dev, "cannot send an empty command\n");
+		return -EINVAL;
+	}
+
+	if (cmd->n_tx && cmd->n_rx) {
+		dev_err(&spi->dev, "cannot send and receive data at the same time\n");
+		return -EINVAL;
+	}
+
+	spi_message_init(&message);
+	memset(x, 0, sizeof(x));
+
+	/* Command and address */
+	x[0].len = cmd->n_cmd;
+	x[0].tx_buf = cmd->cmd;
+	x[0].tx_nbits = cmd->tx_nbits;
+	spi_message_add_tail(&x[0], &message);
+
+	/* Data to be transmitted */
+	if (cmd->n_tx) {
+		x[1].len = cmd->n_tx;
+		x[1].tx_buf = cmd->tx_buf;
+		x[1].tx_nbits = cmd->tx_nbits;
+		spi_message_add_tail(&x[1], &message);
+	}
+
+	/* Data to be received */
+	if (cmd->n_rx) {
+		x[1].len = cmd->n_rx;
+		x[1].rx_buf = cmd->rx_buf;
+		x[1].rx_nbits = cmd->rx_nbits;
+		spi_message_add_tail(&x[1], &message);
+	}
+
+	return spi_sync(spi, &message);
+}
+
+static int spi_nand_device_reset(struct spi_nand *snand)
+{
+	struct spi_nand_device *snand_dev = snand->priv;
+	struct spi_nand_device_cmd *cmd = &snand_dev->cmd;
+
+	memset(cmd, 0, sizeof(struct spi_nand_device_cmd));
+	cmd->n_cmd = 1;
+	cmd->cmd[0] = SPI_NAND_RESET;
+
+	dev_dbg(snand->dev, "%s\n", __func__);
+
+	return spi_nand_send_command(snand_dev->spi, cmd);
+}
+
+static int spi_nand_device_read_reg(struct spi_nand *snand, u8 opcode, u8 *buf)
+{
+	struct spi_nand_device *snand_dev = snand->priv;
+	struct spi_nand_device_cmd *cmd = &snand_dev->cmd;
+
+	memset(cmd, 0, sizeof(struct spi_nand_device_cmd));
+	cmd->n_cmd = 2;
+	cmd->cmd[0] = SPI_NAND_GET_FEATURE;
+	cmd->cmd[1] = opcode;
+	cmd->n_rx = 1;
+	cmd->rx_buf = buf;
+
+	dev_dbg(snand->dev, "%s: reg 0%x\n", __func__, opcode);
+
+	return spi_nand_send_command(snand_dev->spi, cmd);
+}
+
+static int spi_nand_device_write_reg(struct spi_nand *snand, u8 opcode, u8 *buf)
+{
+	struct spi_nand_device *snand_dev = snand->priv;
+	struct spi_nand_device_cmd *cmd = &snand_dev->cmd;
+
+	memset(cmd, 0, sizeof(struct spi_nand_device_cmd));
+	cmd->n_cmd = 2;
+	cmd->cmd[0] = SPI_NAND_SET_FEATURE;
+	cmd->cmd[1] = opcode;
+	cmd->n_tx = 1;
+	cmd->tx_buf = buf;
+
+	dev_dbg(snand->dev, "%s: reg 0%x\n", __func__, opcode);
+
+	return spi_nand_send_command(snand_dev->spi, cmd);
+}
+
+static int spi_nand_device_write_enable(struct spi_nand *snand)
+{
+	struct spi_nand_device *snand_dev = snand->priv;
+	struct spi_nand_device_cmd *cmd = &snand_dev->cmd;
+
+	memset(cmd, 0, sizeof(struct spi_nand_device_cmd));
+	cmd->n_cmd = 1;
+	cmd->cmd[0] = SPI_NAND_WRITE_ENABLE;
+
+	dev_dbg(snand->dev, "%s\n", __func__);
+
+	return spi_nand_send_command(snand_dev->spi, cmd);
+}
+
+static int spi_nand_device_write_disable(struct spi_nand *snand)
+{
+	struct spi_nand_device *snand_dev = snand->priv;
+	struct spi_nand_device_cmd *cmd = &snand_dev->cmd;
+
+	memset(cmd, 0, sizeof(struct spi_nand_device_cmd));
+	cmd->n_cmd = 1;
+	cmd->cmd[0] = SPI_NAND_WRITE_DISABLE;
+
+	dev_dbg(snand->dev, "%s\n", __func__);
+
+	return spi_nand_send_command(snand_dev->spi, cmd);
+}
+
+static int spi_nand_device_write_page(struct spi_nand *snand,
+				      unsigned int page_addr)
+{
+	struct spi_nand_device *snand_dev = snand->priv;
+	struct spi_nand_device_cmd *cmd = &snand_dev->cmd;
+
+	memset(cmd, 0, sizeof(struct spi_nand_device_cmd));
+	cmd->n_cmd = 4;
+	cmd->cmd[0] = SPI_NAND_PROGRAM_EXEC;
+	cmd->cmd[1] = (u8)((page_addr & 0xff0000) >> 16);
+	cmd->cmd[2] = (u8)((page_addr & 0xff00) >> 8);
+	cmd->cmd[3] = (u8)(page_addr & 0xff);
+
+	dev_dbg(snand->dev, "%s: page 0x%x\n", __func__, page_addr);
+
+	return spi_nand_send_command(snand_dev->spi, cmd);
+}
+
+static int spi_nand_device_store_cache(struct spi_nand *snand,
+				       unsigned int page_offset, size_t length,
+				       u8 *write_buf)
+{
+	struct spi_nand_device *snand_dev = snand->priv;
+	struct spi_nand_device_cmd *cmd = &snand_dev->cmd;
+	struct spi_device *spi = snand_dev->spi;
+
+	memset(cmd, 0, sizeof(struct spi_nand_device_cmd));
+	cmd->n_cmd = 3;
+	cmd->cmd[0] = spi->mode & SPI_TX_QUAD ? SPI_NAND_PROGRAM_LOAD4 :
+			SPI_NAND_PROGRAM_LOAD;
+	cmd->cmd[1] = (u8)((page_offset & 0xff00) >> 8);
+	cmd->cmd[2] = (u8)(page_offset & 0xff);
+	cmd->n_tx = length;
+	cmd->tx_buf = write_buf;
+	cmd->tx_nbits = spi->mode & SPI_TX_QUAD ? 4 : 1;
+
+	dev_dbg(snand->dev, "%s: offset 0x%x\n", __func__, page_offset);
+
+	return spi_nand_send_command(snand_dev->spi, cmd);
+}
+
+static int spi_nand_device_load_page(struct spi_nand *snand,
+				     unsigned int page_addr)
+{
+	struct spi_nand_device *snand_dev = snand->priv;
+	struct spi_nand_device_cmd *cmd = &snand_dev->cmd;
+
+	memset(cmd, 0, sizeof(struct spi_nand_device_cmd));
+	cmd->n_cmd = 4;
+	cmd->cmd[0] = SPI_NAND_PAGE_READ;
+	cmd->cmd[1] = (u8)((page_addr & 0xff0000) >> 16);
+	cmd->cmd[2] = (u8)((page_addr & 0xff00) >> 8);
+	cmd->cmd[3] = (u8)(page_addr & 0xff);
+
+	dev_dbg(snand->dev, "%s: page 0x%x\n", __func__, page_addr);
+
+	return spi_nand_send_command(snand_dev->spi, cmd);
+}
+
+static int spi_nand_device_read_cache(struct spi_nand *snand,
+				      unsigned int page_offset, size_t length,
+				      u8 *read_buf)
+{
+	struct spi_nand_device *snand_dev = snand->priv;
+	struct spi_nand_device_cmd *cmd = &snand_dev->cmd;
+	struct spi_device *spi = snand_dev->spi;
+
+	memset(cmd, 0, sizeof(struct spi_nand_device_cmd));
+	cmd->n_cmd = 4;
+	cmd->cmd[0] = (spi->mode & SPI_RX_QUAD) ? SPI_NAND_READ_CACHE_X4 :
+			((spi->mode & SPI_RX_DUAL) ? SPI_NAND_READ_CACHE_X2 :
+			SPI_NAND_READ_CACHE);
+	cmd->cmd[1] = (u8)((page_offset & 0xff00) >> 8);
+	cmd->cmd[2] = (u8)(page_offset & 0xff);
+	cmd->cmd[3] = 0; /* dummy byte */
+	cmd->n_rx = length;
+	cmd->rx_buf = read_buf;
+	cmd->rx_nbits = (spi->mode & SPI_RX_QUAD) ? 4 :
+			((spi->mode & SPI_RX_DUAL) ? 2 : 1);
+
+	dev_dbg(snand->dev, "%s: offset 0x%x\n", __func__, page_offset);
+
+	return spi_nand_send_command(snand_dev->spi, cmd);
+}
+
+static int spi_nand_device_block_erase(struct spi_nand *snand,
+				       unsigned int page_addr)
+{
+	struct spi_nand_device *snand_dev = snand->priv;
+	struct spi_nand_device_cmd *cmd = &snand_dev->cmd;
+
+	memset(cmd, 0, sizeof(struct spi_nand_device_cmd));
+	cmd->n_cmd = 4;
+	cmd->cmd[0] = SPI_NAND_BLOCK_ERASE;
+	cmd->cmd[1] = (u8)((page_addr & 0xff0000) >> 16);
+	cmd->cmd[2] = (u8)((page_addr & 0xff00) >> 8);
+	cmd->cmd[3] = (u8)(page_addr & 0xff);
+
+	dev_dbg(snand->dev, "%s: block 0x%x\n", __func__, page_addr);
+
+	return spi_nand_send_command(snand_dev->spi, cmd);
+}
+
+static int spi_nand_gd5f_read_id(struct spi_nand *snand, u8 *buf)
+{
+	struct spi_nand_device *snand_dev = snand->priv;
+	struct spi_nand_device_cmd *cmd = &snand_dev->cmd;
+
+	memset(cmd, 0, sizeof(struct spi_nand_device_cmd));
+	cmd->n_cmd = 2;
+	cmd->cmd[0] = SPI_NAND_READ_ID;
+	cmd->cmd[1] = 0; /* dummy byte */
+	cmd->n_rx = SPI_NAND_GD5F_READID_LEN;
+	cmd->rx_buf = buf;
+
+	dev_dbg(snand->dev, "%s\n", __func__);
+
+	return spi_nand_send_command(snand_dev->spi, cmd);
+}
+
+static void spi_nand_gd5f_ecc_status(unsigned int status,
+				     unsigned int *corrected,
+				     unsigned int *ecc_error)
+{
+	unsigned int ecc_status = (status >> SPI_NAND_GD5F_ECC_SHIFT) &
+					     SPI_NAND_GD5F_ECC_MASK;
+
+	*ecc_error = (ecc_status == SPI_NAND_GD5F_ECC_UNCORR) ? 1 : 0;
+	if (*ecc_error == 0)
+		*corrected = (ecc_status > 1) ? (2 + ecc_status) : 0;
+}
+
+struct nand_ecclayout *spi_nand_post_probe(u8 *id, int len)
+{
+	int i;
+	struct nand_flash_dev *nfd = NULL;
+
+	if (len < 2)
+		return NULL;
+
+	for (i = 0; i < ARRAY_SIZE(spi_nand_flash_ids); i++) {
+		if (spi_nand_flash_ids[i].id[0] == id[0] &&
+		    spi_nand_flash_ids[i].id[1] == id[1]) {
+			nfd = &spi_nand_flash_ids[i];
+			break;
+		}
+	}
+
+	if (!nfd)
+		return NULL;
+
+	switch (id[0])
+	{
+	case NAND_MFR_GIGADEVICE:
+		switch (nfd->oobsize) {
+		case 64:
+			if (id[0x20] == 'S' &&
+			    id[0x21] == 'N' &&
+			    id[0x22] == 'F' &&
+			    id[0x23] == 'I')
+				return &gd25_snfi_oob_64_layout;
+			else
+				return &gd25_oob_64_layout;
+		case 128:
+			return &gd25_oob_128_layout;
+		case 256:
+			return &gd25_oob_256_layout;
+		}
+	}
+
+	return NULL; 
+}
+
+static int spi_nand_device_probe(struct spi_device *spi)
+{
+	enum spi_nand_device_variant variant;
+	struct spi_nand_device *priv;
+	struct spi_nand *snand;
+	int ret;
+
+	priv = devm_kzalloc(&spi->dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	snand = &priv->spi_nand;
+
+	snand->read_cache = spi_nand_device_read_cache;
+	snand->load_page = spi_nand_device_load_page;
+	snand->store_cache = spi_nand_device_store_cache;
+	snand->write_page = spi_nand_device_write_page;
+	snand->write_reg = spi_nand_device_write_reg;
+	snand->read_reg = spi_nand_device_read_reg;
+	snand->block_erase = spi_nand_device_block_erase;
+	snand->reset = spi_nand_device_reset;
+	snand->write_enable = spi_nand_device_write_enable;
+	snand->write_disable = spi_nand_device_write_disable;
+	snand->dev = &spi->dev;
+	snand->priv = priv;
+
+	/* This'll mean we won't need to specify any specific compatible string
+	 * for a given device, and instead just support spi-nand.
+	 */
+	variant = spi_get_device_id(spi)->driver_data;
+	switch (variant) {
+	case SPI_NAND_GD5F:
+		snand->read_id = spi_nand_gd5f_read_id;
+		snand->get_ecc_status = spi_nand_gd5f_ecc_status;
+		break;
+	default:
+		dev_err(snand->dev, "unknown device\n");
+		return -ENODEV;
+	}
+
+	spi_set_drvdata(spi, snand);
+	priv->spi = spi;
+
+	ret = spi_nand_register(snand, spi_nand_flash_ids);
+	if (ret)
+		return ret;
+	return 0;
+}
+
+static int spi_nand_device_remove(struct spi_device *spi)
+{
+	struct spi_nand *snand = spi_get_drvdata(spi);
+
+	spi_nand_unregister(snand);
+
+	return 0;
+}
+
+const struct spi_device_id spi_nand_id_table[] = {
+	{ "spi-nand", SPI_NAND_GENERIC },
+	{ "gd5f", SPI_NAND_GD5F },
+	{ },
+};
+MODULE_DEVICE_TABLE(spi, spi_nand_id_table);
+
+static struct spi_driver spi_nand_device_driver = {
+	.driver = {
+		.name	= "spi_nand_device",
+		.owner	= THIS_MODULE,
+	},
+	.id_table = spi_nand_id_table,
+	.probe	= spi_nand_device_probe,
+	.remove	= spi_nand_device_remove,
+};
+module_spi_driver(spi_nand_device_driver);
+
+MODULE_AUTHOR("Ezequiel Garcia <ezequiel.garcia@imgtec.com>");
+MODULE_DESCRIPTION("SPI NAND device support");
+MODULE_LICENSE("GPL v2");
diff --git a/target/linux/generic/files/include/linux/mtd/spi-nand.h b/target/linux/generic/files/include/linux/mtd/spi-nand.h
new file mode 100644
index 0000000000..5fcc98e7bb
--- /dev/null
+++ b/target/linux/generic/files/include/linux/mtd/spi-nand.h
@@ -0,0 +1,56 @@ 
+/*
+ * Copyright (C) 2014 Imagination Technologies Ltd.
+ *
+ * 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; version 2 of the License.
+ */
+
+#ifndef __LINUX_MTD_SPI_NAND_H
+#define __LINUX_MTD_SPI_NAND_H
+
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/nand.h>
+
+struct spi_nand {
+	struct nand_chip	nand_chip;
+	struct mtd_info		mtd;
+	struct device		*dev;
+	const char		*name;
+
+	u8			*buf, *data_buf;
+	size_t			buf_size;
+	off_t			buf_start;
+	unsigned int		page_addr;
+	unsigned int		bitflips;
+	bool			ecc;
+
+	int (*reset)(struct spi_nand *snand);
+	int (*read_id)(struct spi_nand *snand, u8 *buf);
+
+	int (*write_disable)(struct spi_nand *snand);
+	int (*write_enable)(struct spi_nand *snand);
+
+	int (*read_reg)(struct spi_nand *snand, u8 opcode, u8 *buf);
+	int (*write_reg)(struct spi_nand *snand, u8 opcode, u8 *buf);
+	void (*get_ecc_status)(unsigned int status,
+			       unsigned int *corrected,
+			       unsigned int *ecc_errors);
+
+	int (*store_cache)(struct spi_nand *snand, unsigned int page_offset,
+			   size_t length, u8 *write_buf);
+	int (*write_page)(struct spi_nand *snand, unsigned int page_addr);
+	int (*load_page)(struct spi_nand *snand, unsigned int page_addr);
+	int (*read_cache)(struct spi_nand *snand, unsigned int page_offset,
+			  size_t length, u8 *read_buf);
+	int (*block_erase)(struct spi_nand *snand, unsigned int page_addr);
+
+	void *priv;
+};
+
+struct nand_ecclayout *spi_nand_post_probe(u8 *id, int len);
+
+int spi_nand_register(struct spi_nand *snand, struct nand_flash_dev *flash_ids);
+void spi_nand_unregister(struct spi_nand *snand);
+
+#endif
diff --git a/target/linux/generic/pending-4.4/452-mtd-nand-Check-length-of-ID-before-reading-bits-per-cell.patch b/target/linux/generic/pending-4.4/452-mtd-nand-Check-length-of-ID-before-reading-bits-per-cell.patch
new file mode 100644
index 0000000000..60da4d6459
--- /dev/null
+++ b/target/linux/generic/pending-4.4/452-mtd-nand-Check-length-of-ID-before-reading-bits-per-cell.patch
@@ -0,0 +1,33 @@ 
+From 42ebff638003be18fab503b37de4ad7853244e95 Mon Sep 17 00:00:00 2001
+From: Ezequiel Garcia <ezequiel.garcia@imgtec.com>
+Date: Sat, 25 Feb 2017 15:58:22 +0000
+Subject: mtd: nand: Check length of ID before reading bits per cell
+
+The table-based NAND identification currently reads the number
+of bits per cell from the 3rd byte of the extended ID. This is done
+for the so-called 'full ID' devices; i.e. devices that have a known
+length ID.
+
+However, if the ID length is shorter than three, there's no 3rd byte,
+and so it's wrong to read the bits per cell from there. Fix this by
+adding a check for the ID length.
+
+(picked from http://lists.infradead.org/pipermail/linux-mtd/2014-December/056764.html)
+
+Signed-off-by: Ezequiel Garcia <ezequiel.garcia@imgtec.com>
+---
+ drivers/mtd/nand/nand_base.c | 3 ++-
+ 1 file changed, 2 insertions(+), 1 deletion(-)
+
+--- a/drivers/mtd/nand/nand_base.c
++++ b/drivers/mtd/nand/nand_base.c
+@@ -3758,7 +3758,8 @@ static bool find_full_id_nand(struct mtd
+ 		mtd->erasesize = type->erasesize;
+ 		mtd->oobsize = type->oobsize;
+ 
+-		chip->bits_per_cell = nand_get_bits_per_cell(id_data[2]);
++		if (type->id_len > 2)
++			chip->bits_per_cell = nand_get_bits_per_cell(id_data[2]);
+ 		chip->chipsize = (uint64_t)type->chipsize << 20;
+ 		chip->options |= type->options;
+ 		chip->ecc_strength_ds = NAND_ECC_STRENGTH(type);
diff --git a/target/linux/generic/pending-4.4/453-mtd-nand-Add-JEDEC-manufacturer-ID-for-Gigadevice.patch b/target/linux/generic/pending-4.4/453-mtd-nand-Add-JEDEC-manufacturer-ID-for-Gigadevice.patch
new file mode 100644
index 0000000000..70b311be70
--- /dev/null
+++ b/target/linux/generic/pending-4.4/453-mtd-nand-Add-JEDEC-manufacturer-ID-for-Gigadevice.patch
@@ -0,0 +1,35 @@ 
+From a4bc33b205fd9b1db862f1e45173dba57b0fa57f Mon Sep 17 00:00:00 2001
+From: Ezequiel Garcia <ezequiel.garcia@imgtec.com>
+Date: Sat, 25 Feb 2017 15:43:09 +0000
+Subject: mtd: nand: Add JEDEC manufacturer ID for Gigadevice
+
+This commit adds Gigadevice to the list of manufacturer ID and name strings.
+
+(picked from http://lists.infradead.org/pipermail/linux-mtd/2014-December/056765.html)
+
+Signed-off-by: Ezequiel Garcia <ezequiel.garcia@imgtec.com>
+---
+ drivers/mtd/nand/nand_ids.c | 1 +
+ include/linux/mtd/nand.h    | 1 +
+ 2 files changed, 2 insertions(+)
+
+--- a/drivers/mtd/nand/nand_ids.c
++++ b/drivers/mtd/nand/nand_ids.c
+@@ -181,6 +181,7 @@ struct nand_manufacturers nand_manuf_ids
+ 	{NAND_MFR_SANDISK, "SanDisk"},
+ 	{NAND_MFR_INTEL, "Intel"},
+ 	{NAND_MFR_ATO, "ATO"},
++	{NAND_MFR_GIGADEVICE, "Gigadevice"},
+ 	{0x0, "Unknown"}
+ };
+ 
+--- a/include/linux/mtd/nand.h
++++ b/include/linux/mtd/nand.h
+@@ -736,6 +736,7 @@ static inline void nand_set_controller_d
+ #define NAND_MFR_SANDISK	0x45
+ #define NAND_MFR_INTEL		0x89
+ #define NAND_MFR_ATO		0x9b
++#define NAND_MFR_GIGADEVICE	0xc8
+ 
+ /* The maximum expected count of bytes in the NAND ID sequence */
+ #define NAND_MAX_ID_LEN 8
diff --git a/target/linux/generic/pending-4.4/454-mtd-Introduce-SPI-NAND-framework.patch b/target/linux/generic/pending-4.4/454-mtd-Introduce-SPI-NAND-framework.patch
new file mode 100644
index 0000000000..18c703026b
--- /dev/null
+++ b/target/linux/generic/pending-4.4/454-mtd-Introduce-SPI-NAND-framework.patch
@@ -0,0 +1,20 @@ 
+--- a/drivers/mtd/Kconfig
++++ b/drivers/mtd/Kconfig
+@@ -369,6 +369,8 @@ source "drivers/mtd/onenand/Kconfig"
+ 
+ source "drivers/mtd/lpddr/Kconfig"
+ 
++source "drivers/mtd/spi-nand/Kconfig"
++
+ source "drivers/mtd/spi-nor/Kconfig"
+ 
+ source "drivers/mtd/ubi/Kconfig"
+--- a/drivers/mtd/Makefile
++++ b/drivers/mtd/Makefile
+@@ -35,5 +35,6 @@ inftl-objs		:= inftlcore.o inftlmount.o
+ 
+ obj-y		+= chips/ lpddr/ maps/ devices/ nand/ onenand/ tests/
+ 
++obj-$(CONFIG_MTD_SPI_NAND)	+= spi-nand/
+ obj-$(CONFIG_MTD_SPI_NOR)	+= spi-nor/
+ obj-$(CONFIG_MTD_UBI)		+= ubi/