diff options
Diffstat (limited to 'drivers/tty/serial/meson_uart.c')
| -rw-r--r-- | drivers/tty/serial/meson_uart.c | 406 |
1 files changed, 243 insertions, 163 deletions
diff --git a/drivers/tty/serial/meson_uart.c b/drivers/tty/serial/meson_uart.c index 8a842591b37c..a6cb2a535f9d 100644 --- a/drivers/tty/serial/meson_uart.c +++ b/drivers/tty/serial/meson_uart.c @@ -5,15 +5,12 @@ * Copyright (C) 2014 Carlo Caione <carlo@caione.org> */ -#if defined(CONFIG_SERIAL_MESON_CONSOLE) && defined(CONFIG_MAGIC_SYSRQ) -#define SUPPORT_SYSRQ -#endif - #include <linux/clk.h> #include <linux/console.h> #include <linux/delay.h> #include <linux/init.h> #include <linux/io.h> +#include <linux/iopoll.h> #include <linux/module.h> #include <linux/kernel.h> #include <linux/of.h> @@ -71,15 +68,24 @@ #define AML_UART_BAUD_MASK 0x7fffff #define AML_UART_BAUD_USE BIT(23) #define AML_UART_BAUD_XTAL BIT(24) +#define AML_UART_BAUD_XTAL_DIV2 BIT(27) -#define AML_UART_PORT_NUM 6 -#define AML_UART_DEV_NAME "ttyAML" +#define AML_UART_PORT_NUM 12 +#define AML_UART_PORT_OFFSET 6 +#define AML_UART_POLL_USEC 5 +#define AML_UART_TIMEOUT_USEC 10000 -static struct uart_driver meson_uart_driver; +static struct uart_driver meson_uart_driver_ttyAML; +static struct uart_driver meson_uart_driver_ttyS; static struct uart_port *meson_ports[AML_UART_PORT_NUM]; +struct meson_uart_data { + struct uart_driver *uart_driver; + bool has_xtal_div2; +}; + static void meson_uart_set_mctrl(struct uart_port *port, unsigned int mctrl) { } @@ -123,20 +129,20 @@ static void meson_uart_shutdown(struct uart_port *port) free_irq(port->irq, port); - spin_lock_irqsave(&port->lock, flags); + uart_port_lock_irqsave(port, &flags); val = readl(port->membase + AML_UART_CONTROL); val &= ~AML_UART_RX_EN; val &= ~(AML_UART_RX_INT_EN | AML_UART_TX_INT_EN); writel(val, port->membase + AML_UART_CONTROL); - spin_unlock_irqrestore(&port->lock, flags); + uart_port_unlock_irqrestore(port, flags); } static void meson_uart_start_tx(struct uart_port *port) { - struct circ_buf *xmit = &port->state->xmit; - unsigned int ch; + struct tty_port *tport = &port->state->port; + unsigned char ch; u32 val; if (uart_tx_stopped(port)) { @@ -152,22 +158,19 @@ static void meson_uart_start_tx(struct uart_port *port) continue; } - if (uart_circ_empty(xmit)) + if (!uart_fifo_get(port, &ch)) break; - ch = xmit->buf[xmit->tail]; writel(ch, port->membase + AML_UART_WFIFO); - xmit->tail = (xmit->tail+1) & (SERIAL_XMIT_SIZE - 1); - port->icount.tx++; } - if (!uart_circ_empty(xmit)) { + if (!kfifo_is_empty(&tport->xmit_fifo)) { val = readl(port->membase + AML_UART_CONTROL); val |= AML_UART_TX_INT_EN; writel(val, port->membase + AML_UART_CONTROL); } - if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS) + if (kfifo_len(&tport->xmit_fifo) < WAKEUP_CHARS) uart_write_wakeup(port); } @@ -215,7 +218,7 @@ static void meson_receive_chars(struct uart_port *port) continue; } - if (uart_handle_sysrq_char(port, ch)) + if (uart_prepare_sysrq_char(port, ch)) continue; if ((status & port->ignore_status_mask) == 0) @@ -226,16 +229,14 @@ static void meson_receive_chars(struct uart_port *port) } while (!(readl(port->membase + AML_UART_STATUS) & AML_UART_RX_EMPTY)); - spin_unlock(&port->lock); tty_flip_buffer_push(tport); - spin_lock(&port->lock); } static irqreturn_t meson_uart_interrupt(int irq, void *dev_id) { struct uart_port *port = (struct uart_port *)dev_id; - spin_lock(&port->lock); + uart_port_lock(port); if (!(readl(port->membase + AML_UART_STATUS) & AML_UART_RX_EMPTY)) meson_receive_chars(port); @@ -245,7 +246,7 @@ static irqreturn_t meson_uart_interrupt(int irq, void *dev_id) meson_uart_start_tx(port); } - spin_unlock(&port->lock); + uart_unlock_and_check_sysrq(port); return IRQ_HANDLED; } @@ -255,6 +256,14 @@ static const char *meson_uart_type(struct uart_port *port) return (port->type == PORT_MESON) ? "meson_uart" : NULL; } +/* + * This function is called only from probe() using a temporary io mapping + * in order to perform a reset before setting up the device. Since the + * temporarily mapped region was successfully requested, there can be no + * console on this port at this time. Hence it is not necessary for this + * function to acquire the port->lock. (Since there is no console on this + * port at this time, the port->lock is not initialized yet.) + */ static void meson_uart_reset(struct uart_port *port) { u32 val; @@ -269,9 +278,12 @@ static void meson_uart_reset(struct uart_port *port) static int meson_uart_startup(struct uart_port *port) { + unsigned long flags; u32 val; int ret = 0; + uart_port_lock_irqsave(port, &flags); + val = readl(port->membase + AML_UART_CONTROL); val |= AML_UART_CLEAR_ERR; writel(val, port->membase + AML_UART_CONTROL); @@ -287,6 +299,8 @@ static int meson_uart_startup(struct uart_port *port) val = (AML_UART_RECV_IRQ(1) | AML_UART_XMIT_IRQ(port->fifosize / 2)); writel(val, port->membase + AML_UART_MISC); + uart_port_unlock_irqrestore(port, flags); + ret = request_irq(port->irq, meson_uart_interrupt, 0, port->name, port); @@ -295,16 +309,23 @@ static int meson_uart_startup(struct uart_port *port) static void meson_uart_change_speed(struct uart_port *port, unsigned long baud) { - u32 val; + const struct meson_uart_data *private_data = port->private_data; + u32 val = 0; while (!meson_uart_tx_empty(port)) cpu_relax(); if (port->uartclk == 24000000) { - val = ((port->uartclk / 3) / baud) - 1; + unsigned int xtal_div = 3; + + if (private_data && private_data->has_xtal_div2) { + xtal_div = 2; + val |= AML_UART_BAUD_XTAL_DIV2; + } + val |= DIV_ROUND_CLOSEST(port->uartclk / xtal_div, baud) - 1; val |= AML_UART_BAUD_XTAL; } else { - val = ((port->uartclk * 10 / (baud * 4) + 5) / 10) - 1; + val = DIV_ROUND_CLOSEST(port->uartclk / 4, baud) - 1; } val |= AML_UART_BAUD_USE; writel(val, port->membase + AML_UART_REG5); @@ -312,13 +333,13 @@ static void meson_uart_change_speed(struct uart_port *port, unsigned long baud) static void meson_uart_set_termios(struct uart_port *port, struct ktermios *termios, - struct ktermios *old) + const struct ktermios *old) { unsigned int cflags, iflags, baud; unsigned long flags; u32 val; - spin_lock_irqsave(&port->lock, flags); + uart_port_lock_irqsave(port, &flags); cflags = termios->c_cflag; iflags = termios->c_iflag; @@ -357,10 +378,14 @@ static void meson_uart_set_termios(struct uart_port *port, else val |= AML_UART_STOP_BIT_1SB; - if (cflags & CRTSCTS) - val &= ~AML_UART_TWO_WIRE_EN; - else + if (cflags & CRTSCTS) { + if (port->flags & UPF_HARD_FLOW) + val &= ~AML_UART_TWO_WIRE_EN; + else + termios->c_cflag &= ~CRTSCTS; + } else { val |= AML_UART_TWO_WIRE_EN; + } writel(val, port->membase + AML_UART_CONTROL); @@ -378,7 +403,7 @@ static void meson_uart_set_termios(struct uart_port *port, AML_UART_FRAME_ERR; uart_update_timeout(port, termios->c_cflag, baud); - spin_unlock_irqrestore(&port->lock, flags); + uart_port_unlock_irqrestore(port, flags); } static int meson_uart_verify_port(struct uart_port *port, @@ -410,7 +435,7 @@ static int meson_uart_request_port(struct uart_port *port) return -EBUSY; } - port->membase = devm_ioremap_nocache(port->dev, port->mapbase, + port->membase = devm_ioremap(port->dev, port->mapbase, port->mapsize); if (!port->membase) return -ENOMEM; @@ -426,6 +451,64 @@ static void meson_uart_config_port(struct uart_port *port, int flags) } } +#ifdef CONFIG_CONSOLE_POLL +/* + * Console polling routines for writing and reading from the uart while + * in an interrupt or debug context (i.e. kgdb). + */ + +static int meson_uart_poll_get_char(struct uart_port *port) +{ + u32 c; + unsigned long flags; + + uart_port_lock_irqsave(port, &flags); + + if (readl(port->membase + AML_UART_STATUS) & AML_UART_RX_EMPTY) + c = NO_POLL_CHAR; + else + c = readl(port->membase + AML_UART_RFIFO); + + uart_port_unlock_irqrestore(port, flags); + + return c; +} + +static void meson_uart_poll_put_char(struct uart_port *port, unsigned char c) +{ + unsigned long flags; + u32 reg; + int ret; + + uart_port_lock_irqsave(port, &flags); + + /* Wait until FIFO is empty or timeout */ + ret = readl_poll_timeout_atomic(port->membase + AML_UART_STATUS, reg, + reg & AML_UART_TX_EMPTY, + AML_UART_POLL_USEC, + AML_UART_TIMEOUT_USEC); + if (ret == -ETIMEDOUT) { + dev_err(port->dev, "Timeout waiting for UART TX EMPTY\n"); + goto out; + } + + /* Write the character */ + writel(c, port->membase + AML_UART_WFIFO); + + /* Wait until FIFO is empty or timeout */ + ret = readl_poll_timeout_atomic(port->membase + AML_UART_STATUS, reg, + reg & AML_UART_TX_EMPTY, + AML_UART_POLL_USEC, + AML_UART_TIMEOUT_USEC); + if (ret == -ETIMEDOUT) + dev_err(port->dev, "Timeout waiting for UART TX EMPTY\n"); + +out: + uart_port_unlock_irqrestore(port, flags); +} + +#endif /* CONFIG_CONSOLE_POLL */ + static const struct uart_ops meson_uart_ops = { .set_mctrl = meson_uart_set_mctrl, .get_mctrl = meson_uart_get_mctrl, @@ -441,6 +524,10 @@ static const struct uart_ops meson_uart_ops = { .request_port = meson_uart_request_port, .release_port = meson_uart_release_port, .verify_port = meson_uart_verify_port, +#ifdef CONFIG_CONSOLE_POLL + .poll_get_char = meson_uart_poll_get_char, + .poll_put_char = meson_uart_poll_put_char, +#endif }; #ifdef CONFIG_SERIAL_MESON_CONSOLE @@ -453,7 +540,7 @@ static void meson_uart_enable_tx_engine(struct uart_port *port) writel(val, port->membase + AML_UART_CONTROL); } -static void meson_console_putchar(struct uart_port *port, int ch) +static void meson_console_putchar(struct uart_port *port, unsigned char ch) { if (!port->membase) return; @@ -467,18 +554,13 @@ static void meson_serial_port_write(struct uart_port *port, const char *s, u_int count) { unsigned long flags; - int locked; + int locked = 1; u32 val, tmp; - local_irq_save(flags); - if (port->sysrq) { - locked = 0; - } else if (oops_in_progress) { - locked = spin_trylock(&port->lock); - } else { - spin_lock(&port->lock); - locked = 1; - } + if (oops_in_progress) + locked = uart_port_trylock_irqsave(port, &flags); + else + uart_port_lock_irqsave(port, &flags); val = readl(port->membase + AML_UART_CONTROL); tmp = val & ~(AML_UART_TX_INT_EN | AML_UART_RX_INT_EN); @@ -488,8 +570,7 @@ static void meson_serial_port_write(struct uart_port *port, const char *s, writel(val, port->membase + AML_UART_CONTROL); if (locked) - spin_unlock(&port->lock); - local_irq_restore(flags); + uart_port_unlock_irqrestore(port, flags); } static void meson_serial_console_write(struct console *co, const char *s, @@ -527,22 +608,19 @@ static int meson_serial_console_setup(struct console *co, char *options) return uart_set_options(port, co, baud, parity, bits, flow); } -static struct console meson_serial_console = { - .name = AML_UART_DEV_NAME, - .write = meson_serial_console_write, - .device = uart_console_device, - .setup = meson_serial_console_setup, - .flags = CON_PRINTBUFFER, - .index = -1, - .data = &meson_uart_driver, -}; +#define MESON_SERIAL_CONSOLE(_devname) \ + static struct console meson_serial_console_##_devname = { \ + .name = __stringify(_devname), \ + .write = meson_serial_console_write, \ + .device = uart_console_device, \ + .setup = meson_serial_console_setup, \ + .flags = CON_PRINTBUFFER, \ + .index = -1, \ + .data = &meson_uart_driver_##_devname, \ + } -static int __init meson_serial_console_init(void) -{ - register_console(&meson_serial_console); - return 0; -} -console_initcall(meson_serial_console_init); +MESON_SERIAL_CONSOLE(ttyAML); +MESON_SERIAL_CONSOLE(ttyS); static void meson_serial_early_console_write(struct console *co, const char *s, @@ -563,67 +641,26 @@ meson_serial_early_console_setup(struct earlycon_device *device, const char *opt device->con->write = meson_serial_early_console_write; return 0; } -/* Legacy bindings, should be removed when no more used */ -OF_EARLYCON_DECLARE(meson, "amlogic,meson-uart", - meson_serial_early_console_setup); -/* Stable bindings */ -OF_EARLYCON_DECLARE(meson, "amlogic,meson-ao-uart", - meson_serial_early_console_setup); - -#define MESON_SERIAL_CONSOLE (&meson_serial_console) -#else -#define MESON_SERIAL_CONSOLE NULL -#endif - -static struct uart_driver meson_uart_driver = { - .owner = THIS_MODULE, - .driver_name = "meson_uart", - .dev_name = AML_UART_DEV_NAME, - .nr = AML_UART_PORT_NUM, - .cons = MESON_SERIAL_CONSOLE, -}; -static inline struct clk *meson_uart_probe_clock(struct device *dev, - const char *id) -{ - struct clk *clk = NULL; - int ret; +OF_EARLYCON_DECLARE(meson, "amlogic,meson-ao-uart", meson_serial_early_console_setup); +OF_EARLYCON_DECLARE(meson, "amlogic,meson-s4-uart", meson_serial_early_console_setup); - clk = devm_clk_get(dev, id); - if (IS_ERR(clk)) - return clk; +#define MESON_SERIAL_CONSOLE_PTR(_devname) (&meson_serial_console_##_devname) +#else +#define MESON_SERIAL_CONSOLE_PTR(_devname) (NULL) +#endif - ret = clk_prepare_enable(clk); - if (ret) { - dev_err(dev, "couldn't enable clk\n"); - return ERR_PTR(ret); +#define MESON_UART_DRIVER(_devname) \ + static struct uart_driver meson_uart_driver_##_devname = { \ + .owner = THIS_MODULE, \ + .driver_name = "meson_uart", \ + .dev_name = __stringify(_devname), \ + .nr = AML_UART_PORT_NUM, \ + .cons = MESON_SERIAL_CONSOLE_PTR(_devname), \ } - devm_add_action_or_reset(dev, - (void(*)(void *))clk_disable_unprepare, - clk); - - return clk; -} - -/* - * This function gets clocks in the legacy non-stable DT bindings. - * This code will be remove once all the platforms switch to the - * new DT bindings. - */ -static int meson_uart_probe_clocks_legacy(struct platform_device *pdev, - struct uart_port *port) -{ - struct clk *clk = NULL; - - clk = meson_uart_probe_clock(&pdev->dev, NULL); - if (IS_ERR(clk)) - return PTR_ERR(clk); - - port->uartclk = clk_get_rate(clk); - - return 0; -} +MESON_UART_DRIVER(ttyAML); +MESON_UART_DRIVER(ttyS); static int meson_uart_probe_clocks(struct platform_device *pdev, struct uart_port *port) @@ -632,15 +669,15 @@ static int meson_uart_probe_clocks(struct platform_device *pdev, struct clk *clk_pclk = NULL; struct clk *clk_baud = NULL; - clk_pclk = meson_uart_probe_clock(&pdev->dev, "pclk"); + clk_pclk = devm_clk_get_enabled(&pdev->dev, "pclk"); if (IS_ERR(clk_pclk)) return PTR_ERR(clk_pclk); - clk_xtal = meson_uart_probe_clock(&pdev->dev, "xtal"); + clk_xtal = devm_clk_get_enabled(&pdev->dev, "xtal"); if (IS_ERR(clk_xtal)) return PTR_ERR(clk_xtal); - clk_baud = meson_uart_probe_clock(&pdev->dev, "baud"); + clk_baud = devm_clk_get_enabled(&pdev->dev, "baud"); if (IS_ERR(clk_baud)) return PTR_ERR(clk_baud); @@ -649,15 +686,37 @@ static int meson_uart_probe_clocks(struct platform_device *pdev, return 0; } +static struct uart_driver *meson_uart_current(const struct meson_uart_data *pd) +{ + return (pd && pd->uart_driver) ? + pd->uart_driver : &meson_uart_driver_ttyAML; +} + static int meson_uart_probe(struct platform_device *pdev) { - struct resource *res_mem, *res_irq; + const struct meson_uart_data *priv_data; + struct uart_driver *uart_driver; + struct resource *res_mem; struct uart_port *port; + u32 fifosize = 64; /* Default is 64, 128 for EE UART_0 */ int ret = 0; + int irq; + bool has_rtscts; if (pdev->dev.of_node) pdev->id = of_alias_get_id(pdev->dev.of_node, "serial"); + if (pdev->id < 0) { + int id; + + for (id = AML_UART_PORT_OFFSET; id < AML_UART_PORT_NUM; id++) { + if (!meson_ports[id]) { + pdev->id = id; + break; + } + } + } + if (pdev->id < 0 || pdev->id >= AML_UART_PORT_NUM) return -EINVAL; @@ -665,39 +724,52 @@ static int meson_uart_probe(struct platform_device *pdev) if (!res_mem) return -ENODEV; - res_irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0); - if (!res_irq) - return -ENODEV; + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + of_property_read_u32(pdev->dev.of_node, "fifo-size", &fifosize); + has_rtscts = of_property_read_bool(pdev->dev.of_node, "uart-has-rtscts"); if (meson_ports[pdev->id]) { - dev_err(&pdev->dev, "port %d already allocated\n", pdev->id); - return -EBUSY; + return dev_err_probe(&pdev->dev, -EBUSY, + "port %d already allocated\n", pdev->id); } port = devm_kzalloc(&pdev->dev, sizeof(struct uart_port), GFP_KERNEL); if (!port) return -ENOMEM; - /* Use legacy way until all platforms switch to new bindings */ - if (of_device_is_compatible(pdev->dev.of_node, "amlogic,meson-uart")) - ret = meson_uart_probe_clocks_legacy(pdev, port); - else - ret = meson_uart_probe_clocks(pdev, port); - + ret = meson_uart_probe_clocks(pdev, port); if (ret) return ret; + priv_data = device_get_match_data(&pdev->dev); + + uart_driver = meson_uart_current(priv_data); + + if (!uart_driver->state) { + ret = uart_register_driver(uart_driver); + if (ret) + return dev_err_probe(&pdev->dev, ret, + "can't register uart driver\n"); + } + port->iotype = UPIO_MEM; port->mapbase = res_mem->start; port->mapsize = resource_size(res_mem); - port->irq = res_irq->start; + port->irq = irq; port->flags = UPF_BOOT_AUTOCONF | UPF_LOW_LATENCY; + if (has_rtscts) + port->flags |= UPF_HARD_FLOW; + port->has_sysrq = IS_ENABLED(CONFIG_SERIAL_MESON_CONSOLE); port->dev = &pdev->dev; port->line = pdev->id; port->type = PORT_MESON; port->x_char = 0; port->ops = &meson_uart_ops; - port->fifosize = 64; + port->fifosize = fifosize; + port->private_data = (void *)priv_data; meson_ports[pdev->id] = port; platform_set_drvdata(pdev, port); @@ -708,32 +780,62 @@ static int meson_uart_probe(struct platform_device *pdev) meson_uart_release_port(port); } - ret = uart_add_one_port(&meson_uart_driver, port); + ret = uart_add_one_port(uart_driver, port); if (ret) meson_ports[pdev->id] = NULL; return ret; } -static int meson_uart_remove(struct platform_device *pdev) +static void meson_uart_remove(struct platform_device *pdev) { + struct uart_driver *uart_driver; struct uart_port *port; port = platform_get_drvdata(pdev); - uart_remove_one_port(&meson_uart_driver, port); + uart_driver = meson_uart_current(port->private_data); + uart_remove_one_port(uart_driver, port); meson_ports[pdev->id] = NULL; - return 0; + for (int id = 0; id < AML_UART_PORT_NUM; id++) + if (meson_ports[id]) + return; + + /* No more available uart ports, unregister uart driver */ + uart_unregister_driver(uart_driver); } +static struct meson_uart_data meson_g12a_uart_data = { + .has_xtal_div2 = true, +}; + +static struct meson_uart_data meson_a1_uart_data = { + .uart_driver = &meson_uart_driver_ttyS, + .has_xtal_div2 = false, +}; + +static struct meson_uart_data meson_s4_uart_data = { + .uart_driver = &meson_uart_driver_ttyS, + .has_xtal_div2 = true, +}; + static const struct of_device_id meson_uart_dt_match[] = { - /* Legacy bindings, should be removed when no more used */ - { .compatible = "amlogic,meson-uart" }, - /* Stable bindings */ { .compatible = "amlogic,meson6-uart" }, { .compatible = "amlogic,meson8-uart" }, { .compatible = "amlogic,meson8b-uart" }, { .compatible = "amlogic,meson-gx-uart" }, + { + .compatible = "amlogic,meson-g12a-uart", + .data = (void *)&meson_g12a_uart_data, + }, + { + .compatible = "amlogic,meson-s4-uart", + .data = (void *)&meson_s4_uart_data, + }, + { + .compatible = "amlogic,meson-a1-uart", + .data = (void *)&meson_a1_uart_data, + }, { /* sentinel */ }, }; MODULE_DEVICE_TABLE(of, meson_uart_dt_match); @@ -747,29 +849,7 @@ static struct platform_driver meson_uart_platform_driver = { }, }; -static int __init meson_uart_init(void) -{ - int ret; - - ret = uart_register_driver(&meson_uart_driver); - if (ret) - return ret; - - ret = platform_driver_register(&meson_uart_platform_driver); - if (ret) - uart_unregister_driver(&meson_uart_driver); - - return ret; -} - -static void __exit meson_uart_exit(void) -{ - platform_driver_unregister(&meson_uart_platform_driver); - uart_unregister_driver(&meson_uart_driver); -} - -module_init(meson_uart_init); -module_exit(meson_uart_exit); +module_platform_driver(meson_uart_platform_driver); MODULE_AUTHOR("Carlo Caione <carlo@caione.org>"); MODULE_DESCRIPTION("Amlogic Meson serial port driver"); |
