diff options
-rw-r--r-- | arch/arm/mach-tegra/Kconfig | 10 | ||||
-rw-r--r-- | arch/arm/mach-tegra/dvfs.c | 110 | ||||
-rw-r--r-- | arch/arm/mach-tegra/dvfs.h | 10 | ||||
-rw-r--r-- | arch/arm/mach-tegra/include/mach/clk.h | 1 |
4 files changed, 129 insertions, 2 deletions
diff --git a/arch/arm/mach-tegra/Kconfig b/arch/arm/mach-tegra/Kconfig index 1568e85a91e2..0224d4a48323 100644 --- a/arch/arm/mach-tegra/Kconfig +++ b/arch/arm/mach-tegra/Kconfig @@ -127,6 +127,7 @@ config ARCH_TEGRA_11x_SOC select TEGRA_ISOMGR_DEBUG select TEGRA_ERRATA_1157520 select TEGRA_CORE_EDP_LIMITS + select TEGRA_VDD_CORE_OVERRIDE select TEGRA_THERMAL_THROTTLE_EXACT_FREQ select ARM_ERRATA_799270 help @@ -899,4 +900,13 @@ config TEGRA_PLLM_SCALED granularity of possible memory rate steps. In this case PLLC provides a backup memory clock while PLLM is re-locking to the new rate. + +config TEGRA_VDD_CORE_OVERRIDE + bool "Enable core rail override support" + depends on TEGRA_SILICON_PLATFORM + default n + help + When enabled, core rail can be fixed and locked at specified voltage + within override range, and core modules clocks are capped at rates + safe at override level. endif diff --git a/arch/arm/mach-tegra/dvfs.c b/arch/arm/mach-tegra/dvfs.c index 50fbb406f4b0..66877de687f3 100644 --- a/arch/arm/mach-tegra/dvfs.c +++ b/arch/arm/mach-tegra/dvfs.c @@ -289,7 +289,10 @@ static inline int dvfs_rail_apply_limits(struct dvfs_rail *rail, int millivolts) min_mv = max(min_mv, rail->thermal_idx ? 0 : rail->min_millivolts_cold); - millivolts += rail->offs_millivolts; + if (rail->override_millivolts) + millivolts = rail->override_millivolts; + else + millivolts += rail->offs_millivolts; if (millivolts > rail->max_millivolts) millivolts = rail->max_millivolts; else if (millivolts < min_mv) @@ -529,6 +532,71 @@ int tegra_dvfs_set_rate(struct clk *c, unsigned long rate) } EXPORT_SYMBOL(tegra_dvfs_set_rate); +#ifdef CONFIG_TEGRA_VDD_CORE_OVERRIDE +static DEFINE_MUTEX(rail_override_lock); + +int tegra_dvfs_override_core_voltage(int override_mv) +{ + int ret, floor, ceiling; + struct dvfs_rail *rail = tegra_core_rail; + + if (!rail) + return -ENOENT; + + floor = rail->min_override_millivolts; + ceiling = rail->nominal_millivolts; + if (override_mv && ((override_mv < floor) || (override_mv > ceiling))) { + pr_err("%s: override level %d outside the range [%d...%d]\n", + __func__, override_mv, floor, ceiling); + return -EINVAL; + } + + mutex_lock(&rail_override_lock); + + if (override_mv) { + ret = tegra_dvfs_core_cap_level_apply(override_mv); + if (ret) { + pr_err("%s: failed to set cap for override level %d\n", + __func__, override_mv); + goto out; + } + } + + mutex_lock(&dvfs_lock); + if (rail->disabled || rail->suspended) { + pr_err("%s: cannot scale %s rail\n", __func__, + rail->disabled ? "disabled" : "suspended"); + ret = -EPERM; + if (!override_mv) { + mutex_unlock(&dvfs_lock); + goto out; + } + } else { + rail->override_millivolts = override_mv; + ret = dvfs_rail_update(rail); + if (ret) { + pr_err("%s: failed to set override level %d\n", + __func__, override_mv); + rail->override_millivolts = 0; + dvfs_rail_update(rail); + } + } + mutex_unlock(&dvfs_lock); + + if (!override_mv || ret) + tegra_dvfs_core_cap_level_apply(0); +out: + mutex_unlock(&rail_override_lock); + return ret; +} +#else +int tegra_dvfs_override_core_voltage(int override_mv) +{ + return -ENOSYS; +} +#endif +EXPORT_SYMBOL(tegra_dvfs_override_core_voltage); + /* May only be called during clock init, does not take any locks on clock c. */ int __init tegra_enable_dvfs_on_clk(struct clk *c, struct dvfs *d) { @@ -561,6 +629,18 @@ int __init tegra_enable_dvfs_on_clk(struct clk *c, struct dvfs *d) c->dvfs = d; + /* + * Minimum core override level is determined as maximum voltage required + * for clocks outside shared buses (shared bus rates can be capped to + * safe levels when override limit is set) + */ + if (i && c->ops && !c->ops->shared_bus_update && + !(c->flags & PERIPH_ON_CBUS)) { + int mv = tegra_dvfs_predict_millivolts(c, d->freqs[i-1]); + if (d->dvfs_rail->min_override_millivolts < mv) + d->dvfs_rail->min_override_millivolts = mv; + } + mutex_lock(&dvfs_lock); list_add_tail(&d->reg_node, &d->dvfs_rail->dvfs); mutex_unlock(&dvfs_lock); @@ -1056,6 +1136,13 @@ static int dvfs_tree_show(struct seq_file *s, void *data) } seq_printf(s, " offset %-7d mV\n", rail->offs_millivolts); + if (rail == tegra_core_rail) { + seq_printf(s, " override %-7d mV [%-4d...%-4d]\n", + rail->override_millivolts, + rail->min_override_millivolts, + rail->nominal_millivolts); + } + list_sort(NULL, &rail->dvfs, dvfs_tree_sort_cmp); list_for_each_entry(d, &rail->dvfs, reg_node) { @@ -1151,6 +1238,22 @@ static int core_offs_set(void *data, u64 val) } DEFINE_SIMPLE_ATTRIBUTE(core_offs_fops, core_offs_get, core_offs_set, "%lld\n"); +static int core_override_get(void *data, u64 *val) +{ + if (tegra_core_rail) { + *val = (u64)tegra_core_rail->override_millivolts; + return 0; + } + *val = 0; + return -ENOENT; +} +static int core_override_set(void *data, u64 val) +{ + return tegra_dvfs_override_core_voltage((int)val); +} +DEFINE_SIMPLE_ATTRIBUTE(core_override_fops, + core_override_get, core_override_set, "%llu\n"); + int __init dvfs_debugfs_init(struct dentry *clk_debugfs_root) { struct dentry *d; @@ -1175,6 +1278,11 @@ int __init dvfs_debugfs_init(struct dentry *clk_debugfs_root) if (!d) return -ENOMEM; + d = debugfs_create_file("vdd_core_override", S_IRUGO | S_IWUSR, + clk_debugfs_root, NULL, &core_override_fops); + if (!d) + return -ENOMEM; + return 0; } diff --git a/arch/arm/mach-tegra/dvfs.h b/arch/arm/mach-tegra/dvfs.h index 2b73b6d200a3..a532c82f92ff 100644 --- a/arch/arm/mach-tegra/dvfs.h +++ b/arch/arm/mach-tegra/dvfs.h @@ -63,7 +63,8 @@ struct dvfs_rail { int reg_max_millivolts; int nominal_millivolts; int min_millivolts_cold; - + int override_millivolts; + int min_override_millivolts; int step; bool jmp_to_zero; bool disabled; @@ -292,4 +293,11 @@ static inline int tegra_dvfs_rail_get_nominal_millivolts(struct dvfs_rail *rail) return -ENOENT; } +static inline int tegra_dvfs_rail_get_override_floor(struct dvfs_rail *rail) +{ + if (rail) + return rail->min_override_millivolts; + return -ENOENT; +} + #endif diff --git a/arch/arm/mach-tegra/include/mach/clk.h b/arch/arm/mach-tegra/include/mach/clk.h index ea9dbae6c7e7..bcce817cc1b3 100644 --- a/arch/arm/mach-tegra/include/mach/clk.h +++ b/arch/arm/mach-tegra/include/mach/clk.h @@ -40,6 +40,7 @@ void tegra_periph_reset_deassert(struct clk *c); void tegra_periph_reset_assert(struct clk *c); int tegra_dvfs_set_rate(struct clk *c, unsigned long rate); +int tegra_dvfs_override_core_voltage(int override_mv); unsigned long clk_get_rate_all_locked(struct clk *c); #ifdef CONFIG_ARCH_TEGRA_2x_SOC |