Quelle sprd-efuse.c
Sprache: 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.h>
#include <linux/platform_device.h>
#define SPRD_EFUSE_ENABLE 0 x20
#define SPRD_EFUSE_ERR_FLAG 0 x24
#define SPRD_EFUSE_ERR_CLR 0 x28
#define SPRD_EFUSE_MAGIC_NUM 0 x2c
#define SPRD_EFUSE_FW_CFG 0 x50
#define SPRD_EFUSE_PW_SWT 0 x54
#define SPRD_EFUSE_MEM(val) (0 x1000 + ((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 0 x8810
/* 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" );
Messung V0.5 in Prozent C=100 H=100 G=100
¤ Dauer der Verarbeitung: 0.10 Sekunden
(vorverarbeitet am 2026-09-29)
¤
*© Formatika GbR, Deutschland
2026-10-10
Neuigkeiten
Aktuelles
Motto des Tages
Open Source Software
Jenseits des Üblichen ....
Besucherstatistik
Statistik
#Sources=1126864
#Domains=1897691