summaryrefslogtreecommitdiff
path: root/drivers/spi
diff options
context:
space:
mode:
authorMark Brown <broonie@kernel.org>2026-01-28 11:22:06 +0000
committerMark Brown <broonie@kernel.org>2026-01-28 11:22:06 +0000
commit751ec6dd6773237bf480291ca894a696a2991c62 (patch)
tree6ac1b53826f7836b3f7b29f3f17bb45d118535c0 /drivers/spi
parente540be7d56d740144b1bd6f220b61ffe2f3830d4 (diff)
parent04f7516ab70f7b82aae1d2830af2ee6f17f3fe98 (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.c134
-rw-r--r--drivers/spi/spi-cadence.c1
-rw-r--r--drivers/spi/spi-hisi-kunpeng.c4
-rw-r--r--drivers/spi/spi-intel-pci.c1
-rw-r--r--drivers/spi/spi-mem.c11
-rw-r--r--drivers/spi/spi-sprd-adi.c33
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 = {