Perf: Use CPU PM notifiers to save and restore PMU regs

Some PMU registers have undefined values at reset. Power collapsing
CPU's can cause the PMU state to be messed up. Use the CPU PM notifiers
to save and restore PMU state correctly.

Change-Id: I4d5d73abc3455b38219239527d94d4afc30ed886
Signed-off-by: Ashwin Chaugule <ashwinc@codeaurora.org>
diff --git a/arch/arm/kernel/perf_event.c b/arch/arm/kernel/perf_event.c
index 9144461..b59ad93 100644
--- a/arch/arm/kernel/perf_event.c
+++ b/arch/arm/kernel/perf_event.c
@@ -26,6 +26,7 @@
 #include <asm/irq_regs.h>
 #include <asm/pmu.h>
 #include <asm/stacktrace.h>
+#include <linux/cpu_pm.h>
 
 static struct platform_device *pmu_device;
 
@@ -651,6 +652,30 @@
 #include "perf_event_msm_krait.c"
 #include "perf_event_msm_krait_l2.c"
 
+static int perf_cpu_pm_notifier(struct notifier_block *self, unsigned long cmd,
+		void *v)
+{
+	switch (cmd) {
+	case CPU_PM_ENTER:
+		perf_pmu_disable(&pmu);
+		break;
+
+	case CPU_PM_ENTER_FAILED:
+	case CPU_PM_EXIT:
+		if (armpmu && armpmu->reset)
+			armpmu->reset(NULL);
+		perf_pmu_enable(&pmu);
+
+		break;
+	}
+
+	return NOTIFY_OK;
+}
+
+static struct notifier_block perf_cpu_pm_notifier_block = {
+	.notifier_call = perf_cpu_pm_notifier,
+};
+
 /*
  * Ensure the PMU has sane values out of reset.
  * This requires SMP to be available, so exists as a separate initcall.
@@ -733,6 +758,8 @@
 
 	perf_pmu_register(&pmu, "cpu", PERF_TYPE_RAW);
 
+	cpu_pm_register_notifier(&perf_cpu_pm_notifier_block);
+
 	return 0;
 }
 early_initcall(init_hw_perf_events);
diff --git a/arch/arm/kernel/perf_event_msm.c b/arch/arm/kernel/perf_event_msm.c
index 259617a..b40a226 100644
--- a/arch/arm/kernel/perf_event_msm.c
+++ b/arch/arm/kernel/perf_event_msm.c
@@ -686,6 +686,26 @@
 }
 #endif
 
+static void scorpion_pmu_reset(void *info)
+{
+	u32 idx, nb_cnt = armpmu->num_events;
+
+	/* Stop all counters and their interrupts */
+	for (idx = 1; idx < nb_cnt; ++idx) {
+		armv7_pmnc_disable_counter(idx);
+		armv7_pmnc_disable_intens(idx);
+	}
+
+	/* Clear all pmresrs */
+	scorpion_clear_pmuregs();
+
+	/* Reset irq stat reg */
+	armv7_pmnc_getreset_flags();
+
+	/* Reset all ctrs to 0 */
+	armv7_pmnc_write(ARMV7_PMNC_P | ARMV7_PMNC_C);
+}
+
 static struct arm_pmu scorpion_pmu = {
 	.handle_irq		= armv7pmu_handle_irq,
 #ifdef CONFIG_SMP
@@ -700,6 +720,7 @@
 	.get_event_idx		= armv7pmu_get_event_idx,
 	.start			= armv7pmu_start,
 	.stop			= armv7pmu_stop,
+	.reset			= scorpion_pmu_reset,
 	.max_period		= (1LLU << 32) - 1,
 };
 
diff --git a/arch/arm/kernel/perf_event_msm_krait.c b/arch/arm/kernel/perf_event_msm_krait.c
index 34abb48..cf1f1ee 100644
--- a/arch/arm/kernel/perf_event_msm_krait.c
+++ b/arch/arm/kernel/perf_event_msm_krait.c
@@ -472,6 +472,26 @@
 	raw_spin_unlock_irqrestore(&pmu_lock, flags);
 }
 
+static void krait_pmu_reset(void *info)
+{
+	u32 idx, nb_cnt = armpmu->num_events;
+
+	/* Stop all counters and their interrupts */
+	for (idx = 1; idx < nb_cnt; ++idx) {
+		armv7_pmnc_disable_counter(idx);
+		armv7_pmnc_disable_intens(idx);
+	}
+
+	/* Clear all pmresrs */
+	krait_clear_pmuregs();
+
+	/* Reset irq stat reg */
+	armv7_pmnc_getreset_flags();
+
+	/* Reset all ctrs to 0 */
+	armv7_pmnc_write(ARMV7_PMNC_P | ARMV7_PMNC_C);
+}
+
 static struct arm_pmu krait_pmu = {
 	.handle_irq		= armv7pmu_handle_irq,
 #ifdef CONFIG_SMP
@@ -486,6 +506,7 @@
 	.get_event_idx		= armv7pmu_get_event_idx,
 	.start			= armv7pmu_start,
 	.stop			= armv7pmu_stop,
+	.reset			= krait_pmu_reset,
 	.max_period		= (1LLU << 32) - 1,
 };