Merge "tty: serial: qcom_geni_serial: Fix wrap around of TX buffer"
diff --git a/drivers/tty/serial/msm_geni_serial.c b/drivers/tty/serial/msm_geni_serial.c
index 79cad3f..02dd8e4 100644
--- a/drivers/tty/serial/msm_geni_serial.c
+++ b/drivers/tty/serial/msm_geni_serial.c
@@ -176,6 +176,7 @@
 	int edge_count;
 	bool manual_flow;
 	struct msm_geni_serial_ver_info ver_info;
+	u32 cur_tx_remaining;
 };
 
 static const struct uart_ops msm_geni_serial_pops;
@@ -781,6 +782,7 @@
 	struct msm_geni_serial_port *port;
 	bool locked = true;
 	unsigned long flags;
+	unsigned int geni_status;
 
 	WARN_ON(co->index < 0 || co->index >= GENI_UART_NR_PORTS);
 
@@ -794,6 +796,8 @@
 	else
 		spin_lock_irqsave(&uport->lock, flags);
 
+	geni_status = readl_relaxed(uport->membase + SE_GENI_STATUS);
+
 	/* Cancel the current write to log the fault */
 	if (!locked) {
 		geni_cancel_m_cmd(uport->membase);
@@ -807,9 +811,17 @@
 		}
 		writel_relaxed(M_CMD_CANCEL_EN, uport->membase +
 							SE_GENI_M_IRQ_CLEAR);
-	}
+	} else if ((geni_status & M_GENI_CMD_ACTIVE) && !port->cur_tx_remaining)
+		/* It seems we can interrupt existing transfers unless all data
+		 * has been sent, in which case we need to look for done first.
+		 */
+		msm_geni_serial_poll_cancel_tx(uport);
 
 	__msm_geni_serial_console_write(uport, s, count);
+
+	if (port->cur_tx_remaining)
+		msm_geni_serial_setup_tx(uport, port->cur_tx_remaining);
+
 	if (locked)
 		spin_unlock_irqrestore(&uport->lock, flags);
 }
@@ -1275,13 +1287,15 @@
 	return ret;
 }
 
-static int msm_geni_serial_handle_tx(struct uart_port *uport)
+static int msm_geni_serial_handle_tx(struct uart_port *uport, bool done,
+		bool active)
 {
 	int ret = 0;
 	struct msm_geni_serial_port *msm_port = GET_DEV_PORT(uport);
 	struct circ_buf *xmit = &uport->state->xmit;
 	unsigned int avail_fifo_bytes = 0;
 	unsigned int bytes_remaining = 0;
+	unsigned int pending;
 	int i = 0;
 	unsigned int tx_fifo_status;
 	unsigned int xmit_size;
@@ -1289,28 +1303,39 @@
 		(uart_console(uport) ? 1 : (msm_port->tx_fifo_width >> 3));
 	int temp_tail = 0;
 
-	xmit_size = uart_circ_chars_pending(xmit);
 	tx_fifo_status = geni_read_reg_nolog(uport->membase,
 					SE_GENI_TX_FIFO_STATUS);
-	/* Both FIFO and framework buffer are drained */
-	if (!xmit_size && !tx_fifo_status) {
+
+	/* Complete the current tx command before taking newly added data */
+	if (active)
+		pending = msm_port->cur_tx_remaining;
+	else
+		pending = uart_circ_chars_pending(xmit);
+
+	/* All data has been transmitted and acknowledged as received */
+	if (!pending && !tx_fifo_status && done) {
 		msm_geni_serial_stop_tx(uport);
 		goto exit_handle_tx;
 	}
 
-	avail_fifo_bytes = (msm_port->tx_fifo_depth - msm_port->tx_wm) *
-							fifo_width_bytes;
-	temp_tail = xmit->tail & (UART_XMIT_SIZE - 1);
+	avail_fifo_bytes = msm_port->tx_fifo_depth - (tx_fifo_status &
+								TX_FIFO_WC);
+	avail_fifo_bytes *= fifo_width_bytes;
+	if (avail_fifo_bytes < 0)
+		avail_fifo_bytes = 0;
 
-	if (xmit_size > (UART_XMIT_SIZE - temp_tail))
-		xmit_size = (UART_XMIT_SIZE - temp_tail);
-	if (xmit_size > avail_fifo_bytes)
-		xmit_size = avail_fifo_bytes;
+	temp_tail = xmit->tail;
+	xmit_size = min(avail_fifo_bytes, pending);
 	if (!xmit_size)
 		goto exit_handle_tx;
 
-	msm_geni_serial_setup_tx(uport, xmit_size);
+	if (!msm_port->cur_tx_remaining) {
+		msm_geni_serial_setup_tx(uport, pending);
+		msm_port->cur_tx_remaining = pending;
+	}
+
 	bytes_remaining = xmit_size;
+
 	while (i < xmit_size) {
 		unsigned int tx_bytes;
 		unsigned int buf = 0;
@@ -1319,19 +1344,21 @@
 		tx_bytes = ((bytes_remaining < fifo_width_bytes) ?
 					bytes_remaining : fifo_width_bytes);
 
-		for (c = 0; c < tx_bytes ; c++)
-			buf |= (xmit->buf[temp_tail + c] << (c * 8));
+		for (c = 0; c < tx_bytes ; c++) {
+			buf |= (xmit->buf[temp_tail++] << (c * 8));
+			temp_tail &= UART_XMIT_SIZE - 1;
+		}
+
 		geni_write_reg_nolog(buf, uport->membase, SE_GENI_TX_FIFOn);
+
 		i += tx_bytes;
 		bytes_remaining -= tx_bytes;
 		uport->icount.tx += tx_bytes;
-		temp_tail += tx_bytes;
+		msm_port->cur_tx_remaining -= tx_bytes;
 		/* Ensure FIFO write goes through */
 		wmb();
 	}
-	xmit->tail = temp_tail & (UART_XMIT_SIZE - 1);
-	if (uart_console(uport))
-		msm_geni_serial_poll_cancel_tx(uport);
+	xmit->tail = temp_tail;
 exit_handle_tx:
 	if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)
 		uart_write_wakeup(uport);
@@ -1428,6 +1455,7 @@
 	struct uart_port *uport = dev;
 	unsigned long flags;
 	unsigned int m_irq_en;
+	unsigned int geni_status;
 	struct msm_geni_serial_port *msm_port = GET_DEV_PORT(uport);
 	struct tty_port *tport = &uport->state->port;
 	bool drop_rx = false;
@@ -1449,6 +1477,7 @@
 	dma = geni_read_reg_nolog(uport->membase, SE_GENI_DMA_MODE_EN);
 	dma_tx_status = geni_read_reg_nolog(uport->membase, SE_DMA_TX_IRQ_STAT);
 	dma_rx_status = geni_read_reg_nolog(uport->membase, SE_DMA_RX_IRQ_STAT);
+	geni_status = readl_relaxed(uport->membase + SE_GENI_STATUS);
 
 	geni_write_reg_nolog(m_irq_status, uport->membase, SE_GENI_M_IRQ_CLEAR);
 	geni_write_reg_nolog(s_irq_status, uport->membase, SE_GENI_S_IRQ_CLEAR);
@@ -1469,7 +1498,9 @@
 	if (!dma) {
 		if ((m_irq_status & m_irq_en) &
 		    (M_TX_FIFO_WATERMARK_EN | M_CMD_DONE_EN))
-			msm_geni_serial_handle_tx(uport);
+			msm_geni_serial_handle_tx(uport,
+					m_irq_status & M_CMD_DONE_EN,
+					geni_status & M_GENI_CMD_ACTIVE);
 
 		if ((s_irq_status & S_GP_IRQ_0_EN) ||
 			(s_irq_status & S_GP_IRQ_1_EN)) {