forked from Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
178 lines
4.2 KiB
178 lines
4.2 KiB
// SPDX-License-Identifier: GPL-2.0-or-later |
|
/* |
|
* Copyright (C) 2014 Imagination Technologies |
|
* Author: Paul Burton <[email protected]> |
|
*/ |
|
|
|
#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 (cpus_are_siblings(0, dev->cpu) && (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, 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) { |
|
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);
|
|
|