oleavr-rgl-a500-mini-linux-.../drivers/cpuidle/cpuidle-arm.c
Ole André Vadla Ravnås 169c65d57e Initial commit
2022-05-07 01:01:45 +02:00

269 lines
5.8 KiB
C

/*
* ARM/ARM64 generic CPU idle driver.
*
* Copyright (C) 2014 ARM Ltd.
* Author: Lorenzo Pieralisi <lorenzo.pieralisi@arm.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#define pr_fmt(fmt) "CPUidle arm: " fmt
#include <linux/cpuidle.h>
#include <linux/cpumask.h>
#include <linux/cpu_pm.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/slab.h>
#include <asm/cpuidle.h>
#include <asm/suspend.h>
#include <asm/io.h>
#include <asm/cacheflush.h>
#include <linux/clockchips.h>
#include <linux/irqchip/arm-gic.h>
#include <linux/cpu.h>
#include <linux/sunxi-cpuidle.h>
#include <linux/syscore_ops.h>
#include "dt_idle_states.h"
#if defined CONFIG_ARCH_SUN50IW6P1
void __iomem *sunxi_rtc_base;
#endif
/*
* arm_enter_idle_state - Programs CPU to enter the specified state
*
* dev: cpuidle device
* drv: cpuidle driver
* idx: state index
*
* Called from the CPUidle framework to program the device to the
* specified target state selected by the governor.
*/
static int arm_enter_idle_state(struct cpuidle_device *dev,
struct cpuidle_driver *drv, int idx)
{
#if defined CONFIG_ARCH_SUN50IW6P1
int ret;
/* if (!idx) */
{
cpu_do_idle();
return idx;
}
idx = 1;
ret = cpu_pm_enter();
if (!ret) {
/*
* Pass idle state index to cpu_suspend which in turn will
* call the CPU ops suspend protocol with idle index as a
* parameter.
*/
smp_wmb();
arm_cpuidle_suspend(idx);
cpu_pm_exit();
}
return ret ? -1 : idx;
#elif defined CONFIG_ARCH_SUN50IW3P1
int ret;
idx = 0;
if (!idx) {
cpu_do_idle();
return idx;
}
if (idx > 2)
idx = 2;
ret = cpu_pm_enter();
if (!ret) {
/*
* Pass idle state index to cpu_suspend which in turn will
* call the CPU ops suspend protocol with idle index as a
* parameter.
*/
smp_wmb();
arm_cpuidle_suspend(idx);
cpu_pm_exit();
}
return ret ? -1 : idx;
#else
cpu_do_idle();
return idx;
#endif
}
static int cpuidle_notify(struct notifier_block *self, unsigned long action,
void *hcpu)
{
switch (action) {
case CPU_UP_PREPARE:
cpuidle_pause_and_lock();
break;
case CPU_ONLINE:
cpuidle_resume_and_unlock();
break;
case CPU_DOWN_PREPARE:
cpuidle_pause_and_lock();
break;
case CPU_POST_DEAD:
cpuidle_resume_and_unlock();
break;
}
return NOTIFY_OK;
}
static struct notifier_block cpuidle_nb = {
.notifier_call = cpuidle_notify,
};
static struct cpuidle_driver arm_idle_driver = {
.name = "arm_idle",
.owner = THIS_MODULE,
/*
* State at index 0 is standby wfi and considered standard
* on all ARM platforms. If in some platforms simple wfi
* can't be used as "state 0", DT bindings must be implemented
* to work around this issue and allow installing a special
* handler for idle state index 0.
*/
.states[0] = {
.enter = arm_enter_idle_state,
.exit_latency = 1,
.target_residency = 1,
.power_usage = UINT_MAX,
.flags = CPUIDLE_FLAG_TIME_VALID,
.name = "WFI",
.desc = "ARM WFI",
},
.states[1] = {
.enter = arm_enter_idle_state,
.exit_latency = 100,
.target_residency = 200,
.flags = CPUIDLE_FLAG_TIMER_STOP,
.name = "CPD",
.desc = "CORE POWER DOWN",
},
.state_count = 2,
};
static const struct of_device_id arm_idle_state_match[] __initconst = {
{ .compatible = "arm,idle-state",
.data = arm_enter_idle_state },
{ },
};
static void sunxi_idle_pm_resume(void)
{
/* write hotplug flag, cpu0 use */
}
static int sunxi_idle_pm_suspend(void)
{
return 0;
}
static struct syscore_ops sunxi_idle_pm_syscore_ops = {
.resume = sunxi_idle_pm_resume,
.suspend = sunxi_idle_pm_suspend,
};
/*
* arm_idle_init
*
* Registers the arm specific cpuidle driver with the cpuidle
* framework. It relies on core code to parse the idle states
* and initialize them using driver data structures accordingly.
*/
static int __init arm_idle_init(void)
{
int cpu, ret;
struct cpuidle_driver *drv = &arm_idle_driver;
struct cpuidle_device *dev;
/*
* Initialize idle states data, starting at index 1.
* This driver is DT only, if no DT idle states are detected (ret == 0)
* let the driver initialization fail accordingly since there is no
* reason to initialize the idle driver if only wfi is supported.
*/
ret = dt_init_idle_driver(drv, arm_idle_state_match, 1);
if (ret <= 0)
return ret ? : -ENODEV;
ret = register_cpu_notifier(&cpuidle_nb);
register_syscore_ops(&sunxi_idle_pm_syscore_ops);
#if defined CONFIG_ARCH_SUN50IW6P1
sunxi_rtc_base = ioremap(SUN50IW6_RTC_PBASE, SZ_1K);
#endif
ret = cpuidle_register_driver(drv);
if (ret) {
pr_err("Failed to register cpuidle driver\n");
return ret;
}
/*
* Call arch CPU operations in order to initialize
* idle states suspend back-end specific data
*/
for_each_possible_cpu(cpu) {
ret = arm_cpuidle_init(cpu);
/*
* Skip the cpuidle device initialization if the reported
* failure is a HW misconfiguration/breakage (-ENXIO).
*/
if (ret == -ENXIO)
continue;
if (ret) {
pr_err("CPU %d failed to init idle CPU ops\n", cpu);
goto out_fail;
}
dev = kzalloc(sizeof(*dev), GFP_KERNEL);
if (!dev) {
pr_err("Failed to allocate cpuidle device\n");
goto out_fail;
}
dev->cpu = cpu;
ret = cpuidle_register_device(dev);
if (ret) {
pr_err("Failed to register cpuidle device for CPU %d\n",
cpu);
kfree(dev);
goto out_fail;
}
}
return 0;
out_fail:
while (--cpu >= 0) {
dev = per_cpu(cpuidle_devices, cpu);
cpuidle_unregister_device(dev);
kfree(dev);
}
cpuidle_unregister_driver(drv);
return ret;
}
device_initcall(arm_idle_init);