From patchwork Thu Oct 15 15:47:40 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Alexander Kochetkov X-Patchwork-Id: 11839553 Return-Path: Received: from mail.kernel.org (pdx-korg-mail-1.web.codeaurora.org [172.30.200.123]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 6DB421744 for ; Thu, 15 Oct 2020 15:48:05 +0000 (UTC) Received: from merlin.infradead.org (merlin.infradead.org [205.233.59.134]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.kernel.org (Postfix) with ESMTPS id 143AA21D41 for ; Thu, 15 Oct 2020 15:48:05 +0000 (UTC) Authentication-Results: mail.kernel.org; dkim=pass (2048-bit key) header.d=lists.infradead.org header.i=@lists.infradead.org header.b="eGsi114Y"; dkim=fail reason="signature verification failed" (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="q/JCsOki" DMARC-Filter: OpenDMARC Filter v1.3.2 mail.kernel.org 143AA21D41 Authentication-Results: mail.kernel.org; dmarc=fail (p=none dis=none) header.from=gmail.com Authentication-Results: mail.kernel.org; spf=none smtp.mailfrom=linux-arm-kernel-bounces+patchwork-linux-arm=patchwork.kernel.org@lists.infradead.org DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=lists.infradead.org; s=merlin.20170209; h=Sender:Content-Transfer-Encoding: Content-Type:MIME-Version:Cc:List-Subscribe:List-Help:List-Post:List-Archive: List-Unsubscribe:List-Id:Message-Id:Date:Subject:To:From:Reply-To:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:In-Reply-To:References:List-Owner; bh=fYik6JPkXKx6G0HDGpNKYn5eOwCNfXRQhDB7Vt7IHZg=; b=eGsi114YVPD10OqydUUTSuWl5e 3GT6FnS3FjNlH5BQ9xxdhLcSRtU+GMMdQfXkDMxCgqfICIQww6UbWl0S23VG7zu2aIYtHlEHFGtmp 8g/aDDiriQ96fP/I9MhJKHbWxC6VNcNzSwOKkoHhAthqpQ6O003x9xJL4UrhQgb5MSs0lvUXHqK2A 1mJY9UTxo5MTIx7ejdRFiDBougE3azMadZHT1LKMY79bYnEIY7keZ8jk9eZt+khRYyoBgEHAWADp+ sP4Re6IvOgpT5LGDyUFdg3KN94pVoGFsbinMW8m5Eeu0qqiEsJL4POY4VMGAr9gjX9Nfo0rZRJ6+P hhjCatMw==; Received: from localhost ([::1] helo=merlin.infradead.org) by merlin.infradead.org with esmtp (Exim 4.92.3 #3 (Red Hat Linux)) id 1kT5Tt-0004f0-Ih; Thu, 15 Oct 2020 15:47:53 +0000 Received: from mail-lj1-x242.google.com ([2a00:1450:4864:20::242]) by merlin.infradead.org with esmtps (Exim 4.92.3 #3 (Red Hat Linux)) id 1kT5Tp-0004dl-Ug for linux-arm-kernel@lists.infradead.org; Thu, 15 Oct 2020 15:47:51 +0000 Received: by mail-lj1-x242.google.com with SMTP id h20so3630189lji.9 for ; Thu, 15 Oct 2020 08:47:48 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:cc:subject:date:message-id; bh=LuKF0N1JWbU39+L/415wOhZopR6HqG/zcA49QCFaGoU=; b=q/JCsOkiRkSfwE9CeyPER2pmdNqet2sgfhpNu+Z7z/nRBVQAWH/YLTWyvHx2Mu1rww gg7MHdSynSX/mLZbYpyQn26huirzbqm2mfnWTRBqMjhSFDN2SHHl6qlIunVIak779d+I IrC9mKusAF2Eown+rQINqzXMFvcLkvNjm3u8qj0Pk3Bgwd/9SYvJe0Ohd0BseUwk6NGw fnnbhkfXppli8NjVXnXFXjsq+FkZLxknhM+jBZ6GGXqvCUvyfigotiu1qNXL2MRA7npo Mxh4N8Hfmr560DHjys1ZA8jnLWEAux+VT4DL+PP23gEMDUMh11jpHS/+eGvvYVdOXlEr tEXg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id; bh=LuKF0N1JWbU39+L/415wOhZopR6HqG/zcA49QCFaGoU=; b=cH+Ev/pmhNo2U3u4U0efcoyOs0pz+xp6jNTXxeU93Il7xPyP9dA6rwhF2+YuVT/MqD r+fbV35QCVyDDgl83QEGy023TDXe1Xw0nt2X5yEgeWg7DLzyaN8H9x6GwydS+OeUH2jC BSExJoNvCMotlog5ubt667byFp3FtYGfwFGKMkMSoEaVBAwz9FQ22jqCBPKcB9YuzP6r pwWAn2JEyHsPSpfjtcinMUNa7GhCyjVTwpds2RD/9mdbwec4PBLMemXC1ZhEpdWwQTUX J9kanUxW5qAnYJlIV+xJK+ddf2Xcn1R7m27/jYYLEahU6HRSPJImiHZknQFQp1a6XHT7 fubA== X-Gm-Message-State: AOAM530XqZecBEH7VJODJsPj865jWVkqUJ/BAAgOF1GGBc6vc6BHaKtP Sg3S+eaAz+NlTg9y62E9zhc= X-Google-Smtp-Source: ABdhPJxYlqpNs9ZSfaxC2+6cCXCeZdTV39Ou1WQlV24xesWTKcO0afuTLZOpQMy5wb3uAjgVc9qIlQ== X-Received: by 2002:a2e:9f4d:: with SMTP id v13mr1625625ljk.379.1602776866730; Thu, 15 Oct 2020 08:47:46 -0700 (PDT) Received: from ubuntu-18.lintech.local ([80.87.144.137]) by smtp.gmail.com with ESMTPSA id i13sm161294lfe.16.2020.10.15.08.47.45 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 15 Oct 2020 08:47:46 -0700 (PDT) From: Alexander Kochetkov To: Mark Brown , Maxime Ripard , Chen-Yu Tsai Subject: [PATCH] spi: spi-sun6i: implement DMA-based transfer mode Date: Thu, 15 Oct 2020 18:47:40 +0300 Message-Id: <20201015154740.20825-1-al.kochet@gmail.com> X-Mailer: git-send-email 2.17.1 X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20201015_114749_991268_908E1D95 X-CRM114-Status: GOOD ( 25.00 ) X-Spam-Score: -0.2 (/) X-Spam-Report: SpamAssassin version 3.4.4 on merlin.infradead.org summary: Content analysis details: (-0.2 points) pts rule name description ---- ---------------------- -------------------------------------------------- -0.0 RCVD_IN_DNSWL_NONE RBL: Sender listed at https://www.dnswl.org/, no trust [2a00:1450:4864:20:0:0:0:242 listed in] [list.dnswl.org] 0.0 FREEMAIL_FROM Sender email is commonly abused enduser mail provider [al.kochet[at]gmail.com] -0.0 SPF_PASS SPF: sender matches SPF record 0.0 SPF_HELO_NONE SPF: HELO does not publish an SPF Record -0.1 DKIM_VALID_EF Message has a valid DKIM or DK signature from envelope-from domain 0.1 DKIM_SIGNED Message has a DKIM or DK signature, not necessarily valid -0.1 DKIM_VALID_AU Message has a valid DKIM or DK signature from author's domain -0.1 DKIM_VALID Message has at least one valid DKIM or DK signature X-BeenThere: linux-arm-kernel@lists.infradead.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: Alexander Kochetkov , linux-kernel@vger.kernel.org, linux-arm-kernel@lists.infradead.org, linux-spi@vger.kernel.org MIME-Version: 1.0 Sender: "linux-arm-kernel" Errors-To: linux-arm-kernel-bounces+patchwork-linux-arm=patchwork.kernel.org@lists.infradead.org DMA-based transfer will be enabled if data length is larger than FIFO size (64 bytes for A64). This greatly reduce number of interrupts for transferring data. For smaller data size PIO mode will be used. In PIO mode whole buffer will be loaded into FIFO. If driver failed to request DMA channels then it fallback for PIO mode. Tested on SOPINE (https://www.pine64.org/sopine/) Signed-off-by: Alexander Kochetkov --- drivers/spi/spi-sun6i.c | 171 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 159 insertions(+), 12 deletions(-) diff --git a/drivers/spi/spi-sun6i.c b/drivers/spi/spi-sun6i.c index 19238e1b76b4..38e5b8af7da6 100644 --- a/drivers/spi/spi-sun6i.c +++ b/drivers/spi/spi-sun6i.c @@ -18,6 +18,7 @@ #include #include #include +#include #include @@ -52,10 +53,12 @@ #define SUN6I_FIFO_CTL_REG 0x18 #define SUN6I_FIFO_CTL_RF_RDY_TRIG_LEVEL_MASK 0xff +#define SUN6I_FIFO_CTL_RF_DRQ_EN BIT(8) #define SUN6I_FIFO_CTL_RF_RDY_TRIG_LEVEL_BITS 0 #define SUN6I_FIFO_CTL_RF_RST BIT(15) #define SUN6I_FIFO_CTL_TF_ERQ_TRIG_LEVEL_MASK 0xff #define SUN6I_FIFO_CTL_TF_ERQ_TRIG_LEVEL_BITS 16 +#define SUN6I_FIFO_CTL_TF_DRQ_EN BIT(24) #define SUN6I_FIFO_CTL_TF_RST BIT(31) #define SUN6I_FIFO_STA_REG 0x1c @@ -83,6 +86,8 @@ struct sun6i_spi { struct spi_master *master; void __iomem *base_addr; + dma_addr_t dma_addr_rx; + dma_addr_t dma_addr_tx; struct clk *hclk; struct clk *mclk; struct reset_control *rstc; @@ -92,6 +97,7 @@ struct sun6i_spi { const u8 *tx_buf; u8 *rx_buf; int len; + bool use_dma; unsigned long fifo_depth; }; @@ -182,6 +188,66 @@ static size_t sun6i_spi_max_transfer_size(struct spi_device *spi) return SUN6I_MAX_XFER_SIZE - 1; } +static int sun6i_spi_prepare_dma(struct sun6i_spi *sspi, + struct spi_transfer *tfr) +{ + struct dma_async_tx_descriptor *rxdesc, *txdesc; + struct spi_master *master = sspi->master; + + rxdesc = NULL; + if (tfr->rx_buf) { + struct dma_slave_config rxconf = { + .direction = DMA_DEV_TO_MEM, + .src_addr = sspi->dma_addr_rx, + .src_addr_width = 1, + .src_maxburst = 1, + }; + + dmaengine_slave_config(master->dma_rx, &rxconf); + + rxdesc = dmaengine_prep_slave_sg( + master->dma_rx, + tfr->rx_sg.sgl, tfr->rx_sg.nents, + DMA_DEV_TO_MEM, DMA_PREP_INTERRUPT); + if (!rxdesc) + return -EINVAL; + } + + txdesc = NULL; + if (tfr->tx_buf) { + struct dma_slave_config txconf = { + .direction = DMA_MEM_TO_DEV, + .dst_addr = sspi->dma_addr_tx, + .dst_addr_width = 1, + .dst_maxburst = 1, + }; + + dmaengine_slave_config(master->dma_tx, &txconf); + + txdesc = dmaengine_prep_slave_sg( + master->dma_tx, + tfr->tx_sg.sgl, tfr->tx_sg.nents, + DMA_MEM_TO_DEV, DMA_PREP_INTERRUPT); + if (!txdesc) { + if (rxdesc) + dmaengine_terminate_sync(master->dma_rx); + return -EINVAL; + } + } + + if (tfr->rx_buf) { + dmaengine_submit(rxdesc); + dma_async_issue_pending(master->dma_rx); + } + + if (tfr->tx_buf) { + dmaengine_submit(txdesc); + dma_async_issue_pending(master->dma_tx); + } + + return 0; +} + static int sun6i_spi_transfer_one(struct spi_master *master, struct spi_device *spi, struct spi_transfer *tfr) @@ -201,6 +267,8 @@ static int sun6i_spi_transfer_one(struct spi_master *master, sspi->tx_buf = tfr->tx_buf; sspi->rx_buf = tfr->rx_buf; sspi->len = tfr->len; + sspi->use_dma = master->can_dma ? + master->can_dma(master, spi, tfr) : false; /* Clear pending interrupts */ sun6i_spi_write(sspi, SUN6I_INT_STA_REG, ~0); @@ -216,9 +284,17 @@ static int sun6i_spi_transfer_one(struct spi_master *master, * (See spi-sun4i.c) */ trig_level = sspi->fifo_depth / 4 * 3; - sun6i_spi_write(sspi, SUN6I_FIFO_CTL_REG, - (trig_level << SUN6I_FIFO_CTL_RF_RDY_TRIG_LEVEL_BITS) | - (trig_level << SUN6I_FIFO_CTL_TF_ERQ_TRIG_LEVEL_BITS)); + reg = (trig_level << SUN6I_FIFO_CTL_RF_RDY_TRIG_LEVEL_BITS) | + (trig_level << SUN6I_FIFO_CTL_TF_ERQ_TRIG_LEVEL_BITS); + + if (sspi->use_dma) { + if (tfr->tx_buf) + reg |= SUN6I_FIFO_CTL_TF_DRQ_EN; + if (tfr->rx_buf) + reg |= SUN6I_FIFO_CTL_RF_DRQ_EN; + } + + sun6i_spi_write(sspi, SUN6I_FIFO_CTL_REG, reg); /* * Setup the transfer control register: Chip Select, @@ -300,16 +376,28 @@ static int sun6i_spi_transfer_one(struct spi_master *master, sun6i_spi_write(sspi, SUN6I_XMIT_CNT_REG, tx_len); sun6i_spi_write(sspi, SUN6I_BURST_CTL_CNT_REG, tx_len); - /* Fill the TX FIFO */ - sun6i_spi_fill_fifo(sspi); + if (!sspi->use_dma) { + /* Fill the TX FIFO */ + sun6i_spi_fill_fifo(sspi); + } else { + ret = sun6i_spi_prepare_dma(sspi, tfr); + if (ret) { + dev_warn(&master->dev, + "%s: prepare DMA failed, ret=%d", + dev_name(&spi->dev), ret); + return ret; + } + } /* Enable the interrupts */ reg = SUN6I_INT_CTL_TC; - if (rx_len > sspi->fifo_depth) - reg |= SUN6I_INT_CTL_RF_RDY; - if (tx_len > sspi->fifo_depth) - reg |= SUN6I_INT_CTL_TF_ERQ; + if (!sspi->use_dma) { + if (rx_len > sspi->fifo_depth) + reg |= SUN6I_INT_CTL_RF_RDY; + if (tx_len > sspi->fifo_depth) + reg |= SUN6I_INT_CTL_TF_ERQ; + } sun6i_spi_write(sspi, SUN6I_INT_CTL_REG, reg); @@ -332,6 +420,11 @@ static int sun6i_spi_transfer_one(struct spi_master *master, sun6i_spi_write(sspi, SUN6I_INT_CTL_REG, 0); + if (ret && sspi->use_dma) { + dmaengine_terminate_sync(master->dma_rx); + dmaengine_terminate_sync(master->dma_tx); + } + return ret; } @@ -343,7 +436,8 @@ static irqreturn_t sun6i_spi_handler(int irq, void *dev_id) /* Transfer complete */ if (status & SUN6I_INT_CTL_TC) { sun6i_spi_write(sspi, SUN6I_INT_STA_REG, SUN6I_INT_CTL_TC); - sun6i_spi_drain_fifo(sspi); + if (!sspi->use_dma) + sun6i_spi_drain_fifo(sspi); complete(&sspi->done); return IRQ_HANDLED; } @@ -422,10 +516,24 @@ static int sun6i_spi_runtime_suspend(struct device *dev) return 0; } +static bool sun6i_spi_can_dma(struct spi_master *master, + struct spi_device *spi, + struct spi_transfer *xfer) +{ + struct sun6i_spi *sspi = spi_master_get_devdata(master); + + /* if the number of spi words to transfer is less or equal than + * the fifo length we can just fill the fifo and wait for a single + * irq, so don't bother setting up dma + */ + return xfer->len > sspi->fifo_depth; +} + static int sun6i_spi_probe(struct platform_device *pdev) { struct spi_master *master; struct sun6i_spi *sspi; + struct resource *mem; int ret = 0, irq; master = spi_alloc_master(&pdev->dev, sizeof(struct sun6i_spi)); @@ -437,7 +545,7 @@ static int sun6i_spi_probe(struct platform_device *pdev) platform_set_drvdata(pdev, master); sspi = spi_master_get_devdata(master); - sspi->base_addr = devm_platform_ioremap_resource(pdev, 0); + sspi->base_addr = devm_platform_get_and_ioremap_resource(pdev, 0, &mem); if (IS_ERR(sspi->base_addr)) { ret = PTR_ERR(sspi->base_addr); goto err_free_master; @@ -494,6 +602,33 @@ static int sun6i_spi_probe(struct platform_device *pdev) goto err_free_master; } + master->dma_tx = dma_request_chan(&pdev->dev, "tx"); + if (IS_ERR(master->dma_tx)) { + /* Check tx to see if we need defer probing driver */ + if (PTR_ERR(master->dma_tx) == -EPROBE_DEFER) { + ret = -EPROBE_DEFER; + goto err_free_master; + } + dev_warn(&pdev->dev, "Failed to request TX DMA channel\n"); + master->dma_tx = NULL; + } + + master->dma_rx = dma_request_chan(&pdev->dev, "rx"); + if (IS_ERR(master->dma_rx)) { + if (PTR_ERR(master->dma_rx) == -EPROBE_DEFER) { + ret = -EPROBE_DEFER; + goto err_free_dma_tx; + } + dev_warn(&pdev->dev, "Failed to request RX DMA channel\n"); + master->dma_rx = NULL; + } + + if (master->dma_tx && master->dma_rx) { + sspi->dma_addr_tx = mem->start + SUN6I_TXDATA_REG; + sspi->dma_addr_rx = mem->start + SUN6I_RXDATA_REG; + master->can_dma = sun6i_spi_can_dma; + } + /* * This wake-up/shutdown pattern is to be able to have the * device woken up, even if runtime_pm is disabled @@ -501,7 +636,7 @@ static int sun6i_spi_probe(struct platform_device *pdev) ret = sun6i_spi_runtime_resume(&pdev->dev); if (ret) { dev_err(&pdev->dev, "Couldn't resume the device\n"); - goto err_free_master; + goto err_free_dma_rx; } pm_runtime_set_active(&pdev->dev); @@ -519,6 +654,12 @@ static int sun6i_spi_probe(struct platform_device *pdev) err_pm_disable: pm_runtime_disable(&pdev->dev); sun6i_spi_runtime_suspend(&pdev->dev); +err_free_dma_rx: + if (master->dma_rx) + dma_release_channel(master->dma_rx); +err_free_dma_tx: + if (master->dma_tx) + dma_release_channel(master->dma_tx); err_free_master: spi_master_put(master); return ret; @@ -526,8 +667,14 @@ static int sun6i_spi_probe(struct platform_device *pdev) static int sun6i_spi_remove(struct platform_device *pdev) { + struct spi_master *master = platform_get_drvdata(pdev); + pm_runtime_force_suspend(&pdev->dev); + if (master->dma_tx) + dma_release_channel(master->dma_tx); + if (master->dma_rx) + dma_release_channel(master->dma_rx); return 0; }