443 lines · 10.7 KB · C
RawDownload
// 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.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.add_legacy_fixed_of_cells = true;	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 },	{ }};MODULE_DEVICE_TABLE(of, sprd_efuse_of_match); 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");