275 lines
		
	
	
		
			7.8 KiB
		
	
	
	
		
			Diff
		
	
	
	
	
	
			
		
		
	
	
			275 lines
		
	
	
		
			7.8 KiB
		
	
	
	
		
			Diff
		
	
	
	
	
	
| From 9e8f208ad5229ddda97cd4a83ecf89c735d99592 Mon Sep 17 00:00:00 2001
 | |
| From: Horatiu Vultur <horatiu.vultur@microchip.com>
 | |
| Date: Fri, 16 Sep 2022 13:20:59 +0100
 | |
| Subject: [PATCH] nvmem: lan9662-otp: add support
 | |
| 
 | |
| Add support for OTP controller available on LAN9662. The OTPC controls
 | |
| the access to a non-volatile memory. The size of the memory is 8KB.
 | |
| The OTPC can access the memory based on an offset.
 | |
| Implement both the read and the write functionality.
 | |
| 
 | |
| Signed-off-by: Horatiu Vultur <horatiu.vultur@microchip.com>
 | |
| Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
 | |
| Link: https://lore.kernel.org/r/20220916122100.170016-13-srinivas.kandagatla@linaro.org
 | |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
 | |
| ---
 | |
|  drivers/nvmem/Kconfig        |   8 ++
 | |
|  drivers/nvmem/Makefile       |   2 +
 | |
|  drivers/nvmem/lan9662-otpc.c | 222 +++++++++++++++++++++++++++++++++++
 | |
|  3 files changed, 232 insertions(+)
 | |
|  create mode 100644 drivers/nvmem/lan9662-otpc.c
 | |
| 
 | |
| --- a/drivers/nvmem/Kconfig
 | |
| +++ b/drivers/nvmem/Kconfig
 | |
| @@ -98,6 +98,14 @@ config NVMEM_JZ4780_EFUSE
 | |
|  	  To compile this driver as a module, choose M here: the module
 | |
|  	  will be called nvmem_jz4780_efuse.
 | |
|  
 | |
| +config NVMEM_LAN9662_OTPC
 | |
| +	tristate "Microchip LAN9662 OTP controller support"
 | |
| +	depends on SOC_LAN966 || COMPILE_TEST
 | |
| +	depends on HAS_IOMEM
 | |
| +	help
 | |
| +	  This driver enables the OTP controller available on Microchip LAN9662
 | |
| +	  SoCs. It controls the access to the OTP memory connected to it.
 | |
| +
 | |
|  config NVMEM_LAYERSCAPE_SFP
 | |
|  	tristate "Layerscape SFP (Security Fuse Processor) support"
 | |
|  	depends on ARCH_LAYERSCAPE || COMPILE_TEST
 | |
| --- a/drivers/nvmem/Makefile
 | |
| +++ b/drivers/nvmem/Makefile
 | |
| @@ -21,6 +21,8 @@ obj-$(CONFIG_NVMEM_IMX_OCOTP_SCU)	+= nvm
 | |
|  nvmem-imx-ocotp-scu-y			:= imx-ocotp-scu.o
 | |
|  obj-$(CONFIG_NVMEM_JZ4780_EFUSE)	+= nvmem_jz4780_efuse.o
 | |
|  nvmem_jz4780_efuse-y			:= jz4780-efuse.o
 | |
| +obj-$(CONFIG_NVMEM_LAN9662_OTPC)	+= nvmem-lan9662-otpc.o
 | |
| +nvmem-lan9662-otpc-y		:= lan9662-otpc.o
 | |
|  obj-$(CONFIG_NVMEM_LAYERSCAPE_SFP)	+= nvmem-layerscape-sfp.o
 | |
|  nvmem-layerscape-sfp-y			:= layerscape-sfp.o
 | |
|  obj-$(CONFIG_NVMEM_LPC18XX_EEPROM)	+= nvmem_lpc18xx_eeprom.o
 | |
| --- /dev/null
 | |
| +++ b/drivers/nvmem/lan9662-otpc.c
 | |
| @@ -0,0 +1,222 @@
 | |
| +// SPDX-License-Identifier: GPL-2.0
 | |
| +
 | |
| +#include <linux/iopoll.h>
 | |
| +#include <linux/module.h>
 | |
| +#include <linux/nvmem-provider.h>
 | |
| +#include <linux/of.h>
 | |
| +#include <linux/platform_device.h>
 | |
| +
 | |
| +#define OTP_OTP_PWR_DN(t)			(t + 0x00)
 | |
| +#define OTP_OTP_PWR_DN_OTP_PWRDN_N		BIT(0)
 | |
| +#define OTP_OTP_ADDR_HI(t)			(t + 0x04)
 | |
| +#define OTP_OTP_ADDR_LO(t)			(t + 0x08)
 | |
| +#define OTP_OTP_PRGM_DATA(t)			(t + 0x10)
 | |
| +#define OTP_OTP_PRGM_MODE(t)			(t + 0x14)
 | |
| +#define OTP_OTP_PRGM_MODE_OTP_PGM_MODE_BYTE	BIT(0)
 | |
| +#define OTP_OTP_RD_DATA(t)			(t + 0x18)
 | |
| +#define OTP_OTP_FUNC_CMD(t)			(t + 0x20)
 | |
| +#define OTP_OTP_FUNC_CMD_OTP_PROGRAM		BIT(1)
 | |
| +#define OTP_OTP_FUNC_CMD_OTP_READ		BIT(0)
 | |
| +#define OTP_OTP_CMD_GO(t)			(t + 0x28)
 | |
| +#define OTP_OTP_CMD_GO_OTP_GO			BIT(0)
 | |
| +#define OTP_OTP_PASS_FAIL(t)			(t + 0x2c)
 | |
| +#define OTP_OTP_PASS_FAIL_OTP_READ_PROHIBITED	BIT(3)
 | |
| +#define OTP_OTP_PASS_FAIL_OTP_WRITE_PROHIBITED	BIT(2)
 | |
| +#define OTP_OTP_PASS_FAIL_OTP_FAIL		BIT(0)
 | |
| +#define OTP_OTP_STATUS(t)			(t + 0x30)
 | |
| +#define OTP_OTP_STATUS_OTP_CPUMPEN		BIT(1)
 | |
| +#define OTP_OTP_STATUS_OTP_BUSY			BIT(0)
 | |
| +
 | |
| +#define OTP_MEM_SIZE 8192
 | |
| +#define OTP_SLEEP_US 10
 | |
| +#define OTP_TIMEOUT_US 500000
 | |
| +
 | |
| +struct lan9662_otp {
 | |
| +	struct device *dev;
 | |
| +	void __iomem *base;
 | |
| +};
 | |
| +
 | |
| +static bool lan9662_otp_wait_flag_clear(void __iomem *reg, u32 flag)
 | |
| +{
 | |
| +	u32 val;
 | |
| +
 | |
| +	return readl_poll_timeout(reg, val, !(val & flag),
 | |
| +				  OTP_SLEEP_US, OTP_TIMEOUT_US);
 | |
| +}
 | |
| +
 | |
| +static int lan9662_otp_power(struct lan9662_otp *otp, bool up)
 | |
| +{
 | |
| +	void __iomem *pwrdn = OTP_OTP_PWR_DN(otp->base);
 | |
| +
 | |
| +	if (up) {
 | |
| +		writel(readl(pwrdn) & ~OTP_OTP_PWR_DN_OTP_PWRDN_N, pwrdn);
 | |
| +		if (lan9662_otp_wait_flag_clear(OTP_OTP_STATUS(otp->base),
 | |
| +						OTP_OTP_STATUS_OTP_CPUMPEN))
 | |
| +			return -ETIMEDOUT;
 | |
| +	} else {
 | |
| +		writel(readl(pwrdn) | OTP_OTP_PWR_DN_OTP_PWRDN_N, pwrdn);
 | |
| +	}
 | |
| +
 | |
| +	return 0;
 | |
| +}
 | |
| +
 | |
| +static int lan9662_otp_execute(struct lan9662_otp *otp)
 | |
| +{
 | |
| +	if (lan9662_otp_wait_flag_clear(OTP_OTP_CMD_GO(otp->base),
 | |
| +					OTP_OTP_CMD_GO_OTP_GO))
 | |
| +		return -ETIMEDOUT;
 | |
| +
 | |
| +	if (lan9662_otp_wait_flag_clear(OTP_OTP_STATUS(otp->base),
 | |
| +					OTP_OTP_STATUS_OTP_BUSY))
 | |
| +		return -ETIMEDOUT;
 | |
| +
 | |
| +	return 0;
 | |
| +}
 | |
| +
 | |
| +static void lan9662_otp_set_address(struct lan9662_otp *otp, u32 offset)
 | |
| +{
 | |
| +	writel(0xff & (offset >> 8), OTP_OTP_ADDR_HI(otp->base));
 | |
| +	writel(0xff & offset, OTP_OTP_ADDR_LO(otp->base));
 | |
| +}
 | |
| +
 | |
| +static int lan9662_otp_read_byte(struct lan9662_otp *otp, u32 offset, u8 *dst)
 | |
| +{
 | |
| +	u32 pass;
 | |
| +	int rc;
 | |
| +
 | |
| +	lan9662_otp_set_address(otp, offset);
 | |
| +	writel(OTP_OTP_FUNC_CMD_OTP_READ, OTP_OTP_FUNC_CMD(otp->base));
 | |
| +	writel(OTP_OTP_CMD_GO_OTP_GO, OTP_OTP_CMD_GO(otp->base));
 | |
| +	rc = lan9662_otp_execute(otp);
 | |
| +	if (!rc) {
 | |
| +		pass = readl(OTP_OTP_PASS_FAIL(otp->base));
 | |
| +		if (pass & OTP_OTP_PASS_FAIL_OTP_READ_PROHIBITED)
 | |
| +			return -EACCES;
 | |
| +		*dst = (u8) readl(OTP_OTP_RD_DATA(otp->base));
 | |
| +	}
 | |
| +	return rc;
 | |
| +}
 | |
| +
 | |
| +static int lan9662_otp_write_byte(struct lan9662_otp *otp, u32 offset, u8 data)
 | |
| +{
 | |
| +	u32 pass;
 | |
| +	int rc;
 | |
| +
 | |
| +	lan9662_otp_set_address(otp, offset);
 | |
| +	writel(OTP_OTP_PRGM_MODE_OTP_PGM_MODE_BYTE, OTP_OTP_PRGM_MODE(otp->base));
 | |
| +	writel(data, OTP_OTP_PRGM_DATA(otp->base));
 | |
| +	writel(OTP_OTP_FUNC_CMD_OTP_PROGRAM, OTP_OTP_FUNC_CMD(otp->base));
 | |
| +	writel(OTP_OTP_CMD_GO_OTP_GO, OTP_OTP_CMD_GO(otp->base));
 | |
| +
 | |
| +	rc = lan9662_otp_execute(otp);
 | |
| +	if (!rc) {
 | |
| +		pass = readl(OTP_OTP_PASS_FAIL(otp->base));
 | |
| +		if (pass & OTP_OTP_PASS_FAIL_OTP_WRITE_PROHIBITED)
 | |
| +			return -EACCES;
 | |
| +		if (pass & OTP_OTP_PASS_FAIL_OTP_FAIL)
 | |
| +			return -EIO;
 | |
| +	}
 | |
| +	return rc;
 | |
| +}
 | |
| +
 | |
| +static int lan9662_otp_read(void *context, unsigned int offset,
 | |
| +			    void *_val, size_t bytes)
 | |
| +{
 | |
| +	struct lan9662_otp *otp = context;
 | |
| +	u8 *val = _val;
 | |
| +	uint8_t data;
 | |
| +	int i, rc = 0;
 | |
| +
 | |
| +	lan9662_otp_power(otp, true);
 | |
| +	for (i = 0; i < bytes; i++) {
 | |
| +		rc = lan9662_otp_read_byte(otp, offset + i, &data);
 | |
| +		if (rc < 0)
 | |
| +			break;
 | |
| +		*val++ = data;
 | |
| +	}
 | |
| +	lan9662_otp_power(otp, false);
 | |
| +
 | |
| +	return rc;
 | |
| +}
 | |
| +
 | |
| +static int lan9662_otp_write(void *context, unsigned int offset,
 | |
| +			     void *_val, size_t bytes)
 | |
| +{
 | |
| +	struct lan9662_otp *otp = context;
 | |
| +	u8 *val = _val;
 | |
| +	u8 data, newdata;
 | |
| +	int i, rc = 0;
 | |
| +
 | |
| +	lan9662_otp_power(otp, true);
 | |
| +	for (i = 0; i < bytes; i++) {
 | |
| +		/* Skip zero bytes */
 | |
| +		if (val[i]) {
 | |
| +			rc = lan9662_otp_read_byte(otp, offset + i, &data);
 | |
| +			if (rc < 0)
 | |
| +				break;
 | |
| +
 | |
| +			newdata = data | val[i];
 | |
| +			if (newdata == data)
 | |
| +				continue;
 | |
| +
 | |
| +			rc = lan9662_otp_write_byte(otp, offset + i,
 | |
| +						      newdata);
 | |
| +			if (rc < 0)
 | |
| +				break;
 | |
| +		}
 | |
| +	}
 | |
| +	lan9662_otp_power(otp, false);
 | |
| +
 | |
| +	return rc;
 | |
| +}
 | |
| +
 | |
| +static struct nvmem_config otp_config = {
 | |
| +	.name = "lan9662-otp",
 | |
| +	.stride = 1,
 | |
| +	.word_size = 1,
 | |
| +	.reg_read = lan9662_otp_read,
 | |
| +	.reg_write = lan9662_otp_write,
 | |
| +	.size = OTP_MEM_SIZE,
 | |
| +};
 | |
| +
 | |
| +static int lan9662_otp_probe(struct platform_device *pdev)
 | |
| +{
 | |
| +	struct device *dev = &pdev->dev;
 | |
| +	struct nvmem_device *nvmem;
 | |
| +	struct lan9662_otp *otp;
 | |
| +
 | |
| +	otp = devm_kzalloc(&pdev->dev, sizeof(*otp), GFP_KERNEL);
 | |
| +	if (!otp)
 | |
| +		return -ENOMEM;
 | |
| +
 | |
| +	otp->dev = dev;
 | |
| +	otp->base = devm_platform_ioremap_resource(pdev, 0);
 | |
| +	if (IS_ERR(otp->base))
 | |
| +		return PTR_ERR(otp->base);
 | |
| +
 | |
| +	otp_config.priv = otp;
 | |
| +	otp_config.dev = dev;
 | |
| +
 | |
| +	nvmem = devm_nvmem_register(dev, &otp_config);
 | |
| +
 | |
| +	return PTR_ERR_OR_ZERO(nvmem);
 | |
| +}
 | |
| +
 | |
| +static const struct of_device_id lan9662_otp_match[] = {
 | |
| +	{ .compatible = "microchip,lan9662-otp", },
 | |
| +	{ },
 | |
| +};
 | |
| +MODULE_DEVICE_TABLE(of, lan9662_otp_match);
 | |
| +
 | |
| +static struct platform_driver lan9662_otp_driver = {
 | |
| +	.probe = lan9662_otp_probe,
 | |
| +	.driver = {
 | |
| +		.name = "lan9662-otp",
 | |
| +		.of_match_table = lan9662_otp_match,
 | |
| +	},
 | |
| +};
 | |
| +module_platform_driver(lan9662_otp_driver);
 | |
| +
 | |
| +MODULE_AUTHOR("Horatiu Vultur <horatiu.vultur@microchip.com>");
 | |
| +MODULE_DESCRIPTION("lan9662 OTP driver");
 | |
| +MODULE_LICENSE("GPL");
 | 
