162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * OPAL PNOR flash MTD abstraction
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright IBM 2015
662306a36Sopenharmony_ci */
762306a36Sopenharmony_ci
862306a36Sopenharmony_ci#include <linux/kernel.h>
962306a36Sopenharmony_ci#include <linux/module.h>
1062306a36Sopenharmony_ci#include <linux/errno.h>
1162306a36Sopenharmony_ci#include <linux/of.h>
1262306a36Sopenharmony_ci#include <linux/of_address.h>
1362306a36Sopenharmony_ci#include <linux/platform_device.h>
1462306a36Sopenharmony_ci#include <linux/string.h>
1562306a36Sopenharmony_ci#include <linux/slab.h>
1662306a36Sopenharmony_ci#include <linux/mtd/mtd.h>
1762306a36Sopenharmony_ci#include <linux/mtd/partitions.h>
1862306a36Sopenharmony_ci
1962306a36Sopenharmony_ci#include <linux/debugfs.h>
2062306a36Sopenharmony_ci#include <linux/seq_file.h>
2162306a36Sopenharmony_ci
2262306a36Sopenharmony_ci#include <asm/opal.h>
2362306a36Sopenharmony_ci
2462306a36Sopenharmony_ci
2562306a36Sopenharmony_ci/*
2662306a36Sopenharmony_ci * This driver creates the a Linux MTD abstraction for platform PNOR flash
2762306a36Sopenharmony_ci * backed by OPAL calls
2862306a36Sopenharmony_ci */
2962306a36Sopenharmony_ci
3062306a36Sopenharmony_cistruct powernv_flash {
3162306a36Sopenharmony_ci	struct mtd_info	mtd;
3262306a36Sopenharmony_ci	u32 id;
3362306a36Sopenharmony_ci};
3462306a36Sopenharmony_ci
3562306a36Sopenharmony_cienum flash_op {
3662306a36Sopenharmony_ci	FLASH_OP_READ,
3762306a36Sopenharmony_ci	FLASH_OP_WRITE,
3862306a36Sopenharmony_ci	FLASH_OP_ERASE,
3962306a36Sopenharmony_ci};
4062306a36Sopenharmony_ci
4162306a36Sopenharmony_ci/*
4262306a36Sopenharmony_ci * Don't return -ERESTARTSYS if we can't get a token, the MTD core
4362306a36Sopenharmony_ci * might have split up the call from userspace and called into the
4462306a36Sopenharmony_ci * driver more than once, we'll already have done some amount of work.
4562306a36Sopenharmony_ci */
4662306a36Sopenharmony_cistatic int powernv_flash_async_op(struct mtd_info *mtd, enum flash_op op,
4762306a36Sopenharmony_ci		loff_t offset, size_t len, size_t *retlen, u_char *buf)
4862306a36Sopenharmony_ci{
4962306a36Sopenharmony_ci	struct powernv_flash *info = (struct powernv_flash *)mtd->priv;
5062306a36Sopenharmony_ci	struct device *dev = &mtd->dev;
5162306a36Sopenharmony_ci	int token;
5262306a36Sopenharmony_ci	struct opal_msg msg;
5362306a36Sopenharmony_ci	int rc;
5462306a36Sopenharmony_ci
5562306a36Sopenharmony_ci	dev_dbg(dev, "%s(op=%d, offset=0x%llx, len=%zu)\n",
5662306a36Sopenharmony_ci			__func__, op, offset, len);
5762306a36Sopenharmony_ci
5862306a36Sopenharmony_ci	token = opal_async_get_token_interruptible();
5962306a36Sopenharmony_ci	if (token < 0) {
6062306a36Sopenharmony_ci		if (token != -ERESTARTSYS)
6162306a36Sopenharmony_ci			dev_err(dev, "Failed to get an async token\n");
6262306a36Sopenharmony_ci		else
6362306a36Sopenharmony_ci			token = -EINTR;
6462306a36Sopenharmony_ci		return token;
6562306a36Sopenharmony_ci	}
6662306a36Sopenharmony_ci
6762306a36Sopenharmony_ci	switch (op) {
6862306a36Sopenharmony_ci	case FLASH_OP_READ:
6962306a36Sopenharmony_ci		rc = opal_flash_read(info->id, offset, __pa(buf), len, token);
7062306a36Sopenharmony_ci		break;
7162306a36Sopenharmony_ci	case FLASH_OP_WRITE:
7262306a36Sopenharmony_ci		rc = opal_flash_write(info->id, offset, __pa(buf), len, token);
7362306a36Sopenharmony_ci		break;
7462306a36Sopenharmony_ci	case FLASH_OP_ERASE:
7562306a36Sopenharmony_ci		rc = opal_flash_erase(info->id, offset, len, token);
7662306a36Sopenharmony_ci		break;
7762306a36Sopenharmony_ci	default:
7862306a36Sopenharmony_ci		WARN_ON_ONCE(1);
7962306a36Sopenharmony_ci		opal_async_release_token(token);
8062306a36Sopenharmony_ci		return -EIO;
8162306a36Sopenharmony_ci	}
8262306a36Sopenharmony_ci
8362306a36Sopenharmony_ci	if (rc == OPAL_ASYNC_COMPLETION) {
8462306a36Sopenharmony_ci		rc = opal_async_wait_response_interruptible(token, &msg);
8562306a36Sopenharmony_ci		if (rc) {
8662306a36Sopenharmony_ci			/*
8762306a36Sopenharmony_ci			 * If we return the mtd core will free the
8862306a36Sopenharmony_ci			 * buffer we've just passed to OPAL but OPAL
8962306a36Sopenharmony_ci			 * will continue to read or write from that
9062306a36Sopenharmony_ci			 * memory.
9162306a36Sopenharmony_ci			 * It may be tempting to ultimately return 0
9262306a36Sopenharmony_ci			 * if we're doing a read or a write since we
9362306a36Sopenharmony_ci			 * are going to end up waiting until OPAL is
9462306a36Sopenharmony_ci			 * done. However, because the MTD core sends
9562306a36Sopenharmony_ci			 * us the userspace request in chunks, we need
9662306a36Sopenharmony_ci			 * it to know we've been interrupted.
9762306a36Sopenharmony_ci			 */
9862306a36Sopenharmony_ci			rc = -EINTR;
9962306a36Sopenharmony_ci			if (opal_async_wait_response(token, &msg))
10062306a36Sopenharmony_ci				dev_err(dev, "opal_async_wait_response() failed\n");
10162306a36Sopenharmony_ci			goto out;
10262306a36Sopenharmony_ci		}
10362306a36Sopenharmony_ci		rc = opal_get_async_rc(msg);
10462306a36Sopenharmony_ci	}
10562306a36Sopenharmony_ci
10662306a36Sopenharmony_ci	/*
10762306a36Sopenharmony_ci	 * OPAL does mutual exclusion on the flash, it will return
10862306a36Sopenharmony_ci	 * OPAL_BUSY.
10962306a36Sopenharmony_ci	 * During firmware updates by the service processor OPAL may
11062306a36Sopenharmony_ci	 * be (temporarily) prevented from accessing the flash, in
11162306a36Sopenharmony_ci	 * this case OPAL will also return OPAL_BUSY.
11262306a36Sopenharmony_ci	 * Both cases aren't errors exactly but the flash could have
11362306a36Sopenharmony_ci	 * changed, userspace should be informed.
11462306a36Sopenharmony_ci	 */
11562306a36Sopenharmony_ci	if (rc != OPAL_SUCCESS && rc != OPAL_BUSY)
11662306a36Sopenharmony_ci		dev_err(dev, "opal_flash_async_op(op=%d) failed (rc %d)\n",
11762306a36Sopenharmony_ci				op, rc);
11862306a36Sopenharmony_ci
11962306a36Sopenharmony_ci	if (rc == OPAL_SUCCESS && retlen)
12062306a36Sopenharmony_ci		*retlen = len;
12162306a36Sopenharmony_ci
12262306a36Sopenharmony_ci	rc = opal_error_code(rc);
12362306a36Sopenharmony_ciout:
12462306a36Sopenharmony_ci	opal_async_release_token(token);
12562306a36Sopenharmony_ci	return rc;
12662306a36Sopenharmony_ci}
12762306a36Sopenharmony_ci
12862306a36Sopenharmony_ci/**
12962306a36Sopenharmony_ci * powernv_flash_read
13062306a36Sopenharmony_ci * @mtd: the device
13162306a36Sopenharmony_ci * @from: the offset to read from
13262306a36Sopenharmony_ci * @len: the number of bytes to read
13362306a36Sopenharmony_ci * @retlen: the number of bytes actually read
13462306a36Sopenharmony_ci * @buf: the filled in buffer
13562306a36Sopenharmony_ci *
13662306a36Sopenharmony_ci * Returns 0 if read successful, or -ERRNO if an error occurred
13762306a36Sopenharmony_ci */
13862306a36Sopenharmony_cistatic int powernv_flash_read(struct mtd_info *mtd, loff_t from, size_t len,
13962306a36Sopenharmony_ci	     size_t *retlen, u_char *buf)
14062306a36Sopenharmony_ci{
14162306a36Sopenharmony_ci	return powernv_flash_async_op(mtd, FLASH_OP_READ, from,
14262306a36Sopenharmony_ci			len, retlen, buf);
14362306a36Sopenharmony_ci}
14462306a36Sopenharmony_ci
14562306a36Sopenharmony_ci/**
14662306a36Sopenharmony_ci * powernv_flash_write
14762306a36Sopenharmony_ci * @mtd: the device
14862306a36Sopenharmony_ci * @to: the offset to write to
14962306a36Sopenharmony_ci * @len: the number of bytes to write
15062306a36Sopenharmony_ci * @retlen: the number of bytes actually written
15162306a36Sopenharmony_ci * @buf: the buffer to get bytes from
15262306a36Sopenharmony_ci *
15362306a36Sopenharmony_ci * Returns 0 if write successful, -ERRNO if error occurred
15462306a36Sopenharmony_ci */
15562306a36Sopenharmony_cistatic int powernv_flash_write(struct mtd_info *mtd, loff_t to, size_t len,
15662306a36Sopenharmony_ci		     size_t *retlen, const u_char *buf)
15762306a36Sopenharmony_ci{
15862306a36Sopenharmony_ci	return powernv_flash_async_op(mtd, FLASH_OP_WRITE, to,
15962306a36Sopenharmony_ci			len, retlen, (u_char *)buf);
16062306a36Sopenharmony_ci}
16162306a36Sopenharmony_ci
16262306a36Sopenharmony_ci/**
16362306a36Sopenharmony_ci * powernv_flash_erase
16462306a36Sopenharmony_ci * @mtd: the device
16562306a36Sopenharmony_ci * @erase: the erase info
16662306a36Sopenharmony_ci * Returns 0 if erase successful or -ERRNO if an error occurred
16762306a36Sopenharmony_ci */
16862306a36Sopenharmony_cistatic int powernv_flash_erase(struct mtd_info *mtd, struct erase_info *erase)
16962306a36Sopenharmony_ci{
17062306a36Sopenharmony_ci	int rc;
17162306a36Sopenharmony_ci
17262306a36Sopenharmony_ci	rc =  powernv_flash_async_op(mtd, FLASH_OP_ERASE, erase->addr,
17362306a36Sopenharmony_ci			erase->len, NULL, NULL);
17462306a36Sopenharmony_ci	if (rc)
17562306a36Sopenharmony_ci		erase->fail_addr = erase->addr;
17662306a36Sopenharmony_ci
17762306a36Sopenharmony_ci	return rc;
17862306a36Sopenharmony_ci}
17962306a36Sopenharmony_ci
18062306a36Sopenharmony_ci/**
18162306a36Sopenharmony_ci * powernv_flash_set_driver_info - Fill the mtd_info structure and docg3
18262306a36Sopenharmony_ci * @dev: The device structure
18362306a36Sopenharmony_ci * @mtd: The structure to fill
18462306a36Sopenharmony_ci */
18562306a36Sopenharmony_cistatic int powernv_flash_set_driver_info(struct device *dev,
18662306a36Sopenharmony_ci		struct mtd_info *mtd)
18762306a36Sopenharmony_ci{
18862306a36Sopenharmony_ci	u64 size;
18962306a36Sopenharmony_ci	u32 erase_size;
19062306a36Sopenharmony_ci	int rc;
19162306a36Sopenharmony_ci
19262306a36Sopenharmony_ci	rc = of_property_read_u32(dev->of_node, "ibm,flash-block-size",
19362306a36Sopenharmony_ci			&erase_size);
19462306a36Sopenharmony_ci	if (rc) {
19562306a36Sopenharmony_ci		dev_err(dev, "couldn't get resource block size information\n");
19662306a36Sopenharmony_ci		return rc;
19762306a36Sopenharmony_ci	}
19862306a36Sopenharmony_ci
19962306a36Sopenharmony_ci	rc = of_property_read_u64(dev->of_node, "reg", &size);
20062306a36Sopenharmony_ci	if (rc) {
20162306a36Sopenharmony_ci		dev_err(dev, "couldn't get resource size information\n");
20262306a36Sopenharmony_ci		return rc;
20362306a36Sopenharmony_ci	}
20462306a36Sopenharmony_ci
20562306a36Sopenharmony_ci	/*
20662306a36Sopenharmony_ci	 * Going to have to check what details I need to set and how to
20762306a36Sopenharmony_ci	 * get them
20862306a36Sopenharmony_ci	 */
20962306a36Sopenharmony_ci	mtd->name = devm_kasprintf(dev, GFP_KERNEL, "%pOFP", dev->of_node);
21062306a36Sopenharmony_ci	mtd->type = MTD_NORFLASH;
21162306a36Sopenharmony_ci	mtd->flags = MTD_WRITEABLE;
21262306a36Sopenharmony_ci	mtd->size = size;
21362306a36Sopenharmony_ci	mtd->erasesize = erase_size;
21462306a36Sopenharmony_ci	mtd->writebufsize = mtd->writesize = 1;
21562306a36Sopenharmony_ci	mtd->owner = THIS_MODULE;
21662306a36Sopenharmony_ci	mtd->_erase = powernv_flash_erase;
21762306a36Sopenharmony_ci	mtd->_read = powernv_flash_read;
21862306a36Sopenharmony_ci	mtd->_write = powernv_flash_write;
21962306a36Sopenharmony_ci	mtd->dev.parent = dev;
22062306a36Sopenharmony_ci	mtd_set_of_node(mtd, dev->of_node);
22162306a36Sopenharmony_ci	return 0;
22262306a36Sopenharmony_ci}
22362306a36Sopenharmony_ci
22462306a36Sopenharmony_ci/**
22562306a36Sopenharmony_ci * powernv_flash_probe
22662306a36Sopenharmony_ci * @pdev: platform device
22762306a36Sopenharmony_ci *
22862306a36Sopenharmony_ci * Returns 0 on success, -ENOMEM, -ENXIO on error
22962306a36Sopenharmony_ci */
23062306a36Sopenharmony_cistatic int powernv_flash_probe(struct platform_device *pdev)
23162306a36Sopenharmony_ci{
23262306a36Sopenharmony_ci	struct device *dev = &pdev->dev;
23362306a36Sopenharmony_ci	struct powernv_flash *data;
23462306a36Sopenharmony_ci	int ret;
23562306a36Sopenharmony_ci
23662306a36Sopenharmony_ci	data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
23762306a36Sopenharmony_ci	if (!data)
23862306a36Sopenharmony_ci		return -ENOMEM;
23962306a36Sopenharmony_ci
24062306a36Sopenharmony_ci	data->mtd.priv = data;
24162306a36Sopenharmony_ci
24262306a36Sopenharmony_ci	ret = of_property_read_u32(dev->of_node, "ibm,opal-id", &(data->id));
24362306a36Sopenharmony_ci	if (ret) {
24462306a36Sopenharmony_ci		dev_err(dev, "no device property 'ibm,opal-id'\n");
24562306a36Sopenharmony_ci		return ret;
24662306a36Sopenharmony_ci	}
24762306a36Sopenharmony_ci
24862306a36Sopenharmony_ci	ret = powernv_flash_set_driver_info(dev, &data->mtd);
24962306a36Sopenharmony_ci	if (ret)
25062306a36Sopenharmony_ci		return ret;
25162306a36Sopenharmony_ci
25262306a36Sopenharmony_ci	dev_set_drvdata(dev, data);
25362306a36Sopenharmony_ci
25462306a36Sopenharmony_ci	/*
25562306a36Sopenharmony_ci	 * The current flash that skiboot exposes is one contiguous flash chip
25662306a36Sopenharmony_ci	 * with an ffs partition at the start, it should prove easier for users
25762306a36Sopenharmony_ci	 * to deal with partitions or not as they see fit
25862306a36Sopenharmony_ci	 */
25962306a36Sopenharmony_ci	return mtd_device_register(&data->mtd, NULL, 0);
26062306a36Sopenharmony_ci}
26162306a36Sopenharmony_ci
26262306a36Sopenharmony_ci/**
26362306a36Sopenharmony_ci * op_release - Release the driver
26462306a36Sopenharmony_ci * @pdev: the platform device
26562306a36Sopenharmony_ci *
26662306a36Sopenharmony_ci * Returns 0
26762306a36Sopenharmony_ci */
26862306a36Sopenharmony_cistatic int powernv_flash_release(struct platform_device *pdev)
26962306a36Sopenharmony_ci{
27062306a36Sopenharmony_ci	struct powernv_flash *data = dev_get_drvdata(&(pdev->dev));
27162306a36Sopenharmony_ci
27262306a36Sopenharmony_ci	/* All resources should be freed automatically */
27362306a36Sopenharmony_ci	WARN_ON(mtd_device_unregister(&data->mtd));
27462306a36Sopenharmony_ci
27562306a36Sopenharmony_ci	return 0;
27662306a36Sopenharmony_ci}
27762306a36Sopenharmony_ci
27862306a36Sopenharmony_cistatic const struct of_device_id powernv_flash_match[] = {
27962306a36Sopenharmony_ci	{ .compatible = "ibm,opal-flash" },
28062306a36Sopenharmony_ci	{}
28162306a36Sopenharmony_ci};
28262306a36Sopenharmony_ci
28362306a36Sopenharmony_cistatic struct platform_driver powernv_flash_driver = {
28462306a36Sopenharmony_ci	.driver		= {
28562306a36Sopenharmony_ci		.name		= "powernv_flash",
28662306a36Sopenharmony_ci		.of_match_table	= powernv_flash_match,
28762306a36Sopenharmony_ci	},
28862306a36Sopenharmony_ci	.remove		= powernv_flash_release,
28962306a36Sopenharmony_ci	.probe		= powernv_flash_probe,
29062306a36Sopenharmony_ci};
29162306a36Sopenharmony_ci
29262306a36Sopenharmony_cimodule_platform_driver(powernv_flash_driver);
29362306a36Sopenharmony_ci
29462306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, powernv_flash_match);
29562306a36Sopenharmony_ciMODULE_LICENSE("GPL");
29662306a36Sopenharmony_ciMODULE_AUTHOR("Cyril Bur <cyril.bur@au1.ibm.com>");
29762306a36Sopenharmony_ciMODULE_DESCRIPTION("MTD abstraction for OPAL flash");
298