summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTomasz Nowicki <tomasz.nowicki@linaro.org>2014-01-10 13:24:20 +0100
committerGraeme Gregory <graeme.gregory@linaro.org>2014-01-10 16:26:32 +0000
commitab0a120c58c1698d19a5e87956809593a2947d5e (patch)
treec58960fbdf4df2108020ee9cd4d8ec1bec0de749
parente2733886d01257c0ec473b5b47a5bc18e86ff612 (diff)
acpi, mtd: Add support for flash device in physical memory map based on ACPI descriptionacpi-legacy
This provides a 'mapping' driver which allows the NOR Flash and ROM driver code to communicate with chips which are mapped physically into the CPU's memory. The mapping description here is taken from DSDT ACPI table. This code was "inspired" by physmap_of.c Signed-off-by: Tomasz Nowicki <tomasz.nowicki@linaro.org> Acked-by: Graeme Gregory <graeme.gregory@linaro.org>
-rw-r--r--drivers/acpi/acpi_platform.c4
-rw-r--r--drivers/mtd/maps/Kconfig9
-rw-r--r--drivers/mtd/maps/Makefile1
-rw-r--r--drivers/mtd/maps/physmap_acpi.c372
4 files changed, 386 insertions, 0 deletions
diff --git a/drivers/acpi/acpi_platform.c b/drivers/acpi/acpi_platform.c
index 36467a682f8f..c8aff25188b3 100644
--- a/drivers/acpi/acpi_platform.c
+++ b/drivers/acpi/acpi_platform.c
@@ -40,6 +40,10 @@ static const struct acpi_device_id acpi_platform_device_ids[] = {
{ "LNRO000F" }, /* kmi0,kmi1 */
{ "LNRO0011" }, /* wdt */
{ "LNRO0013" }, /* rtc */
+ { "LNRO0016" }, /* flash jedec_probe */
+ { "LNRO0015" }, /* flash cfi_probe */
+ { "LNRO0017" }, /* flash map_ram */
+ { "LNRO0018" }, /* flash direct-mapped */
{ "LNRO0019" }, /* fixed regulator */
{ "AMBA0000" },
diff --git a/drivers/mtd/maps/Kconfig b/drivers/mtd/maps/Kconfig
index 310dc7c93425..07d26e00f140 100644
--- a/drivers/mtd/maps/Kconfig
+++ b/drivers/mtd/maps/Kconfig
@@ -74,6 +74,15 @@ config MTD_PHYSMAP_OF
physically into the CPU's memory. The mapping description here is
taken from OF device tree.
+config MTD_PHYSMAP_ACPI
+ tristate "Flash device in physical memory map based on ACPI description"
+ depends on ACPI && (MTD_CFI || MTD_JEDECPROBE || MTD_ROM)
+ help
+ This provides a 'mapping' driver which allows the NOR Flash and
+ ROM driver code to communicate with chips which are mapped
+ physically into the CPU's memory. The mapping description here is
+ taken from DSDT ACPI table.
+
config MTD_PMC_MSP_EVM
tristate "CFI Flash device mapped on PMC-Sierra MSP"
depends on PMC_MSP && MTD_CFI
diff --git a/drivers/mtd/maps/Makefile b/drivers/mtd/maps/Makefile
index 141c91a5b24c..379cfff569c5 100644
--- a/drivers/mtd/maps/Makefile
+++ b/drivers/mtd/maps/Makefile
@@ -18,6 +18,7 @@ obj-$(CONFIG_MTD_TSUNAMI) += tsunami_flash.o
obj-$(CONFIG_MTD_PXA2XX) += pxa2xx-flash.o
obj-$(CONFIG_MTD_PHYSMAP) += physmap.o
obj-$(CONFIG_MTD_PHYSMAP_OF) += physmap_of.o
+obj-$(CONFIG_MTD_PHYSMAP_ACPI) += physmap_acpi.o
obj-$(CONFIG_MTD_PISMO) += pismo.o
obj-$(CONFIG_MTD_PMC_MSP_EVM) += pmcmsp-flash.o
obj-$(CONFIG_MTD_PCMCIA) += pcmciamtd.o
diff --git a/drivers/mtd/maps/physmap_acpi.c b/drivers/mtd/maps/physmap_acpi.c
new file mode 100644
index 000000000000..f425dfd27a7a
--- /dev/null
+++ b/drivers/mtd/maps/physmap_acpi.c
@@ -0,0 +1,372 @@
+/*
+ * Flash mappings described by the ACPI
+ *
+ * Copyright (C) 2006 MontaVista Software Inc.
+ * Author: Vitaly Wool <vwool@ru.mvista.com>
+ * Copyright (C) 2007 David Gibson, IBM Corporation.
+ *
+ * Revised to handle ACPI style flash binding by:
+ * Copyright (C) 2013 Tomasz Nowicki <tomasz.nowicki@linaro.org>
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/init.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/acpi.h>
+#include <linux/slab.h>
+#include <linux/platform_device.h>
+
+struct acpi_flash_list {
+ struct mtd_info *mtd;
+ struct map_info map;
+ struct resource *res;
+};
+
+struct acpi_flash {
+ struct mtd_info *cmtd;
+ int list_size; /* number of elements in acpi_flash_list */
+ struct acpi_flash_list list[0];
+};
+
+static const char * const rom_probe_types[] = {
+ "cfi_probe", "jedec_probe", "map_rom" };
+
+/* Helper function to handle probing of the obsolete "direct-mapped"
+ * compatible binding, which has an extra "probe-type" property
+ * describing the type of flash probe necessary. */
+static struct mtd_info *obsolete_probe(struct platform_device *dev,
+ struct map_info *map)
+{
+ struct acpi_dsm_entry entry;
+ struct mtd_info *mtd;
+ acpi_handle handler;
+ char *acpi_probe;
+ int i, err, len;
+
+ handler = ACPI_HANDLE(&dev->dev);
+ dev_warn(&dev->dev, "ACPI uses obsolete \"direct-mapped\" flash "
+ "binding\n");
+
+ err = acpi_dsm_lookup_value(handler, "probe-type", 0, &entry);
+ if (err || entry.value == NULL) {
+ for (i = 0; i < ARRAY_SIZE(rom_probe_types); i++) {
+ mtd = do_map_probe(rom_probe_types[i], map);
+ if (mtd)
+ return mtd;
+ }
+ return NULL;
+ }
+
+ len = strlen(entry.value) + 1;
+ acpi_probe = devm_kzalloc(&dev->dev, len, GFP_KERNEL);
+ strncpy(acpi_probe, entry.value, len);
+ kfree(entry.key);
+ kfree(entry.value);
+
+ if (strcmp(acpi_probe, "CFI") == 0) {
+ return do_map_probe("cfi_probe", map);
+ } else if (strcmp(acpi_probe, "JEDEC") == 0) {
+ return do_map_probe("jedec_probe", map);
+ } else {
+ if (strcmp(acpi_probe, "ROM") != 0)
+ dev_warn(&dev->dev, "obsolete_probe: don't know probe "
+ "type '%s', mapping as rom\n", acpi_probe);
+ return do_map_probe("mtd_rom", map);
+ }
+}
+
+/* When partitions are set we look for a linux,part-probe property which
+ specifies the list of partition probers to use. If none is given then the
+ default is use. These take precedence over other device tree
+ information. */
+static const char * const part_probe_types_def[] = {
+ "cmdlinepart", "RedBoot", NULL };
+
+static const char * const *acpi_get_part_probes(struct device *dev)
+{
+ struct acpi_dsm_entry entry;
+ const char **res;
+ int cplen, err, len;
+ unsigned int count = 0, i;
+ char *cp = NULL;
+
+ acpi_handle handler = ACPI_HANDLE(dev);
+
+ /* Get space separated strings */
+ err = acpi_dsm_lookup_value(handler, "linux,part-probe", 0, &entry);
+ if (err || entry.value == NULL)
+ return part_probe_types_def;
+
+ len = strlen(entry.value) + 1;
+ cp = devm_kzalloc(dev, len, GFP_KERNEL);
+ strncpy(cp, entry.value, len);
+ kfree(entry.key);
+ kfree(entry.value);
+
+ cplen = strlen(cp);
+ for (i = 0; i != cplen; i++)
+ if (cp[i] == ' ')
+ count++;
+
+ /* Create the table with references to strings */
+ res = kzalloc((count + 1) * sizeof(char *), GFP_KERNEL);
+ for (i = 0; i < count + 1; i++) {
+ res[i] = cp;
+ cp = strnchr(cp, cplen, ' ');
+ if (cp == NULL)
+ break;
+
+ *cp++ = '\0';
+ cplen = strlen(cp);
+ }
+ return res;
+}
+
+static void acpi_free_probes(const char * const *probes)
+{
+ if (probes != part_probe_types_def)
+ kfree(probes);
+}
+
+static const struct acpi_device_id acpi_flash_match[];
+static int acpi_flash_remove(struct platform_device *dev);
+
+static int acpi_flash_probe(struct platform_device *dev)
+{
+ const char * const *part_probe_types;
+ const struct acpi_device_id *id;
+ resource_size_t res_size;
+ struct acpi_flash *info;
+ const char *probe_type;
+ struct resource *res;
+ acpi_handle handler;
+ int err = 0, i;
+ int bank_width = 0, map_indirect = 0;
+ struct mtd_info **mtd_list = NULL;
+ char *mtd_name = NULL;
+ int len, count = 0;
+ struct acpi_dsm_entry entry;
+
+ handler = ACPI_HANDLE(&dev->dev);
+
+ id = acpi_match_device(acpi_flash_match, &dev->dev);
+ if (!id)
+ return -ENODEV;
+
+ probe_type = (const char *)id->driver_data;
+
+ err = acpi_dsm_lookup_value(handler, "linux,mtd-name", 0, &entry);
+ if (err == 0 && entry.value) {
+ len = strlen(entry.value) + 1;
+ mtd_name = devm_kzalloc(&dev->dev, len, GFP_KERNEL);
+ strncpy(mtd_name, entry.value, len);
+ kfree(entry.key);
+ kfree(entry.value);
+ }
+
+ err = acpi_dsm_lookup_value(handler, "no-unaligned-direct-access",
+ 0, &entry);
+ if (err == 0 && kstrtoint(entry.value, 0, &map_indirect) == 0) {
+ kfree(entry.key);
+ kfree(entry.value);
+ }
+
+ while (platform_get_resource(dev, IORESOURCE_MEM, count))
+ count++;
+
+ if (!count) {
+ dev_err(&dev->dev, "No resources found for %s device\n",
+ dev_name(&dev->dev));
+ err = -ENXIO;
+ goto err_flash_remove;
+ }
+
+ info = devm_kzalloc(&dev->dev,
+ sizeof(struct acpi_flash) +
+ sizeof(struct acpi_flash_list) * count, GFP_KERNEL);
+ if (!info) {
+ err = -ENOMEM;
+ goto err_flash_remove;
+ }
+
+ dev_set_drvdata(&dev->dev, info);
+
+ mtd_list = kzalloc(sizeof(*mtd_list) * count, GFP_KERNEL);
+ if (!mtd_list)
+ goto err_flash_remove;
+
+ for (i = 0; i < count; i++) {
+ res = platform_get_resource(dev, IORESOURCE_MEM, i);
+ dev_dbg(&dev->dev, "resource[%d]: address 0x%lx size 0x%lx\n",
+ i, (long)res->start, (long)resource_size(res));
+
+ 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) {
+ err = -EBUSY;
+ goto err_out;
+ }
+
+ /* Mandatory property */
+ err = acpi_dsm_lookup_value(handler, "bank-width", 0, &entry);
+ if (err || kstrtoint(entry.value, 0, &bank_width) != 0) {
+ dev_err(&dev->dev,
+ "Can't get bank width from DSDT\n");
+ goto err_out;
+ }
+ kfree(entry.key);
+ kfree(entry.value);
+
+ 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 = bank_width;
+ 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");
+ err = -ENOMEM;
+ goto err_out;
+ }
+
+ simple_map_init(&info->list[i].map);
+
+ /*
+ * On some platforms (e.g. MPC5200) a direct 1:1 mapping
+ * may cause problems with JFFS2 usage, as the local bus (LPB)
+ * doesn't support unaligned accesses as implemented in the
+ * JFFS2 code via memcpy(). By setting NO_XIP, the
+ * flash will not be exposed directly to the MTD users
+ * (e.g. JFFS2) any more.
+ */
+ if (map_indirect)
+ info->list[i].map.phys = NO_XIP;
+
+ if (probe_type) {
+ info->list[i].mtd = do_map_probe(probe_type,
+ &info->list[i].map);
+ } else {
+ info->list[i].mtd = obsolete_probe(dev,
+ &info->list[i].map);
+ }
+
+ if (!info->list[i].mtd) {
+ dev_err(&dev->dev, "do_map_probe() failed\n");
+ err = -ENXIO;
+ goto err_out;
+ } else
+ info->list_size++;
+
+ info->list[i].mtd->owner = THIS_MODULE;
+ info->list[i].mtd->dev.parent = &dev->dev;
+ mtd_list[i] = info->list[i].mtd;
+ }
+
+ 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;
+ goto err_out;
+ }
+
+ part_probe_types = acpi_get_part_probes(&dev->dev);
+ mtd_device_parse_register(info->cmtd, part_probe_types, NULL,
+ NULL, 0);
+ acpi_free_probes(part_probe_types);
+
+ kfree(mtd_list);
+
+ return 0;
+
+err_out:
+ kfree(mtd_list);
+err_flash_remove:
+ acpi_flash_remove(dev);
+
+ return err;
+}
+
+static int acpi_flash_remove(struct platform_device *dev)
+{
+ struct acpi_flash *info;
+ int i;
+
+ info = dev_get_drvdata(&dev->dev);
+ if (!info)
+ return 0;
+ dev_set_drvdata(&dev->dev, NULL);
+
+ if (info->cmtd != info->list[0].mtd) {
+ mtd_device_unregister(info->cmtd);
+ mtd_concat_destroy(info->cmtd);
+ }
+
+ if (info->cmtd)
+ mtd_device_unregister(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 const struct acpi_device_id acpi_flash_match[] = {
+ { "LNRO0015", (unsigned long)"cfi_probe"},
+ { "LNRO0016", (unsigned long)"jedec_probe"},
+ { "LNRO0017", (unsigned long)"map_ram"},
+ { "LNRO0018", (unsigned long)"direct-mapped"},
+ {},
+};
+MODULE_DEVICE_TABLE(acpi, acpi_flash_match);
+
+static struct platform_driver acpi_flash_driver = {
+ .driver = {
+ .name = "acpi-flash",
+ .owner = THIS_MODULE,
+ .acpi_match_table = ACPI_PTR(acpi_flash_match),
+ },
+ .probe = acpi_flash_probe,
+ .remove = acpi_flash_remove,
+};
+
+module_platform_driver(acpi_flash_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Tomasz Nowicki <tomasz.nowicki@linaro.org>");
+MODULE_DESCRIPTION("ACPI based MTD map driver");