msm_serial_hs: Add flush state machine for UART Tx Transfer
Handle below error cases with UART Tx transfer:
1. Application doing I/O to UART port and is being terminated abnormally.
2. Used ADM Channel for UART TX transfer stalls.
In both above conditions, queued Tx command to ADM is not flushed. Hence
restarting of application shows list corruption for queueing next UART
Tx command to ADM. This change fixes it by having state machine for UART
Tx, identifying such condition and flushing the queue Tx command before
closing the UART device.
Change-Id: I4e567a539d890dffed50b5837dde58d5cd62d641
Signed-off-by: Mayank Rana <mrana@codeaurora.org>
diff --git a/drivers/tty/serial/msm_serial_hs.c b/drivers/tty/serial/msm_serial_hs.c
index 21b9669..083ed19 100644
--- a/drivers/tty/serial/msm_serial_hs.c
+++ b/drivers/tty/serial/msm_serial_hs.c
@@ -95,6 +95,8 @@
struct msm_hs_tx {
unsigned int tx_ready_int_en; /* ok to dma more tx */
unsigned int dma_in_flight; /* tx dma in progress */
+ enum flush_reason flush;
+ wait_queue_head_t wait;
struct msm_dmov_cmd xfer;
dmov_box *command_ptr;
u32 *command_ptr_ptr;
@@ -895,7 +897,7 @@
dma_sync_single_for_device(uport->dev, tx->mapped_cmd_ptr_ptr,
sizeof(u32), DMA_TO_DEVICE);
-
+ msm_uport->tx.flush = FLUSH_NONE;
msm_dmov_enqueue_cmd(msm_uport->dma_tx_channel, &tx->xfer);
}
@@ -1115,9 +1117,13 @@
{
struct msm_hs_port *msm_uport;
- WARN_ON(result != 0x80000002); /* DMA did not finish properly */
-
msm_uport = container_of(cmd_ptr, struct msm_hs_port, tx.xfer);
+ if (msm_uport->tx.flush == FLUSH_STOP)
+ /* DMA FLUSH unsuccesfful */
+ WARN_ON(!(result & DMOV_RSLT_FLUSH));
+ else
+ /* DMA did not finish properly */
+ WARN_ON(!(result & DMOV_RSLT_DONE));
tasklet_schedule(&msm_uport->tx.tlet);
}
@@ -1129,6 +1135,12 @@
tlet_ptr, struct msm_hs_port, tx.tlet);
spin_lock_irqsave(&(msm_uport->uport.lock), flags);
+ if (msm_uport->tx.flush == FLUSH_STOP) {
+ msm_uport->tx.flush = FLUSH_SHUTDOWN;
+ wake_up(&msm_uport->tx.wait);
+ spin_unlock_irqrestore(&(msm_uport->uport.lock), flags);
+ return;
+ }
msm_uport->imr_reg |= UARTDM_ISR_TX_READY_BMSK;
msm_hs_write(&(msm_uport->uport), UARTDM_IMR_ADDR, msm_uport->imr_reg);
@@ -1770,6 +1782,7 @@
tx->xfer.cmdptr = DMOV_CMD_ADDR(tx->mapped_cmd_ptr_ptr);
init_waitqueue_head(&rx->wait);
+ init_waitqueue_head(&tx->wait);
wake_lock_init(&rx->wake_lock, WAKE_LOCK_SUSPEND, "msm_serial_hs_rx");
wake_lock_init(&msm_uport->dma_wake_lock, WAKE_LOCK_SUSPEND,
"msm_serial_hs_dma");
@@ -2043,19 +2056,41 @@
*/
static void msm_hs_shutdown(struct uart_port *uport)
{
+ int ret;
+ unsigned int data;
+ unsigned long flags;
struct msm_hs_port *msm_uport = UARTDM_TO_MSM(uport);
- BUG_ON(msm_uport->rx.flush < FLUSH_STOP);
+ if (msm_uport->tx.dma_in_flight) {
+ spin_lock_irqsave(&uport->lock, flags);
+ /* disable UART TX interface to DM */
+ data = msm_hs_read(uport, UARTDM_DMEN_ADDR);
+ data &= ~UARTDM_TX_DM_EN_BMSK;
+ msm_hs_write(uport, UARTDM_DMEN_ADDR, data);
+ /* turn OFF UART Transmitter */
+ msm_hs_write(uport, UARTDM_CR_ADDR, UARTDM_CR_TX_DISABLE_BMSK);
+ /* reset UART TX */
+ msm_hs_write(uport, UARTDM_CR_ADDR, RESET_TX);
+ /* reset UART TX Error */
+ msm_hs_write(uport, UARTDM_CR_ADDR, RESET_TX_ERROR);
+ msm_uport->tx.flush = FLUSH_STOP;
+ spin_unlock_irqrestore(&uport->lock, flags);
+ /* discard flush */
+ msm_dmov_flush(msm_uport->dma_tx_channel, 0);
+ ret = wait_event_timeout(msm_uport->tx.wait,
+ msm_uport->tx.flush == FLUSH_SHUTDOWN, 100);
+ if (!ret)
+ pr_err("%s():HSUART TX Stalls.\n", __func__);
+ }
tasklet_kill(&msm_uport->tx.tlet);
+ BUG_ON(msm_uport->rx.flush < FLUSH_STOP);
wait_event(msm_uport->rx.wait, msm_uport->rx.flush == FLUSH_SHUTDOWN);
tasklet_kill(&msm_uport->rx.tlet);
cancel_delayed_work_sync(&msm_uport->rx.flip_insert_work);
-
flush_workqueue(msm_uport->hsuart_wq);
pm_runtime_disable(uport->dev);
pm_runtime_set_suspended(uport->dev);
- mutex_lock(&msm_uport->clk_mutex);
/* Disable the transmitter */
msm_hs_write(uport, UARTDM_CR_ADDR, UARTDM_CR_TX_DISABLE_BMSK);
/* Disable the receiver */
@@ -2068,7 +2103,6 @@
* Hence mb() requires here.
*/
mb();
-
if (msm_uport->clk_state != MSM_HS_CLK_OFF) {
/* to balance clk_state */
clk_disable_unprepare(msm_uport->clk);
@@ -2076,8 +2110,8 @@
clk_disable_unprepare(msm_uport->pclk);
wake_unlock(&msm_uport->dma_wake_lock);
}
- msm_uport->clk_state = MSM_HS_CLK_PORT_OFF;
+ msm_uport->clk_state = MSM_HS_CLK_PORT_OFF;
dma_unmap_single(uport->dev, msm_uport->tx.dma_base,
UART_XMIT_SIZE, DMA_TO_DEVICE);
@@ -2088,7 +2122,6 @@
free_irq(uport->irq, msm_uport);
if (use_low_power_wakeup(msm_uport))
free_irq(msm_uport->wakeup.irq, msm_uport);
- mutex_unlock(&msm_uport->clk_mutex);
mutex_destroy(&msm_uport->clk_mutex);
}