2023-09-19 15:02:49

by Stephan Gerhold

[permalink] [raw]
Subject: [PATCH v2 0/4] spi: qup: Allow scaling power domains and interconnect

Make it possible to scale performance states of the power domain and
interconnect of the SPI QUP controller in relation to the selected SPI
speed / core clock. This is done separately by:

- Parsing the OPP table from the device tree for performance state
votes of the power domain
- Voting for the necessary bandwidth on the interconnect path to DRAM

Signed-off-by: Stephan Gerhold <[email protected]>
---
Changes in v2:
- Vote for bandwidth only when DMA is used and not with PIO
- Add review tags from Krzysztof and Konrad
- Link to v1: https://lore.kernel.org/r/[email protected]

---
Stephan Gerhold (4):
spi: dt-bindings: qup: Document power-domains and OPP
spi: qup: Parse OPP table for DVFS support
spi: dt-bindings: qup: Document interconnects
spi: qup: Vote for interconnect bandwidth to DRAM

.../devicetree/bindings/spi/qcom,spi-qup.yaml | 13 ++++++
drivers/spi/spi-qup.c | 50 +++++++++++++++++++++-
2 files changed, 62 insertions(+), 1 deletion(-)
---
base-commit: baaa2957b0c6feb4ff7806b5d8e0039bd80acbdf
change-id: 20230912-spi-qup-dvfs-71fc8a5e0cb1

Best regards,
--
Stephan Gerhold <[email protected]>
Kernkonzept GmbH at Dresden, Germany, HRB 31129, CEO Dr.-Ing. Michael Hohmuth


2023-09-19 15:48:44

by Stephan Gerhold

[permalink] [raw]
Subject: [PATCH v2 2/4] spi: qup: Parse OPP table for DVFS support

Parse the OPP table from the device tree and use dev_pm_opp_set_rate()
instead of clk_set_rate() to allow making performance state for power
domains specified in the OPP table.

This is needed to guarantee correct behavior of the clock, especially
with the higher clock/SPI bus frequencies.

Acked-by: Konrad Dybcio <[email protected]>
Signed-off-by: Stephan Gerhold <[email protected]>
---
drivers/spi/spi-qup.c | 12 +++++++++++-
1 file changed, 11 insertions(+), 1 deletion(-)

diff --git a/drivers/spi/spi-qup.c b/drivers/spi/spi-qup.c
index 4b6f6b25219b..bf043be3a2a9 100644
--- a/drivers/spi/spi-qup.c
+++ b/drivers/spi/spi-qup.c
@@ -12,6 +12,7 @@
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
+#include <linux/pm_opp.h>
#include <linux/pm_runtime.h>
#include <linux/spi/spi.h>
#include <linux/dmaengine.h>
@@ -667,7 +668,7 @@ static int spi_qup_io_prep(struct spi_device *spi, struct spi_transfer *xfer)
return -EIO;
}

- ret = clk_set_rate(controller->cclk, xfer->speed_hz);
+ ret = dev_pm_opp_set_rate(controller->dev, xfer->speed_hz);
if (ret) {
dev_err(controller->dev, "fail to set frequency %d",
xfer->speed_hz);
@@ -1027,6 +1028,15 @@ static int spi_qup_probe(struct platform_device *pdev)
return -ENXIO;
}

+ ret = devm_pm_opp_set_clkname(dev, "core");
+ if (ret)
+ return ret;
+
+ /* OPP table is optional */
+ ret = devm_pm_opp_of_add_table(dev);
+ if (ret && ret != -ENODEV)
+ return dev_err_probe(dev, ret, "invalid OPP table\n");
+
host = spi_alloc_host(dev, sizeof(struct spi_qup));
if (!host) {
dev_err(dev, "cannot allocate host\n");

--
2.39.2

2023-09-19 18:27:56

by Stephan Gerhold

[permalink] [raw]
Subject: [PATCH v2 1/4] spi: dt-bindings: qup: Document power-domains and OPP

Document power-domains and operating-points-v2 to allow making
performance state votes for certain clock frequencies of the SPI QUP
controller.

Reviewed-by: Krzysztof Kozlowski <[email protected]>
Signed-off-by: Stephan Gerhold <[email protected]>
---
Documentation/devicetree/bindings/spi/qcom,spi-qup.yaml | 8 ++++++++
1 file changed, 8 insertions(+)

diff --git a/Documentation/devicetree/bindings/spi/qcom,spi-qup.yaml b/Documentation/devicetree/bindings/spi/qcom,spi-qup.yaml
index 93f14dd01afc..1e498a791406 100644
--- a/Documentation/devicetree/bindings/spi/qcom,spi-qup.yaml
+++ b/Documentation/devicetree/bindings/spi/qcom,spi-qup.yaml
@@ -47,6 +47,11 @@ properties:
interrupts:
maxItems: 1

+ operating-points-v2: true
+
+ power-domains:
+ maxItems: 1
+
reg:
maxItems: 1

@@ -63,6 +68,7 @@ examples:
- |
#include <dt-bindings/clock/qcom,gcc-msm8996.h>
#include <dt-bindings/interrupt-controller/arm-gic.h>
+ #include <dt-bindings/power/qcom-rpmpd.h>

spi@7575000 {
compatible = "qcom,spi-qup-v2.2.1";
@@ -76,6 +82,8 @@ examples:
pinctrl-1 = <&blsp1_spi1_sleep>;
dmas = <&blsp1_dma 12>, <&blsp1_dma 13>;
dma-names = "tx", "rx";
+ power-domains = <&rpmpd MSM8996_VDDCX>;
+ operating-points-v2 = <&spi_opp_table>;
#address-cells = <1>;
#size-cells = <0>;
};

--
2.39.2

2023-09-19 18:28:24

by Stephan Gerhold

[permalink] [raw]
Subject: [PATCH v2 3/4] spi: dt-bindings: qup: Document interconnects

When the SPI QUP controller is used together with a DMA engine it needs
to vote for the interconnect path to the DRAM. Otherwise it may be
unable to access the memory quickly enough.

Reviewed-by: Krzysztof Kozlowski <[email protected]>
Signed-off-by: Stephan Gerhold <[email protected]>
---
Documentation/devicetree/bindings/spi/qcom,spi-qup.yaml | 5 +++++
1 file changed, 5 insertions(+)

diff --git a/Documentation/devicetree/bindings/spi/qcom,spi-qup.yaml b/Documentation/devicetree/bindings/spi/qcom,spi-qup.yaml
index 1e498a791406..88be13268962 100644
--- a/Documentation/devicetree/bindings/spi/qcom,spi-qup.yaml
+++ b/Documentation/devicetree/bindings/spi/qcom,spi-qup.yaml
@@ -44,6 +44,9 @@ properties:
- const: tx
- const: rx

+ interconnects:
+ maxItems: 1
+
interrupts:
maxItems: 1

@@ -67,6 +70,7 @@ unevaluatedProperties: false
examples:
- |
#include <dt-bindings/clock/qcom,gcc-msm8996.h>
+ #include <dt-bindings/interconnect/qcom,msm8996.h>
#include <dt-bindings/interrupt-controller/arm-gic.h>
#include <dt-bindings/power/qcom-rpmpd.h>

@@ -84,6 +88,7 @@ examples:
dma-names = "tx", "rx";
power-domains = <&rpmpd MSM8996_VDDCX>;
operating-points-v2 = <&spi_opp_table>;
+ interconnects = <&pnoc MASTER_BLSP_1 &bimc SLAVE_EBI_CH0>;
#address-cells = <1>;
#size-cells = <0>;
};

--
2.39.2

2023-09-20 12:32:46

by Stephan Gerhold

[permalink] [raw]
Subject: [PATCH v2 4/4] spi: qup: Vote for interconnect bandwidth to DRAM

When the SPI QUP controller is used together with a DMA engine it needs
to vote for the interconnect path to the DRAM. Otherwise it may be
unable to access the memory quickly enough.

The requested peak bandwidth is dependent on the SPI core/bus clock so
that the bandwidth scales together with the selected SPI speed.

To avoid sending votes too often the bandwidth is always requested when
a DMA transfer starts, but dropped only on runtime suspend. Runtime
suspend should only happen if no transfer is active. After resumption we
can defer the next vote until the first DMA transfer actually happens.

Signed-off-by: Stephan Gerhold <[email protected]>
---
The bandwidth calculation is taken over from Qualcomm's
downstream/vendor driver [1]. Due to lack of documentation about the
interconnect setup/behavior I cannot say exactly if this is right.
Unfortunately, this is not implemented very consistently downstream...

[1]: https://git.codelinaro.org/clo/la/kernel/msm-3.18/-/commit/deca0f346089d32941d6d8194ae9605554486413
---
drivers/spi/spi-qup.c | 38 ++++++++++++++++++++++++++++++++++++++
1 file changed, 38 insertions(+)

diff --git a/drivers/spi/spi-qup.c b/drivers/spi/spi-qup.c
index bf043be3a2a9..2af63040ac6e 100644
--- a/drivers/spi/spi-qup.c
+++ b/drivers/spi/spi-qup.c
@@ -6,6 +6,7 @@
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/err.h>
+#include <linux/interconnect.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/list.h>
@@ -122,11 +123,14 @@
#define SPI_DELAY_THRESHOLD 1
#define SPI_DELAY_RETRY 10

+#define SPI_BUS_WIDTH 8
+
struct spi_qup {
void __iomem *base;
struct device *dev;
struct clk *cclk; /* core clock */
struct clk *iclk; /* interface clock */
+ struct icc_path *icc_path; /* interconnect to RAM */
int irq;
spinlock_t lock;

@@ -149,6 +153,8 @@ struct spi_qup {
int mode;
struct dma_slave_config rx_conf;
struct dma_slave_config tx_conf;
+
+ u32 bw_speed_hz;
};

static int spi_qup_io_config(struct spi_device *spi, struct spi_transfer *xfer);
@@ -181,6 +187,23 @@ static inline bool spi_qup_is_valid_state(struct spi_qup *controller)
return opstate & QUP_STATE_VALID;
}

+static int spi_qup_vote_bw(struct spi_qup *controller, u32 speed_hz)
+{
+ u32 needed_peak_bw;
+ int ret;
+
+ if (controller->bw_speed_hz == speed_hz)
+ return 0;
+
+ needed_peak_bw = Bps_to_icc(speed_hz * SPI_BUS_WIDTH);
+ ret = icc_set_bw(controller->icc_path, 0, needed_peak_bw);
+ if (ret)
+ return ret;
+
+ controller->bw_speed_hz = speed_hz;
+ return 0;
+}
+
static int spi_qup_set_state(struct spi_qup *controller, u32 state)
{
unsigned long loop;
@@ -451,6 +474,12 @@ static int spi_qup_do_dma(struct spi_device *spi, struct spi_transfer *xfer,
struct scatterlist *tx_sgl, *rx_sgl;
int ret;

+ ret = spi_qup_vote_bw(qup, xfer->speed_hz);
+ if (ret) {
+ dev_err(qup->dev, "fail to vote for ICC bandwidth: %d\n", ret);
+ return -EIO;
+ }
+
if (xfer->rx_buf)
rx_done = spi_qup_dma_done;
else if (xfer->tx_buf)
@@ -994,6 +1023,7 @@ static void spi_qup_set_cs(struct spi_device *spi, bool val)
static int spi_qup_probe(struct platform_device *pdev)
{
struct spi_controller *host;
+ struct icc_path *icc_path;
struct clk *iclk, *cclk;
struct spi_qup *controller;
struct resource *res;
@@ -1019,6 +1049,11 @@ static int spi_qup_probe(struct platform_device *pdev)
if (IS_ERR(iclk))
return PTR_ERR(iclk);

+ icc_path = devm_of_icc_get(dev, NULL);
+ if (IS_ERR(icc_path))
+ return dev_err_probe(dev, PTR_ERR(icc_path),
+ "failed to get interconnect path\n");
+
/* This is optional parameter */
if (of_property_read_u32(dev->of_node, "spi-max-frequency", &max_freq))
max_freq = SPI_MAX_RATE;
@@ -1070,6 +1105,7 @@ static int spi_qup_probe(struct platform_device *pdev)
controller->base = base;
controller->iclk = iclk;
controller->cclk = cclk;
+ controller->icc_path = icc_path;
controller->irq = irq;

ret = spi_qup_init_dma(host, res->start);
@@ -1190,6 +1226,7 @@ static int spi_qup_pm_suspend_runtime(struct device *device)
writel_relaxed(config, controller->base + QUP_CONFIG);

clk_disable_unprepare(controller->cclk);
+ spi_qup_vote_bw(controller, 0);
clk_disable_unprepare(controller->iclk);

return 0;
@@ -1241,6 +1278,7 @@ static int spi_qup_suspend(struct device *device)
return ret;

clk_disable_unprepare(controller->cclk);
+ spi_qup_vote_bw(controller, 0);
clk_disable_unprepare(controller->iclk);
return 0;
}

--
2.39.2

2023-09-26 09:27:20

by Mark Brown

[permalink] [raw]
Subject: Re: [PATCH v2 0/4] spi: qup: Allow scaling power domains and interconnect

On Tue, 19 Sep 2023 13:59:47 +0200, Stephan Gerhold wrote:
> Make it possible to scale performance states of the power domain and
> interconnect of the SPI QUP controller in relation to the selected SPI
> speed / core clock. This is done separately by:
>
> - Parsing the OPP table from the device tree for performance state
> votes of the power domain
> - Voting for the necessary bandwidth on the interconnect path to DRAM
>
> [...]

Applied to

https://git.kernel.org/pub/scm/linux/kernel/git/broonie/spi.git for-next

Thanks!

[1/4] spi: dt-bindings: qup: Document power-domains and OPP
commit: e6419c35f0d92632e06708c5610a31957f1bd6b3
[2/4] spi: qup: Parse OPP table for DVFS support
commit: 287fcdaa35fc666640f805310095c52f2d818d26
[3/4] spi: dt-bindings: qup: Document interconnects
commit: d15befc0cef42db7712714157d9483cab81149a1
[4/4] spi: qup: Vote for interconnect bandwidth to DRAM
commit: ecdaa9473019f94e0ad6974a5f69b9be7de137d3

All being well this means that it will be integrated into the linux-next
tree (usually sometime in the next 24 hours) and sent to Linus during
the next merge window (or sooner if it is a bug fix), however if
problems are discovered then the patch may be dropped or reverted.

You may get further e-mails resulting from automated or manual testing
and review of the tree, please engage with people reporting problems and
send followup patches addressing any issues that are reported if needed.

If any updates are required or you are submitting further changes they
should be sent as incremental updates against current git, existing
patches will not be replaced.

Please add any relevant lists and maintainers to the CCs when replying
to this mail.

Thanks,
Mark