Unverified Commit da6de6d3 authored by Mark Brown's avatar Mark Brown
Browse files

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
Loading
Loading
Loading
Loading
+13 −0
Original line number Diff line number Diff line
@@ -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>;
    };
+49 −1
Original line number Diff line number Diff line
@@ -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;
}