From patchwork Tue Jul 16 02:43:43 2013 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Song, Elen" X-Patchwork-Id: 2827864 Return-Path: X-Original-To: patchwork-linux-arm@patchwork.kernel.org Delivered-To: patchwork-parsemail@patchwork1.web.kernel.org Received: from mail.kernel.org (mail.kernel.org [198.145.19.201]) by patchwork1.web.kernel.org (Postfix) with ESMTP id 31ABC9F967 for ; Tue, 16 Jul 2013 02:53:57 +0000 (UTC) Received: from mail.kernel.org (localhost [127.0.0.1]) by mail.kernel.org (Postfix) with ESMTP id 06D9C20265 for ; Tue, 16 Jul 2013 02:53:56 +0000 (UTC) Received: from casper.infradead.org (casper.infradead.org [85.118.1.10]) (using TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits)) (No client certificate requested) by mail.kernel.org (Postfix) with ESMTPS id A82DD20266 for ; Tue, 16 Jul 2013 02:53:54 +0000 (UTC) Received: from merlin.infradead.org ([2001:4978:20e::2]) by casper.infradead.org with esmtps (Exim 4.80.1 #2 (Red Hat Linux)) id 1UyvMG-0002XY-O3; Tue, 16 Jul 2013 02:51:22 +0000 Received: from localhost ([::1] helo=merlin.infradead.org) by merlin.infradead.org with esmtp (Exim 4.80.1 #2 (Red Hat Linux)) id 1UyvLq-0006zB-DZ; Tue, 16 Jul 2013 02:50:54 +0000 Received: from nasmtp01.atmel.com ([192.199.1.246] helo=DVREDG02.corp.atmel.com) by merlin.infradead.org with esmtps (Exim 4.80.1 #2 (Red Hat Linux)) id 1UyvLm-0006yQ-TX for linux-arm-kernel@lists.infradead.org; Tue, 16 Jul 2013 02:50:51 +0000 Received: from apsmtp01.atmel.com (10.168.254.31) by DVREDG02.corp.atmel.com (10.42.103.31) with Microsoft SMTP Server (TLS) id 14.2.342.3; Mon, 15 Jul 2013 20:50:27 -0600 Received: from shaarm01.corp.atmel.com (10.168.254.13) by apsmtp01.atmel.com (10.168.254.31) with Microsoft SMTP Server id 14.2.342.3; Tue, 16 Jul 2013 10:50:46 +0800 From: Elen Song To: , , Subject: [PATCH V2 4/8] serial: at91: add rx dma support Date: Tue, 16 Jul 2013 10:43:43 +0800 Message-ID: <1373942627-7121-5-git-send-email-elen.song@atmel.com> X-Mailer: git-send-email 1.7.9.5 In-Reply-To: <1373942627-7121-1-git-send-email-elen.song@atmel.com> References: <1373942627-7121-1-git-send-email-elen.song@atmel.com> MIME-Version: 1.0 X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20130715_225051_199626_75D57444 X-CRM114-Status: GOOD ( 19.51 ) X-Spam-Score: -2.3 (--) Cc: gregkh@linuxfoundation.org, elen.song@atmel.com, linux-arm-kernel@lists.infradead.org X-BeenThere: linux-arm-kernel@lists.infradead.org X-Mailman-Version: 2.1.15 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: "linux-arm-kernel" Errors-To: linux-arm-kernel-bounces+patchwork-linux-arm=patchwork.kernel.org@lists.infradead.org X-Spam-Status: No, score=-4.6 required=5.0 tests=BAYES_00, RCVD_IN_DNSWL_MED, RP_MATCHES_RCVD, UNPARSEABLE_RELAY autolearn=unavailable version=3.3.1 X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on mail.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP 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: Elen Song Signed-off-by: Ludovic Desroches --- drivers/tty/serial/atmel_serial.c | 224 ++++++++++++++++++++++++++++++++++++- 1 file changed, 220 insertions(+), 4 deletions(-) diff --git a/drivers/tty/serial/atmel_serial.c b/drivers/tty/serial/atmel_serial.c index 1176daa..298b58c 100644 --- a/drivers/tty/serial/atmel_serial.c +++ b/drivers/tty/serial/atmel_serial.c @@ -140,6 +140,7 @@ struct atmel_uart_port { u32 backup_imr; /* IMR saved during suspend */ int break_active; /* break being received */ + short use_dma_rx; /* enable DMA receiver */ short use_pdc_rx; /* enable PDC receiver */ short pdc_rx_idx; /* current PDC RX buffer */ struct atmel_dma_buffer pdc_rx[2]; /* PDC receier */ @@ -149,10 +150,15 @@ struct atmel_uart_port { struct atmel_dma_buffer pdc_tx; /* PDC transmitter */ spinlock_t lock_tx; /* port lock */ + spinlock_t lock_rx; /* port lock */ struct dma_chan *chan_tx; + struct dma_chan *chan_rx; struct dma_async_tx_descriptor *desc_tx; + struct dma_async_tx_descriptor *desc_rx; dma_cookie_t cookie_tx; + dma_cookie_t cookie_rx; struct scatterlist sg_tx; + struct scatterlist sg_rx; struct tasklet_struct tasklet; unsigned int irq_status; unsigned int irq_status_prev; @@ -225,6 +231,13 @@ static bool atmel_use_dma_tx(struct uart_port *port) 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 */ void atmel_config_rs485(struct uart_port *port, struct serial_rs485 *rs485conf) { @@ -759,6 +772,184 @@ chan_err: 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. */ @@ -786,6 +977,13 @@ atmel_handle_receive(struct uart_port *port, unsigned int 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 */ if (pending & ATMEL_US_RXRDY) atmel_rx_chars(port); @@ -1177,7 +1375,11 @@ static void atmel_set_ops(struct 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->schedule_rx = &atmel_rx_from_pdc; atmel_port->release_rx = &atmel_release_rx_pdc; @@ -1273,6 +1475,11 @@ static int atmel_startup(struct uart_port *port) UART_PUT_IER(port, ATMEL_US_ENDRX | ATMEL_US_TIMEOUT); /* enable PDC controller */ 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 { /* enable receive only */ UART_PUT_IER(port, ATMEL_US_RXRDY); @@ -1677,10 +1884,18 @@ static void atmel_of_init_port(struct atmel_uart_port *atmel_port, u32 rs485_delay[2]; /* DMA/PDC usage specification */ - if (of_get_property(np, "atmel,use-dma-rx", NULL)) - atmel_port->use_pdc_rx = 1; - else + if (of_get_property(np, "atmel,use-dma-rx", NULL)) { + if (of_get_property(np, "dmas", NULL)) { + atmel_port->use_dma_rx = 1; + atmel_port->use_pdc_rx = 0; + } else { + atmel_port->use_dma_rx = 0; + atmel_port->use_pdc_rx = 1; + } + } else { + atmel_port->use_dma_rx = 0; atmel_port->use_pdc_rx = 0; + } if (of_get_property(np, "atmel,use-dma-tx", NULL)) { if (of_get_property(np, "dmas", NULL)) { @@ -1726,6 +1941,7 @@ static void atmel_init_port(struct atmel_uart_port *atmel_port, } else { atmel_port->use_pdc_rx = pdata->use_dma_rx; atmel_port->use_pdc_tx = pdata->use_dma_tx; + atmel_port->use_dma_rx = 0; atmel_port->use_dma_tx = 0; atmel_port->rs485 = pdata->rs485; }