--- /dev/null
+/*
+ * 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);
+}
+
+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);