1 /* SPDX-License-Identifier: GPL-2.0-only */
2
3 #include <device/mmio.h>
4 #include <boot/coreboot_tables.h>
5 #include <console/uart.h>
6 #include <types.h>
7
8 /*
9 * This is a driver for SiFive's own UART, documented in the FU540 manual:
10 * https://www.sifive.com/documentation/chips/freedom-u540-c000-manual/
11 */
12
13 struct sifive_uart_registers {
14 uint32_t txdata; /* Transmit data register */
15 uint32_t rxdata; /* Receive data register */
16 uint32_t txctrl; /* Transmit control register */
17 uint32_t rxctrl; /* Receive control register */
18 uint32_t ie; /* UART interrupt enable */
19 uint32_t ip; /* UART interrupt pending */
20 uint32_t div; /* Baud rate divisor */
21 } __packed;
22
23 #define TXDATA_FULL BIT(31)
24 #define RXDATA_EMPTY BIT(31)
25 #define TXCTRL_TXEN BIT(0)
26 #define TXCTRL_NSTOP_SHIFT 1
27 #define TXCTRL_NSTOP(x) (((x)-1) << TXCTRL_NSTOP_SHIFT)
28 #define TXCTRL_TXCNT_SHIFT 16
29 #define TXCTRL_TXCNT(x) ((x) << TXCTRL_TXCNT_SHIFT)
30 #define RXCTRL_RXEN BIT(0)
31 #define RXCTRL_RXCNT_SHIFT 16
32 #define RXCTRL_RXCNT(x) ((x) << RXCTRL_RXCNT_SHIFT)
33 #define IP_TXWM BIT(0)
34 #define IP_RXWM BIT(1)
35
sifive_uart_init(struct sifive_uart_registers * regs,int div)36 static void sifive_uart_init(struct sifive_uart_registers *regs, int div)
37 {
38 /* Configure the divisor */
39 write32(®s->div, div);
40
41 /* Enable transmission, one stop bit, transmit watermark at 1 */
42 write32(®s->txctrl, TXCTRL_TXEN|TXCTRL_NSTOP(1)|TXCTRL_TXCNT(1));
43
44 /* Enable reception, receive watermark at 0 */
45 write32(®s->rxctrl, RXCTRL_RXEN|RXCTRL_RXCNT(0));
46 }
47
uart_init(unsigned int idx)48 void uart_init(unsigned int idx)
49 {
50 /*
51 * according to FU540/FU740 manual:
52 * f_baud = f_in / (div + 1)
53 * <=>
54 * div = (f_in / f_baud) - 1
55 */
56 unsigned int div = uart_baudrate_divisor(get_uart_baudrate(), uart_platform_refclk(),
57 uart_input_clock_divider());
58 div -= 1;
59
60 sifive_uart_init(uart_platform_baseptr(idx), div);
61 }
62
uart_can_tx(struct sifive_uart_registers * regs)63 static bool uart_can_tx(struct sifive_uart_registers *regs)
64 {
65 return !(read32(®s->txdata) & TXDATA_FULL);
66 }
67
uart_tx_byte(unsigned int idx,unsigned char data)68 void uart_tx_byte(unsigned int idx, unsigned char data)
69 {
70 struct sifive_uart_registers *regs = uart_platform_baseptr(idx);
71
72 while (!uart_can_tx(regs))
73 ; /* TODO: implement a timeout */
74
75 write32(®s->txdata, data);
76 }
77
uart_tx_flush(unsigned int idx)78 void uart_tx_flush(unsigned int idx)
79 {
80 struct sifive_uart_registers *regs = uart_platform_baseptr(idx);
81 uint32_t ip;
82
83 /* Use the TX watermark bit to find out if the TX FIFO is empty */
84 do {
85 ip = read32(®s->ip);
86 } while (!(ip & IP_TXWM));
87 }
88
uart_rx_byte(unsigned int idx)89 unsigned char uart_rx_byte(unsigned int idx)
90 {
91 struct sifive_uart_registers *regs = uart_platform_baseptr(idx);
92 uint32_t rxdata;
93
94 do {
95 rxdata = read32(®s->rxdata);
96 } while (rxdata & RXDATA_EMPTY);
97
98 return rxdata & 0xff;
99 }
100
uart_input_clock_divider(void)101 unsigned int uart_input_clock_divider(void)
102 {
103 /*
104 * The SiFive UART handles oversampling internally. The divided clock
105 * is the baud clock.
106 */
107 return 1;
108 }
109
fill_lb_serial(struct lb_serial * serial)110 enum cb_err fill_lb_serial(struct lb_serial *serial)
111 {
112 return CB_ERR;
113 /* TODO */
114 }
115