123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184 |
- /*
- * 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_ARCH_NEEDS_CPU_IDLE_COUPLED
- 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);
|