linux_dsm_epyc7002/arch/arm/mach-ux500/cpuidle.c
Linus Walleij 1e22a8c614 ARM: ux500: move PM-related PRCMU functions to machine
We are trying to decompose and decentralize the code in
the DB8500 PRCMU out into subdrivers. The code moved in
this patch concerns a group of functions used for
decoupling and recoupling the IRQs from the GIC. During
sleep and idle the Ux500 system will transfer all IRQ
handling to the PRCMU using these functions.

Basically we are left with the two alternatives of code
placement as:

- arch/arm/mach-ux500/pm.c - this because the code is
  closely related to the GIC, and takes ownership of
  some of the registers from the PRCMU related to this
  PM functionality.

- drivers/mfd/db8500-prcmu-pm.c - because the code is
  affecting stuff in the PRCMU register range. But then
  this code needs to remap and handle GIC registers.

This patch implementation is taking the first approach.

Currently the cpuidle driver is the only piece of code
using this set of functions, but it will later also be
used by the suspend/resume code which is currently under
review.

The header file is moved to:
<linux/platform_data/arm-ux500-pm.h>
The function prototypes need to be placed in a globally
visible header since the CPUidle code is planned to move
out to drivers/cpuidle.

Acked-by: Samuel Ortiz <sameo@linux.intel.com>
Acked-by: Rickard Andersson <rickard.andersson@stericsson.com>
Acked-by: Daniel Lezcano <daniel.lezcano@linaro.org>
Signed-off-by: Linus Walleij <linus.walleij@linaro.org>
2013-04-08 13:57:53 +02:00

175 lines
4.3 KiB
C

/*
* Copyright (c) 2012 Linaro : Daniel Lezcano <daniel.lezcano@linaro.org> (IBM)
*
* Based on the work of Rickard Andersson <rickard.andersson@stericsson.com>
* and Jonas Aaberg <jonas.aberg@stericsson.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.
*/
#include <linux/module.h>
#include <linux/cpuidle.h>
#include <linux/clockchips.h>
#include <linux/spinlock.h>
#include <linux/atomic.h>
#include <linux/smp.h>
#include <linux/mfd/dbx500-prcmu.h>
#include <linux/platform_data/arm-ux500-pm.h>
#include <asm/cpuidle.h>
#include <asm/proc-fns.h>
static atomic_t master = ATOMIC_INIT(0);
static DEFINE_SPINLOCK(master_lock);
static DEFINE_PER_CPU(struct cpuidle_device, ux500_cpuidle_device);
static inline int ux500_enter_idle(struct cpuidle_device *dev,
struct cpuidle_driver *drv, int index)
{
int this_cpu = smp_processor_id();
bool recouple = false;
clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_ENTER, &this_cpu);
if (atomic_inc_return(&master) == num_online_cpus()) {
/* With this lock, we prevent the other cpu to exit and enter
* this function again and become the master */
if (!spin_trylock(&master_lock))
goto wfi;
/* decouple the gic from the A9 cores */
if (prcmu_gic_decouple()) {
spin_unlock(&master_lock);
goto out;
}
/* If an error occur, we will have to recouple the gic
* manually */
recouple = true;
/* At this state, as the gic is decoupled, if the other
* cpu is in WFI, we have the guarantee it won't be wake
* up, so we can safely go to retention */
if (!prcmu_is_cpu_in_wfi(this_cpu ? 0 : 1))
goto out;
/* The prcmu will be in charge of watching the interrupts
* and wake up the cpus */
if (prcmu_copy_gic_settings())
goto out;
/* Check in the meantime an interrupt did
* not occur on the gic ... */
if (prcmu_gic_pending_irq())
goto out;
/* ... and the prcmu */
if (prcmu_pending_irq())
goto out;
/* Go to the retention state, the prcmu will wait for the
* cpu to go WFI and this is what happens after exiting this
* 'master' critical section */
if (prcmu_set_power_state(PRCMU_AP_IDLE, true, true))
goto out;
/* When we switch to retention, the prcmu is in charge
* of recoupling the gic automatically */
recouple = false;
spin_unlock(&master_lock);
}
wfi:
cpu_do_idle();
out:
atomic_dec(&master);
if (recouple) {
prcmu_gic_recouple();
spin_unlock(&master_lock);
}
clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_EXIT, &this_cpu);
return index;
}
static struct cpuidle_driver ux500_idle_driver = {
.name = "ux500_idle",
.owner = THIS_MODULE,
.en_core_tk_irqen = 1,
.states = {
ARM_CPUIDLE_WFI_STATE,
{
.enter = ux500_enter_idle,
.exit_latency = 70,
.target_residency = 260,
.flags = CPUIDLE_FLAG_TIME_VALID,
.name = "ApIdle",
.desc = "ARM Retention",
},
},
.safe_state_index = 0,
.state_count = 2,
};
/*
* For each cpu, setup the broadcast timer because we will
* need to migrate the timers for the states >= ApIdle.
*/
static void ux500_setup_broadcast_timer(void *arg)
{
int cpu = smp_processor_id();
clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_ON, &cpu);
}
int __init ux500_idle_init(void)
{
int ret, cpu;
struct cpuidle_device *device;
/* Configure wake up reasons */
prcmu_enable_wakeups(PRCMU_WAKEUP(ARM) | PRCMU_WAKEUP(RTC) |
PRCMU_WAKEUP(ABB));
/*
* Configure the timer broadcast for each cpu, that must
* be done from the cpu context, so we use a smp cross
* call with 'on_each_cpu'.
*/
on_each_cpu(ux500_setup_broadcast_timer, NULL, 1);
ret = cpuidle_register_driver(&ux500_idle_driver);
if (ret) {
printk(KERN_ERR "failed to register ux500 idle driver\n");
return ret;
}
for_each_online_cpu(cpu) {
device = &per_cpu(ux500_cpuidle_device, cpu);
device->cpu = cpu;
ret = cpuidle_register_device(device);
if (ret) {
printk(KERN_ERR "Failed to register cpuidle "
"device for cpu%d\n", cpu);
goto out_unregister;
}
}
out:
return ret;
out_unregister:
for_each_online_cpu(cpu) {
device = &per_cpu(ux500_cpuidle_device, cpu);
cpuidle_unregister_device(device);
}
cpuidle_unregister_driver(&ux500_idle_driver);
goto out;
}
device_initcall(ux500_idle_init);