442 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			442 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| // SPDX-License-Identifier: GPL-2.0
 | |
| // Copyright (C) 2019 Spreadtrum Communications Inc.
 | |
| 
 | |
| #include <linux/clk.h>
 | |
| #include <linux/delay.h>
 | |
| #include <linux/hwspinlock.h>
 | |
| #include <linux/io.h>
 | |
| #include <linux/module.h>
 | |
| #include <linux/nvmem-provider.h>
 | |
| #include <linux/of_device.h>
 | |
| #include <linux/platform_device.h>
 | |
| 
 | |
| #define SPRD_EFUSE_ENABLE		0x20
 | |
| #define SPRD_EFUSE_ERR_FLAG		0x24
 | |
| #define SPRD_EFUSE_ERR_CLR		0x28
 | |
| #define SPRD_EFUSE_MAGIC_NUM		0x2c
 | |
| #define SPRD_EFUSE_FW_CFG		0x50
 | |
| #define SPRD_EFUSE_PW_SWT		0x54
 | |
| #define SPRD_EFUSE_MEM(val)		(0x1000 + ((val) << 2))
 | |
| 
 | |
| #define SPRD_EFUSE_VDD_EN		BIT(0)
 | |
| #define SPRD_EFUSE_AUTO_CHECK_EN	BIT(1)
 | |
| #define SPRD_EFUSE_DOUBLE_EN		BIT(2)
 | |
| #define SPRD_EFUSE_MARGIN_RD_EN		BIT(3)
 | |
| #define SPRD_EFUSE_LOCK_WR_EN		BIT(4)
 | |
| 
 | |
| #define SPRD_EFUSE_ERR_CLR_MASK		GENMASK(13, 0)
 | |
| 
 | |
| #define SPRD_EFUSE_ENK1_ON		BIT(0)
 | |
| #define SPRD_EFUSE_ENK2_ON		BIT(1)
 | |
| #define SPRD_EFUSE_PROG_EN		BIT(2)
 | |
| 
 | |
| #define SPRD_EFUSE_MAGIC_NUMBER		0x8810
 | |
| 
 | |
| /* Block width (bytes) definitions */
 | |
| #define SPRD_EFUSE_BLOCK_WIDTH		4
 | |
| 
 | |
| /*
 | |
|  * The Spreadtrum AP efuse contains 2 parts: normal efuse and secure efuse,
 | |
|  * and we can only access the normal efuse in kernel. So define the normal
 | |
|  * block offset index and normal block numbers.
 | |
|  */
 | |
| #define SPRD_EFUSE_NORMAL_BLOCK_NUMS	24
 | |
| #define SPRD_EFUSE_NORMAL_BLOCK_OFFSET	72
 | |
| 
 | |
| /* Timeout (ms) for the trylock of hardware spinlocks */
 | |
| #define SPRD_EFUSE_HWLOCK_TIMEOUT	5000
 | |
| 
 | |
| /*
 | |
|  * Since different Spreadtrum SoC chip can have different normal block numbers
 | |
|  * and offset. And some SoC can support block double feature, which means
 | |
|  * when reading or writing data to efuse memory, the controller can save double
 | |
|  * data in case one data become incorrect after a long period.
 | |
|  *
 | |
|  * Thus we should save them in the device data structure.
 | |
|  */
 | |
| struct sprd_efuse_variant_data {
 | |
| 	u32 blk_nums;
 | |
| 	u32 blk_offset;
 | |
| 	bool blk_double;
 | |
| };
 | |
| 
 | |
| struct sprd_efuse {
 | |
| 	struct device *dev;
 | |
| 	struct clk *clk;
 | |
| 	struct hwspinlock *hwlock;
 | |
| 	struct mutex mutex;
 | |
| 	void __iomem *base;
 | |
| 	const struct sprd_efuse_variant_data *data;
 | |
| };
 | |
| 
 | |
| static const struct sprd_efuse_variant_data ums312_data = {
 | |
| 	.blk_nums = SPRD_EFUSE_NORMAL_BLOCK_NUMS,
 | |
| 	.blk_offset = SPRD_EFUSE_NORMAL_BLOCK_OFFSET,
 | |
| 	.blk_double = false,
 | |
| };
 | |
| 
 | |
| /*
 | |
|  * On Spreadtrum platform, we have multi-subsystems will access the unique
 | |
|  * efuse controller, so we need one hardware spinlock to synchronize between
 | |
|  * the multiple subsystems.
 | |
|  */
 | |
| static int sprd_efuse_lock(struct sprd_efuse *efuse)
 | |
| {
 | |
| 	int ret;
 | |
| 
 | |
| 	mutex_lock(&efuse->mutex);
 | |
| 
 | |
| 	ret = hwspin_lock_timeout_raw(efuse->hwlock,
 | |
| 				      SPRD_EFUSE_HWLOCK_TIMEOUT);
 | |
| 	if (ret) {
 | |
| 		dev_err(efuse->dev, "timeout get the hwspinlock\n");
 | |
| 		mutex_unlock(&efuse->mutex);
 | |
| 		return ret;
 | |
| 	}
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static void sprd_efuse_unlock(struct sprd_efuse *efuse)
 | |
| {
 | |
| 	hwspin_unlock_raw(efuse->hwlock);
 | |
| 	mutex_unlock(&efuse->mutex);
 | |
| }
 | |
| 
 | |
| static void sprd_efuse_set_prog_power(struct sprd_efuse *efuse, bool en)
 | |
| {
 | |
| 	u32 val = readl(efuse->base + SPRD_EFUSE_PW_SWT);
 | |
| 
 | |
| 	if (en)
 | |
| 		val &= ~SPRD_EFUSE_ENK2_ON;
 | |
| 	else
 | |
| 		val &= ~SPRD_EFUSE_ENK1_ON;
 | |
| 
 | |
| 	writel(val, efuse->base + SPRD_EFUSE_PW_SWT);
 | |
| 
 | |
| 	/* Open or close efuse power need wait 1000us to make power stable. */
 | |
| 	usleep_range(1000, 1200);
 | |
| 
 | |
| 	if (en)
 | |
| 		val |= SPRD_EFUSE_ENK1_ON;
 | |
| 	else
 | |
| 		val |= SPRD_EFUSE_ENK2_ON;
 | |
| 
 | |
| 	writel(val, efuse->base + SPRD_EFUSE_PW_SWT);
 | |
| 
 | |
| 	/* Open or close efuse power need wait 1000us to make power stable. */
 | |
| 	usleep_range(1000, 1200);
 | |
| }
 | |
| 
 | |
| static void sprd_efuse_set_read_power(struct sprd_efuse *efuse, bool en)
 | |
| {
 | |
| 	u32 val = readl(efuse->base + SPRD_EFUSE_ENABLE);
 | |
| 
 | |
| 	if (en)
 | |
| 		val |= SPRD_EFUSE_VDD_EN;
 | |
| 	else
 | |
| 		val &= ~SPRD_EFUSE_VDD_EN;
 | |
| 
 | |
| 	writel(val, efuse->base + SPRD_EFUSE_ENABLE);
 | |
| 
 | |
| 	/* Open or close efuse power need wait 1000us to make power stable. */
 | |
| 	usleep_range(1000, 1200);
 | |
| }
 | |
| 
 | |
| static void sprd_efuse_set_prog_lock(struct sprd_efuse *efuse, bool en)
 | |
| {
 | |
| 	u32 val = readl(efuse->base + SPRD_EFUSE_ENABLE);
 | |
| 
 | |
| 	if (en)
 | |
| 		val |= SPRD_EFUSE_LOCK_WR_EN;
 | |
| 	else
 | |
| 		val &= ~SPRD_EFUSE_LOCK_WR_EN;
 | |
| 
 | |
| 	writel(val, efuse->base + SPRD_EFUSE_ENABLE);
 | |
| }
 | |
| 
 | |
| static void sprd_efuse_set_auto_check(struct sprd_efuse *efuse, bool en)
 | |
| {
 | |
| 	u32 val = readl(efuse->base + SPRD_EFUSE_ENABLE);
 | |
| 
 | |
| 	if (en)
 | |
| 		val |= SPRD_EFUSE_AUTO_CHECK_EN;
 | |
| 	else
 | |
| 		val &= ~SPRD_EFUSE_AUTO_CHECK_EN;
 | |
| 
 | |
| 	writel(val, efuse->base + SPRD_EFUSE_ENABLE);
 | |
| }
 | |
| 
 | |
| static void sprd_efuse_set_data_double(struct sprd_efuse *efuse, bool en)
 | |
| {
 | |
| 	u32 val = readl(efuse->base + SPRD_EFUSE_ENABLE);
 | |
| 
 | |
| 	if (en)
 | |
| 		val |= SPRD_EFUSE_DOUBLE_EN;
 | |
| 	else
 | |
| 		val &= ~SPRD_EFUSE_DOUBLE_EN;
 | |
| 
 | |
| 	writel(val, efuse->base + SPRD_EFUSE_ENABLE);
 | |
| }
 | |
| 
 | |
| static void sprd_efuse_set_prog_en(struct sprd_efuse *efuse, bool en)
 | |
| {
 | |
| 	u32 val = readl(efuse->base + SPRD_EFUSE_PW_SWT);
 | |
| 
 | |
| 	if (en)
 | |
| 		val |= SPRD_EFUSE_PROG_EN;
 | |
| 	else
 | |
| 		val &= ~SPRD_EFUSE_PROG_EN;
 | |
| 
 | |
| 	writel(val, efuse->base + SPRD_EFUSE_PW_SWT);
 | |
| }
 | |
| 
 | |
| static int sprd_efuse_raw_prog(struct sprd_efuse *efuse, u32 blk, bool doub,
 | |
| 			       bool lock, u32 *data)
 | |
| {
 | |
| 	u32 status;
 | |
| 	int ret = 0;
 | |
| 
 | |
| 	/*
 | |
| 	 * We need set the correct magic number before writing the efuse to
 | |
| 	 * allow programming, and block other programming until we clear the
 | |
| 	 * magic number.
 | |
| 	 */
 | |
| 	writel(SPRD_EFUSE_MAGIC_NUMBER,
 | |
| 	       efuse->base + SPRD_EFUSE_MAGIC_NUM);
 | |
| 
 | |
| 	/*
 | |
| 	 * Power on the efuse, enable programme and enable double data
 | |
| 	 * if asked.
 | |
| 	 */
 | |
| 	sprd_efuse_set_prog_power(efuse, true);
 | |
| 	sprd_efuse_set_prog_en(efuse, true);
 | |
| 	sprd_efuse_set_data_double(efuse, doub);
 | |
| 
 | |
| 	/*
 | |
| 	 * Enable the auto-check function to validate if the programming is
 | |
| 	 * successful.
 | |
| 	 */
 | |
| 	if (lock)
 | |
| 		sprd_efuse_set_auto_check(efuse, true);
 | |
| 
 | |
| 	writel(*data, efuse->base + SPRD_EFUSE_MEM(blk));
 | |
| 
 | |
| 	/* Disable auto-check and data double after programming */
 | |
| 	if (lock)
 | |
| 		sprd_efuse_set_auto_check(efuse, false);
 | |
| 	sprd_efuse_set_data_double(efuse, false);
 | |
| 
 | |
| 	/*
 | |
| 	 * Check the efuse error status, if the programming is successful,
 | |
| 	 * we should lock this efuse block to avoid programming again.
 | |
| 	 */
 | |
| 	status = readl(efuse->base + SPRD_EFUSE_ERR_FLAG);
 | |
| 	if (status) {
 | |
| 		dev_err(efuse->dev,
 | |
| 			"write error status %u of block %d\n", status, blk);
 | |
| 
 | |
| 		writel(SPRD_EFUSE_ERR_CLR_MASK,
 | |
| 		       efuse->base + SPRD_EFUSE_ERR_CLR);
 | |
| 		ret = -EBUSY;
 | |
| 	} else if (lock) {
 | |
| 		sprd_efuse_set_prog_lock(efuse, lock);
 | |
| 		writel(0, efuse->base + SPRD_EFUSE_MEM(blk));
 | |
| 		sprd_efuse_set_prog_lock(efuse, false);
 | |
| 	}
 | |
| 
 | |
| 	sprd_efuse_set_prog_power(efuse, false);
 | |
| 	writel(0, efuse->base + SPRD_EFUSE_MAGIC_NUM);
 | |
| 
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| static int sprd_efuse_raw_read(struct sprd_efuse *efuse, int blk, u32 *val,
 | |
| 			       bool doub)
 | |
| {
 | |
| 	u32 status;
 | |
| 
 | |
| 	/*
 | |
| 	 * Need power on the efuse before reading data from efuse, and will
 | |
| 	 * power off the efuse after reading process.
 | |
| 	 */
 | |
| 	sprd_efuse_set_read_power(efuse, true);
 | |
| 
 | |
| 	/* Enable double data if asked */
 | |
| 	sprd_efuse_set_data_double(efuse, doub);
 | |
| 
 | |
| 	/* Start to read data from efuse block */
 | |
| 	*val = readl(efuse->base + SPRD_EFUSE_MEM(blk));
 | |
| 
 | |
| 	/* Disable double data */
 | |
| 	sprd_efuse_set_data_double(efuse, false);
 | |
| 
 | |
| 	/* Power off the efuse */
 | |
| 	sprd_efuse_set_read_power(efuse, false);
 | |
| 
 | |
| 	/*
 | |
| 	 * Check the efuse error status and clear them if there are some
 | |
| 	 * errors occurred.
 | |
| 	 */
 | |
| 	status = readl(efuse->base + SPRD_EFUSE_ERR_FLAG);
 | |
| 	if (status) {
 | |
| 		dev_err(efuse->dev,
 | |
| 			"read error status %d of block %d\n", status, blk);
 | |
| 
 | |
| 		writel(SPRD_EFUSE_ERR_CLR_MASK,
 | |
| 		       efuse->base + SPRD_EFUSE_ERR_CLR);
 | |
| 		return -EBUSY;
 | |
| 	}
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int sprd_efuse_read(void *context, u32 offset, void *val, size_t bytes)
 | |
| {
 | |
| 	struct sprd_efuse *efuse = context;
 | |
| 	bool blk_double = efuse->data->blk_double;
 | |
| 	u32 index = offset / SPRD_EFUSE_BLOCK_WIDTH + efuse->data->blk_offset;
 | |
| 	u32 blk_offset = (offset % SPRD_EFUSE_BLOCK_WIDTH) * BITS_PER_BYTE;
 | |
| 	u32 data;
 | |
| 	int ret;
 | |
| 
 | |
| 	ret = sprd_efuse_lock(efuse);
 | |
| 	if (ret)
 | |
| 		return ret;
 | |
| 
 | |
| 	ret = clk_prepare_enable(efuse->clk);
 | |
| 	if (ret)
 | |
| 		goto unlock;
 | |
| 
 | |
| 	ret = sprd_efuse_raw_read(efuse, index, &data, blk_double);
 | |
| 	if (!ret) {
 | |
| 		data >>= blk_offset;
 | |
| 		memcpy(val, &data, bytes);
 | |
| 	}
 | |
| 
 | |
| 	clk_disable_unprepare(efuse->clk);
 | |
| 
 | |
| unlock:
 | |
| 	sprd_efuse_unlock(efuse);
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| static int sprd_efuse_write(void *context, u32 offset, void *val, size_t bytes)
 | |
| {
 | |
| 	struct sprd_efuse *efuse = context;
 | |
| 	bool blk_double = efuse->data->blk_double;
 | |
| 	bool lock;
 | |
| 	int ret;
 | |
| 
 | |
| 	ret = sprd_efuse_lock(efuse);
 | |
| 	if (ret)
 | |
| 		return ret;
 | |
| 
 | |
| 	ret = clk_prepare_enable(efuse->clk);
 | |
| 	if (ret)
 | |
| 		goto unlock;
 | |
| 
 | |
| 	/*
 | |
| 	 * If the writing bytes are equal with the block width, which means the
 | |
| 	 * whole block will be programmed. For this case, we should not allow
 | |
| 	 * this block to be programmed again by locking this block.
 | |
| 	 *
 | |
| 	 * If the block was programmed partially, we should allow this block to
 | |
| 	 * be programmed again.
 | |
| 	 */
 | |
| 	if (bytes < SPRD_EFUSE_BLOCK_WIDTH)
 | |
| 		lock = false;
 | |
| 	else
 | |
| 		lock = true;
 | |
| 
 | |
| 	ret = sprd_efuse_raw_prog(efuse, offset, blk_double, lock, val);
 | |
| 
 | |
| 	clk_disable_unprepare(efuse->clk);
 | |
| 
 | |
| unlock:
 | |
| 	sprd_efuse_unlock(efuse);
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| static int sprd_efuse_probe(struct platform_device *pdev)
 | |
| {
 | |
| 	struct device_node *np = pdev->dev.of_node;
 | |
| 	struct nvmem_device *nvmem;
 | |
| 	struct nvmem_config econfig = { };
 | |
| 	struct sprd_efuse *efuse;
 | |
| 	const struct sprd_efuse_variant_data *pdata;
 | |
| 	int ret;
 | |
| 
 | |
| 	pdata = of_device_get_match_data(&pdev->dev);
 | |
| 	if (!pdata) {
 | |
| 		dev_err(&pdev->dev, "No matching driver data found\n");
 | |
| 		return -EINVAL;
 | |
| 	}
 | |
| 
 | |
| 	efuse = devm_kzalloc(&pdev->dev, sizeof(*efuse), GFP_KERNEL);
 | |
| 	if (!efuse)
 | |
| 		return -ENOMEM;
 | |
| 
 | |
| 	efuse->base = devm_platform_ioremap_resource(pdev, 0);
 | |
| 	if (IS_ERR(efuse->base))
 | |
| 		return PTR_ERR(efuse->base);
 | |
| 
 | |
| 	ret = of_hwspin_lock_get_id(np, 0);
 | |
| 	if (ret < 0) {
 | |
| 		dev_err(&pdev->dev, "failed to get hwlock id\n");
 | |
| 		return ret;
 | |
| 	}
 | |
| 
 | |
| 	efuse->hwlock = devm_hwspin_lock_request_specific(&pdev->dev, ret);
 | |
| 	if (!efuse->hwlock) {
 | |
| 		dev_err(&pdev->dev, "failed to request hwlock\n");
 | |
| 		return -ENXIO;
 | |
| 	}
 | |
| 
 | |
| 	efuse->clk = devm_clk_get(&pdev->dev, "enable");
 | |
| 	if (IS_ERR(efuse->clk)) {
 | |
| 		dev_err(&pdev->dev, "failed to get enable clock\n");
 | |
| 		return PTR_ERR(efuse->clk);
 | |
| 	}
 | |
| 
 | |
| 	mutex_init(&efuse->mutex);
 | |
| 	efuse->dev = &pdev->dev;
 | |
| 	efuse->data = pdata;
 | |
| 
 | |
| 	econfig.stride = 1;
 | |
| 	econfig.word_size = 1;
 | |
| 	econfig.read_only = false;
 | |
| 	econfig.name = "sprd-efuse";
 | |
| 	econfig.size = efuse->data->blk_nums * SPRD_EFUSE_BLOCK_WIDTH;
 | |
| 	econfig.reg_read = sprd_efuse_read;
 | |
| 	econfig.reg_write = sprd_efuse_write;
 | |
| 	econfig.priv = efuse;
 | |
| 	econfig.dev = &pdev->dev;
 | |
| 	nvmem = devm_nvmem_register(&pdev->dev, &econfig);
 | |
| 	if (IS_ERR(nvmem)) {
 | |
| 		dev_err(&pdev->dev, "failed to register nvmem\n");
 | |
| 		return PTR_ERR(nvmem);
 | |
| 	}
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static const struct of_device_id sprd_efuse_of_match[] = {
 | |
| 	{ .compatible = "sprd,ums312-efuse", .data = &ums312_data },
 | |
| 	{ }
 | |
| };
 | |
| 
 | |
| static struct platform_driver sprd_efuse_driver = {
 | |
| 	.probe = sprd_efuse_probe,
 | |
| 	.driver = {
 | |
| 		.name = "sprd-efuse",
 | |
| 		.of_match_table = sprd_efuse_of_match,
 | |
| 	},
 | |
| };
 | |
| 
 | |
| module_platform_driver(sprd_efuse_driver);
 | |
| 
 | |
| MODULE_AUTHOR("Freeman Liu <freeman.liu@spreadtrum.com>");
 | |
| MODULE_DESCRIPTION("Spreadtrum AP efuse driver");
 | |
| MODULE_LICENSE("GPL v2");
 |