From: Park Bumgyu Date: Fri, 19 Jan 2018 01:42:49 +0000 (+0900) Subject: [COMMON] cpuidle: support cpuidle driver for exynos X-Git-Url: https://git.stricted.de/?a=commitdiff_plain;h=57e9d2e2718d759fb12bfe5d6024f44dfca0d0e2;p=GitHub%2FLineageOS%2Fandroid_kernel_motorola_exynos9610.git [COMMON] cpuidle: support cpuidle driver for exynos Change-Id: I206e3204542c8b6d87e5c48c830a8f67fd0288a8 Signed-off-by: Park Bumgyu --- diff --git a/drivers/cpuidle/Kconfig.arm b/drivers/cpuidle/Kconfig.arm index f52144808455..a543e046825d 100644 --- a/drivers/cpuidle/Kconfig.arm +++ b/drivers/cpuidle/Kconfig.arm @@ -70,6 +70,14 @@ config ARM_EXYNOS_CPUIDLE help Select this to enable cpuidle for Exynos processors +config ARM64_EXYNOS_CPUIDLE + bool "Exynos ARM64 CPU idle Driver" + depends on ARCH_EXYNOS && ARM64 + select CPU_IDLE_MULTIPLE_DRIVERS + select DT_IDLE_STATES + help + Select this to enable cpuidle for ARM64 Exynos processors. + config ARM_MVEBU_V7_CPUIDLE bool "CPU Idle Driver for mvebu v7 family processors" depends on ARCH_MVEBU && !ARM64 diff --git a/drivers/cpuidle/Makefile b/drivers/cpuidle/Makefile index 9d7176cee3d3..b07bac6e7fd1 100644 --- a/drivers/cpuidle/Makefile +++ b/drivers/cpuidle/Makefile @@ -19,6 +19,7 @@ obj-$(CONFIG_ARM_ZYNQ_CPUIDLE) += cpuidle-zynq.o obj-$(CONFIG_ARM_U8500_CPUIDLE) += cpuidle-ux500.o obj-$(CONFIG_ARM_AT91_CPUIDLE) += cpuidle-at91.o obj-$(CONFIG_ARM_EXYNOS_CPUIDLE) += cpuidle-exynos.o +obj-$(CONFIG_ARM64_EXYNOS_CPUIDLE) += cpuidle-exynos64.o profiler.o obj-$(CONFIG_ARM_CPUIDLE) += cpuidle-arm.o ############################################################################### diff --git a/drivers/cpuidle/cpuidle-exynos64.c b/drivers/cpuidle/cpuidle-exynos64.c new file mode 100644 index 000000000000..e6191fd48ef0 --- /dev/null +++ b/drivers/cpuidle/cpuidle-exynos64.c @@ -0,0 +1,227 @@ +/* + * Copyright (c) 2014 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * + * CPUIDLE driver for exynos 64bit + * + * 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 + +#include + +#include "dt_idle_states.h" +#include "profiler.h" + +/* + * Exynos cpuidle driver supports the below idle states + * + * IDLE_C1 : WFI(Wait For Interrupt) low-power state + * IDLE_C2 : Local CPU power gating + */ + +/*************************************************************************** + * Cpuidle state handler * + ***************************************************************************/ +static unsigned int prepare_idle(unsigned int cpu, int index) +{ + unsigned int entry_state = 0; + + if (index > 0) { + cpu_pm_enter(); + entry_state = exynos_cpu_pm_enter(cpu, index); + } + + cpuidle_profile_start(cpu, index, entry_state); + + return entry_state; +} + +static void post_idle(unsigned int cpu, int index, int fail) +{ + cpuidle_profile_finish(cpu, fail); + + if (!index) + return; + + exynos_cpu_pm_exit(cpu, fail); + cpu_pm_exit(); +} + +static int enter_idle(unsigned int index) +{ + /* + * idle state index 0 corresponds to wfi, should never be called + * from the cpu_suspend operations + */ + if (!index) { + cpu_do_idle(); + return 0; + } + + return arm_cpuidle_suspend(index); +} + +static int exynos_enter_idle(struct cpuidle_device *dev, + struct cpuidle_driver *drv, int index) +{ + int entry_state, ret = 0; + + entry_state = prepare_idle(dev->cpu, index); + + ret = enter_idle(entry_state); + + post_idle(dev->cpu, index, ret); + + /* + * If cpu fail to enter idle, it should not update state usage + * count. Driver have to return an error value to + * cpuidle_enter_state(). + */ + if (ret < 0) + return ret; + else + return index; +} + +/*************************************************************************** + * Define notifier call * + ***************************************************************************/ +static int exynos_cpuidle_reboot_notifier(struct notifier_block *this, + unsigned long event, void *_cmd) +{ + switch (event) { + case SYSTEM_POWER_OFF: + case SYS_RESTART: + cpuidle_pause(); + break; + } + + return NOTIFY_OK; +} + +static struct notifier_block exynos_cpuidle_reboot_nb = { + .notifier_call = exynos_cpuidle_reboot_notifier, +}; + +/*************************************************************************** + * Initialize cpuidle driver * + ***************************************************************************/ +#define exynos_idle_wfi_state(state) \ + do { \ + state.enter = exynos_enter_idle; \ + state.exit_latency = 1; \ + state.target_residency = 1; \ + state.power_usage = UINT_MAX; \ + strncpy(state.name, "WFI", CPUIDLE_NAME_LEN - 1); \ + strncpy(state.desc, "c1", CPUIDLE_DESC_LEN - 1); \ + } while (0) + +static struct cpuidle_driver exynos_idle_driver[NR_CPUS]; + +static const struct of_device_id exynos_idle_state_match[] __initconst = { + { .compatible = "exynos,idle-state", + .data = exynos_enter_idle }, + { }, +}; + +static int __init exynos_idle_driver_init(struct cpuidle_driver *drv, + struct cpumask *cpumask) +{ + int cpu = cpumask_first(cpumask); + + drv->name = kzalloc(sizeof("exynos_idleX"), GFP_KERNEL); + if (!drv->name) + return -ENOMEM; + + scnprintf((char *)drv->name, 12, "exynos_idle%d", cpu); + drv->owner = THIS_MODULE; + drv->cpumask = cpumask; + exynos_idle_wfi_state(drv->states[0]); + + return 0; +} + +static int __init exynos_idle_init(void) +{ + int ret, cpu, i; + + for_each_possible_cpu(cpu) { + ret = exynos_idle_driver_init(&exynos_idle_driver[cpu], + topology_sibling_cpumask(cpu)); + + if (ret) { + pr_err("failed to initialize cpuidle driver for cpu%d", + cpu); + goto out_unregister; + } + + /* + * 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(&exynos_idle_driver[cpu], + exynos_idle_state_match, 1); + if (ret < 0) { + pr_err("failed to initialize idle state for cpu%d\n", cpu); + goto out_unregister; + } + + /* + * Call arch CPU operations in order to initialize + * idle states suspend back-end specific data + */ + ret = arm_cpuidle_init(cpu); + if (ret) { + pr_err("failed to initialize idle operation for cpu%d\n", cpu); + goto out_unregister; + } + + ret = cpuidle_register(&exynos_idle_driver[cpu], NULL); + if (ret) { + pr_err("failed to register cpuidle for cpu%d\n", cpu); + goto out_unregister; + } + } + + register_reboot_notifier(&exynos_cpuidle_reboot_nb); + + cpuidle_profile_register(&exynos_idle_driver[0]); + + pr_info("Exynos cpuidle driver Initialized\n"); + + return 0; + +out_unregister: + for (i = 0; i <= cpu; i++) { + kfree(exynos_idle_driver[i].name); + + /* + * Cpuidle driver of variable "cpu" is always not registered. + * "cpu" should not call cpuidle_unregister(). + */ + if (i < cpu) + cpuidle_unregister(&exynos_idle_driver[i]); + } + + return ret; +} +device_initcall(exynos_idle_init); diff --git a/drivers/cpuidle/profiler.c b/drivers/cpuidle/profiler.c new file mode 100644 index 000000000000..841b57c6ed37 --- /dev/null +++ b/drivers/cpuidle/profiler.c @@ -0,0 +1,641 @@ +/* + * Copyright (c) 2015 Samsung Electronics Co., Ltd. + * http://www.samsung.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 +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include "profiler.h" + +static bool profile_started; + +/* + * "profile_info" contains profiling data for per cpu idle state which + * declared in cpuidle driver. + */ +static DEFINE_PER_CPU(struct cpuidle_profile_info, profile_info); + +/* + * "cpd_info" contains profiling data for CPD(Cluster Power Down) which + * is subordinate to C2 state idle. Each cluster has one element in + * cpd_info[]. + */ +static struct cpuidle_profile_info cpd_info[MAX_CLUSTER]; + +/* + * "sys_info" contains profiling data for system power mode + */ +static struct cpuidle_profile_info sys_info; + +/* + * "idle_ip_pending" contains which blocks to enter system power mode + */ +static int idle_ip_pending[NUM_SYS_POWERDOWN][NUM_IDLE_IP][IDLE_IP_REG_SIZE]; + +/* + * "idle_ip_list" contains IP name in IDLE_IP + */ +char *idle_ip_list[NUM_IDLE_IP][IDLE_IP_REG_SIZE]; + +/************************************************************************ + * Profiling * + ************************************************************************/ +/* + * If cpu does not enter idle state, cur_state has -EINVAL. By this, + * profiler can be aware of cpu state. + */ +#define state_entered(state) ((state < 0) ? 0 : 1) + +static void enter_idle_state(struct cpuidle_profile_info *info, + int state, ktime_t now) +{ + if (state_entered(info->cur_state)) + return; + + info->cur_state = state; + info->last_entry_time = now; + + info->usage[state].entry_count++; +} + +static void exit_idle_state(struct cpuidle_profile_info *info, + int state, ktime_t now, + int earlywakeup) +{ + s64 diff; + + if (!state_entered(info->cur_state)) + return; + + info->cur_state = -EINVAL; + + if (earlywakeup) { + /* + * If cpu cannot enter power mode, residency time + * should not be updated. + */ + info->usage[state].early_wakeup_count++; + return; + } + + diff = ktime_to_us(ktime_sub(now, info->last_entry_time)); + info->usage[state].time += diff; +} + +/* + * C2 subordinate state such as CPD and SICD can be entered by many cpus. + * The variables which contains these idle states need to keep + * synchronization. + */ +static DEFINE_SPINLOCK(substate_lock); + +void __cpuidle_profile_start(int cpu, int state, int substate) +{ + struct cpuidle_profile_info *info = &per_cpu(profile_info, cpu); + ktime_t now = ktime_get(); + + /* + * Start to profile idle state. profile_info is per-CPU variable, + * it does not need to synchronization. + */ + enter_idle_state(info, state, now); + + /* Start to profile subordinate idle state. */ + if (substate) { + spin_lock(&substate_lock); + + if (state == PROFILE_C2) { + switch (substate) { + case C2_CPD: + info = &cpd_info[to_cluster(cpu)]; + enter_idle_state(info, 0, now); + break; + case C2_SICD: + /* + * SICD is a system power mode but it is also + * C2 subordinate state. In case of SICD, + * profiler updates sys_info although state is + * PROFILE_C2. + */ + info = &sys_info; + enter_idle_state(info, SYS_SICD, now); + break; + } + } else if (state == PROFILE_SYS) + enter_idle_state(&sys_info, substate, now); + + spin_unlock(&substate_lock); + } +} + +void cpuidle_profile_start(int cpu, int state, int substate) +{ + /* + * Return if profile is not started + */ + if (!profile_started) + return; + + __cpuidle_profile_start(cpu, state, substate); +} + +void __cpuidle_profile_finish(int cpu, int earlywakeup) +{ + struct cpuidle_profile_info *info = &per_cpu(profile_info, cpu); + int state = info->cur_state; + ktime_t now = ktime_get(); + + exit_idle_state(info, state, now, earlywakeup); + + spin_lock(&substate_lock); + + /* + * Subordinate state can be wakeup by many cpus. We cannot predict + * which cpu wakeup from idle state, profiler always try to update + * residency time of subordinate state. To avoid duplicate updating, + * exit_idle_state() checks validation. + */ + if (has_sub_state(state)) { + info = &cpd_info[to_cluster(cpu)]; + exit_idle_state(info, info->cur_state, now, earlywakeup); + + info = &sys_info; + exit_idle_state(info, info->cur_state, now, earlywakeup); + } + + spin_unlock(&substate_lock); +} + +void cpuidle_profile_finish(int cpu, int earlywakeup) +{ + /* + * Return if profile is not started + */ + if (!profile_started) + return; + + __cpuidle_profile_finish(cpu, earlywakeup); +} + +/* + * Before system enters system power mode, it checks idle-ip status. Its + * status is conveyed to cpuidle_profile_collect_idle_ip(). + */ +void cpuidle_profile_collect_idle_ip(int mode, int index, + unsigned int idle_ip) +{ + int i; + + /* + * Return if profile is not started + */ + if (!profile_started) + return; + + for (i = 0; i < IDLE_IP_REG_SIZE; i++) { + /* + * If bit of idle_ip has 1, IP corresponding to its bit + * is not idle. + */ + if (idle_ip & (1 << i)) + idle_ip_pending[mode][index][i]++; + } +} + +/************************************************************************ + * Show result * + ************************************************************************/ +static ktime_t profile_start_time; +static ktime_t profile_finish_time; +static s64 profile_time; + +static int calculate_percent(s64 residency) +{ + if (!residency) + return 0; + + residency *= 100; + do_div(residency, profile_time); + + return residency; +} + +static unsigned long long sum_idle_time(int cpu) +{ + int i; + unsigned long long idle_time = 0; + struct cpuidle_profile_info *info = &per_cpu(profile_info, cpu); + + for (i = 0; i < info->state_count; i++) + idle_time += info->usage[i].time; + + return idle_time; +} + +static int total_idle_ratio(int cpu) +{ + return calculate_percent(sum_idle_time(cpu)); +} + +static void show_result(void) +{ + int i, idle_ip, bit, cpu; + struct cpuidle_profile_info *info; + int state_count; + + pr_info("#############################################################\n"); + pr_info("Profiling Time : %lluus\n", profile_time); + + pr_info("\n"); + + pr_info("[total idle ratio]\n"); + pr_info("#cpu #time #ratio\n"); + for_each_possible_cpu(cpu) + pr_info("cpu%d %10lluus %3u%%\n", cpu, + sum_idle_time(cpu), total_idle_ratio(cpu)); + + pr_info("\n"); + + /* + * All profile_info has same state_count. As a representative, + * cpu0's is used. + */ + state_count = per_cpu(profile_info, 0).state_count; + + for (i = 0; i < state_count; i++) { + pr_info("[state%d]\n", i); + pr_info("#cpu #entry #early #time #ratio\n"); + for_each_possible_cpu(cpu) { + info = &per_cpu(profile_info, cpu); + pr_info("cpu%d %5u %5u %10lluus %3u%%\n", cpu, + info->usage[i].entry_count, + info->usage[i].early_wakeup_count, + info->usage[i].time, + calculate_percent(info->usage[i].time)); + } + + pr_info("\n"); + } + + pr_info("[Cluster Power Down]\n"); + pr_info("#cluster #entry #early #time #ratio\n"); + for_each_cluster(i) { + pr_info("cl_%s %5u %5u %10lluus %3u%%\n", + i == to_cluster(0) ? "boot " : "nonboot", + cpd_info[i].usage->entry_count, + cpd_info[i].usage->early_wakeup_count, + cpd_info[i].usage->time, + calculate_percent(cpd_info[i].usage->time)); + } + + pr_info("\n"); + + pr_info("[System Power Mode]\n"); + pr_info("#mode #entry #early #time #ratio\n"); + for_each_syspwr_mode(i) { + pr_info("%-13s %5u %5u %10lluus %3u%%\n", + get_sys_powerdown_str(i), + sys_info.usage[i].entry_count, + sys_info.usage[i].early_wakeup_count, + sys_info.usage[i].time, + calculate_percent(sys_info.usage[i].time)); + } + + pr_info("\n"); + + pr_info("[LPM blockers]\n"); + for_each_syspwr_mode(i) { + for_each_idle_ip(idle_ip) { + for (bit = 0; bit < IDLE_IP_REG_SIZE; bit++) { + if (idle_ip_pending[i][idle_ip][bit]) + pr_info("%s block by IDLE_IP%d[%d](%s, count = %d)\n", + get_sys_powerdown_str(i), + idle_ip, bit, idle_ip_list[idle_ip][bit], + idle_ip_pending[i][idle_ip][bit]); + } + } + } + + pr_info("\n"); + pr_info("#############################################################\n"); +} + +/************************************************************************ + * Profile control * + ************************************************************************/ +static void clear_time(ktime_t *time) +{ + *time = 0; +} + +static void clear_profile_info(struct cpuidle_profile_info *info) +{ + memset(info->usage, 0, + sizeof(struct cpuidle_profile_state_usage) * info->state_count); + + clear_time(&info->last_entry_time); + info->cur_state = -EINVAL; +} + +static void reset_profile_record(void) +{ + int i; + + clear_time(&profile_start_time); + clear_time(&profile_finish_time); + + for_each_possible_cpu(i) + clear_profile_info(&per_cpu(profile_info, i)); + + for_each_cluster(i) + clear_profile_info(&cpd_info[i]); + + clear_profile_info(&sys_info); + + memset(idle_ip_pending, 0, + NUM_SYS_POWERDOWN * NUM_IDLE_IP * IDLE_IP_REG_SIZE * sizeof(int)); +} + +static void call_cpu_start_profile(void *p) {}; +static void call_cpu_finish_profile(void *p) {}; + +static void cpuidle_profile_main_start(void) +{ + if (profile_started) { + pr_err("cpuidle profile is ongoing\n"); + return; + } + + reset_profile_record(); + profile_start_time = ktime_get(); + + profile_started = 1; + + /* Wakeup all cpus and clear own profile data to start profile */ + preempt_disable(); + smp_call_function(call_cpu_start_profile, NULL, 1); + preempt_enable(); + + pr_info("cpuidle profile start\n"); +} + +static void cpuidle_profile_main_finish(void) +{ + if (!profile_started) { + pr_err("CPUIDLE profile does not start yet\n"); + return; + } + + pr_info("cpuidle profile finish\n"); + + /* Wakeup all cpus to update own profile data to finish profile */ + preempt_disable(); + smp_call_function(call_cpu_finish_profile, NULL, 1); + preempt_enable(); + + profile_started = 0; + + profile_finish_time = ktime_get(); + profile_time = ktime_to_us(ktime_sub(profile_finish_time, + profile_start_time)); + + show_result(); +} + +/********************************************************************* + * Sysfs interface * + *********************************************************************/ +static ssize_t show_sysfs_result(struct kobject *kobj, + struct kobj_attribute *attr, + char *buf) +{ + int ret = 0; + int i, cpu, idle_ip, bit; + struct cpuidle_profile_info *info; + int state_count; + + if (profile_started) { + ret += snprintf(buf + ret, PAGE_SIZE - ret, + "CPUIDLE profile is ongoing\n"); + return ret; + } + + if (profile_time == 0) { + ret += snprintf(buf + ret, PAGE_SIZE - ret, + "CPUIDLE profiler has not started yet\n"); + return ret; + } + + ret += snprintf(buf + ret, PAGE_SIZE - ret, + "#############################################################\n"); + ret += snprintf(buf + ret, PAGE_SIZE - ret, + "Profiling Time : %lluus\n", profile_time); + + ret += snprintf(buf + ret, PAGE_SIZE - ret, + "\n"); + + ret += snprintf(buf + ret, PAGE_SIZE - ret, + "[total idle ratio]\n"); + ret += snprintf(buf + ret, PAGE_SIZE - ret, + "#cpu #time #ratio\n"); + for_each_possible_cpu(cpu) + ret += snprintf(buf + ret, PAGE_SIZE - ret, + "cpu%d %10lluus %3u%%\n", + cpu, sum_idle_time(cpu), total_idle_ratio(cpu)); + + ret += snprintf(buf + ret, PAGE_SIZE - ret, + "\n"); + + /* + * All profile_info has same state_count. As a representative, + * cpu0's is used. + */ + state_count = per_cpu(profile_info, 0).state_count; + + for (i = 0; i < state_count; i++) { + ret += snprintf(buf + ret, PAGE_SIZE - ret, + "[state%d]\n", i); + ret += snprintf(buf + ret, PAGE_SIZE - ret, + "#cpu #entry #early #time #ratio\n"); + for_each_possible_cpu(cpu) { + info = &per_cpu(profile_info, cpu); + ret += snprintf(buf + ret, PAGE_SIZE - ret, + "cpu%d %5u %5u %10lluus %3u%%\n", + cpu, + info->usage[i].entry_count, + info->usage[i].early_wakeup_count, + info->usage[i].time, + calculate_percent(info->usage[i].time)); + } + + ret += snprintf(buf + ret, PAGE_SIZE - ret, + "\n"); + } + + ret += snprintf(buf + ret, PAGE_SIZE - ret, + "[CPD] - Cluster Power Down\n"); + ret += snprintf(buf + ret, PAGE_SIZE - ret, + "#cluster #entry #early #time #ratio\n"); + for_each_cluster(i) { + ret += snprintf(buf + ret, PAGE_SIZE - ret, + "cl_%s %5u %5u %10lluus %3u%%\n", + i == to_cluster(0) ? "boot " : "nonboot", + cpd_info[i].usage->entry_count, + cpd_info[i].usage->early_wakeup_count, + cpd_info[i].usage->time, + calculate_percent(cpd_info[i].usage->time)); + } + + ret += snprintf(buf + ret, PAGE_SIZE - ret, + "\n"); + + ret += snprintf(buf + ret, PAGE_SIZE - ret, + "[LPM] - Low Power Mode\n"); + ret += snprintf(buf + ret, PAGE_SIZE - ret, + "#mode #entry #early #time #ratio\n"); + for_each_syspwr_mode(i) { + ret += snprintf(buf + ret, PAGE_SIZE - ret, + "%-9s %5u %5u %10lluus %3u%%\n", + get_sys_powerdown_str(i), + sys_info.usage[i].entry_count, + sys_info.usage[i].early_wakeup_count, + sys_info.usage[i].time, + calculate_percent(sys_info.usage[i].time)); + } + + ret += snprintf(buf + ret, PAGE_SIZE - ret, + "\n"); + + ret += snprintf(buf + ret, PAGE_SIZE - ret, + "[LPM blockers]\n"); + for_each_syspwr_mode(i) { + for_each_idle_ip(idle_ip) { + for (bit = 0; bit < IDLE_IP_REG_SIZE; bit++) { + if (idle_ip_pending[i][idle_ip][bit]) + ret += snprintf(buf + ret, PAGE_SIZE - ret, + "%s block by IDLE_IP%d[%d](%s, count = %d)\n", + get_sys_powerdown_str(i), + idle_ip, bit, idle_ip_list[idle_ip][bit], + idle_ip_pending[i][idle_ip][bit]); + } + } + } + + ret += snprintf(buf + ret, PAGE_SIZE - ret, + "#############################################################\n"); + + return ret; +} + +static ssize_t show_cpuidle_profile(struct kobject *kobj, + struct kobj_attribute *attr, + char *buf) +{ + int ret = 0; + + if (profile_started) + ret += snprintf(buf + ret, PAGE_SIZE - ret, + "CPUIDLE profile is ongoing\n"); + else + ret = show_sysfs_result(kobj, attr, buf); + + + return ret; +} + +static ssize_t store_cpuidle_profile(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t count) +{ + int input; + + if (!sscanf(buf, "%1d", &input)) + return -EINVAL; + + if (!!input) + cpuidle_profile_main_start(); + else + cpuidle_profile_main_finish(); + + return count; +} + +static struct kobj_attribute cpuidle_profile_attr = + __ATTR(profile, 0644, show_cpuidle_profile, store_cpuidle_profile); + +static struct attribute *cpuidle_profile_attrs[] = { + &cpuidle_profile_attr.attr, + NULL, +}; + +static const struct attribute_group cpuidle_profile_group = { + .attrs = cpuidle_profile_attrs, +}; + + +/********************************************************************* + * Initialize cpuidle profiler * + *********************************************************************/ +static void __init cpuidle_profile_info_init(struct cpuidle_profile_info *info, + int state_count) +{ + int size = sizeof(struct cpuidle_profile_state_usage) * state_count; + + info->state_count = state_count; + info->usage = kmalloc(size, GFP_KERNEL); + if (!info->usage) { + pr_err("%s:%d: Memory allocation failed\n", __func__, __LINE__); + return; + } +} + +void __init cpuidle_profile_register(struct cpuidle_driver *drv) +{ + int idle_state_count = drv->state_count; + int i; + + /* Initialize each cpuidle state information */ + for_each_possible_cpu(i) + cpuidle_profile_info_init(&per_cpu(profile_info, i), + idle_state_count); + + /* Initiailize CPD(Cluster Power Down) information */ + for_each_cluster(i) + cpuidle_profile_info_init(&cpd_info[i], 1); + + /* Initiailize System power mode information */ + cpuidle_profile_info_init(&sys_info, NUM_SYS_POWERDOWN); +} + +static int __init cpuidle_profile_init(void) +{ + struct class *class; + struct device *dev; + + class = class_create(THIS_MODULE, "cpuidle"); + dev = device_create(class, NULL, 0, NULL, "cpuidle_profiler"); + + if (sysfs_create_group(&dev->kobj, &cpuidle_profile_group)) { + pr_err("CPUIDLE Profiler : error to create sysfs\n"); + return -EINVAL; + } + + exynos_get_idle_ip_list(idle_ip_list); + + return 0; +} +late_initcall(cpuidle_profile_init); diff --git a/drivers/cpuidle/profiler.h b/drivers/cpuidle/profiler.h new file mode 100644 index 000000000000..a13b8637ec6a --- /dev/null +++ b/drivers/cpuidle/profiler.h @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2015 Samsung Electronics Co., Ltd. + * + * 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. + */ + +#ifndef CPUIDLE_PROFILE_H +#define CPUIDLE_PROFILE_H __FILE__ + +#include +#include +#include + +#include + +/* + * cpuidle major state + */ +#define PROFILE_C1 0 +#define PROFILE_C2 1 +#define PROFILE_SYS 2 + +#define has_sub_state(_state) (_state > PROFILE_C1 ? 1 : 0) + +/* + * C2 sub state + */ +#define C2_CPD PSCI_CLUSTER_SLEEP +#define C2_SICD PSCI_SYSTEM_IDLE + +#define to_cluster(cpu) MPIDR_AFFINITY_LEVEL(cpu_logical_map(cpu), 1) + +struct cpuidle_profile_state_usage { + unsigned int entry_count; + unsigned int early_wakeup_count; + unsigned long long time; +}; + +struct cpuidle_profile_info { + ktime_t last_entry_time; + int cur_state; + int state_count; + + struct cpuidle_profile_state_usage *usage; +}; + +extern void cpuidle_profile_start(int cpu, int state, int sub_state); +extern void cpuidle_profile_finish(int cpuid, int early_wakeup); +extern void cpuidle_profile_register(struct cpuidle_driver *drv); + +#ifdef CONFIG_CPU_IDLE +extern void cpuidle_profile_collect_idle_ip(int mode, + int index, unsigned int idle_ip); +#else +static inline void cpuidle_profile_collect_idle_ip(int mode, + int index, unsigned int idle_ip) +{ + /* Nothing to do */ +} +#endif + +#endif /* CPUIDLE_PROFILE_H */