blob: 2e8098aa71fc04ac2dd44be672ecb654174199c5 [file] [log] [blame]
/* Copyright 2018 SiFive, Inc */
/* SPDX-License-Identifier: Apache-2.0 */
#include <metal/machine/platform.h>
#ifdef METAL_SIFIVE_UART0
#include <metal/drivers/sifive_uart0.h>
#include <metal/machine.h>
/* TXDATA Fields */
#define UART_TXEN (1 << 0)
#define UART_TXFULL (1 << 31)
/* RXDATA Fields */
#define UART_RXEN (1 << 0)
#define UART_RXEMPTY (1 << 31)
/* TXCTRL Fields */
#define UART_NSTOP (1 << 1)
#define UART_TXCNT(count) ((0x7 & count) << 16)
/* IP Fields */
#define UART_TXWM (1 << 0)
#define UART_REG(offset) (((unsigned long)control_base + offset))
#define UART_REGB(offset) (__METAL_ACCESS_ONCE((__metal_io_u8 *)UART_REG(offset)))
#define UART_REGW(offset) (__METAL_ACCESS_ONCE((__metal_io_u32 *)UART_REG(offset)))
struct metal_interrupt *
__metal_driver_sifive_uart0_interrupt_controller(struct metal_uart *uart)
{
return __metal_driver_sifive_uart0_interrupt_parent(uart);
}
int __metal_driver_sifive_uart0_get_interrupt_id(struct metal_uart *uart)
{
return (__metal_driver_sifive_uart0_interrupt_line(uart) + METAL_INTERRUPT_ID_GL0);
}
int __metal_driver_sifive_uart0_txready(struct metal_uart *uart)
{
long control_base = __metal_driver_sifive_uart0_control_base(uart);
return !((UART_REGW(METAL_SIFIVE_UART0_TXDATA) & UART_TXFULL));
}
int __metal_driver_sifive_uart0_putc(struct metal_uart *uart, int c)
{
long control_base = __metal_driver_sifive_uart0_control_base(uart);
while (!__metal_driver_sifive_uart0_txready(uart)) {
/* wait */
}
UART_REGW(METAL_SIFIVE_UART0_TXDATA) = c;
return 0;
}
int __metal_driver_sifive_uart0_getc(struct metal_uart *uart, int *c)
{
uint32_t ch;
long control_base = __metal_driver_sifive_uart0_control_base(uart);
/* No seperate status register, we get status and the byte at same time */
ch = UART_REGW(METAL_SIFIVE_UART0_RXDATA);;
if( ch & UART_RXEMPTY ){
*c = -1; /* aka: EOF in most of the world */
} else {
*c = ch & 0x0ff;
}
return 0;
}
int __metal_driver_sifive_uart0_get_baud_rate(struct metal_uart *guart)
{
struct __metal_driver_sifive_uart0 *uart = (void *)guart;
return uart->baud_rate;
}
int __metal_driver_sifive_uart0_set_baud_rate(struct metal_uart *guart, int baud_rate)
{
struct __metal_driver_sifive_uart0 *uart = (void *)guart;
long control_base = __metal_driver_sifive_uart0_control_base(guart);
struct metal_clock *clock = __metal_driver_sifive_uart0_clock(guart);
uart->baud_rate = baud_rate;
if (clock != NULL) {
long clock_rate = clock->vtable->get_rate_hz(clock);
UART_REGW(METAL_SIFIVE_UART0_DIV) = clock_rate / baud_rate - 1;
UART_REGW(METAL_SIFIVE_UART0_TXCTRL) |= UART_TXEN;
UART_REGW(METAL_SIFIVE_UART0_RXCTRL) |= UART_RXEN;
}
return 0;
}
static void pre_rate_change_callback_func(void *priv)
{
struct __metal_driver_sifive_uart0 *uart = priv;
long control_base = __metal_driver_sifive_uart0_control_base((struct metal_uart *)priv);
struct metal_clock *clock = __metal_driver_sifive_uart0_clock((struct metal_uart *)priv);
/* Detect when the TXDATA is empty by setting the transmit watermark count
* to one and waiting until an interrupt is pending */
UART_REGW(METAL_SIFIVE_UART0_TXCTRL) &= ~(UART_TXCNT(0x7));
UART_REGW(METAL_SIFIVE_UART0_TXCTRL) |= UART_TXCNT(1);
while((UART_REGW(METAL_SIFIVE_UART0_IP) & UART_TXWM) == 0) ;
/* When the TXDATA clears, the UART is still shifting out the last byte.
* Calculate the time we must drain to finish transmitting and then wait
* that long. */
long bits_per_symbol = (UART_REGW(METAL_SIFIVE_UART0_TXCTRL) & (1 << 1)) ? 9 : 10;
long clk_freq = clock->vtable->get_rate_hz(clock);
long cycles_to_wait = bits_per_symbol * clk_freq / uart->baud_rate;
for(volatile long x = 0; x < cycles_to_wait; x++)
__asm__("nop");
}
static void post_rate_change_callback_func(void *priv)
{
struct __metal_driver_sifive_uart0 *uart = priv;
metal_uart_set_baud_rate(&uart->uart, uart->baud_rate);
}
void __metal_driver_sifive_uart0_init(struct metal_uart *guart, int baud_rate)
{
struct __metal_driver_sifive_uart0 *uart = (void *)(guart);
struct metal_clock *clock = __metal_driver_sifive_uart0_clock(guart);
struct __metal_driver_sifive_gpio0 *pinmux = __metal_driver_sifive_uart0_pinmux(guart);
if(clock != NULL) {
uart->pre_rate_change_callback.callback = &pre_rate_change_callback_func;
uart->pre_rate_change_callback.priv = guart;
metal_clock_register_pre_rate_change_callback(clock, &(uart->pre_rate_change_callback));
uart->post_rate_change_callback.callback = &post_rate_change_callback_func;
uart->post_rate_change_callback.priv = guart;
metal_clock_register_post_rate_change_callback(clock, &(uart->post_rate_change_callback));
}
metal_uart_set_baud_rate(&(uart->uart), baud_rate);
if (pinmux != NULL) {
long pinmux_output_selector = __metal_driver_sifive_uart0_pinmux_output_selector(guart);
long pinmux_source_selector = __metal_driver_sifive_uart0_pinmux_source_selector(guart);
pinmux->gpio.vtable->enable_io(
(struct metal_gpio *) pinmux,
pinmux_output_selector,
pinmux_source_selector
);
}
}
__METAL_DEFINE_VTABLE(__metal_driver_vtable_sifive_uart0) = {
.uart.init = __metal_driver_sifive_uart0_init,
.uart.putc = __metal_driver_sifive_uart0_putc,
.uart.getc = __metal_driver_sifive_uart0_getc,
.uart.get_baud_rate = __metal_driver_sifive_uart0_get_baud_rate,
.uart.set_baud_rate = __metal_driver_sifive_uart0_set_baud_rate,
.uart.controller_interrupt = __metal_driver_sifive_uart0_interrupt_controller,
.uart.get_interrupt_id = __metal_driver_sifive_uart0_get_interrupt_id,
};
#endif /* METAL_SIFIVE_UART0 */
typedef int no_empty_translation_units;