[PATCH] [MTD] NAND: add ts7xxx driver

Alexander Clouter alex at digriz.org.uk
Sun Feb 8 03:59:19 EST 2009


This patch adds support for the NAND found in Technologic Systems ARM
boards[1].  The platform specific parts (IO address and parititoning
schemes) have been moved into platform specific files whilst the driver
it's self can be used as a complete replacement for the ts7250 NAND
driver.

[1] http://www.embeddedarm.com/products/arm-sbc.php

Signed-off-by: Alexander Clouter <alex at digriz.org.uk>
---
 drivers/mtd/nand/Kconfig        |    8 +-
 drivers/mtd/nand/Makefile       |    1 +
 drivers/mtd/nand/ts7xxx.c       |  334 +++++++++++++++++++++++++++++++++++++++
 include/linux/mtd/nand-ts7xxx.h |   35 ++++
 4 files changed, 377 insertions(+), 1 deletions(-)
 create mode 100644 drivers/mtd/nand/ts7xxx.c
 create mode 100644 include/linux/mtd/nand-ts7xxx.h

diff --git a/drivers/mtd/nand/Kconfig b/drivers/mtd/nand/Kconfig
index f8ae040..6c9d344 100644
--- a/drivers/mtd/nand/Kconfig
+++ b/drivers/mtd/nand/Kconfig
@@ -76,10 +76,16 @@ config MTD_NAND_AMS_DELTA
 
 config MTD_NAND_TS7250
 	tristate "NAND Flash device on TS-7250 board"
-	depends on MACH_TS72XX
+	depends on MACH_TS72XX && !MTD_NAND_TS7XXX
 	help
 	  Support for NAND flash on Technologic Systems TS-7250 platform.
 
+config MTD_NAND_TS7XXX
+	tristate "NAND Flash device on TS-7xxx boards"
+	depends on MACH_TS72XX || MACH_TS78XX
+	help
+	  Support for NAND flash on Technologic Systems TS-7xxx platforms.
+
 config MTD_NAND_IDS
 	tristate
 
diff --git a/drivers/mtd/nand/Makefile b/drivers/mtd/nand/Makefile
index b661586..68694c7 100644
--- a/drivers/mtd/nand/Makefile
+++ b/drivers/mtd/nand/Makefile
@@ -19,6 +19,7 @@ obj-$(CONFIG_MTD_NAND_H1900)		+= h1910.o
 obj-$(CONFIG_MTD_NAND_RTC_FROM4)	+= rtc_from4.o
 obj-$(CONFIG_MTD_NAND_SHARPSL)		+= sharpsl.o
 obj-$(CONFIG_MTD_NAND_TS7250)		+= ts7250.o
+obj-$(CONFIG_MTD_NAND_TS7XXX)		+= ts7xxx.o
 obj-$(CONFIG_MTD_NAND_NANDSIM)		+= nandsim.o
 obj-$(CONFIG_MTD_NAND_CS553X)		+= cs553x_nand.o
 obj-$(CONFIG_MTD_NAND_NDFC)		+= ndfc.o
diff --git a/drivers/mtd/nand/ts7xxx.c b/drivers/mtd/nand/ts7xxx.c
new file mode 100644
index 0000000..a2bc309
--- /dev/null
+++ b/drivers/mtd/nand/ts7xxx.c
@@ -0,0 +1,334 @@
+/*
+ * drivers/mtd/nand/ts7xxx.c
+ *
+ * Copyright (C) 2008 Alexander Clouter (alex at digriz.org.uk)
+ *
+ * Derived from drivers/mtd/nand/ts7250.c
+ *   Copyright (C) 2004 Technologic Systems (support at embeddedARM.com)
+ *
+ * More information of course gleaned from Technologic Systems driver
+ * in their supplied kernel drivers/mtd/nand/ts7800.c and
+ * ts-7800-nand-regmap.txt; of course also the existing ts7250.c driver
+ *
+ * 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.
+ *
+ * Overview:
+ *   This is a device driver for the NAND flash device found on the TS-7xxx
+ *   board ranges.  DMA and HW ECC support is only available on the TS-78xx
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/nand.h>
+#include <linux/mtd/nand_ecc.h>
+#include <linux/mtd/partitions.h>
+#include <linux/mtd/nand-ts7xxx.h>
+#include <linux/platform_device.h>
+#include <linux/io.h>
+#include <asm/mach-types.h>
+
+/* needed for board_is_ts7200() */
+#ifdef CONFIG_MACH_TS72XX
+#include <mach/hardware.h>
+#endif
+
+/*
+ * MTD structure for TS7xxx board
+ */
+static struct mtd_info *ts7xxx_mtd;
+
+static struct ts7xxx_nand_data *ts7xxx_nand_data;
+static void __iomem **ts7xxx_iobase;
+static void __iomem *ts7xxx_ioctrl;
+static void __iomem *ts7xxx_iodata;
+static void __iomem *ts7xxx_iobusy;
+
+/*
+ * hardware specific access to control-lines
+ *
+ * ctrl:
+ * NAND_NCE: bit 0 -> bit 2
+ * NAND_CLE: bit 1 -> bit 1
+ * NAND_ALE: bit 2 -> bit 0
+ */
+static void ts7xxx_hwcontrol(struct mtd_info *mtd, int cmd, unsigned int ctrl)
+{
+	struct nand_chip *this = mtd->priv;
+
+	if (ctrl & NAND_CTRL_CHANGE) {
+		unsigned char bits;
+
+		bits = (ctrl & NAND_NCE) << 2;
+		bits |= ctrl & NAND_CLE;
+		bits |= (ctrl & NAND_ALE) >> 2;
+
+		writeb((readb(ts7xxx_ioctrl) & ~0x7) | bits, ts7xxx_ioctrl);
+	}
+
+	if (cmd != NAND_CMD_NONE)
+		writeb(cmd, this->IO_ADDR_W);
+}
+
+static int ts7xxx_device_ready(struct mtd_info *mtd)
+{
+	return readb(ts7xxx_iobusy) & 0x20;
+}
+
+#ifdef CONFIG_MACH_TS78XX
+/*
+ * The HW ECC offloading functions, gives about a 9% performance increase
+ * for 'dd if=/dev/mtdblockX' and 5% for nanddump.
+ */
+static void ts7xxx_enable_hwecc(struct mtd_info *mtd, int mode)
+{
+	unsigned int ecc;
+
+	/* we loop as the FIFO might need emptying */
+	do {
+		ecc = readl(ts7xxx_ioctrl);
+		writel(ecc | 0x8, ts7xxx_ioctrl);
+	} while ((ecc & 0x18) != 0x18);
+}
+
+static int ts7xxx_calculate_ecc(struct mtd_info *mtd, const uint8_t *dat,
+				uint8_t *ecc_code)
+{
+	unsigned int i, ecc;
+
+	for (i = 0; i < 8; i++) {
+		/*
+		 * TS told me that their HW ECC implementation spits out on the
+		 * 22 LSB's and not the MSB's which is why we right shift
+		 * trimming the top and not the bottom!
+		 */
+		ecc = (readl(ts7xxx_ioctrl) >> 6) | 0x03;
+
+		*ecc_code++ = (ecc >> 16) & 0xff;
+		*ecc_code++ = (ecc >>  8) & 0xff;
+		*ecc_code++ = (ecc >>  0) & 0xff;
+	};
+
+	return 0;
+}
+
+static int ts7xx_correct_data(struct mtd_info *mtd, uint8_t *dat,
+				uint8_t *read_ecc, uint8_t *calc_ecc)
+{
+	int ret = 0;
+	unsigned int i;
+
+	for (i = 0; i < 8; i++) {
+		ret |= nand_correct_data(mtd, dat + (256 * i),
+						&read_ecc[i * 3],
+						&calc_ecc[i * 3]);
+	}
+
+	return ret;
+}
+#endif
+
+static void ts7xxx_nand_cleanup(struct platform_device *pdev)
+{
+	unsigned int i;
+
+	for (i = 0; i < pdev->num_resources; i++) {
+		if (ts7xxx_iobase[i])
+			iounmap(ts7xxx_iobase[i]);
+	}
+	kfree(ts7xxx_iobase);
+	kfree(ts7xxx_mtd);
+}
+
+/*
+ * Main initialization routine
+ */
+static int __devinit ts7xxx_nand_probe(struct platform_device *pdev)
+{
+	struct nand_chip *this;
+	unsigned int i;
+	struct ts7xxx_nand_data	*(*ts7xxx_nand_data_func)(unsigned int)
+						= pdev->dev.platform_data;
+#ifdef CONFIG_MTD_PARTITIONS
+#ifdef CONFIG_MTD_CMDLINE_PARTS
+	static const char *part_probes[] = { "cmdlinepart", NULL };
+#endif
+	struct mtd_partition *mtd_parts = NULL;
+	int mtd_parts_nb = 0;
+	const char *part_type = NULL;
+#endif
+
+#ifdef CONFIG_MACH_TS72XX
+	if (!machine_is_ts72xx() || board_is_ts7200())
+		return -ENXIO;
+#endif
+#ifdef CONFIG_MACH_TS78XX
+	if (!machine_is_ts78xx())
+		return -ENXIO;
+#endif
+
+	/* Allocate memory for MTD device structure and private data */
+	ts7xxx_mtd = kmalloc(sizeof(struct mtd_info)
+					+ sizeof(struct nand_chip),
+				GFP_KERNEL);
+	if (!ts7xxx_mtd) {
+		printk(KERN_ERR "Unable to allocate TS7xxx NAND "
+				"MTD device structure.\n");
+		return -ENOMEM;
+	}
+
+	/*
+	 * this is runtime as we do not know the size of the NAND, and thus
+	 * its partitioning scheme, until we scan it here
+	 *
+	 * N.B. we use a dummy size of zero to get the currently useful info
+	 */
+	ts7xxx_nand_data = (*ts7xxx_nand_data_func)(0);
+
+	ts7xxx_iobase = kmalloc(pdev->num_resources * sizeof(void __iomem *),
+					GFP_KERNEL);
+	if (!ts7xxx_iobase) {
+		printk(KERN_ERR "ts7xxx: Unable to allocate ioremap array.\n");
+		kfree(ts7xxx_mtd);
+		return -ENOMEM;
+	}
+	memset(ts7xxx_iobase, 0, pdev->num_resources * sizeof(void __iomem *));
+
+	for (i = 0; i < pdev->num_resources; i++) {
+		ts7xxx_iobase[i] = ioremap(pdev->resource[i].start,
+					pdev->resource[i].end
+						- pdev->resource[i].start + 1);
+		if (!ts7xxx_iobase[i]) {
+			printk(KERN_ERR "ts7xxx: ioremap failed\n");
+			ts7xxx_nand_cleanup(pdev);
+			return -EIO;
+		}
+	}
+
+	ts7xxx_ioctrl = ts7xxx_iobase[ts7xxx_nand_data->ioports.ctrl.res]
+				+ ts7xxx_nand_data->ioports.ctrl.offset;
+	ts7xxx_iodata = ts7xxx_iobase[ts7xxx_nand_data->ioports.data.res]
+				+ ts7xxx_nand_data->ioports.data.offset;
+	ts7xxx_iobusy = ts7xxx_iobase[ts7xxx_nand_data->ioports.busy.res]
+				+ ts7xxx_nand_data->ioports.busy.offset;
+
+	/* Get pointer to private data */
+	this = (struct nand_chip *)(&ts7xxx_mtd[1]);
+
+	/* Initialize structures */
+	memset(ts7xxx_mtd, 0, sizeof(struct mtd_info));
+	memset(this, 0, sizeof(struct nand_chip));
+
+	/* Link the private data with the MTD structure */
+	ts7xxx_mtd->priv = this;
+	ts7xxx_mtd->owner = THIS_MODULE;
+
+	/* insert callbacks */
+	this->IO_ADDR_R = this->IO_ADDR_W = ts7xxx_iodata;
+	this->cmd_ctrl = ts7xxx_hwcontrol;
+	this->dev_ready = ts7xxx_device_ready;
+	this->chip_delay = 15;
+
+	do {
+		this->ecc.mode = NAND_ECC_SOFT;
+
+#ifdef CONFIG_MACH_TS78XX
+		if (!machine_is_ts78xx()) {
+			this->options = NAND_USE_FLASH_BBT;
+			this->ecc.mode = NAND_ECC_HW;
+			/* the HW ECC has an eight entry FIFO */
+			this->ecc.size = 8 * 256;
+			this->ecc.bytes = 8 * 3;
+			this->ecc.hwctl  = ts7xxx_enable_hwecc;
+			this->ecc.calculate = ts7xxx_calculate_ecc;
+			this->ecc.correct  = ts7xx_correct_data;
+			break;
+		}
+#endif
+	} while (0);
+
+	printk(KERN_NOTICE "Searching for NAND flash...\n");
+	/* Scan to find existence of the device */
+	if (nand_scan(ts7xxx_mtd, 1)) {
+		ts7xxx_nand_cleanup(pdev);
+		return -ENODEV;
+	}
+
+#ifdef CONFIG_MTD_PARTITIONS
+	ts7xxx_mtd->name = "ts7xxx-nand";
+#ifdef CONFIG_MTD_CMDLINE_PARTS
+	mtd_parts_nb = parse_mtd_partitions(ts7xxx_mtd,
+						part_probes, &mtd_parts, 0);
+	if (mtd_parts_nb > 0)
+		part_type = "command line";
+	else
+		mtd_parts_nb = 0;
+#endif
+
+	if (mtd_parts_nb == 0) {
+		/*
+		 * ...and now we can use size to find the partitioning scheme
+		 */
+		ts7xxx_nand_data = (*ts7xxx_nand_data_func)(ts7xxx_mtd->size);
+
+		if (ts7xxx_nand_data->partitions == NULL) {
+			printk(KERN_ERR
+				"ts7xxx_nand: unsupported NAND size, received "
+				"no paritioning scheme from platform\n");
+			ts7xxx_nand_cleanup(pdev);
+			/* FIXME: is this the most suitable return value? */
+			return -ENXIO;
+		}
+
+		mtd_parts = ts7xxx_nand_data->partitions;
+		mtd_parts_nb = ts7xxx_nand_data->nr_partitions;
+		part_type = "static";
+	}
+
+	/* Register the partitions */
+	printk(KERN_NOTICE "Using %s partition definition\n", part_type);
+	add_mtd_partitions(ts7xxx_mtd, mtd_parts, mtd_parts_nb);
+#endif
+
+	/* Return happy */
+	return 0;
+}
+
+static int __devexit ts7xxx_nand_remove(struct platform_device *pdev)
+{
+#ifdef CONFIG_MTD_PARTITIONS
+	del_mtd_partitions(ts7xxx_mtd);
+#endif
+	ts7xxx_nand_cleanup(pdev);
+
+	return 0;
+}
+
+/* driver device registration */
+static struct platform_driver ts7xxx_nand_driver = {
+	.driver		= {
+		.name	= "ts7xxx_nand",
+		.owner	= THIS_MODULE,
+	},
+	.probe		= ts7xxx_nand_probe,
+	.remove		= __devexit_p(ts7xxx_nand_remove),
+};
+
+static int __init ts7xxx_nand_init(void)
+{
+	return platform_driver_register(&ts7xxx_nand_driver);
+}
+
+static void __exit ts7xxx_nand_exit(void)
+{
+	platform_driver_unregister(&ts7xxx_nand_driver);
+}
+
+MODULE_AUTHOR("Alexander Clouter <alex at digriz.org.uk>");
+MODULE_DESCRIPTION("MTD NAND driver for Technologic Systems TS-7xxx boards");
+MODULE_LICENSE("GPL");
+
+module_init(ts7xxx_nand_init);
+module_exit(ts7xxx_nand_exit);
diff --git a/include/linux/mtd/nand-ts7xxx.h b/include/linux/mtd/nand-ts7xxx.h
new file mode 100644
index 0000000..955209a
--- /dev/null
+++ b/include/linux/mtd/nand-ts7xxx.h
@@ -0,0 +1,35 @@
+/*
+ * linux/include/linux/mtd/nand-ts7xxx.h
+ *
+ * Copyright (c) 2008 Alexander Clouter <alex at digriz.org.uk>
+ *
+ * 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.
+ */
+
+#include <linux/mtd/partitions.h>
+
+struct ts7xxx_ioport_entry {
+	unsigned int			res;
+	unsigned int			offset;
+};
+
+struct ts7xxx_ioports {
+	struct ts7xxx_ioport_entry	ctrl;
+	struct ts7xxx_ioport_entry	data;
+
+	struct ts7xxx_ioport_entry	busy;
+};
+
+/*
+ * struct ts7xxx_nand_data - chip level device structure
+ * @nr_partitions:	number of partitions pointed to by partitions (or zero)
+ * @partitions:		mtd partition list
+ */
+struct ts7xxx_nand_data {
+	int				nr_partitions;
+	struct mtd_partition		*partitions;
+
+	struct ts7xxx_ioports		ioports;
+};
-- 
1.5.6.5



More information about the linux-mtd mailing list