msm: copper: Add Low Power Management drivers for CPUIdle

When the core is in idle, CPUIdle framework calls into the architecture
specific idle driver. The idle driver uses RPM resources (L2, VDD, XO)
and the time available to determine the lowest power mode possible and
uses the SPM driver to put the core in low power mode.

Add LPM levels that specify the latency and the energy required to enter
different low power modes. These values are used to determine how deep a
sleep can be achieved before the next scheduled wakeup.

Change-Id: Ia2ba25e618aa72cb02f49fbd0028a0615e465dc8
Signed-off-by: Praveen Chidambaram <pchidamb@codeaurora.org>
diff --git a/arch/arm/mach-msm/lpm_levels.c b/arch/arm/mach-msm/lpm_levels.c
new file mode 100644
index 0000000..48cf3f7
--- /dev/null
+++ b/arch/arm/mach-msm/lpm_levels.c
@@ -0,0 +1,232 @@
+/* Copyright (c) 2012, Code Aurora Forum. 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/slab.h>
+#include <linux/platform_device.h>
+#include <linux/of.h>
+#include <mach/mpm.h>
+#include "rpm_resources.h"
+
+static struct msm_rpmrs_level *msm_lpm_levels;
+static int msm_lpm_level_count;
+
+int msm_rpmrs_enter_sleep(uint32_t sclk_count, struct msm_rpmrs_limits *limits,
+		bool from_idle, bool notify_rpm)
+{
+	/* TODO */
+	return 0;
+}
+
+void msm_rpmrs_exit_sleep(struct msm_rpmrs_limits *limits, bool from_idle,
+		bool notify_rpm, bool collapsed)
+{
+	/* TODO */
+	return;
+}
+
+static bool msm_rpmrs_irqs_detectable(struct msm_rpmrs_limits *limits,
+		bool irqs_detect, bool gpio_detect)
+{
+	/* TODO */
+	return true;
+}
+
+void msm_rpmrs_show_resources(void)
+{
+	/* TODO */
+	return;
+}
+
+struct msm_rpmrs_limits *msm_rpmrs_lowest_limits(
+	bool from_idle, enum msm_pm_sleep_mode sleep_mode, uint32_t latency_us,
+	uint32_t sleep_us)
+{
+	unsigned int cpu = smp_processor_id();
+	struct msm_rpmrs_level *best_level = NULL;
+	bool irqs_detectable = false;
+	bool gpio_detectable = false;
+	int i;
+
+	if (!msm_lpm_levels)
+		return NULL;
+
+	if (sleep_mode == MSM_PM_SLEEP_MODE_POWER_COLLAPSE) {
+		irqs_detectable = msm_mpm_irqs_detectable(from_idle);
+		gpio_detectable = msm_mpm_gpio_irqs_detectable(from_idle);
+	}
+
+	for (i = 0; i < msm_lpm_level_count; i++) {
+		struct msm_rpmrs_level *level = &msm_lpm_levels[i];
+		uint32_t power;
+
+		if (!level->available)
+			continue;
+
+		if (sleep_mode != level->sleep_mode)
+			continue;
+
+		if (latency_us < level->latency_us)
+			continue;
+
+		if (!msm_rpmrs_irqs_detectable(&level->rs_limits,
+					irqs_detectable, gpio_detectable))
+			continue;
+
+		if (sleep_us <= 1) {
+			power = level->energy_overhead;
+		} else if (sleep_us <= level->time_overhead_us) {
+			power = level->energy_overhead / sleep_us;
+		} else if ((sleep_us >> 10) > level->time_overhead_us) {
+			power = level->steady_state_power;
+		} else {
+			power = level->steady_state_power;
+			power -= (level->time_overhead_us *
+					level->steady_state_power)/sleep_us;
+			power += level->energy_overhead / sleep_us;
+		}
+
+		if (!best_level ||
+				best_level->rs_limits.power[cpu] >= power) {
+			level->rs_limits.latency_us[cpu] = level->latency_us;
+			level->rs_limits.power[cpu] = power;
+			best_level = level;
+		}
+	}
+
+	return best_level ? &best_level->rs_limits : NULL;
+}
+
+static int __devinit msm_lpm_levels_probe(struct platform_device *pdev)
+{
+	struct msm_rpmrs_level *levels = NULL;
+	struct msm_rpmrs_level *level = NULL;
+	struct device_node *node = NULL;
+	char *key = NULL;
+	uint32_t val = 0;
+	int ret = 0;
+	uint32_t num_levels = 0;
+	int idx = 0;
+
+	for_each_child_of_node(pdev->dev.of_node, node)
+		num_levels++;
+
+	levels = kzalloc(num_levels * sizeof(struct msm_rpmrs_level),
+			GFP_KERNEL);
+	if (!levels)
+		return -ENOMEM;
+
+	for_each_child_of_node(pdev->dev.of_node, node) {
+		level = &levels[idx++];
+		level->available = false;
+
+		key = "qcom,mode";
+		ret = of_property_read_u32(node, key, &val);
+		if (ret)
+			goto fail;
+		level->sleep_mode = val;
+
+		key = "qcom,xo";
+		ret = of_property_read_u32(node, key, &val);
+		if (ret)
+			goto fail;
+		level->rs_limits.pxo = val;
+
+		key = "qcom,l2";
+		ret = of_property_read_u32(node, key, &val);
+		if (ret)
+			goto fail;
+		level->rs_limits.l2_cache = val;
+
+		key = "qcom,vdd-dig-upper-bound";
+		ret = of_property_read_u32(node, key, &val);
+		if (ret)
+			goto fail;
+		level->rs_limits.vdd_dig_upper_bound = val;
+
+		key = "qcom,vdd-dig-lower-bound";
+		ret = of_property_read_u32(node, key, &val);
+		if (ret)
+			goto fail;
+		level->rs_limits.vdd_dig = val;
+
+		key = "qcom,vdd-mem-upper-bound";
+		ret = of_property_read_u32(node, key, &val);
+		if (ret)
+			goto fail;
+		level->rs_limits.vdd_mem_upper_bound = val;
+
+		key = "qcom,vdd-mem-lower-bound";
+		ret = of_property_read_u32(node, key, &val);
+		if (ret)
+			goto fail;
+		level->rs_limits.vdd_mem = val;
+
+		key = "qcom,latency-us";
+		ret = of_property_read_u32(node, key, &val);
+		if (ret)
+			goto fail;
+		level->latency_us = val;
+
+		key = "qcom,ss-power";
+		ret = of_property_read_u32(node, key, &val);
+		if (ret)
+			goto fail;
+		level->steady_state_power = val;
+
+		key = "qcom,energy-overhead";
+		ret = of_property_read_u32(node, key, &val);
+		if (ret)
+			goto fail;
+		level->energy_overhead = val;
+
+		key = "qcom,time-overhead";
+		ret = of_property_read_u32(node, key, &val);
+		if (ret)
+			goto fail;
+		level->time_overhead_us = val;
+
+		level->available = true;
+	}
+
+	msm_lpm_levels = levels;
+	msm_lpm_level_count = idx;
+
+	return 0;
+fail:
+	pr_err("%s: Error in name %s key %s\n", __func__, node->full_name, key);
+	kfree(levels);
+	return -EFAULT;
+}
+
+static struct of_device_id msm_lpm_levels_match_table[] = {
+	{.compatible = "qcom,lpm-levels"},
+	{},
+};
+
+static struct platform_driver msm_lpm_levels_driver = {
+	.probe = msm_lpm_levels_probe,
+	.driver = {
+		.name = "lpm-levels",
+		.owner = THIS_MODULE,
+		.of_match_table = msm_lpm_levels_match_table,
+	},
+};
+
+static int __init msm_lpm_levels_module_init(void)
+{
+	return platform_driver_register(&msm_lpm_levels_driver);
+}
+late_initcall(msm_lpm_levels_module_init);