From patchwork Thu Oct 22 07:52:21 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Alexander Kochetkov X-Patchwork-Id: 11850465 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-12.5 required=3.0 tests=BAYES_00, DKIM_ADSP_CUSTOM_MED,DKIM_SIGNED,DKIM_VALID,FREEMAIL_FORGED_FROMDOMAIN, FREEMAIL_FROM,HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_PATCH,MAILING_LIST_MULTI, SIGNED_OFF_BY,SPF_HELO_NONE,SPF_PASS,URIBL_BLOCKED,USER_AGENT_GIT autolearn=unavailable autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id EECF6C4363A for ; Thu, 22 Oct 2020 07:53:56 +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 76E1C22267 for ; Thu, 22 Oct 2020 07:53:54 +0000 (UTC) Authentication-Results: mail.kernel.org; dkim=pass (2048-bit key) header.d=lists.infradead.org header.i=@lists.infradead.org header.b="ZB8VGSmn"; dkim=fail reason="signature verification failed" (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="mEtyK8gB" DMARC-Filter: OpenDMARC Filter v1.3.2 mail.kernel.org 76E1C22267 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+linux-arm-kernel=archiver.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=GmbT2JWJoGB21QteIT588+pVDLEgy0wMqzAbmrRxAmA=; b=ZB8VGSmnZ4WG2NwrXn4Ldya1sh YTd+o2u4T/B/gohlQfktqZQTc5dj1aLL8uGOs02zR/Eqo8lDfXrfPVDbLiiNbDzsX3Oq1j3apO8JC QAnNwM0zYcMimvHMCuIBjJGjM+UB+DAu9mvyyiYzsYbcqfFZAHhV2riUa98U0WqT7gGTZ4XZrOxwu LKUbxJ71z9Z75MnETTfHD09dz473AhPIeY0Z/5dH1O2jX1bu96mix2z/0CZZvvgJWoMyzxbsvPOPW KSd5rlPU/loO80gmUyua77FqtGPzJFGHFW6UmNijJFvIfYpXx+ciCUYZwUXzHa0I17bx7IxXnaMah TqGCVS8Q==; Received: from localhost ([::1] helo=merlin.infradead.org) by merlin.infradead.org with esmtp (Exim 4.92.3 #3 (Red Hat Linux)) id 1kVVOl-000743-Lp; Thu, 22 Oct 2020 07:52:35 +0000 Received: from mail-lj1-x243.google.com ([2a00:1450:4864:20::243]) by merlin.infradead.org with esmtps (Exim 4.92.3 #3 (Red Hat Linux)) id 1kVVOh-00072u-TP for linux-arm-kernel@lists.infradead.org; Thu, 22 Oct 2020 07:52:33 +0000 Received: by mail-lj1-x243.google.com with SMTP id a28so914839ljn.3 for ; Thu, 22 Oct 2020 00:52:29 -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=Lz3T0/wMSg0jK0Mv0DuUm0wgU204qfdehJM5VRwUP0c=; b=mEtyK8gBczkQAsNMhRR+rVJ9TXDu3CUECutH3plCUVT/25LhIVAt623VnAUhEGetNe D2Dznj4lFIqywdrM+QoLB5rIFEJkLX+CU3W/eGly1qYcRbUZqWkpgGr1Oj4iEExCcMui d8gEEyn69bydfV1khqZK+iYynv7R0WKIVDIKVEc+yojYT5g1Qvb/m2/K7cmhuLKqWVvW t4JC1phBv4uINR9hk0G0Z80rGLFoWmXYnY++4pkAzFLi3lgjjCXkQROJB0ZN3xgIpowW XfIuVqPCbDF/mRwWTmZceEjQpLPk8F/lkiwwZgu+z3sH++/fLkITBzJRchvsJEg133kd bGxQ== 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=Lz3T0/wMSg0jK0Mv0DuUm0wgU204qfdehJM5VRwUP0c=; b=rkvfhStkqJId+KaBbeM2u9ItwacZfelQa40zHQaPy046wXipQJ/DxXKiXCUlfT66KM QhGx4Ay6HETfUjjzDC9bOflhToGkp9PzB8NCTUBfJnTojvbIgjCIMzpkXlOtl+tRCBOO qGymz+heqNFNq3D9N9lM0Zy6Qh2qVkxdkHrKUvfv6HK6HcEn+eeP4IJbZcp4CukuOhnR ANejiaYCnuNorbiYCEIaagjG775j/UrSxfxaCDMd2AuhFoxpVXFiE6Bw0WND/7vSHmEE 1jq14c6ujpb2QQoxvzf6FpkL4I+Kru6igHZHQZJSKC4xYQ+/dwN7ywFeTBDMEKTFDZh9 Qguw== X-Gm-Message-State: AOAM533C7o3QWqoMIYVG5ewaDPyfoLjZMKMcM9P6kFVrJ06Wy/dQ9J+T NYJRLCQ/xGHyxq/0lPk6Unc= X-Google-Smtp-Source: ABdhPJxR6Qb/J9Z5dlbhKC3yqPKyuIWdZqYJaJx2Er5GAGO7fTntF2htF2DRIGp33eAIzRRM224dRw== X-Received: by 2002:a05:651c:1101:: with SMTP id d1mr465845ljo.150.1603353147714; Thu, 22 Oct 2020 00:52:27 -0700 (PDT) Received: from ubuntu-18.lintech.local ([80.87.144.137]) by smtp.gmail.com with ESMTPSA id x16sm139426lfn.268.2020.10.22.00.52.26 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 22 Oct 2020 00:52:26 -0700 (PDT) From: Alexander Kochetkov X-Google-Original-From: Alexander Kochetkov To: Mark Brown , Maxime Ripard , Chen-Yu Tsai Subject: [PATCH v3] spi: spi-sun6i: implement DMA-based transfer mode Date: Thu, 22 Oct 2020 10:52:21 +0300 Message-Id: <20201022075221.23332-1-akochetkov@lintech.ru> X-Mailer: git-send-email 2.17.1 X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20201022_035231_951131_A13F08E9 X-CRM114-Status: GOOD ( 28.88 ) 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+linux-arm-kernel=archiver.kernel.org@lists.infradead.org From: Alexander Kochetkov 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 Acked-by: Maxime Ripard --- Changes in v3: - Remove use_dma from struct sun6i_spi Changes in v2: - Fix 'checkpatch.pl --strict' warnings - Optimezed DMA transfers. Fix burst size and address width for DMA transfers. The code works for me an I did some tests with different values and conditions. Empirically found that trigger level used for PIO mode also can be used for DMA mode (TRM for A64 lacks that description) - Handling inside sun6i_spi_handler() leaved as is, it explicity states that sun6i_spi_drain_fifo() is not used in DMA mode. Yes, if we call sun6i_spi_drain_fifo() after DMA transfer, it will not drain anything becase DMA already drain FIFO. I can remove condition if it's better without it. drivers/spi/spi-sun6i.c | 194 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 176 insertions(+), 18 deletions(-) diff --git a/drivers/spi/spi-sun6i.c b/drivers/spi/spi-sun6i.c index 19238e1b76b4..5336c5e32694 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; @@ -182,6 +187,68 @@ 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 = DMA_SLAVE_BUSWIDTH_4_BYTES, + .src_maxburst = 8, + }; + + 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 = DMA_SLAVE_BUSWIDTH_4_BYTES, + .dst_maxburst = 8, + }; + + 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) @@ -191,6 +258,7 @@ static int sun6i_spi_transfer_one(struct spi_master *master, unsigned int start, end, tx_time; unsigned int trig_level; unsigned int tx_len = 0, rx_len = 0; + bool use_dma; int ret = 0; u32 reg; @@ -201,6 +269,7 @@ 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; + use_dma = master->can_dma ? master->can_dma(master, spi, tfr) : false; /* Clear pending interrupts */ sun6i_spi_write(sspi, SUN6I_INT_STA_REG, ~0); @@ -209,16 +278,34 @@ static int sun6i_spi_transfer_one(struct spi_master *master, sun6i_spi_write(sspi, SUN6I_FIFO_CTL_REG, SUN6I_FIFO_CTL_RF_RST | SUN6I_FIFO_CTL_TF_RST); - /* - * Setup FIFO interrupt trigger level - * Here we choose 3/4 of the full fifo depth, as it's the hardcoded - * value used in old generation of Allwinner SPI controller. - * (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 = 0; + + if (!use_dma) { + /* + * Setup FIFO interrupt trigger level + * Here we choose 3/4 of the full fifo depth, as it's + * the hardcoded value used in old generation of Allwinner + * SPI controller. (See spi-sun4i.c) + */ + trig_level = sspi->fifo_depth / 4 * 3; + } else { + /* + * Setup FIFO DMA request trigger level + * We choose 1/2 of the full fifo depth, that value will + * be used as DMA burst length. + */ + trig_level = sspi->fifo_depth / 2; + + if (tfr->tx_buf) + reg |= SUN6I_FIFO_CTL_TF_DRQ_EN; + if (tfr->rx_buf) + reg |= SUN6I_FIFO_CTL_RF_DRQ_EN; + } + + reg |= (trig_level << SUN6I_FIFO_CTL_RF_RDY_TRIG_LEVEL_BITS) | + (trig_level << SUN6I_FIFO_CTL_TF_ERQ_TRIG_LEVEL_BITS); + + sun6i_spi_write(sspi, SUN6I_FIFO_CTL_REG, reg); /* * Setup the transfer control register: Chip Select, @@ -300,16 +387,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 (!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 (!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 +431,11 @@ static int sun6i_spi_transfer_one(struct spi_master *master, sun6i_spi_write(sspi, SUN6I_INT_CTL_REG, 0); + if (ret && use_dma) { + dmaengine_terminate_sync(master->dma_rx); + dmaengine_terminate_sync(master->dma_tx); + } + return ret; } @@ -422,10 +526,25 @@ 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 +556,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 +613,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 +647,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 +665,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 +678,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; }