[sw, dif_uart] Add API useful for debugging

There could be many use-cases for the loopback functionality, one of
which is dif_uart_sanitytest.

`dif_uart_fifo_reset` API is added for flexibility, which is
particularily useful together with a loopback functionality.

Signed-off-by: Silvestrs Timofejevs <silvestrst@lowrisc.org>
diff --git a/sw/device/lib/dif/dif_uart.c b/sw/device/lib/dif/dif_uart.c
index f7480b6..3771a08 100644
--- a/sw/device/lib/dif/dif_uart.c
+++ b/sw/device/lib/dif/dif_uart.c
@@ -472,3 +472,50 @@
 
   return kDifUartOk;
 }
+
+dif_uart_result_t dif_uart_fifo_reset(const dif_uart_t *uart,
+                                      dif_uart_fifo_reset_t reset) {
+  if (uart == NULL) {
+    return kDifUartBadArg;
+  }
+
+  uint32_t reg = mmio_region_read32(uart->base_addr, UART_FIFO_CTRL_REG_OFFSET);
+
+  if (reset == kDifUartFifoResetRx || reset == kDifUartFifoResetAll) {
+    reg = bitfield_set_field32(
+        reg, (bitfield_field32_t){
+                 .mask = 0x1, .value = 1, .index = UART_FIFO_CTRL_RXRST,
+             });
+  }
+
+  if (reset == kDifUartFifoResetTx || reset == kDifUartFifoResetAll) {
+    reg = bitfield_set_field32(
+        reg, (bitfield_field32_t){
+                 .mask = 0x1, .value = 1, .index = UART_FIFO_CTRL_TXRST,
+             });
+  }
+
+  mmio_region_write32(uart->base_addr, UART_FIFO_CTRL_REG_OFFSET, reg);
+
+  return kDifUartOk;
+}
+
+dif_uart_result_t dif_uart_loopback_set(const dif_uart_t *uart,
+                                        dif_uart_loopback_t loopback,
+                                        dif_uart_enable_t enable) {
+  if (uart == NULL) {
+    return kDifUartBadArg;
+  }
+
+  bitfield_field32_t field = {
+      .mask = 0x1,
+      .index = loopback ? UART_CTRL_LLPBK : UART_CTRL_SLPBK,
+      .value = enable ? 1 : 0,
+  };
+
+  uint32_t reg = mmio_region_read32(uart->base_addr, UART_CTRL_REG_OFFSET);
+  reg = bitfield_set_field32(reg, field);
+  mmio_region_write32(uart->base_addr, UART_CTRL_REG_OFFSET, reg);
+
+  return kDifUartOk;
+}
diff --git a/sw/device/lib/dif/dif_uart.h b/sw/device/lib/dif/dif_uart.h
index 0f6857c..a8d93d6 100644
--- a/sw/device/lib/dif/dif_uart.h
+++ b/sw/device/lib/dif/dif_uart.h
@@ -60,13 +60,30 @@
 } dif_uart_watermark_t;
 
 /**
+ * UART TX/RX FIFO reset enumeration.
+ */
+typedef enum dif_uart_fifo_reset {
+  kDifUartFifoResetRx = 0, /**< Reset RX FIFO. */
+  kDifUartFifoResetTx,     /**< Reset TX FIFO. */
+  kDifUartFifoResetAll,    /**< All above. */
+} dif_uart_fifo_reset_t;
+
+/**
+ * UART System/Line loopback enumeration.
+ */
+typedef enum dif_uart_loopback {
+  kDifUartLoopbackSystem = 0, /**< Outgoing TX bits received through RX. */
+  kDifUartLoopbackLine,       /**< Incoming RX bits are forwarded to TX. */
+} dif_uart_loopback_t;
+
+/**
  * Generic enable/disable enumeration.
  *
  * Enumeration used to enable/disable bits, flags, ...
  */
 typedef enum dif_uart_enable {
-  kDifUartEnable = 0, /**< enable. */
-  kDifUartDisable,    /**< disable. */
+  kDifUartDisable = 0, /**< disable. */
+  kDifUartEnable,      /**< enable. */
 } dif_uart_enable_t;
 
 /**
@@ -365,6 +382,41 @@
 DIF_WARN_UNUSED_RESULT
 dif_uart_result_t dif_uart_tx_bytes_available(const dif_uart_t *uart,
                                               size_t *num_bytes);
+/**
+ * UART TX reset RX/TX FIFO.
+ *
+ * Reset both FIFOs, or the requested one.
+ *
+ * @param uart UART state data.
+ * @param reset FIFO to reset (RX or TX).
+ * @return `dif_uart_result_t`.
+ */
+dif_uart_result_t dif_uart_fifo_reset(const dif_uart_t *uart,
+                                      dif_uart_fifo_reset_t reset);
+
+/**
+ * UART enable/disable transmit/receive loopback.
+ *
+ * This API can be used for testing purpose. For example, to validate transmit
+ * and receive routines.
+ *
+ * Loopback should only be enabled when device is in the IDLE state to prevent
+ * data loss/coruption. Behaviour depends on the `loopback` parameter:
+ *    - `kDifUartLoopbackSystem`:
+ *      Receives the data that is being transmitted. No external data can be
+ *      received (from the RX line). When enabled the TX line goes high.
+ *    - `kDifUartLoopbackLine`:
+ *      Transmits the data that is being received. No internal data can be
+ *      sent out (from the TX FIFO). When enabled the RX line goes high.
+ *
+ * @param uart UART state data.
+ * @param loopback Loopback type (transmit/receive).
+ * @param enable Enable/disable control flag.
+ * @return `dif_uart_result_t`.
+ */
+dif_uart_result_t dif_uart_loopback_set(const dif_uart_t *uart,
+                                        dif_uart_loopback_t loopback,
+                                        dif_uart_enable_t enable);
 
 #ifdef __cplusplus
 }  // extern "C"
diff --git a/sw/device/tests/dif/dif_uart_unittest.cc b/sw/device/tests/dif/dif_uart_unittest.cc
index a2581e4..d63863d 100644
--- a/sw/device/tests/dif/dif_uart_unittest.cc
+++ b/sw/device/tests/dif/dif_uart_unittest.cc
@@ -736,5 +736,62 @@
   EXPECT_EQ(num_bytes, kDifUartFifoSizeBytes);
 }
 
+class FifoResetTest : public UartTest {};
+
+TEST_F(FifoResetTest, NullArgs) {
+  dif_uart_result_t result = dif_uart_fifo_reset(nullptr, kDifUartFifoResetRx);
+  EXPECT_EQ(result, kDifUartBadArg);
+}
+
+TEST_F(FifoResetTest, Success) {
+  EXPECT_MASK32(UART_FIFO_CTRL_REG_OFFSET, {{UART_FIFO_CTRL_RXRST, 0x1, true}});
+  dif_uart_result_t result =
+      dif_uart_fifo_reset(&dif_uart_, kDifUartFifoResetRx);
+  EXPECT_EQ(result, kDifUartOk);
+
+  EXPECT_MASK32(UART_FIFO_CTRL_REG_OFFSET, {{UART_FIFO_CTRL_TXRST, 0x1, true}});
+  result = dif_uart_fifo_reset(&dif_uart_, kDifUartFifoResetTx);
+  EXPECT_EQ(result, kDifUartOk);
+
+  EXPECT_MASK32(
+      UART_FIFO_CTRL_REG_OFFSET,
+      {
+          {UART_FIFO_CTRL_RXRST, 0x1, true}, {UART_FIFO_CTRL_TXRST, 0x1, true},
+      });
+
+  result = dif_uart_fifo_reset(&dif_uart_, kDifUartFifoResetAll);
+  EXPECT_EQ(result, kDifUartOk);
+}
+
+class LoopbackSetTest : public UartTest {};
+
+TEST_F(LoopbackSetTest, NullArgs) {
+  dif_uart_result_t result =
+      dif_uart_loopback_set(nullptr, kDifUartLoopbackSystem, kDifUartEnable);
+  EXPECT_EQ(result, kDifUartBadArg);
+}
+
+TEST_F(LoopbackSetTest, Success) {
+  EXPECT_MASK32(UART_CTRL_REG_OFFSET, {{UART_CTRL_SLPBK, 0x1, true}});
+  dif_uart_result_t result =
+      dif_uart_loopback_set(&dif_uart_, kDifUartLoopbackSystem, kDifUartEnable);
+  EXPECT_EQ(result, kDifUartOk);
+
+  EXPECT_MASK32(UART_CTRL_REG_OFFSET, {{UART_CTRL_SLPBK, 0x1, false}});
+  result = dif_uart_loopback_set(&dif_uart_, kDifUartLoopbackSystem,
+                                 kDifUartDisable);
+  EXPECT_EQ(result, kDifUartOk);
+
+  EXPECT_MASK32(UART_CTRL_REG_OFFSET, {{UART_CTRL_LLPBK, 0x1, true}});
+  result =
+      dif_uart_loopback_set(&dif_uart_, kDifUartLoopbackLine, kDifUartEnable);
+  EXPECT_EQ(result, kDifUartOk);
+
+  EXPECT_MASK32(UART_CTRL_REG_OFFSET, {{UART_CTRL_LLPBK, 0x1, false}});
+  result =
+      dif_uart_loopback_set(&dif_uart_, kDifUartLoopbackLine, kDifUartDisable);
+  EXPECT_EQ(result, kDifUartOk);
+}
+
 }  // namespace
 }  // namespace dif_uart_unittest