From mboxrd@z Thu Jan 1 00:00:00 1970 From: Mark Rutland Subject: Re: [PATCH 01/10] serial: mvebu-uart: initial support for Armada-3700 serial port Date: Tue, 2 Feb 2016 18:19:29 +0000 Message-ID: <20160202181929.GB29778@leverpostej> References: <1454436468-4241-1-git-send-email-gregory.clement@free-electrons.com> <1454436468-4241-2-git-send-email-gregory.clement@free-electrons.com> Mime-Version: 1.0 Content-Type: text/plain; charset=us-ascii Return-path: Content-Disposition: inline In-Reply-To: <1454436468-4241-2-git-send-email-gregory.clement@free-electrons.com> Sender: linux-kernel-owner@vger.kernel.org To: Gregory CLEMENT Cc: Jason Cooper , Andrew Lunn , Sebastian Hesselbarth , arm@kernel.org, Catalin Marinas , Will Deacon , Jonathan Corbet , Greg Kroah-Hartman , Jiri Slaby , linux-serial@vger.kernel.org, Tejun Heo , Hans de Goede , linux-ide@vger.kernel.org, linux-kernel@vger.kernel.org, devicetree@vger.kernel.org, Thomas Petazzoni , linux-arm-kernel@lists.infradead.org, Wilson Ding , Nadav Haklai List-Id: linux-ide@vger.kernel.org On Tue, Feb 02, 2016 at 07:07:39PM +0100, Gregory CLEMENT wrote: > From: Wilson Ding > > Armada-3700's uart is a simple serial port, which doesn't > support. Configuring the modem control lines. The uart port has a 32 > bytes Tx FIFO and a 64 bytes Rx FIFO > > The uart driver implements the uart core operations. It also support the > system (early) console based on Armada-3700's serial port. > > Known Issue: > > The uart driver currently doesn't support clock programming, which means > the baud-rate stays with the default value configured by the bootloader > at boot time This looks like any other early console. Why does this not use the earlycon infrastructure? See {OF_,}EARLYCON_DECLARE (e.g. in drivers/tty/serial/amba-pl011.c). Mark. > [gregory.clement@free-electrons.com: Rewrite many part which are too long > to enumerate] > > Signed-off-by: Wilson Ding > Signed-off-by: Nadav Haklai > Signed-off-by: Gregory CLEMENT > --- > .../devicetree/bindings/tty/serial/mvebu-uart.txt | 13 + > Documentation/kernel-parameters.txt | 6 + > drivers/tty/serial/Kconfig | 22 + > drivers/tty/serial/Makefile | 1 + > drivers/tty/serial/mvebu-uart.c | 649 +++++++++++++++++++++ > include/uapi/linux/serial_core.h | 3 + > 6 files changed, 694 insertions(+) > create mode 100644 Documentation/devicetree/bindings/tty/serial/mvebu-uart.txt > create mode 100644 drivers/tty/serial/mvebu-uart.c > > diff --git a/Documentation/devicetree/bindings/tty/serial/mvebu-uart.txt b/Documentation/devicetree/bindings/tty/serial/mvebu-uart.txt > new file mode 100644 > index 000000000000..6087defd9f93 > --- /dev/null > +++ b/Documentation/devicetree/bindings/tty/serial/mvebu-uart.txt > @@ -0,0 +1,13 @@ > +* Marvell UART : Non standard UART used in some of Marvell EBU SoCs (e.g., Armada-3700) > + > +Required properties: > +- compatible: "marvell,armada-3700-uart" > +- reg: offset and length of the register set for the device. > +- interrupts: device interrupt > + > +Example: > + serial@12000 { > + compatible = "marvell,armada-3700-uart"; > + reg = <0x12000 0x400>; > + interrupts = <43>; > + }; > diff --git a/Documentation/kernel-parameters.txt b/Documentation/kernel-parameters.txt > index 87d40a72f6a1..198f6bd56e84 100644 > --- a/Documentation/kernel-parameters.txt > +++ b/Documentation/kernel-parameters.txt > @@ -1058,6 +1058,12 @@ bytes respectively. Such letter suffixes can also be entirely omitted. > A valid base address must be provided, and the serial > port must already be setup and configured. > > + mvebu_uart, > + Start an early, polled-mode console on an some mvebu > + SoC (as the Armada-3700) serial port at the specified > + address. The serial port must already be setup and > + configured. Options are not yet supported. > + > earlyprintk= [X86,SH,BLACKFIN,ARM,M68k] > earlyprintk=vga > earlyprintk=efi > diff --git a/drivers/tty/serial/Kconfig b/drivers/tty/serial/Kconfig > index 39721ec4f415..b291f934d51b 100644 > --- a/drivers/tty/serial/Kconfig > +++ b/drivers/tty/serial/Kconfig > @@ -1606,6 +1606,28 @@ config SERIAL_STM32_CONSOLE > depends on SERIAL_STM32=y > select SERIAL_CORE_CONSOLE > > +config SERIAL_MVEBU_UART > + bool "Marvell EBU serial port support" > + select SERIAL_CORE > + help > + This driver is for Marvell EBU SoC's UART. If you have a machine > + based on the Armada-3700 SoC and wish to use the on-board serial > + port, > + say 'Y' here. > + Otherwise, say 'N'. > + > +config SERIAL_MVEBU_CONSOLE > + bool "Console on Marvell EBU serial port" > + depends on SERIAL_MVEBU_UART > + select SERIAL_CORE_CONSOLE > + select SERIAL_EARLYCON > + default y > + help > + Say 'Y' here if you wish to use Armada-3700 UART as the system console. > + (the system console is the device which receives all kernel messages > + and warnings and which allows logins in single user mode) > + Otherwise, say 'N'. > + > endmenu > > config SERIAL_MCTRL_GPIO > diff --git a/drivers/tty/serial/Makefile b/drivers/tty/serial/Makefile > index b391c9b31960..988167595330 100644 > --- a/drivers/tty/serial/Makefile > +++ b/drivers/tty/serial/Makefile > @@ -91,6 +91,7 @@ obj-$(CONFIG_SERIAL_CONEXANT_DIGICOLOR) += digicolor-usart.o > obj-$(CONFIG_SERIAL_MEN_Z135) += men_z135_uart.o > obj-$(CONFIG_SERIAL_SPRD) += sprd_serial.o > obj-$(CONFIG_SERIAL_STM32) += stm32-usart.o > +obj-$(CONFIG_SERIAL_MVEBU_UART) += mvebu-uart.o > > # GPIOLIB helpers for modem control lines > obj-$(CONFIG_SERIAL_MCTRL_GPIO) += serial_mctrl_gpio.o > diff --git a/drivers/tty/serial/mvebu-uart.c b/drivers/tty/serial/mvebu-uart.c > new file mode 100644 > index 000000000000..9624070d6058 > --- /dev/null > +++ b/drivers/tty/serial/mvebu-uart.c > @@ -0,0 +1,649 @@ > +/* > +* *************************************************************************** > +* Copyright (C) 2015 Marvell International Ltd. > +* *************************************************************************** > +* 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 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. > +* > +* You should have received a copy of the GNU General Public License > +* along with this program. If not, see . > +* *************************************************************************** > +*/ > + > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > + > +/* Register Map */ > +#define UART_RBR 0x00 > +#define RBR_BRK_DET BIT(15) > +#define RBR_FRM_ERR_DET BIT(14) > +#define RBR_PAR_ERR_DET BIT(13) > +#define RBR_OVR_ERR_DET BIT(12) > + > +#define UART_TSH 0x04 > + > +#define UART_CTRL 0x08 > +#define CTRL_SOFT_RST BIT(31) > +#define CTRL_TXFIFO_RST BIT(15) > +#define CTRL_RXFIFO_RST BIT(14) > +#define CTRL_ST_MIRR_EN BIT(13) > +#define CTRL_LPBK_EN BIT(12) > +#define CTRL_SND_BRK_SEQ BIT(11) > +#define CTRL_PAR_EN BIT(10) > +#define CTRL_TWO_STOP BIT(9) > +#define CTRL_TX_HFL_INT BIT(8) > +#define CTRL_RX_HFL_INT BIT(7) > +#define CTRL_TX_EMP_INT BIT(6) > +#define CTRL_TX_RDY_INT BIT(5) > +#define CTRL_RX_RDY_INT BIT(4) > +#define CTRL_BRK_DET_INT BIT(3) > +#define CTRL_FRM_ERR_INT BIT(2) > +#define CTRL_PAR_ERR_INT BIT(1) > +#define CTRL_OVR_ERR_INT BIT(0) > +#define CTRL_RX_INT (CTRL_RX_RDY_INT | CTRL_BRK_DET_INT |\ > + CTRL_FRM_ERR_INT | CTRL_PAR_ERR_INT | CTRL_OVR_ERR_INT) > + > +#define UART_STAT 0x0c > +#define STAT_TX_FIFO_EMP BIT(13) > +#define STAT_RX_FIFO_EMP BIT(12) > +#define STAT_TX_FIFO_FUL BIT(11) > +#define STAT_TX_FIFO_HFL BIT(10) > +#define STAT_RX_TOGL BIT(9) > +#define STAT_RX_FIFO_FUL BIT(8) > +#define STAT_RX_FIFO_HFL BIT(7) > +#define STAT_TX_EMP BIT(6) > +#define STAT_TX_RDY BIT(5) > +#define STAT_RX_RDY BIT(4) > +#define STAT_BRK_DET BIT(3) > +#define STAT_FRM_ERR BIT(2) > +#define STAT_PAR_ERR BIT(1) > +#define STAT_OVR_ERR BIT(0) > +#define STAT_BRK_ERR (STAT_BRK_DET | STAT_FRM_ERR | STAT_FRM_ERR\ > + | STAT_PAR_ERR | STAT_OVR_ERR) > + > +#define UART_BRDV 0x10 > + > +#define MVEBU_NR_UARTS 1 > + > +#define MVEBU_UART_TYPE "mvebu-uart" > + > +static struct uart_port mvebu_uart_ports[MVEBU_NR_UARTS]; > + > +struct mvebu_uart_data { > + struct uart_port *port; > + struct clk *clk; > +}; > + > +/* Core UART Driver Operations */ > +static unsigned int mvebu_uart_tx_empty(struct uart_port *port) > +{ > + unsigned long flags; > + unsigned int st; > + > + spin_lock_irqsave(&port->lock, flags); > + st = readl(port->membase + UART_STAT); > + spin_unlock_irqrestore(&port->lock, flags); > + > + return (st & STAT_TX_FIFO_EMP) ? TIOCSER_TEMT : 0; > +} > + > +static unsigned int mvebu_uart_get_mctrl(struct uart_port *port) > +{ > + return TIOCM_CTS | TIOCM_DSR | TIOCM_CAR; > +} > + > +static void mvebu_uart_set_mctrl(struct uart_port *port, > + unsigned int mctrl) > +{ > +/* > + * Even if we do not support configuring the modem control lines, this > + * function must be proided to the serial core > + */ > +} > + > +static void mvebu_uart_stop_tx(struct uart_port *port) > +{ > + unsigned int ctl = readl(port->membase + UART_CTRL); > + > + ctl &= ~CTRL_TX_RDY_INT; > + writel(ctl, port->membase + UART_CTRL); > +} > + > +static void mvebu_uart_start_tx(struct uart_port *port) > +{ > + unsigned int ctl = readl(port->membase + UART_CTRL); > + > + ctl |= CTRL_TX_RDY_INT; > + writel(ctl, port->membase + UART_CTRL); > +} > + > +static void mvebu_uart_stop_rx(struct uart_port *port) > +{ > + unsigned int ctl = readl(port->membase + UART_CTRL); > + > + ctl &= ~CTRL_RX_INT; > + writel(ctl, port->membase + UART_CTRL); > +} > + > +static void mvebu_uart_break_ctl(struct uart_port *port, int brk) > +{ > + unsigned int ctl; > + unsigned long flags; > + > + spin_lock_irqsave(&port->lock, flags); > + ctl = readl(port->membase + UART_CTRL); > + if (brk == -1) > + ctl |= CTRL_SND_BRK_SEQ; > + else > + ctl &= ~CTRL_SND_BRK_SEQ; > + writel(ctl, port->membase + UART_CTRL); > + spin_unlock_irqrestore(&port->lock, flags); > +} > + > +static void mvebu_uart_rx_chars(struct uart_port *port, unsigned int status) > +{ > + struct tty_port *tport = &port->state->port; > + unsigned char ch = 0; > + char flag = 0; > + > + do { > + if (status & STAT_RX_RDY) { > + ch = readl(port->membase + UART_RBR); > + ch &= 0xff; > + flag = TTY_NORMAL; > + port->icount.rx++; > + > + if (status & STAT_PAR_ERR) > + port->icount.parity++; > + } > + > + if (status & STAT_BRK_DET) { > + port->icount.brk++; > + status &= ~(STAT_FRM_ERR | STAT_PAR_ERR); > + if (uart_handle_break(port)) > + goto ignore_char; > + } > + > + if (status & STAT_OVR_ERR) > + port->icount.overrun++; > + > + if (status & STAT_FRM_ERR) > + port->icount.frame++; > + > + if (uart_handle_sysrq_char(port, ch)) > + goto ignore_char; > + > + if (status & port->ignore_status_mask & STAT_PAR_ERR) > + status &= ~STAT_RX_RDY; > + > + status &= port->read_status_mask; > + > + if (status & STAT_PAR_ERR) > + flag = TTY_PARITY; > + > + status &= ~port->ignore_status_mask; > + > + if (status & STAT_RX_RDY) > + tty_insert_flip_char(tport, ch, flag); > + > + if (status & STAT_BRK_DET) > + tty_insert_flip_char(tport, 0, TTY_BREAK); > + > + if (status & STAT_FRM_ERR) > + tty_insert_flip_char(tport, 0, TTY_FRAME); > + > + if (status & STAT_OVR_ERR) > + tty_insert_flip_char(tport, 0, TTY_OVERRUN); > + > +ignore_char: > + status = readl(port->membase + UART_STAT); > + } while (status & (STAT_RX_RDY | STAT_BRK_DET)); > + > + tty_flip_buffer_push(tport); > +} > + > +static void mvebu_uart_tx_chars(struct uart_port *port, unsigned int status) > +{ > + struct circ_buf *xmit = &port->state->xmit; > + unsigned int count; > + unsigned int st; > + > + if (port->x_char) { > + writel(port->x_char, port->membase + UART_TSH); > + port->icount.tx++; > + port->x_char = 0; > + return; > + } > + > + if (uart_circ_empty(xmit) || uart_tx_stopped(port)) { > + mvebu_uart_stop_tx(port); > + return; > + } > + > + for (count = 0; count < port->fifosize; count++) { > + writel(xmit->buf[xmit->tail], port->membase + UART_TSH); > + xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1); > + port->icount.tx++; > + > + if (uart_circ_empty(xmit)) > + break; > + > + st = readl(port->membase + UART_STAT); > + if (st & STAT_TX_FIFO_FUL) > + break; > + } > + > + if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS) > + uart_write_wakeup(port); > + > + if (uart_circ_empty(xmit)) > + mvebu_uart_stop_tx(port); > +} > + > +static irqreturn_t mvebu_uart_isr(int irq, void *dev_id) > +{ > + struct uart_port *port = (struct uart_port *)dev_id; > + unsigned int st = readl(port->membase + UART_STAT); > + > + if (st & (STAT_RX_RDY | STAT_OVR_ERR | STAT_FRM_ERR | STAT_BRK_DET)) > + mvebu_uart_rx_chars(port, st); > + > + if (st & STAT_TX_RDY) > + mvebu_uart_tx_chars(port, st); > + > + return IRQ_HANDLED; > +} > + > +static int mvebu_uart_startup(struct uart_port *port) > +{ > + int ret; > + > + writel(CTRL_TXFIFO_RST | CTRL_RXFIFO_RST, > + port->membase + UART_CTRL); > + udelay(1); > + writel(CTRL_RX_INT, port->membase + UART_CTRL); > + > + ret = request_irq(port->irq, mvebu_uart_isr, port->irqflags, "serial", > + port); > + if (ret) { > + dev_err(port->dev, "failed to request irq\n"); > + return ret; > + } > + > + return 0; > +} > + > +static void mvebu_uart_shutdown(struct uart_port *port) > +{ > + writel(0, port->membase + UART_CTRL); > +} > + > +static void mvebu_uart_set_termios(struct uart_port *port, > + struct ktermios *termios, > + struct ktermios *old) > +{ > + unsigned long flags; > + unsigned int baud; > + > + spin_lock_irqsave(&port->lock, flags); > + > + port->read_status_mask = STAT_RX_RDY | STAT_OVR_ERR | > + STAT_TX_RDY | STAT_TX_FIFO_FUL; > + > + if (termios->c_iflag & INPCK) > + port->read_status_mask |= STAT_FRM_ERR | STAT_PAR_ERR; > + > + port->ignore_status_mask = 0; > + if (termios->c_iflag & IGNPAR) > + port->ignore_status_mask |= > + STAT_FRM_ERR | STAT_PAR_ERR | STAT_OVR_ERR; > + > + if ((termios->c_cflag & CREAD) == 0) > + port->ignore_status_mask |= STAT_RX_RDY | STAT_BRK_ERR; > + > + baud = uart_get_baud_rate(port, termios, old, 0, 460800); > + uart_update_timeout(port, termios->c_cflag, baud); > + > + spin_unlock_irqrestore(&port->lock, flags); > +} > + > +static const char *mvebu_uart_type(struct uart_port *port) > +{ > + return MVEBU_UART_TYPE; > +} > + > +static void mvebu_uart_release_port(struct uart_port *port) > +{ > + /* Nothing to do here */ > +} > + > +static int mvebu_uart_request_port(struct uart_port *port) > +{ > + return 0; > +} > + > +#ifdef CONFIG_CONSOLE_POLL > +static int mvebu_uart_get_poll_char(struct uart_port *port) > +{ > + unsigned int st = readl(port->membase + UART_STAT); > + > + if (!(st & STAT_RX_RDY)) > + return NO_POLL_CHAR; > + > + return readl(port->membase + UART_RBR); > +} > + > +static void mvebu_uart_put_poll_char(struct uart_port *port, unsigned char c) > +{ > + unsigned int st; > + > + for (;;) { > + st = readl(port->membase + UART_STAT); > + > + if (!(st & STAT_TX_FIFO_FUL)) > + break; > + > + udelay(1); > + } > + > + writel(c, port->membase + UART_TSH); > +} > +#endif > + > +static const struct uart_ops mvebu_uart_ops = { > + .tx_empty = mvebu_uart_tx_empty, > + .set_mctrl = mvebu_uart_set_mctrl, > + .get_mctrl = mvebu_uart_get_mctrl, > + .stop_tx = mvebu_uart_stop_tx, > + .start_tx = mvebu_uart_start_tx, > + .stop_rx = mvebu_uart_stop_rx, > + .break_ctl = mvebu_uart_break_ctl, > + .startup = mvebu_uart_startup, > + .shutdown = mvebu_uart_shutdown, > + .set_termios = mvebu_uart_set_termios, > + .type = mvebu_uart_type, > + .release_port = mvebu_uart_release_port, > + .request_port = mvebu_uart_request_port, > +#ifdef CONFIG_CONSOLE_POLL > + .poll_get_char = mvebu_uart_get_poll_char, > + .poll_put_char = mvebu_uart_put_poll_char, > +#endif > +}; > + > +/* Console Driver Operations */ > + > +#ifdef CONFIG_SERIAL_MVEBU_CONSOLE > +/* Early Console */ > +static void mvebu_uart_putc(struct uart_port *port, int c) > +{ > + unsigned int st; > + > + for (;;) { > + st = readl(port->membase + UART_STAT); > + if (!(st & STAT_TX_FIFO_FUL)) > + break; > + } > + > + writel(c, port->membase + UART_TSH); > + > + for (;;) { > + st = readl(port->membase + UART_STAT); > + if (st & STAT_TX_FIFO_EMP) > + break; > + } > +} > + > +static void mvebu_uart_putc_early_write(struct console *con, > + const char *s, > + unsigned n) > +{ > + struct earlycon_device *dev = con->data; > + > + uart_console_write(&dev->port, s, n, mvebu_uart_putc); > +} > + > +static int __init > +mvebu_uart_early_console_setup(struct earlycon_device *device, > + const char *opt) > +{ > + if (!device->port.membase) > + return -ENODEV; > + > + device->con->write = mvebu_uart_putc_early_write; > + > + return 0; > +} > + > +EARLYCON_DECLARE(ar3700_uart, mvebu_uart_early_console_setup); > +OF_EARLYCON_DECLARE(ar3700_uart, "marvell,armada-3700-uart", > + mvebu_uart_early_console_setup); > + > +static void wait_for_xmitr(struct uart_port *port) > +{ > + u32 val; > + > + readl_poll_timeout_atomic(port->membase + UART_STAT, val, > + (val & STAT_TX_EMP), 1, 10000); > +} > + > +static void mvebu_uart_console_putchar(struct uart_port *port, int ch) > +{ > + wait_for_xmitr(port); > + writel(ch, port->membase + UART_TSH); > +} > + > +static void mvebu_uart_console_write(struct console *co, const char *s, > + unsigned int count) > +{ > + struct uart_port *port = &mvebu_uart_ports[co->index]; > + unsigned long flags; > + unsigned int ier; > + int locked = 1; > + > + if (oops_in_progress) > + locked = spin_trylock_irqsave(&port->lock, flags); > + else > + spin_lock_irqsave(&port->lock, flags); > + > + ier = readl(port->membase + UART_CTRL) & > + (CTRL_RX_INT | CTRL_TX_RDY_INT); > + writel(0, port->membase + UART_CTRL); > + > + uart_console_write(port, s, count, mvebu_uart_console_putchar); > + > + wait_for_xmitr(port); > + > + if (ier) > + writel(ier, port->membase + UART_CTRL); > + > + if (locked) > + spin_unlock_irqrestore(&port->lock, flags); > +} > + > +static int mvebu_uart_console_setup(struct console *co, char *options) > +{ > + struct uart_port *port; > + int baud = 9600; > + int bits = 8; > + int parity = 'n'; > + int flow = 'n'; > + > + if (co->index < 0 || co->index >= MVEBU_NR_UARTS) > + return -EINVAL; > + > + port = &mvebu_uart_ports[co->index]; > + > + if (!port->mapbase || !port->membase) { > + pr_debug("console on ttyS%i not present\n", co->index); > + return -ENODEV; > + } > + > + if (options) > + uart_parse_options(options, &baud, &parity, &bits, &flow); > + > + return uart_set_options(port, co, baud, parity, bits, flow); > +} > + > +static struct uart_driver mvebu_uart_driver; > + > +static struct console mvebu_uart_console = { > + .name = "ttyS", > + .write = mvebu_uart_console_write, > + .device = uart_console_device, > + .setup = mvebu_uart_console_setup, > + .flags = CON_PRINTBUFFER, > + .index = -1, > + .data = &mvebu_uart_driver, > +}; > + > +static int __init mvebu_uart_console_init(void) > +{ > + register_console(&mvebu_uart_console); > + return 0; > +} > + > +console_initcall(mvebu_uart_console_init); > + > + > +#endif /* CONFIG_SERIAL_MVEBU_CONSOLE */ > + > +static struct uart_driver mvebu_uart_driver = { > + .owner = THIS_MODULE, > + .driver_name = "serial", > + .dev_name = "ttyS", > + .major = TTY_MAJOR, > + .minor = 64, > + .nr = MVEBU_NR_UARTS, > +#ifdef CONFIG_SERIAL_MVEBU_CONSOLE > + .cons = &mvebu_uart_console, > +#endif > +}; > + > +static int mvebu_uart_probe(struct platform_device *pdev) > +{ > + struct resource *reg = platform_get_resource(pdev, IORESOURCE_MEM, 0); > + struct resource *irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0); > + struct uart_port *port; > + struct mvebu_uart_data *data; > + int ret; > + > + if (!reg || !irq) { > + dev_err(&pdev->dev, "no registers/irq defined\n"); > + return -EINVAL; > + } > + > + port = &mvebu_uart_ports[0]; > + > + spin_lock_init(&port->lock); > + > + port->dev = &pdev->dev; > + port->type = PORT_MVEBU; > + port->ops = &mvebu_uart_ops; > + port->regshift = 0; > + > + port->fifosize = 32; > + port->iotype = UPIO_MEM32; > + port->flags = UPF_FIXED_PORT; > + port->line = 0; /* single port: force line number to 0 */ > + > + port->irq = irq->start; > + port->irqflags = 0; > + port->mapbase = reg->start; > + > + port->membase = devm_ioremap_resource(&pdev->dev, reg); > + if (IS_ERR(port->membase)) > + return -PTR_ERR(port->membase); > + > + data = devm_kzalloc(&pdev->dev, sizeof(struct mvebu_uart_data), > + GFP_KERNEL); > + if (!data) > + return -ENOMEM; > + > + data->port = port; > + > + port->private_data = data; > + platform_set_drvdata(pdev, data); > + > + ret = uart_add_one_port(&mvebu_uart_driver, port); > + if (ret) > + return ret; > + return 0; > +} > + > +static int mvebu_uart_remove(struct platform_device *pdev) > +{ > + struct mvebu_uart_data *data = platform_get_drvdata(pdev); > + > + uart_remove_one_port(&mvebu_uart_driver, data->port); > + data->port->private_data = NULL; > + data->port->mapbase = 0; > + return 0; > +} > + > +/* Match table for of_platform binding */ > +static const struct of_device_id mvebu_uart_of_match[] = { > + { .compatible = "marvell,armada-3700-uart", }, > + {} > +}; > +MODULE_DEVICE_TABLE(of, mvebu_uart_of_match); > + > +static struct platform_driver mvebu_uart_platform_driver = { > + .probe = mvebu_uart_probe, > + .remove = mvebu_uart_remove, > + .driver = { > + .owner = THIS_MODULE, > + .name = "mvebu-uart", > + .of_match_table = of_match_ptr(mvebu_uart_of_match), > + }, > +}; > + > +static int __init mvebu_uart_init(void) > +{ > + int ret; > + > + ret = uart_register_driver(&mvebu_uart_driver); > + if (ret) > + return ret; > + > + ret = platform_driver_register(&mvebu_uart_platform_driver); > + if (ret) > + uart_unregister_driver(&mvebu_uart_driver); > + > + return ret; > +} > + > +static void __exit mvebu_uart_exit(void) > +{ > + platform_driver_unregister(&mvebu_uart_platform_driver); > + uart_unregister_driver(&mvebu_uart_driver); > +} > + > +arch_initcall(mvebu_uart_init); > +module_exit(mvebu_uart_exit); > + > +MODULE_AUTHOR("Wilson Ding "); > +MODULE_DESCRIPTION("Marvell Armada-3700 Serial Driver"); > +MODULE_LICENSE("GPL"); > diff --git a/include/uapi/linux/serial_core.h b/include/uapi/linux/serial_core.h > index 3e5d757407fb..e513a4ee369b 100644 > --- a/include/uapi/linux/serial_core.h > +++ b/include/uapi/linux/serial_core.h > @@ -261,4 +261,7 @@ > /* STM32 USART */ > #define PORT_STM32 113 > > +/* MVEBU UART */ > +#define PORT_MVEBU 114 > + > #endif /* _UAPILINUX_SERIAL_CORE_H */ > -- > 2.5.0 > From mboxrd@z Thu Jan 1 00:00:00 1970 From: mark.rutland@arm.com (Mark Rutland) Date: Tue, 2 Feb 2016 18:19:29 +0000 Subject: [PATCH 01/10] serial: mvebu-uart: initial support for Armada-3700 serial port In-Reply-To: <1454436468-4241-2-git-send-email-gregory.clement@free-electrons.com> References: <1454436468-4241-1-git-send-email-gregory.clement@free-electrons.com> <1454436468-4241-2-git-send-email-gregory.clement@free-electrons.com> Message-ID: <20160202181929.GB29778@leverpostej> To: linux-arm-kernel@lists.infradead.org List-Id: linux-arm-kernel.lists.infradead.org On Tue, Feb 02, 2016 at 07:07:39PM +0100, Gregory CLEMENT wrote: > From: Wilson Ding > > Armada-3700's uart is a simple serial port, which doesn't > support. Configuring the modem control lines. The uart port has a 32 > bytes Tx FIFO and a 64 bytes Rx FIFO > > The uart driver implements the uart core operations. It also support the > system (early) console based on Armada-3700's serial port. > > Known Issue: > > The uart driver currently doesn't support clock programming, which means > the baud-rate stays with the default value configured by the bootloader > at boot time This looks like any other early console. Why does this not use the earlycon infrastructure? See {OF_,}EARLYCON_DECLARE (e.g. in drivers/tty/serial/amba-pl011.c). Mark. > [gregory.clement at free-electrons.com: Rewrite many part which are too long > to enumerate] > > Signed-off-by: Wilson Ding > Signed-off-by: Nadav Haklai > Signed-off-by: Gregory CLEMENT > --- > .../devicetree/bindings/tty/serial/mvebu-uart.txt | 13 + > Documentation/kernel-parameters.txt | 6 + > drivers/tty/serial/Kconfig | 22 + > drivers/tty/serial/Makefile | 1 + > drivers/tty/serial/mvebu-uart.c | 649 +++++++++++++++++++++ > include/uapi/linux/serial_core.h | 3 + > 6 files changed, 694 insertions(+) > create mode 100644 Documentation/devicetree/bindings/tty/serial/mvebu-uart.txt > create mode 100644 drivers/tty/serial/mvebu-uart.c > > diff --git a/Documentation/devicetree/bindings/tty/serial/mvebu-uart.txt b/Documentation/devicetree/bindings/tty/serial/mvebu-uart.txt > new file mode 100644 > index 000000000000..6087defd9f93 > --- /dev/null > +++ b/Documentation/devicetree/bindings/tty/serial/mvebu-uart.txt > @@ -0,0 +1,13 @@ > +* Marvell UART : Non standard UART used in some of Marvell EBU SoCs (e.g., Armada-3700) > + > +Required properties: > +- compatible: "marvell,armada-3700-uart" > +- reg: offset and length of the register set for the device. > +- interrupts: device interrupt > + > +Example: > + serial at 12000 { > + compatible = "marvell,armada-3700-uart"; > + reg = <0x12000 0x400>; > + interrupts = <43>; > + }; > diff --git a/Documentation/kernel-parameters.txt b/Documentation/kernel-parameters.txt > index 87d40a72f6a1..198f6bd56e84 100644 > --- a/Documentation/kernel-parameters.txt > +++ b/Documentation/kernel-parameters.txt > @@ -1058,6 +1058,12 @@ bytes respectively. Such letter suffixes can also be entirely omitted. > A valid base address must be provided, and the serial > port must already be setup and configured. > > + mvebu_uart, > + Start an early, polled-mode console on an some mvebu > + SoC (as the Armada-3700) serial port at the specified > + address. The serial port must already be setup and > + configured. Options are not yet supported. > + > earlyprintk= [X86,SH,BLACKFIN,ARM,M68k] > earlyprintk=vga > earlyprintk=efi > diff --git a/drivers/tty/serial/Kconfig b/drivers/tty/serial/Kconfig > index 39721ec4f415..b291f934d51b 100644 > --- a/drivers/tty/serial/Kconfig > +++ b/drivers/tty/serial/Kconfig > @@ -1606,6 +1606,28 @@ config SERIAL_STM32_CONSOLE > depends on SERIAL_STM32=y > select SERIAL_CORE_CONSOLE > > +config SERIAL_MVEBU_UART > + bool "Marvell EBU serial port support" > + select SERIAL_CORE > + help > + This driver is for Marvell EBU SoC's UART. If you have a machine > + based on the Armada-3700 SoC and wish to use the on-board serial > + port, > + say 'Y' here. > + Otherwise, say 'N'. > + > +config SERIAL_MVEBU_CONSOLE > + bool "Console on Marvell EBU serial port" > + depends on SERIAL_MVEBU_UART > + select SERIAL_CORE_CONSOLE > + select SERIAL_EARLYCON > + default y > + help > + Say 'Y' here if you wish to use Armada-3700 UART as the system console. > + (the system console is the device which receives all kernel messages > + and warnings and which allows logins in single user mode) > + Otherwise, say 'N'. > + > endmenu > > config SERIAL_MCTRL_GPIO > diff --git a/drivers/tty/serial/Makefile b/drivers/tty/serial/Makefile > index b391c9b31960..988167595330 100644 > --- a/drivers/tty/serial/Makefile > +++ b/drivers/tty/serial/Makefile > @@ -91,6 +91,7 @@ obj-$(CONFIG_SERIAL_CONEXANT_DIGICOLOR) += digicolor-usart.o > obj-$(CONFIG_SERIAL_MEN_Z135) += men_z135_uart.o > obj-$(CONFIG_SERIAL_SPRD) += sprd_serial.o > obj-$(CONFIG_SERIAL_STM32) += stm32-usart.o > +obj-$(CONFIG_SERIAL_MVEBU_UART) += mvebu-uart.o > > # GPIOLIB helpers for modem control lines > obj-$(CONFIG_SERIAL_MCTRL_GPIO) += serial_mctrl_gpio.o > diff --git a/drivers/tty/serial/mvebu-uart.c b/drivers/tty/serial/mvebu-uart.c > new file mode 100644 > index 000000000000..9624070d6058 > --- /dev/null > +++ b/drivers/tty/serial/mvebu-uart.c > @@ -0,0 +1,649 @@ > +/* > +* *************************************************************************** > +* Copyright (C) 2015 Marvell International Ltd. > +* *************************************************************************** > +* 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 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. > +* > +* You should have received a copy of the GNU General Public License > +* along with this program. If not, see . > +* *************************************************************************** > +*/ > + > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > + > +/* Register Map */ > +#define UART_RBR 0x00 > +#define RBR_BRK_DET BIT(15) > +#define RBR_FRM_ERR_DET BIT(14) > +#define RBR_PAR_ERR_DET BIT(13) > +#define RBR_OVR_ERR_DET BIT(12) > + > +#define UART_TSH 0x04 > + > +#define UART_CTRL 0x08 > +#define CTRL_SOFT_RST BIT(31) > +#define CTRL_TXFIFO_RST BIT(15) > +#define CTRL_RXFIFO_RST BIT(14) > +#define CTRL_ST_MIRR_EN BIT(13) > +#define CTRL_LPBK_EN BIT(12) > +#define CTRL_SND_BRK_SEQ BIT(11) > +#define CTRL_PAR_EN BIT(10) > +#define CTRL_TWO_STOP BIT(9) > +#define CTRL_TX_HFL_INT BIT(8) > +#define CTRL_RX_HFL_INT BIT(7) > +#define CTRL_TX_EMP_INT BIT(6) > +#define CTRL_TX_RDY_INT BIT(5) > +#define CTRL_RX_RDY_INT BIT(4) > +#define CTRL_BRK_DET_INT BIT(3) > +#define CTRL_FRM_ERR_INT BIT(2) > +#define CTRL_PAR_ERR_INT BIT(1) > +#define CTRL_OVR_ERR_INT BIT(0) > +#define CTRL_RX_INT (CTRL_RX_RDY_INT | CTRL_BRK_DET_INT |\ > + CTRL_FRM_ERR_INT | CTRL_PAR_ERR_INT | CTRL_OVR_ERR_INT) > + > +#define UART_STAT 0x0c > +#define STAT_TX_FIFO_EMP BIT(13) > +#define STAT_RX_FIFO_EMP BIT(12) > +#define STAT_TX_FIFO_FUL BIT(11) > +#define STAT_TX_FIFO_HFL BIT(10) > +#define STAT_RX_TOGL BIT(9) > +#define STAT_RX_FIFO_FUL BIT(8) > +#define STAT_RX_FIFO_HFL BIT(7) > +#define STAT_TX_EMP BIT(6) > +#define STAT_TX_RDY BIT(5) > +#define STAT_RX_RDY BIT(4) > +#define STAT_BRK_DET BIT(3) > +#define STAT_FRM_ERR BIT(2) > +#define STAT_PAR_ERR BIT(1) > +#define STAT_OVR_ERR BIT(0) > +#define STAT_BRK_ERR (STAT_BRK_DET | STAT_FRM_ERR | STAT_FRM_ERR\ > + | STAT_PAR_ERR | STAT_OVR_ERR) > + > +#define UART_BRDV 0x10 > + > +#define MVEBU_NR_UARTS 1 > + > +#define MVEBU_UART_TYPE "mvebu-uart" > + > +static struct uart_port mvebu_uart_ports[MVEBU_NR_UARTS]; > + > +struct mvebu_uart_data { > + struct uart_port *port; > + struct clk *clk; > +}; > + > +/* Core UART Driver Operations */ > +static unsigned int mvebu_uart_tx_empty(struct uart_port *port) > +{ > + unsigned long flags; > + unsigned int st; > + > + spin_lock_irqsave(&port->lock, flags); > + st = readl(port->membase + UART_STAT); > + spin_unlock_irqrestore(&port->lock, flags); > + > + return (st & STAT_TX_FIFO_EMP) ? TIOCSER_TEMT : 0; > +} > + > +static unsigned int mvebu_uart_get_mctrl(struct uart_port *port) > +{ > + return TIOCM_CTS | TIOCM_DSR | TIOCM_CAR; > +} > + > +static void mvebu_uart_set_mctrl(struct uart_port *port, > + unsigned int mctrl) > +{ > +/* > + * Even if we do not support configuring the modem control lines, this > + * function must be proided to the serial core > + */ > +} > + > +static void mvebu_uart_stop_tx(struct uart_port *port) > +{ > + unsigned int ctl = readl(port->membase + UART_CTRL); > + > + ctl &= ~CTRL_TX_RDY_INT; > + writel(ctl, port->membase + UART_CTRL); > +} > + > +static void mvebu_uart_start_tx(struct uart_port *port) > +{ > + unsigned int ctl = readl(port->membase + UART_CTRL); > + > + ctl |= CTRL_TX_RDY_INT; > + writel(ctl, port->membase + UART_CTRL); > +} > + > +static void mvebu_uart_stop_rx(struct uart_port *port) > +{ > + unsigned int ctl = readl(port->membase + UART_CTRL); > + > + ctl &= ~CTRL_RX_INT; > + writel(ctl, port->membase + UART_CTRL); > +} > + > +static void mvebu_uart_break_ctl(struct uart_port *port, int brk) > +{ > + unsigned int ctl; > + unsigned long flags; > + > + spin_lock_irqsave(&port->lock, flags); > + ctl = readl(port->membase + UART_CTRL); > + if (brk == -1) > + ctl |= CTRL_SND_BRK_SEQ; > + else > + ctl &= ~CTRL_SND_BRK_SEQ; > + writel(ctl, port->membase + UART_CTRL); > + spin_unlock_irqrestore(&port->lock, flags); > +} > + > +static void mvebu_uart_rx_chars(struct uart_port *port, unsigned int status) > +{ > + struct tty_port *tport = &port->state->port; > + unsigned char ch = 0; > + char flag = 0; > + > + do { > + if (status & STAT_RX_RDY) { > + ch = readl(port->membase + UART_RBR); > + ch &= 0xff; > + flag = TTY_NORMAL; > + port->icount.rx++; > + > + if (status & STAT_PAR_ERR) > + port->icount.parity++; > + } > + > + if (status & STAT_BRK_DET) { > + port->icount.brk++; > + status &= ~(STAT_FRM_ERR | STAT_PAR_ERR); > + if (uart_handle_break(port)) > + goto ignore_char; > + } > + > + if (status & STAT_OVR_ERR) > + port->icount.overrun++; > + > + if (status & STAT_FRM_ERR) > + port->icount.frame++; > + > + if (uart_handle_sysrq_char(port, ch)) > + goto ignore_char; > + > + if (status & port->ignore_status_mask & STAT_PAR_ERR) > + status &= ~STAT_RX_RDY; > + > + status &= port->read_status_mask; > + > + if (status & STAT_PAR_ERR) > + flag = TTY_PARITY; > + > + status &= ~port->ignore_status_mask; > + > + if (status & STAT_RX_RDY) > + tty_insert_flip_char(tport, ch, flag); > + > + if (status & STAT_BRK_DET) > + tty_insert_flip_char(tport, 0, TTY_BREAK); > + > + if (status & STAT_FRM_ERR) > + tty_insert_flip_char(tport, 0, TTY_FRAME); > + > + if (status & STAT_OVR_ERR) > + tty_insert_flip_char(tport, 0, TTY_OVERRUN); > + > +ignore_char: > + status = readl(port->membase + UART_STAT); > + } while (status & (STAT_RX_RDY | STAT_BRK_DET)); > + > + tty_flip_buffer_push(tport); > +} > + > +static void mvebu_uart_tx_chars(struct uart_port *port, unsigned int status) > +{ > + struct circ_buf *xmit = &port->state->xmit; > + unsigned int count; > + unsigned int st; > + > + if (port->x_char) { > + writel(port->x_char, port->membase + UART_TSH); > + port->icount.tx++; > + port->x_char = 0; > + return; > + } > + > + if (uart_circ_empty(xmit) || uart_tx_stopped(port)) { > + mvebu_uart_stop_tx(port); > + return; > + } > + > + for (count = 0; count < port->fifosize; count++) { > + writel(xmit->buf[xmit->tail], port->membase + UART_TSH); > + xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1); > + port->icount.tx++; > + > + if (uart_circ_empty(xmit)) > + break; > + > + st = readl(port->membase + UART_STAT); > + if (st & STAT_TX_FIFO_FUL) > + break; > + } > + > + if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS) > + uart_write_wakeup(port); > + > + if (uart_circ_empty(xmit)) > + mvebu_uart_stop_tx(port); > +} > + > +static irqreturn_t mvebu_uart_isr(int irq, void *dev_id) > +{ > + struct uart_port *port = (struct uart_port *)dev_id; > + unsigned int st = readl(port->membase + UART_STAT); > + > + if (st & (STAT_RX_RDY | STAT_OVR_ERR | STAT_FRM_ERR | STAT_BRK_DET)) > + mvebu_uart_rx_chars(port, st); > + > + if (st & STAT_TX_RDY) > + mvebu_uart_tx_chars(port, st); > + > + return IRQ_HANDLED; > +} > + > +static int mvebu_uart_startup(struct uart_port *port) > +{ > + int ret; > + > + writel(CTRL_TXFIFO_RST | CTRL_RXFIFO_RST, > + port->membase + UART_CTRL); > + udelay(1); > + writel(CTRL_RX_INT, port->membase + UART_CTRL); > + > + ret = request_irq(port->irq, mvebu_uart_isr, port->irqflags, "serial", > + port); > + if (ret) { > + dev_err(port->dev, "failed to request irq\n"); > + return ret; > + } > + > + return 0; > +} > + > +static void mvebu_uart_shutdown(struct uart_port *port) > +{ > + writel(0, port->membase + UART_CTRL); > +} > + > +static void mvebu_uart_set_termios(struct uart_port *port, > + struct ktermios *termios, > + struct ktermios *old) > +{ > + unsigned long flags; > + unsigned int baud; > + > + spin_lock_irqsave(&port->lock, flags); > + > + port->read_status_mask = STAT_RX_RDY | STAT_OVR_ERR | > + STAT_TX_RDY | STAT_TX_FIFO_FUL; > + > + if (termios->c_iflag & INPCK) > + port->read_status_mask |= STAT_FRM_ERR | STAT_PAR_ERR; > + > + port->ignore_status_mask = 0; > + if (termios->c_iflag & IGNPAR) > + port->ignore_status_mask |= > + STAT_FRM_ERR | STAT_PAR_ERR | STAT_OVR_ERR; > + > + if ((termios->c_cflag & CREAD) == 0) > + port->ignore_status_mask |= STAT_RX_RDY | STAT_BRK_ERR; > + > + baud = uart_get_baud_rate(port, termios, old, 0, 460800); > + uart_update_timeout(port, termios->c_cflag, baud); > + > + spin_unlock_irqrestore(&port->lock, flags); > +} > + > +static const char *mvebu_uart_type(struct uart_port *port) > +{ > + return MVEBU_UART_TYPE; > +} > + > +static void mvebu_uart_release_port(struct uart_port *port) > +{ > + /* Nothing to do here */ > +} > + > +static int mvebu_uart_request_port(struct uart_port *port) > +{ > + return 0; > +} > + > +#ifdef CONFIG_CONSOLE_POLL > +static int mvebu_uart_get_poll_char(struct uart_port *port) > +{ > + unsigned int st = readl(port->membase + UART_STAT); > + > + if (!(st & STAT_RX_RDY)) > + return NO_POLL_CHAR; > + > + return readl(port->membase + UART_RBR); > +} > + > +static void mvebu_uart_put_poll_char(struct uart_port *port, unsigned char c) > +{ > + unsigned int st; > + > + for (;;) { > + st = readl(port->membase + UART_STAT); > + > + if (!(st & STAT_TX_FIFO_FUL)) > + break; > + > + udelay(1); > + } > + > + writel(c, port->membase + UART_TSH); > +} > +#endif > + > +static const struct uart_ops mvebu_uart_ops = { > + .tx_empty = mvebu_uart_tx_empty, > + .set_mctrl = mvebu_uart_set_mctrl, > + .get_mctrl = mvebu_uart_get_mctrl, > + .stop_tx = mvebu_uart_stop_tx, > + .start_tx = mvebu_uart_start_tx, > + .stop_rx = mvebu_uart_stop_rx, > + .break_ctl = mvebu_uart_break_ctl, > + .startup = mvebu_uart_startup, > + .shutdown = mvebu_uart_shutdown, > + .set_termios = mvebu_uart_set_termios, > + .type = mvebu_uart_type, > + .release_port = mvebu_uart_release_port, > + .request_port = mvebu_uart_request_port, > +#ifdef CONFIG_CONSOLE_POLL > + .poll_get_char = mvebu_uart_get_poll_char, > + .poll_put_char = mvebu_uart_put_poll_char, > +#endif > +}; > + > +/* Console Driver Operations */ > + > +#ifdef CONFIG_SERIAL_MVEBU_CONSOLE > +/* Early Console */ > +static void mvebu_uart_putc(struct uart_port *port, int c) > +{ > + unsigned int st; > + > + for (;;) { > + st = readl(port->membase + UART_STAT); > + if (!(st & STAT_TX_FIFO_FUL)) > + break; > + } > + > + writel(c, port->membase + UART_TSH); > + > + for (;;) { > + st = readl(port->membase + UART_STAT); > + if (st & STAT_TX_FIFO_EMP) > + break; > + } > +} > + > +static void mvebu_uart_putc_early_write(struct console *con, > + const char *s, > + unsigned n) > +{ > + struct earlycon_device *dev = con->data; > + > + uart_console_write(&dev->port, s, n, mvebu_uart_putc); > +} > + > +static int __init > +mvebu_uart_early_console_setup(struct earlycon_device *device, > + const char *opt) > +{ > + if (!device->port.membase) > + return -ENODEV; > + > + device->con->write = mvebu_uart_putc_early_write; > + > + return 0; > +} > + > +EARLYCON_DECLARE(ar3700_uart, mvebu_uart_early_console_setup); > +OF_EARLYCON_DECLARE(ar3700_uart, "marvell,armada-3700-uart", > + mvebu_uart_early_console_setup); > + > +static void wait_for_xmitr(struct uart_port *port) > +{ > + u32 val; > + > + readl_poll_timeout_atomic(port->membase + UART_STAT, val, > + (val & STAT_TX_EMP), 1, 10000); > +} > + > +static void mvebu_uart_console_putchar(struct uart_port *port, int ch) > +{ > + wait_for_xmitr(port); > + writel(ch, port->membase + UART_TSH); > +} > + > +static void mvebu_uart_console_write(struct console *co, const char *s, > + unsigned int count) > +{ > + struct uart_port *port = &mvebu_uart_ports[co->index]; > + unsigned long flags; > + unsigned int ier; > + int locked = 1; > + > + if (oops_in_progress) > + locked = spin_trylock_irqsave(&port->lock, flags); > + else > + spin_lock_irqsave(&port->lock, flags); > + > + ier = readl(port->membase + UART_CTRL) & > + (CTRL_RX_INT | CTRL_TX_RDY_INT); > + writel(0, port->membase + UART_CTRL); > + > + uart_console_write(port, s, count, mvebu_uart_console_putchar); > + > + wait_for_xmitr(port); > + > + if (ier) > + writel(ier, port->membase + UART_CTRL); > + > + if (locked) > + spin_unlock_irqrestore(&port->lock, flags); > +} > + > +static int mvebu_uart_console_setup(struct console *co, char *options) > +{ > + struct uart_port *port; > + int baud = 9600; > + int bits = 8; > + int parity = 'n'; > + int flow = 'n'; > + > + if (co->index < 0 || co->index >= MVEBU_NR_UARTS) > + return -EINVAL; > + > + port = &mvebu_uart_ports[co->index]; > + > + if (!port->mapbase || !port->membase) { > + pr_debug("console on ttyS%i not present\n", co->index); > + return -ENODEV; > + } > + > + if (options) > + uart_parse_options(options, &baud, &parity, &bits, &flow); > + > + return uart_set_options(port, co, baud, parity, bits, flow); > +} > + > +static struct uart_driver mvebu_uart_driver; > + > +static struct console mvebu_uart_console = { > + .name = "ttyS", > + .write = mvebu_uart_console_write, > + .device = uart_console_device, > + .setup = mvebu_uart_console_setup, > + .flags = CON_PRINTBUFFER, > + .index = -1, > + .data = &mvebu_uart_driver, > +}; > + > +static int __init mvebu_uart_console_init(void) > +{ > + register_console(&mvebu_uart_console); > + return 0; > +} > + > +console_initcall(mvebu_uart_console_init); > + > + > +#endif /* CONFIG_SERIAL_MVEBU_CONSOLE */ > + > +static struct uart_driver mvebu_uart_driver = { > + .owner = THIS_MODULE, > + .driver_name = "serial", > + .dev_name = "ttyS", > + .major = TTY_MAJOR, > + .minor = 64, > + .nr = MVEBU_NR_UARTS, > +#ifdef CONFIG_SERIAL_MVEBU_CONSOLE > + .cons = &mvebu_uart_console, > +#endif > +}; > + > +static int mvebu_uart_probe(struct platform_device *pdev) > +{ > + struct resource *reg = platform_get_resource(pdev, IORESOURCE_MEM, 0); > + struct resource *irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0); > + struct uart_port *port; > + struct mvebu_uart_data *data; > + int ret; > + > + if (!reg || !irq) { > + dev_err(&pdev->dev, "no registers/irq defined\n"); > + return -EINVAL; > + } > + > + port = &mvebu_uart_ports[0]; > + > + spin_lock_init(&port->lock); > + > + port->dev = &pdev->dev; > + port->type = PORT_MVEBU; > + port->ops = &mvebu_uart_ops; > + port->regshift = 0; > + > + port->fifosize = 32; > + port->iotype = UPIO_MEM32; > + port->flags = UPF_FIXED_PORT; > + port->line = 0; /* single port: force line number to 0 */ > + > + port->irq = irq->start; > + port->irqflags = 0; > + port->mapbase = reg->start; > + > + port->membase = devm_ioremap_resource(&pdev->dev, reg); > + if (IS_ERR(port->membase)) > + return -PTR_ERR(port->membase); > + > + data = devm_kzalloc(&pdev->dev, sizeof(struct mvebu_uart_data), > + GFP_KERNEL); > + if (!data) > + return -ENOMEM; > + > + data->port = port; > + > + port->private_data = data; > + platform_set_drvdata(pdev, data); > + > + ret = uart_add_one_port(&mvebu_uart_driver, port); > + if (ret) > + return ret; > + return 0; > +} > + > +static int mvebu_uart_remove(struct platform_device *pdev) > +{ > + struct mvebu_uart_data *data = platform_get_drvdata(pdev); > + > + uart_remove_one_port(&mvebu_uart_driver, data->port); > + data->port->private_data = NULL; > + data->port->mapbase = 0; > + return 0; > +} > + > +/* Match table for of_platform binding */ > +static const struct of_device_id mvebu_uart_of_match[] = { > + { .compatible = "marvell,armada-3700-uart", }, > + {} > +}; > +MODULE_DEVICE_TABLE(of, mvebu_uart_of_match); > + > +static struct platform_driver mvebu_uart_platform_driver = { > + .probe = mvebu_uart_probe, > + .remove = mvebu_uart_remove, > + .driver = { > + .owner = THIS_MODULE, > + .name = "mvebu-uart", > + .of_match_table = of_match_ptr(mvebu_uart_of_match), > + }, > +}; > + > +static int __init mvebu_uart_init(void) > +{ > + int ret; > + > + ret = uart_register_driver(&mvebu_uart_driver); > + if (ret) > + return ret; > + > + ret = platform_driver_register(&mvebu_uart_platform_driver); > + if (ret) > + uart_unregister_driver(&mvebu_uart_driver); > + > + return ret; > +} > + > +static void __exit mvebu_uart_exit(void) > +{ > + platform_driver_unregister(&mvebu_uart_platform_driver); > + uart_unregister_driver(&mvebu_uart_driver); > +} > + > +arch_initcall(mvebu_uart_init); > +module_exit(mvebu_uart_exit); > + > +MODULE_AUTHOR("Wilson Ding "); > +MODULE_DESCRIPTION("Marvell Armada-3700 Serial Driver"); > +MODULE_LICENSE("GPL"); > diff --git a/include/uapi/linux/serial_core.h b/include/uapi/linux/serial_core.h > index 3e5d757407fb..e513a4ee369b 100644 > --- a/include/uapi/linux/serial_core.h > +++ b/include/uapi/linux/serial_core.h > @@ -261,4 +261,7 @@ > /* STM32 USART */ > #define PORT_STM32 113 > > +/* MVEBU UART */ > +#define PORT_MVEBU 114 > + > #endif /* _UAPILINUX_SERIAL_CORE_H */ > -- > 2.5.0 >