diff options
Diffstat (limited to 'drivers/serial/serial_ns16550.c')
-rw-r--r-- | drivers/serial/serial_ns16550.c | 261 |
1 files changed, 162 insertions, 99 deletions
diff --git a/drivers/serial/serial_ns16550.c b/drivers/serial/serial_ns16550.c index 3edeb0dcbe..1b1692658f 100644 --- a/drivers/serial/serial_ns16550.c +++ b/drivers/serial/serial_ns16550.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /** * @file * @brief NS16550 Driver implementation @@ -16,20 +17,6 @@ * * (C) Copyright 2000 * Rob Taylor, Flying Pig Systems. robt@flyingpig.com. - * - * See file CREDITS for list of people who contributed to this - * project. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation; either version 2 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * */ #include <common.h> @@ -52,11 +39,18 @@ struct ns16550_priv { unsigned iobase; void (*write_reg)(struct ns16550_priv *, uint8_t val, unsigned offset); uint8_t (*read_reg)(struct ns16550_priv *, unsigned offset); + const char *access_type; + + bool rs485_mode; + bool rs485_rts_active_low; + bool rs485_rx_during_tx; }; struct ns16550_drvdata { void (*init_port)(struct console_device *cdev); const char *linux_console_name; + const char *linux_earlycon_name; + unsigned int clk_default; }; static inline struct ns16550_priv *to_ns16550_priv(struct console_device *cdev) @@ -182,7 +176,6 @@ static inline unsigned int ns16550_calc_divisor(struct console_device *cdev, unsigned int clk = plat->clock; return (clk / MODE_X_DIV / baudrate); - } /** @@ -253,14 +246,10 @@ static void ns16550_jz_init_port(struct console_device *cdev) ns16550_serial_init_port(cdev); } -#define BCM2836_AUX_CLOCK_ENB 0x3f215004 /* BCM2835 AUX Clock enable register */ -#define BCM2836_AUX_CLOCK_EN_UART BIT(0) /* Bit 0 enables the Miniuart */ - static void rpi_init_port(struct console_device *cdev) { struct ns16550_priv *priv = to_ns16550_priv(cdev); - writeb(BCM2836_AUX_CLOCK_EN_UART, BCM2836_AUX_CLOCK_ENB); priv->plat.shift = 2; /* * We double the clock rate since the 16550 will divide by 16 @@ -280,9 +269,37 @@ static void rpi_init_port(struct console_device *cdev) */ static void ns16550_putc(struct console_device *cdev, char c) { - /* Loop Doing Nothing */ - while ((ns16550_read(cdev, lsr) & LSR_THRE) == 0) ; + struct ns16550_priv *priv = to_ns16550_priv(cdev); + + /* wait until FIFO can accept at least one byte */ + while ((ns16550_read(cdev, lsr) & (LSR_THRE)) != (LSR_THRE)) + ; + + if (priv->rs485_mode) { + if (priv->rs485_rts_active_low) + ns16550_write(cdev, MCR_RTS, mcr); + else + ns16550_write(cdev, 0, mcr); + + if (!priv->rs485_rx_during_tx) + ns16550_write(cdev, CNTL_TXEN, cntl); + } + ns16550_write(cdev, c, thr); + + if (priv->rs485_mode) { + /* wait until FIFO is cleared*/ + while ((ns16550_read(cdev, lsr) & (LSR_EMPTY)) != (LSR_EMPTY)) + ; + + if (priv->rs485_rts_active_low) + ns16550_write(cdev, 0, mcr); + else + ns16550_write(cdev, MCR_RTS, mcr); + + if (!priv->rs485_rx_during_tx) + ns16550_write(cdev, CNTL_TXEN | CNTL_RXEN, cntl); + } } /** @@ -311,13 +328,24 @@ static int ns16550_tstc(struct console_device *cdev) return ((ns16550_read(cdev, lsr) & LSR_DR) != 0); } -static void ns16550_probe_dt(struct device_d *dev, struct ns16550_priv *priv) +/** + * @brief Flush remaining characters in serial device + * + * @param[in] cdev pointer to console device + */ +static void ns16550_flush(struct console_device *cdev) +{ + /* Loop Doing Nothing */ + while ((ns16550_read(cdev, lsr) & LSR_TEMT) == 0) ; +} + +static void ns16550_probe_dt(struct device *dev, struct ns16550_priv *priv) { - struct device_node *np = dev->device_node; + struct device_node *np = dev_of_node(dev); u32 offset; u32 width = 1; - if (!IS_ENABLED(CONFIG_OFDEVICE)) + if (!np) return; of_property_read_u32(np, "clock-frequency", &priv->plat.clock); @@ -325,22 +353,33 @@ static void ns16550_probe_dt(struct device_d *dev, struct ns16550_priv *priv) priv->mmiobase += offset; of_property_read_u32(np, "reg-shift", &priv->plat.shift); of_property_read_u32(np, "reg-io-width", &width); + priv->rs485_rts_active_low = + of_property_read_bool(np, "rs485-rts-active-low"); + priv->rs485_mode = + of_property_read_bool(np, "linux,rs485-enabled-at-boot-time"); + priv->rs485_rx_during_tx = + of_property_read_bool(np, "rs485-rx-during-tx"); + switch (width) { case 1: priv->read_reg = ns16550_read_reg_mmio_8; priv->write_reg = ns16550_write_reg_mmio_8; + priv->access_type = "mmio"; break; case 2: priv->read_reg = ns16550_read_reg_mmio_16; priv->write_reg = ns16550_write_reg_mmio_16; + priv->access_type = "mmio16"; break; case 4: if (of_device_is_big_endian(np)) { priv->read_reg = ns16550_read_reg_mmio_32be; priv->write_reg = ns16550_write_reg_mmio_32be; + priv->access_type = "mmio32be"; } else { priv->read_reg = ns16550_read_reg_mmio_32; priv->write_reg = ns16550_write_reg_mmio_32; + priv->access_type = "mmio32"; } break; default: @@ -352,98 +391,101 @@ static void ns16550_probe_dt(struct device_d *dev, struct ns16550_priv *priv) static struct ns16550_drvdata ns16450_drvdata = { .init_port = ns16450_serial_init_port, .linux_console_name = "ttyS", + .linux_earlycon_name = "uart8250", }; static struct ns16550_drvdata ns16550_drvdata = { .init_port = ns16550_serial_init_port, .linux_console_name = "ttyS", + .linux_earlycon_name = "uart8250", }; static __maybe_unused struct ns16550_drvdata omap_drvdata = { .init_port = ns16550_omap_init_port, .linux_console_name = "ttyO", + .linux_earlycon_name = "omap8250", }; -static __maybe_unused struct ns16550_drvdata jz_drvdata = { - .init_port = ns16550_jz_init_port, +static __maybe_unused struct ns16550_drvdata omap_clk48m_drvdata = { + .init_port = ns16550_omap_init_port, + .linux_console_name = "ttyO", + .clk_default = 48000000, }; -static __maybe_unused struct ns16550_drvdata tegra_drvdata = { - .init_port = ns16550_serial_init_port, - .linux_console_name = "ttyS", +static __maybe_unused struct ns16550_drvdata jz_drvdata = { + .init_port = ns16550_jz_init_port, + .linux_earlycon_name = "jz4740_uart", }; static __maybe_unused struct ns16550_drvdata rpi_drvdata = { .init_port = rpi_init_port, .linux_console_name = "ttyS", + .linux_earlycon_name = "bcm2835aux", }; -static int ns16550_init_iomem(struct device_d *dev, struct ns16550_priv *priv) +/** + * @return the requested resource to be properly released in case probe fail + */ +static struct resource *ns16550_init_iores(struct device *dev, struct ns16550_priv *priv) { - struct resource *iores; struct resource *res; - int width; + struct resource *iores; + unsigned long flags; res = dev_get_resource(dev, IORESOURCE_MEM, 0); if (IS_ERR(res)) - return PTR_ERR(res); + res = dev_get_resource(dev, IORESOURCE_IO, 0); + if (IS_ERR(res)) + return res; + + flags = res->flags & (IORESOURCE_MEM_TYPE_MASK | IORESOURCE_IO); - iores = dev_request_mem_resource(dev, 0); + if (flags & IORESOURCE_IO) + iores = request_ioport_region(dev_name(dev), res->start, res->end); + else + iores = request_iomem_region(dev_name(dev), res->start, res->end); if (IS_ERR(iores)) - return PTR_ERR(iores); - priv->mmiobase = IOMEM(iores->start); + return iores; - width = res->flags & IORESOURCE_MEM_TYPE_MASK; - switch (width) { + if (flags & IORESOURCE_IO) + priv->iobase = iores->start; + else + priv->mmiobase = IOMEM(iores->start); + + switch (flags) { + case IORESOURCE_IO | IORESOURCE_MEM_8BIT: + priv->read_reg = ns16550_read_reg_ioport_8; + priv->write_reg = ns16550_write_reg_ioport_8; + priv->access_type = "io"; + break; + case IORESOURCE_IO | IORESOURCE_MEM_16BIT: + priv->read_reg = ns16550_read_reg_ioport_16; + priv->write_reg = ns16550_write_reg_ioport_16; + priv->access_type = "io"; + break; + case IORESOURCE_IO | IORESOURCE_MEM_32BIT: + priv->read_reg = ns16550_read_reg_ioport_32; + priv->write_reg = ns16550_write_reg_ioport_32; + priv->access_type = "io"; + break; case IORESOURCE_MEM_8BIT: priv->read_reg = ns16550_read_reg_mmio_8; priv->write_reg = ns16550_write_reg_mmio_8; + priv->access_type = "mmio"; break; case IORESOURCE_MEM_16BIT: priv->read_reg = ns16550_read_reg_mmio_16; priv->write_reg = ns16550_write_reg_mmio_16; + priv->access_type = "mmio16"; break; case IORESOURCE_MEM_32BIT: priv->read_reg = ns16550_read_reg_mmio_32; priv->write_reg = ns16550_write_reg_mmio_32; + priv->access_type = "mmio32"; break; } - return 0; -} - -static int ns16550_init_ioport(struct device_d *dev, struct ns16550_priv *priv) -{ - struct resource *res; - int width; - - res = dev_get_resource(dev, IORESOURCE_IO, 0); - if (IS_ERR(res)) - return PTR_ERR(res); - - res = request_ioport_region(dev_name(dev), res->start, res->end); - if (IS_ERR(res)) - return PTR_ERR(res); - - priv->iobase = res->start; - - width = res->flags & IORESOURCE_MEM_TYPE_MASK; - switch (width) { - case IORESOURCE_MEM_8BIT: - priv->read_reg = ns16550_read_reg_ioport_8; - priv->write_reg = ns16550_write_reg_ioport_8; - break; - case IORESOURCE_MEM_16BIT: - priv->read_reg = ns16550_read_reg_ioport_16; - priv->write_reg = ns16550_write_reg_ioport_16; - break; - case IORESOURCE_MEM_32BIT: - priv->read_reg = ns16550_read_reg_ioport_32; - priv->write_reg = ns16550_write_reg_ioport_32; - break; - } - - return 0; + return iores; } /** @@ -455,47 +497,50 @@ static int ns16550_init_ioport(struct device_d *dev, struct ns16550_priv *priv) * ENOMEM if calloc failed * else return result of console_register */ -static int ns16550_probe(struct device_d *dev) +static int ns16550_probe(struct device *dev) { struct ns16550_priv *priv; struct console_device *cdev; struct NS16550_plat *plat = (struct NS16550_plat *)dev->platform_data; - struct ns16550_drvdata *devtype; + const struct ns16550_drvdata *devtype; + struct resource *iores; int ret; - ret = dev_get_drvdata(dev, (const void **)&devtype); - if (ret) - devtype = &ns16550_drvdata; + devtype = device_get_match_data(dev) ?: &ns16550_drvdata; priv = xzalloc(sizeof(*priv)); - ret = ns16550_init_iomem(dev, priv); - if (ret) - ret = ns16550_init_ioport(dev, priv); - - if (ret) - return ret; + iores = ns16550_init_iores(dev, priv); + if (IS_ERR(iores)) { + ret = PTR_ERR(iores); + goto err; + } if (plat) priv->plat = *plat; else ns16550_probe_dt(dev, priv); + if (devtype->clk_default && !priv->plat.clock) + priv->plat.clock = devtype->clk_default; + if (!priv->plat.clock) { priv->clk = clk_get(dev, NULL); if (IS_ERR(priv->clk)) { ret = PTR_ERR(priv->clk); dev_err(dev, "failed to get clk (%d)\n", ret); - goto err; + goto release_region; } - clk_enable(priv->clk); + ret = clk_enable(priv->clk); + if (ret) + goto clk_put; priv->plat.clock = clk_get_rate(priv->clk); } if (priv->plat.clock == 0) { dev_err(dev, "no valid clockrate\n"); ret = -EINVAL; - goto err; + goto clk_disable; } cdev = &priv->cdev; @@ -504,14 +549,29 @@ static int ns16550_probe(struct device_d *dev) cdev->putc = ns16550_putc; cdev->getc = ns16550_getc; cdev->setbrg = ns16550_setbaudrate; + cdev->flush = ns16550_flush; cdev->linux_console_name = devtype->linux_console_name; + cdev->linux_earlycon_name = basprintf("%s,%s", devtype->linux_earlycon_name, + priv->access_type); + cdev->phys_base = !strcmp(priv->access_type, "io") ? + IOMEM((ulong)priv->iobase) : priv->mmiobase; priv->fcrval = FCRVAL; devtype->init_port(cdev); - return console_register(cdev); + ret = console_register(cdev); + if (ret) + goto clk_disable; + return 0; + +clk_disable: + clk_disable(priv->clk); +clk_put: + clk_put(priv->clk); +release_region: + release_region(iores); err: free(priv); @@ -524,14 +584,19 @@ static struct of_device_id ns16550_serial_dt_ids[] = { .data = &ns16450_drvdata, }, { .compatible = "ns16550a", - .data = &ns16550_drvdata, }, { .compatible = "snps,dw-apb-uart", - .data = &ns16550_drvdata, }, { .compatible = "marvell,armada-38x-uart", - .data = &ns16550_drvdata, + }, { + .compatible = "nvidia,tegra20-uart", + }, +#if IS_ENABLED(CONFIG_ARCH_K3) + { + .compatible = "ti,am654-uart", + .data = &omap_clk48m_drvdata, }, +#endif #if IS_ENABLED(CONFIG_ARCH_OMAP) { .compatible = "ti,omap2-uart", @@ -542,12 +607,9 @@ static struct of_device_id ns16550_serial_dt_ids[] = { }, { .compatible = "ti,omap4-uart", .data = &omap_drvdata, - }, -#endif -#if IS_ENABLED(CONFIG_ARCH_TEGRA) - { - .compatible = "nvidia,tegra20-uart", - .data = &tegra_drvdata, + }, { + .compatible = "ti,am4372-uart", + .data = &omap_clk48m_drvdata, }, #endif #if IS_ENABLED(CONFIG_MACH_MIPS_XBURST) @@ -566,11 +628,12 @@ static struct of_device_id ns16550_serial_dt_ids[] = { /* sentinel */ }, }; +MODULE_DEVICE_TABLE(of, ns16550_serial_dt_ids); static __maybe_unused struct platform_device_id ns16550_serial_ids[] = { { .name = "omap-uart", - .driver_data = (unsigned long)&omap_drvdata, + .driver_data = (unsigned long)&omap_clk48m_drvdata, }, { /* sentinel */ }, @@ -579,7 +642,7 @@ static __maybe_unused struct platform_device_id ns16550_serial_ids[] = { /** * @brief Driver registration structure */ -static struct driver_d ns16550_serial_driver = { +static struct driver ns16550_serial_driver = { .name = "ns16550_serial", .probe = ns16550_probe, .of_compatible = DRV_OF_COMPAT(ns16550_serial_dt_ids), |