From b926b15547d29a88932de3c24a05c12826fc1dbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Monin?= Date: Thu, 2 Oct 2025 14:14:37 +0200 Subject: [PATCH 01/80] spi: dw: rename the spi controller to ctlr MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since the designware SPI controller can act as both a target and a host, rename spi_controller member of the dw_spi struct to ctlr instead of host. Similarly, rename the functions handling the controller, using controller instead of host as the suffix. No functional changes intended. Signed-off-by: Benoît Monin Link: https://patch.msgid.link/20251002-spi-dw-target-v1-1-993e91c1a712@bootlin.com Signed-off-by: Mark Brown --- drivers/spi/spi-dw-bt1.c | 4 +- drivers/spi/spi-dw-core.c | 128 +++++++++++++++++++------------------- drivers/spi/spi-dw-dma.c | 22 +++---- drivers/spi/spi-dw-mmio.c | 4 +- drivers/spi/spi-dw-pci.c | 8 +-- drivers/spi/spi-dw.h | 12 ++-- 6 files changed, 89 insertions(+), 89 deletions(-) diff --git a/drivers/spi/spi-dw-bt1.c b/drivers/spi/spi-dw-bt1.c index 4a5be813efa7..91642e05ac60 100644 --- a/drivers/spi/spi-dw-bt1.c +++ b/drivers/spi/spi-dw-bt1.c @@ -288,7 +288,7 @@ static int dw_spi_bt1_probe(struct platform_device *pdev) pm_runtime_enable(&pdev->dev); - ret = dw_spi_add_host(&pdev->dev, dws); + ret = dw_spi_add_controller(&pdev->dev, dws); if (ret) { pm_runtime_disable(&pdev->dev); return ret; @@ -303,7 +303,7 @@ static void dw_spi_bt1_remove(struct platform_device *pdev) { struct dw_spi_bt1 *dwsbt1 = platform_get_drvdata(pdev); - dw_spi_remove_host(&dwsbt1->dws); + dw_spi_remove_controller(&dwsbt1->dws); pm_runtime_disable(&pdev->dev); } diff --git a/drivers/spi/spi-dw-core.c b/drivers/spi/spi-dw-core.c index b3b883cb9541..90dea6f9b3da 100644 --- a/drivers/spi/spi-dw-core.c +++ b/drivers/spi/spi-dw-core.c @@ -63,7 +63,7 @@ static void dw_spi_debugfs_init(struct dw_spi *dws) { char name[32]; - snprintf(name, 32, "dw_spi%d", dws->host->bus_num); + snprintf(name, 32, "dw_spi%d", dws->ctlr->bus_num); dws->debugfs = debugfs_create_dir(name, NULL); dws->regset.regs = dw_spi_dbgfs_regs; @@ -185,25 +185,25 @@ int dw_spi_check_status(struct dw_spi *dws, bool raw) irq_status = dw_readl(dws, DW_SPI_ISR); if (irq_status & DW_SPI_INT_RXOI) { - dev_err(&dws->host->dev, "RX FIFO overflow detected\n"); + dev_err(&dws->ctlr->dev, "RX FIFO overflow detected\n"); ret = -EIO; } if (irq_status & DW_SPI_INT_RXUI) { - dev_err(&dws->host->dev, "RX FIFO underflow detected\n"); + dev_err(&dws->ctlr->dev, "RX FIFO underflow detected\n"); ret = -EIO; } if (irq_status & DW_SPI_INT_TXOI) { - dev_err(&dws->host->dev, "TX FIFO overflow detected\n"); + dev_err(&dws->ctlr->dev, "TX FIFO overflow detected\n"); ret = -EIO; } /* Generically handle the erroneous situation */ if (ret) { dw_spi_reset_chip(dws); - if (dws->host->cur_msg) - dws->host->cur_msg->status = ret; + if (dws->ctlr->cur_msg) + dws->ctlr->cur_msg->status = ret; } return ret; @@ -215,7 +215,7 @@ static irqreturn_t dw_spi_transfer_handler(struct dw_spi *dws) u16 irq_status = dw_readl(dws, DW_SPI_ISR); if (dw_spi_check_status(dws, false)) { - spi_finalize_current_transfer(dws->host); + spi_finalize_current_transfer(dws->ctlr); return IRQ_HANDLED; } @@ -229,7 +229,7 @@ static irqreturn_t dw_spi_transfer_handler(struct dw_spi *dws) dw_reader(dws); if (!dws->rx_len) { dw_spi_mask_intr(dws, 0xff); - spi_finalize_current_transfer(dws->host); + spi_finalize_current_transfer(dws->ctlr); } else if (dws->rx_len <= dw_readl(dws, DW_SPI_RXFTLR)) { dw_writel(dws, DW_SPI_RXFTLR, dws->rx_len - 1); } @@ -250,14 +250,14 @@ static irqreturn_t dw_spi_transfer_handler(struct dw_spi *dws) static irqreturn_t dw_spi_irq(int irq, void *dev_id) { - struct spi_controller *host = dev_id; - struct dw_spi *dws = spi_controller_get_devdata(host); + struct spi_controller *ctlr = dev_id; + struct dw_spi *dws = spi_controller_get_devdata(ctlr); u16 irq_status = dw_readl(dws, DW_SPI_ISR) & DW_SPI_INT_MASK; if (!irq_status) return IRQ_NONE; - if (!host->cur_msg) { + if (!ctlr->cur_msg) { dw_spi_mask_intr(dws, 0xff); return IRQ_HANDLED; } @@ -410,11 +410,11 @@ static int dw_spi_poll_transfer(struct dw_spi *dws, return 0; } -static int dw_spi_transfer_one(struct spi_controller *host, +static int dw_spi_transfer_one(struct spi_controller *ctlr, struct spi_device *spi, struct spi_transfer *transfer) { - struct dw_spi *dws = spi_controller_get_devdata(host); + struct dw_spi *dws = spi_controller_get_devdata(ctlr); struct dw_spi_cfg cfg = { .tmode = DW_SPI_CTRLR0_TMOD_TR, .dfs = transfer->bits_per_word, @@ -439,7 +439,7 @@ static int dw_spi_transfer_one(struct spi_controller *host, transfer->effective_speed_hz = dws->current_freq; /* Check if current transfer is a DMA transaction */ - dws->dma_mapped = spi_xfer_is_dma_mapped(host, spi, transfer); + dws->dma_mapped = spi_xfer_is_dma_mapped(ctlr, spi, transfer); /* For poll mode just disable all interrupts */ dw_spi_mask_intr(dws, 0xff); @@ -462,10 +462,10 @@ static int dw_spi_transfer_one(struct spi_controller *host, return 1; } -static void dw_spi_handle_err(struct spi_controller *host, +static void dw_spi_handle_err(struct spi_controller *ctlr, struct spi_message *msg) { - struct dw_spi *dws = spi_controller_get_devdata(host); + struct dw_spi *dws = spi_controller_get_devdata(ctlr); if (dws->dma_mapped) dws->dma_ops->dma_stop(dws); @@ -574,7 +574,7 @@ static int dw_spi_write_then_read(struct dw_spi *dws, struct spi_device *spi) while (len) { entries = readl_relaxed(dws->regs + DW_SPI_TXFLR); if (!entries) { - dev_err(&dws->host->dev, "CS de-assertion on Tx\n"); + dev_err(&dws->ctlr->dev, "CS de-assertion on Tx\n"); return -EIO; } room = min(dws->fifo_len - entries, len); @@ -594,7 +594,7 @@ static int dw_spi_write_then_read(struct dw_spi *dws, struct spi_device *spi) if (!entries) { sts = readl_relaxed(dws->regs + DW_SPI_RISR); if (sts & DW_SPI_INT_RXOI) { - dev_err(&dws->host->dev, "FIFO overflow on Rx\n"); + dev_err(&dws->ctlr->dev, "FIFO overflow on Rx\n"); return -EIO; } continue; @@ -635,7 +635,7 @@ static int dw_spi_wait_mem_op_done(struct dw_spi *dws) spi_delay_exec(&delay, NULL); if (retry < 0) { - dev_err(&dws->host->dev, "Mem op hanged up\n"); + dev_err(&dws->ctlr->dev, "Mem op hanged up\n"); return -EIO; } @@ -898,60 +898,60 @@ static const struct spi_controller_mem_caps dw_spi_mem_caps = { .per_op_freq = true, }; -int dw_spi_add_host(struct device *dev, struct dw_spi *dws) +int dw_spi_add_controller(struct device *dev, struct dw_spi *dws) { - struct spi_controller *host; + struct spi_controller *ctlr; int ret; if (!dws) return -EINVAL; - host = spi_alloc_host(dev, 0); - if (!host) + ctlr = spi_alloc_host(dev, 0); + if (!ctlr) return -ENOMEM; - device_set_node(&host->dev, dev_fwnode(dev)); + device_set_node(&ctlr->dev, dev_fwnode(dev)); - dws->host = host; + dws->ctlr = ctlr; dws->dma_addr = (dma_addr_t)(dws->paddr + DW_SPI_DR); - spi_controller_set_devdata(host, dws); + spi_controller_set_devdata(ctlr, dws); /* Basic HW init */ dw_spi_hw_init(dev, dws); ret = request_irq(dws->irq, dw_spi_irq, IRQF_SHARED, dev_name(dev), - host); + ctlr); if (ret < 0 && ret != -ENOTCONN) { dev_err(dev, "can not get IRQ\n"); - goto err_free_host; + goto err_free_ctlr; } dw_spi_init_mem_ops(dws); - host->use_gpio_descriptors = true; - host->mode_bits = SPI_CPOL | SPI_CPHA | SPI_LOOP; + ctlr->use_gpio_descriptors = true; + ctlr->mode_bits = SPI_CPOL | SPI_CPHA | SPI_LOOP; if (dws->caps & DW_SPI_CAP_DFS32) - host->bits_per_word_mask = SPI_BPW_RANGE_MASK(4, 32); + ctlr->bits_per_word_mask = SPI_BPW_RANGE_MASK(4, 32); else - host->bits_per_word_mask = SPI_BPW_RANGE_MASK(4, 16); - host->bus_num = dws->bus_num; - host->num_chipselect = dws->num_cs; - host->setup = dw_spi_setup; - host->cleanup = dw_spi_cleanup; + ctlr->bits_per_word_mask = SPI_BPW_RANGE_MASK(4, 16); + ctlr->bus_num = dws->bus_num; + ctlr->num_chipselect = dws->num_cs; + ctlr->setup = dw_spi_setup; + ctlr->cleanup = dw_spi_cleanup; if (dws->set_cs) - host->set_cs = dws->set_cs; + ctlr->set_cs = dws->set_cs; else - host->set_cs = dw_spi_set_cs; - host->transfer_one = dw_spi_transfer_one; - host->handle_err = dw_spi_handle_err; + ctlr->set_cs = dw_spi_set_cs; + ctlr->transfer_one = dw_spi_transfer_one; + ctlr->handle_err = dw_spi_handle_err; if (dws->mem_ops.exec_op) { - host->mem_ops = &dws->mem_ops; - host->mem_caps = &dw_spi_mem_caps; + ctlr->mem_ops = &dws->mem_ops; + ctlr->mem_caps = &dw_spi_mem_caps; } - host->max_speed_hz = dws->max_freq; - host->flags = SPI_CONTROLLER_GPIO_SS; - host->auto_runtime_pm = true; + ctlr->max_speed_hz = dws->max_freq; + ctlr->flags = SPI_CONTROLLER_GPIO_SS; + ctlr->auto_runtime_pm = true; /* Get default rx sample delay */ device_property_read_u32(dev, "rx-sample-delay-ns", @@ -964,14 +964,14 @@ int dw_spi_add_host(struct device *dev, struct dw_spi *dws) } else if (ret) { dev_warn(dev, "DMA init failed\n"); } else { - host->can_dma = dws->dma_ops->can_dma; - host->flags |= SPI_CONTROLLER_MUST_TX; + ctlr->can_dma = dws->dma_ops->can_dma; + ctlr->flags |= SPI_CONTROLLER_MUST_TX; } } - ret = spi_register_controller(host); + ret = spi_register_controller(ctlr); if (ret) { - dev_err_probe(dev, ret, "problem registering spi host\n"); + dev_err_probe(dev, ret, "problem registering spi controller\n"); goto err_dma_exit; } @@ -983,47 +983,47 @@ int dw_spi_add_host(struct device *dev, struct dw_spi *dws) dws->dma_ops->dma_exit(dws); dw_spi_enable_chip(dws, 0); err_free_irq: - free_irq(dws->irq, host); -err_free_host: - spi_controller_put(host); + free_irq(dws->irq, ctlr); +err_free_ctlr: + spi_controller_put(ctlr); return ret; } -EXPORT_SYMBOL_NS_GPL(dw_spi_add_host, "SPI_DW_CORE"); +EXPORT_SYMBOL_NS_GPL(dw_spi_add_controller, "SPI_DW_CORE"); -void dw_spi_remove_host(struct dw_spi *dws) +void dw_spi_remove_controller(struct dw_spi *dws) { dw_spi_debugfs_remove(dws); - spi_unregister_controller(dws->host); + spi_unregister_controller(dws->ctlr); if (dws->dma_ops && dws->dma_ops->dma_exit) dws->dma_ops->dma_exit(dws); dw_spi_shutdown_chip(dws); - free_irq(dws->irq, dws->host); + free_irq(dws->irq, dws->ctlr); } -EXPORT_SYMBOL_NS_GPL(dw_spi_remove_host, "SPI_DW_CORE"); +EXPORT_SYMBOL_NS_GPL(dw_spi_remove_controller, "SPI_DW_CORE"); -int dw_spi_suspend_host(struct dw_spi *dws) +int dw_spi_suspend_controller(struct dw_spi *dws) { int ret; - ret = spi_controller_suspend(dws->host); + ret = spi_controller_suspend(dws->ctlr); if (ret) return ret; dw_spi_shutdown_chip(dws); return 0; } -EXPORT_SYMBOL_NS_GPL(dw_spi_suspend_host, "SPI_DW_CORE"); +EXPORT_SYMBOL_NS_GPL(dw_spi_suspend_controller, "SPI_DW_CORE"); -int dw_spi_resume_host(struct dw_spi *dws) +int dw_spi_resume_controller(struct dw_spi *dws) { - dw_spi_hw_init(&dws->host->dev, dws); - return spi_controller_resume(dws->host); + dw_spi_hw_init(&dws->ctlr->dev, dws); + return spi_controller_resume(dws->ctlr); } -EXPORT_SYMBOL_NS_GPL(dw_spi_resume_host, "SPI_DW_CORE"); +EXPORT_SYMBOL_NS_GPL(dw_spi_resume_controller, "SPI_DW_CORE"); MODULE_AUTHOR("Feng Tang "); MODULE_DESCRIPTION("Driver for DesignWare SPI controller core"); diff --git a/drivers/spi/spi-dw-dma.c b/drivers/spi/spi-dw-dma.c index b5bed02b7e50..65adec7c7524 100644 --- a/drivers/spi/spi-dw-dma.c +++ b/drivers/spi/spi-dw-dma.c @@ -139,8 +139,8 @@ static int dw_spi_dma_init_mfld(struct device *dev, struct dw_spi *dws) if (!dws->txchan) goto free_rxchan; - dws->host->dma_rx = dws->rxchan; - dws->host->dma_tx = dws->txchan; + dws->ctlr->dma_rx = dws->rxchan; + dws->ctlr->dma_tx = dws->txchan; init_completion(&dws->dma_completion); @@ -183,8 +183,8 @@ static int dw_spi_dma_init_generic(struct device *dev, struct dw_spi *dws) goto free_rxchan; } - dws->host->dma_rx = dws->rxchan; - dws->host->dma_tx = dws->txchan; + dws->ctlr->dma_rx = dws->rxchan; + dws->ctlr->dma_tx = dws->txchan; init_completion(&dws->dma_completion); @@ -242,10 +242,10 @@ static enum dma_slave_buswidth dw_spi_dma_convert_width(u8 n_bytes) } } -static bool dw_spi_can_dma(struct spi_controller *host, +static bool dw_spi_can_dma(struct spi_controller *ctlr, struct spi_device *spi, struct spi_transfer *xfer) { - struct dw_spi *dws = spi_controller_get_devdata(host); + struct dw_spi *dws = spi_controller_get_devdata(ctlr); enum dma_slave_buswidth dma_bus_width; if (xfer->len <= dws->fifo_len) @@ -271,7 +271,7 @@ static int dw_spi_dma_wait(struct dw_spi *dws, unsigned int len, u32 speed) msecs_to_jiffies(ms)); if (ms == 0) { - dev_err(&dws->host->cur_msg->spi->dev, + dev_err(&dws->ctlr->cur_msg->spi->dev, "DMA transaction timed out\n"); return -ETIMEDOUT; } @@ -299,7 +299,7 @@ static int dw_spi_dma_wait_tx_done(struct dw_spi *dws, spi_delay_exec(&delay, xfer); if (retry < 0) { - dev_err(&dws->host->dev, "Tx hanged up\n"); + dev_err(&dws->ctlr->dev, "Tx hanged up\n"); return -EIO; } @@ -400,7 +400,7 @@ static int dw_spi_dma_wait_rx_done(struct dw_spi *dws) spi_delay_exec(&delay, NULL); if (retry < 0) { - dev_err(&dws->host->dev, "Rx hanged up\n"); + dev_err(&dws->ctlr->dev, "Rx hanged up\n"); return -EIO; } @@ -656,13 +656,13 @@ static int dw_spi_dma_transfer(struct dw_spi *dws, struct spi_transfer *xfer) if (ret) return ret; - if (dws->host->cur_msg->status == -EINPROGRESS) { + if (dws->ctlr->cur_msg->status == -EINPROGRESS) { ret = dw_spi_dma_wait_tx_done(dws, xfer); if (ret) return ret; } - if (xfer->rx_buf && dws->host->cur_msg->status == -EINPROGRESS) + if (xfer->rx_buf && dws->ctlr->cur_msg->status == -EINPROGRESS) ret = dw_spi_dma_wait_rx_done(dws); return ret; diff --git a/drivers/spi/spi-dw-mmio.c b/drivers/spi/spi-dw-mmio.c index 7a5197586919..a33f246560d8 100644 --- a/drivers/spi/spi-dw-mmio.c +++ b/drivers/spi/spi-dw-mmio.c @@ -382,7 +382,7 @@ static int dw_spi_mmio_probe(struct platform_device *pdev) pm_runtime_enable(&pdev->dev); - ret = dw_spi_add_host(&pdev->dev, dws); + ret = dw_spi_add_controller(&pdev->dev, dws); if (ret) goto out; @@ -401,7 +401,7 @@ static void dw_spi_mmio_remove(struct platform_device *pdev) { struct dw_spi_mmio *dwsmmio = platform_get_drvdata(pdev); - dw_spi_remove_host(&dwsmmio->dws); + dw_spi_remove_controller(&dwsmmio->dws); pm_runtime_disable(&pdev->dev); reset_control_assert(dwsmmio->rstc); } diff --git a/drivers/spi/spi-dw-pci.c b/drivers/spi/spi-dw-pci.c index b32d6648a32e..72d9f5bc87f7 100644 --- a/drivers/spi/spi-dw-pci.c +++ b/drivers/spi/spi-dw-pci.c @@ -127,7 +127,7 @@ static int dw_spi_pci_probe(struct pci_dev *pdev, const struct pci_device_id *en goto err_free_irq_vectors; } - ret = dw_spi_add_host(&pdev->dev, dws); + ret = dw_spi_add_controller(&pdev->dev, dws); if (ret) goto err_free_irq_vectors; @@ -156,7 +156,7 @@ static void dw_spi_pci_remove(struct pci_dev *pdev) pm_runtime_forbid(&pdev->dev); pm_runtime_get_noresume(&pdev->dev); - dw_spi_remove_host(dws); + dw_spi_remove_controller(dws); pci_free_irq_vectors(pdev); } @@ -165,14 +165,14 @@ static int dw_spi_pci_suspend(struct device *dev) { struct dw_spi *dws = dev_get_drvdata(dev); - return dw_spi_suspend_host(dws); + return dw_spi_suspend_controller(dws); } static int dw_spi_pci_resume(struct device *dev) { struct dw_spi *dws = dev_get_drvdata(dev); - return dw_spi_resume_host(dws); + return dw_spi_resume_controller(dws); } #endif diff --git a/drivers/spi/spi-dw.h b/drivers/spi/spi-dw.h index fc267c6437ae..9cc79c566a70 100644 --- a/drivers/spi/spi-dw.h +++ b/drivers/spi/spi-dw.h @@ -142,14 +142,14 @@ struct dw_spi_dma_ops { int (*dma_init)(struct device *dev, struct dw_spi *dws); void (*dma_exit)(struct dw_spi *dws); int (*dma_setup)(struct dw_spi *dws, struct spi_transfer *xfer); - bool (*can_dma)(struct spi_controller *host, struct spi_device *spi, + bool (*can_dma)(struct spi_controller *ctlr, struct spi_device *spi, struct spi_transfer *xfer); int (*dma_transfer)(struct dw_spi *dws, struct spi_transfer *xfer); void (*dma_stop)(struct dw_spi *dws); }; struct dw_spi { - struct spi_controller *host; + struct spi_controller *ctlr; u32 ip; /* Synopsys DW SSI IP-core ID */ u32 ver; /* Synopsys component version */ @@ -288,10 +288,10 @@ extern void dw_spi_set_cs(struct spi_device *spi, bool enable); extern void dw_spi_update_config(struct dw_spi *dws, struct spi_device *spi, struct dw_spi_cfg *cfg); extern int dw_spi_check_status(struct dw_spi *dws, bool raw); -extern int dw_spi_add_host(struct device *dev, struct dw_spi *dws); -extern void dw_spi_remove_host(struct dw_spi *dws); -extern int dw_spi_suspend_host(struct dw_spi *dws); -extern int dw_spi_resume_host(struct dw_spi *dws); +extern int dw_spi_add_controller(struct device *dev, struct dw_spi *dws); +extern void dw_spi_remove_controller(struct dw_spi *dws); +extern int dw_spi_suspend_controller(struct dw_spi *dws); +extern int dw_spi_resume_controller(struct dw_spi *dws); #ifdef CONFIG_SPI_DW_DMA From fe8cc44dd173cde5788ab4e3730ac61f3d316d9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Monin?= Date: Thu, 2 Oct 2025 14:14:38 +0200 Subject: [PATCH 02/80] spi: dw: add target mode support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implement target mode for the DesignWare controller with the following changes: Allocate an SPI controller of the correct type based on the spi-slave property in dw_spi_add_controller() and set the controller properties depending on its type. Since they are only relevant when acting as a host controller, settings related to chip-select control and the set_cs() callback are only set in host mode, as are the loopback support, the memory operations and the maximum frequency. The target_abort() callback is set only when configured in target mode. The number of chip-select is set to 1 in dw_spi_hw_init() since the controller only has one CS input in target mode. In dw_spi_update_config(), return after setting the CTRLR0 register as the other registers are only relevant in host mode and are read-only in target mode. This function is called as part of the transfer_one() callback, which is identical in both the host and target mode. Move the code implementing the handle_err() callback to a new function named dw_spi_abort(), and use it to implement both the handle_err() and the target_abort() callbacks. Finally, drop the error path on the spi-slave property in dw_spi_mmio_probe(), as it is now a valid configuration. Signed-off-by: Benoît Monin Link: https://patch.msgid.link/20251002-spi-dw-target-v1-2-993e91c1a712@bootlin.com Signed-off-by: Mark Brown --- drivers/spi/spi-dw-core.c | 82 +++++++++++++++++++++++++++------------ drivers/spi/spi-dw-mmio.c | 5 --- 2 files changed, 57 insertions(+), 30 deletions(-) diff --git a/drivers/spi/spi-dw-core.c b/drivers/spi/spi-dw-core.c index 90dea6f9b3da..9ebf244294f8 100644 --- a/drivers/spi/spi-dw-core.c +++ b/drivers/spi/spi-dw-core.c @@ -332,6 +332,9 @@ void dw_spi_update_config(struct dw_spi *dws, struct spi_device *spi, dw_writel(dws, DW_SPI_CTRLR0, cr0); + if (spi_controller_is_target(dws->ctlr)) + return; + if (cfg->tmode == DW_SPI_CTRLR0_TMOD_EPROMREAD || cfg->tmode == DW_SPI_CTRLR0_TMOD_RO) dw_writel(dws, DW_SPI_CTRLR1, cfg->ndf ? cfg->ndf - 1 : 0); @@ -462,8 +465,7 @@ static int dw_spi_transfer_one(struct spi_controller *ctlr, return 1; } -static void dw_spi_handle_err(struct spi_controller *ctlr, - struct spi_message *msg) +static inline void dw_spi_abort(struct spi_controller *ctlr) { struct dw_spi *dws = spi_controller_get_devdata(ctlr); @@ -473,6 +475,19 @@ static void dw_spi_handle_err(struct spi_controller *ctlr, dw_spi_reset_chip(dws); } +static void dw_spi_handle_err(struct spi_controller *ctlr, + struct spi_message *msg) +{ + dw_spi_abort(ctlr); +} + +static int dw_spi_target_abort(struct spi_controller *ctlr) +{ + dw_spi_abort(ctlr); + + return 0; +} + static int dw_spi_adjust_mem_op_size(struct spi_mem *mem, struct spi_mem_op *op) { if (op->data.dir == SPI_MEM_DATA_IN) @@ -834,18 +849,23 @@ static void dw_spi_hw_init(struct device *dev, struct dw_spi *dws) DW_SPI_GET_BYTE(dws->ver, 1)); } - /* - * Try to detect the number of native chip-selects if the platform - * driver didn't set it up. There can be up to 16 lines configured. - */ - if (!dws->num_cs) { - u32 ser; + if (spi_controller_is_target(dws->ctlr)) { + /* There is only one CS input signal in target mode */ + dws->num_cs = 1; + } else { + /* + * Try to detect the number of native chip-selects if the platform + * driver didn't set it up. There can be up to 16 lines configured. + */ + if (!dws->num_cs) { + u32 ser; - dw_writel(dws, DW_SPI_SER, 0xffff); - ser = dw_readl(dws, DW_SPI_SER); - dw_writel(dws, DW_SPI_SER, 0); + dw_writel(dws, DW_SPI_SER, 0xffff); + ser = dw_readl(dws, DW_SPI_SER); + dw_writel(dws, DW_SPI_SER, 0); - dws->num_cs = hweight16(ser); + dws->num_cs = hweight16(ser); + } } /* @@ -901,12 +921,18 @@ static const struct spi_controller_mem_caps dw_spi_mem_caps = { int dw_spi_add_controller(struct device *dev, struct dw_spi *dws) { struct spi_controller *ctlr; + bool target; int ret; if (!dws) return -EINVAL; - ctlr = spi_alloc_host(dev, 0); + target = device_property_read_bool(dev, "spi-slave"); + if (target) + ctlr = spi_alloc_target(dev, 0); + else + ctlr = spi_alloc_host(dev, 0); + if (!ctlr) return -ENOMEM; @@ -929,8 +955,7 @@ int dw_spi_add_controller(struct device *dev, struct dw_spi *dws) dw_spi_init_mem_ops(dws); - ctlr->use_gpio_descriptors = true; - ctlr->mode_bits = SPI_CPOL | SPI_CPHA | SPI_LOOP; + ctlr->mode_bits = SPI_CPOL | SPI_CPHA; if (dws->caps & DW_SPI_CAP_DFS32) ctlr->bits_per_word_mask = SPI_BPW_RANGE_MASK(4, 32); else @@ -939,20 +964,27 @@ int dw_spi_add_controller(struct device *dev, struct dw_spi *dws) ctlr->num_chipselect = dws->num_cs; ctlr->setup = dw_spi_setup; ctlr->cleanup = dw_spi_cleanup; - if (dws->set_cs) - ctlr->set_cs = dws->set_cs; - else - ctlr->set_cs = dw_spi_set_cs; ctlr->transfer_one = dw_spi_transfer_one; ctlr->handle_err = dw_spi_handle_err; - if (dws->mem_ops.exec_op) { - ctlr->mem_ops = &dws->mem_ops; - ctlr->mem_caps = &dw_spi_mem_caps; - } - ctlr->max_speed_hz = dws->max_freq; - ctlr->flags = SPI_CONTROLLER_GPIO_SS; ctlr->auto_runtime_pm = true; + if (!target) { + ctlr->use_gpio_descriptors = true; + ctlr->mode_bits |= SPI_LOOP; + if (dws->set_cs) + ctlr->set_cs = dws->set_cs; + else + ctlr->set_cs = dw_spi_set_cs; + if (dws->mem_ops.exec_op) { + ctlr->mem_ops = &dws->mem_ops; + ctlr->mem_caps = &dw_spi_mem_caps; + } + ctlr->max_speed_hz = dws->max_freq; + ctlr->flags = SPI_CONTROLLER_GPIO_SS; + } else { + ctlr->target_abort = dw_spi_target_abort; + } + /* Get default rx sample delay */ device_property_read_u32(dev, "rx-sample-delay-ns", &dws->def_rx_sample_dly_ns); diff --git a/drivers/spi/spi-dw-mmio.c b/drivers/spi/spi-dw-mmio.c index a33f246560d8..33239b4778cb 100644 --- a/drivers/spi/spi-dw-mmio.c +++ b/drivers/spi/spi-dw-mmio.c @@ -321,11 +321,6 @@ static int dw_spi_mmio_probe(struct platform_device *pdev) struct dw_spi *dws; int ret; - if (device_property_read_bool(&pdev->dev, "spi-slave")) { - dev_warn(&pdev->dev, "spi-slave is not yet supported\n"); - return -ENODEV; - } - dwsmmio = devm_kzalloc(&pdev->dev, sizeof(struct dw_spi_mmio), GFP_KERNEL); if (!dwsmmio) From 31dcc7e1f8a9377d8fd9f967f84c121c5ba8f89c Mon Sep 17 00:00:00 2001 From: Chin-Ting Kuo Date: Wed, 1 Oct 2025 19:26:00 +0800 Subject: [PATCH 03/80] spi: aspeed: Update clock selection strategy This patch updates the SPI clock selection logic for cases where timing calibration is not performed or the results are failed. Timing calibration process is skipped in the two scenarios below. - Low-entropy data in the calibration region: The driver skips timing calibration if the data read from the SPI flash contains mostly 0x00 or 0xFF. Originally, the driver used a low-frequency clock to read this region as golden data. However, due to variations in host characteristics and image layout, we cannot assume sufficient entropy in this region to ensure reliable calibration. - Low-speed configurations (< 40MHz): The ASPEED SPI controller does not support timing calibration when the max_speed_hz of the SPI device is below 40MHz. In both cases, the SPI clock frequency specified in the device tree should be used directly. When timing calibration is skipped, it is the board vendor's responsibility to ensure that the SPI flash SI (Signal Integrity) is sufficient for reliable operation at the configured frequency. When timing calibration processes is execued and all potential clock frequencies are performed, but are all failed, the lower clock frequency should be adopted to ensure the overall system can boot up successfully. Signed-off-by: Chin-Ting Kuo Link: https://patch.msgid.link/20251001112605.1130723-2-chin-ting_kuo@aspeedtech.com Signed-off-by: Mark Brown --- drivers/spi/spi-aspeed-smc.c | 179 +++++++++++++++++++++++++++++++---- 1 file changed, 160 insertions(+), 19 deletions(-) diff --git a/drivers/spi/spi-aspeed-smc.c b/drivers/spi/spi-aspeed-smc.c index 62a11142bd63..9c54c05da3cc 100644 --- a/drivers/spi/spi-aspeed-smc.c +++ b/drivers/spi/spi-aspeed-smc.c @@ -82,6 +82,7 @@ struct aspeed_spi_data { u32 (*segment_start)(struct aspeed_spi *aspi, u32 reg); u32 (*segment_end)(struct aspeed_spi *aspi, u32 reg); u32 (*segment_reg)(struct aspeed_spi *aspi, u32 start, u32 end); + u32 (*get_clk_div)(struct aspeed_spi_chip *chip, u32 hz); int (*calibrate)(struct aspeed_spi_chip *chip, u32 hdiv, const u8 *golden_buf, u8 *test_buf); }; @@ -942,26 +943,149 @@ static bool aspeed_spi_check_calib_data(const u8 *test_buf, u32 size) } static const u32 aspeed_spi_hclk_divs[] = { - 0xf, /* HCLK */ - 0x7, /* HCLK/2 */ - 0xe, /* HCLK/3 */ - 0x6, /* HCLK/4 */ - 0xd, /* HCLK/5 */ + /* HCLK, HCLK/2, HCLK/3, HCLK/4, HCLK/5, ..., HCLK/16 */ + 0xf, 0x7, 0xe, 0x6, 0xd, + 0x5, 0xc, 0x4, 0xb, 0x3, + 0xa, 0x2, 0x9, 0x1, 0x8, + 0x0 }; #define ASPEED_SPI_HCLK_DIV(i) \ (aspeed_spi_hclk_divs[(i) - 1] << CTRL_FREQ_SEL_SHIFT) +/* Transfer maximum clock frequency to register setting */ +static u32 aspeed_get_clk_div_ast2400(struct aspeed_spi_chip *chip, + u32 max_hz) +{ + struct device *dev = chip->aspi->dev; + u32 hclk_clk = chip->aspi->clk_freq; + u32 div_ctl = 0; + u32 i; + bool found = false; + + /* FMC/SPIR10[11:8] */ + for (i = 1; i <= ARRAY_SIZE(aspeed_spi_hclk_divs); i++) { + if (hclk_clk / i <= max_hz) { + found = true; + break; + } + } + + if (found) { + div_ctl = ASPEED_SPI_HCLK_DIV(i); + chip->clk_freq = hclk_clk / i; + } + + dev_dbg(dev, "found: %s, hclk: %d, max_clk: %d\n", + found ? "yes" : "no", hclk_clk, max_hz); + + if (found) { + dev_dbg(dev, "h_div: 0x%08x, speed: %d\n", + div_ctl, chip->clk_freq); + } + + return div_ctl; +} + +static u32 aspeed_get_clk_div_ast2500(struct aspeed_spi_chip *chip, + u32 max_hz) +{ + struct device *dev = chip->aspi->dev; + u32 hclk_clk = chip->aspi->clk_freq; + u32 div_ctl = 0; + u32 i; + bool found = false; + + /* FMC/SPIR10[11:8] */ + for (i = 1; i <= ARRAY_SIZE(aspeed_spi_hclk_divs); i++) { + if (hclk_clk / i <= max_hz) { + found = true; + chip->clk_freq = hclk_clk / i; + break; + } + } + + if (found) { + div_ctl = ASPEED_SPI_HCLK_DIV(i); + goto end; + } + + for (i = 1; i <= ARRAY_SIZE(aspeed_spi_hclk_divs); i++) { + if (hclk_clk / (i * 4) <= max_hz) { + found = true; + chip->clk_freq = hclk_clk / (i * 4); + break; + } + } + + if (found) + div_ctl = BIT(13) | ASPEED_SPI_HCLK_DIV(i); + +end: + dev_dbg(dev, "found: %s, hclk: %d, max_clk: %d\n", + found ? "yes" : "no", hclk_clk, max_hz); + + if (found) { + dev_dbg(dev, "h_div: 0x%08x, speed: %d\n", + div_ctl, chip->clk_freq); + } + + return div_ctl; +} + +static u32 aspeed_get_clk_div_ast2600(struct aspeed_spi_chip *chip, + u32 max_hz) +{ + struct device *dev = chip->aspi->dev; + u32 hclk_clk = chip->aspi->clk_freq; + u32 div_ctl = 0; + u32 i, j; + bool found = false; + + /* FMC/SPIR10[27:24] */ + for (j = 0; j < 16; j++) { + /* FMC/SPIR10[11:8] */ + for (i = 1; i <= ARRAY_SIZE(aspeed_spi_hclk_divs); i++) { + if (j == 0 && i == 1) + continue; + + if (hclk_clk / (j * 16 + i) <= max_hz) { + found = true; + break; + } + } + + if (found) { + div_ctl = ((j << 24) | ASPEED_SPI_HCLK_DIV(i)); + chip->clk_freq = hclk_clk / (j * 16 + i); + break; + } + } + + dev_dbg(dev, "found: %s, hclk: %d, max_clk: %d\n", + found ? "yes" : "no", hclk_clk, max_hz); + + if (found) { + dev_dbg(dev, "h_div: 0x%08x, speed: %d\n", + div_ctl, chip->clk_freq); + } + + return div_ctl; +} + static int aspeed_spi_do_calibration(struct aspeed_spi_chip *chip) { struct aspeed_spi *aspi = chip->aspi; const struct aspeed_spi_data *data = aspi->data; u32 ahb_freq = aspi->clk_freq; u32 max_freq = chip->clk_freq; + bool exec_calib = false; + u32 best_freq = 0; u32 ctl_val; u8 *golden_buf = NULL; u8 *test_buf = NULL; - int i, rc, best_div = -1; + int i, rc; + u32 div_ctl; dev_dbg(aspi->dev, "calculate timing compensation - AHB freq: %d MHz", ahb_freq / 1000000); @@ -982,7 +1106,7 @@ static int aspeed_spi_do_calibration(struct aspeed_spi_chip *chip) memcpy_fromio(golden_buf, chip->ahb_base, CALIBRATE_BUF_SIZE); if (!aspeed_spi_check_calib_data(golden_buf, CALIBRATE_BUF_SIZE)) { dev_info(aspi->dev, "Calibration area too uniform, using low speed"); - goto no_calib; + goto end_calib; } #if defined(VERBOSE_DEBUG) @@ -991,7 +1115,7 @@ static int aspeed_spi_do_calibration(struct aspeed_spi_chip *chip) #endif /* Now we iterate the HCLK dividers until we find our breaking point */ - for (i = ARRAY_SIZE(aspeed_spi_hclk_divs); i > data->hdiv_max - 1; i--) { + for (i = 5; i > data->hdiv_max - 1; i--) { u32 tv, freq; freq = ahb_freq / i; @@ -1004,22 +1128,33 @@ static int aspeed_spi_do_calibration(struct aspeed_spi_chip *chip) dev_dbg(aspi->dev, "Trying HCLK/%d [%08x] ...", i, tv); rc = data->calibrate(chip, i, golden_buf, test_buf); if (rc == 0) - best_div = i; + best_freq = freq; + + exec_calib = true; } - /* Nothing found ? */ - if (best_div < 0) { - dev_warn(aspi->dev, "No good frequency, using dumb slow"); +end_calib: + if (!exec_calib) { + /* calibration process is not executed */ + dev_warn(aspi->dev, "Force to dts configuration %dkHz.\n", + max_freq / 1000); + div_ctl = data->get_clk_div(chip, max_freq); + } else if (best_freq == 0) { + /* calibration process is executed, but no good frequency */ + dev_warn(aspi->dev, "No good frequency, using dumb slow\n"); + div_ctl = 0; } else { - dev_dbg(aspi->dev, "Found good read timings at HCLK/%d", best_div); - - /* Record the freq */ - for (i = 0; i < ASPEED_SPI_MAX; i++) - chip->ctl_val[i] = (chip->ctl_val[i] & data->hclk_mask) | - ASPEED_SPI_HCLK_DIV(best_div); + dev_dbg(aspi->dev, "Found good read timings at %dMHz.\n", + best_freq / 1000000); + div_ctl = data->get_clk_div(chip, best_freq); + } + + /* Record the freq */ + for (i = 0; i < ASPEED_SPI_MAX; i++) { + chip->ctl_val[i] = (chip->ctl_val[i] & data->hclk_mask) | + div_ctl; } -no_calib: writel(chip->ctl_val[ASPEED_SPI_READ], chip->ctl); kfree(test_buf); return 0; @@ -1096,6 +1231,7 @@ static const struct aspeed_spi_data ast2400_fmc_data = { .hclk_mask = 0xfffff0ff, .hdiv_max = 1, .calibrate = aspeed_spi_calibrate, + .get_clk_div = aspeed_get_clk_div_ast2400, .segment_start = aspeed_spi_segment_start, .segment_end = aspeed_spi_segment_end, .segment_reg = aspeed_spi_segment_reg, @@ -1109,6 +1245,7 @@ static const struct aspeed_spi_data ast2400_spi_data = { .timing = 0x14, .hclk_mask = 0xfffff0ff, .hdiv_max = 1, + .get_clk_div = aspeed_get_clk_div_ast2400, .calibrate = aspeed_spi_calibrate, /* No segment registers */ }; @@ -1121,6 +1258,7 @@ static const struct aspeed_spi_data ast2500_fmc_data = { .timing = CE0_TIMING_COMPENSATION_REG, .hclk_mask = 0xffffd0ff, .hdiv_max = 1, + .get_clk_div = aspeed_get_clk_div_ast2500, .calibrate = aspeed_spi_calibrate, .segment_start = aspeed_spi_segment_start, .segment_end = aspeed_spi_segment_end, @@ -1135,6 +1273,7 @@ static const struct aspeed_spi_data ast2500_spi_data = { .timing = CE0_TIMING_COMPENSATION_REG, .hclk_mask = 0xffffd0ff, .hdiv_max = 1, + .get_clk_div = aspeed_get_clk_div_ast2500, .calibrate = aspeed_spi_calibrate, .segment_start = aspeed_spi_segment_start, .segment_end = aspeed_spi_segment_end, @@ -1150,6 +1289,7 @@ static const struct aspeed_spi_data ast2600_fmc_data = { .timing = CE0_TIMING_COMPENSATION_REG, .hclk_mask = 0xf0fff0ff, .hdiv_max = 2, + .get_clk_div = aspeed_get_clk_div_ast2600, .calibrate = aspeed_spi_ast2600_calibrate, .segment_start = aspeed_spi_segment_ast2600_start, .segment_end = aspeed_spi_segment_ast2600_end, @@ -1165,6 +1305,7 @@ static const struct aspeed_spi_data ast2600_spi_data = { .timing = CE0_TIMING_COMPENSATION_REG, .hclk_mask = 0xf0fff0ff, .hdiv_max = 2, + .get_clk_div = aspeed_get_clk_div_ast2600, .calibrate = aspeed_spi_ast2600_calibrate, .segment_start = aspeed_spi_segment_ast2600_start, .segment_end = aspeed_spi_segment_ast2600_end, From efb79de36e947d136517bac14c139d494fcc72fa Mon Sep 17 00:00:00 2001 From: Chin-Ting Kuo Date: Wed, 1 Oct 2025 19:26:01 +0800 Subject: [PATCH 04/80] spi: aspeed: Improve timing calibration algorithm for AST2600 platform Starting with the AST2600 platform, most platfom manufacturers have adopted more complex board designs and signal routing, making SPI timing calibration increasingly sensitive and critical. Previously, the driver selected the first "PASS" timing point during calibration, which may not yield the most stable result. This patch introduces a more robust calibration method: - It evaluates all combinations of HCLK sample point delay and DI input delay. The results are stored in a 2D buffer for further comparison. - Because the timing delay behavior is non-linear across HCLK sample points, the optimal timing point is selected as the center of the longest consecutive "PASS" interval within a single HCLK sample point row. This approach ensures better stability and precision in SPI read timing, especially under complex signal integrity conditions. Signed-off-by: Chin-Ting Kuo Link: https://patch.msgid.link/20251001112605.1130723-3-chin-ting_kuo@aspeedtech.com Signed-off-by: Mark Brown --- drivers/spi/spi-aspeed-smc.c | 70 +++++++++++++++++++++++++++++------- 1 file changed, 58 insertions(+), 12 deletions(-) diff --git a/drivers/spi/spi-aspeed-smc.c b/drivers/spi/spi-aspeed-smc.c index 9c54c05da3cc..d2d9e13e9bda 100644 --- a/drivers/spi/spi-aspeed-smc.c +++ b/drivers/spi/spi-aspeed-smc.c @@ -1162,21 +1162,57 @@ static int aspeed_spi_do_calibration(struct aspeed_spi_chip *chip) #define TIMING_DELAY_DI BIT(3) #define TIMING_DELAY_HCYCLE_MAX 5 +#define TIMING_DELAY_INPUT_MAX 16 #define TIMING_REG_AST2600(chip) \ ((chip)->aspi->regs + (chip)->aspi->data->timing + \ (chip)->cs * 4) +/* + * This function returns the center point of the longest + * continuous "pass" interval within the buffer. The interval + * must contains the highest number of consecutive "pass" + * results and not span across multiple rows. + */ +static u32 aspeed_spi_ast2600_optimized_timing(u32 rows, u32 cols, + u8 buf[rows][cols]) +{ + int r = 0, c = 0; + int max = 0; + int i, j; + + for (i = 0; i < rows; i++) { + for (j = 0; j < cols;) { + int k = j; + + while (k < cols && buf[i][k]) + k++; + + if (k - j > max) { + max = k - j; + r = i; + c = j + (k - j) / 2; + } + + j = k + 1; + } + } + + return max > 4 ? r * cols + c : 0; +} + static int aspeed_spi_ast2600_calibrate(struct aspeed_spi_chip *chip, u32 hdiv, const u8 *golden_buf, u8 *test_buf) { struct aspeed_spi *aspi = chip->aspi; int hcycle; + int delay_ns; u32 shift = (hdiv - 2) << 3; - u32 mask = ~(0xfu << shift); + u32 mask = ~(0xffu << shift); u32 fread_timing_val = 0; + u8 calib_res[6][17] = {0}; + u32 calib_point; for (hcycle = 0; hcycle <= TIMING_DELAY_HCYCLE_MAX; hcycle++) { - int delay_ns; bool pass = false; fread_timing_val &= mask; @@ -1189,14 +1225,14 @@ static int aspeed_spi_ast2600_calibrate(struct aspeed_spi_chip *chip, u32 hdiv, " * [%08x] %d HCLK delay, DI delay none : %s", fread_timing_val, hcycle, pass ? "PASS" : "FAIL"); if (pass) - return 0; + calib_res[hcycle][0] = 1; /* Add DI input delays */ fread_timing_val &= mask; fread_timing_val |= (TIMING_DELAY_DI | hcycle) << shift; - for (delay_ns = 0; delay_ns < 0x10; delay_ns++) { - fread_timing_val &= ~(0xf << (4 + shift)); + for (delay_ns = 0; delay_ns < TIMING_DELAY_INPUT_MAX; delay_ns++) { + fread_timing_val &= ~(0xfu << (4 + shift)); fread_timing_val |= delay_ns << (4 + shift); writel(fread_timing_val, TIMING_REG_AST2600(chip)); @@ -1205,18 +1241,28 @@ static int aspeed_spi_ast2600_calibrate(struct aspeed_spi_chip *chip, u32 hdiv, " * [%08x] %d HCLK delay, DI delay %d.%dns : %s", fread_timing_val, hcycle, (delay_ns + 1) / 2, (delay_ns + 1) & 1 ? 5 : 5, pass ? "PASS" : "FAIL"); - /* - * TODO: This is optimistic. We should look - * for a working interval and save the middle - * value in the read timing register. - */ + if (pass) - return 0; + calib_res[hcycle][delay_ns + 1] = 1; } } + calib_point = aspeed_spi_ast2600_optimized_timing(6, 17, calib_res); /* No good setting for this frequency */ - return -1; + if (calib_point == 0) + return -1; + + hcycle = calib_point / 17; + delay_ns = calib_point % 17; + + fread_timing_val = (TIMING_DELAY_DI | hcycle | (delay_ns << 4)) << shift; + + dev_dbg(aspi->dev, "timing val: %08x, final hcycle: %d, delay_ns: %d\n", + fread_timing_val, hcycle, delay_ns); + + writel(fread_timing_val, TIMING_REG_AST2600(chip)); + + return 0; } /* From 630a185fd06109193574d10f38b29812986c21de Mon Sep 17 00:00:00 2001 From: Chin-Ting Kuo Date: Wed, 1 Oct 2025 19:26:02 +0800 Subject: [PATCH 05/80] spi: aspeed: Force default address decoding range assignment for each CS On some platforms, we cannot assume that the whole address decoding range value is ready for each CS. Especially for chip selects other than CS0, the address decoding range may not be properly configured before the kernel stage, or the existing configuration may be unsuitable. This can lead to SPI flash detection failures during driver probe. To ensure reliable initialization, this patch forcibly assigns a default address decoding range to each chip select based on a platform-specific minimum window size. Unused chip selects are explicitly disabled to avoid conflicts. This change improves robustness across platforms with varying bootloader behavior and ensures consistent SPI flash initialization. Signed-off-by: Chin-Ting Kuo Link: https://patch.msgid.link/20251001112605.1130723-4-chin-ting_kuo@aspeedtech.com Signed-off-by: Mark Brown --- drivers/spi/spi-aspeed-smc.c | 106 ++++++++++++++++++++++------------- 1 file changed, 67 insertions(+), 39 deletions(-) diff --git a/drivers/spi/spi-aspeed-smc.c b/drivers/spi/spi-aspeed-smc.c index d2d9e13e9bda..29fed8477958 100644 --- a/drivers/spi/spi-aspeed-smc.c +++ b/drivers/spi/spi-aspeed-smc.c @@ -78,6 +78,7 @@ struct aspeed_spi_data { u32 timing; u32 hclk_mask; u32 hdiv_max; + u32 min_window_size; u32 (*segment_start)(struct aspeed_spi *aspi, u32 reg); u32 (*segment_end)(struct aspeed_spi *aspi, u32 reg); @@ -96,6 +97,7 @@ struct aspeed_spi { void __iomem *ahb_base; u32 ahb_base_phy; u32 ahb_window_size; + u32 num_cs; struct device *dev; struct clk *clk; @@ -401,35 +403,6 @@ static void aspeed_spi_get_windows(struct aspeed_spi *aspi, } } -/* - * On the AST2600, some CE windows are closed by default at reset but - * U-Boot should open all. - */ -static int aspeed_spi_chip_set_default_window(struct aspeed_spi_chip *chip) -{ - struct aspeed_spi *aspi = chip->aspi; - struct aspeed_spi_window windows[ASPEED_SPI_MAX_NUM_CS] = { 0 }; - struct aspeed_spi_window *win = &windows[chip->cs]; - - /* No segment registers for the AST2400 SPI controller */ - if (aspi->data == &ast2400_spi_data) { - win->offset = 0; - win->size = aspi->ahb_window_size; - } else { - aspeed_spi_get_windows(aspi, windows); - } - - chip->ahb_base = aspi->ahb_base + win->offset; - chip->ahb_window_size = win->size; - - dev_dbg(aspi->dev, "CE%d default window [ 0x%.8x - 0x%.8x ] %dMB", - chip->cs, aspi->ahb_base_phy + win->offset, - aspi->ahb_base_phy + win->offset + win->size - 1, - win->size >> 20); - - return chip->ahb_window_size ? 0 : -1; -} - static int aspeed_spi_set_window(struct aspeed_spi *aspi, const struct aspeed_spi_window *win) { @@ -464,16 +437,63 @@ static int aspeed_spi_set_window(struct aspeed_spi *aspi, return 0; } +static const struct aspeed_spi_data ast2500_spi_data; +static const struct aspeed_spi_data ast2600_spi_data; +static const struct aspeed_spi_data ast2600_fmc_data; + +static int aspeed_spi_chip_set_default_window(struct aspeed_spi *aspi) +{ + int ret; + u32 cs; + struct aspeed_spi_window win; + + /* No segment registers for the AST2400 SPI controller */ + if (aspi->data == &ast2400_spi_data) { + aspi->chips[0].ahb_base = aspi->ahb_base; + aspi->chips[0].ahb_window_size = aspi->ahb_window_size; + return 0; + } + + /* Assign the minimum window size to each CS */ + for (cs = 0; cs < aspi->num_cs; cs++) { + if (cs == 0) + aspi->chips[cs].ahb_base = aspi->ahb_base; + else + aspi->chips[cs].ahb_base = + aspi->chips[cs - 1].ahb_base + + aspi->chips[cs - 1].ahb_window_size; + + aspi->chips[cs].ahb_window_size = aspi->data->min_window_size; + + dev_dbg(aspi->dev, "CE%d default window [ 0x%.8x - 0x%.8x ]", + cs, aspi->ahb_base_phy + aspi->data->min_window_size * cs, + aspi->ahb_base_phy + aspi->data->min_window_size * cs - 1); + } + + /* Close unused CS */ + for (cs = aspi->num_cs; cs < aspi->data->max_cs; cs++) { + aspi->chips[cs].ahb_base = aspi->ahb_base; + aspi->chips[cs].ahb_window_size = 0; + } + + for (cs = 0; cs < aspi->num_cs; cs++) { + win.cs = cs; + win.offset = aspi->chips[cs].ahb_base - aspi->ahb_base; + win.size = aspi->chips[cs].ahb_window_size; + ret = aspeed_spi_set_window(aspi, &win); + if (ret) + return ret; + } + + return 0; +} + /* * Yet to be done when possible : * - Align mappings on flash size (we don't have the info) * - ioremap each window, not strictly necessary since the overall window * is correct. */ -static const struct aspeed_spi_data ast2500_spi_data; -static const struct aspeed_spi_data ast2600_spi_data; -static const struct aspeed_spi_data ast2600_fmc_data; - static int aspeed_spi_chip_adjust_window(struct aspeed_spi_chip *chip, u32 local_offset, u32 size) { @@ -678,11 +698,6 @@ static int aspeed_spi_setup(struct spi_device *spi) if (data->hastype) aspeed_spi_chip_set_type(aspi, cs, CONFIG_TYPE_SPI); - if (aspeed_spi_chip_set_default_window(chip) < 0) { - dev_warn(aspi->dev, "CE%d window invalid", cs); - return -EINVAL; - } - aspeed_spi_chip_enable(aspi, cs, true); chip->ctl_val[ASPEED_SPI_BASE] = CTRL_CE_STOP_ACTIVE | CTRL_IO_MODE_USER; @@ -763,9 +778,17 @@ static int aspeed_spi_probe(struct platform_device *pdev) ctlr->mem_ops = &aspeed_spi_mem_ops; ctlr->setup = aspeed_spi_setup; ctlr->cleanup = aspeed_spi_cleanup; - ctlr->num_chipselect = data->max_cs; + ctlr->num_chipselect = of_get_available_child_count(dev->of_node); ctlr->dev.of_node = dev->of_node; + aspi->num_cs = ctlr->num_chipselect; + + ret = aspeed_spi_chip_set_default_window(aspi); + if (ret) { + dev_err(&pdev->dev, "fail to set default window\n"); + return ret; + } + ret = devm_spi_register_controller(dev, ctlr); if (ret) dev_err(&pdev->dev, "spi_register_controller failed\n"); @@ -1276,6 +1299,7 @@ static const struct aspeed_spi_data ast2400_fmc_data = { .timing = CE0_TIMING_COMPENSATION_REG, .hclk_mask = 0xfffff0ff, .hdiv_max = 1, + .min_window_size = 0x800000, .calibrate = aspeed_spi_calibrate, .get_clk_div = aspeed_get_clk_div_ast2400, .segment_start = aspeed_spi_segment_start, @@ -1304,6 +1328,7 @@ static const struct aspeed_spi_data ast2500_fmc_data = { .timing = CE0_TIMING_COMPENSATION_REG, .hclk_mask = 0xffffd0ff, .hdiv_max = 1, + .min_window_size = 0x800000, .get_clk_div = aspeed_get_clk_div_ast2500, .calibrate = aspeed_spi_calibrate, .segment_start = aspeed_spi_segment_start, @@ -1319,6 +1344,7 @@ static const struct aspeed_spi_data ast2500_spi_data = { .timing = CE0_TIMING_COMPENSATION_REG, .hclk_mask = 0xffffd0ff, .hdiv_max = 1, + .min_window_size = 0x800000, .get_clk_div = aspeed_get_clk_div_ast2500, .calibrate = aspeed_spi_calibrate, .segment_start = aspeed_spi_segment_start, @@ -1335,6 +1361,7 @@ static const struct aspeed_spi_data ast2600_fmc_data = { .timing = CE0_TIMING_COMPENSATION_REG, .hclk_mask = 0xf0fff0ff, .hdiv_max = 2, + .min_window_size = 0x200000, .get_clk_div = aspeed_get_clk_div_ast2600, .calibrate = aspeed_spi_ast2600_calibrate, .segment_start = aspeed_spi_segment_ast2600_start, @@ -1351,6 +1378,7 @@ static const struct aspeed_spi_data ast2600_spi_data = { .timing = CE0_TIMING_COMPENSATION_REG, .hclk_mask = 0xf0fff0ff, .hdiv_max = 2, + .min_window_size = 0x200000, .get_clk_div = aspeed_get_clk_div_ast2600, .calibrate = aspeed_spi_ast2600_calibrate, .segment_start = aspeed_spi_segment_ast2600_start, From b546e0023a203e7edf9377ac8f4f490a6965afd6 Mon Sep 17 00:00:00 2001 From: Chin-Ting Kuo Date: Wed, 1 Oct 2025 19:26:03 +0800 Subject: [PATCH 06/80] spi: aspeed: Centralize address decoding region management The original approach to handling address decoding overlaps was to trim the next region directly. If the next CS's decoding range was fully overlapped by the current one, it would be forcibly closed by trimming its size to zero. This could lead expected behavior, especially on the platform with multiple flashes layout. To solve improper trimming problem, this patch collects the required address decoding size at each stage, then, (re-)arragne address decoding region to each CS centrally with knowing the total AHB decoding size. If a segment register cannot be updated (e.g. due to bootloader write protection), the original value is kept to avoid breaking access and an error is reported if the total decoding size of all CS exceeds the total AHB decoding size. Signed-off-by: Chin-Ting Kuo Link: https://patch.msgid.link/20251001112605.1130723-5-chin-ting_kuo@aspeedtech.com Signed-off-by: Mark Brown --- drivers/spi/spi-aspeed-smc.c | 164 +++++++++++++---------------------- 1 file changed, 62 insertions(+), 102 deletions(-) diff --git a/drivers/spi/spi-aspeed-smc.c b/drivers/spi/spi-aspeed-smc.c index 29fed8477958..83a47ac0711e 100644 --- a/drivers/spi/spi-aspeed-smc.c +++ b/drivers/spi/spi-aspeed-smc.c @@ -379,60 +379,58 @@ static const char *aspeed_spi_get_name(struct spi_mem *mem) spi_get_chipselect(mem->spi, 0)); } -struct aspeed_spi_window { - u32 cs; - u32 offset; - u32 size; -}; - -static void aspeed_spi_get_windows(struct aspeed_spi *aspi, - struct aspeed_spi_window windows[ASPEED_SPI_MAX_NUM_CS]) +static int aspeed_spi_set_window(struct aspeed_spi *aspi) { - const struct aspeed_spi_data *data = aspi->data; - u32 reg_val; + struct device *dev = aspi->dev; + off_t offset = 0; + phys_addr_t start; + phys_addr_t end; + void __iomem *seg_reg_base = aspi->regs + CE0_SEGMENT_ADDR_REG; + void __iomem *seg_reg; + u32 seg_val_backup; + u32 seg_val; u32 cs; + size_t window_size; for (cs = 0; cs < aspi->data->max_cs; cs++) { - reg_val = readl(aspi->regs + CE0_SEGMENT_ADDR_REG + cs * 4); - windows[cs].cs = cs; - windows[cs].size = data->segment_end(aspi, reg_val) - - data->segment_start(aspi, reg_val); - windows[cs].offset = data->segment_start(aspi, reg_val) - aspi->ahb_base_phy; - dev_vdbg(aspi->dev, "CE%d offset=0x%.8x size=0x%x\n", cs, - windows[cs].offset, windows[cs].size); + seg_reg = seg_reg_base + cs * 4; + seg_val_backup = readl(seg_reg); + + start = aspi->ahb_base_phy + offset; + window_size = aspi->chips[cs].ahb_window_size; + end = start + window_size; + + seg_val = aspi->data->segment_reg(aspi, start, end); + writel(seg_val, seg_reg); + + /* + * Restore initial value if something goes wrong or the segment + * register is written protected. + */ + if (seg_val != readl(seg_reg)) { + dev_warn(dev, "CE%d expected window [ 0x%.9llx - 0x%.9llx ] %zdMB\n", + cs, (u64)start, (u64)end - 1, window_size >> 20); + writel(seg_val_backup, seg_reg); + window_size = aspi->data->segment_end(aspi, seg_val_backup) - + aspi->data->segment_start(aspi, seg_val_backup); + aspi->chips[cs].ahb_window_size = window_size; + end = start + window_size; + } + + if (window_size != 0) + dev_dbg(dev, "CE%d window [ 0x%.9llx - 0x%.9llx ] %zdMB\n", + cs, (u64)start, (u64)end - 1, window_size >> 20); + else + dev_dbg(dev, "CE%d window closed\n", cs); + + aspi->chips[cs].ahb_base = aspi->ahb_base + offset; + offset += window_size; + if (offset > aspi->ahb_window_size) { + dev_err(dev, "CE%d offset value 0x%llx is too large.\n", + cs, (u64)offset); + return -ENOSPC; + } } -} - -static int aspeed_spi_set_window(struct aspeed_spi *aspi, - const struct aspeed_spi_window *win) -{ - u32 start = aspi->ahb_base_phy + win->offset; - u32 end = start + win->size; - void __iomem *seg_reg = aspi->regs + CE0_SEGMENT_ADDR_REG + win->cs * 4; - u32 seg_val_backup = readl(seg_reg); - u32 seg_val = aspi->data->segment_reg(aspi, start, end); - - if (seg_val == seg_val_backup) - return 0; - - writel(seg_val, seg_reg); - - /* - * Restore initial value if something goes wrong else we could - * loose access to the chip. - */ - if (seg_val != readl(seg_reg)) { - dev_err(aspi->dev, "CE%d invalid window [ 0x%.8x - 0x%.8x ] %dMB", - win->cs, start, end - 1, win->size >> 20); - writel(seg_val_backup, seg_reg); - return -EIO; - } - - if (win->size) - dev_dbg(aspi->dev, "CE%d new window [ 0x%.8x - 0x%.8x ] %dMB", - win->cs, start, end - 1, win->size >> 20); - else - dev_dbg(aspi->dev, "CE%d window closed", win->cs); return 0; } @@ -443,9 +441,7 @@ static const struct aspeed_spi_data ast2600_fmc_data; static int aspeed_spi_chip_set_default_window(struct aspeed_spi *aspi) { - int ret; u32 cs; - struct aspeed_spi_window win; /* No segment registers for the AST2400 SPI controller */ if (aspi->data == &ast2400_spi_data) { @@ -456,36 +452,17 @@ static int aspeed_spi_chip_set_default_window(struct aspeed_spi *aspi) /* Assign the minimum window size to each CS */ for (cs = 0; cs < aspi->num_cs; cs++) { - if (cs == 0) - aspi->chips[cs].ahb_base = aspi->ahb_base; - else - aspi->chips[cs].ahb_base = - aspi->chips[cs - 1].ahb_base + - aspi->chips[cs - 1].ahb_window_size; - aspi->chips[cs].ahb_window_size = aspi->data->min_window_size; - dev_dbg(aspi->dev, "CE%d default window [ 0x%.8x - 0x%.8x ]", cs, aspi->ahb_base_phy + aspi->data->min_window_size * cs, aspi->ahb_base_phy + aspi->data->min_window_size * cs - 1); } /* Close unused CS */ - for (cs = aspi->num_cs; cs < aspi->data->max_cs; cs++) { - aspi->chips[cs].ahb_base = aspi->ahb_base; + for (cs = aspi->num_cs; cs < aspi->data->max_cs; cs++) aspi->chips[cs].ahb_window_size = 0; - } - for (cs = 0; cs < aspi->num_cs; cs++) { - win.cs = cs; - win.offset = aspi->chips[cs].ahb_base - aspi->ahb_base; - win.size = aspi->chips[cs].ahb_window_size; - ret = aspeed_spi_set_window(aspi, &win); - if (ret) - return ret; - } - - return 0; + return aspeed_spi_set_window(aspi); } /* @@ -498,8 +475,8 @@ static int aspeed_spi_chip_adjust_window(struct aspeed_spi_chip *chip, u32 local_offset, u32 size) { struct aspeed_spi *aspi = chip->aspi; - struct aspeed_spi_window windows[ASPEED_SPI_MAX_NUM_CS] = { 0 }; - struct aspeed_spi_window *win = &windows[chip->cs]; + u32 cs; + u32 total_window_size; int ret; /* No segment registers for the AST2400 SPI controller */ @@ -527,41 +504,24 @@ static int aspeed_spi_chip_adjust_window(struct aspeed_spi_chip *chip, chip->cs, size >> 20); } - aspeed_spi_get_windows(aspi, windows); - /* Adjust this chip window */ - win->offset += local_offset; - win->size = size; + aspi->chips[chip->cs].ahb_window_size = size; - if (win->offset + win->size > aspi->ahb_window_size) { - win->size = aspi->ahb_window_size - win->offset; - dev_warn(aspi->dev, "CE%d window resized to %dMB", chip->cs, win->size >> 20); + total_window_size = 0; + for (cs = 0; cs < aspi->data->max_cs; cs++) + total_window_size += aspi->chips[cs].ahb_window_size; + + if (total_window_size > aspi->ahb_window_size) { + aspi->chips[chip->cs].ahb_window_size -= (total_window_size - + aspi->ahb_window_size); + dev_warn(aspi->dev, "CE%d window resized to %zdMB", + chip->cs, aspi->chips[chip->cs].ahb_window_size >> 20); } - ret = aspeed_spi_set_window(aspi, win); + ret = aspeed_spi_set_window(aspi); if (ret) return ret; - /* Update chip mapping info */ - chip->ahb_base = aspi->ahb_base + win->offset; - chip->ahb_window_size = win->size; - - /* - * Also adjust next chip window to make sure that it does not - * overlap with the current window. - */ - if (chip->cs < aspi->data->max_cs - 1) { - struct aspeed_spi_window *next = &windows[chip->cs + 1]; - - /* Change offset and size to keep the same end address */ - if ((next->offset + next->size) > (win->offset + win->size)) - next->size = (next->offset + next->size) - (win->offset + win->size); - else - next->size = 0; - next->offset = win->offset + win->size; - - aspeed_spi_set_window(aspi, next); - } return 0; } From 0586b53d4a0c7c5a132629f99da934cc674ea4cd Mon Sep 17 00:00:00 2001 From: Chin-Ting Kuo Date: Wed, 1 Oct 2025 19:26:04 +0800 Subject: [PATCH 07/80] spi: aspeed: Add per-platform adjust_window callback for decoding range Different ASPEED SoCs have specific limitations on SPI address decoding, such as total range size, minimum window size per CS, and alignment requirements. The original adjustment logic only handles simple cases and could fail in more complex setups found in advanced board designs, e.g., small flash on CS0 and large flash on CS1, or when the total physical flash size exceeds the decoding range supported by the SPI controller. This patch introduces a per-platform adjust_window callback to handle these constraints properly. Each platform defines its own logic to adjust decoding ranges, trim excess size, and ensure alignment. If trimming is required, the affected CS will fall back to user mode access to ensure the entire flash remains accessible from the MTD layer. Signed-off-by: Chin-Ting Kuo Link: https://patch.msgid.link/20251001112605.1130723-6-chin-ting_kuo@aspeedtech.com Signed-off-by: Mark Brown --- drivers/spi/spi-aspeed-smc.c | 202 +++++++++++++++++++++++++++++------ 1 file changed, 168 insertions(+), 34 deletions(-) diff --git a/drivers/spi/spi-aspeed-smc.c b/drivers/spi/spi-aspeed-smc.c index 83a47ac0711e..4f6ae48dd904 100644 --- a/drivers/spi/spi-aspeed-smc.c +++ b/drivers/spi/spi-aspeed-smc.c @@ -67,6 +67,7 @@ struct aspeed_spi_chip { u32 ahb_window_size; u32 ctl_val[ASPEED_SPI_MAX]; u32 clk_freq; + bool force_user_mode; }; struct aspeed_spi_data { @@ -83,6 +84,7 @@ struct aspeed_spi_data { u32 (*segment_start)(struct aspeed_spi *aspi, u32 reg); u32 (*segment_end)(struct aspeed_spi *aspi, u32 reg); u32 (*segment_reg)(struct aspeed_spi *aspi, u32 start, u32 end); + int (*adjust_window)(struct aspeed_spi *aspi); u32 (*get_clk_div)(struct aspeed_spi_chip *chip, u32 hz); int (*calibrate)(struct aspeed_spi_chip *chip, u32 hdiv, const u8 *golden_buf, u8 *test_buf); @@ -462,9 +464,166 @@ static int aspeed_spi_chip_set_default_window(struct aspeed_spi *aspi) for (cs = aspi->num_cs; cs < aspi->data->max_cs; cs++) aspi->chips[cs].ahb_window_size = 0; + if (aspi->data->adjust_window) + aspi->data->adjust_window(aspi); + return aspeed_spi_set_window(aspi); } +/* + * As the flash size grows up, we need to trim some decoding + * size if needed for the sake of conforming the maximum + * decoding size. We trim the decoding size from the rear CS + * to avoid affecting the default boot up sequence, usually, + * from CS0. Notice, if a CS decoding size is trimmed, + * command mode may not work perfectly on that CS, but it only + * affect performance and the debug function. + */ +static int aspeed_spi_trim_window_size(struct aspeed_spi *aspi) +{ + struct aspeed_spi_chip *chips = aspi->chips; + size_t total_sz; + int cs = aspi->data->max_cs - 1; + u32 i; + bool trimmed = false; + + do { + total_sz = 0; + for (i = 0; i < aspi->data->max_cs; i++) + total_sz += chips[i].ahb_window_size; + + if (cs < 0) + return -ENOMEM; + + if (chips[cs].ahb_window_size <= aspi->data->min_window_size) { + cs--; + continue; + } + + if (total_sz > aspi->ahb_window_size) { + chips[cs].ahb_window_size -= + aspi->data->min_window_size; + total_sz -= aspi->data->min_window_size; + /* + * If the ahb window size is ever trimmed, only user + * mode can be adopted to access the whole flash. + */ + chips[cs].force_user_mode = true; + trimmed = true; + } + } while (total_sz > aspi->ahb_window_size); + + if (trimmed) { + dev_warn(aspi->dev, "Window size after triming:\n"); + for (cs = 0; cs < aspi->data->max_cs; cs++) { + dev_warn(aspi->dev, "CE%d: 0x%08x\n", + cs, chips[cs].ahb_window_size); + } + } + + return 0; +} + +static int aspeed_adjust_window_ast2400(struct aspeed_spi *aspi) +{ + int ret; + int cs; + struct aspeed_spi_chip *chips = aspi->chips; + + /* Close unused CS. */ + for (cs = aspi->num_cs; cs < aspi->data->max_cs; cs++) + chips[cs].ahb_window_size = 0; + + ret = aspeed_spi_trim_window_size(aspi); + if (ret != 0) + return ret; + + return 0; +} + +/* + * For AST2500, the minimum address decoding size for each CS + * is 8MB. This address decoding size is mandatory for each + * CS no matter whether it will be used. This is a HW limitation. + */ +static int aspeed_adjust_window_ast2500(struct aspeed_spi *aspi) +{ + int ret; + int cs, i; + u32 cum_size, rem_size; + struct aspeed_spi_chip *chips = aspi->chips; + + /* Assign min_window_sz to unused CS. */ + for (cs = aspi->num_cs; cs < aspi->data->max_cs; cs++) { + if (chips[cs].ahb_window_size < aspi->data->min_window_size) + chips[cs].ahb_window_size = + aspi->data->min_window_size; + } + + /* + * If command mode or normal mode is used by dirmap read, the start + * address of a window should be multiple of its related flash size. + * Namely, the total windows size from flash 0 to flash N should + * be multiple of the size of flash (N + 1). + */ + for (cs = aspi->num_cs - 1; cs >= 0; cs--) { + cum_size = 0; + for (i = 0; i < cs; i++) + cum_size += chips[i].ahb_window_size; + + rem_size = cum_size % chips[cs].ahb_window_size; + if (chips[cs].ahb_window_size != 0 && rem_size != 0) + chips[0].ahb_window_size += + chips[cs].ahb_window_size - rem_size; + } + + ret = aspeed_spi_trim_window_size(aspi); + if (ret != 0) + return ret; + + /* The total window size of AST2500 SPI1 CS0 and CS1 must be 128MB */ + if (aspi->data == &ast2500_spi_data) + chips[1].ahb_window_size = + 0x08000000 - chips[0].ahb_window_size; + + return 0; +} + +static int aspeed_adjust_window_ast2600(struct aspeed_spi *aspi) +{ + int ret; + int cs, i; + u32 cum_size, rem_size; + struct aspeed_spi_chip *chips = aspi->chips; + + /* Close unused CS. */ + for (cs = aspi->num_cs; cs < aspi->data->max_cs; cs++) + chips[cs].ahb_window_size = 0; + + /* + * If command mode or normal mode is used by dirmap read, the start + * address of a window should be multiple of its related flash size. + * Namely, the total windows size from flash 0 to flash N should + * be multiple of the size of flash (N + 1). + */ + for (cs = aspi->num_cs - 1; cs >= 0; cs--) { + cum_size = 0; + for (i = 0; i < cs; i++) + cum_size += chips[i].ahb_window_size; + + rem_size = cum_size % chips[cs].ahb_window_size; + if (chips[cs].ahb_window_size != 0 && rem_size != 0) + chips[0].ahb_window_size += + chips[cs].ahb_window_size - rem_size; + } + + ret = aspeed_spi_trim_window_size(aspi); + if (ret != 0) + return ret; + + return 0; +} + /* * Yet to be done when possible : * - Align mappings on flash size (we don't have the info) @@ -475,48 +634,18 @@ static int aspeed_spi_chip_adjust_window(struct aspeed_spi_chip *chip, u32 local_offset, u32 size) { struct aspeed_spi *aspi = chip->aspi; - u32 cs; - u32 total_window_size; int ret; /* No segment registers for the AST2400 SPI controller */ if (aspi->data == &ast2400_spi_data) return 0; - /* - * Due to an HW issue on the AST2500 SPI controller, the CE0 - * window size should be smaller than the maximum 128MB. - */ - if (aspi->data == &ast2500_spi_data && chip->cs == 0 && size == SZ_128M) { - size = 120 << 20; - dev_info(aspi->dev, "CE%d window resized to %dMB (AST2500 HW quirk)", - chip->cs, size >> 20); - } - - /* - * The decoding size of AST2600 SPI controller should set at - * least 2MB. - */ - if ((aspi->data == &ast2600_spi_data || aspi->data == &ast2600_fmc_data) && - size < SZ_2M) { - size = SZ_2M; - dev_info(aspi->dev, "CE%d window resized to %dMB (AST2600 Decoding)", - chip->cs, size >> 20); - } - /* Adjust this chip window */ aspi->chips[chip->cs].ahb_window_size = size; - total_window_size = 0; - for (cs = 0; cs < aspi->data->max_cs; cs++) - total_window_size += aspi->chips[cs].ahb_window_size; - - if (total_window_size > aspi->ahb_window_size) { - aspi->chips[chip->cs].ahb_window_size -= (total_window_size - - aspi->ahb_window_size); - dev_warn(aspi->dev, "CE%d window resized to %zdMB", - chip->cs, aspi->chips[chip->cs].ahb_window_size >> 20); - } + /* Adjust the overall windows size regarding each platform */ + if (aspi->data->adjust_window) + aspi->data->adjust_window(aspi); ret = aspeed_spi_set_window(aspi); if (ret) @@ -600,7 +729,7 @@ static ssize_t aspeed_spi_dirmap_read(struct spi_mem_dirmap_desc *desc, struct aspeed_spi_chip *chip = &aspi->chips[spi_get_chipselect(desc->mem->spi, 0)]; /* Switch to USER command mode if mapping window is too small */ - if (chip->ahb_window_size < offset + len) { + if (chip->ahb_window_size < offset + len || chip->force_user_mode) { int ret; ret = aspeed_spi_read_user(chip, &desc->info.op_tmpl, offset, len, buf); @@ -1265,6 +1394,7 @@ static const struct aspeed_spi_data ast2400_fmc_data = { .segment_start = aspeed_spi_segment_start, .segment_end = aspeed_spi_segment_end, .segment_reg = aspeed_spi_segment_reg, + .adjust_window = aspeed_adjust_window_ast2400, }; static const struct aspeed_spi_data ast2400_spi_data = { @@ -1294,6 +1424,7 @@ static const struct aspeed_spi_data ast2500_fmc_data = { .segment_start = aspeed_spi_segment_start, .segment_end = aspeed_spi_segment_end, .segment_reg = aspeed_spi_segment_reg, + .adjust_window = aspeed_adjust_window_ast2500, }; static const struct aspeed_spi_data ast2500_spi_data = { @@ -1310,6 +1441,7 @@ static const struct aspeed_spi_data ast2500_spi_data = { .segment_start = aspeed_spi_segment_start, .segment_end = aspeed_spi_segment_end, .segment_reg = aspeed_spi_segment_reg, + .adjust_window = aspeed_adjust_window_ast2500, }; static const struct aspeed_spi_data ast2600_fmc_data = { @@ -1327,6 +1459,7 @@ static const struct aspeed_spi_data ast2600_fmc_data = { .segment_start = aspeed_spi_segment_ast2600_start, .segment_end = aspeed_spi_segment_ast2600_end, .segment_reg = aspeed_spi_segment_ast2600_reg, + .adjust_window = aspeed_adjust_window_ast2600, }; static const struct aspeed_spi_data ast2600_spi_data = { @@ -1344,6 +1477,7 @@ static const struct aspeed_spi_data ast2600_spi_data = { .segment_start = aspeed_spi_segment_ast2600_start, .segment_end = aspeed_spi_segment_ast2600_end, .segment_reg = aspeed_spi_segment_ast2600_reg, + .adjust_window = aspeed_adjust_window_ast2600, }; static const struct of_device_id aspeed_spi_matches[] = { From 64d87ccfae3326a9561fe41dc6073064a083e0df Mon Sep 17 00:00:00 2001 From: Chin-Ting Kuo Date: Wed, 1 Oct 2025 19:26:05 +0800 Subject: [PATCH 08/80] spi: aspeed: Only map necessary address window region Previously, the driver mapped the entire SPI address decoding region during probe. On systems with small flash or limited memory, this could lead to excessive memory usage or allocation failures. This patch changes the strategy to initially map a small address window for SPI flash device probing. After determining each chip select's flash size, the driver unmaps the temporary region and remaps only the required address window accordingly. Signed-off-by: Chin-Ting Kuo Link: https://patch.msgid.link/20251001112605.1130723-7-chin-ting_kuo@aspeedtech.com Signed-off-by: Mark Brown --- drivers/spi/spi-aspeed-smc.c | 39 +++++++++++++++++++++++++++++------- 1 file changed, 32 insertions(+), 7 deletions(-) diff --git a/drivers/spi/spi-aspeed-smc.c b/drivers/spi/spi-aspeed-smc.c index 4f6ae48dd904..0c3de371fd39 100644 --- a/drivers/spi/spi-aspeed-smc.c +++ b/drivers/spi/spi-aspeed-smc.c @@ -7,6 +7,7 @@ */ #include +#include #include #include #include @@ -96,7 +97,6 @@ struct aspeed_spi { const struct aspeed_spi_data *data; void __iomem *regs; - void __iomem *ahb_base; u32 ahb_base_phy; u32 ahb_window_size; u32 num_cs; @@ -394,6 +394,13 @@ static int aspeed_spi_set_window(struct aspeed_spi *aspi) u32 cs; size_t window_size; + for (cs = 0; cs < aspi->data->max_cs; cs++) { + if (aspi->chips[cs].ahb_base) { + iounmap(aspi->chips[cs].ahb_base); + aspi->chips[cs].ahb_base = NULL; + } + } + for (cs = 0; cs < aspi->data->max_cs; cs++) { seg_reg = seg_reg_base + cs * 4; seg_val_backup = readl(seg_reg); @@ -425,13 +432,29 @@ static int aspeed_spi_set_window(struct aspeed_spi *aspi) else dev_dbg(dev, "CE%d window closed\n", cs); - aspi->chips[cs].ahb_base = aspi->ahb_base + offset; offset += window_size; if (offset > aspi->ahb_window_size) { dev_err(dev, "CE%d offset value 0x%llx is too large.\n", cs, (u64)offset); return -ENOSPC; } + + /* + * No need to map the address deocding range when + * - window size is 0. + * - the CS is unused. + */ + if (window_size == 0 || cs >= aspi->num_cs) + continue; + + aspi->chips[cs].ahb_base = + devm_ioremap(aspi->dev, start, window_size); + if (!aspi->chips[cs].ahb_base) { + dev_err(aspi->dev, + "Fail to remap window [0x%.9llx - 0x%.9llx]\n", + (u64)start, (u64)end - 1); + return -ENOMEM; + } } return 0; @@ -447,7 +470,9 @@ static int aspeed_spi_chip_set_default_window(struct aspeed_spi *aspi) /* No segment registers for the AST2400 SPI controller */ if (aspi->data == &ast2400_spi_data) { - aspi->chips[0].ahb_base = aspi->ahb_base; + aspi->chips[0].ahb_base = devm_ioremap(aspi->dev, + aspi->ahb_base_phy, + aspi->ahb_window_size); aspi->chips[0].ahb_window_size = aspi->ahb_window_size; return 0; } @@ -839,10 +864,10 @@ static int aspeed_spi_probe(struct platform_device *pdev) if (IS_ERR(aspi->regs)) return PTR_ERR(aspi->regs); - aspi->ahb_base = devm_platform_get_and_ioremap_resource(pdev, 1, &res); - if (IS_ERR(aspi->ahb_base)) { - dev_err(dev, "missing AHB mapping window\n"); - return PTR_ERR(aspi->ahb_base); + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + if (IS_ERR(res)) { + dev_err(dev, "missing AHB memory\n"); + return PTR_ERR(res); } aspi->ahb_window_size = resource_size(res); From b83fb1b14c06bdd765903ac852ba20a14e24f227 Mon Sep 17 00:00:00 2001 From: Axel Haslam Date: Mon, 6 Oct 2025 11:25:41 -0300 Subject: [PATCH 09/80] spi: offload: Add offset parameter Add an offset parameter that can be passed in the periodic trigger. This is useful for example when ADC drivers implement a separate periodic signal to trigger conversion and need offload to read the result with some delay. While at it, add some documentation to offload periodic trigger parameters. Reviewed-by: David Lechner Signed-off-by: Axel Haslam Signed-off-by: Marcelo Schmitt Link: https://patch.msgid.link/cd315e95c0bd8523f00e91c400abcd6a418e5924.1759760519.git.marcelo.schmitt@analog.com Signed-off-by: Mark Brown --- drivers/spi/spi-offload-trigger-pwm.c | 3 +++ include/linux/spi/offload/types.h | 9 +++++++++ 2 files changed, 12 insertions(+) diff --git a/drivers/spi/spi-offload-trigger-pwm.c b/drivers/spi/spi-offload-trigger-pwm.c index 805ed41560df..3e8c19227edb 100644 --- a/drivers/spi/spi-offload-trigger-pwm.c +++ b/drivers/spi/spi-offload-trigger-pwm.c @@ -51,12 +51,14 @@ static int spi_offload_trigger_pwm_validate(struct spi_offload_trigger *trigger, wf.period_length_ns = DIV_ROUND_UP_ULL(NSEC_PER_SEC, periodic->frequency_hz); /* REVISIT: 50% duty-cycle for now - may add config parameter later */ wf.duty_length_ns = wf.period_length_ns / 2; + wf.duty_offset_ns = periodic->offset_ns; ret = pwm_round_waveform_might_sleep(st->pwm, &wf); if (ret < 0) return ret; periodic->frequency_hz = DIV_ROUND_UP_ULL(NSEC_PER_SEC, wf.period_length_ns); + periodic->offset_ns = wf.duty_offset_ns; return 0; } @@ -77,6 +79,7 @@ static int spi_offload_trigger_pwm_enable(struct spi_offload_trigger *trigger, wf.period_length_ns = DIV_ROUND_UP_ULL(NSEC_PER_SEC, periodic->frequency_hz); /* REVISIT: 50% duty-cycle for now - may add config parameter later */ wf.duty_length_ns = wf.period_length_ns / 2; + wf.duty_offset_ns = periodic->offset_ns; return pwm_set_waveform_might_sleep(st->pwm, &wf, false); } diff --git a/include/linux/spi/offload/types.h b/include/linux/spi/offload/types.h index 6f7892347871..cd61f8adb7a5 100644 --- a/include/linux/spi/offload/types.h +++ b/include/linux/spi/offload/types.h @@ -57,8 +57,17 @@ enum spi_offload_trigger_type { SPI_OFFLOAD_TRIGGER_PERIODIC, }; +/** + * spi_offload_trigger_periodic - configuration parameters for periodic triggers + * @frequency_hz: The rate that the trigger should fire in Hz. + * @offset_ns: A delay in nanoseconds between when this trigger fires + * compared to another trigger. This requires specialized hardware + * that supports such synchronization with a delay between two or + * more triggers. Set to 0 when not needed. + */ struct spi_offload_trigger_periodic { u64 frequency_hz; + u64 offset_ns; }; struct spi_offload_trigger_config { From 454cd43a283f7697297c52981c7a499a16725656 Mon Sep 17 00:00:00 2001 From: Md Sadre Alam Date: Wed, 8 Oct 2025 14:34:05 +0530 Subject: [PATCH 10/80] spi: dt-bindings: spi-qpic-snand: Add IPQ5424 compatible IPQ5424 contains the QPIC-SPI-NAND flash controller which is the same as the one found in IPQ9574. So let's document the IPQ5424 compatible and use IPQ9574 as the fallback. Acked-by: Rob Herring (Arm) Signed-off-by: Md Sadre Alam Link: https://patch.msgid.link/20251008090413.458791-2-quic_mdalam@quicinc.com Signed-off-by: Mark Brown --- Documentation/devicetree/bindings/spi/qcom,spi-qpic-snand.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/spi/qcom,spi-qpic-snand.yaml b/Documentation/devicetree/bindings/spi/qcom,spi-qpic-snand.yaml index cb1f15224b45..39e086ced891 100644 --- a/Documentation/devicetree/bindings/spi/qcom,spi-qpic-snand.yaml +++ b/Documentation/devicetree/bindings/spi/qcom,spi-qpic-snand.yaml @@ -25,6 +25,7 @@ properties: - items: - enum: - qcom,ipq5018-snand + - qcom,ipq5424-snand - const: qcom,ipq9574-snand - const: qcom,ipq9574-snand From 4412ab501677606436e5c49e41151a1e6eac7ac0 Mon Sep 17 00:00:00 2001 From: Md Sadre Alam Date: Wed, 8 Oct 2025 14:34:06 +0530 Subject: [PATCH 11/80] spi: dt-bindings: spi-qpic-snand: Add IPQ5332 compatible IPQ5332 contains the QPIC-SPI-NAND flash controller which is the same as the one found in IPQ9574. So let's document the IPQ5332 compatible and use IPQ9574 as the fallback. Acked-by: Rob Herring (Arm) Signed-off-by: Md Sadre Alam Link: https://patch.msgid.link/20251008090413.458791-3-quic_mdalam@quicinc.com Signed-off-by: Mark Brown --- Documentation/devicetree/bindings/spi/qcom,spi-qpic-snand.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/spi/qcom,spi-qpic-snand.yaml b/Documentation/devicetree/bindings/spi/qcom,spi-qpic-snand.yaml index 39e086ced891..7d0571feb46d 100644 --- a/Documentation/devicetree/bindings/spi/qcom,spi-qpic-snand.yaml +++ b/Documentation/devicetree/bindings/spi/qcom,spi-qpic-snand.yaml @@ -25,6 +25,7 @@ properties: - items: - enum: - qcom,ipq5018-snand + - qcom,ipq5332-snand - qcom,ipq5424-snand - const: qcom,ipq9574-snand - const: qcom,ipq9574-snand From d77daa49085b067137d0adbe3263f75a7ee13a1b Mon Sep 17 00:00:00 2001 From: Colin Ian King Date: Thu, 16 Oct 2025 16:30:00 +0100 Subject: [PATCH 12/80] spi: aspeed: fix spelling mistake "triming" -> "trimming" There is a spelling mistake in a dev_warn message. Fix it. Signed-off-by: Colin Ian King Link: https://patch.msgid.link/20251016153000.9142-1-coking@nvidia.com Signed-off-by: Mark Brown --- drivers/spi/spi-aspeed-smc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/spi/spi-aspeed-smc.c b/drivers/spi/spi-aspeed-smc.c index 0c3de371fd39..f3a7189afd51 100644 --- a/drivers/spi/spi-aspeed-smc.c +++ b/drivers/spi/spi-aspeed-smc.c @@ -539,7 +539,7 @@ static int aspeed_spi_trim_window_size(struct aspeed_spi *aspi) } while (total_sz > aspi->ahb_window_size); if (trimmed) { - dev_warn(aspi->dev, "Window size after triming:\n"); + dev_warn(aspi->dev, "Window size after trimming:\n"); for (cs = 0; cs < aspi->data->max_cs; cs++) { dev_warn(aspi->dev, "CE%d: 0x%08x\n", cs, chips[cs].ahb_window_size); From 661856ca131c8bf6724905966e02149805660abe Mon Sep 17 00:00:00 2001 From: Mikhail Kshevetskiy Date: Sun, 12 Oct 2025 15:16:53 +0300 Subject: [PATCH 13/80] spi: airoha: remove unnecessary restriction length The "length < 160" restriction is not needed because airoha_snand_write_data() and airoha_snand_read_data() will properly handle data transfers above SPI_MAX_TRANSFER_SIZE. Signed-off-by: Mikhail Kshevetskiy Reviewed-by: AngeloGioacchino Del Regno Link: https://patch.msgid.link/20251012121707.2296160-3-mikhail.kshevetskiy@iopsys.eu Signed-off-by: Mark Brown --- drivers/spi/spi-airoha-snfi.c | 7 ------- 1 file changed, 7 deletions(-) diff --git a/drivers/spi/spi-airoha-snfi.c b/drivers/spi/spi-airoha-snfi.c index b78163eaed61..3d3233c525df 100644 --- a/drivers/spi/spi-airoha-snfi.c +++ b/drivers/spi/spi-airoha-snfi.c @@ -619,13 +619,6 @@ static int airoha_snand_adjust_op_size(struct spi_mem *mem, if (op->data.nbytes > max_len) op->data.nbytes = max_len; - } else { - max_len = 1 + op->addr.nbytes + op->dummy.nbytes; - if (max_len >= 160) - return -EOPNOTSUPP; - - if (op->data.nbytes > 160 - max_len) - op->data.nbytes = 160 - max_len; } return 0; From 7350f8dc15bfbb7abf1ce4babea6fcace1c574c5 Mon Sep 17 00:00:00 2001 From: Mikhail Kshevetskiy Date: Sun, 12 Oct 2025 15:16:55 +0300 Subject: [PATCH 14/80] spi: airoha: remove unnecessary switch to non-dma mode The code switches to dma at the start of dirmap operation and returns to non-dma at the end of dirmap operation, so an additional switch to non-dma at the start of dirmap write is not required. Signed-off-by: Mikhail Kshevetskiy Acked-by: Lorenzo Bianconi Reviewed-by: AngeloGioacchino Del Regno Link: https://patch.msgid.link/20251012121707.2296160-5-mikhail.kshevetskiy@iopsys.eu Signed-off-by: Mark Brown --- drivers/spi/spi-airoha-snfi.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/drivers/spi/spi-airoha-snfi.c b/drivers/spi/spi-airoha-snfi.c index 3d3233c525df..5ad3180ac6da 100644 --- a/drivers/spi/spi-airoha-snfi.c +++ b/drivers/spi/spi-airoha-snfi.c @@ -815,9 +815,6 @@ static ssize_t airoha_snand_dirmap_write(struct spi_mem_dirmap_desc *desc, int err; as_ctrl = spi_controller_get_devdata(spi->controller); - err = airoha_snand_set_mode(as_ctrl, SPI_MODE_MANUAL); - if (err < 0) - return err; memcpy(txrx_buf + offs, buf, len); dma_addr = dma_map_single(as_ctrl->dev, txrx_buf, SPI_NAND_CACHE_SIZE, From 233a22687411ea053a4b169c07324ee6aa33bf38 Mon Sep 17 00:00:00 2001 From: Mikhail Kshevetskiy Date: Sun, 12 Oct 2025 15:16:58 +0300 Subject: [PATCH 15/80] spi: airoha: unify dirmap read/write code Makes dirmap writing looks similar to dirmap reading. Just a minor refactoring, no behavior change is expected. Signed-off-by: Mikhail Kshevetskiy Link: https://patch.msgid.link/20251012121707.2296160-8-mikhail.kshevetskiy@iopsys.eu Signed-off-by: Mark Brown --- drivers/spi/spi-airoha-snfi.c | 56 ++++++++++++++++++++++------------- 1 file changed, 35 insertions(+), 21 deletions(-) diff --git a/drivers/spi/spi-airoha-snfi.c b/drivers/spi/spi-airoha-snfi.c index 5ad3180ac6da..e90fab69d81e 100644 --- a/drivers/spi/spi-airoha-snfi.c +++ b/drivers/spi/spi-airoha-snfi.c @@ -672,6 +672,8 @@ static ssize_t airoha_snand_dirmap_read(struct spi_mem_dirmap_desc *desc, u32 val, rd_mode; int err; + as_ctrl = spi_controller_get_devdata(spi->controller); + switch (op->cmd.opcode) { case SPI_NAND_OP_READ_FROM_CACHE_DUAL: rd_mode = 1; @@ -684,7 +686,6 @@ static ssize_t airoha_snand_dirmap_read(struct spi_mem_dirmap_desc *desc, break; } - as_ctrl = spi_controller_get_devdata(spi->controller); err = airoha_snand_set_mode(as_ctrl, SPI_MODE_DMA); if (err < 0) return err; @@ -748,7 +749,7 @@ static ssize_t airoha_snand_dirmap_read(struct spi_mem_dirmap_desc *desc, if (err) goto error_dma_unmap; - /* trigger dma start read */ + /* trigger dma reading */ err = regmap_clear_bits(as_ctrl->regmap_nfi, REG_SPI_NFI_CON, SPI_NFI_RD_TRIG); if (err) @@ -806,37 +807,47 @@ static ssize_t airoha_snand_dirmap_read(struct spi_mem_dirmap_desc *desc, static ssize_t airoha_snand_dirmap_write(struct spi_mem_dirmap_desc *desc, u64 offs, size_t len, const void *buf) { - struct spi_mem_op *op = &desc->info.op_tmpl; struct spi_device *spi = desc->mem->spi; u8 *txrx_buf = spi_get_ctldata(spi); struct airoha_snand_ctrl *as_ctrl; dma_addr_t dma_addr; - u32 wr_mode, val; + u32 wr_mode, val, opcode; int err; as_ctrl = spi_controller_get_devdata(spi->controller); + opcode = desc->info.op_tmpl.cmd.opcode; + switch (opcode) { + case SPI_NAND_OP_PROGRAM_LOAD_SINGLE: + case SPI_NAND_OP_PROGRAM_LOAD_RAMDOM_SINGLE: + wr_mode = 0; + break; + case SPI_NAND_OP_PROGRAM_LOAD_QUAD: + case SPI_NAND_OP_PROGRAM_LOAD_RAMDON_QUAD: + wr_mode = 2; + break; + default: + /* unknown opcode */ + return -EOPNOTSUPP; + } + memcpy(txrx_buf + offs, buf, len); + + err = airoha_snand_set_mode(as_ctrl, SPI_MODE_DMA); + if (err < 0) + return err; + + err = airoha_snand_nfi_config(as_ctrl); + if (err) + goto error_dma_mode_off; + dma_addr = dma_map_single(as_ctrl->dev, txrx_buf, SPI_NAND_CACHE_SIZE, DMA_TO_DEVICE); err = dma_mapping_error(as_ctrl->dev, dma_addr); if (err) - return err; - - err = airoha_snand_set_mode(as_ctrl, SPI_MODE_DMA); - if (err < 0) - goto error_dma_unmap; - - err = airoha_snand_nfi_config(as_ctrl); - if (err) - goto error_dma_unmap; - - if (op->cmd.opcode == SPI_NAND_OP_PROGRAM_LOAD_QUAD || - op->cmd.opcode == SPI_NAND_OP_PROGRAM_LOAD_RAMDON_QUAD) - wr_mode = BIT(1); - else - wr_mode = 0; + goto error_dma_mode_off; + /* set dma addr */ err = regmap_write(as_ctrl->regmap_nfi, REG_SPI_NFI_STRADDR, dma_addr); if (err) @@ -850,12 +861,13 @@ static ssize_t airoha_snand_dirmap_write(struct spi_mem_dirmap_desc *desc, if (err) goto error_dma_unmap; + /* set write command */ err = regmap_write(as_ctrl->regmap_nfi, REG_SPI_NFI_PG_CTL1, - FIELD_PREP(SPI_NFI_PG_LOAD_CMD, - op->cmd.opcode)); + FIELD_PREP(SPI_NFI_PG_LOAD_CMD, opcode)); if (err) goto error_dma_unmap; + /* set write mode */ err = regmap_write(as_ctrl->regmap_nfi, REG_SPI_NFI_SNF_MISC_CTL, FIELD_PREP(SPI_NFI_DATA_READ_WR_MODE, wr_mode)); if (err) @@ -887,6 +899,7 @@ static ssize_t airoha_snand_dirmap_write(struct spi_mem_dirmap_desc *desc, if (err) goto error_dma_unmap; + /* trigger dma writing */ err = regmap_clear_bits(as_ctrl->regmap_nfi, REG_SPI_NFI_CON, SPI_NFI_WR_TRIG); if (err) @@ -931,6 +944,7 @@ static ssize_t airoha_snand_dirmap_write(struct spi_mem_dirmap_desc *desc, error_dma_unmap: dma_unmap_single(as_ctrl->dev, dma_addr, SPI_NAND_CACHE_SIZE, DMA_TO_DEVICE); +error_dma_mode_off: airoha_snand_set_mode(as_ctrl, SPI_MODE_MANUAL); return err; } From 80b09137aeab27e59004383058f8cc696a9ee048 Mon Sep 17 00:00:00 2001 From: Mikhail Kshevetskiy Date: Sun, 12 Oct 2025 15:16:59 +0300 Subject: [PATCH 16/80] spi: airoha: support of dualio/quadio flash reading commands Airoha snfi spi controller supports acceleration of DUAL/QUAD operations, but does not supports DUAL_IO/QUAD_IO operations. Luckily DUAL/QUAD operations do the same as DUAL_IO/QUAD_IO ones, so we can issue corresponding DUAL/QUAD operation instead of DUAL_IO/QUAD_IO one. Signed-off-by: Mikhail Kshevetskiy Reviewed-by: AngeloGioacchino Del Regno Link: https://patch.msgid.link/20251012121707.2296160-9-mikhail.kshevetskiy@iopsys.eu Signed-off-by: Mark Brown --- drivers/spi/spi-airoha-snfi.c | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/drivers/spi/spi-airoha-snfi.c b/drivers/spi/spi-airoha-snfi.c index e90fab69d81e..744eeb2b24ef 100644 --- a/drivers/spi/spi-airoha-snfi.c +++ b/drivers/spi/spi-airoha-snfi.c @@ -147,6 +147,8 @@ #define SPI_NFI_CUS_SEC_SIZE_EN BIT(16) #define REG_SPI_NFI_RD_CTL2 0x0510 +#define SPI_NFI_DATA_READ_CMD GENMASK(7, 0) + #define REG_SPI_NFI_RD_CTL3 0x0514 #define REG_SPI_NFI_PG_CTL1 0x0524 @@ -179,7 +181,9 @@ #define SPI_NAND_OP_READ_FROM_CACHE_SINGLE 0x03 #define SPI_NAND_OP_READ_FROM_CACHE_SINGLE_FAST 0x0b #define SPI_NAND_OP_READ_FROM_CACHE_DUAL 0x3b +#define SPI_NAND_OP_READ_FROM_CACHE_DUALIO 0xbb #define SPI_NAND_OP_READ_FROM_CACHE_QUAD 0x6b +#define SPI_NAND_OP_READ_FROM_CACHE_QUADIO 0xeb #define SPI_NAND_OP_WRITE_ENABLE 0x06 #define SPI_NAND_OP_WRITE_DISABLE 0x04 #define SPI_NAND_OP_PROGRAM_LOAD_SINGLE 0x02 @@ -664,26 +668,38 @@ static int airoha_snand_dirmap_create(struct spi_mem_dirmap_desc *desc) static ssize_t airoha_snand_dirmap_read(struct spi_mem_dirmap_desc *desc, u64 offs, size_t len, void *buf) { - struct spi_mem_op *op = &desc->info.op_tmpl; struct spi_device *spi = desc->mem->spi; struct airoha_snand_ctrl *as_ctrl; u8 *txrx_buf = spi_get_ctldata(spi); dma_addr_t dma_addr; - u32 val, rd_mode; + u32 val, rd_mode, opcode; int err; as_ctrl = spi_controller_get_devdata(spi->controller); - switch (op->cmd.opcode) { + /* + * DUALIO and QUADIO opcodes are not supported by the spi controller, + * replace them with supported opcodes. + */ + opcode = desc->info.op_tmpl.cmd.opcode; + switch (opcode) { + case SPI_NAND_OP_READ_FROM_CACHE_SINGLE: + case SPI_NAND_OP_READ_FROM_CACHE_SINGLE_FAST: + rd_mode = 0; + break; case SPI_NAND_OP_READ_FROM_CACHE_DUAL: + case SPI_NAND_OP_READ_FROM_CACHE_DUALIO: + opcode = SPI_NAND_OP_READ_FROM_CACHE_DUAL; rd_mode = 1; break; case SPI_NAND_OP_READ_FROM_CACHE_QUAD: + case SPI_NAND_OP_READ_FROM_CACHE_QUADIO: + opcode = SPI_NAND_OP_READ_FROM_CACHE_QUAD; rd_mode = 2; break; default: - rd_mode = 0; - break; + /* unknown opcode */ + return -EOPNOTSUPP; } err = airoha_snand_set_mode(as_ctrl, SPI_MODE_DMA); @@ -717,7 +733,7 @@ static ssize_t airoha_snand_dirmap_read(struct spi_mem_dirmap_desc *desc, /* set read command */ err = regmap_write(as_ctrl->regmap_nfi, REG_SPI_NFI_RD_CTL2, - op->cmd.opcode); + FIELD_PREP(SPI_NFI_DATA_READ_CMD, opcode)); if (err) goto error_dma_unmap; From 70eec454f2d6cdfab547c262781acd38328e11a1 Mon Sep 17 00:00:00 2001 From: Mikhail Kshevetskiy Date: Sun, 12 Oct 2025 15:17:00 +0300 Subject: [PATCH 17/80] spi: airoha: avoid setting of page/oob sizes in REG_SPI_NFI_PAGEFMT spi-airoha-snfi uses custom sector size in REG_SPI_NFI_SECCUS_SIZE register, so setting of page/oob sizes in REG_SPI_NFI_PAGEFMT is not required. Signed-off-by: Mikhail Kshevetskiy Link: https://patch.msgid.link/20251012121707.2296160-10-mikhail.kshevetskiy@iopsys.eu Signed-off-by: Mark Brown --- drivers/spi/spi-airoha-snfi.c | 38 ----------------------------------- 1 file changed, 38 deletions(-) diff --git a/drivers/spi/spi-airoha-snfi.c b/drivers/spi/spi-airoha-snfi.c index 744eeb2b24ef..719b424d4cce 100644 --- a/drivers/spi/spi-airoha-snfi.c +++ b/drivers/spi/spi-airoha-snfi.c @@ -518,44 +518,6 @@ static int airoha_snand_nfi_config(struct airoha_snand_ctrl *as_ctrl) if (err) return err; - /* page format */ - switch (as_ctrl->nfi_cfg.spare_size) { - case 26: - val = FIELD_PREP(SPI_NFI_SPARE_SIZE, 0x1); - break; - case 27: - val = FIELD_PREP(SPI_NFI_SPARE_SIZE, 0x2); - break; - case 28: - val = FIELD_PREP(SPI_NFI_SPARE_SIZE, 0x3); - break; - default: - val = FIELD_PREP(SPI_NFI_SPARE_SIZE, 0x0); - break; - } - - err = regmap_update_bits(as_ctrl->regmap_nfi, REG_SPI_NFI_PAGEFMT, - SPI_NFI_SPARE_SIZE, val); - if (err) - return err; - - switch (as_ctrl->nfi_cfg.page_size) { - case 2048: - val = FIELD_PREP(SPI_NFI_PAGE_SIZE, 0x1); - break; - case 4096: - val = FIELD_PREP(SPI_NFI_PAGE_SIZE, 0x2); - break; - default: - val = FIELD_PREP(SPI_NFI_PAGE_SIZE, 0x0); - break; - } - - err = regmap_update_bits(as_ctrl->regmap_nfi, REG_SPI_NFI_PAGEFMT, - SPI_NFI_PAGE_SIZE, val); - if (err) - return err; - /* sec num */ val = FIELD_PREP(SPI_NFI_SEC_NUM, as_ctrl->nfi_cfg.sec_num); err = regmap_update_bits(as_ctrl->regmap_nfi, REG_SPI_NFI_CON, From d1ff30df1d9a4eb4c067795abb5e2a66910fd108 Mon Sep 17 00:00:00 2001 From: Mikhail Kshevetskiy Date: Sun, 12 Oct 2025 15:17:01 +0300 Subject: [PATCH 18/80] spi: airoha: reduce the number of modification of REG_SPI_NFI_CNFG and REG_SPI_NFI_SECCUS_SIZE registers This just reduce the number of modification of REG_SPI_NFI_CNFG and REG_SPI_NFI_SECCUS_SIZE registers during dirmap operation. This patch is a necessary step to avoid reading flash page settings from SNFI registers during driver startup. Signed-off-by: Mikhail Kshevetskiy Reviewed-by: AngeloGioacchino Del Regno Link: https://patch.msgid.link/20251012121707.2296160-11-mikhail.kshevetskiy@iopsys.eu Signed-off-by: Mark Brown --- drivers/spi/spi-airoha-snfi.c | 135 +++++++++++++++++++++++++--------- 1 file changed, 102 insertions(+), 33 deletions(-) diff --git a/drivers/spi/spi-airoha-snfi.c b/drivers/spi/spi-airoha-snfi.c index 719b424d4cce..3fb76e2dd5e3 100644 --- a/drivers/spi/spi-airoha-snfi.c +++ b/drivers/spi/spi-airoha-snfi.c @@ -668,7 +668,48 @@ static ssize_t airoha_snand_dirmap_read(struct spi_mem_dirmap_desc *desc, if (err < 0) return err; - err = airoha_snand_nfi_config(as_ctrl); + /* NFI reset */ + err = regmap_write(as_ctrl->regmap_nfi, REG_SPI_NFI_CON, + SPI_NFI_FIFO_FLUSH | SPI_NFI_RST); + if (err) + goto error_dma_mode_off; + + /* NFI configure: + * - No AutoFDM (custom sector size (SECCUS) register will be used) + * - No SoC's hardware ECC (flash internal ECC will be used) + * - Use burst mode (faster, but requires 16 byte alignment for addresses) + * - Setup for reading (SPI_NFI_READ_MODE) + * - Setup reading command: FIELD_PREP(SPI_NFI_OPMODE, 6) + * - Use DMA instead of PIO for data reading + */ + err = regmap_update_bits(as_ctrl->regmap_nfi, REG_SPI_NFI_CNFG, + SPI_NFI_DMA_MODE | + SPI_NFI_READ_MODE | + SPI_NFI_DMA_BURST_EN | + SPI_NFI_HW_ECC_EN | + SPI_NFI_AUTO_FDM_EN | + SPI_NFI_OPMODE, + SPI_NFI_DMA_MODE | + SPI_NFI_READ_MODE | + SPI_NFI_DMA_BURST_EN | + FIELD_PREP(SPI_NFI_OPMODE, 6)); + if (err) + goto error_dma_mode_off; + + /* Set number of sector will be read */ + val = FIELD_PREP(SPI_NFI_SEC_NUM, as_ctrl->nfi_cfg.sec_num); + err = regmap_update_bits(as_ctrl->regmap_nfi, REG_SPI_NFI_CON, + SPI_NFI_SEC_NUM, val); + if (err) + goto error_dma_mode_off; + + /* Set custom sector size */ + val = as_ctrl->nfi_cfg.sec_size; + err = regmap_update_bits(as_ctrl->regmap_nfi, REG_SPI_NFI_SECCUS_SIZE, + SPI_NFI_CUS_SEC_SIZE | + SPI_NFI_CUS_SEC_SIZE_EN, + FIELD_PREP(SPI_NFI_CUS_SEC_SIZE, val) | + SPI_NFI_CUS_SEC_SIZE_EN); if (err) goto error_dma_mode_off; @@ -684,7 +725,14 @@ static ssize_t airoha_snand_dirmap_read(struct spi_mem_dirmap_desc *desc, if (err) goto error_dma_unmap; - /* set cust sec size */ + /* + * Setup transfer length + * --------------------- + * The following rule MUST be met: + * transfer_length = + * = NFI_SNF_MISC_CTL2.read_data_byte_number = + * = NFI_CON.sector_number * NFI_SECCUS.custom_sector_size + */ val = as_ctrl->nfi_cfg.sec_size * as_ctrl->nfi_cfg.sec_num; val = FIELD_PREP(SPI_NFI_READ_DATA_BYTE_NUM, val); err = regmap_update_bits(as_ctrl->regmap_nfi, @@ -711,18 +759,6 @@ static ssize_t airoha_snand_dirmap_read(struct spi_mem_dirmap_desc *desc, if (err) goto error_dma_unmap; - /* set nfi read */ - err = regmap_update_bits(as_ctrl->regmap_nfi, REG_SPI_NFI_CNFG, - SPI_NFI_OPMODE, - FIELD_PREP(SPI_NFI_OPMODE, 6)); - if (err) - goto error_dma_unmap; - - err = regmap_set_bits(as_ctrl->regmap_nfi, REG_SPI_NFI_CNFG, - SPI_NFI_READ_MODE | SPI_NFI_DMA_MODE); - if (err) - goto error_dma_unmap; - err = regmap_write(as_ctrl->regmap_nfi, REG_SPI_NFI_CMD, 0x0); if (err) goto error_dma_unmap; @@ -815,7 +851,48 @@ static ssize_t airoha_snand_dirmap_write(struct spi_mem_dirmap_desc *desc, if (err < 0) return err; - err = airoha_snand_nfi_config(as_ctrl); + /* NFI reset */ + err = regmap_write(as_ctrl->regmap_nfi, REG_SPI_NFI_CON, + SPI_NFI_FIFO_FLUSH | SPI_NFI_RST); + if (err) + goto error_dma_mode_off; + + /* + * NFI configure: + * - No AutoFDM (custom sector size (SECCUS) register will be used) + * - No SoC's hardware ECC (flash internal ECC will be used) + * - Use burst mode (faster, but requires 16 byte alignment for addresses) + * - Setup for writing (SPI_NFI_READ_MODE bit is cleared) + * - Setup writing command: FIELD_PREP(SPI_NFI_OPMODE, 3) + * - Use DMA instead of PIO for data writing + */ + err = regmap_update_bits(as_ctrl->regmap_nfi, REG_SPI_NFI_CNFG, + SPI_NFI_DMA_MODE | + SPI_NFI_READ_MODE | + SPI_NFI_DMA_BURST_EN | + SPI_NFI_HW_ECC_EN | + SPI_NFI_AUTO_FDM_EN | + SPI_NFI_OPMODE, + SPI_NFI_DMA_MODE | + SPI_NFI_DMA_BURST_EN | + FIELD_PREP(SPI_NFI_OPMODE, 3)); + if (err) + goto error_dma_mode_off; + + /* Set number of sector will be written */ + val = FIELD_PREP(SPI_NFI_SEC_NUM, as_ctrl->nfi_cfg.sec_num); + err = regmap_update_bits(as_ctrl->regmap_nfi, REG_SPI_NFI_CON, + SPI_NFI_SEC_NUM, val); + if (err) + goto error_dma_mode_off; + + /* Set custom sector size */ + val = as_ctrl->nfi_cfg.sec_size; + err = regmap_update_bits(as_ctrl->regmap_nfi, REG_SPI_NFI_SECCUS_SIZE, + SPI_NFI_CUS_SEC_SIZE | + SPI_NFI_CUS_SEC_SIZE_EN, + FIELD_PREP(SPI_NFI_CUS_SEC_SIZE, val) | + SPI_NFI_CUS_SEC_SIZE_EN); if (err) goto error_dma_mode_off; @@ -831,8 +908,16 @@ static ssize_t airoha_snand_dirmap_write(struct spi_mem_dirmap_desc *desc, if (err) goto error_dma_unmap; - val = FIELD_PREP(SPI_NFI_PROG_LOAD_BYTE_NUM, - as_ctrl->nfi_cfg.sec_size * as_ctrl->nfi_cfg.sec_num); + /* + * Setup transfer length + * --------------------- + * The following rule MUST be met: + * transfer_length = + * = NFI_SNF_MISC_CTL2.write_data_byte_number = + * = NFI_CON.sector_number * NFI_SECCUS.custom_sector_size + */ + val = as_ctrl->nfi_cfg.sec_size * as_ctrl->nfi_cfg.sec_num; + val = FIELD_PREP(SPI_NFI_PROG_LOAD_BYTE_NUM, val); err = regmap_update_bits(as_ctrl->regmap_nfi, REG_SPI_NFI_SNF_MISC_CTL2, SPI_NFI_PROG_LOAD_BYTE_NUM, val); @@ -857,22 +942,6 @@ static ssize_t airoha_snand_dirmap_write(struct spi_mem_dirmap_desc *desc, if (err) goto error_dma_unmap; - err = regmap_clear_bits(as_ctrl->regmap_nfi, REG_SPI_NFI_CNFG, - SPI_NFI_READ_MODE); - if (err) - goto error_dma_unmap; - - err = regmap_update_bits(as_ctrl->regmap_nfi, REG_SPI_NFI_CNFG, - SPI_NFI_OPMODE, - FIELD_PREP(SPI_NFI_OPMODE, 3)); - if (err) - goto error_dma_unmap; - - err = regmap_set_bits(as_ctrl->regmap_nfi, REG_SPI_NFI_CNFG, - SPI_NFI_DMA_MODE); - if (err) - goto error_dma_unmap; - err = regmap_write(as_ctrl->regmap_nfi, REG_SPI_NFI_CMD, 0x80); if (err) goto error_dma_unmap; From fb81b5cecb8553e3ca2b45288cf340d43c9c2991 Mon Sep 17 00:00:00 2001 From: Mikhail Kshevetskiy Date: Sun, 12 Oct 2025 15:17:02 +0300 Subject: [PATCH 19/80] spi: airoha: set custom sector size equal to flash page size Set custom sector size equal to flash page size including oob. Thus we will always read a single sector. The maximum custom sector size is 8187, so all possible flash sector sizes are supported. This patch is a necessary step to avoid reading flash page settings from SNFI registers during driver startup. Signed-off-by: Mikhail Kshevetskiy Reviewed-by: AngeloGioacchino Del Regno Link: https://patch.msgid.link/20251012121707.2296160-12-mikhail.kshevetskiy@iopsys.eu Signed-off-by: Mark Brown --- drivers/spi/spi-airoha-snfi.c | 35 +++++++++++++++++++---------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/drivers/spi/spi-airoha-snfi.c b/drivers/spi/spi-airoha-snfi.c index 3fb76e2dd5e3..babf7b958dc3 100644 --- a/drivers/spi/spi-airoha-snfi.c +++ b/drivers/spi/spi-airoha-snfi.c @@ -519,7 +519,7 @@ static int airoha_snand_nfi_config(struct airoha_snand_ctrl *as_ctrl) return err; /* sec num */ - val = FIELD_PREP(SPI_NFI_SEC_NUM, as_ctrl->nfi_cfg.sec_num); + val = FIELD_PREP(SPI_NFI_SEC_NUM, 1); err = regmap_update_bits(as_ctrl->regmap_nfi, REG_SPI_NFI_CON, SPI_NFI_SEC_NUM, val); if (err) @@ -532,7 +532,8 @@ static int airoha_snand_nfi_config(struct airoha_snand_ctrl *as_ctrl) return err; /* set cust sec size */ - val = FIELD_PREP(SPI_NFI_CUS_SEC_SIZE, as_ctrl->nfi_cfg.sec_size); + val = FIELD_PREP(SPI_NFI_CUS_SEC_SIZE, + as_ctrl->nfi_cfg.sec_size * as_ctrl->nfi_cfg.sec_num); return regmap_update_bits(as_ctrl->regmap_nfi, REG_SPI_NFI_SECCUS_SIZE, SPI_NFI_CUS_SEC_SIZE, val); @@ -635,10 +636,13 @@ static ssize_t airoha_snand_dirmap_read(struct spi_mem_dirmap_desc *desc, u8 *txrx_buf = spi_get_ctldata(spi); dma_addr_t dma_addr; u32 val, rd_mode, opcode; + size_t bytes; int err; as_ctrl = spi_controller_get_devdata(spi->controller); + bytes = as_ctrl->nfi_cfg.sec_num * as_ctrl->nfi_cfg.sec_size; + /* * DUALIO and QUADIO opcodes are not supported by the spi controller, * replace them with supported opcodes. @@ -697,18 +701,17 @@ static ssize_t airoha_snand_dirmap_read(struct spi_mem_dirmap_desc *desc, goto error_dma_mode_off; /* Set number of sector will be read */ - val = FIELD_PREP(SPI_NFI_SEC_NUM, as_ctrl->nfi_cfg.sec_num); err = regmap_update_bits(as_ctrl->regmap_nfi, REG_SPI_NFI_CON, - SPI_NFI_SEC_NUM, val); + SPI_NFI_SEC_NUM, + FIELD_PREP(SPI_NFI_SEC_NUM, 1)); if (err) goto error_dma_mode_off; /* Set custom sector size */ - val = as_ctrl->nfi_cfg.sec_size; err = regmap_update_bits(as_ctrl->regmap_nfi, REG_SPI_NFI_SECCUS_SIZE, SPI_NFI_CUS_SEC_SIZE | SPI_NFI_CUS_SEC_SIZE_EN, - FIELD_PREP(SPI_NFI_CUS_SEC_SIZE, val) | + FIELD_PREP(SPI_NFI_CUS_SEC_SIZE, bytes) | SPI_NFI_CUS_SEC_SIZE_EN); if (err) goto error_dma_mode_off; @@ -733,11 +736,10 @@ static ssize_t airoha_snand_dirmap_read(struct spi_mem_dirmap_desc *desc, * = NFI_SNF_MISC_CTL2.read_data_byte_number = * = NFI_CON.sector_number * NFI_SECCUS.custom_sector_size */ - val = as_ctrl->nfi_cfg.sec_size * as_ctrl->nfi_cfg.sec_num; - val = FIELD_PREP(SPI_NFI_READ_DATA_BYTE_NUM, val); err = regmap_update_bits(as_ctrl->regmap_nfi, REG_SPI_NFI_SNF_MISC_CTL2, - SPI_NFI_READ_DATA_BYTE_NUM, val); + SPI_NFI_READ_DATA_BYTE_NUM, + FIELD_PREP(SPI_NFI_READ_DATA_BYTE_NUM, bytes)); if (err) goto error_dma_unmap; @@ -826,10 +828,13 @@ static ssize_t airoha_snand_dirmap_write(struct spi_mem_dirmap_desc *desc, struct airoha_snand_ctrl *as_ctrl; dma_addr_t dma_addr; u32 wr_mode, val, opcode; + size_t bytes; int err; as_ctrl = spi_controller_get_devdata(spi->controller); + bytes = as_ctrl->nfi_cfg.sec_num * as_ctrl->nfi_cfg.sec_size; + opcode = desc->info.op_tmpl.cmd.opcode; switch (opcode) { case SPI_NAND_OP_PROGRAM_LOAD_SINGLE: @@ -880,18 +885,17 @@ static ssize_t airoha_snand_dirmap_write(struct spi_mem_dirmap_desc *desc, goto error_dma_mode_off; /* Set number of sector will be written */ - val = FIELD_PREP(SPI_NFI_SEC_NUM, as_ctrl->nfi_cfg.sec_num); err = regmap_update_bits(as_ctrl->regmap_nfi, REG_SPI_NFI_CON, - SPI_NFI_SEC_NUM, val); + SPI_NFI_SEC_NUM, + FIELD_PREP(SPI_NFI_SEC_NUM, 1)); if (err) goto error_dma_mode_off; /* Set custom sector size */ - val = as_ctrl->nfi_cfg.sec_size; err = regmap_update_bits(as_ctrl->regmap_nfi, REG_SPI_NFI_SECCUS_SIZE, SPI_NFI_CUS_SEC_SIZE | SPI_NFI_CUS_SEC_SIZE_EN, - FIELD_PREP(SPI_NFI_CUS_SEC_SIZE, val) | + FIELD_PREP(SPI_NFI_CUS_SEC_SIZE, bytes) | SPI_NFI_CUS_SEC_SIZE_EN); if (err) goto error_dma_mode_off; @@ -916,11 +920,10 @@ static ssize_t airoha_snand_dirmap_write(struct spi_mem_dirmap_desc *desc, * = NFI_SNF_MISC_CTL2.write_data_byte_number = * = NFI_CON.sector_number * NFI_SECCUS.custom_sector_size */ - val = as_ctrl->nfi_cfg.sec_size * as_ctrl->nfi_cfg.sec_num; - val = FIELD_PREP(SPI_NFI_PROG_LOAD_BYTE_NUM, val); err = regmap_update_bits(as_ctrl->regmap_nfi, REG_SPI_NFI_SNF_MISC_CTL2, - SPI_NFI_PROG_LOAD_BYTE_NUM, val); + SPI_NFI_PROG_LOAD_BYTE_NUM, + FIELD_PREP(SPI_NFI_PROG_LOAD_BYTE_NUM, bytes)); if (err) goto error_dma_unmap; From 902c0ea18a97b1a6eeee5799cb1fd9a79ef9208e Mon Sep 17 00:00:00 2001 From: Mikhail Kshevetskiy Date: Sun, 12 Oct 2025 15:17:03 +0300 Subject: [PATCH 20/80] spi: airoha: avoid reading flash page settings from SNFI registers during driver startup The spinand driver do 3 type of dirmap requests: * read/write whole flash page without oob (offs = 0, len = page_size) * read/write whole flash page including oob (offs = 0, len = page_size + oob_size) * read/write oob area only (offs = page_size, len = oob_size) The trick is: * read/write a single "sector" * set a custom sector size equal to offs + len. It's a bit safer to rounded up "sector size" value 64. * set the transfer length equal to custom sector size And it works! Thus we can remove a dirty hack that reads flash page settings from SNFI registers during driver startup. Also airoha_snand_adjust_op_size() function becomes unnecessary. Signed-off-by: Mikhail Kshevetskiy Link: https://patch.msgid.link/20251012121707.2296160-13-mikhail.kshevetskiy@iopsys.eu Signed-off-by: Mark Brown --- drivers/spi/spi-airoha-snfi.c | 115 ++-------------------------------- 1 file changed, 5 insertions(+), 110 deletions(-) diff --git a/drivers/spi/spi-airoha-snfi.c b/drivers/spi/spi-airoha-snfi.c index babf7b958dc3..437ab6745b1a 100644 --- a/drivers/spi/spi-airoha-snfi.c +++ b/drivers/spi/spi-airoha-snfi.c @@ -223,13 +223,6 @@ struct airoha_snand_ctrl { struct regmap *regmap_ctrl; struct regmap *regmap_nfi; struct clk *spi_clk; - - struct { - size_t page_size; - size_t sec_size; - u8 sec_num; - u8 spare_size; - } nfi_cfg; }; static int airoha_snand_set_fifo_op(struct airoha_snand_ctrl *as_ctrl, @@ -490,55 +483,6 @@ static int airoha_snand_nfi_init(struct airoha_snand_ctrl *as_ctrl) SPI_NFI_ALL_IRQ_EN, SPI_NFI_AHB_DONE_EN); } -static int airoha_snand_nfi_config(struct airoha_snand_ctrl *as_ctrl) -{ - int err; - u32 val; - - err = regmap_write(as_ctrl->regmap_nfi, REG_SPI_NFI_CON, - SPI_NFI_FIFO_FLUSH | SPI_NFI_RST); - if (err) - return err; - - /* auto FDM */ - err = regmap_clear_bits(as_ctrl->regmap_nfi, REG_SPI_NFI_CNFG, - SPI_NFI_AUTO_FDM_EN); - if (err) - return err; - - /* HW ECC */ - err = regmap_clear_bits(as_ctrl->regmap_nfi, REG_SPI_NFI_CNFG, - SPI_NFI_HW_ECC_EN); - if (err) - return err; - - /* DMA Burst */ - err = regmap_set_bits(as_ctrl->regmap_nfi, REG_SPI_NFI_CNFG, - SPI_NFI_DMA_BURST_EN); - if (err) - return err; - - /* sec num */ - val = FIELD_PREP(SPI_NFI_SEC_NUM, 1); - err = regmap_update_bits(as_ctrl->regmap_nfi, REG_SPI_NFI_CON, - SPI_NFI_SEC_NUM, val); - if (err) - return err; - - /* enable cust sec size */ - err = regmap_set_bits(as_ctrl->regmap_nfi, REG_SPI_NFI_SECCUS_SIZE, - SPI_NFI_CUS_SEC_SIZE_EN); - if (err) - return err; - - /* set cust sec size */ - val = FIELD_PREP(SPI_NFI_CUS_SEC_SIZE, - as_ctrl->nfi_cfg.sec_size * as_ctrl->nfi_cfg.sec_num); - return regmap_update_bits(as_ctrl->regmap_nfi, - REG_SPI_NFI_SECCUS_SIZE, - SPI_NFI_CUS_SEC_SIZE, val); -} - static bool airoha_snand_is_page_ops(const struct spi_mem_op *op) { if (op->addr.nbytes != 2) @@ -571,26 +515,6 @@ static bool airoha_snand_is_page_ops(const struct spi_mem_op *op) } } -static int airoha_snand_adjust_op_size(struct spi_mem *mem, - struct spi_mem_op *op) -{ - size_t max_len; - - if (airoha_snand_is_page_ops(op)) { - struct airoha_snand_ctrl *as_ctrl; - - as_ctrl = spi_controller_get_devdata(mem->spi->controller); - max_len = as_ctrl->nfi_cfg.sec_size; - max_len += as_ctrl->nfi_cfg.spare_size; - max_len *= as_ctrl->nfi_cfg.sec_num; - - if (op->data.nbytes > max_len) - op->data.nbytes = max_len; - } - - return 0; -} - static bool airoha_snand_supports_op(struct spi_mem *mem, const struct spi_mem_op *op) { @@ -641,7 +565,8 @@ static ssize_t airoha_snand_dirmap_read(struct spi_mem_dirmap_desc *desc, as_ctrl = spi_controller_get_devdata(spi->controller); - bytes = as_ctrl->nfi_cfg.sec_num * as_ctrl->nfi_cfg.sec_size; + /* minimum oob size is 64 */ + bytes = round_up(offs + len, 64); /* * DUALIO and QUADIO opcodes are not supported by the spi controller, @@ -833,7 +758,8 @@ static ssize_t airoha_snand_dirmap_write(struct spi_mem_dirmap_desc *desc, as_ctrl = spi_controller_get_devdata(spi->controller); - bytes = as_ctrl->nfi_cfg.sec_num * as_ctrl->nfi_cfg.sec_size; + /* minimum oob size is 64 */ + bytes = round_up(offs + len, 64); opcode = desc->info.op_tmpl.cmd.opcode; switch (opcode) { @@ -1076,7 +1002,6 @@ static int airoha_snand_exec_op(struct spi_mem *mem, } static const struct spi_controller_mem_ops airoha_snand_mem_ops = { - .adjust_op_size = airoha_snand_adjust_op_size, .supports_op = airoha_snand_supports_op, .exec_op = airoha_snand_exec_op, .dirmap_create = airoha_snand_dirmap_create, @@ -1101,36 +1026,6 @@ static int airoha_snand_setup(struct spi_device *spi) return 0; } -static int airoha_snand_nfi_setup(struct airoha_snand_ctrl *as_ctrl) -{ - u32 val, sec_size, sec_num; - int err; - - err = regmap_read(as_ctrl->regmap_nfi, REG_SPI_NFI_CON, &val); - if (err) - return err; - - sec_num = FIELD_GET(SPI_NFI_SEC_NUM, val); - - err = regmap_read(as_ctrl->regmap_nfi, REG_SPI_NFI_SECCUS_SIZE, &val); - if (err) - return err; - - sec_size = FIELD_GET(SPI_NFI_CUS_SEC_SIZE, val); - - /* init default value */ - as_ctrl->nfi_cfg.sec_size = sec_size; - as_ctrl->nfi_cfg.sec_num = sec_num; - as_ctrl->nfi_cfg.page_size = round_down(sec_size * sec_num, 1024); - as_ctrl->nfi_cfg.spare_size = 16; - - err = airoha_snand_nfi_init(as_ctrl); - if (err) - return err; - - return airoha_snand_nfi_config(as_ctrl); -} - static const struct regmap_config spi_ctrl_regmap_config = { .name = "ctrl", .reg_bits = 32, @@ -1204,7 +1099,7 @@ static int airoha_snand_probe(struct platform_device *pdev) ctrl->setup = airoha_snand_setup; device_set_node(&ctrl->dev, dev_fwnode(dev)); - err = airoha_snand_nfi_setup(as_ctrl); + err = airoha_snand_nfi_init(as_ctrl); if (err) return err; From 0743acf746a81e0460a56fd5ff847d97fa7eb370 Mon Sep 17 00:00:00 2001 From: Mikhail Kshevetskiy Date: Sun, 12 Oct 2025 15:17:04 +0300 Subject: [PATCH 21/80] spi: airoha: buffer must be 0xff-ed before writing During writing, the entire flash page (including OOB) will be updated with the values from the temporary buffer, so we need to fill the untouched areas of the buffer with 0xff value to prevent accidental data overwriting. Signed-off-by: Mikhail Kshevetskiy Reviewed-by: AngeloGioacchino Del Regno Link: https://patch.msgid.link/20251012121707.2296160-14-mikhail.kshevetskiy@iopsys.eu Signed-off-by: Mark Brown --- drivers/spi/spi-airoha-snfi.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/spi/spi-airoha-snfi.c b/drivers/spi/spi-airoha-snfi.c index 437ab6745b1a..8408aee9c06e 100644 --- a/drivers/spi/spi-airoha-snfi.c +++ b/drivers/spi/spi-airoha-snfi.c @@ -776,7 +776,11 @@ static ssize_t airoha_snand_dirmap_write(struct spi_mem_dirmap_desc *desc, return -EOPNOTSUPP; } + if (offs > 0) + memset(txrx_buf, 0xff, offs); memcpy(txrx_buf + offs, buf, len); + if (bytes > offs + len) + memset(txrx_buf + offs + len, 0xff, bytes - offs - len); err = airoha_snand_set_mode(as_ctrl, SPI_MODE_DMA); if (err < 0) From 0cc08c8130ac8f74419f99fe707dc193b7f79d86 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Fri, 17 Oct 2025 19:04:34 +0300 Subject: [PATCH 22/80] spi: aspeed: Fix an IS_ERR() vs NULL bug in probe() The platform_get_resource() function doesn't return error pointers, it returns NULL on error. Update the error checking to match. Fixes: 64d87ccfae33 ("spi: aspeed: Only map necessary address window region") Signed-off-by: Dan Carpenter Link: https://patch.msgid.link/aPJpEnfK31pHz8_w@stanley.mountain Signed-off-by: Mark Brown --- drivers/spi/spi-aspeed-smc.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/spi/spi-aspeed-smc.c b/drivers/spi/spi-aspeed-smc.c index f3a7189afd51..e8bd8fe6c4e7 100644 --- a/drivers/spi/spi-aspeed-smc.c +++ b/drivers/spi/spi-aspeed-smc.c @@ -865,9 +865,9 @@ static int aspeed_spi_probe(struct platform_device *pdev) return PTR_ERR(aspi->regs); res = platform_get_resource(pdev, IORESOURCE_MEM, 1); - if (IS_ERR(res)) { + if (!res) { dev_err(dev, "missing AHB memory\n"); - return PTR_ERR(res); + return -EINVAL; } aspi->ahb_window_size = resource_size(res); From 7c69694cec869e3bf7c810fd94f860253aeb8053 Mon Sep 17 00:00:00 2001 From: Wolfram Sang Date: Mon, 20 Oct 2025 08:09:53 +0200 Subject: [PATCH 23/80] spi: dt-bindings: don't check node names Node names are already and properly checked by the core schema. No need to do it again. Signed-off-by: Wolfram Sang Acked-by: Conor Dooley Link: https://patch.msgid.link/20251020060951.30776-10-wsa+renesas@sang-engineering.com Signed-off-by: Mark Brown --- Documentation/devicetree/bindings/spi/snps,dw-apb-ssi.yaml | 2 +- Documentation/devicetree/bindings/spi/spi-controller.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Documentation/devicetree/bindings/spi/snps,dw-apb-ssi.yaml b/Documentation/devicetree/bindings/spi/snps,dw-apb-ssi.yaml index 0543c526b783..5c87fc8a845d 100644 --- a/Documentation/devicetree/bindings/spi/snps,dw-apb-ssi.yaml +++ b/Documentation/devicetree/bindings/spi/snps,dw-apb-ssi.yaml @@ -153,7 +153,7 @@ properties: provides an interface to override the native DWC SSI CS control. patternProperties: - "^.*@[0-9a-f]+$": + "@[0-9a-f]+$": type: object additionalProperties: true diff --git a/Documentation/devicetree/bindings/spi/spi-controller.yaml b/Documentation/devicetree/bindings/spi/spi-controller.yaml index 82d051f7bd6e..3b8e990e30c4 100644 --- a/Documentation/devicetree/bindings/spi/spi-controller.yaml +++ b/Documentation/devicetree/bindings/spi/spi-controller.yaml @@ -111,7 +111,7 @@ properties: - compatible patternProperties: - "^.*@[0-9a-f]+$": + "@[0-9a-f]+$": type: object $ref: spi-peripheral-props.yaml additionalProperties: true From fd5ef3d69f8975bad16c437a337b5cb04c8217a2 Mon Sep 17 00:00:00 2001 From: Can Peng Date: Thu, 23 Oct 2025 10:42:50 +0800 Subject: [PATCH 24/80] spi: spi-qpic-snand: make qcom_spi_ecc_engine_ops_pipelined const Marking the qcom_spi_ecc_engine_ops_pipelined as const provides memory protection by preventing accidental modification of critical function pointers at runtime. It also enables memory optimization by placing the structure in read-only sections and improves code safety by explicitly documenting the design intent that these operations should not change after initialization. Signed-off-by: Can Peng Link: https://patch.msgid.link/20251023024250.3181084-1-pengcan@kylinos.cn Signed-off-by: Mark Brown --- drivers/spi/spi-qpic-snand.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/spi/spi-qpic-snand.c b/drivers/spi/spi-qpic-snand.c index 58ceea1ea8fb..7681a91d67d5 100644 --- a/drivers/spi/spi-qpic-snand.c +++ b/drivers/spi/spi-qpic-snand.c @@ -448,7 +448,7 @@ static int qcom_spi_ecc_finish_io_req_pipelined(struct nand_device *nand, return snandc->qspi->ecc_stats.bitflips; } -static struct nand_ecc_engine_ops qcom_spi_ecc_engine_ops_pipelined = { +static const struct nand_ecc_engine_ops qcom_spi_ecc_engine_ops_pipelined = { .init_ctx = qcom_spi_ecc_init_ctx_pipelined, .cleanup_ctx = qcom_spi_ecc_cleanup_ctx_pipelined, .prepare_io_req = qcom_spi_ecc_prepare_io_req_pipelined, From 77a58ba7c64ccca20616aa03599766ccb0d1a330 Mon Sep 17 00:00:00 2001 From: Sean Anderson Date: Tue, 21 Oct 2025 10:47:03 -0400 Subject: [PATCH 25/80] spi: spi-mem: Trace exec_op The spi subsystem has tracing, which is very convenient when debugging problems. Add tracing for spi-mem too so that accesses that skip the spi subsystem can still be seen. The format is roughly based on the existing spi tracing. We don't bother tracing the op's address because the tracing happens while the memory is locked, so there can be no confusion about the matching of start and stop. The conversion of cmd/addr/dummy to an array is directly analogous to the conversion in the latter half of spi_mem_exec_op. Signed-off-by: Sean Anderson Link: https://patch.msgid.link/20251021144702.1582397-1-sean.anderson@linux.dev Signed-off-by: Mark Brown --- MAINTAINERS | 1 + drivers/spi/spi-mem.c | 5 ++ include/trace/events/spi-mem.h | 106 +++++++++++++++++++++++++++++++++ 3 files changed, 112 insertions(+) create mode 100644 include/trace/events/spi-mem.h diff --git a/MAINTAINERS b/MAINTAINERS index 46126ce2f968..f5095fa88370 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -24226,6 +24226,7 @@ T: git git://git.kernel.org/pub/scm/linux/kernel/git/broonie/spi.git F: Documentation/devicetree/bindings/spi/ F: Documentation/spi/ F: drivers/spi/ +F: include/trace/events/spi* F: include/linux/spi/ F: include/uapi/linux/spi/ F: tools/spi/ diff --git a/drivers/spi/spi-mem.c b/drivers/spi/spi-mem.c index 064b99204d9a..c8b2add2640e 100644 --- a/drivers/spi/spi-mem.c +++ b/drivers/spi/spi-mem.c @@ -12,6 +12,9 @@ #include #include +#define CREATE_TRACE_POINTS +#include + #include "internals.h" #define SPI_MEM_MAX_BUSWIDTH 8 @@ -403,7 +406,9 @@ int spi_mem_exec_op(struct spi_mem *mem, const struct spi_mem_op *op) if (ret) return ret; + trace_spi_mem_start_op(mem, op); ret = ctlr->mem_ops->exec_op(mem, op); + trace_spi_mem_stop_op(mem, op); spi_mem_access_end(mem); diff --git a/include/trace/events/spi-mem.h b/include/trace/events/spi-mem.h new file mode 100644 index 000000000000..d13f0bcff5e7 --- /dev/null +++ b/include/trace/events/spi-mem.h @@ -0,0 +1,106 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#undef TRACE_SYSTEM +#define TRACE_SYSTEM spi-mem + +#undef TRACE_SYSTEM_VAR +#define TRACE_SYSTEM_VAR spi_mem + +#if !defined(_TRACE_SPI_MEM_H) || defined(TRACE_HEADER_MULTI_READ) +#define _TRACE_SPI_MEM_H + +#include +#include + +#define decode_dtr(dtr) \ + __print_symbolic(dtr, \ + { 0, "S" }, \ + { 1, "D" }) + +TRACE_EVENT(spi_mem_start_op, + TP_PROTO(struct spi_mem *mem, const struct spi_mem_op *op), + TP_ARGS(mem, op), + + TP_STRUCT__entry( + __string(name, mem->name) + __dynamic_array(u8, op, 1 + op->addr.nbytes + op->dummy.nbytes) + __dynamic_array(u8, data, op->data.dir == SPI_MEM_DATA_OUT ? + min(op->data.nbytes, 64) : 0) + __field(u32, data_len) + __field(u32, max_freq) + __field(u8, cmd_buswidth) + __field(bool, cmd_dtr) + __field(u8, addr_buswidth) + __field(bool, addr_dtr) + __field(u8, dummy_nbytes) + __field(u8, data_buswidth) + __field(bool, data_dtr) + ), + + TP_fast_assign( + int i; + + __assign_str(name); + __entry->max_freq = op->max_freq ?: mem->spi->max_speed_hz; + + __entry->cmd_buswidth = op->cmd.buswidth; + __entry->cmd_dtr = op->cmd.dtr; + *((u8 *)__get_dynamic_array(op)) = op->cmd.opcode; + + __entry->addr_buswidth = op->addr.buswidth; + __entry->addr_dtr = op->addr.dtr; + for (i = 0; i < op->addr.nbytes; i++) + ((u8 *)__get_dynamic_array(op))[i + 1] = + op->addr.val >> (8 * (op->addr.nbytes - i - 1)); + + memset(((u8 *)__get_dynamic_array(op)) + op->addr.nbytes + 1, + 0xff, op->dummy.nbytes); + + __entry->data_len = op->data.nbytes; + __entry->data_buswidth = op->data.buswidth; + __entry->data_dtr = op->data.dtr; + if (op->data.dir == SPI_MEM_DATA_OUT) + memcpy(__get_dynamic_array(data), op->data.buf.out, + __get_dynamic_array_len(data)); + ), + + TP_printk("%s %u%s-%u%s-%u%s @%u Hz op=[%*phD] len=%u tx=[%*phD]", + __get_str(name), + __entry->cmd_buswidth, decode_dtr(__entry->cmd_dtr), + __entry->addr_buswidth, decode_dtr(__entry->addr_dtr), + __entry->data_buswidth, decode_dtr(__entry->data_dtr), + __entry->max_freq, + __get_dynamic_array_len(op), __get_dynamic_array(op), + __entry->data_len, + __get_dynamic_array_len(data), __get_dynamic_array(data)) +); + +TRACE_EVENT(spi_mem_stop_op, + TP_PROTO(struct spi_mem *mem, const struct spi_mem_op *op), + TP_ARGS(mem, op), + + TP_STRUCT__entry( + __string(name, mem->name) + __dynamic_array(u8, data, op->data.dir == SPI_MEM_DATA_IN ? + min(op->data.nbytes, 64) : 0) + __field(u32, data_len) + ), + + TP_fast_assign( + __assign_str(name); + __entry->data_len = op->data.nbytes; + if (op->data.dir == SPI_MEM_DATA_IN) + memcpy(__get_dynamic_array(data), op->data.buf.in, + __get_dynamic_array_len(data)); + ), + + TP_printk("%s len=%u rx=[%*phD]", + __get_str(name), + __entry->data_len, + __get_dynamic_array_len(data), __get_dynamic_array(data)) +); + + +#endif /* _TRACE_SPI_MEM_H */ + +/* This part must be outside protection */ +#include From ecd0de438c1f0ee86cf8f6d5047965a2a181444b Mon Sep 17 00:00:00 2001 From: Zhongqiu Han Date: Thu, 30 Oct 2025 20:47:55 +0800 Subject: [PATCH 26/80] spi: tle62x0: Add newline to sysfs attribute output Append a newline to the sysfs_emit() output in tle62x0_gpio_show. This aligns with common kernel conventions and improves readability for userspace tools that expect newline-terminated values. Signed-off-by: Zhongqiu Han Link: https://patch.msgid.link/20251030124755.1828434-1-zhongqiu.han@oss.qualcomm.com Signed-off-by: Mark Brown --- drivers/spi/spi-tle62x0.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/spi/spi-tle62x0.c b/drivers/spi/spi-tle62x0.c index a565352f6381..663c0136d119 100644 --- a/drivers/spi/spi-tle62x0.c +++ b/drivers/spi/spi-tle62x0.c @@ -141,7 +141,7 @@ static ssize_t tle62x0_gpio_show(struct device *dev, value = (st->gpio_state >> gpio_num) & 1; mutex_unlock(&st->lock); - return sysfs_emit(buf, "%d", value); + return sysfs_emit(buf, "%d\n", value); } static ssize_t tle62x0_gpio_store(struct device *dev, From b4e002d8a7cee3b1d70efad0e222567f92a73000 Mon Sep 17 00:00:00 2001 From: Vishwaroop A Date: Tue, 28 Oct 2025 15:57:01 +0000 Subject: [PATCH 27/80] spi: tegra210-quad: Fix timeout handling When the CPU that the QSPI interrupt handler runs on (typically CPU 0) is excessively busy, it can lead to rare cases of the IRQ thread not running before the transfer timeout is reached. While handling the timeouts, any pending transfers are cleaned up and the message that they correspond to is marked as failed, which leaves the curr_xfer field pointing at stale memory. To avoid this, clear curr_xfer to NULL upon timeout and check for this condition when the IRQ thread is finally run. While at it, also make sure to clear interrupts on failure so that new interrupts can be run. A better, more involved, fix would move the interrupt clearing into a hard IRQ handler. Ideally we would also want to signal that the IRQ thread no longer needs to be run after the timeout is hit to avoid the extra check for a valid transfer. Fixes: 921fc1838fb0 ("spi: tegra210-quad: Add support for Tegra210 QSPI controller") Signed-off-by: Thierry Reding Signed-off-by: Vishwaroop A Link: https://patch.msgid.link/20251028155703.4151791-2-va@nvidia.com Signed-off-by: Mark Brown --- drivers/spi/spi-tegra210-quad.c | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/drivers/spi/spi-tegra210-quad.c b/drivers/spi/spi-tegra210-quad.c index 3be7499db21e..d9ca3d7b082f 100644 --- a/drivers/spi/spi-tegra210-quad.c +++ b/drivers/spi/spi-tegra210-quad.c @@ -1024,8 +1024,10 @@ static void tegra_qspi_handle_error(struct tegra_qspi *tqspi) dev_err(tqspi->dev, "error in transfer, fifo status 0x%08x\n", tqspi->status_reg); tegra_qspi_dump_regs(tqspi); tegra_qspi_flush_fifos(tqspi, true); - if (device_reset(tqspi->dev) < 0) + if (device_reset(tqspi->dev) < 0) { dev_warn_once(tqspi->dev, "device reset failed\n"); + tegra_qspi_mask_clear_irq(tqspi); + } } static void tegra_qspi_transfer_end(struct spi_device *spi) @@ -1176,9 +1178,11 @@ static int tegra_qspi_combined_seq_xfer(struct tegra_qspi *tqspi, } /* Reset controller if timeout happens */ - if (device_reset(tqspi->dev) < 0) + if (device_reset(tqspi->dev) < 0) { dev_warn_once(tqspi->dev, "device reset failed\n"); + tegra_qspi_mask_clear_irq(tqspi); + } ret = -EIO; goto exit; } @@ -1200,11 +1204,13 @@ static int tegra_qspi_combined_seq_xfer(struct tegra_qspi *tqspi, tegra_qspi_transfer_end(spi); spi_transfer_delay_exec(xfer); } + tqspi->curr_xfer = NULL; transfer_phase++; } ret = 0; exit: + tqspi->curr_xfer = NULL; msg->status = ret; return ret; @@ -1290,6 +1296,8 @@ static int tegra_qspi_non_combined_seq_xfer(struct tegra_qspi *tqspi, msg->actual_length += xfer->len + dummy_bytes; complete_xfer: + tqspi->curr_xfer = NULL; + if (ret < 0) { tegra_qspi_transfer_end(spi); spi_transfer_delay_exec(xfer); @@ -1395,6 +1403,7 @@ static irqreturn_t handle_cpu_based_xfer(struct tegra_qspi *tqspi) tegra_qspi_calculate_curr_xfer_param(tqspi, t); tegra_qspi_start_cpu_based_transfer(tqspi, t); exit: + tqspi->curr_xfer = NULL; spin_unlock_irqrestore(&tqspi->lock, flags); return IRQ_HANDLED; } @@ -1480,6 +1489,15 @@ static irqreturn_t tegra_qspi_isr_thread(int irq, void *context_data) { struct tegra_qspi *tqspi = context_data; + /* + * Occasionally the IRQ thread takes a long time to wake up (usually + * when the CPU that it's running on is excessively busy) and we have + * already reached the timeout before and cleaned up the timed out + * transfer. Avoid any processing in that case and bail out early. + */ + if (!tqspi->curr_xfer) + return IRQ_NONE; + tqspi->status_reg = tegra_qspi_readl(tqspi, QSPI_FIFO_STATUS); if (tqspi->cur_direction & DATA_DIR_TX) From 6022eacdda8b0b06a2e1d4122e5268099b62ff5d Mon Sep 17 00:00:00 2001 From: Vishwaroop A Date: Tue, 28 Oct 2025 15:57:02 +0000 Subject: [PATCH 28/80] spi: tegra210-quad: Refactor error handling into helper functions Extract common cleanup code into dedicated helper functions to simplify the code and improve readability. This refactoring includes: - tegra_qspi_reset(): Device reset and interrupt cleanup - tegra_qspi_dma_stop(): DMA termination and disable - tegra_qspi_pio_stop(): PIO mode disable No functional changes. This is purely a code reorganization to prepare for improved timeout handling in subsequent patches. Signed-off-by: Vishwaroop A Acked-by: Thierry Reding Link: https://patch.msgid.link/20251028155703.4151791-3-va@nvidia.com Signed-off-by: Mark Brown --- drivers/spi/spi-tegra210-quad.c | 84 +++++++++++++++++---------------- 1 file changed, 44 insertions(+), 40 deletions(-) diff --git a/drivers/spi/spi-tegra210-quad.c b/drivers/spi/spi-tegra210-quad.c index d9ca3d7b082f..69defb4ffe49 100644 --- a/drivers/spi/spi-tegra210-quad.c +++ b/drivers/spi/spi-tegra210-quad.c @@ -1019,15 +1019,20 @@ static void tegra_qspi_dump_regs(struct tegra_qspi *tqspi) tegra_qspi_readl(tqspi, QSPI_FIFO_STATUS)); } +static void tegra_qspi_reset(struct tegra_qspi *tqspi) +{ + if (device_reset(tqspi->dev) < 0) { + dev_warn_once(tqspi->dev, "device reset failed\n"); + tegra_qspi_mask_clear_irq(tqspi); + } +} + static void tegra_qspi_handle_error(struct tegra_qspi *tqspi) { dev_err(tqspi->dev, "error in transfer, fifo status 0x%08x\n", tqspi->status_reg); tegra_qspi_dump_regs(tqspi); tegra_qspi_flush_fifos(tqspi, true); - if (device_reset(tqspi->dev) < 0) { - dev_warn_once(tqspi->dev, "device reset failed\n"); - tegra_qspi_mask_clear_irq(tqspi); - } + tegra_qspi_reset(tqspi); } static void tegra_qspi_transfer_end(struct spi_device *spi) @@ -1074,6 +1079,30 @@ static u32 tegra_qspi_addr_config(bool is_ddr, u8 bus_width, u8 len) return addr_config; } +static void tegra_qspi_dma_stop(struct tegra_qspi *tqspi) +{ + u32 value; + + if ((tqspi->cur_direction & DATA_DIR_TX) && tqspi->tx_dma_chan) + dmaengine_terminate_all(tqspi->tx_dma_chan); + + if ((tqspi->cur_direction & DATA_DIR_RX) && tqspi->rx_dma_chan) + dmaengine_terminate_all(tqspi->rx_dma_chan); + + value = tegra_qspi_readl(tqspi, QSPI_DMA_CTL); + value &= ~QSPI_DMA_EN; + tegra_qspi_writel(tqspi, value, QSPI_DMA_CTL); +} + +static void tegra_qspi_pio_stop(struct tegra_qspi *tqspi) +{ + u32 value; + + value = tegra_qspi_readl(tqspi, QSPI_COMMAND1); + value &= ~QSPI_PIO; + tegra_qspi_writel(tqspi, value, QSPI_COMMAND1); +} + static int tegra_qspi_combined_seq_xfer(struct tegra_qspi *tqspi, struct spi_message *msg) { @@ -1081,7 +1110,7 @@ static int tegra_qspi_combined_seq_xfer(struct tegra_qspi *tqspi, struct spi_transfer *xfer; struct spi_device *spi = msg->spi; u8 transfer_phase = 0; - u32 cmd1 = 0, dma_ctl = 0; + u32 cmd1 = 0; int ret = 0; u32 address_value = 0; u32 cmd_config = 0, addr_config = 0; @@ -1150,39 +1179,16 @@ static int tegra_qspi_combined_seq_xfer(struct tegra_qspi *tqspi, if (WARN_ON_ONCE(ret == 0)) { dev_err_ratelimited(tqspi->dev, "QSPI Transfer failed with timeout\n"); - if (tqspi->is_curr_dma_xfer) { - if ((tqspi->cur_direction & DATA_DIR_TX) && - tqspi->tx_dma_chan) - dmaengine_terminate_all(tqspi->tx_dma_chan); - if ((tqspi->cur_direction & DATA_DIR_RX) && - tqspi->rx_dma_chan) - dmaengine_terminate_all(tqspi->rx_dma_chan); - } /* Abort transfer by resetting pio/dma bit */ - if (!tqspi->is_curr_dma_xfer) { - cmd1 = tegra_qspi_readl - (tqspi, - QSPI_COMMAND1); - cmd1 &= ~QSPI_PIO; - tegra_qspi_writel - (tqspi, cmd1, - QSPI_COMMAND1); - } else { - dma_ctl = tegra_qspi_readl - (tqspi, - QSPI_DMA_CTL); - dma_ctl &= ~QSPI_DMA_EN; - tegra_qspi_writel(tqspi, dma_ctl, - QSPI_DMA_CTL); - } + if (tqspi->is_curr_dma_xfer) + tegra_qspi_dma_stop(tqspi); + else + tegra_qspi_pio_stop(tqspi); /* Reset controller if timeout happens */ - if (device_reset(tqspi->dev) < 0) { - dev_warn_once(tqspi->dev, - "device reset failed\n"); - tegra_qspi_mask_clear_irq(tqspi); - } + tegra_qspi_reset(tqspi); + ret = -EIO; goto exit; } @@ -1276,12 +1282,10 @@ static int tegra_qspi_non_combined_seq_xfer(struct tegra_qspi *tqspi, QSPI_DMA_TIMEOUT); if (WARN_ON(ret == 0)) { dev_err(tqspi->dev, "transfer timeout\n"); - if (tqspi->is_curr_dma_xfer) { - if ((tqspi->cur_direction & DATA_DIR_TX) && tqspi->tx_dma_chan) - dmaengine_terminate_all(tqspi->tx_dma_chan); - if ((tqspi->cur_direction & DATA_DIR_RX) && tqspi->rx_dma_chan) - dmaengine_terminate_all(tqspi->rx_dma_chan); - } + + if (tqspi->is_curr_dma_xfer) + tegra_qspi_dma_stop(tqspi); + tegra_qspi_handle_error(tqspi); ret = -EIO; goto complete_xfer; From 380fd29d57abe6679d87ec56babe65ddc5873a37 Mon Sep 17 00:00:00 2001 From: Vishwaroop A Date: Tue, 28 Oct 2025 15:57:03 +0000 Subject: [PATCH 29/80] spi: tegra210-quad: Check hardware status on timeout Under high system load, QSPI interrupts can be delayed or blocked on the target CPU, causing wait_for_completion_timeout() to report failure even though the hardware successfully completed the transfer. When a timeout occurs, check the QSPI_RDY bit in QSPI_TRANS_STATUS to determine if the hardware actually completed the transfer. If so, manually invoke the completion handler to process the transfer successfully instead of failing it. This distinguishes lost/delayed interrupts from real hardware timeouts, preventing unnecessary failures of transfers that completed successfully. Signed-off-by: Vishwaroop A Acked-by: Thierry Reding Link: https://patch.msgid.link/20251028155703.4151791-4-va@nvidia.com Signed-off-by: Mark Brown --- drivers/spi/spi-tegra210-quad.c | 94 +++++++++++++++++++++++++++------ 1 file changed, 77 insertions(+), 17 deletions(-) diff --git a/drivers/spi/spi-tegra210-quad.c b/drivers/spi/spi-tegra210-quad.c index 69defb4ffe49..cdc3cb7c01f9 100644 --- a/drivers/spi/spi-tegra210-quad.c +++ b/drivers/spi/spi-tegra210-quad.c @@ -1048,6 +1048,49 @@ static void tegra_qspi_transfer_end(struct spi_device *spi) tegra_qspi_writel(tqspi, tqspi->def_command1_reg, QSPI_COMMAND1); } +static irqreturn_t handle_cpu_based_xfer(struct tegra_qspi *tqspi); +static irqreturn_t handle_dma_based_xfer(struct tegra_qspi *tqspi); + +/** + * tegra_qspi_handle_timeout - Handle transfer timeout with hardware check + * @tqspi: QSPI controller instance + * + * When a timeout occurs but hardware has completed the transfer (interrupt + * was lost or delayed), manually trigger transfer completion processing. + * This avoids failing transfers that actually succeeded. + * + * Returns: 0 if transfer was completed, -ETIMEDOUT if real timeout + */ +static int tegra_qspi_handle_timeout(struct tegra_qspi *tqspi) +{ + irqreturn_t ret; + u32 status; + + /* Check if hardware actually completed the transfer */ + status = tegra_qspi_readl(tqspi, QSPI_TRANS_STATUS); + if (!(status & QSPI_RDY)) + return -ETIMEDOUT; + + /* + * Hardware completed but interrupt was lost/delayed. Manually + * process the completion by calling the appropriate handler. + */ + dev_warn_ratelimited(tqspi->dev, + "QSPI interrupt timeout, but transfer complete\n"); + + /* Clear the transfer status */ + status = tegra_qspi_readl(tqspi, QSPI_TRANS_STATUS); + tegra_qspi_writel(tqspi, status, QSPI_TRANS_STATUS); + + /* Manually trigger completion handler */ + if (!tqspi->is_curr_dma_xfer) + ret = handle_cpu_based_xfer(tqspi); + else + ret = handle_dma_based_xfer(tqspi); + + return (ret == IRQ_HANDLED) ? 0 : -EIO; +} + static u32 tegra_qspi_cmd_config(bool is_ddr, u8 bus_width, u8 len) { u32 cmd_config = 0; @@ -1177,20 +1220,28 @@ static int tegra_qspi_combined_seq_xfer(struct tegra_qspi *tqspi, QSPI_DMA_TIMEOUT); if (WARN_ON_ONCE(ret == 0)) { - dev_err_ratelimited(tqspi->dev, - "QSPI Transfer failed with timeout\n"); + /* + * Check if hardware completed the transfer + * even though interrupt was lost or delayed. + * If so, process the completion and continue. + */ + ret = tegra_qspi_handle_timeout(tqspi); + if (ret < 0) { + /* Real timeout - clean up and fail */ + dev_err(tqspi->dev, "transfer timeout\n"); - /* Abort transfer by resetting pio/dma bit */ - if (tqspi->is_curr_dma_xfer) - tegra_qspi_dma_stop(tqspi); - else - tegra_qspi_pio_stop(tqspi); + /* Abort transfer by resetting pio/dma bit */ + if (tqspi->is_curr_dma_xfer) + tegra_qspi_dma_stop(tqspi); + else + tegra_qspi_pio_stop(tqspi); - /* Reset controller if timeout happens */ - tegra_qspi_reset(tqspi); + /* Reset controller if timeout happens */ + tegra_qspi_reset(tqspi); - ret = -EIO; - goto exit; + ret = -EIO; + goto exit; + } } if (tqspi->tx_status || tqspi->rx_status) { @@ -1281,14 +1332,23 @@ static int tegra_qspi_non_combined_seq_xfer(struct tegra_qspi *tqspi, ret = wait_for_completion_timeout(&tqspi->xfer_completion, QSPI_DMA_TIMEOUT); if (WARN_ON(ret == 0)) { - dev_err(tqspi->dev, "transfer timeout\n"); + /* + * Check if hardware completed the transfer even though + * interrupt was lost or delayed. If so, process the + * completion and continue. + */ + ret = tegra_qspi_handle_timeout(tqspi); + if (ret < 0) { + /* Real timeout - clean up and fail */ + dev_err(tqspi->dev, "transfer timeout\n"); - if (tqspi->is_curr_dma_xfer) - tegra_qspi_dma_stop(tqspi); + if (tqspi->is_curr_dma_xfer) + tegra_qspi_dma_stop(tqspi); - tegra_qspi_handle_error(tqspi); - ret = -EIO; - goto complete_xfer; + tegra_qspi_handle_error(tqspi); + ret = -EIO; + goto complete_xfer; + } } if (tqspi->tx_status || tqspi->rx_status) { From 2f538ef9f6f7c3d700c68536f21447dfc598f8c8 Mon Sep 17 00:00:00 2001 From: Chin-Ting Kuo Date: Wed, 5 Nov 2025 16:49:52 +0800 Subject: [PATCH 30/80] spi: aspeed: Use devm_iounmap() to unmap devm_ioremap() memory MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The AHB IO memory for each chip select is mapped using devm_ioremap(), so it should be unmapped using devm_iounmap() to ensure proper device-managed resource cleanup. Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202510292356.JnTUBxCl-lkp@intel.com/ Signed-off-by: Chin-Ting Kuo Reviewed-by: Cédric Le Goater Link: https://patch.msgid.link/20251105084952.1063489-1-chin-ting_kuo@aspeedtech.com Signed-off-by: Mark Brown --- drivers/spi/spi-aspeed-smc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/spi/spi-aspeed-smc.c b/drivers/spi/spi-aspeed-smc.c index e8bd8fe6c4e7..179c47ffbfeb 100644 --- a/drivers/spi/spi-aspeed-smc.c +++ b/drivers/spi/spi-aspeed-smc.c @@ -396,7 +396,7 @@ static int aspeed_spi_set_window(struct aspeed_spi *aspi) for (cs = 0; cs < aspi->data->max_cs; cs++) { if (aspi->chips[cs].ahb_base) { - iounmap(aspi->chips[cs].ahb_base); + devm_iounmap(dev, aspi->chips[cs].ahb_base); aspi->chips[cs].ahb_base = NULL; } } From 55d03b5b5bdd04daf9a35ce49db18d8bb488dffb Mon Sep 17 00:00:00 2001 From: Clark Wang Date: Fri, 24 Oct 2025 13:52:11 +0800 Subject: [PATCH 31/80] spi: imx: remove CLK calculation and check for target mode In target mode, the clock signal is controlled by the master. Target does not need to check, calculate and configure the clock frequency division. The target can directly use the root clock to sample the SCL signal. Therefore, remove check, calculation and frequency division function of the clock for target mode. Signed-off-by: Carlos Song Signed-off-by: Clark Wang Reviewed-by: Frank Li Link: https://patch.msgid.link/20251024055211.408440-1-carlos.song@nxp.com Signed-off-by: Mark Brown --- drivers/spi/spi-imx.c | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/drivers/spi/spi-imx.c b/drivers/spi/spi-imx.c index 155ddeb8fcd4..510074ef4ed4 100644 --- a/drivers/spi/spi-imx.c +++ b/drivers/spi/spi-imx.c @@ -42,6 +42,7 @@ MODULE_PARM_DESC(polling_limit_us, "time in us to run a transfer in polling mode\n"); #define MXC_RPM_TIMEOUT 2000 /* 2000ms */ +#define MXC_SPI_DEFAULT_SPEED 500000 /* 500KHz */ #define MXC_CSPIRXDATA 0x00 #define MXC_CSPITXDATA 0x04 @@ -684,8 +685,11 @@ static int mx51_ecspi_prepare_transfer(struct spi_imx_data *spi_imx, /* set clock speed */ ctrl &= ~(0xf << MX51_ECSPI_CTRL_POSTDIV_OFFSET | 0xf << MX51_ECSPI_CTRL_PREDIV_OFFSET); - ctrl |= mx51_ecspi_clkdiv(spi_imx, spi_imx->spi_bus_clk, &clk); - spi_imx->spi_bus_clk = clk; + + if (!spi_imx->target_mode) { + ctrl |= mx51_ecspi_clkdiv(spi_imx, spi_imx->spi_bus_clk, &clk); + spi_imx->spi_bus_clk = clk; + } mx51_configure_cpha(spi_imx, spi); @@ -1308,15 +1312,18 @@ static int spi_imx_setupxfer(struct spi_device *spi, if (!t) return 0; - if (!t->speed_hz) { - if (!spi->max_speed_hz) { - dev_err(&spi->dev, "no speed_hz provided!\n"); - return -EINVAL; + if (!spi_imx->target_mode) { + if (!t->speed_hz) { + if (!spi->max_speed_hz) { + dev_err(&spi->dev, "no speed_hz provided!\n"); + return -EINVAL; + } + dev_dbg(&spi->dev, "using spi->max_speed_hz!\n"); + spi_imx->spi_bus_clk = spi->max_speed_hz; + } else { + spi_imx->spi_bus_clk = t->speed_hz; } - dev_dbg(&spi->dev, "using spi->max_speed_hz!\n"); - spi_imx->spi_bus_clk = spi->max_speed_hz; - } else - spi_imx->spi_bus_clk = t->speed_hz; + } spi_imx->bits_per_word = t->bits_per_word; spi_imx->count = t->len; @@ -1831,6 +1838,7 @@ static int spi_imx_probe(struct platform_device *pdev) controller->prepare_message = spi_imx_prepare_message; controller->unprepare_message = spi_imx_unprepare_message; controller->target_abort = spi_imx_target_abort; + spi_imx->spi_bus_clk = MXC_SPI_DEFAULT_SPEED; controller->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH | SPI_NO_CS | SPI_MOSI_IDLE_LOW; From bd352547df647be8a1e6c9d4ca2b54b459f3abc1 Mon Sep 17 00:00:00 2001 From: Alex Elder Date: Mon, 27 Oct 2025 08:29:59 -0500 Subject: [PATCH 32/80] spi: dt-bindings: fsl-qspi: support SpacemiT K1 Add the SpacemiT K1 SoC QSPI IP to the list of supported hardware. This is the first non-Freescale device represented here. It has a nearly identidal register set, and this binding correctly describes the hardware. Acked-by: Conor Dooley Reviewed-by: Frank Li Signed-off-by: Alex Elder Link: https://patch.msgid.link/20251027133008.360237-2-elder@riscstar.com Signed-off-by: Mark Brown --- Documentation/devicetree/bindings/spi/fsl,spi-fsl-qspi.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/spi/fsl,spi-fsl-qspi.yaml b/Documentation/devicetree/bindings/spi/fsl,spi-fsl-qspi.yaml index f2dd20370dbb..5e6aff1bc2ed 100644 --- a/Documentation/devicetree/bindings/spi/fsl,spi-fsl-qspi.yaml +++ b/Documentation/devicetree/bindings/spi/fsl,spi-fsl-qspi.yaml @@ -22,6 +22,7 @@ properties: - fsl,imx6ul-qspi - fsl,ls1021a-qspi - fsl,ls2080a-qspi + - spacemit,k1-qspi - items: - enum: - fsl,ls1043a-qspi From 873a46141460d209bb62eaa0dc9e7b67bff924a6 Mon Sep 17 00:00:00 2001 From: Alex Elder Date: Mon, 27 Oct 2025 08:30:00 -0500 Subject: [PATCH 33/80] spi: dt-bindings: fsl-qspi: add optional resets Allow two resets to be defined to support the SpacemiT K1 SoC QSPI IP. Move the allOf block down, below the required section. Reviewed-by: Frank Li Acked-by: Conor Dooley Signed-off-by: Alex Elder Link: https://patch.msgid.link/20251027133008.360237-3-elder@riscstar.com Signed-off-by: Mark Brown --- .../bindings/spi/fsl,spi-fsl-qspi.yaml | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/Documentation/devicetree/bindings/spi/fsl,spi-fsl-qspi.yaml b/Documentation/devicetree/bindings/spi/fsl,spi-fsl-qspi.yaml index 5e6aff1bc2ed..1d10cfbad86c 100644 --- a/Documentation/devicetree/bindings/spi/fsl,spi-fsl-qspi.yaml +++ b/Documentation/devicetree/bindings/spi/fsl,spi-fsl-qspi.yaml @@ -9,9 +9,6 @@ title: Freescale Quad Serial Peripheral Interface (QuadSPI) maintainers: - Han Xu -allOf: - - $ref: spi-controller.yaml# - properties: compatible: oneOf: @@ -55,6 +52,11 @@ properties: - const: qspi_en - const: qspi + resets: + items: + - description: SoC QSPI reset + - description: SoC QSPI bus reset + required: - compatible - reg @@ -63,6 +65,18 @@ required: - clocks - clock-names +allOf: + - $ref: spi-controller.yaml# + - if: + properties: + compatible: + not: + contains: + const: spacemit,k1-qspi + then: + properties: + resets: false + unevaluatedProperties: false examples: From 106d7641e55a472e7523c1f525c77fb6d420064d Mon Sep 17 00:00:00 2001 From: Alex Elder Date: Mon, 27 Oct 2025 08:30:01 -0500 Subject: [PATCH 34/80] spi: fsl-qspi: add optional reset support Add support for one or more optional exclusive resets. These simply need to be deasserted at probe time, and can remain that way for the life of the device. Reviewed-by: Frank Li Signed-off-by: Alex Elder Link: https://patch.msgid.link/20251027133008.360237-4-elder@riscstar.com Signed-off-by: Mark Brown --- drivers/spi/spi-fsl-qspi.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/drivers/spi/spi-fsl-qspi.c b/drivers/spi/spi-fsl-qspi.c index c887abb028d7..1e27647dd2a0 100644 --- a/drivers/spi/spi-fsl-qspi.c +++ b/drivers/spi/spi-fsl-qspi.c @@ -36,6 +36,7 @@ #include #include #include +#include #include #include @@ -267,6 +268,7 @@ struct fsl_qspi { const struct fsl_qspi_devtype_data *devtype_data; struct mutex lock; struct completion c; + struct reset_control *resets; struct clk *clk, *clk_en; struct pm_qos_request pm_qos_req; struct device *dev; @@ -857,6 +859,8 @@ static void fsl_qspi_cleanup(void *data) { struct fsl_qspi *q = data; + reset_control_assert(q->resets); + fsl_qspi_clk_disable_unprep(q); mutex_destroy(&q->lock); @@ -902,6 +906,10 @@ static int fsl_qspi_probe(struct platform_device *pdev) if (!q->ahb_addr) return -ENOMEM; + q->resets = devm_reset_control_array_get_optional_exclusive(dev); + if (IS_ERR(q->resets)) + return PTR_ERR(q->resets); + /* find the clocks */ q->clk_en = devm_clk_get(dev, "qspi_en"); if (IS_ERR(q->clk_en)) @@ -923,6 +931,10 @@ static int fsl_qspi_probe(struct platform_device *pdev) if (ret) return ret; + ret = reset_control_deassert(q->resets); + if (ret) + return ret; + /* find the irq */ ret = platform_get_irq(pdev, 0); if (ret < 0) From 6b398c1d3da7a673b13b1857f9fff4c15ee20cef Mon Sep 17 00:00:00 2001 From: Alex Elder Date: Mon, 27 Oct 2025 08:30:02 -0500 Subject: [PATCH 35/80] spi: fsl-qspi: switch predicates to bool Change all the needs_*() functions so they are no longer inline, and return bool rather than int. Reviewed-by: Frank Li Signed-off-by: Alex Elder Link: https://patch.msgid.link/20251027133008.360237-5-elder@riscstar.com Signed-off-by: Mark Brown --- drivers/spi/spi-fsl-qspi.c | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/drivers/spi/spi-fsl-qspi.c b/drivers/spi/spi-fsl-qspi.c index 1e27647dd2a0..1944e63169d3 100644 --- a/drivers/spi/spi-fsl-qspi.c +++ b/drivers/spi/spi-fsl-qspi.c @@ -276,34 +276,34 @@ struct fsl_qspi { u32 memmap_phy; }; -static inline int needs_swap_endian(struct fsl_qspi *q) +static bool needs_swap_endian(struct fsl_qspi *q) { - return q->devtype_data->quirks & QUADSPI_QUIRK_SWAP_ENDIAN; + return !!(q->devtype_data->quirks & QUADSPI_QUIRK_SWAP_ENDIAN); } -static inline int needs_4x_clock(struct fsl_qspi *q) +static bool needs_4x_clock(struct fsl_qspi *q) { - return q->devtype_data->quirks & QUADSPI_QUIRK_4X_INT_CLK; + return !!(q->devtype_data->quirks & QUADSPI_QUIRK_4X_INT_CLK); } -static inline int needs_fill_txfifo(struct fsl_qspi *q) +static bool needs_fill_txfifo(struct fsl_qspi *q) { - return q->devtype_data->quirks & QUADSPI_QUIRK_TKT253890; + return !!(q->devtype_data->quirks & QUADSPI_QUIRK_TKT253890); } -static inline int needs_wakeup_wait_mode(struct fsl_qspi *q) +static bool needs_wakeup_wait_mode(struct fsl_qspi *q) { - return q->devtype_data->quirks & QUADSPI_QUIRK_TKT245618; + return !!(q->devtype_data->quirks & QUADSPI_QUIRK_TKT245618); } -static inline int needs_amba_base_offset(struct fsl_qspi *q) +static bool needs_amba_base_offset(struct fsl_qspi *q) { return !(q->devtype_data->quirks & QUADSPI_QUIRK_BASE_INTERNAL); } -static inline int needs_tdh_setting(struct fsl_qspi *q) +static bool needs_tdh_setting(struct fsl_qspi *q) { - return q->devtype_data->quirks & QUADSPI_QUIRK_USE_TDH_SETTING; + return !!(q->devtype_data->quirks & QUADSPI_QUIRK_USE_TDH_SETTING); } /* From 1797d254f5c4b46b295527a635af7321a3fe1318 Mon Sep 17 00:00:00 2001 From: Alex Elder Date: Mon, 27 Oct 2025 08:30:03 -0500 Subject: [PATCH 36/80] spi: fsl-qspi: add a clock disable quirk The SpacemiT K1 SoC QSPI implementation needs to avoid shutting off the clock when changing its rate. Add a new quirk to indicate that disabling and enabling the clock should be skipped when changing its rate. Reviewed-by: Frank Li Signed-off-by: Alex Elder Link: https://patch.msgid.link/20251027133008.360237-6-elder@riscstar.com Signed-off-by: Mark Brown --- drivers/spi/spi-fsl-qspi.c | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/drivers/spi/spi-fsl-qspi.c b/drivers/spi/spi-fsl-qspi.c index 1944e63169d3..2c030dd6facc 100644 --- a/drivers/spi/spi-fsl-qspi.c +++ b/drivers/spi/spi-fsl-qspi.c @@ -197,6 +197,11 @@ */ #define QUADSPI_QUIRK_USE_TDH_SETTING BIT(5) +/* + * Do not disable the "qspi" clock when changing its rate. + */ +#define QUADSPI_QUIRK_SKIP_CLK_DISABLE BIT(6) + struct fsl_qspi_devtype_data { unsigned int rxfifo; unsigned int txfifo; @@ -306,6 +311,11 @@ static bool needs_tdh_setting(struct fsl_qspi *q) return !!(q->devtype_data->quirks & QUADSPI_QUIRK_USE_TDH_SETTING); } +static bool needs_clk_disable(struct fsl_qspi *q) +{ + return !(q->devtype_data->quirks & QUADSPI_QUIRK_SKIP_CLK_DISABLE); +} + /* * An IC bug makes it necessary to rearrange the 32-bit data. * Later chips, such as IMX6SLX, have fixed this bug. @@ -536,15 +546,18 @@ static void fsl_qspi_select_mem(struct fsl_qspi *q, struct spi_device *spi, if (needs_4x_clock(q)) rate *= 4; - fsl_qspi_clk_disable_unprep(q); + if (needs_clk_disable(q)) + fsl_qspi_clk_disable_unprep(q); ret = clk_set_rate(q->clk, rate); if (ret) return; - ret = fsl_qspi_clk_prep_enable(q); - if (ret) - return; + if (needs_clk_disable(q)) { + ret = fsl_qspi_clk_prep_enable(q); + if (ret) + return; + } q->selected = spi_get_chipselect(spi, 0); From 56931105074fe7e5fc9d54e3163df3b95075643c Mon Sep 17 00:00:00 2001 From: Alex Elder Date: Mon, 27 Oct 2025 08:30:04 -0500 Subject: [PATCH 37/80] spi: fsl-qspi: introduce sfa_size devtype data In fsl_qspi_default_setup(), four registers define the size of blocks of data to written to each of four chips that comprise SPI NOR flash storage. They are currently defined to be the same as the AHB buffer size. The SpacemiT QSPI has an AHB buffer size of 512 bytes, but requires these four sizes to be multiples of 1024 bytes. Define a new field sfa_size in the fsl_qspi_devtype_data structure that, if non-zero, will be used instead of the AHB buffer size to define the size of these chip regions. Reviewed-by: Frank Li Signed-off-by: Alex Elder Link: https://patch.msgid.link/20251027133008.360237-7-elder@riscstar.com Signed-off-by: Mark Brown --- drivers/spi/spi-fsl-qspi.c | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/drivers/spi/spi-fsl-qspi.c b/drivers/spi/spi-fsl-qspi.c index 2c030dd6facc..46a3187b3354 100644 --- a/drivers/spi/spi-fsl-qspi.c +++ b/drivers/spi/spi-fsl-qspi.c @@ -207,6 +207,7 @@ struct fsl_qspi_devtype_data { unsigned int txfifo; int invalid_mstrid; unsigned int ahb_buf_size; + unsigned int sfa_size; unsigned int quirks; bool little_endian; }; @@ -737,6 +738,7 @@ static int fsl_qspi_default_setup(struct fsl_qspi *q) { void __iomem *base = q->iobase; u32 reg, addr_offset = 0; + u32 sfa_size; int ret; /* disable and unprepare clock to avoid glitch pass to controller */ @@ -795,17 +797,17 @@ static int fsl_qspi_default_setup(struct fsl_qspi *q) * In HW there can be a maximum of four chips on two buses with * two chip selects on each bus. We use four chip selects in SW * to differentiate between the four chips. - * We use ahb_buf_size for each chip and set SFA1AD, SFA2AD, SFB1AD, - * SFB2AD accordingly. + * + * By default we write the AHB buffer size to each chip, but + * a different size can be specified with devtype_data->sfa_size. + * The SFA1AD, SFA2AD, SFB1AD, and SFB2AD registers define the + * top (end) of these four regions. */ - qspi_writel(q, q->devtype_data->ahb_buf_size + addr_offset, - base + QUADSPI_SFA1AD); - qspi_writel(q, q->devtype_data->ahb_buf_size * 2 + addr_offset, - base + QUADSPI_SFA2AD); - qspi_writel(q, q->devtype_data->ahb_buf_size * 3 + addr_offset, - base + QUADSPI_SFB1AD); - qspi_writel(q, q->devtype_data->ahb_buf_size * 4 + addr_offset, - base + QUADSPI_SFB2AD); + sfa_size = q->devtype_data->sfa_size ? : q->devtype_data->ahb_buf_size; + qspi_writel(q, addr_offset + 1 * sfa_size, base + QUADSPI_SFA1AD); + qspi_writel(q, addr_offset + 2 * sfa_size, base + QUADSPI_SFA2AD); + qspi_writel(q, addr_offset + 3 * sfa_size, base + QUADSPI_SFB1AD); + qspi_writel(q, addr_offset + 4 * sfa_size, base + QUADSPI_SFB2AD); q->selected = -1; From abc9a349b87ac0fd3ba8787ca00971b59c2e1257 Mon Sep 17 00:00:00 2001 From: Alex Elder Date: Mon, 27 Oct 2025 08:30:05 -0500 Subject: [PATCH 38/80] spi: fsl-qspi: support the SpacemiT K1 SoC Allow the SPI_FSL_QUADSPI Kconfig option to be selected if ARCH_SPACEMIT enabled. Add support for the SpacemiT K1 SoC in the Freescale QSPI driver by defining the device type data for its QSPI implementation. Signed-off-by: Alex Elder Link: https://patch.msgid.link/20251027133008.360237-8-elder@riscstar.com Signed-off-by: Mark Brown --- drivers/spi/Kconfig | 3 ++- drivers/spi/spi-fsl-qspi.c | 11 +++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index 4d8f00c850c1..592d46c9998b 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -435,7 +435,8 @@ config SPI_FSL_LPSPI config SPI_FSL_QUADSPI tristate "Freescale QSPI controller" - depends on ARCH_MXC || SOC_LS1021A || ARCH_LAYERSCAPE || COMPILE_TEST + depends on ARCH_MXC || SOC_LS1021A || ARCH_LAYERSCAPE || \ + ARCH_SPACEMIT || COMPILE_TEST depends on HAS_IOMEM help This enables support for the Quad SPI controller in master mode. diff --git a/drivers/spi/spi-fsl-qspi.c b/drivers/spi/spi-fsl-qspi.c index 46a3187b3354..a223b4bc6e63 100644 --- a/drivers/spi/spi-fsl-qspi.c +++ b/drivers/spi/spi-fsl-qspi.c @@ -268,6 +268,16 @@ static const struct fsl_qspi_devtype_data ls2080a_data = { .little_endian = true, }; +static const struct fsl_qspi_devtype_data spacemit_k1_data = { + .rxfifo = SZ_128, + .txfifo = SZ_256, + .ahb_buf_size = SZ_512, + .sfa_size = SZ_1K, + .invalid_mstrid = QUADSPI_BUFXCR_INVALID_MSTRID, + .quirks = QUADSPI_QUIRK_TKT253890 | QUADSPI_QUIRK_SKIP_CLK_DISABLE, + .little_endian = true, +}; + struct fsl_qspi { void __iomem *iobase; void __iomem *ahb_addr; @@ -1003,6 +1013,7 @@ static const struct of_device_id fsl_qspi_dt_ids[] = { { .compatible = "fsl,imx6ul-qspi", .data = &imx6ul_data, }, { .compatible = "fsl,ls1021a-qspi", .data = &ls1021a_data, }, { .compatible = "fsl,ls2080a-qspi", .data = &ls2080a_data, }, + { .compatible = "spacemit,k1-qspi", .data = &spacemit_k1_data, }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, fsl_qspi_dt_ids); From 4e92abd0a11b91af3742197a9ca962c3c00d0948 Mon Sep 17 00:00:00 2001 From: Carlos Song Date: Mon, 27 Oct 2025 19:02:56 +0800 Subject: [PATCH 39/80] spi: imx: add i.MX51 ECSPI target mode support ECSPI in i.MX51 and i.MX53 support target mode. Current code only support i.MX53. Remove is_imx53_ecspi() check for target mode to support i.MX51. Signed-off-by: Carlos Song Link: https://patch.msgid.link/20251027110256.543314-1-carlos.song@nxp.com Signed-off-by: Mark Brown --- drivers/spi/spi-imx.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/drivers/spi/spi-imx.c b/drivers/spi/spi-imx.c index 510074ef4ed4..edd33fc28c7d 100644 --- a/drivers/spi/spi-imx.c +++ b/drivers/spi/spi-imx.c @@ -586,7 +586,7 @@ static int mx51_ecspi_prepare_message(struct spi_imx_data *spi_imx, * is not functional for imx53 Soc, config SPI burst completed when * BURST_LENGTH + 1 bits are received */ - if (spi_imx->target_mode && is_imx53_ecspi(spi_imx)) + if (spi_imx->target_mode) cfg &= ~MX51_ECSPI_CONFIG_SBBCTRL(channel); else cfg |= MX51_ECSPI_CONFIG_SBBCTRL(channel); @@ -674,7 +674,7 @@ static int mx51_ecspi_prepare_transfer(struct spi_imx_data *spi_imx, /* Clear BL field and set the right value */ ctrl &= ~MX51_ECSPI_CTRL_BL_MASK; - if (spi_imx->target_mode && is_imx53_ecspi(spi_imx)) + if (spi_imx->target_mode) ctrl |= (spi_imx->target_burst * 8 - 1) << MX51_ECSPI_CTRL_BL_OFFSET; else { @@ -1367,7 +1367,7 @@ static int spi_imx_setupxfer(struct spi_device *spi, spi_imx->rx_only = ((t->tx_buf == NULL) || (t->tx_buf == spi->controller->dummy_tx)); - if (is_imx53_ecspi(spi_imx) && spi_imx->target_mode) { + if (spi_imx->target_mode) { spi_imx->rx = mx53_ecspi_rx_target; spi_imx->tx = mx53_ecspi_tx_target; spi_imx->target_burst = t->len; @@ -1641,8 +1641,7 @@ static int spi_imx_pio_transfer_target(struct spi_device *spi, struct spi_imx_data *spi_imx = spi_controller_get_devdata(spi->controller); int ret = 0; - if (is_imx53_ecspi(spi_imx) && - transfer->len > MX53_MAX_TRANSFER_BYTES) { + if (transfer->len > MX53_MAX_TRANSFER_BYTES) { dev_err(&spi->dev, "Transaction too big, max size is %d bytes\n", MX53_MAX_TRANSFER_BYTES); return -EMSGSIZE; From 1d562ba0aa7df81335bf96c02be77efe8d5bab87 Mon Sep 17 00:00:00 2001 From: Tomer Maimon Date: Wed, 12 Nov 2025 17:09:50 +0200 Subject: [PATCH 40/80] spi: dt-bindings: nuvoton,npcm-pspi: Convert to DT schema Convert the Nuvoton NPCM PSPI binding to DT schema format. Also update the binding to fix shortcoming: * Drop clock-frequency property: it is never read in the NPCM PSPI driver and has no effect. Signed-off-by: Tomer Maimon Reviewed-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20251112150950.1680154-1-tmaimon77@gmail.com Signed-off-by: Mark Brown --- .../bindings/spi/nuvoton,npcm-pspi.txt | 36 ---------- .../bindings/spi/nuvoton,npcm-pspi.yaml | 72 +++++++++++++++++++ 2 files changed, 72 insertions(+), 36 deletions(-) delete mode 100644 Documentation/devicetree/bindings/spi/nuvoton,npcm-pspi.txt create mode 100644 Documentation/devicetree/bindings/spi/nuvoton,npcm-pspi.yaml diff --git a/Documentation/devicetree/bindings/spi/nuvoton,npcm-pspi.txt b/Documentation/devicetree/bindings/spi/nuvoton,npcm-pspi.txt deleted file mode 100644 index a4e72e52af59..000000000000 --- a/Documentation/devicetree/bindings/spi/nuvoton,npcm-pspi.txt +++ /dev/null @@ -1,36 +0,0 @@ -Nuvoton NPCM Peripheral Serial Peripheral Interface(PSPI) controller driver - -Nuvoton NPCM7xx SOC support two PSPI channels. - -Required properties: - - compatible : "nuvoton,npcm750-pspi" for Poleg NPCM7XX. - "nuvoton,npcm845-pspi" for Arbel NPCM8XX. - - #address-cells : should be 1. see spi-bus.txt - - #size-cells : should be 0. see spi-bus.txt - - specifies physical base address and size of the register. - - interrupts : contain PSPI interrupt. - - clocks : phandle of PSPI reference clock. - - clock-names: Should be "clk_apb5". - - pinctrl-names : a pinctrl state named "default" must be defined. - - pinctrl-0 : phandle referencing pin configuration of the device. - - resets : phandle to the reset control for this device. - - cs-gpios: Specifies the gpio pins to be used for chipselects. - See: Documentation/devicetree/bindings/spi/spi-bus.txt - -Optional properties: -- clock-frequency : Input clock frequency to the PSPI block in Hz. - Default is 25000000 Hz. - -spi0: spi@f0200000 { - compatible = "nuvoton,npcm750-pspi"; - reg = <0xf0200000 0x1000>; - pinctrl-names = "default"; - pinctrl-0 = <&pspi1_pins>; - #address-cells = <1>; - #size-cells = <0>; - interrupts = ; - clocks = <&clk NPCM7XX_CLK_APB5>; - clock-names = "clk_apb5"; - resets = <&rstc NPCM7XX_RESET_IPSRST2 NPCM7XX_RESET_PSPI1> - cs-gpios = <&gpio6 11 GPIO_ACTIVE_LOW>; -}; diff --git a/Documentation/devicetree/bindings/spi/nuvoton,npcm-pspi.yaml b/Documentation/devicetree/bindings/spi/nuvoton,npcm-pspi.yaml new file mode 100644 index 000000000000..db0fb872020a --- /dev/null +++ b/Documentation/devicetree/bindings/spi/nuvoton,npcm-pspi.yaml @@ -0,0 +1,72 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/spi/nuvoton,npcm-pspi.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Nuvoton NPCM Peripheral SPI (PSPI) Controller + +maintainers: + - Tomer Maimon + +allOf: + - $ref: spi-controller.yaml# + +description: + Nuvoton NPCM Peripheral Serial Peripheral Interface (PSPI) controller. + Nuvoton NPCM7xx SOC supports two PSPI channels. + Nuvoton NPCM8xx SOC support one PSPI channel. + +properties: + compatible: + enum: + - nuvoton,npcm750-pspi # Poleg NPCM7XX + - nuvoton,npcm845-pspi # Arbel NPCM8XX + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + + clocks: + maxItems: 1 + description: PSPI reference clock. + + clock-names: + items: + - const: clk_apb5 + + resets: + maxItems: 1 + +required: + - compatible + - reg + - interrupts + - clocks + - clock-names + - resets + +unevaluatedProperties: false + +examples: + - | + #include + #include + #include + #include "dt-bindings/gpio/gpio.h" + spi0: spi@f0200000 { + compatible = "nuvoton,npcm750-pspi"; + reg = <0xf0200000 0x1000>; + pinctrl-names = "default"; + pinctrl-0 = <&pspi1_pins>; + #address-cells = <1>; + #size-cells = <0>; + interrupts = ; + clocks = <&clk NPCM7XX_CLK_APB5>; + clock-names = "clk_apb5"; + resets = <&rstc NPCM7XX_RESET_IPSRST2 NPCM7XX_RESET_PSPI1>; + cs-gpios = <&gpio6 11 GPIO_ACTIVE_LOW>; + }; + From 55b5d192bab5e152bda8f8cefe837c4ed0ec60c5 Mon Sep 17 00:00:00 2001 From: Jun Guo Date: Fri, 31 Oct 2025 15:30:01 +0800 Subject: [PATCH 41/80] dt-bindings: spi: spi-cadence: update DT binding docs to support cix sky1 SoC - Add new compatible strings to the DT binding documents to support cix sky1 SoC. Signed-off-by: Jun Guo Acked-by: Conor Dooley Link: https://patch.msgid.link/20251031073003.3289573-2-jun.guo@cixtech.com Signed-off-by: Mark Brown --- Documentation/devicetree/bindings/spi/spi-cadence.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/spi/spi-cadence.yaml b/Documentation/devicetree/bindings/spi/spi-cadence.yaml index 27414b78d61d..347bed0c4956 100644 --- a/Documentation/devicetree/bindings/spi/spi-cadence.yaml +++ b/Documentation/devicetree/bindings/spi/spi-cadence.yaml @@ -21,6 +21,7 @@ properties: - enum: - xlnx,zynqmp-spi-r1p6 - xlnx,versal-net-spi-r1p6 + - cix,sky1-spi-r1p6 - const: cdns,spi-r1p6 reg: From 4e00135b2dd1d7924a58bffa551b6ceb3bd836f2 Mon Sep 17 00:00:00 2001 From: Jun Guo Date: Fri, 31 Oct 2025 15:30:02 +0800 Subject: [PATCH 42/80] spi: spi-cadence: supports transmission with bits_per_word of 16 and 32 The default FIFO data width of the Cadence SPI IP is 8 bits, but the hardware supports configurations of 16 bits and 32 bits. This patch enhances the driver to support communication with both 16-bits and 32-bits FIFO data widths. Signed-off-by: Jun Guo Link: https://patch.msgid.link/20251031073003.3289573-3-jun.guo@cixtech.com Signed-off-by: Mark Brown --- drivers/spi/spi-cadence.c | 106 +++++++++++++++++++++++++++++++++----- 1 file changed, 93 insertions(+), 13 deletions(-) diff --git a/drivers/spi/spi-cadence.c b/drivers/spi/spi-cadence.c index 5ae09b21d23a..47054da630d0 100644 --- a/drivers/spi/spi-cadence.c +++ b/drivers/spi/spi-cadence.c @@ -109,6 +109,7 @@ * @rxbuf: Pointer to the RX buffer * @tx_bytes: Number of bytes left to transfer * @rx_bytes: Number of bytes requested + * @n_bytes: Number of bytes per word * @dev_busy: Device busy flag * @is_decoded_cs: Flag for decoder property set or not * @tx_fifo_depth: Depth of the TX FIFO @@ -120,16 +121,24 @@ struct cdns_spi { struct clk *pclk; unsigned int clk_rate; u32 speed_hz; - const u8 *txbuf; - u8 *rxbuf; + const void *txbuf; + void *rxbuf; int tx_bytes; int rx_bytes; + u8 n_bytes; u8 dev_busy; u32 is_decoded_cs; unsigned int tx_fifo_depth; struct reset_control *rstc; }; +enum cdns_spi_frame_n_bytes { + CDNS_SPI_N_BYTES_NULL = 0, + CDNS_SPI_N_BYTES_U8 = 1, + CDNS_SPI_N_BYTES_U16 = 2, + CDNS_SPI_N_BYTES_U32 = 4 +}; + /* Macros for the SPI controller read/write */ static inline u32 cdns_spi_read(struct cdns_spi *xspi, u32 offset) { @@ -305,6 +314,78 @@ static int cdns_spi_setup_transfer(struct spi_device *spi, return 0; } +static u8 cdns_spi_n_bytes(struct spi_transfer *transfer) +{ + if (transfer->bits_per_word <= 8) + return CDNS_SPI_N_BYTES_U8; + else if (transfer->bits_per_word <= 16) + return CDNS_SPI_N_BYTES_U16; + else + return CDNS_SPI_N_BYTES_U32; +} + +static inline void cdns_spi_reader(struct cdns_spi *xspi) +{ + u32 rxw = 0; + + if (xspi->rxbuf && !IS_ALIGNED((uintptr_t)xspi->rxbuf, xspi->n_bytes)) { + pr_err("%s: rxbuf address is not aligned for %d bytes\n", + __func__, xspi->n_bytes); + return; + } + + rxw = cdns_spi_read(xspi, CDNS_SPI_RXD); + if (xspi->rxbuf) { + switch (xspi->n_bytes) { + case CDNS_SPI_N_BYTES_U8: + *(u8 *)xspi->rxbuf = rxw; + break; + case CDNS_SPI_N_BYTES_U16: + *(u16 *)xspi->rxbuf = rxw; + break; + case CDNS_SPI_N_BYTES_U32: + *(u32 *)xspi->rxbuf = rxw; + break; + default: + pr_err("%s invalid n_bytes %d\n", __func__, + xspi->n_bytes); + return; + } + xspi->rxbuf = (u8 *)xspi->rxbuf + xspi->n_bytes; + } +} + +static inline void cdns_spi_writer(struct cdns_spi *xspi) +{ + u32 txw = 0; + + if (xspi->txbuf && !IS_ALIGNED((uintptr_t)xspi->txbuf, xspi->n_bytes)) { + pr_err("%s: txbuf address is not aligned for %d bytes\n", + __func__, xspi->n_bytes); + return; + } + + if (xspi->txbuf) { + switch (xspi->n_bytes) { + case CDNS_SPI_N_BYTES_U8: + txw = *(u8 *)xspi->txbuf; + break; + case CDNS_SPI_N_BYTES_U16: + txw = *(u16 *)xspi->txbuf; + break; + case CDNS_SPI_N_BYTES_U32: + txw = *(u32 *)xspi->txbuf; + break; + default: + pr_err("%s invalid n_bytes %d\n", __func__, + xspi->n_bytes); + return; + } + cdns_spi_write(xspi, CDNS_SPI_TXD, txw); + xspi->txbuf = (u8 *)xspi->txbuf + xspi->n_bytes; + } +} + /** * cdns_spi_process_fifo - Fills the TX FIFO, and drain the RX FIFO * @xspi: Pointer to the cdns_spi structure @@ -321,23 +402,14 @@ static void cdns_spi_process_fifo(struct cdns_spi *xspi, int ntx, int nrx) while (ntx || nrx) { if (nrx) { - u8 data = cdns_spi_read(xspi, CDNS_SPI_RXD); - - if (xspi->rxbuf) - *xspi->rxbuf++ = data; - + cdns_spi_reader(xspi); nrx--; } if (ntx) { - if (xspi->txbuf) - cdns_spi_write(xspi, CDNS_SPI_TXD, *xspi->txbuf++); - else - cdns_spi_write(xspi, CDNS_SPI_TXD, 0); - + cdns_spi_writer(xspi); ntx--; } - } } @@ -454,6 +526,10 @@ static int cdns_transfer_one(struct spi_controller *ctlr, if (cdns_spi_read(xspi, CDNS_SPI_ISR) & CDNS_SPI_IXR_TXFULL) udelay(10); + xspi->n_bytes = cdns_spi_n_bytes(transfer); + xspi->tx_bytes = DIV_ROUND_UP(xspi->tx_bytes, xspi->n_bytes); + xspi->rx_bytes = DIV_ROUND_UP(xspi->rx_bytes, xspi->n_bytes); + cdns_spi_process_fifo(xspi, xspi->tx_fifo_depth, 0); cdns_spi_write(xspi, CDNS_SPI_IER, CDNS_SPI_IXR_DEFAULT); @@ -654,6 +730,9 @@ static int cdns_spi_probe(struct platform_device *pdev) ctlr->mode_bits = SPI_CPOL | SPI_CPHA; ctlr->bits_per_word_mask = SPI_BPW_MASK(8); + if (of_device_is_compatible(pdev->dev.of_node, "cix,sky1-spi-r1p6")) + ctlr->bits_per_word_mask |= SPI_BPW_MASK(16) | SPI_BPW_MASK(32); + if (!spi_controller_is_target(ctlr)) { ctlr->mode_bits |= SPI_CS_HIGH; ctlr->set_cs = cdns_spi_chipselect; @@ -797,6 +876,7 @@ static const struct dev_pm_ops cdns_spi_dev_pm_ops = { static const struct of_device_id cdns_spi_of_match[] = { { .compatible = "xlnx,zynq-spi-r1p6" }, + { .compatible = "cix,sky1-spi-r1p6" }, { .compatible = "cdns,spi-r1p6" }, { /* end of table */ } }; From 71c814e98696f2cd53e9e6cef7501c2d667d4c5a Mon Sep 17 00:00:00 2001 From: Prajna Rajendra Kumar Date: Fri, 14 Nov 2025 10:45:43 +0000 Subject: [PATCH 43/80] spi: microchip: rename driver file and internal identifiers The spi-microchip-core.c driver provides support for the Microchip PolarFire SoC (MPFS) "hard" SPI controller. It was originally named "core" with the expectation that it might also cover Microchip's CoreSPI "soft" IP, but that never materialized. The CoreSPI IP cannot be supported by this driver because its register layout differs substantially from the MPFS SPI controller. In practice most of the code would need to be replaced to handle those differences so keeping the drivers separate is the simpler approach. The file and internal symbols are renamed to reflect MPFS support and to free up "spi-microchip-core.c" for CoreSPI driver. Fixes: 9ac8d17694b6 ("spi: add support for microchip fpga spi controllers") Signed-off-by: Prajna Rajendra Kumar Acked-by: Conor Dooley Link: https://patch.msgid.link/20251114104545.284765-2-prajna.rajendrakumar@microchip.com Signed-off-by: Mark Brown --- drivers/spi/Kconfig | 19 +- drivers/spi/Makefile | 2 +- .../spi/{spi-microchip-core.c => spi-mpfs.c} | 207 +++++++++--------- 3 files changed, 115 insertions(+), 113 deletions(-) rename drivers/spi/{spi-microchip-core.c => spi-mpfs.c} (68%) diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index 4d8f00c850c1..d53798036076 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -706,15 +706,6 @@ config SPI_MESON_SPIFC This enables master mode support for the SPIFC (SPI flash controller) available in Amlogic Meson SoCs. -config SPI_MICROCHIP_CORE - tristate "Microchip FPGA SPI controllers" - depends on SPI_MASTER - help - This enables the SPI driver for Microchip FPGA SPI controllers. - Say Y or M here if you want to use the "hard" controllers on - PolarFire SoC. - If built as a module, it will be called spi-microchip-core. - config SPI_MICROCHIP_CORE_QSPI tristate "Microchip FPGA QSPI controllers" depends on SPI_MASTER @@ -871,6 +862,16 @@ config SPI_PL022 controller. If you have an embedded system with an AMBA(R) bus and a PL022 controller, say Y or M here. +config SPI_POLARFIRE_SOC + + tristate "Microchip FPGA SPI controllers" + depends on SPI_MASTER && ARCH_MICROCHIP + help + This enables the SPI driver for Microchip FPGA SPI controllers. + Say Y or M here if you want to use the "hard" controllers on + PolarFire SoC. + If built as a module, it will be called spi-mpfs. + config SPI_PPC4xx tristate "PPC4xx SPI Controller" depends on PPC32 && 4xx diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile index 8ff74a13faaa..1f7c06a3091d 100644 --- a/drivers/spi/Makefile +++ b/drivers/spi/Makefile @@ -86,7 +86,6 @@ obj-$(CONFIG_SPI_LOONGSON_PLATFORM) += spi-loongson-plat.o obj-$(CONFIG_SPI_LP8841_RTC) += spi-lp8841-rtc.o obj-$(CONFIG_SPI_MESON_SPICC) += spi-meson-spicc.o obj-$(CONFIG_SPI_MESON_SPIFC) += spi-meson-spifc.o -obj-$(CONFIG_SPI_MICROCHIP_CORE) += spi-microchip-core.o obj-$(CONFIG_SPI_MICROCHIP_CORE_QSPI) += spi-microchip-core-qspi.o obj-$(CONFIG_SPI_MPC512x_PSC) += spi-mpc512x-psc.o obj-$(CONFIG_SPI_MPC52xx_PSC) += spi-mpc52xx-psc.o @@ -97,6 +96,7 @@ obj-$(CONFIG_SPI_MTK_NOR) += spi-mtk-nor.o obj-$(CONFIG_SPI_MTK_SNFI) += spi-mtk-snfi.o obj-$(CONFIG_SPI_MXIC) += spi-mxic.o obj-$(CONFIG_SPI_MXS) += spi-mxs.o +obj-$(CONFIG_SPI_POLARFIRE_SOC) += spi-mpfs.o obj-$(CONFIG_SPI_WPCM_FIU) += spi-wpcm-fiu.o obj-$(CONFIG_SPI_NPCM_FIU) += spi-npcm-fiu.o obj-$(CONFIG_SPI_NPCM_PSPI) += spi-npcm-pspi.o diff --git a/drivers/spi/spi-microchip-core.c b/drivers/spi/spi-mpfs.c similarity index 68% rename from drivers/spi/spi-microchip-core.c rename to drivers/spi/spi-mpfs.c index 9128b86c5366..9a14d1732a15 100644 --- a/drivers/spi/spi-microchip-core.c +++ b/drivers/spi/spi-mpfs.c @@ -99,7 +99,7 @@ #define REG_CTRL2 (0x48) #define REG_FRAMESUP (0x50) -struct mchp_corespi { +struct mpfs_spi { void __iomem *regs; struct clk *clk; const u8 *tx_buf; @@ -113,34 +113,34 @@ struct mchp_corespi { int n_bytes; }; -static inline u32 mchp_corespi_read(struct mchp_corespi *spi, unsigned int reg) +static inline u32 mpfs_spi_read(struct mpfs_spi *spi, unsigned int reg) { return readl(spi->regs + reg); } -static inline void mchp_corespi_write(struct mchp_corespi *spi, unsigned int reg, u32 val) +static inline void mpfs_spi_write(struct mpfs_spi *spi, unsigned int reg, u32 val) { writel(val, spi->regs + reg); } -static inline void mchp_corespi_disable(struct mchp_corespi *spi) +static inline void mpfs_spi_disable(struct mpfs_spi *spi) { - u32 control = mchp_corespi_read(spi, REG_CONTROL); + u32 control = mpfs_spi_read(spi, REG_CONTROL); control &= ~CONTROL_ENABLE; - mchp_corespi_write(spi, REG_CONTROL, control); + mpfs_spi_write(spi, REG_CONTROL, control); } -static inline void mchp_corespi_read_fifo(struct mchp_corespi *spi, int fifo_max) +static inline void mpfs_spi_read_fifo(struct mpfs_spi *spi, int fifo_max) { for (int i = 0; i < fifo_max; i++) { u32 data; - while (mchp_corespi_read(spi, REG_STATUS) & STATUS_RXFIFO_EMPTY) + while (mpfs_spi_read(spi, REG_STATUS) & STATUS_RXFIFO_EMPTY) ; - data = mchp_corespi_read(spi, REG_RX_DATA); + data = mpfs_spi_read(spi, REG_RX_DATA); spi->rx_len -= spi->n_bytes; @@ -158,34 +158,34 @@ static inline void mchp_corespi_read_fifo(struct mchp_corespi *spi, int fifo_max } } -static void mchp_corespi_enable_ints(struct mchp_corespi *spi) +static void mpfs_spi_enable_ints(struct mpfs_spi *spi) { - u32 control = mchp_corespi_read(spi, REG_CONTROL); + u32 control = mpfs_spi_read(spi, REG_CONTROL); control |= INT_ENABLE_MASK; - mchp_corespi_write(spi, REG_CONTROL, control); + mpfs_spi_write(spi, REG_CONTROL, control); } -static void mchp_corespi_disable_ints(struct mchp_corespi *spi) +static void mpfs_spi_disable_ints(struct mpfs_spi *spi) { - u32 control = mchp_corespi_read(spi, REG_CONTROL); + u32 control = mpfs_spi_read(spi, REG_CONTROL); control &= ~INT_ENABLE_MASK; - mchp_corespi_write(spi, REG_CONTROL, control); + mpfs_spi_write(spi, REG_CONTROL, control); } -static inline void mchp_corespi_set_xfer_size(struct mchp_corespi *spi, int len) +static inline void mpfs_spi_set_xfer_size(struct mpfs_spi *spi, int len) { u32 control; u32 lenpart; - u32 frames = mchp_corespi_read(spi, REG_FRAMESUP); + u32 frames = mpfs_spi_read(spi, REG_FRAMESUP); /* * Writing to FRAMECNT in REG_CONTROL will reset the frame count, taking * a shortcut requires an explicit clear. */ if (frames == len) { - mchp_corespi_write(spi, REG_COMMAND, COMMAND_CLRFRAMECNT); + mpfs_spi_write(spi, REG_COMMAND, COMMAND_CLRFRAMECNT); return; } @@ -208,20 +208,20 @@ static inline void mchp_corespi_set_xfer_size(struct mchp_corespi *spi, int len) * that matches the documentation. */ lenpart = len & 0xffff; - control = mchp_corespi_read(spi, REG_CONTROL); + control = mpfs_spi_read(spi, REG_CONTROL); control &= ~CONTROL_FRAMECNT_MASK; control |= lenpart << CONTROL_FRAMECNT_SHIFT; - mchp_corespi_write(spi, REG_CONTROL, control); - mchp_corespi_write(spi, REG_FRAMESUP, len); + mpfs_spi_write(spi, REG_CONTROL, control); + mpfs_spi_write(spi, REG_FRAMESUP, len); } -static inline void mchp_corespi_write_fifo(struct mchp_corespi *spi, int fifo_max) +static inline void mpfs_spi_write_fifo(struct mpfs_spi *spi, int fifo_max) { int i = 0; - mchp_corespi_set_xfer_size(spi, fifo_max); + mpfs_spi_set_xfer_size(spi, fifo_max); - while ((i < fifo_max) && !(mchp_corespi_read(spi, REG_STATUS) & STATUS_TXFIFO_FULL)) { + while ((i < fifo_max) && !(mpfs_spi_read(spi, REG_STATUS) & STATUS_TXFIFO_FULL)) { u32 word; if (spi->n_bytes == 4) @@ -231,7 +231,7 @@ static inline void mchp_corespi_write_fifo(struct mchp_corespi *spi, int fifo_ma else word = spi->tx_buf ? *spi->tx_buf : 0xaa; - mchp_corespi_write(spi, REG_TX_DATA, word); + mpfs_spi_write(spi, REG_TX_DATA, word); if (spi->tx_buf) spi->tx_buf += spi->n_bytes; i++; @@ -240,9 +240,9 @@ static inline void mchp_corespi_write_fifo(struct mchp_corespi *spi, int fifo_ma spi->tx_len -= i * spi->n_bytes; } -static inline void mchp_corespi_set_framesize(struct mchp_corespi *spi, int bt) +static inline void mpfs_spi_set_framesize(struct mpfs_spi *spi, int bt) { - u32 frame_size = mchp_corespi_read(spi, REG_FRAME_SIZE); + u32 frame_size = mpfs_spi_read(spi, REG_FRAME_SIZE); u32 control; if ((frame_size & FRAME_SIZE_MASK) == bt) @@ -252,25 +252,25 @@ static inline void mchp_corespi_set_framesize(struct mchp_corespi *spi, int bt) * Disable the SPI controller. Writes to the frame size have * no effect when the controller is enabled. */ - control = mchp_corespi_read(spi, REG_CONTROL); + control = mpfs_spi_read(spi, REG_CONTROL); control &= ~CONTROL_ENABLE; - mchp_corespi_write(spi, REG_CONTROL, control); + mpfs_spi_write(spi, REG_CONTROL, control); - mchp_corespi_write(spi, REG_FRAME_SIZE, bt); + mpfs_spi_write(spi, REG_FRAME_SIZE, bt); control |= CONTROL_ENABLE; - mchp_corespi_write(spi, REG_CONTROL, control); + mpfs_spi_write(spi, REG_CONTROL, control); } -static void mchp_corespi_set_cs(struct spi_device *spi, bool disable) +static void mpfs_spi_set_cs(struct spi_device *spi, bool disable) { u32 reg; - struct mchp_corespi *corespi = spi_controller_get_devdata(spi->controller); + struct mpfs_spi *mspi = spi_controller_get_devdata(spi->controller); - reg = mchp_corespi_read(corespi, REG_SLAVE_SELECT); + reg = mpfs_spi_read(mspi, REG_SLAVE_SELECT); reg &= ~BIT(spi_get_chipselect(spi, 0)); reg |= !disable << spi_get_chipselect(spi, 0); - corespi->pending_slave_select = reg; + mspi->pending_slave_select = reg; /* * Only deassert chip select immediately. Writing to some registers @@ -281,12 +281,12 @@ static void mchp_corespi_set_cs(struct spi_device *spi, bool disable) * doesn't see any spurious clock transitions whilst CS is enabled. */ if (((spi->mode & SPI_CS_HIGH) == 0) == disable) - mchp_corespi_write(corespi, REG_SLAVE_SELECT, reg); + mpfs_spi_write(mspi, REG_SLAVE_SELECT, reg); } -static int mchp_corespi_setup(struct spi_device *spi) +static int mpfs_spi_setup(struct spi_device *spi) { - struct mchp_corespi *corespi = spi_controller_get_devdata(spi->controller); + struct mpfs_spi *mspi = spi_controller_get_devdata(spi->controller); u32 reg; if (spi_is_csgpiod(spi)) @@ -298,21 +298,21 @@ static int mchp_corespi_setup(struct spi_device *spi) * driving their select line low. */ if (spi->mode & SPI_CS_HIGH) { - reg = mchp_corespi_read(corespi, REG_SLAVE_SELECT); + reg = mpfs_spi_read(mspi, REG_SLAVE_SELECT); reg |= BIT(spi_get_chipselect(spi, 0)); - corespi->pending_slave_select = reg; - mchp_corespi_write(corespi, REG_SLAVE_SELECT, reg); + mspi->pending_slave_select = reg; + mpfs_spi_write(mspi, REG_SLAVE_SELECT, reg); } return 0; } -static void mchp_corespi_init(struct spi_controller *host, struct mchp_corespi *spi) +static void mpfs_spi_init(struct spi_controller *host, struct mpfs_spi *spi) { unsigned long clk_hz; - u32 control = mchp_corespi_read(spi, REG_CONTROL); + u32 control = mpfs_spi_read(spi, REG_CONTROL); control &= ~CONTROL_ENABLE; - mchp_corespi_write(spi, REG_CONTROL, control); + mpfs_spi_write(spi, REG_CONTROL, control); control |= CONTROL_MASTER; control &= ~CONTROL_MODE_MASK; @@ -328,15 +328,15 @@ static void mchp_corespi_init(struct spi_controller *host, struct mchp_corespi * */ control |= CONTROL_SPS | CONTROL_BIGFIFO; - mchp_corespi_write(spi, REG_CONTROL, control); + mpfs_spi_write(spi, REG_CONTROL, control); - mchp_corespi_set_framesize(spi, DEFAULT_FRAMESIZE); + mpfs_spi_set_framesize(spi, DEFAULT_FRAMESIZE); /* max. possible spi clock rate is the apb clock rate */ clk_hz = clk_get_rate(spi->clk); host->max_speed_hz = clk_hz; - mchp_corespi_enable_ints(spi); + mpfs_spi_enable_ints(spi); /* * It is required to enable direct mode, otherwise control over the chip @@ -344,34 +344,34 @@ static void mchp_corespi_init(struct spi_controller *host, struct mchp_corespi * * can deal with active high targets. */ spi->pending_slave_select = SSELOUT | SSEL_DIRECT; - mchp_corespi_write(spi, REG_SLAVE_SELECT, spi->pending_slave_select); + mpfs_spi_write(spi, REG_SLAVE_SELECT, spi->pending_slave_select); - control = mchp_corespi_read(spi, REG_CONTROL); + control = mpfs_spi_read(spi, REG_CONTROL); control &= ~CONTROL_RESET; control |= CONTROL_ENABLE; - mchp_corespi_write(spi, REG_CONTROL, control); + mpfs_spi_write(spi, REG_CONTROL, control); } -static inline void mchp_corespi_set_clk_gen(struct mchp_corespi *spi) +static inline void mpfs_spi_set_clk_gen(struct mpfs_spi *spi) { u32 control; - control = mchp_corespi_read(spi, REG_CONTROL); + control = mpfs_spi_read(spi, REG_CONTROL); if (spi->clk_mode) control |= CONTROL_CLKMODE; else control &= ~CONTROL_CLKMODE; - mchp_corespi_write(spi, REG_CLK_GEN, spi->clk_gen); - mchp_corespi_write(spi, REG_CONTROL, control); + mpfs_spi_write(spi, REG_CLK_GEN, spi->clk_gen); + mpfs_spi_write(spi, REG_CONTROL, control); } -static inline void mchp_corespi_set_mode(struct mchp_corespi *spi, unsigned int mode) +static inline void mpfs_spi_set_mode(struct mpfs_spi *spi, unsigned int mode) { u32 mode_val; - u32 control = mchp_corespi_read(spi, REG_CONTROL); + u32 control = mpfs_spi_read(spi, REG_CONTROL); switch (mode & SPI_MODE_X_MASK) { case SPI_MODE_0: @@ -394,22 +394,22 @@ static inline void mchp_corespi_set_mode(struct mchp_corespi *spi, unsigned int */ control &= ~CONTROL_ENABLE; - mchp_corespi_write(spi, REG_CONTROL, control); + mpfs_spi_write(spi, REG_CONTROL, control); control &= ~(SPI_MODE_X_MASK << MODE_X_MASK_SHIFT); control |= mode_val; - mchp_corespi_write(spi, REG_CONTROL, control); + mpfs_spi_write(spi, REG_CONTROL, control); control |= CONTROL_ENABLE; - mchp_corespi_write(spi, REG_CONTROL, control); + mpfs_spi_write(spi, REG_CONTROL, control); } -static irqreturn_t mchp_corespi_interrupt(int irq, void *dev_id) +static irqreturn_t mpfs_spi_interrupt(int irq, void *dev_id) { struct spi_controller *host = dev_id; - struct mchp_corespi *spi = spi_controller_get_devdata(host); - u32 intfield = mchp_corespi_read(spi, REG_MIS) & 0xf; + struct mpfs_spi *spi = spi_controller_get_devdata(host); + u32 intfield = mpfs_spi_read(spi, REG_MIS) & 0xf; bool finalise = false; /* Interrupt line may be shared and not for us at all */ @@ -417,7 +417,7 @@ static irqreturn_t mchp_corespi_interrupt(int irq, void *dev_id) return IRQ_NONE; if (intfield & INT_RX_CHANNEL_OVERFLOW) { - mchp_corespi_write(spi, REG_INT_CLEAR, INT_RX_CHANNEL_OVERFLOW); + mpfs_spi_write(spi, REG_INT_CLEAR, INT_RX_CHANNEL_OVERFLOW); finalise = true; dev_err(&host->dev, "%s: RX OVERFLOW: rxlen: %d, txlen: %d\n", __func__, @@ -425,7 +425,7 @@ static irqreturn_t mchp_corespi_interrupt(int irq, void *dev_id) } if (intfield & INT_TX_CHANNEL_UNDERRUN) { - mchp_corespi_write(spi, REG_INT_CLEAR, INT_TX_CHANNEL_UNDERRUN); + mpfs_spi_write(spi, REG_INT_CLEAR, INT_TX_CHANNEL_UNDERRUN); finalise = true; dev_err(&host->dev, "%s: TX UNDERFLOW: rxlen: %d, txlen: %d\n", __func__, @@ -438,8 +438,8 @@ static irqreturn_t mchp_corespi_interrupt(int irq, void *dev_id) return IRQ_HANDLED; } -static int mchp_corespi_calculate_clkgen(struct mchp_corespi *spi, - unsigned long target_hz) +static int mpfs_spi_calculate_clkgen(struct mpfs_spi *spi, + unsigned long target_hz) { unsigned long clk_hz, spi_hz, clk_gen; @@ -475,20 +475,20 @@ static int mchp_corespi_calculate_clkgen(struct mchp_corespi *spi, return 0; } -static int mchp_corespi_transfer_one(struct spi_controller *host, - struct spi_device *spi_dev, - struct spi_transfer *xfer) +static int mpfs_spi_transfer_one(struct spi_controller *host, + struct spi_device *spi_dev, + struct spi_transfer *xfer) { - struct mchp_corespi *spi = spi_controller_get_devdata(host); + struct mpfs_spi *spi = spi_controller_get_devdata(host); int ret; - ret = mchp_corespi_calculate_clkgen(spi, (unsigned long)xfer->speed_hz); + ret = mpfs_spi_calculate_clkgen(spi, (unsigned long)xfer->speed_hz); if (ret) { dev_err(&host->dev, "failed to set clk_gen for target %u Hz\n", xfer->speed_hz); return ret; } - mchp_corespi_set_clk_gen(spi); + mpfs_spi_set_clk_gen(spi); spi->tx_buf = xfer->tx_buf; spi->rx_buf = xfer->rx_buf; @@ -496,45 +496,46 @@ static int mchp_corespi_transfer_one(struct spi_controller *host, spi->rx_len = xfer->len; spi->n_bytes = roundup_pow_of_two(DIV_ROUND_UP(xfer->bits_per_word, BITS_PER_BYTE)); - mchp_corespi_set_framesize(spi, xfer->bits_per_word); + mpfs_spi_set_framesize(spi, xfer->bits_per_word); - mchp_corespi_write(spi, REG_COMMAND, COMMAND_RXFIFORST | COMMAND_TXFIFORST); + mpfs_spi_write(spi, REG_COMMAND, COMMAND_RXFIFORST | COMMAND_TXFIFORST); - mchp_corespi_write(spi, REG_SLAVE_SELECT, spi->pending_slave_select); + mpfs_spi_write(spi, REG_SLAVE_SELECT, spi->pending_slave_select); while (spi->tx_len) { int fifo_max = DIV_ROUND_UP(min(spi->tx_len, FIFO_DEPTH), spi->n_bytes); - mchp_corespi_write_fifo(spi, fifo_max); - mchp_corespi_read_fifo(spi, fifo_max); + mpfs_spi_write_fifo(spi, fifo_max); + mpfs_spi_read_fifo(spi, fifo_max); } spi_finalize_current_transfer(host); return 1; } -static int mchp_corespi_prepare_message(struct spi_controller *host, - struct spi_message *msg) +static int mpfs_spi_prepare_message(struct spi_controller *host, + struct spi_message *msg) { struct spi_device *spi_dev = msg->spi; - struct mchp_corespi *spi = spi_controller_get_devdata(host); + struct mpfs_spi *spi = spi_controller_get_devdata(host); - mchp_corespi_set_mode(spi, spi_dev->mode); + mpfs_spi_set_mode(spi, spi_dev->mode); return 0; } -static int mchp_corespi_probe(struct platform_device *pdev) +static int mpfs_spi_probe(struct platform_device *pdev) { struct spi_controller *host; - struct mchp_corespi *spi; + struct mpfs_spi *spi; struct resource *res; u32 num_cs; int ret = 0; host = devm_spi_alloc_host(&pdev->dev, sizeof(*spi)); if (!host) - return -ENOMEM; + return dev_err_probe(&pdev->dev, -ENOMEM, + "unable to allocate host for SPI controller\n"); platform_set_drvdata(pdev, host); @@ -544,11 +545,11 @@ static int mchp_corespi_probe(struct platform_device *pdev) host->num_chipselect = num_cs; host->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH; host->use_gpio_descriptors = true; - host->setup = mchp_corespi_setup; + host->setup = mpfs_spi_setup; host->bits_per_word_mask = SPI_BPW_RANGE_MASK(1, 32); - host->transfer_one = mchp_corespi_transfer_one; - host->prepare_message = mchp_corespi_prepare_message; - host->set_cs = mchp_corespi_set_cs; + host->transfer_one = mpfs_spi_transfer_one; + host->prepare_message = mpfs_spi_prepare_message; + host->set_cs = mpfs_spi_set_cs; host->dev.of_node = pdev->dev.of_node; spi = spi_controller_get_devdata(host); @@ -561,7 +562,7 @@ static int mchp_corespi_probe(struct platform_device *pdev) if (spi->irq < 0) return spi->irq; - ret = devm_request_irq(&pdev->dev, spi->irq, mchp_corespi_interrupt, + ret = devm_request_irq(&pdev->dev, spi->irq, mpfs_spi_interrupt, IRQF_SHARED, dev_name(&pdev->dev), host); if (ret) return dev_err_probe(&pdev->dev, ret, @@ -572,11 +573,11 @@ static int mchp_corespi_probe(struct platform_device *pdev) return dev_err_probe(&pdev->dev, PTR_ERR(spi->clk), "could not get clk\n"); - mchp_corespi_init(host, spi); + mpfs_spi_init(host, spi); ret = devm_spi_register_controller(&pdev->dev, host); if (ret) { - mchp_corespi_disable(spi); + mpfs_spi_disable(spi); return dev_err_probe(&pdev->dev, ret, "unable to register host for SPI controller\n"); } @@ -586,13 +587,13 @@ static int mchp_corespi_probe(struct platform_device *pdev) return 0; } -static void mchp_corespi_remove(struct platform_device *pdev) +static void mpfs_spi_remove(struct platform_device *pdev) { struct spi_controller *host = platform_get_drvdata(pdev); - struct mchp_corespi *spi = spi_controller_get_devdata(host); + struct mpfs_spi *spi = spi_controller_get_devdata(host); - mchp_corespi_disable_ints(spi); - mchp_corespi_disable(spi); + mpfs_spi_disable_ints(spi); + mpfs_spi_disable(spi); } #define MICROCHIP_SPI_PM_OPS (NULL) @@ -602,23 +603,23 @@ static void mchp_corespi_remove(struct platform_device *pdev) */ #if defined(CONFIG_OF) -static const struct of_device_id mchp_corespi_dt_ids[] = { +static const struct of_device_id mpfs_spi_dt_ids[] = { { .compatible = "microchip,mpfs-spi" }, { /* sentinel */ } }; -MODULE_DEVICE_TABLE(of, mchp_corespi_dt_ids); +MODULE_DEVICE_TABLE(of, mpfs_spi_dt_ids); #endif -static struct platform_driver mchp_corespi_driver = { - .probe = mchp_corespi_probe, +static struct platform_driver mpfs_spi_driver = { + .probe = mpfs_spi_probe, .driver = { - .name = "microchip-corespi", + .name = "microchip-spi", .pm = MICROCHIP_SPI_PM_OPS, - .of_match_table = of_match_ptr(mchp_corespi_dt_ids), + .of_match_table = of_match_ptr(mpfs_spi_dt_ids), }, - .remove = mchp_corespi_remove, + .remove = mpfs_spi_remove, }; -module_platform_driver(mchp_corespi_driver); +module_platform_driver(mpfs_spi_driver); MODULE_DESCRIPTION("Microchip coreSPI SPI controller driver"); MODULE_AUTHOR("Daire McNamara "); MODULE_AUTHOR("Conor Dooley "); From 8ce9a2ed153bcaa750aa494e91ce2e70c3b0cdc5 Mon Sep 17 00:00:00 2001 From: Prajna Rajendra Kumar Date: Fri, 14 Nov 2025 10:45:44 +0000 Subject: [PATCH 44/80] spi: dt-binding: document Microchip CoreSPI Add device tree bindings for Microchip's CoreSPI controller. CoreSPI is a "soft" IP core intended for FPGA implementations. Its configurations are set in Libero. These properties represent non-discoverable configurations determined by Verilog parameters to the IP. Signed-off-by: Prajna Rajendra Kumar Reviewed-by: Conor Dooley Link: https://patch.msgid.link/20251114104545.284765-3-prajna.rajendrakumar@microchip.com Signed-off-by: Mark Brown --- .../bindings/spi/microchip,mpfs-spi.yaml | 70 ++++++++++++++++++- 1 file changed, 68 insertions(+), 2 deletions(-) diff --git a/Documentation/devicetree/bindings/spi/microchip,mpfs-spi.yaml b/Documentation/devicetree/bindings/spi/microchip,mpfs-spi.yaml index 62a568bdbfa0..636338d24bdf 100644 --- a/Documentation/devicetree/bindings/spi/microchip,mpfs-spi.yaml +++ b/Documentation/devicetree/bindings/spi/microchip,mpfs-spi.yaml @@ -21,11 +21,13 @@ properties: - microchip,mpfs-qspi - microchip,pic64gx-qspi - const: microchip,coreqspi-rtl-v2 - - const: microchip,coreqspi-rtl-v2 # FPGA QSPI + - enum: + - microchip,coreqspi-rtl-v2 # FPGA QSPI + - microchip,corespi-rtl-v5 # FPGA CoreSPI + - microchip,mpfs-spi - items: - const: microchip,pic64gx-spi - const: microchip,mpfs-spi - - const: microchip,mpfs-spi reg: maxItems: 1 @@ -39,6 +41,45 @@ properties: clocks: maxItems: 1 + microchip,apb-datawidth: + description: APB bus data width in bits. + $ref: /schemas/types.yaml#/definitions/uint32 + enum: [8, 16, 32] + default: 8 + + microchip,frame-size: + description: | + Number of bits per SPI frame, as configured in Libero. + In Motorola and TI modes, this corresponds directly + to the requested frame size. For NSC mode this is set + to 9 + the required data frame size. + $ref: /schemas/types.yaml#/definitions/uint32 + minimum: 4 + maximum: 32 + default: 8 + + microchip,protocol-configuration: + description: CoreSPI protocol selection. Determines operating mode + $ref: /schemas/types.yaml#/definitions/string + enum: + - motorola + - ti + - nsc + default: motorola + + microchip,motorola-mode: + description: Motorola SPI mode selection + $ref: /schemas/types.yaml#/definitions/uint32 + enum: [0, 1, 2, 3] + default: 3 + + microchip,ssel-active: + description: | + Keep SSEL asserted between frames when using the Motorola protocol. + When present, the controller keeps SSEL active across contiguous + transfers and deasserts only when the overall transfer completes. + type: boolean + required: - compatible - reg @@ -71,6 +112,31 @@ allOf: num-cs: maximum: 1 + - if: + properties: + compatible: + contains: + const: microchip,corespi-rtl-v5 + then: + properties: + num-cs: + minimum: 1 + maximum: 8 + default: 8 + + fifo-depth: + minimum: 1 + maximum: 32 + default: 4 + + else: + properties: + microchip,apb-datawidth: false + microchip,frame-size: false + microchip,protocol-configuration: false + microchip,motorola-mode: false + microchip,ssel-active: false + unevaluatedProperties: false examples: From 059f545832be85d29ac9ccc416a16f647aa78485 Mon Sep 17 00:00:00 2001 From: Prajna Rajendra Kumar Date: Fri, 14 Nov 2025 10:45:45 +0000 Subject: [PATCH 45/80] spi: add support for microchip "soft" spi controller Introduce driver support for the Microchip FPGA CoreSPI IP. This driver supports only Motorola SPI mode and frame size of 8-bits. TI/NSC modes and wider frame sizes are not currently supported. Signed-off-by: Prajna Rajendra Kumar Reviewed-by: Conor Dooley Acked-by: Conor Dooley Link: https://patch.msgid.link/20251114104545.284765-4-prajna.rajendrakumar@microchip.com Signed-off-by: Mark Brown --- drivers/spi/Kconfig | 9 + drivers/spi/Makefile | 1 + drivers/spi/spi-microchip-core-spi.c | 442 +++++++++++++++++++++++++++ 3 files changed, 452 insertions(+) create mode 100644 drivers/spi/spi-microchip-core-spi.c diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index d53798036076..96806806d48b 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -715,6 +715,15 @@ config SPI_MICROCHIP_CORE_QSPI PolarFire SoC. If built as a module, it will be called spi-microchip-core-qspi. +config SPI_MICROCHIP_CORE_SPI + tristate "Microchip FPGA CoreSPI controller" + depends on SPI_MASTER + help + This enables the SPI driver for Microchip FPGA CoreSPI controller. + Say Y or M here if you want to use the "soft" controllers on + PolarFire SoC. + If built as a module, it will be called spi-microchip-core-spi. + config SPI_MT65XX tristate "MediaTek SPI controller" depends on ARCH_MEDIATEK || COMPILE_TEST diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile index 1f7c06a3091d..863b628ff1ec 100644 --- a/drivers/spi/Makefile +++ b/drivers/spi/Makefile @@ -87,6 +87,7 @@ obj-$(CONFIG_SPI_LP8841_RTC) += spi-lp8841-rtc.o obj-$(CONFIG_SPI_MESON_SPICC) += spi-meson-spicc.o obj-$(CONFIG_SPI_MESON_SPIFC) += spi-meson-spifc.o obj-$(CONFIG_SPI_MICROCHIP_CORE_QSPI) += spi-microchip-core-qspi.o +obj-$(CONFIG_SPI_MICROCHIP_CORE_SPI) += spi-microchip-core-spi.o obj-$(CONFIG_SPI_MPC512x_PSC) += spi-mpc512x-psc.o obj-$(CONFIG_SPI_MPC52xx_PSC) += spi-mpc52xx-psc.o obj-$(CONFIG_SPI_MPC52xx) += spi-mpc52xx.o diff --git a/drivers/spi/spi-microchip-core-spi.c b/drivers/spi/spi-microchip-core-spi.c new file mode 100644 index 000000000000..b8738190cdcb --- /dev/null +++ b/drivers/spi/spi-microchip-core-spi.c @@ -0,0 +1,442 @@ +// SPDX-License-Identifier: (GPL-2.0) +// +// Microchip CoreSPI controller driver +// +// Copyright (c) 2025 Microchip Technology Inc. and its subsidiaries +// +// Author: Prajna Rajendra Kumar + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MCHP_CORESPI_MAX_CS (8) +#define MCHP_CORESPI_DEFAULT_FIFO_DEPTH (4) +#define MCHP_CORESPI_DEFAULT_MOTOROLA_MODE (3) + +#define MCHP_CORESPI_CONTROL_ENABLE BIT(0) +#define MCHP_CORESPI_CONTROL_MASTER BIT(1) +#define MCHP_CORESPI_CONTROL_TX_DATA_INT BIT(3) +#define MCHP_CORESPI_CONTROL_RX_OVER_INT BIT(4) +#define MCHP_CORESPI_CONTROL_TX_UNDER_INT BIT(5) +#define MCHP_CORESPI_CONTROL_FRAMEURUN BIT(6) +#define MCHP_CORESPI_CONTROL_OENOFF BIT(7) + +#define MCHP_CORESPI_STATUS_ACTIVE BIT(7) +#define MCHP_CORESPI_STATUS_SSEL BIT(6) +#define MCHP_CORESPI_STATUS_TXFIFO_UNDERFLOW BIT(5) +#define MCHP_CORESPI_STATUS_RXFIFO_FULL BIT(4) +#define MCHP_CORESPI_STATUS_TXFIFO_FULL BIT(3) +#define MCHP_CORESPI_STATUS_RXFIFO_EMPTY BIT(2) +#define MCHP_CORESPI_STATUS_DONE BIT(1) +#define MCHP_CORESPI_STATUS_FIRSTFRAME BIT(0) + +#define MCHP_CORESPI_INT_TXDONE BIT(0) +#define MCHP_CORESPI_INT_RX_CHANNEL_OVERFLOW BIT(2) +#define MCHP_CORESPI_INT_TX_CHANNEL_UNDERRUN BIT(3) +#define MCHP_CORESPI_INT_CMDINT BIT(4) +#define MCHP_CORESPI_INT_SSEND BIT(5) +#define MCHP_CORESPI_INT_DATA_RX BIT(6) +#define MCHP_CORESPI_INT_TXRFM BIT(7) + +#define MCHP_CORESPI_CONTROL2_INTEN_TXRFMT BIT(7) +#define MCHP_CORESPI_CONTROL2_INTEN_DATA_RX BIT(6) +#define MCHP_CORESPI_CONTROL2_INTEN_SSEND BIT(5) +#define MCHP_CORESPI_CONTROL2_INTEN_CMD BIT(4) + +#define INT_ENABLE_MASK (MCHP_CORESPI_CONTROL_TX_DATA_INT | MCHP_CORESPI_CONTROL_RX_OVER_INT | \ + MCHP_CORESPI_CONTROL_TX_UNDER_INT) + +#define MCHP_CORESPI_REG_CONTROL (0x00) +#define MCHP_CORESPI_REG_INTCLEAR (0x04) +#define MCHP_CORESPI_REG_RXDATA (0x08) +#define MCHP_CORESPI_REG_TXDATA (0x0c) +#define MCHP_CORESPI_REG_INTMASK (0X10) +#define MCHP_CORESPI_REG_INTRAW (0X14) +#define MCHP_CORESPI_REG_CONTROL2 (0x18) +#define MCHP_CORESPI_REG_COMMAND (0x1c) +#define MCHP_CORESPI_REG_STAT (0x20) +#define MCHP_CORESPI_REG_SSEL (0x24) +#define MCHP_CORESPI_REG_TXDATA_LAST (0X28) +#define MCHP_CORESPI_REG_CLK_DIV (0x2c) + +struct mchp_corespi { + void __iomem *regs; + struct clk *clk; + const u8 *tx_buf; + u8 *rx_buf; + u32 clk_gen; + int irq; + int tx_len; + int rx_len; + u32 fifo_depth; +}; + +static inline void mchp_corespi_disable(struct mchp_corespi *spi) +{ + u8 control = readb(spi->regs + MCHP_CORESPI_REG_CONTROL); + + control &= ~MCHP_CORESPI_CONTROL_ENABLE; + + writeb(control, spi->regs + MCHP_CORESPI_REG_CONTROL); +} + +static inline void mchp_corespi_read_fifo(struct mchp_corespi *spi, u32 fifo_max) +{ + for (int i = 0; i < fifo_max; i++) { + u32 data; + + while (readb(spi->regs + MCHP_CORESPI_REG_STAT) & + MCHP_CORESPI_STATUS_RXFIFO_EMPTY) + ; + + data = readb(spi->regs + MCHP_CORESPI_REG_RXDATA); + + spi->rx_len--; + if (!spi->rx_buf) + continue; + + *spi->rx_buf = data; + + spi->rx_buf++; + } +} + +static void mchp_corespi_enable_ints(struct mchp_corespi *spi) +{ + u8 control = readb(spi->regs + MCHP_CORESPI_REG_CONTROL); + + control |= INT_ENABLE_MASK; + writeb(control, spi->regs + MCHP_CORESPI_REG_CONTROL); +} + +static void mchp_corespi_disable_ints(struct mchp_corespi *spi) +{ + u8 control = readb(spi->regs + MCHP_CORESPI_REG_CONTROL); + + control &= ~INT_ENABLE_MASK; + writeb(control, spi->regs + MCHP_CORESPI_REG_CONTROL); +} + +static inline void mchp_corespi_write_fifo(struct mchp_corespi *spi, u32 fifo_max) +{ + int i = 0; + + while ((i < fifo_max) && + !(readb(spi->regs + MCHP_CORESPI_REG_STAT) & + MCHP_CORESPI_STATUS_TXFIFO_FULL)) { + u32 word; + + word = spi->tx_buf ? *spi->tx_buf : 0xaa; + writeb(word, spi->regs + MCHP_CORESPI_REG_TXDATA); + + if (spi->tx_buf) + spi->tx_buf++; + + i++; + } + + spi->tx_len -= i; +} + +static void mchp_corespi_set_cs(struct spi_device *spi, bool disable) +{ + struct mchp_corespi *corespi = spi_controller_get_devdata(spi->controller); + u32 reg; + + reg = readb(corespi->regs + MCHP_CORESPI_REG_SSEL); + reg &= ~BIT(spi_get_chipselect(spi, 0)); + reg |= !disable << spi_get_chipselect(spi, 0); + + writeb(reg, corespi->regs + MCHP_CORESPI_REG_SSEL); +} + +static int mchp_corespi_setup(struct spi_device *spi) +{ + u32 dev_mode = spi->mode & (SPI_CPOL | SPI_CPHA); + + if (spi_get_csgpiod(spi, 0)) + return 0; + + if (spi->mode & (SPI_CS_HIGH)) { + dev_err(&spi->dev, "unable to support active-high CS in Motorola mode\n"); + return -EOPNOTSUPP; + } + + if (dev_mode & ~spi->controller->mode_bits) { + dev_err(&spi->dev, "incompatible CPOL/CPHA, must match controller's Motorola mode\n"); + return -EINVAL; + } + + return 0; +} + +static void mchp_corespi_init(struct spi_controller *host, struct mchp_corespi *spi) +{ + u8 control = readb(spi->regs + MCHP_CORESPI_REG_CONTROL); + + /* Master mode changes require core to be disabled.*/ + control = (control & ~MCHP_CORESPI_CONTROL_ENABLE) | MCHP_CORESPI_CONTROL_MASTER; + + writeb(control, spi->regs + MCHP_CORESPI_REG_CONTROL); + + mchp_corespi_enable_ints(spi); + + control = readb(spi->regs + MCHP_CORESPI_REG_CONTROL); + control |= MCHP_CORESPI_CONTROL_ENABLE; + + writeb(control, spi->regs + MCHP_CORESPI_REG_CONTROL); +} + +static irqreturn_t mchp_corespi_interrupt(int irq, void *dev_id) +{ + struct spi_controller *host = dev_id; + struct mchp_corespi *spi = spi_controller_get_devdata(host); + u8 intfield = readb(spi->regs + MCHP_CORESPI_REG_INTMASK) & 0xff; + bool finalise = false; + + /* Interrupt line may be shared and not for us at all */ + if (intfield == 0) + return IRQ_NONE; + + if (intfield & MCHP_CORESPI_INT_TXDONE) + writeb(MCHP_CORESPI_INT_TXDONE, spi->regs + MCHP_CORESPI_REG_INTCLEAR); + + if (intfield & MCHP_CORESPI_INT_RX_CHANNEL_OVERFLOW) { + writeb(MCHP_CORESPI_INT_RX_CHANNEL_OVERFLOW, + spi->regs + MCHP_CORESPI_REG_INTCLEAR); + finalise = true; + dev_err(&host->dev, + "RX OVERFLOW: rxlen: %d, txlen: %d\n", + spi->rx_len, spi->tx_len); + } + + if (intfield & MCHP_CORESPI_INT_TX_CHANNEL_UNDERRUN) { + writeb(MCHP_CORESPI_INT_TX_CHANNEL_UNDERRUN, + spi->regs + MCHP_CORESPI_REG_INTCLEAR); + finalise = true; + dev_err(&host->dev, + "TX UNDERFLOW: rxlen: %d, txlen: %d\n", + spi->rx_len, spi->tx_len); + } + + if (finalise) + spi_finalize_current_transfer(host); + + return IRQ_HANDLED; +} + +static int mchp_corespi_set_clk_div(struct mchp_corespi *spi, + unsigned long target_hz) +{ + unsigned long pclk_hz, spi_hz; + u32 clk_div; + + /* Get peripheral clock rate */ + pclk_hz = clk_get_rate(spi->clk); + if (!pclk_hz) + return -EINVAL; + + /* + * Calculate clock rate generated by SPI master + * Formula: SPICLK = PCLK / (2 * (CLK_DIV + 1)) + */ + clk_div = DIV_ROUND_UP(pclk_hz, 2 * target_hz) - 1; + + if (clk_div > 0xFF) + return -EINVAL; + + spi_hz = pclk_hz / (2 * (clk_div + 1)); + + if (spi_hz > target_hz) + return -EINVAL; + + writeb(clk_div, spi->regs + MCHP_CORESPI_REG_CLK_DIV); + + return 0; +} + +static int mchp_corespi_transfer_one(struct spi_controller *host, + struct spi_device *spi_dev, + struct spi_transfer *xfer) +{ + struct mchp_corespi *spi = spi_controller_get_devdata(host); + int ret; + + ret = mchp_corespi_set_clk_div(spi, (unsigned long)xfer->speed_hz); + if (ret) { + dev_err(&host->dev, "failed to set clock divider for target %u Hz\n", + xfer->speed_hz); + return ret; + } + + spi->tx_buf = xfer->tx_buf; + spi->rx_buf = xfer->rx_buf; + spi->tx_len = xfer->len; + spi->rx_len = xfer->len; + + while (spi->tx_len) { + int fifo_max = min_t(int, spi->tx_len, spi->fifo_depth); + + mchp_corespi_write_fifo(spi, fifo_max); + mchp_corespi_read_fifo(spi, fifo_max); + } + + spi_finalize_current_transfer(host); + return 1; +} + +static int mchp_corespi_probe(struct platform_device *pdev) +{ + struct spi_controller *host; + struct mchp_corespi *spi; + struct resource *res; + const char *protocol; + u32 num_cs, mode, frame_size; + bool assert_ssel; + int ret = 0; + + host = devm_spi_alloc_host(&pdev->dev, sizeof(*spi)); + if (!host) + return dev_err_probe(&pdev->dev, -ENOMEM, + "unable to allocate host for SPI controller\n"); + + platform_set_drvdata(pdev, host); + + if (of_property_read_u32(pdev->dev.of_node, "num-cs", &num_cs)) + num_cs = MCHP_CORESPI_MAX_CS; + + /* + * Protocol: CFG_MODE + * CoreSPI can be configured for Motorola, TI or NSC. + * The current driver supports only Motorola mode. + */ + ret = of_property_read_string(pdev->dev.of_node, "microchip,protocol-configuration", + &protocol); + if (strcmp(protocol, "motorola") != 0) + return dev_err_probe(&pdev->dev, -EINVAL, + "CoreSPI: protocol '%s' not supported by this driver\n", + protocol); + + /* + * Motorola mode (0-3): CFG_MOT_MODE + * Mode is fixed in the IP configurator. + */ + ret = of_property_read_u32(pdev->dev.of_node, "microchip,motorola-mode", &mode); + if (ret) + mode = MCHP_CORESPI_DEFAULT_MOTOROLA_MODE; + else if (mode > 3) + return dev_err_probe(&pdev->dev, -EINVAL, + "invalid 'microchip,motorola-mode' value %u\n", mode); + + /* + * Frame size: CFG_FRAME_SIZE + * The hardware allows frame sizes <= APB data width. + * However, this driver currently only supports 8-bit frames. + */ + ret = of_property_read_u32(pdev->dev.of_node, "microchip,frame-size", &frame_size); + if (!ret && frame_size != 8) + return dev_err_probe(&pdev->dev, -EINVAL, + "CoreSPI: frame size %u not supported by this driver\n", + frame_size); + + /* + * SSEL: CFG_MOT_SSEL + * CoreSPI deasserts SSEL when the TX FIFO empties. + * To prevent CS deassertion when TX FIFO drains, the ssel-active property + * keeps CS asserted for the full SPI transfer. + */ + assert_ssel = of_property_read_bool(pdev->dev.of_node, "microchip,ssel-active"); + if (!assert_ssel) + return dev_err_probe(&pdev->dev, -EINVAL, + "hardware must enable 'microchip,ssel-active' to keep CS asserted for the SPI transfer\n"); + + spi = spi_controller_get_devdata(host); + + host->num_chipselect = num_cs; + host->mode_bits = mode; + host->setup = mchp_corespi_setup; + host->use_gpio_descriptors = true; + host->bits_per_word_mask = SPI_BPW_RANGE_MASK(4, 32); + host->transfer_one = mchp_corespi_transfer_one; + host->set_cs = mchp_corespi_set_cs; + host->dev.of_node = pdev->dev.of_node; + + ret = of_property_read_u32(pdev->dev.of_node, "fifo-depth", &spi->fifo_depth); + if (ret) + spi->fifo_depth = MCHP_CORESPI_DEFAULT_FIFO_DEPTH; + + spi->regs = devm_platform_get_and_ioremap_resource(pdev, 0, &res); + if (IS_ERR(spi->regs)) + return PTR_ERR(spi->regs); + + spi->irq = platform_get_irq(pdev, 0); + if (spi->irq < 0) + return spi->irq; + + ret = devm_request_irq(&pdev->dev, spi->irq, mchp_corespi_interrupt, + IRQF_SHARED, dev_name(&pdev->dev), host); + if (ret) + return dev_err_probe(&pdev->dev, ret, + "could not request irq\n"); + + spi->clk = devm_clk_get_enabled(&pdev->dev, NULL); + if (IS_ERR(spi->clk)) + return dev_err_probe(&pdev->dev, PTR_ERR(spi->clk), + "could not get clk\n"); + + mchp_corespi_init(host, spi); + + ret = devm_spi_register_controller(&pdev->dev, host); + if (ret) { + mchp_corespi_disable(spi); + return dev_err_probe(&pdev->dev, ret, + "unable to register host for CoreSPI controller\n"); + } + + return 0; +} + +static void mchp_corespi_remove(struct platform_device *pdev) +{ + struct spi_controller *host = platform_get_drvdata(pdev); + struct mchp_corespi *spi = spi_controller_get_devdata(host); + + mchp_corespi_disable_ints(spi); + mchp_corespi_disable(spi); +} + +#define MICROCHIP_SPI_PM_OPS (NULL) + +/* + * Platform driver data structure + */ + +#if defined(CONFIG_OF) +static const struct of_device_id mchp_corespi_dt_ids[] = { + { .compatible = "microchip,corespi-rtl-v5" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, mchp_corespi_dt_ids); +#endif + +static struct platform_driver mchp_corespi_driver = { + .probe = mchp_corespi_probe, + .driver = { + .name = "microchip-corespi", + .pm = MICROCHIP_SPI_PM_OPS, + .of_match_table = of_match_ptr(mchp_corespi_dt_ids), + }, + .remove = mchp_corespi_remove, +}; +module_platform_driver(mchp_corespi_driver); +MODULE_DESCRIPTION("Microchip CoreSPI controller driver"); +MODULE_AUTHOR("Prajna Rajendra Kumar "); +MODULE_LICENSE("GPL"); From 118eb2cb97b8fc0d515bb0449495959247db58f0 Mon Sep 17 00:00:00 2001 From: Jonas Gorski Date: Sun, 16 Nov 2025 10:33:34 +0100 Subject: [PATCH 46/80] spi: bcm63xx: drop wrong casts in probe() Both bs->regs and bs->{rx,tx}_io are tagged __iomem, so we shouldn't cast them to anything else. Silences the following sparse warning: drivers/spi/spi-bcm63xx.c:571:22: warning: cast removes address space '__iomem' of expression drivers/spi/spi-bcm63xx.c:571:19: warning: incorrect type in assignment (different address spaces) drivers/spi/spi-bcm63xx.c:571:19: expected unsigned char [noderef] [usertype] __iomem *tx_io drivers/spi/spi-bcm63xx.c:571:19: got unsigned char [usertype] * drivers/spi/spi-bcm63xx.c:572:22: warning: cast removes address space '__iomem' of expression drivers/spi/spi-bcm63xx.c:572:19: warning: incorrect type in assignment (different address spaces) drivers/spi/spi-bcm63xx.c:572:19: expected unsigned char const [noderef] [usertype] __iomem *rx_io drivers/spi/spi-bcm63xx.c:572:19: got unsigned char const [usertype] * Fixes: b42dfed83d95 ("spi: add Broadcom BCM63xx SPI controller driver") Signed-off-by: Jonas Gorski Reviewed-by: Florian Fainelli Link: https://patch.msgid.link/20251116093334.17423-1-jonas.gorski@gmail.com Signed-off-by: Mark Brown --- drivers/spi/spi-bcm63xx.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/spi/spi-bcm63xx.c b/drivers/spi/spi-bcm63xx.c index b56210734caa..55db5299d725 100644 --- a/drivers/spi/spi-bcm63xx.c +++ b/drivers/spi/spi-bcm63xx.c @@ -568,8 +568,8 @@ static int bcm63xx_spi_probe(struct platform_device *pdev) host->auto_runtime_pm = true; bs->msg_type_shift = bs->reg_offsets[SPI_MSG_TYPE_SHIFT]; bs->msg_ctl_width = bs->reg_offsets[SPI_MSG_CTL_WIDTH]; - bs->tx_io = (u8 *)(bs->regs + bs->reg_offsets[SPI_MSG_DATA]); - bs->rx_io = (const u8 *)(bs->regs + bs->reg_offsets[SPI_RX_DATA]); + bs->tx_io = bs->regs + bs->reg_offsets[SPI_MSG_DATA]; + bs->rx_io = bs->regs + bs->reg_offsets[SPI_RX_DATA]; /* Initialize hardware */ ret = clk_prepare_enable(bs->clk); From 716d0a0a2ab00c601120c19bb357f4373f4722d1 Mon Sep 17 00:00:00 2001 From: Chin-Ting Kuo Date: Fri, 14 Nov 2025 18:10:40 +0800 Subject: [PATCH 47/80] spi: aspeed: Enable Quad SPI mode for page program Ensure the controller switches to quad I/O mode when spi-tx-bus-width dts property is 4 and the Quad SPI program opcode (32h or 34h) is used. Without this change, high-bit data will be lost during page programming. Signed-off-by: Chin-Ting Kuo Link: https://patch.msgid.link/20251114101042.1520997-3-chin-ting_kuo@aspeedtech.com Signed-off-by: Mark Brown --- drivers/spi/spi-aspeed-smc.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/spi/spi-aspeed-smc.c b/drivers/spi/spi-aspeed-smc.c index 179c47ffbfeb..4163632fed8b 100644 --- a/drivers/spi/spi-aspeed-smc.c +++ b/drivers/spi/spi-aspeed-smc.c @@ -263,11 +263,15 @@ static ssize_t aspeed_spi_write_user(struct aspeed_spi_chip *chip, const struct spi_mem_op *op) { int ret; + int io_mode = aspeed_spi_get_io_mode(op); aspeed_spi_start_user(chip); ret = aspeed_spi_send_cmd_addr(chip, op->addr.nbytes, op->addr.val, op->cmd.opcode); if (ret < 0) goto stop_user; + + aspeed_spi_set_io_mode(chip, io_mode); + aspeed_spi_write_to_ahb(chip->ahb_base, op->data.buf.out, op->data.nbytes); stop_user: aspeed_spi_stop_user(chip); From be6671d3908e97a2128f5327610a1dcb4d420cfa Mon Sep 17 00:00:00 2001 From: Chin-Ting Kuo Date: Fri, 14 Nov 2025 18:10:39 +0800 Subject: [PATCH 48/80] spi: dt-bindings: aspeed,ast2600-fmc: Add AST2700 SoC support Add AST2700 to the list of supported SoCs in the ASPEED FMC/SPI bindings. AST2700 FMC/SPI controllers are not compatible with AST2600 due to the following hardware differences: - Address decoding unit uses 64KB granularity (AST2600 uses 1MB). - Segment register semantics are changed. AST2600: start <= range <= end AST2700: start <= range < end - Hardware limitations in AST2600 address decoding registers have been resolved in AST2700, so extra callback function used for bug fixup is no longer required. These differences require distinct compatible strings for AST2700. Signed-off-by: Chin-Ting Kuo Acked-by: Conor Dooley Link: https://patch.msgid.link/20251114101042.1520997-2-chin-ting_kuo@aspeedtech.com Signed-off-by: Mark Brown --- Documentation/devicetree/bindings/spi/aspeed,ast2600-fmc.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/spi/aspeed,ast2600-fmc.yaml b/Documentation/devicetree/bindings/spi/aspeed,ast2600-fmc.yaml index 57d932af4506..80e542624cc6 100644 --- a/Documentation/devicetree/bindings/spi/aspeed,ast2600-fmc.yaml +++ b/Documentation/devicetree/bindings/spi/aspeed,ast2600-fmc.yaml @@ -12,7 +12,7 @@ maintainers: description: | This binding describes the Aspeed Static Memory Controllers (FMC and - SPI) of the AST2400, AST2500 and AST2600 SOCs. + SPI) of the AST2400, AST2500, AST2600 and AST2700 SOCs. allOf: - $ref: spi-controller.yaml# @@ -20,6 +20,8 @@ allOf: properties: compatible: enum: + - aspeed,ast2700-fmc + - aspeed,ast2700-spi - aspeed,ast2600-fmc - aspeed,ast2600-spi - aspeed,ast2500-fmc From 508f3d3b688e1650ed383fe208b323aa6c164420 Mon Sep 17 00:00:00 2001 From: Chin-Ting Kuo Date: Fri, 14 Nov 2025 18:10:41 +0800 Subject: [PATCH 49/80] spi: aspeed: Use phys_addr_t for bus addresses to support 64-bit platforms Update bus address types from u32 to phys_addr_t to support systems with 64-bit memory address space. This change ensures compatibility with upcoming SoCs that extend the system bus beyond 32-bit, while maintaining support for existing platforms. Signed-off-by: Chin-Ting Kuo Link: https://patch.msgid.link/20251114101042.1520997-4-chin-ting_kuo@aspeedtech.com Signed-off-by: Mark Brown --- drivers/spi/spi-aspeed-smc.c | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/drivers/spi/spi-aspeed-smc.c b/drivers/spi/spi-aspeed-smc.c index 4163632fed8b..d1a8bdf6d540 100644 --- a/drivers/spi/spi-aspeed-smc.c +++ b/drivers/spi/spi-aspeed-smc.c @@ -82,9 +82,10 @@ struct aspeed_spi_data { u32 hdiv_max; u32 min_window_size; - u32 (*segment_start)(struct aspeed_spi *aspi, u32 reg); - u32 (*segment_end)(struct aspeed_spi *aspi, u32 reg); - u32 (*segment_reg)(struct aspeed_spi *aspi, u32 start, u32 end); + phys_addr_t (*segment_start)(struct aspeed_spi *aspi, u32 reg); + phys_addr_t (*segment_end)(struct aspeed_spi *aspi, u32 reg); + u32 (*segment_reg)(struct aspeed_spi *aspi, phys_addr_t start, + phys_addr_t end); int (*adjust_window)(struct aspeed_spi *aspi); u32 (*get_clk_div)(struct aspeed_spi_chip *chip, u32 hz); int (*calibrate)(struct aspeed_spi_chip *chip, u32 hdiv, @@ -97,7 +98,7 @@ struct aspeed_spi { const struct aspeed_spi_data *data; void __iomem *regs; - u32 ahb_base_phy; + phys_addr_t ahb_base_phy; u32 ahb_window_size; u32 num_cs; struct device *dev; @@ -484,9 +485,9 @@ static int aspeed_spi_chip_set_default_window(struct aspeed_spi *aspi) /* Assign the minimum window size to each CS */ for (cs = 0; cs < aspi->num_cs; cs++) { aspi->chips[cs].ahb_window_size = aspi->data->min_window_size; - dev_dbg(aspi->dev, "CE%d default window [ 0x%.8x - 0x%.8x ]", - cs, aspi->ahb_base_phy + aspi->data->min_window_size * cs, - aspi->ahb_base_phy + aspi->data->min_window_size * cs - 1); + dev_dbg(aspi->dev, "CE%d default window [ 0x%.9llx - 0x%.9llx ]", + cs, (u64)(aspi->ahb_base_phy + aspi->data->min_window_size * cs), + (u64)(aspi->ahb_base_phy + aspi->data->min_window_size * cs - 1)); } /* Close unused CS */ @@ -930,17 +931,18 @@ static void aspeed_spi_remove(struct platform_device *pdev) * The address range is encoded with absolute addresses in the overall * mapping window. */ -static u32 aspeed_spi_segment_start(struct aspeed_spi *aspi, u32 reg) +static phys_addr_t aspeed_spi_segment_start(struct aspeed_spi *aspi, u32 reg) { return ((reg >> 16) & 0xFF) << 23; } -static u32 aspeed_spi_segment_end(struct aspeed_spi *aspi, u32 reg) +static phys_addr_t aspeed_spi_segment_end(struct aspeed_spi *aspi, u32 reg) { return ((reg >> 24) & 0xFF) << 23; } -static u32 aspeed_spi_segment_reg(struct aspeed_spi *aspi, u32 start, u32 end) +static u32 aspeed_spi_segment_reg(struct aspeed_spi *aspi, + phys_addr_t start, phys_addr_t end) { return (((start >> 23) & 0xFF) << 16) | (((end >> 23) & 0xFF) << 24); } @@ -952,16 +954,16 @@ static u32 aspeed_spi_segment_reg(struct aspeed_spi *aspi, u32 start, u32 end) #define AST2600_SEG_ADDR_MASK 0x0ff00000 -static u32 aspeed_spi_segment_ast2600_start(struct aspeed_spi *aspi, - u32 reg) +static phys_addr_t aspeed_spi_segment_ast2600_start(struct aspeed_spi *aspi, + u32 reg) { u32 start_offset = (reg << 16) & AST2600_SEG_ADDR_MASK; return aspi->ahb_base_phy + start_offset; } -static u32 aspeed_spi_segment_ast2600_end(struct aspeed_spi *aspi, - u32 reg) +static phys_addr_t aspeed_spi_segment_ast2600_end(struct aspeed_spi *aspi, + u32 reg) { u32 end_offset = reg & AST2600_SEG_ADDR_MASK; @@ -973,7 +975,7 @@ static u32 aspeed_spi_segment_ast2600_end(struct aspeed_spi *aspi, } static u32 aspeed_spi_segment_ast2600_reg(struct aspeed_spi *aspi, - u32 start, u32 end) + phys_addr_t start, phys_addr_t end) { /* disable zero size segments */ if (start == end) From 9e510e677090bb794b46348b10e1c8038286e00a Mon Sep 17 00:00:00 2001 From: Chin-Ting Kuo Date: Fri, 14 Nov 2025 18:10:42 +0800 Subject: [PATCH 50/80] spi: aspeed: Add support for the AST2700 SPI controller Extend the driver to support the AST2700 SPI controller. Compared to AST2600, AST2700 has the following characteristics: - A 64-bit memory address space. - A 64KB address decoding unit. - Segment registers now use (start <= range < end) semantics, which differs slightly from (start <= range <= end) in AST2600. - Known issues related to address decoding range registers have been resolved, and the decoding range is now 1GB, which is sufficient. Therefore, the adjust_window callback is no longer required on AST2700 for range adjustment and bug fixes. - The SPI clock divider method and timing calibration logic remain unchanged from AST2600. Signed-off-by: Chin-Ting Kuo Link: https://patch.msgid.link/20251114101042.1520997-5-chin-ting_kuo@aspeedtech.com Signed-off-by: Mark Brown --- drivers/spi/spi-aspeed-smc.c | 71 ++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/drivers/spi/spi-aspeed-smc.c b/drivers/spi/spi-aspeed-smc.c index d1a8bdf6d540..db3e096f2eb0 100644 --- a/drivers/spi/spi-aspeed-smc.c +++ b/drivers/spi/spi-aspeed-smc.c @@ -985,6 +985,41 @@ static u32 aspeed_spi_segment_ast2600_reg(struct aspeed_spi *aspi, ((end - 1) & AST2600_SEG_ADDR_MASK); } +/* The Segment Registers of the AST2700 use a 64KB unit. */ +#define AST2700_SEG_ADDR_MASK 0x7fff0000 + +static phys_addr_t aspeed_spi_segment_ast2700_start(struct aspeed_spi *aspi, + u32 reg) +{ + u64 start_offset = (reg << 16) & AST2700_SEG_ADDR_MASK; + + if (!start_offset) + return aspi->ahb_base_phy; + + return aspi->ahb_base_phy + start_offset; +} + +static phys_addr_t aspeed_spi_segment_ast2700_end(struct aspeed_spi *aspi, + u32 reg) +{ + u64 end_offset = reg & AST2700_SEG_ADDR_MASK; + + if (!end_offset) + return aspi->ahb_base_phy; + + return aspi->ahb_base_phy + end_offset; +} + +static u32 aspeed_spi_segment_ast2700_reg(struct aspeed_spi *aspi, + phys_addr_t start, phys_addr_t end) +{ + if (start == end) + return 0; + + return (u32)(((start & AST2700_SEG_ADDR_MASK) >> 16) | + (end & AST2700_SEG_ADDR_MASK)); +} + /* * Read timing compensation sequences */ @@ -1511,6 +1546,40 @@ static const struct aspeed_spi_data ast2600_spi_data = { .adjust_window = aspeed_adjust_window_ast2600, }; +static const struct aspeed_spi_data ast2700_fmc_data = { + .max_cs = 3, + .hastype = false, + .mode_bits = SPI_RX_QUAD | SPI_TX_QUAD, + .we0 = 16, + .ctl0 = CE0_CTRL_REG, + .timing = CE0_TIMING_COMPENSATION_REG, + .hclk_mask = 0xf0fff0ff, + .hdiv_max = 2, + .min_window_size = 0x10000, + .get_clk_div = aspeed_get_clk_div_ast2600, + .calibrate = aspeed_spi_ast2600_calibrate, + .segment_start = aspeed_spi_segment_ast2700_start, + .segment_end = aspeed_spi_segment_ast2700_end, + .segment_reg = aspeed_spi_segment_ast2700_reg, +}; + +static const struct aspeed_spi_data ast2700_spi_data = { + .max_cs = 2, + .hastype = false, + .mode_bits = SPI_RX_QUAD | SPI_TX_QUAD, + .we0 = 16, + .ctl0 = CE0_CTRL_REG, + .timing = CE0_TIMING_COMPENSATION_REG, + .hclk_mask = 0xf0fff0ff, + .hdiv_max = 2, + .min_window_size = 0x10000, + .get_clk_div = aspeed_get_clk_div_ast2600, + .calibrate = aspeed_spi_ast2600_calibrate, + .segment_start = aspeed_spi_segment_ast2700_start, + .segment_end = aspeed_spi_segment_ast2700_end, + .segment_reg = aspeed_spi_segment_ast2700_reg, +}; + static const struct of_device_id aspeed_spi_matches[] = { { .compatible = "aspeed,ast2400-fmc", .data = &ast2400_fmc_data }, { .compatible = "aspeed,ast2400-spi", .data = &ast2400_spi_data }, @@ -1518,6 +1587,8 @@ static const struct of_device_id aspeed_spi_matches[] = { { .compatible = "aspeed,ast2500-spi", .data = &ast2500_spi_data }, { .compatible = "aspeed,ast2600-fmc", .data = &ast2600_fmc_data }, { .compatible = "aspeed,ast2600-spi", .data = &ast2600_spi_data }, + { .compatible = "aspeed,ast2700-fmc", .data = &ast2700_fmc_data }, + { .compatible = "aspeed,ast2700-spi", .data = &ast2700_spi_data }, { } }; MODULE_DEVICE_TABLE(of, aspeed_spi_matches); From 96498e804cb6629e02747336a0a33e4955449732 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Mon, 17 Nov 2025 17:12:47 +0100 Subject: [PATCH 51/80] spi: davinci: remove platform data header There are no longer any board files including the DaVinci SPI platform data header. Let's move the bits and pieces that are used in the driver into the driver .c file itself and remove the header. Signed-off-by: Bartosz Golaszewski Link: https://patch.msgid.link/20251117-davinci-spi-v2-1-cd799d17f04a@linaro.org Signed-off-by: Mark Brown --- drivers/spi/spi-davinci.c | 64 +++++++++++++++++++- include/linux/platform_data/spi-davinci.h | 73 ----------------------- 2 files changed, 62 insertions(+), 75 deletions(-) delete mode 100644 include/linux/platform_data/spi-davinci.h diff --git a/drivers/spi/spi-davinci.c b/drivers/spi/spi-davinci.c index a29934422356..21a14e800eed 100644 --- a/drivers/spi/spi-davinci.c +++ b/drivers/spi/spi-davinci.c @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -19,8 +20,6 @@ #include #include -#include - #define CS_DEFAULT 0xFF #define SPIFMT_PHASE_MASK BIT(16) @@ -98,8 +97,69 @@ #define SPIDEF 0x4c #define SPIFMT0 0x50 +#define SPI_IO_TYPE_POLL 1 +#define SPI_IO_TYPE_DMA 2 + #define DMA_MIN_BYTES 16 +enum { + SPI_VERSION_1, /* For DM355/DM365/DM6467 */ + SPI_VERSION_2, /* For DA8xx */ +}; + +/** + * struct davinci_spi_platform_data - Platform data for SPI master device on DaVinci + * + * @version: version of the SPI IP. Different DaVinci devices have slightly + * varying versions of the same IP. + * @num_chipselect: number of chipselects supported by this SPI master + * @intr_line: interrupt line used to connect the SPI IP to the ARM interrupt + * controller withn the SoC. Possible values are 0 and 1. + * @prescaler_limit: max clock prescaler value + * @cshold_bug: set this to true if the SPI controller on your chip requires + * a write to CSHOLD bit in between transfers (like in DM355). + * @dma_event_q: DMA event queue to use if SPI_IO_TYPE_DMA is used for any + * device on the bus. + */ +struct davinci_spi_platform_data { + u8 version; + u8 num_chipselect; + u8 intr_line; + u8 prescaler_limit; + bool cshold_bug; + enum dma_event_q dma_event_q; +}; + +/** + * struct davinci_spi_config - Per-chip-select configuration for SPI slave devices + * + * @wdelay: amount of delay between transmissions. Measured in number of + * SPI module clocks. + * @odd_parity: polarity of parity flag at the end of transmit data stream. + * 0 - odd parity, 1 - even parity. + * @parity_enable: enable transmission of parity at end of each transmit + * data stream. + * @io_type: type of IO transfer. Choose between polled, interrupt and DMA. + * @timer_disable: disable chip-select timers (setup and hold) + * @c2tdelay: chip-select setup time. Measured in number of SPI module clocks. + * @t2cdelay: chip-select hold time. Measured in number of SPI module clocks. + * @t2edelay: transmit data finished to SPI ENAn pin inactive time. Measured + * in number of SPI clocks. + * @c2edelay: chip-select active to SPI ENAn signal active time. Measured in + * number of SPI clocks. + */ +struct davinci_spi_config { + u8 wdelay; + u8 odd_parity; + u8 parity_enable; + u8 io_type; + u8 timer_disable; + u8 c2tdelay; + u8 t2cdelay; + u8 t2edelay; + u8 c2edelay; +}; + /* SPI Controller driver's private data. */ struct davinci_spi { struct spi_bitbang bitbang; diff --git a/include/linux/platform_data/spi-davinci.h b/include/linux/platform_data/spi-davinci.h deleted file mode 100644 index 2cb5cc70fd9d..000000000000 --- a/include/linux/platform_data/spi-davinci.h +++ /dev/null @@ -1,73 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* - * Copyright 2009 Texas Instruments. - */ - -#ifndef __ARCH_ARM_DAVINCI_SPI_H -#define __ARCH_ARM_DAVINCI_SPI_H - -#include - -#define SPI_INTERN_CS 0xFF - -enum { - SPI_VERSION_1, /* For DM355/DM365/DM6467 */ - SPI_VERSION_2, /* For DA8xx */ -}; - -/** - * davinci_spi_platform_data - Platform data for SPI master device on DaVinci - * - * @version: version of the SPI IP. Different DaVinci devices have slightly - * varying versions of the same IP. - * @num_chipselect: number of chipselects supported by this SPI master - * @intr_line: interrupt line used to connect the SPI IP to the ARM interrupt - * controller withn the SoC. Possible values are 0 and 1. - * @cshold_bug: set this to true if the SPI controller on your chip requires - * a write to CSHOLD bit in between transfers (like in DM355). - * @dma_event_q: DMA event queue to use if SPI_IO_TYPE_DMA is used for any - * device on the bus. - */ -struct davinci_spi_platform_data { - u8 version; - u8 num_chipselect; - u8 intr_line; - u8 prescaler_limit; - bool cshold_bug; - enum dma_event_q dma_event_q; -}; - -/** - * davinci_spi_config - Per-chip-select configuration for SPI slave devices - * - * @wdelay: amount of delay between transmissions. Measured in number of - * SPI module clocks. - * @odd_parity: polarity of parity flag at the end of transmit data stream. - * 0 - odd parity, 1 - even parity. - * @parity_enable: enable transmission of parity at end of each transmit - * data stream. - * @io_type: type of IO transfer. Choose between polled, interrupt and DMA. - * @timer_disable: disable chip-select timers (setup and hold) - * @c2tdelay: chip-select setup time. Measured in number of SPI module clocks. - * @t2cdelay: chip-select hold time. Measured in number of SPI module clocks. - * @t2edelay: transmit data finished to SPI ENAn pin inactive time. Measured - * in number of SPI clocks. - * @c2edelay: chip-select active to SPI ENAn signal active time. Measured in - * number of SPI clocks. - */ -struct davinci_spi_config { - u8 wdelay; - u8 odd_parity; - u8 parity_enable; -#define SPI_IO_TYPE_INTR 0 -#define SPI_IO_TYPE_POLL 1 -#define SPI_IO_TYPE_DMA 2 - u8 io_type; - u8 timer_disable; - u8 c2tdelay; - u8 t2cdelay; - u8 t2edelay; - u8 c2edelay; -}; - -#endif /* __ARCH_ARM_DAVINCI_SPI_H */ From 4dbb5f6e1b4eed64037d4462977c196acab2af16 Mon Sep 17 00:00:00 2001 From: Carlos Song Date: Tue, 18 Nov 2025 14:50:12 +0800 Subject: [PATCH 52/80] spi: imx: add 16/32 bits per word support for target PIO mode Enable 16/32 bits per word support for spi-imx target PIO mode. Signed-off-by: Carlos Song Signed-off-by: Clark Wang Reviewed-by: Frank Li Link: https://patch.msgid.link/20251118065012.1418279-1-carlos.song@nxp.com Signed-off-by: Mark Brown --- drivers/spi/spi-imx.c | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/drivers/spi/spi-imx.c b/drivers/spi/spi-imx.c index edd33fc28c7d..e889680a3002 100644 --- a/drivers/spi/spi-imx.c +++ b/drivers/spi/spi-imx.c @@ -425,8 +425,15 @@ static void spi_imx_buf_tx_swap(struct spi_imx_data *spi_imx) static void mx53_ecspi_rx_target(struct spi_imx_data *spi_imx) { - u32 val = ioread32be(spi_imx->base + MXC_CSPIRXDATA); + u32 val = readl(spi_imx->base + MXC_CSPIRXDATA); +#ifdef __LITTLE_ENDIAN + unsigned int bytes_per_word = spi_imx_bytes_per_word(spi_imx->bits_per_word); + if (bytes_per_word == 1) + swab32s(&val); + else if (bytes_per_word == 2) + swahw32s(&val); +#endif if (spi_imx->rx_buf) { int n_bytes = spi_imx->target_burst % sizeof(val); @@ -447,6 +454,9 @@ static void mx53_ecspi_tx_target(struct spi_imx_data *spi_imx) { u32 val = 0; int n_bytes = spi_imx->count % sizeof(val); +#ifdef __LITTLE_ENDIAN + unsigned int bytes_per_word; +#endif if (!n_bytes) n_bytes = sizeof(val); @@ -459,7 +469,14 @@ static void mx53_ecspi_tx_target(struct spi_imx_data *spi_imx) spi_imx->count -= n_bytes; - iowrite32be(val, spi_imx->base + MXC_CSPITXDATA); +#ifdef __LITTLE_ENDIAN + bytes_per_word = spi_imx_bytes_per_word(spi_imx->bits_per_word); + if (bytes_per_word == 1) + swab32s(&val); + else if (bytes_per_word == 2) + swahw32s(&val); +#endif + writel(val, spi_imx->base + MXC_CSPITXDATA); } /* MX51 eCSPI */ From bd79452b39c21599e2cff42e9fbeb182656b6f6a Mon Sep 17 00:00:00 2001 From: Lukas Bulwahn Date: Mon, 17 Nov 2025 11:40:44 +0100 Subject: [PATCH 53/80] MAINTAINERS: adjust file entry in RISC-V MICROCHIP SUPPORT Commit 71c814e98696 ("spi: microchip: rename driver file and internal identifiers") renames spi-microchip-core.c to spi-mpfs.c, but misses to adjust the file entry in RISC-V MICROCHIP SUPPORT. Adjust the file entry after this renaming. Signed-off-by: Lukas Bulwahn Link: https://patch.msgid.link/20251117104044.291517-1-lukas.bulwahn@redhat.com Signed-off-by: Mark Brown --- MAINTAINERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MAINTAINERS b/MAINTAINERS index 9595e50e4ba8..c09ff947ef6a 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -22137,7 +22137,7 @@ F: drivers/reset/reset-mpfs.c F: drivers/rtc/rtc-mpfs.c F: drivers/soc/microchip/mpfs-sys-controller.c F: drivers/spi/spi-microchip-core-qspi.c -F: drivers/spi/spi-microchip-core.c +F: drivers/spi/spi-mpfs.c F: drivers/usb/musb/mpfs.c F: include/soc/microchip/mpfs.h From d9813cd23d5a7b254cc1b1c1ea042634d8da62e6 Mon Sep 17 00:00:00 2001 From: Longbin Li Date: Mon, 17 Nov 2025 17:05:39 +0800 Subject: [PATCH 54/80] spi: sophgo: Fix incorrect use of bus width value macros The previous code initialized the 'reg' value with specific bus-width values (BUS_WIDTH_2_BIT and BUS_WIDTH_4_BIT), which introduces ambiguity. Replace them with BUS_WIDTH_MASK to express the intention clearly. Fixes: de16c322eefb ("spi: sophgo: add SG2044 SPI NOR controller driver") Signed-off-by: Longbin Li Link: https://patch.msgid.link/20251117090559.78288-1-looong.bin@gmail.com Signed-off-by: Mark Brown --- drivers/spi/spi-sg2044-nor.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/spi/spi-sg2044-nor.c b/drivers/spi/spi-sg2044-nor.c index af48b1fcda93..37f1cfe10be4 100644 --- a/drivers/spi/spi-sg2044-nor.c +++ b/drivers/spi/spi-sg2044-nor.c @@ -42,6 +42,7 @@ #define SPIFMC_TRAN_CSR_TRAN_MODE_RX BIT(0) #define SPIFMC_TRAN_CSR_TRAN_MODE_TX BIT(1) #define SPIFMC_TRAN_CSR_FAST_MODE BIT(3) +#define SPIFMC_TRAN_CSR_BUS_WIDTH_MASK GENMASK(5, 4) #define SPIFMC_TRAN_CSR_BUS_WIDTH_1_BIT (0x00 << 4) #define SPIFMC_TRAN_CSR_BUS_WIDTH_2_BIT (0x01 << 4) #define SPIFMC_TRAN_CSR_BUS_WIDTH_4_BIT (0x02 << 4) @@ -122,8 +123,7 @@ static u32 sg2044_spifmc_init_reg(struct sg2044_spifmc *spifmc) reg = readl(spifmc->io_base + SPIFMC_TRAN_CSR); reg &= ~(SPIFMC_TRAN_CSR_TRAN_MODE_MASK | SPIFMC_TRAN_CSR_FAST_MODE | - SPIFMC_TRAN_CSR_BUS_WIDTH_2_BIT | - SPIFMC_TRAN_CSR_BUS_WIDTH_4_BIT | + SPIFMC_TRAN_CSR_BUS_WIDTH_MASK | SPIFMC_TRAN_CSR_DMA_EN | SPIFMC_TRAN_CSR_ADDR_BYTES_MASK | SPIFMC_TRAN_CSR_WITH_CMD | From a7bde7c10902a0f6f903d3bbe67461f2b402a9ca Mon Sep 17 00:00:00 2001 From: Riccardo Mereu Date: Thu, 20 Nov 2025 16:58:21 +0100 Subject: [PATCH 55/80] dt-bindings: trivial-devices: add arduino spi mcu interface Add unoq mcu interface. It describes an iterface between Qualcomm QRB2210 microprocessor and STMicroelectronics STM32U585 microcontroller on Arduino UnoQ single-board computer. This is a trivial device since it's usage is handled in user space by the arduino-router service. Signed-off-by: Riccardo Mereu Reviewed-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20251120155825.121483-3-r.mereu.kernel@arduino.cc Signed-off-by: Mark Brown --- Documentation/devicetree/bindings/trivial-devices.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Documentation/devicetree/bindings/trivial-devices.yaml b/Documentation/devicetree/bindings/trivial-devices.yaml index 58ff948d93c9..9bff5f020108 100644 --- a/Documentation/devicetree/bindings/trivial-devices.yaml +++ b/Documentation/devicetree/bindings/trivial-devices.yaml @@ -53,6 +53,8 @@ properties: - adi,lt7182s # AMS iAQ-Core VOC Sensor - ams,iaq-core + # Arduino microcontroller interface over SPI on UnoQ board + - arduino,unoq-mcu # Temperature monitoring of Astera Labs PT5161L PCIe retimer - asteralabs,pt5161l # i2c h/w elliptic curve crypto module From 43a3adb6dd39d98bf84e04569e7604be5e5c0d79 Mon Sep 17 00:00:00 2001 From: Riccardo Mereu Date: Thu, 20 Nov 2025 16:58:22 +0100 Subject: [PATCH 56/80] spi: spidev: add compatible for arduino spi mcu interface Add compatible entry in spidev describing in Arduino UnoQ single-board computer the interface between Qualcomm QRB2210 microprocessor and STMicroelectronics STM32U585 microcontroller. It is handled in user space by the arduino-router service. Signed-off-by: Riccardo Mereu Reviewed-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20251120155825.121483-4-r.mereu.kernel@arduino.cc Signed-off-by: Mark Brown --- drivers/spi/spidev.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/spi/spidev.c b/drivers/spi/spidev.c index 5300c942a2a4..9a0160f6dc3d 100644 --- a/drivers/spi/spidev.c +++ b/drivers/spi/spidev.c @@ -704,6 +704,7 @@ static const struct class spidev_class = { */ static const struct spi_device_id spidev_spi_ids[] = { { .name = /* abb */ "spi-sensor" }, + { .name = /* arduino */ "unoq-mcu" }, { .name = /* cisco */ "spi-petra" }, { .name = /* dh */ "dhcom-board" }, { .name = /* elgin */ "jg10309-01" }, @@ -737,6 +738,7 @@ static int spidev_of_check(struct device *dev) static const struct of_device_id spidev_dt_ids[] = { { .compatible = "abb,spi-sensor", .data = &spidev_of_check }, + { .compatible = "arduino,unoq-mcu", .data = &spidev_of_check }, { .compatible = "cisco,spi-petra", .data = &spidev_of_check }, { .compatible = "dh,dhcom-board", .data = &spidev_of_check }, { .compatible = "elgin,jg10309-01", .data = &spidev_of_check }, From aead5ae91e4cbadac817d15737eca3b531237448 Mon Sep 17 00:00:00 2001 From: Cosmin Tanislav Date: Wed, 19 Nov 2025 18:14:22 +0200 Subject: [PATCH 57/80] spi: rzv2h-rspi: make resets optional The Renesas RZ/T2H (R9A09G077) and RZ/N2H (R9A09G087) SoCs don't have reset lines for the SPI peripheral, make them optional to prepare for adding support for them. Signed-off-by: Cosmin Tanislav Link: https://patch.msgid.link/20251119161434.595677-2-cosmin-gabriel.tanislav.xa@renesas.com Signed-off-by: Mark Brown --- drivers/spi/spi-rzv2h-rspi.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/spi/spi-rzv2h-rspi.c b/drivers/spi/spi-rzv2h-rspi.c index dcc431ba60a9..09b9362e9b1f 100644 --- a/drivers/spi/spi-rzv2h-rspi.c +++ b/drivers/spi/spi-rzv2h-rspi.c @@ -384,8 +384,8 @@ static int rzv2h_rspi_probe(struct platform_device *pdev) rspi->resets[0].id = "presetn"; rspi->resets[1].id = "tresetn"; - ret = devm_reset_control_bulk_get_exclusive(dev, RSPI_RESET_NUM, - rspi->resets); + ret = devm_reset_control_bulk_get_optional_exclusive(dev, RSPI_RESET_NUM, + rspi->resets); if (ret) return dev_err_probe(dev, ret, "cannot get resets\n"); From 8e89ee6cd2b928a8431bef61e8b851ce5df1ecb0 Mon Sep 17 00:00:00 2001 From: Cosmin Tanislav Date: Wed, 19 Nov 2025 18:14:23 +0200 Subject: [PATCH 58/80] spi: rzv2h-rspi: make FIFO size chip-specific The Renesas RZ/T2H (R9A09G077) and RZ/N2H (R9A09G087) SoCs have a different FIFO size compared to RZ/V2H. Add a chip-specific structure, and set the FIFO size inside it, to prepare for adding support for them. Signed-off-by: Cosmin Tanislav Link: https://patch.msgid.link/20251119161434.595677-3-cosmin-gabriel.tanislav.xa@renesas.com Signed-off-by: Mark Brown --- drivers/spi/spi-rzv2h-rspi.c | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/drivers/spi/spi-rzv2h-rspi.c b/drivers/spi/spi-rzv2h-rspi.c index 09b9362e9b1f..7a7a576c17dd 100644 --- a/drivers/spi/spi-rzv2h-rspi.c +++ b/drivers/spi/spi-rzv2h-rspi.c @@ -58,7 +58,6 @@ /* Register SPDCR2 */ #define RSPI_SPDCR2_TTRG GENMASK(11, 8) #define RSPI_SPDCR2_RTRG GENMASK(3, 0) -#define RSPI_FIFO_SIZE 16 /* Register SPSR */ #define RSPI_SPSR_SPRF BIT(15) @@ -69,9 +68,14 @@ #define RSPI_RESET_NUM 2 #define RSPI_CLK_NUM 3 +struct rzv2h_rspi_info { + unsigned int fifo_size; +}; + struct rzv2h_rspi_priv { struct reset_control_bulk_data resets[RSPI_RESET_NUM]; struct spi_controller *controller; + const struct rzv2h_rspi_info *info; void __iomem *base; struct clk *tclk; wait_queue_head_t wait; @@ -305,7 +309,7 @@ static int rzv2h_rspi_prepare_message(struct spi_controller *ctlr, writeb(0, rspi->base + RSPI_SSLP); /* Setup FIFO thresholds */ - conf16 = FIELD_PREP(RSPI_SPDCR2_TTRG, RSPI_FIFO_SIZE - 1); + conf16 = FIELD_PREP(RSPI_SPDCR2_TTRG, rspi->info->fifo_size - 1); conf16 |= FIELD_PREP(RSPI_SPDCR2_RTRG, 0); writew(conf16, rspi->base + RSPI_SPDCR2); @@ -362,6 +366,8 @@ static int rzv2h_rspi_probe(struct platform_device *pdev) rspi->controller = controller; + rspi->info = device_get_match_data(dev); + rspi->base = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(rspi->base)) return PTR_ERR(rspi->base); @@ -445,8 +451,12 @@ static void rzv2h_rspi_remove(struct platform_device *pdev) reset_control_bulk_assert(RSPI_RESET_NUM, rspi->resets); } +static const struct rzv2h_rspi_info rzv2h_info = { + .fifo_size = 16, +}; + static const struct of_device_id rzv2h_rspi_match[] = { - { .compatible = "renesas,r9a09g057-rspi" }, + { .compatible = "renesas,r9a09g057-rspi", &rzv2h_info }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, rzv2h_rspi_match); From ebd7d6ae0dc7d65e21460c928519f40ccf95f3b9 Mon Sep 17 00:00:00 2001 From: Cosmin Tanislav Date: Wed, 19 Nov 2025 18:14:24 +0200 Subject: [PATCH 59/80] spi: rzv2h-rspi: make clocks chip-specific The Renesas RZ/T2H (R9A09G077) and RZ/N2H (R9A09G087) SoCs have different clocks compared to RZ/V2H. Set the number of clocks and the name of the transfer clock in the chip-specific structure to prepare for adding support for them. Signed-off-by: Cosmin Tanislav Link: https://patch.msgid.link/20251119161434.595677-4-cosmin-gabriel.tanislav.xa@renesas.com Signed-off-by: Mark Brown --- drivers/spi/spi-rzv2h-rspi.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/drivers/spi/spi-rzv2h-rspi.c b/drivers/spi/spi-rzv2h-rspi.c index 7a7a576c17dd..a1f17ec8727b 100644 --- a/drivers/spi/spi-rzv2h-rspi.c +++ b/drivers/spi/spi-rzv2h-rspi.c @@ -66,10 +66,11 @@ #define RSPI_SPSRC_CLEAR 0xfd80 #define RSPI_RESET_NUM 2 -#define RSPI_CLK_NUM 3 struct rzv2h_rspi_info { + const char *tclk_name; unsigned int fifo_size; + unsigned int num_clks; }; struct rzv2h_rspi_priv { @@ -373,11 +374,11 @@ static int rzv2h_rspi_probe(struct platform_device *pdev) return PTR_ERR(rspi->base); ret = devm_clk_bulk_get_all_enabled(dev, &clks); - if (ret != RSPI_CLK_NUM) + if (ret != rspi->info->num_clks) return dev_err_probe(dev, ret >= 0 ? -EINVAL : ret, "cannot get clocks\n"); - for (i = 0; i < RSPI_CLK_NUM; i++) { - if (!strcmp(clks[i].id, "tclk")) { + for (i = 0; i < rspi->info->num_clks; i++) { + if (!strcmp(clks[i].id, rspi->info->tclk_name)) { rspi->tclk = clks[i].clk; break; } @@ -452,7 +453,9 @@ static void rzv2h_rspi_remove(struct platform_device *pdev) } static const struct rzv2h_rspi_info rzv2h_info = { + .tclk_name = "tclk", .fifo_size = 16, + .num_clks = 3, }; static const struct of_device_id rzv2h_rspi_match[] = { From 1b7ce968ab2579702ea9dbc2fb599e540bbd8c88 Mon Sep 17 00:00:00 2001 From: Cosmin Tanislav Date: Wed, 19 Nov 2025 18:14:25 +0200 Subject: [PATCH 60/80] spi: rzv2h-rspi: move register writes out of rzv2h_rspi_setup_clock() In preparation for caching the last requested transfer frequency, move register writes outside of rzv2h_rspi_setup_clock(). The transfer list is iterated to determine the speed of the transfer and the bits per word. The speed of the transfer is used to compute SPR and BRDV inside rzv2h_rspi_setup_clock(). BRDV and SPB are stored in the SPCMD register. Move the transfer iteration earlier, move the SPR and BRDV writing out of rzv2h_rspi_setup_clock(), consolidate writing BRDV and SPB into the initial write to the SPCMD register. Signed-off-by: Cosmin Tanislav Link: https://patch.msgid.link/20251119161434.595677-5-cosmin-gabriel.tanislav.xa@renesas.com Signed-off-by: Mark Brown --- drivers/spi/spi-rzv2h-rspi.c | 45 ++++++++++++++++++++---------------- 1 file changed, 25 insertions(+), 20 deletions(-) diff --git a/drivers/spi/spi-rzv2h-rspi.c b/drivers/spi/spi-rzv2h-rspi.c index a1f17ec8727b..f02f25b98ec6 100644 --- a/drivers/spi/spi-rzv2h-rspi.c +++ b/drivers/spi/spi-rzv2h-rspi.c @@ -83,6 +83,8 @@ struct rzv2h_rspi_priv { unsigned int bytes_per_word; u32 freq; u16 status; + u8 spr; + u8 brdv; }; #define RZV2H_RSPI_TX(func, type) \ @@ -263,8 +265,8 @@ static u32 rzv2h_rspi_setup_clock(struct rzv2h_rspi_priv *rspi, u32 hz) return 0; clock_found: - rzv2h_rspi_reg_rmw(rspi, RSPI_SPCMD, RSPI_SPCMD_BRDV, brdv); - writeb(spr, rspi->base + RSPI_SPBR); + rspi->spr = spr; + rspi->brdv = brdv; return rzv2h_rspi_calc_bitrate(tclk_rate, spr, brdv); } @@ -283,6 +285,25 @@ static int rzv2h_rspi_prepare_message(struct spi_controller *ctlr, /* Make sure SPCR.SPE is 0 before amending the configuration */ rzv2h_rspi_spe_disable(rspi); + list_for_each_entry(xfer, &message->transfers, transfer_list) { + if (!xfer->speed_hz) + continue; + + speed_hz = min(xfer->speed_hz, speed_hz); + bits_per_word = xfer->bits_per_word; + } + + if (speed_hz == U32_MAX) + return -EINVAL; + + rspi->bytes_per_word = roundup_pow_of_two(BITS_TO_BYTES(bits_per_word)); + + rspi->freq = rzv2h_rspi_setup_clock(rspi, speed_hz); + if (!rspi->freq) + return -EINVAL; + + writeb(rspi->spr, rspi->base + RSPI_SPBR); + /* Configure the device to work in "host" mode */ conf32 = RSPI_SPCR_MSTR; @@ -301,6 +322,8 @@ static int rzv2h_rspi_prepare_message(struct spi_controller *ctlr, conf32 = FIELD_PREP(RSPI_SPCMD_CPOL, !!(spi->mode & SPI_CPOL)); conf32 |= FIELD_PREP(RSPI_SPCMD_CPHA, !!(spi->mode & SPI_CPHA)); conf32 |= FIELD_PREP(RSPI_SPCMD_LSBF, !!(spi->mode & SPI_LSB_FIRST)); + conf32 |= FIELD_PREP(RSPI_SPCMD_SPB, bits_per_word - 1); + conf32 |= FIELD_PREP(RSPI_SPCMD_BRDV, rspi->brdv); conf32 |= FIELD_PREP(RSPI_SPCMD_SSLKP, 1); conf32 |= FIELD_PREP(RSPI_SPCMD_SSLA, spi_get_chipselect(spi, 0)); writel(conf32, rspi->base + RSPI_SPCMD); @@ -316,24 +339,6 @@ static int rzv2h_rspi_prepare_message(struct spi_controller *ctlr, rzv2h_rspi_clear_fifos(rspi); - list_for_each_entry(xfer, &message->transfers, transfer_list) { - if (!xfer->speed_hz) - continue; - - speed_hz = min(xfer->speed_hz, speed_hz); - bits_per_word = xfer->bits_per_word; - } - - if (speed_hz == U32_MAX) - return -EINVAL; - - rspi->bytes_per_word = roundup_pow_of_two(BITS_TO_BYTES(bits_per_word)); - rzv2h_rspi_reg_rmw(rspi, RSPI_SPCMD, RSPI_SPCMD_SPB, bits_per_word - 1); - - rspi->freq = rzv2h_rspi_setup_clock(rspi, speed_hz); - if (!rspi->freq) - return -EINVAL; - rzv2h_rspi_spe_enable(rspi); return 0; From 88782493204512fcf4e020e2385bca3e3c5bd4c0 Mon Sep 17 00:00:00 2001 From: Cosmin Tanislav Date: Wed, 19 Nov 2025 18:14:26 +0200 Subject: [PATCH 61/80] spi: rzv2h-rspi: avoid recomputing transfer frequency Renesas RZ/T2H (R9A09G077) and RZ/N2H (R9A09G087) SoCs have a more complicated algorithm for calculating the optimal SPI transfer frequency compared to RZ/V2H, as the clock from which the SPI frequency is generated supports multiple dividers. Cache the requested transfer frequency and skip calling rzv2h_rspi_setup_clock() if it matches the last used one to prepare for adding support for variable clock frequency handling. Signed-off-by: Cosmin Tanislav Link: https://patch.msgid.link/20251119161434.595677-6-cosmin-gabriel.tanislav.xa@renesas.com Signed-off-by: Mark Brown --- drivers/spi/spi-rzv2h-rspi.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/drivers/spi/spi-rzv2h-rspi.c b/drivers/spi/spi-rzv2h-rspi.c index f02f25b98ec6..d7719f3c7b13 100644 --- a/drivers/spi/spi-rzv2h-rspi.c +++ b/drivers/spi/spi-rzv2h-rspi.c @@ -81,6 +81,7 @@ struct rzv2h_rspi_priv { struct clk *tclk; wait_queue_head_t wait; unsigned int bytes_per_word; + u32 last_speed_hz; u32 freq; u16 status; u8 spr; @@ -298,9 +299,13 @@ static int rzv2h_rspi_prepare_message(struct spi_controller *ctlr, rspi->bytes_per_word = roundup_pow_of_two(BITS_TO_BYTES(bits_per_word)); - rspi->freq = rzv2h_rspi_setup_clock(rspi, speed_hz); - if (!rspi->freq) - return -EINVAL; + if (speed_hz != rspi->last_speed_hz) { + rspi->freq = rzv2h_rspi_setup_clock(rspi, speed_hz); + if (!rspi->freq) + return -EINVAL; + + rspi->last_speed_hz = speed_hz; + } writeb(rspi->spr, rspi->base + RSPI_SPBR); From 77d931584dd38916b66c65320c80a65cbef4b122 Mon Sep 17 00:00:00 2001 From: Cosmin Tanislav Date: Wed, 19 Nov 2025 18:14:27 +0200 Subject: [PATCH 62/80] spi: rzv2h-rspi: make transfer clock rate finding chip-specific The Renesas RZ/T2H (R9A09G077) and RZ/N2H (R9A09G087) SoCs have a more complicated clocking setup for the SPI transfer clock than RZ/V2H, as the clock from which it is generated supports multiple dividers. To prepare for adding support for these SoCs, split out the logic for finding the SPR and BRDV for a fixed clock into rzv2h_rspi_find_rate_fixed(), and add and use a .find_tclk_rate() callback into the chip-specific structure. Signed-off-by: Cosmin Tanislav Link: https://patch.msgid.link/20251119161434.595677-7-cosmin-gabriel.tanislav.xa@renesas.com Signed-off-by: Mark Brown --- drivers/spi/spi-rzv2h-rspi.c | 62 ++++++++++++++++++++++++++++++------ 1 file changed, 53 insertions(+), 9 deletions(-) diff --git a/drivers/spi/spi-rzv2h-rspi.c b/drivers/spi/spi-rzv2h-rspi.c index d7719f3c7b13..f59bcadf5e38 100644 --- a/drivers/spi/spi-rzv2h-rspi.c +++ b/drivers/spi/spi-rzv2h-rspi.c @@ -67,7 +67,18 @@ #define RSPI_RESET_NUM 2 +struct rzv2h_rspi_best_clock { + struct clk *clk; + unsigned long clk_rate; + unsigned long error; + u32 actual_hz; + u8 brdv; + u8 spr; +}; + struct rzv2h_rspi_info { + void (*find_tclk_rate)(struct clk *clk, u32 hz, u8 spr_min, u8 spr_max, + struct rzv2h_rspi_best_clock *best_clk); const char *tclk_name; unsigned int fifo_size; unsigned int num_clks; @@ -240,9 +251,13 @@ static inline u32 rzv2h_rspi_calc_bitrate(unsigned long tclk_rate, u8 spr, return DIV_ROUND_UP(tclk_rate, (2 * (spr + 1) * (1 << brdv))); } -static u32 rzv2h_rspi_setup_clock(struct rzv2h_rspi_priv *rspi, u32 hz) +static void rzv2h_rspi_find_rate_fixed(struct clk *clk, u32 hz, + u8 spr_min, u8 spr_max, + struct rzv2h_rspi_best_clock *best) { - unsigned long tclk_rate; + unsigned long clk_rate; + unsigned long error; + u32 actual_hz; int spr; u8 brdv; @@ -255,21 +270,49 @@ static u32 rzv2h_rspi_setup_clock(struct rzv2h_rspi_priv *rspi, u32 hz) * * n = SPR - is RSPI_SPBR.SPR (from 0 to 255) * * N = BRDV - is RSPI_SPCMD.BRDV (from 0 to 3) */ - tclk_rate = clk_get_rate(rspi->tclk); + clk_rate = clk_get_rate(clk); for (brdv = RSPI_SPCMD_BRDV_MIN; brdv <= RSPI_SPCMD_BRDV_MAX; brdv++) { - spr = DIV_ROUND_UP(tclk_rate, hz * (1 << (brdv + 1))); + spr = DIV_ROUND_UP(clk_rate, hz * (1 << (brdv + 1))); spr--; - if (spr >= RSPI_SPBR_SPR_MIN && spr <= RSPI_SPBR_SPR_MAX) + if (spr >= spr_min && spr <= spr_max) goto clock_found; } - return 0; + return; clock_found: - rspi->spr = spr; - rspi->brdv = brdv; + actual_hz = rzv2h_rspi_calc_bitrate(clk_rate, spr, brdv); + error = abs((long)hz - (long)actual_hz); - return rzv2h_rspi_calc_bitrate(tclk_rate, spr, brdv); + if (error >= best->error) + return; + + *best = (struct rzv2h_rspi_best_clock) { + .clk = clk, + .clk_rate = clk_rate, + .error = error, + .actual_hz = actual_hz, + .brdv = brdv, + .spr = spr, + }; +} + +static u32 rzv2h_rspi_setup_clock(struct rzv2h_rspi_priv *rspi, u32 hz) +{ + struct rzv2h_rspi_best_clock best_clock = { + .error = ULONG_MAX, + }; + + rspi->info->find_tclk_rate(rspi->tclk, hz, RSPI_SPBR_SPR_MIN, + RSPI_SPBR_SPR_MAX, &best_clock); + + if (!best_clock.clk_rate) + return -EINVAL; + + rspi->spr = best_clock.spr; + rspi->brdv = best_clock.brdv; + + return best_clock.actual_hz; } static int rzv2h_rspi_prepare_message(struct spi_controller *ctlr, @@ -463,6 +506,7 @@ static void rzv2h_rspi_remove(struct platform_device *pdev) } static const struct rzv2h_rspi_info rzv2h_info = { + .find_tclk_rate = rzv2h_rspi_find_rate_fixed, .tclk_name = "tclk", .fifo_size = 16, .num_clks = 3, From 1ce3e8adc7d0038e59a7c9f5c9e5f399ba0db5d6 Mon Sep 17 00:00:00 2001 From: Cosmin Tanislav Date: Wed, 19 Nov 2025 18:14:28 +0200 Subject: [PATCH 63/80] spi: rzv2h-rspi: add support for using PCLK for transfer clock The Renesas RZ/T2H (R9A09G077) and RZ/N2H (R9A09G087) SoCs support generating the SPI transfer clock from PCLK, with the quirk that SPR 0 is not supported, causing the highest achievable SPI transfer frequency to be 31.25MHz. Add support for generating the SPI transfer clock from PCLK. Renesas RZ/V2H (R9A09G057) also has the BPEN bit used to enable this option in the datasheet, but it is not explicitly documented and there's no details about its limitations as there are on RZ/T2H. Signed-off-by: Cosmin Tanislav Link: https://patch.msgid.link/20251119161434.595677-8-cosmin-gabriel.tanislav.xa@renesas.com Signed-off-by: Mark Brown --- drivers/spi/spi-rzv2h-rspi.c | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/drivers/spi/spi-rzv2h-rspi.c b/drivers/spi/spi-rzv2h-rspi.c index f59bcadf5e38..e9d8ee919261 100644 --- a/drivers/spi/spi-rzv2h-rspi.c +++ b/drivers/spi/spi-rzv2h-rspi.c @@ -34,6 +34,7 @@ #define RSPI_SPFCR 0x6c /* Register SPCR */ +#define RSPI_SPCR_BPEN BIT(31) #define RSPI_SPCR_MSTR BIT(30) #define RSPI_SPCR_SPRIE BIT(17) #define RSPI_SPCR_SCKASE BIT(12) @@ -41,6 +42,7 @@ /* Register SPBR */ #define RSPI_SPBR_SPR_MIN 0 +#define RSPI_SPBR_SPR_PCLK_MIN 1 #define RSPI_SPBR_SPR_MAX 255 /* Register SPCMD */ @@ -79,6 +81,8 @@ struct rzv2h_rspi_best_clock { struct rzv2h_rspi_info { void (*find_tclk_rate)(struct clk *clk, u32 hz, u8 spr_min, u8 spr_max, struct rzv2h_rspi_best_clock *best_clk); + void (*find_pclk_rate)(struct clk *clk, u32 hz, u8 spr_low, u8 spr_high, + struct rzv2h_rspi_best_clock *best_clk); const char *tclk_name; unsigned int fifo_size; unsigned int num_clks; @@ -90,6 +94,7 @@ struct rzv2h_rspi_priv { const struct rzv2h_rspi_info *info; void __iomem *base; struct clk *tclk; + struct clk *pclk; wait_queue_head_t wait; unsigned int bytes_per_word; u32 last_speed_hz; @@ -97,6 +102,7 @@ struct rzv2h_rspi_priv { u16 status; u8 spr; u8 brdv; + bool use_pclk; }; #define RZV2H_RSPI_TX(func, type) \ @@ -306,9 +312,18 @@ static u32 rzv2h_rspi_setup_clock(struct rzv2h_rspi_priv *rspi, u32 hz) rspi->info->find_tclk_rate(rspi->tclk, hz, RSPI_SPBR_SPR_MIN, RSPI_SPBR_SPR_MAX, &best_clock); + /* + * T2H and N2H can also use PCLK as a source, which is 125MHz, but not + * when both SPR and BRDV are 0. + */ + if (best_clock.error && rspi->info->find_pclk_rate) + rspi->info->find_pclk_rate(rspi->pclk, hz, RSPI_SPBR_SPR_PCLK_MIN, + RSPI_SPBR_SPR_MAX, &best_clock); + if (!best_clock.clk_rate) return -EINVAL; + rspi->use_pclk = best_clock.clk == rspi->pclk; rspi->spr = best_clock.spr; rspi->brdv = best_clock.brdv; @@ -361,6 +376,9 @@ static int rzv2h_rspi_prepare_message(struct spi_controller *ctlr, /* SPI receive buffer full interrupt enable */ conf32 |= RSPI_SPCR_SPRIE; + /* Bypass synchronization circuit */ + conf32 |= FIELD_PREP(RSPI_SPCR_BPEN, rspi->use_pclk); + writel(conf32, rspi->base + RSPI_SPCR); /* Use SPCMD0 only */ @@ -433,7 +451,9 @@ static int rzv2h_rspi_probe(struct platform_device *pdev) for (i = 0; i < rspi->info->num_clks; i++) { if (!strcmp(clks[i].id, rspi->info->tclk_name)) { rspi->tclk = clks[i].clk; - break; + } else if (rspi->info->find_pclk_rate && + !strcmp(clks[i].id, "pclk")) { + rspi->pclk = clks[i].clk; } } From 9c9bf4fdc5e5d09d5f4280ed2c582df6e1f837d9 Mon Sep 17 00:00:00 2001 From: Cosmin Tanislav Date: Wed, 19 Nov 2025 18:14:29 +0200 Subject: [PATCH 64/80] spi: rzv2h-rspi: add support for variable transfer clock The Renesas RZ/T2H (R9A09G077) and RZ/N2H (R9A09G087) SoCs have a more complicated clocking setup for the SPI transfer clock than RZ/V2H, as the clock from which it is generated supports multiple dividers. To prepare for adding support for these SoCs, do the following changes. Use the minimum frequency of SPI clock to calculate the SPI controller's min_speed_hz, and the maximum frequency to calculate max_speed_hz. Apply the clock rate found by the .find_tclk_rate() to the found clock. Signed-off-by: Cosmin Tanislav Link: https://patch.msgid.link/20251119161434.595677-9-cosmin-gabriel.tanislav.xa@renesas.com Signed-off-by: Mark Brown --- drivers/spi/spi-rzv2h-rspi.c | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/drivers/spi/spi-rzv2h-rspi.c b/drivers/spi/spi-rzv2h-rspi.c index e9d8ee919261..be45269e8853 100644 --- a/drivers/spi/spi-rzv2h-rspi.c +++ b/drivers/spi/spi-rzv2h-rspi.c @@ -308,6 +308,7 @@ static u32 rzv2h_rspi_setup_clock(struct rzv2h_rspi_priv *rspi, u32 hz) struct rzv2h_rspi_best_clock best_clock = { .error = ULONG_MAX, }; + int ret; rspi->info->find_tclk_rate(rspi->tclk, hz, RSPI_SPBR_SPR_MIN, RSPI_SPBR_SPR_MAX, &best_clock); @@ -323,6 +324,10 @@ static u32 rzv2h_rspi_setup_clock(struct rzv2h_rspi_priv *rspi, u32 hz) if (!best_clock.clk_rate) return -EINVAL; + ret = clk_set_rate(best_clock.clk, best_clock.clk_rate); + if (ret) + return 0; + rspi->use_pclk = best_clock.clk == rspi->pclk; rspi->spr = best_clock.spr; rspi->brdv = best_clock.brdv; @@ -426,8 +431,8 @@ static int rzv2h_rspi_probe(struct platform_device *pdev) struct device *dev = &pdev->dev; struct rzv2h_rspi_priv *rspi; struct clk_bulk_data *clks; - unsigned long tclk_rate; int irq_rx, ret, i; + long tclk_rate; controller = devm_spi_alloc_host(dev, sizeof(*rspi)); if (!controller) @@ -460,8 +465,6 @@ static int rzv2h_rspi_probe(struct platform_device *pdev) if (!rspi->tclk) return dev_err_probe(dev, -EINVAL, "Failed to get tclk\n"); - tclk_rate = clk_get_rate(rspi->tclk); - rspi->resets[0].id = "presetn"; rspi->resets[1].id = "tresetn"; ret = devm_reset_control_bulk_get_optional_exclusive(dev, RSPI_RESET_NUM, @@ -493,9 +496,23 @@ static int rzv2h_rspi_probe(struct platform_device *pdev) controller->unprepare_message = rzv2h_rspi_unprepare_message; controller->num_chipselect = 4; controller->transfer_one = rzv2h_rspi_transfer_one; + + tclk_rate = clk_round_rate(rspi->tclk, 0); + if (tclk_rate < 0) { + ret = tclk_rate; + goto quit_resets; + } + controller->min_speed_hz = rzv2h_rspi_calc_bitrate(tclk_rate, RSPI_SPBR_SPR_MAX, RSPI_SPCMD_BRDV_MAX); + + tclk_rate = clk_round_rate(rspi->tclk, ULONG_MAX); + if (tclk_rate < 0) { + ret = tclk_rate; + goto quit_resets; + } + controller->max_speed_hz = rzv2h_rspi_calc_bitrate(tclk_rate, RSPI_SPBR_SPR_MIN, RSPI_SPCMD_BRDV_MIN); From bc4f0b1e39035b9bb3d5d9692074702110f5e2b1 Mon Sep 17 00:00:00 2001 From: Cosmin Tanislav Date: Wed, 19 Nov 2025 18:14:30 +0200 Subject: [PATCH 65/80] spi: rzv2h-rspi: add support for loopback mode Add support for loopback mode for debugging purposes, allowing us to test the SPI controller at the maximum SPI transfer clock without being limited by external wiring. Signed-off-by: Cosmin Tanislav Link: https://patch.msgid.link/20251119161434.595677-10-cosmin-gabriel.tanislav.xa@renesas.com Signed-off-by: Mark Brown --- drivers/spi/spi-rzv2h-rspi.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/drivers/spi/spi-rzv2h-rspi.c b/drivers/spi/spi-rzv2h-rspi.c index be45269e8853..da110efba971 100644 --- a/drivers/spi/spi-rzv2h-rspi.c +++ b/drivers/spi/spi-rzv2h-rspi.c @@ -24,6 +24,7 @@ /* Registers */ #define RSPI_SPDR 0x00 #define RSPI_SPCR 0x08 +#define RSPI_SPPCR 0x0e #define RSPI_SSLP 0x10 #define RSPI_SPBR 0x11 #define RSPI_SPSCR 0x13 @@ -40,6 +41,9 @@ #define RSPI_SPCR_SCKASE BIT(12) #define RSPI_SPCR_SPE BIT(0) +/* Register SPPCR */ +#define RSPI_SPPCR_SPLP2 BIT(1) + /* Register SPBR */ #define RSPI_SPBR_SPR_MIN 0 #define RSPI_SPBR_SPR_PCLK_MIN 1 @@ -345,6 +349,7 @@ static int rzv2h_rspi_prepare_message(struct spi_controller *ctlr, u8 bits_per_word; u32 conf32; u16 conf16; + u8 conf8; /* Make sure SPCR.SPE is 0 before amending the configuration */ rzv2h_rspi_spe_disable(rspi); @@ -389,6 +394,10 @@ static int rzv2h_rspi_prepare_message(struct spi_controller *ctlr, /* Use SPCMD0 only */ writeb(0x0, rspi->base + RSPI_SPSCR); + /* Setup loopback */ + conf8 = FIELD_PREP(RSPI_SPPCR_SPLP2, !!(spi->mode & SPI_LOOP)); + writeb(conf8, rspi->base + RSPI_SPPCR); + /* Setup mode */ conf32 = FIELD_PREP(RSPI_SPCMD_CPOL, !!(spi->mode & SPI_CPOL)); conf32 |= FIELD_PREP(RSPI_SPCMD_CPHA, !!(spi->mode & SPI_CPHA)); @@ -490,7 +499,7 @@ static int rzv2h_rspi_probe(struct platform_device *pdev) } controller->mode_bits = SPI_CPHA | SPI_CPOL | SPI_CS_HIGH | - SPI_LSB_FIRST; + SPI_LSB_FIRST | SPI_LOOP; controller->bits_per_word_mask = SPI_BPW_RANGE_MASK(4, 32); controller->prepare_message = rzv2h_rspi_prepare_message; controller->unprepare_message = rzv2h_rspi_unprepare_message; From e93d7b2d8b349f659fa9456048ee86e10eb422f9 Mon Sep 17 00:00:00 2001 From: Cosmin Tanislav Date: Wed, 19 Nov 2025 18:14:31 +0200 Subject: [PATCH 66/80] spi: dt-bindings: renesas,rzv2h-rspi: document RZ/T2H and RZ/N2H The Renesas RZ/T2H (R9A09G077) and RZ/N2H (R9A09G087) SoCs have four SPI peripherals. Compared to the previously supported RZ/V2H, these SoCs have a smaller FIFO, no resets, and only two clocks: PCLKSPIn and PCLK. PCLKSPIn, being the clock from which the SPI transfer clock is generated, is the equivalent of the TCLK from V2H. Document them, and use RZ/T2H as a fallback for RZ/N2H as the SPIs are entirely compatible. Signed-off-by: Cosmin Tanislav Acked-by: Conor Dooley Link: https://patch.msgid.link/20251119161434.595677-11-cosmin-gabriel.tanislav.xa@renesas.com Signed-off-by: Mark Brown --- .../bindings/spi/renesas,rzv2h-rspi.yaml | 62 ++++++++++++++++--- 1 file changed, 52 insertions(+), 10 deletions(-) diff --git a/Documentation/devicetree/bindings/spi/renesas,rzv2h-rspi.yaml b/Documentation/devicetree/bindings/spi/renesas,rzv2h-rspi.yaml index ab27fefc3c3a..4331df3e3d47 100644 --- a/Documentation/devicetree/bindings/spi/renesas,rzv2h-rspi.yaml +++ b/Documentation/devicetree/bindings/spi/renesas,rzv2h-rspi.yaml @@ -9,12 +9,15 @@ title: Renesas RZ/V2H(P) Renesas Serial Peripheral Interface (RSPI) maintainers: - Fabrizio Castro -allOf: - - $ref: spi-controller.yaml# - properties: compatible: - const: renesas,r9a09g057-rspi # RZ/V2H(P) + oneOf: + - enum: + - renesas,r9a09g057-rspi # RZ/V2H(P) + - renesas,r9a09g077-rspi # RZ/T2H + - items: + - const: renesas,r9a09g087-rspi # RZ/N2H + - const: renesas,r9a09g077-rspi # RZ/T2H reg: maxItems: 1 @@ -36,13 +39,12 @@ properties: - const: tx clocks: + minItems: 2 maxItems: 3 clock-names: - items: - - const: pclk - - const: pclk_sfr - - const: tclk + minItems: 2 + maxItems: 3 resets: maxItems: 2 @@ -62,12 +64,52 @@ required: - interrupt-names - clocks - clock-names - - resets - - reset-names - power-domains - '#address-cells' - '#size-cells' +allOf: + - $ref: spi-controller.yaml# + - if: + properties: + compatible: + contains: + enum: + - renesas,r9a09g057-rspi + then: + properties: + clocks: + minItems: 3 + + clock-names: + items: + - const: pclk + - const: pclk_sfr + - const: tclk + + required: + - resets + - reset-names + + - if: + properties: + compatible: + contains: + enum: + - renesas,r9a09g077-rspi + then: + properties: + clocks: + maxItems: 2 + + clock-names: + items: + - const: pclk + - const: pclkspi + + resets: false + reset-names: false + unevaluatedProperties: false examples: From 0cc8cd824b9fb7fb087a2ec6b0c80d812cc4fde7 Mon Sep 17 00:00:00 2001 From: Cosmin Tanislav Date: Wed, 19 Nov 2025 18:14:32 +0200 Subject: [PATCH 67/80] spi: rzv2h-rspi: add support for RZ/T2H and RZ/N2H Compared to the previously supported RZ/V2H, the Renesas RZ/T2H (R9A09G077) and RZ/N2H (R9A09G087) SoCs have a smaller FIFO, no resets, and only two clocks: PCLKSPIn and PCLK. PCLKSPIn, being the clock from which the SPI transfer clock is generated, is the equivalent of the TCLK clock from RZ/V2H. They also support generating the SPI transfer clock from PCLK. PCLKSPIn supports multiple dividers, generating multiple possible frequencies from its parent. To handle this, do the following changes. Use the minimum frequency of SPI clock to calculate the SPI controller's min_speed_hz, and the maximum frequency to calculate max_speed_hz. Add a new function, rzv2h_rspi_find_rate_variable(), which is used for the .find_tclk_rate() callback, and which supports handling clocks with a variable rate, with the following overall logic. Iterate through all possible BRDV values. For each BRDV, calculate two different SPRs, one for the clock's minimum frequency, and one for the maxmimum, and iterate through each SPR between them. If the minimum SPR is higher than the upper SPR limit, the minimum rate is too high to achieve the requested SPI frequency, skip to the next BRDV. For each SPR, calculate a rate and let the clock framework round it to the closest supported rate of the clock. The rate and SPR that generate a transfer frequency closest to the requested SPI transfer frequency will be picked. Signed-off-by: Cosmin Tanislav Link: https://patch.msgid.link/20251119161434.595677-12-cosmin-gabriel.tanislav.xa@renesas.com Signed-off-by: Mark Brown --- drivers/spi/spi-rzv2h-rspi.c | 108 +++++++++++++++++++++++++++++++++++ 1 file changed, 108 insertions(+) diff --git a/drivers/spi/spi-rzv2h-rspi.c b/drivers/spi/spi-rzv2h-rspi.c index da110efba971..1db7e4e5d64e 100644 --- a/drivers/spi/spi-rzv2h-rspi.c +++ b/drivers/spi/spi-rzv2h-rspi.c @@ -261,6 +261,105 @@ static inline u32 rzv2h_rspi_calc_bitrate(unsigned long tclk_rate, u8 spr, return DIV_ROUND_UP(tclk_rate, (2 * (spr + 1) * (1 << brdv))); } +static void rzv2h_rspi_find_rate_variable(struct clk *clk, u32 hz, + u8 spr_min, u8 spr_max, + struct rzv2h_rspi_best_clock *best) +{ + long clk_rate, clk_min_rate, clk_max_rate; + int min_rate_spr, max_rate_spr; + unsigned long error; + u32 actual_hz; + u8 brdv; + int spr; + + /* + * On T2H / N2H, the source for the SPI clock is PCLKSPIn, which is a + * 1/32, 1/30, 1/25 or 1/24 divider of PLL4, which is 2400MHz, + * resulting in either 75MHz, 80MHz, 96MHz or 100MHz. + */ + clk_min_rate = clk_round_rate(clk, 0); + if (clk_min_rate < 0) + return; + + clk_max_rate = clk_round_rate(clk, ULONG_MAX); + if (clk_max_rate < 0) + return; + + /* + * From the manual: + * Bit rate = f(PCLKSPIn) / (2 * (n + 1) * 2^N) + * + * If we adapt it to the current context, we get the following: + * hz = rate / ((spr + 1) * (1 << (brdv + 1))) + * + * This can be written in multiple forms depending on what we want to + * determine. + * + * To find the rate, having hz, spr and brdv: + * rate = hz * (spr + 1) * (1 << (brdv + 1) + * + * To find the spr, having rate, hz, and spr: + * spr = rate / (hz * (1 << (brdv + 1)) - 1 + */ + + for (brdv = RSPI_SPCMD_BRDV_MIN; brdv <= RSPI_SPCMD_BRDV_MAX; brdv++) { + /* Calculate the divisor needed to find the SPR from a rate. */ + u32 rate_div = hz * (1 << (brdv + 1)); + + /* + * If the SPR for the minimum rate is greater than the maximum + * allowed value skip this BRDV. The divisor increases with each + * BRDV iteration, so the following BRDV might result in a + * minimum SPR that is in the valid range. + */ + min_rate_spr = DIV_ROUND_CLOSEST(clk_min_rate, rate_div) - 1; + if (min_rate_spr > spr_max) + continue; + + /* + * If the SPR for the maximum rate is less than the minimum + * allowed value, exit. The divisor only increases with each + * BRDV iteration, so the following BRDV cannot result in a + * maximum SPR that is in the valid range. + */ + max_rate_spr = DIV_ROUND_CLOSEST(clk_max_rate, rate_div) - 1; + if (max_rate_spr < spr_min) + break; + + if (min_rate_spr < spr_min) + min_rate_spr = spr_min; + + if (max_rate_spr > spr_max) + max_rate_spr = spr_max; + + for (spr = min_rate_spr; spr <= max_rate_spr; spr++) { + clk_rate = (spr + 1) * rate_div; + + clk_rate = clk_round_rate(clk, clk_rate); + if (clk_rate <= 0) + continue; + + actual_hz = rzv2h_rspi_calc_bitrate(clk_rate, spr, brdv); + error = abs((long)hz - (long)actual_hz); + + if (error >= best->error) + continue; + + *best = (struct rzv2h_rspi_best_clock) { + .clk = clk, + .clk_rate = clk_rate, + .error = error, + .actual_hz = actual_hz, + .brdv = brdv, + .spr = spr, + }; + + if (!error) + return; + } + } +} + static void rzv2h_rspi_find_rate_fixed(struct clk *clk, u32 hz, u8 spr_min, u8 spr_max, struct rzv2h_rspi_best_clock *best) @@ -558,8 +657,17 @@ static const struct rzv2h_rspi_info rzv2h_info = { .num_clks = 3, }; +static const struct rzv2h_rspi_info rzt2h_info = { + .find_tclk_rate = rzv2h_rspi_find_rate_variable, + .find_pclk_rate = rzv2h_rspi_find_rate_fixed, + .tclk_name = "pclkspi", + .fifo_size = 4, + .num_clks = 2, +}; + static const struct of_device_id rzv2h_rspi_match[] = { { .compatible = "renesas,r9a09g057-rspi", &rzv2h_info }, + { .compatible = "renesas,r9a09g077-rspi", &rzt2h_info }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, rzv2h_rspi_match); From cb99656b7c4185953c9d272bbdab63c8aa651e6e Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Mon, 24 Nov 2025 10:56:01 +0300 Subject: [PATCH 68/80] spi: Fix potential uninitialized variable in probe() If the device tree is messed up, then potentially the "protocol" string could potentially be uninitialized. The property is supposed to default to "motorola" so if the of_property_read_string() function returns -EINVAL then default to "motorola". Fixes: 059f545832be ("spi: add support for microchip "soft" spi controller") Signed-off-by: Dan Carpenter Reviewed-by: Prajna Rajendra Kumar Link: https://patch.msgid.link/aSQPkfkiJ0w-FJMW@stanley.mountain Signed-off-by: Mark Brown --- drivers/spi/spi-microchip-core-spi.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/spi/spi-microchip-core-spi.c b/drivers/spi/spi-microchip-core-spi.c index b8738190cdcb..16e0885474a0 100644 --- a/drivers/spi/spi-microchip-core-spi.c +++ b/drivers/spi/spi-microchip-core-spi.c @@ -295,10 +295,10 @@ static int mchp_corespi_transfer_one(struct spi_controller *host, static int mchp_corespi_probe(struct platform_device *pdev) { + const char *protocol = "motorola"; struct spi_controller *host; struct mchp_corespi *spi; struct resource *res; - const char *protocol; u32 num_cs, mode, frame_size; bool assert_ssel; int ret = 0; @@ -320,6 +320,8 @@ static int mchp_corespi_probe(struct platform_device *pdev) */ ret = of_property_read_string(pdev->dev.of_node, "microchip,protocol-configuration", &protocol); + if (ret && ret != -EINVAL) + return dev_err_probe(&pdev->dev, ret, "Error reading protocol-configuration\n"); if (strcmp(protocol, "motorola") != 0) return dev_err_probe(&pdev->dev, -EINVAL, "CoreSPI: protocol '%s' not supported by this driver\n", From 84b7344c05c5e48db4cf75cd3e91aef8d553d88e Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Tue, 25 Nov 2025 17:06:56 +0100 Subject: [PATCH 69/80] spi: microchip: Enable compile-testing for FPGA SPI controllers The Microchip FPGA SPI controller driver builds fine on other platforms. While at it, drop a superfluous empty line. Signed-off-by: Geert Uytterhoeven Link: https://patch.msgid.link/6f96848b026f9a343b80d48179149b30c6b76d1d.1764086805.git.geert+renesas@glider.be Signed-off-by: Mark Brown --- drivers/spi/Kconfig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index e34aa246eef9..2421d9ca22b6 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -873,9 +873,9 @@ config SPI_PL022 bus and a PL022 controller, say Y or M here. config SPI_POLARFIRE_SOC - tristate "Microchip FPGA SPI controllers" - depends on SPI_MASTER && ARCH_MICROCHIP + depends on SPI_MASTER + depends on ARCH_MICROCHIP || COMPILE_TEST help This enables the SPI driver for Microchip FPGA SPI controllers. Say Y or M here if you want to use the "hard" controllers on From 043cc033451530f81d7fe791dcc29874f6a147fd Mon Sep 17 00:00:00 2001 From: Lad Prabhakar Date: Wed, 26 Nov 2025 13:16:19 +0000 Subject: [PATCH 70/80] spi: dt-bindings: renesas,rzv2h-rspi: Document RZ/V2N SoC support Document the RSPI controller on the Renesas RZ/V2N SoC. The block is compatible with the RSPI implementation found on the RZ/V2H(P) family. Signed-off-by: Lad Prabhakar Acked-by: Krzysztof Kozlowski Reviewed-by: Geert Uytterhoeven Link: https://patch.msgid.link/20251126131619.136605-1-prabhakar.mahadev-lad.rj@bp.renesas.com Signed-off-by: Mark Brown --- Documentation/devicetree/bindings/spi/renesas,rzv2h-rspi.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Documentation/devicetree/bindings/spi/renesas,rzv2h-rspi.yaml b/Documentation/devicetree/bindings/spi/renesas,rzv2h-rspi.yaml index 4331df3e3d47..069557a587b5 100644 --- a/Documentation/devicetree/bindings/spi/renesas,rzv2h-rspi.yaml +++ b/Documentation/devicetree/bindings/spi/renesas,rzv2h-rspi.yaml @@ -15,6 +15,9 @@ properties: - enum: - renesas,r9a09g057-rspi # RZ/V2H(P) - renesas,r9a09g077-rspi # RZ/T2H + - items: + - const: renesas,r9a09g056-rspi # RZ/V2N + - const: renesas,r9a09g057-rspi - items: - const: renesas,r9a09g087-rspi # RZ/N2H - const: renesas,r9a09g077-rspi # RZ/T2H From 625f43be3f50966bce1337d744f1bd78dd42ef64 Mon Sep 17 00:00:00 2001 From: Lad Prabhakar Date: Wed, 26 Nov 2025 13:16:19 +0000 Subject: [PATCH 71/80] spi: dt-bindings: renesas,rzv2h-rspi: Document RZ/V2N SoC support Document the RSPI controller on the Renesas RZ/V2N SoC. The block is compatible with the RSPI implementation found on the RZ/V2H(P) family. Signed-off-by: Lad Prabhakar Acked-by: Krzysztof Kozlowski Reviewed-by: Geert Uytterhoeven Link: https://patch.msgid.link/20251126131619.136605-1-prabhakar.mahadev-lad.rj@bp.renesas.com Signed-off-by: Mark Brown --- Documentation/devicetree/bindings/spi/renesas,rzv2h-rspi.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Documentation/devicetree/bindings/spi/renesas,rzv2h-rspi.yaml b/Documentation/devicetree/bindings/spi/renesas,rzv2h-rspi.yaml index 4331df3e3d47..069557a587b5 100644 --- a/Documentation/devicetree/bindings/spi/renesas,rzv2h-rspi.yaml +++ b/Documentation/devicetree/bindings/spi/renesas,rzv2h-rspi.yaml @@ -15,6 +15,9 @@ properties: - enum: - renesas,r9a09g057-rspi # RZ/V2H(P) - renesas,r9a09g077-rspi # RZ/T2H + - items: + - const: renesas,r9a09g056-rspi # RZ/V2N + - const: renesas,r9a09g057-rspi - items: - const: renesas,r9a09g087-rspi # RZ/N2H - const: renesas,r9a09g077-rspi # RZ/T2H From 061795b345aff371df8f71d54ae7c7dc8ae630d0 Mon Sep 17 00:00:00 2001 From: Mikhail Kshevetskiy Date: Wed, 26 Nov 2025 02:40:45 +0300 Subject: [PATCH 72/80] spi: airoha-snfi: en7523: workaround flash damaging if UART_TXD was short to GND Airoha EN7523 specific bug -------------------------- We found that some serial console may pull TX line to GROUND during board boot time. Airoha uses TX line as one of its bootstrap pins. On the EN7523 SoC this may lead to booting in RESERVED boot mode. It was found that some flashes operates incorrectly in RESERVED mode. Micron and Skyhigh flashes are definitely affected by the issue, Winbond flashes are not affected. Details: -------- DMA reading of odd pages on affected flashes operates incorrectly. Page reading offset (start of the page) on hardware level is replaced by 0x10. Thus results in incorrect data reading. As result OS loading becomes impossible. Usage of UBI make things even worse. On attaching, UBI will detects corruptions (because of wrong reading of odd pages) and will try to recover. For recovering UBI will erase and write 'damaged' blocks with a valid information. This will destroy all UBI data. Non-DMA reading is OK. This patch detects booting in reserved mode, turn off DMA and print big fat warning. It's worth noting that the boot configuration is preserved across reboots. Therefore, to boot normally, you should do the following: - disconnect the serial console from the board, - power cycle the board. Fixes: a403997c12019 ("spi: airoha: add SPI-NAND Flash controller driver") Signed-off-by: Mikhail Kshevetskiy Reviewed-by: AngeloGioacchino Del Regno Reviewed-by: Andy Shevchenko Link: https://patch.msgid.link/20251125234047.1101985-2-mikhail.kshevetskiy@iopsys.eu Signed-off-by: Mark Brown --- drivers/spi/spi-airoha-snfi.c | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/drivers/spi/spi-airoha-snfi.c b/drivers/spi/spi-airoha-snfi.c index 8408aee9c06e..70327aebc26b 100644 --- a/drivers/spi/spi-airoha-snfi.c +++ b/drivers/spi/spi-airoha-snfi.c @@ -1013,6 +1013,11 @@ static const struct spi_controller_mem_ops airoha_snand_mem_ops = { .dirmap_write = airoha_snand_dirmap_write, }; +static const struct spi_controller_mem_ops airoha_snand_nodma_mem_ops = { + .supports_op = airoha_snand_supports_op, + .exec_op = airoha_snand_exec_op, +}; + static int airoha_snand_setup(struct spi_device *spi) { struct airoha_snand_ctrl *as_ctrl; @@ -1057,7 +1062,9 @@ static int airoha_snand_probe(struct platform_device *pdev) struct airoha_snand_ctrl *as_ctrl; struct device *dev = &pdev->dev; struct spi_controller *ctrl; + bool dma_enable = true; void __iomem *base; + u32 sfc_strap; int err; ctrl = devm_spi_alloc_host(dev, sizeof(*as_ctrl)); @@ -1092,12 +1099,28 @@ static int airoha_snand_probe(struct platform_device *pdev) return dev_err_probe(dev, PTR_ERR(as_ctrl->spi_clk), "unable to get spi clk\n"); + if (device_is_compatible(dev, "airoha,en7523-snand")) { + err = regmap_read(as_ctrl->regmap_ctrl, + REG_SPI_CTRL_SFC_STRAP, &sfc_strap); + if (err) + return err; + + if (!(sfc_strap & 0x04)) { + dma_enable = false; + dev_warn(dev, "Detected booting in RESERVED mode (UART_TXD was short to GND).\n"); + dev_warn(dev, "This mode is known for incorrect DMA reading of some flashes.\n"); + dev_warn(dev, "Much slower PIO mode will be used to prevent flash data damage.\n"); + dev_warn(dev, "Unplug UART cable and power cycle board to get full performance.\n"); + } + } + err = dma_set_mask(as_ctrl->dev, DMA_BIT_MASK(32)); if (err) return err; ctrl->num_chipselect = 2; - ctrl->mem_ops = &airoha_snand_mem_ops; + ctrl->mem_ops = dma_enable ? &airoha_snand_mem_ops + : &airoha_snand_nodma_mem_ops; ctrl->bits_per_word_mask = SPI_BPW_MASK(8); ctrl->mode_bits = SPI_RX_DUAL; ctrl->setup = airoha_snand_setup; From de59a8a3a1aab3a6608777f62fa098b5abb2704a Mon Sep 17 00:00:00 2001 From: Mikhail Kshevetskiy Date: Wed, 26 Nov 2025 02:40:46 +0300 Subject: [PATCH 73/80] spi: dt-bindings: airoha: add compatible for EN7523 Add dt-bindings documentation of SPI NAND controller for Airoha EN7523 SoC platform. Signed-off-by: Mikhail Kshevetskiy Reviewed-by: Krzysztof Kozlowski Reviewed-by: AngeloGioacchino Del Regno Link: https://patch.msgid.link/20251125234047.1101985-3-mikhail.kshevetskiy@iopsys.eu Signed-off-by: Mark Brown --- .../devicetree/bindings/spi/airoha,en7581-snand.yaml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/spi/airoha,en7581-snand.yaml b/Documentation/devicetree/bindings/spi/airoha,en7581-snand.yaml index b820c5613dcc..855aa08995b9 100644 --- a/Documentation/devicetree/bindings/spi/airoha,en7581-snand.yaml +++ b/Documentation/devicetree/bindings/spi/airoha,en7581-snand.yaml @@ -14,7 +14,12 @@ allOf: properties: compatible: - const: airoha,en7581-snand + oneOf: + - const: airoha,en7581-snand + - items: + - enum: + - airoha,en7523-snand + - const: airoha,en7581-snand reg: items: From e29aca7038f3c292c18048922c5f4436a034da99 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Wed, 26 Nov 2025 08:54:39 +0100 Subject: [PATCH 74/80] spi: microchip-core: use min() instead of min_t() min_t(int, a, b) casts an 'unsigned int' to 'int'. This might lead to the cases when big number is wrongly chosen. On the other hand, the SPI transfer length is unsigned and driver uses signed type for an unknown reason. Change the type of the transfer length to be unsigned and convert use min() instead of min_t(). Signed-off-by: Andy Shevchenko Reviewed-by: David Laight Reviewed-by: Prajna Rajendra Kumar Link: https://patch.msgid.link/20251126075558.2035012-2-andriy.shevchenko@linux.intel.com Signed-off-by: Mark Brown --- drivers/spi/spi-microchip-core-spi.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/spi/spi-microchip-core-spi.c b/drivers/spi/spi-microchip-core-spi.c index 16e0885474a0..08ccdc5f0cc9 100644 --- a/drivers/spi/spi-microchip-core-spi.c +++ b/drivers/spi/spi-microchip-core-spi.c @@ -74,8 +74,8 @@ struct mchp_corespi { u8 *rx_buf; u32 clk_gen; int irq; - int tx_len; - int rx_len; + unsigned int tx_len; + unsigned int rx_len; u32 fifo_depth; }; @@ -214,7 +214,7 @@ static irqreturn_t mchp_corespi_interrupt(int irq, void *dev_id) spi->regs + MCHP_CORESPI_REG_INTCLEAR); finalise = true; dev_err(&host->dev, - "RX OVERFLOW: rxlen: %d, txlen: %d\n", + "RX OVERFLOW: rxlen: %u, txlen: %u\n", spi->rx_len, spi->tx_len); } @@ -223,7 +223,7 @@ static irqreturn_t mchp_corespi_interrupt(int irq, void *dev_id) spi->regs + MCHP_CORESPI_REG_INTCLEAR); finalise = true; dev_err(&host->dev, - "TX UNDERFLOW: rxlen: %d, txlen: %d\n", + "TX UNDERFLOW: rxlen: %u, txlen: %u\n", spi->rx_len, spi->tx_len); } @@ -283,7 +283,7 @@ static int mchp_corespi_transfer_one(struct spi_controller *host, spi->rx_len = xfer->len; while (spi->tx_len) { - int fifo_max = min_t(int, spi->tx_len, spi->fifo_depth); + unsigned int fifo_max = min(spi->tx_len, spi->fifo_depth); mchp_corespi_write_fifo(spi, fifo_max); mchp_corespi_read_fifo(spi, fifo_max); From 274b3458af1f9c665faae70b560852461c30acef Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Wed, 26 Nov 2025 08:54:41 +0100 Subject: [PATCH 75/80] spi: microchip-core: Replace dead code (-ENOMEM error message) First of all, the convention in the kernel that we do not issue error messages for -ENOMEM. Second, it's ignored by dev_err_probe(). Replace dead code by a simple return statement. Signed-off-by: Andy Shevchenko Reviewed-by: Prajna Rajendra Kumar Link: https://patch.msgid.link/20251126075558.2035012-4-andriy.shevchenko@linux.intel.com Signed-off-by: Mark Brown --- drivers/spi/spi-microchip-core-spi.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/spi/spi-microchip-core-spi.c b/drivers/spi/spi-microchip-core-spi.c index 08ccdc5f0cc9..ab31b9f9557a 100644 --- a/drivers/spi/spi-microchip-core-spi.c +++ b/drivers/spi/spi-microchip-core-spi.c @@ -305,8 +305,7 @@ static int mchp_corespi_probe(struct platform_device *pdev) host = devm_spi_alloc_host(&pdev->dev, sizeof(*spi)); if (!host) - return dev_err_probe(&pdev->dev, -ENOMEM, - "unable to allocate host for SPI controller\n"); + return -ENOMEM; platform_set_drvdata(pdev, host); From 06b010d3c778075108041074a8fb785074231ac4 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Wed, 26 Nov 2025 08:54:42 +0100 Subject: [PATCH 76/80] spi: microchip-core: Utilise temporary variable for struct device Add a temporary variable to keep a pointer to struct device. Utilise it where it makes sense. Signed-off-by: Andy Shevchenko Reviewed-by: Prajna Rajendra Kumar Link: https://patch.msgid.link/20251126075558.2035012-5-andriy.shevchenko@linux.intel.com Signed-off-by: Mark Brown --- drivers/spi/spi-microchip-core-spi.c | 44 +++++++++++++--------------- 1 file changed, 21 insertions(+), 23 deletions(-) diff --git a/drivers/spi/spi-microchip-core-spi.c b/drivers/spi/spi-microchip-core-spi.c index ab31b9f9557a..ceaaf95e6b80 100644 --- a/drivers/spi/spi-microchip-core-spi.c +++ b/drivers/spi/spi-microchip-core-spi.c @@ -296,6 +296,7 @@ static int mchp_corespi_transfer_one(struct spi_controller *host, static int mchp_corespi_probe(struct platform_device *pdev) { const char *protocol = "motorola"; + struct device *dev = &pdev->dev; struct spi_controller *host; struct mchp_corespi *spi; struct resource *res; @@ -303,13 +304,13 @@ static int mchp_corespi_probe(struct platform_device *pdev) bool assert_ssel; int ret = 0; - host = devm_spi_alloc_host(&pdev->dev, sizeof(*spi)); + host = devm_spi_alloc_host(dev, sizeof(*spi)); if (!host) return -ENOMEM; platform_set_drvdata(pdev, host); - if (of_property_read_u32(pdev->dev.of_node, "num-cs", &num_cs)) + if (of_property_read_u32(dev->of_node, "num-cs", &num_cs)) num_cs = MCHP_CORESPI_MAX_CS; /* @@ -317,12 +318,12 @@ static int mchp_corespi_probe(struct platform_device *pdev) * CoreSPI can be configured for Motorola, TI or NSC. * The current driver supports only Motorola mode. */ - ret = of_property_read_string(pdev->dev.of_node, "microchip,protocol-configuration", + ret = of_property_read_string(dev->of_node, "microchip,protocol-configuration", &protocol); if (ret && ret != -EINVAL) - return dev_err_probe(&pdev->dev, ret, "Error reading protocol-configuration\n"); + return dev_err_probe(dev, ret, "Error reading protocol-configuration\n"); if (strcmp(protocol, "motorola") != 0) - return dev_err_probe(&pdev->dev, -EINVAL, + return dev_err_probe(dev, -EINVAL, "CoreSPI: protocol '%s' not supported by this driver\n", protocol); @@ -330,11 +331,11 @@ static int mchp_corespi_probe(struct platform_device *pdev) * Motorola mode (0-3): CFG_MOT_MODE * Mode is fixed in the IP configurator. */ - ret = of_property_read_u32(pdev->dev.of_node, "microchip,motorola-mode", &mode); + ret = of_property_read_u32(dev->of_node, "microchip,motorola-mode", &mode); if (ret) mode = MCHP_CORESPI_DEFAULT_MOTOROLA_MODE; else if (mode > 3) - return dev_err_probe(&pdev->dev, -EINVAL, + return dev_err_probe(dev, -EINVAL, "invalid 'microchip,motorola-mode' value %u\n", mode); /* @@ -342,9 +343,9 @@ static int mchp_corespi_probe(struct platform_device *pdev) * The hardware allows frame sizes <= APB data width. * However, this driver currently only supports 8-bit frames. */ - ret = of_property_read_u32(pdev->dev.of_node, "microchip,frame-size", &frame_size); + ret = of_property_read_u32(dev->of_node, "microchip,frame-size", &frame_size); if (!ret && frame_size != 8) - return dev_err_probe(&pdev->dev, -EINVAL, + return dev_err_probe(dev, -EINVAL, "CoreSPI: frame size %u not supported by this driver\n", frame_size); @@ -354,9 +355,9 @@ static int mchp_corespi_probe(struct platform_device *pdev) * To prevent CS deassertion when TX FIFO drains, the ssel-active property * keeps CS asserted for the full SPI transfer. */ - assert_ssel = of_property_read_bool(pdev->dev.of_node, "microchip,ssel-active"); + assert_ssel = of_property_read_bool(dev->of_node, "microchip,ssel-active"); if (!assert_ssel) - return dev_err_probe(&pdev->dev, -EINVAL, + return dev_err_probe(dev, -EINVAL, "hardware must enable 'microchip,ssel-active' to keep CS asserted for the SPI transfer\n"); spi = spi_controller_get_devdata(host); @@ -368,9 +369,9 @@ static int mchp_corespi_probe(struct platform_device *pdev) host->bits_per_word_mask = SPI_BPW_RANGE_MASK(4, 32); host->transfer_one = mchp_corespi_transfer_one; host->set_cs = mchp_corespi_set_cs; - host->dev.of_node = pdev->dev.of_node; + host->dev.of_node = dev->of_node; - ret = of_property_read_u32(pdev->dev.of_node, "fifo-depth", &spi->fifo_depth); + ret = of_property_read_u32(dev->of_node, "fifo-depth", &spi->fifo_depth); if (ret) spi->fifo_depth = MCHP_CORESPI_DEFAULT_FIFO_DEPTH; @@ -382,24 +383,21 @@ static int mchp_corespi_probe(struct platform_device *pdev) if (spi->irq < 0) return spi->irq; - ret = devm_request_irq(&pdev->dev, spi->irq, mchp_corespi_interrupt, - IRQF_SHARED, dev_name(&pdev->dev), host); + ret = devm_request_irq(dev, spi->irq, mchp_corespi_interrupt, IRQF_SHARED, + dev_name(dev), host); if (ret) - return dev_err_probe(&pdev->dev, ret, - "could not request irq\n"); + return dev_err_probe(dev, ret, "could not request irq\n"); - spi->clk = devm_clk_get_enabled(&pdev->dev, NULL); + spi->clk = devm_clk_get_enabled(dev, NULL); if (IS_ERR(spi->clk)) - return dev_err_probe(&pdev->dev, PTR_ERR(spi->clk), - "could not get clk\n"); + return dev_err_probe(dev, PTR_ERR(spi->clk), "could not get clk\n"); mchp_corespi_init(host, spi); - ret = devm_spi_register_controller(&pdev->dev, host); + ret = devm_spi_register_controller(dev, host); if (ret) { mchp_corespi_disable(spi); - return dev_err_probe(&pdev->dev, ret, - "unable to register host for CoreSPI controller\n"); + return dev_err_probe(dev, ret, "unable to register host for CoreSPI controller\n"); } return 0; From 4db5a0705b1e03abb6ff4e7d7789b32c31384429 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Wed, 26 Nov 2025 08:54:43 +0100 Subject: [PATCH 77/80] spi: microchip-core: Use SPI_MODE_X_MASK Use SPI_MODE_X_MASK instead of open coded variant. Signed-off-by: Andy Shevchenko Reviewed-by: Prajna Rajendra Kumar Link: https://patch.msgid.link/20251126075558.2035012-6-andriy.shevchenko@linux.intel.com Signed-off-by: Mark Brown --- drivers/spi/spi-microchip-core-spi.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/spi/spi-microchip-core-spi.c b/drivers/spi/spi-microchip-core-spi.c index ceaaf95e6b80..25e92ba55c8c 100644 --- a/drivers/spi/spi-microchip-core-spi.c +++ b/drivers/spi/spi-microchip-core-spi.c @@ -160,8 +160,6 @@ static void mchp_corespi_set_cs(struct spi_device *spi, bool disable) static int mchp_corespi_setup(struct spi_device *spi) { - u32 dev_mode = spi->mode & (SPI_CPOL | SPI_CPHA); - if (spi_get_csgpiod(spi, 0)) return 0; @@ -170,7 +168,7 @@ static int mchp_corespi_setup(struct spi_device *spi) return -EOPNOTSUPP; } - if (dev_mode & ~spi->controller->mode_bits) { + if (spi->mode & SPI_MODE_X_MASK & ~spi->controller->mode_bits) { dev_err(&spi->dev, "incompatible CPOL/CPHA, must match controller's Motorola mode\n"); return -EINVAL; } From f458fc9b1946bc882a217d65bfe5ba50787f253f Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Wed, 26 Nov 2025 08:54:44 +0100 Subject: [PATCH 78/80] spi: microchip-core: Remove unneeded PM related macro Static declaration by default are 0 or NULL, no need to initialise them explicitly. Remove unneeded PM related macro. Signed-off-by: Andy Shevchenko Reviewed-by: Prajna Rajendra Kumar Link: https://patch.msgid.link/20251126075558.2035012-7-andriy.shevchenko@linux.intel.com Signed-off-by: Mark Brown --- drivers/spi/spi-microchip-core-spi.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/drivers/spi/spi-microchip-core-spi.c b/drivers/spi/spi-microchip-core-spi.c index 25e92ba55c8c..892f066f0074 100644 --- a/drivers/spi/spi-microchip-core-spi.c +++ b/drivers/spi/spi-microchip-core-spi.c @@ -410,8 +410,6 @@ static void mchp_corespi_remove(struct platform_device *pdev) mchp_corespi_disable(spi); } -#define MICROCHIP_SPI_PM_OPS (NULL) - /* * Platform driver data structure */ @@ -428,7 +426,6 @@ static struct platform_driver mchp_corespi_driver = { .probe = mchp_corespi_probe, .driver = { .name = "microchip-corespi", - .pm = MICROCHIP_SPI_PM_OPS, .of_match_table = of_match_ptr(mchp_corespi_dt_ids), }, .remove = mchp_corespi_remove, From 545d1287e40a55242f6ab68bcc1ba3b74088b1bc Mon Sep 17 00:00:00 2001 From: Tianchu Chen Date: Fri, 28 Nov 2025 16:06:30 +0800 Subject: [PATCH 79/80] spi: ch341: fix out-of-bounds memory access in ch341_transfer_one Discovered by Atuin - Automated Vulnerability Discovery Engine. The 'len' variable is calculated as 'min(32, trans->len + 1)', which includes the 1-byte command header. When copying data from 'trans->tx_buf' to 'ch341->tx_buf + 1', using 'len' as the length is incorrect because: 1. It causes an out-of-bounds read from 'trans->tx_buf' (which has size 'trans->len', i.e., 'len - 1' in this context). 2. It can cause an out-of-bounds write to 'ch341->tx_buf' if 'len' is CH341_PACKET_LENGTH (32). Writing 32 bytes to ch341->tx_buf + 1 overflows the buffer. Fix this by copying 'len - 1' bytes. Fixes: 8846739f52af ("spi: add ch341a usb2spi driver") Signed-off-by: Tianchu Chen Link: https://patch.msgid.link/20251128160630.0f922c45ec6084a46fb57099@linux.dev Signed-off-by: Mark Brown --- drivers/spi/spi-ch341.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/spi/spi-ch341.c b/drivers/spi/spi-ch341.c index 46bc208f2d05..79d2f9ab4ef0 100644 --- a/drivers/spi/spi-ch341.c +++ b/drivers/spi/spi-ch341.c @@ -78,7 +78,7 @@ static int ch341_transfer_one(struct spi_controller *host, ch341->tx_buf[0] = CH341A_CMD_SPI_STREAM; - memcpy(ch341->tx_buf + 1, trans->tx_buf, len); + memcpy(ch341->tx_buf + 1, trans->tx_buf, len - 1); ret = usb_bulk_msg(ch341->udev, ch341->write_pipe, ch341->tx_buf, len, NULL, CH341_DEFAULT_TIMEOUT); From cb5c2eb459f4c98d584eaf3d3ea7c3612385d081 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Thu, 27 Nov 2025 19:58:59 +0100 Subject: [PATCH 80/80] spi: microchip-core: Refactor FIFO read and write handlers Make both handlers to be shorter and easier to understand. While at it, unify their style. Signed-off-by: Andy Shevchenko Reviewed-by: Prajna Rajendra Kumar Link: https://patch.msgid.link/20251127190031.2998705-3-andriy.shevchenko@linux.intel.com Signed-off-by: Mark Brown --- drivers/spi/spi-microchip-core-spi.c | 31 +++++++++++----------------- 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/drivers/spi/spi-microchip-core-spi.c b/drivers/spi/spi-microchip-core-spi.c index 892f066f0074..98bf0e6cd00e 100644 --- a/drivers/spi/spi-microchip-core-spi.c +++ b/drivers/spi/spi-microchip-core-spi.c @@ -97,15 +97,12 @@ static inline void mchp_corespi_read_fifo(struct mchp_corespi *spi, u32 fifo_max MCHP_CORESPI_STATUS_RXFIFO_EMPTY) ; + /* On TX-only transfers always perform a dummy read */ data = readb(spi->regs + MCHP_CORESPI_REG_RXDATA); + if (spi->rx_buf) + *spi->rx_buf++ = data; spi->rx_len--; - if (!spi->rx_buf) - continue; - - *spi->rx_buf = data; - - spi->rx_buf++; } } @@ -127,23 +124,19 @@ static void mchp_corespi_disable_ints(struct mchp_corespi *spi) static inline void mchp_corespi_write_fifo(struct mchp_corespi *spi, u32 fifo_max) { - int i = 0; - - while ((i < fifo_max) && - !(readb(spi->regs + MCHP_CORESPI_REG_STAT) & - MCHP_CORESPI_STATUS_TXFIFO_FULL)) { - u32 word; - - word = spi->tx_buf ? *spi->tx_buf : 0xaa; - writeb(word, spi->regs + MCHP_CORESPI_REG_TXDATA); + for (int i = 0; i < fifo_max; i++) { + if (readb(spi->regs + MCHP_CORESPI_REG_STAT) & + MCHP_CORESPI_STATUS_TXFIFO_FULL) + break; + /* On RX-only transfers always perform a dummy write */ if (spi->tx_buf) - spi->tx_buf++; + writeb(*spi->tx_buf++, spi->regs + MCHP_CORESPI_REG_TXDATA); + else + writeb(0xaa, spi->regs + MCHP_CORESPI_REG_TXDATA); - i++; + spi->tx_len--; } - - spi->tx_len -= i; } static void mchp_corespi_set_cs(struct spi_device *spi, bool disable)