
This patch adds uart support for MediaTek MT7620 and earlier SoCs.
The UART used by MT7620 is incompatible with the ns16550a driver. All registers of this UART have different addresses. A special 16-bit register for Divisor Latch is used to set the baudrate instead of the original two 8-bit registers (DLL and DLM).
The driver can be built without DM which is useful for tiny SPL.
Signed-off-by: Weijie Gao weijie.gao@mediatek.com --- drivers/serial/Kconfig | 20 ++ drivers/serial/Makefile | 1 + drivers/serial/serial.c | 2 + drivers/serial/serial_mt7620.c | 350 +++++++++++++++++++++++++++++++++ 4 files changed, 373 insertions(+) create mode 100644 drivers/serial/serial_mt7620.c
diff --git a/drivers/serial/Kconfig b/drivers/serial/Kconfig index b4805a2e4e..44fff8a3cd 100644 --- a/drivers/serial/Kconfig +++ b/drivers/serial/Kconfig @@ -401,6 +401,16 @@ config DEBUG_UART_MTK driver will be available until the real driver model serial is running.
+config DEBUG_UART_MT7620 + bool "UART driver for MediaTek MT7620 and earlier SoCs" + depends on MT7620_SERIAL + help + Select this to enable a debug UART using the UART driver for + MediaTek MT7620 and earlier SoCs. + You will need to provide parameters to make this work. The + driver will be available until the real driver model serial is + running. + endchoice
config DEBUG_UART_BASE @@ -817,6 +827,16 @@ config MTK_SERIAL The High-speed UART is compatible with the ns16550a UART and have its own high-speed registers.
+config MT7620_SERIAL + bool "UART driver for MediaTek MT7620 and earlier SoCs" + depends on DM_SERIAL + help + Select this to enable UART support for MediaTek MT7620 and earlier + SoCs. This driver uses driver model and requires a device tree + binding to operate. + The UART driver for MediaTek MT7620 and earlier SoCs is *NOT* + compatible with the ns16550a UART. + config MPC8XX_CONS bool "Console driver for MPC8XX" depends on MPC8xx diff --git a/drivers/serial/Makefile b/drivers/serial/Makefile index 25f7f8d342..0c3810f5d5 100644 --- a/drivers/serial/Makefile +++ b/drivers/serial/Makefile @@ -69,6 +69,7 @@ obj-$(CONFIG_NULLDEV_SERIAL) += serial_nulldev.o obj-$(CONFIG_OWL_SERIAL) += serial_owl.o obj-$(CONFIG_OMAP_SERIAL) += serial_omap.o obj-$(CONFIG_MTK_SERIAL) += serial_mtk.o +obj-$(CONFIG_MT7620_SERIAL) += serial_mt7620.o obj-$(CONFIG_SIFIVE_SERIAL) += serial_sifive.o obj-$(CONFIG_XEN_SERIAL) += serial_xen.o
diff --git a/drivers/serial/serial.c b/drivers/serial/serial.c index 53358acb81..312c591bf1 100644 --- a/drivers/serial/serial.c +++ b/drivers/serial/serial.c @@ -127,6 +127,7 @@ serial_initfunc(pl01x_serial_initialize); serial_initfunc(pxa_serial_initialize); serial_initfunc(sh_serial_initialize); serial_initfunc(mtk_serial_initialize); +serial_initfunc(mt7620_serial_initialize);
/** * serial_register() - Register serial driver with serial driver core @@ -181,6 +182,7 @@ int serial_initialize(void) pxa_serial_initialize(); sh_serial_initialize(); mtk_serial_initialize(); + mt7620_serial_initialize();
serial_assign(default_serial_console()->name);
diff --git a/drivers/serial/serial_mt7620.c b/drivers/serial/serial_mt7620.c new file mode 100644 index 0000000000..af0919978f --- /dev/null +++ b/drivers/serial/serial_mt7620.c @@ -0,0 +1,350 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * UART driver for MediaTek MT7620 and earlier SoCs + * + * Copyright (C) 2020 MediaTek Inc. + * Author: Weijie Gao weijie.gao@mediatek.com + */ + +#include <clk.h> +#include <div64.h> +#include <dm.h> +#include <errno.h> +#include <log.h> +#include <reset.h> +#include <serial.h> +#include <watchdog.h> +#include <asm/io.h> +#include <asm/types.h> +#include <linux/err.h> + +struct mt7620_serial_regs { + u32 rbr; + u32 thr; + u32 ier; + u32 iir; + u32 fcr; + u32 lcr; + u32 mcr; + u32 lsr; + u32 msr; + u32 scratch; + u32 dl; + u32 dll; + u32 dlm; + u32 ifctl; +}; + +#define UART_LCR_WLS_8 0x03 /* 8 bit character length */ + +#define UART_LSR_DR 0x01 /* Data ready */ +#define UART_LSR_THRE 0x20 /* Xmit holding register empty */ +#define UART_LSR_TEMT 0x40 /* Xmitter empty */ + +#define UART_MCR_DTR 0x01 /* DTR */ +#define UART_MCR_RTS 0x02 /* RTS */ + +#define UART_FCR_FIFO_EN 0x01 /* Fifo enable */ +#define UART_FCR_RXSR 0x02 /* Receiver soft reset */ +#define UART_FCR_TXSR 0x04 /* Transmitter soft reset */ + +#define UART_MCRVAL (UART_MCR_DTR | \ + UART_MCR_RTS) + +/* Clear & enable FIFOs */ +#define UART_FCRVAL (UART_FCR_FIFO_EN | \ + UART_FCR_RXSR | \ + UART_FCR_TXSR) + +struct mt7620_serial_priv { + struct mt7620_serial_regs __iomem *regs; + u32 clock; +}; + +static void _mt7620_serial_setbrg(struct mt7620_serial_priv *priv, int baud) +{ + u32 quot; + + /* set divisor */ + quot = DIV_ROUND_CLOSEST(priv->clock, 16 * baud); + writel(quot, &priv->regs->dl); + + /* set character length and stop bits */ + writel(UART_LCR_WLS_8, &priv->regs->lcr); +} + +static int _mt7620_serial_putc(struct mt7620_serial_priv *priv, const char ch) +{ + if (!(readl(&priv->regs->lsr) & UART_LSR_THRE)) + return -EAGAIN; + + writel(ch, &priv->regs->thr); + + if (ch == '\n') + WATCHDOG_RESET(); + + return 0; +} + +static int _mt7620_serial_getc(struct mt7620_serial_priv *priv) +{ + if (!(readl(&priv->regs->lsr) & UART_LSR_DR)) + return -EAGAIN; + + return readl(&priv->regs->rbr); +} + +static int _mt7620_serial_pending(struct mt7620_serial_priv *priv, bool input) +{ + if (input) + return (readl(&priv->regs->lsr) & UART_LSR_DR) ? 1 : 0; + else + return (readl(&priv->regs->lsr) & UART_LSR_THRE) ? 0 : 1; +} + +#if defined(CONFIG_DM_SERIAL) && \ + (!defined(CONFIG_SPL_BUILD) || defined(CONFIG_SPL_DM)) +static int mt7620_serial_setbrg(struct udevice *dev, int baudrate) +{ + struct mt7620_serial_priv *priv = dev_get_priv(dev); + + _mt7620_serial_setbrg(priv, baudrate); + + return 0; +} + +static int mt7620_serial_putc(struct udevice *dev, const char ch) +{ + struct mt7620_serial_priv *priv = dev_get_priv(dev); + + return _mt7620_serial_putc(priv, ch); +} + +static int mt7620_serial_getc(struct udevice *dev) +{ + struct mt7620_serial_priv *priv = dev_get_priv(dev); + + return _mt7620_serial_getc(priv); +} + +static int mt7620_serial_pending(struct udevice *dev, bool input) +{ + struct mt7620_serial_priv *priv = dev_get_priv(dev); + + return _mt7620_serial_pending(priv, input); +} + +static int mt7620_serial_probe(struct udevice *dev) +{ + struct mt7620_serial_priv *priv = dev_get_priv(dev); + + /* Disable interrupt */ + writel(0, &priv->regs->ier); + + writel(UART_MCRVAL, &priv->regs->mcr); + writel(UART_FCRVAL, &priv->regs->fcr); + + return 0; +} + +static int mt7620_serial_ofdata_to_platdata(struct udevice *dev) +{ + struct mt7620_serial_priv *priv = dev_get_priv(dev); + struct reset_ctl reset_uart; + struct clk clk; + int err; + + err = reset_get_by_index(dev, 0, &reset_uart); + if (!err) + reset_deassert(&reset_uart); + + priv->regs = dev_remap_addr_index(dev, 0); + if (!priv->regs) { + dev_err(dev, "mt7620_serial: unable to map UART registers\n"); + return -EINVAL; + } + + err = clk_get_by_index(dev, 0, &clk); + if (!err) { + err = clk_get_rate(&clk); + if (!IS_ERR_VALUE(err)) + priv->clock = err; + } else if (err != -ENOENT && err != -ENODEV && err != -ENOSYS) { + dev_err(dev, "mt7620_serial: failed to get clock\n"); + return err; + } + + if (!priv->clock) + priv->clock = dev_read_u32_default(dev, "clock-frequency", 0); + + if (!priv->clock) { + dev_err(dev, "mt7620_serial: clock not defined\n"); + return -EINVAL; + } + + return 0; +} + +static const struct dm_serial_ops mt7620_serial_ops = { + .putc = mt7620_serial_putc, + .pending = mt7620_serial_pending, + .getc = mt7620_serial_getc, + .setbrg = mt7620_serial_setbrg, +}; + +static const struct udevice_id mt7620_serial_ids[] = { + { .compatible = "mediatek,mt7620-uart" }, + { } +}; + +U_BOOT_DRIVER(serial_mt7620) = { + .name = "serial_mt7620", + .id = UCLASS_SERIAL, + .of_match = mt7620_serial_ids, + .ofdata_to_platdata = mt7620_serial_ofdata_to_platdata, + .priv_auto_alloc_size = sizeof(struct mt7620_serial_priv), + .probe = mt7620_serial_probe, + .ops = &mt7620_serial_ops, + .flags = DM_FLAG_PRE_RELOC, +}; +#else + +DECLARE_GLOBAL_DATA_PTR; + +#define DECLARE_MT7620_UART_PRIV(port) \ + static struct mt7620_serial_priv mt7620_uart##port = { \ + .regs = (struct mt7620_serial_regs *)CONFIG_MT7620_UART##port##_BASE, \ + .clock = CONFIG_MT7620_UART##port##_CLK \ +}; + +#define DECLARE_MT7620_UART_FUNCTIONS(port) \ + static int mt7620_serial##port##_init(void) \ + { \ + writel(0, &mt7620_uart##port.regs->ier); \ + writel(UART_MCRVAL, &mt7620_uart##port.regs->mcr); \ + writel(UART_FCRVAL, &mt7620_uart##port.regs->fcr); \ + _mt7620_serial_setbrg(&mt7620_uart##port, gd->baudrate); \ + return 0 ; \ + } \ + static void mt7620_serial##port##_setbrg(void) \ + { \ + _mt7620_serial_setbrg(&mt7620_uart##port, gd->baudrate); \ + } \ + static int mt7620_serial##port##_getc(void) \ + { \ + int err; \ + do { \ + err = _mt7620_serial_getc(&mt7620_uart##port); \ + if (err == -EAGAIN) \ + WATCHDOG_RESET(); \ + } while (err == -EAGAIN); \ + return err >= 0 ? err : 0; \ + } \ + static int mt7620_serial##port##_tstc(void) \ + { \ + return _mt7620_serial_pending(&mt7620_uart##port, true); \ + } \ + static void mt7620_serial##port##_putc(const char c) \ + { \ + int err; \ + if (c == '\n') \ + mt7620_serial##port##_putc('\r'); \ + do { \ + err = _mt7620_serial_putc(&mt7620_uart##port, c); \ + } while (err == -EAGAIN); \ + } \ + static void mt7620_serial##port##_puts(const char *s) \ + { \ + while (*s) { \ + mt7620_serial##port##_putc(*s++); \ + } \ + } + +/* Serial device descriptor */ +#define INIT_MT7620_UART_STRUCTURE(port, __name) { \ + .name = __name, \ + .start = mt7620_serial##port##_init, \ + .stop = NULL, \ + .setbrg = mt7620_serial##port##_setbrg, \ + .getc = mt7620_serial##port##_getc, \ + .tstc = mt7620_serial##port##_tstc, \ + .putc = mt7620_serial##port##_putc, \ + .puts = mt7620_serial##port##_puts, \ +} + +#define DECLARE_MT7620_UART(port, __name) \ + DECLARE_MT7620_UART_PRIV(port); \ + DECLARE_MT7620_UART_FUNCTIONS(port); \ + struct serial_device mt7620_uart##port##_device = \ + INIT_MT7620_UART_STRUCTURE(port, __name); + +#if defined(CONFIG_MT7620_UART1_BASE) && defined(CONFIG_MT7620_UART1_CLK) +DECLARE_MT7620_UART(1, "mt7620-uart0"); +#endif +#if defined(CONFIG_MT7620_UART2_BASE) && defined(CONFIG_MT7620_UART2_CLK) +DECLARE_MT7620_UART(2, "mt7620-uart1"); +#endif +#if defined(CONFIG_MT7620_UART3_BASE) && defined(CONFIG_MT7620_UART3_CLK) +DECLARE_MT7620_UART(3, "mt7620-uart2"); +#endif + +__weak struct serial_device *default_serial_console(void) +{ +#if CONFIG_CONS_INDEX == 1 + return &mt7620_uart1_device; +#elif CONFIG_CONS_INDEX == 2 + return &mt7620_uart2_device; +#elif CONFIG_CONS_INDEX == 3 + return &mt7620_uart3_device; +#else +#error "Bad CONFIG_CONS_INDEX." +#endif +} + +void mt7620_serial_initialize(void) +{ +#if defined(CONFIG_MT7620_UART1_BASE) && defined(CONFIG_MT7620_UART1_CLK) + serial_register(&mt7620_uart1_device); +#endif +#if defined(CONFIG_MT7620_UART2_BASE) && defined(CONFIG_MT7620_UART2_CLK) + serial_register(&mt7620_uart2_device); +#endif +#if defined(CONFIG_MT7620_UART3_BASE) && defined(CONFIG_MT7620_UART3_CLK) + serial_register(&mt7620_uart3_device); +#endif +} + +#endif + +#ifdef CONFIG_DEBUG_UART_MT7620 + +#include <debug_uart.h> + +static inline void _debug_uart_init(void) +{ + struct mt7620_serial_priv priv; + + priv.regs = (void *)CONFIG_DEBUG_UART_BASE; + priv.clock = CONFIG_DEBUG_UART_CLOCK; + + writel(0, &priv.regs->ier); + writel(UART_MCRVAL, &priv.regs->mcr); + writel(UART_FCRVAL, &priv.regs->fcr); + + _mt7620_serial_setbrg(&priv, CONFIG_BAUDRATE); +} + +static inline void _debug_uart_putc(int ch) +{ + struct mt7620_serial_regs __iomem *regs = + (void *)CONFIG_DEBUG_UART_BASE; + + while (!(readl(®s->lsr) & UART_LSR_THRE)) + ; + + writel(ch, ®s->thr); +} + +DEBUG_UART_FUNCS + +#endif