From 082a89a78e29b15008284df90441747cb742f149 Mon Sep 17 00:00:00 2001
From: Ezequiel Garcia <ezequiel.garcia@imgtec.com>
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 <ionela.voinescu@imgtec.com>
Signed-off-by: Ezequiel Garcia <ezequiel.garcia@imgtec.com>
Signed-off-by: Ian Pozella <Ian.Pozella@imgtec.com>
---
 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
@@ -36,5 +36,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/
--- /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 <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->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 <ezequiel.garcia@imgtec.com>");
+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 <linux/mtd/mtd.h>
+#include <linux/mtd/nand.h>
+
+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
