diff options
author | Mark Brown <broonie@kernel.org> | 2020-10-09 16:01:22 +0100 |
---|---|---|
committer | Mark Brown <broonie@kernel.org> | 2020-10-09 16:01:22 +0100 |
commit | 988731181359efd771ae967f94936906fa38868a (patch) | |
tree | 49b5aee659fe3686d1f40374ecd36136d0d2f836 /drivers/spi | |
parent | d4f3a651ab82685c63e6fb38bec20b3ccf08c085 (diff) | |
parent | 855a40cd8cccfbf5597adfa77f55cdc8c44b6e42 (diff) |
Merge remote-tracking branch 'spi/for-5.10' into spi-next
Diffstat (limited to 'drivers/spi')
43 files changed, 2680 insertions, 1148 deletions
diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index c6ea760ea5f0..d2c976e55b8b 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -235,6 +235,7 @@ config SPI_DAVINCI config SPI_DESIGNWARE tristate "DesignWare SPI controller core support" + imply SPI_MEM help general driver for SPI controller core from DesignWare @@ -251,6 +252,34 @@ config SPI_DW_MMIO tristate "Memory-mapped io interface driver for DW SPI core" depends on HAS_IOMEM +config SPI_DW_BT1 + tristate "Baikal-T1 SPI driver for DW SPI core" + depends on MIPS_BAIKAL_T1 || COMPILE_TEST + help + Baikal-T1 SoC is equipped with three DW APB SSI-based MMIO SPI + controllers. Two of them are pretty much normal: with IRQ, DMA, + FIFOs of 64 words depth, 4x CSs, but the third one as being a + part of the Baikal-T1 System Boot Controller has got a very + limited resources: no IRQ, no DMA, only a single native + chip-select and Tx/Rx FIFO with just 8 words depth available. + The later one is normally connected to an external SPI-nor flash + of 128Mb (in general can be of bigger size). + +config SPI_DW_BT1_DIRMAP + bool "Directly mapped Baikal-T1 Boot SPI flash support" + depends on SPI_DW_BT1 + select MULTIPLEXER + select MUX_MMIO + help + Directly mapped SPI flash memory is an interface specific to the + Baikal-T1 System Boot Controller. It is a 16MB MMIO region, which + can be used to access a peripheral memory device just by + reading/writing data from/to it. Note that the system APB bus + will stall during each IO from/to the dirmap region until the + operation is finished. So try not to use it concurrently with + time-critical tasks (like the SPI memory operations implemented + in this driver). + endif config SPI_DLN2 @@ -637,7 +666,7 @@ config SPI_QCOM_QSPI config SPI_QUP tristate "Qualcomm SPI controller with QUP interface" - depends on ARCH_QCOM || (ARM && COMPILE_TEST) + depends on ARCH_QCOM || COMPILE_TEST help Qualcomm Universal Peripheral (QUP) core is an AHB slave that provides a common data path (an output FIFO and an input FIFO) diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile index cf955ea803cd..21dc75842aca 100644 --- a/drivers/spi/Makefile +++ b/drivers/spi/Makefile @@ -39,6 +39,7 @@ obj-$(CONFIG_SPI_DLN2) += spi-dln2.o obj-$(CONFIG_SPI_DESIGNWARE) += spi-dw.o spi-dw-y := spi-dw-core.o spi-dw-$(CONFIG_SPI_DW_DMA) += spi-dw-dma.o +obj-$(CONFIG_SPI_DW_BT1) += spi-dw-bt1.o obj-$(CONFIG_SPI_DW_MMIO) += spi-dw-mmio.o obj-$(CONFIG_SPI_DW_PCI) += spi-dw-pci.o obj-$(CONFIG_SPI_EFM32) += spi-efm32.o diff --git a/drivers/spi/spi-armada-3700.c b/drivers/spi/spi-armada-3700.c index fcde419e480c..46feafe4e201 100644 --- a/drivers/spi/spi-armada-3700.c +++ b/drivers/spi/spi-armada-3700.c @@ -848,7 +848,6 @@ static int a3700_spi_probe(struct platform_device *pdev) platform_set_drvdata(pdev, master); spi = spi_master_get_devdata(master); - memset(spi, 0, sizeof(struct a3700_spi)); spi->master = master; diff --git a/drivers/spi/spi-atmel.c b/drivers/spi/spi-atmel.c index 2cfe6253a784..ce5bd06d49b7 100644 --- a/drivers/spi/spi-atmel.c +++ b/drivers/spi/spi-atmel.c @@ -513,9 +513,8 @@ static int atmel_spi_configure_dma(struct spi_master *master, master->dma_tx = dma_request_chan(dev, "tx"); if (IS_ERR(master->dma_tx)) { - err = PTR_ERR(master->dma_tx); - if (err != -EPROBE_DEFER) - dev_err(dev, "No TX DMA channel, DMA is disabled\n"); + err = dev_err_probe(dev, PTR_ERR(master->dma_tx), + "No TX DMA channel, DMA is disabled\n"); goto error_clear; } @@ -859,6 +858,7 @@ static int atmel_spi_set_xfer_speed(struct atmel_spi *as, csr = spi_readl(as, CSR0 + 4 * chip_select); csr = SPI_BFINS(SCBR, scbr, csr); spi_writel(as, CSR0 + 4 * chip_select, csr); + xfer->effective_speed_hz = bus_hz / scbr; return 0; } diff --git a/drivers/spi/spi-bcm-qspi.c b/drivers/spi/spi-bcm-qspi.c index 9cfa15ec8b08..14c9d0133bce 100644 --- a/drivers/spi/spi-bcm-qspi.c +++ b/drivers/spi/spi-bcm-qspi.c @@ -1282,16 +1282,9 @@ static const struct bcm_qspi_data bcm_qspi_spcr3_data = { static const struct of_device_id bcm_qspi_of_match[] = { { - .compatible = "brcm,spi-bcm7425-qspi", - .data = &bcm_qspi_no_rev_data, - }, - { - .compatible = "brcm,spi-bcm7429-qspi", - .data = &bcm_qspi_no_rev_data, - }, - { - .compatible = "brcm,spi-bcm7435-qspi", - .data = &bcm_qspi_no_rev_data, + .compatible = "brcm,spi-bcm7445-qspi", + .data = &bcm_qspi_rev_data, + }, { .compatible = "brcm,spi-bcm-qspi", diff --git a/drivers/spi/spi-bcm2835.c b/drivers/spi/spi-bcm2835.c index 41986ac0fbfb..b87116e9b413 100644 --- a/drivers/spi/spi-bcm2835.c +++ b/drivers/spi/spi-bcm2835.c @@ -1319,11 +1319,8 @@ static int bcm2835_spi_probe(struct platform_device *pdev) bs->clk = devm_clk_get(&pdev->dev, NULL); if (IS_ERR(bs->clk)) { - err = PTR_ERR(bs->clk); - if (err == -EPROBE_DEFER) - dev_dbg(&pdev->dev, "could not get clk: %d\n", err); - else - dev_err(&pdev->dev, "could not get clk: %d\n", err); + err = dev_err_probe(&pdev->dev, PTR_ERR(bs->clk), + "could not get clk\n"); goto out_controller_put; } diff --git a/drivers/spi/spi-cadence-quadspi.c b/drivers/spi/spi-cadence-quadspi.c index c6795c684b16..40938cf3806d 100644 --- a/drivers/spi/spi-cadence-quadspi.c +++ b/drivers/spi/spi-cadence-quadspi.c @@ -1119,11 +1119,8 @@ static int cqspi_request_mmap_dma(struct cqspi_st *cqspi) cqspi->rx_chan = dma_request_chan_by_mask(&mask); if (IS_ERR(cqspi->rx_chan)) { int ret = PTR_ERR(cqspi->rx_chan); - - if (ret != -EPROBE_DEFER) - dev_err(&cqspi->pdev->dev, "No Rx DMA available\n"); cqspi->rx_chan = NULL; - return ret; + return dev_err_probe(&cqspi->pdev->dev, ret, "No Rx DMA available\n"); } init_completion(&cqspi->rx_dma_complete); diff --git a/drivers/spi/spi-cadence.c b/drivers/spi/spi-cadence.c index 2b6b9c1ad9d0..70467b9d61ba 100644 --- a/drivers/spi/spi-cadence.c +++ b/drivers/spi/spi-cadence.c @@ -418,8 +418,8 @@ static int cdns_transfer_one(struct spi_master *master, xspi->rx_bytes = transfer->len; cdns_spi_setup_transfer(spi, transfer); - cdns_spi_fill_tx_fifo(xspi); + spi_transfer_delay_exec(transfer); cdns_spi_write(xspi, CDNS_SPI_IER, CDNS_SPI_IXR_DEFAULT); return transfer->len; diff --git a/drivers/spi/spi-dw-bt1.c b/drivers/spi/spi-dw-bt1.c new file mode 100644 index 000000000000..f382dfad7842 --- /dev/null +++ b/drivers/spi/spi-dw-bt1.c @@ -0,0 +1,339 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright (C) 2020 BAIKAL ELECTRONICS, JSC +// +// Authors: +// Ramil Zaripov <Ramil.Zaripov@baikalelectronics.ru> +// Serge Semin <Sergey.Semin@baikalelectronics.ru> +// +// Baikal-T1 DW APB SPI and System Boot SPI driver +// + +#include <linux/clk.h> +#include <linux/cpumask.h> +#include <linux/err.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/mux/consumer.h> +#include <linux/of.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/property.h> +#include <linux/slab.h> +#include <linux/spi/spi-mem.h> +#include <linux/spi/spi.h> + +#include "spi-dw.h" + +#define BT1_BOOT_DIRMAP 0 +#define BT1_BOOT_REGS 1 + +struct dw_spi_bt1 { + struct dw_spi dws; + struct clk *clk; + struct mux_control *mux; + +#ifdef CONFIG_SPI_DW_BT1_DIRMAP + void __iomem *map; + resource_size_t map_len; +#endif +}; +#define to_dw_spi_bt1(_ctlr) \ + container_of(spi_controller_get_devdata(_ctlr), struct dw_spi_bt1, dws) + +typedef int (*dw_spi_bt1_init_cb)(struct platform_device *pdev, + struct dw_spi_bt1 *dwsbt1); + +#ifdef CONFIG_SPI_DW_BT1_DIRMAP + +static int dw_spi_bt1_dirmap_create(struct spi_mem_dirmap_desc *desc) +{ + struct dw_spi_bt1 *dwsbt1 = to_dw_spi_bt1(desc->mem->spi->controller); + + if (!dwsbt1->map || + !dwsbt1->dws.mem_ops.supports_op(desc->mem, &desc->info.op_tmpl)) + return -EOPNOTSUPP; + + /* + * Make sure the requested region doesn't go out of the physically + * mapped flash memory bounds and the operation is read-only. + */ + if (desc->info.offset + desc->info.length > dwsbt1->map_len || + desc->info.op_tmpl.data.dir != SPI_MEM_DATA_IN) + return -EOPNOTSUPP; + + return 0; +} + +/* + * Directly mapped SPI memory region is only accessible in the dword chunks. + * That's why we have to create a dedicated read-method to copy data from there + * to the passed buffer. + */ +static void dw_spi_bt1_dirmap_copy_from_map(void *to, void __iomem *from, size_t len) +{ + size_t shift, chunk; + u32 data; + + /* + * We split the copying up into the next three stages: unaligned head, + * aligned body, unaligned tail. + */ + shift = (size_t)from & 0x3; + if (shift) { + chunk = min_t(size_t, 4 - shift, len); + data = readl_relaxed(from - shift); + memcpy(to, &data + shift, chunk); + from += chunk; + to += chunk; + len -= chunk; + } + + while (len >= 4) { + data = readl_relaxed(from); + memcpy(to, &data, 4); + from += 4; + to += 4; + len -= 4; + } + + if (len) { + data = readl_relaxed(from); + memcpy(to, &data, len); + } +} + +static ssize_t dw_spi_bt1_dirmap_read(struct spi_mem_dirmap_desc *desc, + u64 offs, size_t len, void *buf) +{ + struct dw_spi_bt1 *dwsbt1 = to_dw_spi_bt1(desc->mem->spi->controller); + struct dw_spi *dws = &dwsbt1->dws; + struct spi_mem *mem = desc->mem; + struct dw_spi_cfg cfg; + int ret; + + /* + * Make sure the requested operation length is valid. Truncate the + * length if it's greater than the length of the MMIO region. + */ + if (offs >= dwsbt1->map_len || !len) + return 0; + + len = min_t(size_t, len, dwsbt1->map_len - offs); + + /* Collect the controller configuration required by the operation */ + cfg.tmode = SPI_TMOD_EPROMREAD; + cfg.dfs = 8; + cfg.ndf = 4; + cfg.freq = mem->spi->max_speed_hz; + + /* Make sure the corresponding CS is de-asserted on transmission */ + dw_spi_set_cs(mem->spi, false); + + spi_enable_chip(dws, 0); + + dw_spi_update_config(dws, mem->spi, &cfg); + + spi_umask_intr(dws, SPI_INT_RXFI); + + spi_enable_chip(dws, 1); + + /* + * Enable the transparent mode of the System Boot Controller. + * The SPI core IO should have been locked before calling this method + * so noone would be touching the controller' registers during the + * dirmap operation. + */ + ret = mux_control_select(dwsbt1->mux, BT1_BOOT_DIRMAP); + if (ret) + return ret; + + dw_spi_bt1_dirmap_copy_from_map(buf, dwsbt1->map + offs, len); + + mux_control_deselect(dwsbt1->mux); + + dw_spi_set_cs(mem->spi, true); + + ret = dw_spi_check_status(dws, true); + + return ret ?: len; +} + +#endif /* CONFIG_SPI_DW_BT1_DIRMAP */ + +static int dw_spi_bt1_std_init(struct platform_device *pdev, + struct dw_spi_bt1 *dwsbt1) +{ + struct dw_spi *dws = &dwsbt1->dws; + + dws->irq = platform_get_irq(pdev, 0); + if (dws->irq < 0) + return dws->irq; + + dws->num_cs = 4; + + /* + * Baikal-T1 Normal SPI Controllers don't always keep up with full SPI + * bus speed especially when it comes to the concurrent access to the + * APB bus resources. Thus we have no choice but to set a constraint on + * the SPI bus frequency for the memory operations which require to + * read/write data as fast as possible. + */ + dws->max_mem_freq = 20000000U; + + dw_spi_dma_setup_generic(dws); + + return 0; +} + +static int dw_spi_bt1_sys_init(struct platform_device *pdev, + struct dw_spi_bt1 *dwsbt1) +{ + struct resource *mem __maybe_unused; + struct dw_spi *dws = &dwsbt1->dws; + + /* + * Baikal-T1 System Boot Controller is equipped with a mux, which + * switches between the directly mapped SPI flash access mode and + * IO access to the DW APB SSI registers. Note the mux controller + * must be setup to preserve the registers being accessible by default + * (on idle-state). + */ + dwsbt1->mux = devm_mux_control_get(&pdev->dev, NULL); + if (IS_ERR(dwsbt1->mux)) + return PTR_ERR(dwsbt1->mux); + + /* + * Directly mapped SPI flash memory is a 16MB MMIO region, which can be + * used to access a peripheral memory device just by reading/writing + * data from/to it. Note the system APB bus will stall during each IO + * from/to the dirmap region until the operation is finished. So don't + * use it concurrently with time-critical tasks (like the SPI memory + * operations implemented in the DW APB SSI driver). + */ +#ifdef CONFIG_SPI_DW_BT1_DIRMAP + mem = platform_get_resource(pdev, IORESOURCE_MEM, 1); + if (mem) { + dwsbt1->map = devm_ioremap_resource(&pdev->dev, mem); + if (!IS_ERR(dwsbt1->map)) { + dwsbt1->map_len = (mem->end - mem->start + 1); + dws->mem_ops.dirmap_create = dw_spi_bt1_dirmap_create; + dws->mem_ops.dirmap_read = dw_spi_bt1_dirmap_read; + } else { + dwsbt1->map = NULL; + } + } +#endif /* CONFIG_SPI_DW_BT1_DIRMAP */ + + /* + * There is no IRQ, no DMA and just one CS available on the System Boot + * SPI controller. + */ + dws->irq = IRQ_NOTCONNECTED; + dws->num_cs = 1; + + /* + * Baikal-T1 System Boot SPI Controller doesn't keep up with the full + * SPI bus speed due to relatively slow APB bus and races for it' + * resources from different CPUs. The situation is worsen by a small + * FIFOs depth (just 8 words). It works better in a single CPU mode + * though, but still tends to be not fast enough at low CPU + * frequencies. + */ + if (num_possible_cpus() > 1) + dws->max_mem_freq = 10000000U; + else + dws->max_mem_freq = 20000000U; + + return 0; +} + +static int dw_spi_bt1_probe(struct platform_device *pdev) +{ + dw_spi_bt1_init_cb init_func; + struct dw_spi_bt1 *dwsbt1; + struct resource *mem; + struct dw_spi *dws; + int ret; + + dwsbt1 = devm_kzalloc(&pdev->dev, sizeof(struct dw_spi_bt1), GFP_KERNEL); + if (!dwsbt1) + return -ENOMEM; + + dws = &dwsbt1->dws; + + dws->regs = devm_platform_get_and_ioremap_resource(pdev, 0, &mem); + if (IS_ERR(dws->regs)) + return PTR_ERR(dws->regs); + + dws->paddr = mem->start; + + dwsbt1->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(dwsbt1->clk)) + return PTR_ERR(dwsbt1->clk); + + ret = clk_prepare_enable(dwsbt1->clk); + if (ret) + return ret; + + dws->bus_num = pdev->id; + dws->reg_io_width = 4; + dws->max_freq = clk_get_rate(dwsbt1->clk); + if (!dws->max_freq) + goto err_disable_clk; + + init_func = device_get_match_data(&pdev->dev); + ret = init_func(pdev, dwsbt1); + if (ret) + goto err_disable_clk; + + pm_runtime_enable(&pdev->dev); + + ret = dw_spi_add_host(&pdev->dev, dws); + if (ret) + goto err_disable_clk; + + platform_set_drvdata(pdev, dwsbt1); + + return 0; + +err_disable_clk: + clk_disable_unprepare(dwsbt1->clk); + + return ret; +} + +static int dw_spi_bt1_remove(struct platform_device *pdev) +{ + struct dw_spi_bt1 *dwsbt1 = platform_get_drvdata(pdev); + + dw_spi_remove_host(&dwsbt1->dws); + + pm_runtime_disable(&pdev->dev); + + clk_disable_unprepare(dwsbt1->clk); + + return 0; +} + +static const struct of_device_id dw_spi_bt1_of_match[] = { + { .compatible = "baikal,bt1-ssi", .data = dw_spi_bt1_std_init}, + { .compatible = "baikal,bt1-sys-ssi", .data = dw_spi_bt1_sys_init}, + { } +}; +MODULE_DEVICE_TABLE(of, dw_spi_bt1_of_match); + +static struct platform_driver dw_spi_bt1_driver = { + .probe = dw_spi_bt1_probe, + .remove = dw_spi_bt1_remove, + .driver = { + .name = "bt1-sys-ssi", + .of_match_table = dw_spi_bt1_of_match, + }, +}; +module_platform_driver(dw_spi_bt1_driver); + +MODULE_AUTHOR("Serge Semin <Sergey.Semin@baikalelectronics.ru>"); +MODULE_DESCRIPTION("Baikal-T1 System Boot SPI Controller driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/spi/spi-dw-core.c b/drivers/spi/spi-dw-core.c index 323c66c5db50..2e50cc0a9291 100644 --- a/drivers/spi/spi-dw-core.c +++ b/drivers/spi/spi-dw-core.c @@ -8,10 +8,14 @@ #include <linux/dma-mapping.h> #include <linux/interrupt.h> #include <linux/module.h> +#include <linux/preempt.h> #include <linux/highmem.h> #include <linux/delay.h> #include <linux/slab.h> #include <linux/spi/spi.h> +#include <linux/spi/spi-mem.h> +#include <linux/string.h> +#include <linux/of.h> #include "spi-dw.h" @@ -19,13 +23,10 @@ #include <linux/debugfs.h> #endif -/* Slave spi_dev related */ +/* Slave spi_device related */ struct chip_data { - u8 tmode; /* TR/TO/RO/EEPROM */ - u8 type; /* SPI/SSP/MicroWire */ - - u16 clk_div; /* baud rate divider */ - u32 speed_hz; /* baud rate */ + u32 cr0; + u32 rx_sample_dly; /* RX sample delay */ }; #ifdef CONFIG_DEBUG_FS @@ -52,6 +53,7 @@ static const struct debugfs_reg32 dw_spi_dbgfs_regs[] = { DW_SPI_DBGFS_REG("DMACR", DW_SPI_DMACR), DW_SPI_DBGFS_REG("DMATDLR", DW_SPI_DMATDLR), DW_SPI_DBGFS_REG("DMARDLR", DW_SPI_DMARDLR), + DW_SPI_DBGFS_REG("RX_SAMPLE_DLY", DW_SPI_RX_SAMPLE_DLY), }; static int dw_spi_debugfs_init(struct dw_spi *dws) @@ -101,7 +103,7 @@ void dw_spi_set_cs(struct spi_device *spi, bool enable) */ if (cs_high == enable) dw_writel(dws, DW_SPI_SER, BIT(spi->chip_select)); - else if (dws->cs_override) + else dw_writel(dws, DW_SPI_SER, 0); } EXPORT_SYMBOL_GPL(dw_spi_set_cs); @@ -109,9 +111,8 @@ EXPORT_SYMBOL_GPL(dw_spi_set_cs); /* Return the max entries we can fill into tx fifo */ static inline u32 tx_max(struct dw_spi *dws) { - u32 tx_left, tx_room, rxtx_gap; + u32 tx_room, rxtx_gap; - tx_left = (dws->tx_end - dws->tx) / dws->n_bytes; tx_room = dws->fifo_len - dw_readl(dws, DW_SPI_TXFLR); /* @@ -122,93 +123,124 @@ static inline u32 tx_max(struct dw_spi *dws) * shift registers. So a control from sw point of * view is taken. */ - rxtx_gap = ((dws->rx_end - dws->rx) - (dws->tx_end - dws->tx)) - / dws->n_bytes; + rxtx_gap = dws->fifo_len - (dws->rx_len - dws->tx_len); - return min3(tx_left, tx_room, (u32) (dws->fifo_len - rxtx_gap)); + return min3((u32)dws->tx_len, tx_room, rxtx_gap); } /* Return the max entries we should read out of rx fifo */ static inline u32 rx_max(struct dw_spi *dws) { - u32 rx_left = (dws->rx_end - dws->rx) / dws->n_bytes; - - return min_t(u32, rx_left, dw_readl(dws, DW_SPI_RXFLR)); + return min_t(u32, dws->rx_len, dw_readl(dws, DW_SPI_RXFLR)); } static void dw_writer(struct dw_spi *dws) { - u32 max; + u32 max = tx_max(dws); u16 txw = 0; - spin_lock(&dws->buf_lock); - max = tx_max(dws); while (max--) { - /* Set the tx word if the transfer's original "tx" is not null */ - if (dws->tx_end - dws->len) { + if (dws->tx) { if (dws->n_bytes == 1) txw = *(u8 *)(dws->tx); else txw = *(u16 *)(dws->tx); + + dws->tx += dws->n_bytes; } dw_write_io_reg(dws, DW_SPI_DR, txw); - dws->tx += dws->n_bytes; + --dws->tx_len; } - spin_unlock(&dws->buf_lock); } static void dw_reader(struct dw_spi *dws) { - u32 max; + u32 max = rx_max(dws); u16 rxw; - spin_lock(&dws->buf_lock); - max = rx_max(dws); while (max--) { rxw = dw_read_io_reg(dws, DW_SPI_DR); - /* Care rx only if the transfer's original "rx" is not null */ - if (dws->rx_end - dws->len) { + if (dws->rx) { if (dws->n_bytes == 1) *(u8 *)(dws->rx) = rxw; else *(u16 *)(dws->rx) = rxw; + + dws->rx += dws->n_bytes; } - dws->rx += dws->n_bytes; + --dws->rx_len; } - spin_unlock(&dws->buf_lock); } -static void int_error_stop(struct dw_spi *dws, const char *msg) +int dw_spi_check_status(struct dw_spi *dws, bool raw) { - spi_reset_chip(dws); + u32 irq_status; + int ret = 0; + + if (raw) + irq_status = dw_readl(dws, DW_SPI_RISR); + else + irq_status = dw_readl(dws, DW_SPI_ISR); + + if (irq_status & SPI_INT_RXOI) { + dev_err(&dws->master->dev, "RX FIFO overflow detected\n"); + ret = -EIO; + } + + if (irq_status & SPI_INT_RXUI) { + dev_err(&dws->master->dev, "RX FIFO underflow detected\n"); + ret = -EIO; + } + + if (irq_status & SPI_INT_TXOI) { + dev_err(&dws->master->dev, "TX FIFO overflow detected\n"); + ret = -EIO; + } - dev_err(&dws->master->dev, "%s\n", msg); - dws->master->cur_msg->status = -EIO; - spi_finalize_current_transfer(dws->master); + /* Generically handle the erroneous situation */ + if (ret) { + spi_reset_chip(dws); + if (dws->master->cur_msg) + dws->master->cur_msg->status = ret; + } + + return ret; } +EXPORT_SYMBOL_GPL(dw_spi_check_status); -static irqreturn_t interrupt_transfer(struct dw_spi *dws) +static irqreturn_t dw_spi_transfer_handler(struct dw_spi *dws) { u16 irq_status = dw_readl(dws, DW_SPI_ISR); - /* Error handling */ - if (irq_status & (SPI_INT_TXOI | SPI_INT_RXOI | SPI_INT_RXUI)) { - dw_readl(dws, DW_SPI_ICR); - int_error_stop(dws, "interrupt_transfer: fifo overrun/underrun"); + if (dw_spi_check_status(dws, false)) { + spi_finalize_current_transfer(dws->master); return IRQ_HANDLED; } + /* + * Read data from the Rx FIFO every time we've got a chance executing + * this method. If there is nothing left to receive, terminate the + * procedure. Otherwise adjust the Rx FIFO Threshold level if it's a + * final stage of the transfer. By doing so we'll get the next IRQ + * right when the leftover incoming data is received. + */ dw_reader(dws); - if (dws->rx_end == dws->rx) { - spi_mask_intr(dws, SPI_INT_TXEI); + if (!dws->rx_len) { + spi_mask_intr(dws, 0xff); spi_finalize_current_transfer(dws->master); - return IRQ_HANDLED; + } else if (dws->rx_len <= dw_readl(dws, DW_SPI_RXFTLR)) { + dw_writel(dws, DW_SPI_RXFTLR, dws->rx_len - 1); } + + /* + * Send data out if Tx FIFO Empty IRQ is received. The IRQ will be + * disabled after the data transmission is finished so not to + * have the TXE IRQ flood at the final stage of the transfer. + */ if (irq_status & SPI_INT_TXEI) { - spi_mask_intr(dws, SPI_INT_TXEI); dw_writer(dws); - /* Enable TX irq always, it will be disabled when RX finished */ - spi_umask_intr(dws, SPI_INT_TXEI); + if (!dws->tx_len) + spi_mask_intr(dws, SPI_INT_TXEI); } return IRQ_HANDLED; @@ -224,105 +256,176 @@ static irqreturn_t dw_spi_irq(int irq, void *dev_id) return IRQ_NONE; if (!master->cur_msg) { - spi_mask_intr(dws, SPI_INT_TXEI); + spi_mask_intr(dws, 0xff); return IRQ_HANDLED; } return dws->transfer_handler(dws); } -/* Configure CTRLR0 for DW_apb_ssi */ -u32 dw_spi_update_cr0(struct spi_controller *master, struct spi_device *spi, - struct spi_transfer *transfer) +static u32 dw_spi_prepare_cr0(struct dw_spi *dws, struct spi_device *spi) { - struct chip_data *chip = spi_get_ctldata(spi); - u32 cr0; - - /* Default SPI mode is SCPOL = 0, SCPH = 0 */ - cr0 = (transfer->bits_per_word - 1) - | (chip->type << SPI_FRF_OFFSET) - | ((((spi->mode & SPI_CPOL |