[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <54118E1B.5070706@hurleysoftware.com>
Date: Thu, 11 Sep 2014 07:57:15 -0400
From: Peter Hurley <peter@...leysoftware.com>
To: Sebastian Andrzej Siewior <bigeasy@...utronix.de>,
linux-serial@...r.kernel.org
CC: linux-kernel@...r.kernel.org, linux-omap@...r.kernel.org,
linux-arm-kernel@...ts.infradead.org, tony@...mide.com,
balbi@...com, gregkh@...uxfoundation.org
Subject: Re: [PATCH 06/16] tty: serial: Add 8250-core based omap driver
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 <tony@...mide.com>
> Tested-by: Tony Lindgren <tony@...mide.com>
> Signed-off-by: Sebastian Andrzej Siewior <bigeasy@...utronix.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-kernel" in
the body of a message to majordomo@...r.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/
Powered by blists - more mailing lists