[PATCH v2] mtd: map: new driver for NXP IFC
Boris Brezillon
boris.brezillon at free-electrons.com
Wed Aug 30 12:34:50 PDT 2017
Hi,
On Tue, 29 Aug 2017 14:47:27 -0500
Matt Weber <matthew.weber at rockwellcollins.com> wrote:
> 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.
>
Did you consider re-using the physmap driver [1] and adjust it to your
needs like the gemini [2] or versatile [3] drivers do? If you did, what
prevents you from using this approach?
Regards,
Boris
[1]http://elixir.free-electrons.com/linux/v4.13-rc7/source/drivers/mtd/maps/physmap_of_core.c
[2]http://elixir.free-electrons.com/linux/v4.13-rc7/source/drivers/mtd/maps/physmap_of_gemini.c
[3]http://elixir.free-electrons.com/linux/v4.13-rc7/source/drivers/mtd/maps/physmap_of_versatile.c
> 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");
More information about the linux-mtd
mailing list