diff mbox

[7/7] serial: imx: add support for half duplex rs485

Message ID 1424292068-28197-7-git-send-email-u.kleine-koenig@pengutronix.de
State New
Headers show

Commit Message

Uwe Kleine-König Feb. 18, 2015, 8:41 p.m. UTC
The transmitter is expected to be controlled by the UART's RTS pin.

Signed-off-by: Uwe Kleine-König <u.kleine-koenig@pengutronix.de>
---
 drivers/tty/serial/imx.c | 94 +++++++++++++++++++++++++++++++++++++++++-------
 1 file changed, 81 insertions(+), 13 deletions(-)

Comments

aurélien bouin Feb. 19, 2015, 9:15 a.m. UTC | #1
Hello,

2015-02-18 21:41 GMT+01:00 Uwe Kleine-König <u.kleine-koenig@pengutronix.de>:
> The transmitter is expected to be controlled by the UART's RTS pin.

You definately can be more open in using the last patch I have sent
the 11th of februrary using rs485.padding[0] that give in parameter
the GPIO to use for transmit pin

> Signed-off-by: Uwe Kleine-König <u.kleine-koenig@pengutronix.de>
> ---
>  drivers/tty/serial/imx.c | 94 +++++++++++++++++++++++++++++++++++++++++-------
>  1 file changed, 81 insertions(+), 13 deletions(-)
>
> diff --git a/drivers/tty/serial/imx.c b/drivers/tty/serial/imx.c
> index 806b3cfb7f55..ad43019a4880 100644
> --- a/drivers/tty/serial/imx.c
> +++ b/drivers/tty/serial/imx.c
> @@ -364,8 +364,20 @@ static void imx_stop_tx(struct uart_port *port)
>         if (sport->dma_is_enabled && sport->dma_is_txing)
>                 return;
>
> -       temp = readl(sport->port.membase + UCR1);
> -       writel(temp & ~UCR1_TXMPTYEN, sport->port.membase + UCR1);
> +       temp = readl(port->membase + UCR1);
> +       writel(temp & ~UCR1_TXMPTYEN, port->membase + UCR1);
> +
> +       /* in rs485 mode disable transmitter if shifter is empty */
> +       if (port->rs485.flags & SER_RS485_ENABLED &&
> +           readl(port->membase + USR2) & USR2_TXDC) {
> +               temp = readl(port->membase + UCR2);
> +               temp |= UCR2_CTS;
> +               writel(temp, port->membase + UCR2);
> +
> +               temp = readl(port->membase + UCR4);
> +               temp &= ~UCR4_TCEN;
> +               writel(temp, port->membase + UCR4);
> +       }
>  }
>
>  /*
> @@ -520,6 +532,17 @@ static void imx_start_tx(struct uart_port *port)
>         struct imx_port *sport = (struct imx_port *)port;
>         unsigned long temp;
>
> +       if (port->rs485.flags & SER_RS485_ENABLED) {
> +               /* enable transmitter and shifter empty irq */
> +               temp = readl(port->membase + UCR2);
> +               temp &= ~UCR2_CTS;
> +               writel(temp, port->membase + UCR2);
> +
> +               temp = readl(port->membase + UCR4);
> +               temp |= UCR4_TCEN;
> +               writel(temp, port->membase + UCR4);
> +       }
> +
>         if (!sport->dma_is_enabled) {
>                 temp = readl(sport->port.membase + UCR1);
>                 writel(temp | UCR1_TXMPTYEN, sport->port.membase + UCR1);
> @@ -661,6 +684,7 @@ static irqreturn_t imx_int(int irq, void *dev_id)
>         unsigned int sts2;
>
>         sts = readl(sport->port.membase + USR1);
> +       sts2 = readl(sport->port.membase + USR2);
>
>         if (sts & USR1_RRDY) {
>                 if (sport->dma_is_enabled)
> @@ -669,8 +693,10 @@ static irqreturn_t imx_int(int irq, void *dev_id)
>                         imx_rxint(irq, dev_id);
>         }
>
> -       if (sts & USR1_TRDY &&
> -                       readl(sport->port.membase + UCR1) & UCR1_TXMPTYEN)
> +       if ((sts & USR1_TRDY &&
> +            readl(sport->port.membase + UCR1) & UCR1_TXMPTYEN) ||
> +           (sts2 & USR2_TXDC &&
> +            readl(sport->port.membase + UCR4) & UCR4_TCEN))
>                 imx_txint(irq, dev_id);
>
>         if (sts & USR1_RTSD)
> @@ -679,7 +705,6 @@ static irqreturn_t imx_int(int irq, void *dev_id)
>         if (sts & USR1_AWAKE)
>                 writel(USR1_AWAKE, sport->port.membase + USR1);
>
> -       sts2 = readl(sport->port.membase + USR2);
>         if (sts2 & USR2_ORE) {
>                 dev_err(sport->port.dev, "Rx FIFO overrun\n");
>                 sport->port.icount.overrun++;
> @@ -731,11 +756,13 @@ static void imx_set_mctrl(struct uart_port *port, unsigned int mctrl)
>         struct imx_port *sport = (struct imx_port *)port;
>         unsigned long temp;
>
> -       temp = readl(sport->port.membase + UCR2) & ~(UCR2_CTS | UCR2_CTSC);
> -       if (mctrl & TIOCM_RTS)
> -               temp |= UCR2_CTS | UCR2_CTSC;
> -
> -       writel(temp, sport->port.membase + UCR2);
> +       if (!(port->rs485.flags & SER_RS485_ENABLED)) {
> +               temp = readl(sport->port.membase + UCR2);
> +               temp &= ~(UCR2_CTS | UCR2_CTSC);
> +               if (mctrl & TIOCM_RTS)
> +                       temp |= UCR2_CTS | UCR2_CTSC;
> +               writel(temp, sport->port.membase + UCR2);
> +       }
>
>         temp = readl(sport->port.membase + uts_reg(sport)) & ~UTS_LOOP;
>         if (mctrl & TIOCM_LOOP)
> @@ -1029,7 +1056,7 @@ static int imx_startup(struct uart_port *port)
>         temp |= UCR1_RRDYEN | UCR1_RTSDEN | UCR1_UARTEN;
>         writel(temp, sport->port.membase + UCR1);
>
> -       temp = read(sport->port.membase + UCR4);
> +       temp = readl(sport->port.membase + UCR4);
>         temp |= UCR4_OREN;
>         writel(temp, sport->port.membase + UCR4);
>
> @@ -1144,7 +1171,17 @@ imx_set_termios(struct uart_port *port, struct ktermios *termios,
>         if (termios->c_cflag & CRTSCTS) {
>                 if (sport->have_rtscts) {
>                         ucr2 &= ~UCR2_IRTS;
> -                       ucr2 |= UCR2_CTSC;
> +
> +                       if (port->rs485.flags & SER_RS485_ENABLED)
> +                               /*
> +                                * RTS is mandatory for rs485 operation, so keep
> +                                * it under manual control and keep transmitter
> +                                * disabled.
> +                                */
> +                               ucr2 |= UCR2_CTS;
> +                       else
> +                               ucr2 |= UCR2_CTSC;
> +
>
>                         /* Can we enable the DMA support? */
>                         if (is_imx6q_uart(sport) && !uart_console(port)
> @@ -1153,7 +1190,9 @@ imx_set_termios(struct uart_port *port, struct ktermios *termios,
>                 } else {
>                         termios->c_cflag &= ~CRTSCTS;
>                 }
> -       }
> +       } else if (port->rs485.flags & SER_RS485_ENABLED)
> +               /* disable transmitter */
> +               ucr2 |= UCR2_CTS;
>
>         if (termios->c_cflag & CSTOPB)
>                 ucr2 |= UCR2_STPB;
> @@ -1374,6 +1413,34 @@ static void imx_poll_put_char(struct uart_port *port, unsigned char c)
>  }
>  #endif
>
> +static int imx_rs485_config(struct uart_port *port,
> +                           struct serial_rs485 *rs485conf)
> +{
> +       struct imx_port *sport = (struct imx_port *)port;
> +
> +       /* unimplemented */
> +       rs485conf->delay_rts_before_send = 0;
> +       rs485conf->delay_rts_after_send = 0;
> +
> +       /* RTS is required to control the transmitter */
> +       if (!sport->have_rtscts)
> +               rs485conf->flags &= ~SER_RS485_ENABLED;
> +
> +       if (rs485conf->flags & SER_RS485_ENABLED) {
> +               unsigned long temp;
> +
> +               /* disable transmitter */
> +               temp = readl(sport->port.membase + UCR2);
> +               temp &= ~UCR2_CTSC;
> +               temp |= UCR2_CTS;
> +               writel(temp, sport->port.membase + UCR2);
> +       }
> +
> +       port->rs485 = *rs485conf;
> +
> +       return 0;
> +}
> +
>  static struct uart_ops imx_pops = {
>         .tx_empty       = imx_tx_empty,
>         .set_mctrl      = imx_set_mctrl,
> @@ -1731,6 +1798,7 @@ static int serial_imx_probe(struct platform_device *pdev)
>         sport->port.irq = rxirq;
>         sport->port.fifosize = 32;
>         sport->port.ops = &imx_pops;
> +       sport->port.rs485_config = imx_rs485_config;
>         sport->port.flags = UPF_BOOT_AUTOCONF;
>         init_timer(&sport->timer);
>         sport->timer.function = imx_timeout;
> --
> 2.1.4
>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-serial" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
Uwe Kleine-König Feb. 19, 2015, 9:37 a.m. UTC | #2
Hello Aurélien,

On Thu, Feb 19, 2015 at 10:15:10AM +0100, aurélien bouin wrote:
> 2015-02-18 21:41 GMT+01:00 Uwe Kleine-König <u.kleine-koenig@pengutronix.de>:
> > The transmitter is expected to be controlled by the UART's RTS pin.
> 
> You definately can be more open in using the last patch I have sent
> the 11th of februrary using rs485.padding[0] that give in parameter
> the GPIO to use for transmit pin
you have a link? Without having seen that patch this can well be a
separate patch. And (also without having seen the patch) I wonder why
the information which gpio to use doesn't come from the device tree but
is supplied by userspace. I thought about expanding mctrl-gpio for that,
but as there are a few conflicting patches to mctrl-gpio out I wanted to
wait until the dust settles.

Best regards
Uwe
aurélien bouin Feb. 19, 2015, 9:45 a.m. UTC | #3
I hope this link will be ok :
http://marc.info/?l=linux-kernel&m=142366865609389&q=raw
Best regards,



2015-02-19 10:37 GMT+01:00 Uwe Kleine-König <u.kleine-koenig@pengutronix.de>:
> Hello Aurélien,
>
> On Thu, Feb 19, 2015 at 10:15:10AM +0100, aurélien bouin wrote:
>> 2015-02-18 21:41 GMT+01:00 Uwe Kleine-König <u.kleine-koenig@pengutronix.de>:
>> > The transmitter is expected to be controlled by the UART's RTS pin.
>>
>> You definately can be more open in using the last patch I have sent
>> the 11th of februrary using rs485.padding[0] that give in parameter
>> the GPIO to use for transmit pin
> you have a link? Without having seen that patch this can well be a
> separate patch. And (also without having seen the patch) I wonder why
> the information which gpio to use doesn't come from the device tree but
> is supplied by userspace. I thought about expanding mctrl-gpio for that,
> but as there are a few conflicting patches to mctrl-gpio out I wanted to
> wait until the dust settles.
>
> Best regards
> Uwe
>
> --
> Pengutronix e.K.                           | Uwe Kleine-König            |
> Industrial Linux Solutions                 | http://www.pengutronix.de/  |
Uwe Kleine-König Feb. 20, 2015, 3:34 p.m. UTC | #4
On Thu, Feb 19, 2015 at 10:45:47AM +0100, aurélien bouin wrote:
> I hope this link will be ok :
> http://marc.info/?l=linux-kernel&m=142366865609389&q=raw
Good heavens, this patch is broken. It depends on user space to get the
reference counting for the gpio right if at all. It uses a reserved part
of the struct serial_rs485 while being driver specific. It ignores
gpio_request returning an error (well, it does a dev_err in this case,
but continues to use the gpio). It does change how frame error are
handled even if rs485 is off without mentioning in the changelog.

Are you aware what happens if a user issues the rs485 callback without
being aware that padding[0] has this meaning?

I'm sorry, I like my patch better.

Best regards
Uwe
gianluca Feb. 20, 2015, 3:53 p.m. UTC | #5
Hello,
speaking of serial patch for rs485, I wrote a device driver for mxs 
(iMX28): (it is half-duplex too)

It rely on device tree node as:

>> 				rs485_tx_en: rx485-tx-en {
>> 					fsl,pinmux-ids = <
>> 						0x2133 /* MX28_PAD_SSP2_SS0__GPIO_2_19 UART2_DIR */
>> 					>;
>> 					fsl,drive-strength = <0>;
>> 					fsl,pull-up = <1>;
>> 				};


and this:

>> 			auart2: serial@8006e000 {
>> 				compatible = "fsl,imx28-ekuart";
>> 				pinctrl-names = "default";
>> 				pinctrl-0 = <&auart2_2pins_a
>> 					&rs485_tx_en>;
>> 				rs485-rts-active-low;
>> 				rts-gpio = <&gpio2 19 1>;
>> 				linux,rs485-enabled-at-boot-time;
>> 				rs485-rts-delay = <0 0>;
>> 				status = "okay";
>> 			};

And in attachment the driver. The hr_timer implementation is working 
correctly even with a big load and 3/4 serials at the time.

Give it a look if you want.

On 02/20/2015 04:34 PM, Uwe Kleine-König wrote:
> On Thu, Feb 19, 2015 at 10:45:47AM +0100, aurélien bouin wrote:
>> I hope this link will be ok :
>> http://marc.info/?l=linux-kernel&m=142366865609389&q=raw
> Good heavens, this patch is broken. It depends on user space to get the
> reference counting for the gpio right if at all. It uses a reserved part
> of the struct serial_rs485 while being driver specific. It ignores
> gpio_request returning an error (well, it does a dev_err in this case,
> but continues to use the gpio). It does change how frame error are
> handled even if rs485 is off without mentioning in the changelog.
>
> Are you aware what happens if a user issues the rs485 callback without
> being aware that padding[0] has this meaning?
>
> I'm sorry, I like my patch better.
>
> Best regards
> Uwe
>
diff mbox

Patch

diff --git a/drivers/tty/serial/imx.c b/drivers/tty/serial/imx.c
index 806b3cfb7f55..ad43019a4880 100644
--- a/drivers/tty/serial/imx.c
+++ b/drivers/tty/serial/imx.c
@@ -364,8 +364,20 @@  static void imx_stop_tx(struct uart_port *port)
 	if (sport->dma_is_enabled && sport->dma_is_txing)
 		return;
 
-	temp = readl(sport->port.membase + UCR1);
-	writel(temp & ~UCR1_TXMPTYEN, sport->port.membase + UCR1);
+	temp = readl(port->membase + UCR1);
+	writel(temp & ~UCR1_TXMPTYEN, port->membase + UCR1);
+
+	/* in rs485 mode disable transmitter if shifter is empty */
+	if (port->rs485.flags & SER_RS485_ENABLED &&
+	    readl(port->membase + USR2) & USR2_TXDC) {
+		temp = readl(port->membase + UCR2);
+		temp |= UCR2_CTS;
+		writel(temp, port->membase + UCR2);
+
+		temp = readl(port->membase + UCR4);
+		temp &= ~UCR4_TCEN;
+		writel(temp, port->membase + UCR4);
+	}
 }
 
 /*
@@ -520,6 +532,17 @@  static void imx_start_tx(struct uart_port *port)
 	struct imx_port *sport = (struct imx_port *)port;
 	unsigned long temp;
 
+	if (port->rs485.flags & SER_RS485_ENABLED) {
+		/* enable transmitter and shifter empty irq */
+		temp = readl(port->membase + UCR2);
+		temp &= ~UCR2_CTS;
+		writel(temp, port->membase + UCR2);
+
+		temp = readl(port->membase + UCR4);
+		temp |= UCR4_TCEN;
+		writel(temp, port->membase + UCR4);
+	}
+
 	if (!sport->dma_is_enabled) {
 		temp = readl(sport->port.membase + UCR1);
 		writel(temp | UCR1_TXMPTYEN, sport->port.membase + UCR1);
@@ -661,6 +684,7 @@  static irqreturn_t imx_int(int irq, void *dev_id)
 	unsigned int sts2;
 
 	sts = readl(sport->port.membase + USR1);
+	sts2 = readl(sport->port.membase + USR2);
 
 	if (sts & USR1_RRDY) {
 		if (sport->dma_is_enabled)
@@ -669,8 +693,10 @@  static irqreturn_t imx_int(int irq, void *dev_id)
 			imx_rxint(irq, dev_id);
 	}
 
-	if (sts & USR1_TRDY &&
-			readl(sport->port.membase + UCR1) & UCR1_TXMPTYEN)
+	if ((sts & USR1_TRDY &&
+	     readl(sport->port.membase + UCR1) & UCR1_TXMPTYEN) ||
+	    (sts2 & USR2_TXDC &&
+	     readl(sport->port.membase + UCR4) & UCR4_TCEN))
 		imx_txint(irq, dev_id);
 
 	if (sts & USR1_RTSD)
@@ -679,7 +705,6 @@  static irqreturn_t imx_int(int irq, void *dev_id)
 	if (sts & USR1_AWAKE)
 		writel(USR1_AWAKE, sport->port.membase + USR1);
 
-	sts2 = readl(sport->port.membase + USR2);
 	if (sts2 & USR2_ORE) {
 		dev_err(sport->port.dev, "Rx FIFO overrun\n");
 		sport->port.icount.overrun++;
@@ -731,11 +756,13 @@  static void imx_set_mctrl(struct uart_port *port, unsigned int mctrl)
 	struct imx_port *sport = (struct imx_port *)port;
 	unsigned long temp;
 
-	temp = readl(sport->port.membase + UCR2) & ~(UCR2_CTS | UCR2_CTSC);
-	if (mctrl & TIOCM_RTS)
-		temp |= UCR2_CTS | UCR2_CTSC;
-
-	writel(temp, sport->port.membase + UCR2);
+	if (!(port->rs485.flags & SER_RS485_ENABLED)) {
+		temp = readl(sport->port.membase + UCR2);
+		temp &= ~(UCR2_CTS | UCR2_CTSC);
+		if (mctrl & TIOCM_RTS)
+			temp |= UCR2_CTS | UCR2_CTSC;
+		writel(temp, sport->port.membase + UCR2);
+	}
 
 	temp = readl(sport->port.membase + uts_reg(sport)) & ~UTS_LOOP;
 	if (mctrl & TIOCM_LOOP)
@@ -1029,7 +1056,7 @@  static int imx_startup(struct uart_port *port)
 	temp |= UCR1_RRDYEN | UCR1_RTSDEN | UCR1_UARTEN;
 	writel(temp, sport->port.membase + UCR1);
 
-	temp = read(sport->port.membase + UCR4);
+	temp = readl(sport->port.membase + UCR4);
 	temp |= UCR4_OREN;
 	writel(temp, sport->port.membase + UCR4);
 
@@ -1144,7 +1171,17 @@  imx_set_termios(struct uart_port *port, struct ktermios *termios,
 	if (termios->c_cflag & CRTSCTS) {
 		if (sport->have_rtscts) {
 			ucr2 &= ~UCR2_IRTS;
-			ucr2 |= UCR2_CTSC;
+
+			if (port->rs485.flags & SER_RS485_ENABLED)
+				/*
+				 * RTS is mandatory for rs485 operation, so keep
+				 * it under manual control and keep transmitter
+				 * disabled.
+				 */
+				ucr2 |= UCR2_CTS;
+			else
+				ucr2 |= UCR2_CTSC;
+
 
 			/* Can we enable the DMA support? */
 			if (is_imx6q_uart(sport) && !uart_console(port)
@@ -1153,7 +1190,9 @@  imx_set_termios(struct uart_port *port, struct ktermios *termios,
 		} else {
 			termios->c_cflag &= ~CRTSCTS;
 		}
-	}
+	} else if (port->rs485.flags & SER_RS485_ENABLED)
+		/* disable transmitter */
+		ucr2 |= UCR2_CTS;
 
 	if (termios->c_cflag & CSTOPB)
 		ucr2 |= UCR2_STPB;
@@ -1374,6 +1413,34 @@  static void imx_poll_put_char(struct uart_port *port, unsigned char c)
 }
 #endif
 
+static int imx_rs485_config(struct uart_port *port,
+			    struct serial_rs485 *rs485conf)
+{
+	struct imx_port *sport = (struct imx_port *)port;
+
+	/* unimplemented */
+	rs485conf->delay_rts_before_send = 0;
+	rs485conf->delay_rts_after_send = 0;
+
+	/* RTS is required to control the transmitter */
+	if (!sport->have_rtscts)
+		rs485conf->flags &= ~SER_RS485_ENABLED;
+
+	if (rs485conf->flags & SER_RS485_ENABLED) {
+		unsigned long temp;
+
+		/* disable transmitter */
+		temp = readl(sport->port.membase + UCR2);
+		temp &= ~UCR2_CTSC;
+		temp |= UCR2_CTS;
+		writel(temp, sport->port.membase + UCR2);
+	}
+
+	port->rs485 = *rs485conf;
+
+	return 0;
+}
+
 static struct uart_ops imx_pops = {
 	.tx_empty	= imx_tx_empty,
 	.set_mctrl	= imx_set_mctrl,
@@ -1731,6 +1798,7 @@  static int serial_imx_probe(struct platform_device *pdev)
 	sport->port.irq = rxirq;
 	sport->port.fifosize = 32;
 	sport->port.ops = &imx_pops;
+	sport->port.rs485_config = imx_rs485_config;
 	sport->port.flags = UPF_BOOT_AUTOCONF;
 	init_timer(&sport->timer);
 	sport->timer.function = imx_timeout;