[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