diff mbox

[2/3] serial: mxs-auart: add the DMA support for mx28

Message ID 1350367386-7742-3-git-send-email-b32955@freescale.com (mailing list archive)
State New, archived
Headers show

Commit Message

Huang Shijie Oct. 16, 2012, 6:03 a.m. UTC
Only we meet the following conditions, we can enable the DMA support for
auart:

  (1) We enable the DMA support in the dts file, such as
      arch/arm/boot/dts/imx28.dtsi.

  (2) We enable the hardware flow control.

  (3) We use the mx28, not the mx23. Due to hardware bug(see errata: 2836),
      we can not add the DMA support to mx23.

Signed-off-by: Huang Shijie <b32955@freescale.com>
---
 .../bindings/tty/serial/fsl-mxs-auart.txt          |    7 +
 drivers/tty/serial/mxs-auart.c                     |  307 +++++++++++++++++++-
 2 files changed, 311 insertions(+), 3 deletions(-)

Comments

Shawn Guo Oct. 18, 2012, 3:20 a.m. UTC | #1
On Tue, Oct 16, 2012 at 02:03:05PM +0800, Huang Shijie wrote:
> Only we meet the following conditions, we can enable the DMA support for
> auart:
> 
>   (1) We enable the DMA support in the dts file, such as
>       arch/arm/boot/dts/imx28.dtsi.
> 
>   (2) We enable the hardware flow control.
> 
>   (3) We use the mx28, not the mx23. Due to hardware bug(see errata: 2836),
>       we can not add the DMA support to mx23.
> 
> Signed-off-by: Huang Shijie <b32955@freescale.com>
> ---
>  .../bindings/tty/serial/fsl-mxs-auart.txt          |    7 +
>  drivers/tty/serial/mxs-auart.c                     |  307 +++++++++++++++++++-
>  2 files changed, 311 insertions(+), 3 deletions(-)
> 
> diff --git a/Documentation/devicetree/bindings/tty/serial/fsl-mxs-auart.txt b/Documentation/devicetree/bindings/tty/serial/fsl-mxs-auart.txt
> index a154bf1..67e54b4 100644
> --- a/Documentation/devicetree/bindings/tty/serial/fsl-mxs-auart.txt
> +++ b/Documentation/devicetree/bindings/tty/serial/fsl-mxs-auart.txt
> @@ -6,11 +6,18 @@ Required properties:
>  - reg : Address and length of the register set for the device
>  - interrupts : Should contain the auart interrupt numbers
>  
> +Optional properties:
> +- fsl,auart-dma-channel : The DMA channels, the first is for RX, the other
> +			is for TX.
> +- fsl,auart-enable-dma : Enable the DMA support for the auart.
> +

If we want to have it decided by device tree, can we drop the property
and simply check if "fsl,auart-dma-channel" presents?

>  Example:
>  auart0: serial@8006a000 {
>  	compatible = "fsl,imx28-auart";
>  	reg = <0x8006a000 0x2000>;
>  	interrupts = <112 70 71>;
> +	fsl,auart-dma-channel = <8 9>;
> +	fsl,auart-enable-dma;
>  };
>  
>  Note: Each auart port should have an alias correctly numbered in "aliases"
> diff --git a/drivers/tty/serial/mxs-auart.c b/drivers/tty/serial/mxs-auart.c
> index cd9ec1d..2271330 100644
> --- a/drivers/tty/serial/mxs-auart.c
> +++ b/drivers/tty/serial/mxs-auart.c
> @@ -34,6 +34,8 @@
>  #include <linux/io.h>
>  #include <linux/pinctrl/consumer.h>
>  #include <linux/of_device.h>
> +#include <linux/dma-mapping.h>
> +#include <linux/fsl/mxs-dma.h>
>  
>  #include <asm/cacheflush.h>
>  
> @@ -76,7 +78,15 @@
>  
>  #define AUART_CTRL0_SFTRST			(1 << 31)
>  #define AUART_CTRL0_CLKGATE			(1 << 30)
> +#define AUART_CTRL0_RXTO_ENABLE			(1 << 27)
> +#define AUART_CTRL0_RXTIMEOUT(v)		(((v) & 0x7ff) << 16)
> +#define AUART_CTRL0_XFER_COUNT(v)		((v) & 0xffff)
>  
> +#define AUART_CTRL1_XFER_COUNT(v)		((v) & 0xffff)
> +
> +#define AUART_CTRL2_DMAONERR			(1 << 26)
> +#define AUART_CTRL2_TXDMAE			(1 << 25)
> +#define AUART_CTRL2_RXDMAE			(1 << 24)
>  #define AUART_CTRL2_CTSEN			(1 << 15)
>  #define AUART_CTRL2_RTSEN			(1 << 14)
>  #define AUART_CTRL2_RTS				(1 << 11)
> @@ -116,12 +126,15 @@
>  #define AUART_STAT_BERR				(1 << 18)
>  #define AUART_STAT_PERR				(1 << 17)
>  #define AUART_STAT_FERR				(1 << 16)
> +#define AUART_STAT_RXCOUNT_MASK			0xffff
>  
>  static struct uart_driver auart_driver;
>  
>  struct mxs_auart_port {
>  	struct uart_port port;
>  
> +#define MXS_AUART_DMA_CONFIG	0x1
> +#define MXS_AUART_DMA_ENABLED	0x2
>  	unsigned int flags;
>  	unsigned int ctrl;
>  
> @@ -130,16 +143,116 @@ struct mxs_auart_port {
>  	struct clk *clk;
>  	struct device *dev;
>  	struct platform_device *pdev;
> +
> +	/* for DMA */
> +	struct mxs_dma_data dma_data;
> +	int dma_channel_rx, dma_channel_tx;
> +	int dma_irq_rx, dma_irq_tx;
> +	int dma_channel;
> +
> +	struct scatterlist tx_sgl;
> +	struct dma_chan	*tx_dma_chan;
> +	void *tx_dma_buf;
> +
> +	struct scatterlist rx_sgl;
> +	struct dma_chan	*rx_dma_chan;
> +	void *rx_dma_buf;
>  };
>  
> +static inline bool auart_dma_enabled(struct mxs_auart_port *s)
> +{
> +	return s->flags & MXS_AUART_DMA_ENABLED;
> +}
> +
>  static void mxs_auart_stop_tx(struct uart_port *u);
>  
>  #define to_auart_port(u) container_of(u, struct mxs_auart_port, port)
>  
> +static inline void mxs_auart_tx_chars(struct mxs_auart_port *s);
> +
> +static void dma_tx_callback(void *param)
> +{
> +	struct mxs_auart_port *s = param;
> +	struct circ_buf *xmit = &s->port.state->xmit;
> +
> +	dma_unmap_sg(s->dev, &s->tx_sgl, 1, DMA_TO_DEVICE);
> +
> +	/* wake up the possible processes. */
> +	if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)
> +		uart_write_wakeup(&s->port);
> +
> +	mxs_auart_tx_chars(s);
> +}
> +
> +static int mxs_auart_dma_tx(struct mxs_auart_port *s, int size)
> +{
> +	struct dma_async_tx_descriptor *desc;
> +	struct scatterlist *sgl = &s->tx_sgl;
> +	struct dma_chan *channel = s->tx_dma_chan;
> +	u32 pio[1];

One element array looks strange to me.

> +
> +	/* [1] : send PIO. Note, the first pio word is CTRL1. */
> +	pio[0] = AUART_CTRL1_XFER_COUNT(size);
> +	desc = dmaengine_prep_slave_sg(channel, (struct scatterlist *)pio,
> +					1, DMA_TRANS_NONE, 0);
> +	if (!desc) {
> +		dev_err(s->dev, "step 1 error\n");
> +		return -EINVAL;
> +	}
> +
> +	/* [2] : set DMA buffer. */
> +	sg_init_one(sgl, s->tx_dma_buf, size);
> +	dma_map_sg(s->dev, sgl, 1, DMA_TO_DEVICE);
> +	desc = dmaengine_prep_slave_sg(channel, sgl,
> +			1, DMA_MEM_TO_DEV, DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
> +	if (!desc) {
> +		dev_err(s->dev, "step 2 error\n");
> +		return -EINVAL;
> +	}
> +
> +	/* [3] : submit the DMA */
> +	desc->callback = dma_tx_callback;
> +	desc->callback_param = s;
> +	dmaengine_submit(desc);
> +	dma_async_issue_pending(channel);
> +	return 0;
> +}
> +
>  static inline void mxs_auart_tx_chars(struct mxs_auart_port *s)

I'm not sure why this function is inline from the beginning.  It
becomes even more inappropriate after you add more codes below.

Shawn

>  {
>  	struct circ_buf *xmit = &s->port.state->xmit;
>  
> +	if (auart_dma_enabled(s)) {
> +		int i = 0;
> +		int size;
> +		void *buffer = s->tx_dma_buf;
> +		enum dma_status status;
> +
> +		/* Check whether there is pending DMA operations. */
> +		status = dmaengine_tx_status(s->tx_dma_chan, 0, NULL);
> +		if (status != DMA_SUCCESS)
> +			return;
> +
> +		while (!uart_circ_empty(xmit) && !uart_tx_stopped(&s->port)) {
> +			size = min_t(u32, UART_XMIT_SIZE - i,
> +				     CIRC_CNT_TO_END(xmit->head,
> +						     xmit->tail,
> +						     UART_XMIT_SIZE));
> +			memcpy(buffer + i, xmit->buf + xmit->tail, size);
> +			xmit->tail = (xmit->tail + size) & (UART_XMIT_SIZE - 1);
> +
> +			i += size;
> +			if (i >= UART_XMIT_SIZE)
> +				break;
> +		}
> +
> +		if (uart_tx_stopped(&s->port))
> +			mxs_auart_stop_tx(&s->port);
> +		if (i)
> +			mxs_auart_dma_tx(s, i);
> +		return;
> +	}
> +
>  	while (!(readl(s->port.membase + AUART_STAT) &
>  		 AUART_STAT_TXFF)) {
>  		if (s->port.x_char) {
> @@ -293,10 +406,155 @@ static u32 mxs_auart_get_mctrl(struct uart_port *u)
>  	return mctrl;
>  }
>  
> +static bool mxs_auart_dma_filter(struct dma_chan *chan, void *param)
> +{
> +	struct mxs_auart_port *s = param;
> +
> +	if (!mxs_dma_is_apbx(chan))
> +		return false;
> +
> +	if (s->dma_channel == chan->chan_id) {
> +		chan->private = &s->dma_data;
> +		return true;
> +	}
> +	return false;
> +}
> +
> +static int mxs_auart_dma_prep_rx(struct mxs_auart_port *s);
> +static void dma_rx_callback(void *arg)
> +{
> +	struct mxs_auart_port *s = (struct mxs_auart_port *) arg;
> +	struct tty_struct *tty = s->port.state->port.tty;
> +	int count;
> +	u32 stat;
> +
> +	stat = readl(s->port.membase + AUART_STAT);
> +	stat &= ~(AUART_STAT_OERR | AUART_STAT_BERR |
> +			AUART_STAT_PERR | AUART_STAT_FERR);
> +
> +	count = stat & AUART_STAT_RXCOUNT_MASK;
> +	tty_insert_flip_string(tty, s->rx_dma_buf, count);
> +
> +	writel(stat, s->port.membase + AUART_STAT);
> +	tty_flip_buffer_push(tty);
> +
> +	/* start the next DMA for RX. */
> +	mxs_auart_dma_prep_rx(s);
> +}
> +
> +static int mxs_auart_dma_prep_rx(struct mxs_auart_port *s)
> +{
> +	struct dma_async_tx_descriptor *desc;
> +	struct scatterlist *sgl = &s->rx_sgl;
> +	struct dma_chan *channel = s->rx_dma_chan;
> +	u32 pio[1];
> +
> +	/* [1] : send PIO */
> +	pio[0] = AUART_CTRL0_RXTO_ENABLE
> +		| AUART_CTRL0_RXTIMEOUT(0x80)
> +		| AUART_CTRL0_XFER_COUNT(UART_XMIT_SIZE);
> +	desc = dmaengine_prep_slave_sg(channel, (struct scatterlist *)pio,
> +					1, DMA_TRANS_NONE, 0);
> +	if (!desc) {
> +		dev_err(s->dev, "step 1 error\n");
> +		return -EINVAL;
> +	}
> +
> +	/* [2] : send DMA request */
> +	sg_init_one(sgl, s->rx_dma_buf, UART_XMIT_SIZE);
> +	dma_map_sg(s->dev, sgl, 1, DMA_FROM_DEVICE);
> +	desc = dmaengine_prep_slave_sg(channel, sgl, 1, DMA_DEV_TO_MEM,
> +					DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
> +	if (!desc) {
> +		dev_err(s->dev, "step 2 error\n");
> +		return -1;
> +	}
> +
> +	/* [3] : submit the DMA, but do not issue it. */
> +	desc->callback = dma_rx_callback;
> +	desc->callback_param = s;
> +	dmaengine_submit(desc);
> +	dma_async_issue_pending(channel);
> +	return 0;
> +}
> +
> +static void mxs_auart_dma_exit_channel(struct mxs_auart_port *s)
> +{
> +	if (s->tx_dma_chan) {
> +		dma_release_channel(s->tx_dma_chan);
> +		s->tx_dma_chan = NULL;
> +	}
> +	if (s->rx_dma_chan) {
> +		dma_release_channel(s->rx_dma_chan);
> +		s->rx_dma_chan = NULL;
> +	}
> +
> +	kfree(s->tx_dma_buf);
> +	kfree(s->rx_dma_buf);
> +	s->tx_dma_buf = NULL;
> +	s->rx_dma_buf = NULL;
> +}
> +
> +static void mxs_auart_dma_exit(struct mxs_auart_port *s)
> +{
> +
> +	writel(AUART_CTRL2_TXDMAE | AUART_CTRL2_RXDMAE | AUART_CTRL2_DMAONERR,
> +		s->port.membase + AUART_CTRL2_CLR);
> +
> +	mxs_auart_dma_exit_channel(s);
> +	s->flags &= ~MXS_AUART_DMA_ENABLED;
> +}
> +
> +static int mxs_auart_dma_init(struct mxs_auart_port *s)
> +{
> +	dma_cap_mask_t mask;
> +
> +	if (auart_dma_enabled(s))
> +		return 0;
> +
> +	/* We do not get the right DMA channels. */
> +	if (s->dma_channel_rx == -1 || s->dma_channel_rx == -1)
> +		return -EINVAL;
> +
> +	/* init for RX */
> +	dma_cap_zero(mask);
> +	dma_cap_set(DMA_SLAVE, mask);
> +	s->dma_channel = s->dma_channel_rx;
> +	s->dma_data.chan_irq = s->dma_irq_rx;
> +	s->rx_dma_chan = dma_request_channel(mask, mxs_auart_dma_filter, s);
> +	if (!s->rx_dma_chan)
> +		goto err_out;
> +	s->rx_dma_buf = kzalloc(UART_XMIT_SIZE, GFP_KERNEL | GFP_DMA);
> +	if (!s->rx_dma_buf)
> +		goto err_out;
> +
> +	/* init for TX */
> +	s->dma_channel = s->dma_channel_tx;
> +	s->dma_data.chan_irq = s->dma_irq_tx;
> +	s->tx_dma_chan = dma_request_channel(mask, mxs_auart_dma_filter, s);
> +	if (!s->tx_dma_chan)
> +		goto err_out;
> +	s->tx_dma_buf = kzalloc(UART_XMIT_SIZE, GFP_KERNEL | GFP_DMA);
> +	if (!s->tx_dma_buf)
> +		goto err_out;
> +
> +	/* set the flags */
> +	s->flags |= MXS_AUART_DMA_ENABLED;
> +	dev_dbg(s->dev, "enabled the DMA support.");
> +
> +	return 0;
> +
> +err_out:
> +	mxs_auart_dma_exit_channel(s);
> +	return -EINVAL;
> +
> +}
> +
>  static void mxs_auart_settermios(struct uart_port *u,
>  				 struct ktermios *termios,
>  				 struct ktermios *old)
>  {
> +	struct mxs_auart_port *s = to_auart_port(u);
>  	u32 bm, ctrl, ctrl2, div;
>  	unsigned int cflag, baud;
>  
> @@ -368,10 +626,23 @@ static void mxs_auart_settermios(struct uart_port *u,
>  		ctrl |= AUART_LINECTRL_STP2;
>  
>  	/* figure out the hardware flow control settings */
> -	if (cflag & CRTSCTS)
> +	if (cflag & CRTSCTS) {
> +		/*
> +		 * The DMA has a bug(see errata:2836) in mx23.
> +		 * So we can not implement the DMA for auart in mx23,
> +		 * we can only implement the DMA support for auart
> +		 * in mx28.
> +		 */
> +		if (!AUART_IS_MX23(s) && (s->flags & MXS_AUART_DMA_CONFIG)) {
> +			if (!mxs_auart_dma_init(s))
> +				/* enable DMA tranfer */
> +				ctrl2 |= AUART_CTRL2_TXDMAE | AUART_CTRL2_RXDMAE
> +				       | AUART_CTRL2_DMAONERR;
> +		}
>  		ctrl2 |= AUART_CTRL2_CTSEN | AUART_CTRL2_RTSEN;
> -	else
> +	} else {
>  		ctrl2 &= ~(AUART_CTRL2_CTSEN | AUART_CTRL2_RTSEN);
> +	}
>  
>  	/* set baud rate */
>  	baud = uart_get_baud_rate(u, termios, old, 0, u->uartclk);
> @@ -383,6 +654,17 @@ static void mxs_auart_settermios(struct uart_port *u,
>  	writel(ctrl2, u->membase + AUART_CTRL2);
>  
>  	uart_update_timeout(u, termios->c_cflag, baud);
> +
> +	/* prepare for the DMA RX. */
> +	if (auart_dma_enabled(s)) {
> +		if (!mxs_auart_dma_prep_rx(s)) {
> +			/* Disable the normal RX interrupt. */
> +			writel(AUART_INTR_RXIEN, u->membase + AUART_INTR_CLR);
> +		} else {
> +			mxs_auart_dma_exit(s);
> +			dev_err(s->dev, "We can not start up the DMA.\n");
> +		}
> +	}
>  }
>  
>  static irqreturn_t mxs_auart_irq_handle(int irq, void *context)
> @@ -461,6 +743,9 @@ static void mxs_auart_shutdown(struct uart_port *u)
>  {
>  	struct mxs_auart_port *s = to_auart_port(u);
>  
> +	if (auart_dma_enabled(s))
> +		mxs_auart_dma_exit(s);
> +
>  	writel(AUART_CTRL2_UARTEN, u->membase + AUART_CTRL2_CLR);
>  
>  	writel(AUART_INTR_RXIEN | AUART_INTR_RTIEN | AUART_INTR_CTSMIEN,
> @@ -707,6 +992,7 @@ static int serial_mxs_probe_dt(struct mxs_auart_port *s,
>  		struct platform_device *pdev)
>  {
>  	struct device_node *np = pdev->dev.of_node;
> +	u32 dma_channel[2];
>  	int ret;
>  
>  	if (!np)
> @@ -722,6 +1008,22 @@ static int serial_mxs_probe_dt(struct mxs_auart_port *s,
>  
>  	pdev->id_entry = of_match_device(mxs_auart_dt_ids, &pdev->dev)->data;
>  
> +	ret = of_property_read_u32_array(np, "fsl,auart-dma-channel",
> +					dma_channel, 2);
> +	if (ret == 0) {
> +		s->dma_channel_rx = dma_channel[0];
> +		s->dma_channel_tx = dma_channel[1];
> +	} else {
> +		s->dma_channel_rx = -1;
> +		s->dma_channel_tx = -1;
> +	}
> +
> +	s->dma_irq_rx = platform_get_irq(pdev, 1);
> +	s->dma_irq_tx = platform_get_irq(pdev, 2);
> +
> +	if (of_property_read_bool(np, "fsl,auart-enable-dma"))
> +		s->flags |= MXS_AUART_DMA_CONFIG;
> +
>  	return 0;
>  }
>  
> @@ -772,7 +1074,6 @@ static int __devinit mxs_auart_probe(struct platform_device *pdev)
>  	s->port.type = PORT_IMX;
>  	s->port.dev = s->dev = get_device(&pdev->dev);
>  
> -	s->flags = 0;
>  	s->ctrl = 0;
>  	s->pdev = pdev;
>  
> -- 
> 1.7.0.4
> 
>
Huang Shijie Oct. 18, 2012, 6:22 a.m. UTC | #2
? 2012?10?18? 11:20, Shawn Guo ??:
> On Tue, Oct 16, 2012 at 02:03:05PM +0800, Huang Shijie wrote:
>> Only we meet the following conditions, we can enable the DMA support for
>> auart:
>>
>> @@ -6,11 +6,18 @@ Required properties:
>>   - reg : Address and length of the register set for the device
>>   - interrupts : Should contain the auart interrupt numbers
>>
>> +Optional properties:
>> +- fsl,auart-dma-channel : The DMA channels, the first is for RX, the other
>> +			is for TX.
>> +- fsl,auart-enable-dma : Enable the DMA support for the auart.
>> +
> If we want to have it decided by device tree, can we drop the property
> and simply check if "fsl,auart-dma-channel" presents?
It's ok to me. fix it in next version.
>>   Example:
>>   auart0: serial@8006a000 {
>>   	compatible = "fsl,imx28-auart";
>>   	reg =<0x8006a000 0x2000>;
>>   	interrupts =<112 70 71>;
>> +	fsl,auart-dma-channel =<8 9>;
>> +	fsl,auart-enable-dma;
>>   };
>>
>>   Note: Each auart port should have an alias correctly numbered in "aliases"
>> diff --git a/drivers/tty/serial/mxs-auart.c b/drivers/tty/serial/mxs-auart.c
>> index cd9ec1d..2271330 100644
>> --- a/drivers/tty/serial/mxs-auart.c
>> +++ b/drivers/tty/serial/mxs-auart.c
>> @@ -34,6 +34,8 @@
>>   #include<linux/io.h>
>>   #include<linux/pinctrl/consumer.h>
>>   #include<linux/of_device.h>
>> +#include<linux/dma-mapping.h>
>> +#include<linux/fsl/mxs-dma.h>
>>
>>   #include<asm/cacheflush.h>
>>
>> @@ -76,7 +78,15 @@
>>
>>   #define AUART_CTRL0_SFTRST			(1<<  31)
>>   #define AUART_CTRL0_CLKGATE			(1<<  30)
>> +#define AUART_CTRL0_RXTO_ENABLE			(1<<  27)
>> +#define AUART_CTRL0_RXTIMEOUT(v)		(((v)&  0x7ff)<<  16)
>> +#define AUART_CTRL0_XFER_COUNT(v)		((v)&  0xffff)
>>
>> +#define AUART_CTRL1_XFER_COUNT(v)		((v)&  0xffff)
>> +
>> +#define AUART_CTRL2_DMAONERR			(1<<  26)
>> +#define AUART_CTRL2_TXDMAE			(1<<  25)
>> +#define AUART_CTRL2_RXDMAE			(1<<  24)
>>   #define AUART_CTRL2_CTSEN			(1<<  15)
>>   #define AUART_CTRL2_RTSEN			(1<<  14)
>>   #define AUART_CTRL2_RTS				(1<<  11)
>> @@ -116,12 +126,15 @@
>>   #define AUART_STAT_BERR				(1<<  18)
>>   #define AUART_STAT_PERR				(1<<  17)
>>   #define AUART_STAT_FERR				(1<<  16)
>> +#define AUART_STAT_RXCOUNT_MASK			0xffff
>>
>>   static struct uart_driver auart_driver;
>>
>>   struct mxs_auart_port {
>>   	struct uart_port port;
>>
>> +#define MXS_AUART_DMA_CONFIG	0x1
>> +#define MXS_AUART_DMA_ENABLED	0x2
>>   	unsigned int flags;
>>   	unsigned int ctrl;
>>
>> @@ -130,16 +143,116 @@ struct mxs_auart_port {
>>   	struct clk *clk;
>>   	struct device *dev;
>>   	struct platform_device *pdev;
>> +
>> +	/* for DMA */
>> +	struct mxs_dma_data dma_data;
>> +	int dma_channel_rx, dma_channel_tx;
>> +	int dma_irq_rx, dma_irq_tx;
>> +	int dma_channel;
>> +
>> +	struct scatterlist tx_sgl;
>> +	struct dma_chan	*tx_dma_chan;
>> +	void *tx_dma_buf;
>> +
>> +	struct scatterlist rx_sgl;
>> +	struct dma_chan	*rx_dma_chan;
>> +	void *rx_dma_buf;
>>   };
>>
>> +static inline bool auart_dma_enabled(struct mxs_auart_port *s)
>> +{
>> +	return s->flags&  MXS_AUART_DMA_ENABLED;
>> +}
>> +
>>   static void mxs_auart_stop_tx(struct uart_port *u);
>>
>>   #define to_auart_port(u) container_of(u, struct mxs_auart_port, port)
>>
>> +static inline void mxs_auart_tx_chars(struct mxs_auart_port *s);
>> +
>> +static void dma_tx_callback(void *param)
>> +{
>> +	struct mxs_auart_port *s = param;
>> +	struct circ_buf *xmit =&s->port.state->xmit;
>> +
>> +	dma_unmap_sg(s->dev,&s->tx_sgl, 1, DMA_TO_DEVICE);
>> +
>> +	/* wake up the possible processes. */
>> +	if (uart_circ_chars_pending(xmit)<  WAKEUP_CHARS)
>> +		uart_write_wakeup(&s->port);
>> +
>> +	mxs_auart_tx_chars(s);
>> +}
>> +
>> +static int mxs_auart_dma_tx(struct mxs_auart_port *s, int size)
>> +{
>> +	struct dma_async_tx_descriptor *desc;
>> +	struct scatterlist *sgl =&s->tx_sgl;
>> +	struct dma_chan *channel = s->tx_dma_chan;
>> +	u32 pio[1];
> One element array looks strange to me.
got it.
>> +
>> +	/* [1] : send PIO. Note, the first pio word is CTRL1. */
>> +	pio[0] = AUART_CTRL1_XFER_COUNT(size);
>> +	desc = dmaengine_prep_slave_sg(channel, (struct scatterlist *)pio,
>> +					1, DMA_TRANS_NONE, 0);
>> +	if (!desc) {
>> +		dev_err(s->dev, "step 1 error\n");
>> +		return -EINVAL;
>> +	}
>> +
>> +	/* [2] : set DMA buffer. */
>> +	sg_init_one(sgl, s->tx_dma_buf, size);
>> +	dma_map_sg(s->dev, sgl, 1, DMA_TO_DEVICE);
>> +	desc = dmaengine_prep_slave_sg(channel, sgl,
>> +			1, DMA_MEM_TO_DEV, DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
>> +	if (!desc) {
>> +		dev_err(s->dev, "step 2 error\n");
>> +		return -EINVAL;
>> +	}
>> +
>> +	/* [3] : submit the DMA */
>> +	desc->callback = dma_tx_callback;
>> +	desc->callback_param = s;
>> +	dmaengine_submit(desc);
>> +	dma_async_issue_pending(channel);
>> +	return 0;
>> +}
>> +
>>   static inline void mxs_auart_tx_chars(struct mxs_auart_port *s)
> I'm not sure why this function is inline from the beginning.  It
yes. The inline is not proper.

Best Regards
Huang Shijie
diff mbox

Patch

diff --git a/Documentation/devicetree/bindings/tty/serial/fsl-mxs-auart.txt b/Documentation/devicetree/bindings/tty/serial/fsl-mxs-auart.txt
index a154bf1..67e54b4 100644
--- a/Documentation/devicetree/bindings/tty/serial/fsl-mxs-auart.txt
+++ b/Documentation/devicetree/bindings/tty/serial/fsl-mxs-auart.txt
@@ -6,11 +6,18 @@  Required properties:
 - reg : Address and length of the register set for the device
 - interrupts : Should contain the auart interrupt numbers
 
+Optional properties:
+- fsl,auart-dma-channel : The DMA channels, the first is for RX, the other
+			is for TX.
+- fsl,auart-enable-dma : Enable the DMA support for the auart.
+
 Example:
 auart0: serial@8006a000 {
 	compatible = "fsl,imx28-auart";
 	reg = <0x8006a000 0x2000>;
 	interrupts = <112 70 71>;
+	fsl,auart-dma-channel = <8 9>;
+	fsl,auart-enable-dma;
 };
 
 Note: Each auart port should have an alias correctly numbered in "aliases"
diff --git a/drivers/tty/serial/mxs-auart.c b/drivers/tty/serial/mxs-auart.c
index cd9ec1d..2271330 100644
--- a/drivers/tty/serial/mxs-auart.c
+++ b/drivers/tty/serial/mxs-auart.c
@@ -34,6 +34,8 @@ 
 #include <linux/io.h>
 #include <linux/pinctrl/consumer.h>
 #include <linux/of_device.h>
+#include <linux/dma-mapping.h>
+#include <linux/fsl/mxs-dma.h>
 
 #include <asm/cacheflush.h>
 
@@ -76,7 +78,15 @@ 
 
 #define AUART_CTRL0_SFTRST			(1 << 31)
 #define AUART_CTRL0_CLKGATE			(1 << 30)
+#define AUART_CTRL0_RXTO_ENABLE			(1 << 27)
+#define AUART_CTRL0_RXTIMEOUT(v)		(((v) & 0x7ff) << 16)
+#define AUART_CTRL0_XFER_COUNT(v)		((v) & 0xffff)
 
+#define AUART_CTRL1_XFER_COUNT(v)		((v) & 0xffff)
+
+#define AUART_CTRL2_DMAONERR			(1 << 26)
+#define AUART_CTRL2_TXDMAE			(1 << 25)
+#define AUART_CTRL2_RXDMAE			(1 << 24)
 #define AUART_CTRL2_CTSEN			(1 << 15)
 #define AUART_CTRL2_RTSEN			(1 << 14)
 #define AUART_CTRL2_RTS				(1 << 11)
@@ -116,12 +126,15 @@ 
 #define AUART_STAT_BERR				(1 << 18)
 #define AUART_STAT_PERR				(1 << 17)
 #define AUART_STAT_FERR				(1 << 16)
+#define AUART_STAT_RXCOUNT_MASK			0xffff
 
 static struct uart_driver auart_driver;
 
 struct mxs_auart_port {
 	struct uart_port port;
 
+#define MXS_AUART_DMA_CONFIG	0x1
+#define MXS_AUART_DMA_ENABLED	0x2
 	unsigned int flags;
 	unsigned int ctrl;
 
@@ -130,16 +143,116 @@  struct mxs_auart_port {
 	struct clk *clk;
 	struct device *dev;
 	struct platform_device *pdev;
+
+	/* for DMA */
+	struct mxs_dma_data dma_data;
+	int dma_channel_rx, dma_channel_tx;
+	int dma_irq_rx, dma_irq_tx;
+	int dma_channel;
+
+	struct scatterlist tx_sgl;
+	struct dma_chan	*tx_dma_chan;
+	void *tx_dma_buf;
+
+	struct scatterlist rx_sgl;
+	struct dma_chan	*rx_dma_chan;
+	void *rx_dma_buf;
 };
 
+static inline bool auart_dma_enabled(struct mxs_auart_port *s)
+{
+	return s->flags & MXS_AUART_DMA_ENABLED;
+}
+
 static void mxs_auart_stop_tx(struct uart_port *u);
 
 #define to_auart_port(u) container_of(u, struct mxs_auart_port, port)
 
+static inline void mxs_auart_tx_chars(struct mxs_auart_port *s);
+
+static void dma_tx_callback(void *param)
+{
+	struct mxs_auart_port *s = param;
+	struct circ_buf *xmit = &s->port.state->xmit;
+
+	dma_unmap_sg(s->dev, &s->tx_sgl, 1, DMA_TO_DEVICE);
+
+	/* wake up the possible processes. */
+	if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)
+		uart_write_wakeup(&s->port);
+
+	mxs_auart_tx_chars(s);
+}
+
+static int mxs_auart_dma_tx(struct mxs_auart_port *s, int size)
+{
+	struct dma_async_tx_descriptor *desc;
+	struct scatterlist *sgl = &s->tx_sgl;
+	struct dma_chan *channel = s->tx_dma_chan;
+	u32 pio[1];
+
+	/* [1] : send PIO. Note, the first pio word is CTRL1. */
+	pio[0] = AUART_CTRL1_XFER_COUNT(size);
+	desc = dmaengine_prep_slave_sg(channel, (struct scatterlist *)pio,
+					1, DMA_TRANS_NONE, 0);
+	if (!desc) {
+		dev_err(s->dev, "step 1 error\n");
+		return -EINVAL;
+	}
+
+	/* [2] : set DMA buffer. */
+	sg_init_one(sgl, s->tx_dma_buf, size);
+	dma_map_sg(s->dev, sgl, 1, DMA_TO_DEVICE);
+	desc = dmaengine_prep_slave_sg(channel, sgl,
+			1, DMA_MEM_TO_DEV, DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
+	if (!desc) {
+		dev_err(s->dev, "step 2 error\n");
+		return -EINVAL;
+	}
+
+	/* [3] : submit the DMA */
+	desc->callback = dma_tx_callback;
+	desc->callback_param = s;
+	dmaengine_submit(desc);
+	dma_async_issue_pending(channel);
+	return 0;
+}
+
 static inline void mxs_auart_tx_chars(struct mxs_auart_port *s)
 {
 	struct circ_buf *xmit = &s->port.state->xmit;
 
+	if (auart_dma_enabled(s)) {
+		int i = 0;
+		int size;
+		void *buffer = s->tx_dma_buf;
+		enum dma_status status;
+
+		/* Check whether there is pending DMA operations. */
+		status = dmaengine_tx_status(s->tx_dma_chan, 0, NULL);
+		if (status != DMA_SUCCESS)
+			return;
+
+		while (!uart_circ_empty(xmit) && !uart_tx_stopped(&s->port)) {
+			size = min_t(u32, UART_XMIT_SIZE - i,
+				     CIRC_CNT_TO_END(xmit->head,
+						     xmit->tail,
+						     UART_XMIT_SIZE));
+			memcpy(buffer + i, xmit->buf + xmit->tail, size);
+			xmit->tail = (xmit->tail + size) & (UART_XMIT_SIZE - 1);
+
+			i += size;
+			if (i >= UART_XMIT_SIZE)
+				break;
+		}
+
+		if (uart_tx_stopped(&s->port))
+			mxs_auart_stop_tx(&s->port);
+		if (i)
+			mxs_auart_dma_tx(s, i);
+		return;
+	}
+
 	while (!(readl(s->port.membase + AUART_STAT) &
 		 AUART_STAT_TXFF)) {
 		if (s->port.x_char) {
@@ -293,10 +406,155 @@  static u32 mxs_auart_get_mctrl(struct uart_port *u)
 	return mctrl;
 }
 
+static bool mxs_auart_dma_filter(struct dma_chan *chan, void *param)
+{
+	struct mxs_auart_port *s = param;
+
+	if (!mxs_dma_is_apbx(chan))
+		return false;
+
+	if (s->dma_channel == chan->chan_id) {
+		chan->private = &s->dma_data;
+		return true;
+	}
+	return false;
+}
+
+static int mxs_auart_dma_prep_rx(struct mxs_auart_port *s);
+static void dma_rx_callback(void *arg)
+{
+	struct mxs_auart_port *s = (struct mxs_auart_port *) arg;
+	struct tty_struct *tty = s->port.state->port.tty;
+	int count;
+	u32 stat;
+
+	stat = readl(s->port.membase + AUART_STAT);
+	stat &= ~(AUART_STAT_OERR | AUART_STAT_BERR |
+			AUART_STAT_PERR | AUART_STAT_FERR);
+
+	count = stat & AUART_STAT_RXCOUNT_MASK;
+	tty_insert_flip_string(tty, s->rx_dma_buf, count);
+
+	writel(stat, s->port.membase + AUART_STAT);
+	tty_flip_buffer_push(tty);
+
+	/* start the next DMA for RX. */
+	mxs_auart_dma_prep_rx(s);
+}
+
+static int mxs_auart_dma_prep_rx(struct mxs_auart_port *s)
+{
+	struct dma_async_tx_descriptor *desc;
+	struct scatterlist *sgl = &s->rx_sgl;
+	struct dma_chan *channel = s->rx_dma_chan;
+	u32 pio[1];
+
+	/* [1] : send PIO */
+	pio[0] = AUART_CTRL0_RXTO_ENABLE
+		| AUART_CTRL0_RXTIMEOUT(0x80)
+		| AUART_CTRL0_XFER_COUNT(UART_XMIT_SIZE);
+	desc = dmaengine_prep_slave_sg(channel, (struct scatterlist *)pio,
+					1, DMA_TRANS_NONE, 0);
+	if (!desc) {
+		dev_err(s->dev, "step 1 error\n");
+		return -EINVAL;
+	}
+
+	/* [2] : send DMA request */
+	sg_init_one(sgl, s->rx_dma_buf, UART_XMIT_SIZE);
+	dma_map_sg(s->dev, sgl, 1, DMA_FROM_DEVICE);
+	desc = dmaengine_prep_slave_sg(channel, sgl, 1, DMA_DEV_TO_MEM,
+					DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
+	if (!desc) {
+		dev_err(s->dev, "step 2 error\n");
+		return -1;
+	}
+
+	/* [3] : submit the DMA, but do not issue it. */
+	desc->callback = dma_rx_callback;
+	desc->callback_param = s;
+	dmaengine_submit(desc);
+	dma_async_issue_pending(channel);
+	return 0;
+}
+
+static void mxs_auart_dma_exit_channel(struct mxs_auart_port *s)
+{
+	if (s->tx_dma_chan) {
+		dma_release_channel(s->tx_dma_chan);
+		s->tx_dma_chan = NULL;
+	}
+	if (s->rx_dma_chan) {
+		dma_release_channel(s->rx_dma_chan);
+		s->rx_dma_chan = NULL;
+	}
+
+	kfree(s->tx_dma_buf);
+	kfree(s->rx_dma_buf);
+	s->tx_dma_buf = NULL;
+	s->rx_dma_buf = NULL;
+}
+
+static void mxs_auart_dma_exit(struct mxs_auart_port *s)
+{
+
+	writel(AUART_CTRL2_TXDMAE | AUART_CTRL2_RXDMAE | AUART_CTRL2_DMAONERR,
+		s->port.membase + AUART_CTRL2_CLR);
+
+	mxs_auart_dma_exit_channel(s);
+	s->flags &= ~MXS_AUART_DMA_ENABLED;
+}
+
+static int mxs_auart_dma_init(struct mxs_auart_port *s)
+{
+	dma_cap_mask_t mask;
+
+	if (auart_dma_enabled(s))
+		return 0;
+
+	/* We do not get the right DMA channels. */
+	if (s->dma_channel_rx == -1 || s->dma_channel_rx == -1)
+		return -EINVAL;
+
+	/* init for RX */
+	dma_cap_zero(mask);
+	dma_cap_set(DMA_SLAVE, mask);
+	s->dma_channel = s->dma_channel_rx;
+	s->dma_data.chan_irq = s->dma_irq_rx;
+	s->rx_dma_chan = dma_request_channel(mask, mxs_auart_dma_filter, s);
+	if (!s->rx_dma_chan)
+		goto err_out;
+	s->rx_dma_buf = kzalloc(UART_XMIT_SIZE, GFP_KERNEL | GFP_DMA);
+	if (!s->rx_dma_buf)
+		goto err_out;
+
+	/* init for TX */
+	s->dma_channel = s->dma_channel_tx;
+	s->dma_data.chan_irq = s->dma_irq_tx;
+	s->tx_dma_chan = dma_request_channel(mask, mxs_auart_dma_filter, s);
+	if (!s->tx_dma_chan)
+		goto err_out;
+	s->tx_dma_buf = kzalloc(UART_XMIT_SIZE, GFP_KERNEL | GFP_DMA);
+	if (!s->tx_dma_buf)
+		goto err_out;
+
+	/* set the flags */
+	s->flags |= MXS_AUART_DMA_ENABLED;
+	dev_dbg(s->dev, "enabled the DMA support.");
+
+	return 0;
+
+err_out:
+	mxs_auart_dma_exit_channel(s);
+	return -EINVAL;
+
+}
+
 static void mxs_auart_settermios(struct uart_port *u,
 				 struct ktermios *termios,
 				 struct ktermios *old)
 {
+	struct mxs_auart_port *s = to_auart_port(u);
 	u32 bm, ctrl, ctrl2, div;
 	unsigned int cflag, baud;
 
@@ -368,10 +626,23 @@  static void mxs_auart_settermios(struct uart_port *u,
 		ctrl |= AUART_LINECTRL_STP2;
 
 	/* figure out the hardware flow control settings */
-	if (cflag & CRTSCTS)
+	if (cflag & CRTSCTS) {
+		/*
+		 * The DMA has a bug(see errata:2836) in mx23.
+		 * So we can not implement the DMA for auart in mx23,
+		 * we can only implement the DMA support for auart
+		 * in mx28.
+		 */
+		if (!AUART_IS_MX23(s) && (s->flags & MXS_AUART_DMA_CONFIG)) {
+			if (!mxs_auart_dma_init(s))
+				/* enable DMA tranfer */
+				ctrl2 |= AUART_CTRL2_TXDMAE | AUART_CTRL2_RXDMAE
+				       | AUART_CTRL2_DMAONERR;
+		}
 		ctrl2 |= AUART_CTRL2_CTSEN | AUART_CTRL2_RTSEN;
-	else
+	} else {
 		ctrl2 &= ~(AUART_CTRL2_CTSEN | AUART_CTRL2_RTSEN);
+	}
 
 	/* set baud rate */
 	baud = uart_get_baud_rate(u, termios, old, 0, u->uartclk);
@@ -383,6 +654,17 @@  static void mxs_auart_settermios(struct uart_port *u,
 	writel(ctrl2, u->membase + AUART_CTRL2);
 
 	uart_update_timeout(u, termios->c_cflag, baud);
+
+	/* prepare for the DMA RX. */
+	if (auart_dma_enabled(s)) {
+		if (!mxs_auart_dma_prep_rx(s)) {
+			/* Disable the normal RX interrupt. */
+			writel(AUART_INTR_RXIEN, u->membase + AUART_INTR_CLR);
+		} else {
+			mxs_auart_dma_exit(s);
+			dev_err(s->dev, "We can not start up the DMA.\n");
+		}
+	}
 }
 
 static irqreturn_t mxs_auart_irq_handle(int irq, void *context)
@@ -461,6 +743,9 @@  static void mxs_auart_shutdown(struct uart_port *u)
 {
 	struct mxs_auart_port *s = to_auart_port(u);
 
+	if (auart_dma_enabled(s))
+		mxs_auart_dma_exit(s);
+
 	writel(AUART_CTRL2_UARTEN, u->membase + AUART_CTRL2_CLR);
 
 	writel(AUART_INTR_RXIEN | AUART_INTR_RTIEN | AUART_INTR_CTSMIEN,
@@ -707,6 +992,7 @@  static int serial_mxs_probe_dt(struct mxs_auart_port *s,
 		struct platform_device *pdev)
 {
 	struct device_node *np = pdev->dev.of_node;
+	u32 dma_channel[2];
 	int ret;
 
 	if (!np)
@@ -722,6 +1008,22 @@  static int serial_mxs_probe_dt(struct mxs_auart_port *s,
 
 	pdev->id_entry = of_match_device(mxs_auart_dt_ids, &pdev->dev)->data;
 
+	ret = of_property_read_u32_array(np, "fsl,auart-dma-channel",
+					dma_channel, 2);
+	if (ret == 0) {
+		s->dma_channel_rx = dma_channel[0];
+		s->dma_channel_tx = dma_channel[1];
+	} else {
+		s->dma_channel_rx = -1;
+		s->dma_channel_tx = -1;
+	}
+
+	s->dma_irq_rx = platform_get_irq(pdev, 1);
+	s->dma_irq_tx = platform_get_irq(pdev, 2);
+
+	if (of_property_read_bool(np, "fsl,auart-enable-dma"))
+		s->flags |= MXS_AUART_DMA_CONFIG;
+
 	return 0;
 }
 
@@ -772,7 +1074,6 @@  static int __devinit mxs_auart_probe(struct platform_device *pdev)
 	s->port.type = PORT_IMX;
 	s->port.dev = s->dev = get_device(&pdev->dev);
 
-	s->flags = 0;
 	s->ctrl = 0;
 	s->pdev = pdev;