[sw, dif_uart] Add API for UART RX timeout control

Add the functions dif_uart_enable_rx_timeout,
dif_uart_disable_rx_timeout and dif_uart_get_rx_timeout to allow the
UART RX timeout to be enabled, disabled and queried as required.

Updates #4277.

Signed-off-by: Michael Munday <mike.munday@lowrisc.org>
diff --git a/sw/device/lib/dif/dif_uart.c b/sw/device/lib/dif/dif_uart.c
index b34fb12..8868348 100644
--- a/sw/device/lib/dif/dif_uart.c
+++ b/sw/device/lib/dif/dif_uart.c
@@ -539,3 +539,51 @@
 
   return kDifUartOk;
 }
+
+dif_uart_result_t dif_uart_enable_rx_timeout(const dif_uart_t *uart,
+                                             uint32_t duration_ticks) {
+  if (uart == NULL || (duration_ticks & ~UART_TIMEOUT_CTRL_VAL_MASK) != 0) {
+    return kDifUartBadArg;
+  }
+
+  uint32_t reg = bitfield_bit32_write(0, UART_TIMEOUT_CTRL_EN_BIT, true);
+  reg =
+      bitfield_field32_write(reg, UART_TIMEOUT_CTRL_VAL_FIELD, duration_ticks);
+  mmio_region_write32(uart->params.base_addr, UART_TIMEOUT_CTRL_REG_OFFSET,
+                      reg);
+
+  return kDifUartOk;
+}
+
+dif_uart_result_t dif_uart_disable_rx_timeout(const dif_uart_t *uart) {
+  if (uart == NULL) {
+    return kDifUartBadArg;
+  }
+
+  uint32_t reg = bitfield_bit32_write(0, UART_TIMEOUT_CTRL_EN_BIT, false);
+  reg = bitfield_field32_write(reg, UART_TIMEOUT_CTRL_VAL_FIELD, 0);
+  mmio_region_write32(uart->params.base_addr, UART_TIMEOUT_CTRL_REG_OFFSET,
+                      reg);
+
+  return kDifUartOk;
+}
+
+dif_uart_result_t dif_uart_get_rx_timeout(const dif_uart_t *uart,
+                                          dif_uart_toggle_t *status,
+                                          uint32_t *duration_ticks) {
+  if (uart == NULL || status == NULL) {
+    return kDifUartBadArg;
+  }
+
+  uint32_t reg =
+      mmio_region_read32(uart->params.base_addr, UART_TIMEOUT_CTRL_REG_OFFSET);
+  *status = bitfield_bit32_read(reg, UART_TIMEOUT_CTRL_EN_BIT)
+                ? kDifUartToggleEnabled
+                : kDifUartToggleDisabled;
+
+  if (duration_ticks != NULL) {
+    *duration_ticks = bitfield_field32_read(reg, UART_TIMEOUT_CTRL_VAL_FIELD);
+  }
+
+  return kDifUartOk;
+}
diff --git a/sw/device/lib/dif/dif_uart.h b/sw/device/lib/dif/dif_uart.h
index bedf408..31f3472 100644
--- a/sw/device/lib/dif/dif_uart.h
+++ b/sw/device/lib/dif/dif_uart.h
@@ -532,6 +532,45 @@
                                         dif_uart_loopback_t loopback,
                                         dif_uart_toggle_t enable);
 
+/**
+ * Enables the RX timeout with the given duration.
+ *
+ * @param uart A UART handle.
+ * @param duration_ticks RX timeout value in UART bit times (using the baud rate
+ * clock as reference) in the range [0,0xffffff].
+ * @return The result of the operation.
+ */
+DIF_WARN_UNUSED_RESULT
+dif_uart_result_t dif_uart_enable_rx_timeout(const dif_uart_t *uart,
+                                             uint32_t duration_ticks);
+
+/**
+ * Disables the RX timeout.
+ *
+ * In addition to disabling the RX timeout the timeout duration is reset to 0
+ * ticks.
+ *
+ * @param uart A UART handle.
+ * @return The result of the operation.
+ */
+DIF_WARN_UNUSED_RESULT
+dif_uart_result_t dif_uart_disable_rx_timeout(const dif_uart_t *uart);
+
+/**
+ * Gets the current status of the RX timeout control.
+ *
+ * @param uart A UART handle.
+ * @param[out] status The status of the RX timeout control (enabled or
+ * disabled).
+ * @param[out] duration_ticks RX timeout value in UART bit times (using the baud
+ * rate clock as reference) in the range [0,0xffffff] (optional).
+ * @return The result of the operation.
+ */
+DIF_WARN_UNUSED_RESULT
+dif_uart_result_t dif_uart_get_rx_timeout(const dif_uart_t *uart,
+                                          dif_uart_toggle_t *status,
+                                          uint32_t *duration_ticks);
+
 #ifdef __cplusplus
 }  // extern "C"
 #endif  // __cplusplus
diff --git a/sw/device/tests/dif/dif_uart_unittest.cc b/sw/device/tests/dif/dif_uart_unittest.cc
index 12909c0..403b103 100644
--- a/sw/device/tests/dif/dif_uart_unittest.cc
+++ b/sw/device/tests/dif/dif_uart_unittest.cc
@@ -653,5 +653,76 @@
             kDifUartOk);
 }
 
+class RxTimeoutTest : public UartTest {};
+
+TEST_F(RxTimeoutTest, NullArgs) {
+  EXPECT_EQ(dif_uart_enable_rx_timeout(nullptr, 1), kDifUartBadArg);
+  EXPECT_EQ(dif_uart_disable_rx_timeout(nullptr), kDifUartBadArg);
+
+  uint32_t value;            // optional
+  dif_uart_toggle_t status;  // mandatory
+  EXPECT_EQ(dif_uart_get_rx_timeout(nullptr, &status, &value), kDifUartBadArg);
+  EXPECT_EQ(dif_uart_get_rx_timeout(&uart_, nullptr, &value), kDifUartBadArg);
+}
+
+TEST_F(RxTimeoutTest, OutOfRange) {
+  // RX timeout value must be in the range [0,0xffffff].
+  EXPECT_EQ(dif_uart_enable_rx_timeout(&uart_, 0x01000000), kDifUartBadArg);
+  EXPECT_EQ(dif_uart_enable_rx_timeout(&uart_, 0xffffffff), kDifUartBadArg);
+}
+
+TEST_F(RxTimeoutTest, Enable) {
+  // Enable RX timeout and set to 0x123.
+  const uint32_t duration = 0x123;
+  EXPECT_WRITE32(UART_TIMEOUT_CTRL_REG_OFFSET,
+                 {{UART_TIMEOUT_CTRL_VAL_OFFSET, duration},
+                  {UART_TIMEOUT_CTRL_EN_BIT, true}});
+  EXPECT_EQ(dif_uart_enable_rx_timeout(&uart_, duration), kDifUartOk);
+}
+
+TEST_F(RxTimeoutTest, Disable) {
+  // Disable RX timeout.
+  EXPECT_WRITE32(
+      UART_TIMEOUT_CTRL_REG_OFFSET,
+      {{UART_TIMEOUT_CTRL_VAL_OFFSET, 0}, {UART_TIMEOUT_CTRL_EN_BIT, false}});
+  EXPECT_EQ(dif_uart_disable_rx_timeout(&uart_), kDifUartOk);
+}
+
+TEST_F(RxTimeoutTest, GetStatusOnly) {
+  // Enable RX timeout and set to 0x800000.
+  const uint32_t duration = 0x800000;
+  EXPECT_WRITE32(UART_TIMEOUT_CTRL_REG_OFFSET,
+                 {{UART_TIMEOUT_CTRL_VAL_OFFSET, duration},
+                  {UART_TIMEOUT_CTRL_EN_BIT, true}});
+  EXPECT_EQ(dif_uart_enable_rx_timeout(&uart_, duration), kDifUartOk);
+
+  // Read out status only.
+  dif_uart_toggle_t status = kDifUartToggleDisabled;
+  EXPECT_READ32(UART_TIMEOUT_CTRL_REG_OFFSET,
+                {{UART_TIMEOUT_CTRL_VAL_OFFSET, duration},
+                 {UART_TIMEOUT_CTRL_EN_BIT, true}});
+  EXPECT_EQ(dif_uart_get_rx_timeout(&uart_, &status, nullptr), kDifUartOk);
+  EXPECT_EQ(status, kDifUartToggleEnabled);
+}
+
+TEST_F(RxTimeoutTest, GetAll) {
+  // Enable RX timeout and set to 0xf0f0f0.
+  const uint32_t duration = 0xf0f0f0;
+  EXPECT_WRITE32(UART_TIMEOUT_CTRL_REG_OFFSET,
+                 {{UART_TIMEOUT_CTRL_VAL_OFFSET, duration},
+                  {UART_TIMEOUT_CTRL_EN_BIT, true}});
+  EXPECT_EQ(dif_uart_enable_rx_timeout(&uart_, duration), kDifUartOk);
+
+  // Read out duration and status.
+  uint32_t out = 0;
+  dif_uart_toggle_t status = kDifUartToggleDisabled;
+  EXPECT_READ32(UART_TIMEOUT_CTRL_REG_OFFSET,
+                {{UART_TIMEOUT_CTRL_VAL_OFFSET, duration},
+                 {UART_TIMEOUT_CTRL_EN_BIT, true}});
+  EXPECT_EQ(dif_uart_get_rx_timeout(&uart_, &status, &out), kDifUartOk);
+  EXPECT_EQ(status, kDifUartToggleEnabled);
+  EXPECT_EQ(out, duration);
+}
+
 }  // namespace
 }  // namespace dif_uart_unittest