diff options
| author | Mark Brown <broonie@kernel.org> | 2026-01-28 11:22:06 +0000 |
|---|---|---|
| committer | Mark Brown <broonie@kernel.org> | 2026-01-28 11:22:06 +0000 |
| commit | 751ec6dd6773237bf480291ca894a696a2991c62 (patch) | |
| tree | 6ac1b53826f7836b3f7b29f3f17bb45d118535c0 /drivers/spi | |
| parent | e540be7d56d740144b1bd6f220b61ffe2f3830d4 (diff) | |
| parent | 04f7516ab70f7b82aae1d2830af2ee6f17f3fe98 (diff) | |
spi: aspeed: Improve handling of shared SPI
Merge series from Chin-Ting Kuo <chin-ting_kuo@aspeedtech.com>:
This patch series improves handling of SPI controllers that are
shared by spi-mem devices and other SPI peripherals.
The primary goal of this series is to support non-spi-mem devices in
the ASPEED FMC/SPI controller driver. It also addresses an issue in
the spi-mem framework observed when different types of SPI devices
operate concurrently on the same controller, ensuring that spi-mem
operations are properly serialized.
Diffstat (limited to 'drivers/spi')
| -rw-r--r-- | drivers/spi/spi-aspeed-smc.c | 134 | ||||
| -rw-r--r-- | drivers/spi/spi-cadence.c | 1 | ||||
| -rw-r--r-- | drivers/spi/spi-hisi-kunpeng.c | 4 | ||||
| -rw-r--r-- | drivers/spi/spi-intel-pci.c | 1 | ||||
| -rw-r--r-- | drivers/spi/spi-mem.c | 11 | ||||
| -rw-r--r-- | drivers/spi/spi-sprd-adi.c | 33 |
6 files changed, 151 insertions, 33 deletions
diff --git a/drivers/spi/spi-aspeed-smc.c b/drivers/spi/spi-aspeed-smc.c index fc565065c8fd..9c286e534bf0 100644 --- a/drivers/spi/spi-aspeed-smc.c +++ b/drivers/spi/spi-aspeed-smc.c @@ -48,6 +48,8 @@ /* CEx Address Decoding Range Register */ #define CE0_SEGMENT_ADDR_REG 0x30 +#define FULL_DUPLEX_RX_DATA 0x1e4 + /* CEx Read timing compensation register */ #define CE0_TIMING_COMPENSATION_REG 0x94 @@ -81,6 +83,7 @@ struct aspeed_spi_data { u32 hclk_mask; u32 hdiv_max; u32 min_window_size; + bool full_duplex; phys_addr_t (*segment_start)(struct aspeed_spi *aspi, u32 reg); phys_addr_t (*segment_end)(struct aspeed_spi *aspi, u32 reg); @@ -105,6 +108,7 @@ struct aspeed_spi { struct clk *clk; u32 clk_freq; + u8 cs_change; struct aspeed_spi_chip chips[ASPEED_SPI_MAX_NUM_CS]; }; @@ -280,7 +284,8 @@ stop_user: } /* support for 1-1-1, 1-1-2 or 1-1-4 */ -static bool aspeed_spi_supports_op(struct spi_mem *mem, const struct spi_mem_op *op) +static bool aspeed_spi_supports_mem_op(struct spi_mem *mem, + const struct spi_mem_op *op) { if (op->cmd.buswidth > 1) return false; @@ -305,7 +310,8 @@ static bool aspeed_spi_supports_op(struct spi_mem *mem, const struct spi_mem_op static const struct aspeed_spi_data ast2400_spi_data; -static int do_aspeed_spi_exec_op(struct spi_mem *mem, const struct spi_mem_op *op) +static int do_aspeed_spi_exec_mem_op(struct spi_mem *mem, + const struct spi_mem_op *op) { struct aspeed_spi *aspi = spi_controller_get_devdata(mem->spi->controller); struct aspeed_spi_chip *chip = &aspi->chips[spi_get_chipselect(mem->spi, 0)]; @@ -367,11 +373,12 @@ static int do_aspeed_spi_exec_op(struct spi_mem *mem, const struct spi_mem_op *o return ret; } -static int aspeed_spi_exec_op(struct spi_mem *mem, const struct spi_mem_op *op) +static int aspeed_spi_exec_mem_op(struct spi_mem *mem, + const struct spi_mem_op *op) { int ret; - ret = do_aspeed_spi_exec_op(mem, op); + ret = do_aspeed_spi_exec_mem_op(mem, op); if (ret) dev_err(&mem->spi->dev, "operation failed: %d\n", ret); return ret; @@ -773,8 +780,8 @@ static ssize_t aspeed_spi_dirmap_read(struct spi_mem_dirmap_desc *desc, } static const struct spi_controller_mem_ops aspeed_spi_mem_ops = { - .supports_op = aspeed_spi_supports_op, - .exec_op = aspeed_spi_exec_op, + .supports_op = aspeed_spi_supports_mem_op, + .exec_op = aspeed_spi_exec_mem_op, .get_name = aspeed_spi_get_name, .dirmap_create = aspeed_spi_dirmap_create, .dirmap_read = aspeed_spi_dirmap_read, @@ -843,6 +850,110 @@ static void aspeed_spi_enable(struct aspeed_spi *aspi, bool enable) aspeed_spi_chip_enable(aspi, cs, enable); } +static int aspeed_spi_user_prepare_msg(struct spi_controller *ctlr, + struct spi_message *msg) +{ + struct aspeed_spi *aspi = + (struct aspeed_spi *)spi_controller_get_devdata(ctlr); + const struct aspeed_spi_data *data = aspi->data; + struct spi_device *spi = msg->spi; + u32 cs = spi_get_chipselect(spi, 0); + struct aspeed_spi_chip *chip = &aspi->chips[cs]; + u32 ctrl_val; + u32 clk_div = data->get_clk_div(chip, spi->max_speed_hz); + + ctrl_val = chip->ctl_val[ASPEED_SPI_BASE]; + ctrl_val &= ~CTRL_IO_MODE_MASK & data->hclk_mask; + ctrl_val |= clk_div; + chip->ctl_val[ASPEED_SPI_BASE] = ctrl_val; + + if (aspi->cs_change == 0) + aspeed_spi_start_user(chip); + + return 0; +} + +static int aspeed_spi_user_unprepare_msg(struct spi_controller *ctlr, + struct spi_message *msg) +{ + struct aspeed_spi *aspi = + (struct aspeed_spi *)spi_controller_get_devdata(ctlr); + struct spi_device *spi = msg->spi; + u32 cs = spi_get_chipselect(spi, 0); + struct aspeed_spi_chip *chip = &aspi->chips[cs]; + + if (aspi->cs_change == 0) + aspeed_spi_stop_user(chip); + + return 0; +} + +static void aspeed_spi_user_transfer_tx(struct aspeed_spi *aspi, + struct spi_device *spi, + const u8 *tx_buf, u8 *rx_buf, + void *dst, u32 len) +{ + const struct aspeed_spi_data *data = aspi->data; + bool full_duplex_transfer = data->full_duplex && tx_buf == rx_buf; + u32 i; + + if (full_duplex_transfer && + !!(spi->mode & (SPI_TX_DUAL | SPI_TX_QUAD | + SPI_RX_DUAL | SPI_RX_QUAD))) { + dev_err(aspi->dev, + "full duplex is only supported for single IO mode\n"); + return; + } + + for (i = 0; i < len; i++) { + writeb(tx_buf[i], dst); + if (full_duplex_transfer) + rx_buf[i] = readb(aspi->regs + FULL_DUPLEX_RX_DATA); + } +} + +static int aspeed_spi_user_transfer(struct spi_controller *ctlr, + struct spi_device *spi, + struct spi_transfer *xfer) +{ + struct aspeed_spi *aspi = + (struct aspeed_spi *)spi_controller_get_devdata(ctlr); + u32 cs = spi_get_chipselect(spi, 0); + struct aspeed_spi_chip *chip = &aspi->chips[cs]; + void __iomem *ahb_base = aspi->chips[cs].ahb_base; + const u8 *tx_buf = xfer->tx_buf; + u8 *rx_buf = xfer->rx_buf; + + dev_dbg(aspi->dev, + "[cs%d] xfer: width %d, len %u, tx %p, rx %p\n", + cs, xfer->bits_per_word, xfer->len, + tx_buf, rx_buf); + + if (tx_buf) { + if (spi->mode & SPI_TX_DUAL) + aspeed_spi_set_io_mode(chip, CTRL_IO_DUAL_DATA); + else if (spi->mode & SPI_TX_QUAD) + aspeed_spi_set_io_mode(chip, CTRL_IO_QUAD_DATA); + + aspeed_spi_user_transfer_tx(aspi, spi, tx_buf, rx_buf, + (void *)ahb_base, xfer->len); + } + + if (rx_buf && rx_buf != tx_buf) { + if (spi->mode & SPI_RX_DUAL) + aspeed_spi_set_io_mode(chip, CTRL_IO_DUAL_DATA); + else if (spi->mode & SPI_RX_QUAD) + aspeed_spi_set_io_mode(chip, CTRL_IO_QUAD_DATA); + + ioread8_rep(ahb_base, rx_buf, xfer->len); + } + + xfer->error = 0; + aspi->cs_change = xfer->cs_change; + + return 0; +} + static int aspeed_spi_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -898,6 +1009,9 @@ static int aspeed_spi_probe(struct platform_device *pdev) ctlr->setup = aspeed_spi_setup; ctlr->cleanup = aspeed_spi_cleanup; ctlr->num_chipselect = of_get_available_child_count(dev->of_node); + ctlr->prepare_message = aspeed_spi_user_prepare_msg; + ctlr->unprepare_message = aspeed_spi_user_unprepare_msg; + ctlr->transfer_one = aspeed_spi_user_transfer; aspi->num_cs = ctlr->num_chipselect; @@ -1454,6 +1568,7 @@ static const struct aspeed_spi_data ast2400_fmc_data = { .hclk_mask = 0xfffff0ff, .hdiv_max = 1, .min_window_size = 0x800000, + .full_duplex = false, .calibrate = aspeed_spi_calibrate, .get_clk_div = aspeed_get_clk_div_ast2400, .segment_start = aspeed_spi_segment_start, @@ -1470,6 +1585,7 @@ static const struct aspeed_spi_data ast2400_spi_data = { .timing = 0x14, .hclk_mask = 0xfffff0ff, .hdiv_max = 1, + .full_duplex = false, .get_clk_div = aspeed_get_clk_div_ast2400, .calibrate = aspeed_spi_calibrate, /* No segment registers */ @@ -1484,6 +1600,7 @@ static const struct aspeed_spi_data ast2500_fmc_data = { .hclk_mask = 0xffffd0ff, .hdiv_max = 1, .min_window_size = 0x800000, + .full_duplex = false, .get_clk_div = aspeed_get_clk_div_ast2500, .calibrate = aspeed_spi_calibrate, .segment_start = aspeed_spi_segment_start, @@ -1501,6 +1618,7 @@ static const struct aspeed_spi_data ast2500_spi_data = { .hclk_mask = 0xffffd0ff, .hdiv_max = 1, .min_window_size = 0x800000, + .full_duplex = false, .get_clk_div = aspeed_get_clk_div_ast2500, .calibrate = aspeed_spi_calibrate, .segment_start = aspeed_spi_segment_start, @@ -1519,6 +1637,7 @@ static const struct aspeed_spi_data ast2600_fmc_data = { .hclk_mask = 0xf0fff0ff, .hdiv_max = 2, .min_window_size = 0x200000, + .full_duplex = false, .get_clk_div = aspeed_get_clk_div_ast2600, .calibrate = aspeed_spi_ast2600_calibrate, .segment_start = aspeed_spi_segment_ast2600_start, @@ -1537,6 +1656,7 @@ static const struct aspeed_spi_data ast2600_spi_data = { .hclk_mask = 0xf0fff0ff, .hdiv_max = 2, .min_window_size = 0x200000, + .full_duplex = false, .get_clk_div = aspeed_get_clk_div_ast2600, .calibrate = aspeed_spi_ast2600_calibrate, .segment_start = aspeed_spi_segment_ast2600_start, @@ -1555,6 +1675,7 @@ static const struct aspeed_spi_data ast2700_fmc_data = { .hclk_mask = 0xf0fff0ff, .hdiv_max = 2, .min_window_size = 0x10000, + .full_duplex = true, .get_clk_div = aspeed_get_clk_div_ast2600, .calibrate = aspeed_spi_ast2600_calibrate, .segment_start = aspeed_spi_segment_ast2700_start, @@ -1572,6 +1693,7 @@ static const struct aspeed_spi_data ast2700_spi_data = { .hclk_mask = 0xf0fff0ff, .hdiv_max = 2, .min_window_size = 0x10000, + .full_duplex = true, .get_clk_div = aspeed_get_clk_div_ast2600, .calibrate = aspeed_spi_ast2600_calibrate, .segment_start = aspeed_spi_segment_ast2700_start, diff --git a/drivers/spi/spi-cadence.c b/drivers/spi/spi-cadence.c index 6cac015cfb5b..caa7a57e6d27 100644 --- a/drivers/spi/spi-cadence.c +++ b/drivers/spi/spi-cadence.c @@ -728,6 +728,7 @@ static int cdns_spi_probe(struct platform_device *pdev) ctlr->unprepare_transfer_hardware = cdns_unprepare_transfer_hardware; ctlr->mode_bits = SPI_CPOL | SPI_CPHA; ctlr->bits_per_word_mask = SPI_BPW_MASK(8); + ctlr->flags = SPI_CONTROLLER_MUST_TX; 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); diff --git a/drivers/spi/spi-hisi-kunpeng.c b/drivers/spi/spi-hisi-kunpeng.c index afe51adcc507..f123cdab9007 100644 --- a/drivers/spi/spi-hisi-kunpeng.c +++ b/drivers/spi/spi-hisi-kunpeng.c @@ -161,10 +161,8 @@ static const struct debugfs_reg32 hisi_spi_regs[] = { static int hisi_spi_debugfs_init(struct hisi_spi *hs) { char name[32]; + struct spi_controller *host = dev_get_drvdata(hs->dev); - struct spi_controller *host; - - host = container_of(hs->dev, struct spi_controller, dev); snprintf(name, 32, "hisi_spi%d", host->bus_num); hs->debugfs = debugfs_create_dir(name, NULL); if (IS_ERR(hs->debugfs)) diff --git a/drivers/spi/spi-intel-pci.c b/drivers/spi/spi-intel-pci.c index b8c572394aac..bce3d149bea1 100644 --- a/drivers/spi/spi-intel-pci.c +++ b/drivers/spi/spi-intel-pci.c @@ -81,6 +81,7 @@ static const struct pci_device_id intel_spi_pci_ids[] = { { PCI_VDEVICE(INTEL, 0x54a4), (unsigned long)&cnl_info }, { PCI_VDEVICE(INTEL, 0x5794), (unsigned long)&cnl_info }, { PCI_VDEVICE(INTEL, 0x5825), (unsigned long)&cnl_info }, + { PCI_VDEVICE(INTEL, 0x6e24), (unsigned long)&cnl_info }, { PCI_VDEVICE(INTEL, 0x7723), (unsigned long)&cnl_info }, { PCI_VDEVICE(INTEL, 0x7a24), (unsigned long)&cnl_info }, { PCI_VDEVICE(INTEL, 0x7aa4), (unsigned long)&cnl_info }, diff --git a/drivers/spi/spi-mem.c b/drivers/spi/spi-mem.c index 6c7921469b90..965673bac98b 100644 --- a/drivers/spi/spi-mem.c +++ b/drivers/spi/spi-mem.c @@ -719,9 +719,18 @@ spi_mem_dirmap_create(struct spi_mem *mem, desc->mem = mem; desc->info = *info; - if (ctlr->mem_ops && ctlr->mem_ops->dirmap_create) + if (ctlr->mem_ops && ctlr->mem_ops->dirmap_create) { + ret = spi_mem_access_start(mem); + if (ret) { + kfree(desc); + return ERR_PTR(ret); + } + ret = ctlr->mem_ops->dirmap_create(desc); + spi_mem_access_end(mem); + } + if (ret) { desc->nodirmap = true; if (!spi_mem_supports_op(desc->mem, &desc->info.op_tmpl)) diff --git a/drivers/spi/spi-sprd-adi.c b/drivers/spi/spi-sprd-adi.c index a05cc9a0a4ae..e7d83c16b46c 100644 --- a/drivers/spi/spi-sprd-adi.c +++ b/drivers/spi/spi-sprd-adi.c @@ -528,7 +528,7 @@ static int sprd_adi_probe(struct platform_device *pdev) pdev->id = of_alias_get_id(np, "spi"); num_chipselect = of_get_child_count(np); - ctlr = spi_alloc_host(&pdev->dev, sizeof(struct sprd_adi)); + ctlr = devm_spi_alloc_host(&pdev->dev, sizeof(struct sprd_adi)); if (!ctlr) return -ENOMEM; @@ -536,10 +536,8 @@ static int sprd_adi_probe(struct platform_device *pdev) sadi = spi_controller_get_devdata(ctlr); sadi->base = devm_platform_get_and_ioremap_resource(pdev, 0, &res); - if (IS_ERR(sadi->base)) { - ret = PTR_ERR(sadi->base); - goto put_ctlr; - } + if (IS_ERR(sadi->base)) + return PTR_ERR(sadi->base); sadi->slave_vbase = (unsigned long)sadi->base + data->slave_offset; @@ -551,18 +549,15 @@ static int sprd_adi_probe(struct platform_device *pdev) if (ret > 0 || (IS_ENABLED(CONFIG_HWSPINLOCK) && ret == 0)) { sadi->hwlock = devm_hwspin_lock_request_specific(&pdev->dev, ret); - if (!sadi->hwlock) { - ret = -ENXIO; - goto put_ctlr; - } + if (!sadi->hwlock) + return -ENXIO; } else { switch (ret) { case -ENOENT: dev_info(&pdev->dev, "no hardware spinlock supplied\n"); break; default: - dev_err_probe(&pdev->dev, ret, "failed to find hwlock id\n"); - goto put_ctlr; + return dev_err_probe(&pdev->dev, ret, "failed to find hwlock id\n"); } } @@ -578,26 +573,18 @@ static int sprd_adi_probe(struct platform_device *pdev) ctlr->transfer_one = sprd_adi_transfer_one; ret = devm_spi_register_controller(&pdev->dev, ctlr); - if (ret) { - dev_err(&pdev->dev, "failed to register SPI controller\n"); - goto put_ctlr; - } + if (ret) + return dev_err_probe(&pdev->dev, ret, "failed to register SPI controller\n"); if (sadi->data->restart) { ret = devm_register_restart_handler(&pdev->dev, sadi->data->restart, sadi); - if (ret) { - dev_err(&pdev->dev, "can not register restart handler\n"); - goto put_ctlr; - } + if (ret) + return dev_err_probe(&pdev->dev, ret, "can not register restart handler\n"); } return 0; - -put_ctlr: - spi_controller_put(ctlr); - return ret; } static struct sprd_adi_data sc9860_data = { |
