From 082a89a78e29b15008284df90441747cb742f149 Mon Sep 17 00:00:00 2001 From: Ezequiel Garcia Date: Tue, 2 Dec 2014 09:58:52 -0300 Subject: mtd: Introduce SPI NAND framework Add a new framework, to support SPI NAND devices. The framework registers a NAND chip and handles the generic SPI NAND protocol, calling device-specific hooks for each SPI NAND command. The following is the stack design, from userspace to hardware. This commit adds the "SPI NAND core" layer. Userspace ------------------ MTD ------------------ NAND core ------------------ SPI NAND core ------------------ SPI NAND device ------------------ SPI core ------------------ SPI master ------------------ Hardware (based on http://lists.infradead.org/pipermail/linux-mtd/2014-December/056763.html) Signed-off-by: Ionela Voinescu Signed-off-by: Ezequiel Garcia Signed-off-by: Ian Pozella --- drivers/mtd/Kconfig | 2 + drivers/mtd/Makefile | 1 + drivers/mtd/spi-nand/Kconfig | 7 + drivers/mtd/spi-nand/Makefile | 1 + drivers/mtd/spi-nand/spi-nand-base.c | 566 +++++++++++++++++++++++++++++++++++ include/linux/mtd/spi-nand.h | 54 ++++ 6 files changed, 631 insertions(+) create mode 100644 drivers/mtd/spi-nand/Kconfig create mode 100644 drivers/mtd/spi-nand/Makefile create mode 100644 drivers/mtd/spi-nand/spi-nand-base.c create mode 100644 include/linux/mtd/spi-nand.h --- a/drivers/mtd/Kconfig +++ b/drivers/mtd/Kconfig @@ -373,6 +373,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 @@ -37,6 +37,7 @@ 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/ --- /dev/null +++ b/drivers/mtd/spi-nand/Kconfig @@ -0,0 +1,7 @@ +menuconfig MTD_SPI_NAND + tristate "SPI NAND device support" + depends on MTD + select MTD_NAND + help + This is the framework for the SPI NAND. + --- /dev/null +++ b/drivers/mtd/spi-nand/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_MTD_SPI_NAND) += spi-nand-base.o --- /dev/null +++ b/drivers/mtd/spi-nand/spi-nand-base.c @@ -0,0 +1,566 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* 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->nand_chip.mtd.ecc_stats.failed++; + } else { + snand->nand_chip.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_to_nand(mtd); + struct spi_nand *snand = nand_get_controller_data(chip); + 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_to_nand(mtd); + struct spi_nand *snand = nand_get_controller_data(chip); + 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_to_nand(mtd); + struct spi_nand *snand = nand_get_controller_data(chip); + 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 = nand_get_controller_data(chip); + + 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 = nand_get_controller_data(chip); + 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_to_nand(mtd); + struct spi_nand *snand = nand_get_controller_data(chip); + + /* + * 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 */ +} + +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 = nand_to_mtd(chip); + struct device_node *np = snand->dev->of_node; + const char __maybe_unused *of_mtd_name = NULL; + 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; + + nand_set_controller_data(chip, snand); + nand_set_flash_node(chip, np); + 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; + + mtd_set_ooblayout(mtd, snand->ooblayout); + 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 (of_property_read_bool(np, "nand-on-flash-bbt")) + chip->bbt_options |= NAND_BBT_USE_FLASH | NAND_BBT_NO_OOB; + +#ifdef CONFIG_MTD_OF_PARTS + of_property_read_string(np, "linux,mtd-name", &of_mtd_name); +#endif + if (of_mtd_name) + mtd->name = of_mtd_name; + else + mtd->name = snand->name; + mtd->owner = THIS_MODULE; + + /* 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; + + /* + * 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 "); +MODULE_DESCRIPTION("Framework for SPI NAND"); +MODULE_LICENSE("GPL v2"); --- /dev/null +++ b/include/linux/mtd/spi-nand.h @@ -0,0 +1,54 @@ +/* + * 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 +#include + +struct spi_nand { + struct nand_chip nand_chip; + 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; + struct mtd_ooblayout_ops *ooblayout; + + 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; +}; + +int spi_nand_register(struct spi_nand *snand, struct nand_flash_dev *flash_ids); +void spi_nand_unregister(struct spi_nand *snand); + +#endif