[PATCH 13/20] i.MX: Add 'lpuart' serial driver

Andrey Smirnov andrew.smirnov at gmail.com
Tue Oct 4 06:56:58 PDT 2016


On Tue, Oct 4, 2016 at 12:13 AM, Sascha Hauer <s.hauer at pengutronix.de> wrote:
> On Mon, Oct 03, 2016 at 07:40:50AM -0700, Andrey Smirnov wrote:
>> Add 'lpuart' serial driver, based on analogous driver from U-Boot
>>
>> Signed-off-by: Andrey Smirnov <andrew.smirnov at gmail.com>
>> ---
>>  drivers/serial/Kconfig         |   4 +
>>  drivers/serial/Makefile        |   1 +
>>  drivers/serial/serial_lpuart.c | 217 +++++++++++++++++++++++++++++++++++++++++
>>  include/serial/lpuart.h        |  28 ++++--
>>  4 files changed, 244 insertions(+), 6 deletions(-)
>>  create mode 100644 drivers/serial/serial_lpuart.c
>>
>> diff --git a/drivers/serial/Kconfig b/drivers/serial/Kconfig
>> index 146bf1e..02e869a 100644
>> --- a/drivers/serial/Kconfig
>> +++ b/drivers/serial/Kconfig
>> @@ -137,4 +137,8 @@ config DRIVER_SERIAL_DIGIC
>>       bool "Canon DIGIC serial driver"
>>       depends on ARCH_DIGIC
>>
>> +config DRIVER_SERIAL_LPUART
>> +     depends on ARCH_IMX
>> +     bool "LPUART serial driver"
>> +
>>  endmenu
>> diff --git a/drivers/serial/Makefile b/drivers/serial/Makefile
>> index 189e777..7d1bae1 100644
>> --- a/drivers/serial/Makefile
>> +++ b/drivers/serial/Makefile
>> @@ -20,3 +20,4 @@ obj-$(CONFIG_DRIVER_SERIAL_AUART)           += serial_auart.o
>>  obj-$(CONFIG_DRIVER_SERIAL_CADENCE)          += serial_cadence.o
>>  obj-$(CONFIG_DRIVER_SERIAL_EFI_STDIO)                += efi-stdio.o
>>  obj-$(CONFIG_DRIVER_SERIAL_DIGIC)            += serial_digic.o
>> +obj-$(CONFIG_DRIVER_SERIAL_LPUART)           += serial_lpuart.o
>> diff --git a/drivers/serial/serial_lpuart.c b/drivers/serial/serial_lpuart.c
>> new file mode 100644
>> index 0000000..52fb6d3
>> --- /dev/null
>> +++ b/drivers/serial/serial_lpuart.c
>> @@ -0,0 +1,217 @@
>> +/*
>> + * Copyright (c) 2016 Zodiac Inflight Innovation
>> + * Author: Andrey Smirnov <andrew.smirnov at gmail.com>
>> + *
>> + * Based on analogous driver from U-Boot
>> + *
>> + * See file CREDITS for list of people who contributed to this
>> + * project.
>> + *
>> + * This program is free software; you can redistribute it and/or modify
>> + * it under the terms of the GNU General Public License version 2
>> + * as published by the Free Software Foundation.
>> + *
>> + * This program is distributed in the hope that 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.
>> + *
>> + */
>> +
>> +#include <common.h>
>> +#include <driver.h>
>> +#include <init.h>
>> +#include <malloc.h>
>> +#include <notifier.h>
>> +#include <io.h>
>> +#include <of.h>
>> +#include <linux/err.h>
>> +#include <linux/clk.h>
>> +#include <serial/lpuart.h>
>> +
>> +struct lpuart {
>> +     struct console_device cdev;
>> +     int baudrate;
>> +     int dte_mode;
>> +     struct notifier_block notify;
>> +     struct resource *io;
>> +     void __iomem *base;
>> +     struct clk *clk;
>> +};
>> +
>> +static struct lpuart *cdev_to_lpuart(struct console_device *cdev)
>> +{
>> +     return container_of(cdev, struct lpuart, cdev);
>> +}
>> +
>> +static struct lpuart *nb_to_lpuart(struct notifier_block *nb)
>> +{
>> +     return container_of(nb, struct lpuart, notify);
>> +}
>> +
>> +static void lpuart_enable(struct lpuart *lpuart, bool on)
>> +{
>> +     u8 ctrl;
>> +
>> +     ctrl = readb(lpuart->base + UARTCR2);
>> +     if (on)
>> +             ctrl |= UARTCR2_TE | UARTCR2_RE;
>> +     else
>> +             ctrl &= ~(UARTCR2_TE | UARTCR2_RE);
>> +     writeb(ctrl, lpuart->base + UARTCR2);
>> +}
>> +
>> +static int lpuart_serial_setbaudrate(struct console_device *cdev,
>> +                                  int baudrate)
>> +{
>> +     struct lpuart *lpuart = cdev_to_lpuart(cdev);
>> +
>> +     lpuart_enable(lpuart, false);
>> +
>> +     lpuart_setbrg(lpuart->base,
>> +                   clk_get_rate(lpuart->clk),
>> +                   baudrate);
>> +
>> +     lpuart_enable(lpuart, true);
>> +
>> +     lpuart->baudrate = baudrate;
>> +
>> +     return 0;
>> +}
>> +
>> +static int lpuart_serial_getc(struct console_device *cdev)
>> +{
>> +     bool ready;
>> +     struct lpuart *lpuart = cdev_to_lpuart(cdev);
>> +
>> +     do {
>> +             const u8 sr1 = readb(lpuart->base + UARTSR1);
>> +             ready = !!(sr1 & (UARTSR1_OR | UARTSR1_RDRF));
>> +     } while (!ready);
>> +
>> +     return readb(lpuart->base + UARTDR);
>> +}
>> +
>> +static void lpuart_serial_putc(struct console_device *cdev, char c)
>> +{
>> +     lpuart_putc(cdev_to_lpuart(cdev)->base, c);
>> +}
>> +
>> +/* Test whether a character is in the RX buffer */
>> +static int lpuart_serial_tstc(struct console_device *cdev)
>> +{
>> +     return !!readb(cdev_to_lpuart(cdev)->base + UARTRCFIFO);
>> +}
>> +
>> +static void lpuart_serial_flush(struct console_device *cdev)
>> +{
>> +     bool tx_empty;
>> +     struct lpuart *lpuart = cdev_to_lpuart(cdev);
>> +
>> +     do {
>> +             const u8 sr1 = readb(lpuart->base + UARTSR1);
>> +             tx_empty = !!(sr1 & UARTSR1_TDRE);
>> +     } while (!tx_empty);
>> +}
>> +
>> +static int lpuart_clocksource_clock_change(struct notifier_block *nb,
>> +                                        unsigned long event, void *data)
>> +{
>> +     struct lpuart *lpuart = nb_to_lpuart(nb);
>> +
>> +     return lpuart_serial_setbaudrate(&lpuart->cdev, lpuart->baudrate);
>> +}
>
> This doesn't make sense in this form. I introduced this code in the i.MX
> uart driver since I had the need to change PLL rates while the uart is
> active. When this happens I had to adjust the dividers for the new uart
> base clock. The code above doesn't react to base clock changes though,
> it takes the old rate stored in lpuart->baudrate.
>
> If you don't have to adjust PLL rates while the uart is active then I
> suggest that you just remove this code.

I am not sure I understand what you mean. I modeled this part of the
code after i.MX driver (serial_imx.c) and unless I missed something
(which I am not seeing) it should work exactly the same way.

That is: parent clock changes, this notifier gets called, it sets
configured baud rate again via lpuart_serial_setbaudrate, which in
turn sets dividers based off of value it gets from
clk_get_rate(lpuart->clk).

It does use old value in lpuart->baudrate, just as i.MX driver does,
since AFAIU the purpose of this callback is to make sure that UART
operates at the originally configured baudrate despite the clock rate
change.

I feel like I am missing something, although it is 7AM where I am now,
so I wouldn't be surprised if I am :-)

>
>> @@ -225,25 +225,35 @@ static inline void lpuart_setbrg(void __iomem *base,
>>                                unsigned int refclock,
>>                                unsigned int baudrate)
>>  {
>> +     unsigned int bfra;
>>       u16 sbr;
>> +
>>       sbr = (u16) (refclock / (16 * baudrate));
>>
>>       writeb(sbr >> 8,   base + UARTBDH);
>>       writeb(sbr & 0xff, base + UARTBDL);
>> +
>> +     bfra  = DIV_ROUND_UP(2 * refclock, baudrate) - 32 * sbr;
>> +     bfra &= UARTCR4_BRFA_MASK;
>> +     writeb(bfra, base + UARTCR4);
>>  }
>>
>> -static inline void lpuart_setup(void __iomem *base,
>> -                             unsigned int refclock)
>> +static inline void lpuart_setup_with_fifo(void __iomem *base,
>> +                                       unsigned int refclock,
>> +                                       unsigned int twfifo)
>>  {
>> -
>>       /* Disable UART */
>>       writeb(0, base + UARTCR2);
>>       writeb(0, base + UARTMODEM);
>>       writeb(0, base + UARTCR1);
>>
>> -     /* Disable FIFOs */
>> -     writeb(0, base + UARTPFIFO);
>> -     writeb(0, base + UARTTWFIFO);
>> +     if (twfifo) {
>> +             writeb(UARTPFIFO_TXFE | UARTPFIFO_RXFE, base + UARTPFIFO);
>> +             writeb((u8)twfifo, base + UARTTWFIFO);
>> +     } else {
>> +             writeb(0, base + UARTPFIFO);
>> +             writeb(0, base + UARTTWFIFO);
>> +     }
>>       writeb(1, base + UARTRWFIFO);
>>       writeb(UARTCFIFO_RXFLUSH | UARTCFIFO_TXFLUSH, base + UARTCFIFO);
>>
>> @@ -252,6 +262,12 @@ static inline void lpuart_setup(void __iomem *base,
>>       writeb(UARTCR2_TE | UARTCR2_RE, base + UARTCR2);
>>  }
>>
>> +static inline void lpuart_setup(void __iomem *base,
>> +                             unsigned int refclock)
>> +{
>> +     lpuart_setup_with_fifo(base, refclock, 0x00);
>> +}
>> +
>>  static inline void lpuart_putc(void __iomem *base, int c)
>>  {
>>       if (!(readb(base + UARTCR2) & UARTCR2_TE))
>
> This was introduced earlier with this series. No need to change it, just
> create it correctly in the first place.

OK, will fix in v2.

Thanks,
Andrey



More information about the barebox mailing list