241 lines
6.7 KiB
C
Executable File
241 lines
6.7 KiB
C
Executable File
/*
|
|
* Copyright (c) 2018, Park Choonghoon
|
|
* Samsung Electronics Co., Ltd
|
|
* <choong.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 FF(Frequency Filter) driver implementation
|
|
*/
|
|
|
|
#include <linux/cpu.h>
|
|
#include <linux/cpufreq.h>
|
|
#include <linux/init.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/syscore_ops.h>
|
|
#include <linux/tick.h>
|
|
#include <linux/delay.h>
|
|
#include <soc/samsung/cal-if.h>
|
|
#include "exynos-ff.h"
|
|
#include "exynos-acme.h"
|
|
#include "../../../kernel/sched/sched.h"
|
|
|
|
static struct exynos_ff_driver *eff_driver;
|
|
static int (*eff_target)(struct cpufreq_policy *policy,
|
|
unsigned int target_freq,
|
|
unsigned int relation);
|
|
|
|
/*********************************************************************
|
|
* HELPER FUNCTION *
|
|
*********************************************************************/
|
|
static bool policy_need_filter(struct cpufreq_policy *policy)
|
|
{
|
|
return cpumask_intersects(policy->cpus, &eff_driver->cpus);
|
|
}
|
|
|
|
#ifdef CONFIG_EXYNOS_PSTATE_HAFM_TB
|
|
static bool check_filtering(unsigned int target_freq, unsigned int flag)
|
|
{
|
|
unsigned int cur_freq;
|
|
|
|
cur_freq = (unsigned int)cal_dfs_get_rate(eff_driver->cal_id);
|
|
|
|
/*
|
|
* Filtering conditions
|
|
* 1) SW request (normal request)
|
|
* turbo boost is already activated (cur_freq >= boost_threshold)
|
|
* and
|
|
* this request could activate turbo boost (target_freq >= boost_threshold)
|
|
*
|
|
* 2) HWI request
|
|
* turbo boost is released (cur_freq < boost_threshold)
|
|
*/
|
|
if ((flag & CPUFREQ_REQUEST_MASK) == CPUFREQ_NORMAL_REQ)
|
|
return cur_freq >= eff_driver->boost_threshold &&
|
|
target_freq >= eff_driver->boost_threshold;
|
|
else
|
|
return cur_freq < eff_driver->boost_threshold;
|
|
}
|
|
|
|
static bool check_boost_freq_throttled(struct cpufreq_policy *policy)
|
|
{
|
|
return (policy->cur > eff_driver->boost_threshold) &&
|
|
(policy->cur > policy->max);
|
|
}
|
|
#endif
|
|
|
|
/*********************************************************************
|
|
* EXTERNAL REFERENCE APIs *
|
|
*********************************************************************/
|
|
int __cpufreq_driver_target(struct cpufreq_policy *policy,
|
|
unsigned int target_freq,
|
|
unsigned int flag)
|
|
{
|
|
int ret = 0;
|
|
unsigned int old_target_freq = target_freq;
|
|
|
|
if (!eff_driver)
|
|
return -EINVAL;
|
|
|
|
/* Make sure that target_freq is within supported range */
|
|
target_freq = clamp_val(target_freq, policy->min, policy->max);
|
|
|
|
pr_debug("target for CPU %u: %u kHz, relation %u, requested %u kHz\n",
|
|
policy->cpu, target_freq, flag & CPUFREQ_RELATION_MASK, old_target_freq);
|
|
|
|
if (policy_need_filter(policy)) {
|
|
mutex_lock(&eff_driver->lock);
|
|
|
|
#ifdef CONFIG_EXYNOS_PSTATE_HAFM_TB
|
|
if (check_filtering(target_freq, flag))
|
|
goto out;
|
|
|
|
/*
|
|
* This flag is used in lower SW layer to determine
|
|
* whether this DVFS request is HW interventioned or not.
|
|
*/
|
|
hwi_dvfs_req = (flag & CPUFREQ_REQUEST_MASK) == CPUFREQ_HW_DVFS_REQ;
|
|
|
|
/*
|
|
* In case of normal DVFS request (not HWI request),
|
|
* clamp target value to boost threshold,
|
|
* if target value > boost threshold.
|
|
* SW must not request DVFS with frequency above boost threshold.
|
|
*/
|
|
if (!hwi_dvfs_req && target_freq > eff_driver->boost_threshold)
|
|
target_freq = eff_driver->boost_threshold;
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* This might look like a redundant call as we are checking it again
|
|
* after finding index. But it is left intentionally for cases where
|
|
* exactly same freq is called again and so we can save on few function
|
|
* calls.
|
|
*/
|
|
if (target_freq == policy->cur)
|
|
goto out;
|
|
|
|
/* Save last value to restore later on errors */
|
|
policy->restore_freq = policy->cur;
|
|
|
|
if (eff_target)
|
|
ret = eff_target(policy, target_freq,
|
|
flag & CPUFREQ_RELATION_MASK);
|
|
out:
|
|
if (policy_need_filter(policy))
|
|
mutex_unlock(&eff_driver->lock);
|
|
|
|
return ret;
|
|
}
|
|
|
|
void cpufreq_policy_apply_limits(struct cpufreq_policy *policy)
|
|
{
|
|
#ifdef CONFIG_EXYNOS_PSTATE_HAFM_TB
|
|
if (policy_need_filter(policy)) {
|
|
if (check_boost_freq_throttled(policy)) {
|
|
pr_debug("exynos-ff: wait for boost freq throttling completion\n");
|
|
/* Wait for Completion of HWI Request */
|
|
while (atomic_read(&boost_throttling))
|
|
usleep_range(100, 200);
|
|
|
|
/* After HW reqeusted request completes, policy->cur <= policy->max */
|
|
pr_debug("exynos-ff: apply limits done, max:%d, cur:%d\n",
|
|
policy->max, policy->cur);
|
|
return;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if (policy->max < policy->cur)
|
|
__cpufreq_driver_target(policy, policy->max, CPUFREQ_RELATION_H);
|
|
else if (policy->min > policy->cur)
|
|
__cpufreq_driver_target(policy, policy->min, CPUFREQ_RELATION_L);
|
|
}
|
|
|
|
/*********************************************************************
|
|
* INITIALIZE EXYNOS FF DRIVER *
|
|
*********************************************************************/
|
|
|
|
static int exynos_ff_get_target(struct cpufreq_policy *policy, target_fn target)
|
|
{
|
|
if (!cpumask_intersects(&eff_driver->cpus, policy->cpus))
|
|
return 0;
|
|
|
|
eff_target = target;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct exynos_cpufreq_ready_block exynos_ff_ready = {
|
|
.get_target = exynos_ff_get_target,
|
|
};
|
|
|
|
/*********************************************************************
|
|
* INITIALIZE EXYNOS FF DRIVER *
|
|
*********************************************************************/
|
|
static int alloc_driver(void)
|
|
{
|
|
int ret;
|
|
const char *buf;
|
|
struct device_node *dn;
|
|
|
|
eff_driver = kzalloc(sizeof(struct exynos_ff_driver), GFP_KERNEL);
|
|
if (!eff_driver) {
|
|
pr_err("failed to allocate eff driver\n");
|
|
return -ENODATA;
|
|
}
|
|
|
|
mutex_init(&eff_driver->lock);
|
|
|
|
dn = of_find_node_by_type(NULL, "exynos-ff");
|
|
if (!dn) {
|
|
pr_err("Failed to initialize eff driver\n");
|
|
return -ENODATA;
|
|
}
|
|
|
|
/* Get boost frequency threshold */
|
|
ret = of_property_read_u32(dn, "boost-threshold", &eff_driver->boost_threshold);
|
|
if (ret)
|
|
return ret;
|
|
|
|
/* Get cal id to get current frequency */
|
|
ret = of_property_read_u32(dn, "cal-id", &eff_driver->cal_id);
|
|
if (ret)
|
|
return ret;
|
|
|
|
/* Get cpumask which belongs to domain */
|
|
ret = of_property_read_string(dn, "sibling-cpus", &buf);
|
|
if (ret)
|
|
return ret;
|
|
|
|
cpulist_parse(buf, &eff_driver->cpus);
|
|
cpumask_and(&eff_driver->cpus, &eff_driver->cpus, cpu_online_mask);
|
|
if (cpumask_weight(&eff_driver->cpus) == 0)
|
|
return -ENODEV;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int __init exynos_ff_init(void)
|
|
{
|
|
int ret;
|
|
|
|
ret = alloc_driver();
|
|
if (ret) {
|
|
pr_err("exynos-ff: Fail to allocate Exynos FF driver\n");
|
|
BUG_ON(1);
|
|
return ret;
|
|
}
|
|
|
|
exynos_cpufreq_ready_list_add(&exynos_ff_ready);
|
|
|
|
pr_info("exynos-ff: Initialized Exynos Frequency Filter driver\n");
|
|
|
|
return ret;
|
|
}
|
|
device_initcall(exynos_ff_init);
|