[sw] Add lc_ctrl registers to opentitanlib bindgen

Signed-off-by: Dan McArdle <dmcardle@opentitan.org>
diff --git a/sw/host/opentitanlib/bindgen/BUILD b/sw/host/opentitanlib/bindgen/BUILD
index 4dee520..34790ba 100644
--- a/sw/host/opentitanlib/bindgen/BUILD
+++ b/sw/host/opentitanlib/bindgen/BUILD
@@ -35,6 +35,9 @@
     name = "dif",
     bindgen_flags = [
         "--allowlist-type=dif_lc_ctrl_state",
+        "--allowlist-type=dif_lc_ctrl_token",
+        "--allowlist-var=LC_CTRL_.*_BIT",
+        "--allowlist-var=LC_CTRL_.*_REG_OFFSET",
         "--allowlist-type=dif_rstmgr_reset_info",
     ],
     cc_lib = ":difs",
diff --git a/sw/host/opentitanlib/bindgen/difs.h b/sw/host/opentitanlib/bindgen/difs.h
index 775b9f3..b4bd77a 100644
--- a/sw/host/opentitanlib/bindgen/difs.h
+++ b/sw/host/opentitanlib/bindgen/difs.h
@@ -4,6 +4,10 @@
 //
 #ifndef OPENTITAN_SW_HOST_OPENTITANLIB_BINDGEN_DIFS_H_
 #define OPENTITAN_SW_HOST_OPENTITANLIB_BINDGEN_DIFS_H_
+
 #include "sw/device/lib/dif/dif_lc_ctrl.h"
 #include "sw/device/lib/dif/dif_rstmgr.h"
+
+#include "lc_ctrl_regs.h"  // Generated.
+
 #endif  // OPENTITAN_SW_HOST_OPENTITANLIB_BINDGEN_DIFS_H_
diff --git a/sw/host/opentitanlib/src/dif/lc_ctrl.rs b/sw/host/opentitanlib/src/dif/lc_ctrl.rs
index fe3f534..4d9b2bd 100644
--- a/sw/host/opentitanlib/src/dif/lc_ctrl.rs
+++ b/sw/host/opentitanlib/src/dif/lc_ctrl.rs
@@ -3,6 +3,7 @@
 // SPDX-License-Identifier: Apache-2.0
 
 use crate::with_unknown;
+use num_enum::IntoPrimitive;
 
 with_unknown! {
     pub enum DifLcCtrlState: u32 {
@@ -29,6 +30,196 @@
         Scrap = bindgen::dif::dif_lc_ctrl_state_kDifLcCtrlStateScrap as u32,
         PostTransition = bindgen::dif::dif_lc_ctrl_state_kDifLcCtrlStatePostTransition as u32,
         Escalate = bindgen::dif::dif_lc_ctrl_state_kDifLcCtrlStateEscalate as u32,
-        StateInvalide = bindgen::dif::dif_lc_ctrl_state_kDifLcCtrlStateInvalid as u32,
+        StateInvalid = bindgen::dif::dif_lc_ctrl_state_kDifLcCtrlStateInvalid as u32,
+    }
+}
+
+impl DifLcCtrlState {
+    /// Encode the given life cycle state in a redundant format where the
+    /// five-bit value is repeated six times.
+    pub fn redundant_encoding(&self) -> u32 {
+        let value: u32 = self.0;
+        assert_eq!(value & 0b11111, value);
+        (0..6).fold(0u32, |acc, _| (acc << 5) | value)
+    }
+}
+
+pub struct DifLcCtrlToken(bindgen::dif::dif_lc_ctrl_token);
+
+impl From<[u8; 16]> for DifLcCtrlToken {
+    fn from(bytes: [u8; 16]) -> Self {
+        DifLcCtrlToken(bindgen::dif::dif_lc_ctrl_token { data: bytes })
+    }
+}
+
+impl DifLcCtrlToken {
+    /// Converts a 128-bit transition token into four native u32 words. These
+    /// values are suitable to write to [LcCtrlReg::TransitionToken0] and
+    /// friends.
+    pub fn into_register_values(self) -> [u32; 4] {
+        let mut out_words = [0u32; 4];
+        let bytes = self.0.data;
+        bytes
+            .chunks_exact(std::mem::size_of::<u32>())
+            .map(|chunk| u32::from_le_bytes(chunk.try_into().unwrap()))
+            .zip(&mut out_words)
+            .for_each(|(word, out)| *out = word);
+        out_words
+    }
+}
+
+#[derive(IntoPrimitive, Clone, Debug)]
+#[repr(u32)]
+pub enum LcCtrlReg {
+    AlertTest = bindgen::dif::LC_CTRL_ALERT_TEST_REG_OFFSET,
+    Status = bindgen::dif::LC_CTRL_STATUS_REG_OFFSET,
+    ClaimTransitionIf = bindgen::dif::LC_CTRL_CLAIM_TRANSITION_IF_REG_OFFSET,
+    TransitionRegwen = bindgen::dif::LC_CTRL_TRANSITION_REGWEN_REG_OFFSET,
+    TransitionCmd = bindgen::dif::LC_CTRL_TRANSITION_CMD_REG_OFFSET,
+    TransitionCtrl = bindgen::dif::LC_CTRL_TRANSITION_CTRL_REG_OFFSET,
+    TransitionToken0 = bindgen::dif::LC_CTRL_TRANSITION_TOKEN_0_REG_OFFSET,
+    TransitionToken1 = bindgen::dif::LC_CTRL_TRANSITION_TOKEN_1_REG_OFFSET,
+    TransitionToken2 = bindgen::dif::LC_CTRL_TRANSITION_TOKEN_2_REG_OFFSET,
+    TransitionToken3 = bindgen::dif::LC_CTRL_TRANSITION_TOKEN_3_REG_OFFSET,
+    TransitionTarget = bindgen::dif::LC_CTRL_TRANSITION_TARGET_REG_OFFSET,
+    OtpVendorTestCtrl = bindgen::dif::LC_CTRL_OTP_VENDOR_TEST_CTRL_REG_OFFSET,
+    OtpVendorTestStatus = bindgen::dif::LC_CTRL_OTP_VENDOR_TEST_STATUS_REG_OFFSET,
+    LcState = bindgen::dif::LC_CTRL_LC_STATE_REG_OFFSET,
+    LcTransitionCnt = bindgen::dif::LC_CTRL_LC_TRANSITION_CNT_REG_OFFSET,
+    LcIdState = bindgen::dif::LC_CTRL_LC_ID_STATE_REG_OFFSET,
+    HwRev = bindgen::dif::LC_CTRL_HW_REV_REG_OFFSET,
+    DeviceId0 = bindgen::dif::LC_CTRL_DEVICE_ID_0_REG_OFFSET,
+    DeviceId1 = bindgen::dif::LC_CTRL_DEVICE_ID_1_REG_OFFSET,
+    DeviceId2 = bindgen::dif::LC_CTRL_DEVICE_ID_2_REG_OFFSET,
+    DeviceId3 = bindgen::dif::LC_CTRL_DEVICE_ID_3_REG_OFFSET,
+    DeviceId4 = bindgen::dif::LC_CTRL_DEVICE_ID_4_REG_OFFSET,
+    DeviceId5 = bindgen::dif::LC_CTRL_DEVICE_ID_5_REG_OFFSET,
+    DeviceId6 = bindgen::dif::LC_CTRL_DEVICE_ID_6_REG_OFFSET,
+    DeviceId7 = bindgen::dif::LC_CTRL_DEVICE_ID_7_REG_OFFSET,
+    ManufState0 = bindgen::dif::LC_CTRL_MANUF_STATE_0_REG_OFFSET,
+    ManufState1 = bindgen::dif::LC_CTRL_MANUF_STATE_1_REG_OFFSET,
+    ManufState2 = bindgen::dif::LC_CTRL_MANUF_STATE_2_REG_OFFSET,
+    ManufState3 = bindgen::dif::LC_CTRL_MANUF_STATE_3_REG_OFFSET,
+    ManufState4 = bindgen::dif::LC_CTRL_MANUF_STATE_4_REG_OFFSET,
+    ManufState5 = bindgen::dif::LC_CTRL_MANUF_STATE_5_REG_OFFSET,
+    ManufState6 = bindgen::dif::LC_CTRL_MANUF_STATE_6_REG_OFFSET,
+    ManufState7 = bindgen::dif::LC_CTRL_MANUF_STATE_7_REG_OFFSET,
+}
+
+impl LcCtrlReg {
+    pub fn byte_offset(&self) -> u32 {
+        self.clone().into()
+    }
+    /// Converts the register's byte offset into a word offset for use with DMI.
+    /// https://docs.opentitan.org/hw/ip/lc_ctrl/doc/#life-cycle-tap-controller
+    pub fn word_offset(&self) -> u32 {
+        const BYTES_PER_WORD: u32 = std::mem::size_of::<u32>() as u32;
+        assert_eq!(self.byte_offset() % BYTES_PER_WORD, 0);
+        self.byte_offset() / BYTES_PER_WORD
+    }
+}
+
+pub trait LcBit: Clone + Into<u32> {
+    /// Builds a register value from a collection of [LcBit] bits.
+    fn union<const N: usize>(bits: [Self; N]) -> u32 {
+        bits.into_iter()
+            .map(|bit| bit.into())
+            .map(|bit: u32| 1 << bit)
+            .fold(0u32, |acc, shifted| acc | shifted)
+    }
+}
+
+/// Bits of the lc_ctrl.STATUS register, aka [LcCtrlReg::Status].
+#[derive(IntoPrimitive, Clone, Debug)]
+#[repr(u32)]
+pub enum LcCtrlStatusBit {
+    Initialized = bindgen::dif::LC_CTRL_STATUS_INITIALIZED_BIT,
+    Ready = bindgen::dif::LC_CTRL_STATUS_READY_BIT,
+    TransitionSuccessful = bindgen::dif::LC_CTRL_STATUS_TRANSITION_SUCCESSFUL_BIT,
+    TransitionCountError = bindgen::dif::LC_CTRL_STATUS_TRANSITION_COUNT_ERROR_BIT,
+    TransitionError = bindgen::dif::LC_CTRL_STATUS_TRANSITION_ERROR_BIT,
+    TokenError = bindgen::dif::LC_CTRL_STATUS_TOKEN_ERROR_BIT,
+    FlashRmaError = bindgen::dif::LC_CTRL_STATUS_FLASH_RMA_ERROR_BIT,
+    OtpError = bindgen::dif::LC_CTRL_STATUS_OTP_ERROR_BIT,
+    StateError = bindgen::dif::LC_CTRL_STATUS_STATE_ERROR_BIT,
+    BusIntegError = bindgen::dif::LC_CTRL_STATUS_BUS_INTEG_ERROR_BIT,
+    OtpPartitionError = bindgen::dif::LC_CTRL_STATUS_OTP_PARTITION_ERROR_BIT,
+}
+impl LcBit for LcCtrlStatusBit {}
+
+/// Bits of the lc_ctrl.TRANSITION_REGWEN register, aka [LcCtrlReg::TransitionRegwen].
+#[derive(IntoPrimitive, Clone, Debug)]
+#[repr(u32)]
+pub enum LcCtrlTransitionRegwenBit {
+    TransitionRegwen = bindgen::dif::LC_CTRL_TRANSITION_REGWEN_TRANSITION_REGWEN_BIT,
+}
+impl LcBit for LcCtrlTransitionRegwenBit {}
+
+/// Bits of the lc_ctrl.TRANSITION_CMD register, aka [LcCtrlReg::TransitionCmd].
+#[derive(IntoPrimitive, Clone, Debug)]
+#[repr(u32)]
+pub enum LcCtrlTransitionCmdBit {
+    Start = bindgen::dif::LC_CTRL_TRANSITION_CMD_START_BIT,
+}
+impl LcBit for LcCtrlTransitionCmdBit {}
+
+/// Bits of the lc_ctrl.TRANSITION_CTRL register, aka [LcCtrlReg::TransitionCtrl].
+#[derive(IntoPrimitive, Clone, Debug)]
+#[repr(u32)]
+pub enum LcCtrlTransitionCtrlBit {
+    ExtClockEn = bindgen::dif::LC_CTRL_TRANSITION_CTRL_EXT_CLOCK_EN_BIT,
+}
+impl LcBit for LcCtrlTransitionCtrlBit {}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    /// Raw is zero, so its redundant encoding is a fixed point.
+    #[test]
+    fn lc_ctrl_state_redundant_encoding_zero() {
+        assert_eq!(u32::from(DifLcCtrlState::Raw), 0);
+        assert_eq!(DifLcCtrlState::Raw.redundant_encoding(), 0);
+    }
+
+    /// The redundant encoding of non-zero values shouldn't be a fixed point.
+    #[test]
+    fn lc_ctrl_state_redundant_encoding_nonzero() {
+        assert_ne!(
+            u32::from(DifLcCtrlState::Rma),
+            DifLcCtrlState::Rma.redundant_encoding()
+        );
+        assert_eq!(DifLcCtrlState::Rma.redundant_encoding(), 0x2739ce73);
+    }
+
+    #[test]
+    fn lc_ctrl_token() {
+        // This test assumes the system is little-endian.
+        let token_bytes: [u8; 16] = [
+            0x01, 0x02, 0x03, 0x04, // TOKEN_0
+            0x11, 0x12, 0x13, 0x14, // TOKEN_1
+            0x21, 0x22, 0x23, 0x24, // TOKEN_2
+            0x31, 0x32, 0x33, 0x34, // TOKEN_3
+        ];
+        let token = DifLcCtrlToken::from(token_bytes);
+        let words: [u32; 4] = token.into_register_values();
+        assert_eq!(words, [0x04030201, 0x14131211, 0x24232221, 0x34333231]);
+    }
+
+    #[test]
+    fn lc_ctrl_register_offsets() {
+        assert_eq!(LcCtrlReg::LcState.byte_offset(), 0x34);
+        assert_eq!(0x34 / 4, 0xd);
+        assert_eq!(LcCtrlReg::LcState.word_offset(), 0xd);
+    }
+
+    #[test]
+    fn lc_bit_union() {
+        assert_eq!(LcBit::union([] as [LcCtrlStatusBit; 0]), 0);
+        assert_eq!(LcBit::union([LcCtrlStatusBit::Initialized]), 1);
+        assert_eq!(
+            LcBit::union([LcCtrlStatusBit::Initialized, LcCtrlStatusBit::Ready]),
+            3
+        );
     }
 }