162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/* sun_uflash.c - Driver for user-programmable flash on
362306a36Sopenharmony_ci *                Sun Microsystems SME boardsets.
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * This driver does NOT provide access to the OBP-flash for
662306a36Sopenharmony_ci * safety reasons-- use <linux>/drivers/sbus/char/flash.c instead.
762306a36Sopenharmony_ci *
862306a36Sopenharmony_ci * Copyright (c) 2001 Eric Brower (ebrower@usa.net)
962306a36Sopenharmony_ci */
1062306a36Sopenharmony_ci
1162306a36Sopenharmony_ci#include <linux/kernel.h>
1262306a36Sopenharmony_ci#include <linux/module.h>
1362306a36Sopenharmony_ci#include <linux/fs.h>
1462306a36Sopenharmony_ci#include <linux/errno.h>
1562306a36Sopenharmony_ci#include <linux/ioport.h>
1662306a36Sopenharmony_ci#include <linux/of.h>
1762306a36Sopenharmony_ci#include <linux/platform_device.h>
1862306a36Sopenharmony_ci#include <linux/slab.h>
1962306a36Sopenharmony_ci#include <asm/prom.h>
2062306a36Sopenharmony_ci#include <linux/uaccess.h>
2162306a36Sopenharmony_ci#include <asm/io.h>
2262306a36Sopenharmony_ci
2362306a36Sopenharmony_ci#include <linux/mtd/mtd.h>
2462306a36Sopenharmony_ci#include <linux/mtd/map.h>
2562306a36Sopenharmony_ci
2662306a36Sopenharmony_ci#define UFLASH_OBPNAME	"flashprom"
2762306a36Sopenharmony_ci#define DRIVER_NAME	"sun_uflash"
2862306a36Sopenharmony_ci#define PFX		DRIVER_NAME ": "
2962306a36Sopenharmony_ci
3062306a36Sopenharmony_ci#define UFLASH_WINDOW_SIZE	0x200000
3162306a36Sopenharmony_ci#define UFLASH_BUSWIDTH		1			/* EBus is 8-bit */
3262306a36Sopenharmony_ci
3362306a36Sopenharmony_ciMODULE_AUTHOR("Eric Brower <ebrower@usa.net>");
3462306a36Sopenharmony_ciMODULE_DESCRIPTION("User-programmable flash device on Sun Microsystems boardsets");
3562306a36Sopenharmony_ciMODULE_LICENSE("GPL");
3662306a36Sopenharmony_ciMODULE_VERSION("2.1");
3762306a36Sopenharmony_ci
3862306a36Sopenharmony_cistruct uflash_dev {
3962306a36Sopenharmony_ci	const char		*name;	/* device name */
4062306a36Sopenharmony_ci	struct map_info 	map;	/* mtd map info */
4162306a36Sopenharmony_ci	struct mtd_info		*mtd;	/* mtd info */
4262306a36Sopenharmony_ci};
4362306a36Sopenharmony_ci
4462306a36Sopenharmony_cistruct map_info uflash_map_templ = {
4562306a36Sopenharmony_ci	.name =		"SUNW,???-????",
4662306a36Sopenharmony_ci	.size =		UFLASH_WINDOW_SIZE,
4762306a36Sopenharmony_ci	.bankwidth =	UFLASH_BUSWIDTH,
4862306a36Sopenharmony_ci};
4962306a36Sopenharmony_ci
5062306a36Sopenharmony_ciint uflash_devinit(struct platform_device *op, struct device_node *dp)
5162306a36Sopenharmony_ci{
5262306a36Sopenharmony_ci	struct uflash_dev *up;
5362306a36Sopenharmony_ci
5462306a36Sopenharmony_ci	if (op->resource[1].flags) {
5562306a36Sopenharmony_ci		/* Non-CFI userflash device-- once I find one we
5662306a36Sopenharmony_ci		 * can work on supporting it.
5762306a36Sopenharmony_ci		 */
5862306a36Sopenharmony_ci		printk(KERN_ERR PFX "Unsupported device at %pOF, 0x%llx\n",
5962306a36Sopenharmony_ci		       dp, (unsigned long long)op->resource[0].start);
6062306a36Sopenharmony_ci
6162306a36Sopenharmony_ci		return -ENODEV;
6262306a36Sopenharmony_ci	}
6362306a36Sopenharmony_ci
6462306a36Sopenharmony_ci	up = kzalloc(sizeof(struct uflash_dev), GFP_KERNEL);
6562306a36Sopenharmony_ci	if (!up)
6662306a36Sopenharmony_ci		return -ENOMEM;
6762306a36Sopenharmony_ci
6862306a36Sopenharmony_ci	/* copy defaults and tweak parameters */
6962306a36Sopenharmony_ci	memcpy(&up->map, &uflash_map_templ, sizeof(uflash_map_templ));
7062306a36Sopenharmony_ci
7162306a36Sopenharmony_ci	up->map.size = resource_size(&op->resource[0]);
7262306a36Sopenharmony_ci
7362306a36Sopenharmony_ci	up->name = of_get_property(dp, "model", NULL);
7462306a36Sopenharmony_ci	if (up->name && 0 < strlen(up->name))
7562306a36Sopenharmony_ci		up->map.name = up->name;
7662306a36Sopenharmony_ci
7762306a36Sopenharmony_ci	up->map.phys = op->resource[0].start;
7862306a36Sopenharmony_ci
7962306a36Sopenharmony_ci	up->map.virt = of_ioremap(&op->resource[0], 0, up->map.size,
8062306a36Sopenharmony_ci				  DRIVER_NAME);
8162306a36Sopenharmony_ci	if (!up->map.virt) {
8262306a36Sopenharmony_ci		printk(KERN_ERR PFX "Failed to map device.\n");
8362306a36Sopenharmony_ci		kfree(up);
8462306a36Sopenharmony_ci
8562306a36Sopenharmony_ci		return -EINVAL;
8662306a36Sopenharmony_ci	}
8762306a36Sopenharmony_ci
8862306a36Sopenharmony_ci	simple_map_init(&up->map);
8962306a36Sopenharmony_ci
9062306a36Sopenharmony_ci	/* MTD registration */
9162306a36Sopenharmony_ci	up->mtd = do_map_probe("cfi_probe", &up->map);
9262306a36Sopenharmony_ci	if (!up->mtd) {
9362306a36Sopenharmony_ci		of_iounmap(&op->resource[0], up->map.virt, up->map.size);
9462306a36Sopenharmony_ci		kfree(up);
9562306a36Sopenharmony_ci
9662306a36Sopenharmony_ci		return -ENXIO;
9762306a36Sopenharmony_ci	}
9862306a36Sopenharmony_ci
9962306a36Sopenharmony_ci	up->mtd->owner = THIS_MODULE;
10062306a36Sopenharmony_ci
10162306a36Sopenharmony_ci	mtd_device_register(up->mtd, NULL, 0);
10262306a36Sopenharmony_ci
10362306a36Sopenharmony_ci	dev_set_drvdata(&op->dev, up);
10462306a36Sopenharmony_ci
10562306a36Sopenharmony_ci	return 0;
10662306a36Sopenharmony_ci}
10762306a36Sopenharmony_ci
10862306a36Sopenharmony_cistatic int uflash_probe(struct platform_device *op)
10962306a36Sopenharmony_ci{
11062306a36Sopenharmony_ci	struct device_node *dp = op->dev.of_node;
11162306a36Sopenharmony_ci
11262306a36Sopenharmony_ci	/* Flashprom must have the "user" property in order to
11362306a36Sopenharmony_ci	 * be used by this driver.
11462306a36Sopenharmony_ci	 */
11562306a36Sopenharmony_ci	if (!of_property_read_bool(dp, "user"))
11662306a36Sopenharmony_ci		return -ENODEV;
11762306a36Sopenharmony_ci
11862306a36Sopenharmony_ci	return uflash_devinit(op, dp);
11962306a36Sopenharmony_ci}
12062306a36Sopenharmony_ci
12162306a36Sopenharmony_cistatic int uflash_remove(struct platform_device *op)
12262306a36Sopenharmony_ci{
12362306a36Sopenharmony_ci	struct uflash_dev *up = dev_get_drvdata(&op->dev);
12462306a36Sopenharmony_ci
12562306a36Sopenharmony_ci	if (up->mtd) {
12662306a36Sopenharmony_ci		mtd_device_unregister(up->mtd);
12762306a36Sopenharmony_ci		map_destroy(up->mtd);
12862306a36Sopenharmony_ci	}
12962306a36Sopenharmony_ci	if (up->map.virt) {
13062306a36Sopenharmony_ci		of_iounmap(&op->resource[0], up->map.virt, up->map.size);
13162306a36Sopenharmony_ci		up->map.virt = NULL;
13262306a36Sopenharmony_ci	}
13362306a36Sopenharmony_ci
13462306a36Sopenharmony_ci	kfree(up);
13562306a36Sopenharmony_ci
13662306a36Sopenharmony_ci	return 0;
13762306a36Sopenharmony_ci}
13862306a36Sopenharmony_ci
13962306a36Sopenharmony_cistatic const struct of_device_id uflash_match[] = {
14062306a36Sopenharmony_ci	{
14162306a36Sopenharmony_ci		.name = UFLASH_OBPNAME,
14262306a36Sopenharmony_ci	},
14362306a36Sopenharmony_ci	{},
14462306a36Sopenharmony_ci};
14562306a36Sopenharmony_ci
14662306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, uflash_match);
14762306a36Sopenharmony_ci
14862306a36Sopenharmony_cistatic struct platform_driver uflash_driver = {
14962306a36Sopenharmony_ci	.driver = {
15062306a36Sopenharmony_ci		.name = DRIVER_NAME,
15162306a36Sopenharmony_ci		.of_match_table = uflash_match,
15262306a36Sopenharmony_ci	},
15362306a36Sopenharmony_ci	.probe		= uflash_probe,
15462306a36Sopenharmony_ci	.remove		= uflash_remove,
15562306a36Sopenharmony_ci};
15662306a36Sopenharmony_ci
15762306a36Sopenharmony_cimodule_platform_driver(uflash_driver);
158