mirror of
https://github.com/AsahiLinux/u-boot
synced 2024-11-25 14:10:43 +00:00
2ae80437fb
- Merge the patch to take <asm/global_data.h> out of <common.h>
233 lines
5.1 KiB
C
233 lines
5.1 KiB
C
// SPDX-License-Identifier: GPL-2.0+
|
|
/*
|
|
* Copyright (C) 2018 Anup Patel <anup@brainfault.org>
|
|
*/
|
|
|
|
#include <common.h>
|
|
#include <clk.h>
|
|
#include <debug_uart.h>
|
|
#include <dm.h>
|
|
#include <errno.h>
|
|
#include <fdtdec.h>
|
|
#include <log.h>
|
|
#include <watchdog.h>
|
|
#include <asm/global_data.h>
|
|
#include <asm/io.h>
|
|
#include <linux/compiler.h>
|
|
#include <serial.h>
|
|
#include <linux/err.h>
|
|
|
|
DECLARE_GLOBAL_DATA_PTR;
|
|
|
|
#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
|
|
|
|
/* IP register */
|
|
#define UART_IP_RXWM 0x2
|
|
|
|
struct uart_sifive {
|
|
u32 txfifo;
|
|
u32 rxfifo;
|
|
u32 txctrl;
|
|
u32 rxctrl;
|
|
u32 ie;
|
|
u32 ip;
|
|
u32 div;
|
|
};
|
|
|
|
struct sifive_uart_plat {
|
|
unsigned long clock;
|
|
struct uart_sifive *regs;
|
|
};
|
|
|
|
/**
|
|
* 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(unsigned long in_freq,
|
|
unsigned long max_target_hz)
|
|
{
|
|
unsigned long quotient =
|
|
(in_freq + max_target_hz - 1) / (max_target_hz);
|
|
/* Avoid underflow */
|
|
if (quotient == 0)
|
|
return 0;
|
|
else
|
|
return quotient - 1;
|
|
}
|
|
|
|
/* Set up the baud rate in gd struct */
|
|
static void _sifive_serial_setbrg(struct uart_sifive *regs,
|
|
unsigned long clock, unsigned long baud)
|
|
{
|
|
writel((uart_min_clk_divisor(clock, baud)), ®s->div);
|
|
}
|
|
|
|
static void _sifive_serial_init(struct uart_sifive *regs)
|
|
{
|
|
writel(UART_TXCTRL_TXEN, ®s->txctrl);
|
|
writel(UART_RXCTRL_RXEN, ®s->rxctrl);
|
|
writel(0, ®s->ie);
|
|
}
|
|
|
|
static int _sifive_serial_putc(struct uart_sifive *regs, const char c)
|
|
{
|
|
if (readl(®s->txfifo) & UART_TXFIFO_FULL)
|
|
return -EAGAIN;
|
|
|
|
writel(c, ®s->txfifo);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int _sifive_serial_getc(struct uart_sifive *regs)
|
|
{
|
|
int ch = readl(®s->rxfifo);
|
|
|
|
if (ch & UART_RXFIFO_EMPTY)
|
|
return -EAGAIN;
|
|
ch &= UART_RXFIFO_DATA;
|
|
|
|
return ch;
|
|
}
|
|
|
|
static int sifive_serial_setbrg(struct udevice *dev, int baudrate)
|
|
{
|
|
int ret;
|
|
struct clk clk;
|
|
struct sifive_uart_plat *plat = dev_get_plat(dev);
|
|
u32 clock = 0;
|
|
|
|
ret = clk_get_by_index(dev, 0, &clk);
|
|
if (IS_ERR_VALUE(ret)) {
|
|
debug("SiFive UART failed to get clock\n");
|
|
ret = dev_read_u32(dev, "clock-frequency", &clock);
|
|
if (IS_ERR_VALUE(ret)) {
|
|
debug("SiFive UART clock not defined\n");
|
|
return 0;
|
|
}
|
|
} else {
|
|
clock = clk_get_rate(&clk);
|
|
if (IS_ERR_VALUE(clock)) {
|
|
debug("SiFive UART clock get rate failed\n");
|
|
return 0;
|
|
}
|
|
}
|
|
plat->clock = clock;
|
|
_sifive_serial_setbrg(plat->regs, plat->clock, baudrate);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int sifive_serial_probe(struct udevice *dev)
|
|
{
|
|
struct sifive_uart_plat *plat = dev_get_plat(dev);
|
|
|
|
/* No need to reinitialize the UART after relocation */
|
|
if (gd->flags & GD_FLG_RELOC)
|
|
return 0;
|
|
|
|
_sifive_serial_init(plat->regs);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int sifive_serial_getc(struct udevice *dev)
|
|
{
|
|
int c;
|
|
struct sifive_uart_plat *plat = dev_get_plat(dev);
|
|
struct uart_sifive *regs = plat->regs;
|
|
|
|
while ((c = _sifive_serial_getc(regs)) == -EAGAIN) ;
|
|
|
|
return c;
|
|
}
|
|
|
|
static int sifive_serial_putc(struct udevice *dev, const char ch)
|
|
{
|
|
int rc;
|
|
struct sifive_uart_plat *plat = dev_get_plat(dev);
|
|
|
|
while ((rc = _sifive_serial_putc(plat->regs, ch)) == -EAGAIN) ;
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int sifive_serial_pending(struct udevice *dev, bool input)
|
|
{
|
|
struct sifive_uart_plat *plat = dev_get_plat(dev);
|
|
struct uart_sifive *regs = plat->regs;
|
|
|
|
if (input)
|
|
return (readl(®s->ip) & UART_IP_RXWM);
|
|
else
|
|
return !!(readl(®s->txfifo) & UART_TXFIFO_FULL);
|
|
}
|
|
|
|
static int sifive_serial_of_to_plat(struct udevice *dev)
|
|
{
|
|
struct sifive_uart_plat *plat = dev_get_plat(dev);
|
|
|
|
plat->regs = (struct uart_sifive *)(uintptr_t)dev_read_addr(dev);
|
|
if (IS_ERR(plat->regs))
|
|
return PTR_ERR(plat->regs);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct dm_serial_ops sifive_serial_ops = {
|
|
.putc = sifive_serial_putc,
|
|
.getc = sifive_serial_getc,
|
|
.pending = sifive_serial_pending,
|
|
.setbrg = sifive_serial_setbrg,
|
|
};
|
|
|
|
static const struct udevice_id sifive_serial_ids[] = {
|
|
{ .compatible = "sifive,uart0" },
|
|
{ }
|
|
};
|
|
|
|
U_BOOT_DRIVER(serial_sifive) = {
|
|
.name = "serial_sifive",
|
|
.id = UCLASS_SERIAL,
|
|
.of_match = sifive_serial_ids,
|
|
.of_to_plat = sifive_serial_of_to_plat,
|
|
.plat_auto = sizeof(struct sifive_uart_plat),
|
|
.probe = sifive_serial_probe,
|
|
.ops = &sifive_serial_ops,
|
|
};
|
|
|
|
#ifdef CONFIG_DEBUG_UART_SIFIVE
|
|
static inline void _debug_uart_init(void)
|
|
{
|
|
struct uart_sifive *regs =
|
|
(struct uart_sifive *)CONFIG_DEBUG_UART_BASE;
|
|
|
|
_sifive_serial_setbrg(regs, CONFIG_DEBUG_UART_CLOCK,
|
|
CONFIG_BAUDRATE);
|
|
_sifive_serial_init(regs);
|
|
}
|
|
|
|
static inline void _debug_uart_putc(int ch)
|
|
{
|
|
struct uart_sifive *regs =
|
|
(struct uart_sifive *)CONFIG_DEBUG_UART_BASE;
|
|
|
|
while (_sifive_serial_putc(regs, ch) == -EAGAIN)
|
|
WATCHDOG_RESET();
|
|
}
|
|
|
|
DEBUG_UART_FUNCS
|
|
|
|
#endif
|