[PATCH] mtd: Add open firmware GPIO NAND driver

Matthias Fuchs matthias.fuchs at esd-electronics.com
Sun Jan 25 12:01:51 EST 2009


This patch adds a GPIO based nand driver that is configured through
the open firmware flat device tree. It's based on the gpio platform
nand driver.

The driver is tested on a PPC405EP based PowerPC board with a single
8 bit NAND chip. nCE, ALE, CLE and RDY are connected to the CPUs GPIO
pins GPIO1..4.

The driver does not handle:
 - 16 bit NAND chips (no hardware for testing)
 - any other interfacing than ALE, CLE, nCE, RDY via GPIO and databus via
   8 bit mem'mapped access
 - multiple NAND chips

Just as an idea: this driver could replace ppchameleonevb.c.

Signed-off-by: Matthias Fuchs <matthias.fuchs at esd-electronics.com>
---
 drivers/mtd/nand/Kconfig   |    6 +
 drivers/mtd/nand/Makefile  |    1 +
 drivers/mtd/nand/of_gpio.c |  366 ++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 373 insertions(+), 0 deletions(-)
 create mode 100644 drivers/mtd/nand/of_gpio.c

diff --git a/drivers/mtd/nand/Kconfig b/drivers/mtd/nand/Kconfig
index 8b12e6e..1853c63 100644
--- a/drivers/mtd/nand/Kconfig
+++ b/drivers/mtd/nand/Kconfig
@@ -62,6 +62,12 @@ config MTD_NAND_GPIO
 	help
 	  This enables a GPIO based NAND flash driver.
 
+config MTD_NAND_OF_GPIO
+	tristate "Open Firmware GPIO NAND Flash driver"
+	depends on GENERIC_GPIO && PPC_OF
+	help
+	  This enables a GPIO based NAND flash driver.
+
 config MTD_NAND_SPIA
 	tristate "NAND Flash device on SPIA board"
 	depends on ARCH_P720T
diff --git a/drivers/mtd/nand/Makefile b/drivers/mtd/nand/Makefile
index b661586..d89332b 100644
--- a/drivers/mtd/nand/Makefile
+++ b/drivers/mtd/nand/Makefile
@@ -24,6 +24,7 @@ obj-$(CONFIG_MTD_NAND_CS553X)		+= cs553x_nand.o
 obj-$(CONFIG_MTD_NAND_NDFC)		+= ndfc.o
 obj-$(CONFIG_MTD_NAND_ATMEL)		+= atmel_nand.o
 obj-$(CONFIG_MTD_NAND_GPIO)		+= gpio.o
+obj-$(CONFIG_MTD_NAND_OF_GPIO)		+= of_gpio.o
 obj-$(CONFIG_MTD_NAND_CM_X270)		+= cmx270_nand.o
 obj-$(CONFIG_MTD_NAND_BASLER_EXCITE)	+= excite_nandflash.o
 obj-$(CONFIG_MTD_NAND_PXA3xx)		+= pxa3xx_nand.o
diff --git a/drivers/mtd/nand/of_gpio.c b/drivers/mtd/nand/of_gpio.c
new file mode 100644
index 0000000..2c45e3b
--- /dev/null
+++ b/drivers/mtd/nand/of_gpio.c
@@ -0,0 +1,366 @@
+/*
+ * drivers/mtd/nand/of_gpio.c
+ *
+ * Open Firmware device driver for NAND connected via GPIO
+ *
+ * Copyright 2009 esd Gmbh
+ *   Matthias Fuchs <matthias.fuchs at esd-electronics.com>
+ *
+ * Based on GPIO platform NAND driver by
+ * Ben Dooks <ben at simtec.co.uk> and Russel King
+ *
+ * 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.
+ *
+ *
+ * Sample device tree bindings:
+ *
+ *	...
+ *	nand at 1,0 {
+ *		compatible = "gpio-nand";
+ *		reg = <0x00000001 0x00000000 0x00002000>;
+ *		chip-delay = <50>;
+ *		options = <0x00000000>;
+ *		#address-cells = <1>;
+ *		#size-cells = <1>;
+ *
+ *		gpios =	<&GPIO 1 0
+ *			 &GPIO 2 0
+ *			 &GPIO 3 0
+ *			 &GPIO 4 0>;
+ *
+ *		nand {
+ *			#address-cells = <1>;
+ *			#size-cells = <1>;
+ *
+ *			partition at 0 {
+ *				...
+ *			};
+ *			...
+ *		};
+ *	};
+ *	...
+ */
+
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/gpio.h>
+#include <linux/of_gpio.h>
+#include <linux/io.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/nand.h>
+#include <linux/mtd/partitions.h>
+#include <linux/of_platform.h>
+
+struct of_gpio_nand_controller {
+	struct of_device *ofdev;
+	void __iomem *base;
+	int gpio_nce;
+	int gpio_cle;
+	int gpio_ale;
+	int gpio_rdy;
+	struct mtd_info mtd;
+	struct nand_chip chip;
+#ifdef CONFIG_MTD_PARTITIONS
+	struct mtd_partition *parts;
+#endif
+};
+
+static struct of_gpio_nand_controller of_gpio_nand_ctrl;
+
+static void
+of_gpio_nand_hwcontrol(struct mtd_info *mtd, int cmd, unsigned int ctrl)
+{
+	struct nand_chip *chip = mtd->priv;
+	struct of_gpio_nand_controller *of_gpio_nand = &of_gpio_nand_ctrl;
+
+	if (ctrl & NAND_CTRL_CHANGE) {
+		gpio_set_value(of_gpio_nand->gpio_nce, !(ctrl & NAND_NCE));
+		gpio_set_value(of_gpio_nand->gpio_cle, !!(ctrl & NAND_CLE));
+		gpio_set_value(of_gpio_nand->gpio_ale, !!(ctrl & NAND_ALE));
+	}
+	if (cmd == NAND_CMD_NONE)
+		return;
+
+	writeb(cmd, chip->IO_ADDR_W);
+}
+
+static void
+of_gpio_nand_writebuf(struct mtd_info *mtd, const u_char *buf, int len)
+{
+	struct nand_chip *chip = mtd->priv;
+
+	writesb(chip->IO_ADDR_W, buf, len);
+}
+
+static void of_gpio_nand_readbuf(struct mtd_info *mtd, u_char *buf, int len)
+{
+	struct nand_chip *chip = mtd->priv;
+
+	readsb(chip->IO_ADDR_R, buf, len);
+}
+
+static int
+of_gpio_nand_verifybuf(struct mtd_info *mtd, const u_char *buf, int len)
+{
+	struct nand_chip *chip = mtd->priv;
+	unsigned char read, *p = (unsigned char *)buf;
+	int i, err = 0;
+
+	for (i = 0; i < len; i++) {
+		read = readb(chip->IO_ADDR_R);
+		if (read != p[i]) {
+			pr_debug("%s: err at %d (read %04x vs %04x)\n",
+				 __func__, i, read, p[i]);
+			err = -EFAULT;
+		}
+	}
+	return err;
+}
+
+static int of_gpio_nand_devready(struct mtd_info *mtd)
+{
+	struct of_gpio_nand_controller *of_gpio_nand = &of_gpio_nand_ctrl;
+
+	return gpio_get_value(of_gpio_nand->gpio_rdy);
+}
+
+static int of_gpio_nand_chip_init(struct of_gpio_nand_controller *of_gpio_nand,
+				  struct device_node *node)
+{
+#ifdef CONFIG_MTD_PARTITIONS
+#ifdef CONFIG_MTD_CMDLINE_PARTS
+	static const char *part_types[] = { "cmdlinepart", NULL };
+#else
+	static const char *part_types[] = { NULL };
+#endif
+#endif
+	struct device_node *flash_np;
+	struct nand_chip *chip = &of_gpio_nand->chip;
+	int ret;
+	const u32 *reg;
+
+	chip->IO_ADDR_R = of_gpio_nand->base;
+	chip->IO_ADDR_W = of_gpio_nand->base;
+	chip->cmd_ctrl = of_gpio_nand_hwcontrol;
+	chip->dev_ready = of_gpio_nand_devready;
+	chip->read_buf = of_gpio_nand_readbuf;
+	chip->write_buf = of_gpio_nand_writebuf;
+	chip->verify_buf = of_gpio_nand_verifybuf;
+	chip->ecc.mode   = NAND_ECC_SOFT;
+
+	reg = of_get_property(node, "chip-delay", NULL);
+	if (reg)
+		chip->chip_delay = *reg;
+
+	reg = of_get_property(node, "options", NULL);
+	if (reg)
+		chip->options = *reg;
+
+	if (chip->options & NAND_BUSWIDTH_16) {
+		dev_err(&of_gpio_nand->ofdev->dev,
+			"16 bit bus width not supported\n");
+		return -ENOENT;
+	}
+
+	of_gpio_nand->mtd.priv = chip;
+	of_gpio_nand->mtd.owner = THIS_MODULE;
+
+	flash_np = of_get_next_child(node, NULL);
+	if (!flash_np)
+		return -ENODEV;
+
+	of_gpio_nand->mtd.name = kasprintf(GFP_KERNEL, "%s.%s",
+					   of_gpio_nand->ofdev->dev.bus_id,
+					   flash_np->name);
+	if (!of_gpio_nand->mtd.name) {
+		ret = -ENOMEM;
+		goto err;
+	}
+
+	ret = nand_scan(&of_gpio_nand->mtd, 1);
+	if (ret)
+		goto err;
+
+#ifdef CONFIG_MTD_PARTITIONS
+	ret = parse_mtd_partitions(&of_gpio_nand->mtd, part_types,
+				   &of_gpio_nand->parts, 0);
+	if (ret < 0)
+		goto err;
+
+#ifdef CONFIG_MTD_OF_PARTS
+	if (ret == 0) {
+		ret = of_mtd_parse_partitions(&of_gpio_nand->ofdev->dev,
+					      flash_np,
+					      &of_gpio_nand->parts);
+		if (ret < 0)
+			goto err;
+	}
+#endif
+
+	if (ret > 0)
+		ret = add_mtd_partitions(&of_gpio_nand->mtd,
+					 of_gpio_nand->parts, ret);
+	else
+#endif
+		ret = add_mtd_device(&of_gpio_nand->mtd);
+
+err:
+	of_node_put(flash_np);
+	if (ret)
+		kfree(of_gpio_nand->mtd.name);
+	return ret;
+}
+
+static int __devinit of_gpio_nand_probe(struct of_device *ofdev,
+					const struct of_device_id *match)
+{
+	struct of_gpio_nand_controller *of_gpio_nand = &of_gpio_nand_ctrl;
+	struct device *dev = &ofdev->dev;
+	int ret;
+
+	of_gpio_nand->ofdev = ofdev;
+	dev_set_drvdata(&ofdev->dev, of_gpio_nand);
+
+	of_gpio_nand->base = of_iomap(ofdev->node, 0);
+	if (!of_gpio_nand->base) {
+		dev_err(&ofdev->dev, "failed to get memory\n");
+		return -EIO;
+	}
+
+	if (of_gpio_count(ofdev->node) != 4) {
+		ret = -ENOENT;
+		goto err_nce;
+	}
+
+	/* CE# */
+	ret = of_get_gpio(ofdev->node, 0);
+	if (ret < 0) {
+		dev_err(dev, "Invalid gpio spec 0\n");
+		goto err_nce;
+	} else {
+		of_gpio_nand->gpio_nce = ret;
+		ret = gpio_request(of_gpio_nand->gpio_nce, ofdev->node->name);
+		if (ret) {
+			dev_err(dev, "Requesting gpio %d failed\n",
+				of_gpio_nand->gpio_nce);
+			goto err_nce;
+		}
+		gpio_direction_output(of_gpio_nand->gpio_nce, 1);
+	}
+
+	/* CLE */
+	ret = of_get_gpio(ofdev->node, 1);
+	if (ret < 0) {
+		dev_err(dev, "Invalid gpio spec 1\n");
+		goto err_cle;
+	} else {
+		of_gpio_nand->gpio_cle = ret;
+		ret = gpio_request(of_gpio_nand->gpio_cle, ofdev->node->name);
+		if (ret) {
+			dev_err(dev, "Requesting gpio %d failed\n",
+				of_gpio_nand->gpio_cle);
+			goto err_cle;
+		}
+		gpio_direction_output(of_gpio_nand->gpio_cle, 0);
+	}
+
+	/* ALE */
+	ret = of_get_gpio(ofdev->node, 2);
+	if (ret < 0) {
+		dev_err(dev, "Invalid gpio spec 2\n");
+		goto err_ale;
+	} else {
+		of_gpio_nand->gpio_ale = ret;
+		ret = gpio_request(of_gpio_nand->gpio_ale, ofdev->node->name);
+		if (ret) {
+			dev_err(dev, "Requesting gpio %d failed\n",
+				of_gpio_nand->gpio_ale);
+			goto err_ale;
+		}
+		gpio_direction_output(of_gpio_nand->gpio_ale, 0);
+	}
+
+	/* RDY */
+	ret = of_get_gpio(ofdev->node, 3);
+	if (ret < 0) {
+		dev_err(dev, "Invalid gpio spec 3\n");
+		goto err_rdy;
+	} else {
+		of_gpio_nand->gpio_rdy = ret;
+		ret = gpio_request(of_gpio_nand->gpio_rdy, ofdev->node->name);
+		if (ret) {
+			dev_err(dev, "Requesting gpio %d failed\n",
+				of_gpio_nand->gpio_rdy);
+			goto err_rdy;
+		}
+		gpio_direction_input(of_gpio_nand->gpio_rdy);
+	}
+
+	ret = of_gpio_nand_chip_init(of_gpio_nand, ofdev->node);
+	if (ret == 0)
+		return 0;
+
+	gpio_free(of_gpio_nand->gpio_rdy);
+err_rdy:
+	gpio_free(of_gpio_nand->gpio_ale);
+err_ale:
+	gpio_free(of_gpio_nand->gpio_cle);
+err_cle:
+	gpio_free(of_gpio_nand->gpio_nce);
+err_nce:
+	iounmap(of_gpio_nand->base);
+
+	return ret;
+}
+
+static int __devexit of_gpio_nand_remove(struct of_device *ofdev)
+{
+	struct of_gpio_nand_controller *of_gpio_nand =
+		dev_get_drvdata(&ofdev->dev);
+
+	nand_release(&of_gpio_nand->mtd);
+
+	gpio_free(of_gpio_nand->gpio_rdy);
+	gpio_free(of_gpio_nand->gpio_ale);
+	gpio_free(of_gpio_nand->gpio_cle);
+	gpio_free(of_gpio_nand->gpio_nce);
+	iounmap(of_gpio_nand->base);
+
+	return 0;
+}
+
+static const struct of_device_id of_gpio_nand_match[] = {
+	{ .compatible = "gpio-nand", },
+	{}
+};
+MODULE_DEVICE_TABLE(of, of_gpio_nand_match);
+
+static struct of_platform_driver of_gpio_nand_driver = {
+	.driver = {
+		.name	= "of_gpio_nand",
+	},
+	.match_table = of_gpio_nand_match,
+	.probe = of_gpio_nand_probe,
+	.remove = __devexit_p(of_gpio_nand_remove),
+};
+
+static int __init of_gpio_nand_init(void)
+{
+	return of_register_platform_driver(&of_gpio_nand_driver);
+}
+
+static void __exit of_gpio_nand_exit(void)
+{
+	of_unregister_platform_driver(&of_gpio_nand_driver);
+}
+
+module_init(of_gpio_nand_init);
+module_exit(of_gpio_nand_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Matthias Fuchs <matthias.fuchs at esd-electronics.com>");
+MODULE_DESCRIPTION("Open Firmware GPIO NAND Driver");
-- 
1.5.6.3





More information about the linux-mtd mailing list