From: Petar Dimitrijevic <[email protected]> Solves issue of not being able to change baud rate when sunxi UART is receiving data.
This is a well known issue with DesignWare UART and the specific revision of their IP. If the device is configured with UART_16550_COMPATIBLE=NO or in versions prior to the introduction of this option, the UART will ignore writes to the LCR if the UART is busy. This means that communication parameters: baud rate, stop bits, etc., can't be set when the device is receiving data. This patch solves the issue by verifying the data written to the LCR register. If the write failes FIFO buffers are cleared and the operation is retried. Signed-off-by: Petar Dimitrijevic <[email protected]> --- drivers/tty/serial/8250/8250_sunxi.c | 96 +++++++++++++++++++++++++++--------- 1 file changed, 74 insertions(+), 22 deletions(-) diff --git a/drivers/tty/serial/8250/8250_sunxi.c b/drivers/tty/serial/8250/8250_sunxi.c index 25fdcff..bc58957 100644 --- a/drivers/tty/serial/8250/8250_sunxi.c +++ b/drivers/tty/serial/8250/8250_sunxi.c @@ -32,9 +32,9 @@ #include <linux/device.h> #include <linux/init.h> -#include <asm/io.h> +#include <linux/io.h> #include <asm/ecard.h> -#include <asm/string.h> +#include <linux/string.h> #include <linux/clk.h> #include <plat/system.h> @@ -57,7 +57,6 @@ struct sw_serial_port { int port_no; int line; - int last_lcr; u32 pio_hdle; struct clk *clk; u32 sclk; @@ -128,41 +127,83 @@ static int sw_serial_put_resource(struct sw_serial_port *sport) return 0; } +/* + * Copied from mainline kernel 4.2. Used by sw_force_idle. + */ +static inline struct uart_8250_port *up_to_u8250p(struct uart_port *up) +{ + return container_of(up, struct uart_8250_port, port); +} + +/* + * Same function exists in 8250.c but it's not exposed in this versions of + * the kernel. Cleanest solution is to copy it completely. + */ +static void serial8250_clear_fifos(struct uart_8250_port *p) +{ + if (p->capabilities & UART_CAP_FIFO) { + serial_out(p, UART_FCR, UART_FCR_ENABLE_FIFO); + serial_out(p, UART_FCR, UART_FCR_ENABLE_FIFO | + UART_FCR_CLEAR_RCVR | UART_FCR_CLEAR_XMIT); + serial_out(p, UART_FCR, 0); + } +} + +/* + * This function is introduced in later kernel but it doesn't exist in 3.4. + */ +void serial8250_clear_and_reinit_fifos(struct uart_8250_port *p) +{ + serial8250_clear_fifos(p); + /* 8250.c PORT_16550A */ + serial_out(p, UART_FCR, UART_FCR_ENABLE_FIFO | UART_FCR_R_TRIG_10); +} + +static void sw_force_idle(struct uart_port *p) +{ + struct uart_8250_port *up = up_to_u8250p(p); + serial8250_clear_and_reinit_fifos(up); + (void)p->serial_in(p, UART_RX); +} + static void sw_serial_out32(struct uart_port *p, int offset, int value) { struct sw_serial_port *d = p->private_data; - if (offset == UART_LCR) - d->last_lcr = value; + writel(value, p->membase + (offset << p->regshift)); - offset <<= p->regshift; - writel(value, p->membase + offset); + if (offset == UART_LCR) { + int tries = 1000; + while (tries--) { + unsigned int lcr = p->serial_in(p, UART_LCR); + + if ((value & ~UART_LCR_SPAR) == (lcr & ~UART_LCR_SPAR)) + return; + + sw_force_idle(p); + writel(value, p->membase + (UART_LCR << p->regshift)); + } + + pr_err("SUNXI SERIAL: Force reset. Port: %d, Failed after %d tries.\n", + d->port_no, 1000); + } } static unsigned int sw_serial_in32(struct uart_port *p, int offset) { - offset <<= p->regshift; - - return readl(p->membase + offset); + return readl(p->membase + (offset << p->regshift)); } static int sw_serial_handle_irq(struct uart_port *p) { - struct sw_serial_port *d = p->private_data; unsigned int iir = p->serial_in(p, UART_IIR); if (serial8250_handle_irq(p, iir)) { return 1; } else if ((iir & UART_IIR_BUSY) == UART_IIR_BUSY) { - if (p->serial_in(p, UART_USR) & 1) { - p->serial_out(p, UART_HALT, UART_FORCE_CFG); - p->serial_out(p, UART_LCR, d->last_lcr); - p->serial_out(p, UART_HALT, UART_FORCE_CFG | UART_FORCE_UPDATE); - while(p->serial_in(p, UART_HALT) & UART_FORCE_UPDATE); - p->serial_out(p, UART_HALT, 0x00); - p->serial_in(p, UART_USR); - } else - p->serial_out(p, UART_LCR, d->last_lcr); + /* Clear the USR */ + (void)p->serial_in(p, UART_USR); + return 1; } @@ -187,8 +228,10 @@ static int __devinit sw_serial_probe(struct platform_device *dev) int ret; sport = kzalloc(sizeof(struct sw_serial_port), GFP_KERNEL); + if (!sport) return -ENOMEM; + sport->port_no = dev->id; sport->pdev = dev; @@ -222,7 +265,7 @@ static int __devinit sw_serial_probe(struct platform_device *dev) platform_set_drvdata(dev, sport); return 0; - free_dev: +free_dev: kfree(sport); sport = NULL; return ret; @@ -255,6 +298,7 @@ static struct platform_driver sw_serial_driver = { {.start = MEM_BASE, .end = MEM_BASE + UART_BASE_OS - 1, .flags = IORESOURCE_MEM}, \ {.start = IRQ, .end = IRQ, .flags = IORESOURCE_IRQ}, \ } + static struct resource sw_uart_res[8][2] = { RES(UARTx_BASE(0), SW_INT_IRQNO_UART0), RES(UARTx_BASE(1), SW_INT_IRQNO_UART1), @@ -330,6 +374,7 @@ static int sw_serial_get_max_ports(void) } static unsigned uart_used; + static int __init sw_serial_init(void) { int ret; @@ -341,11 +386,16 @@ static int __init sw_serial_init(void) for (i = 0; i < max; i++, used = 0) { if (sunxi_is_a13() && i == 2) /* No uart2 on a13 */ continue; + sprintf(uart_para, "uart_para%d", i); - ret = script_parser_fetch(uart_para, "uart_used", &used, sizeof(int)); + ret = script_parser_fetch(uart_para, "uart_used", &used, + sizeof(int)); + if (ret) pr_err("failed to get uart%d's used information\n", i); + pr_debug("uart:%d used:%d\n", i, used); + if (used) { uart_used |= 1 << i; platform_device_register(&sw_uart_dev[i]); @@ -374,6 +424,8 @@ static void __exit sw_serial_exit(void) } } + + MODULE_AUTHOR("Aaron.myeh<[email protected]>"); MODULE_DESCRIPTION("SUNXI 8250-compatible serial port expansion card driver"); MODULE_LICENSE("GPL"); -- 2.6.2 -- You received this message because you are subscribed to the Google Groups "linux-sunxi" group. To unsubscribe from this group and stop receiving emails from it, send an email to [email protected]. For more options, visit https://groups.google.com/d/optout.
