text: // 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/macros.h"
  #include "sw/device/lib/base/memory.h"
  #include "sw/device/lib/base/multibits.h"
  
  #include "lc_ctrl_regs.h"  // Generated.
  
  dif_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 kDifBadArg;
    }
  
    uint32_t reg = mmio_region_read32(lc->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_TEST_LOCKED3:
        *state = kDifLcCtrlStateTestLocked3;
        break;
      case LC_CTRL_LC_STATE_STATE_VALUE_TEST_UNLOCKED4:
        *state = kDifLcCtrlStateTestUnlocked4;
        break;
      case LC_CTRL_LC_STATE_STATE_VALUE_TEST_LOCKED4:
        *state = kDifLcCtrlStateTestLocked4;
        break;
      case LC_CTRL_LC_STATE_STATE_VALUE_TEST_UNLOCKED5:
        *state = kDifLcCtrlStateTestUnlocked5;
        break;
      case LC_CTRL_LC_STATE_STATE_VALUE_TEST_LOCKED5:
        *state = kDifLcCtrlStateTestLocked5;
        break;
      case LC_CTRL_LC_STATE_STATE_VALUE_TEST_UNLOCKED6:
        *state = kDifLcCtrlStateTestUnlocked6;
        break;
      case LC_CTRL_LC_STATE_STATE_VALUE_TEST_LOCKED6:
        *state = kDifLcCtrlStateTestLocked6;
        break;
      case LC_CTRL_LC_STATE_STATE_VALUE_TEST_UNLOCKED7:
        *state = kDifLcCtrlStateTestUnlocked7;
        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 kDifError;
    }
  
    return kDifOk;
  }
  
  dif_result_t dif_lc_ctrl_get_attempts(const dif_lc_ctrl_t *lc, uint8_t *count) {
    if (lc == NULL || count == NULL) {
      return kDifBadArg;
    }
  
    uint32_t reg =
        mmio_region_read32(lc->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 kDifError;
    }
  
    *count = value;
    return kDifOk;
  }
  
  dif_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 kDifBadArg;
    }
  
    uint32_t reg = mmio_region_read32(lc->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 kDifOk;
  }
  
  dif_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 kDifBadArg;
    }
  
    uint32_t reg =
        mmio_region_read32(lc->base_addr, LC_CTRL_LC_ID_STATE_REG_OFFSET);
    switch (reg) {
      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 kDifError;
    }
  
    return kDifOk;
  }
  
  dif_result_t dif_lc_ctrl_get_hw_rev(const dif_lc_ctrl_t *lc,
                                      dif_lc_ctrl_hw_rev_t *hw_rev) {
    if (lc == NULL || hw_rev == NULL) {
      return kDifBadArg;
    }
  
    uint32_t reg = mmio_region_read32(lc->base_addr, LC_CTRL_HW_REV_REG_OFFSET);
    hw_rev->chip_gen = bitfield_field32_read(reg, LC_CTRL_HW_REV_CHIP_GEN_FIELD);
    hw_rev->chip_rev = bitfield_field32_read(reg, LC_CTRL_HW_REV_CHIP_REV_FIELD);
    return kDifOk;
  }
  
  dif_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 || device_id == NULL) {
      return kDifBadArg;
    }
  
    mmio_region_memcpy_from_mmio32(lc->base_addr, LC_CTRL_DEVICE_ID_0_REG_OFFSET,
                                   device_id->data,
                                   ARRAYSIZE(device_id->data) * sizeof(uint32_t));
    return kDifOk;
  }
  
  dif_result_t dif_lc_ctrl_mutex_try_acquire(const dif_lc_ctrl_t *lc) {
    if (lc == NULL) {
      return kDifBadArg;
    }
  
    mmio_region_write32(lc->base_addr, LC_CTRL_CLAIM_TRANSITION_IF_REG_OFFSET,
                        kMultiBitBool8True);
    uint32_t reg =
        mmio_region_read32(lc->base_addr, LC_CTRL_CLAIM_TRANSITION_IF_REG_OFFSET);
    // If the register is not `kMultiBitBool8True`, that means we failed to take
    // the mutex for whatever reason.
    if (reg != kMultiBitBool8True) {
      return kDifUnavailable;
    } else {
      return kDifOk;
    }
  }
  
  dif_result_t dif_lc_ctrl_mutex_release(const dif_lc_ctrl_t *lc) {
    if (lc == NULL) {
      return kDifBadArg;
    }
  
    uint32_t reg =
        mmio_region_read32(lc->base_addr, LC_CTRL_CLAIM_TRANSITION_IF_REG_OFFSET);
    if (reg != kMultiBitBool8True) {
      // We're not holding the mutex, which is a programmer error.
      return kDifError;
    }
  
    mmio_region_write32(lc->base_addr, LC_CTRL_CLAIM_TRANSITION_IF_REG_OFFSET,
                        kMultiBitBool8False);
    return kDifOk;
  }
  
  dif_result_t dif_lc_ctrl_configure(const dif_lc_ctrl_t *lc,
                                     dif_lc_ctrl_state_t state,
                                     bool use_ext_clock,
                                     const dif_lc_ctrl_token_t *token) {
    if (lc == NULL) {
      return kDifBadArg;
    }
  
    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 kDifLcCtrlStateTestLocked3:
        target = LC_CTRL_TRANSITION_TARGET_STATE_VALUE_TEST_LOCKED3;
        break;
      case kDifLcCtrlStateTestUnlocked4:
        target = LC_CTRL_TRANSITION_TARGET_STATE_VALUE_TEST_UNLOCKED4;
        break;
      case kDifLcCtrlStateTestLocked4:
        target = LC_CTRL_TRANSITION_TARGET_STATE_VALUE_TEST_LOCKED4;
        break;
      case kDifLcCtrlStateTestUnlocked5:
        target = LC_CTRL_TRANSITION_TARGET_STATE_VALUE_TEST_UNLOCKED5;
        break;
      case kDifLcCtrlStateTestLocked5:
        target = LC_CTRL_TRANSITION_TARGET_STATE_VALUE_TEST_LOCKED5;
        break;
      case kDifLcCtrlStateTestUnlocked6:
        target = LC_CTRL_TRANSITION_TARGET_STATE_VALUE_TEST_UNLOCKED6;
        break;
      case kDifLcCtrlStateTestLocked6:
        target = LC_CTRL_TRANSITION_TARGET_STATE_VALUE_TEST_LOCKED6;
        break;
      case kDifLcCtrlStateTestUnlocked7:
        target = LC_CTRL_TRANSITION_TARGET_STATE_VALUE_TEST_UNLOCKED7;
        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 kDifBadArg;
    }
  
    // Check if the mutex has been acquired.
    if (!mmio_region_read32(lc->base_addr,
                            LC_CTRL_TRANSITION_REGWEN_REG_OFFSET)) {
      return kDifUnavailable;
    }
  
    // Set the target for the transition.
    mmio_region_write32(lc->base_addr, LC_CTRL_TRANSITION_TARGET_REG_OFFSET,
                        target);
  
    // Program the clock selection.
    uint32_t ctrl_reg = 0;
    if (use_ext_clock) {
      ctrl_reg = bitfield_bit32_write(
          ctrl_reg, LC_CTRL_TRANSITION_CTRL_EXT_CLOCK_EN_BIT, true);
    } else {
      // Default to internal clock.
      ctrl_reg = bitfield_bit32_write(
          ctrl_reg, LC_CTRL_TRANSITION_CTRL_EXT_CLOCK_EN_BIT, false);
    }
    mmio_region_write32(lc->base_addr, LC_CTRL_TRANSITION_CTRL_REG_OFFSET,
                        ctrl_reg);
  
    // 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->base_addr,
                            LC_CTRL_TRANSITION_TOKEN_0_REG_OFFSET + i, word);
      }
    }
  
    return kDifOk;
  }
  
  dif_result_t dif_lc_ctrl_transition(const dif_lc_ctrl_t *lc) {
    if (lc == NULL) {
      return kDifBadArg;
    }
  
    // Check if the mutex has been acquired.
    if (!mmio_region_read32(lc->base_addr,
                            LC_CTRL_TRANSITION_REGWEN_REG_OFFSET)) {
      return kDifUnavailable;
    }
  
    mmio_region_write32(lc->base_addr, LC_CTRL_TRANSITION_CMD_REG_OFFSET, 1);
  
    return kDifOk;
  }
  
  dif_result_t dif_lc_ctrl_set_otp_vendor_test_reg(const dif_lc_ctrl_t *lc,
                                                   uint32_t settings) {
    if (lc == NULL) {
      return kDifBadArg;
    }
  
    uint32_t busy =
        mmio_region_read32(lc->base_addr, LC_CTRL_TRANSITION_REGWEN_REG_OFFSET);
    if (busy == 0) {
      return kDifUnavailable;
    }
  
    mmio_region_write32(lc->base_addr, LC_CTRL_OTP_VENDOR_TEST_CTRL_REG_OFFSET,
                        settings);
  
    return kDifOk;
  }
  
  dif_result_t dif_lc_ctrl_get_otp_vendor_test_reg(const dif_lc_ctrl_t *lc,
                                                   uint32_t *settings) {
    if (lc == NULL || settings == NULL) {
      return kDifBadArg;
    }
  
    uint32_t busy =
        mmio_region_read32(lc->base_addr, LC_CTRL_TRANSITION_REGWEN_REG_OFFSET);
    if (busy == 0) {
      return kDifUnavailable;
    }
  
    *settings = mmio_region_read32(lc->base_addr,
                                   LC_CTRL_OTP_VENDOR_TEST_CTRL_REG_OFFSET);
  
    return kDifOk;
  }
  