linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH 00/13] Enable CPRh/3/4, CPU Scaling on various QCOM SoCs
@ 2020-11-26 18:45 AngeloGioacchino Del Regno
  2020-11-26 18:45 ` [PATCH 01/13] cpuidle: qcom_spm: Detach state machine from main SPM handling AngeloGioacchino Del Regno
                   ` (12 more replies)
  0 siblings, 13 replies; 25+ messages in thread
From: AngeloGioacchino Del Regno @ 2020-11-26 18:45 UTC (permalink / raw)
  To: linux-arm-msm
  Cc: linux-kernel, devicetree, linux-pm, ulf.hansson,
	jorge.ramirez-ortiz, broonie, lgirdwood, daniel.lezcano, nks,
	bjorn.andersson, agross, robh+dt, viresh.kumar, rjw,
	konrad.dybcio, martin.botka, marijn.suijten, phone-devel,
	AngeloGioacchino Del Regno

This patch series is definitely big.
Yup. But it all goes together... here's why:

This series implements full support for CPU scaling on *many* Qualcomm
SoCs and partial support for the others on which the Operating State
Manager is not present.
Since this is a bit tangled, let's go step by step.

First of all, there's the SPM: this is a component that we can find on
very old chips, like MSM8974; there, it has been used to actually do the
power scaling basically "on its own" - sending the cores in a specific
sleep mode to save power.
On the newer ones, including MSM8998, SDM630, 660 and others, it is still
present! Though, this time, it's being used for the cluster caches and it
has a different firmware (and maybe it's also slightly different HW),
implementing the SAWv4.1 set and getting controlled *not by the OS* but
by other controllers in the SoC (like the OSM).

Contrary from MSM8974 and the like, this new version of the SPM just
requires us to set the initial parameters for AVS and *nothing else*, as
its states will be totally managed internally.

Then, hardening here we come!
In all the new SoCs - as new as SM8150 and most probably even newer ones -
there are also new versions of "the same old story".. and here I'm
referring to the Core Power Reduction (CPR) block: since MSM8996 (or
around that time frame), this block has got a sort of major change...
which actually varies the register set and implements "threads".
I won't go far with explaining that in this cover letter (as it's all
explained in the commits) but, in short, here's the catch:
CPR v3, v4 and CPR-Hardened are all based over the same register set
and are extensions of their previous.

A sort of special treatment must be given to CPR-Hardened (CPRh): this is
the one that's present on the newest SoCs, as this is a hardened version
of CPR4 and - in this version - it has got the ability to also get managed
internally, along with the SAWv4.1, by the Operating State Manager (OSM).

And finally, we get to the OSM.
This final piece appeared on MSM8998 for the first time (as far as I know),
and it is (a sort of microcontroller?) doing the "real deal": CPU DVFS
through a lookup table providing "corners" - or "performance states" - to
the OS; pretty straightforward way of offloading a whole lot of tasks that
the kernel would otherwise have to do.

And there we go with the full picture:
- From SDM845 onwards, SAW, CPRh and OSM are getting setup by the
  bootloader/TZ *before* booting the OS, so then all the OS has to do
  is to request a specific performance state to the OSM hardware and
  forget about all the rest, which is anyway protected by the hypervisor
  (so there's no access anyway); BUT:
- In MSM/APQ 8998, SDM/SDA 630/636/660 (and other variants), there is no
  setup of any of these puzzle pieces, and they're also (basically) fully
  accessible, which means that the OS must do it in order to get in the
  same state as the newer ones and to get the entire scaling hardware to
  start rolling.

"Simply", that's it. Now that I've written a kilometer-long "short
explaination" of what's going on, there's a shorter version of it:
- On new SoCs, the bootloader sets up the entire thing
- On old ones, the OS must do what the bootloader didn't do.
- That's what this patch series does. :))

There's also slightly more: since - as already explained - the
CPR-Hardened is an incremental upgrade of CPR v3->v4, it was necessary
for me to also implement support for these two versions, present in
"another whole bunch" of Qualcomm SoCs, including MSM8953, MSM8996 and
others, which is used to do either power reduction or complete voltage
scaling for the CPU clusters on these old ones... and, well, also
in 8998/630/660 along with the Hardened one... and the reason is...
that this piece of HW is also capable of doing the same with the GPU,
even though this is not yet implemented in this set.

I didn't feel like implementing the Multimedia Subsystem (MMSS) part
of the CPR3/4 in this patch series because, eh, it's already long enough,
I'd say.

Perhaps, later... :)

AngeloGioacchino Del Regno (13):
  cpuidle: qcom_spm: Detach state machine from main SPM handling
  soc: qcom: spm: Implement support for SAWv4.1, SDM630/660 L2 AVS
  soc: qcom: spm: Add compatible for MSM8998 SAWv4.1 L2
  cpufreq: blacklist SDM630/636/660 in cpufreq-dt-platdev
  soc: qcom: cpr: Move common functions to new file
  arm64: qcom: qcs404: Change CPR nvmem-names
  dt-bindings: avs: cpr: Convert binding to YAML schema
  soc: qcom: Add support for Core Power Reduction v3, v4 and Hardened
  MAINTAINERS: Add entry for Qualcomm CPRv3/v4/Hardened driver
  dt-bindings: soc: qcom: cpr3: Add bindings for CPR3 driver
  dt-bindings: cpufreq: Convert qcom-cpufreq-hw to YAML binding
  cpufreq: qcom-hw: Implement CPRh aware OSM programming
  dt-bindings: cpufreq: qcom-hw: Add bindings for 8998

 .../bindings/cpufreq/cpufreq-qcom-hw.txt      |  173 +-
 .../bindings/cpufreq/qcom,cpufreq-hw.yaml     |  219 ++
 .../bindings/power/avs/qcom,cpr.txt           |  131 +-
 .../bindings/soc/qcom/qcom,cpr.yaml           |  115 +
 .../bindings/soc/qcom/qcom,cpr3.yaml          |  226 ++
 MAINTAINERS                                   |    8 +-
 arch/arm64/boot/dts/qcom/qcs404.dtsi          |   26 +-
 drivers/cpufreq/cpufreq-dt-platdev.c          |    3 +
 drivers/cpufreq/qcom-cpufreq-hw.c             |  914 +++++-
 drivers/cpuidle/Kconfig.arm                   |    1 +
 drivers/cpuidle/cpuidle-qcom-spm.c            |  294 +-
 drivers/soc/qcom/Kconfig                      |   26 +
 drivers/soc/qcom/Makefile                     |    4 +-
 drivers/soc/qcom/cpr-common.c                 |  382 +++
 drivers/soc/qcom/cpr-common.h                 |  113 +
 drivers/soc/qcom/cpr.c                        |  441 +--
 drivers/soc/qcom/cpr3.c                       | 2474 +++++++++++++++++
 drivers/soc/qcom/spm.c                        |  226 ++
 include/soc/qcom/spm.h                        |   45 +
 19 files changed, 4824 insertions(+), 997 deletions(-)
 create mode 100644 Documentation/devicetree/bindings/cpufreq/qcom,cpufreq-hw.yaml
 create mode 100644 Documentation/devicetree/bindings/soc/qcom/qcom,cpr.yaml
 create mode 100644 Documentation/devicetree/bindings/soc/qcom/qcom,cpr3.yaml
 create mode 100644 drivers/soc/qcom/cpr-common.c
 create mode 100644 drivers/soc/qcom/cpr-common.h
 create mode 100644 drivers/soc/qcom/cpr3.c
 create mode 100644 drivers/soc/qcom/spm.c
 create mode 100644 include/soc/qcom/spm.h

-- 
2.29.2


^ permalink raw reply	[flat|nested] 25+ messages in thread

* [PATCH 01/13] cpuidle: qcom_spm: Detach state machine from main SPM handling
  2020-11-26 18:45 [PATCH 00/13] Enable CPRh/3/4, CPU Scaling on various QCOM SoCs AngeloGioacchino Del Regno
@ 2020-11-26 18:45 ` AngeloGioacchino Del Regno
  2020-11-27 18:07   ` kernel test robot
  2020-11-26 18:45 ` [PATCH 02/13] soc: qcom: spm: Implement support for SAWv4.1, SDM630/660 L2 AVS AngeloGioacchino Del Regno
                   ` (11 subsequent siblings)
  12 siblings, 1 reply; 25+ messages in thread
From: AngeloGioacchino Del Regno @ 2020-11-26 18:45 UTC (permalink / raw)
  To: linux-arm-msm
  Cc: linux-kernel, devicetree, linux-pm, ulf.hansson,
	jorge.ramirez-ortiz, broonie, lgirdwood, daniel.lezcano, nks,
	bjorn.andersson, agross, robh+dt, viresh.kumar, rjw,
	konrad.dybcio, martin.botka, marijn.suijten, phone-devel,
	AngeloGioacchino Del Regno

In commit a871be6b8eee the SPM driver has been converted to a
generic CPUidle driver: that was mainly made to simplify the
driver and that was a great accomplishment;
Though, it was ignored that the SPM driver is not used only
on the ARM architecture.

In preparation for the enablement of SPM features on AArch64/ARM64,
split the cpuidle-qcom-spm driver in two: the CPUIdle related
state machine (currently used only on ARM SoCs) stays there, while
the SPM communication handling lands back in soc/qcom/spm.c and
also making sure to not discard the simplifications that were
introduced in the aforementioned commit.

Since now the "two drivers" are split, the SCM dependency in the
main SPM handling is gone and for this reason it was also possible
to move the SPM initialization early: this will also make sure that
whenever the SAW CPUIdle driver is getting initialized, the SPM
driver will be ready to do the job.

Please note that the anticipation of the SPM initialization was
also done to optimize the boot times on platforms that have their
CPU/L2 idle states managed by other means (such as PSCI), while
needing SAW initialization for other purposes, like AVS control.

Signed-off-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@somainline.org>
---
 drivers/cpuidle/Kconfig.arm        |   1 +
 drivers/cpuidle/cpuidle-qcom-spm.c | 294 ++++++-----------------------
 drivers/soc/qcom/Kconfig           |   9 +
 drivers/soc/qcom/Makefile          |   1 +
 drivers/soc/qcom/spm.c             | 198 +++++++++++++++++++
 include/soc/qcom/spm.h             |  45 +++++
 6 files changed, 312 insertions(+), 236 deletions(-)
 create mode 100644 drivers/soc/qcom/spm.c
 create mode 100644 include/soc/qcom/spm.h

diff --git a/drivers/cpuidle/Kconfig.arm b/drivers/cpuidle/Kconfig.arm
index 0844fadc4be8..c5e2f4d0df04 100644
--- a/drivers/cpuidle/Kconfig.arm
+++ b/drivers/cpuidle/Kconfig.arm
@@ -112,6 +112,7 @@ config ARM_QCOM_SPM_CPUIDLE
 	select CPU_IDLE_MULTIPLE_DRIVERS
 	select DT_IDLE_STATES
 	select QCOM_SCM
+	select QCOM_SPM
 	help
 	  Select this to enable cpuidle for Qualcomm processors.
 	  The Subsystem Power Manager (SPM) controls low power modes for the
diff --git a/drivers/cpuidle/cpuidle-qcom-spm.c b/drivers/cpuidle/cpuidle-qcom-spm.c
index adf91a6e4d7d..3dd7bb10b82d 100644
--- a/drivers/cpuidle/cpuidle-qcom-spm.c
+++ b/drivers/cpuidle/cpuidle-qcom-spm.c
@@ -18,146 +18,13 @@
 #include <linux/cpuidle.h>
 #include <linux/cpu_pm.h>
 #include <linux/qcom_scm.h>
+#include <soc/qcom/spm.h>
 
 #include <asm/proc-fns.h>
 #include <asm/suspend.h>
 
 #include "dt_idle_states.h"
 
-#define MAX_PMIC_DATA		2
-#define MAX_SEQ_DATA		64
-#define SPM_CTL_INDEX		0x7f
-#define SPM_CTL_INDEX_SHIFT	4
-#define SPM_CTL_EN		BIT(0)
-
-enum pm_sleep_mode {
-	PM_SLEEP_MODE_STBY,
-	PM_SLEEP_MODE_RET,
-	PM_SLEEP_MODE_SPC,
-	PM_SLEEP_MODE_PC,
-	PM_SLEEP_MODE_NR,
-};
-
-enum spm_reg {
-	SPM_REG_CFG,
-	SPM_REG_SPM_CTL,
-	SPM_REG_DLY,
-	SPM_REG_PMIC_DLY,
-	SPM_REG_PMIC_DATA_0,
-	SPM_REG_PMIC_DATA_1,
-	SPM_REG_VCTL,
-	SPM_REG_SEQ_ENTRY,
-	SPM_REG_SPM_STS,
-	SPM_REG_PMIC_STS,
-	SPM_REG_NR,
-};
-
-struct spm_reg_data {
-	const u8 *reg_offset;
-	u32 spm_cfg;
-	u32 spm_dly;
-	u32 pmic_dly;
-	u32 pmic_data[MAX_PMIC_DATA];
-	u8 seq[MAX_SEQ_DATA];
-	u8 start_index[PM_SLEEP_MODE_NR];
-};
-
-struct spm_driver_data {
-	struct cpuidle_driver cpuidle_driver;
-	void __iomem *reg_base;
-	const struct spm_reg_data *reg_data;
-};
-
-static const u8 spm_reg_offset_v2_1[SPM_REG_NR] = {
-	[SPM_REG_CFG]		= 0x08,
-	[SPM_REG_SPM_CTL]	= 0x30,
-	[SPM_REG_DLY]		= 0x34,
-	[SPM_REG_SEQ_ENTRY]	= 0x80,
-};
-
-/* SPM register data for 8974, 8084 */
-static const struct spm_reg_data spm_reg_8974_8084_cpu  = {
-	.reg_offset = spm_reg_offset_v2_1,
-	.spm_cfg = 0x1,
-	.spm_dly = 0x3C102800,
-	.seq = { 0x03, 0x0B, 0x0F, 0x00, 0x20, 0x80, 0x10, 0xE8, 0x5B, 0x03,
-		0x3B, 0xE8, 0x5B, 0x82, 0x10, 0x0B, 0x30, 0x06, 0x26, 0x30,
-		0x0F },
-	.start_index[PM_SLEEP_MODE_STBY] = 0,
-	.start_index[PM_SLEEP_MODE_SPC] = 3,
-};
-
-static const u8 spm_reg_offset_v1_1[SPM_REG_NR] = {
-	[SPM_REG_CFG]		= 0x08,
-	[SPM_REG_SPM_CTL]	= 0x20,
-	[SPM_REG_PMIC_DLY]	= 0x24,
-	[SPM_REG_PMIC_DATA_0]	= 0x28,
-	[SPM_REG_PMIC_DATA_1]	= 0x2C,
-	[SPM_REG_SEQ_ENTRY]	= 0x80,
-};
-
-/* SPM register data for 8064 */
-static const struct spm_reg_data spm_reg_8064_cpu = {
-	.reg_offset = spm_reg_offset_v1_1,
-	.spm_cfg = 0x1F,
-	.pmic_dly = 0x02020004,
-	.pmic_data[0] = 0x0084009C,
-	.pmic_data[1] = 0x00A4001C,
-	.seq = { 0x03, 0x0F, 0x00, 0x24, 0x54, 0x10, 0x09, 0x03, 0x01,
-		0x10, 0x54, 0x30, 0x0C, 0x24, 0x30, 0x0F },
-	.start_index[PM_SLEEP_MODE_STBY] = 0,
-	.start_index[PM_SLEEP_MODE_SPC] = 2,
-};
-
-static inline void spm_register_write(struct spm_driver_data *drv,
-					enum spm_reg reg, u32 val)
-{
-	if (drv->reg_data->reg_offset[reg])
-		writel_relaxed(val, drv->reg_base +
-				drv->reg_data->reg_offset[reg]);
-}
-
-/* Ensure a guaranteed write, before return */
-static inline void spm_register_write_sync(struct spm_driver_data *drv,
-					enum spm_reg reg, u32 val)
-{
-	u32 ret;
-
-	if (!drv->reg_data->reg_offset[reg])
-		return;
-
-	do {
-		writel_relaxed(val, drv->reg_base +
-				drv->reg_data->reg_offset[reg]);
-		ret = readl_relaxed(drv->reg_base +
-				drv->reg_data->reg_offset[reg]);
-		if (ret == val)
-			break;
-		cpu_relax();
-	} while (1);
-}
-
-static inline u32 spm_register_read(struct spm_driver_data *drv,
-					enum spm_reg reg)
-{
-	return readl_relaxed(drv->reg_base + drv->reg_data->reg_offset[reg]);
-}
-
-static void spm_set_low_power_mode(struct spm_driver_data *drv,
-					enum pm_sleep_mode mode)
-{
-	u32 start_index;
-	u32 ctl_val;
-
-	start_index = drv->reg_data->start_index[mode];
-
-	ctl_val = spm_register_read(drv, SPM_REG_SPM_CTL);
-	ctl_val &= ~(SPM_CTL_INDEX << SPM_CTL_INDEX_SHIFT);
-	ctl_val |= start_index << SPM_CTL_INDEX_SHIFT;
-	ctl_val |= SPM_CTL_EN;
-	spm_register_write_sync(drv, SPM_REG_SPM_CTL, ctl_val);
-}
-
 static int qcom_pm_collapse(unsigned long int unused)
 {
 	qcom_scm_cpu_power_down(QCOM_SCM_CPU_PWR_DOWN_L2_ON);
@@ -213,132 +80,87 @@ static const struct of_device_id qcom_idle_state_match[] = {
 	{ },
 };
 
-static int spm_cpuidle_init(struct cpuidle_driver *drv, int cpu)
+static int spm_cpuidle_register(int cpu)
 {
+	struct platform_device *pdev = NULL;
+	struct spm_driver_data *spm = NULL;
+	struct device_node *cpu_node, *saw_node;
 	int ret;
 
-	memcpy(drv, &qcom_spm_idle_driver, sizeof(*drv));
-	drv->cpumask = (struct cpumask *)cpumask_of(cpu);
+	cpu_node = of_cpu_device_node_get(cpu);
+	if (!cpu_node)
+		return -ENODEV;
 
-	/* Parse idle states from device tree */
-	ret = dt_init_idle_driver(drv, qcom_idle_state_match, 1);
-	if (ret <= 0)
-		return ret ? : -ENODEV;
+	saw_node = of_parse_phandle(cpu_node, "qcom,saw", 0);
+	if (!saw_node)
+		return -ENODEV;
 
-	/* We have atleast one power down mode */
-	return qcom_scm_set_warm_boot_addr(cpu_resume_arm, drv->cpumask);
-}
+	pdev = of_find_device_by_node(saw_node);
+	of_node_put(saw_node);
+	of_node_put(cpu_node);
+	if (!pdev)
+		return -ENODEV;
 
-static struct spm_driver_data *spm_get_drv(struct platform_device *pdev,
-		int *spm_cpu)
-{
-	struct spm_driver_data *drv = NULL;
-	struct device_node *cpu_node, *saw_node;
-	int cpu;
-	bool found = 0;
+	spm = dev_get_drvdata(&pdev->dev);
+	if (!spm)
+		return -EINVAL;
 
-	for_each_possible_cpu(cpu) {
-		cpu_node = of_cpu_device_node_get(cpu);
-		if (!cpu_node)
-			continue;
-		saw_node = of_parse_phandle(cpu_node, "qcom,saw", 0);
-		found = (saw_node == pdev->dev.of_node);
-		of_node_put(saw_node);
-		of_node_put(cpu_node);
-		if (found)
-			break;
-	}
+	spm->cpuidle_driver = qcom_spm_idle_driver;
 
-	if (found) {
-		drv = devm_kzalloc(&pdev->dev, sizeof(*drv), GFP_KERNEL);
-		if (drv)
-			*spm_cpu = cpu;
-	}
+	ret = dt_init_idle_driver(&spm->cpuidle_driver,
+				  qcom_idle_state_match, 1);
+	if (ret <= 0)
+		return ret ? : -ENODEV;
 
-	return drv;
-}
+	ret = qcom_scm_set_warm_boot_addr(cpu_resume_arm, cpumask_of(cpu));
+	if (ret)
+		return ret;
 
-static const struct of_device_id spm_match_table[] = {
-	{ .compatible = "qcom,msm8974-saw2-v2.1-cpu",
-	  .data = &spm_reg_8974_8084_cpu },
-	{ .compatible = "qcom,apq8084-saw2-v2.1-cpu",
-	  .data = &spm_reg_8974_8084_cpu },
-	{ .compatible = "qcom,apq8064-saw2-v1.1-cpu",
-	  .data = &spm_reg_8064_cpu },
-	{ },
-};
+	return cpuidle_register(&spm->cpuidle_driver, NULL);
+}
 
-static int spm_dev_probe(struct platform_device *pdev)
+static int spm_cpuidle_drv_probe(struct platform_device *pdev)
 {
-	struct spm_driver_data *drv;
-	struct resource *res;
-	const struct of_device_id *match_id;
-	void __iomem *addr;
 	int cpu, ret;
 
 	if (!qcom_scm_is_available())
 		return -EPROBE_DEFER;
 
-	drv = spm_get_drv(pdev, &cpu);
-	if (!drv)
-		return -EINVAL;
-	platform_set_drvdata(pdev, drv);
+	for_each_possible_cpu(cpu) {
+		ret = spm_cpuidle_register(cpu);
+		if (ret != -ENODEV) {
+			dev_err(&pdev->dev,
+				"Cannot register for CPU%d: %d\n", cpu, ret);
+			break;
+		}
+	}
 
-	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
-	drv->reg_base = devm_ioremap_resource(&pdev->dev, res);
-	if (IS_ERR(drv->reg_base))
-		return PTR_ERR(drv->reg_base);
+	return ret;
+}
 
-	match_id = of_match_node(spm_match_table, pdev->dev.of_node);
-	if (!match_id)
-		return -ENODEV;
+static struct platform_driver spm_cpuidle_driver = {
+	.probe = spm_cpuidle_drv_probe,
+	.driver = {
+		.name = "qcom-spm-cpuidle",
+	},
+};
 
-	drv->reg_data = match_id->data;
+static int __init qcom_spm_cpuidle_init(void)
+{
+	struct platform_device *pdev;
+	int ret;
 
-	ret = spm_cpuidle_init(&drv->cpuidle_driver, cpu);
+	ret = platform_driver_register(&spm_cpuidle_driver);
 	if (ret)
 		return ret;
 
-	/* Write the SPM sequences first.. */
-	addr = drv->reg_base + drv->reg_data->reg_offset[SPM_REG_SEQ_ENTRY];
-	__iowrite32_copy(addr, drv->reg_data->seq,
-			ARRAY_SIZE(drv->reg_data->seq) / 4);
-
-	/*
-	 * ..and then the control registers.
-	 * On some SoC if the control registers are written first and if the
-	 * CPU was held in reset, the reset signal could trigger the SPM state
-	 * machine, before the sequences are completely written.
-	 */
-	spm_register_write(drv, SPM_REG_CFG, drv->reg_data->spm_cfg);
-	spm_register_write(drv, SPM_REG_DLY, drv->reg_data->spm_dly);
-	spm_register_write(drv, SPM_REG_PMIC_DLY, drv->reg_data->pmic_dly);
-	spm_register_write(drv, SPM_REG_PMIC_DATA_0,
-				drv->reg_data->pmic_data[0]);
-	spm_register_write(drv, SPM_REG_PMIC_DATA_1,
-				drv->reg_data->pmic_data[1]);
-
-	/* Set up Standby as the default low power mode */
-	spm_set_low_power_mode(drv, PM_SLEEP_MODE_STBY);
-
-	return cpuidle_register(&drv->cpuidle_driver, NULL);
-}
-
-static int spm_dev_remove(struct platform_device *pdev)
-{
-	struct spm_driver_data *drv = platform_get_drvdata(pdev);
+	pdev = platform_device_register_simple("qcom-spm-cpuidle",
+					       -1, NULL, 0);
+	if (IS_ERR(pdev)) {
+		platform_driver_unregister(&spm_cpuidle_driver);
+		return PTR_ERR(pdev);
+	}
 
-	cpuidle_unregister(&drv->cpuidle_driver);
 	return 0;
 }
-
-static struct platform_driver spm_driver = {
-	.probe = spm_dev_probe,
-	.remove = spm_dev_remove,
-	.driver = {
-		.name = "saw",
-		.of_match_table = spm_match_table,
-	},
-};
-
-builtin_platform_driver(spm_driver);
+device_initcall(qcom_spm_cpuidle_init);
diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig
index 6a3b69b43ad5..37f22dcd20af 100644
--- a/drivers/soc/qcom/Kconfig
+++ b/drivers/soc/qcom/Kconfig
@@ -189,6 +189,15 @@ config QCOM_SOCINFO
 	 Say yes here to support the Qualcomm socinfo driver, providing
 	 information about the SoC to user space.
 
+config QCOM_SPM
+	tristate "Qualcomm Subsystem Power Manager (SPM)"
+	depends on ARCH_QCOM
+	select QCOM_SCM
+	help
+	  Enable the support for the Qualcomm Subsystem Power Manager, used
+	  to manage cores, L2 low power modes and to configure the internal
+	  Adaptive Voltage Scaler parameters, where supported.
+
 config QCOM_WCNSS_CTRL
 	tristate "Qualcomm WCNSS control driver"
 	depends on ARCH_QCOM || COMPILE_TEST
diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile
index ad675a6593d0..24514c722832 100644
--- a/drivers/soc/qcom/Makefile
+++ b/drivers/soc/qcom/Makefile
@@ -20,6 +20,7 @@ obj-$(CONFIG_QCOM_SMEM_STATE) += smem_state.o
 obj-$(CONFIG_QCOM_SMP2P)	+= smp2p.o
 obj-$(CONFIG_QCOM_SMSM)	+= smsm.o
 obj-$(CONFIG_QCOM_SOCINFO)	+= socinfo.o
+obj-$(CONFIG_QCOM_SPM)		+= spm.o
 obj-$(CONFIG_QCOM_WCNSS_CTRL) += wcnss_ctrl.o
 obj-$(CONFIG_QCOM_APR) += apr.o
 obj-$(CONFIG_QCOM_LLCC) += llcc-qcom.o
diff --git a/drivers/soc/qcom/spm.c b/drivers/soc/qcom/spm.c
new file mode 100644
index 000000000000..0c8aa9240c41
--- /dev/null
+++ b/drivers/soc/qcom/spm.c
@@ -0,0 +1,198 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2011-2014, The Linux Foundation. All rights reserved.
+ * Copyright (c) 2014,2015, Linaro Ltd.
+ *
+ * SAW power controller driver
+ */
+
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/slab.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_device.h>
+#include <linux/err.h>
+#include <linux/platform_device.h>
+#include <soc/qcom/spm.h>
+
+#define SPM_CTL_INDEX		0x7f
+#define SPM_CTL_INDEX_SHIFT	4
+#define SPM_CTL_EN		BIT(0)
+
+enum spm_reg {
+	SPM_REG_CFG,
+	SPM_REG_SPM_CTL,
+	SPM_REG_DLY,
+	SPM_REG_PMIC_DLY,
+	SPM_REG_PMIC_DATA_0,
+	SPM_REG_PMIC_DATA_1,
+	SPM_REG_VCTL,
+	SPM_REG_SEQ_ENTRY,
+	SPM_REG_SPM_STS,
+	SPM_REG_PMIC_STS,
+	SPM_REG_NR,
+};
+
+static const u16 spm_reg_offset_v2_1[SPM_REG_NR] = {
+	[SPM_REG_CFG]		= 0x08,
+	[SPM_REG_SPM_CTL]	= 0x30,
+	[SPM_REG_DLY]		= 0x34,
+	[SPM_REG_SEQ_ENTRY]	= 0x80,
+};
+
+/* SPM register data for 8974, 8084 */
+static const struct spm_reg_data spm_reg_8974_8084_cpu  = {
+	.reg_offset = spm_reg_offset_v2_1,
+	.spm_cfg = 0x1,
+	.spm_dly = 0x3C102800,
+	.seq = { 0x03, 0x0B, 0x0F, 0x00, 0x20, 0x80, 0x10, 0xE8, 0x5B, 0x03,
+		0x3B, 0xE8, 0x5B, 0x82, 0x10, 0x0B, 0x30, 0x06, 0x26, 0x30,
+		0x0F },
+	.start_index[PM_SLEEP_MODE_STBY] = 0,
+	.start_index[PM_SLEEP_MODE_SPC] = 3,
+};
+
+static const u16 spm_reg_offset_v1_1[SPM_REG_NR] = {
+	[SPM_REG_CFG]		= 0x08,
+	[SPM_REG_SPM_CTL]	= 0x20,
+	[SPM_REG_PMIC_DLY]	= 0x24,
+	[SPM_REG_PMIC_DATA_0]	= 0x28,
+	[SPM_REG_PMIC_DATA_1]	= 0x2C,
+	[SPM_REG_SEQ_ENTRY]	= 0x80,
+};
+
+/* SPM register data for 8064 */
+static const struct spm_reg_data spm_reg_8064_cpu = {
+	.reg_offset = spm_reg_offset_v1_1,
+	.spm_cfg = 0x1F,
+	.pmic_dly = 0x02020004,
+	.pmic_data[0] = 0x0084009C,
+	.pmic_data[1] = 0x00A4001C,
+	.seq = { 0x03, 0x0F, 0x00, 0x24, 0x54, 0x10, 0x09, 0x03, 0x01,
+		0x10, 0x54, 0x30, 0x0C, 0x24, 0x30, 0x0F },
+	.start_index[PM_SLEEP_MODE_STBY] = 0,
+	.start_index[PM_SLEEP_MODE_SPC] = 2,
+};
+
+static inline void spm_register_write(struct spm_driver_data *drv,
+					enum spm_reg reg, u32 val)
+{
+	if (drv->reg_data->reg_offset[reg])
+		writel_relaxed(val, drv->reg_base +
+				drv->reg_data->reg_offset[reg]);
+}
+
+/* Ensure a guaranteed write, before return */
+static inline void spm_register_write_sync(struct spm_driver_data *drv,
+					enum spm_reg reg, u32 val)
+{
+	u32 ret;
+
+	if (!drv->reg_data->reg_offset[reg])
+		return;
+
+	do {
+		writel_relaxed(val, drv->reg_base +
+				drv->reg_data->reg_offset[reg]);
+		ret = readl_relaxed(drv->reg_base +
+				drv->reg_data->reg_offset[reg]);
+		if (ret == val)
+			break;
+		cpu_relax();
+	} while (1);
+}
+
+static inline u32 spm_register_read(struct spm_driver_data *drv,
+					enum spm_reg reg)
+{
+	return readl_relaxed(drv->reg_base + drv->reg_data->reg_offset[reg]);
+}
+
+void spm_set_low_power_mode(struct spm_driver_data *drv,
+					enum pm_sleep_mode mode)
+{
+	u32 start_index;
+	u32 ctl_val;
+
+	start_index = drv->reg_data->start_index[mode];
+
+	ctl_val = spm_register_read(drv, SPM_REG_SPM_CTL);
+	ctl_val &= ~(SPM_CTL_INDEX << SPM_CTL_INDEX_SHIFT);
+	ctl_val |= start_index << SPM_CTL_INDEX_SHIFT;
+	ctl_val |= SPM_CTL_EN;
+	spm_register_write_sync(drv, SPM_REG_SPM_CTL, ctl_val);
+}
+
+static const struct of_device_id spm_match_table[] = {
+	{ .compatible = "qcom,msm8974-saw2-v2.1-cpu",
+	  .data = &spm_reg_8974_8084_cpu },
+	{ .compatible = "qcom,apq8084-saw2-v2.1-cpu",
+	  .data = &spm_reg_8974_8084_cpu },
+	{ .compatible = "qcom,apq8064-saw2-v1.1-cpu",
+	  .data = &spm_reg_8064_cpu },
+	{ },
+};
+
+static int spm_dev_probe(struct platform_device *pdev)
+{
+	const struct of_device_id *match_id;
+	struct spm_driver_data *drv;
+	struct resource *res;
+	void __iomem *addr;
+
+	drv = devm_kzalloc(&pdev->dev, sizeof(*drv), GFP_KERNEL);
+	if (!drv)
+		return -ENOMEM;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	drv->reg_base = devm_ioremap_resource(&pdev->dev, res);
+	if (IS_ERR(drv->reg_base))
+		return PTR_ERR(drv->reg_base);
+
+	match_id = of_match_node(spm_match_table, pdev->dev.of_node);
+	if (!match_id)
+		return -ENODEV;
+
+	drv->reg_data = match_id->data;
+	platform_set_drvdata(pdev, drv);
+
+	/* Write the SPM sequences first.. */
+	addr = drv->reg_base + drv->reg_data->reg_offset[SPM_REG_SEQ_ENTRY];
+	__iowrite32_copy(addr, drv->reg_data->seq,
+			ARRAY_SIZE(drv->reg_data->seq) / 4);
+
+	/*
+	 * ..and then the control registers.
+	 * On some SoC if the control registers are written first and if the
+	 * CPU was held in reset, the reset signal could trigger the SPM state
+	 * machine, before the sequences are completely written.
+	 */
+	spm_register_write(drv, SPM_REG_CFG, drv->reg_data->spm_cfg);
+	spm_register_write(drv, SPM_REG_DLY, drv->reg_data->spm_dly);
+	spm_register_write(drv, SPM_REG_PMIC_DLY, drv->reg_data->pmic_dly);
+	spm_register_write(drv, SPM_REG_PMIC_DATA_0,
+				drv->reg_data->pmic_data[0]);
+	spm_register_write(drv, SPM_REG_PMIC_DATA_1,
+				drv->reg_data->pmic_data[1]);
+
+	/* Set up Standby as the default low power mode */
+	spm_set_low_power_mode(drv, PM_SLEEP_MODE_STBY);
+
+	return 0;
+}
+
+static struct platform_driver spm_driver = {
+	.probe = spm_dev_probe,
+	.driver = {
+		.name = "qcom_spm",
+		.of_match_table = spm_match_table,
+	},
+};
+
+static int __init qcom_spm_init(void)
+{
+	return platform_driver_register(&spm_driver);
+}
+arch_initcall(qcom_spm_init);
diff --git a/include/soc/qcom/spm.h b/include/soc/qcom/spm.h
new file mode 100644
index 000000000000..dfdb9e9edfc6
--- /dev/null
+++ b/include/soc/qcom/spm.h
@@ -0,0 +1,45 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) 2011-2014, The Linux Foundation. All rights reserved.
+ * Copyright (c) 2014,2015, Linaro Ltd.
+ * Copyright (C) 2020, AngeloGioacchino Del Regno <kholk11@gmail.com>
+ */
+
+#ifndef __SPM_H__
+#define __SPM_H__
+
+#include <linux/cpuidle.h>
+
+#define MAX_PMIC_DATA		2
+#define MAX_SEQ_DATA		64
+
+enum pm_sleep_mode {
+	PM_SLEEP_MODE_STBY,
+	PM_SLEEP_MODE_RET,
+	PM_SLEEP_MODE_SPC,
+	PM_SLEEP_MODE_PC,
+	PM_SLEEP_MODE_NR,
+};
+
+struct spm_reg_data {
+	const u16 *reg_offset;
+	u32 spm_cfg;
+	u32 spm_dly;
+	u32 pmic_dly;
+	u32 pmic_data[MAX_PMIC_DATA];
+	u32 avs_ctl;
+	u32 avs_limit;
+	u8 seq[MAX_SEQ_DATA];
+	u8 start_index[PM_SLEEP_MODE_NR];
+};
+
+struct spm_driver_data {
+	struct cpuidle_driver cpuidle_driver;
+	void __iomem *reg_base;
+	const struct spm_reg_data *reg_data;
+};
+
+void spm_set_low_power_mode(struct spm_driver_data *drv,
+					enum pm_sleep_mode mode);
+
+#endif /* __SPM_H__ */
-- 
2.29.2


^ permalink raw reply related	[flat|nested] 25+ messages in thread

* [PATCH 02/13] soc: qcom: spm: Implement support for SAWv4.1, SDM630/660 L2 AVS
  2020-11-26 18:45 [PATCH 00/13] Enable CPRh/3/4, CPU Scaling on various QCOM SoCs AngeloGioacchino Del Regno
  2020-11-26 18:45 ` [PATCH 01/13] cpuidle: qcom_spm: Detach state machine from main SPM handling AngeloGioacchino Del Regno
@ 2020-11-26 18:45 ` AngeloGioacchino Del Regno
  2020-11-26 18:45 ` [PATCH 03/13] soc: qcom: spm: Add compatible for MSM8998 SAWv4.1 L2 AngeloGioacchino Del Regno
                   ` (10 subsequent siblings)
  12 siblings, 0 replies; 25+ messages in thread
From: AngeloGioacchino Del Regno @ 2020-11-26 18:45 UTC (permalink / raw)
  To: linux-arm-msm
  Cc: linux-kernel, devicetree, linux-pm, ulf.hansson,
	jorge.ramirez-ortiz, broonie, lgirdwood, daniel.lezcano, nks,
	bjorn.andersson, agross, robh+dt, viresh.kumar, rjw,
	konrad.dybcio, martin.botka, marijn.suijten, phone-devel,
	AngeloGioacchino Del Regno

Implement the support for SAW v4.1, used in at least MSM8998,
SDM630, SDM660 and APQ variants and, while at it, also add the
configuration for the SDM630/660 Silver and Gold cluster L2
Adaptive Voltage Scaler: this is also one of the prerequisites
to allow the OSM controller to perform DCVS.

Signed-off-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@somainline.org>
---
 drivers/soc/qcom/spm.c | 28 +++++++++++++++++++++++++++-
 1 file changed, 27 insertions(+), 1 deletion(-)

diff --git a/drivers/soc/qcom/spm.c b/drivers/soc/qcom/spm.c
index 0c8aa9240c41..843732d12c54 100644
--- a/drivers/soc/qcom/spm.c
+++ b/drivers/soc/qcom/spm.c
@@ -32,9 +32,28 @@ enum spm_reg {
 	SPM_REG_SEQ_ENTRY,
 	SPM_REG_SPM_STS,
 	SPM_REG_PMIC_STS,
+	SPM_REG_AVS_CTL,
+	SPM_REG_AVS_LIMIT,
 	SPM_REG_NR,
 };
 
+static const u16 spm_reg_offset_v4_1[SPM_REG_NR] = {
+	[SPM_REG_AVS_CTL]	= 0x904,
+	[SPM_REG_AVS_LIMIT]	= 0x908,
+};
+
+static const struct spm_reg_data spm_reg_660_gold_l2  = {
+	.reg_offset = spm_reg_offset_v4_1,
+	.avs_ctl = 0x1010031,
+	.avs_limit = 0x4580458,
+};
+
+static const struct spm_reg_data spm_reg_660_silver_l2  = {
+	.reg_offset = spm_reg_offset_v4_1,
+	.avs_ctl = 0x101c031,
+	.avs_limit = 0x4580458,
+};
+
 static const u16 spm_reg_offset_v2_1[SPM_REG_NR] = {
 	[SPM_REG_CFG]		= 0x08,
 	[SPM_REG_SPM_CTL]	= 0x30,
@@ -126,6 +145,10 @@ void spm_set_low_power_mode(struct spm_driver_data *drv,
 }
 
 static const struct of_device_id spm_match_table[] = {
+	{ .compatible = "qcom,sdm660-gold-saw2-v4.1-l2",
+	  .data = &spm_reg_660_gold_l2 },
+	{ .compatible = "qcom,sdm660-silver-saw2-v4.1-l2",
+	  .data = &spm_reg_660_silver_l2 },
 	{ .compatible = "qcom,msm8974-saw2-v2.1-cpu",
 	  .data = &spm_reg_8974_8084_cpu },
 	{ .compatible = "qcom,apq8084-saw2-v2.1-cpu",
@@ -169,6 +192,8 @@ static int spm_dev_probe(struct platform_device *pdev)
 	 * CPU was held in reset, the reset signal could trigger the SPM state
 	 * machine, before the sequences are completely written.
 	 */
+	spm_register_write(drv, SPM_REG_AVS_CTL, drv->reg_data->avs_ctl);
+	spm_register_write(drv, SPM_REG_AVS_LIMIT, drv->reg_data->avs_limit);
 	spm_register_write(drv, SPM_REG_CFG, drv->reg_data->spm_cfg);
 	spm_register_write(drv, SPM_REG_DLY, drv->reg_data->spm_dly);
 	spm_register_write(drv, SPM_REG_PMIC_DLY, drv->reg_data->pmic_dly);
@@ -178,7 +203,8 @@ static int spm_dev_probe(struct platform_device *pdev)
 				drv->reg_data->pmic_data[1]);
 
 	/* Set up Standby as the default low power mode */
-	spm_set_low_power_mode(drv, PM_SLEEP_MODE_STBY);
+	if (drv->reg_data->reg_offset[SPM_REG_SPM_CTL])
+		spm_set_low_power_mode(drv, PM_SLEEP_MODE_STBY);
 
 	return 0;
 }
-- 
2.29.2


^ permalink raw reply related	[flat|nested] 25+ messages in thread

* [PATCH 03/13] soc: qcom: spm: Add compatible for MSM8998 SAWv4.1 L2
  2020-11-26 18:45 [PATCH 00/13] Enable CPRh/3/4, CPU Scaling on various QCOM SoCs AngeloGioacchino Del Regno
  2020-11-26 18:45 ` [PATCH 01/13] cpuidle: qcom_spm: Detach state machine from main SPM handling AngeloGioacchino Del Regno
  2020-11-26 18:45 ` [PATCH 02/13] soc: qcom: spm: Implement support for SAWv4.1, SDM630/660 L2 AVS AngeloGioacchino Del Regno
@ 2020-11-26 18:45 ` AngeloGioacchino Del Regno
  2020-11-26 18:45 ` [PATCH 04/13] cpufreq: blacklist SDM630/636/660 in cpufreq-dt-platdev AngeloGioacchino Del Regno
                   ` (9 subsequent siblings)
  12 siblings, 0 replies; 25+ messages in thread
From: AngeloGioacchino Del Regno @ 2020-11-26 18:45 UTC (permalink / raw)
  To: linux-arm-msm
  Cc: linux-kernel, devicetree, linux-pm, ulf.hansson,
	jorge.ramirez-ortiz, broonie, lgirdwood, daniel.lezcano, nks,
	bjorn.andersson, agross, robh+dt, viresh.kumar, rjw,
	konrad.dybcio, martin.botka, marijn.suijten, phone-devel,
	AngeloGioacchino Del Regno

The MSM8998 SoC happens to use the same configuration, for both
clusters, as SDM660 Gold L2: add a compatible for it and point
it to the 660 Gold configuration.

Signed-off-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@somainline.org>
---
 drivers/soc/qcom/spm.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/drivers/soc/qcom/spm.c b/drivers/soc/qcom/spm.c
index 843732d12c54..0618fa080367 100644
--- a/drivers/soc/qcom/spm.c
+++ b/drivers/soc/qcom/spm.c
@@ -149,6 +149,8 @@ static const struct of_device_id spm_match_table[] = {
 	  .data = &spm_reg_660_gold_l2 },
 	{ .compatible = "qcom,sdm660-silver-saw2-v4.1-l2",
 	  .data = &spm_reg_660_silver_l2 },
+	{ .compatible = "qcom,msm8998-saw2-v4.1-l2",
+	  .data = &spm_reg_660_gold_l2 },
 	{ .compatible = "qcom,msm8974-saw2-v2.1-cpu",
 	  .data = &spm_reg_8974_8084_cpu },
 	{ .compatible = "qcom,apq8084-saw2-v2.1-cpu",
-- 
2.29.2


^ permalink raw reply related	[flat|nested] 25+ messages in thread

* [PATCH 04/13] cpufreq: blacklist SDM630/636/660 in cpufreq-dt-platdev
  2020-11-26 18:45 [PATCH 00/13] Enable CPRh/3/4, CPU Scaling on various QCOM SoCs AngeloGioacchino Del Regno
                   ` (2 preceding siblings ...)
  2020-11-26 18:45 ` [PATCH 03/13] soc: qcom: spm: Add compatible for MSM8998 SAWv4.1 L2 AngeloGioacchino Del Regno
@ 2020-11-26 18:45 ` AngeloGioacchino Del Regno
  2020-11-26 18:45 ` [PATCH 05/13] soc: qcom: cpr: Move common functions to new file AngeloGioacchino Del Regno
                   ` (8 subsequent siblings)
  12 siblings, 0 replies; 25+ messages in thread
From: AngeloGioacchino Del Regno @ 2020-11-26 18:45 UTC (permalink / raw)
  To: linux-arm-msm
  Cc: linux-kernel, devicetree, linux-pm, ulf.hansson,
	jorge.ramirez-ortiz, broonie, lgirdwood, daniel.lezcano, nks,
	bjorn.andersson, agross, robh+dt, viresh.kumar, rjw,
	konrad.dybcio, martin.botka, marijn.suijten, phone-devel,
	AngeloGioacchino Del Regno

Add the SDM630, SDM636 and SDM660 to the blacklist since the CPU
scaling is handled out of this.

Signed-off-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@somainline.org>
---
 drivers/cpufreq/cpufreq-dt-platdev.c | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/drivers/cpufreq/cpufreq-dt-platdev.c b/drivers/cpufreq/cpufreq-dt-platdev.c
index 3776d960f405..d8935e525807 100644
--- a/drivers/cpufreq/cpufreq-dt-platdev.c
+++ b/drivers/cpufreq/cpufreq-dt-platdev.c
@@ -133,6 +133,9 @@ static const struct of_device_id blacklist[] __initconst = {
 	{ .compatible = "qcom,msm8996", },
 	{ .compatible = "qcom,qcs404", },
 	{ .compatible = "qcom,sc7180", },
+	{ .compatible = "qcom,sdm630", },
+	{ .compatible = "qcom,sdm636", },
+	{ .compatible = "qcom,sdm660", },
 	{ .compatible = "qcom,sdm845", },
 
 	{ .compatible = "st,stih407", },
-- 
2.29.2


^ permalink raw reply related	[flat|nested] 25+ messages in thread

* [PATCH 05/13] soc: qcom: cpr: Move common functions to new file
  2020-11-26 18:45 [PATCH 00/13] Enable CPRh/3/4, CPU Scaling on various QCOM SoCs AngeloGioacchino Del Regno
                   ` (3 preceding siblings ...)
  2020-11-26 18:45 ` [PATCH 04/13] cpufreq: blacklist SDM630/636/660 in cpufreq-dt-platdev AngeloGioacchino Del Regno
@ 2020-11-26 18:45 ` AngeloGioacchino Del Regno
  2020-11-26 18:45 ` [PATCH 06/13] arm64: qcom: qcs404: Change CPR nvmem-names AngeloGioacchino Del Regno
                   ` (7 subsequent siblings)
  12 siblings, 0 replies; 25+ messages in thread
From: AngeloGioacchino Del Regno @ 2020-11-26 18:45 UTC (permalink / raw)
  To: linux-arm-msm
  Cc: linux-kernel, devicetree, linux-pm, ulf.hansson,
	jorge.ramirez-ortiz, broonie, lgirdwood, daniel.lezcano, nks,
	bjorn.andersson, agross, robh+dt, viresh.kumar, rjw,
	konrad.dybcio, martin.botka, marijn.suijten, phone-devel,
	AngeloGioacchino Del Regno

In preparation for implementing a new driver that will be handling
CPRv3, CPRv4 and CPR-Hardened, format out common functions to a new
file.

Signed-off-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@somainline.org>
---
 drivers/soc/qcom/Makefile     |   2 +-
 drivers/soc/qcom/cpr-common.c | 382 +++++++++++++++++++++++++++++
 drivers/soc/qcom/cpr-common.h | 113 +++++++++
 drivers/soc/qcom/cpr.c        | 441 +++-------------------------------
 4 files changed, 523 insertions(+), 415 deletions(-)
 create mode 100644 drivers/soc/qcom/cpr-common.c
 create mode 100644 drivers/soc/qcom/cpr-common.h

diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile
index 24514c722832..778a2a5f07bb 100644
--- a/drivers/soc/qcom/Makefile
+++ b/drivers/soc/qcom/Makefile
@@ -3,7 +3,7 @@ CFLAGS_rpmh-rsc.o := -I$(src)
 obj-$(CONFIG_QCOM_AOSS_QMP) +=	qcom_aoss.o
 obj-$(CONFIG_QCOM_GENI_SE) +=	qcom-geni-se.o
 obj-$(CONFIG_QCOM_COMMAND_DB) += cmd-db.o
-obj-$(CONFIG_QCOM_CPR)		+= cpr.o
+obj-$(CONFIG_QCOM_CPR)		+= cpr-common.o cpr.o
 obj-$(CONFIG_QCOM_GSBI)	+=	qcom_gsbi.o
 obj-$(CONFIG_QCOM_MDT_LOADER)	+= mdt_loader.o
 obj-$(CONFIG_QCOM_OCMEM)	+= ocmem.o
diff --git a/drivers/soc/qcom/cpr-common.c b/drivers/soc/qcom/cpr-common.c
new file mode 100644
index 000000000000..70e1e0f441db
--- /dev/null
+++ b/drivers/soc/qcom/cpr-common.c
@@ -0,0 +1,382 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2013-2015, The Linux Foundation. All rights reserved.
+ * Copyright (c) 2019, Linaro Limited
+ */
+
+#include <linux/module.h>
+#include <linux/err.h>
+#include <linux/debugfs.h>
+#include <linux/string.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/bitops.h>
+#include <linux/slab.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/pm_domain.h>
+#include <linux/pm_opp.h>
+#include <linux/interrupt.h>
+#include <linux/regmap.h>
+#include <linux/mfd/syscon.h>
+#include <linux/regulator/consumer.h>
+#include <linux/clk.h>
+#include <linux/nvmem-consumer.h>
+#include "cpr-common.h"
+
+int cpr_read_efuse(struct device *dev, const char *cname, u32 *data)
+{
+	struct nvmem_cell *cell;
+	ssize_t len;
+	char *ret;
+	int i;
+
+	*data = 0;
+
+	cell = nvmem_cell_get(dev, cname);
+	if (IS_ERR(cell)) {
+		if (PTR_ERR(cell) != -EPROBE_DEFER)
+			dev_err(dev, "undefined cell %s\n", cname);
+		return PTR_ERR(cell);
+	}
+
+	ret = nvmem_cell_read(cell, &len);
+	nvmem_cell_put(cell);
+	if (IS_ERR(ret)) {
+		dev_err(dev, "can't read cell %s\n", cname);
+		return PTR_ERR(ret);
+	}
+
+	for (i = 0; i < len; i++)
+		*data |= ret[i] << (8 * i);
+
+	kfree(ret);
+	dev_dbg(dev, "efuse read(%s) = %x, bytes %zd\n", cname, *data, len);
+
+	return 0;
+}
+
+int cpr_populate_ring_osc_idx(struct device *dev,
+			      struct fuse_corner *fuse_corner,
+			      const struct cpr_fuse *cpr_fuse,
+			      int num_fuse_corners)
+{
+	struct fuse_corner *end = fuse_corner + num_fuse_corners;
+	u32 data;
+	int ret;
+
+	for (; fuse_corner < end; fuse_corner++, cpr_fuse++) {
+		ret = cpr_read_efuse(dev, cpr_fuse->ring_osc,
+				     &data);
+		if (ret)
+			return ret;
+		fuse_corner->ring_osc_idx = data;
+	}
+
+	return 0;
+}
+
+int cpr_read_fuse_uV(int init_v_width, int step_size_uV, int ref_uV,
+		     int adj, int step_volt, const char *init_v_efuse,
+		     struct device *dev)
+{
+	int steps, uV;
+	u32 bits = 0;
+	int ret;
+
+	ret = cpr_read_efuse(dev, init_v_efuse, &bits);
+	if (ret)
+		return ret;
+
+	steps = bits & (BIT(init_v_width - 1) - 1);
+	/* Not two's complement.. instead highest bit is sign bit */
+	if (bits & BIT(init_v_width - 1))
+		steps = -steps;
+
+	uV = ref_uV + steps * step_size_uV;
+
+	/* Apply open-loop fixed adjustments to fused values */
+	uV += adj;
+
+	return DIV_ROUND_UP(uV, step_volt) * step_volt;
+}
+
+const struct cpr_fuse *cpr_get_fuses(struct device *dev, int tid,
+				     int num_fuse_corners)
+{
+	struct cpr_fuse *fuses;
+	int i;
+
+	fuses = devm_kcalloc(dev, num_fuse_corners,
+			     sizeof(struct cpr_fuse),
+			     GFP_KERNEL);
+	if (!fuses)
+		return ERR_PTR(-ENOMEM);
+
+	for (i = 0; i < num_fuse_corners; i++) {
+		char tbuf[50];
+
+		snprintf(tbuf, sizeof(tbuf), "cpr%d_ring_osc%d", tid, i + 1);
+		fuses[i].ring_osc = devm_kstrdup(dev, tbuf, GFP_KERNEL);
+		if (!fuses[i].ring_osc)
+			return ERR_PTR(-ENOMEM);
+
+		snprintf(tbuf, sizeof(tbuf),
+			 "cpr%d_init_voltage%d", tid, i + 1);
+		fuses[i].init_voltage = devm_kstrdup(dev, tbuf,
+						     GFP_KERNEL);
+		if (!fuses[i].init_voltage)
+			return ERR_PTR(-ENOMEM);
+
+		snprintf(tbuf, sizeof(tbuf), "cpr%d_quotient%d", tid, i + 1);
+		fuses[i].quotient = devm_kstrdup(dev, tbuf, GFP_KERNEL);
+		if (!fuses[i].quotient)
+			return ERR_PTR(-ENOMEM);
+
+		snprintf(tbuf, sizeof(tbuf),
+			 "cpr%d_quotient_offset%d", tid, i + 1);
+		fuses[i].quotient_offset = devm_kstrdup(dev, tbuf,
+							GFP_KERNEL);
+		if (!fuses[i].quotient_offset)
+			return ERR_PTR(-ENOMEM);
+	}
+
+	return fuses;
+}
+
+int cpr_populate_fuse_common(struct device *dev,
+			     struct fuse_corner_data *fdata,
+			     const struct cpr_fuse *cpr_fuse,
+			     struct fuse_corner *fuse_corner,
+			     int step_volt, int init_v_width,
+			     int init_v_step)
+{
+	int uV, ret;
+
+	/* Populate uV */
+	uV = cpr_read_fuse_uV(init_v_width, init_v_step,
+			      fdata->ref_uV, fdata->volt_oloop_adjust,
+			      step_volt, cpr_fuse->init_voltage, dev);
+	if (uV < 0)
+		return uV;
+
+	/*
+	 * Update SoC voltages: platforms might choose a different
+	 * regulators than the one used to characterize the algorithms
+	 * (ie, init_voltage_step).
+	 */
+	fdata->min_uV = roundup(fdata->min_uV, step_volt);
+	fdata->max_uV = roundup(fdata->max_uV, step_volt);
+
+	fuse_corner->min_uV = fdata->min_uV;
+	fuse_corner->max_uV = fdata->max_uV;
+	fuse_corner->uV = clamp(uV, fuse_corner->min_uV, fuse_corner->max_uV);
+
+	/* Populate target quotient by scaling */
+	ret = cpr_read_efuse(dev, cpr_fuse->quotient, &fuse_corner->quot);
+	if (ret)
+		return ret;
+
+	fuse_corner->quot *= fdata->quot_scale;
+	fuse_corner->quot += fdata->quot_offset;
+	fuse_corner->quot += fdata->quot_adjust;
+
+	return 0;
+}
+
+/*
+ * Returns: Index of the initial corner or negative number for error.
+ */
+int cpr_find_initial_corner(struct device *dev, struct clk *cpu_clk,
+			    struct corner *corners, int num_corners)
+{
+	unsigned long rate;
+	struct corner *iter, *corner;
+	const struct corner *end;
+	unsigned int ret = 0;
+
+	if (!cpu_clk)
+		return -EINVAL;
+
+	end = &corners[num_corners - 1];
+	rate = clk_get_rate(cpu_clk);
+
+	/*
+	 * Some bootloaders set a CPU clock frequency that is not defined
+	 * in the OPP table. When running at an unlisted frequency,
+	 * cpufreq_online() will change to the OPP which has the lowest
+	 * frequency, at or above the unlisted frequency.
+	 * Since cpufreq_online() always "rounds up" in the case of an
+	 * unlisted frequency, this function always "rounds down" in case
+	 * of an unlisted frequency. That way, when cpufreq_online()
+	 * triggers the first ever call to cpr_set_performance_state(),
+	 * it will correctly determine the direction as UP.
+	 */
+	for (iter = corners; iter <= end; iter++) {
+		if (iter->freq > rate)
+			break;
+		ret++;
+		if (iter->freq == rate) {
+			corner = iter;
+			break;
+		}
+		if (iter->freq < rate)
+			corner = iter;
+	}
+
+	if (!corner) {
+		dev_err(dev, "boot up corner not found\n");
+		return -EINVAL;
+	}
+
+	dev_dbg(dev, "boot up perf state: %u\n", ret);
+
+	return ret;
+}
+
+u32 cpr_get_fuse_corner(struct dev_pm_opp *opp, u32 tid)
+{
+	struct device_node *np;
+	u32 fc;
+
+	np = dev_pm_opp_get_of_node(opp);
+	if (of_property_read_u32_index(np, "qcom,opp-fuse-level", tid, &fc)) {
+		pr_debug("%s: missing 'qcom,opp-fuse-level' property\n",
+			 __func__);
+		fc = 0;
+	}
+
+	of_node_put(np);
+
+	return fc;
+}
+
+unsigned long cpr_get_opp_hz_for_req(struct dev_pm_opp *ref,
+				     struct device *cpu_dev)
+{
+	u64 rate = 0;
+	struct device_node *ref_np;
+	struct device_node *desc_np;
+	struct device_node *child_np = NULL;
+	struct device_node *child_req_np = NULL;
+
+	desc_np = dev_pm_opp_of_get_opp_desc_node(cpu_dev);
+	if (!desc_np)
+		return 0;
+
+	ref_np = dev_pm_opp_get_of_node(ref);
+	if (!ref_np)
+		goto out_ref;
+
+	do {
+		of_node_put(child_req_np);
+		child_np = of_get_next_available_child(desc_np, child_np);
+		child_req_np = of_parse_phandle(child_np, "required-opps", 0);
+	} while (child_np && child_req_np != ref_np);
+
+	if (child_np && child_req_np == ref_np)
+		of_property_read_u64(child_np, "opp-hz", &rate);
+
+	of_node_put(child_req_np);
+	of_node_put(child_np);
+	of_node_put(ref_np);
+out_ref:
+	of_node_put(desc_np);
+
+	return (unsigned long) rate;
+}
+
+int cpr_calculate_scaling(const char *quot_offset,
+			  struct device *dev,
+			  const struct fuse_corner_data *fdata,
+			  const struct corner *corner)
+{
+	u32 quot_diff = 0;
+	unsigned long freq_diff;
+	int scaling;
+	const struct fuse_corner *fuse, *prev_fuse;
+	int ret;
+
+	fuse = corner->fuse_corner;
+	prev_fuse = fuse - 1;
+
+	if (quot_offset) {
+		ret = cpr_read_efuse(dev, quot_offset, &quot_diff);
+		if (ret)
+			return ret;
+
+		quot_diff *= fdata->quot_offset_scale;
+		quot_diff += fdata->quot_offset_adjust;
+	} else {
+		quot_diff = fuse->quot - prev_fuse->quot;
+	}
+
+	freq_diff = fuse->max_freq - prev_fuse->max_freq;
+	freq_diff /= 1000000; /* Convert to MHz */
+	scaling = 1000 * quot_diff / freq_diff;
+	return min(scaling, fdata->max_quot_scale);
+}
+
+int cpr_interpolate(const struct corner *corner, int step_volt,
+		    const struct fuse_corner_data *fdata)
+{
+	unsigned long f_high, f_low, f_diff;
+	int uV_high, uV_low, uV;
+	u64 temp, temp_limit;
+	const struct fuse_corner *fuse, *prev_fuse;
+
+	fuse = corner->fuse_corner;
+	prev_fuse = fuse - 1;
+
+	f_high = fuse->max_freq;
+	f_low = prev_fuse->max_freq;
+	uV_high = fuse->uV;
+	uV_low = prev_fuse->uV;
+	f_diff = fuse->max_freq - corner->freq;
+
+	/*
+	 * Don't interpolate in the wrong direction. This could happen
+	 * if the adjusted fuse voltage overlaps with the previous fuse's
+	 * adjusted voltage.
+	 */
+	if (f_high <= f_low || uV_high <= uV_low || f_high <= corner->freq)
+		return corner->uV;
+
+	temp = f_diff * (uV_high - uV_low);
+	do_div(temp, f_high - f_low);
+
+	/*
+	 * max_volt_scale has units of uV/MHz while freq values
+	 * have units of Hz.  Divide by 1000000 to convert to.
+	 */
+	temp_limit = f_diff * fdata->max_volt_scale;
+	do_div(temp_limit, 1000000);
+
+	uV = uV_high - min(temp, temp_limit);
+	return roundup(uV, step_volt);
+}
+
+int cpr_check_vreg_constraints(struct device *dev, struct regulator *vreg,
+			       struct fuse_corner *f)
+{
+	int ret;
+
+	ret = regulator_is_supported_voltage(vreg, f->min_uV, f->min_uV);
+	if (!ret) {
+		dev_err(dev, "min uV: %d not supported by regulator\n",
+			f->min_uV);
+		return -EINVAL;
+	}
+
+	ret = regulator_is_supported_voltage(vreg, f->max_uV, f->max_uV);
+	if (!ret) {
+		dev_err(dev, "max uV: %d not supported by regulator\n",
+			f->max_uV);
+		return -EINVAL;
+	}
+
+	return 0;
+}
diff --git a/drivers/soc/qcom/cpr-common.h b/drivers/soc/qcom/cpr-common.h
new file mode 100644
index 000000000000..83a1f7c941b8
--- /dev/null
+++ b/drivers/soc/qcom/cpr-common.h
@@ -0,0 +1,113 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#include <linux/clk.h>
+#include <linux/platform_device.h>
+#include <linux/pm_opp.h>
+#include <linux/regulator/consumer.h>
+
+enum voltage_change_dir {
+	NO_CHANGE,
+	DOWN,
+	UP,
+};
+
+struct fuse_corner_data {
+	int ref_uV;
+	int max_uV;
+	int min_uV;
+	int range_uV;
+	/* fuse volt: closed/open loop */
+	int volt_cloop_adjust;
+	int volt_oloop_adjust;
+	int max_volt_scale;
+	int max_quot_scale;
+	/* fuse quot */
+	int quot_offset;
+	int quot_scale;
+	int quot_adjust;
+	/* fuse quot_offset */
+	int quot_offset_scale;
+	int quot_offset_adjust;
+};
+
+struct cpr_fuse {
+	char *ring_osc;
+	char *init_voltage;
+	char *quotient;
+	char *quotient_offset;
+};
+
+struct fuse_corner {
+	int min_uV;
+	int max_uV;
+	int uV;
+	int quot;
+	int step_quot;
+	const struct reg_sequence *accs;
+	int num_accs;
+	unsigned long max_freq;
+	u8 ring_osc_idx;
+};
+
+struct corner {
+	int min_uV;
+	int max_uV;
+	int uV;
+	int last_uV;
+	int quot_adjust;
+	u32 save_ctl;
+	u32 save_irq;
+	unsigned long freq;
+	bool is_open_loop;
+	struct fuse_corner *fuse_corner;
+};
+
+struct corner_data {
+	unsigned int fuse_corner;
+	unsigned long freq;
+};
+
+struct acc_desc {
+	unsigned int	enable_reg;
+	u32		enable_mask;
+
+	struct reg_sequence	*config;
+	struct reg_sequence	*settings;
+	int			num_regs_per_fuse;
+};
+
+struct cpr_acc_desc {
+	const struct cpr_desc *cpr_desc;
+	const struct acc_desc *acc_desc;
+};
+
+
+int cpr_read_efuse(struct device *dev, const char *cname, u32 *data);
+int cpr_populate_ring_osc_idx(struct device *dev,
+			      struct fuse_corner *fuse_corner,
+			      const struct cpr_fuse *cpr_fuse,
+			      int num_fuse_corners);
+int cpr_read_fuse_uV(int init_v_width, int step_size_uV, int ref_uV,
+		     int adj, int step_volt, const char *init_v_efuse,
+		     struct device *dev);
+const struct cpr_fuse *cpr_get_fuses(struct device *dev, int tid,
+				     int num_fuse_corners);
+int cpr_populate_fuse_common(struct device *dev,
+			     struct fuse_corner_data *fdata,
+			     const struct cpr_fuse *cpr_fuse,
+			     struct fuse_corner *fuse_corner,
+			     int step_volt, int init_v_width,
+			     int init_v_step);
+int cpr_find_initial_corner(struct device *dev, struct clk *cpu_clk,
+			    struct corner *corners, int num_corners);
+u32 cpr_get_fuse_corner(struct dev_pm_opp *opp, u32 tid);
+unsigned long cpr_get_opp_hz_for_req(struct dev_pm_opp *ref,
+				     struct device *cpu_dev);
+int cpr_calculate_scaling(const char *quot_offset,
+			  struct device *dev,
+			  const struct fuse_corner_data *fdata,
+			  const struct corner *corner);
+int cpr_interpolate(const struct corner *corner, int step_volt,
+		    const struct fuse_corner_data *fdata);
+int cpr_check_vreg_constraints(struct device *dev, struct regulator *vreg,
+			       struct fuse_corner *f);
diff --git a/drivers/soc/qcom/cpr.c b/drivers/soc/qcom/cpr.c
index b24cc77d1889..29121567956b 100644
--- a/drivers/soc/qcom/cpr.c
+++ b/drivers/soc/qcom/cpr.c
@@ -25,6 +25,7 @@
 #include <linux/regulator/consumer.h>
 #include <linux/clk.h>
 #include <linux/nvmem-consumer.h>
+#include "cpr-common.h"
 
 /* Register Offsets for RB-CPR and Bit Definitions */
 
@@ -124,45 +125,12 @@
 
 #define FUSE_REVISION_UNKNOWN		(-1)
 
-enum voltage_change_dir {
-	NO_CHANGE,
-	DOWN,
-	UP,
-};
-
-struct cpr_fuse {
-	char *ring_osc;
-	char *init_voltage;
-	char *quotient;
-	char *quotient_offset;
-};
-
-struct fuse_corner_data {
-	int ref_uV;
-	int max_uV;
-	int min_uV;
-	int max_volt_scale;
-	int max_quot_scale;
-	/* fuse quot */
-	int quot_offset;
-	int quot_scale;
-	int quot_adjust;
-	/* fuse quot_offset */
-	int quot_offset_scale;
-	int quot_offset_adjust;
-};
-
 struct cpr_fuses {
 	int init_voltage_step;
 	int init_voltage_width;
 	struct fuse_corner_data *fuse_corner_data;
 };
 
-struct corner_data {
-	unsigned int fuse_corner;
-	unsigned long freq;
-};
-
 struct cpr_desc {
 	unsigned int num_fuse_corners;
 	int min_diff_quot;
@@ -184,44 +152,6 @@ struct cpr_desc {
 	bool reduce_to_corner_uV;
 };
 
-struct acc_desc {
-	unsigned int	enable_reg;
-	u32		enable_mask;
-
-	struct reg_sequence	*config;
-	struct reg_sequence	*settings;
-	int			num_regs_per_fuse;
-};
-
-struct cpr_acc_desc {
-	const struct cpr_desc *cpr_desc;
-	const struct acc_desc *acc_desc;
-};
-
-struct fuse_corner {
-	int min_uV;
-	int max_uV;
-	int uV;
-	int quot;
-	int step_quot;
-	const struct reg_sequence *accs;
-	int num_accs;
-	unsigned long max_freq;
-	u8 ring_osc_idx;
-};
-
-struct corner {
-	int min_uV;
-	int max_uV;
-	int uV;
-	int last_uV;
-	int quot_adjust;
-	u32 save_ctl;
-	u32 save_irq;
-	unsigned long freq;
-	struct fuse_corner *fuse_corner;
-};
-
 struct cpr_drv {
 	unsigned int		num_corners;
 	unsigned int		ref_clk_khz;
@@ -801,95 +731,16 @@ static int cpr_set_performance_state(struct generic_pm_domain *domain,
 	return ret;
 }
 
-static int cpr_read_efuse(struct device *dev, const char *cname, u32 *data)
-{
-	struct nvmem_cell *cell;
-	ssize_t len;
-	char *ret;
-	int i;
-
-	*data = 0;
-
-	cell = nvmem_cell_get(dev, cname);
-	if (IS_ERR(cell)) {
-		if (PTR_ERR(cell) != -EPROBE_DEFER)
-			dev_err(dev, "undefined cell %s\n", cname);
-		return PTR_ERR(cell);
-	}
-
-	ret = nvmem_cell_read(cell, &len);
-	nvmem_cell_put(cell);
-	if (IS_ERR(ret)) {
-		dev_err(dev, "can't read cell %s\n", cname);
-		return PTR_ERR(ret);
-	}
-
-	for (i = 0; i < len; i++)
-		*data |= ret[i] << (8 * i);
-
-	kfree(ret);
-	dev_dbg(dev, "efuse read(%s) = %x, bytes %zd\n", cname, *data, len);
-
-	return 0;
-}
-
-static int
-cpr_populate_ring_osc_idx(struct cpr_drv *drv)
-{
-	struct fuse_corner *fuse = drv->fuse_corners;
-	struct fuse_corner *end = fuse + drv->desc->num_fuse_corners;
-	const struct cpr_fuse *fuses = drv->cpr_fuses;
-	u32 data;
-	int ret;
-
-	for (; fuse < end; fuse++, fuses++) {
-		ret = cpr_read_efuse(drv->dev, fuses->ring_osc,
-				     &data);
-		if (ret)
-			return ret;
-		fuse->ring_osc_idx = data;
-	}
-
-	return 0;
-}
-
-static int cpr_read_fuse_uV(const struct cpr_desc *desc,
-			    const struct fuse_corner_data *fdata,
-			    const char *init_v_efuse,
-			    int step_volt,
-			    struct cpr_drv *drv)
-{
-	int step_size_uV, steps, uV;
-	u32 bits = 0;
-	int ret;
-
-	ret = cpr_read_efuse(drv->dev, init_v_efuse, &bits);
-	if (ret)
-		return ret;
-
-	steps = bits & ~BIT(desc->cpr_fuses.init_voltage_width - 1);
-	/* Not two's complement.. instead highest bit is sign bit */
-	if (bits & BIT(desc->cpr_fuses.init_voltage_width - 1))
-		steps = -steps;
-
-	step_size_uV = desc->cpr_fuses.init_voltage_step;
-
-	uV = fdata->ref_uV + steps * step_size_uV;
-	return DIV_ROUND_UP(uV, step_volt) * step_volt;
-}
-
 static int cpr_fuse_corner_init(struct cpr_drv *drv)
 {
 	const struct cpr_desc *desc = drv->desc;
-	const struct cpr_fuse *fuses = drv->cpr_fuses;
+	const struct cpr_fuse *cpr_fuse = drv->cpr_fuses;
 	const struct acc_desc *acc_desc = drv->acc_desc;
-	int i;
-	unsigned int step_volt;
 	struct fuse_corner_data *fdata;
 	struct fuse_corner *fuse, *end;
-	int uV;
 	const struct reg_sequence *accs;
-	int ret;
+	unsigned int step_volt;
+	int i, ret;
 
 	accs = acc_desc->settings;
 
@@ -902,24 +753,16 @@ static int cpr_fuse_corner_init(struct cpr_drv *drv)
 	end = &fuse[desc->num_fuse_corners - 1];
 	fdata = desc->cpr_fuses.fuse_corner_data;
 
-	for (i = 0; fuse <= end; fuse++, fuses++, i++, fdata++) {
-		/*
-		 * Update SoC voltages: platforms might choose a different
-		 * regulators than the one used to characterize the algorithms
-		 * (ie, init_voltage_step).
-		 */
-		fdata->min_uV = roundup(fdata->min_uV, step_volt);
-		fdata->max_uV = roundup(fdata->max_uV, step_volt);
-
-		/* Populate uV */
-		uV = cpr_read_fuse_uV(desc, fdata, fuses->init_voltage,
-				      step_volt, drv);
-		if (uV < 0)
-			return uV;
+	for (i = 0; fuse <= end; fuse++, cpr_fuse++, i++, fdata++) {
+		ret = cpr_populate_fuse_common(
+					drv->dev, fdata, cpr_fuse,
+					fuse, step_volt,
+					desc->cpr_fuses.init_voltage_width,
+					desc->cpr_fuses.init_voltage_step);
+		if (ret)
+			return ret;
 
-		fuse->min_uV = fdata->min_uV;
-		fuse->max_uV = fdata->max_uV;
-		fuse->uV = clamp(uV, fuse->min_uV, fuse->max_uV);
+		fuse->step_quot = desc->step_quot[fuse->ring_osc_idx];
 
 		if (fuse == end) {
 			/*
@@ -931,16 +774,6 @@ static int cpr_fuse_corner_init(struct cpr_drv *drv)
 			end->max_uV = max(end->max_uV, end->uV);
 		}
 
-		/* Populate target quotient by scaling */
-		ret = cpr_read_efuse(drv->dev, fuses->quotient, &fuse->quot);
-		if (ret)
-			return ret;
-
-		fuse->quot *= fdata->quot_scale;
-		fuse->quot += fdata->quot_offset;
-		fuse->quot += fdata->quot_adjust;
-		fuse->step_quot = desc->step_quot[fuse->ring_osc_idx];
-
 		/* Populate acc settings */
 		fuse->accs = accs;
 		fuse->num_accs = acc_desc->num_regs_per_fuse;
@@ -957,25 +790,9 @@ static int cpr_fuse_corner_init(struct cpr_drv *drv)
 		else if (fuse->uV < fuse->min_uV)
 			fuse->uV = fuse->min_uV;
 
-		ret = regulator_is_supported_voltage(drv->vdd_apc,
-						     fuse->min_uV,
-						     fuse->min_uV);
-		if (!ret) {
-			dev_err(drv->dev,
-				"min uV: %d (fuse corner: %d) not supported by regulator\n",
-				fuse->min_uV, i);
-			return -EINVAL;
-		}
-
-		ret = regulator_is_supported_voltage(drv->vdd_apc,
-						     fuse->max_uV,
-						     fuse->max_uV);
-		if (!ret) {
-			dev_err(drv->dev,
-				"max uV: %d (fuse corner: %d) not supported by regulator\n",
-				fuse->max_uV, i);
-			return -EINVAL;
-		}
+		ret = cpr_check_vreg_constraints(drv->dev, drv->vdd_apc, fuse);
+		if (ret)
+			return ret;
 
 		dev_dbg(drv->dev,
 			"fuse corner %d: [%d %d %d] RO%hhu quot %d squot %d\n",
@@ -986,126 +803,6 @@ static int cpr_fuse_corner_init(struct cpr_drv *drv)
 	return 0;
 }
 
-static int cpr_calculate_scaling(const char *quot_offset,
-				 struct cpr_drv *drv,
-				 const struct fuse_corner_data *fdata,
-				 const struct corner *corner)
-{
-	u32 quot_diff = 0;
-	unsigned long freq_diff;
-	int scaling;
-	const struct fuse_corner *fuse, *prev_fuse;
-	int ret;
-
-	fuse = corner->fuse_corner;
-	prev_fuse = fuse - 1;
-
-	if (quot_offset) {
-		ret = cpr_read_efuse(drv->dev, quot_offset, &quot_diff);
-		if (ret)
-			return ret;
-
-		quot_diff *= fdata->quot_offset_scale;
-		quot_diff += fdata->quot_offset_adjust;
-	} else {
-		quot_diff = fuse->quot - prev_fuse->quot;
-	}
-
-	freq_diff = fuse->max_freq - prev_fuse->max_freq;
-	freq_diff /= 1000000; /* Convert to MHz */
-	scaling = 1000 * quot_diff / freq_diff;
-	return min(scaling, fdata->max_quot_scale);
-}
-
-static int cpr_interpolate(const struct corner *corner, int step_volt,
-			   const struct fuse_corner_data *fdata)
-{
-	unsigned long f_high, f_low, f_diff;
-	int uV_high, uV_low, uV;
-	u64 temp, temp_limit;
-	const struct fuse_corner *fuse, *prev_fuse;
-
-	fuse = corner->fuse_corner;
-	prev_fuse = fuse - 1;
-
-	f_high = fuse->max_freq;
-	f_low = prev_fuse->max_freq;
-	uV_high = fuse->uV;
-	uV_low = prev_fuse->uV;
-	f_diff = fuse->max_freq - corner->freq;
-
-	/*
-	 * Don't interpolate in the wrong direction. This could happen
-	 * if the adjusted fuse voltage overlaps with the previous fuse's
-	 * adjusted voltage.
-	 */
-	if (f_high <= f_low || uV_high <= uV_low || f_high <= corner->freq)
-		return corner->uV;
-
-	temp = f_diff * (uV_high - uV_low);
-	do_div(temp, f_high - f_low);
-
-	/*
-	 * max_volt_scale has units of uV/MHz while freq values
-	 * have units of Hz.  Divide by 1000000 to convert to.
-	 */
-	temp_limit = f_diff * fdata->max_volt_scale;
-	do_div(temp_limit, 1000000);
-
-	uV = uV_high - min(temp, temp_limit);
-	return roundup(uV, step_volt);
-}
-
-static unsigned int cpr_get_fuse_corner(struct dev_pm_opp *opp)
-{
-	struct device_node *np;
-	unsigned int fuse_corner = 0;
-
-	np = dev_pm_opp_get_of_node(opp);
-	if (of_property_read_u32(np, "qcom,opp-fuse-level", &fuse_corner))
-		pr_err("%s: missing 'qcom,opp-fuse-level' property\n",
-		       __func__);
-
-	of_node_put(np);
-
-	return fuse_corner;
-}
-
-static unsigned long cpr_get_opp_hz_for_req(struct dev_pm_opp *ref,
-					    struct device *cpu_dev)
-{
-	u64 rate = 0;
-	struct device_node *ref_np;
-	struct device_node *desc_np;
-	struct device_node *child_np = NULL;
-	struct device_node *child_req_np = NULL;
-
-	desc_np = dev_pm_opp_of_get_opp_desc_node(cpu_dev);
-	if (!desc_np)
-		return 0;
-
-	ref_np = dev_pm_opp_get_of_node(ref);
-	if (!ref_np)
-		goto out_ref;
-
-	do {
-		of_node_put(child_req_np);
-		child_np = of_get_next_available_child(desc_np, child_np);
-		child_req_np = of_parse_phandle(child_np, "required-opps", 0);
-	} while (child_np && child_req_np != ref_np);
-
-	if (child_np && child_req_np == ref_np)
-		of_property_read_u64(child_np, "opp-hz", &rate);
-
-	of_node_put(child_req_np);
-	of_node_put(child_np);
-	of_node_put(ref_np);
-out_ref:
-	of_node_put(desc_np);
-
-	return (unsigned long) rate;
-}
-
 static int cpr_corner_init(struct cpr_drv *drv)
 {
 	const struct cpr_desc *desc = drv->desc;
@@ -1143,7 +840,7 @@ static int cpr_corner_init(struct cpr_drv *drv)
 		opp = dev_pm_opp_find_level_exact(&drv->pd.dev, level);
 		if (IS_ERR(opp))
 			return -EINVAL;
-		fc = cpr_get_fuse_corner(opp);
+		fc = cpr_get_fuse_corner(opp, 0);
 		if (!fc) {
 			dev_pm_opp_put(opp);
 			return -EINVAL;
@@ -1219,7 +916,7 @@ static int cpr_corner_init(struct cpr_drv *drv)
 		corner->uV = fuse->uV;
 
 		if (prev_fuse && cdata[i - 1].freq == prev_fuse->max_freq) {
-			scaling = cpr_calculate_scaling(quot_offset, drv,
+			scaling = cpr_calculate_scaling(quot_offset, drv->dev,
 							fdata, corner);
 			if (scaling < 0)
 				return scaling;
@@ -1257,47 +954,6 @@ static int cpr_corner_init(struct cpr_drv *drv)
 	return 0;
 }
 
-static const struct cpr_fuse *cpr_get_fuses(struct cpr_drv *drv)
-{
-	const struct cpr_desc *desc = drv->desc;
-	struct cpr_fuse *fuses;
-	int i;
-
-	fuses = devm_kcalloc(drv->dev, desc->num_fuse_corners,
-			     sizeof(struct cpr_fuse),
-			     GFP_KERNEL);
-	if (!fuses)
-		return ERR_PTR(-ENOMEM);
-
-	for (i = 0; i < desc->num_fuse_corners; i++) {
-		char tbuf[32];
-
-		snprintf(tbuf, 32, "cpr_ring_osc%d", i + 1);
-		fuses[i].ring_osc = devm_kstrdup(drv->dev, tbuf, GFP_KERNEL);
-		if (!fuses[i].ring_osc)
-			return ERR_PTR(-ENOMEM);
-
-		snprintf(tbuf, 32, "cpr_init_voltage%d", i + 1);
-		fuses[i].init_voltage = devm_kstrdup(drv->dev, tbuf,
-						     GFP_KERNEL);
-		if (!fuses[i].init_voltage)
-			return ERR_PTR(-ENOMEM);
-
-		snprintf(tbuf, 32, "cpr_quotient%d", i + 1);
-		fuses[i].quotient = devm_kstrdup(drv->dev, tbuf, GFP_KERNEL);
-		if (!fuses[i].quotient)
-			return ERR_PTR(-ENOMEM);
-
-		snprintf(tbuf, 32, "cpr_quotient_offset%d", i + 1);
-		fuses[i].quotient_offset = devm_kstrdup(drv->dev, tbuf,
-							GFP_KERNEL);
-		if (!fuses[i].quotient_offset)
-			return ERR_PTR(-ENOMEM);
-	}
-
-	return fuses;
-}
-
 static void cpr_set_loop_allowed(struct cpr_drv *drv)
 {
 	drv->loop_disabled = false;
@@ -1329,54 +985,6 @@ static int cpr_init_parameters(struct cpr_drv *drv)
 	return 0;
 }
 
-static int cpr_find_initial_corner(struct cpr_drv *drv)
-{
-	unsigned long rate;
-	const struct corner *end;
-	struct corner *iter;
-	unsigned int i = 0;
-
-	if (!drv->cpu_clk) {
-		dev_err(drv->dev, "cannot get rate from NULL clk\n");
-		return -EINVAL;
-	}
-
-	end = &drv->corners[drv->num_corners - 1];
-	rate = clk_get_rate(drv->cpu_clk);
-
-	/*
-	 * Some bootloaders set a CPU clock frequency that is not defined
-	 * in the OPP table. When running at an unlisted frequency,
-	 * cpufreq_online() will change to the OPP which has the lowest
-	 * frequency, at or above the unlisted frequency.
-	 * Since cpufreq_online() always "rounds up" in the case of an
-	 * unlisted frequency, this function always "rounds down" in case
-	 * of an unlisted frequency. That way, when cpufreq_online()
-	 * triggers the first ever call to cpr_set_performance_state(),
-	 * it will correctly determine the direction as UP.
-	 */
-	for (iter = drv->corners; iter <= end; iter++) {
-		if (iter->freq > rate)
-			break;
-		i++;
-		if (iter->freq == rate) {
-			drv->corner = iter;
-			break;
-		}
-		if (iter->freq < rate)
-			drv->corner = iter;
-	}
-
-	if (!drv->corner) {
-		dev_err(drv->dev, "boot up corner not found\n");
-		return -EINVAL;
-	}
-
-	dev_dbg(drv->dev, "boot up perf state: %u\n", i);
-
-	return 0;
-}
-
 static const struct cpr_desc qcs404_cpr_desc = {
 	.num_fuse_corners = 3,
 	.min_diff_quot = CPR_FUSE_MIN_QUOT_DIFF,
@@ -1564,8 +1172,9 @@ static int cpr_pd_attach_dev(struct generic_pm_domain *domain,
 	if (ret)
 		goto unlock;
 
-	ret = cpr_find_initial_corner(drv);
-	if (ret)
+	ret = cpr_find_initial_corner(drv->dev, drv->cpu_clk, drv->corners,
+				      drv->num_corners);
+	if (ret < 0)
 		goto unlock;
 
 	if (acc_desc->config)
@@ -1650,6 +1259,7 @@ static int cpr_probe(struct platform_device *pdev)
 	struct resource *res;
 	struct device *dev = &pdev->dev;
 	struct cpr_drv *drv;
+	const struct cpr_desc *desc;
 	int irq, ret;
 	const struct cpr_acc_desc *data;
 	struct device_node *np;
@@ -1665,6 +1275,7 @@ static int cpr_probe(struct platform_device *pdev)
 	drv->dev = dev;
 	drv->desc = data->cpr_desc;
 	drv->acc_desc = data->acc_desc;
+	desc = drv->desc;
 
 	drv->fuse_corners = devm_kcalloc(dev, drv->desc->num_fuse_corners,
 					 sizeof(*drv->fuse_corners),
@@ -1705,11 +1316,13 @@ static int cpr_probe(struct platform_device *pdev)
 	if (ret)
 		return ret;
 
-	drv->cpr_fuses = cpr_get_fuses(drv);
+	drv->cpr_fuses = cpr_get_fuses(drv->dev, 0, desc->num_fuse_corners);
 	if (IS_ERR(drv->cpr_fuses))
 		return PTR_ERR(drv->cpr_fuses);
 
-	ret = cpr_populate_ring_osc_idx(drv);
+	ret = cpr_populate_ring_osc_idx(drv->dev, drv->fuse_corners,
+					drv->cpr_fuses,
+					desc->num_fuse_corners);
 	if (ret)
 		return ret;
 
-- 
2.29.2


^ permalink raw reply related	[flat|nested] 25+ messages in thread

* [PATCH 06/13] arm64: qcom: qcs404: Change CPR nvmem-names
  2020-11-26 18:45 [PATCH 00/13] Enable CPRh/3/4, CPU Scaling on various QCOM SoCs AngeloGioacchino Del Regno
                   ` (4 preceding siblings ...)
  2020-11-26 18:45 ` [PATCH 05/13] soc: qcom: cpr: Move common functions to new file AngeloGioacchino Del Regno
@ 2020-11-26 18:45 ` AngeloGioacchino Del Regno
  2020-11-26 18:45 ` [PATCH 07/13] dt-bindings: avs: cpr: Convert binding to YAML schema AngeloGioacchino Del Regno
                   ` (6 subsequent siblings)
  12 siblings, 0 replies; 25+ messages in thread
From: AngeloGioacchino Del Regno @ 2020-11-26 18:45 UTC (permalink / raw)
  To: linux-arm-msm
  Cc: linux-kernel, devicetree, linux-pm, ulf.hansson,
	jorge.ramirez-ortiz, broonie, lgirdwood, daniel.lezcano, nks,
	bjorn.andersson, agross, robh+dt, viresh.kumar, rjw,
	konrad.dybcio, martin.botka, marijn.suijten, phone-devel,
	AngeloGioacchino Del Regno

The CPR driver's common functions were split and put in another
file in order to support newer CPR revisions: to simplify the
commonization, the expected names of the fuses had to be changed
in order for both new and old support to use the same fuse name
retrieval function and keeping the naming consistent.

The thread id was added to the fuse name and, since CPRv1 does
not support threads, it is expected to always read ID 0, which
means that the expected name here is now "cpr0_(fuse_name)"
instead of "cpr_(fuse_name)": luckily, QCS404 is the only user
so change it accordingly.

Signed-off-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@somainline.org>
---
 arch/arm64/boot/dts/qcom/qcs404.dtsi | 26 +++++++++++++-------------
 1 file changed, 13 insertions(+), 13 deletions(-)

diff --git a/arch/arm64/boot/dts/qcom/qcs404.dtsi b/arch/arm64/boot/dts/qcom/qcs404.dtsi
index b654b802e95c..5d5a33c7eb82 100644
--- a/arch/arm64/boot/dts/qcom/qcs404.dtsi
+++ b/arch/arm64/boot/dts/qcom/qcs404.dtsi
@@ -1168,19 +1168,19 @@ cpr: power-controller@b018000 {
 				<&cpr_efuse_ring2>,
 				<&cpr_efuse_ring3>,
 				<&cpr_efuse_revision>;
-			nvmem-cell-names = "cpr_quotient_offset1",
-				"cpr_quotient_offset2",
-				"cpr_quotient_offset3",
-				"cpr_init_voltage1",
-				"cpr_init_voltage2",
-				"cpr_init_voltage3",
-				"cpr_quotient1",
-				"cpr_quotient2",
-				"cpr_quotient3",
-				"cpr_ring_osc1",
-				"cpr_ring_osc2",
-				"cpr_ring_osc3",
-				"cpr_fuse_revision";
+			nvmem-cell-names = "cpr0_quotient_offset1",
+				"cpr0_quotient_offset2",
+				"cpr0_quotient_offset3",
+				"cpr0_init_voltage1",
+				"cpr0_init_voltage2",
+				"cpr0_init_voltage3",
+				"cpr0_quotient1",
+				"cpr0_quotient2",
+				"cpr0_quotient3",
+				"cpr0_ring_osc1",
+				"cpr0_ring_osc2",
+				"cpr0_ring_osc3",
+				"cpr0_fuse_revision";
 		};
 
 		timer@b120000 {
-- 
2.29.2


^ permalink raw reply related	[flat|nested] 25+ messages in thread

* [PATCH 07/13] dt-bindings: avs: cpr: Convert binding to YAML schema
  2020-11-26 18:45 [PATCH 00/13] Enable CPRh/3/4, CPU Scaling on various QCOM SoCs AngeloGioacchino Del Regno
                   ` (5 preceding siblings ...)
  2020-11-26 18:45 ` [PATCH 06/13] arm64: qcom: qcs404: Change CPR nvmem-names AngeloGioacchino Del Regno
@ 2020-11-26 18:45 ` AngeloGioacchino Del Regno
  2020-11-30 17:03   ` Rob Herring
  2020-11-26 18:45 ` [PATCH 08/13] soc: qcom: Add support for Core Power Reduction v3, v4 and Hardened AngeloGioacchino Del Regno
                   ` (5 subsequent siblings)
  12 siblings, 1 reply; 25+ messages in thread
From: AngeloGioacchino Del Regno @ 2020-11-26 18:45 UTC (permalink / raw)
  To: linux-arm-msm
  Cc: linux-kernel, devicetree, linux-pm, ulf.hansson,
	jorge.ramirez-ortiz, broonie, lgirdwood, daniel.lezcano, nks,
	bjorn.andersson, agross, robh+dt, viresh.kumar, rjw,
	konrad.dybcio, martin.botka, marijn.suijten, phone-devel,
	AngeloGioacchino Del Regno

Convert the qcom,cpr.txt document to YAML schema and place it in the
appropriate directory, since commit a7305e684fc moves this driver
from power/avs to soc/qcom, but forgets to move the documentation.

Signed-off-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@somainline.org>
---
 .../bindings/power/avs/qcom,cpr.txt           | 131 +-----------------
 .../bindings/soc/qcom/qcom,cpr.yaml           | 115 +++++++++++++++
 MAINTAINERS                                   |   2 +-
 3 files changed, 117 insertions(+), 131 deletions(-)
 create mode 100644 Documentation/devicetree/bindings/soc/qcom/qcom,cpr.yaml

diff --git a/Documentation/devicetree/bindings/power/avs/qcom,cpr.txt b/Documentation/devicetree/bindings/power/avs/qcom,cpr.txt
index ab0d5ebbad4e..2ada8cd08949 100644
--- a/Documentation/devicetree/bindings/power/avs/qcom,cpr.txt
+++ b/Documentation/devicetree/bindings/power/avs/qcom,cpr.txt
@@ -1,130 +1 @@
-QCOM CPR (Core Power Reduction)
-
-CPR (Core Power Reduction) is a technology to reduce core power on a CPU
-or other device. Each OPP of a device corresponds to a "corner" that has
-a range of valid voltages for a particular frequency. While the device is
-running at a particular frequency, CPR monitors dynamic factors such as
-temperature, etc. and suggests adjustments to the voltage to save power
-and meet silicon characteristic requirements.
-
-- compatible:
-	Usage: required
-	Value type: <string>
-	Definition: should be "qcom,qcs404-cpr", "qcom,cpr" for qcs404
-
-- reg:
-	Usage: required
-	Value type: <prop-encoded-array>
-	Definition: base address and size of the rbcpr register region
-
-- interrupts:
-	Usage: required
-	Value type: <prop-encoded-array>
-	Definition: should specify the CPR interrupt
-
-- clocks:
-	Usage: required
-	Value type: <prop-encoded-array>
-	Definition: phandle to the reference clock
-
-- clock-names:
-	Usage: required
-	Value type: <stringlist>
-	Definition: must be "ref"
-
-- vdd-apc-supply:
-	Usage: required
-	Value type: <phandle>
-	Definition: phandle to the vdd-apc-supply regulator
-
-- #power-domain-cells:
-	Usage: required
-	Value type: <u32>
-	Definition: should be 0
-
-- operating-points-v2:
-	Usage: required
-	Value type: <phandle>
-	Definition: A phandle to the OPP table containing the
-		    performance states supported by the CPR
-		    power domain
-
-- acc-syscon:
-	Usage: optional
-	Value type: <phandle>
-	Definition: phandle to syscon for writing ACC settings
-
-- nvmem-cells:
-	Usage: required
-	Value type: <phandle>
-	Definition: phandle to nvmem cells containing the data
-		    that makes up a fuse corner, for each fuse corner.
-		    As well as the CPR fuse revision.
-
-- nvmem-cell-names:
-	Usage: required
-	Value type: <stringlist>
-	Definition: should be "cpr_quotient_offset1", "cpr_quotient_offset2",
-		    "cpr_quotient_offset3", "cpr_init_voltage1",
-		    "cpr_init_voltage2", "cpr_init_voltage3", "cpr_quotient1",
-		    "cpr_quotient2", "cpr_quotient3", "cpr_ring_osc1",
-		    "cpr_ring_osc2", "cpr_ring_osc3", "cpr_fuse_revision"
-		    for qcs404.
-
-Example:
-
-	cpr_opp_table: cpr-opp-table {
-		compatible = "operating-points-v2-qcom-level";
-
-		cpr_opp1: opp1 {
-			opp-level = <1>;
-			qcom,opp-fuse-level = <1>;
-		};
-		cpr_opp2: opp2 {
-			opp-level = <2>;
-			qcom,opp-fuse-level = <2>;
-		};
-		cpr_opp3: opp3 {
-			opp-level = <3>;
-			qcom,opp-fuse-level = <3>;
-		};
-	};
-
-	power-controller@b018000 {
-		compatible = "qcom,qcs404-cpr", "qcom,cpr";
-		reg = <0x0b018000 0x1000>;
-		interrupts = <0 15 IRQ_TYPE_EDGE_RISING>;
-		clocks = <&xo_board>;
-		clock-names = "ref";
-		vdd-apc-supply = <&pms405_s3>;
-		#power-domain-cells = <0>;
-		operating-points-v2 = <&cpr_opp_table>;
-		acc-syscon = <&tcsr>;
-
-		nvmem-cells = <&cpr_efuse_quot_offset1>,
-			<&cpr_efuse_quot_offset2>,
-			<&cpr_efuse_quot_offset3>,
-			<&cpr_efuse_init_voltage1>,
-			<&cpr_efuse_init_voltage2>,
-			<&cpr_efuse_init_voltage3>,
-			<&cpr_efuse_quot1>,
-			<&cpr_efuse_quot2>,
-			<&cpr_efuse_quot3>,
-			<&cpr_efuse_ring1>,
-			<&cpr_efuse_ring2>,
-			<&cpr_efuse_ring3>,
-			<&cpr_efuse_revision>;
-		nvmem-cell-names = "cpr_quotient_offset1",
-			"cpr_quotient_offset2",
-			"cpr_quotient_offset3",
-			"cpr_init_voltage1",
-			"cpr_init_voltage2",
-			"cpr_init_voltage3",
-			"cpr_quotient1",
-			"cpr_quotient2",
-			"cpr_quotient3",
-			"cpr_ring_osc1",
-			"cpr_ring_osc2",
-			"cpr_ring_osc3",
-			"cpr_fuse_revision";
-	};
+This file has been moved to ../../soc/qcom/qcom,cpr.yaml
diff --git a/Documentation/devicetree/bindings/soc/qcom/qcom,cpr.yaml b/Documentation/devicetree/bindings/soc/qcom/qcom,cpr.yaml
new file mode 100644
index 000000000000..d8b6c623a66b
--- /dev/null
+++ b/Documentation/devicetree/bindings/soc/qcom/qcom,cpr.yaml
@@ -0,0 +1,115 @@
+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: "http://devicetree.org/schemas/soc/qcom/qcom,cpr.yaml#"
+$schema: "http://devicetree.org/meta-schemas/core.yaml#"
+
+title: Qualcomm Core Power Reduction (CPR)
+
+description: |
+  CPR (Core Power Reduction) is a technology to reduce core power on a CPU
+  or other device. Each OPP of a device corresponds to a "corner" that has
+  a range of valid voltages for a particular frequency. While the device is
+  running at a particular frequency, CPR monitors dynamic factors such as
+  temperature, etc. and suggests adjustments to the voltage to save power
+  and meet silicon characteristic requirements.
+
+maintainers:
+  - Niklas Cassel <nks@flawful.org>
+
+properties:
+  compatible:
+    items:
+      - enum:
+          - qcom,qcs404-cpr
+      - const: qcom,cpr
+
+  reg:
+    description: Base address and size of the RBCPR register region
+    maxItems: 1
+
+  interrupts:
+    maxItems: 1
+
+  clock-names:
+    - const: ref
+
+  clocks:
+    items:
+      - description: CPR reference clock
+
+  vdd-apc-supply:
+    description: Autonomous Phase Control (APC) power supply
+
+  '#power-domain-cells':
+    const: 0
+
+  acc-syscon:
+    description: phandle to syscon for writing ACC settings
+
+  nvmem-cells:
+    minItems: 9
+    description: Cells containing the fuse corners and revision data
+
+  nvmem-cell-names:
+    minItems: 9
+
+  operating-points-v2: true
+
+required:
+  - compatible
+  - reg
+  - interrupts
+  - clock-names
+  - clocks
+  - vdd-apc-supply
+  - "#power-domain-cells"
+  - nvmem-cells
+  - nvmem-cell-names
+  - operating-points-v2
+
+additionalProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/interrupt-controller/irq.h>
+
+    power-controller@b018000 {
+        compatible = "qcom,qcs404-cpr", "qcom,cpr";
+        reg = <0x0b018000 0x1000>;
+        interrupts = <0 15 IRQ_TYPE_EDGE_RISING>;
+        clocks = <&xo_board>;
+        clock-names = "ref";
+        vdd-apc-supply = <&pms405_s3>;
+        #power-domain-cells = <0>;
+        operating-points-v2 = <&cpr_opp_table>;
+        acc-syscon = <&tcsr>;
+
+        nvmem-cells = <&cpr_efuse_quot_offset1>,
+                      <&cpr_efuse_quot_offset2>,
+                      <&cpr_efuse_quot_offset3>,
+                      <&cpr_efuse_init_voltage1>,
+                      <&cpr_efuse_init_voltage2>,
+                      <&cpr_efuse_init_voltage3>,
+                      <&cpr_efuse_quot1>,
+                      <&cpr_efuse_quot2>,
+                      <&cpr_efuse_quot3>,
+                      <&cpr_efuse_ring1>,
+                      <&cpr_efuse_ring2>,
+                      <&cpr_efuse_ring3>,
+                      <&cpr_efuse_revision>;
+        nvmem-cell-names = "cpr_quotient_offset1",
+                           "cpr_quotient_offset2",
+                           "cpr_quotient_offset3",
+                           "cpr_init_voltage1",
+                           "cpr_init_voltage2",
+                           "cpr_init_voltage3",
+                           "cpr_quotient1",
+                           "cpr_quotient2",
+                           "cpr_quotient3",
+                           "cpr_ring_osc1",
+                           "cpr_ring_osc2",
+                           "cpr_ring_osc3",
+                           "cpr_fuse_revision";
+    };
+...
diff --git a/MAINTAINERS b/MAINTAINERS
index 3da6d8c154e4..c8c006f08dcc 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -14458,7 +14458,7 @@ M:	Niklas Cassel <nks@flawful.org>
 L:	linux-pm@vger.kernel.org
 L:	linux-arm-msm@vger.kernel.org
 S:	Maintained
-F:	Documentation/devicetree/bindings/power/avs/qcom,cpr.txt
+F:	Documentation/devicetree/bindings/soc/qcom/qcom,cpr.yaml
 F:	drivers/soc/qcom/cpr.c
 
 QUALCOMM CPUFREQ DRIVER MSM8996/APQ8096
-- 
2.29.2


^ permalink raw reply related	[flat|nested] 25+ messages in thread

* [PATCH 08/13] soc: qcom: Add support for Core Power Reduction v3, v4 and Hardened
  2020-11-26 18:45 [PATCH 00/13] Enable CPRh/3/4, CPU Scaling on various QCOM SoCs AngeloGioacchino Del Regno
                   ` (6 preceding siblings ...)
  2020-11-26 18:45 ` [PATCH 07/13] dt-bindings: avs: cpr: Convert binding to YAML schema AngeloGioacchino Del Regno
@ 2020-11-26 18:45 ` AngeloGioacchino Del Regno
  2020-11-26 18:45 ` [PATCH 09/13] MAINTAINERS: Add entry for Qualcomm CPRv3/v4/Hardened driver AngeloGioacchino Del Regno
                   ` (4 subsequent siblings)
  12 siblings, 0 replies; 25+ messages in thread
From: AngeloGioacchino Del Regno @ 2020-11-26 18:45 UTC (permalink / raw)
  To: linux-arm-msm
  Cc: linux-kernel, devicetree, linux-pm, ulf.hansson,
	jorge.ramirez-ortiz, broonie, lgirdwood, daniel.lezcano, nks,
	bjorn.andersson, agross, robh+dt, viresh.kumar, rjw,
	konrad.dybcio, martin.botka, marijn.suijten, phone-devel,
	AngeloGioacchino Del Regno

This commit introduces a new driver, based on the one for cpr v1,
to enable support for the newer Qualcomm Core Power Reduction
hardware, known downstream as CPR3, CPR4 and CPRh.

In these new versions of the hardware, support for various new
features was introduced, including voltage reduction for the GPU,
security hardening and a new way of controlling CPU DVFS,
consisting in internal communication between microcontrollers,
specifically the CPR-Hardened and the Operating State Manager.

The CPR v3, v4 and CPRh are present in a broad range of SoCs,
from the mid-range to the high end ones including, but not limited
to, MSM8953/8996/8998, SDM630/636/660/845.

Signed-off-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@somainline.org>
---
 drivers/soc/qcom/Kconfig  |   17 +
 drivers/soc/qcom/Makefile |    1 +
 drivers/soc/qcom/cpr3.c   | 2474 +++++++++++++++++++++++++++++++++++++
 3 files changed, 2492 insertions(+)
 create mode 100644 drivers/soc/qcom/cpr3.c

diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig
index 37f22dcd20af..9d3f35fc4ab1 100644
--- a/drivers/soc/qcom/Kconfig
+++ b/drivers/soc/qcom/Kconfig
@@ -42,6 +42,23 @@ config QCOM_CPR
 	  To compile this driver as a module, choose M here: the module will
 	  be called qcom-cpr
 
+config QCOM_CPR3
+	tristate "QCOM Core Power Reduction (CPR v3/v4/Hardened) support"
+	depends on ARCH_QCOM && HAS_IOMEM
+	select PM_OPP
+	select REGMAP
+	help
+	  Say Y here to enable support for the CPR hardware found on a broad
+	  variety of Qualcomm SoCs like MSM8996, MSM8998, SDM630, SDM660,
+	  SDM845 and others.
+
+	  This driver populates OPP tables and makes adjustments to them
+	  based on feedback from the CPR hardware. If you want to do CPU
+	  and/or GPU frequency scaling say Y here.
+
+	  To compile this driver as a module, choose M here: the module will
+	  be called qcom-cpr3
+
 config QCOM_GENI_SE
 	tristate "QCOM GENI Serial Engine Driver"
 	depends on ARCH_QCOM || COMPILE_TEST
diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile
index 778a2a5f07bb..0500283c2dc5 100644
--- a/drivers/soc/qcom/Makefile
+++ b/drivers/soc/qcom/Makefile
@@ -4,6 +4,7 @@ obj-$(CONFIG_QCOM_AOSS_QMP) +=	qcom_aoss.o
 obj-$(CONFIG_QCOM_GENI_SE) +=	qcom-geni-se.o
 obj-$(CONFIG_QCOM_COMMAND_DB) += cmd-db.o
 obj-$(CONFIG_QCOM_CPR)		+= cpr-common.o cpr.o
+obj-$(CONFIG_QCOM_CPR3)		+= cpr-common.o cpr3.o
 obj-$(CONFIG_QCOM_GSBI)	+=	qcom_gsbi.o
 obj-$(CONFIG_QCOM_MDT_LOADER)	+= mdt_loader.o
 obj-$(CONFIG_QCOM_OCMEM)	+= ocmem.o
diff --git a/drivers/soc/qcom/cpr3.c b/drivers/soc/qcom/cpr3.c
new file mode 100644
index 000000000000..a7e83ef8f478
--- /dev/null
+++ b/drivers/soc/qcom/cpr3.c
@@ -0,0 +1,2474 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2013-2020, The Linux Foundation. All rights reserved.
+ * Copyright (c) 2019 Linaro Limited
+ * Copyright (c) 2020, AngeloGioacchino Del Regno
+ *                     <angelogioacchino.delregno@somainline.org>
+ */
+
+#include <linux/module.h>
+#include <linux/err.h>
+#include <linux/debugfs.h>
+#include <linux/string.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/bitops.h>
+#include <linux/slab.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/pm_domain.h>
+#include <linux/pm_opp.h>
+#include <linux/interrupt.h>
+#include <linux/regmap.h>
+#include <linux/mfd/syscon.h>
+#include <linux/regulator/consumer.h>
+#include <linux/workqueue.h>
+#include <linux/clk.h>
+#include <linux/nvmem-consumer.h>
+#include "cpr-common.h"
+
+#define CPR3_RO_COUNT				16
+#define CPR3_RO_MASK				GENMASK(CPR3_RO_COUNT - 1, 0)
+
+/* CPR3 registers */
+#define CPR3_REG_CPR_VERSION			0x0
+#define CPRH_CPR_VERSION_4P5			0x40050000
+
+#define CPR3_REG_CPR_CTL			0x4
+#define CPR3_CPR_CTL_LOOP_EN_MASK		BIT(0)
+#define CPR3_CPR_CTL_IDLE_CLOCKS_MASK		GENMASK(4, 0)
+#define CPR3_CPR_CTL_IDLE_CLOCKS_SHIFT		1
+#define CPR3_CPR_CTL_COUNT_MODE_MASK		GENMASK(1, 0)
+#define CPR3_CPR_CTL_COUNT_MODE_SHIFT		6
+#define CPR3_CPR_CTL_COUNT_MODE_ALL_AT_ONCE_MIN	0
+#define CPR3_CPR_CTL_COUNT_MODE_ALL_AT_ONCE_MAX	1
+#define CPR3_CPR_CTL_COUNT_MODE_STAGGERED	2
+#define CPR3_CPR_CTL_COUNT_MODE_ALL_AT_ONCE_AGE	3
+#define CPR3_CPR_CTL_COUNT_REPEAT_MASK		GENMASK(22, 0)
+#define CPR3_CPR_CTL_COUNT_REPEAT_SHIFT		9
+
+#define CPR3_REG_CPR_STATUS			0x8
+#define CPR3_CPR_STATUS_BUSY_MASK		BIT(0)
+
+/*
+ * This register is not present on controllers that support HW closed-loop
+ * except CPR4 APSS controller.
+ */
+#define CPR3_REG_CPR_TIMER_AUTO_CONT		0xC
+
+#define CPR3_REG_CPR_STEP_QUOT			0x14
+#define CPR3_CPR_STEP_QUOT_MIN_MASK		GENMASK(5, 0)
+#define CPR3_CPR_STEP_QUOT_MIN_SHIFT		0
+#define CPR3_CPR_STEP_QUOT_MAX_MASK		GENMASK(5, 0)
+#define CPR3_CPR_STEP_QUOT_MAX_SHIFT		6
+#define CPRH_DELTA_QUOT_STEP_FACTOR		4
+
+#define CPR3_REG_GCNT(ro)			(0xA0 + 0x4 * (ro))
+#define CPR3_REG_SENSOR_OWNER(sensor)		(0x200 + 0x4 * (sensor))
+
+#define CPR3_REG_CONT_CMD			0x800
+#define CPR3_CONT_CMD_ACK			0x1
+#define CPR3_CONT_CMD_NACK			0x0
+
+#define CPR3_REG_THRESH(thread)			(0x808 + 0x440 * (thread))
+#define CPR3_THRESH_CONS_DOWN_MASK		GENMASK(3, 0)
+#define CPR3_THRESH_CONS_DOWN_SHIFT		0
+#define CPR3_THRESH_CONS_UP_MASK		GENMASK(3, 0)
+#define CPR3_THRESH_CONS_UP_SHIFT		4
+#define CPR3_THRESH_DOWN_THRESH_MASK		GENMASK(4, 0)
+#define CPR3_THRESH_DOWN_THRESH_SHIFT		8
+#define CPR3_THRESH_UP_THRESH_MASK		GENMASK(4, 0)
+#define CPR3_THRESH_UP_THRESH_SHIFT		13
+
+#define CPR3_REG_RO_MASK(thread)		(0x80C + 0x440 * (thread))
+
+#define CPR3_REG_RESULT0(thread)		(0x810 + 0x440 * (thread))
+#define CPR3_RESULT0_BUSY_MASK			BIT(0)
+#define CPR3_RESULT0_STEP_DN_MASK		BIT(1)
+#define CPR3_RESULT0_STEP_UP_MASK		BIT(2)
+#define CPR3_RESULT0_ERROR_STEPS_MASK		GENMASK(4, 0)
+#define CPR3_RESULT0_ERROR_STEPS_SHIFT		3
+#define CPR3_RESULT0_ERROR_MASK			GENMASK(11, 0)
+#define CPR3_RESULT0_ERROR_SHIFT		8
+
+#define CPR3_REG_RESULT1(thread)		(0x814 + 0x440 * (thread))
+#define CPR3_RESULT1_QUOT_MIN_MASK		GENMASK(11, 0)
+#define CPR3_RESULT1_QUOT_MIN_SHIFT		0
+#define CPR3_RESULT1_QUOT_MAX_MASK		GENMASK(11, 0)
+#define CPR3_RESULT1_QUOT_MAX_SHIFT		12
+#define CPR3_RESULT1_RO_MIN_MASK		GENMASK(3, 0)
+#define CPR3_RESULT1_RO_MIN_SHIFT		24
+#define CPR3_RESULT1_RO_MAX_MASK		GENMASK(3, 0)
+#define CPR3_RESULT1_RO_MAX_SHIFT		28
+
+#define CPR3_REG_RESULT2(thread)		(0x818 + 0x440 * (thread))
+#define CPR3_RESULT2_STEP_QUOT_MIN_MASK		GENMASK(5, 0)
+#define CPR3_RESULT2_STEP_QUOT_MIN_SHIFT	0
+#define CPR3_RESULT2_STEP_QUOT_MAX_MASK		GENMASK(5, 0)
+#define CPR3_RESULT2_STEP_QUOT_MAX_SHIFT	6
+#define CPR3_RESULT2_SENSOR_MIN_MASK		GENMASK(7, 0)
+#define CPR3_RESULT2_SENSOR_MIN_SHIFT		16
+#define CPR3_RESULT2_SENSOR_MAX_MASK		GENMASK(7, 0)
+#define CPR3_RESULT2_SENSOR_MAX_SHIFT		24
+
+#define CPR3_REG_IRQ_EN				0x81C
+#define CPR3_REG_IRQ_CLEAR			0x820
+#define CPR3_REG_IRQ_STATUS			0x824
+#define CPR3_IRQ_UP				BIT(3)
+#define CPR3_IRQ_MID				BIT(2)
+#define CPR3_IRQ_DOWN				BIT(1)
+#define CPR3_IRQ_ALL				(CPR3_IRQ_UP | CPR3_IRQ_MID | CPR3_IRQ_DOWN)
+
+#define CPR3_REG_TARGET_QUOT(thread, ro)	(0x840 + 0x440 * (thread) + 0x4 * (ro))
+
+/* Registers found only on controllers that support HW closed-loop. */
+#define CPR3_REG_PD_THROTTLE			0xE8
+
+#define CPR3_REG_HW_CLOSED_LOOP_DISABLED	0x3000
+#define CPR3_REG_CPR_TIMER_MID_CONT		0x3004
+#define CPR3_REG_CPR_TIMER_UP_DN_CONT		0x3008
+
+/* CPR4 controller specific registers and bit definitions */
+#define CPR4_REG_CPR_TIMER_CLAMP			0x10
+#define CPR4_CPR_TIMER_CLAMP_THREAD_AGGREGATION_EN	BIT(27)
+
+#define CPR4_REG_MISC				0x700
+#define CPR4_MISC_RESET_STEP_QUOT_LOOP_EN	BIT(2)
+#define CPR4_MISC_THREAD_HAS_ALWAYS_VOTE_EN	BIT(3)
+
+#define CPR4_REG_SAW_ERROR_STEP_LIMIT		0x7A4
+#define CPR4_SAW_ERROR_STEP_LIMIT_UP_MASK	GENMASK(4, 0)
+#define CPR4_SAW_ERROR_STEP_LIMIT_UP_SHIFT	0
+#define CPR4_SAW_ERROR_STEP_LIMIT_DN_MASK	GENMASK(4, 0)
+#define CPR4_SAW_ERROR_STEP_LIMIT_DN_SHIFT	5
+
+#define CPR4_REG_MARGIN_TEMP_CORE_TIMERS			0x7A8
+#define CPR4_MARGIN_TEMP_CORE_TIMERS_SETTLE_VOLTAGE_COUNT_MASK	GENMASK(10, 0)
+#define CPR4_MARGIN_TEMP_CORE_TIMERS_SETTLE_VOLTAGE_COUNT_SHFT	18
+
+#define CPR4_REG_MARGIN_ADJ_CTL				0x7F8
+#define CPR4_MARGIN_ADJ_HW_CLOSED_LOOP_EN		BIT(4)
+#define CPR4_MARGIN_ADJ_PER_RO_KV_MARGIN_EN		BIT(7)
+#define CPR4_MARGIN_ADJ_PMIC_STEP_SIZE_MASK		GENMASK(4, 0)
+#define CPR4_MARGIN_ADJ_PMIC_STEP_SIZE_SHIFT		12
+
+#define CPR4_REG_CPR_MASK_THREAD(thread)		(0x80C + 0x440 * (thread))
+#define CPR4_CPR_MASK_THREAD_DISABLE_THREAD		BIT(31)
+#define CPR4_CPR_MASK_THREAD_RO_MASK4THREAD_MASK	GENMASK(15, 0)
+
+/* CPRh controller specific registers and bit definitions */
+#define __CPRH_REG_CORNER(rbase, tbase, tid, cnum) (rbase + (tbase * tid) + \
+						    (0x4 * cnum))
+#define CPRH_REG_CORNER(d, t, c) __CPRH_REG_CORNER(d->reg_corner,     \
+						   d->reg_corner_tid, \
+						   t, c);
+
+#define CPRH_CTL_OSM_ENABLED			BIT(0)
+#define CPRH_CTL_BASE_VOLTAGE_MASK		GENMASK(10, 1)
+#define CPRH_CTL_BASE_VOLTAGE_SHIFT		1
+#define CPRH_CTL_MODE_SWITCH_DELAY_MASK		GENMASK(24, 17)
+#define CPRH_CTL_MODE_SWITCH_DELAY_SHIFT	17
+#define CPRH_CTL_VOLTAGE_MULTIPLIER_MASK	GENMASK(28, 25)
+#define CPRH_CTL_VOLTAGE_MULTIPLIER_SHIFT	25
+
+#define CPRH_CORNER_INIT_VOLTAGE_MASK		GENMASK(7, 0)
+#define CPRH_CORNER_INIT_VOLTAGE_SHIFT		0
+#define CPRH_CORNER_FLOOR_VOLTAGE_MASK		GENMASK(15, 8)
+#define CPRH_CORNER_FLOOR_VOLTAGE_SHIFT		8
+#define CPRH_CORNER_QUOT_DELTA_MASK		GENMASK(24, 16)
+#define CPRH_CORNER_QUOT_DELTA_SHIFT		16
+#define CPRH_CORNER_RO_SEL_MASK			GENMASK(28, 25)
+#define CPRH_CORNER_RO_SEL_SHIFT		25
+#define CPRH_CORNER_CPR_CL_DISABLE		BIT(29)
+
+#define CPRH_CORNER_INIT_VOLTAGE_MAX_VALUE	255
+#define CPRH_CORNER_FLOOR_VOLTAGE_MAX_VALUE	255
+#define CPRH_CORNER_QUOT_DELTA_MAX_VALUE	511
+
+enum cpr_type {
+	CTRL_TYPE_CPR3,
+	CTRL_TYPE_CPR4,
+	CTRL_TYPE_CPRH,
+	CTRL_TYPE_MAX,
+};
+
+/*
+ * struct cpr_thread_desc - CPR Thread-specific parameters
+ *
+ * @controller_id:      Identifier of the CPR controller expected by the HW
+ * @ro_scaling_factor:  Scaling factor for each ring oscillator entry
+ * @hw_tid:             Identifier of the CPR thread expected by the HW
+ * @init_voltage_step:  Voltage in uV for each step read from the fuse array
+ * @init_voltage_width: Bitwidth of the voltage read from the fuse array
+ * @sensor_range_start: First sensor ID used by a thread
+ * @sensor_range_end:   Last sensor ID used by a thread
+ * @num_fuse_corners:   Number of valid entries in fuse_corner_data
+ * @fuse_corner_data:   Parameters for calculation of each fuse corner
+ */
+struct cpr_thread_desc {
+	int		controller_id;
+	int		hw_tid;
+	const int	*ro_scaling_factor;
+	int		init_voltage_step;
+	int		init_voltage_width;
+	int		sensor_range_start;
+	int		sensor_range_end;
+	unsigned int	num_fuse_corners;
+	struct fuse_corner_data *fuse_corner_data;
+};
+
+/*
+ * struct cpr_desc - Driver instance-wide CPR parameters
+ *
+ * @cpr_type:              Type (base version) of the CPR controller
+ * @num_threads:           Max. number of threads supported by this controller
+ * @timer_delay_us:        Loop delay time in uS
+ * @timer_updn_delay_us:   Voltage after-up/before-down delay time in uS
+ * @timer_cons_up:         Wait between consecutive up requests in uS
+ * @timer_cons_down:       Wait between consecutive down requests in uS
+ * @up_threshold:          Generic corner up threshold
+ * @down_threshold:        Generic corner down threshold
+ * @idle_clocks:           CPR Sensor: idle timer in cpr clocks unit
+ * @count_mode:            CPR Sensor: counting mode
+ * @count_repeat:          CPR Sensor: number of times to repeat reading
+ * @step_quot_init_min:    Minimum achievable step quotient
+ * @step_quot_init_max:    Maximum achievable step quotient
+ * @gcnt_us:               CPR measurement interval in uS
+ * @vreg_step_fixed:       Regulator voltage per step (if vreg unusable)
+ * @vreg_step_up_limit:    Num. of steps up at once before re-measuring sensors
+ * @vreg_step_down_limit:  Num. of steps dn at once before re-measuring sensors
+ * @vdd_settle_time_us:    Settling timer to account for one VDD supply step
+ * @corner_settle_time_us: Settle time for corner switch request
+ * @apm_threshold:         Array Power Mux (APM) voltage threshold
+ * @apm_hysteresis:        Hysteresis for APM V-threshold related calculations
+ * @cpr_base_voltage:      Safety: Absolute minimum voltage (uV) on this CPR
+ * @cpr_max_voltage:       Safety: Absolute maximum voltage (uV) on this CPR
+ * @pd_throttle_val:       CPR Power Domain throttle during voltage switch
+ * @threads:               Array containing "CPR Thread" specific parameters
+ * @reduce_to_fuse_uV:     Reduce corner max volts (if higher) to fuse ceiling
+ * @reduce_to_corner_uV:   Reduce corner max volts (if higher) to corner ceil.
+ * @hw_closed_loop_en:     Enable CPR HW Closed-Loop voltage auto-adjustment
+ */
+struct cpr_desc {
+	enum cpr_type		cpr_type;
+	unsigned int		num_threads;
+	unsigned int		timer_delay_us;
+	unsigned int		timer_updn_delay_us;
+	unsigned int		timer_cons_up;
+	unsigned int		timer_cons_down;
+	unsigned int		up_threshold;
+	unsigned int		down_threshold;
+	unsigned int		idle_clocks;
+	unsigned int		count_mode;
+	unsigned int		count_repeat;
+	unsigned int		step_quot_init_min;
+	unsigned int		step_quot_init_max;
+	unsigned int		gcnt_us;
+	unsigned int		vreg_step_fixed;
+	unsigned int		vreg_step_up_limit;
+	unsigned int		vreg_step_down_limit;
+	unsigned int		vdd_settle_time_us;
+	unsigned int		corner_settle_time_us;
+	int			apm_threshold;
+	int			apm_hysteresis;
+	u32			cpr_base_voltage;
+	u32			cpr_max_voltage;
+	u32			pd_throttle_val;
+
+	const struct cpr_thread_desc **threads;
+	bool reduce_to_fuse_uV;
+	bool reduce_to_corner_uV;
+	bool hw_closed_loop_en;
+};
+
+struct cpr_drv;
+struct cpr_thread {
+	int			num_corners;
+	int			id;
+	bool			enabled;
+	void __iomem		*base;
+	struct clk		*cpu_clk;
+	struct corner		*corner;
+	struct corner		*corners;
+	struct fuse_corner	*fuse_corners;
+	struct cpr_drv		*drv;
+	struct generic_pm_domain pd;
+	struct device		*attached_cpu_dev;
+	struct work_struct	restart_work;
+	bool			restarting;
+
+	const struct cpr_fuse	*cpr_fuses;
+	const struct cpr_thread_desc *desc;
+};
+
+struct cpr_drv {
+	int			num_threads;
+	int			irq;
+	unsigned int		ref_clk_khz;
+	struct device		*dev;
+	struct mutex		lock;
+	struct regulator	*vreg;
+	struct regmap		*tcsr;
+	u32			gcnt;
+	u32			speed_bin;
+	u32			fusing_rev;
+	u32			last_uV;
+	u32			cpr_hw_rev;
+	u32			reg_corner;
+	u32			reg_corner_tid;
+	u32			reg_ctl;
+	u32			reg_status;
+	int			fuse_level_set;
+	int			extra_corners;
+	unsigned int		vreg_step;
+	bool			enabled;
+
+	struct cpr_thread	*threads;
+	struct genpd_onecell_data cell_data;
+
+	const struct cpr_desc *desc;
+	const struct acc_desc *acc_desc;
+	struct dentry *debugfs;
+};
+
+static void cpr_write(struct cpr_thread *thread, u32 offset, u32 value)
+{
+	writel_relaxed(value, thread->base + offset);
+}
+
+static u32 cpr_read(struct cpr_thread *thread, u32 offset)
+{
+	return readl_relaxed(thread->base + offset);
+}
+
+static void
+cpr_masked_write(struct cpr_thread *thread, u32 offset, u32 mask, u32 value)
+{
+	u32 val;
+
+	val = readl_relaxed(thread->base + offset);
+	val &= ~mask;
+	val |= value & mask;
+	writel_relaxed(val, thread->base + offset);
+}
+
+static void cpr_irq_clr(struct cpr_thread *thread)
+{
+	cpr_write(thread, CPR3_REG_IRQ_CLEAR, CPR3_IRQ_ALL);
+}
+
+static void cpr_irq_clr_nack(struct cpr_thread *thread)
+{
+	cpr_irq_clr(thread);
+	cpr_write(thread, CPR3_REG_CONT_CMD, CPR3_CONT_CMD_NACK);
+}
+
+static void cpr_irq_clr_ack(struct cpr_thread *thread)
+{
+	cpr_irq_clr(thread);
+	cpr_write(thread, CPR3_REG_CONT_CMD, CPR3_CONT_CMD_ACK);
+}
+
+static void cpr_irq_set(struct cpr_thread *thread, u32 int_bits)
+{
+	/* On CPR-hardened, interrupts are managed by and on firmware */
+	if (thread->drv->desc->cpr_type == CTRL_TYPE_CPRH)
+		return;
+
+	cpr_write(thread, CPR3_REG_IRQ_EN, int_bits);
+}
+
+/**
+ * cpr_ctl_enable - Enable CPR thread
+ * @thread:     Structure holding CPR thread-specific parameters
+ */
+static void cpr_ctl_enable(struct cpr_thread *thread)
+{
+	if (thread->drv->enabled && !thread->restarting)
+		cpr_masked_write(thread, CPR3_REG_CPR_CTL,
+				CPR3_CPR_CTL_LOOP_EN_MASK,
+				CPR3_CPR_CTL_LOOP_EN_MASK);
+}
+
+/**
+ * cpr_ctl_disable - Disable CPR thread
+ * @thread:     Structure holding CPR thread-specific parameters
+ */
+static void cpr_ctl_disable(struct cpr_thread *thread)
+{
+	const struct cpr_desc *desc = thread->drv->desc;
+
+	if (desc->cpr_type != CTRL_TYPE_CPRH) {
+		cpr_irq_set(thread, 0);
+		cpr_irq_clr(thread);
+	}
+
+	cpr_masked_write(thread, CPR3_REG_CPR_CTL,
+			 CPR3_CPR_CTL_LOOP_EN_MASK, 0);
+}
+
+/**
+ * cpr_ctl_is_enabled - Check if thread is enabled
+ * @thread:     Structure holding CPR thread-specific parameters
+ *
+ * Returns: true if the CPR is enabled, false if it is disabled.
+ */
+static bool cpr_ctl_is_enabled(struct cpr_thread *thread)
+{
+	u32 reg_val;
+
+	reg_val = cpr_read(thread, CPR3_REG_CPR_CTL);
+	return reg_val & CPR3_CPR_CTL_LOOP_EN_MASK;
+}
+
+/**
+ * cpr_check_any_thread_busy - Check if HW is done processing
+ * @thread:     Structure holding CPR thread-specific parameters
+ *
+ * Returns: true if the CPR is busy, false if it is ready.
+ */
+static bool cpr_check_any_thread_busy(struct cpr_thread *thread)
+{
+	int i;
+
+	for (i = 0; i < thread->drv->num_threads; i++)
+		if (cpr_read(thread, CPR3_REG_RESULT0(i)) &
+		    CPR3_RESULT0_BUSY_MASK)
+			return true;
+
+	return false;
+}
+
+static void cpr_restart_worker(struct work_struct *work)
+{
+	struct cpr_thread *thread = container_of(work, struct cpr_thread,
+						 restart_work);
+	struct cpr_drv *drv = thread->drv;
+	int i;
+
+	mutex_lock(&drv->lock);
+
+	thread->restarting = true;
+	cpr_ctl_disable(thread);
+	disable_irq(drv->irq);
+
+	mutex_unlock(&drv->lock);
+
+	for (i = 0; i < 20; i++) {
+		u32 cpr_status = cpr_read(thread, CPR3_REG_CPR_STATUS);
+		u32 ctl = cpr_read(thread, CPR3_REG_CPR_CTL);
+
+		if ((cpr_status & CPR3_CPR_STATUS_BUSY_MASK) &&
+		   !(ctl & CPR3_CPR_CTL_LOOP_EN_MASK))
+			break;
+
+		udelay(10);
+	}
+
+	cpr_irq_clr(thread);
+
+	for (i = 0; i < 20; i++) {
+		u32 status = cpr_read(thread, CPR3_REG_IRQ_STATUS);
+
+		if (!(status & CPR3_IRQ_ALL))
+			break;
+		udelay(10);
+	}
+
+	mutex_lock(&drv->lock);
+
+	thread->restarting = false;
+	enable_irq(drv->irq);
+	cpr_ctl_enable(thread);
+
+	mutex_unlock(&drv->lock);
+}
+
+/**
+ * cpr_corner_restore - Restore saved corner level
+ * @thread: Structure holding CPR thread-specific parameters
+ * @corner: Structure holding the saved corner level
+ */
+static void cpr_corner_restore(struct cpr_thread *thread,
+			       struct corner *corner)
+{
+	struct cpr_drv *drv = thread->drv;
+	struct fuse_corner *fuse = corner->fuse_corner;
+	const struct cpr_thread_desc *tdesc = thread->desc;
+	u32 ro_sel = fuse->ring_osc_idx;
+
+	cpr_write(thread, CPR3_REG_GCNT(ro_sel), drv->gcnt);
+
+	cpr_write(thread, CPR3_REG_RO_MASK(tdesc->hw_tid),
+		  CPR3_RO_MASK & ~BIT(ro_sel));
+
+	cpr_write(thread, CPR3_REG_TARGET_QUOT(tdesc->hw_tid, ro_sel),
+		  fuse->quot - corner->quot_adjust);
+
+	if (drv->desc->cpr_type == CTRL_TYPE_CPR4) {
+		cpr_masked_write(thread,
+				 CPR4_REG_CPR_MASK_THREAD(tdesc->hw_tid),
+				 CPR4_CPR_MASK_THREAD_DISABLE_THREAD |
+				 CPR4_CPR_MASK_THREAD_RO_MASK4THREAD_MASK, 0);
+	}
+
+	thread->corner = corner;
+	corner->last_uV = corner->uV;
+}
+
+/**
+ * cpr_set_acc - Set fuse level to the mem-acc
+ * @thread: Structure holding CPR thread-specific parameters
+ * @f:      Fuse level
+ */
+static void cpr_set_acc(struct cpr_drv *drv, int f)
+{
+	const struct acc_desc *desc = drv->acc_desc;
+	struct reg_sequence *s = desc->settings;
+	int n = desc->num_regs_per_fuse;
+
+	if (!s || f == drv->fuse_level_set)
+		return;
+
+	regmap_multi_reg_write(drv->tcsr, s + (n * f), n);
+	drv->fuse_level_set = f;
+}
+
+/**
+ * cpr_post_voltage - Actions to execute before setting voltage
+ * @thread:     Structure holding CPR thread-specific parameters
+ * @dir:        Enumeration for voltage change direction
+ * @fuse_level: Fuse corner for mem-acc, if supported.
+ *
+ * Returns: Zero for success or negative number on errors.
+ */
+static int cpr_pre_voltage(struct cpr_thread *thread,
+			   enum voltage_change_dir dir,
+			   int fuse_level)
+{
+	struct cpr_drv *drv = thread->drv;
+
+	if (drv->desc->cpr_type == CTRL_TYPE_CPR3 &&
+	    drv->desc->pd_throttle_val)
+		cpr_write(thread, CPR3_REG_PD_THROTTLE,
+			  drv->desc->pd_throttle_val);
+
+	if (drv->tcsr && dir == DOWN)
+		cpr_set_acc(drv, fuse_level);
+
+	return 0;
+}
+
+/**
+ * cpr_post_voltage - Actions to execute after setting voltage
+ * @thread:     Structure holding CPR thread-specific parameters
+ * @dir:        Enumeration for voltage change direction
+ * @fuse_level: Fuse corner for mem-acc, if supported.
+ *
+ * Returns: Zero for success or negative number on errors.
+ */
+static int cpr_post_voltage(struct cpr_thread *thread,
+			    enum voltage_change_dir dir,
+			    int fuse_level)
+{
+	struct cpr_drv *drv = thread->drv;
+
+	if (drv->tcsr && dir == UP)
+		cpr_set_acc(drv, fuse_level);
+
+	if (drv->desc->cpr_type == CTRL_TYPE_CPR3)
+		cpr_write(thread, CPR3_REG_PD_THROTTLE, 0);
+
+	return 0;
+}
+
+/**
+ * cpr_commit_state - Set the newly requested voltage
+ * @thread:     Structure holding CPR thread-specific parameters
+ *
+ * Returns: IRQ_SUCCESS for success, IRQ_NONE if the CPR is disabled.
+ */
+static int cpr_commit_state(struct cpr_thread *thread)
+{
+	struct cpr_drv *drv = thread->drv;
+	int min_uV = 0, max_uV = 0, new_uV = 0, fuse_level = 0;
+	enum voltage_change_dir dir;
+	u32 next_irqmask = 0;
+	int ret, i;
+
+	/* On CPRhardened, control states are managed in firmware */
+	if (drv->desc->cpr_type == CTRL_TYPE_CPRH)
+		return 0;
+
+	for (i = 0; i < drv->num_threads; i++) {
+		struct cpr_thread *thread = &drv->threads[i];
+
+		if (!thread->corner)
+			continue;
+
+		fuse_level = max(fuse_level,
+				 (int) (thread->corner->fuse_corner -
+				 &thread->fuse_corners[0]));
+
+		max_uV = max(max_uV, thread->corner->max_uV);
+		min_uV = max(min_uV, thread->corner->min_uV);
+		new_uV = max(new_uV, thread->corner->last_uV);
+	}
+	dev_vdbg(drv->dev, "%s: new uV: %d, last uV: %d\n",
+		 __func__, new_uV, drv->last_uV);
+
+	/*
+	 * Safety measure: if the voltage is out of the globally allowed
+	 * range, then go out and warn the user.
+	 * This should *never* happen.
+	 */
+	if (new_uV > drv->desc->cpr_max_voltage ||
+	    new_uV < drv->desc->cpr_base_voltage) {
+		dev_warn(drv->dev, "Voltage (%u uV) out of range.", new_uV);
+		return -EINVAL;
+	}
+
+	if (new_uV == drv->last_uV || fuse_level == drv->fuse_level_set)
+		goto out;
+
+	if (fuse_level > drv->fuse_level_set)
+		dir = UP;
+	else
+		dir = DOWN;
+
+	ret = cpr_pre_voltage(thread, fuse_level, dir);
+	if (ret)
+		return ret;
+
+	dev_vdbg(drv->dev, "setting voltage: %d\n", new_uV);
+
+	ret = regulator_set_voltage(drv->vreg, new_uV, new_uV);
+	if (ret) {
+		dev_err_ratelimited(drv->dev, "failed to set voltage %d: %d\n",
+				    new_uV, ret);
+		return ret;
+	}
+
+	ret = cpr_post_voltage(thread, fuse_level, dir);
+	if (ret)
+		return ret;
+
+	drv->last_uV = new_uV;
+out:
+	if (new_uV > min_uV)
+		next_irqmask |= CPR3_IRQ_DOWN;
+	if (new_uV < max_uV)
+		next_irqmask |= CPR3_IRQ_UP;
+
+	cpr_irq_set(thread, next_irqmask);
+
+	return 0;
+}
+
+static unsigned int cpr_get_cur_perf_state(struct cpr_thread *thread)
+{
+	return thread->corner ? thread->corner - thread->corners + 1 : 0;
+}
+
+/**
+ * cpr_scale - Calculate new voltage for the received direction
+ * @thread: Structure holding CPR thread-specific parameters
+ * @dir:    Enumeration for voltage change direction
+ *
+ * The CPR scales one by one: this function calculates the new
+ * voltage to set when a voltage-UP or voltage-DOWN request comes
+ * and stores it into the per-thread structure that gets passed.
+ */
+static void cpr_scale(struct cpr_thread *thread, enum voltage_change_dir dir)
+{
+	struct cpr_drv *drv = thread->drv;
+	const struct cpr_thread_desc *tdesc = thread->desc;
+	u32 val, error_steps;
+	int last_uV, new_uV;
+	struct corner *corner;
+
+	if (dir != UP && dir != DOWN)
+		return;
+
+	corner = thread->corner;
+	val = cpr_read(thread, CPR3_REG_RESULT0(tdesc->hw_tid));
+	error_steps = val >> CPR3_RESULT0_ERROR_STEPS_SHIFT;
+	error_steps &= CPR3_RESULT0_ERROR_STEPS_MASK;
+
+	last_uV = corner->last_uV;
+
+	if (dir == UP) {
+		if (!(val & CPR3_RESULT0_STEP_UP_MASK))
+			return;
+
+		/* Calculate new voltage */
+		new_uV = last_uV + drv->vreg_step;
+		new_uV = min(new_uV, corner->max_uV);
+
+		dev_vdbg(drv->dev,
+			"[T%u] UP - new_uV=%d last_uV=%d p-state=%u st=%u\n",
+			thread->id, new_uV, last_uV,
+			cpr_get_cur_perf_state(thread), error_steps);
+	} else {
+		if (!(val & CPR3_RESULT0_STEP_DN_MASK))
+			return;
+
+		/* Calculate new voltage */
+		new_uV = last_uV - drv->vreg_step;
+		new_uV = max(new_uV, corner->min_uV);
+		dev_vdbg(drv->dev,
+			"[T%u] DOWN - new_uV=%d last_uV=%d p-state=%u st=%u\n",
+			thread->id, new_uV, last_uV,
+			cpr_get_cur_perf_state(thread), error_steps);
+	}
+	corner->last_uV = new_uV;
+}
+
+/**
+ * cpr_irq_handler - Handle CPR3/CPR4 status interrupts
+ * @irq: Number of the interrupt
+ * @dev: Pointer to the cpr_thread structure
+ *
+ * Handle the interrupts coming from non-hardened CPR HW as to get
+ * an ok to scale voltages immediately, or to pass error status to
+ * the hardware (either success/ACK or failure/NACK).
+ *
+ * Returns: IRQ_SUCCESS for success, IRQ_NONE if the CPR is disabled.
+ */
+static irqreturn_t cpr_irq_handler(int irq, void *dev)
+{
+	struct cpr_thread *thread = dev;
+	struct cpr_drv *drv = thread->drv;
+	irqreturn_t ret = IRQ_HANDLED;
+	int i, rc;
+	enum voltage_change_dir dir = NO_CHANGE;
+	u32 val;
+
+	mutex_lock(&drv->lock);
+
+	val = cpr_read(thread, CPR3_REG_IRQ_STATUS);
+
+	dev_vdbg(drv->dev, "IRQ_STATUS = %#02x\n", val);
+
+	if (!cpr_ctl_is_enabled(thread)) {
+		dev_vdbg(drv->dev, "CPR is disabled\n");
+		ret = IRQ_NONE;
+	} else if (cpr_check_any_thread_busy(thread)) {
+		cpr_irq_clr_nack(thread);
+		dev_dbg(drv->dev, "CPR measurement is not ready\n");
+	} else {
+		/*
+		 * Following sequence of handling is as per each IRQ's
+		 * priority
+		 */
+		if (val & CPR3_IRQ_UP)
+			dir = UP;
+		else if (val & CPR3_IRQ_DOWN)
+			dir = DOWN;
+
+		if (dir != NO_CHANGE) {
+			for (i = 0; i < drv->num_threads; i++) {
+				thread = &drv->threads[i];
+				cpr_scale(thread, dir);
+			}
+
+			rc = cpr_commit_state(thread);
+			if (rc)
+				cpr_irq_clr_nack(thread);
+			else
+				cpr_irq_clr_ack(thread);
+		} else if (val & CPR3_IRQ_MID) {
+			dev_dbg(drv->dev, "IRQ occurred for Mid Flag\n");
+		} else {
+			dev_warn(drv->dev,
+				 "IRQ occurred for unknown flag (%#08x)\n",
+				 val);
+			schedule_work(&thread->restart_work);
+		}
+	}
+
+	mutex_unlock(&drv->lock);
+
+	return ret;
+}
+
+static int cpr_switch(struct cpr_drv *drv)
+{
+	int i, ret;
+	bool enabled = false;
+
+	if (drv->desc->cpr_type == CTRL_TYPE_CPRH)
+		return 0;
+
+	for (i = 0; i < drv->num_threads && !enabled; i++)
+		enabled = drv->threads[i].enabled;
+
+	dev_vdbg(drv->dev, "%s: enabled = %d\n", __func__, enabled);
+
+	if (enabled == drv->enabled)
+		return 0;
+
+	if (enabled) {
+		ret = regulator_enable(drv->vreg);
+		if (ret)
+			return ret;
+
+		drv->enabled = enabled;
+
+		for (i = 0; i < drv->num_threads; i++)
+			if (drv->threads[i].corner)
+				break;
+
+		if (i < drv->num_threads) {
+			cpr_irq_clr(&drv->threads[i]);
+
+			cpr_commit_state(&drv->threads[i]);
+			cpr_ctl_enable(&drv->threads[i]);
+		}
+	} else {
+		for (i = 0; i < drv->num_threads && !enabled; i++)
+			cpr_ctl_disable(&drv->threads[i]);
+
+		drv->enabled = enabled;
+
+		ret = regulator_disable(drv->vreg);
+		if (ret < 0)
+			return ret;
+	}
+
+	return 0;
+}
+
+/**
+ * cpr_enable - Enables a CPR thread
+ * @thread: Structure holding CPR thread-specific parameters
+ *
+ * Returns: Zero for success or negative number on errors.
+ */
+static int cpr_enable(struct cpr_thread *thread)
+{
+	struct cpr_drv *drv = thread->drv;
+	int ret;
+
+	dev_dbg(drv->dev, "Enabling thread %d\n", thread->id);
+
+	mutex_lock(&drv->lock);
+
+	thread->enabled = true;
+	ret = cpr_switch(thread->drv);
+
+	mutex_unlock(&drv->lock);
+
+	return ret;
+}
+
+/**
+ * cpr_disable - Disables a CPR thread
+ * @thread: Structure holding CPR thread-specific parameters
+ *
+ * Returns: Zero for success or negative number on errors.
+ */
+static int cpr_disable(struct cpr_thread *thread)
+{
+	struct cpr_drv *drv = thread->drv;
+	int ret;
+
+	dev_dbg(drv->dev, "Disabling thread %d\n", thread->id);
+
+	mutex_lock(&drv->lock);
+
+	thread->enabled = false;
+	ret = cpr_switch(thread->drv);
+
+	mutex_unlock(&drv->lock);
+
+	return ret;
+}
+
+/**
+ * cpr_configure - Configure main HW parameters
+ * @thread: Structure holding CPR thread-specific parameters
+ *
+ * This function configures the main CPR hardware parameters, such as
+ * internal timers (and delays), sensor ownerships, activates and/or
+ * deactivates cpr-threads and others, as one sequence for all of the
+ * versions supported in this driver. By design, the function may
+ * return a success earlier if the sequence for "a previous version"
+ * has ended.
+ *
+ * NOTE: The CPR must be clocked before calling this function!
+ *
+ * Returns: Zero for success or negative number on errors.
+ */
+static int cpr_configure(struct cpr_thread *thread)
+{
+	struct cpr_drv *drv = thread->drv;
+	const struct cpr_desc *desc = drv->desc;
+	const struct cpr_thread_desc *tdesc = thread->desc;
+	int i;
+	u32 val;
+
+	/* Disable interrupt and CPR */
+	cpr_irq_set(thread, 0);
+	cpr_write(thread, CPR3_REG_CPR_CTL, 0);
+
+	/* Init and save gcnt */
+	drv->gcnt = drv->ref_clk_khz * desc->gcnt_us;
+	do_div(drv->gcnt, 1000);
+
+	/* Program the delay count for the timer */
+	val = drv->ref_clk_khz * desc->timer_delay_us;
+	do_div(val, 1000);
+	if (desc->cpr_type == CTRL_TYPE_CPR3) {
+		cpr_write(thread, CPR3_REG_CPR_TIMER_MID_CONT, val);
+
+		val = drv->ref_clk_khz * desc->timer_updn_delay_us;
+		do_div(val, 1000);
+		cpr_write(thread, CPR3_REG_CPR_TIMER_UP_DN_CONT, val);
+	} else {
+		cpr_write(thread, CPR3_REG_CPR_TIMER_AUTO_CONT, val);
+	}
+	dev_dbg(drv->dev, "Timer count: %#0x (for %d us)\n", val,
+		desc->timer_delay_us);
+
+	/* Program the control register */
+	val = desc->idle_clocks << CPR3_CPR_CTL_IDLE_CLOCKS_SHIFT
+	    | desc->count_mode << CPR3_CPR_CTL_COUNT_MODE_SHIFT
+	    | desc->count_repeat << CPR3_CPR_CTL_COUNT_REPEAT_SHIFT;
+	cpr_write(thread, CPR3_REG_CPR_CTL, val);
+
+	/* Configure CPR default step quotients */
+	val = desc->step_quot_init_min << CPR3_CPR_STEP_QUOT_MIN_SHIFT
+	    | desc->step_quot_init_max << CPR3_CPR_STEP_QUOT_MAX_SHIFT;
+	cpr_write(thread, CPR3_REG_CPR_STEP_QUOT, val);
+
+	/*
+	 * Configure the CPR sensor ownership always on thread 0
+	 * TODO: SDM845 has different ownership for sensors!!
+	 */
+	for (i = tdesc->sensor_range_start; i < tdesc->sensor_range_end; i++)
+		cpr_write(thread, CPR3_REG_SENSOR_OWNER(i), 0);
+
+	/* Program Consecutive Up & Down */
+	val = desc->timer_cons_up << CPR3_THRESH_CONS_UP_SHIFT;
+	val |= desc->timer_cons_down << CPR3_THRESH_CONS_DOWN_SHIFT;
+	val |= desc->up_threshold << CPR3_THRESH_UP_THRESH_SHIFT;
+	val |= desc->down_threshold << CPR3_THRESH_DOWN_THRESH_SHIFT;
+	cpr_write(thread, CPR3_REG_THRESH(tdesc->hw_tid), val);
+
+	/* Mask all ring oscillators for all threads initially */
+	cpr_write(thread, CPR3_REG_RO_MASK(tdesc->hw_tid), CPR3_RO_MASK);
+
+	/* HW Closed-loop control */
+	if (desc->cpr_type == CTRL_TYPE_CPR3)
+		cpr_write(thread, CPR3_REG_HW_CLOSED_LOOP_DISABLED,
+			  !desc->hw_closed_loop_en);
+	else
+		cpr_masked_write(thread, CPR4_REG_MARGIN_ADJ_CTL,
+				CPR4_MARGIN_ADJ_HW_CLOSED_LOOP_EN,
+				desc->hw_closed_loop_en ?
+				CPR4_MARGIN_ADJ_HW_CLOSED_LOOP_EN : 0);
+
+	/* Additional configuration for CPR4 and beyond */
+	if (desc->cpr_type < CTRL_TYPE_CPR4)
+		return 0;
+
+	/* Disable threads initially only on non-hardened CPR4 */
+	if (desc->cpr_type == CTRL_TYPE_CPR4)
+		cpr_masked_write(thread, CPR4_REG_CPR_MASK_THREAD(1),
+				CPR4_CPR_MASK_THREAD_DISABLE_THREAD |
+				CPR4_CPR_MASK_THREAD_RO_MASK4THREAD_MASK,
+				CPR4_CPR_MASK_THREAD_DISABLE_THREAD |
+				CPR4_CPR_MASK_THREAD_RO_MASK4THREAD_MASK);
+
+	if (drv->num_threads == 2)
+		cpr_masked_write(thread, CPR4_REG_MISC,
+				 CPR4_MISC_RESET_STEP_QUOT_LOOP_EN |
+				 CPR4_MISC_THREAD_HAS_ALWAYS_VOTE_EN,
+				 CPR4_MISC_RESET_STEP_QUOT_LOOP_EN |
+				 CPR4_MISC_THREAD_HAS_ALWAYS_VOTE_EN);
+
+	val = drv->vreg_step;
+	do_div(val, 1000);
+	cpr_masked_write(thread, CPR4_REG_MARGIN_ADJ_CTL,
+			 CPR4_MARGIN_ADJ_PMIC_STEP_SIZE_MASK,
+			 val << CPR4_MARGIN_ADJ_PMIC_STEP_SIZE_SHIFT);
+
+	cpr_masked_write(thread, CPR4_REG_SAW_ERROR_STEP_LIMIT,
+			 CPR4_SAW_ERROR_STEP_LIMIT_DN_MASK,
+			 desc->vreg_step_down_limit <<
+			 CPR4_SAW_ERROR_STEP_LIMIT_DN_SHIFT);
+
+	cpr_masked_write(thread, CPR4_REG_SAW_ERROR_STEP_LIMIT,
+			 CPR4_SAW_ERROR_STEP_LIMIT_UP_MASK,
+			 desc->vreg_step_up_limit <<
+			 CPR4_SAW_ERROR_STEP_LIMIT_UP_SHIFT);
+
+	cpr_masked_write(thread, CPR4_REG_MARGIN_ADJ_CTL,
+			 CPR4_MARGIN_ADJ_PER_RO_KV_MARGIN_EN,
+			 CPR4_MARGIN_ADJ_PER_RO_KV_MARGIN_EN);
+
+	if (drv->num_threads > 1)
+		cpr_masked_write(thread, CPR4_REG_CPR_TIMER_CLAMP,
+				 CPR4_CPR_TIMER_CLAMP_THREAD_AGGREGATION_EN,
+				 CPR4_CPR_TIMER_CLAMP_THREAD_AGGREGATION_EN);
+
+	/* Settling timer to account for one VDD supply step */
+	if (desc->vdd_settle_time_us > 0) {
+		u32 m = CPR4_MARGIN_TEMP_CORE_TIMERS_SETTLE_VOLTAGE_COUNT_MASK;
+		u32 s = CPR4_MARGIN_TEMP_CORE_TIMERS_SETTLE_VOLTAGE_COUNT_SHFT;
+
+		cpr_masked_write(thread, CPR4_REG_MARGIN_TEMP_CORE_TIMERS,
+				 m, desc->vdd_settle_time_us << s);
+	}
+
+	/* Additional configuration for CPR-hardened */
+	if (desc->cpr_type < CTRL_TYPE_CPRH)
+		return 0;
+
+	/* Settling timer to account for one corner-switch request */
+	if (desc->corner_settle_time_us > 0)
+		cpr_masked_write(thread, drv->reg_ctl,
+				 CPRH_CTL_MODE_SWITCH_DELAY_MASK,
+				 desc->corner_settle_time_us <<
+				 CPRH_CTL_MODE_SWITCH_DELAY_SHIFT);
+
+	/* Base voltage and multiplier values for CPRh internal calculations */
+	cpr_masked_write(thread, drv->reg_ctl,
+			 CPRH_CTL_BASE_VOLTAGE_MASK,
+			 (DIV_ROUND_UP(desc->cpr_base_voltage,
+				       drv->vreg_step) <<
+			  CPRH_CTL_BASE_VOLTAGE_SHIFT));
+
+	cpr_masked_write(thread, drv->reg_ctl,
+			 CPRH_CTL_VOLTAGE_MULTIPLIER_MASK,
+			 DIV_ROUND_UP(drv->vreg_step, 1000) <<
+			 CPRH_CTL_VOLTAGE_MULTIPLIER_SHIFT);
+
+	return 0;
+}
+
+static int cpr_set_performance_state(struct generic_pm_domain *domain,
+				     unsigned int state)
+{
+	struct cpr_thread *thread = container_of(domain, struct cpr_thread, pd);
+	struct cpr_drv *drv = thread->drv;
+	struct corner *corner, *end;
+	int ret = 0;
+
+	/* On CPRhardened, performance states are managed in firmware */
+	if (drv->desc->cpr_type == CTRL_TYPE_CPRH)
+		return 0;
+
+	mutex_lock(&drv->lock);
+
+	dev_dbg(drv->dev,
+		"setting perf state: %u (prev state: %u thread: %u)\n",
+		state, cpr_get_cur_perf_state(thread), thread->id);
+
+	/*
+	 * Determine new corner we're going to.
+	 * Remove one since lowest performance state is 1.
+	 */
+	corner = thread->corners + state - 1;
+	end = &thread->corners[thread->num_corners - 1];
+	if (corner > end || corner < thread->corners) {
+		ret = -EINVAL;
+		goto unlock;
+	}
+
+	cpr_ctl_disable(thread);
+
+	cpr_irq_clr(thread);
+	if (thread->corner != corner)
+		cpr_corner_restore(thread, corner);
+
+	ret = cpr_commit_state(thread);
+	if (ret)
+		goto unlock;
+
+	cpr_ctl_enable(thread);
+unlock:
+	mutex_unlock(&drv->lock);
+
+	dev_dbg(drv->dev,
+		"set perf state %u on thread %u\n", state, thread->id);
+
+	return ret;
+}
+
+/**
+ * cpr3_adjust_quot - Adjust the closed-loop quotients
+ * @thread: Structure holding CPR thread-specific parameters
+ *
+ * Calculates the quotient adjustment factor based on closed-loop
+ * quotients and ring oscillator factor.
+ *
+ * Returns: Adjusted quotient
+ */
+static int cpr3_adjust_quot(int ring_osc_factor, int volt_closed_loop)
+{
+	s64 temp;
+
+	if (ring_osc_factor == 0 || volt_closed_loop == 0)
+		return 0;
+
+	temp = (s64)(ring_osc_factor * volt_closed_loop);
+
+	return (int)div_s64(temp, 1000000);
+}
+
+/**
+ * cpr_fuse_corner_init - Calculate fuse corner table
+ * @thread: Structure holding CPR thread-specific parameters
+ *
+ * This function populates the fuse corners table by reading the
+ * values from the fuses, eventually adjusting them with a fixed
+ * per-corner offset and doing basic checks about them being
+ * supported by the regulator that is assigned to this CPR - if
+ * it is available (on CPR-Hardened, there is no usable vreg, as
+ * that is protected by the hypervisor).
+ *
+ * Returns: Zero for success, negative number on error
+ */
+static int cpr_fuse_corner_init(struct cpr_thread *thread)
+{
+	struct cpr_drv *drv = thread->drv;
+	const struct cpr_thread_desc *desc = thread->desc;
+	const struct cpr_fuse *cpr_fuse = thread->cpr_fuses;
+	struct fuse_corner_data *fdata;
+	struct fuse_corner *fuse, *end;
+	int i, ret;
+
+	/* Populate fuse_corner members */
+	fuse = thread->fuse_corners;
+	end = &fuse[desc->num_fuse_corners - 1];
+	fdata = desc->fuse_corner_data;
+
+	for (i = 0; fuse <= end; fuse++, cpr_fuse++, i++, fdata++) {
+		int ro_fac = desc->ro_scaling_factor[fuse->ring_osc_idx];
+
+		ret = cpr_populate_fuse_common(drv->dev, fdata, cpr_fuse,
+					       fuse, drv->vreg_step,
+					       desc->init_voltage_width,
+					       desc->init_voltage_step);
+		if (ret)
+			return ret;
+
+		/*
+		 * Adjust the fuse quot with per-fuse-corner closed-loop
+		 * voltage adjustment parameters.
+		 */
+		fuse->quot += cpr3_adjust_quot(ro_fac,
+					       fdata->volt_cloop_adjust);
+
+		/* CPRh: no regulator access... */
+		if (drv->desc->cpr_type == CTRL_TYPE_CPRH)
+			goto skip_pvs_restrict;
+
+		/* Re-check if corner voltage range is supported by regulator */
+		ret = cpr_check_vreg_constraints(drv->dev, drv->vreg, fuse);
+		if (ret)
+			return ret;
+
+skip_pvs_restrict:
+		dev_dbg(drv->dev,
+			"fuse corner %d: [%d %d %d] RO%hhu quot %d\n",
+			i, fuse->min_uV, fuse->uV, fuse->max_uV,
+			fuse->ring_osc_idx, fuse->quot);
+	}
+
+	return 0;
+}
+
+/*
+ * cprh_corner_adjust_opps - Set voltage on each CPU OPP table entry
+ *
+ * On CPR-Hardened, the voltage level is controlled internally through
+ * the OSM hardware: in order to initialize the latter, we have to
+ * communicate the voltage to its driver, so that it will be able to
+ * write the right parameters (as they have to be set both on the CPRh
+ * and on the OSM) on it.
+ * This function is called only for CPRh.
+ *
+ * Return: Zero for success, negative number for error.
+ */
+static int cprh_corner_adjust_opps(struct cpr_thread *thread)
+{
+	struct corner *corner = thread->corners;
+	struct cpr_drv *drv = thread->drv;
+	struct opp_table *tbl;
+	int i, ret;
+
+	tbl = dev_pm_opp_get_opp_table(thread->attached_cpu_dev);
+	if (IS_ERR(tbl))
+		return PTR_ERR(tbl);
+
+	for (i = 0; i < thread->num_corners; i++) {
+		ret = dev_pm_opp_adjust_voltage(thread->attached_cpu_dev,
+						corner[i].freq,
+						corner[i].uV,
+						corner[i].min_uV,
+						corner[i].max_uV);
+		if (ret)
+			break;
+
+		dev_dbg(drv->dev,
+			"OPP voltage adjusted for %lu kHz, %d uV\n",
+			corner[i].freq, corner[i].uV);
+	}
+
+	dev_pm_opp_put_opp_table(tbl);
+	return ret;
+}
+
+/**
+ * cpr3_corner_init - Calculate and set-up corners for the CPR HW
+ * @thread: Structure holding CPR thread-specific parameters
+ *
+ * This function calculates all the corner parameters by comparing
+ * and interpolating the values read from the various set-points
+ * read from the fuses (also called "fuse corners") to generate and
+ * program to the CPR a lookup table that describes each voltage
+ * step, mapped to a performance level (or corner number).
+ *
+ * It also programs other essential parameters on the CPR and - if
+ * we are dealing with CPR-Hardened, it will also enable the internal
+ * interface between the Operating State Manager (OSM) and the CPRh
+ * in order to achieve CPU DVFS.
+ *
+ * Returns: Zero for success, negative number on error
+ */
+static int cpr3_corner_init(struct cpr_thread *thread)
+{
+	struct cpr_drv *drv = thread->drv;
+	const struct cpr_desc *desc = drv->desc;
+	const struct cpr_thread_desc *tdesc = thread->desc;
+	const struct cpr_fuse *fuses = thread->cpr_fuses;
+	int i, ret, total_corners, level, scaling = 0;
+	unsigned int fnum, fc;
+	const char *quot_offset;
+	const struct fuse_corner_data *fdata;
+	struct fuse_corner *fuse, *prev_fuse;
+	struct corner *corner, *end;
+	struct corner_data *cdata;
+	struct dev_pm_opp *opp;
+	bool apply_scaling;
+	unsigned long freq;
+	u32 ring_osc_mask = CPR3_RO_MASK, min_quotient = U32_MAX;
+
+	corner = thread->corners;
+	end = &corner[thread->num_corners - 1];
+
+	cdata = devm_kcalloc(drv->dev,
+			     thread->num_corners + drv->extra_corners,
+			     sizeof(struct corner_data),
+			     GFP_KERNEL);
+	if (!cdata)
+		return -ENOMEM;
+
+	for (level = 1; level <= thread->num_corners; level++) {
+		opp = dev_pm_opp_find_level_exact(&thread->pd.dev, level);
+		if (IS_ERR(opp))
+			return -EINVAL;
+
+		/*
+		 * If there is only one specified qcom,opp-fuse-level, then
+		 * it is assumed that this only one is global and valid for
+		 * all IDs, so try to get the specific one but, on failure,
+		 * go for the global one.
+		 */
+		fc = cpr_get_fuse_corner(opp, thread->id);
+		if (fc == 0) {
+			fc = cpr_get_fuse_corner(opp, 0);
+			if (fc == 0) {
+				dev_err(drv->dev,
+					"qcom,opp-fuse-level is missing!\n");
+				dev_pm_opp_put(opp);
+				return -EINVAL;
+			}
+		}
+		fnum = fc - 1;
+
+		freq = cpr_get_opp_hz_for_req(opp, thread->attached_cpu_dev);
+		if (!freq) {
+			thread->num_corners = max(level - 1, 0);
+			end = &thread->corners[thread->num_corners - 1];
+			break;
+		}
+		cdata[level - 1].fuse_corner = fnum;
+		cdata[level - 1].freq = freq;
+
+		fuse = &thread->fuse_corners[fnum];
+		dev_dbg(drv->dev, "freq: %lu level: %u fuse level: %u\n",
+			freq, dev_pm_opp_get_level(opp) - 1, fnum);
+		if (freq > fuse->max_freq)
+			fuse->max_freq = freq;
+		dev_pm_opp_put(opp);
+	}
+
+	if (thread->num_corners < 2) {
+		dev_err(drv->dev, "need at least 2 OPPs to use CPR\n");
+		return -EINVAL;
+	}
+
+	/*
+	 * Get the quotient adjustment scaling factor, according to:
+	 *
+	 * scaling = min(1000 * (QUOT(corner_N) - QUOT(corner_N-1))
+	 *		/ (freq(corner_N) - freq(corner_N-1)), max_factor)
+	 *
+	 * QUOT(corner_N):	quotient read from fuse for fuse corner N
+	 * QUOT(corner_N-1):	quotient read from fuse for fuse corner (N - 1)
+	 * freq(corner_N):	max frequency in MHz supported by fuse corner N
+	 * freq(corner_N-1):	max frequency in MHz supported by fuse corner
+	 *			 (N - 1)
+	 *
+	 * Then walk through the corners mapped to each fuse corner
+	 * and calculate the quotient adjustment for each one using the
+	 * following formula:
+	 *
+	 * quot_adjust = (freq_max - freq_corner) * scaling / 1000
+	 *
+	 * freq_max: max frequency in MHz supported by the fuse corner
+	 * freq_corner: frequency in MHz corresponding to the corner
+	 * scaling: calculated from above equation
+	 *
+	 *
+	 *     +                           +
+	 *     |                         v |
+	 *   q |           f c           o |           f c
+	 *   u |         c               l |         c
+	 *   o |       f                 t |       f
+	 *   t |     c                   a |     c
+	 *     | c f                     g | c f
+	 *     |                         e |
+	 *     +---------------            +----------------
+	 *       0 1 2 3 4 5 6               0 1 2 3 4 5 6
+	 *          corner                      corner
+	 *
+	 *    c = corner
+	 *    f = fuse corner
+	 *
+	 */
+	for (apply_scaling = false, i = 0; corner <= end; corner++, i++) {
+		unsigned long freq_diff_mhz;
+
+		fnum = cdata[i].fuse_corner;
+		fdata = &tdesc->fuse_corner_data[fnum];
+		quot_offset = fuses[fnum].quotient_offset;
+		fuse = &thread->fuse_corners[fnum];
+		ring_osc_mask &= (u16)(~BIT(fuse->ring_osc_idx));
+		if (fnum)
+			prev_fuse = &thread->fuse_corners[fnum - 1];
+		else
+			prev_fuse = NULL;
+
+		corner->fuse_corner = fuse;
+		corner->freq = cdata[i].freq;
+		corner->uV = fuse->uV;
+
+		if (prev_fuse) {
+			scaling = cpr_calculate_scaling(quot_offset, drv->dev,
+							fdata, corner);
+			if (scaling < 0)
+				return scaling;
+
+			apply_scaling = true;
+		} else if (corner->freq == fuse->max_freq) {
+			/* This is a fuse corner; don't scale anything */
+			apply_scaling = false;
+		}
+
+		if (apply_scaling) {
+			freq_diff_mhz = fuse->max_freq - corner->freq;
+			do_div(freq_diff_mhz, 1000000); /* now in MHz */
+
+			corner->quot_adjust = scaling * freq_diff_mhz;
+			do_div(corner->quot_adjust, 1000);
+
+			corner->uV = cpr_interpolate(corner,
+						     drv->vreg_step, fdata);
+		}
+
+		/* Negative fuse quotients are nonsense. */
+		if (fuse->quot < corner->quot_adjust)
+			return -EINVAL;
+
+		min_quotient = min(min_quotient,
+				   (u32)(fuse->quot - corner->quot_adjust));
+		corner->max_uV = fuse->max_uV;
+		corner->min_uV = fuse->min_uV;
+		corner->uV = clamp(corner->uV, corner->min_uV, corner->max_uV);
+		corner->last_uV = corner->uV;
+
+		/* Reduce the ceiling voltage if needed */
+		if (desc->reduce_to_corner_uV && corner->uV < corner->max_uV)
+			corner->max_uV = corner->uV;
+		else if (desc->reduce_to_fuse_uV && fuse->uV < corner->max_uV)
+			corner->max_uV = max(corner->min_uV, fuse->uV);
+
+		corner->min_uV = max(corner->max_uV - fdata->range_uV,
+				     corner->min_uV);
+		/*
+		 * Adjust per-corner floor and ceiling voltages so that
+		 * they do not overlap the memory Array Power Mux (APM)
+		 * threshold voltage.
+		 * The apm_hysteresis is checked because it is available
+		 * only on CPR controllers that need to fullfill this
+		 * requirement.
+		 */
+		if (desc->apm_hysteresis > 0 &&
+		    desc->apm_threshold > corner->min_uV &&
+		    desc->apm_threshold < corner->max_uV) {
+			if (corner->uV >= desc->apm_threshold) {
+				corner->min_uV = max(corner->min_uV,
+						     desc->apm_threshold -
+						     desc->apm_hysteresis);
+				if (corner->min_uV > corner->uV)
+					corner->uV = corner->min_uV;
+			} else {
+				corner->max_uV = desc->apm_threshold;
+				corner->max_uV -= drv->vreg_step;
+			}
+		}
+
+		dev_dbg(drv->dev, "corner %d: [%d %d %d] scaling %d quot %d\n", i,
+			corner->min_uV, corner->uV, corner->max_uV, scaling,
+			fuse->quot - corner->quot_adjust);
+	}
+
+	/* Additional setup for CPRh only */
+	if (desc->cpr_type < CTRL_TYPE_CPRH)
+		return 0;
+
+	/* If the OPPs can't be adjusted, programming the CPRh is useless */
+	ret = cprh_corner_adjust_opps(thread);
+	if (ret) {
+		dev_err(drv->dev, "Cannot adjust CPU OPP voltages: %d\n", ret);
+		return ret;
+	}
+
+	/*
+	 * Unmask the ring oscillator(s) that we're going to use: it seems
+	 * to be mandatory to do this *before* sending the rest of the
+	 * CPRhardened specific configuration.
+	 */
+	dev_dbg(drv->dev,
+		"Unmasking ring oscillators with mask 0x%x\n", ring_osc_mask);
+	cpr_write(thread, CPR3_REG_RO_MASK(tdesc->hw_tid), ring_osc_mask);
+
+	total_corners = thread->num_corners;
+
+	/* If the APM extra corner exists, add it now. */
+	if (desc->apm_threshold && desc->apm_hysteresis &&
+	    drv->extra_corners) {
+		thread->corners[total_corners].uV = desc->apm_threshold;
+		thread->corners[total_corners].min_uV = desc->apm_threshold;
+		thread->corners[total_corners].max_uV = desc->apm_threshold;
+		thread->corners[total_corners].is_open_loop = true;
+
+		/*
+		 * Also add and disable an opp with zero frequency: other
+		 * drivers will need to know what is the APM threshold voltage.
+		 */
+		ret = dev_pm_opp_add(thread->attached_cpu_dev, 0,
+				     desc->apm_threshold);
+		if (ret)
+			return ret;
+
+		ret = dev_pm_opp_disable(thread->attached_cpu_dev, 0);
+		if (ret)
+			return ret;
+
+		dev_dbg(drv->dev, "corner %d (APM): [%d %d %d] Open-Loop\n",
+			total_corners, desc->apm_threshold,
+			desc->apm_threshold, desc->apm_threshold);
+
+		total_corners += drv->extra_corners;
+	}
+
+	for (i = 0; i < total_corners; i++) {
+		int volt_oloop_steps, volt_floor_steps, delta_quot_steps;
+		int ring_osc;
+		u32 val;
+
+		fnum = cdata[i].fuse_corner;
+		fuse = &thread->fuse_corners[fnum];
+
+		val = thread->corners[i].uV - desc->cpr_base_voltage;
+		volt_oloop_steps = DIV_ROUND_UP(val, drv->vreg_step);
+
+		val = thread->corners[i].min_uV - desc->cpr_base_voltage;
+		volt_floor_steps = DIV_ROUND_UP(val, drv->vreg_step);
+
+		val = fuse->quot - thread->corners[i].quot_adjust;
+		val -= min_quotient;
+		delta_quot_steps = DIV_ROUND_UP(val,
+						CPRH_DELTA_QUOT_STEP_FACTOR);
+
+		/*
+		 * If we are accessing corners that are not used as
+		 * an active DCVS set-point, then always select RO 0
+		 * and zero out the delta quotient
+		 */
+		if (i >= thread->num_corners) {
+			ring_osc = 0;
+			delta_quot_steps = 0;
+		} else {
+			ring_osc = fuse->ring_osc_idx;
+		}
+
+		if (volt_oloop_steps > CPRH_CORNER_INIT_VOLTAGE_MAX_VALUE  ||
+		    volt_floor_steps > CPRH_CORNER_FLOOR_VOLTAGE_MAX_VALUE ||
+		    delta_quot_steps > CPRH_CORNER_QUOT_DELTA_MAX_VALUE) {
+			dev_err(drv->dev,
+				"Invalid cfg: oloop=%d, floor=%d, delta=%d\n",
+				volt_oloop_steps, volt_floor_steps,
+				delta_quot_steps);
+			return -EINVAL;
+		}
+		/* Green light: Go, Go, Go! */
+		val = CPRH_REG_CORNER(drv, tdesc->hw_tid, i);
+
+		/* Set the minimal quotient for all corners */
+		cpr_write(thread, CPR3_REG_TARGET_QUOT(tdesc->hw_tid, i),
+			  min_quotient);
+
+		/* Set number of open-loop steps */
+		cpr_masked_write(thread, val,
+				 CPRH_CORNER_INIT_VOLTAGE_MASK,
+				 volt_oloop_steps <<
+				 CPRH_CORNER_INIT_VOLTAGE_SHIFT);
+
+		/* Set number of floor voltage steps */
+		cpr_masked_write(thread, val,
+				 CPRH_CORNER_FLOOR_VOLTAGE_MASK,
+				 volt_floor_steps <<
+				 CPRH_CORNER_FLOOR_VOLTAGE_SHIFT);
+
+		/* Set number of target quotient delta steps */
+		cpr_masked_write(thread, val,
+				 CPRH_CORNER_QUOT_DELTA_MASK,
+				 delta_quot_steps <<
+				 CPRH_CORNER_QUOT_DELTA_SHIFT);
+
+		/* Select ring oscillator for this corner */
+		cpr_masked_write(thread, val,
+				 CPRH_CORNER_RO_SEL_MASK,
+				 ring_osc << CPRH_CORNER_RO_SEL_SHIFT);
+
+		dev_dbg(drv->dev,
+			"steps [%d]: open-loop %d, floor %d, delta_quot %d\n",
+			i, volt_oloop_steps, volt_floor_steps,
+			delta_quot_steps);
+
+		/* Open loop only corner is for APM crossover */
+		if (thread->corners[i].is_open_loop) {
+			dev_dbg(drv->dev,
+				"Disabling Closed-Loop on corner %d\n", i);
+			cpr_masked_write(thread, val,
+					 CPRH_CORNER_CPR_CL_DISABLE,
+					 CPRH_CORNER_CPR_CL_DISABLE);
+		}
+	}
+	/*
+	 * Make sure that all the writes go through before enabling
+	 * internal communication between the OSM and the CPRh
+	 * controllers, otherwise there is a high risk of hardware
+	 * lockups due to under-voltage for the selected CPU clock.
+	 *
+	 * Please note that the CPR-hardened gets set-up in Linux but
+	 * then gets actually used in firmware (and only by the OSM);
+	 * after handing it off we will have no more control on it,
+	 * hence the only way to get things up properly for sure here
+	 * is a write barrier.
+	 */
+	wmb();
+
+	/* Enable the interface between OSM and CPRh */
+	cpr_masked_write(thread, drv->reg_ctl,
+			 CPRH_CTL_OSM_ENABLED,
+			 CPRH_CTL_OSM_ENABLED);
+
+	/* On success, free cdata manually */
+	devm_kfree(drv->dev, cdata);
+	return 0;
+}
+
+/**
+ * cpr3_init_parameters - Initialize CPR global parameters
+ * @drv: Main driver structure
+ *
+ * Initial "integrity" checks and setup for the thread-independent parameters.
+ *
+ * Returns: Zero for success, negative number on error
+ */
+static int cpr3_init_parameters(struct cpr_drv *drv)
+{
+	const struct cpr_desc *desc = drv->desc;
+	struct clk *clk;
+
+	clk = devm_clk_get(drv->dev, "ref");
+	if (IS_ERR(clk))
+		return PTR_ERR(clk);
+
+	drv->ref_clk_khz = clk_get_rate(clk);
+	do_div(drv->ref_clk_khz, 1000);
+
+	/* On CPRh this clock is not always-on... */
+	if (desc->cpr_type == CTRL_TYPE_CPRH)
+		clk_prepare_enable(clk);
+	else
+		devm_clk_put(drv->dev, clk);
+
+	if (desc->timer_cons_up > CPR3_THRESH_CONS_UP_MASK ||
+	    desc->timer_cons_down > CPR3_THRESH_CONS_DOWN_MASK ||
+	    desc->up_threshold > CPR3_THRESH_UP_THRESH_MASK ||
+	    desc->down_threshold > CPR3_THRESH_DOWN_THRESH_MASK ||
+	    desc->idle_clocks > CPR3_CPR_CTL_IDLE_CLOCKS_MASK ||
+	    desc->count_mode > CPR3_CPR_CTL_COUNT_MODE_MASK ||
+	    desc->count_repeat > CPR3_CPR_CTL_COUNT_REPEAT_MASK ||
+	    desc->step_quot_init_min > CPR3_CPR_STEP_QUOT_MIN_MASK ||
+	    desc->step_quot_init_max > CPR3_CPR_STEP_QUOT_MAX_MASK)
+		return -EINVAL;
+
+	/*
+	 * Read the CPR version register only from CPR3 onwards:
+	 * this is needed to get the additional register offsets.
+	 *
+	 * Note: When threaded, even if multi-controller, there
+	 *       is no chance to have different versions at the
+	 *       same time in the same domain, so it is safe to
+	 *       check this only on the first controller/thread.
+	 */
+	drv->cpr_hw_rev = cpr_read(&drv->threads[0],
+				   CPR3_REG_CPR_VERSION);
+	dev_dbg(drv->dev,
+		"CPR hardware revision: 0x%x\n", drv->cpr_hw_rev);
+
+	if (drv->cpr_hw_rev >= CPRH_CPR_VERSION_4P5) {
+		drv->reg_corner = 0x3500;
+		drv->reg_corner_tid = 0xa0;
+		drv->reg_ctl = 0x3a80;
+		drv->reg_status = 0x3a84;
+	} else {
+		drv->reg_corner = 0x3a00;
+		drv->reg_corner_tid = 0;
+		drv->reg_ctl = 0x3aa0;
+		drv->reg_status = 0x3aa4;
+	}
+
+	dev_dbg(drv->dev, "up threshold = %u, down threshold = %u\n",
+		desc->up_threshold, desc->down_threshold);
+
+	return 0;
+}
+
+/**
+ * cpr3_find_initial_corner - Finds boot-up p-state and enables CPR
+ * @thread: Structure holding CPR thread-specific parameters
+ *
+ * Differently from CPRv1, from CPRv3 onwards when we successfully find
+ * the target boot-up performance state, we must refresh the HW
+ * immediately to guarantee system stability and to avoid overheating
+ * during the boot process, thing that would more likely happen without
+ * this driver doing its job.
+ *
+ * Returns: Zero for success, negative number on error
+ */
+static int cpr3_find_initial_corner(struct cpr_thread *thread)
+{
+	struct cpr_drv *drv = thread->drv;
+	struct corner *corner;
+	int uV, idx;
+
+	idx = cpr_find_initial_corner(drv->dev, thread->cpu_clk,
+				      thread->corners,
+				      thread->num_corners);
+	if (idx < 0)
+		return idx;
+
+	cpr_ctl_disable(thread);
+
+	corner = &thread->corners[idx];
+	cpr_corner_restore(thread, corner);
+
+	uV = regulator_get_voltage(drv->vreg);
+	uV = clamp(uV, corner->min_uV, corner->max_uV);
+
+	corner->last_uV = uV;
+	if (!drv->last_uV)
+		drv->last_uV = uV;
+
+	cpr_commit_state(thread);
+	thread->enabled = true;
+	cpr_switch(drv);
+
+	return 0;
+}
+
+static const int sdm630_gold_scaling_factor[] = {
+	4040, 3230,    0, 2210,
+	2560, 2450, 2230, 2220,
+	2410, 2300, 2560, 2470,
+	1600, 3120, 2620, 2280,
+};
+
+static const int sdm630_silver_scaling_factor[] = {
+	3600, 3600, 3830, 2430,
+	2520, 2700, 1790, 1760,
+	1970, 1880, 2110, 2010,
+	2510, 4900, 4370, 4780,
+};
+
+static const struct cpr_thread_desc sdm630_thread_gold = {
+	.controller_id = 0,
+	.hw_tid = 0,
+	.ro_scaling_factor = sdm630_gold_scaling_factor,
+	.sensor_range_start = 0,
+	.sensor_range_end = 6,
+	.init_voltage_step = 10000,
+	.init_voltage_width = 6,
+	.num_fuse_corners = 5,
+	.fuse_corner_data = (struct fuse_corner_data[]){
+		/* fuse corner 0 */
+		{
+			.ref_uV = 644000,
+			.max_uV = 724000,
+			.min_uV = 588000,
+			.range_uV = 40000,
+			.volt_cloop_adjust = -30000,
+			.volt_oloop_adjust = 15000,
+			.max_volt_scale = 10,
+			.max_quot_scale = 300,
+			.quot_offset = 0,
+			.quot_scale = 1,
+			.quot_adjust = 0,
+			.quot_offset_scale = 5,
+			.quot_offset_adjust = 0,
+		},
+		/* fuse corner 1 */
+		{
+			.ref_uV = 788000,
+			.max_uV = 788000,
+			.min_uV = 652000,
+			.range_uV = 40000,
+			.volt_cloop_adjust = -30000,
+			.volt_oloop_adjust = 5000,
+			.max_volt_scale = 320,
+			.max_quot_scale = 275,
+			.quot_offset = 0,
+			.quot_scale = 1,
+			.quot_adjust = 0,
+			.quot_offset_scale = 5,
+			.quot_offset_adjust = 0,
+		},
+		/* fuse corner 2 */
+		{
+			.ref_uV = 868000,
+			.max_uV = 868000,
+			.min_uV = 712000,
+			.range_uV = 40000,
+			.volt_cloop_adjust = -30000,
+			.volt_oloop_adjust = 5000,
+			.max_volt_scale = 350,
+			.max_quot_scale = 800,
+			.quot_offset = 0,
+			.quot_scale = 1,
+			.quot_adjust = 0,
+			.quot_offset_scale = 5,
+			.quot_offset_adjust = 0,
+		},
+		/* fuse corner 3 */
+		{
+			.ref_uV = 988000,
+			.max_uV = 988000,
+			.min_uV = 784000,
+			.range_uV = 66000,
+			.volt_cloop_adjust = -30000,
+			.volt_oloop_adjust = 0,
+			.max_volt_scale = 868,
+			.max_quot_scale = 980,
+			.quot_offset = 0,
+			.quot_scale = 1,
+			.quot_adjust = 0,
+			.quot_offset_scale = 5,
+			.quot_offset_adjust = 0,
+		},
+		/* fuse corner 4 */
+		{
+			.ref_uV = 1068000,
+			.max_uV = 1068000,
+			.min_uV = 844000,
+			.range_uV = 40000,
+			.volt_cloop_adjust = -30000,
+			.volt_oloop_adjust = 0,
+			.max_volt_scale = 868,
+			.max_quot_scale = 980,
+			.quot_offset = 0,
+			.quot_scale = 1,
+			.quot_adjust = 0,
+			.quot_offset_scale = 5,
+			.quot_offset_adjust = 0,
+		},
+	},
+};
+
+static const struct cpr_thread_desc sdm630_thread_silver = {
+	.controller_id = 1,
+	.hw_tid = 0,
+	.ro_scaling_factor = sdm630_silver_scaling_factor,
+	.sensor_range_start = 0,
+	.sensor_range_end = 6,
+	.init_voltage_step = 10000,
+	.init_voltage_width = 6,
+	.num_fuse_corners = 3,
+	.fuse_corner_data = (struct fuse_corner_data[]){
+		/* fuse corner 0 */
+		{
+			.ref_uV = 644000,
+			.max_uV = 724000,
+			.min_uV = 588000,
+			.range_uV = 32000,
+			.volt_cloop_adjust = -30000,
+			.volt_oloop_adjust = 0,
+			.max_volt_scale = 10,
+			.max_quot_scale = 360,
+			.quot_offset = 0,
+			.quot_scale = 1,
+			.quot_adjust = 0,
+			.quot_offset_scale = 5,
+			.quot_offset_adjust = 0,
+		},
+		/* fuse corner 1 */
+		{
+			.ref_uV = 788000,
+			.max_uV = 788000,
+			.min_uV = 652000,
+			.range_uV = 40000,
+			.volt_cloop_adjust = -30000,
+			.volt_oloop_adjust = 0,
+			.max_volt_scale = 500,
+			.max_quot_scale = 550,
+			.quot_offset = 0,
+			.quot_scale = 1,
+			.quot_adjust = 0,
+			.quot_offset_scale = 5,
+			.quot_offset_adjust = 0,
+		},
+		/* fuse corner 2 */
+		{
+			.ref_uV = 1068000,
+			.max_uV = 1068000,
+			.min_uV = 800000,
+			.range_uV = 40000,
+			.volt_cloop_adjust = -30000,
+			.volt_oloop_adjust = 0,
+			.max_volt_scale = 2370,
+			.max_quot_scale = 550,
+			.quot_offset = 0,
+			.quot_scale = 1,
+			.quot_adjust = 0,
+			.quot_offset_scale = 5,
+			.quot_offset_adjust = 0,
+		},
+	},
+};
+
+static const struct cpr_desc sdm630_cpr_desc = {
+	.cpr_type = CTRL_TYPE_CPRH,
+	.num_threads = 2,
+	.apm_threshold = 872000,
+	.apm_hysteresis = 20000,
+	.cpr_base_voltage = 400000,
+	.cpr_max_voltage = 1300000,
+	.step_quot_init_min = 12,
+	.step_quot_init_max = 14,
+	.timer_delay_us = 5000,
+	.timer_cons_up = 0,
+	.timer_cons_down = 2,
+	.up_threshold = 2,
+	.down_threshold = 2,
+	.idle_clocks = 15,
+	.count_mode = CPR3_CPR_CTL_COUNT_MODE_ALL_AT_ONCE_MIN,
+	.count_repeat = 14,
+	.gcnt_us = 1,
+	.vreg_step_fixed = 4000,
+	.vreg_step_up_limit = 1,
+	.vreg_step_down_limit = 1,
+	.vdd_settle_time_us = 34,
+	.corner_settle_time_us = 5,
+	.reduce_to_corner_uV = true,
+	.hw_closed_loop_en = true,
+	.threads = (const struct cpr_thread_desc *[]) {
+		&sdm630_thread_gold,
+		&sdm630_thread_silver,
+	},
+};
+
+static const struct cpr_acc_desc sdm630_cpr_acc_desc = {
+	.cpr_desc = &sdm630_cpr_desc,
+};
+
+static unsigned int cpr_get_performance_state(struct generic_pm_domain *genpd,
+					      struct dev_pm_opp *opp)
+{
+	return dev_pm_opp_get_level(opp);
+}
+
+static int cpr_power_off(struct generic_pm_domain *domain)
+{
+	struct cpr_thread *thread = container_of(domain, struct cpr_thread, pd);
+
+	return cpr_disable(thread);
+}
+
+static int cpr_power_on(struct generic_pm_domain *domain)
+{
+	struct cpr_thread *thread = container_of(domain, struct cpr_thread, pd);
+
+	return cpr_enable(thread);
+}
+
+static void cpr_pd_detach_dev(struct generic_pm_domain *domain,
+			      struct device *dev)
+{
+	struct cpr_thread *thread = container_of(domain, struct cpr_thread, pd);
+	struct cpr_drv *drv = thread->drv;
+
+	mutex_lock(&drv->lock);
+
+	dev_dbg(drv->dev, "detach callback for: %s\n", dev_name(dev));
+	thread->attached_cpu_dev = NULL;
+
+	mutex_unlock(&drv->lock);
+}
+
+static int cpr_pd_attach_dev(struct generic_pm_domain *domain,
+			     struct device *dev)
+{
+	struct cpr_thread *thread = container_of(domain, struct cpr_thread, pd);
+	struct cpr_drv *drv = thread->drv;
+	const struct acc_desc *acc_desc = drv->acc_desc;
+	int ret = 0;
+
+	mutex_lock(&drv->lock);
+
+	dev_dbg(drv->dev, "attach callback for: %s\n", dev_name(dev));
+
+	/*
+	 * This driver only supports scaling voltage for a CPU cluster
+	 * where all CPUs in the cluster share a single regulator.
+	 * Therefore, save the struct device pointer only for the first
+	 * CPU device that gets attached. There is no need to do any
+	 * additional initialization when further CPUs get attached.
+	 */
+	if (thread->attached_cpu_dev)
+		goto unlock;
+
+	/*
+	 * cpr_scale_voltage() requires the direction (if we are changing
+	 * to a higher or lower OPP). The first time
+	 * cpr_set_performance_state() is called, there is no previous
+	 * performance state defined. Therefore, we call
+	 * cpr_find_initial_corner() that gets the CPU clock frequency
+	 * set by the bootloader, so that we can determine the direction
+	 * the first time cpr_set_performance_state() is called.
+	 */
+	thread->cpu_clk = devm_clk_get(dev, NULL);
+	if (drv->desc->cpr_type < CTRL_TYPE_CPRH &&
+	    IS_ERR(thread->cpu_clk)) {
+		ret = PTR_ERR(thread->cpu_clk);
+		if (ret != -EPROBE_DEFER)
+			dev_err(drv->dev, "could not get cpu clk: %d\n", ret);
+		goto unlock;
+	}
+	thread->attached_cpu_dev = dev;
+
+	dev_dbg(drv->dev, "using cpu clk from: %s\n",
+		dev_name(thread->attached_cpu_dev));
+
+	/*
+	 * Everything related to (virtual) corners has to be initialized
+	 * here, when attaching to the power domain, since we need to know
+	 * the maximum frequency for each fuse corner, and this is only
+	 * available after the cpufreq driver has attached to us.
+	 * The reason for this is that we need to know the highest
+	 * frequency associated with each fuse corner.
+	 */
+	ret = dev_pm_opp_get_opp_count(&thread->pd.dev);
+	if (ret < 0) {
+		dev_err(drv->dev, "could not get OPP count\n");
+		thread->attached_cpu_dev = NULL;
+		goto unlock;
+	}
+	thread->num_corners = ret;
+
+	thread->corners = devm_kcalloc(drv->dev,
+				       thread->num_corners +
+				       drv->extra_corners,
+				       sizeof(*thread->corners),
+				       GFP_KERNEL);
+	if (!thread->corners) {
+		ret = -ENOMEM;
+		goto unlock;
+	}
+
+	ret = cpr3_corner_init(thread);
+	if (ret)
+		goto unlock;
+
+	if (drv->desc->cpr_type < CTRL_TYPE_CPRH) {
+		ret = cpr3_find_initial_corner(thread);
+		if (ret)
+			goto unlock;
+
+		if (acc_desc->config)
+			regmap_multi_reg_write(drv->tcsr, acc_desc->config,
+					       acc_desc->num_regs_per_fuse);
+
+		/* Enable ACC if required */
+		if (acc_desc->enable_mask)
+			regmap_update_bits(drv->tcsr, acc_desc->enable_reg,
+					   acc_desc->enable_mask,
+					   acc_desc->enable_mask);
+	}
+	dev_info(drv->dev, "thread %d initialized with %u OPPs\n",
+		 thread->id, thread->num_corners);
+
+unlock:
+	mutex_unlock(&drv->lock);
+
+	return ret;
+}
+
+static int cpr3_debug_info_show(struct seq_file *s, void *unused)
+{
+	u32 ro_sel, ctl, irq_status, reg, quot;
+	struct cpr_thread *thread = s->private;
+	struct corner *corner = thread->corners;
+	struct fuse_corner *fuse = thread->fuse_corners;
+	unsigned int i;
+
+	const struct {
+		const char *name;
+		uint32_t mask;
+		uint8_t shift;
+	} result0_fields[] = {
+		{ "busy", 1, 0 },
+		{ "step_dn", 1, 1 },
+		{ "step_up", 1, 2 },
+		{ "error_steps", CPR3_RESULT0_ERROR_STEPS_MASK,
+				 CPR3_RESULT0_ERROR_STEPS_SHIFT },
+		{ "error", CPR3_RESULT0_ERROR_MASK, CPR3_RESULT0_ERROR_SHIFT },
+		{ "negative", 1, 20 },
+	}, result1_fields[] = {
+		{ "quot_min", CPR3_RESULT1_QUOT_MIN_MASK,
+			      CPR3_RESULT1_QUOT_MIN_SHIFT },
+		{ "quot_max", CPR3_RESULT1_QUOT_MAX_MASK,
+			      CPR3_RESULT1_QUOT_MAX_SHIFT },
+		{ "ro_min", CPR3_RESULT1_RO_MIN_MASK,
+			    CPR3_RESULT1_RO_MIN_SHIFT },
+		{ "ro_max", CPR3_RESULT1_RO_MAX_MASK,
+			    CPR3_RESULT1_RO_MAX_SHIFT },
+	}, result2_fields[] = {
+		{ "qout_step_min", CPR3_RESULT2_STEP_QUOT_MIN_MASK,
+				   CPR3_RESULT2_STEP_QUOT_MIN_SHIFT },
+		{ "qout_step_max", CPR3_RESULT2_STEP_QUOT_MAX_MASK,
+				   CPR3_RESULT2_STEP_QUOT_MAX_SHIFT },
+		{ "sensor_min", CPR3_RESULT2_SENSOR_MIN_MASK,
+				CPR3_RESULT2_SENSOR_MIN_SHIFT },
+		{ "sensor_max", CPR3_RESULT2_SENSOR_MAX_MASK,
+				CPR3_RESULT2_SENSOR_MAX_SHIFT },
+	};
+
+	if (thread->drv->desc->cpr_type < CTRL_TYPE_CPRH)
+		seq_printf(s, "current_volt = %d uV\n", thread->drv->last_uV);
+
+	irq_status = cpr_read(thread, CPR3_REG_IRQ_STATUS);
+	seq_printf(s, "irq_status = %#02X\n", irq_status);
+
+	ctl = cpr_read(thread, CPR3_REG_CPR_CTL);
+	seq_printf(s, "cpr_ctl = %#02X\n", ctl);
+
+	seq_printf(s, "thread %d - hw tid: %d - enabled: %d:\n",
+		   thread->id, thread->desc->hw_tid, thread->enabled);
+	seq_printf(s, "%d corners, derived from %d fuse corners\n",
+		   thread->num_corners, thread->desc->num_fuse_corners);
+
+	for (i = 0; i < thread->num_corners; i++, corner++)
+		seq_printf(s, "corner %d - uV=[%d %d %d] quot=%d freq=%lu\n",
+			   i, corner->min_uV, corner->uV, corner->max_uV,
+			   corner->quot_adjust, corner->freq);
+
+	for (i = 0; i < thread->desc->num_fuse_corners; i++, fuse++)
+		seq_printf(s, "fuse %d - uV=[%d %d %d] quot=%d freq=%lu\n",
+			   i, fuse->min_uV, fuse->uV, fuse->max_uV,
+			   fuse->quot, corner->freq);
+
+	seq_printf(s, "requested voltage: %d uV\n",
+		   thread->corner->last_uV);
+
+	ro_sel = corner->fuse_corner->ring_osc_idx;
+	quot = cpr_read(thread, CPR3_REG_TARGET_QUOT(i, ro_sel));
+	seq_printf(s, "quot_target (%u) = %#02X\n", ro_sel, quot);
+
+	reg = cpr_read(thread, CPR3_REG_RESULT0(i));
+	seq_printf(s, "cpr_result_0 = %#02X\n  [", reg);
+	for (i = 0; i < ARRAY_SIZE(result0_fields); i++)
+		seq_printf(s, "%s%s = %u",
+			   i ? ", " : "",
+			   result0_fields[i].name,
+			   (reg >> result0_fields[i].shift) &
+				result0_fields[i].mask);
+	seq_puts(s, "]\n");
+	reg = cpr_read(thread, CPR3_REG_RESULT1(i));
+	seq_printf(s, "cpr_result_1 = %#02X\n  [", reg);
+	for (i = 0; i < ARRAY_SIZE(result1_fields); i++)
+		seq_printf(s, "%s%s = %u",
+			   i ? ", " : "",
+			   result1_fields[i].name,
+			   (reg >> result1_fields[i].shift) &
+				result1_fields[i].mask);
+	seq_puts(s, "]\n");
+	reg = cpr_read(thread, CPR3_REG_RESULT2(i));
+	seq_printf(s, "cpr_result_2 = %#02X\n  [", reg);
+	for (i = 0; i < ARRAY_SIZE(result2_fields); i++)
+		seq_printf(s, "%s%s = %u",
+			   i ? ", " : "",
+			   result2_fields[i].name,
+			   (reg >> result2_fields[i].shift) &
+				result2_fields[i].mask);
+	seq_puts(s, "]\n");
+
+	return 0;
+}
+DEFINE_SHOW_ATTRIBUTE(cpr3_debug_info);
+
+static void cpr3_debugfs_init(struct cpr_drv *drv)
+{
+	int i;
+
+	drv->debugfs = debugfs_create_dir("qcom_cpr3", NULL);
+
+	for (i = 0; i < drv->num_threads; i++) {
+		char buf[50];
+
+		snprintf(buf, sizeof(buf), "thread%d", i);
+
+		debugfs_create_file(buf, 0444, drv->debugfs, &drv->threads[i],
+				    &cpr3_debug_info_fops);
+	}
+}
+
+/**
+ * cpr_thread_init - Initialize CPR thread related parameters
+ * @drv: Main driver structure
+ * @tid: Thread ID
+ *
+ * Returns: Zero for success, negative number on error
+ */
+static int cpr_thread_init(struct cpr_drv *drv, int tid)
+{
+	const struct cpr_desc *desc = drv->desc;
+	const struct cpr_thread_desc *tdesc = desc->threads[tid];
+	struct cpr_thread *thread = &drv->threads[tid];
+	int ret;
+
+	thread->id = tid;
+	thread->drv = drv;
+	thread->desc = tdesc;
+	thread->fuse_corners = devm_kcalloc(drv->dev,
+					    tdesc->num_fuse_corners +
+					    drv->extra_corners,
+					    sizeof(*thread->fuse_corners),
+					    GFP_KERNEL);
+	if (!thread->fuse_corners)
+		return -ENOMEM;
+
+	thread->cpr_fuses = cpr_get_fuses(drv->dev, tid,
+					  tdesc->num_fuse_corners);
+	if (IS_ERR(thread->cpr_fuses))
+		return PTR_ERR(thread->cpr_fuses);
+
+	ret = cpr_populate_ring_osc_idx(thread->drv->dev, thread->fuse_corners,
+					thread->cpr_fuses,
+					tdesc->num_fuse_corners);
+	if (ret)
+		return ret;
+
+	ret = cpr_fuse_corner_init(thread);
+	if (ret)
+		return ret;
+
+	thread->pd.name = devm_kasprintf(drv->dev, GFP_KERNEL,
+					 "%s_thread%d",
+					 drv->dev->of_node->full_name,
+					 thread->id);
+	if (!thread->pd.name)
+		return -EINVAL;
+
+	thread->pd.power_off = cpr_power_off;
+	thread->pd.power_on = cpr_power_on;
+	thread->pd.set_performance_state = cpr_set_performance_state;
+	thread->pd.opp_to_performance_state = cpr_get_performance_state;
+	thread->pd.attach_dev = cpr_pd_attach_dev;
+	thread->pd.detach_dev = cpr_pd_detach_dev;
+
+	/* Anything later than CPR1 must be always-on for now */
+	thread->pd.flags = GENPD_FLAG_ALWAYS_ON;
+
+	drv->cell_data.domains[tid] = &thread->pd;
+
+	ret = pm_genpd_init(&thread->pd, NULL, false);
+	if (ret)
+		return ret;
+
+	/* On CPRhardened, the interrupts are managed in firmware */
+	if (desc->cpr_type < CTRL_TYPE_CPRH) {
+		INIT_WORK(&thread->restart_work, cpr_restart_worker);
+
+		ret = devm_request_threaded_irq(drv->dev, drv->irq,
+						NULL, cpr_irq_handler,
+						IRQF_ONESHOT |
+						IRQF_TRIGGER_RISING,
+						"cpr", drv);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+/**
+ * cpr3_resources_init - Initialize resources used by this driver
+ * @pdev: Platform device
+ * @drv:  Main driver structure
+ *
+ * Returns: Zero for success, negative number on error
+ */
+static int cpr3_resources_init(struct platform_device *pdev,
+			       struct cpr_drv *drv)
+{
+	const struct cpr_desc *desc = drv->desc;
+	struct cpr_thread *threads = drv->threads;
+	unsigned int i;
+	u8 cid_mask = 0;
+
+	/*
+	 * Here, we are accounting for the following usecases:
+	 * - One controller
+	 *   - One or multiple threads on the same iospace
+	 *
+	 * - Multiple controllers
+	 *   - Each controller has its own iospace and each
+	 *     may have one or multiple threads in their
+	 *     parent controller's iospace
+	 *
+	 * Then, to avoid complicating the code for no reason,
+	 * this also needs a mandatory order in the list of
+	 * threads which implies that all of them from the same
+	 * controllers are specified sequentially. As an example:
+	 *
+	 *      C0-T0, C0-T1...C0-Tn, C1-T0, C1-T1...C1-Tn
+	 */
+	for (i = 0; i < desc->num_threads; i++) {
+		u8 cid = desc->threads[i]->controller_id;
+
+		if (cid_mask & BIT(cid)) {
+			if (desc->threads[i - 1]->controller_id != cid) {
+				dev_err(drv->dev,
+					"Bad threads order. Please fix!\n");
+				return -EINVAL;
+			}
+			threads[i].base = threads[i - 1].base;
+			continue;
+		}
+
+		dev_dbg(drv->dev, "Mapping resource for CID %d\n", cid);
+		threads[i].base = devm_platform_ioremap_resource(pdev, cid);
+		if (IS_ERR(threads[i].base))
+			return PTR_ERR(threads[i].base);
+		cid_mask |= BIT(cid);
+	}
+
+	return 0;
+}
+
+static int cpr_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct cpr_drv *drv;
+	const struct cpr_desc *desc;
+	const struct cpr_acc_desc *data;
+	struct device_node *np;
+	unsigned int i;
+	int ret;
+
+	data = of_device_get_match_data(dev);
+	if (!data || !data->cpr_desc)
+		return -EINVAL;
+
+	desc = data->cpr_desc;
+
+	/* CPRh disallows MEM-ACC access from the HLOS */
+	if (!data->acc_desc && desc->cpr_type < CTRL_TYPE_CPRH)
+		return -EINVAL;
+
+	drv = devm_kzalloc(dev, sizeof(*drv), GFP_KERNEL);
+	if (!drv)
+		return -ENOMEM;
+
+	drv->dev = dev;
+	drv->desc = desc;
+	drv->num_threads = desc->num_threads;
+	drv->threads = devm_kcalloc(dev, drv->num_threads,
+				    sizeof(*drv->threads), GFP_KERNEL);
+	if (!drv->threads)
+		return -ENOMEM;
+
+	drv->cell_data.num_domains = desc->num_threads,
+	drv->cell_data.domains = devm_kcalloc(drv->dev,
+					      drv->cell_data.num_domains,
+					      sizeof(*drv->cell_data.domains),
+					      GFP_KERNEL);
+	if (!drv->cell_data.domains)
+		return -ENOMEM;
+
+	if (data->acc_desc)
+		drv->acc_desc = data->acc_desc;
+
+	mutex_init(&drv->lock);
+
+	if (desc->cpr_type < CTRL_TYPE_CPRH) {
+		np = of_parse_phandle(dev->of_node, "acc-syscon", 0);
+		if (!np)
+			return -ENODEV;
+
+		drv->tcsr = syscon_node_to_regmap(np);
+		of_node_put(np);
+		if (IS_ERR(drv->tcsr))
+			return PTR_ERR(drv->tcsr);
+	}
+
+	ret = cpr3_resources_init(pdev, drv);
+	if (ret)
+		return ret;
+
+	drv->irq = platform_get_irq_optional(pdev, 0);
+	if ((desc->cpr_type != CTRL_TYPE_CPRH) && (drv->irq < 0))
+		return -EINVAL;
+
+	/* On CPRhardened, vreg access it not allowed */
+	drv->vreg = devm_regulator_get_optional(dev, "vdd");
+	if (desc->cpr_type != CTRL_TYPE_CPRH && IS_ERR(drv->vreg))
+		return PTR_ERR(drv->vreg);
+
+	/*
+	 * On at least CPRhardened, vreg is unaccessible and there is no
+	 * way to read linear step from that regulator, hence it is hardcoded
+	 * in the driver;
+	 * When the vreg_step is not declared in the cpr data (or is zero),
+	 * then having access to the vreg regulator is mandatory, as this
+	 * will be retrieved through the regulator API.
+	 */
+	if (desc->vreg_step_fixed)
+		drv->vreg_step = desc->vreg_step_fixed;
+	else
+		drv->vreg_step = regulator_get_linear_step(drv->vreg);
+
+	if (!drv->vreg_step)
+		return -EINVAL;
+
+	/*
+	 * Initialize fuse corners, since it simply depends
+	 * on data in efuses.
+	 * Everything related to (virtual) corners has to be
+	 * initialized after attaching to the power domain,
+	 * since it depends on the CPU's OPP table.
+	 */
+	ret = cpr_read_efuse(dev, "cpr_fuse_revision", &drv->fusing_rev);
+	if (ret)
+		return ret;
+
+	ret = cpr_read_efuse(dev, "cpr_speed_bin", &drv->speed_bin);
+	if (ret)
+		return ret;
+
+	/*
+	 * If the APM threshold and hysteresis params are specified,
+	 * reserve a corner for APM crossover, used by OSM and CPRh
+	 * HW to set the supply voltage during the APM switch routine
+	 */
+	if (desc->cpr_type == CTRL_TYPE_CPRH &&
+	    desc->apm_threshold && desc->apm_hysteresis)
+		drv->extra_corners++;
+
+	/* Initialize all threads */
+	for (i = 0; i < desc->num_threads; i++) {
+		ret = cpr_thread_init(drv, i);
+		if (ret)
+			return ret;
+	}
+
+	/* Initialize global parameters */
+	ret = cpr3_init_parameters(drv);
+	if (ret)
+		return ret;
+
+	/* Write initial configuration on all threads */
+	for (i = 0; i < desc->num_threads; i++) {
+		ret = cpr_configure(&drv->threads[i]);
+		if (ret)
+			return ret;
+	}
+
+	ret = of_genpd_add_provider_onecell(dev->of_node, &drv->cell_data);
+	if (ret)
+		return ret;
+
+	platform_set_drvdata(pdev, drv);
+	cpr3_debugfs_init(drv);
+
+	return 0;
+}
+
+static int cpr_remove(struct platform_device *pdev)
+{
+	struct cpr_drv *drv = platform_get_drvdata(pdev);
+	int i;
+
+	of_genpd_del_provider(pdev->dev.of_node);
+
+	for (i = 0; i < drv->num_threads; i++) {
+		cpr_ctl_disable(&drv->threads[i]);
+		cpr_irq_set(&drv->threads[i], 0);
+		pm_genpd_remove(&drv->threads[i].pd);
+	}
+
+	debugfs_remove_recursive(drv->debugfs);
+
+	return 0;
+}
+
+static const struct of_device_id cpr3_match_table[] = {
+	{ .compatible = "qcom,sdm630-cprh", .data = &sdm630_cpr_acc_desc },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, cpr3_match_table);
+
+static struct platform_driver cpr3_driver = {
+	.probe		= cpr_probe,
+	.remove		= cpr_remove,
+	.driver		= {
+		.name	= "qcom-cpr3",
+		.of_match_table = cpr3_match_table,
+	},
+};
+module_platform_driver(cpr3_driver)
+
+MODULE_DESCRIPTION("Core Power Reduction (CPR) v3/v4 driver");
+MODULE_LICENSE("GPL v2");
-- 
2.29.2


^ permalink raw reply related	[flat|nested] 25+ messages in thread

* [PATCH 09/13] MAINTAINERS: Add entry for Qualcomm CPRv3/v4/Hardened driver
  2020-11-26 18:45 [PATCH 00/13] Enable CPRh/3/4, CPU Scaling on various QCOM SoCs AngeloGioacchino Del Regno
                   ` (7 preceding siblings ...)
  2020-11-26 18:45 ` [PATCH 08/13] soc: qcom: Add support for Core Power Reduction v3, v4 and Hardened AngeloGioacchino Del Regno
@ 2020-11-26 18:45 ` AngeloGioacchino Del Regno
  2020-11-26 18:45 ` [PATCH 10/13] dt-bindings: soc: qcom: cpr3: Add bindings for CPR3 driver AngeloGioacchino Del Regno
                   ` (3 subsequent siblings)
  12 siblings, 0 replies; 25+ messages in thread
From: AngeloGioacchino Del Regno @ 2020-11-26 18:45 UTC (permalink / raw)
  To: linux-arm-msm
  Cc: linux-kernel, devicetree, linux-pm, ulf.hansson,
	jorge.ramirez-ortiz, broonie, lgirdwood, daniel.lezcano, nks,
	bjorn.andersson, agross, robh+dt, viresh.kumar, rjw,
	konrad.dybcio, martin.botka, marijn.suijten, phone-devel,
	AngeloGioacchino Del Regno

Add maintainers entry for the Qualcomm CPR3/CPR4/CPRh driver.

Signed-off-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@somainline.org>
---
 MAINTAINERS | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index c8c006f08dcc..a37c3ae91f2f 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -14461,6 +14461,12 @@ S:	Maintained
 F:	Documentation/devicetree/bindings/soc/qcom/qcom,cpr.yaml
 F:	drivers/soc/qcom/cpr.c
 
+QUALCOMM CORE POWER REDUCTION v3/v4/Hardened AVS DRIVER
+M:	AngeloGioacchino Del Regno <angelogioacchino.delregno@somainline.org>
+S:	Maintained
+F:	Documentation/devicetree/bindings/soc/qcom/qcom,cpr3.yaml
+F:	drivers/soc/qcom/cpr3.c
+
 QUALCOMM CPUFREQ DRIVER MSM8996/APQ8096
 M:	Ilia Lin <ilia.lin@kernel.org>
 L:	linux-pm@vger.kernel.org
-- 
2.29.2


^ permalink raw reply related	[flat|nested] 25+ messages in thread

* [PATCH 10/13] dt-bindings: soc: qcom: cpr3: Add bindings for CPR3 driver
  2020-11-26 18:45 [PATCH 00/13] Enable CPRh/3/4, CPU Scaling on various QCOM SoCs AngeloGioacchino Del Regno
                   ` (8 preceding siblings ...)
  2020-11-26 18:45 ` [PATCH 09/13] MAINTAINERS: Add entry for Qualcomm CPRv3/v4/Hardened driver AngeloGioacchino Del Regno
@ 2020-11-26 18:45 ` AngeloGioacchino Del Regno
  2020-11-30 17:22   ` Rob Herring
  2020-11-26 18:45 ` [PATCH 11/13] dt-bindings: cpufreq: Convert qcom-cpufreq-hw to YAML binding AngeloGioacchino Del Regno
                   ` (2 subsequent siblings)
  12 siblings, 1 reply; 25+ messages in thread
From: AngeloGioacchino Del Regno @ 2020-11-26 18:45 UTC (permalink / raw)
  To: linux-arm-msm
  Cc: linux-kernel, devicetree, linux-pm, ulf.hansson,
	jorge.ramirez-ortiz, broonie, lgirdwood, daniel.lezcano, nks,
	bjorn.andersson, agross, robh+dt, viresh.kumar, rjw,
	konrad.dybcio, martin.botka, marijn.suijten, phone-devel,
	AngeloGioacchino Del Regno

Add the bindings for the CPR3 driver to the documentation.

Signed-off-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@somainline.org>
---
 .../bindings/soc/qcom/qcom,cpr3.yaml          | 226 ++++++++++++++++++
 1 file changed, 226 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/soc/qcom/qcom,cpr3.yaml

diff --git a/Documentation/devicetree/bindings/soc/qcom/qcom,cpr3.yaml b/Documentation/devicetree/bindings/soc/qcom/qcom,cpr3.yaml
new file mode 100644
index 000000000000..58cd9a6e9bde
--- /dev/null
+++ b/Documentation/devicetree/bindings/soc/qcom/qcom,cpr3.yaml
@@ -0,0 +1,226 @@
+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: "http://devicetree.org/schemas/soc/qcom/qcom,cpr3.yaml#"
+$schema: "http://devicetree.org/meta-schemas/core.yaml#"
+
+title: Qualcomm Core Power Reduction v3/v4/Hardened (CPR3, CPR4, CPRh)
+
+description: |
+  CPR (Core Power Reduction) is a technology to reduce core power on a CPU
+  or other device. Each OPP of a device corresponds to a "corner" that has
+  a range of valid voltages for a particular frequency. While the device is
+  running at a particular frequency, CPR monitors dynamic factors such as
+  temperature, etc. and suggests or, in the CPR-Hardened case performs,
+  adjustments to the voltage to save power and meet silicon characteristic
+  requirements.
+
+maintainers:
+  - AngeloGioacchino Del Regno <angelogioacchino.delregno@somainline.org>
+
+properties:
+  compatible:
+    items:
+      - enum:
+          - qcom,sdm630-cprh
+      - const: qcom,cpr3
+      - const: qcom,cpr4
+      - const: qcom,cprh
+
+  reg:
+    description: Base address and size of the CPR controller(s)
+    minItems: 1
+
+  interrupts:
+    maxItems: 1
+
+  clock-names:
+    - const: ref
+
+  clocks:
+    items:
+      - description: CPR reference clock
+
+  vdd-supply:
+    description: Autonomous Phase Control (APC) or other power supply
+
+  '#power-domain-cells':
+    const: 1
+
+  acc-syscon:
+    description: phandle to syscon for writing ACC settings
+
+  nvmem-cells:
+    minItems: 10
+    description: Cells containing the fuse corners and revision data
+
+  nvmem-cell-names:
+    minItems: 10
+
+  operating-points-v2: true
+
+required:
+  - compatible
+  - reg
+  - clock-names
+  - clocks
+  - "#power-domain-cells"
+  - nvmem-cells
+  - nvmem-cell-names
+  - operating-points-v2
+
+additionalProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/clock/qcom,gcc-sdm660.h>
+    #include <dt-bindings/interrupt-controller/irq.h>
+
+    cpus {
+        #address-cells = <2>;
+        #size-cells = <0>;
+
+        cpu@100 {
+            operating-points-v2 = <&cpu_gold_opp_table>;
+            power-domains = <&apc_cprh 0>;
+            power-domain-names = "cprh";
+        };
+
+        cpu@0 {
+            operating-points-v2 = <&cpu_silver_opp_table>;
+            power-domains = <&apc_cprh 1>;
+            power-domain-names = "cprh";
+        };
+    }
+
+    cpu_silver_opp_table: cpu-silver-opp-table {
+        compatible = "operating-points-v2";
+        opp-shared;
+
+        opp-1843200000 {
+            opp-hz = /bits/ 64 <1843200000>;
+            required-opps = <&cprh_opp3>;
+        };
+        opp-1094400000 {
+            opp-hz = /bits/ 64 <1094400000>;
+            required-opps = <&cprh_opp2>;
+        };
+        opp-300000000 {
+            opp-hz = /bits/ 64 <300000000>;
+            required-opps = <&cprh_opp1>;
+        };
+    };
+
+    cpu_gold_opp_table: cpu-gold-opp-table {
+        compatible = "operating-points-v2";
+        opp-shared;
+
+        opp-2208000000 {
+            opp-hz = /bits/ 64 <2208000000>;
+            required-opps = <&cprh_opp3>;
+        };
+        opp-1113600000 {
+            opp-hz = /bits/ 64 <1113600000>;
+            required-opps = <&cprh_opp2>;
+        };
+        opp-300000000 {
+            opp-hz = /bits/ 64 <300000000>;
+            required-opps = <&cprh_opp1>;
+        };
+    };
+
+    cprh_opp_table: cpr-hardened-opp-table {
+        compatible = "operating-points-v2-qcom-level";
+
+        cprh_opp1: opp1 {
+            opp-level = <1>;
+            qcom,opp-fuse-level = <1>;
+        };
+        cprh_opp2: opp2 {
+            opp-level = <2>;
+            qcom,opp-fuse-level = <2>;
+        };
+        cprh_opp3: opp3 {
+            opp-level = <3>;
+            qcom,opp-fuse-level = <2 3>;
+        };
+    }
+
+    apc_cprh: power-controller@179c4000 {
+        compatible = "qcom,sdm630-cprh", "qcom,cprh";
+        reg = <0x0179c4000 0x4000>, <0x0179c8000 0x4000>;
+        clocks = <&gcc GCC_HMSS_RBCPR_CLK>;
+        clock-names = "ref";
+        assigned-clocks = <&gcc GCC_HMSS_RBCPR_CLK>;
+        assigned-clock-rates = <19200000>;
+
+        #power-domain-cells = <1>;
+        operating-points-v2 = <&cprh_opp_table>;
+
+        nvmem-cells = <&cpr_efuse_speedbin>,
+                      <&cpr_fuse_revision>,
+                      <&cpr_quot00_perfcl>,
+                      <&cpr_quot01_perfcl>,
+                      <&cpr_quot02_perfcl>,
+                      <&cpr_quot03_perfcl>,
+                      <&cpr_quot04_perfcl>,
+                      <&cpr_quot_offset01_perfcl>,
+                      <&cpr_quot_offset02_perfcl>,
+                      <&cpr_quot_offset03_perfcl>,
+                      <&cpr_quot_offset04_perfcl>,
+                      <&cpr_init_voltage00_perfcl>,
+                      <&cpr_init_voltage01_perfcl>,
+                      <&cpr_init_voltage02_perfcl>,
+                      <&cpr_init_voltage03_perfcl>,
+                      <&cpr_init_voltage04_perfcl>,
+                      <&cpr_ro_sel00_perfcl>,
+                      <&cpr_ro_sel01_perfcl>,
+                      <&cpr_ro_sel02_perfcl>,
+                      <&cpr_ro_sel03_perfcl>,
+                      <&cpr_ro_sel04_perfcl>,
+                      <&cpr_quot00_pwrcl>,
+                      <&cpr_quot01_pwrcl>,
+                      <&cpr_quot02_pwrcl>,
+                      <&cpr_quot_offset01_pwrcl>,
+                      <&cpr_quot_offset02_pwrcl>,
+                      <&cpr_init_voltage00_pwrcl>,
+                      <&cpr_init_voltage01_pwrcl>,
+                      <&cpr_init_voltage02_pwrcl>,
+                      <&cpr_ro_sel00_pwrcl>,
+                      <&cpr_ro_sel01_pwrcl>,
+                      <&cpr_ro_sel02_pwrcl>;
+
+        nvmem-cell-names = "cpr_speed_bin",
+                           "cpr_fuse_revision",
+                           "cpr0_quotient1",
+                           "cpr0_quotient2",
+                           "cpr0_quotient3",
+                           "cpr0_quotient4",
+                           "cpr0_quotient5",
+                           "cpr0_quotient_offset2",
+                           "cpr0_quotient_offset3",
+                           "cpr0_quotient_offset4",
+                           "cpr0_quotient_offset5",
+                           "cpr0_init_voltage1",
+                           "cpr0_init_voltage2",
+                           "cpr0_init_voltage3",
+                           "cpr0_init_voltage4",
+                           "cpr0_init_voltage5",
+                           "cpr0_ring_osc1",
+                           "cpr0_ring_osc2",
+                           "cpr0_ring_osc3",
+                           "cpr0_ring_osc4",
+                           "cpr0_ring_osc5",
+                           "cpr1_quotient1",
+                           "cpr1_quotient2",
+                           "cpr1_quotient3",
+                           "cpr1_quotient_offset2",
+                           "cpr1_quotient_offset3",
+                           "cpr1_init_voltage1",
+                           "cpr1_init_voltage2",
+                           "cpr1_init_voltage3",
+                           "cpr1_ring_osc1",
+                           "cpr1_ring_osc2",
+                           "cpr1_ring_osc3";
+    };
+...
-- 
2.29.2


^ permalink raw reply related	[flat|nested] 25+ messages in thread

* [PATCH 11/13] dt-bindings: cpufreq: Convert qcom-cpufreq-hw to YAML binding
  2020-11-26 18:45 [PATCH 00/13] Enable CPRh/3/4, CPU Scaling on various QCOM SoCs AngeloGioacchino Del Regno
                   ` (9 preceding siblings ...)
  2020-11-26 18:45 ` [PATCH 10/13] dt-bindings: soc: qcom: cpr3: Add bindings for CPR3 driver AngeloGioacchino Del Regno
@ 2020-11-26 18:45 ` AngeloGioacchino Del Regno
  2020-11-30 17:23   ` Rob Herring
  2020-11-26 18:45 ` [PATCH 12/13] cpufreq: qcom-hw: Implement CPRh aware OSM programming AngeloGioacchino Del Regno
  2020-11-26 18:45 ` [PATCH 13/13] dt-bindings: cpufreq: qcom-hw: Add bindings for 8998 AngeloGioacchino Del Regno
  12 siblings, 1 reply; 25+ messages in thread
From: AngeloGioacchino Del Regno @ 2020-11-26 18:45 UTC (permalink / raw)
  To: linux-arm-msm
  Cc: linux-kernel, devicetree, linux-pm, ulf.hansson,
	jorge.ramirez-ortiz, broonie, lgirdwood, daniel.lezcano, nks,
	bjorn.andersson, agross, robh+dt, viresh.kumar, rjw,
	konrad.dybcio, martin.botka, marijn.suijten, phone-devel,
	AngeloGioacchino Del Regno

Convert the qcom-cpufreq-hw documentation to YAML binding as
qcom,cpufreq-hw.yaml.

Signed-off-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@somainline.org>
---
 .../bindings/cpufreq/cpufreq-qcom-hw.txt      | 173 +---------------
 .../bindings/cpufreq/qcom,cpufreq-hw.yaml     | 196 ++++++++++++++++++
 2 files changed, 197 insertions(+), 172 deletions(-)
 create mode 100644 Documentation/devicetree/bindings/cpufreq/qcom,cpufreq-hw.yaml

diff --git a/Documentation/devicetree/bindings/cpufreq/cpufreq-qcom-hw.txt b/Documentation/devicetree/bindings/cpufreq/cpufreq-qcom-hw.txt
index 9299028ee712..bd4e81f6f835 100644
--- a/Documentation/devicetree/bindings/cpufreq/cpufreq-qcom-hw.txt
+++ b/Documentation/devicetree/bindings/cpufreq/cpufreq-qcom-hw.txt
@@ -1,172 +1 @@
-Qualcomm Technologies, Inc. CPUFREQ Bindings
-
-CPUFREQ HW is a hardware engine used by some Qualcomm Technologies, Inc. (QTI)
-SoCs to manage frequency in hardware. It is capable of controlling frequency
-for multiple clusters.
-
-Properties:
-- compatible
-	Usage:		required
-	Value type:	<string>
-	Definition:	must be "qcom,cpufreq-hw" or "qcom,cpufreq-epss".
-
-- clocks
-	Usage:		required
-	Value type:	<phandle> From common clock binding.
-	Definition:	clock handle for XO clock and GPLL0 clock.
-
-- clock-names
-	Usage:		required
-	Value type:	<string> From common clock binding.
-	Definition:	must be "xo", "alternate".
-
-- reg
-	Usage:		required
-	Value type:	<prop-encoded-array>
-	Definition:	Addresses and sizes for the memory of the HW bases in
-			each frequency domain.
-- reg-names
-	Usage:		Optional
-	Value type:	<string>
-	Definition:	Frequency domain name i.e.
-			"freq-domain0", "freq-domain1".
-
-- #freq-domain-cells:
-	Usage:		required.
-	Definition:	Number of cells in a freqency domain specifier.
-
-* Property qcom,freq-domain
-Devices supporting freq-domain must set their "qcom,freq-domain" property with
-phandle to a cpufreq_hw followed by the Domain ID(0/1) in the CPU DT node.
-
-
-Example:
-
-Example 1: Dual-cluster, Quad-core per cluster. CPUs within a cluster switch
-DCVS state together.
-
-/ {
-	cpus {
-		#address-cells = <2>;
-		#size-cells = <0>;
-
-		CPU0: cpu@0 {
-			device_type = "cpu";
-			compatible = "qcom,kryo385";
-			reg = <0x0 0x0>;
-			enable-method = "psci";
-			next-level-cache = <&L2_0>;
-			qcom,freq-domain = <&cpufreq_hw 0>;
-			L2_0: l2-cache {
-				compatible = "cache";
-				next-level-cache = <&L3_0>;
-				L3_0: l3-cache {
-				      compatible = "cache";
-				};
-			};
-		};
-
-		CPU1: cpu@100 {
-			device_type = "cpu";
-			compatible = "qcom,kryo385";
-			reg = <0x0 0x100>;
-			enable-method = "psci";
-			next-level-cache = <&L2_100>;
-			qcom,freq-domain = <&cpufreq_hw 0>;
-			L2_100: l2-cache {
-				compatible = "cache";
-				next-level-cache = <&L3_0>;
-			};
-		};
-
-		CPU2: cpu@200 {
-			device_type = "cpu";
-			compatible = "qcom,kryo385";
-			reg = <0x0 0x200>;
-			enable-method = "psci";
-			next-level-cache = <&L2_200>;
-			qcom,freq-domain = <&cpufreq_hw 0>;
-			L2_200: l2-cache {
-				compatible = "cache";
-				next-level-cache = <&L3_0>;
-			};
-		};
-
-		CPU3: cpu@300 {
-			device_type = "cpu";
-			compatible = "qcom,kryo385";
-			reg = <0x0 0x300>;
-			enable-method = "psci";
-			next-level-cache = <&L2_300>;
-			qcom,freq-domain = <&cpufreq_hw 0>;
-			L2_300: l2-cache {
-				compatible = "cache";
-				next-level-cache = <&L3_0>;
-			};
-		};
-
-		CPU4: cpu@400 {
-			device_type = "cpu";
-			compatible = "qcom,kryo385";
-			reg = <0x0 0x400>;
-			enable-method = "psci";
-			next-level-cache = <&L2_400>;
-			qcom,freq-domain = <&cpufreq_hw 1>;
-			L2_400: l2-cache {
-				compatible = "cache";
-				next-level-cache = <&L3_0>;
-			};
-		};
-
-		CPU5: cpu@500 {
-			device_type = "cpu";
-			compatible = "qcom,kryo385";
-			reg = <0x0 0x500>;
-			enable-method = "psci";
-			next-level-cache = <&L2_500>;
-			qcom,freq-domain = <&cpufreq_hw 1>;
-			L2_500: l2-cache {
-				compatible = "cache";
-				next-level-cache = <&L3_0>;
-			};
-		};
-
-		CPU6: cpu@600 {
-			device_type = "cpu";
-			compatible = "qcom,kryo385";
-			reg = <0x0 0x600>;
-			enable-method = "psci";
-			next-level-cache = <&L2_600>;
-			qcom,freq-domain = <&cpufreq_hw 1>;
-			L2_600: l2-cache {
-				compatible = "cache";
-				next-level-cache = <&L3_0>;
-			};
-		};
-
-		CPU7: cpu@700 {
-			device_type = "cpu";
-			compatible = "qcom,kryo385";
-			reg = <0x0 0x700>;
-			enable-method = "psci";
-			next-level-cache = <&L2_700>;
-			qcom,freq-domain = <&cpufreq_hw 1>;
-			L2_700: l2-cache {
-				compatible = "cache";
-				next-level-cache = <&L3_0>;
-			};
-		};
-	};
-
- soc {
-	cpufreq_hw: cpufreq@17d43000 {
-		compatible = "qcom,cpufreq-hw";
-		reg = <0x17d43000 0x1400>, <0x17d45800 0x1400>;
-		reg-names = "freq-domain0", "freq-domain1";
-
-		clocks = <&rpmhcc RPMH_CXO_CLK>, <&gcc GPLL0>;
-		clock-names = "xo", "alternate";
-
-		#freq-domain-cells = <1>;
-	};
-}
+This file has been moved to qcom,cpufreq-hw.yaml
diff --git a/Documentation/devicetree/bindings/cpufreq/qcom,cpufreq-hw.yaml b/Documentation/devicetree/bindings/cpufreq/qcom,cpufreq-hw.yaml
new file mode 100644
index 000000000000..94a56317b14b
--- /dev/null
+++ b/Documentation/devicetree/bindings/cpufreq/qcom,cpufreq-hw.yaml
@@ -0,0 +1,196 @@
+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: "http://devicetree.org/schemas/cpufreq/qcom,cpufreq-hw.yaml#"
+$schema: "http://devicetree.org/meta-schemas/core.yaml#"
+
+title: Qualcomm Technologies, Inc. CPUFREQ HW Bindings
+
+description: |
+  CPUFREQ HW is a hardware engine used by some Qualcomm Technologies, Inc. (QTI)
+  SoCs to manage frequency in hardware. It is capable of controlling frequency
+  for multiple clusters.
+
+maintainers:
+  - TBD
+
+properties:
+  compatible:
+    items:
+      - enum:
+          - qcom,cpufreq-hw
+          - qcom,cpufreq-hw-8998
+          - qcom,cpufreq-epss
+
+  reg:
+    minItems: 2
+    maxItems: 2
+
+  reg-names:
+    description:
+      Frequency domain register region for each domain.
+    items:
+      - const: "freq-domain0"
+      - const: "freq-domain1"
+
+  clock-names:
+    - const: xo
+    - const: ref
+
+  clocks:
+    maxItems: 2
+
+  '#freq-domain-cells':
+    description: Number of cells in a freqency domain specifier.
+    const: 1
+
+  operating-points-v2: true
+
+  qcom,freq-domain:
+    $ref: '/schemas/types.yaml#/definitions/phandle'
+    description: |
+      Devices supporting freq-domain must set their "qcom,freq-domain"
+      property with phandle to a cpufreq_hw followed by the Domain ID(0/1)
+      in the CPU DT node.
+
+required:
+  - compatible
+  - reg
+  - clock-names
+  - clocks
+  - "#freq-domain-cells"
+
+additionalProperties: false
+
+examples:
+  - |
+    // Example 1: Dual-cluster, Quad-core per cluster. CPUs within a
+    //            cluster switch DCVS state together.
+
+    #include <dt-bindings/clock/qcom,rpmh.h>
+    #include <dt-bindings/clock/qcom,gcc-sc7180.h>
+
+    cpus {
+        #address-cells = <2>;
+        #size-cells = <0>;
+
+        CPU0: cpu@0 {
+            device_type = "cpu";
+            compatible = "qcom,kryo385";
+            reg = <0x0 0x0>;
+            enable-method = "psci";
+            next-level-cache = <&L2_0>;
+            qcom,freq-domain = <&cpufreq_hw 0>;
+            L2_0: l2-cache {
+                compatible = "cache";
+                next-level-cache = <&L3_0>;
+                L3_0: l3-cache {
+                      compatible = "cache";
+                };
+            };
+        };
+
+        CPU1: cpu@100 {
+            device_type = "cpu";
+            compatible = "qcom,kryo385";
+            reg = <0x0 0x100>;
+            enable-method = "psci";
+            next-level-cache = <&L2_100>;
+            qcom,freq-domain = <&cpufreq_hw 0>;
+            L2_100: l2-cache {
+                compatible = "cache";
+                next-level-cache = <&L3_0>;
+            };
+        };
+
+        CPU2: cpu@200 {
+            device_type = "cpu";
+            compatible = "qcom,kryo385";
+            reg = <0x0 0x200>;
+            enable-method = "psci";
+            next-level-cache = <&L2_200>;
+            qcom,freq-domain = <&cpufreq_hw 0>;
+            L2_200: l2-cache {
+                compatible = "cache";
+                next-level-cache = <&L3_0>;
+            };
+        };
+
+        CPU3: cpu@300 {
+            device_type = "cpu";
+            compatible = "qcom,kryo385";
+            reg = <0x0 0x300>;
+            enable-method = "psci";
+            next-level-cache = <&L2_300>;
+            qcom,freq-domain = <&cpufreq_hw 0>;
+            L2_300: l2-cache {
+                compatible = "cache";
+                next-level-cache = <&L3_0>;
+            };
+        };
+
+        CPU4: cpu@400 {
+            device_type = "cpu";
+            compatible = "qcom,kryo385";
+            reg = <0x0 0x400>;
+            enable-method = "psci";
+            next-level-cache = <&L2_400>;
+            qcom,freq-domain = <&cpufreq_hw 1>;
+            L2_400: l2-cache {
+                compatible = "cache";
+                next-level-cache = <&L3_0>;
+            };
+        };
+
+        CPU5: cpu@500 {
+            device_type = "cpu";
+            compatible = "qcom,kryo385";
+            reg = <0x0 0x500>;
+            enable-method = "psci";
+            next-level-cache = <&L2_500>;
+            qcom,freq-domain = <&cpufreq_hw 1>;
+            L2_500: l2-cache {
+                compatible = "cache";
+                next-level-cache = <&L3_0>;
+            };
+        };
+
+        CPU6: cpu@600 {
+            device_type = "cpu";
+            compatible = "qcom,kryo385";
+            reg = <0x0 0x600>;
+            enable-method = "psci";
+            next-level-cache = <&L2_600>;
+            qcom,freq-domain = <&cpufreq_hw 1>;
+            L2_600: l2-cache {
+                compatible = "cache";
+                next-level-cache = <&L3_0>;
+            };
+        };
+
+        CPU7: cpu@700 {
+            device_type = "cpu";
+            compatible = "qcom,kryo385";
+            reg = <0x0 0x700>;
+            enable-method = "psci";
+            next-level-cache = <&L2_700>;
+            qcom,freq-domain = <&cpufreq_hw 1>;
+            L2_700: l2-cache {
+                compatible = "cache";
+                next-level-cache = <&L3_0>;
+            };
+        };
+    };
+
+    soc {
+        cpufreq_hw: cpufreq@17d43000 {
+            compatible = "qcom,cpufreq-hw";
+            reg = <0x17d43000 0x1400>, <0x17d45800 0x1400>;
+            reg-names = "freq-domain0", "freq-domain1";
+
+            clocks = <&rpmhcc RPMH_CXO_CLK>, <&gcc GPLL0>;
+            clock-names = "xo", "alternate";
+
+            #freq-domain-cells = <1>;
+    };
+...
-- 
2.29.2


^ permalink raw reply related	[flat|nested] 25+ messages in thread

* [PATCH 12/13] cpufreq: qcom-hw: Implement CPRh aware OSM programming
  2020-11-26 18:45 [PATCH 00/13] Enable CPRh/3/4, CPU Scaling on various QCOM SoCs AngeloGioacchino Del Regno
                   ` (10 preceding siblings ...)
  2020-11-26 18:45 ` [PATCH 11/13] dt-bindings: cpufreq: Convert qcom-cpufreq-hw to YAML binding AngeloGioacchino Del Regno
@ 2020-11-26 18:45 ` AngeloGioacchino Del Regno
  2020-12-18  7:16   ` Viresh Kumar
  2020-11-26 18:45 ` [PATCH 13/13] dt-bindings: cpufreq: qcom-hw: Add bindings for 8998 AngeloGioacchino Del Regno
  12 siblings, 1 reply; 25+ messages in thread
From: AngeloGioacchino Del Regno @ 2020-11-26 18:45 UTC (permalink / raw)
  To: linux-arm-msm
  Cc: linux-kernel, devicetree, linux-pm, ulf.hansson,
	jorge.ramirez-ortiz, broonie, lgirdwood, daniel.lezcano, nks,
	bjorn.andersson, agross, robh+dt, viresh.kumar, rjw,
	konrad.dybcio, martin.botka, marijn.suijten, phone-devel,
	AngeloGioacchino Del Regno

On new SoCs (SDM845 onwards) the Operating State Manager (OSM) is
being programmed in the bootloader and write-protected by the
hypervisor, leaving to the OS read-only access to some of its
registers (in order to read the Lookup Tables and also some
status registers) and write access to the p-state register, for
for the OS to request a specific performance state to trigger a
DVFS switch on the CPU through the OSM hardware.

On old SoCs though (MSM8998, SDM630/660 and variants), the
bootloader will *not* initialize the OSM (and the CPRh, as it
is a requirement for it) before booting the OS, making any
request to trigger a performance state change ineffective, as
the hardware doesn't have any Lookup Table, nor is storing any
parameter to trigger a DVFS switch. In this case, basically all
of the OSM registers are *not* write protected for the OS, even
though some are - but write access is granted through SCM calls.

This commit introduces support for OSM programming, which has to
be done on these old SoCs that were distributed (almost?) always
with a bootloader that does not do any CPRh nor OSM init before
booting the kernel.
In order to program the OSM on these SoCs, it is necessary to
fullfill a "special" requirement: the Core Power Reduction
Hardened (CPRh) hardware block must be initialized, as the OSM
is "talking" to it in order to perform the Voltage part of DVFS;
here, we are calling initialization of this through Linux generic
power domains, specifically by requesting a genpd attach from the
qcom-cpufreq-hw driver, which will give back voltages associated
to each CPU frequency that has been declared in the OPPs, scaled
and interpolated with the previous one, and will also give us
parameters for the Array Power Mux (APM) and mem-acc, in order
for this driver to be then able to generate the Lookup Tables
that will be finally programmed to the OSM hardware.

After writing the parameters to the OSM and enabling it, all the
programming work will never happen anymore until a OS reboot, so
all of the allocations and "the rest" will be disposed-of: this
is done mainly to leave the code that was referred only to the
new SoCs intact, as to also emphasize on the fact that the OSM
HW is, in the end, the exact same; apart some register offsets
that are slightly different, the entire logic is the same.

Signed-off-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@somainline.org>
---
 drivers/cpufreq/qcom-cpufreq-hw.c | 914 +++++++++++++++++++++++++++++-
 1 file changed, 884 insertions(+), 30 deletions(-)

diff --git a/drivers/cpufreq/qcom-cpufreq-hw.c b/drivers/cpufreq/qcom-cpufreq-hw.c
index 9ed5341dc515..67a6f23335e1 100644
--- a/drivers/cpufreq/qcom-cpufreq-hw.c
+++ b/drivers/cpufreq/qcom-cpufreq-hw.c
@@ -1,33 +1,201 @@
 // SPDX-License-Identifier: GPL-2.0
 /*
  * Copyright (c) 2018, The Linux Foundation. All rights reserved.
+ *
+ * OSM hardware initial programming
+ * Copyright (C) 2020, AngeloGioacchino Del Regno
+ *                     <angelogioacchino.delregno@somainline.org>
  */
 
 #include <linux/bitfield.h>
 #include <linux/cpufreq.h>
+#include <linux/delay.h>
 #include <linux/init.h>
 #include <linux/interconnect.h>
 #include <linux/kernel.h>
 #include <linux/module.h>
 #include <linux/of_address.h>
 #include <linux/of_platform.h>
+#include <linux/pm_domain.h>
 #include <linux/pm_opp.h>
 #include <linux/slab.h>
+#include <linux/qcom_scm.h>
 
 #define LUT_MAX_ENTRIES			40U
-#define LUT_SRC				GENMASK(31, 30)
+#define LUT_SRC_845			GENMASK(31, 30)
+#define LUT_SRC_8998			GENMASK(27, 26)
+#define LUT_PLL_DIV			GENMASK(25, 24)
 #define LUT_L_VAL			GENMASK(7, 0)
 #define LUT_CORE_COUNT			GENMASK(18, 16)
+#define LUT_VOLT_VC			GENMASK(21, 16)
 #define LUT_VOLT			GENMASK(11, 0)
-#define CLK_HW_DIV			2
 #define LUT_TURBO_IND			1
+#define OSM_BOOT_TIME_US		5
+
+#define CYCLE_COUNTER_CLK_RATIO		GENMASK(5, 1)
+#define OSM_XO_RATIO_VAL		(10 - 1)
+#define CYCLE_COUNTER_USE_XO_EDGE	BIT(8)
+
+/* FSM Boost Control */
+#define CC_BOOST_EN			BIT(0)
+#define PS_BOOST_EN			BIT(1)
+#define DCVS_BOOST_EN			BIT(2)
+#define BOOST_TIMER_REG_HI		GENMASK(31, 16)
+#define BOOST_TIMER_REG_LO		GENMASK(15, 0)
+
+#define PLL_WAIT_LOCK_TIME_NS		2000
+#define SAFE_FREQ_WAIT_NS		1000
+#define DEXT_DECREMENT_WAIT_NS		200
+
+#define BOOST_SYNC_DELAY		5
+
+#define HYSTERESIS_UP_MASK		GENMASK(31, 16)
+#define HYSTERESIS_DN_MASK		GENMASK(15, 0)
+#define HYSTERESIS_CC_NS		200
+#define HYSTERESIS_LLM_NS		65535
+
+/* FSM Droop Control */
+#define PC_RET_EXIT_DROOP_EN		BIT(3)
+#define WFX_DROOP_EN			BIT(4)
+#define DCVS_DROOP_EN			BIT(5)
+#define DROOP_TIMER1			GENMASK(31, 16)
+#define DROOP_TIMER0			GENMASK(15, 0)
+#define DROOP_CTRL_VAL			(BIT(3) | BIT(17) | BIT(31))
+#define DROOP_TIMER_NS			100
+#define DROOP_WAIT_RELEASE_TIMER_NS	50
+#define DROOP_RELEASE_TIMER_NS		1
+
+/* PLL Override Control */
+#define PLL_OVERRIDE_DROOP_EN		BIT(0)
+
+/* Sequencer */
+#define SEQUENCER_REG(base, n)		(base + (n * 4))
+#define SEQ_APM_THRESH_VC		15
+#define SEQ_APM_THRESH_PREVC		31
+#define SEQ_MEM_ACC_LVAL		32
+#define SEQ_MEM_ACC_0			55
+#define SEQ_APM_CROSSOVER_VC		72
+#define SEQ_APM_PARAM			76
+#define SEQ_MEM_ACC_MAX_LEVELS		4
+#define SEQ_MEMACC_REG(base, n)		SEQUENCER_REG(base, SEQ_MEM_ACC_0 + n)
+
+/**
+ * struct qcom_cpufreq_soc_setup_data - Register offsets for OSM setup
+ *
+ * @reg_osm_sequencer:      OSM Sequencer (used to get physical address)
+ * @reg_override:           Override parameters
+ * @reg_spare:              Spare parameters (MEMACC-to-VC)
+ * @reg_cc_zero_behav:      Virtual Corner for cluster power collapse
+ * @reg_spm_cc_hyst:        DCVS-CC Wait time for frequency inc/decrement
+ * @reg_spm_cc_dcvs_dis:    DCVS-CC en/disable control
+ * @reg_spm_core_ret_map:   Treat cores in retention as active/inactive
+ * @reg_llm_freq_vote_hyst: DCVS-LLM Wait time for frequency inc/decrement
+ * @reg_llm_volt_vote_hyst: DCVS-LLM Wait time for voltage inc/decrement
+ * @reg_llm_intf_dcvs_dis:  DCVS-LLM en/disable control
+ * @reg_pdn_fsm_ctrl:       Boost and Droop FSMs en/disable control
+ * @reg_cc_boost_timer:     CC-Boost FSM wait first timer register
+ * @reg_dcvs_boost_timer:   DCVS-Boost FSM wait first timer register
+ * @reg_ps_boost_timer:     PS-Boost FSM wait first timer register
+ * @boost_timer_reg_len:    Length of boost timer registers
+ * @reg_boost_sync_delay:   PLL signal timing control for Boost
+ * @reg_droop_ctrl:         Droop control value
+ * @reg_droop_release_ctrl: Wait for Droop release
+ * @reg_droop_unstall_ctrl: Wait for Droop unstall
+ * @reg_droop_wait_release_ctrl: Time to wait for state release
+ * @reg_droop_timer_ctrl:   Droop timer
+ * @reg_droop_sync_delay:   PLL signal timing control for Droop
+ * @reg_pll_override:       PLL Droop Override en/disable control
+ * @reg_cycle_counter:      OSM CPU cycle counter
+ *
+ * This structure holds the register offsets that are used to set-up
+ * the Operating State Manager (OSM) parameters, when it is not (or
+ * not entirely) configured from the bootloader and TrustZone.
+ *
+ * Acronyms used in this documentation:
+ * CC = Core Count
+ * PS = Power-Save
+ * VC = Virtual Corner
+ * LLM = Limits Load Management
+ * DCVS = Dynamic Clock and Voltage Scaling
+ */
+struct qcom_cpufreq_soc_setup_data {
+	/* OSM phys register offsets */
+	u16 reg_osm_sequencer;
+
+	/* Frequency domain register offsets */
+	u16 reg_override;
+	u16 reg_spare;
+	u16 reg_cc_zero_behav;
+	u16 reg_spm_cc_hyst;
+	u16 reg_spm_cc_dcvs_dis;
+	u16 reg_spm_core_ret_map;
+	u16 reg_llm_freq_vote_hyst;
+	u16 reg_llm_volt_vote_hyst;
+	u16 reg_llm_intf_dcvs_dis;
+	u16 reg_pdn_fsm_ctrl;
+	u16 reg_cc_boost_timer;
+	u16 reg_dcvs_boost_timer;
+	u16 reg_ps_boost_timer;
+	u16 boost_timer_reg_len;
+	u16 reg_boost_sync_delay;
+	u16 reg_droop_ctrl;
+	u16 reg_droop_release_ctrl;
+	u16 reg_droop_unstall_ctrl;
+	u16 reg_droop_wait_release_ctrl;
+	u16 reg_droop_timer_ctrl;
+	u16 reg_droop_sync_delay;
+	u16 reg_pll_override;
+	u16 reg_cycle_counter;
+};
+
+/**
+ * struct qcom_cpufreq_hw_params - Operating State Manager (OSM) Parameters
+ *
+ * @volt_lut_val: Value composed of: virtual corner (vc) and voltage in mV.
+ * @freq_lut_val: Value composed of: core count, clock source and output
+ *                frequency in MHz.
+ * @override_val: PLL parameters that the OSM uses to override the previous
+ *                setting coming from the bootloader, or when uninitialized.
+ * @spare_val:    Spare register, used by both this driver and the OSM HW
+ *                to identify MEM-ACC levels in relation to virtual corners.
+ * @acc_thresh:   MEM-ACC threshold level
+ *
+ * This structure holds the parameters to write to the OSM registers for
+ * one "Virtual Corner" (VC), or one Performance State (p-state).
+ */
+struct qcom_cpufreq_hw_params {
+	u32 volt_lut_val;
+	u32 freq_lut_val;
+	u32 override_val;
+	u32 spare_val;
+	u32 acc_thresh;
+};
 
+/**
+ * struct qcom_cpufreq_soc_data - SoC specific register offsets of the OSM
+ *
+ * @reg_enable:            OSM enable status
+ * @reg_index:             Index of the Virtual Corner
+ * @reg_freq_lut:          Frequency Lookup Table
+ * @reg_freq_lut_src_mask: Frequency Lookup Table clock-source mask
+ * @reg_volt_lut:          Voltage Lookup Table
+ * @reg_perf_state:        Performance State request register
+ * @lut_row_size:          Lookup Table row size
+ * @clk_hw_div:            Divider for "alternate" OSM clock-source
+ * @uses_tz:               OSM already set-up and protected by TrustZone
+ * @setup_regs:            Register offsets for OSM setup
+ */
 struct qcom_cpufreq_soc_data {
 	u32 reg_enable;
+	u32 reg_index;
 	u32 reg_freq_lut;
+	u32 reg_freq_lut_src_mask;
 	u32 reg_volt_lut;
 	u32 reg_perf_state;
-	u8 lut_row_size;
+	u8  lut_row_size;
+	u8  clk_hw_div;
+	bool uses_tz;
+	const struct qcom_cpufreq_soc_setup_data setup_regs;
 };
 
 struct qcom_cpufreq_data {
@@ -35,9 +203,17 @@ struct qcom_cpufreq_data {
 	const struct qcom_cpufreq_soc_data *soc_data;
 };
 
+static const char *cprh_genpd_names[] = { "cprh", NULL };
 static unsigned long cpu_hw_rate, xo_rate;
 static bool icc_scaling_enabled;
 
+/**
+ * qcom_cpufreq_set_bw - Set interconnect bandwidth
+ * @policy:   CPUFreq policy structure
+ * @freq_khz: CPU Frequency in KHz
+ *
+ * Returns: Zero for success, otherwise negative value on errors
+ */
 static int qcom_cpufreq_set_bw(struct cpufreq_policy *policy,
 			       unsigned long freq_khz)
 {
@@ -59,6 +235,20 @@ static int qcom_cpufreq_set_bw(struct cpufreq_policy *policy,
 	return ret;
 }
 
+/**
+ * qcom_cpufreq_update_opp - Update CPU OPP tables
+ * @policy:   CPUFreq policy structure
+ * @freq_khz: CPU Frequency for OPP entry in KHz
+ * @volt:     CPU Voltage for OPP entry in microvolts
+ *
+ * The CPU frequencies and voltages are being read from the Operating
+ * State Manager (OSM) and the related OPPs, read from DT, need to be
+ * updated to reflect what the hardware will set for each p-state.
+ * If there is no OPP table specified in DT, then this function will
+ * add dynamic ones.
+ *
+ * Returns: Zero for success, otherwise negative value on errors
+ */
 static int qcom_cpufreq_update_opp(struct device *cpu_dev,
 				   unsigned long freq_khz,
 				   unsigned long volt)
@@ -79,6 +269,17 @@ static int qcom_cpufreq_update_opp(struct device *cpu_dev,
 	return dev_pm_opp_enable(cpu_dev, freq_hz);
 }
 
+/**
+ * qcom_cpufreq_hw_target_index - Set frequency/voltage
+ * @policy:   CPUFreq policy structure
+ * @index:    Performance state index to be set
+ *
+ * This function sends a request to the Operating State Manager
+ * to set a Performance State index, so, to set frequency and
+ * voltage for the target CPU/cluster.
+ *
+ * Returns: Always zero
+ */
 static int qcom_cpufreq_hw_target_index(struct cpufreq_policy *policy,
 					unsigned int index)
 {
@@ -94,6 +295,12 @@ static int qcom_cpufreq_hw_target_index(struct cpufreq_policy *policy,
 	return 0;
 }
 
+/**
+ * qcom_cpufreq_hw_get - Get current Performance State from OSM
+ * @cpu:      CPU number
+ *
+ * Returns: Current CPU/Cluster frequency or zero
+ */
 static unsigned int qcom_cpufreq_hw_get(unsigned int cpu)
 {
 	struct qcom_cpufreq_data *data;
@@ -127,6 +334,401 @@ static unsigned int qcom_cpufreq_hw_fast_switch(struct cpufreq_policy *policy,
 	return policy->freq_table[index].frequency;
 }
 
+static void qcom_cpufreq_hw_boost_setup(void __iomem *timer0_addr, u32 len)
+{
+	u32 val;
+
+	/* timer_reg0 */
+	val = FIELD_PREP(BOOST_TIMER_REG_LO, PLL_WAIT_LOCK_TIME_NS);
+	val |= FIELD_PREP(BOOST_TIMER_REG_HI, SAFE_FREQ_WAIT_NS);
+	writel_relaxed(val, timer0_addr);
+
+	/* timer_reg1 */
+	val = FIELD_PREP(BOOST_TIMER_REG_LO, PLL_WAIT_LOCK_TIME_NS);
+	val |= FIELD_PREP(BOOST_TIMER_REG_HI, PLL_WAIT_LOCK_TIME_NS);
+	writel_relaxed(val, timer0_addr + len);
+
+	/* timer_reg2 */
+	val = FIELD_PREP(BOOST_TIMER_REG_LO, DEXT_DECREMENT_WAIT_NS);
+	writel_relaxed(val, timer0_addr + (2 * len));
+}
+
+/*
+ * qcom_cpufreq_gen_params - Generate parameters to send to the hardware
+ * @cpu_dev:  CPU device
+ * @tbl:      Pointer to return the array of parameters
+ *
+ * This function allocates a 'qcom_cpufreq_hw_params' parameters table,
+ * fills it and returns it to the consumer, ready to get sent to the HW.
+ * Since the APM threshold is just one
+ * Freeing the table after usage is left to the caller.
+ *
+ * Returns: Number of allocated (and filled) elements in the table,
+ *          otherwise negative value on errors.
+ */
+static int qcom_cpufreq_gen_params(struct device *cpu_dev,
+				   struct qcom_cpufreq_data *data,
+				   struct qcom_cpufreq_hw_params **hw_tbl,
+				   int *apm_vc,
+				   int cpu_count)
+{
+	struct platform_device *pdev = cpufreq_get_driver_data();
+	const struct qcom_cpufreq_soc_data *soc_data = data->soc_data;
+	struct device **opp_virt_dev;
+	struct opp_table *genpd_opp_table;
+	struct dev_pm_opp *apm_opp;
+	unsigned long apm_uV, rate;
+	int i, gpd_opp_cnt, ret;
+	u8 last_acc_corner = 0, prev_spare = 0;
+
+	/*
+	 * Install the OPP table that we get from DT here already,
+	 * otherwise it gets really messy as we'd have to manually walk
+	 * through the entire OPP DT, manually find frequencies and
+	 * also manually exclude supported-hw (for speed-binning).
+	 * This also makes it easier for the genpd to add info in it.
+	 */
+	ret = dev_pm_opp_of_add_table(cpu_dev);
+	if (ret) {
+		dev_err(&pdev->dev, "Cannot install CPU OPP table: %d\n", ret);
+		return ret;
+	}
+
+	/* Get a handle to the genpd virtual device */
+	genpd_opp_table = dev_pm_opp_attach_genpd(cpu_dev, cprh_genpd_names,
+						  &opp_virt_dev);
+	if (IS_ERR(genpd_opp_table)) {
+		ret = PTR_ERR(genpd_opp_table);
+		if (ret != -EPROBE_DEFER)
+			dev_err(&pdev->dev,
+				"Could not attach to pm_domain: %d\n", ret);
+		goto opp_dispose;
+	}
+
+	/* Get the count of available OPPs coming from the power domain */
+	gpd_opp_cnt = dev_pm_opp_get_opp_count(cpu_dev);
+	if (gpd_opp_cnt < 2) {
+		ret = gpd_opp_cnt > 0 ? -EINVAL : gpd_opp_cnt;
+		goto detach_gpd;
+	}
+
+	/* Find the APM threshold disabled OPP and "annotate" the voltage */
+	apm_opp = dev_pm_opp_find_freq_exact(cpu_dev, 0, false);
+	if (IS_ERR(apm_opp)) {
+		ret = -EINVAL;
+		goto detach_gpd;
+	}
+
+	/* If we get no APM voltage, the system is going to be unstable */
+	apm_uV = dev_pm_opp_get_voltage(apm_opp);
+	if (apm_uV == 0) {
+		ret = -EINVAL;
+		goto detach_gpd;
+	}
+
+	*hw_tbl = devm_kmalloc_array(&pdev->dev, gpd_opp_cnt,
+				     sizeof(**hw_tbl), GFP_KERNEL);
+	if (!hw_tbl) {
+		ret = -ENOMEM;
+		goto detach_gpd;
+	}
+
+	for (i = 0, rate = 0; ; rate++, i++) {
+		struct qcom_cpufreq_hw_params *entry = *hw_tbl + i;
+		struct dev_pm_opp *genpd_opp;
+		struct device_node *np;
+		u32 pll_div, millivolts, f_src;
+
+		genpd_opp = dev_pm_opp_find_freq_ceil(cpu_dev, &rate);
+		if (IS_ERR(genpd_opp))
+			break;
+
+		/* Get mandatory and optional properties from the OPP DT */
+		np = dev_pm_opp_get_of_node(genpd_opp);
+		if (!np)
+			break;
+
+		if (of_property_read_u32(np, "qcom,pll-override",
+					 &entry->override_val)) {
+			of_node_put(np);
+			return -EINVAL;
+		}
+
+		if (of_property_read_u32(np, "qcom,spare-data",
+					 &entry->spare_val))
+			entry->spare_val = 0;
+
+		if (of_property_read_u32(np, "qcom,pll-div", &pll_div))
+			pll_div = 0;
+
+		of_node_put(np);
+
+		/* Get voltage in microvolts, then convert to millivolts */
+		millivolts = dev_pm_opp_get_voltage(genpd_opp);
+		if (millivolts >= apm_uV)
+			*apm_vc = i;
+
+		do_div(millivolts, 1000);
+
+		if (millivolts < 150 || millivolts > 1400) {
+			dev_err(&pdev->dev,
+				"Read invalid voltage: %u.\n", millivolts);
+			return -EINVAL;
+		}
+
+		/* In the OSM firmware, "Virtual Corner" levels start from 0 */
+		entry->volt_lut_val = FIELD_PREP(LUT_VOLT_VC, i);
+		entry->volt_lut_val |= FIELD_PREP(LUT_VOLT, millivolts);
+
+		/* Only the first frequency can have alternate source */
+		f_src = i ? 1 : 0;
+		f_src <<= ffs(soc_data->reg_freq_lut_src_mask) - 1;
+		entry->freq_lut_val = f_src | div_u64(rate, xo_rate);
+		entry->freq_lut_val |= FIELD_PREP(LUT_CORE_COUNT, cpu_count);
+
+		/*
+		 * PLL divider is not always 0 and there is no way to determine
+		 * it automatically, as setting this value higher than DIV1
+		 * will make the OSM HW to effectively set the PLL at 2-4x
+		 * the CPU frequency and then divide the CPU clock by this div,
+		 * so this value is effectively used as both a multiplier and
+		 * divider.
+		 * This value cannot be calculated because it depends on
+		 * manual calibration and is (most probably) used to choose
+		 * a PLL frequency that gives the least possible jitter.
+		 */
+		entry->freq_lut_val |= FIELD_PREP(LUT_PLL_DIV, pll_div);
+
+		/*
+		 * MEM-ACC Virtual Corner threshold voltage: set the
+		 * acc_thresh to the "next different" spare_val, otherwise
+		 * set it to an invalid value, so that we can recognize it
+		 * and exclude it later, when this set of data will be used
+		 * for programming the OSM.
+		 */
+		if (entry->spare_val != prev_spare) {
+			prev_spare = entry->spare_val;
+			entry->acc_thresh = last_acc_corner;
+		} else {
+			entry->acc_thresh = SEQ_MEM_ACC_MAX_LEVELS + 1;
+		}
+
+		dev_dbg(&pdev->dev,
+			"[%d] freq=0x%x volt=0x%x override=0x%x spare=0x%x\n",
+			i, entry->freq_lut_val, entry->volt_lut_val,
+			entry->override_val, entry->spare_val);
+		dev_pm_opp_put(genpd_opp);
+	}
+
+	/*
+	 * If we have probed less params than what we need, then the
+	 * OPP table that we got from the genpd is malformed for some
+	 * reason: in this case, do not apply the table to the HW.
+	 */
+	if (i < gpd_opp_cnt) {
+		dev_err(&pdev->dev, "Got bad OPP table from power domain.\n");
+		ret = -EINVAL;
+		goto detach_gpd;
+	}
+
+detach_gpd:
+	dev_pm_opp_detach_genpd(genpd_opp_table);
+opp_dispose:
+	/*
+	 * Now that we're totally done with it, dispose of all the dynamic
+	 * OPPs in the table: like this, at the end of the OSM configuration
+	 * we are leaving it like it was magically configured by the TZ or
+	 * by the bootloader and using the rest of the driver as if this
+	 * programming phase has never happened in the OS, like new SoCs.
+	 * This also makes us able to not modify a single bit in the basic
+	 * OSM frequency request logic that was meant for the newest SoCs.
+	 */
+	dev_pm_opp_remove_all_dynamic(cpu_dev);
+	return ret < 0 ? ret : i;
+}
+
+/**
+ * qcom_cpufreq_hw_write_lut - Write Lookup Table params to the OSM
+ * @cpu_dev:   CPU device
+ * @policy:    CPUFreq policy structure
+ * @cpu_count: Number of CPUs in the frequency domain
+ *
+ * Program all the Lookup Table (LUT) entries and related thresholds
+ * to the Operating State Manager on platforms where the same hasn't
+ * been done already by the bootloader or TrustZone before booting
+ * the operating system's kernel;
+ * On these platforms, write access to the OSM is (obviously) not
+ * blocked by the hypervisor.
+ *
+ * Returns: Zero for success, otherwise negative number on errors.
+ */
+static int qcom_cpufreq_hw_write_lut(struct device *cpu_dev,
+				     struct cpufreq_policy *policy,
+				     int cpu_count, int index)
+{
+	struct platform_device *pdev = cpufreq_get_driver_data();
+	struct qcom_cpufreq_data *ddata = policy->driver_data;
+	const struct qcom_cpufreq_soc_data *sdata = ddata->soc_data;
+	const struct qcom_cpufreq_soc_setup_data *sregs = &sdata->setup_regs;
+	struct qcom_cpufreq_hw_params *hw_tbl;
+	struct resource *osm_rsrc;
+	const char *osm_resname;
+	u32 seq_addr, acc_lval = 0, last_spare = 1;
+	int i, ret, num_entries, apm_vc, acc_idx = 0;
+
+	osm_resname = kasprintf(GFP_KERNEL, "osm-domain%d", index);
+	if (!osm_resname)
+		return -ENOMEM;
+
+	/*
+	 * On some SoCs the OSM is not getting programmed from bootloader
+	 * and needs to be done here: in this case, we need to retrieve
+	 * the base physical address for the "Sequencer", so we will get
+	 * the OSM base phys and apply the sequencer offset.
+	 *
+	 * Note: We are not remapping this iospace because we are really
+	 *       sending the physical address through SCM calls later.
+	 */
+	osm_rsrc = platform_get_resource_byname(pdev, IORESOURCE_MEM,
+						osm_resname);
+	kfree(osm_resname);
+
+	if (!osm_rsrc)
+		return -ENODEV;
+
+	seq_addr = osm_rsrc->start + sregs->reg_osm_sequencer;
+
+	num_entries = qcom_cpufreq_gen_params(cpu_dev, ddata, &hw_tbl,
+					      &apm_vc, cpu_count);
+	if (num_entries < 0)
+		return num_entries;
+
+	for (i = 0; i < LUT_MAX_ENTRIES; i++) {
+		struct qcom_cpufreq_hw_params *entry;
+		int pos = i * sdata->lut_row_size;
+
+		/*
+		 * If we have reached the end of the params table, write
+		 * the last valid entry until the end of the OSM table.
+		 */
+		if (i < num_entries)
+			entry = &hw_tbl[i];
+		else
+			entry = &hw_tbl[num_entries - 1];
+
+		writel_relaxed(i, ddata->base + sdata->reg_index + pos);
+
+		writel_relaxed(entry->volt_lut_val,
+			       ddata->base + sdata->reg_volt_lut + pos);
+
+		writel_relaxed(entry->freq_lut_val,
+			       ddata->base + sdata->reg_freq_lut + pos);
+
+		writel_relaxed(entry->override_val,
+			       ddata->base + sregs->reg_override + pos);
+
+		writel_relaxed(entry->spare_val,
+			       ddata->base + sregs->reg_spare + pos);
+
+		/* Find the first corner with MEM-ACC level 3 */
+		if (acc_lval == 0 && entry->spare_val == 3)
+			acc_lval = FIELD_GET(LUT_L_VAL, entry->freq_lut_val);
+
+		dev_dbg(cpu_dev,
+			"Writing [%d] v:0x%x f:0x%x ovr:0x%x s:0x%x\n", i,
+			entry->volt_lut_val, entry->freq_lut_val,
+			entry->override_val, entry->spare_val);
+
+		/*
+		 * MEM-ACC Virtual Corner threshold voltage: this gets set
+		 * as the pairs of corners in which there is a transition
+		 * between one MEM-ACC level and the next one.
+		 *
+		 * Notes: The spare_val can never be zero;
+		 *        The first spare_val is always 1;
+		 *        The maximum number of pairs is two (four registers).
+		 *
+		 * Example: (C = Corner Level - M = MEM-ACC Level)
+		 *          C0 M1 - C1 M1 - C2 M2 - C3 M2 - C4 M2 - C5 M3
+		 *          Pairs: 1-2, 4-5
+		 */
+		if (entry->spare_val == last_spare ||
+		    acc_idx > SEQ_MEM_ACC_MAX_LEVELS)
+			continue;
+
+		dev_dbg(cpu_dev, "Writing MEM-ACC Pair: %u-%u\n",
+			i - 1, i);
+
+		last_spare = entry->spare_val;
+		ret = qcom_scm_io_writel(SEQ_MEMACC_REG(seq_addr, acc_idx),
+					 i - 1);
+		if (ret)
+			return ret;
+
+		acc_idx++;
+		ret = qcom_scm_io_writel(SEQ_MEMACC_REG(seq_addr, acc_idx), i);
+		if (ret)
+			return ret;
+		acc_idx++;
+	}
+
+	/*
+	 * Program the L_VAL of the first corner requesting MEM-ACC
+	 * voltage level 3 to the right sequencer register
+	 */
+	ret = qcom_scm_io_writel(SEQUENCER_REG(seq_addr, SEQ_MEM_ACC_LVAL),
+				 acc_lval);
+	if (ret) {
+		dev_dbg(cpu_dev, "Cannot send memacc l_val\n");
+		return ret;
+	}
+
+	/*
+	 * Array Power Mux threshold level: the first virtual corner
+	 * that requires a switch sequence of the APM from MX to APC.
+	 */
+	if (apm_vc == 0)
+		apm_vc = LUT_MAX_ENTRIES - 1;
+
+	/*
+	 * APM crossover virtual corner refers to CPRh: there, the APM corner
+	 * is always appended to the table (so, at the end of it, right after
+	 * the cluster dvfs entries).
+	 */
+	ret = qcom_scm_io_writel(SEQUENCER_REG(seq_addr, SEQ_APM_CROSSOVER_VC),
+				 num_entries);
+	if (ret)
+		return ret;
+
+	ret = qcom_scm_io_writel(SEQUENCER_REG(seq_addr, SEQ_APM_THRESH_VC),
+				 apm_vc);
+	if (ret)
+		return ret;
+
+	ret = qcom_scm_io_writel(SEQUENCER_REG(seq_addr, SEQ_APM_THRESH_PREVC),
+				 apm_vc - 1);
+	if (ret)
+		return ret;
+
+	ret = qcom_scm_io_writel(SEQUENCER_REG(seq_addr, SEQ_APM_PARAM),
+				 (0x39 | apm_vc << 6));
+	if (ret)
+		return ret;
+
+	return ret;
+}
+
+/**
+ * qcom_cpufreq_hw_read_lut - Read Lookup Table from the OSM
+ * @cpu_dev:   CPU device
+ * @policy:    CPUFreq policy structure
+ *
+ * The Operating State Manager Lookup Table can always be read, even
+ * in case it was pre-programmed by the bootloader or by TrustZone.
+ * Read the LUT from it in order to build OPPs containing DVFS info.
+ *
+ * Returns: Zero for success, otherwise negative number on errors.
+ */
 static int qcom_cpufreq_hw_read_lut(struct device *cpu_dev,
 				    struct cpufreq_policy *policy)
 {
@@ -166,7 +768,9 @@ static int qcom_cpufreq_hw_read_lut(struct device *cpu_dev,
 	for (i = 0; i < LUT_MAX_ENTRIES; i++) {
 		data = readl_relaxed(drv_data->base + soc_data->reg_freq_lut +
 				      i * soc_data->lut_row_size);
-		src = FIELD_GET(LUT_SRC, data);
+		src = data & soc_data->reg_freq_lut_src_mask;
+		src >>= ffs(soc_data->reg_freq_lut_src_mask) - 1;
+
 		lval = FIELD_GET(LUT_L_VAL, data);
 		core_count = FIELD_GET(LUT_CORE_COUNT, data);
 
@@ -175,17 +779,21 @@ static int qcom_cpufreq_hw_read_lut(struct device *cpu_dev,
 		volt = FIELD_GET(LUT_VOLT, data) * 1000;
 
 		if (src)
-			freq = xo_rate * lval / 1000;
+			freq = xo_rate * lval;
 		else
-			freq = cpu_hw_rate / 1000;
+			freq = cpu_hw_rate;
+		do_div(freq, 1000);
 
 		if (freq != prev_freq && core_count != LUT_TURBO_IND) {
 			if (!qcom_cpufreq_update_opp(cpu_dev, freq, volt)) {
 				table[i].frequency = freq;
-				dev_dbg(cpu_dev, "index=%d freq=%d, core_count %d\n", i,
-				freq, core_count);
+				dev_dbg(cpu_dev,
+					"index=%d freq=%d, core_count %d\n",
+					i, freq, core_count);
 			} else {
-				dev_warn(cpu_dev, "failed to update OPP for freq=%d\n", freq);
+				dev_warn(cpu_dev,
+					 "failed to update OPP for freq=%d\n",
+					 freq);
 				table[i].frequency = CPUFREQ_ENTRY_INVALID;
 			}
 
@@ -205,18 +813,19 @@ static int qcom_cpufreq_hw_read_lut(struct device *cpu_dev,
 			 * as the boost frequency
 			 */
 			if (prev->frequency == CPUFREQ_ENTRY_INVALID) {
-				if (!qcom_cpufreq_update_opp(cpu_dev, prev_freq, volt)) {
+				if (!qcom_cpufreq_update_opp(cpu_dev, prev_freq,
+							     volt)) {
 					prev->frequency = prev_freq;
 					prev->flags = CPUFREQ_BOOST_FREQ;
 				} else {
-					dev_warn(cpu_dev, "failed to update OPP for freq=%d\n",
+					dev_warn(cpu_dev,
+						 "can't update OPP for freq=%u\n",
 						 freq);
 				}
 			}
 
 			break;
 		}
-
 		prev_freq = freq;
 	}
 
@@ -227,10 +836,18 @@ static int qcom_cpufreq_hw_read_lut(struct device *cpu_dev,
 	return 0;
 }
 
-static void qcom_get_related_cpus(int index, struct cpumask *m)
+/*
+ * qcom_get_related_cpus - Get mask of CPUs in the same frequency domain
+ * @index: CPU number
+ * @m:     Returned CPU mask
+ *
+ * Returns: Count of CPUs inserted in the cpumask or negative number for error.
+ */
+static int qcom_get_related_cpus(int index, struct cpumask *m)
 {
 	struct device_node *cpu_np;
 	struct of_phandle_args args;
+	int count = 0;
 	int cpu, ret;
 
 	for_each_possible_cpu(cpu) {
@@ -245,34 +862,211 @@ static void qcom_get_related_cpus(int index, struct cpumask *m)
 		if (ret < 0)
 			continue;
 
-		if (index == args.args[0])
+		if (index == args.args[0]) {
 			cpumask_set_cpu(cpu, m);
+			count++;
+		}
 	}
+
+	return count > 0 ? count : -EINVAL;
 }
 
 static const struct qcom_cpufreq_soc_data qcom_soc_data = {
 	.reg_enable = 0x0,
 	.reg_freq_lut = 0x110,
+	.reg_freq_lut_src_mask = LUT_SRC_845,
 	.reg_volt_lut = 0x114,
 	.reg_perf_state = 0x920,
 	.lut_row_size = 32,
+	.clk_hw_div = 2,
+	.uses_tz = true,
+};
+
+static const struct qcom_cpufreq_soc_data msm8998_soc_data = {
+	.reg_enable = 0x4,
+	.reg_index = 0x150,
+	.reg_freq_lut = 0x154,
+	.reg_freq_lut_src_mask = LUT_SRC_8998,
+	.reg_volt_lut = 0x158,
+	.reg_perf_state = 0xf10,
+	.lut_row_size = 32,
+	.clk_hw_div = 1,
+	.uses_tz = false,
+	.setup_regs = {
+		/* Physical offset for sequencer scm calls */
+		.reg_osm_sequencer = 0x300,
+
+		/* Frequency domain offsets */
+		.reg_override = 0x15c,
+		.reg_spare = 0x164,
+		.reg_cc_zero_behav = 0x0c,
+		.reg_spm_cc_hyst = 0x1c,
+		.reg_spm_cc_dcvs_dis = 0x20,
+		.reg_spm_core_ret_map = 0x24,
+		.reg_llm_freq_vote_hyst = 0x2c,
+		.reg_llm_volt_vote_hyst = 0x30,
+		.reg_llm_intf_dcvs_dis = 0x34,
+		.reg_pdn_fsm_ctrl = 0x70,
+		.reg_cc_boost_timer = 0x74,
+		.reg_dcvs_boost_timer = 0x84,
+		.reg_ps_boost_timer = 0x94,
+		.boost_timer_reg_len = 0x4,
+		.reg_boost_sync_delay = 0xa0,
+		.reg_droop_ctrl = 0xa4,
+		.reg_droop_release_ctrl = 0xa8,
+		.reg_droop_unstall_ctrl = 0xac,
+		.reg_droop_wait_release_ctrl = 0xb0,
+		.reg_droop_timer_ctrl = 0xb8,
+		.reg_droop_sync_delay = 0xbc,
+		.reg_pll_override = 0xc0,
+		.reg_cycle_counter = 0xf00,
+	},
 };
 
 static const struct qcom_cpufreq_soc_data epss_soc_data = {
 	.reg_enable = 0x0,
 	.reg_freq_lut = 0x100,
+	.reg_freq_lut_src_mask = LUT_SRC_845,
 	.reg_volt_lut = 0x200,
 	.reg_perf_state = 0x320,
 	.lut_row_size = 4,
+	.clk_hw_div = 2,
+	.uses_tz = true,
 };
 
 static const struct of_device_id qcom_cpufreq_hw_match[] = {
 	{ .compatible = "qcom,cpufreq-hw", .data = &qcom_soc_data },
+	{ .compatible = "qcom,cpufreq-hw-8998", .data = &msm8998_soc_data },
 	{ .compatible = "qcom,cpufreq-epss", .data = &epss_soc_data },
 	{}
 };
 MODULE_DEVICE_TABLE(of, qcom_cpufreq_hw_match);
 
+/**
+ * qcom_cpufreq_hw_osm_setup - Setup and enable the OSM
+ * @cpu_dev:   CPU device
+ * @policy:    CPUFreq policy structure
+ * @cpu_count: Number of CPUs in the frequency domain
+ *
+ * On some platforms, the Operating State Manager (OSM) is not getting
+ * programmed by the bootloader, nor by TrustZone before booting the OS
+ * and its register space is not write-protected by the hypervisor.
+ * In this case, to achieve CPU DVFS, it is needed to program it from
+ * the OS itself, which includes setting LUT and all the various tunables
+ * that are required for it to manage the CPU frequencies and voltages
+ * on its own.
+ * Calling this function on a platform that had the OSM set-up by TZ
+ * will result in a hypervisor fault with system reboot in most cases.
+ *
+ * Returns: Zero for success, otherwise negative number on errors.
+ */
+static int qcom_cpufreq_hw_osm_setup(struct device *cpu_dev,
+				     struct cpufreq_policy *policy,
+				     int cpu_count, int index)
+{
+	struct qcom_cpufreq_data *drv_data = policy->driver_data;
+	const struct qcom_cpufreq_soc_setup_data *setup_regs;
+	u32 val;
+	int ret;
+
+	ret = qcom_cpufreq_hw_write_lut(cpu_dev, policy, cpu_count, index);
+	if (ret)
+		return ret;
+
+	setup_regs = &drv_data->soc_data->setup_regs;
+
+	/* Set OSM to XO clock ratio and use XO edge for the cycle counter */
+	val = FIELD_PREP(CYCLE_COUNTER_CLK_RATIO, OSM_XO_RATIO_VAL);
+	val |= CYCLE_COUNTER_USE_XO_EDGE;
+
+	/* Enable the CPU cycle counter */
+	val |= BIT(0);
+	writel_relaxed(val, drv_data->base + setup_regs->reg_cycle_counter);
+
+	/* CoreCount DCVS Policy: Wait time for frequency inc/decrement */
+	val = FIELD_PREP(HYSTERESIS_UP_MASK, HYSTERESIS_CC_NS);
+	val |= FIELD_PREP(HYSTERESIS_DN_MASK, HYSTERESIS_CC_NS);
+	writel_relaxed(val, drv_data->base + setup_regs->reg_spm_cc_hyst);
+
+	/* Set the frequency index for cluster power collapse */
+	writel_relaxed(0, drv_data->base + setup_regs->reg_cc_zero_behav);
+
+	/* Treat cores in retention as active */
+	writel_relaxed(0, drv_data->base + setup_regs->reg_spm_core_ret_map);
+
+	/* Enable CoreCount based DCVS */
+	writel_relaxed(0, drv_data->base + setup_regs->reg_spm_cc_dcvs_dis);
+
+	/* CoreCount DCVS-LLM Policy: Wait time for frequency inc/decrement */
+	val = FIELD_PREP(HYSTERESIS_UP_MASK, HYSTERESIS_LLM_NS);
+	val |= FIELD_PREP(HYSTERESIS_DN_MASK, HYSTERESIS_LLM_NS);
+	writel_relaxed(val, drv_data->base + setup_regs->reg_llm_freq_vote_hyst);
+
+	/* CoreCount DCVS-LLM Policy: Wait time for voltage inc/decrement */
+	val = FIELD_PREP(HYSTERESIS_UP_MASK, HYSTERESIS_LLM_NS);
+	val |= FIELD_PREP(HYSTERESIS_DN_MASK, HYSTERESIS_LLM_NS);
+	writel_relaxed(val, drv_data->base + setup_regs->reg_llm_volt_vote_hyst);
+
+	/* Enable LLM frequency+voltage voting */
+	writel_relaxed(0, drv_data->base + setup_regs->reg_llm_intf_dcvs_dis);
+
+	/* Setup Boost FSM Timers */
+	qcom_cpufreq_hw_boost_setup(drv_data->base +
+				    setup_regs->reg_cc_boost_timer,
+				    setup_regs->boost_timer_reg_len);
+	qcom_cpufreq_hw_boost_setup(drv_data->base +
+				    setup_regs->reg_dcvs_boost_timer,
+				    setup_regs->boost_timer_reg_len);
+	qcom_cpufreq_hw_boost_setup(drv_data->base +
+				    setup_regs->reg_ps_boost_timer,
+				    setup_regs->boost_timer_reg_len);
+
+	/* PLL signal timing control for Boost */
+	writel_relaxed(BOOST_SYNC_DELAY,
+		       drv_data->base + setup_regs->reg_boost_sync_delay);
+
+	/* Setup WFx and PC/RET droop unstall */
+	val = FIELD_PREP(DROOP_TIMER1, DROOP_TIMER_NS);
+	val |= FIELD_PREP(DROOP_TIMER0, DROOP_TIMER_NS);
+	writel_relaxed(val, drv_data->base + setup_regs->reg_droop_unstall_ctrl);
+
+	/* Setup WFx and PC/RET droop wait-to-release */
+	val = FIELD_PREP(DROOP_TIMER1, DROOP_WAIT_RELEASE_TIMER_NS);
+	val |= FIELD_PREP(DROOP_TIMER0, DROOP_WAIT_RELEASE_TIMER_NS);
+	writel_relaxed(val,
+		       drv_data->base + setup_regs->reg_droop_wait_release_ctrl);
+
+	/* PLL signal timing control for Droop */
+	writel_relaxed(1, drv_data->base + setup_regs->reg_droop_sync_delay);
+
+	/* Setup DCVS timers */
+	writel_relaxed(DROOP_RELEASE_TIMER_NS,
+		       drv_data->base + setup_regs->reg_droop_release_ctrl);
+	writel_relaxed(DROOP_TIMER_NS,
+		       drv_data->base + setup_regs->reg_droop_timer_ctrl);
+
+	/* Setup Droop control */
+	val = readl_relaxed(drv_data->base + setup_regs->reg_droop_ctrl);
+	val |= DROOP_CTRL_VAL;
+	writel_relaxed(val, drv_data->base + setup_regs->reg_droop_ctrl);
+
+	/* Enable CC-Boost, DCVS-Boost, PS-Boost, WFx, PC/RET, DCVS FSM */
+	val = readl_relaxed(drv_data->base + setup_regs->reg_pdn_fsm_ctrl);
+	val |= CC_BOOST_EN | PS_BOOST_EN | DCVS_BOOST_EN;
+	val |= WFX_DROOP_EN | PC_RET_EXIT_DROOP_EN | DCVS_DROOP_EN;
+	writel_relaxed(val, drv_data->base + setup_regs->reg_pdn_fsm_ctrl);
+
+	/* Enable PLL Droop Override */
+	val = PLL_OVERRIDE_DROOP_EN;
+	writel_relaxed(val, drv_data->base + setup_regs->reg_pll_override);
+
+	/* We're ready: enable the OSM and give it time to boot (5uS) */
+	writel_relaxed(1, drv_data->base + drv_data->soc_data->reg_enable);
+	udelay(OSM_BOOT_TIME_US);
+
+	return ret;
+}
+
 static int qcom_cpufreq_hw_cpu_init(struct cpufreq_policy *policy)
 {
 	struct platform_device *pdev = cpufreq_get_driver_data();
@@ -282,7 +1076,9 @@ static int qcom_cpufreq_hw_cpu_init(struct cpufreq_policy *policy)
 	struct device *cpu_dev;
 	void __iomem *base;
 	struct qcom_cpufreq_data *data;
-	int ret, index;
+	unsigned int transition_latency;
+	const char *fdom_resname;
+	int cpu_count, index, ret;
 
 	cpu_dev = get_cpu_device(policy->cpu);
 	if (!cpu_dev) {
@@ -303,9 +1099,14 @@ static int qcom_cpufreq_hw_cpu_init(struct cpufreq_policy *policy)
 
 	index = args.args[0];
 
-	base = devm_platform_ioremap_resource(pdev, index);
+	fdom_resname = kasprintf(GFP_KERNEL, "freq-domain%d", index);
+	if (!fdom_resname)
+		return -ENOMEM;
+
+	base = devm_platform_ioremap_resource_byname(pdev, fdom_resname);
 	if (IS_ERR(base))
 		return PTR_ERR(base);
+	kfree(fdom_resname);
 
 	data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
 	if (!data) {
@@ -315,22 +1116,31 @@ static int qcom_cpufreq_hw_cpu_init(struct cpufreq_policy *policy)
 
 	data->soc_data = of_device_get_match_data(&pdev->dev);
 	data->base = base;
+	policy->driver_data = data;
 
-	/* HW should be in enabled state to proceed */
-	if (!(readl_relaxed(base + data->soc_data->reg_enable) & 0x1)) {
-		dev_err(dev, "Domain-%d cpufreq hardware not enabled\n", index);
-		ret = -ENODEV;
-		goto error;
-	}
-
-	qcom_get_related_cpus(index, policy->cpus);
+	cpu_count = qcom_get_related_cpus(index, policy->cpus);
 	if (!cpumask_weight(policy->cpus)) {
 		dev_err(dev, "Domain-%d failed to get related CPUs\n", index);
 		ret = -ENOENT;
 		goto error;
 	}
 
-	policy->driver_data = data;
+	if (!data->soc_data->uses_tz) {
+		ret = qcom_cpufreq_hw_osm_setup(cpu_dev, policy,
+						cpu_count, index);
+		if (ret) {
+			dev_err(dev, "Cannot setup the OSM for CPU%d: %d\n",
+				policy->cpu, ret);
+			goto error;
+		}
+	}
+
+	/* HW should be in enabled state to proceed */
+	if (!(readl_relaxed(base + data->soc_data->reg_enable) & 0x1)) {
+		dev_err(dev, "Domain-%d cpufreq hardware not enabled\n", index);
+		ret = -ENODEV;
+		goto error;
+	}
 
 	ret = qcom_cpufreq_hw_read_lut(cpu_dev, policy);
 	if (ret) {
@@ -345,10 +1155,18 @@ static int qcom_cpufreq_hw_cpu_init(struct cpufreq_policy *policy)
 		goto error;
 	}
 
+	transition_latency = dev_pm_opp_get_max_transition_latency(cpu_dev);
+	if (!transition_latency)
+		transition_latency = CPUFREQ_ETERNAL;
+
+	policy->cpuinfo.transition_latency = transition_latency;
+	policy->dvfs_possible_from_any_cpu = true;
+
 	dev_pm_opp_of_register_em(cpu_dev, policy->cpus);
 
 	return 0;
 error:
+	policy->driver_data = NULL;
 	devm_iounmap(dev, base);
 	return ret;
 }
@@ -389,10 +1207,49 @@ static struct cpufreq_driver cpufreq_qcom_hw_driver = {
 
 static int qcom_cpufreq_hw_driver_probe(struct platform_device *pdev)
 {
+	const struct qcom_cpufreq_soc_data *soc_data;
+	struct device_node *pd_node;
+	struct platform_device *pd_dev;
 	struct device *cpu_dev;
 	struct clk *clk;
 	int ret;
 
+	cpu_dev = get_cpu_device(0);
+	if (!cpu_dev)
+		return -EPROBE_DEFER;
+
+	soc_data = of_device_get_match_data(&pdev->dev);
+	if (!soc_data->uses_tz) {
+		/*
+		 * When the OSM is not pre-programmed from TZ, we will
+		 * need to program the sequencer through SCM calls.
+		 */
+		if (!qcom_scm_is_available())
+			return -EPROBE_DEFER;
+
+		/*
+		 * If there are no power-domains, OSM programming cannot be
+		 * performed, as in that case, we wouldn't know where to take
+		 * the params from...
+		 */
+		pd_node = of_parse_phandle(cpu_dev->of_node,
+					   "power-domains", 0);
+		if (!pd_node) {
+			ret = PTR_ERR(pd_node);
+			dev_err(cpu_dev, "power domain not found: %d\n", ret);
+			return ret;
+		}
+
+		/*
+		 * If the power domain device is not registered yet, then
+		 * defer probing this driver until that is available.
+		 */
+		pd_dev = of_find_device_by_node(pd_node);
+		if (!pd_dev || !pd_dev->dev.driver ||
+		    !device_is_bound(&pd_dev->dev))
+			return -EPROBE_DEFER;
+	}
+
 	clk = clk_get(&pdev->dev, "xo");
 	if (IS_ERR(clk))
 		return PTR_ERR(clk);
@@ -404,16 +1261,13 @@ static int qcom_cpufreq_hw_driver_probe(struct platform_device *pdev)
 	if (IS_ERR(clk))
 		return PTR_ERR(clk);
 
-	cpu_hw_rate = clk_get_rate(clk) / CLK_HW_DIV;
+	cpu_hw_rate = clk_get_rate(clk);
+	do_div(cpu_hw_rate, soc_data->clk_hw_div);
 	clk_put(clk);
 
 	cpufreq_qcom_hw_driver.driver_data = pdev;
 
 	/* Check for optional interconnect paths on CPU0 */
-	cpu_dev = get_cpu_device(0);
-	if (!cpu_dev)
-		return -EPROBE_DEFER;
-
 	ret = dev_pm_opp_of_find_icc_paths(cpu_dev, NULL);
 	if (ret)
 		return ret;
-- 
2.29.2


^ permalink raw reply related	[flat|nested] 25+ messages in thread

* [PATCH 13/13] dt-bindings: cpufreq: qcom-hw: Add bindings for 8998
  2020-11-26 18:45 [PATCH 00/13] Enable CPRh/3/4, CPU Scaling on various QCOM SoCs AngeloGioacchino Del Regno
                   ` (11 preceding siblings ...)
  2020-11-26 18:45 ` [PATCH 12/13] cpufreq: qcom-hw: Implement CPRh aware OSM programming AngeloGioacchino Del Regno
@ 2020-11-26 18:45 ` AngeloGioacchino Del Regno
  2020-12-08 18:11   ` Rob Herring
  12 siblings, 1 reply; 25+ messages in thread
From: AngeloGioacchino Del Regno @ 2020-11-26 18:45 UTC (permalink / raw)
  To: linux-arm-msm
  Cc: linux-kernel, devicetree, linux-pm, ulf.hansson,
	jorge.ramirez-ortiz, broonie, lgirdwood, daniel.lezcano, nks,
	bjorn.andersson, agross, robh+dt, viresh.kumar, rjw,
	konrad.dybcio, martin.botka, marijn.suijten, phone-devel,
	AngeloGioacchino Del Regno

The OSM programming addition has been done under the
qcom,cpufreq-hw-8998 compatible name: specify the requirement
of two additional register spaces for this functionality.

Signed-off-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@somainline.org>
---
 .../bindings/cpufreq/qcom,cpufreq-hw.yaml     | 31 ++++++++++++++++---
 1 file changed, 27 insertions(+), 4 deletions(-)

diff --git a/Documentation/devicetree/bindings/cpufreq/qcom,cpufreq-hw.yaml b/Documentation/devicetree/bindings/cpufreq/qcom,cpufreq-hw.yaml
index 94a56317b14b..f64cea73037e 100644
--- a/Documentation/devicetree/bindings/cpufreq/qcom,cpufreq-hw.yaml
+++ b/Documentation/devicetree/bindings/cpufreq/qcom,cpufreq-hw.yaml
@@ -23,17 +23,21 @@ properties:
           - qcom,cpufreq-epss
 
   reg:
+    description: Base address and size of the RBCPR register region
     minItems: 2
     maxItems: 2
 
   reg-names:
     description:
-      Frequency domain register region for each domain.
-    items:
-      - const: "freq-domain0"
-      - const: "freq-domain1"
+      Frequency domain register region for each domain. If OSM programming
+      does not happen in the bootloader and has to be done in this driver,
+      then also the OSM domain region osm-domain[0-1] has to be provided.
+    minItems: 2
+    maxItems: 2
 
   clock-names:
+    minItems: 2
+    maxItems: 2
     - const: xo
     - const: ref
 
@@ -53,9 +57,28 @@ properties:
       property with phandle to a cpufreq_hw followed by the Domain ID(0/1)
       in the CPU DT node.
 
+allOf:
+ - if:
+     properties:
+       reg-names:
+         contains:
+           const: qcom,cpufreq-hw-8998
+   then:
+     properties:
+       reg:
+         minItems: 4
+         maxItems: 4
+       reg-names:
+         items:
+           - const: "freq-domain0"
+           - const: "freq-domain1"
+           - const: "osm-domain0"
+           - const: "osm-domain1"
+
 required:
   - compatible
   - reg
+  - reg-names
   - clock-names
   - clocks
   - "#freq-domain-cells"
-- 
2.29.2


^ permalink raw reply related	[flat|nested] 25+ messages in thread

* Re: [PATCH 01/13] cpuidle: qcom_spm: Detach state machine from main SPM handling
  2020-11-26 18:45 ` [PATCH 01/13] cpuidle: qcom_spm: Detach state machine from main SPM handling AngeloGioacchino Del Regno
@ 2020-11-27 18:07   ` kernel test robot
  0 siblings, 0 replies; 25+ messages in thread
From: kernel test robot @ 2020-11-27 18:07 UTC (permalink / raw)
  To: AngeloGioacchino Del Regno, linux-arm-msm
  Cc: kbuild-all, linux-kernel, devicetree, linux-pm, ulf.hansson,
	jorge.ramirez-ortiz, broonie, lgirdwood, daniel.lezcano, nks

[-- Attachment #1: Type: text/plain, Size: 2454 bytes --]

Hi AngeloGioacchino,

Thank you for the patch! Yet something to improve:

[auto build test ERROR on pm/linux-next]
[also build test ERROR on robh/for-next linux/master linus/master v5.10-rc5 next-20201127]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch]

url:    https://github.com/0day-ci/linux/commits/AngeloGioacchino-Del-Regno/Enable-CPRh-3-4-CPU-Scaling-on-various-QCOM-SoCs/20201127-030047
base:   https://git.kernel.org/pub/scm/linux/kernel/git/rafael/linux-pm.git linux-next
config: arm64-allmodconfig (attached as .config)
compiler: aarch64-linux-gcc (GCC) 9.3.0
reproduce (this is a W=1 build):
        wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross
        chmod +x ~/bin/make.cross
        # https://github.com/0day-ci/linux/commit/76b61383ddbed889da6d4e399e49314a0243f462
        git remote add linux-review https://github.com/0day-ci/linux
        git fetch --no-tags linux-review AngeloGioacchino-Del-Regno/Enable-CPRh-3-4-CPU-Scaling-on-various-QCOM-SoCs/20201127-030047
        git checkout 76b61383ddbed889da6d4e399e49314a0243f462
        # save the attached .config to linux build tree
        COMPILER_INSTALL_PATH=$HOME/0day COMPILER=gcc-9.3.0 make.cross ARCH=arm64 

If you fix the issue, kindly add following tag as appropriate
Reported-by: kernel test robot <lkp@intel.com>

All error/warnings (new ones prefixed by >>):

>> drivers/soc/qcom/spm.c:198:1: warning: data definition has no type or storage class
     198 | arch_initcall(qcom_spm_init);
         | ^~~~~~~~~~~~~
>> drivers/soc/qcom/spm.c:198:1: error: type defaults to 'int' in declaration of 'arch_initcall' [-Werror=implicit-int]
>> drivers/soc/qcom/spm.c:198:1: warning: parameter names (without types) in function declaration
   drivers/soc/qcom/spm.c:194:19: warning: 'qcom_spm_init' defined but not used [-Wunused-function]
     194 | static int __init qcom_spm_init(void)
         |                   ^~~~~~~~~~~~~
   cc1: some warnings being treated as errors

vim +198 drivers/soc/qcom/spm.c

   193	
   194	static int __init qcom_spm_init(void)
   195	{
   196		return platform_driver_register(&spm_driver);
   197	}
 > 198	arch_initcall(qcom_spm_init);

---
0-DAY CI Kernel Test Service, Intel Corporation
https://lists.01.org/hyperkitty/list/kbuild-all@lists.01.org

[-- Attachment #2: .config.gz --]
[-- Type: application/gzip, Size: 75402 bytes --]

^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCH 07/13] dt-bindings: avs: cpr: Convert binding to YAML schema
  2020-11-26 18:45 ` [PATCH 07/13] dt-bindings: avs: cpr: Convert binding to YAML schema AngeloGioacchino Del Regno
@ 2020-11-30 17:03   ` Rob Herring
  0 siblings, 0 replies; 25+ messages in thread
From: Rob Herring @ 2020-11-30 17:03 UTC (permalink / raw)
  To: AngeloGioacchino Del Regno
  Cc: linux-pm, viresh.kumar, linux-kernel, martin.botka, rjw,
	phone-devel, ulf.hansson, linux-arm-msm, marijn.suijten,
	jorge.ramirez-ortiz, agross, robh+dt, devicetree, lgirdwood, nks,
	konrad.dybcio, daniel.lezcano, bjorn.andersson, broonie

On Thu, 26 Nov 2020 19:45:53 +0100, AngeloGioacchino Del Regno wrote:
> Convert the qcom,cpr.txt document to YAML schema and place it in the
> appropriate directory, since commit a7305e684fc moves this driver
> from power/avs to soc/qcom, but forgets to move the documentation.
> 
> Signed-off-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@somainline.org>
> ---
>  .../bindings/power/avs/qcom,cpr.txt           | 131 +-----------------
>  .../bindings/soc/qcom/qcom,cpr.yaml           | 115 +++++++++++++++
>  MAINTAINERS                                   |   2 +-
>  3 files changed, 117 insertions(+), 131 deletions(-)
>  create mode 100644 Documentation/devicetree/bindings/soc/qcom/qcom,cpr.yaml
> 


My bot found errors running 'make dt_binding_check' on your patch:

yamllint warnings/errors:

dtschema/dtc warnings/errors:
/builds/robherring/linux-dt-review/Documentation/devicetree/bindings/soc/qcom/qcom,cpr.yaml: properties:clock-names: [{'const': 'ref'}] is not of type 'object', 'boolean'
/builds/robherring/linux-dt-review/Documentation/devicetree/bindings/soc/qcom/qcom,cpr.yaml: ignoring, error in schema: properties: clock-names
warning: no schema found in file: ./Documentation/devicetree/bindings/soc/qcom/qcom,cpr.yaml


See https://patchwork.ozlabs.org/patch/1406855

The base for the patch is generally the last rc1. Any dependencies
should be noted.

If you already ran 'make dt_binding_check' and didn't see the above
error(s), then make sure 'yamllint' is installed and dt-schema is up to
date:

pip3 install dtschema --upgrade

Please check and re-submit.


^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCH 10/13] dt-bindings: soc: qcom: cpr3: Add bindings for CPR3 driver
  2020-11-26 18:45 ` [PATCH 10/13] dt-bindings: soc: qcom: cpr3: Add bindings for CPR3 driver AngeloGioacchino Del Regno
@ 2020-11-30 17:22   ` Rob Herring
  0 siblings, 0 replies; 25+ messages in thread
From: Rob Herring @ 2020-11-30 17:22 UTC (permalink / raw)
  To: AngeloGioacchino Del Regno
  Cc: jorge.ramirez-ortiz, agross, daniel.lezcano, robh+dt, rjw,
	devicetree, martin.botka, phone-devel, broonie, konrad.dybcio,
	bjorn.andersson, marijn.suijten, nks, viresh.kumar, linux-pm,
	linux-arm-msm, ulf.hansson, lgirdwood, linux-kernel

On Thu, 26 Nov 2020 19:45:56 +0100, AngeloGioacchino Del Regno wrote:
> Add the bindings for the CPR3 driver to the documentation.
> 
> Signed-off-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@somainline.org>
> ---
>  .../bindings/soc/qcom/qcom,cpr3.yaml          | 226 ++++++++++++++++++
>  1 file changed, 226 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/soc/qcom/qcom,cpr3.yaml
> 


My bot found errors running 'make dt_binding_check' on your patch:

yamllint warnings/errors:

dtschema/dtc warnings/errors:
/builds/robherring/linux-dt-review/Documentation/devicetree/bindings/soc/qcom/qcom,cpr3.yaml: properties:clock-names: [{'const': 'ref'}] is not of type 'object', 'boolean'
/builds/robherring/linux-dt-review/Documentation/devicetree/bindings/soc/qcom/qcom,cpr3.yaml: ignoring, error in schema: properties: clock-names
warning: no schema found in file: ./Documentation/devicetree/bindings/soc/qcom/qcom,cpr3.yaml
Error: Documentation/devicetree/bindings/soc/qcom/qcom,cpr3.example.dts:39.9-30 syntax error
FATAL ERROR: Unable to parse input tree
make[1]: *** [scripts/Makefile.lib:342: Documentation/devicetree/bindings/soc/qcom/qcom,cpr3.example.dt.yaml] Error 1
make[1]: *** Waiting for unfinished jobs....
make: *** [Makefile:1364: dt_binding_check] Error 2


See https://patchwork.ozlabs.org/patch/1406856

The base for the patch is generally the last rc1. Any dependencies
should be noted.

If you already ran 'make dt_binding_check' and didn't see the above
error(s), then make sure 'yamllint' is installed and dt-schema is up to
date:

pip3 install dtschema --upgrade

Please check and re-submit.


^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCH 11/13] dt-bindings: cpufreq: Convert qcom-cpufreq-hw to YAML binding
  2020-11-26 18:45 ` [PATCH 11/13] dt-bindings: cpufreq: Convert qcom-cpufreq-hw to YAML binding AngeloGioacchino Del Regno
@ 2020-11-30 17:23   ` Rob Herring
  2020-11-30 18:23     ` AngeloGioacchino Del Regno
  2020-12-03 11:14     ` Manivannan Sadhasivam
  0 siblings, 2 replies; 25+ messages in thread
From: Rob Herring @ 2020-11-30 17:23 UTC (permalink / raw)
  To: AngeloGioacchino Del Regno
  Cc: viresh.kumar, rjw, jorge.ramirez-ortiz, robh+dt, konrad.dybcio,
	linux-pm, linux-kernel, ulf.hansson, nks, lgirdwood,
	daniel.lezcano, devicetree, bjorn.andersson, phone-devel,
	broonie, linux-arm-msm, agross, marijn.suijten, martin.botka

On Thu, 26 Nov 2020 19:45:57 +0100, AngeloGioacchino Del Regno wrote:
> Convert the qcom-cpufreq-hw documentation to YAML binding as
> qcom,cpufreq-hw.yaml.
> 
> Signed-off-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@somainline.org>
> ---
>  .../bindings/cpufreq/cpufreq-qcom-hw.txt      | 173 +---------------
>  .../bindings/cpufreq/qcom,cpufreq-hw.yaml     | 196 ++++++++++++++++++
>  2 files changed, 197 insertions(+), 172 deletions(-)
>  create mode 100644 Documentation/devicetree/bindings/cpufreq/qcom,cpufreq-hw.yaml
> 


My bot found errors running 'make dt_binding_check' on your patch:

yamllint warnings/errors:

dtschema/dtc warnings/errors:
/builds/robherring/linux-dt-review/Documentation/devicetree/bindings/cpufreq/qcom,cpufreq-hw.yaml: properties:clock-names: [{'const': 'xo'}, {'const': 'ref'}] is not of type 'object', 'boolean'
/builds/robherring/linux-dt-review/Documentation/devicetree/bindings/cpufreq/qcom,cpufreq-hw.yaml: maintainers:0: 'TBD' is not a 'email'
/builds/robherring/linux-dt-review/Documentation/devicetree/bindings/cpufreq/qcom,cpufreq-hw.yaml: ignoring, error in schema: properties: clock-names
warning: no schema found in file: ./Documentation/devicetree/bindings/cpufreq/qcom,cpufreq-hw.yaml
Error: Documentation/devicetree/bindings/cpufreq/qcom,cpufreq-hw.example.dts:150.3-151.1 syntax error
FATAL ERROR: Unable to parse input tree
make[1]: *** [scripts/Makefile.lib:342: Documentation/devicetree/bindings/cpufreq/qcom,cpufreq-hw.example.dt.yaml] Error 1
make[1]: *** Waiting for unfinished jobs....
make: *** [Makefile:1364: dt_binding_check] Error 2


See https://patchwork.ozlabs.org/patch/1406857

The base for the patch is generally the last rc1. Any dependencies
should be noted.

If you already ran 'make dt_binding_check' and didn't see the above
error(s), then make sure 'yamllint' is installed and dt-schema is up to
date:

pip3 install dtschema --upgrade

Please check and re-submit.


^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCH 11/13] dt-bindings: cpufreq: Convert qcom-cpufreq-hw to YAML binding
  2020-11-30 17:23   ` Rob Herring
@ 2020-11-30 18:23     ` AngeloGioacchino Del Regno
  2020-12-03 11:14     ` Manivannan Sadhasivam
  1 sibling, 0 replies; 25+ messages in thread
From: AngeloGioacchino Del Regno @ 2020-11-30 18:23 UTC (permalink / raw)
  To: Rob Herring
  Cc: viresh.kumar, rjw, jorge.ramirez-ortiz, robh+dt, konrad.dybcio,
	linux-pm, linux-kernel, ulf.hansson, nks, lgirdwood,
	daniel.lezcano, devicetree, bjorn.andersson, phone-devel,
	broonie, linux-arm-msm, agross, marijn.suijten, martin.botka

Il 30/11/20 18:23, Rob Herring ha scritto:
> On Thu, 26 Nov 2020 19:45:57 +0100, AngeloGioacchino Del Regno wrote:
>> Convert the qcom-cpufreq-hw documentation to YAML binding as
>> qcom,cpufreq-hw.yaml.
>>
>> Signed-off-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@somainline.org>
>> ---
>>   .../bindings/cpufreq/cpufreq-qcom-hw.txt      | 173 +---------------
>>   .../bindings/cpufreq/qcom,cpufreq-hw.yaml     | 196 ++++++++++++++++++
>>   2 files changed, 197 insertions(+), 172 deletions(-)
>>   create mode 100644 Documentation/devicetree/bindings/cpufreq/qcom,cpufreq-hw.yaml
>>
> 
> 
> My bot found errors running 'make dt_binding_check' on your patch:
> 
> yamllint warnings/errors:
> 
> dtschema/dtc warnings/errors:
> /builds/robherring/linux-dt-review/Documentation/devicetree/bindings/cpufreq/qcom,cpufreq-hw.yaml: properties:clock-names: [{'const': 'xo'}, {'const': 'ref'}] is not of type 'object', 'boolean'
> /builds/robherring/linux-dt-review/Documentation/devicetree/bindings/cpufreq/qcom,cpufreq-hw.yaml: maintainers:0: 'TBD' is not a 'email'
> /builds/robherring/linux-dt-review/Documentation/devicetree/bindings/cpufreq/qcom,cpufreq-hw.yaml: ignoring, error in schema: properties: clock-names
> warning: no schema found in file: ./Documentation/devicetree/bindings/cpufreq/qcom,cpufreq-hw.yaml
> Error: Documentation/devicetree/bindings/cpufreq/qcom,cpufreq-hw.example.dts:150.3-151.1 syntax error
> FATAL ERROR: Unable to parse input tree
> make[1]: *** [scripts/Makefile.lib:342: Documentation/devicetree/bindings/cpufreq/qcom,cpufreq-hw.example.dt.yaml] Error 1
> make[1]: *** Waiting for unfinished jobs....
> make: *** [Makefile:1364: dt_binding_check] Error 2
> 
> 
> See https://patchwork.ozlabs.org/patch/1406857
> 
> The base for the patch is generally the last rc1. Any dependencies
> should be noted.
> 
> If you already ran 'make dt_binding_check' and didn't see the above
> error(s), then make sure 'yamllint' is installed and dt-schema is up to
> date:
> 
> pip3 install dtschema --upgrade
> 
> Please check and re-submit.
> 

Hello!
All the errors that you've pointed out have been fixed on both the CPR,
CPR3 and cpufreq-hw, but before pushing a V2 of this patch series...

Well, I have a question: the qcom-cpufreq-hw driver has no MAINTAINERS
entry and there was no maintainer for this driver specified in the old
txt format binding.

What should I write in the "maintainers" field of the YAML binding for
this driver?
Should I assign it to the subsystem maintainer?

Thanks,
- Angelo

^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCH 11/13] dt-bindings: cpufreq: Convert qcom-cpufreq-hw to YAML binding
  2020-11-30 17:23   ` Rob Herring
  2020-11-30 18:23     ` AngeloGioacchino Del Regno
@ 2020-12-03 11:14     ` Manivannan Sadhasivam
  2020-12-04  0:13       ` AngeloGioacchino Del Regno
  1 sibling, 1 reply; 25+ messages in thread
From: Manivannan Sadhasivam @ 2020-12-03 11:14 UTC (permalink / raw)
  To: angelogioacchino.delregno
  Cc: viresh.kumar, rjw, jorge.ramirez-ortiz, robh+dt, konrad.dybcio,
	linux-pm, linux-kernel, ulf.hansson, nks, lgirdwood,
	daniel.lezcano, devicetree, bjorn.andersson, phone-devel,
	broonie, linux-arm-msm, agross, marijn.suijten, martin.botka,
	robh

Hi,

On Mon, Nov 30, 2020 at 10:23:05AM -0700, Rob Herring wrote:
> On Thu, 26 Nov 2020 19:45:57 +0100, AngeloGioacchino Del Regno wrote:
> > Convert the qcom-cpufreq-hw documentation to YAML binding as
> > qcom,cpufreq-hw.yaml.
> > 
> > Signed-off-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@somainline.org>

There is already a patch floating for this. Please see:
https://lkml.org/lkml/2020/10/20/676

Thanks,
Mani

> > ---
> >  .../bindings/cpufreq/cpufreq-qcom-hw.txt      | 173 +---------------
> >  .../bindings/cpufreq/qcom,cpufreq-hw.yaml     | 196 ++++++++++++++++++
> >  2 files changed, 197 insertions(+), 172 deletions(-)
> >  create mode 100644 Documentation/devicetree/bindings/cpufreq/qcom,cpufreq-hw.yaml
> > 
> 
> 
> My bot found errors running 'make dt_binding_check' on your patch:
> 
> yamllint warnings/errors:
> 
> dtschema/dtc warnings/errors:
> /builds/robherring/linux-dt-review/Documentation/devicetree/bindings/cpufreq/qcom,cpufreq-hw.yaml: properties:clock-names: [{'const': 'xo'}, {'const': 'ref'}] is not of type 'object', 'boolean'
> /builds/robherring/linux-dt-review/Documentation/devicetree/bindings/cpufreq/qcom,cpufreq-hw.yaml: maintainers:0: 'TBD' is not a 'email'
> /builds/robherring/linux-dt-review/Documentation/devicetree/bindings/cpufreq/qcom,cpufreq-hw.yaml: ignoring, error in schema: properties: clock-names
> warning: no schema found in file: ./Documentation/devicetree/bindings/cpufreq/qcom,cpufreq-hw.yaml
> Error: Documentation/devicetree/bindings/cpufreq/qcom,cpufreq-hw.example.dts:150.3-151.1 syntax error
> FATAL ERROR: Unable to parse input tree
> make[1]: *** [scripts/Makefile.lib:342: Documentation/devicetree/bindings/cpufreq/qcom,cpufreq-hw.example.dt.yaml] Error 1
> make[1]: *** Waiting for unfinished jobs....
> make: *** [Makefile:1364: dt_binding_check] Error 2
> 
> 
> See https://patchwork.ozlabs.org/patch/1406857
> 
> The base for the patch is generally the last rc1. Any dependencies
> should be noted.
> 
> If you already ran 'make dt_binding_check' and didn't see the above
> error(s), then make sure 'yamllint' is installed and dt-schema is up to
> date:
> 
> pip3 install dtschema --upgrade
> 
> Please check and re-submit.
> 

^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCH 11/13] dt-bindings: cpufreq: Convert qcom-cpufreq-hw to YAML binding
  2020-12-03 11:14     ` Manivannan Sadhasivam
@ 2020-12-04  0:13       ` AngeloGioacchino Del Regno
  2020-12-08 15:41         ` Manivannan Sadhasivam
  0 siblings, 1 reply; 25+ messages in thread
From: AngeloGioacchino Del Regno @ 2020-12-04  0:13 UTC (permalink / raw)
  To: Manivannan Sadhasivam
  Cc: viresh.kumar, rjw, jorge.ramirez-ortiz, robh+dt, konrad.dybcio,
	linux-pm, linux-kernel, ulf.hansson, nks, lgirdwood,
	daniel.lezcano, devicetree, bjorn.andersson, phone-devel,
	broonie, linux-arm-msm, agross, marijn.suijten, martin.botka,
	robh

Il 03/12/20 12:14, Manivannan Sadhasivam ha scritto:
> Hi,
> 
> On Mon, Nov 30, 2020 at 10:23:05AM -0700, Rob Herring wrote:
>> On Thu, 26 Nov 2020 19:45:57 +0100, AngeloGioacchino Del Regno wrote:
>>> Convert the qcom-cpufreq-hw documentation to YAML binding as
>>> qcom,cpufreq-hw.yaml.
>>>
>>> Signed-off-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@somainline.org>
> 
> There is already a patch floating for this. Please see:
> https://lkml.org/lkml/2020/10/20/676
> 
> Thanks,
> Mani
> 
Oh, I'm sorry, I wasn't aware of that, didn't want to step on you.

Should I rebase patch 1345789 (patch 13/13 of this series) on top of
the one that you pointed out and drop this one?

- Angelo

>>> ---
>>>   .../bindings/cpufreq/cpufreq-qcom-hw.txt      | 173 +---------------
>>>   .../bindings/cpufreq/qcom,cpufreq-hw.yaml     | 196 ++++++++++++++++++
>>>   2 files changed, 197 insertions(+), 172 deletions(-)
>>>   create mode 100644 Documentation/devicetree/bindings/cpufreq/qcom,cpufreq-hw.yaml
>>>
>>
>>
>> My bot found errors running 'make dt_binding_check' on your patch:
>>
>> yamllint warnings/errors:
>>
>> dtschema/dtc warnings/errors:
>> /builds/robherring/linux-dt-review/Documentation/devicetree/bindings/cpufreq/qcom,cpufreq-hw.yaml: properties:clock-names: [{'const': 'xo'}, {'const': 'ref'}] is not of type 'object', 'boolean'
>> /builds/robherring/linux-dt-review/Documentation/devicetree/bindings/cpufreq/qcom,cpufreq-hw.yaml: maintainers:0: 'TBD' is not a 'email'
>> /builds/robherring/linux-dt-review/Documentation/devicetree/bindings/cpufreq/qcom,cpufreq-hw.yaml: ignoring, error in schema: properties: clock-names
>> warning: no schema found in file: ./Documentation/devicetree/bindings/cpufreq/qcom,cpufreq-hw.yaml
>> Error: Documentation/devicetree/bindings/cpufreq/qcom,cpufreq-hw.example.dts:150.3-151.1 syntax error
>> FATAL ERROR: Unable to parse input tree
>> make[1]: *** [scripts/Makefile.lib:342: Documentation/devicetree/bindings/cpufreq/qcom,cpufreq-hw.example.dt.yaml] Error 1
>> make[1]: *** Waiting for unfinished jobs....
>> make: *** [Makefile:1364: dt_binding_check] Error 2
>>
>>
>> See https://patchwork.ozlabs.org/patch/1406857
>>
>> The base for the patch is generally the last rc1. Any dependencies
>> should be noted.
>>
>> If you already ran 'make dt_binding_check' and didn't see the above
>> error(s), then make sure 'yamllint' is installed and dt-schema is up to
>> date:
>>
>> pip3 install dtschema --upgrade
>>
>> Please check and re-submit.
>>


^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCH 11/13] dt-bindings: cpufreq: Convert qcom-cpufreq-hw to YAML binding
  2020-12-04  0:13       ` AngeloGioacchino Del Regno
@ 2020-12-08 15:41         ` Manivannan Sadhasivam
  0 siblings, 0 replies; 25+ messages in thread
From: Manivannan Sadhasivam @ 2020-12-08 15:41 UTC (permalink / raw)
  To: AngeloGioacchino Del Regno
  Cc: viresh.kumar, rjw, jorge.ramirez-ortiz, robh+dt, konrad.dybcio,
	linux-pm, linux-kernel, ulf.hansson, nks, lgirdwood,
	daniel.lezcano, devicetree, bjorn.andersson, phone-devel,
	broonie, linux-arm-msm, agross, marijn.suijten, martin.botka,
	robh

On Fri, Dec 04, 2020 at 01:13:52AM +0100, AngeloGioacchino Del Regno wrote:
> Il 03/12/20 12:14, Manivannan Sadhasivam ha scritto:
> > Hi,
> > 
> > On Mon, Nov 30, 2020 at 10:23:05AM -0700, Rob Herring wrote:
> > > On Thu, 26 Nov 2020 19:45:57 +0100, AngeloGioacchino Del Regno wrote:
> > > > Convert the qcom-cpufreq-hw documentation to YAML binding as
> > > > qcom,cpufreq-hw.yaml.
> > > > 
> > > > Signed-off-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@somainline.org>
> > 
> > There is already a patch floating for this. Please see:
> > https://lkml.org/lkml/2020/10/20/676
> > 
> > Thanks,
> > Mani
> > 
> Oh, I'm sorry, I wasn't aware of that, didn't want to step on you.
> 
> Should I rebase patch 1345789 (patch 13/13 of this series) on top of
> the one that you pointed out and drop this one?

Yes please.

Thanks,
Mani

> 
> - Angelo
> 
> > > > ---
> > > >   .../bindings/cpufreq/cpufreq-qcom-hw.txt      | 173 +---------------
> > > >   .../bindings/cpufreq/qcom,cpufreq-hw.yaml     | 196 ++++++++++++++++++
> > > >   2 files changed, 197 insertions(+), 172 deletions(-)
> > > >   create mode 100644 Documentation/devicetree/bindings/cpufreq/qcom,cpufreq-hw.yaml
> > > > 
> > > 
> > > 
> > > My bot found errors running 'make dt_binding_check' on your patch:
> > > 
> > > yamllint warnings/errors:
> > > 
> > > dtschema/dtc warnings/errors:
> > > /builds/robherring/linux-dt-review/Documentation/devicetree/bindings/cpufreq/qcom,cpufreq-hw.yaml: properties:clock-names: [{'const': 'xo'}, {'const': 'ref'}] is not of type 'object', 'boolean'
> > > /builds/robherring/linux-dt-review/Documentation/devicetree/bindings/cpufreq/qcom,cpufreq-hw.yaml: maintainers:0: 'TBD' is not a 'email'
> > > /builds/robherring/linux-dt-review/Documentation/devicetree/bindings/cpufreq/qcom,cpufreq-hw.yaml: ignoring, error in schema: properties: clock-names
> > > warning: no schema found in file: ./Documentation/devicetree/bindings/cpufreq/qcom,cpufreq-hw.yaml
> > > Error: Documentation/devicetree/bindings/cpufreq/qcom,cpufreq-hw.example.dts:150.3-151.1 syntax error
> > > FATAL ERROR: Unable to parse input tree
> > > make[1]: *** [scripts/Makefile.lib:342: Documentation/devicetree/bindings/cpufreq/qcom,cpufreq-hw.example.dt.yaml] Error 1
> > > make[1]: *** Waiting for unfinished jobs....
> > > make: *** [Makefile:1364: dt_binding_check] Error 2
> > > 
> > > 
> > > See https://patchwork.ozlabs.org/patch/1406857
> > > 
> > > The base for the patch is generally the last rc1. Any dependencies
> > > should be noted.
> > > 
> > > If you already ran 'make dt_binding_check' and didn't see the above
> > > error(s), then make sure 'yamllint' is installed and dt-schema is up to
> > > date:
> > > 
> > > pip3 install dtschema --upgrade
> > > 
> > > Please check and re-submit.
> > > 
> 

^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCH 13/13] dt-bindings: cpufreq: qcom-hw: Add bindings for 8998
  2020-11-26 18:45 ` [PATCH 13/13] dt-bindings: cpufreq: qcom-hw: Add bindings for 8998 AngeloGioacchino Del Regno
@ 2020-12-08 18:11   ` Rob Herring
  2020-12-22 21:11     ` AngeloGioacchino Del Regno
  0 siblings, 1 reply; 25+ messages in thread
From: Rob Herring @ 2020-12-08 18:11 UTC (permalink / raw)
  To: AngeloGioacchino Del Regno
  Cc: linux-arm-msm, linux-kernel, devicetree, linux-pm, ulf.hansson,
	jorge.ramirez-ortiz, broonie, lgirdwood, daniel.lezcano, nks,
	bjorn.andersson, agross, viresh.kumar, rjw, konrad.dybcio,
	martin.botka, marijn.suijten, phone-devel

On Thu, Nov 26, 2020 at 07:45:59PM +0100, AngeloGioacchino Del Regno wrote:
> The OSM programming addition has been done under the
> qcom,cpufreq-hw-8998 compatible name: specify the requirement
> of two additional register spaces for this functionality.
> 
> Signed-off-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@somainline.org>
> ---
>  .../bindings/cpufreq/qcom,cpufreq-hw.yaml     | 31 ++++++++++++++++---
>  1 file changed, 27 insertions(+), 4 deletions(-)
> 
> diff --git a/Documentation/devicetree/bindings/cpufreq/qcom,cpufreq-hw.yaml b/Documentation/devicetree/bindings/cpufreq/qcom,cpufreq-hw.yaml
> index 94a56317b14b..f64cea73037e 100644
> --- a/Documentation/devicetree/bindings/cpufreq/qcom,cpufreq-hw.yaml
> +++ b/Documentation/devicetree/bindings/cpufreq/qcom,cpufreq-hw.yaml
> @@ -23,17 +23,21 @@ properties:
>            - qcom,cpufreq-epss
>  
>    reg:
> +    description: Base address and size of the RBCPR register region

That doesn't make sense given you have 2 regions.

>      minItems: 2
>      maxItems: 2

maxItems: 4

>  
>    reg-names:
>      description:
> -      Frequency domain register region for each domain.
> -    items:
> -      - const: "freq-domain0"
> -      - const: "freq-domain1"
> +      Frequency domain register region for each domain. If OSM programming
> +      does not happen in the bootloader and has to be done in this driver,
> +      then also the OSM domain region osm-domain[0-1] has to be provided.

Don't write free form text for what can be expressed as schema.

> +    minItems: 2
> +    maxItems: 2

You obviously haven't tried this change with 8998. It will fail with 
more than 2. What you need here is:

minItems: 2
maxItems: 4

items:
  - const: "freq-domain0"
  - const: "freq-domain1"
  - const: "osm-domain0"
  - const: "osm-domain1"

And then...

>  
>    clock-names:
> +    minItems: 2
> +    maxItems: 2
>      - const: xo
>      - const: ref
>  
> @@ -53,9 +57,28 @@ properties:
>        property with phandle to a cpufreq_hw followed by the Domain ID(0/1)
>        in the CPU DT node.
>  
> +allOf:
> + - if:
> +     properties:
> +       reg-names:
> +         contains:
> +           const: qcom,cpufreq-hw-8998
> +   then:
> +     properties:
> +       reg:
> +         minItems: 4
> +         maxItems: 4
> +       reg-names:

...here just:

minItems: 4

And you'll need an 'else' clause with 'maxItems: 2' for reg and 
reg-names.

> +         items:
> +           - const: "freq-domain0"
> +           - const: "freq-domain1"
> +           - const: "osm-domain0"
> +           - const: "osm-domain1"
> +
>  required:
>    - compatible
>    - reg
> +  - reg-names

You can't make something that was optional now required. (Unless it was 
a mistake and all existing users always had 'reg-names'.)

>    - clock-names
>    - clocks
>    - "#freq-domain-cells"
> -- 
> 2.29.2
> 

^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCH 12/13] cpufreq: qcom-hw: Implement CPRh aware OSM programming
  2020-11-26 18:45 ` [PATCH 12/13] cpufreq: qcom-hw: Implement CPRh aware OSM programming AngeloGioacchino Del Regno
@ 2020-12-18  7:16   ` Viresh Kumar
  0 siblings, 0 replies; 25+ messages in thread
From: Viresh Kumar @ 2020-12-18  7:16 UTC (permalink / raw)
  To: bjorn.andersson, AngeloGioacchino Del Regno
  Cc: linux-arm-msm, linux-kernel, devicetree, linux-pm, ulf.hansson,
	jorge.ramirez-ortiz, broonie, lgirdwood, daniel.lezcano, nks,
	agross, robh+dt, rjw, konrad.dybcio, martin.botka,
	marijn.suijten, phone-devel

On 26-11-20, 19:45, AngeloGioacchino Del Regno wrote:
> On new SoCs (SDM845 onwards) the Operating State Manager (OSM) is
> being programmed in the bootloader and write-protected by the
> hypervisor, leaving to the OS read-only access to some of its
> registers (in order to read the Lookup Tables and also some
> status registers) and write access to the p-state register, for
> for the OS to request a specific performance state to trigger a
> DVFS switch on the CPU through the OSM hardware.
> 
> On old SoCs though (MSM8998, SDM630/660 and variants), the
> bootloader will *not* initialize the OSM (and the CPRh, as it
> is a requirement for it) before booting the OS, making any
> request to trigger a performance state change ineffective, as
> the hardware doesn't have any Lookup Table, nor is storing any
> parameter to trigger a DVFS switch. In this case, basically all
> of the OSM registers are *not* write protected for the OS, even
> though some are - but write access is granted through SCM calls.
> 
> This commit introduces support for OSM programming, which has to
> be done on these old SoCs that were distributed (almost?) always
> with a bootloader that does not do any CPRh nor OSM init before
> booting the kernel.
> In order to program the OSM on these SoCs, it is necessary to
> fullfill a "special" requirement: the Core Power Reduction
> Hardened (CPRh) hardware block must be initialized, as the OSM
> is "talking" to it in order to perform the Voltage part of DVFS;
> here, we are calling initialization of this through Linux generic
> power domains, specifically by requesting a genpd attach from the
> qcom-cpufreq-hw driver, which will give back voltages associated
> to each CPU frequency that has been declared in the OPPs, scaled
> and interpolated with the previous one, and will also give us
> parameters for the Array Power Mux (APM) and mem-acc, in order
> for this driver to be then able to generate the Lookup Tables
> that will be finally programmed to the OSM hardware.
> 
> After writing the parameters to the OSM and enabling it, all the
> programming work will never happen anymore until a OS reboot, so
> all of the allocations and "the rest" will be disposed-of: this
> is done mainly to leave the code that was referred only to the
> new SoCs intact, as to also emphasize on the fact that the OSM
> HW is, in the end, the exact same; apart some register offsets
> that are slightly different, the entire logic is the same.
> 
> Signed-off-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@somainline.org>
> ---
>  drivers/cpufreq/qcom-cpufreq-hw.c | 914 +++++++++++++++++++++++++++++-
>  1 file changed, 884 insertions(+), 30 deletions(-)

This is a lot of code, I need someone from Qcom's team to review it
and make sure it doesn't break anything for the existing platforms.

-- 
viresh

^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCH 13/13] dt-bindings: cpufreq: qcom-hw: Add bindings for 8998
  2020-12-08 18:11   ` Rob Herring
@ 2020-12-22 21:11     ` AngeloGioacchino Del Regno
  0 siblings, 0 replies; 25+ messages in thread
From: AngeloGioacchino Del Regno @ 2020-12-22 21:11 UTC (permalink / raw)
  To: Rob Herring
  Cc: linux-arm-msm, linux-kernel, devicetree, linux-pm, ulf.hansson,
	jorge.ramirez-ortiz, broonie, lgirdwood, daniel.lezcano, nks,
	bjorn.andersson, agross, viresh.kumar, rjw, konrad.dybcio,
	martin.botka, marijn.suijten, phone-devel

Il 08/12/20 19:11, Rob Herring ha scritto:

Hello! Replying very late seem to be obligatory for me nowadays
so for this and for any other late replies: I'm sorry!

> On Thu, Nov 26, 2020 at 07:45:59PM +0100, AngeloGioacchino Del Regno wrote:
>> The OSM programming addition has been done under the
>> qcom,cpufreq-hw-8998 compatible name: specify the requirement
>> of two additional register spaces for this functionality.
>>
>> Signed-off-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@somainline.org>
>> ---
>>   .../bindings/cpufreq/qcom,cpufreq-hw.yaml     | 31 ++++++++++++++++---
>>   1 file changed, 27 insertions(+), 4 deletions(-)
>>
>> diff --git a/Documentation/devicetree/bindings/cpufreq/qcom,cpufreq-hw.yaml b/Documentation/devicetree/bindings/cpufreq/qcom,cpufreq-hw.yaml
>> index 94a56317b14b..f64cea73037e 100644
>> --- a/Documentation/devicetree/bindings/cpufreq/qcom,cpufreq-hw.yaml
>> +++ b/Documentation/devicetree/bindings/cpufreq/qcom,cpufreq-hw.yaml
>> @@ -23,17 +23,21 @@ properties:
>>             - qcom,cpufreq-epss
>>   
>>     reg:
>> +    description: Base address and size of the RBCPR register region
> 
> That doesn't make sense given you have 2 regions.
> 
>>       minItems: 2
>>       maxItems: 2
> 
> maxItems: 4
> 
Indeed it doesn't make sense.

>>   
>>     reg-names:
>>       description:
>> -      Frequency domain register region for each domain.
>> -    items:
>> -      - const: "freq-domain0"
>> -      - const: "freq-domain1"
>> +      Frequency domain register region for each domain. If OSM programming
>> +      does not happen in the bootloader and has to be done in this driver,
>> +      then also the OSM domain region osm-domain[0-1] has to be provided.
> 
> Don't write free form text for what can be expressed as schema.
> 
I guess the later 'if' for 8998 is sufficient to express that, then...
right?

>> +    minItems: 2
>> +    maxItems: 2
> 
> You obviously haven't tried this change with 8998. It will fail with
> more than 2. What you need here is:
> 
My testing methodology must be flawed. Or perhaps I just need some more 
sleep... probably the latter.

> minItems: 2
> maxItems: 4
> 
> items:
>    - const: "freq-domain0"
>    - const: "freq-domain1"
>    - const: "osm-domain0"
>    - const: "osm-domain1"
> 
> And then...
> 
>>   
>>     clock-names:
>> +    minItems: 2
>> +    maxItems: 2
>>       - const: xo
>>       - const: ref
>>   
>> @@ -53,9 +57,28 @@ properties:
>>         property with phandle to a cpufreq_hw followed by the Domain ID(0/1)
>>         in the CPU DT node.
>>   
>> +allOf:
>> + - if:
>> +     properties:
>> +       reg-names:
>> +         contains:
>> +           const: qcom,cpufreq-hw-8998
>> +   then:
>> +     properties:
>> +       reg:
>> +         minItems: 4
>> +         maxItems: 4
>> +       reg-names:
> 
> ...here just:
> 
> minItems: 4
> 
> And you'll need an 'else' clause with 'maxItems: 2' for reg and
> reg-names.
> 
Big thank you for that!!!

>> +         items:
>> +           - const: "freq-domain0"
>> +           - const: "freq-domain1"
>> +           - const: "osm-domain0"
>> +           - const: "osm-domain1"
>> +
>>   required:
>>     - compatible
>>     - reg
>> +  - reg-names
> 
> You can't make something that was optional now required. (Unless it was
> a mistake and all existing users always had 'reg-names'.)
> 
Well, yes. All existing users are already declaring reg-names, no DT 
changes to do for them.

>>     - clock-names
>>     - clocks
>>     - "#freq-domain-cells"
>> -- 
>> 2.29.2
>>

Thanks for the review.
A V2 of the entire series will come soon!

-- Angelo

^ permalink raw reply	[flat|nested] 25+ messages in thread

end of thread, other threads:[~2020-12-22 21:12 UTC | newest]

Thread overview: 25+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2020-11-26 18:45 [PATCH 00/13] Enable CPRh/3/4, CPU Scaling on various QCOM SoCs AngeloGioacchino Del Regno
2020-11-26 18:45 ` [PATCH 01/13] cpuidle: qcom_spm: Detach state machine from main SPM handling AngeloGioacchino Del Regno
2020-11-27 18:07   ` kernel test robot
2020-11-26 18:45 ` [PATCH 02/13] soc: qcom: spm: Implement support for SAWv4.1, SDM630/660 L2 AVS AngeloGioacchino Del Regno
2020-11-26 18:45 ` [PATCH 03/13] soc: qcom: spm: Add compatible for MSM8998 SAWv4.1 L2 AngeloGioacchino Del Regno
2020-11-26 18:45 ` [PATCH 04/13] cpufreq: blacklist SDM630/636/660 in cpufreq-dt-platdev AngeloGioacchino Del Regno
2020-11-26 18:45 ` [PATCH 05/13] soc: qcom: cpr: Move common functions to new file AngeloGioacchino Del Regno
2020-11-26 18:45 ` [PATCH 06/13] arm64: qcom: qcs404: Change CPR nvmem-names AngeloGioacchino Del Regno
2020-11-26 18:45 ` [PATCH 07/13] dt-bindings: avs: cpr: Convert binding to YAML schema AngeloGioacchino Del Regno
2020-11-30 17:03   ` Rob Herring
2020-11-26 18:45 ` [PATCH 08/13] soc: qcom: Add support for Core Power Reduction v3, v4 and Hardened AngeloGioacchino Del Regno
2020-11-26 18:45 ` [PATCH 09/13] MAINTAINERS: Add entry for Qualcomm CPRv3/v4/Hardened driver AngeloGioacchino Del Regno
2020-11-26 18:45 ` [PATCH 10/13] dt-bindings: soc: qcom: cpr3: Add bindings for CPR3 driver AngeloGioacchino Del Regno
2020-11-30 17:22   ` Rob Herring
2020-11-26 18:45 ` [PATCH 11/13] dt-bindings: cpufreq: Convert qcom-cpufreq-hw to YAML binding AngeloGioacchino Del Regno
2020-11-30 17:23   ` Rob Herring
2020-11-30 18:23     ` AngeloGioacchino Del Regno
2020-12-03 11:14     ` Manivannan Sadhasivam
2020-12-04  0:13       ` AngeloGioacchino Del Regno
2020-12-08 15:41         ` Manivannan Sadhasivam
2020-11-26 18:45 ` [PATCH 12/13] cpufreq: qcom-hw: Implement CPRh aware OSM programming AngeloGioacchino Del Regno
2020-12-18  7:16   ` Viresh Kumar
2020-11-26 18:45 ` [PATCH 13/13] dt-bindings: cpufreq: qcom-hw: Add bindings for 8998 AngeloGioacchino Del Regno
2020-12-08 18:11   ` Rob Herring
2020-12-22 21:11     ` AngeloGioacchino Del Regno

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).