Commit 34df42f5 authored by Elen Song's avatar Elen Song Committed by Greg Kroah-Hartman

serial: at91: add rx dma support

Request a cyclic dma channel for rx dma use. Use cyclic transfer is to prevent
receive data overrun.

We allocate a cycle dma cookie after request channel,
after that, enable uart timeout interrupt in startup stage, when data successful
received, the timeout callback will check the residual bytes and insert
receiving datas into the framework during the transfer interval.

When current descriptor finished, the dma callback will also check the residual
bytes and filp the receiving data.
Signed-off-by: default avatarElen Song <elen.song@atmel.com>
Signed-off-by: default avatarLudovic Desroches <ludovic.desroches@atmel.com>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent 08f738be
...@@ -139,6 +139,7 @@ struct atmel_uart_port { ...@@ -139,6 +139,7 @@ struct atmel_uart_port {
u32 backup_imr; /* IMR saved during suspend */ u32 backup_imr; /* IMR saved during suspend */
int break_active; /* break being received */ int break_active; /* break being received */
bool use_dma_rx; /* enable DMA receiver */
bool use_pdc_rx; /* enable PDC receiver */ bool use_pdc_rx; /* enable PDC receiver */
short pdc_rx_idx; /* current PDC RX buffer */ short pdc_rx_idx; /* current PDC RX buffer */
struct atmel_dma_buffer pdc_rx[2]; /* PDC receier */ struct atmel_dma_buffer pdc_rx[2]; /* PDC receier */
...@@ -148,10 +149,15 @@ struct atmel_uart_port { ...@@ -148,10 +149,15 @@ struct atmel_uart_port {
struct atmel_dma_buffer pdc_tx; /* PDC transmitter */ struct atmel_dma_buffer pdc_tx; /* PDC transmitter */
spinlock_t lock_tx; /* port lock */ spinlock_t lock_tx; /* port lock */
spinlock_t lock_rx; /* port lock */
struct dma_chan *chan_tx; struct dma_chan *chan_tx;
struct dma_chan *chan_rx;
struct dma_async_tx_descriptor *desc_tx; struct dma_async_tx_descriptor *desc_tx;
struct dma_async_tx_descriptor *desc_rx;
dma_cookie_t cookie_tx; dma_cookie_t cookie_tx;
dma_cookie_t cookie_rx;
struct scatterlist sg_tx; struct scatterlist sg_tx;
struct scatterlist sg_rx;
struct tasklet_struct tasklet; struct tasklet_struct tasklet;
unsigned int irq_status; unsigned int irq_status;
unsigned int irq_status_prev; unsigned int irq_status_prev;
...@@ -224,6 +230,13 @@ static bool atmel_use_dma_tx(struct uart_port *port) ...@@ -224,6 +230,13 @@ static bool atmel_use_dma_tx(struct uart_port *port)
return atmel_port->use_dma_tx; return atmel_port->use_dma_tx;
} }
static bool atmel_use_dma_rx(struct uart_port *port)
{
struct atmel_uart_port *atmel_port = to_atmel_uart_port(port);
return atmel_port->use_dma_rx;
}
/* Enable or disable the rs485 support */ /* Enable or disable the rs485 support */
void atmel_config_rs485(struct uart_port *port, struct serial_rs485 *rs485conf) void atmel_config_rs485(struct uart_port *port, struct serial_rs485 *rs485conf)
{ {
...@@ -758,6 +771,184 @@ static int atmel_prepare_tx_dma(struct uart_port *port) ...@@ -758,6 +771,184 @@ static int atmel_prepare_tx_dma(struct uart_port *port)
return -EINVAL; return -EINVAL;
} }
static void atmel_flip_buffer_rx_dma(struct uart_port *port,
char *buf, size_t count)
{
struct atmel_uart_port *atmel_port = to_atmel_uart_port(port);
struct tty_port *tport = &port->state->port;
dma_sync_sg_for_cpu(port->dev,
&atmel_port->sg_rx,
1,
DMA_DEV_TO_MEM);
tty_insert_flip_string(tport, buf, count);
dma_sync_sg_for_device(port->dev,
&atmel_port->sg_rx,
1,
DMA_DEV_TO_MEM);
/*
* Drop the lock here since it might end up calling
* uart_start(), which takes the lock.
*/
spin_unlock(&port->lock);
tty_flip_buffer_push(tport);
spin_lock(&port->lock);
}
static void atmel_complete_rx_dma(void *arg)
{
struct uart_port *port = arg;
struct atmel_uart_port *atmel_port = to_atmel_uart_port(port);
tasklet_schedule(&atmel_port->tasklet);
}
static void atmel_release_rx_dma(struct uart_port *port)
{
struct atmel_uart_port *atmel_port = to_atmel_uart_port(port);
struct dma_chan *chan = atmel_port->chan_rx;
if (chan) {
dmaengine_terminate_all(chan);
dma_release_channel(chan);
dma_unmap_sg(port->dev, &atmel_port->sg_rx, 1,
DMA_DEV_TO_MEM);
}
atmel_port->desc_rx = NULL;
atmel_port->chan_rx = NULL;
atmel_port->cookie_rx = -EINVAL;
}
static void atmel_rx_from_dma(struct uart_port *port)
{
struct atmel_uart_port *atmel_port = to_atmel_uart_port(port);
struct circ_buf *ring = &atmel_port->rx_ring;
struct dma_chan *chan = atmel_port->chan_rx;
struct dma_tx_state state;
enum dma_status dmastat;
size_t pending, count;
/* Reset the UART timeout early so that we don't miss one */
UART_PUT_CR(port, ATMEL_US_STTTO);
dmastat = dmaengine_tx_status(chan,
atmel_port->cookie_rx,
&state);
/* Restart a new tasklet if DMA status is error */
if (dmastat == DMA_ERROR) {
dev_dbg(port->dev, "Get residue error, restart tasklet\n");
UART_PUT_IER(port, ATMEL_US_TIMEOUT);
tasklet_schedule(&atmel_port->tasklet);
return;
}
/* current transfer size should no larger than dma buffer */
pending = sg_dma_len(&atmel_port->sg_rx) - state.residue;
BUG_ON(pending > sg_dma_len(&atmel_port->sg_rx));
/*
* This will take the chars we have so far,
* ring->head will record the transfer size, only new bytes come
* will insert into the framework.
*/
if (pending > ring->head) {
count = pending - ring->head;
atmel_flip_buffer_rx_dma(port, ring->buf + ring->head, count);
ring->head += count;
if (ring->head == sg_dma_len(&atmel_port->sg_rx))
ring->head = 0;
port->icount.rx += count;
}
UART_PUT_IER(port, ATMEL_US_TIMEOUT);
}
static int atmel_prepare_rx_dma(struct uart_port *port)
{
struct atmel_uart_port *atmel_port = to_atmel_uart_port(port);
struct dma_async_tx_descriptor *desc;
dma_cap_mask_t mask;
struct dma_slave_config config;
struct circ_buf *ring;
int ret, nent;
ring = &atmel_port->rx_ring;
dma_cap_zero(mask);
dma_cap_set(DMA_CYCLIC, mask);
atmel_port->chan_rx = dma_request_slave_channel(port->dev, "rx");
if (atmel_port->chan_rx == NULL)
goto chan_err;
dev_info(port->dev, "using %s for rx DMA transfers\n",
dma_chan_name(atmel_port->chan_rx));
spin_lock_init(&atmel_port->lock_rx);
sg_init_table(&atmel_port->sg_rx, 1);
/* UART circular rx buffer is an aligned page. */
BUG_ON((int)port->state->xmit.buf & ~PAGE_MASK);
sg_set_page(&atmel_port->sg_rx,
virt_to_page(ring->buf),
ATMEL_SERIAL_RINGSIZE,
(int)ring->buf & ~PAGE_MASK);
nent = dma_map_sg(port->dev,
&atmel_port->sg_rx,
1,
DMA_DEV_TO_MEM);
if (!nent) {
dev_dbg(port->dev, "need to release resource of dma\n");
goto chan_err;
} else {
dev_dbg(port->dev, "%s: mapped %d@%p to %x\n", __func__,
sg_dma_len(&atmel_port->sg_rx),
ring->buf,
sg_dma_address(&atmel_port->sg_rx));
}
/* Configure the slave DMA */
memset(&config, 0, sizeof(config));
config.direction = DMA_DEV_TO_MEM;
config.src_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE;
config.src_addr = port->mapbase + ATMEL_US_RHR;
ret = dmaengine_device_control(atmel_port->chan_rx,
DMA_SLAVE_CONFIG,
(unsigned long)&config);
if (ret) {
dev_err(port->dev, "DMA rx slave configuration failed\n");
goto chan_err;
}
/*
* Prepare a cyclic dma transfer, assign 2 descriptors,
* each one is half ring buffer size
*/
desc = dmaengine_prep_dma_cyclic(atmel_port->chan_rx,
sg_dma_address(&atmel_port->sg_rx),
sg_dma_len(&atmel_port->sg_rx),
sg_dma_len(&atmel_port->sg_rx)/2,
DMA_DEV_TO_MEM,
DMA_PREP_INTERRUPT);
desc->callback = atmel_complete_rx_dma;
desc->callback_param = port;
atmel_port->desc_rx = desc;
atmel_port->cookie_rx = dmaengine_submit(desc);
return 0;
chan_err:
dev_err(port->dev, "RX channel not available, switch to pio\n");
atmel_port->use_dma_rx = 0;
if (atmel_port->chan_rx)
atmel_release_rx_dma(port);
return -EINVAL;
}
/* /*
* receive interrupt handler. * receive interrupt handler.
*/ */
...@@ -785,6 +976,13 @@ atmel_handle_receive(struct uart_port *port, unsigned int pending) ...@@ -785,6 +976,13 @@ atmel_handle_receive(struct uart_port *port, unsigned int pending)
atmel_pdc_rxerr(port, pending); atmel_pdc_rxerr(port, pending);
} }
if (atmel_use_dma_rx(port)) {
if (pending & ATMEL_US_TIMEOUT) {
UART_PUT_IDR(port, ATMEL_US_TIMEOUT);
tasklet_schedule(&atmel_port->tasklet);
}
}
/* Interrupt receive */ /* Interrupt receive */
if (pending & ATMEL_US_RXRDY) if (pending & ATMEL_US_RXRDY)
atmel_rx_chars(port); atmel_rx_chars(port);
...@@ -1176,7 +1374,11 @@ static void atmel_set_ops(struct uart_port *port) ...@@ -1176,7 +1374,11 @@ static void atmel_set_ops(struct uart_port *port)
{ {
struct atmel_uart_port *atmel_port = to_atmel_uart_port(port); struct atmel_uart_port *atmel_port = to_atmel_uart_port(port);
if (atmel_use_pdc_rx(port)) { if (atmel_use_dma_rx(port)) {
atmel_port->prepare_rx = &atmel_prepare_rx_dma;
atmel_port->schedule_rx = &atmel_rx_from_dma;
atmel_port->release_rx = &atmel_release_rx_dma;
} else if (atmel_use_pdc_rx(port)) {
atmel_port->prepare_rx = &atmel_prepare_rx_pdc; atmel_port->prepare_rx = &atmel_prepare_rx_pdc;
atmel_port->schedule_rx = &atmel_rx_from_pdc; atmel_port->schedule_rx = &atmel_rx_from_pdc;
atmel_port->release_rx = &atmel_release_rx_pdc; atmel_port->release_rx = &atmel_release_rx_pdc;
...@@ -1272,6 +1474,11 @@ static int atmel_startup(struct uart_port *port) ...@@ -1272,6 +1474,11 @@ static int atmel_startup(struct uart_port *port)
UART_PUT_IER(port, ATMEL_US_ENDRX | ATMEL_US_TIMEOUT); UART_PUT_IER(port, ATMEL_US_ENDRX | ATMEL_US_TIMEOUT);
/* enable PDC controller */ /* enable PDC controller */
UART_PUT_PTCR(port, ATMEL_PDC_RXTEN); UART_PUT_PTCR(port, ATMEL_PDC_RXTEN);
} else if (atmel_use_dma_rx(port)) {
UART_PUT_RTOR(port, PDC_RX_TIMEOUT);
UART_PUT_CR(port, ATMEL_US_STTTO);
UART_PUT_IER(port, ATMEL_US_TIMEOUT);
} else { } else {
/* enable receive only */ /* enable receive only */
UART_PUT_IER(port, ATMEL_US_RXRDY); UART_PUT_IER(port, ATMEL_US_RXRDY);
...@@ -1676,10 +1883,18 @@ static void atmel_of_init_port(struct atmel_uart_port *atmel_port, ...@@ -1676,10 +1883,18 @@ static void atmel_of_init_port(struct atmel_uart_port *atmel_port,
u32 rs485_delay[2]; u32 rs485_delay[2];
/* DMA/PDC usage specification */ /* DMA/PDC usage specification */
if (of_get_property(np, "atmel,use-dma-rx", NULL)) if (of_get_property(np, "atmel,use-dma-rx", NULL)) {
atmel_port->use_pdc_rx = true; if (of_get_property(np, "dmas", NULL)) {
else atmel_port->use_dma_rx = true;
atmel_port->use_pdc_rx = false;
} else {
atmel_port->use_dma_rx = false;
atmel_port->use_pdc_rx = true;
}
} else {
atmel_port->use_dma_rx = false;
atmel_port->use_pdc_rx = false; atmel_port->use_pdc_rx = false;
}
if (of_get_property(np, "atmel,use-dma-tx", NULL)) { if (of_get_property(np, "atmel,use-dma-tx", NULL)) {
if (of_get_property(np, "dmas", NULL)) { if (of_get_property(np, "dmas", NULL)) {
...@@ -1726,6 +1941,7 @@ static int atmel_init_port(struct atmel_uart_port *atmel_port, ...@@ -1726,6 +1941,7 @@ static int atmel_init_port(struct atmel_uart_port *atmel_port,
} else { } else {
atmel_port->use_pdc_rx = pdata->use_dma_rx; atmel_port->use_pdc_rx = pdata->use_dma_rx;
atmel_port->use_pdc_tx = pdata->use_dma_tx; atmel_port->use_pdc_tx = pdata->use_dma_tx;
atmel_port->use_dma_rx = false;
atmel_port->use_dma_tx = false; atmel_port->use_dma_tx = false;
atmel_port->rs485 = pdata->rs485; atmel_port->rs485 = pdata->rs485;
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment