blob: 19fb4a53ba016ea3217512ca650ec38147aa3eca [file] [log] [blame]
// 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;
}