Import serial driver from Linux v5.11. Signed-off-by: Ahmad Fatoum <a.fatoum@xxxxxxxxxxxxxx> --- drivers/serial/Kconfig | 9 ++ drivers/serial/Makefile | 1 + drivers/serial/serial_sifive.c | 171 +++++++++++++++++++++++++++++++++ 3 files changed, 181 insertions(+) create mode 100644 drivers/serial/serial_sifive.c diff --git a/drivers/serial/Kconfig b/drivers/serial/Kconfig index db924efa02a6..b9750d1774f8 100644 --- a/drivers/serial/Kconfig +++ b/drivers/serial/Kconfig @@ -164,4 +164,13 @@ config VIRTIO_CONSOLE Also serves as a general-purpose serial device for data transfer between the guest and host. + +config SERIAL_SIFIVE + tristate "SiFive UART support" + depends on OFDEVICE + help + Select this option if you are building barebox for a device that + contains a SiFive UART IP block. This type of UART is present on + SiFive FU540 SoCs, among others. + endmenu diff --git a/drivers/serial/Makefile b/drivers/serial/Makefile index 7ff41cd5c744..5120b1737664 100644 --- a/drivers/serial/Makefile +++ b/drivers/serial/Makefile @@ -23,3 +23,4 @@ obj-$(CONFIG_DRIVER_SERIAL_EFI_STDIO) += efi-stdio.o obj-$(CONFIG_DRIVER_SERIAL_DIGIC) += serial_digic.o obj-$(CONFIG_DRIVER_SERIAL_LPUART) += serial_lpuart.o obj-$(CONFIG_VIRTIO_CONSOLE) += virtio_console.o +obj-$(CONFIG_SERIAL_SIFIVE) += serial_sifive.o diff --git a/drivers/serial/serial_sifive.c b/drivers/serial/serial_sifive.c new file mode 100644 index 000000000000..45f7c2bc9ace --- /dev/null +++ b/drivers/serial/serial_sifive.c @@ -0,0 +1,171 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2018 Anup Patel <anup@xxxxxxxxxxxxxx> + */ + +#include <common.h> +#include <driver.h> +#include <malloc.h> +#include <io.h> +#include <of.h> +#include <linux/err.h> +#include <linux/clk.h> + +#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 sifive_serial_regs { + u32 txfifo; + u32 rxfifo; + u32 txctrl; + u32 rxctrl; + u32 ie; + u32 ip; + u32 div; +}; + +struct sifive_serial_priv { + unsigned long freq; + struct sifive_serial_regs __iomem *regs; + struct console_device cdev; +}; + +#define to_priv(cdev) container_of(cdev, struct sifive_serial_priv, cdev) + +/** + * 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; +} + +static void sifive_serial_init(struct sifive_serial_regs __iomem *regs) +{ + writel(UART_TXCTRL_TXEN, ®s->txctrl); + writel(UART_RXCTRL_RXEN, ®s->rxctrl); + writel(0, ®s->ie); +} + +static int sifive_serial_setbrg(struct console_device *cdev, int baudrate) +{ + struct sifive_serial_priv *priv = to_priv(cdev); + + writel((uart_min_clk_divisor(priv->freq, baudrate)), &priv->regs->div); + + return 0; +} + +static int sifive_serial_getc(struct console_device *cdev) +{ + struct sifive_serial_regs __iomem *regs = to_priv(cdev)->regs; + u32 ch; + + do { + ch = readl(®s->rxfifo); + } while (ch & UART_RXFIFO_EMPTY); + + return ch & UART_RXFIFO_DATA; +} + +static void sifive_serial_putc(struct console_device *cdev, const char ch) +{ + struct sifive_serial_regs __iomem *regs = to_priv(cdev)->regs; + + // TODO: how to check for !empty to utilize fifo? + while (readl(®s->txfifo) & UART_TXFIFO_FULL) + ; + + writel(ch, ®s->txfifo); +} + +static int sifive_serial_tstc(struct console_device *cdev) +{ + struct sifive_serial_regs __iomem *regs = to_priv(cdev)->regs; + + return readl(®s->ip) & UART_IP_RXWM; +} + +static void sifive_serial_flush(struct console_device *cdev) +{ + struct sifive_serial_regs __iomem *regs = to_priv(cdev)->regs; + + while (readl(®s->txfifo) & UART_TXFIFO_FULL) + ; +} + +static int sifive_serial_probe(struct device_d *dev) +{ + struct sifive_serial_priv *priv; + struct resource *iores; + struct clk *clk; + u32 freq; + int ret; + + clk = clk_get(dev, NULL); + if (!IS_ERR(clk)) { + freq = clk_get_rate(clk); + } else { + dev_dbg(dev, "failed to get clock. Fallback to device tree.\n"); + + ret = of_property_read_u32(dev->device_node, "clock-frequency", &freq); + if (ret) { + dev_warn(dev, "unknown clock frequency\n"); + return ret; + } + } + + iores = dev_request_mem_resource(dev, 0); + if (IS_ERR(iores)) + return PTR_ERR(iores); + + priv = xzalloc(sizeof(*priv)); + + priv->freq = freq; + priv->regs = IOMEM(iores->start); + + priv->cdev.dev = dev; + priv->cdev.putc = sifive_serial_putc; + priv->cdev.getc = sifive_serial_getc; + priv->cdev.tstc = sifive_serial_tstc; + priv->cdev.flush = sifive_serial_flush; + priv->cdev.setbrg = sifive_serial_setbrg, + + sifive_serial_init(priv->regs); + + return console_register(&priv->cdev); +} + +static __maybe_unused struct of_device_id sifive_serial_dt_ids[] = { + { .compatible = "sifive,uart0" }, + { /* sentinel */ } +}; + +static struct driver_d serial_sifive_driver = { + .name = "serial_sifive", + .probe = sifive_serial_probe, + .of_compatible = sifive_serial_dt_ids, +}; +console_platform_driver(serial_sifive_driver); -- 2.29.2 _______________________________________________ barebox mailing list barebox@xxxxxxxxxxxxxxxxxxx http://lists.infradead.org/mailman/listinfo/barebox