summaryrefslogtreecommitdiffstats
path: root/drivers/mtd/nand/raw/ndfc.c
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2018-04-06 12:15:41 -0700
committerLinus Torvalds <torvalds@linux-foundation.org>2018-04-06 12:15:41 -0700
commit3fd14cdcc05a682b03743683ce3a726898b20555 (patch)
treed9f1a1f18d8a4168dec47d85397f36eae7f61633 /drivers/mtd/nand/raw/ndfc.c
parent83c7c18b169bdac3dabab763d16549c1e4688a8b (diff)
parentfe5f31a8010a0cb13e72cfb72905fefa2a41730c (diff)
downloadop-kernel-dev-3fd14cdcc05a682b03743683ce3a726898b20555.zip
op-kernel-dev-3fd14cdcc05a682b03743683ce3a726898b20555.tar.gz
Merge tag 'mtd/for-4.17' of git://git.infradead.org/linux-mtd
Pull MTD updates from Boris Brezillon: "MTD Core: - Remove support for asynchronous erase (not implemented by any of the existing drivers anyway) - Remove Cyrille from the list of SPI NOR and MTD maintainers - Fix kernel doc headers - Allow users to define the partitions parsers they want to test through a DT property (compatible of the partitions subnode) - Remove the bfin-async-flash driver (the only architecture using it has been removed) - Fix pagetest test - Add extra checks in mtd_erase() - Simplify the MTD partition creation logic and get rid of mtd_add_device_partitions() MTD Drivers: - Add endianness information to the physmap DT binding - Add Eon EN29LV400A IDs to JEDEC probe logic - Use %*ph where appropriate SPI NOR Drivers: - Make fsl-quaspi assign different names to MTD devices connected to the same QSPI controller - Remove an unneeded driver.bus assigned in the fsl-qspi driver NAND Core: - Prepare arrival of the SPI NAND subsystem by implementing a generic (interface-agnostic) layer to ease manipulation of NAND devices - Move onenand code base to the drivers/mtd/nand/ dir - Rework timing mode selection - Provide a generic way for NAND chip drivers to flag a specific GET/SET FEATURE operation as supported/unsupported - Stop embedding ONFI/JEDEC param page in nand_chip NAND Drivers: - Rework/cleanup of the mxc driver - Various cleanups in the vf610 driver - Migrate the fsmc and vf610 to ->exec_op() - Get rid of the pxa driver (replaced by marvell_nand) - Support ->setup_data_interface() in the GPMI driver - Fix probe error path in several drivers - Remove support for unused hw_syndrome mode in sunxi_nand - Various minor improvements" * tag 'mtd/for-4.17' of git://git.infradead.org/linux-mtd: (89 commits) dt-bindings: fsl-quadspi: Add the example of two SPI NOR mtd: fsl-quadspi: Distinguish the mtd device names mtd: nand: Fix some function description mismatches in core.c mtd: fsl-quadspi: Remove unneeded driver.bus assignment mtd: rawnand: marvell: Rename ->ecc_clk into ->core_clk mtd: rawnand: s3c2410: enhance the probe function error path mtd: rawnand: tango: fix probe function error path mtd: rawnand: sh_flctl: fix the probe function error path mtd: rawnand: omap2: fix the probe function error path mtd: rawnand: mxc: fix probe function error path mtd: rawnand: denali: fix probe function error path mtd: rawnand: davinci: fix probe function error path mtd: rawnand: cafe: fix probe function error path mtd: rawnand: brcmnand: fix probe function error path mtd: rawnand: sunxi: Stop supporting ECC_HW_SYNDROME mode mtd: rawnand: marvell: Fix clock resource by adding a register clock mtd: ftl: Use DIV_ROUND_UP() mtd: Fix some function description mismatches in mtdcore.c mtd: physmap_of: update struct map_info's swap as per map requirement dt-bindings: mtd-physmap: Add endianness supports ...
Diffstat (limited to 'drivers/mtd/nand/raw/ndfc.c')
-rw-r--r--drivers/mtd/nand/raw/ndfc.c286
1 files changed, 286 insertions, 0 deletions
diff --git a/drivers/mtd/nand/raw/ndfc.c b/drivers/mtd/nand/raw/ndfc.c
new file mode 100644
index 0000000..d8a8068
--- /dev/null
+++ b/drivers/mtd/nand/raw/ndfc.c
@@ -0,0 +1,286 @@
+/*
+ * Overview:
+ * Platform independent driver for NDFC (NanD Flash Controller)
+ * integrated into EP440 cores
+ *
+ * Ported to an OF platform driver by Sean MacLennan
+ *
+ * The NDFC supports multiple chips, but this driver only supports a
+ * single chip since I do not have access to any boards with
+ * multiple chips.
+ *
+ * Author: Thomas Gleixner
+ *
+ * Copyright 2006 IBM
+ * Copyright 2008 PIKA Technologies
+ * Sean MacLennan <smaclennan@pikatech.com>
+ *
+ * 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.
+ *
+ */
+#include <linux/module.h>
+#include <linux/mtd/rawnand.h>
+#include <linux/mtd/nand_ecc.h>
+#include <linux/mtd/partitions.h>
+#include <linux/mtd/ndfc.h>
+#include <linux/slab.h>
+#include <linux/mtd/mtd.h>
+#include <linux/of_address.h>
+#include <linux/of_platform.h>
+#include <asm/io.h>
+
+#define NDFC_MAX_CS 4
+
+struct ndfc_controller {
+ struct platform_device *ofdev;
+ void __iomem *ndfcbase;
+ struct nand_chip chip;
+ int chip_select;
+ struct nand_hw_control ndfc_control;
+};
+
+static struct ndfc_controller ndfc_ctrl[NDFC_MAX_CS];
+
+static void ndfc_select_chip(struct mtd_info *mtd, int chip)
+{
+ uint32_t ccr;
+ struct nand_chip *nchip = mtd_to_nand(mtd);
+ struct ndfc_controller *ndfc = nand_get_controller_data(nchip);
+
+ ccr = in_be32(ndfc->ndfcbase + NDFC_CCR);
+ if (chip >= 0) {
+ ccr &= ~NDFC_CCR_BS_MASK;
+ ccr |= NDFC_CCR_BS(chip + ndfc->chip_select);
+ } else
+ ccr |= NDFC_CCR_RESET_CE;
+ out_be32(ndfc->ndfcbase + NDFC_CCR, ccr);
+}
+
+static void ndfc_hwcontrol(struct mtd_info *mtd, int cmd, unsigned int ctrl)
+{
+ struct nand_chip *chip = mtd_to_nand(mtd);
+ struct ndfc_controller *ndfc = nand_get_controller_data(chip);
+
+ if (cmd == NAND_CMD_NONE)
+ return;
+
+ if (ctrl & NAND_CLE)
+ writel(cmd & 0xFF, ndfc->ndfcbase + NDFC_CMD);
+ else
+ writel(cmd & 0xFF, ndfc->ndfcbase + NDFC_ALE);
+}
+
+static int ndfc_ready(struct mtd_info *mtd)
+{
+ struct nand_chip *chip = mtd_to_nand(mtd);
+ struct ndfc_controller *ndfc = nand_get_controller_data(chip);
+
+ return in_be32(ndfc->ndfcbase + NDFC_STAT) & NDFC_STAT_IS_READY;
+}
+
+static void ndfc_enable_hwecc(struct mtd_info *mtd, int mode)
+{
+ uint32_t ccr;
+ struct nand_chip *chip = mtd_to_nand(mtd);
+ struct ndfc_controller *ndfc = nand_get_controller_data(chip);
+
+ ccr = in_be32(ndfc->ndfcbase + NDFC_CCR);
+ ccr |= NDFC_CCR_RESET_ECC;
+ out_be32(ndfc->ndfcbase + NDFC_CCR, ccr);
+ wmb();
+}
+
+static int ndfc_calculate_ecc(struct mtd_info *mtd,
+ const u_char *dat, u_char *ecc_code)
+{
+ struct nand_chip *chip = mtd_to_nand(mtd);
+ struct ndfc_controller *ndfc = nand_get_controller_data(chip);
+ uint32_t ecc;
+ uint8_t *p = (uint8_t *)&ecc;
+
+ wmb();
+ ecc = in_be32(ndfc->ndfcbase + NDFC_ECC);
+ /* The NDFC uses Smart Media (SMC) bytes order */
+ ecc_code[0] = p[1];
+ ecc_code[1] = p[2];
+ ecc_code[2] = p[3];
+
+ return 0;
+}
+
+/*
+ * Speedups for buffer read/write/verify
+ *
+ * NDFC allows 32bit read/write of data. So we can speed up the buffer
+ * functions. No further checking, as nand_base will always read/write
+ * page aligned.
+ */
+static void ndfc_read_buf(struct mtd_info *mtd, uint8_t *buf, int len)
+{
+ struct nand_chip *chip = mtd_to_nand(mtd);
+ struct ndfc_controller *ndfc = nand_get_controller_data(chip);
+ uint32_t *p = (uint32_t *) buf;
+
+ for(;len > 0; len -= 4)
+ *p++ = in_be32(ndfc->ndfcbase + NDFC_DATA);
+}
+
+static void ndfc_write_buf(struct mtd_info *mtd, const uint8_t *buf, int len)
+{
+ struct nand_chip *chip = mtd_to_nand(mtd);
+ struct ndfc_controller *ndfc = nand_get_controller_data(chip);
+ uint32_t *p = (uint32_t *) buf;
+
+ for(;len > 0; len -= 4)
+ out_be32(ndfc->ndfcbase + NDFC_DATA, *p++);
+}
+
+/*
+ * Initialize chip structure
+ */
+static int ndfc_chip_init(struct ndfc_controller *ndfc,
+ struct device_node *node)
+{
+ struct device_node *flash_np;
+ struct nand_chip *chip = &ndfc->chip;
+ struct mtd_info *mtd = nand_to_mtd(chip);
+ int ret;
+
+ chip->IO_ADDR_R = ndfc->ndfcbase + NDFC_DATA;
+ chip->IO_ADDR_W = ndfc->ndfcbase + NDFC_DATA;
+ chip->cmd_ctrl = ndfc_hwcontrol;
+ chip->dev_ready = ndfc_ready;
+ chip->select_chip = ndfc_select_chip;
+ chip->chip_delay = 50;
+ chip->controller = &ndfc->ndfc_control;
+ chip->read_buf = ndfc_read_buf;
+ chip->write_buf = ndfc_write_buf;
+ chip->ecc.correct = nand_correct_data;
+ chip->ecc.hwctl = ndfc_enable_hwecc;
+ chip->ecc.calculate = ndfc_calculate_ecc;
+ chip->ecc.mode = NAND_ECC_HW;
+ chip->ecc.size = 256;
+ chip->ecc.bytes = 3;
+ chip->ecc.strength = 1;
+ nand_set_controller_data(chip, ndfc);
+
+ mtd->dev.parent = &ndfc->ofdev->dev;
+
+ flash_np = of_get_next_child(node, NULL);
+ if (!flash_np)
+ return -ENODEV;
+ nand_set_flash_node(chip, flash_np);
+
+ mtd->name = kasprintf(GFP_KERNEL, "%s.%s", dev_name(&ndfc->ofdev->dev),
+ flash_np->name);
+ if (!mtd->name) {
+ ret = -ENOMEM;
+ goto err;
+ }
+
+ ret = nand_scan(mtd, 1);
+ if (ret)
+ goto err;
+
+ ret = mtd_device_register(mtd, NULL, 0);
+
+err:
+ of_node_put(flash_np);
+ if (ret)
+ kfree(mtd->name);
+ return ret;
+}
+
+static int ndfc_probe(struct platform_device *ofdev)
+{
+ struct ndfc_controller *ndfc;
+ const __be32 *reg;
+ u32 ccr;
+ u32 cs;
+ int err, len;
+
+ /* Read the reg property to get the chip select */
+ reg = of_get_property(ofdev->dev.of_node, "reg", &len);
+ if (reg == NULL || len != 12) {
+ dev_err(&ofdev->dev, "unable read reg property (%d)\n", len);
+ return -ENOENT;
+ }
+
+ cs = be32_to_cpu(reg[0]);
+ if (cs >= NDFC_MAX_CS) {
+ dev_err(&ofdev->dev, "invalid CS number (%d)\n", cs);
+ return -EINVAL;
+ }
+
+ ndfc = &ndfc_ctrl[cs];
+ ndfc->chip_select = cs;
+
+ nand_hw_control_init(&ndfc->ndfc_control);
+ ndfc->ofdev = ofdev;
+ dev_set_drvdata(&ofdev->dev, ndfc);
+
+ ndfc->ndfcbase = of_iomap(ofdev->dev.of_node, 0);
+ if (!ndfc->ndfcbase) {
+ dev_err(&ofdev->dev, "failed to get memory\n");
+ return -EIO;
+ }
+
+ ccr = NDFC_CCR_BS(ndfc->chip_select);
+
+ /* It is ok if ccr does not exist - just default to 0 */
+ reg = of_get_property(ofdev->dev.of_node, "ccr", NULL);
+ if (reg)
+ ccr |= be32_to_cpup(reg);
+
+ out_be32(ndfc->ndfcbase + NDFC_CCR, ccr);
+
+ /* Set the bank settings if given */
+ reg = of_get_property(ofdev->dev.of_node, "bank-settings", NULL);
+ if (reg) {
+ int offset = NDFC_BCFG0 + (ndfc->chip_select << 2);
+ out_be32(ndfc->ndfcbase + offset, be32_to_cpup(reg));
+ }
+
+ err = ndfc_chip_init(ndfc, ofdev->dev.of_node);
+ if (err) {
+ iounmap(ndfc->ndfcbase);
+ return err;
+ }
+
+ return 0;
+}
+
+static int ndfc_remove(struct platform_device *ofdev)
+{
+ struct ndfc_controller *ndfc = dev_get_drvdata(&ofdev->dev);
+ struct mtd_info *mtd = nand_to_mtd(&ndfc->chip);
+
+ nand_release(mtd);
+ kfree(mtd->name);
+
+ return 0;
+}
+
+static const struct of_device_id ndfc_match[] = {
+ { .compatible = "ibm,ndfc", },
+ {}
+};
+MODULE_DEVICE_TABLE(of, ndfc_match);
+
+static struct platform_driver ndfc_driver = {
+ .driver = {
+ .name = "ndfc",
+ .of_match_table = ndfc_match,
+ },
+ .probe = ndfc_probe,
+ .remove = ndfc_remove,
+};
+
+module_platform_driver(ndfc_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Thomas Gleixner <tglx@linutronix.de>");
+MODULE_DESCRIPTION("OF Platform driver for NDFC");
OpenPOWER on IntegriCloud