forked from Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
441 lines
11 KiB
441 lines
11 KiB
// 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 <[email protected]>"); |
|
MODULE_DESCRIPTION("Spreadtrum AP efuse driver"); |
|
MODULE_LICENSE("GPL v2");
|
|
|