cpufreq: eff: Introduce Exynos FF.
authorChoonghoon Park <choong.park@samsung.com>
Mon, 29 Jan 2018 10:08:13 +0000 (19:08 +0900)
committerlakkyung.jung <lakkyung.jung@samsung.com>
Mon, 23 Jul 2018 05:59:27 +0000 (14:59 +0900)
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
drivers/cpufreq/Makefile
drivers/cpufreq/cpufreq.c
drivers/cpufreq/exynos-ff.c [new file with mode: 0644]
drivers/cpufreq/exynos-ff.h [new file with mode: 0644]
include/linux/cpufreq.h

index 1c332863a3e8768afc691c16cf99bd1f9231c0bc..ded5f935fafe01cc5143133d7a72f75bcb5f8e76 100644 (file)
@@ -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.
index 9819fa89020acae6c1452441bbef2def46d07511..45792083d944803b2eab73831b7b5375810852e8 100644 (file)
@@ -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
 
 
index 55fa42807221ad30216590878bd378407fff7e00..a69d7274b2810119e2ee0ae935e2b5c14e1b8348 100644 (file)
@@ -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 (file)
index 0000000..19e609b
--- /dev/null
@@ -0,0 +1,234 @@
+/*
+ * 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);
diff --git a/drivers/cpufreq/exynos-ff.h b/drivers/cpufreq/exynos-ff.h
new file mode 100644 (file)
index 0000000..e802b42
--- /dev/null
@@ -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
index 065f3a8eb48615becdb299e0b50241794799facf..1dad6680d9547833f1339d9a1dff6611b1b317c0 100644 (file)
@@ -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 {