472 lines
13 KiB
C
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;
|
|
} |