Add support for using DMA to avoid generating one interrupt per character and losing characters while copy-paste. In UART mode, the DMA capability can be used only if the UART Tx/Rx buffers are configured as FIFOs. If the DMA related properties are missing from the device tree, the driver will fall back to interrupt + Buffer mode. On the RX side, a timer is used to periodically poll for received data.
Signed-off-by: Larisa Grigore <[email protected]> Co-developed-by: Stoica Cosmin-Stefan <[email protected]> Signed-off-by: Stoica Cosmin-Stefan <[email protected]> Co-developed-by: Radu Pirea <[email protected]> Signed-off-by: Radu Pirea <[email protected]> Co-developed-by: Phu Luu An <[email protected]> Signed-off-by: Phu Luu An <[email protected]> Co-developed-by: Js Ha <[email protected]> Signed-off-by: Js Ha <[email protected]> Co-developed-by: Ghennadi Procopciuc <[email protected]> Signed-off-by: Ghennadi Procopciuc <[email protected]> --- drivers/tty/serial/fsl_linflexuart.c | 642 +++++++++++++++++++++++++-- 1 file changed, 597 insertions(+), 45 deletions(-) diff --git a/drivers/tty/serial/fsl_linflexuart.c b/drivers/tty/serial/fsl_linflexuart.c index a5a34fd81bcf..dff37c68cff0 100644 --- a/drivers/tty/serial/fsl_linflexuart.c +++ b/drivers/tty/serial/fsl_linflexuart.c @@ -3,19 +3,24 @@ * Freescale LINFlexD UART serial port driver * * Copyright 2012-2016 Freescale Semiconductor, Inc. - * Copyright 2017-2019, 2021-2022 NXP + * Copyright 2017-2019, 2021-2022, 2025 NXP */ #include <linux/clk.h> #include <linux/console.h> +#include <linux/dma-mapping.h> +#include <linux/dmaengine.h> +#include <linux/dmapool.h> #include <linux/io.h> #include <linux/irq.h> #include <linux/module.h> #include <linux/of.h> +#include <linux/of_dma.h> #include <linux/platform_device.h> #include <linux/serial_core.h> #include <linux/slab.h> #include <linux/tty_flip.h> +#include <linux/jiffies.h> #include <linux/delay.h> /* All registers are 32-bit width */ @@ -42,6 +47,12 @@ #define GCR 0x004C /* Global control register */ #define UARTPTO 0x0050 /* UART preset timeout register */ #define UARTCTO 0x0054 /* UART current timeout register */ +/* The offsets for DMARXE/DMATXE in master mode only */ +#define DMATXE 0x0058 /* DMA Tx enable register */ +#define DMARXE 0x005C /* DMA Rx enable register */ + +#define DMATXE_DRE0 BIT(0) +#define DMARXE_DRE0 BIT(0) /* * Register field definitions @@ -140,6 +151,9 @@ #define PREINIT_DELAY 2000 /* us */ +#define FSL_UART_RX_DMA_BUFFER_SIZE (PAGE_SIZE) +#define LINFLEXD_UARTCR_FIFO_SIZE (4) + enum linflex_clk { LINFLEX_CLK_LIN, LINFLEX_CLK_IPG, @@ -154,6 +168,24 @@ static const char * const linflex_clks_id[] = { struct linflex_port { struct uart_port port; struct clk_bulk_data clks[LINFLEX_CLK_NUM]; + unsigned int txfifo_size; + unsigned int rxfifo_size; + bool dma_tx_use; + bool dma_rx_use; + struct dma_chan *dma_tx_chan; + struct dma_chan *dma_rx_chan; + struct dma_async_tx_descriptor *dma_tx_desc; + struct dma_async_tx_descriptor *dma_rx_desc; + dma_addr_t dma_tx_buf_bus; + dma_addr_t dma_rx_buf_bus; + dma_cookie_t dma_tx_cookie; + dma_cookie_t dma_rx_cookie; + unsigned char *dma_rx_buf_virt; + unsigned int dma_tx_bytes; + int dma_tx_in_progress; + int dma_rx_in_progress; + unsigned long dma_rx_timeout; + struct timer_list timer; }; static const struct of_device_id linflex_dt_ids[] = { @@ -168,6 +200,76 @@ MODULE_DEVICE_TABLE(of, linflex_dt_ids); static struct uart_port *earlycon_port; #endif +static void linflex_dma_tx_complete(void *arg); +static void linflex_dma_rx_complete(void *arg); +static void linflex_console_putchar(struct uart_port *port, unsigned char ch); + +static inline struct linflex_port * +to_linflex_port(struct uart_port *uart) +{ + return container_of(uart, struct linflex_port, port); +} + +static void linflex_copy_rx_to_tty(struct linflex_port *lfport, + struct tty_port *tty, int count) +{ + size_t copied; + + lfport->port.icount.rx += count; + + if (!tty) { + dev_err(lfport->port.dev, "No tty port\n"); + return; + } + + dma_sync_single_for_cpu(lfport->port.dev, lfport->dma_rx_buf_bus, + FSL_UART_RX_DMA_BUFFER_SIZE, DMA_FROM_DEVICE); + copied = tty_insert_flip_string(tty, + ((unsigned char *)(lfport->dma_rx_buf_virt)), + count); + + if (copied != count) { + WARN_ON(1); + dev_err(lfport->port.dev, "RxData copy to tty layer failed\n"); + } +} + +static void linflex_enable_dma_rx(struct uart_port *port) +{ + unsigned long dmarxe = readl(port->membase + DMARXE); + + writel(dmarxe | DMARXE_DRE0, port->membase + DMARXE); + while (!(readl(port->membase + DMARXE) & DMARXE_DRE0)) + ; +} + +static void linflex_enable_dma_tx(struct uart_port *port) +{ + unsigned long dmatxe = readl(port->membase + DMATXE); + + writel(dmatxe | DMATXE_DRE0, port->membase + DMATXE); + while (!(readl(port->membase + DMATXE) & DMATXE_DRE0)) + ; +} + +static void linflex_disable_dma_rx(struct uart_port *port) +{ + unsigned long dmarxe = readl(port->membase + DMARXE); + + writel(dmarxe & 0xFFFF0000, port->membase + DMARXE); + while (readl(port->membase + DMARXE) & DMARXE_DRE0) + ; +} + +static void linflex_disable_dma_tx(struct uart_port *port) +{ + unsigned long dmatxe = readl(port->membase + DMATXE); + + writel(dmatxe & 0xFFFF0000, port->membase + DMATXE); + while (readl(port->membase + DMATXE) & DMATXE_DRE0) + ; +} + static inline void linflex_wait_tx_fifo_empty(struct uart_port *port) { unsigned long cr = readl(port->membase + UARTCR); @@ -179,36 +281,113 @@ static inline void linflex_wait_tx_fifo_empty(struct uart_port *port) ; } +static void _linflex_stop_tx(struct uart_port *port) +{ + struct linflex_port *lfport = to_linflex_port(port); + unsigned long ier; + + if (!lfport->dma_tx_use) { + ier = readl(port->membase + LINIER); + ier &= ~(LINFLEXD_LINIER_DTIE); + writel(ier, port->membase + LINIER); + return; + } + + linflex_disable_dma_tx(port); +} + static void linflex_stop_tx(struct uart_port *port) { + struct linflex_port *lfport = to_linflex_port(port); + struct dma_tx_state state; + unsigned int count; + + _linflex_stop_tx(port); + + if (!lfport->dma_tx_in_progress) + return; + + dmaengine_pause(lfport->dma_tx_chan); + dmaengine_tx_status(lfport->dma_tx_chan, + lfport->dma_tx_cookie, &state); + dmaengine_terminate_all(lfport->dma_tx_chan); + count = lfport->dma_tx_bytes - state.residue; + uart_xmit_advance(port, count); + + lfport->dma_tx_in_progress = 0; +} + +static void _linflex_start_rx(struct uart_port *port) +{ + struct linflex_port *lfport = to_linflex_port(port); unsigned long ier; - ier = readl(port->membase + LINIER); - ier &= ~(LINFLEXD_LINIER_DTIE); - writel(ier, port->membase + LINIER); + if (!lfport->dma_rx_use) { + ier = readl(port->membase + LINIER); + writel(ier | LINFLEXD_LINIER_DRIE, port->membase + LINIER); + return; + } + + linflex_enable_dma_rx(port); } -static void linflex_stop_rx(struct uart_port *port) +static void _linflex_stop_rx(struct uart_port *port) { + struct linflex_port *lfport = to_linflex_port(port); unsigned long ier; - ier = readl(port->membase + LINIER); - writel(ier & ~LINFLEXD_LINIER_DRIE, port->membase + LINIER); + if (!lfport->dma_rx_use) { + ier = readl(port->membase + LINIER); + writel(ier & ~LINFLEXD_LINIER_DRIE, port->membase + LINIER); + return; + } + + linflex_disable_dma_rx(port); +} + +static void linflex_stop_rx(struct uart_port *port) +{ + struct linflex_port *lfport = to_linflex_port(port); + struct dma_tx_state state; + unsigned int count; + + _linflex_stop_rx(port); + + if (!lfport->dma_rx_in_progress) + return; + + dmaengine_pause(lfport->dma_rx_chan); + dmaengine_tx_status(lfport->dma_rx_chan, + lfport->dma_rx_cookie, &state); + dmaengine_terminate_all(lfport->dma_rx_chan); + count = FSL_UART_RX_DMA_BUFFER_SIZE - state.residue; + + lfport->dma_rx_in_progress = 0; + linflex_copy_rx_to_tty(lfport, &port->state->port, count); + tty_flip_buffer_push(&port->state->port); } static void linflex_put_char(struct uart_port *sport, unsigned char c) { + struct linflex_port *lfport = to_linflex_port(sport); unsigned long status; writeb(c, sport->membase + BDRL); /* Waiting for data transmission completed. */ - while (((status = readl(sport->membase + UARTSR)) & - LINFLEXD_UARTSR_DTFTFF) != - LINFLEXD_UARTSR_DTFTFF) - ; + if (!lfport->dma_tx_use) { + while (((status = readl(sport->membase + UARTSR)) & + LINFLEXD_UARTSR_DTFTFF) != + LINFLEXD_UARTSR_DTFTFF) + ; + } else { + while (((status = readl(sport->membase + UARTSR)) & + LINFLEXD_UARTSR_DTFTFF)) + ; + } - writel(LINFLEXD_UARTSR_DTFTFF, sport->membase + UARTSR); + if (!lfport->dma_tx_use) + writel(LINFLEXD_UARTSR_DTFTFF, sport->membase + UARTSR); } static inline void linflex_transmit_buffer(struct uart_port *sport) @@ -228,18 +407,198 @@ static inline void linflex_transmit_buffer(struct uart_port *sport) linflex_stop_tx(sport); } +static int linflex_dma_tx(struct linflex_port *lfport, unsigned int count, + unsigned int tail) +{ + struct uart_port *sport = &lfport->port; + dma_addr_t tx_bus_addr; + + while ((readl(sport->membase + UARTSR) & LINFLEXD_UARTSR_DTFTFF)) + ; + + dma_sync_single_for_device(sport->dev, lfport->dma_tx_buf_bus, + UART_XMIT_SIZE, DMA_TO_DEVICE); + lfport->dma_tx_bytes = count; + tx_bus_addr = lfport->dma_tx_buf_bus + tail; + lfport->dma_tx_desc = + dmaengine_prep_slave_single(lfport->dma_tx_chan, tx_bus_addr, + lfport->dma_tx_bytes, DMA_MEM_TO_DEV, + DMA_PREP_INTERRUPT | DMA_CTRL_ACK); + + if (!lfport->dma_tx_desc) { + dev_err(sport->dev, "Not able to get desc for tx\n"); + return -EIO; + } + + lfport->dma_tx_desc->callback = linflex_dma_tx_complete; + lfport->dma_tx_desc->callback_param = sport; + lfport->dma_tx_in_progress = 1; + lfport->dma_tx_cookie = dmaengine_submit(lfport->dma_tx_desc); + dma_async_issue_pending(lfport->dma_tx_chan); + + linflex_enable_dma_tx(&lfport->port); + return 0; +} + +static void linflex_prepare_tx(struct linflex_port *lfport) +{ + struct tty_port *tport = &lfport->port.state->port; + unsigned int count, tail; + + count = kfifo_out_linear(&tport->xmit_fifo, &tail, UART_XMIT_SIZE); + + if (!count || lfport->dma_tx_in_progress) + return; + + linflex_dma_tx(lfport, count, tail); +} + +static void linflex_restart_dma_tx(struct linflex_port *lfport) +{ + struct uart_port *sport = &lfport->port; + struct tty_port *tport = &sport->state->port; + + if (kfifo_len(&tport->xmit_fifo) < WAKEUP_CHARS) + uart_write_wakeup(sport); + + linflex_prepare_tx(lfport); +} + +static void linflex_dma_tx_complete(void *arg) +{ + struct linflex_port *lfport = arg; + struct uart_port *sport = &lfport->port; + unsigned long flags; + + uart_port_lock_irqsave(sport, &flags); + + /* stopped before? */ + if (!lfport->dma_tx_in_progress) + goto out_tx_callback; + + uart_xmit_advance(sport, lfport->dma_tx_bytes); + lfport->dma_tx_in_progress = 0; + + linflex_restart_dma_tx(lfport); + +out_tx_callback: + uart_port_unlock_irqrestore(sport, flags); +} + +static void linflex_flush_buffer(struct uart_port *port) +{ + struct linflex_port *lfport = to_linflex_port(port); + + if (lfport->dma_tx_use) { + linflex_disable_dma_tx(port); + dmaengine_terminate_async(lfport->dma_tx_chan); + lfport->dma_tx_in_progress = 0; + } +} + +static int linflex_dma_rx(struct linflex_port *lfport) +{ + dma_sync_single_for_device(lfport->port.dev, lfport->dma_rx_buf_bus, + FSL_UART_RX_DMA_BUFFER_SIZE, + DMA_FROM_DEVICE); + lfport->dma_rx_desc = dmaengine_prep_slave_single(lfport->dma_rx_chan, + lfport->dma_rx_buf_bus, + FSL_UART_RX_DMA_BUFFER_SIZE, + DMA_DEV_TO_MEM, DMA_PREP_INTERRUPT | + DMA_CTRL_ACK); + + if (!lfport->dma_rx_desc) { + dev_err(lfport->port.dev, "Not able to get desc for rx\n"); + return -EIO; + } + + lfport->dma_rx_desc->callback = linflex_dma_rx_complete; + lfport->dma_rx_desc->callback_param = lfport; + lfport->dma_rx_in_progress = 1; + lfport->dma_rx_cookie = dmaengine_submit(lfport->dma_rx_desc); + dma_async_issue_pending(lfport->dma_rx_chan); + + linflex_enable_dma_rx(&lfport->port); + return 0; +} + +static void linflex_dma_rx_complete(void *arg) +{ + struct linflex_port *lfport = arg; + struct tty_port *port = &lfport->port.state->port; + unsigned long flags; + + timer_delete_sync(&lfport->timer); + + uart_port_lock_irqsave(&lfport->port, &flags); + + /* stopped before? */ + if (!lfport->dma_rx_in_progress) { + uart_port_unlock_irqrestore(&lfport->port, flags); + return; + } + + lfport->dma_rx_in_progress = 0; + linflex_copy_rx_to_tty(lfport, port, FSL_UART_RX_DMA_BUFFER_SIZE); + tty_flip_buffer_push(port); + linflex_dma_rx(lfport); + + uart_port_unlock_irqrestore(&lfport->port, flags); + + mod_timer(&lfport->timer, jiffies + lfport->dma_rx_timeout); +} + +static void linflex_timer_func(struct timer_list *t) +{ + struct linflex_port *lfport = timer_container_of(lfport, t, timer); + unsigned long flags; + + uart_port_lock_irqsave(&lfport->port, &flags); + + /* stopped before? */ + if (!lfport->dma_rx_in_progress) { + uart_port_unlock_irqrestore(&lfport->port, flags); + return; + } + + linflex_stop_rx(&lfport->port); + linflex_dma_rx(lfport); + + uart_port_unlock_irqrestore(&lfport->port, flags); + mod_timer(&lfport->timer, jiffies + lfport->dma_rx_timeout); +} + +static void _linflex_start_tx(struct uart_port *port) +{ + struct linflex_port *lfport = to_linflex_port(port); + unsigned long ier; + + if (lfport->dma_tx_use) { + linflex_enable_dma_tx(&lfport->port); + } else { + ier = readl(port->membase + LINIER); + writel(ier | LINFLEXD_LINIER_DTIE, port->membase + LINIER); + } +} + static void linflex_start_tx(struct uart_port *port) { + struct linflex_port *lfport = to_linflex_port(port); unsigned long ier; - linflex_transmit_buffer(port); - ier = readl(port->membase + LINIER); - writel(ier | LINFLEXD_LINIER_DTIE, port->membase + LINIER); + if (lfport->dma_tx_use) { + linflex_prepare_tx(lfport); + } else { + linflex_transmit_buffer(port); + ier = readl(port->membase + LINIER); + writel(ier | LINFLEXD_LINIER_DTIE, port->membase + LINIER); + } } static irqreturn_t linflex_txint(int irq, void *dev_id) { - struct uart_port *sport = dev_id; + struct linflex_port *lfport = dev_id; + struct uart_port *sport = &lfport->port; struct tty_port *tport = &sport->state->port; unsigned long flags; @@ -263,7 +622,8 @@ static irqreturn_t linflex_txint(int irq, void *dev_id) static irqreturn_t linflex_rxint(int irq, void *dev_id) { - struct uart_port *sport = dev_id; + struct linflex_port *lfport = dev_id; + struct uart_port *sport = &lfport->port; unsigned int flg; struct tty_port *port = &sport->state->port; unsigned long flags, status; @@ -316,14 +676,14 @@ static irqreturn_t linflex_rxint(int irq, void *dev_id) static irqreturn_t linflex_int(int irq, void *dev_id) { - struct uart_port *sport = dev_id; + struct linflex_port *lfport = dev_id; unsigned long status; - status = readl(sport->membase + UARTSR); + status = readl(lfport->port.membase + UARTSR); - if (status & LINFLEXD_UARTSR_DRFRFE) + if (status & LINFLEXD_UARTSR_DRFRFE && !lfport->dma_rx_use) linflex_rxint(irq, dev_id); - if (status & LINFLEXD_UARTSR_DTFTFF) + if (status & LINFLEXD_UARTSR_DTFTFF && !lfport->dma_rx_use) linflex_txint(irq, dev_id); return IRQ_HANDLED; @@ -332,11 +692,15 @@ static irqreturn_t linflex_int(int irq, void *dev_id) /* return TIOCSER_TEMT when transmitter is not busy */ static unsigned int linflex_tx_empty(struct uart_port *port) { + struct linflex_port *lfport = to_linflex_port(port); unsigned long status; status = readl(port->membase + UARTSR) & LINFLEXD_UARTSR_DTFTFF; - return status ? TIOCSER_TEMT : 0; + if (!lfport->dma_tx_use) + return status ? TIOCSER_TEMT : 0; + else + return status ? 0 : TIOCSER_TEMT; } static unsigned int linflex_get_mctrl(struct uart_port *port) @@ -354,6 +718,7 @@ static void linflex_break_ctl(struct uart_port *port, int break_state) static void linflex_setup_watermark(struct uart_port *sport) { + struct linflex_port *lfport = to_linflex_port(sport); unsigned long cr, ier, cr1; /* Disable transmission/reception */ @@ -396,6 +761,14 @@ static void linflex_setup_watermark(struct uart_port *sport) cr = (LINFLEXD_UARTCR_WL0 | LINFLEXD_UARTCR_UART); + /* FIFO mode enabled for DMA Rx mode. */ + if (lfport->dma_rx_use) + cr |= LINFLEXD_UARTCR_RFBM; + + /* FIFO mode enabled for DMA Tx mode. */ + if (lfport->dma_tx_use) + cr |= LINFLEXD_UARTCR_TFBM; + writel(cr, sport->membase + UARTCR); cr1 &= ~(LINFLEXD_LINCR1_INIT); @@ -406,44 +779,169 @@ static void linflex_setup_watermark(struct uart_port *sport) writel(cr, sport->membase + UARTCR); ier = readl(sport->membase + LINIER); - ier |= LINFLEXD_LINIER_DRIE; - ier |= LINFLEXD_LINIER_DTIE; + if (!lfport->dma_rx_use) + ier |= LINFLEXD_LINIER_DRIE; + + if (!lfport->dma_tx_use) + ier |= LINFLEXD_LINIER_DTIE; writel(ier, sport->membase + LINIER); } +static int linflex_dma_tx_request(struct uart_port *port) +{ + struct linflex_port *lfport = to_linflex_port(port); + struct tty_port *tport = &port->state->port; + struct dma_slave_config dma_tx_sconfig; + dma_addr_t dma_bus; + int ret; + + dma_bus = dma_map_single(port->dev, tport->xmit_buf, + UART_XMIT_SIZE, DMA_TO_DEVICE); + + if (dma_mapping_error(port->dev, dma_bus)) { + dev_err(port->dev, "dma_map_single tx failed\n"); + return -ENOMEM; + } + + memset(&dma_tx_sconfig, 0, sizeof(dma_tx_sconfig)); + dma_tx_sconfig.dst_addr = port->mapbase + BDRL; + dma_tx_sconfig.dst_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE; + dma_tx_sconfig.dst_maxburst = 1; + dma_tx_sconfig.direction = DMA_MEM_TO_DEV; + ret = dmaengine_slave_config(lfport->dma_tx_chan, &dma_tx_sconfig); + + if (ret < 0) { + dev_err(port->dev, "Dma slave config failed, err = %d\n", + ret); + return ret; + } + + lfport->dma_tx_buf_bus = dma_bus; + lfport->dma_tx_in_progress = 0; + + return 0; +} + +static int linflex_dma_rx_request(struct uart_port *port) +{ + struct linflex_port *lfport = to_linflex_port(port); + struct dma_slave_config dma_rx_sconfig; + unsigned char *dma_buf; + dma_addr_t dma_bus; + int ret; + + dma_buf = devm_kmalloc(port->dev, FSL_UART_RX_DMA_BUFFER_SIZE, + GFP_KERNEL); + + if (!dma_buf) { + dev_err(port->dev, "Dma rx alloc failed\n"); + return -ENOMEM; + } + + dma_bus = dma_map_single(port->dev, dma_buf, + FSL_UART_RX_DMA_BUFFER_SIZE, DMA_FROM_DEVICE); + + if (dma_mapping_error(port->dev, dma_bus)) { + dev_err(port->dev, "dma_map_single rx failed\n"); + return -ENOMEM; + } + + memset(&dma_rx_sconfig, 0, sizeof(dma_rx_sconfig)); + dma_rx_sconfig.src_addr = port->mapbase + BDRM; + dma_rx_sconfig.src_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE; + dma_rx_sconfig.src_maxburst = 1; + dma_rx_sconfig.direction = DMA_DEV_TO_MEM; + ret = dmaengine_slave_config(lfport->dma_rx_chan, &dma_rx_sconfig); + + if (ret < 0) { + dev_err(port->dev, "Dma slave config failed, err = %d\n", + ret); + return ret; + } + + lfport->dma_rx_buf_virt = dma_buf; + lfport->dma_rx_buf_bus = dma_bus; + lfport->dma_rx_in_progress = 0; + + return 0; +} + +static void linflex_dma_tx_free(struct uart_port *port) +{ + struct linflex_port *lfport = to_linflex_port(port); + + dma_unmap_single(lfport->port.dev, lfport->dma_tx_buf_bus, UART_XMIT_SIZE, + DMA_TO_DEVICE); + + lfport->dma_tx_buf_bus = 0; +} + +static void linflex_dma_rx_free(struct uart_port *port) +{ + struct linflex_port *lfport = to_linflex_port(port); + + dma_unmap_single(lfport->port.dev, lfport->dma_rx_buf_bus, + FSL_UART_RX_DMA_BUFFER_SIZE, DMA_FROM_DEVICE); + devm_kfree(lfport->port.dev, lfport->dma_rx_buf_virt); + + lfport->dma_rx_buf_bus = 0; + lfport->dma_rx_buf_virt = NULL; +} + static int linflex_startup(struct uart_port *port) { + struct linflex_port *lfport = to_linflex_port(port); int ret = 0; unsigned long flags; + bool dma_rx_use, dma_tx_use; + + dma_rx_use = lfport->dma_rx_chan && !linflex_dma_rx_request(port); + dma_tx_use = lfport->dma_tx_chan && !linflex_dma_tx_request(port); uart_port_lock_irqsave(port, &flags); + lfport->dma_rx_use = dma_rx_use; + lfport->dma_tx_use = dma_tx_use; + lfport->port.fifosize = LINFLEXD_UARTCR_FIFO_SIZE; + linflex_setup_watermark(port); + if (lfport->dma_rx_use && !linflex_dma_rx(lfport)) { + timer_setup(&lfport->timer, linflex_timer_func, 0); + mod_timer(&lfport->timer, jiffies + lfport->dma_rx_timeout); + } uart_port_unlock_irqrestore(port, flags); - ret = devm_request_irq(port->dev, port->irq, linflex_int, 0, - DRIVER_NAME, port); - + if (!lfport->dma_rx_use || !lfport->dma_tx_use) { + ret = devm_request_irq(port->dev, port->irq, linflex_int, 0, + DRIVER_NAME, lfport); + } return ret; } static void linflex_shutdown(struct uart_port *port) { - unsigned long ier; + struct linflex_port *lfport = to_linflex_port(port); unsigned long flags; + timer_delete_sync(&lfport->timer); + uart_port_lock_irqsave(port, &flags); - /* disable interrupts */ - ier = readl(port->membase + LINIER); - ier &= ~(LINFLEXD_LINIER_DRIE | LINFLEXD_LINIER_DTIE); - writel(ier, port->membase + LINIER); + linflex_stop_tx(port); + linflex_stop_rx(port); uart_port_unlock_irqrestore(port, flags); - devm_free_irq(port->dev, port->irq, port); + if (!lfport->dma_rx_use || !lfport->dma_tx_use) + devm_free_irq(port->dev, port->irq, lfport); + + if (lfport->dma_rx_use) + linflex_dma_rx_free(port); + + if (lfport->dma_tx_use) + linflex_dma_tx_free(port); } static unsigned char @@ -463,6 +961,7 @@ static void linflex_set_termios(struct uart_port *port, struct ktermios *termios, const struct ktermios *old) { + struct linflex_port *lfport = to_linflex_port(port); unsigned long flags; unsigned long cr, old_cr, cr1, gcr; unsigned int old_csize = old ? old->c_cflag & CSIZE : CS8; @@ -472,6 +971,9 @@ linflex_set_termios(struct uart_port *port, struct ktermios *termios, uart_port_lock_irqsave(port, &flags); + _linflex_stop_rx(port); + _linflex_stop_tx(port); + old_cr = readl(port->membase + UARTCR) & ~(LINFLEXD_UARTCR_RXEN | LINFLEXD_UARTCR_TXEN); cr = old_cr; @@ -608,6 +1110,8 @@ linflex_set_termios(struct uart_port *port, struct ktermios *termios, writel(fbr, port->membase + LINFBRR); } + lfport->dma_rx_timeout = msecs_to_jiffies(DIV_ROUND_UP(10000000, baud)); + writel(cr, port->membase + UARTCR); cr1 &= ~(LINFLEXD_LINCR1_INIT); @@ -617,6 +1121,9 @@ linflex_set_termios(struct uart_port *port, struct ktermios *termios, cr |= (LINFLEXD_UARTCR_TXEN) | (LINFLEXD_UARTCR_RXEN); writel(cr, port->membase + UARTCR); + _linflex_start_rx(port); + _linflex_start_tx(port); + uart_port_unlock_irqrestore(port, flags); } @@ -657,6 +1164,7 @@ static const struct uart_ops linflex_pops = { .request_port = linflex_request_port, .release_port = linflex_release_port, .config_port = linflex_config_port, + .flush_buffer = linflex_flush_buffer, }; static struct uart_port *linflex_ports[UART_NR]; @@ -690,18 +1198,16 @@ static void linflex_console_putchar(struct uart_port *port, unsigned char ch) static void linflex_string_write(struct uart_port *sport, const char *s, unsigned int count) { - unsigned long cr, ier = 0; - - ier = readl(sport->membase + LINIER); - linflex_stop_tx(sport); + unsigned long cr; + _linflex_stop_tx(sport); cr = readl(sport->membase + UARTCR); cr |= (LINFLEXD_UARTCR_TXEN); writel(cr, sport->membase + UARTCR); uart_console_write(sport, s, count, linflex_console_putchar); - writel(ier, sport->membase + LINIER); + _linflex_start_tx(sport); } static void @@ -881,30 +1387,59 @@ static int linflex_probe(struct platform_device *pdev) return -ENOMEM; sport = &lfport->port; + sport->dev = &pdev->dev; + + lfport->dma_tx_chan = dma_request_chan(sport->dev, "tx"); + if (IS_ERR(lfport->dma_tx_chan)) { + ret = PTR_ERR(lfport->dma_tx_chan); + if (ret == -EPROBE_DEFER) + return ret; + + dev_info(sport->dev, + "DMA tx channel request failed, operating without tx DMA %ld\n", + PTR_ERR(lfport->dma_tx_chan)); + lfport->dma_tx_chan = NULL; + } + + lfport->dma_rx_chan = dma_request_chan(sport->dev, "rx"); + if (IS_ERR(lfport->dma_rx_chan)) { + ret = PTR_ERR(lfport->dma_rx_chan); + if (ret == -EPROBE_DEFER) { + dma_release_channel(lfport->dma_tx_chan); + return ret; + } + + dev_info(sport->dev, + "DMA rx channel request failed, operating without rx DMA %ld\n", + PTR_ERR(lfport->dma_rx_chan)); + lfport->dma_rx_chan = NULL; + } ret = of_alias_get_id(np, "serial"); if (ret < 0) { dev_err(&pdev->dev, "failed to get alias id, errno %d\n", ret); - return ret; + goto linflex_probe_free_dma; } if (ret >= UART_NR) { dev_err(&pdev->dev, "driver limited to %d serial ports\n", UART_NR); - return -ENOMEM; + ret = -ENOMEM; + goto linflex_probe_free_dma; } sport->line = ret; sport->membase = devm_platform_get_and_ioremap_resource(pdev, 0, &res); - if (IS_ERR(sport->membase)) - return PTR_ERR(sport->membase); + if (IS_ERR(sport->membase)) { + ret = PTR_ERR(sport->membase); + goto linflex_probe_free_dma; + } sport->mapbase = res->start; ret = platform_get_irq(pdev, 0); if (ret < 0) return ret; - sport->dev = &pdev->dev; sport->iotype = UPIO_MEM; sport->irq = ret; sport->ops = &linflex_pops; @@ -913,15 +1448,25 @@ static int linflex_probe(struct platform_device *pdev) ret = linflex_init_clk(lfport); if (ret) - return ret; + goto linflex_probe_free_dma; linflex_ports[sport->line] = sport; platform_set_drvdata(pdev, lfport); ret = uart_add_one_port(&linflex_reg, sport); - if (ret) + if (ret) { clk_bulk_disable_unprepare(LINFLEX_CLK_NUM, lfport->clks); + goto linflex_probe_free_dma; + } + + return 0; + +linflex_probe_free_dma: + if (lfport->dma_tx_chan) + dma_release_channel(lfport->dma_tx_chan); + if (lfport->dma_rx_chan) + dma_release_channel(lfport->dma_rx_chan); return ret; } @@ -933,6 +1478,13 @@ static void linflex_remove(struct platform_device *pdev) uart_remove_one_port(&linflex_reg, sport); clk_bulk_disable_unprepare(LINFLEX_CLK_NUM, lfport->clks); + + if (lfport->dma_tx_chan) + dma_release_channel(lfport->dma_tx_chan); + + if (lfport->dma_rx_chan) + dma_release_channel(lfport->dma_rx_chan); + } #ifdef CONFIG_PM_SLEEP -- 2.47.0
