|  | // Copyright lowRISC contributors. | 
|  | // Licensed under the Apache License, Version 2.0, see LICENSE for details. | 
|  | // SPDX-License-Identifier: Apache-2.0 | 
|  |  | 
|  | #include "sw/device/lib/dif/dif_lc_ctrl.h" | 
|  |  | 
|  | #include "sw/device/lib/base/memory.h" | 
|  |  | 
|  | #include "lc_ctrl_regs.h"  // Generated. | 
|  |  | 
|  | enum { | 
|  | /** | 
|  | * To claim the hardware mutex, SW can write 0xa5 to register | 
|  | * `CLAIM_TRANSITION_IF`. See | 
|  | * https://docs.opentitan.org/hw/ip/lc_ctrl/doc/index.html#Reg_claim_transition_if | 
|  | * for details. | 
|  | */ | 
|  | kLcCtrlMutexAcquire = 0xa5, | 
|  | /** | 
|  | * To release the hardware mutex, SW can write 0 to register | 
|  | * `CLAIM_TRANSITION_IF`. | 
|  | */ | 
|  | kLcCtrlMutexRelease = 0, | 
|  | }; | 
|  |  | 
|  | dif_lc_ctrl_result_t dif_lc_ctrl_init(dif_lc_ctrl_params_t params, | 
|  | dif_lc_ctrl_t *lc) { | 
|  | if (lc == NULL) { | 
|  | return kDifLcCtrlBadArg; | 
|  | } | 
|  |  | 
|  | lc->params = params; | 
|  | return kDifLcCtrlOk; | 
|  | } | 
|  |  | 
|  | dif_lc_ctrl_result_t dif_lc_ctrl_get_state(const dif_lc_ctrl_t *lc, | 
|  | dif_lc_ctrl_state_t *state) { | 
|  | if (lc == NULL || state == NULL) { | 
|  | return kDifLcCtrlBadArg; | 
|  | } | 
|  |  | 
|  | uint32_t reg = | 
|  | mmio_region_read32(lc->params.base_addr, LC_CTRL_LC_STATE_REG_OFFSET); | 
|  | switch (bitfield_field32_read(reg, LC_CTRL_LC_STATE_STATE_FIELD)) { | 
|  | case LC_CTRL_LC_STATE_STATE_VALUE_RAW: | 
|  | *state = kDifLcCtrlStateRaw; | 
|  | break; | 
|  | case LC_CTRL_LC_STATE_STATE_VALUE_TEST_UNLOCKED0: | 
|  | *state = kDifLcCtrlStateTestUnlocked0; | 
|  | break; | 
|  | case LC_CTRL_LC_STATE_STATE_VALUE_TEST_LOCKED0: | 
|  | *state = kDifLcCtrlStateTestLocked0; | 
|  | break; | 
|  | case LC_CTRL_LC_STATE_STATE_VALUE_TEST_UNLOCKED1: | 
|  | *state = kDifLcCtrlStateTestUnlocked1; | 
|  | break; | 
|  | case LC_CTRL_LC_STATE_STATE_VALUE_TEST_LOCKED1: | 
|  | *state = kDifLcCtrlStateTestLocked1; | 
|  | break; | 
|  | case LC_CTRL_LC_STATE_STATE_VALUE_TEST_UNLOCKED2: | 
|  | *state = kDifLcCtrlStateTestUnlocked2; | 
|  | break; | 
|  | case LC_CTRL_LC_STATE_STATE_VALUE_TEST_LOCKED2: | 
|  | *state = kDifLcCtrlStateTestLocked2; | 
|  | break; | 
|  | case LC_CTRL_LC_STATE_STATE_VALUE_TEST_UNLOCKED3: | 
|  | *state = kDifLcCtrlStateTestUnlocked3; | 
|  | break; | 
|  | case LC_CTRL_LC_STATE_STATE_VALUE_DEV: | 
|  | *state = kDifLcCtrlStateDev; | 
|  | break; | 
|  | case LC_CTRL_LC_STATE_STATE_VALUE_PROD: | 
|  | *state = kDifLcCtrlStateProd; | 
|  | break; | 
|  | case LC_CTRL_LC_STATE_STATE_VALUE_PROD_END: | 
|  | *state = kDifLcCtrlStateProdEnd; | 
|  | break; | 
|  | case LC_CTRL_LC_STATE_STATE_VALUE_RMA: | 
|  | *state = kDifLcCtrlStateRma; | 
|  | break; | 
|  | case LC_CTRL_LC_STATE_STATE_VALUE_SCRAP: | 
|  | *state = kDifLcCtrlStateScrap; | 
|  | break; | 
|  |  | 
|  | case LC_CTRL_LC_STATE_STATE_VALUE_POST_TRANSITION: | 
|  | *state = kDifLcCtrlStatePostTransition; | 
|  | break; | 
|  | case LC_CTRL_LC_STATE_STATE_VALUE_ESCALATE: | 
|  | *state = kDifLcCtrlStateEscalate; | 
|  | break; | 
|  | case LC_CTRL_LC_STATE_STATE_VALUE_INVALID: | 
|  | *state = kDifLcCtrlStateInvalid; | 
|  | break; | 
|  |  | 
|  | default: | 
|  | return kDifLcCtrlError; | 
|  | } | 
|  |  | 
|  | return kDifLcCtrlOk; | 
|  | } | 
|  |  | 
|  | dif_lc_ctrl_attempts_result_t dif_lc_ctrl_get_attempts(const dif_lc_ctrl_t *lc, | 
|  | uint8_t *count) { | 
|  | if (lc == NULL || count == NULL) { | 
|  | return kDifLcCtrlAttemptsBadArg; | 
|  | } | 
|  |  | 
|  | uint32_t reg = mmio_region_read32(lc->params.base_addr, | 
|  | LC_CTRL_LC_TRANSITION_CNT_REG_OFFSET); | 
|  | uint8_t value = | 
|  | bitfield_field32_read(reg, LC_CTRL_LC_TRANSITION_CNT_CNT_FIELD); | 
|  | if (value == LC_CTRL_LC_TRANSITION_CNT_CNT_MASK) { | 
|  | return kDifLcCtrlAttemptsTooMany; | 
|  | } | 
|  |  | 
|  | *count = value; | 
|  | return kDifLcCtrlAttemptsOk; | 
|  | } | 
|  |  | 
|  | dif_lc_ctrl_result_t dif_lc_ctrl_get_status(const dif_lc_ctrl_t *lc, | 
|  | dif_lc_ctrl_status_t *status) { | 
|  | if (lc == NULL || status == NULL) { | 
|  | return kDifLcCtrlBadArg; | 
|  | } | 
|  |  | 
|  | uint32_t reg = | 
|  | mmio_region_read32(lc->params.base_addr, LC_CTRL_STATUS_REG_OFFSET); | 
|  |  | 
|  | dif_lc_ctrl_status_t status_word = 0; | 
|  |  | 
|  | if (bitfield_bit32_read(reg, LC_CTRL_STATUS_READY_BIT)) { | 
|  | status_word = | 
|  | bitfield_bit32_write(status_word, kDifLcCtrlStatusCodeReady, true); | 
|  | } | 
|  |  | 
|  | if (bitfield_bit32_read(reg, LC_CTRL_STATUS_TRANSITION_SUCCESSFUL_BIT)) { | 
|  | status_word = | 
|  | bitfield_bit32_write(status_word, kDifLcCtrlStatusCodeSuccess, true); | 
|  | } | 
|  |  | 
|  | if (bitfield_bit32_read(reg, LC_CTRL_STATUS_TRANSITION_COUNT_ERROR_BIT)) { | 
|  | status_word = bitfield_bit32_write( | 
|  | status_word, kDifLcCtrlStatusCodeTooManyTransitions, true); | 
|  | } | 
|  |  | 
|  | if (bitfield_bit32_read(reg, LC_CTRL_STATUS_TRANSITION_ERROR_BIT)) { | 
|  | status_word = bitfield_bit32_write( | 
|  | status_word, kDifLcCtrlStatusCodeInvalidTransition, true); | 
|  | } | 
|  |  | 
|  | if (bitfield_bit32_read(reg, LC_CTRL_STATUS_TOKEN_ERROR_BIT)) { | 
|  | status_word = | 
|  | bitfield_bit32_write(status_word, kDifLcCtrlStatusCodeBadToken, true); | 
|  | } | 
|  |  | 
|  | if (bitfield_bit32_read(reg, LC_CTRL_STATUS_FLASH_RMA_ERROR_BIT)) { | 
|  | status_word = bitfield_bit32_write(status_word, | 
|  | kDifLcCtrlStatusCodeFlashRmaError, true); | 
|  | } | 
|  |  | 
|  | if (bitfield_bit32_read(reg, LC_CTRL_STATUS_OTP_ERROR_BIT)) { | 
|  | status_word = | 
|  | bitfield_bit32_write(status_word, kDifLcCtrlStatusCodeOtpError, true); | 
|  | } | 
|  |  | 
|  | if (bitfield_bit32_read(reg, LC_CTRL_STATUS_STATE_ERROR_BIT)) { | 
|  | status_word = | 
|  | bitfield_bit32_write(status_word, kDifLcCtrlStatusCodeCorrupt, true); | 
|  | } | 
|  |  | 
|  | if (bitfield_bit32_read(reg, LC_CTRL_STATUS_BUS_INTEG_ERROR_BIT)) { | 
|  | status_word = bitfield_bit32_write(status_word, | 
|  | kDifLcCtrlStatusCodeBusIntegError, true); | 
|  | } | 
|  |  | 
|  | if (bitfield_bit32_read(reg, LC_CTRL_STATUS_OTP_PARTITION_ERROR_BIT)) { | 
|  | status_word = bitfield_bit32_write(status_word, | 
|  | kDifLcCtrlStatusCodeOtpPartError, true); | 
|  | } | 
|  |  | 
|  | *status = status_word; | 
|  | return kDifLcCtrlOk; | 
|  | } | 
|  |  | 
|  | dif_lc_ctrl_result_t dif_lc_ctrl_get_id_state(const dif_lc_ctrl_t *lc, | 
|  | dif_lc_ctrl_id_state_t *state) { | 
|  | if (lc == NULL || state == NULL) { | 
|  | return kDifLcCtrlBadArg; | 
|  | } | 
|  |  | 
|  | uint32_t reg = | 
|  | mmio_region_read32(lc->params.base_addr, LC_CTRL_LC_ID_STATE_REG_OFFSET); | 
|  | switch (bitfield_field32_read(reg, LC_CTRL_LC_ID_STATE_STATE_FIELD)) { | 
|  | case LC_CTRL_LC_ID_STATE_STATE_VALUE_BLANK: | 
|  | *state = kDifLcCtrlIdStateBlank; | 
|  | break; | 
|  | case LC_CTRL_LC_ID_STATE_STATE_VALUE_PERSONALIZED: | 
|  | *state = kDifLcCtrlIdStatePersonalized; | 
|  | break; | 
|  | case LC_CTRL_LC_ID_STATE_STATE_VALUE_INVALID: | 
|  | *state = kDifLcCtrlIdStateInvalid; | 
|  | break; | 
|  | default: | 
|  | return kDifLcCtrlError; | 
|  | } | 
|  |  | 
|  | return kDifLcCtrlOk; | 
|  | } | 
|  |  | 
|  | dif_lc_ctrl_result_t dif_lc_ctrl_get_device_id( | 
|  | const dif_lc_ctrl_t *lc, dif_lc_ctrl_device_id_t *device_id) { | 
|  | if (lc == NULL) { | 
|  | return kDifLcCtrlBadArg; | 
|  | } | 
|  |  | 
|  | mmio_region_memcpy_from_mmio32( | 
|  | lc->params.base_addr, LC_CTRL_DEVICE_ID_0_REG_OFFSET, device_id->data, | 
|  | ARRAYSIZE(device_id->data) * sizeof(uint32_t)); | 
|  | return kDifLcCtrlOk; | 
|  | } | 
|  |  | 
|  | dif_lc_ctrl_result_t dif_lc_ctrl_alert_force(const dif_lc_ctrl_t *lc, | 
|  | dif_lc_ctrl_alert_t alert) { | 
|  | if (lc == NULL) { | 
|  | return kDifLcCtrlBadArg; | 
|  | } | 
|  |  | 
|  | bitfield_bit32_index_t alert_idx; | 
|  | switch (alert) { | 
|  | case kDifLcCtrlAlertOtp: | 
|  | alert_idx = LC_CTRL_ALERT_TEST_FATAL_PROG_ERROR_BIT; | 
|  | break; | 
|  | case kDifLcCtrlAlertCorrupt: | 
|  | alert_idx = LC_CTRL_ALERT_TEST_FATAL_STATE_ERROR_BIT; | 
|  | break; | 
|  | case kDifLcCtrlAlertBus: | 
|  | alert_idx = LC_CTRL_ALERT_TEST_FATAL_BUS_INTEG_ERROR_BIT; | 
|  | break; | 
|  | default: | 
|  | return kDifLcCtrlBadArg; | 
|  | } | 
|  |  | 
|  | uint32_t reg = bitfield_bit32_write(0, alert_idx, true); | 
|  | mmio_region_write32(lc->params.base_addr, LC_CTRL_ALERT_TEST_REG_OFFSET, reg); | 
|  |  | 
|  | return kDifLcCtrlOk; | 
|  | } | 
|  |  | 
|  | dif_lc_ctrl_mutex_result_t dif_lc_ctrl_mutex_try_acquire( | 
|  | const dif_lc_ctrl_t *lc) { | 
|  | if (lc == NULL) { | 
|  | return kDifLcCtrlMutexBadArg; | 
|  | } | 
|  |  | 
|  | mmio_region_write32(lc->params.base_addr, | 
|  | LC_CTRL_CLAIM_TRANSITION_IF_REG_OFFSET, | 
|  | kLcCtrlMutexAcquire); | 
|  | uint32_t reg = mmio_region_read32(lc->params.base_addr, | 
|  | LC_CTRL_CLAIM_TRANSITION_IF_REG_OFFSET); | 
|  | // If the register is zero, that means we failed to take the mutex for | 
|  | // whatever reason. | 
|  | if (reg == kLcCtrlMutexRelease) { | 
|  | return kDifLcCtrlMutexAlreadyTaken; | 
|  | } else { | 
|  | return kDifLcCtrlMutexOk; | 
|  | } | 
|  | } | 
|  |  | 
|  | dif_lc_ctrl_mutex_result_t dif_lc_ctrl_mutex_release(const dif_lc_ctrl_t *lc) { | 
|  | if (lc == NULL) { | 
|  | return kDifLcCtrlMutexBadArg; | 
|  | } | 
|  |  | 
|  | uint32_t reg = mmio_region_read32(lc->params.base_addr, | 
|  | LC_CTRL_CLAIM_TRANSITION_IF_REG_OFFSET); | 
|  | if (reg == kLcCtrlMutexRelease) { | 
|  | // We're not holding the mutex, which is a programmer error. | 
|  | return kDifLcCtrlMutexError; | 
|  | } | 
|  |  | 
|  | mmio_region_write32(lc->params.base_addr, | 
|  | LC_CTRL_CLAIM_TRANSITION_IF_REG_OFFSET, | 
|  | kLcCtrlMutexRelease); | 
|  | return kDifLcCtrlMutexOk; | 
|  | } | 
|  |  | 
|  | dif_lc_ctrl_mutex_result_t dif_lc_ctrl_transition( | 
|  | const dif_lc_ctrl_t *lc, dif_lc_ctrl_state_t state, | 
|  | const dif_lc_ctrl_token_t *token) { | 
|  | if (lc == NULL) { | 
|  | return kDifLcCtrlMutexBadArg; | 
|  | } | 
|  |  | 
|  | uint32_t target; | 
|  | switch (state) { | 
|  | case kDifLcCtrlStateRaw: | 
|  | target = LC_CTRL_TRANSITION_TARGET_STATE_VALUE_RAW; | 
|  | break; | 
|  | case kDifLcCtrlStateTestUnlocked0: | 
|  | target = LC_CTRL_TRANSITION_TARGET_STATE_VALUE_TEST_UNLOCKED0; | 
|  | break; | 
|  | case kDifLcCtrlStateTestLocked0: | 
|  | target = LC_CTRL_TRANSITION_TARGET_STATE_VALUE_TEST_LOCKED0; | 
|  | break; | 
|  | case kDifLcCtrlStateTestUnlocked1: | 
|  | target = LC_CTRL_TRANSITION_TARGET_STATE_VALUE_TEST_UNLOCKED1; | 
|  | break; | 
|  | case kDifLcCtrlStateTestLocked1: | 
|  | target = LC_CTRL_TRANSITION_TARGET_STATE_VALUE_TEST_LOCKED1; | 
|  | break; | 
|  | case kDifLcCtrlStateTestUnlocked2: | 
|  | target = LC_CTRL_TRANSITION_TARGET_STATE_VALUE_TEST_UNLOCKED2; | 
|  | break; | 
|  | case kDifLcCtrlStateTestLocked2: | 
|  | target = LC_CTRL_TRANSITION_TARGET_STATE_VALUE_TEST_LOCKED2; | 
|  | break; | 
|  | case kDifLcCtrlStateTestUnlocked3: | 
|  | target = LC_CTRL_TRANSITION_TARGET_STATE_VALUE_TEST_UNLOCKED3; | 
|  | break; | 
|  | case kDifLcCtrlStateDev: | 
|  | target = LC_CTRL_TRANSITION_TARGET_STATE_VALUE_DEV; | 
|  | break; | 
|  | case kDifLcCtrlStateProd: | 
|  | target = LC_CTRL_TRANSITION_TARGET_STATE_VALUE_PROD; | 
|  | break; | 
|  | case kDifLcCtrlStateProdEnd: | 
|  | target = LC_CTRL_TRANSITION_TARGET_STATE_VALUE_PROD_END; | 
|  | break; | 
|  | case kDifLcCtrlStateRma: | 
|  | target = LC_CTRL_TRANSITION_TARGET_STATE_VALUE_RMA; | 
|  | break; | 
|  | case kDifLcCtrlStateScrap: | 
|  | target = LC_CTRL_TRANSITION_TARGET_STATE_VALUE_SCRAP; | 
|  | break; | 
|  |  | 
|  | default: | 
|  | return kDifLcCtrlMutexBadArg; | 
|  | } | 
|  |  | 
|  | uint32_t busy = mmio_region_read32(lc->params.base_addr, | 
|  | LC_CTRL_TRANSITION_REGWEN_REG_OFFSET); | 
|  | if (busy == 0) { | 
|  | return kDifLcCtrlMutexAlreadyTaken; | 
|  | } | 
|  |  | 
|  | // Set the target for the transition. | 
|  | mmio_region_write32(lc->params.base_addr, | 
|  | LC_CTRL_TRANSITION_TARGET_REG_OFFSET, target); | 
|  |  | 
|  | // Fill in a token, if necessary. | 
|  | if (token != NULL) { | 
|  | for (int i = 0; i < sizeof(token->data); i += sizeof(uint32_t)) { | 
|  | uint32_t word; | 
|  | memcpy(&word, &token->data[i], sizeof(uint32_t)); | 
|  | mmio_region_write32(lc->params.base_addr, | 
|  | LC_CTRL_TRANSITION_TOKEN_0_REG_OFFSET + i, word); | 
|  | } | 
|  | } | 
|  |  | 
|  | // With both parameters set up, schedule the transition. | 
|  | mmio_region_write32(lc->params.base_addr, LC_CTRL_TRANSITION_CMD_REG_OFFSET, | 
|  | 1); | 
|  | return kDifLcCtrlMutexOk; | 
|  | } | 
|  |  | 
|  | dif_lc_ctrl_mutex_result_t dif_lc_ctrl_set_otp_test_reg(const dif_lc_ctrl_t *lc, | 
|  | uint32_t settings) { | 
|  | if (lc == NULL) { | 
|  | return kDifLcCtrlMutexBadArg; | 
|  | } | 
|  |  | 
|  | uint32_t busy = mmio_region_read32(lc->params.base_addr, | 
|  | LC_CTRL_TRANSITION_REGWEN_REG_OFFSET); | 
|  | if (busy == 0) { | 
|  | return kDifLcCtrlMutexAlreadyTaken; | 
|  | } | 
|  |  | 
|  | mmio_region_write32(lc->params.base_addr, LC_CTRL_OTP_TEST_CTRL_REG_OFFSET, | 
|  | settings); | 
|  |  | 
|  | return kDifLcCtrlMutexOk; | 
|  | } | 
|  |  | 
|  | dif_lc_ctrl_result_t dif_lc_ctrl_get_otp_test_reg(const dif_lc_ctrl_t *lc, | 
|  | uint32_t *settings) { | 
|  | if (lc == NULL || settings == NULL) { | 
|  | return kDifLcCtrlBadArg; | 
|  | } | 
|  |  | 
|  | uint32_t reg = mmio_region_read32(lc->params.base_addr, | 
|  | LC_CTRL_OTP_TEST_CTRL_REG_OFFSET); | 
|  |  | 
|  | *settings = bitfield_field32_read(reg, LC_CTRL_OTP_TEST_CTRL_VAL_FIELD); | 
|  |  | 
|  | return kDifLcCtrlOk; | 
|  | } |