From patchwork Tue Aug 29 19:47:27 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Matt Weber X-Patchwork-Id: 807250 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=none (mailfrom) smtp.mailfrom=lists.infradead.org (client-ip=65.50.211.133; helo=bombadil.infradead.org; envelope-from=linux-mtd-bounces+incoming=patchwork.ozlabs.org@lists.infradead.org; receiver=) Authentication-Results: ozlabs.org; dkim=pass (2048-bit key; unprotected) header.d=lists.infradead.org header.i=@lists.infradead.org header.b="Z37mNPRY"; dkim-atps=neutral Received: from bombadil.infradead.org (bombadil.infradead.org [65.50.211.133]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 3xhfLy0379z9sNc for ; Wed, 30 Aug 2017 05:48:10 +1000 (AEST) DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=lists.infradead.org; s=bombadil.20170209; h=Sender: Content-Transfer-Encoding:Content-Type:MIME-Version:Cc:List-Subscribe: List-Help:List-Post:List-Archive:List-Unsubscribe:List-Id:Message-Id:Date: Subject:To:From:Reply-To:Content-ID:Content-Description:Resent-Date: Resent-From:Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID:In-Reply-To: References:List-Owner; bh=bYAa31T9VMd7RNgODsu2MCx5dOW5W1NXPeYaWhowU6Q=; b=Z37 mNPRY8rJwhVpS7b6l5itAvCAJ6wjb/rU3GFE1F0QjkNv9LBNPgPu7lGQ1fLhCcja5osNxnqPw5c+v NQvxCjaIDfgyRqc20Ip9+AjiLVHqvw9NLhHVu/jQ4Po9UwZHwib53Zru05wBXRBMnrt6q/6WosxCu JCQJG2hpfp1Z3qdK6sh2Dh1y/gDyIOW0ArTQ7zAOTu1I/xiX8KOqN+QKgoXD0u1ZJE/gpKqnEmva7 f4Tvsc1R7T3Zx1m5UjuOYV6s2orax3wU7OzZ9f8rGNqOFtuOlcYnvSgZvfnz5+lh8jLNFvLloQGgv Sf1pzcXVizd9iMTwa/Yemdo/XRazaxQ==; Received: from localhost ([127.0.0.1] helo=bombadil.infradead.org) by bombadil.infradead.org with esmtp (Exim 4.87 #1 (Red Hat Linux)) id 1dmmUN-0004Ae-69; Tue, 29 Aug 2017 19:47:55 +0000 Received: from ch3vs04.rockwellcollins.com ([205.175.226.52]) by bombadil.infradead.org with esmtps (Exim 4.87 #1 (Red Hat Linux)) id 1dmmUI-00049P-DX for linux-mtd@lists.infradead.org; Tue, 29 Aug 2017 19:47:53 +0000 Received: from ofwch3n02.rockwellcollins.com (HELO crulimr01.rockwellcollins.com) ([205.175.226.14]) by ch3vs04.rockwellcollins.com with ESMTP; 29 Aug 2017 14:47:29 -0500 X-Received: from largo.rockwellcollins.com (unknown [192.168.140.76]) by crulimr01.rockwellcollins.com (Postfix) with ESMTP id DB9766032C; Tue, 29 Aug 2017 14:47:28 -0500 (CDT) From: Matt Weber To: linux-mtd@lists.infradead.org Subject: [PATCH v2] mtd: map: new driver for NXP IFC Date: Tue, 29 Aug 2017 14:47:27 -0500 Message-Id: <1504036047-38848-1-git-send-email-matthew.weber@rockwellcollins.com> X-Mailer: git-send-email 1.9.1 X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20170829_124750_582057_CDC6E86E X-CRM114-Status: GOOD ( 23.39 ) X-Spam-Score: -1.9 (-) X-Spam-Report: SpamAssassin version 3.4.1 on bombadil.infradead.org summary: Content analysis details: (-1.9 points) pts rule name description ---- ---------------------- -------------------------------------------------- -0.0 SPF_PASS SPF: sender matches SPF record -0.0 RP_MATCHES_RCVD Envelope sender domain matches handover relay domain -1.9 BAYES_00 BODY: Bayes spam probability is 0 to 1% [score: 0.0000] X-BeenThere: linux-mtd@lists.infradead.org X-Mailman-Version: 2.1.21 Precedence: list List-Id: Linux MTD discussion mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: Matt Weber , boris.brezillon@free-electrons.com, Sanjay Tandel , arnd@arndb.de MIME-Version: 1.0 Sender: "linux-mtd" Errors-To: linux-mtd-bounces+incoming=patchwork.ozlabs.org@lists.infradead.org From: Sanjay Tandel 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 Signed-off-by: Matt Weber --- 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@1,0 { + #address-cells = <1>; + #size-cells = <1>; + compatible = "everspin,mram", "ifc-mram"; + reg = <0x1 0x0 0x10000 0x1 0x10000 0x10000>; + bank-width = <2>; + + partition@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 + * + * Based on: + * Flash mappings described by the OF (or flattened) device tree + * + * Copyright (C) 2006 MontaVista Software Inc. + * Author: Vitaly Wool + * + * Revised to handle newer style flash binding by: + * Copyright (C) 2007 David Gibson, IBM Corporation. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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");