Re: [PATCH 06/16] tty: serial: Add 8250-core based omap driver

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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@xxxxxxxxxxx>
> Tested-by: Tony Lindgren <tony@xxxxxxxxxxx>
> Signed-off-by: Sebastian Andrzej Siewior <bigeasy@xxxxxxxxxxxxx>
> ---
>  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 majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html




[Index of Archives]     [Linux Arm (vger)]     [ARM Kernel]     [ARM MSM]     [Linux Tegra]     [Linux WPAN Networking]     [Linux Wireless Networking]     [Maemo Users]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite Trails]     [Linux Kernel]     [Linux SCSI]

  Powered by Linux