From patchwork Mon Aug 4 20:09:56 2014 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Emilio_L=C3=B3pez?= X-Patchwork-Id: 4673461 Return-Path: X-Original-To: patchwork-linux-spi@patchwork.kernel.org Delivered-To: patchwork-parsemail@patchwork2.web.kernel.org Received: from mail.kernel.org (mail.kernel.org [198.145.19.201]) by patchwork2.web.kernel.org (Postfix) with ESMTP id 89A74C0338 for ; Mon, 4 Aug 2014 20:12:02 +0000 (UTC) Received: from mail.kernel.org (localhost [127.0.0.1]) by mail.kernel.org (Postfix) with ESMTP id 3F5D72013D for ; Mon, 4 Aug 2014 20:12:01 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.kernel.org (Postfix) with ESMTP id EB9F32014A for ; Mon, 4 Aug 2014 20:11:58 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1751330AbaHDUL5 (ORCPT ); Mon, 4 Aug 2014 16:11:57 -0400 Received: from yotta.elopez.com.ar ([31.220.24.173]:53727 "EHLO yotta.elopez.com.ar" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1751311AbaHDUL4 (ORCPT ); Mon, 4 Aug 2014 16:11:56 -0400 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=elopez.com.ar; s=mail; h=Content-Transfer-Encoding:Content-Type:MIME-Version:References:In-Reply-To:Message-Id:Date:Subject:Cc:To:From; bh=3838BT1mT3tkoFKMphSlE2OYbwRUGuvzqHaRT5wGI4c=; b=PzETF99C2wVEtM4So0SRoNeM0DHAUXK5LD2FXhWI3ONNe+S2JLUZRbALa66nTGFf6NkftEp0USpJO3iMyJiC1VHzrxEWJHwkEXB2Y9JRvtp4sDhjNh2x7SsorbsZ1CBmGQAUWfvwliSXP4qg8rV9drJx97+bplmQ5Bax2jkkiSM6UoM1hX43lS/1uCsv4TIvIfdQt5xlDRJzqL3BvLvV1D0o1MPQ4AaPFuBBPmx+8TGN6C5S5caFjQLNZ+M0FdRaomwziHoJgt/uFGMaG0zomZNIyJ5GMRVRyS6ikxGHFCu806nnG1pADrqVwAhxV9UdQu6lsgH7rGR4KymtCHbJVg==; Received: from [181.164.71.8] (helo=desktop.lan) by yotta.elopez.com.ar with esmtpsa (TLS1.2:ECDHE_RSA_AES_128_CBC_SHA256:128) (Exim 4.83) id 1XEObO-0007yF-KG; Mon, 04 Aug 2014 17:11:26 -0300 From: =?UTF-8?q?Emilio=20L=C3=B3pez?= To: Maxime Ripard , vinod.koul@intel.com, dan.j.williams@intel.com, broonie@kernel.org Cc: , , , sunny@allwinnertech.com, kevin.z.m.zh@gmail.com, shuge@allwinnertech.com, zhuzhenhua@allwinnertech.com, =?UTF-8?q?Emilio=20L=C3=B3pez?= Subject: [PATCH v3 2/8] spi: sun4i: add DMA support Date: Mon, 4 Aug 2014 17:09:56 -0300 Message-Id: <1407183002-29420-3-git-send-email-emilio@elopez.com.ar> X-Mailer: git-send-email 2.0.4 In-Reply-To: <1407183002-29420-1-git-send-email-emilio@elopez.com.ar> References: <1407183002-29420-1-git-send-email-emilio@elopez.com.ar> MIME-Version: 1.0 Sender: linux-spi-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-spi@vger.kernel.org X-Spam-Status: No, score=-7.5 required=5.0 tests=BAYES_00,DKIM_SIGNED, RCVD_IN_DNSWL_HI,RP_MATCHES_RCVD,T_DKIM_INVALID,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 This patch adds support for 64 byte or bigger transfers on the sun4i SPI controller. Said transfers will be performed via DMA. Signed-off-by: Emilio López --- Changes from v2: * Fix commit message Changes from v1: * remove 64 byte limitation on transfers * wrap stuff so it fits better in 80 cols drivers/spi/spi-sun4i.c | 155 ++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 144 insertions(+), 11 deletions(-) diff --git a/drivers/spi/spi-sun4i.c b/drivers/spi/spi-sun4i.c index 85204c9..e674100 100644 --- a/drivers/spi/spi-sun4i.c +++ b/drivers/spi/spi-sun4i.c @@ -14,6 +14,8 @@ #include #include #include +#include +#include #include #include #include @@ -34,6 +36,7 @@ #define SUN4I_CTL_CPHA BIT(2) #define SUN4I_CTL_CPOL BIT(3) #define SUN4I_CTL_CS_ACTIVE_LOW BIT(4) +#define SUN4I_CTL_DMAMC_DEDICATED BIT(5) #define SUN4I_CTL_LMTF BIT(6) #define SUN4I_CTL_TF_RST BIT(8) #define SUN4I_CTL_RF_RST BIT(9) @@ -51,6 +54,8 @@ #define SUN4I_INT_STA_REG 0x10 #define SUN4I_DMA_CTL_REG 0x14 +#define SUN4I_DMA_CTL_RF_READY BIT(0) +#define SUN4I_DMA_CTL_TF_NOT_FULL BIT(10) #define SUN4I_WAIT_REG 0x18 @@ -84,6 +89,9 @@ struct sun4i_spi { const u8 *tx_buf; u8 *rx_buf; int len; + + struct dma_chan *rx_dma_chan; + struct dma_chan *tx_dma_chan; }; static inline u32 sun4i_spi_read(struct sun4i_spi *sspi, u32 reg) @@ -130,6 +138,24 @@ static inline void sun4i_spi_fill_fifo(struct sun4i_spi *sspi, int len) } } +static bool sun4i_spi_can_dma(struct spi_master *master, + struct spi_device *spi, + struct spi_transfer *tfr) +{ + return tfr->len >= SUN4I_FIFO_DEPTH; +} + +static int sun4i_spi_prepare_message(struct spi_master *master, + struct spi_message *msg) +{ + struct sun4i_spi *sspi = spi_master_get_devdata(master); + + master->dma_rx = sspi->rx_dma_chan; + master->dma_tx = sspi->tx_dma_chan; + + return 0; +} + static void sun4i_spi_set_cs(struct spi_device *spi, bool enable) { struct sun4i_spi *sspi = spi_master_get_devdata(spi->master); @@ -169,15 +195,12 @@ static int sun4i_spi_transfer_one(struct spi_master *master, struct spi_transfer *tfr) { struct sun4i_spi *sspi = spi_master_get_devdata(master); + struct dma_async_tx_descriptor *desc_tx = NULL, *desc_rx = NULL; unsigned int mclk_rate, div, timeout; unsigned int tx_len = 0; int ret = 0; u32 reg; - /* We don't support transfer larger than the FIFO */ - if (tfr->len > SUN4I_FIFO_DEPTH) - return -EINVAL; - reinit_completion(&sspi->done); sspi->tx_buf = tfr->tx_buf; sspi->rx_buf = tfr->rx_buf; @@ -186,7 +209,6 @@ static int sun4i_spi_transfer_one(struct spi_master *master, /* Clear pending interrupts */ sun4i_spi_write(sspi, SUN4I_INT_STA_REG, ~0); - reg = sun4i_spi_read(sspi, SUN4I_CTL_REG); /* Reset FIFOs */ @@ -269,12 +291,63 @@ static int sun4i_spi_transfer_one(struct spi_master *master, sun4i_spi_write(sspi, SUN4I_BURST_CNT_REG, SUN4I_BURST_CNT(tfr->len)); sun4i_spi_write(sspi, SUN4I_XMIT_CNT_REG, SUN4I_XMIT_CNT(tx_len)); - /* Fill the TX FIFO */ - sun4i_spi_fill_fifo(sspi, SUN4I_FIFO_DEPTH); - /* Enable the interrupts */ sun4i_spi_write(sspi, SUN4I_INT_CTL_REG, SUN4I_INT_CTL_TC); + if (sun4i_spi_can_dma(master, spi, tfr)) { + dev_dbg(&sspi->master->dev, "Using DMA mode for transfer\n"); + + if (sspi->tx_buf) { + desc_tx = dmaengine_prep_slave_sg(sspi->tx_dma_chan, + tfr->tx_sg.sgl, tfr->tx_sg.nents, + DMA_TO_DEVICE, + DMA_PREP_INTERRUPT | DMA_CTRL_ACK); + if (!desc_tx) { + dev_err(&sspi->master->dev, + "Couldn't prepare dma slave\n"); + return -EIO; + } + + dmaengine_submit(desc_tx); + } + + if (sspi->rx_buf) { + desc_rx = dmaengine_prep_slave_sg(sspi->rx_dma_chan, + tfr->rx_sg.sgl, tfr->rx_sg.nents, + DMA_FROM_DEVICE, + DMA_PREP_INTERRUPT | DMA_CTRL_ACK); + if (!desc_rx) { + dev_err(&sspi->master->dev, + "Couldn't prepare dma slave\n"); + return -EIO; + } + + dmaengine_submit(desc_rx); + } + + /* Enable DMA requests */ + reg = sun4i_spi_read(sspi, SUN4I_CTL_REG); + sun4i_spi_write(sspi, SUN4I_CTL_REG, + reg | SUN4I_CTL_DMAMC_DEDICATED); + sun4i_spi_write(sspi, SUN4I_DMA_CTL_REG, + SUN4I_DMA_CTL_TF_NOT_FULL | + SUN4I_DMA_CTL_RF_READY); + + dma_async_issue_pending(sspi->rx_dma_chan); + dma_async_issue_pending(sspi->tx_dma_chan); + } else { + dev_dbg(&sspi->master->dev, "Using PIO mode for transfer\n"); + + /* Disable DMA requests */ + reg = sun4i_spi_read(sspi, SUN4I_CTL_REG); + sun4i_spi_write(sspi, SUN4I_CTL_REG, + reg & ~SUN4I_CTL_DMAMC_DEDICATED); + sun4i_spi_write(sspi, SUN4I_DMA_CTL_REG, 0); + + /* Fill the TX FIFO */ + sun4i_spi_fill_fifo(sspi, SUN4I_FIFO_DEPTH); + } + /* Start the transfer */ reg = sun4i_spi_read(sspi, SUN4I_CTL_REG); sun4i_spi_write(sspi, SUN4I_CTL_REG, reg | SUN4I_CTL_XCH); @@ -286,7 +359,12 @@ static int sun4i_spi_transfer_one(struct spi_master *master, goto out; } - sun4i_spi_drain_fifo(sspi, SUN4I_FIFO_DEPTH); + if (sun4i_spi_can_dma(master, spi, tfr) && desc_rx) { + /* The receive transfer should be the last one to finish */ + dma_wait_for_async_tx(desc_rx); + } else { + sun4i_spi_drain_fifo(sspi, SUN4I_FIFO_DEPTH); + } out: sun4i_spi_write(sspi, SUN4I_INT_CTL_REG, 0); @@ -351,6 +429,7 @@ static int sun4i_spi_runtime_suspend(struct device *dev) static int sun4i_spi_probe(struct platform_device *pdev) { + struct dma_slave_config dma_sconfig; struct spi_master *master; struct sun4i_spi *sspi; struct resource *res; @@ -386,7 +465,10 @@ static int sun4i_spi_probe(struct platform_device *pdev) goto err_free_master; } + init_completion(&sspi->done); sspi->master = master; + master->can_dma = sun4i_spi_can_dma; + master->prepare_message = sun4i_spi_prepare_message; master->set_cs = sun4i_spi_set_cs; master->transfer_one = sun4i_spi_transfer_one; master->num_chipselect = 4; @@ -409,7 +491,45 @@ static int sun4i_spi_probe(struct platform_device *pdev) goto err_free_master; } - init_completion(&sspi->done); + sspi->tx_dma_chan = dma_request_slave_channel_reason(&pdev->dev, "tx"); + if (IS_ERR(sspi->tx_dma_chan)) { + dev_err(&pdev->dev, "Unable to acquire DMA channel TX\n"); + ret = PTR_ERR(sspi->tx_dma_chan); + goto err_free_master; + } + + dma_sconfig.direction = DMA_MEM_TO_DEV; + dma_sconfig.src_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE; + dma_sconfig.dst_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE; + dma_sconfig.dst_addr = res->start + SUN4I_TXDATA_REG; + dma_sconfig.src_maxburst = 1; + dma_sconfig.dst_maxburst = 1; + + ret = dmaengine_slave_config(sspi->tx_dma_chan, &dma_sconfig); + if (ret) { + dev_err(&pdev->dev, "Unable to configure TX DMA slave\n"); + goto err_tx_dma_release; + } + + sspi->rx_dma_chan = dma_request_slave_channel_reason(&pdev->dev, "rx"); + if (IS_ERR(sspi->rx_dma_chan)) { + dev_err(&pdev->dev, "Unable to acquire DMA channel RX\n"); + ret = PTR_ERR(sspi->rx_dma_chan); + goto err_tx_dma_release; + } + + dma_sconfig.direction = DMA_DEV_TO_MEM; + dma_sconfig.src_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE; + dma_sconfig.dst_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE; + dma_sconfig.src_addr = res->start + SUN4I_RXDATA_REG; + dma_sconfig.src_maxburst = 1; + dma_sconfig.dst_maxburst = 1; + + ret = dmaengine_slave_config(sspi->rx_dma_chan, &dma_sconfig); + if (ret) { + dev_err(&pdev->dev, "Unable to configure RX DMA slave\n"); + goto err_rx_dma_release; + } /* * This wake-up/shutdown pattern is to be able to have the @@ -418,7 +538,7 @@ static int sun4i_spi_probe(struct platform_device *pdev) ret = sun4i_spi_runtime_resume(&pdev->dev); if (ret) { dev_err(&pdev->dev, "Couldn't resume the device\n"); - goto err_free_master; + goto err_rx_dma_release; } pm_runtime_set_active(&pdev->dev); @@ -436,6 +556,10 @@ static int sun4i_spi_probe(struct platform_device *pdev) err_pm_disable: pm_runtime_disable(&pdev->dev); sun4i_spi_runtime_suspend(&pdev->dev); +err_rx_dma_release: + dma_release_channel(sspi->rx_dma_chan); +err_tx_dma_release: + dma_release_channel(sspi->tx_dma_chan); err_free_master: spi_master_put(master); return ret; @@ -443,8 +567,17 @@ err_free_master: static int sun4i_spi_remove(struct platform_device *pdev) { + struct spi_master *master = platform_get_drvdata(pdev); + struct sun4i_spi *sspi = spi_master_get_devdata(master); + + if (pm_runtime_active(&pdev->dev)) + sun4i_spi_runtime_suspend(&pdev->dev); + pm_runtime_disable(&pdev->dev); + dma_release_channel(sspi->rx_dma_chan); + dma_release_channel(sspi->tx_dma_chan); + return 0; }