[RFC,07/13] qcom: sleep-status: Add ability to recognize cpu power down state

Message ID 1407470722-23015-8-git-send-email-lina.iyer@linaro.org
State New
Headers show

Commit Message

Lina Iyer Aug. 8, 2014, 4:05 a.m.
QCOM processors get notified by the processor subsystem logic that
recognizes when a processor has entered the low power state. This is
used by PM to guarantee that the processor is indeed in its low
power state, before powering down the associated resources.

Signed-off-by: Mahesh Sivasubramanian <msivasub@codeaurora.org>
Signed-off-by: Lina Iyer <lina.iyer@linaro.org>
---
 .../bindings/arm/msm/qcom,cpu-sleep-status.txt     |  41 +++++
 drivers/soc/qcom/Makefile                          |   2 +-
 drivers/soc/qcom/sleep-status.c                    | 178 +++++++++++++++++++++
 include/soc/qcom/pm.h                              |   2 +
 4 files changed, 222 insertions(+), 1 deletion(-)
 create mode 100644 Documentation/devicetree/bindings/arm/msm/qcom,cpu-sleep-status.txt
 create mode 100644 drivers/soc/qcom/sleep-status.c

Patch

diff --git a/Documentation/devicetree/bindings/arm/msm/qcom,cpu-sleep-status.txt b/Documentation/devicetree/bindings/arm/msm/qcom,cpu-sleep-status.txt
new file mode 100644
index 0000000..3d2974e
--- /dev/null
+++ b/Documentation/devicetree/bindings/arm/msm/qcom,cpu-sleep-status.txt
@@ -0,0 +1,41 @@ 
+* MSM Sleep Status
+
+MSM Sleep status device is used to check the power collapsed status of a
+offlined core. The core that initiates the hotplug would wait on the
+sleep status device before CPU_DEAD notifications are sent out. Some hardware
+devices require that the offlined core is power collapsed before turning off
+the resources that are used by the offlined core.
+
+
+PROPERTIES
+
+- compatible:
+	Usage: required
+	Value type: <string>
+	Definition: Should be "qcom,cpu-sleep-status"
+
+- reg:
+	Usage: required
+	Value type: <register address>
+	Definition: The physical address of the sleep status register for
+	core0, the second element is the size of the register.
+
+- qcom,cpu-alias-addr:
+	Usage: required
+	Value type: <integer>
+	Definition: On MSM chipset, the each cores registers are at a
+	fixed offset each other.
+
+- qcom,cpu-sleep-status-mask:
+	Usage: required
+	Value type: <integer>
+	Definition: The bit mask within the status register that
+	indicates the Core's sleep state.
+
+Example:
+	qcom,cpu-sleep-status@f9088008 {
+		compatible = "qcom,cpu-sleep-status";
+		reg = <0xf9088008 0x4>;
+		qcom,cpu-alias-addr = <0x10000>;
+		qcom,sleep-status-mask = <0x80000>;
+	};
diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile
index 87c3b9704..d2cc9c0 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 msm-pm.o
+obj-$(CONFIG_QCOM_PM) +=	spm_devices.o spm.o msm-pm.o sleep-status.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/sleep-status.c b/drivers/soc/qcom/sleep-status.c
new file mode 100644
index 0000000..aa6340c
--- /dev/null
+++ b/drivers/soc/qcom/sleep-status.c
@@ -0,0 +1,178 @@ 
+/* 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/platform_device.h>
+#include <linux/of_platform.h>
+#include <linux/io.h>
+#include <linux/delay.h>
+
+#include <soc/qcom/spm.h>
+#include <soc/qcom/pm.h>
+
+struct msm_pm_sleep_status_data {
+	void *base_addr;
+	uint32_t cpu_offset;
+	uint32_t mask;
+};
+
+static struct msm_pm_sleep_status_data *msm_pm_slp_sts;
+
+/**
+ * msm_pm_wait_cpu_shutdown() - Wait for a core to be power collapsed during
+ *				hotplug
+ *
+ * @ cpu - cpu to wait on.
+ *
+ * Blocking function call that waits on the core to be power collapsed. This
+ * function is called from platform_cpu_die to ensure that a core is power
+ * collapsed before sending the CPU_DEAD notification so the drivers could
+ * remove the resource votes for this CPU(regulator and clock)
+ */
+int msm_pm_wait_cpu_shutdown(unsigned int cpu)
+{
+	int timeout = 0;
+
+	if (!msm_pm_slp_sts)
+		return 0;
+
+	if (!msm_pm_slp_sts[cpu].base_addr)
+		return 0;
+
+	while (1) {
+		/*
+		 * Check for the SPM of the core being hotplugged to set
+		 * its sleep state.The SPM sleep state indicates that the
+		 * core has been power collapsed.
+		 */
+		int acc_sts = __raw_readl(msm_pm_slp_sts[cpu].base_addr);
+
+		if (acc_sts & msm_pm_slp_sts[cpu].mask)
+			return 0;
+
+		udelay(100);
+		/*
+		 * Dump spm registers for debugging
+		 */
+		if (++timeout == 20) {
+			msm_spm_dump_regs(cpu);
+			__WARN_printf(
+			"CPU%u didn't collapse in 2ms, sleep status: 0x%x\n",
+					cpu, acc_sts);
+		}
+	}
+
+	return -EBUSY;
+}
+
+static int msm_cpu_status_probe(struct platform_device *pdev)
+{
+	struct msm_pm_sleep_status_data *pdata;
+	char *key;
+	u32 cpu;
+
+	if (!pdev)
+		return -EFAULT;
+
+	msm_pm_slp_sts = devm_kcalloc(&pdev->dev, num_possible_cpus(),
+					sizeof(*msm_pm_slp_sts), GFP_KERNEL);
+
+	if (!msm_pm_slp_sts)
+		return -ENOMEM;
+
+	if (pdev->dev.of_node) {
+		struct resource *res;
+		u32 offset;
+		int rc;
+		u32 mask;
+		bool offset_available = true;
+
+		res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+		if (!res)
+			return -ENODEV;
+
+		key = "qcom,cpu-alias-addr";
+		rc = of_property_read_u32(pdev->dev.of_node, key, &offset);
+
+		if (rc)
+			offset_available = false;
+
+		key = "qcom,sleep-status-mask";
+		rc = of_property_read_u32(pdev->dev.of_node, key, &mask);
+
+		if (rc)
+			return -ENODEV;
+
+		for_each_possible_cpu(cpu) {
+			phys_addr_t base_c;
+
+			if (offset_available)
+				base_c = res->start + cpu * offset;
+			else {
+				res = platform_get_resource(pdev,
+							IORESOURCE_MEM, cpu);
+				if (!res)
+					return -ENODEV;
+				base_c = res->start;
+			}
+
+			msm_pm_slp_sts[cpu].base_addr =
+				devm_ioremap(&pdev->dev, base_c,
+						resource_size(res));
+			msm_pm_slp_sts[cpu].mask = mask;
+
+			if (!msm_pm_slp_sts[cpu].base_addr)
+				return -ENOMEM;
+		}
+	} else {
+		pdata = pdev->dev.platform_data;
+		if (!pdev->dev.platform_data)
+			return -EINVAL;
+
+		for_each_possible_cpu(cpu) {
+			msm_pm_slp_sts[cpu].base_addr =
+				pdata->base_addr + cpu * pdata->cpu_offset;
+			msm_pm_slp_sts[cpu].mask = pdata->mask;
+		}
+	}
+
+	return 0;
+};
+
+static struct of_device_id msm_slp_sts_match_tbl[] = {
+	{.compatible = "qcom,cpu-sleep-status"},
+	{},
+};
+
+static struct platform_driver msm_cpu_status_driver = {
+	.probe = msm_cpu_status_probe,
+	.driver = {
+		.name = "qcom,cpu-sleep-status",
+		.owner = THIS_MODULE,
+		.of_match_table = msm_slp_sts_match_tbl,
+	},
+};
+
+int __init msm_pm_sleep_status_init(void)
+{
+	static bool registered;
+
+	if (registered)
+		return 0;
+	registered = true;
+
+	return platform_driver_register(&msm_cpu_status_driver);
+}
+arch_initcall(msm_pm_sleep_status_init);
diff --git a/include/soc/qcom/pm.h b/include/soc/qcom/pm.h
index ed6124a..3de04b8 100644
--- a/include/soc/qcom/pm.h
+++ b/include/soc/qcom/pm.h
@@ -32,12 +32,14 @@  enum msm_pm_l2_scm_flag {
 bool msm_cpu_pm_enter_sleep(enum msm_pm_sleep_mode mode, bool from_idle);
 int msm_pm_cpu_hotplug_enter(unsigned int cpu);
 int msm_pm_secondary_startup(unsigned int cpu);
+int msm_pm_wait_cpu_shutdown(unsigned int cpu);
 #else
 static inline bool msm_cpu_pm_enter_sleep(enum msm_pm_sleep_mode mode,
 						bool from_idle)
 { return true; }
 static inline int msm_pm_cpu_hotplug_enter(unsigned int cpu) { return 0; }
 static inline int msm_pm_secondary_startup(unsigned int cpu) { return 0; }
+static inline int msm_pm_wait_cpu_shutdown(unsigned int cpu) { return 0; }
 #endif
 
 #endif  /* __QCOM_PM_H */