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
This commit is contained in:
commit
da6de6d3ec
@ -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;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user