ARM: tegra: power: Add dynamic CPU regulator mode control
Alex Frid [Thu, 17 Oct 2013 01:50:08 +0000 (18:50 -0700)]
Operational mode of CPU voltage regulator depends on load current.
Commonly on Tegra platforms this dependency was handled by regulator
h/w itself. There are exceptions, however, introduced on some Tegra12
designs that requires s/w control of the mode.

In order to dynamically control regulator mode based on load,
s/w has to

(a) estimate load based on CPU frequency, number of on-line CPU cores,
and temperature
(b) compare load estimation with regulator specific threshold whenever
any of the above factors changes
(c) change regulator mode when the respective threshold is crossed

This commit adds layer (b) in cpu-tegra driver. It expects existing
Tegra CPU load calculator in EDP driver to implement (a), and provide
look-up table of frequency thresholds for each combination of on-line
CPU cores and temperature ranges. When the respective threshold is
crossed standard regulator mode change interface is called to carry
on (c).

Only switching between IDLE and NORMAL regulator modes is supported.
The respective EDP calculator functions are just stubbed, for now.

Bug 1302884

Change-Id: Iaea42a101aaea239643c0c80a7ad165ece3b1e36
Signed-off-by: Alex Frid <afrid@nvidia.com>
Reviewed-on: http://git-master/r/301520
Reviewed-by: Yu-Huan Hsu <yhsu@nvidia.com>

arch/arm/mach-tegra/clock.c
arch/arm/mach-tegra/cpu-tegra.c
arch/arm/mach-tegra/cpu-tegra.h
arch/arm/mach-tegra/dvfs.c
arch/arm/mach-tegra/dvfs.h
arch/arm/mach-tegra/edp.c
arch/arm/mach-tegra/include/mach/edp.h
arch/arm/mach-tegra/tegra12_clocks.c

index 2720821..2678315 100644 (file)
@@ -1,11 +1,12 @@
 /*
  *
  * Copyright (C) 2010 Google, Inc.
- * Copyright (c) 2012-2013, NVIDIA CORPORATION.  All rights reserved.
  *
  * Author:
  *     Colin Cross <ccross@google.com>
  *
+ * Copyright (c) 2010-2013, NVIDIA CORPORATION.  All rights reserved.
+ *
  * This software is licensed under the terms of the GNU General Public
  * License version 2, as published by the Free Software Foundation, and
  * may be copied, distributed, and modified under those terms.
@@ -44,6 +45,7 @@
 #include "dvfs.h"
 #include "iomap.h"
 #include "tegra_emc.h"
+#include "cpu-tegra.h"
 
 /* Global data of Tegra CPU CAR ops */
 struct tegra_cpu_car_ops *tegra_cpu_car_ops;
@@ -1073,7 +1075,7 @@ static int __init tegra_clk_late_init(void)
                tegra_dfll_cpu_start();
 
        tegra_sync_cpu_clock();         /* after attempt to get dfll ready */
-       tegra_recalculate_cpu_edp_limits();
+       tegra_update_cpu_edp_limits();
        return 0;
 }
 late_initcall(tegra_clk_late_init);
index ddee440..9382e88 100644 (file)
@@ -33,6 +33,7 @@
 #include <linux/suspend.h>
 #include <linux/debugfs.h>
 #include <linux/cpu.h>
+#include <linux/regulator/consumer.h>
 
 #include <mach/edp.h>
 
@@ -182,6 +183,10 @@ static struct attribute_group stats_attr_grp = {
 static const struct tegra_edp_limits *cpu_edp_limits;
 static int cpu_edp_limits_size;
 
+static const struct tegra_edp_limits *cpu_reg_idle_limits;
+static unsigned int reg_mode;
+static bool reg_mode_force_normal;
+
 static const unsigned int *system_edp_limits;
 static bool system_edp_alarm;
 
@@ -358,7 +363,8 @@ static int tegra_cpu_edp_notify(
 
                cpu_speed = tegra_getspeed(0);
                new_speed = edp_governor_speed(cpu_speed);
-               if (new_speed < cpu_speed) {
+               if (new_speed < cpu_speed ||
+                   (!is_suspended && cpu_reg_idle_limits)) {
                        ret = tegra_cpu_set_speed_cap_locked(NULL);
                        printk(KERN_DEBUG "cpu-tegra:%sforce EDP limit %u kHz"
                                "\n", ret ? " failed to " : " ", new_speed);
@@ -422,6 +428,69 @@ static void tegra_cpu_edp_exit(void)
        unregister_hotcpu_notifier(&tegra_cpu_edp_notifier);
 }
 
+static unsigned int cpu_reg_mode_predict_idle_limit(void)
+{
+       unsigned int cpus, limit;
+
+       if (!cpu_reg_idle_limits)
+               return 0;
+
+       cpus = cpumask_weight(&edp_cpumask);
+       BUG_ON(cpus == 0);
+       BUG_ON(edp_thermal_index >= cpu_edp_limits_size);
+       limit = cpu_reg_idle_limits[edp_thermal_index].freq_limits[cpus - 1];
+       return limit ? : 1; /* bump 0 to 1kHz to differentiate from no-table */
+}
+
+static int cpu_reg_mode_limits_init(void)
+{
+       int limits_size;
+
+       tegra_get_cpu_reg_mode_limits(&cpu_reg_idle_limits, &limits_size,
+                                     REGULATOR_MODE_IDLE);
+
+       if (!cpu_reg_idle_limits) {
+               reg_mode = -ENOENT;
+               return -ENOENT;
+       }
+
+       if (!cpu_edp_limits || (limits_size != cpu_edp_limits_size)) {
+               pr_err("cpu-tegra: EDP and regulator mode tables mismatch\n");
+               cpu_reg_idle_limits = NULL;
+               reg_mode = -EINVAL;
+               return -EINVAL;
+       }
+
+       reg_mode_force_normal = false;
+       return 0;
+}
+
+int tegra_cpu_reg_mode_force_normal(bool force)
+{
+       int ret = 0;
+
+       mutex_lock(&tegra_cpu_lock);
+       if (cpu_reg_idle_limits) {
+               reg_mode_force_normal = force;
+               ret = tegra_cpu_set_speed_cap_locked(NULL);
+       }
+       mutex_unlock(&tegra_cpu_lock);
+       return ret;
+}
+
+int tegra_update_cpu_edp_limits(void)
+{
+       int ret;
+
+       mutex_lock(&tegra_cpu_lock);
+       tegra_recalculate_cpu_edp_limits();
+       cpu_reg_mode_limits_init();
+       ret = tegra_cpu_set_speed_cap_locked(NULL);
+       mutex_unlock(&tegra_cpu_lock);
+
+       return ret;
+}
+
 #ifdef CONFIG_DEBUG_FS
 
 static int system_edp_alarm_get(void *data, u64 *val)
@@ -440,12 +509,39 @@ static int system_edp_alarm_set(void *data, u64 val)
 DEFINE_SIMPLE_ATTRIBUTE(system_edp_alarm_fops,
                        system_edp_alarm_get, system_edp_alarm_set, "%llu\n");
 
+static int reg_mode_force_normal_get(void *data, u64 *val)
+{
+       *val = (u64)reg_mode_force_normal;
+       return 0;
+}
+static int reg_mode_force_normal_set(void *data, u64 val)
+{
+       return tegra_cpu_reg_mode_force_normal(val);
+}
+DEFINE_SIMPLE_ATTRIBUTE(force_normal_fops, reg_mode_force_normal_get,
+                       reg_mode_force_normal_set, "%llu\n");
+
+static int reg_mode_get(void *data, u64 *val)
+{
+       *val = (u64)reg_mode;
+       return 0;
+}
+DEFINE_SIMPLE_ATTRIBUTE(reg_mode_fops, reg_mode_get, NULL, "0x%llx\n");
+
 static int __init tegra_edp_debug_init(struct dentry *cpu_tegra_debugfs_root)
 {
        if (!debugfs_create_file("edp_alarm", 0644, cpu_tegra_debugfs_root,
                                 NULL, &system_edp_alarm_fops))
                return -ENOMEM;
 
+       if (!debugfs_create_file("reg_mode_force_normal", 0644,
+                       cpu_tegra_debugfs_root, NULL, &force_normal_fops))
+               return -ENOMEM;
+
+       if (!debugfs_create_file("reg_mode", 0444,
+                       cpu_tegra_debugfs_root, NULL, &reg_mode_fops))
+               return -ENOMEM;
+
        return 0;
 }
 #endif
@@ -455,6 +551,8 @@ static int __init tegra_edp_debug_init(struct dentry *cpu_tegra_debugfs_root)
 #define tegra_cpu_edp_init(resume)
 #define tegra_cpu_edp_exit()
 #define tegra_edp_debug_init(cpu_tegra_debugfs_root) (0)
+#define cpu_reg_mode_predict_idle_limit() (0)
+static unsigned int reg_mode;
 #endif /* CONFIG_TEGRA_EDP_LIMITS */
 
 #ifdef CONFIG_DEBUG_FS
@@ -507,6 +605,7 @@ int tegra_update_cpu_speed(unsigned long rate)
 {
        int ret = 0;
        struct cpufreq_freqs freqs;
+       unsigned int mode, mode_limit = cpu_reg_mode_predict_idle_limit();
 
        freqs.old = tegra_getspeed(0);
        freqs.new = rate;
@@ -515,8 +614,19 @@ int tegra_update_cpu_speed(unsigned long rate)
        if (!IS_ERR_VALUE(rate))
                freqs.new = rate / 1000;
 
+       mode = REGULATOR_MODE_NORMAL;
+       if (mode_limit && (mode_limit < freqs.new || reg_mode_force_normal)) {
+               mode_limit = 0;         /* prevent further mode controls */
+               if (reg_mode != mode) {
+                       ret = tegra_dvfs_rail_set_mode(tegra_cpu_rail, mode);
+                       if (ret)
+                               return ret;
+                       reg_mode = mode;
+               }
+       }
+
        if (freqs.old == freqs.new)
-               return ret;
+               goto _out;
 
        /*
         * Vote on memory bus frequency based on cpu frequency
@@ -551,6 +661,11 @@ int tegra_update_cpu_speed(unsigned long rate)
 
        if (freqs.old > freqs.new)
                tegra_update_mselect_rate(freqs.new);
+_out:
+       mode = REGULATOR_MODE_IDLE;
+       if ((mode_limit >= freqs.new) && (reg_mode != mode))
+               if (!tegra_dvfs_rail_set_mode(tegra_cpu_rail, mode))
+                       reg_mode = mode;
 
        return 0;
 }
@@ -706,6 +821,8 @@ static int tegra_pm_notify(struct notifier_block *nb, unsigned long event,
        mutex_lock(&tegra_cpu_lock);
        if (event == PM_SUSPEND_PREPARE) {
                is_suspended = true;
+               if (cpu_reg_idle_limits)
+                       reg_mode_force_normal = true;
                pr_info("Tegra cpufreq suspend: setting frequency to %d kHz\n",
                        freq_table[suspend_index].frequency);
                tegra_update_cpu_speed(freq_table[suspend_index].frequency);
@@ -714,6 +831,7 @@ static int tegra_pm_notify(struct notifier_block *nb, unsigned long event,
        } else if (event == PM_POST_SUSPEND) {
                unsigned int freq;
                is_suspended = false;
+               reg_mode_force_normal = false;
                tegra_cpu_edp_init(true);
                tegra_cpu_set_speed_cap_locked(&freq);
                pr_info("Tegra cpufreq resume: restoring frequency to %d kHz\n",
index 84504bb..cd11c24 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * arch/arm/mach-tegra/cpu-tegra.h
  *
- * Copyright (c) 2011-2013, NVIDIA Corporation.
+ * Copyright (c) 2011-2013, NVIDIA Corporation. All rights reserved.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -65,4 +65,14 @@ static inline int tegra_suspended_target(unsigned int target_freq)
 { return -ENOSYS; }
 #endif
 
+#if defined(CONFIG_CPU_FREQ) && defined(CONFIG_TEGRA_EDP_LIMITS)
+int tegra_update_cpu_edp_limits(void);
+int tegra_cpu_reg_mode_force_normal(bool force);
+#else
+static inline int tegra_update_cpu_edp_limits(void)
+{ return 0; }
+static inline int tegra_cpu_reg_mode_force_normal(bool force)
+{ return 0; }
+#endif
+
 #endif /* __MACH_TEGRA_CPU_TEGRA_H */
index 7737920..f9885a2 100644 (file)
@@ -1208,6 +1208,22 @@ bool tegra_dvfs_is_rail_up(struct dvfs_rail *rail)
        return ret;
 }
 
+int tegra_dvfs_rail_set_mode(struct dvfs_rail *rail, unsigned int mode)
+{
+       int ret = -ENOENT;
+
+       pr_debug("%s: updating %s mode from %u to %u\n", __func__,
+                rail->reg_id, regulator_get_mode(rail->reg), mode);
+
+       if (rail && rail->reg)
+               ret = regulator_set_mode(rail->reg, mode);
+
+       if (ret)
+               pr_err("Failed to set dvfs regulator %s mode %u\n",
+                      rail->reg_id, mode);
+       return ret;
+}
+
 bool tegra_dvfs_rail_updating(struct clk *clk)
 {
        return (!clk ? false :
index b981a12..6508d97 100644 (file)
@@ -237,6 +237,7 @@ bool tegra_dvfs_rail_updating(struct clk *clk);
 void tegra_dvfs_rail_off(struct dvfs_rail *rail, ktime_t now);
 void tegra_dvfs_rail_on(struct dvfs_rail *rail, ktime_t now);
 void tegra_dvfs_rail_pause(struct dvfs_rail *rail, ktime_t delta, bool on);
+int tegra_dvfs_rail_set_mode(struct dvfs_rail *rail, unsigned int mode);
 struct dvfs_rail *tegra_dvfs_get_rail_by_name(const char *reg_id);
 
 int tegra_dvfs_predict_millivolts(struct clk *c, unsigned long rate);
index 8435a91..83bd03b 100644 (file)
@@ -42,6 +42,8 @@ static unsigned int regulator_cur;
 /* Value to subtract from regulator current limit */
 static unsigned int edp_reg_override_mA = OVERRIDE_DEFAULT;
 
+static struct tegra_edp_limits *reg_mode_limits;
+
 static const unsigned int *system_edp_limits;
 
 static struct tegra_system_edp_entry *power_edp_limits;
@@ -507,6 +509,18 @@ void tegra_get_cpu_edp_limits(const struct tegra_edp_limits **limits, int *size)
        *size = edp_limits_size;
 }
 
+void __init tegra_init_cpu_reg_mode_limits(unsigned int regulator_mA,
+                                          unsigned int mode)
+{
+}
+
+void tegra_get_cpu_reg_mode_limits(const struct tegra_edp_limits **limits,
+                                  int *size, unsigned int mode)
+{
+       *limits = reg_mode_limits;
+       *size = edp_limits_size;
+}
+
 void tegra_get_system_edp_limits(const unsigned int **limits)
 {
        *limits = system_edp_limits;
index 056298a..e2c5467 100644 (file)
@@ -96,8 +96,13 @@ struct tegra_core_edp_limits {
 struct thermal_cooling_device *edp_cooling_device_create(void *v);
 void tegra_init_cpu_edp_limits(unsigned int regulator_mA);
 void tegra_recalculate_cpu_edp_limits(void);
-void tegra_get_cpu_edp_limits(const struct tegra_edp_limits **limits, int *size);
+void tegra_get_cpu_edp_limits(const struct tegra_edp_limits **limits,
+                             int *size);
 unsigned int tegra_get_edp_limit(int *get_edp_thermal_index);
+void tegra_init_cpu_reg_mode_limits(unsigned int regulator_mA,
+                                   unsigned int mode);
+void tegra_get_cpu_reg_mode_limits(const struct tegra_edp_limits **limits,
+                                  int *size, unsigned int mode);
 void tegra_get_system_edp_limits(const unsigned int **limits);
 int tegra_system_edp_alarm(bool alarm);
 void tegra_platform_edp_init(struct thermal_trip_info *trips,
@@ -115,6 +120,12 @@ static inline void tegra_recalculate_cpu_edp_limits(void)
 static inline void tegra_get_cpu_edp_limits(struct tegra_edp_limits **limits,
                                            int *size)
 {}
+static inline void tegra_init_cpu_reg_mode_limits(unsigned int regulator_mA,
+                                                 unsigned int mode)
+{}
+static inline void tegra_get_cpu_reg_mode_limits(
+       const struct tegra_edp_limits **limits, int *size, unsigned int mode)
+{}
 static inline unsigned int tegra_get_edp_limit(int *get_edp_thermal_index)
 { return -1; }
 static inline void tegra_get_system_edp_limits(unsigned int **limits)
index 2ce3d9e..151d1c2 100644 (file)
@@ -44,6 +44,7 @@
 #include "devices.h"
 #include "tegra12_emc.h"
 #include "tegra_cl_dvfs.h"
+#include "cpu-tegra.h"
 
 #define RST_DEVICES_L                  0x004
 #define RST_DEVICES_H                  0x008
@@ -4094,15 +4095,23 @@ static int tegra12_use_dfll_cb(const char *arg, const struct kernel_param *kp)
        if (!c->parent || !c->parent->dvfs || !dfll)
                return -ENOSYS;
 
+       ret = tegra_cpu_reg_mode_force_normal(true);
+       if (ret) {
+               pr_err("%s: Failed to force regulator normal mode\n", __func__);
+               return ret;
+       }
+
        clk_lock_save(c, &c_flags);
        if (dfll->state == UNINITIALIZED) {
                pr_err("%s: DFLL is not initialized\n", __func__);
                clk_unlock_restore(c, &c_flags);
+               tegra_cpu_reg_mode_force_normal(false);
                return -ENOSYS;
        }
        if (c->parent->u.cpu.mode == MODE_LP) {
                pr_err("%s: DFLL is not used on LP CPU\n", __func__);
                clk_unlock_restore(c, &c_flags);
+               tegra_cpu_reg_mode_force_normal(false);
                return -ENOSYS;
        }
 
@@ -4126,7 +4135,7 @@ static int tegra12_use_dfll_cb(const char *arg, const struct kernel_param *kp)
        }
        clk_unlock_restore(c->parent, &p_flags);
        clk_unlock_restore(c, &c_flags);
-       tegra_recalculate_cpu_edp_limits();
+       tegra_update_cpu_edp_limits();
        return ret;
 }