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 supports of-platdata which is useful for tiny SPL.

Signed-off-by: Weijie Gao <weijie....@mediatek.com>
---
v2 changes: replace non-DM code with of-platdata
---
 drivers/serial/Kconfig         |  20 +++
 drivers/serial/Makefile        |   1 +
 drivers/serial/serial.c        |   2 +
 drivers/serial/serial_mt7620.c | 246 +++++++++++++++++++++++++++++++++
 4 files changed, 269 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 355659ba05..1546f41e42 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..4f6b5de4cf
--- /dev/null
+++ b/drivers/serial/serial_mt7620.c
@@ -0,0 +1,246 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * UART driver for MediaTek MT7620 and earlier SoCs
+ *
+ * Copyright (C) 2020 MediaTek Inc.
+ * Author: Weijie Gao <weijie....@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 <asm/addrspace.h>
+#include <dm/device_compat.h>
+#include <linux/err.h>
+
+#if CONFIG_IS_ENABLED(OF_PLATDATA)
+#include <dt-structs.h>
+#endif
+
+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_plat {
+#if CONFIG_IS_ENABLED(OF_PLATDATA)
+       struct dtd_serial_mt7620 dtplat;
+#endif
+
+       struct mt7620_serial_regs __iomem *regs;
+       u32 clock;
+};
+
+static void _mt7620_serial_setbrg(struct mt7620_serial_plat *plat, int baud)
+{
+       u32 quot;
+
+       /* set divisor */
+       quot = DIV_ROUND_CLOSEST(plat->clock, 16 * baud);
+       writel(quot, &plat->regs->dl);
+
+       /* set character length and stop bits */
+       writel(UART_LCR_WLS_8, &plat->regs->lcr);
+}
+
+static int mt7620_serial_setbrg(struct udevice *dev, int baudrate)
+{
+       struct mt7620_serial_plat *plat = dev_get_platdata(dev);
+
+       _mt7620_serial_setbrg(plat, baudrate);
+
+       return 0;
+}
+
+static int mt7620_serial_putc(struct udevice *dev, const char ch)
+{
+       struct mt7620_serial_plat *plat = dev_get_platdata(dev);
+
+       if (!(readl(&plat->regs->lsr) & UART_LSR_THRE))
+               return -EAGAIN;
+
+       writel(ch, &plat->regs->thr);
+
+       if (ch == '\n')
+               WATCHDOG_RESET();
+
+       return 0;
+}
+
+static int mt7620_serial_getc(struct udevice *dev)
+{
+       struct mt7620_serial_plat *plat = dev_get_platdata(dev);
+
+       if (!(readl(&plat->regs->lsr) & UART_LSR_DR))
+               return -EAGAIN;
+
+       return readl(&plat->regs->rbr);
+}
+
+static int mt7620_serial_pending(struct udevice *dev, bool input)
+{
+       struct mt7620_serial_plat *plat = dev_get_platdata(dev);
+
+       if (input)
+               return (readl(&plat->regs->lsr) & UART_LSR_DR) ? 1 : 0;
+
+       return (readl(&plat->regs->lsr) & UART_LSR_THRE) ? 0 : 1;
+}
+
+static int mt7620_serial_probe(struct udevice *dev)
+{
+       struct mt7620_serial_plat *plat = dev_get_platdata(dev);
+
+#if CONFIG_IS_ENABLED(OF_PLATDATA)
+       plat->regs = (void __iomem *)KSEG1ADDR(plat->dtplat.reg[0]);
+       plat->clock = plat->dtplat.clock_frequency;
+#endif
+
+       /* Disable interrupt */
+       writel(0, &plat->regs->ier);
+
+       writel(UART_MCRVAL, &plat->regs->mcr);
+       writel(UART_FCRVAL, &plat->regs->fcr);
+
+       return 0;
+}
+
+#if !CONFIG_IS_ENABLED(OF_PLATDATA)
+static int mt7620_serial_ofdata_to_platdata(struct udevice *dev)
+{
+       struct mt7620_serial_plat *plat = dev_get_platdata(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);
+
+       plat->regs = dev_remap_addr_index(dev, 0);
+       if (!plat->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))
+                       plat->clock = err;
+       } else if (err != -ENOENT && err != -ENODEV && err != -ENOSYS) {
+               dev_err(dev, "mt7620_serial: failed to get clock\n");
+               return err;
+       }
+
+       if (!plat->clock)
+               plat->clock = dev_read_u32_default(dev, "clock-frequency", 0);
+
+       if (!plat->clock) {
+               dev_err(dev, "mt7620_serial: clock not defined\n");
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+static const struct udevice_id mt7620_serial_ids[] = {
+       { .compatible = "mediatek,mt7620-uart" },
+       { }
+};
+#endif
+
+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,
+};
+
+U_BOOT_DRIVER(serial_mt7620) = {
+       .name = "serial_mt7620",
+       .id = UCLASS_SERIAL,
+#if !CONFIG_IS_ENABLED(OF_PLATDATA)
+       .of_match = mt7620_serial_ids,
+       .ofdata_to_platdata = mt7620_serial_ofdata_to_platdata,
+#endif
+       .platdata_auto_alloc_size = sizeof(struct mt7620_serial_plat),
+       .probe = mt7620_serial_probe,
+       .ops = &mt7620_serial_ops,
+       .flags = DM_FLAG_PRE_RELOC,
+};
+
+U_BOOT_DRIVER_ALIAS(serial_mt7620, mediatek_mt7620_uart);
+
+#ifdef CONFIG_DEBUG_UART_MT7620
+
+#include <debug_uart.h>
+
+static inline void _debug_uart_init(void)
+{
+       struct mt7620_serial_plat plat;
+
+       plat.regs = (void *)CONFIG_DEBUG_UART_BASE;
+       plat.clock = CONFIG_DEBUG_UART_CLOCK;
+
+       writel(0, &plat.regs->ier);
+       writel(UART_MCRVAL, &plat.regs->mcr);
+       writel(UART_FCRVAL, &plat.regs->fcr);
+
+       _mt7620_serial_setbrg(&plat, CONFIG_BAUDRATE);
+}
+
+static inline void _debug_uart_putc(int ch)
+{
+       struct mt7620_serial_regs __iomem *regs =
+               (void *)CONFIG_DEBUG_UART_BASE;
+
+       while (!(readl(&regs->lsr) & UART_LSR_THRE))
+               ;
+
+       writel(ch, &regs->thr);
+}
+
+DEBUG_UART_FUNCS
+
+#endif
-- 
2.17.1

Reply via email to