[sw/silicon_creator] Add OTP ROM driver

Add a otp_ctrl driver implementation with unittests. This driver
supports reading from OTP using the memory mapped software config
partitions.

Signed-off-by: Jon Flatley <jflat@google.com>
diff --git a/sw/device/silicon_creator/lib/drivers/meson.build b/sw/device/silicon_creator/lib/drivers/meson.build
index ecd73c6..bf97129 100644
--- a/sw/device/silicon_creator/lib/drivers/meson.build
+++ b/sw/device/silicon_creator/lib/drivers/meson.build
@@ -65,3 +65,35 @@
   ),
   suite: 'mask_rom',
 )
+
+# Mask OTP uart driver
+sw_silicon_creator_lib_driver_otp = declare_dependency(
+  link_with: static_library(
+    'sw_silicon_creator_lib_driver_otp',
+    sources: [
+      hw_ip_otp_ctrl_reg_h,
+      'otp.c',
+    ],
+    dependencies: [
+      sw_lib_mmio,
+    ],
+  ),
+)
+
+test('sw_silicon_creator_lib_driver_otp_unittest', executable(
+    'sw_silicon_creator_lib_driver_otp_unittest',
+    sources: [
+      'otp_unittest.cc',
+      hw_ip_otp_ctrl_reg_h,
+      'otp.c',
+    ],
+    dependencies: [
+      sw_vendor_gtest,
+      sw_lib_base_testing_mock_mmio,
+    ],
+    native: true,
+    c_args: ['-DMOCK_MMIO'],
+    cpp_args: ['-DMOCK_MMIO'],
+    ),
+  suite: 'mask_rom',
+)
diff --git a/sw/device/silicon_creator/lib/drivers/otp.c b/sw/device/silicon_creator/lib/drivers/otp.c
new file mode 100644
index 0000000..59e033a
--- /dev/null
+++ b/sw/device/silicon_creator/lib/drivers/otp.c
@@ -0,0 +1,33 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+#include "sw/device/silicon_creator/lib/drivers/otp.h"
+
+#include <stddef.h>
+
+#include "sw/device/lib/base/bitfield.h"
+#include "sw/device/lib/base/mmio.h"
+#include "sw/device/silicon_creator/lib/error.h"
+
+#include "otp_ctrl_regs.h"
+
+uint32_t otp_read32(const otp_t *otp, uint32_t address) {
+  return mmio_region_read32(otp->base_addr,
+                            OTP_CTRL_SW_CFG_WINDOW_REG_OFFSET + address);
+}
+
+uint64_t otp_read64(const otp_t *otp, uint32_t address) {
+  ptrdiff_t reg_offset = OTP_CTRL_SW_CFG_WINDOW_REG_OFFSET + address;
+  uint64_t value =
+      mmio_region_read32(otp->base_addr, reg_offset + sizeof(uint32_t));
+  value <<= 32;
+  value |= mmio_region_read32(otp->base_addr, reg_offset);
+
+  return value;
+}
+
+void otp_read(const otp_t *otp, uint32_t address, void *data, size_t len) {
+  ptrdiff_t reg_offset = OTP_CTRL_SW_CFG_WINDOW_REG_OFFSET + address;
+  mmio_region_memcpy_from_mmio32(otp->base_addr, reg_offset, data, len);
+}
diff --git a/sw/device/silicon_creator/lib/drivers/otp.h b/sw/device/silicon_creator/lib/drivers/otp.h
new file mode 100644
index 0000000..4fcde70
--- /dev/null
+++ b/sw/device/silicon_creator/lib/drivers/otp.h
@@ -0,0 +1,60 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+#ifndef OPENTITAN_SW_DEVICE_SILICON_CREATOR_LIB_DRIVERS_OTP_H_
+#define OPENTITAN_SW_DEVICE_SILICON_CREATOR_LIB_DRIVERS_OTP_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include "sw/device/lib/base/mmio.h"
+#include "sw/device/silicon_creator/lib/error.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct otp {
+  /**
+   * The base address for the OTP hardware registers.
+   */
+  mmio_region_t base_addr;
+} otp_t;
+
+/**
+ * Perform a blocking 32-bit read from the memory mapped software config
+ * partitions.
+ *
+ * @param otp The handle to the otp_ctrl device.
+ * @param address The address to read from offset from the start of OTP memory.
+ * @return The 32-bit value from OTP.
+ */
+uint32_t otp_read32(const otp_t *otp, uint32_t address);
+
+/**
+ * Perform a blocking 64-bit read from the memory mapped software config
+ * partitions.
+ *
+ * @param otp The handle to the otp_ctrl device.
+ * @param address The address to read from offset from the start of OTP memory.
+ * @return The 64-bit value from OTP.
+ */
+uint64_t otp_read64(const otp_t *otp, uint32_t address);
+
+/**
+ * Perform a blocking read of `len` bytes from the memory mapped software config
+ * partitions.
+ *
+ * @param otp The handle to the otp_ctrl device.
+ * @param address The address to read from offset from the start of OTP memory.
+ * @param data The output buffer of at least length `len`.
+ * @param len The number of bytes to read from OTP.
+ */
+void otp_read(const otp_t *otp, uint32_t address, void *data, size_t len);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif  // OPENTITAN_SW_DEVICE_SILICON_CREATOR_LIB_DRIVERS_OTP_H_
diff --git a/sw/device/silicon_creator/lib/drivers/otp_unittest.cc b/sw/device/silicon_creator/lib/drivers/otp_unittest.cc
new file mode 100644
index 0000000..7fb670d
--- /dev/null
+++ b/sw/device/silicon_creator/lib/drivers/otp_unittest.cc
@@ -0,0 +1,73 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+#include "sw/device/silicon_creator/lib/drivers/otp.h"
+
+#include "gtest/gtest.h"
+#include "sw/device/lib/base/mmio.h"
+#include "sw/device/lib/base/testing/mock_mmio.h"
+#include "sw/device/silicon_creator/lib/error.h"
+
+#include "otp_ctrl_regs.h"  // Generated.
+
+namespace otp_unittest {
+namespace {
+using ::mock_mmio::MmioTest;
+using ::testing::Test;
+
+class OtpTest : public Test, public MmioTest {
+ protected:
+  otp_t otp_ = {.base_addr = dev().region()};
+};
+
+class OtpReadTest : public OtpTest {
+ protected:
+  const ptrdiff_t offset_ = OTP_CTRL_SW_CFG_WINDOW_REG_OFFSET;
+};
+
+TEST_F(OtpReadTest, Read32) {
+  EXPECT_READ32(offset_, 0x00010203);
+
+  EXPECT_EQ(otp_read32(&otp_, 0), 0x00010203);
+}
+
+TEST_F(OtpReadTest, Read64) {
+  EXPECT_READ32(offset_ + 8, 0x04050607);
+  EXPECT_READ32(offset_ + 4, 0x08090A0B);
+
+  EXPECT_EQ(otp_read64(&otp_, 4), 0x0405060708090A0B);
+}
+
+TEST_F(OtpReadTest, ReadLen32) {
+  EXPECT_READ32(offset_, 0x08090A0B);
+
+  uint32_t value = 0;
+  otp_read(&otp_, 0, &value, sizeof(value));
+  EXPECT_EQ(value, 0x08090A0B);
+}
+
+TEST_F(OtpReadTest, ReadLen64) {
+  EXPECT_READ32(offset_, 0x0C0D0E0F);
+  EXPECT_READ32(offset_ + 4, 0x08090A0B);
+
+  uint64_t value = 0;
+  otp_read(&otp_, 0, &value, sizeof(value));
+  EXPECT_EQ(value, 0x08090A0B0C0D0E0F);
+}
+
+TEST_F(OtpReadTest, ReadLenN) {
+  for (int val = 0; val < 16; ++val) {
+    EXPECT_READ32(offset_ + val * sizeof(uint32_t), val);
+  }
+
+  std::vector<uint32_t> arr(16);
+  otp_read(&otp_, 0, &arr[0], arr.size() * sizeof(uint32_t));
+
+  for (int i = 0; i < arr.size(); ++i) {
+    EXPECT_EQ(arr[i], i);
+  }
+}
+
+}  // namespace
+}  // namespace otp_unittest
diff --git a/sw/device/silicon_creator/lib/error.h b/sw/device/silicon_creator/lib/error.h
index 7552ee5..3dd82c5 100644
--- a/sw/device/silicon_creator/lib/error.h
+++ b/sw/device/silicon_creator/lib/error.h
@@ -24,6 +24,7 @@
   kModuleUart = 0x4155,       // ASCII "UA".
   kModuleHmac = 0x4d48,       // ASCII "HM".
   kModuleSigverify = 0x5653,  // ASCII "SV".
+  kModuleOtp = 0x504f,        // ASCII "OP".
 };
 
 /**
@@ -47,6 +48,8 @@
   X(kErrorUartBadBaudRate,          ERROR_(2, kModuleUart, kInvalidArgument)), \
   X(kErrorHmacInvalidArgument,      ERROR_(1, kModuleHmac, kInvalidArgument)), \
   X(kErrorSigverifyInvalidArgument, ERROR_(1, kModuleSigverify, kInvalidArgument)), \
+  X(kErrorOtpBusy,                  ERROR_(1, kModuleOtp, kUnavailable)),  \
+  X(kErrorOtpUnknown,               ERROR_(2, kModuleOtp, kUnknown)), \
   X(kErrorUnknown, 0xFFFFFFFF)
 // clang-format on