Skip to contentSkip to conversation

add DMA support to uart

PATCH New Huang Shijie 7 participants 18 messages Apr 26, 2012 – May 16, 2012

Subsystems (3)
lore
Share & tools
lore
#0 lore raw

cover add DMA support to uart

Add DMA support to UART driver.

Tested this patch on imx6q(arm2) board with a new SDMA firmware.

Huang Shijie (2):
serial/imx: add DMA support
ARM: MX6q: enable DMA support for UART2

 .../bindings/tty/serial/fsl-imx-uart.txt           |    7 +
 arch/arm/boot/dts/imx6q-arm2.dts                   |    8 +
 drivers/tty/serial/imx.c                           |  386 +++++++++++++++++++-
 3 files changed, 397 insertions(+), 4 deletions(-)
#1 lore raw

PATCH 1/2 serial/imx: add DMA support

Add the DMA support for uart RX and TX.

---
 .../bindings/tty/serial/fsl-imx-uart.txt           |    7 +
 drivers/tty/serial/imx.c                           |  386 +++++++++++++++++++-
 2 files changed, 389 insertions(+), 4 deletions(-)
Documentation/devicetree/bindings/tty/serial/fsl-imx-uart.txt +7 -0
index a9c0406..f27489d 100644
@@ -8,6 +8,10 @@ Required properties:
 Optional properties:
 - fsl,uart-has-rtscts : Indicate the uart has rts and cts
 - fsl,irda-mode : Indicate the uart supports irda mode
+- fsl,enable-dma : Indicate the uart supports DMA
+- fsl,uart-dma-events : contains the DMA events for RX and TX,
+	          The first is the RX event, while the other is TX.
+- fsl,enable-dte: Indicate the uart works in DTE mode
 
 Example:
 
@@ -16,4 +20,7 @@ uart@73fbc000 {
 	reg = <0x73fbc000 0x4000>;
 	interrupts = <31>;
 	fsl,uart-has-rtscts;
+	fsl,enable-dma;
+	fsl,uart-dma-events = <xx xx>;
+	fsl,enable-dte;
 };
drivers/tty/serial/imx.c +382 -4

517 lines not on this page · raw message

Signature · 1 line
1.7.0.4
#2 lore raw

PATCH 1/2 serial/imx: add DMA support

Quoted 4 lines · Huang Shijie

Add the DMA support for uart RX and TX.

---

Apart from the comments below,

1. How do you deal with transmitting the high-priority XON/XOFF characters (port->x_char) which occur with s/w flow control and the tty buffers fill up?
2. How do you deal with flow control in general? IOW, what happens when the remote end deasserts your CTS with h/w flow control enabled. How does your end deal with sending RTS according to flow control conditions?

Quoted 79 lines
 .../bindings/tty/serial/fsl-imx-uart.txt           |    7 +
 drivers/tty/serial/imx.c                           |  386 +++++++++++++++++++-
 2 files changed, 389 insertions(+), 4 deletions(-)
Documentation/devicetree/bindings/tty/serial/fsl-imx-uart.txt +7 -0
index a9c0406..f27489d 100644
@@ -8,6 +8,10 @@ Required properties:
 Optional properties:
 - fsl,uart-has-rtscts : Indicate the uart has rts and cts
 - fsl,irda-mode : Indicate the uart supports irda mode
+- fsl,enable-dma : Indicate the uart supports DMA
+- fsl,uart-dma-events : contains the DMA events for RX and TX,
+	          The first is the RX event, while the other is TX.
+- fsl,enable-dte: Indicate the uart works in DTE mode
 
 Example:
 
@@ -16,4 +20,7 @@ uart at 73fbc000 {
 	reg = <0x73fbc000 0x4000>;
 	interrupts = <31>;
 	fsl,uart-has-rtscts;
+	fsl,enable-dma;
+	fsl,uart-dma-events = <xx xx>;
+	fsl,enable-dte;
 };
drivers/tty/serial/imx.c +7 -0
index e7fecee..65ba24d 100644
@@ -47,9 +47,11 @@
 #include <linux/slab.h>
 #include <linux/of.h>
 #include <linux/of_device.h>
+#include <linux/dma-mapping.h>
 
 #include <asm/io.h>
 #include <asm/irq.h>
+#include <mach/dma.h>
 #include <mach/imx-uart.h>
 
 /* Register definitions */
@@ -82,6 +84,7 @@
 #define  UCR1_ADBR       (1<<14) /* Auto detect baud rate */
 #define  UCR1_TRDYEN     (1<<13) /* Transmitter ready interrupt enable */
 #define  UCR1_IDEN       (1<<12) /* Idle condition interrupt */
+#define  UCR1_ICD_REG(x) (((x) & 3) << 10) /* idle condition detect */
 #define  UCR1_RRDYEN     (1<<9)	 /* Recv ready interrupt enable */
 #define  UCR1_RDMAEN     (1<<8)	 /* Recv ready DMA enable */
 #define  UCR1_IREN       (1<<7)	 /* Infrared interface enable */
@@ -125,6 +128,7 @@
 #define  UCR4_ENIRI 	 (1<<8)  /* Serial infrared interrupt enable */
 #define  UCR4_WKEN  	 (1<<7)  /* Wake interrupt enable */
 #define  UCR4_REF16 	 (1<<6)  /* Ref freq 16 MHz */
+#define  UCR4_IDDMAEN    (1<<6)  /* DMA IDLE Condition Detected */
 #define  UCR4_IRSC  	 (1<<5)  /* IR special case */
 #define  UCR4_TCEN  	 (1<<3)  /* Transmit complete interrupt enable */
 #define  UCR4_BKEN  	 (1<<2)  /* Break condition interrupt enable */
@@ -134,6 +138,7 @@
 #define  UFCR_RFDIV      (7<<7)  /* Reference freq divider mask */
 #define  UFCR_RFDIV_REG(x)	(((x) < 7 ? 6 - (x) : 6) << 7)
 #define  UFCR_TXTL_SHF   10      /* Transmitter trigger level shift */
+#define  UFCR_DCEDTE	 (1<<6)
 #define  USR1_PARITYERR  (1<<15) /* Parity error interrupt flag */
 #define  USR1_RTSS  	 (1<<14) /* RTS pin status */
 #define  USR1_TRDY  	 (1<<13) /* Transmitter ready interrupt/dma flag */
@@ -200,12 +205,27 @@ struct imx_port {
 	unsigned int		old_status;
 	int			txirq,rxirq,rtsirq;
 	unsigned int		have_rtscts:1;
+	unsigned int		enable_dte:1;
+	unsigned int		enable_dma:1;
 	unsigned int		use_irda:1;
 	unsigned int		irda_inv_rx:1;
 	unsigned int		irda_inv_tx:1;
 	unsigned short		trcv_delay; /* transceiver delay */
 	struct clk		*clk;
 	struct imx_uart_data	*devdata;

drivers/tty/serial/imx.c struct imx_port { · line 225

Quoted 10 lines
drivers/tty/serial/imx.c +10 -0
+
+	/* DMA fields */
+	unsigned int		dma_req_rx;
+	unsigned int		dma_req_tx;
+	struct imx_dma_data	dma_data;
+	struct dma_chan		*dma_chan_rx, *dma_chan_tx;
+	struct scatterlist	rx_sgl, tx_sgl[2];
+	void			*rx_buf;
+	unsigned int		rx_bytes, tx_bytes;
+	struct work_struct	tsk_dma_rx, tsk_dma_tx;

Why do you need a work struct to deal with DMA?

Quoted 21 lines
drivers/tty/serial/imx.c +10 -0
+	unsigned int		dma_tx_nents;
+	bool			dma_is_rxing;
+	wait_queue_head_t	dma_wait;
 };
 
 struct imx_port_ucrs {
@@ -394,6 +414,13 @@ static void imx_stop_rx(struct uart_port *port)
 	struct imx_port *sport = (struct imx_port *)port;
 	unsigned long temp;
 
+	/*
+	 * We are maybe in the 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);
 }

drivers/tty/serial/imx.c static inline void imx_transmit_buffer(struct imx_port *sport) · line 462

Quoted 7 lines
drivers/tty/serial/imx.c +4 -0
@@ -429,6 +456,80 @@ static inline void imx_transmit_buffer(struct imx_port *sport)
 		imx_stop_tx(&sport->port);
 }
 
+static void dma_tx_callback(void *data)
+{
+	struct imx_port *sport = data;
+	struct scatterlist *sgl = &sport->tx_sgl[0];
	struct scatterlist *sgl = sport->tx_sgl;

is equivalent, and less typing.

drivers/tty/serial/imx.c static inline void imx_transmit_buffer(struct imx_port *sport) · line 471

Quoted 9 lines
drivers/tty/serial/imx.c +9 -0
+	struct circ_buf *xmit = &sport->port.state->xmit;
+
+	dma_unmap_sg(sport->port.dev, sgl, sport->dma_tx_nents, DMA_TO_DEVICE);
+
+	/* update the stat */
+	spin_lock(&sport->port.lock);
+	xmit->tail = (xmit->tail + sport->tx_bytes) & (UART_XMIT_SIZE - 1);
+	sport->port.icount.tx += sport->tx_bytes;
+	spin_unlock(&sport->port.lock);

Callbacks are called from tasklet context, and will have IRQs enabled. As the port lock is taken from IRQ context, this is waiting for a deadlock to happen. Have you run this with lockdep enabled?

Quoted 8 lines
drivers/tty/serial/imx.c +8 -0
+
+	if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)
+		uart_write_wakeup(&sport->port);
+	schedule_work(&sport->tsk_dma_tx);
+}
+
+static void dma_tx_work(struct work_struct *w)
+{

drivers/tty/serial/imx.c static inline void imx_transmit_buffer(struct imx_port *sport) · line 489

Quoted 10 lines
drivers/tty/serial/imx.c +10 -0
+	struct imx_port *sport = container_of(w, struct imx_port, tsk_dma_tx);
+	struct circ_buf *xmit = &sport->port.state->xmit;
+	struct scatterlist *sgl = &sport->tx_sgl[0];
+	struct dma_async_tx_descriptor *desc;
+	struct dma_chan	*chan = sport->dma_chan_tx;
+	enum dma_status status;
+	unsigned long flags;
+	int ret;
+
+	status = chan->device->device_tx_status(chan, (dma_cookie_t)NULL, NULL);

Cookies aren't pointers. NULL is inappropriate here.

Quoted 17 lines
drivers/tty/serial/imx.c +17 -0
+	if (DMA_IN_PROGRESS == status)
+		return;
+
+	spin_lock_irqsave(&sport->port.lock, flags);
+	sport->tx_bytes = uart_circ_chars_pending(xmit);
+	if (sport->tx_bytes > 0) {
+		if (xmit->tail > xmit->head) {
+			sport->dma_tx_nents = 2;
+			sg_init_table(sgl, 2);
+			sg_set_buf(sgl, xmit->buf + xmit->tail,
+					UART_XMIT_SIZE - xmit->tail);
+			sg_set_buf(&sgl[1], xmit->buf, xmit->head);
+		} else {
+			sport->dma_tx_nents = 1;
+			sg_init_one(sgl, xmit->buf + xmit->tail,
+					sport->tx_bytes);
+		}

drivers/tty/serial/imx.c static inline void imx_transmit_buffer(struct imx_port *sport) · line 516

Quoted 10 lines
drivers/tty/serial/imx.c +10 -0
+		spin_unlock_irqrestore(&sport->port.lock, flags);
+
+		ret = dma_map_sg(sport->port.dev, sgl,
+				sport->dma_tx_nents, DMA_TO_DEVICE);
+		if (ret == 0) {
+			pr_err("DMA mapping error for TX.\n");
+			return;
+		}
+		desc = dmaengine_prep_slave_sg(chan, sgl,
+				sport->dma_tx_nents, DMA_MEM_TO_DEV, 0);

If you're setting a callback, you should set DMA_PREP_INTERRUPT in the flags too.

Quoted 406 lines
drivers/tty/serial/imx.c +297 -4
+		if (!desc) {
+			pr_err("We cannot prepare for the TX slave dma!\n");
+			return;
+		}
+		desc->callback = dma_tx_callback;
+		desc->callback_param = sport;
+
+		/* fire it */
+		dmaengine_submit(desc);
+		dma_async_issue_pending(chan);
+		return;
+	}
+	spin_unlock_irqrestore(&sport->port.lock, flags);
+	return;
+}
+
 /*
  * interrupts disabled on entry
  */
@@ -448,8 +549,10 @@ static void imx_start_tx(struct uart_port *port)
 		writel(temp, sport->port.membase + UCR1);
 	}
 
-	temp = readl(sport->port.membase + UCR1);
-	writel(temp | UCR1_TXMPTYEN, sport->port.membase + UCR1);
+	if (!sport->enable_dma) {
+		temp = readl(sport->port.membase + UCR1);
+		writel(temp | UCR1_TXMPTYEN, sport->port.membase + UCR1);
+	}
 
 	if (USE_IRDA(sport)) {
 		temp = readl(sport->port.membase + UCR1);
@@ -461,6 +564,11 @@ static void imx_start_tx(struct uart_port *port)
 		writel(temp, sport->port.membase + UCR4);
 	}
 
+	if (sport->enable_dma) {
+		schedule_work(&sport->tsk_dma_tx);
+		return;
+	}
+
 	if (readl(sport->port.membase + uts_reg(sport)) & UTS_TXEMPTY)
 		imx_transmit_buffer(sport);
 }
@@ -577,6 +685,28 @@ out:
 	return IRQ_HANDLED;
 }
 
+/*
+ * We wait for the RXFIFO is filled with some data, and then
+ * arise a DMA operation to receive the data.
+ */
+static void imx_dma_rxint(struct imx_port *sport)
+{
+	unsigned long temp;
+
+	temp = readl(sport->port.membase + USR2);
+	if ((temp & USR2_RDR) && !sport->dma_is_rxing) {
+		sport->dma_is_rxing = true;
+
+		/* disable the `Recerver Ready Interrrupt` */
+		temp = readl(sport->port.membase + UCR1);
+		temp &= ~(UCR1_RRDYEN);
+		writel(temp, sport->port.membase + UCR1);
+
+		/* tell the DMA to receive the data. */
+		schedule_work(&sport->tsk_dma_rx);
+	}
+}
+
 static irqreturn_t imx_int(int irq, void *dev_id)
 {
 	struct imx_port *sport = dev_id;
@@ -584,8 +714,12 @@ static irqreturn_t imx_int(int irq, void *dev_id)
 
 	sts = readl(sport->port.membase + USR1);
 
-	if (sts & USR1_RRDY)
-		imx_rxint(irq, dev_id);
+	if (sts & USR1_RRDY) {
+		if (sport->enable_dma)
+			imx_dma_rxint(sport);
+		else
+			imx_rxint(irq, dev_id);
+	}
 
 	if (sts & USR1_TRDY &&
 			readl(sport->port.membase + UCR1) & UCR1_TXMPTYEN)
@@ -685,6 +819,195 @@ static int imx_setup_ufcr(struct imx_port *sport, unsigned int mode)
 	return 0;
 }
 
+static bool imx_uart_filter(struct dma_chan *chan, void *param)
+{
+	struct imx_port *sport = param;
+
+	if (!imx_dma_is_general_purpose(chan))
+		return false;
+	chan->private = &sport->dma_data;
+	return true;
+}
+
+#define RX_BUF_SIZE	(PAGE_SIZE)
+static int start_rx_dma(struct imx_port *sport);
+static void dma_rx_work(struct work_struct *w)
+{
+	struct imx_port *sport = container_of(w, struct imx_port, tsk_dma_rx);
+	struct tty_struct *tty = sport->port.state->port.tty;
+
+	if (sport->rx_bytes) {
+		tty_insert_flip_string(tty, sport->rx_buf, sport->rx_bytes);
+		tty_flip_buffer_push(tty);
+		sport->rx_bytes = 0;
+	}
+
+	if (sport->dma_is_rxing)
+		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 three kinds of RX DMA interrupts in the MX6Q:
+ *   [1] the RX DMA buffer is full.
+ *   [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)
+{
+	struct imx_port *sport = data;
+	struct dma_chan	*chan = sport->dma_chan_rx;
+	unsigned int count;
+	struct tty_struct *tty;
+	struct scatterlist *sgl;
+	struct dma_tx_state state;
+	enum dma_status status;
+
+	tty = sport->port.state->port.tty;
+	sgl = &sport->rx_sgl;
+
+	/* 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
+		imx_finish_dma(sport);
+}
+
+static int start_rx_dma(struct imx_port *sport)
+{
+	struct scatterlist *sgl = &sport->rx_sgl;
+	struct dma_chan	*chan = sport->dma_chan_rx;
+	struct dma_async_tx_descriptor *desc;
+	int ret;
+
+	sg_init_one(sgl, sport->rx_buf, RX_BUF_SIZE);
+	ret = dma_map_sg(sport->port.dev, sgl, 1, DMA_FROM_DEVICE);
+	if (ret == 0) {
+		pr_err("DMA mapping error for RX.\n");
+		return -EINVAL;
+	}
+	desc = dmaengine_prep_slave_sg(chan, sgl, 1, DMA_DEV_TO_MEM, 0);
+	if (!desc) {
+		pr_err("We cannot prepare for the RX slave dma!\n");
+		return -EINVAL;
+	}
+	desc->callback = dma_rx_callback;
+	desc->callback_param = sport;
+
+	dmaengine_submit(desc);
+	dma_async_issue_pending(chan);
+	return 0;
+}
+
+static void imx_uart_dma_exit(struct imx_port *sport)
+{
+	if (sport->dma_chan_rx) {
+		dma_release_channel(sport->dma_chan_rx);
+		sport->dma_chan_rx = NULL;
+
+		kfree(sport->rx_buf);
+		sport->rx_buf = NULL;
+	}
+
+	if (sport->dma_chan_tx) {
+		dma_release_channel(sport->dma_chan_tx);
+		sport->dma_chan_tx = NULL;
+	}
+}
+
+/* see the "i.MX61 SDMA Scripts User Manual.doc" for the parameters */
+static int imx_uart_dma_init(struct imx_port *sport)
+{
+	struct dma_slave_config slave_config;
+	dma_cap_mask_t mask;
+	int ret;
+
+	/* prepare for RX : */
+	dma_cap_zero(mask);
+	dma_cap_set(DMA_SLAVE, mask);
+
+	sport->dma_data.priority = DMA_PRIO_HIGH;
+	sport->dma_data.dma_request = sport->dma_req_rx;
+	sport->dma_data.peripheral_type = IMX_DMATYPE_UART;
+
+	sport->dma_chan_rx = dma_request_channel(mask, imx_uart_filter, sport);
+	if (!sport->dma_chan_rx) {
+		pr_err("cannot get the DMA channel.\n");
+		ret = -EINVAL;
+		goto err;
+	}
+
+	slave_config.direction = DMA_DEV_TO_MEM;
+	slave_config.src_addr = sport->port.mapbase + URXD0;
+	slave_config.src_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE;
+	slave_config.src_maxburst = RXTL; /* fix me */
+	ret = dmaengine_slave_config(sport->dma_chan_rx, &slave_config);
+	if (ret) {
+		pr_err("error in RX dma configuration.\n");
+		goto err;
+	}
+
+	sport->rx_buf = kzalloc(PAGE_SIZE, GFP_DMA);
+	if (!sport->rx_buf) {
+		pr_err("cannot alloc DMA buffer.\n");
+		ret = -ENOMEM;
+		goto err;
+	}
+	sport->rx_bytes = 0;
+
+	/* prepare for TX : */
+	sport->dma_data.dma_request = sport->dma_req_tx;
+	sport->dma_chan_tx = dma_request_channel(mask, imx_uart_filter, sport);
+	if (!sport->dma_chan_tx) {
+		pr_err("cannot get the TX DMA channel!\n");
+		ret = -EINVAL;
+		goto err;
+	}
+
+	slave_config.direction = DMA_MEM_TO_DEV;
+	slave_config.dst_addr = sport->port.mapbase + URTX0;
+	slave_config.dst_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE;
+	slave_config.dst_maxburst = TXTL; /* fix me */
+	ret = dmaengine_slave_config(sport->dma_chan_tx, &slave_config);
+	if (ret) {
+		pr_err("error in TX dma configuration.");
+		goto err;
+	}
+
+	return 0;
+err:
+	imx_uart_dma_exit(sport);
+	return ret;
+}
+
 /* half the RX buffer size */
 #define CTSTL 16
 
@@ -756,6 +1079,19 @@ static int imx_startup(struct uart_port *port)
 		}
 	}
 
+	/* Enable the SDMA for uart. */
+	if (sport->enable_dma) {
+		int ret;
+		ret = imx_uart_dma_init(sport);
+		if (ret)
+			goto error_out3;
+
+		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);
+	}
+
 	/*
 	 * Finally, clear and enable interrupts
 	 */
@@ -763,6 +1099,11 @@ 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;
+		/* ICD, wait for more than 32 frames, but it still to short. */
+		temp |= UCR1_ICD_REG(3);
+	}
 
 	if (USE_IRDA(sport)) {
 		temp |= UCR1_IREN;
@@ -806,6 +1147,12 @@ static int imx_startup(struct uart_port *port)
 		writel(temp, sport->port.membase + UCR3);
 	}
 
+	if (sport->enable_dma) {
+		temp = readl(sport->port.membase + UCR4);
+		temp |= UCR4_IDDMAEN;
+		writel(temp, sport->port.membase + UCR4);
+	}
+
 	/*
 	 * Enable modem status interrupts
 	 */
@@ -840,6 +1187,13 @@ static void imx_shutdown(struct uart_port *port)
 	struct imx_port *sport = (struct imx_port *)port;
 	unsigned long temp;
 
+	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);
+	}
+
 	temp = readl(sport->port.membase + UCR2);
 	temp &= ~(UCR2_TXEN);
 	writel(temp, sport->port.membase + UCR2);
@@ -875,8 +1229,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);
+
+	if (sport->enable_dma) {
+		temp = readl(sport->port.membase + UCR4);
+		temp &= ~UCR4_IDDMAEN;
+		writel(temp, sport->port.membase + UCR4);
+	}
 }
 
 static void
@@ -1013,6 +1375,9 @@ imx_set_termios(struct uart_port *port, struct ktermios *termios,
 
 	ufcr = readl(sport->port.membase + UFCR);
 	ufcr = (ufcr & (~UFCR_RFDIV)) | UFCR_RFDIV_REG(div);
+	/* set the DTE mode */
+	if (sport->enable_dte)
+		ufcr |= UFCR_DCEDTE;
 	writel(ufcr, sport->port.membase + UFCR);
 
 	writel(num, sport->port.membase + UBIR);
@@ -1409,6 +1774,7 @@ static int serial_imx_probe_dt(struct imx_port *sport,
 	const struct of_device_id *of_id =
 			of_match_device(imx_uart_dt_ids, &pdev->dev);
 	int ret;
+	u32 dma_req[2];
 
 	if (!np)
 		/* no device tree device */
@@ -1427,6 +1793,18 @@ static int serial_imx_probe_dt(struct imx_port *sport,
 	if (of_get_property(np, "fsl,irda-mode", NULL))
 		sport->use_irda = 1;
 
+	if (of_get_property(np, "fsl,enable-dma", NULL))
+		sport->enable_dma = 1;
+
+	if (of_get_property(np, "fsl,enable-dte", NULL))
+		sport->enable_dte = 1;
+
+	if (of_property_read_u32_array(np, "fsl,uart-dma-events", dma_req,
+				ARRAY_SIZE(dma_req)) == 0) {
+		sport->dma_req_rx = dma_req[0];
+		sport->dma_req_tx = dma_req[1];
+	}
+
 	sport->devdata = of_id->data;
 
 	return 0;
Signature · 8 lines
1.7.0.4



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

PATCH 1/2 serial/imx: add DMA support

Quoted 15 lines · Russell King - ARM Linux
Quoted 4 lines · Huang Shijie

Add the DMA support for uart RX and TX.

---

Apart from the comments below,

1. How do you deal with transmitting the high-priority XON/XOFF characters (port->x_char) which occur with s/w flow control and the tty buffers fill up?
2. How do you deal with flow control in general? IOW, what happens when the remote end deasserts your CTS with h/w flow control enabled. How does your end deal with sending RTS according to flow control conditions?

Quoted 1 line

i

The UART uses the DMA for RX/TX with the hardware flow control (RTS/CTS) enabled all the time. If we use the software flow control(XON/XOFF), we should not enable the DMA.
I think i should add more comments about this issue.

For example:
The MX6Q arm2 has two uarts, one with the DMA disabled is used for debug, the other one with the DMA/RTS.CTS enabled can be used for the Bluetooth.

Quoted 82 lines
Quoted 82 lines
  .../bindings/tty/serial/fsl-imx-uart.txt           |    7 +
  drivers/tty/serial/imx.c                           |  386 +++++++++++++++++++-
  2 files changed, 389 insertions(+), 4 deletions(-)
Documentation/devicetree/bindings/tty/serial/fsl-imx-uart.txt +7 -0
index a9c0406..f27489d 100644
@@ -8,6 +8,10 @@ Required properties:
  Optional properties:
  - fsl,uart-has-rtscts : Indicate the uart has rts and cts
  - fsl,irda-mode : Indicate the uart supports irda mode
+- fsl,enable-dma : Indicate the uart supports DMA
+- fsl,uart-dma-events : contains the DMA events for RX and TX,
+	          The first is the RX event, while the other is TX.
+- fsl,enable-dte: Indicate the uart works in DTE mode
 
  Example:
 
@@ -16,4 +20,7 @@ uart@73fbc000 {
  	reg =<0x73fbc000 0x4000>;
  	interrupts =<31>;
  	fsl,uart-has-rtscts;
+	fsl,enable-dma;
+	fsl,uart-dma-events =<xx xx>;
+	fsl,enable-dte;
  };
drivers/tty/serial/imx.c +10 -0
index e7fecee..65ba24d 100644
@@ -47,9 +47,11 @@
  #include<linux/slab.h>
  #include<linux/of.h>
  #include<linux/of_device.h>
+#include<linux/dma-mapping.h>
 
  #include<asm/io.h>
  #include<asm/irq.h>
+#include<mach/dma.h>
  #include<mach/imx-uart.h>
 
  /* Register definitions */
@@ -82,6 +84,7 @@
  #define  UCR1_ADBR       (1<<14) /* Auto detect baud rate */
  #define  UCR1_TRDYEN     (1<<13) /* Transmitter ready interrupt enable */
  #define  UCR1_IDEN       (1<<12) /* Idle condition interrupt */
+#define  UCR1_ICD_REG(x) (((x)&  3)<<  10) /* idle condition detect */
  #define  UCR1_RRDYEN     (1<<9)	 /* Recv ready interrupt enable */
  #define  UCR1_RDMAEN     (1<<8)	 /* Recv ready DMA enable */
  #define  UCR1_IREN       (1<<7)	 /* Infrared interface enable */
@@ -125,6 +128,7 @@
  #define  UCR4_ENIRI 	 (1<<8)  /* Serial infrared interrupt enable */
  #define  UCR4_WKEN  	 (1<<7)  /* Wake interrupt enable */
  #define  UCR4_REF16 	 (1<<6)  /* Ref freq 16 MHz */
+#define  UCR4_IDDMAEN    (1<<6)  /* DMA IDLE Condition Detected */
  #define  UCR4_IRSC  	 (1<<5)  /* IR special case */
  #define  UCR4_TCEN  	 (1<<3)  /* Transmit complete interrupt enable */
  #define  UCR4_BKEN  	 (1<<2)  /* Break condition interrupt enable */
@@ -134,6 +138,7 @@
  #define  UFCR_RFDIV      (7<<7)  /* Reference freq divider mask */
  #define  UFCR_RFDIV_REG(x)	(((x)<  7 ? 6 - (x) : 6)<<  7)
  #define  UFCR_TXTL_SHF   10      /* Transmitter trigger level shift */
+#define  UFCR_DCEDTE	 (1<<6)
  #define  USR1_PARITYERR  (1<<15) /* Parity error interrupt flag */
  #define  USR1_RTSS  	 (1<<14) /* RTS pin status */
  #define  USR1_TRDY  	 (1<<13) /* Transmitter ready interrupt/dma flag */
@@ -200,12 +205,27 @@ struct imx_port {
  	unsigned int		old_status;
  	int			txirq,rxirq,rtsirq;
  	unsigned int		have_rtscts:1;
+	unsigned int		enable_dte:1;
+	unsigned int		enable_dma:1;
  	unsigned int		use_irda:1;
  	unsigned int		irda_inv_rx:1;
  	unsigned int		irda_inv_tx:1;
  	unsigned short		trcv_delay; /* transceiver delay */
  	struct clk		*clk;
  	struct imx_uart_data	*devdata;
+
+	/* DMA fields */
+	unsigned int		dma_req_rx;
Quoted 9 lines

drivers/tty/serial/imx.c struct imx_port { · line 225

Quoted 7 lines
drivers/tty/serial/imx.c +7 -0
+	unsigned int		dma_req_tx;
+	struct imx_dma_data	dma_data;
+	struct dma_chan		*dma_chan_rx, *dma_chan_tx;
+	struct scatterlist	rx_sgl, tx_sgl[2];
+	void			*rx_buf;
+	unsigned int		rx_bytes, tx_bytes;
+	struct work_struct	tsk_dma_rx, tsk_dma_tx;

Why do you need a work struct to deal with DMA?

The uart uses the SDMA (drivers/dma/imx-sdma.c). And the SDMA may schedule out in :
sdma_prep_slave_sg() --> sdma_load_contex() -->sdma_run_channel() .

So i have to add a work struct to prepare and triger the DMA operations.

Quoted 32 lines
Quoted 21 lines
drivers/tty/serial/imx.c +10 -0
+	unsigned int		dma_tx_nents;
+	bool			dma_is_rxing;
+	wait_queue_head_t	dma_wait;
  };
 
  struct imx_port_ucrs {
@@ -394,6 +414,13 @@ static void imx_stop_rx(struct uart_port *port)
  	struct imx_port *sport = (struct imx_port *)port;
  	unsigned long temp;
 
+	/*
+	 * We are maybe in the 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);
  }

drivers/tty/serial/imx.c static inline void imx_transmit_buffer(struct imx_port *sport) · line 462

Quoted 7 lines
drivers/tty/serial/imx.c +4 -0
@@ -429,6 +456,80 @@ static inline void imx_transmit_buffer(struct imx_port *sport)
  		imx_stop_tx(&sport->port);
  }
 
+static void dma_tx_callback(void *data)
+{
+	struct imx_port *sport = data;
+	struct scatterlist *sgl =&sport->tx_sgl[0];
	struct scatterlist *sgl = sport->tx_sgl;

is equivalent, and less typing.

Quoted 13 lines

drivers/tty/serial/imx.c static inline void imx_transmit_buffer(struct imx_port *sport) · line 471

Quoted 9 lines
drivers/tty/serial/imx.c +9 -0
+	struct circ_buf *xmit =&sport->port.state->xmit;
+
+	dma_unmap_sg(sport->port.dev, sgl, sport->dma_tx_nents, DMA_TO_DEVICE);
+
+	/* update the stat */
+	spin_lock(&sport->port.lock);
+	xmit->tail = (xmit->tail + sport->tx_bytes)&  (UART_XMIT_SIZE - 1);
+	sport->port.icount.tx += sport->tx_bytes;
+	spin_unlock(&sport->port.lock);

Callbacks are called from tasklet context, and will have IRQs enabled. As the port lock is taken from IRQ context, this is waiting for a deadlock to happen. Have you run this with lockdep enabled?

This callback is called from IRQ context, with all the IRQ disabled. see:
sdma_int_handler() -->mxc_sdma_handle_channel() --> mxc_sdma_handle_channel_normal() --> .callback().

So spin_lock() is enough.

Best Regards
Huang Shijie

--
To unsubscribe from this list: send the line "unsubscribe linux-serial" in the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html

#4 lore raw

PATCH 1/2 serial/imx: add DMA support

Quoted 16 lines · Huang Shijie

? 2012?04?26? 19:11, Russell King - ARM Linux ??:

Quoted 15 lines · Huang Shijie
Quoted 4 lines

Add the DMA support for uart RX and TX.

---

Apart from the comments below,

1. How do you deal with transmitting the high-priority XON/XOFF characters (port->x_char) which occur with s/w flow control and the tty buffers fill up?
2. How do you deal with flow control in general? IOW, what happens when the remote end deasserts your CTS with h/w flow control enabled. How does your end deal with sending RTS according to flow control conditions?

Quoted 3 lines
Quoted 1 line

i

The UART uses the DMA for RX/TX with the hardware flow control (RTS/CTS) enabled all the time.

Your answer is too vague. Please try again.

Quoted 97 lines

If we use the software flow control(XON/XOFF), we should not enable the DMA. I think i should add more comments about this issue.

For example:
The MX6Q arm2 has two uarts, one with the DMA disabled is used for debug, the other one with the DMA/RTS.CTS enabled can be used for the Bluetooth.

Quoted 82 lines
Quoted 82 lines
  .../bindings/tty/serial/fsl-imx-uart.txt           |    7 +
  drivers/tty/serial/imx.c                           |  386 +++++++++++++++++++-
  2 files changed, 389 insertions(+), 4 deletions(-)
Documentation/devicetree/bindings/tty/serial/fsl-imx-uart.txt +7 -0
index a9c0406..f27489d 100644
@@ -8,6 +8,10 @@ Required properties:
  Optional properties:
  - fsl,uart-has-rtscts : Indicate the uart has rts and cts
  - fsl,irda-mode : Indicate the uart supports irda mode
+- fsl,enable-dma : Indicate the uart supports DMA
+- fsl,uart-dma-events : contains the DMA events for RX and TX,
+	          The first is the RX event, while the other is TX.
+- fsl,enable-dte: Indicate the uart works in DTE mode
 
  Example:
 
@@ -16,4 +20,7 @@ uart at 73fbc000 {
  	reg =<0x73fbc000 0x4000>;
  	interrupts =<31>;
  	fsl,uart-has-rtscts;
+	fsl,enable-dma;
+	fsl,uart-dma-events =<xx xx>;
+	fsl,enable-dte;
  };
drivers/tty/serial/imx.c +10 -0
index e7fecee..65ba24d 100644
@@ -47,9 +47,11 @@
  #include<linux/slab.h>
  #include<linux/of.h>
  #include<linux/of_device.h>
+#include<linux/dma-mapping.h>
 
  #include<asm/io.h>
  #include<asm/irq.h>
+#include<mach/dma.h>
  #include<mach/imx-uart.h>
 
  /* Register definitions */
@@ -82,6 +84,7 @@
  #define  UCR1_ADBR       (1<<14) /* Auto detect baud rate */
  #define  UCR1_TRDYEN     (1<<13) /* Transmitter ready interrupt enable */
  #define  UCR1_IDEN       (1<<12) /* Idle condition interrupt */
+#define  UCR1_ICD_REG(x) (((x)&  3)<<  10) /* idle condition detect */
  #define  UCR1_RRDYEN     (1<<9)	 /* Recv ready interrupt enable */
  #define  UCR1_RDMAEN     (1<<8)	 /* Recv ready DMA enable */
  #define  UCR1_IREN       (1<<7)	 /* Infrared interface enable */
@@ -125,6 +128,7 @@
  #define  UCR4_ENIRI 	 (1<<8)  /* Serial infrared interrupt enable */
  #define  UCR4_WKEN  	 (1<<7)  /* Wake interrupt enable */
  #define  UCR4_REF16 	 (1<<6)  /* Ref freq 16 MHz */
+#define  UCR4_IDDMAEN    (1<<6)  /* DMA IDLE Condition Detected */
  #define  UCR4_IRSC  	 (1<<5)  /* IR special case */
  #define  UCR4_TCEN  	 (1<<3)  /* Transmit complete interrupt enable */
  #define  UCR4_BKEN  	 (1<<2)  /* Break condition interrupt enable */
@@ -134,6 +138,7 @@
  #define  UFCR_RFDIV      (7<<7)  /* Reference freq divider mask */
  #define  UFCR_RFDIV_REG(x)	(((x)<  7 ? 6 - (x) : 6)<<  7)
  #define  UFCR_TXTL_SHF   10      /* Transmitter trigger level shift */
+#define  UFCR_DCEDTE	 (1<<6)
  #define  USR1_PARITYERR  (1<<15) /* Parity error interrupt flag */
  #define  USR1_RTSS  	 (1<<14) /* RTS pin status */
  #define  USR1_TRDY  	 (1<<13) /* Transmitter ready interrupt/dma flag */
@@ -200,12 +205,27 @@ struct imx_port {
  	unsigned int		old_status;
  	int			txirq,rxirq,rtsirq;
  	unsigned int		have_rtscts:1;
+	unsigned int		enable_dte:1;
+	unsigned int		enable_dma:1;
  	unsigned int		use_irda:1;
  	unsigned int		irda_inv_rx:1;
  	unsigned int		irda_inv_tx:1;
  	unsigned short		trcv_delay; /* transceiver delay */
  	struct clk		*clk;
  	struct imx_uart_data	*devdata;
+
+	/* DMA fields */
+	unsigned int		dma_req_rx;
Quoted 9 lines

drivers/tty/serial/imx.c struct imx_port { · line 225

Quoted 7 lines
drivers/tty/serial/imx.c +7 -0
+	unsigned int		dma_req_tx;
+	struct imx_dma_data	dma_data;
+	struct dma_chan		*dma_chan_rx, *dma_chan_tx;
+	struct scatterlist	rx_sgl, tx_sgl[2];
+	void			*rx_buf;
+	unsigned int		rx_bytes, tx_bytes;
+	struct work_struct	tsk_dma_rx, tsk_dma_tx;

Why do you need a work struct to deal with DMA?

Quoted 3 lines

The uart uses the SDMA (drivers/dma/imx-sdma.c). And the SDMA may schedule out in :
sdma_prep_slave_sg() --> sdma_load_contex() -->sdma_run_channel() .

It should not. prep_slave_sg() must be callable from atomic contexts.

Quoted 4 lines

This callback is called from IRQ context, with all the IRQ disabled. see:
sdma_int_handler() -->mxc_sdma_handle_channel() --> mxc_sdma_handle_channel_normal()
--> .callback().

That's a bug in your dmaengine driver.

#5 lore raw

PATCH 1/2 serial/imx: add DMA support

Quoted 120 lines · Russell King - ARM Linux
Quoted 16 lines · Huang Shijie

? 2012?04?26? 19:11, Russell King - ARM Linux ??:

Quoted 15 lines · Huang Shijie
Quoted 4 lines

Add the DMA support for uart RX and TX.

---

Apart from the comments below,

1. How do you deal with transmitting the high-priority XON/XOFF characters (port->x_char) which occur with s/w flow control and the tty buffers fill up?
2. How do you deal with flow control in general? IOW, what happens when the remote end deasserts your CTS with h/w flow control enabled. How does your end deal with sending RTS according to flow control conditions?

Quoted 3 lines
Quoted 1 line

i

The UART uses the DMA for RX/TX with the hardware flow control (RTS/CTS) enabled all the time.

Your answer is too vague. Please try again.

Quoted 97 lines

If we use the software flow control(XON/XOFF), we should not enable the DMA. I think i should add more comments about this issue.

For example:
The MX6Q arm2 has two uarts, one with the DMA disabled is used for debug, the other one with the DMA/RTS.CTS enabled can be used for the Bluetooth.

Quoted 91 lines
Quoted 79 lines
  .../bindings/tty/serial/fsl-imx-uart.txt           |    7 +
  drivers/tty/serial/imx.c                           |  386 +++++++++++++++++++-
  2 files changed, 389 insertions(+), 4 deletions(-)
Documentation/devicetree/bindings/tty/serial/fsl-imx-uart.txt +7 -0
index a9c0406..f27489d 100644
@@ -8,6 +8,10 @@ Required properties:
  Optional properties:
  - fsl,uart-has-rtscts : Indicate the uart has rts and cts
  - fsl,irda-mode : Indicate the uart supports irda mode
+- fsl,enable-dma : Indicate the uart supports DMA
+- fsl,uart-dma-events : contains the DMA events for RX and TX,
+	          The first is the RX event, while the other is TX.
+- fsl,enable-dte: Indicate the uart works in DTE mode
 
  Example:
 
@@ -16,4 +20,7 @@ uart at 73fbc000 {
  	reg =<0x73fbc000 0x4000>;
  	interrupts =<31>;
  	fsl,uart-has-rtscts;
+	fsl,enable-dma;
+	fsl,uart-dma-events =<xx xx>;
+	fsl,enable-dte;
  };
drivers/tty/serial/imx.c +7 -0
index e7fecee..65ba24d 100644
@@ -47,9 +47,11 @@
  #include<linux/slab.h>
  #include<linux/of.h>
  #include<linux/of_device.h>
+#include<linux/dma-mapping.h>
 
  #include<asm/io.h>
  #include<asm/irq.h>
+#include<mach/dma.h>
  #include<mach/imx-uart.h>
 
  /* Register definitions */
@@ -82,6 +84,7 @@
  #define  UCR1_ADBR       (1<<14) /* Auto detect baud rate */
  #define  UCR1_TRDYEN     (1<<13) /* Transmitter ready interrupt enable */
  #define  UCR1_IDEN       (1<<12) /* Idle condition interrupt */
+#define  UCR1_ICD_REG(x) (((x)&  3)<<  10) /* idle condition detect */
  #define  UCR1_RRDYEN     (1<<9)	 /* Recv ready interrupt enable */
  #define  UCR1_RDMAEN     (1<<8)	 /* Recv ready DMA enable */
  #define  UCR1_IREN       (1<<7)	 /* Infrared interface enable */
@@ -125,6 +128,7 @@
  #define  UCR4_ENIRI 	 (1<<8)  /* Serial infrared interrupt enable */
  #define  UCR4_WKEN  	 (1<<7)  /* Wake interrupt enable */
  #define  UCR4_REF16 	 (1<<6)  /* Ref freq 16 MHz */
+#define  UCR4_IDDMAEN    (1<<6)  /* DMA IDLE Condition Detected */
  #define  UCR4_IRSC  	 (1<<5)  /* IR special case */
  #define  UCR4_TCEN  	 (1<<3)  /* Transmit complete interrupt enable */
  #define  UCR4_BKEN  	 (1<<2)  /* Break condition interrupt enable */
@@ -134,6 +138,7 @@
  #define  UFCR_RFDIV      (7<<7)  /* Reference freq divider mask */
  #define  UFCR_RFDIV_REG(x)	(((x)<  7 ? 6 - (x) : 6)<<  7)
  #define  UFCR_TXTL_SHF   10      /* Transmitter trigger level shift */
+#define  UFCR_DCEDTE	 (1<<6)
  #define  USR1_PARITYERR  (1<<15) /* Parity error interrupt flag */
  #define  USR1_RTSS  	 (1<<14) /* RTS pin status */
  #define  USR1_TRDY  	 (1<<13) /* Transmitter ready interrupt/dma flag */
@@ -200,12 +205,27 @@ struct imx_port {
  	unsigned int		old_status;
  	int			txirq,rxirq,rtsirq;
  	unsigned int		have_rtscts:1;
+	unsigned int		enable_dte:1;
+	unsigned int		enable_dma:1;
  	unsigned int		use_irda:1;
  	unsigned int		irda_inv_rx:1;
  	unsigned int		irda_inv_tx:1;
  	unsigned short		trcv_delay; /* transceiver delay */
  	struct clk		*clk;
  	struct imx_uart_data	*devdata;

drivers/tty/serial/imx.c struct imx_port { · line 225

Quoted 10 lines
drivers/tty/serial/imx.c +10 -0
+
+	/* DMA fields */
+	unsigned int		dma_req_rx;
+	unsigned int		dma_req_tx;
+	struct imx_dma_data	dma_data;
+	struct dma_chan		*dma_chan_rx, *dma_chan_tx;
+	struct scatterlist	rx_sgl, tx_sgl[2];
+	void			*rx_buf;
+	unsigned int		rx_bytes, tx_bytes;
+	struct work_struct	tsk_dma_rx, tsk_dma_tx;

Why do you need a work struct to deal with DMA?

Quoted 5 lines
Quoted 3 lines

The uart uses the SDMA (drivers/dma/imx-sdma.c). And the SDMA may schedule out in :
sdma_prep_slave_sg() --> sdma_load_contex() -->sdma_run_channel() .

It should not. prep_slave_sg() must be callable from atomic contexts.

I just sent out a patch to fix it.
[PATCH 01/11] dma: imx-sdma: make channel0 operations atomic

Thanks
Richard

Quoted 8 lines
Quoted 5 lines

This callback is called from IRQ context, with all the IRQ disabled. see:
sdma_int_handler() -->mxc_sdma_handle_channel() --> mxc_sdma_handle_channel_normal()
--> .callback().

That's a bug in your dmaengine driver.

#6 lore raw

PATCH 1/2 serial/imx: add DMA support

In reply to Russell King - ARM Linux
Quoted 7 lines · Russell King - ARM Linux
Quoted 6 lines · Huang Shijie
Quoted 5 lines · Russell King - ARM Linux
Quoted 4 lines · Huang Shijie

Add the DMA support for uart RX and TX.

---

Quoted 7 lines
Quoted 7 lines
Quoted 7 lines

Apart from the comments below,

1. How do you deal with transmitting the high-priority XON/XOFF characters (port->x_char) which occur with s/w flow control and the tty buffers fill up?
2. How do you deal with flow control in general? IOW, what happens when the remote end deasserts your CTS with h/w flow control enabled.

If the remote end deasserts my CTS, it means the remote will not send any data.

My DMA for RX will expire in the following steps:
[1] the UART only waits for 32 bytes time long
[2] the UART triggers an IDLE Condition Detect DMA.
[3] the dma_rx_callback() will release the DMA for Rx.

Quoted 3 lines
Quoted 3 lines
Quoted 3 lines

How does your end deal with sending RTS according to flow control conditions?

If a CTS is received after we sent out a RTS, it will follow the steps:
imx_int() --> imx_rtsint() --> uart_handle_cts_change() -->start_tx()

The start_tx() will create an TX DMA operation, and send out the data.

Quoted 102 lines
Quoted 3 lines
Quoted 1 line

i

The UART uses the DMA for RX/TX with the hardware flow control (RTS/CTS) enabled all the time.

Your answer is too vague. Please try again.

Quoted 97 lines

If we use the software flow control(XON/XOFF), we should not enable the DMA. I think i should add more comments about this issue.

For example:
The MX6Q arm2 has two uarts, one with the DMA disabled is used for debug, the other one with the DMA/RTS.CTS enabled can be used for the Bluetooth.

Quoted 91 lines
Quoted 79 lines
   .../bindings/tty/serial/fsl-imx-uart.txt           |    7 +
   drivers/tty/serial/imx.c                           |  386 +++++++++++++++++++-
   2 files changed, 389 insertions(+), 4 deletions(-)
Documentation/devicetree/bindings/tty/serial/fsl-imx-uart.txt +7 -0
index a9c0406..f27489d 100644
@@ -8,6 +8,10 @@ Required properties:
   Optional properties:
   - fsl,uart-has-rtscts : Indicate the uart has rts and cts
   - fsl,irda-mode : Indicate the uart supports irda mode
+- fsl,enable-dma : Indicate the uart supports DMA
+- fsl,uart-dma-events : contains the DMA events for RX and TX,
+	          The first is the RX event, while the other is TX.
+- fsl,enable-dte: Indicate the uart works in DTE mode
 
   Example:
 
@@ -16,4 +20,7 @@ uart@73fbc000 {
   	reg =<0x73fbc000 0x4000>;
   	interrupts =<31>;
   	fsl,uart-has-rtscts;
+	fsl,enable-dma;
+	fsl,uart-dma-events =<xx xx>;
+	fsl,enable-dte;
   };
drivers/tty/serial/imx.c +7 -0
index e7fecee..65ba24d 100644
@@ -47,9 +47,11 @@
   #include<linux/slab.h>
   #include<linux/of.h>
   #include<linux/of_device.h>
+#include<linux/dma-mapping.h>
 
   #include<asm/io.h>
   #include<asm/irq.h>
+#include<mach/dma.h>
   #include<mach/imx-uart.h>
 
   /* Register definitions */
@@ -82,6 +84,7 @@
   #define  UCR1_ADBR       (1<<14) /* Auto detect baud rate */
   #define  UCR1_TRDYEN     (1<<13) /* Transmitter ready interrupt enable */
   #define  UCR1_IDEN       (1<<12) /* Idle condition interrupt */
+#define  UCR1_ICD_REG(x) (((x)&   3)<<   10) /* idle condition detect */
   #define  UCR1_RRDYEN     (1<<9)	 /* Recv ready interrupt enable */
   #define  UCR1_RDMAEN     (1<<8)	 /* Recv ready DMA enable */
   #define  UCR1_IREN       (1<<7)	 /* Infrared interface enable */
@@ -125,6 +128,7 @@
   #define  UCR4_ENIRI 	 (1<<8)  /* Serial infrared interrupt enable */
   #define  UCR4_WKEN  	 (1<<7)  /* Wake interrupt enable */
   #define  UCR4_REF16 	 (1<<6)  /* Ref freq 16 MHz */
+#define  UCR4_IDDMAEN    (1<<6)  /* DMA IDLE Condition Detected */
   #define  UCR4_IRSC  	 (1<<5)  /* IR special case */
   #define  UCR4_TCEN  	 (1<<3)  /* Transmit complete interrupt enable */
   #define  UCR4_BKEN  	 (1<<2)  /* Break condition interrupt enable */
@@ -134,6 +138,7 @@
   #define  UFCR_RFDIV      (7<<7)  /* Reference freq divider mask */
   #define  UFCR_RFDIV_REG(x)	(((x)<   7 ? 6 - (x) : 6)<<   7)
   #define  UFCR_TXTL_SHF   10      /* Transmitter trigger level shift */
+#define  UFCR_DCEDTE	 (1<<6)
   #define  USR1_PARITYERR  (1<<15) /* Parity error interrupt flag */
   #define  USR1_RTSS  	 (1<<14) /* RTS pin status */
   #define  USR1_TRDY  	 (1<<13) /* Transmitter ready interrupt/dma flag */
@@ -200,12 +205,27 @@ struct imx_port {
   	unsigned int		old_status;
   	int			txirq,rxirq,rtsirq;
   	unsigned int		have_rtscts:1;
+	unsigned int		enable_dte:1;
+	unsigned int		enable_dma:1;
   	unsigned int		use_irda:1;
   	unsigned int		irda_inv_rx:1;
   	unsigned int		irda_inv_tx:1;
   	unsigned short		trcv_delay; /* transceiver delay */
   	struct clk		*clk;
   	struct imx_uart_data	*devdata;

drivers/tty/serial/imx.c struct imx_port { · line 225

Quoted 10 lines
drivers/tty/serial/imx.c +10 -0
+
+	/* DMA fields */
+	unsigned int		dma_req_rx;
+	unsigned int		dma_req_tx;
+	struct imx_dma_data	dma_data;
+	struct dma_chan		*dma_chan_rx, *dma_chan_tx;
+	struct scatterlist	rx_sgl, tx_sgl[2];
+	void			*rx_buf;
+	unsigned int		rx_bytes, tx_bytes;
+	struct work_struct	tsk_dma_rx, tsk_dma_tx;

Why do you need a work struct to deal with DMA?

Quoted 5 lines
Quoted 3 lines

The uart uses the SDMA (drivers/dma/imx-sdma.c). And the SDMA may schedule out in :
sdma_prep_slave_sg() --> sdma_load_contex() -->sdma_run_channel() .

It should not. prep_slave_sg() must be callable from atomic contexts.

the Documentation/dmaengine.txt does not explicitly force all the dma engine driver to do so.

Add more comments for it in Documentation/dmaengine.txt?

Quoted 6 lines
Quoted 4 lines

This callback is called from IRQ context, with all the IRQ disabled. see:
sdma_int_handler() -->mxc_sdma_handle_channel() --> mxc_sdma_handle_channel_normal()
--> .callback().

That's a bug in your dmaengine driver.

OK, it's really a bug in the sdma driver.
thanks.

Best Regards
Huang Shijie

--
To unsubscribe from this list: send the line "unsubscribe linux-serial" in the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html

#7 lore raw

PATCH 1/2 serial/imx: add DMA support

Quoted 5 lines · Huang Shijie
Quoted 5 lines
Quoted 5 lines
Quoted 5 lines

1. How do you deal with transmitting the high-priority XON/XOFF characters (port->x_char) which occur with s/w flow control and the tty buffers fill up?
2. How do you deal with flow control in general? IOW, what happens when the remote end deasserts your CTS with h/w flow control enabled.

Quoted 7 lines

If the remote end deasserts my CTS, it means the remote will not send any data.

My DMA for RX will expire in the following steps:
[1] the UART only waits for 32 bytes time long
[2] the UART triggers an IDLE Condition Detect DMA.
[3] the dma_rx_callback() will release the DMA for Rx.

Err, hang on. I think you're totally confused about hardware flow control. Certainly you're not using the correct terms for what you're describing.

The CTS input normally controls the transmitter. In many hardware assisted hardware flow control setups, the deassertion of CTS merely prevents the transmitter starting a new character.

This shouldn't have any effect on the receiver of the same UART at all.

Quoted 7 lines
Quoted 3 lines
Quoted 3 lines
Quoted 3 lines

How does your end deal with sending RTS according to flow control conditions?

If a CTS is received after we sent out a RTS, it will follow the steps:
imx_int() --> imx_rtsint() --> uart_handle_cts_change() -->start_tx()

The start_tx() will create an TX DMA operation, and send out the data.

The generation of RTS (connected to the remote ends CTS signal) is supposed to control whether the remote end sends you characters. RTS gets deasserted either by software control when you're running out of space to store the received characters, or (in the case of hardware assisted hardware flow control) your receivers FIFO fills above a certain watermark.

Quoted 7 lines
Quoted 2 lines

It should not. prep_slave_sg() must be callable from atomic contexts.

the Documentation/dmaengine.txt does not explicitly force all the dma engine driver
to do so.

Add more comments for it in Documentation/dmaengine.txt?

Nevertheless, it is common practice for drivers to call prep_slave_sg() from atomic contexts. It sounds like your DMA driver is being fixed anyway, so these tasklets can be killed.

#8 lore raw

PATCH 1/2 serial/imx: add DMA support

Quoted 27 lines · Russell King - ARM Linux
Quoted 5 lines · Huang Shijie
Quoted 5 lines
Quoted 5 lines
Quoted 5 lines

1. How do you deal with transmitting the high-priority XON/XOFF ? ? ?characters (port->x_char) which occur with s/w flow control and ? ? ?the tty buffers fill up?
2. How do you deal with flow control in general? ?IOW, what happens ? ? ?when the remote end deasserts your CTS with h/w flow control enabled.

Quoted 7 lines

If the remote end deasserts my CTS, it means the remote will not send any data.

My DMA for RX will expire in the following steps:
[1] the UART only waits for 32 bytes time long
[2] the UART triggers an IDLE Condition Detect DMA.
[3] the dma_rx_callback() will release the DMA for Rx.

Err, hang on. ?I think you're totally confused about hardware flow control. ?Certainly you're not using the correct terms for what you're describing.

The CTS input normally controls the transmitter. ?In many hardware assisted hardware flow control setups, the deassertion of CTS merely prevents the transmitter starting a new character.

This shouldn't have any effect on the receiver of the same UART at all.

Quoted 3 lines
Quoted 3 lines
Quoted 3 lines
Quoted 3 lines

? ? ?How does your end deal with sending RTS according to flow control ? ? ?conditions?

Quoted 7 lines
Quoted 4 lines

If a CTS is received after we sent out a RTS, it will follow the steps:
?imx_int() --> imx_rtsint() --> uart_handle_cts_change() -->start_tx()

The start_tx() will create an TX DMA operation, and send out the data.

The generation of RTS (connected to the remote ends CTS signal) is supposed to control whether the remote end sends you characters. ?RTS

http://en.wikipedia.org/wiki/Flow_control#Hardware_flow_control

Quoted 1 line

From the wiki, the generation of RTS (assert by the "master end") is

used to send data
from the master to slave(the remote), not control the remote end sends me characters.

Best Regards
Huang Shijie

Quoted 6 lines

gets deasserted either by software control when you're running out of space to store the received characters, or (in the case of hardware assisted hardware flow control) your receivers FIFO fills above a certain watermark.

Quoted 1 line
Quoted 1 line
#9 lore raw

PATCH 1/2 serial/imx: add DMA support

Quoted 34 lines · Huang Shijie
Quoted 32 lines · Russell King - ARM Linux
Quoted 5 lines · Huang Shijie
Quoted 5 lines
Quoted 5 lines
Quoted 5 lines

1. How do you deal with transmitting the high-priority XON/XOFF ? ? ?characters (port->x_char) which occur with s/w flow control and ? ? ?the tty buffers fill up?
2. How do you deal with flow control in general? ?IOW, what happens ? ? ?when the remote end deasserts your CTS with h/w flow control enabled.

Quoted 7 lines

If the remote end deasserts my CTS, it means the remote will not send any data.

My DMA for RX will expire in the following steps:
[1] the UART only waits for 32 bytes time long
[2] the UART triggers an IDLE Condition Detect DMA.
[3] the dma_rx_callback() will release the DMA for Rx.

Err, hang on. ?I think you're totally confused about hardware flow control. ?Certainly you're not using the correct terms for what you're describing.

The CTS input normally controls the transmitter. ?In many hardware assisted hardware flow control setups, the deassertion of CTS merely prevents the transmitter starting a new character.

This shouldn't have any effect on the receiver of the same UART at all.

Quoted 7 lines
Quoted 3 lines
Quoted 3 lines
Quoted 3 lines

? ? ?How does your end deal with sending RTS according to flow control ? ? ?conditions?

If a CTS is received after we sent out a RTS, it will follow the steps:
?imx_int() --> imx_rtsint() --> uart_handle_cts_change() -->start_tx()

The start_tx() will create an TX DMA operation, and send out the data.

Quoted 8 lines
Quoted 2 lines

The generation of RTS (connected to the remote ends CTS signal) is supposed to control whether the remote end sends you characters. ?RTS

http://en.wikipedia.org/wiki/Flow_control#Hardware_flow_control

Quoted 1 line

From the wiki, the generation of RTS (assert by the "master end") is

used to send data
from the master to slave(the remote), not control the remote end sends me characters.

Well, there's a lot of confusion over RTS. Most implementations of it are as per this paragraph on the above page:

A non-standard symmetric alternative, commonly called "RTS/CTS handshaking," was developed by various equipment manufacturers. In this scheme, CTS is no longer a response to RTS; instead, CTS indicates permission from the DCE for the DTE to send data to the DCE, and RTS indicates permission from the DTE for the DCE to send data to the DTE. RTS and CTS are controlled by the DTE and DCE respectively, each independent of the other. This was eventually codified in version RS-232-E (actually TIA-232-E by that time) by defining a new signal, "RTR (Ready to Receive)," which is CCITT V.24 circuit 133. TIA-232-E and the corresponding international standards were updated to show that circuit 133, when implemented, shares the same pin as RTS (Request to Send), and that when 133 is in use, RTS is assumed by the DCE to be ON at all times

This is what is actually used by all NULL modem cables, serial consoles, and many modems can be (sensibly) configured to support it. Many modems can be configured via AT commands to respond as per the above paragraph too.

And this is the hardware flow control scheme implemented by the Linux Kernel for CRTSCTS, plus the hardware assisted hardware flow control provided by industry standard UARTs such as the 1675x and later UARTs.

#10 lore raw

PATCH 1/2 serial/imx: add DMA support

Quoted 57 lines · Russell King - ARM Linux
Quoted 32 lines · Huang Shijie
Quoted 30 lines · Russell King - ARM Linux
Quoted 5 lines · Huang Shijie
Quoted 5 lines
Quoted 5 lines
Quoted 5 lines

1. How do you deal with transmitting the high-priority XON/XOFF characters (port->x_char) which occur with s/w flow control and the tty buffers fill up?
2. How do you deal with flow control in general? IOW, what happens when the remote end deasserts your CTS with h/w flow control enabled.

Quoted 7 lines

If the remote end deasserts my CTS, it means the remote will not send any data.

My DMA for RX will expire in the following steps:
[1] the UART only waits for 32 bytes time long
[2] the UART triggers an IDLE Condition Detect DMA.
[3] the dma_rx_callback() will release the DMA for Rx.

Err, hang on. I think you're totally confused about hardware flow control. Certainly you're not using the correct terms for what you're describing.

The CTS input normally controls the transmitter. In many hardware assisted hardware flow control setups, the deassertion of CTS merely prevents the transmitter starting a new character.

This shouldn't have any effect on the receiver of the same UART at all.

Quoted 7 lines
Quoted 3 lines
Quoted 3 lines
Quoted 3 lines

How does your end deal with sending RTS according to flow control conditions?

If a CTS is received after we sent out a RTS, it will follow the steps:
imx_int() --> imx_rtsint() --> uart_handle_cts_change() -->start_tx()

The start_tx() will create an TX DMA operation, and send out the data.

Quoted 8 lines
Quoted 2 lines

The generation of RTS (connected to the remote ends CTS signal) is supposed to control whether the remote end sends you characters. RTS

http://en.wikipedia.org/wiki/Flow_control#Hardware_flow_control

Quoted 1 line

From the wiki, the generation of RTS (assert by the "master end") is

used to send data
from the master to slave(the remote), not control the remote end sends me characters.

Well, there's a lot of confusion over RTS. Most implementations of it are as per this paragraph on the above page:

A non-standard symmetric alternative, commonly called "RTS/CTS handshaking," was developed by various equipment manufacturers. In this scheme, CTS is no longer a response to RTS; instead, CTS indicates permission from the DCE for the DTE to send data to the DCE, and RTS indicates permission from the DTE for the DCE to send data to the DTE. RTS and CTS are controlled by the DTE and DCE respectively, each independent of the other. This was eventually codified in version RS-232-E (actually TIA-232-E by that time) by defining a new signal, "RTR (Ready to Receive)," which is CCITT V.24 circuit 133. TIA-232-E and the corresponding international standards were updated to show that circuit 133, when implemented, shares the same pin as RTS (Request to Send), and that when 133 is in use, RTS is assumed by the DCE to be ON at all times

Quoted 9 lines

This is what is actually used by all NULL modem cables, serial consoles, and many modems can be (sensibly) configured to support it. Many modems can be configured via AT commands to respond as per the above paragraph too.

And this is the hardware flow control scheme implemented by the Linux Kernel for CRTSCTS, plus the hardware assisted hardware flow control provided by industry standard UARTs such as the 1675x and later UARTs.

thanks a lot.

I will read the this explanation carefully, and fix my code.

Best Regards
Huang Shijie

--
To unsubscribe from this list: send the line "unsubscribe linux-serial" in the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html

maintainer
#11 lore raw

PATCH 1/2 serial/imx: add DMA support

In reply to Huang Shijie
Quoted 9 lines · Huang Shijie

Add the DMA support for uart RX and TX.

---
 .../bindings/tty/serial/fsl-imx-uart.txt           |    7 +
 drivers/tty/serial/imx.c                           |  386 +++++++++++++++++++-
 2 files changed, 389 insertions(+), 4 deletions(-)
drivers/tty/serial/imx.c +1 -0
+	enum dma_status status;

drivers/tty/serial/imx.c static inline void imx_transmit_buffer(struct imx_port *sport) · line 495

Quoted 10 lines
drivers/tty/serial/imx.c +10 -0
+	unsigned long flags;
+	int ret;
+
+	status = chan->device->device_tx_status(chan, (dma_cookie_t)NULL, NULL);
+	if (DMA_IN_PROGRESS == status)
+		return;
+
+	spin_lock_irqsave(&sport->port.lock, flags);
+	sport->tx_bytes = uart_circ_chars_pending(xmit);
+	if (sport->tx_bytes > 0) {

Instead of putting nearly the whole body of this function inside a 'if' you should return here.

Quoted 7 lines
drivers/tty/serial/imx.c +7 -0
+		if (xmit->tail > xmit->head) {
+			sport->dma_tx_nents = 2;
+			sg_init_table(sgl, 2);
+			sg_set_buf(sgl, xmit->buf + xmit->tail,
+					UART_XMIT_SIZE - xmit->tail);
+			sg_set_buf(&sgl[1], xmit->buf, xmit->head);
+		} else {

drivers/tty/serial/imx.c static inline void imx_transmit_buffer(struct imx_port *sport) · line 512

Quoted 10 lines
drivers/tty/serial/imx.c +10 -0
+			sport->dma_tx_nents = 1;
+			sg_init_one(sgl, xmit->buf + xmit->tail,
+					sport->tx_bytes);
+		}
+		spin_unlock_irqrestore(&sport->port.lock, flags);
+
+		ret = dma_map_sg(sport->port.dev, sgl,
+				sport->dma_tx_nents, DMA_TO_DEVICE);
+		if (ret == 0) {
+			pr_err("DMA mapping error for TX.\n");

Use dev_* functions. Whoever reads the above in the logs won't have a clue that it comes from the serial driver.

Quoted 12 lines
drivers/tty/serial/imx.c +12 -0
+			return;
+		}
+		desc = dmaengine_prep_slave_sg(chan, sgl,
+				sport->dma_tx_nents, DMA_MEM_TO_DEV, 0);
+		if (!desc) {
+			pr_err("We cannot prepare for the TX slave dma!\n");
+			return;
+		}
+		desc->callback = dma_tx_callback;
+		desc->callback_param = sport;
+
+		/* fire it */

drivers/tty/serial/imx.c static inline void imx_transmit_buffer(struct imx_port *sport)

Quoted 10 lines
drivers/tty/serial/imx.c +9 -0
+		dmaengine_submit(desc);
+		dma_async_issue_pending(chan);
+		return;
+	}
+	spin_unlock_irqrestore(&sport->port.lock, flags);
+	return;
+}
+
 
+/* see the "i.MX61 SDMA Scripts User Manual.doc" for the parameters */

I can't see how the manual helps here.

Please test this patch at least on one more SoC. There should be nothing i.MX6 specific in here, the fact that the i.MX6 is mentioned several times in the comments make me suspicious.

Sascha

Signature · 4 lines
Pengutronix e.K.                           |                             |
Industrial Linux Solutions                 | http://www.pengutronix.de/  |
Peiner Str. 6-8, 31137 Hildesheim, Germany | Phone: +49-5121-206917-0    |
Amtsgericht Hildesheim, HRA 2686           | Fax:   +49-5121-206917-5555 |
#12 lore raw

PATCH 1/2 serial/imx: add DMA support

Hi,

Quoted 2 lines
Quoted 1 line

+/* see the "i.MX61 SDMA Scripts User Manual.doc" for the parameters */

I can't see how the manual helps here.

sorry, the comment is in the wrong place. it should placed at configuring the slave_config.

Quoted 3 lines

Please test this patch at least on one more SoC. There should be nothing i.MX6 specific in here, the fact that the i.MX6 is mentioned several times in the comments make me suspicious.

There are bugs in the UART DMA code in the firmware before the imx6q. So we can not test the UART DMA except the imx6q arm2 board.

thanks

Huang Shijie

#13 lore raw

PATCH 1/2 serial/imx: add DMA support

Le vendredi 27 avril 2012 14:09:53 Huang Shijie a ?crit :

Quoted 9 lines

Hi,

Quoted 3 lines
Quoted 1 line

+/* see the "i.MX61 SDMA Scripts User Manual.doc" for the parameters */

I can't see how the manual helps here.

sorry, the comment is in the wrong place. it should placed at configuring the slave_config.

Quoted 6 lines
Quoted 3 lines

Please test this patch at least on one more SoC. There should be nothing i.MX6 specific in here, the fact that the i.MX6 is mentioned several times in the comments make me suspicious.

There are bugs in the UART DMA code in the firmware before the imx6q. So we can not test the UART DMA except the imx6q arm2 board.

Does this mean that there is no hope for a working uart dma on imx31 ?

Thanks,

Philippe

#14 lore raw

PATCH 1/2 serial/imx: add DMA support

Quoted 1 line · Philippe Rétornaz

Does this mean that there is no hope for a working uart dma on imx31 ?

yes.

only works in mx6q now.

BR
Huang Shijie

--
To unsubscribe from this list: send the line "unsubscribe linux-serial" in the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html

#15 lore raw

PATCH 1/2 serial/imx: add DMA support

In reply to Huang Shijie

Documentation/devicetree/bindings/tty/serial/fsl-imx-uart.txt Required properties: · line 15

Quoted 13 lines · Huang Shijie
Documentation/devicetree/bindings/tty/serial/fsl-imx-uart.txt +4 -0
index a9c0406..f27489d 100644
@@ -8,6 +8,10 @@ Required properties:
 Optional properties:
 - fsl,uart-has-rtscts : Indicate the uart has rts and cts
 - fsl,irda-mode : Indicate the uart supports irda mode
+- fsl,enable-dma : Indicate the uart supports DMA
+- fsl,uart-dma-events : contains the DMA events for RX and TX,
+                 The first is the RX event, while the other is TX.
+- fsl,enable-dte: Indicate the uart works in DTE mode
 

I think we should not add any ad-hoc DMA bindings for device drivers until we have a proper generic binding for DMA.

Arnd

#16 lore raw

PATCH 1/2 serial/imx: add DMA support

Hi,

Quoted 2 lines

I think we should not add any ad-hoc DMA bindings for device drivers until we have a proper generic binding for DMA.

I hope the generic binding for DMA becomes ready as soon as possilbe.

Best Regards
Huang Shijie

#17 lore raw

PATCH 2/2 ARM: MX6q: enable DMA support for UART2

In reply to Huang Shijie

add uart2 for mx6q-arm2 board, and enable the DMA support for it.

---
 arch/arm/boot/dts/imx6q-arm2.dts |    8 ++++++++
 1 files changed, 8 insertions(+), 0 deletions(-)
arch/arm/boot/dts/imx6q-arm2.dts +8 -0
index ce1c823..c9a6cb3 100644
@@ -46,6 +46,14 @@
 				status = "okay";
 			};
 
+			uart2: uart@021e8000 {
+				fsl,uart-has-rtscts;
+				fsl,enable-dma;
+				fsl,uart-dma-events = <27 28>;
+				fsl,enable-dte;
+				status = "okay";
+			};
+
 			uart4: uart@021f0000 {
 				status = "okay";
 			};
Signature · 1 line
1.7.0.4