| // 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"); |