diff options
Diffstat (limited to 'drivers/tty/serial/amba-pl011.c')
-rw-r--r-- | drivers/tty/serial/amba-pl011.c | 357 |
1 files changed, 230 insertions, 127 deletions
diff --git a/drivers/tty/serial/amba-pl011.c b/drivers/tty/serial/amba-pl011.c index 2fa3fb30dc6c..22939841b1de 100644 --- a/drivers/tty/serial/amba-pl011.c +++ b/drivers/tty/serial/amba-pl011.c @@ -248,6 +248,13 @@ struct pl011_dmatx_data { bool queued; }; +enum pl011_rs485_tx_state { + OFF, + WAIT_AFTER_RTS, + SEND, + WAIT_AFTER_SEND, +}; + /* * We wrap our port structure around the generic uart_port. */ @@ -256,16 +263,19 @@ struct uart_amba_port { const u16 *reg_offset; struct clk *clk; const struct vendor_data *vendor; - unsigned int dmacr; /* dma control reg */ unsigned int im; /* interrupt mask */ unsigned int old_status; unsigned int fifosize; /* vendor-specific */ unsigned int fixed_baud; /* vendor-set fixed baud rate */ char type[12]; - bool rs485_tx_started; - unsigned int rs485_tx_drain_interval; /* usecs */ + ktime_t rs485_tx_drain_interval; /* nano */ + enum pl011_rs485_tx_state rs485_tx_state; + struct hrtimer trigger_start_tx; + struct hrtimer trigger_stop_tx; + bool console_line_ended; #ifdef CONFIG_DMA_ENGINE /* DMA stuff */ + unsigned int dmacr; /* dma control reg */ bool using_tx_dma; bool using_rx_dma; struct pl011_dmarx_data dmarx; @@ -535,6 +545,7 @@ static void pl011_start_tx_pio(struct uart_amba_port *uap); static void pl011_dma_tx_callback(void *data) { struct uart_amba_port *uap = data; + struct tty_port *tport = &uap->port.state->port; struct pl011_dmatx_data *dmatx = &uap->dmatx; unsigned long flags; u16 dmacr; @@ -558,7 +569,7 @@ static void pl011_dma_tx_callback(void *data) * get further refills (hence we check dmacr). */ if (!(dmacr & UART011_TXDMAE) || uart_tx_stopped(&uap->port) || - uart_circ_empty(&uap->port.state->xmit)) { + kfifo_is_empty(&tport->xmit_fifo)) { uap->dmatx.queued = false; uart_port_unlock_irqrestore(&uap->port, flags); return; @@ -588,7 +599,7 @@ static int pl011_dma_tx_refill(struct uart_amba_port *uap) struct dma_chan *chan = dmatx->chan; struct dma_device *dma_dev = chan->device; struct dma_async_tx_descriptor *desc; - struct circ_buf *xmit = &uap->port.state->xmit; + struct tty_port *tport = &uap->port.state->port; unsigned int count; /* @@ -597,7 +608,7 @@ static int pl011_dma_tx_refill(struct uart_amba_port *uap) * the standard interrupt handling. This ensures that we * issue a uart_write_wakeup() at the appropriate time. */ - count = uart_circ_chars_pending(xmit); + count = kfifo_len(&tport->xmit_fifo); if (count < (uap->fifosize >> 1)) { uap->dmatx.queued = false; return 0; @@ -613,21 +624,7 @@ static int pl011_dma_tx_refill(struct uart_amba_port *uap) if (count > PL011_DMA_BUFFER_SIZE) count = PL011_DMA_BUFFER_SIZE; - if (xmit->tail < xmit->head) { - memcpy(&dmatx->buf[0], &xmit->buf[xmit->tail], count); - } else { - size_t first = UART_XMIT_SIZE - xmit->tail; - size_t second; - - if (first > count) - first = count; - second = count - first; - - memcpy(&dmatx->buf[0], &xmit->buf[xmit->tail], first); - if (second) - memcpy(&dmatx->buf[first], &xmit->buf[0], second); - } - + count = kfifo_out_peek(&tport->xmit_fifo, dmatx->buf, count); dmatx->len = count; dmatx->dma = dma_map_single(dma_dev->dev, dmatx->buf, count, DMA_TO_DEVICE); @@ -670,7 +667,7 @@ static int pl011_dma_tx_refill(struct uart_amba_port *uap) */ uart_xmit_advance(&uap->port, count); - if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS) + if (kfifo_len(&tport->xmit_fifo) < WAKEUP_CHARS) uart_write_wakeup(&uap->port); return 1; @@ -1049,7 +1046,7 @@ static inline void pl011_dma_rx_stop(struct uart_amba_port *uap) */ static void pl011_dma_rx_poll(struct timer_list *t) { - struct uart_amba_port *uap = from_timer(uap, t, dmarx.timer); + struct uart_amba_port *uap = timer_container_of(uap, t, dmarx.timer); struct tty_port *port = &uap->port.state->port; struct pl011_dmarx_data *dmarx = &uap->dmarx; struct dma_chan *rxchan = uap->dmarx.chan; @@ -1087,7 +1084,7 @@ static void pl011_dma_rx_poll(struct timer_list *t) uap->dmarx.running = false; dmaengine_terminate_all(rxchan); - del_timer(&uap->dmarx.timer); + timer_delete(&uap->dmarx.timer); } else { mod_timer(&uap->dmarx.timer, jiffies + msecs_to_jiffies(uap->dmarx.poll_rate)); @@ -1202,7 +1199,7 @@ static void pl011_dma_shutdown(struct uart_amba_port *uap) pl011_dmabuf_free(uap->dmarx.chan, &uap->dmarx.dbuf_a, DMA_FROM_DEVICE); pl011_dmabuf_free(uap->dmarx.chan, &uap->dmarx.dbuf_b, DMA_FROM_DEVICE); if (uap->dmarx.poll_rate) - del_timer_sync(&uap->dmarx.timer); + timer_delete_sync(&uap->dmarx.timer); uap->using_rx_dma = false; } } @@ -1273,30 +1270,31 @@ static inline bool pl011_dma_rx_running(struct uart_amba_port *uap) static void pl011_rs485_tx_stop(struct uart_amba_port *uap) { - /* - * To be on the safe side only time out after twice as many iterations - * as fifo size. - */ - const int MAX_TX_DRAIN_ITERS = uap->port.fifosize * 2; struct uart_port *port = &uap->port; - int i = 0; u32 cr; - /* Wait until hardware tx queue is empty */ - while (!pl011_tx_empty(port)) { - if (i > MAX_TX_DRAIN_ITERS) { - dev_warn(port->dev, - "timeout while draining hardware tx queue\n"); - break; - } + if (uap->rs485_tx_state == SEND) + uap->rs485_tx_state = WAIT_AFTER_SEND; - udelay(uap->rs485_tx_drain_interval); - i++; + if (uap->rs485_tx_state == WAIT_AFTER_SEND) { + /* Schedule hrtimer if tx queue not empty */ + if (!pl011_tx_empty(port)) { + hrtimer_start(&uap->trigger_stop_tx, + uap->rs485_tx_drain_interval, + HRTIMER_MODE_REL); + return; + } + if (port->rs485.delay_rts_after_send > 0) { + hrtimer_start(&uap->trigger_stop_tx, + ms_to_ktime(port->rs485.delay_rts_after_send), + HRTIMER_MODE_REL); + return; + } + /* Continue without any delay */ + } else if (uap->rs485_tx_state == WAIT_AFTER_RTS) { + hrtimer_try_to_cancel(&uap->trigger_start_tx); } - if (port->rs485.delay_rts_after_send) - mdelay(port->rs485.delay_rts_after_send); - cr = pl011_read(uap, REG_CR); if (port->rs485.flags & SER_RS485_RTS_AFTER_SEND) @@ -1309,7 +1307,7 @@ static void pl011_rs485_tx_stop(struct uart_amba_port *uap) cr |= UART011_CR_RXE; pl011_write(cr, uap, REG_CR); - uap->rs485_tx_started = false; + uap->rs485_tx_state = OFF; } static void pl011_stop_tx(struct uart_port *port) @@ -1317,11 +1315,18 @@ static void pl011_stop_tx(struct uart_port *port) struct uart_amba_port *uap = container_of(port, struct uart_amba_port, port); + if (port->rs485.flags & SER_RS485_ENABLED && + uap->rs485_tx_state == WAIT_AFTER_RTS) { + pl011_rs485_tx_stop(uap); + return; + } + uap->im &= ~UART011_TXIM; pl011_write(uap->im, uap, REG_IMSC); pl011_dma_tx_stop(uap); - if ((port->rs485.flags & SER_RS485_ENABLED) && uap->rs485_tx_started) + if (port->rs485.flags & SER_RS485_ENABLED && + uap->rs485_tx_state != OFF) pl011_rs485_tx_stop(uap); } @@ -1341,10 +1346,19 @@ static void pl011_rs485_tx_start(struct uart_amba_port *uap) struct uart_port *port = &uap->port; u32 cr; + if (uap->rs485_tx_state == WAIT_AFTER_RTS) { + uap->rs485_tx_state = SEND; + return; + } + if (uap->rs485_tx_state == WAIT_AFTER_SEND) { + hrtimer_try_to_cancel(&uap->trigger_stop_tx); + uap->rs485_tx_state = SEND; + return; + } + /* uap->rs485_tx_state == OFF */ /* Enable transmitter */ cr = pl011_read(uap, REG_CR); cr |= UART011_CR_TXE; - /* Disable receiver if half-duplex */ if (!(port->rs485.flags & SER_RS485_RX_DURING_TX)) cr &= ~UART011_CR_RXE; @@ -1356,10 +1370,14 @@ static void pl011_rs485_tx_start(struct uart_amba_port *uap) pl011_write(cr, uap, REG_CR); - if (port->rs485.delay_rts_before_send) - mdelay(port->rs485.delay_rts_before_send); - - uap->rs485_tx_started = true; + if (port->rs485.delay_rts_before_send > 0) { + uap->rs485_tx_state = WAIT_AFTER_RTS; + hrtimer_start(&uap->trigger_start_tx, + ms_to_ktime(port->rs485.delay_rts_before_send), + HRTIMER_MODE_REL); + } else { + uap->rs485_tx_state = SEND; + } } static void pl011_start_tx(struct uart_port *port) @@ -1368,13 +1386,44 @@ static void pl011_start_tx(struct uart_port *port) container_of(port, struct uart_amba_port, port); if ((uap->port.rs485.flags & SER_RS485_ENABLED) && - !uap->rs485_tx_started) + uap->rs485_tx_state != SEND) { pl011_rs485_tx_start(uap); + if (uap->rs485_tx_state == WAIT_AFTER_RTS) + return; + } if (!pl011_dma_tx_start(uap)) pl011_start_tx_pio(uap); } +static enum hrtimer_restart pl011_trigger_start_tx(struct hrtimer *t) +{ + struct uart_amba_port *uap = + container_of(t, struct uart_amba_port, trigger_start_tx); + unsigned long flags; + + uart_port_lock_irqsave(&uap->port, &flags); + if (uap->rs485_tx_state == WAIT_AFTER_RTS) + pl011_start_tx(&uap->port); + uart_port_unlock_irqrestore(&uap->port, flags); + + return HRTIMER_NORESTART; +} + +static enum hrtimer_restart pl011_trigger_stop_tx(struct hrtimer *t) +{ + struct uart_amba_port *uap = + container_of(t, struct uart_amba_port, trigger_stop_tx); + unsigned long flags; + + uart_port_lock_irqsave(&uap->port, &flags); + if (uap->rs485_tx_state == WAIT_AFTER_SEND) + pl011_rs485_tx_stop(uap); + uart_port_unlock_irqrestore(&uap->port, flags); + + return HRTIMER_NORESTART; +} + static void pl011_stop_rx(struct uart_port *port) { struct uart_amba_port *uap = @@ -1454,7 +1503,7 @@ static bool pl011_tx_char(struct uart_amba_port *uap, unsigned char c, /* Returns true if tx interrupts have to be (kept) enabled */ static bool pl011_tx_chars(struct uart_amba_port *uap, bool from_irq) { - struct circ_buf *xmit = &uap->port.state->xmit; + struct tty_port *tport = &uap->port.state->port; int count = uap->fifosize >> 1; if (uap->port.x_char) { @@ -1463,7 +1512,7 @@ static bool pl011_tx_chars(struct uart_amba_port *uap, bool from_irq) uap->port.x_char = 0; --count; } - if (uart_circ_empty(xmit) || uart_tx_stopped(&uap->port)) { + if (kfifo_is_empty(&tport->xmit_fifo) || uart_tx_stopped(&uap->port)) { pl011_stop_tx(&uap->port); return false; } @@ -1472,20 +1521,25 @@ static bool pl011_tx_chars(struct uart_amba_port *uap, bool from_irq) if (pl011_dma_tx_irq(uap)) return true; - do { + while (1) { + unsigned char c; + if (likely(from_irq) && count-- == 0) break; - if (!pl011_tx_char(uap, xmit->buf[xmit->tail], from_irq)) + if (!kfifo_peek(&tport->xmit_fifo, &c)) break; - xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1); - } while (!uart_circ_empty(xmit)); + if (!pl011_tx_char(uap, c, from_irq)) + break; + + kfifo_skip(&tport->xmit_fifo); + } - if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS) + if (kfifo_len(&tport->xmit_fifo) < WAKEUP_CHARS) uart_write_wakeup(&uap->port); - if (uart_circ_empty(xmit)) { + if (kfifo_is_empty(&tport->xmit_fifo)) { pl011_stop_tx(&uap->port); return false; } @@ -1827,6 +1881,13 @@ static void pl011_unthrottle_rx(struct uart_port *port) pl011_write(uap->im, uap, REG_IMSC); +#ifdef CONFIG_DMA_ENGINE + if (uap->using_rx_dma) { + uap->dmacr |= UART011_RXDMAE; + pl011_write(uap->dmacr, uap, REG_DMACR); + } +#endif + uart_port_unlock_irqrestore(&uap->port, flags); } @@ -1954,7 +2015,7 @@ static void pl011_shutdown(struct uart_port *port) pl011_dma_shutdown(uap); - if ((port->rs485.flags & SER_RS485_ENABLED) && uap->rs485_tx_started) + if ((port->rs485.flags & SER_RS485_ENABLED && uap->rs485_tx_state != OFF)) pl011_rs485_tx_stop(uap); free_irq(uap->port.irq, uap); @@ -2099,7 +2160,7 @@ pl011_set_termios(struct uart_port *port, struct ktermios *termios, * with the given baud rate. We use this as the poll interval when we * wait for the tx queue to empty. */ - uap->rs485_tx_drain_interval = DIV_ROUND_UP(bits * 1000 * 1000, baud); + uap->rs485_tx_drain_interval = ns_to_ktime(DIV_ROUND_UP(bits * NSEC_PER_SEC, baud)); pl011_setup_status_masks(port, termios); @@ -2210,7 +2271,7 @@ static int pl011_verify_port(struct uart_port *port, struct serial_struct *ser) if (ser->type != PORT_UNKNOWN && ser->type != PORT_AMBA) ret = -EINVAL; - if (ser->irq < 0 || ser->irq >= nr_irqs) + if (ser->irq < 0 || ser->irq >= irq_get_nr_irqs()) ret = -EINVAL; if (ser->baud_base < 9600) ret = -EINVAL; @@ -2306,50 +2367,7 @@ static void pl011_console_putchar(struct uart_port *port, unsigned char ch) while (pl011_read(uap, REG_FR) & UART01x_FR_TXFF) cpu_relax(); pl011_write(ch, uap, REG_DR); -} - -static void -pl011_console_write(struct console *co, const char *s, unsigned int count) -{ - struct uart_amba_port *uap = amba_ports[co->index]; - unsigned int old_cr = 0, new_cr; - unsigned long flags; - int locked = 1; - - clk_enable(uap->clk); - - if (oops_in_progress) - locked = uart_port_trylock_irqsave(&uap->port, &flags); - else - uart_port_lock_irqsave(&uap->port, &flags); - - /* - * First save the CR then disable the interrupts - */ - if (!uap->vendor->always_enabled) { - old_cr = pl011_read(uap, REG_CR); - new_cr = old_cr & ~UART011_CR_CTSEN; - new_cr |= UART01x_CR_UARTEN | UART011_CR_TXE; - pl011_write(new_cr, uap, REG_CR); - } - - uart_console_write(&uap->port, s, count, pl011_console_putchar); - - /* - * Finally, wait for transmitter to become empty and restore the - * TCR. Allow feature register bits to be inverted to work around - * errata. - */ - while ((pl011_read(uap, REG_FR) ^ uap->vendor->inv_fr) - & uap->vendor->fr_busy) - cpu_relax(); - if (!uap->vendor->always_enabled) - pl011_write(old_cr, uap, REG_CR); - - if (locked) - uart_port_unlock_irqrestore(&uap->port, flags); - - clk_disable(uap->clk); + uap->console_line_ended = (ch == '\n'); } static void pl011_console_get_options(struct uart_amba_port *uap, int *baud, @@ -2412,6 +2430,8 @@ static int pl011_console_setup(struct console *co, char *options) if (ret) return ret; + uap->console_line_ended = true; + if (dev_get_platdata(uap->port.dev)) { struct amba_pl011_data *plat; @@ -2456,7 +2476,7 @@ static int pl011_console_setup(struct console *co, char *options) static int pl011_console_match(struct console *co, char *name, int idx, char *options) { - unsigned char iotype; + enum uart_iotype iotype; resource_size_t addr; int i; @@ -2488,21 +2508,112 @@ static int pl011_console_match(struct console *co, char *name, int idx, continue; co->index = i; - port->cons = co; + uart_port_set_cons(port, co); return pl011_console_setup(co, options); } return -ENODEV; } +static void +pl011_console_write_atomic(struct console *co, struct nbcon_write_context *wctxt) +{ + struct uart_amba_port *uap = amba_ports[co->index]; + unsigned int old_cr = 0; + + if (!nbcon_enter_unsafe(wctxt)) + return; + + clk_enable(uap->clk); + + if (!uap->vendor->always_enabled) { + old_cr = pl011_read(uap, REG_CR); + pl011_write((old_cr & ~UART011_CR_CTSEN) | (UART01x_CR_UARTEN | UART011_CR_TXE), + uap, REG_CR); + } + + if (!uap->console_line_ended) + uart_console_write(&uap->port, "\n", 1, pl011_console_putchar); + uart_console_write(&uap->port, wctxt->outbuf, wctxt->len, pl011_console_putchar); + + while ((pl011_read(uap, REG_FR) ^ uap->vendor->inv_fr) & uap->vendor->fr_busy) + cpu_relax(); + + if (!uap->vendor->always_enabled) + pl011_write(old_cr, uap, REG_CR); + + clk_disable(uap->clk); + + nbcon_exit_unsafe(wctxt); +} + +static void +pl011_console_write_thread(struct console *co, struct nbcon_write_context *wctxt) +{ + struct uart_amba_port *uap = amba_ports[co->index]; + unsigned int old_cr = 0; + + if (!nbcon_enter_unsafe(wctxt)) + return; + + clk_enable(uap->clk); + + if (!uap->vendor->always_enabled) { + old_cr = pl011_read(uap, REG_CR); + pl011_write((old_cr & ~UART011_CR_CTSEN) | (UART01x_CR_UARTEN | UART011_CR_TXE), + uap, REG_CR); + } + + if (nbcon_exit_unsafe(wctxt)) { + int i; + unsigned int len = READ_ONCE(wctxt->len); + + for (i = 0; i < len; i++) { + if (!nbcon_enter_unsafe(wctxt)) + break; + uart_console_write(&uap->port, wctxt->outbuf + i, 1, pl011_console_putchar); + if (!nbcon_exit_unsafe(wctxt)) + break; + } + } + + while (!nbcon_enter_unsafe(wctxt)) + nbcon_reacquire_nobuf(wctxt); + + while ((pl011_read(uap, REG_FR) ^ uap->vendor->inv_fr) & uap->vendor->fr_busy) + cpu_relax(); + + if (!uap->vendor->always_enabled) + pl011_write(old_cr, uap, REG_CR); + + clk_disable(uap->clk); + + nbcon_exit_unsafe(wctxt); +} + +static void +pl011_console_device_lock(struct console *co, unsigned long *flags) +{ + __uart_port_lock_irqsave(&amba_ports[co->index]->port, flags); +} + +static void +pl011_console_device_unlock(struct console *co, unsigned long flags) +{ + __uart_port_unlock_irqrestore(&amba_ports[co->index]->port, flags); +} + static struct uart_driver amba_reg; static struct console amba_console = { .name = "ttyAMA", - .write = pl011_console_write, .device = uart_console_device, .setup = pl011_console_setup, .match = pl011_console_match, - .flags = CON_PRINTBUFFER | CON_ANYTIME, + .write_atomic = pl011_console_write_atomic, + .write_thread = pl011_console_write_thread, + .device_lock = pl011_console_device_lock, + .device_unlock = pl011_console_device_unlock, + .flags = CON_PRINTBUFFER | CON_ANYTIME | CON_NBCON, .index = -1, .data = &amba_reg, }; @@ -2700,18 +2811,6 @@ static int pl011_find_free_port(void) return -EBUSY; } -static int pl011_get_rs485_mode(struct uart_amba_port *uap) -{ - struct uart_port *port = &uap->port; - int ret; - - ret = uart_get_rs485_mode(port); - if (ret) - return ret; - - return 0; -} - static int pl011_setup_port(struct device *dev, struct uart_amba_port *uap, struct resource *mmiobase, int index) { @@ -2732,7 +2831,7 @@ static int pl011_setup_port(struct device *dev, struct uart_amba_port *uap, uap->port.flags = UPF_BOOT_AUTOCONF; uap->port.line = index; - ret = pl011_get_rs485_mode(uap); + ret = uart_get_rs485_mode(&uap->port); if (ret) return ret; @@ -2819,6 +2918,10 @@ static int pl011_probe(struct amba_device *dev, const struct amba_id *id) return -EINVAL; } } + hrtimer_setup(&uap->trigger_start_tx, pl011_trigger_start_tx, CLOCK_MONOTONIC, + HRTIMER_MODE_REL); + hrtimer_setup(&uap->trigger_stop_tx, pl011_trigger_stop_tx, CLOCK_MONOTONIC, + HRTIMER_MODE_REL); ret = pl011_setup_port(&dev->dev, uap, &dev->res, portnr); if (ret) @@ -2948,7 +3051,7 @@ static const struct of_device_id sbsa_uart_of_match[] = { }; MODULE_DEVICE_TABLE(of, sbsa_uart_of_match); -static const struct acpi_device_id __maybe_unused sbsa_uart_acpi_match[] = { +static const struct acpi_device_id sbsa_uart_acpi_match[] = { { "ARMH0011", 0 }, { "ARMHB000", 0 }, {}, @@ -2957,12 +3060,12 @@ MODULE_DEVICE_TABLE(acpi, sbsa_uart_acpi_match); static struct platform_driver arm_sbsa_uart_platform_driver = { .probe = sbsa_uart_probe, - .remove_new = sbsa_uart_remove, + .remove = sbsa_uart_remove, .driver = { .name = "sbsa-uart", .pm = &pl011_dev_pm_ops, - .of_match_table = of_match_ptr(sbsa_uart_of_match), - .acpi_match_table = ACPI_PTR(sbsa_uart_acpi_match), + .of_match_table = sbsa_uart_of_match, + .acpi_match_table = sbsa_uart_acpi_match, .suppress_bind_attrs = IS_BUILTIN(CONFIG_SERIAL_AMBA_PL011), }, }; |