>From a1aa5dd0924b8c7493c6fec31c00b2feba7f5ad7 Mon Sep 17 00:00:00 2001 From: Leo Yan Date: Mon, 23 Sep 2013 17:05:42 +0800 Subject: [PATCH 1/2] cpuidle: add simple driver for arm64 Signed-off-by: Leo Yan --- drivers/cpuidle/Kconfig | 6 ++ drivers/cpuidle/Makefile | 1 + drivers/cpuidle/cpuidle-arm64.c | 180 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 187 insertions(+) create mode 100644 drivers/cpuidle/cpuidle-arm64.c diff --git a/drivers/cpuidle/Kconfig b/drivers/cpuidle/Kconfig index c4cc27e..361325e 100644 --- a/drivers/cpuidle/Kconfig +++ b/drivers/cpuidle/Kconfig @@ -39,4 +39,10 @@ config CPU_IDLE_CALXEDA help Select this to enable cpuidle on Calxeda processors. +config CPU_IDLE_ARM64 + bool "CPU Idle Driver for ARM64" + depends on ARM64 + help + Select this to enable cpuidle on ARM64 processors. + endif diff --git a/drivers/cpuidle/Makefile b/drivers/cpuidle/Makefile index 0d8bd55..954b67f 100644 --- a/drivers/cpuidle/Makefile +++ b/drivers/cpuidle/Makefile @@ -7,3 +7,4 @@ obj-$(CONFIG_ARCH_NEEDS_CPU_IDLE_COUPLED) += coupled.o obj-$(CONFIG_CPU_IDLE_CALXEDA) += cpuidle-calxeda.o obj-$(CONFIG_ARCH_KIRKWOOD) += cpuidle-kirkwood.o +obj-$(CONFIG_CPU_IDLE_ARM64) += cpuidle-arm64.o diff --git a/drivers/cpuidle/cpuidle-arm64.c b/drivers/cpuidle/cpuidle-arm64.c new file mode 100644 index 0000000..7d376db --- /dev/null +++ b/drivers/cpuidle/cpuidle-arm64.c @@ -0,0 +1,180 @@ +/* + * ARM64 CPU idle driver. + * + * Copyright (C) 2012 ARM Ltd. + * Author: Lorenzo Pieralisi + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static int arm64_cpuidle_simple_enter(struct cpuidle_device *dev, + struct cpuidle_driver *drv, int index) +{ + ktime_t time_start, time_end; + s64 diff; + + time_start = ktime_get(); + + cpu_do_idle(); + + time_end = ktime_get(); + + local_irq_enable(); + + diff = ktime_to_us(ktime_sub(time_end, time_start)); + if (diff > INT_MAX) + diff = INT_MAX; + + dev->last_residency = (int) diff; + + return index; +} + +static int arm64_enter_powerdown(struct cpuidle_device *dev, + struct cpuidle_driver *drv, int idx); + +static struct cpuidle_state arm64_cpuidle_set[] __initdata = { + [0] = { + .enter = arm64_cpuidle_simple_enter, + .exit_latency = 1, + .target_residency = 1, + .power_usage = UINT_MAX, + .flags = CPUIDLE_FLAG_TIME_VALID, + .name = "WFI", + .desc = "ARM64 WFI", + }, +#if 1 + [1] = { + .enter = arm64_enter_powerdown, + .exit_latency = 300, + .target_residency = 1000, + .flags = CPUIDLE_FLAG_TIME_VALID, + .name = "C1", + .desc = "ARM64 power down", + }, +#endif +}; + +struct cpuidle_driver arm64_idle_driver = { + .name = "arm64_idle", + .owner = THIS_MODULE, + .safe_state_index = 0 +}; + +static DEFINE_PER_CPU(struct cpuidle_device, arm64_idle_dev); + +extern void arm64_cpu_tear_down(void); +typedef void (*phys_reset_t)(unsigned long); + +static int notrace arm64_powerdown_finisher(unsigned long arg) +{ + phys_reset_t phys_reset; + + setup_mm_for_reboot(); + + arm64_cpu_tear_down(); + wfi(); + + phys_reset = (phys_reset_t)(unsigned long)virt_to_phys(cpu_reset); + phys_reset(virt_to_phys(cpu_resume)); + + /* should never get here */ + BUG(); +} + +/* + * arm64_enter_powerdown - Programs CPU to enter the specified state + * @dev: cpuidle device + * @drv: The target state to be programmed + * @idx: state index + * + * Called from the CPUidle framework to program the device to the + * specified target state selected by the governor. + */ +static int arm64_enter_powerdown(struct cpuidle_device *dev, + struct cpuidle_driver *drv, int idx) +{ + struct timespec ts_preidle, ts_postidle, ts_idle; + int ret; + + /* Used to keep track of the total time in idle */ + getnstimeofday(&ts_preidle); + + BUG_ON(!irqs_disabled()); + + cpu_pm_enter(); + + clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_ENTER, &dev->cpu); + + ret = cpu_suspend((unsigned long) dev, arm64_powerdown_finisher); + if (ret) + BUG(); + + clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_EXIT, &dev->cpu); + + cpu_pm_exit(); + + getnstimeofday(&ts_postidle); + local_irq_enable(); + ts_idle = timespec_sub(ts_postidle, ts_preidle); + + dev->last_residency = ts_idle.tv_nsec / NSEC_PER_USEC + + ts_idle.tv_sec * USEC_PER_SEC; + return idx; +} + +/* + * arm64_idle_init + * + * Registers the bl specific cpuidle driver with the cpuidle + * framework with the valid set of states. + */ +int __init arm64_idle_init(void) +{ + struct cpuidle_device *dev; + int i, cpu_id; + struct cpuidle_driver *drv = &arm64_idle_driver; + + drv->state_count = (sizeof(arm64_cpuidle_set) / + sizeof(struct cpuidle_state)); + + for (i = 0; i < drv->state_count; i++) { + memcpy(&drv->states[i], &arm64_cpuidle_set[i], + sizeof(struct cpuidle_state)); + } + + cpuidle_register_driver(drv); + + for_each_cpu(cpu_id, cpu_online_mask) { + pr_err("CPUidle for CPU%d registered\n", cpu_id); + dev = &per_cpu(arm64_idle_dev, cpu_id); + dev->cpu = cpu_id; + + dev->state_count = drv->state_count; + + if (cpuidle_register_device(dev)) { + printk(KERN_ERR "%s: Cpuidle register device failed\n", + __func__); + return -EIO; + } + } + + return 0; +} + +device_initcall(arm64_idle_init); -- 1.7.9.5