| /* | 
 |  * i.MX6 OCOTP fusebox driver | 
 |  * | 
 |  * Copyright (c) 2015 Pengutronix, Philipp Zabel <p.zabel@pengutronix.de> | 
 |  * | 
 |  * Based on the barebox ocotp driver, | 
 |  * Copyright (c) 2010 Baruch Siach <baruch@tkos.co.il>, | 
 |  *	Orex Computed Radiography | 
 |  * | 
 |  * 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. | 
 |  * | 
 |  * http://www.opensource.org/licenses/gpl-license.html | 
 |  * http://www.gnu.org/copyleft/gpl.html | 
 |  */ | 
 |  | 
 | #include <linux/clk.h> | 
 | #include <linux/device.h> | 
 | #include <linux/io.h> | 
 | #include <linux/module.h> | 
 | #include <linux/nvmem-provider.h> | 
 | #include <linux/of.h> | 
 | #include <linux/of_device.h> | 
 | #include <linux/platform_device.h> | 
 | #include <linux/slab.h> | 
 | #include <soc/imx8/sc/sci.h> | 
 |  | 
 | enum ocotp_devtype { | 
 | 	IMX8QM, | 
 | 	IMX8QXP, | 
 | }; | 
 |  | 
 | struct ocotp_devtype_data { | 
 | 	int devtype; | 
 | 	int nregs; | 
 | }; | 
 |  | 
 | struct ocotp_priv { | 
 | 	struct device *dev; | 
 | 	struct ocotp_devtype_data *data; | 
 | 	sc_ipc_t nvmem_ipc; | 
 | }; | 
 |  | 
 | static struct ocotp_devtype_data imx8qm_data = { | 
 | 	.devtype = IMX8QM, | 
 | 	.nregs = 800, | 
 | }; | 
 |  | 
 | static struct ocotp_devtype_data imx8qxp_data = { | 
 | 	.devtype = IMX8QXP, | 
 | 	.nregs = 800, | 
 | }; | 
 |  | 
 | static int imx_scu_ocotp_read(void *context, unsigned int offset, | 
 | 			      void *val, size_t bytes) | 
 | { | 
 | 	struct ocotp_priv *priv = context; | 
 | 	sc_err_t sciErr = SC_ERR_NONE; | 
 | 	unsigned int count; | 
 | 	u32 index; | 
 | 	u32 num_bytes; | 
 | 	int i; | 
 | 	u8 *buf, *p; | 
 |  | 
 | 	index = offset >> 2; | 
 | 	num_bytes = round_up((offset % 4) + bytes, 4); | 
 | 	count = num_bytes >> 2; | 
 |  | 
 | 	if (count > (priv->data->nregs - index)) | 
 | 		count = priv->data->nregs - index; | 
 |  | 
 | 	p = kzalloc(num_bytes, GFP_KERNEL); | 
 | 	if (!p) | 
 | 		return -ENOMEM; | 
 |  | 
 | 	buf = p; | 
 |  | 
 | 	for (i = index; i < (index + count); i++) { | 
 | 		if (priv->data->devtype == IMX8QXP) { | 
 | 			if ((i > 271) && (i < 544)) { | 
 | 				*(u32 *)buf = 0; | 
 | 				buf += 4; | 
 | 				continue; | 
 | 			} | 
 | 		} | 
 |  | 
 | 		sciErr = sc_misc_otp_fuse_read(priv->nvmem_ipc, i, (u32 *)buf); | 
 | 		if (sciErr != SC_ERR_NONE) { | 
 | 			kfree(p); | 
 | 			return -EIO; | 
 | 		} | 
 | 		buf += 4; | 
 | 	} | 
 |  | 
 | 	index = offset % 4; | 
 | 	memcpy(val, &p[index], bytes); | 
 |  | 
 | 	kfree(p); | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static struct nvmem_config imx_scu_ocotp_nvmem_config = { | 
 | 	.name = "imx-ocotp", | 
 | 	.read_only = true, | 
 | 	.word_size = 4, | 
 | 	.stride = 1, | 
 | 	.owner = THIS_MODULE, | 
 | 	.reg_read = imx_scu_ocotp_read, | 
 | }; | 
 |  | 
 | static const struct of_device_id imx_scu_ocotp_dt_ids[] = { | 
 | 	{ .compatible = "fsl,imx8qm-ocotp", (void *)&imx8qm_data }, | 
 | 	{ .compatible = "fsl,imx8qxp-ocotp", (void *)&imx8qxp_data }, | 
 | 	{ }, | 
 | }; | 
 | MODULE_DEVICE_TABLE(of, imx_scu_ocotp_dt_ids); | 
 |  | 
 | static int imx_scu_ocotp_probe(struct platform_device *pdev) | 
 | { | 
 | 	const struct of_device_id *of_id; | 
 | 	struct device *dev = &pdev->dev; | 
 | 	struct ocotp_priv *priv; | 
 | 	struct nvmem_device *nvmem; | 
 | 	uint32_t mu_id; | 
 | 	sc_err_t sciErr = SC_ERR_NONE; | 
 |  | 
 | 	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); | 
 | 	if (!priv) | 
 | 		return -ENOMEM; | 
 |  | 
 | 	sciErr = sc_ipc_getMuID(&mu_id); | 
 | 	if (sciErr != SC_ERR_NONE) { | 
 | 		pr_info("pinctrl: Cannot obtain MU ID\n"); | 
 | 		return -EIO; | 
 | 	} | 
 |  | 
 | 	sciErr = sc_ipc_open(&priv->nvmem_ipc, mu_id); | 
 |  | 
 | 	if (sciErr != SC_ERR_NONE) { | 
 | 		pr_info("pinctrl: Cannot open MU channel to SCU\n"); | 
 | 		return -EIO; | 
 | 	}; | 
 |  | 
 | 	of_id = of_match_device(imx_scu_ocotp_dt_ids, dev); | 
 | 	priv->data = (struct ocotp_devtype_data *)of_id->data; | 
 | 	priv->dev = dev; | 
 | 	imx_scu_ocotp_nvmem_config.size = 4 * priv->data->nregs; | 
 | 	imx_scu_ocotp_nvmem_config.dev = dev; | 
 | 	imx_scu_ocotp_nvmem_config.priv = priv; | 
 | 	nvmem = nvmem_register(&imx_scu_ocotp_nvmem_config); | 
 | 	if (IS_ERR(nvmem)) | 
 | 		return PTR_ERR(nvmem); | 
 |  | 
 | 	platform_set_drvdata(pdev, nvmem); | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int imx_scu_ocotp_remove(struct platform_device *pdev) | 
 | { | 
 | 	struct nvmem_device *nvmem = platform_get_drvdata(pdev); | 
 |  | 
 | 	return nvmem_unregister(nvmem); | 
 | } | 
 |  | 
 | static struct platform_driver imx_scu_ocotp_driver = { | 
 | 	.probe	= imx_scu_ocotp_probe, | 
 | 	.remove	= imx_scu_ocotp_remove, | 
 | 	.driver = { | 
 | 		.name	= "imx_scu_ocotp", | 
 | 		.of_match_table = imx_scu_ocotp_dt_ids, | 
 | 	}, | 
 | }; | 
 | module_platform_driver(imx_scu_ocotp_driver); | 
 |  | 
 | MODULE_AUTHOR("Peng Fan <peng.fan@nxp.com>"); | 
 | MODULE_DESCRIPTION("i.MX8QM OCOTP fuse box driver"); | 
 | MODULE_LICENSE("GPL v2"); |