diff options
| author | Kevin Hilman <khilman@deeprootsystems.com> | 2010-10-01 13:24:10 -0700 | 
|---|---|---|
| committer | Kevin Hilman <khilman@deeprootsystems.com> | 2010-10-01 13:24:10 -0700 | 
| commit | 69758ab7a1e2de5636a2188d5357dd03140bf1d8 (patch) | |
| tree | 13d65cbee4cc6bfed697bd164a15529ad86c2b25 /drivers/serial/omap-serial.c | |
| parent | 963bfb0939232e415c7bfb19b08dce300eb148d9 (diff) | |
| parent | 257f23d87f9309fee41d468575404b9371bf4c7d (diff) | |
| download | olio-linux-3.10-69758ab7a1e2de5636a2188d5357dd03140bf1d8.tar.xz olio-linux-3.10-69758ab7a1e2de5636a2188d5357dd03140bf1d8.zip  | |
manual merge for pm-hwmod-uart due to conflicts
Diffstat (limited to 'drivers/serial/omap-serial.c')
| -rw-r--r-- | drivers/serial/omap-serial.c | 1333 | 
1 files changed, 1333 insertions, 0 deletions
diff --git a/drivers/serial/omap-serial.c b/drivers/serial/omap-serial.c new file mode 100644 index 00000000000..2ee1d3282a8 --- /dev/null +++ b/drivers/serial/omap-serial.c @@ -0,0 +1,1333 @@ +/* + * Driver for OMAP-UART controller. + * Based on drivers/serial/8250.c + * + * Copyright (C) 2010 Texas Instruments. + * + * Authors: + *	Govindraj R	<govindraj.raja@ti.com> + *	Thara Gopinath	<thara@ti.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * Note: This driver is made seperate from 8250 driver as we cannot + * over load 8250 driver with omap platform specific configuration for + * features like DMA, it makes easier to implement features like DMA and + * hardware flow control and software flow control configuration with + * this driver as required for the omap-platform. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/console.h> +#include <linux/serial_reg.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/tty.h> +#include <linux/tty_flip.h> +#include <linux/io.h> +#include <linux/dma-mapping.h> +#include <linux/clk.h> +#include <linux/serial_core.h> +#include <linux/irq.h> + +#include <plat/dma.h> +#include <plat/dmtimer.h> +#include <plat/omap-serial.h> + +static struct uart_omap_port *ui[OMAP_MAX_HSUART_PORTS]; + +/* Forward declaration of functions */ +static void uart_tx_dma_callback(int lch, u16 ch_status, void *data); +static void serial_omap_rx_timeout(unsigned long uart_no); +static int serial_omap_start_rxdma(struct uart_omap_port *up); + +static inline unsigned int serial_in(struct uart_omap_port *up, int offset) +{ +	offset <<= up->port.regshift; +	return readw(up->port.membase + offset); +} + +static inline void serial_out(struct uart_omap_port *up, int offset, int value) +{ +	offset <<= up->port.regshift; +	writew(value, up->port.membase + offset); +} + +static inline void serial_omap_clear_fifos(struct uart_omap_port *up) +{ +	serial_out(up, UART_FCR, UART_FCR_ENABLE_FIFO); +	serial_out(up, UART_FCR, UART_FCR_ENABLE_FIFO | +		       UART_FCR_CLEAR_RCVR | UART_FCR_CLEAR_XMIT); +	serial_out(up, UART_FCR, 0); +} + +/* + * serial_omap_get_divisor - calculate divisor value + * @port: uart port info + * @baud: baudrate for which divisor needs to be calculated. + * + * We have written our own function to get the divisor so as to support + * 13x mode. 3Mbps Baudrate as an different divisor. + * Reference OMAP TRM Chapter 17: + * Table 17-1. UART Mode Baud Rates, Divisor Values, and Error Rates + * referring to oversampling - divisor value + * baudrate 460,800 to 3,686,400 all have divisor 13 + * except 3,000,000 which has divisor value 16 + */ +static unsigned int +serial_omap_get_divisor(struct uart_port *port, unsigned int baud) +{ +	unsigned int divisor; + +	if (baud > OMAP_MODE13X_SPEED && baud != 3000000) +		divisor = 13; +	else +		divisor = 16; +	return port->uartclk/(baud * divisor); +} + +static void serial_omap_stop_rxdma(struct uart_omap_port *up) +{ +	if (up->uart_dma.rx_dma_used) { +		del_timer(&up->uart_dma.rx_timer); +		omap_stop_dma(up->uart_dma.rx_dma_channel); +		omap_free_dma(up->uart_dma.rx_dma_channel); +		up->uart_dma.rx_dma_channel = OMAP_UART_DMA_CH_FREE; +		up->uart_dma.rx_dma_used = false; +	} +} + +static void serial_omap_enable_ms(struct uart_port *port) +{ +	struct uart_omap_port *up = (struct uart_omap_port *)port; + +	dev_dbg(up->port.dev, "serial_omap_enable_ms+%d\n", up->pdev->id); +	up->ier |= UART_IER_MSI; +	serial_out(up, UART_IER, up->ier); +} + +static void serial_omap_stop_tx(struct uart_port *port) +{ +	struct uart_omap_port *up = (struct uart_omap_port *)port; + +	if (up->use_dma && +		up->uart_dma.tx_dma_channel != OMAP_UART_DMA_CH_FREE) { +		/* +		 * Check if dma is still active. If yes do nothing, +		 * return. Else stop dma +		 */ +		if (omap_get_dma_active_status(up->uart_dma.tx_dma_channel)) +			return; +		omap_stop_dma(up->uart_dma.tx_dma_channel); +		omap_free_dma(up->uart_dma.tx_dma_channel); +		up->uart_dma.tx_dma_channel = OMAP_UART_DMA_CH_FREE; +	} + +	if (up->ier & UART_IER_THRI) { +		up->ier &= ~UART_IER_THRI; +		serial_out(up, UART_IER, up->ier); +	} +} + +static void serial_omap_stop_rx(struct uart_port *port) +{ +	struct uart_omap_port *up = (struct uart_omap_port *)port; + +	if (up->use_dma) +		serial_omap_stop_rxdma(up); +	up->ier &= ~UART_IER_RLSI; +	up->port.read_status_mask &= ~UART_LSR_DR; +	serial_out(up, UART_IER, up->ier); +} + +static inline void receive_chars(struct uart_omap_port *up, int *status) +{ +	struct tty_struct *tty = up->port.state->port.tty; +	unsigned int flag; +	unsigned char ch, lsr = *status; +	int max_count = 256; + +	do { +		if (likely(lsr & UART_LSR_DR)) +			ch = serial_in(up, UART_RX); +		flag = TTY_NORMAL; +		up->port.icount.rx++; + +		if (unlikely(lsr & UART_LSR_BRK_ERROR_BITS)) { +			/* +			 * For statistics only +			 */ +			if (lsr & UART_LSR_BI) { +				lsr &= ~(UART_LSR_FE | UART_LSR_PE); +				up->port.icount.brk++; +				/* +				 * We do the SysRQ and SAK checking +				 * here because otherwise the break +				 * may get masked by ignore_status_mask +				 * or read_status_mask. +				 */ +				if (uart_handle_break(&up->port)) +					goto ignore_char; +			} else if (lsr & UART_LSR_PE) { +				up->port.icount.parity++; +			} else if (lsr & UART_LSR_FE) { +				up->port.icount.frame++; +			} + +			if (lsr & UART_LSR_OE) +				up->port.icount.overrun++; + +			/* +			 * Mask off conditions which should be ignored. +			 */ +			lsr &= up->port.read_status_mask; + +#ifdef CONFIG_SERIAL_OMAP_CONSOLE +			if (up->port.line == up->port.cons->index) { +				/* Recover the break flag from console xmit */ +				lsr |= up->lsr_break_flag; +				up->lsr_break_flag = 0; +			} +#endif +			if (lsr & UART_LSR_BI) +				flag = TTY_BREAK; +			else if (lsr & UART_LSR_PE) +				flag = TTY_PARITY; +			else if (lsr & UART_LSR_FE) +				flag = TTY_FRAME; +		} + +		if (uart_handle_sysrq_char(&up->port, ch)) +			goto ignore_char; +		uart_insert_char(&up->port, lsr, UART_LSR_OE, ch, flag); +ignore_char: +		lsr = serial_in(up, UART_LSR); +	} while ((lsr & (UART_LSR_DR | UART_LSR_BI)) && (max_count-- > 0)); +	spin_unlock(&up->port.lock); +	tty_flip_buffer_push(tty); +	spin_lock(&up->port.lock); +} + +static void transmit_chars(struct uart_omap_port *up) +{ +	struct circ_buf *xmit = &up->port.state->xmit; +	int count; + +	if (up->port.x_char) { +		serial_out(up, UART_TX, up->port.x_char); +		up->port.icount.tx++; +		up->port.x_char = 0; +		return; +	} +	if (uart_circ_empty(xmit) || uart_tx_stopped(&up->port)) { +		serial_omap_stop_tx(&up->port); +		return; +	} +	count = up->port.fifosize / 4; +	do { +		serial_out(up, UART_TX, xmit->buf[xmit->tail]); +		xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1); +		up->port.icount.tx++; +		if (uart_circ_empty(xmit)) +			break; +	} while (--count > 0); + +	if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS) +		uart_write_wakeup(&up->port); + +	if (uart_circ_empty(xmit)) +		serial_omap_stop_tx(&up->port); +} + +static inline void serial_omap_enable_ier_thri(struct uart_omap_port *up) +{ +	if (!(up->ier & UART_IER_THRI)) { +		up->ier |= UART_IER_THRI; +		serial_out(up, UART_IER, up->ier); +	} +} + +static void serial_omap_start_tx(struct uart_port *port) +{ +	struct uart_omap_port *up = (struct uart_omap_port *)port; +	struct circ_buf *xmit; +	unsigned int start; +	int ret = 0; + +	if (!up->use_dma) { +		serial_omap_enable_ier_thri(up); +		return; +	} + +	if (up->uart_dma.tx_dma_used) +		return; + +	xmit = &up->port.state->xmit; + +	if (up->uart_dma.tx_dma_channel == OMAP_UART_DMA_CH_FREE) { +		ret = omap_request_dma(up->uart_dma.uart_dma_tx, +				"UART Tx DMA", +				(void *)uart_tx_dma_callback, up, +				&(up->uart_dma.tx_dma_channel)); + +		if (ret < 0) { +			serial_omap_enable_ier_thri(up); +			return; +		} +	} +	spin_lock(&(up->uart_dma.tx_lock)); +	up->uart_dma.tx_dma_used = true; +	spin_unlock(&(up->uart_dma.tx_lock)); + +	start = up->uart_dma.tx_buf_dma_phys + +				(xmit->tail & (UART_XMIT_SIZE - 1)); + +	up->uart_dma.tx_buf_size = uart_circ_chars_pending(xmit); +	/* +	 * It is a circular buffer. See if the buffer has wounded back. +	 * If yes it will have to be transferred in two separate dma +	 * transfers +	 */ +	if (start + up->uart_dma.tx_buf_size >= +			up->uart_dma.tx_buf_dma_phys + UART_XMIT_SIZE) +		up->uart_dma.tx_buf_size = +			(up->uart_dma.tx_buf_dma_phys + +			UART_XMIT_SIZE) - start; + +	omap_set_dma_dest_params(up->uart_dma.tx_dma_channel, 0, +				OMAP_DMA_AMODE_CONSTANT, +				up->uart_dma.uart_base, 0, 0); +	omap_set_dma_src_params(up->uart_dma.tx_dma_channel, 0, +				OMAP_DMA_AMODE_POST_INC, start, 0, 0); +	omap_set_dma_transfer_params(up->uart_dma.tx_dma_channel, +				OMAP_DMA_DATA_TYPE_S8, +				up->uart_dma.tx_buf_size, 1, +				OMAP_DMA_SYNC_ELEMENT, +				up->uart_dma.uart_dma_tx, 0); +	/* FIXME: Cache maintenance needed here? */ +	omap_start_dma(up->uart_dma.tx_dma_channel); +} + +static unsigned int check_modem_status(struct uart_omap_port *up) +{ +	unsigned int status; + +	status = serial_in(up, UART_MSR); +	status |= up->msr_saved_flags; +	up->msr_saved_flags = 0; +	if ((status & UART_MSR_ANY_DELTA) == 0) +		return status; + +	if (status & UART_MSR_ANY_DELTA && up->ier & UART_IER_MSI && +	    up->port.state != NULL) { +		if (status & UART_MSR_TERI) +			up->port.icount.rng++; +		if (status & UART_MSR_DDSR) +			up->port.icount.dsr++; +		if (status & UART_MSR_DDCD) +			uart_handle_dcd_change +				(&up->port, status & UART_MSR_DCD); +		if (status & UART_MSR_DCTS) +			uart_handle_cts_change +				(&up->port, status & UART_MSR_CTS); +		wake_up_interruptible(&up->port.state->port.delta_msr_wait); +	} + +	return status; +} + +/** + * serial_omap_irq() - This handles the interrupt from one port + * @irq: uart port irq number + * @dev_id: uart port info + */ +static inline irqreturn_t serial_omap_irq(int irq, void *dev_id) +{ +	struct uart_omap_port *up = dev_id; +	unsigned int iir, lsr; +	unsigned long flags; + +	iir = serial_in(up, UART_IIR); +	if (iir & UART_IIR_NO_INT) +		return IRQ_NONE; + +	spin_lock_irqsave(&up->port.lock, flags); +	lsr = serial_in(up, UART_LSR); +	if (iir & UART_IIR_RLSI) { +		if (!up->use_dma) { +			if (lsr & UART_LSR_DR) +				receive_chars(up, &lsr); +		} else { +			up->ier &= ~(UART_IER_RDI | UART_IER_RLSI); +			serial_out(up, UART_IER, up->ier); +			if ((serial_omap_start_rxdma(up) != 0) && +					(lsr & UART_LSR_DR)) +				receive_chars(up, &lsr); +		} +	} + +	check_modem_status(up); +	if ((lsr & UART_LSR_THRE) && (iir & UART_IIR_THRI)) +		transmit_chars(up); + +	spin_unlock_irqrestore(&up->port.lock, flags); +	up->port_activity = jiffies; +	return IRQ_HANDLED; +} + +static unsigned int serial_omap_tx_empty(struct uart_port *port) +{ +	struct uart_omap_port *up = (struct uart_omap_port *)port; +	unsigned long flags = 0; +	unsigned int ret = 0; + +	dev_dbg(up->port.dev, "serial_omap_tx_empty+%d\n", up->pdev->id); +	spin_lock_irqsave(&up->port.lock, flags); +	ret = serial_in(up, UART_LSR) & UART_LSR_TEMT ? TIOCSER_TEMT : 0; +	spin_unlock_irqrestore(&up->port.lock, flags); + +	return ret; +} + +static unsigned int serial_omap_get_mctrl(struct uart_port *port) +{ +	struct uart_omap_port *up = (struct uart_omap_port *)port; +	unsigned char status; +	unsigned int ret = 0; + +	status = check_modem_status(up); +	dev_dbg(up->port.dev, "serial_omap_get_mctrl+%d\n", up->pdev->id); + +	if (status & UART_MSR_DCD) +		ret |= TIOCM_CAR; +	if (status & UART_MSR_RI) +		ret |= TIOCM_RNG; +	if (status & UART_MSR_DSR) +		ret |= TIOCM_DSR; +	if (status & UART_MSR_CTS) +		ret |= TIOCM_CTS; +	return ret; +} + +static void serial_omap_set_mctrl(struct uart_port *port, unsigned int mctrl) +{ +	struct uart_omap_port *up = (struct uart_omap_port *)port; +	unsigned char mcr = 0; + +	dev_dbg(up->port.dev, "serial_omap_set_mctrl+%d\n", up->pdev->id); +	if (mctrl & TIOCM_RTS) +		mcr |= UART_MCR_RTS; +	if (mctrl & TIOCM_DTR) +		mcr |= UART_MCR_DTR; +	if (mctrl & TIOCM_OUT1) +		mcr |= UART_MCR_OUT1; +	if (mctrl & TIOCM_OUT2) +		mcr |= UART_MCR_OUT2; +	if (mctrl & TIOCM_LOOP) +		mcr |= UART_MCR_LOOP; + +	mcr |= up->mcr; +	serial_out(up, UART_MCR, mcr); +} + +static void serial_omap_break_ctl(struct uart_port *port, int break_state) +{ +	struct uart_omap_port *up = (struct uart_omap_port *)port; +	unsigned long flags = 0; + +	dev_dbg(up->port.dev, "serial_omap_break_ctl+%d\n", up->pdev->id); +	spin_lock_irqsave(&up->port.lock, flags); +	if (break_state == -1) +		up->lcr |= UART_LCR_SBC; +	else +		up->lcr &= ~UART_LCR_SBC; +	serial_out(up, UART_LCR, up->lcr); +	spin_unlock_irqrestore(&up->port.lock, flags); +} + +static int serial_omap_startup(struct uart_port *port) +{ +	struct uart_omap_port *up = (struct uart_omap_port *)port; +	unsigned long flags = 0; +	int retval; + +	/* +	 * Allocate the IRQ +	 */ +	retval = request_irq(up->port.irq, serial_omap_irq, up->port.irqflags, +				up->name, up); +	if (retval) +		return retval; + +	dev_dbg(up->port.dev, "serial_omap_startup+%d\n", up->pdev->id); + +	/* +	 * Clear the FIFO buffers and disable them. +	 * (they will be reenabled in set_termios()) +	 */ +	serial_omap_clear_fifos(up); +	/* For Hardware flow control */ +	serial_out(up, UART_MCR, UART_MCR_RTS); + +	/* +	 * Clear the interrupt registers. +	 */ +	(void) serial_in(up, UART_LSR); +	if (serial_in(up, UART_LSR) & UART_LSR_DR) +		(void) serial_in(up, UART_RX); +	(void) serial_in(up, UART_IIR); +	(void) serial_in(up, UART_MSR); + +	/* +	 * Now, initialize the UART +	 */ +	serial_out(up, UART_LCR, UART_LCR_WLEN8); +	spin_lock_irqsave(&up->port.lock, flags); +	/* +	 * Most PC uarts need OUT2 raised to enable interrupts. +	 */ +	up->port.mctrl |= TIOCM_OUT2; +	serial_omap_set_mctrl(&up->port, up->port.mctrl); +	spin_unlock_irqrestore(&up->port.lock, flags); + +	up->msr_saved_flags = 0; +	if (up->use_dma) { +		free_page((unsigned long)up->port.state->xmit.buf); +		up->port.state->xmit.buf = dma_alloc_coherent(NULL, +			UART_XMIT_SIZE, +			(dma_addr_t *)&(up->uart_dma.tx_buf_dma_phys), +			0); +		init_timer(&(up->uart_dma.rx_timer)); +		up->uart_dma.rx_timer.function = serial_omap_rx_timeout; +		up->uart_dma.rx_timer.data = up->pdev->id; +		/* Currently the buffer size is 4KB. Can increase it */ +		up->uart_dma.rx_buf = dma_alloc_coherent(NULL, +			up->uart_dma.rx_buf_size, +			(dma_addr_t *)&(up->uart_dma.rx_buf_dma_phys), 0); +	} +	/* +	 * Finally, enable interrupts. Note: Modem status interrupts +	 * are set via set_termios(), which will be occurring imminently +	 * anyway, so we don't enable them here. +	 */ +	up->ier = UART_IER_RLSI | UART_IER_RDI; +	serial_out(up, UART_IER, up->ier); + +	up->port_activity = jiffies; +	return 0; +} + +static void serial_omap_shutdown(struct uart_port *port) +{ +	struct uart_omap_port *up = (struct uart_omap_port *)port; +	unsigned long flags = 0; + +	dev_dbg(up->port.dev, "serial_omap_shutdown+%d\n", up->pdev->id); +	/* +	 * Disable interrupts from this port +	 */ +	up->ier = 0; +	serial_out(up, UART_IER, 0); + +	spin_lock_irqsave(&up->port.lock, flags); +	up->port.mctrl &= ~TIOCM_OUT2; +	serial_omap_set_mctrl(&up->port, up->port.mctrl); +	spin_unlock_irqrestore(&up->port.lock, flags); + +	/* +	 * Disable break condition and FIFOs +	 */ +	serial_out(up, UART_LCR, serial_in(up, UART_LCR) & ~UART_LCR_SBC); +	serial_omap_clear_fifos(up); + +	/* +	 * Read data port to reset things, and then free the irq +	 */ +	if (serial_in(up, UART_LSR) & UART_LSR_DR) +		(void) serial_in(up, UART_RX); +	if (up->use_dma) { +		dma_free_coherent(up->port.dev, +			UART_XMIT_SIZE,	up->port.state->xmit.buf, +			up->uart_dma.tx_buf_dma_phys); +		up->port.state->xmit.buf = NULL; +		serial_omap_stop_rx(port); +		dma_free_coherent(up->port.dev, +			up->uart_dma.rx_buf_size, up->uart_dma.rx_buf, +			up->uart_dma.rx_buf_dma_phys); +		up->uart_dma.rx_buf = NULL; +	} +	free_irq(up->port.irq, up); +} + +static inline void +serial_omap_configure_xonxoff +		(struct uart_omap_port *up, struct ktermios *termios) +{ +	unsigned char efr = 0; + +	up->lcr = serial_in(up, UART_LCR); +	serial_out(up, UART_LCR, OMAP_UART_LCR_CONF_MDB); +	up->efr = serial_in(up, UART_EFR); +	serial_out(up, UART_EFR, up->efr & ~UART_EFR_ECB); + +	serial_out(up, UART_XON1, termios->c_cc[VSTART]); +	serial_out(up, UART_XOFF1, termios->c_cc[VSTOP]); + +	/* clear SW control mode bits */ +	efr = up->efr; +	efr &= OMAP_UART_SW_CLR; + +	/* +	 * IXON Flag: +	 * Enable XON/XOFF flow control on output. +	 * Transmit XON1, XOFF1 +	 */ +	if (termios->c_iflag & IXON) +		efr |= OMAP_UART_SW_TX; + +	/* +	 * IXOFF Flag: +	 * Enable XON/XOFF flow control on input. +	 * Receiver compares XON1, XOFF1. +	 */ +	if (termios->c_iflag & IXOFF) +		efr |= OMAP_UART_SW_RX; + +	serial_out(up, UART_EFR, up->efr | UART_EFR_ECB); +	serial_out(up, UART_LCR, UART_LCR_DLAB); + +	up->mcr = serial_in(up, UART_MCR); + +	/* +	 * 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; + +	serial_out(up, UART_MCR, up->mcr | UART_MCR_TCRTLR); +	serial_out(up, UART_LCR, OMAP_UART_LCR_CONF_MDB); +	serial_out(up, UART_TI752_TCR, OMAP_UART_TCR_TRIG); +	/* Enable special char function UARTi.EFR_REG[5] and +	 * load the new software flow control mode IXON or IXOFF +	 * and restore the UARTi.EFR_REG[4] ENHANCED_EN value. +	 */ +	serial_out(up, UART_EFR, efr | UART_EFR_SCD); +	serial_out(up, UART_LCR, UART_LCR_DLAB); + +	serial_out(up, UART_MCR, up->mcr & ~UART_MCR_TCRTLR); +	serial_out(up, UART_LCR, up->lcr); +} + +static void +serial_omap_set_termios(struct uart_port *port, struct ktermios *termios, +			struct ktermios *old) +{ +	struct uart_omap_port *up = (struct uart_omap_port *)port; +	unsigned char cval = 0; +	unsigned char efr = 0; +	unsigned long flags = 0; +	unsigned int baud, quot; + +	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; + +	/* +	 * Ask the core to calculate the divisor for us. +	 */ + +	baud = uart_get_baud_rate(port, termios, old, 0, port->uartclk/13); +	quot = serial_omap_get_divisor(port, baud); + +	up->fcr = UART_FCR_R_TRIG_01 | UART_FCR_T_TRIG_01 | +			UART_FCR_ENABLE_FIFO; +	if (up->use_dma) +		up->fcr |= UART_FCR_DMA_SELECT; + +	/* +	 * Ok, we're now changing the port state. Do it with +	 * interrupts disabled. +	 */ +	spin_lock_irqsave(&up->port.lock, flags); + +	/* +	 * 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)) +		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; +	serial_out(up, UART_IER, up->ier); +	serial_out(up, UART_LCR, cval);		/* reset DLAB */ + +	/* FIFOs and DMA Settings */ + +	/* FCR can be changed only when the +	 * baud clock is not running +	 * DLL_REG and DLH_REG set to 0. +	 */ +	serial_out(up, UART_LCR, UART_LCR_DLAB); +	serial_out(up, UART_DLL, 0); +	serial_out(up, UART_DLM, 0); +	serial_out(up, UART_LCR, 0); + +	serial_out(up, UART_LCR, OMAP_UART_LCR_CONF_MDB); + +	up->efr = serial_in(up, UART_EFR); +	serial_out(up, UART_EFR, up->efr | UART_EFR_ECB); + +	serial_out(up, UART_LCR, UART_LCR_DLAB); +	up->mcr = serial_in(up, UART_MCR); +	serial_out(up, UART_MCR, up->mcr | UART_MCR_TCRTLR); +	/* FIFO ENABLE, DMA MODE */ +	serial_out(up, UART_FCR, up->fcr); +	serial_out(up, UART_LCR, OMAP_UART_LCR_CONF_MDB); + +	if (up->use_dma) { +		serial_out(up, UART_TI752_TLR, 0); +		serial_out(up, UART_OMAP_SCR, +			(UART_FCR_TRIGGER_4 | UART_FCR_TRIGGER_8)); +	} + +	serial_out(up, UART_EFR, up->efr); +	serial_out(up, UART_LCR, UART_LCR_DLAB); +	serial_out(up, UART_MCR, up->mcr); + +	/* Protocol, Baud Rate, and Interrupt Settings */ + +	serial_out(up, UART_OMAP_MDR1, OMAP_MDR1_DISABLE); +	serial_out(up, UART_LCR, OMAP_UART_LCR_CONF_MDB); + +	up->efr = serial_in(up, UART_EFR); +	serial_out(up, UART_EFR, up->efr | UART_EFR_ECB); + +	serial_out(up, UART_LCR, 0); +	serial_out(up, UART_IER, 0); +	serial_out(up, UART_LCR, OMAP_UART_LCR_CONF_MDB); + +	serial_out(up, UART_DLL, quot & 0xff);          /* LS of divisor */ +	serial_out(up, UART_DLM, quot >> 8);            /* MS of divisor */ + +	serial_out(up, UART_LCR, 0); +	serial_out(up, UART_IER, up->ier); +	serial_out(up, UART_LCR, OMAP_UART_LCR_CONF_MDB); + +	serial_out(up, UART_EFR, up->efr); +	serial_out(up, UART_LCR, cval); + +	if (baud > 230400 && baud != 3000000) +		serial_out(up, UART_OMAP_MDR1, OMAP_MDR1_MODE13X); +	else +		serial_out(up, UART_OMAP_MDR1, OMAP_MDR1_MODE16X); + +	/* Hardware Flow Control Configuration */ + +	if (termios->c_cflag & CRTSCTS) { +		efr |= (UART_EFR_CTS | UART_EFR_RTS); +		serial_out(up, UART_LCR, UART_LCR_DLAB); + +		up->mcr = serial_in(up, UART_MCR); +		serial_out(up, UART_MCR, up->mcr | UART_MCR_TCRTLR); + +		serial_out(up, UART_LCR, OMAP_UART_LCR_CONF_MDB); +		up->efr = serial_in(up, UART_EFR); +		serial_out(up, UART_EFR, up->efr | UART_EFR_ECB); + +		serial_out(up, UART_TI752_TCR, OMAP_UART_TCR_TRIG); +		serial_out(up, UART_EFR, efr); /* Enable AUTORTS and AUTOCTS */ +		serial_out(up, UART_LCR, UART_LCR_DLAB); +		serial_out(up, UART_MCR, up->mcr | UART_MCR_RTS); +		serial_out(up, UART_LCR, cval); +	} + +	serial_omap_set_mctrl(&up->port, up->port.mctrl); +	/* Software Flow Control Configuration */ +	if (termios->c_iflag & (IXON | IXOFF)) +		serial_omap_configure_xonxoff(up, termios); + +	spin_unlock_irqrestore(&up->port.lock, flags); +	dev_dbg(up->port.dev, "serial_omap_set_termios+%d\n", up->pdev->id); +} + +static void +serial_omap_pm(struct uart_port *port, unsigned int state, +	       unsigned int oldstate) +{ +	struct uart_omap_port *up = (struct uart_omap_port *)port; +	unsigned char efr; + +	dev_dbg(up->port.dev, "serial_omap_pm+%d\n", up->pdev->id); +	serial_out(up, UART_LCR, OMAP_UART_LCR_CONF_MDB); +	efr = serial_in(up, UART_EFR); +	serial_out(up, UART_EFR, 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, OMAP_UART_LCR_CONF_MDB); +	serial_out(up, UART_EFR, efr); +	serial_out(up, UART_LCR, 0); +	/* Enable module level wake up */ +	serial_out(up, UART_OMAP_WER, +		(state != 0) ? OMAP_UART_WER_MOD_WKUP : 0); +} + +static void serial_omap_release_port(struct uart_port *port) +{ +	dev_dbg(port->dev, "serial_omap_release_port+\n"); +} + +static int serial_omap_request_port(struct uart_port *port) +{ +	dev_dbg(port->dev, "serial_omap_request_port+\n"); +	return 0; +} + +static void serial_omap_config_port(struct uart_port *port, int flags) +{ +	struct uart_omap_port *up = (struct uart_omap_port *)port; + +	dev_dbg(up->port.dev, "serial_omap_config_port+%d\n", +							up->pdev->id); +	up->port.type = PORT_OMAP; +} + +static int +serial_omap_verify_port(struct uart_port *port, struct serial_struct *ser) +{ +	/* we don't want the core code to modify any port params */ +	dev_dbg(port->dev, "serial_omap_verify_port+\n"); +	return -EINVAL; +} + +static const char * +serial_omap_type(struct uart_port *port) +{ +	struct uart_omap_port *up = (struct uart_omap_port *)port; + +	dev_dbg(up->port.dev, "serial_omap_type+%d\n", up->pdev->id); +	return up->name; +} + +#ifdef CONFIG_SERIAL_OMAP_CONSOLE + +static struct uart_omap_port *serial_omap_console_ports[4]; + +static struct uart_driver serial_omap_reg; + +#define BOTH_EMPTY (UART_LSR_TEMT | UART_LSR_THRE) + +static inline void wait_for_xmitr(struct uart_omap_port *up) +{ +	unsigned int status, tmout = 10000; + +	/* Wait up to 10ms for the character(s) to be sent. */ +	do { +		status = serial_in(up, UART_LSR); + +		if (status & UART_LSR_BI) +			up->lsr_break_flag = UART_LSR_BI; + +		if (--tmout == 0) +			break; +		udelay(1); +	} while ((status & BOTH_EMPTY) != BOTH_EMPTY); + +	/* Wait up to 1s for flow control if necessary */ +	if (up->port.flags & UPF_CONS_FLOW) { +		tmout = 1000000; +		for (tmout = 1000000; tmout; tmout--) { +			unsigned int msr = serial_in(up, UART_MSR); + +			up->msr_saved_flags |= msr & MSR_SAVE_FLAGS; +			if (msr & UART_MSR_CTS) +				break; + +			udelay(1); +		} +	} +} + +static void serial_omap_console_putchar(struct uart_port *port, int ch) +{ +	struct uart_omap_port *up = (struct uart_omap_port *)port; + +	wait_for_xmitr(up); +	serial_out(up, UART_TX, ch); +} + +static void +serial_omap_console_write(struct console *co, const char *s, +		unsigned int count) +{ +	struct uart_omap_port *up = serial_omap_console_ports[co->index]; +	unsigned long flags; +	unsigned int ier; +	int locked = 1; + +	local_irq_save(flags); +	if (up->port.sysrq) +		locked = 0; +	else if (oops_in_progress) +		locked = spin_trylock(&up->port.lock); +	else +		spin_lock(&up->port.lock); + +	/* +	 * First save the IER then disable the interrupts +	 */ +	ier = serial_in(up, UART_IER); +	serial_out(up, UART_IER, 0); + +	uart_console_write(&up->port, s, count, serial_omap_console_putchar); + +	/* +	 * Finally, wait for transmitter to become empty +	 * and restore the IER +	 */ +	wait_for_xmitr(up); +	serial_out(up, UART_IER, ier); +	/* +	 * The receive handling will happen properly because the +	 * receive ready bit will still be set; it is not cleared +	 * on read.  However, modem control will not, we must +	 * call it if we have saved something in the saved flags +	 * while processing with interrupts off. +	 */ +	if (up->msr_saved_flags) +		check_modem_status(up); + +	if (locked) +		spin_unlock(&up->port.lock); +	local_irq_restore(flags); +} + +static int __init +serial_omap_console_setup(struct console *co, char *options) +{ +	struct uart_omap_port *up; +	int baud = 115200; +	int bits = 8; +	int parity = 'n'; +	int flow = 'n'; + +	if (serial_omap_console_ports[co->index] == NULL) +		return -ENODEV; +	up = serial_omap_console_ports[co->index]; + +	if (options) +		uart_parse_options(options, &baud, &parity, &bits, &flow); + +	return uart_set_options(&up->port, co, baud, parity, bits, flow); +} + +static struct console serial_omap_console = { +	.name		= OMAP_SERIAL_NAME, +	.write		= serial_omap_console_write, +	.device		= uart_console_device, +	.setup		= serial_omap_console_setup, +	.flags		= CON_PRINTBUFFER, +	.index		= -1, +	.data		= &serial_omap_reg, +}; + +static void serial_omap_add_console_port(struct uart_omap_port *up) +{ +	serial_omap_console_ports[up->pdev->id] = up; +} + +#define OMAP_CONSOLE	(&serial_omap_console) + +#else + +#define OMAP_CONSOLE	NULL + +static inline void serial_omap_add_console_port(struct uart_omap_port *up) +{} + +#endif + +static struct uart_ops serial_omap_pops = { +	.tx_empty	= serial_omap_tx_empty, +	.set_mctrl	= serial_omap_set_mctrl, +	.get_mctrl	= serial_omap_get_mctrl, +	.stop_tx	= serial_omap_stop_tx, +	.start_tx	= serial_omap_start_tx, +	.stop_rx	= serial_omap_stop_rx, +	.enable_ms	= serial_omap_enable_ms, +	.break_ctl	= serial_omap_break_ctl, +	.startup	= serial_omap_startup, +	.shutdown	= serial_omap_shutdown, +	.set_termios	= serial_omap_set_termios, +	.pm		= serial_omap_pm, +	.type		= serial_omap_type, +	.release_port	= serial_omap_release_port, +	.request_port	= serial_omap_request_port, +	.config_port	= serial_omap_config_port, +	.verify_port	= serial_omap_verify_port, +}; + +static struct uart_driver serial_omap_reg = { +	.owner		= THIS_MODULE, +	.driver_name	= "OMAP-SERIAL", +	.dev_name	= OMAP_SERIAL_NAME, +	.nr		= OMAP_MAX_HSUART_PORTS, +	.cons		= OMAP_CONSOLE, +}; + +static int +serial_omap_suspend(struct platform_device *pdev, pm_message_t state) +{ +	struct uart_omap_port *up = platform_get_drvdata(pdev); + +	if (up) +		uart_suspend_port(&serial_omap_reg, &up->port); +	return 0; +} + +static int serial_omap_resume(struct platform_device *dev) +{ +	struct uart_omap_port *up = platform_get_drvdata(dev); + +	if (up) +		uart_resume_port(&serial_omap_reg, &up->port); +	return 0; +} + +static void serial_omap_rx_timeout(unsigned long uart_no) +{ +	struct uart_omap_port *up = ui[uart_no]; +	unsigned int curr_dma_pos, curr_transmitted_size; +	unsigned int ret = 0; + +	curr_dma_pos = omap_get_dma_dst_pos(up->uart_dma.rx_dma_channel); +	if ((curr_dma_pos == up->uart_dma.prev_rx_dma_pos) || +			     (curr_dma_pos == 0)) { +		if (jiffies_to_msecs(jiffies - up->port_activity) < +							RX_TIMEOUT) { +			mod_timer(&up->uart_dma.rx_timer, jiffies + +				usecs_to_jiffies(up->uart_dma.rx_timeout)); +		} else { +			serial_omap_stop_rxdma(up); +			up->ier |= (UART_IER_RDI | UART_IER_RLSI); +			serial_out(up, UART_IER, up->ier); +		} +		return; +	} + +	curr_transmitted_size = curr_dma_pos - +					up->uart_dma.prev_rx_dma_pos; +	up->port.icount.rx += curr_transmitted_size; +	tty_insert_flip_string(up->port.state->port.tty, +			up->uart_dma.rx_buf + +			(up->uart_dma.prev_rx_dma_pos - +			up->uart_dma.rx_buf_dma_phys), +			curr_transmitted_size); +	tty_flip_buffer_push(up->port.state->port.tty); +	up->uart_dma.prev_rx_dma_pos = curr_dma_pos; +	if (up->uart_dma.rx_buf_size + +			up->uart_dma.rx_buf_dma_phys == curr_dma_pos) { +		ret = serial_omap_start_rxdma(up); +		if (ret < 0) { +			serial_omap_stop_rxdma(up); +			up->ier |= (UART_IER_RDI | UART_IER_RLSI); +			serial_out(up, UART_IER, up->ier); +		} +	} else  { +		mod_timer(&up->uart_dma.rx_timer, jiffies + +			usecs_to_jiffies(up->uart_dma.rx_timeout)); +	} +	up->port_activity = jiffies; +} + +static void uart_rx_dma_callback(int lch, u16 ch_status, void *data) +{ +	return; +} + +static int serial_omap_start_rxdma(struct uart_omap_port *up) +{ +	int ret = 0; + +	if (up->uart_dma.rx_dma_channel == -1) { +		ret = omap_request_dma(up->uart_dma.uart_dma_rx, +				"UART Rx DMA", +				(void *)uart_rx_dma_callback, up, +				&(up->uart_dma.rx_dma_channel)); +		if (ret < 0) +			return ret; + +		omap_set_dma_src_params(up->uart_dma.rx_dma_channel, 0, +				OMAP_DMA_AMODE_CONSTANT, +				up->uart_dma.uart_base, 0, 0); +		omap_set_dma_dest_params(up->uart_dma.rx_dma_channel, 0, +				OMAP_DMA_AMODE_POST_INC, +				up->uart_dma.rx_buf_dma_phys, 0, 0); +		omap_set_dma_transfer_params(up->uart_dma.rx_dma_channel, +				OMAP_DMA_DATA_TYPE_S8, +				up->uart_dma.rx_buf_size, 1, +				OMAP_DMA_SYNC_ELEMENT, +				up->uart_dma.uart_dma_rx, 0); +	} +	up->uart_dma.prev_rx_dma_pos = up->uart_dma.rx_buf_dma_phys; +	/* FIXME: Cache maintenance needed here? */ +	omap_start_dma(up->uart_dma.rx_dma_channel); +	mod_timer(&up->uart_dma.rx_timer, jiffies + +				usecs_to_jiffies(up->uart_dma.rx_timeout)); +	up->uart_dma.rx_dma_used = true; +	return ret; +} + +static void serial_omap_continue_tx(struct uart_omap_port *up) +{ +	struct circ_buf *xmit = &up->port.state->xmit; +	unsigned int start = up->uart_dma.tx_buf_dma_phys +			+ (xmit->tail & (UART_XMIT_SIZE - 1)); + +	if (uart_circ_empty(xmit)) +		return; + +	up->uart_dma.tx_buf_size = uart_circ_chars_pending(xmit); +	/* +	 * It is a circular buffer. See if the buffer has wounded back. +	 * If yes it will have to be transferred in two separate dma +	 * transfers +	 */ +	if (start + up->uart_dma.tx_buf_size >= +			up->uart_dma.tx_buf_dma_phys + UART_XMIT_SIZE) +		up->uart_dma.tx_buf_size = +			(up->uart_dma.tx_buf_dma_phys + UART_XMIT_SIZE) - start; +	omap_set_dma_dest_params(up->uart_dma.tx_dma_channel, 0, +				OMAP_DMA_AMODE_CONSTANT, +				up->uart_dma.uart_base, 0, 0); +	omap_set_dma_src_params(up->uart_dma.tx_dma_channel, 0, +				OMAP_DMA_AMODE_POST_INC, start, 0, 0); +	omap_set_dma_transfer_params(up->uart_dma.tx_dma_channel, +				OMAP_DMA_DATA_TYPE_S8, +				up->uart_dma.tx_buf_size, 1, +				OMAP_DMA_SYNC_ELEMENT, +				up->uart_dma.uart_dma_tx, 0); +	/* FIXME: Cache maintenance needed here? */ +	omap_start_dma(up->uart_dma.tx_dma_channel); +} + +static void uart_tx_dma_callback(int lch, u16 ch_status, void *data) +{ +	struct uart_omap_port *up = (struct uart_omap_port *)data; +	struct circ_buf *xmit = &up->port.state->xmit; + +	xmit->tail = (xmit->tail + up->uart_dma.tx_buf_size) & \ +			(UART_XMIT_SIZE - 1); +	up->port.icount.tx += up->uart_dma.tx_buf_size; + +	if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS) +		uart_write_wakeup(&up->port); + +	if (uart_circ_empty(xmit)) { +		spin_lock(&(up->uart_dma.tx_lock)); +		serial_omap_stop_tx(&up->port); +		up->uart_dma.tx_dma_used = false; +		spin_unlock(&(up->uart_dma.tx_lock)); +	} else { +		omap_stop_dma(up->uart_dma.tx_dma_channel); +		serial_omap_continue_tx(up); +	} +	up->port_activity = jiffies; +	return; +} + +static int serial_omap_probe(struct platform_device *pdev) +{ +	struct uart_omap_port	*up; +	struct resource		*mem, *irq, *dma_tx, *dma_rx; +	struct omap_uart_port_info *omap_up_info = pdev->dev.platform_data; +	int ret = -ENOSPC; + +	mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); +	if (!mem) { +		dev_err(&pdev->dev, "no mem resource?\n"); +		return -ENODEV; +	} + +	irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0); +	if (!irq) { +		dev_err(&pdev->dev, "no irq resource?\n"); +		return -ENODEV; +	} + +	if (!request_mem_region(mem->start, (mem->end - mem->start) + 1, +				     pdev->dev.driver->name)) { +		dev_err(&pdev->dev, "memory region already claimed\n"); +		return -EBUSY; +	} + +	dma_rx = platform_get_resource_byname(pdev, IORESOURCE_DMA, "rx"); +	if (!dma_rx) { +		ret = -EINVAL; +		goto err; +	} + +	dma_tx = platform_get_resource_byname(pdev, IORESOURCE_DMA, "tx"); +	if (!dma_tx) { +		ret = -EINVAL; +		goto err; +	} + +	up = kzalloc(sizeof(*up), GFP_KERNEL); +	if (up == NULL) { +		ret = -ENOMEM; +		goto do_release_region; +	} +	sprintf(up->name, "OMAP UART%d", pdev->id); +	up->pdev = pdev; +	up->port.dev = &pdev->dev; +	up->port.type = PORT_OMAP; +	up->port.iotype = UPIO_MEM; +	up->port.irq = irq->start; + +	up->port.regshift = 2; +	up->port.fifosize = 64; +	up->port.ops = &serial_omap_pops; +	up->port.line = pdev->id; + +	up->port.membase = omap_up_info->membase; +	up->port.mapbase = omap_up_info->mapbase; +	up->port.flags = omap_up_info->flags; +	up->port.irqflags = omap_up_info->irqflags; +	up->port.uartclk = omap_up_info->uartclk; +	up->uart_dma.uart_base = mem->start; + +	if (omap_up_info->dma_enabled) { +		up->uart_dma.uart_dma_tx = dma_tx->start; +		up->uart_dma.uart_dma_rx = dma_rx->start; +		up->use_dma = 1; +		up->uart_dma.rx_buf_size = 4096; +		up->uart_dma.rx_timeout = 2; +		spin_lock_init(&(up->uart_dma.tx_lock)); +		spin_lock_init(&(up->uart_dma.rx_lock)); +		up->uart_dma.tx_dma_channel = OMAP_UART_DMA_CH_FREE; +		up->uart_dma.rx_dma_channel = OMAP_UART_DMA_CH_FREE; +	} + +	ui[pdev->id] = up; +	serial_omap_add_console_port(up); + +	ret = uart_add_one_port(&serial_omap_reg, &up->port); +	if (ret != 0) +		goto do_release_region; + +	platform_set_drvdata(pdev, up); +	return 0; +err: +	dev_err(&pdev->dev, "[UART%d]: failure [%s]: %d\n", +				pdev->id, __func__, ret); +do_release_region: +	release_mem_region(mem->start, (mem->end - mem->start) + 1); +	return ret; +} + +static int serial_omap_remove(struct platform_device *dev) +{ +	struct uart_omap_port *up = platform_get_drvdata(dev); + +	platform_set_drvdata(dev, NULL); +	if (up) { +		uart_remove_one_port(&serial_omap_reg, &up->port); +		kfree(up); +	} +	return 0; +} + +static struct platform_driver serial_omap_driver = { +	.probe          = serial_omap_probe, +	.remove         = serial_omap_remove, + +	.suspend	= serial_omap_suspend, +	.resume		= serial_omap_resume, +	.driver		= { +		.name	= DRIVER_NAME, +	}, +}; + +static int __init serial_omap_init(void) +{ +	int ret; + +	ret = uart_register_driver(&serial_omap_reg); +	if (ret != 0) +		return ret; +	ret = platform_driver_register(&serial_omap_driver); +	if (ret != 0) +		uart_unregister_driver(&serial_omap_reg); +	return ret; +} + +static void __exit serial_omap_exit(void) +{ +	platform_driver_unregister(&serial_omap_driver); +	uart_unregister_driver(&serial_omap_reg); +} + +module_init(serial_omap_init); +module_exit(serial_omap_exit); + +MODULE_DESCRIPTION("OMAP High Speed UART driver"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Texas Instruments Inc");  |