Add the rt linux 4.1.3-rt3 as base
[kvmfornfv.git] / kernel / drivers / cpuidle / cpuidle-cps.c
diff --git a/kernel/drivers/cpuidle/cpuidle-cps.c b/kernel/drivers/cpuidle/cpuidle-cps.c
new file mode 100644 (file)
index 0000000..1adb698
--- /dev/null
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2014 Imagination Technologies
+ * Author: Paul Burton <paul.burton@imgtec.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation;  either version 2 of the  License, or (at your
+ * option) any later version.
+ */
+
+#include <linux/cpu_pm.h>
+#include <linux/cpuidle.h>
+#include <linux/init.h>
+
+#include <asm/idle.h>
+#include <asm/pm-cps.h>
+
+/* Enumeration of the various idle states this driver may enter */
+enum cps_idle_state {
+       STATE_WAIT = 0,         /* MIPS wait instruction, coherent */
+       STATE_NC_WAIT,          /* MIPS wait instruction, non-coherent */
+       STATE_CLOCK_GATED,      /* Core clock gated */
+       STATE_POWER_GATED,      /* Core power gated */
+       STATE_COUNT
+};
+
+static int cps_nc_enter(struct cpuidle_device *dev,
+                       struct cpuidle_driver *drv, int index)
+{
+       enum cps_pm_state pm_state;
+       int err;
+
+       /*
+        * At least one core must remain powered up & clocked in order for the
+        * system to have any hope of functioning.
+        *
+        * TODO: don't treat core 0 specially, just prevent the final core
+        * TODO: remap interrupt affinity temporarily
+        */
+       if (!cpu_data[dev->cpu].core && (index > STATE_NC_WAIT))
+               index = STATE_NC_WAIT;
+
+       /* Select the appropriate cps_pm_state */
+       switch (index) {
+       case STATE_NC_WAIT:
+               pm_state = CPS_PM_NC_WAIT;
+               break;
+       case STATE_CLOCK_GATED:
+               pm_state = CPS_PM_CLOCK_GATED;
+               break;
+       case STATE_POWER_GATED:
+               pm_state = CPS_PM_POWER_GATED;
+               break;
+       default:
+               BUG();
+               return -EINVAL;
+       }
+
+       /* Notify listeners the CPU is about to power down */
+       if ((pm_state == CPS_PM_POWER_GATED) && cpu_pm_enter())
+               return -EINTR;
+
+       /* Enter that state */
+       err = cps_pm_enter_state(pm_state);
+
+       /* Notify listeners the CPU is back up */
+       if (pm_state == CPS_PM_POWER_GATED)
+               cpu_pm_exit();
+
+       return err ?: index;
+}
+
+static struct cpuidle_driver cps_driver = {
+       .name                   = "cpc_cpuidle",
+       .owner                  = THIS_MODULE,
+       .states = {
+               [STATE_WAIT] = MIPS_CPUIDLE_WAIT_STATE,
+               [STATE_NC_WAIT] = {
+                       .enter  = cps_nc_enter,
+                       .exit_latency           = 200,
+                       .target_residency       = 450,
+                       .name   = "nc-wait",
+                       .desc   = "non-coherent MIPS wait",
+               },
+               [STATE_CLOCK_GATED] = {
+                       .enter  = cps_nc_enter,
+                       .exit_latency           = 300,
+                       .target_residency       = 700,
+                       .flags  = CPUIDLE_FLAG_TIMER_STOP,
+                       .name   = "clock-gated",
+                       .desc   = "core clock gated",
+               },
+               [STATE_POWER_GATED] = {
+                       .enter  = cps_nc_enter,
+                       .exit_latency           = 600,
+                       .target_residency       = 1000,
+                       .flags  = CPUIDLE_FLAG_TIMER_STOP,
+                       .name   = "power-gated",
+                       .desc   = "core power gated",
+               },
+       },
+       .state_count            = STATE_COUNT,
+       .safe_state_index       = 0,
+};
+
+static void __init cps_cpuidle_unregister(void)
+{
+       int cpu;
+       struct cpuidle_device *device;
+
+       for_each_possible_cpu(cpu) {
+               device = &per_cpu(cpuidle_dev, cpu);
+               cpuidle_unregister_device(device);
+       }
+
+       cpuidle_unregister_driver(&cps_driver);
+}
+
+static int __init cps_cpuidle_init(void)
+{
+       int err, cpu, core, i;
+       struct cpuidle_device *device;
+
+       /* Detect supported states */
+       if (!cps_pm_support_state(CPS_PM_POWER_GATED))
+               cps_driver.state_count = STATE_CLOCK_GATED + 1;
+       if (!cps_pm_support_state(CPS_PM_CLOCK_GATED))
+               cps_driver.state_count = STATE_NC_WAIT + 1;
+       if (!cps_pm_support_state(CPS_PM_NC_WAIT))
+               cps_driver.state_count = STATE_WAIT + 1;
+
+       /* Inform the user if some states are unavailable */
+       if (cps_driver.state_count < STATE_COUNT) {
+               pr_info("cpuidle-cps: limited to ");
+               switch (cps_driver.state_count - 1) {
+               case STATE_WAIT:
+                       pr_cont("coherent wait\n");
+                       break;
+               case STATE_NC_WAIT:
+                       pr_cont("non-coherent wait\n");
+                       break;
+               case STATE_CLOCK_GATED:
+                       pr_cont("clock gating\n");
+                       break;
+               }
+       }
+
+       /*
+        * Set the coupled flag on the appropriate states if this system
+        * requires it.
+        */
+       if (coupled_coherence)
+               for (i = STATE_NC_WAIT; i < cps_driver.state_count; i++)
+                       cps_driver.states[i].flags |= CPUIDLE_FLAG_COUPLED;
+
+       err = cpuidle_register_driver(&cps_driver);
+       if (err) {
+               pr_err("Failed to register CPS cpuidle driver\n");
+               return err;
+       }
+
+       for_each_possible_cpu(cpu) {
+               core = cpu_data[cpu].core;
+               device = &per_cpu(cpuidle_dev, cpu);
+               device->cpu = cpu;
+#ifdef CONFIG_MIPS_MT
+               cpumask_copy(&device->coupled_cpus, &cpu_sibling_map[cpu]);
+#endif
+
+               err = cpuidle_register_device(device);
+               if (err) {
+                       pr_err("Failed to register CPU%d cpuidle device\n",
+                              cpu);
+                       goto err_out;
+               }
+       }
+
+       return 0;
+err_out:
+       cps_cpuidle_unregister();
+       return err;
+}
+device_initcall(cps_cpuidle_init);