From 6d75c589982713b76bd09a2882cb8183580c7195 Mon Sep 17 00:00:00 2001 From: Choonghoon Park Date: Mon, 29 Jan 2018 19:08:13 +0900 Subject: [PATCH] cpufreq: eff: Introduce Exynos FF. Change-Id: Ic26f61d8776f2d2420ed279449f017c2074145ef [9820] cpufreq: eff: get target function using cpufreq ready callback Change-Id: I54615aea3d248d584490271a0c30a66f42a2ba00 [9820] cpufreq: eff: make filtering condition more precisely Filtering conditions 1) SW request (normal request) turbo boost is already activated (cur_freq >= boost_threshold) and this request could activate turbo boost (req_freq >= boost_threshold) 2) HWI request turbo boost is released (cur_freq < boost_threshold) Change-Id: I5fc21741706de0c0f26d9b4a15c1e8bcad0d1bd6 [9820] cpufreq: eff: clamp frequency SW requests above boost threshold 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. Change-Id: Ie2cb26e75d2d172f3cfbe02c0a95ca5eb7700c83 --- drivers/cpufreq/Kconfig.arm | 12 ++ drivers/cpufreq/Makefile | 1 + drivers/cpufreq/cpufreq.c | 2 +- drivers/cpufreq/exynos-ff.c | 234 ++++++++++++++++++++++++++++++++++++ drivers/cpufreq/exynos-ff.h | 17 +++ include/linux/cpufreq.h | 15 ++- 6 files changed, 277 insertions(+), 4 deletions(-) create mode 100644 drivers/cpufreq/exynos-ff.c create mode 100644 drivers/cpufreq/exynos-ff.h diff --git a/drivers/cpufreq/Kconfig.arm b/drivers/cpufreq/Kconfig.arm index 1c332863a3e8..ded5f935fafe 100644 --- a/drivers/cpufreq/Kconfig.arm +++ b/drivers/cpufreq/Kconfig.arm @@ -301,3 +301,15 @@ config ARM_EXYNOS_ACME ACME supported SoC must use the exynos CAL framework. If in doubt, say N. + +config ARM_EXYNOS_FF + bool "SAMSUNG EXYNOS FF(Frequency Filter) driver" + depends on ARCH_EXYNOS && EXYNOS_HIU + default n + help + This adds the CPUFreq driver for exynos chipsets which needs + frequency filtering due to unnecessary frequency change request + such as frequency requests above frequency which + SW is not able to set to CPUFreq Driver. + + If in doubt, say N. diff --git a/drivers/cpufreq/Makefile b/drivers/cpufreq/Makefile index 9819fa89020a..45792083d944 100644 --- a/drivers/cpufreq/Makefile +++ b/drivers/cpufreq/Makefile @@ -83,6 +83,7 @@ obj-$(CONFIG_ARM_TI_CPUFREQ) += ti-cpufreq.o obj-$(CONFIG_ARM_VEXPRESS_SPC_CPUFREQ) += vexpress-spc-cpufreq.o obj-$(CONFIG_ACPI_CPPC_CPUFREQ) += cppc_cpufreq.o obj-$(CONFIG_MACH_MVEBU_V7) += mvebu-cpufreq.o +obj-$(CONFIG_ARM_EXYNOS_FF) += exynos-ff.o obj-$(CONFIG_ARM_EXYNOS_ACME) += exynos-acme.o diff --git a/drivers/cpufreq/cpufreq.c b/drivers/cpufreq/cpufreq.c index 55fa42807221..a69d7274b281 100644 --- a/drivers/cpufreq/cpufreq.c +++ b/drivers/cpufreq/cpufreq.c @@ -2022,7 +2022,7 @@ static int __target_index(struct cpufreq_policy *policy, int index) return retval; } -int __cpufreq_driver_target(struct cpufreq_policy *policy, +int __weak __cpufreq_driver_target(struct cpufreq_policy *policy, unsigned int target_freq, unsigned int relation) { diff --git a/drivers/cpufreq/exynos-ff.c b/drivers/cpufreq/exynos-ff.c new file mode 100644 index 000000000000..19e609b8372b --- /dev/null +++ b/drivers/cpufreq/exynos-ff.c @@ -0,0 +1,234 @@ +/* + * Copyright (c) 2018, Park Choonghoon + * 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. + * + * Exynos FF(Frequency Filter) driver implementation + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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); +} + +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); +} + +/********************************************************************* + * 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); + + 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; + } + + /* + * 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) +{ + 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; + } + } + + 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); diff --git a/drivers/cpufreq/exynos-ff.h b/drivers/cpufreq/exynos-ff.h new file mode 100644 index 000000000000..e802b4288d62 --- /dev/null +++ b/drivers/cpufreq/exynos-ff.h @@ -0,0 +1,17 @@ +#ifndef __EXYNOS_FF_H__ +#define __EXYNOS_FF_H__ + +struct exynos_ff_driver { + bool big_dvfs_done; + + unsigned int boost_threshold; + unsigned int cal_id; + + struct mutex lock; + struct cpumask cpus; +}; + +static bool hwi_dvfs_req; +static atomic_t boost_throttling = ATOMIC_INIT(0); + +#endif diff --git a/include/linux/cpufreq.h b/include/linux/cpufreq.h index 065f3a8eb486..1dad6680d954 100644 --- a/include/linux/cpufreq.h +++ b/include/linux/cpufreq.h @@ -228,9 +228,14 @@ static inline void cpufreq_stats_record_transition(struct cpufreq_policy *policy * CPUFREQ DRIVER INTERFACE * *********************************************************************/ -#define CPUFREQ_RELATION_L 0 /* lowest frequency at or above target */ -#define CPUFREQ_RELATION_H 1 /* highest frequency below or at target */ -#define CPUFREQ_RELATION_C 2 /* closest frequency to target */ +#define CPUFREQ_RELATION_MASK (0x3 << 0) +#define CPUFREQ_RELATION_L (0 << 0) /* lowest frequency at or above target */ +#define CPUFREQ_RELATION_H (1 << 0) /* highest frequency below or at target */ +#define CPUFREQ_RELATION_C (2 << 0) /* closest frequency to target */ + +#define CPUFREQ_REQUEST_MASK (0x3 << 2) +#define CPUFREQ_NORMAL_REQ (0 << 2) /* normal frequency request */ +#define CPUFREQ_HW_DVFS_REQ (1 << 2) /* for processing HW DVFS; it needs to be dealt specially */ struct freq_attr { struct attribute attr; @@ -540,6 +545,9 @@ void cpufreq_unregister_governor(struct cpufreq_governor *governor); struct cpufreq_governor *cpufreq_default_governor(void); struct cpufreq_governor *cpufreq_fallback_governor(void); +#if defined (CONFIG_ARM_EXYNOS_FF) +void cpufreq_policy_apply_limits(struct cpufreq_policy *policy); +#else static inline void cpufreq_policy_apply_limits(struct cpufreq_policy *policy) { if (policy->max < policy->cur) @@ -547,6 +555,7 @@ static inline void cpufreq_policy_apply_limits(struct cpufreq_policy *policy) else if (policy->min > policy->cur) __cpufreq_driver_target(policy, policy->min, CPUFREQ_RELATION_L); } +#endif /* Governor attribute set */ struct gov_attr_set { -- 2.20.1