From patchwork Sun Jul 6 04:05:09 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: 4489281 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 BAB629FBD2 for ; Sun, 6 Jul 2014 04:10:44 +0000 (UTC) Received: from mail.kernel.org (localhost [127.0.0.1]) by mail.kernel.org (Postfix) with ESMTP id 95E562041B for ; Sun, 6 Jul 2014 04:10:43 +0000 (UTC) Received: from bombadil.infradead.org (bombadil.infradead.org [198.137.202.9]) (using TLSv1.2 with cipher DHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.kernel.org (Postfix) with ESMTPS id C7552203EB for ; Sun, 6 Jul 2014 04:10:41 +0000 (UTC) Received: from localhost ([127.0.0.1] helo=bombadil.infradead.org) by bombadil.infradead.org with esmtp (Exim 4.80.1 #2 (Red Hat Linux)) id 1X3djW-0007vn-PW; Sun, 06 Jul 2014 04:07:22 +0000 Received: from yotta.elopez.com.ar ([2a00:1768:1004:d00d:c0de:4:f00d:cafe]) by bombadil.infradead.org with esmtps (Exim 4.80.1 #2 (Red Hat Linux)) id 1X3djM-0007qK-71 for linux-arm-kernel@lists.infradead.org; Sun, 06 Jul 2014 04:07:13 +0000 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=DlwQua1RqHdE1m1JEKH/B1YucqCAvwmDENcxSLQTgG4=; b=rwvi6tkh2FzhbQCi+/Pf98sye2ZtRzty8yhnIPfC4GW9McxaT6cNdL8kw20irlVWfwvY+tC7ZjThNR08d3mE6RPj0TyIrOgGAadFYtUFcQ43d4Y2/l9Ol7nB5nw2DZjcvqrQwvLVrDygYQdF/x5fAAH6EfK7v4DP8hk2O9uHZUzev9ebWsgcSwJJgfP6LMpa2+2n5msjMxBq36DppBfV3XU6NYIb9VmTlSogG+KstA4Tx6m08SpbbTAXAQGnCiurls7thEIwMdaJ/Xs2j0Wze2GZ49pEv7OhiVkEtX3h0I2VKyGImnjJv4vxXO8pT8Dn/bQ41NixH70yKmjzDCPSxA==; 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.82_1-5b7a7c0-XX) id 1X3dio-0001kf-CC; Sun, 06 Jul 2014 01:06:38 -0300 From: =?UTF-8?q?Emilio=20L=C3=B3pez?= To: Maxime Ripard , vinod.koul@intel.com, dan.j.williams@intel.com Subject: [PATCH v2 2/8] spi: sun4i: add DMA support Date: Sun, 6 Jul 2014 01:05:09 -0300 Message-Id: <1404619518-7592-3-git-send-email-emilio@elopez.com.ar> X-Mailer: git-send-email 2.0.1 In-Reply-To: <1404619518-7592-1-git-send-email-emilio@elopez.com.ar> References: <1404619518-7592-1-git-send-email-emilio@elopez.com.ar> MIME-Version: 1.0 X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20140705_210712_632242_FC1AF77A X-CRM114-Status: GOOD ( 19.45 ) X-Spam-Score: -0.1 (/) Cc: =?UTF-8?q?Emilio=20L=C3=B3pez?= , zhuzhenhua@allwinnertech.com, shuge@allwinnertech.com, Chen-Yu Tsai , kevin.z.m.zh@gmail.com, Ezequiel Garcia , sunny@allwinnertech.com, dmaengine@vger.kernel.org, linux-arm-kernel@lists.infradead.org X-BeenThere: linux-arm-kernel@lists.infradead.org X-Mailman-Version: 2.1.18-1 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=-1.8 required=5.0 tests=BAYES_00,DKIM_SIGNED, RP_MATCHES_RCVD, T_DKIM_INVALID, UNPARSEABLE_RELAY autolearn=no 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 DMA support for 64 byte transfers on the sun4i SPI controller. Bigger transfers did not seem to work when tested, and if the hardware actually supports those, it will need further investigation as to how to issue them correctly. Signed-off-by: Emilio López --- 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; }