/** * @file sdrv_fuse.c * @brief SemiDrive eFuse driver. * * @Copyright (c) 2022 Semidrive Semiconductor. * All rights reserved. */ #include #include #include #include #include #include #include #include #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; }