| /* |
| * SPDX-License-Identifier: BSD-2-Clause |
| * |
| * Copyright (c) 2022 StarFive Technology Co., Ltd. |
| * |
| * Author: Jun Liang Tan <junliang.tan@linux.starfivetech.com> |
| */ |
| |
| #include <sbi/sbi_domain.h> |
| #include <sbi/riscv_asm.h> |
| #include <sbi/riscv_io.h> |
| #include <sbi/sbi_console.h> |
| #include <sbi_utils/serial/cadence-uart.h> |
| |
| /* clang-format off */ |
| |
| #define UART_REG_CTRL 0x00 |
| #define UART_REG_MODE 0x04 |
| #define UART_REG_IDR 0x0C |
| #define UART_REG_BRGR 0x18 |
| #define UART_REG_CSR 0x2C |
| #define UART_REG_RFIFO_TFIFO 0x30 |
| #define UART_REG_BDIVR 0x34 |
| |
| #define UART_CTRL_RXRES 0x00000001 |
| #define UART_CTRL_TXRES 0x00000002 |
| #define UART_CTRL_RXEN 0x00000004 |
| #define UART_CTRL_RXDIS 0x00000008 |
| #define UART_CTRL_TXEN 0x00000010 |
| #define UART_CTRL_TXDIS 0x00000020 |
| |
| #define UART_MODE_PAR_NONE 0x00000020 /* No parity set */ |
| |
| #define UART_BRGR_CD_CLKDIVISOR 0x00000001 /* baud_sample = sel_clk */ |
| |
| #define UART_CSR_REMPTY 0x00000002 |
| #define UART_CSR_TFUL 0x00000010 |
| |
| /* clang-format on */ |
| |
| static volatile void *uart_base; |
| static u32 uart_in_freq; |
| static u32 uart_baudrate; |
| |
| /* |
| * Find minimum divisor divides in_freq to max_target_hz; |
| * Based on SiFive UART driver (sifive-uart.c) |
| */ |
| 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 offset) |
| { |
| return readl(uart_base + offset); |
| } |
| |
| static void set_reg(u32 offset, u32 val) |
| { |
| writel(val, uart_base + offset); |
| } |
| |
| static void cadence_uart_putc(char ch) |
| { |
| while (get_reg(UART_REG_CSR) & UART_CSR_TFUL) |
| ; |
| |
| set_reg(UART_REG_RFIFO_TFIFO, ch); |
| } |
| |
| static int cadence_uart_getc(void) |
| { |
| u32 ret = get_reg(UART_REG_CSR); |
| |
| if (!(ret & UART_CSR_REMPTY)) |
| return get_reg(UART_REG_RFIFO_TFIFO); |
| |
| return -1; |
| } |
| |
| static struct sbi_console_device cadence_console = { |
| .name = "cadence_uart", |
| .console_putc = cadence_uart_putc, |
| .console_getc = cadence_uart_getc |
| }; |
| |
| int cadence_uart_init(unsigned long base, u32 in_freq, u32 baudrate) |
| { |
| uart_base = (volatile void *)base; |
| uart_in_freq = in_freq; |
| uart_baudrate = baudrate; |
| |
| /* Disable interrupts */ |
| set_reg(UART_REG_IDR, 0xFFFFFFFF); |
| |
| /* Disable TX RX */ |
| set_reg(UART_REG_CTRL, UART_CTRL_TXDIS | UART_CTRL_RXDIS); |
| |
| /* Configure baudrate */ |
| if (in_freq && baudrate) { |
| set_reg(UART_REG_BRGR, UART_BRGR_CD_CLKDIVISOR); |
| set_reg(UART_REG_BDIVR, |
| uart_min_clk_divisor(in_freq, baudrate)); |
| } |
| |
| /* Software reset TX RX data path and enable TX RX */ |
| set_reg(UART_REG_CTRL, UART_CTRL_TXRES | UART_CTRL_RXRES |
| | UART_CTRL_TXEN | UART_CTRL_RXEN); |
| |
| /* |
| * Set: |
| * 1 stop bit, bits[07:06] = 0x00, |
| * no parity set, bits[05:03] = 0x100, |
| * 8 bits character length, bits[02:01] = 0x00, |
| * sel_clk = uart_clk, bit[0] = 0x0 |
| */ |
| set_reg(UART_REG_MODE, UART_MODE_PAR_NONE); |
| |
| sbi_console_set_device(&cadence_console); |
| |
| return sbi_domain_root_add_memrange(base, PAGE_SIZE, PAGE_SIZE, |
| (SBI_DOMAIN_MEMREGION_MMIO | |
| SBI_DOMAIN_MEMREGION_SHARED_SURW_MRW)); |
| } |