[PATCH v2] mtd: map: new driver for NXP IFC
Matt Weber
matthew.weber at rockwellcollins.com
Tue Aug 29 12:47:27 PDT 2017
From: Sanjay Tandel <sanjay.tandel at rockwellcollins.com>
This patch adds map driver for parallel flash chips interfaced over
a NXP Integrated Flash Controller (IFC). This driver allows either
8-bit or 16-bit accesses, depending on bank-width, to parallel flash
chips(like Everspin MR0A16A), which are physically mapped to CPU's
memory space. For unaligned accesses, it performs read-modify-write
operations to keep access size same as bank-width.
Signed-off-by: Sanjay Tandel <sanjay.tandel at rockwellcollins.com>
Signed-off-by: Matt Weber <matthew.weber at rockwellcollins.com>
---
Changes
v1 -> v2
- Refactored driver to be custom for the IFC bus controllder
(Suggested by Boris)
- Updated patch name
- Cleaned up description to be specific about issue and behavior
- Version 1 - https://patchwork.ozlabs.org/patch/797787/
---
Documentation/devicetree/bindings/mtd/ifc-mram.txt | 34 ++
drivers/mtd/maps/Kconfig | 13 +
drivers/mtd/maps/Makefile | 1 +
drivers/mtd/maps/ifc_mram.c | 343 +++++++++++++++++++++
4 files changed, 391 insertions(+)
create mode 100644 Documentation/devicetree/bindings/mtd/ifc-mram.txt
create mode 100644 drivers/mtd/maps/ifc_mram.c
diff --git a/Documentation/devicetree/bindings/mtd/ifc-mram.txt b/Documentation/devicetree/bindings/mtd/ifc-mram.txt
new file mode 100644
index 0000000..c5c3210
--- /dev/null
+++ b/Documentation/devicetree/bindings/mtd/ifc-mram.txt
@@ -0,0 +1,34 @@
+Integrated Flash Controller based physically-mapped parallel MRAM,
+NOR flash, MTD-RAM (NVRAM...)
+
+ - compatible : should contain the specific model of mtd chip(s)
+ used, if known, followed by "ifc-mram".
+ - reg : Address range(s) of the mtd chip(s)
+ It's possible to (optionally) define multiple "reg" tuples so that
+ non-identical chips can be described in one node.
+ - bank-width : Width (in bytes) of the bank. Equal to the
+ device width times the number of interleaved chips.
+ - device-width : (optional) Width of a single mtd chip. If
+ omitted, assumed to be equal to 'bank-width'.
+ - #address-cells, #size-cells : Must be present if the device has
+ sub-nodes representing partitions (see below). In this case
+ both #address-cells and #size-cells must be equal to 1.
+ - linux,mtd-name: allow to specify the mtd name.
+
+The device tree may optionally contain sub-nodes describing partitions of the
+address space. See partition.txt for more detail.
+
+Example:
+
+ mram at 1,0 {
+ #address-cells = <1>;
+ #size-cells = <1>;
+ compatible = "everspin,mram", "ifc-mram";
+ reg = <0x1 0x0 0x10000 0x1 0x10000 0x10000>;
+ bank-width = <2>;
+
+ partition at 0 {
+ reg = <0 0x00020000>;
+ label = "MRAM Data 0";
+ };
+ };
diff --git a/drivers/mtd/maps/Kconfig b/drivers/mtd/maps/Kconfig
index 542fdf8..95bd44e 100644
--- a/drivers/mtd/maps/Kconfig
+++ b/drivers/mtd/maps/Kconfig
@@ -419,4 +419,17 @@ config MTD_LATCH_ADDR
If compiled as a module, it will be called latch-addr-flash.
+config MTD_IFC_MRAM
+ tristate "Map driver for Integrated Flash Controller"
+ depends on MTD_COMPLEX_MAPPINGS
+ help
+ Map driver for chips connected parallely to Integrated Flash
+ Controller. This driver allows either 8-bit or 16-bit accesses,
+ depending on bank-width, to parallel flash chips, which are
+ physically mapped to CPU's memory space. For unaligned accesses,
+ it does read-modify-write.
+ Example: Everspin MR0A16A.
+
+ If compiled as a module, it will be called ifc-mram.
+
endmenu
diff --git a/drivers/mtd/maps/Makefile b/drivers/mtd/maps/Makefile
index 5a09a72..0ab793e 100644
--- a/drivers/mtd/maps/Makefile
+++ b/drivers/mtd/maps/Makefile
@@ -47,3 +47,4 @@ obj-$(CONFIG_MTD_VMU) += vmu-flash.o
obj-$(CONFIG_MTD_GPIO_ADDR) += gpio-addr-flash.o
obj-$(CONFIG_MTD_LATCH_ADDR) += latch-addr-flash.o
obj-$(CONFIG_MTD_LANTIQ) += lantiq-flash.o
+obj-$(CONFIG_MTD_IFC_MRAM) += ifc_mram.o
diff --git a/drivers/mtd/maps/ifc_mram.c b/drivers/mtd/maps/ifc_mram.c
new file mode 100644
index 0000000..1341e0a
--- /dev/null
+++ b/drivers/mtd/maps/ifc_mram.c
@@ -0,0 +1,343 @@
+/*
+ * Integrated Flash Controlller Map Driver
+ *
+ * Copyright 2017 Rockwell Collins
+ *
+ * Aug 18 2017 Sanjay Tandel <sanjay.tandel at rockwellcollins.com>
+ *
+ * Based on:
+ * Flash mappings described by the OF (or flattened) device tree
+ *
+ * Copyright (C) 2006 MontaVista Software Inc.
+ * Author: Vitaly Wool <vwool at ru.mvista.com>
+ *
+ * Revised to handle newer style flash binding by:
+ * Copyright (C) 2007 David Gibson, IBM Corporation.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/device.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/map.h>
+#include <linux/mtd/partitions.h>
+#include <linux/mtd/concat.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_platform.h>
+#include <linux/slab.h>
+
+static const struct of_device_id ifc_mram_match[] = {
+ {
+ .compatible = "ifc-mram",
+ },
+ {}
+};
+MODULE_DEVICE_TABLE(of, ifc_mram_match);
+struct ifc_mram_list {
+ struct mtd_info *mtd;
+ struct map_info map;
+ struct resource *res;
+};
+
+struct ifc_mram {
+ struct mtd_info *cmtd;
+ int list_size; /* number of elements in ifc_mram_list */
+ struct ifc_mram_list list[0];
+};
+
+
+static void ifc_mram_copy_from8(void *to, void __iomem *from, size_t count)
+{
+ u8 *t = to;
+
+ while (count > 0) {
+ *t = (u8)readb_relaxed(from);
+ t++;
+ from++;
+ count--;
+ }
+}
+
+static void ifc_mram_copy_from16(void *to, void __iomem *from, size_t count)
+{
+ u8 *t = to;
+
+ if (!(IS_ALIGNED((unsigned long)from, 2))) {
+ from = (void __iomem *)ALIGN((unsigned long)from, 2) - 2;
+ *(u8 *)t = (u8)((cpu_to_le16(readw_relaxed(from)) & 0xff00)
+ >> 8);
+ count--;
+ t++;
+ from += 2;
+ }
+ while (count >= 2) {
+ *(u16 *)t = cpu_to_le16(readw_relaxed(from));
+ count -= 2;
+ t += 2;
+ from += 2;
+ };
+ while (count > 0) {
+ *(u8 *)t = (u8)(cpu_to_le16(readw_relaxed(from)) & 0x00ff);
+ count--;
+ t++;
+ from++;
+ }
+}
+
+static void ifc_mram_copy_from(struct map_info *map, void *to,
+ unsigned long from, ssize_t len)
+{
+ if (map->cached)
+ memcpy(to, (char *)map->cached + from, len);
+ else if (map_bankwidth_is_1(map))
+ ifc_mram_copy_from8(to, map->virt + from, len);
+ else if (map_bankwidth_is_2(map))
+ ifc_mram_copy_from16(to, map->virt + from, len);
+ else
+ memcpy_fromio(to, map->virt + from, len);
+}
+
+static void ifc_mram_copy_to8(void __iomem *to, const void *from, size_t count)
+{
+ const unsigned char *f = from;
+
+ while (count > 0) {
+ writeb_relaxed(*f, to);
+ count--;
+ to++;
+ f++;
+ }
+}
+
+static void ifc_mram_copy_to16(void __iomem *to, const void *from, size_t count)
+{
+ const unsigned char *f = from;
+ u16 d;
+
+ if (!(IS_ALIGNED((unsigned long)to, 2))) {
+ to = (void __iomem *)ALIGN((unsigned long)to, 2) - 2;
+ d = (cpu_to_le16(readw_relaxed(to)) & 0x00ff)
+ | ((u16)(*(const u8 *)f) << 8);
+ writew_relaxed(le16_to_cpu(d), to);
+ count--;
+ to += 2;
+ f++;
+ }
+ while (count >= 2) {
+ writew_relaxed(le16_to_cpu(*(const u16 *)f), to);
+ count -= 2;
+ to += 2;
+ f += 2;
+ };
+ while (count > 0) {
+ d = (cpu_to_le16(readw_relaxed(to)) & 0xff00)
+ | (u16)(*(const u8 *)f);
+ writew_relaxed(le16_to_cpu(d), to);
+ count--;
+ to++;
+ f++;
+ }
+}
+
+static void ifc_mram_copy_to(struct map_info *map, unsigned long to,
+ const void *from, ssize_t len)
+{
+ if (map_bankwidth_is_1(map))
+ ifc_mram_copy_to8(map->virt + to, from, len);
+ else if (map_bankwidth_is_2(map))
+ ifc_mram_copy_to16(map->virt + to, from, len);
+ else
+ memcpy_toio(map->virt + to, from, len);
+}
+static int ifc_mram_remove(struct platform_device *dev)
+{
+ struct ifc_mram *info;
+ int i;
+
+ info = dev_get_drvdata(&dev->dev);
+ if (!info)
+ return 0;
+ dev_set_drvdata(&dev->dev, NULL);
+
+ if (info->cmtd) {
+ mtd_device_unregister(info->cmtd);
+ if (info->cmtd != info->list[0].mtd)
+ mtd_concat_destroy(info->cmtd);
+ }
+
+ for (i = 0; i < info->list_size; i++) {
+ if (info->list[i].mtd)
+ map_destroy(info->list[i].mtd);
+
+ if (info->list[i].map.virt)
+ iounmap(info->list[i].map.virt);
+
+ if (info->list[i].res) {
+ release_resource(info->list[i].res);
+ kfree(info->list[i].res);
+ }
+ }
+ return 0;
+}
+
+static int ifc_mram_probe(struct platform_device *dev)
+{
+ const struct of_device_id *match;
+ struct device_node *dp = dev->dev.of_node;
+ struct resource res;
+ struct ifc_mram *info;
+ const __be32 *width;
+ int err;
+ int i;
+ int count;
+ const __be32 *p;
+ int reg_tuple_size;
+ struct mtd_info **mtd_list = NULL;
+ resource_size_t res_size;
+ struct mtd_part_parser_data ppdata;
+ bool map_indirect;
+ const char *mtd_name = NULL;
+
+ match = of_match_device(ifc_mram_match, &dev->dev);
+ if (!match) {
+ pr_info("%s: compatible string not matched\n", __func__);
+ return -EINVAL;
+ }
+ reg_tuple_size =
+ (of_n_addr_cells(dp) + of_n_size_cells(dp)) * sizeof(u32);
+
+ of_property_read_string(dp, "linux,mtd-name", &mtd_name);
+
+ p = of_get_property(dp, "reg", &count);
+ if (count % reg_tuple_size != 0) {
+ dev_err(&dev->dev, "Malformed reg property on %s\n",
+ dev->dev.of_node->full_name);
+ err = -EINVAL;
+ goto err_flash_remove;
+ }
+ count /= reg_tuple_size;
+
+ err = -ENOMEM;
+ info = devm_kzalloc(&dev->dev,
+ sizeof(struct ifc_mram) +
+ sizeof(struct ifc_mram_list) * count, GFP_KERNEL);
+ if (!info)
+ goto err_flash_remove;
+
+ dev_set_drvdata(&dev->dev, info);
+
+ mtd_list = kcalloc(count, sizeof(*mtd_list), GFP_KERNEL);
+ if (!mtd_list)
+ goto err_flash_remove;
+
+ for (i = 0; i < count; i++) {
+ err = -ENXIO;
+ if (of_address_to_resource(dp, i, &res)) {
+ /*
+ * Continue with next register tuple if this
+ * one is not mappable
+ */
+ continue;
+ }
+
+ dev_dbg(&dev->dev, "ifc_mram device: %pR\n", &res);
+
+ err = -EBUSY;
+ res_size = resource_size(&res);
+ info->list[i].res = request_mem_region(res.start, res_size,
+ dev_name(&dev->dev));
+ if (!info->list[i].res)
+ goto err_out;
+
+ err = -ENXIO;
+ width = of_get_property(dp, "bank-width", NULL);
+ if (!width) {
+ dev_err(&dev->dev,
+ "Can't get bank width from device tree\n");
+ goto err_out;
+ }
+
+ info->list[i].map.name = mtd_name ?: dev_name(&dev->dev);
+ info->list[i].map.phys = res.start;
+ info->list[i].map.size = res_size;
+ info->list[i].map.bankwidth = be32_to_cpup(width);
+ info->list[i].map.device_node = dp;
+
+ err = -ENOMEM;
+ info->list[i].map.virt = ioremap(info->list[i].map.phys,
+ info->list[i].map.size);
+ if (!info->list[i].map.virt) {
+ dev_err(&dev->dev,
+ "Failed to ioremap() flash region\n");
+ goto err_out;
+ }
+
+ simple_map_init(&info->list[i].map);
+#ifdef CONFIG_MTD_COMPLEX_MAPPINGS
+ info->list[i].map.copy_from = ifc_mram_copy_from;
+ info->list[i].map.copy_to = ifc_mram_copy_to;
+#endif
+
+
+ info->list[i].mtd = do_map_probe("map_ram",
+ &info->list[i].map);
+ mtd_list[i] = info->list[i].mtd;
+
+ err = -ENXIO;
+ if (!info->list[i].mtd) {
+ dev_err(&dev->dev, "do_map_probe() failed\n");
+ goto err_out;
+ } else {
+ info->list_size++;
+ }
+ info->list[i].mtd->owner = THIS_MODULE;
+ info->list[i].mtd->dev.parent = &dev->dev;
+ }
+
+ err = 0;
+ info->cmtd = NULL;
+ if (info->list_size == 1) {
+ info->cmtd = info->list[0].mtd;
+ } else if (info->list_size > 1) {
+ /*
+ * We detected multiple devices. Concatenate them together.
+ */
+ info->cmtd = mtd_concat_create(mtd_list, info->list_size,
+ dev_name(&dev->dev));
+ }
+ if (info->cmtd == NULL)
+ err = -ENXIO;
+
+ if (err)
+ goto err_out;
+
+ ppdata.of_node = dp;
+ mtd_device_parse_register(info->cmtd, NULL, &ppdata, NULL, 0);
+ kfree(mtd_list);
+ return 0;
+
+err_out:
+ kfree(mtd_list);
+err_flash_remove:
+ ifc_mram_remove(dev);
+
+ return err;
+}
+
+
+static struct platform_driver ifc_mram_driver = {
+ .driver = {
+ .name = "ifc-mram",
+ .of_match_table = ifc_mram_match,
+ },
+ .probe = ifc_mram_probe,
+ .remove = ifc_mram_remove,
+};
+
+module_platform_driver(ifc_mram_driver);
+
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Sanjay Tandel");
+MODULE_DESCRIPTION("IFC MRAM Map Driver");
--
1.9.1
More information about the linux-mtd
mailing list