mirror of
https://github.com/riscv-software-src/opensbi.git
synced 2025-08-25 07:41:42 +01:00

RISC-V doesn't generate exceptions on divide-by-zero, but the result, all bits set, is not likely what people expect either. In all cases where we divide by baudrate there's a chance it's zero (when the DT it came from is "bad"). To avoid difficult to debug situations, leave baudrate dependent registers alone when baudrate is zero, as, also in all cases, it appears we can skip initialization of those registers and still [hopefully] have a functioning UART. Signed-off-by: Andrew Jones <ajones@ventanamicro.com> Reviewed-by: Anup Patel <anup@brainfault.org>
116 lines
2.5 KiB
C
116 lines
2.5 KiB
C
/*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*
|
|
* Copyright (c) 2019 Western Digital Corporation or its affiliates.
|
|
*
|
|
* Authors:
|
|
* Anup Patel <anup.patel@wdc.com>
|
|
*/
|
|
|
|
#include <sbi/riscv_io.h>
|
|
#include <sbi/sbi_console.h>
|
|
#include <sbi_utils/serial/sifive-uart.h>
|
|
|
|
/* clang-format off */
|
|
|
|
#define UART_REG_TXFIFO 0
|
|
#define UART_REG_RXFIFO 1
|
|
#define UART_REG_TXCTRL 2
|
|
#define UART_REG_RXCTRL 3
|
|
#define UART_REG_IE 4
|
|
#define UART_REG_IP 5
|
|
#define UART_REG_DIV 6
|
|
|
|
#define UART_TXFIFO_FULL 0x80000000
|
|
#define UART_RXFIFO_EMPTY 0x80000000
|
|
#define UART_RXFIFO_DATA 0x000000ff
|
|
#define UART_TXCTRL_TXEN 0x1
|
|
#define UART_RXCTRL_RXEN 0x1
|
|
|
|
/* clang-format on */
|
|
|
|
static volatile char *uart_base;
|
|
static u32 uart_in_freq;
|
|
static u32 uart_baudrate;
|
|
|
|
/**
|
|
* Find minimum divisor divides in_freq to max_target_hz;
|
|
* Based on uart driver n SiFive FSBL.
|
|
*
|
|
* f_baud = f_in / (div + 1) => div = (f_in / f_baud) - 1
|
|
* The nearest integer solution requires rounding up as to not exceed max_target_hz.
|
|
* div = ceil(f_in / f_baud) - 1
|
|
* = floor((f_in - 1 + f_baud) / f_baud) - 1
|
|
* This should not overflow as long as (f_in - 1 + f_baud) does not exceed
|
|
* 2^32 - 1, which is unlikely since we represent frequencies in kHz.
|
|
*/
|
|
static inline unsigned int uart_min_clk_divisor(uint64_t in_freq,
|
|
uint64_t max_target_hz)
|
|
{
|
|
uint64_t quotient = (in_freq + max_target_hz - 1) / (max_target_hz);
|
|
|
|
/* Avoid underflow */
|
|
if (quotient == 0)
|
|
return 0;
|
|
else
|
|
return quotient - 1;
|
|
}
|
|
|
|
static u32 get_reg(u32 num)
|
|
{
|
|
return readl(uart_base + (num * 0x4));
|
|
}
|
|
|
|
static void set_reg(u32 num, u32 val)
|
|
{
|
|
writel(val, uart_base + (num * 0x4));
|
|
}
|
|
|
|
static void sifive_uart_putc(char ch)
|
|
{
|
|
while (get_reg(UART_REG_TXFIFO) & UART_TXFIFO_FULL)
|
|
;
|
|
|
|
set_reg(UART_REG_TXFIFO, ch);
|
|
}
|
|
|
|
static int sifive_uart_getc(void)
|
|
{
|
|
u32 ret = get_reg(UART_REG_RXFIFO);
|
|
|
|
if (!(ret & UART_RXFIFO_EMPTY))
|
|
return ret & UART_RXFIFO_DATA;
|
|
|
|
return -1;
|
|
}
|
|
|
|
static struct sbi_console_device sifive_console = {
|
|
.name = "sifive_uart",
|
|
.console_putc = sifive_uart_putc,
|
|
.console_getc = sifive_uart_getc
|
|
};
|
|
|
|
int sifive_uart_init(unsigned long base, u32 in_freq, u32 baudrate)
|
|
{
|
|
uart_base = (volatile char *)base;
|
|
uart_in_freq = in_freq;
|
|
uart_baudrate = baudrate;
|
|
|
|
/* Configure baudrate */
|
|
if (in_freq && baudrate)
|
|
set_reg(UART_REG_DIV, uart_min_clk_divisor(in_freq, baudrate));
|
|
|
|
/* Disable interrupts */
|
|
set_reg(UART_REG_IE, 0);
|
|
|
|
/* Enable TX */
|
|
set_reg(UART_REG_TXCTRL, UART_TXCTRL_TXEN);
|
|
|
|
/* Enable Rx */
|
|
set_reg(UART_REG_RXCTRL, UART_RXCTRL_RXEN);
|
|
|
|
sbi_console_set_device(&sifive_console);
|
|
|
|
return 0;
|
|
}
|