|  | // 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_uart.h" | 
|  |  | 
|  | #include <assert.h> | 
|  | #include <stddef.h> | 
|  |  | 
|  | #include "sw/device/lib/base/bitfield.h" | 
|  | #include "sw/device/lib/base/math.h" | 
|  | #include "sw/device/lib/base/mmio.h" | 
|  |  | 
|  | #include "uart_regs.h"  // Generated. | 
|  |  | 
|  | #define UART_INTR_STATE_MASK 0xffffffffu | 
|  |  | 
|  | const uint32_t kDifUartFifoSizeBytes = 32u; | 
|  |  | 
|  | static bool uart_tx_full(const dif_uart_t *uart) { | 
|  | uint32_t reg = mmio_region_read32(uart->base_addr, UART_STATUS_REG_OFFSET); | 
|  | return bitfield_bit32_read(reg, UART_STATUS_TXFULL_BIT); | 
|  | } | 
|  |  | 
|  | static bool uart_tx_idle(const dif_uart_t *uart) { | 
|  | uint32_t reg = mmio_region_read32(uart->base_addr, UART_STATUS_REG_OFFSET); | 
|  | return bitfield_bit32_read(reg, UART_STATUS_TXIDLE_BIT); | 
|  | } | 
|  |  | 
|  | static bool uart_rx_empty(const dif_uart_t *uart) { | 
|  | uint32_t reg = mmio_region_read32(uart->base_addr, UART_STATUS_REG_OFFSET); | 
|  | return bitfield_bit32_read(reg, UART_STATUS_RXEMPTY_BIT); | 
|  | } | 
|  |  | 
|  | static uint8_t uart_rx_fifo_read(const dif_uart_t *uart) { | 
|  | uint32_t reg = mmio_region_read32(uart->base_addr, UART_RDATA_REG_OFFSET); | 
|  |  | 
|  | return bitfield_field32_read(reg, UART_RDATA_RDATA_FIELD); | 
|  | } | 
|  |  | 
|  | static void uart_tx_fifo_write(const dif_uart_t *uart, uint8_t byte) { | 
|  | uint32_t reg = bitfield_field32_write(0, UART_WDATA_WDATA_FIELD, byte); | 
|  | mmio_region_write32(uart->base_addr, UART_WDATA_REG_OFFSET, reg); | 
|  | } | 
|  |  | 
|  | static void uart_reset(const dif_uart_t *uart) { | 
|  | mmio_region_write32(uart->base_addr, UART_CTRL_REG_OFFSET, 0u); | 
|  |  | 
|  | // Write to the relevant bits clears the FIFOs. | 
|  | uint32_t reg = 0; | 
|  | reg = bitfield_bit32_write(reg, UART_FIFO_CTRL_RXRST_BIT, true); | 
|  | reg = bitfield_bit32_write(reg, UART_FIFO_CTRL_TXRST_BIT, true); | 
|  | mmio_region_write32(uart->base_addr, UART_FIFO_CTRL_REG_OFFSET, reg); | 
|  |  | 
|  | mmio_region_write32(uart->base_addr, UART_OVRD_REG_OFFSET, 0u); | 
|  | mmio_region_write32(uart->base_addr, UART_TIMEOUT_CTRL_REG_OFFSET, 0u); | 
|  | mmio_region_write32(uart->base_addr, UART_INTR_ENABLE_REG_OFFSET, 0u); | 
|  | mmio_region_write32(uart->base_addr, UART_INTR_STATE_REG_OFFSET, | 
|  | UART_INTR_STATE_MASK); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Write up to `bytes_requested` number of bytes to the TX FIFO. | 
|  | */ | 
|  | static size_t uart_bytes_send(const dif_uart_t *uart, const uint8_t *data, | 
|  | size_t bytes_requested) { | 
|  | size_t bytes_written = 0; | 
|  | while ((bytes_written < bytes_requested) && !uart_tx_full(uart)) { | 
|  | uart_tx_fifo_write(uart, data[bytes_written]); | 
|  | ++bytes_written; | 
|  | } | 
|  |  | 
|  | return bytes_written; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Read up to `bytes_requested` number of bytes from the RX FIFO. | 
|  | */ | 
|  | static size_t uart_bytes_receive(const dif_uart_t *uart, size_t bytes_requested, | 
|  | uint8_t *data) { | 
|  | size_t bytes_read = 0; | 
|  | while ((bytes_read < bytes_requested) && !uart_rx_empty(uart)) { | 
|  | data[bytes_read] = uart_rx_fifo_read(uart); | 
|  | ++bytes_read; | 
|  | } | 
|  |  | 
|  | return bytes_read; | 
|  | } | 
|  |  | 
|  | dif_result_t dif_uart_configure(const dif_uart_t *uart, | 
|  | dif_uart_config_t config) { | 
|  | if (uart == NULL || config.baudrate == 0 || config.clk_freq_hz == 0 || | 
|  | !dif_is_valid_toggle(config.tx_enable) || | 
|  | !dif_is_valid_toggle(config.rx_enable)) { | 
|  | return kDifBadArg; | 
|  | } | 
|  |  | 
|  | // Calculation formula: NCO = 16 * 2^nco_width * baud / fclk. | 
|  |  | 
|  | // Compute NCO register bit width | 
|  | uint32_t nco_width = 0; | 
|  |  | 
|  | for (int i = 0; i < 32; i++) { | 
|  | nco_width += (UART_CTRL_NCO_MASK >> i) & 1; | 
|  | } | 
|  |  | 
|  | static_assert((UART_CTRL_NCO_MASK >> 28) == 0, | 
|  | "NCO bit width exceeds 28 bits."); | 
|  |  | 
|  | // NCO creates 16x of baudrate. So, in addition to the nco_width, | 
|  | // 2^4 should be multiplied. | 
|  | // If uart baud rate is 1.5Mbps and IO is 24Mhz, NCO is 0x10000, which is over | 
|  | // the NCO width, use NCO = 0xffff for this case since the error is tolerable. | 
|  | // Refer to #4263 | 
|  | uint64_t nco = | 
|  | ((uint64_t)config.baudrate == 1500000 && config.clk_freq_hz == 24000000) | 
|  | ? 0xffff | 
|  | : udiv64_slow((uint64_t)config.baudrate << (nco_width + 4), | 
|  | config.clk_freq_hz, NULL); | 
|  | uint32_t nco_masked = nco & UART_CTRL_NCO_MASK; | 
|  |  | 
|  | // Requested baudrate is too high for the given clock frequency. | 
|  | if (nco != nco_masked) { | 
|  | return kDifBadArg; | 
|  | } | 
|  |  | 
|  | // Must be called before the first write to any of the UART registers. | 
|  | uart_reset(uart); | 
|  |  | 
|  | // Set baudrate, enable RX and TX, configure parity. | 
|  | uint32_t reg = 0; | 
|  | reg = bitfield_field32_write(reg, UART_CTRL_NCO_FIELD, nco_masked); | 
|  | if (dif_toggle_to_bool(config.tx_enable)) { | 
|  | reg = bitfield_bit32_write(reg, UART_CTRL_TX_BIT, true); | 
|  | } | 
|  | if (dif_toggle_to_bool(config.rx_enable)) { | 
|  | reg = bitfield_bit32_write(reg, UART_CTRL_RX_BIT, true); | 
|  | } | 
|  | if (config.parity_enable == kDifToggleEnabled) { | 
|  | reg = bitfield_bit32_write(reg, UART_CTRL_PARITY_EN_BIT, true); | 
|  | } | 
|  | if (config.parity == kDifUartParityOdd) { | 
|  | reg = bitfield_bit32_write(reg, UART_CTRL_PARITY_ODD_BIT, true); | 
|  | } | 
|  | mmio_region_write32(uart->base_addr, UART_CTRL_REG_OFFSET, reg); | 
|  |  | 
|  | // Disable interrupts. | 
|  | mmio_region_write32(uart->base_addr, UART_INTR_ENABLE_REG_OFFSET, 0u); | 
|  |  | 
|  | return kDifOk; | 
|  | } | 
|  |  | 
|  | dif_result_t dif_uart_watermark_rx_set(const dif_uart_t *uart, | 
|  | dif_uart_watermark_t watermark) { | 
|  | if (uart == NULL) { | 
|  | return kDifBadArg; | 
|  | } | 
|  |  | 
|  | // Check if the requested watermark is valid, and get a corresponding | 
|  | // register definition to be written. | 
|  | uint32_t value; | 
|  | switch (watermark) { | 
|  | case kDifUartWatermarkByte1: | 
|  | value = UART_FIFO_CTRL_RXILVL_VALUE_RXLVL1; | 
|  | break; | 
|  | case kDifUartWatermarkByte4: | 
|  | value = UART_FIFO_CTRL_RXILVL_VALUE_RXLVL4; | 
|  | break; | 
|  | case kDifUartWatermarkByte8: | 
|  | value = UART_FIFO_CTRL_RXILVL_VALUE_RXLVL8; | 
|  | break; | 
|  | case kDifUartWatermarkByte16: | 
|  | value = UART_FIFO_CTRL_RXILVL_VALUE_RXLVL16; | 
|  | break; | 
|  | case kDifUartWatermarkByte30: | 
|  | value = UART_FIFO_CTRL_RXILVL_VALUE_RXLVL30; | 
|  | break; | 
|  | default: | 
|  | return kDifError; | 
|  | } | 
|  |  | 
|  | // Set watermark level. | 
|  | uint32_t reg = mmio_region_read32(uart->base_addr, UART_FIFO_CTRL_REG_OFFSET); | 
|  | reg = bitfield_field32_write(reg, UART_FIFO_CTRL_RXILVL_FIELD, value); | 
|  | mmio_region_write32(uart->base_addr, UART_FIFO_CTRL_REG_OFFSET, reg); | 
|  |  | 
|  | return kDifOk; | 
|  | } | 
|  |  | 
|  | dif_result_t dif_uart_watermark_tx_set(const dif_uart_t *uart, | 
|  | dif_uart_watermark_t watermark) { | 
|  | if (uart == NULL) { | 
|  | return kDifBadArg; | 
|  | } | 
|  |  | 
|  | // Check if the requested watermark is valid, and get a corresponding | 
|  | // register definition to be written. | 
|  | uint32_t value; | 
|  | switch (watermark) { | 
|  | case kDifUartWatermarkByte1: | 
|  | value = UART_FIFO_CTRL_TXILVL_VALUE_TXLVL1; | 
|  | break; | 
|  | case kDifUartWatermarkByte4: | 
|  | value = UART_FIFO_CTRL_TXILVL_VALUE_TXLVL4; | 
|  | break; | 
|  | case kDifUartWatermarkByte8: | 
|  | value = UART_FIFO_CTRL_TXILVL_VALUE_TXLVL8; | 
|  | break; | 
|  | case kDifUartWatermarkByte16: | 
|  | value = UART_FIFO_CTRL_TXILVL_VALUE_TXLVL16; | 
|  | break; | 
|  | default: | 
|  | // The minimal TX watermark is 1 byte, maximal 16 bytes. | 
|  | return kDifError; | 
|  | } | 
|  |  | 
|  | // Set watermark level. | 
|  | uint32_t reg = mmio_region_read32(uart->base_addr, UART_FIFO_CTRL_REG_OFFSET); | 
|  | reg = bitfield_field32_write(reg, UART_FIFO_CTRL_TXILVL_FIELD, value); | 
|  | mmio_region_write32(uart->base_addr, UART_FIFO_CTRL_REG_OFFSET, reg); | 
|  |  | 
|  | return kDifOk; | 
|  | } | 
|  |  | 
|  | dif_result_t dif_uart_set_enable(const dif_uart_t *uart, | 
|  | dif_uart_datapath_t datapath, | 
|  | dif_toggle_t enabled) { | 
|  | if (uart == NULL || !dif_is_valid_toggle(enabled)) { | 
|  | return kDifBadArg; | 
|  | } | 
|  |  | 
|  | uint32_t reg = mmio_region_read32(uart->base_addr, UART_CTRL_REG_OFFSET); | 
|  |  | 
|  | switch (datapath) { | 
|  | case kDifUartDatapathRx: | 
|  | reg = bitfield_bit32_write(reg, UART_CTRL_RX_BIT, | 
|  | dif_toggle_to_bool(enabled)); | 
|  | break; | 
|  | case kDifUartDatapathTx: | 
|  | reg = bitfield_bit32_write(reg, UART_CTRL_TX_BIT, | 
|  | dif_toggle_to_bool(enabled)); | 
|  | break; | 
|  | case kDifUartDatapathAll: | 
|  | reg = bitfield_bit32_write(reg, UART_CTRL_RX_BIT, | 
|  | dif_toggle_to_bool(enabled)); | 
|  | reg = bitfield_bit32_write(reg, UART_CTRL_TX_BIT, | 
|  | dif_toggle_to_bool(enabled)); | 
|  | break; | 
|  | default: | 
|  | return kDifBadArg; | 
|  | } | 
|  |  | 
|  | mmio_region_write32(uart->base_addr, UART_CTRL_REG_OFFSET, reg); | 
|  |  | 
|  | return kDifOk; | 
|  | } | 
|  |  | 
|  | dif_result_t dif_uart_bytes_send(const dif_uart_t *uart, const uint8_t *data, | 
|  | size_t bytes_requested, | 
|  | size_t *bytes_written) { | 
|  | if (uart == NULL || data == NULL) { | 
|  | return kDifBadArg; | 
|  | } | 
|  |  | 
|  | // `bytes_written` is an optional parameter. | 
|  | size_t res = uart_bytes_send(uart, data, bytes_requested); | 
|  | if (bytes_written != NULL) { | 
|  | *bytes_written = res; | 
|  | } | 
|  |  | 
|  | return kDifOk; | 
|  | } | 
|  |  | 
|  | dif_result_t dif_uart_bytes_receive(const dif_uart_t *uart, | 
|  | size_t bytes_requested, uint8_t *data, | 
|  | size_t *bytes_read) { | 
|  | if (uart == NULL || data == NULL) { | 
|  | return kDifBadArg; | 
|  | } | 
|  |  | 
|  | // `bytes_read` is an optional parameter. | 
|  | size_t res = uart_bytes_receive(uart, bytes_requested, data); | 
|  | if (bytes_read != NULL) { | 
|  | *bytes_read = res; | 
|  | } | 
|  |  | 
|  | return kDifOk; | 
|  | } | 
|  |  | 
|  | dif_result_t dif_uart_byte_send_polled(const dif_uart_t *uart, uint8_t byte) { | 
|  | if (uart == NULL) { | 
|  | return kDifBadArg; | 
|  | } | 
|  |  | 
|  | // Busy wait for the TX FIFO to free up. | 
|  | while (uart_tx_full(uart)) { | 
|  | } | 
|  |  | 
|  | (void)uart_bytes_send(uart, &byte, 1); | 
|  |  | 
|  | // Busy wait for the TX FIFO to be drained and for HW to finish processing | 
|  | // the last byte. | 
|  | while (!uart_tx_idle(uart)) { | 
|  | } | 
|  |  | 
|  | return kDifOk; | 
|  | } | 
|  |  | 
|  | dif_result_t dif_uart_byte_receive_polled(const dif_uart_t *uart, | 
|  | uint8_t *byte) { | 
|  | if (uart == NULL || byte == NULL) { | 
|  | return kDifBadArg; | 
|  | } | 
|  |  | 
|  | // Busy wait for the RX message in the FIFO. | 
|  | while (uart_rx_empty(uart)) { | 
|  | } | 
|  |  | 
|  | (void)uart_bytes_receive(uart, 1, byte); | 
|  |  | 
|  | return kDifOk; | 
|  | } | 
|  |  | 
|  | dif_result_t dif_uart_rx_bytes_available(const dif_uart_t *uart, | 
|  | size_t *num_bytes) { | 
|  | if (uart == NULL || num_bytes == NULL) { | 
|  | return kDifBadArg; | 
|  | } | 
|  |  | 
|  | // RX FIFO fill level (in bytes). | 
|  | uint32_t reg = | 
|  | mmio_region_read32(uart->base_addr, UART_FIFO_STATUS_REG_OFFSET); | 
|  | *num_bytes = (size_t)bitfield_field32_read(reg, UART_FIFO_STATUS_RXLVL_FIELD); | 
|  |  | 
|  | return kDifOk; | 
|  | } | 
|  |  | 
|  | dif_result_t dif_uart_tx_bytes_available(const dif_uart_t *uart, | 
|  | size_t *num_bytes) { | 
|  | if (uart == NULL || num_bytes == NULL) { | 
|  | return kDifBadArg; | 
|  | } | 
|  |  | 
|  | // TX FIFO fill level (in bytes). | 
|  | uint32_t reg = | 
|  | mmio_region_read32(uart->base_addr, UART_FIFO_STATUS_REG_OFFSET); | 
|  | uint32_t fill_bytes = | 
|  | bitfield_field32_read(reg, UART_FIFO_STATUS_TXLVL_FIELD); | 
|  | *num_bytes = kDifUartFifoSizeBytes - fill_bytes; | 
|  |  | 
|  | return kDifOk; | 
|  | } | 
|  |  | 
|  | dif_result_t dif_uart_fifo_reset(const dif_uart_t *uart, | 
|  | dif_uart_datapath_t fifo) { | 
|  | if (uart == NULL) { | 
|  | return kDifBadArg; | 
|  | } | 
|  |  | 
|  | uint32_t reg = mmio_region_read32(uart->base_addr, UART_FIFO_CTRL_REG_OFFSET); | 
|  |  | 
|  | switch (fifo) { | 
|  | case kDifUartDatapathRx: | 
|  | reg = bitfield_bit32_write(reg, UART_FIFO_CTRL_RXRST_BIT, true); | 
|  | break; | 
|  | case kDifUartDatapathTx: | 
|  | reg = bitfield_bit32_write(reg, UART_FIFO_CTRL_TXRST_BIT, true); | 
|  | break; | 
|  | case kDifUartDatapathAll: | 
|  | reg = bitfield_bit32_write(reg, UART_FIFO_CTRL_RXRST_BIT, true); | 
|  | reg = bitfield_bit32_write(reg, UART_FIFO_CTRL_TXRST_BIT, true); | 
|  | break; | 
|  | default: | 
|  | return kDifBadArg; | 
|  | } | 
|  |  | 
|  | mmio_region_write32(uart->base_addr, UART_FIFO_CTRL_REG_OFFSET, reg); | 
|  |  | 
|  | return kDifOk; | 
|  | } | 
|  |  | 
|  | dif_result_t dif_uart_loopback_set(const dif_uart_t *uart, | 
|  | dif_uart_loopback_t loopback, | 
|  | dif_toggle_t enable) { | 
|  | if (uart == NULL) { | 
|  | return kDifBadArg; | 
|  | } | 
|  |  | 
|  | uint32_t index = loopback ? UART_CTRL_LLPBK_BIT : UART_CTRL_SLPBK_BIT; | 
|  | uint32_t reg = mmio_region_read32(uart->base_addr, UART_CTRL_REG_OFFSET); | 
|  | reg = bitfield_bit32_write(reg, index, enable == kDifToggleEnabled); | 
|  | mmio_region_write32(uart->base_addr, UART_CTRL_REG_OFFSET, reg); | 
|  |  | 
|  | return kDifOk; | 
|  | } | 
|  |  | 
|  | dif_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 kDifBadArg; | 
|  | } | 
|  |  | 
|  | 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->base_addr, UART_TIMEOUT_CTRL_REG_OFFSET, reg); | 
|  |  | 
|  | return kDifOk; | 
|  | } | 
|  |  | 
|  | dif_result_t dif_uart_disable_rx_timeout(const dif_uart_t *uart) { | 
|  | if (uart == NULL) { | 
|  | return kDifBadArg; | 
|  | } | 
|  |  | 
|  | 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->base_addr, UART_TIMEOUT_CTRL_REG_OFFSET, reg); | 
|  |  | 
|  | return kDifOk; | 
|  | } | 
|  |  | 
|  | dif_result_t dif_uart_get_rx_timeout(const dif_uart_t *uart, | 
|  | dif_toggle_t *status, | 
|  | uint32_t *duration_ticks) { | 
|  | if (uart == NULL || status == NULL) { | 
|  | return kDifBadArg; | 
|  | } | 
|  |  | 
|  | uint32_t reg = | 
|  | mmio_region_read32(uart->base_addr, UART_TIMEOUT_CTRL_REG_OFFSET); | 
|  | *status = bitfield_bit32_read(reg, UART_TIMEOUT_CTRL_EN_BIT) | 
|  | ? kDifToggleEnabled | 
|  | : kDifToggleDisabled; | 
|  |  | 
|  | if (duration_ticks != NULL) { | 
|  | *duration_ticks = bitfield_field32_read(reg, UART_TIMEOUT_CTRL_VAL_FIELD); | 
|  | } | 
|  |  | 
|  | return kDifOk; | 
|  | } |