/* * 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); } #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);