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

Andre Przywara andre.przywara at arm.com
Fri Sep 5 07:44:49 PDT 2014


Hi Peter,

thanks for the review and the useful comments.
I will incorporate your comments in the next version.

...

On 02/09/14 19:19, Peter Hurley wrote:
> 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.

Why is this? Because this code cannot be called with interrupts already
off? This would apply to the original PL011 driver also then?

>> +
>> +     /*
>> +      * 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.

Ah, good hint. Saves me a rebase conflict later ;-)

Thanks and Regards,
Andre.

> 
>> +
>> +     /*
>> +      * 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