diff options
author | Alex Frid <afrid@nvidia.com> | 2013-07-12 19:58:41 -0700 |
---|---|---|
committer | Harshada Kale <hkale@nvidia.com> | 2013-09-12 02:21:25 -0700 |
commit | 1e821c4257c85b8d2dddc3eb67ca1b61445f9eba (patch) | |
tree | 759b4a6bb3fc1f810301b31cb2eb5d2e5df65ae1 /arch | |
parent | 5d927cc305aa8d0c2e6af9fedda9f5b10d378e16 (diff) |
ARM: tegra: dvfs: Decouple nominal and detached voltages
DVFS rail nominal voltage is minimum voltage required to run all
associated clocks at maximum allowed rates. DVFS rail can be detached
from clocks during initial boot, on suspend entry/exit, or when
voltage scaling is disabled. So far, rail voltage in any detached mode
was set to nominal level. This commit introduced separate voltages for
each detached mode. If any of these levels is not specified, backward
compatible nominal voltage is used.
Since, suspend voltage may now be different from nominal (below), it
is important for dvfs to suspend after suspend edp rate caps are set,
and resume before edp. Hence, priorities of dvfs suspend notifiers
were adjusted accordingly.
Change-Id: Id05e0b16f24dc7d28b1ee9e87afd63d98a9ab86e
Signed-off-by: Alex Frid <afrid@nvidia.com>
Reviewed-on: http://git-master/r/250029
(cherry picked from commit 57d1ea085f098f43db40a9484e5f9d13ec49a45b)
Reviewed-on: http://git-master/r/253648
Tested-by: Shaoming Feng <shaomingf@nvidia.com>
GVS: Gerrit_Virtual_Submit
Reviewed-by: Bharat Nihalani <bnihalani@nvidia.com>
Diffstat (limited to 'arch')
-rw-r--r-- | arch/arm/mach-tegra/dvfs.c | 129 | ||||
-rw-r--r-- | arch/arm/mach-tegra/dvfs.h | 11 | ||||
-rw-r--r-- | arch/arm/mach-tegra/tegra_core_volt_cap.c | 3 |
3 files changed, 116 insertions, 27 deletions
diff --git a/arch/arm/mach-tegra/dvfs.c b/arch/arm/mach-tegra/dvfs.c index 12fe0d01f273..46fe8bb94e8e 100644 --- a/arch/arm/mach-tegra/dvfs.c +++ b/arch/arm/mach-tegra/dvfs.c @@ -51,6 +51,16 @@ static DEFINE_MUTEX(rail_disable_lock); static int dvfs_rail_update(struct dvfs_rail *rail); +static inline int tegra_dvfs_rail_get_disable_level(struct dvfs_rail *rail) +{ + return rail->disable_millivolts ? : rail->nominal_millivolts; +} + +static inline int tegra_dvfs_rail_get_suspend_level(struct dvfs_rail *rail) +{ + return rail->suspend_millivolts ? : rail->nominal_millivolts; +} + void tegra_dvfs_add_relationships(struct dvfs_relationship *rels, int n) { int i; @@ -69,7 +79,7 @@ void tegra_dvfs_add_relationships(struct dvfs_relationship *rels, int n) int tegra_dvfs_init_rails(struct dvfs_rail *rails[], int n) { - int i; + int i, mv; mutex_lock(&dvfs_lock); @@ -77,8 +87,19 @@ int tegra_dvfs_init_rails(struct dvfs_rail *rails[], int n) INIT_LIST_HEAD(&rails[i]->dvfs); INIT_LIST_HEAD(&rails[i]->relationships_from); INIT_LIST_HEAD(&rails[i]->relationships_to); - rails[i]->millivolts = rails[i]->nominal_millivolts; - rails[i]->new_millivolts = rails[i]->nominal_millivolts; + + mv = rails[i]->nominal_millivolts; + if (rails[i]->boot_millivolts > mv) + WARN(1, "%s: boot voltage %d above nominal %d\n", + rails[i]->reg_id, rails[i]->boot_millivolts, mv); + if (rails[i]->disable_millivolts > mv) + rails[i]->disable_millivolts = mv; + if (rails[i]->suspend_millivolts > mv) + rails[i]->suspend_millivolts = mv; + + mv = tegra_dvfs_rail_get_boot_level(rails[i]); + rails[i]->millivolts = mv; + rails[i]->new_millivolts = mv; if (!rails[i]->step) rails[i]->step = rails[i]->max_millivolts; @@ -380,6 +401,13 @@ static int dvfs_rail_connect_to_regulator(struct dvfs_rail *rail) rail->millivolts = v / 1000; rail->new_millivolts = rail->millivolts; dvfs_rail_stats_init(rail, rail->millivolts); + + if (rail->boot_millivolts && + (rail->boot_millivolts != rail->millivolts)) { + WARN(1, "%s boot voltage %d does not match expected %d\n", + rail->reg_id, rail->millivolts, rail->boot_millivolts); + rail->boot_millivolts = rail->millivolts; + } return 0; } @@ -400,7 +428,7 @@ static int __tegra_dvfs_set_rate(struct dvfs *d, unsigned long rate) { int i = 0; - int ret; + int ret, mv, detach_mv; unsigned long *freqs = dvfs_get_freqs(d); const int *millivolts = dvfs_get_millivolts(d, rate); @@ -430,6 +458,28 @@ __tegra_dvfs_set_rate(struct dvfs *d, unsigned long rate) " %s\n", millivolts[i], d->clk_name); return -EINVAL; } + + mv = millivolts[i]; + detach_mv = tegra_dvfs_rail_get_boot_level(d->dvfs_rail); + if (!d->dvfs_rail->reg && (mv > detach_mv)) { + pr_warn("%s: %s: voltage %d above boot limit %d\n", + __func__, d->clk_name, mv, detach_mv); + return -EINVAL; + } + + detach_mv = tegra_dvfs_rail_get_disable_level(d->dvfs_rail); + if (d->dvfs_rail->disabled && (mv > detach_mv)) { + pr_warn("%s: %s: voltage %d above disable limit %d\n", + __func__, d->clk_name, mv, detach_mv); + return -EINVAL; + } + + detach_mv = tegra_dvfs_rail_get_suspend_level(d->dvfs_rail); + if (d->dvfs_rail->suspended && (mv > detach_mv)) { + pr_warn("%s: %s: voltage %d above disable limit %d\n", + __func__, d->clk_name, mv, detach_mv); + return -EINVAL; + } d->cur_millivolts = millivolts[i]; } @@ -686,11 +736,19 @@ static int tegra_dvfs_suspend_one(void) list_for_each_entry(rail, &dvfs_rail_list, node) { if (!rail->suspended && !rail->disabled && tegra_dvfs_from_rails_suspended_or_solved(rail)) { - int mv = dvfs_rail_apply_limits( - rail, rail->nominal_millivolts); - ret = dvfs_rail_set_voltage(rail, mv); - if (ret) + /* apply suspend limit only if it is above current mv */ + int mv = tegra_dvfs_rail_get_suspend_level(rail); + mv = dvfs_rail_apply_limits(rail, mv); + ret = -EPERM; + + if (mv >= rail->millivolts) + ret = dvfs_rail_set_voltage(rail, mv); + if (ret) { + pr_err("tegra_dvfs: failed %s suspend at %d\n", + rail->reg_id, rail->millivolts); return ret; + } + rail->suspended = true; return 0; } @@ -734,24 +792,35 @@ static int tegra_dvfs_suspend(void) return ret; } -static int tegra_dvfs_pm_notify(struct notifier_block *nb, - unsigned long event, void *data) +static int tegra_dvfs_pm_suspend(struct notifier_block *nb, + unsigned long event, void *data) { - switch (event) { - case PM_SUSPEND_PREPARE: + if (event == PM_SUSPEND_PREPARE) { if (tegra_dvfs_suspend()) return NOTIFY_STOP; - break; - case PM_POST_SUSPEND: - tegra_dvfs_resume(); - break; + pr_info("tegra_dvfs: suspended\n"); } + return NOTIFY_OK; +}; +static int tegra_dvfs_pm_resume(struct notifier_block *nb, + unsigned long event, void *data) +{ + if (event == PM_POST_SUSPEND) { + tegra_dvfs_resume(); + pr_info("tegra_dvfs: resumed\n"); + } return NOTIFY_OK; }; -static struct notifier_block tegra_dvfs_nb = { - .notifier_call = tegra_dvfs_pm_notify, +static struct notifier_block tegra_dvfs_suspend_nb = { + .notifier_call = tegra_dvfs_pm_suspend, + .priority = -1, +}; + +static struct notifier_block tegra_dvfs_resume_nb = { + .notifier_call = tegra_dvfs_pm_resume, + .priority = 1, }; static int tegra_dvfs_reboot_notify(struct notifier_block *nb, @@ -774,7 +843,8 @@ static struct notifier_block tegra_dvfs_reboot_nb = { /* must be called with dvfs lock held */ static void __tegra_dvfs_rail_disable(struct dvfs_rail *rail) { - int ret; + int ret = -EPERM; + int mv; /* don't set voltage in DFLL mode - won't work, but break stats */ if (rail->dfll_mode) { @@ -782,12 +852,15 @@ static void __tegra_dvfs_rail_disable(struct dvfs_rail *rail) return; } - ret = dvfs_rail_set_voltage(rail, - dvfs_rail_apply_limits(rail, rail->nominal_millivolts)); + /* apply detach mode limit provided it is above current volatge */ + mv = tegra_dvfs_rail_get_disable_level(rail); + mv = dvfs_rail_apply_limits(rail, mv); + + if (mv >= rail->millivolts) + ret = dvfs_rail_set_voltage(rail, mv); if (ret) { - pr_info("dvfs: failed to set regulator %s to disable " - "voltage %d\n", rail->reg_id, - rail->nominal_millivolts); + pr_err("tegra_dvfs: failed to disable %s at %d\n", + rail->reg_id, rail->millivolts); return; } rail->disabled = true; @@ -1049,10 +1122,14 @@ int __init tegra_dvfs_late_init(void) mutex_unlock(&dvfs_lock); #ifdef CONFIG_TEGRA_SILICON_PLATFORM - if (!connected) + if (!connected) { + pr_warn("tegra_dvfs: DVFS regulators connection failed\n" + " !!!! voltage scaling is disabled !!!!\n"); return -ENODEV; + } #endif - register_pm_notifier(&tegra_dvfs_nb); + register_pm_notifier(&tegra_dvfs_suspend_nb); + register_pm_notifier(&tegra_dvfs_resume_nb); register_reboot_notifier(&tegra_dvfs_reboot_nb); list_for_each_entry(rail, &dvfs_rail_list, node) diff --git a/arch/arm/mach-tegra/dvfs.h b/arch/arm/mach-tegra/dvfs.h index b6bda2e4f8f6..3105606f9372 100644 --- a/arch/arm/mach-tegra/dvfs.h +++ b/arch/arm/mach-tegra/dvfs.h @@ -79,6 +79,10 @@ struct dvfs_rail { int millivolts; int new_millivolts; int offs_millivolts; + int boot_millivolts; + int disable_millivolts; + int suspend_millivolts; + bool suspended; bool dfll_mode; bool dfll_mode_updating; @@ -292,6 +296,13 @@ static inline int tegra_dvfs_rail_get_nominal_millivolts(struct dvfs_rail *rail) return -ENOENT; } +static inline int tegra_dvfs_rail_get_boot_level(struct dvfs_rail *rail) +{ + if (rail) + return rail->boot_millivolts ? : rail->nominal_millivolts; + return -ENOENT; +} + static inline int tegra_dvfs_rail_get_override_floor(struct dvfs_rail *rail) { if (rail) diff --git a/arch/arm/mach-tegra/tegra_core_volt_cap.c b/arch/arm/mach-tegra/tegra_core_volt_cap.c index 4627ef97360b..c842158c2b19 100644 --- a/arch/arm/mach-tegra/tegra_core_volt_cap.c +++ b/arch/arm/mach-tegra/tegra_core_volt_cap.c @@ -57,7 +57,8 @@ static int core_cap_level_set(int level, int core_nominal_mv) int ret = 0; if (!core_cap_table) { - if (level == core_nominal_mv) { + int mv = tegra_dvfs_rail_get_boot_level(tegra_core_rail); + if (level == mv) { core_buses_cap.level = level; return 0; } |