From patchwork Mon Jul 22 08:30:27 2013 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Song, Elen" X-Patchwork-Id: 2831178 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 6CA789F4D4 for ; Mon, 22 Jul 2013 09:24:53 +0000 (UTC) Received: from mail.kernel.org (localhost [127.0.0.1]) by mail.kernel.org (Postfix) with ESMTP id CE42D200FF for ; Mon, 22 Jul 2013 09:24:51 +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 8EC19200F4 for ; Mon, 22 Jul 2013 09:24:48 +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 1V1BdU-0002Kn-NC; Mon, 22 Jul 2013 08:38:31 +0000 Received: from localhost ([::1] helo=merlin.infradead.org) by merlin.infradead.org with esmtp (Exim 4.80.1 #2 (Red Hat Linux)) id 1V1BcQ-0001Pr-B8; Mon, 22 Jul 2013 08:37:22 +0000 Received: from eusmtp01.atmel.com ([212.144.249.242]) by merlin.infradead.org with esmtps (Exim 4.80.1 #2 (Red Hat Linux)) id 1V1BcM-0001Mv-Fx for linux-arm-kernel@lists.infradead.org; Mon, 22 Jul 2013 08:37:19 +0000 Received: from apsmtp01.atmel.com (10.168.254.31) by eusmtp01.atmel.com (10.161.101.30) with Microsoft SMTP Server id 14.2.342.3; Mon, 22 Jul 2013 10:36:56 +0200 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; Mon, 22 Jul 2013 16:38:48 +0800 From: Elen Song To: , , Subject: [PATCH 4/8 V3] serial: at91: add rx dma support Date: Mon, 22 Jul 2013 16:30:27 +0800 Message-ID: <1374481831-24573-5-git-send-email-elen.song@atmel.com> X-Mailer: git-send-email 1.7.9.5 In-Reply-To: <1374481831-24573-1-git-send-email-elen.song@atmel.com> References: <1374481831-24573-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-20130722_043718_976276_6BFB95B6 X-CRM114-Status: GOOD ( 18.75 ) X-Spam-Score: -3.4 (---) 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=-5.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 b8d8594..0c15c5b 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 */ + bool use_dma_rx; /* enable DMA receiver */ bool 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 = true; - else + if (of_get_property(np, "atmel,use-dma-rx", NULL)) { + if (of_get_property(np, "dmas", NULL)) { + 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; + } 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 = false; atmel_port->use_dma_tx = false; atmel_port->rs485 = pdata->rs485; }