Files
6CAR/drivers/source/fuse/sdrv_fuse.c
2026-04-18 09:16:58 +08:00

472 lines
13 KiB
C

/**
* @file sdrv_fuse.c
* @brief SemiDrive eFuse driver.
*
* @Copyright (c) 2022 Semidrive Semiconductor.
* All rights reserved.
*/
#include <compiler.h>
#include <debug.h>
#include <param.h>
#include <reg.h>
#include <string.h>
#include <sdrv_ckgen.h>
#include <clock_ip.h>
#include <part.h>
#include "regs_base.h"
#include "sdrv_fuse.h"
#include "sdrv_ckgen.h"
#include "fuse_ctrl_reg.h"
#if !defined(FUSE0_OFFSET)
#define FUSE0_OFFSET 0x1000
#endif
#define FUSE_PROG_KEY 0x9458u
/* Is this fuse protected by redudancy bits? */
#define IS_RED_FUSE(i) (((i) < 4u) || (((i) >= 176u) && ((i) < 207u)))
#define FUSE_INDEX_MAX 199u
#define FUSE_INDEX_RAW_MAX 255
#define FUSE_FUSE_MAX 256
/* default fuse timing parameters for APB Bus 150MHz */
#define FUSE_PWR_UP_TIMING_OFF_DEFAUT_VALUE 0x13f63f9fU
#define FUSE_STB_TIMING_OFF_DEFAUT_VALUE 0x0016e360U
#define FUSE_IPEN_RD_TIMING_OFF_DEFAUT_VALUE 0x4fd563f9U
#define FUSE_PG_TIMING_OFF_DEFAUT_VALUE 0x9f63ece3U
#define FUSE_REPAIR_TIMING_OFF_DEFAUT_VALUE 0x00005f3fU
#define FUSE_TIMING_CAL(fld, freq) (((fld) * (freq_mhz) + 75) / 150)
/* The maximum APB BUS frequency of E3 and D3 is 800 / 4 = 200 MHz
* The maximum APB BUS frequency of E3L is 400 / 4 = 100 MHz */
#if (CONFIG_E3 || CONFIG_D3)
#define SDRV_APB_BUS_FREQ_MAX 220000000U
#else
#define SDRV_APB_BUS_FREQ_MAX 110000000U
#endif
/* The minimum frequency is 24 / 4 = 6 MHz */
#define SDRV_APB_BUS_FREQ_MIN 5000000U
/**
* @brief Read fuse value from shadow register.
*
* This function load fuse value from shadow registers, which are filled
* automatically by hardware after chip reset.
*
* @param index Fuse index
* @return Fuse value
*/
uint32_t sdrv_fuse_read(uint32_t index)
{
ASSERT(index <= FUSE_INDEX_MAX);
return readl(APB_EFUSEC_BASE + FUSE0_OFFSET + index * 4);
}
/**
* @brief Set stick bit for specified fuse Id.
*
* @param id fuse id
* @return SDRV_STATUS_OK represents success
*/
status_t sdrv_fuse_set_sticky_bit(uint32_t id)
{
if(id > FUSE_FUSE_MAX){
return SDRV_STATUS_INVALID_PARAM;
}
uint32_t b = APB_EFUSEC_BASE;
uint32_t sticky_off = SW_STICKY_OFF + (id / 32) * 4;
uint32_t v = readl(b + sticky_off);
v |= (0x01u << (id % 32));
writel(v, b + sticky_off);
return SDRV_STATUS_OK;
}
/**
* @brief Get fuse bank lock type.
*
* @param bank The bank to get lock type.
* @return Lock type bitmask.
*/
uint32_t sdrv_fuse_get_bank_lock(fuse_lock_bank_e bank)
{
uint32_t b = APB_EFUSEC_BASE;
uint32_t lock_off = BANK_LOCK0_OFF + (bank / 8) * 4;
uint32_t v = readl(b + lock_off);
v &= (0xFu << ((bank % 8) * 4));
v = v >> ((bank % 8) * 4);
uint32_t lock_off_fuse = FUSE0_OFFSET + (bank / 8) * 4;
uint32_t vfuse = readl(b + lock_off_fuse);
vfuse &= (0xFu << ((bank % 8) * 4));
vfuse = vfuse >> ((bank % 8) * 4);
return (v | vfuse);
}
/**
* @brief Lock fuse banks.
*
* @param bank The bank to lock.
* @param lock_bits Lock type bitmask.
* @return SDRV_STATUS_OK represents success
*/
status_t sdrv_fuse_lock_bank(fuse_lock_bank_e bank, fuse_lock_bits_e lock_bits)
{
uint32_t b = APB_EFUSEC_BASE;
uint32_t lock_off = BANK_LOCK0_OFF + (bank / 8) * 4;
uint32_t v = readl(b + lock_off);
v &= ~(0xFu << ((bank % 8) * 4));
v |= (lock_bits << ((bank % 8) * 4));
writel(v, b + lock_off);
return SDRV_STATUS_OK;
}
/**
* @brief Update fuse control timing.
*
* @param freq_mhz APB clock frequency. Default hardware setting is
* based on 150MHz APB clock.
* @return SDRV_STATUS_OK represents success
*/
status_t sdrv_fuse_ctl_cfg_timing(uint32_t freq_mhz)
{
/* the default timing settings are based on 150MHz. */
uint32_t b = APB_EFUSEC_BASE;
uint32_t v = FUSE_PWR_UP_TIMING_OFF_DEFAUT_VALUE;
uint32_t fld =
FUSE_TIMING_CAL(GFV_FUSE_PWR_UP_TIMING_TPENS_CNT(v), freq_mhz);
v &= ~FM_FUSE_PWR_UP_TIMING_TPENS_CNT;
v |= FV_FUSE_PWR_UP_TIMING_TPENS_CNT(fld);
fld = FUSE_TIMING_CAL(GFV_FUSE_PWR_UP_TIMING_TSAS_CNT(v), freq_mhz);
v &= ~FM_FUSE_PWR_UP_TIMING_TSAS_CNT;
v |= FV_FUSE_PWR_UP_TIMING_TSAS_CNT(fld);
fld = FUSE_TIMING_CAL(GFV_FUSE_PWR_UP_TIMING_TPLS_CNT(v), freq_mhz);
v &= ~FM_FUSE_PWR_UP_TIMING_TPLS_CNT;
v |= FV_FUSE_PWR_UP_TIMING_TPLS_CNT(fld);
writel(v, b + FUSE_PWR_UP_TIMING_OFF);
v = FUSE_STB_TIMING_OFF_DEFAUT_VALUE;
fld = FUSE_TIMING_CAL(GFV_FUSE_STB_TIMING_TSTB_CNT(v), freq_mhz);
writel(fld, b + FUSE_STB_TIMING_OFF);
v = FUSE_IPEN_RD_TIMING_OFF_DEFAUT_VALUE;
fld = FUSE_TIMING_CAL(GFV_FUSE_IPEN_RD_TIMING_TIPEN_CNT(v), freq_mhz);
v &= ~FM_FUSE_IPEN_RD_TIMING_TIPEN_CNT;
v |= FV_FUSE_IPEN_RD_TIMING_TIPEN_CNT(fld);
fld = FUSE_TIMING_CAL(GFV_FUSE_IPEN_RD_TIMING_TASH_CNT(v), freq_mhz);
v &= ~FM_FUSE_IPEN_RD_TIMING_TASH_CNT;
v |= FV_FUSE_IPEN_RD_TIMING_TASH_CNT(fld);
fld = FUSE_TIMING_CAL(GFV_FUSE_IPEN_RD_TIMING_TASP_CNT(v), freq_mhz);
v &= ~FM_FUSE_IPEN_RD_TIMING_TASP_CNT;
v |= FV_FUSE_IPEN_RD_TIMING_TASP_CNT(fld);
fld = FUSE_TIMING_CAL(GFV_FUSE_IPEN_RD_TIMING_TAS_CNT(v), freq_mhz);
v &= ~FM_FUSE_IPEN_RD_TIMING_TAS_CNT;
v |= FV_FUSE_IPEN_RD_TIMING_TAS_CNT(fld);
fld = FUSE_TIMING_CAL(GFV_FUSE_IPEN_RD_TIMING_TCS_CNT(v), freq_mhz);
v &= ~FM_FUSE_IPEN_RD_TIMING_TCS_CNT;
v |= FV_FUSE_IPEN_RD_TIMING_TCS_CNT(fld);
fld = FUSE_TIMING_CAL(GFV_FUSE_IPEN_RD_TIMING_TRD_CNT(v), freq_mhz);
v &= ~FM_FUSE_IPEN_RD_TIMING_TRD_CNT;
v |= FV_FUSE_IPEN_RD_TIMING_TRD_CNT(fld);
writel(v, b + FUSE_IPEN_RD_TIMING_OFF);
v = FUSE_PG_TIMING_OFF_DEFAUT_VALUE;
fld = FUSE_TIMING_CAL(GFV_FUSE_PG_TIMING_TPWI_CNT(v), freq_mhz);
v &= ~FM_FUSE_PG_TIMING_TPWI_CNT;
v |= FV_FUSE_PG_TIMING_TPWI_CNT(fld);
fld = FUSE_TIMING_CAL(GFV_FUSE_PG_TIMING_TPM_CNT(v), freq_mhz);
v &= ~FM_FUSE_PG_TIMING_TPM_CNT;
v |= FV_FUSE_PG_TIMING_TPM_CNT(fld);
fld = FUSE_TIMING_CAL(GFV_FUSE_PG_TIMING_TPW_CNT(v), freq_mhz);
v &= ~FM_FUSE_PG_TIMING_TPW_CNT;
v |= FV_FUSE_PG_TIMING_TPW_CNT(fld);
writel(v, b + FUSE_PG_TIMING_OFF);
v = FUSE_REPAIR_TIMING_OFF_DEFAUT_VALUE;
fld = FUSE_TIMING_CAL(GFV_FUSE_REPAIR_TIMING_TTAH_CNT(v), freq_mhz);
v &= ~FM_FUSE_REPAIR_TIMING_TTAH_CNT;
v |= FV_FUSE_REPAIR_TIMING_TTAH_CNT(fld);
fld = FUSE_TIMING_CAL(GFV_FUSE_REPAIR_TIMING_TTAS_CNT(v), freq_mhz);
v &= ~FM_FUSE_REPAIR_TIMING_TTAS_CNT;
v |= FV_FUSE_REPAIR_TIMING_TTAS_CNT(fld);
writel(v, b + FUSE_REPAIR_TIMING_OFF);
return SDRV_STATUS_OK;
}
/**
* @brief Trigger software violation.
* vio0 to seip and vio1 to xspi, to clear keys there
*
* @param msk Lock type bitmask.
* @return SDRV_STATUS_OK represents success
*/
status_t sdrv_fuse_ctl_trigger_sw_vio(fuse_lock_bits_e msk)
{
uint32_t b = APB_EFUSEC_BASE;
uint32_t v = readl(b + VIO0_CFG_OFF);
v |= 0x01u << 15;
writel(v, b + VIO0_CFG_OFF);
v = readl(b + VIO1_CFG_OFF);
v |= 0x01u << 15;
writel(v, b + VIO1_CFG_OFF);
v = readl(b + SW_TRIG_VIO_OFF);
v |= (msk & FM_SW_TRIG_VIO_SW_TRIG);
writel(v, b + SW_TRIG_VIO_OFF);
return SDRV_STATUS_OK;
}
/**
* @brief Load fuse value by software.
*
* This function manually triggers fuse reading operation to specified
* index, and store fuse value in user buffer.
*
* @param index Fuse index
* @param data Buffer to place fuse data
* @return Return SDRV_STATUS_OK or error code
*/
status_t sdrv_fuse_sense(uint32_t index, uint32_t *data)
{
uint32_t b = APB_EFUSEC_BASE;
int res = SDRV_STATUS_INVALID_PARAM;
uint32_t freq = sdrv_ckgen_bus_get_rate(CLK_NODE(g_ckgen_bus_cr5_sf), CKGEN_BUS_CLK_OUT_P);
if ((freq < SDRV_APB_BUS_FREQ_MAX) && (freq > SDRV_APB_BUS_FREQ_MIN)) {
sdrv_fuse_ctl_cfg_timing(freq / 1000000);
}
do {
if ((index > FUSE_INDEX_RAW_MAX) || (NULL == data)) {
break;
}
uint32_t v = readl(b + FUSE_CTRL_OFF);
if (v & (BM_FUSE_CTRL_ERROR | BM_FUSE_CTRL_BUSY)) {
res = SDRV_FUSE_CTRL_ERROR_OR_BUSY;
break;
}
v &= ~FM_FUSE_CTRL_ADDR;
v |= FV_FUSE_CTRL_ADDR(index);
if (IS_RED_FUSE(index)) {
v |= BM_FUSE_CTRL_PECCB;
} else {
v &= ~BM_FUSE_CTRL_PECCB;
}
writel(v, b + FUSE_CTRL_OFF);
v = readl(b + FUSE_TEST_CFG_OFF);
v &= ~FM_FUSE_TEST_CFG_PTM;
writel(v, b + FUSE_TEST_CFG_OFF);
v = BM_FUSE_TRIG_READ;
writel(v, b + FUSE_TRIG_OFF);
while (readl(b + FUSE_CTRL_OFF) & BM_FUSE_CTRL_BUSY)
;
if (readl(b + FUSE_CTRL_OFF) & BM_FUSE_CTRL_ERROR) {
res = SDRV_FUSE_CTRL_ERROR;
break;
}
*data = readl(b + READ_FUSE_DATA_OFF);
res = SDRV_STATUS_OK;
} while (0);
return res;
}
/**
* @brief Reload fuse into shadow registers.
*
* @return Return SDRV_STATUS_OK or error code
*/
status_t sdrv_fuse_reload(void)
{
uint32_t b = APB_EFUSEC_BASE;
int res = SDRV_STATUS_FAIL;
uint32_t freq = sdrv_ckgen_bus_get_rate(CLK_NODE(g_ckgen_bus_cr5_sf), CKGEN_BUS_CLK_OUT_P);
if ((freq < SDRV_APB_BUS_FREQ_MAX) && (freq > SDRV_APB_BUS_FREQ_MIN)) {
sdrv_fuse_ctl_cfg_timing(freq / 1000000);
}
do {
uint32_t v = readl(b + FUSE_CTRL_OFF);
if (v & (BM_FUSE_CTRL_ERROR | BM_FUSE_CTRL_BUSY)) {
res = SDRV_FUSE_CTRL_ERROR_OR_BUSY;
break;
}
v = BM_FUSE_TRIG_LOAD;
writel(v, b + FUSE_TRIG_OFF);
while (readl(b + FUSE_CTRL_OFF) & BM_FUSE_CTRL_BUSY)
;
if (readl(b + FUSE_CTRL_OFF) & BM_FUSE_CTRL_ERROR) {
res = SDRV_FUSE_CTRL_ERROR;
break;
}
res = SDRV_STATUS_OK;
} while (0);
return res;
}
/**
* @brief Program raw fuse value.
*
* This function program specified value into fuse area, without redundancy
* protection bits.
*
* @param index Fuse index
* @param val Fuse value
* @param ecc True to enable ECC protection, only for non-redundancy bits
* @return Return SDRV_STATUS_OK or error code
*/
status_t sdrv_fuse_program_raw(uint32_t index, uint32_t val, bool ecc)
{
uint32_t freq = sdrv_ckgen_bus_get_rate(CLK_NODE(g_ckgen_bus_cr5_sf), CKGEN_BUS_CLK_OUT_P);
if ((freq < SDRV_APB_BUS_FREQ_MAX) && (freq > SDRV_APB_BUS_FREQ_MIN)) {
sdrv_fuse_ctl_cfg_timing(freq / 1000000);
}
uint32_t b = APB_EFUSEC_BASE;
int res = SDRV_STATUS_INVALID_PARAM;
do {
if (index > FUSE_INDEX_RAW_MAX) {
break;
}
uint32_t v = readl(b + FUSE_CTRL_OFF);
if (v & (BM_FUSE_CTRL_ERROR | BM_FUSE_CTRL_BUSY)) {
res = SDRV_FUSE_CTRL_ERROR_OR_BUSY;
break;
}
v &= ~(FM_FUSE_CTRL_ADDR | FM_FUSE_CTRL_PROG_KEY);
v |= FV_FUSE_CTRL_ADDR(index);
v |= FV_FUSE_CTRL_PROG_KEY(FUSE_PROG_KEY);
if (ecc) {
v &= ~BM_FUSE_CTRL_PECCB;
} else {
v |= BM_FUSE_CTRL_PECCB;
}
writel(v, b + FUSE_CTRL_OFF);
writel(val, b + PROG_DATA_OFF);
v = readl(b + FUSE_TEST_CFG_OFF);
v &= ~FM_FUSE_TEST_CFG_PTM;
v |= FV_FUSE_TEST_CFG_PTM(2u);
writel(v, b + FUSE_TEST_CFG_OFF);
v = BM_FUSE_TRIG_PROG;
writel(v, b + FUSE_TRIG_OFF);
while (readl(b + FUSE_CTRL_OFF) & BM_FUSE_CTRL_BUSY)
;
v = readl(b + FUSE_CTRL_OFF);
v &= ~FM_FUSE_CTRL_PROG_KEY;
writel(v, b + FUSE_CTRL_OFF);
/* error bit is 'write 1 to clear' bit */
if (v & BM_FUSE_CTRL_ERROR) {
res = SDRV_FUSE_CTRL_ERROR;
break;
}
res = SDRV_STATUS_OK;
} while (0);
return res;
}
/**
* @brief Program fuse.
*
* This function program specified value into fuse area. Redundant bits
* are programmed as well, for fuse indices protected by redundancy.
*
* @param index Fuse index
* @param v Fuse value
* @return Return SDRV_STATUS_OK or error code
*/
status_t sdrv_fuse_program(uint32_t index, uint32_t v)
{
int res = SDRV_STATUS_FAIL;
do {
if (index > FUSE_INDEX_MAX) {
break;
}
if (IS_RED_FUSE(index)) {
uint32_t red_pos = 0;
if (index < 4u) {
red_pos = 200u + index * 2u;
} else {
red_pos = 208u + (index - 176u) * 2u;
}
/* shall blow redundancy fuse bits then the main bit */
if ((0 != sdrv_fuse_program_raw(red_pos, v, false)) ||
(0 != sdrv_fuse_program_raw(red_pos + 1, v, false)) ||
(0 != sdrv_fuse_program_raw(index, v, false))) {
res = SDRV_FUSE_CTRL_ERROR_OR_BUSY;
break;
}
} else {
if (0 != sdrv_fuse_program_raw(index, v, true)) {
res = SDRV_FUSE_CTRL_ERROR;
break;
}
}
res = SDRV_STATUS_OK;
} while (0);
return res;
}