summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHuang Shijie <b32955@freescale.com>2011-12-21 16:34:08 +0800
committerHuang Shijie <b32955@freescale.com>2011-12-26 14:22:58 +0800
commit2ef1d55396f6ce0e6bf61f982b91bf7426fbca5d (patch)
tree5f27a6fc30894059da435e80229f6f320bf05203
parentf51432527ebcea1ec7097fc9a1ffae409b8b5f51 (diff)
ENGR00170901-2 IMX/UART : add a wait queue for DMA RX in SMP
The DMA RX thread may run on another CPU, while the application runs on one CPU. The application will close the uart when it finishes the required reading. But the DMA RX thread maybe still running in this situation. So add a wait queue to synchronize the __TWO__ sides in the SMP. Signed-off-by: Huang Shijie <b32955@freescale.com>
-rw-r--r--drivers/tty/serial/imx.c69
1 files changed, 53 insertions, 16 deletions
diff --git a/drivers/tty/serial/imx.c b/drivers/tty/serial/imx.c
index 76d593c31848..4f6edd9b8718 100644
--- a/drivers/tty/serial/imx.c
+++ b/drivers/tty/serial/imx.c
@@ -213,6 +213,7 @@ struct imx_port {
struct work_struct tsk_dma_rx, tsk_dma_tx;
unsigned int dma_tx_nents;
bool dma_is_rxing;
+ wait_queue_head_t dma_wait;
};
#ifdef CONFIG_IRDA
@@ -328,6 +329,13 @@ static void imx_stop_rx(struct uart_port *port)
struct imx_port *sport = (struct imx_port *)port;
unsigned long temp;
+ /*
+ * We are in SMP now, so if the DMA RX thread is running,
+ * we have to wait for it to finish.
+ */
+ if (sport->enable_dma && sport->dma_is_rxing)
+ return;
+
temp = readl(sport->port.membase + UCR2);
writel(temp &~ UCR2_RXEN, sport->port.membase + UCR2);
}
@@ -748,10 +756,28 @@ static void dma_rx_work(struct work_struct *w)
start_rx_dma(sport);
}
+static void imx_finish_dma(struct imx_port *sport)
+{
+ unsigned long temp;
+
+ /* Enable the interrupt when the RXFIFO is not empty. */
+ temp = readl(sport->port.membase + UCR1);
+ temp |= UCR1_RRDYEN;
+ writel(temp, sport->port.membase + UCR1);
+
+ sport->dma_is_rxing = false;
+ if (waitqueue_active(&sport->dma_wait))
+ wake_up(&sport->dma_wait);
+}
+
/*
- * There are two kinds RX DMA interrupts:
+ * There are three kinds of RX DMA interrupts:
* [1] the RX DMA buffer is full.
- * [2] the Aging timer reached its final value(enabled the UCR4_IDDMAEN).
+ * [2] the Aging timer expires(wait for 8 bytes long)
+ * [3] the Idle Condition Detect(enabled the UCR4_IDDMAEN).
+ *
+ * The [2] and [3] are similar, but [3] is better.
+ * [3] can wait for 32 bytes long, so we do not use [2].
*/
static void dma_rx_callback(void *data)
{
@@ -769,21 +795,20 @@ static void dma_rx_callback(void *data)
/* unmap it first */
dma_unmap_sg(sport->port.dev, sgl, 1, DMA_FROM_DEVICE);
+ /* If we have finish the reading. we will not accept any more data. */
+ if (tty->closing) {
+ imx_finish_dma(sport);
+ return;
+ }
+
status = chan->device->device_tx_status(chan,
(dma_cookie_t)NULL, &state);
count = RX_BUF_SIZE - state.residue;
if (count) {
sport->rx_bytes = count;
schedule_work(&sport->tsk_dma_rx);
- } else {
- unsigned long temp;
-
- /* Enable the interrupt when the RXFIFO is not empty. */
- temp = readl(sport->port.membase + UCR1);
- temp |= UCR1_RRDYEN;
- writel(temp, sport->port.membase + UCR1);
- sport->dma_is_rxing = false;
- }
+ } else
+ imx_finish_dma(sport);
}
static int start_rx_dma(struct imx_port *sport)
@@ -978,6 +1003,7 @@ static int imx_startup(struct uart_port *port)
sport->port.flags |= UPF_LOW_LATENCY;
INIT_WORK(&sport->tsk_dma_tx, dma_tx_work);
INIT_WORK(&sport->tsk_dma_rx, dma_rx_work);
+ init_waitqueue_head(&sport->dma_wait);
}
spin_lock_irqsave(&sport->port.lock, flags);
@@ -989,7 +1015,7 @@ static int imx_startup(struct uart_port *port)
temp = readl(sport->port.membase + UCR1);
temp |= UCR1_RRDYEN | UCR1_RTSDEN | UCR1_UARTEN;
if (sport->enable_dma) {
- temp |= UCR1_RDMAEN | UCR1_TDMAEN | UCR1_ATDMAEN;
+ temp |= UCR1_RDMAEN | UCR1_TDMAEN;
/* ICD, wait for more than 32 frames, but it still to short. */
temp |= UCR1_ICD_REG(3);
}
@@ -1078,6 +1104,13 @@ static void imx_shutdown(struct uart_port *port)
unsigned long temp;
unsigned long flags;
+ if (sport->enable_dma) {
+ /* We have to wait for the DMA to finish. */
+ wait_event(sport->dma_wait, !sport->dma_is_rxing);
+ imx_stop_rx(port);
+ imx_uart_dma_exit(sport);
+ }
+
spin_lock_irqsave(&sport->port.lock, flags);
temp = readl(sport->port.membase + UCR2);
temp &= ~(UCR2_TXEN);
@@ -1116,12 +1149,16 @@ static void imx_shutdown(struct uart_port *port)
temp &= ~(UCR1_TXMPTYEN | UCR1_RRDYEN | UCR1_RTSDEN | UCR1_UARTEN);
if (USE_IRDA(sport))
temp &= ~(UCR1_IREN);
-
+ if (sport->enable_dma)
+ temp &= ~(UCR1_RDMAEN | UCR1_TDMAEN);
writel(temp, sport->port.membase + UCR1);
- spin_unlock_irqrestore(&sport->port.lock, flags);
- if (sport->enable_dma)
- imx_uart_dma_exit(sport);
+ if (sport->enable_dma) {
+ temp = readl(sport->port.membase + UCR4);
+ temp &= ~UCR4_IDDMAEN;
+ writel(temp, sport->port.membase + UCR4);
+ }
+ spin_unlock_irqrestore(&sport->port.lock, flags);
}
static void