Commit da6de6d3 authored by Mark Brown's avatar Mark Brown

spi: qup: Allow scaling power domains and

Merge series from Stephan Gerhold <stephan.gerhold@kernkonzept.com>:

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
parents 8a771075 ecdaa947
......@@ -44,9 +44,17 @@ properties:
- const: tx
- const: rx
interconnects:
maxItems: 1
interrupts:
maxItems: 1
operating-points-v2: true
power-domains:
maxItems: 1
reg:
maxItems: 1
......@@ -62,7 +70,9 @@ 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>
spi@7575000 {
compatible = "qcom,spi-qup-v2.2.1";
......@@ -76,6 +86,9 @@ 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>;
interconnects = <&pnoc MASTER_BLSP_1 &bimc SLAVE_EBI_CH0>;
#address-cells = <1>;
#size-cells = <0>;
};
......@@ -6,12 +6,14 @@
#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>
#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>
......@@ -121,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;
......@@ -148,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);
......@@ -180,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;
......@@ -450,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)
......@@ -667,7 +697,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);
......@@ -993,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;
......@@ -1018,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;
......@@ -1027,6 +1063,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");
......@@ -1060,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);
......@@ -1180,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;
......@@ -1231,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;
}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment