diff mbox

[15/15] tty: serial: 8250: omap: add dma support

Message ID 1408124563-31541-16-git-send-email-bigeasy@linutronix.de (mailing list archive)
State New, archived
Headers show

Commit Message

Sebastian Andrzej Siewior Aug. 15, 2014, 5:42 p.m. UTC
This patch adds the required pieces to 8250-OMAP UART driver for DMA
support. The TX burst size is set to 1 so we can send an arbitrary
amount of bytes.

The RX burst is currently set to 48 which means we receive an DMA
interrupt every 48 bytes and have to reprogram everything. Less bytes in
the RX-FIFO mean that no DMA transfer will happen and the UART will send a
RX-timeout _or_ RDI event at which point the FIFO will be manually purged.
There is a workaround for TX-DMA on AM33xx where we put the first byte
into the FIFO to kick start the DMA process. Haven't seen this problem on
OMAP3 (beagle bone) or DRA7xx.

Signed-off-by: Sebastian Andrzej Siewior <bigeasy@linutronix.de>
---
 drivers/tty/serial/8250/8250_omap.c | 79 +++++++++++++++++++++++++++++++++++--
 1 file changed, 76 insertions(+), 3 deletions(-)

Comments

Tony Lindgren Aug. 15, 2014, 9:02 p.m. UTC | #1
* Sebastian Andrzej Siewior <bigeasy@linutronix.de> [140815 11:13]:
> +#ifdef CONFIG_SERIAL_8250_DMA
> +	if (pdev->dev.of_node) {
> +		/*
> +		 * Oh DMA support. If there are no DMA properties in the DT then
> +		 * we will fall back to a generic DMA channel which does not
> +		 * really work here. To ensure that we do not get a generic DMA
> +		 * channel assigned, we have the the_no_dma_filter_fn() here.
> +		 * To avoid "failed to request DMA" messages we check for DMA
> +		 * properties in DT.
> +		 */
> +		ret = of_property_count_strings(pdev->dev.of_node, "dma-names");
> +		if (ret == 2) {
> +			up.dma = &priv->omap8250_dma;
> +			priv->omap8250_dma.fn = the_no_dma_filter_fn;
> +			priv->omap8250_dma.rx_size = RX_TRIGGER;
> +			priv->omap8250_dma.rxconf.src_maxburst = RX_TRIGGER;
> +			priv->omap8250_dma.txconf.dst_maxburst = TX_TRIGGER;
> +
> +			if (of_machine_is_compatible("ti,am33xx"))
> +				up.bugs |= UART_BUG_DMATX;
> +		}
> +	}
> +#endif

Hmm I wonder if there's a more generic solution to this?

It would be also nice to be able to specify the use of DMA from
the board specific .dts file.

Also, with DMA enabled, looks like omap deeper idle states are
blocked as the DMA stays reserved. After I commented out the
DMA info for my console UART, PM started working.

Regards,

Tony
--
To unsubscribe from this list: send the line "unsubscribe linux-omap" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Sebastian Andrzej Siewior Aug. 21, 2014, 8:34 a.m. UTC | #2
On 08/15/2014 11:02 PM, Tony Lindgren wrote:
> * Sebastian Andrzej Siewior <bigeasy@linutronix.de> [140815 11:13]:
>> +#ifdef CONFIG_SERIAL_8250_DMA
>> +	if (pdev->dev.of_node) {
>> +		/*
>> +		 * Oh DMA support. If there are no DMA properties in the DT then
>> +		 * we will fall back to a generic DMA channel which does not
>> +		 * really work here. To ensure that we do not get a generic DMA
>> +		 * channel assigned, we have the the_no_dma_filter_fn() here.
>> +		 * To avoid "failed to request DMA" messages we check for DMA
>> +		 * properties in DT.
>> +		 */
>> +		ret = of_property_count_strings(pdev->dev.of_node, "dma-names");
>> +		if (ret == 2) {
>> +			up.dma = &priv->omap8250_dma;
>> +			priv->omap8250_dma.fn = the_no_dma_filter_fn;
>> +			priv->omap8250_dma.rx_size = RX_TRIGGER;
>> +			priv->omap8250_dma.rxconf.src_maxburst = RX_TRIGGER;
>> +			priv->omap8250_dma.txconf.dst_maxburst = TX_TRIGGER;
>> +
>> +			if (of_machine_is_compatible("ti,am33xx"))
>> +				up.bugs |= UART_BUG_DMATX;
>> +		}
>> +	}
>> +#endif
> 
> Hmm I wonder if there's a more generic solution to this?

To what exactly? The trigger level, the TX-bug or using DMA in the
first place?

> It would be also nice to be able to specify the use of DMA from
> the board specific .dts file.

Really? I don't see this requirement for MMC for instance. However you
still could provide an empty "dma-names" property in your board .dts
file if you do not want use DMA. Would this work?

> Also, with DMA enabled, looks like omap deeper idle states are
> blocked as the DMA stays reserved. After I commented out the
> DMA info for my console UART, PM started working.

Hmm. This would explain something. This would mean that I should cancel
the RX DMA transfer in the PM-suspend routine. Let me see how that
works.

> Regards,
> 
> Tony

Sebastian
--
To unsubscribe from this list: send the line "unsubscribe linux-omap" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Tony Lindgren Aug. 21, 2014, 6:44 p.m. UTC | #3
* Sebastian Andrzej Siewior <bigeasy@linutronix.de> [140821 01:37]:
> On 08/15/2014 11:02 PM, Tony Lindgren wrote:
> > * Sebastian Andrzej Siewior <bigeasy@linutronix.de> [140815 11:13]:
> >> +#ifdef CONFIG_SERIAL_8250_DMA
> >> +	if (pdev->dev.of_node) {
> >> +		/*
> >> +		 * Oh DMA support. If there are no DMA properties in the DT then
> >> +		 * we will fall back to a generic DMA channel which does not
> >> +		 * really work here. To ensure that we do not get a generic DMA
> >> +		 * channel assigned, we have the the_no_dma_filter_fn() here.
> >> +		 * To avoid "failed to request DMA" messages we check for DMA
> >> +		 * properties in DT.
> >> +		 */
> >> +		ret = of_property_count_strings(pdev->dev.of_node, "dma-names");
> >> +		if (ret == 2) {
> >> +			up.dma = &priv->omap8250_dma;
> >> +			priv->omap8250_dma.fn = the_no_dma_filter_fn;
> >> +			priv->omap8250_dma.rx_size = RX_TRIGGER;
> >> +			priv->omap8250_dma.rxconf.src_maxburst = RX_TRIGGER;
> >> +			priv->omap8250_dma.txconf.dst_maxburst = TX_TRIGGER;
> >> +
> >> +			if (of_machine_is_compatible("ti,am33xx"))
> >> +				up.bugs |= UART_BUG_DMATX;
> >> +		}
> >> +	}
> >> +#endif
> > 
> > Hmm I wonder if there's a more generic solution to this?
> 
> To what exactly? The trigger level, the TX-bug or using DMA in the
> first place?

Oh sorry, I meant to having to do of_property_count_strings to
figure out if it's correct or not. 
 
> > It would be also nice to be able to specify the use of DMA from
> > the board specific .dts file.
> 
> Really? I don't see this requirement for MMC for instance. However you
> still could provide an empty "dma-names" property in your board .dts
> file if you do not want use DMA. Would this work?

OK yeah that works. And that's needed mostly because of the issue
below.
 
> > Also, with DMA enabled, looks like omap deeper idle states are
> > blocked as the DMA stays reserved. After I commented out the
> > DMA info for my console UART, PM started working.
> 
> Hmm. This would explain something. This would mean that I should cancel
> the RX DMA transfer in the PM-suspend routine. Let me see how that
> works.

OK and if the DMA works with PM, then I don't see why we would not
want to have it automatically enabled.

BTW, looks like the ports move around now though. If set a port
to disabled with status = "disabled"; in the .dts file, you'll get
a different console which does not happen with omap-serial I believe.

Regards,

Tony
--
To unsubscribe from this list: send the line "unsubscribe linux-omap" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Sebastian Andrzej Siewior Aug. 27, 2014, 7:54 p.m. UTC | #4
On 08/21/2014 08:44 PM, Tony Lindgren wrote:
>>> Also, with DMA enabled, looks like omap deeper idle states are
>>> blocked as the DMA stays reserved. After I commented out the
>>> DMA info for my console UART, PM started working.
>>
>> Hmm. This would explain something. This would mean that I should cancel
>> the RX DMA transfer in the PM-suspend routine. Let me see how that
>> works.
> 
> OK and if the DMA works with PM, then I don't see why we would not
> want to have it automatically enabled.

I re-did that part where the registers are restored. Mostly for that
reason to use function in runtime_resume() as in set_termios(). I think
that is cute :) _And_ if somebody changes here something and breaks it
then it doesn't work with and without runtime-pm. It looks like the
omap-serial doesn't restore the XON1 & XOFF1 registers.

While at it I made sure that it works as good as I could and that means:

core_pwrdm
(ON),OFF:182,RET:21,INA:131,ON:335,RET-LOGIC-OFF:0,RET-MEMBANK1-OFF:0,RET-MEMBANK2-OFF:0

The core off part with DMA looks like a no no:
I #if 0 the block in where it assigned up.dma. With this I hit
core-off. Step two was

|static void omap8250_update_scr(struct uart_8250_port *up,
|                 struct omap8250_priv *priv)
|{
|serial_out(up, UART_OMAP_SCR, priv->scr | OMAP_UART_SCR_DMAMODE_CTL);
|serial_out(up, UART_OMAP_SCR, priv->scr | OMAP_UART_SCR_DMAMODE_CTL |
OMAP_UART_SCR_DMAMODE_1);
|serial_out(up, UART_OMAP_SCR, priv->scr | |OMAP_UART_SCR_DMAMODE_CTL);
|serial_out(up, UART_OMAP_SCR, priv->scr);
|}

which means I just enable DMA mode in UART and disable it. No DMA
operations were performed.
With this change I see a lost character now and then which means the
UART-IP goes into off and loses its context. Good. However I don't see
core off anymore. This looks like a bug beyond my responsibilities :)

I added code to cancel & and start DMA transfers in runtime suspend
callbacks.
However core-off with DMA won't work. I think we could document this in
the binding document. What do you think?

> BTW, looks like the ports move around now though. If set a port
> to disabled with status = "disabled"; in the .dts file, you'll get
> a different console which does not happen with omap-serial I believe.

You a right. I fixed it in the 8250-core code.

> Regards,
> 
> Tony

Sebastian
--
To unsubscribe from this list: send the line "unsubscribe linux-omap" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Tony Lindgren Aug. 27, 2014, 8:23 p.m. UTC | #5
* Sebastian Andrzej Siewior <bigeasy@linutronix.de> [140827 12:54]:
> On 08/21/2014 08:44 PM, Tony Lindgren wrote:
> >>> Also, with DMA enabled, looks like omap deeper idle states are
> >>> blocked as the DMA stays reserved. After I commented out the
> >>> DMA info for my console UART, PM started working.
> >>
> >> Hmm. This would explain something. This would mean that I should cancel
> >> the RX DMA transfer in the PM-suspend routine. Let me see how that
> >> works.
> > 
> > OK and if the DMA works with PM, then I don't see why we would not
> > want to have it automatically enabled.
> 
> I re-did that part where the registers are restored. Mostly for that
> reason to use function in runtime_resume() as in set_termios(). I think
> that is cute :) _And_ if somebody changes here something and breaks it
> then it doesn't work with and without runtime-pm. It looks like the
> omap-serial doesn't restore the XON1 & XOFF1 registers.
> 
> While at it I made sure that it works as good as I could and that means:
> 
> core_pwrdm
> (ON),OFF:182,RET:21,INA:131,ON:335,RET-LOGIC-OFF:0,RET-MEMBANK1-OFF:0,RET-MEMBANK2-OFF:0

Hey that's great, that's the ultimate torture test here!
There's nothing like rebooting the system every time you hit
idle and still have drivers working :)
 
> The core off part with DMA looks like a no no:
> I #if 0 the block in where it assigned up.dma. With this I hit
> core-off. Step two was
> 
> |static void omap8250_update_scr(struct uart_8250_port *up,
> |                 struct omap8250_priv *priv)
> |{
> |serial_out(up, UART_OMAP_SCR, priv->scr | OMAP_UART_SCR_DMAMODE_CTL);
> |serial_out(up, UART_OMAP_SCR, priv->scr | OMAP_UART_SCR_DMAMODE_CTL |
> OMAP_UART_SCR_DMAMODE_1);
> |serial_out(up, UART_OMAP_SCR, priv->scr | |OMAP_UART_SCR_DMAMODE_CTL);
> |serial_out(up, UART_OMAP_SCR, priv->scr);
> |}
> 
> which means I just enable DMA mode in UART and disable it. No DMA
> operations were performed.
> With this change I see a lost character now and then which means the
> UART-IP goes into off and loses its context. Good. However I don't see
> core off anymore. This looks like a bug beyond my responsibilities :)

OK, that sounds like a bug still lurking around somewhere. The core
domain won't hit idle if there are any hardware pieces blocking.

> I added code to cancel & and start DMA transfers in runtime suspend
> callbacks.

Do you mean just the OMAP_UART_SCR_DMAMODE_CTL related code, or
also the dmaengine calls?

> However core-off with DMA won't work. I think we could document this in
> the binding document. What do you think?

There should not be such a limitation though. Maybe dump out the values
of cm_idlest_per and cm_idlest1_core for working and failing off idle
cases and see what the difference is?

It could be the either the dma or the uart hardware blocking. I guess
it could be also an issue with runtime pm use somewhere.

> > BTW, looks like the ports move around now though. If set a port
> > to disabled with status = "disabled"; in the .dts file, you'll get
> > a different console which does not happen with omap-serial I believe.
> 
> You a right. I fixed it in the 8250-core code.

OK thanks.

Tony
--
To unsubscribe from this list: send the line "unsubscribe linux-omap" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Sebastian Andrzej Siewior Aug. 28, 2014, 8:23 a.m. UTC | #6
* Tony Lindgren | 2014-08-27 13:23:14 [-0700]:

>> which means I just enable DMA mode in UART and disable it. No DMA
>> operations were performed.
>> With this change I see a lost character now and then which means the
>> UART-IP goes into off and loses its context. Good. However I don't see
>> core off anymore. This looks like a bug beyond my responsibilities :)
>
>OK, that sounds like a bug still lurking around somewhere. The core
>domain won't hit idle if there are any hardware pieces blocking.

Yes.

>> I added code to cancel & and start DMA transfers in runtime suspend
>> callbacks.
>
>Do you mean just the OMAP_UART_SCR_DMAMODE_CTL related code, or
>also the dmaengine calls?

dmaengine calls are unused because up.dma is not assigned. It is 
basically like you wouldn't have the dma properties in the devicetree.
And while in that non-DMA mode I just set and unset the DMAMODE_CTL + 
DMAMODE_1 bits in the SCR register. Nothing else. Based on some testing
I just did, DMAMODE_CTL does not make the difference. DMAMODE_CTL +
DMAMODE_1 does.

>> However core-off with DMA won't work. I think we could document this in
>> the binding document. What do you think?
>
>There should not be such a limitation though. Maybe dump out the values
>of cm_idlest_per and cm_idlest1_core for working and failing off idle
>cases and see what the difference is?

I can't follow here. This is the working case:

usbhost_pwrdm (OFF),OFF:1,RET:1,INA:0,ON:2,RET-LOGIC-OFF:0,RET-MEMBANK1-OFF:0
sgx_pwrdm (OFF),OFF:1,RET:0,INA:0,ON:1,RET-LOGIC-OFF:0,RET-MEMBANK1-OFF:0
core_pwrdm (ON),OFF:109,RET:22,INA:71,ON:203,RET-LOGIC-OFF:0,RET-MEMBANK1-OFF:0,RET-MEMBANK2-OFF:0
per_pwrdm (ON),OFF:137,RET:126,INA:0,ON:264,RET-LOGIC-OFF:0,RET-MEMBANK1-OFF:0
dss_pwrdm (ON),OFF:383,RET:861,INA:0,ON:1245,RET-LOGIC-OFF:0,RET-MEMBANK1-OFF:0
cam_pwrdm (OFF),OFF:1,RET:1,INA:0,ON:2,RET-LOGIC-OFF:0,RET-MEMBANK1-OFF:0
neon_pwrdm (ON),OFF:311,RET:889,INA:44,ON:1245,RET-LOGIC-OFF:0
mpu_pwrdm (ON),OFF:311,RET:889,INA:44,ON:1245,RET-LOGIC-OFF:0,RET-MEMBANK1-OFF:0
iva2_pwrdm (OFF),OFF:1,RET:1,INA:0,ON:2,RET-LOGIC-OFF:0,RET-MEMBANK1-OFF:0,RET-MEMBANK2-OFF:0,RET-MEMBANK3-OFF:0,RET-MEMBANK4-OFF:0
usbhost_clkdm->usbhost_pwrdm (0)
sgx_clkdm->sgx_pwrdm (0)
per_clkdm->per_pwrdm (15)
cam_clkdm->cam_pwrdm (0)
dss_clkdm->dss_pwrdm (1)
d2d_clkdm->core_pwrdm (0)
iva2_clkdm->iva2_pwrdm (0)
mpu_clkdm->mpu_pwrdm (0)
core_l4_clkdm->core_pwrdm (20)
core_l3_clkdm->core_pwrdm (1)
neon_clkdm->neon_pwrdm (0)

non-working:

usbhost_pwrdm (OFF),OFF:1,RET:1,INA:0,ON:2,RET-LOGIC-OFF:0,RET-MEMBANK1-OFF:0
sgx_pwrdm (OFF),OFF:1,RET:0,INA:0,ON:1,RET-LOGIC-OFF:0,RET-MEMBANK1-OFF:0
core_pwrdm (ON),OFF:0,RET:0,INA:0,ON:1,RET-LOGIC-OFF:0,RET-MEMBANK1-OFF:0,RET-MEMBANK2-OFF:0
per_pwrdm (ON),OFF:0,RET:0,INA:0,ON:1,RET-LOGIC-OFF:0,RET-MEMBANK1-OFF:0
dss_pwrdm (ON),OFF:253,RET:159,INA:0,ON:413,RET-LOGIC-OFF:0,RET-MEMBANK1-OFF:0
cam_pwrdm (OFF),OFF:1,RET:1,INA:0,ON:2,RET-LOGIC-OFF:0,RET-MEMBANK1-OFF:0
neon_pwrdm (ON),OFF:135,RET:242,INA:35,ON:413,RET-LOGIC-OFF:0
mpu_pwrdm (ON),OFF:135,RET:242,INA:35,ON:413,RET-LOGIC-OFF:0,RET-MEMBANK1-OFF:0
iva2_pwrdm (OFF),OFF:1,RET:1,INA:0,ON:2,RET-LOGIC-OFF:0,RET-MEMBANK1-OFF:0,RET-MEMBANK2-OFF:0,RET-MEMBANK3-OFF:0,RET-MEMBANK4-OFF:0
usbhost_clkdm->usbhost_pwrdm (0)
sgx_clkdm->sgx_pwrdm (0)
per_clkdm->per_pwrdm (15)
cam_clkdm->cam_pwrdm (0)
dss_clkdm->dss_pwrdm (1)
d2d_clkdm->core_pwrdm (0)
iva2_clkdm->iva2_pwrdm (0)
mpu_clkdm->mpu_pwrdm (0)
core_l4_clkdm->core_pwrdm (21)
core_l3_clkdm->core_pwrdm (1)
neon_clkdm->neon_pwrdm (0)

so per_pwrdm and core_pwrdm remain on and I guess the former is where
the UART sits.

>It could be the either the dma or the uart hardware blocking. I guess
>it could be also an issue with runtime pm use somewhere.

So I just toggle the two DMA bits in SCR and the UART seems to block
since the DMA hw is not involved. Reading SCR back says that those bits
are not set.
To use DMA you don't have to enable it in SCR register you can also use
the FCR register. The manual says that you can only write this DMA
enable bit in the FCR register if the baud clock is not running. And
guess what: same thing: I only *toggle* the DMA enable bit here (it
remains 0 later) and the core won't hit idle.
Same effect if I toggle this bit while the baud clock is running (the
manual says that this bit can only be written if the baud clock is not
running). Seems like the UART is following its own specification and it
remains blocking once the DMA was enabled.
It would be nice if someone from the UART-IP team could ACK this.

Bah. Does it make sense to use runtime-PM if we can't hit core-off? I'm
thinking to add a printk once dma is enabled says that runtime-pm is
switched off.

>Tony

Sebastian
--
To unsubscribe from this list: send the line "unsubscribe linux-omap" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
diff mbox

Patch

diff --git a/drivers/tty/serial/8250/8250_omap.c b/drivers/tty/serial/8250/8250_omap.c
index 368e9d8..dfd2ddd 100644
--- a/drivers/tty/serial/8250/8250_omap.c
+++ b/drivers/tty/serial/8250/8250_omap.c
@@ -31,10 +31,16 @@ 
 #define UART_ERRATA_i202_MDR1_ACCESS	(1 << 0)
 #define OMAP_UART_WER_HAS_TX_WAKEUP	(1 << 1)
 
+#define OMAP_UART_FCR_RX_TRIG		6
+#define OMAP_UART_FCR_TX_TRIG		4
+
 /* SCR register bitmasks */
 #define OMAP_UART_SCR_RX_TRIG_GRANU1_MASK	(1 << 7)
 #define OMAP_UART_SCR_TX_TRIG_GRANU1_MASK	(1 << 6)
 #define OMAP_UART_SCR_TX_EMPTY			(1 << 3)
+#define OMAP_UART_SCR_DMAMODE_MASK		(3 << 1)
+#define OMAP_UART_SCR_DMAMODE_1			(1 << 1)
+#define OMAP_UART_SCR_DMAMODE_CTL		(1 << 0)
 
 /* MVR register bitmasks */
 #define OMAP_UART_MVR_SCHEME_SHIFT	30
@@ -45,6 +51,12 @@ 
 #define OMAP_UART_MVR_MAJ_SHIFT		8
 #define OMAP_UART_MVR_MIN_MASK		0x3f
 
+#define UART_TI752_TLR_TX	0
+#define UART_TI752_TLR_RX	4
+
+#define TRIGGER_TLR_MASK(x)	((x & 0x3c) >> 2)
+#define TRIGGER_FCR_MASK(x)	(x & 3)
+
 /* Enable XON/XOFF flow control on output */
 #define OMAP_UART_SW_TX		0x08
 /* Enable XON/XOFF flow control on input */
@@ -82,6 +94,7 @@  struct omap8250_priv {
 	u32 calc_latency;
 	struct pm_qos_request pm_qos_request;
 	struct work_struct qos_work;
+	struct uart_8250_dma omap8250_dma;
 };
 
 static u32 uart_read(struct uart_8250_port *up, u32 reg)
@@ -162,6 +175,20 @@  static void omap_8250_get_divisor(struct uart_port *port, unsigned int baud,
 	}
 }
 
+static void omap8250_update_scr(struct uart_8250_port *up,
+		struct omap8250_priv *priv)
+{
+	/*
+	 * The manual recommends not to enable the DMA mode selector in the SCR
+	 * (instead of the FCR) register _and_ selecting the DMA mode as one
+	 * register write because this may lead to malfunction.
+	 */
+	if (priv->scr & OMAP_UART_SCR_DMAMODE_MASK)
+		serial_out(up, UART_OMAP_SCR,
+				priv->scr & ~OMAP_UART_SCR_DMAMODE_MASK);
+	serial_out(up, UART_OMAP_SCR, priv->scr);
+}
+
 /*
  * OMAP can use "CLK / (16 or 13) / div" for baud rate. And then we have have
  * some differences in how we want to handle flow control.
@@ -286,6 +313,9 @@  static void omap_8250_set_termios(struct uart_port *port,
 	serial_out(up, UART_TI752_TLR,
 			TRIGGER_TLR_MASK(TX_TRIGGER) << UART_TI752_TLR_TX |
 			TRIGGER_TLR_MASK(RX_TRIGGER) << UART_TI752_TLR_RX);
+	if (up->dma)
+		priv->scr |= OMAP_UART_SCR_DMAMODE_1 |
+			OMAP_UART_SCR_DMAMODE_CTL;
 	/*
 	 * We enable TRIG_GRANU for RX and TX and additionaly we set
 	 * SCR_TX_EMPTY bit. The result is the following:
@@ -294,6 +324,14 @@  static void omap_8250_set_termios(struct uart_port *port,
 	 *   once the UART decides that there no new bytes arriving.
 	 * - Once THRE is enabled, the interrupt will be fired once the FIFO is
 	 *   empty - the trigger level is ignored here.
+	 *
+	 * Once DMA is enabled:
+	 * - UART will assert the TX DMA line once there is room for TX_TRIGGER
+	 *   bytes in the TX FIFO. On each assert the DMA engine will move
+	 *   TX_TRIGGER bytes into the FIFO.
+	 * - UART will assert the RX DMA line once there are RX_TRIGGER bytes in
+	 *   the FIFO and move RX_TRIGGER bytes.
+	 * This is because treshold and trigger values are the same.
 	 */
 	priv->fcr = UART_FCR_ENABLE_FIFO;
 	priv->fcr |= TRIGGER_FCR_MASK(TX_TRIGGER) << OMAP_UART_FCR_TX_TRIG;
@@ -302,7 +340,7 @@  static void omap_8250_set_termios(struct uart_port *port,
 	serial_out(up, UART_FCR, priv->fcr);
 	serial_out(up, UART_LCR, UART_LCR_CONF_MODE_B);
 
-	serial_out(up, UART_OMAP_SCR, priv->scr);
+	omap8250_update_scr(up, priv);
 
 	/* Reset UART_MCR_TCRTLR: this must be done with the EFR_ECB bit set */
 	serial_out(up, UART_LCR, UART_LCR_CONF_MODE_A);
@@ -525,6 +563,9 @@  static int omap_8250_startup(struct uart_port *port)
 		priv->wer |= OMAP_UART_TX_WAKEUP_EN;
 	serial_out(up, UART_OMAP_WER, priv->wer);
 
+	if (up->dma)
+		serial8250_rx_dma(up, 0);
+
 	pm_runtime_mark_last_busy(port->dev);
 	pm_runtime_put_autosuspend(port->dev);
 	return 0;
@@ -540,6 +581,8 @@  static void omap_8250_shutdown(struct uart_port *port)
 		container_of(port, struct uart_8250_port, port);
 	struct omap8250_priv *priv = port->private_data;
 
+	if (up->dma)
+		dmaengine_terminate_all(up->dma->rxchan);
 	pm_runtime_get_sync(port->dev);
 
 	serial_out(up, UART_OMAP_WER, 0);
@@ -586,6 +629,13 @@  static void omap_8250_unthrottle(struct uart_port *port)
 	pm_runtime_put_autosuspend(port->dev);
 }
 
+#ifdef CONFIG_SERIAL_8250_DMA
+static bool the_no_dma_filter_fn(struct dma_chan *chan, void *param)
+{
+	return false;
+}
+#endif
+
 static int omap8250_probe(struct platform_device *pdev)
 {
 	struct resource *regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);
@@ -677,7 +727,29 @@  static int omap8250_probe(struct platform_device *pdev)
 	pm_runtime_get_sync(&pdev->dev);
 
 	omap_serial_fill_features_erratas(&up, priv);
-
+#ifdef CONFIG_SERIAL_8250_DMA
+	if (pdev->dev.of_node) {
+		/*
+		 * Oh DMA support. If there are no DMA properties in the DT then
+		 * we will fall back to a generic DMA channel which does not
+		 * really work here. To ensure that we do not get a generic DMA
+		 * channel assigned, we have the the_no_dma_filter_fn() here.
+		 * To avoid "failed to request DMA" messages we check for DMA
+		 * properties in DT.
+		 */
+		ret = of_property_count_strings(pdev->dev.of_node, "dma-names");
+		if (ret == 2) {
+			up.dma = &priv->omap8250_dma;
+			priv->omap8250_dma.fn = the_no_dma_filter_fn;
+			priv->omap8250_dma.rx_size = RX_TRIGGER;
+			priv->omap8250_dma.rxconf.src_maxburst = RX_TRIGGER;
+			priv->omap8250_dma.txconf.dst_maxburst = TX_TRIGGER;
+
+			if (of_machine_is_compatible("ti,am33xx"))
+				up.bugs |= UART_BUG_DMATX;
+		}
+	}
+#endif
 	ret = serial8250_register_8250_port(&up);
 	if (ret < 0) {
 		dev_err(&pdev->dev, "unable to register 8250 port\n");
@@ -848,7 +920,8 @@  static void omap8250_restore_context(struct omap8250_priv *priv)
 	serial_out(up, UART_LCR, UART_LCR_CONF_MODE_A);
 	serial_out(up, UART_MCR, up->mcr);
 	serial_out(up, UART_LCR, UART_LCR_CONF_MODE_B); /* Config B mode */
-	serial_out(up, UART_OMAP_SCR, priv->scr);
+	omap8250_update_scr(up, priv);
+
 	serial_out(up, UART_EFR, priv->efr);
 	serial_out(up, UART_LCR, up->lcr);
 	if (priv->habit & UART_ERRATA_i202_MDR1_ACCESS)