1403 lines
35 KiB
C
Executable File
1403 lines
35 KiB
C
Executable File
/*
|
|
* Copyright (c) 2016 Park Bumgyu, Samsung Electronics Co., Ltd <bumgyu.park@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.
|
|
*
|
|
* Exynos ACME(A Cpufreq that Meets Every chipset) driver implementation
|
|
*/
|
|
|
|
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
|
|
|
#include <linux/init.h>
|
|
#include <linux/of.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/cpu.h>
|
|
#include <linux/cpumask.h>
|
|
#include <linux/cpufreq.h>
|
|
#include <linux/debug-snapshot.h>
|
|
#include <linux/pm_opp.h>
|
|
#include <linux/cpu_cooling.h>
|
|
#include <linux/suspend.h>
|
|
#include <linux/ems.h>
|
|
|
|
#include <soc/samsung/cal-if.h>
|
|
#include <soc/samsung/ect_parser.h>
|
|
#include <soc/samsung/exynos-cpuhp.h>
|
|
#include <soc/samsung/exynos-cpupm.h>
|
|
#include <soc/samsung/exynos-emc.h>
|
|
|
|
#include "exynos-acme.h"
|
|
|
|
/*
|
|
* list head of cpufreq domain
|
|
*/
|
|
LIST_HEAD(domains);
|
|
|
|
/*
|
|
* list head of units which have cpufreq policy dependancy
|
|
*/
|
|
LIST_HEAD(ready_list);
|
|
|
|
/*********************************************************************
|
|
* HELPER FUNCTION *
|
|
*********************************************************************/
|
|
static struct exynos_cpufreq_domain *find_domain(unsigned int cpu)
|
|
{
|
|
struct exynos_cpufreq_domain *domain;
|
|
|
|
list_for_each_entry(domain, &domains, list)
|
|
if (cpumask_test_cpu(cpu, &domain->cpus))
|
|
return domain;
|
|
|
|
pr_err("cannot find cpufreq domain by cpu\n");
|
|
return NULL;
|
|
}
|
|
|
|
static
|
|
struct exynos_cpufreq_domain *find_domain_pm_qos_class(int pm_qos_class)
|
|
{
|
|
struct exynos_cpufreq_domain *domain;
|
|
|
|
list_for_each_entry(domain, &domains, list)
|
|
if (domain->pm_qos_min_class == pm_qos_class ||
|
|
domain->pm_qos_max_class == pm_qos_class)
|
|
return domain;
|
|
|
|
pr_err("cannot find cpufreq domain by PM QoS class\n");
|
|
return NULL;
|
|
}
|
|
|
|
struct
|
|
exynos_cpufreq_domain *find_domain_cpumask(const struct cpumask *mask)
|
|
{
|
|
struct exynos_cpufreq_domain *domain;
|
|
|
|
list_for_each_entry(domain, &domains, list)
|
|
if (cpumask_intersects(mask, &domain->cpus))
|
|
return domain;
|
|
|
|
pr_err("cannot find cpufreq domain by cpumask\n");
|
|
return NULL;
|
|
}
|
|
|
|
struct list_head *get_domain_list(void)
|
|
{
|
|
return &domains;
|
|
}
|
|
|
|
struct exynos_cpufreq_domain *first_domain(void)
|
|
{
|
|
return list_first_entry(&domains,
|
|
struct exynos_cpufreq_domain, list);
|
|
}
|
|
|
|
struct exynos_cpufreq_domain *last_domain(void)
|
|
{
|
|
return list_last_entry(&domains,
|
|
struct exynos_cpufreq_domain, list);
|
|
}
|
|
|
|
int exynos_cpufreq_domain_count(void)
|
|
{
|
|
return last_domain()->id + 1;
|
|
}
|
|
|
|
/* __enable_domain/__disable_domain MUST be called with holding domain->lock */
|
|
static inline void __enable_domain(struct exynos_cpufreq_domain *domain)
|
|
{
|
|
domain->enabled = true;
|
|
}
|
|
|
|
static inline void __disable_domain(struct exynos_cpufreq_domain *domain)
|
|
{
|
|
domain->enabled = false;
|
|
}
|
|
|
|
static void enable_domain(struct exynos_cpufreq_domain *domain)
|
|
{
|
|
mutex_lock(&domain->lock);
|
|
__enable_domain(domain);
|
|
mutex_unlock(&domain->lock);
|
|
}
|
|
|
|
static void disable_domain(struct exynos_cpufreq_domain *domain)
|
|
{
|
|
mutex_lock(&domain->lock);
|
|
__disable_domain(domain);
|
|
mutex_unlock(&domain->lock);
|
|
}
|
|
|
|
static bool static_governor(struct cpufreq_policy *policy)
|
|
{
|
|
/*
|
|
* During cpu hotplug in sequence, governor can be empty for
|
|
* a while. In this case, we regard governor as default
|
|
* governor. Exynos never use static governor as default.
|
|
*/
|
|
if (!policy->governor)
|
|
return false;
|
|
|
|
if ((strcmp(policy->governor->name, "userspace") == 0)
|
|
|| (strcmp(policy->governor->name, "performance") == 0)
|
|
|| (strcmp(policy->governor->name, "powersave") == 0))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
static unsigned int index_to_freq(struct cpufreq_frequency_table *table,
|
|
unsigned int index)
|
|
{
|
|
return table[index].frequency;
|
|
}
|
|
|
|
|
|
/*********************************************************************
|
|
* FREQUENCY SCALING *
|
|
*********************************************************************/
|
|
/*
|
|
* Depending on cluster structure, it cannot be possible to get/set
|
|
* cpu frequency while cluster is off. For this, disable cluster-wide
|
|
* power mode while getting/setting frequency.
|
|
*/
|
|
static unsigned int get_freq(struct exynos_cpufreq_domain *domain)
|
|
{
|
|
int wakeup_flag = 0;
|
|
unsigned int freq;
|
|
struct cpumask temp;
|
|
|
|
cpumask_and(&temp, &domain->cpus, cpu_active_mask);
|
|
|
|
if (cpumask_empty(&temp))
|
|
return domain->old;
|
|
|
|
if (domain->need_awake) {
|
|
if (likely(domain->old))
|
|
return domain->old;
|
|
|
|
wakeup_flag = 1;
|
|
disable_power_mode(cpumask_any(&domain->cpus), POWERMODE_TYPE_CLUSTER);
|
|
}
|
|
|
|
freq = (unsigned int)cal_dfs_get_rate(domain->cal_id);
|
|
if (!freq) {
|
|
/* On changing state, CAL returns 0 */
|
|
freq = domain->old;
|
|
}
|
|
|
|
if (unlikely(wakeup_flag))
|
|
enable_power_mode(cpumask_any(&domain->cpus), POWERMODE_TYPE_CLUSTER);
|
|
|
|
return freq;
|
|
}
|
|
|
|
static int set_freq(struct exynos_cpufreq_domain *domain,
|
|
unsigned int target_freq)
|
|
{
|
|
int err;
|
|
|
|
dbg_snapshot_printk("ID %d: %d -> %d (%d)\n",
|
|
domain->id, domain->old, target_freq, DSS_FLAG_IN);
|
|
|
|
if (domain->need_awake)
|
|
disable_power_mode(cpumask_any(&domain->cpus), POWERMODE_TYPE_CLUSTER);
|
|
|
|
/* check target freq is available */
|
|
emc_check_available_freq(&domain->cpus, target_freq);
|
|
err = cal_dfs_set_rate(domain->cal_id, target_freq);
|
|
if (err < 0)
|
|
pr_err("failed to scale frequency of domain%d (%d -> %d)\n",
|
|
domain->id, domain->old, target_freq);
|
|
|
|
if (domain->need_awake)
|
|
enable_power_mode(cpumask_any(&domain->cpus), POWERMODE_TYPE_CLUSTER);
|
|
|
|
dbg_snapshot_printk("ID %d: %d -> %d (%d)\n",
|
|
domain->id, domain->old, target_freq, DSS_FLAG_OUT);
|
|
|
|
return err;
|
|
}
|
|
|
|
static unsigned int apply_pm_qos(struct exynos_cpufreq_domain *domain,
|
|
struct cpufreq_policy *policy,
|
|
unsigned int target_freq)
|
|
{
|
|
unsigned int freq;
|
|
int qos_min, qos_max;
|
|
|
|
/*
|
|
* In case of static governor, it should garantee to scale to
|
|
* target, it does not apply PM QoS.
|
|
*/
|
|
if (static_governor(policy))
|
|
return target_freq;
|
|
|
|
qos_min = pm_qos_request(domain->pm_qos_min_class);
|
|
qos_max = pm_qos_request(domain->pm_qos_max_class);
|
|
|
|
if (qos_min < 0 || qos_max < 0)
|
|
return target_freq;
|
|
|
|
freq = max((unsigned int)qos_min, target_freq);
|
|
freq = min((unsigned int)qos_max, freq);
|
|
|
|
return freq;
|
|
}
|
|
|
|
static int pre_scale(void)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static int post_scale(void)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static int scale(struct exynos_cpufreq_domain *domain,
|
|
struct cpufreq_policy *policy,
|
|
unsigned int target_freq)
|
|
{
|
|
int ret;
|
|
struct cpufreq_freqs freqs = {
|
|
.cpu = policy->cpu,
|
|
.old = domain->old,
|
|
.new = target_freq,
|
|
.flags = 0,
|
|
};
|
|
|
|
cpufreq_freq_transition_begin(policy, &freqs);
|
|
dbg_snapshot_freq(domain->id, domain->old, target_freq, DSS_FLAG_IN);
|
|
|
|
ret = pre_scale();
|
|
if (ret)
|
|
goto fail_scale;
|
|
|
|
/* Scale frequency by hooked function, set_freq() */
|
|
ret = set_freq(domain, target_freq);
|
|
if (ret)
|
|
goto fail_scale;
|
|
|
|
ret = post_scale();
|
|
if (ret)
|
|
goto fail_scale;
|
|
|
|
fail_scale:
|
|
/* In scaling failure case, logs -1 to exynos snapshot */
|
|
dbg_snapshot_freq(domain->id, domain->old, target_freq,
|
|
ret < 0 ? ret : DSS_FLAG_OUT);
|
|
cpufreq_freq_transition_end(policy, &freqs, ret);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int update_freq(struct exynos_cpufreq_domain *domain,
|
|
unsigned int freq)
|
|
{
|
|
struct cpufreq_policy *policy;
|
|
int ret;
|
|
struct cpumask mask;
|
|
|
|
pr_debug("update frequency of domain%d to %d kHz\n",
|
|
domain->id, freq);
|
|
|
|
cpumask_and(&mask, &domain->cpus, cpu_online_mask);
|
|
if (cpumask_empty(&mask))
|
|
return -ENODEV;
|
|
policy = cpufreq_cpu_get(cpumask_first(&mask));
|
|
if (!policy)
|
|
return -EINVAL;
|
|
|
|
down_read(&policy->rwsem);
|
|
if (static_governor(policy)) {
|
|
up_read(&policy->rwsem);
|
|
cpufreq_cpu_put(policy);
|
|
return 0;
|
|
}
|
|
up_read(&policy->rwsem);
|
|
|
|
ret = cpufreq_driver_target(policy, freq, CPUFREQ_RELATION_H);
|
|
cpufreq_cpu_put(policy);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*********************************************************************
|
|
* EXYNOS CPUFREQ DRIVER INTERFACE *
|
|
*********************************************************************/
|
|
static int exynos_cpufreq_driver_init(struct cpufreq_policy *policy)
|
|
{
|
|
struct exynos_cpufreq_domain *domain = find_domain(policy->cpu);
|
|
int ret;
|
|
|
|
if (!domain)
|
|
return -EINVAL;
|
|
|
|
ret = cpufreq_table_validate_and_show(policy, domain->freq_table);
|
|
if (ret) {
|
|
pr_err("%s: invalid frequency table: %d\n", __func__, ret);
|
|
return ret;
|
|
}
|
|
|
|
policy->cur = get_freq(domain);
|
|
policy->cpuinfo.transition_latency = TRANSITION_LATENCY;
|
|
cpumask_copy(policy->cpus, &domain->cpus);
|
|
|
|
pr_info("CPUFREQ domain%d registered\n", domain->id);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int exynos_cpufreq_verify(struct cpufreq_policy *policy)
|
|
{
|
|
struct exynos_cpufreq_domain *domain = find_domain(policy->cpu);
|
|
|
|
if (!domain)
|
|
return -EINVAL;
|
|
|
|
return cpufreq_frequency_table_verify(policy, domain->freq_table);
|
|
}
|
|
|
|
static int __exynos_cpufreq_target(struct cpufreq_policy *policy,
|
|
unsigned int target_freq,
|
|
unsigned int relation)
|
|
{
|
|
struct exynos_cpufreq_domain *domain = find_domain(policy->cpu);
|
|
unsigned int index;
|
|
int ret = 0;
|
|
|
|
if (!domain)
|
|
return -EINVAL;
|
|
|
|
mutex_lock(&domain->lock);
|
|
|
|
if (!domain->enabled)
|
|
goto out;
|
|
|
|
if (domain->old != get_freq(domain)) {
|
|
pr_err("oops, inconsistency between domain->old:%d, real clk:%d\n",
|
|
domain->old, get_freq(domain));
|
|
BUG_ON(1);
|
|
}
|
|
|
|
/*
|
|
* Update target_freq.
|
|
* Updated target_freq is in between minimum and maximum PM QoS/policy,
|
|
* priority of policy is higher.
|
|
*/
|
|
index = cpufreq_frequency_table_target(policy, target_freq, relation);
|
|
if (index < 0) {
|
|
pr_err("target frequency(%d) out of range\n", target_freq);
|
|
goto out;
|
|
}
|
|
|
|
target_freq = index_to_freq(domain->freq_table, index);
|
|
|
|
/* Target is same as current, skip scaling */
|
|
if (domain->old == target_freq)
|
|
goto out;
|
|
|
|
ret = scale(domain, policy, target_freq);
|
|
if (ret)
|
|
goto out;
|
|
|
|
pr_debug("CPUFREQ domain%d frequency change %u kHz -> %u kHz\n",
|
|
domain->id, domain->old, target_freq);
|
|
|
|
domain->old = target_freq;
|
|
arch_set_freq_scale(&domain->cpus, target_freq, policy->max);
|
|
|
|
out:
|
|
mutex_unlock(&domain->lock);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static unsigned int exynos_cpufreq_get(unsigned int cpu)
|
|
{
|
|
struct exynos_cpufreq_domain *domain = find_domain(cpu);
|
|
|
|
if (!domain)
|
|
return 0;
|
|
|
|
return get_freq(domain);
|
|
}
|
|
|
|
static int exynos_cpufreq_target(struct cpufreq_policy *policy,
|
|
unsigned int target_freq,
|
|
unsigned int relation)
|
|
{
|
|
struct exynos_cpufreq_domain *domain = find_domain(policy->cpu);
|
|
unsigned long freq;
|
|
unsigned int index;
|
|
unsigned int policy_min, policy_max;
|
|
unsigned int pm_qos_min, pm_qos_max;
|
|
|
|
if (!domain)
|
|
return -EINVAL;
|
|
|
|
target_freq = apply_pm_qos(domain, policy, target_freq);
|
|
|
|
if (list_empty(&domain->dm_list))
|
|
return __exynos_cpufreq_target(policy, target_freq, relation);
|
|
|
|
index = cpufreq_frequency_table_target(policy, target_freq, relation);
|
|
if (index < 0) {
|
|
pr_err("target frequency(%d) out of range\n", target_freq);
|
|
}
|
|
|
|
mutex_lock(&domain->lock);
|
|
|
|
freq = (unsigned long)index_to_freq(domain->freq_table, index);
|
|
if (domain->old == freq) {
|
|
mutex_unlock(&domain->lock);
|
|
return 0;
|
|
}
|
|
mutex_unlock(&domain->lock);
|
|
|
|
policy_min = policy->min;
|
|
policy_max = policy->max;
|
|
|
|
pm_qos_min = pm_qos_request(domain->pm_qos_min_class);
|
|
pm_qos_max = pm_qos_request(domain->pm_qos_max_class);
|
|
|
|
freq = (unsigned long)target_freq;
|
|
return policy_update_with_DM_CALL(domain->dm_type, max(policy_min, pm_qos_min),
|
|
min(policy_max, pm_qos_max), &freq);
|
|
}
|
|
|
|
static int __exynos_cpufreq_suspend(struct exynos_cpufreq_domain *domain)
|
|
{
|
|
unsigned int freq;
|
|
|
|
if (!domain)
|
|
return -EINVAL;
|
|
|
|
/* To handle reboot faster, it does not thrrotle frequency of domain0 */
|
|
if (system_state == SYSTEM_RESTART && domain->id != 0)
|
|
freq = domain->min_freq;
|
|
else
|
|
freq = domain->resume_freq;
|
|
|
|
pm_qos_update_request(&domain->min_qos_req, freq);
|
|
pm_qos_update_request(&domain->max_qos_req, freq);
|
|
|
|
/* To sync current freq with resume freq, check until they become same */
|
|
mutex_lock(&domain->lock);
|
|
while (domain->old > freq) {
|
|
mutex_unlock(&domain->lock);
|
|
update_freq(domain, freq);
|
|
mutex_lock(&domain->lock);
|
|
}
|
|
|
|
/*
|
|
* Although cpufreq governor is stopped in cpufreq_suspend(),
|
|
* afterwards, frequency change can be requested by
|
|
* PM QoS. To prevent chainging frequency after
|
|
* cpufreq suspend, disable scaling for all domains.
|
|
*/
|
|
__disable_domain(domain);
|
|
mutex_unlock(&domain->lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int exynos_cpufreq_suspend(struct cpufreq_policy *policy)
|
|
{
|
|
struct exynos_cpufreq_domain *domain = find_domain(policy->cpu);
|
|
|
|
return __exynos_cpufreq_suspend(domain);
|
|
}
|
|
|
|
static int __exynos_cpufreq_resume(struct exynos_cpufreq_domain *domain)
|
|
{
|
|
if (!domain)
|
|
return -EINVAL;
|
|
|
|
enable_domain(domain);
|
|
|
|
pm_qos_update_request(&domain->min_qos_req, domain->min_freq);
|
|
pm_qos_update_request(&domain->max_qos_req, domain->max_freq);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int exynos_cpufreq_resume(struct cpufreq_policy *policy)
|
|
{
|
|
struct exynos_cpufreq_domain *domain = find_domain(policy->cpu);
|
|
|
|
return __exynos_cpufreq_resume(domain);
|
|
}
|
|
|
|
static void exynos_cpufreq_ready(struct cpufreq_policy *policy)
|
|
{
|
|
struct exynos_cpufreq_ready_block *ready_block;
|
|
|
|
list_for_each_entry(ready_block, &ready_list, list) {
|
|
if (ready_block->update)
|
|
ready_block->update(policy);
|
|
if (ready_block->get_target)
|
|
ready_block->get_target(policy, exynos_cpufreq_target);
|
|
}
|
|
}
|
|
|
|
static int exynos_cpufreq_exit(struct cpufreq_policy *policy)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static int exynos_cpufreq_pm_notifier(struct notifier_block *notifier,
|
|
unsigned long pm_event, void *v)
|
|
{
|
|
struct exynos_cpufreq_domain *domain;
|
|
|
|
switch (pm_event) {
|
|
case PM_SUSPEND_PREPARE:
|
|
list_for_each_entry_reverse(domain, &domains, list)
|
|
if (__exynos_cpufreq_suspend(domain))
|
|
return NOTIFY_BAD;
|
|
break;
|
|
case PM_POST_SUSPEND:
|
|
list_for_each_entry_reverse(domain, &domains, list)
|
|
if (__exynos_cpufreq_resume(domain))
|
|
return NOTIFY_BAD;
|
|
break;
|
|
}
|
|
return NOTIFY_OK;
|
|
}
|
|
|
|
static struct notifier_block exynos_cpufreq_pm = {
|
|
.notifier_call = exynos_cpufreq_pm_notifier,
|
|
};
|
|
|
|
static struct cpufreq_driver exynos_driver = {
|
|
.name = "exynos_cpufreq",
|
|
.flags = CPUFREQ_STICKY | CPUFREQ_HAVE_GOVERNOR_PER_POLICY,
|
|
.init = exynos_cpufreq_driver_init,
|
|
.verify = exynos_cpufreq_verify,
|
|
.target = exynos_cpufreq_target,
|
|
.get = exynos_cpufreq_get,
|
|
.suspend = exynos_cpufreq_suspend,
|
|
.resume = exynos_cpufreq_resume,
|
|
.ready = exynos_cpufreq_ready,
|
|
.exit = exynos_cpufreq_exit,
|
|
.attr = cpufreq_generic_attr,
|
|
};
|
|
|
|
/*********************************************************************
|
|
* SUPPORT for DVFS MANAGER *
|
|
*********************************************************************/
|
|
|
|
static int dm_scaler(int dm_type, void *devdata, unsigned int target_freq,
|
|
unsigned int relation)
|
|
{
|
|
struct exynos_cpufreq_domain *domain = devdata;
|
|
struct cpufreq_policy *policy;
|
|
struct cpumask mask;
|
|
int ret;
|
|
|
|
/* Skip scaling if all cpus of domain are hotplugged out */
|
|
cpumask_and(&mask, &domain->cpus, cpu_online_mask);
|
|
if (cpumask_empty(&mask))
|
|
return -ENODEV;
|
|
|
|
if (relation == EXYNOS_DM_RELATION_L)
|
|
relation = CPUFREQ_RELATION_L;
|
|
else
|
|
relation = CPUFREQ_RELATION_H;
|
|
|
|
policy = cpufreq_cpu_get(cpumask_first(&mask));
|
|
if (!policy) {
|
|
pr_err("%s: failed get cpufreq policy\n", __func__);
|
|
return -ENODEV;
|
|
}
|
|
|
|
ret = __exynos_cpufreq_target(policy, target_freq, relation);
|
|
|
|
cpufreq_cpu_put(policy);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*********************************************************************
|
|
* CPUFREQ PM QOS HANDLER *
|
|
*********************************************************************/
|
|
static int need_update_freq(struct exynos_cpufreq_domain *domain,
|
|
int pm_qos_class, unsigned int freq)
|
|
{
|
|
unsigned int cur = get_freq(domain);
|
|
|
|
if (cur == freq)
|
|
return 0;
|
|
|
|
if (pm_qos_class == domain->pm_qos_min_class) {
|
|
if (cur > freq)
|
|
return 0;
|
|
} else if (domain->pm_qos_max_class == pm_qos_class) {
|
|
if (cur < freq)
|
|
return 0;
|
|
} else {
|
|
/* invalid PM QoS class */
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int exynos_cpufreq_pm_qos_callback(struct notifier_block *nb,
|
|
unsigned long val, void *v)
|
|
{
|
|
int pm_qos_class = *((int *)v);
|
|
struct exynos_cpufreq_domain *domain;
|
|
struct cpufreq_policy *policy;
|
|
struct cpumask mask;
|
|
int ret;
|
|
|
|
pr_debug("update PM QoS class %d to %ld kHz\n", pm_qos_class, val);
|
|
|
|
domain = find_domain_pm_qos_class(pm_qos_class);
|
|
if (!domain)
|
|
return NOTIFY_BAD;
|
|
|
|
cpumask_and(&mask, &domain->cpus, cpu_online_mask);
|
|
if (cpumask_empty(&mask))
|
|
return NOTIFY_BAD;
|
|
|
|
policy = cpufreq_cpu_get(cpumask_first(&mask));
|
|
if (!policy)
|
|
return NOTIFY_BAD;
|
|
|
|
ret = need_update_freq(domain, pm_qos_class, val);
|
|
if (ret < 0)
|
|
return NOTIFY_BAD;
|
|
if (!ret)
|
|
return NOTIFY_OK;
|
|
|
|
if (update_freq(domain, val))
|
|
return NOTIFY_BAD;
|
|
|
|
return NOTIFY_OK;
|
|
}
|
|
|
|
/*********************************************************************
|
|
* EXTERNAL EVENT HANDLER *
|
|
*********************************************************************/
|
|
static int exynos_cpufreq_policy_callback(struct notifier_block *nb,
|
|
unsigned long event, void *data)
|
|
{
|
|
struct cpufreq_policy *policy = data;
|
|
struct exynos_cpufreq_domain *domain = find_domain(policy->cpu);
|
|
|
|
if (!domain)
|
|
return NOTIFY_OK;
|
|
|
|
switch (event) {
|
|
case CPUFREQ_NOTIFY:
|
|
arch_set_freq_scale(&domain->cpus, domain->old, policy->max);
|
|
break;
|
|
}
|
|
|
|
return NOTIFY_OK;
|
|
}
|
|
|
|
static struct notifier_block exynos_cpufreq_policy_notifier = {
|
|
.notifier_call = exynos_cpufreq_policy_callback,
|
|
};
|
|
|
|
extern bool cpuhp_tasks_frozen;
|
|
static int exynos_cpufreq_cpu_up_callback(unsigned int cpu)
|
|
{
|
|
struct exynos_cpufreq_domain *domain;
|
|
struct cpumask mask;
|
|
|
|
/*
|
|
* CPU frequency is not changed before cpufreq_resume() is called.
|
|
* Therefore, if this callback is called by enable_nonboot_cpus(),
|
|
* it is ignored.
|
|
*/
|
|
if (cpuhp_tasks_frozen)
|
|
return 0;
|
|
|
|
domain = find_domain(cpu);
|
|
if (!domain)
|
|
return -ENODEV;
|
|
|
|
/*
|
|
* The first incomming cpu in domain enables frequency scaling
|
|
* and clears limit of frequency.
|
|
*/
|
|
cpumask_and(&mask, &domain->cpus, cpu_online_mask);
|
|
if (cpumask_weight(&mask) == 1) {
|
|
enable_domain(domain);
|
|
pm_qos_update_request(&domain->max_qos_req, domain->max_freq);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int exynos_cpufreq_cpu_down_callback(unsigned int cpu)
|
|
{
|
|
struct exynos_cpufreq_domain *domain;
|
|
struct cpumask mask;
|
|
|
|
/*
|
|
* CPU frequency is not changed after cpufreq_suspend() is called.
|
|
* Therefore, if this callback is called by disable_nonboot_cpus(),
|
|
* it is ignored.
|
|
*/
|
|
if (cpuhp_tasks_frozen)
|
|
return 0;
|
|
|
|
domain = find_domain(cpu);
|
|
if (!domain)
|
|
return -ENODEV;
|
|
|
|
/*
|
|
* The last outgoing cpu in domain limits frequency to minimum
|
|
* and disables frequency scaling.
|
|
*/
|
|
cpumask_and(&mask, &domain->cpus, cpu_online_mask);
|
|
if (cpumask_weight(&mask) == 1) {
|
|
pm_qos_update_request(&domain->max_qos_req, domain->min_freq);
|
|
disable_domain(domain);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*********************************************************************
|
|
* EXTERNAL REFERENCE APIs *
|
|
*********************************************************************/
|
|
unsigned int exynos_cpufreq_get_max_freq(struct cpumask *mask)
|
|
{
|
|
struct exynos_cpufreq_domain *domain = find_domain_cpumask(mask);
|
|
|
|
return domain->max_freq;
|
|
}
|
|
EXPORT_SYMBOL(exynos_cpufreq_get_max_freq);
|
|
|
|
unsigned int exynos_cpufreq_get_locked(unsigned int cpu)
|
|
{
|
|
struct exynos_cpufreq_domain *domain = find_domain(cpu);
|
|
|
|
if (!domain)
|
|
return 0;
|
|
|
|
/*
|
|
* It is accompanied by a lock job to prevent
|
|
* reading frequency during frequency change
|
|
*/
|
|
mutex_lock(&domain->lock);
|
|
mutex_unlock(&domain->lock);
|
|
|
|
return get_freq(domain);
|
|
}
|
|
EXPORT_SYMBOL(exynos_cpufreq_get_locked);
|
|
|
|
void exynos_cpufreq_ready_list_add(struct exynos_cpufreq_ready_block *rb)
|
|
{
|
|
if (!rb)
|
|
return;
|
|
|
|
list_add(&rb->list, &ready_list);
|
|
}
|
|
EXPORT_SYMBOL(exynos_cpufreq_ready_list_add);
|
|
|
|
unsigned int __weak exynos_pstate_get_boost_freq(int cpu)
|
|
{
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(exynos_pstate_get_boost_freq);
|
|
|
|
#ifdef CONFIG_SEC_BOOTSTAT
|
|
void sec_bootstat_get_cpuinfo(int *freq, int *online)
|
|
{
|
|
int cpu;
|
|
int cluster;
|
|
struct exynos_cpufreq_domain *domain;
|
|
|
|
get_online_cpus();
|
|
*online = cpumask_bits(cpu_online_mask)[0];
|
|
for_each_online_cpu(cpu) {
|
|
domain = find_domain(cpu);
|
|
if (!domain)
|
|
continue;
|
|
|
|
cluster = 0;
|
|
if (domain->dm_type == DM_CPU_CL1)
|
|
cluster = 1;
|
|
|
|
freq[cluster] = get_freq(domain);
|
|
}
|
|
put_online_cpus();
|
|
}
|
|
#endif
|
|
|
|
/*********************************************************************
|
|
* INITIALIZE EXYNOS CPUFREQ DRIVER *
|
|
*********************************************************************/
|
|
static void print_domain_info(struct exynos_cpufreq_domain *domain)
|
|
{
|
|
int i;
|
|
char buf[10];
|
|
|
|
pr_info("CPUFREQ of domain%d cal-id : %#x\n",
|
|
domain->id, domain->cal_id);
|
|
|
|
scnprintf(buf, sizeof(buf), "%*pbl", cpumask_pr_args(&domain->cpus));
|
|
pr_info("CPUFREQ of domain%d sibling cpus : %s\n",
|
|
domain->id, buf);
|
|
|
|
pr_info("CPUFREQ of domain%d boot freq = %d kHz, resume freq = %d kHz\n",
|
|
domain->id, domain->boot_freq, domain->resume_freq);
|
|
|
|
pr_info("CPUFREQ of domain%d max freq : %d kHz, min freq : %d kHz\n",
|
|
domain->id,
|
|
domain->max_freq, domain->min_freq);
|
|
|
|
pr_info("CPUFREQ of domain%d PM QoS max-class-id : %d, min-class-id : %d\n",
|
|
domain->id,
|
|
domain->pm_qos_max_class, domain->pm_qos_min_class);
|
|
|
|
pr_info("CPUFREQ of domain%d table size = %d\n",
|
|
domain->id, domain->table_size);
|
|
|
|
for (i = 0; i < domain->table_size; i++) {
|
|
if (domain->freq_table[i].frequency == CPUFREQ_ENTRY_INVALID)
|
|
continue;
|
|
|
|
pr_info("CPUFREQ of domain%d : L%2d %7d kHz\n",
|
|
domain->id,
|
|
domain->freq_table[i].driver_data,
|
|
domain->freq_table[i].frequency);
|
|
}
|
|
}
|
|
|
|
static __init void init_sysfs(void)
|
|
{
|
|
}
|
|
|
|
static __init int init_table(struct exynos_cpufreq_domain *domain)
|
|
{
|
|
unsigned int index;
|
|
unsigned long *table;
|
|
unsigned int *volt_table;
|
|
struct exynos_cpufreq_dm *dm;
|
|
struct exynos_ufc *ufc;
|
|
struct device *dev;
|
|
int ret = 0;
|
|
|
|
/*
|
|
* Initialize frequency and voltage table of domain.
|
|
* Allocate temporary table to get DVFS table from CAL.
|
|
* Deliver this table to CAL API, then CAL fills the information.
|
|
*/
|
|
table = kzalloc(sizeof(unsigned long) * domain->table_size, GFP_KERNEL);
|
|
if (!table)
|
|
return -ENOMEM;
|
|
|
|
volt_table = kzalloc(sizeof(unsigned int) * domain->table_size, GFP_KERNEL);
|
|
if (!volt_table) {
|
|
ret = -ENOMEM;
|
|
goto free_table;
|
|
}
|
|
|
|
cal_dfs_get_rate_table(domain->cal_id, table);
|
|
cal_dfs_get_asv_table(domain->cal_id, volt_table);
|
|
|
|
for (index = 0; index < domain->table_size; index++) {
|
|
domain->freq_table[index].driver_data = index;
|
|
|
|
if (table[index] > domain->max_freq)
|
|
domain->freq_table[index].frequency = CPUFREQ_ENTRY_INVALID;
|
|
else if (table[index] < domain->min_freq)
|
|
domain->freq_table[index].frequency = CPUFREQ_ENTRY_INVALID;
|
|
else {
|
|
struct cpumask mask;
|
|
domain->freq_table[index].frequency = table[index];
|
|
/* Add OPP table to first cpu of domain */
|
|
dev = get_cpu_device(cpumask_first(&domain->cpus));
|
|
if (!dev)
|
|
continue;
|
|
cpumask_and(&mask, &domain->cpus, cpu_online_mask);
|
|
dev_pm_opp_add(get_cpu_device(cpumask_first(&mask)),
|
|
table[index] * 1000, volt_table[index]);
|
|
}
|
|
|
|
/* Initialize table of DVFS manager constraint */
|
|
list_for_each_entry(dm, &domain->dm_list, list)
|
|
dm->c.freq_table[index].master_freq = table[index];
|
|
|
|
/* Initialize table of UFC */
|
|
list_for_each_entry(ufc, &domain->ufc_list, list)
|
|
ufc->info.freq_table[index].master_freq =
|
|
domain->freq_table[index].frequency;
|
|
}
|
|
domain->freq_table[index].driver_data = index;
|
|
domain->freq_table[index].frequency = CPUFREQ_TABLE_END;
|
|
|
|
init_sched_energy_table(&domain->cpus, domain->table_size, table, volt_table,
|
|
domain->max_freq, domain->min_freq);
|
|
|
|
kfree(volt_table);
|
|
|
|
free_table:
|
|
kfree(table);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static __init void set_boot_qos(struct exynos_cpufreq_domain *domain)
|
|
{
|
|
unsigned int boot_qos, val;
|
|
struct device_node *dn = domain->dn;
|
|
|
|
/*
|
|
* Basically booting pm_qos is set to max frequency of domain.
|
|
* But if pm_qos-booting exists in device tree,
|
|
* booting pm_qos is selected to smaller one
|
|
* between max frequency of domain and the value defined in device tree.
|
|
*/
|
|
boot_qos = domain->max_freq;
|
|
if (!of_property_read_u32(dn, "pm_qos-booting", &val))
|
|
boot_qos = min(boot_qos, val);
|
|
|
|
pm_qos_update_request_timeout(&domain->min_qos_req,
|
|
boot_qos, 40 * USEC_PER_SEC);
|
|
pm_qos_update_request_timeout(&domain->max_qos_req,
|
|
boot_qos, 40 * USEC_PER_SEC);
|
|
|
|
}
|
|
|
|
static __init int init_pm_qos(struct exynos_cpufreq_domain *domain,
|
|
struct device_node *dn)
|
|
{
|
|
int ret;
|
|
|
|
ret = of_property_read_u32(dn, "pm_qos-min-class",
|
|
&domain->pm_qos_min_class);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = of_property_read_u32(dn, "pm_qos-max-class",
|
|
&domain->pm_qos_max_class);
|
|
if (ret)
|
|
return ret;
|
|
|
|
domain->pm_qos_min_notifier.notifier_call = exynos_cpufreq_pm_qos_callback;
|
|
domain->pm_qos_min_notifier.priority = INT_MAX;
|
|
domain->pm_qos_max_notifier.notifier_call = exynos_cpufreq_pm_qos_callback;
|
|
domain->pm_qos_max_notifier.priority = INT_MAX;
|
|
|
|
pm_qos_add_notifier(domain->pm_qos_min_class,
|
|
&domain->pm_qos_min_notifier);
|
|
pm_qos_add_notifier(domain->pm_qos_max_class,
|
|
&domain->pm_qos_max_notifier);
|
|
|
|
pm_qos_add_request(&domain->min_qos_req,
|
|
domain->pm_qos_min_class, domain->min_freq);
|
|
pm_qos_add_request(&domain->max_qos_req,
|
|
domain->pm_qos_max_class, domain->max_freq);
|
|
return 0;
|
|
}
|
|
|
|
static int init_constraint_table_ect(struct exynos_cpufreq_domain *domain,
|
|
struct exynos_cpufreq_dm *dm,
|
|
struct device_node *dn)
|
|
{
|
|
void *block;
|
|
struct ect_minlock_domain *ect_domain;
|
|
const char *ect_name;
|
|
unsigned int index, c_index;
|
|
bool valid_row = false;
|
|
int ret;
|
|
|
|
ret = of_property_read_string(dn, "ect-name", &ect_name);
|
|
if (ret)
|
|
return ret;
|
|
|
|
block = ect_get_block(BLOCK_MINLOCK);
|
|
if (!block)
|
|
return -ENODEV;
|
|
|
|
ect_domain = ect_minlock_get_domain(block, (char *)ect_name);
|
|
if (!ect_domain)
|
|
return -ENODEV;
|
|
|
|
for (index = 0; index < domain->table_size; index++) {
|
|
unsigned int freq = domain->freq_table[index].frequency;
|
|
|
|
for (c_index = 0; c_index < ect_domain->num_of_level; c_index++) {
|
|
/* find row same as frequency */
|
|
if (freq == ect_domain->level[c_index].main_frequencies) {
|
|
dm->c.freq_table[index].constraint_freq
|
|
= ect_domain->level[c_index].sub_frequencies;
|
|
valid_row = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Due to higher levels of constraint_freq should not be NULL,
|
|
* they should be filled with highest value of sub_frequencies of ect
|
|
* until finding first(highest) domain frequency fit with main_frequeucy of ect.
|
|
*/
|
|
if (!valid_row)
|
|
dm->c.freq_table[index].constraint_freq
|
|
= ect_domain->level[0].sub_frequencies;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int init_constraint_table_dt(struct exynos_cpufreq_domain *domain,
|
|
struct exynos_cpufreq_dm *dm,
|
|
struct device_node *dn)
|
|
{
|
|
struct exynos_dm_freq *table;
|
|
int size, index, c_index;
|
|
|
|
/*
|
|
* A DVFS Manager table row consists of CPU and MIF frequency
|
|
* value, the size of a row is 64bytes. Divide size in half when
|
|
* table is allocated.
|
|
*/
|
|
size = of_property_count_u32_elems(dn, "table");
|
|
if (size < 0)
|
|
return size;
|
|
|
|
table = kzalloc(sizeof(struct exynos_dm_freq) * size / 2, GFP_KERNEL);
|
|
if (!table)
|
|
return -ENOMEM;
|
|
|
|
of_property_read_u32_array(dn, "table", (unsigned int *)table, size);
|
|
for (index = 0; index < domain->table_size; index++) {
|
|
unsigned int freq = domain->freq_table[index].frequency;
|
|
|
|
if (freq == CPUFREQ_ENTRY_INVALID)
|
|
continue;
|
|
|
|
for (c_index = 0; c_index < size / 2; c_index++) {
|
|
/* find row same or nearby frequency */
|
|
if (freq <= table[c_index].master_freq)
|
|
dm->c.freq_table[index].constraint_freq
|
|
= table[c_index].constraint_freq;
|
|
|
|
if (freq >= table[c_index].master_freq)
|
|
break;
|
|
|
|
}
|
|
}
|
|
|
|
kfree(table);
|
|
return 0;
|
|
}
|
|
|
|
static int init_dm(struct exynos_cpufreq_domain *domain,
|
|
struct device_node *dn)
|
|
{
|
|
struct device_node *root, *child;
|
|
struct exynos_cpufreq_dm *dm;
|
|
int ret;
|
|
|
|
if (list_empty(&domain->dm_list))
|
|
return 0;
|
|
|
|
ret = of_property_read_u32(dn, "dm-type", &domain->dm_type);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = exynos_dm_data_init(domain->dm_type, domain, domain->min_freq,
|
|
domain->max_freq, domain->old);
|
|
if (ret)
|
|
return ret;
|
|
|
|
dm = list_entry(&domain->dm_list, struct exynos_cpufreq_dm, list);
|
|
root = of_find_node_by_name(dn, "dm-constraints");
|
|
for_each_child_of_node(root, child) {
|
|
/*
|
|
* Initialize DVFS Manaver constraints
|
|
* - constraint_type : minimum or maximum constraint
|
|
* - constraint_dm_type : cpu/mif/int/.. etc
|
|
* - guidance : constraint from chipset characteristic
|
|
* - freq_table : constraint table
|
|
*/
|
|
dm = list_next_entry(dm, list);
|
|
|
|
of_property_read_u32(child, "const-type", &dm->c.constraint_type);
|
|
of_property_read_u32(child, "dm-type", &dm->c.constraint_dm_type);
|
|
|
|
if (of_property_read_bool(child, "guidance")) {
|
|
dm->c.guidance = true;
|
|
if (init_constraint_table_ect(domain, dm, child))
|
|
continue;
|
|
} else {
|
|
if (init_constraint_table_dt(domain, dm, child))
|
|
continue;
|
|
}
|
|
|
|
dm->c.table_length = domain->table_size;
|
|
|
|
ret = register_exynos_dm_constraint_table(domain->dm_type, &dm->c);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int register_dm_callback(struct exynos_cpufreq_domain *domain)
|
|
{
|
|
return register_exynos_dm_freq_scaler(domain->dm_type, dm_scaler);
|
|
}
|
|
|
|
static __init int init_domain(struct exynos_cpufreq_domain *domain,
|
|
struct device_node *dn)
|
|
{
|
|
unsigned int val;
|
|
int ret;
|
|
|
|
mutex_init(&domain->lock);
|
|
|
|
/* Initialize frequency scaling */
|
|
domain->max_freq = cal_dfs_get_max_freq(domain->cal_id);
|
|
domain->min_freq = cal_dfs_get_min_freq(domain->cal_id);
|
|
|
|
/*
|
|
* If max-freq property exists in device tree, max frequency is
|
|
* selected to smaller one between the value defined in device
|
|
* tree and CAL. In case of min-freq, min frequency is selected
|
|
* to bigger one.
|
|
*/
|
|
if (!of_property_read_u32(dn, "max-freq", &val))
|
|
domain->max_freq = min(domain->max_freq, val);
|
|
if (!of_property_read_u32(dn, "min-freq", &val))
|
|
domain->min_freq = max(domain->min_freq, val);
|
|
|
|
/* If this domain has boost freq, change max */
|
|
val = exynos_pstate_get_boost_freq(cpumask_first(&domain->cpus));
|
|
if (val > domain->max_freq)
|
|
domain->max_freq = val;
|
|
|
|
if (of_property_read_bool(dn, "need-awake"))
|
|
domain->need_awake = true;
|
|
|
|
domain->boot_freq = cal_dfs_get_boot_freq(domain->cal_id);
|
|
domain->resume_freq = cal_dfs_get_resume_freq(domain->cal_id);
|
|
|
|
ufc_domain_init(domain);
|
|
|
|
ret = init_table(domain);
|
|
if (ret)
|
|
return ret;
|
|
|
|
domain->old = get_freq(domain);
|
|
|
|
/* Initialize PM QoS */
|
|
ret = init_pm_qos(domain, dn);
|
|
if (ret)
|
|
return ret;
|
|
|
|
/*
|
|
* Initialize CPUFreq DVFS Manager
|
|
* DVFS Manager is the optional function, it does not check return value
|
|
*/
|
|
init_dm(domain, dn);
|
|
|
|
pr_info("Complete to initialize cpufreq-domain%d\n", domain->id);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static __init int early_init_domain(struct exynos_cpufreq_domain *domain,
|
|
struct device_node *dn)
|
|
{
|
|
const char *buf;
|
|
int ret;
|
|
|
|
/* Initialize list head of DVFS Manager constraints */
|
|
INIT_LIST_HEAD(&domain->dm_list);
|
|
INIT_LIST_HEAD(&domain->ufc_list);
|
|
|
|
ret = of_property_read_u32(dn, "cal-id", &domain->cal_id);
|
|
if (ret)
|
|
return ret;
|
|
|
|
/* Get size of frequency table from CAL */
|
|
domain->table_size = cal_dfs_get_lv_num(domain->cal_id);
|
|
|
|
/* Get cpumask which belongs to domain */
|
|
ret = of_property_read_string(dn, "sibling-cpus", &buf);
|
|
if (ret)
|
|
return ret;
|
|
cpulist_parse(buf, &domain->cpus);
|
|
cpumask_and(&domain->cpus, &domain->cpus, cpu_online_mask);
|
|
if (cpumask_weight(&domain->cpus) == 0)
|
|
return -ENODEV;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static __init void __free_domain(struct exynos_cpufreq_domain *domain)
|
|
{
|
|
struct exynos_cpufreq_dm *dm;
|
|
|
|
while (!list_empty(&domain->dm_list)) {
|
|
dm = list_last_entry(&domain->dm_list,
|
|
struct exynos_cpufreq_dm, list);
|
|
list_del(&dm->list);
|
|
kfree(dm->c.freq_table);
|
|
kfree(dm);
|
|
}
|
|
|
|
kfree(domain->freq_table);
|
|
kfree(domain);
|
|
}
|
|
|
|
static __init void free_domain(struct exynos_cpufreq_domain *domain)
|
|
{
|
|
list_del(&domain->list);
|
|
unregister_exynos_dm_freq_scaler(domain->dm_type);
|
|
|
|
__free_domain(domain);
|
|
}
|
|
|
|
static __init struct exynos_cpufreq_domain *alloc_domain(struct device_node *dn)
|
|
{
|
|
struct exynos_cpufreq_domain *domain;
|
|
struct device_node *root, *child;
|
|
|
|
domain = kzalloc(sizeof(struct exynos_cpufreq_domain), GFP_KERNEL);
|
|
if (!domain)
|
|
return NULL;
|
|
|
|
/*
|
|
* early_init_domain() initailize the domain information requisite
|
|
* to allocate domain and table.
|
|
*/
|
|
if (early_init_domain(domain, dn))
|
|
goto free;
|
|
|
|
/*
|
|
* Allocate frequency table.
|
|
* Last row of frequency table must be set to CPUFREQ_TABLE_END.
|
|
* Table size should be one larger than real table size.
|
|
*/
|
|
domain->freq_table =
|
|
kzalloc(sizeof(struct cpufreq_frequency_table)
|
|
* (domain->table_size + 1), GFP_KERNEL);
|
|
if (!domain->freq_table)
|
|
goto free;
|
|
|
|
/*
|
|
* Allocate DVFS Manager constraints.
|
|
* Constraints are needed only by DVFS Manager, these are not
|
|
* created when DVFS Manager is disabled. If constraints does
|
|
* not exist, driver does scaling without DVFS Manager.
|
|
*/
|
|
#ifndef CONFIG_EXYNOS_DVFS_MANAGER
|
|
return domain;
|
|
#endif
|
|
|
|
root = of_find_node_by_name(dn, "dm-constraints");
|
|
for_each_child_of_node(root, child) {
|
|
struct exynos_cpufreq_dm *dm;
|
|
|
|
dm = kzalloc(sizeof(struct exynos_cpufreq_dm), GFP_KERNEL);
|
|
if (!dm)
|
|
goto free;
|
|
|
|
dm->c.freq_table = kzalloc(sizeof(struct exynos_dm_freq)
|
|
* domain->table_size, GFP_KERNEL);
|
|
if (!dm->c.freq_table)
|
|
goto free;
|
|
|
|
list_add_tail(&dm->list, &domain->dm_list);
|
|
}
|
|
|
|
return domain;
|
|
|
|
free:
|
|
__free_domain(domain);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static int __init exynos_cpufreq_init(void)
|
|
{
|
|
struct device_node *dn = NULL;
|
|
struct exynos_cpufreq_domain *domain;
|
|
int ret = 0;
|
|
unsigned int domain_id = 0;
|
|
|
|
while ((dn = of_find_node_by_type(dn, "cpufreq-domain"))) {
|
|
domain = alloc_domain(dn);
|
|
if (!domain) {
|
|
pr_err("failed to allocate domain%d\n", domain_id);
|
|
continue;
|
|
}
|
|
|
|
list_add_tail(&domain->list, &domains);
|
|
|
|
domain->dn = dn;
|
|
domain->id = domain_id++;
|
|
ret = init_domain(domain, dn);
|
|
if (ret) {
|
|
pr_err("failed to initialize cpufreq domain%d\n",
|
|
domain_id);
|
|
free_domain(domain);
|
|
continue;
|
|
}
|
|
|
|
print_domain_info(domain);
|
|
}
|
|
|
|
if (!domain_id) {
|
|
pr_err("Can't find device type cpufreq-domain\n");
|
|
return -ENODATA;
|
|
}
|
|
|
|
ret = cpufreq_register_driver(&exynos_driver);
|
|
if (ret) {
|
|
pr_err("failed to register cpufreq driver\n");
|
|
return ret;
|
|
}
|
|
|
|
init_sysfs();
|
|
|
|
exynos_cpuhp_register("ACME", *cpu_possible_mask, 0);
|
|
|
|
cpufreq_register_notifier(&exynos_cpufreq_policy_notifier,
|
|
CPUFREQ_POLICY_NOTIFIER);
|
|
|
|
cpuhp_setup_state_nocalls(CPUHP_AP_EXYNOS_ACME,
|
|
"exynos:acme",
|
|
exynos_cpufreq_cpu_up_callback,
|
|
exynos_cpufreq_cpu_down_callback);
|
|
|
|
register_pm_notifier(&exynos_cpufreq_pm);
|
|
|
|
/*
|
|
* Enable scale of domain.
|
|
* Update frequency as soon as domain is enabled.
|
|
*/
|
|
list_for_each_entry(domain, &domains, list) {
|
|
struct cpufreq_policy *policy;
|
|
enable_domain(domain);
|
|
if (register_dm_callback(domain))
|
|
pr_err("Couldn't register DM callback\n");
|
|
policy = cpufreq_cpu_get_raw(cpumask_first(&domain->cpus));
|
|
if (policy)
|
|
exynos_cpufreq_cooling_register(domain->dn, policy);
|
|
|
|
set_boot_qos(domain);
|
|
}
|
|
|
|
pr_info("Initialized Exynos cpufreq driver\n");
|
|
|
|
return ret;
|
|
}
|
|
device_initcall(exynos_cpufreq_init);
|