From 2e4d9f5111a3b3c24550e34710efa690c03b3ea1 Mon Sep 17 00:00:00 2001 From: Srikanth Boyapally Date: Wed, 20 Nov 2024 17:39:50 +0530 Subject: [PATCH 01/49] spi: cadence-quadspi: Use quirks to set dma_set_mask instead of compatible string for 64-bit DMA support Remove device compatible property check and instead use quirks to program DMA addressing. Signed-off-by: Srikanth Boyapally Link: https://patch.msgid.link/20241120120951.56327-3-srikanth.boyapally@amd.com Signed-off-by: Mark Brown --- drivers/spi/spi-cadence-quadspi.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/spi/spi-cadence-quadspi.c b/drivers/spi/spi-cadence-quadspi.c index 0b45b7b2b3ab..a7557ba6eeed 100644 --- a/drivers/spi/spi-cadence-quadspi.c +++ b/drivers/spi/spi-cadence-quadspi.c @@ -43,6 +43,7 @@ static_assert(CQSPI_MAX_CHIPSELECT <= SPI_CS_CNT_MAX); #define CQSPI_SLOW_SRAM BIT(4) #define CQSPI_NEEDS_APB_AHB_HAZARD_WAR BIT(5) #define CQSPI_RD_NO_IRQ BIT(6) +#define CQSPI_DMA_SET_MASK BIT(7) /* Capabilities */ #define CQSPI_SUPPORTS_OCTAL BIT(0) @@ -1881,8 +1882,7 @@ static int cqspi_probe(struct platform_device *pdev) goto probe_reset_failed; } - if (of_device_is_compatible(pdev->dev.of_node, - "xlnx,versal-ospi-1.0")) { + if (ddata->quirks & CQSPI_DMA_SET_MASK) { ret = dma_set_mask(&pdev->dev, DMA_BIT_MASK(64)); if (ret) goto probe_reset_failed; @@ -2048,7 +2048,8 @@ static const struct cqspi_driver_platdata socfpga_qspi = { static const struct cqspi_driver_platdata versal_ospi = { .hwcaps_mask = CQSPI_SUPPORTS_OCTAL, - .quirks = CQSPI_DISABLE_DAC_MODE | CQSPI_SUPPORT_EXTERNAL_DMA, + .quirks = CQSPI_DISABLE_DAC_MODE | CQSPI_SUPPORT_EXTERNAL_DMA + | CQSPI_DMA_SET_MASK, .indirect_read_dma = cqspi_versal_indirect_read_dma, .get_dma_status = cqspi_get_versal_dma_status, }; From 27cf57f65bea55d985f0ad0dc1737ce1d01f05dc Mon Sep 17 00:00:00 2001 From: Srikanth Boyapally Date: Wed, 20 Nov 2024 17:39:51 +0530 Subject: [PATCH 02/49] spi: cadence-quadspi: Support for device reset via OSPI controller Add support for flash device reset via ospi controller, instead of using GPIO, as ospi IP has device reset feature on Versal Gen2 platform. Signed-off-by: Srikanth Boyapally Link: https://patch.msgid.link/20241120120951.56327-4-srikanth.boyapally@amd.com Signed-off-by: Mark Brown --- drivers/spi/spi-cadence-quadspi.c | 40 ++++++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/drivers/spi/spi-cadence-quadspi.c b/drivers/spi/spi-cadence-quadspi.c index a7557ba6eeed..27bd759d39ec 100644 --- a/drivers/spi/spi-cadence-quadspi.c +++ b/drivers/spi/spi-cadence-quadspi.c @@ -44,6 +44,7 @@ static_assert(CQSPI_MAX_CHIPSELECT <= SPI_CS_CNT_MAX); #define CQSPI_NEEDS_APB_AHB_HAZARD_WAR BIT(5) #define CQSPI_RD_NO_IRQ BIT(6) #define CQSPI_DMA_SET_MASK BIT(7) +#define CQSPI_SUPPORT_DEVICE_RESET BIT(8) /* Capabilities */ #define CQSPI_SUPPORTS_OCTAL BIT(0) @@ -110,7 +111,7 @@ struct cqspi_st { struct cqspi_driver_platdata { u32 hwcaps_mask; - u8 quirks; + u16 quirks; int (*indirect_read_dma)(struct cqspi_flash_pdata *f_pdata, u_char *rxbuf, loff_t from_addr, size_t n_rx); u32 (*get_dma_status)(struct cqspi_st *cqspi); @@ -145,6 +146,8 @@ struct cqspi_driver_platdata { #define CQSPI_REG_CONFIG_IDLE_LSB 31 #define CQSPI_REG_CONFIG_CHIPSELECT_MASK 0xF #define CQSPI_REG_CONFIG_BAUD_MASK 0xF +#define CQSPI_REG_CONFIG_RESET_PIN_FLD_MASK BIT(5) +#define CQSPI_REG_CONFIG_RESET_CFG_FLD_MASK BIT(6) #define CQSPI_REG_RD_INSTR 0x04 #define CQSPI_REG_RD_INSTR_OPCODE_LSB 0 @@ -831,6 +834,25 @@ static int cqspi_indirect_read_execute(struct cqspi_flash_pdata *f_pdata, return ret; } +static void cqspi_device_reset(struct cqspi_st *cqspi) +{ + u32 reg; + + reg = readl(cqspi->iobase + CQSPI_REG_CONFIG); + reg |= CQSPI_REG_CONFIG_RESET_CFG_FLD_MASK; + writel(reg, cqspi->iobase + CQSPI_REG_CONFIG); + /* + * NOTE: Delay timing implementation is derived from + * spi_nor_hw_reset() + */ + writel(reg & ~CQSPI_REG_CONFIG_RESET_PIN_FLD_MASK, cqspi->iobase + CQSPI_REG_CONFIG); + usleep_range(1, 5); + writel(reg | CQSPI_REG_CONFIG_RESET_PIN_FLD_MASK, cqspi->iobase + CQSPI_REG_CONFIG); + usleep_range(100, 150); + writel(reg & ~CQSPI_REG_CONFIG_RESET_PIN_FLD_MASK, cqspi->iobase + CQSPI_REG_CONFIG); + usleep_range(1000, 1200); +} + static void cqspi_controller_enable(struct cqspi_st *cqspi, bool enable) { void __iomem *reg_base = cqspi->iobase; @@ -1912,6 +1934,9 @@ static int cqspi_probe(struct platform_device *pdev) host->num_chipselect = cqspi->num_chipselect; + if (ddata->quirks & CQSPI_SUPPORT_DEVICE_RESET) + cqspi_device_reset(cqspi); + if (cqspi->use_direct_mode) { ret = cqspi_request_mmap_dma(cqspi); if (ret == -EPROBE_DEFER) @@ -2054,6 +2079,15 @@ static const struct cqspi_driver_platdata versal_ospi = { .get_dma_status = cqspi_get_versal_dma_status, }; +static const struct cqspi_driver_platdata versal2_ospi = { + .hwcaps_mask = CQSPI_SUPPORTS_OCTAL, + .quirks = CQSPI_DISABLE_DAC_MODE | CQSPI_SUPPORT_EXTERNAL_DMA + | CQSPI_DMA_SET_MASK + | CQSPI_SUPPORT_DEVICE_RESET, + .indirect_read_dma = cqspi_versal_indirect_read_dma, + .get_dma_status = cqspi_get_versal_dma_status, +}; + static const struct cqspi_driver_platdata jh7110_qspi = { .quirks = CQSPI_DISABLE_DAC_MODE, .jh7110_clk_init = cqspi_jh7110_clk_init, @@ -2106,6 +2140,10 @@ static const struct of_device_id cqspi_dt_ids[] = { .compatible = "mobileye,eyeq5-ospi", .data = &mobileye_eyeq5_ospi, }, + { + .compatible = "amd,versal2-ospi", + .data = &versal2_ospi, + }, { /* end of table */ } }; From 707080d4fea8f6b8319ceead569f34c2be5bf1d5 Mon Sep 17 00:00:00 2001 From: Srikanth Boyapally Date: Wed, 20 Nov 2024 17:39:49 +0530 Subject: [PATCH 03/49] spi: dt-bindings: cdns,qspi-nor: Add compatible string to support OSPI controller on Versal Gen2 platform Add compatible string to support OSPI controller device reset IP feature on Versal Gen2 platform. Signed-off-by: Srikanth Boyapally Link: https://patch.msgid.link/20241120120951.56327-2-srikanth.boyapally@amd.com Acked-by: Conor Dooley Signed-off-by: Mark Brown --- Documentation/devicetree/bindings/spi/cdns,qspi-nor.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/spi/cdns,qspi-nor.yaml b/Documentation/devicetree/bindings/spi/cdns,qspi-nor.yaml index d48ecd6cd5ad..b6bc71d19286 100644 --- a/Documentation/devicetree/bindings/spi/cdns,qspi-nor.yaml +++ b/Documentation/devicetree/bindings/spi/cdns,qspi-nor.yaml @@ -68,6 +68,7 @@ properties: - items: - enum: - amd,pensando-elba-qspi + - amd,versal2-ospi - intel,lgm-qspi - intel,socfpga-qspi - mobileye,eyeq5-ospi From 36e7886075262429158aec6f258e6a5a92f025b1 Mon Sep 17 00:00:00 2001 From: Fabio Estevam Date: Wed, 23 Oct 2024 09:00:13 -0300 Subject: [PATCH 04/49] dt-bindings: misc: lwn,bk4-spi: Add binding Add a lwn,bk4-spi.yaml binding for Liebherr's BK4 external SPI controller. Currently, the compatible string used for this device is "lwn,bk4", which is the same as the board compatible string documented at fsl.yaml. This causes several dt-schema warnings: make dtbs_check DT_SCHEMA_FILES=fsl.yaml ... ['lwn,bk4'] is too short 'lwn,bk4' is not one of ['tq,imx8dxp-tqma8xdp-mba8xx'] 'lwn,bk4' is not one of ['tq,imx8qxp-tqma8xqp-mba8xx'] 'lwn,bk4' is not one of ['armadeus,imx1-apf9328', 'fsl,imx1ads'] ... Use a more specific "lwn,bk4-spi" compatible string for this device. Signed-off-by: Fabio Estevam Link: https://patch.msgid.link/20241023120015.1049008-1-festevam@gmail.com Signed-off-by: Mark Brown --- .../devicetree/bindings/misc/lwn,bk4-spi.yaml | 54 +++++++++++++++++++ .../devicetree/bindings/misc/lwn-bk4.txt | 26 --------- 2 files changed, 54 insertions(+), 26 deletions(-) create mode 100644 Documentation/devicetree/bindings/misc/lwn,bk4-spi.yaml delete mode 100644 Documentation/devicetree/bindings/misc/lwn-bk4.txt diff --git a/Documentation/devicetree/bindings/misc/lwn,bk4-spi.yaml b/Documentation/devicetree/bindings/misc/lwn,bk4-spi.yaml new file mode 100644 index 000000000000..73fbf672e22a --- /dev/null +++ b/Documentation/devicetree/bindings/misc/lwn,bk4-spi.yaml @@ -0,0 +1,54 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/misc/lwn,bk4-spi.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Liebherr's BK4 external SPI controller + +maintainers: + - Lukasz Majewski + +description: | + Liebherr's BK4 external SPI controller is a device which handles data + acquisition from compatible industrial peripherals. + The SPI is used for data and management purposes in both master and + slave modes. + +allOf: + - $ref: /schemas/spi/spi-peripheral-props.yaml# + +properties: + compatible: + const: lwn,bk4-spi + + reg: + maxItems: 1 + + spi-max-frequency: + maximum: 30000000 + + fsl,spi-cs-sck-delay: true + + fsl,spi-sck-cs-delay: true + +required: + - compatible + - spi-max-frequency + +additionalProperties: false + +examples: + - | + spi { + #address-cells = <1>; + #size-cells = <0>; + + spidev@0 { + compatible = "lwn,bk4-spi"; + reg = <0>; + spi-max-frequency = <30000000>; + fsl,spi-cs-sck-delay = <200>; + fsl,spi-sck-cs-delay = <400>; + }; + }; diff --git a/Documentation/devicetree/bindings/misc/lwn-bk4.txt b/Documentation/devicetree/bindings/misc/lwn-bk4.txt deleted file mode 100644 index d6a8c188c087..000000000000 --- a/Documentation/devicetree/bindings/misc/lwn-bk4.txt +++ /dev/null @@ -1,26 +0,0 @@ -* Liebherr's BK4 controller external SPI - -A device which handles data acquisition from compatible industrial -peripherals. -The SPI is used for data and management purposes in both master and -slave modes. - -Required properties: - -- compatible : Should be "lwn,bk4" - -Required SPI properties: - -- reg : Should be address of the device chip select within - the controller. - -- spi-max-frequency : Maximum SPI clocking speed of device in Hz, should be - 30MHz at most for the Liebherr's BK4 external bus. - -Example: - -spidev0: spi@0 { - compatible = "lwn,bk4"; - spi-max-frequency = <30000000>; - reg = <0>; -}; From 096c34ddf5835f02f5260719cd8a16fcf5e5e56f Mon Sep 17 00:00:00 2001 From: Fabio Estevam Date: Wed, 23 Oct 2024 09:00:14 -0300 Subject: [PATCH 05/49] spi: spidev: Add an entry for lwn,bk4-spi Currently, the compatible string used for Liebherr's BK4 external SPI controller device is "lwn,bk4", which is the same as the board compatible string documented at fsl.yaml. This causes several dt-schema warnings: make dtbs_check DT_SCHEMA_FILES=fsl.yaml ... ['lwn,bk4'] is too short 'lwn,bk4' is not one of ['tq,imx8dxp-tqma8xdp-mba8xx'] 'lwn,bk4' is not one of ['tq,imx8qxp-tqma8xqp-mba8xx'] 'lwn,bk4' is not one of ['armadeus,imx1-apf9328', 'fsl,imx1ads'] ... Add a more specific "lwn,bk4-spi" compatible string entry for this device to fix the problem. The original "lwn,bk4" is kept to keep compatibility with old DTBs. Signed-off-by: Fabio Estevam Link: https://patch.msgid.link/20241023120015.1049008-2-festevam@gmail.com 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 653f82984216..2b87b9ae56c0 100644 --- a/drivers/spi/spidev.c +++ b/drivers/spi/spidev.c @@ -705,6 +705,7 @@ static const struct spi_device_id spidev_spi_ids[] = { { .name = "ltc2488" }, { .name = "sx1301" }, { .name = "bk4" }, + { .name = "bk4-spi" }, { .name = "dhcom-board" }, { .name = "m53cpld" }, { .name = "spi-petra" }, @@ -734,6 +735,7 @@ static const struct of_device_id spidev_dt_ids[] = { { .compatible = "elgin,jg10309-01", .data = &spidev_of_check }, { .compatible = "lineartechnology,ltc2488", .data = &spidev_of_check }, { .compatible = "lwn,bk4", .data = &spidev_of_check }, + { .compatible = "lwn,bk4-spi", .data = &spidev_of_check }, { .compatible = "menlo,m53cpld", .data = &spidev_of_check }, { .compatible = "micron,spi-authenta", .data = &spidev_of_check }, { .compatible = "rohm,bh2228fv", .data = &spidev_of_check }, From 2c55f67c3a71cf57665294a02f258625c1da9385 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Thu, 14 Nov 2024 22:50:51 +0200 Subject: [PATCH 06/49] spi: sc18is602: Switch to generic firmware properties and drop of_match_ptr() This enables using the driver with other firmware types such as ACPI via PRP0001. Also part of a general attempt to move drivers over to generic properties to avoid opportunities for cut and paste. Signed-off-by: Andy Shevchenko Link: https://patch.msgid.link/20241114205051.3747458-1-andriy.shevchenko@linux.intel.com Signed-off-by: Mark Brown --- drivers/spi/spi-sc18is602.c | 34 ++++++++++++---------------------- 1 file changed, 12 insertions(+), 22 deletions(-) diff --git a/drivers/spi/spi-sc18is602.c b/drivers/spi/spi-sc18is602.c index eecf9ea95ae3..1627aa66c965 100644 --- a/drivers/spi/spi-sc18is602.c +++ b/drivers/spi/spi-sc18is602.c @@ -7,13 +7,15 @@ #include #include +#include #include #include #include #include #include -#include #include +#include + #include enum chips { sc18is602, sc18is602b, sc18is603 }; @@ -236,9 +238,7 @@ static int sc18is602_setup(struct spi_device *spi) static int sc18is602_probe(struct i2c_client *client) { - const struct i2c_device_id *id = i2c_client_get_device_id(client); struct device *dev = &client->dev; - struct device_node *np = dev->of_node; struct sc18is602_platform_data *pdata = dev_get_platdata(dev); struct sc18is602 *hw; struct spi_controller *host; @@ -251,8 +251,9 @@ static int sc18is602_probe(struct i2c_client *client) if (!host) return -ENOMEM; + device_set_node(&host->dev, dev_fwnode(dev)); + hw = spi_controller_get_devdata(host); - i2c_set_clientdata(client, hw); /* assert reset and then release */ hw->reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH); @@ -265,11 +266,7 @@ static int sc18is602_probe(struct i2c_client *client) hw->dev = dev; hw->ctrl = 0xff; - if (client->dev.of_node) - hw->id = (uintptr_t)of_device_get_match_data(&client->dev); - else - hw->id = id->driver_data; - + hw->id = (uintptr_t)i2c_get_match_data(client); switch (hw->id) { case sc18is602: case sc18is602b: @@ -278,28 +275,21 @@ static int sc18is602_probe(struct i2c_client *client) break; case sc18is603: host->num_chipselect = 2; - if (pdata) { + if (pdata) hw->freq = pdata->clock_frequency; - } else { - const __be32 *val; - int len; - - val = of_get_property(np, "clock-frequency", &len); - if (val && len >= sizeof(__be32)) - hw->freq = be32_to_cpup(val); - } + else + device_property_read_u32(dev, "clock-frequency", &hw->freq); if (!hw->freq) hw->freq = SC18IS602_CLOCK; break; } - host->bus_num = np ? -1 : client->adapter->nr; + host->bus_num = dev_fwnode(dev) ? -1 : client->adapter->nr; host->mode_bits = SPI_CPHA | SPI_CPOL | SPI_LSB_FIRST; host->bits_per_word_mask = SPI_BPW_MASK(8); host->setup = sc18is602_setup; host->transfer_one_message = sc18is602_transfer_one; host->max_transfer_size = sc18is602_max_transfer_size; host->max_message_size = sc18is602_max_transfer_size; - host->dev.of_node = np; host->min_speed_hz = hw->freq / 128; host->max_speed_hz = hw->freq / 4; @@ -314,7 +304,7 @@ static const struct i2c_device_id sc18is602_id[] = { }; MODULE_DEVICE_TABLE(i2c, sc18is602_id); -static const struct of_device_id sc18is602_of_match[] __maybe_unused = { +static const struct of_device_id sc18is602_of_match[] = { { .compatible = "nxp,sc18is602", .data = (void *)sc18is602 @@ -334,7 +324,7 @@ MODULE_DEVICE_TABLE(of, sc18is602_of_match); static struct i2c_driver sc18is602_driver = { .driver = { .name = "sc18is602", - .of_match_table = of_match_ptr(sc18is602_of_match), + .of_match_table = sc18is602_of_match, }, .probe = sc18is602_probe, .id_table = sc18is602_id, From e39531352aed30156b270a61ba075e5b8d3b8498 Mon Sep 17 00:00:00 2001 From: Jon Lin Date: Wed, 20 Nov 2024 09:22:08 +0800 Subject: [PATCH 07/49] spi: rockchip-sfc: Support ACPI Compatible with ACPI. Signed-off-by: Jon Lin Link: https://patch.msgid.link/20241120012208.1193034-1-jon.lin@rock-chips.com Signed-off-by: Mark Brown --- drivers/spi/spi-rockchip-sfc.c | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/drivers/spi/spi-rockchip-sfc.c b/drivers/spi/spi-rockchip-sfc.c index 69d0f2175568..138508d2c736 100644 --- a/drivers/spi/spi-rockchip-sfc.c +++ b/drivers/spi/spi-rockchip-sfc.c @@ -491,7 +491,7 @@ static int rockchip_sfc_exec_mem_op(struct spi_mem *mem, const struct spi_mem_op u32 len = op->data.nbytes; int ret; - if (unlikely(mem->spi->max_speed_hz != sfc->frequency)) { + if (unlikely(mem->spi->max_speed_hz != sfc->frequency) && !has_acpi_companion(sfc->dev)) { ret = clk_set_rate(sfc->clk, mem->spi->max_speed_hz); if (ret) return ret; @@ -579,16 +579,24 @@ static int rockchip_sfc_probe(struct platform_device *pdev) if (IS_ERR(sfc->regbase)) return PTR_ERR(sfc->regbase); - sfc->clk = devm_clk_get(&pdev->dev, "clk_sfc"); + if (!has_acpi_companion(&pdev->dev)) + sfc->clk = devm_clk_get(&pdev->dev, "clk_sfc"); if (IS_ERR(sfc->clk)) return dev_err_probe(&pdev->dev, PTR_ERR(sfc->clk), "Failed to get sfc interface clk\n"); - sfc->hclk = devm_clk_get(&pdev->dev, "hclk_sfc"); + if (!has_acpi_companion(&pdev->dev)) + sfc->hclk = devm_clk_get(&pdev->dev, "hclk_sfc"); if (IS_ERR(sfc->hclk)) return dev_err_probe(&pdev->dev, PTR_ERR(sfc->hclk), "Failed to get sfc ahb clk\n"); + if (has_acpi_companion(&pdev->dev)) { + ret = device_property_read_u32(&pdev->dev, "clock-frequency", &sfc->frequency); + if (ret) + return dev_err_probe(&pdev->dev, ret, "Failed to find clock-frequency\n"); + } + sfc->use_dma = !of_property_read_bool(sfc->dev->of_node, "rockchip,sfc-no-dma"); if (sfc->use_dma) { From 577f1cf76ceedb5fbdc9aca4f712b21864ac15ee Mon Sep 17 00:00:00 2001 From: Jon Lin Date: Tue, 3 Dec 2024 09:35:13 +0800 Subject: [PATCH 08/49] spi: rockchip-sfc: Optimize the judgment mechanism completed by the controller There is very little data left in fifo, and the controller will complete the transmission in a short period of time, so use readl_poll_timeout() for busy wait 10us to accelerate response. Signed-off-by: Jon Lin Link: https://patch.msgid.link/20241203013513.2628810-1-jon.lin@rock-chips.com Signed-off-by: Mark Brown --- drivers/spi/spi-rockchip-sfc.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/drivers/spi/spi-rockchip-sfc.c b/drivers/spi/spi-rockchip-sfc.c index 138508d2c736..14f5b9346050 100644 --- a/drivers/spi/spi-rockchip-sfc.c +++ b/drivers/spi/spi-rockchip-sfc.c @@ -472,6 +472,16 @@ static int rockchip_sfc_xfer_done(struct rockchip_sfc *sfc, u32 timeout_us) int ret = 0; u32 status; + /* + * There is very little data left in fifo, and the controller will + * complete the transmission in a short period of time. + */ + ret = readl_poll_timeout(sfc->regbase + SFC_SR, status, + !(status & SFC_SR_IS_BUSY), + 0, 10); + if (!ret) + return 0; + ret = readl_poll_timeout(sfc->regbase + SFC_SR, status, !(status & SFC_SR_IS_BUSY), 20, timeout_us); From 1a90aae3b25b72e639da6062e0dc5efb9cff58f1 Mon Sep 17 00:00:00 2001 From: Gerhard Engleder Date: Mon, 2 Dec 2024 20:40:03 +0100 Subject: [PATCH 09/49] spi: spi-kspi2: Add KEBA SPI controller support The KEBA SPI controller is found in the system FPGA of KEBA PLC devices. It is used to connect the SPI flash chip of the FPGA and some SPI devices. It is a simple SPI controller with configurable speed. The hardware supports only single byte transfers. There are no FIFOs or interrupts. Signed-off-by: Gerhard Engleder Link: https://patch.msgid.link/20241202194003.57679-1-gerhard@engleder-embedded.com Signed-off-by: Mark Brown --- drivers/spi/Kconfig | 12 ++ drivers/spi/Makefile | 1 + drivers/spi/spi-kspi2.c | 431 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 444 insertions(+) create mode 100644 drivers/spi/spi-kspi2.c diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index f51f9466e518..ea8a31032927 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -542,6 +542,18 @@ config SPI_JCORE This enables support for the SPI master controller in the J-Core synthesizable, open source SoC. +config SPI_KSPI2 + tristate "Support for KEBA SPI master type 2 hardware" + depends on HAS_IOMEM + depends on KEBA_CP500 || COMPILE_TEST + select AUXILIARY_BUS + help + This driver supports KEBA SPI master type 2 FPGA implementation, + as found on CP500 devices for example. + + This driver can also be built as a module. If so, the module + will be called spi-kspi2. + config SPI_LM70_LLP tristate "Parallel port adapter for LM70 eval board (DEVELOPMENT)" depends on PARPORT diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile index aea5e54de195..9db7554c1864 100644 --- a/drivers/spi/Makefile +++ b/drivers/spi/Makefile @@ -74,6 +74,7 @@ obj-$(CONFIG_SPI_INTEL_PCI) += spi-intel-pci.o obj-$(CONFIG_SPI_INTEL_PLATFORM) += spi-intel-platform.o obj-$(CONFIG_SPI_LANTIQ_SSC) += spi-lantiq-ssc.o obj-$(CONFIG_SPI_JCORE) += spi-jcore.o +obj-$(CONFIG_SPI_KSPI2) += spi-kspi2.o obj-$(CONFIG_SPI_LJCA) += spi-ljca.o obj-$(CONFIG_SPI_LM70_LLP) += spi-lm70llp.o obj-$(CONFIG_SPI_LOONGSON_CORE) += spi-loongson-core.o diff --git a/drivers/spi/spi-kspi2.c b/drivers/spi/spi-kspi2.c new file mode 100644 index 000000000000..ca73ec52ce63 --- /dev/null +++ b/drivers/spi/spi-kspi2.c @@ -0,0 +1,431 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) KEBA Industrial Automation Gmbh 2024 + * + * Driver for KEBA SPI host controller type 2 FPGA IP core + */ + +#include +#include +#include + +#define KSPI2 "kspi2" + +#define KSPI2_CLK_FREQ_REG 0x03 +#define KSPI2_CLK_FREQ_MASK 0x0f +#define KSPI2_CLK_FREQ_62_5M 0x0 +#define KSPI2_CLK_FREQ_33_3M 0x1 +#define KSPI2_CLK_FREQ_125M 0x2 +#define KSPI2_CLK_FREQ_50M 0x3 +#define KSPI2_CLK_FREQ_100M 0x4 + +#define KSPI2_CONTROL_REG 0x04 +#define KSPI2_CONTROL_CLK_DIV_MAX 0x0f +#define KSPI2_CONTROL_CLK_DIV_MASK 0x0f +#define KSPI2_CONTROL_CPHA 0x10 +#define KSPI2_CONTROL_CPOL 0x20 +#define KSPI2_CONTROL_CLK_MODE_MASK 0x30 +#define KSPI2_CONTROL_INIT KSPI2_CONTROL_CLK_DIV_MAX + +#define KSPI2_STATUS_REG 0x08 +#define KSPI2_STATUS_IN_USE 0x01 +#define KSPI2_STATUS_BUSY 0x02 + +#define KSPI2_DATA_REG 0x0c + +#define KSPI2_CS_NR_REG 0x10 +#define KSPI2_CS_NR_NONE 0xff + +#define KSPI2_MODE_BITS (SPI_CPHA | SPI_CPOL) +#define KSPI2_NUM_CS 255 + +#define KSPI2_SPEED_HZ_MIN(kspi) (kspi->base_speed_hz / 65536) +#define KSPI2_SPEED_HZ_MAX(kspi) (kspi->base_speed_hz / 2) + +/* timeout is 10 times the time to transfer one byte at slowest clock */ +#define KSPI2_XFER_TIMEOUT_US(kspi) (USEC_PER_SEC / \ + KSPI2_SPEED_HZ_MIN(kspi) * 8 * 10) + +#define KSPI2_INUSE_SLEEP_US (2 * USEC_PER_MSEC) +#define KSPI2_INUSE_TIMEOUT_US (10 * USEC_PER_SEC) + +struct kspi2 { + struct keba_spi_auxdev *auxdev; + void __iomem *base; + struct spi_controller *host; + + u32 base_speed_hz; /* SPI base clock frequency in HZ */ + u8 control_shadow; + + struct spi_device **device; + int device_size; +}; + +static int kspi2_inuse_lock(struct kspi2 *kspi) +{ + u8 sts; + int ret; + + /* + * The SPI controller has an IN_USE bit for locking access to the + * controller. This enables the use of the SPI controller by other none + * Linux processors. + * + * If the SPI controller is free, then the first read returns + * IN_USE == 0. After that the SPI controller is locked and further + * reads of IN_USE return 1. + * + * The SPI controller is unlocked by writing 1 into IN_USE. + * + * The IN_USE bit acts as a hardware semaphore for the SPI controller. + * Poll for semaphore, but sleep while polling to free the CPU. + */ + ret = readb_poll_timeout(kspi->base + KSPI2_STATUS_REG, + sts, (sts & KSPI2_STATUS_IN_USE) == 0, + KSPI2_INUSE_SLEEP_US, KSPI2_INUSE_TIMEOUT_US); + if (ret != 0) + dev_warn(&kspi->auxdev->auxdev.dev, "%s err!\n", __func__); + + return ret; +} + +static void kspi2_inuse_unlock(struct kspi2 *kspi) +{ + /* unlock the controller by writing 1 into IN_USE */ + iowrite8(KSPI2_STATUS_IN_USE, kspi->base + KSPI2_STATUS_REG); +} + +static int kspi2_prepare_hardware(struct spi_controller *host) +{ + struct kspi2 *kspi = spi_controller_get_devdata(host); + + /* lock hardware semaphore before actual use of controller */ + return kspi2_inuse_lock(kspi); +} + +static int kspi2_unprepare_hardware(struct spi_controller *host) +{ + struct kspi2 *kspi = spi_controller_get_devdata(host); + + /* unlock hardware semaphore after actual use of controller */ + kspi2_inuse_unlock(kspi); + + return 0; +} + +static u8 kspi2_calc_minimal_divider(struct kspi2 *kspi, u32 max_speed_hz) +{ + u8 div; + + /* + * Divider values 2, 4, 8, 16, ..., 65536 are possible. They are coded + * as 0, 1, 2, 3, ..., 15 in the CONTROL_CLK_DIV bit. + */ + for (div = 0; div < KSPI2_CONTROL_CLK_DIV_MAX; div++) { + if ((kspi->base_speed_hz >> (div + 1)) <= max_speed_hz) + return div; + } + + /* return divider for slowest clock if loop fails to find one */ + return KSPI2_CONTROL_CLK_DIV_MAX; +} + +static void kspi2_write_control_reg(struct kspi2 *kspi, u8 val, u8 mask) +{ + /* write control register only when necessary to improve performance */ + if (val != (kspi->control_shadow & mask)) { + kspi->control_shadow = (kspi->control_shadow & ~mask) | val; + iowrite8(kspi->control_shadow, kspi->base + KSPI2_CONTROL_REG); + } +} + +static int kspi2_txrx_byte(struct kspi2 *kspi, u8 tx, u8 *rx) +{ + u8 sts; + int ret; + + /* start transfer by writing TX byte */ + iowrite8(tx, kspi->base + KSPI2_DATA_REG); + + /* wait till finished (BUSY == 0) */ + ret = readb_poll_timeout(kspi->base + KSPI2_STATUS_REG, + sts, (sts & KSPI2_STATUS_BUSY) == 0, + 0, KSPI2_XFER_TIMEOUT_US(kspi)); + if (ret != 0) + return ret; + + /* read RX byte */ + if (rx) + *rx = ioread8(kspi->base + KSPI2_DATA_REG); + + return 0; +} + +static int kspi2_process_transfer(struct kspi2 *kspi, struct spi_transfer *t) +{ + u8 tx = 0; + u8 rx; + int i; + int ret; + + for (i = 0; i < t->len; i++) { + if (t->tx_buf) + tx = ((const u8 *)t->tx_buf)[i]; + + ret = kspi2_txrx_byte(kspi, tx, &rx); + if (ret) + return ret; + + if (t->rx_buf) + ((u8 *)t->rx_buf)[i] = rx; + } + + return 0; +} + +static int kspi2_setup_transfer(struct kspi2 *kspi, + struct spi_device *spi, + struct spi_transfer *t) +{ + u32 max_speed_hz = spi->max_speed_hz; + u8 clk_div; + + /* + * spi_device (spi) has default parameters. Some of these can be + * overwritten by parameters in spi_transfer (t). + */ + if (t->bits_per_word && ((t->bits_per_word % 8) != 0)) { + dev_err(&spi->dev, "Word width %d not supported!\n", + t->bits_per_word); + + return -EINVAL; + } + + if (t->speed_hz && (t->speed_hz < max_speed_hz)) + max_speed_hz = t->speed_hz; + + clk_div = kspi2_calc_minimal_divider(kspi, max_speed_hz); + kspi2_write_control_reg(kspi, clk_div, KSPI2_CONTROL_CLK_DIV_MASK); + + return 0; +} + +static int kspi2_transfer_one(struct spi_controller *host, + struct spi_device *spi, + struct spi_transfer *t) +{ + struct kspi2 *kspi = spi_controller_get_devdata(host); + int ret; + + ret = kspi2_setup_transfer(kspi, spi, t); + if (ret != 0) + return ret; + + if (t->len) { + ret = kspi2_process_transfer(kspi, t); + if (ret != 0) + return ret; + } + + return 0; +} + +static void kspi2_set_cs(struct spi_device *spi, bool enable) +{ + struct spi_controller *host = spi->controller; + struct kspi2 *kspi = spi_controller_get_devdata(host); + + /* controller is using active low chip select signals by design */ + if (!enable) + iowrite8(spi_get_chipselect(spi, 0), kspi->base + KSPI2_CS_NR_REG); + else + iowrite8(KSPI2_CS_NR_NONE, kspi->base + KSPI2_CS_NR_REG); +} + +static int kspi2_prepare_message(struct spi_controller *host, + struct spi_message *msg) +{ + struct kspi2 *kspi = spi_controller_get_devdata(host); + struct spi_device *spi = msg->spi; + u8 mode = 0; + + /* setup SPI clock phase and polarity */ + if (spi->mode & SPI_CPHA) + mode |= KSPI2_CONTROL_CPHA; + if (spi->mode & SPI_CPOL) + mode |= KSPI2_CONTROL_CPOL; + kspi2_write_control_reg(kspi, mode, KSPI2_CONTROL_CLK_MODE_MASK); + + return 0; +} + +static int kspi2_setup(struct spi_device *spi) +{ + struct kspi2 *kspi = spi_controller_get_devdata(spi->controller); + + /* + * Check only parameters. Actual setup is done in kspi2_prepare_message + * and directly before the SPI transfer starts. + */ + + if (spi->mode & ~KSPI2_MODE_BITS) { + dev_err(&spi->dev, "Mode %d not supported!\n", spi->mode); + + return -EINVAL; + } + + if ((spi->bits_per_word % 8) != 0) { + dev_err(&spi->dev, "Word width %d not supported!\n", + spi->bits_per_word); + + return -EINVAL; + } + + if ((spi->max_speed_hz == 0) || + (spi->max_speed_hz > KSPI2_SPEED_HZ_MAX(kspi))) + spi->max_speed_hz = KSPI2_SPEED_HZ_MAX(kspi); + + if (spi->max_speed_hz < KSPI2_SPEED_HZ_MIN(kspi)) { + dev_err(&spi->dev, "Requested speed of %d Hz is too low!\n", + spi->max_speed_hz); + + return -EINVAL; + } + + return 0; +} + +static void kspi2_unregister_devices(struct kspi2 *kspi) +{ + int i; + + for (i = 0; i < kspi->device_size; i++) { + struct spi_device *device = kspi->device[i]; + + if (device) + spi_unregister_device(device); + } +} + +static int kspi2_register_devices(struct kspi2 *kspi) +{ + struct spi_board_info *info = kspi->auxdev->info; + int i; + + /* register all known SPI devices */ + for (i = 0; i < kspi->auxdev->info_size; i++) { + struct spi_device *device = spi_new_device(kspi->host, &info[i]); + + if (!device) { + kspi2_unregister_devices(kspi); + + return -ENODEV; + } + kspi->device[i] = device; + } + + return 0; +} + +static void kspi2_init(struct kspi2 *kspi) +{ + iowrite8(KSPI2_CONTROL_INIT, kspi->base + KSPI2_CONTROL_REG); + kspi->control_shadow = KSPI2_CONTROL_INIT; + + iowrite8(KSPI2_CS_NR_NONE, kspi->base + KSPI2_CS_NR_REG); +} + +static int kspi2_probe(struct auxiliary_device *auxdev, + const struct auxiliary_device_id *id) +{ + struct device *dev = &auxdev->dev; + struct spi_controller *host; + struct kspi2 *kspi; + u8 clk_reg; + int ret; + + host = devm_spi_alloc_host(dev, sizeof(struct kspi2)); + if (!host) + return -ENOMEM; + kspi = spi_controller_get_devdata(host); + kspi->auxdev = container_of(auxdev, struct keba_spi_auxdev, auxdev); + kspi->host = host; + kspi->device = devm_kcalloc(dev, kspi->auxdev->info_size, + sizeof(*kspi->device), GFP_KERNEL); + if (!kspi->device) + return -ENOMEM; + kspi->device_size = kspi->auxdev->info_size; + auxiliary_set_drvdata(auxdev, kspi); + + kspi->base = devm_ioremap_resource(dev, &kspi->auxdev->io); + if (IS_ERR(kspi->base)) + return PTR_ERR(kspi->base); + + /* read the SPI base clock frequency */ + clk_reg = ioread8(kspi->base + KSPI2_CLK_FREQ_REG); + switch (clk_reg & KSPI2_CLK_FREQ_MASK) { + case KSPI2_CLK_FREQ_62_5M: + kspi->base_speed_hz = 62500000; break; + case KSPI2_CLK_FREQ_33_3M: + kspi->base_speed_hz = 33333333; break; + case KSPI2_CLK_FREQ_125M: + kspi->base_speed_hz = 125000000; break; + case KSPI2_CLK_FREQ_50M: + kspi->base_speed_hz = 50000000; break; + case KSPI2_CLK_FREQ_100M: + kspi->base_speed_hz = 100000000; break; + default: + dev_err(dev, "Undefined SPI base clock frequency!\n"); + return -ENODEV; + } + + kspi2_init(kspi); + + host->bus_num = -1; + host->num_chipselect = KSPI2_NUM_CS; + host->mode_bits = KSPI2_MODE_BITS; + host->setup = kspi2_setup; + host->prepare_transfer_hardware = kspi2_prepare_hardware; + host->unprepare_transfer_hardware = kspi2_unprepare_hardware; + host->prepare_message = kspi2_prepare_message; + host->set_cs = kspi2_set_cs; + host->transfer_one = kspi2_transfer_one; + ret = devm_spi_register_controller(dev, host); + if (ret) { + dev_err(dev, "Failed to register host (%d)!\n", ret); + return ret; + } + + ret = kspi2_register_devices(kspi); + if (ret) { + dev_err(dev, "Failed to register devices (%d)!\n", ret); + return ret; + } + + return 0; +} + +static void kspi2_remove(struct auxiliary_device *auxdev) +{ + struct kspi2 *kspi = auxiliary_get_drvdata(auxdev); + + kspi2_unregister_devices(kspi); +} + +static const struct auxiliary_device_id kspi2_devtype_aux[] = { + { .name = "keba.spi" }, + { }, +}; +MODULE_DEVICE_TABLE(auxiliary, kspi2_devtype_aux); + +static struct auxiliary_driver kspi2_driver_aux = { + .name = KSPI2, + .id_table = kspi2_devtype_aux, + .probe = kspi2_probe, + .remove = kspi2_remove, +}; +module_auxiliary_driver(kspi2_driver_aux); + +MODULE_AUTHOR("Gerhard Engleder "); +MODULE_DESCRIPTION("KEBA SPI host controller driver"); +MODULE_LICENSE("GPL"); From b6ffe0e6147915fe3d31705e14dfbbecb724fb81 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Sun, 8 Dec 2024 18:03:16 +0200 Subject: [PATCH 10/49] spi: Unify firmware node type checks The few functions are using different approaches on how to check for the type of firmware node. Unify them to use a modern way of it. With that in place it becomes obvious that no need to have independent conditionals when they are dependent and hence the code generation can be improved a little bit (clang-18, x86_64): add/remove: 0/0 grow/shrink: 2/2 up/down: 16/-46 (-30) Total: Before=49801, After=49771, chg -0.06% Meanwhile no functional changes intended. Signed-off-by: Andy Shevchenko Link: https://patch.msgid.link/20241208195635.1271656-2-andriy.shevchenko@linux.intel.com Signed-off-by: Mark Brown --- drivers/spi/spi.c | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/drivers/spi/spi.c b/drivers/spi/spi.c index ff1add2ecb91..88f785b9e6ec 100644 --- a/drivers/spi/spi.c +++ b/drivers/spi/spi.c @@ -410,24 +410,21 @@ static int spi_probe(struct device *dev) { const struct spi_driver *sdrv = to_spi_driver(dev->driver); struct spi_device *spi = to_spi_device(dev); + struct fwnode_handle *fwnode = dev_fwnode(dev); int ret; ret = of_clk_set_defaults(dev->of_node, false); if (ret) return ret; - if (dev->of_node) { + if (is_of_node(fwnode)) { spi->irq = of_irq_get(dev->of_node, 0); if (spi->irq == -EPROBE_DEFER) return dev_err_probe(dev, -EPROBE_DEFER, "Failed to get irq\n"); if (spi->irq < 0) spi->irq = 0; - } - - if (has_acpi_companion(dev) && spi->irq < 0) { - struct acpi_device *adev = to_acpi_device_node(dev->fwnode); - - spi->irq = acpi_dev_gpio_irq_get(adev, 0); + } else if (is_acpi_device_node(fwnode) && spi->irq < 0) { + spi->irq = acpi_dev_gpio_irq_get(to_acpi_device_node(fwnode), 0); if (spi->irq == -EPROBE_DEFER) return -EPROBE_DEFER; if (spi->irq < 0) @@ -874,15 +871,18 @@ EXPORT_SYMBOL_GPL(spi_new_device); */ void spi_unregister_device(struct spi_device *spi) { + struct fwnode_handle *fwnode; + if (!spi) return; - if (spi->dev.of_node) { - of_node_clear_flag(spi->dev.of_node, OF_POPULATED); - of_node_put(spi->dev.of_node); + fwnode = dev_fwnode(&spi->dev); + if (is_of_node(fwnode)) { + of_node_clear_flag(to_of_node(fwnode), OF_POPULATED); + of_node_put(to_of_node(fwnode)); + } else if (is_acpi_device_node(fwnode)) { + acpi_device_clear_enumerated(to_acpi_device_node(fwnode)); } - if (ACPI_COMPANION(&spi->dev)) - acpi_device_clear_enumerated(ACPI_COMPANION(&spi->dev)); device_remove_software_node(&spi->dev); device_del(&spi->dev); spi_cleanup(spi); @@ -1059,7 +1059,7 @@ static void spi_toggle_csgpiod(struct spi_device *spi, u8 idx, bool enable, bool * ambiguity. That's why we use enable, that takes SPI_CS_HIGH * into account. */ - if (has_acpi_companion(&spi->dev)) + if (is_acpi_device_node(dev_fwnode(&spi->dev))) gpiod_set_value_cansleep(spi_get_csgpiod(spi, idx), !enable); else /* Polarity handled by GPIO library */ @@ -4841,7 +4841,7 @@ extern struct notifier_block spi_of_notifier; #if IS_ENABLED(CONFIG_ACPI) static int spi_acpi_controller_match(struct device *dev, const void *data) { - return ACPI_COMPANION(dev->parent) == data; + return device_match_acpi_dev(dev->parent, data); } struct spi_controller *acpi_spi_find_controller_by_adev(struct acpi_device *adev) From 0020c9d2d572b49c55b2e1fabe6f6687e11a3ced Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Sun, 8 Dec 2024 18:03:17 +0200 Subject: [PATCH 11/49] spi: Deduplicate deferred probe checks in spi_probe() Deduplicate deferred probe checks in spi_probe() and enable the error message for ACPI case as well. Signed-off-by: Andy Shevchenko Link: https://patch.msgid.link/20241208195635.1271656-3-andriy.shevchenko@linux.intel.com Signed-off-by: Mark Brown --- drivers/spi/spi.c | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/drivers/spi/spi.c b/drivers/spi/spi.c index 88f785b9e6ec..e0f79773be70 100644 --- a/drivers/spi/spi.c +++ b/drivers/spi/spi.c @@ -417,19 +417,14 @@ static int spi_probe(struct device *dev) if (ret) return ret; - if (is_of_node(fwnode)) { + if (is_of_node(fwnode)) spi->irq = of_irq_get(dev->of_node, 0); - if (spi->irq == -EPROBE_DEFER) - return dev_err_probe(dev, -EPROBE_DEFER, "Failed to get irq\n"); - if (spi->irq < 0) - spi->irq = 0; - } else if (is_acpi_device_node(fwnode) && spi->irq < 0) { + else if (is_acpi_device_node(fwnode) && spi->irq < 0) spi->irq = acpi_dev_gpio_irq_get(to_acpi_device_node(fwnode), 0); - if (spi->irq == -EPROBE_DEFER) - return -EPROBE_DEFER; - if (spi->irq < 0) - spi->irq = 0; - } + if (spi->irq == -EPROBE_DEFER) + return dev_err_probe(dev, spi->irq, "Failed to get irq\n"); + if (spi->irq < 0) + spi->irq = 0; ret = dev_pm_domain_attach(dev, true); if (ret) From 8332e667099712e05ec87ba2058af394b51ebdc9 Mon Sep 17 00:00:00 2001 From: Mingwei Zheng Date: Fri, 6 Dec 2024 20:52:06 -0500 Subject: [PATCH 12/49] spi: zynq-qspi: Add check for clk_enable() Add check for the return value of clk_enable() to catch the potential error. Fixes: c618a90dcaf3 ("spi: zynq-qspi: Drop GPIO header") Signed-off-by: Mingwei Zheng Signed-off-by: Jiasheng Jiang Link: https://patch.msgid.link/20241207015206.3689364-1-zmw12306@gmail.com Signed-off-by: Mark Brown --- drivers/spi/spi-zynq-qspi.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/drivers/spi/spi-zynq-qspi.c b/drivers/spi/spi-zynq-qspi.c index dee9c339a35e..5caf0abf3763 100644 --- a/drivers/spi/spi-zynq-qspi.c +++ b/drivers/spi/spi-zynq-qspi.c @@ -379,12 +379,21 @@ static int zynq_qspi_setup_op(struct spi_device *spi) { struct spi_controller *ctlr = spi->controller; struct zynq_qspi *qspi = spi_controller_get_devdata(ctlr); + int ret; if (ctlr->busy) return -EBUSY; - clk_enable(qspi->refclk); - clk_enable(qspi->pclk); + ret = clk_enable(qspi->refclk); + if (ret) + return ret; + + ret = clk_enable(qspi->pclk); + if (ret) { + clk_disable(qspi->refclk); + return ret; + } + zynq_qspi_write(qspi, ZYNQ_QSPI_ENABLE_OFFSET, ZYNQ_QSPI_ENABLE_ENABLE_MASK); From a17162f0b2dc97aa2dedfde8a7226fb9b5210534 Mon Sep 17 00:00:00 2001 From: Stefan Wahren Date: Mon, 9 Dec 2024 12:13:16 +0100 Subject: [PATCH 13/49] spi: mxs: support effective_speed_hz Setting spi_transfer->effective_speed_hz in transfer_one_message so that SPI client driver can use it. Signed-off-by: Stefan Wahren Link: https://patch.msgid.link/20241209111316.4767-1-wahrenst@gmx.net Signed-off-by: Mark Brown --- drivers/spi/spi-mxs.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/spi/spi-mxs.c b/drivers/spi/spi-mxs.c index e6d955d964f4..43455305fdf4 100644 --- a/drivers/spi/spi-mxs.c +++ b/drivers/spi/spi-mxs.c @@ -381,6 +381,8 @@ static int mxs_spi_transfer_one(struct spi_controller *host, if (status) break; + t->effective_speed_hz = ssp->clk_rate; + /* De-assert on last transfer, inverted by cs_change flag */ flag = (&t->transfer_list == m->transfers.prev) ^ t->cs_change ? TXRX_DEASSERT_CS : 0; From c0a0203cf57963792d59b3e4317a1d07b73df42a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cs=C3=B3k=C3=A1s=2C=20Bence?= Date: Thu, 28 Nov 2024 18:43:14 +0100 Subject: [PATCH 14/49] spi: atmel-quadspi: Create `atmel_qspi_ops` to support newer SoC families MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Refactor the code to introduce an ops struct, to prepare for merging support for later SoCs, such as SAMA7G5. This code was based on the vendor's kernel (linux4microchip). Cc'ing original contributors. Signed-off-by: Csókás, Bence Link: https://patch.msgid.link/20241128174316.3209354-2-csokas.bence@prolan.hu Signed-off-by: Mark Brown --- drivers/spi/atmel-quadspi.c | 111 +++++++++++++++++++++++++----------- 1 file changed, 77 insertions(+), 34 deletions(-) diff --git a/drivers/spi/atmel-quadspi.c b/drivers/spi/atmel-quadspi.c index 316bce577081..2aa68d58cfd7 100644 --- a/drivers/spi/atmel-quadspi.c +++ b/drivers/spi/atmel-quadspi.c @@ -138,11 +138,15 @@ #define QSPI_WPSR_WPVSRC_MASK GENMASK(15, 8) #define QSPI_WPSR_WPVSRC(src) (((src) << 8) & QSPI_WPSR_WPVSRC) +#define ATMEL_QSPI_TIMEOUT 1000 /* ms */ + struct atmel_qspi_caps { bool has_qspick; bool has_ricr; }; +struct atmel_qspi_ops; + struct atmel_qspi { void __iomem *regs; void __iomem *mem; @@ -150,13 +154,22 @@ struct atmel_qspi { struct clk *qspick; struct platform_device *pdev; const struct atmel_qspi_caps *caps; + const struct atmel_qspi_ops *ops; resource_size_t mmap_size; u32 pending; + u32 irq_mask; u32 mr; u32 scr; struct completion cmd_completion; }; +struct atmel_qspi_ops { + int (*set_cfg)(struct atmel_qspi *aq, const struct spi_mem_op *op, + u32 *offset); + int (*transfer)(struct spi_mem *mem, const struct spi_mem_op *op, + u32 offset); +}; + struct atmel_qspi_mode { u8 cmd_buswidth; u8 addr_buswidth; @@ -404,10 +417,60 @@ static int atmel_qspi_set_cfg(struct atmel_qspi *aq, return 0; } +static int atmel_qspi_wait_for_completion(struct atmel_qspi *aq, u32 irq_mask) +{ + int err = 0; + u32 sr; + + /* Poll INSTRuction End status */ + sr = atmel_qspi_read(aq, QSPI_SR); + if ((sr & irq_mask) == irq_mask) + return 0; + + /* Wait for INSTRuction End interrupt */ + reinit_completion(&aq->cmd_completion); + aq->pending = sr & irq_mask; + aq->irq_mask = irq_mask; + atmel_qspi_write(irq_mask, aq, QSPI_IER); + if (!wait_for_completion_timeout(&aq->cmd_completion, + msecs_to_jiffies(ATMEL_QSPI_TIMEOUT))) + err = -ETIMEDOUT; + atmel_qspi_write(irq_mask, aq, QSPI_IDR); + + return err; +} + +static int atmel_qspi_transfer(struct spi_mem *mem, + const struct spi_mem_op *op, u32 offset) +{ + struct atmel_qspi *aq = spi_controller_get_devdata(mem->spi->controller); + + /* Skip to the final steps if there is no data */ + if (!op->data.nbytes) + return atmel_qspi_wait_for_completion(aq, + QSPI_SR_CMD_COMPLETED); + + /* Dummy read of QSPI_IFR to synchronize APB and AHB accesses */ + (void)atmel_qspi_read(aq, QSPI_IFR); + + /* Send/Receive data */ + if (op->data.dir == SPI_MEM_DATA_IN) + memcpy_fromio(op->data.buf.in, aq->mem + offset, + op->data.nbytes); + else + memcpy_toio(aq->mem + offset, op->data.buf.out, + op->data.nbytes); + + /* Release the chip-select */ + atmel_qspi_write(QSPI_CR_LASTXFER, aq, QSPI_CR); + + return atmel_qspi_wait_for_completion(aq, QSPI_SR_CMD_COMPLETED); +} + static int atmel_qspi_exec_op(struct spi_mem *mem, const struct spi_mem_op *op) { struct atmel_qspi *aq = spi_controller_get_devdata(mem->spi->controller); - u32 sr, offset; + u32 offset; int err; /* @@ -416,46 +479,20 @@ static int atmel_qspi_exec_op(struct spi_mem *mem, const struct spi_mem_op *op) * when the flash memories overrun the controller's memory space. */ if (op->addr.val + op->data.nbytes > aq->mmap_size) - return -ENOTSUPP; + return -EOPNOTSUPP; + + if (op->addr.nbytes > 4) + return -EOPNOTSUPP; err = pm_runtime_resume_and_get(&aq->pdev->dev); if (err < 0) return err; - err = atmel_qspi_set_cfg(aq, op, &offset); + err = aq->ops->set_cfg(aq, op, &offset); if (err) goto pm_runtime_put; - /* Skip to the final steps if there is no data */ - if (op->data.nbytes) { - /* Dummy read of QSPI_IFR to synchronize APB and AHB accesses */ - (void)atmel_qspi_read(aq, QSPI_IFR); - - /* Send/Receive data */ - if (op->data.dir == SPI_MEM_DATA_IN) - memcpy_fromio(op->data.buf.in, aq->mem + offset, - op->data.nbytes); - else - memcpy_toio(aq->mem + offset, op->data.buf.out, - op->data.nbytes); - - /* Release the chip-select */ - atmel_qspi_write(QSPI_CR_LASTXFER, aq, QSPI_CR); - } - - /* Poll INSTRuction End status */ - sr = atmel_qspi_read(aq, QSPI_SR); - if ((sr & QSPI_SR_CMD_COMPLETED) == QSPI_SR_CMD_COMPLETED) - goto pm_runtime_put; - - /* Wait for INSTRuction End interrupt */ - reinit_completion(&aq->cmd_completion); - aq->pending = sr & QSPI_SR_CMD_COMPLETED; - atmel_qspi_write(QSPI_SR_CMD_COMPLETED, aq, QSPI_IER); - if (!wait_for_completion_timeout(&aq->cmd_completion, - msecs_to_jiffies(1000))) - err = -ETIMEDOUT; - atmel_qspi_write(QSPI_SR_CMD_COMPLETED, aq, QSPI_IDR); + err = aq->ops->transfer(mem, op, offset); pm_runtime_put: pm_runtime_mark_last_busy(&aq->pdev->dev); @@ -599,12 +636,17 @@ static irqreturn_t atmel_qspi_interrupt(int irq, void *dev_id) return IRQ_NONE; aq->pending |= pending; - if ((aq->pending & QSPI_SR_CMD_COMPLETED) == QSPI_SR_CMD_COMPLETED) + if ((aq->pending & aq->irq_mask) == aq->irq_mask) complete(&aq->cmd_completion); return IRQ_HANDLED; } +static const struct atmel_qspi_ops atmel_qspi_ops = { + .set_cfg = atmel_qspi_set_cfg, + .transfer = atmel_qspi_transfer, +}; + static int atmel_qspi_probe(struct platform_device *pdev) { struct spi_controller *ctrl; @@ -629,6 +671,7 @@ static int atmel_qspi_probe(struct platform_device *pdev) init_completion(&aq->cmd_completion); aq->pdev = pdev; + aq->ops = &atmel_qspi_ops; /* Map the registers */ aq->regs = devm_platform_ioremap_resource_byname(pdev, "qspi_base"); From 5af42209a4d23adc9a09e59e586d1b47fef3cc86 Mon Sep 17 00:00:00 2001 From: Tudor Ambarus Date: Thu, 28 Nov 2024 18:43:15 +0100 Subject: [PATCH 15/49] spi: atmel-quadspi: Add support for sama7g5 QSPI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The sama7g5 QSPI controller uses dedicated clocks for the QSPI Controller Interface and the QSPI Controller Core, and requires synchronization before accessing registers or bit fields. QSPI_SR.SYNCBSY must be zero before accessing any of the bits: QSPI_CR.QSPIEN, QSPI_CR.QSPIDIS, QSPI_CR.SRFRSH, QSPI_CR.SWRST, QSPI_CR.UPDCFG, QSPI_CR.STTFR, QSPI_CR.RTOUT, QSPI_CR.LASTXFER. Also, the QSPI controller core configuration can be updated by writing the QSPI_CR.UPDCFG bit to ‘1’. This is needed by the following registers: QSPI_MR, QSPI_SCR, QSPI_IAR, QSPI_WICR, QSPI_IFR, QSPI_RICR, QSPI_SMR, QSPI_SKR,QSPI_REFRESH, QSPI_WRACNT QSPI_PCALCFG. The Octal SPI supports frequencies up to 200 MHZ DDR. The need for output impedance calibration arises. To avoid the degradation of the signal quality, a PAD calibration cell is used to adjust the output impedance to the driven I/Os. The transmission flow requires different sequences for setting the configuration and for the actual transfer, than what is in the sama5d2 and sam9x60 versions of the IP. Different interrupts are handled. aq->ops->set_cfg() and aq->ops->transfer() are introduced to help differentiating the flows. Tested single and octal SPI mode with mx66lm1g45g. Signed-off-by: Tudor Ambarus Link: https://lore.kernel.org/r/20211214133404.121739-1-tudor.ambarus@microchip.com [varshini.rajendran@microchip.com: Fixed conflicts and ported to 6.1.4] Signed-off-by: Varshini Rajendran [ csokas.bence: Forward-port to master and address feedback ] Signed-off-by: Csókás, Bence Link: https://patch.msgid.link/20241128174316.3209354-3-csokas.bence@prolan.hu Signed-off-by: Mark Brown --- drivers/spi/atmel-quadspi.c | 816 +++++++++++++++++++++++++++++++++++- 1 file changed, 798 insertions(+), 18 deletions(-) diff --git a/drivers/spi/atmel-quadspi.c b/drivers/spi/atmel-quadspi.c index 2aa68d58cfd7..73cf0c3f1477 100644 --- a/drivers/spi/atmel-quadspi.c +++ b/drivers/spi/atmel-quadspi.c @@ -11,11 +11,15 @@ * This driver is based on drivers/mtd/spi-nor/fsl-quadspi.c from Freescale. */ +#include #include #include +#include +#include #include #include #include +#include #include #include #include @@ -34,6 +38,7 @@ #define QSPI_IDR 0x0018 /* Interrupt Disable Register */ #define QSPI_IMR 0x001c /* Interrupt Mask Register */ #define QSPI_SCR 0x0020 /* Serial Clock Register */ +#define QSPI_SR2 0x0024 /* SAMA7G5 Status Register */ #define QSPI_IAR 0x0030 /* Instruction Address Register */ #define QSPI_ICR 0x0034 /* Instruction Code Register */ @@ -44,16 +49,32 @@ #define QSPI_SMR 0x0040 /* Scrambling Mode Register */ #define QSPI_SKR 0x0044 /* Scrambling Key Register */ +#define QSPI_REFRESH 0x0050 /* Refresh Register */ +#define QSPI_WRACNT 0x0054 /* Write Access Counter Register */ +#define QSPI_DLLCFG 0x0058 /* DLL Configuration Register */ +#define QSPI_PCALCFG 0x005C /* Pad Calibration Configuration Register */ +#define QSPI_PCALBP 0x0060 /* Pad Calibration Bypass Register */ +#define QSPI_TOUT 0x0064 /* Timeout Register */ + #define QSPI_WPMR 0x00E4 /* Write Protection Mode Register */ #define QSPI_WPSR 0x00E8 /* Write Protection Status Register */ #define QSPI_VERSION 0x00FC /* Version Register */ +#define SAMA7G5_QSPI0_MAX_SPEED_HZ 200000000 +#define SAMA7G5_QSPI1_SDR_MAX_SPEED_HZ 133000000 /* Bitfields in QSPI_CR (Control Register) */ #define QSPI_CR_QSPIEN BIT(0) #define QSPI_CR_QSPIDIS BIT(1) +#define QSPI_CR_DLLON BIT(2) +#define QSPI_CR_DLLOFF BIT(3) +#define QSPI_CR_STPCAL BIT(4) +#define QSPI_CR_SRFRSH BIT(5) #define QSPI_CR_SWRST BIT(7) +#define QSPI_CR_UPDCFG BIT(8) +#define QSPI_CR_STTFR BIT(9) +#define QSPI_CR_RTOUT BIT(10) #define QSPI_CR_LASTXFER BIT(24) /* Bitfields in QSPI_MR (Mode Register) */ @@ -61,12 +82,14 @@ #define QSPI_MR_LLB BIT(1) #define QSPI_MR_WDRBT BIT(2) #define QSPI_MR_SMRM BIT(3) +#define QSPI_MR_DQSDLYEN BIT(3) #define QSPI_MR_CSMODE_MASK GENMASK(5, 4) #define QSPI_MR_CSMODE_NOT_RELOADED (0 << 4) #define QSPI_MR_CSMODE_LASTXFER (1 << 4) #define QSPI_MR_CSMODE_SYSTEMATICALLY (2 << 4) #define QSPI_MR_NBBITS_MASK GENMASK(11, 8) #define QSPI_MR_NBBITS(n) ((((n) - 8) << 8) & QSPI_MR_NBBITS_MASK) +#define QSPI_MR_OENSD BIT(15) #define QSPI_MR_DLYBCT_MASK GENMASK(23, 16) #define QSPI_MR_DLYBCT(n) (((n) << 16) & QSPI_MR_DLYBCT_MASK) #define QSPI_MR_DLYCS_MASK GENMASK(31, 24) @@ -80,6 +103,13 @@ #define QSPI_SR_CSR BIT(8) #define QSPI_SR_CSS BIT(9) #define QSPI_SR_INSTRE BIT(10) +#define QSPI_SR_LWRA BIT(11) +#define QSPI_SR_QITF BIT(12) +#define QSPI_SR_QITR BIT(13) +#define QSPI_SR_CSFA BIT(14) +#define QSPI_SR_CSRA BIT(15) +#define QSPI_SR_RFRSHD BIT(16) +#define QSPI_SR_TOUT BIT(17) #define QSPI_SR_QSPIENS BIT(24) #define QSPI_SR_CMD_COMPLETED (QSPI_SR_INSTRE | QSPI_SR_CSR) @@ -92,9 +122,22 @@ #define QSPI_SCR_DLYBS_MASK GENMASK(23, 16) #define QSPI_SCR_DLYBS(n) (((n) << 16) & QSPI_SCR_DLYBS_MASK) +/* Bitfields in QSPI_SR2 (SAMA7G5 Status Register) */ +#define QSPI_SR2_SYNCBSY BIT(0) +#define QSPI_SR2_QSPIENS BIT(1) +#define QSPI_SR2_CSS BIT(2) +#define QSPI_SR2_RBUSY BIT(3) +#define QSPI_SR2_HIDLE BIT(4) +#define QSPI_SR2_DLOCK BIT(5) +#define QSPI_SR2_CALBSY BIT(6) + +/* Bitfields in QSPI_IAR (Instruction Address Register) */ +#define QSPI_IAR_ADDR GENMASK(31, 0) + /* Bitfields in QSPI_ICR (Read/Write Instruction Code Register) */ #define QSPI_ICR_INST_MASK GENMASK(7, 0) #define QSPI_ICR_INST(inst) (((inst) << 0) & QSPI_ICR_INST_MASK) +#define QSPI_ICR_INST_MASK_SAMA7G5 GENMASK(15, 0) #define QSPI_ICR_OPT_MASK GENMASK(23, 16) #define QSPI_ICR_OPT(opt) (((opt) << 16) & QSPI_ICR_OPT_MASK) @@ -107,6 +150,9 @@ #define QSPI_IFR_WIDTH_QUAD_IO (4 << 0) #define QSPI_IFR_WIDTH_DUAL_CMD (5 << 0) #define QSPI_IFR_WIDTH_QUAD_CMD (6 << 0) +#define QSPI_IFR_WIDTH_OCT_OUTPUT (7 << 0) +#define QSPI_IFR_WIDTH_OCT_IO (8 << 0) +#define QSPI_IFR_WIDTH_OCT_CMD (9 << 0) #define QSPI_IFR_INSTEN BIT(4) #define QSPI_IFR_ADDREN BIT(5) #define QSPI_IFR_OPTEN BIT(6) @@ -117,19 +163,60 @@ #define QSPI_IFR_OPTL_4BIT (2 << 8) #define QSPI_IFR_OPTL_8BIT (3 << 8) #define QSPI_IFR_ADDRL BIT(10) +#define QSPI_IFR_ADDRL_SAMA7G5 GENMASK(11, 10) #define QSPI_IFR_TFRTYP_MEM BIT(12) #define QSPI_IFR_SAMA5D2_WRITE_TRSFR BIT(13) #define QSPI_IFR_CRM BIT(14) +#define QSPI_IFR_DDREN BIT(15) #define QSPI_IFR_NBDUM_MASK GENMASK(20, 16) #define QSPI_IFR_NBDUM(n) (((n) << 16) & QSPI_IFR_NBDUM_MASK) +#define QSPI_IFR_END BIT(22) +#define QSPI_IFR_SMRM BIT(23) #define QSPI_IFR_APBTFRTYP_READ BIT(24) /* Defined in SAM9X60 */ +#define QSPI_IFR_DQSEN BIT(25) +#define QSPI_IFR_DDRCMDEN BIT(26) +#define QSPI_IFR_HFWBEN BIT(27) +#define QSPI_IFR_PROTTYP GENMASK(29, 28) +#define QSPI_IFR_PROTTYP_STD_SPI 0 +#define QSPI_IFR_PROTTYP_TWIN_QUAD 1 +#define QSPI_IFR_PROTTYP_OCTAFLASH 2 +#define QSPI_IFR_PROTTYP_HYPERFLASH 3 /* Bitfields in QSPI_SMR (Scrambling Mode Register) */ #define QSPI_SMR_SCREN BIT(0) #define QSPI_SMR_RVDIS BIT(1) +#define QSPI_SMR_SCRKL BIT(2) + +/* Bitfields in QSPI_REFRESH (Refresh Register) */ +#define QSPI_REFRESH_DELAY_COUNTER GENMASK(31, 0) + +/* Bitfields in QSPI_WRACNT (Write Access Counter Register) */ +#define QSPI_WRACNT_NBWRA GENMASK(31, 0) + +/* Bitfields in QSPI_DLLCFG (DLL Configuration Register) */ +#define QSPI_DLLCFG_RANGE BIT(0) + +/* Bitfields in QSPI_PCALCFG (DLL Pad Calibration Configuration Register) */ +#define QSPI_PCALCFG_AAON BIT(0) +#define QSPI_PCALCFG_DAPCAL BIT(1) +#define QSPI_PCALCFG_DIFFPM BIT(2) +#define QSPI_PCALCFG_CLKDIV GENMASK(6, 4) +#define QSPI_PCALCFG_CALCNT GENMASK(16, 8) +#define QSPI_PCALCFG_CALP GENMASK(27, 24) +#define QSPI_PCALCFG_CALN GENMASK(31, 28) + +/* Bitfields in QSPI_PCALBP (DLL Pad Calibration Bypass Register) */ +#define QSPI_PCALBP_BPEN BIT(0) +#define QSPI_PCALBP_CALPBP GENMASK(11, 8) +#define QSPI_PCALBP_CALNBP GENMASK(19, 16) + +/* Bitfields in QSPI_TOUT (Timeout Register) */ +#define QSPI_TOUT_TCNTM GENMASK(15, 0) /* Bitfields in QSPI_WPMR (Write Protection Mode Register) */ #define QSPI_WPMR_WPEN BIT(0) +#define QSPI_WPMR_WPITEN BIT(1) +#define QSPI_WPMR_WPCREN BIT(2) #define QSPI_WPMR_WPKEY_MASK GENMASK(31, 8) #define QSPI_WPMR_WPKEY(wpkey) (((wpkey) << 8) & QSPI_WPMR_WPKEY_MASK) @@ -139,10 +226,42 @@ #define QSPI_WPSR_WPVSRC(src) (((src) << 8) & QSPI_WPSR_WPVSRC) #define ATMEL_QSPI_TIMEOUT 1000 /* ms */ +#define ATMEL_QSPI_SYNC_TIMEOUT 300 /* ms */ +#define QSPI_DLLCFG_THRESHOLD_FREQ 90000000U +#define QSPI_CALIB_TIME 2000 /* 2 us */ + +/* Use PIO for small transfers. */ +#define ATMEL_QSPI_DMA_MIN_BYTES 16 +/** + * struct atmel_qspi_pcal - Pad Calibration Clock Division + * @pclk_rate: peripheral clock rate. + * @pclkdiv: calibration clock division. The clock applied to the calibration + * cell is divided by pclkdiv + 1. + */ +struct atmel_qspi_pcal { + u32 pclk_rate; + u8 pclk_div; +}; + +#define ATMEL_QSPI_PCAL_ARRAY_SIZE 8 +static const struct atmel_qspi_pcal pcal[ATMEL_QSPI_PCAL_ARRAY_SIZE] = { + {25000000, 0}, + {50000000, 1}, + {75000000, 2}, + {100000000, 3}, + {125000000, 4}, + {150000000, 5}, + {175000000, 6}, + {200000000, 7}, +}; struct atmel_qspi_caps { + u32 max_speed_hz; bool has_qspick; + bool has_gclk; bool has_ricr; + bool octal; + bool has_dma; }; struct atmel_qspi_ops; @@ -152,6 +271,7 @@ struct atmel_qspi { void __iomem *mem; struct clk *pclk; struct clk *qspick; + struct clk *gclk; struct platform_device *pdev; const struct atmel_qspi_caps *caps; const struct atmel_qspi_ops *ops; @@ -160,7 +280,12 @@ struct atmel_qspi { u32 irq_mask; u32 mr; u32 scr; + u32 slave_max_speed_hz; struct completion cmd_completion; + struct completion dma_completion; + dma_addr_t mmap_phys_base; + struct dma_chan *rx_chan; + struct dma_chan *tx_chan; }; struct atmel_qspi_ops { @@ -187,6 +312,19 @@ static const struct atmel_qspi_mode atmel_qspi_modes[] = { { 4, 4, 4, QSPI_IFR_WIDTH_QUAD_CMD }, }; +static const struct atmel_qspi_mode atmel_qspi_sama7g5_modes[] = { + { 1, 1, 1, QSPI_IFR_WIDTH_SINGLE_BIT_SPI }, + { 1, 1, 2, QSPI_IFR_WIDTH_DUAL_OUTPUT }, + { 1, 1, 4, QSPI_IFR_WIDTH_QUAD_OUTPUT }, + { 1, 2, 2, QSPI_IFR_WIDTH_DUAL_IO }, + { 1, 4, 4, QSPI_IFR_WIDTH_QUAD_IO }, + { 2, 2, 2, QSPI_IFR_WIDTH_DUAL_CMD }, + { 4, 4, 4, QSPI_IFR_WIDTH_QUAD_CMD }, + { 1, 1, 8, QSPI_IFR_WIDTH_OCT_OUTPUT }, + { 1, 8, 8, QSPI_IFR_WIDTH_OCT_IO }, + { 8, 8, 8, QSPI_IFR_WIDTH_OCT_CMD }, +}; + #ifdef VERBOSE_DEBUG static const char *atmel_qspi_reg_name(u32 offset, char *tmp, size_t sz) { @@ -209,6 +347,8 @@ static const char *atmel_qspi_reg_name(u32 offset, char *tmp, size_t sz) return "IMR"; case QSPI_SCR: return "SCR"; + case QSPI_SR2: + return "SR2"; case QSPI_IAR: return "IAR"; case QSPI_ICR: @@ -221,6 +361,18 @@ static const char *atmel_qspi_reg_name(u32 offset, char *tmp, size_t sz) return "SMR"; case QSPI_SKR: return "SKR"; + case QSPI_REFRESH: + return "REFRESH"; + case QSPI_WRACNT: + return "WRACNT"; + case QSPI_DLLCFG: + return "DLLCFG"; + case QSPI_PCALCFG: + return "PCALCFG"; + case QSPI_PCALBP: + return "PCALBP"; + case QSPI_TOUT: + return "TOUT"; case QSPI_WPMR: return "WPMR"; case QSPI_WPSR: @@ -288,12 +440,31 @@ static int atmel_qspi_find_mode(const struct spi_mem_op *op) return -EOPNOTSUPP; } +static int atmel_qspi_sama7g5_find_mode(const struct spi_mem_op *op) +{ + u32 i; + + for (i = 0; i < ARRAY_SIZE(atmel_qspi_sama7g5_modes); i++) + if (atmel_qspi_is_compatible(op, &atmel_qspi_sama7g5_modes[i])) + return i; + + return -EOPNOTSUPP; +} + static bool atmel_qspi_supports_op(struct spi_mem *mem, const struct spi_mem_op *op) { + struct atmel_qspi *aq = spi_controller_get_devdata(mem->spi->controller); if (!spi_mem_default_supports_op(mem, op)) return false; + if (aq->caps->octal) { + if (atmel_qspi_sama7g5_find_mode(op) < 0) + return false; + else + return true; + } + if (atmel_qspi_find_mode(op) < 0) return false; @@ -467,6 +638,296 @@ static int atmel_qspi_transfer(struct spi_mem *mem, return atmel_qspi_wait_for_completion(aq, QSPI_SR_CMD_COMPLETED); } +static int atmel_qspi_reg_sync(struct atmel_qspi *aq) +{ + u32 val; + int ret; + + ret = readl_poll_timeout(aq->regs + QSPI_SR2, val, + !(val & QSPI_SR2_SYNCBSY), 40, + ATMEL_QSPI_SYNC_TIMEOUT); + return ret; +} + +static int atmel_qspi_update_config(struct atmel_qspi *aq) +{ + int ret; + + ret = atmel_qspi_reg_sync(aq); + if (ret) + return ret; + atmel_qspi_write(QSPI_CR_UPDCFG, aq, QSPI_CR); + return atmel_qspi_reg_sync(aq); +} + +static int atmel_qspi_sama7g5_set_cfg(struct atmel_qspi *aq, + const struct spi_mem_op *op, u32 *offset) +{ + u32 iar, icr, ifr; + int mode, ret; + + iar = 0; + icr = FIELD_PREP(QSPI_ICR_INST_MASK_SAMA7G5, op->cmd.opcode); + ifr = QSPI_IFR_INSTEN; + + mode = atmel_qspi_sama7g5_find_mode(op); + if (mode < 0) + return mode; + ifr |= atmel_qspi_sama7g5_modes[mode].config; + + if (op->dummy.buswidth && op->dummy.nbytes) { + if (op->addr.dtr && op->dummy.dtr && op->data.dtr) + ifr |= QSPI_IFR_NBDUM(op->dummy.nbytes * 8 / + (2 * op->dummy.buswidth)); + else + ifr |= QSPI_IFR_NBDUM(op->dummy.nbytes * 8 / + op->dummy.buswidth); + } + + if (op->addr.buswidth && op->addr.nbytes) { + ifr |= FIELD_PREP(QSPI_IFR_ADDRL_SAMA7G5, op->addr.nbytes - 1) | + QSPI_IFR_ADDREN; + iar = FIELD_PREP(QSPI_IAR_ADDR, op->addr.val); + } + + if (op->addr.dtr && op->dummy.dtr && op->data.dtr) { + ifr |= QSPI_IFR_DDREN; + if (op->cmd.dtr) + ifr |= QSPI_IFR_DDRCMDEN; + + ifr |= QSPI_IFR_DQSEN; + } + + if (op->cmd.buswidth == 8 || op->addr.buswidth == 8 || + op->data.buswidth == 8) + ifr |= FIELD_PREP(QSPI_IFR_PROTTYP, QSPI_IFR_PROTTYP_OCTAFLASH); + + /* offset of the data access in the QSPI memory space */ + *offset = iar; + + /* Set data enable */ + if (op->data.nbytes) { + ifr |= QSPI_IFR_DATAEN; + + if (op->addr.nbytes) + ifr |= QSPI_IFR_TFRTYP_MEM; + } + + /* + * If the QSPI controller is set in regular SPI mode, set it in + * Serial Memory Mode (SMM). + */ + if (aq->mr != QSPI_MR_SMM) { + atmel_qspi_write(QSPI_MR_SMM | QSPI_MR_DQSDLYEN, aq, QSPI_MR); + aq->mr = QSPI_MR_SMM; + + ret = atmel_qspi_update_config(aq); + if (ret) + return ret; + } + + /* Clear pending interrupts */ + (void)atmel_qspi_read(aq, QSPI_SR); + + /* Set QSPI Instruction Frame registers */ + if (op->addr.nbytes && !op->data.nbytes) + atmel_qspi_write(iar, aq, QSPI_IAR); + + if (op->data.dir == SPI_MEM_DATA_IN) { + atmel_qspi_write(icr, aq, QSPI_RICR); + } else { + atmel_qspi_write(icr, aq, QSPI_WICR); + if (op->data.nbytes) + atmel_qspi_write(FIELD_PREP(QSPI_WRACNT_NBWRA, + op->data.nbytes), + aq, QSPI_WRACNT); + } + + atmel_qspi_write(ifr, aq, QSPI_IFR); + + return atmel_qspi_update_config(aq); +} + +static void atmel_qspi_dma_callback(void *param) +{ + struct atmel_qspi *aq = param; + + complete(&aq->dma_completion); +} + +static int atmel_qspi_dma_xfer(struct atmel_qspi *aq, struct dma_chan *chan, + dma_addr_t dma_dst, dma_addr_t dma_src, + unsigned int len) +{ + struct dma_async_tx_descriptor *tx; + dma_cookie_t cookie; + int ret; + + tx = dmaengine_prep_dma_memcpy(chan, dma_dst, dma_src, len, + DMA_PREP_INTERRUPT | DMA_CTRL_ACK); + if (!tx) { + dev_err(&aq->pdev->dev, "device_prep_dma_memcpy error\n"); + return -EIO; + } + + reinit_completion(&aq->dma_completion); + tx->callback = atmel_qspi_dma_callback; + tx->callback_param = aq; + cookie = tx->tx_submit(tx); + ret = dma_submit_error(cookie); + if (ret) { + dev_err(&aq->pdev->dev, "dma_submit_error %d\n", cookie); + return ret; + } + + dma_async_issue_pending(chan); + ret = wait_for_completion_timeout(&aq->dma_completion, + msecs_to_jiffies(20 * ATMEL_QSPI_TIMEOUT)); + if (ret == 0) { + dmaengine_terminate_sync(chan); + dev_err(&aq->pdev->dev, "DMA wait_for_completion_timeout\n"); + return -ETIMEDOUT; + } + + return 0; +} + +static int atmel_qspi_dma_rx_xfer(struct spi_mem *mem, + const struct spi_mem_op *op, + struct sg_table *sgt, loff_t loff) +{ + struct atmel_qspi *aq = + spi_controller_get_devdata(mem->spi->controller); + struct scatterlist *sg; + dma_addr_t dma_src; + unsigned int i, len; + int ret; + + dma_src = aq->mmap_phys_base + loff; + + for_each_sg(sgt->sgl, sg, sgt->nents, i) { + len = sg_dma_len(sg); + ret = atmel_qspi_dma_xfer(aq, aq->rx_chan, sg_dma_address(sg), + dma_src, len); + if (ret) + return ret; + dma_src += len; + } + + return 0; +} + +static int atmel_qspi_dma_tx_xfer(struct spi_mem *mem, + const struct spi_mem_op *op, + struct sg_table *sgt, loff_t loff) +{ + struct atmel_qspi *aq = + spi_controller_get_devdata(mem->spi->controller); + struct scatterlist *sg; + dma_addr_t dma_dst; + unsigned int i, len; + int ret; + + dma_dst = aq->mmap_phys_base + loff; + + for_each_sg(sgt->sgl, sg, sgt->nents, i) { + len = sg_dma_len(sg); + ret = atmel_qspi_dma_xfer(aq, aq->tx_chan, dma_dst, + sg_dma_address(sg), len); + if (ret) + return ret; + dma_dst += len; + } + + return 0; +} + +static int atmel_qspi_dma_transfer(struct spi_mem *mem, + const struct spi_mem_op *op, loff_t loff) +{ + struct sg_table sgt; + int ret; + + ret = spi_controller_dma_map_mem_op_data(mem->spi->controller, op, + &sgt); + if (ret) + return ret; + + if (op->data.dir == SPI_MEM_DATA_IN) + ret = atmel_qspi_dma_rx_xfer(mem, op, &sgt, loff); + else + ret = atmel_qspi_dma_tx_xfer(mem, op, &sgt, loff); + + spi_controller_dma_unmap_mem_op_data(mem->spi->controller, op, &sgt); + + return ret; +} + +static int atmel_qspi_sama7g5_transfer(struct spi_mem *mem, + const struct spi_mem_op *op, u32 offset) +{ + struct atmel_qspi *aq = + spi_controller_get_devdata(mem->spi->controller); + u32 val; + int ret; + + if (!op->data.nbytes) { + /* Start the transfer. */ + ret = atmel_qspi_reg_sync(aq); + if (ret) + return ret; + atmel_qspi_write(QSPI_CR_STTFR, aq, QSPI_CR); + + return atmel_qspi_wait_for_completion(aq, QSPI_SR_CSRA); + } + + /* Send/Receive data. */ + if (op->data.dir == SPI_MEM_DATA_IN) { + if (aq->rx_chan && op->addr.nbytes && + op->data.nbytes > ATMEL_QSPI_DMA_MIN_BYTES) { + ret = atmel_qspi_dma_transfer(mem, op, offset); + if (ret) + return ret; + } else { + memcpy_fromio(op->data.buf.in, aq->mem + offset, + op->data.nbytes); + } + + if (op->addr.nbytes) { + ret = readl_poll_timeout(aq->regs + QSPI_SR2, val, + !(val & QSPI_SR2_RBUSY), 40, + ATMEL_QSPI_SYNC_TIMEOUT); + if (ret) + return ret; + } + } else { + if (aq->tx_chan && op->addr.nbytes && + op->data.nbytes > ATMEL_QSPI_DMA_MIN_BYTES) { + ret = atmel_qspi_dma_transfer(mem, op, offset); + if (ret) + return ret; + } else { + memcpy_toio(aq->mem + offset, op->data.buf.out, + op->data.nbytes); + } + + ret = atmel_qspi_wait_for_completion(aq, QSPI_SR_LWRA); + if (ret) + return ret; + } + + /* Release the chip-select. */ + ret = atmel_qspi_reg_sync(aq); + if (ret) { + pm_runtime_mark_last_busy(&aq->pdev->dev); + pm_runtime_put_autosuspend(&aq->pdev->dev); + return ret; + } + atmel_qspi_write(QSPI_CR_LASTXFER, aq, QSPI_CR); + + return atmel_qspi_wait_for_completion(aq, QSPI_SR_CSRA); +} + static int atmel_qspi_exec_op(struct spi_mem *mem, const struct spi_mem_op *op) { struct atmel_qspi *aq = spi_controller_get_devdata(mem->spi->controller); @@ -511,6 +972,160 @@ static const struct spi_controller_mem_ops atmel_qspi_mem_ops = { .get_name = atmel_qspi_get_name }; +static int atmel_qspi_set_pad_calibration(struct atmel_qspi *aq) +{ + unsigned long pclk_rate; + u32 status, val; + int i, ret; + u8 pclk_div = 0; + + pclk_rate = clk_get_rate(aq->pclk); + if (!pclk_rate) + return -EINVAL; + + for (i = 0; i < ATMEL_QSPI_PCAL_ARRAY_SIZE; i++) { + if (pclk_rate <= pcal[i].pclk_rate) { + pclk_div = pcal[i].pclk_div; + break; + } + } + + /* + * Use the biggest divider in case the peripheral clock exceeds + * 200MHZ. + */ + if (pclk_rate > pcal[ATMEL_QSPI_PCAL_ARRAY_SIZE - 1].pclk_rate) + pclk_div = pcal[ATMEL_QSPI_PCAL_ARRAY_SIZE - 1].pclk_div; + + /* Disable QSPI while configuring the pad calibration. */ + status = atmel_qspi_read(aq, QSPI_SR2); + if (status & QSPI_SR2_QSPIENS) { + ret = atmel_qspi_reg_sync(aq); + if (ret) + return ret; + atmel_qspi_write(QSPI_CR_QSPIDIS, aq, QSPI_CR); + } + + /* + * The analog circuitry is not shut down at the end of the calibration + * and the start-up time is only required for the first calibration + * sequence, thus increasing performance. Set the delay between the Pad + * calibration analog circuitry and the calibration request to 2us. + */ + atmel_qspi_write(QSPI_PCALCFG_AAON | + FIELD_PREP(QSPI_PCALCFG_CLKDIV, pclk_div) | + FIELD_PREP(QSPI_PCALCFG_CALCNT, + 2 * (pclk_rate / 1000000)), + aq, QSPI_PCALCFG); + + /* DLL On + start calibration. */ + atmel_qspi_write(QSPI_CR_DLLON | QSPI_CR_STPCAL, aq, QSPI_CR); + + /* Check synchronization status before updating configuration. */ + ret = readl_poll_timeout(aq->regs + QSPI_SR2, val, + (val & QSPI_SR2_DLOCK) && + !(val & QSPI_SR2_CALBSY), 40, + ATMEL_QSPI_TIMEOUT); + + /* Refresh analogic blocks every 1 ms.*/ + atmel_qspi_write(FIELD_PREP(QSPI_REFRESH_DELAY_COUNTER, + aq->slave_max_speed_hz / 1000), + aq, QSPI_REFRESH); + + return ret; +} + +static int atmel_qspi_set_gclk(struct atmel_qspi *aq) +{ + u32 status, val; + int ret; + + /* Disable DLL before setting GCLK */ + status = atmel_qspi_read(aq, QSPI_SR2); + if (status & QSPI_SR2_DLOCK) { + atmel_qspi_write(QSPI_CR_DLLOFF, aq, QSPI_CR); + + ret = readl_poll_timeout(aq->regs + QSPI_SR2, val, + !(val & QSPI_SR2_DLOCK), 40, + ATMEL_QSPI_TIMEOUT); + if (ret) + return ret; + } + + if (aq->slave_max_speed_hz > QSPI_DLLCFG_THRESHOLD_FREQ) + atmel_qspi_write(QSPI_DLLCFG_RANGE, aq, QSPI_DLLCFG); + else + atmel_qspi_write(0, aq, QSPI_DLLCFG); + + ret = clk_set_rate(aq->gclk, aq->slave_max_speed_hz); + if (ret) { + dev_err(&aq->pdev->dev, "Failed to set generic clock rate.\n"); + return ret; + } + + /* Enable the QSPI generic clock */ + ret = clk_prepare_enable(aq->gclk); + if (ret) + dev_err(&aq->pdev->dev, "Failed to enable generic clock.\n"); + + return ret; +} + +static int atmel_qspi_sama7g5_init(struct atmel_qspi *aq) +{ + u32 val; + int ret; + + ret = atmel_qspi_set_gclk(aq); + if (ret) + return ret; + + if (aq->caps->octal) { + ret = atmel_qspi_set_pad_calibration(aq); + if (ret) + return ret; + } else { + atmel_qspi_write(QSPI_CR_DLLON, aq, QSPI_CR); + ret = readl_poll_timeout(aq->regs + QSPI_SR2, val, + (val & QSPI_SR2_DLOCK), 40, + ATMEL_QSPI_TIMEOUT); + } + + /* Set the QSPI controller by default in Serial Memory Mode */ + atmel_qspi_write(QSPI_MR_SMM | QSPI_MR_DQSDLYEN, aq, QSPI_MR); + aq->mr = QSPI_MR_SMM; + ret = atmel_qspi_update_config(aq); + if (ret) + return ret; + + /* Enable the QSPI controller. */ + atmel_qspi_write(QSPI_CR_QSPIEN, aq, QSPI_CR); + ret = readl_poll_timeout(aq->regs + QSPI_SR2, val, + val & QSPI_SR2_QSPIENS, 40, + ATMEL_QSPI_SYNC_TIMEOUT); + if (ret) + return ret; + + if (aq->caps->octal) { + ret = readl_poll_timeout(aq->regs + QSPI_SR, val, + val & QSPI_SR_RFRSHD, 40, + ATMEL_QSPI_TIMEOUT); + } + + atmel_qspi_write(QSPI_TOUT_TCNTM, aq, QSPI_TOUT); + return ret; +} + +static int atmel_qspi_sama7g5_setup(struct spi_device *spi) +{ + struct atmel_qspi *aq = spi_controller_get_devdata(spi->controller); + + /* The controller can communicate with a single slave. */ + aq->slave_max_speed_hz = spi->max_speed_hz; + + return atmel_qspi_sama7g5_init(aq); +} + static int atmel_qspi_setup(struct spi_device *spi) { struct spi_controller *ctrl = spi->controller; @@ -525,6 +1140,9 @@ static int atmel_qspi_setup(struct spi_device *spi) if (!spi->max_speed_hz) return -EINVAL; + if (aq->caps->has_gclk) + return atmel_qspi_sama7g5_setup(spi); + src_rate = clk_get_rate(aq->pclk); if (!src_rate) return -EINVAL; @@ -610,8 +1228,18 @@ static int atmel_qspi_set_cs_timing(struct spi_device *spi) return 0; } -static void atmel_qspi_init(struct atmel_qspi *aq) +static int atmel_qspi_init(struct atmel_qspi *aq) { + int ret; + + if (aq->caps->has_gclk) { + ret = atmel_qspi_reg_sync(aq); + if (ret) + return ret; + atmel_qspi_write(QSPI_CR_SWRST, aq, QSPI_CR); + return 0; + } + /* Reset the QSPI controller */ atmel_qspi_write(QSPI_CR_SWRST, aq, QSPI_CR); @@ -621,6 +1249,7 @@ static void atmel_qspi_init(struct atmel_qspi *aq) /* Enable the QSPI controller */ atmel_qspi_write(QSPI_CR_QSPIEN, aq, QSPI_CR); + return 0; } static irqreturn_t atmel_qspi_interrupt(int irq, void *dev_id) @@ -642,11 +1271,59 @@ static irqreturn_t atmel_qspi_interrupt(int irq, void *dev_id) return IRQ_HANDLED; } +static int atmel_qspi_dma_init(struct spi_controller *ctrl) +{ + struct atmel_qspi *aq = spi_controller_get_devdata(ctrl); + int ret; + + aq->rx_chan = dma_request_chan(&aq->pdev->dev, "rx"); + if (IS_ERR(aq->rx_chan)) { + aq->rx_chan = NULL; + return dev_err_probe(&aq->pdev->dev, PTR_ERR(aq->rx_chan), + "RX DMA channel is not available\n"); + } + + aq->tx_chan = dma_request_chan(&aq->pdev->dev, "tx"); + if (IS_ERR(aq->tx_chan)) { + ret = dev_err_probe(&aq->pdev->dev, PTR_ERR(aq->tx_chan), + "TX DMA channel is not available\n"); + goto release_rx_chan; + } + + ctrl->dma_rx = aq->rx_chan; + ctrl->dma_tx = aq->tx_chan; + init_completion(&aq->dma_completion); + + dev_info(&aq->pdev->dev, "Using %s (tx) and %s (rx) for DMA transfers\n", + dma_chan_name(aq->tx_chan), dma_chan_name(aq->rx_chan)); + + return 0; + +release_rx_chan: + dma_release_channel(aq->rx_chan); + aq->rx_chan = NULL; + aq->tx_chan = NULL; + return ret; +} + +static void atmel_qspi_dma_release(struct atmel_qspi *aq) +{ + if (aq->rx_chan) + dma_release_channel(aq->rx_chan); + if (aq->tx_chan) + dma_release_channel(aq->tx_chan); +} + static const struct atmel_qspi_ops atmel_qspi_ops = { .set_cfg = atmel_qspi_set_cfg, .transfer = atmel_qspi_transfer, }; +static const struct atmel_qspi_ops atmel_qspi_sama7g5_ops = { + .set_cfg = atmel_qspi_sama7g5_set_cfg, + .transfer = atmel_qspi_sama7g5_transfer, +}; + static int atmel_qspi_probe(struct platform_device *pdev) { struct spi_controller *ctrl; @@ -658,7 +1335,27 @@ static int atmel_qspi_probe(struct platform_device *pdev) if (!ctrl) return -ENOMEM; + aq = spi_controller_get_devdata(ctrl); + + aq->caps = of_device_get_match_data(&pdev->dev); + if (!aq->caps) { + dev_err(&pdev->dev, "Could not retrieve QSPI caps\n"); + return -EINVAL; + } + + init_completion(&aq->cmd_completion); + aq->pdev = pdev; + ctrl->mode_bits = SPI_RX_DUAL | SPI_RX_QUAD | SPI_TX_DUAL | SPI_TX_QUAD; + if (aq->caps->octal) + ctrl->mode_bits |= SPI_RX_OCTAL | SPI_TX_OCTAL; + + if (aq->caps->has_gclk) + aq->ops = &atmel_qspi_sama7g5_ops; + else + aq->ops = &atmel_qspi_ops; + + ctrl->max_speed_hz = aq->caps->max_speed_hz; ctrl->setup = atmel_qspi_setup; ctrl->set_cs_timing = atmel_qspi_set_cs_timing; ctrl->bus_num = -1; @@ -667,12 +1364,6 @@ static int atmel_qspi_probe(struct platform_device *pdev) ctrl->dev.of_node = pdev->dev.of_node; platform_set_drvdata(pdev, ctrl); - aq = spi_controller_get_devdata(ctrl); - - init_completion(&aq->cmd_completion); - aq->pdev = pdev; - aq->ops = &atmel_qspi_ops; - /* Map the registers */ aq->regs = devm_platform_ioremap_resource_byname(pdev, "qspi_base"); if (IS_ERR(aq->regs)) @@ -687,6 +1378,7 @@ static int atmel_qspi_probe(struct platform_device *pdev) "missing AHB memory\n"); aq->mmap_size = resource_size(res); + aq->mmap_phys_base = (dma_addr_t)res->start; /* Get the peripheral clock */ aq->pclk = devm_clk_get(&pdev->dev, "pclk"); @@ -703,13 +1395,6 @@ static int atmel_qspi_probe(struct platform_device *pdev) return dev_err_probe(&pdev->dev, err, "failed to enable the peripheral clock\n"); - aq->caps = of_device_get_match_data(&pdev->dev); - if (!aq->caps) { - dev_err(&pdev->dev, "Could not retrieve QSPI caps\n"); - err = -EINVAL; - goto disable_pclk; - } - if (aq->caps->has_qspick) { /* Get the QSPI system clock */ aq->qspick = devm_clk_get(&pdev->dev, "qspick"); @@ -726,18 +1411,32 @@ static int atmel_qspi_probe(struct platform_device *pdev) "failed to enable the QSPI system clock\n"); goto disable_pclk; } + } else if (aq->caps->has_gclk) { + /* Get the QSPI generic clock */ + aq->gclk = devm_clk_get(&pdev->dev, "gclk"); + if (IS_ERR(aq->gclk)) { + dev_err(&pdev->dev, "missing Generic clock\n"); + err = PTR_ERR(aq->gclk); + goto disable_pclk; + } + } + + if (aq->caps->has_dma) { + err = atmel_qspi_dma_init(ctrl); + if (err == -EPROBE_DEFER) + goto disable_qspick; } /* Request the IRQ */ irq = platform_get_irq(pdev, 0); if (irq < 0) { err = irq; - goto disable_qspick; + goto dma_release; } err = devm_request_irq(&pdev->dev, irq, atmel_qspi_interrupt, 0, dev_name(&pdev->dev), aq); if (err) - goto disable_qspick; + goto dma_release; pm_runtime_set_autosuspend_delay(&pdev->dev, 500); pm_runtime_use_autosuspend(&pdev->dev); @@ -745,7 +1444,9 @@ static int atmel_qspi_probe(struct platform_device *pdev) pm_runtime_enable(&pdev->dev); pm_runtime_get_noresume(&pdev->dev); - atmel_qspi_init(aq); + err = atmel_qspi_init(aq); + if (err) + goto dma_release; err = spi_register_controller(ctrl); if (err) { @@ -753,13 +1454,16 @@ static int atmel_qspi_probe(struct platform_device *pdev) pm_runtime_disable(&pdev->dev); pm_runtime_set_suspended(&pdev->dev); pm_runtime_dont_use_autosuspend(&pdev->dev); - goto disable_qspick; + goto dma_release; } pm_runtime_mark_last_busy(&pdev->dev); pm_runtime_put_autosuspend(&pdev->dev); return 0; +dma_release: + if (aq->caps->has_dma) + atmel_qspi_dma_release(aq); disable_qspick: clk_disable_unprepare(aq->qspick); disable_pclk: @@ -768,6 +1472,44 @@ static int atmel_qspi_probe(struct platform_device *pdev) return err; } +static int atmel_qspi_sama7g5_suspend(struct atmel_qspi *aq) +{ + int ret; + u32 val; + + ret = readl_poll_timeout(aq->regs + QSPI_SR2, val, + !(val & QSPI_SR2_RBUSY) && + (val & QSPI_SR2_HIDLE), 40, + ATMEL_QSPI_SYNC_TIMEOUT); + if (ret) + return ret; + + atmel_qspi_write(QSPI_CR_QSPIDIS, aq, QSPI_CR); + ret = readl_poll_timeout(aq->regs + QSPI_SR2, val, + !(val & QSPI_SR2_QSPIENS), 40, + ATMEL_QSPI_SYNC_TIMEOUT); + if (ret) + return ret; + + clk_disable_unprepare(aq->gclk); + + atmel_qspi_write(QSPI_CR_DLLOFF, aq, QSPI_CR); + ret = readl_poll_timeout(aq->regs + QSPI_SR2, val, + !(val & QSPI_SR2_DLOCK), 40, + ATMEL_QSPI_TIMEOUT); + if (ret) + return ret; + + ret = readl_poll_timeout(aq->regs + QSPI_SR2, val, + !(val & QSPI_SR2_CALBSY), 40, + ATMEL_QSPI_TIMEOUT); + if (ret) + return ret; + + clk_disable_unprepare(aq->pclk); + return 0; +} + static void atmel_qspi_remove(struct platform_device *pdev) { struct spi_controller *ctrl = platform_get_drvdata(pdev); @@ -778,6 +1520,16 @@ static void atmel_qspi_remove(struct platform_device *pdev) ret = pm_runtime_get_sync(&pdev->dev); if (ret >= 0) { + if (aq->caps->has_dma) + atmel_qspi_dma_release(aq); + + if (aq->caps->has_gclk) { + ret = atmel_qspi_sama7g5_suspend(aq); + if (ret) + dev_warn(&pdev->dev, "Failed to de-init device on remove: %d\n", ret); + return; + } + atmel_qspi_write(QSPI_CR_QSPIDIS, aq, QSPI_CR); clk_disable(aq->qspick); clk_disable(aq->pclk); @@ -808,6 +1560,9 @@ static int __maybe_unused atmel_qspi_suspend(struct device *dev) if (ret < 0) return ret; + if (aq->caps->has_gclk) + return atmel_qspi_sama7g5_suspend(aq); + atmel_qspi_write(QSPI_CR_QSPIDIS, aq, QSPI_CR); pm_runtime_mark_last_busy(dev); @@ -835,6 +1590,9 @@ static int __maybe_unused atmel_qspi_resume(struct device *dev) return ret; } + if (aq->caps->has_gclk) + return atmel_qspi_sama7g5_init(aq); + ret = pm_runtime_force_resume(dev); if (ret < 0) return ret; @@ -890,6 +1648,19 @@ static const struct atmel_qspi_caps atmel_sam9x60_qspi_caps = { .has_ricr = true, }; +static const struct atmel_qspi_caps atmel_sama7g5_ospi_caps = { + .max_speed_hz = SAMA7G5_QSPI0_MAX_SPEED_HZ, + .has_gclk = true, + .octal = true, + .has_dma = true, +}; + +static const struct atmel_qspi_caps atmel_sama7g5_qspi_caps = { + .max_speed_hz = SAMA7G5_QSPI1_SDR_MAX_SPEED_HZ, + .has_gclk = true, + .has_dma = true, +}; + static const struct of_device_id atmel_qspi_dt_ids[] = { { .compatible = "atmel,sama5d2-qspi", @@ -899,6 +1670,15 @@ static const struct of_device_id atmel_qspi_dt_ids[] = { .compatible = "microchip,sam9x60-qspi", .data = &atmel_sam9x60_qspi_caps, }, + { + .compatible = "microchip,sama7g5-ospi", + .data = &atmel_sama7g5_ospi_caps, + }, + { + .compatible = "microchip,sama7g5-qspi", + .data = &atmel_sama7g5_qspi_caps, + }, + { /* sentinel */ } }; From 8011709906d0d6ff1ba9589de5a906bf6e430782 Mon Sep 17 00:00:00 2001 From: Jon Lin Date: Sun, 8 Dec 2024 21:03:11 +0800 Subject: [PATCH 16/49] spi: rockchip-sfc: Support pm ops Support system_sleep and runtime_pm ops. Signed-off-by: Jon Lin Link: https://patch.msgid.link/20241208130311.1324024-1-jon.lin@rock-chips.com Signed-off-by: Mark Brown --- drivers/spi/spi-rockchip-sfc.c | 148 ++++++++++++++++++++++++++++----- 1 file changed, 128 insertions(+), 20 deletions(-) diff --git a/drivers/spi/spi-rockchip-sfc.c b/drivers/spi/spi-rockchip-sfc.c index 14f5b9346050..9146b56713b6 100644 --- a/drivers/spi/spi-rockchip-sfc.c +++ b/drivers/spi/spi-rockchip-sfc.c @@ -13,12 +13,14 @@ #include #include #include +#include #include #include #include +#include #include +#include #include -#include #include /* System control */ @@ -150,11 +152,9 @@ /* Data */ #define SFC_DATA 0x108 -/* The controller and documentation reports that it supports up to 4 CS - * devices (0-3), however I have only been able to test a single CS (CS 0) - * due to the configuration of my device. - */ -#define SFC_MAX_CHIPSELECT_NUM 4 +#define SFC_CS1_REG_OFFSET 0x200 + +#define SFC_MAX_CHIPSELECT_NUM 2 /* The SFC can transfer max 16KB - 1 at one time * we set it to 15.5KB here for alignment. @@ -169,12 +169,14 @@ */ #define SFC_MAX_SPEED (150 * 1000 * 1000) +#define ROCKCHIP_AUTOSUSPEND_DELAY 2000 + struct rockchip_sfc { struct device *dev; void __iomem *regbase; struct clk *hclk; struct clk *clk; - u32 frequency; + u32 speed[SFC_MAX_CHIPSELECT_NUM]; /* virtual mapped addr for dma_buffer */ void *buffer; dma_addr_t dma_buffer; @@ -301,6 +303,7 @@ static int rockchip_sfc_xfer_setup(struct rockchip_sfc *sfc, u32 len) { u32 ctrl = 0, cmd = 0; + u8 cs = spi_get_chipselect(mem->spi, 0); /* set CMD */ cmd = op->cmd.opcode; @@ -314,7 +317,8 @@ static int rockchip_sfc_xfer_setup(struct rockchip_sfc *sfc, cmd |= SFC_CMD_ADDR_24BITS << SFC_CMD_ADDR_SHIFT; } else { cmd |= SFC_CMD_ADDR_XBITS << SFC_CMD_ADDR_SHIFT; - writel(op->addr.nbytes * 8 - 1, sfc->regbase + SFC_ABIT); + writel(op->addr.nbytes * 8 - 1, + sfc->regbase + cs * SFC_CS1_REG_OFFSET + SFC_ABIT); } ctrl |= ((op->addr.buswidth >> 1) << SFC_CTRL_ADDR_BITS_SHIFT); @@ -346,7 +350,7 @@ static int rockchip_sfc_xfer_setup(struct rockchip_sfc *sfc, /* set the Controller */ ctrl |= SFC_CTRL_PHASE_SEL_NEGETIVE; - cmd |= spi_get_chipselect(mem->spi, 0) << SFC_CMD_CS_SHIFT; + cmd |= cs << SFC_CMD_CS_SHIFT; dev_dbg(sfc->dev, "sfc addr.nbytes=%x(x%d) dummy.nbytes=%x(x%d)\n", op->addr.nbytes, op->addr.buswidth, @@ -354,7 +358,7 @@ static int rockchip_sfc_xfer_setup(struct rockchip_sfc *sfc, dev_dbg(sfc->dev, "sfc ctrl=%x cmd=%x addr=%llx len=%x\n", ctrl, cmd, op->addr.val, len); - writel(ctrl, sfc->regbase + SFC_CTRL); + writel(ctrl, sfc->regbase + cs * SFC_CS1_REG_OFFSET + SFC_CTRL); writel(cmd, sfc->regbase + SFC_CMD); if (op->addr.nbytes) writel(op->addr.val, sfc->regbase + SFC_ADDR); @@ -500,14 +504,22 @@ static int rockchip_sfc_exec_mem_op(struct spi_mem *mem, const struct spi_mem_op struct rockchip_sfc *sfc = spi_controller_get_devdata(mem->spi->controller); u32 len = op->data.nbytes; int ret; + u8 cs = spi_get_chipselect(mem->spi, 0); - if (unlikely(mem->spi->max_speed_hz != sfc->frequency) && !has_acpi_companion(sfc->dev)) { + ret = pm_runtime_get_sync(sfc->dev); + if (ret < 0) { + pm_runtime_put_noidle(sfc->dev); + return ret; + } + + if (unlikely(mem->spi->max_speed_hz != sfc->speed[cs]) && + !has_acpi_companion(sfc->dev)) { ret = clk_set_rate(sfc->clk, mem->spi->max_speed_hz); if (ret) - return ret; - sfc->frequency = mem->spi->max_speed_hz; + goto out; + sfc->speed[cs] = mem->spi->max_speed_hz; dev_dbg(sfc->dev, "set_freq=%dHz real_freq=%ldHz\n", - sfc->frequency, clk_get_rate(sfc->clk)); + sfc->speed[cs], clk_get_rate(sfc->clk)); } rockchip_sfc_adjust_op_work((struct spi_mem_op *)op); @@ -524,11 +536,17 @@ static int rockchip_sfc_exec_mem_op(struct spi_mem *mem, const struct spi_mem_op if (ret != len) { dev_err(sfc->dev, "xfer data failed ret %d dir %d\n", ret, op->data.dir); - return -EIO; + ret = -EIO; + goto out; } } - return rockchip_sfc_xfer_done(sfc, 100000); + ret = rockchip_sfc_xfer_done(sfc, 100000); +out: + pm_runtime_mark_last_busy(sfc->dev); + pm_runtime_put_autosuspend(sfc->dev); + + return ret; } static int rockchip_sfc_adjust_op_size(struct spi_mem *mem, struct spi_mem_op *op) @@ -570,6 +588,7 @@ static int rockchip_sfc_probe(struct platform_device *pdev) struct spi_controller *host; struct rockchip_sfc *sfc; int ret; + u32 i, val; host = devm_spi_alloc_host(&pdev->dev, sizeof(*sfc)); if (!host) @@ -602,9 +621,12 @@ static int rockchip_sfc_probe(struct platform_device *pdev) "Failed to get sfc ahb clk\n"); if (has_acpi_companion(&pdev->dev)) { - ret = device_property_read_u32(&pdev->dev, "clock-frequency", &sfc->frequency); + ret = device_property_read_u32(&pdev->dev, "clock-frequency", &val); if (ret) - return dev_err_probe(&pdev->dev, ret, "Failed to find clock-frequency\n"); + return dev_err_probe(&pdev->dev, ret, + "Failed to find clock-frequency in ACPI\n"); + for (i = 0; i < SFC_MAX_CHIPSELECT_NUM; i++) + sfc->speed[i] = val; } sfc->use_dma = !of_property_read_bool(sfc->dev->of_node, "rockchip,sfc-no-dma"); @@ -646,6 +668,8 @@ static int rockchip_sfc_probe(struct platform_device *pdev) goto err_irq; } + platform_set_drvdata(pdev, sfc); + ret = rockchip_sfc_init(sfc); if (ret) goto err_irq; @@ -653,12 +677,27 @@ static int rockchip_sfc_probe(struct platform_device *pdev) sfc->max_iosize = rockchip_sfc_get_max_iosize(sfc); sfc->version = rockchip_sfc_get_version(sfc); - ret = spi_register_controller(host); + pm_runtime_set_autosuspend_delay(dev, ROCKCHIP_AUTOSUSPEND_DELAY); + pm_runtime_use_autosuspend(dev); + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + pm_runtime_get_noresume(dev); + + ret = devm_spi_register_controller(dev, host); if (ret) - goto err_irq; + goto err_pm_runtime_free; + + pm_runtime_mark_last_busy(dev); + pm_runtime_put_autosuspend(dev); return 0; +err_pm_runtime_free: + pm_runtime_get_sync(dev); + pm_runtime_put_noidle(dev); + pm_runtime_disable(dev); + pm_runtime_set_suspended(dev); + pm_runtime_dont_use_autosuspend(dev); err_irq: clk_disable_unprepare(sfc->clk); err_clk: @@ -678,6 +717,74 @@ static void rockchip_sfc_remove(struct platform_device *pdev) clk_disable_unprepare(sfc->hclk); } +#ifdef CONFIG_PM +static int rockchip_sfc_runtime_suspend(struct device *dev) +{ + struct rockchip_sfc *sfc = dev_get_drvdata(dev); + + clk_disable_unprepare(sfc->clk); + clk_disable_unprepare(sfc->hclk); + + return 0; +} + +static int rockchip_sfc_runtime_resume(struct device *dev) +{ + struct rockchip_sfc *sfc = dev_get_drvdata(dev); + int ret; + + ret = clk_prepare_enable(sfc->hclk); + if (ret < 0) + return ret; + + ret = clk_prepare_enable(sfc->clk); + if (ret < 0) + clk_disable_unprepare(sfc->hclk); + + return ret; +} +#endif /* CONFIG_PM */ + +#ifdef CONFIG_PM_SLEEP +static int rockchip_sfc_suspend(struct device *dev) +{ + pinctrl_pm_select_sleep_state(dev); + + return pm_runtime_force_suspend(dev); +} + +static int rockchip_sfc_resume(struct device *dev) +{ + struct rockchip_sfc *sfc = dev_get_drvdata(dev); + int ret; + + ret = pm_runtime_force_resume(dev); + if (ret < 0) + return ret; + + pinctrl_pm_select_default_state(dev); + + ret = pm_runtime_get_sync(dev); + if (ret < 0) { + pm_runtime_put_noidle(dev); + return ret; + } + + rockchip_sfc_init(sfc); + + pm_runtime_mark_last_busy(dev); + pm_runtime_put_autosuspend(dev); + + return 0; +} +#endif /* CONFIG_PM_SLEEP */ + +static const struct dev_pm_ops rockchip_sfc_pm_ops = { + SET_RUNTIME_PM_OPS(rockchip_sfc_runtime_suspend, + rockchip_sfc_runtime_resume, NULL) + SET_SYSTEM_SLEEP_PM_OPS(rockchip_sfc_suspend, rockchip_sfc_resume) +}; + static const struct of_device_id rockchip_sfc_dt_ids[] = { { .compatible = "rockchip,sfc"}, { /* sentinel */ } @@ -688,6 +795,7 @@ static struct platform_driver rockchip_sfc_driver = { .driver = { .name = "rockchip-sfc", .of_match_table = rockchip_sfc_dt_ids, + .pm = &rockchip_sfc_pm_ops, }, .probe = rockchip_sfc_probe, .remove = rockchip_sfc_remove, From e490ceff433fbc111404ce040bd9e8f41f12af16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig?= Date: Tue, 17 Dec 2024 12:42:25 +0100 Subject: [PATCH 17/49] spi: spidev: Align ordering of spidev_spi_ids[] and spidev_dt_ids[] MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit There is a 1:1 correspondance between the list of spi device-ids and the devicetree compatibles. The latter is ordered alphabetically by vendor and device. To simplify keeping the two lists in sync, mention the vendor in a comment for the spi device-ids and order alphabetically, too. Signed-off-by: Uwe Kleine-König Link: https://patch.msgid.link/20241217114226.1223724-2-u.kleine-koenig@baylibre.com Signed-off-by: Mark Brown --- drivers/spi/spidev.c | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/drivers/spi/spidev.c b/drivers/spi/spidev.c index 2b87b9ae56c0..58ae4304fdab 100644 --- a/drivers/spi/spidev.c +++ b/drivers/spi/spidev.c @@ -698,20 +698,24 @@ static const struct class spidev_class = { .name = "spidev", }; +/* + * The spi device ids are expected to match the device names of the + * spidev_dt_ids array below. Both arrays are kept in the same ordering. + */ static const struct spi_device_id spidev_spi_ids[] = { - { .name = "bh2228fv" }, - { .name = "dh2228fv" }, - { .name = "jg10309-01" }, - { .name = "ltc2488" }, - { .name = "sx1301" }, - { .name = "bk4" }, - { .name = "bk4-spi" }, - { .name = "dhcom-board" }, - { .name = "m53cpld" }, - { .name = "spi-petra" }, - { .name = "spi-authenta" }, - { .name = "em3581" }, - { .name = "si3210" }, + { .name = /* cisco */ "spi-petra" }, + { .name = /* dh */ "dhcom-board" }, + { .name = /* elgin */ "jg10309-01" }, + { .name = /* lineartechnology */ "ltc2488" }, + { .name = /* lwn */ "bk4" }, + { .name = /* lwn */ "bk4-spi" }, + { .name = /* menlo */ "m53cpld" }, + { .name = /* micron */ "spi-authenta" }, + { .name = /* rohm */ "bh2228fv" }, + { .name = /* rohm */ "dh2228fv" }, + { .name = /* semtech */ "sx1301" }, + { .name = /* silabs */ "em3581" }, + { .name = /* silabs */ "si3210" }, {}, }; MODULE_DEVICE_TABLE(spi, spidev_spi_ids); From 7b4035ebf2af2c2f1450e8c38bf4f41acd3f01bf Mon Sep 17 00:00:00 2001 From: Iker Pedrosa Date: Mon, 16 Dec 2024 10:57:33 +0100 Subject: [PATCH 18/49] spi: dt-bindings: Document CS active-high The current documentation does not clearly explain how to invert the SPI CS signal to make it active-high. This makes it very difficult to understand. This patch adds a simple explanation on how to set the CS line in active-high and adds an example to make it easier for users who need that setup for their SPI peripherals. Link: https://forums.raspberrypi.com/viewtopic.php?t=378222 Signed-off-by: Iker Pedrosa Reviewed-by: Rob Herring (Arm) Link: https://patch.msgid.link/20241216095739.27320-1-ikerpedrosam@gmail.com Signed-off-by: Mark Brown --- .../bindings/spi/spi-controller.yaml | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/Documentation/devicetree/bindings/spi/spi-controller.yaml b/Documentation/devicetree/bindings/spi/spi-controller.yaml index 093150c0cb87..82d051f7bd6e 100644 --- a/Documentation/devicetree/bindings/spi/spi-controller.yaml +++ b/Documentation/devicetree/bindings/spi/spi-controller.yaml @@ -69,6 +69,11 @@ properties: Should be generally avoided and be replaced by spi-cs-high + ACTIVE_HIGH. + The simplest way to obtain an active-high CS signal is to configure the + controller's cs-gpio property with the ACTIVE_HIGH flag and set the + peripheral's spi-cs-high property. See example below for a better + understanding. + fifo-depth: $ref: /schemas/types.yaml#/definitions/uint32 description: @@ -189,3 +194,23 @@ examples: stacked-memories = /bits/ 64 <0x10000000 0x10000000>; }; }; + + - | + #include + + spi@20204000 { + #address-cells = <1>; + #size-cells = <0>; + compatible = "brcm,bcm2835-spi"; + reg = <0x7e204000 0x1000>; + interrupts = <2 22>; + clocks = <&clk_spi>; + cs-gpios = <&gpio 8 GPIO_ACTIVE_HIGH>; + + display@0 { + compatible = "lg,lg4573"; + spi-max-frequency = <1000000>; + reg = <0>; + spi-cs-high; + }; + }; From f663898d047a7a0a04d30732b1405ee007fdd243 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bence=20Cs=C3=B3k=C3=A1s?= Date: Wed, 18 Dec 2024 16:17:54 +0100 Subject: [PATCH 19/49] spi: atmel-quadspi: Factor out switching to Serial Memory Mode to function MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SAMA7G5 support (that was forward-ported from v6.1) re-introduced a bug that was fixed in v6.12, thankfully only in the codepath of the new SoC. But to prevent similar mistakes in the future, we split out the offending code to a function, and use this, fixed version everywhere. To facilitate this, support function `atmel_qspi_update_config()` also had to be moved upwards. For best viewing experience, use `--color-moved-ws="allow-indentation-change" --color-moved`. Fixes: 5af42209a4d2 ("spi: atmel-quadspi: Add support for sama7g5 QSPI") Reported-by: Alexander Dahl Closes: https://lore.kernel.org/linux-spi/20241218-appliance-jaws-90773405977a@thorsis.com/ Signed-off-by: Bence Csókás Link: https://patch.msgid.link/20241218151754.365519-1-csokas.bence@prolan.hu Signed-off-by: Mark Brown --- drivers/spi/atmel-quadspi.c | 101 +++++++++++++++++++----------------- 1 file changed, 53 insertions(+), 48 deletions(-) diff --git a/drivers/spi/atmel-quadspi.c b/drivers/spi/atmel-quadspi.c index 73cf0c3f1477..f46da363574f 100644 --- a/drivers/spi/atmel-quadspi.c +++ b/drivers/spi/atmel-quadspi.c @@ -414,6 +414,28 @@ static void atmel_qspi_write(u32 value, struct atmel_qspi *aq, u32 offset) writel_relaxed(value, aq->regs + offset); } +static int atmel_qspi_reg_sync(struct atmel_qspi *aq) +{ + u32 val; + int ret; + + ret = readl_poll_timeout(aq->regs + QSPI_SR2, val, + !(val & QSPI_SR2_SYNCBSY), 40, + ATMEL_QSPI_SYNC_TIMEOUT); + return ret; +} + +static int atmel_qspi_update_config(struct atmel_qspi *aq) +{ + int ret; + + ret = atmel_qspi_reg_sync(aq); + if (ret) + return ret; + atmel_qspi_write(QSPI_CR_UPDCFG, aq, QSPI_CR); + return atmel_qspi_reg_sync(aq); +} + static inline bool atmel_qspi_is_compatible(const struct spi_mem_op *op, const struct atmel_qspi_mode *mode) { @@ -476,6 +498,25 @@ static bool atmel_qspi_supports_op(struct spi_mem *mem, return true; } +/* + * If the QSPI controller is set in regular SPI mode, set it in + * Serial Memory Mode (SMM). + */ +static int atmel_qspi_set_serial_memory_mode(struct atmel_qspi *aq) +{ + int ret = 0; + + if (!(aq->mr & QSPI_MR_SMM)) { + aq->mr |= QSPI_MR_SMM; + atmel_qspi_write(aq->mr, aq, QSPI_MR); + + if (aq->caps->has_gclk) + ret = atmel_qspi_update_config(aq); + } + + return ret; +} + static int atmel_qspi_set_cfg(struct atmel_qspi *aq, const struct spi_mem_op *op, u32 *offset) { @@ -555,14 +596,9 @@ static int atmel_qspi_set_cfg(struct atmel_qspi *aq, ifr |= QSPI_IFR_TFRTYP_MEM; } - /* - * If the QSPI controller is set in regular SPI mode, set it in - * Serial Memory Mode (SMM). - */ - if (!(aq->mr & QSPI_MR_SMM)) { - aq->mr |= QSPI_MR_SMM; - atmel_qspi_write(aq->mr, aq, QSPI_MR); - } + mode = atmel_qspi_set_serial_memory_mode(aq); + if (mode < 0) + return mode; /* Clear pending interrupts */ (void)atmel_qspi_read(aq, QSPI_SR); @@ -638,28 +674,6 @@ static int atmel_qspi_transfer(struct spi_mem *mem, return atmel_qspi_wait_for_completion(aq, QSPI_SR_CMD_COMPLETED); } -static int atmel_qspi_reg_sync(struct atmel_qspi *aq) -{ - u32 val; - int ret; - - ret = readl_poll_timeout(aq->regs + QSPI_SR2, val, - !(val & QSPI_SR2_SYNCBSY), 40, - ATMEL_QSPI_SYNC_TIMEOUT); - return ret; -} - -static int atmel_qspi_update_config(struct atmel_qspi *aq) -{ - int ret; - - ret = atmel_qspi_reg_sync(aq); - if (ret) - return ret; - atmel_qspi_write(QSPI_CR_UPDCFG, aq, QSPI_CR); - return atmel_qspi_reg_sync(aq); -} - static int atmel_qspi_sama7g5_set_cfg(struct atmel_qspi *aq, const struct spi_mem_op *op, u32 *offset) { @@ -713,18 +727,9 @@ static int atmel_qspi_sama7g5_set_cfg(struct atmel_qspi *aq, ifr |= QSPI_IFR_TFRTYP_MEM; } - /* - * If the QSPI controller is set in regular SPI mode, set it in - * Serial Memory Mode (SMM). - */ - if (aq->mr != QSPI_MR_SMM) { - atmel_qspi_write(QSPI_MR_SMM | QSPI_MR_DQSDLYEN, aq, QSPI_MR); - aq->mr = QSPI_MR_SMM; - - ret = atmel_qspi_update_config(aq); - if (ret) - return ret; - } + ret = atmel_qspi_set_serial_memory_mode(aq); + if (ret < 0) + return ret; /* Clear pending interrupts */ (void)atmel_qspi_read(aq, QSPI_SR); @@ -1092,10 +1097,9 @@ static int atmel_qspi_sama7g5_init(struct atmel_qspi *aq) } /* Set the QSPI controller by default in Serial Memory Mode */ - atmel_qspi_write(QSPI_MR_SMM | QSPI_MR_DQSDLYEN, aq, QSPI_MR); - aq->mr = QSPI_MR_SMM; - ret = atmel_qspi_update_config(aq); - if (ret) + aq->mr |= QSPI_MR_DQSDLYEN; + ret = atmel_qspi_set_serial_memory_mode(aq); + if (ret < 0) return ret; /* Enable the QSPI controller. */ @@ -1244,8 +1248,9 @@ static int atmel_qspi_init(struct atmel_qspi *aq) atmel_qspi_write(QSPI_CR_SWRST, aq, QSPI_CR); /* Set the QSPI controller by default in Serial Memory Mode */ - aq->mr |= QSPI_MR_SMM; - atmel_qspi_write(aq->mr, aq, QSPI_MR); + ret = atmel_qspi_set_serial_memory_mode(aq); + if (ret < 0) + return ret; /* Enable the QSPI controller */ atmel_qspi_write(QSPI_CR_QSPIEN, aq, QSPI_CR); From b69386fcbc6066fb4885667743ab4d4967d561b8 Mon Sep 17 00:00:00 2001 From: Jon Lin Date: Thu, 19 Dec 2024 09:05:57 +0800 Subject: [PATCH 20/49] spi: rockchip-sfc: Using normal memory for dma Nornal memory CPU copy with cache invalidate is more efficient than uncache memory copy. Signed-off-by: Jon Lin Link: https://patch.msgid.link/20241219010557.333327-1-jon.lin@rock-chips.com Signed-off-by: Mark Brown --- drivers/spi/spi-rockchip-sfc.c | 47 ++++++++++++++++++---------------- 1 file changed, 25 insertions(+), 22 deletions(-) diff --git a/drivers/spi/spi-rockchip-sfc.c b/drivers/spi/spi-rockchip-sfc.c index 9146b56713b6..bb453479e5ae 100644 --- a/drivers/spi/spi-rockchip-sfc.c +++ b/drivers/spi/spi-rockchip-sfc.c @@ -156,10 +156,9 @@ #define SFC_MAX_CHIPSELECT_NUM 2 -/* The SFC can transfer max 16KB - 1 at one time - * we set it to 15.5KB here for alignment. - */ #define SFC_MAX_IOSIZE_VER3 (512 * 31) +/* Although up to 4GB, 64KB is enough with less mem reserved */ +#define SFC_MAX_IOSIZE_VER4 (0x10000U) /* DMA is only enabled for large data transmission */ #define SFC_DMA_TRANS_THRETHOLD (0x40) @@ -456,8 +455,10 @@ static int rockchip_sfc_xfer_data_dma(struct rockchip_sfc *sfc, dev_dbg(sfc->dev, "sfc xfer_dma len=%x\n", len); - if (op->data.dir == SPI_MEM_DATA_OUT) + if (op->data.dir == SPI_MEM_DATA_OUT) { memcpy(sfc->buffer, op->data.buf.out, len); + dma_sync_single_for_device(sfc->dev, sfc->dma_buffer, len, DMA_TO_DEVICE); + } ret = rockchip_sfc_fifo_transfer_dma(sfc, sfc->dma_buffer, len); if (!wait_for_completion_timeout(&sfc->cp, msecs_to_jiffies(2000))) { @@ -465,8 +466,11 @@ static int rockchip_sfc_xfer_data_dma(struct rockchip_sfc *sfc, ret = -ETIMEDOUT; } rockchip_sfc_irq_mask(sfc, SFC_IMR_DMA); - if (op->data.dir == SPI_MEM_DATA_IN) + + if (op->data.dir == SPI_MEM_DATA_IN) { + dma_sync_single_for_cpu(sfc->dev, sfc->dma_buffer, len, DMA_FROM_DEVICE); memcpy(op->data.buf.in, sfc->buffer, len); + } return ret; } @@ -631,19 +635,6 @@ static int rockchip_sfc_probe(struct platform_device *pdev) sfc->use_dma = !of_property_read_bool(sfc->dev->of_node, "rockchip,sfc-no-dma"); - if (sfc->use_dma) { - ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(32)); - if (ret) { - dev_warn(dev, "Unable to set dma mask\n"); - return ret; - } - - sfc->buffer = dmam_alloc_coherent(dev, SFC_MAX_IOSIZE_VER3, - &sfc->dma_buffer, GFP_KERNEL); - if (!sfc->buffer) - return -ENOMEM; - } - ret = clk_prepare_enable(sfc->hclk); if (ret) { dev_err(&pdev->dev, "Failed to enable ahb clk\n"); @@ -674,8 +665,8 @@ static int rockchip_sfc_probe(struct platform_device *pdev) if (ret) goto err_irq; - sfc->max_iosize = rockchip_sfc_get_max_iosize(sfc); sfc->version = rockchip_sfc_get_version(sfc); + sfc->max_iosize = rockchip_sfc_get_max_iosize(sfc); pm_runtime_set_autosuspend_delay(dev, ROCKCHIP_AUTOSUSPEND_DELAY); pm_runtime_use_autosuspend(dev); @@ -683,16 +674,27 @@ static int rockchip_sfc_probe(struct platform_device *pdev) pm_runtime_enable(dev); pm_runtime_get_noresume(dev); + if (sfc->use_dma) { + sfc->buffer = (u8 *)__get_free_pages(GFP_KERNEL | GFP_DMA32, + get_order(sfc->max_iosize)); + if (!sfc->buffer) { + ret = -ENOMEM; + goto err_dma; + } + sfc->dma_buffer = virt_to_phys(sfc->buffer); + } + ret = devm_spi_register_controller(dev, host); if (ret) - goto err_pm_runtime_free; + goto err_register; pm_runtime_mark_last_busy(dev); pm_runtime_put_autosuspend(dev); return 0; - -err_pm_runtime_free: +err_register: + free_pages((unsigned long)sfc->buffer, get_order(sfc->max_iosize)); +err_dma: pm_runtime_get_sync(dev); pm_runtime_put_noidle(dev); pm_runtime_disable(dev); @@ -712,6 +714,7 @@ static void rockchip_sfc_remove(struct platform_device *pdev) struct rockchip_sfc *sfc = platform_get_drvdata(pdev); spi_unregister_controller(host); + free_pages((unsigned long)sfc->buffer, get_order(sfc->max_iosize)); clk_disable_unprepare(sfc->clk); clk_disable_unprepare(sfc->hclk); From be92ab2de0ee1a13291c3b47b2d7eb24d80c0a2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bence=20Cs=C3=B3k=C3=A1s?= Date: Thu, 19 Dec 2024 10:12:58 +0100 Subject: [PATCH 21/49] spi: atmel-qspi: Memory barriers after memory-mapped I/O MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The QSPI peripheral control and status registers are accessible via the SoC's APB bus, whereas MMIO transactions' data travels on the AHB bus. Microchip documentation and even sample code from Atmel emphasises the need for a memory barrier before the first MMIO transaction to the AHB-connected QSPI, and before the last write to its registers via APB. This is achieved by the following lines in `atmel_qspi_transfer()`: /* Dummy read of QSPI_IFR to synchronize APB and AHB accesses */ (void)atmel_qspi_read(aq, QSPI_IFR); However, the current documentation makes no mention to synchronization requirements in the other direction, i.e. after the last data written via AHB, and before the first register access on APB. In our case, we were facing an issue where the QSPI peripheral would cease to send any new CSR (nCS Rise) interrupts, leading to a timeout in `atmel_qspi_wait_for_completion()` and ultimately this panic in higher levels: ubi0 error: ubi_io_write: error -110 while writing 63108 bytes to PEB 491:128, written 63104 bytes After months of extensive research of the codebase, fiddling around the debugger with kgdb, and back-and-forth with Microchip, we came to the conclusion that the issue is probably that the peripheral is still busy receiving on AHB when the LASTXFER bit is written to its Control Register on APB, therefore this write gets lost, and the peripheral still thinks there is more data to come in the MMIO transfer. This was first formulated when we noticed that doubling the write() of QSPI_CR_LASTXFER seemed to solve the problem. Ultimately, the solution is to introduce memory barriers after the AHB-mapped MMIO transfers, to ensure ordering. Fixes: d5433def3153 ("mtd: spi-nor: atmel-quadspi: Add spi-mem support to atmel-quadspi") Cc: Hari.PrasathGE@microchip.com Cc: Mahesh.Abotula@microchip.com Cc: Marco.Cardellini@microchip.com Cc: stable@vger.kernel.org # c0a0203cf579: ("spi: atmel-quadspi: Create `atmel_qspi_ops`"...) Cc: stable@vger.kernel.org # 6.x.y Signed-off-by: Bence Csókás Link: https://patch.msgid.link/20241219091258.395187-1-csokas.bence@prolan.hu Signed-off-by: Mark Brown --- drivers/spi/atmel-quadspi.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/drivers/spi/atmel-quadspi.c b/drivers/spi/atmel-quadspi.c index f46da363574f..8fdc9d27a95e 100644 --- a/drivers/spi/atmel-quadspi.c +++ b/drivers/spi/atmel-quadspi.c @@ -661,13 +661,20 @@ static int atmel_qspi_transfer(struct spi_mem *mem, (void)atmel_qspi_read(aq, QSPI_IFR); /* Send/Receive data */ - if (op->data.dir == SPI_MEM_DATA_IN) + if (op->data.dir == SPI_MEM_DATA_IN) { memcpy_fromio(op->data.buf.in, aq->mem + offset, op->data.nbytes); - else + + /* Synchronize AHB and APB accesses again */ + rmb(); + } else { memcpy_toio(aq->mem + offset, op->data.buf.out, op->data.nbytes); + /* Synchronize AHB and APB accesses again */ + wmb(); + } + /* Release the chip-select */ atmel_qspi_write(QSPI_CR_LASTXFER, aq, QSPI_CR); From 1e293574c6f5d5d87acd7d64415eae055d0672e7 Mon Sep 17 00:00:00 2001 From: Santhosh Kumar K Date: Thu, 2 Jan 2025 17:35:44 +0530 Subject: [PATCH 22/49] spi: cadence-quadspi: Enable SPI_TX_QUAD Enable the SPI_TX_QUAD mode bit in the host->mode_bits to support data transmission over four lines to improve the performance. Tested the functionality on AM62Lx EVM (W25N01JW) in 1S-4S-4S mode. Signed-off-by: Santhosh Kumar K Link: https://patch.msgid.link/20250102120544.1407152-1-s-k6@ti.com Signed-off-by: Mark Brown --- drivers/spi/spi-cadence-quadspi.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/spi/spi-cadence-quadspi.c b/drivers/spi/spi-cadence-quadspi.c index 47477f2d9a25..594408d53400 100644 --- a/drivers/spi/spi-cadence-quadspi.c +++ b/drivers/spi/spi-cadence-quadspi.c @@ -49,6 +49,7 @@ static_assert(CQSPI_MAX_CHIPSELECT <= SPI_CS_CNT_MAX); /* Capabilities */ #define CQSPI_SUPPORTS_OCTAL BIT(0) +#define CQSPI_SUPPORTS_QUAD BIT(1) #define CQSPI_OP_WIDTH(part) ((part).nbytes ? ilog2((part).buswidth) : 0) @@ -1888,6 +1889,8 @@ static int cqspi_probe(struct platform_device *pdev) cqspi->master_ref_clk_hz); if (ddata->hwcaps_mask & CQSPI_SUPPORTS_OCTAL) host->mode_bits |= SPI_RX_OCTAL | SPI_TX_OCTAL; + if (ddata->hwcaps_mask & CQSPI_SUPPORTS_QUAD) + host->mode_bits |= SPI_TX_QUAD; if (!(ddata->quirks & CQSPI_DISABLE_DAC_MODE)) { cqspi->use_direct_mode = true; cqspi->use_direct_mode_wr = true; @@ -2062,7 +2065,7 @@ static const struct cqspi_driver_platdata k2g_qspi = { }; static const struct cqspi_driver_platdata am654_ospi = { - .hwcaps_mask = CQSPI_SUPPORTS_OCTAL, + .hwcaps_mask = CQSPI_SUPPORTS_OCTAL | CQSPI_SUPPORTS_QUAD, .quirks = CQSPI_NEEDS_WR_DELAY, }; From af103eb7d7d881cf6ff7414242bce2d8e394cc32 Mon Sep 17 00:00:00 2001 From: Jon Lin Date: Thu, 19 Dec 2024 22:22:16 +0800 Subject: [PATCH 23/49] spi: rockchip-sfc: Support sclk_x2 version SFC after version 8 supports dtr mode, so the IO is the binary output of the controller clock. Signed-off-by: Jon Lin Link: https://patch.msgid.link/20241219142216.2123065-1-jon.lin@rock-chips.com Signed-off-by: Mark Brown --- drivers/spi/spi-rockchip-sfc.c | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/drivers/spi/spi-rockchip-sfc.c b/drivers/spi/spi-rockchip-sfc.c index bb453479e5ae..956363859b91 100644 --- a/drivers/spi/spi-rockchip-sfc.c +++ b/drivers/spi/spi-rockchip-sfc.c @@ -112,6 +112,7 @@ #define SFC_VER_3 0x3 #define SFC_VER_4 0x4 #define SFC_VER_5 0x5 +#define SFC_VER_8 0x8 /* Delay line controller register */ #define SFC_DLL_CTRL0 0x3C @@ -216,6 +217,22 @@ static u32 rockchip_sfc_get_max_iosize(struct rockchip_sfc *sfc) return SFC_MAX_IOSIZE_VER3; } +static int rockchip_sfc_clk_set_rate(struct rockchip_sfc *sfc, unsigned long speed) +{ + if (sfc->version >= SFC_VER_8) + return clk_set_rate(sfc->clk, speed * 2); + else + return clk_set_rate(sfc->clk, speed); +} + +static unsigned long rockchip_sfc_clk_get_rate(struct rockchip_sfc *sfc) +{ + if (sfc->version >= SFC_VER_8) + return clk_get_rate(sfc->clk) / 2; + else + return clk_get_rate(sfc->clk); +} + static void rockchip_sfc_irq_unmask(struct rockchip_sfc *sfc, u32 mask) { u32 reg; @@ -518,12 +535,12 @@ static int rockchip_sfc_exec_mem_op(struct spi_mem *mem, const struct spi_mem_op if (unlikely(mem->spi->max_speed_hz != sfc->speed[cs]) && !has_acpi_companion(sfc->dev)) { - ret = clk_set_rate(sfc->clk, mem->spi->max_speed_hz); + ret = rockchip_sfc_clk_set_rate(sfc, mem->spi->max_speed_hz); if (ret) goto out; sfc->speed[cs] = mem->spi->max_speed_hz; dev_dbg(sfc->dev, "set_freq=%dHz real_freq=%ldHz\n", - sfc->speed[cs], clk_get_rate(sfc->clk)); + sfc->speed[cs], rockchip_sfc_clk_get_rate(sfc)); } rockchip_sfc_adjust_op_work((struct spi_mem_op *)op); From a38509fd5cdc125ef54562760a05c68ebd4812bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bence=20Cs=C3=B3k=C3=A1s?= Date: Thu, 19 Dec 2024 15:28:51 +0100 Subject: [PATCH 24/49] spi: atmel-quadspi: Use devm_ clock management MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Clean up error handling by using the new devm_ clock handling functions. This should make it easier to add new code, as we can eliminate the "goto ladder" in probe(). Signed-off-by: Bence Csókás Link: https://patch.msgid.link/20241219142851.430959-1-csokas.bence@prolan.hu Signed-off-by: Mark Brown --- drivers/spi/atmel-quadspi.c | 42 ++++++++++--------------------------- 1 file changed, 11 insertions(+), 31 deletions(-) diff --git a/drivers/spi/atmel-quadspi.c b/drivers/spi/atmel-quadspi.c index 8fdc9d27a95e..093eb4fd7dd2 100644 --- a/drivers/spi/atmel-quadspi.c +++ b/drivers/spi/atmel-quadspi.c @@ -1393,50 +1393,37 @@ static int atmel_qspi_probe(struct platform_device *pdev) aq->mmap_phys_base = (dma_addr_t)res->start; /* Get the peripheral clock */ - aq->pclk = devm_clk_get(&pdev->dev, "pclk"); + aq->pclk = devm_clk_get_enabled(&pdev->dev, "pclk"); if (IS_ERR(aq->pclk)) - aq->pclk = devm_clk_get(&pdev->dev, NULL); + aq->pclk = devm_clk_get_enabled(&pdev->dev, NULL); if (IS_ERR(aq->pclk)) return dev_err_probe(&pdev->dev, PTR_ERR(aq->pclk), "missing peripheral clock\n"); - /* Enable the peripheral clock */ - err = clk_prepare_enable(aq->pclk); - if (err) - return dev_err_probe(&pdev->dev, err, - "failed to enable the peripheral clock\n"); - if (aq->caps->has_qspick) { /* Get the QSPI system clock */ - aq->qspick = devm_clk_get(&pdev->dev, "qspick"); + aq->qspick = devm_clk_get_enabled(&pdev->dev, "qspick"); if (IS_ERR(aq->qspick)) { dev_err(&pdev->dev, "missing system clock\n"); err = PTR_ERR(aq->qspick); - goto disable_pclk; + return err; } - /* Enable the QSPI system clock */ - err = clk_prepare_enable(aq->qspick); - if (err) { - dev_err(&pdev->dev, - "failed to enable the QSPI system clock\n"); - goto disable_pclk; - } } else if (aq->caps->has_gclk) { /* Get the QSPI generic clock */ aq->gclk = devm_clk_get(&pdev->dev, "gclk"); if (IS_ERR(aq->gclk)) { dev_err(&pdev->dev, "missing Generic clock\n"); err = PTR_ERR(aq->gclk); - goto disable_pclk; + return err; } } if (aq->caps->has_dma) { err = atmel_qspi_dma_init(ctrl); if (err == -EPROBE_DEFER) - goto disable_qspick; + return err; } /* Request the IRQ */ @@ -1476,10 +1463,6 @@ static int atmel_qspi_probe(struct platform_device *pdev) dma_release: if (aq->caps->has_dma) atmel_qspi_dma_release(aq); -disable_qspick: - clk_disable_unprepare(aq->qspick); -disable_pclk: - clk_disable_unprepare(aq->pclk); return err; } @@ -1518,7 +1501,6 @@ static int atmel_qspi_sama7g5_suspend(struct atmel_qspi *aq) if (ret) return ret; - clk_disable_unprepare(aq->pclk); return 0; } @@ -1543,8 +1525,6 @@ static void atmel_qspi_remove(struct platform_device *pdev) } atmel_qspi_write(QSPI_CR_QSPIDIS, aq, QSPI_CR); - clk_disable(aq->qspick); - clk_disable(aq->pclk); } else { /* * atmel_qspi_runtime_{suspend,resume} just disable and enable @@ -1554,9 +1534,6 @@ static void atmel_qspi_remove(struct platform_device *pdev) dev_warn(&pdev->dev, "Failed to resume device on remove\n"); } - clk_unprepare(aq->qspick); - clk_unprepare(aq->pclk); - pm_runtime_disable(&pdev->dev); pm_runtime_dont_use_autosuspend(&pdev->dev); pm_runtime_put_noidle(&pdev->dev); @@ -1572,8 +1549,11 @@ static int __maybe_unused atmel_qspi_suspend(struct device *dev) if (ret < 0) return ret; - if (aq->caps->has_gclk) - return atmel_qspi_sama7g5_suspend(aq); + if (aq->caps->has_gclk) { + ret = atmel_qspi_sama7g5_suspend(aq); + clk_disable_unprepare(aq->pclk); + return ret; + } atmel_qspi_write(QSPI_CR_QSPIDIS, aq, QSPI_CR); From 636ee5781d259258dc9425a5552be1ffa458633c Mon Sep 17 00:00:00 2001 From: Christophe Leroy Date: Thu, 9 Jan 2025 07:51:45 +0100 Subject: [PATCH 25/49] spi: fsl-spi: Remove display of virtual address The following appears in kernel log at boot: fsl_spi b01004c0.spi: at 0x(ptrval) (irq = 51), QE mode This is useless, so remove the display of that virtual address and display the MMIO address instead, just like serial core does. Signed-off-by: Christophe Leroy Link: https://patch.msgid.link/8a37a960ff084dfdb9233849c00714e9317ae6a5.1736405336.git.christophe.leroy@csgroup.eu Signed-off-by: Mark Brown --- drivers/spi/spi-fsl-spi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/spi/spi-fsl-spi.c b/drivers/spi/spi-fsl-spi.c index 856a4a9def66..2f2082652a1a 100644 --- a/drivers/spi/spi-fsl-spi.c +++ b/drivers/spi/spi-fsl-spi.c @@ -618,7 +618,7 @@ static struct spi_controller *fsl_spi_probe(struct device *dev, if (ret < 0) goto err_probe; - dev_info(dev, "at 0x%p (irq = %d), %s mode\n", reg_base, + dev_info(dev, "at MMIO %pa (irq = %d), %s mode\n", &mem->start, mpc8xxx_spi->irq, mpc8xxx_spi_strmode(mpc8xxx_spi->flags)); return host; From 5e56618e1593a9eb9d72dc9433ac7a02a6c48c8f Mon Sep 17 00:00:00 2001 From: Alexander Dahl Date: Thu, 9 Jan 2025 10:48:43 +0100 Subject: [PATCH 26/49] spi: atmel-quadspi: Update to current device naming terminology For v6.9 the spi subsystem changed the terminology to host and target devices, see commit 99769a52464d ("spi: Update the "master/slave" terminology in documentation") for reference. Support for SAMA7G5 was forward ported recently from an old vendor branch before that terminology change, so naming for the new struct member is adapted to follow the current scheme. Signed-off-by: Alexander Dahl Link: https://patch.msgid.link/20250109094843.36014-1-ada@thorsis.com Signed-off-by: Mark Brown --- drivers/spi/atmel-quadspi.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/spi/atmel-quadspi.c b/drivers/spi/atmel-quadspi.c index 093eb4fd7dd2..abdc49d9d940 100644 --- a/drivers/spi/atmel-quadspi.c +++ b/drivers/spi/atmel-quadspi.c @@ -280,7 +280,7 @@ struct atmel_qspi { u32 irq_mask; u32 mr; u32 scr; - u32 slave_max_speed_hz; + u32 target_max_speed_hz; struct completion cmd_completion; struct completion dma_completion; dma_addr_t mmap_phys_base; @@ -1041,7 +1041,7 @@ static int atmel_qspi_set_pad_calibration(struct atmel_qspi *aq) /* Refresh analogic blocks every 1 ms.*/ atmel_qspi_write(FIELD_PREP(QSPI_REFRESH_DELAY_COUNTER, - aq->slave_max_speed_hz / 1000), + aq->target_max_speed_hz / 1000), aq, QSPI_REFRESH); return ret; @@ -1064,12 +1064,12 @@ static int atmel_qspi_set_gclk(struct atmel_qspi *aq) return ret; } - if (aq->slave_max_speed_hz > QSPI_DLLCFG_THRESHOLD_FREQ) + if (aq->target_max_speed_hz > QSPI_DLLCFG_THRESHOLD_FREQ) atmel_qspi_write(QSPI_DLLCFG_RANGE, aq, QSPI_DLLCFG); else atmel_qspi_write(0, aq, QSPI_DLLCFG); - ret = clk_set_rate(aq->gclk, aq->slave_max_speed_hz); + ret = clk_set_rate(aq->gclk, aq->target_max_speed_hz); if (ret) { dev_err(&aq->pdev->dev, "Failed to set generic clock rate.\n"); return ret; @@ -1131,8 +1131,8 @@ static int atmel_qspi_sama7g5_setup(struct spi_device *spi) { struct atmel_qspi *aq = spi_controller_get_devdata(spi->controller); - /* The controller can communicate with a single slave. */ - aq->slave_max_speed_hz = spi->max_speed_hz; + /* The controller can communicate with a single peripheral device (target). */ + aq->target_max_speed_hz = spi->max_speed_hz; return atmel_qspi_sama7g5_init(aq); } From 0fefeade90e74bc8f40ab0e460f483565c492e28 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Tue, 24 Dec 2024 18:05:46 +0100 Subject: [PATCH 27/49] spi: spi-mem: Extend spi-mem operations with a per-operation maximum frequency In the spi subsystem, the bus frequency is derived as follows: - the controller may expose a minimum and maximum operating frequency - the hardware description, through the spi peripheral properties, advise what is the maximum acceptable frequency from a device/wiring point of view. Transfers must be observed at a frequency which fits both (so in practice, the lowest maximum). Actually, this second point mixes two information and already takes the lowest frequency among: - what the spi device is capable of (what is written in the component datasheet) - what the wiring allows (electromagnetic sensibility, crossovers, terminations, antenna effect, etc). This logic works until spi devices are no longer capable of sustaining their highest frequency regardless of the operation. Spi memories are typically subject to such variation. Some devices are capable of spitting their internally stored data (essentially in read mode) at a very fast rate, typically up to 166MHz on Winbond SPI-NAND chips, using "fast" commands. However, some of the low-end operations, such as regular page read-from-cache commands, are more limited and can only be executed at 54MHz at most. This is currently a problem in the SPI-NAND subsystem. Another situation, even if not yet supported, will be with DTR commands, when the data is latched on both edges of the clock. The same chips as mentioned previously are in this case limited to 80MHz. Yet another example might be continuous reads, which, under certain circumstances, can also run at most at 104 or 120MHz. As a matter of fact, the "one frequency per chip" policy is outdated and more fine grain configuration is needed: we need to allow per-operation frequency limitations. So far, all datasheets I encountered advertise a maximum default frequency, which need to be lowered for certain specific operations. So based on the current infrastructure, we can still expect firmware (device trees in general) to continued advertising the same maximum speed which is a mix between the PCB limitations and the chip maximum capability, and expect per-operation lower frequencies when this is relevant. Add a `struct spi_mem_op` member to carry this information. Not providing this field explicitly from upper layers means that there is no further constraint and the default spi device maximum speed will be carried instead. The SPI_MEM_OP() macro is also expanded with an optional frequency argument, because virtually all operations can be subject to such a limitation, and this will allow for a smooth and discrete transition. For controller drivers which do not implement the spi-mem interface, the per-transfer speed is also set acordingly to a lower (than the maximum default) speed when relevant. Acked-by: Pratyush Yadav Signed-off-by: Miquel Raynal Link: https://patch.msgid.link/20241224-winbond-6-11-rc1-quad-support-v2-1-ad218dbc406f@bootlin.com Signed-off-by: Mark Brown --- drivers/mtd/nand/spi/core.c | 2 ++ drivers/spi/spi-mem.c | 28 ++++++++++++++++++++++++++++ include/linux/spi/spi-mem.h | 12 +++++++++++- 3 files changed, 41 insertions(+), 1 deletion(-) diff --git a/drivers/mtd/nand/spi/core.c b/drivers/mtd/nand/spi/core.c index b1df7f627161..94f33c8be031 100644 --- a/drivers/mtd/nand/spi/core.c +++ b/drivers/mtd/nand/spi/core.c @@ -1214,6 +1214,8 @@ spinand_select_op_variant(struct spinand_device *spinand, if (ret) break; + spi_mem_adjust_op_freq(spinand->spimem, &op); + if (!spi_mem_supports_op(spinand->spimem, &op)) break; diff --git a/drivers/spi/spi-mem.c b/drivers/spi/spi-mem.c index abc6792e738c..12299ce89a1c 100644 --- a/drivers/spi/spi-mem.c +++ b/drivers/spi/spi-mem.c @@ -187,6 +187,10 @@ bool spi_mem_default_supports_op(struct spi_mem *mem, return false; } + if (op->max_freq && mem->spi->controller->min_speed_hz && + op->max_freq < mem->spi->controller->min_speed_hz) + return false; + return spi_mem_check_buswidth(mem, op); } EXPORT_SYMBOL_GPL(spi_mem_default_supports_op); @@ -364,6 +368,9 @@ int spi_mem_exec_op(struct spi_mem *mem, const struct spi_mem_op *op) u8 *tmpbuf; int ret; + /* Make sure the operation frequency is correct before going futher */ + spi_mem_adjust_op_freq(mem, (struct spi_mem_op *)op); + ret = spi_mem_check_op(op); if (ret) return ret; @@ -410,6 +417,7 @@ int spi_mem_exec_op(struct spi_mem *mem, const struct spi_mem_op *op) xfers[xferpos].tx_buf = tmpbuf; xfers[xferpos].len = op->cmd.nbytes; xfers[xferpos].tx_nbits = op->cmd.buswidth; + xfers[xferpos].speed_hz = op->max_freq; spi_message_add_tail(&xfers[xferpos], &msg); xferpos++; totalxferlen++; @@ -424,6 +432,7 @@ int spi_mem_exec_op(struct spi_mem *mem, const struct spi_mem_op *op) xfers[xferpos].tx_buf = tmpbuf + 1; xfers[xferpos].len = op->addr.nbytes; xfers[xferpos].tx_nbits = op->addr.buswidth; + xfers[xferpos].speed_hz = op->max_freq; spi_message_add_tail(&xfers[xferpos], &msg); xferpos++; totalxferlen += op->addr.nbytes; @@ -435,6 +444,7 @@ int spi_mem_exec_op(struct spi_mem *mem, const struct spi_mem_op *op) xfers[xferpos].len = op->dummy.nbytes; xfers[xferpos].tx_nbits = op->dummy.buswidth; xfers[xferpos].dummy_data = 1; + xfers[xferpos].speed_hz = op->max_freq; spi_message_add_tail(&xfers[xferpos], &msg); xferpos++; totalxferlen += op->dummy.nbytes; @@ -450,6 +460,7 @@ int spi_mem_exec_op(struct spi_mem *mem, const struct spi_mem_op *op) } xfers[xferpos].len = op->data.nbytes; + xfers[xferpos].speed_hz = op->max_freq; spi_message_add_tail(&xfers[xferpos], &msg); xferpos++; totalxferlen += op->data.nbytes; @@ -528,6 +539,23 @@ int spi_mem_adjust_op_size(struct spi_mem *mem, struct spi_mem_op *op) } EXPORT_SYMBOL_GPL(spi_mem_adjust_op_size); +/** + * spi_mem_adjust_op_freq() - Adjust the frequency of a SPI mem operation to + * match controller, PCB and chip limitations + * @mem: the SPI memory + * @op: the operation to adjust + * + * Some chips have per-op frequency limitations and must adapt the maximum + * speed. This function allows SPI mem drivers to set @op->max_freq to the + * maximum supported value. + */ +void spi_mem_adjust_op_freq(struct spi_mem *mem, struct spi_mem_op *op) +{ + if (!op->max_freq || op->max_freq > mem->spi->max_speed_hz) + op->max_freq = mem->spi->max_speed_hz; +} +EXPORT_SYMBOL_GPL(spi_mem_adjust_op_freq); + static ssize_t spi_mem_no_dirmap_read(struct spi_mem_dirmap_desc *desc, u64 offs, size_t len, void *buf) { diff --git a/include/linux/spi/spi-mem.h b/include/linux/spi/spi-mem.h index c46d2b8029be..84ec52498792 100644 --- a/include/linux/spi/spi-mem.h +++ b/include/linux/spi/spi-mem.h @@ -68,6 +68,9 @@ enum spi_mem_data_dir { SPI_MEM_DATA_OUT, }; +#define SPI_MEM_OP_MAX_FREQ(__freq) \ + .max_freq = __freq + /** * struct spi_mem_op - describes a SPI memory operation * @cmd.nbytes: number of opcode bytes (only 1 or 2 are valid). The opcode is @@ -97,6 +100,9 @@ enum spi_mem_data_dir { * operation does not involve transferring data * @data.buf.in: input buffer (must be DMA-able) * @data.buf.out: output buffer (must be DMA-able) + * @max_freq: frequency limitation wrt this operation. 0 means there is no + * specific constraint and the highest achievable frequency can be + * attempted. */ struct spi_mem_op { struct { @@ -135,14 +141,17 @@ struct spi_mem_op { const void *out; } buf; } data; + + unsigned int max_freq; }; -#define SPI_MEM_OP(__cmd, __addr, __dummy, __data) \ +#define SPI_MEM_OP(__cmd, __addr, __dummy, __data, ...) \ { \ .cmd = __cmd, \ .addr = __addr, \ .dummy = __dummy, \ .data = __data, \ + __VA_ARGS__ \ } /** @@ -371,6 +380,7 @@ bool spi_mem_default_supports_op(struct spi_mem *mem, #endif /* CONFIG_SPI_MEM */ int spi_mem_adjust_op_size(struct spi_mem *mem, struct spi_mem_op *op); +void spi_mem_adjust_op_freq(struct spi_mem *mem, struct spi_mem_op *op); bool spi_mem_supports_op(struct spi_mem *mem, const struct spi_mem_op *op); From 1248c9b8d54120950fda10fbeb98fb8932b4d45c Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Tue, 24 Dec 2024 18:05:47 +0100 Subject: [PATCH 28/49] spi: spi-mem: Add a new controller capability There are spi devices with multiple frequency limitations depending on the invoked command. We probably do not want to afford running at the lowest supported frequency all the time, so if we want to get the most of our hardware, we need to allow per-operation frequency limitations. Among all the SPI memory controllers, I believe all are capable of changing the spi frequency on the fly. Some of the drivers do not make any frequency setup though. And some others will derive a per chip prescaler value which will be used forever. Actually changing the frequency on the fly is something new in Linux, so we need to carefully flag the drivers which do and do not support it. A controller capability is created for that, and the presence for this capability will always be checked before accepting such pattern. Signed-off-by: Miquel Raynal Reviewed-by: Tudor Ambarus Link: https://patch.msgid.link/20241224-winbond-6-11-rc1-quad-support-v2-2-ad218dbc406f@bootlin.com Signed-off-by: Mark Brown --- drivers/spi/spi-mem.c | 6 ++++++ include/linux/spi/spi-mem.h | 2 ++ 2 files changed, 8 insertions(+) diff --git a/drivers/spi/spi-mem.c b/drivers/spi/spi-mem.c index 12299ce89a1c..96374afd0193 100644 --- a/drivers/spi/spi-mem.c +++ b/drivers/spi/spi-mem.c @@ -191,6 +191,12 @@ bool spi_mem_default_supports_op(struct spi_mem *mem, op->max_freq < mem->spi->controller->min_speed_hz) return false; + if (op->max_freq && + op->max_freq < mem->spi->max_speed_hz) { + if (!spi_mem_controller_is_capable(ctlr, per_op_freq)) + return false; + } + return spi_mem_check_buswidth(mem, op); } EXPORT_SYMBOL_GPL(spi_mem_default_supports_op); diff --git a/include/linux/spi/spi-mem.h b/include/linux/spi/spi-mem.h index 84ec52498792..c7a7719c2648 100644 --- a/include/linux/spi/spi-mem.h +++ b/include/linux/spi/spi-mem.h @@ -311,11 +311,13 @@ struct spi_controller_mem_ops { * @ecc: Supports operations with error correction * @swap16: Supports swapping bytes on a 16 bit boundary when configured in * Octal DTR + * @per_op_freq: Supports per operation frequency switching */ struct spi_controller_mem_caps { bool dtr; bool ecc; bool swap16; + bool per_op_freq; }; #define spi_mem_controller_is_capable(ctlr, cap) \ From d0e5faccb229b1dacc4c9fa11f6df33bb1fdabd8 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Tue, 24 Dec 2024 18:05:48 +0100 Subject: [PATCH 29/49] spi: amd: Support per spi-mem operation frequency switches Every ->exec_op() call correctly configures the spi bus speed to the maximum allowed frequency for the memory using the constant spi default parameter. Since we can now have per-operation constraints, let's use the value that comes from the spi-mem operation structure instead. In case there is no specific limitation for this operation, the default spi device value will be given anyway. This controller however performed a frequency check, which is also observed during the ->check_op() phase. The per-operation frequency capability is thus advertised to the spi-mem core. Cc: Sanjay R Mehta Signed-off-by: Miquel Raynal Link: https://patch.msgid.link/20241224-winbond-6-11-rc1-quad-support-v2-3-ad218dbc406f@bootlin.com Signed-off-by: Mark Brown --- drivers/spi/spi-amd.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/drivers/spi/spi-amd.c b/drivers/spi/spi-amd.c index d30a21b0b05f..485ae1e382e3 100644 --- a/drivers/spi/spi-amd.c +++ b/drivers/spi/spi-amd.c @@ -479,6 +479,9 @@ static bool amd_spi_supports_op(struct spi_mem *mem, return false; } + if (op->max_freq < mem->spi->controller->min_speed_hz) + return false; + return spi_mem_default_supports_op(mem, op); } @@ -676,7 +679,7 @@ static int amd_spi_exec_mem_op(struct spi_mem *mem, amd_spi = spi_controller_get_devdata(mem->spi->controller); - ret = amd_set_spi_freq(amd_spi, mem->spi->max_speed_hz); + ret = amd_set_spi_freq(amd_spi, op->max_freq); if (ret) return ret; @@ -705,6 +708,10 @@ static const struct spi_controller_mem_ops amd_spi_mem_ops = { .supports_op = amd_spi_supports_op, }; +static const struct spi_controller_mem_caps amd_spi_mem_caps = { + .per_op_freq = true, +}; + static int amd_spi_host_transfer(struct spi_controller *host, struct spi_message *msg) { @@ -782,6 +789,7 @@ static int amd_spi_probe(struct platform_device *pdev) host->setup = amd_spi_host_setup; host->transfer_one_message = amd_spi_host_transfer; host->mem_ops = &amd_spi_mem_ops; + host->mem_caps = &amd_spi_mem_caps; host->max_transfer_size = amd_spi_max_transfer_size; host->max_message_size = amd_spi_max_transfer_size; From e6204f39fe3a7b4538815a2d778b601bd543649e Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Tue, 24 Dec 2024 18:05:49 +0100 Subject: [PATCH 30/49] spi: amd: Drop redundant check Both spi and spi-mem cores already take care of checking the minimum and maximum speed for transfers depending on the controller capabilities. There is no reason to repeat this check in controller drivers. Once this possible error condition removed from the function, it makes no longer sense to return an int. Signed-off-by: Miquel Raynal Link: https://patch.msgid.link/20241224-winbond-6-11-rc1-quad-support-v2-4-ad218dbc406f@bootlin.com Signed-off-by: Mark Brown --- drivers/spi/spi-amd.c | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/drivers/spi/spi-amd.c b/drivers/spi/spi-amd.c index 485ae1e382e3..fbe795bbcf50 100644 --- a/drivers/spi/spi-amd.c +++ b/drivers/spi/spi-amd.c @@ -298,19 +298,16 @@ static const struct amd_spi_freq amd_spi_freq[] = { { AMD_SPI_MIN_HZ, F_800KHz, 0}, }; -static int amd_set_spi_freq(struct amd_spi *amd_spi, u32 speed_hz) +static void amd_set_spi_freq(struct amd_spi *amd_spi, u32 speed_hz) { unsigned int i, spd7_val, alt_spd; - if (speed_hz < AMD_SPI_MIN_HZ) - return -EINVAL; - for (i = 0; i < ARRAY_SIZE(amd_spi_freq); i++) if (speed_hz >= amd_spi_freq[i].speed_hz) break; if (amd_spi->speed_hz == amd_spi_freq[i].speed_hz) - return 0; + return; amd_spi->speed_hz = amd_spi_freq[i].speed_hz; @@ -329,8 +326,6 @@ static int amd_set_spi_freq(struct amd_spi *amd_spi, u32 speed_hz) amd_spi_setclear_reg32(amd_spi, AMD_SPI_SPEED_REG, spd7_val, AMD_SPI_SPD7_MASK); } - - return 0; } static inline int amd_spi_fifo_xfer(struct amd_spi *amd_spi, @@ -679,9 +674,7 @@ static int amd_spi_exec_mem_op(struct spi_mem *mem, amd_spi = spi_controller_get_devdata(mem->spi->controller); - ret = amd_set_spi_freq(amd_spi, op->max_freq); - if (ret) - return ret; + amd_set_spi_freq(amd_spi, op->max_freq); if (amd_spi->version == AMD_SPI_V2) amd_set_spi_addr_mode(amd_spi, op); From 5baa189789e8894c58eacc7803e3c163c1d0fc0a Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Tue, 24 Dec 2024 18:05:50 +0100 Subject: [PATCH 31/49] spi: amlogic-spifc-a1: Support per spi-mem operation frequency switches Every ->exec_op() call correctly configures the spi bus speed to the maximum allowed frequency for the memory using the constant spi default parameter. Since we can now have per-operation constraints, let's use the value that comes from the spi-mem operation structure instead. In case there is no specific limitation for this operation, the default spi device value will be given anyway. The per-operation frequency capability is thus advertised to the spi-mem core. Signed-off-by: Miquel Raynal Link: https://patch.msgid.link/20241224-winbond-6-11-rc1-quad-support-v2-5-ad218dbc406f@bootlin.com Signed-off-by: Mark Brown --- drivers/spi/spi-amlogic-spifc-a1.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/spi/spi-amlogic-spifc-a1.c b/drivers/spi/spi-amlogic-spifc-a1.c index fadf6667cd51..18c9aa2cbc29 100644 --- a/drivers/spi/spi-amlogic-spifc-a1.c +++ b/drivers/spi/spi-amlogic-spifc-a1.c @@ -259,7 +259,7 @@ static int amlogic_spifc_a1_exec_op(struct spi_mem *mem, size_t data_size = op->data.nbytes; int ret; - ret = amlogic_spifc_a1_set_freq(spifc, mem->spi->max_speed_hz); + ret = amlogic_spifc_a1_set_freq(spifc, op->max_freq); if (ret) return ret; @@ -320,6 +320,10 @@ static const struct spi_controller_mem_ops amlogic_spifc_a1_mem_ops = { .adjust_op_size = amlogic_spifc_a1_adjust_op_size, }; +static const struct spi_controller_mem_caps amlogic_spifc_a1_mem_caps = { + .per_op_freq = true, +}; + static int amlogic_spifc_a1_probe(struct platform_device *pdev) { struct spi_controller *ctrl; @@ -356,6 +360,7 @@ static int amlogic_spifc_a1_probe(struct platform_device *pdev) ctrl->bits_per_word_mask = SPI_BPW_MASK(8); ctrl->auto_runtime_pm = true; ctrl->mem_ops = &amlogic_spifc_a1_mem_ops; + ctrl->mem_caps = &amlogic_spifc_a1_mem_caps; ctrl->min_speed_hz = SPIFC_A1_MIN_HZ; ctrl->max_speed_hz = SPIFC_A1_MAX_HZ; ctrl->mode_bits = (SPI_RX_DUAL | SPI_TX_DUAL | From 06e9f5a1f6ba774d8942a168d3ec5ed5a008fbcb Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Tue, 24 Dec 2024 18:05:51 +0100 Subject: [PATCH 32/49] spi: cadence-qspi: Support per spi-mem operation frequency switches Every ->exec_op() call correctly configures the spi bus speed to the maximum allowed frequency for the memory using the constant spi default parameter. Since we can now have per-operation constraints, let's use the value that comes from the spi-mem operation structure instead. In case there is no specific limitation for this operation, the default spi device value will be given anyway. The per-operation frequency capability is thus advertised to the spi-mem core. Reviewed-by: Tudor Ambarus Signed-off-by: Miquel Raynal Link: https://patch.msgid.link/20241224-winbond-6-11-rc1-quad-support-v2-6-ad218dbc406f@bootlin.com Signed-off-by: Mark Brown --- drivers/spi/spi-cadence-quadspi.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/spi/spi-cadence-quadspi.c b/drivers/spi/spi-cadence-quadspi.c index a031ecb358e0..c2379ad13327 100644 --- a/drivers/spi/spi-cadence-quadspi.c +++ b/drivers/spi/spi-cadence-quadspi.c @@ -1409,7 +1409,7 @@ static int cqspi_mem_process(struct spi_mem *mem, const struct spi_mem_op *op) struct cqspi_flash_pdata *f_pdata; f_pdata = &cqspi->f_pdata[spi_get_chipselect(mem->spi, 0)]; - cqspi_configure(f_pdata, mem->spi->max_speed_hz); + cqspi_configure(f_pdata, op->max_freq); if (op->data.dir == SPI_MEM_DATA_IN && op->data.buf.in) { /* @@ -1658,6 +1658,7 @@ static const struct spi_controller_mem_ops cqspi_mem_ops = { static const struct spi_controller_mem_caps cqspi_mem_caps = { .dtr = true, + .per_op_freq = true, }; static int cqspi_setup_flash(struct cqspi_st *cqspi) From eee7bc9e7ade6f7ac17d9ec02887cd5509ba9427 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Tue, 24 Dec 2024 18:05:52 +0100 Subject: [PATCH 33/49] spi: dw: Support per spi-mem operation frequency switches Every ->exec_op() call correctly configures the spi bus speed to the maximum allowed frequency for the memory using the constant spi default parameter. Since we can now have per-operation constraints, let's use the value that comes from the spi-mem operation structure instead. In case there is no specific limitation for this operation, the default spi device value will be given anyway. The per-operation frequency capability is thus advertised to the spi-mem core. Signed-off-by: Miquel Raynal Link: https://patch.msgid.link/20241224-winbond-6-11-rc1-quad-support-v2-7-ad218dbc406f@bootlin.com Signed-off-by: Mark Brown --- drivers/spi/spi-dw-core.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/drivers/spi/spi-dw-core.c b/drivers/spi/spi-dw-core.c index ea517af9435f..941ecc6f59f8 100644 --- a/drivers/spi/spi-dw-core.c +++ b/drivers/spi/spi-dw-core.c @@ -677,7 +677,7 @@ static int dw_spi_exec_mem_op(struct spi_mem *mem, const struct spi_mem_op *op) * operation. Transmit-only mode is suitable for the rest of them. */ cfg.dfs = 8; - cfg.freq = clamp(mem->spi->max_speed_hz, 0U, dws->max_mem_freq); + cfg.freq = clamp(op->max_freq, 0U, dws->max_mem_freq); if (op->data.dir == SPI_MEM_DATA_IN) { cfg.tmode = DW_SPI_CTRLR0_TMOD_EPROMREAD; cfg.ndf = op->data.nbytes; @@ -894,6 +894,10 @@ static void dw_spi_hw_init(struct device *dev, struct dw_spi *dws) dw_writel(dws, DW_SPI_CS_OVERRIDE, 0xF); } +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) { struct spi_controller *host; @@ -941,8 +945,10 @@ int dw_spi_add_host(struct device *dev, struct dw_spi *dws) host->set_cs = dw_spi_set_cs; host->transfer_one = dw_spi_transfer_one; host->handle_err = dw_spi_handle_err; - if (dws->mem_ops.exec_op) + if (dws->mem_ops.exec_op) { host->mem_ops = &dws->mem_ops; + host->mem_caps = &dw_spi_mem_caps; + } host->max_speed_hz = dws->max_freq; host->flags = SPI_CONTROLLER_GPIO_SS; host->auto_runtime_pm = true; From 2438db5253eb17a7c0ccb15aea4252a150dda057 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Tue, 24 Dec 2024 18:05:53 +0100 Subject: [PATCH 34/49] spi: fsl-qspi: Support per spi-mem operation frequency switches Every ->exec_op() call correctly configures the spi bus speed to the maximum allowed frequency for the memory using the constant spi default parameter. Since we can now have per-operation constraints, let's use the value that comes from the spi-mem operation structure instead. In case there is no specific limitation for this operation, the default spi device value will be given anyway. The per-operation frequency capability is thus advertised to the spi-mem core. Cc: Han Xu Signed-off-by: Miquel Raynal Link: https://patch.msgid.link/20241224-winbond-6-11-rc1-quad-support-v2-8-ad218dbc406f@bootlin.com Signed-off-by: Mark Brown --- drivers/spi/spi-fsl-qspi.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/drivers/spi/spi-fsl-qspi.c b/drivers/spi/spi-fsl-qspi.c index 9ec53bf0dda8..355e6a39fb41 100644 --- a/drivers/spi/spi-fsl-qspi.c +++ b/drivers/spi/spi-fsl-qspi.c @@ -522,9 +522,10 @@ static void fsl_qspi_invalidate(struct fsl_qspi *q) qspi_writel(q, reg, q->iobase + QUADSPI_MCR); } -static void fsl_qspi_select_mem(struct fsl_qspi *q, struct spi_device *spi) +static void fsl_qspi_select_mem(struct fsl_qspi *q, struct spi_device *spi, + const struct spi_mem_op *op) { - unsigned long rate = spi->max_speed_hz; + unsigned long rate = op->max_freq; int ret; if (q->selected == spi_get_chipselect(spi, 0)) @@ -652,7 +653,7 @@ static int fsl_qspi_exec_op(struct spi_mem *mem, const struct spi_mem_op *op) fsl_qspi_readl_poll_tout(q, base + QUADSPI_SR, (QUADSPI_SR_IP_ACC_MASK | QUADSPI_SR_AHB_ACC_MASK), 10, 1000); - fsl_qspi_select_mem(q, mem->spi); + fsl_qspi_select_mem(q, mem->spi, op); if (needs_amba_base_offset(q)) addr_offset = q->memmap_phy; @@ -839,6 +840,10 @@ static const struct spi_controller_mem_ops fsl_qspi_mem_ops = { .get_name = fsl_qspi_get_name, }; +static const struct spi_controller_mem_caps fsl_qspi_mem_caps = { + .per_op_freq = true, +}; + static int fsl_qspi_probe(struct platform_device *pdev) { struct spi_controller *ctlr; @@ -923,6 +928,7 @@ static int fsl_qspi_probe(struct platform_device *pdev) ctlr->bus_num = -1; ctlr->num_chipselect = 4; ctlr->mem_ops = &fsl_qspi_mem_ops; + ctlr->mem_caps = &fsl_qspi_mem_caps; fsl_qspi_default_setup(q); From 13529647743d906ed3cf991f1d77727e7ff1fb6f Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Tue, 24 Dec 2024 18:05:54 +0100 Subject: [PATCH 35/49] spi: microchip-core-qspi: Support per spi-mem operation frequency switches Every ->exec_op() call correctly configures the spi bus speed to the maximum allowed frequency for the memory using the constant spi default parameter. Since we can now have per-operation constraints, let's use the value that comes from the spi-mem operation structure instead. In case there is no specific limitation for this operation, the default spi device value will be given anyway. This controller however performed a frequency check, which is also observed during the ->check_op() phase. The per-operation frequency capability is thus advertised to the spi-mem core. Cc: Conor Dooley Cc: Daire McNamara Signed-off-by: Miquel Raynal Link: https://patch.msgid.link/20241224-winbond-6-11-rc1-quad-support-v2-9-ad218dbc406f@bootlin.com Signed-off-by: Mark Brown --- drivers/spi/spi-microchip-core-qspi.c | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/drivers/spi/spi-microchip-core-qspi.c b/drivers/spi/spi-microchip-core-qspi.c index ad2b5ffa6153..fa828fcaaef2 100644 --- a/drivers/spi/spi-microchip-core-qspi.c +++ b/drivers/spi/spi-microchip-core-qspi.c @@ -265,7 +265,8 @@ static irqreturn_t mchp_coreqspi_isr(int irq, void *dev_id) return ret; } -static int mchp_coreqspi_setup_clock(struct mchp_coreqspi *qspi, struct spi_device *spi) +static int mchp_coreqspi_setup_clock(struct mchp_coreqspi *qspi, struct spi_device *spi, + const struct spi_mem_op *op) { unsigned long clk_hz; u32 control, baud_rate_val = 0; @@ -274,11 +275,11 @@ static int mchp_coreqspi_setup_clock(struct mchp_coreqspi *qspi, struct spi_devi if (!clk_hz) return -EINVAL; - baud_rate_val = DIV_ROUND_UP(clk_hz, 2 * spi->max_speed_hz); + baud_rate_val = DIV_ROUND_UP(clk_hz, 2 * op->max_freq); if (baud_rate_val > MAX_DIVIDER || baud_rate_val < MIN_DIVIDER) { dev_err(&spi->dev, "could not configure the clock for spi clock %d Hz & system clock %ld Hz\n", - spi->max_speed_hz, clk_hz); + op->max_freq, clk_hz); return -EINVAL; } @@ -399,7 +400,7 @@ static int mchp_coreqspi_exec_op(struct spi_mem *mem, const struct spi_mem_op *o if (err) goto error; - err = mchp_coreqspi_setup_clock(qspi, mem->spi); + err = mchp_coreqspi_setup_clock(qspi, mem->spi, op); if (err) goto error; @@ -457,6 +458,10 @@ static int mchp_coreqspi_exec_op(struct spi_mem *mem, const struct spi_mem_op *o static bool mchp_coreqspi_supports_op(struct spi_mem *mem, const struct spi_mem_op *op) { + struct mchp_coreqspi *qspi = spi_controller_get_devdata(mem->spi->controller); + unsigned long clk_hz; + u32 baud_rate_val; + if (!spi_mem_default_supports_op(mem, op)) return false; @@ -479,6 +484,14 @@ static bool mchp_coreqspi_supports_op(struct spi_mem *mem, const struct spi_mem_ return false; } + clk_hz = clk_get_rate(qspi->clk); + if (!clk_hz) + return false; + + baud_rate_val = DIV_ROUND_UP(clk_hz, 2 * op->max_freq); + if (baud_rate_val > MAX_DIVIDER || baud_rate_val < MIN_DIVIDER) + return false; + return true; } @@ -498,6 +511,10 @@ static const struct spi_controller_mem_ops mchp_coreqspi_mem_ops = { .exec_op = mchp_coreqspi_exec_op, }; +static const struct spi_controller_mem_caps mchp_coreqspi_mem_caps = { + .per_op_freq = true, +}; + static int mchp_coreqspi_probe(struct platform_device *pdev) { struct spi_controller *ctlr; @@ -540,6 +557,7 @@ static int mchp_coreqspi_probe(struct platform_device *pdev) ctlr->bits_per_word_mask = SPI_BPW_MASK(8); ctlr->mem_ops = &mchp_coreqspi_mem_ops; + ctlr->mem_caps = &mchp_coreqspi_mem_caps; ctlr->setup = mchp_coreqspi_setup_op; ctlr->mode_bits = SPI_CPOL | SPI_CPHA | SPI_RX_DUAL | SPI_RX_QUAD | SPI_TX_DUAL | SPI_TX_QUAD; From 13fd04b53053bbfa741a0f2a781837ab80e485f6 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Tue, 24 Dec 2024 18:05:55 +0100 Subject: [PATCH 36/49] spi: mt65xx: Support per spi-mem operation frequency switches Every ->exec_op() call correctly configures the spi bus speed to the maximum allowed frequency for the memory using the constant spi default parameter. Since we can now have per-operation constraints, let's use the value that comes from the spi-mem operation structure instead. In case there is no specific limitation for this operation, the default spi device value will be given anyway. The per-operation frequency capability is thus advertised to the spi-mem core. Cc: Matthias Brugger Signed-off-by: Miquel Raynal Link: https://patch.msgid.link/20241224-winbond-6-11-rc1-quad-support-v2-10-ad218dbc406f@bootlin.com Signed-off-by: Mark Brown --- drivers/spi/spi-mt65xx.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/spi/spi-mt65xx.c b/drivers/spi/spi-mt65xx.c index 85f3bafc975d..197bf2dbe5de 100644 --- a/drivers/spi/spi-mt65xx.c +++ b/drivers/spi/spi-mt65xx.c @@ -961,7 +961,7 @@ static int mtk_spi_mem_exec_op(struct spi_mem *mem, mtk_spi_reset(mdata); mtk_spi_hw_init(mem->spi->controller, mem->spi); - mtk_spi_prepare_transfer(mem->spi->controller, mem->spi->max_speed_hz); + mtk_spi_prepare_transfer(mem->spi->controller, op->max_freq); reg_val = readl(mdata->base + SPI_CFG3_IPM_REG); /* opcode byte len */ @@ -1122,6 +1122,10 @@ static const struct spi_controller_mem_ops mtk_spi_mem_ops = { .exec_op = mtk_spi_mem_exec_op, }; +static const struct spi_controller_mem_caps mtk_spi_mem_caps = { + .per_op_freq = true, +}; + static int mtk_spi_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -1160,6 +1164,7 @@ static int mtk_spi_probe(struct platform_device *pdev) if (mdata->dev_comp->ipm_design) { mdata->dev = dev; host->mem_ops = &mtk_spi_mem_ops; + host->mem_caps = &mtk_spi_mem_caps; init_completion(&mdata->spimem_done); } From 67707cb094f134f5b3931eefbedbb9ca7e3209d0 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Tue, 24 Dec 2024 18:05:56 +0100 Subject: [PATCH 37/49] spi: mxic: Support per spi-mem operation frequency switches Every ->exec_op() call correctly configures the spi bus speed to the maximum allowed frequency for the memory using the constant spi default parameter. Since we can now have per-operation constraints, let's use the value that comes from the spi-mem operation structure instead. In case there is no specific limitation for this operation, the default spi device value will be given anyway. The per-operation frequency capability is thus advertised to the spi-mem core. Signed-off-by: Miquel Raynal Link: https://patch.msgid.link/20241224-winbond-6-11-rc1-quad-support-v2-11-ad218dbc406f@bootlin.com Signed-off-by: Mark Brown --- drivers/spi/spi-mxic.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/spi/spi-mxic.c b/drivers/spi/spi-mxic.c index 809767d3145c..eeaea6a5e310 100644 --- a/drivers/spi/spi-mxic.c +++ b/drivers/spi/spi-mxic.c @@ -522,7 +522,7 @@ static int mxic_spi_mem_exec_op(struct spi_mem *mem, int i, ret; u8 addr[8], cmd[2]; - ret = mxic_spi_set_freq(mxic, mem->spi->max_speed_hz); + ret = mxic_spi_set_freq(mxic, op->max_freq); if (ret) return ret; @@ -582,6 +582,7 @@ static const struct spi_controller_mem_caps mxic_spi_mem_caps = { .dtr = true, .ecc = true, .swap16 = true, + .per_op_freq = true, }; static void mxic_spi_set_cs(struct spi_device *spi, bool lvl) From 26851cf65ffca2d3a8d529a125e54cf0084d69e7 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Tue, 24 Dec 2024 18:05:57 +0100 Subject: [PATCH 38/49] spi: nxp-fspi: Support per spi-mem operation frequency switches Every ->exec_op() call correctly configures the spi bus speed to the maximum allowed frequency for the memory using the constant spi default parameter. Since we can now have per-operation constraints, let's use the value that comes from the spi-mem operation structure instead. In case there is no specific limitation for this operation, the default spi device value will be given anyway. The per-operation frequency capability is thus advertised to the spi-mem core. Cc: AngeloGioacchino Del Regno Signed-off-by: Miquel Raynal Link: https://patch.msgid.link/20241224-winbond-6-11-rc1-quad-support-v2-12-ad218dbc406f@bootlin.com Signed-off-by: Mark Brown --- drivers/spi/spi-nxp-fspi.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/drivers/spi/spi-nxp-fspi.c b/drivers/spi/spi-nxp-fspi.c index 1161b9e5a4dc..bad6b30bab0e 100644 --- a/drivers/spi/spi-nxp-fspi.c +++ b/drivers/spi/spi-nxp-fspi.c @@ -705,9 +705,10 @@ static void nxp_fspi_dll_calibration(struct nxp_fspi *f) * Value for rest of the CS FLSHxxCR0 register would be zero. * */ -static void nxp_fspi_select_mem(struct nxp_fspi *f, struct spi_device *spi) +static void nxp_fspi_select_mem(struct nxp_fspi *f, struct spi_device *spi, + const struct spi_mem_op *op) { - unsigned long rate = spi->max_speed_hz; + unsigned long rate = op->max_freq; int ret; uint64_t size_kb; @@ -931,7 +932,7 @@ static int nxp_fspi_exec_op(struct spi_mem *mem, const struct spi_mem_op *op) FSPI_STS0_ARB_IDLE, 1, POLL_TOUT, true); WARN_ON(err); - nxp_fspi_select_mem(f, mem->spi); + nxp_fspi_select_mem(f, mem->spi, op); nxp_fspi_prepare_lut(f, op); /* @@ -1149,6 +1150,10 @@ static const struct spi_controller_mem_ops nxp_fspi_mem_ops = { .get_name = nxp_fspi_get_name, }; +static const struct spi_controller_mem_caps nxp_fspi_mem_caps = { + .per_op_freq = true, +}; + static int nxp_fspi_probe(struct platform_device *pdev) { struct spi_controller *ctlr; @@ -1246,6 +1251,7 @@ static int nxp_fspi_probe(struct platform_device *pdev) ctlr->bus_num = -1; ctlr->num_chipselect = NXP_FSPI_MAX_CHIPSELECT; ctlr->mem_ops = &nxp_fspi_mem_ops; + ctlr->mem_caps = &nxp_fspi_mem_caps; nxp_fspi_default_setup(f); From d3f35dd3ad968256ed1080e3ea2022f947861cff Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Tue, 24 Dec 2024 18:05:58 +0100 Subject: [PATCH 39/49] spi: rockchip-sfc: Support per spi-mem operation frequency switches Every ->exec_op() call correctly configures the spi bus speed to the maximum allowed frequency for the memory using the constant spi default parameter. Since we can now have per-operation constraints, let's use the value that comes from the spi-mem operation structure instead. In case there is no specific limitation for this operation, the default spi device value will be given anyway. The per-operation frequency capability is thus advertised to the spi-mem core. Cc: Han Xu Cc: Haibo Chen Cc: Yogesh Gaur Signed-off-by: Miquel Raynal Link: https://patch.msgid.link/20241224-winbond-6-11-rc1-quad-support-v2-13-ad218dbc406f@bootlin.com Signed-off-by: Mark Brown --- drivers/spi/spi-rockchip-sfc.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/drivers/spi/spi-rockchip-sfc.c b/drivers/spi/spi-rockchip-sfc.c index 70bbb459caa4..0cf0e1d3fbae 100644 --- a/drivers/spi/spi-rockchip-sfc.c +++ b/drivers/spi/spi-rockchip-sfc.c @@ -492,11 +492,11 @@ static int rockchip_sfc_exec_mem_op(struct spi_mem *mem, const struct spi_mem_op u32 len = op->data.nbytes; int ret; - if (unlikely(mem->spi->max_speed_hz != sfc->frequency)) { - ret = clk_set_rate(sfc->clk, mem->spi->max_speed_hz); + if (unlikely(op->max_freq != sfc->frequency)) { + ret = clk_set_rate(sfc->clk, op->max_freq); if (ret) return ret; - sfc->frequency = mem->spi->max_speed_hz; + sfc->frequency = op->max_freq; dev_dbg(sfc->dev, "set_freq=%dHz real_freq=%ldHz\n", sfc->frequency, clk_get_rate(sfc->clk)); } @@ -536,6 +536,10 @@ static const struct spi_controller_mem_ops rockchip_sfc_mem_ops = { .adjust_op_size = rockchip_sfc_adjust_op_size, }; +static const struct spi_controller_mem_caps rockchip_sfc_mem_caps = { + .per_op_freq = true, +}; + static irqreturn_t rockchip_sfc_irq_handler(int irq, void *dev_id) { struct rockchip_sfc *sfc = dev_id; @@ -568,6 +572,7 @@ static int rockchip_sfc_probe(struct platform_device *pdev) host->flags = SPI_CONTROLLER_HALF_DUPLEX; host->mem_ops = &rockchip_sfc_mem_ops; + host->mem_caps = &rockchip_sfc_mem_caps; host->dev.of_node = pdev->dev.of_node; host->mode_bits = SPI_TX_QUAD | SPI_TX_DUAL | SPI_RX_QUAD | SPI_RX_DUAL; host->max_speed_hz = SFC_MAX_SPEED; From 1a206344218cc15ad8f321e3abab3f3d36ab639f Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Tue, 24 Dec 2024 18:05:59 +0100 Subject: [PATCH 40/49] spi: spi-sn-f-ospi: Support per spi-mem operation frequency switches Every ->exec_op() call correctly configures the spi bus speed to the maximum allowed frequency for the memory using the constant spi default parameter. Since we can now have per-operation constraints, let's use the value that comes from the spi-mem operation structure instead. In case there is no specific limitation for this operation, the default spi device value will be given anyway. The per-operation frequency capability is thus advertised to the spi-mem core. Signed-off-by: Miquel Raynal Link: https://patch.msgid.link/20241224-winbond-6-11-rc1-quad-support-v2-14-ad218dbc406f@bootlin.com Signed-off-by: Mark Brown --- drivers/spi/spi-sn-f-ospi.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/spi/spi-sn-f-ospi.c b/drivers/spi/spi-sn-f-ospi.c index adac645732fe..6ad4b729897e 100644 --- a/drivers/spi/spi-sn-f-ospi.c +++ b/drivers/spi/spi-sn-f-ospi.c @@ -335,7 +335,6 @@ static void f_ospi_config_indir_protocol(struct f_ospi *ospi, static int f_ospi_indir_prepare_op(struct f_ospi *ospi, struct spi_mem *mem, const struct spi_mem_op *op) { - struct spi_device *spi = mem->spi; u32 irq_stat_en; int ret; @@ -343,7 +342,7 @@ static int f_ospi_indir_prepare_op(struct f_ospi *ospi, struct spi_mem *mem, if (ret) return ret; - f_ospi_config_clk(ospi, spi->max_speed_hz); + f_ospi_config_clk(ospi, op->max_freq); f_ospi_config_indir_protocol(ospi, mem, op); @@ -577,6 +576,10 @@ static const struct spi_controller_mem_ops f_ospi_mem_ops = { .exec_op = f_ospi_exec_op, }; +static const struct spi_controller_mem_caps f_ospi_mem_caps = { + .per_op_freq = true, +}; + static int f_ospi_init(struct f_ospi *ospi) { int ret; @@ -614,6 +617,7 @@ static int f_ospi_probe(struct platform_device *pdev) | SPI_RX_DUAL | SPI_RX_QUAD | SPI_RX_OCTAL | SPI_MODE_0 | SPI_MODE_1 | SPI_LSB_FIRST; ctlr->mem_ops = &f_ospi_mem_ops; + ctlr->mem_caps = &f_ospi_mem_caps; ctlr->bus_num = -1; of_property_read_u32(dev->of_node, "num-cs", &num_cs); if (num_cs > OSPI_NUM_CS) { From b2fac3192919dd07e7ce30558e34abd7e07dde77 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Tue, 24 Dec 2024 18:06:00 +0100 Subject: [PATCH 41/49] spi: spi-ti-qspi: Support per spi-mem operation frequency switches Every ->exec_op() call correctly configures the spi bus speed to the maximum allowed frequency for the memory using the constant spi default parameter. Since we can now have per-operation constraints, let's use the value that comes from the spi-mem operation structure instead. In case there is no specific limitation for this operation, the default spi device value will be given anyway. The per-operation frequency capability is thus advertised to the spi-mem core. Signed-off-by: Miquel Raynal Link: https://patch.msgid.link/20241224-winbond-6-11-rc1-quad-support-v2-15-ad218dbc406f@bootlin.com Signed-off-by: Mark Brown --- drivers/spi/spi-ti-qspi.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/spi/spi-ti-qspi.c b/drivers/spi/spi-ti-qspi.c index 9122350402b5..49516fee74b0 100644 --- a/drivers/spi/spi-ti-qspi.c +++ b/drivers/spi/spi-ti-qspi.c @@ -623,7 +623,7 @@ static int ti_qspi_exec_mem_op(struct spi_mem *mem, mutex_lock(&qspi->list_lock); if (!qspi->mmap_enabled || qspi->current_cs != spi_get_chipselect(mem->spi, 0)) { - ti_qspi_setup_clk(qspi, mem->spi->max_speed_hz); + ti_qspi_setup_clk(qspi, op->max_freq); ti_qspi_enable_memory_map(mem->spi); } ti_qspi_setup_mmap_read(mem->spi, op->cmd.opcode, op->data.buswidth, @@ -658,6 +658,10 @@ static const struct spi_controller_mem_ops ti_qspi_mem_ops = { .adjust_op_size = ti_qspi_adjust_op_size, }; +static const struct spi_controller_mem_caps ti_qspi_mem_caps = { + .per_op_freq = true, +}; + static int ti_qspi_start_transfer_one(struct spi_controller *host, struct spi_message *m) { @@ -777,6 +781,7 @@ static int ti_qspi_probe(struct platform_device *pdev) host->bits_per_word_mask = SPI_BPW_MASK(32) | SPI_BPW_MASK(16) | SPI_BPW_MASK(8); host->mem_ops = &ti_qspi_mem_ops; + host->mem_caps = &ti_qspi_mem_caps; if (!of_property_read_u32(np, "num-cs", &num_cs)) host->num_chipselect = num_cs; From 9a68f6c8d6cfddeac7c5874528ed04e50a1cb579 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Tue, 24 Dec 2024 18:06:01 +0100 Subject: [PATCH 42/49] spi: zynq-qspi: Support per spi-mem operation frequency switches Every ->exec_op() call correctly configures the spi bus speed to the maximum allowed frequency for the memory using the constant spi default parameter. Since we can now have per-operation constraints, let's use the value that comes from the spi-mem operation structure instead. In case there is no specific limitation for this operation, the default spi device value will be given anyway. The per-operation frequency capability is thus advertised to the spi-mem core. Cc: Heiko Stuebner Signed-off-by: Miquel Raynal Link: https://patch.msgid.link/20241224-winbond-6-11-rc1-quad-support-v2-16-ad218dbc406f@bootlin.com Signed-off-by: Mark Brown --- drivers/spi/spi-zynq-qspi.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/drivers/spi/spi-zynq-qspi.c b/drivers/spi/spi-zynq-qspi.c index dee9c339a35e..f3cacb01e25b 100644 --- a/drivers/spi/spi-zynq-qspi.c +++ b/drivers/spi/spi-zynq-qspi.c @@ -318,6 +318,7 @@ static void zynq_qspi_chipselect(struct spi_device *spi, bool assert) * zynq_qspi_config_op - Configure QSPI controller for specified transfer * @xqspi: Pointer to the zynq_qspi structure * @spi: Pointer to the spi_device structure + * @op: The memory operation to execute * * Sets the operational mode of QSPI controller for the next QSPI transfer and * sets the requested clock frequency. @@ -331,7 +332,8 @@ static void zynq_qspi_chipselect(struct spi_device *spi, bool assert) * controller the driver will set the highest or lowest frequency supported by * controller. */ -static int zynq_qspi_config_op(struct zynq_qspi *xqspi, struct spi_device *spi) +static int zynq_qspi_config_op(struct zynq_qspi *xqspi, struct spi_device *spi, + const struct spi_mem_op *op) { u32 config_reg, baud_rate_val = 0; @@ -346,7 +348,7 @@ static int zynq_qspi_config_op(struct zynq_qspi *xqspi, struct spi_device *spi) */ while ((baud_rate_val < ZYNQ_QSPI_CONFIG_BAUD_DIV_MAX) && (clk_get_rate(xqspi->refclk) / (2 << baud_rate_val)) > - spi->max_speed_hz) + op->max_freq) baud_rate_val++; config_reg = zynq_qspi_read(xqspi, ZYNQ_QSPI_CONFIG_OFFSET); @@ -534,7 +536,7 @@ static int zynq_qspi_exec_mem_op(struct spi_mem *mem, op->dummy.buswidth, op->data.buswidth); zynq_qspi_chipselect(mem->spi, true); - zynq_qspi_config_op(xqspi, mem->spi); + zynq_qspi_config_op(xqspi, mem->spi, op); if (op->cmd.opcode) { reinit_completion(&xqspi->data_completion); @@ -620,6 +622,10 @@ static const struct spi_controller_mem_ops zynq_qspi_mem_ops = { .exec_op = zynq_qspi_exec_mem_op, }; +static const struct spi_controller_mem_caps zynq_qspi_mem_caps = { + .per_op_freq = true, +}; + /** * zynq_qspi_probe - Probe method for the QSPI driver * @pdev: Pointer to the platform_device structure @@ -706,6 +712,7 @@ static int zynq_qspi_probe(struct platform_device *pdev) ctlr->mode_bits = SPI_RX_DUAL | SPI_RX_QUAD | SPI_TX_DUAL | SPI_TX_QUAD; ctlr->mem_ops = &zynq_qspi_mem_ops; + ctlr->mem_caps = &zynq_qspi_mem_caps; ctlr->setup = zynq_qspi_setup_op; ctlr->max_speed_hz = clk_get_rate(xqspi->refclk) / 2; ctlr->dev.of_node = np; From 30eb2e6e78225f92f04a2325c6fd77fe8f5b4aab Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Tue, 24 Dec 2024 18:06:02 +0100 Subject: [PATCH 43/49] spi: zynqmp-gqspi: Support per spi-mem operation frequency switches Every ->exec_op() call correctly configures the spi bus speed to the maximum allowed frequency for the memory using the constant spi default parameter. Since we can now have per-operation constraints, let's use the value that comes from the spi-mem operation structure instead. In case there is no specific limitation for this operation, the default spi device value will be given anyway. The per-operation frequency capability is thus advertised to the spi-mem core. Cc: Michal Simek Signed-off-by: Miquel Raynal Link: https://patch.msgid.link/20241224-winbond-6-11-rc1-quad-support-v2-17-ad218dbc406f@bootlin.com Signed-off-by: Mark Brown --- drivers/spi/spi-zynqmp-gqspi.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/drivers/spi/spi-zynqmp-gqspi.c b/drivers/spi/spi-zynqmp-gqspi.c index 549a6e0c9654..d800d79f62a7 100644 --- a/drivers/spi/spi-zynqmp-gqspi.c +++ b/drivers/spi/spi-zynqmp-gqspi.c @@ -535,7 +535,7 @@ static inline u32 zynqmp_qspi_selectspimode(struct zynqmp_qspi *xqspi, * zynqmp_qspi_config_op - Configure QSPI controller for specified * transfer * @xqspi: Pointer to the zynqmp_qspi structure - * @qspi: Pointer to the spi_device structure + * @op: The memory operation to execute * * Sets the operational mode of QSPI controller for the next QSPI transfer and * sets the requested clock frequency. @@ -553,12 +553,12 @@ static inline u32 zynqmp_qspi_selectspimode(struct zynqmp_qspi *xqspi, * frequency supported by controller. */ static int zynqmp_qspi_config_op(struct zynqmp_qspi *xqspi, - struct spi_device *qspi) + const struct spi_mem_op *op) { ulong clk_rate; u32 config_reg, req_speed_hz, baud_rate_val = 0; - req_speed_hz = qspi->max_speed_hz; + req_speed_hz = op->max_freq; if (xqspi->speed_hz != req_speed_hz) { xqspi->speed_hz = req_speed_hz; @@ -1072,7 +1072,7 @@ static int zynqmp_qspi_exec_op(struct spi_mem *mem, op->dummy.buswidth, op->data.buswidth); mutex_lock(&xqspi->op_lock); - zynqmp_qspi_config_op(xqspi, mem->spi); + zynqmp_qspi_config_op(xqspi, op); zynqmp_qspi_chipselect(mem->spi, false); genfifoentry |= xqspi->genfifocs; genfifoentry |= xqspi->genfifobus; @@ -1224,6 +1224,10 @@ static const struct spi_controller_mem_ops zynqmp_qspi_mem_ops = { .exec_op = zynqmp_qspi_exec_op, }; +static const struct spi_controller_mem_caps zynqmp_qspi_mem_caps = { + .per_op_freq = true, +}; + /** * zynqmp_qspi_probe - Probe method for the QSPI driver * @pdev: Pointer to the platform_device structure @@ -1333,6 +1337,7 @@ static int zynqmp_qspi_probe(struct platform_device *pdev) ctlr->bits_per_word_mask = SPI_BPW_MASK(8); ctlr->mem_ops = &zynqmp_qspi_mem_ops; + ctlr->mem_caps = &zynqmp_qspi_mem_caps; ctlr->setup = zynqmp_qspi_setup_op; ctlr->bits_per_word_mask = SPI_BPW_MASK(8); ctlr->dev.of_node = np; From d1f85873d2d62d6980e68d21d3a21f20b0664cc3 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Tue, 24 Dec 2024 18:06:03 +0100 Subject: [PATCH 44/49] spi: spi-mem: Reorder spi-mem macro assignments Follow the order in which all the `struct spi_mem_op` members are defined. This is purely aesthetics, there is no functional change. Signed-off-by: Miquel Raynal Link: https://patch.msgid.link/20241224-winbond-6-11-rc1-quad-support-v2-18-ad218dbc406f@bootlin.com Signed-off-by: Mark Brown --- include/linux/spi/spi-mem.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/include/linux/spi/spi-mem.h b/include/linux/spi/spi-mem.h index c7a7719c2648..ca6ea01c40f8 100644 --- a/include/linux/spi/spi-mem.h +++ b/include/linux/spi/spi-mem.h @@ -15,16 +15,16 @@ #define SPI_MEM_OP_CMD(__opcode, __buswidth) \ { \ + .nbytes = 1, \ .buswidth = __buswidth, \ .opcode = __opcode, \ - .nbytes = 1, \ } #define SPI_MEM_OP_ADDR(__nbytes, __val, __buswidth) \ { \ .nbytes = __nbytes, \ - .val = __val, \ .buswidth = __buswidth, \ + .val = __val, \ } #define SPI_MEM_OP_NO_ADDR { } @@ -39,18 +39,18 @@ #define SPI_MEM_OP_DATA_IN(__nbytes, __buf, __buswidth) \ { \ + .buswidth = __buswidth, \ .dir = SPI_MEM_DATA_IN, \ .nbytes = __nbytes, \ .buf.in = __buf, \ - .buswidth = __buswidth, \ } #define SPI_MEM_OP_DATA_OUT(__nbytes, __buf, __buswidth) \ { \ + .buswidth = __buswidth, \ .dir = SPI_MEM_DATA_OUT, \ .nbytes = __nbytes, \ .buf.out = __buf, \ - .buswidth = __buswidth, \ } #define SPI_MEM_OP_NO_DATA { } From f0006897a96c736623ddeb9b68c3880eb5cdebe7 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Tue, 24 Dec 2024 18:06:04 +0100 Subject: [PATCH 45/49] spi: spi-mem: Create macros for DTR operation We do have macros for defining command, address, dummy and data cycles. We also have a .dtr flag that implies sampling the bus on both edges, but there are currently no macros enabling it. We might make use of such macros, so let's create: - SPI_MEM_DTR_OP_CMD - SPI_MEM_DTR_OP_ADDR - SPI_MEM_DTR_OP_DUMMY - SPI_MEM_DTR_OP_DATA_OUT - SPI_MEM_DTR_OP_DATA_OUT Signed-off-by: Miquel Raynal Link: https://patch.msgid.link/20241224-winbond-6-11-rc1-quad-support-v2-19-ad218dbc406f@bootlin.com Signed-off-by: Mark Brown --- include/linux/spi/spi-mem.h | 41 +++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/include/linux/spi/spi-mem.h b/include/linux/spi/spi-mem.h index ca6ea01c40f8..306c05dd1378 100644 --- a/include/linux/spi/spi-mem.h +++ b/include/linux/spi/spi-mem.h @@ -20,6 +20,14 @@ .opcode = __opcode, \ } +#define SPI_MEM_DTR_OP_CMD(__opcode, __buswidth) \ + { \ + .nbytes = 1, \ + .opcode = __opcode, \ + .buswidth = __buswidth, \ + .dtr = true, \ + } + #define SPI_MEM_OP_ADDR(__nbytes, __val, __buswidth) \ { \ .nbytes = __nbytes, \ @@ -27,6 +35,14 @@ .val = __val, \ } +#define SPI_MEM_DTR_OP_ADDR(__nbytes, __val, __buswidth) \ + { \ + .nbytes = __nbytes, \ + .val = __val, \ + .buswidth = __buswidth, \ + .dtr = true, \ + } + #define SPI_MEM_OP_NO_ADDR { } #define SPI_MEM_OP_DUMMY(__nbytes, __buswidth) \ @@ -35,6 +51,13 @@ .buswidth = __buswidth, \ } +#define SPI_MEM_DTR_OP_DUMMY(__nbytes, __buswidth) \ + { \ + .nbytes = __nbytes, \ + .buswidth = __buswidth, \ + .dtr = true, \ + } + #define SPI_MEM_OP_NO_DUMMY { } #define SPI_MEM_OP_DATA_IN(__nbytes, __buf, __buswidth) \ @@ -45,6 +68,15 @@ .buf.in = __buf, \ } +#define SPI_MEM_DTR_OP_DATA_IN(__nbytes, __buf, __buswidth) \ + { \ + .dir = SPI_MEM_DATA_IN, \ + .nbytes = __nbytes, \ + .buf.in = __buf, \ + .buswidth = __buswidth, \ + .dtr = true, \ + } + #define SPI_MEM_OP_DATA_OUT(__nbytes, __buf, __buswidth) \ { \ .buswidth = __buswidth, \ @@ -53,6 +85,15 @@ .buf.out = __buf, \ } +#define SPI_MEM_DTR_OP_DATA_OUT(__nbytes, __buf, __buswidth) \ + { \ + .dir = SPI_MEM_DATA_OUT, \ + .nbytes = __nbytes, \ + .buf.out = __buf, \ + .buswidth = __buswidth, \ + .dtr = true, \ + } + #define SPI_MEM_OP_NO_DATA { } /** From 226d6cb3cb799aae46d0dd19a521133997d9db11 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 10 Jan 2025 15:45:22 +0100 Subject: [PATCH 46/49] spi: spi-mem: Estimate the time taken by operations In the SPI-NAND layer, we currently make list of operation variants from the fastest one to the slowest and there is a bit of logic in the core to go over them and pick the first one that is supported by the controller, ie. the fastest one among the supported ops. This kind of logic only works if all operations run at the same frequency, but as soon as we introduce per operation max frequencies it is not longer as obvious which operation will be faster, especially since it also depends on the PCB/controller frequency limitation. One way to make this choice more clever is to go over all the variants and for each of them derive an indicator which will help derive the theoretical best. In this case, we derive a theoretical duration for the entire operation and we take the smallest one. Add a helper that parses the spi-mem operation and returns this value. Signed-off-by: Miquel Raynal Link: https://patch.msgid.link/20250110-winbond-6-11-rc1-quad-support-v3-20-7ab4bd56cf6e@bootlin.com Signed-off-by: Mark Brown --- drivers/spi/spi-mem.c | 30 ++++++++++++++++++++++++++++++ include/linux/spi/spi-mem.h | 1 + 2 files changed, 31 insertions(+) diff --git a/drivers/spi/spi-mem.c b/drivers/spi/spi-mem.c index 96374afd0193..a9f0f47f4759 100644 --- a/drivers/spi/spi-mem.c +++ b/drivers/spi/spi-mem.c @@ -562,6 +562,36 @@ void spi_mem_adjust_op_freq(struct spi_mem *mem, struct spi_mem_op *op) } EXPORT_SYMBOL_GPL(spi_mem_adjust_op_freq); +/** + * spi_mem_calc_op_duration() - Derives the theoretical length (in ns) of an + * operation. This helps finding the best variant + * among a list of possible choices. + * @op: the operation to benchmark + * + * Some chips have per-op frequency limitations, PCBs usually have their own + * limitations as well, and controllers can support dual, quad or even octal + * modes, sometimes in DTR. All these combinations make it impossible to + * statically list the best combination for all situations. If we want something + * accurate, all these combinations should be rated (eg. with a time estimate) + * and the best pick should be taken based on these calculations. + * + * Returns a ns estimate for the time this op would take. + */ +u64 spi_mem_calc_op_duration(struct spi_mem_op *op) +{ + u64 ncycles = 0; + u32 ns_per_cycles; + + ns_per_cycles = 1000000000 / op->max_freq; + ncycles += ((op->cmd.nbytes * 8) / op->cmd.buswidth) / (op->cmd.dtr ? 2 : 1); + ncycles += ((op->addr.nbytes * 8) / op->addr.buswidth) / (op->addr.dtr ? 2 : 1); + ncycles += ((op->dummy.nbytes * 8) / op->dummy.buswidth) / (op->dummy.dtr ? 2 : 1); + ncycles += ((op->data.nbytes * 8) / op->data.buswidth) / (op->data.dtr ? 2 : 1); + + return ncycles * ns_per_cycles; +} +EXPORT_SYMBOL_GPL(spi_mem_calc_op_duration); + static ssize_t spi_mem_no_dirmap_read(struct spi_mem_dirmap_desc *desc, u64 offs, size_t len, void *buf) { diff --git a/include/linux/spi/spi-mem.h b/include/linux/spi/spi-mem.h index 306c05dd1378..c4830dfaff3d 100644 --- a/include/linux/spi/spi-mem.h +++ b/include/linux/spi/spi-mem.h @@ -424,6 +424,7 @@ bool spi_mem_default_supports_op(struct spi_mem *mem, int spi_mem_adjust_op_size(struct spi_mem *mem, struct spi_mem_op *op); void spi_mem_adjust_op_freq(struct spi_mem *mem, struct spi_mem_op *op); +u64 spi_mem_calc_op_duration(struct spi_mem_op *op); bool spi_mem_supports_op(struct spi_mem *mem, const struct spi_mem_op *op); From e896c04890aeff2292364c19632fc15d890d436c Mon Sep 17 00:00:00 2001 From: Nathan Chancellor Date: Sat, 11 Jan 2025 12:08:38 -0700 Subject: [PATCH 47/49] spi: amd: Fix -Wuninitialized in amd_spi_exec_mem_op() After commit e6204f39fe3a ("spi: amd: Drop redundant check"), clang warns (or errors with CONFIG_WERROR=y): drivers/spi/spi-amd.c:695:9: error: variable 'ret' is uninitialized when used here [-Werror,-Wuninitialized] 695 | return ret; | ^~~ drivers/spi/spi-amd.c:673:9: note: initialize the variable 'ret' to silence this warning 673 | int ret; | ^ | = 0 1 error generated. ret is no longer set on anything other than the default switch path. Replace ret with a direct return of 0 at the end of the function and -EOPNOTSUPP in the default case to resolve the warning. Fixes: e6204f39fe3a ("spi: amd: Drop redundant check") Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202501112315.ugYQ7Ce7-lkp@intel.com/ Signed-off-by: Nathan Chancellor Reviewed-by: Miguel Ojeda Reviewed-by: Miquel Raynal Link: https://patch.msgid.link/20250111-spi-amd-fix-uninitialized-ret-v1-1-c66ab9f6a23d@kernel.org Signed-off-by: Mark Brown --- drivers/spi/spi-amd.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/spi/spi-amd.c b/drivers/spi/spi-amd.c index fbe795bbcf50..c85997478b81 100644 --- a/drivers/spi/spi-amd.c +++ b/drivers/spi/spi-amd.c @@ -670,7 +670,6 @@ static int amd_spi_exec_mem_op(struct spi_mem *mem, const struct spi_mem_op *op) { struct amd_spi *amd_spi; - int ret; amd_spi = spi_controller_get_devdata(mem->spi->controller); @@ -689,10 +688,10 @@ static int amd_spi_exec_mem_op(struct spi_mem *mem, amd_spi_mem_data_out(amd_spi, op); break; default: - ret = -EOPNOTSUPP; + return -EOPNOTSUPP; } - return ret; + return 0; } static const struct spi_controller_mem_ops amd_spi_mem_ops = { From 40ba3c9019ccd34d576fa14f94ef4f3bd9284c1a Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Sat, 11 Jan 2025 19:54:00 +0100 Subject: [PATCH 48/49] spi: ti-qspi: Use syscon_regmap_lookup_by_phandle_args Use syscon_regmap_lookup_by_phandle_args() which is a wrapper over syscon_regmap_lookup_by_phandle() combined with getting the syscon argument. Except simpler code this annotates within one line that given phandle has arguments, so grepping for code would be easier. There is also no real benefit in printing errors on missing syscon argument, because this is done just too late: runtime check on static/build-time data. Dtschema and Devicetree bindings offer the static/build-time check for this already. Signed-off-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20250111185400.183760-1-krzysztof.kozlowski@linaro.org Signed-off-by: Mark Brown --- drivers/spi/spi-ti-qspi.c | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/drivers/spi/spi-ti-qspi.c b/drivers/spi/spi-ti-qspi.c index 49516fee74b0..a284d2794586 100644 --- a/drivers/spi/spi-ti-qspi.c +++ b/drivers/spi/spi-ti-qspi.c @@ -831,20 +831,12 @@ static int ti_qspi_probe(struct platform_device *pdev) if (of_property_present(np, "syscon-chipselects")) { qspi->ctrl_base = - syscon_regmap_lookup_by_phandle(np, - "syscon-chipselects"); + syscon_regmap_lookup_by_phandle_args(np, "syscon-chipselects", + 1, &qspi->ctrl_reg); if (IS_ERR(qspi->ctrl_base)) { ret = PTR_ERR(qspi->ctrl_base); goto free_host; } - ret = of_property_read_u32_index(np, - "syscon-chipselects", - 1, &qspi->ctrl_reg); - if (ret) { - dev_err(&pdev->dev, - "couldn't get ctrl_mod reg index\n"); - goto free_host; - } } qspi->fclk = devm_clk_get(&pdev->dev, "fck"); From 78b435c9044a9ec321da29d299c70cb14b059682 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Thu, 16 Jan 2025 18:21:09 +0200 Subject: [PATCH 49/49] spi: pxa2xx: Introduce __lpss_ssp_update_priv() helper In a few places we repeat RMW IO operations on LPSS private registers. Let's introduce a helper to make the code better to read and maintain. Signed-off-by: Andy Shevchenko Link: https://patch.msgid.link/20250116162109.263081-1-andriy.shevchenko@linux.intel.com Signed-off-by: Mark Brown --- drivers/spi/spi-pxa2xx.c | 88 ++++++++++++++++++---------------------- 1 file changed, 40 insertions(+), 48 deletions(-) diff --git a/drivers/spi/spi-pxa2xx.c b/drivers/spi/spi-pxa2xx.c index 903d76145272..5f9cac41baff 100644 --- a/drivers/spi/spi-pxa2xx.c +++ b/drivers/spi/spi-pxa2xx.c @@ -73,8 +73,9 @@ struct chip_data { #define LPSS_CAPS_CS_EN_MASK (0xf << LPSS_CAPS_CS_EN_SHIFT) #define LPSS_PRIV_CLOCK_GATE 0x38 -#define LPSS_PRIV_CLOCK_GATE_CLK_CTL_MASK 0x3 -#define LPSS_PRIV_CLOCK_GATE_CLK_CTL_FORCE_ON 0x3 +#define LPSS_PRIV_CLOCK_GATE_CLK_CTL_MASK 0x3 +#define LPSS_PRIV_CLOCK_GATE_CLK_CTL_FORCE_ON 0x3 +#define LPSS_PRIV_CLOCK_GATE_CLK_CTL_FORCE_OFF 0x0 struct lpss_config { /* LPSS offset from drv_data->ioaddr */ @@ -321,6 +322,20 @@ static void __lpss_ssp_write_priv(struct driver_data *drv_data, writel(value, drv_data->lpss_base + offset); } +static bool __lpss_ssp_update_priv(struct driver_data *drv_data, unsigned int offset, + u32 mask, u32 value) +{ + u32 new, curr; + + curr = __lpss_ssp_read_priv(drv_data, offset); + new = (curr & ~mask) | (value & mask); + if (new == curr) + return false; + + __lpss_ssp_write_priv(drv_data, offset, new); + return true; +} + /* * lpss_ssp_setup - perform LPSS SSP specific setup * @drv_data: pointer to the driver private data @@ -337,21 +352,16 @@ static void lpss_ssp_setup(struct driver_data *drv_data) drv_data->lpss_base = drv_data->ssp->mmio_base + config->offset; /* Enable software chip select control */ - value = __lpss_ssp_read_priv(drv_data, config->reg_cs_ctrl); - value &= ~(LPSS_CS_CONTROL_SW_MODE | LPSS_CS_CONTROL_CS_HIGH); - value |= LPSS_CS_CONTROL_SW_MODE | LPSS_CS_CONTROL_CS_HIGH; - __lpss_ssp_write_priv(drv_data, config->reg_cs_ctrl, value); + value = LPSS_CS_CONTROL_SW_MODE | LPSS_CS_CONTROL_CS_HIGH; + __lpss_ssp_update_priv(drv_data, config->reg_cs_ctrl, value, value); /* Enable multiblock DMA transfers */ if (drv_data->controller_info->enable_dma) { - __lpss_ssp_write_priv(drv_data, config->reg_ssp, 1); + __lpss_ssp_update_priv(drv_data, config->reg_ssp, BIT(0), BIT(0)); if (config->reg_general >= 0) { - value = __lpss_ssp_read_priv(drv_data, - config->reg_general); - value |= LPSS_GENERAL_REG_RXTO_HOLDOFF_DISABLE; - __lpss_ssp_write_priv(drv_data, - config->reg_general, value); + value = LPSS_GENERAL_REG_RXTO_HOLDOFF_DISABLE; + __lpss_ssp_update_priv(drv_data, config->reg_general, value, value); } } } @@ -361,30 +371,19 @@ static void lpss_ssp_select_cs(struct spi_device *spi, { struct driver_data *drv_data = spi_controller_get_devdata(spi->controller); - u32 value, cs; + u32 cs; - if (!config->cs_sel_mask) + cs = spi_get_chipselect(spi, 0) << config->cs_sel_shift; + if (!__lpss_ssp_update_priv(drv_data, config->reg_cs_ctrl, config->cs_sel_mask, cs)) return; - value = __lpss_ssp_read_priv(drv_data, config->reg_cs_ctrl); - - cs = spi_get_chipselect(spi, 0); - cs <<= config->cs_sel_shift; - if (cs != (value & config->cs_sel_mask)) { - /* - * When switching another chip select output active the - * output must be selected first and wait 2 ssp_clk cycles - * before changing state to active. Otherwise a short - * glitch will occur on the previous chip select since - * output select is latched but state control is not. - */ - value &= ~config->cs_sel_mask; - value |= cs; - __lpss_ssp_write_priv(drv_data, - config->reg_cs_ctrl, value); - ndelay(1000000000 / - (drv_data->controller->max_speed_hz / 2)); - } + /* + * When switching another chip select output active the output must be + * selected first and wait 2 ssp_clk cycles before changing state to + * active. Otherwise a short glitch will occur on the previous chip + * select since output select is latched but state control is not. + */ + ndelay(1000000000 / (drv_data->controller->max_speed_hz / 2)); } static void lpss_ssp_cs_control(struct spi_device *spi, bool enable) @@ -392,34 +391,27 @@ static void lpss_ssp_cs_control(struct spi_device *spi, bool enable) struct driver_data *drv_data = spi_controller_get_devdata(spi->controller); const struct lpss_config *config; - u32 value; + u32 mask; config = lpss_get_config(drv_data); if (enable) lpss_ssp_select_cs(spi, config); - value = __lpss_ssp_read_priv(drv_data, config->reg_cs_ctrl); - if (enable) - value &= ~LPSS_CS_CONTROL_CS_HIGH; - else - value |= LPSS_CS_CONTROL_CS_HIGH; - __lpss_ssp_write_priv(drv_data, config->reg_cs_ctrl, value); + mask = LPSS_CS_CONTROL_CS_HIGH; + __lpss_ssp_update_priv(drv_data, config->reg_cs_ctrl, mask, enable ? mask : 0); if (config->cs_clk_stays_gated) { - u32 clkgate; - /* * Changing CS alone when dynamic clock gating is on won't * actually flip CS at that time. This ruins SPI transfers * that specify delays, or have no data. Toggle the clock mode * to force on briefly to poke the CS pin to move. */ - clkgate = __lpss_ssp_read_priv(drv_data, LPSS_PRIV_CLOCK_GATE); - value = (clkgate & ~LPSS_PRIV_CLOCK_GATE_CLK_CTL_MASK) | - LPSS_PRIV_CLOCK_GATE_CLK_CTL_FORCE_ON; - - __lpss_ssp_write_priv(drv_data, LPSS_PRIV_CLOCK_GATE, value); - __lpss_ssp_write_priv(drv_data, LPSS_PRIV_CLOCK_GATE, clkgate); + mask = LPSS_PRIV_CLOCK_GATE_CLK_CTL_MASK; + if (__lpss_ssp_update_priv(drv_data, LPSS_PRIV_CLOCK_GATE, mask, + LPSS_PRIV_CLOCK_GATE_CLK_CTL_FORCE_ON)) + __lpss_ssp_update_priv(drv_data, LPSS_PRIV_CLOCK_GATE, mask, + LPSS_PRIV_CLOCK_GATE_CLK_CTL_FORCE_OFF); } }