162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Normal mappings of chips in physical memory
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright (C) 2003 MontaVista Software Inc.
662306a36Sopenharmony_ci * Author: Jun Sun, jsun@mvista.com or jsun@junsun.net
762306a36Sopenharmony_ci *
862306a36Sopenharmony_ci * 031022 - [jsun] add run-time configure and partition setup
962306a36Sopenharmony_ci *
1062306a36Sopenharmony_ci * Device tree support:
1162306a36Sopenharmony_ci *    Copyright (C) 2006 MontaVista Software Inc.
1262306a36Sopenharmony_ci *    Author: Vitaly Wool <vwool@ru.mvista.com>
1362306a36Sopenharmony_ci *
1462306a36Sopenharmony_ci *    Revised to handle newer style flash binding by:
1562306a36Sopenharmony_ci *    Copyright (C) 2007 David Gibson, IBM Corporation.
1662306a36Sopenharmony_ci *
1762306a36Sopenharmony_ci * GPIO address extension:
1862306a36Sopenharmony_ci *    Handle the case where a flash device is mostly addressed using physical
1962306a36Sopenharmony_ci *    line and supplemented by GPIOs.  This way you can hook up say a 8MiB flash
2062306a36Sopenharmony_ci *    to a 2MiB memory range and use the GPIOs to select a particular range.
2162306a36Sopenharmony_ci *
2262306a36Sopenharmony_ci *    Copyright © 2000 Nicolas Pitre <nico@cam.org>
2362306a36Sopenharmony_ci *    Copyright © 2005-2009 Analog Devices Inc.
2462306a36Sopenharmony_ci */
2562306a36Sopenharmony_ci
2662306a36Sopenharmony_ci#include <linux/module.h>
2762306a36Sopenharmony_ci#include <linux/types.h>
2862306a36Sopenharmony_ci#include <linux/kernel.h>
2962306a36Sopenharmony_ci#include <linux/init.h>
3062306a36Sopenharmony_ci#include <linux/slab.h>
3162306a36Sopenharmony_ci#include <linux/device.h>
3262306a36Sopenharmony_ci#include <linux/platform_device.h>
3362306a36Sopenharmony_ci#include <linux/mtd/mtd.h>
3462306a36Sopenharmony_ci#include <linux/mtd/map.h>
3562306a36Sopenharmony_ci#include <linux/mtd/partitions.h>
3662306a36Sopenharmony_ci#include <linux/mtd/physmap.h>
3762306a36Sopenharmony_ci#include <linux/mtd/concat.h>
3862306a36Sopenharmony_ci#include <linux/mtd/cfi_endian.h>
3962306a36Sopenharmony_ci#include <linux/io.h>
4062306a36Sopenharmony_ci#include <linux/of_device.h>
4162306a36Sopenharmony_ci#include <linux/pm_runtime.h>
4262306a36Sopenharmony_ci#include <linux/gpio/consumer.h>
4362306a36Sopenharmony_ci
4462306a36Sopenharmony_ci#include "physmap-bt1-rom.h"
4562306a36Sopenharmony_ci#include "physmap-gemini.h"
4662306a36Sopenharmony_ci#include "physmap-ixp4xx.h"
4762306a36Sopenharmony_ci#include "physmap-versatile.h"
4862306a36Sopenharmony_ci
4962306a36Sopenharmony_cistruct physmap_flash_info {
5062306a36Sopenharmony_ci	unsigned int		nmaps;
5162306a36Sopenharmony_ci	struct mtd_info		**mtds;
5262306a36Sopenharmony_ci	struct mtd_info		*cmtd;
5362306a36Sopenharmony_ci	struct map_info		*maps;
5462306a36Sopenharmony_ci	spinlock_t		vpp_lock;
5562306a36Sopenharmony_ci	int			vpp_refcnt;
5662306a36Sopenharmony_ci	const char		*probe_type;
5762306a36Sopenharmony_ci	const char * const	*part_types;
5862306a36Sopenharmony_ci	unsigned int		nparts;
5962306a36Sopenharmony_ci	const struct mtd_partition *parts;
6062306a36Sopenharmony_ci	struct gpio_descs	*gpios;
6162306a36Sopenharmony_ci	unsigned int		gpio_values;
6262306a36Sopenharmony_ci	unsigned int		win_order;
6362306a36Sopenharmony_ci};
6462306a36Sopenharmony_ci
6562306a36Sopenharmony_cistatic int physmap_flash_remove(struct platform_device *dev)
6662306a36Sopenharmony_ci{
6762306a36Sopenharmony_ci	struct physmap_flash_info *info;
6862306a36Sopenharmony_ci	struct physmap_flash_data *physmap_data;
6962306a36Sopenharmony_ci	int i;
7062306a36Sopenharmony_ci
7162306a36Sopenharmony_ci	info = platform_get_drvdata(dev);
7262306a36Sopenharmony_ci
7362306a36Sopenharmony_ci	if (info->cmtd) {
7462306a36Sopenharmony_ci		WARN_ON(mtd_device_unregister(info->cmtd));
7562306a36Sopenharmony_ci
7662306a36Sopenharmony_ci		if (info->cmtd != info->mtds[0])
7762306a36Sopenharmony_ci			mtd_concat_destroy(info->cmtd);
7862306a36Sopenharmony_ci	}
7962306a36Sopenharmony_ci
8062306a36Sopenharmony_ci	for (i = 0; i < info->nmaps; i++) {
8162306a36Sopenharmony_ci		if (info->mtds[i])
8262306a36Sopenharmony_ci			map_destroy(info->mtds[i]);
8362306a36Sopenharmony_ci	}
8462306a36Sopenharmony_ci
8562306a36Sopenharmony_ci	physmap_data = dev_get_platdata(&dev->dev);
8662306a36Sopenharmony_ci	if (physmap_data && physmap_data->exit)
8762306a36Sopenharmony_ci		physmap_data->exit(dev);
8862306a36Sopenharmony_ci
8962306a36Sopenharmony_ci	pm_runtime_put(&dev->dev);
9062306a36Sopenharmony_ci	pm_runtime_disable(&dev->dev);
9162306a36Sopenharmony_ci	return 0;
9262306a36Sopenharmony_ci}
9362306a36Sopenharmony_ci
9462306a36Sopenharmony_cistatic void physmap_set_vpp(struct map_info *map, int state)
9562306a36Sopenharmony_ci{
9662306a36Sopenharmony_ci	struct platform_device *pdev;
9762306a36Sopenharmony_ci	struct physmap_flash_data *physmap_data;
9862306a36Sopenharmony_ci	struct physmap_flash_info *info;
9962306a36Sopenharmony_ci	unsigned long flags;
10062306a36Sopenharmony_ci
10162306a36Sopenharmony_ci	pdev = (struct platform_device *)map->map_priv_1;
10262306a36Sopenharmony_ci	physmap_data = dev_get_platdata(&pdev->dev);
10362306a36Sopenharmony_ci
10462306a36Sopenharmony_ci	if (!physmap_data->set_vpp)
10562306a36Sopenharmony_ci		return;
10662306a36Sopenharmony_ci
10762306a36Sopenharmony_ci	info = platform_get_drvdata(pdev);
10862306a36Sopenharmony_ci
10962306a36Sopenharmony_ci	spin_lock_irqsave(&info->vpp_lock, flags);
11062306a36Sopenharmony_ci	if (state) {
11162306a36Sopenharmony_ci		if (++info->vpp_refcnt == 1)    /* first nested 'on' */
11262306a36Sopenharmony_ci			physmap_data->set_vpp(pdev, 1);
11362306a36Sopenharmony_ci	} else {
11462306a36Sopenharmony_ci		if (--info->vpp_refcnt == 0)    /* last nested 'off' */
11562306a36Sopenharmony_ci			physmap_data->set_vpp(pdev, 0);
11662306a36Sopenharmony_ci	}
11762306a36Sopenharmony_ci	spin_unlock_irqrestore(&info->vpp_lock, flags);
11862306a36Sopenharmony_ci}
11962306a36Sopenharmony_ci
12062306a36Sopenharmony_ci#if IS_ENABLED(CONFIG_MTD_PHYSMAP_GPIO_ADDR)
12162306a36Sopenharmony_cistatic void physmap_set_addr_gpios(struct physmap_flash_info *info,
12262306a36Sopenharmony_ci				   unsigned long ofs)
12362306a36Sopenharmony_ci{
12462306a36Sopenharmony_ci	unsigned int i;
12562306a36Sopenharmony_ci
12662306a36Sopenharmony_ci	ofs >>= info->win_order;
12762306a36Sopenharmony_ci	if (info->gpio_values == ofs)
12862306a36Sopenharmony_ci		return;
12962306a36Sopenharmony_ci
13062306a36Sopenharmony_ci	for (i = 0; i < info->gpios->ndescs; i++) {
13162306a36Sopenharmony_ci		if ((BIT(i) & ofs) == (BIT(i) & info->gpio_values))
13262306a36Sopenharmony_ci			continue;
13362306a36Sopenharmony_ci
13462306a36Sopenharmony_ci		gpiod_set_value(info->gpios->desc[i], !!(BIT(i) & ofs));
13562306a36Sopenharmony_ci	}
13662306a36Sopenharmony_ci
13762306a36Sopenharmony_ci	info->gpio_values = ofs;
13862306a36Sopenharmony_ci}
13962306a36Sopenharmony_ci
14062306a36Sopenharmony_ci#define win_mask(order)		(BIT(order) - 1)
14162306a36Sopenharmony_ci
14262306a36Sopenharmony_cistatic map_word physmap_addr_gpios_read(struct map_info *map,
14362306a36Sopenharmony_ci					unsigned long ofs)
14462306a36Sopenharmony_ci{
14562306a36Sopenharmony_ci	struct platform_device *pdev;
14662306a36Sopenharmony_ci	struct physmap_flash_info *info;
14762306a36Sopenharmony_ci	map_word mw;
14862306a36Sopenharmony_ci	u16 word;
14962306a36Sopenharmony_ci
15062306a36Sopenharmony_ci	pdev = (struct platform_device *)map->map_priv_1;
15162306a36Sopenharmony_ci	info = platform_get_drvdata(pdev);
15262306a36Sopenharmony_ci	physmap_set_addr_gpios(info, ofs);
15362306a36Sopenharmony_ci
15462306a36Sopenharmony_ci	word = readw(map->virt + (ofs & win_mask(info->win_order)));
15562306a36Sopenharmony_ci	mw.x[0] = word;
15662306a36Sopenharmony_ci	return mw;
15762306a36Sopenharmony_ci}
15862306a36Sopenharmony_ci
15962306a36Sopenharmony_cistatic void physmap_addr_gpios_copy_from(struct map_info *map, void *buf,
16062306a36Sopenharmony_ci					 unsigned long ofs, ssize_t len)
16162306a36Sopenharmony_ci{
16262306a36Sopenharmony_ci	struct platform_device *pdev;
16362306a36Sopenharmony_ci	struct physmap_flash_info *info;
16462306a36Sopenharmony_ci
16562306a36Sopenharmony_ci	pdev = (struct platform_device *)map->map_priv_1;
16662306a36Sopenharmony_ci	info = platform_get_drvdata(pdev);
16762306a36Sopenharmony_ci
16862306a36Sopenharmony_ci	while (len) {
16962306a36Sopenharmony_ci		unsigned int winofs = ofs & win_mask(info->win_order);
17062306a36Sopenharmony_ci		unsigned int chunklen = min_t(unsigned int, len,
17162306a36Sopenharmony_ci					      BIT(info->win_order) - winofs);
17262306a36Sopenharmony_ci
17362306a36Sopenharmony_ci		physmap_set_addr_gpios(info, ofs);
17462306a36Sopenharmony_ci		memcpy_fromio(buf, map->virt + winofs, chunklen);
17562306a36Sopenharmony_ci		len -= chunklen;
17662306a36Sopenharmony_ci		buf += chunklen;
17762306a36Sopenharmony_ci		ofs += chunklen;
17862306a36Sopenharmony_ci	}
17962306a36Sopenharmony_ci}
18062306a36Sopenharmony_ci
18162306a36Sopenharmony_cistatic void physmap_addr_gpios_write(struct map_info *map, map_word mw,
18262306a36Sopenharmony_ci				     unsigned long ofs)
18362306a36Sopenharmony_ci{
18462306a36Sopenharmony_ci	struct platform_device *pdev;
18562306a36Sopenharmony_ci	struct physmap_flash_info *info;
18662306a36Sopenharmony_ci	u16 word;
18762306a36Sopenharmony_ci
18862306a36Sopenharmony_ci	pdev = (struct platform_device *)map->map_priv_1;
18962306a36Sopenharmony_ci	info = platform_get_drvdata(pdev);
19062306a36Sopenharmony_ci	physmap_set_addr_gpios(info, ofs);
19162306a36Sopenharmony_ci
19262306a36Sopenharmony_ci	word = mw.x[0];
19362306a36Sopenharmony_ci	writew(word, map->virt + (ofs & win_mask(info->win_order)));
19462306a36Sopenharmony_ci}
19562306a36Sopenharmony_ci
19662306a36Sopenharmony_cistatic void physmap_addr_gpios_copy_to(struct map_info *map, unsigned long ofs,
19762306a36Sopenharmony_ci				       const void *buf, ssize_t len)
19862306a36Sopenharmony_ci{
19962306a36Sopenharmony_ci	struct platform_device *pdev;
20062306a36Sopenharmony_ci	struct physmap_flash_info *info;
20162306a36Sopenharmony_ci
20262306a36Sopenharmony_ci	pdev = (struct platform_device *)map->map_priv_1;
20362306a36Sopenharmony_ci	info = platform_get_drvdata(pdev);
20462306a36Sopenharmony_ci
20562306a36Sopenharmony_ci	while (len) {
20662306a36Sopenharmony_ci		unsigned int winofs = ofs & win_mask(info->win_order);
20762306a36Sopenharmony_ci		unsigned int chunklen = min_t(unsigned int, len,
20862306a36Sopenharmony_ci					      BIT(info->win_order) - winofs);
20962306a36Sopenharmony_ci
21062306a36Sopenharmony_ci		physmap_set_addr_gpios(info, ofs);
21162306a36Sopenharmony_ci		memcpy_toio(map->virt + winofs, buf, chunklen);
21262306a36Sopenharmony_ci		len -= chunklen;
21362306a36Sopenharmony_ci		buf += chunklen;
21462306a36Sopenharmony_ci		ofs += chunklen;
21562306a36Sopenharmony_ci	}
21662306a36Sopenharmony_ci}
21762306a36Sopenharmony_ci
21862306a36Sopenharmony_cistatic int physmap_addr_gpios_map_init(struct map_info *map)
21962306a36Sopenharmony_ci{
22062306a36Sopenharmony_ci	map->phys = NO_XIP;
22162306a36Sopenharmony_ci	map->read = physmap_addr_gpios_read;
22262306a36Sopenharmony_ci	map->copy_from = physmap_addr_gpios_copy_from;
22362306a36Sopenharmony_ci	map->write = physmap_addr_gpios_write;
22462306a36Sopenharmony_ci	map->copy_to = physmap_addr_gpios_copy_to;
22562306a36Sopenharmony_ci
22662306a36Sopenharmony_ci	return 0;
22762306a36Sopenharmony_ci}
22862306a36Sopenharmony_ci#else
22962306a36Sopenharmony_cistatic int physmap_addr_gpios_map_init(struct map_info *map)
23062306a36Sopenharmony_ci{
23162306a36Sopenharmony_ci	return -ENOTSUPP;
23262306a36Sopenharmony_ci}
23362306a36Sopenharmony_ci#endif
23462306a36Sopenharmony_ci
23562306a36Sopenharmony_ci#if IS_ENABLED(CONFIG_MTD_PHYSMAP_OF)
23662306a36Sopenharmony_cistatic const struct of_device_id of_flash_match[] = {
23762306a36Sopenharmony_ci	{
23862306a36Sopenharmony_ci		.compatible = "cfi-flash",
23962306a36Sopenharmony_ci		.data = "cfi_probe",
24062306a36Sopenharmony_ci	},
24162306a36Sopenharmony_ci	{
24262306a36Sopenharmony_ci		/*
24362306a36Sopenharmony_ci		 * FIXME: JEDEC chips can't be safely and reliably
24462306a36Sopenharmony_ci		 * probed, although the mtd code gets it right in
24562306a36Sopenharmony_ci		 * practice most of the time.  We should use the
24662306a36Sopenharmony_ci		 * vendor and device ids specified by the binding to
24762306a36Sopenharmony_ci		 * bypass the heuristic probe code, but the mtd layer
24862306a36Sopenharmony_ci		 * provides, at present, no interface for doing so
24962306a36Sopenharmony_ci		 * :(.
25062306a36Sopenharmony_ci		 */
25162306a36Sopenharmony_ci		.compatible = "jedec-flash",
25262306a36Sopenharmony_ci		.data = "jedec_probe",
25362306a36Sopenharmony_ci	},
25462306a36Sopenharmony_ci	{
25562306a36Sopenharmony_ci		.compatible = "mtd-ram",
25662306a36Sopenharmony_ci		.data = "map_ram",
25762306a36Sopenharmony_ci	},
25862306a36Sopenharmony_ci	{
25962306a36Sopenharmony_ci		.compatible = "mtd-rom",
26062306a36Sopenharmony_ci		.data = "map_rom",
26162306a36Sopenharmony_ci	},
26262306a36Sopenharmony_ci	{
26362306a36Sopenharmony_ci		.type = "rom",
26462306a36Sopenharmony_ci		.compatible = "direct-mapped"
26562306a36Sopenharmony_ci	},
26662306a36Sopenharmony_ci	{ /* sentinel */ },
26762306a36Sopenharmony_ci};
26862306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, of_flash_match);
26962306a36Sopenharmony_ci
27062306a36Sopenharmony_cistatic const char * const of_default_part_probes[] = {
27162306a36Sopenharmony_ci	"cmdlinepart", "RedBoot", "ofpart", "ofoldpart", NULL
27262306a36Sopenharmony_ci};
27362306a36Sopenharmony_ci
27462306a36Sopenharmony_cistatic const char * const *of_get_part_probes(struct platform_device *dev)
27562306a36Sopenharmony_ci{
27662306a36Sopenharmony_ci	struct device_node *dp = dev->dev.of_node;
27762306a36Sopenharmony_ci	const char **res;
27862306a36Sopenharmony_ci	int count;
27962306a36Sopenharmony_ci
28062306a36Sopenharmony_ci	count = of_property_count_strings(dp, "linux,part-probe");
28162306a36Sopenharmony_ci	if (count < 0)
28262306a36Sopenharmony_ci		return of_default_part_probes;
28362306a36Sopenharmony_ci
28462306a36Sopenharmony_ci	res = devm_kcalloc(&dev->dev, count + 1, sizeof(*res), GFP_KERNEL);
28562306a36Sopenharmony_ci	if (!res)
28662306a36Sopenharmony_ci		return NULL;
28762306a36Sopenharmony_ci
28862306a36Sopenharmony_ci	count = of_property_read_string_array(dp, "linux,part-probe", res,
28962306a36Sopenharmony_ci					      count);
29062306a36Sopenharmony_ci	if (count < 0)
29162306a36Sopenharmony_ci		return NULL;
29262306a36Sopenharmony_ci
29362306a36Sopenharmony_ci	return res;
29462306a36Sopenharmony_ci}
29562306a36Sopenharmony_ci
29662306a36Sopenharmony_cistatic const char *of_select_probe_type(struct platform_device *dev)
29762306a36Sopenharmony_ci{
29862306a36Sopenharmony_ci	struct device_node *dp = dev->dev.of_node;
29962306a36Sopenharmony_ci	const struct of_device_id *match;
30062306a36Sopenharmony_ci	const char *probe_type;
30162306a36Sopenharmony_ci
30262306a36Sopenharmony_ci	match = of_match_device(of_flash_match, &dev->dev);
30362306a36Sopenharmony_ci	if (!match)
30462306a36Sopenharmony_ci		return NULL;
30562306a36Sopenharmony_ci
30662306a36Sopenharmony_ci	probe_type = match->data;
30762306a36Sopenharmony_ci	if (probe_type)
30862306a36Sopenharmony_ci		return probe_type;
30962306a36Sopenharmony_ci
31062306a36Sopenharmony_ci	dev_warn(&dev->dev,
31162306a36Sopenharmony_ci		 "Device tree uses obsolete \"direct-mapped\" flash binding\n");
31262306a36Sopenharmony_ci
31362306a36Sopenharmony_ci	of_property_read_string(dp, "probe-type", &probe_type);
31462306a36Sopenharmony_ci	if (!probe_type)
31562306a36Sopenharmony_ci		return NULL;
31662306a36Sopenharmony_ci
31762306a36Sopenharmony_ci	if (!strcmp(probe_type, "CFI")) {
31862306a36Sopenharmony_ci		probe_type = "cfi_probe";
31962306a36Sopenharmony_ci	} else if (!strcmp(probe_type, "JEDEC")) {
32062306a36Sopenharmony_ci		probe_type = "jedec_probe";
32162306a36Sopenharmony_ci	} else if (!strcmp(probe_type, "ROM")) {
32262306a36Sopenharmony_ci		probe_type = "map_rom";
32362306a36Sopenharmony_ci	} else {
32462306a36Sopenharmony_ci		dev_warn(&dev->dev,
32562306a36Sopenharmony_ci			 "obsolete_probe: don't know probe type '%s', mapping as rom\n",
32662306a36Sopenharmony_ci			 probe_type);
32762306a36Sopenharmony_ci		probe_type = "map_rom";
32862306a36Sopenharmony_ci	}
32962306a36Sopenharmony_ci
33062306a36Sopenharmony_ci	return probe_type;
33162306a36Sopenharmony_ci}
33262306a36Sopenharmony_ci
33362306a36Sopenharmony_cistatic int physmap_flash_of_init(struct platform_device *dev)
33462306a36Sopenharmony_ci{
33562306a36Sopenharmony_ci	struct physmap_flash_info *info = platform_get_drvdata(dev);
33662306a36Sopenharmony_ci	struct device_node *dp = dev->dev.of_node;
33762306a36Sopenharmony_ci	const char *mtd_name = NULL;
33862306a36Sopenharmony_ci	int err, swap = 0;
33962306a36Sopenharmony_ci	bool map_indirect;
34062306a36Sopenharmony_ci	unsigned int i;
34162306a36Sopenharmony_ci	u32 bankwidth;
34262306a36Sopenharmony_ci
34362306a36Sopenharmony_ci	if (!dp)
34462306a36Sopenharmony_ci		return -EINVAL;
34562306a36Sopenharmony_ci
34662306a36Sopenharmony_ci	info->probe_type = of_select_probe_type(dev);
34762306a36Sopenharmony_ci
34862306a36Sopenharmony_ci	info->part_types = of_get_part_probes(dev);
34962306a36Sopenharmony_ci	if (!info->part_types)
35062306a36Sopenharmony_ci		return -ENOMEM;
35162306a36Sopenharmony_ci
35262306a36Sopenharmony_ci	of_property_read_string(dp, "linux,mtd-name", &mtd_name);
35362306a36Sopenharmony_ci
35462306a36Sopenharmony_ci	map_indirect = of_property_read_bool(dp, "no-unaligned-direct-access");
35562306a36Sopenharmony_ci
35662306a36Sopenharmony_ci	err = of_property_read_u32(dp, "bank-width", &bankwidth);
35762306a36Sopenharmony_ci	if (err) {
35862306a36Sopenharmony_ci		dev_err(&dev->dev, "Can't get bank width from device tree\n");
35962306a36Sopenharmony_ci		return err;
36062306a36Sopenharmony_ci	}
36162306a36Sopenharmony_ci
36262306a36Sopenharmony_ci	if (of_property_read_bool(dp, "big-endian"))
36362306a36Sopenharmony_ci		swap = CFI_BIG_ENDIAN;
36462306a36Sopenharmony_ci	else if (of_property_read_bool(dp, "little-endian"))
36562306a36Sopenharmony_ci		swap = CFI_LITTLE_ENDIAN;
36662306a36Sopenharmony_ci
36762306a36Sopenharmony_ci	for (i = 0; i < info->nmaps; i++) {
36862306a36Sopenharmony_ci		info->maps[i].name = mtd_name;
36962306a36Sopenharmony_ci		info->maps[i].swap = swap;
37062306a36Sopenharmony_ci		info->maps[i].bankwidth = bankwidth;
37162306a36Sopenharmony_ci		info->maps[i].device_node = dp;
37262306a36Sopenharmony_ci
37362306a36Sopenharmony_ci		err = of_flash_probe_bt1_rom(dev, dp, &info->maps[i]);
37462306a36Sopenharmony_ci		if (err)
37562306a36Sopenharmony_ci			return err;
37662306a36Sopenharmony_ci
37762306a36Sopenharmony_ci		err = of_flash_probe_gemini(dev, dp, &info->maps[i]);
37862306a36Sopenharmony_ci		if (err)
37962306a36Sopenharmony_ci			return err;
38062306a36Sopenharmony_ci
38162306a36Sopenharmony_ci		err = of_flash_probe_ixp4xx(dev, dp, &info->maps[i]);
38262306a36Sopenharmony_ci		if (err)
38362306a36Sopenharmony_ci			return err;
38462306a36Sopenharmony_ci
38562306a36Sopenharmony_ci		err = of_flash_probe_versatile(dev, dp, &info->maps[i]);
38662306a36Sopenharmony_ci		if (err)
38762306a36Sopenharmony_ci			return err;
38862306a36Sopenharmony_ci
38962306a36Sopenharmony_ci		/*
39062306a36Sopenharmony_ci		 * On some platforms (e.g. MPC5200) a direct 1:1 mapping
39162306a36Sopenharmony_ci		 * may cause problems with JFFS2 usage, as the local bus (LPB)
39262306a36Sopenharmony_ci		 * doesn't support unaligned accesses as implemented in the
39362306a36Sopenharmony_ci		 * JFFS2 code via memcpy(). By setting NO_XIP, the
39462306a36Sopenharmony_ci		 * flash will not be exposed directly to the MTD users
39562306a36Sopenharmony_ci		 * (e.g. JFFS2) any more.
39662306a36Sopenharmony_ci		 */
39762306a36Sopenharmony_ci		if (map_indirect)
39862306a36Sopenharmony_ci			info->maps[i].phys = NO_XIP;
39962306a36Sopenharmony_ci	}
40062306a36Sopenharmony_ci
40162306a36Sopenharmony_ci	return 0;
40262306a36Sopenharmony_ci}
40362306a36Sopenharmony_ci#else /* IS_ENABLED(CONFIG_MTD_PHYSMAP_OF) */
40462306a36Sopenharmony_ci#define of_flash_match NULL
40562306a36Sopenharmony_ci
40662306a36Sopenharmony_cistatic int physmap_flash_of_init(struct platform_device *dev)
40762306a36Sopenharmony_ci{
40862306a36Sopenharmony_ci	return -ENOTSUPP;
40962306a36Sopenharmony_ci}
41062306a36Sopenharmony_ci#endif /* IS_ENABLED(CONFIG_MTD_PHYSMAP_OF) */
41162306a36Sopenharmony_ci
41262306a36Sopenharmony_cistatic const char * const rom_probe_types[] = {
41362306a36Sopenharmony_ci	"cfi_probe", "jedec_probe", "qinfo_probe", "map_rom",
41462306a36Sopenharmony_ci};
41562306a36Sopenharmony_ci
41662306a36Sopenharmony_cistatic const char * const part_probe_types[] = {
41762306a36Sopenharmony_ci	"cmdlinepart", "RedBoot", "afs", NULL
41862306a36Sopenharmony_ci};
41962306a36Sopenharmony_ci
42062306a36Sopenharmony_cistatic int physmap_flash_pdata_init(struct platform_device *dev)
42162306a36Sopenharmony_ci{
42262306a36Sopenharmony_ci	struct physmap_flash_info *info = platform_get_drvdata(dev);
42362306a36Sopenharmony_ci	struct physmap_flash_data *physmap_data;
42462306a36Sopenharmony_ci	unsigned int i;
42562306a36Sopenharmony_ci	int err;
42662306a36Sopenharmony_ci
42762306a36Sopenharmony_ci	physmap_data = dev_get_platdata(&dev->dev);
42862306a36Sopenharmony_ci	if (!physmap_data)
42962306a36Sopenharmony_ci		return -EINVAL;
43062306a36Sopenharmony_ci
43162306a36Sopenharmony_ci	info->probe_type = physmap_data->probe_type;
43262306a36Sopenharmony_ci	info->part_types = physmap_data->part_probe_types ? : part_probe_types;
43362306a36Sopenharmony_ci	info->parts = physmap_data->parts;
43462306a36Sopenharmony_ci	info->nparts = physmap_data->nr_parts;
43562306a36Sopenharmony_ci
43662306a36Sopenharmony_ci	if (physmap_data->init) {
43762306a36Sopenharmony_ci		err = physmap_data->init(dev);
43862306a36Sopenharmony_ci		if (err)
43962306a36Sopenharmony_ci			return err;
44062306a36Sopenharmony_ci	}
44162306a36Sopenharmony_ci
44262306a36Sopenharmony_ci	for (i = 0; i < info->nmaps; i++) {
44362306a36Sopenharmony_ci		info->maps[i].bankwidth = physmap_data->width;
44462306a36Sopenharmony_ci		info->maps[i].pfow_base = physmap_data->pfow_base;
44562306a36Sopenharmony_ci		info->maps[i].set_vpp = physmap_set_vpp;
44662306a36Sopenharmony_ci	}
44762306a36Sopenharmony_ci
44862306a36Sopenharmony_ci	return 0;
44962306a36Sopenharmony_ci}
45062306a36Sopenharmony_ci
45162306a36Sopenharmony_cistatic int physmap_flash_probe(struct platform_device *dev)
45262306a36Sopenharmony_ci{
45362306a36Sopenharmony_ci	struct physmap_flash_info *info;
45462306a36Sopenharmony_ci	int err = 0;
45562306a36Sopenharmony_ci	int i;
45662306a36Sopenharmony_ci
45762306a36Sopenharmony_ci	if (!dev->dev.of_node && !dev_get_platdata(&dev->dev))
45862306a36Sopenharmony_ci		return -EINVAL;
45962306a36Sopenharmony_ci
46062306a36Sopenharmony_ci	info = devm_kzalloc(&dev->dev, sizeof(*info), GFP_KERNEL);
46162306a36Sopenharmony_ci	if (!info)
46262306a36Sopenharmony_ci		return -ENOMEM;
46362306a36Sopenharmony_ci
46462306a36Sopenharmony_ci	while (platform_get_resource(dev, IORESOURCE_MEM, info->nmaps))
46562306a36Sopenharmony_ci		info->nmaps++;
46662306a36Sopenharmony_ci
46762306a36Sopenharmony_ci	if (!info->nmaps)
46862306a36Sopenharmony_ci		return -ENODEV;
46962306a36Sopenharmony_ci
47062306a36Sopenharmony_ci	info->maps = devm_kzalloc(&dev->dev,
47162306a36Sopenharmony_ci				  sizeof(*info->maps) * info->nmaps,
47262306a36Sopenharmony_ci				  GFP_KERNEL);
47362306a36Sopenharmony_ci	if (!info->maps)
47462306a36Sopenharmony_ci		return -ENOMEM;
47562306a36Sopenharmony_ci
47662306a36Sopenharmony_ci	info->mtds = devm_kzalloc(&dev->dev,
47762306a36Sopenharmony_ci				  sizeof(*info->mtds) * info->nmaps,
47862306a36Sopenharmony_ci				  GFP_KERNEL);
47962306a36Sopenharmony_ci	if (!info->mtds)
48062306a36Sopenharmony_ci		return -ENOMEM;
48162306a36Sopenharmony_ci
48262306a36Sopenharmony_ci	platform_set_drvdata(dev, info);
48362306a36Sopenharmony_ci
48462306a36Sopenharmony_ci	info->gpios = devm_gpiod_get_array_optional(&dev->dev, "addr",
48562306a36Sopenharmony_ci						    GPIOD_OUT_LOW);
48662306a36Sopenharmony_ci	if (IS_ERR(info->gpios))
48762306a36Sopenharmony_ci		return PTR_ERR(info->gpios);
48862306a36Sopenharmony_ci
48962306a36Sopenharmony_ci	if (info->gpios && info->nmaps > 1) {
49062306a36Sopenharmony_ci		dev_err(&dev->dev, "addr-gpios only supported for nmaps == 1\n");
49162306a36Sopenharmony_ci		return -EINVAL;
49262306a36Sopenharmony_ci	}
49362306a36Sopenharmony_ci
49462306a36Sopenharmony_ci	pm_runtime_enable(&dev->dev);
49562306a36Sopenharmony_ci	pm_runtime_get_sync(&dev->dev);
49662306a36Sopenharmony_ci
49762306a36Sopenharmony_ci	if (dev->dev.of_node)
49862306a36Sopenharmony_ci		err = physmap_flash_of_init(dev);
49962306a36Sopenharmony_ci	else
50062306a36Sopenharmony_ci		err = physmap_flash_pdata_init(dev);
50162306a36Sopenharmony_ci
50262306a36Sopenharmony_ci	if (err) {
50362306a36Sopenharmony_ci		pm_runtime_put(&dev->dev);
50462306a36Sopenharmony_ci		pm_runtime_disable(&dev->dev);
50562306a36Sopenharmony_ci		return err;
50662306a36Sopenharmony_ci	}
50762306a36Sopenharmony_ci
50862306a36Sopenharmony_ci	for (i = 0; i < info->nmaps; i++) {
50962306a36Sopenharmony_ci		struct resource *res;
51062306a36Sopenharmony_ci
51162306a36Sopenharmony_ci		info->maps[i].virt = devm_platform_get_and_ioremap_resource(dev, i, &res);
51262306a36Sopenharmony_ci		if (IS_ERR(info->maps[i].virt)) {
51362306a36Sopenharmony_ci			err = PTR_ERR(info->maps[i].virt);
51462306a36Sopenharmony_ci			goto err_out;
51562306a36Sopenharmony_ci		}
51662306a36Sopenharmony_ci
51762306a36Sopenharmony_ci		dev_notice(&dev->dev, "physmap platform flash device: %pR\n",
51862306a36Sopenharmony_ci			   res);
51962306a36Sopenharmony_ci
52062306a36Sopenharmony_ci		if (!info->maps[i].name)
52162306a36Sopenharmony_ci			info->maps[i].name = dev_name(&dev->dev);
52262306a36Sopenharmony_ci
52362306a36Sopenharmony_ci		if (!info->maps[i].phys)
52462306a36Sopenharmony_ci			info->maps[i].phys = res->start;
52562306a36Sopenharmony_ci
52662306a36Sopenharmony_ci		info->win_order = fls64(resource_size(res)) - 1;
52762306a36Sopenharmony_ci		info->maps[i].size = BIT(info->win_order +
52862306a36Sopenharmony_ci					 (info->gpios ?
52962306a36Sopenharmony_ci					  info->gpios->ndescs : 0));
53062306a36Sopenharmony_ci
53162306a36Sopenharmony_ci		info->maps[i].map_priv_1 = (unsigned long)dev;
53262306a36Sopenharmony_ci
53362306a36Sopenharmony_ci		if (info->gpios) {
53462306a36Sopenharmony_ci			err = physmap_addr_gpios_map_init(&info->maps[i]);
53562306a36Sopenharmony_ci			if (err)
53662306a36Sopenharmony_ci				goto err_out;
53762306a36Sopenharmony_ci		}
53862306a36Sopenharmony_ci
53962306a36Sopenharmony_ci#ifdef CONFIG_MTD_COMPLEX_MAPPINGS
54062306a36Sopenharmony_ci		/*
54162306a36Sopenharmony_ci		 * Only use the simple_map implementation if map hooks are not
54262306a36Sopenharmony_ci		 * implemented. Since map->read() is mandatory checking for its
54362306a36Sopenharmony_ci		 * presence is enough.
54462306a36Sopenharmony_ci		 */
54562306a36Sopenharmony_ci		if (!info->maps[i].read)
54662306a36Sopenharmony_ci			simple_map_init(&info->maps[i]);
54762306a36Sopenharmony_ci#else
54862306a36Sopenharmony_ci		simple_map_init(&info->maps[i]);
54962306a36Sopenharmony_ci#endif
55062306a36Sopenharmony_ci
55162306a36Sopenharmony_ci		if (info->probe_type) {
55262306a36Sopenharmony_ci			info->mtds[i] = do_map_probe(info->probe_type,
55362306a36Sopenharmony_ci						     &info->maps[i]);
55462306a36Sopenharmony_ci
55562306a36Sopenharmony_ci			/* Fall back to mapping region as ROM */
55662306a36Sopenharmony_ci			if (!info->mtds[i] && IS_ENABLED(CONFIG_MTD_ROM) &&
55762306a36Sopenharmony_ci			    strcmp(info->probe_type, "map_rom")) {
55862306a36Sopenharmony_ci				dev_warn(&dev->dev,
55962306a36Sopenharmony_ci					 "map_probe() failed for type %s\n",
56062306a36Sopenharmony_ci					 info->probe_type);
56162306a36Sopenharmony_ci
56262306a36Sopenharmony_ci				info->mtds[i] = do_map_probe("map_rom",
56362306a36Sopenharmony_ci							     &info->maps[i]);
56462306a36Sopenharmony_ci			}
56562306a36Sopenharmony_ci		} else {
56662306a36Sopenharmony_ci			int j;
56762306a36Sopenharmony_ci
56862306a36Sopenharmony_ci			for (j = 0; j < ARRAY_SIZE(rom_probe_types); j++) {
56962306a36Sopenharmony_ci				info->mtds[i] = do_map_probe(rom_probe_types[j],
57062306a36Sopenharmony_ci							     &info->maps[i]);
57162306a36Sopenharmony_ci				if (info->mtds[i])
57262306a36Sopenharmony_ci					break;
57362306a36Sopenharmony_ci			}
57462306a36Sopenharmony_ci		}
57562306a36Sopenharmony_ci
57662306a36Sopenharmony_ci		if (!info->mtds[i]) {
57762306a36Sopenharmony_ci			dev_err(&dev->dev, "map_probe failed\n");
57862306a36Sopenharmony_ci			err = -ENXIO;
57962306a36Sopenharmony_ci			goto err_out;
58062306a36Sopenharmony_ci		}
58162306a36Sopenharmony_ci		info->mtds[i]->dev.parent = &dev->dev;
58262306a36Sopenharmony_ci	}
58362306a36Sopenharmony_ci
58462306a36Sopenharmony_ci	if (info->nmaps == 1) {
58562306a36Sopenharmony_ci		info->cmtd = info->mtds[0];
58662306a36Sopenharmony_ci	} else {
58762306a36Sopenharmony_ci		/*
58862306a36Sopenharmony_ci		 * We detected multiple devices. Concatenate them together.
58962306a36Sopenharmony_ci		 */
59062306a36Sopenharmony_ci		info->cmtd = mtd_concat_create(info->mtds, info->nmaps,
59162306a36Sopenharmony_ci					       dev_name(&dev->dev));
59262306a36Sopenharmony_ci		if (!info->cmtd)
59362306a36Sopenharmony_ci			err = -ENXIO;
59462306a36Sopenharmony_ci	}
59562306a36Sopenharmony_ci	if (err)
59662306a36Sopenharmony_ci		goto err_out;
59762306a36Sopenharmony_ci
59862306a36Sopenharmony_ci	spin_lock_init(&info->vpp_lock);
59962306a36Sopenharmony_ci
60062306a36Sopenharmony_ci	mtd_set_of_node(info->cmtd, dev->dev.of_node);
60162306a36Sopenharmony_ci	err = mtd_device_parse_register(info->cmtd, info->part_types, NULL,
60262306a36Sopenharmony_ci					info->parts, info->nparts);
60362306a36Sopenharmony_ci	if (err)
60462306a36Sopenharmony_ci		goto err_out;
60562306a36Sopenharmony_ci
60662306a36Sopenharmony_ci	return 0;
60762306a36Sopenharmony_ci
60862306a36Sopenharmony_cierr_out:
60962306a36Sopenharmony_ci	physmap_flash_remove(dev);
61062306a36Sopenharmony_ci	return err;
61162306a36Sopenharmony_ci}
61262306a36Sopenharmony_ci
61362306a36Sopenharmony_ci#ifdef CONFIG_PM
61462306a36Sopenharmony_cistatic void physmap_flash_shutdown(struct platform_device *dev)
61562306a36Sopenharmony_ci{
61662306a36Sopenharmony_ci	struct physmap_flash_info *info = platform_get_drvdata(dev);
61762306a36Sopenharmony_ci	int i;
61862306a36Sopenharmony_ci
61962306a36Sopenharmony_ci	for (i = 0; i < info->nmaps && info->mtds[i]; i++)
62062306a36Sopenharmony_ci		if (mtd_suspend(info->mtds[i]) == 0)
62162306a36Sopenharmony_ci			mtd_resume(info->mtds[i]);
62262306a36Sopenharmony_ci}
62362306a36Sopenharmony_ci#else
62462306a36Sopenharmony_ci#define physmap_flash_shutdown NULL
62562306a36Sopenharmony_ci#endif
62662306a36Sopenharmony_ci
62762306a36Sopenharmony_cistatic struct platform_driver physmap_flash_driver = {
62862306a36Sopenharmony_ci	.probe		= physmap_flash_probe,
62962306a36Sopenharmony_ci	.remove		= physmap_flash_remove,
63062306a36Sopenharmony_ci	.shutdown	= physmap_flash_shutdown,
63162306a36Sopenharmony_ci	.driver		= {
63262306a36Sopenharmony_ci		.name	= "physmap-flash",
63362306a36Sopenharmony_ci		.of_match_table = of_flash_match,
63462306a36Sopenharmony_ci	},
63562306a36Sopenharmony_ci};
63662306a36Sopenharmony_ci
63762306a36Sopenharmony_ci#ifdef CONFIG_MTD_PHYSMAP_COMPAT
63862306a36Sopenharmony_cistatic struct physmap_flash_data physmap_flash_data = {
63962306a36Sopenharmony_ci	.width		= CONFIG_MTD_PHYSMAP_BANKWIDTH,
64062306a36Sopenharmony_ci};
64162306a36Sopenharmony_ci
64262306a36Sopenharmony_cistatic struct resource physmap_flash_resource = {
64362306a36Sopenharmony_ci	.start		= CONFIG_MTD_PHYSMAP_START,
64462306a36Sopenharmony_ci	.end		= CONFIG_MTD_PHYSMAP_START + CONFIG_MTD_PHYSMAP_LEN - 1,
64562306a36Sopenharmony_ci	.flags		= IORESOURCE_MEM,
64662306a36Sopenharmony_ci};
64762306a36Sopenharmony_ci
64862306a36Sopenharmony_cistatic struct platform_device physmap_flash = {
64962306a36Sopenharmony_ci	.name		= "physmap-flash",
65062306a36Sopenharmony_ci	.id		= 0,
65162306a36Sopenharmony_ci	.dev		= {
65262306a36Sopenharmony_ci		.platform_data	= &physmap_flash_data,
65362306a36Sopenharmony_ci	},
65462306a36Sopenharmony_ci	.num_resources	= 1,
65562306a36Sopenharmony_ci	.resource	= &physmap_flash_resource,
65662306a36Sopenharmony_ci};
65762306a36Sopenharmony_ci#endif
65862306a36Sopenharmony_ci
65962306a36Sopenharmony_cistatic int __init physmap_init(void)
66062306a36Sopenharmony_ci{
66162306a36Sopenharmony_ci	int err;
66262306a36Sopenharmony_ci
66362306a36Sopenharmony_ci	err = platform_driver_register(&physmap_flash_driver);
66462306a36Sopenharmony_ci#ifdef CONFIG_MTD_PHYSMAP_COMPAT
66562306a36Sopenharmony_ci	if (err == 0) {
66662306a36Sopenharmony_ci		err = platform_device_register(&physmap_flash);
66762306a36Sopenharmony_ci		if (err)
66862306a36Sopenharmony_ci			platform_driver_unregister(&physmap_flash_driver);
66962306a36Sopenharmony_ci	}
67062306a36Sopenharmony_ci#endif
67162306a36Sopenharmony_ci
67262306a36Sopenharmony_ci	return err;
67362306a36Sopenharmony_ci}
67462306a36Sopenharmony_ci
67562306a36Sopenharmony_cistatic void __exit physmap_exit(void)
67662306a36Sopenharmony_ci{
67762306a36Sopenharmony_ci#ifdef CONFIG_MTD_PHYSMAP_COMPAT
67862306a36Sopenharmony_ci	platform_device_unregister(&physmap_flash);
67962306a36Sopenharmony_ci#endif
68062306a36Sopenharmony_ci	platform_driver_unregister(&physmap_flash_driver);
68162306a36Sopenharmony_ci}
68262306a36Sopenharmony_ci
68362306a36Sopenharmony_cimodule_init(physmap_init);
68462306a36Sopenharmony_cimodule_exit(physmap_exit);
68562306a36Sopenharmony_ci
68662306a36Sopenharmony_ciMODULE_LICENSE("GPL");
68762306a36Sopenharmony_ciMODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org>");
68862306a36Sopenharmony_ciMODULE_AUTHOR("Vitaly Wool <vwool@ru.mvista.com>");
68962306a36Sopenharmony_ciMODULE_AUTHOR("Mike Frysinger <vapier@gentoo.org>");
69062306a36Sopenharmony_ciMODULE_DESCRIPTION("Generic configurable MTD map driver");
69162306a36Sopenharmony_ci
69262306a36Sopenharmony_ci/* legacy platform drivers can't hotplug or coldplg */
69362306a36Sopenharmony_ci#ifndef CONFIG_MTD_PHYSMAP_COMPAT
69462306a36Sopenharmony_ci/* work with hotplug and coldplug */
69562306a36Sopenharmony_ciMODULE_ALIAS("platform:physmap-flash");
69662306a36Sopenharmony_ci#endif
697