msm_serial: Add support for poll_{get,put}_char()

Implement the polling functionality for the MSM serial driver.
This allows us to use KGDB on this hardware.

Cc: David Brown <davidb@codeaurora.org>
Signed-off-by: Stephen Boyd <sboyd@codeaurora.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
diff --git a/drivers/tty/serial/msm_serial.c b/drivers/tty/serial/msm_serial.c
index b5d779c..053b98e 100644
--- a/drivers/tty/serial/msm_serial.c
+++ b/drivers/tty/serial/msm_serial.c
@@ -39,6 +39,13 @@
 
 #include "msm_serial.h"
 
+enum {
+	UARTDM_1P1 = 1,
+	UARTDM_1P2,
+	UARTDM_1P3,
+	UARTDM_1P4,
+};
+
 struct msm_port {
 	struct uart_port	uart;
 	char			name[16];
@@ -309,6 +316,8 @@
 
 static void msm_reset(struct uart_port *port)
 {
+	struct msm_port *msm_port = UART_TO_MSM(port);
+
 	/* reset everything */
 	msm_write(port, UART_CR_CMD_RESET_RX, UART_CR);
 	msm_write(port, UART_CR_CMD_RESET_TX, UART_CR);
@@ -316,6 +325,10 @@
 	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);
+
+	/* Disable DM modes */
+	if (msm_port->is_uartdm)
+		msm_write(port, 0, UARTDM_DMEN);
 }
 
 static void msm_set_mctrl(struct uart_port *port, unsigned int mctrl)
@@ -711,6 +724,117 @@
 	}
 }
 
+#ifdef CONFIG_CONSOLE_POLL
+static int msm_poll_init(struct uart_port *port)
+{
+	struct msm_port *msm_port = UART_TO_MSM(port);
+
+	/* Enable single character mode on RX FIFO */
+	if (msm_port->is_uartdm >= UARTDM_1P4)
+		msm_write(port, UARTDM_DMEN_RX_SC_ENABLE, UARTDM_DMEN);
+
+	return 0;
+}
+
+static int msm_poll_get_char_single(struct uart_port *port)
+{
+	struct msm_port *msm_port = UART_TO_MSM(port);
+	unsigned int rf_reg = msm_port->is_uartdm ? UARTDM_RF : UART_RF;
+
+	if (!(msm_read(port, UART_SR) & UART_SR_RX_READY))
+		return NO_POLL_CHAR;
+	else
+		return msm_read(port, rf_reg) & 0xff;
+}
+
+static int msm_poll_get_char_dm_1p3(struct uart_port *port)
+{
+	int c;
+	static u32 slop;
+	static int count;
+	unsigned char *sp = (unsigned char *)&slop;
+
+	/* Check if a previous read had more than one char */
+	if (count) {
+		c = sp[sizeof(slop) - count];
+		count--;
+	/* Or if FIFO is empty */
+	} else if (!(msm_read(port, UART_SR) & UART_SR_RX_READY)) {
+		/*
+		 * If RX packing buffer has less than a word, force stale to
+		 * push contents into RX FIFO
+		 */
+		count = msm_read(port, UARTDM_RXFS);
+		count = (count >> UARTDM_RXFS_BUF_SHIFT) & UARTDM_RXFS_BUF_MASK;
+		if (count) {
+			msm_write(port, UART_CR_CMD_FORCE_STALE, UART_CR);
+			slop = msm_read(port, UARTDM_RF);
+			c = sp[0];
+			count--;
+		} else {
+			c = NO_POLL_CHAR;
+		}
+	/* FIFO has a word */
+	} else {
+		slop = msm_read(port, UARTDM_RF);
+		c = sp[0];
+		count = sizeof(slop) - 1;
+	}
+
+	return c;
+}
+
+static int msm_poll_get_char(struct uart_port *port)
+{
+	u32 imr;
+	int c;
+	struct msm_port *msm_port = UART_TO_MSM(port);
+
+	/* Disable all interrupts */
+	imr = msm_read(port, UART_IMR);
+	msm_write(port, 0, UART_IMR);
+
+	if (msm_port->is_uartdm == UARTDM_1P3)
+		c = msm_poll_get_char_dm_1p3(port);
+	else
+		c = msm_poll_get_char_single(port);
+
+	/* Enable interrupts */
+	msm_write(port, imr, UART_IMR);
+
+	return c;
+}
+
+static void msm_poll_put_char(struct uart_port *port, unsigned char c)
+{
+	u32 imr;
+	struct msm_port *msm_port = UART_TO_MSM(port);
+
+	/* Disable all interrupts */
+	imr = msm_read(port, UART_IMR);
+	msm_write(port, 0, UART_IMR);
+
+	if (msm_port->is_uartdm)
+		reset_dm_count(port, 1);
+
+	/* Wait until FIFO is empty */
+	while (!(msm_read(port, UART_SR) & UART_SR_TX_READY))
+		cpu_relax();
+
+	/* Write a character */
+	msm_write(port, c, msm_port->is_uartdm ? UARTDM_TF : UART_TF);
+
+	/* Wait until FIFO is empty */
+	while (!(msm_read(port, UART_SR) & UART_SR_TX_READY))
+		cpu_relax();
+
+	/* Enable interrupts */
+	msm_write(port, imr, UART_IMR);
+
+	return;
+}
+#endif
+
 static struct uart_ops msm_uart_pops = {
 	.tx_empty = msm_tx_empty,
 	.set_mctrl = msm_set_mctrl,
@@ -729,6 +853,11 @@
 	.config_port = msm_config_port,
 	.verify_port = msm_verify_port,
 	.pm = msm_power,
+#ifdef CONFIG_CONSOLE_POLL
+	.poll_init = msm_poll_init,
+	.poll_get_char	= msm_poll_get_char,
+	.poll_put_char	= msm_poll_put_char,
+#endif
 };
 
 static struct msm_port msm_uart_ports[] = {
@@ -900,7 +1029,10 @@
 static atomic_t msm_uart_next_id = ATOMIC_INIT(0);
 
 static const struct of_device_id msm_uartdm_table[] = {
-	{ .compatible = "qcom,msm-uartdm" },
+	{ .compatible = "qcom,msm-uartdm-v1.1", .data = (void *)UARTDM_1P1 },
+	{ .compatible = "qcom,msm-uartdm-v1.2", .data = (void *)UARTDM_1P2 },
+	{ .compatible = "qcom,msm-uartdm-v1.3", .data = (void *)UARTDM_1P3 },
+	{ .compatible = "qcom,msm-uartdm-v1.4", .data = (void *)UARTDM_1P4 },
 	{ }
 };
 
@@ -909,6 +1041,7 @@
 	struct msm_port *msm_port;
 	struct resource *resource;
 	struct uart_port *port;
+	const struct of_device_id *id;
 	int irq;
 
 	if (pdev->id == -1)
@@ -923,8 +1056,9 @@
 	port->dev = &pdev->dev;
 	msm_port = UART_TO_MSM(port);
 
-	if (of_match_device(msm_uartdm_table, &pdev->dev))
-		msm_port->is_uartdm = 1;
+	id = of_match_device(msm_uartdm_table, &pdev->dev);
+	if (id)
+		msm_port->is_uartdm = (unsigned long)id->data;
 	else
 		msm_port->is_uartdm = 0;