Linux Serial subsystem development
 help / color / mirror / Atom feed
* Edgeport/416 io_edgeport problem.
From: David Robillard @ 2012-12-13 20:03 UTC (permalink / raw)
  To: linux-serial

Hello everyone,

I'm having a problem with a Digi Inside Out Networks Edgeport/416
device. According to the kernel.org file (see exerpt below) this
particular model is not listed in the supported products.

The device is connected to a 32 bit CentOS 6.3 machine running with
kernel 2.6.32-279.14.1.el6.i686. When I connect the Edgeport/416, the
messages which show up in /var/log/messages don't show any errors (see
below). The 16 /dev/ttyUSB character devices are created in /dev. I
can see the Edgeport/416 in /proc/bus/devices and in
/sys/bus/usb/drivers/io_edgeport.

But when I try to access any one of the 16 ports, I always get a
timeout followed by a no such device error. If I do this...

sudo strace cat /dev/ttyUSB14

...it always fail with this line :

open("/dev/ttyUSB14", O_RDONLY|O_LARGEFILE) = -1 ENODEV (No such device)

I tried to contact Digi, but their answer is a bit dull :

this driver is supported by the Linux community, we recommend
contacting the Linux-USB group to see if they might be familiar with
this behavior and a possible patch. Otherwise, a bug report should be
filed with them.

I'm not quite sure where to go from there? Any help would be very appreciated.

Many thanks,

David

P.S. Below is the output when I connect the unit to the CentOS machine
and the contents of the
http://www.kernel.org/doc/Documentation/usb/usb-serial.txt file.

Dec 12 14:42:40 solo kernel: usb 1-2: new full speed USB device number
2 using ohci_hcd
Dec 12 14:42:40 solo kernel: usb 1-2: New USB device found,
idVendor=0451, idProduct=2077
Dec 12 14:42:40 solo kernel: usb 1-2: New USB device strings: Mfr=0,
Product=1, SerialNumber=0
Dec 12 14:42:40 solo kernel: usb 1-2: Product: General Purpose USB Hub
Dec 12 14:42:40 solo kernel: usb 1-2: configuration #1 chosen from 1 choice
Dec 12 14:42:40 solo kernel: hub 1-2:1.0: USB hub found
Dec 12 14:42:40 solo kernel: hub 1-2:1.0: 7 ports detected
Dec 12 14:42:40 solo kernel: usb 1-2.5: new full speed USB device
number 3 using ohci_hcd
Dec 12 14:42:40 solo kernel: usb 1-2.5: New USB device found,
idVendor=1608, idProduct=0012
Dec 12 14:42:40 solo kernel: usb 1-2.5: New USB device strings: Mfr=1,
Product=2, SerialNumber=5
Dec 12 14:42:40 solo kernel: usb 1-2.5: Product: Edgeport/416
Dec 12 14:42:40 solo kernel: usb 1-2.5: Manufacturer: Inside Out Networks
Dec 12 14:42:40 solo kernel: usb 1-2.5: SerialNumber: V70430350-0
Dec 12 14:42:40 solo kernel: usb 1-2.5: configuration #1 chosen from 1 choice
Dec 12 14:42:40 solo kernel: usbcore: registered new interface driver usbserial
Dec 12 14:42:40 solo kernel: USB Serial support registered for generic
Dec 12 14:42:40 solo kernel: usb 1-2.6: new full speed USB device
number 4 using ohci_hcd
Dec 12 14:42:41 solo kernel: usb 1-2.6: New USB device found,
idVendor=1608, idProduct=0012
Dec 12 14:42:41 solo kernel: usb 1-2.6: New USB device strings: Mfr=1,
Product=2, SerialNumber=5
Dec 12 14:42:41 solo kernel: usb 1-2.6: Product: Edgeport/416
Dec 12 14:42:41 solo kernel: usb 1-2.6: Manufacturer: Inside Out Networks
Dec 12 14:42:41 solo kernel: usb 1-2.6: SerialNumber: V70430350-1
Dec 12 14:42:41 solo kernel: usb 1-2.6: configuration #1 chosen from 1 choice
Dec 12 14:42:41 solo kernel: usbcore: registered new interface driver
usbserial_generic
Dec 12 14:42:41 solo kernel: usbserial: USB Serial Driver core
Dec 12 14:42:41 solo kernel: USB Serial support registered for
Edgeport 2 port adapter
Dec 12 14:42:41 solo kernel: USB Serial support registered for
Edgeport 4 port adapter
Dec 12 14:42:41 solo kernel: USB Serial support registered for
Edgeport 8 port adapter
Dec 12 14:42:41 solo kernel: USB Serial support registered for EPiC device
Dec 12 14:42:41 solo kernel: io_edgeport 1-2.5:1.0: Edgeport 8 port
adapter converter detected
Dec 12 14:42:41 solo kernel: usb 1-2.5: Inside Out Networks
Edgeport/416 detected
Dec 12 14:42:41 solo kernel: usb 1-2.5: firmware: requesting edgeport/down.fw
Dec 12 14:42:42 solo kernel: usb 1-2.5: firmware: requesting edgeport/boot.fw
Dec 12 14:42:42 solo kernel: usb 1-2.5: Edgeport 8 port adapter
converter now attached to ttyUSB0
Dec 12 14:42:42 solo kernel: usb 1-2.5: Edgeport 8 port adapter
converter now attached to ttyUSB1
Dec 12 14:42:42 solo kernel: usb 1-2.5: Edgeport 8 port adapter
converter now attached to ttyUSB2
Dec 12 14:42:42 solo kernel: usb 1-2.5: Edgeport 8 port adapter
converter now attached to ttyUSB3
Dec 12 14:42:42 solo kernel: usb 1-2.5: Edgeport 8 port adapter
converter now attached to ttyUSB4
Dec 12 14:42:42 solo kernel: usb 1-2.5: Edgeport 8 port adapter
converter now attached to ttyUSB5
Dec 12 14:42:42 solo kernel: usb 1-2.5: Edgeport 8 port adapter
converter now attached to ttyUSB6
Dec 12 14:42:42 solo kernel: usb 1-2.5: Edgeport 8 port adapter
converter now attached to ttyUSB7
Dec 12 14:42:42 solo kernel: io_edgeport 1-2.6:1.0: Edgeport 8 port
adapter converter detected
Dec 12 14:42:42 solo kernel: usb 1-2.6: Inside Out Networks
Edgeport/416 detected
Dec 12 14:42:42 solo kernel: usb 1-2.6: firmware: requesting edgeport/down.fw
Dec 12 14:42:43 solo kernel: usb 1-2.6: firmware: requesting edgeport/boot.fw
Dec 12 14:42:43 solo kernel: usb 1-2.6: Edgeport 8 port adapter
converter now attached to ttyUSB8
Dec 12 14:42:43 solo kernel: usb 1-2.6: Edgeport 8 port adapter
converter now attached to ttyUSB9
Dec 12 14:42:43 solo kernel: usb 1-2.6: Edgeport 8 port adapter
converter now attached to ttyUSB10
Dec 12 14:42:43 solo kernel: usb 1-2.6: Edgeport 8 port adapter
converter now attached to ttyUSB11
Dec 12 14:42:43 solo kernel: usb 1-2.6: Edgeport 8 port adapter
converter now attached to ttyUSB12
Dec 12 14:42:43 solo kernel: usb 1-2.6: Edgeport 8 port adapter
converter now attached to ttyUSB13
Dec 12 14:42:43 solo kernel: usb 1-2.6: Edgeport 8 port adapter
converter now attached to ttyUSB14
Dec 12 14:42:43 solo kernel: usb 1-2.6: Edgeport 8 port adapter
converter now attached to ttyUSB15
Dec 12 14:42:43 solo kernel: usbcore: registered new interface driver
io_edgeport
Dec 12 14:42:43 solo kernel: io_edgeport: v2.7:Edgeport USB Serial Driver


Inside Out Networks Edgeport Driver

  This driver supports all devices made by Inside Out Networks, specifically
  the following models:
       Edgeport/4
       Rapidport/4
       Edgeport/4t
       Edgeport/2
       Edgeport/4i
       Edgeport/2i
       Edgeport/421
       Edgeport/21
       Edgeport/8
       Edgeport/8 Dual
       Edgeport/2D8
       Edgeport/4D8
       Edgeport/8i
       Edgeport/2 DIN
       Edgeport/4 DIN
       Edgeport/16 Dual

  For any questions or problems with this driver, please contact Greg
  Kroah-Hartman at greg@kroah.com

^ permalink raw reply

* Re: [PATCH v3] mxs: uart: allow setting RTS from software
From: Huang Shijie @ 2012-12-14  2:27 UTC (permalink / raw)
  To: Steffen Trumtrar; +Cc: linux-serial, stable, linux-arm-kernel, Alan Cox
In-Reply-To: <1355405263-29625-1-git-send-email-s.trumtrar@pengutronix.de>

于 2012年12月13日 21:27, Steffen Trumtrar 写道:
> With the patch "serial: mxs-auart: fix the wrong RTS hardware flow control" the
> mainline mxs-uart driver now sets RTSEN only when hardware flow control is
> enabled via software. It is not possible any longer to set RTS manually via
> software. However, the manual modification is a valid operation.
> Regain the possibility to set RTS via software and only set RTSEN when hardware
> flow control is explicitly enabled via settermios cflag CRTSCTS.
>
> Signed-off-by: Steffen Trumtrar <s.trumtrar@pengutronix.de>
> ---
>  drivers/tty/serial/mxs-auart.c |    4 +++-
>  1 file changed, 3 insertions(+), 1 deletion(-)
>
> diff --git a/drivers/tty/serial/mxs-auart.c b/drivers/tty/serial/mxs-auart.c
> index 6db23b0..9f63f88 100644
> --- a/drivers/tty/serial/mxs-auart.c
> +++ b/drivers/tty/serial/mxs-auart.c
> @@ -412,10 +412,12 @@ static void mxs_auart_set_mctrl(struct uart_port *u, unsigned mctrl)
>  
>  	u32 ctrl = readl(u->membase + AUART_CTRL2);
>  
> -	ctrl &= ~AUART_CTRL2_RTSEN;
> +	ctrl &= ~(AUART_CTRL2_RTSEN | AUART_CTRL2_RTS);
>  	if (mctrl & TIOCM_RTS) {
>  		if (tty_port_cts_enabled(&u->state->port))
>  			ctrl |= AUART_CTRL2_RTSEN;
> +		else
> +			ctrl |= AUART_CTRL2_RTS;
>  	}
>  
>  	s->ctrl = mctrl;
Reviewed-by: Huang Shijie <b32955@freescale.com>

^ permalink raw reply

* Re: Edgeport/416 io_edgeport problem.
From: Alan Cox @ 2012-12-14  9:52 UTC (permalink / raw)
  To: David Robillard; +Cc: linux-serial
In-Reply-To: <CADH15GikmfJh+nFbMFVcrW8RB8pmLsJvhtutue7L7iq8+7sdHQ@mail.gmail.com>

On Thu, 13 Dec 2012 15:03:59 -0500
David Robillard <david.robillard@gmail.com> wrote:

> Hello everyone,
> 
> I'm having a problem with a Digi Inside Out Networks Edgeport/416
> device. According to the kernel.org file (see exerpt below) this
> particular model is not listed in the supported products.
> 
> The device is connected to a 32 bit CentOS 6.3 machine running with
> kernel 2.6.32-279.14.1.el6.i686.

Please try a recent upstream kernel. 2.6.32 is old, and the Red
Hat/CentOS version quite a bit different. If 3.6/3.7 break then yes we
might care about it.

Alan

^ permalink raw reply

* Re: [PATCH] mxs: uart: allow setting RTS from software
From: Ben Hutchings @ 2012-12-14 14:11 UTC (permalink / raw)
  To: Steffen Trumtrar
  Cc: linux-serial, stable, linux-arm-kernel, Alan Cox, Huang Shijie
In-Reply-To: <1355329913-27666-1-git-send-email-s.trumtrar@pengutronix.de>

On Wed, Dec 12, 2012 at 05:31:53PM +0100, Steffen Trumtrar wrote:
> With the patch "serial: mxs-auart: fix the wrong RTS hardware flow control" the
> mainline mxs-uart driver now sets RTSEN only when hardware flow control is
> enabled via software. It is not possible any longer to set RTS manually via
> software. However, the manual modification is a valid operation.
> Regain the possibility to set RTS via software and only set RTSEN when hardware
> flow control is explicitly enabled via settermios cflag CRTSCTS.
> 
> Signed-off-by: Steffen Trumtrar <s.trumtrar@pengutronix.de>
[...]

This is not the correct way to submit a patch to stable.  See
Documentation/stable_kernel_rules.txt

Ben.

-- 
Ben Hutchings
We get into the habit of living before acquiring the habit of thinking.
                                                              - Albert Camus

^ permalink raw reply

* Re: Edgeport/416 io_edgeport problem.
From: David Robillard @ 2012-12-14 16:47 UTC (permalink / raw)
  To: Alan Cox; +Cc: linux-serial
In-Reply-To: <20121214095220.1be799ee@pyramind.ukuu.org.uk>

Hello Alan,

Thank you for the suggestion.

> Please try a recent upstream kernel. 2.6.32 is old, and the Red
> Hat/CentOS version quite a bit different. If 3.6/3.7 break then yes we
> might care about it.

I've tried with kernel 3.7.0-1.el6.elrepo.i686 from
http://elrepo.org/tiki/kernel-ml but unfortunately it's even worse.
Now the OS only sees half of the Edgeport/416. Ports 1 to 8 are seen
and /dev/ttyUSB0 to 7 are created. But I still can't access them.
Ports 10 to 16 are not seen and the character devices are not created.

Performing `sudo strace cat /dev/ttyUSB0` results in this error :

open("/dev/ttyUSB0", O_RDONLY|O_LARGEFILE) = -1 ENODEV (No such device)

Which is exactly the same error I used to get with kernel
2.6.32-279.14.1.el6.i686. But at least with that kernel version, the
OS would see the full 16 ports of the Edgeport/416.

This Edgeport/416 hardware successfully passed the Digi digital
loopback test as explained in this article
http://www.digi.com/support/kbase/kbaseresultdetl?id=3061. The test
was performed on a Windows 7 machine.

The model's PN is (1P) 50000780-01 E.

This device appears to be faulty according this Digi forum thread :
http://forums.digi.com/support/forum/viewthread_thread,4977#15792

Any help would be appreciated.

Many thanks,

David

P.S. Syslog messages when connecting the device to a CentOS 6.3
machine running kernel 3.7.0-1.el6.elrepo.i686

ec 14 11:30:52 solo kernel: usb 1-2: new full-speed USB device number
20 using ohci_hcd
Dec 14 11:30:52 solo kernel: usb 1-2: New USB device found,
idVendor=0451, idProduct=2077
Dec 14 11:30:52 solo kernel: usb 1-2: New USB device strings: Mfr=0,
Product=1, SerialNumber=0
Dec 14 11:30:52 solo kernel: usb 1-2: Product: General Purpose USB Hub
Dec 14 11:30:52 solo kernel: hub 1-2:1.0: USB hub found
Dec 14 11:30:52 solo kernel: usb 1-2.5: new full-speed USB device
number 21 using ohci_hcd
Dec 14 11:30:52 solo kernel: usb 1-2.5: device descriptor read/64, error -62
Dec 14 11:30:53 solo kernel: usb 1-2.5: device descriptor read/64, error -62
Dec 14 11:30:53 solo kernel: usb 1-2.5: new full-speed USB device
number 22 using ohci_hcd
Dec 14 11:30:53 solo kernel: usb 1-2.5: device descriptor read/64, error -62
Dec 14 11:30:53 solo kernel: usb 1-2.5: device descriptor read/64, error -62
Dec 14 11:30:53 solo kernel: usb 1-2.5: new full-speed USB device
number 23 using ohci_hcd
Dec 14 11:30:54 solo kernel: usb 1-2.5: device not accepting address
23, error -62
Dec 14 11:30:54 solo kernel: usb 1-2.5: new full-speed USB device
number 24 using ohci_hcd
Dec 14 11:30:54 solo kernel: usb 1-2.5: device not accepting address
24, error -62
Dec 14 11:30:54 solo kernel: hub 1-2:1.0: unable to enumerate USB
device on port 5
Dec 14 11:30:54 solo kernel: usb 1-2.6: new full-speed USB device
number 25 using ohci_hcd
Dec 14 11:30:54 solo kernel: usb 1-2.6: New USB device found,
idVendor=1608, idProduct=0012
Dec 14 11:30:54 solo kernel: usb 1-2.6: New USB device strings: Mfr=1,
Product=2, SerialNumber=5
Dec 14 11:30:54 solo kernel: usb 1-2.6: Product: Edgeport/416
Dec 14 11:30:54 solo kernel: usb 1-2.6: Manufacturer: Inside Out Networks
Dec 14 11:30:54 solo kernel: usb 1-2.6: SerialNumber: V70430350-1
Dec 14 11:30:54 solo kernel: usb 1-2.6: Inside Out Networks
Edgeport/416 detected
Dec 14 11:30:56 solo kernel: usb 1-2.6: Edgeport 8 port adapter
converter now attached to ttyUSB0
Dec 14 11:30:56 solo kernel: usb 1-2.6: Edgeport 8 port adapter
converter now attached to ttyUSB1
Dec 14 11:30:56 solo kernel: usb 1-2.6: Edgeport 8 port adapter
converter now attached to ttyUSB2
Dec 14 11:30:56 solo kernel: usb 1-2.6: Edgeport 8 port adapter
converter now attached to ttyUSB3
Dec 14 11:30:56 solo kernel: usb 1-2.6: Edgeport 8 port adapter
converter now attached to ttyUSB4
Dec 14 11:30:56 solo kernel: usb 1-2.6: Edgeport 8 port adapter
converter now attached to ttyUSB5
Dec 14 11:30:56 solo kernel: usb 1-2.6: Edgeport 8 port adapter
converter now attached to ttyUSB6
Dec 14 11:30:56 solo kernel: usb 1-2.6: Edgeport 8 port adapter
converter now attached to ttyUSB7

On Fri, Dec 14, 2012 at 4:52 AM, Alan Cox <alan@lxorguk.ukuu.org.uk> wrote:
> On Thu, 13 Dec 2012 15:03:59 -0500
> David Robillard <david.robillard@gmail.com> wrote:
>
>> Hello everyone,
>>
>> I'm having a problem with a Digi Inside Out Networks Edgeport/416
>> device. According to the kernel.org file (see exerpt below) this
>> particular model is not listed in the supported products.
>>
>> The device is connected to a 32 bit CentOS 6.3 machine running with
>> kernel 2.6.32-279.14.1.el6.i686.
>
>
> Alan

^ permalink raw reply

* [PATCH v2 01/11] tty: debug buffer work race with tty free
From: Peter Hurley @ 2012-12-14 18:22 UTC (permalink / raw)
  To: Alan Cox, Jiri Slaby
  Cc: linux-serial, Greg Kroah-Hartman, Ilya Zykov, Sasha Levin,
	linux-kernel, Peter Hurley
In-Reply-To: <1355509370-5883-1-git-send-email-peter@hurleysoftware.com>


Signed-off-by: Peter Hurley <peter@hurleysoftware.com>
---
 drivers/tty/pty.c        | 3 ++-
 drivers/tty/tty_buffer.c | 2 +-
 drivers/tty/tty_io.c     | 2 +-
 3 files changed, 4 insertions(+), 3 deletions(-)

diff --git a/drivers/tty/pty.c b/drivers/tty/pty.c
index be6a373..baf52b4 100644
--- a/drivers/tty/pty.c
+++ b/drivers/tty/pty.c
@@ -407,8 +407,9 @@ static void pty_unix98_shutdown(struct tty_struct *tty)
 
 static void pty_cleanup(struct tty_struct *tty)
 {
-	tty->port->itty = NULL;
+	tty->port->itty = ERR_PTR(-2);
 	tty_port_put(tty->port);
+	tty->port = NULL;
 }
 
 /* Traditional BSD devices */
diff --git a/drivers/tty/tty_buffer.c b/drivers/tty/tty_buffer.c
index b29ba87..cc9cbcf 100644
--- a/drivers/tty/tty_buffer.c
+++ b/drivers/tty/tty_buffer.c
@@ -432,7 +432,7 @@ static void flush_to_ldisc(struct work_struct *work)
 	struct tty_ldisc *disc;
 
 	tty = port->itty;
-	if (WARN_RATELIMIT(tty == NULL, "tty is NULL\n"))
+	if (WARN_RATELIMIT(IS_ERR_OR_NULL(tty), "tty is bad=%ld", PTR_ERR(tty)))
 		return;
 
 	disc = tty_ldisc_ref(tty);
diff --git a/drivers/tty/tty_io.c b/drivers/tty/tty_io.c
index da9fde8..78c3000 100644
--- a/drivers/tty/tty_io.c
+++ b/drivers/tty/tty_io.c
@@ -1553,7 +1553,7 @@ static void release_tty(struct tty_struct *tty, int idx)
 		tty->ops->shutdown(tty);
 	tty_free_termios(tty);
 	tty_driver_remove_tty(tty->driver, tty);
-	tty->port->itty = NULL;
+	tty->port->itty = ERR_PTR(-1);
 
 	if (tty->link)
 		tty_kref_put(tty->link);
-- 
1.8.0.1

^ permalink raw reply related

* [PATCH v2 03/11] tty: Add diagnostic for halted line discipline
From: Peter Hurley @ 2012-12-14 18:22 UTC (permalink / raw)
  To: Alan Cox, Jiri Slaby
  Cc: linux-serial, Greg Kroah-Hartman, Ilya Zykov, Sasha Levin,
	linux-kernel, Peter Hurley
In-Reply-To: <1355509370-5883-1-git-send-email-peter@hurleysoftware.com>

Flip buffer work must not be scheduled after the line discipline
has been halted; issue warning.

Signed-off-by: Peter Hurley <peter@hurleysoftware.com>
---
 drivers/tty/n_tty.c      |  6 ++++++
 drivers/tty/tty_buffer.c |  3 +++
 drivers/tty/tty_ldisc.c  | 45 +++++++++++++++++++++++++--------------------
 include/linux/tty.h      |  1 +
 4 files changed, 35 insertions(+), 20 deletions(-)

diff --git a/drivers/tty/n_tty.c b/drivers/tty/n_tty.c
index 19083ef..3f704a9 100644
--- a/drivers/tty/n_tty.c
+++ b/drivers/tty/n_tty.c
@@ -152,6 +152,12 @@ static void n_tty_set_room(struct tty_struct *tty)
 	if (left && !old_left) {
 		WARN_RATELIMIT(tty->port->itty == NULL,
 				"scheduling with invalid itty\n");
+		/* see if ldisc has been killed - if so, this means that
+		 * even though the ldisc has been halted and ->buf.work
+		 * cancelled, ->buf.work is about to be rescheduled
+		 */
+		WARN_RATELIMIT(test_bit(TTY_LDISC_HALTED, &tty->flags),
+			       "scheduling buffer work for halted ldisc\n");
 		schedule_work(&tty->port->buf.work);
 	}
 }
diff --git a/drivers/tty/tty_buffer.c b/drivers/tty/tty_buffer.c
index cc9cbcf..d8d6f77 100644
--- a/drivers/tty/tty_buffer.c
+++ b/drivers/tty/tty_buffer.c
@@ -520,6 +520,9 @@ void tty_flip_buffer_push(struct tty_struct *tty)
 	struct tty_bufhead *buf = &tty->port->buf;
 	unsigned long flags;
 
+	WARN_RATELIMIT(test_bit(TTY_LDISC_HALTED, &tty->flags),
+		       "scheduling buffer work for halted ldisc\n");
+
 	spin_lock_irqsave(&buf->lock, flags);
 	if (buf->tail != NULL)
 		buf->tail->commit = buf->tail->used;
diff --git a/drivers/tty/tty_ldisc.c b/drivers/tty/tty_ldisc.c
index c578229..f986bb0 100644
--- a/drivers/tty/tty_ldisc.c
+++ b/drivers/tty/tty_ldisc.c
@@ -373,6 +373,7 @@ static inline void tty_ldisc_put(struct tty_ldisc *ld)
 
 void tty_ldisc_enable(struct tty_struct *tty)
 {
+	clear_bit(TTY_LDISC_HALTED, &tty->flags);
 	set_bit(TTY_LDISC, &tty->flags);
 	clear_bit(TTY_LDISC_CHANGING, &tty->flags);
 	wake_up(&tty_ldisc_wait);
@@ -496,26 +497,6 @@ static void tty_ldisc_restore(struct tty_struct *tty, struct tty_ldisc *old)
 }
 
 /**
- *	tty_ldisc_halt		-	shut down the line discipline
- *	@tty: tty device
- *
- *	Shut down the line discipline and work queue for this tty device.
- *	The TTY_LDISC flag being cleared ensures no further references can
- *	be obtained while the delayed work queue halt ensures that no more
- *	data is fed to the ldisc.
- *
- *	You need to do a 'flush_scheduled_work()' (outside the ldisc_mutex)
- *	in order to make sure any currently executing ldisc work is also
- *	flushed.
- */
-
-static int tty_ldisc_halt(struct tty_struct *tty)
-{
-	clear_bit(TTY_LDISC, &tty->flags);
-	return cancel_work_sync(&tty->port->buf.work);
-}
-
-/**
  *	tty_ldisc_flush_works	-	flush all works of a tty
  *	@tty: tty device to flush works for
  *
@@ -545,6 +526,29 @@ static int tty_ldisc_wait_idle(struct tty_struct *tty, long timeout)
 }
 
 /**
+ *	tty_ldisc_halt		-	shut down the line discipline
+ *	@tty: tty device
+ *
+ *	Shut down the line discipline and work queue for this tty device.
+ *	The TTY_LDISC flag being cleared ensures no further references can
+ *	be obtained while the delayed work queue halt ensures that no more
+ *	data is fed to the ldisc.
+ *
+ *	You need to do a 'flush_scheduled_work()' (outside the ldisc_mutex)
+ *	in order to make sure any currently executing ldisc work is also
+ *	flushed.
+ */
+
+static int tty_ldisc_halt(struct tty_struct *tty)
+{
+	int scheduled;
+	clear_bit(TTY_LDISC, &tty->flags);
+	scheduled = cancel_work_sync(&tty->port->buf.work);
+	set_bit(TTY_LDISC_HALTED, &tty->flags);
+	return scheduled;
+}
+
+/**
  *	tty_set_ldisc		-	set line discipline
  *	@tty: the terminal to set
  *	@ldisc: the line discipline
@@ -818,6 +822,7 @@ void tty_ldisc_hangup(struct tty_struct *tty)
 	clear_bit(TTY_LDISC, &tty->flags);
 	tty_unlock(tty);
 	cancel_work_sync(&tty->port->buf.work);
+	set_bit(TTY_LDISC_HALTED, &tty->flags);
 	mutex_unlock(&tty->ldisc_mutex);
 retry:
 	tty_lock(tty);
diff --git a/include/linux/tty.h b/include/linux/tty.h
index 8db1b56..e6b968d 100644
--- a/include/linux/tty.h
+++ b/include/linux/tty.h
@@ -314,6 +314,7 @@ struct tty_file_private {
 #define TTY_NO_WRITE_SPLIT 	17	/* Preserve write boundaries to driver */
 #define TTY_HUPPED 		18	/* Post driver->hangup() */
 #define TTY_HUPPING 		21	/* ->hangup() in progress */
+#define TTY_LDISC_HALTED	22	/* Line discipline is halted */
 
 #define TTY_WRITE_FLUSH(tty) tty_write_flush((tty))
 
-- 
1.8.0.1

^ permalink raw reply related

* [PATCH v2 04/11] tty: Refactor n_tty_flush_buffer
From: Peter Hurley @ 2012-12-14 18:22 UTC (permalink / raw)
  To: Alan Cox, Jiri Slaby
  Cc: linux-serial, Greg Kroah-Hartman, Ilya Zykov, Sasha Levin,
	linux-kernel, Peter Hurley
In-Reply-To: <1355509370-5883-1-git-send-email-peter@hurleysoftware.com>

Factor out the packet mode status change for use by follow-on patch.

Signed-off-by: Peter Hurley <peter@hurleysoftware.com>
---
 drivers/tty/n_tty.c | 24 ++++++++++++++----------
 1 file changed, 14 insertions(+), 10 deletions(-)

diff --git a/drivers/tty/n_tty.c b/drivers/tty/n_tty.c
index 3f704a9..41e58bf 100644
--- a/drivers/tty/n_tty.c
+++ b/drivers/tty/n_tty.c
@@ -237,6 +237,18 @@ static void reset_buffer_flags(struct tty_struct *tty)
 	n_tty_set_room(tty);
 }
 
+static void packet_mode_flush(struct tty_struct *tty)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&tty->ctrl_lock, flags);
+	if (tty->link->packet) {
+		tty->ctrl_status |= TIOCPKT_FLUSHREAD;
+		wake_up_interruptible(&tty->link->read_wait);
+	}
+	spin_unlock_irqrestore(&tty->ctrl_lock, flags);
+}
+
 /**
  *	n_tty_flush_buffer	-	clean input queue
  *	@tty:	terminal device
@@ -251,19 +263,11 @@ static void reset_buffer_flags(struct tty_struct *tty)
 
 static void n_tty_flush_buffer(struct tty_struct *tty)
 {
-	unsigned long flags;
 	/* clear everything and unthrottle the driver */
 	reset_buffer_flags(tty);
 
-	if (!tty->link)
-		return;
-
-	spin_lock_irqsave(&tty->ctrl_lock, flags);
-	if (tty->link->packet) {
-		tty->ctrl_status |= TIOCPKT_FLUSHREAD;
-		wake_up_interruptible(&tty->link->read_wait);
-	}
-	spin_unlock_irqrestore(&tty->ctrl_lock, flags);
+	if (tty->link)
+		packet_mode_flush(tty);
 }
 
 /**
-- 
1.8.0.1

^ permalink raw reply related

* [PATCH v2 05/11] tty: Don't flush buffer when closing ldisc
From: Peter Hurley @ 2012-12-14 18:22 UTC (permalink / raw)
  To: Alan Cox, Jiri Slaby
  Cc: linux-serial, Greg Kroah-Hartman, Ilya Zykov, Sasha Levin,
	linux-kernel, Peter Hurley
In-Reply-To: <1355509370-5883-1-git-send-email-peter@hurleysoftware.com>

A buffer flush is both undesirable and unnecessary when the ldisc
is closing. A buffer flush performs the following:
 1. resets ldisc data fields to their initial state
 2. resets tty->receive_room to indicate more data can be sent
 3. schedules buffer work to receive more data
 4. signals a buffer flush has happened to linked pty in packet mode

Since the ldisc has been halted and the tty may soon be destructed,
buffer work must not be scheduled as that work might access
an invalid tty and ldisc state. Also, the ldisc read buffer is about
to be freed, so that's pointless.

Resetting the ldisc data fields is pointless as well since that
structure is about to be freed.

Resetting tty->receive_room is unnecessary, as it will be properly
reset if a new ldisc is reopened. Besides, resetting the original
receive_room value would be wrong since the read buffer will be
gone.

Since the packet mode flush is observable from userspace, this
behavior has been preserved.

The test jig originally authored by Ilya Zykov <ilya@ilyx.ru> and
signed off by him is included below. The test jig prompts the
following warnings which this patch fixes.

[   38.051111] ------------[ cut here ]------------
[   38.052113] WARNING: at /home/peter/src/kernels/next/drivers/tty/n_tty.c:160 n_tty_set_room.part.6+0x8b/0xa0()
[   38.053916] Hardware name: Bochs
[   38.054819] Modules linked in: netconsole configfs bnep rfcomm bluetooth parport_pc ppdev snd_hda_intel snd_hda_codec
snd_hwdep snd_pcm snd_seq_midi snd_rawmidi snd_seq_midi_event snd_seq psmouse snd_timer serio_raw mac_hid snd_seq_device
snd microcode lp parport virtio_balloon soundcore i2c_piix4 snd_page_alloc floppy 8139too 8139cp
[   38.059704] Pid: 1564, comm: pty_kill Tainted: G        W    3.7.0-next-20121130+ttydebug-xeon #20121130+ttydebug
[   38.061578] Call Trace:
[   38.062491]  [<ffffffff81058b4f>] warn_slowpath_common+0x7f/0xc0
[   38.063448]  [<ffffffff81058baa>] warn_slowpath_null+0x1a/0x20
[   38.064439]  [<ffffffff8142dc2b>] n_tty_set_room.part.6+0x8b/0xa0
[   38.065381]  [<ffffffff8142dc82>] n_tty_set_room+0x42/0x80
[   38.066323]  [<ffffffff8142e6f2>] reset_buffer_flags+0x102/0x160
[   38.077508]  [<ffffffff8142e76d>] n_tty_flush_buffer+0x1d/0x90
[   38.078782]  [<ffffffff81046569>] ? default_spin_lock_flags+0x9/0x10
[   38.079734]  [<ffffffff8142e804>] n_tty_close+0x24/0x60
[   38.080730]  [<ffffffff81431b61>] tty_ldisc_close.isra.2+0x41/0x60
[   38.081680]  [<ffffffff81431bbb>] tty_ldisc_kill+0x3b/0x80
[   38.082618]  [<ffffffff81432a07>] tty_ldisc_release+0x77/0xe0
[   38.083549]  [<ffffffff8142b781>] tty_release+0x451/0x4d0
[   38.084525]  [<ffffffff811950be>] __fput+0xae/0x230
[   38.085472]  [<ffffffff8119524e>] ____fput+0xe/0x10
[   38.086401]  [<ffffffff8107aa88>] task_work_run+0xc8/0xf0
[   38.087334]  [<ffffffff8105ea56>] do_exit+0x196/0x4b0
[   38.088304]  [<ffffffff8106c77b>] ? __dequeue_signal+0x6b/0xb0
[   38.089240]  [<ffffffff8105ef34>] do_group_exit+0x44/0xa0
[   38.090182]  [<ffffffff8106f43d>] get_signal_to_deliver+0x20d/0x4e0
[   38.091125]  [<ffffffff81016979>] do_signal+0x29/0x130
[   38.092096]  [<ffffffff81431a9e>] ? tty_ldisc_deref+0xe/0x10
[   38.093030]  [<ffffffff8142a317>] ? tty_write+0xb7/0xf0
[   38.093976]  [<ffffffff81193f53>] ? vfs_write+0xb3/0x180
[   38.094904]  [<ffffffff81016b20>] do_notify_resume+0x80/0xc0
[   38.095830]  [<ffffffff81700492>] int_signal+0x12/0x17
[   38.096788] ---[ end trace 5f6f7a9651cd999b ]---

[ 2730.570602] ------------[ cut here ]------------
[ 2730.572130] WARNING: at drivers/tty/n_tty.c:160 n_tty_set_room+0x107/0x140()
[ 2730.574904] scheduling buffer work for halted ldisc
[ 2730.578303] Pid: 9691, comm: trinity-child15 Tainted: G        W 3.7.0-rc8-next-20121205-sasha-00023-g59f0d85 #207
[ 2730.588694] Call Trace:
[ 2730.590486]  [<ffffffff81c41d77>] ? n_tty_set_room+0x107/0x140
[ 2730.592559]  [<ffffffff8110c827>] warn_slowpath_common+0x87/0xb0
[ 2730.595317]  [<ffffffff8110c8b1>] warn_slowpath_fmt+0x41/0x50
[ 2730.599186]  [<ffffffff81c41d77>] n_tty_set_room+0x107/0x140
[ 2730.603141]  [<ffffffff81c42fe7>] reset_buffer_flags+0x137/0x150
[ 2730.607166]  [<ffffffff81c43018>] n_tty_flush_buffer+0x18/0x90
[ 2730.610123]  [<ffffffff81c430af>] n_tty_close+0x1f/0x60
[ 2730.612068]  [<ffffffff81c461f2>] tty_ldisc_close.isra.4+0x52/0x60
[ 2730.614078]  [<ffffffff81c462ab>] tty_ldisc_reinit+0x3b/0x70
[ 2730.615891]  [<ffffffff81c46db2>] tty_ldisc_hangup+0x102/0x1e0
[ 2730.617780]  [<ffffffff81c3e537>] __tty_hangup+0x137/0x440
[ 2730.619547]  [<ffffffff81c3e869>] tty_vhangup+0x9/0x10
[ 2730.621266]  [<ffffffff81c48f1c>] pty_close+0x14c/0x160
[ 2730.622952]  [<ffffffff81c3fd45>] tty_release+0xd5/0x490
[ 2730.624674]  [<ffffffff8127fbe2>] __fput+0x122/0x250
[ 2730.626195]  [<ffffffff8127fd19>] ____fput+0x9/0x10
[ 2730.627758]  [<ffffffff81134602>] task_work_run+0xb2/0xf0
[ 2730.629491]  [<ffffffff811139ad>] do_exit+0x36d/0x580
[ 2730.631159]  [<ffffffff81113c8a>] do_group_exit+0x8a/0xc0
[ 2730.632819]  [<ffffffff81127351>] get_signal_to_deliver+0x501/0x5b0
[ 2730.634758]  [<ffffffff8106de34>] do_signal+0x24/0x100
[ 2730.636412]  [<ffffffff81204865>] ? user_exit+0xa5/0xd0
[ 2730.638078]  [<ffffffff81183cd8>] ? trace_hardirqs_on_caller+0x118/0x140
[ 2730.640279]  [<ffffffff81183d0d>] ? trace_hardirqs_on+0xd/0x10
[ 2730.642164]  [<ffffffff8106df78>] do_notify_resume+0x48/0xa0
[ 2730.643966]  [<ffffffff83cdff6a>] int_signal+0x12/0x17
[ 2730.645672] ---[ end trace a40d53149c07fce0 ]---

/*
 * pty_thrash.c
 *
 * Based on original test jig by Ilya Zykov <ilya@ilyx.ru>
 *
 * Signed-off-by: Peter Hurley <peter@hurleysoftware.com>
 * Signed-off-by: Ilya Zykov <ilya@ilyx.ru>
 */

static int fd;

static void error_exit(char *f, ...)
{
        va_list va;

        va_start(va, f);
        vprintf(f, va);
        printf(": %s\n", strerror(errno));
        va_end(va);

        if (fd >= 0)
                close(fd);

        exit(EXIT_FAILURE);
}

int main(int argc, char *argv[]) {
        int parent;
        char pts_name[24];
        int ptn, unlock;

        while (1) {

                fd = open("/dev/ptmx", O_RDWR);
                if (fd < 0)
                        error_exit("opening pty master");
                unlock = 0;
                if (ioctl(fd, TIOCSPTLCK, &unlock) < 0)
                        error_exit("unlocking pty pair");
                if (ioctl(fd, TIOCGPTN, &ptn) < 0)
                        error_exit("getting pty #");
                snprintf(pts_name, sizeof(pts_name), "/dev/pts/%d", ptn);

                child_id = fork();
                if (child_id == -1)
                        error_exit("forking child");

                if (parent) {
                        int err, id, status;
                        char buf[128];
                        int n;

                        n = read(fd, buf, sizeof(buf));
                        if (n < 0)
                                error_exit("master reading");
                        printf("%.*s\n", n-1, buf);

                        close(fd);

                        err = kill(child_id, SIGKILL);
                        if (err < 0)
                                error_exit("killing child");
                        id = waitpid(child_id, &status, 0);
                        if (id < 0 || id != child_id)
                                error_exit("waiting for child");

                } else { /* Child */

                        close(fd);
                        printf("Test cycle on slave pty %s\n", pts_name);
                        fd = open(pts_name, O_RDWR);
                        if (fd < 0)
                                error_exit("opening pty slave");

                        while (1) {
                                char pattern[] = "test\n";
                                if (write(fd, pattern, strlen(pattern)) < 0)
                                        error_exit("slave writing");
                        }

                }
        }

        /* never gets here */
        return 0;
}

Reported-by: Sasha Levin <levinsasha928@gmail.com>
---
 drivers/tty/n_tty.c | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/drivers/tty/n_tty.c b/drivers/tty/n_tty.c
index 41e58bf..3890f5b 100644
--- a/drivers/tty/n_tty.c
+++ b/drivers/tty/n_tty.c
@@ -1616,7 +1616,9 @@ static void n_tty_close(struct tty_struct *tty)
 {
 	struct n_tty_data *ldata = tty->disc_data;
 
-	n_tty_flush_buffer(tty);
+	if (tty->link)
+		packet_mode_flush(tty);
+
 	kfree(ldata->read_buf);
 	kfree(ldata->echo_buf);
 	kfree(ldata);
-- 
1.8.0.1

^ permalink raw reply related

* [PATCH v2 06/11] tty: Refactor wait for ldisc refs out of tty_ldisc_hangup()
From: Peter Hurley @ 2012-12-14 18:22 UTC (permalink / raw)
  To: Alan Cox, Jiri Slaby
  Cc: linux-serial, Greg Kroah-Hartman, Ilya Zykov, Sasha Levin,
	linux-kernel, Peter Hurley
In-Reply-To: <1355509370-5883-1-git-send-email-peter@hurleysoftware.com>

Refactor tty_ldisc_hangup() to extract standalone function,
tty_ldisc_hangup_wait_idle(), to wait for ldisc references
to be released.

Signed-off-by: Peter Hurley <peter@hurleysoftware.com>
---
 drivers/tty/tty_ldisc.c | 54 ++++++++++++++++++++++++++++++++-----------------
 1 file changed, 36 insertions(+), 18 deletions(-)

diff --git a/drivers/tty/tty_ldisc.c b/drivers/tty/tty_ldisc.c
index f986bb0..6c1d8aa 100644
--- a/drivers/tty/tty_ldisc.c
+++ b/drivers/tty/tty_ldisc.c
@@ -549,6 +549,41 @@ static int tty_ldisc_halt(struct tty_struct *tty)
 }
 
 /**
+ *	tty_ldisc_hangup_wait_idle - wait for the ldisc to become idle
+ *	@tty: tty to wait for
+ *
+ *	Wait for the line discipline to become idle. The discipline must
+ *	have been halted for this to guarantee it remains idle.
+ *
+ *	Caller must hold legacy and ->ldisc_mutex.
+ */
+static bool tty_ldisc_hangup_wait_idle(struct tty_struct *tty)
+{
+	while (tty->ldisc) {	/* Not yet closed */
+		if (atomic_read(&tty->ldisc->users) != 1) {
+			char cur_n[TASK_COMM_LEN], tty_n[64];
+			long timeout = 3 * HZ;
+			tty_unlock(tty);
+
+			while (tty_ldisc_wait_idle(tty, timeout) == -EBUSY) {
+				timeout = MAX_SCHEDULE_TIMEOUT;
+				printk_ratelimited(KERN_WARNING
+					"%s: waiting (%s) for %s took too long, but we keep waiting...\n",
+					__func__, get_task_comm(cur_n, current),
+					tty_name(tty, tty_n));
+			}
+			/* must reacquire both locks and preserve lock order */
+			mutex_unlock(&tty->ldisc_mutex);
+			tty_lock(tty);
+			mutex_lock(&tty->ldisc_mutex);
+			continue;
+		}
+		break;
+	}
+	return !!(tty->ldisc);
+}
+
+/**
  *	tty_set_ldisc		-	set line discipline
  *	@tty: the terminal to set
  *	@ldisc: the line discipline
@@ -824,7 +859,6 @@ void tty_ldisc_hangup(struct tty_struct *tty)
 	cancel_work_sync(&tty->port->buf.work);
 	set_bit(TTY_LDISC_HALTED, &tty->flags);
 	mutex_unlock(&tty->ldisc_mutex);
-retry:
 	tty_lock(tty);
 	mutex_lock(&tty->ldisc_mutex);
 
@@ -832,23 +866,7 @@ retry:
 	   reopen it. We could defer this to the next open but
 	   it means auditing a lot of other paths so this is
 	   a FIXME */
-	if (tty->ldisc) {	/* Not yet closed */
-		if (atomic_read(&tty->ldisc->users) != 1) {
-			char cur_n[TASK_COMM_LEN], tty_n[64];
-			long timeout = 3 * HZ;
-			tty_unlock(tty);
-
-			while (tty_ldisc_wait_idle(tty, timeout) == -EBUSY) {
-				timeout = MAX_SCHEDULE_TIMEOUT;
-				printk_ratelimited(KERN_WARNING
-					"%s: waiting (%s) for %s took too long, but we keep waiting...\n",
-					__func__, get_task_comm(cur_n, current),
-					tty_name(tty, tty_n));
-			}
-			mutex_unlock(&tty->ldisc_mutex);
-			goto retry;
-		}
-
+	if (tty_ldisc_hangup_wait_idle(tty)) {
 		if (reset == 0) {
 
 			if (!tty_ldisc_reinit(tty, tty->termios.c_line))
-- 
1.8.0.1

^ permalink raw reply related

* [PATCH v2 07/11] tty: Remove unnecessary re-test of ldisc ref count
From: Peter Hurley @ 2012-12-14 18:22 UTC (permalink / raw)
  To: Alan Cox, Jiri Slaby
  Cc: linux-serial, Greg Kroah-Hartman, Ilya Zykov, Sasha Levin,
	linux-kernel, Peter Hurley
In-Reply-To: <1355509370-5883-1-git-send-email-peter@hurleysoftware.com>

Since the tty->ldisc is prevented from being changed by tty_set_ldisc()
when a tty is being hung up, re-testing the ldisc user count is
unnecessary -- ie, it cannot be a different ldisc and the user count
cannot have increased (assuming the caller meets the precondition that
TTY_LDISC flag is cleared)

Removal of the 'early-out' locking optimization is necessary for
the subsequent patch 'tty: Fix ldisc halt sequence on hangup'.

Signed-off-by: Peter Hurley <peter@hurleysoftware.com>
---
 drivers/tty/tty_ldisc.c | 38 +++++++++++++++++++-------------------
 1 file changed, 19 insertions(+), 19 deletions(-)

diff --git a/drivers/tty/tty_ldisc.c b/drivers/tty/tty_ldisc.c
index 6c1d8aa..0d18d1e 100644
--- a/drivers/tty/tty_ldisc.c
+++ b/drivers/tty/tty_ldisc.c
@@ -556,29 +556,29 @@ static int tty_ldisc_halt(struct tty_struct *tty)
  *	have been halted for this to guarantee it remains idle.
  *
  *	Caller must hold legacy and ->ldisc_mutex.
+ *
+ *	NB: tty_set_ldisc() is prevented from changing the ldisc concurrently
+ *	with this function when it checks the TTY_HUPPING state.
  */
 static bool tty_ldisc_hangup_wait_idle(struct tty_struct *tty)
 {
-	while (tty->ldisc) {	/* Not yet closed */
-		if (atomic_read(&tty->ldisc->users) != 1) {
-			char cur_n[TASK_COMM_LEN], tty_n[64];
-			long timeout = 3 * HZ;
-			tty_unlock(tty);
-
-			while (tty_ldisc_wait_idle(tty, timeout) == -EBUSY) {
-				timeout = MAX_SCHEDULE_TIMEOUT;
-				printk_ratelimited(KERN_WARNING
-					"%s: waiting (%s) for %s took too long, but we keep waiting...\n",
-					__func__, get_task_comm(cur_n, current),
-					tty_name(tty, tty_n));
-			}
-			/* must reacquire both locks and preserve lock order */
-			mutex_unlock(&tty->ldisc_mutex);
-			tty_lock(tty);
-			mutex_lock(&tty->ldisc_mutex);
-			continue;
+	char cur_n[TASK_COMM_LEN], tty_n[64];
+	long timeout = 3 * HZ;
+
+	if (tty->ldisc) {	/* Not yet closed */
+		tty_unlock(tty);
+
+		while (tty_ldisc_wait_idle(tty, timeout) == -EBUSY) {
+			timeout = MAX_SCHEDULE_TIMEOUT;
+			printk_ratelimited(KERN_WARNING
+				"%s: waiting (%s) for %s took too long, but we keep waiting...\n",
+				__func__, get_task_comm(cur_n, current),
+				tty_name(tty, tty_n));
 		}
-		break;
+		/* must reacquire both locks and preserve lock order */
+		mutex_unlock(&tty->ldisc_mutex);
+		tty_lock(tty);
+		mutex_lock(&tty->ldisc_mutex);
 	}
 	return !!(tty->ldisc);
 }
-- 
1.8.0.1

^ permalink raw reply related

* [PATCH v2 08/11] tty: Fix ldisc halt sequence on hangup
From: Peter Hurley @ 2012-12-14 18:22 UTC (permalink / raw)
  To: Alan Cox, Jiri Slaby
  Cc: linux-serial, Greg Kroah-Hartman, Ilya Zykov, Sasha Levin,
	linux-kernel, Peter Hurley
In-Reply-To: <1355509370-5883-1-git-send-email-peter@hurleysoftware.com>

Flip buffer work cannot be cancelled until all outstanding ldisc
references have been released. Convert the ldisc ref wait into
a full ldisc halt with buffer work cancellation.

Note that the legacy mutex is not held while cancelling.

Signed-off-by: Peter Hurley <peter@hurleysoftware.com>
---
 drivers/tty/tty_ldisc.c | 40 ++++++++++++++++++++--------------------
 1 file changed, 20 insertions(+), 20 deletions(-)

diff --git a/drivers/tty/tty_ldisc.c b/drivers/tty/tty_ldisc.c
index 0d18d1e..c3dec37 100644
--- a/drivers/tty/tty_ldisc.c
+++ b/drivers/tty/tty_ldisc.c
@@ -549,22 +549,31 @@ static int tty_ldisc_halt(struct tty_struct *tty)
 }
 
 /**
- *	tty_ldisc_hangup_wait_idle - wait for the ldisc to become idle
- *	@tty: tty to wait for
- *
- *	Wait for the line discipline to become idle. The discipline must
- *	have been halted for this to guarantee it remains idle.
+ *	tty_ldisc_hangup_halt - halt the line discipline for hangup
+ *	@tty: tty being hung up
  *
+ *	Shut down the line discipline and work queue for the tty device
+ *	being hungup. Clear the TTY_LDISC flag to ensure no further
+ *	references can be obtained, wait for remaining references to be
+ *	released, and cancel pending buffer work to ensure no more
+ *	data is fed to the ldisc.
  *	Caller must hold legacy and ->ldisc_mutex.
  *
  *	NB: tty_set_ldisc() is prevented from changing the ldisc concurrently
  *	with this function when it checks the TTY_HUPPING state.
+ *
+ *	NB: if tty->ldisc is NULL then buffer work does not need to be
+ *	cancelled because it must already have done as a precondition
+ *	of setting tty->ldisc to NULL
+ *
  */
-static bool tty_ldisc_hangup_wait_idle(struct tty_struct *tty)
+static bool tty_ldisc_hangup_halt(struct tty_struct *tty)
 {
 	char cur_n[TASK_COMM_LEN], tty_n[64];
 	long timeout = 3 * HZ;
 
+	clear_bit(TTY_LDISC, &tty->flags);
+
 	if (tty->ldisc) {	/* Not yet closed */
 		tty_unlock(tty);
 
@@ -575,6 +584,10 @@ static bool tty_ldisc_hangup_wait_idle(struct tty_struct *tty)
 				__func__, get_task_comm(cur_n, current),
 				tty_name(tty, tty_n));
 		}
+
+		cancel_work_sync(&tty->port->buf.work);
+		set_bit(TTY_LDISC_HALTED, &tty->flags);
+
 		/* must reacquire both locks and preserve lock order */
 		mutex_unlock(&tty->ldisc_mutex);
 		tty_lock(tty);
@@ -849,24 +862,11 @@ void tty_ldisc_hangup(struct tty_struct *tty)
 	 */
 	mutex_lock(&tty->ldisc_mutex);
 
-	/*
-	 * this is like tty_ldisc_halt, but we need to give up
-	 * the BTM before calling cancel_work_sync, which may
-	 * need to wait for another function taking the BTM
-	 */
-	clear_bit(TTY_LDISC, &tty->flags);
-	tty_unlock(tty);
-	cancel_work_sync(&tty->port->buf.work);
-	set_bit(TTY_LDISC_HALTED, &tty->flags);
-	mutex_unlock(&tty->ldisc_mutex);
-	tty_lock(tty);
-	mutex_lock(&tty->ldisc_mutex);
-
 	/* At this point we have a closed ldisc and we want to
 	   reopen it. We could defer this to the next open but
 	   it means auditing a lot of other paths so this is
 	   a FIXME */
-	if (tty_ldisc_hangup_wait_idle(tty)) {
+	if (tty_ldisc_hangup_halt(tty)) {
 		if (reset == 0) {
 
 			if (!tty_ldisc_reinit(tty, tty->termios.c_line))
-- 
1.8.0.1

^ permalink raw reply related

* [PATCH v2 10/11] tty: Remove unnecessary buffer work flush
From: Peter Hurley @ 2012-12-14 18:22 UTC (permalink / raw)
  To: Alan Cox, Jiri Slaby
  Cc: linux-serial, Greg Kroah-Hartman, Ilya Zykov, Sasha Levin,
	linux-kernel, Peter Hurley
In-Reply-To: <1355509370-5883-1-git-send-email-peter@hurleysoftware.com>

In order to safely call tty_ldisc_flush_works(), the ldisc must
already have been halted, so any pending buffer work has already
been cancelled or flushed as part of the halt.

Signed-off-by: Peter Hurley <peter@hurleysoftware.com>
---
 drivers/tty/tty_ldisc.c | 1 -
 1 file changed, 1 deletion(-)

diff --git a/drivers/tty/tty_ldisc.c b/drivers/tty/tty_ldisc.c
index 9f4c7b0..19e088a 100644
--- a/drivers/tty/tty_ldisc.c
+++ b/drivers/tty/tty_ldisc.c
@@ -506,7 +506,6 @@ static void tty_ldisc_flush_works(struct tty_struct *tty)
 {
 	flush_work(&tty->hangup_work);
 	flush_work(&tty->SAK_work);
-	flush_work(&tty->port->buf.work);
 }
 
 /**
-- 
1.8.0.1

^ permalink raw reply related

* [PATCH v2 00/11] tty: Fix buffer work access-after-free
From: Peter Hurley @ 2012-12-14 18:22 UTC (permalink / raw)
  To: Alan Cox, Jiri Slaby
  Cc: linux-serial, Greg Kroah-Hartman, Ilya Zykov, Sasha Levin,
	linux-kernel, Peter Hurley

I wasn't sure if this is something to squeeze into 3.8, so don't yell
if not. At least Sasha can apply this and re-test against trinity.

Changes in v2:

- Please review "tty: Don't flush buffer when closing ldisc".
  This patch replaces the earlier
  "tty: Don't reschedule buffer work while closing". The text of
  this commit details why not calling n_tty_flush_buffer() is the
  correct thing to do, so I won't repeat it here.

- Jiri's debug patch "tty: debug buffer work race with tty free"
  has been included (albeit a slightly different version)
  Jiri, please sign off (or point out what you'd like changed).

- The test jig has been included in the commit message for
  "tty: Don't flush buffer when closing ldisc" as Alan requested.

- Ilya Zykov was added as the Signed-off-by: for the test jig in
  that same commit message.

- Sasha Levin was added as the Reported-by: in that same patch.


This patch series addresses the causes of flush_to_ldisc accessing
the tty after freeing.

The most common cause stems from the n_tty_close() path spuriously
scheduling buffer work, when the ldisc has already been halted.
This is fixed in 'tty: Don't flush buffer when closing ldisc'

The other causes have a central theme: incorrect order-of-operations
when halting a line discipline. In general, to prevent buffer work
from being scheduled requires:
  1. Disallowing further ldisc references
  2. Waiting for all existing ldisc references to be released
  3. Cancelling existing buffer work
If the wait takes place _after_ cancellation, then new work
can be scheduled by existing holder(s) of ldisc reference(s). That's
bad.

Halting the line discipline is performed when,
 - hanging up the tty (tty_ldisc_hangup())
 - TIOCSETD ioctl (tty_set_ldisc())
 - finally closing the tty (pair) (tty_ldisc_release())

Concurrent halts are governed by the following requirements:
1. tty_ldisc_release is not concurrent with the other two and so
   does not need lock or state protection during the ldiscs halt.
2. Accesses through tty->ldisc must be protected by the ldisc_mutex.
   The wait operation checks the user count (ldisc references)
   in tty->ldisc->users.
3. tty_set_ldisc() reschedules buffer work that was pending when
   the ldiscs were halted. That must be an atomic operation in
   conjunction with re-enabling the ldisc -- which necessitates
   locking concurrent halts (tty_ldisc_release is exempt here)
4. The legacy mutex cannot be held while waiting for ldisc
   reference(s) release -or- for cancelling buffer work.
5. Because of #4, the legacy mutex must be dropped prior to or
   during the halt. Which means reacquiring after the halt. But
   to preserve lock order the ldisc_mutex must be dropped and
   reacquired after reacquiring the legacy mutex.
6. Because of #5, the ldisc state may have changed while the
   ldisc mutex was dropped.

Note: this series does not include the lock correction initially
reported on by Sebastian Andrzej Siewior <bigeasy@linutronix.de> here
https://lkml.org/lkml/2012/11/21/267 . I commented on the latest
version here https://lkml.org/lkml/2012/12/3/362


Peter Hurley (11):
  tty: debug buffer work race with tty free
  tty: WARN if buffer work racing with tty free
  tty: Add diagnostic for halted line discipline
  tty: Refactor n_tty_flush_buffer
  tty: Don't flush buffer when closing ldisc
  tty: Refactor wait for ldisc refs out of tty_ldisc_hangup()
  tty: Remove unnecessary re-test of ldisc ref count
  tty: Fix ldisc halt sequence on hangup
  tty: Strengthen no-subsequent-use guarantee of tty_ldisc_halt()
  tty: Remove unnecessary buffer work flush
  tty: Halt both ldiscs concurrently

 drivers/tty/n_tty.c      |  34 +++++++---
 drivers/tty/pty.c        |   3 +-
 drivers/tty/tty_buffer.c |   5 +-
 drivers/tty/tty_io.c     |   4 +-
 drivers/tty/tty_ldisc.c  | 171 +++++++++++++++++++++++++++++------------------
 include/linux/tty.h      |   1 +
 6 files changed, 139 insertions(+), 79 deletions(-)

-- 
1.8.0.1


^ permalink raw reply

* [PATCH v2 02/11] tty: WARN if buffer work racing with tty free
From: Peter Hurley @ 2012-12-14 18:22 UTC (permalink / raw)
  To: Alan Cox, Jiri Slaby
  Cc: linux-serial, Greg Kroah-Hartman, Ilya Zykov, Sasha Levin,
	linux-kernel, Peter Hurley
In-Reply-To: <1355509370-5883-1-git-send-email-peter@hurleysoftware.com>


Signed-off-by: Peter Hurley <peter@hurleysoftware.com>
---
 drivers/tty/tty_io.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/drivers/tty/tty_io.c b/drivers/tty/tty_io.c
index 78c3000..3d2b6d7 100644
--- a/drivers/tty/tty_io.c
+++ b/drivers/tty/tty_io.c
@@ -1511,6 +1511,8 @@ static void queue_release_one_tty(struct kref *kref)
 {
 	struct tty_struct *tty = container_of(kref, struct tty_struct, kref);
 
+	WARN_ON(work_pending(&tty->port->buf.work));
+
 	/* The hangup queue is now free so we can reuse it rather than
 	   waste a chunk of memory for each port */
 	INIT_WORK(&tty->hangup_work, release_one_tty);
-- 
1.8.0.1


^ permalink raw reply related

* [PATCH v2 09/11] tty: Strengthen no-subsequent-use guarantee of tty_ldisc_halt()
From: Peter Hurley @ 2012-12-14 18:22 UTC (permalink / raw)
  To: Alan Cox, Jiri Slaby
  Cc: linux-serial, Greg Kroah-Hartman, Ilya Zykov, Sasha Levin,
	linux-kernel, Peter Hurley
In-Reply-To: <1355509370-5883-1-git-send-email-peter@hurleysoftware.com>

In preparation for destructing and freeing the tty, the line discipline
must first be brought to an inactive state before it can be destructed.
This line discipline shutdown must:
 - disallow new users of the ldisc
 - wait for existing ldisc users to finish
 - only then, cancel/flush their pending/running work

Factor tty_ldisc_wait_idle() from tty_set_ldisc() and tty_ldisc_kill()
to ensure this shutdown order.

Failure to provide this guarantee can result in scheduled work
running after the tty has already been freed, as indicated in the
following log message:

[   88.331234] WARNING: at /home/peter/src/kernels/next/drivers/tty/tty_buffer.c:435 flush_to_ldisc+0x194/0x1d0()
[   88.334505] Hardware name: Bochs
[   88.335618] tty is bad=-1
[   88.335703] Modules linked in: netconsole configfs bnep rfcomm bluetooth ......
[   88.345272] Pid: 39, comm: kworker/1:1 Tainted: G        W    3.7.0-next-20121129+ttydebug-xeon #20121129+ttydebug
[   88.347736] Call Trace:
[   88.349024]  [<ffffffff81058aff>] warn_slowpath_common+0x7f/0xc0
[   88.350383]  [<ffffffff81058bf6>] warn_slowpath_fmt+0x46/0x50
[   88.351745]  [<ffffffff81432bd4>] flush_to_ldisc+0x194/0x1d0
[   88.353047]  [<ffffffff816f7fe1>] ? _raw_spin_unlock_irq+0x21/0x50
[   88.354190]  [<ffffffff8108a809>] ? finish_task_switch+0x49/0xe0
[   88.355436]  [<ffffffff81077ad1>] process_one_work+0x121/0x490
[   88.357674]  [<ffffffff81432a40>] ? __tty_buffer_flush+0x90/0x90
[   88.358954]  [<ffffffff81078c84>] worker_thread+0x164/0x3e0
[   88.360247]  [<ffffffff81078b20>] ? manage_workers+0x120/0x120
[   88.361282]  [<ffffffff8107e230>] kthread+0xc0/0xd0
[   88.362284]  [<ffffffff816f0000>] ? cmos_do_probe+0x2eb/0x3bf
[   88.363391]  [<ffffffff8107e170>] ? flush_kthread_worker+0xb0/0xb0
[   88.364797]  [<ffffffff816fff6c>] ret_from_fork+0x7c/0xb0
[   88.366087]  [<ffffffff8107e170>] ? flush_kthread_worker+0xb0/0xb0
[   88.367266] ---[ end trace 453a7c9f38fbfec0 ]---

Signed-off-by: Peter Hurley <peter@hurleysoftware.com>
---
 drivers/tty/tty_ldisc.c | 35 +++++++++++++++++++++--------------
 1 file changed, 21 insertions(+), 14 deletions(-)

diff --git a/drivers/tty/tty_ldisc.c b/drivers/tty/tty_ldisc.c
index c3dec37..9f4c7b0 100644
--- a/drivers/tty/tty_ldisc.c
+++ b/drivers/tty/tty_ldisc.c
@@ -528,24 +528,38 @@ static int tty_ldisc_wait_idle(struct tty_struct *tty, long timeout)
 /**
  *	tty_ldisc_halt		-	shut down the line discipline
  *	@tty: tty device
+ *	@pending: returns true if work was scheduled when cancelled
+ *		  (can be set to NULL)
+ *	@timeout: # of jiffies to wait for ldisc refs to be released
  *
  *	Shut down the line discipline and work queue for this tty device.
  *	The TTY_LDISC flag being cleared ensures no further references can
  *	be obtained while the delayed work queue halt ensures that no more
  *	data is fed to the ldisc.
  *
+ *	Furthermore, guarantee that existing ldisc references have been
+ *	released, which in turn, guarantees that no future buffer work
+ *	can be rescheduled.
+ *
  *	You need to do a 'flush_scheduled_work()' (outside the ldisc_mutex)
  *	in order to make sure any currently executing ldisc work is also
  *	flushed.
  */
 
-static int tty_ldisc_halt(struct tty_struct *tty)
+static int tty_ldisc_halt(struct tty_struct *tty, int *pending, long timeout)
 {
-	int scheduled;
+	int scheduled, retval;
+
 	clear_bit(TTY_LDISC, &tty->flags);
+	retval = tty_ldisc_wait_idle(tty, timeout);
+	if (retval)
+		return retval;
+
 	scheduled = cancel_work_sync(&tty->port->buf.work);
 	set_bit(TTY_LDISC_HALTED, &tty->flags);
-	return scheduled;
+	if (pending)
+		*pending = scheduled;
+	return 0;
 }
 
 /**
@@ -687,9 +701,9 @@ int tty_set_ldisc(struct tty_struct *tty, int ldisc)
 	 *	parallel to the change and re-referencing the tty.
 	 */
 
-	work = tty_ldisc_halt(tty);
+	retval = tty_ldisc_halt(tty, &work, 5 * HZ);
 	if (o_tty)
-		o_work = tty_ldisc_halt(o_tty);
+		tty_ldisc_halt(o_tty, &o_work, 0);
 
 	/*
 	 * Wait for ->hangup_work and ->buf.work handlers to terminate.
@@ -700,8 +714,6 @@ int tty_set_ldisc(struct tty_struct *tty, int ldisc)
 
 	tty_ldisc_flush_works(tty);
 
-	retval = tty_ldisc_wait_idle(tty, 5 * HZ);
-
 	tty_lock(tty);
 	mutex_lock(&tty->ldisc_mutex);
 
@@ -920,11 +932,6 @@ int tty_ldisc_setup(struct tty_struct *tty, struct tty_struct *o_tty)
 
 static void tty_ldisc_kill(struct tty_struct *tty)
 {
-	/* There cannot be users from userspace now. But there still might be
-	 * drivers holding a reference via tty_ldisc_ref. Do not steal them the
-	 * ldisc until they are done. */
-	tty_ldisc_wait_idle(tty, MAX_SCHEDULE_TIMEOUT);
-
 	mutex_lock(&tty->ldisc_mutex);
 	/*
 	 * Now kill off the ldisc
@@ -958,10 +965,10 @@ void tty_ldisc_release(struct tty_struct *tty, struct tty_struct *o_tty)
 	 */
 
 	tty_lock_pair(tty, o_tty);
-	tty_ldisc_halt(tty);
+	tty_ldisc_halt(tty, NULL, MAX_SCHEDULE_TIMEOUT);
 	tty_ldisc_flush_works(tty);
 	if (o_tty) {
-		tty_ldisc_halt(o_tty);
+		tty_ldisc_halt(o_tty, NULL, MAX_SCHEDULE_TIMEOUT);
 		tty_ldisc_flush_works(o_tty);
 	}
 
-- 
1.8.0.1


^ permalink raw reply related

* [PATCH v2 11/11] tty: Halt both ldiscs concurrently
From: Peter Hurley @ 2012-12-14 18:22 UTC (permalink / raw)
  To: Alan Cox, Jiri Slaby
  Cc: linux-serial, Greg Kroah-Hartman, Ilya Zykov, Sasha Levin,
	linux-kernel, Peter Hurley
In-Reply-To: <1355509370-5883-1-git-send-email-peter@hurleysoftware.com>

The pty driver does not obtain an ldisc reference to the linked
tty when writing. When the ldiscs are sequentially halted, it
is possible for one ldisc to be halted, and before the second
ldisc can be halted, a concurrent write schedules buffer work on
the first ldisc. This can lead to an access-after-free error when
the scheduled buffer work starts on the closed ldisc.

Prevent subsequent use after halt by performing each stage
of the halt alternately the tty pair.

Signed-off-by: Peter Hurley <peter@hurleysoftware.com>
---
 drivers/tty/tty_ldisc.c | 40 ++++++++++++++++++++++++++--------------
 1 file changed, 26 insertions(+), 14 deletions(-)

diff --git a/drivers/tty/tty_ldisc.c b/drivers/tty/tty_ldisc.c
index 19e088a..a6d3078 100644
--- a/drivers/tty/tty_ldisc.c
+++ b/drivers/tty/tty_ldisc.c
@@ -527,37 +527,53 @@ static int tty_ldisc_wait_idle(struct tty_struct *tty, long timeout)
 /**
  *	tty_ldisc_halt		-	shut down the line discipline
  *	@tty: tty device
+ *	@o_tty: paired pty device (can be NULL)
  *	@pending: returns true if work was scheduled when cancelled
  *		  (can be set to NULL)
+ *	@o_pending: returns true if work was scheduled when cancelled
+ *		    (can be set to NULL)
  *	@timeout: # of jiffies to wait for ldisc refs to be released
  *
- *	Shut down the line discipline and work queue for this tty device.
- *	The TTY_LDISC flag being cleared ensures no further references can
- *	be obtained while the delayed work queue halt ensures that no more
- *	data is fed to the ldisc.
+ *	Shut down the line discipline and work queue for this tty device and
+ *	its paired pty (if exists). Clearing the TTY_LDISC flag ensures
+ *	no further references can be obtained while the work queue halt
+ *	ensures that no more data is fed to the ldisc.
  *
  *	Furthermore, guarantee that existing ldisc references have been
  *	released, which in turn, guarantees that no future buffer work
  *	can be rescheduled.
  *
- *	You need to do a 'flush_scheduled_work()' (outside the ldisc_mutex)
+ *	You need to do a 'tty_ldisc_flush_works()' (outside the ldisc_mutex)
  *	in order to make sure any currently executing ldisc work is also
  *	flushed.
  */
 
-static int tty_ldisc_halt(struct tty_struct *tty, int *pending, long timeout)
+static int tty_ldisc_halt(struct tty_struct *tty, struct tty_struct *o_tty,
+			  int *pending, int *o_pending, long timeout)
 {
-	int scheduled, retval;
+	int scheduled, o_scheduled, retval;
 
 	clear_bit(TTY_LDISC, &tty->flags);
+	if (o_tty)
+		clear_bit(TTY_LDISC, &o_tty->flags);
+
 	retval = tty_ldisc_wait_idle(tty, timeout);
+	if (!retval && o_tty)
+		retval = tty_ldisc_wait_idle(o_tty, timeout);
 	if (retval)
 		return retval;
 
 	scheduled = cancel_work_sync(&tty->port->buf.work);
 	set_bit(TTY_LDISC_HALTED, &tty->flags);
+	if (o_tty) {
+		o_scheduled = cancel_work_sync(&o_tty->port->buf.work);
+		set_bit(TTY_LDISC_HALTED, &o_tty->flags);
+	}
+
 	if (pending)
 		*pending = scheduled;
+	if (o_tty && o_pending)
+		*o_pending = o_scheduled;
 	return 0;
 }
 
@@ -700,9 +716,7 @@ int tty_set_ldisc(struct tty_struct *tty, int ldisc)
 	 *	parallel to the change and re-referencing the tty.
 	 */
 
-	retval = tty_ldisc_halt(tty, &work, 5 * HZ);
-	if (o_tty)
-		tty_ldisc_halt(o_tty, &o_work, 0);
+	retval = tty_ldisc_halt(tty, o_tty, &work, &o_work, 5 * HZ);
 
 	/*
 	 * Wait for ->hangup_work and ->buf.work handlers to terminate.
@@ -964,12 +978,10 @@ void tty_ldisc_release(struct tty_struct *tty, struct tty_struct *o_tty)
 	 */
 
 	tty_lock_pair(tty, o_tty);
-	tty_ldisc_halt(tty, NULL, MAX_SCHEDULE_TIMEOUT);
+	tty_ldisc_halt(tty, o_tty, NULL, NULL, MAX_SCHEDULE_TIMEOUT);
 	tty_ldisc_flush_works(tty);
-	if (o_tty) {
-		tty_ldisc_halt(o_tty, NULL, MAX_SCHEDULE_TIMEOUT);
+	if (o_tty)
 		tty_ldisc_flush_works(o_tty);
-	}
 
 	/* This will need doing differently if we need to lock */
 	tty_ldisc_kill(tty);
-- 
1.8.0.1


^ permalink raw reply related

* Re: Edgeport/416 io_edgeport problem.
From: Greg KH @ 2012-12-15  2:03 UTC (permalink / raw)
  To: David Robillard; +Cc: Alan Cox, linux-serial
In-Reply-To: <CADH15GiJeTmL=WPNoc_8g17AkzyrU4w-jRB0H2wy_+OoGGQByQ@mail.gmail.com>

On Fri, Dec 14, 2012 at 11:47:47AM -0500, David Robillard wrote:
> Hello Alan,
> 
> Thank you for the suggestion.
> 
> > Please try a recent upstream kernel. 2.6.32 is old, and the Red
> > Hat/CentOS version quite a bit different. If 3.6/3.7 break then yes we
> > might care about it.
> 
> I've tried with kernel 3.7.0-1.el6.elrepo.i686 from
> http://elrepo.org/tiki/kernel-ml but unfortunately it's even worse.
> Now the OS only sees half of the Edgeport/416. Ports 1 to 8 are seen
> and /dev/ttyUSB0 to 7 are created. But I still can't access them.
> Ports 10 to 16 are not seen and the character devices are not created.
> 
> Performing `sudo strace cat /dev/ttyUSB0` results in this error :
> 
> open("/dev/ttyUSB0", O_RDONLY|O_LARGEFILE) = -1 ENODEV (No such device)
> 
> Which is exactly the same error I used to get with kernel
> 2.6.32-279.14.1.el6.i686. But at least with that kernel version, the
> OS would see the full 16 ports of the Edgeport/416.
> 
> This Edgeport/416 hardware successfully passed the Digi digital
> loopback test as explained in this article
> http://www.digi.com/support/kbase/kbaseresultdetl?id=3061. The test
> was performed on a Windows 7 machine.
> 
> The model's PN is (1P) 50000780-01 E.
> 
> This device appears to be faulty according this Digi forum thread :
> http://forums.digi.com/support/forum/viewthread_thread,4977#15792
> 
> Any help would be appreciated.
> Dec 14 11:30:54 solo kernel: usb 1-2.6: New USB device found,
> idVendor=1608, idProduct=0012
> Dec 14 11:30:54 solo kernel: usb 1-2.6: New USB device strings: Mfr=1,
> Product=2, SerialNumber=5
> Dec 14 11:30:54 solo kernel: usb 1-2.6: Product: Edgeport/416
> Dec 14 11:30:54 solo kernel: usb 1-2.6: Manufacturer: Inside Out Networks
> Dec 14 11:30:54 solo kernel: usb 1-2.6: SerialNumber: V70430350-1
> Dec 14 11:30:54 solo kernel: usb 1-2.6: Inside Out Networks
> Edgeport/416 detected
> Dec 14 11:30:56 solo kernel: usb 1-2.6: Edgeport 8 port adapter
> converter now attached to ttyUSB0
> Dec 14 11:30:56 solo kernel: usb 1-2.6: Edgeport 8 port adapter
> converter now attached to ttyUSB1
> Dec 14 11:30:56 solo kernel: usb 1-2.6: Edgeport 8 port adapter
> converter now attached to ttyUSB2
> Dec 14 11:30:56 solo kernel: usb 1-2.6: Edgeport 8 port adapter
> converter now attached to ttyUSB3
> Dec 14 11:30:56 solo kernel: usb 1-2.6: Edgeport 8 port adapter
> converter now attached to ttyUSB4
> Dec 14 11:30:56 solo kernel: usb 1-2.6: Edgeport 8 port adapter
> converter now attached to ttyUSB5
> Dec 14 11:30:56 solo kernel: usb 1-2.6: Edgeport 8 port adapter
> converter now attached to ttyUSB6
> Dec 14 11:30:56 solo kernel: usb 1-2.6: Edgeport 8 port adapter
> converter now attached to ttyUSB7

This looks good, so you should be able to talk to the device, the fact
that it returns -ENODEV is odd.

Can you do the following from a command line as root:
Clear out the kernel log:
	dmesg -c
remove the io_edgeport driver
	rmmod io_edgeport
Unplug the device.
load the edgeport driver with debugging enabled:
	modprobe io_edgeport debug=1
plug the device in.

Try to access the device
	cat /dev/ttyUSB0

Send us the output of the kernel log:
	dmesg

Hopefully that should show us what is going on here.

thanks,

greg k-h

^ permalink raw reply

* [PATCH] serial: tegra: add serial driver
From: Laxman Dewangan @ 2012-12-17 12:10 UTC (permalink / raw)
  To: alan, gregkh, jslaby
  Cc: grant.likely, rob.herring, devicetree-discuss, linux-doc,
	linux-kernel, linux-serial, linux-tegra, swarren, Laxman Dewangan

Nvidia's Tegra has multiple uart controller which supports:
- APB dma based controller fifo read/write.
- End Of Data interrupt in incoming data to know whether end
  of frame achieve or not.
- Hw controlled RTS and CTS flow control to reduce SW overhead.

Add serial driver to use all above feature.

Signed-off-by: Laxman Dewangan <ldewangan@nvidia.com>
---
 .../bindings/serial/nvidia,serial-tegra.txt        |   26 +
 drivers/tty/serial/Kconfig                         |   14 +
 drivers/tty/serial/Makefile                        |    1 +
 drivers/tty/serial/serial_tegra.c                  | 1398 ++++++++++++++++++++
 include/linux/serial_tegra.h                       |   33 +
 5 files changed, 1472 insertions(+), 0 deletions(-)
 create mode 100644 Documentation/devicetree/bindings/serial/nvidia,serial-tegra.txt
 create mode 100644 drivers/tty/serial/serial_tegra.c
 create mode 100644 include/linux/serial_tegra.h

diff --git a/Documentation/devicetree/bindings/serial/nvidia,serial-tegra.txt b/Documentation/devicetree/bindings/serial/nvidia,serial-tegra.txt
new file mode 100644
index 0000000..fc5803b
--- /dev/null
+++ b/Documentation/devicetree/bindings/serial/nvidia,serial-tegra.txt
@@ -0,0 +1,26 @@
+NVIDIA Tegra20/Tegra30 high speed (dma based) UART controller driver.
+
+Required properties:
+- compatible : should be "nvidia,tegra20-hsuart", "nvidia,tegra30-hsuart".
+- reg: Should contain UART controller registers location and length.
+- interrupts: Should contain UART controller interrupts.
+- nvidia,dma-request-selector : The Tegra DMA controller's phandle and
+  request selector for this UART controller.
+- port-number: Uart port number for /dev/ttyHSx where x is port number.
+
+Optional properties:
+- nvidia,enable-modem-interrupt: Enable modem interrupts. Should be enable
+		only if all 8 lines of uart controller is pinmuxed.
+
+Example:
+
+serial@70006000 {
+	compatible = "nvidia,tegra30-hsuart", "nvidia,tegra20-hsuart";
+	reg = <0x70006000 0x40>;
+	reg-shift = <2>;
+	interrupts = <0 36 0x04>;
+	port-number = <0>;
+	nvidia,dma-request-selector = <&apbdma 8>;
+	nvidia,enable-modem-interrupt;
+	status = "disabled";
+};
diff --git a/drivers/tty/serial/Kconfig b/drivers/tty/serial/Kconfig
index 59c23d0..57dbbc1 100644
--- a/drivers/tty/serial/Kconfig
+++ b/drivers/tty/serial/Kconfig
@@ -269,6 +269,20 @@ config SERIAL_SIRFSOC_CONSOLE
           your boot loader about how to pass options to the kernel at
           boot time.)
 
+config SERIAL_SAMSUNG_UARTS_4
+	bool
+
+config SERIAL_TEGRA
+	tristate "Nvidia Tegra20/30 SoC serial controller"
+	depends on ARCH_TEGRA && TEGRA20_APB_DMA
+	select SERIAL_CORE
+	help
+	  Support for the on-chip UARTs on the Nvidia Tegra seria SOCs
+	  providing /dev/ttyHS0, 1, 2, 3 and 4 (note, some machines may not
+	  provide all of these ports, depending on how the serial port
+	  are enabled). This driver uses the APB dma to achieve higher baudrate
+	  and better performance.
+
 config SERIAL_MAX3100
 	tristate "MAX3100 support"
 	depends on SPI
diff --git a/drivers/tty/serial/Makefile b/drivers/tty/serial/Makefile
index df1b998..73fb688 100644
--- a/drivers/tty/serial/Makefile
+++ b/drivers/tty/serial/Makefile
@@ -80,6 +80,7 @@ obj-$(CONFIG_SERIAL_MXS_AUART) += mxs-auart.o
 obj-$(CONFIG_SERIAL_LANTIQ)	+= lantiq.o
 obj-$(CONFIG_SERIAL_XILINX_PS_UART) += xilinx_uartps.o
 obj-$(CONFIG_SERIAL_SIRFSOC) += sirfsoc_uart.o
+obj-$(CONFIG_SERIAL_TEGRA) += serial_tegra.o
 obj-$(CONFIG_SERIAL_AR933X)   += ar933x_uart.o
 obj-$(CONFIG_SERIAL_EFM32_UART) += efm32-uart.o
 obj-$(CONFIG_SERIAL_ARC)	+= arc_uart.o
diff --git a/drivers/tty/serial/serial_tegra.c b/drivers/tty/serial/serial_tegra.c
new file mode 100644
index 0000000..791cf35
--- /dev/null
+++ b/drivers/tty/serial/serial_tegra.c
@@ -0,0 +1,1398 @@
+/*
+ * serial_tegra.c
+ *
+ * High-speed serial driver for NVIDIA Tegra SoCs
+ *
+ * Copyright (c) 2012, NVIDIA CORPORATION.  All rights reserved.
+ *
+ * Author: Laxman Dewangan <ldewangan@nvidia.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <linux/clk.h>
+#include <linux/debugfs.h>
+#include <linux/delay.h>
+#include <linux/dmaengine.h>
+#include <linux/dma-mapping.h>
+#include <linux/dmapool.h>
+#include <linux/io.h>
+#include <linux/irq.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/pagemap.h>
+#include <linux/platform_device.h>
+#include <linux/serial.h>
+#include <linux/serial_8250.h>
+#include <linux/serial_core.h>
+#include <linux/serial_reg.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/serial_tegra.h>
+#include <linux/termios.h>
+#include <linux/tty_flip.h>
+
+#include <mach/clk.h>
+
+#define TEGRA_UART_TYPE				"TEGRA_UART"
+#define TX_EMPTY_STATUS				(UART_LSR_TEMT | UART_LSR_THRE)
+#define BYTES_TO_ALIGN(x)			((unsigned long)(x) & 0x3)
+
+#define TEGRA_UART_RX_DMA_BUFFER_SIZE		4096
+#define TEGRA_UART_LSR_TXFIFO_FULL		0x100
+#define TEGRA_UART_IER_EORD			0x20
+#define TEGRA_UART_MCR_RTS_EN			0x40
+#define TEGRA_UART_MCR_CTS_EN			0x20
+#define TEGRA_UART_LSR_ANY			(UART_LSR_OE | UART_LSR_BI | \
+						UART_LSR_PE | UART_LSR_FE)
+#define TEGRA_UART_IRDA_CSR			0x08
+#define TEGRA_UART_SIR_ENABLED			0x80
+
+#define TEGRA_UART_TX_PIO			1
+#define TEGRA_UART_TX_DMA			2
+#define TEGRA_UART_MIN_DMA			16
+#define TEGRA_UART_FIFO_SIZE			32
+
+/*
+ * Tx fifo trigger level setting in tegra uart is in
+ * reverse way then conventional uart.
+ */
+#define TEGRA_UART_TX_TRIG_16B			0x00
+#define TEGRA_UART_TX_TRIG_8B			0x10
+#define TEGRA_UART_TX_TRIG_4B			0x20
+#define TEGRA_UART_TX_TRIG_1B			0x30
+
+#define TEGRA_UART_MAXIMUM			5
+
+/* Default UART setting when started: 115200 no parity, stop, 8 data bits */
+#define TEGRA_UART_DEFAULT_BAUD			115200
+#define TEGRA_UART_DEFAULT_LSR			UART_LCR_WLEN8
+
+/* Tx transfer mode */
+#define TEGRA_TX_PIO				1
+#define TEGRA_TX_DMA				2
+
+/**
+ * tegra_uart_chip_data: SOC specific data.
+ *
+ * @tx_fifo_full_status: Status flag available for checking tx fifo full.
+ * @allow_txfifo_reset_fifo_mode: allow_tx fifo reset with fifo mode or not.
+ *			Tegra30 does not allow this.
+ * @support_clk_src_div: Clock source support the clock divider.
+ */
+struct tegra_uart_chip_data {
+	bool	tx_fifo_full_status;
+	bool	allow_txfifo_reset_fifo_mode;
+	bool	support_clk_src_div;
+};
+
+struct tegra_uart_chip_data tegra20_uart_chip_data = {
+	.tx_fifo_full_status		= false,
+	.allow_txfifo_reset_fifo_mode	= true,
+	.support_clk_src_div		= false,
+};
+
+struct tegra_uart_chip_data tegra30_uart_chip_data = {
+	.tx_fifo_full_status		= true,
+	.allow_txfifo_reset_fifo_mode	= false,
+	.support_clk_src_div		= true,
+};
+
+struct tegra_uart_port {
+	struct uart_port			uport;
+	const struct tegra_uart_chip_data	*cdata;
+
+	struct clk				*uart_clk;
+	unsigned int				current_baud;
+
+	/* Register shadow */
+	unsigned long				fcr_shadow;
+	unsigned long				mcr_shadow;
+	unsigned long				lcr_shadow;
+	unsigned long				ier_shadow;
+	bool					rts_active;
+
+	int					tx_in_progress;
+	unsigned int				tx_bytes;
+
+	int					enable_modem_interrupt;
+
+	bool					rx_timeout;
+	int					rx_in_progress;
+	int					symb_bit;
+	int					dma_req_sel;
+
+	struct dma_chan				*rx_dma_chan;
+	struct dma_chan				*tx_dma_chan;
+	dma_addr_t				rx_dma_buf_phys;
+	dma_addr_t				tx_dma_buf_phys;
+	unsigned char				*rx_dma_buf_virt;
+	unsigned char				*tx_dma_buf_virt;
+	struct dma_async_tx_descriptor		*tx_dma_desc;
+	struct dma_async_tx_descriptor		*rx_dma_desc;
+	dma_cookie_t				tx_cookie;
+	dma_cookie_t				rx_cookie;
+	int					tx_bytes_requested;
+	int					rx_bytes_requested;
+};
+
+static void tegra_uart_start_next_tx(struct tegra_uart_port *tup);
+static int tegra_uart_start_rx_dma(struct tegra_uart_port *tup);
+
+static inline unsigned long tegra_uart_read(struct tegra_uart_port *tup,
+		unsigned long reg)
+{
+	return readl(tup->uport.membase + (reg << tup->uport.regshift));
+}
+
+static inline void tegra_uart_write(struct tegra_uart_port *tup, unsigned val,
+	unsigned long reg)
+{
+	writel(val, tup->uport.membase + (reg << tup->uport.regshift));
+}
+
+static inline struct tegra_uart_port *to_tegra_uport(struct uart_port *u)
+{
+	return container_of(u, struct tegra_uart_port, uport);
+}
+
+static unsigned int tegra_uart_get_mctrl(struct uart_port *u)
+{
+	struct tegra_uart_port *tup = to_tegra_uport(u);
+
+	/*
+	 * RI - Ring detector is active
+	 * CD/DCD/CAR - Carrier detect is always active. For some reason
+	 *	linux has different names for carrier detect.
+	 * DSR - Data Set ready is active as the hardware doesn't support it.
+	 *	Don't know if the linux support this yet?
+	 * CTS - Clear to send. Always set to active, as the hardware handles
+	 *	CTS automatically.
+	 */
+	if (tup->enable_modem_interrupt)
+		return TIOCM_RI | TIOCM_CD | TIOCM_DSR | TIOCM_CTS;
+	return TIOCM_CTS;
+}
+
+static void set_rts(struct tegra_uart_port *tup, bool active)
+{
+	unsigned long mcr;
+
+	mcr = tup->mcr_shadow;
+	if (active)
+		mcr |= TEGRA_UART_MCR_RTS_EN;
+	else
+		mcr &= ~TEGRA_UART_MCR_RTS_EN;
+	if (mcr != tup->mcr_shadow) {
+		tegra_uart_write(tup, mcr, UART_MCR);
+		tup->mcr_shadow = mcr;
+	}
+	return;
+}
+
+static void set_dtr(struct tegra_uart_port *tup, bool active)
+{
+	unsigned long mcr;
+
+	mcr = tup->mcr_shadow;
+	if (active)
+		mcr |= UART_MCR_DTR;
+	else
+		mcr &= ~UART_MCR_DTR;
+	if (mcr != tup->mcr_shadow) {
+		tegra_uart_write(tup, mcr, UART_MCR);
+		tup->mcr_shadow = mcr;
+	}
+	return;
+}
+
+static void tegra_uart_set_mctrl(struct uart_port *u, unsigned int mctrl)
+{
+	struct tegra_uart_port *tup = to_tegra_uport(u);
+	unsigned long mcr;
+
+	mcr = tup->mcr_shadow;
+	if (mctrl & TIOCM_RTS) {
+		tup->rts_active = true;
+		set_rts(tup, true);
+	} else {
+		tup->rts_active = false;
+		set_rts(tup, false);
+	}
+
+	if (mctrl & TIOCM_DTR)
+		set_dtr(tup, true);
+	else
+		set_dtr(tup, false);
+	return;
+}
+
+static void tegra_uart_break_ctl(struct uart_port *u, int break_ctl)
+{
+	struct tegra_uart_port *tup = to_tegra_uport(u);
+	unsigned long lcr;
+
+	lcr = tup->lcr_shadow;
+	if (break_ctl)
+		lcr |= UART_LCR_SBC;
+	else
+		lcr &= ~UART_LCR_SBC;
+	tegra_uart_write(tup, lcr, UART_LCR);
+	tup->lcr_shadow = lcr;
+}
+
+/* Wait for a symbol-time. */
+static void tegra_uart_wait_sym_time(struct tegra_uart_port *tup,
+		unsigned int syms)
+{
+	if (tup->current_baud)
+		udelay(DIV_ROUND_UP(syms * tup->symb_bit * 1000000,
+			tup->current_baud));
+}
+
+static void tegra_uart_fifo_reset(struct tegra_uart_port *tup, u8 fcr_bits)
+{
+	unsigned long fcr = tup->fcr_shadow;
+
+	if (tup->cdata->allow_txfifo_reset_fifo_mode) {
+		fcr |= fcr_bits & (UART_FCR_CLEAR_RCVR | UART_FCR_CLEAR_XMIT);
+		tegra_uart_write(tup, fcr, UART_FCR);
+	} else {
+		fcr &= ~UART_FCR_ENABLE_FIFO;
+		tegra_uart_write(tup, fcr, UART_FCR);
+		udelay(60);
+		fcr |= fcr_bits & (UART_FCR_CLEAR_RCVR | UART_FCR_CLEAR_XMIT);
+		tegra_uart_write(tup, fcr, UART_FCR);
+		fcr |= UART_FCR_ENABLE_FIFO;
+		tegra_uart_write(tup, fcr, UART_FCR);
+	}
+
+	/* Dummy read to ensure the write is posted */
+	tegra_uart_read(tup, UART_SCR);
+
+	/* Wait for the flush to propagate. */
+	tegra_uart_wait_sym_time(tup, 1);
+}
+
+static int tegra_set_baudrate(struct tegra_uart_port *tup, unsigned int baud)
+{
+	unsigned long rate;
+	unsigned int divisor;
+	unsigned long lcr;
+	int ret;
+
+	if (tup->current_baud == baud)
+		return 0;
+
+	if (tup->cdata->support_clk_src_div) {
+		rate = baud * 16;
+		ret = clk_set_rate(tup->uart_clk, rate);
+		if (ret < 0) {
+			dev_err(tup->uport.dev,
+				"clk_set_rate() failed for rate %lu\n", rate);
+			return ret;
+		}
+		divisor = 1;
+	} else {
+		rate = clk_get_rate(tup->uart_clk);
+		divisor = DIV_ROUND_CLOSEST(rate, baud * 16);
+	}
+
+	lcr = tup->lcr_shadow;
+	lcr |= UART_LCR_DLAB;
+	tegra_uart_write(tup, lcr, UART_LCR);
+
+	tegra_uart_write(tup, divisor & 0xFF, UART_TX);
+	tegra_uart_write(tup, ((divisor >> 8) & 0xFF), UART_IER);
+
+	lcr &= ~UART_LCR_DLAB;
+	tegra_uart_write(tup, lcr, UART_LCR);
+
+	/* Dummy read to ensure the write is posted */
+	tegra_uart_read(tup, UART_SCR);
+
+	tup->current_baud = baud;
+
+	/* wait two character intervals at new rate */
+	tegra_uart_wait_sym_time(tup, 2);
+	return 0;
+}
+
+static char tegra_uart_decode_rx_error(struct tegra_uart_port *tup,
+			unsigned long lsr)
+{
+	char flag = TTY_NORMAL;
+
+	if (unlikely(lsr & TEGRA_UART_LSR_ANY)) {
+		if (lsr & UART_LSR_OE) {
+			/* Overrrun error */
+			flag |= TTY_OVERRUN;
+			tup->uport.icount.overrun++;
+			dev_err(tup->uport.dev, "Got overrun errors\n");
+		} else if (lsr & UART_LSR_PE) {
+			/* Parity error */
+			flag |= TTY_PARITY;
+			tup->uport.icount.parity++;
+			dev_err(tup->uport.dev, "Got Parity errors\n");
+		} else if (lsr & UART_LSR_FE) {
+			flag |= TTY_FRAME;
+			tup->uport.icount.frame++;
+			dev_err(tup->uport.dev, "Got frame errors\n");
+		} else if (lsr & UART_LSR_BI) {
+			dev_err(tup->uport.dev, "Got Break\n");
+			tup->uport.icount.brk++;
+			/* If FIFO read error without any data, reset Rx FIFO */
+			if (!(lsr & UART_LSR_DR) && (lsr & UART_LSR_FIFOE))
+				tegra_uart_fifo_reset(tup, UART_FCR_CLEAR_RCVR);
+		}
+	}
+	return flag;
+}
+
+static int tegra_uart_request_port(struct uart_port *u)
+{
+	return 0;
+}
+
+static void tegra_uart_release_port(struct uart_port *u)
+{
+	/* Nothing to do here */
+}
+
+static void tegra_uart_fill_tx_fifo(struct tegra_uart_port *tup, int max_bytes)
+{
+	struct circ_buf *xmit = &tup->uport.state->xmit;
+	int i;
+
+	for (i = 0; i < max_bytes; i++) {
+		BUG_ON(uart_circ_empty(xmit));
+		if (tup->cdata->tx_fifo_full_status) {
+			unsigned long lsr = tegra_uart_read(tup, UART_LSR);
+			if ((lsr & TEGRA_UART_LSR_TXFIFO_FULL))
+				break;
+		}
+		tegra_uart_write(tup, xmit->buf[xmit->tail], UART_TX);
+		xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1);
+		tup->uport.icount.tx++;
+	}
+}
+
+static void tegra_uart_start_pio_tx(struct tegra_uart_port *tup,
+		unsigned int bytes)
+{
+	if (bytes > TEGRA_UART_MIN_DMA)
+		bytes = TEGRA_UART_MIN_DMA;
+
+	tup->tx_in_progress = TEGRA_UART_TX_PIO;
+	tup->tx_bytes = bytes;
+	tup->ier_shadow |= UART_IER_THRI;
+	tegra_uart_write(tup, tup->ier_shadow, UART_IER);
+}
+
+static void tegra_uart_tx_dma_complete(void *args)
+{
+	struct tegra_uart_port *tup = args;
+	struct circ_buf *xmit = &tup->uport.state->xmit;
+	struct dma_tx_state state;
+	unsigned long flags;
+	int count;
+
+	dmaengine_tx_status(tup->tx_dma_chan, tup->rx_cookie, &state);
+	count = tup->tx_bytes_requested - state.residue;
+	async_tx_ack(tup->tx_dma_desc);
+	spin_lock_irqsave(&tup->uport.lock, flags);
+	xmit->tail = (xmit->tail + count) & (UART_XMIT_SIZE - 1);
+	tup->tx_in_progress = 0;
+	if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)
+		uart_write_wakeup(&tup->uport);
+	tegra_uart_start_next_tx(tup);
+	spin_unlock_irqrestore(&tup->uport.lock, flags);
+}
+
+static int tegra_uart_start_tx_dma(struct tegra_uart_port *tup,
+		unsigned long count)
+{
+	struct circ_buf *xmit = &tup->uport.state->xmit;
+	dma_addr_t tx_phys_addr;
+
+	dma_sync_single_for_device(tup->uport.dev, tup->tx_dma_buf_phys,
+				UART_XMIT_SIZE, DMA_TO_DEVICE);
+
+	tup->tx_bytes = count & ~(0xF);
+	tx_phys_addr = tup->tx_dma_buf_phys + xmit->tail;
+	tup->tx_dma_desc = dmaengine_prep_slave_single(tup->tx_dma_chan,
+				tx_phys_addr, tup->tx_bytes, DMA_MEM_TO_DEV,
+				DMA_PREP_INTERRUPT);
+	if (!tup->tx_dma_desc) {
+		dev_err(tup->uport.dev, "Not able to get desc for Tx\n");
+		return -EIO;
+	}
+
+	tup->tx_dma_desc->callback = tegra_uart_tx_dma_complete;
+	tup->tx_dma_desc->callback_param = tup;
+	tup->tx_in_progress = TEGRA_UART_TX_DMA;
+	tup->tx_bytes_requested = tup->tx_bytes;
+	tup->tx_cookie = dmaengine_submit(tup->tx_dma_desc);
+	dma_async_issue_pending(tup->tx_dma_chan);
+	return 0;
+}
+
+static void tegra_uart_start_next_tx(struct tegra_uart_port *tup)
+{
+	unsigned long tail;
+	unsigned long count;
+	struct circ_buf *xmit = &tup->uport.state->xmit;
+
+	tail = (unsigned long)&xmit->buf[xmit->tail];
+	count = CIRC_CNT_TO_END(xmit->head, xmit->tail, UART_XMIT_SIZE);
+	if (!count)
+		return;
+
+	if (count < TEGRA_UART_MIN_DMA)
+		tegra_uart_start_pio_tx(tup, count);
+	else if (BYTES_TO_ALIGN(tail) > 0)
+		tegra_uart_start_pio_tx(tup, BYTES_TO_ALIGN(tail));
+	else
+		tegra_uart_start_tx_dma(tup, count);
+}
+
+/* Called by serial core driver with u->lock taken. */
+static void tegra_uart_start_tx(struct uart_port *u)
+{
+	struct tegra_uart_port *tup = to_tegra_uport(u);
+	struct circ_buf *xmit = &u->state->xmit;
+
+	if (!uart_circ_empty(xmit) && !tup->tx_in_progress)
+		tegra_uart_start_next_tx(tup);
+}
+
+static unsigned int tegra_uart_tx_empty(struct uart_port *u)
+{
+	struct tegra_uart_port *tup = to_tegra_uport(u);
+	unsigned int ret = 0;
+	unsigned long flags;
+
+	spin_lock_irqsave(&u->lock, flags);
+	if (!tup->tx_in_progress) {
+		unsigned long lsr = tegra_uart_read(tup, UART_LSR);
+		if ((lsr & TX_EMPTY_STATUS) == TX_EMPTY_STATUS)
+			ret = TIOCSER_TEMT;
+	}
+	spin_unlock_irqrestore(&u->lock, flags);
+	return ret;
+}
+
+static void tegra_uart_stop_tx(struct uart_port *u)
+{
+	struct tegra_uart_port *tup = to_tegra_uport(u);
+	struct circ_buf *xmit = &tup->uport.state->xmit;
+	struct dma_tx_state state;
+	int count;
+
+	dmaengine_terminate_all(tup->tx_dma_chan);
+	dmaengine_tx_status(tup->tx_dma_chan, tup->tx_cookie, &state);
+	count = tup->tx_bytes_requested - state.residue;
+	async_tx_ack(tup->tx_dma_desc);
+	xmit->tail = (xmit->tail + count) & (UART_XMIT_SIZE - 1);
+	tup->tx_in_progress = 0;
+	return;
+}
+
+static void tegra_uart_handle_tx_pio(struct tegra_uart_port *tup)
+{
+	struct circ_buf *xmit = &tup->uport.state->xmit;
+
+	tegra_uart_fill_tx_fifo(tup, tup->tx_bytes);
+	tup->tx_in_progress = 0;
+	if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)
+		uart_write_wakeup(&tup->uport);
+	tegra_uart_start_next_tx(tup);
+	return;
+}
+
+static void tegra_uart_handle_rx_pio(struct tegra_uart_port *tup)
+{
+	do {
+		char flag = TTY_NORMAL;
+		unsigned long lsr = 0;
+		unsigned char ch;
+
+		lsr = tegra_uart_read(tup, UART_LSR);
+		if (!(lsr & UART_LSR_DR))
+			break;
+
+		flag = tegra_uart_decode_rx_error(tup, lsr);
+		ch = (unsigned char) tegra_uart_read(tup, UART_RX);
+		tup->uport.icount.rx++;
+
+		if (!uart_handle_sysrq_char(&tup->uport, ch))
+			uart_insert_char(&tup->uport, lsr,
+					UART_LSR_OE, ch, flag);
+	} while (1);
+
+	return;
+}
+
+static void tegra_uart_copy_rx_to_tty(struct tegra_uart_port *tup, int count)
+{
+	int copied;
+
+	tup->uport.icount.rx += count;
+	dma_sync_single_for_cpu(tup->uport.dev, tup->rx_dma_buf_phys,
+				TEGRA_UART_RX_DMA_BUFFER_SIZE, DMA_FROM_DEVICE);
+	copied = tty_insert_flip_string(tup->uport.state->port.tty,
+			((unsigned char *)(tup->rx_dma_buf_virt)), count);
+	if (copied != count) {
+		WARN_ON(1);
+		dev_err(tup->uport.dev, "RxData copy to tty layer failed\n");
+	}
+	dma_sync_single_for_device(tup->uport.dev, tup->rx_dma_buf_phys,
+				TEGRA_UART_RX_DMA_BUFFER_SIZE, DMA_TO_DEVICE);
+}
+
+static void tegra_uart_rx_dma_complete(void *args)
+{
+	struct tegra_uart_port *tup = args;
+	struct uart_port *u = &tup->uport;
+	int count = tup->rx_bytes_requested;
+	unsigned long flags;
+
+	async_tx_ack(tup->rx_dma_desc);
+	spin_lock_irqsave(&u->lock, flags);
+
+	/* Deactivate flow control to stop sender */
+	if (tup->rts_active)
+		set_rts(tup, false);
+
+	/* If we are here, DMA is stopped */
+	if (count)
+		tegra_uart_copy_rx_to_tty(tup, count);
+
+	tegra_uart_handle_rx_pio(tup);
+	tty_flip_buffer_push(u->state->port.tty);
+	tegra_uart_start_rx_dma(tup);
+
+	/* Activate flow control to start transfer */
+	if (tup->rts_active)
+		set_rts(tup, true);
+
+	spin_unlock_irqrestore(&u->lock, flags);
+}
+
+static void tegra_uart_handle_rx_dma(struct tegra_uart_port *tup)
+{
+	struct uart_port *u = &tup->uport;
+	struct dma_tx_state state;
+	int count;
+
+	/* Deactivate flow control to stop sender */
+	if (tup->rts_active)
+		set_rts(tup, false);
+
+	dmaengine_terminate_all(tup->rx_dma_chan);
+	dmaengine_tx_status(tup->rx_dma_chan,  tup->rx_cookie, &state);
+	count = tup->rx_bytes_requested - state.residue;
+
+	/* If we are here, DMA is stopped */
+	if (count)
+		tegra_uart_copy_rx_to_tty(tup, count);
+
+	tegra_uart_handle_rx_pio(tup);
+	tty_flip_buffer_push(u->state->port.tty);
+	tegra_uart_start_rx_dma(tup);
+
+	if (tup->rts_active)
+		set_rts(tup, true);
+}
+
+static int tegra_uart_start_rx_dma(struct tegra_uart_port *tup)
+{
+	unsigned int count = TEGRA_UART_RX_DMA_BUFFER_SIZE;
+
+	tup->rx_dma_desc = dmaengine_prep_slave_single(tup->rx_dma_chan,
+				tup->rx_dma_buf_phys, count, DMA_DEV_TO_MEM,
+				DMA_PREP_INTERRUPT);
+	if (!tup->rx_dma_desc) {
+		dev_err(tup->uport.dev, "Not able to get desc for Rx\n");
+		return -EIO;
+	}
+
+	tup->rx_dma_desc->callback = tegra_uart_rx_dma_complete;
+	tup->rx_dma_desc->callback_param = tup;
+	dma_sync_single_for_device(tup->uport.dev, tup->rx_dma_buf_phys,
+				count, DMA_TO_DEVICE);
+	tup->rx_bytes_requested = count;
+	tup->rx_cookie = dmaengine_submit(tup->rx_dma_desc);
+	dma_async_issue_pending(tup->rx_dma_chan);
+	return 0;
+}
+
+static void tegra_uart_handle_modem_signal_change(struct uart_port *u)
+{
+	struct tegra_uart_port *tup = to_tegra_uport(u);
+	unsigned long msr;
+
+	msr = tegra_uart_read(tup, UART_MSR);
+	if (!(msr & UART_MSR_ANY_DELTA))
+		return;
+
+	if (msr & UART_MSR_TERI)
+		tup->uport.icount.rng++;
+	if (msr & UART_MSR_DDSR)
+		tup->uport.icount.dsr++;
+	/* We may only get DDCD when HW init and reset */
+	if (msr & UART_MSR_DDCD)
+		uart_handle_dcd_change(&tup->uport, msr & UART_MSR_DCD);
+	/* Will start/stop_tx accordingly */
+	if (msr & UART_MSR_DCTS)
+		uart_handle_cts_change(&tup->uport, msr & UART_MSR_CTS);
+	return;
+}
+
+static irqreturn_t tegra_uart_isr(int irq, void *data)
+{
+	struct tegra_uart_port *tup = data;
+	struct uart_port *u = &tup->uport;
+	unsigned long iir;
+	unsigned long ier;
+	bool is_rx_int = false;
+	unsigned long flags;
+
+	spin_lock_irqsave(&u->lock, flags);
+	while (1) {
+		iir = tegra_uart_read(tup, UART_IIR);
+		if (iir & UART_IIR_NO_INT) {
+			if (is_rx_int) {
+				tegra_uart_handle_rx_dma(tup);
+				if (tup->rx_in_progress) {
+					ier = tup->ier_shadow;
+					ier |= (UART_IER_RLSI | UART_IER_RTOIE |
+						TEGRA_UART_IER_EORD);
+					tup->ier_shadow = ier;
+					tegra_uart_write(tup, ier, UART_IER);
+				}
+			}
+			spin_unlock_irqrestore(&u->lock, flags);
+			return IRQ_HANDLED;
+		}
+
+		switch ((iir >> 1) & 0x7) {
+		case 0: /* Modem signal change interrupt */
+			tegra_uart_handle_modem_signal_change(u);
+			break;
+
+		case 1: /* Transmit interrupt only triggered when using PIO */
+			tup->ier_shadow &= ~UART_IER_THRI;
+			tegra_uart_write(tup, tup->ier_shadow, UART_IER);
+			tegra_uart_handle_tx_pio(tup);
+			break;
+
+		case 4: /* End of data */
+		case 6: /* Rx timeout */
+		case 2: /* Receive */
+			if (!is_rx_int) {
+				is_rx_int = true;
+				/* Disable Rx interrupts */
+				ier = tup->ier_shadow;
+				ier |= UART_IER_RDI;
+				tegra_uart_write(tup, ier, UART_IER);
+				ier &= ~(UART_IER_RDI | UART_IER_RLSI |
+					UART_IER_RTOIE | TEGRA_UART_IER_EORD);
+				tup->ier_shadow = ier;
+				tegra_uart_write(tup, ier, UART_IER);
+			}
+			break;
+
+		case 3: /* Receive error */
+			tegra_uart_decode_rx_error(tup,
+					tegra_uart_read(tup, UART_LSR));
+			break;
+
+		case 5: /* break nothing to handle */
+		case 7: /* break nothing to handle */
+			break;
+		}
+	}
+}
+
+static void tegra_uart_stop_rx(struct uart_port *u)
+{
+	struct tegra_uart_port *tup = to_tegra_uport(u);
+	struct dma_tx_state state;
+	unsigned long ier;
+	int count;
+
+	if (tup->rts_active)
+		set_rts(tup, false);
+
+	if (!tup->rx_in_progress)
+		return;
+
+	tegra_uart_wait_sym_time(tup, 1); /* wait a character interval */
+
+	ier = tup->ier_shadow;
+	ier &= ~(UART_IER_RDI | UART_IER_RLSI | UART_IER_RTOIE |
+					TEGRA_UART_IER_EORD);
+	tup->ier_shadow = ier;
+	tegra_uart_write(tup, ier, UART_IER);
+	tup->rx_in_progress = 0;
+	if (tup->rx_dma_chan) {
+		dmaengine_terminate_all(tup->rx_dma_chan);
+		dmaengine_tx_status(tup->rx_dma_chan, tup->rx_cookie, &state);
+		async_tx_ack(tup->rx_dma_desc);
+		count = tup->rx_bytes_requested - state.residue;
+		tegra_uart_copy_rx_to_tty(tup, count);
+		tegra_uart_handle_rx_pio(tup);
+	} else {
+		tegra_uart_handle_rx_pio(tup);
+	}
+	tty_flip_buffer_push(u->state->port.tty);
+	return;
+}
+
+static void tegra_uart_hw_deinit(struct tegra_uart_port *tup)
+{
+	unsigned long flags;
+	unsigned long char_time = DIV_ROUND_UP(10000000, tup->current_baud);
+	unsigned long fifo_empty_time = tup->uport.fifosize * char_time;
+	unsigned long wait_time;
+	unsigned long lsr;
+	unsigned long msr;
+	unsigned long mcr;
+
+	/* Disable interrupts */
+	tegra_uart_write(tup, 0, UART_IER);
+
+	lsr = tegra_uart_read(tup, UART_LSR);
+	if ((lsr & UART_LSR_TEMT) != UART_LSR_TEMT) {
+		msr = tegra_uart_read(tup, UART_MSR);
+		mcr = tegra_uart_read(tup, UART_MCR);
+		if ((mcr & TEGRA_UART_MCR_CTS_EN) && (msr & UART_MSR_CTS))
+			dev_err(tup->uport.dev,
+				"Tx Fifo not empty, CTS disabled, waiting\n");
+
+		/* Wait for Tx fifo to be empty */
+		while ((lsr & UART_LSR_TEMT) != UART_LSR_TEMT) {
+			wait_time = min(fifo_empty_time, 100lu);
+			udelay(wait_time);
+			fifo_empty_time -= wait_time;
+			if (!fifo_empty_time) {
+				msr = tegra_uart_read(tup, UART_MSR);
+				mcr = tegra_uart_read(tup, UART_MCR);
+				if ((mcr & TEGRA_UART_MCR_CTS_EN) &&
+					(msr & UART_MSR_CTS))
+					dev_err(tup->uport.dev,
+						"Slave not ready\n");
+				break;
+			}
+			lsr = tegra_uart_read(tup, UART_LSR);
+		}
+	}
+
+	spin_lock_irqsave(&tup->uport.lock, flags);
+	/* Reset the Rx and Tx FIFOs */
+	tegra_uart_fifo_reset(tup, UART_FCR_CLEAR_XMIT | UART_FCR_CLEAR_RCVR);
+	tup->current_baud = 0;
+	spin_unlock_irqrestore(&tup->uport.lock, flags);
+
+	clk_disable_unprepare(tup->uart_clk);
+}
+
+static int tegra_uart_hw_init(struct tegra_uart_port *tup)
+{
+	int ret;
+
+	tup->fcr_shadow = 0;
+	tup->mcr_shadow = 0;
+	tup->lcr_shadow = 0;
+	tup->ier_shadow = 0;
+	tup->current_baud = 0;
+
+	clk_prepare_enable(tup->uart_clk);
+
+	/* Reset the UART controller to clear all previous status.*/
+	tegra_periph_reset_assert(tup->uart_clk);
+	udelay(10);
+	tegra_periph_reset_deassert(tup->uart_clk);
+
+	tup->rx_in_progress = 0;
+	tup->tx_in_progress = 0;
+
+	/*
+	 * Set the trigger level
+	 *
+	 * For PIO mode:
+	 *
+	 * For receive, this will interrupt the CPU after that many number of
+	 * bytes are received, for the remaining bytes the receive timeout
+	 * interrupt is received. Rx high watermark is set to 4.
+	 *
+	 * For transmit, if the trasnmit interrupt is enabled, this will
+	 * interrupt the CPU when the number of entries in the FIFO reaches the
+	 * low watermark. Tx low watermark is set to 16 bytes.
+	 *
+	 * For DMA mode:
+	 *
+	 * Set the Tx trigger to 16. This should match the DMA burst size that
+	 * programmed in the DMA registers.
+	 */
+	tup->fcr_shadow = UART_FCR_ENABLE_FIFO;
+	tup->fcr_shadow |= UART_FCR_R_TRIG_01;
+	tup->fcr_shadow |= TEGRA_UART_TX_TRIG_16B;
+	tegra_uart_write(tup, tup->fcr_shadow, UART_FCR);
+
+	/*
+	 * Initialize the UART with default configuration
+	 * (115200, N, 8, 1) so that the receive DMA buffer may be
+	 * enqueued
+	 */
+	tup->lcr_shadow = TEGRA_UART_DEFAULT_LSR;
+	tegra_set_baudrate(tup, TEGRA_UART_DEFAULT_BAUD);
+	tup->fcr_shadow |= UART_FCR_DMA_SELECT;
+	tegra_uart_write(tup, tup->fcr_shadow, UART_FCR);
+
+	ret = tegra_uart_start_rx_dma(tup);
+	if (ret < 0) {
+		dev_err(tup->uport.dev, "Not able to start Rx DMA\n");
+		return ret;
+	}
+	tup->rx_in_progress = 1;
+
+	/*
+	 * Enable IE_RXS for the receive status interrupts like line errros.
+	 * Enable IE_RX_TIMEOUT to get the bytes which cannot be DMA'd.
+	 *
+	 * If using DMA mode, enable EORD instead of receive interrupt which
+	 * will interrupt after the UART is done with the receive instead of
+	 * the interrupt when the FIFO "threshold" is reached.
+	 *
+	 * EORD is different interrupt than RX_TIMEOUT - RX_TIMEOUT occurs when
+	 * the DATA is sitting in the FIFO and couldn't be transferred to the
+	 * DMA as the DMA size alignment(4 bytes) is not met. EORD will be
+	 * triggered when there is a pause of the incomming data stream for 4
+	 * characters long.
+	 *
+	 * For pauses in the data which is not aligned to 4 bytes, we get
+	 * both the EORD as well as RX_TIMEOUT - SW sees RX_TIMEOUT first
+	 * then the EORD.
+	 */
+	tup->ier_shadow = UART_IER_RLSI | UART_IER_RTOIE | TEGRA_UART_IER_EORD;
+	tegra_uart_write(tup, tup->ier_shadow, UART_IER);
+	return 0;
+}
+
+static int tegra_uart_dma_channel_allocate(struct tegra_uart_port *tup,
+			bool dma_to_memory)
+{
+	struct dma_chan *dma_chan;
+	unsigned char *dma_buf;
+	dma_addr_t dma_phys;
+	int ret;
+	struct dma_slave_config dma_sconfig;
+	dma_cap_mask_t mask;
+
+	dma_cap_zero(mask);
+	dma_cap_set(DMA_SLAVE, mask);
+	dma_chan = dma_request_channel(mask, NULL, NULL);
+	if (!dma_chan) {
+		dev_err(tup->uport.dev,
+			"Dma channel is not available, will try later\n");
+		return -EPROBE_DEFER;
+	}
+
+	if (dma_to_memory) {
+		dma_buf = dma_alloc_coherent(tup->uport.dev,
+				TEGRA_UART_RX_DMA_BUFFER_SIZE,
+				 &dma_phys, GFP_KERNEL);
+		if (!dma_buf) {
+			dev_err(tup->uport.dev,
+				"Not able to allocate the dma buffer\n");
+			dma_release_channel(dma_chan);
+			return -ENOMEM;
+		}
+	} else {
+		dma_phys = dma_map_single(tup->uport.dev,
+			tup->uport.state->xmit.buf, UART_XMIT_SIZE,
+			DMA_TO_DEVICE);
+		dma_buf = tup->uport.state->xmit.buf;
+	}
+
+	dma_sconfig.slave_id = tup->dma_req_sel;
+	if (dma_to_memory) {
+		dma_sconfig.src_addr = tup->uport.mapbase;
+		dma_sconfig.src_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE;
+		dma_sconfig.src_maxburst = 4;
+	} else {
+		dma_sconfig.dst_addr = tup->uport.mapbase;
+		dma_sconfig.dst_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE;
+		dma_sconfig.dst_maxburst = 16;
+	}
+
+	ret = dmaengine_slave_config(dma_chan, &dma_sconfig);
+	if (ret < 0) {
+		dev_err(tup->uport.dev,
+			"Dma slave config failed, err = %d\n", ret);
+		goto scrub;
+	}
+
+	if (dma_to_memory) {
+		tup->rx_dma_chan = dma_chan;
+		tup->rx_dma_buf_virt = dma_buf;
+		tup->rx_dma_buf_phys = dma_phys;
+	} else {
+		tup->tx_dma_chan = dma_chan;
+		tup->tx_dma_buf_virt = dma_buf;
+		tup->tx_dma_buf_phys = dma_phys;
+	}
+	return 0;
+
+scrub:
+	dma_release_channel(dma_chan);
+	return ret;
+}
+
+static void tegra_uart_dma_channel_free(struct tegra_uart_port *tup,
+		bool dma_to_memory)
+{
+	struct dma_chan *dma_chan;
+
+	if (dma_to_memory) {
+		dma_free_coherent(tup->uport.dev, TEGRA_UART_RX_DMA_BUFFER_SIZE,
+				tup->rx_dma_buf_virt, tup->rx_dma_buf_phys);
+		dma_chan = tup->rx_dma_chan;
+		tup->rx_dma_chan = NULL;
+		tup->rx_dma_buf_phys = 0;
+		tup->rx_dma_buf_virt = NULL;
+	} else {
+		dma_unmap_single(tup->uport.dev, tup->tx_dma_buf_phys,
+			UART_XMIT_SIZE, DMA_TO_DEVICE);
+		dma_chan = tup->tx_dma_chan;
+		tup->tx_dma_chan = NULL;
+		tup->tx_dma_buf_phys = 0;
+		tup->tx_dma_buf_virt = NULL;
+	}
+	dma_release_channel(dma_chan);
+}
+
+static int tegra_uart_startup(struct uart_port *u)
+{
+	struct tegra_uart_port *tup = to_tegra_uport(u);
+	int ret;
+
+	ret = tegra_uart_dma_channel_allocate(tup, false);
+	if (ret < 0) {
+		dev_err(u->dev, "Tx Dma allocation failed, err = %d\n", ret);
+		return ret;
+	}
+
+	ret = tegra_uart_dma_channel_allocate(tup, true);
+	if (ret < 0) {
+		dev_err(u->dev, "Rx Dma allocation failed, err = %d\n", ret);
+		goto fail_rx_dma;
+	}
+
+	ret = tegra_uart_hw_init(tup);
+	if (ret < 0) {
+		dev_err(u->dev, "Uart HW init failed, err = %d\n", ret);
+		goto fail_hw_init;
+	}
+
+	ret = request_irq(u->irq, tegra_uart_isr, IRQF_DISABLED,
+				dev_name(u->dev), tup);
+	if (ret < 0) {
+		dev_err(u->dev, "Failed to register ISR for IRQ %d\n", u->irq);
+		goto fail_hw_init;
+	}
+	return 0;
+
+fail_hw_init:
+	tegra_uart_dma_channel_free(tup, true);
+fail_rx_dma:
+	tegra_uart_dma_channel_free(tup, false);
+	return ret;
+}
+
+static void tegra_uart_shutdown(struct uart_port *u)
+{
+	struct tegra_uart_port *tup = to_tegra_uport(u);
+
+	tegra_uart_hw_deinit(tup);
+
+	tup->rx_in_progress = 0;
+	tup->tx_in_progress = 0;
+
+	tegra_uart_dma_channel_free(tup, true);
+	tegra_uart_dma_channel_free(tup, false);
+	free_irq(u->irq, tup);
+}
+
+static void tegra_uart_enable_ms(struct uart_port *u)
+{
+	struct tegra_uart_port *tup = to_tegra_uport(u);
+
+	if (tup->enable_modem_interrupt) {
+		tup->ier_shadow |= UART_IER_MSI;
+		tegra_uart_write(tup, tup->ier_shadow, UART_IER);
+	}
+}
+
+static void tegra_uart_set_termios(struct uart_port *u,
+		struct ktermios *termios, struct ktermios *oldtermios)
+{
+	struct tegra_uart_port *tup = to_tegra_uport(u);
+	unsigned int baud;
+	unsigned long flags;
+	unsigned int lcr;
+	unsigned int c_cflag = termios->c_cflag;
+	int symb_bit = 1;
+
+	spin_lock_irqsave(&u->lock, flags);
+
+	/* Changing configuration, it is safe to stop any rx now */
+	if (tup->rts_active)
+		set_rts(tup, false);
+
+	/* Clear all interrupts as configuration is going to be change */
+	tegra_uart_write(tup, tup->ier_shadow | UART_IER_RDI, UART_IER);
+	tegra_uart_read(tup, UART_IER);
+	tegra_uart_write(tup, 0, UART_IER);
+	tegra_uart_read(tup, UART_IER);
+
+	/* Parity */
+	lcr = tup->lcr_shadow;
+	lcr &= ~UART_LCR_PARITY;
+	if ((c_cflag & PARENB) == PARENB) {
+		symb_bit++;
+		if ((c_cflag & CMSPAR) == CMSPAR) {
+			dev_err(tup->uport.dev, "Space parity not supported\n");
+			return;
+		} else if (c_cflag & PARODD) {
+			lcr |= UART_LCR_PARITY;
+			lcr &= ~UART_LCR_EPAR;
+			lcr &= ~UART_LCR_SPAR;
+		} else {
+			lcr |= UART_LCR_PARITY;
+			lcr |= UART_LCR_EPAR;
+			lcr &= ~UART_LCR_SPAR;
+		}
+	}
+
+	lcr &= ~UART_LCR_WLEN8;
+	switch (c_cflag & CSIZE) {
+	case CS5:
+		lcr |= UART_LCR_WLEN5;
+		symb_bit += 5;
+		break;
+	case CS6:
+		lcr |= UART_LCR_WLEN6;
+		symb_bit += 6;
+		break;
+	case CS7:
+		lcr |= UART_LCR_WLEN7;
+		symb_bit += 7;
+		break;
+	default:
+		lcr |= UART_LCR_WLEN8;
+		symb_bit += 8;
+		break;
+	}
+
+	/* Stop bits */
+	if (termios->c_cflag & CSTOPB) {
+		lcr |= UART_LCR_STOP;
+		symb_bit += 2;
+	} else {
+		lcr &= ~UART_LCR_STOP;
+		symb_bit++;
+	}
+
+	tegra_uart_write(tup, lcr, UART_LCR);
+	tup->lcr_shadow = lcr;
+	tup->symb_bit = symb_bit;
+
+	/* Baud rate. */
+	baud = uart_get_baud_rate(u, termios, oldtermios, 200, 4000000);
+	spin_unlock_irqrestore(&u->lock, flags);
+	tegra_set_baudrate(tup, baud);
+	spin_lock_irqsave(&u->lock, flags);
+
+	/* Flow control */
+	if (termios->c_cflag & CRTSCTS)	{
+		tup->mcr_shadow |= TEGRA_UART_MCR_CTS_EN;
+		tup->mcr_shadow &= ~TEGRA_UART_MCR_RTS_EN;
+		tegra_uart_write(tup, tup->mcr_shadow, UART_MCR);
+		/* if top layer has asked to set rts active then do so here */
+		if (tup->rts_active)
+			set_rts(tup, true);
+	} else {
+		tup->mcr_shadow &= ~TEGRA_UART_MCR_CTS_EN;
+		tup->mcr_shadow &= ~TEGRA_UART_MCR_RTS_EN;
+		tegra_uart_write(tup, tup->mcr_shadow, UART_MCR);
+	}
+
+	/* update the port timeout based on new settings */
+	uart_update_timeout(u, termios->c_cflag, baud);
+
+	/* Make sure all write has completed */
+	tegra_uart_read(tup, UART_IER);
+
+	/* Reenable interrupt */
+	tegra_uart_write(tup, tup->ier_shadow, UART_IER);
+	tegra_uart_read(tup, UART_IER);
+
+	spin_unlock_irqrestore(&u->lock, flags);
+	return;
+}
+
+/*
+ * Flush any TX data submitted for DMA and PIO. Called when the
+ * TX circular buffer is reset.
+ */
+static void tegra_uart_flush_buffer(struct uart_port *u)
+{
+	struct tegra_uart_port *tup = to_tegra_uport(u);
+
+	tup->tx_bytes = 0;
+	if (tup->tx_dma_chan)
+		dmaengine_terminate_all(tup->tx_dma_chan);
+	return;
+}
+
+static const char *tegra_uart_type(struct uart_port *u)
+{
+	return TEGRA_UART_TYPE;
+}
+
+static struct uart_ops tegra_uart_ops = {
+	.tx_empty	= tegra_uart_tx_empty,
+	.set_mctrl	= tegra_uart_set_mctrl,
+	.get_mctrl	= tegra_uart_get_mctrl,
+	.stop_tx	= tegra_uart_stop_tx,
+	.start_tx	= tegra_uart_start_tx,
+	.stop_rx	= tegra_uart_stop_rx,
+	.flush_buffer	= tegra_uart_flush_buffer,
+	.enable_ms	= tegra_uart_enable_ms,
+	.break_ctl	= tegra_uart_break_ctl,
+	.startup	= tegra_uart_startup,
+	.shutdown	= tegra_uart_shutdown,
+	.set_termios	= tegra_uart_set_termios,
+	.type		= tegra_uart_type,
+	.request_port	= tegra_uart_request_port,
+	.release_port	= tegra_uart_release_port,
+};
+
+static struct uart_driver tegra_uart_driver = {
+	.owner		= THIS_MODULE,
+	.driver_name	= "tegra_hsuart",
+	.dev_name	= "ttyHS",
+	.cons		= 0,
+	.nr		= TEGRA_UART_MAXIMUM,
+};
+
+static struct tegra_uart_platform_data *tegra_uart_parse_dt(
+		struct platform_device *pdev)
+{
+	struct tegra_uart_platform_data *pdata;
+	struct device_node *np = pdev->dev.of_node;
+	u32 of_dma[2];
+
+	pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL);
+	if (!pdata) {
+		dev_err(&pdev->dev, "Memory alloc for pdata failed\n");
+		return NULL;
+	}
+
+	if (of_property_read_u32_array(np, "nvidia,dma-request-selector",
+				of_dma, 2) >= 0) {
+		pdata->dma_req_sel = of_dma[1];
+	} else {
+		dev_err(&pdev->dev, "missing dma requestor in device tree\n");
+		return NULL;
+	}
+
+	if (!of_property_read_u32(np, "port-number", &of_dma[0])) {
+		pdata->port_number = of_dma[0];
+	} else {
+		dev_err(&pdev->dev, "missing port-number in device tree\n");
+		return NULL;
+	}
+
+	if (of_get_property(np, "nvidia,enable-modem-interrupt", NULL))
+		pdata->enable_modem_interrupt = 1;
+
+	return pdata;
+}
+
+static struct of_device_id tegra_uart_of_match[] __devinitconst = {
+	{
+		.compatible	= "nvidia,tegra30-hsuart",
+		.data		= &tegra30_uart_chip_data,
+	}, {
+		.compatible	= "nvidia,tegra20-hsuart",
+		.data		= &tegra20_uart_chip_data,
+	}, {
+	},
+};
+MODULE_DEVICE_TABLE(of, tegra_uart_of_match);
+
+static int __devinit tegra_uart_probe(struct platform_device *pdev)
+{
+	struct tegra_uart_port *tup;
+	struct uart_port *u;
+	struct tegra_uart_platform_data *pdata = pdev->dev.platform_data;
+	struct resource *resource;
+	int ret;
+	const struct tegra_uart_chip_data *cdata = &tegra30_uart_chip_data;
+	const struct of_device_id *match;
+
+	if (pdev->dev.of_node) {
+		match = of_match_device(of_match_ptr(tegra_uart_of_match),
+				&pdev->dev);
+		if (!match) {
+			dev_err(&pdev->dev, "Error: No device match found\n");
+			return -ENODEV;
+		}
+		cdata = match->data;
+	}
+
+	if (!pdata && pdev->dev.of_node)
+		pdata = tegra_uart_parse_dt(pdev);
+
+	if (!pdata) {
+		dev_err(&pdev->dev, "No platform data, exiting\n");
+		return -ENODEV;
+	}
+
+	tup = devm_kzalloc(&pdev->dev, sizeof(*tup), GFP_KERNEL);
+	if (!tup) {
+		dev_err(&pdev->dev, "Failed to allocate memory for tup\n");
+		return -ENOMEM;
+	}
+
+	u = &tup->uport;
+	u->dev = &pdev->dev;
+	u->line = pdata->port_number;
+	u->ops = &tegra_uart_ops;
+	u->type = PORT_TEGRA;
+	u->fifosize = 32;
+	tup->dma_req_sel = pdata->dma_req_sel;
+	tup->cdata = cdata;
+	tup->enable_modem_interrupt = pdata->enable_modem_interrupt;
+
+	platform_set_drvdata(pdev, tup);
+	resource = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!resource) {
+		dev_err(&pdev->dev, "No IO memory resource\n");
+		return -ENODEV;
+	}
+
+	u->mapbase = resource->start;
+	u->membase = devm_request_and_ioremap(&pdev->dev, resource);
+	if (!u->membase) {
+		dev_err(&pdev->dev, "memregion/iomap address req failed\n");
+		return -EADDRNOTAVAIL;
+	}
+
+	tup->uart_clk = devm_clk_get(&pdev->dev, "uart-clk");
+	if (IS_ERR(tup->uart_clk)) {
+		dev_err(&pdev->dev, "Couldn't get the clock\n");
+		return PTR_ERR(tup->uart_clk);
+	}
+
+	u->iotype = UPIO_MEM32;
+	u->irq = platform_get_irq(pdev, 0);
+	u->regshift = 2;
+	ret = uart_add_one_port(&tegra_uart_driver, u);
+	if (ret < 0) {
+		dev_err(&pdev->dev, "Failed to add uart port, err %d\n", ret);
+		return ret;
+	}
+	return ret;
+}
+
+static int __devexit tegra_uart_remove(struct platform_device *pdev)
+{
+	struct tegra_uart_port *tup = platform_get_drvdata(pdev);
+	struct uart_port *u = &tup->uport;
+
+	uart_remove_one_port(&tegra_uart_driver, u);
+	return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int tegra_uart_suspend(struct device *dev)
+{
+	struct tegra_uart_port *tup = dev_get_drvdata(dev);
+	struct uart_port *u = &tup->uport;
+
+	return uart_suspend_port(&tegra_uart_driver, u);
+}
+
+static int tegra_uart_resume(struct device *dev)
+{
+	struct tegra_uart_port *tup = dev_get_drvdata(dev);
+	struct uart_port *u = &tup->uport;
+
+	return uart_resume_port(&tegra_uart_driver, u);
+}
+#endif
+
+static const struct dev_pm_ops tegra_uart_pm_ops = {
+	SET_SYSTEM_SLEEP_PM_OPS(tegra_uart_suspend, tegra_uart_resume)
+};
+
+static struct platform_driver tegra_uart_platform_driver = {
+	.probe		= tegra_uart_probe,
+	.remove		= __devexit_p(tegra_uart_remove),
+	.driver		= {
+		.name	= "serial-tegra",
+		.of_match_table = of_match_ptr(tegra_uart_of_match),
+		.pm	= &tegra_uart_pm_ops,
+	},
+};
+
+static int __init tegra_uart_init(void)
+{
+	int ret;
+
+	ret = uart_register_driver(&tegra_uart_driver);
+	if (ret < 0) {
+		pr_err("Could not register %s driver\n",
+			tegra_uart_driver.driver_name);
+		return ret;
+	}
+
+	ret = platform_driver_register(&tegra_uart_platform_driver);
+	if (ret < 0) {
+		pr_err("Uart platfrom driver register failed, e = %d\n", ret);
+		uart_unregister_driver(&tegra_uart_driver);
+		return ret;
+	}
+	return 0;
+}
+
+static void __exit tegra_uart_exit(void)
+{
+	pr_info("Unloading tegra uart driver\n");
+	platform_driver_unregister(&tegra_uart_platform_driver);
+	uart_unregister_driver(&tegra_uart_driver);
+}
+
+module_init(tegra_uart_init);
+module_exit(tegra_uart_exit);
+
+MODULE_ALIAS("platform:serial-tegra");
+MODULE_DESCRIPTION("High speed UART driver for tegra chipset");
+MODULE_AUTHOR("Laxman Dewangan <ldewangan@nvidia.com>");
+MODULE_LICENSE("GPL v2");
diff --git a/include/linux/serial_tegra.h b/include/linux/serial_tegra.h
new file mode 100644
index 0000000..f9828b8
--- /dev/null
+++ b/include/linux/serial_tegra.h
@@ -0,0 +1,33 @@
+/*
+ * serial_tegra.h
+ *
+ * Interface for High-speed serial driver for NVIDIA Tegra SoCs.
+ *
+ * Copyright (c) 2012, NVIDIA CORPORATION.  All rights reserved.
+ *
+ * Author: Laxman Dewangan <ldewangan@nvidia.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _SERIAL_TEGRA_H_
+#define _SERIAL_TEGRA_H_
+
+struct tegra_uart_platform_data {
+	int dma_req_sel;
+	int port_number;
+	unsigned enable_modem_interrupt:1;
+};
+
+#endif /* _SERIAL_TEGRA_H_ */
+
-- 
1.7.1.1


^ permalink raw reply related

* Re: [PATCH] serial: tegra: add serial driver
From: Greg KH @ 2012-12-17 15:13 UTC (permalink / raw)
  To: Laxman Dewangan
  Cc: alan, jslaby, grant.likely, rob.herring, devicetree-discuss,
	linux-doc, linux-kernel, linux-serial, linux-tegra, swarren
In-Reply-To: <1355746249-15347-1-git-send-email-ldewangan@nvidia.com>

On Mon, Dec 17, 2012 at 05:40:49PM +0530, Laxman Dewangan wrote:
> Nvidia's Tegra has multiple uart controller which supports:
> - APB dma based controller fifo read/write.
> - End Of Data interrupt in incoming data to know whether end
>   of frame achieve or not.
> - Hw controlled RTS and CTS flow control to reduce SW overhead.
> 
> Add serial driver to use all above feature.
> 
> Signed-off-by: Laxman Dewangan <ldewangan@nvidia.com>
> ---
>  .../bindings/serial/nvidia,serial-tegra.txt        |   26 +
>  drivers/tty/serial/Kconfig                         |   14 +
>  drivers/tty/serial/Makefile                        |    1 +
>  drivers/tty/serial/serial_tegra.c                  | 1398 ++++++++++++++++++++
>  include/linux/serial_tegra.h                       |   33 +

This file should be in include/linux/platform_data/, right?

thanks,

greg k-h

^ permalink raw reply

* Re: [PATCH] serial: tegra: add serial driver
From: Rob Herring @ 2012-12-17 15:24 UTC (permalink / raw)
  To: Laxman Dewangan
  Cc: alan, gregkh, jslaby, grant.likely, devicetree-discuss, linux-doc,
	linux-kernel, linux-serial, linux-tegra, swarren
In-Reply-To: <1355746249-15347-1-git-send-email-ldewangan@nvidia.com>

On 12/17/2012 06:10 AM, Laxman Dewangan wrote:
> Nvidia's Tegra has multiple uart controller which supports:
> - APB dma based controller fifo read/write.
> - End Of Data interrupt in incoming data to know whether end
>   of frame achieve or not.
> - Hw controlled RTS and CTS flow control to reduce SW overhead.
> 
> Add serial driver to use all above feature.
> 
> Signed-off-by: Laxman Dewangan <ldewangan@nvidia.com>
> ---
>  .../bindings/serial/nvidia,serial-tegra.txt        |   26 +

Can we have a name without a comma. If serial-tegra is specific enough
for the kernel, then it is for the binding doc too.

>  drivers/tty/serial/Kconfig                         |   14 +
>  drivers/tty/serial/Makefile                        |    1 +
>  drivers/tty/serial/serial_tegra.c                  | 1398 ++++++++++++++++++++
>  include/linux/serial_tegra.h                       |   33 +
>  5 files changed, 1472 insertions(+), 0 deletions(-)
>  create mode 100644 Documentation/devicetree/bindings/serial/nvidia,serial-tegra.txt
>  create mode 100644 drivers/tty/serial/serial_tegra.c
>  create mode 100644 include/linux/serial_tegra.h
> 
> diff --git a/Documentation/devicetree/bindings/serial/nvidia,serial-tegra.txt b/Documentation/devicetree/bindings/serial/nvidia,serial-tegra.txt
> new file mode 100644
> index 0000000..fc5803b
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/serial/nvidia,serial-tegra.txt
> @@ -0,0 +1,26 @@
> +NVIDIA Tegra20/Tegra30 high speed (dma based) UART controller driver.
> +
> +Required properties:
> +- compatible : should be "nvidia,tegra20-hsuart", "nvidia,tegra30-hsuart".
> +- reg: Should contain UART controller registers location and length.
> +- interrupts: Should contain UART controller interrupts.
> +- nvidia,dma-request-selector : The Tegra DMA controller's phandle and
> +  request selector for this UART controller.

This should use the generic DMA binding although I'm not sure if that
made it into 3.8.

> +- port-number: Uart port number for /dev/ttyHSx where x is port number.

Use aliases like other platforms like i.MX have done.

> +Optional properties:
> +- nvidia,enable-modem-interrupt: Enable modem interrupts. Should be enable

s/enable/enabled/ on the last one.

> +		only if all 8 lines of uart controller is pinmuxed.

s/is/are/

> +
> +Example:
> +
> +serial@70006000 {
> +	compatible = "nvidia,tegra30-hsuart", "nvidia,tegra20-hsuart";
> +	reg = <0x70006000 0x40>;
> +	reg-shift = <2>;
> +	interrupts = <0 36 0x04>;
> +	port-number = <0>;
> +	nvidia,dma-request-selector = <&apbdma 8>;
> +	nvidia,enable-modem-interrupt;
> +	status = "disabled";
> +};
> diff --git a/drivers/tty/serial/Kconfig b/drivers/tty/serial/Kconfig
> index 59c23d0..57dbbc1 100644
> --- a/drivers/tty/serial/Kconfig
> +++ b/drivers/tty/serial/Kconfig
> @@ -269,6 +269,20 @@ config SERIAL_SIRFSOC_CONSOLE
>            your boot loader about how to pass options to the kernel at
>            boot time.)
>  
> +config SERIAL_SAMSUNG_UARTS_4
> +	bool

Huh?

> +
> +config SERIAL_TEGRA
> +	tristate "Nvidia Tegra20/30 SoC serial controller"
> +	depends on ARCH_TEGRA && TEGRA20_APB_DMA
> +	select SERIAL_CORE
> +	help
> +	  Support for the on-chip UARTs on the Nvidia Tegra seria SOCs
> +	  providing /dev/ttyHS0, 1, 2, 3 and 4 (note, some machines may not
> +	  provide all of these ports, depending on how the serial port
> +	  are enabled). This driver uses the APB dma to achieve higher baudrate
> +	  and better performance.
> +
>  config SERIAL_MAX3100
>  	tristate "MAX3100 support"
>  	depends on SPI
> diff --git a/drivers/tty/serial/Makefile b/drivers/tty/serial/Makefile
> index df1b998..73fb688 100644
> --- a/drivers/tty/serial/Makefile
> +++ b/drivers/tty/serial/Makefile
> @@ -80,6 +80,7 @@ obj-$(CONFIG_SERIAL_MXS_AUART) += mxs-auart.o
>  obj-$(CONFIG_SERIAL_LANTIQ)	+= lantiq.o
>  obj-$(CONFIG_SERIAL_XILINX_PS_UART) += xilinx_uartps.o
>  obj-$(CONFIG_SERIAL_SIRFSOC) += sirfsoc_uart.o
> +obj-$(CONFIG_SERIAL_TEGRA) += serial_tegra.o
>  obj-$(CONFIG_SERIAL_AR933X)   += ar933x_uart.o
>  obj-$(CONFIG_SERIAL_EFM32_UART) += efm32-uart.o
>  obj-$(CONFIG_SERIAL_ARC)	+= arc_uart.o
> diff --git a/drivers/tty/serial/serial_tegra.c b/drivers/tty/serial/serial_tegra.c
> new file mode 100644
> index 0000000..791cf35
> --- /dev/null
> +++ b/drivers/tty/serial/serial_tegra.c
> @@ -0,0 +1,1398 @@
> +/*
> + * serial_tegra.c
> + *
> + * High-speed serial driver for NVIDIA Tegra SoCs
> + *
> + * Copyright (c) 2012, NVIDIA CORPORATION.  All rights reserved.
> + *
> + * Author: Laxman Dewangan <ldewangan@nvidia.com>
> + *
> + * This program is free software; you can redistribute it and/or modify it
> + * under the terms and conditions of the GNU General Public License,
> + * version 2, as published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope it will be useful, but WITHOUT
> + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
> + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
> + * more details.
> + *
> + * You should have received a copy of the GNU General Public License
> + * along with this program.  If not, see <http://www.gnu.org/licenses/>.
> + */
> +
> +#include <linux/clk.h>
> +#include <linux/debugfs.h>
> +#include <linux/delay.h>
> +#include <linux/dmaengine.h>
> +#include <linux/dma-mapping.h>
> +#include <linux/dmapool.h>
> +#include <linux/io.h>
> +#include <linux/irq.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/of_device.h>
> +#include <linux/pagemap.h>
> +#include <linux/platform_device.h>
> +#include <linux/serial.h>
> +#include <linux/serial_8250.h>
> +#include <linux/serial_core.h>
> +#include <linux/serial_reg.h>
> +#include <linux/slab.h>
> +#include <linux/string.h>
> +#include <linux/serial_tegra.h>
> +#include <linux/termios.h>
> +#include <linux/tty_flip.h>
> +
> +#include <mach/clk.h>
> +
> +#define TEGRA_UART_TYPE				"TEGRA_UART"
> +#define TX_EMPTY_STATUS				(UART_LSR_TEMT | UART_LSR_THRE)
> +#define BYTES_TO_ALIGN(x)			((unsigned long)(x) & 0x3)
> +
> +#define TEGRA_UART_RX_DMA_BUFFER_SIZE		4096
> +#define TEGRA_UART_LSR_TXFIFO_FULL		0x100
> +#define TEGRA_UART_IER_EORD			0x20
> +#define TEGRA_UART_MCR_RTS_EN			0x40
> +#define TEGRA_UART_MCR_CTS_EN			0x20
> +#define TEGRA_UART_LSR_ANY			(UART_LSR_OE | UART_LSR_BI | \
> +						UART_LSR_PE | UART_LSR_FE)
> +#define TEGRA_UART_IRDA_CSR			0x08
> +#define TEGRA_UART_SIR_ENABLED			0x80
> +
> +#define TEGRA_UART_TX_PIO			1
> +#define TEGRA_UART_TX_DMA			2
> +#define TEGRA_UART_MIN_DMA			16
> +#define TEGRA_UART_FIFO_SIZE			32
> +
> +/*
> + * Tx fifo trigger level setting in tegra uart is in
> + * reverse way then conventional uart.
> + */
> +#define TEGRA_UART_TX_TRIG_16B			0x00
> +#define TEGRA_UART_TX_TRIG_8B			0x10
> +#define TEGRA_UART_TX_TRIG_4B			0x20
> +#define TEGRA_UART_TX_TRIG_1B			0x30
> +
> +#define TEGRA_UART_MAXIMUM			5
> +
> +/* Default UART setting when started: 115200 no parity, stop, 8 data bits */
> +#define TEGRA_UART_DEFAULT_BAUD			115200
> +#define TEGRA_UART_DEFAULT_LSR			UART_LCR_WLEN8
> +
> +/* Tx transfer mode */
> +#define TEGRA_TX_PIO				1
> +#define TEGRA_TX_DMA				2
> +
> +/**
> + * tegra_uart_chip_data: SOC specific data.
> + *
> + * @tx_fifo_full_status: Status flag available for checking tx fifo full.
> + * @allow_txfifo_reset_fifo_mode: allow_tx fifo reset with fifo mode or not.
> + *			Tegra30 does not allow this.
> + * @support_clk_src_div: Clock source support the clock divider.
> + */
> +struct tegra_uart_chip_data {
> +	bool	tx_fifo_full_status;
> +	bool	allow_txfifo_reset_fifo_mode;
> +	bool	support_clk_src_div;
> +};
> +
> +struct tegra_uart_chip_data tegra20_uart_chip_data = {
> +	.tx_fifo_full_status		= false,
> +	.allow_txfifo_reset_fifo_mode	= true,
> +	.support_clk_src_div		= false,
> +};
> +
> +struct tegra_uart_chip_data tegra30_uart_chip_data = {
> +	.tx_fifo_full_status		= true,
> +	.allow_txfifo_reset_fifo_mode	= false,
> +	.support_clk_src_div		= true,
> +};
> +
> +struct tegra_uart_port {
> +	struct uart_port			uport;
> +	const struct tegra_uart_chip_data	*cdata;
> +
> +	struct clk				*uart_clk;
> +	unsigned int				current_baud;
> +
> +	/* Register shadow */
> +	unsigned long				fcr_shadow;
> +	unsigned long				mcr_shadow;
> +	unsigned long				lcr_shadow;
> +	unsigned long				ier_shadow;
> +	bool					rts_active;
> +
> +	int					tx_in_progress;
> +	unsigned int				tx_bytes;
> +
> +	int					enable_modem_interrupt;
> +
> +	bool					rx_timeout;
> +	int					rx_in_progress;
> +	int					symb_bit;
> +	int					dma_req_sel;
> +
> +	struct dma_chan				*rx_dma_chan;
> +	struct dma_chan				*tx_dma_chan;
> +	dma_addr_t				rx_dma_buf_phys;
> +	dma_addr_t				tx_dma_buf_phys;
> +	unsigned char				*rx_dma_buf_virt;
> +	unsigned char				*tx_dma_buf_virt;
> +	struct dma_async_tx_descriptor		*tx_dma_desc;
> +	struct dma_async_tx_descriptor		*rx_dma_desc;
> +	dma_cookie_t				tx_cookie;
> +	dma_cookie_t				rx_cookie;
> +	int					tx_bytes_requested;
> +	int					rx_bytes_requested;
> +};
> +
> +static void tegra_uart_start_next_tx(struct tegra_uart_port *tup);
> +static int tegra_uart_start_rx_dma(struct tegra_uart_port *tup);
> +
> +static inline unsigned long tegra_uart_read(struct tegra_uart_port *tup,
> +		unsigned long reg)
> +{
> +	return readl(tup->uport.membase + (reg << tup->uport.regshift));
> +}
> +
> +static inline void tegra_uart_write(struct tegra_uart_port *tup, unsigned val,
> +	unsigned long reg)
> +{
> +	writel(val, tup->uport.membase + (reg << tup->uport.regshift));
> +}
> +
> +static inline struct tegra_uart_port *to_tegra_uport(struct uart_port *u)
> +{
> +	return container_of(u, struct tegra_uart_port, uport);
> +}
> +
> +static unsigned int tegra_uart_get_mctrl(struct uart_port *u)
> +{
> +	struct tegra_uart_port *tup = to_tegra_uport(u);
> +
> +	/*
> +	 * RI - Ring detector is active
> +	 * CD/DCD/CAR - Carrier detect is always active. For some reason
> +	 *	linux has different names for carrier detect.
> +	 * DSR - Data Set ready is active as the hardware doesn't support it.
> +	 *	Don't know if the linux support this yet?
> +	 * CTS - Clear to send. Always set to active, as the hardware handles
> +	 *	CTS automatically.
> +	 */
> +	if (tup->enable_modem_interrupt)
> +		return TIOCM_RI | TIOCM_CD | TIOCM_DSR | TIOCM_CTS;
> +	return TIOCM_CTS;
> +}
> +
> +static void set_rts(struct tegra_uart_port *tup, bool active)
> +{
> +	unsigned long mcr;
> +
> +	mcr = tup->mcr_shadow;
> +	if (active)
> +		mcr |= TEGRA_UART_MCR_RTS_EN;
> +	else
> +		mcr &= ~TEGRA_UART_MCR_RTS_EN;
> +	if (mcr != tup->mcr_shadow) {
> +		tegra_uart_write(tup, mcr, UART_MCR);
> +		tup->mcr_shadow = mcr;
> +	}
> +	return;
> +}
> +
> +static void set_dtr(struct tegra_uart_port *tup, bool active)
> +{
> +	unsigned long mcr;
> +
> +	mcr = tup->mcr_shadow;
> +	if (active)
> +		mcr |= UART_MCR_DTR;
> +	else
> +		mcr &= ~UART_MCR_DTR;
> +	if (mcr != tup->mcr_shadow) {
> +		tegra_uart_write(tup, mcr, UART_MCR);
> +		tup->mcr_shadow = mcr;
> +	}
> +	return;
> +}
> +
> +static void tegra_uart_set_mctrl(struct uart_port *u, unsigned int mctrl)
> +{
> +	struct tegra_uart_port *tup = to_tegra_uport(u);
> +	unsigned long mcr;
> +
> +	mcr = tup->mcr_shadow;
> +	if (mctrl & TIOCM_RTS) {
> +		tup->rts_active = true;
> +		set_rts(tup, true);
> +	} else {
> +		tup->rts_active = false;
> +		set_rts(tup, false);
> +	}
> +
> +	if (mctrl & TIOCM_DTR)
> +		set_dtr(tup, true);
> +	else
> +		set_dtr(tup, false);
> +	return;
> +}
> +
> +static void tegra_uart_break_ctl(struct uart_port *u, int break_ctl)
> +{
> +	struct tegra_uart_port *tup = to_tegra_uport(u);
> +	unsigned long lcr;
> +
> +	lcr = tup->lcr_shadow;
> +	if (break_ctl)
> +		lcr |= UART_LCR_SBC;
> +	else
> +		lcr &= ~UART_LCR_SBC;
> +	tegra_uart_write(tup, lcr, UART_LCR);
> +	tup->lcr_shadow = lcr;
> +}
> +
> +/* Wait for a symbol-time. */
> +static void tegra_uart_wait_sym_time(struct tegra_uart_port *tup,
> +		unsigned int syms)
> +{
> +	if (tup->current_baud)
> +		udelay(DIV_ROUND_UP(syms * tup->symb_bit * 1000000,
> +			tup->current_baud));
> +}
> +
> +static void tegra_uart_fifo_reset(struct tegra_uart_port *tup, u8 fcr_bits)
> +{
> +	unsigned long fcr = tup->fcr_shadow;
> +
> +	if (tup->cdata->allow_txfifo_reset_fifo_mode) {
> +		fcr |= fcr_bits & (UART_FCR_CLEAR_RCVR | UART_FCR_CLEAR_XMIT);
> +		tegra_uart_write(tup, fcr, UART_FCR);
> +	} else {
> +		fcr &= ~UART_FCR_ENABLE_FIFO;
> +		tegra_uart_write(tup, fcr, UART_FCR);
> +		udelay(60);
> +		fcr |= fcr_bits & (UART_FCR_CLEAR_RCVR | UART_FCR_CLEAR_XMIT);
> +		tegra_uart_write(tup, fcr, UART_FCR);
> +		fcr |= UART_FCR_ENABLE_FIFO;
> +		tegra_uart_write(tup, fcr, UART_FCR);
> +	}
> +
> +	/* Dummy read to ensure the write is posted */
> +	tegra_uart_read(tup, UART_SCR);
> +
> +	/* Wait for the flush to propagate. */
> +	tegra_uart_wait_sym_time(tup, 1);
> +}
> +
> +static int tegra_set_baudrate(struct tegra_uart_port *tup, unsigned int baud)
> +{
> +	unsigned long rate;
> +	unsigned int divisor;
> +	unsigned long lcr;
> +	int ret;
> +
> +	if (tup->current_baud == baud)
> +		return 0;
> +
> +	if (tup->cdata->support_clk_src_div) {
> +		rate = baud * 16;
> +		ret = clk_set_rate(tup->uart_clk, rate);
> +		if (ret < 0) {
> +			dev_err(tup->uport.dev,
> +				"clk_set_rate() failed for rate %lu\n", rate);
> +			return ret;
> +		}
> +		divisor = 1;
> +	} else {
> +		rate = clk_get_rate(tup->uart_clk);
> +		divisor = DIV_ROUND_CLOSEST(rate, baud * 16);
> +	}
> +
> +	lcr = tup->lcr_shadow;
> +	lcr |= UART_LCR_DLAB;
> +	tegra_uart_write(tup, lcr, UART_LCR);
> +
> +	tegra_uart_write(tup, divisor & 0xFF, UART_TX);
> +	tegra_uart_write(tup, ((divisor >> 8) & 0xFF), UART_IER);
> +
> +	lcr &= ~UART_LCR_DLAB;
> +	tegra_uart_write(tup, lcr, UART_LCR);
> +
> +	/* Dummy read to ensure the write is posted */
> +	tegra_uart_read(tup, UART_SCR);
> +
> +	tup->current_baud = baud;
> +
> +	/* wait two character intervals at new rate */
> +	tegra_uart_wait_sym_time(tup, 2);
> +	return 0;
> +}
> +
> +static char tegra_uart_decode_rx_error(struct tegra_uart_port *tup,
> +			unsigned long lsr)
> +{
> +	char flag = TTY_NORMAL;
> +
> +	if (unlikely(lsr & TEGRA_UART_LSR_ANY)) {
> +		if (lsr & UART_LSR_OE) {
> +			/* Overrrun error */
> +			flag |= TTY_OVERRUN;
> +			tup->uport.icount.overrun++;
> +			dev_err(tup->uport.dev, "Got overrun errors\n");
> +		} else if (lsr & UART_LSR_PE) {
> +			/* Parity error */
> +			flag |= TTY_PARITY;
> +			tup->uport.icount.parity++;
> +			dev_err(tup->uport.dev, "Got Parity errors\n");
> +		} else if (lsr & UART_LSR_FE) {
> +			flag |= TTY_FRAME;
> +			tup->uport.icount.frame++;
> +			dev_err(tup->uport.dev, "Got frame errors\n");
> +		} else if (lsr & UART_LSR_BI) {
> +			dev_err(tup->uport.dev, "Got Break\n");
> +			tup->uport.icount.brk++;
> +			/* If FIFO read error without any data, reset Rx FIFO */
> +			if (!(lsr & UART_LSR_DR) && (lsr & UART_LSR_FIFOE))
> +				tegra_uart_fifo_reset(tup, UART_FCR_CLEAR_RCVR);
> +		}
> +	}
> +	return flag;
> +}
> +
> +static int tegra_uart_request_port(struct uart_port *u)
> +{
> +	return 0;
> +}
> +
> +static void tegra_uart_release_port(struct uart_port *u)
> +{
> +	/* Nothing to do here */
> +}
> +
> +static void tegra_uart_fill_tx_fifo(struct tegra_uart_port *tup, int max_bytes)
> +{
> +	struct circ_buf *xmit = &tup->uport.state->xmit;
> +	int i;
> +
> +	for (i = 0; i < max_bytes; i++) {
> +		BUG_ON(uart_circ_empty(xmit));
> +		if (tup->cdata->tx_fifo_full_status) {
> +			unsigned long lsr = tegra_uart_read(tup, UART_LSR);
> +			if ((lsr & TEGRA_UART_LSR_TXFIFO_FULL))
> +				break;
> +		}
> +		tegra_uart_write(tup, xmit->buf[xmit->tail], UART_TX);
> +		xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1);
> +		tup->uport.icount.tx++;
> +	}
> +}
> +
> +static void tegra_uart_start_pio_tx(struct tegra_uart_port *tup,
> +		unsigned int bytes)
> +{
> +	if (bytes > TEGRA_UART_MIN_DMA)
> +		bytes = TEGRA_UART_MIN_DMA;
> +
> +	tup->tx_in_progress = TEGRA_UART_TX_PIO;
> +	tup->tx_bytes = bytes;
> +	tup->ier_shadow |= UART_IER_THRI;
> +	tegra_uart_write(tup, tup->ier_shadow, UART_IER);
> +}
> +
> +static void tegra_uart_tx_dma_complete(void *args)
> +{
> +	struct tegra_uart_port *tup = args;
> +	struct circ_buf *xmit = &tup->uport.state->xmit;
> +	struct dma_tx_state state;
> +	unsigned long flags;
> +	int count;
> +
> +	dmaengine_tx_status(tup->tx_dma_chan, tup->rx_cookie, &state);
> +	count = tup->tx_bytes_requested - state.residue;
> +	async_tx_ack(tup->tx_dma_desc);
> +	spin_lock_irqsave(&tup->uport.lock, flags);
> +	xmit->tail = (xmit->tail + count) & (UART_XMIT_SIZE - 1);
> +	tup->tx_in_progress = 0;
> +	if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)
> +		uart_write_wakeup(&tup->uport);
> +	tegra_uart_start_next_tx(tup);
> +	spin_unlock_irqrestore(&tup->uport.lock, flags);
> +}
> +
> +static int tegra_uart_start_tx_dma(struct tegra_uart_port *tup,
> +		unsigned long count)
> +{
> +	struct circ_buf *xmit = &tup->uport.state->xmit;
> +	dma_addr_t tx_phys_addr;
> +
> +	dma_sync_single_for_device(tup->uport.dev, tup->tx_dma_buf_phys,
> +				UART_XMIT_SIZE, DMA_TO_DEVICE);
> +
> +	tup->tx_bytes = count & ~(0xF);
> +	tx_phys_addr = tup->tx_dma_buf_phys + xmit->tail;
> +	tup->tx_dma_desc = dmaengine_prep_slave_single(tup->tx_dma_chan,
> +				tx_phys_addr, tup->tx_bytes, DMA_MEM_TO_DEV,
> +				DMA_PREP_INTERRUPT);
> +	if (!tup->tx_dma_desc) {
> +		dev_err(tup->uport.dev, "Not able to get desc for Tx\n");
> +		return -EIO;
> +	}
> +
> +	tup->tx_dma_desc->callback = tegra_uart_tx_dma_complete;
> +	tup->tx_dma_desc->callback_param = tup;
> +	tup->tx_in_progress = TEGRA_UART_TX_DMA;
> +	tup->tx_bytes_requested = tup->tx_bytes;
> +	tup->tx_cookie = dmaengine_submit(tup->tx_dma_desc);
> +	dma_async_issue_pending(tup->tx_dma_chan);
> +	return 0;
> +}
> +
> +static void tegra_uart_start_next_tx(struct tegra_uart_port *tup)
> +{
> +	unsigned long tail;
> +	unsigned long count;
> +	struct circ_buf *xmit = &tup->uport.state->xmit;
> +
> +	tail = (unsigned long)&xmit->buf[xmit->tail];
> +	count = CIRC_CNT_TO_END(xmit->head, xmit->tail, UART_XMIT_SIZE);
> +	if (!count)
> +		return;
> +
> +	if (count < TEGRA_UART_MIN_DMA)
> +		tegra_uart_start_pio_tx(tup, count);
> +	else if (BYTES_TO_ALIGN(tail) > 0)
> +		tegra_uart_start_pio_tx(tup, BYTES_TO_ALIGN(tail));
> +	else
> +		tegra_uart_start_tx_dma(tup, count);
> +}
> +
> +/* Called by serial core driver with u->lock taken. */
> +static void tegra_uart_start_tx(struct uart_port *u)
> +{
> +	struct tegra_uart_port *tup = to_tegra_uport(u);
> +	struct circ_buf *xmit = &u->state->xmit;
> +
> +	if (!uart_circ_empty(xmit) && !tup->tx_in_progress)
> +		tegra_uart_start_next_tx(tup);
> +}
> +
> +static unsigned int tegra_uart_tx_empty(struct uart_port *u)
> +{
> +	struct tegra_uart_port *tup = to_tegra_uport(u);
> +	unsigned int ret = 0;
> +	unsigned long flags;
> +
> +	spin_lock_irqsave(&u->lock, flags);
> +	if (!tup->tx_in_progress) {
> +		unsigned long lsr = tegra_uart_read(tup, UART_LSR);
> +		if ((lsr & TX_EMPTY_STATUS) == TX_EMPTY_STATUS)
> +			ret = TIOCSER_TEMT;
> +	}
> +	spin_unlock_irqrestore(&u->lock, flags);
> +	return ret;
> +}
> +
> +static void tegra_uart_stop_tx(struct uart_port *u)
> +{
> +	struct tegra_uart_port *tup = to_tegra_uport(u);
> +	struct circ_buf *xmit = &tup->uport.state->xmit;
> +	struct dma_tx_state state;
> +	int count;
> +
> +	dmaengine_terminate_all(tup->tx_dma_chan);
> +	dmaengine_tx_status(tup->tx_dma_chan, tup->tx_cookie, &state);
> +	count = tup->tx_bytes_requested - state.residue;
> +	async_tx_ack(tup->tx_dma_desc);
> +	xmit->tail = (xmit->tail + count) & (UART_XMIT_SIZE - 1);
> +	tup->tx_in_progress = 0;
> +	return;
> +}
> +
> +static void tegra_uart_handle_tx_pio(struct tegra_uart_port *tup)
> +{
> +	struct circ_buf *xmit = &tup->uport.state->xmit;
> +
> +	tegra_uart_fill_tx_fifo(tup, tup->tx_bytes);
> +	tup->tx_in_progress = 0;
> +	if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)
> +		uart_write_wakeup(&tup->uport);
> +	tegra_uart_start_next_tx(tup);
> +	return;
> +}
> +
> +static void tegra_uart_handle_rx_pio(struct tegra_uart_port *tup)
> +{
> +	do {
> +		char flag = TTY_NORMAL;
> +		unsigned long lsr = 0;
> +		unsigned char ch;
> +
> +		lsr = tegra_uart_read(tup, UART_LSR);
> +		if (!(lsr & UART_LSR_DR))
> +			break;
> +
> +		flag = tegra_uart_decode_rx_error(tup, lsr);
> +		ch = (unsigned char) tegra_uart_read(tup, UART_RX);
> +		tup->uport.icount.rx++;
> +
> +		if (!uart_handle_sysrq_char(&tup->uport, ch))
> +			uart_insert_char(&tup->uport, lsr,
> +					UART_LSR_OE, ch, flag);
> +	} while (1);
> +
> +	return;
> +}
> +
> +static void tegra_uart_copy_rx_to_tty(struct tegra_uart_port *tup, int count)
> +{
> +	int copied;
> +
> +	tup->uport.icount.rx += count;
> +	dma_sync_single_for_cpu(tup->uport.dev, tup->rx_dma_buf_phys,
> +				TEGRA_UART_RX_DMA_BUFFER_SIZE, DMA_FROM_DEVICE);
> +	copied = tty_insert_flip_string(tup->uport.state->port.tty,
> +			((unsigned char *)(tup->rx_dma_buf_virt)), count);
> +	if (copied != count) {
> +		WARN_ON(1);
> +		dev_err(tup->uport.dev, "RxData copy to tty layer failed\n");
> +	}
> +	dma_sync_single_for_device(tup->uport.dev, tup->rx_dma_buf_phys,
> +				TEGRA_UART_RX_DMA_BUFFER_SIZE, DMA_TO_DEVICE);
> +}
> +
> +static void tegra_uart_rx_dma_complete(void *args)
> +{
> +	struct tegra_uart_port *tup = args;
> +	struct uart_port *u = &tup->uport;
> +	int count = tup->rx_bytes_requested;
> +	unsigned long flags;
> +
> +	async_tx_ack(tup->rx_dma_desc);
> +	spin_lock_irqsave(&u->lock, flags);
> +
> +	/* Deactivate flow control to stop sender */
> +	if (tup->rts_active)
> +		set_rts(tup, false);
> +
> +	/* If we are here, DMA is stopped */
> +	if (count)
> +		tegra_uart_copy_rx_to_tty(tup, count);
> +
> +	tegra_uart_handle_rx_pio(tup);
> +	tty_flip_buffer_push(u->state->port.tty);
> +	tegra_uart_start_rx_dma(tup);
> +
> +	/* Activate flow control to start transfer */
> +	if (tup->rts_active)
> +		set_rts(tup, true);
> +
> +	spin_unlock_irqrestore(&u->lock, flags);
> +}
> +
> +static void tegra_uart_handle_rx_dma(struct tegra_uart_port *tup)
> +{
> +	struct uart_port *u = &tup->uport;
> +	struct dma_tx_state state;
> +	int count;
> +
> +	/* Deactivate flow control to stop sender */
> +	if (tup->rts_active)
> +		set_rts(tup, false);
> +
> +	dmaengine_terminate_all(tup->rx_dma_chan);
> +	dmaengine_tx_status(tup->rx_dma_chan,  tup->rx_cookie, &state);
> +	count = tup->rx_bytes_requested - state.residue;
> +
> +	/* If we are here, DMA is stopped */
> +	if (count)
> +		tegra_uart_copy_rx_to_tty(tup, count);
> +
> +	tegra_uart_handle_rx_pio(tup);
> +	tty_flip_buffer_push(u->state->port.tty);
> +	tegra_uart_start_rx_dma(tup);
> +
> +	if (tup->rts_active)
> +		set_rts(tup, true);
> +}
> +
> +static int tegra_uart_start_rx_dma(struct tegra_uart_port *tup)
> +{
> +	unsigned int count = TEGRA_UART_RX_DMA_BUFFER_SIZE;
> +
> +	tup->rx_dma_desc = dmaengine_prep_slave_single(tup->rx_dma_chan,
> +				tup->rx_dma_buf_phys, count, DMA_DEV_TO_MEM,
> +				DMA_PREP_INTERRUPT);
> +	if (!tup->rx_dma_desc) {
> +		dev_err(tup->uport.dev, "Not able to get desc for Rx\n");
> +		return -EIO;
> +	}
> +
> +	tup->rx_dma_desc->callback = tegra_uart_rx_dma_complete;
> +	tup->rx_dma_desc->callback_param = tup;
> +	dma_sync_single_for_device(tup->uport.dev, tup->rx_dma_buf_phys,
> +				count, DMA_TO_DEVICE);
> +	tup->rx_bytes_requested = count;
> +	tup->rx_cookie = dmaengine_submit(tup->rx_dma_desc);
> +	dma_async_issue_pending(tup->rx_dma_chan);
> +	return 0;
> +}
> +
> +static void tegra_uart_handle_modem_signal_change(struct uart_port *u)
> +{
> +	struct tegra_uart_port *tup = to_tegra_uport(u);
> +	unsigned long msr;
> +
> +	msr = tegra_uart_read(tup, UART_MSR);
> +	if (!(msr & UART_MSR_ANY_DELTA))
> +		return;
> +
> +	if (msr & UART_MSR_TERI)
> +		tup->uport.icount.rng++;
> +	if (msr & UART_MSR_DDSR)
> +		tup->uport.icount.dsr++;
> +	/* We may only get DDCD when HW init and reset */
> +	if (msr & UART_MSR_DDCD)
> +		uart_handle_dcd_change(&tup->uport, msr & UART_MSR_DCD);
> +	/* Will start/stop_tx accordingly */
> +	if (msr & UART_MSR_DCTS)
> +		uart_handle_cts_change(&tup->uport, msr & UART_MSR_CTS);
> +	return;
> +}
> +
> +static irqreturn_t tegra_uart_isr(int irq, void *data)
> +{
> +	struct tegra_uart_port *tup = data;
> +	struct uart_port *u = &tup->uport;
> +	unsigned long iir;
> +	unsigned long ier;
> +	bool is_rx_int = false;
> +	unsigned long flags;
> +
> +	spin_lock_irqsave(&u->lock, flags);
> +	while (1) {
> +		iir = tegra_uart_read(tup, UART_IIR);
> +		if (iir & UART_IIR_NO_INT) {
> +			if (is_rx_int) {
> +				tegra_uart_handle_rx_dma(tup);
> +				if (tup->rx_in_progress) {
> +					ier = tup->ier_shadow;
> +					ier |= (UART_IER_RLSI | UART_IER_RTOIE |
> +						TEGRA_UART_IER_EORD);
> +					tup->ier_shadow = ier;
> +					tegra_uart_write(tup, ier, UART_IER);
> +				}
> +			}
> +			spin_unlock_irqrestore(&u->lock, flags);
> +			return IRQ_HANDLED;
> +		}
> +
> +		switch ((iir >> 1) & 0x7) {
> +		case 0: /* Modem signal change interrupt */
> +			tegra_uart_handle_modem_signal_change(u);
> +			break;
> +
> +		case 1: /* Transmit interrupt only triggered when using PIO */
> +			tup->ier_shadow &= ~UART_IER_THRI;
> +			tegra_uart_write(tup, tup->ier_shadow, UART_IER);
> +			tegra_uart_handle_tx_pio(tup);
> +			break;
> +
> +		case 4: /* End of data */
> +		case 6: /* Rx timeout */
> +		case 2: /* Receive */
> +			if (!is_rx_int) {
> +				is_rx_int = true;
> +				/* Disable Rx interrupts */
> +				ier = tup->ier_shadow;
> +				ier |= UART_IER_RDI;
> +				tegra_uart_write(tup, ier, UART_IER);
> +				ier &= ~(UART_IER_RDI | UART_IER_RLSI |
> +					UART_IER_RTOIE | TEGRA_UART_IER_EORD);
> +				tup->ier_shadow = ier;
> +				tegra_uart_write(tup, ier, UART_IER);
> +			}
> +			break;
> +
> +		case 3: /* Receive error */
> +			tegra_uart_decode_rx_error(tup,
> +					tegra_uart_read(tup, UART_LSR));
> +			break;
> +
> +		case 5: /* break nothing to handle */
> +		case 7: /* break nothing to handle */
> +			break;
> +		}
> +	}
> +}
> +
> +static void tegra_uart_stop_rx(struct uart_port *u)
> +{
> +	struct tegra_uart_port *tup = to_tegra_uport(u);
> +	struct dma_tx_state state;
> +	unsigned long ier;
> +	int count;
> +
> +	if (tup->rts_active)
> +		set_rts(tup, false);
> +
> +	if (!tup->rx_in_progress)
> +		return;
> +
> +	tegra_uart_wait_sym_time(tup, 1); /* wait a character interval */
> +
> +	ier = tup->ier_shadow;
> +	ier &= ~(UART_IER_RDI | UART_IER_RLSI | UART_IER_RTOIE |
> +					TEGRA_UART_IER_EORD);
> +	tup->ier_shadow = ier;
> +	tegra_uart_write(tup, ier, UART_IER);
> +	tup->rx_in_progress = 0;
> +	if (tup->rx_dma_chan) {
> +		dmaengine_terminate_all(tup->rx_dma_chan);
> +		dmaengine_tx_status(tup->rx_dma_chan, tup->rx_cookie, &state);
> +		async_tx_ack(tup->rx_dma_desc);
> +		count = tup->rx_bytes_requested - state.residue;
> +		tegra_uart_copy_rx_to_tty(tup, count);
> +		tegra_uart_handle_rx_pio(tup);
> +	} else {
> +		tegra_uart_handle_rx_pio(tup);
> +	}
> +	tty_flip_buffer_push(u->state->port.tty);
> +	return;
> +}
> +
> +static void tegra_uart_hw_deinit(struct tegra_uart_port *tup)
> +{
> +	unsigned long flags;
> +	unsigned long char_time = DIV_ROUND_UP(10000000, tup->current_baud);
> +	unsigned long fifo_empty_time = tup->uport.fifosize * char_time;
> +	unsigned long wait_time;
> +	unsigned long lsr;
> +	unsigned long msr;
> +	unsigned long mcr;
> +
> +	/* Disable interrupts */
> +	tegra_uart_write(tup, 0, UART_IER);
> +
> +	lsr = tegra_uart_read(tup, UART_LSR);
> +	if ((lsr & UART_LSR_TEMT) != UART_LSR_TEMT) {
> +		msr = tegra_uart_read(tup, UART_MSR);
> +		mcr = tegra_uart_read(tup, UART_MCR);
> +		if ((mcr & TEGRA_UART_MCR_CTS_EN) && (msr & UART_MSR_CTS))
> +			dev_err(tup->uport.dev,
> +				"Tx Fifo not empty, CTS disabled, waiting\n");
> +
> +		/* Wait for Tx fifo to be empty */
> +		while ((lsr & UART_LSR_TEMT) != UART_LSR_TEMT) {
> +			wait_time = min(fifo_empty_time, 100lu);
> +			udelay(wait_time);
> +			fifo_empty_time -= wait_time;
> +			if (!fifo_empty_time) {
> +				msr = tegra_uart_read(tup, UART_MSR);
> +				mcr = tegra_uart_read(tup, UART_MCR);
> +				if ((mcr & TEGRA_UART_MCR_CTS_EN) &&
> +					(msr & UART_MSR_CTS))
> +					dev_err(tup->uport.dev,
> +						"Slave not ready\n");
> +				break;
> +			}
> +			lsr = tegra_uart_read(tup, UART_LSR);
> +		}
> +	}
> +
> +	spin_lock_irqsave(&tup->uport.lock, flags);
> +	/* Reset the Rx and Tx FIFOs */
> +	tegra_uart_fifo_reset(tup, UART_FCR_CLEAR_XMIT | UART_FCR_CLEAR_RCVR);
> +	tup->current_baud = 0;
> +	spin_unlock_irqrestore(&tup->uport.lock, flags);
> +
> +	clk_disable_unprepare(tup->uart_clk);
> +}
> +
> +static int tegra_uart_hw_init(struct tegra_uart_port *tup)
> +{
> +	int ret;
> +
> +	tup->fcr_shadow = 0;
> +	tup->mcr_shadow = 0;
> +	tup->lcr_shadow = 0;
> +	tup->ier_shadow = 0;
> +	tup->current_baud = 0;
> +
> +	clk_prepare_enable(tup->uart_clk);
> +
> +	/* Reset the UART controller to clear all previous status.*/
> +	tegra_periph_reset_assert(tup->uart_clk);
> +	udelay(10);
> +	tegra_periph_reset_deassert(tup->uart_clk);
> +
> +	tup->rx_in_progress = 0;
> +	tup->tx_in_progress = 0;
> +
> +	/*
> +	 * Set the trigger level
> +	 *
> +	 * For PIO mode:
> +	 *
> +	 * For receive, this will interrupt the CPU after that many number of
> +	 * bytes are received, for the remaining bytes the receive timeout
> +	 * interrupt is received. Rx high watermark is set to 4.
> +	 *
> +	 * For transmit, if the trasnmit interrupt is enabled, this will
> +	 * interrupt the CPU when the number of entries in the FIFO reaches the
> +	 * low watermark. Tx low watermark is set to 16 bytes.
> +	 *
> +	 * For DMA mode:
> +	 *
> +	 * Set the Tx trigger to 16. This should match the DMA burst size that
> +	 * programmed in the DMA registers.
> +	 */
> +	tup->fcr_shadow = UART_FCR_ENABLE_FIFO;
> +	tup->fcr_shadow |= UART_FCR_R_TRIG_01;
> +	tup->fcr_shadow |= TEGRA_UART_TX_TRIG_16B;
> +	tegra_uart_write(tup, tup->fcr_shadow, UART_FCR);
> +
> +	/*
> +	 * Initialize the UART with default configuration
> +	 * (115200, N, 8, 1) so that the receive DMA buffer may be
> +	 * enqueued
> +	 */
> +	tup->lcr_shadow = TEGRA_UART_DEFAULT_LSR;
> +	tegra_set_baudrate(tup, TEGRA_UART_DEFAULT_BAUD);
> +	tup->fcr_shadow |= UART_FCR_DMA_SELECT;
> +	tegra_uart_write(tup, tup->fcr_shadow, UART_FCR);
> +
> +	ret = tegra_uart_start_rx_dma(tup);
> +	if (ret < 0) {
> +		dev_err(tup->uport.dev, "Not able to start Rx DMA\n");
> +		return ret;
> +	}
> +	tup->rx_in_progress = 1;
> +
> +	/*
> +	 * Enable IE_RXS for the receive status interrupts like line errros.
> +	 * Enable IE_RX_TIMEOUT to get the bytes which cannot be DMA'd.
> +	 *
> +	 * If using DMA mode, enable EORD instead of receive interrupt which
> +	 * will interrupt after the UART is done with the receive instead of
> +	 * the interrupt when the FIFO "threshold" is reached.
> +	 *
> +	 * EORD is different interrupt than RX_TIMEOUT - RX_TIMEOUT occurs when
> +	 * the DATA is sitting in the FIFO and couldn't be transferred to the
> +	 * DMA as the DMA size alignment(4 bytes) is not met. EORD will be
> +	 * triggered when there is a pause of the incomming data stream for 4
> +	 * characters long.
> +	 *
> +	 * For pauses in the data which is not aligned to 4 bytes, we get
> +	 * both the EORD as well as RX_TIMEOUT - SW sees RX_TIMEOUT first
> +	 * then the EORD.
> +	 */
> +	tup->ier_shadow = UART_IER_RLSI | UART_IER_RTOIE | TEGRA_UART_IER_EORD;
> +	tegra_uart_write(tup, tup->ier_shadow, UART_IER);
> +	return 0;
> +}
> +
> +static int tegra_uart_dma_channel_allocate(struct tegra_uart_port *tup,
> +			bool dma_to_memory)
> +{
> +	struct dma_chan *dma_chan;
> +	unsigned char *dma_buf;
> +	dma_addr_t dma_phys;
> +	int ret;
> +	struct dma_slave_config dma_sconfig;
> +	dma_cap_mask_t mask;
> +
> +	dma_cap_zero(mask);
> +	dma_cap_set(DMA_SLAVE, mask);
> +	dma_chan = dma_request_channel(mask, NULL, NULL);
> +	if (!dma_chan) {
> +		dev_err(tup->uport.dev,
> +			"Dma channel is not available, will try later\n");
> +		return -EPROBE_DEFER;
> +	}
> +
> +	if (dma_to_memory) {
> +		dma_buf = dma_alloc_coherent(tup->uport.dev,
> +				TEGRA_UART_RX_DMA_BUFFER_SIZE,
> +				 &dma_phys, GFP_KERNEL);
> +		if (!dma_buf) {
> +			dev_err(tup->uport.dev,
> +				"Not able to allocate the dma buffer\n");
> +			dma_release_channel(dma_chan);
> +			return -ENOMEM;
> +		}
> +	} else {
> +		dma_phys = dma_map_single(tup->uport.dev,
> +			tup->uport.state->xmit.buf, UART_XMIT_SIZE,
> +			DMA_TO_DEVICE);
> +		dma_buf = tup->uport.state->xmit.buf;
> +	}
> +
> +	dma_sconfig.slave_id = tup->dma_req_sel;
> +	if (dma_to_memory) {
> +		dma_sconfig.src_addr = tup->uport.mapbase;
> +		dma_sconfig.src_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE;
> +		dma_sconfig.src_maxburst = 4;
> +	} else {
> +		dma_sconfig.dst_addr = tup->uport.mapbase;
> +		dma_sconfig.dst_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE;
> +		dma_sconfig.dst_maxburst = 16;
> +	}
> +
> +	ret = dmaengine_slave_config(dma_chan, &dma_sconfig);
> +	if (ret < 0) {
> +		dev_err(tup->uport.dev,
> +			"Dma slave config failed, err = %d\n", ret);
> +		goto scrub;
> +	}
> +
> +	if (dma_to_memory) {
> +		tup->rx_dma_chan = dma_chan;
> +		tup->rx_dma_buf_virt = dma_buf;
> +		tup->rx_dma_buf_phys = dma_phys;
> +	} else {
> +		tup->tx_dma_chan = dma_chan;
> +		tup->tx_dma_buf_virt = dma_buf;
> +		tup->tx_dma_buf_phys = dma_phys;
> +	}
> +	return 0;
> +
> +scrub:
> +	dma_release_channel(dma_chan);
> +	return ret;
> +}
> +
> +static void tegra_uart_dma_channel_free(struct tegra_uart_port *tup,
> +		bool dma_to_memory)
> +{
> +	struct dma_chan *dma_chan;
> +
> +	if (dma_to_memory) {
> +		dma_free_coherent(tup->uport.dev, TEGRA_UART_RX_DMA_BUFFER_SIZE,
> +				tup->rx_dma_buf_virt, tup->rx_dma_buf_phys);
> +		dma_chan = tup->rx_dma_chan;
> +		tup->rx_dma_chan = NULL;
> +		tup->rx_dma_buf_phys = 0;
> +		tup->rx_dma_buf_virt = NULL;
> +	} else {
> +		dma_unmap_single(tup->uport.dev, tup->tx_dma_buf_phys,
> +			UART_XMIT_SIZE, DMA_TO_DEVICE);
> +		dma_chan = tup->tx_dma_chan;
> +		tup->tx_dma_chan = NULL;
> +		tup->tx_dma_buf_phys = 0;
> +		tup->tx_dma_buf_virt = NULL;
> +	}
> +	dma_release_channel(dma_chan);
> +}
> +
> +static int tegra_uart_startup(struct uart_port *u)
> +{
> +	struct tegra_uart_port *tup = to_tegra_uport(u);
> +	int ret;
> +
> +	ret = tegra_uart_dma_channel_allocate(tup, false);
> +	if (ret < 0) {
> +		dev_err(u->dev, "Tx Dma allocation failed, err = %d\n", ret);
> +		return ret;
> +	}
> +
> +	ret = tegra_uart_dma_channel_allocate(tup, true);
> +	if (ret < 0) {
> +		dev_err(u->dev, "Rx Dma allocation failed, err = %d\n", ret);
> +		goto fail_rx_dma;
> +	}
> +
> +	ret = tegra_uart_hw_init(tup);
> +	if (ret < 0) {
> +		dev_err(u->dev, "Uart HW init failed, err = %d\n", ret);
> +		goto fail_hw_init;
> +	}
> +
> +	ret = request_irq(u->irq, tegra_uart_isr, IRQF_DISABLED,
> +				dev_name(u->dev), tup);
> +	if (ret < 0) {
> +		dev_err(u->dev, "Failed to register ISR for IRQ %d\n", u->irq);
> +		goto fail_hw_init;
> +	}
> +	return 0;
> +
> +fail_hw_init:
> +	tegra_uart_dma_channel_free(tup, true);
> +fail_rx_dma:
> +	tegra_uart_dma_channel_free(tup, false);
> +	return ret;
> +}
> +
> +static void tegra_uart_shutdown(struct uart_port *u)
> +{
> +	struct tegra_uart_port *tup = to_tegra_uport(u);
> +
> +	tegra_uart_hw_deinit(tup);
> +
> +	tup->rx_in_progress = 0;
> +	tup->tx_in_progress = 0;
> +
> +	tegra_uart_dma_channel_free(tup, true);
> +	tegra_uart_dma_channel_free(tup, false);
> +	free_irq(u->irq, tup);
> +}
> +
> +static void tegra_uart_enable_ms(struct uart_port *u)
> +{
> +	struct tegra_uart_port *tup = to_tegra_uport(u);
> +
> +	if (tup->enable_modem_interrupt) {
> +		tup->ier_shadow |= UART_IER_MSI;
> +		tegra_uart_write(tup, tup->ier_shadow, UART_IER);
> +	}
> +}
> +
> +static void tegra_uart_set_termios(struct uart_port *u,
> +		struct ktermios *termios, struct ktermios *oldtermios)
> +{
> +	struct tegra_uart_port *tup = to_tegra_uport(u);
> +	unsigned int baud;
> +	unsigned long flags;
> +	unsigned int lcr;
> +	unsigned int c_cflag = termios->c_cflag;
> +	int symb_bit = 1;
> +
> +	spin_lock_irqsave(&u->lock, flags);
> +
> +	/* Changing configuration, it is safe to stop any rx now */
> +	if (tup->rts_active)
> +		set_rts(tup, false);
> +
> +	/* Clear all interrupts as configuration is going to be change */
> +	tegra_uart_write(tup, tup->ier_shadow | UART_IER_RDI, UART_IER);
> +	tegra_uart_read(tup, UART_IER);
> +	tegra_uart_write(tup, 0, UART_IER);
> +	tegra_uart_read(tup, UART_IER);
> +
> +	/* Parity */
> +	lcr = tup->lcr_shadow;
> +	lcr &= ~UART_LCR_PARITY;
> +	if ((c_cflag & PARENB) == PARENB) {
> +		symb_bit++;
> +		if ((c_cflag & CMSPAR) == CMSPAR) {
> +			dev_err(tup->uport.dev, "Space parity not supported\n");
> +			return;
> +		} else if (c_cflag & PARODD) {
> +			lcr |= UART_LCR_PARITY;
> +			lcr &= ~UART_LCR_EPAR;
> +			lcr &= ~UART_LCR_SPAR;
> +		} else {
> +			lcr |= UART_LCR_PARITY;
> +			lcr |= UART_LCR_EPAR;
> +			lcr &= ~UART_LCR_SPAR;
> +		}
> +	}
> +
> +	lcr &= ~UART_LCR_WLEN8;
> +	switch (c_cflag & CSIZE) {
> +	case CS5:
> +		lcr |= UART_LCR_WLEN5;
> +		symb_bit += 5;
> +		break;
> +	case CS6:
> +		lcr |= UART_LCR_WLEN6;
> +		symb_bit += 6;
> +		break;
> +	case CS7:
> +		lcr |= UART_LCR_WLEN7;
> +		symb_bit += 7;
> +		break;
> +	default:
> +		lcr |= UART_LCR_WLEN8;
> +		symb_bit += 8;
> +		break;
> +	}
> +
> +	/* Stop bits */
> +	if (termios->c_cflag & CSTOPB) {
> +		lcr |= UART_LCR_STOP;
> +		symb_bit += 2;
> +	} else {
> +		lcr &= ~UART_LCR_STOP;
> +		symb_bit++;
> +	}
> +
> +	tegra_uart_write(tup, lcr, UART_LCR);
> +	tup->lcr_shadow = lcr;
> +	tup->symb_bit = symb_bit;
> +
> +	/* Baud rate. */
> +	baud = uart_get_baud_rate(u, termios, oldtermios, 200, 4000000);
> +	spin_unlock_irqrestore(&u->lock, flags);
> +	tegra_set_baudrate(tup, baud);
> +	spin_lock_irqsave(&u->lock, flags);
> +
> +	/* Flow control */
> +	if (termios->c_cflag & CRTSCTS)	{
> +		tup->mcr_shadow |= TEGRA_UART_MCR_CTS_EN;
> +		tup->mcr_shadow &= ~TEGRA_UART_MCR_RTS_EN;
> +		tegra_uart_write(tup, tup->mcr_shadow, UART_MCR);
> +		/* if top layer has asked to set rts active then do so here */
> +		if (tup->rts_active)
> +			set_rts(tup, true);
> +	} else {
> +		tup->mcr_shadow &= ~TEGRA_UART_MCR_CTS_EN;
> +		tup->mcr_shadow &= ~TEGRA_UART_MCR_RTS_EN;
> +		tegra_uart_write(tup, tup->mcr_shadow, UART_MCR);
> +	}
> +
> +	/* update the port timeout based on new settings */
> +	uart_update_timeout(u, termios->c_cflag, baud);
> +
> +	/* Make sure all write has completed */
> +	tegra_uart_read(tup, UART_IER);
> +
> +	/* Reenable interrupt */
> +	tegra_uart_write(tup, tup->ier_shadow, UART_IER);
> +	tegra_uart_read(tup, UART_IER);
> +
> +	spin_unlock_irqrestore(&u->lock, flags);
> +	return;
> +}
> +
> +/*
> + * Flush any TX data submitted for DMA and PIO. Called when the
> + * TX circular buffer is reset.
> + */
> +static void tegra_uart_flush_buffer(struct uart_port *u)
> +{
> +	struct tegra_uart_port *tup = to_tegra_uport(u);
> +
> +	tup->tx_bytes = 0;
> +	if (tup->tx_dma_chan)
> +		dmaengine_terminate_all(tup->tx_dma_chan);
> +	return;
> +}
> +
> +static const char *tegra_uart_type(struct uart_port *u)
> +{
> +	return TEGRA_UART_TYPE;
> +}
> +
> +static struct uart_ops tegra_uart_ops = {
> +	.tx_empty	= tegra_uart_tx_empty,
> +	.set_mctrl	= tegra_uart_set_mctrl,
> +	.get_mctrl	= tegra_uart_get_mctrl,
> +	.stop_tx	= tegra_uart_stop_tx,
> +	.start_tx	= tegra_uart_start_tx,
> +	.stop_rx	= tegra_uart_stop_rx,
> +	.flush_buffer	= tegra_uart_flush_buffer,
> +	.enable_ms	= tegra_uart_enable_ms,
> +	.break_ctl	= tegra_uart_break_ctl,
> +	.startup	= tegra_uart_startup,
> +	.shutdown	= tegra_uart_shutdown,
> +	.set_termios	= tegra_uart_set_termios,
> +	.type		= tegra_uart_type,
> +	.request_port	= tegra_uart_request_port,
> +	.release_port	= tegra_uart_release_port,
> +};
> +
> +static struct uart_driver tegra_uart_driver = {
> +	.owner		= THIS_MODULE,
> +	.driver_name	= "tegra_hsuart",
> +	.dev_name	= "ttyHS",
> +	.cons		= 0,
> +	.nr		= TEGRA_UART_MAXIMUM,
> +};
> +
> +static struct tegra_uart_platform_data *tegra_uart_parse_dt(
> +		struct platform_device *pdev)
> +{
> +	struct tegra_uart_platform_data *pdata;
> +	struct device_node *np = pdev->dev.of_node;
> +	u32 of_dma[2];
> +
> +	pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL);
> +	if (!pdata) {
> +		dev_err(&pdev->dev, "Memory alloc for pdata failed\n");
> +		return NULL;
> +	}
> +
> +	if (of_property_read_u32_array(np, "nvidia,dma-request-selector",
> +				of_dma, 2) >= 0) {
> +		pdata->dma_req_sel = of_dma[1];
> +	} else {
> +		dev_err(&pdev->dev, "missing dma requestor in device tree\n");
> +		return NULL;
> +	}
> +
> +	if (!of_property_read_u32(np, "port-number", &of_dma[0])) {
> +		pdata->port_number = of_dma[0];
> +	} else {
> +		dev_err(&pdev->dev, "missing port-number in device tree\n");
> +		return NULL;
> +	}
> +
> +	if (of_get_property(np, "nvidia,enable-modem-interrupt", NULL))
> +		pdata->enable_modem_interrupt = 1;
> +
> +	return pdata;
> +}
> +
> +static struct of_device_id tegra_uart_of_match[] __devinitconst = {
> +	{
> +		.compatible	= "nvidia,tegra30-hsuart",
> +		.data		= &tegra30_uart_chip_data,
> +	}, {
> +		.compatible	= "nvidia,tegra20-hsuart",
> +		.data		= &tegra20_uart_chip_data,
> +	}, {
> +	},
> +};
> +MODULE_DEVICE_TABLE(of, tegra_uart_of_match);
> +
> +static int __devinit tegra_uart_probe(struct platform_device *pdev)
> +{
> +	struct tegra_uart_port *tup;
> +	struct uart_port *u;
> +	struct tegra_uart_platform_data *pdata = pdev->dev.platform_data;
> +	struct resource *resource;
> +	int ret;
> +	const struct tegra_uart_chip_data *cdata = &tegra30_uart_chip_data;
> +	const struct of_device_id *match;
> +
> +	if (pdev->dev.of_node) {
> +		match = of_match_device(of_match_ptr(tegra_uart_of_match),
> +				&pdev->dev);
> +		if (!match) {
> +			dev_err(&pdev->dev, "Error: No device match found\n");
> +			return -ENODEV;
> +		}
> +		cdata = match->data;
> +	}
> +
> +	if (!pdata && pdev->dev.of_node)
> +		pdata = tegra_uart_parse_dt(pdev);
> +
> +	if (!pdata) {
> +		dev_err(&pdev->dev, "No platform data, exiting\n");
> +		return -ENODEV;
> +	}
> +
> +	tup = devm_kzalloc(&pdev->dev, sizeof(*tup), GFP_KERNEL);
> +	if (!tup) {
> +		dev_err(&pdev->dev, "Failed to allocate memory for tup\n");
> +		return -ENOMEM;
> +	}
> +
> +	u = &tup->uport;
> +	u->dev = &pdev->dev;
> +	u->line = pdata->port_number;
> +	u->ops = &tegra_uart_ops;
> +	u->type = PORT_TEGRA;
> +	u->fifosize = 32;
> +	tup->dma_req_sel = pdata->dma_req_sel;
> +	tup->cdata = cdata;
> +	tup->enable_modem_interrupt = pdata->enable_modem_interrupt;
> +
> +	platform_set_drvdata(pdev, tup);
> +	resource = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> +	if (!resource) {
> +		dev_err(&pdev->dev, "No IO memory resource\n");
> +		return -ENODEV;
> +	}
> +
> +	u->mapbase = resource->start;
> +	u->membase = devm_request_and_ioremap(&pdev->dev, resource);
> +	if (!u->membase) {
> +		dev_err(&pdev->dev, "memregion/iomap address req failed\n");
> +		return -EADDRNOTAVAIL;
> +	}
> +
> +	tup->uart_clk = devm_clk_get(&pdev->dev, "uart-clk");
> +	if (IS_ERR(tup->uart_clk)) {
> +		dev_err(&pdev->dev, "Couldn't get the clock\n");
> +		return PTR_ERR(tup->uart_clk);
> +	}
> +
> +	u->iotype = UPIO_MEM32;
> +	u->irq = platform_get_irq(pdev, 0);
> +	u->regshift = 2;
> +	ret = uart_add_one_port(&tegra_uart_driver, u);
> +	if (ret < 0) {
> +		dev_err(&pdev->dev, "Failed to add uart port, err %d\n", ret);
> +		return ret;
> +	}
> +	return ret;
> +}
> +
> +static int __devexit tegra_uart_remove(struct platform_device *pdev)
> +{
> +	struct tegra_uart_port *tup = platform_get_drvdata(pdev);
> +	struct uart_port *u = &tup->uport;
> +
> +	uart_remove_one_port(&tegra_uart_driver, u);
> +	return 0;
> +}
> +
> +#ifdef CONFIG_PM_SLEEP
> +static int tegra_uart_suspend(struct device *dev)
> +{
> +	struct tegra_uart_port *tup = dev_get_drvdata(dev);
> +	struct uart_port *u = &tup->uport;
> +
> +	return uart_suspend_port(&tegra_uart_driver, u);
> +}
> +
> +static int tegra_uart_resume(struct device *dev)
> +{
> +	struct tegra_uart_port *tup = dev_get_drvdata(dev);
> +	struct uart_port *u = &tup->uport;
> +
> +	return uart_resume_port(&tegra_uart_driver, u);
> +}
> +#endif
> +
> +static const struct dev_pm_ops tegra_uart_pm_ops = {
> +	SET_SYSTEM_SLEEP_PM_OPS(tegra_uart_suspend, tegra_uart_resume)
> +};
> +
> +static struct platform_driver tegra_uart_platform_driver = {
> +	.probe		= tegra_uart_probe,
> +	.remove		= __devexit_p(tegra_uart_remove),
> +	.driver		= {
> +		.name	= "serial-tegra",
> +		.of_match_table = of_match_ptr(tegra_uart_of_match),
> +		.pm	= &tegra_uart_pm_ops,
> +	},
> +};
> +
> +static int __init tegra_uart_init(void)
> +{
> +	int ret;
> +
> +	ret = uart_register_driver(&tegra_uart_driver);
> +	if (ret < 0) {
> +		pr_err("Could not register %s driver\n",
> +			tegra_uart_driver.driver_name);
> +		return ret;
> +	}
> +
> +	ret = platform_driver_register(&tegra_uart_platform_driver);
> +	if (ret < 0) {
> +		pr_err("Uart platfrom driver register failed, e = %d\n", ret);
> +		uart_unregister_driver(&tegra_uart_driver);
> +		return ret;
> +	}
> +	return 0;
> +}
> +
> +static void __exit tegra_uart_exit(void)
> +{
> +	pr_info("Unloading tegra uart driver\n");
> +	platform_driver_unregister(&tegra_uart_platform_driver);
> +	uart_unregister_driver(&tegra_uart_driver);
> +}
> +
> +module_init(tegra_uart_init);
> +module_exit(tegra_uart_exit);
> +
> +MODULE_ALIAS("platform:serial-tegra");
> +MODULE_DESCRIPTION("High speed UART driver for tegra chipset");
> +MODULE_AUTHOR("Laxman Dewangan <ldewangan@nvidia.com>");
> +MODULE_LICENSE("GPL v2");
> diff --git a/include/linux/serial_tegra.h b/include/linux/serial_tegra.h
> new file mode 100644
> index 0000000..f9828b8
> --- /dev/null
> +++ b/include/linux/serial_tegra.h
> @@ -0,0 +1,33 @@
> +/*
> + * serial_tegra.h
> + *
> + * Interface for High-speed serial driver for NVIDIA Tegra SoCs.
> + *
> + * Copyright (c) 2012, NVIDIA CORPORATION.  All rights reserved.
> + *
> + * Author: Laxman Dewangan <ldewangan@nvidia.com>
> + *
> + * This program is free software; you can redistribute it and/or modify it
> + * under the terms and conditions of the GNU General Public License,
> + * version 2, as published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope it will be useful, but WITHOUT
> + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
> + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
> + * more details.
> + *
> + * You should have received a copy of the GNU General Public License
> + * along with this program.  If not, see <http://www.gnu.org/licenses/>.
> + */
> +
> +#ifndef _SERIAL_TEGRA_H_
> +#define _SERIAL_TEGRA_H_
> +
> +struct tegra_uart_platform_data {
> +	int dma_req_sel;
> +	int port_number;
> +	unsigned enable_modem_interrupt:1;
> +};
> +
> +#endif /* _SERIAL_TEGRA_H_ */
> +
> 

^ permalink raw reply

* Re: [PATCH] serial: tegra: add serial driver
From: Grant Likely @ 2012-12-17 17:10 UTC (permalink / raw)
  To: alan, gregkh, jslaby
  Cc: rob.herring, devicetree-discuss, linux-doc, linux-kernel,
	linux-serial, linux-tegra, swarren, Laxman Dewangan
In-Reply-To: <1355746249-15347-1-git-send-email-ldewangan@nvidia.com>

On Mon, 17 Dec 2012 17:40:49 +0530, Laxman Dewangan <ldewangan@nvidia.com> wrote:
> Nvidia's Tegra has multiple uart controller which supports:
> - APB dma based controller fifo read/write.
> - End Of Data interrupt in incoming data to know whether end
>   of frame achieve or not.
> - Hw controlled RTS and CTS flow control to reduce SW overhead.
> 
> Add serial driver to use all above feature.
> 
> Signed-off-by: Laxman Dewangan <ldewangan@nvidia.com>

Hi Laxman,

Comments below...

> ---
>  .../bindings/serial/nvidia,serial-tegra.txt        |   26 +
>  drivers/tty/serial/Kconfig                         |   14 +
>  drivers/tty/serial/Makefile                        |    1 +
>  drivers/tty/serial/serial_tegra.c                  | 1398 ++++++++++++++++++++
>  include/linux/serial_tegra.h                       |   33 +
>  5 files changed, 1472 insertions(+), 0 deletions(-)
>  create mode 100644 Documentation/devicetree/bindings/serial/nvidia,serial-tegra.txt
>  create mode 100644 drivers/tty/serial/serial_tegra.c
>  create mode 100644 include/linux/serial_tegra.h
> 
> diff --git a/Documentation/devicetree/bindings/serial/nvidia,serial-tegra.txt b/Documentation/devicetree/bindings/serial/nvidia,serial-tegra.txt
> new file mode 100644
> index 0000000..fc5803b
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/serial/nvidia,serial-tegra.txt

Nit: nvidia-tegra.txt would be sufficient.

> @@ -0,0 +1,26 @@
> +NVIDIA Tegra20/Tegra30 high speed (dma based) UART controller driver.
> +
> +Required properties:
> +- compatible : should be "nvidia,tegra20-hsuart", "nvidia,tegra30-hsuart".
> +- reg: Should contain UART controller registers location and length.
> +- interrupts: Should contain UART controller interrupts.
> +- nvidia,dma-request-selector : The Tegra DMA controller's phandle and
> +  request selector for this UART controller.
> +- port-number: Uart port number for /dev/ttyHSx where x is port number.

Drop port-number. Use an alias instead (/aliases/serial#)

Otherwise the binding looks fine.

> diff --git a/drivers/tty/serial/Kconfig b/drivers/tty/serial/Kconfig
> index 59c23d0..57dbbc1 100644
> --- a/drivers/tty/serial/Kconfig
> +++ b/drivers/tty/serial/Kconfig
> @@ -269,6 +269,20 @@ config SERIAL_SIRFSOC_CONSOLE
>            your boot loader about how to pass options to the kernel at
>            boot time.)
>  
> +config SERIAL_SAMSUNG_UARTS_4
> +	bool

? This looks like a stale hunk

> +config SERIAL_TEGRA
> +	tristate "Nvidia Tegra20/30 SoC serial controller"
> +	depends on ARCH_TEGRA && TEGRA20_APB_DMA
> +	select SERIAL_CORE
> +	help
> +	  Support for the on-chip UARTs on the Nvidia Tegra seria SOCs
> +	  providing /dev/ttyHS0, 1, 2, 3 and 4 (note, some machines may not
> +	  provide all of these ports, depending on how the serial port
> +	  are enabled). This driver uses the APB dma to achieve higher baudrate
> +	  and better performance.
> +
>  config SERIAL_MAX3100
>  	tristate "MAX3100 support"
>  	depends on SPI
[...]
> +static void tegra_uart_set_mctrl(struct uart_port *u, unsigned int mctrl)
> +{
> +	struct tegra_uart_port *tup = to_tegra_uport(u);
> +	unsigned long mcr;
> +
> +	mcr = tup->mcr_shadow;
> +	if (mctrl & TIOCM_RTS) {
> +		tup->rts_active = true;
> +		set_rts(tup, true);
> +	} else {
> +		tup->rts_active = false;
> +		set_rts(tup, false);
> +	}

Or simply:
	tup->rts_active = !!(mctrl & TIOCM_RTS)
	set_rts(tup, tup->rts_active);

The driver seems rather verbose in this regard. It isn't something I'd
nak over, but it does increase the maintenance burden.

> +
> +	if (mctrl & TIOCM_DTR)
> +		set_dtr(tup, true);
> +	else
> +		set_dtr(tup, false);
> +	return;
> +}
> +static int __devinit tegra_uart_probe(struct platform_device *pdev)
> +{
> +	struct tegra_uart_port *tup;
> +	struct uart_port *u;
> +	struct tegra_uart_platform_data *pdata = pdev->dev.platform_data;

Since this is a new driver, and all new board support will use device
tree, when would this platform_data pointer get set? Can you drop the
platform_data support code entirely?

The driver itself is a lot of code. I've not gone through it in the
detail that I'd like to, but it appears to be fine. Fix up the above
comments and you can add my:

Reviewed-by: Grant Likely <grant.likely@secretlab.ca>


^ permalink raw reply

* Re: [PATCH] serial: tegra: add serial driver
From: Alan Cox @ 2012-12-17 18:23 UTC (permalink / raw)
  To: Laxman Dewangan
  Cc: alan, gregkh, jslaby, grant.likely, rob.herring,
	devicetree-discuss, linux-doc, linux-kernel, linux-serial,
	linux-tegra, swarren
In-Reply-To: <1355746249-15347-1-git-send-email-ldewangan@nvidia.com>



> +
> +static void tegra_uart_copy_rx_to_tty(struct tegra_uart_port *tup, int count)
> +{
> +	int copied;
> +
> +	tup->uport.icount.rx += count;
> +	dma_sync_single_for_cpu(tup->uport.dev, tup->rx_dma_buf_phys,
> +				TEGRA_UART_RX_DMA_BUFFER_SIZE, DMA_FROM_DEVICE);
> +	copied = tty_insert_flip_string(tup->uport.state->port.tty,

Reference needed and port.tty can go to NULL
(good rule of thumb - any use of port.tty directly is probably wrong)

‎
> +	tty_flip_buffer_push(u->state->port.tty);

Ditto (and elsewhere)

> +	if ((c_cflag & PARENB) == PARENB) {
> +		symb_bit++;
> +		if ((c_cflag & CMSPAR) == CMSPAR) {
> +			dev_err(tup->uport.dev, "Space parity not supported\n");
> +			return;

No - this is out of spec and lets user tasks spam the log. Just clear the
flag if not supported.


> +	/* Baud rate. */
> +	baud = uart_get_baud_rate(u, termios, oldtermios, 200, 4000000);
> +	spin_unlock_irqrestore(&u->lock, flags);
> +	tegra_set_baudrate(tup, baud);
> +	spin_lock_irqsave(&u->lock, flags);

I think you need to use the helpers to put the actual baud it got into the
termios if not B0 - see 8250.c ?

Alan

^ permalink raw reply

* Re: [PATCH] serial: tegra: add serial driver
From: Stephen Warren @ 2012-12-17 21:30 UTC (permalink / raw)
  To: Rob Herring
  Cc: Laxman Dewangan, alan, gregkh, jslaby, grant.likely,
	devicetree-discuss, linux-doc, linux-kernel, linux-serial,
	linux-tegra
In-Reply-To: <50CF393F.4040009@gmail.com>

On 12/17/2012 08:24 AM, Rob Herring wrote:
> On 12/17/2012 06:10 AM, Laxman Dewangan wrote:
>> Nvidia's Tegra has multiple uart controller which supports:
>> - APB dma based controller fifo read/write.
>> - End Of Data interrupt in incoming data to know whether end
>>   of frame achieve or not.
>> - Hw controlled RTS and CTS flow control to reduce SW overhead.
>>
>> Add serial driver to use all above feature.
>>
>> Signed-off-by: Laxman Dewangan <ldewangan@nvidia.com>
>> ---
>>  .../bindings/serial/nvidia,serial-tegra.txt        |   26 +
> 
> Can we have a name without a comma. If serial-tegra is specific enough
> for the kernel, then it is for the binding doc too.

I would vastly prefer that the binding doc filenames use the compatible
value exactly. All (or as many as possible) of the other Tegra bindings
do this, and I think it's good style.

>> diff --git a/Documentation/devicetree/bindings/serial/nvidia,serial-tegra.txt b/Documentation/devicetree/bindings/serial/nvidia,serial-tegra.txt

>> +- nvidia,dma-request-selector : The Tegra DMA controller's phandle and
>> +  request selector for this UART controller.
> 
> This should use the generic DMA binding although I'm not sure if that
> made it into 3.8.

It didn't as far as I can tell, even though I think someone said it
should... Will it make 3.9? As a general rule, I'd like to move forward
on drivers and then refactor this aspect once the generic API/binding is
finalized and implemented.

(I couldn't find any more comments of yours below, but since the whole
patch was quoted, it was difficult to tell)

^ permalink raw reply

* Re: [PATCH] serial: tegra: add serial driver
From: Stephen Warren @ 2012-12-17 21:31 UTC (permalink / raw)
  To: Grant Likely
  Cc: Laxman Dewangan, alan, gregkh, jslaby, rob.herring,
	devicetree-discuss, linux-doc, linux-kernel, linux-serial,
	linux-tegra
In-Reply-To: <20121217171027.6AE573E0BDD@localhost>

On 12/17/2012 10:10 AM, Grant Likely wrote:
> On Mon, 17 Dec 2012 17:40:49 +0530, Laxman Dewangan <ldewangan@nvidia.com> wrote:
>> Nvidia's Tegra has multiple uart controller which supports:
>> - APB dma based controller fifo read/write.
>> - End Of Data interrupt in incoming data to know whether end
>>   of frame achieve or not.
>> - Hw controlled RTS and CTS flow control to reduce SW overhead.

>> +static int __devinit tegra_uart_probe(struct platform_device *pdev)
>> +{
>> +	struct tegra_uart_port *tup;
>> +	struct uart_port *u;
>> +	struct tegra_uart_platform_data *pdata = pdev->dev.platform_data;
> 
> Since this is a new driver, and all new board support will use device
> tree, when would this platform_data pointer get set? Can you drop the
> platform_data support code entirely?

Aren't we still supposed to support platform data so that it can
override what's in DT in order to fix up bad DTs? Or, has that
requirement been dropped. If it has, we can drop a bunch of code from a
variety of Tegra-specific drivers, I expect.

^ permalink raw reply


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox