[RFC PATCH 1/1] drivers: introduce ARM SBSA generic UART driver

Peter Hurley peter at hurleysoftware.com
Tue Sep 2 11:19:55 PDT 2014


Hi Andre,

On 08/29/2014 12:13 PM, Andre Przywara wrote:
> The ARM Server Base System Architecture (SBSA) describes a generic
> UART which all compliant level 1 systems should implement. This is
> actually a PL011 subset, so a full PL011 implementation will satisfy
> this requirement.
> However if a system does not have a PL011, a very stripped down
> implementation complying to the SBSA defined specification will
> suffice. The Linux PL011 driver is not guaranteed to drive this
> limited device (and indeed the fast model implentation hangs the
> kernel if driven by the PL011 driver).
> So introduce a new driver just implementing the part specified by the
> SBSA (which lacks DMA, the modem control signals and many of the
> registers including baud rate control). This driver has been derived
> by the actual PL011 one, removing all unnecessary code.
> 
> Signed-off-by: Andre Przywara <andre.przywara at arm.com>
> ---
>  .../devicetree/bindings/serial/arm_sbsa_uart.txt   |    6 +
>  drivers/tty/serial/Kconfig                         |   28 +
>  drivers/tty/serial/Makefile                        |    1 +
>  drivers/tty/serial/sbsa_uart.c                     |  793 ++++++++++++++++++++
>  include/uapi/linux/serial_core.h                   |    1 +
>  5 files changed, 829 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/serial/arm_sbsa_uart.txt
>  create mode 100644 drivers/tty/serial/sbsa_uart.c
> 
> diff --git a/Documentation/devicetree/bindings/serial/arm_sbsa_uart.txt b/Documentation/devicetree/bindings/serial/arm_sbsa_uart.txt
> new file mode 100644
> index 0000000..8e2c5d6
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/serial/arm_sbsa_uart.txt
> @@ -0,0 +1,6 @@
> +* ARM SBSA defined generic UART
> +
> +Required properties:
> +- compatible: must be "arm,sbsa-uart"
> +- reg: exactly one register range
> +- interrupts: exactly one interrupt specifier
> diff --git a/drivers/tty/serial/Kconfig b/drivers/tty/serial/Kconfig
> index 26cec64..faecfad 100644
> --- a/drivers/tty/serial/Kconfig
> +++ b/drivers/tty/serial/Kconfig
> @@ -45,6 +45,34 @@ config SERIAL_AMBA_PL010_CONSOLE
>  	  your boot loader (lilo or loadlin) about how to pass options to the
>  	  kernel at boot time.)
>  
> +config SERIAL_SBSA_UART
> +	tristate "ARM SBSA UART serial port support"
> +	select SERIAL_CORE
> +	help
> +	  This selects the UART defined in the ARM(R) Server Base System
> +	  Architecture (SBSA).
> +	  It is a true subset of the ARM(R) PL011 UART and this driver can
> +	  also drive those full featured UARTs.
> +	  To match the SBSA, this driver will not support initialization, DMA,
> +	  baudrate control and modem control lines.
> +
> +config SERIAL_SBSA_UART_CONSOLE
> +	bool "Support for console on ARM SBSA UART serial port"
> +	depends on SERIAL_SBSA_UART=y
> +	select SERIAL_CORE_CONSOLE
> +	select SERIAL_EARLYCON
> +	---help---
> +	  Say Y here if you wish to use an ARM SBSA 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).
> +
> +	  Even if you say Y here, the currently visible framebuffer console
> +	  (/dev/tty0) will still be used as the system console by default, but
> +	  you can alter that using a kernel command line option such as
> +	  "console=ttyAMA0". (Try "man bootparam" or see the documentation of
> +	  your boot loader (lilo or loadlin) about how to pass options to the
> +	  kernel at boot time.)
> +
>  config SERIAL_AMBA_PL011
>  	tristate "ARM AMBA PL011 serial port support"
>  	depends on ARM_AMBA
> diff --git a/drivers/tty/serial/Makefile b/drivers/tty/serial/Makefile
> index 0080cc3..2e560e9 100644
> --- a/drivers/tty/serial/Makefile
> +++ b/drivers/tty/serial/Makefile
> @@ -22,6 +22,7 @@ obj-$(CONFIG_SERIAL_8250) += 8250/
>  
>  obj-$(CONFIG_SERIAL_AMBA_PL010) += amba-pl010.o
>  obj-$(CONFIG_SERIAL_AMBA_PL011) += amba-pl011.o
> +obj-$(CONFIG_SERIAL_SBSA_UART) += sbsa_uart.o
>  obj-$(CONFIG_SERIAL_CLPS711X) += clps711x.o
>  obj-$(CONFIG_SERIAL_PXA) += pxa.o
>  obj-$(CONFIG_SERIAL_PNX8XXX) += pnx8xxx_uart.o
> diff --git a/drivers/tty/serial/sbsa_uart.c b/drivers/tty/serial/sbsa_uart.c
> new file mode 100644
> index 0000000..941a305
> --- /dev/null
> +++ b/drivers/tty/serial/sbsa_uart.c
> @@ -0,0 +1,793 @@
> +/*
> + *  Driver for ARM SBSA specified serial ports (stripped down PL011)
> + *
> + *  Based on drivers/tty/char/amba-pl011.c
> + *
> + *  Copyright 2014 ARM Limited
> + *
> + * 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.
> + *
> + * You should have received a copy of the GNU General Public License
> + * along with this program.  If not, see <http://www.gnu.org/licenses/>.
> + *
> + * This is a driver implementing the ARM SBSA specified generic UART,
> + * which is a subset of the PL011 UART, but lacking:
> + *  - DMA
> + *  - hardware flow control
> + *  - modem control
> + *  - IrDA SIR
> + *  - word lengths other than 8 bits
> + *  - baudrate settings
> + */
> +
> +
> +#if defined(CONFIG_SERIAL_SBSA_UART_CONSOLE) && defined(CONFIG_MAGIC_SYSRQ)
> +#define SUPPORT_SYSRQ
> +#endif
> +
> +#include <linux/module.h>
> +#include <linux/init.h>
> +#include <linux/console.h>
> +#include <linux/sysrq.h>
> +#include <linux/device.h>
> +#include <linux/tty.h>
> +#include <linux/tty_flip.h>
> +#include <linux/serial_core.h>
> +#include <linux/serial.h>
> +#include <linux/amba/serial.h>
> +#include <linux/slab.h>
> +#include <linux/types.h>
> +#include <linux/of.h>
> +#include <linux/of_device.h>
> +#include <linux/sizes.h>
> +#include <linux/io.h>
> +
> +#define UART_NR			14
> +
> +#define UART_DR_ERROR		(UART011_DR_OE|UART011_DR_BE|UART011_DR_PE|UART011_DR_FE)
> +#define UART_DUMMY_DR_RX	(1 << 16)
> +
> +/*
> + * Reads up to 256 characters from the FIFO or until it's empty and
> + * inserts them into the TTY layer. Returns the number of characters
> + * read from the FIFO.
> + */
> +static int sbsa_uart_fifo_to_tty(struct uart_port *port)
> +{
> +	u16 status, ch;
> +	unsigned int flag, max_count = 256;
> +	int fifotaken = 0;
> +
> +	while (max_count--) {
> +		status = readw(port->membase + UART01x_FR);
> +		if (status & UART01x_FR_RXFE)
> +			break;
> +
> +		/* Take chars from the FIFO and update status */
> +		ch = readw(port->membase + UART01x_DR) | UART_DUMMY_DR_RX;
> +		flag = TTY_NORMAL;
> +		port->icount.rx++;
> +		fifotaken++;
> +
> +		if (unlikely(ch & UART_DR_ERROR)) {
> +			if (ch & UART011_DR_BE) {
> +				ch &= ~(UART011_DR_FE | UART011_DR_PE);
> +				port->icount.brk++;
> +				if (uart_handle_break(port))
> +					continue;
> +			} else if (ch & UART011_DR_PE)
> +				port->icount.parity++;
> +			else if (ch & UART011_DR_FE)
> +				port->icount.frame++;
> +			if (ch & UART011_DR_OE)
> +				port->icount.overrun++;
> +
> +			ch &= port->read_status_mask;
> +
> +			if (ch & UART011_DR_BE)
> +				flag = TTY_BREAK;
> +			else if (ch & UART011_DR_PE)
> +				flag = TTY_PARITY;
> +			else if (ch & UART011_DR_FE)
> +				flag = TTY_FRAME;
> +		}
> +
> +		if (uart_handle_sysrq_char(port, ch & 255))
> +			continue;
> +
> +		uart_insert_char(port, ch, UART011_DR_OE, ch, flag);
> +	}
> +
> +	return fifotaken;
> +}
> +
> +static void sbsa_uart_mask_irq(struct uart_port *port, u16 irqclr, u16 irqset)
> +{
> +	u16 imsc;
> +
> +	imsc = readw(port->membase + UART011_IMSC);
> +	imsc = (imsc & ~irqclr) | irqset;
> +	writew(imsc, port->membase + UART011_IMSC);
> +}
> +
> +static void sbsa_uart_stop_tx(struct uart_port *port)
> +{
> +	sbsa_uart_mask_irq(port, UART011_TXIM, 0);
> +}
> +
> +static void sbsa_uart_start_tx(struct uart_port *port)
> +{
> +	sbsa_uart_mask_irq(port, 0, UART011_TXIM);
> +}
> +
> +static void sbsa_uart_stop_rx(struct uart_port *port)
> +{
> +	sbsa_uart_mask_irq(port, UART011_RXIM | UART011_RTIM, 0);
> +}
> +
> +static void sbsa_uart_rx_chars(struct uart_port *port)
> +__releases(&uap->port.lock)
> +__acquires(&uap->port.lock)
              ^^^^^^^^^^
I think sparse will choke here.


> +{
> +	sbsa_uart_fifo_to_tty(port);
> +
> +	spin_unlock(&port->lock);
> +	tty_flip_buffer_push(&port->state->port);
> +	spin_lock(&port->lock);
> +}
> +
> +static void sbsa_uart_tx_chars(struct uart_port *port)
> +{
> +	struct circ_buf *xmit = &port->state->xmit;
> +	int count;
> +
> +	if (port->x_char) {
> +		writew(port->x_char, port->membase + UART01x_DR);
> +		port->icount.tx++;
> +		port->x_char = 0;
> +		return;
> +	}
> +	if (uart_circ_empty(xmit) || uart_tx_stopped(port)) {
> +		sbsa_uart_stop_tx(port);
> +		return;
> +	}
> +
> +	count = port->fifosize >> 1;
> +	do {
> +		writew(xmit->buf[xmit->tail], port->membase + UART01x_DR);
> +		xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1);
> +		port->icount.tx++;
> +		if (uart_circ_empty(xmit))
> +			break;
> +	} while (--count > 0);
> +
> +	if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)
> +		uart_write_wakeup(port);
> +
> +	if (uart_circ_empty(xmit))
> +		sbsa_uart_stop_tx(port);
> +}
> +
> +static irqreturn_t sbsa_uart_int(int irq, void *dev_id)
> +{
> +	struct uart_port *port = dev_id;
> +	unsigned long flags;
> +	unsigned int status, pass_counter = 32;
> +	int handled = 0;
> +
> +	spin_lock_irqsave(&port->lock, flags);
> +	status = readw(port->membase + UART011_RIS);
> +	status &= readw(port->membase + UART011_IMSC);
> +	if (status) {
> +		do {
> +			writew(status & ~(UART011_TXIS|UART011_RTIS|
> +					  UART011_RXIS),
> +			       port->membase + UART011_ICR);
> +
> +			if (status & (UART011_RTIS|UART011_RXIS))
> +				sbsa_uart_rx_chars(port);
> +
> +			if (status & UART011_TXIS)
> +				sbsa_uart_tx_chars(port);
> +
> +			if (pass_counter-- == 0)
> +				break;
> +
> +			status = readw(port->membase + UART011_RIS);
> +			status &= ~readw(port->membase + UART011_IMSC);
> +		} while (status != 0);
> +		handled = 1;
> +	}
> +
> +	spin_unlock_irqrestore(&port->lock, flags);
> +
> +	return IRQ_RETVAL(handled);
> +}
> +
> +static unsigned int sbsa_uart_tx_empty(struct uart_port *port)
> +{
> +	unsigned int status = readw(port->membase + UART01x_FR);
> +
> +	return status & (UART01x_FR_BUSY|UART01x_FR_TXFF) ? 0 : TIOCSER_TEMT;
> +}
> +
> +static unsigned int sbsa_uart_get_mctrl(struct uart_port *port)
> +{
> +	return 0;
> +}
> +
> +static void sbsa_uart_set_mctrl(struct uart_port *port, unsigned int mctrl)
> +{
> +}
> +
> +#ifdef CONFIG_CONSOLE_POLL
> +
> +static void sbsa_uart_quiesce_irqs(struct uart_port *port)
> +{
> +	writew(readw(port->membase + UART011_RIS), port->membase + UART011_ICR);
> +	/*
> +	 * There is no way to clear TXIM as this is "ready to transmit IRQ", so
> +	 * we simply mask it. start_tx() will unmask it.
> +	 *
> +	 * Note we can race with start_tx(), and if the race happens, the
> +	 * polling user might get another interrupt just after we clear it.
> +	 * But it should be OK and can happen even w/o the race, e.g.
> +	 * controller immediately got some new data and raised the IRQ.
> +	 *
> +	 * And whoever uses polling routines assumes that it manages the device
> +	 * (including tx queue), so we're also fine with start_tx()'s caller
> +	 * side.
> +	 */
> +	writew(readw(port->membase + UART011_IMSC) & ~UART011_TXIM,
> +	       port->membase + UART011_IMSC);
> +}
> +
> +static int sbsa_uart_get_poll_char(struct uart_port *port)
> +{
> +	unsigned int status;
> +
> +	/*
> +	 * The caller might need IRQs lowered, e.g. if used with KDB NMI
> +	 * debugger.
> +	 */
> +	sbsa_uart_quiesce_irqs(port);
> +
> +	status = readw(port->membase + UART01x_FR);
> +	if (status & UART01x_FR_RXFE)
> +		return NO_POLL_CHAR;
> +
> +	return readw(port->membase + UART01x_DR);
> +}
> +
> +static void sbsa_uart_put_poll_char(struct uart_port *port, unsigned char ch)
> +{
> +	while (readw(port->membase + UART01x_FR) & UART01x_FR_TXFF)
> +		barrier();
> +
> +	writew(ch, port->membase + UART01x_DR);
> +}
> +
> +#endif /* CONFIG_CONSOLE_POLL */
> +
> +static int sbsa_uart_hwinit(struct uart_port *port)
> +{
> +	/* Clear pending error and receive interrupts */
> +	writew(UART011_OEIS | UART011_BEIS | UART011_PEIS | UART011_FEIS |
> +	       UART011_RTIS | UART011_RXIS, port->membase + UART011_ICR);
> +
> +	writew(UART011_RTIM | UART011_RXIM, port->membase + UART011_IMSC);
> +
> +	return 0;
> +}
> +
> +static int sbsa_uart_startup(struct uart_port *port)
> +{
> +	int retval;
> +
> +	retval = sbsa_uart_hwinit(port);
> +	if (retval)
> +		return retval;
> +
> +	/*
> +	 * Allocate the IRQ
> +	 */
> +	retval = request_irq(port->irq, sbsa_uart_int, 0, "uart-sbsa", port);
> +	if (retval)
> +		return retval;
> +
> +	/*
> +	 * Provoke TX FIFO interrupt into asserting. Taking care to preserve
> +	 * baud rate and data format specified by FBRD, IBRD and LCRH as the
> +	 * UART may already be in use as a console.
> +	 */
> +	spin_lock_irq(&port->lock);
> +
> +	/*
> +	 * this does not work on the SBSA UART, because it does not have
> +	 * a CR and thus not loopback enable bit
> +	 */
> +	writew(0, port->membase + UART01x_DR);
> +	while (readw(port->membase + UART01x_FR) & UART01x_FR_BUSY)
> +		barrier();
> +
> +	/* Clear out any spuriously appearing RX interrupts and enable them */
> +	writew(UART011_RTIS | UART011_RXIS, port->membase + UART011_ICR);
> +	writew(UART011_RTIM | UART011_RXIM, port->membase + UART011_IMSC);
> +	spin_unlock_irq(&port->lock);
> +
> +	return 0;
> +}
> +
> +static void sbsa_uart_shutdown(struct uart_port *port)
> +{
> +	u16 im;
> +
> +	/*
> +	 * disable all interrupts
> +	 */
> +	spin_lock_irq(&port->lock);
> +	im = 0;
> +	writew(im, port->membase + UART011_IMSC);
> +	writew(0xffff, port->membase + UART011_ICR);
> +	spin_unlock_irq(&port->lock);
> +
> +	/*
> +	 * Free the interrupt
> +	 */
> +	free_irq(port->irq, port);
> +
> +}
> +
> +static void
> +sbsa_uart_set_termios(struct uart_port *port, struct ktermios *termios,
> +		  struct ktermios *old)
> +{
> +	unsigned long flags;
> +	unsigned int baud = 115200;
> +
> +	spin_lock_irqsave(&port->lock, flags);
        ^^^^^^^^^^^^^^^^^

Can be spin_lock_irq(&port->lock) here.

> +
> +	/*
> +	 * Update the per-port timeout.
> +	 */
> +	uart_update_timeout(port, termios->c_cflag, baud);
> +
> +	port->read_status_mask = UART011_DR_OE | 255;
> +	if (termios->c_iflag & INPCK)
> +		port->read_status_mask |= UART011_DR_FE | UART011_DR_PE;
> +	if (termios->c_iflag & (BRKINT | PARMRK))
                                ^^^^^^^
                                (IGNBRK | BRKINT | PARMRK)

> +		port->read_status_mask |= UART011_DR_BE;

Otherwise, if IGNBRK is set, read_status_mask will mask the BRK condition
so a garbage TTY_NORMAL byte will be added by uart_insert_char(). The
pl011 driver has been fixed in -next.

> +
> +	/*
> +	 * Characters to ignore
> +	 */
> +	port->ignore_status_mask = 0;
> +	if (termios->c_iflag & IGNPAR)
> +		port->ignore_status_mask |= UART011_DR_FE | UART011_DR_PE;
> +	if (termios->c_iflag & IGNBRK) {
> +		port->ignore_status_mask |= UART011_DR_BE;
> +		/*
> +		 * If we're ignoring parity and break indicators,
> +		 * ignore overruns too (for real raw support).
> +		 */
> +		if (termios->c_iflag & IGNPAR)
> +			port->ignore_status_mask |= UART011_DR_OE;
> +	}
> +
> +	/*
> +	 * Ignore all characters if CREAD is not set.
> +	 */
> +	if ((termios->c_cflag & CREAD) == 0)
> +		port->ignore_status_mask |= UART_DUMMY_DR_RX;
> +
> +	spin_unlock_irqrestore(&port->lock, flags);
> +}
> +
> +static const char *sbsa_uart_type(struct uart_port *port)
> +{
> +	return port->type == PORT_SBSA ? "SBSA" : NULL;
> +}
> +
> +/*
> + * Release the memory region(s) being used by 'port'
> + */
> +static void sbsa_uart_release_port(struct uart_port *port)
> +{
> +	release_mem_region(port->mapbase, SZ_4K);
> +}
> +
> +/*
> + * Request the memory region(s) being used by 'port'
> + */
> +static int sbsa_uart_request_port(struct uart_port *port)
> +{
> +	return request_mem_region(port->mapbase, SZ_4K, "uart-sbsa")
> +			!= NULL ? 0 : -EBUSY;
> +}
> +
> +/*
> + * Configure/autoconfigure the port.
> + */
> +static void sbsa_uart_config_port(struct uart_port *port, int flags)
> +{
> +	if (flags & UART_CONFIG_TYPE) {
> +		port->type = PORT_SBSA;
> +		sbsa_uart_request_port(port);
> +	}
> +}
> +
> +/*
> + * verify the new serial_struct (for TIOCSSERIAL).
> + */
> +static int sbsa_uart_verify_port(struct uart_port *port,
> +				 struct serial_struct *ser)
> +{
> +	int ret = 0;
> +
> +	if (ser->type != PORT_UNKNOWN && ser->type != PORT_SBSA)
> +		ret = -EINVAL;
> +	if (ser->irq < 0 || ser->irq >= nr_irqs)
> +		ret = -EINVAL;
> +	return ret;
> +}
> +
> +static struct uart_ops sbsa_uart_pops = {
> +	.tx_empty	= sbsa_uart_tx_empty,
> +	.set_mctrl	= sbsa_uart_set_mctrl,
> +	.get_mctrl	= sbsa_uart_get_mctrl,
> +	.stop_tx	= sbsa_uart_stop_tx,
> +	.start_tx	= sbsa_uart_start_tx,
> +	.stop_rx	= sbsa_uart_stop_rx,
> +	.enable_ms	= NULL,
> +	.break_ctl	= NULL,
> +	.startup	= sbsa_uart_startup,
> +	.shutdown	= sbsa_uart_shutdown,
> +	.flush_buffer	= NULL,
> +	.set_termios	= sbsa_uart_set_termios,
> +	.type		= sbsa_uart_type,
> +	.release_port	= sbsa_uart_release_port,
> +	.request_port	= sbsa_uart_request_port,
> +	.config_port	= sbsa_uart_config_port,
> +	.verify_port	= sbsa_uart_verify_port,
> +#ifdef CONFIG_CONSOLE_POLL
> +	.poll_init     = sbsa_uart_hwinit,
> +	.poll_get_char = sbsa_uart_get_poll_char,
> +	.poll_put_char = sbsa_uart_put_poll_char,
> +#endif
> +};
> +
> +static struct uart_port *sbsa_uart_ports[UART_NR];
> +
> +#ifdef CONFIG_SERIAL_SBSA_UART_CONSOLE
> +
> +static void sbsa_uart_console_putchar(struct uart_port *port, int ch)
> +{
> +	while (readw(port->membase + UART01x_FR) & UART01x_FR_TXFF)
> +		barrier();
> +	writew(ch, port->membase + UART01x_DR);
> +}
> +
> +static void
> +sbsa_uart_console_write(struct console *co, const char *s, unsigned int count)
> +{
> +	struct uart_port *port = sbsa_uart_ports[co->index];
> +	unsigned int status;
> +	unsigned long flags;
> +	int locked = 1;
> +
> +	local_irq_save(flags);
> +	if (port->sysrq)
> +		locked = 0;
> +	else if (oops_in_progress)
> +		locked = spin_trylock(&port->lock);
> +	else
> +		spin_lock(&port->lock);
> +
> +	uart_console_write(port, s, count, sbsa_uart_console_putchar);
> +
> +	/* Finally, wait for transmitter to become empty */
> +	do {
> +		status = readw(port->membase + UART01x_FR);
> +	} while (status & UART01x_FR_BUSY);
> +
> +	if (locked)
> +		spin_unlock(&port->lock);
> +	local_irq_restore(flags);
> +}
> +
> +static void __init
> +sbsa_uart_console_get_options(struct uart_port *port, int *baud,
> +			     int *parity, int *bits)
> +{
> +	*parity = 'n';
> +	*bits = 8;
> +	*baud = 115200;
> +}
> +
> +static int __init sbsa_uart_console_setup(struct console *co, char *options)
> +{
> +	struct uart_port *port;
> +	int baud = 38400;
> +	int bits = 8;
> +	int parity = 'n';
> +	int flow = 'n';
> +
> +	/*
> +	 * Check whether an invalid uart number has been specified, and
> +	 * if so, search for the first available port that does have
> +	 * console support.
> +	 */
> +	if (co->index >= UART_NR)
> +		co->index = 0;
> +	port = sbsa_uart_ports[co->index];
> +	if (!port)
> +		return -ENODEV;
> +
> +	if (options)
> +		uart_parse_options(options, &baud, &parity, &bits, &flow);
> +	else
> +		sbsa_uart_console_get_options(port, &baud, &parity, &bits);
> +
> +	return uart_set_options(port, co, baud, parity, bits, flow);
> +}
> +
> +static struct uart_driver sbsa_uart_reg;
> +static struct console sbsa_uart_console = {
> +	.name		= "ttyAMA",
> +	.write		= sbsa_uart_console_write,
> +	.device		= uart_console_device,
> +	.setup		= sbsa_uart_console_setup,
> +	.flags		= CON_PRINTBUFFER,
> +	.index		= -1,
> +	.data		= &sbsa_uart_reg,
> +};
> +
> +#define SBSA_UART_CONSOLE	(&sbsa_uart_console)
> +
> +static void sbsa_uart_putc(struct uart_port *port, int c)
> +{
> +	while (readw(port->membase + UART01x_FR) & UART01x_FR_TXFF)
> +		;
> +	writew(c & 0xff, port->membase + UART01x_DR);
> +	while (readw(port->membase + UART01x_FR) & UART01x_FR_BUSY)
> +		;
> +}
> +
> +static void sbsa_uart_early_write(struct console *con, const char *s,
> +				  unsigned n)
> +{
> +	struct earlycon_device *dev = con->data;
> +
> +	uart_console_write(&dev->port, s, n, sbsa_uart_putc);
> +}
> +
> +static int __init sbsa_uart_early_console_setup(struct earlycon_device *device,
> +						const char *opt)
> +{
> +	if (!device->port.membase)
> +		return -ENODEV;
> +
> +	device->con->write = sbsa_uart_early_write;
> +	return 0;
> +}
> +EARLYCON_DECLARE(pl011, sbsa_uart_early_console_setup);
> +OF_EARLYCON_DECLARE(pl011, "arm,sbsa-uart", sbsa_uart_early_console_setup);
> +
> +#else
> +#define SBSA_UART_CONSOLE	NULL
> +#endif
> +
> +static struct uart_driver sbsa_uart_reg = {
> +	.owner			= THIS_MODULE,
> +	.driver_name		= "sbsa_uart",
> +	.dev_name		= "ttyAMA",
> +	.nr			= UART_NR,
> +	.cons			= SBSA_UART_CONSOLE,
> +};
> +
> +#ifdef CONFIG_OF
> +
> +static int dt_probe_serial_alias(int index, struct device *dev)
> +{
> +	struct device_node *np;
> +	static bool seen_dev_with_alias;
> +	static bool seen_dev_without_alias;
> +	int ret = index;
> +
> +	if (!IS_ENABLED(CONFIG_OF))
> +		return ret;
> +
> +	np = dev->of_node;
> +	if (!np)
> +		return ret;
> +
> +	ret = of_alias_get_id(np, "serial");
> +	if (IS_ERR_VALUE(ret)) {
> +		seen_dev_without_alias = true;
> +		ret = index;
> +	} else {
> +		seen_dev_with_alias = true;
> +		if (ret >= ARRAY_SIZE(sbsa_uart_ports) ||
> +		    sbsa_uart_ports[ret] != NULL) {
> +			dev_warn(dev, "requested serial port %d  not available.\n", ret);
> +			ret = index;
> +		}
> +	}
> +
> +	if (seen_dev_with_alias && seen_dev_without_alias)
> +		dev_warn(dev, "aliased and non-aliased serial devices found in device tree. Serial port enumeration may be unpredictable.\n");
> +
> +	return ret;
> +}
> +
> +static const struct of_device_id arm_sbsa_match[] = {
> +	{ .compatible = "arm,sbsa-uart", },
> +	{},
> +};
> +MODULE_DEVICE_TABLE(of, arm_sbsa_match);
> +
> +#else
> +
> +static int dt_probe_serial_alias(int index, struct device *dev)
> +{
> +	static int portnr;
> +
> +	return portnr++;
> +}
> +
> +#endif
> +
> +static int sbsa_uart_probe(struct platform_device *pdev)
> +{
> +	struct resource *r;
> +	struct uart_port *port;
> +	int i, ret;
> +
> +	pr_info("DT probing for PL011-based SBSA UART\n");
> +	for (i = 0; i < ARRAY_SIZE(sbsa_uart_ports); i++)
> +		if (sbsa_uart_ports[i] == NULL)
> +			break;
> +
> +	if (i == ARRAY_SIZE(sbsa_uart_ports)) {
> +		ret = -EBUSY;
> +		goto out;
> +	}
> +
> +	r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> +
> +	port = devm_kzalloc(&pdev->dev, sizeof(struct uart_port), GFP_KERNEL);
> +	if (port == NULL) {
> +		ret = -ENOMEM;
> +		goto out;
> +	}
> +
> +	i = dt_probe_serial_alias(i, &pdev->dev);
> +
> +	port->membase = devm_ioremap_resource(&pdev->dev, r);
> +	if (!port->membase) {
> +		ret = -ENOMEM;
> +		goto out;
> +	}
> +
> +	port->irq = platform_get_irq(pdev, 0);
> +
> +	dev_info(&pdev->dev, "found SBSA UART #%d at 0x%08llx\n", i, r->start);
> +
> +	port->fifosize = 32;
> +	port->dev = &pdev->dev;
> +	port->mapbase = r->start;
> +	port->iotype = UPIO_MEM;
> +	port->ops = &sbsa_uart_pops;
> +	port->flags = UPF_BOOT_AUTOCONF;
> +	port->line = i;
> +
> +	/* Ensure interrupts from this UART are masked and cleared */
> +	writew(0, port->membase + UART011_IMSC);
> +	writew(0xffff, port->membase + UART011_ICR);
> +
> +	sbsa_uart_ports[i] = port;
> +
> +	platform_set_drvdata(pdev, port);
> +
> +	if (!sbsa_uart_reg.state) {
> +		ret = uart_register_driver(&sbsa_uart_reg);
> +		if (ret < 0) {
> +			pr_err("Failed to register ARM SBSA UART driver\n");
> +			return ret;
> +		}
> +	}
> +
> +	ret = uart_add_one_port(&sbsa_uart_reg, port);
> +	if (ret) {
> +		sbsa_uart_ports[i] = NULL;
> +		uart_unregister_driver(&sbsa_uart_reg);
> +	}
> + out:
> +	return ret;
> +}
> +
> +static int sbsa_uart_remove(struct platform_device *pdev)
> +{
> +	struct uart_port *port = platform_get_drvdata(pdev);
> +	bool busy = false;
> +	int i;
> +
> +	uart_remove_one_port(&sbsa_uart_reg, port);
> +
> +	for (i = 0; i < ARRAY_SIZE(sbsa_uart_ports); i++)
> +		if (sbsa_uart_ports[i] == port)
> +			sbsa_uart_ports[i] = NULL;
> +		else if (sbsa_uart_ports[i])
> +			busy = true;
> +
> +	if (!busy)
> +		uart_unregister_driver(&sbsa_uart_reg);
> +	return 0;
> +}
> +
> +static struct platform_driver arm_sbsa_uart_platform_driver = {
> +	.probe		= sbsa_uart_probe,
> +	.remove		= sbsa_uart_remove,
> +	.driver = {
> +		.name	= "sbsa-uart",
> +		.of_match_table = of_match_ptr(arm_sbsa_match),
> +	},
> +};
> +
> +module_platform_driver(arm_sbsa_uart_platform_driver);
> +
> +#ifdef CONFIG_PM_SLEEP
> +static int sbsa_uart_suspend(struct device *dev)
> +{
> +	struct uart_port *port = dev_get_drvdata(dev);
> +
> +	if (!port)
> +		return -EINVAL;
> +
> +	return uart_suspend_port(&sbsa_uart_reg, port);
> +}
> +
> +static int sbsa_uart_resume(struct device *dev)
> +{
> +	struct uart_port *port = dev_get_drvdata(dev);
> +
> +	if (!port)
> +		return -EINVAL;
> +
> +	return uart_resume_port(&sbsa_uart_reg, port);
> +}
> +
> +static SIMPLE_DEV_PM_OPS(sbsa_uart_dev_pm_ops, sbsa_uart_suspend,
> +			 sbsa_uart_resume);
> +
> +#endif
> +
> +static int __init sbsa_uart_init(void)
> +{
> +	pr_info("Serial: ARM SBSA generic UART (PL011) driver\n");
> +
> +	return 0;
> +}
> +
> +static void __exit sbsa_uart_exit(void)
> +{
> +}
> +
> +/*
> + * While this can be a module, if builtin it's most likely the console
> + * So let's leave module_exit but move module_init to an earlier place
> + */
> +arch_initcall(sbsa_uart_init);
> +module_exit(sbsa_uart_exit);
> +
> +MODULE_AUTHOR("ARM Ltd");
> +MODULE_DESCRIPTION("ARM SBSA generic UART serial port driver");
> +MODULE_LICENSE("GPL");
> diff --git a/include/uapi/linux/serial_core.h b/include/uapi/linux/serial_core.h
> index 5820269..9f9ee0c 100644
> --- a/include/uapi/linux/serial_core.h
> +++ b/include/uapi/linux/serial_core.h
> @@ -67,6 +67,7 @@
>  #define PORT_CLPS711X	33
>  #define PORT_SA1100	34
>  #define PORT_UART00	35
> +#define PORT_SBSA	36
>  #define PORT_21285	37
>  
>  /* Sparc type numbers.  */
> 

Regards,
Peter Hurley



More information about the linux-arm-kernel mailing list