From 35700e221b18fa53401e5f315be90af9e0bbcdca Mon Sep 17 00:00:00 2001 From: Patrice Chotard Date: Tue, 16 Jun 2020 13:30:35 +0200 Subject: [PATCH 01/19] spi: stm32-qspi: Fix error path in case of -EPROBE_DEFER In case of -EPROBE_DEFER, stm32_qspi_release() was called in any case which unregistered driver from pm_runtime framework even if it has not been registered yet to it. This leads to: stm32-qspi 58003000.spi: can't setup spi0.0, status -13 spi_master spi0: spi_device register error /soc/spi@58003000/mx66l51235l@0 spi_master spi0: Failed to create SPI device for /soc/spi@58003000/mx66l51235l@0 stm32-qspi 58003000.spi: can't setup spi0.1, status -13 spi_master spi0: spi_device register error /soc/spi@58003000/mx66l51235l@1 spi_master spi0: Failed to create SPI device for /soc/spi@58003000/mx66l51235l@1 On v5.7 kernel,this issue was not "visible", qspi driver was probed successfully. Fixes: 9d282c17b023 ("spi: stm32-qspi: Add pm_runtime support") Signed-off-by: Patrice Chotard Link: https://lore.kernel.org/r/20200616113035.4514-1-patrice.chotard@st.com Signed-off-by: Mark Brown --- drivers/spi/spi-stm32-qspi.c | 46 ++++++++++++++++++++---------------- 1 file changed, 26 insertions(+), 20 deletions(-) diff --git a/drivers/spi/spi-stm32-qspi.c b/drivers/spi/spi-stm32-qspi.c index 3c44bb2fd9b1..a900962b4336 100644 --- a/drivers/spi/spi-stm32-qspi.c +++ b/drivers/spi/spi-stm32-qspi.c @@ -553,20 +553,6 @@ static const struct spi_controller_mem_ops stm32_qspi_mem_ops = { .exec_op = stm32_qspi_exec_op, }; -static void stm32_qspi_release(struct stm32_qspi *qspi) -{ - pm_runtime_get_sync(qspi->dev); - /* disable qspi */ - writel_relaxed(0, qspi->io_base + QSPI_CR); - stm32_qspi_dma_free(qspi); - mutex_destroy(&qspi->lock); - pm_runtime_put_noidle(qspi->dev); - pm_runtime_disable(qspi->dev); - pm_runtime_set_suspended(qspi->dev); - pm_runtime_dont_use_autosuspend(qspi->dev); - clk_disable_unprepare(qspi->clk); -} - static int stm32_qspi_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -642,7 +628,7 @@ static int stm32_qspi_probe(struct platform_device *pdev) if (IS_ERR(rstc)) { ret = PTR_ERR(rstc); if (ret == -EPROBE_DEFER) - goto err_qspi_release; + goto err_clk_disable; } else { reset_control_assert(rstc); udelay(2); @@ -653,7 +639,7 @@ static int stm32_qspi_probe(struct platform_device *pdev) platform_set_drvdata(pdev, qspi); ret = stm32_qspi_dma_setup(qspi); if (ret) - goto err_qspi_release; + goto err_dma_free; mutex_init(&qspi->lock); @@ -673,15 +659,26 @@ static int stm32_qspi_probe(struct platform_device *pdev) ret = devm_spi_register_master(dev, ctrl); if (ret) - goto err_qspi_release; + goto err_pm_runtime_free; pm_runtime_mark_last_busy(dev); pm_runtime_put_autosuspend(dev); return 0; -err_qspi_release: - stm32_qspi_release(qspi); +err_pm_runtime_free: + pm_runtime_get_sync(qspi->dev); + /* disable qspi */ + writel_relaxed(0, qspi->io_base + QSPI_CR); + mutex_destroy(&qspi->lock); + pm_runtime_put_noidle(qspi->dev); + pm_runtime_disable(qspi->dev); + pm_runtime_set_suspended(qspi->dev); + pm_runtime_dont_use_autosuspend(qspi->dev); +err_dma_free: + stm32_qspi_dma_free(qspi); +err_clk_disable: + clk_disable_unprepare(qspi->clk); err_master_put: spi_master_put(qspi->ctrl); @@ -692,7 +689,16 @@ static int stm32_qspi_remove(struct platform_device *pdev) { struct stm32_qspi *qspi = platform_get_drvdata(pdev); - stm32_qspi_release(qspi); + pm_runtime_get_sync(qspi->dev); + /* disable qspi */ + writel_relaxed(0, qspi->io_base + QSPI_CR); + stm32_qspi_dma_free(qspi); + mutex_destroy(&qspi->lock); + pm_runtime_put_noidle(qspi->dev); + pm_runtime_disable(qspi->dev); + pm_runtime_set_suspended(qspi->dev); + pm_runtime_dont_use_autosuspend(qspi->dev); + clk_disable_unprepare(qspi->clk); return 0; } From abd42781c3d2155868821f1b947ae45bbc33330d Mon Sep 17 00:00:00 2001 From: Zhenzhong Duan Date: Thu, 18 Jun 2020 11:21:24 +0800 Subject: [PATCH 02/19] spi: spidev: fix a race between spidev_release and spidev_remove Imagine below scene, spidev is referenced after it's freed. spidev_release() spidev_remove() ... spin_lock_irq(&spidev->spi_lock); spidev->spi = NULL; spin_unlock_irq(&spidev->spi_lock); mutex_lock(&device_list_lock); dofree = (spidev->spi == NULL); if (dofree) kfree(spidev); mutex_unlock(&device_list_lock); mutex_lock(&device_list_lock); list_del(&spidev->device_entry); device_destroy(spidev_class, spidev->devt); clear_bit(MINOR(spidev->devt), minors); if (spidev->users == 0) kfree(spidev); mutex_unlock(&device_list_lock); Fix it by resetting spidev->spi in device_list_lock's protection. Signed-off-by: Zhenzhong Duan Link: https://lore.kernel.org/r/20200618032125.4650-1-zhenzhong.duan@gmail.com Signed-off-by: Mark Brown --- drivers/spi/spidev.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/spi/spidev.c b/drivers/spi/spidev.c index d753df700e9e..f74ea26c382f 100644 --- a/drivers/spi/spidev.c +++ b/drivers/spi/spidev.c @@ -787,13 +787,13 @@ static int spidev_remove(struct spi_device *spi) { struct spidev_data *spidev = spi_get_drvdata(spi); + /* prevent new opens */ + mutex_lock(&device_list_lock); /* make sure ops on existing fds can abort cleanly */ spin_lock_irq(&spidev->spi_lock); spidev->spi = NULL; spin_unlock_irq(&spidev->spi_lock); - /* prevent new opens */ - mutex_lock(&device_list_lock); list_del(&spidev->device_entry); device_destroy(spidev_class, spidev->devt); clear_bit(MINOR(spidev->devt), minors); From 06096cc6c5a84ced929634b0d79376b94c65a4bd Mon Sep 17 00:00:00 2001 From: Zhenzhong Duan Date: Thu, 18 Jun 2020 11:21:25 +0800 Subject: [PATCH 03/19] spi: spidev: fix a potential use-after-free in spidev_release() If an spi device is unbounded from the driver before the release process, there will be an NULL pointer reference when it's referenced in spi_slave_abort(). Fix it by checking it's already freed before reference. Signed-off-by: Zhenzhong Duan Link: https://lore.kernel.org/r/20200618032125.4650-2-zhenzhong.duan@gmail.com Signed-off-by: Mark Brown --- drivers/spi/spidev.c | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/drivers/spi/spidev.c b/drivers/spi/spidev.c index f74ea26c382f..59e07675ef86 100644 --- a/drivers/spi/spidev.c +++ b/drivers/spi/spidev.c @@ -609,15 +609,20 @@ static int spidev_open(struct inode *inode, struct file *filp) static int spidev_release(struct inode *inode, struct file *filp) { struct spidev_data *spidev; + int dofree; mutex_lock(&device_list_lock); spidev = filp->private_data; filp->private_data = NULL; + spin_lock_irq(&spidev->spi_lock); + /* ... after we unbound from the underlying device? */ + dofree = (spidev->spi == NULL); + spin_unlock_irq(&spidev->spi_lock); + /* last close? */ spidev->users--; if (!spidev->users) { - int dofree; kfree(spidev->tx_buffer); spidev->tx_buffer = NULL; @@ -625,19 +630,14 @@ static int spidev_release(struct inode *inode, struct file *filp) kfree(spidev->rx_buffer); spidev->rx_buffer = NULL; - spin_lock_irq(&spidev->spi_lock); - if (spidev->spi) - spidev->speed_hz = spidev->spi->max_speed_hz; - - /* ... after we unbound from the underlying device? */ - dofree = (spidev->spi == NULL); - spin_unlock_irq(&spidev->spi_lock); - if (dofree) kfree(spidev); + else + spidev->speed_hz = spidev->spi->max_speed_hz; } #ifdef CONFIG_SPI_SLAVE - spi_slave_abort(spidev->spi); + if (!dofree) + spi_slave_abort(spidev->spi); #endif mutex_unlock(&device_list_lock); From 7684580d45bd3d84ed9b453a4cadf7a9a5605a3f Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Mon, 22 Jun 2020 13:05:40 +0200 Subject: [PATCH 04/19] spi: spi-fsl-dspi: Fix lockup if device is removed during SPI transfer During device removal, the driver should unregister the SPI controller and stop the hardware. Otherwise the dspi_transfer_one_message() could wait on completion infinitely. Additionally, calling spi_unregister_controller() first in device removal reverse-matches the probe function, where SPI controller is registered at the end. Fixes: 05209f457069 ("spi: fsl-dspi: add missing clk_disable_unprepare() in dspi_remove()") Reported-by: Vladimir Oltean Signed-off-by: Krzysztof Kozlowski Cc: Link: https://lore.kernel.org/r/20200622110543.5035-1-krzk@kernel.org Signed-off-by: Mark Brown --- drivers/spi/spi-fsl-dspi.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/drivers/spi/spi-fsl-dspi.c b/drivers/spi/spi-fsl-dspi.c index 58190c94561f..ec0fd0d366eb 100644 --- a/drivers/spi/spi-fsl-dspi.c +++ b/drivers/spi/spi-fsl-dspi.c @@ -1434,9 +1434,18 @@ static int dspi_remove(struct platform_device *pdev) struct fsl_dspi *dspi = spi_controller_get_devdata(ctlr); /* Disconnect from the SPI framework */ + spi_unregister_controller(dspi->ctlr); + + /* Disable RX and TX */ + regmap_update_bits(dspi->regmap, SPI_MCR, + SPI_MCR_DIS_TXF | SPI_MCR_DIS_RXF, + SPI_MCR_DIS_TXF | SPI_MCR_DIS_RXF); + + /* Stop Running */ + regmap_update_bits(dspi->regmap, SPI_MCR, SPI_MCR_HALT, SPI_MCR_HALT); + dspi_release_dma(dspi); clk_disable_unprepare(dspi->clk); - spi_unregister_controller(dspi->ctlr); return 0; } From 3c525b69e8c1a9a6944e976603c7a1a713e728f9 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Mon, 22 Jun 2020 13:05:41 +0200 Subject: [PATCH 05/19] spi: spi-fsl-dspi: Fix lockup if device is shutdown during SPI transfer During shutdown, the driver should unregister the SPI controller and stop the hardware. Otherwise the dspi_transfer_one_message() could wait on completion infinitely. Additionally, calling spi_unregister_controller() first in device shutdown reverse-matches the probe function, where SPI controller is registered at the end. Fixes: dc234825997e ("spi: spi-fsl-dspi: Adding shutdown hook") Reported-by: Vladimir Oltean Signed-off-by: Krzysztof Kozlowski Tested-by: Vladimir Oltean Reviewed-by: Vladimir Oltean Cc: Link: https://lore.kernel.org/r/20200622110543.5035-2-krzk@kernel.org Signed-off-by: Mark Brown --- drivers/spi/spi-fsl-dspi.c | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/drivers/spi/spi-fsl-dspi.c b/drivers/spi/spi-fsl-dspi.c index ec0fd0d366eb..ec7919d9c0d9 100644 --- a/drivers/spi/spi-fsl-dspi.c +++ b/drivers/spi/spi-fsl-dspi.c @@ -1452,20 +1452,7 @@ static int dspi_remove(struct platform_device *pdev) static void dspi_shutdown(struct platform_device *pdev) { - struct spi_controller *ctlr = platform_get_drvdata(pdev); - struct fsl_dspi *dspi = spi_controller_get_devdata(ctlr); - - /* Disable RX and TX */ - regmap_update_bits(dspi->regmap, SPI_MCR, - SPI_MCR_DIS_TXF | SPI_MCR_DIS_RXF, - SPI_MCR_DIS_TXF | SPI_MCR_DIS_RXF); - - /* Stop Running */ - regmap_update_bits(dspi->regmap, SPI_MCR, SPI_MCR_HALT, SPI_MCR_HALT); - - dspi_release_dma(dspi); - clk_disable_unprepare(dspi->clk); - spi_unregister_controller(dspi->ctlr); + dspi_remove(pdev); } static struct platform_driver fsl_dspi_driver = { From 3d87b613d6a3c6f0980e877ab0895785a2dde581 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Mon, 22 Jun 2020 13:05:42 +0200 Subject: [PATCH 06/19] spi: spi-fsl-dspi: Fix external abort on interrupt in resume or exit paths If shared interrupt comes late, during probe error path or device remove (could be triggered with CONFIG_DEBUG_SHIRQ), the interrupt handler dspi_interrupt() will access registers with the clock being disabled. This leads to external abort on non-linefetch on Toradex Colibri VF50 module (with Vybrid VF5xx): $ echo 4002d000.spi > /sys/devices/platform/soc/40000000.bus/4002d000.spi/driver/unbind Unhandled fault: external abort on non-linefetch (0x1008) at 0x8887f02c Internal error: : 1008 [#1] ARM Hardware name: Freescale Vybrid VF5xx/VF6xx (Device Tree) Backtrace: (regmap_mmio_read32le) (regmap_mmio_read) (_regmap_bus_reg_read) (_regmap_read) (regmap_read) (dspi_interrupt) (free_irq) (devm_irq_release) (release_nodes) (devres_release_all) (device_release_driver_internal) The resource-managed framework should not be used for shared interrupt handling, because the interrupt handler might be called after releasing other resources and disabling clocks. Similar bug could happen during suspend - the shared interrupt handler could be invoked after suspending the device. Each device sharing this interrupt line should disable the IRQ during suspend so handler will be invoked only in following cases: 1. None suspended, 2. All devices resumed. Fixes: 349ad66c0ab0 ("spi:Add Freescale DSPI driver for Vybrid VF610 platform") Signed-off-by: Krzysztof Kozlowski Tested-by: Vladimir Oltean Reviewed-by: Vladimir Oltean Cc: Link: https://lore.kernel.org/r/20200622110543.5035-3-krzk@kernel.org Signed-off-by: Mark Brown --- drivers/spi/spi-fsl-dspi.c | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/drivers/spi/spi-fsl-dspi.c b/drivers/spi/spi-fsl-dspi.c index ec7919d9c0d9..e0b30e4b1b69 100644 --- a/drivers/spi/spi-fsl-dspi.c +++ b/drivers/spi/spi-fsl-dspi.c @@ -1109,6 +1109,8 @@ static int dspi_suspend(struct device *dev) struct spi_controller *ctlr = dev_get_drvdata(dev); struct fsl_dspi *dspi = spi_controller_get_devdata(ctlr); + if (dspi->irq) + disable_irq(dspi->irq); spi_controller_suspend(ctlr); clk_disable_unprepare(dspi->clk); @@ -1129,6 +1131,8 @@ static int dspi_resume(struct device *dev) if (ret) return ret; spi_controller_resume(ctlr); + if (dspi->irq) + enable_irq(dspi->irq); return 0; } @@ -1385,8 +1389,8 @@ static int dspi_probe(struct platform_device *pdev) goto poll_mode; } - ret = devm_request_irq(&pdev->dev, dspi->irq, dspi_interrupt, - IRQF_SHARED, pdev->name, dspi); + ret = request_threaded_irq(dspi->irq, dspi_interrupt, NULL, + IRQF_SHARED, pdev->name, dspi); if (ret < 0) { dev_err(&pdev->dev, "Unable to attach DSPI interrupt\n"); goto out_clk_put; @@ -1400,7 +1404,7 @@ static int dspi_probe(struct platform_device *pdev) ret = dspi_request_dma(dspi, res->start); if (ret < 0) { dev_err(&pdev->dev, "can't get dma channels\n"); - goto out_clk_put; + goto out_free_irq; } } @@ -1415,11 +1419,14 @@ static int dspi_probe(struct platform_device *pdev) ret = spi_register_controller(ctlr); if (ret != 0) { dev_err(&pdev->dev, "Problem registering DSPI ctlr\n"); - goto out_clk_put; + goto out_free_irq; } return ret; +out_free_irq: + if (dspi->irq) + free_irq(dspi->irq, dspi); out_clk_put: clk_disable_unprepare(dspi->clk); out_ctlr_put: @@ -1445,6 +1452,8 @@ static int dspi_remove(struct platform_device *pdev) regmap_update_bits(dspi->regmap, SPI_MCR, SPI_MCR_HALT, SPI_MCR_HALT); dspi_release_dma(dspi); + if (dspi->irq) + free_irq(dspi->irq, dspi); clk_disable_unprepare(dspi->clk); return 0; From f148915f91fccd8c3df1b0bff7d1c8458cad3be5 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Mon, 22 Jun 2020 13:05:43 +0200 Subject: [PATCH 07/19] spi: spi-fsl-dspi: Initialize completion before possible interrupt The interrupt handler calls completion and is IRQ requested before the completion is initialized. Logically it should be the other way. Signed-off-by: Krzysztof Kozlowski Link: https://lore.kernel.org/r/20200622110543.5035-4-krzk@kernel.org Signed-off-by: Mark Brown --- drivers/spi/spi-fsl-dspi.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/spi/spi-fsl-dspi.c b/drivers/spi/spi-fsl-dspi.c index e0b30e4b1b69..91c6affe139c 100644 --- a/drivers/spi/spi-fsl-dspi.c +++ b/drivers/spi/spi-fsl-dspi.c @@ -1389,6 +1389,8 @@ static int dspi_probe(struct platform_device *pdev) goto poll_mode; } + init_completion(&dspi->xfer_done); + ret = request_threaded_irq(dspi->irq, dspi_interrupt, NULL, IRQF_SHARED, pdev->name, dspi); if (ret < 0) { @@ -1396,8 +1398,6 @@ static int dspi_probe(struct platform_device *pdev) goto out_clk_put; } - init_completion(&dspi->xfer_done); - poll_mode: if (dspi->devtype_data->trans_mode == DSPI_DMA_MODE) { From cf961fce30f8269d0c2662c48b2618005b67dfd4 Mon Sep 17 00:00:00 2001 From: Jarkko Nikula Date: Thu, 25 Jun 2020 17:00:41 +0300 Subject: [PATCH 08/19] spi: pxa2xx: Add support for Intel Tiger Lake PCH-H Add Intel Tiger Lake PCH-H PCI IDs. Signed-off-by: Jarkko Nikula Link: https://lore.kernel.org/r/20200625140041.745804-1-jarkko.nikula@linux.intel.com Signed-off-by: Mark Brown --- drivers/spi/spi-pxa2xx.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/spi/spi-pxa2xx.c b/drivers/spi/spi-pxa2xx.c index 20dcbd35611a..179416bb1b25 100644 --- a/drivers/spi/spi-pxa2xx.c +++ b/drivers/spi/spi-pxa2xx.c @@ -1485,6 +1485,11 @@ static const struct pci_device_id pxa2xx_spi_pci_compound_match[] = { { PCI_VDEVICE(INTEL, 0x4daa), LPSS_CNL_SSP }, { PCI_VDEVICE(INTEL, 0x4dab), LPSS_CNL_SSP }, { PCI_VDEVICE(INTEL, 0x4dfb), LPSS_CNL_SSP }, + /* TGL-H */ + { PCI_VDEVICE(INTEL, 0x43aa), LPSS_CNL_SSP }, + { PCI_VDEVICE(INTEL, 0x43ab), LPSS_CNL_SSP }, + { PCI_VDEVICE(INTEL, 0x43fb), LPSS_CNL_SSP }, + { PCI_VDEVICE(INTEL, 0x43fd), LPSS_CNL_SSP }, /* APL */ { PCI_VDEVICE(INTEL, 0x5ac2), LPSS_BXT_SSP }, { PCI_VDEVICE(INTEL, 0x5ac4), LPSS_BXT_SSP }, From 44b37eb79e16a56cb30ba55b2da452396b941e7a Mon Sep 17 00:00:00 2001 From: "leilk.liu" Date: Wed, 1 Jul 2020 17:00:20 +0800 Subject: [PATCH 09/19] spi: mediatek: use correct SPI_CFG2_REG MACRO this patch use correct SPI_CFG2_REG offset. Signed-off-by: leilk.liu Link: https://lore.kernel.org/r/20200701090020.7935-1-leilk.liu@mediatek.com Signed-off-by: Mark Brown --- drivers/spi/spi-mt65xx.c | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/drivers/spi/spi-mt65xx.c b/drivers/spi/spi-mt65xx.c index 6783e12c40c2..a556795caeef 100644 --- a/drivers/spi/spi-mt65xx.c +++ b/drivers/spi/spi-mt65xx.c @@ -36,7 +36,6 @@ #define SPI_CFG0_SCK_LOW_OFFSET 8 #define SPI_CFG0_CS_HOLD_OFFSET 16 #define SPI_CFG0_CS_SETUP_OFFSET 24 -#define SPI_ADJUST_CFG0_SCK_LOW_OFFSET 16 #define SPI_ADJUST_CFG0_CS_HOLD_OFFSET 0 #define SPI_ADJUST_CFG0_CS_SETUP_OFFSET 16 @@ -48,6 +47,8 @@ #define SPI_CFG1_CS_IDLE_MASK 0xff #define SPI_CFG1_PACKET_LOOP_MASK 0xff00 #define SPI_CFG1_PACKET_LENGTH_MASK 0x3ff0000 +#define SPI_CFG2_SCK_HIGH_OFFSET 0 +#define SPI_CFG2_SCK_LOW_OFFSET 16 #define SPI_CMD_ACT BIT(0) #define SPI_CMD_RESUME BIT(1) @@ -283,7 +284,7 @@ static void mtk_spi_set_cs(struct spi_device *spi, bool enable) static void mtk_spi_prepare_transfer(struct spi_master *master, struct spi_transfer *xfer) { - u32 spi_clk_hz, div, sck_time, cs_time, reg_val = 0; + u32 spi_clk_hz, div, sck_time, cs_time, reg_val; struct mtk_spi *mdata = spi_master_get_devdata(master); spi_clk_hz = clk_get_rate(mdata->spi_clk); @@ -296,18 +297,18 @@ static void mtk_spi_prepare_transfer(struct spi_master *master, cs_time = sck_time * 2; if (mdata->dev_comp->enhance_timing) { + reg_val = (((sck_time - 1) & 0xffff) + << SPI_CFG2_SCK_HIGH_OFFSET); reg_val |= (((sck_time - 1) & 0xffff) - << SPI_CFG0_SCK_HIGH_OFFSET); - reg_val |= (((sck_time - 1) & 0xffff) - << SPI_ADJUST_CFG0_SCK_LOW_OFFSET); + << SPI_CFG2_SCK_LOW_OFFSET); writel(reg_val, mdata->base + SPI_CFG2_REG); - reg_val |= (((cs_time - 1) & 0xffff) + reg_val = (((cs_time - 1) & 0xffff) << SPI_ADJUST_CFG0_CS_HOLD_OFFSET); reg_val |= (((cs_time - 1) & 0xffff) << SPI_ADJUST_CFG0_CS_SETUP_OFFSET); writel(reg_val, mdata->base + SPI_CFG0_REG); } else { - reg_val |= (((sck_time - 1) & 0xff) + reg_val = (((sck_time - 1) & 0xff) << SPI_CFG0_SCK_HIGH_OFFSET); reg_val |= (((sck_time - 1) & 0xff) << SPI_CFG0_SCK_LOW_OFFSET); reg_val |= (((cs_time - 1) & 0xff) << SPI_CFG0_CS_HOLD_OFFSET); From ed7815db70d17b1741883f2da8e1d80bc2efe517 Mon Sep 17 00:00:00 2001 From: Marc Kleine-Budde Date: Mon, 6 Jul 2020 16:34:34 +0200 Subject: [PATCH 10/19] spi: spi-sun6i: sun6i_spi_transfer_one(): fix setting of clock rate A SPI transfer defines the _maximum_ speed of the SPI transfer. However the driver doesn't take into account that the clock divider is always rounded down (due to integer arithmetics). This results in a too high clock rate for the SPI transfer. E.g.: with a mclk_rate of 24 MHz and a SPI transfer speed of 10 MHz, the original code calculates a reg of "0", which results in a effective divider of "2" and a 12 MHz clock for the SPI transfer. This patch fixes the issue by using DIV_ROUND_UP() instead of a plain integer division. While there simplify the divider calculation for the CDR1 case, use order_base_2() instead of two ilog2() calculations. Fixes: 3558fe900e8a ("spi: sunxi: Add Allwinner A31 SPI controller driver") Signed-off-by: Marc Kleine-Budde Acked-by: Maxime Ripard Link: https://lore.kernel.org/r/20200706143443.9855-2-mkl@pengutronix.de Signed-off-by: Mark Brown --- drivers/spi/spi-sun6i.c | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/drivers/spi/spi-sun6i.c b/drivers/spi/spi-sun6i.c index ecea15534c42..fa11cc0e809b 100644 --- a/drivers/spi/spi-sun6i.c +++ b/drivers/spi/spi-sun6i.c @@ -198,7 +198,7 @@ static int sun6i_spi_transfer_one(struct spi_master *master, struct spi_transfer *tfr) { struct sun6i_spi *sspi = spi_master_get_devdata(master); - unsigned int mclk_rate, div, timeout; + unsigned int mclk_rate, div, div_cdr1, div_cdr2, timeout; unsigned int start, end, tx_time; unsigned int trig_level; unsigned int tx_len = 0; @@ -287,14 +287,12 @@ static int sun6i_spi_transfer_one(struct spi_master *master, * First try CDR2, and if we can't reach the expected * frequency, fall back to CDR1. */ - div = mclk_rate / (2 * tfr->speed_hz); - if (div <= (SUN6I_CLK_CTL_CDR2_MASK + 1)) { - if (div > 0) - div--; - - reg = SUN6I_CLK_CTL_CDR2(div) | SUN6I_CLK_CTL_DRS; + div_cdr1 = DIV_ROUND_UP(mclk_rate, tfr->speed_hz); + div_cdr2 = DIV_ROUND_UP(div_cdr1, 2); + if (div_cdr2 <= (SUN6I_CLK_CTL_CDR2_MASK + 1)) { + reg = SUN6I_CLK_CTL_CDR2(div_cdr2 - 1) | SUN6I_CLK_CTL_DRS; } else { - div = ilog2(mclk_rate) - ilog2(tfr->speed_hz); + div = min(SUN6I_CLK_CTL_CDR1_MASK, order_base_2(div_cdr1)); reg = SUN6I_CLK_CTL_CDR1(div); } From 0bc7b8a2c307c52dbc9f3b5bc7855e93d281098a Mon Sep 17 00:00:00 2001 From: Marc Kleine-Budde Date: Mon, 6 Jul 2020 16:34:35 +0200 Subject: [PATCH 11/19] spi: spi-sun6i: sun6i_spi_transfer_one(): report effectivly used speed_hz of transfer This patch implementes the reporting of the effectivly used speed_hz for the transfer by setting tfr->effective_speed_hz. See the following patch, which adds this feature to the SPI core for more information: 5d7e2b5ed585 spi: core: allow reporting the effectivly used speed_hz for a transfer Signed-off-by: Marc Kleine-Budde Acked-by: Maxime Ripard Link: https://lore.kernel.org/r/20200706143443.9855-3-mkl@pengutronix.de Signed-off-by: Mark Brown --- drivers/spi/spi-sun6i.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/spi/spi-sun6i.c b/drivers/spi/spi-sun6i.c index fa11cc0e809b..64b4d8077010 100644 --- a/drivers/spi/spi-sun6i.c +++ b/drivers/spi/spi-sun6i.c @@ -291,9 +291,11 @@ static int sun6i_spi_transfer_one(struct spi_master *master, div_cdr2 = DIV_ROUND_UP(div_cdr1, 2); if (div_cdr2 <= (SUN6I_CLK_CTL_CDR2_MASK + 1)) { reg = SUN6I_CLK_CTL_CDR2(div_cdr2 - 1) | SUN6I_CLK_CTL_DRS; + tfr->effective_speed_hz = mclk_rate / (2 * div_cdr2); } else { div = min(SUN6I_CLK_CTL_CDR1_MASK, order_base_2(div_cdr1)); reg = SUN6I_CLK_CTL_CDR1(div); + tfr->effective_speed_hz = mclk_rate / (1 << div); } sun6i_spi_write(sspi, SUN6I_CLK_CTL_REG, reg); From b6d5527389f34ef3c07fcbd7902b79bc39936ecf Mon Sep 17 00:00:00 2001 From: Marc Kleine-Budde Date: Mon, 6 Jul 2020 16:34:36 +0200 Subject: [PATCH 12/19] spi: spi-sun6i: sun6i_spi_transfer_one(): remove useless goto This patch removes an useless goto at the end of sun6i_spi_transfer_one(). Signed-off-by: Marc Kleine-Budde Acked-by: Maxime Ripard Link: https://lore.kernel.org/r/20200706143443.9855-4-mkl@pengutronix.de Signed-off-by: Mark Brown --- drivers/spi/spi-sun6i.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/spi/spi-sun6i.c b/drivers/spi/spi-sun6i.c index 64b4d8077010..955be8921c45 100644 --- a/drivers/spi/spi-sun6i.c +++ b/drivers/spi/spi-sun6i.c @@ -335,10 +335,8 @@ static int sun6i_spi_transfer_one(struct spi_master *master, dev_name(&spi->dev), tfr->len, tfr->speed_hz, jiffies_to_msecs(end - start), tx_time); ret = -ETIMEDOUT; - goto out; } -out: sun6i_spi_write(sspi, SUN6I_INT_CTL_REG, 0); return ret; From 2130be57d5eb01bcd440495cdb5455cf40468f0c Mon Sep 17 00:00:00 2001 From: Marc Kleine-Budde Date: Mon, 6 Jul 2020 16:34:37 +0200 Subject: [PATCH 13/19] spi: spi-sun6i: sun6i_spi_transfer_one(): remove not needed masking of transfer length In sun6i_spi_transfer_one() the driver ensures that the length of the transfer is smaller or equal to SUN6I_MAX_XFER_SIZE. This means the masking of the length to SUN6I_MAX_XFER_SIZE can be skipped when writing the transfer length into the registers. This patch removes the useless masking of the transfer length to SUN6I_MAX_XFER_SIZE. Signed-off-by: Marc Kleine-Budde Acked-by: Maxime Ripard Link: https://lore.kernel.org/r/20200706143443.9855-5-mkl@pengutronix.de Signed-off-by: Mark Brown --- drivers/spi/spi-sun6i.c | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/drivers/spi/spi-sun6i.c b/drivers/spi/spi-sun6i.c index 955be8921c45..882492774986 100644 --- a/drivers/spi/spi-sun6i.c +++ b/drivers/spi/spi-sun6i.c @@ -73,13 +73,10 @@ #define SUN6I_MAX_XFER_SIZE 0xffffff #define SUN6I_BURST_CNT_REG 0x30 -#define SUN6I_BURST_CNT(cnt) ((cnt) & SUN6I_MAX_XFER_SIZE) #define SUN6I_XMIT_CNT_REG 0x34 -#define SUN6I_XMIT_CNT(cnt) ((cnt) & SUN6I_MAX_XFER_SIZE) #define SUN6I_BURST_CTL_CNT_REG 0x38 -#define SUN6I_BURST_CTL_CNT_STC(cnt) ((cnt) & SUN6I_MAX_XFER_SIZE) #define SUN6I_TXDATA_REG 0x200 #define SUN6I_RXDATA_REG 0x300 @@ -305,10 +302,9 @@ static int sun6i_spi_transfer_one(struct spi_master *master, tx_len = tfr->len; /* Setup the counters */ - sun6i_spi_write(sspi, SUN6I_BURST_CNT_REG, SUN6I_BURST_CNT(tfr->len)); - sun6i_spi_write(sspi, SUN6I_XMIT_CNT_REG, SUN6I_XMIT_CNT(tx_len)); - sun6i_spi_write(sspi, SUN6I_BURST_CTL_CNT_REG, - SUN6I_BURST_CTL_CNT_STC(tx_len)); + sun6i_spi_write(sspi, SUN6I_BURST_CNT_REG, tfr->len); + 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, sspi->fifo_depth); From 9a3ef9df22ec1fe8d49b219c181c039f25c3296d Mon Sep 17 00:00:00 2001 From: Marc Kleine-Budde Date: Mon, 6 Jul 2020 16:34:38 +0200 Subject: [PATCH 14/19] spi: spi-sun6i: sun6i_spi_get_tx_fifo_count: Convert manual shift+mask to FIELD_GET() This patch converts the manual shift+mask in sun6i_spi_get_tx_fifo_count() to make use of FIELD_GET() Signed-off-by: Marc Kleine-Budde Link: https://lore.kernel.org/r/20200706143443.9855-6-mkl@pengutronix.de Signed-off-by: Mark Brown --- drivers/spi/spi-sun6i.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/drivers/spi/spi-sun6i.c b/drivers/spi/spi-sun6i.c index 882492774986..40365761f25d 100644 --- a/drivers/spi/spi-sun6i.c +++ b/drivers/spi/spi-sun6i.c @@ -7,6 +7,7 @@ * Maxime Ripard */ +#include #include #include #include @@ -60,8 +61,7 @@ #define SUN6I_FIFO_STA_REG 0x1c #define SUN6I_FIFO_STA_RF_CNT_MASK 0x7f #define SUN6I_FIFO_STA_RF_CNT_BITS 0 -#define SUN6I_FIFO_STA_TF_CNT_MASK 0x7f -#define SUN6I_FIFO_STA_TF_CNT_BITS 16 +#define SUN6I_FIFO_STA_TF_CNT_MASK GENMASK(23, 16) #define SUN6I_CLK_CTL_REG 0x24 #define SUN6I_CLK_CTL_CDR2_MASK 0xff @@ -110,9 +110,7 @@ static inline u32 sun6i_spi_get_tx_fifo_count(struct sun6i_spi *sspi) { u32 reg = sun6i_spi_read(sspi, SUN6I_FIFO_STA_REG); - reg >>= SUN6I_FIFO_STA_TF_CNT_BITS; - - return reg & SUN6I_FIFO_STA_TF_CNT_MASK; + return FIELD_GET(SUN6I_FIFO_STA_TF_CNT_MASK, reg); } static inline void sun6i_spi_enable_interrupt(struct sun6i_spi *sspi, u32 mask) From 5197da036398863c90db90b2ea53760fdaec5d86 Mon Sep 17 00:00:00 2001 From: Marc Kleine-Budde Date: Mon, 6 Jul 2020 16:34:39 +0200 Subject: [PATCH 15/19] spi: spi-sun6i: sun6i_spi_drain_fifo(): introduce sun6i_spi_get_rx_fifo_count() and make use of it This patch introduces the function sun6i_spi_get_rx_fifo_count(), similar to the existing sun6i_spi_get_tx_fifo_count(), to make the sun6i_spi_drain_fifo() function a bit easier to read. Signed-off-by: Marc Kleine-Budde Acked-by: Maxime Ripard Link: https://lore.kernel.org/r/20200706143443.9855-7-mkl@pengutronix.de Signed-off-by: Mark Brown --- drivers/spi/spi-sun6i.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/drivers/spi/spi-sun6i.c b/drivers/spi/spi-sun6i.c index 40365761f25d..44cd07331627 100644 --- a/drivers/spi/spi-sun6i.c +++ b/drivers/spi/spi-sun6i.c @@ -59,8 +59,7 @@ #define SUN6I_FIFO_CTL_TF_RST BIT(31) #define SUN6I_FIFO_STA_REG 0x1c -#define SUN6I_FIFO_STA_RF_CNT_MASK 0x7f -#define SUN6I_FIFO_STA_RF_CNT_BITS 0 +#define SUN6I_FIFO_STA_RF_CNT_MASK GENMASK(7, 0) #define SUN6I_FIFO_STA_TF_CNT_MASK GENMASK(23, 16) #define SUN6I_CLK_CTL_REG 0x24 @@ -106,6 +105,13 @@ static inline void sun6i_spi_write(struct sun6i_spi *sspi, u32 reg, u32 value) writel(value, sspi->base_addr + reg); } +static inline u32 sun6i_spi_get_rx_fifo_count(struct sun6i_spi *sspi) +{ + u32 reg = sun6i_spi_read(sspi, SUN6I_FIFO_STA_REG); + + return FIELD_GET(SUN6I_FIFO_STA_RF_CNT_MASK, reg); +} + static inline u32 sun6i_spi_get_tx_fifo_count(struct sun6i_spi *sspi) { u32 reg = sun6i_spi_read(sspi, SUN6I_FIFO_STA_REG); @@ -131,13 +137,11 @@ static inline void sun6i_spi_disable_interrupt(struct sun6i_spi *sspi, u32 mask) static inline void sun6i_spi_drain_fifo(struct sun6i_spi *sspi, int len) { - u32 reg, cnt; + u32 cnt; u8 byte; /* See how much data is available */ - reg = sun6i_spi_read(sspi, SUN6I_FIFO_STA_REG); - reg &= SUN6I_FIFO_STA_RF_CNT_MASK; - cnt = reg >> SUN6I_FIFO_STA_RF_CNT_BITS; + cnt = sun6i_spi_get_rx_fifo_count(sspi); if (len > cnt) len = cnt; From 92a52ee893c1ee5656df81f34f88aafe4e7e5033 Mon Sep 17 00:00:00 2001 From: Marc Kleine-Budde Date: Mon, 6 Jul 2020 16:34:40 +0200 Subject: [PATCH 16/19] spi: spi-sun6i: sun6i_spi_drain_fifo(): remove not needed length argument The function sun6i_spi_drain_fifo() is called with a length argument of "sspi->fifo_depth" and "SUN6I_FIFO_DEPTH". The driver reads the number of available bytes to read from the FIFO from the hardware and uses the length argument to limit this value. This is not needed as the FIFO can contain only the fifo depth number of bytes. This patch removes the length argument and check. Signed-off-by: Marc Kleine-Budde Acked-by: Maxime Ripard Link: https://lore.kernel.org/r/20200706143443.9855-8-mkl@pengutronix.de Signed-off-by: Mark Brown --- drivers/spi/spi-sun6i.c | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/drivers/spi/spi-sun6i.c b/drivers/spi/spi-sun6i.c index 44cd07331627..5849b65c3b1c 100644 --- a/drivers/spi/spi-sun6i.c +++ b/drivers/spi/spi-sun6i.c @@ -135,16 +135,13 @@ static inline void sun6i_spi_disable_interrupt(struct sun6i_spi *sspi, u32 mask) sun6i_spi_write(sspi, SUN6I_INT_CTL_REG, reg); } -static inline void sun6i_spi_drain_fifo(struct sun6i_spi *sspi, int len) +static inline void sun6i_spi_drain_fifo(struct sun6i_spi *sspi) { - u32 cnt; + u32 len; u8 byte; /* See how much data is available */ - cnt = sun6i_spi_get_rx_fifo_count(sspi); - - if (len > cnt) - len = cnt; + len = sun6i_spi_get_rx_fifo_count(sspi); while (len--) { byte = readb(sspi->base_addr + SUN6I_RXDATA_REG); @@ -348,14 +345,14 @@ 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, sspi->fifo_depth); + sun6i_spi_drain_fifo(sspi); complete(&sspi->done); return IRQ_HANDLED; } /* Receive FIFO 3/4 full */ if (status & SUN6I_INT_CTL_RF_RDY) { - sun6i_spi_drain_fifo(sspi, SUN6I_FIFO_DEPTH); + sun6i_spi_drain_fifo(sspi); /* Only clear the interrupt _after_ draining the FIFO */ sun6i_spi_write(sspi, SUN6I_INT_STA_REG, SUN6I_INT_CTL_RF_RDY); return IRQ_HANDLED; From e4e8ca3f43f97c82dbaef30e018acc4fd1ededf0 Mon Sep 17 00:00:00 2001 From: Marc Kleine-Budde Date: Mon, 6 Jul 2020 16:34:41 +0200 Subject: [PATCH 17/19] spi: spi-sun6i: sun6i_spi_fill_fifo(): remove not needed length argument The function sun6i_spi_fill_fifo() is called with a length argument of "sspi->fifo_depth" and "SUN6I_FIFO_DEPTH". The driver reads the number of free bytes in the FIFO from the hardware and uses the length argument to limit this value. This is not needed as the number of free bytes in the FIFO is always less or equal the depth of the FIFO. This patch removes the length argument and check. Signed-off-by: Marc Kleine-Budde Acked-by: Maxime Ripard Link: https://lore.kernel.org/r/20200706143443.9855-9-mkl@pengutronix.de Signed-off-by: Mark Brown --- drivers/spi/spi-sun6i.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/drivers/spi/spi-sun6i.c b/drivers/spi/spi-sun6i.c index 5849b65c3b1c..2442006c4229 100644 --- a/drivers/spi/spi-sun6i.c +++ b/drivers/spi/spi-sun6i.c @@ -150,15 +150,16 @@ static inline void sun6i_spi_drain_fifo(struct sun6i_spi *sspi) } } -static inline void sun6i_spi_fill_fifo(struct sun6i_spi *sspi, int len) +static inline void sun6i_spi_fill_fifo(struct sun6i_spi *sspi) { u32 cnt; + int len; u8 byte; /* See how much data we can fit */ cnt = sspi->fifo_depth - sun6i_spi_get_tx_fifo_count(sspi); - len = min3(len, (int)cnt, sspi->len); + len = min((int)cnt, sspi->len); while (len--) { byte = sspi->tx_buf ? *sspi->tx_buf++ : 0; @@ -306,7 +307,7 @@ static int sun6i_spi_transfer_one(struct spi_master *master, sun6i_spi_write(sspi, SUN6I_BURST_CTL_CNT_REG, tx_len); /* Fill the TX FIFO */ - sun6i_spi_fill_fifo(sspi, sspi->fifo_depth); + sun6i_spi_fill_fifo(sspi); /* Enable the interrupts */ sun6i_spi_write(sspi, SUN6I_INT_CTL_REG, SUN6I_INT_CTL_TC); @@ -360,7 +361,7 @@ static irqreturn_t sun6i_spi_handler(int irq, void *dev_id) /* Transmit FIFO 3/4 empty */ if (status & SUN6I_INT_CTL_TF_ERQ) { - sun6i_spi_fill_fifo(sspi, SUN6I_FIFO_DEPTH); + sun6i_spi_fill_fifo(sspi); if (!sspi->len) /* nothing left to transmit */ From 4e7390e997af380e20c5979a9963b521e62b4a58 Mon Sep 17 00:00:00 2001 From: Marc Kleine-Budde Date: Mon, 6 Jul 2020 16:34:42 +0200 Subject: [PATCH 18/19] spi: spi-sun6i: sun6i_spi_transfer_one(): collate write to Interrupt Control Register In sun6i_spi_transfer_one() the Interrupt Control Register is written three times. This patch collates the three writes into one. Signed-off-by: Marc Kleine-Budde Acked-by: Maxime Ripard Link: https://lore.kernel.org/r/20200706143443.9855-10-mkl@pengutronix.de Signed-off-by: Mark Brown --- drivers/spi/spi-sun6i.c | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/drivers/spi/spi-sun6i.c b/drivers/spi/spi-sun6i.c index 2442006c4229..bba9843c40c5 100644 --- a/drivers/spi/spi-sun6i.c +++ b/drivers/spi/spi-sun6i.c @@ -119,14 +119,6 @@ static inline u32 sun6i_spi_get_tx_fifo_count(struct sun6i_spi *sspi) return FIELD_GET(SUN6I_FIFO_STA_TF_CNT_MASK, reg); } -static inline void sun6i_spi_enable_interrupt(struct sun6i_spi *sspi, u32 mask) -{ - u32 reg = sun6i_spi_read(sspi, SUN6I_INT_CTL_REG); - - reg |= mask; - sun6i_spi_write(sspi, SUN6I_INT_CTL_REG, reg); -} - static inline void sun6i_spi_disable_interrupt(struct sun6i_spi *sspi, u32 mask) { u32 reg = sun6i_spi_read(sspi, SUN6I_INT_CTL_REG); @@ -310,11 +302,12 @@ static int sun6i_spi_transfer_one(struct spi_master *master, sun6i_spi_fill_fifo(sspi); /* Enable the interrupts */ - sun6i_spi_write(sspi, SUN6I_INT_CTL_REG, SUN6I_INT_CTL_TC); - sun6i_spi_enable_interrupt(sspi, SUN6I_INT_CTL_TC | - SUN6I_INT_CTL_RF_RDY); + reg = SUN6I_INT_CTL_TC | SUN6I_INT_CTL_RF_RDY; + if (tx_len > sspi->fifo_depth) - sun6i_spi_enable_interrupt(sspi, SUN6I_INT_CTL_TF_ERQ); + reg |= SUN6I_INT_CTL_TF_ERQ; + + sun6i_spi_write(sspi, SUN6I_INT_CTL_REG, reg); /* Start the transfer */ reg = sun6i_spi_read(sspi, SUN6I_TFR_CTL_REG); From 7716fa8068d418c39425f0fc9f3bae6c56261e86 Mon Sep 17 00:00:00 2001 From: Marc Kleine-Budde Date: Mon, 6 Jul 2020 16:34:43 +0200 Subject: [PATCH 19/19] spi: spi-sun6i: sun6i_spi_transfer_one(): enable RF_RDY interrupt only if needed In sun6i_spi_transfer_one() the RX FIFO Ready (SUN6I_INT_CTL_RF_RDY) is unconditionally enabled. A RX interrupt is only needed, if more data than fits into the FIFO is going to be received during this transfer. As the RX-FIFO is drained during transfer complete interrupt, enable the RX FIFO Ready interrupt only if the data doesn't fit into the FIFO. Signed-off-by: Marc Kleine-Budde Acked-by: Maxime Ripard Link: https://lore.kernel.org/r/20200706143443.9855-11-mkl@pengutronix.de Signed-off-by: Mark Brown --- drivers/spi/spi-sun6i.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/drivers/spi/spi-sun6i.c b/drivers/spi/spi-sun6i.c index bba9843c40c5..19238e1b76b4 100644 --- a/drivers/spi/spi-sun6i.c +++ b/drivers/spi/spi-sun6i.c @@ -190,7 +190,7 @@ static int sun6i_spi_transfer_one(struct spi_master *master, unsigned int mclk_rate, div, div_cdr1, div_cdr2, timeout; unsigned int start, end, tx_time; unsigned int trig_level; - unsigned int tx_len = 0; + unsigned int tx_len = 0, rx_len = 0; int ret = 0; u32 reg; @@ -245,10 +245,12 @@ static int sun6i_spi_transfer_one(struct spi_master *master, * If it's a TX only transfer, we don't want to fill the RX * FIFO with bogus data */ - if (sspi->rx_buf) + if (sspi->rx_buf) { reg &= ~SUN6I_TFR_CTL_DHB; - else + rx_len = tfr->len; + } else { reg |= SUN6I_TFR_CTL_DHB; + } /* We want to control the chip select manually */ reg |= SUN6I_TFR_CTL_CS_MANUAL; @@ -302,8 +304,10 @@ static int sun6i_spi_transfer_one(struct spi_master *master, sun6i_spi_fill_fifo(sspi); /* Enable the interrupts */ - reg = SUN6I_INT_CTL_TC | SUN6I_INT_CTL_RF_RDY; + 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;