Hi Sebastian,

Nice work. Minor comments within.

On 09/10/2014 03:30 PM, Sebastian Andrzej Siewior wrote:
> This patch provides a 8250-core based UART driver for the internal OMAP
> UART. The long term goal is to provide the same functionality as the
> current OMAP uart driver and DMA support.
> I tried to merge omap-serial code together with the 8250-core code.
> There should should be hardly a noticable difference. The trigger levels
> are different compared to omap-serial:
> - omap serial
>   TX: Interrupt comes after TX FIFO has room for 16 bytes.
>       TX of 4096 bytes in one go results in 256 interrupts
> 
>   RX: Interrupt comes after there is on byte in the FIFO.
>       RX of 4096 bytes results in 4096 interrupts.
> 
> - this driver
>   TX: Interrupt comes once the TX FIFO is empty.
>       TX of 4096 bytes results in 65 interrupts. That means there will
>       be gaps on the line while the driver reloads the FIFO.
> 
>   RX: Interrupt comes once there are 48 bytes in the FIFO or less over
>       "longer" time frame. We have
>           1 / 11520 * 10^3 * 16 => 1.38… ms
>       1.38ms to react and purge the FIFO on 115200,8N1. Since the other
>       driver fired after each byte it had ~5.47ms time to react. This
>       _may_ cause problems if one relies on no missing bytes and has no
>       flow control. On the other hand we get only 85 interrupts for the
>       same amount of data.

After this is merged, it may be worth investigating how to use Yoshihiro's
newly-added 8250-based tunable RX trigger interface for omap.

> It has been only tested as console UART on am335x-evm, dra7-evm and
> beagle bone. I also did some longer raw-transfers to meassure the load.
> 
> The device name is ttyS based instead of ttyO. If a ttyO based node name
> is required please ask udev for it. If both driver are activated (this
> and omap-serial) then this serial driver will take control over the
> device due to the link order
> 
> v8…v9:
>       - less on a file seems to hang the am335x after a while. I
>         believe I introduce this bug a while ago since I can reproduce
>         this prior to v8. Fixed by redoing the omap8250_restore_regs()
> v7…v8:
>       - redo the register write. There is now one function for that
>         which is used from set_termios() and runtime-resume.
>       - drop PORT_OMAP_16750 and move the setup to the omap file. We
>         have our own set termios function anyway (Heikki Krogerus)
>       - use MEM instead of MEM32. TRM of AM/DM37x says that 32bit
>         access on THR might result in data abort. We only need 32bit
>         access in the errata function which is before we use 8250's
>         read function so it doesn't matter.
> v4…v7:
>       - change trigger levels after some tests with raw transfers.
> v3…v4:
>       - drop RS485 support
>       - wire up ->throttle / ->unthrottle
> v2…v3:
>       - wire up startup & shutdown for wakeup-irq handling.
>       - RS485 handling (well the core does).
> 
> v1…v2:
>       - added runtime PM. Could somebody could please double check
>         this?
>       - added omap_8250_set_termios()
> 
> Reviewed-by: Tony Lindgren <t...@atomide.com>
> Tested-by: Tony Lindgren <t...@atomide.com>
> Signed-off-by: Sebastian Andrzej Siewior <bige...@linutronix.de>
> ---
>  drivers/tty/serial/8250/8250_omap.c | 911 
> ++++++++++++++++++++++++++++++++++++
>  drivers/tty/serial/8250/Kconfig     |   9 +
>  drivers/tty/serial/8250/Makefile    |   1 +
>  3 files changed, 921 insertions(+)
>  create mode 100644 drivers/tty/serial/8250/8250_omap.c
> 
> diff --git a/drivers/tty/serial/8250/8250_omap.c 
> b/drivers/tty/serial/8250/8250_omap.c
> new file mode 100644
> index 000000000000..2a187b00ed0a
> --- /dev/null
> +++ b/drivers/tty/serial/8250/8250_omap.c
> @@ -0,0 +1,911 @@
> +/*
> + * 8250-core based driver for the OMAP internal UART
> + *
> + *  Copyright (C) 2014 Sebastian Andrzej Siewior

+ * based on omap-serial.c, Copyright (C) 2010 Texas Instruments.

or something like that, since this is (partly) based on omap-serial.c

> + *
> + */
> +
> +#include <linux/device.h>
> +#include <linux/io.h>
> +#include <linux/module.h>
> +#include <linux/serial_8250.h>
> +#include <linux/serial_core.h>
> +#include <linux/serial_reg.h>
> +#include <linux/platform_device.h>
> +#include <linux/slab.h>
> +#include <linux/of.h>
> +#include <linux/of_gpio.h>
> +#include <linux/of_irq.h>
> +#include <linux/delay.h>
> +#include <linux/pm_runtime.h>
> +#include <linux/console.h>
> +#include <linux/pm_qos.h>
> +
> +#include "8250.h"
> +
> +#define DEFAULT_CLK_SPEED    48000000
> +
> +#define UART_ERRATA_i202_MDR1_ACCESS (1 << 0)
> +#define OMAP_UART_WER_HAS_TX_WAKEUP  (1 << 1)
> +
> +#define OMAP_UART_FCR_RX_TRIG                6
> +#define OMAP_UART_FCR_TX_TRIG                4
> +
> +/* SCR register bitmasks */
> +#define OMAP_UART_SCR_RX_TRIG_GRANU1_MASK    (1 << 7)
> +#define OMAP_UART_SCR_TX_TRIG_GRANU1_MASK    (1 << 6)
> +#define OMAP_UART_SCR_TX_EMPTY                       (1 << 3)
> +#define OMAP_UART_SCR_DMAMODE_MASK           (3 << 1)
> +#define OMAP_UART_SCR_DMAMODE_1                      (1 << 1)
> +#define OMAP_UART_SCR_DMAMODE_CTL            (1 << 0)
> +
> +/* MVR register bitmasks */
> +#define OMAP_UART_MVR_SCHEME_SHIFT   30
> +#define OMAP_UART_LEGACY_MVR_MAJ_MASK        0xf0
> +#define OMAP_UART_LEGACY_MVR_MAJ_SHIFT       4
> +#define OMAP_UART_LEGACY_MVR_MIN_MASK        0x0f
> +#define OMAP_UART_MVR_MAJ_MASK               0x700
> +#define OMAP_UART_MVR_MAJ_SHIFT              8
> +#define OMAP_UART_MVR_MIN_MASK               0x3f
> +
> +#define UART_TI752_TLR_TX    0
> +#define UART_TI752_TLR_RX    4
> +
> +#define TRIGGER_TLR_MASK(x)  ((x & 0x3c) >> 2)
> +#define TRIGGER_FCR_MASK(x)  (x & 3)
> +
> +/* Enable XON/XOFF flow control on output */
> +#define OMAP_UART_SW_TX              0x08
> +/* Enable XON/XOFF flow control on input */
> +#define OMAP_UART_SW_RX              0x02
> +
> +#define OMAP_UART_WER_MOD_WKUP       0x7f
> +#define OMAP_UART_TX_WAKEUP_EN       (1 << 7)
> +
> +#define TX_TRIGGER   1
> +#define RX_TRIGGER   48
> +
> +#define OMAP_UART_TCR_RESTORE(x)     ((x / 4) << 4)
> +#define OMAP_UART_TCR_HALT(x)                ((x / 4) << 0)
> +
> +#define UART_BUILD_REVISION(x, y)    (((x) << 8) | (y))
> +
> +#define OMAP_UART_REV_46 0x0406
> +#define OMAP_UART_REV_52 0x0502
> +#define OMAP_UART_REV_63 0x0603
> +
> +struct omap8250_priv {
> +     int line;
> +     u32 habit;
> +     u32 mdr1;
> +     u32 efr;
> +     u32 quot;
> +     u32 scr;
> +     u32 wer;
> +     u32 xon;
> +     u32 xoff;
> +
> +     bool is_suspending;
> +     int wakeirq;
> +     int wakeups_enabled;
> +     u32 latency;
> +     u32 calc_latency;
> +     struct pm_qos_request pm_qos_request;
> +     struct work_struct qos_work;
> +     struct uart_8250_dma omap8250_dma;
> +     bool dma_active;
> +};
> +
> +static u32 uart_read(struct uart_8250_port *up, u32 reg)
> +{
> +     return readl(up->port.membase + (reg << up->port.regshift));
> +}
> +
> +/*
> + * Work Around for Errata i202 (2430, 3430, 3630, 4430 and 4460)
> + * The access to uart register after MDR1 Access
> + * causes UART to corrupt data.
> + *
> + * Need a delay =
> + * 5 L4 clock cycles + 5 UART functional clock cycle (@48MHz = ~0.2uS)
> + * give 10 times as much
> + */
> +static void omap_8250_mdr1_errataset(struct uart_8250_port *up, u8 mdr1)
> +{
> +     u8 timeout = 255;
> +
> +     serial_out(up, UART_OMAP_MDR1, mdr1);
> +     udelay(2);
> +     serial_out(up, UART_FCR, up->fcr | UART_FCR_CLEAR_XMIT |
> +                     UART_FCR_CLEAR_RCVR);
> +     /*
> +      * Wait for FIFO to empty: when empty, RX_FIFO_E bit is 0 and
> +      * TX_FIFO_E bit is 1.
> +      */
> +     while (UART_LSR_THRE != (serial_in(up, UART_LSR) &
> +                             (UART_LSR_THRE | UART_LSR_DR))) {
> +             timeout--;
> +             if (!timeout) {
> +                     /* Should *never* happen. we warn and carry on */
> +                     dev_crit(up->port.dev, "Errata i202: timedout %x\n",
> +                                             serial_in(up, UART_LSR));
> +                     break;
> +             }
> +             udelay(1);
> +     }
> +}
> +
> +static void omap_8250_get_divisor(struct uart_port *port, unsigned int baud,
> +             struct omap8250_priv *priv)
> +{
> +     unsigned int uartclk = port->uartclk;
> +     unsigned int div_13, div_16;
> +     unsigned int abs_d13, abs_d16;
> +
> +     /*
> +      * Old custom speed handling.
> +      */
> +     if (baud == 38400 && (port->flags & UPF_SPD_MASK) == UPF_SPD_CUST) {
> +             priv->quot = port->custom_divisor & 0xffff;
> +             /*
> +              * I assume that nobody is using this. But hey, if somebody
> +              * would like to specify the divisor _and_ the mode then the
> +              * driver is ready and waiting for it.
> +              */
> +             if (port->custom_divisor & (1 << 16))
> +                     priv->mdr1 = UART_OMAP_MDR1_13X_MODE;
> +             else
> +                     priv->mdr1 = UART_OMAP_MDR1_16X_MODE;
> +             return;
> +     }
> +     div_13 = DIV_ROUND_CLOSEST(uartclk, 13 * baud);
> +     div_16 = DIV_ROUND_CLOSEST(uartclk, 16 * baud);
> +
> +     abs_d13 = abs(baud - port->uartclk / 13 / div_13);
> +     abs_d16 = abs(baud - port->uartclk / 16 / div_16);
> +
> +     if (abs_d13 >= abs_d16) {
> +             priv->mdr1 = UART_OMAP_MDR1_16X_MODE;
> +             priv->quot = div_16;
> +     } else {
> +             priv->mdr1 = UART_OMAP_MDR1_13X_MODE;
> +             priv->quot = div_13;
> +     }
> +}
> +
> +static void omap8250_update_scr(struct uart_8250_port *up,
> +             struct omap8250_priv *priv)
> +{
> +     /*
> +      * The manual recommends not to enable the DMA mode selector in the SCR
> +      * (instead of the FCR) register _and_ selecting the DMA mode as one
> +      * register write because this may lead to malfunction.
> +      */
> +     if (priv->scr & OMAP_UART_SCR_DMAMODE_MASK)
> +             serial_out(up, UART_OMAP_SCR,
> +                             priv->scr & ~OMAP_UART_SCR_DMAMODE_MASK);
> +     serial_out(up, UART_OMAP_SCR, priv->scr);
> +}
> +
> +static void omap8250_restore_regs(struct uart_8250_port *up)
> +{
> +     struct omap8250_priv *priv = up->port.private_data;
> +
> +     serial_out(up, UART_LCR, UART_LCR_CONF_MODE_A);
> +     serial_out(up, UART_DLL, 0);
> +     serial_out(up, UART_DLM, 0);
> +
> +     serial_out(up, UART_LCR, UART_LCR_CONF_MODE_B);
> +     serial_out(up, UART_EFR, UART_EFR_ECB);
> +
> +     serial_out(up, UART_LCR, UART_LCR_CONF_MODE_A);
> +     serial_out(up, UART_MCR, UART_MCR_TCRTLR);
> +     serial_out(up, UART_FCR, up->fcr);
> +
> +     omap8250_update_scr(up, priv);
> +
> +     /* Protocol, Baud Rate, and Interrupt Settings */
> +     /* need mode A for FCR */
> +     if (priv->habit & UART_ERRATA_i202_MDR1_ACCESS)
> +             omap_8250_mdr1_errataset(up, UART_OMAP_MDR1_DISABLE);
> +     else
> +             serial_out(up, UART_OMAP_MDR1, UART_OMAP_MDR1_DISABLE);
> +
> +     serial_out(up, UART_LCR, UART_LCR_CONF_MODE_B);
> +
> +     serial_out(up, UART_TI752_TCR, OMAP_UART_TCR_RESTORE(16) |
> +                     OMAP_UART_TCR_HALT(52));
> +     serial_out(up, UART_TI752_TLR,
> +                     TRIGGER_TLR_MASK(TX_TRIGGER) << UART_TI752_TLR_TX |
> +                     TRIGGER_TLR_MASK(RX_TRIGGER) << UART_TI752_TLR_RX);
> +
> +     serial_out(up, UART_LCR, 0);
> +
> +     /* drop TCR + TLR access, we setup XON/XOFF later */
> +     serial_out(up, UART_MCR, up->mcr);
> +     serial_out(up, UART_IER, 0);
> +
> +     serial_out(up, UART_LCR, UART_LCR_CONF_MODE_B);
> +     serial_dl_write(up, priv->quot);
> +
> +     serial_out(up, UART_EFR, priv->efr);
> +
> +     serial_out(up, UART_LCR, up->lcr);
> +     /* need mode A for FCR */
> +     if (priv->habit & UART_ERRATA_i202_MDR1_ACCESS)
> +             omap_8250_mdr1_errataset(up, priv->mdr1);
> +     else
> +             serial_out(up, UART_OMAP_MDR1, priv->mdr1);
> +
> +     /* Configure flow control */
> +     serial_out(up, UART_LCR, UART_LCR_CONF_MODE_B);
> +     serial_out(up, UART_XON1, priv->xon);
> +     serial_out(up, UART_XOFF1, priv->xoff);
> +
> +     serial_out(up, UART_LCR, up->lcr);
> +     up->port.ops->set_mctrl(&up->port, up->port.mctrl);
> +}
> +/*
> + * OMAP can use "CLK / (16 or 13) / div" for baud rate. And then we have have
> + * some differences in how we want to handle flow control.
> + */
> +static void omap_8250_set_termios(struct uart_port *port,
> +             struct ktermios *termios, struct ktermios *old)
> +{
> +     struct uart_8250_port *up =
> +             container_of(port, struct uart_8250_port, port);
> +     struct omap8250_priv *priv = up->port.private_data;
> +     unsigned char cval = 0;
> +     unsigned long flags = 0;
> +     unsigned int baud;
> +
> +     switch (termios->c_cflag & CSIZE) {
> +     case CS5:
> +             cval = UART_LCR_WLEN5;
> +             break;
> +     case CS6:
> +             cval = UART_LCR_WLEN6;
> +             break;
> +     case CS7:
> +             cval = UART_LCR_WLEN7;
> +             break;
> +     default:
> +     case CS8:
> +             cval = UART_LCR_WLEN8;
> +             break;
> +     }
> +
> +     if (termios->c_cflag & CSTOPB)
> +             cval |= UART_LCR_STOP;
> +     if (termios->c_cflag & PARENB)
> +             cval |= UART_LCR_PARITY;
> +     if (!(termios->c_cflag & PARODD))
> +             cval |= UART_LCR_EPAR;
> +     if (termios->c_cflag & CMSPAR)
> +             cval |= UART_LCR_SPAR;
> +
> +     /*
> +      * Ask the core to calculate the divisor for us.
> +      */
> +     baud = uart_get_baud_rate(port, termios, old,
> +                     port->uartclk / 16 / 0xffff,
> +                     port->uartclk / 13);
> +     omap_8250_get_divisor(port, baud, priv);
> +
> +     /*
> +      * Ok, we're now changing the port state. Do it with
> +      * interrupts disabled.
> +      */
> +     pm_runtime_get_sync(port->dev);
> +     spin_lock_irqsave(&port->lock, flags);
                   ^^^
        spin_lock_irq(&port->lock);

The serial core calls the ->set_termios() method with interrupts enabled.


> +
> +     /*
> +      * Update the per-port timeout.
> +      */
> +     uart_update_timeout(port, termios->c_cflag, baud);
> +
> +     up->port.read_status_mask = UART_LSR_OE | UART_LSR_THRE | UART_LSR_DR;
> +     if (termios->c_iflag & INPCK)
> +             up->port.read_status_mask |= UART_LSR_FE | UART_LSR_PE;
> +     if (termios->c_iflag & (BRKINT | PARMRK))
                                ^
                              IGNBRK |

Otherwise, the read_status_mask will mask out the BI condition, so
uart_insert_char() will send '\0' byte as TTY_NORMAL.

The 8250 and omap RX path differed so the omap driver didn't need this
change, whereas the 8250 driver does.

> +             up->port.read_status_mask |= UART_LSR_BI;
> +
> +     /*
> +      * Characters to ignore
> +      */
> +     up->port.ignore_status_mask = 0;
> +     if (termios->c_iflag & IGNPAR)
> +             up->port.ignore_status_mask |= UART_LSR_PE | UART_LSR_FE;
> +     if (termios->c_iflag & IGNBRK) {
> +             up->port.ignore_status_mask |= UART_LSR_BI;
> +             /*
> +              * If we're ignoring parity and break indicators,
> +              * ignore overruns too (for real raw support).
> +              */
> +             if (termios->c_iflag & IGNPAR)
> +                     up->port.ignore_status_mask |= UART_LSR_OE;
> +     }
> +
> +     /*
> +      * ignore all characters if CREAD is not set
> +      */
> +     if ((termios->c_cflag & CREAD) == 0)
> +             up->port.ignore_status_mask |= UART_LSR_DR;
> +
> +     /*
> +      * Modem status interrupts
> +      */
> +     up->ier &= ~UART_IER_MSI;
> +     if (UART_ENABLE_MS(&up->port, termios->c_cflag))
> +             up->ier |= UART_IER_MSI;
> +
> +     up->lcr = cval;
> +     /* Up to here it was mostly serial8250_do_set_termios() */
> +
> +     /*
> +      * We enable TRIG_GRANU for RX and TX and additionaly we set
> +      * SCR_TX_EMPTY bit. The result is the following:
> +      * - RX_TRIGGER amount of bytes in the FIFO will cause an interrupt.
> +      * - less than RX_TRIGGER number of bytes will also cause an interrupt
> +      *   once the UART decides that there no new bytes arriving.
> +      * - Once THRE is enabled, the interrupt will be fired once the FIFO is
> +      *   empty - the trigger level is ignored here.
> +      *
> +      * Once DMA is enabled:
> +      * - UART will assert the TX DMA line once there is room for TX_TRIGGER
> +      *   bytes in the TX FIFO. On each assert the DMA engine will move
> +      *   TX_TRIGGER bytes into the FIFO.
> +      * - UART will assert the RX DMA line once there are RX_TRIGGER bytes in
> +      *   the FIFO and move RX_TRIGGER bytes.
> +      * This is because treshold and trigger values are the same.
> +      */
> +     up->fcr = UART_FCR_ENABLE_FIFO;
> +     up->fcr |= TRIGGER_FCR_MASK(TX_TRIGGER) << OMAP_UART_FCR_TX_TRIG;
> +     up->fcr |= TRIGGER_FCR_MASK(RX_TRIGGER) << OMAP_UART_FCR_RX_TRIG;
> +
> +     priv->scr = OMAP_UART_SCR_RX_TRIG_GRANU1_MASK | OMAP_UART_SCR_TX_EMPTY |
> +             OMAP_UART_SCR_TX_TRIG_GRANU1_MASK;
> +
> +     priv->xon = termios->c_cc[VSTART];
> +     priv->xoff = termios->c_cc[VSTOP];
> +
> +     priv->efr = 0;
> +     if (termios->c_cflag & CRTSCTS && up->port.flags & UPF_HARD_FLOW) {
> +             /* Enable AUTORTS and AUTOCTS */
> +             priv->efr |= UART_EFR_CTS | UART_EFR_RTS;
> +
> +             /* Ensure MCR RTS is asserted */
> +             up->mcr |= UART_MCR_RTS;
> +     }
> +
> +     if (up->port.flags & UPF_SOFT_FLOW) {

I'm aware that this is basically from the omap driver but can someone clear
up if omap hardware can actually do UPF_HARD_FLOW and UPF_SOFT_FLOW
simultaneously? The datasheets that I've looked at say no.

Regards,
Peter Hurley

> +             /*
> +              * IXON Flag:
> +              * Enable XON/XOFF flow control on input.
> +              * Receiver compares XON1, XOFF1.
> +              */
> +             if (termios->c_iflag & IXON)
> +                     priv->efr |= OMAP_UART_SW_RX;
> +
> +             /*
> +              * IXOFF Flag:
> +              * Enable XON/XOFF flow control on output.
> +              * Transmit XON1, XOFF1
> +              */
> +             if (termios->c_iflag & IXOFF)
> +                     priv->efr |= OMAP_UART_SW_TX;
> +
> +             /*
> +              * IXANY Flag:
> +              * Enable any character to restart output.
> +              * Operation resumes after receiving any
> +              * character after recognition of the XOFF character
> +              */
> +             if (termios->c_iflag & IXANY)
> +                     up->mcr |= UART_MCR_XONANY;
> +             else
> +                     up->mcr &= ~UART_MCR_XONANY;
> +     }
> +     omap8250_restore_regs(up);
> +
> +     spin_unlock_irqrestore(&up->port.lock, flags);
> +     pm_runtime_mark_last_busy(port->dev);
> +     pm_runtime_put_autosuspend(port->dev);
> +
> +     /* calculate wakeup latency constraint */
> +     priv->calc_latency = USEC_PER_SEC * 64 * 8 / baud;
> +     priv->latency = priv->calc_latency;
> +
> +     schedule_work(&priv->qos_work);
> +
> +     /* Don't rewrite B0 */
> +     if (tty_termios_baud_rate(termios))
> +             tty_termios_encode_baud_rate(termios, baud, baud);
> +}
> +
> +/* same as 8250 except that we may have extra flow bits set in EFR */
> +static void omap_8250_pm(struct uart_port *port, unsigned int state,
> +             unsigned int oldstate)
> +{
> +     struct uart_8250_port *up =
> +             container_of(port, struct uart_8250_port, port);
> +     struct omap8250_priv *priv = up->port.private_data;
> +
> +     pm_runtime_get_sync(port->dev);
> +     serial_out(up, UART_LCR, UART_LCR_CONF_MODE_B);
> +     serial_out(up, UART_EFR, priv->efr | UART_EFR_ECB);
> +     serial_out(up, UART_LCR, 0);
> +
> +     serial_out(up, UART_IER, (state != 0) ? UART_IERX_SLEEP : 0);
> +     serial_out(up, UART_LCR, UART_LCR_CONF_MODE_B);
> +     serial_out(up, UART_EFR, priv->efr);
> +     serial_out(up, UART_LCR, 0);
> +
> +     pm_runtime_mark_last_busy(port->dev);
> +     pm_runtime_put_autosuspend(port->dev);
> +}
> +
> +static void omap_serial_fill_features_erratas(struct uart_8250_port *up,
> +             struct omap8250_priv *priv)
> +{
> +     u32 mvr, scheme;
> +     u16 revision, major, minor;
> +
> +     mvr = uart_read(up, UART_OMAP_MVER);
> +
> +     /* Check revision register scheme */
> +     scheme = mvr >> OMAP_UART_MVR_SCHEME_SHIFT;
> +
> +     switch (scheme) {
> +     case 0: /* Legacy Scheme: OMAP2/3 */
> +             /* MINOR_REV[0:4], MAJOR_REV[4:7] */
> +             major = (mvr & OMAP_UART_LEGACY_MVR_MAJ_MASK) >>
> +                     OMAP_UART_LEGACY_MVR_MAJ_SHIFT;
> +             minor = (mvr & OMAP_UART_LEGACY_MVR_MIN_MASK);
> +             break;
> +     case 1:
> +             /* New Scheme: OMAP4+ */
> +             /* MINOR_REV[0:5], MAJOR_REV[8:10] */
> +             major = (mvr & OMAP_UART_MVR_MAJ_MASK) >>
> +                     OMAP_UART_MVR_MAJ_SHIFT;
> +             minor = (mvr & OMAP_UART_MVR_MIN_MASK);
> +             break;
> +     default:
> +             dev_warn(up->port.dev,
> +                             "Unknown revision, defaulting to highest\n");
> +             /* highest possible revision */
> +             major = 0xff;
> +             minor = 0xff;
> +     }
> +     /* normalize revision for the driver */
> +     revision = UART_BUILD_REVISION(major, minor);
> +
> +     switch (revision) {
> +     case OMAP_UART_REV_46:
> +             priv->habit = UART_ERRATA_i202_MDR1_ACCESS;
> +             break;
> +     case OMAP_UART_REV_52:
> +             priv->habit = UART_ERRATA_i202_MDR1_ACCESS |
> +                             OMAP_UART_WER_HAS_TX_WAKEUP;
> +             break;
> +     case OMAP_UART_REV_63:
> +             priv->habit = UART_ERRATA_i202_MDR1_ACCESS |
> +                     OMAP_UART_WER_HAS_TX_WAKEUP;
> +             break;
> +     default:
> +             break;
> +     }
> +}
> +
> +static void omap8250_uart_qos_work(struct work_struct *work)
> +{
> +     struct omap8250_priv *priv;
> +
> +     priv = container_of(work, struct omap8250_priv, qos_work);
> +     pm_qos_update_request(&priv->pm_qos_request, priv->latency);
> +}
> +
> +static irqreturn_t omap_wake_irq(int irq, void *dev_id)
> +{
> +     struct uart_port *port = dev_id;
> +     int ret;
> +
> +     ret = port->handle_irq(port);
> +     if (ret)
> +             return IRQ_HANDLED;
> +     return IRQ_NONE;
> +}
> +
> +static int omap_8250_startup(struct uart_port *port)
> +{
> +     struct uart_8250_port *up =
> +             container_of(port, struct uart_8250_port, port);
> +     struct omap8250_priv *priv = port->private_data;
> +
> +     int ret;
> +
> +     if (priv->wakeirq) {
> +             ret = request_irq(priv->wakeirq, omap_wake_irq,
> +                             port->irqflags, "wakeup irq", port);
> +             if (ret)
> +                     return ret;
> +             disable_irq(priv->wakeirq);
> +     }
> +
> +     pm_runtime_get_sync(port->dev);
> +
> +     ret = serial8250_do_startup(port);
> +     if (ret)
> +             goto err;
> +
> +#ifdef CONFIG_PM_RUNTIME
> +     up->capabilities |= UART_CAP_RPM;
> +#endif
> +
> +     /* Enable module level wake up */
> +     priv->wer = OMAP_UART_WER_MOD_WKUP;
> +     if (priv->habit & OMAP_UART_WER_HAS_TX_WAKEUP)
> +             priv->wer |= OMAP_UART_TX_WAKEUP_EN;
> +     serial_out(up, UART_OMAP_WER, priv->wer);
> +
> +     pm_runtime_mark_last_busy(port->dev);
> +     pm_runtime_put_autosuspend(port->dev);
> +     return 0;
> +err:
> +     pm_runtime_mark_last_busy(port->dev);
> +     pm_runtime_put_autosuspend(port->dev);
> +     if (priv->wakeirq)
> +             free_irq(priv->wakeirq, port);
> +     return ret;
> +}
> +
> +static void omap_8250_shutdown(struct uart_port *port)
> +{
> +     struct uart_8250_port *up =
> +             container_of(port, struct uart_8250_port, port);
> +     struct omap8250_priv *priv = port->private_data;
> +
> +     flush_work(&priv->qos_work);
> +
> +     pm_runtime_get_sync(port->dev);
> +
> +     serial_out(up, UART_OMAP_WER, 0);
> +     serial8250_do_shutdown(port);
> +
> +     pm_runtime_mark_last_busy(port->dev);
> +     pm_runtime_put_autosuspend(port->dev);
> +
> +     if (priv->wakeirq)
> +             free_irq(priv->wakeirq, port);
> +}
> +
> +static void omap_8250_throttle(struct uart_port *port)
> +{
> +     unsigned long flags;
> +     struct uart_8250_port *up =
> +             container_of(port, struct uart_8250_port, port);
> +
> +     pm_runtime_get_sync(port->dev);
> +
> +     spin_lock_irqsave(&port->lock, flags);
> +     up->ier &= ~(UART_IER_RLSI | UART_IER_RDI);
> +     serial_out(up, UART_IER, up->ier);
> +     spin_unlock_irqrestore(&port->lock, flags);
> +
> +     pm_runtime_mark_last_busy(port->dev);
> +     pm_runtime_put_autosuspend(port->dev);
> +}
> +
> +static void omap_8250_unthrottle(struct uart_port *port)
> +{
> +     unsigned long flags;
> +     struct uart_8250_port *up =
> +             container_of(port, struct uart_8250_port, port);
> +
> +     pm_runtime_get_sync(port->dev);
> +
> +     spin_lock_irqsave(&port->lock, flags);
> +     up->ier |= UART_IER_RLSI | UART_IER_RDI;
> +     serial_out(up, UART_IER, up->ier);
> +     spin_unlock_irqrestore(&port->lock, flags);
> +
> +     pm_runtime_mark_last_busy(port->dev);
> +     pm_runtime_put_autosuspend(port->dev);
> +}
> +
> +static int omap8250_probe(struct platform_device *pdev)
> +{
> +     struct resource *regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> +     struct resource *irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
> +     struct omap8250_priv *priv;
> +     struct uart_8250_port up;
> +     int ret;
> +     void __iomem *membase;
> +
> +     if (!regs || !irq) {
> +             dev_err(&pdev->dev, "missing registers or irq\n");
> +             return -EINVAL;
> +     }
> +
> +     priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
> +     if (!priv)
> +             return -ENOMEM;
> +
> +     membase = devm_ioremap_nocache(&pdev->dev, regs->start,
> +                     resource_size(regs));
> +     if (!membase)
> +             return -ENODEV;
> +
> +     memset(&up, 0, sizeof(up));
> +     up.port.dev = &pdev->dev;
> +     up.port.mapbase = regs->start;
> +     up.port.membase = membase;
> +     up.port.irq = irq->start;
> +     /*
> +      * It claims to be 16C750 compatible however it is a little different.
> +      * It has EFR and has no FCR7_64byte bit. The AFE (which it claims to
> +      * have) is enabled via EFR instead of MCR. The type is set here 8250
> +      * just to get things going. UNKNOWN does not work for a few reasons and
> +      * we don't need our own type since we don't use 8250's set_termios()
> +      * and our "bugs" are handeld via the bug member.
> +      */
> +     up.port.type = PORT_8250;
> +     up.port.iotype = UPIO_MEM;
> +     up.port.flags = UPF_FIXED_PORT | UPF_FIXED_TYPE | UPF_SOFT_FLOW |
> +             UPF_HARD_FLOW;
> +     up.port.private_data = priv;
> +
> +     up.port.regshift = 2;
> +     up.port.fifosize = 64;
> +     up.tx_loadsz = 64;
> +     up.capabilities = UART_CAP_FIFO | UART_CAP_EFR | UART_CAP_SLEEP;
> +#ifdef CONFIG_PM_RUNTIME
> +     /*
> +      * PM_RUNTIME is mostly transparent. However to do it right we need to a
> +      * TX empty interrupt before we can put the device to auto idle. So if
> +      * PM_RUNTIME is not enabled we don't add that flag and can spare that
> +      * one extra interrupt in the TX path.
> +      */
> +     up.capabilities |= UART_CAP_RPM;
> +#endif
> +     up.port.set_termios = omap_8250_set_termios;
> +     up.port.pm = omap_8250_pm;
> +     up.port.startup = omap_8250_startup;
> +     up.port.shutdown = omap_8250_shutdown;
> +     up.port.throttle = omap_8250_throttle;
> +     up.port.unthrottle = omap_8250_unthrottle;
> +
> +     if (pdev->dev.of_node) {
> +             up.port.line = of_alias_get_id(pdev->dev.of_node, "serial");
> +             of_property_read_u32(pdev->dev.of_node, "clock-frequency",
> +                             &up.port.uartclk);
> +             priv->wakeirq = irq_of_parse_and_map(pdev->dev.of_node, 1);
> +     } else {
> +             up.port.line = pdev->id;
> +     }
> +
> +     if (up.port.line < 0) {
> +             dev_err(&pdev->dev, "failed to get alias/pdev id, errno %d\n",
> +                             up.port.line);
> +             return -ENODEV;
> +     }
> +     if (!up.port.uartclk) {
> +             up.port.uartclk = DEFAULT_CLK_SPEED;
> +             dev_warn(&pdev->dev,
> +                             "No clock speed specified: using default: %d\n",
> +                             DEFAULT_CLK_SPEED);
> +     }
> +
> +     priv->latency = PM_QOS_CPU_DMA_LAT_DEFAULT_VALUE;
> +     priv->calc_latency = PM_QOS_CPU_DMA_LAT_DEFAULT_VALUE;
> +     pm_qos_add_request(&priv->pm_qos_request,
> +                     PM_QOS_CPU_DMA_LATENCY, priv->latency);
> +     INIT_WORK(&priv->qos_work, omap8250_uart_qos_work);
> +
> +     device_init_wakeup(&pdev->dev, true);
> +     pm_runtime_use_autosuspend(&pdev->dev);
> +     pm_runtime_set_autosuspend_delay(&pdev->dev, -1);
> +
> +     pm_runtime_irq_safe(&pdev->dev);
> +     pm_runtime_enable(&pdev->dev);
> +
> +     pm_runtime_get_sync(&pdev->dev);
> +
> +     omap_serial_fill_features_erratas(&up, priv);
> +     ret = serial8250_register_8250_port(&up);
> +     if (ret < 0) {
> +             dev_err(&pdev->dev, "unable to register 8250 port\n");
> +             goto err;
> +     }
> +     priv->line = ret;
> +     platform_set_drvdata(pdev, priv);
> +     pm_runtime_mark_last_busy(&pdev->dev);
> +     pm_runtime_put_autosuspend(&pdev->dev);
> +     return 0;
> +err:
> +     pm_runtime_put(&pdev->dev);
> +     pm_runtime_disable(&pdev->dev);
> +     return ret;
> +}
> +
> +static int omap8250_remove(struct platform_device *pdev)
> +{
> +     struct omap8250_priv *priv = platform_get_drvdata(pdev);
> +
> +     pm_runtime_put_sync(&pdev->dev);
> +     pm_runtime_disable(&pdev->dev);
> +     serial8250_unregister_port(priv->line);
> +     pm_qos_remove_request(&priv->pm_qos_request);
> +     device_init_wakeup(&pdev->dev, false);
> +     return 0;
> +}
> +
> +#if defined(CONFIG_PM_SLEEP) || defined(CONFIG_PM_RUNTIME)
> +
> +static inline void omap8250_enable_wakeirq(struct omap8250_priv *priv,
> +             bool enable)
> +{
> +     if (!priv->wakeirq)
> +             return;
> +
> +     if (enable)
> +             enable_irq(priv->wakeirq);
> +     else
> +             disable_irq_nosync(priv->wakeirq);
> +}
> +
> +static void omap8250_enable_wakeup(struct omap8250_priv *priv,
> +             bool enable)
> +{
> +     if (enable == priv->wakeups_enabled)
> +             return;
> +
> +     omap8250_enable_wakeirq(priv, enable);
> +     priv->wakeups_enabled = enable;
> +}
> +#endif
> +
> +#ifdef CONFIG_PM_SLEEP
> +static int omap8250_prepare(struct device *dev)
> +{
> +     struct omap8250_priv *priv = dev_get_drvdata(dev);
> +
> +     if (!priv)
> +             return 0;
> +     priv->is_suspending = true;
> +     return 0;
> +}
> +
> +static void omap8250_complete(struct device *dev)
> +{
> +     struct omap8250_priv *priv = dev_get_drvdata(dev);
> +
> +     if (!priv)
> +             return;
> +     priv->is_suspending = false;
> +}
> +
> +static int omap8250_suspend(struct device *dev)
> +{
> +     struct omap8250_priv *priv = dev_get_drvdata(dev);
> +
> +     serial8250_suspend_port(priv->line);
> +     flush_work(&priv->qos_work);
> +
> +     if (device_may_wakeup(dev))
> +             omap8250_enable_wakeup(priv, true);
> +     else
> +             omap8250_enable_wakeup(priv, false);
> +     return 0;
> +}
> +
> +static int omap8250_resume(struct device *dev)
> +{
> +     struct omap8250_priv *priv = dev_get_drvdata(dev);
> +
> +     if (device_may_wakeup(dev))
> +             omap8250_enable_wakeup(priv, false);
> +
> +     serial8250_resume_port(priv->line);
> +     return 0;
> +}
> +#else
> +#define omap8250_prepare NULL
> +#define omap8250_complete NULL
> +#endif
> +
> +#ifdef CONFIG_PM_RUNTIME
> +static int omap8250_lost_context(struct uart_8250_port *up)
> +{
> +     u32 val;
> +
> +     val = serial_in(up, UART_OMAP_MDR1);
> +     /*
> +      * If we lose context, then MDR1 is set to its reset value which is
> +      * UART_OMAP_MDR1_DISABLE. After set_termios() we set it either to 13x
> +      * or 16x but never to disable again.
> +      */
> +     if (val == UART_OMAP_MDR1_DISABLE)
> +             return 1;
> +     return 0;
> +}
> +
> +static int omap8250_runtime_suspend(struct device *dev)
> +{
> +     struct omap8250_priv *priv = dev_get_drvdata(dev);
> +     struct uart_8250_port *up;
> +
> +     up = serial8250_get_port(priv->line);
> +     /*
> +      * When using 'no_console_suspend', the console UART must not be
> +      * suspended. Since driver suspend is managed by runtime suspend,
> +      * preventing runtime suspend (by returning error) will keep device
> +      * active during suspend.
> +      */
> +     if (priv->is_suspending && !console_suspend_enabled) {
> +             if (uart_console(&up->port))
> +                     return -EBUSY;
> +     }
> +
> +     omap8250_enable_wakeup(priv, true);
> +
> +     priv->latency = PM_QOS_CPU_DMA_LAT_DEFAULT_VALUE;
> +     schedule_work(&priv->qos_work);
> +
> +     return 0;
> +}
> +
> +static int omap8250_runtime_resume(struct device *dev)
> +{
> +     struct omap8250_priv *priv = dev_get_drvdata(dev);
> +     struct uart_8250_port *up;
> +     int loss_cntx;
> +
> +     /* In case runtime-pm tries this before we are setup */
> +     if (!priv)
> +             return 0;
> +
> +     up = serial8250_get_port(priv->line);
> +     omap8250_enable_wakeup(priv, false);
> +     loss_cntx = omap8250_lost_context(up);
> +
> +     if (loss_cntx)
> +             omap8250_restore_regs(up);
> +
> +     priv->latency = priv->calc_latency;
> +     schedule_work(&priv->qos_work);
> +     return 0;
> +}
> +#endif
> +
> +static const struct dev_pm_ops omap8250_dev_pm_ops = {
> +     SET_SYSTEM_SLEEP_PM_OPS(omap8250_suspend, omap8250_resume)
> +     SET_RUNTIME_PM_OPS(omap8250_runtime_suspend,
> +                     omap8250_runtime_resume, NULL)
> +     .prepare        = omap8250_prepare,
> +     .complete       = omap8250_complete,
> +};
> +
> +static const struct of_device_id omap8250_dt_ids[] = {
> +     { .compatible = "ti,omap2-uart" },
> +     { .compatible = "ti,omap3-uart" },
> +     { .compatible = "ti,omap4-uart" },
> +     {},
> +};
> +MODULE_DEVICE_TABLE(of, omap8250_dt_ids);
> +
> +static struct platform_driver omap8250_platform_driver = {
> +     .driver = {
> +             .name           = "omap8250",
> +             .pm             = &omap8250_dev_pm_ops,
> +             .of_match_table = omap8250_dt_ids,
> +             .owner          = THIS_MODULE,
> +     },
> +     .probe                  = omap8250_probe,
> +     .remove                 = omap8250_remove,
> +};
> +module_platform_driver(omap8250_platform_driver);
> +
> +MODULE_AUTHOR("Sebastian Andrzej Siewior");
> +MODULE_DESCRIPTION("OMAP 8250 Driver");
> +MODULE_LICENSE("GPL v2");
> diff --git a/drivers/tty/serial/8250/Kconfig b/drivers/tty/serial/8250/Kconfig
> index 21eca79224e4..bb1b7119ecf9 100644
> --- a/drivers/tty/serial/8250/Kconfig
> +++ b/drivers/tty/serial/8250/Kconfig
> @@ -299,6 +299,15 @@ config SERIAL_8250_RT288X
>         serial port, say Y to this option. The driver can handle up to 2 
> serial
>         ports. If unsure, say N.
>  
> +config SERIAL_8250_OMAP
> +     tristate "Support for OMAP internal UART (8250 based driver)"
> +     depends on SERIAL_8250 && ARCH_OMAP2PLUS
> +     help
> +       If you have a machine based on an Texas Instruments OMAP CPU you
> +       can enable its onboard serial ports by enabling this option.
> +
> +       This driver is in early stage and uses ttyS instead of ttyO.
> +
>  config SERIAL_8250_FINTEK
>       tristate "Support for Fintek F81216A LPC to 4 UART"
>       depends on SERIAL_8250 && PNP
> diff --git a/drivers/tty/serial/8250/Makefile 
> b/drivers/tty/serial/8250/Makefile
> index 5256b894e46a..31e7cdc6865c 100644
> --- a/drivers/tty/serial/8250/Makefile
> +++ b/drivers/tty/serial/8250/Makefile
> @@ -20,5 +20,6 @@ obj-$(CONFIG_SERIAL_8250_HUB6)              += 8250_hub6.o
>  obj-$(CONFIG_SERIAL_8250_FSL)                += 8250_fsl.o
>  obj-$(CONFIG_SERIAL_8250_DW)         += 8250_dw.o
>  obj-$(CONFIG_SERIAL_8250_EM)         += 8250_em.o
> +obj-$(CONFIG_SERIAL_8250_OMAP)               += 8250_omap.o
>  obj-$(CONFIG_SERIAL_8250_FINTEK)     += 8250_fintek.o
>  obj-$(CONFIG_SERIAL_8250_MT6577)     += 8250_mtk.o
> 

--
To unsubscribe from this list: send the line "unsubscribe linux-omap" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to