serial: msm: Add support for UARTDM cores

Add support for the next-generation version of the MSM UART
to the msm_serial driver. This version of the hardware is
similar to the original version, but has some DMA
capabilities that are used in PIO mode in this driver.

Signed-off-by: Stepan Moskovchenko <stepanm@codeaurora.org>
Signed-off-by: David Brown <davidb@codeaurora.org>
diff --git a/drivers/serial/msm_serial.c b/drivers/serial/msm_serial.c
index 8e43a7b..bfee9b4 100644
--- a/drivers/serial/msm_serial.c
+++ b/drivers/serial/msm_serial.c
@@ -3,6 +3,7 @@
  *
  * Copyright (C) 2007 Google, Inc.
  * Author: Robert Love <rlove@google.com>
+ * Copyright (c) 2011, Code Aurora Forum. All rights reserved.
  *
  * This software is licensed under the terms of the GNU General Public
  * License version 2, as published by the Free Software Foundation, and
@@ -31,6 +32,7 @@
 #include <linux/serial.h>
 #include <linux/clk.h>
 #include <linux/platform_device.h>
+#include <linux/delay.h>
 
 #include "msm_serial.h"
 
@@ -38,9 +40,20 @@
 	struct uart_port	uart;
 	char			name[16];
 	struct clk		*clk;
+	struct clk		*pclk;
 	unsigned int		imr;
+	unsigned int            *gsbi_base;
+	int			is_uartdm;
+	unsigned int		old_snap_state;
 };
 
+static inline void wait_for_xmitr(struct uart_port *port, int bits)
+{
+	if (!(msm_read(port, UART_SR) & UART_SR_TX_EMPTY))
+		while ((msm_read(port, UART_ISR) & bits) != bits)
+			cpu_relax();
+}
+
 static void msm_stop_tx(struct uart_port *port)
 {
 	struct msm_port *msm_port = UART_TO_MSM(port);
@@ -73,6 +86,61 @@
 	msm_write(port, msm_port->imr, UART_IMR);
 }
 
+static void handle_rx_dm(struct uart_port *port, unsigned int misr)
+{
+	struct tty_struct *tty = port->state->port.tty;
+	unsigned int sr;
+	int count = 0;
+	struct msm_port *msm_port = UART_TO_MSM(port);
+
+	if ((msm_read(port, UART_SR) & UART_SR_OVERRUN)) {
+		port->icount.overrun++;
+		tty_insert_flip_char(tty, 0, TTY_OVERRUN);
+		msm_write(port, UART_CR_CMD_RESET_ERR, UART_CR);
+	}
+
+	if (misr & UART_IMR_RXSTALE) {
+		count = msm_read(port, UARTDM_RX_TOTAL_SNAP) -
+			msm_port->old_snap_state;
+		msm_port->old_snap_state = 0;
+	} else {
+		count = 4 * (msm_read(port, UART_RFWR));
+		msm_port->old_snap_state += count;
+	}
+
+	/* TODO: Precise error reporting */
+
+	port->icount.rx += count;
+
+	while (count > 0) {
+		unsigned int c;
+
+		sr = msm_read(port, UART_SR);
+		if ((sr & UART_SR_RX_READY) == 0) {
+			msm_port->old_snap_state -= count;
+			break;
+		}
+		c = msm_read(port, UARTDM_RF);
+		if (sr & UART_SR_RX_BREAK) {
+			port->icount.brk++;
+			if (uart_handle_break(port))
+				continue;
+		} else if (sr & UART_SR_PAR_FRAME_ERR)
+			port->icount.frame++;
+
+		/* TODO: handle sysrq */
+		tty_insert_flip_string(tty, (char *) &c,
+				       (count > 4) ? 4 : count);
+		count -= 4;
+	}
+
+	tty_flip_buffer_push(tty);
+	if (misr & (UART_IMR_RXSTALE))
+		msm_write(port, UART_CR_CMD_RESET_STALE_INT, UART_CR);
+	msm_write(port, 0xFFFFFF, UARTDM_DMRX);
+	msm_write(port, UART_CR_CMD_STALE_EVENT_ENABLE, UART_CR);
+}
+
 static void handle_rx(struct uart_port *port)
 {
 	struct tty_struct *tty = port->state->port.tty;
@@ -121,6 +189,12 @@
 	tty_flip_buffer_push(tty);
 }
 
+static void reset_dm_count(struct uart_port *port)
+{
+	wait_for_xmitr(port, UART_ISR_TX_READY);
+	msm_write(port, 1, UARTDM_NCF_TX);
+}
+
 static void handle_tx(struct uart_port *port)
 {
 	struct circ_buf *xmit = &port->state->xmit;
@@ -128,11 +202,18 @@
 	int sent_tx;
 
 	if (port->x_char) {
-		msm_write(port, port->x_char, UART_TF);
+		if (msm_port->is_uartdm)
+			reset_dm_count(port);
+
+		msm_write(port, port->x_char,
+			  msm_port->is_uartdm ? UARTDM_TF : UART_TF);
 		port->icount.tx++;
 		port->x_char = 0;
 	}
 
+	if (msm_port->is_uartdm)
+		reset_dm_count(port);
+
 	while (msm_read(port, UART_SR) & UART_SR_TX_READY) {
 		if (uart_circ_empty(xmit)) {
 			/* disable tx interrupts */
@@ -140,8 +221,11 @@
 			msm_write(port, msm_port->imr, UART_IMR);
 			break;
 		}
+		msm_write(port, xmit->buf[xmit->tail],
+			  msm_port->is_uartdm ? UARTDM_TF : UART_TF);
 
-		msm_write(port, xmit->buf[xmit->tail], UART_TF);
+		if (msm_port->is_uartdm)
+			reset_dm_count(port);
 
 		xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1);
 		port->icount.tx++;
@@ -169,8 +253,12 @@
 	misr = msm_read(port, UART_MISR);
 	msm_write(port, 0, UART_IMR); /* disable interrupt */
 
-	if (misr & (UART_IMR_RXLEV | UART_IMR_RXSTALE))
-		handle_rx(port);
+	if (misr & (UART_IMR_RXLEV | UART_IMR_RXSTALE)) {
+		if (msm_port->is_uartdm)
+			handle_rx_dm(port, misr);
+		else
+			handle_rx(port);
+	}
 	if (misr & UART_IMR_TXLEV)
 		handle_tx(port);
 	if (misr & UART_IMR_DELTA_CTS)
@@ -192,10 +280,21 @@
 	return TIOCM_CAR | TIOCM_CTS | TIOCM_DSR | TIOCM_RTS;
 }
 
-static void msm_set_mctrl(struct uart_port *port, unsigned int mctrl)
+
+static void msm_reset(struct uart_port *port)
+{
+	/* reset everything */
+	msm_write(port, UART_CR_CMD_RESET_RX, UART_CR);
+	msm_write(port, UART_CR_CMD_RESET_TX, UART_CR);
+	msm_write(port, UART_CR_CMD_RESET_ERR, UART_CR);
+	msm_write(port, UART_CR_CMD_RESET_BREAK_INT, UART_CR);
+	msm_write(port, UART_CR_CMD_RESET_CTS, UART_CR);
+	msm_write(port, UART_CR_CMD_SET_RFR, UART_CR);
+}
+
+void msm_set_mctrl(struct uart_port *port, unsigned int mctrl)
 {
 	unsigned int mr;
-
 	mr = msm_read(port, UART_MR1);
 
 	if (!(mctrl & TIOCM_RTS)) {
@@ -219,6 +318,7 @@
 static int msm_set_baud_rate(struct uart_port *port, unsigned int baud)
 {
 	unsigned int baud_code, rxstale, watermark;
+	struct msm_port *msm_port = UART_TO_MSM(port);
 
 	switch (baud) {
 	case 300:
@@ -273,6 +373,9 @@
 		break;
 	}
 
+	if (msm_port->is_uartdm)
+		msm_write(port, UART_CR_CMD_RESET_RX, UART_CR);
+
 	msm_write(port, baud_code, UART_CSR);
 
 	/* RX stale watermark */
@@ -288,25 +391,23 @@
 	/* set TX watermark */
 	msm_write(port, 10, UART_TFWR);
 
+	if (msm_port->is_uartdm) {
+		msm_write(port, UART_CR_CMD_RESET_STALE_INT, UART_CR);
+		msm_write(port, 0xFFFFFF, UARTDM_DMRX);
+		msm_write(port, UART_CR_CMD_STALE_EVENT_ENABLE, UART_CR);
+	}
+
 	return baud;
 }
 
-static void msm_reset(struct uart_port *port)
-{
-	/* reset everything */
-	msm_write(port, UART_CR_CMD_RESET_RX, UART_CR);
-	msm_write(port, UART_CR_CMD_RESET_TX, UART_CR);
-	msm_write(port, UART_CR_CMD_RESET_ERR, UART_CR);
-	msm_write(port, UART_CR_CMD_RESET_BREAK_INT, UART_CR);
-	msm_write(port, UART_CR_CMD_RESET_CTS, UART_CR);
-	msm_write(port, UART_CR_CMD_SET_RFR, UART_CR);
-}
 
 static void msm_init_clock(struct uart_port *port)
 {
 	struct msm_port *msm_port = UART_TO_MSM(port);
 
 	clk_enable(msm_port->clk);
+	if (!IS_ERR(msm_port->pclk))
+		clk_enable(msm_port->pclk);
 	msm_serial_set_mnd_regs(port);
 }
 
@@ -347,15 +448,31 @@
 		msm_write(port, data, UART_IPR);
 	}
 
-	msm_reset(port);
+	data = 0;
+	if (!port->cons || (port->cons && !(port->cons->flags & CON_ENABLED))) {
+		msm_write(port, UART_CR_CMD_PROTECTION_EN, UART_CR);
+		msm_reset(port);
+		data = UART_CR_TX_ENABLE;
+	}
 
-	msm_write(port, 0x05, UART_CR);	/* enable TX & RX */
+	data |= UART_CR_RX_ENABLE;
+	msm_write(port, data, UART_CR);	/* enable TX & RX */
+
+	/* Make sure IPR is not 0 to start with*/
+	if (msm_port->is_uartdm)
+		msm_write(port, UART_IPR_STALE_LSB, UART_IPR);
 
 	/* turn on RX and CTS interrupts */
 	msm_port->imr = UART_IMR_RXLEV | UART_IMR_RXSTALE |
 			UART_IMR_CURRENT_CTS;
-	msm_write(port, msm_port->imr, UART_IMR);
 
+	if (msm_port->is_uartdm) {
+		msm_write(port, 0xFFFFFF, UARTDM_DMRX);
+		msm_write(port, UART_CR_CMD_RESET_STALE_INT, UART_CR);
+		msm_write(port, UART_CR_CMD_STALE_EVENT_ENABLE, UART_CR);
+	}
+
+	msm_write(port, msm_port->imr, UART_IMR);
 	return 0;
 }
 
@@ -384,7 +501,7 @@
 	baud = msm_set_baud_rate(port, baud);
 	if (tty_termios_baud_rate(termios))
 		tty_termios_encode_baud_rate(termios, baud, baud);
-	
+
 	/* calculate parity */
 	mr = msm_read(port, UART_MR2);
 	mr &= ~UART_MR2_PARITY_MODE;
@@ -454,48 +571,105 @@
 static void msm_release_port(struct uart_port *port)
 {
 	struct platform_device *pdev = to_platform_device(port->dev);
-	struct resource *resource;
+	struct msm_port *msm_port = UART_TO_MSM(port);
+	struct resource *uart_resource;
+	struct resource *gsbi_resource;
 	resource_size_t size;
 
-	resource = platform_get_resource(pdev, IORESOURCE_MEM, 0);
-	if (unlikely(!resource))
+	uart_resource = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (unlikely(!uart_resource))
 		return;
-	size = resource->end - resource->start + 1;
+	size = resource_size(uart_resource);
 
 	release_mem_region(port->mapbase, size);
 	iounmap(port->membase);
 	port->membase = NULL;
+
+	if (msm_port->gsbi_base) {
+		iowrite32(GSBI_PROTOCOL_IDLE, msm_port->gsbi_base +
+			  GSBI_CONTROL);
+
+		gsbi_resource = platform_get_resource_byname(pdev,
+							     IORESOURCE_MEM,
+							     "gsbi_resource");
+
+		if (unlikely(!gsbi_resource))
+			return;
+
+		size = resource_size(gsbi_resource);
+		release_mem_region(gsbi_resource->start, size);
+		iounmap(msm_port->gsbi_base);
+		msm_port->gsbi_base = NULL;
+	}
 }
 
 static int msm_request_port(struct uart_port *port)
 {
+	struct msm_port *msm_port = UART_TO_MSM(port);
 	struct platform_device *pdev = to_platform_device(port->dev);
-	struct resource *resource;
+	struct resource *uart_resource;
+	struct resource *gsbi_resource;
 	resource_size_t size;
+	int ret;
 
-	resource = platform_get_resource(pdev, IORESOURCE_MEM, 0);
-	if (unlikely(!resource))
+	uart_resource = platform_get_resource_byname(pdev, IORESOURCE_MEM,
+						     "uart_resource");
+	if (unlikely(!uart_resource))
 		return -ENXIO;
-	size = resource->end - resource->start + 1;
 
-	if (unlikely(!request_mem_region(port->mapbase, size, "msm_serial")))
+	size = resource_size(uart_resource);
+
+	if (!request_mem_region(port->mapbase, size, "msm_serial"))
 		return -EBUSY;
 
 	port->membase = ioremap(port->mapbase, size);
 	if (!port->membase) {
-		release_mem_region(port->mapbase, size);
-		return -EBUSY;
+		ret = -EBUSY;
+		goto fail_release_port;
+	}
+
+	gsbi_resource = platform_get_resource_byname(pdev, IORESOURCE_MEM,
+						     "gsbi_resource");
+	/* Is this a GSBI-based port? */
+	if (gsbi_resource) {
+		size = resource_size(gsbi_resource);
+
+		if (!request_mem_region(gsbi_resource->start, size,
+						 "msm_serial")) {
+			ret = -EBUSY;
+			goto fail_release_port;
+		}
+
+		msm_port->gsbi_base = ioremap(gsbi_resource->start, size);
+		if (!msm_port->gsbi_base) {
+			ret = -EBUSY;
+			goto fail_release_gsbi;
+		}
 	}
 
 	return 0;
+
+fail_release_gsbi:
+	release_mem_region(gsbi_resource->start, size);
+fail_release_port:
+	release_mem_region(port->mapbase, size);
+	return ret;
 }
 
 static void msm_config_port(struct uart_port *port, int flags)
 {
+	struct msm_port *msm_port = UART_TO_MSM(port);
+	int ret;
 	if (flags & UART_CONFIG_TYPE) {
 		port->type = PORT_MSM;
-		msm_request_port(port);
+		ret = msm_request_port(port);
+		if (ret)
+			return;
 	}
+
+	if (msm_port->is_uartdm)
+		iowrite32(GSBI_PROTOCOL_UART, msm_port->gsbi_base +
+			  GSBI_CONTROL);
 }
 
 static int msm_verify_port(struct uart_port *port, struct serial_struct *ser)
@@ -515,9 +689,13 @@
 	switch (state) {
 	case 0:
 		clk_enable(msm_port->clk);
+		if (!IS_ERR(msm_port->pclk))
+			clk_enable(msm_port->pclk);
 		break;
 	case 3:
 		clk_disable(msm_port->clk);
+		if (!IS_ERR(msm_port->pclk))
+			clk_disable(msm_port->pclk);
 		break;
 	default:
 		printk(KERN_ERR "msm_serial: Unknown PM state %d\n", state);
@@ -550,7 +728,7 @@
 			.iotype = UPIO_MEM,
 			.ops = &msm_uart_pops,
 			.flags = UPF_BOOT_AUTOCONF,
-			.fifosize = 512,
+			.fifosize = 64,
 			.line = 0,
 		},
 	},
@@ -559,7 +737,7 @@
 			.iotype = UPIO_MEM,
 			.ops = &msm_uart_pops,
 			.flags = UPF_BOOT_AUTOCONF,
-			.fifosize = 512,
+			.fifosize = 64,
 			.line = 1,
 		},
 	},
@@ -585,9 +763,14 @@
 
 static void msm_console_putchar(struct uart_port *port, int c)
 {
+	struct msm_port *msm_port = UART_TO_MSM(port);
+
+	if (msm_port->is_uartdm)
+		reset_dm_count(port);
+
 	while (!(msm_read(port, UART_SR) & UART_SR_TX_READY))
 		;
-	msm_write(port, c, UART_TF);
+	msm_write(port, c, msm_port->is_uartdm ? UARTDM_TF : UART_TF);
 }
 
 static void msm_console_write(struct console *co, const char *s,
@@ -609,12 +792,14 @@
 static int __init msm_console_setup(struct console *co, char *options)
 {
 	struct uart_port *port;
+	struct msm_port *msm_port;
 	int baud, flow, bits, parity;
 
 	if (unlikely(co->index >= UART_NR || co->index < 0))
 		return -ENXIO;
 
 	port = get_port_from_line(co->index);
+	msm_port = UART_TO_MSM(port);
 
 	if (unlikely(!port->membase))
 		return -ENXIO;
@@ -638,6 +823,11 @@
 
 	msm_reset(port);
 
+	if (msm_port->is_uartdm) {
+		msm_write(port, UART_CR_CMD_PROTECTION_EN, UART_CR);
+		msm_write(port, UART_CR_TX_ENABLE, UART_CR);
+	}
+
 	printk(KERN_INFO "msm_serial: console setup on port #%d\n", port->line);
 
 	return uart_set_options(port, co, baud, parity, bits, flow);
@@ -685,14 +875,32 @@
 	port->dev = &pdev->dev;
 	msm_port = UART_TO_MSM(port);
 
-	msm_port->clk = clk_get(&pdev->dev, "uart_clk");
-	if (IS_ERR(msm_port->clk))
-		return PTR_ERR(msm_port->clk);
+	if (platform_get_resource_byname(pdev, IORESOURCE_MEM, "gsbi_resource"))
+		msm_port->is_uartdm = 1;
+	else
+		msm_port->is_uartdm = 0;
+
+	if (msm_port->is_uartdm) {
+		msm_port->clk = clk_get(&pdev->dev, "gsbi_uart_clk");
+		msm_port->pclk = clk_get(&pdev->dev, "gsbi_pclk");
+	} else {
+		msm_port->clk = clk_get(&pdev->dev, "uart_clk");
+		msm_port->pclk = ERR_PTR(-ENOENT);
+	}
+
+	if (unlikely(IS_ERR(msm_port->clk) || (IS_ERR(msm_port->pclk) &&
+					       msm_port->is_uartdm)))
+			return PTR_ERR(msm_port->clk);
+
+	if (msm_port->is_uartdm)
+		clk_set_rate(msm_port->clk, 7372800);
+
 	port->uartclk = clk_get_rate(msm_port->clk);
 	printk(KERN_INFO "uartclk = %d\n", port->uartclk);
 
 
-	resource = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	resource = platform_get_resource_byname(pdev, IORESOURCE_MEM,
+						     "uart_resource");
 	if (unlikely(!resource))
 		return -ENXIO;
 	port->mapbase = resource->start;