From: Weijie Gao <hackpas...@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 <hackpas...@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.gar...@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 <hackpas...@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.gar...@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.gar...@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.gar...@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.gar...@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.gar...@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