diff options
Diffstat (limited to 'drivers/cpuidle/cpuidle-psci.c')
-rw-r--r-- | drivers/cpuidle/cpuidle-psci.c | 120 |
1 files changed, 79 insertions, 41 deletions
diff --git a/drivers/cpuidle/cpuidle-psci.c b/drivers/cpuidle/cpuidle-psci.c index bf68920d038a..4e1ba35deda9 100644 --- a/drivers/cpuidle/cpuidle-psci.c +++ b/drivers/cpuidle/cpuidle-psci.c @@ -16,7 +16,7 @@ #include <linux/kernel.h> #include <linux/module.h> #include <linux/of.h> -#include <linux/platform_device.h> +#include <linux/device/faux.h> #include <linux/psci.h> #include <linux/pm_domain.h> #include <linux/pm_runtime.h> @@ -25,27 +25,41 @@ #include <linux/syscore_ops.h> #include <asm/cpuidle.h> +#include <trace/events/power.h> #include "cpuidle-psci.h" #include "dt_idle_states.h" +#include "dt_idle_genpd.h" struct psci_cpuidle_data { u32 *psci_states; struct device *dev; }; +struct psci_cpuidle_domain_state { + struct generic_pm_domain *pd; + unsigned int state_idx; + u32 state; +}; + static DEFINE_PER_CPU_READ_MOSTLY(struct psci_cpuidle_data, psci_cpuidle_data); -static DEFINE_PER_CPU(u32, domain_state); +static DEFINE_PER_CPU(struct psci_cpuidle_domain_state, psci_domain_state); +static bool psci_cpuidle_use_syscore; static bool psci_cpuidle_use_cpuhp; -void psci_set_domain_state(u32 state) +void psci_set_domain_state(struct generic_pm_domain *pd, unsigned int state_idx, + u32 state) { - __this_cpu_write(domain_state, state); + struct psci_cpuidle_domain_state *ds = this_cpu_ptr(&psci_domain_state); + + ds->pd = pd; + ds->state_idx = state_idx; + ds->state = state; } -static inline u32 psci_get_domain_state(void) +static inline void psci_clear_domain_state(void) { - return __this_cpu_read(domain_state); + __this_cpu_write(psci_domain_state.state, 0); } static __cpuidle int __psci_enter_domain_idle_state(struct cpuidle_device *dev, @@ -55,7 +69,8 @@ static __cpuidle int __psci_enter_domain_idle_state(struct cpuidle_device *dev, struct psci_cpuidle_data *data = this_cpu_ptr(&psci_cpuidle_data); u32 *states = data->psci_states; struct device *pd_dev = data->dev; - u32 state; + struct psci_cpuidle_domain_state *ds; + u32 state = states[idx]; int ret; ret = cpu_pm_enter(); @@ -68,11 +83,13 @@ static __cpuidle int __psci_enter_domain_idle_state(struct cpuidle_device *dev, else pm_runtime_put_sync_suspend(pd_dev); - state = psci_get_domain_state(); - if (!state) - state = states[idx]; + ds = this_cpu_ptr(&psci_domain_state); + if (ds->state) + state = ds->state; + trace_psci_domain_idle_enter(dev->cpu, state, s2idle); ret = psci_cpu_suspend_enter(state) ? -1 : idx; + trace_psci_domain_idle_exit(dev->cpu, state, s2idle); if (s2idle) dev_pm_genpd_resume(pd_dev); @@ -81,8 +98,12 @@ static __cpuidle int __psci_enter_domain_idle_state(struct cpuidle_device *dev, cpu_pm_exit(); + /* Correct domain-idlestate statistics if we failed to enter. */ + if (ret == -1 && ds->state) + pm_genpd_inc_rejected(ds->pd, ds->state_idx); + /* Clear the domain state to start fresh when back from idle. */ - psci_set_domain_state(0); + psci_clear_domain_state(); return ret; } @@ -116,7 +137,7 @@ static int psci_idle_cpuhp_down(unsigned int cpu) if (pd_dev) { pm_runtime_put_sync(pd_dev); /* Clear domain state to start fresh at next online. */ - psci_set_domain_state(0); + psci_clear_domain_state(); } return 0; @@ -142,7 +163,7 @@ static void psci_idle_syscore_switch(bool suspend) /* Clear domain state to re-start fresh. */ if (!cleared) { - psci_set_domain_state(0); + psci_clear_domain_state(); cleared = true; } } @@ -165,6 +186,12 @@ static struct syscore_ops psci_idle_syscore_ops = { .resume = psci_idle_syscore_resume, }; +static void psci_idle_init_syscore(void) +{ + if (psci_cpuidle_use_syscore) + register_syscore_ops(&psci_idle_syscore_ops); +} + static void psci_idle_init_cpuhp(void) { int err; @@ -172,8 +199,6 @@ static void psci_idle_init_cpuhp(void) if (!psci_cpuidle_use_cpuhp) return; - register_syscore_ops(&psci_idle_syscore_ops); - err = cpuhp_setup_state_nocalls(CPUHP_AP_CPU_PM_STARTING, "cpuidle/psci:online", psci_idle_cpuhp_up, @@ -221,22 +246,23 @@ static int psci_dt_cpu_init_topology(struct cpuidle_driver *drv, if (!psci_has_osi_support()) return 0; - if (IS_ENABLED(CONFIG_PREEMPT_RT)) - return 0; - - data->dev = psci_dt_attach_cpu(cpu); + data->dev = dt_idle_attach_cpu(cpu, "psci"); if (IS_ERR_OR_NULL(data->dev)) return PTR_ERR_OR_ZERO(data->dev); + psci_cpuidle_use_syscore = true; + /* * Using the deepest state for the CPU to trigger a potential selection * of a shared state for the domain, assumes the domain states are all - * deeper states. + * deeper states. On PREEMPT_RT the hierarchical topology is limited to + * s2ram and s2idle. */ - drv->states[state_count - 1].flags |= CPUIDLE_FLAG_RCU_IDLE; - drv->states[state_count - 1].enter = psci_enter_domain_idle_state; drv->states[state_count - 1].enter_s2idle = psci_enter_s2idle_domain_idle_state; - psci_cpuidle_use_cpuhp = true; + if (!IS_ENABLED(CONFIG_PREEMPT_RT)) { + drv->states[state_count - 1].enter = psci_enter_domain_idle_state; + psci_cpuidle_use_cpuhp = true; + } return 0; } @@ -311,7 +337,8 @@ static void psci_cpu_deinit_idle(int cpu) { struct psci_cpuidle_data *data = per_cpu_ptr(&psci_cpuidle_data, cpu); - psci_dt_detach_cpu(data->dev); + dt_idle_detach_cpu(data->dev); + psci_cpuidle_use_syscore = false; psci_cpuidle_use_cpuhp = false; } @@ -392,22 +419,23 @@ deinit: /* * psci_idle_probe - Initializes PSCI cpuidle driver * - * Initializes PSCI cpuidle driver for all CPUs, if any CPU fails + * Initializes PSCI cpuidle driver for all present CPUs, if any CPU fails * to register cpuidle driver then rollback to cancel all CPUs * registration. */ -static int psci_cpuidle_probe(struct platform_device *pdev) +static int psci_cpuidle_probe(struct faux_device *fdev) { int cpu, ret; struct cpuidle_driver *drv; struct cpuidle_device *dev; - for_each_possible_cpu(cpu) { - ret = psci_idle_init_cpu(&pdev->dev, cpu); + for_each_present_cpu(cpu) { + ret = psci_idle_init_cpu(&fdev->dev, cpu); if (ret) goto out_fail; } + psci_idle_init_syscore(); psci_idle_init_cpuhp(); return 0; @@ -422,26 +450,36 @@ out_fail: return ret; } -static struct platform_driver psci_cpuidle_driver = { +static struct faux_device_ops psci_cpuidle_ops = { .probe = psci_cpuidle_probe, - .driver = { - .name = "psci-cpuidle", - }, }; +static bool __init dt_idle_state_present(void) +{ + struct device_node *cpu_node __free(device_node) = + of_cpu_device_node_get(cpumask_first(cpu_possible_mask)); + if (!cpu_node) + return false; + + struct device_node *state_node __free(device_node) = + of_get_cpu_state_node(cpu_node, 0); + if (!state_node) + return false; + + return !!of_match_node(psci_idle_state_match, state_node); +} + static int __init psci_idle_init(void) { - struct platform_device *pdev; - int ret; + struct faux_device *fdev; - ret = platform_driver_register(&psci_cpuidle_driver); - if (ret) - return ret; + if (!dt_idle_state_present()) + return 0; - pdev = platform_device_register_simple("psci-cpuidle", -1, NULL, 0); - if (IS_ERR(pdev)) { - platform_driver_unregister(&psci_cpuidle_driver); - return PTR_ERR(pdev); + fdev = faux_device_create("psci-cpuidle", NULL, &psci_cpuidle_ops); + if (!fdev) { + pr_err("Failed to create psci-cpuidle device\n"); + return -ENODEV; } return 0; |