Skip to contentSkip to conversation

the eDMA support for the LPUART send driver

PATCH New Yuan Yao 4 participants 8 messages Dec 27, 2013 – Jan 7, 2014

Subsystems (2)
lore
Share & tools
lore

Yuan Yao

author
#0 lore raw

PATCH 1/1 the eDMA support for the LPUART send driver

This patch add eDMA support for LPUART send function.

---
 arch/arm/boot/dts/vf610.dtsi    |  12 +++
 drivers/tty/serial/fsl_lpuart.c | 187 ++++++++++++++++++++++++++++++++--------
 2 files changed, 163 insertions(+), 36 deletions(-)
arch/arm/boot/dts/vf610.dtsi +12 -0
index 365e0fa..35d7a79 100644
@@ -118,6 +118,9 @@
 				interrupts = <0 61 0x00>;
 				clocks = <&clks VF610_CLK_UART0>;
 				clock-names = "ipg";
+				dma-names = "lpuart-rx","lpuart-tx";
+				dmas = <&edma0 0 VF610_EDMA_MUXID0_UART0_RX>,
+				       <&edma0 0 VF610_EDMA_MUXID0_UART0_TX>;
 				status = "disabled";
 			};
 
@@ -127,6 +130,9 @@
 				interrupts = <0 62 0x04>;
 				clocks = <&clks VF610_CLK_UART1>;
 				clock-names = "ipg";
+				dma-names = "lpuart-rx","lpuart-tx";
+				dmas = <&edma0 0 VF610_EDMA_MUXID0_UART1_RX>,
+				       <&edma0 0 VF610_EDMA_MUXID0_UART1_TX>;
 				status = "disabled";
 			};
 
@@ -136,6 +142,9 @@
 				interrupts = <0 63 0x04>;
 				clocks = <&clks VF610_CLK_UART2>;
 				clock-names = "ipg";
+				dma-names = "lpuart-rx","lpuart-tx";
+				dmas = <&edma0 0 VF610_EDMA_MUXID0_UART2_RX>,
+				       <&edma0 0 VF610_EDMA_MUXID0_UART2_TX>;
 				status = "disabled";
 			};
 
@@ -145,6 +154,9 @@
 				interrupts = <0 64 0x04>;
 				clocks = <&clks VF610_CLK_UART3>;
 				clock-names = "ipg";
+				dma-names = "lpuart-rx","lpuart-tx";
+				dmas = <&edma0 0 VF610_EDMA_MUXID0_UART3_RX>,
+				       <&edma0 0 VF610_EDMA_MUXID0_UART3_TX>;
 				status = "disabled";
 			};
 
drivers/tty/serial/fsl_lpuart.c +153 -36

276 lines not on this page · raw message

Signature · 1 line
1.8.4
#1 lore raw

PATCH 1/1 the eDMA support for the LPUART send driver

Quoted 7 lines · Yuan Yao

This patch add eDMA support for LPUART send function.

---
 arch/arm/boot/dts/vf610.dtsi    |  12 +++
 drivers/tty/serial/fsl_lpuart.c | 187 ++++++++++++++++++++++++++++++++--------
 2 files changed, 163 insertions(+), 36 deletions(-)

Not sure if this got applied already, but you are missing the respective change to Documentation/devicetree/bindings/serial/fsl-lpuart.txt.

Please document the dma-names you use here. While you are at it, please also add the "ipg" clock-name.

Arnd

Quoted 10 lines
drivers/tty/serial/fsl_lpuart.c +10 -0
+static int fsl_request_dma(struct uart_port *port)
+{
+	struct lpuart_port *sport = container_of(port,
+					struct lpuart_port, port);
+	struct dma_chan *tx_chan;
+	struct dma_slave_config dma_tx_sconfig;
+	dma_addr_t dma_phys;
+	unsigned char *dma_buf;
+	int ret;
+

drivers/tty/serial/fsl_lpuart.c static void lpuart_setup_watermark(struct lpuart_port *sport) · line 459

Quoted 10 lines
drivers/tty/serial/fsl_lpuart.c +10 -0
+	tx_chan  = dma_request_slave_channel(sport->port.dev, "lpuart-tx");
+
+	if (!tx_chan) {
+		dev_err(sport->port.dev, "Dma TX channel request failed!\n");
+		return -ENODEV;
+	}
+
+	dma_phys = dma_map_single(sport->port.dev,
+				sport->port.state->xmit.buf,
+				UART_XMIT_SIZE, DMA_TO_DEVICE);

This is wrong: Since the dma is performed by the dma engine rather than the uart, the first argument here needs to be the dma device pointer. In fact, dma_map_single is normally supposed to fail on the uart device as the dma_mask value should be zero. Not sure why this worked.

drivers/tty/serial/fsl_lpuart.c static void lpuart_setup_watermark(struct lpuart_port *sport) · line 464

Quoted 4 lines
drivers/tty/serial/fsl_lpuart.c +4 -0
+	if (!dma_phys) {
+		dev_err(sport->port.dev, "Dma_phys single failed\n");
+		return -ENOMEM;
+	}

Please also change all references to "phys" -- it's not a phys address but a bus address. These are often the same, but that's not for the driver to know.

Arnd

#2 lore raw

PATCH 1/1 the eDMA support for the LPUART send driver

Quoted 11 lines · Arnd Bergmann

drivers/tty/serial/fsl_lpuart.c static void lpuart_setup_watermark(struct lpuart_port *sport) · line 459

Quoted 3 lines · Yuan Yao
drivers/tty/serial/fsl_lpuart.c +3 -0
+	dma_phys = dma_map_single(sport->port.dev,
+				sport->port.state->xmit.buf,
+				UART_XMIT_SIZE, DMA_TO_DEVICE);

This is wrong: Since the dma is performed by the dma engine rather than the uart, the first argument here needs to be the dma device pointer. In fact, dma_map_single is normally supposed to fail on the uart device as the dma_mask value should be zero. Not sure why this worked.

Quoted 8 lines

drivers/tty/serial/fsl_lpuart.c static void lpuart_setup_watermark(struct lpuart_port *sport) · line 464

Quoted 4 lines
drivers/tty/serial/fsl_lpuart.c +4 -0
+	if (!dma_phys) {
+		dev_err(sport->port.dev, "Dma_phys single failed\n");
+		return -ENOMEM;
+	}

Please also change all references to "phys" -- it's not a phys address but a bus address. These are often the same, but that's not for the driver to know.

This is also not how you test for failure of dma_map_single(). I recommend reading the documentation for the APIs you're trying to use... Hint: dma_mapping_error().

Signature · 3 lines
FTTC broadband for 0.8mile line: 5.8Mbps down 500kbps up.  Estimation
in database were 13.1 to 19Mbit for a good line, about 7.5+ for a bad.
Estimate before purchase was "up to 13.2Mbit".
author
#3 lore raw

PATCH 1/1 the eDMA support for the LPUART send driver

Thanks for your suggestion. It's important for me.

I read the source code for dma_map_single again. I found that both of them(as first argument is dma or uart) worked well. But in my code I found that the *sport->port.dev->dma_mask is not zero. By contrast the dma_mask for dma device is zero. You are right. There should be dma device. But I also have a doubt here. Why I find many other driver use the first argument here rather than the dma device pointer.
They usually use the device like uart.

-----Original Message-----
From: Russell King - ARM Linux [mailto:linux at arm.linux.org.uk]
Sent: Sunday, January 05, 2014 10:51 PM
To: Yuan Yao-B46683
Cc: Arnd Bergmann; linux-arm-kernel at lists.infradead.org; gregkh at linuxfoundation.org; linux-serial at vger.kernel.org
Subject: Re: [PATCH] the eDMA support for the LPUART send driver

Quoted 11 lines · Arnd Bergmann

drivers/tty/serial/fsl_lpuart.c static void lpuart_setup_watermark(struct lpuart_port *sport) · line 459

Quoted 3 lines · Yuan Yao
drivers/tty/serial/fsl_lpuart.c +3 -0
+	dma_phys = dma_map_single(sport->port.dev,
+				sport->port.state->xmit.buf,
+				UART_XMIT_SIZE, DMA_TO_DEVICE);

This is wrong: Since the dma is performed by the dma engine rather than the uart, the first argument here needs to be the dma device pointer. In fact, dma_map_single is normally supposed to fail on the uart device as the dma_mask value should be zero. Not sure why this worked.

Quoted 8 lines

drivers/tty/serial/fsl_lpuart.c static void lpuart_setup_watermark(struct lpuart_port *sport) · line 464

Quoted 4 lines
drivers/tty/serial/fsl_lpuart.c +4 -0
+	if (!dma_phys) {
+		dev_err(sport->port.dev, "Dma_phys single failed\n");
+		return -ENOMEM;
+	}

Please also change all references to "phys" -- it's not a phys address but a bus address. These are often the same, but that's not for the driver to know.

This is also not how you test for failure of dma_map_single(). I recommend reading the documentation for the APIs you're trying to use... Hint: dma_mapping_error().

--
FTTC broadband for 0.8mile line: 5.8Mbps down 500kbps up. Estimation in database were 13.1 to 19Mbit for a good line, about 7.5+ for a bad. Estimate before purchase was "up to 13.2Mbit".

#4 lore raw

PATCH 1/1 the eDMA support for the LPUART send driver

Quoted 5 lines · Yao Yuan

Thanks for your suggestion. It's important for me.

I read the source code for dma_map_single again. I found that both of them(as first argument is dma or uart) worked well. But in my code I found that the *sport->port.dev->dma_mask is not zero. By contrast the dma_mask for dma device is zero.

Ah, it seems you found two more bugs then ;-)

Quoted 2 lines

You are right. There should be dma device. But I also have a doubt here. Why I find many other driver use the first argument here rather than the dma device pointer.

It's a common mistake. Most other linux device driver interfaces require you to pass the device you are working on, so it's understandable why people come to the wrong conclusion here.

Arnd

author
#5 lore raw

PATCH 1/1 the eDMA support for the LPUART send driver

In reply to Arnd Bergmann

Thanks for your suggestion.

I will fix those errors and also add the "jpg" in the next version.

-----Original Message-----
From: Arnd Bergmann [mailto:arnd at arndb.de]
Sent: Sunday, January 05, 2014 10:45 PM
To: linux-arm-kernel at lists.infradead.org
Cc: Yuan Yao-B46683; gregkh at linuxfoundation.org; linux-serial at vger.kernel.org
Subject: Re: [PATCH] the eDMA support for the LPUART send driver

Quoted 8 lines · Yuan Yao

This patch add eDMA support for LPUART send function.

---
 arch/arm/boot/dts/vf610.dtsi    |  12 +++
 drivers/tty/serial/fsl_lpuart.c | 187 
++++++++++++++++++++++++++++++++--------
 2 files changed, 163 insertions(+), 36 deletions(-)

Not sure if this got applied already, but you are missing the respective change to Documentation/devicetree/bindings/serial/fsl-lpuart.txt.

Please document the dma-names you use here. While you are at it, please also add the "ipg" clock-name.

Arnd

Quoted 9 lines
drivers/tty/serial/fsl_lpuart.c +9 -0
+static int fsl_request_dma(struct uart_port *port) {
+	struct lpuart_port *sport = container_of(port,
+					struct lpuart_port, port);
+	struct dma_chan *tx_chan;
+	struct dma_slave_config dma_tx_sconfig;
+	dma_addr_t dma_phys;
+	unsigned char *dma_buf;
+	int ret;
+

drivers/tty/serial/fsl_lpuart.c static void lpuart_setup_watermark(struct lpuart_port *sport) · line 459

Quoted 10 lines
drivers/tty/serial/fsl_lpuart.c +10 -0
+	tx_chan  = dma_request_slave_channel(sport->port.dev, "lpuart-tx");
+
+	if (!tx_chan) {
+		dev_err(sport->port.dev, "Dma TX channel request failed!\n");
+		return -ENODEV;
+	}
+
+	dma_phys = dma_map_single(sport->port.dev,
+				sport->port.state->xmit.buf,
+				UART_XMIT_SIZE, DMA_TO_DEVICE);

This is wrong: Since the dma is performed by the dma engine rather than the uart, the first argument here needs to be the dma device pointer. In fact, dma_map_single is normally supposed to fail on the uart device as the dma_mask value should be zero. Not sure why this worked.

drivers/tty/serial/fsl_lpuart.c static void lpuart_setup_watermark(struct lpuart_port *sport) · line 464

Quoted 4 lines
drivers/tty/serial/fsl_lpuart.c +4 -0
+	if (!dma_phys) {
+		dev_err(sport->port.dev, "Dma_phys single failed\n");
+		return -ENOMEM;
+	}

Please also change all references to "phys" -- it's not a phys address but a bus address. These are often the same, but that's not for the driver to know.

Arnd

#6 lore raw

PATCH 1/1 the eDMA support for the LPUART send driver

In reply to Yuan Yao
Quoted 6 lines · Yuan Yao

This patch add eDMA support for LPUART send function.

---
 arch/arm/boot/dts/vf610.dtsi    |  12 +++
 drivers/tty/serial/fsl_lpuart.c | 187 ++++++++++++++++++++++++++++++++--------

Please split it into two patches. The vf610.dtsi part should go via arm-soc tree.

Shawn

Quoted 345 lines
 2 files changed, 163 insertions(+), 36 deletions(-)
arch/arm/boot/dts/vf610.dtsi +12 -0
index 365e0fa..35d7a79 100644
@@ -118,6 +118,9 @@
 				interrupts = <0 61 0x00>;
 				clocks = <&clks VF610_CLK_UART0>;
 				clock-names = "ipg";
+				dma-names = "lpuart-rx","lpuart-tx";
+				dmas = <&edma0 0 VF610_EDMA_MUXID0_UART0_RX>,
+				       <&edma0 0 VF610_EDMA_MUXID0_UART0_TX>;
 				status = "disabled";
 			};
 
@@ -127,6 +130,9 @@
 				interrupts = <0 62 0x04>;
 				clocks = <&clks VF610_CLK_UART1>;
 				clock-names = "ipg";
+				dma-names = "lpuart-rx","lpuart-tx";
+				dmas = <&edma0 0 VF610_EDMA_MUXID0_UART1_RX>,
+				       <&edma0 0 VF610_EDMA_MUXID0_UART1_TX>;
 				status = "disabled";
 			};
 
@@ -136,6 +142,9 @@
 				interrupts = <0 63 0x04>;
 				clocks = <&clks VF610_CLK_UART2>;
 				clock-names = "ipg";
+				dma-names = "lpuart-rx","lpuart-tx";
+				dmas = <&edma0 0 VF610_EDMA_MUXID0_UART2_RX>,
+				       <&edma0 0 VF610_EDMA_MUXID0_UART2_TX>;
 				status = "disabled";
 			};
 
@@ -145,6 +154,9 @@
 				interrupts = <0 64 0x04>;
 				clocks = <&clks VF610_CLK_UART3>;
 				clock-names = "ipg";
+				dma-names = "lpuart-rx","lpuart-tx";
+				dmas = <&edma0 0 VF610_EDMA_MUXID0_UART3_RX>,
+				       <&edma0 0 VF610_EDMA_MUXID0_UART3_TX>;
 				status = "disabled";
 			};
 
drivers/tty/serial/fsl_lpuart.c +153 -36
index 8978dc9..8375141 100644
@@ -13,14 +13,19 @@
 #define SUPPORT_SYSRQ
 #endif
 
-#include <linux/module.h>
+#include <linux/clk.h>
+#include <linux/console.h>
+#include <linux/dma-mapping.h>
+#include <linux/dmaengine.h>
+#include <linux/dmapool.h>
 #include <linux/io.h>
 #include <linux/irq.h>
-#include <linux/clk.h>
+#include <linux/module.h>
 #include <linux/of.h>
 #include <linux/of_device.h>
-#include <linux/console.h>
+#include <linux/of_dma.h>
 #include <linux/serial_core.h>
+#include <linux/slab.h>
 #include <linux/tty_flip.h>
 
 /* All registers are 8-bit width */
@@ -112,6 +117,9 @@
 #define UARTSFIFO_TXOF		0x02
 #define UARTSFIFO_RXUF		0x01
 
+#define DMA_MAXBURST		16
+#define DMA_MAXBURST_MASK	(DMA_MAXBURST - 1)
+
 #define DRIVER_NAME	"fsl-lpuart"
 #define DEV_NAME	"ttyLP"
 #define UART_NR		6
@@ -121,6 +129,13 @@ struct lpuart_port {
 	struct clk		*clk;
 	unsigned int		txfifo_size;
 	unsigned int		rxfifo_size;
+	struct dma_chan		*tx_dma_lpuart;
+	struct dma_async_tx_descriptor  *tx_dma_desc;
+	dma_addr_t		tx_dma_buf_phys;
+	dma_cookie_t		tx_cookie;
+	unsigned char		*tx_dma_buf_virt;
+	unsigned int		tx_bytes;
+	int			tx_in_progress;
 };
 
 static struct of_device_id lpuart_dt_ids[] = {
@@ -131,6 +146,9 @@ static struct of_device_id lpuart_dt_ids[] = {
 };
 MODULE_DEVICE_TABLE(of, lpuart_dt_ids);
 
+static int lpuart_dma_tx(struct lpuart_port *sport, unsigned long count);
+static void lpuart_prepare_tx(struct lpuart_port *sport);
+
 static void lpuart_stop_tx(struct uart_port *port)
 {
 	unsigned char temp;
@@ -152,12 +170,36 @@ static void lpuart_enable_ms(struct uart_port *port)
 {
 }
 
-static inline void lpuart_transmit_buffer(struct lpuart_port *sport)
+static void lpuart_dma_tx_complete(void *arg)
+{
+	struct lpuart_port *sport = arg;
+	struct circ_buf *xmit = &sport->port.state->xmit;
+	unsigned long flags;
+
+	async_tx_ack(sport->tx_dma_desc);
+
+	spin_lock_irqsave(&sport->port.lock, flags);
+
+	xmit->tail = (xmit->tail + sport->tx_bytes) & (UART_XMIT_SIZE - 1);
+	sport->tx_in_progress = 0;
+
+	if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)
+		uart_write_wakeup(&sport->port);
+
+	lpuart_prepare_tx(sport);
+
+	spin_unlock_irqrestore(&sport->port.lock, flags);
+}
+
+static void lpuart_pio_tx(struct lpuart_port *sport)
 {
 	struct circ_buf *xmit = &sport->port.state->xmit;
+	unsigned long flags;
+
+	spin_lock_irqsave(&sport->port.lock, flags);
 
 	while (!uart_circ_empty(xmit) &&
-		(readb(sport->port.membase + UARTTCFIFO) < sport->txfifo_size)) {
+		readb(sport->port.membase + UARTTCFIFO) < sport->txfifo_size) {
 		writeb(xmit->buf[xmit->tail], sport->port.membase + UARTDR);
 		xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1);
 		sport->port.icount.tx++;
@@ -167,46 +211,69 @@ static inline void lpuart_transmit_buffer(struct lpuart_port *sport)
 		uart_write_wakeup(&sport->port);
 
 	if (uart_circ_empty(xmit))
-		lpuart_stop_tx(&sport->port);
-}
+		writeb(readb(sport->port.membase + UARTCR5) | UARTCR5_TDMAS,
+			sport->port.membase + UARTCR5);
 
-static void lpuart_start_tx(struct uart_port *port)
+	spin_unlock_irqrestore(&sport->port.lock, flags);
+}
+
+static int lpuart_dma_tx(struct lpuart_port *sport, unsigned long count)
 {
-	struct lpuart_port *sport = container_of(port, struct lpuart_port, port);
-	unsigned char temp;
+	struct circ_buf *xmit = &sport->port.state->xmit;
+	dma_addr_t tx_phys_addr;
+
+	dma_sync_single_for_device(sport->port.dev, sport->tx_dma_buf_phys,
+				UART_XMIT_SIZE, DMA_TO_DEVICE);
+	sport->tx_bytes = count & ~(DMA_MAXBURST_MASK);
+	tx_phys_addr = sport->tx_dma_buf_phys + xmit->tail;
+	sport->tx_dma_desc = dmaengine_prep_slave_single(sport->tx_dma_lpuart,
+					tx_phys_addr, sport->tx_bytes,
+					DMA_MEM_TO_DEV, DMA_PREP_INTERRUPT);
+
+	if (!sport->tx_dma_desc) {
+		dev_err(sport->port.dev, "Not able to get desc for Tx\n");
+		return -EIO;
+	}
 
-	temp = readb(port->membase + UARTCR2);
-	writeb(temp | UARTCR2_TIE, port->membase + UARTCR2);
+	sport->tx_dma_desc->callback = lpuart_dma_tx_complete;
+	sport->tx_dma_desc->callback_param = sport;
+	sport->tx_in_progress = 1;
+	sport->tx_cookie = dmaengine_submit(sport->tx_dma_desc);
+	dma_async_issue_pending(sport->tx_dma_lpuart);
 
-	if (readb(port->membase + UARTSR1) & UARTSR1_TDRE)
-		lpuart_transmit_buffer(sport);
+	return 0;
 }
 
-static irqreturn_t lpuart_txint(int irq, void *dev_id)
+static void lpuart_prepare_tx(struct lpuart_port *sport)
 {
-	struct lpuart_port *sport = dev_id;
 	struct circ_buf *xmit = &sport->port.state->xmit;
-	unsigned long flags;
+	unsigned long count =  CIRC_CNT_TO_END(xmit->head,
+					xmit->tail, UART_XMIT_SIZE);
 
-	spin_lock_irqsave(&sport->port.lock, flags);
-	if (sport->port.x_char) {
-		writeb(sport->port.x_char, sport->port.membase + UARTDR);
-		goto out;
-	}
-
-	if (uart_circ_empty(xmit) || uart_tx_stopped(&sport->port)) {
-		lpuart_stop_tx(&sport->port);
-		goto out;
-	}
+	if (!count)
+		return;
 
-	lpuart_transmit_buffer(sport);
+	if (count < DMA_MAXBURST)
+		writeb(readb(sport->port.membase + UARTCR5) & ~UARTCR5_TDMAS,
+				sport->port.membase + UARTCR5);
+	else
+		lpuart_dma_tx(sport, count);
+}
+
+static void lpuart_start_tx(struct uart_port *port)
+{
+	struct lpuart_port *sport = container_of(port,
+			struct lpuart_port, port);
+	struct circ_buf *xmit = &sport->port.state->xmit;
+	unsigned char temp;
 
-	if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)
-		uart_write_wakeup(&sport->port);
+	temp = readb(port->membase + UARTCR2);
+	writeb(temp | UARTCR2_TIE, port->membase + UARTCR2);
+	temp = readb(port->membase + UARTCR5);
+	writeb(temp | UARTCR5_TDMAS, port->membase + UARTCR5);
 
-out:
-	spin_unlock_irqrestore(&sport->port.lock, flags);
-	return IRQ_HANDLED;
+	if (!uart_circ_empty(xmit) && !sport->tx_in_progress)
+		lpuart_prepare_tx(sport);
 }
 
 static irqreturn_t lpuart_rxint(int irq, void *dev_id)
@@ -283,8 +346,9 @@ static irqreturn_t lpuart_int(int irq, void *dev_id)
 		lpuart_rxint(irq, dev_id);
 
 	if (sts & UARTSR1_TDRE &&
-		!(readb(sport->port.membase + UARTCR5) & UARTCR5_TDMAS))
-		lpuart_txint(irq, dev_id);
+		!(readb(sport->port.membase + UARTCR5) & UARTCR5_TDMAS)) {
+		lpuart_pio_tx(sport);
+	}
 
 	return IRQ_HANDLED;
 }
@@ -366,13 +430,60 @@ static void lpuart_setup_watermark(struct lpuart_port *sport)
 	writeb(UARTCFIFO_TXFLUSH | UARTCFIFO_RXFLUSH,
 			sport->port.membase + UARTCFIFO);
 
-	writeb(2, sport->port.membase + UARTTWFIFO);
+	writeb(0, sport->port.membase + UARTTWFIFO);
 	writeb(1, sport->port.membase + UARTRWFIFO);
 
 	/* Restore cr2 */
 	writeb(cr2_saved, sport->port.membase + UARTCR2);
 }
 
+static int fsl_request_dma(struct uart_port *port)
+{
+	struct lpuart_port *sport = container_of(port,
+					struct lpuart_port, port);
+	struct dma_chan *tx_chan;
+	struct dma_slave_config dma_tx_sconfig;
+	dma_addr_t dma_phys;
+	unsigned char *dma_buf;
+	int ret;
+
+	tx_chan  = dma_request_slave_channel(sport->port.dev, "lpuart-tx");
+
+	if (!tx_chan) {
+		dev_err(sport->port.dev, "Dma TX channel request failed!\n");
+		return -ENODEV;
+	}
+
+	dma_phys = dma_map_single(sport->port.dev,
+				sport->port.state->xmit.buf,
+				UART_XMIT_SIZE, DMA_TO_DEVICE);
+
+	if (!dma_phys) {
+		dev_err(sport->port.dev, "Dma_phys single failed\n");
+		return -ENOMEM;
+	}
+
+	dma_buf = sport->port.state->xmit.buf;
+	dma_tx_sconfig.dst_addr = sport->port.mapbase + UARTDR;
+	dma_tx_sconfig.dst_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE;
+	dma_tx_sconfig.dst_maxburst = DMA_MAXBURST;
+	dma_tx_sconfig.direction = DMA_MEM_TO_DEV;
+	ret = dmaengine_slave_config(tx_chan, &dma_tx_sconfig);
+
+	if (ret < 0) {
+		dev_err(sport->port.dev,
+				"Dma slave config failed, err = %d\n", ret);
+		return ret;
+	}
+
+	sport->tx_dma_lpuart = tx_chan;
+	sport->tx_dma_buf_virt = dma_buf;
+	sport->tx_dma_buf_phys = dma_phys;
+	sport->tx_in_progress = 0;
+
+	return 0;
+}
+
 static int lpuart_startup(struct uart_port *port)
 {
 	struct lpuart_port *sport = container_of(port, struct lpuart_port, port);
@@ -380,6 +491,7 @@ static int lpuart_startup(struct uart_port *port)
 	unsigned long flags;
 	unsigned char temp;
 
+	fsl_request_dma(port);
 	ret = devm_request_irq(port->dev, port->irq, lpuart_int, 0,
 				DRIVER_NAME, sport);
 	if (ret)
@@ -393,6 +505,9 @@ static int lpuart_startup(struct uart_port *port)
 	temp |= (UARTCR2_RIE | UARTCR2_TIE | UARTCR2_RE | UARTCR2_TE);
 	writeb(temp, sport->port.membase + UARTCR2);
 
+	temp = readb(port->membase + UARTCR5);
+	writeb(temp | UARTCR5_TDMAS, port->membase + UARTCR5);
+
 	spin_unlock_irqrestore(&sport->port.lock, flags);
 	return 0;
 }
Signature · 8 lines
1.8.4



_______________________________________________
linux-arm-kernel mailing list
[email protected]
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
author
#7 lore raw

PATCH 1/1 the eDMA support for the LPUART send driver

Sorry for the mistake, I will split it in the next version.

Thanks.

-----Original Message-----
From: Shawn Guo [mailto:shawn.guo at linaro.org]
Sent: Monday, January 06, 2014 1:38 PM
To: Yuan Yao-B46683
Cc: gregkh at linuxfoundation.org; linux-serial at vger.kernel.org; linux-arm-kernel at lists.infradead.org
Subject: Re: [PATCH] the eDMA support for the LPUART send driver

Quoted 7 lines · Yuan Yao

This patch add eDMA support for LPUART send function.

---
 arch/arm/boot/dts/vf610.dtsi    |  12 +++
 drivers/tty/serial/fsl_lpuart.c | 187 

++++++++++++++++++++++++++++++++--------

Please split it into two patches. The vf610.dtsi part should go via arm-soc tree.

Shawn

Quoted 336 lines
 2 files changed, 163 insertions(+), 36 deletions(-)
(quoted diff) +0 -0

b/arch/arm/boot/dts/vf610.dtsi index 365e0fa..35d7a79 100644

arch/arm/boot/dts/vf610.dtsi +12 -0
@@ -118,6 +118,9 @@
 				interrupts = <0 61 0x00>;
 				clocks = <&clks VF610_CLK_UART0>;
 				clock-names = "ipg";
+				dma-names = "lpuart-rx","lpuart-tx";
+				dmas = <&edma0 0 VF610_EDMA_MUXID0_UART0_RX>,
+				       <&edma0 0 VF610_EDMA_MUXID0_UART0_TX>;
 				status = "disabled";
 			};
 
@@ -127,6 +130,9 @@
 				interrupts = <0 62 0x04>;
 				clocks = <&clks VF610_CLK_UART1>;
 				clock-names = "ipg";
+				dma-names = "lpuart-rx","lpuart-tx";
+				dmas = <&edma0 0 VF610_EDMA_MUXID0_UART1_RX>,
+				       <&edma0 0 VF610_EDMA_MUXID0_UART1_TX>;
 				status = "disabled";
 			};
 
@@ -136,6 +142,9 @@
 				interrupts = <0 63 0x04>;
 				clocks = <&clks VF610_CLK_UART2>;
 				clock-names = "ipg";
+				dma-names = "lpuart-rx","lpuart-tx";
+				dmas = <&edma0 0 VF610_EDMA_MUXID0_UART2_RX>,
+				       <&edma0 0 VF610_EDMA_MUXID0_UART2_TX>;
 				status = "disabled";
 			};
 
@@ -145,6 +154,9 @@
 				interrupts = <0 64 0x04>;
 				clocks = <&clks VF610_CLK_UART3>;
 				clock-names = "ipg";
+				dma-names = "lpuart-rx","lpuart-tx";
+				dmas = <&edma0 0 VF610_EDMA_MUXID0_UART3_RX>,
+				       <&edma0 0 VF610_EDMA_MUXID0_UART3_TX>;
 				status = "disabled";
 			};
 
(quoted diff) +0 -0

b/drivers/tty/serial/fsl_lpuart.c index 8978dc9..8375141 100644

drivers/tty/serial/fsl_lpuart.c +18 -3
@@ -13,14 +13,19 @@
 #define SUPPORT_SYSRQ
 #endif
 
-#include <linux/module.h>
+#include <linux/clk.h>
+#include <linux/console.h>
+#include <linux/dma-mapping.h>
+#include <linux/dmaengine.h>
+#include <linux/dmapool.h>
 #include <linux/io.h>
 #include <linux/irq.h>
-#include <linux/clk.h>
+#include <linux/module.h>
 #include <linux/of.h>
 #include <linux/of_device.h>
-#include <linux/console.h>
+#include <linux/of_dma.h>
 #include <linux/serial_core.h>
+#include <linux/slab.h>
 #include <linux/tty_flip.h>
 
 /* All registers are 8-bit width */
@@ -112,6 +117,9 @@
 #define UARTSFIFO_TXOF		0x02
 #define UARTSFIFO_RXUF		0x01
 
+#define DMA_MAXBURST		16
+#define DMA_MAXBURST_MASK	(DMA_MAXBURST - 1)
+
 #define DRIVER_NAME	"fsl-lpuart"
 #define DEV_NAME	"ttyLP"
 #define UART_NR		6
@@ -121,6 +129,13 @@ struct lpuart_port {
 	struct clk		*clk;
 	unsigned int		txfifo_size;
 	unsigned int		rxfifo_size;
+	struct dma_chan		*tx_dma_lpuart;
+	struct dma_async_tx_descriptor  *tx_dma_desc;
+	dma_addr_t		tx_dma_buf_phys;
+	dma_cookie_t		tx_cookie;
+	unsigned char		*tx_dma_buf_virt;
+	unsigned int		tx_bytes;
+	int			tx_in_progress;
 };
 
 static struct of_device_id lpuart_dt_ids[] = { @@ -131,6 +146,9 @@ 

static struct of_device_id lpuart_dt_ids[] = { }; MODULE_DEVICE_TABLE(of, lpuart_dt_ids);

drivers/tty/serial/fsl_lpuart.c +78 -30
+static int lpuart_dma_tx(struct lpuart_port *sport, unsigned long 
+count); static void lpuart_prepare_tx(struct lpuart_port *sport);
+
 static void lpuart_stop_tx(struct uart_port *port)  {
 	unsigned char temp;
@@ -152,12 +170,36 @@ static void lpuart_enable_ms(struct uart_port 
 *port)  {  }
 
-static inline void lpuart_transmit_buffer(struct lpuart_port *sport)
+static void lpuart_dma_tx_complete(void *arg) {
+	struct lpuart_port *sport = arg;
+	struct circ_buf *xmit = &sport->port.state->xmit;
+	unsigned long flags;
+
+	async_tx_ack(sport->tx_dma_desc);
+
+	spin_lock_irqsave(&sport->port.lock, flags);
+
+	xmit->tail = (xmit->tail + sport->tx_bytes) & (UART_XMIT_SIZE - 1);
+	sport->tx_in_progress = 0;
+
+	if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)
+		uart_write_wakeup(&sport->port);
+
+	lpuart_prepare_tx(sport);
+
+	spin_unlock_irqrestore(&sport->port.lock, flags); }
+
+static void lpuart_pio_tx(struct lpuart_port *sport)
 {
 	struct circ_buf *xmit = &sport->port.state->xmit;
+	unsigned long flags;
+
+	spin_lock_irqsave(&sport->port.lock, flags);
 
 	while (!uart_circ_empty(xmit) &&
-		(readb(sport->port.membase + UARTTCFIFO) < sport->txfifo_size)) {
+		readb(sport->port.membase + UARTTCFIFO) < sport->txfifo_size) {
 		writeb(xmit->buf[xmit->tail], sport->port.membase + UARTDR);
 		xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1);
 		sport->port.icount.tx++;
@@ -167,46 +211,69 @@ static inline void lpuart_transmit_buffer(struct lpuart_port *sport)
 		uart_write_wakeup(&sport->port);
 
 	if (uart_circ_empty(xmit))
-		lpuart_stop_tx(&sport->port);
-}
+		writeb(readb(sport->port.membase + UARTCR5) | UARTCR5_TDMAS,
+			sport->port.membase + UARTCR5);
 
-static void lpuart_start_tx(struct uart_port *port)
+	spin_unlock_irqrestore(&sport->port.lock, flags); }
+
+static int lpuart_dma_tx(struct lpuart_port *sport, unsigned long 
+count)
 {
-	struct lpuart_port *sport = container_of(port, struct lpuart_port, port);
-	unsigned char temp;
+	struct circ_buf *xmit = &sport->port.state->xmit;
+	dma_addr_t tx_phys_addr;
+
+	dma_sync_single_for_device(sport->port.dev, sport->tx_dma_buf_phys,
+				UART_XMIT_SIZE, DMA_TO_DEVICE);
+	sport->tx_bytes = count & ~(DMA_MAXBURST_MASK);
+	tx_phys_addr = sport->tx_dma_buf_phys + xmit->tail;
+	sport->tx_dma_desc = dmaengine_prep_slave_single(sport->tx_dma_lpuart,
+					tx_phys_addr, sport->tx_bytes,
+					DMA_MEM_TO_DEV, DMA_PREP_INTERRUPT);
+
+	if (!sport->tx_dma_desc) {
+		dev_err(sport->port.dev, "Not able to get desc for Tx\n");
+		return -EIO;
+	}
 
-	temp = readb(port->membase + UARTCR2);
-	writeb(temp | UARTCR2_TIE, port->membase + UARTCR2);
+	sport->tx_dma_desc->callback = lpuart_dma_tx_complete;
+	sport->tx_dma_desc->callback_param = sport;
+	sport->tx_in_progress = 1;
+	sport->tx_cookie = dmaengine_submit(sport->tx_dma_desc);
+	dma_async_issue_pending(sport->tx_dma_lpuart);
 
-	if (readb(port->membase + UARTSR1) & UARTSR1_TDRE)
-		lpuart_transmit_buffer(sport);
+	return 0;
 }
 
-static irqreturn_t lpuart_txint(int irq, void *dev_id)
+static void lpuart_prepare_tx(struct lpuart_port *sport)
 {
-	struct lpuart_port *sport = dev_id;
 	struct circ_buf *xmit = &sport->port.state->xmit;
-	unsigned long flags;
+	unsigned long count =  CIRC_CNT_TO_END(xmit->head,
+					xmit->tail, UART_XMIT_SIZE);
 
-	spin_lock_irqsave(&sport->port.lock, flags);
-	if (sport->port.x_char) {
-		writeb(sport->port.x_char, sport->port.membase + UARTDR);
-		goto out;
-	}
-
-	if (uart_circ_empty(xmit) || uart_tx_stopped(&sport->port)) {
-		lpuart_stop_tx(&sport->port);
-		goto out;
-	}
+	if (!count)
+		return;
 
-	lpuart_transmit_buffer(sport);
+	if (count < DMA_MAXBURST)
+		writeb(readb(sport->port.membase + UARTCR5) & ~UARTCR5_TDMAS,
+				sport->port.membase + UARTCR5);
+	else
+		lpuart_dma_tx(sport, count);
+}
+
+static void lpuart_start_tx(struct uart_port *port) {
+	struct lpuart_port *sport = container_of(port,
+			struct lpuart_port, port);
+	struct circ_buf *xmit = &sport->port.state->xmit;
+	unsigned char temp;
 
-	if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)
-		uart_write_wakeup(&sport->port);
+	temp = readb(port->membase + UARTCR2);
+	writeb(temp | UARTCR2_TIE, port->membase + UARTCR2);
+	temp = readb(port->membase + UARTCR5);
+	writeb(temp | UARTCR5_TDMAS, port->membase + UARTCR5);
 
-out:
-	spin_unlock_irqrestore(&sport->port.lock, flags);
-	return IRQ_HANDLED;
+	if (!uart_circ_empty(xmit) && !sport->tx_in_progress)
+		lpuart_prepare_tx(sport);
 }
 
 static irqreturn_t lpuart_rxint(int irq, void *dev_id) @@ -283,8 
+346,9 @@ static irqreturn_t lpuart_int(int irq, void *dev_id)
drivers/tty/serial/fsl_lpuart.c +54 -4
 		lpuart_rxint(irq, dev_id);
 
 	if (sts & UARTSR1_TDRE &&
-		!(readb(sport->port.membase + UARTCR5) & UARTCR5_TDMAS))
-		lpuart_txint(irq, dev_id);
+		!(readb(sport->port.membase + UARTCR5) & UARTCR5_TDMAS)) {
+		lpuart_pio_tx(sport);
+	}
 
 	return IRQ_HANDLED;
 }
@@ -366,13 +430,60 @@ static void lpuart_setup_watermark(struct lpuart_port *sport)
 	writeb(UARTCFIFO_TXFLUSH | UARTCFIFO_RXFLUSH,
 			sport->port.membase + UARTCFIFO);
 
-	writeb(2, sport->port.membase + UARTTWFIFO);
+	writeb(0, sport->port.membase + UARTTWFIFO);
 	writeb(1, sport->port.membase + UARTRWFIFO);
 
 	/* Restore cr2 */
 	writeb(cr2_saved, sport->port.membase + UARTCR2);  }
 
+static int fsl_request_dma(struct uart_port *port) {
+	struct lpuart_port *sport = container_of(port,
+					struct lpuart_port, port);
+	struct dma_chan *tx_chan;
+	struct dma_slave_config dma_tx_sconfig;
+	dma_addr_t dma_phys;
+	unsigned char *dma_buf;
+	int ret;
+
+	tx_chan  = dma_request_slave_channel(sport->port.dev, "lpuart-tx");
+
+	if (!tx_chan) {
+		dev_err(sport->port.dev, "Dma TX channel request failed!\n");
+		return -ENODEV;
+	}
+
+	dma_phys = dma_map_single(sport->port.dev,
+				sport->port.state->xmit.buf,
+				UART_XMIT_SIZE, DMA_TO_DEVICE);
+
+	if (!dma_phys) {
+		dev_err(sport->port.dev, "Dma_phys single failed\n");
+		return -ENOMEM;
+	}
+
+	dma_buf = sport->port.state->xmit.buf;
+	dma_tx_sconfig.dst_addr = sport->port.mapbase + UARTDR;
+	dma_tx_sconfig.dst_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE;
+	dma_tx_sconfig.dst_maxburst = DMA_MAXBURST;
+	dma_tx_sconfig.direction = DMA_MEM_TO_DEV;
+	ret = dmaengine_slave_config(tx_chan, &dma_tx_sconfig);
+
+	if (ret < 0) {
+		dev_err(sport->port.dev,
+				"Dma slave config failed, err = %d\n", ret);
+		return ret;
+	}
+
+	sport->tx_dma_lpuart = tx_chan;
+	sport->tx_dma_buf_virt = dma_buf;
+	sport->tx_dma_buf_phys = dma_phys;
+	sport->tx_in_progress = 0;
+
+	return 0;
+}
+
 static int lpuart_startup(struct uart_port *port)  {
 	struct lpuart_port *sport = container_of(port, struct lpuart_port, 
 port); @@ -380,6 +491,7 @@ static int lpuart_startup(struct uart_port *port)
 	unsigned long flags;
 	unsigned char temp;
 
+	fsl_request_dma(port);
 	ret = devm_request_irq(port->dev, port->irq, lpuart_int, 0,
 				DRIVER_NAME, sport);
 	if (ret)
@@ -393,6 +505,9 @@ static int lpuart_startup(struct uart_port *port)
 	temp |= (UARTCR2_RIE | UARTCR2_TIE | UARTCR2_RE | UARTCR2_TE);
 	writeb(temp, sport->port.membase + UARTCR2);
 
+	temp = readb(port->membase + UARTCR5);
+	writeb(temp | UARTCR5_TDMAS, port->membase + UARTCR5);
+
 	spin_unlock_irqrestore(&sport->port.lock, flags);
 	return 0;
 }
--

1.8.4

_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel at lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel