2007-09-04 05:57:49

by Bryan Wu

[permalink] [raw]
Subject: [PATCH try #2] Blackfin on-chip NAND Flash Controller driver

This is the driver for latest Blackfin on-chip nand flash controller

- use nand_chip and mtd_info common nand driver interface
- provide both PIO and dma operation
- compiled with ezkit bf548 configuration
- use hardware 1-bit ECC
- tested with YAFFS2 and can mount YAFFS2 filesystem as rootfs

ChangeLog from try#1
- use hweight32() instead of count_bits()
- replace bf54x with bf5xx and BF54X with BF5XX
- compare against plat->page_size in 2 cases when enable hardware ECC

Signed-off-by: Bryan Wu <[email protected]>
---
drivers/mtd/nand/Kconfig | 19 +
drivers/mtd/nand/Makefile | 1 +
drivers/mtd/nand/bf5xx_nand.c | 791 +++++++++++++++++++++++++++++++++
include/asm-blackfin/mach-bf548/dma.h | 1 +
include/asm-blackfin/nand.h | 47 ++
5 files changed, 859 insertions(+), 0 deletions(-)
create mode 100644 drivers/mtd/nand/bf5xx_nand.c
create mode 100644 include/asm-blackfin/nand.h

diff --git a/drivers/mtd/nand/Kconfig b/drivers/mtd/nand/Kconfig
index 7992c43..3c88bb8 100644
--- a/drivers/mtd/nand/Kconfig
+++ b/drivers/mtd/nand/Kconfig
@@ -131,6 +131,25 @@ config MTD_NAND_AU1550
This enables the driver for the NAND flash controller on the
AMD/Alchemy 1550 SOC.

+config MTD_NAND_BF5XX
+ tristate "Blackfin on-chip NAND Flash Controller driver"
+ depends on BF54x && MTD_NAND
+ help
+ This enables the Blackfin on-chip NAND flash controller
+
+ No board specific support is done by this driver, each board
+ must advertise a platform_device for the driver to attach.
+
+ This driver can also be built as a module. If so, the module
+ will be called bf5xx-nand.
+
+config MTD_NAND_BF5XX_HWECC
+ bool "BF5XX NAND Hardware ECC"
+ depends on MTD_NAND_BF5XX
+ help
+ Enable the use of the BF5XX's internal ECC generator when
+ using NAND.
+
config MTD_NAND_RTC_FROM4
tristate "Renesas Flash ROM 4-slot interface board (FROM_BOARD4)"
depends on SH_SOLUTION_ENGINE
diff --git a/drivers/mtd/nand/Makefile b/drivers/mtd/nand/Makefile
index b16d234..e43d8d3 100644
--- a/drivers/mtd/nand/Makefile
+++ b/drivers/mtd/nand/Makefile
@@ -13,6 +13,7 @@ obj-$(CONFIG_MTD_NAND_TOTO) += toto.o
obj-$(CONFIG_MTD_NAND_AUTCPU12) += autcpu12.o
obj-$(CONFIG_MTD_NAND_EDB7312) += edb7312.o
obj-$(CONFIG_MTD_NAND_AU1550) += au1550nd.o
+obj-$(CONFIG_MTD_NAND_BF5XX) += bf5xx_nand.o
obj-$(CONFIG_MTD_NAND_PPCHAMELEONEVB) += ppchameleonevb.o
obj-$(CONFIG_MTD_NAND_S3C2410) += s3c2410.o
obj-$(CONFIG_MTD_NAND_DISKONCHIP) += diskonchip.o
diff --git a/drivers/mtd/nand/bf5xx_nand.c b/drivers/mtd/nand/bf5xx_nand.c
new file mode 100644
index 0000000..c5ce8a3
--- /dev/null
+++ b/drivers/mtd/nand/bf5xx_nand.c
@@ -0,0 +1,791 @@
+/* linux/drivers/mtd/nand/bf5xx_nand.c
+ *
+ * Copyright 2006-2007 Analog Devices Inc.
+ * http://blackfin.uclinux.org/
+ * Bryan Wu <[email protected]>
+ *
+ * Blackfin BF5xx on-chip NAND flash controler driver
+ *
+ * Derived from drivers/mtd/nand/s3c2410.c
+ * Copyright (c) 2007 Ben Dooks <[email protected]>
+ *
+ * Derived from drivers/mtd/nand/cafe.c
+ * Copyright © 2006 Red Hat, Inc.
+ * Copyright © 2006 David Woodhouse <[email protected]>
+ *
+ * Changelog:
+ * 12-Jun-2007 Bryan Wu: Initial version
+ * 18-Jul-2007 Bryan Wu:
+ * - ECC_HW and ECC_SW supported
+ * - DMA supported in ECC_HW
+ * - YAFFS tested as rootfs in both ECC_HW and ECC_SW
+ *
+ * TODO:
+ * Enable JFFS2 over NAND as rootfs
+ *
+ * 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; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/string.h>
+#include <linux/ioport.h>
+#include <linux/platform_device.h>
+#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/err.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+#include <linux/bitops.h>
+
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/nand.h>
+#include <linux/mtd/nand_ecc.h>
+#include <linux/mtd/partitions.h>
+
+#include <asm/blackfin.h>
+#include <asm/dma.h>
+#include <asm/cacheflush.h>
+#include <asm/nand.h>
+#include <asm/portmux.h>
+
+#define DRV_NAME "bf5xx-nand"
+#define DRV_VERSION "1.2"
+#define DRV_AUTHOR "Bryan Wu <[email protected]>"
+#define DRV_DESC "BF5xx on-chip NAND FLash Controller Driver"
+
+#ifdef CONFIG_MTD_NAND_BF5XX_HWECC
+static int hardware_ecc = 1;
+#else
+static int hardware_ecc;
+#endif
+
+static unsigned short bfin_nfc_pin_req[] = {P_NAND_CE, P_NAND_RB, 0};
+
+/*----------------------------------------------------------------------------
+ * Data structures for bf5xx nand flash controller driver
+ */
+
+/* bf5xx nand info */
+struct bf5xx_nand_info {
+ /* mtd info */
+ struct nand_hw_control controller;
+ struct mtd_info mtd;
+ struct nand_chip chip;
+
+ /* platform info */
+ struct bf5xx_nand_platform *platform;
+
+ /* device info */
+ struct device *device;
+
+ /* DMA stuff */
+ struct completion dma_completion;
+};
+
+/*----------------------------------------------------------------------------
+ * Conversion functions
+ */
+static struct bf5xx_nand_info *mtd_to_nand_info(struct mtd_info *mtd)
+{
+ return container_of(mtd, struct bf5xx_nand_info, mtd);
+}
+
+static struct bf5xx_nand_info *to_nand_info(struct platform_device *pdev)
+{
+ return platform_get_drvdata(pdev);
+}
+
+static struct bf5xx_nand_platform *to_nand_plat(struct platform_device *pdev)
+{
+ return pdev->dev.platform_data;
+}
+
+
+/*----------------------------------------------------------------------------
+ * struct nand_chip interface function pointers
+ */
+
+/*
+ * bf5xx_nand_hwcontrol
+ *
+ * Issue command and address cycles to the chip
+ */
+static void bf5xx_nand_hwcontrol(struct mtd_info *mtd, int cmd,
+ unsigned int ctrl)
+{
+ if (cmd == NAND_CMD_NONE)
+ return;
+
+ while (bfin_read_NFC_STAT() & WB_FULL)
+ continue;
+
+ if (ctrl & NAND_CLE)
+ bfin_write_NFC_CMD(cmd);
+ else
+ bfin_write_NFC_ADDR(cmd);
+ SSYNC();
+}
+
+/*
+ * bf5xx_nand_devready()
+ *
+ * returns 0 if the nand is busy, 1 if it is ready
+ */
+static int bf5xx_nand_devready(struct mtd_info *mtd)
+{
+ unsigned short val = bfin_read_NFC_IRQSTAT();
+
+ if ((val & NBUSYIRQ) == NBUSYIRQ)
+ return 1;
+ else
+ return 0;
+}
+
+/*----------------------------------------------------------------------------
+ * ECC functions
+ * These allow the bf5xx to use the controller's ECC
+ * generator block to ECC the data as it passes through
+ */
+
+/*
+ * ECC error correction function
+ */
+static int bf5xx_nand_correct_data_256(struct mtd_info *mtd, u_char *dat,
+ u_char *read_ecc, u_char *calc_ecc)
+{
+ struct bf5xx_nand_info *info = mtd_to_nand_info(mtd);
+ u32 syndrome[5];
+ u32 calced, stored;
+ int i;
+ unsigned short failing_bit, failing_byte;
+ u_char data;
+
+ calced = calc_ecc[0] | (calc_ecc[1] << 8) | (calc_ecc[2] << 16);
+ stored = read_ecc[0] | (read_ecc[1] << 8) | (read_ecc[2] << 16);
+
+ syndrome[0] = (calced ^ stored);
+
+ /*
+ * syndrome 0: all zero
+ * No error in data
+ * No action
+ */
+ if (!syndrome[0] || !calced || !stored)
+ return 0;
+
+ /*
+ * sysdrome 0: only one bit is one
+ * ECC data was incorrect
+ * No action
+ */
+ if (hweight32(syndrome[0]) == 1) {
+ dev_err(info->device, "ECC data was incorrect!\n");
+ return 1;
+ }
+
+ syndrome[1] = (calced & 0x7FF) ^ (stored & 0x7FF);
+ syndrome[2] = (calced & 0x7FF) ^ ((calced >> 11) & 0x7FF);
+ syndrome[3] = (stored & 0x7FF) ^ ((stored >> 11) & 0x7FF);
+ syndrome[4] = syndrome[2] ^ syndrome[3];
+
+ for (i = 0; i < 5; i++)
+ dev_info(info->device, "syndrome[%d] 0x%08x\n", i, syndrome[i]);
+
+ dev_info(info->device,
+ "calced[0x%08x], stored[0x%08x]\n",
+ calced, stored);
+
+ /*
+ * sysdrome 0: exactly 11 bits are one, each parity
+ * and parity' pair is 1 & 0 or 0 & 1.
+ * 1-bit correctable error
+ * Correct the error
+ */
+ if (hweight32(syndrome[0]) == 11 && syndrome[4] == 0x7FF) {
+ dev_info(info->device,
+ "1-bit correctable error, correct it.\n");
+ dev_info(info->device,
+ "syndrome[1] 0x%08x\n", syndrome[1]);
+
+ failing_bit = syndrome[1] & 0x7;
+ failing_byte = syndrome[1] >> 0x3;
+ data = *(dat + failing_byte);
+ data = data ^ (0x1 << failing_bit);
+ *(dat + failing_byte) = data;
+
+ return 0;
+ }
+
+ /*
+ * sysdrome 0: random data
+ * More than 1-bit error, non-correctable error
+ * Discard data, mark bad block
+ */
+ dev_err(info->device,
+ "More than 1-bit error, non-correctable error.\n");
+ dev_err(info->device,
+ "Please discard data, mark bad block\n");
+
+ return 1;
+}
+
+static int bf5xx_nand_correct_data(struct mtd_info *mtd, u_char *dat,
+ u_char *read_ecc, u_char *calc_ecc)
+{ struct bf5xx_nand_info *info = mtd_to_nand_info(mtd);
+ struct bf5xx_nand_platform *plat = info->platform;
+ unsigned short page_size = (plat->page_size ? 512 : 256);
+
+ int ret;
+
+ ret = bf5xx_nand_correct_data_256(mtd, dat, read_ecc, calc_ecc);
+
+ /* If page size is 512, correct second 256 bytes */
+ if (page_size == 512) {
+ dat += 256;
+ read_ecc += 8;
+ calc_ecc += 8;
+ ret = bf5xx_nand_correct_data_256(mtd, dat, read_ecc, calc_ecc);
+ }
+
+ return ret;
+}
+
+static void bf5xx_nand_enable_hwecc(struct mtd_info *mtd, int mode)
+{
+ /*
+ * This register must be written before each page is
+ * transferred to generate the correct ECC register
+ * values.
+ */
+ return;
+
+}
+
+static int bf5xx_nand_calculate_ecc(struct mtd_info *mtd,
+ const u_char *dat, u_char *ecc_code)
+{
+ struct bf5xx_nand_info *info = mtd_to_nand_info(mtd);
+ struct bf5xx_nand_platform *plat = info->platform;
+ u16 page_size = (plat->page_size ? 512 : 256);
+ u16 ecc0, ecc1;
+ u32 code[2];
+ u8 *p;
+ int bytes = 3, i;
+
+ /* first 4 bytes ECC code for 256 page size */
+ ecc0 = bfin_read_NFC_ECC0();
+ ecc1 = bfin_read_NFC_ECC1();
+
+ code[0] = (ecc0 & 0x3FF) | ((ecc1 & 0x3FF) << 11);
+
+ dev_dbg(info->device, "returning ecc 0x%08x\n", code[0]);
+
+ /* second 4 bytes ECC code for 512 page size */
+ if (page_size == 512) {
+ ecc0 = bfin_read_NFC_ECC2();
+ ecc1 = bfin_read_NFC_ECC3();
+ code[1] = (ecc0 & 0x3FF) | ((ecc1 & 0x3FF) << 11);
+ bytes = 6;
+ dev_dbg(info->device, "returning ecc 0x%08x\n", code[1]);
+ }
+
+ p = (u8 *)code;
+ for (i = 0; i < bytes; i++)
+ ecc_code[i] = p[i];
+
+ return 0;
+}
+
+/*----------------------------------------------------------------------------
+ * PIO mode for buffer writing and reading
+ */
+static void bf5xx_nand_read_buf(struct mtd_info *mtd, uint8_t *buf, int len)
+{
+ int i;
+ unsigned short val;
+
+ /*
+ * Data reads are requested by first writing to NFC_DATA_RD
+ * and then reading back from NFC_READ.
+ */
+ for (i = 0; i < len; i++) {
+ while (bfin_read_NFC_STAT() & WB_FULL)
+ continue;
+
+ /* Contents do not matter */
+ bfin_write_NFC_DATA_RD(0x0000);
+ SSYNC();
+
+ while ((bfin_read_NFC_IRQSTAT() & RD_RDY) != RD_RDY)
+ continue;
+
+ buf[i] = bfin_read_NFC_READ();
+
+ val = bfin_read_NFC_IRQSTAT();
+ val |= RD_RDY;
+ bfin_write_NFC_IRQSTAT(val);
+ SSYNC();
+ }
+}
+
+static uint8_t bf5xx_nand_read_byte(struct mtd_info *mtd)
+{
+ uint8_t val;
+
+ bf5xx_nand_read_buf(mtd, &val, 1);
+
+ return val;
+}
+
+static void bf5xx_nand_write_buf(struct mtd_info *mtd,
+ const uint8_t *buf, int len)
+{
+ int i;
+
+ for (i = 0; i < len; i++) {
+ while (bfin_read_NFC_STAT() & WB_FULL)
+ continue;
+
+ bfin_write_NFC_DATA_WR(buf[i]);
+ SSYNC();
+ }
+}
+
+static void bf5xx_nand_read_buf16(struct mtd_info *mtd, uint8_t *buf, int len)
+{
+ int i;
+ u16 *p = (u16 *) buf;
+ len >>= 1;
+
+ /*
+ * Data reads are requested by first writing to NFC_DATA_RD
+ * and then reading back from NFC_READ.
+ */
+ bfin_write_NFC_DATA_RD(0x5555);
+
+ SSYNC();
+
+ for (i = 0; i < len; i++)
+ p[i] = bfin_read_NFC_READ();
+}
+
+static void bf5xx_nand_write_buf16(struct mtd_info *mtd,
+ const uint8_t *buf, int len)
+{
+ int i;
+ u16 *p = (u16 *) buf;
+ len >>= 1;
+
+ for (i = 0; i < len; i++)
+ bfin_write_NFC_DATA_WR(p[i]);
+
+ SSYNC();
+}
+
+/*----------------------------------------------------------------------------
+ * DMA functions for buffer writing and reading
+ */
+static irqreturn_t bf5xx_nand_dma_irq(int irq, void *dev_id)
+{
+ struct bf5xx_nand_info *info = (struct bf5xx_nand_info *) dev_id;
+
+ clear_dma_irqstat(CH_NFC);
+ disable_dma(CH_NFC);
+ complete(&info->dma_completion);
+
+ return IRQ_HANDLED;
+}
+
+static int bf5xx_nand_dma_rw(struct mtd_info *mtd,
+ uint8_t *buf, int is_read)
+{
+ struct bf5xx_nand_info *info = mtd_to_nand_info(mtd);
+ struct bf5xx_nand_platform *plat = info->platform;
+ unsigned short page_size = (plat->page_size ? 512 : 256);
+ unsigned short val;
+
+ dev_dbg(info->device, " mtd->%p, buf->%p, is_read %d\n",
+ mtd, buf, is_read);
+
+ if (is_read)
+ invalidate_dcache_range((unsigned int)buf,
+ (unsigned int)(buf + page_size));
+ else
+ flush_dcache_range((unsigned int)buf,
+ (unsigned int)(buf + page_size));
+
+ bfin_write_NFC_RST(0x1);
+ SSYNC();
+
+ disable_dma(CH_NFC);
+ clear_dma_irqstat(CH_NFC);
+
+ /* setup DMA register with Blackfin DMA API */
+ set_dma_config(CH_NFC, 0x0);
+ set_dma_start_addr(CH_NFC, (unsigned long) buf);
+ set_dma_x_count(CH_NFC, (page_size >> 2));
+ set_dma_x_modify(CH_NFC, 4);
+
+ /* setup write or read operation */
+ val = DI_EN | WDSIZE_32;
+ if (is_read)
+ val |= WNR;
+ set_dma_config(CH_NFC, val);
+ enable_dma(CH_NFC);
+
+ /* Start PAGE read/write operation */
+ if (is_read)
+ bfin_write_NFC_PGCTL(0x1);
+ else
+ bfin_write_NFC_PGCTL(0x2);
+ wait_for_completion(&info->dma_completion);
+
+ return 0;
+}
+
+static void bf5xx_nand_dma_read_buf(struct mtd_info *mtd,
+ uint8_t *buf, int len)
+{
+ struct bf5xx_nand_info *info = mtd_to_nand_info(mtd);
+ struct bf5xx_nand_platform *plat = info->platform;
+ unsigned short page_size = (plat->page_size ? 512 : 256);
+
+ dev_dbg(info->device, "mtd->%p, buf->%p, int %d\n", mtd, buf, len);
+
+ if (len == page_size)
+ bf5xx_nand_dma_rw(mtd, buf, 1);
+ else
+ bf5xx_nand_read_buf(mtd, buf, len);
+}
+
+static void bf5xx_nand_dma_write_buf(struct mtd_info *mtd,
+ const uint8_t *buf, int len)
+{
+ struct bf5xx_nand_info *info = mtd_to_nand_info(mtd);
+ struct bf5xx_nand_platform *plat = info->platform;
+ unsigned short page_size = (plat->page_size ? 512 : 256);
+
+ dev_dbg(info->device, "mtd->%p, buf->%p, len %d\n", mtd, buf, len);
+
+ if (len == page_size)
+ bf5xx_nand_dma_rw(mtd, (uint8_t *)buf, 0);
+ else
+ bf5xx_nand_write_buf(mtd, buf, len);
+}
+
+/*----------------------------------------------------------------------------
+ * System initialization functions
+ */
+
+static int bf5xx_nand_dma_init(struct bf5xx_nand_info *info)
+{
+ int ret;
+ unsigned short val;
+
+ /* Do not use dma */
+ if (!hardware_ecc)
+ return 0;
+
+ init_completion(&info->dma_completion);
+
+ /* Setup DMAC1 channel mux for NFC which shared with SDH */
+ val = bfin_read_DMAC1_PERIMUX();
+ val &= 0xFFFE;
+ bfin_write_DMAC1_PERIMUX(val);
+ SSYNC();
+
+ /* Request NFC DMA channel */
+ ret = request_dma(CH_NFC, "BF5XX NFC driver");
+ if (ret < 0) {
+ dev_err(info->device, " unable to get DMA channel\n");
+ return ret;
+ }
+
+ set_dma_callback(CH_NFC, (void *) bf5xx_nand_dma_irq, (void *) info);
+
+ /* Turn off the DMA channel first */
+ disable_dma(CH_NFC);
+ return 0;
+}
+
+/*
+ * BF5XX NFC hardware initialization
+ * - pin mux setup
+ * - clear interrupt status
+ */
+static int bf5xx_nand_hw_init(struct bf5xx_nand_info *info)
+{
+ int err = 0;
+ unsigned short val;
+ struct bf5xx_nand_platform *plat = info->platform;
+
+ if (!info)
+ return -EINVAL;
+
+ /* setup NFC_CTL register */
+ dev_info(info->device,
+ "page_size=%d, data_width=%d, wr_dly=%d, rd_dly=%d\n",
+ (plat->page_size ? 512 : 256),
+ (plat->data_width ? 16 : 8),
+ plat->wr_dly, plat->rd_dly);
+
+ val = (plat->page_size << NFC_PG_SIZE_OFFSET) |
+ (plat->data_width << NFC_NWIDTH_OFFSET) |
+ (plat->rd_dly << NFC_RDDLY_OFFSET) |
+ (plat->rd_dly << NFC_WRDLY_OFFSET);
+ dev_dbg(info->device, "NFC_CTL is 0x%04x\n", val);
+
+ bfin_write_NFC_CTL(val);
+ SSYNC();
+
+ /* clear interrupt status */
+ bfin_write_NFC_IRQMASK(0x0);
+ SSYNC();
+ val = bfin_read_NFC_IRQSTAT();
+ bfin_write_NFC_IRQSTAT(val);
+ SSYNC();
+
+ if (peripheral_request_list(bfin_nfc_pin_req, DRV_NAME)) {
+ printk(KERN_ERR DRV_NAME
+ ": Requesting Peripherals failed\n");
+ return -EFAULT;
+ }
+
+
+ /* DMA initialization */
+ if (bf5xx_nand_dma_init(info))
+ err = -ENXIO;
+
+ return err;
+}
+
+/*----------------------------------------------------------------------------
+ * Device management interface
+ */
+static int bf5xx_nand_add_partition(struct bf5xx_nand_info *info)
+{
+ struct mtd_info *mtd = &info->mtd;
+
+#ifdef CONFIG_MTD_PARTITIONS
+ struct mtd_partition *parts = info->platform->partitions;
+ int nr = info->platform->nr_partitions;
+
+ return add_mtd_partitions(mtd, parts, nr);
+#else
+ return add_mtd_device(mtd);
+#endif
+}
+
+static int bf5xx_nand_remove(struct platform_device *pdev)
+{
+ struct bf5xx_nand_info *info = to_nand_info(pdev);
+ struct mtd_info *mtd = NULL;
+
+ platform_set_drvdata(pdev, NULL);
+
+ if (!info)
+ return 0;
+
+ /* first thing we need to do is release all our mtds
+ * and their partitions, then go through freeing the
+ * resources used
+ */
+ mtd = &info->mtd;
+ if (mtd) {
+ nand_release(mtd);
+ kfree(mtd);
+ }
+
+ peripheral_free_list(bfin_nfc_pin_req);
+
+ /* free the common resources */
+ kfree(info);
+
+ return 0;
+}
+
+/*
+ * bf5xx_nand_probe
+ *
+ * called by device layer when it finds a device matching
+ * one our driver can handled. This code checks to see if
+ * it can allocate all necessary resources then calls the
+ * nand layer to look for devices
+ */
+static int bf5xx_nand_probe(struct platform_device *pdev)
+{
+ struct bf5xx_nand_platform *plat = to_nand_plat(pdev);
+ struct bf5xx_nand_info *info = NULL;
+ struct nand_chip *chip = NULL;
+ struct mtd_info *mtd = NULL;
+ int err = 0;
+
+ dev_dbg(&pdev->dev, "(%p)\n", pdev);
+
+ if (!plat) {
+ dev_err(&pdev->dev, "no platform specific information\n");
+ goto exit_error;
+ }
+
+ info = kzalloc(sizeof(*info), GFP_KERNEL);
+ if (info == NULL) {
+ dev_err(&pdev->dev, "no memory for flash info\n");
+ err = -ENOMEM;
+ goto exit_error;
+ }
+
+ platform_set_drvdata(pdev, info);
+
+ spin_lock_init(&info->controller.lock);
+ init_waitqueue_head(&info->controller.wq);
+
+ info->device = &pdev->dev;
+ info->platform = plat;
+
+ /* initialise chip data struct */
+ chip = &info->chip;
+
+ if (plat->data_width)
+ chip->options |= NAND_BUSWIDTH_16;
+
+ chip->options |= NAND_CACHEPRG | NAND_SKIP_BBTSCAN;
+
+ chip->read_buf = (plat->data_width) ?
+ bf5xx_nand_read_buf16 : bf5xx_nand_read_buf;
+ chip->write_buf = (plat->data_width) ?
+ bf5xx_nand_write_buf16 : bf5xx_nand_write_buf;
+
+ chip->read_byte = bf5xx_nand_read_byte;
+
+ chip->cmd_ctrl = bf5xx_nand_hwcontrol;
+ chip->dev_ready = bf5xx_nand_devready;
+
+ chip->priv = &info->mtd;
+ chip->controller = &info->controller;
+
+ chip->IO_ADDR_R = (void __iomem *) NFC_READ;
+ chip->IO_ADDR_W = (void __iomem *) NFC_DATA_WR;
+
+ chip->chip_delay = 0;
+
+ /* initialise mtd info data struct */
+ mtd = &info->mtd;
+ mtd->priv = chip;
+ mtd->owner = THIS_MODULE;
+
+ /* initialise the hardware */
+ err = bf5xx_nand_hw_init(info);
+ if (err != 0)
+ goto exit_error;
+
+ /* setup hardware ECC data struct */
+ if (hardware_ecc) {
+ if (plat->page_size == NFC_PG_SIZE_256) {
+ chip->ecc.bytes = 3;
+ chip->ecc.size = 256;
+ } else if (plat->page_size == NFC_PG_SIZE_512) {
+ chip->ecc.bytes = 6;
+ chip->ecc.size = 512;
+ }
+
+ chip->read_buf = bf5xx_nand_dma_read_buf;
+ chip->write_buf = bf5xx_nand_dma_write_buf;
+ chip->ecc.calculate = bf5xx_nand_calculate_ecc;
+ chip->ecc.correct = bf5xx_nand_correct_data;
+ chip->ecc.mode = NAND_ECC_HW;
+ chip->ecc.hwctl = bf5xx_nand_enable_hwecc;
+ } else {
+ chip->ecc.mode = NAND_ECC_SOFT;
+ }
+
+ /* scan hardware nand chip and setup mtd info data struct */
+ if (nand_scan(mtd, 1)) {
+ err = -ENXIO;
+ goto exit_error;
+ }
+
+ /* add NAND partition */
+ bf5xx_nand_add_partition(info);
+
+ dev_dbg(&pdev->dev, "initialised ok\n");
+ return 0;
+
+exit_error:
+ bf5xx_nand_remove(pdev);
+
+ if (err == 0)
+ err = -EINVAL;
+ return err;
+}
+
+/* PM Support */
+#ifdef CONFIG_PM
+
+static int bf5xx_nand_suspend(struct platform_device *dev, pm_message_t pm)
+{
+ struct bf5xx_nand_info *info = platform_get_drvdata(dev);
+
+ return 0;
+}
+
+static int bf5xx_nand_resume(struct platform_device *dev)
+{
+ struct bf5xx_nand_info *info = platform_get_drvdata(dev);
+
+ if (info)
+ bf5xx_nand_hw_init(info);
+
+ return 0;
+}
+
+#else
+#define bf5xx_nand_suspend NULL
+#define bf5xx_nand_resume NULL
+#endif
+
+/* driver device registration */
+static struct platform_driver bf5xx_nand_driver = {
+ .probe = bf5xx_nand_probe,
+ .remove = bf5xx_nand_remove,
+ .suspend = bf5xx_nand_suspend,
+ .resume = bf5xx_nand_resume,
+ .driver = {
+ .name = DRV_NAME,
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init bf5xx_nand_init(void)
+{
+ printk(KERN_INFO "%s, Version %s (c) 2007 Analog Devices, Inc.\n",
+ DRV_DESC, DRV_VERSION);
+
+ return platform_driver_register(&bf5xx_nand_driver);
+}
+
+static void __exit bf5xx_nand_exit(void)
+{
+ platform_driver_unregister(&bf5xx_nand_driver);
+}
+
+module_init(bf5xx_nand_init);
+module_exit(bf5xx_nand_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR(DRV_AUTHOR);
+MODULE_DESCRIPTION(DRV_DESC);
diff --git a/include/asm-blackfin/mach-bf548/dma.h b/include/asm-blackfin/mach-bf548/dma.h
index fcc8b4c..14cb10c 100644
--- a/include/asm-blackfin/mach-bf548/dma.h
+++ b/include/asm-blackfin/mach-bf548/dma.h
@@ -55,6 +55,7 @@
#define CH_SPORT3_RX 20
#define CH_SPORT3_TX 21
#define CH_SDH 22
+#define CH_NFC 22
#define CH_SPI2 23

#define CH_MEM_STREAM0_DEST 24
diff --git a/include/asm-blackfin/nand.h b/include/asm-blackfin/nand.h
new file mode 100644
index 0000000..afbaafa
--- /dev/null
+++ b/include/asm-blackfin/nand.h
@@ -0,0 +1,47 @@
+/* linux/include/asm-blackfin/nand.h
+ *
+ * Copyright (c) 2007 Analog Devices, Inc.
+ * Bryan Wu <[email protected]>
+ *
+ * BF5XX - NAND flash controller platfrom_device info
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+/* struct bf5xx_nand_platform
+ *
+ * define a interface between platfrom board specific code and
+ * bf54x NFC driver.
+ *
+ * nr_partitions = number of partitions pointed to be partitoons (or zero)
+ * partitions = mtd partition list
+ */
+
+#define NFC_PG_SIZE_256 0
+#define NFC_PG_SIZE_512 1
+#define NFC_PG_SIZE_OFFSET 9
+
+#define NFC_NWIDTH_8 0
+#define NFC_NWIDTH_16 1
+#define NFC_NWIDTH_OFFSET 8
+
+#define NFC_RDDLY_OFFSET 4
+#define NFC_WRDLY_OFFSET 0
+
+#define NFC_STAT_NBUSY 1
+
+struct bf5xx_nand_platform {
+ /* NAND chip information */
+ unsigned short page_size;
+ unsigned short data_width;
+
+ /* RD/WR strobe delay timing information, all times in SCLK cycles */
+ unsigned short rd_dly;
+ unsigned short wr_dly;
+
+ /* NAND MTD partition information */
+ int nr_partitions;
+ struct mtd_partition *partitions;
+};
--
1.5.2


2007-09-07 05:53:32

by Artem Bityutskiy

[permalink] [raw]
Subject: Re: [PATCH try #2] Blackfin on-chip NAND Flash Controller driver

On Tue, 2007-09-04 at 13:57 +0800, Bryan Wu wrote:
> This is the driver for latest Blackfin on-chip nand flash controller
>
> - use nand_chip and mtd_info common nand driver interface
> - provide both PIO and dma operation
> - compiled with ezkit bf548 configuration
> - use hardware 1-bit ECC
> - tested with YAFFS2 and can mount YAFFS2 filesystem as rootfs
>
> ChangeLog from try#1
> - use hweight32() instead of count_bits()
> - replace bf54x with bf5xx and BF54X with BF5XX
> - compare against plat->page_size in 2 cases when enable hardware ECC
>
> Signed-off-by: Bryan Wu <[email protected]>

Bryan,

I'd suggest you to test your NAND driver with the following NAND tests:

git://git.infradead.org/~ahunter/nand-tests.git

(http://git.infradead.org/?p=users/ahunter/nand-tests.git;a=summary)


You may want to avoid the torture test, but it is useful to run it with
limited amount of erase cycles to make sure your flash/driver survives
really high I/O load.For example, we caught occasional DMA transfer
problems while running the torture test for few days.

--
Best regards,
Artem Bityutskiy (Битюцкий Артём)

2007-09-10 09:54:24

by Bryan Wu

[permalink] [raw]
Subject: Re: [PATCH try #2] Blackfin on-chip NAND Flash Controller driver

On Fri, 2007-09-07 at 13:52 +0800, Artem Bityutskiy wrote:
> On Tue, 2007-09-04 at 13:57 +0800, Bryan Wu wrote:
> > This is the driver for latest Blackfin on-chip nand flash
> controller
> >
> > - use nand_chip and mtd_info common nand driver interface
> > - provide both PIO and dma operation
> > - compiled with ezkit bf548 configuration
> > - use hardware 1-bit ECC
> > - tested with YAFFS2 and can mount YAFFS2 filesystem as rootfs
> >
> > ChangeLog from try#1
> > - use hweight32() instead of count_bits()
> > - replace bf54x with bf5xx and BF54X with BF5XX
> > - compare against plat->page_size in 2 cases when enable hardware
> ECC
> >
> > Signed-off-by: Bryan Wu <[email protected]>
>
> Bryan,
>
> I'd suggest you to test your NAND driver with the following NAND
> tests:
>
> git://git.infradead.org/~ahunter/nand-tests.git
>
> (http://git.infradead.org/?p=users/ahunter/nand-tests.git;a=summary)
>

Thanks Artem, I got the code and did some test on my EZKIT548 with this
driver.

Here is the test log, only 2 errors in oobtest. After digging into the
mtd-nand driver code, it should be no relation to my bf5xx_nand driver.
Maybe somethings wrong in the mtd-nand code. I intend to get some help
from you, please see my test log below:

1. boot message:
---
Linux version 2.6.22.6-ADI-2007R2-pre-svn3643 (roc@roc-desktop) (gcc
version 4.1.2 (ADI svn)) #38 Mon Sep 10 17:35:51 CST 2007
Hardware Trace Active and Enabled
Blackfin support (C) 2004-2007 Analog Devices, Inc.
Compiled for ADSP-BF549 Rev 0.0
Blackfin Linux support by http://blackfin.uclinux.org/
Processor Speed: 525 MHz core clock and 131 Mhz System Clock
Board Memory: 64MB
Kernel Managed Memory: 64MB
Memory map:
text = 0x00001000-0x00117820
rodata = 0x00118000-0x001630dc
data = 0x00164000-0x00178000
stack = 0x00164000-0x00166000
init = 0x00178000-0x002c4c4d
bss = 0x002c4c50-0x002d4890
available = 0x002d4890-0x03eff000
DMA Zone = 0x03f00000-0x04000000
Instruction Cache Enabled
Data Cache Enabled (write-through)
Built 1 zonelists. Total pages: 16002
Kernel command line: root=/dev/mtdblock0 rw
ip=192.168.0.66:192.168.0.55:192.168.0.1:255.255.255.0:BF548:eth0:off
Configuring Blackfin Priority Driven Interrupts
PID hash table entries: 256 (order: 8, 1024 bytes)
Dentry cache hash table entries: 8192 (order: 3, 32768 bytes)
Inode-cache hash table entries: 4096 (order: 2, 16384 bytes)
Physical pages: 3eff
Memory available: 60968k/64112k RAM, (1331k init code, 1114k kernel
code, 63k data, 1024k dma)
Blackfin Scratchpad data SRAM: 4 KB
Blackfin Data A SRAM: 16 KB (15 KB free)
Blackfin Data B SRAM: 16 KB (16 KB free)
Blackfin Instruction SRAM: 48 KB (42 KB free)
Security Framework v1.0.0 initialized
Capability LSM initialized
Mount-cache hash table entries: 512
NET: Registered protocol family 16
Blackfin GPIO Controller
Blackfin DMA Controller
stamp_init(): registering device resources
SCSI subsystem initialized
NET: Registered protocol family 2
IP route cache hash table entries: 1024 (order: 0, 4096 bytes)
TCP established hash table entries: 2048 (order: 2, 16384 bytes)
TCP bind hash table entries: 2048 (order: 1, 8192 bytes)
TCP: Hash tables configured (established 2048 bind 2048)
TCP reno registered
yaffs Sep 10 2007 15:04:46 Installing.
io scheduler noop registered
io scheduler anticipatory registered (default)
io scheduler cfq registered
Serial: Blackfin serial driver
bfin-uart.1: ttyBF0 at MMIO 0xffc02000 (irq = 48) is a BFIN-UART
RAMDISK driver initialized: 16 RAM disks of 4096K size 1024 blocksize
BF5xx on-chip NAND FLash Controller Driver, Version 1.2 (c) 2007 Analog
Devices, Inc.
bf5xx-nand bf5xx-nand.0: page_size=256, data_width=8, wr_dly=3, rd_dly=3
NAND device: Manufacturer ID: 0x20, Chip ID: 0xda (ST Micro NAND 256MiB
3,3V 8-bit)
Creating 2 MTD partitions on "NAND 256MiB 3,3V 8-bit":
0x00000000-0x00400000 : "Linux Kernel"
mtd: Giving out device 0 to Linux Kernel
0x00400000-0x10000000 : "File System"
mtd: Giving out device 1 to File System
rtc-bfin rtc-bfin: rtc core: registered rtc-bfin as rtc0
TCP cubic registered
NET: Registered protocol family 1
NET: Registered protocol family 17
rtc-bfin rtc-bfin: setting the system clock to 2004-06-25 05:06:29
(1088139989)
eth0: SMSC911x/921x identified at 0x24000000, IRQ: 175
eth0: SMSC911x MAC Address: 32:3b:f3:01:26:4a
eth0: link down
IP-Config: Complete:
device=eth0, addr=192.168.0.66, mask=255.255.255.0,
gw=192.168.0.1,
host=BF548, domain=, nis-domain=(none),
bootserver=192.168.0.55, rootserver=192.168.0.55, rootpath=
Freeing unused kernel memory: 1331k freed
---

2. oobtest:
root:~> insmod oobtest.ko

=========================================================================================
oobtest: dev = 0
oobtest: oob available per page = 38
oobtest: scanning for bad blocks
oobtest: scanned 0
oobtest: Block 5 is bad
oobtest: Block 27 is bad
oobtest: scanned 32
oobtest: Test 1 of 5
oobtest: erasing
oobtest: erased 0
oobtest: erased 32
oobtest: writing
oobtest: written 0
oobtest: written 32
oobtest: verifying
oobtest: verified 0
oobtest: verified 32
oobtest: Test 2 of 5
oobtest: erasing
oobtest: erased 0
oobtest: erased 32
oobtest: writing
oobtest: written 0
oobtest: written 32
oobtest: verifying
oobtest: verified 0
oobtest: verified 32
oobtest: Test 3 of 5
oobtest: erasing
oobtest: erased 0
oobtest: erased 32
oobtest: writing
oobtest: written 0
oobtest: written 32
oobtest: verifying
oobtest: verified 0
oobtest: verified 32
oobtest: Test 4 of 5
oobtest: erasing
oobtest: erased 0
oobtest: erased 32
oobtest: Attempting to start write past end of oob
oobtest: An error is expected...
nand_write_oob: Attempt to write past end of page
oobtest: Error occurred as expected
oobtest: Attempting to start read past end of oob
oobtest: An error is expected...
nand_read_oob: Attempt to start read outside oob
oobtest: Error occurred as expected
oobtest: Attempting to write past end of device
oobtest: An error is expected...
nand_write_oob: Attempt to write past end of page
oobtest: Error occurred as expected
oobtest: Attempting to read past end of device
oobtest: An error is expected...
oobtest: error: read past end of device
oobtest: Attempting to write past end of device
oobtest: An error is expected...
nand_write_oob: Attempt to write past end of page
oobtest: Error occurred as expected
oobtest: Attempting to read past end of device
oobtest: An error is expected...
oobtest: error: read past end of device
oobtest: Test 5 of 5
oobtest: erasing
oobtest: erased 0
oobtest: erased 32
oobtest: writing
oobtest: written 0
oobtest: written 0
oobtest: written 31
oobtest: verifying
oobtest: verified 0
oobtest: verified 31
oobtest: oobtest finished with 2 errors
=========================================================================================

3. pagetest
root:~> insmod pagetest.ko

=========================================================================================
pagetest: dev = 0
pagetest: scanning for bad blocks
pagetest: scanned 0
pagetest: Block 5 is bad
pagetest: Block 27 is bad
pagetest: scanned 32
pagetest: erasing
pagetest: erased 0
pagetest: erased 32
pagetest: writing
pagetest: written 0
pagetest: written 32
pagetest: verifying
pagetest: verified 0
pagetest: verified 32
pagetest: crosstest
pagetest: reading page at 0x00000000
pagetest: reading page at 0x003ff800
pagetest: reading page at 0x00000000
pagetest: verifying pages read at 0x00000000 match
pagetest: crosstest ok
pagetest: erasecrosstest
pagetest: erasing block 0
pagetest: writing 1st page of block 0
pagetest: reading 1st page of block 0
pagetest: verifying 1st page of block 0
pagetest: erasing block 0
pagetest: writing 1st page of block 0
pagetest: erasing block 31
pagetest: reading 1st page of block 0
pagetest: verifying 1st page of block 0
pagetest: erasecrosstest ok
pagetest: erasetest
pagetest: erasing block 0
pagetest: writing 1st page of block 0
pagetest: erasing block 0
pagetest: reading 1st page of block 0
pagetest: verifying 1st page of block 0 is all 0xff
pagetest: erasetest ok
pagetest: pagetest finished with 0 errors
=========================================================================================

4. subpagetest

root:~> insmod subpagetest.ko

=========================================================================================
subpagetest: dev = 0
sub-page size is: 512
subpagetest: scanning for bad blocks
subpagetest: scanned 0
subpagetest: Block 5 is bad
subpagetest: Block 27 is bad
subpagetest: scanned 32
subpagetest: erasing
subpagetest: erased 0
subpagetest: erased 32
subpagetest: writing
subpagetest: written 0
subpagetest: written 32
subpagetest: verifying
subpagetest: verified 0
subpagetest: verified 32
subpagetest: erasing
subpagetest: erased 0
subpagetest: erased 32
subpagetest: verifying 0xff
subpagetest: verified 0
subpagetest: verified 32
subpagetest: writing
subpagetest: written 0
subpagetest: written 32
subpagetest: verifying
subpagetest: verified 0
subpagetest: verified 32
subpagetest: erasing
subpagetest: erased 0
subpagetest: erased 32
subpagetest: verifying 0xff
subpagetest: verified 0
subpagetest: verified 32
subpagetest: subpagetest finished with 0 errors
=========================================================================================

> You may want to avoid the torture test, but it is useful to run it
> with
> limited amount of erase cycles to make sure your flash/driver
> survives
> really high I/O load.For example, we caught occasional DMA transfer
> problems while running the torture test for few days.
>
I plan to do the torture test after this oobtest passed. And this
nand_test suites are intended to integrated into our Blackfin
uClinux-dist testsuites.

Thanks a lot
Best Regards,
- Bryan Wu

2007-09-11 08:36:29

by Artem Bityutskiy

[permalink] [raw]
Subject: Re: [PATCH try #2] Blackfin on-chip NAND Flash Controller driver

Hi,

On Mon, 2007-09-10 at 17:50 +0800, Bryan Wu wrote:
> Here is the test log, only 2 errors in oobtest. After digging into the
> mtd-nand driver code, it should be no relation to my bf5xx_nand driver.
> Maybe somethings wrong in the mtd-nand code. I intend to get some help
> from you, please see my test log below:

> oobtest: Attempting to read past end of device
> oobtest: An error is expected...
> oobtest: error: read past end of device

This means that MTD did not return an error when the test tried to read
the last NAND page's OOB. Here is the code:

/* Attempt to write off end of device */
ops.mode = MTD_OOB_AUTO;
ops.ooblen = mtd->ecclayout->oobavail + 1;
ops.oobbuf = writebuf;
printk(PRINT_PREF "Attempting to write past end of device\n");
printk(PRINT_PREF "An error is expected...\n");
err = mtd->write_oob(mtd, mtd->size - mtd->writesize, &ops);

So we read OOB size + 1 byte from the last NAND page and get no error.

This test passes for nandsim, so I'm not sure the problem is in the
generic code. On the other hand, it is generic code which should check
rages.

> oobtest: Attempting to write past end of device
> oobtest: An error is expected...
> nand_write_oob: Attempt to write past end of page
> oobtest: Error occurred as expected
> oobtest: Attempting to read past end of device
> oobtest: An error is expected...
> oobtest: error: read past end of device

Similar.

> I plan to do the torture test after this oobtest passed. And this
> nand_test suites are intended to integrated into our Blackfin
> uClinux-dist testsuites.

We are planning to clean up the tests and submit them for kernel
inclusion some day, when we have time.

--
Best regards,
Artem Bityutskiy (Битюцкий Артём)

2007-09-11 09:08:33

by Bryan Wu

[permalink] [raw]
Subject: Re: [PATCH try #2] Blackfin on-chip NAND Flash Controller driver

On Tue, 2007-09-11 at 11:35 +0300, Artem Bityutskiy wrote:
> Hi,
>
> On Mon, 2007-09-10 at 17:50 +0800, Bryan Wu wrote:
> > Here is the test log, only 2 errors in oobtest. After digging into the
> > mtd-nand driver code, it should be no relation to my bf5xx_nand driver.
> > Maybe somethings wrong in the mtd-nand code. I intend to get some help
> > from you, please see my test log below:
>
> > oobtest: Attempting to read past end of device
> > oobtest: An error is expected...
> > oobtest: error: read past end of device
>
> This means that MTD did not return an error when the test tried to read
> the last NAND page's OOB. Here is the code:
>
> /* Attempt to write off end of device */
> ops.mode = MTD_OOB_AUTO;
> ops.ooblen = mtd->ecclayout->oobavail + 1;
> ops.oobbuf = writebuf;
> printk(PRINT_PREF "Attempting to write past end of device\n");
> printk(PRINT_PREF "An error is expected...\n");
> err = mtd->write_oob(mtd, mtd->size - mtd->writesize, &ops);
>
> So we read OOB size + 1 byte from the last NAND page and get no error.
>

Yes, it makes sense.

> This test passes for nandsim, so I'm not sure the problem is in the
> generic code. On the other hand, it is generic code which should check
> rages.

Actually, I debugged both oobtest.c and drivers/mtd/nand/nand_base.c

in the kernel nand_base.c
mtd->read_oob() --> nand_read_oob() --> nand_do_read_oob() -->
chip->ecc.read_oob() --> nand_read_oob_std() --> chip->read_buf() -->
bf5xx_nand.c bf5xx_nand_dma_read_buf().

In the calling path, rage check should not be in my driver code
bf5xx_nand_dma_read_buf() like other low level nand controller drivers.
It is already in nand_do_read_oob() as below:

---
/* Do not allow reads past end of device */
if (unlikely(from >= mtd->size ||
ops->ooboffs + readlen > ((mtd->size >> chip->page_shift) -
(from >> chip->page_shift)) * len)) {
DEBUG(MTD_DEBUG_LEVEL0, "nand_read_oob: "
"Attempt read beyond end of device\n");
return -EINVAL;
}
---

maybe this condition testing does not include your case. I am not sure,
still busy at other task.

> > oobtest: Attempting to write past end of device
> > oobtest: An error is expected...
> > nand_write_oob: Attempt to write past end of page
> > oobtest: Error occurred as expected
> > oobtest: Attempting to read past end of device
> > oobtest: An error is expected...
> > oobtest: error: read past end of device
>
> Similar.
>
> > I plan to do the torture test after this oobtest passed. And this
> > nand_test suites are intended to integrated into our Blackfin
> > uClinux-dist testsuites.
>
> We are planning to clean up the tests and submit them for kernel
> inclusion some day, when we have time.
>

Good news. Thanks
Best Regards,
- Bryan Wu

2007-09-14 07:19:41

by Bryan Wu

[permalink] [raw]
Subject: [PATCH try #3] Blackfin on-chip NAND Flash Controller driver

This is the driver for latest Blackfin on-chip nand flash controller

- use nand_chip and mtd_info common nand driver interface
- provide both PIO and dma operation
- compiled with ezkit bf548 configuration
- use hardware 1-bit ECC
- tested with YAFFS2 and can mount YAFFS2 filesystem as rootfs

ChangeLog from try#1
- use hweight32() instead of count_bits()
- replace bf54x with bf5xx and BF54X with BF5XX
- compare against plat->page_size in 2 cases when enable hardware ECC

ChangeLog from try#2
- passed nand_test suites
- use cpu_relax() instead of busy wait loop
- some coding style issue pointed out by Andrew

Signed-off-by: Bryan Wu <[email protected]>
---
drivers/mtd/nand/Kconfig | 19 +
drivers/mtd/nand/Makefile | 1 +
drivers/mtd/nand/bf5xx_nand.c | 788 +++++++++++++++++++++++++++++++++
include/asm-blackfin/mach-bf548/dma.h | 1 +
include/asm-blackfin/nand.h | 47 ++
5 files changed, 856 insertions(+), 0 deletions(-)
create mode 100644 drivers/mtd/nand/bf5xx_nand.c
create mode 100644 include/asm-blackfin/nand.h

diff --git a/drivers/mtd/nand/Kconfig b/drivers/mtd/nand/Kconfig
index 7992c43..3c88bb8 100644
--- a/drivers/mtd/nand/Kconfig
+++ b/drivers/mtd/nand/Kconfig
@@ -131,6 +131,25 @@ config MTD_NAND_AU1550
This enables the driver for the NAND flash controller on the
AMD/Alchemy 1550 SOC.

+config MTD_NAND_BF5XX
+ tristate "Blackfin on-chip NAND Flash Controller driver"
+ depends on BF54x && MTD_NAND
+ help
+ This enables the Blackfin on-chip NAND flash controller
+
+ No board specific support is done by this driver, each board
+ must advertise a platform_device for the driver to attach.
+
+ This driver can also be built as a module. If so, the module
+ will be called bf5xx-nand.
+
+config MTD_NAND_BF5XX_HWECC
+ bool "BF5XX NAND Hardware ECC"
+ depends on MTD_NAND_BF5XX
+ help
+ Enable the use of the BF5XX's internal ECC generator when
+ using NAND.
+
config MTD_NAND_RTC_FROM4
tristate "Renesas Flash ROM 4-slot interface board (FROM_BOARD4)"
depends on SH_SOLUTION_ENGINE
diff --git a/drivers/mtd/nand/Makefile b/drivers/mtd/nand/Makefile
index b16d234..e43d8d3 100644
--- a/drivers/mtd/nand/Makefile
+++ b/drivers/mtd/nand/Makefile
@@ -13,6 +13,7 @@ obj-$(CONFIG_MTD_NAND_TOTO) += toto.o
obj-$(CONFIG_MTD_NAND_AUTCPU12) += autcpu12.o
obj-$(CONFIG_MTD_NAND_EDB7312) += edb7312.o
obj-$(CONFIG_MTD_NAND_AU1550) += au1550nd.o
+obj-$(CONFIG_MTD_NAND_BF5XX) += bf5xx_nand.o
obj-$(CONFIG_MTD_NAND_PPCHAMELEONEVB) += ppchameleonevb.o
obj-$(CONFIG_MTD_NAND_S3C2410) += s3c2410.o
obj-$(CONFIG_MTD_NAND_DISKONCHIP) += diskonchip.o
diff --git a/drivers/mtd/nand/bf5xx_nand.c b/drivers/mtd/nand/bf5xx_nand.c
new file mode 100644
index 0000000..1657ecd
--- /dev/null
+++ b/drivers/mtd/nand/bf5xx_nand.c
@@ -0,0 +1,788 @@
+/* linux/drivers/mtd/nand/bf5xx_nand.c
+ *
+ * Copyright 2006-2007 Analog Devices Inc.
+ * http://blackfin.uclinux.org/
+ * Bryan Wu <[email protected]>
+ *
+ * Blackfin BF5xx on-chip NAND flash controler driver
+ *
+ * Derived from drivers/mtd/nand/s3c2410.c
+ * Copyright (c) 2007 Ben Dooks <[email protected]>
+ *
+ * Derived from drivers/mtd/nand/cafe.c
+ * Copyright © 2006 Red Hat, Inc.
+ * Copyright © 2006 David Woodhouse <[email protected]>
+ *
+ * Changelog:
+ * 12-Jun-2007 Bryan Wu: Initial version
+ * 18-Jul-2007 Bryan Wu:
+ * - ECC_HW and ECC_SW supported
+ * - DMA supported in ECC_HW
+ * - YAFFS tested as rootfs in both ECC_HW and ECC_SW
+ *
+ * TODO:
+ * Enable JFFS2 over NAND as rootfs
+ *
+ * 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; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/string.h>
+#include <linux/ioport.h>
+#include <linux/platform_device.h>
+#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/err.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+#include <linux/bitops.h>
+
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/nand.h>
+#include <linux/mtd/nand_ecc.h>
+#include <linux/mtd/partitions.h>
+
+#include <asm/blackfin.h>
+#include <asm/dma.h>
+#include <asm/cacheflush.h>
+#include <asm/nand.h>
+#include <asm/portmux.h>
+
+#define DRV_NAME "bf5xx-nand"
+#define DRV_VERSION "1.2"
+#define DRV_AUTHOR "Bryan Wu <[email protected]>"
+#define DRV_DESC "BF5xx on-chip NAND FLash Controller Driver"
+
+#ifdef CONFIG_MTD_NAND_BF5XX_HWECC
+static int hardware_ecc = 1;
+#else
+static int hardware_ecc;
+#endif
+
+static unsigned short bfin_nfc_pin_req[] = {P_NAND_CE, P_NAND_RB, 0};
+
+/*
+ * Data structures for bf5xx nand flash controller driver
+ */
+
+/* bf5xx nand info */
+struct bf5xx_nand_info {
+ /* mtd info */
+ struct nand_hw_control controller;
+ struct mtd_info mtd;
+ struct nand_chip chip;
+
+ /* platform info */
+ struct bf5xx_nand_platform *platform;
+
+ /* device info */
+ struct device *device;
+
+ /* DMA stuff */
+ struct completion dma_completion;
+};
+
+/*
+ * Conversion functions
+ */
+static struct bf5xx_nand_info *mtd_to_nand_info(struct mtd_info *mtd)
+{
+ return container_of(mtd, struct bf5xx_nand_info, mtd);
+}
+
+static struct bf5xx_nand_info *to_nand_info(struct platform_device *pdev)
+{
+ return platform_get_drvdata(pdev);
+}
+
+static struct bf5xx_nand_platform *to_nand_plat(struct platform_device *pdev)
+{
+ return pdev->dev.platform_data;
+}
+
+/*
+ * struct nand_chip interface function pointers
+ */
+
+/*
+ * bf5xx_nand_hwcontrol
+ *
+ * Issue command and address cycles to the chip
+ */
+static void bf5xx_nand_hwcontrol(struct mtd_info *mtd, int cmd,
+ unsigned int ctrl)
+{
+ if (cmd == NAND_CMD_NONE)
+ return;
+
+ while (bfin_read_NFC_STAT() & WB_FULL)
+ cpu_relax();
+
+ if (ctrl & NAND_CLE)
+ bfin_write_NFC_CMD(cmd);
+ else
+ bfin_write_NFC_ADDR(cmd);
+ SSYNC();
+}
+
+/*
+ * bf5xx_nand_devready()
+ *
+ * returns 0 if the nand is busy, 1 if it is ready
+ */
+static int bf5xx_nand_devready(struct mtd_info *mtd)
+{
+ unsigned short val = bfin_read_NFC_IRQSTAT();
+
+ if ((val & NBUSYIRQ) == NBUSYIRQ)
+ return 1;
+ else
+ return 0;
+}
+
+/*
+ * ECC functions
+ * These allow the bf5xx to use the controller's ECC
+ * generator block to ECC the data as it passes through
+ */
+
+/*
+ * ECC error correction function
+ */
+static int bf5xx_nand_correct_data_256(struct mtd_info *mtd, u_char *dat,
+ u_char *read_ecc, u_char *calc_ecc)
+{
+ struct bf5xx_nand_info *info = mtd_to_nand_info(mtd);
+ u32 syndrome[5];
+ u32 calced, stored;
+ int i;
+ unsigned short failing_bit, failing_byte;
+ u_char data;
+
+ calced = calc_ecc[0] | (calc_ecc[1] << 8) | (calc_ecc[2] << 16);
+ stored = read_ecc[0] | (read_ecc[1] << 8) | (read_ecc[2] << 16);
+
+ syndrome[0] = (calced ^ stored);
+
+ /*
+ * syndrome 0: all zero
+ * No error in data
+ * No action
+ */
+ if (!syndrome[0] || !calced || !stored)
+ return 0;
+
+ /*
+ * sysdrome 0: only one bit is one
+ * ECC data was incorrect
+ * No action
+ */
+ if (hweight32(syndrome[0]) == 1) {
+ dev_err(info->device, "ECC data was incorrect!\n");
+ return 1;
+ }
+
+ syndrome[1] = (calced & 0x7FF) ^ (stored & 0x7FF);
+ syndrome[2] = (calced & 0x7FF) ^ ((calced >> 11) & 0x7FF);
+ syndrome[3] = (stored & 0x7FF) ^ ((stored >> 11) & 0x7FF);
+ syndrome[4] = syndrome[2] ^ syndrome[3];
+
+ for (i = 0; i < 5; i++)
+ dev_info(info->device, "syndrome[%d] 0x%08x\n", i, syndrome[i]);
+
+ dev_info(info->device,
+ "calced[0x%08x], stored[0x%08x]\n",
+ calced, stored);
+
+ /*
+ * sysdrome 0: exactly 11 bits are one, each parity
+ * and parity' pair is 1 & 0 or 0 & 1.
+ * 1-bit correctable error
+ * Correct the error
+ */
+ if (hweight32(syndrome[0]) == 11 && syndrome[4] == 0x7FF) {
+ dev_info(info->device,
+ "1-bit correctable error, correct it.\n");
+ dev_info(info->device,
+ "syndrome[1] 0x%08x\n", syndrome[1]);
+
+ failing_bit = syndrome[1] & 0x7;
+ failing_byte = syndrome[1] >> 0x3;
+ data = *(dat + failing_byte);
+ data = data ^ (0x1 << failing_bit);
+ *(dat + failing_byte) = data;
+
+ return 0;
+ }
+
+ /*
+ * sysdrome 0: random data
+ * More than 1-bit error, non-correctable error
+ * Discard data, mark bad block
+ */
+ dev_err(info->device,
+ "More than 1-bit error, non-correctable error.\n");
+ dev_err(info->device,
+ "Please discard data, mark bad block\n");
+
+ return 1;
+}
+
+static int bf5xx_nand_correct_data(struct mtd_info *mtd, u_char *dat,
+ u_char *read_ecc, u_char *calc_ecc)
+{
+ struct bf5xx_nand_info *info = mtd_to_nand_info(mtd);
+ struct bf5xx_nand_platform *plat = info->platform;
+ unsigned short page_size = (plat->page_size ? 512 : 256);
+ int ret;
+
+ ret = bf5xx_nand_correct_data_256(mtd, dat, read_ecc, calc_ecc);
+
+ /* If page size is 512, correct second 256 bytes */
+ if (page_size == 512) {
+ dat += 256;
+ read_ecc += 8;
+ calc_ecc += 8;
+ ret = bf5xx_nand_correct_data_256(mtd, dat, read_ecc, calc_ecc);
+ }
+
+ return ret;
+}
+
+static void bf5xx_nand_enable_hwecc(struct mtd_info *mtd, int mode)
+{
+ return;
+}
+
+static int bf5xx_nand_calculate_ecc(struct mtd_info *mtd,
+ const u_char *dat, u_char *ecc_code)
+{
+ struct bf5xx_nand_info *info = mtd_to_nand_info(mtd);
+ struct bf5xx_nand_platform *plat = info->platform;
+ u16 page_size = (plat->page_size ? 512 : 256);
+ u16 ecc0, ecc1;
+ u32 code[2];
+ u8 *p;
+ int bytes = 3, i;
+
+ /* first 4 bytes ECC code for 256 page size */
+ ecc0 = bfin_read_NFC_ECC0();
+ ecc1 = bfin_read_NFC_ECC1();
+
+ code[0] = (ecc0 & 0x3FF) | ((ecc1 & 0x3FF) << 11);
+
+ dev_dbg(info->device, "returning ecc 0x%08x\n", code[0]);
+
+ /* second 4 bytes ECC code for 512 page size */
+ if (page_size == 512) {
+ ecc0 = bfin_read_NFC_ECC2();
+ ecc1 = bfin_read_NFC_ECC3();
+ code[1] = (ecc0 & 0x3FF) | ((ecc1 & 0x3FF) << 11);
+ bytes = 6;
+ dev_dbg(info->device, "returning ecc 0x%08x\n", code[1]);
+ }
+
+ p = (u8 *)code;
+ for (i = 0; i < bytes; i++)
+ ecc_code[i] = p[i];
+
+ return 0;
+}
+
+/*
+ * PIO mode for buffer writing and reading
+ */
+static void bf5xx_nand_read_buf(struct mtd_info *mtd, uint8_t *buf, int len)
+{
+ int i;
+ unsigned short val;
+
+ /*
+ * Data reads are requested by first writing to NFC_DATA_RD
+ * and then reading back from NFC_READ.
+ */
+ for (i = 0; i < len; i++) {
+ while (bfin_read_NFC_STAT() & WB_FULL)
+ cpu_relax();
+
+ /* Contents do not matter */
+ bfin_write_NFC_DATA_RD(0x0000);
+ SSYNC();
+
+ while ((bfin_read_NFC_IRQSTAT() & RD_RDY) != RD_RDY)
+ cpu_relax();
+
+ buf[i] = bfin_read_NFC_READ();
+
+ val = bfin_read_NFC_IRQSTAT();
+ val |= RD_RDY;
+ bfin_write_NFC_IRQSTAT(val);
+ SSYNC();
+ }
+}
+
+static uint8_t bf5xx_nand_read_byte(struct mtd_info *mtd)
+{
+ uint8_t val;
+
+ bf5xx_nand_read_buf(mtd, &val, 1);
+
+ return val;
+}
+
+static void bf5xx_nand_write_buf(struct mtd_info *mtd,
+ const uint8_t *buf, int len)
+{
+ int i;
+
+ for (i = 0; i < len; i++) {
+ while (bfin_read_NFC_STAT() & WB_FULL)
+ cpu_relax();
+
+ bfin_write_NFC_DATA_WR(buf[i]);
+ SSYNC();
+ }
+}
+
+static void bf5xx_nand_read_buf16(struct mtd_info *mtd, uint8_t *buf, int len)
+{
+ int i;
+ u16 *p = (u16 *) buf;
+ len >>= 1;
+
+ /*
+ * Data reads are requested by first writing to NFC_DATA_RD
+ * and then reading back from NFC_READ.
+ */
+ bfin_write_NFC_DATA_RD(0x5555);
+
+ SSYNC();
+
+ for (i = 0; i < len; i++)
+ p[i] = bfin_read_NFC_READ();
+}
+
+static void bf5xx_nand_write_buf16(struct mtd_info *mtd,
+ const uint8_t *buf, int len)
+{
+ int i;
+ u16 *p = (u16 *) buf;
+ len >>= 1;
+
+ for (i = 0; i < len; i++)
+ bfin_write_NFC_DATA_WR(p[i]);
+
+ SSYNC();
+}
+
+/*
+ * DMA functions for buffer writing and reading
+ */
+static irqreturn_t bf5xx_nand_dma_irq(int irq, void *dev_id)
+{
+ struct bf5xx_nand_info *info = dev_id;
+
+ clear_dma_irqstat(CH_NFC);
+ disable_dma(CH_NFC);
+ complete(&info->dma_completion);
+
+ return IRQ_HANDLED;
+}
+
+static int bf5xx_nand_dma_rw(struct mtd_info *mtd,
+ uint8_t *buf, int is_read)
+{
+ struct bf5xx_nand_info *info = mtd_to_nand_info(mtd);
+ struct bf5xx_nand_platform *plat = info->platform;
+ unsigned short page_size = (plat->page_size ? 512 : 256);
+ unsigned short val;
+
+ dev_dbg(info->device, " mtd->%p, buf->%p, is_read %d\n",
+ mtd, buf, is_read);
+
+ /*
+ * Before starting a dma transfer, be sure to invalidate/flush
+ * the cache over the address range of your DMA buffer to
+ * prevent cache coherency problems. Otherwise very subtle bugs
+ * can be introduced to your driver.
+ */
+ if (is_read)
+ invalidate_dcache_range((unsigned int)buf,
+ (unsigned int)(buf + page_size));
+ else
+ flush_dcache_range((unsigned int)buf,
+ (unsigned int)(buf + page_size));
+
+ /*
+ * This register must be written before each page is
+ * transferred to generate the correct ECC register
+ * values.
+ */
+ bfin_write_NFC_RST(0x1);
+ SSYNC();
+
+ disable_dma(CH_NFC);
+ clear_dma_irqstat(CH_NFC);
+
+ /* setup DMA register with Blackfin DMA API */
+ set_dma_config(CH_NFC, 0x0);
+ set_dma_start_addr(CH_NFC, (unsigned long) buf);
+ set_dma_x_count(CH_NFC, (page_size >> 2));
+ set_dma_x_modify(CH_NFC, 4);
+
+ /* setup write or read operation */
+ val = DI_EN | WDSIZE_32;
+ if (is_read)
+ val |= WNR;
+ set_dma_config(CH_NFC, val);
+ enable_dma(CH_NFC);
+
+ /* Start PAGE read/write operation */
+ if (is_read)
+ bfin_write_NFC_PGCTL(0x1);
+ else
+ bfin_write_NFC_PGCTL(0x2);
+ wait_for_completion(&info->dma_completion);
+
+ return 0;
+}
+
+static void bf5xx_nand_dma_read_buf(struct mtd_info *mtd,
+ uint8_t *buf, int len)
+{
+ struct bf5xx_nand_info *info = mtd_to_nand_info(mtd);
+ struct bf5xx_nand_platform *plat = info->platform;
+ unsigned short page_size = (plat->page_size ? 512 : 256);
+
+ dev_dbg(info->device, "mtd->%p, buf->%p, int %d\n", mtd, buf, len);
+
+ if (len == page_size)
+ bf5xx_nand_dma_rw(mtd, buf, 1);
+ else
+ bf5xx_nand_read_buf(mtd, buf, len);
+}
+
+static void bf5xx_nand_dma_write_buf(struct mtd_info *mtd,
+ const uint8_t *buf, int len)
+{
+ struct bf5xx_nand_info *info = mtd_to_nand_info(mtd);
+ struct bf5xx_nand_platform *plat = info->platform;
+ unsigned short page_size = (plat->page_size ? 512 : 256);
+
+ dev_dbg(info->device, "mtd->%p, buf->%p, len %d\n", mtd, buf, len);
+
+ if (len == page_size)
+ bf5xx_nand_dma_rw(mtd, (uint8_t *)buf, 0);
+ else
+ bf5xx_nand_write_buf(mtd, buf, len);
+}
+
+/*
+ * System initialization functions
+ */
+
+static int bf5xx_nand_dma_init(struct bf5xx_nand_info *info)
+{
+ int ret;
+ unsigned short val;
+
+ /* Do not use dma */
+ if (!hardware_ecc)
+ return 0;
+
+ init_completion(&info->dma_completion);
+
+ /* Setup DMAC1 channel mux for NFC which shared with SDH */
+ val = bfin_read_DMAC1_PERIMUX();
+ val &= 0xFFFE;
+ bfin_write_DMAC1_PERIMUX(val);
+ SSYNC();
+
+ /* Request NFC DMA channel */
+ ret = request_dma(CH_NFC, "BF5XX NFC driver");
+ if (ret < 0) {
+ dev_err(info->device, " unable to get DMA channel\n");
+ return ret;
+ }
+
+ set_dma_callback(CH_NFC, (void *) bf5xx_nand_dma_irq, (void *) info);
+
+ /* Turn off the DMA channel first */
+ disable_dma(CH_NFC);
+ return 0;
+}
+
+/*
+ * BF5XX NFC hardware initialization
+ * - pin mux setup
+ * - clear interrupt status
+ */
+static int bf5xx_nand_hw_init(struct bf5xx_nand_info *info)
+{
+ int err = 0;
+ unsigned short val;
+ struct bf5xx_nand_platform *plat = info->platform;
+
+ /* setup NFC_CTL register */
+ dev_info(info->device,
+ "page_size=%d, data_width=%d, wr_dly=%d, rd_dly=%d\n",
+ (plat->page_size ? 512 : 256),
+ (plat->data_width ? 16 : 8),
+ plat->wr_dly, plat->rd_dly);
+
+ val = (plat->page_size << NFC_PG_SIZE_OFFSET) |
+ (plat->data_width << NFC_NWIDTH_OFFSET) |
+ (plat->rd_dly << NFC_RDDLY_OFFSET) |
+ (plat->rd_dly << NFC_WRDLY_OFFSET);
+ dev_dbg(info->device, "NFC_CTL is 0x%04x\n", val);
+
+ bfin_write_NFC_CTL(val);
+ SSYNC();
+
+ /* clear interrupt status */
+ bfin_write_NFC_IRQMASK(0x0);
+ SSYNC();
+ val = bfin_read_NFC_IRQSTAT();
+ bfin_write_NFC_IRQSTAT(val);
+ SSYNC();
+
+ if (peripheral_request_list(bfin_nfc_pin_req, DRV_NAME)) {
+ printk(KERN_ERR DRV_NAME
+ ": Requesting Peripherals failed\n");
+ return -EFAULT;
+ }
+
+ /* DMA initialization */
+ if (bf5xx_nand_dma_init(info))
+ err = -ENXIO;
+
+ return err;
+}
+
+/*
+ * Device management interface
+ */
+static int bf5xx_nand_add_partition(struct bf5xx_nand_info *info)
+{
+ struct mtd_info *mtd = &info->mtd;
+
+#ifdef CONFIG_MTD_PARTITIONS
+ struct mtd_partition *parts = info->platform->partitions;
+ int nr = info->platform->nr_partitions;
+
+ return add_mtd_partitions(mtd, parts, nr);
+#else
+ return add_mtd_device(mtd);
+#endif
+}
+
+static int bf5xx_nand_remove(struct platform_device *pdev)
+{
+ struct bf5xx_nand_info *info = to_nand_info(pdev);
+ struct mtd_info *mtd = NULL;
+
+ platform_set_drvdata(pdev, NULL);
+
+ /* first thing we need to do is release all our mtds
+ * and their partitions, then go through freeing the
+ * resources used
+ */
+ mtd = &info->mtd;
+ if (mtd) {
+ nand_release(mtd);
+ kfree(mtd);
+ }
+
+ peripheral_free_list(bfin_nfc_pin_req);
+
+ /* free the common resources */
+ kfree(info);
+
+ return 0;
+}
+
+/*
+ * bf5xx_nand_probe
+ *
+ * called by device layer when it finds a device matching
+ * one our driver can handled. This code checks to see if
+ * it can allocate all necessary resources then calls the
+ * nand layer to look for devices
+ */
+static int bf5xx_nand_probe(struct platform_device *pdev)
+{
+ struct bf5xx_nand_platform *plat = to_nand_plat(pdev);
+ struct bf5xx_nand_info *info = NULL;
+ struct nand_chip *chip = NULL;
+ struct mtd_info *mtd = NULL;
+ int err = 0;
+
+ dev_dbg(&pdev->dev, "(%p)\n", pdev);
+
+ if (!plat) {
+ dev_err(&pdev->dev, "no platform specific information\n");
+ goto exit_error;
+ }
+
+ info = kzalloc(sizeof(*info), GFP_KERNEL);
+ if (info == NULL) {
+ dev_err(&pdev->dev, "no memory for flash info\n");
+ err = -ENOMEM;
+ goto exit_error;
+ }
+
+ platform_set_drvdata(pdev, info);
+
+ spin_lock_init(&info->controller.lock);
+ init_waitqueue_head(&info->controller.wq);
+
+ info->device = &pdev->dev;
+ info->platform = plat;
+
+ /* initialise chip data struct */
+ chip = &info->chip;
+
+ if (plat->data_width)
+ chip->options |= NAND_BUSWIDTH_16;
+
+ chip->options |= NAND_CACHEPRG | NAND_SKIP_BBTSCAN;
+
+ chip->read_buf = (plat->data_width) ?
+ bf5xx_nand_read_buf16 : bf5xx_nand_read_buf;
+ chip->write_buf = (plat->data_width) ?
+ bf5xx_nand_write_buf16 : bf5xx_nand_write_buf;
+
+ chip->read_byte = bf5xx_nand_read_byte;
+
+ chip->cmd_ctrl = bf5xx_nand_hwcontrol;
+ chip->dev_ready = bf5xx_nand_devready;
+
+ chip->priv = &info->mtd;
+ chip->controller = &info->controller;
+
+ chip->IO_ADDR_R = (void __iomem *) NFC_READ;
+ chip->IO_ADDR_W = (void __iomem *) NFC_DATA_WR;
+
+ chip->chip_delay = 0;
+
+ /* initialise mtd info data struct */
+ mtd = &info->mtd;
+ mtd->priv = chip;
+ mtd->owner = THIS_MODULE;
+
+ /* initialise the hardware */
+ err = bf5xx_nand_hw_init(info);
+ if (err != 0)
+ goto exit_error;
+
+ /* setup hardware ECC data struct */
+ if (hardware_ecc) {
+ if (plat->page_size == NFC_PG_SIZE_256) {
+ chip->ecc.bytes = 3;
+ chip->ecc.size = 256;
+ } else if (plat->page_size == NFC_PG_SIZE_512) {
+ chip->ecc.bytes = 6;
+ chip->ecc.size = 512;
+ }
+
+ chip->read_buf = bf5xx_nand_dma_read_buf;
+ chip->write_buf = bf5xx_nand_dma_write_buf;
+ chip->ecc.calculate = bf5xx_nand_calculate_ecc;
+ chip->ecc.correct = bf5xx_nand_correct_data;
+ chip->ecc.mode = NAND_ECC_HW;
+ chip->ecc.hwctl = bf5xx_nand_enable_hwecc;
+ } else {
+ chip->ecc.mode = NAND_ECC_SOFT;
+ }
+
+ /* scan hardware nand chip and setup mtd info data struct */
+ if (nand_scan(mtd, 1)) {
+ err = -ENXIO;
+ goto exit_error;
+ }
+
+ /* add NAND partition */
+ bf5xx_nand_add_partition(info);
+
+ dev_dbg(&pdev->dev, "initialised ok\n");
+ return 0;
+
+exit_error:
+ bf5xx_nand_remove(pdev);
+
+ if (err == 0)
+ err = -EINVAL;
+ return err;
+}
+
+/* PM Support */
+#ifdef CONFIG_PM
+
+static int bf5xx_nand_suspend(struct platform_device *dev, pm_message_t pm)
+{
+ struct bf5xx_nand_info *info = platform_get_drvdata(dev);
+
+ return 0;
+}
+
+static int bf5xx_nand_resume(struct platform_device *dev)
+{
+ struct bf5xx_nand_info *info = platform_get_drvdata(dev);
+
+ if (info)
+ bf5xx_nand_hw_init(info);
+
+ return 0;
+}
+
+#else
+#define bf5xx_nand_suspend NULL
+#define bf5xx_nand_resume NULL
+#endif
+
+/* driver device registration */
+static struct platform_driver bf5xx_nand_driver = {
+ .probe = bf5xx_nand_probe,
+ .remove = bf5xx_nand_remove,
+ .suspend = bf5xx_nand_suspend,
+ .resume = bf5xx_nand_resume,
+ .driver = {
+ .name = DRV_NAME,
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init bf5xx_nand_init(void)
+{
+ printk(KERN_INFO "%s, Version %s (c) 2007 Analog Devices, Inc.\n",
+ DRV_DESC, DRV_VERSION);
+
+ return platform_driver_register(&bf5xx_nand_driver);
+}
+
+static void __exit bf5xx_nand_exit(void)
+{
+ platform_driver_unregister(&bf5xx_nand_driver);
+}
+
+module_init(bf5xx_nand_init);
+module_exit(bf5xx_nand_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR(DRV_AUTHOR);
+MODULE_DESCRIPTION(DRV_DESC);
diff --git a/include/asm-blackfin/mach-bf548/dma.h b/include/asm-blackfin/mach-bf548/dma.h
index fcc8b4c..14cb10c 100644
--- a/include/asm-blackfin/mach-bf548/dma.h
+++ b/include/asm-blackfin/mach-bf548/dma.h
@@ -55,6 +55,7 @@
#define CH_SPORT3_RX 20
#define CH_SPORT3_TX 21
#define CH_SDH 22
+#define CH_NFC 22
#define CH_SPI2 23

#define CH_MEM_STREAM0_DEST 24
diff --git a/include/asm-blackfin/nand.h b/include/asm-blackfin/nand.h
new file mode 100644
index 0000000..afbaafa
--- /dev/null
+++ b/include/asm-blackfin/nand.h
@@ -0,0 +1,47 @@
+/* linux/include/asm-blackfin/nand.h
+ *
+ * Copyright (c) 2007 Analog Devices, Inc.
+ * Bryan Wu <[email protected]>
+ *
+ * BF5XX - NAND flash controller platfrom_device info
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+/* struct bf5xx_nand_platform
+ *
+ * define a interface between platfrom board specific code and
+ * bf54x NFC driver.
+ *
+ * nr_partitions = number of partitions pointed to be partitoons (or zero)
+ * partitions = mtd partition list
+ */
+
+#define NFC_PG_SIZE_256 0
+#define NFC_PG_SIZE_512 1
+#define NFC_PG_SIZE_OFFSET 9
+
+#define NFC_NWIDTH_8 0
+#define NFC_NWIDTH_16 1
+#define NFC_NWIDTH_OFFSET 8
+
+#define NFC_RDDLY_OFFSET 4
+#define NFC_WRDLY_OFFSET 0
+
+#define NFC_STAT_NBUSY 1
+
+struct bf5xx_nand_platform {
+ /* NAND chip information */
+ unsigned short page_size;
+ unsigned short data_width;
+
+ /* RD/WR strobe delay timing information, all times in SCLK cycles */
+ unsigned short rd_dly;
+ unsigned short wr_dly;
+
+ /* NAND MTD partition information */
+ int nr_partitions;
+ struct mtd_partition *partitions;
+};
--
1.5.2