summaryrefslogtreecommitdiff
path: root/drivers/tty/serial/fsl_linflexuart.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/tty/serial/fsl_linflexuart.c')
-rw-r--r--drivers/tty/serial/fsl_linflexuart.c805
1 files changed, 670 insertions, 135 deletions
diff --git a/drivers/tty/serial/fsl_linflexuart.c b/drivers/tty/serial/fsl_linflexuart.c
index 283757264608..ab03ef08e3e5 100644
--- a/drivers/tty/serial/fsl_linflexuart.c
+++ b/drivers/tty/serial/fsl_linflexuart.c
@@ -6,6 +6,7 @@
* Copyright 2017-2019 NXP
*/
+#include <linux/clk.h>
#include <linux/console.h>
#include <linux/io.h>
#include <linux/irq.h>
@@ -16,6 +17,9 @@
#include <linux/slab.h>
#include <linux/tty_flip.h>
#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/of_dma.h>
+#include <linux/jiffies.h>
/* All registers are 32-bit width */
@@ -41,6 +45,9 @@
#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 */
/*
* Register field definitions
@@ -110,6 +117,10 @@
LINFLEXD_UARTSR_PE2 |\
LINFLEXD_UARTSR_PE3)
+#define FSL_UART_RX_DMA_BUFFER_SIZE (PAGE_SIZE)
+
+#define LINFLEXD_UARTCR_TXFIFO_SIZE (4)
+
#define LINFLEX_LDIV_MULTIPLIER (16)
#define DRIVER_NAME "fsl-linflexuart"
@@ -119,6 +130,29 @@
#define EARLYCON_BUFFER_INITIAL_CAP 8
#define PREINIT_DELAY 2000 /* us */
+#define MAX_BOOT_TIME 10 /* s */
+
+struct linflex_port {
+ struct uart_port port;
+ struct clk *clk;
+ 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_tx_buf_virt;
+ unsigned char *dma_rx_buf_virt;
+ unsigned int dma_tx_bytes;
+ int dma_tx_in_progress;
+ int dma_rx_in_progress;
+ unsigned int dma_rx_timeout;
+ struct timer_list timer;
+};
static const struct of_device_id linflex_dt_ids[] = {
{
@@ -140,146 +174,369 @@ static struct {
} earlycon_buf;
#endif
+/* Forward declare this for the dma callbacks. */
+static void linflex_dma_tx_complete(void *arg);
+static void linflex_dma_rx_complete(void *arg);
+static void linflex_string_write(struct linflex_port *sport, const char *s,
+ unsigned int count);
+
+static void linflex_copy_rx_to_tty(struct linflex_port *sport,
+ struct tty_port *tty, int count)
+{
+ int copied;
+
+ sport->port.icount.rx += count;
+
+ if (!tty) {
+ dev_err(sport->port.dev, "No tty port\n");
+ return;
+ }
+
+ dma_sync_single_for_cpu(sport->port.dev, sport->dma_rx_buf_bus,
+ FSL_UART_RX_DMA_BUFFER_SIZE, DMA_FROM_DEVICE);
+ copied = tty_insert_flip_string(tty,
+ ((unsigned char *)(sport->dma_rx_buf_virt)), count);
+
+ if (copied != count) {
+ WARN_ON(1);
+ dev_err(sport->port.dev, "RxData copy to tty layer failed\n");
+ }
+
+ dma_sync_single_for_device(sport->port.dev, sport->dma_rx_buf_bus,
+ FSL_UART_RX_DMA_BUFFER_SIZE, DMA_TO_DEVICE);
+}
+
static void linflex_stop_tx(struct uart_port *port)
{
unsigned long ier;
-
- ier = readl(port->membase + LINIER);
- ier &= ~(LINFLEXD_LINIER_DTIE);
- writel(ier, port->membase + LINIER);
+ unsigned int count;
+ struct dma_tx_state state;
+ struct linflex_port *sport = container_of(port,
+ struct linflex_port, port);
+ struct circ_buf *xmit = &sport->port.state->xmit;
+
+ if (!sport->dma_tx_use) {
+ ier = readl(port->membase + LINIER);
+ ier &= ~(LINFLEXD_LINIER_DTIE);
+ writel(ier, port->membase + LINIER);
+ } else if (sport->dma_tx_in_progress) {
+ dmaengine_pause(sport->dma_tx_chan);
+ dmaengine_tx_status(sport->dma_tx_chan,
+ sport->dma_tx_cookie, &state);
+ dmaengine_terminate_all(sport->dma_tx_chan);
+ dma_sync_single_for_cpu(sport->port.dev, sport->dma_tx_buf_bus,
+ sport->dma_tx_bytes, DMA_TO_DEVICE);
+ count = sport->dma_tx_bytes - state.residue;
+ xmit->tail = (xmit->tail + count) & (UART_XMIT_SIZE - 1);
+ port->icount.tx += count;
+
+ sport->dma_tx_in_progress = 0;
+ }
}
static void linflex_stop_rx(struct uart_port *port)
{
unsigned long ier;
-
- ier = readl(port->membase + LINIER);
- writel(ier & ~LINFLEXD_LINIER_DRIE, port->membase + LINIER);
+ unsigned int count;
+ struct dma_tx_state state;
+ struct linflex_port *sport = container_of(port,
+ struct linflex_port, port);
+
+ if (!sport->dma_rx_use) {
+ ier = readl(port->membase + LINIER);
+ writel(ier & ~LINFLEXD_LINIER_DRIE, port->membase + LINIER);
+ } else if (sport->dma_rx_in_progress) {
+ del_timer(&sport->timer);
+ dmaengine_pause(sport->dma_rx_chan);
+ dmaengine_tx_status(sport->dma_rx_chan,
+ sport->dma_rx_cookie, &state);
+ dmaengine_terminate_all(sport->dma_rx_chan);
+ count = FSL_UART_RX_DMA_BUFFER_SIZE - state.residue;
+
+ sport->dma_rx_in_progress = 0;
+ linflex_copy_rx_to_tty(sport, &sport->port.state->port, count);
+ tty_flip_buffer_push(&sport->port.state->port);
+ }
}
-static inline void linflex_transmit_buffer(struct uart_port *sport)
+static inline void linflex_transmit_buffer(struct linflex_port *sport)
{
- struct circ_buf *xmit = &sport->state->xmit;
+ struct circ_buf *xmit = &sport->port.state->xmit;
unsigned char c;
unsigned long status;
while (!uart_circ_empty(xmit)) {
c = xmit->buf[xmit->tail];
- writeb(c, sport->membase + BDRL);
+ writeb(c, sport->port.membase + BDRL);
/* Waiting for data transmission completed. */
- while (((status = readl(sport->membase + UARTSR)) &
- LINFLEXD_UARTSR_DTFTFF) !=
- LINFLEXD_UARTSR_DTFTFF)
- ;
+ if (!sport->dma_tx_use) {
+ while (((status = readl(sport->port.membase + UARTSR)) &
+ LINFLEXD_UARTSR_DTFTFF) !=
+ LINFLEXD_UARTSR_DTFTFF)
+ ;
+ } else {
+ while (((status = readl(sport->port.membase + UARTSR)) &
+ LINFLEXD_UARTSR_DTFTFF))
+ ;
+ }
xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1);
- sport->icount.tx++;
+ sport->port.icount.tx++;
- writel(status | LINFLEXD_UARTSR_DTFTFF,
- sport->membase + UARTSR);
+ if (!sport->dma_tx_use)
+ writel(status | LINFLEXD_UARTSR_DTFTFF,
+ sport->port.membase + UARTSR);
}
if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)
- uart_write_wakeup(sport);
+ uart_write_wakeup(&sport->port);
if (uart_circ_empty(xmit))
- linflex_stop_tx(sport);
+ linflex_stop_tx(&sport->port);
+}
+
+static int linflex_dma_tx(struct linflex_port *sport, unsigned long count)
+{
+ struct circ_buf *xmit = &sport->port.state->xmit;
+ dma_addr_t tx_bus_addr;
+
+ while ((readl(sport->port.membase + UARTSR) & LINFLEXD_UARTSR_DTFTFF))
+ ;
+
+ dma_sync_single_for_device(sport->port.dev, sport->dma_tx_buf_bus,
+ UART_XMIT_SIZE, DMA_TO_DEVICE);
+ sport->dma_tx_bytes = count;
+ tx_bus_addr = sport->dma_tx_buf_bus + xmit->tail;
+ sport->dma_tx_desc = dmaengine_prep_slave_single(sport->dma_tx_chan,
+ tx_bus_addr, sport->dma_tx_bytes, DMA_MEM_TO_DEV,
+ DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
+
+ if (!sport->dma_tx_desc) {
+ dev_err(sport->port.dev, "Not able to get desc for tx\n");
+ return -EIO;
+ }
+
+ sport->dma_tx_desc->callback = linflex_dma_tx_complete;
+ sport->dma_tx_desc->callback_param = sport;
+ sport->dma_tx_in_progress = 1;
+ sport->dma_tx_cookie = dmaengine_submit(sport->dma_tx_desc);
+ dma_async_issue_pending(sport->dma_tx_chan);
+
+ return 0;
+}
+
+static void linflex_prepare_tx(struct linflex_port *sport)
+{
+ struct circ_buf *xmit = &sport->port.state->xmit;
+ unsigned long count = CIRC_CNT_TO_END(xmit->head,
+ xmit->tail, UART_XMIT_SIZE);
+
+ if (!count || sport->dma_tx_in_progress)
+ return;
+
+ linflex_dma_tx(sport, count);
+}
+
+static void linflex_dma_tx_complete(void *arg)
+{
+ struct linflex_port *sport = arg;
+ struct circ_buf *xmit = &sport->port.state->xmit;
+ unsigned long flags;
+
+ spin_lock_irqsave(&sport->port.lock, flags);
+
+ xmit->tail = (xmit->tail + sport->dma_tx_bytes) & (UART_XMIT_SIZE - 1);
+ sport->port.icount.tx += sport->dma_tx_bytes;
+ sport->dma_tx_in_progress = 0;
+
+ if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)
+ uart_write_wakeup(&sport->port);
+
+ linflex_prepare_tx(sport);
+
+ spin_unlock_irqrestore(&sport->port.lock, flags);
+}
+
+static void linflex_flush_buffer(struct uart_port *port)
+{
+ struct linflex_port *sport = container_of(port,
+ struct linflex_port, port);
+
+ if (sport->dma_tx_use) {
+ dmaengine_terminate_all(sport->dma_tx_chan);
+ sport->dma_tx_in_progress = 0;
+ }
+}
+
+static int linflex_dma_rx(struct linflex_port *sport)
+{
+ dma_sync_single_for_device(sport->port.dev, sport->dma_rx_buf_bus,
+ FSL_UART_RX_DMA_BUFFER_SIZE, DMA_FROM_DEVICE);
+ sport->dma_rx_desc = dmaengine_prep_slave_single(sport->dma_rx_chan,
+ sport->dma_rx_buf_bus, FSL_UART_RX_DMA_BUFFER_SIZE,
+ DMA_DEV_TO_MEM, DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
+
+ if (!sport->dma_rx_desc) {
+ dev_err(sport->port.dev, "Not able to get desc for rx\n");
+ return -EIO;
+ }
+
+ sport->dma_rx_desc->callback = linflex_dma_rx_complete;
+ sport->dma_rx_desc->callback_param = sport;
+ sport->dma_rx_in_progress = 1;
+ sport->dma_rx_cookie = dmaengine_submit(sport->dma_rx_desc);
+ dma_async_issue_pending(sport->dma_rx_chan);
+
+ return 0;
+}
+
+static void linflex_dma_rx_complete(void *arg)
+{
+ struct linflex_port *sport = arg;
+ struct tty_port *port = &sport->port.state->port;
+ unsigned long flags;
+
+ del_timer(&sport->timer);
+
+ spin_lock_irqsave(&sport->port.lock, flags);
+
+ sport->dma_rx_in_progress = 0;
+ linflex_copy_rx_to_tty(sport, port, FSL_UART_RX_DMA_BUFFER_SIZE);
+ tty_flip_buffer_push(port);
+ linflex_dma_rx(sport);
+
+ spin_unlock_irqrestore(&sport->port.lock, flags);
+
+ mod_timer(&sport->timer, jiffies + sport->dma_rx_timeout);
+}
+
+static void linflex_timer_func(struct timer_list *t)
+{
+ struct linflex_port *sport = from_timer(sport, t, timer);
+ struct tty_port *port = &sport->port.state->port;
+ struct dma_tx_state state;
+ unsigned long flags;
+ int count;
+
+ del_timer(&sport->timer);
+ dmaengine_pause(sport->dma_rx_chan);
+ dmaengine_tx_status(sport->dma_rx_chan, sport->dma_rx_cookie, &state);
+ dmaengine_terminate_all(sport->dma_rx_chan);
+ count = FSL_UART_RX_DMA_BUFFER_SIZE - state.residue;
+
+ spin_lock_irqsave(&sport->port.lock, flags);
+
+ sport->dma_rx_in_progress = 0;
+ linflex_copy_rx_to_tty(sport, port, count);
+ tty_flip_buffer_push(port);
+
+ linflex_dma_rx(sport);
+
+ spin_unlock_irqrestore(&sport->port.lock, flags);
+ mod_timer(&sport->timer, jiffies + sport->dma_rx_timeout);
}
static void linflex_start_tx(struct uart_port *port)
{
+ struct linflex_port *sport = container_of(port,
+ struct linflex_port, port);
unsigned long ier;
- linflex_transmit_buffer(port);
- ier = readl(port->membase + LINIER);
- writel(ier | LINFLEXD_LINIER_DTIE, port->membase + LINIER);
+ if (sport->dma_tx_use) {
+ linflex_prepare_tx(sport);
+ } else {
+ linflex_transmit_buffer(sport);
+ 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 circ_buf *xmit = &sport->state->xmit;
+ struct linflex_port *sport = dev_id;
+ struct circ_buf *xmit = &sport->port.state->xmit;
unsigned long flags;
unsigned long status;
- spin_lock_irqsave(&sport->lock, flags);
+ spin_lock_irqsave(&sport->port.lock, flags);
- if (sport->x_char) {
- writeb(sport->x_char, sport->membase + BDRL);
+ if (sport->port.x_char) {
+ writeb(sport->port.x_char, sport->port.membase + BDRL);
/* waiting for data transmission completed */
- while (((status = readl(sport->membase + UARTSR)) &
+ while (((status = readl(sport->port.membase + UARTSR)) &
LINFLEXD_UARTSR_DTFTFF) != LINFLEXD_UARTSR_DTFTFF)
;
writel(status | LINFLEXD_UARTSR_DTFTFF,
- sport->membase + UARTSR);
+ sport->port.membase + UARTSR);
goto out;
}
- if (uart_circ_empty(xmit) || uart_tx_stopped(sport)) {
- linflex_stop_tx(sport);
+ if (uart_circ_empty(xmit) || uart_tx_stopped(&sport->port)) {
+ linflex_stop_tx(&sport->port);
goto out;
}
linflex_transmit_buffer(sport);
if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)
- uart_write_wakeup(sport);
+ uart_write_wakeup(&sport->port);
out:
- spin_unlock_irqrestore(&sport->lock, flags);
+ spin_unlock_irqrestore(&sport->port.lock, flags);
return IRQ_HANDLED;
}
static irqreturn_t linflex_rxint(int irq, void *dev_id)
{
- struct uart_port *sport = dev_id;
+ struct linflex_port *sport = dev_id;
unsigned int flg;
- struct tty_port *port = &sport->state->port;
+ struct tty_port *port = &sport->port.state->port;
unsigned long flags, status;
unsigned char rx;
bool brk;
- spin_lock_irqsave(&sport->lock, flags);
+ spin_lock_irqsave(&sport->port.lock, flags);
- status = readl(sport->membase + UARTSR);
+ status = readl(sport->port.membase + UARTSR);
while (status & LINFLEXD_UARTSR_RMB) {
- rx = readb(sport->membase + BDRM);
+ rx = readb(sport->port.membase + BDRM);
brk = false;
flg = TTY_NORMAL;
- sport->icount.rx++;
+ sport->port.icount.rx++;
if (status & (LINFLEXD_UARTSR_BOF | LINFLEXD_UARTSR_FEF |
LINFLEXD_UARTSR_PE)) {
if (status & LINFLEXD_UARTSR_BOF)
- sport->icount.overrun++;
+ sport->port.icount.overrun++;
if (status & LINFLEXD_UARTSR_FEF) {
if (!rx) {
brk = true;
- sport->icount.brk++;
+ sport->port.icount.brk++;
} else
- sport->icount.frame++;
+ sport->port.icount.frame++;
}
if (status & LINFLEXD_UARTSR_PE)
- sport->icount.parity++;
+ sport->port.icount.parity++;
}
- writel(status, sport->membase + UARTSR);
- status = readl(sport->membase + UARTSR);
+ writel(status, sport->port.membase + UARTSR);
+ status = readl(sport->port.membase + UARTSR);
if (brk) {
- uart_handle_break(sport);
+ uart_handle_break(&sport->port);
} else {
- if (uart_handle_sysrq_char(sport, (unsigned char)rx))
+ if (uart_handle_sysrq_char(&sport->port,
+ (unsigned char)rx))
continue;
tty_insert_flip_char(port, rx, flg);
}
}
- spin_unlock_irqrestore(&sport->lock, flags);
+ spin_unlock_irqrestore(&sport->port.lock, flags);
tty_flip_buffer_push(port);
@@ -288,14 +545,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 *sport = dev_id;
unsigned long status;
- status = readl(sport->membase + UARTSR);
+ status = readl(sport->port.membase + UARTSR);
- if (status & LINFLEXD_UARTSR_DRFRFE)
+ if ((status & LINFLEXD_UARTSR_DRFRFE) && !sport->dma_rx_use)
linflex_rxint(irq, dev_id);
- if (status & LINFLEXD_UARTSR_DTFTFF)
+ if ((status & LINFLEXD_UARTSR_DTFTFF) && !sport->dma_tx_use)
linflex_txint(irq, dev_id);
return IRQ_HANDLED;
@@ -305,10 +562,15 @@ static irqreturn_t linflex_int(int irq, void *dev_id)
static unsigned int linflex_tx_empty(struct uart_port *port)
{
unsigned long status;
+ struct linflex_port *sport = container_of(port,
+ struct linflex_port, port);
- status = readl(port->membase + UARTSR) & LINFLEXD_UARTSR_DTFTFF;
+ status = readl(sport->port.membase + UARTSR) & LINFLEXD_UARTSR_DTFTFF;
- return status ? TIOCSER_TEMT : 0;
+ if (!sport->dma_tx_use)
+ return status ? TIOCSER_TEMT : 0;
+
+ return status ? 0 : TIOCSER_TEMT;
}
static unsigned int linflex_get_mctrl(struct uart_port *port)
@@ -324,28 +586,28 @@ static void linflex_break_ctl(struct uart_port *port, int break_state)
{
}
-static void linflex_setup_watermark(struct uart_port *sport)
+static void linflex_setup_watermark(struct linflex_port *sport)
{
- unsigned long cr, ier, cr1;
+ unsigned long cr, ier, cr1, dmarxe, dmatxe;
/* Disable transmission/reception */
- ier = readl(sport->membase + LINIER);
+ ier = readl(sport->port.membase + LINIER);
ier &= ~(LINFLEXD_LINIER_DRIE | LINFLEXD_LINIER_DTIE);
- writel(ier, sport->membase + LINIER);
+ writel(ier, sport->port.membase + LINIER);
- cr = readl(sport->membase + UARTCR);
+ cr = readl(sport->port.membase + UARTCR);
cr &= ~(LINFLEXD_UARTCR_RXEN | LINFLEXD_UARTCR_TXEN);
- writel(cr, sport->membase + UARTCR);
+ writel(cr, sport->port.membase + UARTCR);
/* Enter initialization mode by setting INIT bit */
/* set the Linflex in master mode and activate by-pass filter */
cr1 = LINFLEXD_LINCR1_BF | LINFLEXD_LINCR1_MME
| LINFLEXD_LINCR1_INIT;
- writel(cr1, sport->membase + LINCR1);
+ writel(cr1, sport->port.membase + LINCR1);
/* wait for init mode entry */
- while ((readl(sport->membase + LINSR)
+ while ((readl(sport->port.membase + LINSR)
& LINFLEXD_LINSR_LINS_MASK)
!= LINFLEXD_LINSR_LINS_INITMODE)
;
@@ -359,45 +621,187 @@ static void linflex_setup_watermark(struct uart_port *sport)
*/
/* set UART bit to allow writing other bits */
- writel(LINFLEXD_UARTCR_UART, sport->membase + UARTCR);
+ writel(LINFLEXD_UARTCR_UART, sport->port.membase + UARTCR);
cr = (LINFLEXD_UARTCR_RXEN | LINFLEXD_UARTCR_TXEN |
LINFLEXD_UARTCR_WL0 | LINFLEXD_UARTCR_UART);
- writel(cr, sport->membase + UARTCR);
+ /* FIFO mode enabled for DMA Rx mode. */
+ if (sport->dma_rx_use)
+ cr |= LINFLEXD_UARTCR_RFBM;
+
+ /* FIFO mode enabled for DMA Tx mode. */
+ if (sport->dma_tx_use)
+ cr |= LINFLEXD_UARTCR_TFBM;
+
+ writel(cr, sport->port.membase + UARTCR);
cr1 &= ~(LINFLEXD_LINCR1_INIT);
- writel(cr1, sport->membase + LINCR1);
+ writel(cr1, sport->port.membase + LINCR1);
+
+ ier = readl(sport->port.membase + LINIER);
+ if (!sport->dma_rx_use)
+ ier |= LINFLEXD_LINIER_DRIE;
+ else {
+ dmarxe = readl(sport->port.membase + DMARXE);
+ writel(dmarxe | 0x1, sport->port.membase + DMARXE);
+ }
+
+ if (!sport->dma_tx_use)
+ ier |= LINFLEXD_LINIER_DTIE;
+ else {
+ dmatxe = readl(sport->port.membase + DMATXE);
+ writel(dmatxe | 0x1, sport->port.membase + DMATXE);
+ }
+
+ writel(ier, sport->port.membase + LINIER);
+}
+
+static int linflex_dma_tx_request(struct uart_port *port)
+{
+ struct linflex_port *sport = container_of(port,
+ struct linflex_port, port);
+ struct dma_slave_config dma_tx_sconfig;
+ dma_addr_t dma_bus;
+ unsigned char *dma_buf;
+ int ret;
+
+ dma_bus = dma_map_single(sport->dma_tx_chan->device->dev,
+ sport->port.state->xmit.buf,
+ UART_XMIT_SIZE, DMA_TO_DEVICE);
+
+ if (dma_mapping_error(sport->dma_tx_chan->device->dev, dma_bus)) {
+ dev_err(sport->port.dev, "dma_map_single tx failed\n");
+ return -ENOMEM;
+ }
- ier = readl(sport->membase + LINIER);
- ier |= LINFLEXD_LINIER_DRIE;
- ier |= LINFLEXD_LINIER_DTIE;
+ dma_buf = sport->port.state->xmit.buf;
+ dma_tx_sconfig.dst_addr = sport->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(sport->dma_tx_chan, &dma_tx_sconfig);
- writel(ier, sport->membase + LINIER);
+ if (ret < 0) {
+ dev_err(sport->port.dev,
+ "Dma slave config failed, err = %d\n", ret);
+ return ret;
+ }
+
+ sport->dma_tx_buf_virt = dma_buf;
+ sport->dma_tx_buf_bus = dma_bus;
+ sport->dma_tx_in_progress = 0;
+
+ return 0;
+}
+
+static int linflex_dma_rx_request(struct uart_port *port)
+{
+ struct linflex_port *sport = container_of(port,
+ struct linflex_port, port);
+ struct dma_slave_config dma_rx_sconfig;
+ dma_addr_t dma_bus;
+ unsigned char *dma_buf;
+ int ret;
+
+ dma_buf = devm_kzalloc(sport->port.dev,
+ FSL_UART_RX_DMA_BUFFER_SIZE, GFP_KERNEL);
+
+ if (!dma_buf) {
+ dev_err(sport->port.dev, "Dma rx alloc failed\n");
+ return -ENOMEM;
+ }
+
+ dma_bus = dma_map_single(sport->dma_rx_chan->device->dev, dma_buf,
+ FSL_UART_RX_DMA_BUFFER_SIZE, DMA_FROM_DEVICE);
+
+ if (dma_mapping_error(sport->dma_rx_chan->device->dev, dma_bus)) {
+ dev_err(sport->port.dev, "dma_map_single rx failed\n");
+ return -ENOMEM;
+ }
+
+ dma_rx_sconfig.src_addr = sport->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(sport->dma_rx_chan, &dma_rx_sconfig);
+
+ if (ret < 0) {
+ dev_err(sport->port.dev,
+ "Dma slave config failed, err = %d\n", ret);
+ return ret;
+ }
+
+ sport->dma_rx_buf_virt = dma_buf;
+ sport->dma_rx_buf_bus = dma_bus;
+ sport->dma_rx_in_progress = 0;
+
+ return 0;
+}
+
+static void linflex_dma_tx_free(struct uart_port *port)
+{
+ struct linflex_port *sport = container_of(port,
+ struct linflex_port, port);
+
+ dma_unmap_single(sport->port.dev, sport->dma_tx_buf_bus,
+ UART_XMIT_SIZE, DMA_TO_DEVICE);
+
+ sport->dma_tx_buf_bus = 0;
+ sport->dma_tx_buf_virt = NULL;
+}
+
+static void linflex_dma_rx_free(struct uart_port *port)
+{
+ struct linflex_port *sport = container_of(port,
+ struct linflex_port, port);
+
+ dma_unmap_single(sport->port.dev, sport->dma_rx_buf_bus,
+ FSL_UART_RX_DMA_BUFFER_SIZE, DMA_FROM_DEVICE);
+ devm_kfree(sport->port.dev, sport->dma_rx_buf_virt);
+
+ sport->dma_rx_buf_bus = 0;
+ sport->dma_rx_buf_virt = NULL;
}
static int linflex_startup(struct uart_port *port)
{
+ struct linflex_port *sport = container_of(port,
+ struct linflex_port, port);
int ret = 0;
unsigned long flags;
- spin_lock_irqsave(&port->lock, flags);
+ sport->port.fifosize = LINFLEXD_UARTCR_TXFIFO_SIZE;
- linflex_setup_watermark(port);
+ sport->dma_rx_use = sport->dma_rx_chan && !linflex_dma_rx_request(port);
+ sport->dma_tx_use = sport->dma_tx_chan && !linflex_dma_tx_request(port);
- spin_unlock_irqrestore(&port->lock, flags);
+ spin_lock_irqsave(&sport->port.lock, flags);
+ linflex_setup_watermark(sport);
+ spin_unlock_irqrestore(&sport->port.lock, flags);
- ret = devm_request_irq(port->dev, port->irq, linflex_int, 0,
- DRIVER_NAME, port);
+ if (!sport->dma_rx_use || !sport->dma_tx_use) {
+ ret = devm_request_irq(port->dev, port->irq, linflex_int, 0,
+ DRIVER_NAME, sport);
+ }
+ if (sport->dma_rx_use) {
+ timer_setup(&sport->timer, linflex_timer_func, 0);
+
+ linflex_dma_rx(sport);
+ sport->timer.expires = jiffies + sport->dma_rx_timeout;
+ add_timer(&sport->timer);
+ }
return ret;
}
static void linflex_shutdown(struct uart_port *port)
{
+ struct linflex_port *sport = container_of(port,
+ struct linflex_port, port);
unsigned long ier;
- unsigned long flags;
+ unsigned long flags, dmarxe, dmatxe;
spin_lock_irqsave(&port->lock, flags);
@@ -408,27 +812,66 @@ static void linflex_shutdown(struct uart_port *port)
spin_unlock_irqrestore(&port->lock, flags);
- devm_free_irq(port->dev, port->irq, port);
+ if (!sport->dma_rx_use || !sport->dma_tx_use)
+ devm_free_irq(port->dev, port->irq, sport);
+
+ if (sport->dma_rx_use) {
+ del_timer(&sport->timer);
+ dmaengine_terminate_all(sport->dma_rx_chan);
+
+ dmarxe = readl(sport->port.membase + DMARXE);
+ writel(dmarxe & 0xFFFF0000, sport->port.membase + DMARXE);
+
+ linflex_dma_rx_free(&sport->port);
+ sport->dma_rx_in_progress = 0;
+ }
+
+ if (sport->dma_tx_use) {
+ dmaengine_terminate_all(sport->dma_tx_chan);
+
+ dmatxe = readl(sport->port.membase + DMATXE);
+ writel(dmatxe & 0xFFFF0000, sport->port.membase + DMATXE);
+
+ linflex_dma_tx_free(&sport->port);
+ sport->dma_tx_in_progress = 0;
+ }
+}
+
+static int
+linflex_ldiv_multiplier(struct linflex_port *sport)
+{
+ unsigned int mul = LINFLEX_LDIV_MULTIPLIER;
+ unsigned long cr;
+
+ cr = readl(sport->port.membase + UARTCR);
+ if (cr & LINFLEXD_UARTCR_ROSE)
+ mul = LINFLEXD_UARTCR_OSR(cr);
+
+ return mul;
}
static void
linflex_set_termios(struct uart_port *port, struct ktermios *termios,
struct ktermios *old)
{
+ struct linflex_port *sport = container_of(port,
+ struct linflex_port, port);
unsigned long flags;
unsigned long cr, old_cr, cr1;
+ unsigned int baud;
unsigned int old_csize = old ? old->c_cflag & CSIZE : CS8;
+ unsigned long ibr, fbr, divisr, dividr;
- cr = readl(port->membase + UARTCR);
+ cr = readl(sport->port.membase + UARTCR);
old_cr = cr;
/* Enter initialization mode by setting INIT bit */
- cr1 = readl(port->membase + LINCR1);
+ cr1 = readl(sport->port.membase + LINCR1);
cr1 |= LINFLEXD_LINCR1_INIT;
- writel(cr1, port->membase + LINCR1);
+ writel(cr1, sport->port.membase + LINCR1);
/* wait for init mode entry */
- while ((readl(port->membase + LINSR)
+ while ((readl(sport->port.membase + LINSR)
& LINFLEXD_LINSR_LINS_MASK)
!= LINFLEXD_LINSR_LINS_INITMODE)
;
@@ -487,40 +930,69 @@ linflex_set_termios(struct uart_port *port, struct ktermios *termios,
cr &= ~LINFLEXD_UARTCR_PCE;
}
- spin_lock_irqsave(&port->lock, flags);
+ /* ask the core to calculate the divisor */
+ baud = uart_get_baud_rate(port, termios, old, 50, port->uartclk / 16);
+
+ spin_lock_irqsave(&sport->port.lock, flags);
- port->read_status_mask = 0;
+ sport->port.read_status_mask = 0;
if (termios->c_iflag & INPCK)
- port->read_status_mask |= (LINFLEXD_UARTSR_FEF |
+ sport->port.read_status_mask |= (LINFLEXD_UARTSR_FEF |
LINFLEXD_UARTSR_PE0 |
LINFLEXD_UARTSR_PE1 |
LINFLEXD_UARTSR_PE2 |
LINFLEXD_UARTSR_PE3);
if (termios->c_iflag & (IGNBRK | BRKINT | PARMRK))
- port->read_status_mask |= LINFLEXD_UARTSR_FEF;
+ sport->port.read_status_mask |= LINFLEXD_UARTSR_FEF;
/* characters to ignore */
- port->ignore_status_mask = 0;
+ sport->port.ignore_status_mask = 0;
if (termios->c_iflag & IGNPAR)
- port->ignore_status_mask |= LINFLEXD_UARTSR_PE;
+ sport->port.ignore_status_mask |= LINFLEXD_UARTSR_PE;
if (termios->c_iflag & IGNBRK) {
- port->ignore_status_mask |= LINFLEXD_UARTSR_PE;
+ sport->port.ignore_status_mask |= LINFLEXD_UARTSR_PE;
/*
* if we're ignoring parity and break indicators,
* ignore overruns too (for real raw support).
*/
if (termios->c_iflag & IGNPAR)
- port->ignore_status_mask |= LINFLEXD_UARTSR_BOF;
+ sport->port.ignore_status_mask |= LINFLEXD_UARTSR_BOF;
}
- writel(cr, port->membase + UARTCR);
+ /* update the per-port timeout */
+ uart_update_timeout(port, termios->c_cflag, baud);
+ sport->dma_rx_timeout = msecs_to_jiffies(DIV_ROUND_UP(10000000, baud));
+
+ /* disable transmit and receive */
+ writel(old_cr & ~(LINFLEXD_UARTCR_RXEN | LINFLEXD_UARTCR_TXEN),
+ sport->port.membase + UARTCR);
+
+ divisr = sport->port.uartclk; //freq in Hz
+ dividr = (baud * linflex_ldiv_multiplier(sport));
+
+ ibr = divisr / dividr;
+ fbr = ((divisr % dividr) * 16 / dividr) & 0xF;
+
+ writel(ibr, sport->port.membase + LINIBRR);
+ writel(fbr, sport->port.membase + LINFBRR);
+
+ writel(cr, sport->port.membase + UARTCR);
cr1 &= ~(LINFLEXD_LINCR1_INIT);
- writel(cr1, port->membase + LINCR1);
+ writel(cr1, sport->port.membase + LINCR1);
- spin_unlock_irqrestore(&port->lock, flags);
+ /* Workaround for driver hanging when running the 'reboot'
+ * command because of the DTFTFF bit in UARTSR not being cleared.
+ * The issue is assumed to be caused by a hardware bug.
+ * Only apply the workaround after the boot sequence is
+ * assumed to be complete.
+ */
+ if ((jiffies - INITIAL_JIFFIES) / HZ > MAX_BOOT_TIME)
+ linflex_string_write(sport, "", 1);
+
+ spin_unlock_irqrestore(&sport->port.lock, flags);
}
static const char *linflex_type(struct uart_port *port)
@@ -560,9 +1032,10 @@ 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];
+static struct linflex_port *linflex_ports[UART_NR];
#ifdef CONFIG_SERIAL_FSL_LINFLEXUART_CONSOLE
static void linflex_console_putchar(struct uart_port *port, int ch)
@@ -632,41 +1105,51 @@ init_release:
spin_unlock_irqrestore(&init_lock, flags);
}
-static void linflex_string_write(struct uart_port *sport, const char *s,
+static void linflex_string_write(struct linflex_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, ier = 0, dmatxe;
+
+ if (!sport->dma_tx_use)
+ ier = readl(sport->port.membase + LINIER);
+ linflex_stop_tx(&sport->port);
+ if (sport->dma_tx_use) {
+ dmatxe = readl(sport->port.membase + DMATXE);
+ writel(dmatxe & 0xFFFF0000, sport->port.membase + DMATXE);
+ }
- cr = readl(sport->membase + UARTCR);
+ cr = readl(sport->port.membase + UARTCR);
cr |= (LINFLEXD_UARTCR_TXEN);
- writel(cr, sport->membase + UARTCR);
+ writel(cr, sport->port.membase + UARTCR);
- uart_console_write(sport, s, count, linflex_console_putchar);
+ uart_console_write(&sport->port, s, count, linflex_console_putchar);
- writel(ier, sport->membase + LINIER);
+ if (!sport->dma_tx_use)
+ writel(ier, sport->port.membase + LINIER);
+ else {
+ dmatxe = readl(sport->port.membase + DMATXE);
+ writel(dmatxe | 0x1, sport->port.membase + DMATXE);
+ }
}
static void
linflex_console_write(struct console *co, const char *s, unsigned int count)
{
- struct uart_port *sport = linflex_ports[co->index];
+ struct linflex_port *sport = linflex_ports[co->index];
unsigned long flags;
int locked = 1;
- if (sport->sysrq)
+ if (sport->port.sysrq)
locked = 0;
else if (oops_in_progress)
- locked = spin_trylock_irqsave(&sport->lock, flags);
+ locked = spin_trylock_irqsave(&sport->port.lock, flags);
else
- spin_lock_irqsave(&sport->lock, flags);
+ spin_lock_irqsave(&sport->port.lock, flags);
linflex_string_write(sport, s, count);
if (locked)
- spin_unlock_irqrestore(&sport->lock, flags);
+ spin_unlock_irqrestore(&sport->port.lock, flags);
}
/*
@@ -674,11 +1157,13 @@ linflex_console_write(struct console *co, const char *s, unsigned int count)
* try to determine the current setup.
*/
static void __init
-linflex_console_get_options(struct uart_port *sport, int *parity, int *bits)
+linflex_console_get_options(struct linflex_port *sport, int *baud,
+ int *parity, int *bits)
{
- unsigned long cr;
+ unsigned long cr, ibr;
+ unsigned int uartclk, baud_raw;
- cr = readl(sport->membase + UARTCR);
+ cr = readl(sport->port.membase + UARTCR);
cr &= LINFLEXD_UARTCR_RXEN | LINFLEXD_UARTCR_TXEN;
if (!cr)
@@ -700,11 +1185,21 @@ linflex_console_get_options(struct uart_port *sport, int *parity, int *bits)
else
*bits = 8;
}
+
+ ibr = readl(sport->port.membase + LINIBRR);
+
+ uartclk = clk_get_rate(sport->clk);
+
+ baud_raw = uartclk / (linflex_ldiv_multiplier(sport) * ibr);
+
+ if (*baud != baud_raw)
+ pr_info("Serial: Console linflex rounded baud rate from %d to %d\n",
+ baud_raw, *baud);
}
static int __init linflex_console_setup(struct console *co, char *options)
{
- struct uart_port *sport;
+ struct linflex_port *sport;
int baud = 115200;
int bits = 8;
int parity = 'n';
@@ -727,9 +1222,9 @@ static int __init linflex_console_setup(struct console *co, char *options)
if (options)
uart_parse_options(options, &baud, &parity, &bits, &flow);
else
- linflex_console_get_options(sport, &parity, &bits);
+ linflex_console_get_options(sport, &baud, &parity, &bits);
- if (earlycon_port && sport->mapbase == earlycon_port->mapbase) {
+ if (earlycon_port && sport->port.mapbase == earlycon_port->mapbase) {
linflex_earlycon_same_instance = true;
spin_lock_irqsave(&init_lock, flags);
@@ -745,7 +1240,7 @@ static int __init linflex_console_setup(struct console *co, char *options)
linflex_setup_watermark(sport);
- ret = uart_set_options(sport, co, baud, parity, bits, flow);
+ ret = uart_set_options(&sport->port, co, baud, parity, bits, flow);
if (!linflex_earlycon_same_instance)
goto done;
@@ -819,7 +1314,7 @@ static struct uart_driver linflex_reg = {
static int linflex_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
- struct uart_port *sport;
+ struct linflex_port *sport;
struct resource *res;
int ret;
@@ -827,6 +1322,8 @@ static int linflex_probe(struct platform_device *pdev)
if (!sport)
return -ENOMEM;
+ pdev->dev.coherent_dma_mask = 0;
+
ret = of_alias_get_id(np, "serial");
if (ret < 0) {
dev_err(&pdev->dev, "failed to get alias id, errno %d\n", ret);
@@ -838,37 +1335,75 @@ static int linflex_probe(struct platform_device *pdev)
return -ENOMEM;
}
- sport->line = ret;
+ sport->port.line = ret;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res)
return -ENODEV;
- sport->mapbase = res->start;
- sport->membase = devm_ioremap_resource(&pdev->dev, res);
- if (IS_ERR(sport->membase))
- return PTR_ERR(sport->membase);
+ sport->port.mapbase = res->start;
+ sport->port.membase = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(sport->port.membase))
+ return PTR_ERR(sport->port.membase);
+
+ sport->port.dev = &pdev->dev;
+ sport->port.type = PORT_LINFLEXUART;
+ sport->port.iotype = UPIO_MEM;
+ sport->port.irq = platform_get_irq(pdev, 0);
+ sport->port.ops = &linflex_pops;
+ sport->port.flags = UPF_BOOT_AUTOCONF;
+ sport->port.has_sysrq =
+ IS_ENABLED(CONFIG_SERIAL_FSL_LINFLEXUART_CONSOLE);
+ sport->clk = devm_clk_get(&pdev->dev, "lin");
+ if (IS_ERR(sport->clk)) {
+ ret = PTR_ERR(sport->clk);
+ dev_err(&pdev->dev, "failed to get uart clk: %d\n", ret);
+ return ret;
+ }
+
+ ret = clk_prepare_enable(sport->clk);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to enable uart clk: %d\n", ret);
+ return ret;
+ }
+
+ sport->port.uartclk = clk_get_rate(sport->clk);
+ linflex_ports[sport->port.line] = sport;
+
+ platform_set_drvdata(pdev, &sport->port);
- sport->dev = &pdev->dev;
- sport->type = PORT_LINFLEXUART;
- sport->iotype = UPIO_MEM;
- sport->irq = platform_get_irq(pdev, 0);
- sport->ops = &linflex_pops;
- sport->flags = UPF_BOOT_AUTOCONF;
- sport->has_sysrq = IS_ENABLED(CONFIG_SERIAL_FSL_LINFLEXUART_CONSOLE);
+ ret = uart_add_one_port(&linflex_reg, &sport->port);
+ if (ret) {
+ clk_disable_unprepare(sport->clk);
+ return ret;
+ }
- linflex_ports[sport->line] = sport;
+ sport->dma_tx_chan = dma_request_slave_channel(sport->port.dev, "tx");
+ if (!sport->dma_tx_chan)
+ dev_info(sport->port.dev,
+ "DMA tx channel request failed, operating without tx DMA\n");
- platform_set_drvdata(pdev, sport);
+ sport->dma_rx_chan = dma_request_slave_channel(sport->port.dev, "rx");
+ if (!sport->dma_rx_chan)
+ dev_info(sport->port.dev,
+ "DMA rx channel request failed, operating without rx DMA\n");
- return uart_add_one_port(&linflex_reg, sport);
+ return 0;
}
static int linflex_remove(struct platform_device *pdev)
{
- struct uart_port *sport = platform_get_drvdata(pdev);
+ struct linflex_port *sport = platform_get_drvdata(pdev);
+
+ uart_remove_one_port(&linflex_reg, &sport->port);
+
+ clk_disable_unprepare(sport->clk);
+
+ if (sport->dma_tx_chan)
+ dma_release_channel(sport->dma_tx_chan);
- uart_remove_one_port(&linflex_reg, sport);
+ if (sport->dma_rx_chan)
+ dma_release_channel(sport->dma_rx_chan);
return 0;
}
@@ -876,18 +1411,18 @@ static int linflex_remove(struct platform_device *pdev)
#ifdef CONFIG_PM_SLEEP
static int linflex_suspend(struct device *dev)
{
- struct uart_port *sport = dev_get_drvdata(dev);
+ struct linflex_port *sport = dev_get_drvdata(dev);
- uart_suspend_port(&linflex_reg, sport);
+ uart_suspend_port(&linflex_reg, &sport->port);
return 0;
}
static int linflex_resume(struct device *dev)
{
- struct uart_port *sport = dev_get_drvdata(dev);
+ struct linflex_port *sport = dev_get_drvdata(dev);
- uart_resume_port(&linflex_reg, sport);
+ uart_resume_port(&linflex_reg, &sport->port);
return 0;
}