|
@@ -0,0 +1,186 @@
|
|
|
+/*
|
|
|
+ * 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,
|
|
|
+ .flags = CPUIDLE_FLAG_TIME_VALID,
|
|
|
+ .name = "nc-wait",
|
|
|
+ .desc = "non-coherent MIPS wait",
|
|
|
+ },
|
|
|
+ [STATE_CLOCK_GATED] = {
|
|
|
+ .enter = cps_nc_enter,
|
|
|
+ .exit_latency = 300,
|
|
|
+ .target_residency = 700,
|
|
|
+ .flags = CPUIDLE_FLAG_TIME_VALID |
|
|
|
+ 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_TIME_VALID |
|
|
|
+ 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);
|