All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v2 00/10] QCOM 8074 cpuidle driver
@ 2014-08-12 19:43 Lina Iyer
  2014-08-12 19:43 ` [PATCH v2 01/10] msm: scm: Move scm-boot files to drivers/soc and include/soc Lina Iyer
                   ` (10 more replies)
  0 siblings, 11 replies; 46+ messages in thread
From: Lina Iyer @ 2014-08-12 19:43 UTC (permalink / raw)
  To: daniel.lezcano, khilman, amit.kucheria, sboyd, davidb, galak,
	linux-arm-msm
  Cc: msivasub, Lina Iyer

This is version #2 of the patches for cpuidle driver and its dependencies.

Changes from version #1/RFC:

- Remove hotplug from the patch series. Will submit it seprately.
- Fix SPM drivers per the review comments
- Modify patch sequence to compile SPM drivers independent of msm-pm, so as to
  allow wfi() calls to use SPM even without SoC interface driver.

8074 like any ARM SoC can do architectural clock gating, that helps save on
power, but not enough of leakage power.  Leakage power of the SoC can be
further reduced by turning off power to the core. To aid this, every core (cpu
and L2) is accompanied by a Sub-system Power Manager (SPM), that can be
configured to indicate the low power mode, the core would be put into and the
SPM programs the peripheral h/w accordingly to enter low power and turn off the
power rail to the core.

The patchsets do the following -

- Move scm-boot files from arm/mach-qcom to drivers/soc, following convention.
They are based on Stephen Boyd's series of patches
[http://www.spinics.net/lists/linux-arm-msm/msg10482.html]

- Add new Secure Monitor flags to support warmboot of a quad core system.

- Introduce the SPM driver to control power to the core. The SPM h/w IP works
in conjunction with the Krait CPU/L2. When the core executes WFI instruction,
the core is clockgated and the SPM state machine takes over and powers the core
down. An interrupt from GIC, resumes the SPM state machine which brings the cpu
out of the low power mode.

- Add the device tree configuration for each of the SPM nodes. There is one for
each cpu. There is one for each cpu and one for L2 and one for L2.

- Introduce the SoC driver interface layer to configure SPM per the core's idle
 state. To power down the cpu core, the SPM h/w needs to be set up correctly
to power down the core, when the core executes WFI. Linux is expected to call
into Secure Monitor to power down the core. At reset, the core will start in
seure mode and will be returned back to Linux. 

- Add CPUIDLE driver for QCOM cpus. The cpuidle driver uses the SoC interface
layer to configure the SPM to allow Krait to be powered down. The driver
supports 4 low power modes, but not all SoCs, support all low power modes. The
modes supported are configured in device tree nodes.

- Provide device configuration for 8074 SoC. Current support is for WFI and
standalone power collapse, which powers only the core independent of the
other cores and caches.

Thanks
Lina



Lina Iyer (10):
  msm: scm: Move scm-boot files to drivers/soc and include/soc
  msm: scm: Add SCM warmboot flags for quad core targets.
  qcom: spm: Add Subsystem Power Manager (SPM) driver for QCOM chipsets
  soc: qcom: Add QCOM Power management config
  arm: qcom-msm8974: Add CPU phandles to CPU definitions
  arm: dts: qcom: Add SPM device bindings for 8974
  qcom: msm-pm: Add cpu low power mode functions
  qcom: cpuidle: Add cpuidle driver for QCOM cpus
  qcom: cpuidle: Config option to enable QCOM cpuidle driver
  qcom: cpuidle: Add cpuidle device nodes for 8974 chipset

 .../devicetree/bindings/arm/msm/qcom,cpuidle.txt   |  73 +++
 .../devicetree/bindings/arm/msm/spm-v2.txt         |  62 ++
 arch/arm/boot/dts/qcom-msm8974-pm.dtsi             | 112 ++++
 arch/arm/boot/dts/qcom-msm8974.dtsi                |  10 +-
 arch/arm/mach-qcom/Makefile                        |   1 -
 arch/arm/mach-qcom/platsmp.c                       |   2 +-
 drivers/cpuidle/Kconfig.arm                        |   6 +
 drivers/cpuidle/Makefile                           |   1 +
 drivers/cpuidle/cpuidle-qcom.c                     | 140 ++++
 drivers/soc/qcom/Kconfig                           |   8 +
 drivers/soc/qcom/Makefile                          |   4 +-
 drivers/soc/qcom/msm-pm.c                          | 219 +++++++
 .../arm/mach-qcom => drivers/soc/qcom}/scm-boot.c  |   4 +-
 drivers/soc/qcom/spm-devices.c                     | 703 +++++++++++++++++++++
 drivers/soc/qcom/spm.c                             | 482 ++++++++++++++
 drivers/soc/qcom/spm_driver.h                      | 116 ++++
 include/soc/qcom/pm.h                              |  39 ++
 .../arm/mach-qcom => include/soc/qcom}/scm-boot.h  |   3 +
 include/soc/qcom/spm.h                             |  70 ++
 19 files changed, 2046 insertions(+), 9 deletions(-)
 create mode 100644 Documentation/devicetree/bindings/arm/msm/qcom,cpuidle.txt
 create mode 100644 Documentation/devicetree/bindings/arm/msm/spm-v2.txt
 create mode 100644 arch/arm/boot/dts/qcom-msm8974-pm.dtsi
 create mode 100644 drivers/cpuidle/cpuidle-qcom.c
 create mode 100644 drivers/soc/qcom/msm-pm.c
 rename {arch/arm/mach-qcom => drivers/soc/qcom}/scm-boot.c (97%)
 create mode 100644 drivers/soc/qcom/spm-devices.c
 create mode 100644 drivers/soc/qcom/spm.c
 create mode 100644 drivers/soc/qcom/spm_driver.h
 create mode 100644 include/soc/qcom/pm.h
 rename {arch/arm/mach-qcom => include/soc/qcom}/scm-boot.h (91%)
 create mode 100644 include/soc/qcom/spm.h

-- 
1.9.1

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

* [PATCH v2 01/10] msm: scm: Move scm-boot files to drivers/soc and include/soc
  2014-08-12 19:43 [PATCH v2 00/10] QCOM 8074 cpuidle driver Lina Iyer
@ 2014-08-12 19:43 ` Lina Iyer
  2014-08-12 19:43 ` [PATCH v2 02/10] msm: scm: Add SCM warmboot flags for quad core targets Lina Iyer
                   ` (9 subsequent siblings)
  10 siblings, 0 replies; 46+ messages in thread
From: Lina Iyer @ 2014-08-12 19:43 UTC (permalink / raw)
  To: daniel.lezcano, khilman, amit.kucheria, sboyd, davidb, galak,
	linux-arm-msm
  Cc: msivasub, Lina Iyer

Signed-off-by: Lina Iyer <lina.iyer@linaro.org>
---
 arch/arm/mach-qcom/Makefile                         | 1 -
 arch/arm/mach-qcom/platsmp.c                        | 2 +-
 drivers/soc/qcom/Makefile                           | 2 +-
 {arch/arm/mach-qcom => drivers/soc/qcom}/scm-boot.c | 4 ++--
 {arch/arm/mach-qcom => include/soc/qcom}/scm-boot.h | 0
 5 files changed, 4 insertions(+), 5 deletions(-)
 rename {arch/arm/mach-qcom => drivers/soc/qcom}/scm-boot.c (97%)
 rename {arch/arm/mach-qcom => include/soc/qcom}/scm-boot.h (100%)

diff --git a/arch/arm/mach-qcom/Makefile b/arch/arm/mach-qcom/Makefile
index db41e8c..e324375 100644
--- a/arch/arm/mach-qcom/Makefile
+++ b/arch/arm/mach-qcom/Makefile
@@ -1,3 +1,2 @@
 obj-y			:= board.o
 obj-$(CONFIG_SMP)	+= platsmp.o
-obj-$(CONFIG_QCOM_SCM)	+= scm-boot.o
diff --git a/arch/arm/mach-qcom/platsmp.c b/arch/arm/mach-qcom/platsmp.c
index d690856..a692bcb 100644
--- a/arch/arm/mach-qcom/platsmp.c
+++ b/arch/arm/mach-qcom/platsmp.c
@@ -20,7 +20,7 @@
 
 #include <asm/smp_plat.h>
 
-#include "scm-boot.h"
+#include <soc/qcom/scm-boot.h>
 
 #define VDD_SC1_ARRAY_CLAMP_GFS_CTL	0x35a0
 #define SCSS_CPU1CORE_RESET		0x2d80
diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile
index a39446d..70d52ed 100644
--- a/drivers/soc/qcom/Makefile
+++ b/drivers/soc/qcom/Makefile
@@ -1,3 +1,3 @@
 obj-$(CONFIG_QCOM_GSBI)	+=	qcom_gsbi.o
 CFLAGS_scm.o :=$(call as-instr,.arch_extension sec,-DREQUIRES_SEC=1)
-obj-$(CONFIG_QCOM_SCM) += scm.o
+obj-$(CONFIG_QCOM_SCM) += scm.o scm-boot.o
diff --git a/arch/arm/mach-qcom/scm-boot.c b/drivers/soc/qcom/scm-boot.c
similarity index 97%
rename from arch/arm/mach-qcom/scm-boot.c
rename to drivers/soc/qcom/scm-boot.c
index 5add20e..60ff7b4 100644
--- a/arch/arm/mach-qcom/scm-boot.c
+++ b/drivers/soc/qcom/scm-boot.c
@@ -17,9 +17,9 @@
 
 #include <linux/module.h>
 #include <linux/slab.h>
-#include <soc/qcom/scm.h>
 
-#include "scm-boot.h"
+#include <soc/qcom/scm.h>
+#include <soc/qcom/scm-boot.h>
 
 /*
  * Set the cold/warm boot address for one of the CPU cores.
diff --git a/arch/arm/mach-qcom/scm-boot.h b/include/soc/qcom/scm-boot.h
similarity index 100%
rename from arch/arm/mach-qcom/scm-boot.h
rename to include/soc/qcom/scm-boot.h
-- 
1.9.1

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

* [PATCH v2 02/10] msm: scm: Add SCM warmboot flags for quad core targets.
  2014-08-12 19:43 [PATCH v2 00/10] QCOM 8074 cpuidle driver Lina Iyer
  2014-08-12 19:43 ` [PATCH v2 01/10] msm: scm: Move scm-boot files to drivers/soc and include/soc Lina Iyer
@ 2014-08-12 19:43 ` Lina Iyer
  2014-08-14 10:20   ` Pramod Gurav
  2014-08-12 19:43 ` [PATCH v2 03/10] qcom: spm: Add Subsystem Power Manager (SPM) driver for QCOM chipsets Lina Iyer
                   ` (8 subsequent siblings)
  10 siblings, 1 reply; 46+ messages in thread
From: Lina Iyer @ 2014-08-12 19:43 UTC (permalink / raw)
  To: daniel.lezcano, khilman, amit.kucheria, sboyd, davidb, galak,
	linux-arm-msm
  Cc: msivasub, Lina Iyer

Quad core targets like APQ8074, APQ8064, APQ8084 need SCM support set up
warm boot addresses in the Secure Monitor. Extend the SCM flags to
support warmboot addresses for seconday cores.

Signed-off-by: Lina Iyer <lina.iyer@linaro.org>
---
 include/soc/qcom/scm-boot.h | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/include/soc/qcom/scm-boot.h b/include/soc/qcom/scm-boot.h
index 6aabb24..7ae2152 100644
--- a/include/soc/qcom/scm-boot.h
+++ b/include/soc/qcom/scm-boot.h
@@ -18,6 +18,9 @@
 #define SCM_FLAG_COLDBOOT_CPU3		0x20
 #define SCM_FLAG_WARMBOOT_CPU0		0x04
 #define SCM_FLAG_WARMBOOT_CPU1		0x02
+#define SCM_FLAG_WARMBOOT_CPU2		0x10
+#define SCM_FLAG_WARMBOOT_CPU3		0x40
+
 
 int scm_set_boot_addr(phys_addr_t addr, int flags);
 
-- 
1.9.1

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

* [PATCH v2 03/10] qcom: spm: Add Subsystem Power Manager (SPM) driver for QCOM chipsets
  2014-08-12 19:43 [PATCH v2 00/10] QCOM 8074 cpuidle driver Lina Iyer
  2014-08-12 19:43 ` [PATCH v2 01/10] msm: scm: Move scm-boot files to drivers/soc and include/soc Lina Iyer
  2014-08-12 19:43 ` [PATCH v2 02/10] msm: scm: Add SCM warmboot flags for quad core targets Lina Iyer
@ 2014-08-12 19:43 ` Lina Iyer
  2014-08-13 10:49   ` Daniel Lezcano
                     ` (3 more replies)
  2014-08-12 19:43 ` [PATCH v2 04/10] soc: qcom: Add QCOM Power management config Lina Iyer
                   ` (7 subsequent siblings)
  10 siblings, 4 replies; 46+ messages in thread
From: Lina Iyer @ 2014-08-12 19:43 UTC (permalink / raw)
  To: daniel.lezcano, khilman, amit.kucheria, sboyd, davidb, galak,
	linux-arm-msm
  Cc: msivasub, Lina Iyer, Praveen Chidamabram, Murali Nalajala

Qualcomm chipsets use an separate h/w block to control the logic around
the processor cores (cpu and L2). The SPM h/w block regulates power to
the cores and controls the power when the core enter low power modes.

Each core has its own instance of SPM. The SPM has the following key
functions
	- Configure the h/w dependencies when entering low power modes
	- Wait for interrupt and wake up on interrupt
	- Ensure the dependencies are ready before bringing the core out
	  of sleep
	- Regulating voltage to the core, interfacing with the PMIC.
	- Optimize power based on runtime recommendations.

The driver identifies and configures the SPMs, by reading the nodes and
the register values from the devicetree. The SPMs need to be configured
to allow the processor to be idled in a low power state.

Signed-off-by: Praveen Chidamabram <pchidamb@codeaurora.org>
Signed-off-by: Murali Nalajala <mnalajal@codeaurora.org>
Signed-off-by: Lina Iyer <lina.iyer@linaro.org>
---
 .../devicetree/bindings/arm/msm/spm-v2.txt         |  62 ++
 drivers/soc/qcom/Makefile                          |   2 +
 drivers/soc/qcom/spm-devices.c                     | 703 +++++++++++++++++++++
 drivers/soc/qcom/spm.c                             | 482 ++++++++++++++
 drivers/soc/qcom/spm_driver.h                      | 116 ++++
 include/soc/qcom/spm.h                             |  70 ++
 6 files changed, 1435 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/arm/msm/spm-v2.txt
 create mode 100644 drivers/soc/qcom/spm-devices.c
 create mode 100644 drivers/soc/qcom/spm.c
 create mode 100644 drivers/soc/qcom/spm_driver.h
 create mode 100644 include/soc/qcom/spm.h

diff --git a/Documentation/devicetree/bindings/arm/msm/spm-v2.txt b/Documentation/devicetree/bindings/arm/msm/spm-v2.txt
new file mode 100644
index 0000000..3130f4b
--- /dev/null
+++ b/Documentation/devicetree/bindings/arm/msm/spm-v2.txt
@@ -0,0 +1,62 @@
+* MSM Subsystem Power Manager (spm-v2)
+
+S4 generation of MSMs have SPM hardware blocks to control the Application
+Processor Sub-System power. These SPM blocks run individual state machine
+to determine what the core (L2 or Krait/Scorpion) would do when the WFI
+instruction is executed by the core.
+
+The devicetree representation of the SPM block should be:
+
+Required properties
+
+- compatible: Could be one of -
+		"qcom,spm-v2.1"
+		"qcom,spm-v3.0"
+- reg: The physical address and the size of the SPM's memory mapped registers
+- qcom,cpu: phandle for the CPU that the SPM block is attached to. On targets
+	that dont support CPU phandles the driver would support qcom,core-id.
+	This field is required on only for SPMs that control the CPU.
+- qcom,saw2-cfg: SAW2 configuration register
+- qcom,saw2-spm-dly: Provides the values for the SPM delay command in the SPM
+	sequence
+- qcom,saw2-spm-ctl: The SPM control register
+- qcom,name: The name with which a SPM device is identified by the power
+	management code.
+
+Optional properties
+
+- qcom,saw2-pmic-data0..7: Specify the pmic data value and the associated FTS
+	(Fast Transient Switch) index to send the PMIC data to
+- qcom,vctl-port: The PVC (PMIC Virtual Channel) port used for changing
+	voltage
+- qcom,phase-port: The PVC port used for changing the number of phases
+- qcom,pfm-port: The PVC port used for enabling PWM/PFM modes
+- qcom,saw2-spm-cmd-wfi: The WFI command sequence
+- qcom,saw2-spm-cmd-ret: The Retention command sequence
+- qcom,saw2-spm-cmd-spc: The Standalone PC command sequence
+- qcom,saw2-spm-cmd-pc-no-rpm: The Power Collapse command sequence where APPS
+	proc won't inform the RPM.
+- qcom,saw2-spm-cmd-pc: The Power Collapse command sequence. This sequence may
+	turn off other SoC components.
+- qcom,saw2-spm-cmd-gdhs: GDHS (Globally Distributed Head Switch) command
+	sequence. This sequence will retain the memory but turn off the logic.
+- qcom,cpu-vctl-list: List of cpu node phandles, whose voltage the spm device
+	can control.
+- qcom,vctl-timeout-us: The timeout value in microseconds to wait for voltage to
+	change after sending the voltage command to the PMIC.
+-
+Example:
+	qcom,spm@f9089000 {
+		compatible = "qcom,spm-v2";
+		#address-cells = <1>;
+		#size-cells = <1>;
+		reg = <0xf9089000 0x1000>;
+		qcom,cpu = <&CPU0>;
+		qcom,saw2-cfg = <0x1>;
+		qcom,saw2-spm-dly= <0x20000400>;
+		qcom,saw2-spm-ctl = <0x1>;
+		qcom,saw2-spm-cmd-wfi = [03 0b 0f];
+		qcom,saw2-spm-cmd-spc = [00 20 50 80 60 70 10 92
+				a0 b0 03 68 70 3b 92 a0 b0
+				82 2b 50 10 30 02 22 30 0f];
+	};
diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile
index 70d52ed..d7ae93b 100644
--- a/drivers/soc/qcom/Makefile
+++ b/drivers/soc/qcom/Makefile
@@ -1,3 +1,5 @@
 obj-$(CONFIG_QCOM_GSBI)	+=	qcom_gsbi.o
+obj-$(CONFIG_QCOM_PM) +=	spm-devices.o spm.o
+
 CFLAGS_scm.o :=$(call as-instr,.arch_extension sec,-DREQUIRES_SEC=1)
 obj-$(CONFIG_QCOM_SCM) += scm.o scm-boot.o
diff --git a/drivers/soc/qcom/spm-devices.c b/drivers/soc/qcom/spm-devices.c
new file mode 100644
index 0000000..567e9f9
--- /dev/null
+++ b/drivers/soc/qcom/spm-devices.c
@@ -0,0 +1,703 @@
+/* Copyright (c) 2011-2014, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/slab.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/err.h>
+#include <linux/platform_device.h>
+#include <linux/err.h>
+
+#include <soc/qcom/spm.h>
+
+#include "spm_driver.h"
+
+#define VDD_DEFAULT 0xDEADF00D
+
+struct msm_spm_power_modes {
+	uint32_t mode;
+	bool notify_rpm;
+	uint32_t start_addr;
+};
+
+struct msm_spm_device {
+	struct list_head list;
+	bool initialized;
+	const char *name;
+	struct msm_spm_driver_data reg_data;
+	struct msm_spm_power_modes *modes;
+	uint32_t num_modes;
+	uint32_t cpu_vdd;
+	struct cpumask mask;
+	void __iomem *q2s_reg;
+};
+
+struct msm_spm_vdd_info {
+	struct msm_spm_device *vctl_dev;
+	uint32_t vlevel;
+	int err;
+};
+
+static LIST_HEAD(spm_list);
+static DEFINE_PER_CPU_SHARED_ALIGNED(struct msm_spm_device, msm_cpu_spm_device);
+static DEFINE_PER_CPU(struct msm_spm_device *, cpu_vctl_device);
+
+static void msm_spm_smp_set_vdd(void *data)
+{
+	struct msm_spm_vdd_info *info = (struct msm_spm_vdd_info *)data;
+	struct msm_spm_device *dev = info->vctl_dev;
+
+	dev->cpu_vdd = info->vlevel;
+	info->err = msm_spm_drv_set_vdd(&dev->reg_data, info->vlevel);
+}
+
+/**
+ * msm_spm_probe_done(): Verify and return the status of the cpu(s) and l2
+ * probe.
+ * Return: 0 if all spm devices have been probed, else return -EPROBE_DEFER.
+ * if probe failed, then return the err number for that failure.
+ */
+int msm_spm_probe_done(void)
+{
+	struct msm_spm_device *dev;
+	int cpu;
+	int ret = 0;
+
+	for_each_possible_cpu(cpu) {
+		dev = per_cpu(cpu_vctl_device, cpu);
+		if (!dev)
+			return -EPROBE_DEFER;
+
+		ret = IS_ERR(dev);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL(msm_spm_probe_done);
+
+void msm_spm_dump_regs(unsigned int cpu)
+{
+	dump_regs(&per_cpu(msm_cpu_spm_device, cpu).reg_data, cpu);
+}
+
+/**
+ * msm_spm_set_vdd(): Set core voltage
+ * @cpu: core id
+ * @vlevel: Encoded PMIC data.
+ *
+ * Return: 0 on success or -(ERRNO) on failure.
+ */
+int msm_spm_set_vdd(unsigned int cpu, unsigned int vlevel)
+{
+	struct msm_spm_vdd_info info;
+	struct msm_spm_device *dev = per_cpu(cpu_vctl_device, cpu);
+	int ret;
+
+	if (!dev)
+		return -EPROBE_DEFER;
+
+	ret = IS_ERR(dev);
+	if (ret)
+		return ret;
+
+	info.vctl_dev = dev;
+	info.vlevel = vlevel;
+
+	ret = smp_call_function_any(&dev->mask, msm_spm_smp_set_vdd, &info,
+					true);
+	if (ret)
+		return ret;
+
+	return info.err;
+}
+EXPORT_SYMBOL(msm_spm_set_vdd);
+
+/**
+ * msm_spm_get_vdd(): Get core voltage
+ * @cpu: core id
+ * @return: Returns encoded PMIC data.
+ */
+unsigned int msm_spm_get_vdd(unsigned int cpu)
+{
+	int ret;
+	struct msm_spm_device *dev = per_cpu(cpu_vctl_device, cpu);
+
+	if (!dev)
+		return -EPROBE_DEFER;
+
+	ret = IS_ERR(dev);
+	if (ret)
+		return ret;
+
+	return dev->cpu_vdd;
+}
+EXPORT_SYMBOL(msm_spm_get_vdd);
+
+static void msm_spm_config_q2s(struct msm_spm_device *dev, unsigned int mode)
+{
+	uint32_t spm_legacy_mode = 0;
+	uint32_t qchannel_ignore = 0;
+	uint32_t val = 0;
+
+	if (!dev->q2s_reg)
+		return;
+
+	switch (mode) {
+	case MSM_SPM_MODE_DISABLED:
+	case MSM_SPM_MODE_CLOCK_GATING:
+		qchannel_ignore = 1;
+		spm_legacy_mode = 0;
+		break;
+	case MSM_SPM_MODE_RETENTION:
+		qchannel_ignore = 0;
+		spm_legacy_mode = 0;
+		break;
+	case MSM_SPM_MODE_GDHS:
+	case MSM_SPM_MODE_POWER_COLLAPSE:
+		qchannel_ignore = 0;
+		spm_legacy_mode = 1;
+		break;
+	default:
+		break;
+	}
+
+	val = spm_legacy_mode << 2 | qchannel_ignore << 1;
+	__raw_writel(val, dev->q2s_reg);
+	mb();
+}
+
+static int msm_spm_dev_set_low_power_mode(struct msm_spm_device *dev,
+		unsigned int mode, bool notify_rpm)
+{
+	uint32_t i;
+	uint32_t start_addr = 0;
+	int ret = -EINVAL;
+	bool pc_mode = false;
+
+	if (!dev->initialized)
+		return -ENXIO;
+
+	if ((mode == MSM_SPM_MODE_POWER_COLLAPSE)
+			|| (mode == MSM_SPM_MODE_GDHS))
+		pc_mode = true;
+
+	if (mode == MSM_SPM_MODE_DISABLED) {
+		ret = msm_spm_drv_set_spm_enable(&dev->reg_data, false);
+	} else if (!msm_spm_drv_set_spm_enable(&dev->reg_data, true)) {
+		for (i = 0; i < dev->num_modes; i++) {
+			if ((dev->modes[i].mode == mode) &&
+				(dev->modes[i].notify_rpm == notify_rpm)) {
+				start_addr = dev->modes[i].start_addr;
+				break;
+			}
+		}
+		ret = msm_spm_drv_set_low_power_mode(&dev->reg_data,
+					start_addr, pc_mode);
+	}
+
+	msm_spm_config_q2s(dev, mode);
+
+	return ret;
+}
+
+static int msm_spm_dev_init(struct msm_spm_device *dev,
+		struct msm_spm_platform_data *data)
+{
+	int i, ret = -ENOMEM;
+	uint32_t offset = 0;
+
+	dev->cpu_vdd = VDD_DEFAULT;
+	dev->num_modes = data->num_modes;
+	dev->modes = kmalloc(
+			sizeof(struct msm_spm_power_modes) * dev->num_modes,
+			GFP_KERNEL);
+
+	if (!dev->modes)
+		goto spm_failed_malloc;
+
+	dev->reg_data.major = data->major;
+	dev->reg_data.minor = data->minor;
+	ret = msm_spm_drv_init(&dev->reg_data, data);
+
+	if (ret)
+		goto spm_failed_init;
+
+	for (i = 0; i < dev->num_modes; i++) {
+
+		/* Default offset is 0 and gets updated as we write more
+		 * sequences into SPM
+		 */
+		dev->modes[i].start_addr = offset;
+		ret = msm_spm_drv_write_seq_data(&dev->reg_data,
+						data->modes[i].cmd, &offset);
+		if (ret < 0)
+			goto spm_failed_init;
+
+		dev->modes[i].mode = data->modes[i].mode;
+		dev->modes[i].notify_rpm = data->modes[i].notify_rpm;
+	}
+	msm_spm_drv_reinit(&dev->reg_data);
+	dev->initialized = true;
+	return 0;
+
+spm_failed_init:
+	kfree(dev->modes);
+spm_failed_malloc:
+	return ret;
+}
+
+/**
+ * msm_spm_turn_on_cpu_rail(): Power on cpu rail before turning on core
+ * @base: The SAW VCTL register which would set the voltage up.
+ * @val: The value to be set on the rail
+ * @cpu: The cpu for this with rail is being powered on
+ */
+int msm_spm_turn_on_cpu_rail(void __iomem *base, unsigned int val, int cpu)
+{
+	uint32_t timeout = 2000; /* delay for voltage to settle on the core */
+	struct msm_spm_device *dev = per_cpu(cpu_vctl_device, cpu);
+
+	/*
+	 * If clock drivers have already set up the voltage,
+	 * do not overwrite that value.
+	 */
+	if (dev && (dev->cpu_vdd != VDD_DEFAULT))
+		return 0;
+
+	/* Set the CPU supply regulator voltage */
+	val = (val & 0xFF);
+	writel_relaxed(val, base);
+	mb();
+	udelay(timeout);
+
+	/* Enable the CPU supply regulator*/
+	val = 0x30080;
+	writel_relaxed(val, base);
+	mb();
+	udelay(timeout);
+
+	return 0;
+}
+EXPORT_SYMBOL(msm_spm_turn_on_cpu_rail);
+
+void msm_spm_reinit(void)
+{
+	unsigned int cpu;
+
+	for_each_possible_cpu(cpu)
+		msm_spm_drv_reinit(&per_cpu(msm_cpu_spm_device.reg_data, cpu));
+}
+EXPORT_SYMBOL(msm_spm_reinit);
+
+/*
+ * msm_spm_is_mode_avail() - Specifies if a mode is available for the cpu
+ * It should only be used to decide a mode before lpm driver is probed.
+ * @mode: SPM LPM mode to be selected
+ */
+bool msm_spm_is_mode_avail(unsigned int mode)
+{
+	struct msm_spm_device *dev = &__get_cpu_var(msm_cpu_spm_device);
+	int i;
+
+	for (i = 0; i < dev->num_modes; i++) {
+		if (dev->modes[i].mode == mode)
+			return true;
+	}
+
+	return false;
+}
+
+/**
+ * msm_spm_set_low_power_mode() - Configure SPM start address for low power mode
+ * @mode: SPM LPM mode to enter
+ * @notify_rpm: Notify RPM in this mode
+ */
+int msm_spm_set_low_power_mode(unsigned int mode, bool notify_rpm)
+{
+	struct msm_spm_device *dev = &__get_cpu_var(msm_cpu_spm_device);
+
+	return msm_spm_dev_set_low_power_mode(dev, mode, notify_rpm);
+}
+EXPORT_SYMBOL(msm_spm_set_low_power_mode);
+
+/**
+ * msm_spm_init(): Board initalization function
+ * @data: platform specific SPM register configuration data
+ * @nr_devs: Number of SPM devices being initialized
+ */
+int __init msm_spm_init(struct msm_spm_platform_data *data, int nr_devs)
+{
+	unsigned int cpu;
+	int ret = 0;
+
+	BUG_ON((nr_devs < num_possible_cpus()) || !data);
+
+	for_each_possible_cpu(cpu) {
+		struct msm_spm_device *dev = &per_cpu(msm_cpu_spm_device, cpu);
+
+		ret = msm_spm_dev_init(dev, &data[cpu]);
+		if (ret < 0) {
+			pr_warn("%s():failed CPU:%u ret:%d\n", __func__,
+					cpu, ret);
+			break;
+		}
+	}
+
+	return ret;
+}
+
+struct msm_spm_device *msm_spm_get_device_by_name(const char *name)
+{
+	struct list_head *list;
+
+	list_for_each(list, &spm_list) {
+		struct msm_spm_device *dev
+			= list_entry(list, typeof(*dev), list);
+		if (dev->name && !strcmp(dev->name, name))
+			return dev;
+	}
+	return ERR_PTR(-ENODEV);
+}
+
+int msm_spm_config_low_power_mode(struct msm_spm_device *dev,
+		unsigned int mode, bool notify_rpm)
+{
+	return msm_spm_dev_set_low_power_mode(dev, mode, notify_rpm);
+}
+#ifdef CONFIG_MSM_L2_SPM
+
+/**
+ * msm_spm_apcs_set_phase(): Set number of SMPS phases.
+ * @cpu: cpu which is requesting the change in number of phases.
+ * @phase_cnt: Number of phases to be set active
+ */
+int msm_spm_apcs_set_phase(int cpu, unsigned int phase_cnt)
+{
+	struct msm_spm_device *dev = per_cpu(cpu_vctl_device, cpu);
+
+	if (!dev)
+		return -ENXIO;
+
+	return msm_spm_drv_set_pmic_data(&dev->reg_data,
+			MSM_SPM_PMIC_PHASE_PORT, phase_cnt);
+}
+EXPORT_SYMBOL(msm_spm_apcs_set_phase);
+
+/** msm_spm_enable_fts_lpm() : Enable FTS to switch to low power
+ *                             when the cores are in low power modes
+ * @cpu: cpu that is entering low power mode.
+ * @mode: The mode configuration for FTS
+ */
+int msm_spm_enable_fts_lpm(int cpu, uint32_t mode)
+{
+	struct msm_spm_device *dev = per_cpu(cpu_vctl_device, cpu);
+
+	if (!dev)
+		return -ENXIO;
+
+	return msm_spm_drv_set_pmic_data(&dev->reg_data,
+			MSM_SPM_PMIC_PFM_PORT, mode);
+}
+EXPORT_SYMBOL(msm_spm_enable_fts_lpm);
+
+#endif
+
+static int get_cpu_id(struct device_node *node)
+{
+	struct device_node *cpu_node;
+	u32 cpu;
+	int ret = -EINVAL;
+	char *key = "qcom,cpu";
+
+	cpu_node = of_parse_phandle(node, key, 0);
+	if (cpu_node) {
+		for_each_possible_cpu(cpu) {
+			if (of_get_cpu_node(cpu, NULL) == cpu_node)
+				return cpu;
+		}
+	}
+	return ret;
+}
+
+static struct msm_spm_device *msm_spm_get_device(struct platform_device *pdev)
+{
+	struct msm_spm_device *dev = NULL;
+	const char *val = NULL;
+	char *key = "qcom,name";
+	int cpu = get_cpu_id(pdev->dev.of_node);
+
+	if ((cpu >= 0) && cpu < num_possible_cpus())
+		dev = &per_cpu(msm_cpu_spm_device, cpu);
+	else if ((cpu == 0xffff) || (cpu < 0))
+		dev = devm_kzalloc(&pdev->dev, sizeof(struct msm_spm_device),
+					GFP_KERNEL);
+
+	if (!dev)
+		return NULL;
+
+	if (of_property_read_string(pdev->dev.of_node, key, &val)) {
+		pr_err("%s(): Cannot find a required node key:%s\n",
+				__func__, key);
+		return NULL;
+	}
+	dev->name = val;
+	list_add(&dev->list, &spm_list);
+
+	return dev;
+}
+
+static void get_cpumask(struct device_node *node, struct cpumask *mask)
+{
+	unsigned long vctl_mask = 0;
+	unsigned c = 0;
+	int idx = 0;
+	struct device_node *cpu_node = NULL;
+	int ret = 0;
+	char *key = "qcom,cpu-vctl-list";
+	bool found = false;
+
+	cpu_node = of_parse_phandle(node, key, idx++);
+	while (cpu_node) {
+		found = true;
+		for_each_possible_cpu(c) {
+			if (of_get_cpu_node(c, NULL) == cpu_node)
+				cpumask_set_cpu(c, mask);
+		}
+		cpu_node = of_parse_phandle(node, key, idx++);
+	};
+
+	if (found)
+		return;
+
+	key = "qcom,cpu-vctl-mask";
+	ret = of_property_read_u32(node, key, (u32 *) &vctl_mask);
+	if (!ret) {
+		for_each_set_bit(c, &vctl_mask, num_possible_cpus()) {
+			cpumask_set_cpu(c, mask);
+		}
+	}
+}
+
+static int msm_spm_dev_probe(struct platform_device *pdev)
+{
+	int ret = 0;
+	int cpu = 0;
+	int i = 0;
+	struct device_node *node = pdev->dev.of_node;
+	struct msm_spm_platform_data spm_data;
+	char *key = NULL;
+	uint32_t val = 0;
+	struct msm_spm_seq_entry modes[MSM_SPM_MODE_NR];
+	int len = 0;
+	struct msm_spm_device *dev = NULL;
+	struct resource *res = NULL;
+	uint32_t mode_count = 0;
+
+	struct spm_of {
+		char *key;
+		uint32_t id;
+	};
+
+	struct spm_of spm_of_data[] = {
+		{"qcom,saw2-cfg", MSM_SPM_REG_SAW2_CFG},
+		{"qcom,saw2-spm-dly", MSM_SPM_REG_SAW2_SPM_DLY},
+		{"qcom,saw2-spm-ctl", MSM_SPM_REG_SAW2_SPM_CTL},
+		{"qcom,saw2-pmic-data0", MSM_SPM_REG_SAW2_PMIC_DATA_0},
+		{"qcom,saw2-pmic-data1", MSM_SPM_REG_SAW2_PMIC_DATA_1},
+		{"qcom,saw2-pmic-data2", MSM_SPM_REG_SAW2_PMIC_DATA_2},
+		{"qcom,saw2-pmic-data3", MSM_SPM_REG_SAW2_PMIC_DATA_3},
+		{"qcom,saw2-pmic-data4", MSM_SPM_REG_SAW2_PMIC_DATA_4},
+		{"qcom,saw2-pmic-data5", MSM_SPM_REG_SAW2_PMIC_DATA_5},
+		{"qcom,saw2-pmic-data6", MSM_SPM_REG_SAW2_PMIC_DATA_6},
+		{"qcom,saw2-pmic-data7", MSM_SPM_REG_SAW2_PMIC_DATA_7},
+	};
+
+	struct mode_of {
+		char *key;
+		uint32_t id;
+		uint32_t notify_rpm;
+	};
+
+	struct mode_of mode_of_data[] = {
+		{"qcom,saw2-spm-cmd-wfi", MSM_SPM_MODE_CLOCK_GATING, 0},
+		{"qcom,saw2-spm-cmd-ret", MSM_SPM_MODE_RETENTION, 0},
+		{"qcom,saw2-spm-cmd-gdhs", MSM_SPM_MODE_GDHS, 1},
+		{"qcom,saw2-spm-cmd-spc", MSM_SPM_MODE_POWER_COLLAPSE, 0},
+		{"qcom,saw2-spm-cmd-pc", MSM_SPM_MODE_POWER_COLLAPSE, 1},
+	};
+
+	dev = msm_spm_get_device(pdev);
+	if (!dev) {
+		ret = -ENOMEM;
+		goto fail;
+	}
+	get_cpumask(node, &dev->mask);
+
+	memset(&spm_data, 0, sizeof(struct msm_spm_platform_data));
+	memset(&modes, 0,
+		(MSM_SPM_MODE_NR - 2) * sizeof(struct msm_spm_seq_entry));
+
+	if (of_device_is_compatible(node, "qcom,spm-v2.1")) {
+		spm_data.major = 2;
+		spm_data.minor = 1;
+	} else if (of_device_is_compatible(node, "qcom,spm-v3.0")) {
+		spm_data.major = 3;
+		spm_data.minor = 0;
+	}
+
+	key = "qcom,vctl-timeout-us";
+	ret = of_property_read_u32(node, key, &val);
+	if (!ret)
+		spm_data.vctl_timeout_us = val;
+
+	/* SAW start address */
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!res) {
+		ret = -EFAULT;
+		goto fail;
+	}
+
+	spm_data.reg_base_addr = devm_ioremap(&pdev->dev, res->start,
+					resource_size(res));
+	if (!spm_data.reg_base_addr) {
+		ret = -ENOMEM;
+		goto fail;
+	}
+
+	spm_data.vctl_port = -1;
+	spm_data.phase_port = -1;
+	spm_data.pfm_port = -1;
+
+	key = "qcom,vctl-port";
+	of_property_read_u32(node, key, &spm_data.vctl_port);
+
+	key = "qcom,phase-port";
+	of_property_read_u32(node, key, &spm_data.phase_port);
+
+	key = "qcom,pfm-port";
+	of_property_read_u32(node, key, &spm_data.pfm_port);
+
+	/* Q2S (QChannel-2-SPM) register */
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+	if (res) {
+		dev->q2s_reg = devm_ioremap(&pdev->dev, res->start,
+						resource_size(res));
+		if (!dev->q2s_reg) {
+			pr_err("%s(): Unable to iomap Q2S register\n",
+					__func__);
+			ret = -EADDRNOTAVAIL;
+			goto fail;
+		}
+	}
+	/*
+	 * At system boot, cpus and or clusters can remain in reset. CCI SPM
+	 * will not be triggered unless SPM_LEGACY_MODE bit is set for the
+	 * cluster in reset. Initialize q2s registers and set the
+	 * SPM_LEGACY_MODE bit.
+	 */
+	msm_spm_config_q2s(dev, MSM_SPM_MODE_POWER_COLLAPSE);
+
+	for (i = 0; i < ARRAY_SIZE(spm_of_data); i++) {
+		ret = of_property_read_u32(node, spm_of_data[i].key, &val);
+		if (ret)
+			continue;
+		spm_data.reg_init_values[spm_of_data[i].id] = val;
+	}
+
+	for (i = 0; i < ARRAY_SIZE(mode_of_data); i++) {
+		key = mode_of_data[i].key;
+		modes[mode_count].cmd =
+			(uint8_t *)of_get_property(node, key, &len);
+		if (!modes[mode_count].cmd)
+			continue;
+		modes[mode_count].mode = mode_of_data[i].id;
+		modes[mode_count].notify_rpm = mode_of_data[i].notify_rpm;
+		pr_debug("%s(): dev: %s cmd:%s, mode:%d rpm:%d\n", __func__,
+				dev->name, key, modes[mode_count].mode,
+				modes[mode_count].notify_rpm);
+		mode_count++;
+	}
+
+	spm_data.modes = modes;
+	spm_data.num_modes = mode_count;
+
+	ret = msm_spm_dev_init(dev, &spm_data);
+	if (ret)
+		goto fail;
+
+	platform_set_drvdata(pdev, dev);
+
+	for_each_cpu(cpu, &dev->mask)
+		per_cpu(cpu_vctl_device, cpu) = dev;
+
+	return ret;
+
+fail:
+	cpu = get_cpu_id(pdev->dev.of_node);
+	if (dev && (cpu >= num_possible_cpus() || (cpu < 0))) {
+		for_each_cpu(cpu, &dev->mask)
+			per_cpu(cpu_vctl_device, cpu) = ERR_PTR(ret);
+	}
+
+	pr_err("%s: CPU%d SPM device probe failed: %d\n", __func__, cpu, ret);
+
+	return ret;
+}
+
+static int msm_spm_dev_remove(struct platform_device *pdev)
+{
+	struct msm_spm_device *dev = platform_get_drvdata(pdev);
+
+	list_del(&dev->list);
+
+	return 0;
+}
+
+static struct of_device_id msm_spm_match_table[] = {
+	{.compatible = "qcom,spm-v2.1"},
+	{.compatible = "qcom,spm-v3.0"},
+	{},
+};
+
+static struct platform_driver msm_spm_device_driver = {
+	.probe = msm_spm_dev_probe,
+	.remove = msm_spm_dev_remove,
+	.driver = {
+		.name = "spm-v2",
+		.owner = THIS_MODULE,
+		.of_match_table = msm_spm_match_table,
+	},
+};
+
+/**
+ * msm_spm_device_init(): Device tree initialization function
+ */
+int __init msm_spm_device_init(void)
+{
+	static bool registered;
+
+	if (registered)
+		return 0;
+
+	registered = true;
+
+	return platform_driver_register(&msm_spm_device_driver);
+}
+device_initcall(msm_spm_device_init);
diff --git a/drivers/soc/qcom/spm.c b/drivers/soc/qcom/spm.c
new file mode 100644
index 0000000..7dbdb64
--- /dev/null
+++ b/drivers/soc/qcom/spm.c
@@ -0,0 +1,482 @@
+/* Copyright (c) 2011-2014, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/slab.h>
+
+#include "spm_driver.h"
+
+#define MSM_SPM_PMIC_STATE_IDLE  0
+
+enum {
+	MSM_SPM_DEBUG_SHADOW = 1U << 0,
+	MSM_SPM_DEBUG_VCTL = 1U << 1,
+};
+
+static int msm_spm_debug_mask;
+module_param_named(
+	debug_mask, msm_spm_debug_mask, int, S_IRUGO | S_IWUSR | S_IWGRP
+);
+
+struct saw2_data {
+	const char *ver_name;
+	uint32_t major;
+	uint32_t minor;
+	uint32_t *spm_reg_offset_ptr;
+};
+
+static uint32_t msm_spm_reg_offsets_saw2_v2_1[MSM_SPM_REG_NR] = {
+	[MSM_SPM_REG_SAW2_SECURE]		= 0x00,
+	[MSM_SPM_REG_SAW2_ID]			= 0x04,
+	[MSM_SPM_REG_SAW2_CFG]			= 0x08,
+	[MSM_SPM_REG_SAW2_SPM_STS]		= 0x0C,
+	[MSM_SPM_REG_SAW2_AVS_STS]		= 0x10,
+	[MSM_SPM_REG_SAW2_PMIC_STS]		= 0x14,
+	[MSM_SPM_REG_SAW2_RST]			= 0x18,
+	[MSM_SPM_REG_SAW2_VCTL]			= 0x1C,
+	[MSM_SPM_REG_SAW2_AVS_CTL]		= 0x20,
+	[MSM_SPM_REG_SAW2_AVS_LIMIT]		= 0x24,
+	[MSM_SPM_REG_SAW2_AVS_DLY]		= 0x28,
+	[MSM_SPM_REG_SAW2_AVS_HYSTERESIS]	= 0x2C,
+	[MSM_SPM_REG_SAW2_SPM_CTL]		= 0x30,
+	[MSM_SPM_REG_SAW2_SPM_DLY]		= 0x34,
+	[MSM_SPM_REG_SAW2_PMIC_DATA_0]		= 0x40,
+	[MSM_SPM_REG_SAW2_PMIC_DATA_1]		= 0x44,
+	[MSM_SPM_REG_SAW2_PMIC_DATA_2]		= 0x48,
+	[MSM_SPM_REG_SAW2_PMIC_DATA_3]		= 0x4C,
+	[MSM_SPM_REG_SAW2_PMIC_DATA_4]		= 0x50,
+	[MSM_SPM_REG_SAW2_PMIC_DATA_5]		= 0x54,
+	[MSM_SPM_REG_SAW2_PMIC_DATA_6]		= 0x58,
+	[MSM_SPM_REG_SAW2_PMIC_DATA_7]		= 0x5C,
+	[MSM_SPM_REG_SAW2_SEQ_ENTRY]		= 0x80,
+	[MSM_SPM_REG_SAW2_VERSION]		= 0xFD0,
+};
+
+static uint32_t msm_spm_reg_offsets_saw2_v3_0[MSM_SPM_REG_NR] = {
+	[MSM_SPM_REG_SAW2_SECURE]		= 0x00,
+	[MSM_SPM_REG_SAW2_ID]			= 0x04,
+	[MSM_SPM_REG_SAW2_CFG]			= 0x08,
+	[MSM_SPM_REG_SAW2_SPM_STS]		= 0x0C,
+	[MSM_SPM_REG_SAW2_AVS_STS]		= 0x10,
+	[MSM_SPM_REG_SAW2_PMIC_STS]		= 0x14,
+	[MSM_SPM_REG_SAW2_RST]			= 0x18,
+	[MSM_SPM_REG_SAW2_VCTL]			= 0x1C,
+	[MSM_SPM_REG_SAW2_AVS_CTL]		= 0x20,
+	[MSM_SPM_REG_SAW2_AVS_LIMIT]		= 0x24,
+	[MSM_SPM_REG_SAW2_AVS_DLY]		= 0x28,
+	[MSM_SPM_REG_SAW2_AVS_HYSTERESIS]	= 0x2C,
+	[MSM_SPM_REG_SAW2_SPM_CTL]		= 0x30,
+	[MSM_SPM_REG_SAW2_SPM_DLY]		= 0x34,
+	[MSM_SPM_REG_SAW2_STS2]			= 0x38,
+	[MSM_SPM_REG_SAW2_PMIC_DATA_0]		= 0x40,
+	[MSM_SPM_REG_SAW2_PMIC_DATA_1]		= 0x44,
+	[MSM_SPM_REG_SAW2_PMIC_DATA_2]		= 0x48,
+	[MSM_SPM_REG_SAW2_PMIC_DATA_3]		= 0x4C,
+	[MSM_SPM_REG_SAW2_PMIC_DATA_4]		= 0x50,
+	[MSM_SPM_REG_SAW2_PMIC_DATA_5]		= 0x54,
+	[MSM_SPM_REG_SAW2_PMIC_DATA_6]		= 0x58,
+	[MSM_SPM_REG_SAW2_PMIC_DATA_7]		= 0x5C,
+	[MSM_SPM_REG_SAW2_SEQ_ENTRY]		= 0x400,
+	[MSM_SPM_REG_SAW2_VERSION]		= 0xFD0,
+};
+
+static struct saw2_data saw2_info[] = {
+	[0] = {
+		"SAW2_v2.1",
+		2,
+		1,
+		msm_spm_reg_offsets_saw2_v2_1,
+	},
+	[1] = {
+		"SAW2_v3.0",
+		3,
+		0,
+		msm_spm_reg_offsets_saw2_v3_0,
+	},
+};
+
+static uint32_t num_pmic_data;
+
+static inline uint32_t msm_spm_drv_get_num_spm_entry(
+		struct msm_spm_driver_data *dev)
+{
+	return 32;
+}
+
+static void msm_spm_drv_flush_shadow(struct msm_spm_driver_data *dev,
+		unsigned int reg_index)
+{
+	__raw_writel(dev->reg_shadow[reg_index],
+		dev->reg_base_addr + dev->reg_offsets[reg_index]);
+}
+
+static void msm_spm_drv_load_shadow(struct msm_spm_driver_data *dev,
+		unsigned int reg_index)
+{
+	dev->reg_shadow[reg_index] =
+		__raw_readl(dev->reg_base_addr +
+				dev->reg_offsets[reg_index]);
+}
+
+static inline void msm_spm_drv_set_start_addr(
+		struct msm_spm_driver_data *dev, uint32_t addr, bool pc_mode)
+{
+	addr &= 0x7F;
+	addr <<= 4;
+	dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] &= 0xFFFFF80F;
+	dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] |= addr;
+
+	if (dev->major != 0x3)
+		return;
+
+	dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] &= 0xFFFEFFFF;
+	if (pc_mode)
+		dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] |= 0x00010000;
+}
+
+static inline bool msm_spm_pmic_arb_present(struct msm_spm_driver_data *dev)
+{
+	msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW2_ID);
+	return (dev->reg_shadow[MSM_SPM_REG_SAW2_ID] >> 2) & 0x1;
+}
+
+static inline void msm_spm_drv_set_vctl2(struct msm_spm_driver_data *dev,
+		uint32_t vlevel)
+{
+	unsigned int pmic_data = 0;
+
+	/**
+	 * VCTL_PORT has to be 0, for PMIC_STS register to be updated.
+	 * Ensure that vctl_port is always set to 0.
+	 */
+	WARN_ON(dev->vctl_port);
+
+	pmic_data |= vlevel;
+	pmic_data |= (dev->vctl_port & 0x7) << 16;
+
+	dev->reg_shadow[MSM_SPM_REG_SAW2_VCTL] &= ~0x700FF;
+	dev->reg_shadow[MSM_SPM_REG_SAW2_VCTL] |= pmic_data;
+
+	dev->reg_shadow[MSM_SPM_REG_SAW2_PMIC_DATA_3] &= ~0x700FF;
+	dev->reg_shadow[MSM_SPM_REG_SAW2_PMIC_DATA_3] |= pmic_data;
+
+	msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW2_VCTL);
+	msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW2_PMIC_DATA_3);
+}
+
+static inline uint32_t msm_spm_drv_get_num_pmic_data(
+		struct msm_spm_driver_data *dev)
+{
+	msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW2_ID);
+	mb();
+	return (dev->reg_shadow[MSM_SPM_REG_SAW2_ID] >> 4) & 0x7;
+}
+
+static inline uint32_t msm_spm_drv_get_sts_pmic_state(
+		struct msm_spm_driver_data *dev)
+{
+	msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW2_PMIC_STS);
+	return (dev->reg_shadow[MSM_SPM_REG_SAW2_PMIC_STS] >> 16) &
+				0x03;
+}
+
+uint32_t msm_spm_drv_get_sts_curr_pmic_data(
+		struct msm_spm_driver_data *dev)
+{
+	msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW2_PMIC_STS);
+	return dev->reg_shadow[MSM_SPM_REG_SAW2_PMIC_STS] & 0xFF;
+}
+
+inline int msm_spm_drv_set_spm_enable(
+		struct msm_spm_driver_data *dev, bool enable)
+{
+	uint32_t value = enable ? 0x01 : 0x00;
+
+	if (!dev)
+		return -EINVAL;
+
+	if ((dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] & 0x01) ^ value) {
+
+		dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] &= ~0x1;
+		dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] |= value;
+
+		msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW2_SPM_CTL);
+		wmb();
+	}
+	return 0;
+}
+void msm_spm_drv_flush_seq_entry(struct msm_spm_driver_data *dev)
+{
+	int i;
+	int num_spm_entry = msm_spm_drv_get_num_spm_entry(dev);
+
+	if (!dev) {
+		__WARN();
+		return;
+	}
+
+	for (i = 0; i < num_spm_entry; i++) {
+		__raw_writel(dev->reg_seq_entry_shadow[i],
+			dev->reg_base_addr
+			+ dev->reg_offsets[MSM_SPM_REG_SAW2_SEQ_ENTRY]
+			+ 4 * i);
+	}
+	mb();
+}
+
+void dump_regs(struct msm_spm_driver_data *dev, int cpu)
+{
+	msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW2_SPM_STS);
+	mb();
+	pr_err("CPU%d: spm register MSM_SPM_REG_SAW2_SPM_STS: 0x%x\n", cpu,
+			dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_STS]);
+	msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW2_SPM_CTL);
+	mb();
+	pr_err("CPU%d: spm register MSM_SPM_REG_SAW2_SPM_CTL: 0x%x\n", cpu,
+			dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL]);
+}
+
+int msm_spm_drv_write_seq_data(struct msm_spm_driver_data *dev,
+		uint8_t *cmd, uint32_t *offset)
+{
+	uint32_t cmd_w;
+	uint32_t offset_w = *offset / 4;
+	uint8_t last_cmd;
+
+	if (!cmd)
+		return -EINVAL;
+
+	while (1) {
+		int i;
+
+		cmd_w = 0;
+		last_cmd = 0;
+		cmd_w = dev->reg_seq_entry_shadow[offset_w];
+
+		for (i = (*offset % 4); i < 4; i++) {
+			last_cmd = *(cmd++);
+			cmd_w |=  last_cmd << (i * 8);
+			(*offset)++;
+			if (last_cmd == 0x0f)
+				break;
+		}
+
+		dev->reg_seq_entry_shadow[offset_w++] = cmd_w;
+		if (last_cmd == 0x0f)
+			break;
+	}
+
+	return 0;
+}
+
+int msm_spm_drv_set_low_power_mode(struct msm_spm_driver_data *dev,
+		uint32_t addr, bool pc_mode)
+{
+
+	if (!dev)
+		return -EINVAL;
+
+	msm_spm_drv_set_start_addr(dev, addr, pc_mode);
+
+	msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW2_SPM_CTL);
+	wmb();
+
+	if (msm_spm_debug_mask & MSM_SPM_DEBUG_SHADOW) {
+		int i;
+
+		for (i = 0; i < MSM_SPM_REG_NR; i++)
+			pr_info("%s: reg %02x = 0x%08x\n", __func__,
+				dev->reg_offsets[i], dev->reg_shadow[i]);
+	}
+	msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW2_SPM_STS);
+
+	return 0;
+}
+
+int msm_spm_drv_set_vdd(struct msm_spm_driver_data *dev, unsigned int vlevel)
+{
+	uint32_t timeout_us, new_level;
+
+	if (!dev)
+		return -EINVAL;
+
+	if (!msm_spm_pmic_arb_present(dev))
+		return -ENOSYS;
+
+	if (msm_spm_debug_mask & MSM_SPM_DEBUG_VCTL)
+		pr_info("%s: requesting vlevel %#x\n", __func__, vlevel);
+
+	/* Kick the state machine back to idle */
+	dev->reg_shadow[MSM_SPM_REG_SAW2_RST] = 1;
+	msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW2_RST);
+
+	msm_spm_drv_set_vctl2(dev, vlevel);
+
+	timeout_us = dev->vctl_timeout_us;
+	/* Confirm the voltage we set was what hardware sent */
+	do {
+		new_level = msm_spm_drv_get_sts_curr_pmic_data(dev);
+		if (new_level == vlevel)
+			break;
+		udelay(1);
+	} while (--timeout_us);
+	if (!timeout_us) {
+		pr_info("Wrong level %#x\n", new_level);
+		goto set_vdd_bail;
+	}
+
+	if (msm_spm_debug_mask & MSM_SPM_DEBUG_VCTL)
+		pr_info("%s: done, remaining timeout %u us\n",
+			__func__, timeout_us);
+
+	return 0;
+
+set_vdd_bail:
+	pr_err("%s: failed %#x, remaining timeout %uus, vlevel %#x\n",
+		__func__, vlevel, timeout_us, new_level);
+	return -EIO;
+}
+
+static int msm_spm_drv_get_pmic_port(struct msm_spm_driver_data *dev,
+		enum msm_spm_pmic_port port)
+{
+	int index = -1;
+
+	switch (port) {
+	case MSM_SPM_PMIC_VCTL_PORT:
+		index = dev->vctl_port;
+		break;
+	case MSM_SPM_PMIC_PHASE_PORT:
+		index = dev->phase_port;
+		break;
+	case MSM_SPM_PMIC_PFM_PORT:
+		index = dev->pfm_port;
+		break;
+	default:
+		break;
+	}
+
+	return index;
+}
+
+int msm_spm_drv_set_pmic_data(struct msm_spm_driver_data *dev,
+		enum msm_spm_pmic_port port, unsigned int data)
+{
+	unsigned int pmic_data = 0;
+	unsigned int timeout_us = 0;
+	int index = 0;
+
+	if (!msm_spm_pmic_arb_present(dev))
+		return -ENOSYS;
+
+	index = msm_spm_drv_get_pmic_port(dev, port);
+	if (index < 0)
+		return -ENODEV;
+
+	pmic_data |= data & 0xFF;
+	pmic_data |= (index & 0x7) << 16;
+
+	dev->reg_shadow[MSM_SPM_REG_SAW2_VCTL] &= ~0x700FF;
+	dev->reg_shadow[MSM_SPM_REG_SAW2_VCTL] |= pmic_data;
+	msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW2_VCTL);
+	mb();
+
+	timeout_us = dev->vctl_timeout_us;
+	/**
+	 * Confirm the pmic data set was what hardware sent by
+	 * checking the PMIC FSM state.
+	 * We cannot use the sts_pmic_data and check it against
+	 * the value like we do fot set_vdd, since the PMIC_STS
+	 * is only updated for SAW_VCTL sent with port index 0.
+	 */
+	do {
+		if (msm_spm_drv_get_sts_pmic_state(dev) ==
+				MSM_SPM_PMIC_STATE_IDLE)
+			break;
+		udelay(1);
+	} while (--timeout_us);
+
+	if (!timeout_us) {
+		pr_err("%s: failed, remaining timeout %u us, data %d\n",
+				__func__, timeout_us, data);
+		return -EIO;
+	}
+
+	return 0;
+}
+
+void msm_spm_drv_reinit(struct msm_spm_driver_data *dev)
+{
+	int i;
+
+	msm_spm_drv_flush_seq_entry(dev);
+	for (i = 0; i < MSM_SPM_REG_SAW2_PMIC_DATA_0 + num_pmic_data; i++)
+		msm_spm_drv_flush_shadow(dev, i);
+
+	mb();
+
+	for (i = MSM_SPM_REG_NR_INITIALIZE + 1; i < MSM_SPM_REG_NR; i++)
+		msm_spm_drv_load_shadow(dev, i);
+}
+
+int msm_spm_drv_init(struct msm_spm_driver_data *dev,
+		struct msm_spm_platform_data *data)
+{
+	int i;
+	int num_spm_entry;
+	bool found = false;
+
+	BUG_ON(!dev || !data);
+
+	dev->vctl_port = data->vctl_port;
+	dev->phase_port = data->phase_port;
+	dev->pfm_port = data->pfm_port;
+	dev->reg_base_addr = data->reg_base_addr;
+	memcpy(dev->reg_shadow, data->reg_init_values,
+			sizeof(data->reg_init_values));
+
+	dev->vctl_timeout_us = data->vctl_timeout_us;
+
+	for (i = 0; i < ARRAY_SIZE(saw2_info); i++)
+		if (dev->major == saw2_info[i].major &&
+			dev->minor == saw2_info[i].minor) {
+			pr_debug("%s: Version found\n",
+					saw2_info[i].ver_name);
+			dev->reg_offsets = saw2_info[i].spm_reg_offset_ptr;
+			found = true;
+			break;
+		}
+
+	if (!found) {
+		pr_err("%s: No SAW2 version found\n", __func__);
+		BUG_ON(!found);
+	}
+
+	if (!num_pmic_data)
+		num_pmic_data = msm_spm_drv_get_num_pmic_data(dev);
+
+	num_spm_entry = msm_spm_drv_get_num_spm_entry(dev);
+
+	dev->reg_seq_entry_shadow =
+		kzalloc(sizeof(*dev->reg_seq_entry_shadow) * num_spm_entry,
+				GFP_KERNEL);
+
+	if (!dev->reg_seq_entry_shadow)
+		return -ENOMEM;
+
+	return 0;
+}
diff --git a/drivers/soc/qcom/spm_driver.h b/drivers/soc/qcom/spm_driver.h
new file mode 100644
index 0000000..b306520
--- /dev/null
+++ b/drivers/soc/qcom/spm_driver.h
@@ -0,0 +1,116 @@
+/* Copyright (c) 2011-2014, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+#ifndef __QCOM_SPM_DRIVER_H
+#define __QCOM_SPM_DRIVER_H
+
+#include <soc/qcom/spm.h>
+
+enum {
+	MSM_SPM_REG_SAW2_CFG,
+	MSM_SPM_REG_SAW2_AVS_CTL,
+	MSM_SPM_REG_SAW2_AVS_HYSTERESIS,
+	MSM_SPM_REG_SAW2_SPM_CTL,
+	MSM_SPM_REG_SAW2_PMIC_DLY,
+	MSM_SPM_REG_SAW2_AVS_LIMIT,
+	MSM_SPM_REG_SAW2_AVS_DLY,
+	MSM_SPM_REG_SAW2_SPM_DLY,
+	MSM_SPM_REG_SAW2_PMIC_DATA_0,
+	MSM_SPM_REG_SAW2_PMIC_DATA_1,
+	MSM_SPM_REG_SAW2_PMIC_DATA_2,
+	MSM_SPM_REG_SAW2_PMIC_DATA_3,
+	MSM_SPM_REG_SAW2_PMIC_DATA_4,
+	MSM_SPM_REG_SAW2_PMIC_DATA_5,
+	MSM_SPM_REG_SAW2_PMIC_DATA_6,
+	MSM_SPM_REG_SAW2_PMIC_DATA_7,
+	MSM_SPM_REG_SAW2_RST,
+
+	MSM_SPM_REG_NR_INITIALIZE = MSM_SPM_REG_SAW2_RST,
+
+	MSM_SPM_REG_SAW2_ID,
+	MSM_SPM_REG_SAW2_SECURE,
+	MSM_SPM_REG_SAW2_STS0,
+	MSM_SPM_REG_SAW2_STS1,
+	MSM_SPM_REG_SAW2_STS2,
+	MSM_SPM_REG_SAW2_VCTL,
+	MSM_SPM_REG_SAW2_SEQ_ENTRY,
+	MSM_SPM_REG_SAW2_SPM_STS,
+	MSM_SPM_REG_SAW2_AVS_STS,
+	MSM_SPM_REG_SAW2_PMIC_STS,
+	MSM_SPM_REG_SAW2_VERSION,
+
+	MSM_SPM_REG_NR,
+};
+
+struct msm_spm_seq_entry {
+	uint32_t mode;
+	uint8_t *cmd;
+	bool  notify_rpm;
+};
+
+struct msm_spm_platform_data {
+	void __iomem *reg_base_addr;
+	uint32_t reg_init_values[MSM_SPM_REG_NR_INITIALIZE];
+
+	uint32_t major;
+	uint32_t minor;
+	uint32_t vctl_port;
+	uint32_t phase_port;
+	uint32_t pfm_port;
+
+	uint8_t awake_vlevel;
+	uint32_t vctl_timeout_us;
+
+	uint32_t num_modes;
+	struct msm_spm_seq_entry *modes;
+};
+
+enum msm_spm_pmic_port {
+	MSM_SPM_PMIC_VCTL_PORT,
+	MSM_SPM_PMIC_PHASE_PORT,
+	MSM_SPM_PMIC_PFM_PORT,
+};
+
+struct msm_spm_driver_data {
+	uint32_t major;
+	uint32_t minor;
+	uint32_t vctl_port;
+	uint32_t phase_port;
+	uint32_t pfm_port;
+	void __iomem *reg_base_addr;
+	uint32_t vctl_timeout_us;
+	uint32_t reg_shadow[MSM_SPM_REG_NR];
+	uint32_t *reg_seq_entry_shadow;
+	uint32_t *reg_offsets;
+};
+
+int msm_spm_drv_init(struct msm_spm_driver_data *dev,
+		struct msm_spm_platform_data *data);
+void msm_spm_drv_reinit(struct msm_spm_driver_data *dev);
+int msm_spm_drv_set_low_power_mode(struct msm_spm_driver_data *dev,
+		uint32_t addr, bool pc_mode);
+int msm_spm_drv_set_vdd(struct msm_spm_driver_data *dev,
+		unsigned int vlevel);
+void dump_regs(struct msm_spm_driver_data *dev, int cpu);
+uint32_t msm_spm_drv_get_sts_curr_pmic_data(
+		struct msm_spm_driver_data *dev);
+int msm_spm_drv_write_seq_data(struct msm_spm_driver_data *dev,
+		uint8_t *cmd, uint32_t *offset);
+void msm_spm_drv_flush_seq_entry(struct msm_spm_driver_data *dev);
+int msm_spm_drv_set_spm_enable(struct msm_spm_driver_data *dev,
+		bool enable);
+int msm_spm_drv_set_pmic_data(struct msm_spm_driver_data *dev,
+		enum msm_spm_pmic_port port, unsigned int data);
+
+void msm_spm_reinit(void);
+int msm_spm_init(struct msm_spm_platform_data *data, int nr_devs);
+
+#endif /* __QCOM_SPM_DRIVER_H */
diff --git a/include/soc/qcom/spm.h b/include/soc/qcom/spm.h
new file mode 100644
index 0000000..f39e0c4
--- /dev/null
+++ b/include/soc/qcom/spm.h
@@ -0,0 +1,70 @@
+/* Copyright (c) 2010-2014, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef __QCOM_SPM_H
+#define __QCOM_SPM_H
+
+enum {
+	MSM_SPM_MODE_DISABLED,
+	MSM_SPM_MODE_CLOCK_GATING,
+	MSM_SPM_MODE_RETENTION,
+	MSM_SPM_MODE_GDHS,
+	MSM_SPM_MODE_POWER_COLLAPSE,
+	MSM_SPM_MODE_NR
+};
+
+struct msm_spm_device;
+
+#if defined(CONFIG_QCOM_PM)
+int msm_spm_set_low_power_mode(unsigned int mode, bool notify_rpm);
+int msm_spm_probe_done(void);
+int msm_spm_set_vdd(unsigned int cpu, unsigned int vlevel);
+unsigned int msm_spm_get_vdd(unsigned int cpu);
+int msm_spm_turn_on_cpu_rail(void __iomem *base, unsigned int val, int cpu);
+struct msm_spm_device *msm_spm_get_device_by_name(const char *name);
+int msm_spm_config_low_power_mode(struct msm_spm_device *dev,
+		unsigned int mode, bool notify_rpm);
+int msm_spm_device_init(void);
+bool msm_spm_is_mode_avail(unsigned int mode);
+void msm_spm_dump_regs(unsigned int cpu);
+int msm_spm_apcs_set_phase(int cpu, unsigned int phase_cnt);
+int msm_spm_enable_fts_lpm(int cpu, uint32_t mode);
+#else /* defined(CONFIG_QCOM_PM) */
+static inline int msm_spm_set_low_power_mode(unsigned int mode, bool notify_rpm)
+{ return -ENOSYS; }
+static inline int msm_spm_probe_done(void)
+{ return -ENOSYS; }
+static inline int msm_spm_set_vdd(unsigned int cpu, unsigned int vlevel)
+{ return -ENOSYS; }
+static inline unsigned int msm_spm_get_vdd(unsigned int cpu)
+{ return 0; }
+static inline int msm_spm_turn_on_cpu_rail(void __iomem *base,
+		unsigned int val, int cpu)
+{ return -ENOSYS; }
+static inline int msm_spm_device_init(void)
+{ return -ENOSYS; }
+static void msm_spm_dump_regs(unsigned int cpu) {}
+static inline int msm_spm_config_low_power_mode(struct msm_spm_device *dev,
+		unsigned int mode, bool notify_rpm)
+{ return -ENODEV; }
+static inline struct msm_spm_device *msm_spm_get_device_by_name(
+		const char *name)
+{ return NULL; }
+static inline bool msm_spm_is_mode_avail(unsigned int mode)
+{ return false; }
+static inline int msm_spm_apcs_set_phase(int cpu, unsigned int phase_cnt)
+{ return -ENOSYS; }
+static inline int msm_spm_enable_fts_lpm(int cpu, uint32_t mode)
+{ return -ENOSYS; }
+#endif  /* defined (CONFIG_QCOM_PM) */
+
+#endif  /* __QCOM_SPM_H */
-- 
1.9.1

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

* [PATCH v2 04/10] soc: qcom: Add QCOM Power management config
  2014-08-12 19:43 [PATCH v2 00/10] QCOM 8074 cpuidle driver Lina Iyer
                   ` (2 preceding siblings ...)
  2014-08-12 19:43 ` [PATCH v2 03/10] qcom: spm: Add Subsystem Power Manager (SPM) driver for QCOM chipsets Lina Iyer
@ 2014-08-12 19:43 ` Lina Iyer
  2014-08-13  9:36   ` Daniel Lezcano
  2014-08-12 19:43 ` [PATCH v2 05/10] arm: qcom-msm8974: Add CPU phandles to CPU definitions Lina Iyer
                   ` (6 subsequent siblings)
  10 siblings, 1 reply; 46+ messages in thread
From: Lina Iyer @ 2014-08-12 19:43 UTC (permalink / raw)
  To: daniel.lezcano, khilman, amit.kucheria, sboyd, davidb, galak,
	linux-arm-msm
  Cc: msivasub, Lina Iyer

Allow power management drivers for QCOM chipsets.

Signed-off-by: Lina Iyer <lina.iyer@linaro.org>
---
 drivers/soc/qcom/Kconfig | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig
index 7dcd554..1569410 100644
--- a/drivers/soc/qcom/Kconfig
+++ b/drivers/soc/qcom/Kconfig
@@ -11,3 +11,11 @@ config QCOM_GSBI
 
 config QCOM_SCM
 	bool
+
+config QCOM_PM
+	tristate "Qualcomm Power Management"
+	depends on PM && ARCH_QCOM && OF
+	help
+	  QCOM Platform specific power driver to manage cores and L2 low power
+	  modes. It interface with various system drivers to put the cores in
+	  low power modes.
-- 
1.9.1

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

* [PATCH v2 05/10] arm: qcom-msm8974: Add CPU phandles to CPU definitions
  2014-08-12 19:43 [PATCH v2 00/10] QCOM 8074 cpuidle driver Lina Iyer
                   ` (3 preceding siblings ...)
  2014-08-12 19:43 ` [PATCH v2 04/10] soc: qcom: Add QCOM Power management config Lina Iyer
@ 2014-08-12 19:43 ` Lina Iyer
  2014-08-12 21:09   ` Kumar Gala
  2014-08-14 10:04   ` Pramod Gurav
  2014-08-12 19:43 ` [PATCH v2 06/10] arm: dts: qcom: Add SPM device bindings for 8974 Lina Iyer
                   ` (5 subsequent siblings)
  10 siblings, 2 replies; 46+ messages in thread
From: Lina Iyer @ 2014-08-12 19:43 UTC (permalink / raw)
  To: daniel.lezcano, khilman, amit.kucheria, sboyd, davidb, galak,
	linux-arm-msm
  Cc: msivasub, Lina Iyer

Add CPU phandle labels for all Krait CPUS.

Signed-off-by: Lina Iyer <lina.iyer@linaro.org>
---
 arch/arm/boot/dts/qcom-msm8974.dtsi | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/arch/arm/boot/dts/qcom-msm8974.dtsi b/arch/arm/boot/dts/qcom-msm8974.dtsi
index 69dca2a..15a75e4 100644
--- a/arch/arm/boot/dts/qcom-msm8974.dtsi
+++ b/arch/arm/boot/dts/qcom-msm8974.dtsi
@@ -14,7 +14,7 @@
 		#size-cells = <0>;
 		interrupts = <1 9 0xf04>;
 
-		cpu@0 {
+		CPU0: cpu@0 {
 			compatible = "qcom,krait";
 			enable-method = "qcom,kpss-acc-v2";
 			device_type = "cpu";
@@ -23,7 +23,7 @@
 			qcom,acc = <&acc0>;
 		};
 
-		cpu@1 {
+		CPU1: cpu@1 {
 			compatible = "qcom,krait";
 			enable-method = "qcom,kpss-acc-v2";
 			device_type = "cpu";
@@ -32,7 +32,7 @@
 			qcom,acc = <&acc1>;
 		};
 
-		cpu@2 {
+		CPU2: cpu@2 {
 			compatible = "qcom,krait";
 			enable-method = "qcom,kpss-acc-v2";
 			device_type = "cpu";
@@ -41,7 +41,7 @@
 			qcom,acc = <&acc2>;
 		};
 
-		cpu@3 {
+		CPU3: cpu@3 {
 			compatible = "qcom,krait";
 			enable-method = "qcom,kpss-acc-v2";
 			device_type = "cpu";
-- 
1.9.1

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

* [PATCH v2 06/10] arm: dts: qcom: Add SPM device bindings for 8974
  2014-08-12 19:43 [PATCH v2 00/10] QCOM 8074 cpuidle driver Lina Iyer
                   ` (4 preceding siblings ...)
  2014-08-12 19:43 ` [PATCH v2 05/10] arm: qcom-msm8974: Add CPU phandles to CPU definitions Lina Iyer
@ 2014-08-12 19:43 ` Lina Iyer
  2014-08-12 21:10   ` Kumar Gala
  2014-08-13  7:39   ` Ivan T. Ivanov
  2014-08-12 19:43 ` [PATCH v2 07/10] qcom: msm-pm: Add cpu low power mode functions Lina Iyer
                   ` (4 subsequent siblings)
  10 siblings, 2 replies; 46+ messages in thread
From: Lina Iyer @ 2014-08-12 19:43 UTC (permalink / raw)
  To: daniel.lezcano, khilman, amit.kucheria, sboyd, davidb, galak,
	linux-arm-msm
  Cc: msivasub, Lina Iyer, Praveen Chidambaram

Add SPM device bindings for QCOM 8974 based cpus. SPM is the sub-system
power manager and controls the logic around the cores (cpu and L2).

Each core has an instance of SPM and controls only that core. Each cpu
SPM is configured to support WFI and SPC (standalone-power collapse) and
L2 can do retention (clock-gating).

Signed-off-by: Praveen Chidambaram <pchidamb@codeaurora.org>
Signed-off-by: Lina Iyer <lina.iyer@linaro.org>
---
 arch/arm/boot/dts/qcom-msm8974-pm.dtsi | 91 ++++++++++++++++++++++++++++++++++
 arch/arm/boot/dts/qcom-msm8974.dtsi    |  2 +
 2 files changed, 93 insertions(+)
 create mode 100644 arch/arm/boot/dts/qcom-msm8974-pm.dtsi

diff --git a/arch/arm/boot/dts/qcom-msm8974-pm.dtsi b/arch/arm/boot/dts/qcom-msm8974-pm.dtsi
new file mode 100644
index 0000000..d7d81ca
--- /dev/null
+++ b/arch/arm/boot/dts/qcom-msm8974-pm.dtsi
@@ -0,0 +1,91 @@
+/* Copyright (c) 2013-2014, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+&soc {
+	qcom,spm@f9089000 {
+		compatible = "qcom,spm-v2.1";
+		#address-cells = <1>;
+		#size-cells = <1>;
+		reg = <0xf9089000 0x1000>;
+		qcom,name = "core0";
+		qcom,cpu = <&CPU0>;
+		qcom,saw2-cfg = <0x01>;
+		qcom,saw2-spm-dly= <0x3C102800>;
+		qcom,saw2-spm-ctl = <0x1>;
+		qcom,saw2-spm-cmd-wfi = [03 0b 0f];
+		qcom,saw2-spm-cmd-spc = [00 20 80 10 E8 5B 03 3B E8 5B 82 10 0B
+			30 06 26 30 0F];
+	};
+
+	qcom,spm@f9099000 {
+		compatible = "qcom,spm-v2.1";
+		#address-cells = <1>;
+		#size-cells = <1>;
+		reg = <0xf9099000 0x1000>;
+		qcom,name = "core1";
+		qcom,cpu = <&CPU1>;
+		qcom,saw2-cfg = <0x01>;
+		qcom,saw2-spm-dly= <0x3C102800>;
+		qcom,saw2-spm-ctl = <0x1>;
+		qcom,saw2-spm-cmd-wfi = [03 0b 0f];
+		qcom,saw2-spm-cmd-spc = [00 20 80 10 E8 5B 03 3B E8 5B 82 10 0B
+			30 06 26 30 0F];
+	};
+
+	qcom,spm@f90a9000 {
+		compatible = "qcom,spm-v2.1";
+		#address-cells = <1>;
+		#size-cells = <1>;
+		reg = <0xf90a9000 0x1000>;
+		qcom,name = "core2";
+		qcom,cpu = <&CPU2>;
+		qcom,saw2-cfg = <0x01>;
+		qcom,saw2-spm-dly= <0x3C102800>;
+		qcom,saw2-spm-ctl = <0x1>;
+		qcom,saw2-spm-cmd-wfi = [03 0b 0f];
+		qcom,saw2-spm-cmd-spc = [00 20 80 10 E8 5B 03 3B E8 5B 82 10 0B
+			30 06 26 30 0F];
+	};
+
+	qcom,spm@f90b9000 {
+		compatible = "qcom,spm-v2.1";
+		#address-cells = <1>;
+		#size-cells = <1>;
+		reg = <0xf90b9000 0x1000>;
+		qcom,name = "core3";
+		qcom,cpu = <&CPU3>;
+		qcom,saw2-cfg = <0x01>;
+		qcom,saw2-spm-dly= <0x3C102800>;
+		qcom,saw2-spm-ctl = <0x1>;
+		qcom,saw2-spm-cmd-wfi = [03 0b 0f];
+		qcom,saw2-spm-cmd-spc = [00 20 80 10 E8 5B 03 3B E8 5B 82 10 0B
+			30 06 26 30 0F];
+	};
+
+	qcom,spm@f9012000 {
+		compatible = "qcom,spm-v2.1";
+		#address-cells = <1>;
+		#size-cells = <1>;
+		reg = <0xf9012000 0x1000>;
+		qcom,name = "system-l2";
+		qcom,cpu-vctl-list = <&CPU0 &CPU1 &CPU2 &CPU3>;
+		qcom,saw2-cfg = <0x14>;
+		qcom,saw2-spm-dly= <0x3C102800>;
+		qcom,saw2-spm-ctl = <0x1>;
+		qcom,vctl-timeout-us = <50>;
+		qcom,vctl-port = <0x0>;
+		qcom,phase-port = <0x1>;
+		qcom,pfm-port = <0x2>;
+		qcom,cpu-vctl-mask = <0xf>;
+		qcom,saw2-spm-cmd-ret = [1f 00 03 00 0f];
+	};
+};
diff --git a/arch/arm/boot/dts/qcom-msm8974.dtsi b/arch/arm/boot/dts/qcom-msm8974.dtsi
index 15a75e4..0580bc2 100644
--- a/arch/arm/boot/dts/qcom-msm8974.dtsi
+++ b/arch/arm/boot/dts/qcom-msm8974.dtsi
@@ -238,3 +238,5 @@
 		};
 	};
 };
+
+#include "qcom-msm8974-pm.dtsi"
-- 
1.9.1

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

* [PATCH v2 07/10] qcom: msm-pm: Add cpu low power mode functions
  2014-08-12 19:43 [PATCH v2 00/10] QCOM 8074 cpuidle driver Lina Iyer
                   ` (5 preceding siblings ...)
  2014-08-12 19:43 ` [PATCH v2 06/10] arm: dts: qcom: Add SPM device bindings for 8974 Lina Iyer
@ 2014-08-12 19:43 ` Lina Iyer
  2014-08-13 11:18   ` Daniel Lezcano
  2014-08-14 13:38   ` Pramod Gurav
  2014-08-12 19:43 ` [PATCH v2 08/10] qcom: cpuidle: Add cpuidle driver for QCOM cpus Lina Iyer
                   ` (3 subsequent siblings)
  10 siblings, 2 replies; 46+ messages in thread
From: Lina Iyer @ 2014-08-12 19:43 UTC (permalink / raw)
  To: daniel.lezcano, khilman, amit.kucheria, sboyd, davidb, galak,
	linux-arm-msm
  Cc: msivasub, Lina Iyer, Venkat Devarasetty

Add interface layer to abstract and handle hardware specific
functionality for executing various cpu low power modes in QCOM
chipsets.

Signed-off-by: Venkat Devarasetty <vdevaras@codeaurora.org>
Signed-off-by: Mahesh Sivasubramanian <msivasub@codeaurora.org>
Signed-off-by: Lina Iyer <lina.iyer@linaro.org>
---
 drivers/soc/qcom/Makefile |   2 +-
 drivers/soc/qcom/msm-pm.c | 219 ++++++++++++++++++++++++++++++++++++++++++++++
 include/soc/qcom/pm.h     |  39 +++++++++
 3 files changed, 259 insertions(+), 1 deletion(-)
 create mode 100644 drivers/soc/qcom/msm-pm.c
 create mode 100644 include/soc/qcom/pm.h

diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile
index d7ae93b..7925f83 100644
--- a/drivers/soc/qcom/Makefile
+++ b/drivers/soc/qcom/Makefile
@@ -1,5 +1,5 @@
 obj-$(CONFIG_QCOM_GSBI)	+=	qcom_gsbi.o
-obj-$(CONFIG_QCOM_PM) +=	spm-devices.o spm.o
+obj-$(CONFIG_QCOM_PM) +=	spm-devices.o spm.o msm-pm.o
 
 CFLAGS_scm.o :=$(call as-instr,.arch_extension sec,-DREQUIRES_SEC=1)
 obj-$(CONFIG_QCOM_SCM) += scm.o scm-boot.o
diff --git a/drivers/soc/qcom/msm-pm.c b/drivers/soc/qcom/msm-pm.c
new file mode 100644
index 0000000..f2f15b8
--- /dev/null
+++ b/drivers/soc/qcom/msm-pm.c
@@ -0,0 +1,219 @@
+/* Copyright (c) 2010-2014, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/smp.h>
+#include <linux/tick.h>
+#include <linux/platform_device.h>
+#include <linux/cpu_pm.h>
+#include <linux/uaccess.h>
+
+#include <soc/qcom/spm.h>
+#include <soc/qcom/pm.h>
+#include <soc/qcom/scm.h>
+#include <soc/qcom/scm-boot.h>
+
+#include <asm/suspend.h>
+#include <asm/cacheflush.h>
+#include <asm/cputype.h>
+#include <asm/system_misc.h>
+
+#define SCM_CMD_TERMINATE_PC	(0x2)
+#define SCM_CMD_CORE_HOTPLUGGED (0x10)
+#define SCM_FLUSH_FLAG_MASK	(0x3)
+
+static bool msm_pm_is_L1_writeback(void)
+{
+	u32 cache_id = 0;
+
+#if defined(CONFIG_CPU_V7)
+	u32 sel = 0;
+
+	asm volatile ("mcr p15, 2, %[ccselr], c0, c0, 0\n\t"
+		      "isb\n\t"
+		      "mrc p15, 1, %[ccsidr], c0, c0, 0\n\t"
+		      :[ccsidr]"=r" (cache_id)
+		      :[ccselr]"r" (sel)
+		     );
+	return cache_id & BIT(30);
+#elif defined(CONFIG_ARM64)
+	u32 sel = 0;
+	asm volatile("msr csselr_el1, %[ccselr]\n\t"
+		     "isb\n\t"
+		     "mrs %[ccsidr],ccsidr_el1\n\t"
+		     :[ccsidr]"=r" (cache_id)
+		     :[ccselr]"r" (sel)
+		    );
+	return cache_id & BIT(30);
+#else
+#error No valid CPU arch selected
+#endif
+}
+
+static inline void msm_arch_idle(void)
+{
+	/* Flush and clock-gate */
+	mb();
+	wfi();
+}
+
+static bool msm_pm_swfi(bool from_idle)
+{
+	msm_arch_idle();
+	return true;
+}
+
+static bool msm_pm_retention(bool from_idle)
+{
+	int ret = 0;
+
+	ret = msm_spm_set_low_power_mode(MSM_SPM_MODE_RETENTION, false);
+	WARN_ON(ret);
+
+	msm_arch_idle();
+
+	ret = msm_spm_set_low_power_mode(MSM_SPM_MODE_CLOCK_GATING, false);
+	WARN_ON(ret);
+
+	return true;
+}
+
+static int msm_pm_collapse(unsigned long from_idle)
+{
+	enum msm_pm_l2_scm_flag flag = MSM_SCM_L2_ON;
+
+	/**
+	 * Single core processors need to have L2
+	 * flushed when powering down the core.
+	 * Notify SCM to flush secure L2 lines.
+	 */
+	if (num_possible_cpus() == 1)
+		flag = MSM_SCM_L2_OFF;
+
+	if (flag == MSM_SCM_L2_OFF)
+		flush_cache_all();
+	else if (msm_pm_is_L1_writeback())
+		flush_cache_louis();
+
+	flag &= SCM_FLUSH_FLAG_MASK;
+	if (!from_idle)
+		flag |= SCM_CMD_CORE_HOTPLUGGED;
+
+	scm_call_atomic1(SCM_SVC_BOOT, SCM_CMD_TERMINATE_PC, flag);
+
+	return 0;
+}
+
+static void set_up_boot_address(void *entry, int cpu)
+{
+	static int flags[NR_CPUS] = {
+		SCM_FLAG_WARMBOOT_CPU0,
+		SCM_FLAG_WARMBOOT_CPU1,
+		SCM_FLAG_WARMBOOT_CPU2,
+		SCM_FLAG_WARMBOOT_CPU3,
+	};
+	static DEFINE_PER_CPU(void *, last_known_entry);
+
+	if (entry == per_cpu(last_known_entry, cpu))
+		return;
+
+	per_cpu(last_known_entry, cpu) = entry;
+	scm_set_boot_addr(virt_to_phys(entry), flags[cpu]);
+}
+
+static bool __ref msm_pm_spm_power_collapse(unsigned int cpu, bool from_idle)
+{
+	void *entry;
+	bool collapsed = 0;
+	int ret;
+	bool save_cpu_regs = (cpu_online(cpu) || from_idle);
+
+	if (from_idle)
+		cpu_pm_enter();
+
+	ret = msm_spm_set_low_power_mode(MSM_SPM_MODE_POWER_COLLAPSE, false);
+	WARN_ON(ret);
+
+	entry = save_cpu_regs ? cpu_resume : secondary_startup;
+	set_up_boot_address(entry, cpu);
+
+#ifdef CONFIG_CPU_V7
+	collapsed = !cpu_suspend(from_idle, msm_pm_collapse);
+#else
+	collapsed = !cpu_suspend(0);
+#endif
+
+	if (collapsed)
+		local_fiq_enable();
+
+	if (from_idle)
+		cpu_pm_exit();
+
+	ret = msm_spm_set_low_power_mode(MSM_SPM_MODE_CLOCK_GATING, false);
+	WARN_ON(ret);
+
+	return collapsed;
+}
+
+static bool msm_pm_power_collapse_standalone(bool from_idle)
+{
+	unsigned int cpu = smp_processor_id();
+	bool collapsed;
+
+	collapsed = msm_pm_spm_power_collapse(cpu, from_idle);
+
+	return collapsed;
+}
+
+static bool msm_pm_power_collapse(bool from_idle)
+{
+	unsigned int cpu = smp_processor_id();
+	bool collapsed;
+
+	collapsed = msm_pm_spm_power_collapse(cpu, from_idle);
+
+	return collapsed;
+}
+
+static bool (*execute[MSM_PM_SLEEP_MODE_NR])(bool idle) = {
+	[MSM_PM_SLEEP_MODE_WAIT_FOR_INTERRUPT] = msm_pm_swfi,
+	[MSM_PM_SLEEP_MODE_POWER_COLLAPSE_STANDALONE] =
+		msm_pm_power_collapse_standalone,
+	[MSM_PM_SLEEP_MODE_RETENTION] = msm_pm_retention,
+	[MSM_PM_SLEEP_MODE_POWER_COLLAPSE] = msm_pm_power_collapse,
+};
+
+/**
+ * msm_cpu_pm_enter_sleep(): Enter a low power mode on current cpu
+ *
+ * @mode - sleep mode to enter
+ * @from_idle - bool to indicate that the mode is exercised during idle/suspend
+ *
+ * The code should be with interrupts disabled and on the core on which the
+ * low power is to be executed.
+ *
+ */
+bool msm_cpu_pm_enter_sleep(enum msm_pm_sleep_mode mode, bool from_idle)
+{
+	bool exit_stat = false;
+
+	if (execute[mode])
+		exit_stat = execute[mode](from_idle);
+
+	local_irq_enable();
+	return exit_stat;
+}
+EXPORT_SYMBOL(msm_cpu_pm_enter_sleep);
diff --git a/include/soc/qcom/pm.h b/include/soc/qcom/pm.h
new file mode 100644
index 0000000..01872ad
--- /dev/null
+++ b/include/soc/qcom/pm.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2009-2014, The Linux Foundation. All rights reserved.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#ifndef __QCOM_PM_H
+#define __QCOM_PM_H
+
+enum msm_pm_sleep_mode {
+	MSM_PM_SLEEP_MODE_WAIT_FOR_INTERRUPT,
+	MSM_PM_SLEEP_MODE_RETENTION,
+	MSM_PM_SLEEP_MODE_POWER_COLLAPSE_STANDALONE,
+	MSM_PM_SLEEP_MODE_POWER_COLLAPSE,
+	MSM_PM_SLEEP_MODE_NR,
+};
+
+enum msm_pm_l2_scm_flag {
+	MSM_SCM_L2_ON = 0,
+	MSM_SCM_L2_OFF = 1,
+};
+
+#ifdef CONFIG_QCOM_PM
+bool msm_cpu_pm_enter_sleep(enum msm_pm_sleep_mode mode, bool from_idle);
+#else
+static inline bool msm_cpu_pm_enter_sleep(enum msm_pm_sleep_mode mode,
+						bool from_idle)
+{ return true; }
+#endif
+
+#endif  /* __QCOM_PM_H */
-- 
1.9.1

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

* [PATCH v2 08/10] qcom: cpuidle: Add cpuidle driver for QCOM cpus
  2014-08-12 19:43 [PATCH v2 00/10] QCOM 8074 cpuidle driver Lina Iyer
                   ` (6 preceding siblings ...)
  2014-08-12 19:43 ` [PATCH v2 07/10] qcom: msm-pm: Add cpu low power mode functions Lina Iyer
@ 2014-08-12 19:43 ` Lina Iyer
  2014-08-13 11:22   ` Daniel Lezcano
  2014-08-12 19:43 ` [PATCH v2 09/10] qcom: cpuidle: Config option to enable QCOM cpuidle driver Lina Iyer
                   ` (2 subsequent siblings)
  10 siblings, 1 reply; 46+ messages in thread
From: Lina Iyer @ 2014-08-12 19:43 UTC (permalink / raw)
  To: daniel.lezcano, khilman, amit.kucheria, sboyd, davidb, galak,
	linux-arm-msm
  Cc: msivasub, Lina Iyer

Add cpuidle driver interface to allow cpus to go into C-States.

Signed-off-by: Lina Iyer <lina.iyer@linaro.org>
---
 .../devicetree/bindings/arm/msm/qcom,cpuidle.txt   |  73 +++++++++++
 drivers/cpuidle/Makefile                           |   1 +
 drivers/cpuidle/cpuidle-qcom.c                     | 140 +++++++++++++++++++++
 3 files changed, 214 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/arm/msm/qcom,cpuidle.txt
 create mode 100644 drivers/cpuidle/cpuidle-qcom.c

diff --git a/Documentation/devicetree/bindings/arm/msm/qcom,cpuidle.txt b/Documentation/devicetree/bindings/arm/msm/qcom,cpuidle.txt
new file mode 100644
index 0000000..b094baf
--- /dev/null
+++ b/Documentation/devicetree/bindings/arm/msm/qcom,cpuidle.txt
@@ -0,0 +1,73 @@
+Qualcomm CPUIdle driver
+
+The Qualcomm cpuidle driver enables the processor to enter low power modes
+when idle. The processors support 4 low power modes.
+	wfi - also known as clock gating
+	retention - processor clock gated and processor power is reduced.
+	standalone-pc - processor is powered down and when reset, the core
+		boots into secure mode and trampolines back to the kernel.
+		Every core can individually enter this low power mode without
+		affecting the state of the other cores.
+	pc - essentially standalone power collapse, but indicates that the
+		latency to put SoC into a low power state is tolerable.
+
+The cpuidle node is comprised of nodes, each of which represent a C-State the
+processor can achieve. Each node provides the latency and residency which
+helps the cpuidle governor to choose the appropriate low power mode based on
+the time available. Not all SoCs may support all the above low power modes.
+
+PROPERTIES
+
+- compatible:
+	Usage: required
+	Value type: <string>
+	Definition: Should be "qcom,cpuidle"
+
+- qcom,cpu-level:
+	Usage: required
+	Value type: { Node }
+	Definition: Describes a C-State of the processor
+
+	PROPERTIES of qcom,cpu-level
+
+	- reg:
+		Usage: required
+		Value type: <integer>
+		Definition: Index of the C-State
+
+	- qcom,state-name:
+		Usage: required
+		Value type: <string>
+		Definition: C-State moniker
+
+	- qcom,spm-cpu-mode:
+		Usage: required
+		Value type: <string>
+		Definition: The description of the h/w mode that will be
+			achieved in this C-State.
+
+	- qcom,latency-us:
+		Usage: required
+		Value type: <integer>
+		Defintion: Time taken to exit from the C-State
+
+	- qcom,residency-us:
+		Usage: required
+		Value type: <integer>
+		Defintion: Time to be spent in this C-State for the power
+			saving to be beneficial.
+
+Example:
+
+	qcom,cpuidle {
+		compatible = "qcom,cpuidle";
+		#address-cells = <1>;
+		#size-cells = <0>;
+		qcom,cpu-level@0 {
+			reg = <0x0>;
+			qcom,state-name = "C1";
+			qcom,spm-cpu-mode = "wfi";
+			qcom,latency-us = <1>;
+			qcom,residency-us = <1>;
+		};
+	};
diff --git a/drivers/cpuidle/Makefile b/drivers/cpuidle/Makefile
index 11edb31..4a2c446 100644
--- a/drivers/cpuidle/Makefile
+++ b/drivers/cpuidle/Makefile
@@ -16,6 +16,7 @@ obj-$(CONFIG_ARM_ZYNQ_CPUIDLE)		+= cpuidle-zynq.o
 obj-$(CONFIG_ARM_U8500_CPUIDLE)         += cpuidle-ux500.o
 obj-$(CONFIG_ARM_AT91_CPUIDLE)          += cpuidle-at91.o
 obj-$(CONFIG_ARM_EXYNOS_CPUIDLE)        += cpuidle-exynos.o
+obj-$(CONFIG_ARM_QCOM_CPUIDLE)		+= cpuidle-qcom.o
 
 ###############################################################################
 # MIPS drivers
diff --git a/drivers/cpuidle/cpuidle-qcom.c b/drivers/cpuidle/cpuidle-qcom.c
new file mode 100644
index 0000000..8e70a88
--- /dev/null
+++ b/drivers/cpuidle/cpuidle-qcom.c
@@ -0,0 +1,140 @@
+/* Copyright (c) 2014, The Linux Foundation. All rights reserved.
+ * Copyright (c) 2014 Linaro.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/of.h>
+#include <linux/cpuidle.h>
+
+#include <soc/qcom/pm.h>
+
+struct lookup {
+	enum msm_pm_sleep_mode mode;
+	char *name;
+};
+
+static enum msm_pm_sleep_mode spm_sleep_modes[MSM_PM_SLEEP_MODE_NR];
+
+static int qcom_lpm_enter(struct cpuidle_device *dev,
+				struct cpuidle_driver *drv,
+				int index)
+{
+	return msm_cpu_pm_enter_sleep(spm_sleep_modes[index], true);
+}
+
+static struct cpuidle_driver qcom_cpuidle_driver = {
+	.name	= "qcom_cpuidle",
+	.owner	= THIS_MODULE,
+};
+
+static int qcom_cpuidle_probe(struct platform_device *pdev)
+{
+	int ret;
+	struct device_node *top = pdev->dev.of_node;
+	struct device_node *n;
+	char *key;
+	const char *val;
+	int index = 0;
+	int i;
+	struct cpuidle_state *state;
+	static const struct lookup pm_sm_lookup[] = {
+		{MSM_PM_SLEEP_MODE_WAIT_FOR_INTERRUPT,
+			"wfi"},
+		{MSM_PM_SLEEP_MODE_POWER_COLLAPSE_STANDALONE,
+			"standalone_pc"},
+		{MSM_PM_SLEEP_MODE_POWER_COLLAPSE,
+			"pc"},
+		{MSM_PM_SLEEP_MODE_RETENTION,
+			"retention"},
+	};
+
+	if (!top)
+		return -ENODEV;
+
+	for_each_child_of_node(top, n) {
+		key = "qcom,cpu-level";
+		if (of_node_cmp(n->name, key))
+			continue;
+
+		state = &qcom_cpuidle_driver.states[index];
+
+		key = "qcom,spm-cpu-mode";
+		ret = of_property_read_string(n, key, &val);
+		if (ret)
+			goto failed;
+		for (i = 0; i < ARRAY_SIZE(pm_sm_lookup); i++) {
+			if (!strcmp(val, pm_sm_lookup[i].name)) {
+				spm_sleep_modes[index] = pm_sm_lookup[i].mode;
+				break;
+			}
+		}
+		if (i == ARRAY_SIZE(pm_sm_lookup)) {
+			ret = -EFAULT;
+			goto failed;
+		}
+
+		strncpy(state->desc, val, CPUIDLE_DESC_LEN);
+
+		key = "qcom,state-name";
+		ret = of_property_read_string(n, key, &val);
+		if (ret)
+			goto failed;
+		strncpy(state->name, val, CPUIDLE_NAME_LEN);
+
+		key = "qcom,latency-us";
+		ret = of_property_read_u32(n, key, &state->exit_latency);
+		if (ret)
+			goto failed;
+
+		key = "qcom,residency-us";
+		ret = of_property_read_u32(n, key, &state->target_residency);
+		if (ret)
+			goto failed;
+
+		state->flags = CPUIDLE_FLAG_TIME_VALID;
+		state->enter = qcom_lpm_enter;
+		index++;
+	}
+
+	qcom_cpuidle_driver.state_count = index;
+	qcom_cpuidle_driver.safe_state_index = 0;
+
+	ret = cpuidle_register(&qcom_cpuidle_driver, NULL);
+	if (ret) {
+		dev_err(&pdev->dev, "failed to register cpuidle driver\n");
+		return ret;
+	}
+
+	return 0;
+
+failed:
+	dev_err(&pdev->dev, "error parsing key: %s\n", key);
+	return ret;
+}
+
+static struct of_device_id qcom_cpuidle_match_tbl[] = {
+	{.compatible = "qcom,cpuidle"},
+	{},
+};
+
+static struct platform_driver qcom_cpuidle_platform_driver = {
+	.probe	= qcom_cpuidle_probe,
+	.driver	= {
+		.name = "qcom,cpuidle",
+		.owner = THIS_MODULE,
+		.of_match_table = qcom_cpuidle_match_tbl,
+	},
+};
+
+module_platform_driver(qcom_cpuidle_platform_driver);
-- 
1.9.1

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

* [PATCH v2 09/10] qcom: cpuidle: Config option to enable QCOM cpuidle driver
  2014-08-12 19:43 [PATCH v2 00/10] QCOM 8074 cpuidle driver Lina Iyer
                   ` (7 preceding siblings ...)
  2014-08-12 19:43 ` [PATCH v2 08/10] qcom: cpuidle: Add cpuidle driver for QCOM cpus Lina Iyer
@ 2014-08-12 19:43 ` Lina Iyer
  2014-08-13 11:18   ` Daniel Lezcano
  2014-08-12 19:44 ` [PATCH v2 10/10] qcom: cpuidle: Add cpuidle device nodes for 8974 chipset Lina Iyer
  2014-08-13  1:52 ` [PATCH v2 00/10] QCOM 8074 cpuidle driver Stephen Boyd
  10 siblings, 1 reply; 46+ messages in thread
From: Lina Iyer @ 2014-08-12 19:43 UTC (permalink / raw)
  To: daniel.lezcano, khilman, amit.kucheria, sboyd, davidb, galak,
	linux-arm-msm
  Cc: msivasub, Lina Iyer

Allow cpuidle framework to determine the C-State of the QCOM cpus.

Signed-off-by: Lina Iyer <lina.iyer@linaro.org>
---
 drivers/cpuidle/Kconfig.arm | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/drivers/cpuidle/Kconfig.arm b/drivers/cpuidle/Kconfig.arm
index 38cff69..ad52605 100644
--- a/drivers/cpuidle/Kconfig.arm
+++ b/drivers/cpuidle/Kconfig.arm
@@ -62,3 +62,9 @@ config ARM_MVEBU_V7_CPUIDLE
 	depends on ARCH_MVEBU
 	help
 	  Select this to enable cpuidle on Armada 370, 38x and XP processors.
+
+config ARM_QCOM_CPUIDLE
+	bool "CPU Idle drivers for Qualcomm processors"
+	depends on QCOM_PM
+	help
+	  Select this to enable cpuidle for QCOM processors
-- 
1.9.1

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

* [PATCH v2 10/10] qcom: cpuidle: Add cpuidle device nodes for 8974 chipset
  2014-08-12 19:43 [PATCH v2 00/10] QCOM 8074 cpuidle driver Lina Iyer
                   ` (8 preceding siblings ...)
  2014-08-12 19:43 ` [PATCH v2 09/10] qcom: cpuidle: Config option to enable QCOM cpuidle driver Lina Iyer
@ 2014-08-12 19:44 ` Lina Iyer
  2014-08-13  1:52 ` [PATCH v2 00/10] QCOM 8074 cpuidle driver Stephen Boyd
  10 siblings, 0 replies; 46+ messages in thread
From: Lina Iyer @ 2014-08-12 19:44 UTC (permalink / raw)
  To: daniel.lezcano, khilman, amit.kucheria, sboyd, davidb, galak,
	linux-arm-msm
  Cc: msivasub, Lina Iyer

Add C-States and the respective residencies supported by the QCOM 8974
chipset. Current support is for WFI (clock gating) and Standlone-PC
(power down of the core).

Signed-off-by: Lina Iyer <lina.iyer@linaro.org>
---
 arch/arm/boot/dts/qcom-msm8974-pm.dtsi | 21 +++++++++++++++++++++
 1 file changed, 21 insertions(+)

diff --git a/arch/arm/boot/dts/qcom-msm8974-pm.dtsi b/arch/arm/boot/dts/qcom-msm8974-pm.dtsi
index d7d81ca..f15796e 100644
--- a/arch/arm/boot/dts/qcom-msm8974-pm.dtsi
+++ b/arch/arm/boot/dts/qcom-msm8974-pm.dtsi
@@ -88,4 +88,25 @@
 		qcom,cpu-vctl-mask = <0xf>;
 		qcom,saw2-spm-cmd-ret = [1f 00 03 00 0f];
 	};
+
+	qcom,cpuidle {
+		compatible = "qcom,cpuidle";
+		#address-cells = <1>;
+		#size-cells = <0>;
+		qcom,cpu-level@0 {
+			reg = <0x0>;
+			qcom,state-name = "C1";
+			qcom,spm-cpu-mode = "wfi";
+			qcom,latency-us = <1>;
+			qcom,residency-us = <1>;
+		};
+
+		qcom,cpu-level@2 {
+			reg = <0x1>;
+			qcom,state-name = "C2";
+			qcom,spm-cpu-mode = "standalone_pc";
+			qcom,latency-us = <300>;
+			qcom,residency-us = <2000>;
+		};
+	};
 };
-- 
1.9.1

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

* Re: [PATCH v2 05/10] arm: qcom-msm8974: Add CPU phandles to CPU definitions
  2014-08-12 19:43 ` [PATCH v2 05/10] arm: qcom-msm8974: Add CPU phandles to CPU definitions Lina Iyer
@ 2014-08-12 21:09   ` Kumar Gala
  2014-08-14 10:04   ` Pramod Gurav
  1 sibling, 0 replies; 46+ messages in thread
From: Kumar Gala @ 2014-08-12 21:09 UTC (permalink / raw)
  To: Lina Iyer
  Cc: Daniel Lezcano, Kevin Hilman, Amit Kucheria, Stephen Boyd,
	David Brown, linux-arm-msm, msivasub


On Aug 12, 2014, at 2:43 PM, Lina Iyer <lina.iyer@linaro.org> wrote:

> Add CPU phandle labels for all Krait CPUS.
> 
> Signed-off-by: Lina Iyer <lina.iyer@linaro.org>
> ---
> arch/arm/boot/dts/qcom-msm8974.dtsi | 8 ++++----
> 1 file changed, 4 insertions(+), 4 deletions(-)

Merge this with patch 6/10

- k
-- 
Employee of Qualcomm Innovation Center, Inc.
Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum, hosted by The Linux Foundation

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

* Re: [PATCH v2 06/10] arm: dts: qcom: Add SPM device bindings for 8974
  2014-08-12 19:43 ` [PATCH v2 06/10] arm: dts: qcom: Add SPM device bindings for 8974 Lina Iyer
@ 2014-08-12 21:10   ` Kumar Gala
  2014-08-12 21:32     ` Lina Iyer
  2014-08-13  7:39   ` Ivan T. Ivanov
  1 sibling, 1 reply; 46+ messages in thread
From: Kumar Gala @ 2014-08-12 21:10 UTC (permalink / raw)
  To: Lina Iyer
  Cc: daniel.lezcano, khilman, amit.kucheria, sboyd, davidb,
	linux-arm-msm, msivasub, Praveen Chidambaram


On Aug 12, 2014, at 2:43 PM, Lina Iyer <lina.iyer@linaro.org> wrote:

> Add SPM device bindings for QCOM 8974 based cpus. SPM is the sub-system
> power manager and controls the logic around the cores (cpu and L2).
> 
> Each core has an instance of SPM and controls only that core. Each cpu
> SPM is configured to support WFI and SPC (standalone-power collapse) and
> L2 can do retention (clock-gating).
> 
> Signed-off-by: Praveen Chidambaram <pchidamb@codeaurora.org>
> Signed-off-by: Lina Iyer <lina.iyer@linaro.org>
> ---
> arch/arm/boot/dts/qcom-msm8974-pm.dtsi | 91 ++++++++++++++++++++++++++++++++++
> arch/arm/boot/dts/qcom-msm8974.dtsi    |  2 +
> 2 files changed, 93 insertions(+)
> create mode 100644 arch/arm/boot/dts/qcom-msm8974-pm.dtsi

Will you be provided dtsi updates for all the other SoCs, APQ8084, IPQ8064, APQ8064, etc?

- k

-- 
Employee of Qualcomm Innovation Center, Inc.
Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum, hosted by The Linux Foundation

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

* Re: [PATCH v2 06/10] arm: dts: qcom: Add SPM device bindings for 8974
  2014-08-12 21:10   ` Kumar Gala
@ 2014-08-12 21:32     ` Lina Iyer
  0 siblings, 0 replies; 46+ messages in thread
From: Lina Iyer @ 2014-08-12 21:32 UTC (permalink / raw)
  To: Kumar Gala
  Cc: daniel.lezcano, khilman, amit.kucheria, sboyd, davidb,
	linux-arm-msm, msivasub, Praveen Chidambaram

On Tue, Aug 12, 2014 at 04:10:22PM -0500, Kumar Gala wrote:
>
>On Aug 12, 2014, at 2:43 PM, Lina Iyer <lina.iyer@linaro.org> wrote:
>
>> Add SPM device bindings for QCOM 8974 based cpus. SPM is the sub-system
>> power manager and controls the logic around the cores (cpu and L2).
>>
>> Each core has an instance of SPM and controls only that core. Each cpu
>> SPM is configured to support WFI and SPC (standalone-power collapse) and
>> L2 can do retention (clock-gating).
>>
>> Signed-off-by: Praveen Chidambaram <pchidamb@codeaurora.org>
>> Signed-off-by: Lina Iyer <lina.iyer@linaro.org>
>> ---
>> arch/arm/boot/dts/qcom-msm8974-pm.dtsi | 91 ++++++++++++++++++++++++++++++++++
>> arch/arm/boot/dts/qcom-msm8974.dtsi    |  2 +
>> 2 files changed, 93 insertions(+)
>> create mode 100644 arch/arm/boot/dts/qcom-msm8974-pm.dtsi
>
>Will you be provided dtsi updates for all the other SoCs, APQ8084, IPQ8064, APQ8064, etc?
Yes, I will have the DTs for the other targets as well.

Lina

>
>- k
>
>-- 
>Employee of Qualcomm Innovation Center, Inc.
>Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum, hosted by The Linux Foundation
>

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

* Re: [PATCH v2 00/10] QCOM 8074 cpuidle driver
  2014-08-12 19:43 [PATCH v2 00/10] QCOM 8074 cpuidle driver Lina Iyer
                   ` (9 preceding siblings ...)
  2014-08-12 19:44 ` [PATCH v2 10/10] qcom: cpuidle: Add cpuidle device nodes for 8974 chipset Lina Iyer
@ 2014-08-13  1:52 ` Stephen Boyd
  2014-08-13  2:17   ` Lina Iyer
  10 siblings, 1 reply; 46+ messages in thread
From: Stephen Boyd @ 2014-08-13  1:52 UTC (permalink / raw)
  To: Lina Iyer
  Cc: daniel.lezcano, khilman, amit.kucheria, davidb, galak,
	linux-arm-msm, msivasub

On 08/12/14 12:43, Lina Iyer wrote:
> This is version #2 of the patches for cpuidle driver and its dependencies.
>
> Changes from version #1/RFC:
>
> - Remove hotplug from the patch series. Will submit it seprately.
> - Fix SPM drivers per the review comments
> - Modify patch sequence to compile SPM drivers independent of msm-pm, so as to
>   allow wfi() calls to use SPM even without SoC interface driver.
>
> 8074 like any ARM SoC can do architectural clock gating, that helps save on
> power, but not enough of leakage power.  Leakage power of the SoC can be
> further reduced by turning off power to the core. To aid this, every core (cpu
> and L2) is accompanied by a Sub-system Power Manager (SPM), that can be
> configured to indicate the low power mode, the core would be put into and the
> SPM programs the peripheral h/w accordingly to enter low power and turn off the
> power rail to the core.
>

General question before I go diving into the code, can this code use the
pending generic cpuidle state bindings[1]? Last I saw those bindings
looked pretty good.

[1] http://lwn.net/Articles/606080/

-- 
Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum,
hosted by The Linux Foundation

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

* Re: [PATCH v2 00/10] QCOM 8074 cpuidle driver
  2014-08-13  1:52 ` [PATCH v2 00/10] QCOM 8074 cpuidle driver Stephen Boyd
@ 2014-08-13  2:17   ` Lina Iyer
  0 siblings, 0 replies; 46+ messages in thread
From: Lina Iyer @ 2014-08-13  2:17 UTC (permalink / raw)
  To: Stephen Boyd
  Cc: daniel.lezcano, khilman, amit.kucheria, davidb, galak,
	linux-arm-msm, msivasub

On Tue, Aug 12, 2014 at 06:52:02PM -0700, Stephen Boyd wrote:
>On 08/12/14 12:43, Lina Iyer wrote:
>> This is version #2 of the patches for cpuidle driver and its dependencies.
>>
>> Changes from version #1/RFC:
>>
>> - Remove hotplug from the patch series. Will submit it seprately.
>> - Fix SPM drivers per the review comments
>> - Modify patch sequence to compile SPM drivers independent of msm-pm, so as to
>>   allow wfi() calls to use SPM even without SoC interface driver.
>>
>> 8074 like any ARM SoC can do architectural clock gating, that helps save on
>> power, but not enough of leakage power.  Leakage power of the SoC can be
>> further reduced by turning off power to the core. To aid this, every core (cpu
>> and L2) is accompanied by a Sub-system Power Manager (SPM), that can be
>> configured to indicate the low power mode, the core would be put into and the
>> SPM programs the peripheral h/w accordingly to enter low power and turn off the
>> power rail to the core.
>>
>
>General question before I go diving into the code, can this code use the
>pending generic cpuidle state bindings[1]? Last I saw those bindings
>looked pretty good.
My bindings look similiar to the cpuidle bindings for exynos in this
patch.

>
>[1] http://lwn.net/Articles/606080/
Thanks for the link. I am working on a similar DT binding for an SoC
Idle driver.

>
>-- 
>Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum,
>hosted by The Linux Foundation
>

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

* Re: [PATCH v2 06/10] arm: dts: qcom: Add SPM device bindings for 8974
  2014-08-12 19:43 ` [PATCH v2 06/10] arm: dts: qcom: Add SPM device bindings for 8974 Lina Iyer
  2014-08-12 21:10   ` Kumar Gala
@ 2014-08-13  7:39   ` Ivan T. Ivanov
  1 sibling, 0 replies; 46+ messages in thread
From: Ivan T. Ivanov @ 2014-08-13  7:39 UTC (permalink / raw)
  To: Lina Iyer
  Cc: daniel.lezcano, khilman, amit.kucheria, sboyd, davidb, galak,
	linux-arm-msm, msivasub, Praveen Chidambaram

On Tue, 2014-08-12 at 13:43 -0600, Lina Iyer wrote:

> +&soc {
> +	qcom,spm@f9089000 {

Nitpick. There is no need to prefix node name with qcom.

Regards,
Ivan

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

* Re: [PATCH v2 04/10] soc: qcom: Add QCOM Power management config
  2014-08-12 19:43 ` [PATCH v2 04/10] soc: qcom: Add QCOM Power management config Lina Iyer
@ 2014-08-13  9:36   ` Daniel Lezcano
  0 siblings, 0 replies; 46+ messages in thread
From: Daniel Lezcano @ 2014-08-13  9:36 UTC (permalink / raw)
  To: Lina Iyer, khilman, amit.kucheria, sboyd, davidb, galak, linux-arm-msm
  Cc: msivasub

On 08/12/2014 09:43 PM, Lina Iyer wrote:
> Allow power management drivers for QCOM chipsets.
>
> Signed-off-by: Lina Iyer <lina.iyer@linaro.org>
> ---
>   drivers/soc/qcom/Kconfig | 8 ++++++++
>   1 file changed, 8 insertions(+)
>
> diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig
> index 7dcd554..1569410 100644
> --- a/drivers/soc/qcom/Kconfig
> +++ b/drivers/soc/qcom/Kconfig
> @@ -11,3 +11,11 @@ config QCOM_GSBI
>
>   config QCOM_SCM
>   	bool
> +
> +config QCOM_PM
> +	tristate "Qualcomm Power Management"
> +	depends on PM && ARCH_QCOM && OF
> +	help
> +	  QCOM Platform specific power driver to manage cores and L2 low power
> +	  modes. It interface with various system drivers to put the cores in
> +	  low power modes.

This patch should be merged with the patch 3/10.

What is the benefit of having a module for this driver ?


-- 
  <http://www.linaro.org/> Linaro.org │ Open source software for ARM SoCs

Follow Linaro:  <http://www.facebook.com/pages/Linaro> Facebook |
<http://twitter.com/#!/linaroorg> Twitter |
<http://www.linaro.org/linaro-blog/> Blog

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

* Re: [PATCH v2 03/10] qcom: spm: Add Subsystem Power Manager (SPM) driver for QCOM chipsets
  2014-08-12 19:43 ` [PATCH v2 03/10] qcom: spm: Add Subsystem Power Manager (SPM) driver for QCOM chipsets Lina Iyer
@ 2014-08-13 10:49   ` Daniel Lezcano
  2014-08-13 14:00     ` Lina Iyer
  2014-08-14 13:01   ` Pramod Gurav
                     ` (2 subsequent siblings)
  3 siblings, 1 reply; 46+ messages in thread
From: Daniel Lezcano @ 2014-08-13 10:49 UTC (permalink / raw)
  To: Lina Iyer, khilman, amit.kucheria, sboyd, davidb, galak, linux-arm-msm
  Cc: msivasub, Praveen Chidamabram, Murali Nalajala

On 08/12/2014 09:43 PM, Lina Iyer wrote:
> Qualcomm chipsets use an separate h/w block to control the logic around
> the processor cores (cpu and L2). The SPM h/w block regulates power to
> the cores and controls the power when the core enter low power modes.
>
> Each core has its own instance of SPM. The SPM has the following key
> functions
> 	- Configure the h/w dependencies when entering low power modes
> 	- Wait for interrupt and wake up on interrupt
> 	- Ensure the dependencies are ready before bringing the core out
> 	  of sleep
> 	- Regulating voltage to the core, interfacing with the PMIC.
> 	- Optimize power based on runtime recommendations.
>
> The driver identifies and configures the SPMs, by reading the nodes and
> the register values from the devicetree. The SPMs need to be configured
> to allow the processor to be idled in a low power state.

I began to comment but I realize I have a lot of questions and comments 
for this patch and because of its size, it will be impossible to follow 
a discussion. This patch is really too big to review, please split it 
into smaller chunks.

Thanks

   -- Daniel

> Signed-off-by: Praveen Chidamabram <pchidamb@codeaurora.org>
> Signed-off-by: Murali Nalajala <mnalajal@codeaurora.org>
> Signed-off-by: Lina Iyer <lina.iyer@linaro.org>
> ---
>   .../devicetree/bindings/arm/msm/spm-v2.txt         |  62 ++
>   drivers/soc/qcom/Makefile                          |   2 +
>   drivers/soc/qcom/spm-devices.c                     | 703 +++++++++++++++++++++
>   drivers/soc/qcom/spm.c                             | 482 ++++++++++++++
>   drivers/soc/qcom/spm_driver.h                      | 116 ++++
>   include/soc/qcom/spm.h                             |  70 ++
>   6 files changed, 1435 insertions(+)
>   create mode 100644 Documentation/devicetree/bindings/arm/msm/spm-v2.txt
>   create mode 100644 drivers/soc/qcom/spm-devices.c
>   create mode 100644 drivers/soc/qcom/spm.c
>   create mode 100644 drivers/soc/qcom/spm_driver.h
>   create mode 100644 include/soc/qcom/spm.h
>
> diff --git a/Documentation/devicetree/bindings/arm/msm/spm-v2.txt b/Documentation/devicetree/bindings/arm/msm/spm-v2.txt
> new file mode 100644
> index 0000000..3130f4b
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/arm/msm/spm-v2.txt
> @@ -0,0 +1,62 @@
> +* MSM Subsystem Power Manager (spm-v2)
> +
> +S4 generation of MSMs have SPM hardware blocks to control the Application
> +Processor Sub-System power. These SPM blocks run individual state machine
> +to determine what the core (L2 or Krait/Scorpion) would do when the WFI
> +instruction is executed by the core.
> +
> +The devicetree representation of the SPM block should be:
> +
> +Required properties
> +
> +- compatible: Could be one of -
> +		"qcom,spm-v2.1"
> +		"qcom,spm-v3.0"
> +- reg: The physical address and the size of the SPM's memory mapped registers
> +- qcom,cpu: phandle for the CPU that the SPM block is attached to. On targets
> +	that dont support CPU phandles the driver would support qcom,core-id.
> +	This field is required on only for SPMs that control the CPU.
> +- qcom,saw2-cfg: SAW2 configuration register
> +- qcom,saw2-spm-dly: Provides the values for the SPM delay command in the SPM
> +	sequence
> +- qcom,saw2-spm-ctl: The SPM control register
> +- qcom,name: The name with which a SPM device is identified by the power
> +	management code.
> +
> +Optional properties
> +
> +- qcom,saw2-pmic-data0..7: Specify the pmic data value and the associated FTS
> +	(Fast Transient Switch) index to send the PMIC data to
> +- qcom,vctl-port: The PVC (PMIC Virtual Channel) port used for changing
> +	voltage
> +- qcom,phase-port: The PVC port used for changing the number of phases
> +- qcom,pfm-port: The PVC port used for enabling PWM/PFM modes
> +- qcom,saw2-spm-cmd-wfi: The WFI command sequence
> +- qcom,saw2-spm-cmd-ret: The Retention command sequence
> +- qcom,saw2-spm-cmd-spc: The Standalone PC command sequence
> +- qcom,saw2-spm-cmd-pc-no-rpm: The Power Collapse command sequence where APPS
> +	proc won't inform the RPM.
> +- qcom,saw2-spm-cmd-pc: The Power Collapse command sequence. This sequence may
> +	turn off other SoC components.
> +- qcom,saw2-spm-cmd-gdhs: GDHS (Globally Distributed Head Switch) command
> +	sequence. This sequence will retain the memory but turn off the logic.
> +- qcom,cpu-vctl-list: List of cpu node phandles, whose voltage the spm device
> +	can control.
> +- qcom,vctl-timeout-us: The timeout value in microseconds to wait for voltage to
> +	change after sending the voltage command to the PMIC.
> +-
> +Example:
> +	qcom,spm@f9089000 {
> +		compatible = "qcom,spm-v2";
> +		#address-cells = <1>;
> +		#size-cells = <1>;
> +		reg = <0xf9089000 0x1000>;
> +		qcom,cpu = <&CPU0>;
> +		qcom,saw2-cfg = <0x1>;
> +		qcom,saw2-spm-dly= <0x20000400>;
> +		qcom,saw2-spm-ctl = <0x1>;
> +		qcom,saw2-spm-cmd-wfi = [03 0b 0f];
> +		qcom,saw2-spm-cmd-spc = [00 20 50 80 60 70 10 92
> +				a0 b0 03 68 70 3b 92 a0 b0
> +				82 2b 50 10 30 02 22 30 0f];
> +	};
> diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile
> index 70d52ed..d7ae93b 100644
> --- a/drivers/soc/qcom/Makefile
> +++ b/drivers/soc/qcom/Makefile
> @@ -1,3 +1,5 @@
>   obj-$(CONFIG_QCOM_GSBI)	+=	qcom_gsbi.o
> +obj-$(CONFIG_QCOM_PM) +=	spm-devices.o spm.o
> +
>   CFLAGS_scm.o :=$(call as-instr,.arch_extension sec,-DREQUIRES_SEC=1)
>   obj-$(CONFIG_QCOM_SCM) += scm.o scm-boot.o
> diff --git a/drivers/soc/qcom/spm-devices.c b/drivers/soc/qcom/spm-devices.c
> new file mode 100644
> index 0000000..567e9f9
> --- /dev/null
> +++ b/drivers/soc/qcom/spm-devices.c
> @@ -0,0 +1,703 @@
> +/* Copyright (c) 2011-2014, The Linux Foundation. All rights reserved.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 and
> + * only version 2 as published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + * GNU General Public License for more details.
> + *
> + */
> +
> +#include <linux/module.h>
> +#include <linux/kernel.h>
> +#include <linux/delay.h>
> +#include <linux/init.h>
> +#include <linux/io.h>
> +#include <linux/slab.h>
> +#include <linux/of.h>
> +#include <linux/of_address.h>
> +#include <linux/err.h>
> +#include <linux/platform_device.h>
> +#include <linux/err.h>
> +
> +#include <soc/qcom/spm.h>
> +
> +#include "spm_driver.h"
> +
> +#define VDD_DEFAULT 0xDEADF00D
> +
> +struct msm_spm_power_modes {
> +	uint32_t mode;
> +	bool notify_rpm;
> +	uint32_t start_addr;
> +};
> +
> +struct msm_spm_device {
> +	struct list_head list;
> +	bool initialized;
> +	const char *name;
> +	struct msm_spm_driver_data reg_data;
> +	struct msm_spm_power_modes *modes;
> +	uint32_t num_modes;
> +	uint32_t cpu_vdd;
> +	struct cpumask mask;
> +	void __iomem *q2s_reg;
> +};
> +
> +struct msm_spm_vdd_info {
> +	struct msm_spm_device *vctl_dev;
> +	uint32_t vlevel;
> +	int err;
> +};
> +
> +static LIST_HEAD(spm_list);
> +static DEFINE_PER_CPU_SHARED_ALIGNED(struct msm_spm_device, msm_cpu_spm_device);
> +static DEFINE_PER_CPU(struct msm_spm_device *, cpu_vctl_device);
> +
> +static void msm_spm_smp_set_vdd(void *data)
> +{
> +	struct msm_spm_vdd_info *info = (struct msm_spm_vdd_info *)data;
> +	struct msm_spm_device *dev = info->vctl_dev;
> +
> +	dev->cpu_vdd = info->vlevel;
> +	info->err = msm_spm_drv_set_vdd(&dev->reg_data, info->vlevel);
> +}
> +
> +/**
> + * msm_spm_probe_done(): Verify and return the status of the cpu(s) and l2
> + * probe.
> + * Return: 0 if all spm devices have been probed, else return -EPROBE_DEFER.
> + * if probe failed, then return the err number for that failure.
> + */
> +int msm_spm_probe_done(void)
> +{
> +	struct msm_spm_device *dev;
> +	int cpu;
> +	int ret = 0;
> +
> +	for_each_possible_cpu(cpu) {
> +		dev = per_cpu(cpu_vctl_device, cpu);
> +		if (!dev)
> +			return -EPROBE_DEFER;
> +
> +		ret = IS_ERR(dev);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	return 0;
> +}
> +EXPORT_SYMBOL(msm_spm_probe_done);

Can you explain how this function is used by the caller ? When is it 
called ? What is its purpose and who is setting 'dev' to an ERR ?

> +void msm_spm_dump_regs(unsigned int cpu)
> +{
> +	dump_regs(&per_cpu(msm_cpu_spm_device, cpu).reg_data, cpu);
> +}
> +
> +/**
> + * msm_spm_set_vdd(): Set core voltage
> + * @cpu: core id
> + * @vlevel: Encoded PMIC data.
> + *
> + * Return: 0 on success or -(ERRNO) on failure.
> + */
> +int msm_spm_set_vdd(unsigned int cpu, unsigned int vlevel)
> +{
> +	struct msm_spm_vdd_info info;
> +	struct msm_spm_device *dev = per_cpu(cpu_vctl_device, cpu);
> +	int ret;
> +
> +	if (!dev)
> +		return -EPROBE_DEFER;
> +
> +	ret = IS_ERR(dev);
> +	if (ret)
> +		return ret;
> +
> +	info.vctl_dev = dev;
> +	info.vlevel = vlevel;
> +
> +	ret = smp_call_function_any(&dev->mask, msm_spm_smp_set_vdd, &info,
> +					true);
> +	if (ret)
> +		return ret;

If cpu_vctl_device is per cpu, why a cpumask is used ?

> +
> +	return info.err;
> +}
> +EXPORT_SYMBOL(msm_spm_set_vdd);
> +
> +/**
> + * msm_spm_get_vdd(): Get core voltage
> + * @cpu: core id
> + * @return: Returns encoded PMIC data.
> + */
> +unsigned int msm_spm_get_vdd(unsigned int cpu)
> +{
> +	int ret;
> +	struct msm_spm_device *dev = per_cpu(cpu_vctl_device, cpu);
> +
> +	if (!dev)
> +		return -EPROBE_DEFER;
> +
> +	ret = IS_ERR(dev);
> +	if (ret)
> +		return ret;
> +
> +	return dev->cpu_vdd;
> +}
> +EXPORT_SYMBOL(msm_spm_get_vdd);
> +
> +static void msm_spm_config_q2s(struct msm_spm_device *dev, unsigned int mode)
> +{
> +	uint32_t spm_legacy_mode = 0;
> +	uint32_t qchannel_ignore = 0;
> +	uint32_t val = 0;
> +
> +	if (!dev->q2s_reg)
> +		return;
> +
> +	switch (mode) {
> +	case MSM_SPM_MODE_DISABLED:
> +	case MSM_SPM_MODE_CLOCK_GATING:
> +		qchannel_ignore = 1;
> +		spm_legacy_mode = 0;
> +		break;
> +	case MSM_SPM_MODE_RETENTION:
> +		qchannel_ignore = 0;
> +		spm_legacy_mode = 0;
> +		break;
> +	case MSM_SPM_MODE_GDHS:
> +	case MSM_SPM_MODE_POWER_COLLAPSE:
> +		qchannel_ignore = 0;
> +		spm_legacy_mode = 1;
> +		break;
> +	default:
> +		break;
> +	}
> +
> +	val = spm_legacy_mode << 2 | qchannel_ignore << 1;
> +	__raw_writel(val, dev->q2s_reg);
> +	mb();
> +}
> +
> +static int msm_spm_dev_set_low_power_mode(struct msm_spm_device *dev,
> +		unsigned int mode, bool notify_rpm)
> +{
> +	uint32_t i;
> +	uint32_t start_addr = 0;
> +	int ret = -EINVAL;
> +	bool pc_mode = false;
> +
> +	if (!dev->initialized)
> +		return -ENXIO;
> +
> +	if ((mode == MSM_SPM_MODE_POWER_COLLAPSE)
> +			|| (mode == MSM_SPM_MODE_GDHS))
> +		pc_mode = true;
> +
> +	if (mode == MSM_SPM_MODE_DISABLED) {
> +		ret = msm_spm_drv_set_spm_enable(&dev->reg_data, false);
> +	} else if (!msm_spm_drv_set_spm_enable(&dev->reg_data, true)) {
> +		for (i = 0; i < dev->num_modes; i++) {
> +			if ((dev->modes[i].mode == mode) &&
> +				(dev->modes[i].notify_rpm == notify_rpm)) {
> +				start_addr = dev->modes[i].start_addr;
> +				break;
> +			}
> +		}
> +		ret = msm_spm_drv_set_low_power_mode(&dev->reg_data,
> +					start_addr, pc_mode);
> +	}
> +
> +	msm_spm_config_q2s(dev, mode);
> +
> +	return ret;
> +}
> +
> +static int msm_spm_dev_init(struct msm_spm_device *dev,
> +		struct msm_spm_platform_data *data)
> +{
> +	int i, ret = -ENOMEM;
> +	uint32_t offset = 0;
> +
> +	dev->cpu_vdd = VDD_DEFAULT;
> +	dev->num_modes = data->num_modes;
> +	dev->modes = kmalloc(
> +			sizeof(struct msm_spm_power_modes) * dev->num_modes,
> +			GFP_KERNEL);
> +
> +	if (!dev->modes)
> +		goto spm_failed_malloc;
> +
> +	dev->reg_data.major = data->major;
> +	dev->reg_data.minor = data->minor;
> +	ret = msm_spm_drv_init(&dev->reg_data, data);
> +
> +	if (ret)
> +		goto spm_failed_init;
> +
> +	for (i = 0; i < dev->num_modes; i++) {
> +
> +		/* Default offset is 0 and gets updated as we write more
> +		 * sequences into SPM
> +		 */
> +		dev->modes[i].start_addr = offset;
> +		ret = msm_spm_drv_write_seq_data(&dev->reg_data,
> +						data->modes[i].cmd, &offset);
> +		if (ret < 0)
> +			goto spm_failed_init;
> +
> +		dev->modes[i].mode = data->modes[i].mode;
> +		dev->modes[i].notify_rpm = data->modes[i].notify_rpm;
> +	}
> +	msm_spm_drv_reinit(&dev->reg_data);
> +	dev->initialized = true;
> +	return 0;
> +
> +spm_failed_init:
> +	kfree(dev->modes);
> +spm_failed_malloc:
> +	return ret;
> +}
> +
> +/**
> + * msm_spm_turn_on_cpu_rail(): Power on cpu rail before turning on core
> + * @base: The SAW VCTL register which would set the voltage up.
> + * @val: The value to be set on the rail
> + * @cpu: The cpu for this with rail is being powered on
> + */
> +int msm_spm_turn_on_cpu_rail(void __iomem *base, unsigned int val, int cpu)
> +{
> +	uint32_t timeout = 2000; /* delay for voltage to settle on the core */
> +	struct msm_spm_device *dev = per_cpu(cpu_vctl_device, cpu);
> +
> +	/*
> +	 * If clock drivers have already set up the voltage,
> +	 * do not overwrite that value.
> +	 */
> +	if (dev && (dev->cpu_vdd != VDD_DEFAULT))
> +		return 0;
> +
> +	/* Set the CPU supply regulator voltage */
> +	val = (val & 0xFF);
> +	writel_relaxed(val, base);
> +	mb();
> +	udelay(timeout);
> +
> +	/* Enable the CPU supply regulator*/
> +	val = 0x30080;
> +	writel_relaxed(val, base);
> +	mb();
> +	udelay(timeout);
> +
> +	return 0;
> +}
> +EXPORT_SYMBOL(msm_spm_turn_on_cpu_rail);
> +
> +void msm_spm_reinit(void)
> +{
> +	unsigned int cpu;
> +
> +	for_each_possible_cpu(cpu)
> +		msm_spm_drv_reinit(&per_cpu(msm_cpu_spm_device.reg_data, cpu));
> +}
> +EXPORT_SYMBOL(msm_spm_reinit);
> +
> +/*
> + * msm_spm_is_mode_avail() - Specifies if a mode is available for the cpu
> + * It should only be used to decide a mode before lpm driver is probed.
> + * @mode: SPM LPM mode to be selected
> + */
> +bool msm_spm_is_mode_avail(unsigned int mode)
> +{
> +	struct msm_spm_device *dev = &__get_cpu_var(msm_cpu_spm_device);
> +	int i;
> +
> +	for (i = 0; i < dev->num_modes; i++) {
> +		if (dev->modes[i].mode == mode)
> +			return true;
> +	}
> +
> +	return false;
> +}
> +
> +/**
> + * msm_spm_set_low_power_mode() - Configure SPM start address for low power mode
> + * @mode: SPM LPM mode to enter
> + * @notify_rpm: Notify RPM in this mode
> + */
> +int msm_spm_set_low_power_mode(unsigned int mode, bool notify_rpm)
> +{
> +	struct msm_spm_device *dev = &__get_cpu_var(msm_cpu_spm_device);
> +
> +	return msm_spm_dev_set_low_power_mode(dev, mode, notify_rpm);
> +}
> +EXPORT_SYMBOL(msm_spm_set_low_power_mode);
> +
> +/**
> + * msm_spm_init(): Board initalization function
> + * @data: platform specific SPM register configuration data
> + * @nr_devs: Number of SPM devices being initialized
> + */
> +int __init msm_spm_init(struct msm_spm_platform_data *data, int nr_devs)
> +{
> +	unsigned int cpu;
> +	int ret = 0;
> +
> +	BUG_ON((nr_devs < num_possible_cpus()) || !data);
> +
> +	for_each_possible_cpu(cpu) {
> +		struct msm_spm_device *dev = &per_cpu(msm_cpu_spm_device, cpu);
> +
> +		ret = msm_spm_dev_init(dev, &data[cpu]);
> +		if (ret < 0) {
> +			pr_warn("%s():failed CPU:%u ret:%d\n", __func__,
> +					cpu, ret);
> +			break;
> +		}
> +	}
> +
> +	return ret;
> +}
> +
> +struct msm_spm_device *msm_spm_get_device_by_name(const char *name)
> +{
> +	struct list_head *list;
> +
> +	list_for_each(list, &spm_list) {
> +		struct msm_spm_device *dev
> +			= list_entry(list, typeof(*dev), list);
> +		if (dev->name && !strcmp(dev->name, name))
> +			return dev;
> +	}
> +	return ERR_PTR(-ENODEV);
> +}
> +
> +int msm_spm_config_low_power_mode(struct msm_spm_device *dev,
> +		unsigned int mode, bool notify_rpm)
> +{
> +	return msm_spm_dev_set_low_power_mode(dev, mode, notify_rpm);
> +}
> +#ifdef CONFIG_MSM_L2_SPM
> +
> +/**
> + * msm_spm_apcs_set_phase(): Set number of SMPS phases.
> + * @cpu: cpu which is requesting the change in number of phases.
> + * @phase_cnt: Number of phases to be set active
> + */
> +int msm_spm_apcs_set_phase(int cpu, unsigned int phase_cnt)
> +{
> +	struct msm_spm_device *dev = per_cpu(cpu_vctl_device, cpu);
> +
> +	if (!dev)
> +		return -ENXIO;
> +
> +	return msm_spm_drv_set_pmic_data(&dev->reg_data,
> +			MSM_SPM_PMIC_PHASE_PORT, phase_cnt);
> +}
> +EXPORT_SYMBOL(msm_spm_apcs_set_phase);
> +
> +/** msm_spm_enable_fts_lpm() : Enable FTS to switch to low power
> + *                             when the cores are in low power modes
> + * @cpu: cpu that is entering low power mode.
> + * @mode: The mode configuration for FTS
> + */
> +int msm_spm_enable_fts_lpm(int cpu, uint32_t mode)
> +{
> +	struct msm_spm_device *dev = per_cpu(cpu_vctl_device, cpu);
> +
> +	if (!dev)
> +		return -ENXIO;
> +
> +	return msm_spm_drv_set_pmic_data(&dev->reg_data,
> +			MSM_SPM_PMIC_PFM_PORT, mode);
> +}
> +EXPORT_SYMBOL(msm_spm_enable_fts_lpm);
> +
> +#endif
> +
> +static int get_cpu_id(struct device_node *node)
> +{
> +	struct device_node *cpu_node;
> +	u32 cpu;
> +	int ret = -EINVAL;
> +	char *key = "qcom,cpu";
> +
> +	cpu_node = of_parse_phandle(node, key, 0);
> +	if (cpu_node) {
> +		for_each_possible_cpu(cpu) {
> +			if (of_get_cpu_node(cpu, NULL) == cpu_node)
> +				return cpu;
> +		}
> +	}
> +	return ret;
> +}
> +
> +static struct msm_spm_device *msm_spm_get_device(struct platform_device *pdev)
> +{
> +	struct msm_spm_device *dev = NULL;
> +	const char *val = NULL;
> +	char *key = "qcom,name";
> +	int cpu = get_cpu_id(pdev->dev.of_node);
> +
> +	if ((cpu >= 0) && cpu < num_possible_cpus())
> +		dev = &per_cpu(msm_cpu_spm_device, cpu);
> +	else if ((cpu == 0xffff) || (cpu < 0))
> +		dev = devm_kzalloc(&pdev->dev, sizeof(struct msm_spm_device),
> +					GFP_KERNEL);
> +
> +	if (!dev)
> +		return NULL;
> +
> +	if (of_property_read_string(pdev->dev.of_node, key, &val)) {
> +		pr_err("%s(): Cannot find a required node key:%s\n",
> +				__func__, key);
> +		return NULL;
> +	}
> +	dev->name = val;

Is the string pointed by val always valid ?

> +	list_add(&dev->list, &spm_list);
> +
> +	return dev;
> +}
> +
> +static void get_cpumask(struct device_node *node, struct cpumask *mask)
> +{
> +	unsigned long vctl_mask = 0;
> +	unsigned c = 0;
> +	int idx = 0;
> +	struct device_node *cpu_node = NULL;
> +	int ret = 0;
> +	char *key = "qcom,cpu-vctl-list";
> +	bool found = false;
> +
> +	cpu_node = of_parse_phandle(node, key, idx++);
> +	while (cpu_node) {
> +		found = true;
> +		for_each_possible_cpu(c) {
> +			if (of_get_cpu_node(c, NULL) == cpu_node)
> +				cpumask_set_cpu(c, mask);
> +		}
> +		cpu_node = of_parse_phandle(node, key, idx++);
> +	};
> +
> +	if (found)
> +		return;
> +
> +	key = "qcom,cpu-vctl-mask";
> +	ret = of_property_read_u32(node, key, (u32 *) &vctl_mask);
> +	if (!ret) {
> +		for_each_set_bit(c, &vctl_mask, num_possible_cpus()) {
> +			cpumask_set_cpu(c, mask);
> +		}
> +	}
> +}
> +
> +static int msm_spm_dev_probe(struct platform_device *pdev)
> +{
> +	int ret = 0;
> +	int cpu = 0;
> +	int i = 0;
> +	struct device_node *node = pdev->dev.of_node;
> +	struct msm_spm_platform_data spm_data;
> +	char *key = NULL;
> +	uint32_t val = 0;
> +	struct msm_spm_seq_entry modes[MSM_SPM_MODE_NR];
> +	int len = 0;
> +	struct msm_spm_device *dev = NULL;
> +	struct resource *res = NULL;
> +	uint32_t mode_count = 0;
> +
> +	struct spm_of {
> +		char *key;
> +		uint32_t id;
> +	};
> +
> +	struct spm_of spm_of_data[] = {
> +		{"qcom,saw2-cfg", MSM_SPM_REG_SAW2_CFG},
> +		{"qcom,saw2-spm-dly", MSM_SPM_REG_SAW2_SPM_DLY},
> +		{"qcom,saw2-spm-ctl", MSM_SPM_REG_SAW2_SPM_CTL},
> +		{"qcom,saw2-pmic-data0", MSM_SPM_REG_SAW2_PMIC_DATA_0},
> +		{"qcom,saw2-pmic-data1", MSM_SPM_REG_SAW2_PMIC_DATA_1},
> +		{"qcom,saw2-pmic-data2", MSM_SPM_REG_SAW2_PMIC_DATA_2},
> +		{"qcom,saw2-pmic-data3", MSM_SPM_REG_SAW2_PMIC_DATA_3},
> +		{"qcom,saw2-pmic-data4", MSM_SPM_REG_SAW2_PMIC_DATA_4},
> +		{"qcom,saw2-pmic-data5", MSM_SPM_REG_SAW2_PMIC_DATA_5},
> +		{"qcom,saw2-pmic-data6", MSM_SPM_REG_SAW2_PMIC_DATA_6},
> +		{"qcom,saw2-pmic-data7", MSM_SPM_REG_SAW2_PMIC_DATA_7},
> +	};
> +
> +	struct mode_of {
> +		char *key;
> +		uint32_t id;
> +		uint32_t notify_rpm;
> +	};
> +
> +	struct mode_of mode_of_data[] = {
> +		{"qcom,saw2-spm-cmd-wfi", MSM_SPM_MODE_CLOCK_GATING, 0},
> +		{"qcom,saw2-spm-cmd-ret", MSM_SPM_MODE_RETENTION, 0},
> +		{"qcom,saw2-spm-cmd-gdhs", MSM_SPM_MODE_GDHS, 1},
> +		{"qcom,saw2-spm-cmd-spc", MSM_SPM_MODE_POWER_COLLAPSE, 0},
> +		{"qcom,saw2-spm-cmd-pc", MSM_SPM_MODE_POWER_COLLAPSE, 1},
> +	};
> +
> +	dev = msm_spm_get_device(pdev);
> +	if (!dev) {
> +		ret = -ENOMEM;
> +		goto fail;
> +	}
> +	get_cpumask(node, &dev->mask);
> +
> +	memset(&spm_data, 0, sizeof(struct msm_spm_platform_data));
> +	memset(&modes, 0,
> +		(MSM_SPM_MODE_NR - 2) * sizeof(struct msm_spm_seq_entry));
> +
> +	if (of_device_is_compatible(node, "qcom,spm-v2.1")) {
> +		spm_data.major = 2;
> +		spm_data.minor = 1;
> +	} else if (of_device_is_compatible(node, "qcom,spm-v3.0")) {
> +		spm_data.major = 3;
> +		spm_data.minor = 0;
> +	}
> +
> +	key = "qcom,vctl-timeout-us";
> +	ret = of_property_read_u32(node, key, &val);
> +	if (!ret)
> +		spm_data.vctl_timeout_us = val;
> +
> +	/* SAW start address */
> +	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> +	if (!res) {
> +		ret = -EFAULT;
> +		goto fail;
> +	}
> +
> +	spm_data.reg_base_addr = devm_ioremap(&pdev->dev, res->start,
> +					resource_size(res));
> +	if (!spm_data.reg_base_addr) {
> +		ret = -ENOMEM;
> +		goto fail;
> +	}
> +
> +	spm_data.vctl_port = -1;
> +	spm_data.phase_port = -1;
> +	spm_data.pfm_port = -1;
> +
> +	key = "qcom,vctl-port";
> +	of_property_read_u32(node, key, &spm_data.vctl_port);
> +
> +	key = "qcom,phase-port";
> +	of_property_read_u32(node, key, &spm_data.phase_port);
> +
> +	key = "qcom,pfm-port";
> +	of_property_read_u32(node, key, &spm_data.pfm_port);
> +
> +	/* Q2S (QChannel-2-SPM) register */
> +	res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
> +	if (res) {
> +		dev->q2s_reg = devm_ioremap(&pdev->dev, res->start,
> +						resource_size(res));
> +		if (!dev->q2s_reg) {
> +			pr_err("%s(): Unable to iomap Q2S register\n",
> +					__func__);
> +			ret = -EADDRNOTAVAIL;
> +			goto fail;
> +		}
> +	}
> +	/*
> +	 * At system boot, cpus and or clusters can remain in reset. CCI SPM
> +	 * will not be triggered unless SPM_LEGACY_MODE bit is set for the
> +	 * cluster in reset. Initialize q2s registers and set the
> +	 * SPM_LEGACY_MODE bit.
> +	 */
> +	msm_spm_config_q2s(dev, MSM_SPM_MODE_POWER_COLLAPSE);
> +
> +	for (i = 0; i < ARRAY_SIZE(spm_of_data); i++) {
> +		ret = of_property_read_u32(node, spm_of_data[i].key, &val);
> +		if (ret)
> +			continue;
> +		spm_data.reg_init_values[spm_of_data[i].id] = val;
> +	}
> +
> +	for (i = 0; i < ARRAY_SIZE(mode_of_data); i++) {
> +		key = mode_of_data[i].key;
> +		modes[mode_count].cmd =
> +			(uint8_t *)of_get_property(node, key, &len);
> +		if (!modes[mode_count].cmd)
> +			continue;
> +		modes[mode_count].mode = mode_of_data[i].id;
> +		modes[mode_count].notify_rpm = mode_of_data[i].notify_rpm;
> +		pr_debug("%s(): dev: %s cmd:%s, mode:%d rpm:%d\n", __func__,
> +				dev->name, key, modes[mode_count].mode,
> +				modes[mode_count].notify_rpm);
> +		mode_count++;
> +	}
> +
> +	spm_data.modes = modes;
> +	spm_data.num_modes = mode_count;
> +
> +	ret = msm_spm_dev_init(dev, &spm_data);
> +	if (ret)
> +		goto fail;
> +
> +	platform_set_drvdata(pdev, dev);
> +
> +	for_each_cpu(cpu, &dev->mask)
> +		per_cpu(cpu_vctl_device, cpu) = dev;
> +
> +	return ret;
> +
> +fail:
> +	cpu = get_cpu_id(pdev->dev.of_node);
> +	if (dev && (cpu >= num_possible_cpus() || (cpu < 0))) {
> +		for_each_cpu(cpu, &dev->mask)
> +			per_cpu(cpu_vctl_device, cpu) = ERR_PTR(ret);
> +	}
> +
> +	pr_err("%s: CPU%d SPM device probe failed: %d\n", __func__, cpu, ret);
> +
> +	return ret;
> +}
> +
> +static int msm_spm_dev_remove(struct platform_device *pdev)
> +{
> +	struct msm_spm_device *dev = platform_get_drvdata(pdev);
> +
> +	list_del(&dev->list);
> +
> +	return 0;
> +}
> +
> +static struct of_device_id msm_spm_match_table[] = {
> +	{.compatible = "qcom,spm-v2.1"},
> +	{.compatible = "qcom,spm-v3.0"},
> +	{},
> +};
> +
> +static struct platform_driver msm_spm_device_driver = {
> +	.probe = msm_spm_dev_probe,
> +	.remove = msm_spm_dev_remove,
> +	.driver = {
> +		.name = "spm-v2",
> +		.owner = THIS_MODULE,
> +		.of_match_table = msm_spm_match_table,
> +	},
> +};
> +
> +/**
> + * msm_spm_device_init(): Device tree initialization function
> + */
> +int __init msm_spm_device_init(void)
> +{
> +	static bool registered;
> +
> +	if (registered)
> +		return 0;
> +
> +	registered = true;
> +
> +	return platform_driver_register(&msm_spm_device_driver);
> +}
> +device_initcall(msm_spm_device_init);

Why is needed this 'registered' thing ?

Couldn't the msm_spm_device_init be removed and replaced by:

module_platform_driver(msm_spm_device_driver);

?

> diff --git a/drivers/soc/qcom/spm.c b/drivers/soc/qcom/spm.c
> new file mode 100644
> index 0000000..7dbdb64
> --- /dev/null
> +++ b/drivers/soc/qcom/spm.c
> @@ -0,0 +1,482 @@
> +/* Copyright (c) 2011-2014, The Linux Foundation. All rights reserved.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 and
> + * only version 2 as published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + * GNU General Public License for more details.
> + *
> + */
> +
> +#include <linux/module.h>
> +#include <linux/kernel.h>
> +#include <linux/delay.h>
> +#include <linux/init.h>
> +#include <linux/io.h>
> +#include <linux/slab.h>
> +
> +#include "spm_driver.h"
> +
> +#define MSM_SPM_PMIC_STATE_IDLE  0
> +
> +enum {
> +	MSM_SPM_DEBUG_SHADOW = 1U << 0,
> +	MSM_SPM_DEBUG_VCTL = 1U << 1,
> +};
> +
> +static int msm_spm_debug_mask;
> +module_param_named(
> +	debug_mask, msm_spm_debug_mask, int, S_IRUGO | S_IWUSR | S_IWGRP
> +);
> +
> +struct saw2_data {
> +	const char *ver_name;
> +	uint32_t major;
> +	uint32_t minor;
> +	uint32_t *spm_reg_offset_ptr;
> +};
> +
> +static uint32_t msm_spm_reg_offsets_saw2_v2_1[MSM_SPM_REG_NR] = {
> +	[MSM_SPM_REG_SAW2_SECURE]		= 0x00,
> +	[MSM_SPM_REG_SAW2_ID]			= 0x04,
> +	[MSM_SPM_REG_SAW2_CFG]			= 0x08,
> +	[MSM_SPM_REG_SAW2_SPM_STS]		= 0x0C,
> +	[MSM_SPM_REG_SAW2_AVS_STS]		= 0x10,
> +	[MSM_SPM_REG_SAW2_PMIC_STS]		= 0x14,
> +	[MSM_SPM_REG_SAW2_RST]			= 0x18,
> +	[MSM_SPM_REG_SAW2_VCTL]			= 0x1C,
> +	[MSM_SPM_REG_SAW2_AVS_CTL]		= 0x20,
> +	[MSM_SPM_REG_SAW2_AVS_LIMIT]		= 0x24,
> +	[MSM_SPM_REG_SAW2_AVS_DLY]		= 0x28,
> +	[MSM_SPM_REG_SAW2_AVS_HYSTERESIS]	= 0x2C,
> +	[MSM_SPM_REG_SAW2_SPM_CTL]		= 0x30,
> +	[MSM_SPM_REG_SAW2_SPM_DLY]		= 0x34,
> +	[MSM_SPM_REG_SAW2_PMIC_DATA_0]		= 0x40,
> +	[MSM_SPM_REG_SAW2_PMIC_DATA_1]		= 0x44,
> +	[MSM_SPM_REG_SAW2_PMIC_DATA_2]		= 0x48,
> +	[MSM_SPM_REG_SAW2_PMIC_DATA_3]		= 0x4C,
> +	[MSM_SPM_REG_SAW2_PMIC_DATA_4]		= 0x50,
> +	[MSM_SPM_REG_SAW2_PMIC_DATA_5]		= 0x54,
> +	[MSM_SPM_REG_SAW2_PMIC_DATA_6]		= 0x58,
> +	[MSM_SPM_REG_SAW2_PMIC_DATA_7]		= 0x5C,
> +	[MSM_SPM_REG_SAW2_SEQ_ENTRY]		= 0x80,
> +	[MSM_SPM_REG_SAW2_VERSION]		= 0xFD0,
> +};
> +
> +static uint32_t msm_spm_reg_offsets_saw2_v3_0[MSM_SPM_REG_NR] = {
> +	[MSM_SPM_REG_SAW2_SECURE]		= 0x00,
> +	[MSM_SPM_REG_SAW2_ID]			= 0x04,
> +	[MSM_SPM_REG_SAW2_CFG]			= 0x08,
> +	[MSM_SPM_REG_SAW2_SPM_STS]		= 0x0C,
> +	[MSM_SPM_REG_SAW2_AVS_STS]		= 0x10,
> +	[MSM_SPM_REG_SAW2_PMIC_STS]		= 0x14,
> +	[MSM_SPM_REG_SAW2_RST]			= 0x18,
> +	[MSM_SPM_REG_SAW2_VCTL]			= 0x1C,
> +	[MSM_SPM_REG_SAW2_AVS_CTL]		= 0x20,
> +	[MSM_SPM_REG_SAW2_AVS_LIMIT]		= 0x24,
> +	[MSM_SPM_REG_SAW2_AVS_DLY]		= 0x28,
> +	[MSM_SPM_REG_SAW2_AVS_HYSTERESIS]	= 0x2C,
> +	[MSM_SPM_REG_SAW2_SPM_CTL]		= 0x30,
> +	[MSM_SPM_REG_SAW2_SPM_DLY]		= 0x34,
> +	[MSM_SPM_REG_SAW2_STS2]			= 0x38,
> +	[MSM_SPM_REG_SAW2_PMIC_DATA_0]		= 0x40,
> +	[MSM_SPM_REG_SAW2_PMIC_DATA_1]		= 0x44,
> +	[MSM_SPM_REG_SAW2_PMIC_DATA_2]		= 0x48,
> +	[MSM_SPM_REG_SAW2_PMIC_DATA_3]		= 0x4C,
> +	[MSM_SPM_REG_SAW2_PMIC_DATA_4]		= 0x50,
> +	[MSM_SPM_REG_SAW2_PMIC_DATA_5]		= 0x54,
> +	[MSM_SPM_REG_SAW2_PMIC_DATA_6]		= 0x58,
> +	[MSM_SPM_REG_SAW2_PMIC_DATA_7]		= 0x5C,
> +	[MSM_SPM_REG_SAW2_SEQ_ENTRY]		= 0x400,
> +	[MSM_SPM_REG_SAW2_VERSION]		= 0xFD0,
> +};
> +
> +static struct saw2_data saw2_info[] = {
> +	[0] = {
> +		"SAW2_v2.1",
> +		2,
> +		1,
> +		msm_spm_reg_offsets_saw2_v2_1,
> +	},
> +	[1] = {
> +		"SAW2_v3.0",
> +		3,
> +		0,
> +		msm_spm_reg_offsets_saw2_v3_0,
> +	},
> +};
> +
> +static uint32_t num_pmic_data;
> +
> +static inline uint32_t msm_spm_drv_get_num_spm_entry(
> +		struct msm_spm_driver_data *dev)
> +{
> +	return 32;
> +}
> +
> +static void msm_spm_drv_flush_shadow(struct msm_spm_driver_data *dev,
> +		unsigned int reg_index)
> +{
> +	__raw_writel(dev->reg_shadow[reg_index],
> +		dev->reg_base_addr + dev->reg_offsets[reg_index]);
> +}
> +
> +static void msm_spm_drv_load_shadow(struct msm_spm_driver_data *dev,
> +		unsigned int reg_index)
> +{
> +	dev->reg_shadow[reg_index] =
> +		__raw_readl(dev->reg_base_addr +
> +				dev->reg_offsets[reg_index]);
> +}
> +
> +static inline void msm_spm_drv_set_start_addr(
> +		struct msm_spm_driver_data *dev, uint32_t addr, bool pc_mode)
> +{
> +	addr &= 0x7F;
> +	addr <<= 4;
> +	dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] &= 0xFFFFF80F;
> +	dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] |= addr;
> +
> +	if (dev->major != 0x3)
> +		return;
> +
> +	dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] &= 0xFFFEFFFF;
> +	if (pc_mode)
> +		dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] |= 0x00010000;
> +}
> +
> +static inline bool msm_spm_pmic_arb_present(struct msm_spm_driver_data *dev)
> +{
> +	msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW2_ID);
> +	return (dev->reg_shadow[MSM_SPM_REG_SAW2_ID] >> 2) & 0x1;
> +}
> +
> +static inline void msm_spm_drv_set_vctl2(struct msm_spm_driver_data *dev,
> +		uint32_t vlevel)
> +{
> +	unsigned int pmic_data = 0;
> +
> +	/**
> +	 * VCTL_PORT has to be 0, for PMIC_STS register to be updated.
> +	 * Ensure that vctl_port is always set to 0.
> +	 */
> +	WARN_ON(dev->vctl_port);
> +
> +	pmic_data |= vlevel;
> +	pmic_data |= (dev->vctl_port & 0x7) << 16;
> +
> +	dev->reg_shadow[MSM_SPM_REG_SAW2_VCTL] &= ~0x700FF;
> +	dev->reg_shadow[MSM_SPM_REG_SAW2_VCTL] |= pmic_data;
> +
> +	dev->reg_shadow[MSM_SPM_REG_SAW2_PMIC_DATA_3] &= ~0x700FF;
> +	dev->reg_shadow[MSM_SPM_REG_SAW2_PMIC_DATA_3] |= pmic_data;
> +
> +	msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW2_VCTL);
> +	msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW2_PMIC_DATA_3);
> +}
> +
> +static inline uint32_t msm_spm_drv_get_num_pmic_data(
> +		struct msm_spm_driver_data *dev)
> +{
> +	msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW2_ID);
> +	mb();
> +	return (dev->reg_shadow[MSM_SPM_REG_SAW2_ID] >> 4) & 0x7;
> +}
> +
> +static inline uint32_t msm_spm_drv_get_sts_pmic_state(
> +		struct msm_spm_driver_data *dev)
> +{
> +	msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW2_PMIC_STS);
> +	return (dev->reg_shadow[MSM_SPM_REG_SAW2_PMIC_STS] >> 16) &
> +				0x03;
> +}
> +
> +uint32_t msm_spm_drv_get_sts_curr_pmic_data(
> +		struct msm_spm_driver_data *dev)
> +{
> +	msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW2_PMIC_STS);
> +	return dev->reg_shadow[MSM_SPM_REG_SAW2_PMIC_STS] & 0xFF;
> +}
> +
> +inline int msm_spm_drv_set_spm_enable(
> +		struct msm_spm_driver_data *dev, bool enable)
> +{
> +	uint32_t value = enable ? 0x01 : 0x00;
> +
> +	if (!dev)
> +		return -EINVAL;
> +
> +	if ((dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] & 0x01) ^ value) {
> +
> +		dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] &= ~0x1;
> +		dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] |= value;
> +
> +		msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW2_SPM_CTL);
> +		wmb();
> +	}
> +	return 0;
> +}
> +void msm_spm_drv_flush_seq_entry(struct msm_spm_driver_data *dev)
> +{
> +	int i;
> +	int num_spm_entry = msm_spm_drv_get_num_spm_entry(dev);
> +
> +	if (!dev) {
> +		__WARN();
> +		return;
> +	}
> +
> +	for (i = 0; i < num_spm_entry; i++) {
> +		__raw_writel(dev->reg_seq_entry_shadow[i],
> +			dev->reg_base_addr
> +			+ dev->reg_offsets[MSM_SPM_REG_SAW2_SEQ_ENTRY]
> +			+ 4 * i);
> +	}
> +	mb();
> +}
> +
> +void dump_regs(struct msm_spm_driver_data *dev, int cpu)
> +{
> +	msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW2_SPM_STS);
> +	mb();
> +	pr_err("CPU%d: spm register MSM_SPM_REG_SAW2_SPM_STS: 0x%x\n", cpu,
> +			dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_STS]);
> +	msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW2_SPM_CTL);
> +	mb();
> +	pr_err("CPU%d: spm register MSM_SPM_REG_SAW2_SPM_CTL: 0x%x\n", cpu,
> +			dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL]);
> +}
> +
> +int msm_spm_drv_write_seq_data(struct msm_spm_driver_data *dev,
> +		uint8_t *cmd, uint32_t *offset)
> +{
> +	uint32_t cmd_w;
> +	uint32_t offset_w = *offset / 4;
> +	uint8_t last_cmd;
> +
> +	if (!cmd)
> +		return -EINVAL;
> +
> +	while (1) {
> +		int i;
> +
> +		cmd_w = 0;
> +		last_cmd = 0;
> +		cmd_w = dev->reg_seq_entry_shadow[offset_w];
> +
> +		for (i = (*offset % 4); i < 4; i++) {
> +			last_cmd = *(cmd++);
> +			cmd_w |=  last_cmd << (i * 8);
> +			(*offset)++;
> +			if (last_cmd == 0x0f)
> +				break;
> +		}
> +
> +		dev->reg_seq_entry_shadow[offset_w++] = cmd_w;
> +		if (last_cmd == 0x0f)
> +			break;
> +	}
> +
> +	return 0;
> +}
> +
> +int msm_spm_drv_set_low_power_mode(struct msm_spm_driver_data *dev,
> +		uint32_t addr, bool pc_mode)
> +{
> +
> +	if (!dev)
> +		return -EINVAL;
> +
> +	msm_spm_drv_set_start_addr(dev, addr, pc_mode);
> +
> +	msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW2_SPM_CTL);
> +	wmb();
> +
> +	if (msm_spm_debug_mask & MSM_SPM_DEBUG_SHADOW) {
> +		int i;
> +
> +		for (i = 0; i < MSM_SPM_REG_NR; i++)
> +			pr_info("%s: reg %02x = 0x%08x\n", __func__,
> +				dev->reg_offsets[i], dev->reg_shadow[i]);
> +	}
> +	msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW2_SPM_STS);
> +
> +	return 0;
> +}
> +
> +int msm_spm_drv_set_vdd(struct msm_spm_driver_data *dev, unsigned int vlevel)
> +{
> +	uint32_t timeout_us, new_level;
> +
> +	if (!dev)
> +		return -EINVAL;
> +
> +	if (!msm_spm_pmic_arb_present(dev))
> +		return -ENOSYS;
> +
> +	if (msm_spm_debug_mask & MSM_SPM_DEBUG_VCTL)
> +		pr_info("%s: requesting vlevel %#x\n", __func__, vlevel);
> +
> +	/* Kick the state machine back to idle */
> +	dev->reg_shadow[MSM_SPM_REG_SAW2_RST] = 1;
> +	msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW2_RST);
> +
> +	msm_spm_drv_set_vctl2(dev, vlevel);
> +
> +	timeout_us = dev->vctl_timeout_us;
> +	/* Confirm the voltage we set was what hardware sent */
> +	do {
> +		new_level = msm_spm_drv_get_sts_curr_pmic_data(dev);
> +		if (new_level == vlevel)
> +			break;
> +		udelay(1);
> +	} while (--timeout_us);
> +	if (!timeout_us) {
> +		pr_info("Wrong level %#x\n", new_leve
> +		goto set_vdd_bail;
> +	}
> +
> +	if (msm_spm_debug_mask & MSM_SPM_DEBUG_VCTL)
> +		pr_info("%s: done, remaining timeout %u us\n",
> +			__func__, timeout_us);
> +
> +	return 0;
> +
> +set_vdd_bail:
> +	pr_err("%s: failed %#x, remaining timeout %uus, vlevel %#x\n",
> +		__func__, vlevel, timeout_us, new_level);
> +	return -EIO;
> +}
> +
> +static int msm_spm_drv_get_pmic_port(struct msm_spm_driver_data *dev,
> +		enum msm_spm_pmic_port port)
> +{
> +	int index = -1;
> +
> +	switch (port) {
> +	case MSM_SPM_PMIC_VCTL_PORT:
> +		index = dev->vctl_port;
> +		break;
> +	case MSM_SPM_PMIC_PHASE_PORT:
> +		index = dev->phase_port;
> +		break;
> +	case MSM_SPM_PMIC_PFM_PORT:
> +		index = dev->pfm_port;
> +		break;
> +	default:
> +		break;
> +	}
> +
> +	return index;
> +}
> +
> +int msm_spm_drv_set_pmic_data(struct msm_spm_driver_data *dev,
> +		enum msm_spm_pmic_port port, unsigned int data)
> +{
> +	unsigned int pmic_data = 0;
> +	unsigned int timeout_us = 0;
> +	int index = 0;
> +
> +	if (!msm_spm_pmic_arb_present(dev))
> +		return -ENOSYS;
> +
> +	index = msm_spm_drv_get_pmic_port(dev, port);
> +	if (index < 0)
> +		return -ENODEV;
> +
> +	pmic_data |= data & 0xFF;
> +	pmic_data |= (index & 0x7) << 16;
> +
> +	dev->reg_shadow[MSM_SPM_REG_SAW2_VCTL] &= ~0x700FF;
> +	dev->reg_shadow[MSM_SPM_REG_SAW2_VCTL] |= pmic_data;
> +	msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW2_VCTL);
> +	mb();
> +
> +	timeout_us = dev->vctl_timeout_us;
> +	/**
> +	 * Confirm the pmic data set was what hardware sent by
> +	 * checking the PMIC FSM state.
> +	 * We cannot use the sts_pmic_data and check it against
> +	 * the value like we do fot set_vdd, since the PMIC_STS
> +	 * is only updated for SAW_VCTL sent with port index 0.
> +	 */
> +	do {
> +		if (msm_spm_drv_get_sts_pmic_state(dev) ==
> +				MSM_SPM_PMIC_STATE_IDLE)
> +			break;
> +		udelay(1);
> +	} while (--timeout_us);
> +
> +	if (!timeout_us) {
> +		pr_err("%s: failed, remaining timeout %u us, data %d\n",
> +				__func__, timeout_us, data);
> +		return -EIO;
> +	}
> +
> +	return 0;
> +}
> +
> +void msm_spm_drv_reinit(struct msm_spm_driver_data *dev)
> +{
> +	int i;
> +
> +	msm_spm_drv_flush_seq_entry(dev);
> +	for (i = 0; i < MSM_SPM_REG_SAW2_PMIC_DATA_0 + num_pmic_data; i++)
> +		msm_spm_drv_flush_shadow(dev, i);
> +
> +	mb();

Why are needed the mb() after calling the msm_spm_drv_flush_shadow 
function ?

> +
> +	for (i = MSM_SPM_REG_NR_INITIALIZE + 1; i < MSM_SPM_REG_NR; i++)
> +		msm_spm_drv_load_shadow(dev, i);
> +}
> +
> +int msm_spm_drv_init(struct msm_spm_driver_data *dev,
> +		struct msm_spm_platform_data *data)
> +{
> +	int i;
> +	int num_spm_entry;
> +	bool found = false;
> +
> +	BUG_ON(!dev || !data);
> +
> +	dev->vctl_port = data->vctl_port;
> +	dev->phase_port = data->phase_port;
> +	dev->pfm_port = data->pfm_port;
> +	dev->reg_base_addr = data->reg_base_addr;
> +	memcpy(dev->reg_shadow, data->reg_init_values,
> +			sizeof(data->reg_init_values));
> +
> +	dev->vctl_timeout_us = data->vctl_timeout_us;
> +
> +	for (i = 0; i < ARRAY_SIZE(saw2_info); i++)
> +		if (dev->major == saw2_info[i].major &&
> +			dev->minor == saw2_info[i].minor) {
> +			pr_debug("%s: Version found\n",
> +					saw2_info[i].ver_name);
> +			dev->reg_offsets = saw2_info[i].spm_reg_offset_ptr;
> +			found = true;
> +			break;
> +		}
> +
> +	if (!found) {
> +		pr_err("%s: No SAW2 version found\n", __func__);
> +		BUG_ON(!found);
> +	}
> +
> +	if (!num_pmic_data)
> +		num_pmic_data = msm_spm_drv_get_num_pmic_data(dev);
> +
> +	num_spm_entry = msm_spm_drv_get_num_spm_entry(dev);
> +
> +	dev->reg_seq_entry_shadow =
> +		kzalloc(sizeof(*dev->reg_seq_entry_shadow) * num_spm_entry,
> +				GFP_KERNEL);
> +
> +	if (!dev->reg_seq_entry_shadow)
> +		return -ENOMEM;
> +
> +	return 0;
> +}
> diff --git a/drivers/soc/qcom/spm_driver.h b/drivers/soc/qcom/spm_driver.h
> new file mode 100644
> index 0000000..b306520
> --- /dev/null
> +++ b/drivers/soc/qcom/spm_driver.h
> @@ -0,0 +1,116 @@
> +/* Copyright (c) 2011-2014, The Linux Foundation. All rights reserved.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 and
> + * only version 2 as published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + * GNU General Public License for more details.
> + */
> +#ifndef __QCOM_SPM_DRIVER_H
> +#define __QCOM_SPM_DRIVER_H
> +
> +#include <soc/qcom/spm.h>
> +
> +enum {
> +	MSM_SPM_REG_SAW2_CFG,
> +	MSM_SPM_REG_SAW2_AVS_CTL,
> +	MSM_SPM_REG_SAW2_AVS_HYSTERESIS,
> +	MSM_SPM_REG_SAW2_SPM_CTL,
> +	MSM_SPM_REG_SAW2_PMIC_DLY,
> +	MSM_SPM_REG_SAW2_AVS_LIMIT,
> +	MSM_SPM_REG_SAW2_AVS_DLY,
> +	MSM_SPM_REG_SAW2_SPM_DLY,
> +	MSM_SPM_REG_SAW2_PMIC_DATA_0,
> +	MSM_SPM_REG_SAW2_PMIC_DATA_1,
> +	MSM_SPM_REG_SAW2_PMIC_DATA_2,
> +	MSM_SPM_REG_SAW2_PMIC_DATA_3,
> +	MSM_SPM_REG_SAW2_PMIC_DATA_4,
> +	MSM_SPM_REG_SAW2_PMIC_DATA_5,
> +	MSM_SPM_REG_SAW2_PMIC_DATA_6,
> +	MSM_SPM_REG_SAW2_PMIC_DATA_7,
> +	MSM_SPM_REG_SAW2_RST,
> +
> +	MSM_SPM_REG_NR_INITIALIZE = MSM_SPM_REG_SAW2_RST,
> +
> +	MSM_SPM_REG_SAW2_ID,
> +	MSM_SPM_REG_SAW2_SECURE,
> +	MSM_SPM_REG_SAW2_STS0,
> +	MSM_SPM_REG_SAW2_STS1,
> +	MSM_SPM_REG_SAW2_STS2,
> +	MSM_SPM_REG_SAW2_VCTL,
> +	MSM_SPM_REG_SAW2_SEQ_ENTRY,
> +	MSM_SPM_REG_SAW2_SPM_STS,
> +	MSM_SPM_REG_SAW2_AVS_STS,
> +	MSM_SPM_REG_SAW2_PMIC_STS,
> +	MSM_SPM_REG_SAW2_VERSION,
> +
> +	MSM_SPM_REG_NR,
> +};
> +
> +struct msm_spm_seq_entry {
> +	uint32_t mode;
> +	uint8_t *cmd;
> +	bool  notify_rpm;
> +};
> +
> +struct msm_spm_platform_data {
> +	void __iomem *reg_base_addr;
> +	uint32_t reg_init_values[MSM_SPM_REG_NR_INITIALIZE];
> +
> +	uint32_t major;
> +	uint32_t minor;
> +	uint32_t vctl_port;
> +	uint32_t phase_port;
> +	uint32_t pfm_port;
> +
> +	uint8_t awake_vlevel;
> +	uint32_t vctl_timeout_us;
> +
> +	uint32_t num_modes;
> +	struct msm_spm_seq_entry *modes;
> +};
> +
> +enum msm_spm_pmic_port {
> +	MSM_SPM_PMIC_VCTL_PORT,
> +	MSM_SPM_PMIC_PHASE_PORT,
> +	MSM_SPM_PMIC_PFM_PORT,
> +};
> +
> +struct msm_spm_driver_data {
> +	uint32_t major;
> +	uint32_t minor;
> +	uint32_t vctl_port;
> +	uint32_t phase_port;
> +	uint32_t pfm_port;
> +	void __iomem *reg_base_addr;
> +	uint32_t vctl_timeout_us;
> +	uint32_t reg_shadow[MSM_SPM_REG_NR];
> +	uint32_t *reg_seq_entry_shadow;
> +	uint32_t *reg_offsets;
> +};
> +
> +int msm_spm_drv_init(struct msm_spm_driver_data *dev,
> +		struct msm_spm_platform_data *data);
> +void msm_spm_drv_reinit(struct msm_spm_driver_data *dev);
> +int msm_spm_drv_set_low_power_mode(struct msm_spm_driver_data *dev,
> +		uint32_t addr, bool pc_mode);
> +int msm_spm_drv_set_vdd(struct msm_spm_driver_data *dev,
> +		unsigned int vlevel);
> +void dump_regs(struct msm_spm_driver_data *dev, int cpu);
> +uint32_t msm_spm_drv_get_sts_curr_pmic_data(
> +		struct msm_spm_driver_data *dev);
> +int msm_spm_drv_write_seq_data(struct msm_spm_driver_data *dev,
> +		uint8_t *cmd, uint32_t *offset);
> +void msm_spm_drv_flush_seq_entry(struct msm_spm_driver_data *dev);
> +int msm_spm_drv_set_spm_enable(struct msm_spm_driver_data *dev,
> +		bool enable);
> +int msm_spm_drv_set_pmic_data(struct msm_spm_driver_data *dev,
> +		enum msm_spm_pmic_port port, unsigned int data);
> +
> +void msm_spm_reinit(void);
> +int msm_spm_init(struct msm_spm_platform_data *data, int nr_devs);
> +
> +#endif /* __QCOM_SPM_DRIVER_H */
> diff --git a/include/soc/qcom/spm.h b/include/soc/qcom/spm.h
> new file mode 100644
> index 0000000..f39e0c4
> --- /dev/null
> +++ b/include/soc/qcom/spm.h
> @@ -0,0 +1,70 @@
> +/* Copyright (c) 2010-2014, The Linux Foundation. All rights reserved.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 and
> + * only version 2 as published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + * GNU General Public License for more details.
> + */
> +
> +#ifndef __QCOM_SPM_H
> +#define __QCOM_SPM_H
> +
> +enum {
> +	MSM_SPM_MODE_DISABLED,
> +	MSM_SPM_MODE_CLOCK_GATING,
> +	MSM_SPM_MODE_RETENTION,
> +	MSM_SPM_MODE_GDHS,
> +	MSM_SPM_MODE_POWER_COLLAPSE,
> +	MSM_SPM_MODE_NR
> +};
> +
> +struct msm_spm_device;
> +
> +#if defined(CONFIG_QCOM_PM)
> +int msm_spm_set_low_power_mode(unsigned int mode, bool notify_rpm);
> +int msm_spm_probe_done(void);
> +int msm_spm_set_vdd(unsigned int cpu, unsigned int vlevel);
> +unsigned int msm_spm_get_vdd(unsigned int cpu);
> +int msm_spm_turn_on_cpu_rail(void __iomem *base, unsigned int val, int cpu);
> +struct msm_spm_device *msm_spm_get_device_by_name(const char *name);
> +int msm_spm_config_low_power_mode(struct msm_spm_device *dev,
> +		unsigned int mode, bool notify_rpm);
> +int msm_spm_device_init(void);
> +bool msm_spm_is_mode_avail(unsigned int mode);
> +void msm_spm_dump_regs(unsigned int cpu);
> +int msm_spm_apcs_set_phase(int cpu, unsigned int phase_cnt);
> +int msm_spm_enable_fts_lpm(int cpu, uint32_t mode);
> +#else /* defined(CONFIG_QCOM_PM) */
> +static inline int msm_spm_set_low_power_mode(unsigned int mode, bool notify_rpm)
> +{ return -ENOSYS; }
> +static inline int msm_spm_probe_done(void)
> +{ return -ENOSYS; }
> +static inline int msm_spm_set_vdd(unsigned int cpu, unsigned int vlevel)
> +{ return -ENOSYS; }
> +static inline unsigned int msm_spm_get_vdd(unsigned int cpu)
> +{ return 0; }
> +static inline int msm_spm_turn_on_cpu_rail(void __iomem *base,
> +		unsigned int val, int cpu)
> +{ return -ENOSYS; }
> +static inline int msm_spm_device_init(void)
> +{ return -ENOSYS; }
> +static void msm_spm_dump_regs(unsigned int cpu) {}
> +static inline int msm_spm_config_low_power_mode(struct msm_spm_device *dev,
> +		unsigned int mode, bool notify_rpm)
> +{ return -ENODEV; }
> +static inline struct msm_spm_device *msm_spm_get_device_by_name(
> +		const char *name)
> +{ return NULL; }
> +static inline bool msm_spm_is_mode_avail(unsigned int mode)
> +{ return false; }
> +static inline int msm_spm_apcs_set_phase(int cpu, unsigned int phase_cnt)
> +{ return -ENOSYS; }
> +static inline int msm_spm_enable_fts_lpm(int cpu, uint32_t mode)
> +{ return -ENOSYS; }
> +#endif  /* defined (CONFIG_QCOM_PM) */
> +
> +#endif  /* __QCOM_SPM_H */
>


-- 
  <http://www.linaro.org/> Linaro.org │ Open source software for ARM SoCs

Follow Linaro:  <http://www.facebook.com/pages/Linaro> Facebook |
<http://twitter.com/#!/linaroorg> Twitter |
<http://www.linaro.org/linaro-blog/> Blog

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

* Re: [PATCH v2 07/10] qcom: msm-pm: Add cpu low power mode functions
  2014-08-12 19:43 ` [PATCH v2 07/10] qcom: msm-pm: Add cpu low power mode functions Lina Iyer
@ 2014-08-13 11:18   ` Daniel Lezcano
  2014-08-13 14:16     ` Lina Iyer
  2014-08-14 13:38   ` Pramod Gurav
  1 sibling, 1 reply; 46+ messages in thread
From: Daniel Lezcano @ 2014-08-13 11:18 UTC (permalink / raw)
  To: Lina Iyer, khilman, amit.kucheria, sboyd, davidb, galak, linux-arm-msm
  Cc: msivasub, Venkat Devarasetty, Nicolas Pitre

On 08/12/2014 09:43 PM, Lina Iyer wrote:
> Add interface layer to abstract and handle hardware specific
> functionality for executing various cpu low power modes in QCOM
> chipsets.
>
> Signed-off-by: Venkat Devarasetty <vdevaras@codeaurora.org>
> Signed-off-by: Mahesh Sivasubramanian <msivasub@codeaurora.org>
> Signed-off-by: Lina Iyer <lina.iyer@linaro.org>
> ---
>   drivers/soc/qcom/Makefile |   2 +-
>   drivers/soc/qcom/msm-pm.c | 219 ++++++++++++++++++++++++++++++++++++++++++++++
>   include/soc/qcom/pm.h     |  39 +++++++++
>   3 files changed, 259 insertions(+), 1 deletion(-)
>   create mode 100644 drivers/soc/qcom/msm-pm.c
>   create mode 100644 include/soc/qcom/pm.h
>
> diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile
> index d7ae93b..7925f83 100644
> --- a/drivers/soc/qcom/Makefile
> +++ b/drivers/soc/qcom/Makefile
> @@ -1,5 +1,5 @@
>   obj-$(CONFIG_QCOM_GSBI)	+=	qcom_gsbi.o
> -obj-$(CONFIG_QCOM_PM) +=	spm-devices.o spm.o
> +obj-$(CONFIG_QCOM_PM) +=	spm-devices.o spm.o msm-pm.o
>
>   CFLAGS_scm.o :=$(call as-instr,.arch_extension sec,-DREQUIRES_SEC=1)
>   obj-$(CONFIG_QCOM_SCM) += scm.o scm-boot.o
> diff --git a/drivers/soc/qcom/msm-pm.c b/drivers/soc/qcom/msm-pm.c
> new file mode 100644
> index 0000000..f2f15b8
> --- /dev/null
> +++ b/drivers/soc/qcom/msm-pm.c
> @@ -0,0 +1,219 @@
> +/* Copyright (c) 2010-2014, The Linux Foundation. All rights reserved.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 and
> + * only version 2 as published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + * GNU General Public License for more details.
> + *
> + */
> +
> +#include <linux/module.h>
> +#include <linux/kernel.h>
> +#include <linux/init.h>
> +#include <linux/io.h>
> +#include <linux/smp.h>
> +#include <linux/tick.h>
> +#include <linux/platform_device.h>
> +#include <linux/cpu_pm.h>
> +#include <linux/uaccess.h>
> +
> +#include <soc/qcom/spm.h>
> +#include <soc/qcom/pm.h>
> +#include <soc/qcom/scm.h>
> +#include <soc/qcom/scm-boot.h>
> +
> +#include <asm/suspend.h>
> +#include <asm/cacheflush.h>
> +#include <asm/cputype.h>
> +#include <asm/system_misc.h>
> +
> +#define SCM_CMD_TERMINATE_PC	(0x2)
> +#define SCM_CMD_CORE_HOTPLUGGED (0x10)
> +#define SCM_FLUSH_FLAG_MASK	(0x3)
> +
> +static bool msm_pm_is_L1_writeback(void)
> +{
> +	u32 cache_id = 0;
> +
> +#if defined(CONFIG_CPU_V7)
> +	u32 sel = 0;
> +
> +	asm volatile ("mcr p15, 2, %[ccselr], c0, c0, 0\n\t"
> +		      "isb\n\t"
> +		      "mrc p15, 1, %[ccsidr], c0, c0, 0\n\t"
> +		      :[ccsidr]"=r" (cache_id)
> +		      :[ccselr]"r" (sel)
> +		     );
> +	return cache_id & BIT(30);
> +#elif defined(CONFIG_ARM64)
> +	u32 sel = 0;
> +	asm volatile("msr csselr_el1, %[ccselr]\n\t"
> +		     "isb\n\t"
> +		     "mrs %[ccsidr],ccsidr_el1\n\t"
> +		     :[ccsidr]"=r" (cache_id)
> +		     :[ccselr]"r" (sel)
> +		    );
> +	return cache_id & BIT(30);
> +#else
> +#error No valid CPU arch selected
> +#endif
> +}
> +
> +static inline void msm_arch_idle(void)
> +{
> +	/* Flush and clock-gate */
> +	mb();

Why is needed this memory barrier ?

> +	wfi();
> +}
> +
> +static bool msm_pm_swfi(bool from_idle)
> +{
> +	msm_arch_idle();
> +	return true;
> +}
> +
> +static bool msm_pm_retention(bool from_idle)
> +{
> +	int ret = 0;
> +
> +	ret = msm_spm_set_low_power_mode(MSM_SPM_MODE_RETENTION, false);
> +	WARN_ON(ret);
> +
> +	msm_arch_idle();
> +
> +	ret = msm_spm_set_low_power_mode(MSM_SPM_MODE_CLOCK_GATING, false);
> +	WARN_ON(ret);

Why do you need to set the clock gating mode each time you exit the 
retention mode ?

> +	return true;
> +}
> +
> +static int msm_pm_collapse(unsigned long from_idle)
> +{
> +	enum msm_pm_l2_scm_flag flag = MSM_SCM_L2_ON;
> +
> +	/**
> +	 * Single core processors need to have L2
> +	 * flushed when powering down the core.
> +	 * Notify SCM to flush secure L2 lines.
> +	 */
> +	if (num_possible_cpus() == 1)
> +		flag = MSM_SCM_L2_OFF;

I am wondering if this shouldn't be handle by a mcpm driver.

Cc nico.

> +	if (flag == MSM_SCM_L2_OFF)
> +		flush_cache_all();
> +	else if (msm_pm_is_L1_writeback())
> +		flush_cache_louis();
> +
> +	flag &= SCM_FLUSH_FLAG_MASK;
> +	if (!from_idle)
> +		flag |= SCM_CMD_CORE_HOTPLUGGED;
> +
> +	scm_call_atomic1(SCM_SVC_BOOT, SCM_CMD_TERMINATE_PC, flag);
> +
> +	return 0;
> +}
> +
> +static void set_up_boot_address(void *entry, int cpu)
> +{
> +	static int flags[NR_CPUS] = {
> +		SCM_FLAG_WARMBOOT_CPU0,
> +		SCM_FLAG_WARMBOOT_CPU1,
> +		SCM_FLAG_WARMBOOT_CPU2,
> +		SCM_FLAG_WARMBOOT_CPU3,
> +	};
> +	static DEFINE_PER_CPU(void *, last_known_entry);
> +
> +	if (entry == per_cpu(last_known_entry, cpu))
> +		return;
> +
> +	per_cpu(last_known_entry, cpu) = entry;
> +	scm_set_boot_addr(virt_to_phys(entry), flags[cpu]);
> +}
> +
> +static bool __ref msm_pm_spm_power_collapse(unsigned int cpu, bool from_idle)
> +{
> +	void *entry;
> +	bool collapsed = 0;
> +	int ret;
> +	bool save_cpu_regs = (cpu_online(cpu) || from_idle);
> +
> +	if (from_idle)
> +		cpu_pm_enter();
> +
> +	ret = msm_spm_set_low_power_mode(MSM_SPM_MODE_POWER_COLLAPSE, false);
> +	WARN_ON(ret);
> +
> +	entry = save_cpu_regs ? cpu_resume : secondary_startup;
> +	set_up_boot_address(entry, cpu);
> +
> +#ifdef CONFIG_CPU_V7
> +	collapsed = !cpu_suspend(from_idle, msm_pm_collapse);
> +#else
> +	collapsed = !cpu_suspend(0);
> +#endif
> +
> +	if (collapsed)
> +		local_fiq_enable();
> +
> +	if (from_idle)
> +		cpu_pm_exit();
> +
> +	ret = msm_spm_set_low_power_mode(MSM_SPM_MODE_CLOCK_GATING, false);
> +	WARN_ON(ret);
> +
> +	return collapsed;
> +}
> +
> +static bool msm_pm_power_collapse_standalone(bool from_idle)
> +{
> +	unsigned int cpu = smp_processor_id();
> +	bool collapsed;
> +
> +	collapsed = msm_pm_spm_power_collapse(cpu, from_idle);
> +
> +	return collapsed;
> +}
> +
> +static bool msm_pm_power_collapse(bool from_idle)
> +{
> +	unsigned int cpu = smp_processor_id();
> +	bool collapsed;
> +
> +	collapsed = msm_pm_spm_power_collapse(cpu, from_idle);
> +
> +	return collapsed;
> +}
> +
> +static bool (*execute[MSM_PM_SLEEP_MODE_NR])(bool idle) = {
> +	[MSM_PM_SLEEP_MODE_WAIT_FOR_INTERRUPT] = msm_pm_swfi,
> +	[MSM_PM_SLEEP_MODE_POWER_COLLAPSE_STANDALONE] =
> +		msm_pm_power_collapse_standalone,
> +	[MSM_PM_SLEEP_MODE_RETENTION] = msm_pm_retention,
> +	[MSM_PM_SLEEP_MODE_POWER_COLLAPSE] = msm_pm_power_collapse,
> +};
> +
> +/**
> + * msm_cpu_pm_enter_sleep(): Enter a low power mode on current cpu
> + *
> + * @mode - sleep mode to enter
> + * @from_idle - bool to indicate that the mode is exercised during idle/suspend
> + *
> + * The code should be with interrupts disabled and on the core on which the
> + * low power is to be executed.
> + *
> + */
> +bool msm_cpu_pm_enter_sleep(enum msm_pm_sleep_mode mode, bool from_idle)
> +{
> +	bool exit_stat = false;
> +
> +	if (execute[mode])
> +		exit_stat = execute[mode](from_idle);
> +
> +	local_irq_enable();
> +	return exit_stat;
> +}
> +EXPORT_SYMBOL(msm_cpu_pm_enter_sleep);
> diff --git a/include/soc/qcom/pm.h b/include/soc/qcom/pm.h
> new file mode 100644
> index 0000000..01872ad
> --- /dev/null
> +++ b/include/soc/qcom/pm.h
> @@ -0,0 +1,39 @@
> +/*
> + * Copyright (c) 2009-2014, The Linux Foundation. All rights reserved.
> + *
> + * This software is licensed under the terms of the GNU General Public
> + * License version 2, as published by the Free Software Foundation, and
> + * may be copied, distributed, and modified under those terms.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + * GNU General Public License for more details.
> + *
> + */
> +
> +#ifndef __QCOM_PM_H
> +#define __QCOM_PM_H
> +
> +enum msm_pm_sleep_mode {
> +	MSM_PM_SLEEP_MODE_WAIT_FOR_INTERRUPT,
> +	MSM_PM_SLEEP_MODE_RETENTION,
> +	MSM_PM_SLEEP_MODE_POWER_COLLAPSE_STANDALONE,
> +	MSM_PM_SLEEP_MODE_POWER_COLLAPSE,
> +	MSM_PM_SLEEP_MODE_NR,
> +};
> +
> +enum msm_pm_l2_scm_flag {
> +	MSM_SCM_L2_ON = 0,
> +	MSM_SCM_L2_OFF = 1,
> +};
> +
> +#ifdef CONFIG_QCOM_PM
> +bool msm_cpu_pm_enter_sleep(enum msm_pm_sleep_mode mode, bool from_idle);
> +#else
> +static inline bool msm_cpu_pm_enter_sleep(enum msm_pm_sleep_mode mode,
> +						bool from_idle)
> +{ return true; }
> +#endif
> +
> +#endif  /* __QCOM_PM_H */
>


-- 
  <http://www.linaro.org/> Linaro.org │ Open source software for ARM SoCs

Follow Linaro:  <http://www.facebook.com/pages/Linaro> Facebook |
<http://twitter.com/#!/linaroorg> Twitter |
<http://www.linaro.org/linaro-blog/> Blog

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

* Re: [PATCH v2 09/10] qcom: cpuidle: Config option to enable QCOM cpuidle driver
  2014-08-12 19:43 ` [PATCH v2 09/10] qcom: cpuidle: Config option to enable QCOM cpuidle driver Lina Iyer
@ 2014-08-13 11:18   ` Daniel Lezcano
  0 siblings, 0 replies; 46+ messages in thread
From: Daniel Lezcano @ 2014-08-13 11:18 UTC (permalink / raw)
  To: Lina Iyer, khilman, amit.kucheria, sboyd, davidb, galak, linux-arm-msm
  Cc: msivasub

On 08/12/2014 09:43 PM, Lina Iyer wrote:
> Allow cpuidle framework to determine the C-State of the QCOM cpus.
>
> Signed-off-by: Lina Iyer <lina.iyer@linaro.org>
> ---
>   drivers/cpuidle/Kconfig.arm | 6 ++++++
>   1 file changed, 6 insertions(+)
>
> diff --git a/drivers/cpuidle/Kconfig.arm b/drivers/cpuidle/Kconfig.arm
> index 38cff69..ad52605 100644
> --- a/drivers/cpuidle/Kconfig.arm
> +++ b/drivers/cpuidle/Kconfig.arm
> @@ -62,3 +62,9 @@ config ARM_MVEBU_V7_CPUIDLE
>   	depends on ARCH_MVEBU
>   	help
>   	  Select this to enable cpuidle on Armada 370, 38x and XP processors.
> +
> +config ARM_QCOM_CPUIDLE
> +	bool "CPU Idle drivers for Qualcomm processors"
> +	depends on QCOM_PM
> +	help
> +	  Select this to enable cpuidle for QCOM processors

Please fold it with the patch 08/10.


-- 
  <http://www.linaro.org/> Linaro.org │ Open source software for ARM SoCs

Follow Linaro:  <http://www.facebook.com/pages/Linaro> Facebook |
<http://twitter.com/#!/linaroorg> Twitter |
<http://www.linaro.org/linaro-blog/> Blog

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

* Re: [PATCH v2 08/10] qcom: cpuidle: Add cpuidle driver for QCOM cpus
  2014-08-12 19:43 ` [PATCH v2 08/10] qcom: cpuidle: Add cpuidle driver for QCOM cpus Lina Iyer
@ 2014-08-13 11:22   ` Daniel Lezcano
  2014-08-13 14:03     ` Lina Iyer
  0 siblings, 1 reply; 46+ messages in thread
From: Daniel Lezcano @ 2014-08-13 11:22 UTC (permalink / raw)
  To: Lina Iyer, khilman, amit.kucheria, sboyd, davidb, galak, linux-arm-msm
  Cc: msivasub, Lorenzo Pieralisi

On 08/12/2014 09:43 PM, Lina Iyer wrote:
> Add cpuidle driver interface to allow cpus to go into C-States.
>
> Signed-off-by: Lina Iyer <lina.iyer@linaro.org>

Hi Lina,

I think you should based this driver on top of Lorenzo's work.

Cc Lorenzo.

> ---
>   .../devicetree/bindings/arm/msm/qcom,cpuidle.txt   |  73 +++++++++++
>   drivers/cpuidle/Makefile                           |   1 +
>   drivers/cpuidle/cpuidle-qcom.c                     | 140 +++++++++++++++++++++
>   3 files changed, 214 insertions(+)
>   create mode 100644 Documentation/devicetree/bindings/arm/msm/qcom,cpuidle.txt
>   create mode 100644 drivers/cpuidle/cpuidle-qcom.c
>
> diff --git a/Documentation/devicetree/bindings/arm/msm/qcom,cpuidle.txt b/Documentation/devicetree/bindings/arm/msm/qcom,cpuidle.txt
> new file mode 100644
> index 0000000..b094baf
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/arm/msm/qcom,cpuidle.txt
> @@ -0,0 +1,73 @@
> +Qualcomm CPUIdle driver
> +
> +The Qualcomm cpuidle driver enables the processor to enter low power modes
> +when idle. The processors support 4 low power modes.
> +	wfi - also known as clock gating
> +	retention - processor clock gated and processor power is reduced.
> +	standalone-pc - processor is powered down and when reset, the core
> +		boots into secure mode and trampolines back to the kernel.
> +		Every core can individually enter this low power mode without
> +		affecting the state of the other cores.
> +	pc - essentially standalone power collapse, but indicates that the
> +		latency to put SoC into a low power state is tolerable.
> +
> +The cpuidle node is comprised of nodes, each of which represent a C-State the
> +processor can achieve. Each node provides the latency and residency which
> +helps the cpuidle governor to choose the appropriate low power mode based on
> +the time available. Not all SoCs may support all the above low power modes.
> +
> +PROPERTIES
> +
> +- compatible:
> +	Usage: required
> +	Value type: <string>
> +	Definition: Should be "qcom,cpuidle"
> +
> +- qcom,cpu-level:
> +	Usage: required
> +	Value type: { Node }
> +	Definition: Describes a C-State of the processor
> +
> +	PROPERTIES of qcom,cpu-level
> +
> +	- reg:
> +		Usage: required
> +		Value type: <integer>
> +		Definition: Index of the C-State
> +
> +	- qcom,state-name:
> +		Usage: required
> +		Value type: <string>
> +		Definition: C-State moniker
> +
> +	- qcom,spm-cpu-mode:
> +		Usage: required
> +		Value type: <string>
> +		Definition: The description of the h/w mode that will be
> +			achieved in this C-State.
> +
> +	- qcom,latency-us:
> +		Usage: required
> +		Value type: <integer>
> +		Defintion: Time taken to exit from the C-State
> +
> +	- qcom,residency-us:
> +		Usage: required
> +		Value type: <integer>
> +		Defintion: Time to be spent in this C-State for the power
> +			saving to be beneficial.
> +
> +Example:
> +
> +	qcom,cpuidle {
> +		compatible = "qcom,cpuidle";
> +		#address-cells = <1>;
> +		#size-cells = <0>;
> +		qcom,cpu-level@0 {
> +			reg = <0x0>;
> +			qcom,state-name = "C1";
> +			qcom,spm-cpu-mode = "wfi";
> +			qcom,latency-us = <1>;
> +			qcom,residency-us = <1>;
> +		};
> +	};
> diff --git a/drivers/cpuidle/Makefile b/drivers/cpuidle/Makefile
> index 11edb31..4a2c446 100644
> --- a/drivers/cpuidle/Makefile
> +++ b/drivers/cpuidle/Makefile
> @@ -16,6 +16,7 @@ obj-$(CONFIG_ARM_ZYNQ_CPUIDLE)		+= cpuidle-zynq.o
>   obj-$(CONFIG_ARM_U8500_CPUIDLE)         += cpuidle-ux500.o
>   obj-$(CONFIG_ARM_AT91_CPUIDLE)          += cpuidle-at91.o
>   obj-$(CONFIG_ARM_EXYNOS_CPUIDLE)        += cpuidle-exynos.o
> +obj-$(CONFIG_ARM_QCOM_CPUIDLE)		+= cpuidle-qcom.o
>
>   ###############################################################################
>   # MIPS drivers
> diff --git a/drivers/cpuidle/cpuidle-qcom.c b/drivers/cpuidle/cpuidle-qcom.c
> new file mode 100644
> index 0000000..8e70a88
> --- /dev/null
> +++ b/drivers/cpuidle/cpuidle-qcom.c
> @@ -0,0 +1,140 @@
> +/* Copyright (c) 2014, The Linux Foundation. All rights reserved.
> + * Copyright (c) 2014 Linaro.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 and
> + * only version 2 as published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + * GNU General Public License for more details.
> + *
> + */
> +
> +#include <linux/module.h>
> +#include <linux/platform_device.h>
> +#include <linux/of.h>
> +#include <linux/cpuidle.h>
> +
> +#include <soc/qcom/pm.h>
> +
> +struct lookup {
> +	enum msm_pm_sleep_mode mode;
> +	char *name;
> +};
> +
> +static enum msm_pm_sleep_mode spm_sleep_modes[MSM_PM_SLEEP_MODE_NR];
> +
> +static int qcom_lpm_enter(struct cpuidle_device *dev,
> +				struct cpuidle_driver *drv,
> +				int index)
> +{
> +	return msm_cpu_pm_enter_sleep(spm_sleep_modes[index], true);
> +}
> +
> +static struct cpuidle_driver qcom_cpuidle_driver = {
> +	.name	= "qcom_cpuidle",
> +	.owner	= THIS_MODULE,
> +};
> +
> +static int qcom_cpuidle_probe(struct platform_device *pdev)
> +{
> +	int ret;
> +	struct device_node *top = pdev->dev.of_node;
> +	struct device_node *n;
> +	char *key;
> +	const char *val;
> +	int index = 0;
> +	int i;
> +	struct cpuidle_state *state;
> +	static const struct lookup pm_sm_lookup[] = {
> +		{MSM_PM_SLEEP_MODE_WAIT_FOR_INTERRUPT,
> +			"wfi"},
> +		{MSM_PM_SLEEP_MODE_POWER_COLLAPSE_STANDALONE,
> +			"standalone_pc"},
> +		{MSM_PM_SLEEP_MODE_POWER_COLLAPSE,
> +			"pc"},
> +		{MSM_PM_SLEEP_MODE_RETENTION,
> +			"retention"},
> +	};
> +
> +	if (!top)
> +		return -ENODEV;
> +
> +	for_each_child_of_node(top, n) {
> +		key = "qcom,cpu-level";
> +		if (of_node_cmp(n->name, key))
> +			continue;
> +
> +		state = &qcom_cpuidle_driver.states[index];
> +
> +		key = "qcom,spm-cpu-mode";
> +		ret = of_property_read_string(n, key, &val);
> +		if (ret)
> +			goto failed;
> +		for (i = 0; i < ARRAY_SIZE(pm_sm_lookup); i++) {
> +			if (!strcmp(val, pm_sm_lookup[i].name)) {
> +				spm_sleep_modes[index] = pm_sm_lookup[i].mode;
> +				break;
> +			}
> +		}
> +		if (i == ARRAY_SIZE(pm_sm_lookup)) {
> +			ret = -EFAULT;
> +			goto failed;
> +		}
> +
> +		strncpy(state->desc, val, CPUIDLE_DESC_LEN);
> +
> +		key = "qcom,state-name";
> +		ret = of_property_read_string(n, key, &val);
> +		if (ret)
> +			goto failed;
> +		strncpy(state->name, val, CPUIDLE_NAME_LEN);
> +
> +		key = "qcom,latency-us";
> +		ret = of_property_read_u32(n, key, &state->exit_latency);
> +		if (ret)
> +			goto failed;
> +
> +		key = "qcom,residency-us";
> +		ret = of_property_read_u32(n, key, &state->target_residency);
> +		if (ret)
> +			goto failed;
> +
> +		state->flags = CPUIDLE_FLAG_TIME_VALID;
> +		state->enter = qcom_lpm_enter;
> +		index++;
> +	}
> +
> +	qcom_cpuidle_driver.state_count = index;
> +	qcom_cpuidle_driver.safe_state_index = 0;
> +
> +	ret = cpuidle_register(&qcom_cpuidle_driver, NULL);
> +	if (ret) {
> +		dev_err(&pdev->dev, "failed to register cpuidle driver\n");
> +		return ret;
> +	}
> +
> +	return 0;
> +
> +failed:
> +	dev_err(&pdev->dev, "error parsing key: %s\n", key);
> +	return ret;
> +}
> +
> +static struct of_device_id qcom_cpuidle_match_tbl[] = {
> +	{.compatible = "qcom,cpuidle"},
> +	{},
> +};
> +
> +static struct platform_driver qcom_cpuidle_platform_driver = {
> +	.probe	= qcom_cpuidle_probe,
> +	.driver	= {
> +		.name = "qcom,cpuidle",
> +		.owner = THIS_MODULE,
> +		.of_match_table = qcom_cpuidle_match_tbl,
> +	},
> +};
> +
> +module_platform_driver(qcom_cpuidle_platform_driver);
>


-- 
  <http://www.linaro.org/> Linaro.org │ Open source software for ARM SoCs

Follow Linaro:  <http://www.facebook.com/pages/Linaro> Facebook |
<http://twitter.com/#!/linaroorg> Twitter |
<http://www.linaro.org/linaro-blog/> Blog

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

* Re: [PATCH v2 03/10] qcom: spm: Add Subsystem Power Manager (SPM) driver for QCOM chipsets
  2014-08-13 10:49   ` Daniel Lezcano
@ 2014-08-13 14:00     ` Lina Iyer
  0 siblings, 0 replies; 46+ messages in thread
From: Lina Iyer @ 2014-08-13 14:00 UTC (permalink / raw)
  To: Daniel Lezcano
  Cc: khilman, amit.kucheria, sboyd, davidb, galak, linux-arm-msm,
	msivasub, Praveen Chidamabram, Murali Nalajala

On Wed, Aug 13, 2014 at 12:49:23PM +0200, Daniel Lezcano wrote:
>On 08/12/2014 09:43 PM, Lina Iyer wrote:
>>Qualcomm chipsets use an separate h/w block to control the logic around
>>the processor cores (cpu and L2). The SPM h/w block regulates power to
>>the cores and controls the power when the core enter low power modes.
>>
>>Each core has its own instance of SPM. The SPM has the following key
>>functions
>>	- Configure the h/w dependencies when entering low power modes
>>	- Wait for interrupt and wake up on interrupt
>>	- Ensure the dependencies are ready before bringing the core out
>>	  of sleep
>>	- Regulating voltage to the core, interfacing with the PMIC.
>>	- Optimize power based on runtime recommendations.
>>
>>The driver identifies and configures the SPMs, by reading the nodes and
>>the register values from the devicetree. The SPMs need to be configured
>>to allow the processor to be idled in a low power state.
>
>I began to comment but I realize I have a lot of questions and 
>comments for this patch and because of its size, it will be impossible 
>to follow a discussion. This patch is really too big to review, please 
>split it into smaller chunks.
>
>Thanks
>
>  -- Daniel
Hmm.. Tedious, but not impossible. Will get right on it.

>
>>Signed-off-by: Praveen Chidamabram <pchidamb@codeaurora.org>
>>Signed-off-by: Murali Nalajala <mnalajal@codeaurora.org>
>>Signed-off-by: Lina Iyer <lina.iyer@linaro.org>
>>---
>>  .../devicetree/bindings/arm/msm/spm-v2.txt         |  62 ++
>>  drivers/soc/qcom/Makefile                          |   2 +
>>  drivers/soc/qcom/spm-devices.c                     | 703 +++++++++++++++++++++
>>  drivers/soc/qcom/spm.c                             | 482 ++++++++++++++
>>  drivers/soc/qcom/spm_driver.h                      | 116 ++++
>>  include/soc/qcom/spm.h                             |  70 ++
>>  6 files changed, 1435 insertions(+)
>>  create mode 100644 Documentation/devicetree/bindings/arm/msm/spm-v2.txt
>>  create mode 100644 drivers/soc/qcom/spm-devices.c
>>  create mode 100644 drivers/soc/qcom/spm.c
>>  create mode 100644 drivers/soc/qcom/spm_driver.h
>>  create mode 100644 include/soc/qcom/spm.h
>>
>>diff --git a/Documentation/devicetree/bindings/arm/msm/spm-v2.txt b/Documentation/devicetree/bindings/arm/msm/spm-v2.txt
>>new file mode 100644
>>index 0000000..3130f4b
>>--- /dev/null
>>+++ b/Documentation/devicetree/bindings/arm/msm/spm-v2.txt
>>@@ -0,0 +1,62 @@
>>+* MSM Subsystem Power Manager (spm-v2)
>>+
>>+S4 generation of MSMs have SPM hardware blocks to control the Application
>>+Processor Sub-System power. These SPM blocks run individual state machine
>>+to determine what the core (L2 or Krait/Scorpion) would do when the WFI
>>+instruction is executed by the core.
>>+
>>+The devicetree representation of the SPM block should be:
>>+
>>+Required properties
>>+
>>+- compatible: Could be one of -
>>+		"qcom,spm-v2.1"
>>+		"qcom,spm-v3.0"
>>+- reg: The physical address and the size of the SPM's memory mapped registers
>>+- qcom,cpu: phandle for the CPU that the SPM block is attached to. On targets
>>+	that dont support CPU phandles the driver would support qcom,core-id.
>>+	This field is required on only for SPMs that control the CPU.
>>+- qcom,saw2-cfg: SAW2 configuration register
>>+- qcom,saw2-spm-dly: Provides the values for the SPM delay command in the SPM
>>+	sequence
>>+- qcom,saw2-spm-ctl: The SPM control register
>>+- qcom,name: The name with which a SPM device is identified by the power
>>+	management code.
>>+
>>+Optional properties
>>+
>>+- qcom,saw2-pmic-data0..7: Specify the pmic data value and the associated FTS
>>+	(Fast Transient Switch) index to send the PMIC data to
>>+- qcom,vctl-port: The PVC (PMIC Virtual Channel) port used for changing
>>+	voltage
>>+- qcom,phase-port: The PVC port used for changing the number of phases
>>+- qcom,pfm-port: The PVC port used for enabling PWM/PFM modes
>>+- qcom,saw2-spm-cmd-wfi: The WFI command sequence
>>+- qcom,saw2-spm-cmd-ret: The Retention command sequence
>>+- qcom,saw2-spm-cmd-spc: The Standalone PC command sequence
>>+- qcom,saw2-spm-cmd-pc-no-rpm: The Power Collapse command sequence where APPS
>>+	proc won't inform the RPM.
>>+- qcom,saw2-spm-cmd-pc: The Power Collapse command sequence. This sequence may
>>+	turn off other SoC components.
>>+- qcom,saw2-spm-cmd-gdhs: GDHS (Globally Distributed Head Switch) command
>>+	sequence. This sequence will retain the memory but turn off the logic.
>>+- qcom,cpu-vctl-list: List of cpu node phandles, whose voltage the spm device
>>+	can control.
>>+- qcom,vctl-timeout-us: The timeout value in microseconds to wait for voltage to
>>+	change after sending the voltage command to the PMIC.
>>+-
>>+Example:
>>+	qcom,spm@f9089000 {
>>+		compatible = "qcom,spm-v2";
>>+		#address-cells = <1>;
>>+		#size-cells = <1>;
>>+		reg = <0xf9089000 0x1000>;
>>+		qcom,cpu = <&CPU0>;
>>+		qcom,saw2-cfg = <0x1>;
>>+		qcom,saw2-spm-dly= <0x20000400>;
>>+		qcom,saw2-spm-ctl = <0x1>;
>>+		qcom,saw2-spm-cmd-wfi = [03 0b 0f];
>>+		qcom,saw2-spm-cmd-spc = [00 20 50 80 60 70 10 92
>>+				a0 b0 03 68 70 3b 92 a0 b0
>>+				82 2b 50 10 30 02 22 30 0f];
>>+	};
>>diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile
>>index 70d52ed..d7ae93b 100644
>>--- a/drivers/soc/qcom/Makefile
>>+++ b/drivers/soc/qcom/Makefile
>>@@ -1,3 +1,5 @@
>>  obj-$(CONFIG_QCOM_GSBI)	+=	qcom_gsbi.o
>>+obj-$(CONFIG_QCOM_PM) +=	spm-devices.o spm.o
>>+
>>  CFLAGS_scm.o :=$(call as-instr,.arch_extension sec,-DREQUIRES_SEC=1)
>>  obj-$(CONFIG_QCOM_SCM) += scm.o scm-boot.o
>>diff --git a/drivers/soc/qcom/spm-devices.c b/drivers/soc/qcom/spm-devices.c
>>new file mode 100644
>>index 0000000..567e9f9
>>--- /dev/null
>>+++ b/drivers/soc/qcom/spm-devices.c
>>@@ -0,0 +1,703 @@
>>+/* Copyright (c) 2011-2014, The Linux Foundation. All rights reserved.
>>+ *
>>+ * This program is free software; you can redistribute it and/or modify
>>+ * it under the terms of the GNU General Public License version 2 and
>>+ * only version 2 as published by the Free Software Foundation.
>>+ *
>>+ * This program is distributed in the hope that it will be useful,
>>+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
>>+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
>>+ * GNU General Public License for more details.
>>+ *
>>+ */
>>+
>>+#include <linux/module.h>
>>+#include <linux/kernel.h>
>>+#include <linux/delay.h>
>>+#include <linux/init.h>
>>+#include <linux/io.h>
>>+#include <linux/slab.h>
>>+#include <linux/of.h>
>>+#include <linux/of_address.h>
>>+#include <linux/err.h>
>>+#include <linux/platform_device.h>
>>+#include <linux/err.h>
>>+
>>+#include <soc/qcom/spm.h>
>>+
>>+#include "spm_driver.h"
>>+
>>+#define VDD_DEFAULT 0xDEADF00D
>>+
>>+struct msm_spm_power_modes {
>>+	uint32_t mode;
>>+	bool notify_rpm;
>>+	uint32_t start_addr;
>>+};
>>+
>>+struct msm_spm_device {
>>+	struct list_head list;
>>+	bool initialized;
>>+	const char *name;
>>+	struct msm_spm_driver_data reg_data;
>>+	struct msm_spm_power_modes *modes;
>>+	uint32_t num_modes;
>>+	uint32_t cpu_vdd;
>>+	struct cpumask mask;
>>+	void __iomem *q2s_reg;
>>+};
>>+
>>+struct msm_spm_vdd_info {
>>+	struct msm_spm_device *vctl_dev;
>>+	uint32_t vlevel;
>>+	int err;
>>+};
>>+
>>+static LIST_HEAD(spm_list);
>>+static DEFINE_PER_CPU_SHARED_ALIGNED(struct msm_spm_device, msm_cpu_spm_device);
>>+static DEFINE_PER_CPU(struct msm_spm_device *, cpu_vctl_device);
>>+
>>+static void msm_spm_smp_set_vdd(void *data)
>>+{
>>+	struct msm_spm_vdd_info *info = (struct msm_spm_vdd_info *)data;
>>+	struct msm_spm_device *dev = info->vctl_dev;
>>+
>>+	dev->cpu_vdd = info->vlevel;
>>+	info->err = msm_spm_drv_set_vdd(&dev->reg_data, info->vlevel);
>>+}
>>+
>>+/**
>>+ * msm_spm_probe_done(): Verify and return the status of the cpu(s) and l2
>>+ * probe.
>>+ * Return: 0 if all spm devices have been probed, else return -EPROBE_DEFER.
>>+ * if probe failed, then return the err number for that failure.
>>+ */
>>+int msm_spm_probe_done(void)
>>+{
>>+	struct msm_spm_device *dev;
>>+	int cpu;
>>+	int ret = 0;
>>+
>>+	for_each_possible_cpu(cpu) {
>>+		dev = per_cpu(cpu_vctl_device, cpu);
>>+		if (!dev)
>>+			return -EPROBE_DEFER;
>>+
>>+		ret = IS_ERR(dev);
>>+		if (ret)
>>+			return ret;
>>+	}
>>+
>>+	return 0;
>>+}
>>+EXPORT_SYMBOL(msm_spm_probe_done);
>
>Can you explain how this function is used by the caller ? When is it 
>called ? What is its purpose and who is setting 'dev' to an ERR ?
>
>>+void msm_spm_dump_regs(unsigned int cpu)
>>+{
>>+	dump_regs(&per_cpu(msm_cpu_spm_device, cpu).reg_data, cpu);
>>+}
>>+
>>+/**
>>+ * msm_spm_set_vdd(): Set core voltage
>>+ * @cpu: core id
>>+ * @vlevel: Encoded PMIC data.
>>+ *
>>+ * Return: 0 on success or -(ERRNO) on failure.
>>+ */
>>+int msm_spm_set_vdd(unsigned int cpu, unsigned int vlevel)
>>+{
>>+	struct msm_spm_vdd_info info;
>>+	struct msm_spm_device *dev = per_cpu(cpu_vctl_device, cpu);
>>+	int ret;
>>+
>>+	if (!dev)
>>+		return -EPROBE_DEFER;
>>+
>>+	ret = IS_ERR(dev);
>>+	if (ret)
>>+		return ret;
>>+
>>+	info.vctl_dev = dev;
>>+	info.vlevel = vlevel;
>>+
>>+	ret = smp_call_function_any(&dev->mask, msm_spm_smp_set_vdd, &info,
>>+					true);
>>+	if (ret)
>>+		return ret;
>
>If cpu_vctl_device is per cpu, why a cpumask is used ?
>
>>+
>>+	return info.err;
>>+}
>>+EXPORT_SYMBOL(msm_spm_set_vdd);
>>+
>>+/**
>>+ * msm_spm_get_vdd(): Get core voltage
>>+ * @cpu: core id
>>+ * @return: Returns encoded PMIC data.
>>+ */
>>+unsigned int msm_spm_get_vdd(unsigned int cpu)
>>+{
>>+	int ret;
>>+	struct msm_spm_device *dev = per_cpu(cpu_vctl_device, cpu);
>>+
>>+	if (!dev)
>>+		return -EPROBE_DEFER;
>>+
>>+	ret = IS_ERR(dev);
>>+	if (ret)
>>+		return ret;
>>+
>>+	return dev->cpu_vdd;
>>+}
>>+EXPORT_SYMBOL(msm_spm_get_vdd);
>>+
>>+static void msm_spm_config_q2s(struct msm_spm_device *dev, unsigned int mode)
>>+{
>>+	uint32_t spm_legacy_mode = 0;
>>+	uint32_t qchannel_ignore = 0;
>>+	uint32_t val = 0;
>>+
>>+	if (!dev->q2s_reg)
>>+		return;
>>+
>>+	switch (mode) {
>>+	case MSM_SPM_MODE_DISABLED:
>>+	case MSM_SPM_MODE_CLOCK_GATING:
>>+		qchannel_ignore = 1;
>>+		spm_legacy_mode = 0;
>>+		break;
>>+	case MSM_SPM_MODE_RETENTION:
>>+		qchannel_ignore = 0;
>>+		spm_legacy_mode = 0;
>>+		break;
>>+	case MSM_SPM_MODE_GDHS:
>>+	case MSM_SPM_MODE_POWER_COLLAPSE:
>>+		qchannel_ignore = 0;
>>+		spm_legacy_mode = 1;
>>+		break;
>>+	default:
>>+		break;
>>+	}
>>+
>>+	val = spm_legacy_mode << 2 | qchannel_ignore << 1;
>>+	__raw_writel(val, dev->q2s_reg);
>>+	mb();
>>+}
>>+
>>+static int msm_spm_dev_set_low_power_mode(struct msm_spm_device *dev,
>>+		unsigned int mode, bool notify_rpm)
>>+{
>>+	uint32_t i;
>>+	uint32_t start_addr = 0;
>>+	int ret = -EINVAL;
>>+	bool pc_mode = false;
>>+
>>+	if (!dev->initialized)
>>+		return -ENXIO;
>>+
>>+	if ((mode == MSM_SPM_MODE_POWER_COLLAPSE)
>>+			|| (mode == MSM_SPM_MODE_GDHS))
>>+		pc_mode = true;
>>+
>>+	if (mode == MSM_SPM_MODE_DISABLED) {
>>+		ret = msm_spm_drv_set_spm_enable(&dev->reg_data, false);
>>+	} else if (!msm_spm_drv_set_spm_enable(&dev->reg_data, true)) {
>>+		for (i = 0; i < dev->num_modes; i++) {
>>+			if ((dev->modes[i].mode == mode) &&
>>+				(dev->modes[i].notify_rpm == notify_rpm)) {
>>+				start_addr = dev->modes[i].start_addr;
>>+				break;
>>+			}
>>+		}
>>+		ret = msm_spm_drv_set_low_power_mode(&dev->reg_data,
>>+					start_addr, pc_mode);
>>+	}
>>+
>>+	msm_spm_config_q2s(dev, mode);
>>+
>>+	return ret;
>>+}
>>+
>>+static int msm_spm_dev_init(struct msm_spm_device *dev,
>>+		struct msm_spm_platform_data *data)
>>+{
>>+	int i, ret = -ENOMEM;
>>+	uint32_t offset = 0;
>>+
>>+	dev->cpu_vdd = VDD_DEFAULT;
>>+	dev->num_modes = data->num_modes;
>>+	dev->modes = kmalloc(
>>+			sizeof(struct msm_spm_power_modes) * dev->num_modes,
>>+			GFP_KERNEL);
>>+
>>+	if (!dev->modes)
>>+		goto spm_failed_malloc;
>>+
>>+	dev->reg_data.major = data->major;
>>+	dev->reg_data.minor = data->minor;
>>+	ret = msm_spm_drv_init(&dev->reg_data, data);
>>+
>>+	if (ret)
>>+		goto spm_failed_init;
>>+
>>+	for (i = 0; i < dev->num_modes; i++) {
>>+
>>+		/* Default offset is 0 and gets updated as we write more
>>+		 * sequences into SPM
>>+		 */
>>+		dev->modes[i].start_addr = offset;
>>+		ret = msm_spm_drv_write_seq_data(&dev->reg_data,
>>+						data->modes[i].cmd, &offset);
>>+		if (ret < 0)
>>+			goto spm_failed_init;
>>+
>>+		dev->modes[i].mode = data->modes[i].mode;
>>+		dev->modes[i].notify_rpm = data->modes[i].notify_rpm;
>>+	}
>>+	msm_spm_drv_reinit(&dev->reg_data);
>>+	dev->initialized = true;
>>+	return 0;
>>+
>>+spm_failed_init:
>>+	kfree(dev->modes);
>>+spm_failed_malloc:
>>+	return ret;
>>+}
>>+
>>+/**
>>+ * msm_spm_turn_on_cpu_rail(): Power on cpu rail before turning on core
>>+ * @base: The SAW VCTL register which would set the voltage up.
>>+ * @val: The value to be set on the rail
>>+ * @cpu: The cpu for this with rail is being powered on
>>+ */
>>+int msm_spm_turn_on_cpu_rail(void __iomem *base, unsigned int val, int cpu)
>>+{
>>+	uint32_t timeout = 2000; /* delay for voltage to settle on the core */
>>+	struct msm_spm_device *dev = per_cpu(cpu_vctl_device, cpu);
>>+
>>+	/*
>>+	 * If clock drivers have already set up the voltage,
>>+	 * do not overwrite that value.
>>+	 */
>>+	if (dev && (dev->cpu_vdd != VDD_DEFAULT))
>>+		return 0;
>>+
>>+	/* Set the CPU supply regulator voltage */
>>+	val = (val & 0xFF);
>>+	writel_relaxed(val, base);
>>+	mb();
>>+	udelay(timeout);
>>+
>>+	/* Enable the CPU supply regulator*/
>>+	val = 0x30080;
>>+	writel_relaxed(val, base);
>>+	mb();
>>+	udelay(timeout);
>>+
>>+	return 0;
>>+}
>>+EXPORT_SYMBOL(msm_spm_turn_on_cpu_rail);
>>+
>>+void msm_spm_reinit(void)
>>+{
>>+	unsigned int cpu;
>>+
>>+	for_each_possible_cpu(cpu)
>>+		msm_spm_drv_reinit(&per_cpu(msm_cpu_spm_device.reg_data, cpu));
>>+}
>>+EXPORT_SYMBOL(msm_spm_reinit);
>>+
>>+/*
>>+ * msm_spm_is_mode_avail() - Specifies if a mode is available for the cpu
>>+ * It should only be used to decide a mode before lpm driver is probed.
>>+ * @mode: SPM LPM mode to be selected
>>+ */
>>+bool msm_spm_is_mode_avail(unsigned int mode)
>>+{
>>+	struct msm_spm_device *dev = &__get_cpu_var(msm_cpu_spm_device);
>>+	int i;
>>+
>>+	for (i = 0; i < dev->num_modes; i++) {
>>+		if (dev->modes[i].mode == mode)
>>+			return true;
>>+	}
>>+
>>+	return false;
>>+}
>>+
>>+/**
>>+ * msm_spm_set_low_power_mode() - Configure SPM start address for low power mode
>>+ * @mode: SPM LPM mode to enter
>>+ * @notify_rpm: Notify RPM in this mode
>>+ */
>>+int msm_spm_set_low_power_mode(unsigned int mode, bool notify_rpm)
>>+{
>>+	struct msm_spm_device *dev = &__get_cpu_var(msm_cpu_spm_device);
>>+
>>+	return msm_spm_dev_set_low_power_mode(dev, mode, notify_rpm);
>>+}
>>+EXPORT_SYMBOL(msm_spm_set_low_power_mode);
>>+
>>+/**
>>+ * msm_spm_init(): Board initalization function
>>+ * @data: platform specific SPM register configuration data
>>+ * @nr_devs: Number of SPM devices being initialized
>>+ */
>>+int __init msm_spm_init(struct msm_spm_platform_data *data, int nr_devs)
>>+{
>>+	unsigned int cpu;
>>+	int ret = 0;
>>+
>>+	BUG_ON((nr_devs < num_possible_cpus()) || !data);
>>+
>>+	for_each_possible_cpu(cpu) {
>>+		struct msm_spm_device *dev = &per_cpu(msm_cpu_spm_device, cpu);
>>+
>>+		ret = msm_spm_dev_init(dev, &data[cpu]);
>>+		if (ret < 0) {
>>+			pr_warn("%s():failed CPU:%u ret:%d\n", __func__,
>>+					cpu, ret);
>>+			break;
>>+		}
>>+	}
>>+
>>+	return ret;
>>+}
>>+
>>+struct msm_spm_device *msm_spm_get_device_by_name(const char *name)
>>+{
>>+	struct list_head *list;
>>+
>>+	list_for_each(list, &spm_list) {
>>+		struct msm_spm_device *dev
>>+			= list_entry(list, typeof(*dev), list);
>>+		if (dev->name && !strcmp(dev->name, name))
>>+			return dev;
>>+	}
>>+	return ERR_PTR(-ENODEV);
>>+}
>>+
>>+int msm_spm_config_low_power_mode(struct msm_spm_device *dev,
>>+		unsigned int mode, bool notify_rpm)
>>+{
>>+	return msm_spm_dev_set_low_power_mode(dev, mode, notify_rpm);
>>+}
>>+#ifdef CONFIG_MSM_L2_SPM
>>+
>>+/**
>>+ * msm_spm_apcs_set_phase(): Set number of SMPS phases.
>>+ * @cpu: cpu which is requesting the change in number of phases.
>>+ * @phase_cnt: Number of phases to be set active
>>+ */
>>+int msm_spm_apcs_set_phase(int cpu, unsigned int phase_cnt)
>>+{
>>+	struct msm_spm_device *dev = per_cpu(cpu_vctl_device, cpu);
>>+
>>+	if (!dev)
>>+		return -ENXIO;
>>+
>>+	return msm_spm_drv_set_pmic_data(&dev->reg_data,
>>+			MSM_SPM_PMIC_PHASE_PORT, phase_cnt);
>>+}
>>+EXPORT_SYMBOL(msm_spm_apcs_set_phase);
>>+
>>+/** msm_spm_enable_fts_lpm() : Enable FTS to switch to low power
>>+ *                             when the cores are in low power modes
>>+ * @cpu: cpu that is entering low power mode.
>>+ * @mode: The mode configuration for FTS
>>+ */
>>+int msm_spm_enable_fts_lpm(int cpu, uint32_t mode)
>>+{
>>+	struct msm_spm_device *dev = per_cpu(cpu_vctl_device, cpu);
>>+
>>+	if (!dev)
>>+		return -ENXIO;
>>+
>>+	return msm_spm_drv_set_pmic_data(&dev->reg_data,
>>+			MSM_SPM_PMIC_PFM_PORT, mode);
>>+}
>>+EXPORT_SYMBOL(msm_spm_enable_fts_lpm);
>>+
>>+#endif
>>+
>>+static int get_cpu_id(struct device_node *node)
>>+{
>>+	struct device_node *cpu_node;
>>+	u32 cpu;
>>+	int ret = -EINVAL;
>>+	char *key = "qcom,cpu";
>>+
>>+	cpu_node = of_parse_phandle(node, key, 0);
>>+	if (cpu_node) {
>>+		for_each_possible_cpu(cpu) {
>>+			if (of_get_cpu_node(cpu, NULL) == cpu_node)
>>+				return cpu;
>>+		}
>>+	}
>>+	return ret;
>>+}
>>+
>>+static struct msm_spm_device *msm_spm_get_device(struct platform_device *pdev)
>>+{
>>+	struct msm_spm_device *dev = NULL;
>>+	const char *val = NULL;
>>+	char *key = "qcom,name";
>>+	int cpu = get_cpu_id(pdev->dev.of_node);
>>+
>>+	if ((cpu >= 0) && cpu < num_possible_cpus())
>>+		dev = &per_cpu(msm_cpu_spm_device, cpu);
>>+	else if ((cpu == 0xffff) || (cpu < 0))
>>+		dev = devm_kzalloc(&pdev->dev, sizeof(struct msm_spm_device),
>>+					GFP_KERNEL);
>>+
>>+	if (!dev)
>>+		return NULL;
>>+
>>+	if (of_property_read_string(pdev->dev.of_node, key, &val)) {
>>+		pr_err("%s(): Cannot find a required node key:%s\n",
>>+				__func__, key);
>>+		return NULL;
>>+	}
>>+	dev->name = val;
>
>Is the string pointed by val always valid ?
>
>>+	list_add(&dev->list, &spm_list);
>>+
>>+	return dev;
>>+}
>>+
>>+static void get_cpumask(struct device_node *node, struct cpumask *mask)
>>+{
>>+	unsigned long vctl_mask = 0;
>>+	unsigned c = 0;
>>+	int idx = 0;
>>+	struct device_node *cpu_node = NULL;
>>+	int ret = 0;
>>+	char *key = "qcom,cpu-vctl-list";
>>+	bool found = false;
>>+
>>+	cpu_node = of_parse_phandle(node, key, idx++);
>>+	while (cpu_node) {
>>+		found = true;
>>+		for_each_possible_cpu(c) {
>>+			if (of_get_cpu_node(c, NULL) == cpu_node)
>>+				cpumask_set_cpu(c, mask);
>>+		}
>>+		cpu_node = of_parse_phandle(node, key, idx++);
>>+	};
>>+
>>+	if (found)
>>+		return;
>>+
>>+	key = "qcom,cpu-vctl-mask";
>>+	ret = of_property_read_u32(node, key, (u32 *) &vctl_mask);
>>+	if (!ret) {
>>+		for_each_set_bit(c, &vctl_mask, num_possible_cpus()) {
>>+			cpumask_set_cpu(c, mask);
>>+		}
>>+	}
>>+}
>>+
>>+static int msm_spm_dev_probe(struct platform_device *pdev)
>>+{
>>+	int ret = 0;
>>+	int cpu = 0;
>>+	int i = 0;
>>+	struct device_node *node = pdev->dev.of_node;
>>+	struct msm_spm_platform_data spm_data;
>>+	char *key = NULL;
>>+	uint32_t val = 0;
>>+	struct msm_spm_seq_entry modes[MSM_SPM_MODE_NR];
>>+	int len = 0;
>>+	struct msm_spm_device *dev = NULL;
>>+	struct resource *res = NULL;
>>+	uint32_t mode_count = 0;
>>+
>>+	struct spm_of {
>>+		char *key;
>>+		uint32_t id;
>>+	};
>>+
>>+	struct spm_of spm_of_data[] = {
>>+		{"qcom,saw2-cfg", MSM_SPM_REG_SAW2_CFG},
>>+		{"qcom,saw2-spm-dly", MSM_SPM_REG_SAW2_SPM_DLY},
>>+		{"qcom,saw2-spm-ctl", MSM_SPM_REG_SAW2_SPM_CTL},
>>+		{"qcom,saw2-pmic-data0", MSM_SPM_REG_SAW2_PMIC_DATA_0},
>>+		{"qcom,saw2-pmic-data1", MSM_SPM_REG_SAW2_PMIC_DATA_1},
>>+		{"qcom,saw2-pmic-data2", MSM_SPM_REG_SAW2_PMIC_DATA_2},
>>+		{"qcom,saw2-pmic-data3", MSM_SPM_REG_SAW2_PMIC_DATA_3},
>>+		{"qcom,saw2-pmic-data4", MSM_SPM_REG_SAW2_PMIC_DATA_4},
>>+		{"qcom,saw2-pmic-data5", MSM_SPM_REG_SAW2_PMIC_DATA_5},
>>+		{"qcom,saw2-pmic-data6", MSM_SPM_REG_SAW2_PMIC_DATA_6},
>>+		{"qcom,saw2-pmic-data7", MSM_SPM_REG_SAW2_PMIC_DATA_7},
>>+	};
>>+
>>+	struct mode_of {
>>+		char *key;
>>+		uint32_t id;
>>+		uint32_t notify_rpm;
>>+	};
>>+
>>+	struct mode_of mode_of_data[] = {
>>+		{"qcom,saw2-spm-cmd-wfi", MSM_SPM_MODE_CLOCK_GATING, 0},
>>+		{"qcom,saw2-spm-cmd-ret", MSM_SPM_MODE_RETENTION, 0},
>>+		{"qcom,saw2-spm-cmd-gdhs", MSM_SPM_MODE_GDHS, 1},
>>+		{"qcom,saw2-spm-cmd-spc", MSM_SPM_MODE_POWER_COLLAPSE, 0},
>>+		{"qcom,saw2-spm-cmd-pc", MSM_SPM_MODE_POWER_COLLAPSE, 1},
>>+	};
>>+
>>+	dev = msm_spm_get_device(pdev);
>>+	if (!dev) {
>>+		ret = -ENOMEM;
>>+		goto fail;
>>+	}
>>+	get_cpumask(node, &dev->mask);
>>+
>>+	memset(&spm_data, 0, sizeof(struct msm_spm_platform_data));
>>+	memset(&modes, 0,
>>+		(MSM_SPM_MODE_NR - 2) * sizeof(struct msm_spm_seq_entry));
>>+
>>+	if (of_device_is_compatible(node, "qcom,spm-v2.1")) {
>>+		spm_data.major = 2;
>>+		spm_data.minor = 1;
>>+	} else if (of_device_is_compatible(node, "qcom,spm-v3.0")) {
>>+		spm_data.major = 3;
>>+		spm_data.minor = 0;
>>+	}
>>+
>>+	key = "qcom,vctl-timeout-us";
>>+	ret = of_property_read_u32(node, key, &val);
>>+	if (!ret)
>>+		spm_data.vctl_timeout_us = val;
>>+
>>+	/* SAW start address */
>>+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
>>+	if (!res) {
>>+		ret = -EFAULT;
>>+		goto fail;
>>+	}
>>+
>>+	spm_data.reg_base_addr = devm_ioremap(&pdev->dev, res->start,
>>+					resource_size(res));
>>+	if (!spm_data.reg_base_addr) {
>>+		ret = -ENOMEM;
>>+		goto fail;
>>+	}
>>+
>>+	spm_data.vctl_port = -1;
>>+	spm_data.phase_port = -1;
>>+	spm_data.pfm_port = -1;
>>+
>>+	key = "qcom,vctl-port";
>>+	of_property_read_u32(node, key, &spm_data.vctl_port);
>>+
>>+	key = "qcom,phase-port";
>>+	of_property_read_u32(node, key, &spm_data.phase_port);
>>+
>>+	key = "qcom,pfm-port";
>>+	of_property_read_u32(node, key, &spm_data.pfm_port);
>>+
>>+	/* Q2S (QChannel-2-SPM) register */
>>+	res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
>>+	if (res) {
>>+		dev->q2s_reg = devm_ioremap(&pdev->dev, res->start,
>>+						resource_size(res));
>>+		if (!dev->q2s_reg) {
>>+			pr_err("%s(): Unable to iomap Q2S register\n",
>>+					__func__);
>>+			ret = -EADDRNOTAVAIL;
>>+			goto fail;
>>+		}
>>+	}
>>+	/*
>>+	 * At system boot, cpus and or clusters can remain in reset. CCI SPM
>>+	 * will not be triggered unless SPM_LEGACY_MODE bit is set for the
>>+	 * cluster in reset. Initialize q2s registers and set the
>>+	 * SPM_LEGACY_MODE bit.
>>+	 */
>>+	msm_spm_config_q2s(dev, MSM_SPM_MODE_POWER_COLLAPSE);
>>+
>>+	for (i = 0; i < ARRAY_SIZE(spm_of_data); i++) {
>>+		ret = of_property_read_u32(node, spm_of_data[i].key, &val);
>>+		if (ret)
>>+			continue;
>>+		spm_data.reg_init_values[spm_of_data[i].id] = val;
>>+	}
>>+
>>+	for (i = 0; i < ARRAY_SIZE(mode_of_data); i++) {
>>+		key = mode_of_data[i].key;
>>+		modes[mode_count].cmd =
>>+			(uint8_t *)of_get_property(node, key, &len);
>>+		if (!modes[mode_count].cmd)
>>+			continue;
>>+		modes[mode_count].mode = mode_of_data[i].id;
>>+		modes[mode_count].notify_rpm = mode_of_data[i].notify_rpm;
>>+		pr_debug("%s(): dev: %s cmd:%s, mode:%d rpm:%d\n", __func__,
>>+				dev->name, key, modes[mode_count].mode,
>>+				modes[mode_count].notify_rpm);
>>+		mode_count++;
>>+	}
>>+
>>+	spm_data.modes = modes;
>>+	spm_data.num_modes = mode_count;
>>+
>>+	ret = msm_spm_dev_init(dev, &spm_data);
>>+	if (ret)
>>+		goto fail;
>>+
>>+	platform_set_drvdata(pdev, dev);
>>+
>>+	for_each_cpu(cpu, &dev->mask)
>>+		per_cpu(cpu_vctl_device, cpu) = dev;
>>+
>>+	return ret;
>>+
>>+fail:
>>+	cpu = get_cpu_id(pdev->dev.of_node);
>>+	if (dev && (cpu >= num_possible_cpus() || (cpu < 0))) {
>>+		for_each_cpu(cpu, &dev->mask)
>>+			per_cpu(cpu_vctl_device, cpu) = ERR_PTR(ret);
>>+	}
>>+
>>+	pr_err("%s: CPU%d SPM device probe failed: %d\n", __func__, cpu, ret);
>>+
>>+	return ret;
>>+}
>>+
>>+static int msm_spm_dev_remove(struct platform_device *pdev)
>>+{
>>+	struct msm_spm_device *dev = platform_get_drvdata(pdev);
>>+
>>+	list_del(&dev->list);
>>+
>>+	return 0;
>>+}
>>+
>>+static struct of_device_id msm_spm_match_table[] = {
>>+	{.compatible = "qcom,spm-v2.1"},
>>+	{.compatible = "qcom,spm-v3.0"},
>>+	{},
>>+};
>>+
>>+static struct platform_driver msm_spm_device_driver = {
>>+	.probe = msm_spm_dev_probe,
>>+	.remove = msm_spm_dev_remove,
>>+	.driver = {
>>+		.name = "spm-v2",
>>+		.owner = THIS_MODULE,
>>+		.of_match_table = msm_spm_match_table,
>>+	},
>>+};
>>+
>>+/**
>>+ * msm_spm_device_init(): Device tree initialization function
>>+ */
>>+int __init msm_spm_device_init(void)
>>+{
>>+	static bool registered;
>>+
>>+	if (registered)
>>+		return 0;
>>+
>>+	registered = true;
>>+
>>+	return platform_driver_register(&msm_spm_device_driver);
>>+}
>>+device_initcall(msm_spm_device_init);
>
>Why is needed this 'registered' thing ?
>
>Couldn't the msm_spm_device_init be removed and replaced by:
>
>module_platform_driver(msm_spm_device_driver);
>
>?
>
>>diff --git a/drivers/soc/qcom/spm.c b/drivers/soc/qcom/spm.c
>>new file mode 100644
>>index 0000000..7dbdb64
>>--- /dev/null
>>+++ b/drivers/soc/qcom/spm.c
>>@@ -0,0 +1,482 @@
>>+/* Copyright (c) 2011-2014, The Linux Foundation. All rights reserved.
>>+ *
>>+ * This program is free software; you can redistribute it and/or modify
>>+ * it under the terms of the GNU General Public License version 2 and
>>+ * only version 2 as published by the Free Software Foundation.
>>+ *
>>+ * This program is distributed in the hope that it will be useful,
>>+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
>>+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
>>+ * GNU General Public License for more details.
>>+ *
>>+ */
>>+
>>+#include <linux/module.h>
>>+#include <linux/kernel.h>
>>+#include <linux/delay.h>
>>+#include <linux/init.h>
>>+#include <linux/io.h>
>>+#include <linux/slab.h>
>>+
>>+#include "spm_driver.h"
>>+
>>+#define MSM_SPM_PMIC_STATE_IDLE  0
>>+
>>+enum {
>>+	MSM_SPM_DEBUG_SHADOW = 1U << 0,
>>+	MSM_SPM_DEBUG_VCTL = 1U << 1,
>>+};
>>+
>>+static int msm_spm_debug_mask;
>>+module_param_named(
>>+	debug_mask, msm_spm_debug_mask, int, S_IRUGO | S_IWUSR | S_IWGRP
>>+);
>>+
>>+struct saw2_data {
>>+	const char *ver_name;
>>+	uint32_t major;
>>+	uint32_t minor;
>>+	uint32_t *spm_reg_offset_ptr;
>>+};
>>+
>>+static uint32_t msm_spm_reg_offsets_saw2_v2_1[MSM_SPM_REG_NR] = {
>>+	[MSM_SPM_REG_SAW2_SECURE]		= 0x00,
>>+	[MSM_SPM_REG_SAW2_ID]			= 0x04,
>>+	[MSM_SPM_REG_SAW2_CFG]			= 0x08,
>>+	[MSM_SPM_REG_SAW2_SPM_STS]		= 0x0C,
>>+	[MSM_SPM_REG_SAW2_AVS_STS]		= 0x10,
>>+	[MSM_SPM_REG_SAW2_PMIC_STS]		= 0x14,
>>+	[MSM_SPM_REG_SAW2_RST]			= 0x18,
>>+	[MSM_SPM_REG_SAW2_VCTL]			= 0x1C,
>>+	[MSM_SPM_REG_SAW2_AVS_CTL]		= 0x20,
>>+	[MSM_SPM_REG_SAW2_AVS_LIMIT]		= 0x24,
>>+	[MSM_SPM_REG_SAW2_AVS_DLY]		= 0x28,
>>+	[MSM_SPM_REG_SAW2_AVS_HYSTERESIS]	= 0x2C,
>>+	[MSM_SPM_REG_SAW2_SPM_CTL]		= 0x30,
>>+	[MSM_SPM_REG_SAW2_SPM_DLY]		= 0x34,
>>+	[MSM_SPM_REG_SAW2_PMIC_DATA_0]		= 0x40,
>>+	[MSM_SPM_REG_SAW2_PMIC_DATA_1]		= 0x44,
>>+	[MSM_SPM_REG_SAW2_PMIC_DATA_2]		= 0x48,
>>+	[MSM_SPM_REG_SAW2_PMIC_DATA_3]		= 0x4C,
>>+	[MSM_SPM_REG_SAW2_PMIC_DATA_4]		= 0x50,
>>+	[MSM_SPM_REG_SAW2_PMIC_DATA_5]		= 0x54,
>>+	[MSM_SPM_REG_SAW2_PMIC_DATA_6]		= 0x58,
>>+	[MSM_SPM_REG_SAW2_PMIC_DATA_7]		= 0x5C,
>>+	[MSM_SPM_REG_SAW2_SEQ_ENTRY]		= 0x80,
>>+	[MSM_SPM_REG_SAW2_VERSION]		= 0xFD0,
>>+};
>>+
>>+static uint32_t msm_spm_reg_offsets_saw2_v3_0[MSM_SPM_REG_NR] = {
>>+	[MSM_SPM_REG_SAW2_SECURE]		= 0x00,
>>+	[MSM_SPM_REG_SAW2_ID]			= 0x04,
>>+	[MSM_SPM_REG_SAW2_CFG]			= 0x08,
>>+	[MSM_SPM_REG_SAW2_SPM_STS]		= 0x0C,
>>+	[MSM_SPM_REG_SAW2_AVS_STS]		= 0x10,
>>+	[MSM_SPM_REG_SAW2_PMIC_STS]		= 0x14,
>>+	[MSM_SPM_REG_SAW2_RST]			= 0x18,
>>+	[MSM_SPM_REG_SAW2_VCTL]			= 0x1C,
>>+	[MSM_SPM_REG_SAW2_AVS_CTL]		= 0x20,
>>+	[MSM_SPM_REG_SAW2_AVS_LIMIT]		= 0x24,
>>+	[MSM_SPM_REG_SAW2_AVS_DLY]		= 0x28,
>>+	[MSM_SPM_REG_SAW2_AVS_HYSTERESIS]	= 0x2C,
>>+	[MSM_SPM_REG_SAW2_SPM_CTL]		= 0x30,
>>+	[MSM_SPM_REG_SAW2_SPM_DLY]		= 0x34,
>>+	[MSM_SPM_REG_SAW2_STS2]			= 0x38,
>>+	[MSM_SPM_REG_SAW2_PMIC_DATA_0]		= 0x40,
>>+	[MSM_SPM_REG_SAW2_PMIC_DATA_1]		= 0x44,
>>+	[MSM_SPM_REG_SAW2_PMIC_DATA_2]		= 0x48,
>>+	[MSM_SPM_REG_SAW2_PMIC_DATA_3]		= 0x4C,
>>+	[MSM_SPM_REG_SAW2_PMIC_DATA_4]		= 0x50,
>>+	[MSM_SPM_REG_SAW2_PMIC_DATA_5]		= 0x54,
>>+	[MSM_SPM_REG_SAW2_PMIC_DATA_6]		= 0x58,
>>+	[MSM_SPM_REG_SAW2_PMIC_DATA_7]		= 0x5C,
>>+	[MSM_SPM_REG_SAW2_SEQ_ENTRY]		= 0x400,
>>+	[MSM_SPM_REG_SAW2_VERSION]		= 0xFD0,
>>+};
>>+
>>+static struct saw2_data saw2_info[] = {
>>+	[0] = {
>>+		"SAW2_v2.1",
>>+		2,
>>+		1,
>>+		msm_spm_reg_offsets_saw2_v2_1,
>>+	},
>>+	[1] = {
>>+		"SAW2_v3.0",
>>+		3,
>>+		0,
>>+		msm_spm_reg_offsets_saw2_v3_0,
>>+	},
>>+};
>>+
>>+static uint32_t num_pmic_data;
>>+
>>+static inline uint32_t msm_spm_drv_get_num_spm_entry(
>>+		struct msm_spm_driver_data *dev)
>>+{
>>+	return 32;
>>+}
>>+
>>+static void msm_spm_drv_flush_shadow(struct msm_spm_driver_data *dev,
>>+		unsigned int reg_index)
>>+{
>>+	__raw_writel(dev->reg_shadow[reg_index],
>>+		dev->reg_base_addr + dev->reg_offsets[reg_index]);
>>+}
>>+
>>+static void msm_spm_drv_load_shadow(struct msm_spm_driver_data *dev,
>>+		unsigned int reg_index)
>>+{
>>+	dev->reg_shadow[reg_index] =
>>+		__raw_readl(dev->reg_base_addr +
>>+				dev->reg_offsets[reg_index]);
>>+}
>>+
>>+static inline void msm_spm_drv_set_start_addr(
>>+		struct msm_spm_driver_data *dev, uint32_t addr, bool pc_mode)
>>+{
>>+	addr &= 0x7F;
>>+	addr <<= 4;
>>+	dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] &= 0xFFFFF80F;
>>+	dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] |= addr;
>>+
>>+	if (dev->major != 0x3)
>>+		return;
>>+
>>+	dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] &= 0xFFFEFFFF;
>>+	if (pc_mode)
>>+		dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] |= 0x00010000;
>>+}
>>+
>>+static inline bool msm_spm_pmic_arb_present(struct msm_spm_driver_data *dev)
>>+{
>>+	msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW2_ID);
>>+	return (dev->reg_shadow[MSM_SPM_REG_SAW2_ID] >> 2) & 0x1;
>>+}
>>+
>>+static inline void msm_spm_drv_set_vctl2(struct msm_spm_driver_data *dev,
>>+		uint32_t vlevel)
>>+{
>>+	unsigned int pmic_data = 0;
>>+
>>+	/**
>>+	 * VCTL_PORT has to be 0, for PMIC_STS register to be updated.
>>+	 * Ensure that vctl_port is always set to 0.
>>+	 */
>>+	WARN_ON(dev->vctl_port);
>>+
>>+	pmic_data |= vlevel;
>>+	pmic_data |= (dev->vctl_port & 0x7) << 16;
>>+
>>+	dev->reg_shadow[MSM_SPM_REG_SAW2_VCTL] &= ~0x700FF;
>>+	dev->reg_shadow[MSM_SPM_REG_SAW2_VCTL] |= pmic_data;
>>+
>>+	dev->reg_shadow[MSM_SPM_REG_SAW2_PMIC_DATA_3] &= ~0x700FF;
>>+	dev->reg_shadow[MSM_SPM_REG_SAW2_PMIC_DATA_3] |= pmic_data;
>>+
>>+	msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW2_VCTL);
>>+	msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW2_PMIC_DATA_3);
>>+}
>>+
>>+static inline uint32_t msm_spm_drv_get_num_pmic_data(
>>+		struct msm_spm_driver_data *dev)
>>+{
>>+	msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW2_ID);
>>+	mb();
>>+	return (dev->reg_shadow[MSM_SPM_REG_SAW2_ID] >> 4) & 0x7;
>>+}
>>+
>>+static inline uint32_t msm_spm_drv_get_sts_pmic_state(
>>+		struct msm_spm_driver_data *dev)
>>+{
>>+	msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW2_PMIC_STS);
>>+	return (dev->reg_shadow[MSM_SPM_REG_SAW2_PMIC_STS] >> 16) &
>>+				0x03;
>>+}
>>+
>>+uint32_t msm_spm_drv_get_sts_curr_pmic_data(
>>+		struct msm_spm_driver_data *dev)
>>+{
>>+	msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW2_PMIC_STS);
>>+	return dev->reg_shadow[MSM_SPM_REG_SAW2_PMIC_STS] & 0xFF;
>>+}
>>+
>>+inline int msm_spm_drv_set_spm_enable(
>>+		struct msm_spm_driver_data *dev, bool enable)
>>+{
>>+	uint32_t value = enable ? 0x01 : 0x00;
>>+
>>+	if (!dev)
>>+		return -EINVAL;
>>+
>>+	if ((dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] & 0x01) ^ value) {
>>+
>>+		dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] &= ~0x1;
>>+		dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] |= value;
>>+
>>+		msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW2_SPM_CTL);
>>+		wmb();
>>+	}
>>+	return 0;
>>+}
>>+void msm_spm_drv_flush_seq_entry(struct msm_spm_driver_data *dev)
>>+{
>>+	int i;
>>+	int num_spm_entry = msm_spm_drv_get_num_spm_entry(dev);
>>+
>>+	if (!dev) {
>>+		__WARN();
>>+		return;
>>+	}
>>+
>>+	for (i = 0; i < num_spm_entry; i++) {
>>+		__raw_writel(dev->reg_seq_entry_shadow[i],
>>+			dev->reg_base_addr
>>+			+ dev->reg_offsets[MSM_SPM_REG_SAW2_SEQ_ENTRY]
>>+			+ 4 * i);
>>+	}
>>+	mb();
>>+}
>>+
>>+void dump_regs(struct msm_spm_driver_data *dev, int cpu)
>>+{
>>+	msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW2_SPM_STS);
>>+	mb();
>>+	pr_err("CPU%d: spm register MSM_SPM_REG_SAW2_SPM_STS: 0x%x\n", cpu,
>>+			dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_STS]);
>>+	msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW2_SPM_CTL);
>>+	mb();
>>+	pr_err("CPU%d: spm register MSM_SPM_REG_SAW2_SPM_CTL: 0x%x\n", cpu,
>>+			dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL]);
>>+}
>>+
>>+int msm_spm_drv_write_seq_data(struct msm_spm_driver_data *dev,
>>+		uint8_t *cmd, uint32_t *offset)
>>+{
>>+	uint32_t cmd_w;
>>+	uint32_t offset_w = *offset / 4;
>>+	uint8_t last_cmd;
>>+
>>+	if (!cmd)
>>+		return -EINVAL;
>>+
>>+	while (1) {
>>+		int i;
>>+
>>+		cmd_w = 0;
>>+		last_cmd = 0;
>>+		cmd_w = dev->reg_seq_entry_shadow[offset_w];
>>+
>>+		for (i = (*offset % 4); i < 4; i++) {
>>+			last_cmd = *(cmd++);
>>+			cmd_w |=  last_cmd << (i * 8);
>>+			(*offset)++;
>>+			if (last_cmd == 0x0f)
>>+				break;
>>+		}
>>+
>>+		dev->reg_seq_entry_shadow[offset_w++] = cmd_w;
>>+		if (last_cmd == 0x0f)
>>+			break;
>>+	}
>>+
>>+	return 0;
>>+}
>>+
>>+int msm_spm_drv_set_low_power_mode(struct msm_spm_driver_data *dev,
>>+		uint32_t addr, bool pc_mode)
>>+{
>>+
>>+	if (!dev)
>>+		return -EINVAL;
>>+
>>+	msm_spm_drv_set_start_addr(dev, addr, pc_mode);
>>+
>>+	msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW2_SPM_CTL);
>>+	wmb();
>>+
>>+	if (msm_spm_debug_mask & MSM_SPM_DEBUG_SHADOW) {
>>+		int i;
>>+
>>+		for (i = 0; i < MSM_SPM_REG_NR; i++)
>>+			pr_info("%s: reg %02x = 0x%08x\n", __func__,
>>+				dev->reg_offsets[i], dev->reg_shadow[i]);
>>+	}
>>+	msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW2_SPM_STS);
>>+
>>+	return 0;
>>+}
>>+
>>+int msm_spm_drv_set_vdd(struct msm_spm_driver_data *dev, unsigned int vlevel)
>>+{
>>+	uint32_t timeout_us, new_level;
>>+
>>+	if (!dev)
>>+		return -EINVAL;
>>+
>>+	if (!msm_spm_pmic_arb_present(dev))
>>+		return -ENOSYS;
>>+
>>+	if (msm_spm_debug_mask & MSM_SPM_DEBUG_VCTL)
>>+		pr_info("%s: requesting vlevel %#x\n", __func__, vlevel);
>>+
>>+	/* Kick the state machine back to idle */
>>+	dev->reg_shadow[MSM_SPM_REG_SAW2_RST] = 1;
>>+	msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW2_RST);
>>+
>>+	msm_spm_drv_set_vctl2(dev, vlevel);
>>+
>>+	timeout_us = dev->vctl_timeout_us;
>>+	/* Confirm the voltage we set was what hardware sent */
>>+	do {
>>+		new_level = msm_spm_drv_get_sts_curr_pmic_data(dev);
>>+		if (new_level == vlevel)
>>+			break;
>>+		udelay(1);
>>+	} while (--timeout_us);
>>+	if (!timeout_us) {
>>+		pr_info("Wrong level %#x\n", new_leve
>>+		goto set_vdd_bail;
>>+	}
>>+
>>+	if (msm_spm_debug_mask & MSM_SPM_DEBUG_VCTL)
>>+		pr_info("%s: done, remaining timeout %u us\n",
>>+			__func__, timeout_us);
>>+
>>+	return 0;
>>+
>>+set_vdd_bail:
>>+	pr_err("%s: failed %#x, remaining timeout %uus, vlevel %#x\n",
>>+		__func__, vlevel, timeout_us, new_level);
>>+	return -EIO;
>>+}
>>+
>>+static int msm_spm_drv_get_pmic_port(struct msm_spm_driver_data *dev,
>>+		enum msm_spm_pmic_port port)
>>+{
>>+	int index = -1;
>>+
>>+	switch (port) {
>>+	case MSM_SPM_PMIC_VCTL_PORT:
>>+		index = dev->vctl_port;
>>+		break;
>>+	case MSM_SPM_PMIC_PHASE_PORT:
>>+		index = dev->phase_port;
>>+		break;
>>+	case MSM_SPM_PMIC_PFM_PORT:
>>+		index = dev->pfm_port;
>>+		break;
>>+	default:
>>+		break;
>>+	}
>>+
>>+	return index;
>>+}
>>+
>>+int msm_spm_drv_set_pmic_data(struct msm_spm_driver_data *dev,
>>+		enum msm_spm_pmic_port port, unsigned int data)
>>+{
>>+	unsigned int pmic_data = 0;
>>+	unsigned int timeout_us = 0;
>>+	int index = 0;
>>+
>>+	if (!msm_spm_pmic_arb_present(dev))
>>+		return -ENOSYS;
>>+
>>+	index = msm_spm_drv_get_pmic_port(dev, port);
>>+	if (index < 0)
>>+		return -ENODEV;
>>+
>>+	pmic_data |= data & 0xFF;
>>+	pmic_data |= (index & 0x7) << 16;
>>+
>>+	dev->reg_shadow[MSM_SPM_REG_SAW2_VCTL] &= ~0x700FF;
>>+	dev->reg_shadow[MSM_SPM_REG_SAW2_VCTL] |= pmic_data;
>>+	msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW2_VCTL);
>>+	mb();
>>+
>>+	timeout_us = dev->vctl_timeout_us;
>>+	/**
>>+	 * Confirm the pmic data set was what hardware sent by
>>+	 * checking the PMIC FSM state.
>>+	 * We cannot use the sts_pmic_data and check it against
>>+	 * the value like we do fot set_vdd, since the PMIC_STS
>>+	 * is only updated for SAW_VCTL sent with port index 0.
>>+	 */
>>+	do {
>>+		if (msm_spm_drv_get_sts_pmic_state(dev) ==
>>+				MSM_SPM_PMIC_STATE_IDLE)
>>+			break;
>>+		udelay(1);
>>+	} while (--timeout_us);
>>+
>>+	if (!timeout_us) {
>>+		pr_err("%s: failed, remaining timeout %u us, data %d\n",
>>+				__func__, timeout_us, data);
>>+		return -EIO;
>>+	}
>>+
>>+	return 0;
>>+}
>>+
>>+void msm_spm_drv_reinit(struct msm_spm_driver_data *dev)
>>+{
>>+	int i;
>>+
>>+	msm_spm_drv_flush_seq_entry(dev);
>>+	for (i = 0; i < MSM_SPM_REG_SAW2_PMIC_DATA_0 + num_pmic_data; i++)
>>+		msm_spm_drv_flush_shadow(dev, i);
>>+
>>+	mb();
>
>Why are needed the mb() after calling the msm_spm_drv_flush_shadow 
>function ?
>
>>+
>>+	for (i = MSM_SPM_REG_NR_INITIALIZE + 1; i < MSM_SPM_REG_NR; i++)
>>+		msm_spm_drv_load_shadow(dev, i);
>>+}
>>+
>>+int msm_spm_drv_init(struct msm_spm_driver_data *dev,
>>+		struct msm_spm_platform_data *data)
>>+{
>>+	int i;
>>+	int num_spm_entry;
>>+	bool found = false;
>>+
>>+	BUG_ON(!dev || !data);
>>+
>>+	dev->vctl_port = data->vctl_port;
>>+	dev->phase_port = data->phase_port;
>>+	dev->pfm_port = data->pfm_port;
>>+	dev->reg_base_addr = data->reg_base_addr;
>>+	memcpy(dev->reg_shadow, data->reg_init_values,
>>+			sizeof(data->reg_init_values));
>>+
>>+	dev->vctl_timeout_us = data->vctl_timeout_us;
>>+
>>+	for (i = 0; i < ARRAY_SIZE(saw2_info); i++)
>>+		if (dev->major == saw2_info[i].major &&
>>+			dev->minor == saw2_info[i].minor) {
>>+			pr_debug("%s: Version found\n",
>>+					saw2_info[i].ver_name);
>>+			dev->reg_offsets = saw2_info[i].spm_reg_offset_ptr;
>>+			found = true;
>>+			break;
>>+		}
>>+
>>+	if (!found) {
>>+		pr_err("%s: No SAW2 version found\n", __func__);
>>+		BUG_ON(!found);
>>+	}
>>+
>>+	if (!num_pmic_data)
>>+		num_pmic_data = msm_spm_drv_get_num_pmic_data(dev);
>>+
>>+	num_spm_entry = msm_spm_drv_get_num_spm_entry(dev);
>>+
>>+	dev->reg_seq_entry_shadow =
>>+		kzalloc(sizeof(*dev->reg_seq_entry_shadow) * num_spm_entry,
>>+				GFP_KERNEL);
>>+
>>+	if (!dev->reg_seq_entry_shadow)
>>+		return -ENOMEM;
>>+
>>+	return 0;
>>+}
>>diff --git a/drivers/soc/qcom/spm_driver.h b/drivers/soc/qcom/spm_driver.h
>>new file mode 100644
>>index 0000000..b306520
>>--- /dev/null
>>+++ b/drivers/soc/qcom/spm_driver.h
>>@@ -0,0 +1,116 @@
>>+/* Copyright (c) 2011-2014, The Linux Foundation. All rights reserved.
>>+ *
>>+ * This program is free software; you can redistribute it and/or modify
>>+ * it under the terms of the GNU General Public License version 2 and
>>+ * only version 2 as published by the Free Software Foundation.
>>+ *
>>+ * This program is distributed in the hope that it will be useful,
>>+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
>>+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
>>+ * GNU General Public License for more details.
>>+ */
>>+#ifndef __QCOM_SPM_DRIVER_H
>>+#define __QCOM_SPM_DRIVER_H
>>+
>>+#include <soc/qcom/spm.h>
>>+
>>+enum {
>>+	MSM_SPM_REG_SAW2_CFG,
>>+	MSM_SPM_REG_SAW2_AVS_CTL,
>>+	MSM_SPM_REG_SAW2_AVS_HYSTERESIS,
>>+	MSM_SPM_REG_SAW2_SPM_CTL,
>>+	MSM_SPM_REG_SAW2_PMIC_DLY,
>>+	MSM_SPM_REG_SAW2_AVS_LIMIT,
>>+	MSM_SPM_REG_SAW2_AVS_DLY,
>>+	MSM_SPM_REG_SAW2_SPM_DLY,
>>+	MSM_SPM_REG_SAW2_PMIC_DATA_0,
>>+	MSM_SPM_REG_SAW2_PMIC_DATA_1,
>>+	MSM_SPM_REG_SAW2_PMIC_DATA_2,
>>+	MSM_SPM_REG_SAW2_PMIC_DATA_3,
>>+	MSM_SPM_REG_SAW2_PMIC_DATA_4,
>>+	MSM_SPM_REG_SAW2_PMIC_DATA_5,
>>+	MSM_SPM_REG_SAW2_PMIC_DATA_6,
>>+	MSM_SPM_REG_SAW2_PMIC_DATA_7,
>>+	MSM_SPM_REG_SAW2_RST,
>>+
>>+	MSM_SPM_REG_NR_INITIALIZE = MSM_SPM_REG_SAW2_RST,
>>+
>>+	MSM_SPM_REG_SAW2_ID,
>>+	MSM_SPM_REG_SAW2_SECURE,
>>+	MSM_SPM_REG_SAW2_STS0,
>>+	MSM_SPM_REG_SAW2_STS1,
>>+	MSM_SPM_REG_SAW2_STS2,
>>+	MSM_SPM_REG_SAW2_VCTL,
>>+	MSM_SPM_REG_SAW2_SEQ_ENTRY,
>>+	MSM_SPM_REG_SAW2_SPM_STS,
>>+	MSM_SPM_REG_SAW2_AVS_STS,
>>+	MSM_SPM_REG_SAW2_PMIC_STS,
>>+	MSM_SPM_REG_SAW2_VERSION,
>>+
>>+	MSM_SPM_REG_NR,
>>+};
>>+
>>+struct msm_spm_seq_entry {
>>+	uint32_t mode;
>>+	uint8_t *cmd;
>>+	bool  notify_rpm;
>>+};
>>+
>>+struct msm_spm_platform_data {
>>+	void __iomem *reg_base_addr;
>>+	uint32_t reg_init_values[MSM_SPM_REG_NR_INITIALIZE];
>>+
>>+	uint32_t major;
>>+	uint32_t minor;
>>+	uint32_t vctl_port;
>>+	uint32_t phase_port;
>>+	uint32_t pfm_port;
>>+
>>+	uint8_t awake_vlevel;
>>+	uint32_t vctl_timeout_us;
>>+
>>+	uint32_t num_modes;
>>+	struct msm_spm_seq_entry *modes;
>>+};
>>+
>>+enum msm_spm_pmic_port {
>>+	MSM_SPM_PMIC_VCTL_PORT,
>>+	MSM_SPM_PMIC_PHASE_PORT,
>>+	MSM_SPM_PMIC_PFM_PORT,
>>+};
>>+
>>+struct msm_spm_driver_data {
>>+	uint32_t major;
>>+	uint32_t minor;
>>+	uint32_t vctl_port;
>>+	uint32_t phase_port;
>>+	uint32_t pfm_port;
>>+	void __iomem *reg_base_addr;
>>+	uint32_t vctl_timeout_us;
>>+	uint32_t reg_shadow[MSM_SPM_REG_NR];
>>+	uint32_t *reg_seq_entry_shadow;
>>+	uint32_t *reg_offsets;
>>+};
>>+
>>+int msm_spm_drv_init(struct msm_spm_driver_data *dev,
>>+		struct msm_spm_platform_data *data);
>>+void msm_spm_drv_reinit(struct msm_spm_driver_data *dev);
>>+int msm_spm_drv_set_low_power_mode(struct msm_spm_driver_data *dev,
>>+		uint32_t addr, bool pc_mode);
>>+int msm_spm_drv_set_vdd(struct msm_spm_driver_data *dev,
>>+		unsigned int vlevel);
>>+void dump_regs(struct msm_spm_driver_data *dev, int cpu);
>>+uint32_t msm_spm_drv_get_sts_curr_pmic_data(
>>+		struct msm_spm_driver_data *dev);
>>+int msm_spm_drv_write_seq_data(struct msm_spm_driver_data *dev,
>>+		uint8_t *cmd, uint32_t *offset);
>>+void msm_spm_drv_flush_seq_entry(struct msm_spm_driver_data *dev);
>>+int msm_spm_drv_set_spm_enable(struct msm_spm_driver_data *dev,
>>+		bool enable);
>>+int msm_spm_drv_set_pmic_data(struct msm_spm_driver_data *dev,
>>+		enum msm_spm_pmic_port port, unsigned int data);
>>+
>>+void msm_spm_reinit(void);
>>+int msm_spm_init(struct msm_spm_platform_data *data, int nr_devs);
>>+
>>+#endif /* __QCOM_SPM_DRIVER_H */
>>diff --git a/include/soc/qcom/spm.h b/include/soc/qcom/spm.h
>>new file mode 100644
>>index 0000000..f39e0c4
>>--- /dev/null
>>+++ b/include/soc/qcom/spm.h
>>@@ -0,0 +1,70 @@
>>+/* Copyright (c) 2010-2014, The Linux Foundation. All rights reserved.
>>+ *
>>+ * This program is free software; you can redistribute it and/or modify
>>+ * it under the terms of the GNU General Public License version 2 and
>>+ * only version 2 as published by the Free Software Foundation.
>>+ *
>>+ * This program is distributed in the hope that it will be useful,
>>+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
>>+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
>>+ * GNU General Public License for more details.
>>+ */
>>+
>>+#ifndef __QCOM_SPM_H
>>+#define __QCOM_SPM_H
>>+
>>+enum {
>>+	MSM_SPM_MODE_DISABLED,
>>+	MSM_SPM_MODE_CLOCK_GATING,
>>+	MSM_SPM_MODE_RETENTION,
>>+	MSM_SPM_MODE_GDHS,
>>+	MSM_SPM_MODE_POWER_COLLAPSE,
>>+	MSM_SPM_MODE_NR
>>+};
>>+
>>+struct msm_spm_device;
>>+
>>+#if defined(CONFIG_QCOM_PM)
>>+int msm_spm_set_low_power_mode(unsigned int mode, bool notify_rpm);
>>+int msm_spm_probe_done(void);
>>+int msm_spm_set_vdd(unsigned int cpu, unsigned int vlevel);
>>+unsigned int msm_spm_get_vdd(unsigned int cpu);
>>+int msm_spm_turn_on_cpu_rail(void __iomem *base, unsigned int val, int cpu);
>>+struct msm_spm_device *msm_spm_get_device_by_name(const char *name);
>>+int msm_spm_config_low_power_mode(struct msm_spm_device *dev,
>>+		unsigned int mode, bool notify_rpm);
>>+int msm_spm_device_init(void);
>>+bool msm_spm_is_mode_avail(unsigned int mode);
>>+void msm_spm_dump_regs(unsigned int cpu);
>>+int msm_spm_apcs_set_phase(int cpu, unsigned int phase_cnt);
>>+int msm_spm_enable_fts_lpm(int cpu, uint32_t mode);
>>+#else /* defined(CONFIG_QCOM_PM) */
>>+static inline int msm_spm_set_low_power_mode(unsigned int mode, bool notify_rpm)
>>+{ return -ENOSYS; }
>>+static inline int msm_spm_probe_done(void)
>>+{ return -ENOSYS; }
>>+static inline int msm_spm_set_vdd(unsigned int cpu, unsigned int vlevel)
>>+{ return -ENOSYS; }
>>+static inline unsigned int msm_spm_get_vdd(unsigned int cpu)
>>+{ return 0; }
>>+static inline int msm_spm_turn_on_cpu_rail(void __iomem *base,
>>+		unsigned int val, int cpu)
>>+{ return -ENOSYS; }
>>+static inline int msm_spm_device_init(void)
>>+{ return -ENOSYS; }
>>+static void msm_spm_dump_regs(unsigned int cpu) {}
>>+static inline int msm_spm_config_low_power_mode(struct msm_spm_device *dev,
>>+		unsigned int mode, bool notify_rpm)
>>+{ return -ENODEV; }
>>+static inline struct msm_spm_device *msm_spm_get_device_by_name(
>>+		const char *name)
>>+{ return NULL; }
>>+static inline bool msm_spm_is_mode_avail(unsigned int mode)
>>+{ return false; }
>>+static inline int msm_spm_apcs_set_phase(int cpu, unsigned int phase_cnt)
>>+{ return -ENOSYS; }
>>+static inline int msm_spm_enable_fts_lpm(int cpu, uint32_t mode)
>>+{ return -ENOSYS; }
>>+#endif  /* defined (CONFIG_QCOM_PM) */
>>+
>>+#endif  /* __QCOM_SPM_H */
>>
>
>
>-- 
> <http://www.linaro.org/> Linaro.org │ Open source software for ARM SoCs
>
>Follow Linaro:  <http://www.facebook.com/pages/Linaro> Facebook |
><http://twitter.com/#!/linaroorg> Twitter |
><http://www.linaro.org/linaro-blog/> Blog
>

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

* Re: [PATCH v2 08/10] qcom: cpuidle: Add cpuidle driver for QCOM cpus
  2014-08-13 11:22   ` Daniel Lezcano
@ 2014-08-13 14:03     ` Lina Iyer
  0 siblings, 0 replies; 46+ messages in thread
From: Lina Iyer @ 2014-08-13 14:03 UTC (permalink / raw)
  To: Daniel Lezcano
  Cc: khilman, amit.kucheria, sboyd, davidb, galak, linux-arm-msm,
	msivasub, Lorenzo Pieralisi

On Wed, Aug 13, 2014 at 01:22:09PM +0200, Daniel Lezcano wrote:
>On 08/12/2014 09:43 PM, Lina Iyer wrote:
>>Add cpuidle driver interface to allow cpus to go into C-States.
>>
>>Signed-off-by: Lina Iyer <lina.iyer@linaro.org>
>
>Hi Lina,
>
>I think you should based this driver on top of Lorenzo's work.
>
>Cc Lorenzo.

OK, I wasnt aware of Lorenzo's patches. Stephen B pointed that to me
yesterday. Looking at it. Its quite similar to what I have here. I
yanked this driver out of the original patch that does both cpuidle and
SoCIdle, which does the cluster part of Lorenzo's drivers.


-Lina.

>
>>---
>>  .../devicetree/bindings/arm/msm/qcom,cpuidle.txt   |  73 +++++++++++
>>  drivers/cpuidle/Makefile                           |   1 +
>>  drivers/cpuidle/cpuidle-qcom.c                     | 140 +++++++++++++++++++++
>>  3 files changed, 214 insertions(+)
>>  create mode 100644 Documentation/devicetree/bindings/arm/msm/qcom,cpuidle.txt
>>  create mode 100644 drivers/cpuidle/cpuidle-qcom.c
>>
>>diff --git a/Documentation/devicetree/bindings/arm/msm/qcom,cpuidle.txt b/Documentation/devicetree/bindings/arm/msm/qcom,cpuidle.txt
>>new file mode 100644
>>index 0000000..b094baf
>>--- /dev/null
>>+++ b/Documentation/devicetree/bindings/arm/msm/qcom,cpuidle.txt
>>@@ -0,0 +1,73 @@
>>+Qualcomm CPUIdle driver
>>+
>>+The Qualcomm cpuidle driver enables the processor to enter low power modes
>>+when idle. The processors support 4 low power modes.
>>+	wfi - also known as clock gating
>>+	retention - processor clock gated and processor power is reduced.
>>+	standalone-pc - processor is powered down and when reset, the core
>>+		boots into secure mode and trampolines back to the kernel.
>>+		Every core can individually enter this low power mode without
>>+		affecting the state of the other cores.
>>+	pc - essentially standalone power collapse, but indicates that the
>>+		latency to put SoC into a low power state is tolerable.
>>+
>>+The cpuidle node is comprised of nodes, each of which represent a C-State the
>>+processor can achieve. Each node provides the latency and residency which
>>+helps the cpuidle governor to choose the appropriate low power mode based on
>>+the time available. Not all SoCs may support all the above low power modes.
>>+
>>+PROPERTIES
>>+
>>+- compatible:
>>+	Usage: required
>>+	Value type: <string>
>>+	Definition: Should be "qcom,cpuidle"
>>+
>>+- qcom,cpu-level:
>>+	Usage: required
>>+	Value type: { Node }
>>+	Definition: Describes a C-State of the processor
>>+
>>+	PROPERTIES of qcom,cpu-level
>>+
>>+	- reg:
>>+		Usage: required
>>+		Value type: <integer>
>>+		Definition: Index of the C-State
>>+
>>+	- qcom,state-name:
>>+		Usage: required
>>+		Value type: <string>
>>+		Definition: C-State moniker
>>+
>>+	- qcom,spm-cpu-mode:
>>+		Usage: required
>>+		Value type: <string>
>>+		Definition: The description of the h/w mode that will be
>>+			achieved in this C-State.
>>+
>>+	- qcom,latency-us:
>>+		Usage: required
>>+		Value type: <integer>
>>+		Defintion: Time taken to exit from the C-State
>>+
>>+	- qcom,residency-us:
>>+		Usage: required
>>+		Value type: <integer>
>>+		Defintion: Time to be spent in this C-State for the power
>>+			saving to be beneficial.
>>+
>>+Example:
>>+
>>+	qcom,cpuidle {
>>+		compatible = "qcom,cpuidle";
>>+		#address-cells = <1>;
>>+		#size-cells = <0>;
>>+		qcom,cpu-level@0 {
>>+			reg = <0x0>;
>>+			qcom,state-name = "C1";
>>+			qcom,spm-cpu-mode = "wfi";
>>+			qcom,latency-us = <1>;
>>+			qcom,residency-us = <1>;
>>+		};
>>+	};
>>diff --git a/drivers/cpuidle/Makefile b/drivers/cpuidle/Makefile
>>index 11edb31..4a2c446 100644
>>--- a/drivers/cpuidle/Makefile
>>+++ b/drivers/cpuidle/Makefile
>>@@ -16,6 +16,7 @@ obj-$(CONFIG_ARM_ZYNQ_CPUIDLE)		+= cpuidle-zynq.o
>>  obj-$(CONFIG_ARM_U8500_CPUIDLE)         += cpuidle-ux500.o
>>  obj-$(CONFIG_ARM_AT91_CPUIDLE)          += cpuidle-at91.o
>>  obj-$(CONFIG_ARM_EXYNOS_CPUIDLE)        += cpuidle-exynos.o
>>+obj-$(CONFIG_ARM_QCOM_CPUIDLE)		+= cpuidle-qcom.o
>>
>>  ###############################################################################
>>  # MIPS drivers
>>diff --git a/drivers/cpuidle/cpuidle-qcom.c b/drivers/cpuidle/cpuidle-qcom.c
>>new file mode 100644
>>index 0000000..8e70a88
>>--- /dev/null
>>+++ b/drivers/cpuidle/cpuidle-qcom.c
>>@@ -0,0 +1,140 @@
>>+/* Copyright (c) 2014, The Linux Foundation. All rights reserved.
>>+ * Copyright (c) 2014 Linaro.
>>+ *
>>+ * This program is free software; you can redistribute it and/or modify
>>+ * it under the terms of the GNU General Public License version 2 and
>>+ * only version 2 as published by the Free Software Foundation.
>>+ *
>>+ * This program is distributed in the hope that it will be useful,
>>+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
>>+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
>>+ * GNU General Public License for more details.
>>+ *
>>+ */
>>+
>>+#include <linux/module.h>
>>+#include <linux/platform_device.h>
>>+#include <linux/of.h>
>>+#include <linux/cpuidle.h>
>>+
>>+#include <soc/qcom/pm.h>
>>+
>>+struct lookup {
>>+	enum msm_pm_sleep_mode mode;
>>+	char *name;
>>+};
>>+
>>+static enum msm_pm_sleep_mode spm_sleep_modes[MSM_PM_SLEEP_MODE_NR];
>>+
>>+static int qcom_lpm_enter(struct cpuidle_device *dev,
>>+				struct cpuidle_driver *drv,
>>+				int index)
>>+{
>>+	return msm_cpu_pm_enter_sleep(spm_sleep_modes[index], true);
>>+}
>>+
>>+static struct cpuidle_driver qcom_cpuidle_driver = {
>>+	.name	= "qcom_cpuidle",
>>+	.owner	= THIS_MODULE,
>>+};
>>+
>>+static int qcom_cpuidle_probe(struct platform_device *pdev)
>>+{
>>+	int ret;
>>+	struct device_node *top = pdev->dev.of_node;
>>+	struct device_node *n;
>>+	char *key;
>>+	const char *val;
>>+	int index = 0;
>>+	int i;
>>+	struct cpuidle_state *state;
>>+	static const struct lookup pm_sm_lookup[] = {
>>+		{MSM_PM_SLEEP_MODE_WAIT_FOR_INTERRUPT,
>>+			"wfi"},
>>+		{MSM_PM_SLEEP_MODE_POWER_COLLAPSE_STANDALONE,
>>+			"standalone_pc"},
>>+		{MSM_PM_SLEEP_MODE_POWER_COLLAPSE,
>>+			"pc"},
>>+		{MSM_PM_SLEEP_MODE_RETENTION,
>>+			"retention"},
>>+	};
>>+
>>+	if (!top)
>>+		return -ENODEV;
>>+
>>+	for_each_child_of_node(top, n) {
>>+		key = "qcom,cpu-level";
>>+		if (of_node_cmp(n->name, key))
>>+			continue;
>>+
>>+		state = &qcom_cpuidle_driver.states[index];
>>+
>>+		key = "qcom,spm-cpu-mode";
>>+		ret = of_property_read_string(n, key, &val);
>>+		if (ret)
>>+			goto failed;
>>+		for (i = 0; i < ARRAY_SIZE(pm_sm_lookup); i++) {
>>+			if (!strcmp(val, pm_sm_lookup[i].name)) {
>>+				spm_sleep_modes[index] = pm_sm_lookup[i].mode;
>>+				break;
>>+			}
>>+		}
>>+		if (i == ARRAY_SIZE(pm_sm_lookup)) {
>>+			ret = -EFAULT;
>>+			goto failed;
>>+		}
>>+
>>+		strncpy(state->desc, val, CPUIDLE_DESC_LEN);
>>+
>>+		key = "qcom,state-name";
>>+		ret = of_property_read_string(n, key, &val);
>>+		if (ret)
>>+			goto failed;
>>+		strncpy(state->name, val, CPUIDLE_NAME_LEN);
>>+
>>+		key = "qcom,latency-us";
>>+		ret = of_property_read_u32(n, key, &state->exit_latency);
>>+		if (ret)
>>+			goto failed;
>>+
>>+		key = "qcom,residency-us";
>>+		ret = of_property_read_u32(n, key, &state->target_residency);
>>+		if (ret)
>>+			goto failed;
>>+
>>+		state->flags = CPUIDLE_FLAG_TIME_VALID;
>>+		state->enter = qcom_lpm_enter;
>>+		index++;
>>+	}
>>+
>>+	qcom_cpuidle_driver.state_count = index;
>>+	qcom_cpuidle_driver.safe_state_index = 0;
>>+
>>+	ret = cpuidle_register(&qcom_cpuidle_driver, NULL);
>>+	if (ret) {
>>+		dev_err(&pdev->dev, "failed to register cpuidle driver\n");
>>+		return ret;
>>+	}
>>+
>>+	return 0;
>>+
>>+failed:
>>+	dev_err(&pdev->dev, "error parsing key: %s\n", key);
>>+	return ret;
>>+}
>>+
>>+static struct of_device_id qcom_cpuidle_match_tbl[] = {
>>+	{.compatible = "qcom,cpuidle"},
>>+	{},
>>+};
>>+
>>+static struct platform_driver qcom_cpuidle_platform_driver = {
>>+	.probe	= qcom_cpuidle_probe,
>>+	.driver	= {
>>+		.name = "qcom,cpuidle",
>>+		.owner = THIS_MODULE,
>>+		.of_match_table = qcom_cpuidle_match_tbl,
>>+	},
>>+};
>>+
>>+module_platform_driver(qcom_cpuidle_platform_driver);
>>
>
>
>-- 
> <http://www.linaro.org/> Linaro.org │ Open source software for ARM SoCs
>
>Follow Linaro:  <http://www.facebook.com/pages/Linaro> Facebook |
><http://twitter.com/#!/linaroorg> Twitter |
><http://www.linaro.org/linaro-blog/> Blog
>

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

* Re: [PATCH v2 07/10] qcom: msm-pm: Add cpu low power mode functions
  2014-08-13 11:18   ` Daniel Lezcano
@ 2014-08-13 14:16     ` Lina Iyer
  2014-08-14 14:24       ` Daniel Lezcano
  2014-08-14 16:11       ` Daniel Lezcano
  0 siblings, 2 replies; 46+ messages in thread
From: Lina Iyer @ 2014-08-13 14:16 UTC (permalink / raw)
  To: Daniel Lezcano
  Cc: khilman, amit.kucheria, sboyd, davidb, galak, linux-arm-msm,
	msivasub, Venkat Devarasetty, Nicolas Pitre

On Wed, Aug 13, 2014 at 01:18:01PM +0200, Daniel Lezcano wrote:
>On 08/12/2014 09:43 PM, Lina Iyer wrote:
>>Add interface layer to abstract and handle hardware specific
>>functionality for executing various cpu low power modes in QCOM
>>chipsets.
>>
>>Signed-off-by: Venkat Devarasetty <vdevaras@codeaurora.org>
>>Signed-off-by: Mahesh Sivasubramanian <msivasub@codeaurora.org>
>>Signed-off-by: Lina Iyer <lina.iyer@linaro.org>
>>---
>>  drivers/soc/qcom/Makefile |   2 +-
>>  drivers/soc/qcom/msm-pm.c | 219 ++++++++++++++++++++++++++++++++++++++++++++++
>>  include/soc/qcom/pm.h     |  39 +++++++++
>>  3 files changed, 259 insertions(+), 1 deletion(-)
>>  create mode 100644 drivers/soc/qcom/msm-pm.c
>>  create mode 100644 include/soc/qcom/pm.h
>>
>>diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile
>>index d7ae93b..7925f83 100644
>>--- a/drivers/soc/qcom/Makefile
>>+++ b/drivers/soc/qcom/Makefile
>>@@ -1,5 +1,5 @@
>>  obj-$(CONFIG_QCOM_GSBI)	+=	qcom_gsbi.o
>>-obj-$(CONFIG_QCOM_PM) +=	spm-devices.o spm.o
>>+obj-$(CONFIG_QCOM_PM) +=	spm-devices.o spm.o msm-pm.o
>>
>>  CFLAGS_scm.o :=$(call as-instr,.arch_extension sec,-DREQUIRES_SEC=1)
>>  obj-$(CONFIG_QCOM_SCM) += scm.o scm-boot.o
>>diff --git a/drivers/soc/qcom/msm-pm.c b/drivers/soc/qcom/msm-pm.c
>>new file mode 100644
>>index 0000000..f2f15b8
>>--- /dev/null
>>+++ b/drivers/soc/qcom/msm-pm.c
>>@@ -0,0 +1,219 @@
>>+/* Copyright (c) 2010-2014, The Linux Foundation. All rights reserved.
>>+ *
>>+ * This program is free software; you can redistribute it and/or modify
>>+ * it under the terms of the GNU General Public License version 2 and
>>+ * only version 2 as published by the Free Software Foundation.
>>+ *
>>+ * This program is distributed in the hope that it will be useful,
>>+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
>>+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
>>+ * GNU General Public License for more details.
>>+ *
>>+ */
>>+
>>+#include <linux/module.h>
>>+#include <linux/kernel.h>
>>+#include <linux/init.h>
>>+#include <linux/io.h>
>>+#include <linux/smp.h>
>>+#include <linux/tick.h>
>>+#include <linux/platform_device.h>
>>+#include <linux/cpu_pm.h>
>>+#include <linux/uaccess.h>
>>+
>>+#include <soc/qcom/spm.h>
>>+#include <soc/qcom/pm.h>
>>+#include <soc/qcom/scm.h>
>>+#include <soc/qcom/scm-boot.h>
>>+
>>+#include <asm/suspend.h>
>>+#include <asm/cacheflush.h>
>>+#include <asm/cputype.h>
>>+#include <asm/system_misc.h>
>>+
>>+#define SCM_CMD_TERMINATE_PC	(0x2)
>>+#define SCM_CMD_CORE_HOTPLUGGED (0x10)
>>+#define SCM_FLUSH_FLAG_MASK	(0x3)
>>+
>>+static bool msm_pm_is_L1_writeback(void)
>>+{
>>+	u32 cache_id = 0;
>>+
>>+#if defined(CONFIG_CPU_V7)
>>+	u32 sel = 0;
>>+
>>+	asm volatile ("mcr p15, 2, %[ccselr], c0, c0, 0\n\t"
>>+		      "isb\n\t"
>>+		      "mrc p15, 1, %[ccsidr], c0, c0, 0\n\t"
>>+		      :[ccsidr]"=r" (cache_id)
>>+		      :[ccselr]"r" (sel)
>>+		     );
>>+	return cache_id & BIT(30);
>>+#elif defined(CONFIG_ARM64)
>>+	u32 sel = 0;
>>+	asm volatile("msr csselr_el1, %[ccselr]\n\t"
>>+		     "isb\n\t"
>>+		     "mrs %[ccsidr],ccsidr_el1\n\t"
>>+		     :[ccsidr]"=r" (cache_id)
>>+		     :[ccselr]"r" (sel)
>>+		    );
>>+	return cache_id & BIT(30);
>>+#else
>>+#error No valid CPU arch selected
>>+#endif
>>+}
>>+
>>+static inline void msm_arch_idle(void)
>>+{
>>+	/* Flush and clock-gate */
>>+	mb();
>
>Why is needed this memory barrier ?
Some QCOM SoCs needed this. I am not sure which one anymore. :(
>
>>+	wfi();
>>+}
>>+
>>+static bool msm_pm_swfi(bool from_idle)
>>+{
>>+	msm_arch_idle();
>>+	return true;
>>+}
>>+
>>+static bool msm_pm_retention(bool from_idle)
>>+{
>>+	int ret = 0;
>>+
>>+	ret = msm_spm_set_low_power_mode(MSM_SPM_MODE_RETENTION, false);
>>+	WARN_ON(ret);
>>+
>>+	msm_arch_idle();
>>+
>>+	ret = msm_spm_set_low_power_mode(MSM_SPM_MODE_CLOCK_GATING, false);
>>+	WARN_ON(ret);
>
>Why do you need to set the clock gating mode each time you exit the 
>retention mode ?
So if the SPM did not reset to clockgating, we would not do retention
when we intended to do clockgating. Btw, we dont set clockgating
everytime we do clockgating, helps reduce the latency in doing WFI.
>
>>+	return true;
>>+}
>>+
>>+static int msm_pm_collapse(unsigned long from_idle)
>>+{
>>+	enum msm_pm_l2_scm_flag flag = MSM_SCM_L2_ON;
>>+
>>+	/**
>>+	 * Single core processors need to have L2
>>+	 * flushed when powering down the core.
>>+	 * Notify SCM to flush secure L2 lines.
>>+	 */
>>+	if (num_possible_cpus() == 1)
>>+		flag = MSM_SCM_L2_OFF;
>
>I am wondering if this shouldn't be handle by a mcpm driver.
>
>Cc nico.

Well, possibly, sorry, not sure what features of the mcpm driver you
think I need here? 

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

* Re: [PATCH v2 05/10] arm: qcom-msm8974: Add CPU phandles to CPU definitions
  2014-08-12 19:43 ` [PATCH v2 05/10] arm: qcom-msm8974: Add CPU phandles to CPU definitions Lina Iyer
  2014-08-12 21:09   ` Kumar Gala
@ 2014-08-14 10:04   ` Pramod Gurav
  1 sibling, 0 replies; 46+ messages in thread
From: Pramod Gurav @ 2014-08-14 10:04 UTC (permalink / raw)
  To: Lina Iyer
  Cc: daniel.lezcano, khilman, amit.kucheria, sboyd, davidb, galak,
	linux-arm-msm, msivasub



On Wednesday 13 August 2014 01:13 AM, Lina Iyer wrote:
> Add CPU phandle labels for all Krait CPUS.
> 
> Signed-off-by: Lina Iyer <lina.iyer@linaro.org>
> ---
>  arch/arm/boot/dts/qcom-msm8974.dtsi | 8 ++++----
>  1 file changed, 4 insertions(+), 4 deletions(-)
> 
Hi Lina,
These changes are already done  by Stephen's below cpufreq patch whicth
adds OPP table for 8974. You might want to sync them up.

Author: Stephen Boyd <sboyd@codeaurora.org>
Date:   Wed Jun 25 05:36:23 2014 +0530

    ARM: dts: qcom: Add necessary DT data for Krait cpufreq

    Add the necessary DT nodes and data so we can probe the cpufreq
    driver on MSM devices with Krait CPUs.

    Signed-off-by: Stephen Boyd <sboyd@codeaurora.org>

> diff --git a/arch/arm/boot/dts/qcom-msm8974.dtsi b/arch/arm/boot/dts/qcom-msm8974.dtsi



Thanks
Pramod

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

* Re: [PATCH v2 02/10] msm: scm: Add SCM warmboot flags for quad core targets.
  2014-08-12 19:43 ` [PATCH v2 02/10] msm: scm: Add SCM warmboot flags for quad core targets Lina Iyer
@ 2014-08-14 10:20   ` Pramod Gurav
  0 siblings, 0 replies; 46+ messages in thread
From: Pramod Gurav @ 2014-08-14 10:20 UTC (permalink / raw)
  To: Lina Iyer
  Cc: daniel.lezcano, khilman, amit.kucheria, sboyd, davidb, galak,
	linux-arm-msm, msivasub

On Wednesday 13 August 2014 01:13 AM, Lina Iyer wrote:
> Quad core targets like APQ8074, APQ8064, APQ8084 need SCM support set up
> warm boot addresses in the Secure Monitor. Extend the SCM flags to
> support warmboot addresses for seconday cores.
> 
> Signed-off-by: Lina Iyer <lina.iyer@linaro.org>
> ---
>  include/soc/qcom/scm-boot.h | 3 +++
>  1 file changed, 3 insertions(+)
> 
> diff --git a/include/soc/qcom/scm-boot.h b/include/soc/qcom/scm-boot.h
> index 6aabb24..7ae2152 100644
> --- a/include/soc/qcom/scm-boot.h
> +++ b/include/soc/qcom/scm-boot.h
> @@ -18,6 +18,9 @@
>  #define SCM_FLAG_COLDBOOT_CPU3		0x20
>  #define SCM_FLAG_WARMBOOT_CPU0		0x04
>  #define SCM_FLAG_WARMBOOT_CPU1		0x02
> +#define SCM_FLAG_WARMBOOT_CPU2		0x10
> +#define SCM_FLAG_WARMBOOT_CPU3		0x40
> +
An extra line introduced here.

>  
>  int scm_set_boot_addr(phys_addr_t addr, int flags);
>  
> 

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

* Re: [PATCH v2 03/10] qcom: spm: Add Subsystem Power Manager (SPM) driver for QCOM chipsets
  2014-08-12 19:43 ` [PATCH v2 03/10] qcom: spm: Add Subsystem Power Manager (SPM) driver for QCOM chipsets Lina Iyer
  2014-08-13 10:49   ` Daniel Lezcano
@ 2014-08-14 13:01   ` Pramod Gurav
  2014-08-14 15:18     ` Lina Iyer
  2014-08-14 15:16   ` Kumar Gala
  2014-08-14 16:09   ` Kumar Gala
  3 siblings, 1 reply; 46+ messages in thread
From: Pramod Gurav @ 2014-08-14 13:01 UTC (permalink / raw)
  To: Lina Iyer
  Cc: daniel.lezcano, khilman, amit.kucheria, sboyd, davidb, galak,
	linux-arm-msm, msivasub, Praveen Chidamabram, Murali Nalajala

On Wednesday 13 August 2014 01:13 AM, Lina Iyer wrote:
> Qualcomm chipsets use an separate h/w block to control the logic around
> the processor cores (cpu and L2). The SPM h/w block regulates power to
> the cores and controls the power when the core enter low power modes.
> 
> Each core has its own instance of SPM. The SPM has the following key
> functions
> 	- Configure the h/w dependencies when entering low power modes
> 	- Wait for interrupt and wake up on interrupt
> 	- Ensure the dependencies are ready before bringing the core out
> 	  of sleep
> 	- Regulating voltage to the core, interfacing with the PMIC.
> 	- Optimize power based on runtime recommendations.
> 
> The driver identifies and configures the SPMs, by reading the nodes and
> the register values from the devicetree. The SPMs need to be configured
> to allow the processor to be idled in a low power state.
> 
> Signed-off-by: Praveen Chidamabram <pchidamb@codeaurora.org>
> Signed-off-by: Murali Nalajala <mnalajal@codeaurora.org>
> Signed-off-by: Lina Iyer <lina.iyer@linaro.org>
> ---
> +

<snip>

>  CFLAGS_scm.o :=$(call as-instr,.arch_extension sec,-DREQUIRES_SEC=1)
>  obj-$(CONFIG_QCOM_SCM) += scm.o scm-boot.o
> diff --git a/drivers/soc/qcom/spm-devices.c b/drivers/soc/qcom/spm-devices.c
> new file mode 100644
> index 0000000..567e9f9
> --- /dev/null
> +++ b/drivers/soc/qcom/spm-devices.c

<snip>

> +	if (ret)
> +		return ret;
> +
> +	return dev->cpu_vdd;
> +}
> +EXPORT_SYMBOL(msm_spm_get_vdd);
> +
> +static void msm_spm_config_q2s(struct msm_spm_device *dev, unsigned int mode)
> +{
> +	uint32_t spm_legacy_mode = 0;
> +	uint32_t qchannel_ignore = 0;
> +	uint32_t val = 0;
Initialization not needed for val.
> +
> +	if (!dev->q2s_reg)
> +		return;
> +
> +	switch (mode) {
> +	case MSM_SPM_MODE_DISABLED:
> +	case MSM_SPM_MODE_CLOCK_GATING:
> +		qchannel_ignore = 1;
> +		spm_legacy_mode = 0;
> +		break;
> +	case MSM_SPM_MODE_RETENTION:
> +		qchannel_ignore = 0;
> +		spm_legacy_mode = 0;
> +		break;
> +	case MSM_SPM_MODE_GDHS:
> +	case MSM_SPM_MODE_POWER_COLLAPSE:
> +		qchannel_ignore = 0;
> +		spm_legacy_mode = 1;
> +		break;
> +	default:
> +		break;
> +	}
> +
> +	val = spm_legacy_mode << 2 | qchannel_ignore << 1;
> +	__raw_writel(val, dev->q2s_reg);
> +	mb();
This may need a comment around it.
> +}
> +
> +static int msm_spm_dev_set_low_power_mode(struct msm_spm_device *dev,
> +		unsigned int mode, bool notify_rpm)
> +{
> +	uint32_t i;
> +	uint32_t start_addr = 0;
> +	int ret = -EINVAL;
> +	bool pc_mode = false;
> +
> +	if (!dev->initialized)
> +		return -ENXIO;
> +
> +	if ((mode == MSM_SPM_MODE_POWER_COLLAPSE)
> +			|| (mode == MSM_SPM_MODE_GDHS))
> +		pc_mode = true;
> +
> +	if (mode == MSM_SPM_MODE_DISABLED) {
> +		ret = msm_spm_drv_set_spm_enable(&dev->reg_data, false);
> +	} else if (!msm_spm_drv_set_spm_enable(&dev->reg_data, true)) {
> +		for (i = 0; i < dev->num_modes; i++) {
> +			if ((dev->modes[i].mode == mode) &&
> +				(dev->modes[i].notify_rpm == notify_rpm)) {
> +				start_addr = dev->modes[i].start_addr;
> +				break;
> +			}
> +		}
> +		ret = msm_spm_drv_set_low_power_mode(&dev->reg_data,
> +					start_addr, pc_mode);
> +	}
> +
> +	msm_spm_config_q2s(dev, mode);
> +
> +	return ret;
> +}
> +
> +static int msm_spm_dev_init(struct msm_spm_device *dev,
> +		struct msm_spm_platform_data *data)
> +{
> +	int i, ret = -ENOMEM;
> +	uint32_t offset = 0;
> +
> +	dev->cpu_vdd = VDD_DEFAULT;
> +	dev->num_modes = data->num_modes;
> +	dev->modes = kmalloc(
> +			sizeof(struct msm_spm_power_modes) * dev->num_modes,
> +			GFP_KERNEL);
> +
> +	if (!dev->modes)
> +		goto spm_failed_malloc;
> +
> +	dev->reg_data.major = data->major;
> +	dev->reg_data.minor = data->minor;
> +	ret = msm_spm_drv_init(&dev->reg_data, data);
> +
Please remove extra line.
> +	if (ret)
> +		goto spm_failed_init;
> +
> +	for (i = 0; i < dev->num_modes; i++) {
> +
> +		/* Default offset is 0 and gets updated as we write more
> +		 * sequences into SPM
> +		 */
> +		dev->modes[i].start_addr = offset;
> +		ret = msm_spm_drv_write_seq_data(&dev->reg_data,
> +						data->modes[i].cmd, &offset);
> +		if (ret < 0)
> +			goto spm_failed_init;
> +
> +		dev->modes[i].mode = data->modes[i].mode;
> +		dev->modes[i].notify_rpm = data->modes[i].notify_rpm;
> +	}
> +	msm_spm_drv_reinit(&dev->reg_data);
> +	dev->initialized = true;
> +	return 0;
> +
> +spm_failed_init:
> +	kfree(dev->modes);
Should not we release dev->reg_seq_entry_shadow allocated in
msm_spm_drv_init?
> +spm_failed_malloc:
> +	return ret;
> +}
> +
> +/**
> + * msm_spm_turn_on_cpu_rail(): Power on cpu rail before turning on core

<snip>

> +int __init msm_spm_init(struct msm_spm_platform_data *data, int nr_devs)
> +{
> +	unsigned int cpu;
> +	int ret = 0;
Initialization not needed.
> +
> +	BUG_ON((nr_devs < num_possible_cpus()) || !data);
> +
> +	for_each_possible_cpu(cpu) {
> +		struct msm_spm_device *dev = &per_cpu(msm_cpu_spm_device, cpu);
> +
> +		ret = msm_spm_dev_init(dev, &data[cpu]);
> +		if (ret < 0) {
> +			pr_warn("%s():failed CPU:%u ret:%d\n", __func__,

<snip>

> +static struct msm_spm_device *msm_spm_get_device(struct platform_device *pdev)
> +{
> +	struct msm_spm_device *dev = NULL;
> +	const char *val = NULL;
> +	char *key = "qcom,name";
> +	int cpu = get_cpu_id(pdev->dev.of_node);
> +
> +	if ((cpu >= 0) && cpu < num_possible_cpus())
> +		dev = &per_cpu(msm_cpu_spm_device, cpu);
> +	else if ((cpu == 0xffff) || (cpu < 0))
> +		dev = devm_kzalloc(&pdev->dev, sizeof(struct msm_spm_device),
> +					GFP_KERNEL);
> +
> +	if (!dev)
> +		return NULL;
Should we return -ENOMEM? ERR_PTR(-ENOMEM)?
> +
> +	if (of_property_read_string(pdev->dev.of_node, key, &val)) {
> +		pr_err("%s(): Cannot find a required node key:%s\n",
> +				__func__, key);
Should it be dev_err as we have access to pdev->dev?
> +		return NULL;
> +	}
> +	dev->name = val;
> +	list_add(&dev->list, &spm_list);
> +
> +	return dev;
> +}
> +
> +static void get_cpumask(struct device_node *node, struct cpumask *mask)
> +{
> +	unsigned long vctl_mask = 0;
> +	unsigned c = 0;
> +	int idx = 0;
> +	struct device_node *cpu_node = NULL;
> +	int ret = 0;
Initialization not needed for ret.
> +	char *key = "qcom,cpu-vctl-list";
> +	bool found = false;
> +
> +	cpu_node = of_parse_phandle(node, key, idx++);
> +	while (cpu_node) {
> +		found = true;
> +		for_each_possible_cpu(c) {
> +			if (of_get_cpu_node(c, NULL) == cpu_node)
> +				cpumask_set_cpu(c, mask);
> +		}
> +		cpu_node = of_parse_phandle(node, key, idx++);
> +	};
> +
> +	if (found)
> +		return;
> +
> +	key = "qcom,cpu-vctl-mask";
> +	ret = of_property_read_u32(node, key, (u32 *) &vctl_mask);
> +	if (!ret) {
> +		for_each_set_bit(c, &vctl_mask, num_possible_cpus()) {
> +			cpumask_set_cpu(c, mask);
> +		}
> +	}
> +}
> +
> +static int msm_spm_dev_probe(struct platform_device *pdev)
> +{
> +	int ret = 0;
> +	int cpu = 0;
> +	int i = 0;
Initialization not needed for all three variables.
> +	struct device_node *node = pdev->dev.of_node;
> +	struct msm_spm_platform_data spm_data;
> +	char *key = NULL;
> +	uint32_t val = 0;
> +	struct msm_spm_seq_entry modes[MSM_SPM_MODE_NR];
> +	int len = 0;
> +	struct msm_spm_device *dev = NULL;
> +	struct resource *res = NULL;
> +	uint32_t mode_count = 0;
> +
> +	struct spm_of {
> +		char *key;
> +		uint32_t id;
> +	};
> +
> +	struct spm_of spm_of_data[] = {
> +		{"qcom,saw2-cfg", MSM_SPM_REG_SAW2_CFG},
> +		{"qcom,saw2-spm-dly", MSM_SPM_REG_SAW2_SPM_DLY},
> +		{"qcom,saw2-spm-ctl", MSM_SPM_REG_SAW2_SPM_CTL},
> +		{"qcom,saw2-pmic-data0", MSM_SPM_REG_SAW2_PMIC_DATA_0},
> +		{"qcom,saw2-pmic-data1", MSM_SPM_REG_SAW2_PMIC_DATA_1},
> +		{"qcom,saw2-pmic-data2", MSM_SPM_REG_SAW2_PMIC_DATA_2},
> +		{"qcom,saw2-pmic-data3", MSM_SPM_REG_SAW2_PMIC_DATA_3},
> +		{"qcom,saw2-pmic-data4", MSM_SPM_REG_SAW2_PMIC_DATA_4},
> +		{"qcom,saw2-pmic-data5", MSM_SPM_REG_SAW2_PMIC_DATA_5},
> +		{"qcom,saw2-pmic-data6", MSM_SPM_REG_SAW2_PMIC_DATA_6},
> +		{"qcom,saw2-pmic-data7", MSM_SPM_REG_SAW2_PMIC_DATA_7},
> +	};
> +
> +	struct mode_of {
> +		char *key;
> +		uint32_t id;
> +		uint32_t notify_rpm;
> +	};
> +
> +	struct mode_of mode_of_data[] = {
> +		{"qcom,saw2-spm-cmd-wfi", MSM_SPM_MODE_CLOCK_GATING, 0},
> +		{"qcom,saw2-spm-cmd-ret", MSM_SPM_MODE_RETENTION, 0},
> +		{"qcom,saw2-spm-cmd-gdhs", MSM_SPM_MODE_GDHS, 1},
> +		{"qcom,saw2-spm-cmd-spc", MSM_SPM_MODE_POWER_COLLAPSE, 0},
> +		{"qcom,saw2-spm-cmd-pc", MSM_SPM_MODE_POWER_COLLAPSE, 1},
> +	};
> +
> +	dev = msm_spm_get_device(pdev);
> +	if (!dev) {
> +		ret = -ENOMEM;
> +		goto fail;
I might be misunderstanding something, but why cant we just return from
here instead of jumping to fail.
> +	}
> +	get_cpumask(node, &dev->mask);
> +
> +	memset(&spm_data, 0, sizeof(struct msm_spm_platform_data));
> +	memset(&modes, 0,
> +		(MSM_SPM_MODE_NR - 2) * sizeof(struct msm_spm_seq_entry));
> +
> +	if (of_device_is_compatible(node, "qcom,spm-v2.1")) {
> +		spm_data.major = 2;
> +		spm_data.minor = 1;
> +	} else if (of_device_is_compatible(node, "qcom,spm-v3.0")) {
> +		spm_data.major = 3;
> +		spm_data.minor = 0;
> +	}
> +
> +	key = "qcom,vctl-timeout-us";
> +	ret = of_property_read_u32(node, key, &val);
> +	if (!ret)
> +		spm_data.vctl_timeout_us = val;
> +
> +	/* SAW start address */
> +	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> +	if (!res) {
> +		ret = -EFAULT;
Should it return -EINVAL?
> +		goto fail;
> +	}
> +
> +	spm_data.reg_base_addr = devm_ioremap(&pdev->dev, res->start,
> +					resource_size(res));
> +	if (!spm_data.reg_base_addr) {
> +		ret = -ENOMEM;
> +		goto fail;
> +	}
> +
> +	spm_data.vctl_port = -1;
> +	spm_data.phase_port = -1;
> +	spm_data.pfm_port = -1;
> +
> +	key = "qcom,vctl-port";
> +	of_property_read_u32(node, key, &spm_data.vctl_port);
> +
> +	key = "qcom,phase-port";
> +	of_property_read_u32(node, key, &spm_data.phase_port);
> +
> +	key = "qcom,pfm-port";
> +	of_property_read_u32(node, key, &spm_data.pfm_port);
> +
> +	/* Q2S (QChannel-2-SPM) register */
> +	res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
> +	if (res) {
> +		dev->q2s_reg = devm_ioremap(&pdev->dev, res->start,
> +						resource_size(res));
> +		if (!dev->q2s_reg) {
> +			pr_err("%s(): Unable to iomap Q2S register\n",
Can we use dev_err since pdev->dev is accessible?
> +					__func__);
> +			ret = -EADDRNOTAVAIL;
ret = -ENOMEM?
> +			goto fail;
> +		}
> +	}
> +	/*
> +	 * At system boot, cpus and or clusters can remain in reset. CCI SPM
> +	 * will not be triggered unless SPM_LEGACY_MODE bit is set for the
> +	 * cluster in reset. Initialize q2s registers and set the
> +	 * SPM_LEGACY_MODE bit.
> +	 */
> +	msm_spm_config_q2s(dev, MSM_SPM_MODE_POWER_COLLAPSE);
> +
> +	for (i = 0; i < ARRAY_SIZE(spm_of_data); i++) {
> +		ret = of_property_read_u32(node, spm_of_data[i].key, &val);
> +		if (ret)
> +			continue;
> +		spm_data.reg_init_values[spm_of_data[i].id] = val;
> +	}
> +
> +	for (i = 0; i < ARRAY_SIZE(mode_of_data); i++) {
> +		key = mode_of_data[i].key;
> +		modes[mode_count].cmd =
> +			(uint8_t *)of_get_property(node, key, &len);
> +		if (!modes[mode_count].cmd)
> +			continue;
> +		modes[mode_count].mode = mode_of_data[i].id;
> +		modes[mode_count].notify_rpm = mode_of_data[i].notify_rpm;
> +		pr_debug("%s(): dev: %s cmd:%s, mode:%d rpm:%d\n", __func__,
dev_info instead of pr_debug?
> +				dev->name, key, modes[mode_count].mode,
> +				modes[mode_count].notify_rpm);
> +		mode_count++;
> +	}
> +
> +	spm_data.modes = modes;
> +	spm_data.num_modes = mode_count;
> +
> +	ret = msm_spm_dev_init(dev, &spm_data);
> +	if (ret)
> +		goto fail;
> +
> +	platform_set_drvdata(pdev, dev);
> +
> +	for_each_cpu(cpu, &dev->mask)
> +		per_cpu(cpu_vctl_device, cpu) = dev;
> +
> +	return ret;
> +
> +fail:
> +	cpu = get_cpu_id(pdev->dev.of_node);
> +	if (dev && (cpu >= num_possible_cpus() || (cpu < 0))) {
> +		for_each_cpu(cpu, &dev->mask)
> +			per_cpu(cpu_vctl_device, cpu) = ERR_PTR(ret);
Should this be part of remove function instead of here as it is last
initialization that is done in probe just before fail lable? If it is
not needed the we can do away with this part of code from fail lable.
> +	}
Are we missing a list_del like in remove function below?
> +
> +	pr_err("%s: CPU%d SPM device probe failed: %d\n", __func__, cpu, ret);
dev_ instead of pr_?
> +
> +	return ret;
> +}
> +
> +static int msm_spm_dev_remove(struct platform_device *pdev)
> +{
> +	struct msm_spm_device *dev = platform_get_drvdata(pdev);
> +
> +	list_del(&dev->list);
kfree(dev->modes) is missing.
kfree(dev->reg_data->reg_seq_entry_shadow) is missing.
> +
> +	return 0;
> +}
> +
> +static struct of_device_id msm_spm_match_table[] = {
> +	{.compatible = "qcom,spm-v2.1"},
> +	{.compatible = "qcom,spm-v3.0"},
> +	{},
> +};
> +
> +static struct platform_driver msm_spm_device_driver = {
> +	.probe = msm_spm_dev_probe,
> +	.remove = msm_spm_dev_remove,
> +	.driver = {
> +		.name = "spm-v2",
> +		.owner = THIS_MODULE,
> +		.of_match_table = msm_spm_match_table,
> +	},
> +};
> +
> +/**
> + * msm_spm_device_init(): Device tree initialization function
> + */
> +int __init msm_spm_device_init(void)
It should be static.
> +{
> +	static bool registered;
> +
> +	if (registered)
> +		return 0;
> +
> +	registered = true;
> +
> +	return platform_driver_register(&msm_spm_device_driver);
> +}
> +device_initcall(msm_spm_device_init);
> diff --git a/drivers/soc/qcom/spm.c b/drivers/soc/qcom/spm.c
> new file mode 100644
> index 0000000..7dbdb64
> --- /dev/null
> +++ b/drivers/soc/qcom/spm.c
> @@ -0,0 +1,482 @@
> +/* Copyright (c) 2011-2014, The Linux Foundation. All rights reserved.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 and
> + * only version 2 as published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + * GNU General Public License for more details.
> + *
> + */
> +
> +#include <linux/module.h>
> +#include <linux/kernel.h>
> +#include <linux/delay.h>
> +#include <linux/init.h>
> +#include <linux/io.h>
> +#include <linux/slab.h>
> +
> +#include "spm_driver.h"
> +
> +#define MSM_SPM_PMIC_STATE_IDLE  0
> +
> +enum {
> +	MSM_SPM_DEBUG_SHADOW = 1U << 0,
> +	MSM_SPM_DEBUG_VCTL = 1U << 1,
> +};
> +
> +static int msm_spm_debug_mask;
> +module_param_named(
> +	debug_mask, msm_spm_debug_mask, int, S_IRUGO | S_IWUSR | S_IWGRP
> +);
> +
> +struct saw2_data {
> +	const char *ver_name;
> +	uint32_t major;
> +	uint32_t minor;
> +	uint32_t *spm_reg_offset_ptr;
> +};
> +
> +static uint32_t msm_spm_reg_offsets_saw2_v2_1[MSM_SPM_REG_NR] = {
> +	[MSM_SPM_REG_SAW2_SECURE]		= 0x00,
> +	[MSM_SPM_REG_SAW2_ID]			= 0x04,
> +	[MSM_SPM_REG_SAW2_CFG]			= 0x08,
> +	[MSM_SPM_REG_SAW2_SPM_STS]		= 0x0C,
> +	[MSM_SPM_REG_SAW2_AVS_STS]		= 0x10,
> +	[MSM_SPM_REG_SAW2_PMIC_STS]		= 0x14,
> +	[MSM_SPM_REG_SAW2_RST]			= 0x18,
> +	[MSM_SPM_REG_SAW2_VCTL]			= 0x1C,
> +	[MSM_SPM_REG_SAW2_AVS_CTL]		= 0x20,
> +	[MSM_SPM_REG_SAW2_AVS_LIMIT]		= 0x24,
> +	[MSM_SPM_REG_SAW2_AVS_DLY]		= 0x28,
> +	[MSM_SPM_REG_SAW2_AVS_HYSTERESIS]	= 0x2C,
> +	[MSM_SPM_REG_SAW2_SPM_CTL]		= 0x30,
> +	[MSM_SPM_REG_SAW2_SPM_DLY]		= 0x34,
> +	[MSM_SPM_REG_SAW2_PMIC_DATA_0]		= 0x40,
> +	[MSM_SPM_REG_SAW2_PMIC_DATA_1]		= 0x44,
> +	[MSM_SPM_REG_SAW2_PMIC_DATA_2]		= 0x48,
> +	[MSM_SPM_REG_SAW2_PMIC_DATA_3]		= 0x4C,
> +	[MSM_SPM_REG_SAW2_PMIC_DATA_4]		= 0x50,msm_spm_drv_init
> +	[MSM_SPM_REG_SAW2_PMIC_DATA_5]		= 0x54,
> +	[MSM_SPM_REG_SAW2_PMIC_DATA_6]		= 0x58,
> +	[MSM_SPM_REG_SAW2_PMIC_DATA_7]		= 0x5C,
> +	[MSM_SPM_REG_SAW2_SEQ_ENTRY]		= 0x80,
> +	[MSM_SPM_REG_SAW2_VERSION]		= 0xFD0,
> +};
> +
> +static uint32_t msm_spm_reg_offsets_saw2_v3_0[MSM_SPM_REG_NR] = {
> +	[MSM_SPM_REG_SAW2_SECURE]		= 0x00,
> +	[MSM_SPM_REG_SAW2_ID]			= 0x04,
> +	[MSM_SPM_REG_SAW2_CFG]			= 0x08,
> +	[MSM_SPM_REG_SAW2_SPM_STS]		= 0x0C,
> +	[MSM_SPM_REG_SAW2_AVS_STS]		= 0x10,
> +	[MSM_SPM_REG_SAW2_PMIC_STS]		= 0x14,
> +	[MSM_SPM_REG_SAW2_RST]			= 0x18,
> +	[MSM_SPM_REG_SAW2_VCTL]			= 0x1C,
> +	[MSM_SPM_REG_SAW2_AVS_CTL]		= 0x20,
> +	[MSM_SPM_REG_SAW2_AVS_LIMIT]		= 0x24,
> +	[MSM_SPM_REG_SAW2_AVS_DLY]		= 0x28,
> +	[MSM_SPM_REG_SAW2_AVS_HYSTERESIS]	= 0x2C,
> +	[MSM_SPM_REG_SAW2_SPM_CTL]		= 0x30,
> +	[MSM_SPM_REG_SAW2_SPM_DLY]		= 0x34,
> +	[MSM_SPM_REG_SAW2_STS2]			= 0x38,
> +	[MSM_SPM_REG_SAW2_PMIC_DATA_0]		= 0x40,
> +	[MSM_SPM_REG_SAW2_PMIC_DATA_1]		= 0x44,
> +	[MSM_SPM_REG_SAW2_PMIC_DATA_2]		= 0x48,
> +	[MSM_SPM_REG_SAW2_PMIC_DATA_3]		= 0x4C,
> +	[MSM_SPM_REG_SAW2_PMIC_DATA_4]		= 0x50,
> +	[MSM_SPM_REG_SAW2_PMIC_DATA_5]		= 0x54,
> +	[MSM_SPM_REG_SAW2_PMIC_DATA_6]		= 0x58,
> +	[MSM_SPM_REG_SAW2_PMIC_DATA_7]		= 0x5C,
> +	[MSM_SPM_REG_SAW2_SEQ_ENTRY]		= 0x400,
> +	[MSM_SPM_REG_SAW2_VERSION]		= 0xFD0,
> +};
> +
> +static struct saw2_data saw2_info[] = {
> +	[0] = {
> +		"SAW2_v2.1",
> +		2,
> +		1,
> +		msm_spm_reg_offsets_saw2_v2_1,
> +	},
> +	[1] = {
> +		"SAW2_v3.0",
> +		3,
> +		0,
> +		msm_spm_reg_offsets_saw2_v3_0,
> +	},
> +};
> +
> +static uint32_t num_pmic_data;
> +
> +static inline uint32_t msm_spm_drv_get_num_spm_entry(
> +		struct msm_spm_driver_data *dev)
> +{
> +	return 32;
> +}
> +
> +static void msm_spm_drv_flush_shadow(struct msm_spm_driver_data *dev,
> +		unsigned int reg_index)
> +{
> +	__raw_writel(dev->reg_shadow[reg_index],
> +		dev->reg_base_addr + dev->reg_offsets[reg_index]);
> +}
> +
> +static void msm_spm_drv_load_shadow(struct msm_spm_driver_data *dev,
> +		unsigned int reg_index)
> +{
> +	dev->reg_shadow[reg_index] =
> +		__raw_readl(dev->reg_base_addr +
> +				dev->reg_offsets[reg_index]);
> +}
> +
> +static inline void msm_spm_drv_set_start_addr(
> +		struct msm_spm_driver_data *dev, uint32_t addr, bool pc_mode)
> +{
> +	addr &= 0x7F;
> +	addr <<= 4;
> +	dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] &= 0xFFFFF80F;
> +	dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] |= addr;
> +
> +	if (dev->major != 0x3)
> +		return;
> +
> +	dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] &= 0xFFFEFFFF;
> +	if (pc_mode)
> +		dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] |= 0x00010000;
> +}
> +
> +static inline bool msm_spm_pmic_arb_present(struct msm_spm_driver_data *dev)
> +{
> +	msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW2_ID);
> +	return (dev->reg_shadow[MSM_SPM_REG_SAW2_ID] >> 2) & 0x1;
> +}
> +
> +static inline void msm_spm_drv_set_vctl2(struct msm_spm_driver_data *dev,
> +		uint32_t vlevel)
> +{
> +	unsigned int pmic_data = 0;
> +
> +	/**
> +	 * VCTL_PORT has to be 0, for PMIC_STS register to be updated.
> +	 * Ensure that vctl_port is always set to 0.
> +	 */
> +	WARN_ON(dev->vctl_port);
> +
> +	pmic_data |= vlevel;
> +	pmic_data |= (dev->vctl_port & 0x7) << 16;
> +
> +	dev->reg_shadow[MSM_SPM_REG_SAW2_VCTL] &= ~0x700FF;
> +	dev->reg_shadow[MSM_SPM_REG_SAW2_VCTL] |= pmic_data;
> +
> +	dev->reg_shadow[MSM_SPM_REG_SAW2_PMIC_DATA_3] &= ~0x700FF;
> +	dev->reg_shadow[MSM_SPM_REG_SAW2_PMIC_DATA_3] |= pmic_data;
> +
> +	msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW2_VCTL);
> +	msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW2_PMIC_DATA_3);
> +}
> +
> +static inline uint32_t msm_spm_drv_get_num_pmic_data(
> +		struct msm_spm_driver_data *dev)
> +{
> +	msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW2_ID);
> +	mb();
> +	return (dev->reg_shadow[MSM_SPM_REG_SAW2_ID] >> 4) & 0x7;
> +}
> +
> +static inline uint32_t msm_spm_drv_get_sts_pmic_state(
> +		struct msm_spm_driver_data *dev)
> +{
> +	msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW2_PMIC_STS);
> +	return (dev->reg_shadow[MSM_SPM_REG_SAW2_PMIC_STS] >> 16) &
> +				0x03;
> +}
> +
> +uint32_t msm_spm_drv_get_sts_curr_pmic_data(
> +		struct msm_spm_driver_data *dev)
> +{
> +	msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW2_PMIC_STS);
> +	return dev->reg_shadow[MSM_SPM_REG_SAW2_PMIC_STS] & 0xFF;
> +}msm_spm_drv_init
> +
> +inline int msm_spm_drv_set_spm_enable(
> +		struct msm_spm_driver_data *dev, bool enable)
> +{
> +	uint32_t value = enable ? 0x01 : 0x00;
> +
> +	if (!dev)
> +		return -EINVAL;
> +
> +	if ((dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] & 0x01) ^ value) {
> +
Please remove extra new line.
> +		dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] &= ~0x1;
> +		dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] |= value;
> +
> +		msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW2_SPM_CTL);
> +		wmb();
> +	}
> +	return 0;
> +}
> +void msm_spm_drv_flush_seq_entry(struct msm_spm_driver_data *dev)
> +{
> +	int i;
> +	int num_spm_entry = msm_spm_drv_get_num_spm_entry(dev);
> +
> +	if (!dev) {
> +		__WARN();
> +		return;
> +	}msm_spm_drv_init
> +
> +	for (i = 0; i < num_spm_entry; i++) {
> +		__raw_writel(dev->reg_seq_entry_shadow[i],
> +			dev->reg_base_addr
> +			+ dev->reg_offsets[MSM_SPM_REG_SAW2_SEQ_ENTRY]
> +			+ 4 * i);
> +	}
> +	mb();
> +}
> +
> +void dump_regs(struct msm_spm_driver_data *dev, int cpu)
> +{
> +	msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW2_SPM_STS);
> +	mb();
> +	pr_err("CPU%d: spm register MSM_SPM_REG_SAW2_SPM_STS: 0x%x\n", cpu,
> +			dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_STS]);
pr_info?
> +	msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW2_SPM_CTL);
> +	mb();
> +	pr_err("CPU%d: spm register MSM_SPM_REG_SAW2_SPM_CTL: 0x%x\n", cpu,
> +			dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL]);
pr_info?
> +}
> +
> +int msm_spm_drv_write_seq_data(struct msm_spm_driver_data *dev,
> +		uint8_t *cmd, uint32_t *offset)

<snip>
> +
> +int msm_spm_drv_set_pmic_data(struct msm_spm_driver_data *dev,
> +		enum msm_spm_pmic_port port, unsigned int data)msm_spm_drv_init
> +{
> +	unsigned int pmic_data = 0;
> +	unsigned int timeout_us = 0;
> +	int index = 0;
Initialization not needed for ind.
> +
> +	if (!msm_spm_pmic_arb_present(dev))
> +		return -ENOSYS;
> +
> +	index = msm_spm_drv_get_pmic_port(dev, port);
> +	if (index < 0)

<snip>

> +	if (!num_pmic_data)
> +		num_pmic_data = msm_spm_drv_get_num_pmic_data(dev);
> +
> +	num_spm_entry = msm_spm_drv_get_num_spm_entry(dev);
> +
> +	dev->reg_seq_entry_shadow =
> +		kzalloc(sizeof(*dev->reg_seq_entry_shadow) * num_spm_entry,
> +				GFP_KERNEL);
> +
Please remove this extra new line here.
> +	if (!dev->reg_seq_entry_shadow)
> +		return -ENOMEM;
> +
> +	return 0;
> +}
> diff --git a/drivers/soc/qcom/spm_driver.h b/drivers/soc/qcom/spm_driver.h

<snip>

> 

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

* Re: [PATCH v2 07/10] qcom: msm-pm: Add cpu low power mode functions
  2014-08-12 19:43 ` [PATCH v2 07/10] qcom: msm-pm: Add cpu low power mode functions Lina Iyer
  2014-08-13 11:18   ` Daniel Lezcano
@ 2014-08-14 13:38   ` Pramod Gurav
  2014-08-14 14:43     ` Lina Iyer
  1 sibling, 1 reply; 46+ messages in thread
From: Pramod Gurav @ 2014-08-14 13:38 UTC (permalink / raw)
  To: Lina Iyer
  Cc: daniel.lezcano, khilman, amit.kucheria, sboyd, davidb, galak,
	linux-arm-msm, msivasub, Venkat Devarasetty

On Wednesday 13 August 2014 01:13 AM, Lina Iyer wrote:
> Add interface layer to abstract and handle hardware specific
> functionality for executing various cpu low power modes in QCOM
> chipsets.
> 
> Signed-off-by: Venkat Devarasetty <vdevaras@codeaurora.org>
> Signed-off-by: Mahesh Sivasubramanian <msivasub@codeaurora.org>
> Signed-off-by: Lina Iyer <lina.iyer@linaro.org>
> ---
>  drivers/soc/qcom/Makefile |   2 +-
>  drivers/soc/qcom/msm-pm.c | 219 ++++++++++++++++++++++++++++++++++++++++++++++
>  include/soc/qcom/pm.h     |  39 +++++++++

<snip>

> +{
> +	u32 cache_id = 0;
> +
> +#if defined(CONFIG_CPU_V7)
> +	u32 sel = 0;
> +
> +	asm volatile ("mcr p15, 2, %[ccselr], c0, c0, 0\n\t"
> +		      "isb\n\t"
> +		      "mrc p15, 1, %[ccsidr], c0, c0, 0\n\t"
> +		      :[ccsidr]"=r" (cache_id)
> +		      :[ccselr]"r" (sel)

Space after ':' is what checkpatch asks. :)

> +		     );
> +	return cache_id & BIT(30);
> +#elif defined(CONFIG_ARM64)
> +	u32 sel = 0;

new line missing after declaration.

> +	asm volatile("msr csselr_el1, %[ccselr]\n\t"
> +		     "isb\n\t"
> +		     "mrs %[ccsidr],ccsidr_el1\n\t"
> +		     :[ccsidr]"=r" (cache_id)
> +		     :[ccselr]"r" (sel)

Space after ':' is what checkpatch asks. :)

> +		    );
> +	return cache_id & BIT(30);
> +#else
> +#error No valid CPU arch selected
> +#endif
> +}
> +

> 

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

* Re: [PATCH v2 07/10] qcom: msm-pm: Add cpu low power mode functions
  2014-08-13 14:16     ` Lina Iyer
@ 2014-08-14 14:24       ` Daniel Lezcano
  2014-08-14 14:53         ` Lina Iyer
  2014-08-14 16:11       ` Daniel Lezcano
  1 sibling, 1 reply; 46+ messages in thread
From: Daniel Lezcano @ 2014-08-14 14:24 UTC (permalink / raw)
  To: Lina Iyer
  Cc: khilman, amit.kucheria, sboyd, davidb, galak, linux-arm-msm,
	msivasub, Venkat Devarasetty, Nicolas Pitre

On 08/13/2014 04:16 PM, Lina Iyer wrote:
> On Wed, Aug 13, 2014 at 01:18:01PM +0200, Daniel Lezcano wrote:
>> On 08/12/2014 09:43 PM, Lina Iyer wrote:
>>> Add interface layer to abstract and handle hardware specific
>>> functionality for executing various cpu low power modes in QCOM
>>> chipsets.
>>>
>>> Signed-off-by: Venkat Devarasetty <vdevaras@codeaurora.org>
>>> Signed-off-by: Mahesh Sivasubramanian <msivasub@codeaurora.org>
>>> Signed-off-by: Lina Iyer <lina.iyer@linaro.org>
>>> ---
>>>  drivers/soc/qcom/Makefile |   2 +-
>>>  drivers/soc/qcom/msm-pm.c | 219
>>> ++++++++++++++++++++++++++++++++++++++++++++++
>>>  include/soc/qcom/pm.h     |  39 +++++++++
>>>  3 files changed, 259 insertions(+), 1 deletion(-)
>>>  create mode 100644 drivers/soc/qcom/msm-pm.c
>>>  create mode 100644 include/soc/qcom/pm.h
>>>
>>> diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile
>>> index d7ae93b..7925f83 100644
>>> --- a/drivers/soc/qcom/Makefile
>>> +++ b/drivers/soc/qcom/Makefile
>>> @@ -1,5 +1,5 @@
>>>  obj-$(CONFIG_QCOM_GSBI)    +=    qcom_gsbi.o
>>> -obj-$(CONFIG_QCOM_PM) +=    spm-devices.o spm.o
>>> +obj-$(CONFIG_QCOM_PM) +=    spm-devices.o spm.o msm-pm.o
>>>
>>>  CFLAGS_scm.o :=$(call as-instr,.arch_extension sec,-DREQUIRES_SEC=1)
>>>  obj-$(CONFIG_QCOM_SCM) += scm.o scm-boot.o
>>> diff --git a/drivers/soc/qcom/msm-pm.c b/drivers/soc/qcom/msm-pm.c
>>> new file mode 100644
>>> index 0000000..f2f15b8
>>> --- /dev/null
>>> +++ b/drivers/soc/qcom/msm-pm.c
>>> @@ -0,0 +1,219 @@
>>> +/* Copyright (c) 2010-2014, The Linux Foundation. All rights reserved.
>>> + *
>>> + * This program is free software; you can redistribute it and/or modify
>>> + * it under the terms of the GNU General Public License version 2 and
>>> + * only version 2 as published by the Free Software Foundation.
>>> + *
>>> + * This program is distributed in the hope that it will be useful,
>>> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
>>> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
>>> + * GNU General Public License for more details.
>>> + *
>>> + */
>>> +
>>> +#include <linux/module.h>
>>> +#include <linux/kernel.h>
>>> +#include <linux/init.h>
>>> +#include <linux/io.h>
>>> +#include <linux/smp.h>
>>> +#include <linux/tick.h>
>>> +#include <linux/platform_device.h>
>>> +#include <linux/cpu_pm.h>
>>> +#include <linux/uaccess.h>
>>> +
>>> +#include <soc/qcom/spm.h>
>>> +#include <soc/qcom/pm.h>
>>> +#include <soc/qcom/scm.h>
>>> +#include <soc/qcom/scm-boot.h>
>>> +
>>> +#include <asm/suspend.h>
>>> +#include <asm/cacheflush.h>
>>> +#include <asm/cputype.h>
>>> +#include <asm/system_misc.h>
>>> +
>>> +#define SCM_CMD_TERMINATE_PC    (0x2)
>>> +#define SCM_CMD_CORE_HOTPLUGGED (0x10)
>>> +#define SCM_FLUSH_FLAG_MASK    (0x3)
>>> +
>>> +static bool msm_pm_is_L1_writeback(void)
>>> +{
>>> +    u32 cache_id = 0;
>>> +
>>> +#if defined(CONFIG_CPU_V7)
>>> +    u32 sel = 0;
>>> +
>>> +    asm volatile ("mcr p15, 2, %[ccselr], c0, c0, 0\n\t"
>>> +              "isb\n\t"
>>> +              "mrc p15, 1, %[ccsidr], c0, c0, 0\n\t"
>>> +              :[ccsidr]"=r" (cache_id)
>>> +              :[ccselr]"r" (sel)
>>> +             );
>>> +    return cache_id & BIT(30);
>>> +#elif defined(CONFIG_ARM64)
>>> +    u32 sel = 0;
>>> +    asm volatile("msr csselr_el1, %[ccselr]\n\t"
>>> +             "isb\n\t"
>>> +             "mrs %[ccsidr],ccsidr_el1\n\t"
>>> +             :[ccsidr]"=r" (cache_id)
>>> +             :[ccselr]"r" (sel)
>>> +            );
>>> +    return cache_id & BIT(30);
>>> +#else
>>> +#error No valid CPU arch selected
>>> +#endif
>>> +}
>>> +
>>> +static inline void msm_arch_idle(void)
>>> +{
>>> +    /* Flush and clock-gate */
>>> +    mb();
>>
>> Why is needed this memory barrier ?
> Some QCOM SoCs needed this. I am not sure which one anymore. :(
>>
>>> +    wfi();
>>> +}
>>> +
>>> +static bool msm_pm_swfi(bool from_idle)
>>> +{
>>> +    msm_arch_idle();
>>> +    return true;
>>> +}
>>> +
>>> +static bool msm_pm_retention(bool from_idle)
>>> +{
>>> +    int ret = 0;
>>> +
>>> +    ret = msm_spm_set_low_power_mode(MSM_SPM_MODE_RETENTION, false);
>>> +    WARN_ON(ret);
>>> +
>>> +    msm_arch_idle();
>>> +
>>> +    ret = msm_spm_set_low_power_mode(MSM_SPM_MODE_CLOCK_GATING, false);
>>> +    WARN_ON(ret);
>>
>> Why do you need to set the clock gating mode each time you exit the
>> retention mode ?
> So if the SPM did not reset to clockgating, we would not do retention
> when we intended to do clockgating. Btw, we dont set clockgating
> everytime we do clockgating, helps reduce the latency in doing WFI.

Can you elaborate ? Or may be just describe what is the doing the 
function because I don't get the connection between your explanation and 
the code.

>>> +    return true;
>>> +}
>>> +
>>> +static int msm_pm_collapse(unsigned long from_idle)
>>> +{
>>> +    enum msm_pm_l2_scm_flag flag = MSM_SCM_L2_ON;
>>> +
>>> +    /**
>>> +     * Single core processors need to have L2
>>> +     * flushed when powering down the core.
>>> +     * Notify SCM to flush secure L2 lines.
>>> +     */
>>> +    if (num_possible_cpus() == 1)
>>> +        flag = MSM_SCM_L2_OFF;
>>
>> I am wondering if this shouldn't be handle by a mcpm driver.
>>
>> Cc nico.
>
> Well, possibly, sorry, not sure what features of the mcpm driver you
> think I need here?

Please correct me if I am wrong. IIUC, this function is checking the 
number of the cpus of the cluster in order to flush the L2 cache because 
the SCM will power down the cluster if it is the last one, right ?



-- 
  <http://www.linaro.org/> Linaro.org │ Open source software for ARM SoCs

Follow Linaro:  <http://www.facebook.com/pages/Linaro> Facebook |
<http://twitter.com/#!/linaroorg> Twitter |
<http://www.linaro.org/linaro-blog/> Blog

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

* Re: [PATCH v2 07/10] qcom: msm-pm: Add cpu low power mode functions
  2014-08-14 13:38   ` Pramod Gurav
@ 2014-08-14 14:43     ` Lina Iyer
  0 siblings, 0 replies; 46+ messages in thread
From: Lina Iyer @ 2014-08-14 14:43 UTC (permalink / raw)
  To: Pramod Gurav
  Cc: daniel.lezcano, khilman, amit.kucheria, sboyd, davidb, galak,
	linux-arm-msm, msivasub, Venkat Devarasetty

On Thu, Aug 14, 2014 at 07:08:21PM +0530, Pramod Gurav wrote:
>On Wednesday 13 August 2014 01:13 AM, Lina Iyer wrote:
>> Add interface layer to abstract and handle hardware specific
>> functionality for executing various cpu low power modes in QCOM
>> chipsets.
>>
>> Signed-off-by: Venkat Devarasetty <vdevaras@codeaurora.org>
>> Signed-off-by: Mahesh Sivasubramanian <msivasub@codeaurora.org>
>> Signed-off-by: Lina Iyer <lina.iyer@linaro.org>
>> ---
>>  drivers/soc/qcom/Makefile |   2 +-
>>  drivers/soc/qcom/msm-pm.c | 219 ++++++++++++++++++++++++++++++++++++++++++++++
>>  include/soc/qcom/pm.h     |  39 +++++++++
>
><snip>
>
>> +{
>> +	u32 cache_id = 0;
>> +
>> +#if defined(CONFIG_CPU_V7)
>> +	u32 sel = 0;
>> +
>> +	asm volatile ("mcr p15, 2, %[ccselr], c0, c0, 0\n\t"
>> +		      "isb\n\t"
>> +		      "mrc p15, 1, %[ccsidr], c0, c0, 0\n\t"
>> +		      :[ccsidr]"=r" (cache_id)
>> +		      :[ccselr]"r" (sel)
>
>Space after ':' is what checkpatch asks. :)
See below
>
>> +		     );
>> +	return cache_id & BIT(30);
>> +#elif defined(CONFIG_ARM64)
>> +	u32 sel = 0;
>
>new line missing after declaration.
Ok.
>
>> +	asm volatile("msr csselr_el1, %[ccselr]\n\t"
>> +		     "isb\n\t"
>> +		     "mrs %[ccsidr],ccsidr_el1\n\t"
>> +		     :[ccsidr]"=r" (cache_id)
>> +		     :[ccselr]"r" (sel)
>
>Space after ':' is what checkpatch asks. :)
Yes, checkpatch seems to complain eitherways, whether you have a space
or not. The advice was to ignore it.

>
>> +		    );
>> +	return cache_id & BIT(30);
>> +#else
>> +#error No valid CPU arch selected
>> +#endif
>> +}
>> +
>
>>

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

* Re: [PATCH v2 07/10] qcom: msm-pm: Add cpu low power mode functions
  2014-08-14 14:24       ` Daniel Lezcano
@ 2014-08-14 14:53         ` Lina Iyer
  0 siblings, 0 replies; 46+ messages in thread
From: Lina Iyer @ 2014-08-14 14:53 UTC (permalink / raw)
  To: Daniel Lezcano
  Cc: khilman, amit.kucheria, sboyd, davidb, galak, linux-arm-msm,
	msivasub, Venkat Devarasetty, Nicolas Pitre

On Thu, Aug 14, 2014 at 04:24:10PM +0200, Daniel Lezcano wrote:
>On 08/13/2014 04:16 PM, Lina Iyer wrote:
>>On Wed, Aug 13, 2014 at 01:18:01PM +0200, Daniel Lezcano wrote:
>>>On 08/12/2014 09:43 PM, Lina Iyer wrote:

>>>+static bool msm_pm_retention(bool from_idle)
>>>>+{
>>>>+    int ret = 0;
>>>>+
>>>>+    ret = msm_spm_set_low_power_mode(MSM_SPM_MODE_RETENTION, false);
>>>>+    WARN_ON(ret);
>>>>+
>>>>+    msm_arch_idle();
>>>>+
>>>>+    ret = msm_spm_set_low_power_mode(MSM_SPM_MODE_CLOCK_GATING, false);
>>>>+    WARN_ON(ret);
>>>
>>>Why do you need to set the clock gating mode each time you exit the
>>>retention mode ?
>>So if the SPM did not reset to clockgating, we would not do retention
>>when we intended to do clockgating. Btw, we dont set clockgating
>>everytime we do clockgating, helps reduce the latency in doing WFI.
>
>Can you elaborate ? Or may be just describe what is the doing the 
>function because I don't get the connection between your explanation 
>and the code.
>

Retention still has an higher latency than clock gating. Retention gets
triggered with the core executes wfi() instruction. The entry into a
steady state retention mode is higher than just gating clocks, which may
be bad for power, if we do not stay in the low power modes for a minimum
of the residency period.

If the current idle state was retention and SPM was configured to do
retention and we came out the retention state and the next idle, we
decided to do clockgating we should configure the SPM to do clock
gating. Since we want to speed up clockgating as its one of the state
most commonly entered, we set the configuration of SPM as soon as we
come out any low power mode back to WFI.

>>>>+    return true;
>>>>+}
>>>>+
>>>>+static int msm_pm_collapse(unsigned long from_idle)
>>>>+{
>>>>+    enum msm_pm_l2_scm_flag flag = MSM_SCM_L2_ON;
>>>>+
>>>>+    /**
>>>>+     * Single core processors need to have L2
>>>>+     * flushed when powering down the core.
>>>>+     * Notify SCM to flush secure L2 lines.
>>>>+     */
>>>>+    if (num_possible_cpus() == 1)
>>>>+        flag = MSM_SCM_L2_OFF;
>>>
>>>I am wondering if this shouldn't be handle by a mcpm driver.
>>>
>>>Cc nico.
>>
>>Well, possibly, sorry, not sure what features of the mcpm driver you
>>think I need here?
>
>Please correct me if I am wrong. IIUC, this function is checking the 
>number of the cpus of the cluster in order to flush the L2 cache 
>because the SCM will power down the cluster if it is the last one, 
>right ?
>
>
>
>-- 
> <http://www.linaro.org/> Linaro.org │ Open source software for ARM SoCs
>
>Follow Linaro:  <http://www.facebook.com/pages/Linaro> Facebook |
><http://twitter.com/#!/linaroorg> Twitter |
><http://www.linaro.org/linaro-blog/> Blog
>

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

* Re: [PATCH v2 03/10] qcom: spm: Add Subsystem Power Manager (SPM) driver for QCOM chipsets
  2014-08-12 19:43 ` [PATCH v2 03/10] qcom: spm: Add Subsystem Power Manager (SPM) driver for QCOM chipsets Lina Iyer
  2014-08-13 10:49   ` Daniel Lezcano
  2014-08-14 13:01   ` Pramod Gurav
@ 2014-08-14 15:16   ` Kumar Gala
  2014-08-14 15:27     ` Lina Iyer
  2014-08-14 16:09   ` Kumar Gala
  3 siblings, 1 reply; 46+ messages in thread
From: Kumar Gala @ 2014-08-14 15:16 UTC (permalink / raw)
  To: Lina Iyer
  Cc: daniel.lezcano, khilman, amit.kucheria, sboyd, davidb,
	linux-arm-msm, msivasub, Praveen Chidamabram, Murali Nalajala


On Aug 12, 2014, at 2:43 PM, Lina Iyer <lina.iyer@linaro.org> wrote:

> Qualcomm chipsets use an separate h/w block to control the logic around
> the processor cores (cpu and L2). The SPM h/w block regulates power to
> the cores and controls the power when the core enter low power modes.
> 
> Each core has its own instance of SPM. The SPM has the following key
> functions
> 	- Configure the h/w dependencies when entering low power modes
> 	- Wait for interrupt and wake up on interrupt
> 	- Ensure the dependencies are ready before bringing the core out
> 	  of sleep
> 	- Regulating voltage to the core, interfacing with the PMIC.
> 	- Optimize power based on runtime recommendations.
> 
> The driver identifies and configures the SPMs, by reading the nodes and
> the register values from the devicetree. The SPMs need to be configured
> to allow the processor to be idled in a low power state.
> 
> Signed-off-by: Praveen Chidamabram <pchidamb@codeaurora.org>
> Signed-off-by: Murali Nalajala <mnalajal@codeaurora.org>
> Signed-off-by: Lina Iyer <lina.iyer@linaro.org>
> ---
> .../devicetree/bindings/arm/msm/spm-v2.txt         |  62 ++
> drivers/soc/qcom/Makefile                          |   2 +
> drivers/soc/qcom/spm-devices.c                     | 703 +++++++++++++++++++++
> drivers/soc/qcom/spm.c                             | 482 ++++++++++++++
> drivers/soc/qcom/spm_driver.h                      | 116 ++++
> include/soc/qcom/spm.h                             |  70 ++
> 6 files changed, 1435 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/arm/msm/spm-v2.txt
> create mode 100644 drivers/soc/qcom/spm-devices.c
> create mode 100644 drivers/soc/qcom/spm.c
> create mode 100644 drivers/soc/qcom/spm_driver.h
> create mode 100644 include/soc/qcom/spm.h
> 
> diff --git a/Documentation/devicetree/bindings/arm/msm/spm-v2.txt b/Documentation/devicetree/bindings/arm/msm/spm-v2.txt
> new file mode 100644
> index 0000000..3130f4b
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/arm/msm/spm-v2.txt
> @@ -0,0 +1,62 @@
> +* MSM Subsystem Power Manager (spm-v2)
> +
> +S4 generation of MSMs have SPM hardware blocks to control the Application
> +Processor Sub-System power. These SPM blocks run individual state machine
> +to determine what the core (L2 or Krait/Scorpion) would do when the WFI
> +instruction is executed by the core.
> +
> +The devicetree representation of the SPM block should be:
> +
> +Required properties
> +
> +- compatible: Could be one of -
> +		"qcom,spm-v2.1"
> +		"qcom,spm-v3.0"
> +- reg: The physical address and the size of the SPM's memory mapped registers
> +- qcom,cpu: phandle for the CPU that the SPM block is attached to. On targets
> +	that dont support CPU phandles the driver would support qcom,core-id.
> +	This field is required on only for SPMs that control the CPU.
> +- qcom,saw2-cfg: SAW2 configuration register
> +- qcom,saw2-spm-dly: Provides the values for the SPM delay command in the SPM
> +	sequence
> +- qcom,saw2-spm-ctl: The SPM control register
> +- qcom,name: The name with which a SPM device is identified by the power
> +	management code.

Still not sure about this, wondering why we can’t use the node name.

> +
> +Optional properties
> +
> +- qcom,saw2-pmic-data0..7: Specify the pmic data value and the associated FTS
> +	(Fast Transient Switch) index to send the PMIC data to
> +- qcom,vctl-port: The PVC (PMIC Virtual Channel) port used for changing
> +	voltage
> +- qcom,phase-port: The PVC port used for changing the number of phases
> +- qcom,pfm-port: The PVC port used for enabling PWM/PFM modes
> +- qcom,saw2-spm-cmd-wfi: The WFI command sequence
> +- qcom,saw2-spm-cmd-ret: The Retention command sequence
> +- qcom,saw2-spm-cmd-spc: The Standalone PC command sequence
> +- qcom,saw2-spm-cmd-pc-no-rpm: The Power Collapse command sequence where APPS
> +	proc won't inform the RPM.
> +- qcom,saw2-spm-cmd-pc: The Power Collapse command sequence. This sequence may
> +	turn off other SoC components.
> +- qcom,saw2-spm-cmd-gdhs: GDHS (Globally Distributed Head Switch) command
> +	sequence. This sequence will retain the memory but turn off the logic.
> +- qcom,cpu-vctl-list: List of cpu node phandles, whose voltage the spm device
> +	can control.
> +- qcom,vctl-timeout-us: The timeout value in microseconds to wait for voltage to
> +	change after sending the voltage command to the PMIC.
> +-
> +Example:
> +	qcom,spm@f9089000 {
> +		compatible = "qcom,spm-v2";
> +		#address-cells = <1>;
> +		#size-cells = <1>;
> +		reg = <0xf9089000 0x1000>;
> +		qcom,cpu = <&CPU0>;
> +		qcom,saw2-cfg = <0x1>;
> +		qcom,saw2-spm-dly= <0x20000400>;
> +		qcom,saw2-spm-ctl = <0x1>;
> +		qcom,saw2-spm-cmd-wfi = [03 0b 0f];
> +		qcom,saw2-spm-cmd-spc = [00 20 50 80 60 70 10 92
> +				a0 b0 03 68 70 3b 92 a0 b0
> +				82 2b 50 10 30 02 22 30 0f];
> +	};
> diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile
> index 70d52ed..d7ae93b 100644
> --- a/drivers/soc/qcom/Makefile
> +++ b/drivers/soc/qcom/Makefile
> @@ -1,3 +1,5 @@
> obj-$(CONFIG_QCOM_GSBI)	+=	qcom_gsbi.o
> +obj-$(CONFIG_QCOM_PM) +=	spm-devices.o spm.o
> +
> CFLAGS_scm.o :=$(call as-instr,.arch_extension sec,-DREQUIRES_SEC=1)
> obj-$(CONFIG_QCOM_SCM) += scm.o scm-boot.o
> diff --git a/drivers/soc/qcom/spm-devices.c b/drivers/soc/qcom/spm-devices.c
> new file mode 100644
> index 0000000..567e9f9
> --- /dev/null
> +++ b/drivers/soc/qcom/spm-devices.c
> @@ -0,0 +1,703 @@
> +/* Copyright (c) 2011-2014, The Linux Foundation. All rights reserved.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 and
> + * only version 2 as published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + * GNU General Public License for more details.
> + *
> + */
> +
> +#include <linux/module.h>
> +#include <linux/kernel.h>
> +#include <linux/delay.h>
> +#include <linux/init.h>
> +#include <linux/io.h>
> +#include <linux/slab.h>
> +#include <linux/of.h>
> +#include <linux/of_address.h>
> +#include <linux/err.h>
> +#include <linux/platform_device.h>
> +#include <linux/err.h>
> +
> +#include <soc/qcom/spm.h>
> +
> +#include "spm_driver.h"
> +
> +#define VDD_DEFAULT 0xDEADF00D
> +
> +struct msm_spm_power_modes {
> +	uint32_t mode;
> +	bool notify_rpm;
> +	uint32_t start_addr;
> +};
> +
> +struct msm_spm_device {
> +	struct list_head list;
> +	bool initialized;
> +	const char *name;
> +	struct msm_spm_driver_data reg_data;
> +	struct msm_spm_power_modes *modes;
> +	uint32_t num_modes;
> +	uint32_t cpu_vdd;
> +	struct cpumask mask;
> +	void __iomem *q2s_reg;
> +};
> +
> +struct msm_spm_vdd_info {
> +	struct msm_spm_device *vctl_dev;
> +	uint32_t vlevel;
> +	int err;
> +};
> +
> +static LIST_HEAD(spm_list);
> +static DEFINE_PER_CPU_SHARED_ALIGNED(struct msm_spm_device, msm_cpu_spm_device);
> +static DEFINE_PER_CPU(struct msm_spm_device *, cpu_vctl_device);
> +
> +static void msm_spm_smp_set_vdd(void *data)
> +{
> +	struct msm_spm_vdd_info *info = (struct msm_spm_vdd_info *)data;
> +	struct msm_spm_device *dev = info->vctl_dev;
> +
> +	dev->cpu_vdd = info->vlevel;
> +	info->err = msm_spm_drv_set_vdd(&dev->reg_data, info->vlevel);
> +}
> +
> +/**
> + * msm_spm_probe_done(): Verify and return the status of the cpu(s) and l2
> + * probe.
> + * Return: 0 if all spm devices have been probed, else return -EPROBE_DEFER.
> + * if probe failed, then return the err number for that failure.
> + */
> +int msm_spm_probe_done(void)
> +{
> +	struct msm_spm_device *dev;
> +	int cpu;
> +	int ret = 0;
> +
> +	for_each_possible_cpu(cpu) {
> +		dev = per_cpu(cpu_vctl_device, cpu);
> +		if (!dev)
> +			return -EPROBE_DEFER;
> +
> +		ret = IS_ERR(dev);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	return 0;
> +}
> +EXPORT_SYMBOL(msm_spm_probe_done);
> +
> +void msm_spm_dump_regs(unsigned int cpu)
> +{
> +	dump_regs(&per_cpu(msm_cpu_spm_device, cpu).reg_data, cpu);
> +}

Where is this used?

> +
> +/**
> + * msm_spm_set_vdd(): Set core voltage
> + * @cpu: core id
> + * @vlevel: Encoded PMIC data.
> + *
> + * Return: 0 on success or -(ERRNO) on failure.
> + */
> +int msm_spm_set_vdd(unsigned int cpu, unsigned int vlevel)
> +{
> +	struct msm_spm_vdd_info info;
> +	struct msm_spm_device *dev = per_cpu(cpu_vctl_device, cpu);
> +	int ret;
> +
> +	if (!dev)
> +		return -EPROBE_DEFER;
> +
> +	ret = IS_ERR(dev);
> +	if (ret)
> +		return ret;
> +
> +	info.vctl_dev = dev;
> +	info.vlevel = vlevel;
> +
> +	ret = smp_call_function_any(&dev->mask, msm_spm_smp_set_vdd, &info,
> +					true);
> +	if (ret)
> +		return ret;
> +
> +	return info.err;
> +}
> +EXPORT_SYMBOL(msm_spm_set_vdd);
> +
> +/**
> + * msm_spm_get_vdd(): Get core voltage
> + * @cpu: core id
> + * @return: Returns encoded PMIC data.
> + */
> +unsigned int msm_spm_get_vdd(unsigned int cpu)
> +{
> +	int ret;
> +	struct msm_spm_device *dev = per_cpu(cpu_vctl_device, cpu);
> +
> +	if (!dev)
> +		return -EPROBE_DEFER;
> +
> +	ret = IS_ERR(dev);
> +	if (ret)
> +		return ret;
> +
> +	return dev->cpu_vdd;
> +}
> +EXPORT_SYMBOL(msm_spm_get_vdd);
> +
> +static void msm_spm_config_q2s(struct msm_spm_device *dev, unsigned int mode)
> +{
> +	uint32_t spm_legacy_mode = 0;
> +	uint32_t qchannel_ignore = 0;
> +	uint32_t val = 0;
> +
> +	if (!dev->q2s_reg)
> +		return;
> +
> +	switch (mode) {
> +	case MSM_SPM_MODE_DISABLED:
> +	case MSM_SPM_MODE_CLOCK_GATING:
> +		qchannel_ignore = 1;
> +		spm_legacy_mode = 0;
> +		break;
> +	case MSM_SPM_MODE_RETENTION:
> +		qchannel_ignore = 0;
> +		spm_legacy_mode = 0;
> +		break;
> +	case MSM_SPM_MODE_GDHS:
> +	case MSM_SPM_MODE_POWER_COLLAPSE:
> +		qchannel_ignore = 0;
> +		spm_legacy_mode = 1;
> +		break;
> +	default:
> +		break;
> +	}
> +
> +	val = spm_legacy_mode << 2 | qchannel_ignore << 1;
> +	__raw_writel(val, dev->q2s_reg);
> +	mb();
> +}
> +
> +static int msm_spm_dev_set_low_power_mode(struct msm_spm_device *dev,
> +		unsigned int mode, bool notify_rpm)
> +{
> +	uint32_t i;
> +	uint32_t start_addr = 0;
> +	int ret = -EINVAL;
> +	bool pc_mode = false;
> +
> +	if (!dev->initialized)
> +		return -ENXIO;
> +
> +	if ((mode == MSM_SPM_MODE_POWER_COLLAPSE)
> +			|| (mode == MSM_SPM_MODE_GDHS))
> +		pc_mode = true;
> +
> +	if (mode == MSM_SPM_MODE_DISABLED) {
> +		ret = msm_spm_drv_set_spm_enable(&dev->reg_data, false);
> +	} else if (!msm_spm_drv_set_spm_enable(&dev->reg_data, true)) {
> +		for (i = 0; i < dev->num_modes; i++) {
> +			if ((dev->modes[i].mode == mode) &&
> +				(dev->modes[i].notify_rpm == notify_rpm)) {
> +				start_addr = dev->modes[i].start_addr;
> +				break;
> +			}
> +		}
> +		ret = msm_spm_drv_set_low_power_mode(&dev->reg_data,
> +					start_addr, pc_mode);
> +	}
> +
> +	msm_spm_config_q2s(dev, mode);
> +
> +	return ret;
> +}
> +
> +static int msm_spm_dev_init(struct msm_spm_device *dev,
> +		struct msm_spm_platform_data *data)
> +{
> +	int i, ret = -ENOMEM;
> +	uint32_t offset = 0;
> +
> +	dev->cpu_vdd = VDD_DEFAULT;
> +	dev->num_modes = data->num_modes;
> +	dev->modes = kmalloc(
> +			sizeof(struct msm_spm_power_modes) * dev->num_modes,
> +			GFP_KERNEL);
> +
> +	if (!dev->modes)
> +		goto spm_failed_malloc;
> +
> +	dev->reg_data.major = data->major;
> +	dev->reg_data.minor = data->minor;
> +	ret = msm_spm_drv_init(&dev->reg_data, data);
> +
> +	if (ret)
> +		goto spm_failed_init;
> +
> +	for (i = 0; i < dev->num_modes; i++) {
> +
> +		/* Default offset is 0 and gets updated as we write more
> +		 * sequences into SPM
> +		 */
> +		dev->modes[i].start_addr = offset;
> +		ret = msm_spm_drv_write_seq_data(&dev->reg_data,
> +						data->modes[i].cmd, &offset);
> +		if (ret < 0)
> +			goto spm_failed_init;
> +
> +		dev->modes[i].mode = data->modes[i].mode;
> +		dev->modes[i].notify_rpm = data->modes[i].notify_rpm;
> +	}
> +	msm_spm_drv_reinit(&dev->reg_data);
> +	dev->initialized = true;
> +	return 0;
> +
> +spm_failed_init:
> +	kfree(dev->modes);
> +spm_failed_malloc:
> +	return ret;
> +}
> +
> +/**
> + * msm_spm_turn_on_cpu_rail(): Power on cpu rail before turning on core
> + * @base: The SAW VCTL register which would set the voltage up.
> + * @val: The value to be set on the rail
> + * @cpu: The cpu for this with rail is being powered on
> + */
> +int msm_spm_turn_on_cpu_rail(void __iomem *base, unsigned int val, int cpu)
> +{
> +	uint32_t timeout = 2000; /* delay for voltage to settle on the core */
> +	struct msm_spm_device *dev = per_cpu(cpu_vctl_device, cpu);
> +
> +	/*
> +	 * If clock drivers have already set up the voltage,
> +	 * do not overwrite that value.
> +	 */
> +	if (dev && (dev->cpu_vdd != VDD_DEFAULT))
> +		return 0;
> +
> +	/* Set the CPU supply regulator voltage */
> +	val = (val & 0xFF);
> +	writel_relaxed(val, base);
> +	mb();
> +	udelay(timeout);
> +
> +	/* Enable the CPU supply regulator*/
> +	val = 0x30080;
> +	writel_relaxed(val, base);
> +	mb();
> +	udelay(timeout);
> +
> +	return 0;
> +}
> +EXPORT_SYMBOL(msm_spm_turn_on_cpu_rail);
> +
> +void msm_spm_reinit(void)
> +{
> +	unsigned int cpu;
> +
> +	for_each_possible_cpu(cpu)
> +		msm_spm_drv_reinit(&per_cpu(msm_cpu_spm_device.reg_data, cpu));
> +}
> +EXPORT_SYMBOL(msm_spm_reinit);
> +
> +/*
> + * msm_spm_is_mode_avail() - Specifies if a mode is available for the cpu
> + * It should only be used to decide a mode before lpm driver is probed.
> + * @mode: SPM LPM mode to be selected
> + */
> +bool msm_spm_is_mode_avail(unsigned int mode)
> +{
> +	struct msm_spm_device *dev = &__get_cpu_var(msm_cpu_spm_device);
> +	int i;
> +
> +	for (i = 0; i < dev->num_modes; i++) {
> +		if (dev->modes[i].mode == mode)
> +			return true;
> +	}
> +
> +	return false;
> +}
> +
> +/**
> + * msm_spm_set_low_power_mode() - Configure SPM start address for low power mode
> + * @mode: SPM LPM mode to enter
> + * @notify_rpm: Notify RPM in this mode
> + */
> +int msm_spm_set_low_power_mode(unsigned int mode, bool notify_rpm)
> +{
> +	struct msm_spm_device *dev = &__get_cpu_var(msm_cpu_spm_device);
> +
> +	return msm_spm_dev_set_low_power_mode(dev, mode, notify_rpm);
> +}
> +EXPORT_SYMBOL(msm_spm_set_low_power_mode);
> +
> +/**
> + * msm_spm_init(): Board initalization function
> + * @data: platform specific SPM register configuration data
> + * @nr_devs: Number of SPM devices being initialized
> + */
> +int __init msm_spm_init(struct msm_spm_platform_data *data, int nr_devs)
> +{
> +	unsigned int cpu;
> +	int ret = 0;
> +
> +	BUG_ON((nr_devs < num_possible_cpus()) || !data);
> +
> +	for_each_possible_cpu(cpu) {
> +		struct msm_spm_device *dev = &per_cpu(msm_cpu_spm_device, cpu);
> +
> +		ret = msm_spm_dev_init(dev, &data[cpu]);
> +		if (ret < 0) {
> +			pr_warn("%s():failed CPU:%u ret:%d\n", __func__,
> +					cpu, ret);
> +			break;
> +		}
> +	}
> +
> +	return ret;
> +}
> +
> +struct msm_spm_device *msm_spm_get_device_by_name(const char *name)
> +{
> +	struct list_head *list;
> +
> +	list_for_each(list, &spm_list) {
> +		struct msm_spm_device *dev
> +			= list_entry(list, typeof(*dev), list);
> +		if (dev->name && !strcmp(dev->name, name))
> +			return dev;
> +	}
> +	return ERR_PTR(-ENODEV);
> +}
> +
> +int msm_spm_config_low_power_mode(struct msm_spm_device *dev,
> +		unsigned int mode, bool notify_rpm)
> +{
> +	return msm_spm_dev_set_low_power_mode(dev, mode, notify_rpm);
> +}
> +#ifdef CONFIG_MSM_L2_SPM
> +
> +/**
> + * msm_spm_apcs_set_phase(): Set number of SMPS phases.
> + * @cpu: cpu which is requesting the change in number of phases.
> + * @phase_cnt: Number of phases to be set active
> + */
> +int msm_spm_apcs_set_phase(int cpu, unsigned int phase_cnt)
> +{
> +	struct msm_spm_device *dev = per_cpu(cpu_vctl_device, cpu);
> +
> +	if (!dev)
> +		return -ENXIO;
> +
> +	return msm_spm_drv_set_pmic_data(&dev->reg_data,
> +			MSM_SPM_PMIC_PHASE_PORT, phase_cnt);
> +}
> +EXPORT_SYMBOL(msm_spm_apcs_set_phase);
> +
> +/** msm_spm_enable_fts_lpm() : Enable FTS to switch to low power
> + *                             when the cores are in low power modes
> + * @cpu: cpu that is entering low power mode.
> + * @mode: The mode configuration for FTS
> + */
> +int msm_spm_enable_fts_lpm(int cpu, uint32_t mode)
> +{
> +	struct msm_spm_device *dev = per_cpu(cpu_vctl_device, cpu);
> +
> +	if (!dev)
> +		return -ENXIO;
> +
> +	return msm_spm_drv_set_pmic_data(&dev->reg_data,
> +			MSM_SPM_PMIC_PFM_PORT, mode);
> +}
> +EXPORT_SYMBOL(msm_spm_enable_fts_lpm);
> +
> +#endif
> +
> +static int get_cpu_id(struct device_node *node)
> +{
> +	struct device_node *cpu_node;
> +	u32 cpu;
> +	int ret = -EINVAL;
> +	char *key = "qcom,cpu";
> +
> +	cpu_node = of_parse_phandle(node, key, 0);
> +	if (cpu_node) {
> +		for_each_possible_cpu(cpu) {
> +			if (of_get_cpu_node(cpu, NULL) == cpu_node)
> +				return cpu;
> +		}
> +	}
> +	return ret;
> +}
> +
> +static struct msm_spm_device *msm_spm_get_device(struct platform_device *pdev)
> +{
> +	struct msm_spm_device *dev = NULL;
> +	const char *val = NULL;
> +	char *key = "qcom,name";
> +	int cpu = get_cpu_id(pdev->dev.of_node);
> +
> +	if ((cpu >= 0) && cpu < num_possible_cpus())
> +		dev = &per_cpu(msm_cpu_spm_device, cpu);
> +	else if ((cpu == 0xffff) || (cpu < 0))
> +		dev = devm_kzalloc(&pdev->dev, sizeof(struct msm_spm_device),
> +					GFP_KERNEL);
> +
> +	if (!dev)
> +		return NULL;
> +
> +	if (of_property_read_string(pdev->dev.of_node, key, &val)) {
> +		pr_err("%s(): Cannot find a required node key:%s\n",
> +				__func__, key);
> +		return NULL;
> +	}
> +	dev->name = val;
> +	list_add(&dev->list, &spm_list);
> +
> +	return dev;
> +}
> +
> +static void get_cpumask(struct device_node *node, struct cpumask *mask)
> +{
> +	unsigned long vctl_mask = 0;
> +	unsigned c = 0;
> +	int idx = 0;
> +	struct device_node *cpu_node = NULL;
> +	int ret = 0;
> +	char *key = "qcom,cpu-vctl-list";
> +	bool found = false;
> +
> +	cpu_node = of_parse_phandle(node, key, idx++);
> +	while (cpu_node) {
> +		found = true;
> +		for_each_possible_cpu(c) {
> +			if (of_get_cpu_node(c, NULL) == cpu_node)
> +				cpumask_set_cpu(c, mask);
> +		}
> +		cpu_node = of_parse_phandle(node, key, idx++);
> +	};
> +
> +	if (found)
> +		return;
> +
> +	key = "qcom,cpu-vctl-mask";
> +	ret = of_property_read_u32(node, key, (u32 *) &vctl_mask);
> +	if (!ret) {
> +		for_each_set_bit(c, &vctl_mask, num_possible_cpus()) {
> +			cpumask_set_cpu(c, mask);
> +		}
> +	}

kill this code if we really dropped the ‘qcom,cpu-vctl-mask’ DT support.

> +}
> +
> +static int msm_spm_dev_probe(struct platform_device *pdev)
> +{
> +	int ret = 0;
> +	int cpu = 0;
> +	int i = 0;
> +	struct device_node *node = pdev->dev.of_node;
> +	struct msm_spm_platform_data spm_data;
> +	char *key = NULL;
> +	uint32_t val = 0;
> +	struct msm_spm_seq_entry modes[MSM_SPM_MODE_NR];
> +	int len = 0;
> +	struct msm_spm_device *dev = NULL;
> +	struct resource *res = NULL;
> +	uint32_t mode_count = 0;
> +
> +	struct spm_of {
> +		char *key;
> +		uint32_t id;
> +	};
> +
> +	struct spm_of spm_of_data[] = {
> +		{"qcom,saw2-cfg", MSM_SPM_REG_SAW2_CFG},
> +		{"qcom,saw2-spm-dly", MSM_SPM_REG_SAW2_SPM_DLY},
> +		{"qcom,saw2-spm-ctl", MSM_SPM_REG_SAW2_SPM_CTL},
> +		{"qcom,saw2-pmic-data0", MSM_SPM_REG_SAW2_PMIC_DATA_0},
> +		{"qcom,saw2-pmic-data1", MSM_SPM_REG_SAW2_PMIC_DATA_1},
> +		{"qcom,saw2-pmic-data2", MSM_SPM_REG_SAW2_PMIC_DATA_2},
> +		{"qcom,saw2-pmic-data3", MSM_SPM_REG_SAW2_PMIC_DATA_3},
> +		{"qcom,saw2-pmic-data4", MSM_SPM_REG_SAW2_PMIC_DATA_4},
> +		{"qcom,saw2-pmic-data5", MSM_SPM_REG_SAW2_PMIC_DATA_5},
> +		{"qcom,saw2-pmic-data6", MSM_SPM_REG_SAW2_PMIC_DATA_6},
> +		{"qcom,saw2-pmic-data7", MSM_SPM_REG_SAW2_PMIC_DATA_7},
> +	};
> +
> +	struct mode_of {
> +		char *key;
> +		uint32_t id;
> +		uint32_t notify_rpm;
> +	};
> +
> +	struct mode_of mode_of_data[] = {
> +		{"qcom,saw2-spm-cmd-wfi", MSM_SPM_MODE_CLOCK_GATING, 0},
> +		{"qcom,saw2-spm-cmd-ret", MSM_SPM_MODE_RETENTION, 0},
> +		{"qcom,saw2-spm-cmd-gdhs", MSM_SPM_MODE_GDHS, 1},
> +		{"qcom,saw2-spm-cmd-spc", MSM_SPM_MODE_POWER_COLLAPSE, 0},
> +		{"qcom,saw2-spm-cmd-pc", MSM_SPM_MODE_POWER_COLLAPSE, 1},
> +	};
> +
> +	dev = msm_spm_get_device(pdev);
> +	if (!dev) {
> +		ret = -ENOMEM;
> +		goto fail;
> +	}
> +	get_cpumask(node, &dev->mask);
> +
> +	memset(&spm_data, 0, sizeof(struct msm_spm_platform_data));
> +	memset(&modes, 0,
> +		(MSM_SPM_MODE_NR - 2) * sizeof(struct msm_spm_seq_entry));
> +
> +	if (of_device_is_compatible(node, "qcom,spm-v2.1")) {
> +		spm_data.major = 2;
> +		spm_data.minor = 1;
> +	} else if (of_device_is_compatible(node, "qcom,spm-v3.0")) {
> +		spm_data.major = 3;
> +		spm_data.minor = 0;
> +	}
> +
> +	key = "qcom,vctl-timeout-us";
> +	ret = of_property_read_u32(node, key, &val);
> +	if (!ret)
> +		spm_data.vctl_timeout_us = val;
> +
> +	/* SAW start address */
> +	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> +	if (!res) {
> +		ret = -EFAULT;
> +		goto fail;
> +	}
> +
> +	spm_data.reg_base_addr = devm_ioremap(&pdev->dev, res->start,
> +					resource_size(res));
> +	if (!spm_data.reg_base_addr) {
> +		ret = -ENOMEM;
> +		goto fail;
> +	}
> +
> +	spm_data.vctl_port = -1;
> +	spm_data.phase_port = -1;
> +	spm_data.pfm_port = -1;
> +
> +	key = "qcom,vctl-port";
> +	of_property_read_u32(node, key, &spm_data.vctl_port);
> +
> +	key = "qcom,phase-port";
> +	of_property_read_u32(node, key, &spm_data.phase_port);
> +
> +	key = "qcom,pfm-port";
> +	of_property_read_u32(node, key, &spm_data.pfm_port);
> +
> +	/* Q2S (QChannel-2-SPM) register */
> +	res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
> +	if (res) {
> +		dev->q2s_reg = devm_ioremap(&pdev->dev, res->start,
> +						resource_size(res));
> +		if (!dev->q2s_reg) {
> +			pr_err("%s(): Unable to iomap Q2S register\n",
> +					__func__);
> +			ret = -EADDRNOTAVAIL;
> +			goto fail;
> +		}
> +	}
> +	/*
> +	 * At system boot, cpus and or clusters can remain in reset. CCI SPM
> +	 * will not be triggered unless SPM_LEGACY_MODE bit is set for the
> +	 * cluster in reset. Initialize q2s registers and set the
> +	 * SPM_LEGACY_MODE bit.
> +	 */
> +	msm_spm_config_q2s(dev, MSM_SPM_MODE_POWER_COLLAPSE);
> +
> +	for (i = 0; i < ARRAY_SIZE(spm_of_data); i++) {
> +		ret = of_property_read_u32(node, spm_of_data[i].key, &val);
> +		if (ret)
> +			continue;
> +		spm_data.reg_init_values[spm_of_data[i].id] = val;
> +	}
> +
> +	for (i = 0; i < ARRAY_SIZE(mode_of_data); i++) {
> +		key = mode_of_data[i].key;
> +		modes[mode_count].cmd =
> +			(uint8_t *)of_get_property(node, key, &len);
> +		if (!modes[mode_count].cmd)
> +			continue;
> +		modes[mode_count].mode = mode_of_data[i].id;
> +		modes[mode_count].notify_rpm = mode_of_data[i].notify_rpm;
> +		pr_debug("%s(): dev: %s cmd:%s, mode:%d rpm:%d\n", __func__,
> +				dev->name, key, modes[mode_count].mode,
> +				modes[mode_count].notify_rpm);
> +		mode_count++;
> +	}
> +
> +	spm_data.modes = modes;
> +	spm_data.num_modes = mode_count;
> +
> +	ret = msm_spm_dev_init(dev, &spm_data);
> +	if (ret)
> +		goto fail;
> +
> +	platform_set_drvdata(pdev, dev);
> +
> +	for_each_cpu(cpu, &dev->mask)
> +		per_cpu(cpu_vctl_device, cpu) = dev;
> +
> +	return ret;
> +
> +fail:
> +	cpu = get_cpu_id(pdev->dev.of_node);
> +	if (dev && (cpu >= num_possible_cpus() || (cpu < 0))) {
> +		for_each_cpu(cpu, &dev->mask)
> +			per_cpu(cpu_vctl_device, cpu) = ERR_PTR(ret);
> +	}
> +
> +	pr_err("%s: CPU%d SPM device probe failed: %d\n", __func__, cpu, ret);
> +
> +	return ret;
> +}
> +
> +static int msm_spm_dev_remove(struct platform_device *pdev)
> +{
> +	struct msm_spm_device *dev = platform_get_drvdata(pdev);
> +
> +	list_del(&dev->list);
> +
> +	return 0;
> +}
> +
> +static struct of_device_id msm_spm_match_table[] = {
> +	{.compatible = "qcom,spm-v2.1"},
> +	{.compatible = "qcom,spm-v3.0"},
> +	{},
> +};
> +
> +static struct platform_driver msm_spm_device_driver = {
> +	.probe = msm_spm_dev_probe,
> +	.remove = msm_spm_dev_remove,
> +	.driver = {
> +		.name = "spm-v2",
> +		.owner = THIS_MODULE,
> +		.of_match_table = msm_spm_match_table,
> +	},
> +};
> +
> +/**
> + * msm_spm_device_init(): Device tree initialization function
> + */
> +int __init msm_spm_device_init(void)
> +{
> +	static bool registered;
> +
> +	if (registered)
> +		return 0;
> +
> +	registered = true;
> +
> +	return platform_driver_register(&msm_spm_device_driver);
> +}
> +device_initcall(msm_spm_device_init);
> diff --git a/drivers/soc/qcom/spm.c b/drivers/soc/qcom/spm.c
> new file mode 100644
> index 0000000..7dbdb64
> --- /dev/null
> +++ b/drivers/soc/qcom/spm.c
> @@ -0,0 +1,482 @@
> +/* Copyright (c) 2011-2014, The Linux Foundation. All rights reserved.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 and
> + * only version 2 as published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + * GNU General Public License for more details.
> + *
> + */
> +
> +#include <linux/module.h>
> +#include <linux/kernel.h>
> +#include <linux/delay.h>
> +#include <linux/init.h>
> +#include <linux/io.h>
> +#include <linux/slab.h>
> +
> +#include "spm_driver.h"
> +
> +#define MSM_SPM_PMIC_STATE_IDLE  0
> +
> +enum {
> +	MSM_SPM_DEBUG_SHADOW = 1U << 0,
> +	MSM_SPM_DEBUG_VCTL = 1U << 1,
> +};
> +
> +static int msm_spm_debug_mask;
> +module_param_named(
> +	debug_mask, msm_spm_debug_mask, int, S_IRUGO | S_IWUSR | S_IWGRP
> +);
> +
> +struct saw2_data {
> +	const char *ver_name;
> +	uint32_t major;
> +	uint32_t minor;
> +	uint32_t *spm_reg_offset_ptr;
> +};
> +
> +static uint32_t msm_spm_reg_offsets_saw2_v2_1[MSM_SPM_REG_NR] = {
> +	[MSM_SPM_REG_SAW2_SECURE]		= 0x00,
> +	[MSM_SPM_REG_SAW2_ID]			= 0x04,
> +	[MSM_SPM_REG_SAW2_CFG]			= 0x08,
> +	[MSM_SPM_REG_SAW2_SPM_STS]		= 0x0C,
> +	[MSM_SPM_REG_SAW2_AVS_STS]		= 0x10,
> +	[MSM_SPM_REG_SAW2_PMIC_STS]		= 0x14,
> +	[MSM_SPM_REG_SAW2_RST]			= 0x18,
> +	[MSM_SPM_REG_SAW2_VCTL]			= 0x1C,
> +	[MSM_SPM_REG_SAW2_AVS_CTL]		= 0x20,
> +	[MSM_SPM_REG_SAW2_AVS_LIMIT]		= 0x24,
> +	[MSM_SPM_REG_SAW2_AVS_DLY]		= 0x28,
> +	[MSM_SPM_REG_SAW2_AVS_HYSTERESIS]	= 0x2C,
> +	[MSM_SPM_REG_SAW2_SPM_CTL]		= 0x30,
> +	[MSM_SPM_REG_SAW2_SPM_DLY]		= 0x34,
> +	[MSM_SPM_REG_SAW2_PMIC_DATA_0]		= 0x40,
> +	[MSM_SPM_REG_SAW2_PMIC_DATA_1]		= 0x44,
> +	[MSM_SPM_REG_SAW2_PMIC_DATA_2]		= 0x48,
> +	[MSM_SPM_REG_SAW2_PMIC_DATA_3]		= 0x4C,
> +	[MSM_SPM_REG_SAW2_PMIC_DATA_4]		= 0x50,
> +	[MSM_SPM_REG_SAW2_PMIC_DATA_5]		= 0x54,
> +	[MSM_SPM_REG_SAW2_PMIC_DATA_6]		= 0x58,
> +	[MSM_SPM_REG_SAW2_PMIC_DATA_7]		= 0x5C,
> +	[MSM_SPM_REG_SAW2_SEQ_ENTRY]		= 0x80,
> +	[MSM_SPM_REG_SAW2_VERSION]		= 0xFD0,
> +};
> +
> +static uint32_t msm_spm_reg_offsets_saw2_v3_0[MSM_SPM_REG_NR] = {
> +	[MSM_SPM_REG_SAW2_SECURE]		= 0x00,
> +	[MSM_SPM_REG_SAW2_ID]			= 0x04,
> +	[MSM_SPM_REG_SAW2_CFG]			= 0x08,
> +	[MSM_SPM_REG_SAW2_SPM_STS]		= 0x0C,
> +	[MSM_SPM_REG_SAW2_AVS_STS]		= 0x10,
> +	[MSM_SPM_REG_SAW2_PMIC_STS]		= 0x14,
> +	[MSM_SPM_REG_SAW2_RST]			= 0x18,
> +	[MSM_SPM_REG_SAW2_VCTL]			= 0x1C,
> +	[MSM_SPM_REG_SAW2_AVS_CTL]		= 0x20,
> +	[MSM_SPM_REG_SAW2_AVS_LIMIT]		= 0x24,
> +	[MSM_SPM_REG_SAW2_AVS_DLY]		= 0x28,
> +	[MSM_SPM_REG_SAW2_AVS_HYSTERESIS]	= 0x2C,
> +	[MSM_SPM_REG_SAW2_SPM_CTL]		= 0x30,
> +	[MSM_SPM_REG_SAW2_SPM_DLY]		= 0x34,
> +	[MSM_SPM_REG_SAW2_STS2]			= 0x38,
> +	[MSM_SPM_REG_SAW2_PMIC_DATA_0]		= 0x40,
> +	[MSM_SPM_REG_SAW2_PMIC_DATA_1]		= 0x44,
> +	[MSM_SPM_REG_SAW2_PMIC_DATA_2]		= 0x48,
> +	[MSM_SPM_REG_SAW2_PMIC_DATA_3]		= 0x4C,
> +	[MSM_SPM_REG_SAW2_PMIC_DATA_4]		= 0x50,
> +	[MSM_SPM_REG_SAW2_PMIC_DATA_5]		= 0x54,
> +	[MSM_SPM_REG_SAW2_PMIC_DATA_6]		= 0x58,
> +	[MSM_SPM_REG_SAW2_PMIC_DATA_7]		= 0x5C,
> +	[MSM_SPM_REG_SAW2_SEQ_ENTRY]		= 0x400,
> +	[MSM_SPM_REG_SAW2_VERSION]		= 0xFD0,
> +};

I don’t see what having these arrays provides as the only differences are that v3.0 has MSM_SPM_REG_SAW2_STS2 and the offset of MSM_SPM_REG_SAW2_SEQ_ENTRY.  If so we can remove all this extra code and just add a simple check in msm_spm_drv_flush_seq_entry that looks at the compatible and picks the proper offset when updating MSM_SPM_REG_SAW2_SEQ_ENTRY.

> +
> +static struct saw2_data saw2_info[] = {
> +	[0] = {
> +		"SAW2_v2.1",
> +		2,
> +		1,
> +		msm_spm_reg_offsets_saw2_v2_1,
> +	},
> +	[1] = {
> +		"SAW2_v3.0",
> +		3,
> +		0,
> +		msm_spm_reg_offsets_saw2_v3_0,
> +	},
> +};
> +
> +static uint32_t num_pmic_data;
> +
> +static inline uint32_t msm_spm_drv_get_num_spm_entry(
> +		struct msm_spm_driver_data *dev)
> +{
> +	return 32;
> +}
> +
> +static void msm_spm_drv_flush_shadow(struct msm_spm_driver_data *dev,
> +		unsigned int reg_index)
> +{
> +	__raw_writel(dev->reg_shadow[reg_index],
> +		dev->reg_base_addr + dev->reg_offsets[reg_index]);
> +}

have you looked at regmap and if that can accomplish the same goal as what this shadow stuff is doing?

> +
> +static void msm_spm_drv_load_shadow(struct msm_spm_driver_data *dev,
> +		unsigned int reg_index)
> +{
> +	dev->reg_shadow[reg_index] =
> +		__raw_readl(dev->reg_base_addr +
> +				dev->reg_offsets[reg_index]);
> +}
> +
> +static inline void msm_spm_drv_set_start_addr(
> +		struct msm_spm_driver_data *dev, uint32_t addr, bool pc_mode)
> +{
> +	addr &= 0x7F;
> +	addr <<= 4;
> +	dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] &= 0xFFFFF80F;
> +	dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] |= addr;
> +
> +	if (dev->major != 0x3)
> +		return;
> +
> +	dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] &= 0xFFFEFFFF;
> +	if (pc_mode)
> +		dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] |= 0x00010000;
> +}
> +
> +static inline bool msm_spm_pmic_arb_present(struct msm_spm_driver_data *dev)
> +{
> +	msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW2_ID);
> +	return (dev->reg_shadow[MSM_SPM_REG_SAW2_ID] >> 2) & 0x1;
> +}
> +
> +static inline void msm_spm_drv_set_vctl2(struct msm_spm_driver_data *dev,
> +		uint32_t vlevel)
> +{
> +	unsigned int pmic_data = 0;
> +
> +	/**
> +	 * VCTL_PORT has to be 0, for PMIC_STS register to be updated.
> +	 * Ensure that vctl_port is always set to 0.
> +	 */
> +	WARN_ON(dev->vctl_port);
> +
> +	pmic_data |= vlevel;
> +	pmic_data |= (dev->vctl_port & 0x7) << 16;
> +
> +	dev->reg_shadow[MSM_SPM_REG_SAW2_VCTL] &= ~0x700FF;
> +	dev->reg_shadow[MSM_SPM_REG_SAW2_VCTL] |= pmic_data;
> +
> +	dev->reg_shadow[MSM_SPM_REG_SAW2_PMIC_DATA_3] &= ~0x700FF;
> +	dev->reg_shadow[MSM_SPM_REG_SAW2_PMIC_DATA_3] |= pmic_data;
> +
> +	msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW2_VCTL);
> +	msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW2_PMIC_DATA_3);
> +}
> +
> +static inline uint32_t msm_spm_drv_get_num_pmic_data(
> +		struct msm_spm_driver_data *dev)
> +{
> +	msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW2_ID);
> +	mb();
> +	return (dev->reg_shadow[MSM_SPM_REG_SAW2_ID] >> 4) & 0x7;
> +}
> +
> +static inline uint32_t msm_spm_drv_get_sts_pmic_state(
> +		struct msm_spm_driver_data *dev)
> +{
> +	msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW2_PMIC_STS);
> +	return (dev->reg_shadow[MSM_SPM_REG_SAW2_PMIC_STS] >> 16) &
> +				0x03;
> +}
> +
> +uint32_t msm_spm_drv_get_sts_curr_pmic_data(
> +		struct msm_spm_driver_data *dev)
> +{
> +	msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW2_PMIC_STS);
> +	return dev->reg_shadow[MSM_SPM_REG_SAW2_PMIC_STS] & 0xFF;
> +}
> +
> +inline int msm_spm_drv_set_spm_enable(
> +		struct msm_spm_driver_data *dev, bool enable)
> +{
> +	uint32_t value = enable ? 0x01 : 0x00;
> +
> +	if (!dev)
> +		return -EINVAL;
> +
> +	if ((dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] & 0x01) ^ value) {
> +
> +		dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] &= ~0x1;
> +		dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] |= value;
> +
> +		msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW2_SPM_CTL);
> +		wmb();
> +	}
> +	return 0;
> +}
> +void msm_spm_drv_flush_seq_entry(struct msm_spm_driver_data *dev)
> +{
> +	int i;
> +	int num_spm_entry = msm_spm_drv_get_num_spm_entry(dev);
> +
> +	if (!dev) {
> +		__WARN();
> +		return;
> +	}
> +
> +	for (i = 0; i < num_spm_entry; i++) {
> +		__raw_writel(dev->reg_seq_entry_shadow[i],
> +			dev->reg_base_addr
> +			+ dev->reg_offsets[MSM_SPM_REG_SAW2_SEQ_ENTRY]
> +			+ 4 * i);
> +	}
> +	mb();
> +}
> +
> +void dump_regs(struct msm_spm_driver_data *dev, int cpu)

This should probably be something like __msm_spm_dump_regs()

> +{
> +	msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW2_SPM_STS);
> +	mb();
> +	pr_err("CPU%d: spm register MSM_SPM_REG_SAW2_SPM_STS: 0x%x\n", cpu,
> +			dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_STS]);
> +	msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW2_SPM_CTL);
> +	mb();
> +	pr_err("CPU%d: spm register MSM_SPM_REG_SAW2_SPM_CTL: 0x%x\n", cpu,
> +			dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL]);
> +}
> +
> +int msm_spm_drv_write_seq_data(struct msm_spm_driver_data *dev,
> +		uint8_t *cmd, uint32_t *offset)
> +{
> +	uint32_t cmd_w;
> +	uint32_t offset_w = *offset / 4;
> +	uint8_t last_cmd;
> +
> +	if (!cmd)
> +		return -EINVAL;
> +
> +	while (1) {
> +		int i;
> +
> +		cmd_w = 0;
> +		last_cmd = 0;
> +		cmd_w = dev->reg_seq_entry_shadow[offset_w];
> +
> +		for (i = (*offset % 4); i < 4; i++) {
> +			last_cmd = *(cmd++);
> +			cmd_w |=  last_cmd << (i * 8);
> +			(*offset)++;
> +			if (last_cmd == 0x0f)
> +				break;
> +		}
> +
> +		dev->reg_seq_entry_shadow[offset_w++] = cmd_w;
> +		if (last_cmd == 0x0f)
> +			break;
> +	}
> +
> +	return 0;
> +}
> +
> +int msm_spm_drv_set_low_power_mode(struct msm_spm_driver_data *dev,
> +		uint32_t addr, bool pc_mode)
> +{
> +
> +	if (!dev)
> +		return -EINVAL;
> +
> +	msm_spm_drv_set_start_addr(dev, addr, pc_mode);
> +
> +	msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW2_SPM_CTL);
> +	wmb();
> +
> +	if (msm_spm_debug_mask & MSM_SPM_DEBUG_SHADOW) {
> +		int i;
> +
> +		for (i = 0; i < MSM_SPM_REG_NR; i++)
> +			pr_info("%s: reg %02x = 0x%08x\n", __func__,
> +				dev->reg_offsets[i], dev->reg_shadow[i]);
> +	}
> +	msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW2_SPM_STS);
> +
> +	return 0;
> +}
> +
> +int msm_spm_drv_set_vdd(struct msm_spm_driver_data *dev, unsigned int vlevel)
> +{
> +	uint32_t timeout_us, new_level;
> +
> +	if (!dev)
> +		return -EINVAL;
> +
> +	if (!msm_spm_pmic_arb_present(dev))
> +		return -ENOSYS;
> +
> +	if (msm_spm_debug_mask & MSM_SPM_DEBUG_VCTL)
> +		pr_info("%s: requesting vlevel %#x\n", __func__, vlevel);
> +
> +	/* Kick the state machine back to idle */
> +	dev->reg_shadow[MSM_SPM_REG_SAW2_RST] = 1;
> +	msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW2_RST);
> +
> +	msm_spm_drv_set_vctl2(dev, vlevel);
> +
> +	timeout_us = dev->vctl_timeout_us;
> +	/* Confirm the voltage we set was what hardware sent */
> +	do {
> +		new_level = msm_spm_drv_get_sts_curr_pmic_data(dev);
> +		if (new_level == vlevel)
> +			break;
> +		udelay(1);
> +	} while (--timeout_us);
> +	if (!timeout_us) {
> +		pr_info("Wrong level %#x\n", new_level);
> +		goto set_vdd_bail;
> +	}
> +
> +	if (msm_spm_debug_mask & MSM_SPM_DEBUG_VCTL)
> +		pr_info("%s: done, remaining timeout %u us\n",
> +			__func__, timeout_us);
> +
> +	return 0;
> +
> +set_vdd_bail:
> +	pr_err("%s: failed %#x, remaining timeout %uus, vlevel %#x\n",
> +		__func__, vlevel, timeout_us, new_level);
> +	return -EIO;
> +}
> +
> +static int msm_spm_drv_get_pmic_port(struct msm_spm_driver_data *dev,
> +		enum msm_spm_pmic_port port)
> +{
> +	int index = -1;
> +
> +	switch (port) {
> +	case MSM_SPM_PMIC_VCTL_PORT:
> +		index = dev->vctl_port;
> +		break;
> +	case MSM_SPM_PMIC_PHASE_PORT:
> +		index = dev->phase_port;
> +		break;
> +	case MSM_SPM_PMIC_PFM_PORT:
> +		index = dev->pfm_port;
> +		break;
> +	default:
> +		break;
> +	}
> +
> +	return index;
> +}
> +
> +int msm_spm_drv_set_pmic_data(struct msm_spm_driver_data *dev,
> +		enum msm_spm_pmic_port port, unsigned int data)
> +{
> +	unsigned int pmic_data = 0;
> +	unsigned int timeout_us = 0;
> +	int index = 0;
> +
> +	if (!msm_spm_pmic_arb_present(dev))
> +		return -ENOSYS;
> +
> +	index = msm_spm_drv_get_pmic_port(dev, port);
> +	if (index < 0)
> +		return -ENODEV;
> +
> +	pmic_data |= data & 0xFF;
> +	pmic_data |= (index & 0x7) << 16;
> +
> +	dev->reg_shadow[MSM_SPM_REG_SAW2_VCTL] &= ~0x700FF;
> +	dev->reg_shadow[MSM_SPM_REG_SAW2_VCTL] |= pmic_data;
> +	msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW2_VCTL);
> +	mb();
> +
> +	timeout_us = dev->vctl_timeout_us;
> +	/**
> +	 * Confirm the pmic data set was what hardware sent by
> +	 * checking the PMIC FSM state.
> +	 * We cannot use the sts_pmic_data and check it against
> +	 * the value like we do fot set_vdd, since the PMIC_STS
> +	 * is only updated for SAW_VCTL sent with port index 0.
> +	 */
> +	do {
> +		if (msm_spm_drv_get_sts_pmic_state(dev) ==
> +				MSM_SPM_PMIC_STATE_IDLE)
> +			break;
> +		udelay(1);
> +	} while (--timeout_us);
> +
> +	if (!timeout_us) {
> +		pr_err("%s: failed, remaining timeout %u us, data %d\n",
> +				__func__, timeout_us, data);
> +		return -EIO;
> +	}
> +
> +	return 0;
> +}
> +
> +void msm_spm_drv_reinit(struct msm_spm_driver_data *dev)
> +{
> +	int i;
> +
> +	msm_spm_drv_flush_seq_entry(dev);
> +	for (i = 0; i < MSM_SPM_REG_SAW2_PMIC_DATA_0 + num_pmic_data; i++)
> +		msm_spm_drv_flush_shadow(dev, i);
> +
> +	mb();
> +
> +	for (i = MSM_SPM_REG_NR_INITIALIZE + 1; i < MSM_SPM_REG_NR; i++)
> +		msm_spm_drv_load_shadow(dev, i);
> +}
> +
> +int msm_spm_drv_init(struct msm_spm_driver_data *dev,
> +		struct msm_spm_platform_data *data)
> +{
> +	int i;
> +	int num_spm_entry;
> +	bool found = false;
> +
> +	BUG_ON(!dev || !data);
> +
> +	dev->vctl_port = data->vctl_port;
> +	dev->phase_port = data->phase_port;
> +	dev->pfm_port = data->pfm_port;
> +	dev->reg_base_addr = data->reg_base_addr;
> +	memcpy(dev->reg_shadow, data->reg_init_values,
> +			sizeof(data->reg_init_values));
> +
> +	dev->vctl_timeout_us = data->vctl_timeout_us;
> +
> +	for (i = 0; i < ARRAY_SIZE(saw2_info); i++)
> +		if (dev->major == saw2_info[i].major &&
> +			dev->minor == saw2_info[i].minor) {
> +			pr_debug("%s: Version found\n",
> +					saw2_info[i].ver_name);
> +			dev->reg_offsets = saw2_info[i].spm_reg_offset_ptr;
> +			found = true;
> +			break;
> +		}
> +
> +	if (!found) {
> +		pr_err("%s: No SAW2 version found\n", __func__);
> +		BUG_ON(!found);
> +	}
> +
> +	if (!num_pmic_data)
> +		num_pmic_data = msm_spm_drv_get_num_pmic_data(dev);
> +
> +	num_spm_entry = msm_spm_drv_get_num_spm_entry(dev);
> +
> +	dev->reg_seq_entry_shadow =
> +		kzalloc(sizeof(*dev->reg_seq_entry_shadow) * num_spm_entry,
> +				GFP_KERNEL);
> +
> +	if (!dev->reg_seq_entry_shadow)
> +		return -ENOMEM;
> +
> +	return 0;
> +}
> diff --git a/drivers/soc/qcom/spm_driver.h b/drivers/soc/qcom/spm_driver.h
> new file mode 100644
> index 0000000..b306520
> --- /dev/null
> +++ b/drivers/soc/qcom/spm_driver.h
> @@ -0,0 +1,116 @@
> +/* Copyright (c) 2011-2014, The Linux Foundation. All rights reserved.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 and
> + * only version 2 as published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + * GNU General Public License for more details.
> + */
> +#ifndef __QCOM_SPM_DRIVER_H
> +#define __QCOM_SPM_DRIVER_H
> +
> +#include <soc/qcom/spm.h>
> +
> +enum {
> +	MSM_SPM_REG_SAW2_CFG,
> +	MSM_SPM_REG_SAW2_AVS_CTL,
> +	MSM_SPM_REG_SAW2_AVS_HYSTERESIS,
> +	MSM_SPM_REG_SAW2_SPM_CTL,
> +	MSM_SPM_REG_SAW2_PMIC_DLY,
> +	MSM_SPM_REG_SAW2_AVS_LIMIT,
> +	MSM_SPM_REG_SAW2_AVS_DLY,
> +	MSM_SPM_REG_SAW2_SPM_DLY,
> +	MSM_SPM_REG_SAW2_PMIC_DATA_0,
> +	MSM_SPM_REG_SAW2_PMIC_DATA_1,
> +	MSM_SPM_REG_SAW2_PMIC_DATA_2,
> +	MSM_SPM_REG_SAW2_PMIC_DATA_3,
> +	MSM_SPM_REG_SAW2_PMIC_DATA_4,
> +	MSM_SPM_REG_SAW2_PMIC_DATA_5,
> +	MSM_SPM_REG_SAW2_PMIC_DATA_6,
> +	MSM_SPM_REG_SAW2_PMIC_DATA_7,
> +	MSM_SPM_REG_SAW2_RST,
> +
> +	MSM_SPM_REG_NR_INITIALIZE = MSM_SPM_REG_SAW2_RST,
> +
> +	MSM_SPM_REG_SAW2_ID,
> +	MSM_SPM_REG_SAW2_SECURE,
> +	MSM_SPM_REG_SAW2_STS0,
> +	MSM_SPM_REG_SAW2_STS1,
> +	MSM_SPM_REG_SAW2_STS2,
> +	MSM_SPM_REG_SAW2_VCTL,
> +	MSM_SPM_REG_SAW2_SEQ_ENTRY,
> +	MSM_SPM_REG_SAW2_SPM_STS,
> +	MSM_SPM_REG_SAW2_AVS_STS,
> +	MSM_SPM_REG_SAW2_PMIC_STS,
> +	MSM_SPM_REG_SAW2_VERSION,
> +
> +	MSM_SPM_REG_NR,
> +};
> +
> +struct msm_spm_seq_entry {
> +	uint32_t mode;
> +	uint8_t *cmd;
> +	bool  notify_rpm;
> +};
> +
> +struct msm_spm_platform_data {
> +	void __iomem *reg_base_addr;
> +	uint32_t reg_init_values[MSM_SPM_REG_NR_INITIALIZE];
> +
> +	uint32_t major;
> +	uint32_t minor;
> +	uint32_t vctl_port;
> +	uint32_t phase_port;
> +	uint32_t pfm_port;
> +
> +	uint8_t awake_vlevel;
> +	uint32_t vctl_timeout_us;
> +
> +	uint32_t num_modes;
> +	struct msm_spm_seq_entry *modes;
> +};
> +
> +enum msm_spm_pmic_port {
> +	MSM_SPM_PMIC_VCTL_PORT,
> +	MSM_SPM_PMIC_PHASE_PORT,
> +	MSM_SPM_PMIC_PFM_PORT,
> +};
> +
> +struct msm_spm_driver_data {
> +	uint32_t major;
> +	uint32_t minor;
> +	uint32_t vctl_port;
> +	uint32_t phase_port;
> +	uint32_t pfm_port;
> +	void __iomem *reg_base_addr;
> +	uint32_t vctl_timeout_us;
> +	uint32_t reg_shadow[MSM_SPM_REG_NR];
> +	uint32_t *reg_seq_entry_shadow;
> +	uint32_t *reg_offsets;
> +};
> +
> +int msm_spm_drv_init(struct msm_spm_driver_data *dev,
> +		struct msm_spm_platform_data *data);
> +void msm_spm_drv_reinit(struct msm_spm_driver_data *dev);
> +int msm_spm_drv_set_low_power_mode(struct msm_spm_driver_data *dev,
> +		uint32_t addr, bool pc_mode);
> +int msm_spm_drv_set_vdd(struct msm_spm_driver_data *dev,
> +		unsigned int vlevel);
> +void dump_regs(struct msm_spm_driver_data *dev, int cpu);
> +uint32_t msm_spm_drv_get_sts_curr_pmic_data(
> +		struct msm_spm_driver_data *dev);
> +int msm_spm_drv_write_seq_data(struct msm_spm_driver_data *dev,
> +		uint8_t *cmd, uint32_t *offset);
> +void msm_spm_drv_flush_seq_entry(struct msm_spm_driver_data *dev);
> +int msm_spm_drv_set_spm_enable(struct msm_spm_driver_data *dev,
> +		bool enable);
> +int msm_spm_drv_set_pmic_data(struct msm_spm_driver_data *dev,
> +		enum msm_spm_pmic_port port, unsigned int data);
> +
> +void msm_spm_reinit(void);
> +int msm_spm_init(struct msm_spm_platform_data *data, int nr_devs);
> +
> +#endif /* __QCOM_SPM_DRIVER_H */
> diff --git a/include/soc/qcom/spm.h b/include/soc/qcom/spm.h
> new file mode 100644
> index 0000000..f39e0c4
> --- /dev/null
> +++ b/include/soc/qcom/spm.h
> @@ -0,0 +1,70 @@
> +/* Copyright (c) 2010-2014, The Linux Foundation. All rights reserved.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 and
> + * only version 2 as published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + * GNU General Public License for more details.
> + */
> +
> +#ifndef __QCOM_SPM_H
> +#define __QCOM_SPM_H
> +
> +enum {
> +	MSM_SPM_MODE_DISABLED,
> +	MSM_SPM_MODE_CLOCK_GATING,
> +	MSM_SPM_MODE_RETENTION,
> +	MSM_SPM_MODE_GDHS,
> +	MSM_SPM_MODE_POWER_COLLAPSE,
> +	MSM_SPM_MODE_NR
> +};
> +
> +struct msm_spm_device;
> +
> +#if defined(CONFIG_QCOM_PM)

Where is CONFIG_QCOM_PM defined?  Wondering if we should have a CONFIG_QCOM_SPM and it can depend on any future ‘QCOM_PM’ in the Kconfig.

> +int msm_spm_set_low_power_mode(unsigned int mode, bool notify_rpm);
> +int msm_spm_probe_done(void);
> +int msm_spm_set_vdd(unsigned int cpu, unsigned int vlevel);
> +unsigned int msm_spm_get_vdd(unsigned int cpu);
> +int msm_spm_turn_on_cpu_rail(void __iomem *base, unsigned int val, int cpu);
> +struct msm_spm_device *msm_spm_get_device_by_name(const char *name);
> +int msm_spm_config_low_power_mode(struct msm_spm_device *dev,
> +		unsigned int mode, bool notify_rpm);
> +int msm_spm_device_init(void);
> +bool msm_spm_is_mode_avail(unsigned int mode);
> +void msm_spm_dump_regs(unsigned int cpu);
> +int msm_spm_apcs_set_phase(int cpu, unsigned int phase_cnt);
> +int msm_spm_enable_fts_lpm(int cpu, uint32_t mode);
> +#else /* defined(CONFIG_QCOM_PM) */
> +static inline int msm_spm_set_low_power_mode(unsigned int mode, bool notify_rpm)
> +{ return -ENOSYS; }
> +static inline int msm_spm_probe_done(void)
> +{ return -ENOSYS; }
> +static inline int msm_spm_set_vdd(unsigned int cpu, unsigned int vlevel)
> +{ return -ENOSYS; }
> +static inline unsigned int msm_spm_get_vdd(unsigned int cpu)
> +{ return 0; }
> +static inline int msm_spm_turn_on_cpu_rail(void __iomem *base,
> +		unsigned int val, int cpu)
> +{ return -ENOSYS; }
> +static inline int msm_spm_device_init(void)
> +{ return -ENOSYS; }
> +static void msm_spm_dump_regs(unsigned int cpu) {}
> +static inline int msm_spm_config_low_power_mode(struct msm_spm_device *dev,
> +		unsigned int mode, bool notify_rpm)
> +{ return -ENODEV; }
> +static inline struct msm_spm_device *msm_spm_get_device_by_name(
> +		const char *name)
> +{ return NULL; }
> +static inline bool msm_spm_is_mode_avail(unsigned int mode)
> +{ return false; }
> +static inline int msm_spm_apcs_set_phase(int cpu, unsigned int phase_cnt)
> +{ return -ENOSYS; }
> +static inline int msm_spm_enable_fts_lpm(int cpu, uint32_t mode)
> +{ return -ENOSYS; }
> +#endif  /* defined (CONFIG_QCOM_PM) */
> +
> +#endif  /* __QCOM_SPM_H */
> -- 
> 1.9.1
> 
> --
> To unsubscribe from this list: send the line "unsubscribe linux-arm-msm" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html

-- 
Employee of Qualcomm Innovation Center, Inc.
Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum, hosted by The Linux Foundation

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

* Re: [PATCH v2 03/10] qcom: spm: Add Subsystem Power Manager (SPM) driver for QCOM chipsets
  2014-08-14 13:01   ` Pramod Gurav
@ 2014-08-14 15:18     ` Lina Iyer
  0 siblings, 0 replies; 46+ messages in thread
From: Lina Iyer @ 2014-08-14 15:18 UTC (permalink / raw)
  To: Pramod Gurav
  Cc: daniel.lezcano, khilman, amit.kucheria, sboyd, davidb, galak,
	linux-arm-msm, msivasub, Praveen Chidamabram, Murali Nalajala

On Thu, Aug 14, 2014 at 06:31:09PM +0530, Pramod Gurav wrote:
>On Wednesday 13 August 2014 01:13 AM, Lina Iyer wrote:
>> Qualcomm chipsets use an separate h/w block to control the logic around
>> the processor cores (cpu and L2). The SPM h/w block regulates power to
>> the cores and controls the power when the core enter low power modes.
>>
>> Each core has its own instance of SPM. The SPM has the following key
>> functions
>> 	- Configure the h/w dependencies when entering low power modes
>> 	- Wait for interrupt and wake up on interrupt
>> 	- Ensure the dependencies are ready before bringing the core out
>> 	  of sleep
>> 	- Regulating voltage to the core, interfacing with the PMIC.
>> 	- Optimize power based on runtime recommendations.
>>
>> The driver identifies and configures the SPMs, by reading the nodes and
>> the register values from the devicetree. The SPMs need to be configured
>> to allow the processor to be idled in a low power state.
>>
>> Signed-off-by: Praveen Chidamabram <pchidamb@codeaurora.org>
>> Signed-off-by: Murali Nalajala <mnalajal@codeaurora.org>
>> Signed-off-by: Lina Iyer <lina.iyer@linaro.org>
>> ---
>> +
>
><snip>
>
>>  CFLAGS_scm.o :=$(call as-instr,.arch_extension sec,-DREQUIRES_SEC=1)
>>  obj-$(CONFIG_QCOM_SCM) += scm.o scm-boot.o
>> diff --git a/drivers/soc/qcom/spm-devices.c b/drivers/soc/qcom/spm-devices.c
>> new file mode 100644
>> index 0000000..567e9f9
>> --- /dev/null
>> +++ b/drivers/soc/qcom/spm-devices.c
>
><snip>
>
>> +	if (ret)
>> +		return ret;
>> +
>> +	return dev->cpu_vdd;
>> +}
>> +EXPORT_SYMBOL(msm_spm_get_vdd);
>> +
>> +static void msm_spm_config_q2s(struct msm_spm_device *dev, unsigned int mode)
>> +{
>> +	uint32_t spm_legacy_mode = 0;
>> +	uint32_t qchannel_ignore = 0;
>> +	uint32_t val = 0;
>Initialization not needed for val.
>> +
>> +	if (!dev->q2s_reg)
>> +		return;
>> +
>> +	switch (mode) {
>> +	case MSM_SPM_MODE_DISABLED:
>> +	case MSM_SPM_MODE_CLOCK_GATING:
>> +		qchannel_ignore = 1;
>> +		spm_legacy_mode = 0;
>> +		break;
>> +	case MSM_SPM_MODE_RETENTION:
>> +		qchannel_ignore = 0;
>> +		spm_legacy_mode = 0;
>> +		break;
>> +	case MSM_SPM_MODE_GDHS:
>> +	case MSM_SPM_MODE_POWER_COLLAPSE:
>> +		qchannel_ignore = 0;
>> +		spm_legacy_mode = 1;
>> +		break;
>> +	default:
>> +		break;
>> +	}
>> +
>> +	val = spm_legacy_mode << 2 | qchannel_ignore << 1;
>> +	__raw_writel(val, dev->q2s_reg);
>> +	mb();
>This may need a comment around it.
Sorry, my bad. I picked up the latest driver and this came with it. In my
next version I pruned this code.
>> +}
>> +
>> +static int msm_spm_dev_set_low_power_mode(struct msm_spm_device *dev,
>> +		unsigned int mode, bool notify_rpm)
>> +{
>> +	uint32_t i;
>> +	uint32_t start_addr = 0;
>> +	int ret = -EINVAL;
>> +	bool pc_mode = false;
>> +
>> +	if (!dev->initialized)
>> +		return -ENXIO;
>> +
>> +	if ((mode == MSM_SPM_MODE_POWER_COLLAPSE)
>> +			|| (mode == MSM_SPM_MODE_GDHS))
>> +		pc_mode = true;
>> +
>> +	if (mode == MSM_SPM_MODE_DISABLED) {
>> +		ret = msm_spm_drv_set_spm_enable(&dev->reg_data, false);
>> +	} else if (!msm_spm_drv_set_spm_enable(&dev->reg_data, true)) {
>> +		for (i = 0; i < dev->num_modes; i++) {
>> +			if ((dev->modes[i].mode == mode) &&
>> +				(dev->modes[i].notify_rpm == notify_rpm)) {
>> +				start_addr = dev->modes[i].start_addr;
>> +				break;
>> +			}
>> +		}
>> +		ret = msm_spm_drv_set_low_power_mode(&dev->reg_data,
>> +					start_addr, pc_mode);
>> +	}
>> +
>> +	msm_spm_config_q2s(dev, mode);
>> +
>> +	return ret;
>> +}
>> +
>> +static int msm_spm_dev_init(struct msm_spm_device *dev,
>> +		struct msm_spm_platform_data *data)
>> +{
>> +	int i, ret = -ENOMEM;
>> +	uint32_t offset = 0;
>> +
>> +	dev->cpu_vdd = VDD_DEFAULT;
>> +	dev->num_modes = data->num_modes;
>> +	dev->modes = kmalloc(
>> +			sizeof(struct msm_spm_power_modes) * dev->num_modes,
>> +			GFP_KERNEL);
>> +
>> +	if (!dev->modes)
>> +		goto spm_failed_malloc;
>> +
>> +	dev->reg_data.major = data->major;
>> +	dev->reg_data.minor = data->minor;
>> +	ret = msm_spm_drv_init(&dev->reg_data, data);
>> +
>Please remove extra line.
>> +	if (ret)
>> +		goto spm_failed_init;
>> +
>> +	for (i = 0; i < dev->num_modes; i++) {
>> +
>> +		/* Default offset is 0 and gets updated as we write more
>> +		 * sequences into SPM
>> +		 */
>> +		dev->modes[i].start_addr = offset;
>> +		ret = msm_spm_drv_write_seq_data(&dev->reg_data,
>> +						data->modes[i].cmd, &offset);
>> +		if (ret < 0)
>> +			goto spm_failed_init;
>> +
>> +		dev->modes[i].mode = data->modes[i].mode;
>> +		dev->modes[i].notify_rpm = data->modes[i].notify_rpm;
>> +	}
>> +	msm_spm_drv_reinit(&dev->reg_data);
>> +	dev->initialized = true;
>> +	return 0;
>> +
>> +spm_failed_init:
>> +	kfree(dev->modes);
>Should not we release dev->reg_seq_entry_shadow allocated in
>msm_spm_drv_init?
Good point. Will free it.

>> +spm_failed_malloc:
>> +	return ret;
>> +}
>> +
>> +/**
>> + * msm_spm_turn_on_cpu_rail(): Power on cpu rail before turning on core
>
><snip>
>
>> +int __init msm_spm_init(struct msm_spm_platform_data *data, int nr_devs)
>> +{
>> +	unsigned int cpu;
>> +	int ret = 0;
>Initialization not needed.
>> +
>> +	BUG_ON((nr_devs < num_possible_cpus()) || !data);
>> +
>> +	for_each_possible_cpu(cpu) {
>> +		struct msm_spm_device *dev = &per_cpu(msm_cpu_spm_device, cpu);
>> +
>> +		ret = msm_spm_dev_init(dev, &data[cpu]);
>> +		if (ret < 0) {
>> +			pr_warn("%s():failed CPU:%u ret:%d\n", __func__,
>
><snip>
>
>> +static struct msm_spm_device *msm_spm_get_device(struct platform_device *pdev)
>> +{
>> +	struct msm_spm_device *dev = NULL;
>> +	const char *val = NULL;
>> +	char *key = "qcom,name";
>> +	int cpu = get_cpu_id(pdev->dev.of_node);
>> +
>> +	if ((cpu >= 0) && cpu < num_possible_cpus())
>> +		dev = &per_cpu(msm_cpu_spm_device, cpu);
>> +	else if ((cpu == 0xffff) || (cpu < 0))
>> +		dev = devm_kzalloc(&pdev->dev, sizeof(struct msm_spm_device),
>> +					GFP_KERNEL);
>> +
>> +	if (!dev)
>> +		return NULL;
>Should we return -ENOMEM? ERR_PTR(-ENOMEM)?
>> +
>> +	if (of_property_read_string(pdev->dev.of_node, key, &val)) {
>> +		pr_err("%s(): Cannot find a required node key:%s\n",
>> +				__func__, key);
>Should it be dev_err as we have access to pdev->dev?
Yes.
>> +		return NULL;
>> +	}
>> +	dev->name = val;
>> +	list_add(&dev->list, &spm_list);
>> +
>> +	return dev;
>> +}
>> +
>> +static void get_cpumask(struct device_node *node, struct cpumask *mask)
>> +{
>> +	unsigned long vctl_mask = 0;
>> +	unsigned c = 0;
>> +	int idx = 0;
>> +	struct device_node *cpu_node = NULL;
>> +	int ret = 0;
>Initialization not needed for ret.
>> +	char *key = "qcom,cpu-vctl-list";
>> +	bool found = false;
>> +
>> +	cpu_node = of_parse_phandle(node, key, idx++);
>> +	while (cpu_node) {
>> +		found = true;
>> +		for_each_possible_cpu(c) {
>> +			if (of_get_cpu_node(c, NULL) == cpu_node)
>> +				cpumask_set_cpu(c, mask);
>> +		}
>> +		cpu_node = of_parse_phandle(node, key, idx++);
>> +	};
>> +
>> +	if (found)
>> +		return;
>> +
>> +	key = "qcom,cpu-vctl-mask";
>> +	ret = of_property_read_u32(node, key, (u32 *) &vctl_mask);
>> +	if (!ret) {
>> +		for_each_set_bit(c, &vctl_mask, num_possible_cpus()) {
>> +			cpumask_set_cpu(c, mask);
>> +		}
>> +	}
>> +}
>> +
>> +static int msm_spm_dev_probe(struct platform_device *pdev)
>> +{
>> +	int ret = 0;
>> +	int cpu = 0;
>> +	int i = 0;
>Initialization not needed for all three variables.
>> +	struct device_node *node = pdev->dev.of_node;
>> +	struct msm_spm_platform_data spm_data;
>> +	char *key = NULL;
>> +	uint32_t val = 0;
>> +	struct msm_spm_seq_entry modes[MSM_SPM_MODE_NR];
>> +	int len = 0;
>> +	struct msm_spm_device *dev = NULL;
>> +	struct resource *res = NULL;
>> +	uint32_t mode_count = 0;
>> +
>> +	struct spm_of {
>> +		char *key;
>> +		uint32_t id;
>> +	};
>> +
>> +	struct spm_of spm_of_data[] = {
>> +		{"qcom,saw2-cfg", MSM_SPM_REG_SAW2_CFG},
>> +		{"qcom,saw2-spm-dly", MSM_SPM_REG_SAW2_SPM_DLY},
>> +		{"qcom,saw2-spm-ctl", MSM_SPM_REG_SAW2_SPM_CTL},
>> +		{"qcom,saw2-pmic-data0", MSM_SPM_REG_SAW2_PMIC_DATA_0},
>> +		{"qcom,saw2-pmic-data1", MSM_SPM_REG_SAW2_PMIC_DATA_1},
>> +		{"qcom,saw2-pmic-data2", MSM_SPM_REG_SAW2_PMIC_DATA_2},
>> +		{"qcom,saw2-pmic-data3", MSM_SPM_REG_SAW2_PMIC_DATA_3},
>> +		{"qcom,saw2-pmic-data4", MSM_SPM_REG_SAW2_PMIC_DATA_4},
>> +		{"qcom,saw2-pmic-data5", MSM_SPM_REG_SAW2_PMIC_DATA_5},
>> +		{"qcom,saw2-pmic-data6", MSM_SPM_REG_SAW2_PMIC_DATA_6},
>> +		{"qcom,saw2-pmic-data7", MSM_SPM_REG_SAW2_PMIC_DATA_7},
>> +	};
>> +
>> +	struct mode_of {
>> +		char *key;
>> +		uint32_t id;
>> +		uint32_t notify_rpm;
>> +	};
>> +
>> +	struct mode_of mode_of_data[] = {
>> +		{"qcom,saw2-spm-cmd-wfi", MSM_SPM_MODE_CLOCK_GATING, 0},
>> +		{"qcom,saw2-spm-cmd-ret", MSM_SPM_MODE_RETENTION, 0},
>> +		{"qcom,saw2-spm-cmd-gdhs", MSM_SPM_MODE_GDHS, 1},
>> +		{"qcom,saw2-spm-cmd-spc", MSM_SPM_MODE_POWER_COLLAPSE, 0},
>> +		{"qcom,saw2-spm-cmd-pc", MSM_SPM_MODE_POWER_COLLAPSE, 1},
>> +	};
>> +
>> +	dev = msm_spm_get_device(pdev);
>> +	if (!dev) {
>> +		ret = -ENOMEM;
>> +		goto fail;
>I might be misunderstanding something, but why cant we just return from
>here instead of jumping to fail.
>> +	}
>> +	get_cpumask(node, &dev->mask);
>> +
>> +	memset(&spm_data, 0, sizeof(struct msm_spm_platform_data));
>> +	memset(&modes, 0,
>> +		(MSM_SPM_MODE_NR - 2) * sizeof(struct msm_spm_seq_entry));
>> +
>> +	if (of_device_is_compatible(node, "qcom,spm-v2.1")) {
>> +		spm_data.major = 2;
>> +		spm_data.minor = 1;
>> +	} else if (of_device_is_compatible(node, "qcom,spm-v3.0")) {
>> +		spm_data.major = 3;
>> +		spm_data.minor = 0;
>> +	}
>> +
>> +	key = "qcom,vctl-timeout-us";
>> +	ret = of_property_read_u32(node, key, &val);
>> +	if (!ret)
>> +		spm_data.vctl_timeout_us = val;
>> +
>> +	/* SAW start address */
>> +	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
>> +	if (!res) {
>> +		ret = -EFAULT;
>Should it return -EINVAL?
>> +		goto fail;
>> +	}
>> +
>> +	spm_data.reg_base_addr = devm_ioremap(&pdev->dev, res->start,
>> +					resource_size(res));
>> +	if (!spm_data.reg_base_addr) {
>> +		ret = -ENOMEM;
>> +		goto fail;
>> +	}
>> +
>> +	spm_data.vctl_port = -1;
>> +	spm_data.phase_port = -1;
>> +	spm_data.pfm_port = -1;
>> +
>> +	key = "qcom,vctl-port";
>> +	of_property_read_u32(node, key, &spm_data.vctl_port);
>> +
>> +	key = "qcom,phase-port";
>> +	of_property_read_u32(node, key, &spm_data.phase_port);
>> +
>> +	key = "qcom,pfm-port";
>> +	of_property_read_u32(node, key, &spm_data.pfm_port);
>> +
>> +	/* Q2S (QChannel-2-SPM) register */
>> +	res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
>> +	if (res) {
>> +		dev->q2s_reg = devm_ioremap(&pdev->dev, res->start,
>> +						resource_size(res));
>> +		if (!dev->q2s_reg) {
>> +			pr_err("%s(): Unable to iomap Q2S register\n",
>Can we use dev_err since pdev->dev is accessible?
>> +					__func__);
>> +			ret = -EADDRNOTAVAIL;
>ret = -ENOMEM?
>> +			goto fail;
>> +		}
>> +	}
>> +	/*
>> +	 * At system boot, cpus and or clusters can remain in reset. CCI SPM
>> +	 * will not be triggered unless SPM_LEGACY_MODE bit is set for the
>> +	 * cluster in reset. Initialize q2s registers and set the
>> +	 * SPM_LEGACY_MODE bit.
>> +	 */
>> +	msm_spm_config_q2s(dev, MSM_SPM_MODE_POWER_COLLAPSE);
>> +
>> +	for (i = 0; i < ARRAY_SIZE(spm_of_data); i++) {
>> +		ret = of_property_read_u32(node, spm_of_data[i].key, &val);
>> +		if (ret)
>> +			continue;
>> +		spm_data.reg_init_values[spm_of_data[i].id] = val;
>> +	}
>> +
>> +	for (i = 0; i < ARRAY_SIZE(mode_of_data); i++) {
>> +		key = mode_of_data[i].key;
>> +		modes[mode_count].cmd =
>> +			(uint8_t *)of_get_property(node, key, &len);
>> +		if (!modes[mode_count].cmd)
>> +			continue;
>> +		modes[mode_count].mode = mode_of_data[i].id;
>> +		modes[mode_count].notify_rpm = mode_of_data[i].notify_rpm;
>> +		pr_debug("%s(): dev: %s cmd:%s, mode:%d rpm:%d\n", __func__,
>dev_info instead of pr_debug?
ok
>> +				dev->name, key, modes[mode_count].mode,
>> +				modes[mode_count].notify_rpm);
>> +		mode_count++;
>> +	}
>> +
>> +	spm_data.modes = modes;
>> +	spm_data.num_modes = mode_count;
>> +
>> +	ret = msm_spm_dev_init(dev, &spm_data);
>> +	if (ret)
>> +		goto fail;
>> +
>> +	platform_set_drvdata(pdev, dev);
>> +
>> +	for_each_cpu(cpu, &dev->mask)
>> +		per_cpu(cpu_vctl_device, cpu) = dev;
>> +
>> +	return ret;
>> +
>> +fail:
>> +	cpu = get_cpu_id(pdev->dev.of_node);
>> +	if (dev && (cpu >= num_possible_cpus() || (cpu < 0))) {
>> +		for_each_cpu(cpu, &dev->mask)
>> +			per_cpu(cpu_vctl_device, cpu) = ERR_PTR(ret);
>Should this be part of remove function instead of here as it is last
>initialization that is done in probe just before fail lable? If it is
>not needed the we can do away with this part of code from fail lable.
>> +	}
>Are we missing a list_del like in remove function below?
Yes we are.
>> +
>> +	pr_err("%s: CPU%d SPM device probe failed: %d\n", __func__, cpu, ret);
>dev_ instead of pr_?
>> +
>> +	return ret;
>> +}
>> +
>> +static int msm_spm_dev_remove(struct platform_device *pdev)
>> +{
>> +	struct msm_spm_device *dev = platform_get_drvdata(pdev);
>> +
>> +	list_del(&dev->list);
>kfree(dev->modes) is missing.
>kfree(dev->reg_data->reg_seq_entry_shadow) is missing.
>> +
>> +	return 0;
>> +}
>> +
>> +static struct of_device_id msm_spm_match_table[] = {
>> +	{.compatible = "qcom,spm-v2.1"},
>> +	{.compatible = "qcom,spm-v3.0"},
>> +	{},
>> +};
>> +
>> +static struct platform_driver msm_spm_device_driver = {
>> +	.probe = msm_spm_dev_probe,
>> +	.remove = msm_spm_dev_remove,
>> +	.driver = {
>> +		.name = "spm-v2",
>> +		.owner = THIS_MODULE,
>> +		.of_match_table = msm_spm_match_table,
>> +	},
>> +};
>> +
>> +/**
>> + * msm_spm_device_init(): Device tree initialization function
>> + */
>> +int __init msm_spm_device_init(void)
>It should be static.
Yes.
>> +{
>> +	static bool registered;
>> +
>> +	if (registered)
>> +		return 0;
>> +
>> +	registered = true;
>> +
>> +	return platform_driver_register(&msm_spm_device_driver);
>> +}
>> +device_initcall(msm_spm_device_init);
>> diff --git a/drivers/soc/qcom/spm.c b/drivers/soc/qcom/spm.c
>> new file mode 100644
>> index 0000000..7dbdb64
>> --- /dev/null
>> +++ b/drivers/soc/qcom/spm.c
>> @@ -0,0 +1,482 @@
>> +/* Copyright (c) 2011-2014, The Linux Foundation. All rights reserved.
>> + *
>> + * This program is free software; you can redistribute it and/or modify
>> + * it under the terms of the GNU General Public License version 2 and
>> + * only version 2 as published by the Free Software Foundation.
>> + *
>> + * This program is distributed in the hope that it will be useful,
>> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
>> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
>> + * GNU General Public License for more details.
>> + *
>> + */
>> +
>> +#include <linux/module.h>
>> +#include <linux/kernel.h>
>> +#include <linux/delay.h>
>> +#include <linux/init.h>
>> +#include <linux/io.h>
>> +#include <linux/slab.h>
>> +
>> +#include "spm_driver.h"
>> +
>> +#define MSM_SPM_PMIC_STATE_IDLE  0
>> +
>> +enum {
>> +	MSM_SPM_DEBUG_SHADOW = 1U << 0,
>> +	MSM_SPM_DEBUG_VCTL = 1U << 1,
>> +};
>> +
>> +static int msm_spm_debug_mask;
>> +module_param_named(
>> +	debug_mask, msm_spm_debug_mask, int, S_IRUGO | S_IWUSR | S_IWGRP
>> +);
>> +
>> +struct saw2_data {
>> +	const char *ver_name;
>> +	uint32_t major;
>> +	uint32_t minor;
>> +	uint32_t *spm_reg_offset_ptr;
>> +};
>> +
>> +static uint32_t msm_spm_reg_offsets_saw2_v2_1[MSM_SPM_REG_NR] = {
>> +	[MSM_SPM_REG_SAW2_SECURE]		= 0x00,
>> +	[MSM_SPM_REG_SAW2_ID]			= 0x04,
>> +	[MSM_SPM_REG_SAW2_CFG]			= 0x08,
>> +	[MSM_SPM_REG_SAW2_SPM_STS]		= 0x0C,
>> +	[MSM_SPM_REG_SAW2_AVS_STS]		= 0x10,
>> +	[MSM_SPM_REG_SAW2_PMIC_STS]		= 0x14,
>> +	[MSM_SPM_REG_SAW2_RST]			= 0x18,
>> +	[MSM_SPM_REG_SAW2_VCTL]			= 0x1C,
>> +	[MSM_SPM_REG_SAW2_AVS_CTL]		= 0x20,
>> +	[MSM_SPM_REG_SAW2_AVS_LIMIT]		= 0x24,
>> +	[MSM_SPM_REG_SAW2_AVS_DLY]		= 0x28,
>> +	[MSM_SPM_REG_SAW2_AVS_HYSTERESIS]	= 0x2C,
>> +	[MSM_SPM_REG_SAW2_SPM_CTL]		= 0x30,
>> +	[MSM_SPM_REG_SAW2_SPM_DLY]		= 0x34,
>> +	[MSM_SPM_REG_SAW2_PMIC_DATA_0]		= 0x40,
>> +	[MSM_SPM_REG_SAW2_PMIC_DATA_1]		= 0x44,
>> +	[MSM_SPM_REG_SAW2_PMIC_DATA_2]		= 0x48,
>> +	[MSM_SPM_REG_SAW2_PMIC_DATA_3]		= 0x4C,
>> +	[MSM_SPM_REG_SAW2_PMIC_DATA_4]		= 0x50,msm_spm_drv_init
>> +	[MSM_SPM_REG_SAW2_PMIC_DATA_5]		= 0x54,
>> +	[MSM_SPM_REG_SAW2_PMIC_DATA_6]		= 0x58,
>> +	[MSM_SPM_REG_SAW2_PMIC_DATA_7]		= 0x5C,
>> +	[MSM_SPM_REG_SAW2_SEQ_ENTRY]		= 0x80,
>> +	[MSM_SPM_REG_SAW2_VERSION]		= 0xFD0,
>> +};
>> +
>> +static uint32_t msm_spm_reg_offsets_saw2_v3_0[MSM_SPM_REG_NR] = {
>> +	[MSM_SPM_REG_SAW2_SECURE]		= 0x00,
>> +	[MSM_SPM_REG_SAW2_ID]			= 0x04,
>> +	[MSM_SPM_REG_SAW2_CFG]			= 0x08,
>> +	[MSM_SPM_REG_SAW2_SPM_STS]		= 0x0C,
>> +	[MSM_SPM_REG_SAW2_AVS_STS]		= 0x10,
>> +	[MSM_SPM_REG_SAW2_PMIC_STS]		= 0x14,
>> +	[MSM_SPM_REG_SAW2_RST]			= 0x18,
>> +	[MSM_SPM_REG_SAW2_VCTL]			= 0x1C,
>> +	[MSM_SPM_REG_SAW2_AVS_CTL]		= 0x20,
>> +	[MSM_SPM_REG_SAW2_AVS_LIMIT]		= 0x24,
>> +	[MSM_SPM_REG_SAW2_AVS_DLY]		= 0x28,
>> +	[MSM_SPM_REG_SAW2_AVS_HYSTERESIS]	= 0x2C,
>> +	[MSM_SPM_REG_SAW2_SPM_CTL]		= 0x30,
>> +	[MSM_SPM_REG_SAW2_SPM_DLY]		= 0x34,
>> +	[MSM_SPM_REG_SAW2_STS2]			= 0x38,
>> +	[MSM_SPM_REG_SAW2_PMIC_DATA_0]		= 0x40,
>> +	[MSM_SPM_REG_SAW2_PMIC_DATA_1]		= 0x44,
>> +	[MSM_SPM_REG_SAW2_PMIC_DATA_2]		= 0x48,
>> +	[MSM_SPM_REG_SAW2_PMIC_DATA_3]		= 0x4C,
>> +	[MSM_SPM_REG_SAW2_PMIC_DATA_4]		= 0x50,
>> +	[MSM_SPM_REG_SAW2_PMIC_DATA_5]		= 0x54,
>> +	[MSM_SPM_REG_SAW2_PMIC_DATA_6]		= 0x58,
>> +	[MSM_SPM_REG_SAW2_PMIC_DATA_7]		= 0x5C,
>> +	[MSM_SPM_REG_SAW2_SEQ_ENTRY]		= 0x400,
>> +	[MSM_SPM_REG_SAW2_VERSION]		= 0xFD0,
>> +};
>> +
>> +static struct saw2_data saw2_info[] = {
>> +	[0] = {
>> +		"SAW2_v2.1",
>> +		2,
>> +		1,
>> +		msm_spm_reg_offsets_saw2_v2_1,
>> +	},
>> +	[1] = {
>> +		"SAW2_v3.0",
>> +		3,
>> +		0,
>> +		msm_spm_reg_offsets_saw2_v3_0,
>> +	},
>> +};
>> +
>> +static uint32_t num_pmic_data;
>> +
>> +static inline uint32_t msm_spm_drv_get_num_spm_entry(
>> +		struct msm_spm_driver_data *dev)
>> +{
>> +	return 32;
>> +}
>> +
>> +static void msm_spm_drv_flush_shadow(struct msm_spm_driver_data *dev,
>> +		unsigned int reg_index)
>> +{
>> +	__raw_writel(dev->reg_shadow[reg_index],
>> +		dev->reg_base_addr + dev->reg_offsets[reg_index]);
>> +}
>> +
>> +static void msm_spm_drv_load_shadow(struct msm_spm_driver_data *dev,
>> +		unsigned int reg_index)
>> +{
>> +	dev->reg_shadow[reg_index] =
>> +		__raw_readl(dev->reg_base_addr +
>> +				dev->reg_offsets[reg_index]);
>> +}
>> +
>> +static inline void msm_spm_drv_set_start_addr(
>> +		struct msm_spm_driver_data *dev, uint32_t addr, bool pc_mode)
>> +{
>> +	addr &= 0x7F;
>> +	addr <<= 4;
>> +	dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] &= 0xFFFFF80F;
>> +	dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] |= addr;
>> +
>> +	if (dev->major != 0x3)
>> +		return;
>> +
>> +	dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] &= 0xFFFEFFFF;
>> +	if (pc_mode)
>> +		dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] |= 0x00010000;
>> +}
>> +
>> +static inline bool msm_spm_pmic_arb_present(struct msm_spm_driver_data *dev)
>> +{
>> +	msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW2_ID);
>> +	return (dev->reg_shadow[MSM_SPM_REG_SAW2_ID] >> 2) & 0x1;
>> +}
>> +
>> +static inline void msm_spm_drv_set_vctl2(struct msm_spm_driver_data *dev,
>> +		uint32_t vlevel)
>> +{
>> +	unsigned int pmic_data = 0;
>> +
>> +	/**
>> +	 * VCTL_PORT has to be 0, for PMIC_STS register to be updated.
>> +	 * Ensure that vctl_port is always set to 0.
>> +	 */
>> +	WARN_ON(dev->vctl_port);
>> +
>> +	pmic_data |= vlevel;
>> +	pmic_data |= (dev->vctl_port & 0x7) << 16;
>> +
>> +	dev->reg_shadow[MSM_SPM_REG_SAW2_VCTL] &= ~0x700FF;
>> +	dev->reg_shadow[MSM_SPM_REG_SAW2_VCTL] |= pmic_data;
>> +
>> +	dev->reg_shadow[MSM_SPM_REG_SAW2_PMIC_DATA_3] &= ~0x700FF;
>> +	dev->reg_shadow[MSM_SPM_REG_SAW2_PMIC_DATA_3] |= pmic_data;
>> +
>> +	msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW2_VCTL);
>> +	msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW2_PMIC_DATA_3);
>> +}
>> +
>> +static inline uint32_t msm_spm_drv_get_num_pmic_data(
>> +		struct msm_spm_driver_data *dev)
>> +{
>> +	msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW2_ID);
>> +	mb();
>> +	return (dev->reg_shadow[MSM_SPM_REG_SAW2_ID] >> 4) & 0x7;
>> +}
>> +
>> +static inline uint32_t msm_spm_drv_get_sts_pmic_state(
>> +		struct msm_spm_driver_data *dev)
>> +{
>> +	msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW2_PMIC_STS);
>> +	return (dev->reg_shadow[MSM_SPM_REG_SAW2_PMIC_STS] >> 16) &
>> +				0x03;
>> +}
>> +
>> +uint32_t msm_spm_drv_get_sts_curr_pmic_data(
>> +		struct msm_spm_driver_data *dev)
>> +{
>> +	msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW2_PMIC_STS);
>> +	return dev->reg_shadow[MSM_SPM_REG_SAW2_PMIC_STS] & 0xFF;
>> +}msm_spm_drv_init
>> +
>> +inline int msm_spm_drv_set_spm_enable(
>> +		struct msm_spm_driver_data *dev, bool enable)
>> +{
>> +	uint32_t value = enable ? 0x01 : 0x00;
>> +
>> +	if (!dev)
>> +		return -EINVAL;
>> +
>> +	if ((dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] & 0x01) ^ value) {
>> +
>Please remove extra new line.
>> +		dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] &= ~0x1;
>> +		dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] |= value;
>> +
>> +		msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW2_SPM_CTL);
>> +		wmb();
>> +	}
>> +	return 0;
>> +}
>> +void msm_spm_drv_flush_seq_entry(struct msm_spm_driver_data *dev)
>> +{
>> +	int i;
>> +	int num_spm_entry = msm_spm_drv_get_num_spm_entry(dev);
>> +
>> +	if (!dev) {
>> +		__WARN();
>> +		return;
>> +	}msm_spm_drv_init
>> +
>> +	for (i = 0; i < num_spm_entry; i++) {
>> +		__raw_writel(dev->reg_seq_entry_shadow[i],
>> +			dev->reg_base_addr
>> +			+ dev->reg_offsets[MSM_SPM_REG_SAW2_SEQ_ENTRY]
>> +			+ 4 * i);
>> +	}
>> +	mb();
>> +}
>> +
>> +void dump_regs(struct msm_spm_driver_data *dev, int cpu)
>> +{
>> +	msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW2_SPM_STS);
>> +	mb();
>> +	pr_err("CPU%d: spm register MSM_SPM_REG_SAW2_SPM_STS: 0x%x\n", cpu,
>> +			dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_STS]);
>pr_info?
>> +	msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW2_SPM_CTL);
>> +	mb();
>> +	pr_err("CPU%d: spm register MSM_SPM_REG_SAW2_SPM_CTL: 0x%x\n", cpu,
>> +			dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL]);
>pr_info?
>> +}
>> +
>> +int msm_spm_drv_write_seq_data(struct msm_spm_driver_data *dev,
>> +		uint8_t *cmd, uint32_t *offset)
>
><snip>
>> +
>> +int msm_spm_drv_set_pmic_data(struct msm_spm_driver_data *dev,
>> +		enum msm_spm_pmic_port port, unsigned int data)msm_spm_drv_init
>> +{
>> +	unsigned int pmic_data = 0;
>> +	unsigned int timeout_us = 0;
>> +	int index = 0;
>Initialization not needed for ind.
>> +
>> +	if (!msm_spm_pmic_arb_present(dev))
>> +		return -ENOSYS;
>> +
>> +	index = msm_spm_drv_get_pmic_port(dev, port);
>> +	if (index < 0)
>
><snip>
>
>> +	if (!num_pmic_data)
>> +		num_pmic_data = msm_spm_drv_get_num_pmic_data(dev);
>> +
>> +	num_spm_entry = msm_spm_drv_get_num_spm_entry(dev);
>> +
>> +	dev->reg_seq_entry_shadow =
>> +		kzalloc(sizeof(*dev->reg_seq_entry_shadow) * num_spm_entry,
>> +				GFP_KERNEL);
>> +
>Please remove this extra new line here.
>> +	if (!dev->reg_seq_entry_shadow)
>> +		return -ENOMEM;
>> +
>> +	return 0;
>> +}
>> diff --git a/drivers/soc/qcom/spm_driver.h b/drivers/soc/qcom/spm_driver.h
>
><snip>
>
>>

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

* Re: [PATCH v2 03/10] qcom: spm: Add Subsystem Power Manager (SPM) driver for QCOM chipsets
  2014-08-14 15:16   ` Kumar Gala
@ 2014-08-14 15:27     ` Lina Iyer
  2014-08-14 15:33       ` Kumar Gala
  0 siblings, 1 reply; 46+ messages in thread
From: Lina Iyer @ 2014-08-14 15:27 UTC (permalink / raw)
  To: Kumar Gala
  Cc: daniel.lezcano, khilman, amit.kucheria, sboyd, davidb,
	linux-arm-msm, msivasub, Praveen Chidamabram, Murali Nalajala

On Thu, Aug 14, 2014 at 10:16:15AM -0500, Kumar Gala wrote:
>
>On Aug 12, 2014, at 2:43 PM, Lina Iyer <lina.iyer@linaro.org> wrote:
>
>> Qualcomm chipsets use an separate h/w block to control the logic around
>> the processor cores (cpu and L2). The SPM h/w block regulates power to
>> the cores and controls the power when the core enter low power modes.
>>
>> Each core has its own instance of SPM. The SPM has the following key
>> functions
>> 	- Configure the h/w dependencies when entering low power modes
>> 	- Wait for interrupt and wake up on interrupt
>> 	- Ensure the dependencies are ready before bringing the core out
>> 	  of sleep
>> 	- Regulating voltage to the core, interfacing with the PMIC.
>> 	- Optimize power based on runtime recommendations.
>>
>> The driver identifies and configures the SPMs, by reading the nodes and
>> the register values from the devicetree. The SPMs need to be configured
>> to allow the processor to be idled in a low power state.
>>
>> Signed-off-by: Praveen Chidamabram <pchidamb@codeaurora.org>
>> Signed-off-by: Murali Nalajala <mnalajal@codeaurora.org>
>> Signed-off-by: Lina Iyer <lina.iyer@linaro.org>
>> ---
>> .../devicetree/bindings/arm/msm/spm-v2.txt         |  62 ++
>> drivers/soc/qcom/Makefile                          |   2 +
>> drivers/soc/qcom/spm-devices.c                     | 703 +++++++++++++++++++++
>> drivers/soc/qcom/spm.c                             | 482 ++++++++++++++
>> drivers/soc/qcom/spm_driver.h                      | 116 ++++
>> include/soc/qcom/spm.h                             |  70 ++
>> 6 files changed, 1435 insertions(+)
>> create mode 100644 Documentation/devicetree/bindings/arm/msm/spm-v2.txt
>> create mode 100644 drivers/soc/qcom/spm-devices.c
>> create mode 100644 drivers/soc/qcom/spm.c
>> create mode 100644 drivers/soc/qcom/spm_driver.h
>> create mode 100644 include/soc/qcom/spm.h
>>
>> diff --git a/Documentation/devicetree/bindings/arm/msm/spm-v2.txt b/Documentation/devicetree/bindings/arm/msm/spm-v2.txt
>> new file mode 100644
>> index 0000000..3130f4b
>> --- /dev/null
>> +++ b/Documentation/devicetree/bindings/arm/msm/spm-v2.txt
>> @@ -0,0 +1,62 @@
>> +* MSM Subsystem Power Manager (spm-v2)
>> +
>> +S4 generation of MSMs have SPM hardware blocks to control the Application
>> +Processor Sub-System power. These SPM blocks run individual state machine
>> +to determine what the core (L2 or Krait/Scorpion) would do when the WFI
>> +instruction is executed by the core.
>> +
>> +The devicetree representation of the SPM block should be:
>> +
>> +Required properties
>> +
>> +- compatible: Could be one of -
>> +		"qcom,spm-v2.1"
>> +		"qcom,spm-v3.0"
>> +- reg: The physical address and the size of the SPM's memory mapped registers
>> +- qcom,cpu: phandle for the CPU that the SPM block is attached to. On targets
>> +	that dont support CPU phandles the driver would support qcom,core-id.
>> +	This field is required on only for SPMs that control the CPU.
>> +- qcom,saw2-cfg: SAW2 configuration register
>> +- qcom,saw2-spm-dly: Provides the values for the SPM delay command in the SPM
>> +	sequence
>> +- qcom,saw2-spm-ctl: The SPM control register
>> +- qcom,name: The name with which a SPM device is identified by the power
>> +	management code.
>
>Still not sure about this, wondering why we can’t use the node name.
At this point, without the SoC Idle frameworks code, we dont need this.
I will prune this off from the next version.
>
>> +
>> +Optional properties
>> +
>> +- qcom,saw2-pmic-data0..7: Specify the pmic data value and the associated FTS
>> +	(Fast Transient Switch) index to send the PMIC data to
>> +- qcom,vctl-port: The PVC (PMIC Virtual Channel) port used for changing
>> +	voltage
>> +- qcom,phase-port: The PVC port used for changing the number of phases
>> +- qcom,pfm-port: The PVC port used for enabling PWM/PFM modes
>> +- qcom,saw2-spm-cmd-wfi: The WFI command sequence
>> +- qcom,saw2-spm-cmd-ret: The Retention command sequence
>> +- qcom,saw2-spm-cmd-spc: The Standalone PC command sequence
>> +- qcom,saw2-spm-cmd-pc-no-rpm: The Power Collapse command sequence where APPS
>> +	proc won't inform the RPM.
>> +- qcom,saw2-spm-cmd-pc: The Power Collapse command sequence. This sequence may
>> +	turn off other SoC components.
>> +- qcom,saw2-spm-cmd-gdhs: GDHS (Globally Distributed Head Switch) command
>> +	sequence. This sequence will retain the memory but turn off the logic.
>> +- qcom,cpu-vctl-list: List of cpu node phandles, whose voltage the spm device
>> +	can control.
>> +- qcom,vctl-timeout-us: The timeout value in microseconds to wait for voltage to
>> +	change after sending the voltage command to the PMIC.
>> +-
>> +Example:
>> +	qcom,spm@f9089000 {
>> +		compatible = "qcom,spm-v2";
>> +		#address-cells = <1>;
>> +		#size-cells = <1>;
>> +		reg = <0xf9089000 0x1000>;
>> +		qcom,cpu = <&CPU0>;
>> +		qcom,saw2-cfg = <0x1>;
>> +		qcom,saw2-spm-dly= <0x20000400>;
>> +		qcom,saw2-spm-ctl = <0x1>;
>> +		qcom,saw2-spm-cmd-wfi = [03 0b 0f];
>> +		qcom,saw2-spm-cmd-spc = [00 20 50 80 60 70 10 92
>> +				a0 b0 03 68 70 3b 92 a0 b0
>> +				82 2b 50 10 30 02 22 30 0f];
>> +	};
>> diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile
>> index 70d52ed..d7ae93b 100644
>> --- a/drivers/soc/qcom/Makefile
>> +++ b/drivers/soc/qcom/Makefile
>> @@ -1,3 +1,5 @@
>> obj-$(CONFIG_QCOM_GSBI)	+=	qcom_gsbi.o
>> +obj-$(CONFIG_QCOM_PM) +=	spm-devices.o spm.o
>> +
>> CFLAGS_scm.o :=$(call as-instr,.arch_extension sec,-DREQUIRES_SEC=1)
>> obj-$(CONFIG_QCOM_SCM) += scm.o scm-boot.o
>> diff --git a/drivers/soc/qcom/spm-devices.c b/drivers/soc/qcom/spm-devices.c
>> new file mode 100644
>> index 0000000..567e9f9
>> --- /dev/null
>> +++ b/drivers/soc/qcom/spm-devices.c
>> @@ -0,0 +1,703 @@
>> +/* Copyright (c) 2011-2014, The Linux Foundation. All rights reserved.
>> + *
>> + * This program is free software; you can redistribute it and/or modify
>> + * it under the terms of the GNU General Public License version 2 and
>> + * only version 2 as published by the Free Software Foundation.
>> + *
>> + * This program is distributed in the hope that it will be useful,
>> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
>> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
>> + * GNU General Public License for more details.
>> + *
>> + */
>> +
>> +#include <linux/module.h>
>> +#include <linux/kernel.h>
>> +#include <linux/delay.h>
>> +#include <linux/init.h>
>> +#include <linux/io.h>
>> +#include <linux/slab.h>
>> +#include <linux/of.h>
>> +#include <linux/of_address.h>
>> +#include <linux/err.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/err.h>
>> +
>> +#include <soc/qcom/spm.h>
>> +
>> +#include "spm_driver.h"
>> +
>> +#define VDD_DEFAULT 0xDEADF00D
>> +
>> +struct msm_spm_power_modes {
>> +	uint32_t mode;
>> +	bool notify_rpm;
>> +	uint32_t start_addr;
>> +};
>> +
>> +struct msm_spm_device {
>> +	struct list_head list;
>> +	bool initialized;
>> +	const char *name;
>> +	struct msm_spm_driver_data reg_data;
>> +	struct msm_spm_power_modes *modes;
>> +	uint32_t num_modes;
>> +	uint32_t cpu_vdd;
>> +	struct cpumask mask;
>> +	void __iomem *q2s_reg;
>> +};
>> +
>> +struct msm_spm_vdd_info {
>> +	struct msm_spm_device *vctl_dev;
>> +	uint32_t vlevel;
>> +	int err;
>> +};
>> +
>> +static LIST_HEAD(spm_list);
>> +static DEFINE_PER_CPU_SHARED_ALIGNED(struct msm_spm_device, msm_cpu_spm_device);
>> +static DEFINE_PER_CPU(struct msm_spm_device *, cpu_vctl_device);
>> +
>> +static void msm_spm_smp_set_vdd(void *data)
>> +{
>> +	struct msm_spm_vdd_info *info = (struct msm_spm_vdd_info *)data;
>> +	struct msm_spm_device *dev = info->vctl_dev;
>> +
>> +	dev->cpu_vdd = info->vlevel;
>> +	info->err = msm_spm_drv_set_vdd(&dev->reg_data, info->vlevel);
>> +}
>> +
>> +/**
>> + * msm_spm_probe_done(): Verify and return the status of the cpu(s) and l2
>> + * probe.
>> + * Return: 0 if all spm devices have been probed, else return -EPROBE_DEFER.
>> + * if probe failed, then return the err number for that failure.
>> + */
>> +int msm_spm_probe_done(void)
>> +{
>> +	struct msm_spm_device *dev;
>> +	int cpu;
>> +	int ret = 0;
>> +
>> +	for_each_possible_cpu(cpu) {
>> +		dev = per_cpu(cpu_vctl_device, cpu);
>> +		if (!dev)
>> +			return -EPROBE_DEFER;
>> +
>> +		ret = IS_ERR(dev);
>> +		if (ret)
>> +			return ret;
>> +	}
>> +
>> +	return 0;
>> +}
>> +EXPORT_SYMBOL(msm_spm_probe_done);
>> +
>> +void msm_spm_dump_regs(unsigned int cpu)
>> +{
>> +	dump_regs(&per_cpu(msm_cpu_spm_device, cpu).reg_data, cpu);
>> +}
>
>Where is this used?
>
Debug code. Helpful in debugging crashes. Abort handlers usually call
them. Helps identify the state of the SPM and thereby the core, when it
crashes.

>> +
>> +/**
>> + * msm_spm_set_vdd(): Set core voltage
>> + * @cpu: core id
>> + * @vlevel: Encoded PMIC data.
>> + *
>> + * Return: 0 on success or -(ERRNO) on failure.
>> + */
>> +int msm_spm_set_vdd(unsigned int cpu, unsigned int vlevel)
>> +{
>> +	struct msm_spm_vdd_info info;
>> +	struct msm_spm_device *dev = per_cpu(cpu_vctl_device, cpu);
>> +	int ret;
>> +
>> +	if (!dev)
>> +		return -EPROBE_DEFER;
>> +
>> +	ret = IS_ERR(dev);
>> +	if (ret)
>> +		return ret;
>> +
>> +	info.vctl_dev = dev;
>> +	info.vlevel = vlevel;
>> +
>> +	ret = smp_call_function_any(&dev->mask, msm_spm_smp_set_vdd, &info,
>> +					true);
>> +	if (ret)
>> +		return ret;
>> +
>> +	return info.err;
>> +}
>> +EXPORT_SYMBOL(msm_spm_set_vdd);
>> +
>> +/**
>> + * msm_spm_get_vdd(): Get core voltage
>> + * @cpu: core id
>> + * @return: Returns encoded PMIC data.
>> + */
>> +unsigned int msm_spm_get_vdd(unsigned int cpu)
>> +{
>> +	int ret;
>> +	struct msm_spm_device *dev = per_cpu(cpu_vctl_device, cpu);
>> +
>> +	if (!dev)
>> +		return -EPROBE_DEFER;
>> +
>> +	ret = IS_ERR(dev);
>> +	if (ret)
>> +		return ret;
>> +
>> +	return dev->cpu_vdd;
>> +}
>> +EXPORT_SYMBOL(msm_spm_get_vdd);
>> +
>> +static void msm_spm_config_q2s(struct msm_spm_device *dev, unsigned int mode)
>> +{
>> +	uint32_t spm_legacy_mode = 0;
>> +	uint32_t qchannel_ignore = 0;
>> +	uint32_t val = 0;
>> +
>> +	if (!dev->q2s_reg)
>> +		return;
>> +
>> +	switch (mode) {
>> +	case MSM_SPM_MODE_DISABLED:
>> +	case MSM_SPM_MODE_CLOCK_GATING:
>> +		qchannel_ignore = 1;
>> +		spm_legacy_mode = 0;
>> +		break;
>> +	case MSM_SPM_MODE_RETENTION:
>> +		qchannel_ignore = 0;
>> +		spm_legacy_mode = 0;
>> +		break;
>> +	case MSM_SPM_MODE_GDHS:
>> +	case MSM_SPM_MODE_POWER_COLLAPSE:
>> +		qchannel_ignore = 0;
>> +		spm_legacy_mode = 1;
>> +		break;
>> +	default:
>> +		break;
>> +	}
>> +
>> +	val = spm_legacy_mode << 2 | qchannel_ignore << 1;
>> +	__raw_writel(val, dev->q2s_reg);
>> +	mb();
>> +}
>> +
>> +static int msm_spm_dev_set_low_power_mode(struct msm_spm_device *dev,
>> +		unsigned int mode, bool notify_rpm)
>> +{
>> +	uint32_t i;
>> +	uint32_t start_addr = 0;
>> +	int ret = -EINVAL;
>> +	bool pc_mode = false;
>> +
>> +	if (!dev->initialized)
>> +		return -ENXIO;
>> +
>> +	if ((mode == MSM_SPM_MODE_POWER_COLLAPSE)
>> +			|| (mode == MSM_SPM_MODE_GDHS))
>> +		pc_mode = true;
>> +
>> +	if (mode == MSM_SPM_MODE_DISABLED) {
>> +		ret = msm_spm_drv_set_spm_enable(&dev->reg_data, false);
>> +	} else if (!msm_spm_drv_set_spm_enable(&dev->reg_data, true)) {
>> +		for (i = 0; i < dev->num_modes; i++) {
>> +			if ((dev->modes[i].mode == mode) &&
>> +				(dev->modes[i].notify_rpm == notify_rpm)) {
>> +				start_addr = dev->modes[i].start_addr;
>> +				break;
>> +			}
>> +		}
>> +		ret = msm_spm_drv_set_low_power_mode(&dev->reg_data,
>> +					start_addr, pc_mode);
>> +	}
>> +
>> +	msm_spm_config_q2s(dev, mode);
>> +
>> +	return ret;
>> +}
>> +
>> +static int msm_spm_dev_init(struct msm_spm_device *dev,
>> +		struct msm_spm_platform_data *data)
>> +{
>> +	int i, ret = -ENOMEM;
>> +	uint32_t offset = 0;
>> +
>> +	dev->cpu_vdd = VDD_DEFAULT;
>> +	dev->num_modes = data->num_modes;
>> +	dev->modes = kmalloc(
>> +			sizeof(struct msm_spm_power_modes) * dev->num_modes,
>> +			GFP_KERNEL);
>> +
>> +	if (!dev->modes)
>> +		goto spm_failed_malloc;
>> +
>> +	dev->reg_data.major = data->major;
>> +	dev->reg_data.minor = data->minor;
>> +	ret = msm_spm_drv_init(&dev->reg_data, data);
>> +
>> +	if (ret)
>> +		goto spm_failed_init;
>> +
>> +	for (i = 0; i < dev->num_modes; i++) {
>> +
>> +		/* Default offset is 0 and gets updated as we write more
>> +		 * sequences into SPM
>> +		 */
>> +		dev->modes[i].start_addr = offset;
>> +		ret = msm_spm_drv_write_seq_data(&dev->reg_data,
>> +						data->modes[i].cmd, &offset);
>> +		if (ret < 0)
>> +			goto spm_failed_init;
>> +
>> +		dev->modes[i].mode = data->modes[i].mode;
>> +		dev->modes[i].notify_rpm = data->modes[i].notify_rpm;
>> +	}
>> +	msm_spm_drv_reinit(&dev->reg_data);
>> +	dev->initialized = true;
>> +	return 0;
>> +
>> +spm_failed_init:
>> +	kfree(dev->modes);
>> +spm_failed_malloc:
>> +	return ret;
>> +}
>> +
>> +/**
>> + * msm_spm_turn_on_cpu_rail(): Power on cpu rail before turning on core
>> + * @base: The SAW VCTL register which would set the voltage up.
>> + * @val: The value to be set on the rail
>> + * @cpu: The cpu for this with rail is being powered on
>> + */
>> +int msm_spm_turn_on_cpu_rail(void __iomem *base, unsigned int val, int cpu)
>> +{
>> +	uint32_t timeout = 2000; /* delay for voltage to settle on the core */
>> +	struct msm_spm_device *dev = per_cpu(cpu_vctl_device, cpu);
>> +
>> +	/*
>> +	 * If clock drivers have already set up the voltage,
>> +	 * do not overwrite that value.
>> +	 */
>> +	if (dev && (dev->cpu_vdd != VDD_DEFAULT))
>> +		return 0;
>> +
>> +	/* Set the CPU supply regulator voltage */
>> +	val = (val & 0xFF);
>> +	writel_relaxed(val, base);
>> +	mb();
>> +	udelay(timeout);
>> +
>> +	/* Enable the CPU supply regulator*/
>> +	val = 0x30080;
>> +	writel_relaxed(val, base);
>> +	mb();
>> +	udelay(timeout);
>> +
>> +	return 0;
>> +}
>> +EXPORT_SYMBOL(msm_spm_turn_on_cpu_rail);
>> +
>> +void msm_spm_reinit(void)
>> +{
>> +	unsigned int cpu;
>> +
>> +	for_each_possible_cpu(cpu)
>> +		msm_spm_drv_reinit(&per_cpu(msm_cpu_spm_device.reg_data, cpu));
>> +}
>> +EXPORT_SYMBOL(msm_spm_reinit);
>> +
>> +/*
>> + * msm_spm_is_mode_avail() - Specifies if a mode is available for the cpu
>> + * It should only be used to decide a mode before lpm driver is probed.
>> + * @mode: SPM LPM mode to be selected
>> + */
>> +bool msm_spm_is_mode_avail(unsigned int mode)
>> +{
>> +	struct msm_spm_device *dev = &__get_cpu_var(msm_cpu_spm_device);
>> +	int i;
>> +
>> +	for (i = 0; i < dev->num_modes; i++) {
>> +		if (dev->modes[i].mode == mode)
>> +			return true;
>> +	}
>> +
>> +	return false;
>> +}
>> +
>> +/**
>> + * msm_spm_set_low_power_mode() - Configure SPM start address for low power mode
>> + * @mode: SPM LPM mode to enter
>> + * @notify_rpm: Notify RPM in this mode
>> + */
>> +int msm_spm_set_low_power_mode(unsigned int mode, bool notify_rpm)
>> +{
>> +	struct msm_spm_device *dev = &__get_cpu_var(msm_cpu_spm_device);
>> +
>> +	return msm_spm_dev_set_low_power_mode(dev, mode, notify_rpm);
>> +}
>> +EXPORT_SYMBOL(msm_spm_set_low_power_mode);
>> +
>> +/**
>> + * msm_spm_init(): Board initalization function
>> + * @data: platform specific SPM register configuration data
>> + * @nr_devs: Number of SPM devices being initialized
>> + */
>> +int __init msm_spm_init(struct msm_spm_platform_data *data, int nr_devs)
>> +{
>> +	unsigned int cpu;
>> +	int ret = 0;
>> +
>> +	BUG_ON((nr_devs < num_possible_cpus()) || !data);
>> +
>> +	for_each_possible_cpu(cpu) {
>> +		struct msm_spm_device *dev = &per_cpu(msm_cpu_spm_device, cpu);
>> +
>> +		ret = msm_spm_dev_init(dev, &data[cpu]);
>> +		if (ret < 0) {
>> +			pr_warn("%s():failed CPU:%u ret:%d\n", __func__,
>> +					cpu, ret);
>> +			break;
>> +		}
>> +	}
>> +
>> +	return ret;
>> +}
>> +
>> +struct msm_spm_device *msm_spm_get_device_by_name(const char *name)
>> +{
>> +	struct list_head *list;
>> +
>> +	list_for_each(list, &spm_list) {
>> +		struct msm_spm_device *dev
>> +			= list_entry(list, typeof(*dev), list);
>> +		if (dev->name && !strcmp(dev->name, name))
>> +			return dev;
>> +	}
>> +	return ERR_PTR(-ENODEV);
>> +}
>> +
>> +int msm_spm_config_low_power_mode(struct msm_spm_device *dev,
>> +		unsigned int mode, bool notify_rpm)
>> +{
>> +	return msm_spm_dev_set_low_power_mode(dev, mode, notify_rpm);
>> +}
>> +#ifdef CONFIG_MSM_L2_SPM
>> +
>> +/**
>> + * msm_spm_apcs_set_phase(): Set number of SMPS phases.
>> + * @cpu: cpu which is requesting the change in number of phases.
>> + * @phase_cnt: Number of phases to be set active
>> + */
>> +int msm_spm_apcs_set_phase(int cpu, unsigned int phase_cnt)
>> +{
>> +	struct msm_spm_device *dev = per_cpu(cpu_vctl_device, cpu);
>> +
>> +	if (!dev)
>> +		return -ENXIO;
>> +
>> +	return msm_spm_drv_set_pmic_data(&dev->reg_data,
>> +			MSM_SPM_PMIC_PHASE_PORT, phase_cnt);
>> +}
>> +EXPORT_SYMBOL(msm_spm_apcs_set_phase);
>> +
>> +/** msm_spm_enable_fts_lpm() : Enable FTS to switch to low power
>> + *                             when the cores are in low power modes
>> + * @cpu: cpu that is entering low power mode.
>> + * @mode: The mode configuration for FTS
>> + */
>> +int msm_spm_enable_fts_lpm(int cpu, uint32_t mode)
>> +{
>> +	struct msm_spm_device *dev = per_cpu(cpu_vctl_device, cpu);
>> +
>> +	if (!dev)
>> +		return -ENXIO;
>> +
>> +	return msm_spm_drv_set_pmic_data(&dev->reg_data,
>> +			MSM_SPM_PMIC_PFM_PORT, mode);
>> +}
>> +EXPORT_SYMBOL(msm_spm_enable_fts_lpm);
>> +
>> +#endif
>> +
>> +static int get_cpu_id(struct device_node *node)
>> +{
>> +	struct device_node *cpu_node;
>> +	u32 cpu;
>> +	int ret = -EINVAL;
>> +	char *key = "qcom,cpu";
>> +
>> +	cpu_node = of_parse_phandle(node, key, 0);
>> +	if (cpu_node) {
>> +		for_each_possible_cpu(cpu) {
>> +			if (of_get_cpu_node(cpu, NULL) == cpu_node)
>> +				return cpu;
>> +		}
>> +	}
>> +	return ret;
>> +}
>> +
>> +static struct msm_spm_device *msm_spm_get_device(struct platform_device *pdev)
>> +{
>> +	struct msm_spm_device *dev = NULL;
>> +	const char *val = NULL;
>> +	char *key = "qcom,name";
>> +	int cpu = get_cpu_id(pdev->dev.of_node);
>> +
>> +	if ((cpu >= 0) && cpu < num_possible_cpus())
>> +		dev = &per_cpu(msm_cpu_spm_device, cpu);
>> +	else if ((cpu == 0xffff) || (cpu < 0))
>> +		dev = devm_kzalloc(&pdev->dev, sizeof(struct msm_spm_device),
>> +					GFP_KERNEL);
>> +
>> +	if (!dev)
>> +		return NULL;
>> +
>> +	if (of_property_read_string(pdev->dev.of_node, key, &val)) {
>> +		pr_err("%s(): Cannot find a required node key:%s\n",
>> +				__func__, key);
>> +		return NULL;
>> +	}
>> +	dev->name = val;
>> +	list_add(&dev->list, &spm_list);
>> +
>> +	return dev;
>> +}
>> +
>> +static void get_cpumask(struct device_node *node, struct cpumask *mask)
>> +{
>> +	unsigned long vctl_mask = 0;
>> +	unsigned c = 0;
>> +	int idx = 0;
>> +	struct device_node *cpu_node = NULL;
>> +	int ret = 0;
>> +	char *key = "qcom,cpu-vctl-list";
>> +	bool found = false;
>> +
>> +	cpu_node = of_parse_phandle(node, key, idx++);
>> +	while (cpu_node) {
>> +		found = true;
>> +		for_each_possible_cpu(c) {
>> +			if (of_get_cpu_node(c, NULL) == cpu_node)
>> +				cpumask_set_cpu(c, mask);
>> +		}
>> +		cpu_node = of_parse_phandle(node, key, idx++);
>> +	};
>> +
>> +	if (found)
>> +		return;
>> +
>> +	key = "qcom,cpu-vctl-mask";
>> +	ret = of_property_read_u32(node, key, (u32 *) &vctl_mask);
>> +	if (!ret) {
>> +		for_each_set_bit(c, &vctl_mask, num_possible_cpus()) {
>> +			cpumask_set_cpu(c, mask);
>> +		}
>> +	}
>
>kill this code if we really dropped the ‘qcom,cpu-vctl-mask’ DT support.
Done.
>
>> +}
>> +
>> +static int msm_spm_dev_probe(struct platform_device *pdev)
>> +{
>> +	int ret = 0;
>> +	int cpu = 0;
>> +	int i = 0;
>> +	struct device_node *node = pdev->dev.of_node;
>> +	struct msm_spm_platform_data spm_data;
>> +	char *key = NULL;
>> +	uint32_t val = 0;
>> +	struct msm_spm_seq_entry modes[MSM_SPM_MODE_NR];
>> +	int len = 0;
>> +	struct msm_spm_device *dev = NULL;
>> +	struct resource *res = NULL;
>> +	uint32_t mode_count = 0;
>> +
>> +	struct spm_of {
>> +		char *key;
>> +		uint32_t id;
>> +	};
>> +
>> +	struct spm_of spm_of_data[] = {
>> +		{"qcom,saw2-cfg", MSM_SPM_REG_SAW2_CFG},
>> +		{"qcom,saw2-spm-dly", MSM_SPM_REG_SAW2_SPM_DLY},
>> +		{"qcom,saw2-spm-ctl", MSM_SPM_REG_SAW2_SPM_CTL},
>> +		{"qcom,saw2-pmic-data0", MSM_SPM_REG_SAW2_PMIC_DATA_0},
>> +		{"qcom,saw2-pmic-data1", MSM_SPM_REG_SAW2_PMIC_DATA_1},
>> +		{"qcom,saw2-pmic-data2", MSM_SPM_REG_SAW2_PMIC_DATA_2},
>> +		{"qcom,saw2-pmic-data3", MSM_SPM_REG_SAW2_PMIC_DATA_3},
>> +		{"qcom,saw2-pmic-data4", MSM_SPM_REG_SAW2_PMIC_DATA_4},
>> +		{"qcom,saw2-pmic-data5", MSM_SPM_REG_SAW2_PMIC_DATA_5},
>> +		{"qcom,saw2-pmic-data6", MSM_SPM_REG_SAW2_PMIC_DATA_6},
>> +		{"qcom,saw2-pmic-data7", MSM_SPM_REG_SAW2_PMIC_DATA_7},
>> +	};
>> +
>> +	struct mode_of {
>> +		char *key;
>> +		uint32_t id;
>> +		uint32_t notify_rpm;
>> +	};
>> +
>> +	struct mode_of mode_of_data[] = {
>> +		{"qcom,saw2-spm-cmd-wfi", MSM_SPM_MODE_CLOCK_GATING, 0},
>> +		{"qcom,saw2-spm-cmd-ret", MSM_SPM_MODE_RETENTION, 0},
>> +		{"qcom,saw2-spm-cmd-gdhs", MSM_SPM_MODE_GDHS, 1},
>> +		{"qcom,saw2-spm-cmd-spc", MSM_SPM_MODE_POWER_COLLAPSE, 0},
>> +		{"qcom,saw2-spm-cmd-pc", MSM_SPM_MODE_POWER_COLLAPSE, 1},
>> +	};
>> +
>> +	dev = msm_spm_get_device(pdev);
>> +	if (!dev) {
>> +		ret = -ENOMEM;
>> +		goto fail;
>> +	}
>> +	get_cpumask(node, &dev->mask);
>> +
>> +	memset(&spm_data, 0, sizeof(struct msm_spm_platform_data));
>> +	memset(&modes, 0,
>> +		(MSM_SPM_MODE_NR - 2) * sizeof(struct msm_spm_seq_entry));
>> +
>> +	if (of_device_is_compatible(node, "qcom,spm-v2.1")) {
>> +		spm_data.major = 2;
>> +		spm_data.minor = 1;
>> +	} else if (of_device_is_compatible(node, "qcom,spm-v3.0")) {
>> +		spm_data.major = 3;
>> +		spm_data.minor = 0;
>> +	}
>> +
>> +	key = "qcom,vctl-timeout-us";
>> +	ret = of_property_read_u32(node, key, &val);
>> +	if (!ret)
>> +		spm_data.vctl_timeout_us = val;
>> +
>> +	/* SAW start address */
>> +	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
>> +	if (!res) {
>> +		ret = -EFAULT;
>> +		goto fail;
>> +	}
>> +
>> +	spm_data.reg_base_addr = devm_ioremap(&pdev->dev, res->start,
>> +					resource_size(res));
>> +	if (!spm_data.reg_base_addr) {
>> +		ret = -ENOMEM;
>> +		goto fail;
>> +	}
>> +
>> +	spm_data.vctl_port = -1;
>> +	spm_data.phase_port = -1;
>> +	spm_data.pfm_port = -1;
>> +
>> +	key = "qcom,vctl-port";
>> +	of_property_read_u32(node, key, &spm_data.vctl_port);
>> +
>> +	key = "qcom,phase-port";
>> +	of_property_read_u32(node, key, &spm_data.phase_port);
>> +
>> +	key = "qcom,pfm-port";
>> +	of_property_read_u32(node, key, &spm_data.pfm_port);
>> +
>> +	/* Q2S (QChannel-2-SPM) register */
>> +	res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
>> +	if (res) {
>> +		dev->q2s_reg = devm_ioremap(&pdev->dev, res->start,
>> +						resource_size(res));
>> +		if (!dev->q2s_reg) {
>> +			pr_err("%s(): Unable to iomap Q2S register\n",
>> +					__func__);
>> +			ret = -EADDRNOTAVAIL;
>> +			goto fail;
>> +		}
>> +	}
>> +	/*
>> +	 * At system boot, cpus and or clusters can remain in reset. CCI SPM
>> +	 * will not be triggered unless SPM_LEGACY_MODE bit is set for the
>> +	 * cluster in reset. Initialize q2s registers and set the
>> +	 * SPM_LEGACY_MODE bit.
>> +	 */
>> +	msm_spm_config_q2s(dev, MSM_SPM_MODE_POWER_COLLAPSE);
>> +
>> +	for (i = 0; i < ARRAY_SIZE(spm_of_data); i++) {
>> +		ret = of_property_read_u32(node, spm_of_data[i].key, &val);
>> +		if (ret)
>> +			continue;
>> +		spm_data.reg_init_values[spm_of_data[i].id] = val;
>> +	}
>> +
>> +	for (i = 0; i < ARRAY_SIZE(mode_of_data); i++) {
>> +		key = mode_of_data[i].key;
>> +		modes[mode_count].cmd =
>> +			(uint8_t *)of_get_property(node, key, &len);
>> +		if (!modes[mode_count].cmd)
>> +			continue;
>> +		modes[mode_count].mode = mode_of_data[i].id;
>> +		modes[mode_count].notify_rpm = mode_of_data[i].notify_rpm;
>> +		pr_debug("%s(): dev: %s cmd:%s, mode:%d rpm:%d\n", __func__,
>> +				dev->name, key, modes[mode_count].mode,
>> +				modes[mode_count].notify_rpm);
>> +		mode_count++;
>> +	}
>> +
>> +	spm_data.modes = modes;
>> +	spm_data.num_modes = mode_count;
>> +
>> +	ret = msm_spm_dev_init(dev, &spm_data);
>> +	if (ret)
>> +		goto fail;
>> +
>> +	platform_set_drvdata(pdev, dev);
>> +
>> +	for_each_cpu(cpu, &dev->mask)
>> +		per_cpu(cpu_vctl_device, cpu) = dev;
>> +
>> +	return ret;
>> +
>> +fail:
>> +	cpu = get_cpu_id(pdev->dev.of_node);
>> +	if (dev && (cpu >= num_possible_cpus() || (cpu < 0))) {
>> +		for_each_cpu(cpu, &dev->mask)
>> +			per_cpu(cpu_vctl_device, cpu) = ERR_PTR(ret);
>> +	}
>> +
>> +	pr_err("%s: CPU%d SPM device probe failed: %d\n", __func__, cpu, ret);
>> +
>> +	return ret;
>> +}
>> +
>> +static int msm_spm_dev_remove(struct platform_device *pdev)
>> +{
>> +	struct msm_spm_device *dev = platform_get_drvdata(pdev);
>> +
>> +	list_del(&dev->list);
>> +
>> +	return 0;
>> +}
>> +
>> +static struct of_device_id msm_spm_match_table[] = {
>> +	{.compatible = "qcom,spm-v2.1"},
>> +	{.compatible = "qcom,spm-v3.0"},
>> +	{},
>> +};
>> +
>> +static struct platform_driver msm_spm_device_driver = {
>> +	.probe = msm_spm_dev_probe,
>> +	.remove = msm_spm_dev_remove,
>> +	.driver = {
>> +		.name = "spm-v2",
>> +		.owner = THIS_MODULE,
>> +		.of_match_table = msm_spm_match_table,
>> +	},
>> +};
>> +
>> +/**
>> + * msm_spm_device_init(): Device tree initialization function
>> + */
>> +int __init msm_spm_device_init(void)
>> +{
>> +	static bool registered;
>> +
>> +	if (registered)
>> +		return 0;
>> +
>> +	registered = true;
>> +
>> +	return platform_driver_register(&msm_spm_device_driver);
>> +}
>> +device_initcall(msm_spm_device_init);
>> diff --git a/drivers/soc/qcom/spm.c b/drivers/soc/qcom/spm.c
>> new file mode 100644
>> index 0000000..7dbdb64
>> --- /dev/null
>> +++ b/drivers/soc/qcom/spm.c
>> @@ -0,0 +1,482 @@
>> +/* Copyright (c) 2011-2014, The Linux Foundation. All rights reserved.
>> + *
>> + * This program is free software; you can redistribute it and/or modify
>> + * it under the terms of the GNU General Public License version 2 and
>> + * only version 2 as published by the Free Software Foundation.
>> + *
>> + * This program is distributed in the hope that it will be useful,
>> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
>> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
>> + * GNU General Public License for more details.
>> + *
>> + */
>> +
>> +#include <linux/module.h>
>> +#include <linux/kernel.h>
>> +#include <linux/delay.h>
>> +#include <linux/init.h>
>> +#include <linux/io.h>
>> +#include <linux/slab.h>
>> +
>> +#include "spm_driver.h"
>> +
>> +#define MSM_SPM_PMIC_STATE_IDLE  0
>> +
>> +enum {
>> +	MSM_SPM_DEBUG_SHADOW = 1U << 0,
>> +	MSM_SPM_DEBUG_VCTL = 1U << 1,
>> +};
>> +
>> +static int msm_spm_debug_mask;
>> +module_param_named(
>> +	debug_mask, msm_spm_debug_mask, int, S_IRUGO | S_IWUSR | S_IWGRP
>> +);
>> +
>> +struct saw2_data {
>> +	const char *ver_name;
>> +	uint32_t major;
>> +	uint32_t minor;
>> +	uint32_t *spm_reg_offset_ptr;
>> +};
>> +
>> +static uint32_t msm_spm_reg_offsets_saw2_v2_1[MSM_SPM_REG_NR] = {
>> +	[MSM_SPM_REG_SAW2_SECURE]		= 0x00,
>> +	[MSM_SPM_REG_SAW2_ID]			= 0x04,
>> +	[MSM_SPM_REG_SAW2_CFG]			= 0x08,
>> +	[MSM_SPM_REG_SAW2_SPM_STS]		= 0x0C,
>> +	[MSM_SPM_REG_SAW2_AVS_STS]		= 0x10,
>> +	[MSM_SPM_REG_SAW2_PMIC_STS]		= 0x14,
>> +	[MSM_SPM_REG_SAW2_RST]			= 0x18,
>> +	[MSM_SPM_REG_SAW2_VCTL]			= 0x1C,
>> +	[MSM_SPM_REG_SAW2_AVS_CTL]		= 0x20,
>> +	[MSM_SPM_REG_SAW2_AVS_LIMIT]		= 0x24,
>> +	[MSM_SPM_REG_SAW2_AVS_DLY]		= 0x28,
>> +	[MSM_SPM_REG_SAW2_AVS_HYSTERESIS]	= 0x2C,
>> +	[MSM_SPM_REG_SAW2_SPM_CTL]		= 0x30,
>> +	[MSM_SPM_REG_SAW2_SPM_DLY]		= 0x34,
>> +	[MSM_SPM_REG_SAW2_PMIC_DATA_0]		= 0x40,
>> +	[MSM_SPM_REG_SAW2_PMIC_DATA_1]		= 0x44,
>> +	[MSM_SPM_REG_SAW2_PMIC_DATA_2]		= 0x48,
>> +	[MSM_SPM_REG_SAW2_PMIC_DATA_3]		= 0x4C,
>> +	[MSM_SPM_REG_SAW2_PMIC_DATA_4]		= 0x50,
>> +	[MSM_SPM_REG_SAW2_PMIC_DATA_5]		= 0x54,
>> +	[MSM_SPM_REG_SAW2_PMIC_DATA_6]		= 0x58,
>> +	[MSM_SPM_REG_SAW2_PMIC_DATA_7]		= 0x5C,
>> +	[MSM_SPM_REG_SAW2_SEQ_ENTRY]		= 0x80,
>> +	[MSM_SPM_REG_SAW2_VERSION]		= 0xFD0,
>> +};
>> +
>> +static uint32_t msm_spm_reg_offsets_saw2_v3_0[MSM_SPM_REG_NR] = {
>> +	[MSM_SPM_REG_SAW2_SECURE]		= 0x00,
>> +	[MSM_SPM_REG_SAW2_ID]			= 0x04,
>> +	[MSM_SPM_REG_SAW2_CFG]			= 0x08,
>> +	[MSM_SPM_REG_SAW2_SPM_STS]		= 0x0C,
>> +	[MSM_SPM_REG_SAW2_AVS_STS]		= 0x10,
>> +	[MSM_SPM_REG_SAW2_PMIC_STS]		= 0x14,
>> +	[MSM_SPM_REG_SAW2_RST]			= 0x18,
>> +	[MSM_SPM_REG_SAW2_VCTL]			= 0x1C,
>> +	[MSM_SPM_REG_SAW2_AVS_CTL]		= 0x20,
>> +	[MSM_SPM_REG_SAW2_AVS_LIMIT]		= 0x24,
>> +	[MSM_SPM_REG_SAW2_AVS_DLY]		= 0x28,
>> +	[MSM_SPM_REG_SAW2_AVS_HYSTERESIS]	= 0x2C,
>> +	[MSM_SPM_REG_SAW2_SPM_CTL]		= 0x30,
>> +	[MSM_SPM_REG_SAW2_SPM_DLY]		= 0x34,
>> +	[MSM_SPM_REG_SAW2_STS2]			= 0x38,
>> +	[MSM_SPM_REG_SAW2_PMIC_DATA_0]		= 0x40,
>> +	[MSM_SPM_REG_SAW2_PMIC_DATA_1]		= 0x44,
>> +	[MSM_SPM_REG_SAW2_PMIC_DATA_2]		= 0x48,
>> +	[MSM_SPM_REG_SAW2_PMIC_DATA_3]		= 0x4C,
>> +	[MSM_SPM_REG_SAW2_PMIC_DATA_4]		= 0x50,
>> +	[MSM_SPM_REG_SAW2_PMIC_DATA_5]		= 0x54,
>> +	[MSM_SPM_REG_SAW2_PMIC_DATA_6]		= 0x58,
>> +	[MSM_SPM_REG_SAW2_PMIC_DATA_7]		= 0x5C,
>> +	[MSM_SPM_REG_SAW2_SEQ_ENTRY]		= 0x400,
>> +	[MSM_SPM_REG_SAW2_VERSION]		= 0xFD0,
>> +};
>
>I don’t see what having these arrays provides as the only differences are that v3.0 has MSM_SPM_REG_SAW2_STS2 and the offset of MSM_SPM_REG_SAW2_SEQ_ENTRY.  If so we can remove all this extra code and just add a simple check in msm_spm_drv_flush_seq_entry that looks at the compatible and picks the proper offset when updating MSM_SPM_REG_SAW2_SEQ_ENTRY.
>
Isnt that an hack ?
>> +
>> +static struct saw2_data saw2_info[] = {
>> +	[0] = {
>> +		"SAW2_v2.1",
>> +		2,
>> +		1,
>> +		msm_spm_reg_offsets_saw2_v2_1,
>> +	},
>> +	[1] = {
>> +		"SAW2_v3.0",
>> +		3,
>> +		0,
>> +		msm_spm_reg_offsets_saw2_v3_0,
>> +	},
>> +};
>> +
>> +static uint32_t num_pmic_data;
>> +
>> +static inline uint32_t msm_spm_drv_get_num_spm_entry(
>> +		struct msm_spm_driver_data *dev)
>> +{
>> +	return 32;
>> +}
>> +
>> +static void msm_spm_drv_flush_shadow(struct msm_spm_driver_data *dev,
>> +		unsigned int reg_index)
>> +{
>> +	__raw_writel(dev->reg_shadow[reg_index],
>> +		dev->reg_base_addr + dev->reg_offsets[reg_index]);
>> +}
>
>have you looked at regmap and if that can accomplish the same goal as what this shadow stuff is doing?
>
No. Will look into it.
>> +
>> +static void msm_spm_drv_load_shadow(struct msm_spm_driver_data *dev,
>> +		unsigned int reg_index)
>> +{
>> +	dev->reg_shadow[reg_index] =
>> +		__raw_readl(dev->reg_base_addr +
>> +				dev->reg_offsets[reg_index]);
>> +}
>> +
>> +static inline void msm_spm_drv_set_start_addr(
>> +		struct msm_spm_driver_data *dev, uint32_t addr, bool pc_mode)
>> +{
>> +	addr &= 0x7F;
>> +	addr <<= 4;
>> +	dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] &= 0xFFFFF80F;
>> +	dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] |= addr;
>> +
>> +	if (dev->major != 0x3)
>> +		return;
>> +
>> +	dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] &= 0xFFFEFFFF;
>> +	if (pc_mode)
>> +		dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] |= 0x00010000;
>> +}
>> +
>> +static inline bool msm_spm_pmic_arb_present(struct msm_spm_driver_data *dev)
>> +{
>> +	msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW2_ID);
>> +	return (dev->reg_shadow[MSM_SPM_REG_SAW2_ID] >> 2) & 0x1;
>> +}
>> +
>> +static inline void msm_spm_drv_set_vctl2(struct msm_spm_driver_data *dev,
>> +		uint32_t vlevel)
>> +{
>> +	unsigned int pmic_data = 0;
>> +
>> +	/**
>> +	 * VCTL_PORT has to be 0, for PMIC_STS register to be updated.
>> +	 * Ensure that vctl_port is always set to 0.
>> +	 */
>> +	WARN_ON(dev->vctl_port);
>> +
>> +	pmic_data |= vlevel;
>> +	pmic_data |= (dev->vctl_port & 0x7) << 16;
>> +
>> +	dev->reg_shadow[MSM_SPM_REG_SAW2_VCTL] &= ~0x700FF;
>> +	dev->reg_shadow[MSM_SPM_REG_SAW2_VCTL] |= pmic_data;
>> +
>> +	dev->reg_shadow[MSM_SPM_REG_SAW2_PMIC_DATA_3] &= ~0x700FF;
>> +	dev->reg_shadow[MSM_SPM_REG_SAW2_PMIC_DATA_3] |= pmic_data;
>> +
>> +	msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW2_VCTL);
>> +	msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW2_PMIC_DATA_3);
>> +}
>> +
>> +static inline uint32_t msm_spm_drv_get_num_pmic_data(
>> +		struct msm_spm_driver_data *dev)
>> +{
>> +	msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW2_ID);
>> +	mb();
>> +	return (dev->reg_shadow[MSM_SPM_REG_SAW2_ID] >> 4) & 0x7;
>> +}
>> +
>> +static inline uint32_t msm_spm_drv_get_sts_pmic_state(
>> +		struct msm_spm_driver_data *dev)
>> +{
>> +	msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW2_PMIC_STS);
>> +	return (dev->reg_shadow[MSM_SPM_REG_SAW2_PMIC_STS] >> 16) &
>> +				0x03;
>> +}
>> +
>> +uint32_t msm_spm_drv_get_sts_curr_pmic_data(
>> +		struct msm_spm_driver_data *dev)
>> +{
>> +	msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW2_PMIC_STS);
>> +	return dev->reg_shadow[MSM_SPM_REG_SAW2_PMIC_STS] & 0xFF;
>> +}
>> +
>> +inline int msm_spm_drv_set_spm_enable(
>> +		struct msm_spm_driver_data *dev, bool enable)
>> +{
>> +	uint32_t value = enable ? 0x01 : 0x00;
>> +
>> +	if (!dev)
>> +		return -EINVAL;
>> +
>> +	if ((dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] & 0x01) ^ value) {
>> +
>> +		dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] &= ~0x1;
>> +		dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] |= value;
>> +
>> +		msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW2_SPM_CTL);
>> +		wmb();
>> +	}
>> +	return 0;
>> +}
>> +void msm_spm_drv_flush_seq_entry(struct msm_spm_driver_data *dev)
>> +{
>> +	int i;
>> +	int num_spm_entry = msm_spm_drv_get_num_spm_entry(dev);
>> +
>> +	if (!dev) {
>> +		__WARN();
>> +		return;
>> +	}
>> +
>> +	for (i = 0; i < num_spm_entry; i++) {
>> +		__raw_writel(dev->reg_seq_entry_shadow[i],
>> +			dev->reg_base_addr
>> +			+ dev->reg_offsets[MSM_SPM_REG_SAW2_SEQ_ENTRY]
>> +			+ 4 * i);
>> +	}
>> +	mb();
>> +}
>> +
>> +void dump_regs(struct msm_spm_driver_data *dev, int cpu)
>
>This should probably be something like __msm_spm_dump_regs()
Ok.
>
>> +{
>> +	msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW2_SPM_STS);
>> +	mb();
>> +	pr_err("CPU%d: spm register MSM_SPM_REG_SAW2_SPM_STS: 0x%x\n", cpu,
>> +			dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_STS]);
>> +	msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW2_SPM_CTL);
>> +	mb();
>> +	pr_err("CPU%d: spm register MSM_SPM_REG_SAW2_SPM_CTL: 0x%x\n", cpu,
>> +			dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL]);
>> +}
>> +
>> +int msm_spm_drv_write_seq_data(struct msm_spm_driver_data *dev,
>> +		uint8_t *cmd, uint32_t *offset)
>> +{
>> +	uint32_t cmd_w;
>> +	uint32_t offset_w = *offset / 4;
>> +	uint8_t last_cmd;
>> +
>> +	if (!cmd)
>> +		return -EINVAL;
>> +
>> +	while (1) {
>> +		int i;
>> +
>> +		cmd_w = 0;
>> +		last_cmd = 0;
>> +		cmd_w = dev->reg_seq_entry_shadow[offset_w];
>> +
>> +		for (i = (*offset % 4); i < 4; i++) {
>> +			last_cmd = *(cmd++);
>> +			cmd_w |=  last_cmd << (i * 8);
>> +			(*offset)++;
>> +			if (last_cmd == 0x0f)
>> +				break;
>> +		}
>> +
>> +		dev->reg_seq_entry_shadow[offset_w++] = cmd_w;
>> +		if (last_cmd == 0x0f)
>> +			break;
>> +	}
>> +
>> +	return 0;
>> +}
>> +
>> +int msm_spm_drv_set_low_power_mode(struct msm_spm_driver_data *dev,
>> +		uint32_t addr, bool pc_mode)
>> +{
>> +
>> +	if (!dev)
>> +		return -EINVAL;
>> +
>> +	msm_spm_drv_set_start_addr(dev, addr, pc_mode);
>> +
>> +	msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW2_SPM_CTL);
>> +	wmb();
>> +
>> +	if (msm_spm_debug_mask & MSM_SPM_DEBUG_SHADOW) {
>> +		int i;
>> +
>> +		for (i = 0; i < MSM_SPM_REG_NR; i++)
>> +			pr_info("%s: reg %02x = 0x%08x\n", __func__,
>> +				dev->reg_offsets[i], dev->reg_shadow[i]);
>> +	}
>> +	msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW2_SPM_STS);
>> +
>> +	return 0;
>> +}
>> +
>> +int msm_spm_drv_set_vdd(struct msm_spm_driver_data *dev, unsigned int vlevel)
>> +{
>> +	uint32_t timeout_us, new_level;
>> +
>> +	if (!dev)
>> +		return -EINVAL;
>> +
>> +	if (!msm_spm_pmic_arb_present(dev))
>> +		return -ENOSYS;
>> +
>> +	if (msm_spm_debug_mask & MSM_SPM_DEBUG_VCTL)
>> +		pr_info("%s: requesting vlevel %#x\n", __func__, vlevel);
>> +
>> +	/* Kick the state machine back to idle */
>> +	dev->reg_shadow[MSM_SPM_REG_SAW2_RST] = 1;
>> +	msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW2_RST);
>> +
>> +	msm_spm_drv_set_vctl2(dev, vlevel);
>> +
>> +	timeout_us = dev->vctl_timeout_us;
>> +	/* Confirm the voltage we set was what hardware sent */
>> +	do {
>> +		new_level = msm_spm_drv_get_sts_curr_pmic_data(dev);
>> +		if (new_level == vlevel)
>> +			break;
>> +		udelay(1);
>> +	} while (--timeout_us);
>> +	if (!timeout_us) {
>> +		pr_info("Wrong level %#x\n", new_level);
>> +		goto set_vdd_bail;
>> +	}
>> +
>> +	if (msm_spm_debug_mask & MSM_SPM_DEBUG_VCTL)
>> +		pr_info("%s: done, remaining timeout %u us\n",
>> +			__func__, timeout_us);
>> +
>> +	return 0;
>> +
>> +set_vdd_bail:
>> +	pr_err("%s: failed %#x, remaining timeout %uus, vlevel %#x\n",
>> +		__func__, vlevel, timeout_us, new_level);
>> +	return -EIO;
>> +}
>> +
>> +static int msm_spm_drv_get_pmic_port(struct msm_spm_driver_data *dev,
>> +		enum msm_spm_pmic_port port)
>> +{
>> +	int index = -1;
>> +
>> +	switch (port) {
>> +	case MSM_SPM_PMIC_VCTL_PORT:
>> +		index = dev->vctl_port;
>> +		break;
>> +	case MSM_SPM_PMIC_PHASE_PORT:
>> +		index = dev->phase_port;
>> +		break;
>> +	case MSM_SPM_PMIC_PFM_PORT:
>> +		index = dev->pfm_port;
>> +		break;
>> +	default:
>> +		break;
>> +	}
>> +
>> +	return index;
>> +}
>> +
>> +int msm_spm_drv_set_pmic_data(struct msm_spm_driver_data *dev,
>> +		enum msm_spm_pmic_port port, unsigned int data)
>> +{
>> +	unsigned int pmic_data = 0;
>> +	unsigned int timeout_us = 0;
>> +	int index = 0;
>> +
>> +	if (!msm_spm_pmic_arb_present(dev))
>> +		return -ENOSYS;
>> +
>> +	index = msm_spm_drv_get_pmic_port(dev, port);
>> +	if (index < 0)
>> +		return -ENODEV;
>> +
>> +	pmic_data |= data & 0xFF;
>> +	pmic_data |= (index & 0x7) << 16;
>> +
>> +	dev->reg_shadow[MSM_SPM_REG_SAW2_VCTL] &= ~0x700FF;
>> +	dev->reg_shadow[MSM_SPM_REG_SAW2_VCTL] |= pmic_data;
>> +	msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW2_VCTL);
>> +	mb();
>> +
>> +	timeout_us = dev->vctl_timeout_us;
>> +	/**
>> +	 * Confirm the pmic data set was what hardware sent by
>> +	 * checking the PMIC FSM state.
>> +	 * We cannot use the sts_pmic_data and check it against
>> +	 * the value like we do fot set_vdd, since the PMIC_STS
>> +	 * is only updated for SAW_VCTL sent with port index 0.
>> +	 */
>> +	do {
>> +		if (msm_spm_drv_get_sts_pmic_state(dev) ==
>> +				MSM_SPM_PMIC_STATE_IDLE)
>> +			break;
>> +		udelay(1);
>> +	} while (--timeout_us);
>> +
>> +	if (!timeout_us) {
>> +		pr_err("%s: failed, remaining timeout %u us, data %d\n",
>> +				__func__, timeout_us, data);
>> +		return -EIO;
>> +	}
>> +
>> +	return 0;
>> +}
>> +
>> +void msm_spm_drv_reinit(struct msm_spm_driver_data *dev)
>> +{
>> +	int i;
>> +
>> +	msm_spm_drv_flush_seq_entry(dev);
>> +	for (i = 0; i < MSM_SPM_REG_SAW2_PMIC_DATA_0 + num_pmic_data; i++)
>> +		msm_spm_drv_flush_shadow(dev, i);
>> +
>> +	mb();
>> +
>> +	for (i = MSM_SPM_REG_NR_INITIALIZE + 1; i < MSM_SPM_REG_NR; i++)
>> +		msm_spm_drv_load_shadow(dev, i);
>> +}
>> +
>> +int msm_spm_drv_init(struct msm_spm_driver_data *dev,
>> +		struct msm_spm_platform_data *data)
>> +{
>> +	int i;
>> +	int num_spm_entry;
>> +	bool found = false;
>> +
>> +	BUG_ON(!dev || !data);
>> +
>> +	dev->vctl_port = data->vctl_port;
>> +	dev->phase_port = data->phase_port;
>> +	dev->pfm_port = data->pfm_port;
>> +	dev->reg_base_addr = data->reg_base_addr;
>> +	memcpy(dev->reg_shadow, data->reg_init_values,
>> +			sizeof(data->reg_init_values));
>> +
>> +	dev->vctl_timeout_us = data->vctl_timeout_us;
>> +
>> +	for (i = 0; i < ARRAY_SIZE(saw2_info); i++)
>> +		if (dev->major == saw2_info[i].major &&
>> +			dev->minor == saw2_info[i].minor) {
>> +			pr_debug("%s: Version found\n",
>> +					saw2_info[i].ver_name);
>> +			dev->reg_offsets = saw2_info[i].spm_reg_offset_ptr;
>> +			found = true;
>> +			break;
>> +		}
>> +
>> +	if (!found) {
>> +		pr_err("%s: No SAW2 version found\n", __func__);
>> +		BUG_ON(!found);
>> +	}
>> +
>> +	if (!num_pmic_data)
>> +		num_pmic_data = msm_spm_drv_get_num_pmic_data(dev);
>> +
>> +	num_spm_entry = msm_spm_drv_get_num_spm_entry(dev);
>> +
>> +	dev->reg_seq_entry_shadow =
>> +		kzalloc(sizeof(*dev->reg_seq_entry_shadow) * num_spm_entry,
>> +				GFP_KERNEL);
>> +
>> +	if (!dev->reg_seq_entry_shadow)
>> +		return -ENOMEM;
>> +
>> +	return 0;
>> +}
>> diff --git a/drivers/soc/qcom/spm_driver.h b/drivers/soc/qcom/spm_driver.h
>> new file mode 100644
>> index 0000000..b306520
>> --- /dev/null
>> +++ b/drivers/soc/qcom/spm_driver.h
>> @@ -0,0 +1,116 @@
>> +/* Copyright (c) 2011-2014, The Linux Foundation. All rights reserved.
>> + *
>> + * This program is free software; you can redistribute it and/or modify
>> + * it under the terms of the GNU General Public License version 2 and
>> + * only version 2 as published by the Free Software Foundation.
>> + *
>> + * This program is distributed in the hope that it will be useful,
>> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
>> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
>> + * GNU General Public License for more details.
>> + */
>> +#ifndef __QCOM_SPM_DRIVER_H
>> +#define __QCOM_SPM_DRIVER_H
>> +
>> +#include <soc/qcom/spm.h>
>> +
>> +enum {
>> +	MSM_SPM_REG_SAW2_CFG,
>> +	MSM_SPM_REG_SAW2_AVS_CTL,
>> +	MSM_SPM_REG_SAW2_AVS_HYSTERESIS,
>> +	MSM_SPM_REG_SAW2_SPM_CTL,
>> +	MSM_SPM_REG_SAW2_PMIC_DLY,
>> +	MSM_SPM_REG_SAW2_AVS_LIMIT,
>> +	MSM_SPM_REG_SAW2_AVS_DLY,
>> +	MSM_SPM_REG_SAW2_SPM_DLY,
>> +	MSM_SPM_REG_SAW2_PMIC_DATA_0,
>> +	MSM_SPM_REG_SAW2_PMIC_DATA_1,
>> +	MSM_SPM_REG_SAW2_PMIC_DATA_2,
>> +	MSM_SPM_REG_SAW2_PMIC_DATA_3,
>> +	MSM_SPM_REG_SAW2_PMIC_DATA_4,
>> +	MSM_SPM_REG_SAW2_PMIC_DATA_5,
>> +	MSM_SPM_REG_SAW2_PMIC_DATA_6,
>> +	MSM_SPM_REG_SAW2_PMIC_DATA_7,
>> +	MSM_SPM_REG_SAW2_RST,
>> +
>> +	MSM_SPM_REG_NR_INITIALIZE = MSM_SPM_REG_SAW2_RST,
>> +
>> +	MSM_SPM_REG_SAW2_ID,
>> +	MSM_SPM_REG_SAW2_SECURE,
>> +	MSM_SPM_REG_SAW2_STS0,
>> +	MSM_SPM_REG_SAW2_STS1,
>> +	MSM_SPM_REG_SAW2_STS2,
>> +	MSM_SPM_REG_SAW2_VCTL,
>> +	MSM_SPM_REG_SAW2_SEQ_ENTRY,
>> +	MSM_SPM_REG_SAW2_SPM_STS,
>> +	MSM_SPM_REG_SAW2_AVS_STS,
>> +	MSM_SPM_REG_SAW2_PMIC_STS,
>> +	MSM_SPM_REG_SAW2_VERSION,
>> +
>> +	MSM_SPM_REG_NR,
>> +};
>> +
>> +struct msm_spm_seq_entry {
>> +	uint32_t mode;
>> +	uint8_t *cmd;
>> +	bool  notify_rpm;
>> +};
>> +
>> +struct msm_spm_platform_data {
>> +	void __iomem *reg_base_addr;
>> +	uint32_t reg_init_values[MSM_SPM_REG_NR_INITIALIZE];
>> +
>> +	uint32_t major;
>> +	uint32_t minor;
>> +	uint32_t vctl_port;
>> +	uint32_t phase_port;
>> +	uint32_t pfm_port;
>> +
>> +	uint8_t awake_vlevel;
>> +	uint32_t vctl_timeout_us;
>> +
>> +	uint32_t num_modes;
>> +	struct msm_spm_seq_entry *modes;
>> +};
>> +
>> +enum msm_spm_pmic_port {
>> +	MSM_SPM_PMIC_VCTL_PORT,
>> +	MSM_SPM_PMIC_PHASE_PORT,
>> +	MSM_SPM_PMIC_PFM_PORT,
>> +};
>> +
>> +struct msm_spm_driver_data {
>> +	uint32_t major;
>> +	uint32_t minor;
>> +	uint32_t vctl_port;
>> +	uint32_t phase_port;
>> +	uint32_t pfm_port;
>> +	void __iomem *reg_base_addr;
>> +	uint32_t vctl_timeout_us;
>> +	uint32_t reg_shadow[MSM_SPM_REG_NR];
>> +	uint32_t *reg_seq_entry_shadow;
>> +	uint32_t *reg_offsets;
>> +};
>> +
>> +int msm_spm_drv_init(struct msm_spm_driver_data *dev,
>> +		struct msm_spm_platform_data *data);
>> +void msm_spm_drv_reinit(struct msm_spm_driver_data *dev);
>> +int msm_spm_drv_set_low_power_mode(struct msm_spm_driver_data *dev,
>> +		uint32_t addr, bool pc_mode);
>> +int msm_spm_drv_set_vdd(struct msm_spm_driver_data *dev,
>> +		unsigned int vlevel);
>> +void dump_regs(struct msm_spm_driver_data *dev, int cpu);
>> +uint32_t msm_spm_drv_get_sts_curr_pmic_data(
>> +		struct msm_spm_driver_data *dev);
>> +int msm_spm_drv_write_seq_data(struct msm_spm_driver_data *dev,
>> +		uint8_t *cmd, uint32_t *offset);
>> +void msm_spm_drv_flush_seq_entry(struct msm_spm_driver_data *dev);
>> +int msm_spm_drv_set_spm_enable(struct msm_spm_driver_data *dev,
>> +		bool enable);
>> +int msm_spm_drv_set_pmic_data(struct msm_spm_driver_data *dev,
>> +		enum msm_spm_pmic_port port, unsigned int data);
>> +
>> +void msm_spm_reinit(void);
>> +int msm_spm_init(struct msm_spm_platform_data *data, int nr_devs);
>> +
>> +#endif /* __QCOM_SPM_DRIVER_H */
>> diff --git a/include/soc/qcom/spm.h b/include/soc/qcom/spm.h
>> new file mode 100644
>> index 0000000..f39e0c4
>> --- /dev/null
>> +++ b/include/soc/qcom/spm.h
>> @@ -0,0 +1,70 @@
>> +/* Copyright (c) 2010-2014, The Linux Foundation. All rights reserved.
>> + *
>> + * This program is free software; you can redistribute it and/or modify
>> + * it under the terms of the GNU General Public License version 2 and
>> + * only version 2 as published by the Free Software Foundation.
>> + *
>> + * This program is distributed in the hope that it will be useful,
>> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
>> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
>> + * GNU General Public License for more details.
>> + */
>> +
>> +#ifndef __QCOM_SPM_H
>> +#define __QCOM_SPM_H
>> +
>> +enum {
>> +	MSM_SPM_MODE_DISABLED,
>> +	MSM_SPM_MODE_CLOCK_GATING,
>> +	MSM_SPM_MODE_RETENTION,
>> +	MSM_SPM_MODE_GDHS,
>> +	MSM_SPM_MODE_POWER_COLLAPSE,
>> +	MSM_SPM_MODE_NR
>> +};
>> +
>> +struct msm_spm_device;
>> +
>> +#if defined(CONFIG_QCOM_PM)
>
>Where is CONFIG_QCOM_PM defined?  Wondering if we should have a CONFIG_QCOM_SPM and it can depend on any future ‘QCOM_PM’ in the Kconfig.
In the following patch that introduces SoC interface layer (msm-pm.c).
I will restructure the patches and blend in QCOM_PM as part of this
driver. So this driver may be compiled in. But it may not be of much use
until msm-pm.c comes along.
>
>> +int msm_spm_set_low_power_mode(unsigned int mode, bool notify_rpm);
>> +int msm_spm_probe_done(void);
>> +int msm_spm_set_vdd(unsigned int cpu, unsigned int vlevel);
>> +unsigned int msm_spm_get_vdd(unsigned int cpu);
>> +int msm_spm_turn_on_cpu_rail(void __iomem *base, unsigned int val, int cpu);
>> +struct msm_spm_device *msm_spm_get_device_by_name(const char *name);
>> +int msm_spm_config_low_power_mode(struct msm_spm_device *dev,
>> +		unsigned int mode, bool notify_rpm);
>> +int msm_spm_device_init(void);
>> +bool msm_spm_is_mode_avail(unsigned int mode);
>> +void msm_spm_dump_regs(unsigned int cpu);
>> +int msm_spm_apcs_set_phase(int cpu, unsigned int phase_cnt);
>> +int msm_spm_enable_fts_lpm(int cpu, uint32_t mode);
>> +#else /* defined(CONFIG_QCOM_PM) */
>> +static inline int msm_spm_set_low_power_mode(unsigned int mode, bool notify_rpm)
>> +{ return -ENOSYS; }
>> +static inline int msm_spm_probe_done(void)
>> +{ return -ENOSYS; }
>> +static inline int msm_spm_set_vdd(unsigned int cpu, unsigned int vlevel)
>> +{ return -ENOSYS; }
>> +static inline unsigned int msm_spm_get_vdd(unsigned int cpu)
>> +{ return 0; }
>> +static inline int msm_spm_turn_on_cpu_rail(void __iomem *base,
>> +		unsigned int val, int cpu)
>> +{ return -ENOSYS; }
>> +static inline int msm_spm_device_init(void)
>> +{ return -ENOSYS; }
>> +static void msm_spm_dump_regs(unsigned int cpu) {}
>> +static inline int msm_spm_config_low_power_mode(struct msm_spm_device *dev,
>> +		unsigned int mode, bool notify_rpm)
>> +{ return -ENODEV; }
>> +static inline struct msm_spm_device *msm_spm_get_device_by_name(
>> +		const char *name)
>> +{ return NULL; }
>> +static inline bool msm_spm_is_mode_avail(unsigned int mode)
>> +{ return false; }
>> +static inline int msm_spm_apcs_set_phase(int cpu, unsigned int phase_cnt)
>> +{ return -ENOSYS; }
>> +static inline int msm_spm_enable_fts_lpm(int cpu, uint32_t mode)
>> +{ return -ENOSYS; }
>> +#endif  /* defined (CONFIG_QCOM_PM) */
>> +
>> +#endif  /* __QCOM_SPM_H */
>> --
>> 1.9.1
>>
>> --
>> To unsubscribe from this list: send the line "unsubscribe linux-arm-msm" in
>> the body of a message to majordomo@vger.kernel.org
>> More majordomo info at  http://vger.kernel.org/majordomo-info.html
>
>-- 
>Employee of Qualcomm Innovation Center, Inc.
>Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum, hosted by The Linux Foundation
>

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

* Re: [PATCH v2 03/10] qcom: spm: Add Subsystem Power Manager (SPM) driver for QCOM chipsets
  2014-08-14 15:27     ` Lina Iyer
@ 2014-08-14 15:33       ` Kumar Gala
  0 siblings, 0 replies; 46+ messages in thread
From: Kumar Gala @ 2014-08-14 15:33 UTC (permalink / raw)
  To: Lina Iyer
  Cc: daniel.lezcano, khilman, amit.kucheria, sboyd, davidb,
	linux-arm-msm, msivasub, Praveen Chidamabram, Murali Nalajala


>>> diff --git a/drivers/soc/qcom/spm.c b/drivers/soc/qcom/spm.c
>>> new file mode 100644
>>> index 0000000..7dbdb64
>>> --- /dev/null
>>> +++ b/drivers/soc/qcom/spm.c
>>> @@ -0,0 +1,482 @@
>>> +/* Copyright (c) 2011-2014, The Linux Foundation. All rights reserved.
>>> + *
>>> + * This program is free software; you can redistribute it and/or modify
>>> + * it under the terms of the GNU General Public License version 2 and
>>> + * only version 2 as published by the Free Software Foundation.
>>> + *
>>> + * This program is distributed in the hope that it will be useful,
>>> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
>>> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
>>> + * GNU General Public License for more details.
>>> + *
>>> + */
>>> +
>>> +#include <linux/module.h>
>>> +#include <linux/kernel.h>
>>> +#include <linux/delay.h>
>>> +#include <linux/init.h>
>>> +#include <linux/io.h>
>>> +#include <linux/slab.h>
>>> +
>>> +#include "spm_driver.h"
>>> +
>>> +#define MSM_SPM_PMIC_STATE_IDLE  0
>>> +
>>> +enum {
>>> +	MSM_SPM_DEBUG_SHADOW = 1U << 0,
>>> +	MSM_SPM_DEBUG_VCTL = 1U << 1,
>>> +};
>>> +
>>> +static int msm_spm_debug_mask;
>>> +module_param_named(
>>> +	debug_mask, msm_spm_debug_mask, int, S_IRUGO | S_IWUSR | S_IWGRP
>>> +);
>>> +
>>> +struct saw2_data {
>>> +	const char *ver_name;
>>> +	uint32_t major;
>>> +	uint32_t minor;
>>> +	uint32_t *spm_reg_offset_ptr;
>>> +};
>>> +
>>> +static uint32_t msm_spm_reg_offsets_saw2_v2_1[MSM_SPM_REG_NR] = {
>>> +	[MSM_SPM_REG_SAW2_SECURE]		= 0x00,
>>> +	[MSM_SPM_REG_SAW2_ID]			= 0x04,
>>> +	[MSM_SPM_REG_SAW2_CFG]			= 0x08,
>>> +	[MSM_SPM_REG_SAW2_SPM_STS]		= 0x0C,
>>> +	[MSM_SPM_REG_SAW2_AVS_STS]		= 0x10,
>>> +	[MSM_SPM_REG_SAW2_PMIC_STS]		= 0x14,
>>> +	[MSM_SPM_REG_SAW2_RST]			= 0x18,
>>> +	[MSM_SPM_REG_SAW2_VCTL]			= 0x1C,
>>> +	[MSM_SPM_REG_SAW2_AVS_CTL]		= 0x20,
>>> +	[MSM_SPM_REG_SAW2_AVS_LIMIT]		= 0x24,
>>> +	[MSM_SPM_REG_SAW2_AVS_DLY]		= 0x28,
>>> +	[MSM_SPM_REG_SAW2_AVS_HYSTERESIS]	= 0x2C,
>>> +	[MSM_SPM_REG_SAW2_SPM_CTL]		= 0x30,
>>> +	[MSM_SPM_REG_SAW2_SPM_DLY]		= 0x34,
>>> +	[MSM_SPM_REG_SAW2_PMIC_DATA_0]		= 0x40,
>>> +	[MSM_SPM_REG_SAW2_PMIC_DATA_1]		= 0x44,
>>> +	[MSM_SPM_REG_SAW2_PMIC_DATA_2]		= 0x48,
>>> +	[MSM_SPM_REG_SAW2_PMIC_DATA_3]		= 0x4C,
>>> +	[MSM_SPM_REG_SAW2_PMIC_DATA_4]		= 0x50,
>>> +	[MSM_SPM_REG_SAW2_PMIC_DATA_5]		= 0x54,
>>> +	[MSM_SPM_REG_SAW2_PMIC_DATA_6]		= 0x58,
>>> +	[MSM_SPM_REG_SAW2_PMIC_DATA_7]		= 0x5C,
>>> +	[MSM_SPM_REG_SAW2_SEQ_ENTRY]		= 0x80,
>>> +	[MSM_SPM_REG_SAW2_VERSION]		= 0xFD0,
>>> +};
>>> +
>>> +static uint32_t msm_spm_reg_offsets_saw2_v3_0[MSM_SPM_REG_NR] = {
>>> +	[MSM_SPM_REG_SAW2_SECURE]		= 0x00,
>>> +	[MSM_SPM_REG_SAW2_ID]			= 0x04,
>>> +	[MSM_SPM_REG_SAW2_CFG]			= 0x08,
>>> +	[MSM_SPM_REG_SAW2_SPM_STS]		= 0x0C,
>>> +	[MSM_SPM_REG_SAW2_AVS_STS]		= 0x10,
>>> +	[MSM_SPM_REG_SAW2_PMIC_STS]		= 0x14,
>>> +	[MSM_SPM_REG_SAW2_RST]			= 0x18,
>>> +	[MSM_SPM_REG_SAW2_VCTL]			= 0x1C,
>>> +	[MSM_SPM_REG_SAW2_AVS_CTL]		= 0x20,
>>> +	[MSM_SPM_REG_SAW2_AVS_LIMIT]		= 0x24,
>>> +	[MSM_SPM_REG_SAW2_AVS_DLY]		= 0x28,
>>> +	[MSM_SPM_REG_SAW2_AVS_HYSTERESIS]	= 0x2C,
>>> +	[MSM_SPM_REG_SAW2_SPM_CTL]		= 0x30,
>>> +	[MSM_SPM_REG_SAW2_SPM_DLY]		= 0x34,
>>> +	[MSM_SPM_REG_SAW2_STS2]			= 0x38,
>>> +	[MSM_SPM_REG_SAW2_PMIC_DATA_0]		= 0x40,
>>> +	[MSM_SPM_REG_SAW2_PMIC_DATA_1]		= 0x44,
>>> +	[MSM_SPM_REG_SAW2_PMIC_DATA_2]		= 0x48,
>>> +	[MSM_SPM_REG_SAW2_PMIC_DATA_3]		= 0x4C,
>>> +	[MSM_SPM_REG_SAW2_PMIC_DATA_4]		= 0x50,
>>> +	[MSM_SPM_REG_SAW2_PMIC_DATA_5]		= 0x54,
>>> +	[MSM_SPM_REG_SAW2_PMIC_DATA_6]		= 0x58,
>>> +	[MSM_SPM_REG_SAW2_PMIC_DATA_7]		= 0x5C,
>>> +	[MSM_SPM_REG_SAW2_SEQ_ENTRY]		= 0x400,
>>> +	[MSM_SPM_REG_SAW2_VERSION]		= 0xFD0,
>>> +};
>> 
>> I don’t see what having these arrays provides as the only differences are that v3.0 has MSM_SPM_REG_SAW2_STS2 and the offset of MSM_SPM_REG_SAW2_SEQ_ENTRY.  If so we can remove all this extra code and just add a simple check in msm_spm_drv_flush_seq_entry that looks at the compatible and picks the proper offset when updating MSM_SPM_REG_SAW2_SEQ_ENTRY.
>> 
> Isnt that an hack ?

Why would it be a hack, if the only difference in the driver can be reduced down to 5 lines of code, versus what is currently there I don’t see that as a hack at all.

[ snip ]

>>> diff --git a/include/soc/qcom/spm.h b/include/soc/qcom/spm.h
>>> new file mode 100644
>>> index 0000000..f39e0c4
>>> --- /dev/null
>>> +++ b/include/soc/qcom/spm.h
>>> @@ -0,0 +1,70 @@
>>> +/* Copyright (c) 2010-2014, The Linux Foundation. All rights reserved.
>>> + *
>>> + * This program is free software; you can redistribute it and/or modify
>>> + * it under the terms of the GNU General Public License version 2 and
>>> + * only version 2 as published by the Free Software Foundation.
>>> + *
>>> + * This program is distributed in the hope that it will be useful,
>>> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
>>> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
>>> + * GNU General Public License for more details.
>>> + */
>>> +
>>> +#ifndef __QCOM_SPM_H
>>> +#define __QCOM_SPM_H
>>> +
>>> +enum {
>>> +	MSM_SPM_MODE_DISABLED,
>>> +	MSM_SPM_MODE_CLOCK_GATING,
>>> +	MSM_SPM_MODE_RETENTION,
>>> +	MSM_SPM_MODE_GDHS,
>>> +	MSM_SPM_MODE_POWER_COLLAPSE,
>>> +	MSM_SPM_MODE_NR
>>> +};
>>> +
>>> +struct msm_spm_device;
>>> +
>>> +#if defined(CONFIG_QCOM_PM)
>> 
>> Where is CONFIG_QCOM_PM defined?  Wondering if we should have a CONFIG_QCOM_SPM and it can depend on any future ‘QCOM_PM’ in the Kconfig.
> In the following patch that introduces SoC interface layer (msm-pm.c).
> I will restructure the patches and blend in QCOM_PM as part of this
> driver. So this driver may be compiled in. But it may not be of much use
> until msm-pm.c comes along.

Yeah, is probably best to at least introduce CONFIG_QCOM_PM with this patch so the driver can be built, even if it doesn’t really do much but initialize.

[ snip ]

- k

-- 
Employee of Qualcomm Innovation Center, Inc.
Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum, hosted by The Linux Foundation

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

* Re: [PATCH v2 03/10] qcom: spm: Add Subsystem Power Manager (SPM) driver for QCOM chipsets
  2014-08-12 19:43 ` [PATCH v2 03/10] qcom: spm: Add Subsystem Power Manager (SPM) driver for QCOM chipsets Lina Iyer
                     ` (2 preceding siblings ...)
  2014-08-14 15:16   ` Kumar Gala
@ 2014-08-14 16:09   ` Kumar Gala
  2014-08-14 16:18     ` Lina Iyer
  3 siblings, 1 reply; 46+ messages in thread
From: Kumar Gala @ 2014-08-14 16:09 UTC (permalink / raw)
  To: Lina Iyer
  Cc: Daniel Lezcano, Kevin Hilman, Amit Kucheria, Stephen Boyd,
	David Brown, linux-arm-msm, msivasub, Praveen Chidamabram,
	Murali Nalajala


On Aug 12, 2014, at 2:43 PM, Lina Iyer <lina.iyer@linaro.org> wrote:

> 
> diff --git a/Documentation/devicetree/bindings/arm/msm/spm-v2.txt b/Documentation/devicetree/bindings/arm/msm/spm-v2.txt
> new file mode 100644
> index 0000000..3130f4b
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/arm/msm/spm-v2.txt
> @@ -0,0 +1,62 @@
> +* MSM Subsystem Power Manager (spm-v2)
> +
> +S4 generation of MSMs have SPM hardware blocks to control the Application
> +Processor Sub-System power. These SPM blocks run individual state machine
> +to determine what the core (L2 or Krait/Scorpion) would do when the WFI
> +instruction is executed by the core.
> +
> +The devicetree representation of the SPM block should be:
> +
> +Required properties
> +
> +- compatible: Could be one of -
> +		"qcom,spm-v2.1"
> +		"qcom,spm-v3.0"
> +- reg: The physical address and the size of the SPM's memory mapped registers
> +- qcom,cpu: phandle for the CPU that the SPM block is attached to. On targets
> +	that dont support CPU phandles the driver would support qcom,core-id.
> +	This field is required on only for SPMs that control the CPU.
> +- qcom,saw2-cfg: SAW2 configuration register

Can we change this to qcom,saw2-clk-div as that is what is really getting set, I know there are a few other fields in the saw2-cfg register, but I’m pretty sure we arent ever really setting those from DT. 

> +- qcom,saw2-spm-dly: Provides the values for the SPM delay command in the SPM
> +	sequence
> +- qcom,saw2-spm-ctl: The SPM control register

Can we describe this as “spm-enable”, “spm-inhibit-start-address”, “spm-wakeup-cfg”?

Also, I’m unclear why would we have a case that spm would be disabled?

> +- qcom,name: The name with which a SPM device is identified by the power
> +	management code.
> +
> +Optional properties
> +
> +- qcom,saw2-pmic-data0..7: Specify the pmic data value and the associated FTS
> +	(Fast Transient Switch) index to send the PMIC data to
> +- qcom,vctl-port: The PVC (PMIC Virtual Channel) port used for changing
> +	voltage
> +- qcom,phase-port: The PVC port used for changing the number of phases
> +- qcom,pfm-port: The PVC port used for enabling PWM/PFM modes
> +- qcom,saw2-spm-cmd-wfi: The WFI command sequence
> +- qcom,saw2-spm-cmd-ret: The Retention command sequence
> +- qcom,saw2-spm-cmd-spc: The Standalone PC command sequence
> +- qcom,saw2-spm-cmd-pc-no-rpm: The Power Collapse command sequence where APPS
> +	proc won't inform the RPM.
> +- qcom,saw2-spm-cmd-pc: The Power Collapse command sequence. This sequence may
> +	turn off other SoC components.
> +- qcom,saw2-spm-cmd-gdhs: GDHS (Globally Distributed Head Switch) command
> +	sequence. This sequence will retain the memory but turn off the logic.
> +- qcom,cpu-vctl-list: List of cpu node phandles, whose voltage the spm device
> +	can control.
> +- qcom,vctl-timeout-us: The timeout value in microseconds to wait for voltage to
> +	change after sending the voltage command to the PMIC.
> +-
> +Example:
> +	qcom,spm@f9089000 {
> +		compatible = "qcom,spm-v2";
> +		#address-cells = <1>;
> +		#size-cells = <1>;
> +		reg = <0xf9089000 0x1000>;
> +		qcom,cpu = <&CPU0>;
> +		qcom,saw2-cfg = <0x1>;
> +		qcom,saw2-spm-dly= <0x20000400>;
> +		qcom,saw2-spm-ctl = <0x1>;
> +		qcom,saw2-spm-cmd-wfi = [03 0b 0f];
> +		qcom,saw2-spm-cmd-spc = [00 20 50 80 60 70 10 92
> +				a0 b0 03 68 70 3b 92 a0 b0
> +				82 2b 50 10 30 02 22 30 0f];
> +	};

-- 
Employee of Qualcomm Innovation Center, Inc.
Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum, hosted by The Linux Foundation

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

* Re: [PATCH v2 07/10] qcom: msm-pm: Add cpu low power mode functions
  2014-08-13 14:16     ` Lina Iyer
  2014-08-14 14:24       ` Daniel Lezcano
@ 2014-08-14 16:11       ` Daniel Lezcano
  2014-08-14 19:22         ` Lina Iyer
  1 sibling, 1 reply; 46+ messages in thread
From: Daniel Lezcano @ 2014-08-14 16:11 UTC (permalink / raw)
  To: Lina Iyer
  Cc: khilman, amit.kucheria, sboyd, davidb, galak, linux-arm-msm,
	msivasub, Venkat Devarasetty, Nicolas Pitre

On 08/13/2014 04:16 PM, Lina Iyer wrote:
> On Wed, Aug 13, 2014 at 01:18:01PM +0200, Daniel Lezcano wrote:
>> On 08/12/2014 09:43 PM, Lina Iyer wrote:
>>> Add interface layer to abstract and handle hardware specific
>>> functionality for executing various cpu low power modes in QCOM
>>> chipsets.
>>>
>>> Signed-off-by: Venkat Devarasetty <vdevaras@codeaurora.org>
>>> Signed-off-by: Mahesh Sivasubramanian <msivasub@codeaurora.org>
>>> Signed-off-by: Lina Iyer <lina.iyer@linaro.org>
>>> ---
>>>  drivers/soc/qcom/Makefile |   2 +-
>>>  drivers/soc/qcom/msm-pm.c | 219
>>> ++++++++++++++++++++++++++++++++++++++++++++++
>>>  include/soc/qcom/pm.h     |  39 +++++++++
>>>  3 files changed, 259 insertions(+), 1 deletion(-)
>>>  create mode 100644 drivers/soc/qcom/msm-pm.c
>>>  create mode 100644 include/soc/qcom/pm.h
>>>
>>> diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile
>>> index d7ae93b..7925f83 100644
>>> --- a/drivers/soc/qcom/Makefile
>>> +++ b/drivers/soc/qcom/Makefile
>>> @@ -1,5 +1,5 @@
>>>  obj-$(CONFIG_QCOM_GSBI)    +=    qcom_gsbi.o
>>> -obj-$(CONFIG_QCOM_PM) +=    spm-devices.o spm.o
>>> +obj-$(CONFIG_QCOM_PM) +=    spm-devices.o spm.o msm-pm.o
>>>
>>>  CFLAGS_scm.o :=$(call as-instr,.arch_extension sec,-DREQUIRES_SEC=1)
>>>  obj-$(CONFIG_QCOM_SCM) += scm.o scm-boot.o
>>> diff --git a/drivers/soc/qcom/msm-pm.c b/drivers/soc/qcom/msm-pm.c
>>> new file mode 100644
>>> index 0000000..f2f15b8
>>> --- /dev/null
>>> +++ b/drivers/soc/qcom/msm-pm.c
>>> @@ -0,0 +1,219 @@
>>> +/* Copyright (c) 2010-2014, The Linux Foundation. All rights reserved.
>>> + *
>>> + * This program is free software; you can redistribute it and/or modify
>>> + * it under the terms of the GNU General Public License version 2 and
>>> + * only version 2 as published by the Free Software Foundation.
>>> + *
>>> + * This program is distributed in the hope that it will be useful,
>>> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
>>> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
>>> + * GNU General Public License for more details.
>>> + *
>>> + */
>>> +
>>> +#include <linux/module.h>
>>> +#include <linux/kernel.h>
>>> +#include <linux/init.h>
>>> +#include <linux/io.h>
>>> +#include <linux/smp.h>
>>> +#include <linux/tick.h>
>>> +#include <linux/platform_device.h>
>>> +#include <linux/cpu_pm.h>
>>> +#include <linux/uaccess.h>
>>> +
>>> +#include <soc/qcom/spm.h>
>>> +#include <soc/qcom/pm.h>
>>> +#include <soc/qcom/scm.h>
>>> +#include <soc/qcom/scm-boot.h>
>>> +
>>> +#include <asm/suspend.h>
>>> +#include <asm/cacheflush.h>
>>> +#include <asm/cputype.h>
>>> +#include <asm/system_misc.h>
>>> +
>>> +#define SCM_CMD_TERMINATE_PC    (0x2)
>>> +#define SCM_CMD_CORE_HOTPLUGGED (0x10)
>>> +#define SCM_FLUSH_FLAG_MASK    (0x3)
>>> +
>>> +static bool msm_pm_is_L1_writeback(void)
>>> +{
>>> +    u32 cache_id = 0;
>>> +
>>> +#if defined(CONFIG_CPU_V7)
>>> +    u32 sel = 0;
>>> +
>>> +    asm volatile ("mcr p15, 2, %[ccselr], c0, c0, 0\n\t"
>>> +              "isb\n\t"
>>> +              "mrc p15, 1, %[ccsidr], c0, c0, 0\n\t"
>>> +              :[ccsidr]"=r" (cache_id)
>>> +              :[ccselr]"r" (sel)
>>> +             );
>>> +    return cache_id & BIT(30);
>>> +#elif defined(CONFIG_ARM64)
>>> +    u32 sel = 0;
>>> +    asm volatile("msr csselr_el1, %[ccselr]\n\t"
>>> +             "isb\n\t"
>>> +             "mrs %[ccsidr],ccsidr_el1\n\t"
>>> +             :[ccsidr]"=r" (cache_id)
>>> +             :[ccselr]"r" (sel)
>>> +            );
>>> +    return cache_id & BIT(30);
>>> +#else
>>> +#error No valid CPU arch selected
>>> +#endif
>>> +}
>>> +
>>> +static inline void msm_arch_idle(void)
>>> +{
>>> +    /* Flush and clock-gate */
>>> +    mb();
>>
>> Why is needed this memory barrier ?
> Some QCOM SoCs needed this. I am not sure which one anymore. :(

I guess this is to flush the L1 cache when the core is going down. 
Regarding the kernel option, it seems mb() is as dsb(), so I am 
wondering if this function could be simply replaced by cpu_do_idle().

>>> +    wfi();
>>> +}
>>> +
>>> +static bool msm_pm_swfi(bool from_idle)
>>> +{
>>> +    msm_arch_idle();
>>> +    return true;
>>> +}

Same here, could be replaced by cpu_do_idle(), I think.

>>> +static bool msm_pm_retention(bool from_idle)
>>> +{
>>> +    int ret = 0;
>>> +
>>> +    ret = msm_spm_set_low_power_mode(MSM_SPM_MODE_RETENTION, false);
>>> +    WARN_ON(ret);
>>> +
>>> +    msm_arch_idle();
>>> +
>>> +    ret = msm_spm_set_low_power_mode(MSM_SPM_MODE_CLOCK_GATING, false);
>>> +    WARN_ON(ret);
>>
>> Why do you need to set the clock gating mode each time you exit the
>> retention mode ?
> So if the SPM did not reset to clockgating, we would not do retention
> when we intended to do clockgating. Btw, we dont set clockgating
> everytime we do clockgating, helps reduce the latency in doing WFI.

Thanks for the explanation in the other email. So IIUC, the SCM keeps 
the last state configuration and we have to set it back to clock gating, 
right ?

I don't think it is up to this function to do this but the clock gating 
function.

Also, this function prototype looks a bit weird. Just for the sake of 
using callbacks.

And finally, the WARN_ON is not desirable here, except if the goal is to 
flood the terminal :)

What not using first simple functions ?

void qcom_do_idle(void)
{
	myfirmware_call(MSM_SPM_MODE_CLOCK_GATING);
	wfi();
}

void qcom_cpu_retention(void)
{
	myfirmware_call(MSM_SPM_MODE_RETENTION);
	dsb();
	wfi();
}

void qcom_cpu_powerdown(int flags)
{
	scm_call_atomic1(SCM_SVC_BOOT, SCM_CMD_TERMINATE_PC, flag);
}

and then you build on top of that the cpuidle driver.

The patchset adds all the features in one shot and for someone not used 
with the platform it is really hard to follow all the code.

I suggest you write a simple cpuidle driver based on the DT Lorenzo 
patches bringing the clock gating, then another patchset with the 
retention mode, etc ...

>>> +    return true;
>>> +}
>>> +
>>> +static int msm_pm_collapse(unsigned long from_idle)
>>> +{
>>> +    enum msm_pm_l2_scm_flag flag = MSM_SCM_L2_ON;
>>> +
>>> +    /**
>>> +     * Single core processors need to have L2
>>> +     * flushed when powering down the core.
>>> +     * Notify SCM to flush secure L2 lines.
>>> +     */
>>> +    if (num_possible_cpus() == 1)
>>> +        flag = MSM_SCM_L2_OFF;
>>
>> I am wondering if this shouldn't be handle by a mcpm driver.
>>
>> Cc nico.
>
> Well, possibly, sorry, not sure what features of the mcpm driver you
> think I need here?

Please correct me if I am wrong. IIUC, this function is checking the
number of the cpus of the cluster in order to flush the L2 cache
because the SCM will power down the cluster if it is the last one,
right ?


-- 
  <http://www.linaro.org/> Linaro.org │ Open source software for ARM SoCs

Follow Linaro:  <http://www.facebook.com/pages/Linaro> Facebook |
<http://twitter.com/#!/linaroorg> Twitter |
<http://www.linaro.org/linaro-blog/> Blog

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

* Re: [PATCH v2 03/10] qcom: spm: Add Subsystem Power Manager (SPM) driver for QCOM chipsets
  2014-08-14 16:09   ` Kumar Gala
@ 2014-08-14 16:18     ` Lina Iyer
  2014-08-14 16:41       ` Kumar Gala
  0 siblings, 1 reply; 46+ messages in thread
From: Lina Iyer @ 2014-08-14 16:18 UTC (permalink / raw)
  To: Kumar Gala
  Cc: Daniel Lezcano, Kevin Hilman, Amit Kucheria, Stephen Boyd,
	David Brown, linux-arm-msm, msivasub, Praveen Chidamabram,
	Murali Nalajala

On Thu, Aug 14, 2014 at 11:09:48AM -0500, Kumar Gala wrote:
>
>On Aug 12, 2014, at 2:43 PM, Lina Iyer <lina.iyer@linaro.org> wrote:
>
>>
>> diff --git a/Documentation/devicetree/bindings/arm/msm/spm-v2.txt b/Documentation/devicetree/bindings/arm/msm/spm-v2.txt
>> new file mode 100644
>> index 0000000..3130f4b
>> --- /dev/null
>> +++ b/Documentation/devicetree/bindings/arm/msm/spm-v2.txt
>> @@ -0,0 +1,62 @@
>> +* MSM Subsystem Power Manager (spm-v2)
>> +
>> +S4 generation of MSMs have SPM hardware blocks to control the Application
>> +Processor Sub-System power. These SPM blocks run individual state machine
>> +to determine what the core (L2 or Krait/Scorpion) would do when the WFI
>> +instruction is executed by the core.
>> +
>> +The devicetree representation of the SPM block should be:
>> +
>> +Required properties
>> +
>> +- compatible: Could be one of -
>> +		"qcom,spm-v2.1"
>> +		"qcom,spm-v3.0"
>> +- reg: The physical address and the size of the SPM's memory mapped registers
>> +- qcom,cpu: phandle for the CPU that the SPM block is attached to. On targets
>> +	that dont support CPU phandles the driver would support qcom,core-id.
>> +	This field is required on only for SPMs that control the CPU.
>> +- qcom,saw2-cfg: SAW2 configuration register
>
>Can we change this to qcom,saw2-clk-div as that is what is really getting set, I know there are a few other fields in the saw2-cfg register, but I’m pretty sure we arent ever really setting those from DT.
>
I am pruning them off in the next revision of the patch.
>> +- qcom,saw2-spm-dly: Provides the values for the SPM delay command in the SPM
>> +	sequence
>> +- qcom,saw2-spm-ctl: The SPM control register
>
>Can we describe this as “spm-enable”, “spm-inhibit-start-address”, “spm-wakeup-cfg”?
>
>Also, I’m unclear why would we have a case that spm would be disabled?
>
Much of these registers names make it easier for developers and
debuggers to relate it to the hardware spec. Choosing different names
here though might make it readable would convolute their efforts.

>> +- qcom,name: The name with which a SPM device is identified by the power
>> +	management code.
>> +
>> +Optional properties
>> +
>> +- qcom,saw2-pmic-data0..7: Specify the pmic data value and the associated FTS
>> +	(Fast Transient Switch) index to send the PMIC data to
>> +- qcom,vctl-port: The PVC (PMIC Virtual Channel) port used for changing
>> +	voltage
>> +- qcom,phase-port: The PVC port used for changing the number of phases
>> +- qcom,pfm-port: The PVC port used for enabling PWM/PFM modes
>> +- qcom,saw2-spm-cmd-wfi: The WFI command sequence
>> +- qcom,saw2-spm-cmd-ret: The Retention command sequence
>> +- qcom,saw2-spm-cmd-spc: The Standalone PC command sequence
>> +- qcom,saw2-spm-cmd-pc-no-rpm: The Power Collapse command sequence where APPS
>> +	proc won't inform the RPM.
>> +- qcom,saw2-spm-cmd-pc: The Power Collapse command sequence. This sequence may
>> +	turn off other SoC components.
>> +- qcom,saw2-spm-cmd-gdhs: GDHS (Globally Distributed Head Switch) command
>> +	sequence. This sequence will retain the memory but turn off the logic.
>> +- qcom,cpu-vctl-list: List of cpu node phandles, whose voltage the spm device
>> +	can control.
>> +- qcom,vctl-timeout-us: The timeout value in microseconds to wait for voltage to
>> +	change after sending the voltage command to the PMIC.
>> +-
>> +Example:
>> +	qcom,spm@f9089000 {
>> +		compatible = "qcom,spm-v2";
>> +		#address-cells = <1>;
>> +		#size-cells = <1>;
>> +		reg = <0xf9089000 0x1000>;
>> +		qcom,cpu = <&CPU0>;
>> +		qcom,saw2-cfg = <0x1>;
>> +		qcom,saw2-spm-dly= <0x20000400>;
>> +		qcom,saw2-spm-ctl = <0x1>;
>> +		qcom,saw2-spm-cmd-wfi = [03 0b 0f];
>> +		qcom,saw2-spm-cmd-spc = [00 20 50 80 60 70 10 92
>> +				a0 b0 03 68 70 3b 92 a0 b0
>> +				82 2b 50 10 30 02 22 30 0f];
>> +	};
>
>-- 
>Employee of Qualcomm Innovation Center, Inc.
>Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum, hosted by The Linux Foundation
>

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

* Re: [PATCH v2 03/10] qcom: spm: Add Subsystem Power Manager (SPM) driver for QCOM chipsets
  2014-08-14 16:18     ` Lina Iyer
@ 2014-08-14 16:41       ` Kumar Gala
  2014-08-15  4:18         ` Lina Iyer
  0 siblings, 1 reply; 46+ messages in thread
From: Kumar Gala @ 2014-08-14 16:41 UTC (permalink / raw)
  To: Lina Iyer
  Cc: Daniel Lezcano, Kevin Hilman, Amit Kucheria, Stephen Boyd,
	David Brown, linux-arm-msm, msivasub, Praveen Chidamabram,
	Murali Nalajala


On Aug 14, 2014, at 11:18 AM, Lina Iyer <lina.iyer@linaro.org> wrote:

> On Thu, Aug 14, 2014 at 11:09:48AM -0500, Kumar Gala wrote:
>> 
>> On Aug 12, 2014, at 2:43 PM, Lina Iyer <lina.iyer@linaro.org> wrote:
>> 
>>> 
>>> diff --git a/Documentation/devicetree/bindings/arm/msm/spm-v2.txt b/Documentation/devicetree/bindings/arm/msm/spm-v2.txt
>>> new file mode 100644
>>> index 0000000..3130f4b
>>> --- /dev/null
>>> +++ b/Documentation/devicetree/bindings/arm/msm/spm-v2.txt
>>> @@ -0,0 +1,62 @@
>>> +* MSM Subsystem Power Manager (spm-v2)
>>> +
>>> +S4 generation of MSMs have SPM hardware blocks to control the Application
>>> +Processor Sub-System power. These SPM blocks run individual state machine
>>> +to determine what the core (L2 or Krait/Scorpion) would do when the WFI
>>> +instruction is executed by the core.
>>> +
>>> +The devicetree representation of the SPM block should be:
>>> +
>>> +Required properties
>>> +
>>> +- compatible: Could be one of -
>>> +		"qcom,spm-v2.1"
>>> +		"qcom,spm-v3.0"
>>> +- reg: The physical address and the size of the SPM's memory mapped registers
>>> +- qcom,cpu: phandle for the CPU that the SPM block is attached to. On targets
>>> +	that dont support CPU phandles the driver would support qcom,core-id.
>>> +	This field is required on only for SPMs that control the CPU.
>>> +- qcom,saw2-cfg: SAW2 configuration register
>> 
>> Can we change this to qcom,saw2-clk-div as that is what is really getting set, I know there are a few other fields in the saw2-cfg register, but I’m pretty sure we arent ever really setting those from DT.
>> 
> I am pruning them off in the next revision of the patch.
>>> +- qcom,saw2-spm-dly: Provides the values for the SPM delay command in the SPM
>>> +	sequence
>>> +- qcom,saw2-spm-ctl: The SPM control register
>> 
>> Can we describe this as “spm-enable”, “spm-inhibit-start-address”, “spm-wakeup-cfg”?
>> 
>> Also, I’m unclear why would we have a case that spm would be disabled?
>> 
> Much of these registers names make it easier for developers and
> debuggers to relate it to the hardware spec. Choosing different names
> here though might make it readable would convolute their efforts.

The point is to move away from just dumping a register value directly from DT into the device.  This is pretty bad form.  The names can relate to the register, etc, its just the fields that are really being used/set was the direction I was suggesting we go.

> 
>>> +- qcom,name: The name with which a SPM device is identified by the power
>>> +	management code.
>>> +
>>> +Optional properties
>>> +
>>> +- qcom,saw2-pmic-data0..7: Specify the pmic data value and the associated FTS
>>> +	(Fast Transient Switch) index to send the PMIC data to
>>> +- qcom,vctl-port: The PVC (PMIC Virtual Channel) port used for changing
>>> +	voltage
>>> +- qcom,phase-port: The PVC port used for changing the number of phases
>>> +- qcom,pfm-port: The PVC port used for enabling PWM/PFM modes
>>> +- qcom,saw2-spm-cmd-wfi: The WFI command sequence
>>> +- qcom,saw2-spm-cmd-ret: The Retention command sequence
>>> +- qcom,saw2-spm-cmd-spc: The Standalone PC command sequence
>>> +- qcom,saw2-spm-cmd-pc-no-rpm: The Power Collapse command sequence where APPS
>>> +	proc won't inform the RPM.
>>> +- qcom,saw2-spm-cmd-pc: The Power Collapse command sequence. This sequence may
>>> +	turn off other SoC components.
>>> +- qcom,saw2-spm-cmd-gdhs: GDHS (Globally Distributed Head Switch) command
>>> +	sequence. This sequence will retain the memory but turn off the logic.
>>> +- qcom,cpu-vctl-list: List of cpu node phandles, whose voltage the spm device
>>> +	can control.
>>> +- qcom,vctl-timeout-us: The timeout value in microseconds to wait for voltage to
>>> +	change after sending the voltage command to the PMIC.
>>> +-
>>> +Example:
>>> +	qcom,spm@f9089000 {
>>> +		compatible = "qcom,spm-v2";
>>> +		#address-cells = <1>;
>>> +		#size-cells = <1>;
>>> +		reg = <0xf9089000 0x1000>;
>>> +		qcom,cpu = <&CPU0>;
>>> +		qcom,saw2-cfg = <0x1>;
>>> +		qcom,saw2-spm-dly= <0x20000400>;
>>> +		qcom,saw2-spm-ctl = <0x1>;
>>> +		qcom,saw2-spm-cmd-wfi = [03 0b 0f];
>>> +		qcom,saw2-spm-cmd-spc = [00 20 50 80 60 70 10 92
>>> +				a0 b0 03 68 70 3b 92 a0 b0
>>> +				82 2b 50 10 30 02 22 30 0f];
>>> +	};
>> 
>> -- 
>> Employee of Qualcomm Innovation Center, Inc.
>> Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum, hosted by The Linux Foundation
>> 

- k

-- 
Employee of Qualcomm Innovation Center, Inc.
Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum, hosted by The Linux Foundation

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

* Re: [PATCH v2 07/10] qcom: msm-pm: Add cpu low power mode functions
  2014-08-14 16:11       ` Daniel Lezcano
@ 2014-08-14 19:22         ` Lina Iyer
  2014-08-15  0:01           ` Daniel Lezcano
  0 siblings, 1 reply; 46+ messages in thread
From: Lina Iyer @ 2014-08-14 19:22 UTC (permalink / raw)
  To: Daniel Lezcano
  Cc: khilman, amit.kucheria, sboyd, davidb, galak, linux-arm-msm,
	msivasub, Venkat Devarasetty, Nicolas Pitre

On Thu, Aug 14, 2014 at 06:11:43PM +0200, Daniel Lezcano wrote:
>On 08/13/2014 04:16 PM, Lina Iyer wrote:
>>On Wed, Aug 13, 2014 at 01:18:01PM +0200, Daniel Lezcano wrote:
>>>On 08/12/2014 09:43 PM, Lina Iyer wrote:
>>>>Add interface layer to abstract and handle hardware specific
>>>>functionality for executing various cpu low power modes in QCOM
>>>>chipsets.
>>>>
>>>>Signed-off-by: Venkat Devarasetty <vdevaras@codeaurora.org>
>>>>Signed-off-by: Mahesh Sivasubramanian <msivasub@codeaurora.org>
>>>>Signed-off-by: Lina Iyer <lina.iyer@linaro.org>
>>>>---
>>>> drivers/soc/qcom/Makefile |   2 +-
>>>> drivers/soc/qcom/msm-pm.c | 219
>>>>++++++++++++++++++++++++++++++++++++++++++++++
>>>> include/soc/qcom/pm.h     |  39 +++++++++
>>>> 3 files changed, 259 insertions(+), 1 deletion(-)
>>>> create mode 100644 drivers/soc/qcom/msm-pm.c
>>>> create mode 100644 include/soc/qcom/pm.h
>>>>
>>>>diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile
>>>>index d7ae93b..7925f83 100644
>>>>--- a/drivers/soc/qcom/Makefile
>>>>+++ b/drivers/soc/qcom/Makefile
>>>>@@ -1,5 +1,5 @@
>>>> obj-$(CONFIG_QCOM_GSBI)    +=    qcom_gsbi.o
>>>>-obj-$(CONFIG_QCOM_PM) +=    spm-devices.o spm.o
>>>>+obj-$(CONFIG_QCOM_PM) +=    spm-devices.o spm.o msm-pm.o
>>>>
>>>> CFLAGS_scm.o :=$(call as-instr,.arch_extension sec,-DREQUIRES_SEC=1)
>>>> obj-$(CONFIG_QCOM_SCM) += scm.o scm-boot.o
>>>>diff --git a/drivers/soc/qcom/msm-pm.c b/drivers/soc/qcom/msm-pm.c
>>>>new file mode 100644
>>>>index 0000000..f2f15b8
>>>>--- /dev/null
>>>>+++ b/drivers/soc/qcom/msm-pm.c
>>>>@@ -0,0 +1,219 @@
>>>>+/* Copyright (c) 2010-2014, The Linux Foundation. All rights reserved.
>>>>+ *
>>>>+ * This program is free software; you can redistribute it and/or modify
>>>>+ * it under the terms of the GNU General Public License version 2 and
>>>>+ * only version 2 as published by the Free Software Foundation.
>>>>+ *
>>>>+ * This program is distributed in the hope that it will be useful,
>>>>+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
>>>>+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
>>>>+ * GNU General Public License for more details.
>>>>+ *
>>>>+ */
>>>>+
>>>>+#include <linux/module.h>
>>>>+#include <linux/kernel.h>
>>>>+#include <linux/init.h>
>>>>+#include <linux/io.h>
>>>>+#include <linux/smp.h>
>>>>+#include <linux/tick.h>
>>>>+#include <linux/platform_device.h>
>>>>+#include <linux/cpu_pm.h>
>>>>+#include <linux/uaccess.h>
>>>>+
>>>>+#include <soc/qcom/spm.h>
>>>>+#include <soc/qcom/pm.h>
>>>>+#include <soc/qcom/scm.h>
>>>>+#include <soc/qcom/scm-boot.h>
>>>>+
>>>>+#include <asm/suspend.h>
>>>>+#include <asm/cacheflush.h>
>>>>+#include <asm/cputype.h>
>>>>+#include <asm/system_misc.h>
>>>>+
>>>>+#define SCM_CMD_TERMINATE_PC    (0x2)
>>>>+#define SCM_CMD_CORE_HOTPLUGGED (0x10)
>>>>+#define SCM_FLUSH_FLAG_MASK    (0x3)
>>>>+
>>>>+static bool msm_pm_is_L1_writeback(void)
>>>>+{
>>>>+    u32 cache_id = 0;
>>>>+
>>>>+#if defined(CONFIG_CPU_V7)
>>>>+    u32 sel = 0;
>>>>+
>>>>+    asm volatile ("mcr p15, 2, %[ccselr], c0, c0, 0\n\t"
>>>>+              "isb\n\t"
>>>>+              "mrc p15, 1, %[ccsidr], c0, c0, 0\n\t"
>>>>+              :[ccsidr]"=r" (cache_id)
>>>>+              :[ccselr]"r" (sel)
>>>>+             );
>>>>+    return cache_id & BIT(30);
>>>>+#elif defined(CONFIG_ARM64)
>>>>+    u32 sel = 0;
>>>>+    asm volatile("msr csselr_el1, %[ccselr]\n\t"
>>>>+             "isb\n\t"
>>>>+             "mrs %[ccsidr],ccsidr_el1\n\t"
>>>>+             :[ccsidr]"=r" (cache_id)
>>>>+             :[ccselr]"r" (sel)
>>>>+            );
>>>>+    return cache_id & BIT(30);
>>>>+#else
>>>>+#error No valid CPU arch selected
>>>>+#endif
>>>>+}
>>>>+
>>>>+static inline void msm_arch_idle(void)
>>>>+{
>>>>+    /* Flush and clock-gate */
>>>>+    mb();
>>>
>>>Why is needed this memory barrier ?
>>Some QCOM SoCs needed this. I am not sure which one anymore. :(
>
>I guess this is to flush the L1 cache when the core is going down. 
>Regarding the kernel option, it seems mb() is as dsb(), so I am 
>wondering if this function could be simply replaced by cpu_do_idle().
>
Possibly could. I will do that.

>>>>+    wfi();
>>>>+}
>>>>+
>>>>+static bool msm_pm_swfi(bool from_idle)
>>>>+{
>>>>+    msm_arch_idle();
>>>>+    return true;
>>>>+}
>
>Same here, could be replaced by cpu_do_idle(), I think.
>
>>>>+static bool msm_pm_retention(bool from_idle)
>>>>+{
>>>>+    int ret = 0;
>>>>+
>>>>+    ret = msm_spm_set_low_power_mode(MSM_SPM_MODE_RETENTION, false);
>>>>+    WARN_ON(ret);
>>>>+
>>>>+    msm_arch_idle();
>>>>+
>>>>+    ret = msm_spm_set_low_power_mode(MSM_SPM_MODE_CLOCK_GATING, false);
>>>>+    WARN_ON(ret);
>>>
>>>Why do you need to set the clock gating mode each time you exit the
>>>retention mode ?
>>So if the SPM did not reset to clockgating, we would not do retention
>>when we intended to do clockgating. Btw, we dont set clockgating
>>everytime we do clockgating, helps reduce the latency in doing WFI.
>
>Thanks for the explanation in the other email. So IIUC, the SCM keeps 
>the last state configuration and we have to set it back to clock 
>gating, right ?
Correct.
>
>I don't think it is up to this function to do this but the clock 
>gating function.
>
>Also, this function prototype looks a bit weird. Just for the sake of 
>using callbacks.
>
>And finally, the WARN_ON is not desirable here, except if the goal is 
>to flood the terminal :)
Was debating the use of it myself. Will remove it.
>
>What not using first simple functions ?
>
>void qcom_do_idle(void)
>{
>	myfirmware_call(MSM_SPM_MODE_CLOCK_GATING);
>	wfi();
>}
>
>void qcom_cpu_retention(void)
>{
>	myfirmware_call(MSM_SPM_MODE_RETENTION);
>	dsb();
>	wfi();
>}
>
>void qcom_cpu_powerdown(int flags)
>{
>	scm_call_atomic1(SCM_SVC_BOOT, SCM_CMD_TERMINATE_PC, flag);
>}
>
>and then you build on top of that the cpuidle driver.
Okay. Will do this
>
>The patchset adds all the features in one shot and for someone not 
>used with the platform it is really hard to follow all the code.
>
>I suggest you write a simple cpuidle driver based on the DT Lorenzo 
>patches bringing the clock gating, then another patchset with the 
>retention mode, etc ...
>
>>>>+    return true;
>>>>+}
>>>>+
>>>>+static int msm_pm_collapse(unsigned long from_idle)
>>>>+{
>>>>+    enum msm_pm_l2_scm_flag flag = MSM_SCM_L2_ON;
>>>>+
>>>>+    /**
>>>>+     * Single core processors need to have L2
>>>>+     * flushed when powering down the core.
>>>>+     * Notify SCM to flush secure L2 lines.
>>>>+     */
>>>>+    if (num_possible_cpus() == 1)
>>>>+        flag = MSM_SCM_L2_OFF;
>>>
>>>I am wondering if this shouldn't be handle by a mcpm driver.
>>>
>>>Cc nico.
>>
>>Well, possibly, sorry, not sure what features of the mcpm driver you
>>think I need here?
>
>Please correct me if I am wrong. IIUC, this function is checking the
>number of the cpus of the cluster in order to flush the L2 cache
>because the SCM will power down the cluster if it is the last one,
>right ?
Nope. Some QCOM variants which have a single CPU, cannot be powered down
without flushing the caches. Warm boot of the cpu resets the L2
logic as well. The cluster core is lot more complex than this :)

>
>
>-- 
> <http://www.linaro.org/> Linaro.org │ Open source software for ARM SoCs
>
>Follow Linaro:  <http://www.facebook.com/pages/Linaro> Facebook |
><http://twitter.com/#!/linaroorg> Twitter |
><http://www.linaro.org/linaro-blog/> Blog
>

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

* Re: [PATCH v2 07/10] qcom: msm-pm: Add cpu low power mode functions
  2014-08-14 19:22         ` Lina Iyer
@ 2014-08-15  0:01           ` Daniel Lezcano
  2014-08-15  1:02             ` Lina Iyer
  0 siblings, 1 reply; 46+ messages in thread
From: Daniel Lezcano @ 2014-08-15  0:01 UTC (permalink / raw)
  To: Lina Iyer
  Cc: khilman, amit.kucheria, sboyd, davidb, galak, linux-arm-msm,
	msivasub, Venkat Devarasetty, Nicolas Pitre

On 08/14/2014 09:22 PM, Lina Iyer wrote:
> On Thu, Aug 14, 2014 at 06:11:43PM +0200, Daniel Lezcano wrote:
>> On 08/13/2014 04:16 PM, Lina Iyer wrote:
>>> On Wed, Aug 13, 2014 at 01:18:01PM +0200, Daniel Lezcano wrote:
>>>> On 08/12/2014 09:43 PM, Lina Iyer wrote:
>>>>> Add interface layer to abstract and handle hardware specific
>>>>> functionality for executing various cpu low power modes in QCOM
>>>>> chipsets.
>>>>>
>>>>> Signed-off-by: Venkat Devarasetty <vdevaras@codeaurora.org>
>>>>> Signed-off-by: Mahesh Sivasubramanian <msivasub@codeaurora.org>
>>>>> Signed-off-by: Lina Iyer <lina.iyer@linaro.org>
>>>>> ---

[ ... ]

>>>>> +static bool msm_pm_retention(bool from_idle)
>>>>> +{
>>>>> +    int ret = 0;
>>>>> +
>>>>> +    ret = msm_spm_set_low_power_mode(MSM_SPM_MODE_RETENTION, false);
>>>>> +    WARN_ON(ret);
>>>>> +
>>>>> +    msm_arch_idle();
>>>>> +
>>>>> +    ret = msm_spm_set_low_power_mode(MSM_SPM_MODE_CLOCK_GATING,
>>>>> false);
>>>>> +    WARN_ON(ret);
>>>>
>>>> Why do you need to set the clock gating mode each time you exit the
>>>> retention mode ?
>>> So if the SPM did not reset to clockgating, we would not do retention
>>> when we intended to do clockgating. Btw, we dont set clockgating
>>> everytime we do clockgating, helps reduce the latency in doing WFI.
>>
>> Thanks for the explanation in the other email. So IIUC, the SCM keeps
>> the last state configuration and we have to set it back to clock
>> gating, right ?
> Correct.
>>
>> I don't think it is up to this function to do this but the clock
>> gating function.
>>
>> Also, this function prototype looks a bit weird. Just for the sake of
>> using callbacks.
>>
>> And finally, the WARN_ON is not desirable here, except if the goal is
>> to flood the terminal :)
> Was debating the use of it myself. Will remove it.
>>
>> What not using first simple functions ?
>>
>> void qcom_do_idle(void)
>> {
>>     myfirmware_call(MSM_SPM_MODE_CLOCK_GATING);
>>     wfi();
>> }
>>
>> void qcom_cpu_retention(void)
>> {
>>     myfirmware_call(MSM_SPM_MODE_RETENTION);
>>     dsb();
>>     wfi();
>> }
>>
>> void qcom_cpu_powerdown(int flags)
>> {
>>     scm_call_atomic1(SCM_SVC_BOOT, SCM_CMD_TERMINATE_PC, flag);
>> }
>>
>> and then you build on top of that the cpuidle driver.
> Okay. Will do this
>>
>> The patchset adds all the features in one shot and for someone not
>> used with the platform it is really hard to follow all the code.
>>
>> I suggest you write a simple cpuidle driver based on the DT Lorenzo
>> patches bringing the clock gating, then another patchset with the
>> retention mode, etc ...
 >>

Do you agree with this approach ?

>>>>> +    return true;
>>>>> +}
>>>>> +
>>>>> +static int msm_pm_collapse(unsigned long from_idle)
>>>>> +{
>>>>> +    enum msm_pm_l2_scm_flag flag = MSM_SCM_L2_ON;
>>>>> +
>>>>> +    /**
>>>>> +     * Single core processors need to have L2
>>>>> +     * flushed when powering down the core.
>>>>> +     * Notify SCM to flush secure L2 lines.
>>>>> +     */
>>>>> +    if (num_possible_cpus() == 1)
>>>>> +        flag = MSM_SCM_L2_OFF;
>>>>
>>>> I am wondering if this shouldn't be handle by a mcpm driver.
>>>>
>>>> Cc nico.
>>>
>>> Well, possibly, sorry, not sure what features of the mcpm driver you
>>> think I need here?
>>
>> Please correct me if I am wrong. IIUC, this function is checking the
>> number of the cpus of the cluster in order to flush the L2 cache
>> because the SCM will power down the cluster if it is the last one,
>> right ?
> Nope. Some QCOM variants which have a single CPU, cannot be powered down
> without flushing the caches. Warm boot of the cpu resets the L2
> logic as well. The cluster core is lot more complex than this :)

Ok, probably we can discuss this later when we reach this state in the 
incremental implementation.

Thanks

   -- Daniel

>> --
>> <http://www.linaro.org/> Linaro.org │ Open source software for ARM SoCs
>>
>> Follow Linaro:  <http://www.facebook.com/pages/Linaro> Facebook |
>> <http://twitter.com/#!/linaroorg> Twitter |
>> <http://www.linaro.org/linaro-blog/> Blog
>>


-- 
  <http://www.linaro.org/> Linaro.org │ Open source software for ARM SoCs

Follow Linaro:  <http://www.facebook.com/pages/Linaro> Facebook |
<http://twitter.com/#!/linaroorg> Twitter |
<http://www.linaro.org/linaro-blog/> Blog

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

* Re: [PATCH v2 07/10] qcom: msm-pm: Add cpu low power mode functions
  2014-08-15  0:01           ` Daniel Lezcano
@ 2014-08-15  1:02             ` Lina Iyer
  0 siblings, 0 replies; 46+ messages in thread
From: Lina Iyer @ 2014-08-15  1:02 UTC (permalink / raw)
  To: Daniel Lezcano
  Cc: khilman, amit.kucheria, sboyd, davidb, galak, linux-arm-msm,
	msivasub, Venkat Devarasetty, Nicolas Pitre

On Fri, Aug 15, 2014 at 02:01:47AM +0200, Daniel Lezcano wrote:
>On 08/14/2014 09:22 PM, Lina Iyer wrote:
>>On Thu, Aug 14, 2014 at 06:11:43PM +0200, Daniel Lezcano wrote:
>>>On 08/13/2014 04:16 PM, Lina Iyer wrote:
>>>>On Wed, Aug 13, 2014 at 01:18:01PM +0200, Daniel Lezcano wrote:
>>>>>On 08/12/2014 09:43 PM, Lina Iyer wrote:
>>>>>>Add interface layer to abstract and handle hardware specific
>>>>>>functionality for executing various cpu low power modes in QCOM
>>>>>>chipsets.
>>>>>>
>>>>>>Signed-off-by: Venkat Devarasetty <vdevaras@codeaurora.org>
>>>>>>Signed-off-by: Mahesh Sivasubramanian <msivasub@codeaurora.org>
>>>>>>Signed-off-by: Lina Iyer <lina.iyer@linaro.org>
>>>>>>---
>
>[ ... ]
>
>>>>>>+static bool msm_pm_retention(bool from_idle)
>>>>>>+{
>>>>>>+    int ret = 0;
>>>>>>+
>>>>>>+    ret = msm_spm_set_low_power_mode(MSM_SPM_MODE_RETENTION, false);
>>>>>>+    WARN_ON(ret);
>>>>>>+
>>>>>>+    msm_arch_idle();
>>>>>>+
>>>>>>+    ret = msm_spm_set_low_power_mode(MSM_SPM_MODE_CLOCK_GATING,
>>>>>>false);
>>>>>>+    WARN_ON(ret);
>>>>>
>>>>>Why do you need to set the clock gating mode each time you exit the
>>>>>retention mode ?
>>>>So if the SPM did not reset to clockgating, we would not do retention
>>>>when we intended to do clockgating. Btw, we dont set clockgating
>>>>everytime we do clockgating, helps reduce the latency in doing WFI.
>>>
>>>Thanks for the explanation in the other email. So IIUC, the SCM keeps
>>>the last state configuration and we have to set it back to clock
>>>gating, right ?
>>Correct.
>>>
>>>I don't think it is up to this function to do this but the clock
>>>gating function.
>>>
>>>Also, this function prototype looks a bit weird. Just for the sake of
>>>using callbacks.
>>>
>>>And finally, the WARN_ON is not desirable here, except if the goal is
>>>to flood the terminal :)
>>Was debating the use of it myself. Will remove it.
>>>
>>>What not using first simple functions ?
>>>
>>>void qcom_do_idle(void)
>>>{
>>>    myfirmware_call(MSM_SPM_MODE_CLOCK_GATING);
>>>    wfi();
>>>}
>>>
>>>void qcom_cpu_retention(void)
>>>{
>>>    myfirmware_call(MSM_SPM_MODE_RETENTION);
>>>    dsb();
>>>    wfi();
>>>}
>>>
>>>void qcom_cpu_powerdown(int flags)
>>>{
>>>    scm_call_atomic1(SCM_SVC_BOOT, SCM_CMD_TERMINATE_PC, flag);
>>>}
>>>
>>>and then you build on top of that the cpuidle driver.
>>Okay. Will do this
>>>
>>>The patchset adds all the features in one shot and for someone not
>>>used with the platform it is really hard to follow all the code.
>>>
>>>I suggest you write a simple cpuidle driver based on the DT Lorenzo
>>>patches bringing the clock gating, then another patchset with the
>>>retention mode, etc ...
>>>
>
>Do you agree with this approach ?
Yes, have a working patch with the code cleaned out as you suggested.
Looking at Lorenzo's DT changes and a few SPM driver clean ups. Will
submit a patch revision soon.

>
>>>>>>+    return true;
>>>>>>+}
>>>>>>+
>>>>>>+static int msm_pm_collapse(unsigned long from_idle)
>>>>>>+{
>>>>>>+    enum msm_pm_l2_scm_flag flag = MSM_SCM_L2_ON;
>>>>>>+
>>>>>>+    /**
>>>>>>+     * Single core processors need to have L2
>>>>>>+     * flushed when powering down the core.
>>>>>>+     * Notify SCM to flush secure L2 lines.
>>>>>>+     */
>>>>>>+    if (num_possible_cpus() == 1)
>>>>>>+        flag = MSM_SCM_L2_OFF;
>>>>>
>>>>>I am wondering if this shouldn't be handle by a mcpm driver.
>>>>>
>>>>>Cc nico.
>>>>
>>>>Well, possibly, sorry, not sure what features of the mcpm driver you
>>>>think I need here?
>>>
>>>Please correct me if I am wrong. IIUC, this function is checking the
>>>number of the cpus of the cluster in order to flush the L2 cache
>>>because the SCM will power down the cluster if it is the last one,
>>>right ?
>>Nope. Some QCOM variants which have a single CPU, cannot be powered down
>>without flushing the caches. Warm boot of the cpu resets the L2
>>logic as well. The cluster core is lot more complex than this :)
>
>Ok, probably we can discuss this later when we reach this state in the 
>incremental implementation.
Hmm, yeah.

>
>Thanks
>
>  -- Daniel
>
>>>--
>>><http://www.linaro.org/> Linaro.org │ Open source software for ARM SoCs
>>>
>>>Follow Linaro:  <http://www.facebook.com/pages/Linaro> Facebook |
>>><http://twitter.com/#!/linaroorg> Twitter |
>>><http://www.linaro.org/linaro-blog/> Blog
>>>
>
>
>-- 
> <http://www.linaro.org/> Linaro.org │ Open source software for ARM SoCs
>
>Follow Linaro:  <http://www.facebook.com/pages/Linaro> Facebook |
><http://twitter.com/#!/linaroorg> Twitter |
><http://www.linaro.org/linaro-blog/> Blog
>

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

* Re: [PATCH v2 03/10] qcom: spm: Add Subsystem Power Manager (SPM) driver for QCOM chipsets
  2014-08-14 16:41       ` Kumar Gala
@ 2014-08-15  4:18         ` Lina Iyer
  2014-08-15 13:42           ` Kumar Gala
  0 siblings, 1 reply; 46+ messages in thread
From: Lina Iyer @ 2014-08-15  4:18 UTC (permalink / raw)
  To: Kumar Gala
  Cc: Daniel Lezcano, Kevin Hilman, Amit Kucheria, Stephen Boyd,
	David Brown, linux-arm-msm, msivasub, Praveen Chidamabram,
	Murali Nalajala

On Thu, Aug 14, 2014 at 11:41:39AM -0500, Kumar Gala wrote:
>
>On Aug 14, 2014, at 11:18 AM, Lina Iyer <lina.iyer@linaro.org> wrote:
>
>> On Thu, Aug 14, 2014 at 11:09:48AM -0500, Kumar Gala wrote:
>>>
>>> On Aug 12, 2014, at 2:43 PM, Lina Iyer <lina.iyer@linaro.org> wrote:
>>>
>>>>
>>>> diff --git a/Documentation/devicetree/bindings/arm/msm/spm-v2.txt b/Documentation/devicetree/bindings/arm/msm/spm-v2.txt
>>>> new file mode 100644
>>>> index 0000000..3130f4b
>>>> --- /dev/null
>>>> +++ b/Documentation/devicetree/bindings/arm/msm/spm-v2.txt
>>>> @@ -0,0 +1,62 @@
>>>> +* MSM Subsystem Power Manager (spm-v2)
>>>> +
>>>> +S4 generation of MSMs have SPM hardware blocks to control the Application
>>>> +Processor Sub-System power. These SPM blocks run individual state machine
>>>> +to determine what the core (L2 or Krait/Scorpion) would do when the WFI
>>>> +instruction is executed by the core.
>>>> +
>>>> +The devicetree representation of the SPM block should be:
>>>> +
>>>> +Required properties
>>>> +
>>>> +- compatible: Could be one of -
>>>> +		"qcom,spm-v2.1"
>>>> +		"qcom,spm-v3.0"
>>>> +- reg: The physical address and the size of the SPM's memory mapped registers
>>>> +- qcom,cpu: phandle for the CPU that the SPM block is attached to. On targets
>>>> +	that dont support CPU phandles the driver would support qcom,core-id.
>>>> +	This field is required on only for SPMs that control the CPU.
>>>> +- qcom,saw2-cfg: SAW2 configuration register
>>>
>>> Can we change this to qcom,saw2-clk-div as that is what is really getting set, I know there are a few other fields in the saw2-cfg register, but I’m pretty sure we arent ever really setting those from DT.
>>>
>> I am pruning them off in the next revision of the patch.
>>>> +- qcom,saw2-spm-dly: Provides the values for the SPM delay command in the SPM
>>>> +	sequence
>>>> +- qcom,saw2-spm-ctl: The SPM control register
>>>
>>> Can we describe this as “spm-enable”, “spm-inhibit-start-address”, “spm-wakeup-cfg”?
>>>
>>> Also, I’m unclear why would we have a case that spm would be disabled?
SPM would mostly be disabled for debug reasons or if there was a version
of the hardware tht needed hardware to be disabled. In general, it
wouldnt be.
>>>
>> Much of these registers names make it easier for developers and
>> debuggers to relate it to the hardware spec. Choosing different names
>> here though might make it readable would convolute their efforts.
>
>The point is to move away from just dumping a register value directly from DT into the device.  This is pretty bad form.  The names can relate to the register, etc, its just the fields that are really being used/set was the direction I was suggesting we go.
>
Hmm. I see. Let me if I can address that.. There may be some registers
where I may not have such a luxury, will give it a try.

>>
>>>> +- qcom,name: The name with which a SPM device is identified by the power
>>>> +	management code.
>>>> +
>>>> +Optional properties
>>>> +
>>>> +- qcom,saw2-pmic-data0..7: Specify the pmic data value and the associated FTS
>>>> +	(Fast Transient Switch) index to send the PMIC data to
>>>> +- qcom,vctl-port: The PVC (PMIC Virtual Channel) port used for changing
>>>> +	voltage
>>>> +- qcom,phase-port: The PVC port used for changing the number of phases
>>>> +- qcom,pfm-port: The PVC port used for enabling PWM/PFM modes
>>>> +- qcom,saw2-spm-cmd-wfi: The WFI command sequence
>>>> +- qcom,saw2-spm-cmd-ret: The Retention command sequence
>>>> +- qcom,saw2-spm-cmd-spc: The Standalone PC command sequence
>>>> +- qcom,saw2-spm-cmd-pc-no-rpm: The Power Collapse command sequence where APPS
>>>> +	proc won't inform the RPM.
>>>> +- qcom,saw2-spm-cmd-pc: The Power Collapse command sequence. This sequence may
>>>> +	turn off other SoC components.
>>>> +- qcom,saw2-spm-cmd-gdhs: GDHS (Globally Distributed Head Switch) command
>>>> +	sequence. This sequence will retain the memory but turn off the logic.
>>>> +- qcom,cpu-vctl-list: List of cpu node phandles, whose voltage the spm device
>>>> +	can control.
>>>> +- qcom,vctl-timeout-us: The timeout value in microseconds to wait for voltage to
>>>> +	change after sending the voltage command to the PMIC.
>>>> +-
>>>> +Example:
>>>> +	qcom,spm@f9089000 {
>>>> +		compatible = "qcom,spm-v2";
>>>> +		#address-cells = <1>;
>>>> +		#size-cells = <1>;
>>>> +		reg = <0xf9089000 0x1000>;
>>>> +		qcom,cpu = <&CPU0>;
>>>> +		qcom,saw2-cfg = <0x1>;
>>>> +		qcom,saw2-spm-dly= <0x20000400>;
>>>> +		qcom,saw2-spm-ctl = <0x1>;
>>>> +		qcom,saw2-spm-cmd-wfi = [03 0b 0f];
>>>> +		qcom,saw2-spm-cmd-spc = [00 20 50 80 60 70 10 92
>>>> +				a0 b0 03 68 70 3b 92 a0 b0
>>>> +				82 2b 50 10 30 02 22 30 0f];
>>>> +	};
>>>
>>> --
>>> Employee of Qualcomm Innovation Center, Inc.
>>> Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum, hosted by The Linux Foundation
>>>
>
>- k
>
>-- 
>Employee of Qualcomm Innovation Center, Inc.
>Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum, hosted by The Linux Foundation
>

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

* Re: [PATCH v2 03/10] qcom: spm: Add Subsystem Power Manager (SPM) driver for QCOM chipsets
  2014-08-15  4:18         ` Lina Iyer
@ 2014-08-15 13:42           ` Kumar Gala
  2014-08-16  3:41             ` Lina Iyer
  0 siblings, 1 reply; 46+ messages in thread
From: Kumar Gala @ 2014-08-15 13:42 UTC (permalink / raw)
  To: Lina Iyer
  Cc: Daniel Lezcano, Kevin Hilman, Amit Kucheria, Stephen Boyd,
	David Brown, linux-arm-msm, msivasub, Praveen Chidamabram,
	Murali Nalajala


On Aug 14, 2014, at 11:18 PM, Lina Iyer <lina.iyer@linaro.org> wrote:

> On Thu, Aug 14, 2014 at 11:41:39AM -0500, Kumar Gala wrote:
>> 
>> On Aug 14, 2014, at 11:18 AM, Lina Iyer <lina.iyer@linaro.org> wrote:
>> 
>>> On Thu, Aug 14, 2014 at 11:09:48AM -0500, Kumar Gala wrote:
>>>> 
>>>> On Aug 12, 2014, at 2:43 PM, Lina Iyer <lina.iyer@linaro.org> wrote:
>>>> 
>>>>> 
>>>>> diff --git a/Documentation/devicetree/bindings/arm/msm/spm-v2.txt b/Documentation/devicetree/bindings/arm/msm/spm-v2.txt
>>>>> new file mode 100644
>>>>> index 0000000..3130f4b
>>>>> --- /dev/null
>>>>> +++ b/Documentation/devicetree/bindings/arm/msm/spm-v2.txt
>>>>> @@ -0,0 +1,62 @@
>>>>> +* MSM Subsystem Power Manager (spm-v2)
>>>>> +
>>>>> +S4 generation of MSMs have SPM hardware blocks to control the Application
>>>>> +Processor Sub-System power. These SPM blocks run individual state machine
>>>>> +to determine what the core (L2 or Krait/Scorpion) would do when the WFI
>>>>> +instruction is executed by the core.
>>>>> +
>>>>> +The devicetree representation of the SPM block should be:
>>>>> +
>>>>> +Required properties
>>>>> +
>>>>> +- compatible: Could be one of -
>>>>> +		"qcom,spm-v2.1"
>>>>> +		"qcom,spm-v3.0"
>>>>> +- reg: The physical address and the size of the SPM's memory mapped registers
>>>>> +- qcom,cpu: phandle for the CPU that the SPM block is attached to. On targets
>>>>> +	that dont support CPU phandles the driver would support qcom,core-id.
>>>>> +	This field is required on only for SPMs that control the CPU.
>>>>> +- qcom,saw2-cfg: SAW2 configuration register
>>>> 
>>>> Can we change this to qcom,saw2-clk-div as that is what is really getting set, I know there are a few other fields in the saw2-cfg register, but I’m pretty sure we arent ever really setting those from DT.
>>>> 
>>> I am pruning them off in the next revision of the patch.
>>>>> +- qcom,saw2-spm-dly: Provides the values for the SPM delay command in the SPM
>>>>> +	sequence
>>>>> +- qcom,saw2-spm-ctl: The SPM control register
>>>> 
>>>> Can we describe this as “spm-enable”, “spm-inhibit-start-address”, “spm-wakeup-cfg”?
>>>> 
>>>> Also, I’m unclear why would we have a case that spm would be disabled?
> SPM would mostly be disabled for debug reasons or if there was a version
> of the hardware tht needed hardware to be disabled. In general, it
> wouldnt be.

Why would we not do that via the “status” field?

>>>> 
>>> Much of these registers names make it easier for developers and
>>> debuggers to relate it to the hardware spec. Choosing different names
>>> here though might make it readable would convolute their efforts.
>> 
>> The point is to move away from just dumping a register value directly from DT into the device.  This is pretty bad form.  The names can relate to the register, etc, its just the fields that are really being used/set was the direction I was suggesting we go.
>> 
> Hmm. I see. Let me if I can address that.. There may be some registers
> where I may not have such a luxury, will give it a try.

Lets see, some registers are possibly ok, so lets try as much as possibly and go from there.

> 
>>> 
>>>>> +- qcom,name: The name with which a SPM device is identified by the power
>>>>> +	management code.
>>>>> +
>>>>> +Optional properties
>>>>> +
>>>>> +- qcom,saw2-pmic-data0..7: Specify the pmic data value and the associated FTS
>>>>> +	(Fast Transient Switch) index to send the PMIC data to
>>>>> +- qcom,vctl-port: The PVC (PMIC Virtual Channel) port used for changing
>>>>> +	voltage
>>>>> +- qcom,phase-port: The PVC port used for changing the number of phases
>>>>> +- qcom,pfm-port: The PVC port used for enabling PWM/PFM modes
>>>>> +- qcom,saw2-spm-cmd-wfi: The WFI command sequence
>>>>> +- qcom,saw2-spm-cmd-ret: The Retention command sequence
>>>>> +- qcom,saw2-spm-cmd-spc: The Standalone PC command sequence
>>>>> +- qcom,saw2-spm-cmd-pc-no-rpm: The Power Collapse command sequence where APPS
>>>>> +	proc won't inform the RPM.
>>>>> +- qcom,saw2-spm-cmd-pc: The Power Collapse command sequence. This sequence may
>>>>> +	turn off other SoC components.
>>>>> +- qcom,saw2-spm-cmd-gdhs: GDHS (Globally Distributed Head Switch) command
>>>>> +	sequence. This sequence will retain the memory but turn off the logic.
>>>>> +- qcom,cpu-vctl-list: List of cpu node phandles, whose voltage the spm device
>>>>> +	can control.
>>>>> +- qcom,vctl-timeout-us: The timeout value in microseconds to wait for voltage to
>>>>> +	change after sending the voltage command to the PMIC.
>>>>> +-
>>>>> +Example:
>>>>> +	qcom,spm@f9089000 {
>>>>> +		compatible = "qcom,spm-v2";
>>>>> +		#address-cells = <1>;
>>>>> +		#size-cells = <1>;
>>>>> +		reg = <0xf9089000 0x1000>;
>>>>> +		qcom,cpu = <&CPU0>;
>>>>> +		qcom,saw2-cfg = <0x1>;
>>>>> +		qcom,saw2-spm-dly= <0x20000400>;
>>>>> +		qcom,saw2-spm-ctl = <0x1>;
>>>>> +		qcom,saw2-spm-cmd-wfi = [03 0b 0f];
>>>>> +		qcom,saw2-spm-cmd-spc = [00 20 50 80 60 70 10 92
>>>>> +				a0 b0 03 68 70 3b 92 a0 b0
>>>>> +				82 2b 50 10 30 02 22 30 0f];
>>>>> +	};
>>>> 
>>>> --
>>>> Employee of Qualcomm Innovation Center, Inc.
>>>> Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum, hosted by The Linux Foundation
>>>> 
>> 
>> - k
>> 
>> -- 
>> Employee of Qualcomm Innovation Center, Inc.
>> Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum, hosted by The Linux Foundation
>> 
> --
> To unsubscribe from this list: send the line "unsubscribe linux-arm-msm" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html

-- 
Employee of Qualcomm Innovation Center, Inc.
Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum, hosted by The Linux Foundation

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

* Re: [PATCH v2 03/10] qcom: spm: Add Subsystem Power Manager (SPM) driver for QCOM chipsets
  2014-08-15 13:42           ` Kumar Gala
@ 2014-08-16  3:41             ` Lina Iyer
  0 siblings, 0 replies; 46+ messages in thread
From: Lina Iyer @ 2014-08-16  3:41 UTC (permalink / raw)
  To: Kumar Gala
  Cc: Daniel Lezcano, Kevin Hilman, Amit Kucheria, Stephen Boyd,
	David Brown, linux-arm-msm, msivasub, Praveen Chidamabram,
	Murali Nalajala

On Fri, Aug 15, 2014 at 08:42:17AM -0500, Kumar Gala wrote:
>
>On Aug 14, 2014, at 11:18 PM, Lina Iyer <lina.iyer@linaro.org> wrote:
>
>> On Thu, Aug 14, 2014 at 11:41:39AM -0500, Kumar Gala wrote:
>>>
>>> On Aug 14, 2014, at 11:18 AM, Lina Iyer <lina.iyer@linaro.org> wrote:
>>>
>>>> On Thu, Aug 14, 2014 at 11:09:48AM -0500, Kumar Gala wrote:
>>>>>
>>>>> On Aug 12, 2014, at 2:43 PM, Lina Iyer <lina.iyer@linaro.org> wrote:
>>>>>
>>>>>>
>>>>>> diff --git a/Documentation/devicetree/bindings/arm/msm/spm-v2.txt b/Documentation/devicetree/bindings/arm/msm/spm-v2.txt
>>>>>> new file mode 100644
>>>>>> index 0000000..3130f4b
>>>>>> --- /dev/null
>>>>>> +++ b/Documentation/devicetree/bindings/arm/msm/spm-v2.txt
>>>>>> @@ -0,0 +1,62 @@
>>>>>> +* MSM Subsystem Power Manager (spm-v2)
>>>>>> +
>>>>>> +S4 generation of MSMs have SPM hardware blocks to control the Application
>>>>>> +Processor Sub-System power. These SPM blocks run individual state machine
>>>>>> +to determine what the core (L2 or Krait/Scorpion) would do when the WFI
>>>>>> +instruction is executed by the core.
>>>>>> +
>>>>>> +The devicetree representation of the SPM block should be:
>>>>>> +
>>>>>> +Required properties
>>>>>> +
>>>>>> +- compatible: Could be one of -
>>>>>> +		"qcom,spm-v2.1"
>>>>>> +		"qcom,spm-v3.0"
>>>>>> +- reg: The physical address and the size of the SPM's memory mapped registers
>>>>>> +- qcom,cpu: phandle for the CPU that the SPM block is attached to. On targets
>>>>>> +	that dont support CPU phandles the driver would support qcom,core-id.
>>>>>> +	This field is required on only for SPMs that control the CPU.
>>>>>> +- qcom,saw2-cfg: SAW2 configuration register
>>>>>
>>>>> Can we change this to qcom,saw2-clk-div as that is what is really getting set, I know there are a few other fields in the saw2-cfg register, but I’m pretty sure we arent ever really setting those from DT.
>>>>>
>>>> I am pruning them off in the next revision of the patch.
>>>>>> +- qcom,saw2-spm-dly: Provides the values for the SPM delay command in the SPM
>>>>>> +	sequence
>>>>>> +- qcom,saw2-spm-ctl: The SPM control register
>>>>>
>>>>> Can we describe this as “spm-enable”, “spm-inhibit-start-address”, “spm-wakeup-cfg”?
>>>>>
>>>>> Also, I’m unclear why would we have a case that spm would be disabled?
>> SPM would mostly be disabled for debug reasons or if there was a version
>> of the hardware tht needed hardware to be disabled. In general, it
>> wouldnt be.
>
>Why would we not do that via the “status” field?
>
We could do that instead too.
>>>>>
>>>> Much of these registers names make it easier for developers and
>>>> debuggers to relate it to the hardware spec. Choosing different names
>>>> here though might make it readable would convolute their efforts.
>>>
>>> The point is to move away from just dumping a register value directly from DT into the device.  This is pretty bad form.  The names can relate to the register, etc, its just the fields that are really being used/set was the direction I was suggesting we go.
>>>
>> Hmm. I see. Let me if I can address that.. There may be some registers
>> where I may not have such a luxury, will give it a try.
>
>Lets see, some registers are possibly ok, so lets try as much as possibly and go from there.
Ok.
>
>>
>>>>
>>>>>> +- qcom,name: The name with which a SPM device is identified by the power
>>>>>> +	management code.
>>>>>> +
>>>>>> +Optional properties
>>>>>> +
>>>>>> +- qcom,saw2-pmic-data0..7: Specify the pmic data value and the associated FTS
>>>>>> +	(Fast Transient Switch) index to send the PMIC data to
>>>>>> +- qcom,vctl-port: The PVC (PMIC Virtual Channel) port used for changing
>>>>>> +	voltage
>>>>>> +- qcom,phase-port: The PVC port used for changing the number of phases
>>>>>> +- qcom,pfm-port: The PVC port used for enabling PWM/PFM modes
>>>>>> +- qcom,saw2-spm-cmd-wfi: The WFI command sequence
>>>>>> +- qcom,saw2-spm-cmd-ret: The Retention command sequence
>>>>>> +- qcom,saw2-spm-cmd-spc: The Standalone PC command sequence
>>>>>> +- qcom,saw2-spm-cmd-pc-no-rpm: The Power Collapse command sequence where APPS
>>>>>> +	proc won't inform the RPM.
>>>>>> +- qcom,saw2-spm-cmd-pc: The Power Collapse command sequence. This sequence may
>>>>>> +	turn off other SoC components.
>>>>>> +- qcom,saw2-spm-cmd-gdhs: GDHS (Globally Distributed Head Switch) command
>>>>>> +	sequence. This sequence will retain the memory but turn off the logic.
>>>>>> +- qcom,cpu-vctl-list: List of cpu node phandles, whose voltage the spm device
>>>>>> +	can control.
>>>>>> +- qcom,vctl-timeout-us: The timeout value in microseconds to wait for voltage to
>>>>>> +	change after sending the voltage command to the PMIC.
>>>>>> +-
>>>>>> +Example:
>>>>>> +	qcom,spm@f9089000 {
>>>>>> +		compatible = "qcom,spm-v2";
>>>>>> +		#address-cells = <1>;
>>>>>> +		#size-cells = <1>;
>>>>>> +		reg = <0xf9089000 0x1000>;
>>>>>> +		qcom,cpu = <&CPU0>;
>>>>>> +		qcom,saw2-cfg = <0x1>;
>>>>>> +		qcom,saw2-spm-dly= <0x20000400>;
>>>>>> +		qcom,saw2-spm-ctl = <0x1>;
>>>>>> +		qcom,saw2-spm-cmd-wfi = [03 0b 0f];
>>>>>> +		qcom,saw2-spm-cmd-spc = [00 20 50 80 60 70 10 92
>>>>>> +				a0 b0 03 68 70 3b 92 a0 b0
>>>>>> +				82 2b 50 10 30 02 22 30 0f];
>>>>>> +	};
>>>>>
>>>>> --
>>>>> Employee of Qualcomm Innovation Center, Inc.
>>>>> Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum, hosted by The Linux Foundation
>>>>>
>>>
>>> - k
>>>
>>> --
>>> Employee of Qualcomm Innovation Center, Inc.
>>> Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum, hosted by The Linux Foundation
>>>
>> --
>> To unsubscribe from this list: send the line "unsubscribe linux-arm-msm" in
>> the body of a message to majordomo@vger.kernel.org
>> More majordomo info at  http://vger.kernel.org/majordomo-info.html
>
>-- 
>Employee of Qualcomm Innovation Center, Inc.
>Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum, hosted by The Linux Foundation
>

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

end of thread, other threads:[~2014-08-16  3:41 UTC | newest]

Thread overview: 46+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2014-08-12 19:43 [PATCH v2 00/10] QCOM 8074 cpuidle driver Lina Iyer
2014-08-12 19:43 ` [PATCH v2 01/10] msm: scm: Move scm-boot files to drivers/soc and include/soc Lina Iyer
2014-08-12 19:43 ` [PATCH v2 02/10] msm: scm: Add SCM warmboot flags for quad core targets Lina Iyer
2014-08-14 10:20   ` Pramod Gurav
2014-08-12 19:43 ` [PATCH v2 03/10] qcom: spm: Add Subsystem Power Manager (SPM) driver for QCOM chipsets Lina Iyer
2014-08-13 10:49   ` Daniel Lezcano
2014-08-13 14:00     ` Lina Iyer
2014-08-14 13:01   ` Pramod Gurav
2014-08-14 15:18     ` Lina Iyer
2014-08-14 15:16   ` Kumar Gala
2014-08-14 15:27     ` Lina Iyer
2014-08-14 15:33       ` Kumar Gala
2014-08-14 16:09   ` Kumar Gala
2014-08-14 16:18     ` Lina Iyer
2014-08-14 16:41       ` Kumar Gala
2014-08-15  4:18         ` Lina Iyer
2014-08-15 13:42           ` Kumar Gala
2014-08-16  3:41             ` Lina Iyer
2014-08-12 19:43 ` [PATCH v2 04/10] soc: qcom: Add QCOM Power management config Lina Iyer
2014-08-13  9:36   ` Daniel Lezcano
2014-08-12 19:43 ` [PATCH v2 05/10] arm: qcom-msm8974: Add CPU phandles to CPU definitions Lina Iyer
2014-08-12 21:09   ` Kumar Gala
2014-08-14 10:04   ` Pramod Gurav
2014-08-12 19:43 ` [PATCH v2 06/10] arm: dts: qcom: Add SPM device bindings for 8974 Lina Iyer
2014-08-12 21:10   ` Kumar Gala
2014-08-12 21:32     ` Lina Iyer
2014-08-13  7:39   ` Ivan T. Ivanov
2014-08-12 19:43 ` [PATCH v2 07/10] qcom: msm-pm: Add cpu low power mode functions Lina Iyer
2014-08-13 11:18   ` Daniel Lezcano
2014-08-13 14:16     ` Lina Iyer
2014-08-14 14:24       ` Daniel Lezcano
2014-08-14 14:53         ` Lina Iyer
2014-08-14 16:11       ` Daniel Lezcano
2014-08-14 19:22         ` Lina Iyer
2014-08-15  0:01           ` Daniel Lezcano
2014-08-15  1:02             ` Lina Iyer
2014-08-14 13:38   ` Pramod Gurav
2014-08-14 14:43     ` Lina Iyer
2014-08-12 19:43 ` [PATCH v2 08/10] qcom: cpuidle: Add cpuidle driver for QCOM cpus Lina Iyer
2014-08-13 11:22   ` Daniel Lezcano
2014-08-13 14:03     ` Lina Iyer
2014-08-12 19:43 ` [PATCH v2 09/10] qcom: cpuidle: Config option to enable QCOM cpuidle driver Lina Iyer
2014-08-13 11:18   ` Daniel Lezcano
2014-08-12 19:44 ` [PATCH v2 10/10] qcom: cpuidle: Add cpuidle device nodes for 8974 chipset Lina Iyer
2014-08-13  1:52 ` [PATCH v2 00/10] QCOM 8074 cpuidle driver Stephen Boyd
2014-08-13  2:17   ` Lina Iyer

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.