GD5F1GQ4UAYIG datasheet: https://www.endrich.com/fm/2/GD5F1GQ4UAYIG.pdf
2017-09-03 19:27 GMT+08:00 Tom Psyborg <pozega.tomis...@gmail.com>: > which devices you tested this on? > > On 3 September 2017 at 07:43, hackpascal <hackpas...@gmail.com> wrote: >> >> 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 > > -- _______________________________________________ lede-devel mailing list lede-de...@lists.infradead.org _______________________________________________ Lede-dev mailing list Lede-dev@lists.infradead.org http://lists.infradead.org/mailman/listinfo/lede-dev