tegrakernel/kernel/kernel-4.9/drivers/soc/tegra/tegra-dvfs.c

2450 lines
55 KiB
C
Raw Normal View History

2022-02-16 09:13:02 -06:00
/*
* Copyright (C) 2010 Google, Inc.
*
* Author:
* Colin Cross <ccross@google.com>
*
* Copyright (c) 2014-2018, NVIDIA CORPORATION. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <linux/kernel.h>
#include <linux/clk.h>
#include <linux/clkdev.h>
#include <linux/clk-provider.h>
#include <linux/debugfs.h>
#include <linux/init.h>
#include <linux/list.h>
#include <linux/list_sort.h>
#include <linux/module.h>
#include <linux/regulator/consumer.h>
#include <linux/suspend.h>
#include <linux/thermal.h>
#include <linux/reboot.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/pm_opp.h>
#include <linux/cpu.h>
#include <soc/tegra/tegra-dfll.h>
#include <soc/tegra/tegra-dvfs.h>
struct dvfs_rail *tegra_cpu_rail;
struct dvfs_rail *tegra_core_rail;
static struct dvfs_rail *tegra_gpu_rail;
bool core_dvfs_started;
static LIST_HEAD(dvfs_rail_list);
static DEFINE_MUTEX(dvfs_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;
struct dvfs_relationship *rel;
mutex_lock(&dvfs_lock);
for (i = 0; i < n; i++) {
rel = &rels[i];
list_add_tail(&rel->from_node, &rel->to->relationships_from);
list_add_tail(&rel->to_node, &rel->from->relationships_to);
}
mutex_unlock(&dvfs_lock);
}
static void init_rails_lists(struct dvfs_rail *rails[], int n)
{
int i;
static bool initialized;
if (initialized)
return;
initialized = true;
for (i = 0; i < n; i++) {
INIT_LIST_HEAD(&rails[i]->dvfs);
INIT_LIST_HEAD(&rails[i]->relationships_from);
INIT_LIST_HEAD(&rails[i]->relationships_to);
list_add_tail(&rails[i]->node, &dvfs_rail_list);
}
}
void tegra_dvfs_init_rails_lists(struct dvfs_rail *rails[], int n)
{
mutex_lock(&dvfs_lock);
init_rails_lists(rails, n);
mutex_unlock(&dvfs_lock);
}
int tegra_dvfs_init_rails(struct dvfs_rail *rails[], int n)
{
int i, mv;
mutex_lock(&dvfs_lock);
init_rails_lists(rails, n);
for (i = 0; i < n; i++) {
mv = rails[i]->nominal_millivolts;
if (rails[i]->disable_millivolts > mv)
rails[i]->disable_millivolts = mv;
if (rails[i]->suspend_millivolts > mv)
rails[i]->suspend_millivolts = mv;
rails[i]->millivolts = mv;
rails[i]->new_millivolts = mv;
if (!rails[i]->step)
rails[i]->step = rails[i]->max_millivolts;
if (!rails[i]->step_up)
rails[i]->step_up = rails[i]->step;
if (!strcmp("vdd-cpu", rails[i]->reg_id))
tegra_cpu_rail = rails[i];
else if (!strcmp("vdd-core", rails[i]->reg_id))
tegra_core_rail = rails[i];
else if (!strcmp("vdd-gpu", rails[i]->reg_id))
tegra_gpu_rail = rails[i];
}
mutex_unlock(&dvfs_lock);
return 0;
}
static int dvfs_solve_relationship(struct dvfs_relationship *rel)
{
return rel->solve(rel->from, rel->to);
}
static void dvfs_rail_stats_init(struct dvfs_rail *rail, int millivolts)
{
int dvfs_rail_stats_range;
if (!rail->stats.bin_uv)
rail->stats.bin_uv = DVFS_RAIL_STATS_BIN;
dvfs_rail_stats_range =
(DVFS_RAIL_STATS_TOP_BIN - 1) * rail->stats.bin_uv / 1000;
rail->stats.last_update = ktime_get();
if (millivolts >= rail->min_millivolts) {
int i = 1 + (2 * (millivolts - rail->min_millivolts) * 1000 +
rail->stats.bin_uv) / (2 * rail->stats.bin_uv);
rail->stats.last_index = min(i, DVFS_RAIL_STATS_TOP_BIN);
}
if (rail->max_millivolts >
rail->min_millivolts + dvfs_rail_stats_range)
pr_warn("tegra_dvfs: %s: stats above %d mV will be squashed\n",
rail->reg_id,
rail->min_millivolts + dvfs_rail_stats_range);
}
static void dvfs_rail_stats_update(
struct dvfs_rail *rail, int millivolts, ktime_t now)
{
rail->stats.time_at_mv[rail->stats.last_index] = ktime_add(
rail->stats.time_at_mv[rail->stats.last_index], ktime_sub(
now, rail->stats.last_update));
rail->stats.last_update = now;
if (rail->stats.off)
return;
if (millivolts >= rail->min_millivolts) {
int i = 1 + (2 * (millivolts - rail->min_millivolts) * 1000 +
rail->stats.bin_uv) / (2 * rail->stats.bin_uv);
rail->stats.last_index = min(i, DVFS_RAIL_STATS_TOP_BIN);
} else if (millivolts == 0)
rail->stats.last_index = 0;
}
static int dvfs_rail_set_voltage_reg(struct dvfs_rail *rail, int millivolts)
{
int ret;
if (rail->joint_rail_with_dfll)
tegra_dfll_set_external_floor_mv(rail->new_millivolts);
ret = regulator_set_voltage(rail->reg,
millivolts * 1000,
rail->max_millivolts * 1000);
return ret;
}
/**
* dvfs_rail_set_voltage - set voltage in millivolts to specific rail
*
* @rail: struct dvfs_rail * power rail context
* @millivolts: voltage in millivolts to be set to regulator
*
* Sets the voltage on a dvfs rail to a specific value, and updates any
* rails that depend on this rail.
*/
static int dvfs_rail_set_voltage(struct dvfs_rail *rail, int millivolts)
{
int ret = 0;
struct dvfs_relationship *rel;
int step, offset;
int i;
int steps;
bool jmp_to_zero;
if (!rail->reg) {
if (millivolts == rail->millivolts)
return 0;
else
return -EINVAL;
}
if (millivolts > rail->millivolts) {
step = rail->step_up;
offset = step;
} else {
step = rail->step;
offset = -step;
}
if (rail->dfll_mode) {
rail->millivolts = millivolts;
rail->new_millivolts = millivolts;
dvfs_rail_stats_update(rail, millivolts, ktime_get());
return 0;
}
if (rail->disabled)
return 0;
rail->resolving_to = true;
jmp_to_zero = rail->jmp_to_zero &&
((millivolts == 0) || (rail->millivolts == 0));
if (jmp_to_zero || (rail->in_band_pm && rail->stats.off))
steps = 1;
else
steps = DIV_ROUND_UP(abs(millivolts - rail->millivolts), step);
for (i = 0; i < steps; i++) {
if ((i + 1) < steps)
rail->new_millivolts = rail->millivolts + offset;
else
rail->new_millivolts = millivolts;
/*
* Before changing the voltage, tell each rail that depends
* on this rail that the voltage will change.
* This rail will be the "from" rail in the relationship,
* the rail that depends on this rail will be the "to" rail.
* from->millivolts will be the old voltage
* from->new_millivolts will be the new voltage
*/
list_for_each_entry(rel, &rail->relationships_to, to_node) {
ret = dvfs_rail_update(rel->to);
if (ret)
goto out;
}
ret = dvfs_rail_set_voltage_reg(rail, rail->new_millivolts);
if (ret) {
pr_err("Failed to set dvfs regulator %s\n",
rail->reg_id);
goto out;
}
rail->millivolts = rail->new_millivolts;
dvfs_rail_stats_update(rail, rail->millivolts, ktime_get());
/*
* After changing the voltage, tell each rail that depends
* on this rail that the voltage has changed.
* from->millivolts and from->new_millivolts will be the
* new voltage
*/
list_for_each_entry(rel, &rail->relationships_to, to_node) {
ret = dvfs_rail_update(rel->to);
if (ret)
goto out;
}
}
if (unlikely(rail->millivolts != millivolts)) {
pr_err("%s: rail didn't reach target %d in %d steps (%d)\n",
__func__, millivolts, steps, rail->millivolts);
ret = -EINVAL;
}
out:
rail->resolving_to = false;
return ret;
}
static int dvfs_rail_apply_limits(struct dvfs_rail *rail, int millivolts,
bool warn_on_cap)
{
int min_mv = rail->min_millivolts;
int max_mv = rail->max_millivolts;
if (rail->therm_floors) {
int i = rail->therm_floor_idx;
if (i < rail->therm_floors_size)
min_mv = rail->therm_floors[i].mv;
}
if (rail->therm_caps && warn_on_cap) {
int i = rail->therm_cap_idx;
if ((i > 0) && (millivolts > rail->therm_caps[i - 1].mv)) {
WARN(!rail->therm_cap_warned,
"tegra_dvfs: %s set to %dmV above cap %dmV\n",
rail->reg_id, millivolts,
rail->therm_caps[i - 1].mv);
rail->therm_cap_warned = true;
} else {
rail->therm_cap_warned = false;
}
}
if (rail->override_millivolts) {
millivolts = rail->override_millivolts;
return millivolts;
} else if (rail->dbg_mv_offs) {
/* apply offset and ignore limits */
millivolts += rail->dbg_mv_offs;
return millivolts;
}
millivolts = clamp_val(millivolts, min_mv, max_mv);
return millivolts;
}
/**
* dvfs_rail_update - update rail voltage
*
* @rail: struct dvfs_rail * power rail context
*
* Determine the minimum valid voltage for a rail, taking into account
* the dvfs clocks and any rails that this rail depends on. Calls
* dvfs_rail_set_voltage with the new voltage, which will call
* dvfs_rail_update on any rails that depend on this rail.
*/
static int dvfs_rail_update(struct dvfs_rail *rail)
{
int millivolts = 0;
struct dvfs *d;
struct dvfs_relationship *rel;
int ret = 0;
int steps;
if (rail->disabled)
return 0;
/* if dvfs is suspended, return and handle it during resume */
if (rail->suspended)
return 0;
/* if regulators are not connected yet, return and handle it later */
if (!rail->reg)
return 0;
/* if rail update is entered while resolving circular dependencies,
abort recursion */
if (rail->resolving_to)
return 0;
/* Find the maximum voltage requested by any clock */
list_for_each_entry(d, &rail->dvfs, reg_node)
millivolts = max(d->cur_millivolts, millivolts);
/* Apply offset and min/max limits if any clock is requesting voltage */
if (millivolts)
millivolts = dvfs_rail_apply_limits(rail, millivolts, true);
/* Keep current voltage if regulator is to be disabled via explicitly */
else if (rail->in_band_pm)
return 0;
/* Keep current voltage if regulator must not be disabled at run time */
else if (!rail->jmp_to_zero) {
WARN(1, "%s cannot be turned off by dvfs\n", rail->reg_id);
return 0;
}
/*
* retry update if limited by from-relationship to account for
* circular dependencies
*/
steps = DIV_ROUND_UP(abs(millivolts - rail->millivolts), rail->step);
for (; steps >= 0; steps--) {
rail->new_millivolts = millivolts;
/* Check any rails that this rail depends on */
list_for_each_entry(rel, &rail->relationships_from, from_node)
rail->new_millivolts = dvfs_solve_relationship(rel);
if (rail->new_millivolts == rail->millivolts)
break;
ret = dvfs_rail_set_voltage(rail, rail->new_millivolts);
}
return ret;
}
static int dvfs_rail_connect_to_regulator(struct device *dev,
struct dvfs_rail *rail)
{
struct regulator *reg;
int v;
if (!rail->reg) {
mutex_unlock(&dvfs_lock);
reg = regulator_get(dev, rail->reg_id);
mutex_lock(&dvfs_lock);
if (IS_ERR(reg)) {
pr_err("tegra_dvfs: failed to connect %s rail\n",
rail->reg_id);
return PTR_ERR(reg);
}
rail->reg = reg;
}
if (!rail->in_band_pm) {
v = regulator_enable(rail->reg);
if (v < 0) {
pr_err("tegra_dvfs: failed on enabling regulator %s\n, err %d",
rail->reg_id, v);
return v;
}
}
v = regulator_get_voltage(rail->reg);
if (v < 0) {
pr_err("tegra_dvfs: failed initial get %s voltage\n",
rail->reg_id);
return v;
}
if (!rail->min_millivolts) {
int min_uv, max_uv;
if (!regulator_get_constraint_voltages(rail->reg, &min_uv,
&max_uv))
rail->min_millivolts = min_uv / 1000;
}
if (v > rail->nominal_millivolts * 1000) {
if (dvfs_rail_set_voltage_reg(rail, rail->nominal_millivolts)) {
pr_err("tegra_dvfs: failed lower %s voltage %d to %d\n",
rail->reg_id, v, rail->nominal_millivolts);
return -EINVAL;
}
v = rail->nominal_millivolts * 1000;
}
rail->millivolts = v / 1000;
rail->new_millivolts = rail->millivolts;
dvfs_rail_stats_init(rail, rail->millivolts);
return 0;
}
static inline const int *dvfs_get_millivolts_pll(struct dvfs *d)
{
if (d->therm_dvfs) {
int therm_idx = d->dvfs_rail->therm_scale_idx;
return d->millivolts + therm_idx * MAX_DVFS_FREQS;
}
return d->millivolts;
}
static inline const int *dvfs_get_millivolts(struct dvfs *d, unsigned long rate)
{
if (tegra_dvfs_is_dfll_scale(d, rate))
return d->dfll_millivolts;
return dvfs_get_millivolts_pll(d);
}
static inline const int *dvfs_get_peak_millivolts(struct dvfs *d, unsigned long rate)
{
if (tegra_dvfs_is_dfll_scale(d, rate))
return d->dfll_millivolts;
if (d->peak_millivolts)
return d->peak_millivolts;
return dvfs_get_millivolts_pll(d);
}
static unsigned long *dvfs_get_freqs(struct dvfs *d)
{
if (d->use_alt_freqs)
return &d->alt_freqs[0];
else
return &d->freqs[0];
}
static int __tegra_dvfs_set_rate(struct dvfs *d, unsigned long rate)
{
int i = 0;
int ret, mv;
unsigned long *freqs = dvfs_get_freqs(d);
const int *millivolts = dvfs_get_millivolts(d, rate);
if (freqs == NULL || millivolts == NULL)
return -ENODEV;
/*
* On entry to dfll range limit 1st step to range bottom (full ramp of
* voltage/rate is completed automatically in dfll mode)
*/
if (tegra_dvfs_is_dfll_range_entry(d, rate))
rate = d->use_dfll_rate_min;
if (rate > freqs[d->num_freqs - 1]) {
pr_warn("tegra-dvfs: rate %lu too high for dvfs on %s\n", rate,
d->clk_name);
return -EINVAL;
}
if (rate == 0) {
d->cur_millivolts = 0;
} else {
while (i < d->num_freqs && rate > freqs[i])
i++;
if ((d->max_millivolts) &&
(millivolts[i] > d->max_millivolts)) {
pr_warn("tegra-dvfs: voltage %d too high for dvfs on %s\n",
millivolts[i], d->clk_name);
return -EINVAL;
}
mv = millivolts[i];
d->cur_millivolts = millivolts[i];
}
d->cur_rate = rate;
ret = dvfs_rail_update(d->dvfs_rail);
if (ret)
pr_err("Failed to set regulator %s for clock %s to %d mV\n",
d->dvfs_rail->reg_id, d->clk_name, d->cur_millivolts);
return ret;
}
static struct dvfs *tegra_clk_to_dvfs(struct clk *c)
{
struct dvfs *d;
struct dvfs_rail *rail;
list_for_each_entry(rail, &dvfs_rail_list, node) {
list_for_each_entry(d, &rail->dvfs, reg_node) {
if (clk_is_match(c, d->clk))
return d;
}
}
return NULL;
}
/*
* Using non alt frequencies always results in peak voltage
* (enforced by alt_freqs_validate())
*/
static int predict_non_alt_millivolts(struct dvfs *d, const int *millivolts,
unsigned long rate)
{
int i;
int vmin = d->dvfs_rail->min_millivolts;
unsigned long dvfs_unit = 1 * d->freqs_mult;
if (!millivolts)
return -ENODEV;
if (millivolts == d->dfll_millivolts)
vmin = tegra_dfll_get_min_millivolts();
for (i = 0; i < d->num_freqs; i++) {
unsigned long f = d->freqs[i];
if ((dvfs_unit < f) && (rate <= f))
break;
}
if (i == d->num_freqs)
i--;
return max(millivolts[i], vmin);
}
static int predict_millivolts(struct dvfs *d, const int *millivolts,
unsigned long rate)
{
int i;
unsigned long *freqs = dvfs_get_freqs(d);
if (!millivolts)
return -ENODEV;
for (i = 0; i < d->num_freqs; i++) {
if (rate <= freqs[i])
break;
}
if (i == d->num_freqs)
return -EINVAL;
return millivolts[i];
}
static int dvfs_rail_get_thermal_floor(struct dvfs_rail *rail)
{
if (rail->therm_floors &&
rail->therm_floor_idx < rail->therm_floors_size)
return rail->therm_floors[rail->therm_floor_idx].mv;
return 0;
}
static int dvfs_dfll_get_peak_thermal_floor(void)
{
struct rail_alignment *align = tegra_dfll_get_alignment();
int mv;
if (WARN_ON(IS_ERR(align)))
return 0;
mv = tegra_dfll_get_peak_thermal_floor_mv();
if (mv < 0)
return 0;
return tegra_round_voltage(mv, align, 1);
}
static int dvfs_get_peak_thermal_floor(struct dvfs *d, unsigned long rate)
{
bool dfll_range = dvfs_is_dfll_range(d, rate);
if (!dfll_range && d->dvfs_rail->therm_floors)
return d->dvfs_rail->therm_floors[0].mv;
if (dfll_range)
return dvfs_dfll_get_peak_thermal_floor();
return 0;
}
static int predict_mv_at_hz_no_tfloor(struct dvfs *d, unsigned long rate)
{
const int *millivolts;
millivolts = dvfs_get_millivolts(d, rate);
return predict_millivolts(d, millivolts, rate);
}
static int predict_mv_at_hz_cur_tfloor(struct dvfs *d, unsigned long rate)
{
int mv;
mv = predict_mv_at_hz_no_tfloor(d, rate);
if (mv < 0)
return mv;
return max(mv, dvfs_rail_get_thermal_floor(d->dvfs_rail));
}
int opp_millivolts[MAX_DVFS_FREQS];
unsigned long opp_frequencies[MAX_DVFS_FREQS];
/**
* tegra_get_cpu_fv_table - get CPU frequencies/voltages table
*
* @num_freqs: number of frequencies
* @freqs: the array of frequencies
* @mvs: the array of voltages
*
* Get the frequency and voltage table using CPU OPP which were built by the
* DFLL driver.
*/
int tegra_get_cpu_fv_table(int *num_freqs, unsigned long **freqs, int **mvs)
{
struct device *cpu_dev;
struct dev_pm_opp *opp;
unsigned long rate;
int i, ret = 0;
cpu_dev = get_cpu_device(0);
if (!cpu_dev)
return -EINVAL;
mutex_lock(&dvfs_lock);
for (i = 0, rate = 0;; rate++) {
rcu_read_lock();
opp = dev_pm_opp_find_freq_ceil(cpu_dev, &rate);
if (IS_ERR(opp)) {
rcu_read_unlock();
break;
}
opp_frequencies[i] = rate;
opp_millivolts[i++] = dev_pm_opp_get_voltage(opp);
rcu_read_unlock();
}
if (!i) {
ret = -EINVAL;
goto out;
}
*num_freqs = i;
*freqs = opp_frequencies;
*mvs = opp_millivolts;
out:
mutex_unlock(&dvfs_lock);
return ret;
}
EXPORT_SYMBOL(tegra_get_cpu_fv_table);
/**
* tegra_dvfs_predict_millivolts - return the safe voltage for running
* the clock at one sepcific rate
*
* @c: struct clk * the clock which needs the voltage info
* @rate: the rate being predicted
*
* Extract the voltage table associated with the clock and return the safe
* voltage for ticking the clock at the specified rate
*/
int tegra_dvfs_predict_millivolts(struct clk *c, unsigned long rate)
{
int mv;
struct dvfs *d;
d = tegra_clk_to_dvfs(c);
if (d == NULL)
return -EINVAL;
if (!rate)
return 0;
mutex_lock(&dvfs_lock);
mv = predict_mv_at_hz_no_tfloor(d, rate);
mutex_unlock(&dvfs_lock);
return mv;
}
EXPORT_SYMBOL(tegra_dvfs_predict_millivolts);
int tegra_dvfs_predict_mv_at_hz_cur_tfloor(struct clk *c, unsigned long rate)
{
int mv;
struct dvfs *d;
d = tegra_clk_to_dvfs(c);
if (d == NULL)
return -EINVAL;
if (!rate)
return 0;
mutex_lock(&dvfs_lock);
mv = predict_mv_at_hz_cur_tfloor(d, rate);
mutex_unlock(&dvfs_lock);
return mv;
}
EXPORT_SYMBOL(tegra_dvfs_predict_mv_at_hz_cur_tfloor);
/*
* Predict minimum voltage required to run target clock at specified rate.
* Evaluate target clock domain V/F relation, and apply proper PLL or
* DFLL table depending on specified rate range. Apply maximum thermal floor
* across all temperature ranges.
*/
static int dvfs_predict_mv_at_hz_max_tfloor(struct dvfs *d, unsigned long rate)
{
int mv;
const int *millivolts;
if (!d)
return -ENODATA;
millivolts = dvfs_get_peak_millivolts(d, rate);
mv = predict_non_alt_millivolts(d, millivolts, rate);
if (mv < 0)
return mv;
return max(mv, dvfs_get_peak_thermal_floor(d, rate));
}
int tegra_dvfs_predict_mv_at_hz_max_tfloor(struct clk *c, unsigned long rate)
{
struct dvfs *d;
int mv;
d = tegra_clk_to_dvfs(c);
if (d == NULL)
return -EINVAL;
mutex_lock(&dvfs_lock);
mv = dvfs_predict_mv_at_hz_max_tfloor(d, rate);
mutex_unlock(&dvfs_lock);
return mv;
}
EXPORT_SYMBOL(tegra_dvfs_predict_mv_at_hz_max_tfloor);
long tegra_dvfs_predict_hz_at_mv_max_tfloor(struct clk *c, int mv)
{
int mv_at_f = 0, i;
struct dvfs *d;
const int *millivolts;
unsigned long rate = -EINVAL;
/* Recursively search for ancestor with DVFS */
do {
d = tegra_clk_to_dvfs(c);
if (!d)
c = clk_get_parent(c);
} while (!d && !IS_ERR_OR_NULL(c));
if (d == NULL)
return -EINVAL;
mutex_lock(&dvfs_lock);
if (d->alt_freqs)
goto out;
for (i = 0; i < d->num_freqs; i++) {
rate = d->freqs[i];
millivolts = dvfs_get_peak_millivolts(d, rate);
if (!millivolts)
goto out;
if (d->dvfs_rail->therm_floors)
mv_at_f = d->dvfs_rail->therm_floors[0].mv;
mv_at_f = max(millivolts[i], mv_at_f);
if (mv < mv_at_f)
break;
}
if (i)
rate = d->freqs[i - 1];
if (!i || (rate <= d->freqs_mult))
rate = -ENOENT;
out:
mutex_unlock(&dvfs_lock);
return rate;
}
/**
* tegra_dvfs_set_rate - update rail voltage due to the clock rate change
*
* @c: struct clk * the clock which has changed rate
* @rate: the changed rate
*
* Check if the voltage of the power rail need to be updated due to the clock
* rate change.
*/
int tegra_dvfs_set_rate(struct clk *c, unsigned long rate)
{
int ret = 0;
struct dvfs *d;
if (!core_dvfs_started)
return ret;
mutex_lock(&dvfs_lock);
d = tegra_clk_to_dvfs(c);
if (d)
ret = __tegra_dvfs_set_rate(d, rate);
mutex_unlock(&dvfs_lock);
return ret;
}
EXPORT_SYMBOL(tegra_dvfs_set_rate);
/**
* tegra_dvfs_get_rate - get current rate used for determining rail voltage
*
* @c: struct clk * clock we want to know the rate of used for determining
* rail voltage
*
* Returns 0 if there is no dvfs for the clock.
*/
unsigned long tegra_dvfs_get_rate(struct clk *c)
{
unsigned long rate = 0;
struct dvfs *d;
if (!core_dvfs_started)
return rate;
mutex_lock(&dvfs_lock);
d = tegra_clk_to_dvfs(c);
if (d)
rate = d->cur_rate;
mutex_unlock(&dvfs_lock);
return rate;
}
EXPORT_SYMBOL(tegra_dvfs_get_rate);
/**
* tegra_dvfs_get_freqs - export dvfs frequency array associated with the clock
*
* @c: struct clk * the clock which needs the frequency table
* @freqs: the array of the frequencies
* @num_freqs: number of the frequencies
*
* Check if the voltage of the power rail need to be updated due to the clock
* rate change.
*/
int tegra_dvfs_get_freqs(struct clk *c, unsigned long **freqs, int *num_freqs)
{
struct dvfs *d;
d = tegra_clk_to_dvfs(c);
if (d == NULL) {
if (core_dvfs_started) {
pr_err("Failed to get %s dvfs structure\n", __clk_get_name(c));
return -ENOSYS;
}
return -EINVAL;
}
*num_freqs = d->num_freqs;
*freqs = dvfs_get_freqs(d);
return 0;
}
EXPORT_SYMBOL(tegra_dvfs_get_freqs);
unsigned long tegra_dvfs_get_maxrate(struct clk *c)
{
unsigned long rate = 0;
int err, num_freqs;
unsigned long *freqs;
err = tegra_dvfs_get_freqs(c, &freqs, &num_freqs);
if (err < 0)
return rate;
return freqs[num_freqs - 1];
}
EXPORT_SYMBOL(tegra_dvfs_get_maxrate);
unsigned long tegra_dvfs_round_rate(struct clk *c, unsigned long rate)
{
int i, num_freqs;
unsigned long *freqs, ret;
mutex_lock(&dvfs_lock);
ret = tegra_dvfs_get_freqs(c, &freqs, &num_freqs);
if (IS_ERR_VALUE(ret))
goto out;
for (i = 0; i < num_freqs; i++) {
if (freqs[i] >= rate) {
ret = freqs[i];
goto out;
}
}
ret = freqs[i-1];
out:
mutex_unlock(&dvfs_lock);
return ret;
}
int tegra_dvfs_use_alt_freqs_on_clk(struct clk *c, bool use_alt_freq)
{
struct dvfs *d;
int err = -ENOENT;
mutex_lock(&dvfs_lock);
d = tegra_clk_to_dvfs(c);
if (d && d->alt_freqs) {
err = 0;
if (d->use_alt_freqs != use_alt_freq) {
d->use_alt_freqs = use_alt_freq;
if (__tegra_dvfs_set_rate(d, d->cur_rate) < 0) {
d->use_alt_freqs = !use_alt_freq;
pr_err("%s: %s: %s alt dvfs failed\n", __func__,
d->clk_name,
use_alt_freq ? "set" : "clear");
__tegra_dvfs_set_rate(d, d->cur_rate);
err = -EINVAL;
}
}
}
mutex_unlock(&dvfs_lock);
return err;
}
EXPORT_SYMBOL(tegra_dvfs_use_alt_freqs_on_clk);
static int tegra_dvfs_clk_event(struct notifier_block *this,
unsigned long event, void *ptr)
{
struct clk_notifier_data *cnd = ptr;
struct dvfs *d;
int new_mv, err = 0;
d = tegra_clk_to_dvfs(cnd->clk);
if (d == NULL)
return NOTIFY_DONE;
if (d->dvfs_rail == tegra_core_rail && !core_dvfs_started)
return NOTIFY_DONE;
if (!__clk_is_enabled(cnd->clk) && !__clk_is_prepared(cnd->clk) &&
(d->dvfs_rail != tegra_gpu_rail))
return NOTIFY_DONE;
mutex_lock(&dvfs_lock);
switch (event) {
case PRE_RATE_CHANGE:
if (d->therm_dvfs) {
new_mv = predict_mv_at_hz_cur_tfloor(d, cnd->new_rate);
if (new_mv > d->cur_millivolts)
err = __tegra_dvfs_set_rate(d, cnd->new_rate);
} else if (cnd->old_rate < cnd->new_rate) {
err = __tegra_dvfs_set_rate(d, cnd->new_rate);
}
break;
case POST_RATE_CHANGE:
if (d->therm_dvfs) {
new_mv = predict_mv_at_hz_cur_tfloor(d, cnd->new_rate);
if (new_mv < d->cur_millivolts)
err = __tegra_dvfs_set_rate(d, cnd->new_rate);
} else if (cnd->old_rate > cnd->new_rate) {
err = __tegra_dvfs_set_rate(d, cnd->new_rate);
}
break;
case ABORT_RATE_CHANGE:
break;
}
mutex_unlock(&dvfs_lock);
if (err < 0)
return NOTIFY_BAD;
return NOTIFY_DONE;
}
static struct notifier_block tegra_dvfs_nb = {
.notifier_call = tegra_dvfs_clk_event,
.priority = 1,
};
static int use_alt_freq_get(void *data, u64 *val)
{
struct clk *c = (struct clk *)data;
struct dvfs *d;
d = tegra_clk_to_dvfs(c);
if (!d)
*val = 0;
else
*val = d->use_alt_freqs;
return 0;
}
static int use_alt_freq_set(void *data, u64 val)
{
struct clk *c = (struct clk *)data;
return tegra_dvfs_use_alt_freqs_on_clk(c, !!val);
}
DEFINE_SIMPLE_ATTRIBUTE(use_alt_freq_fops,
use_alt_freq_get, use_alt_freq_set, "%llu\n");
static void cleanup_dvfs_table(struct dvfs *d)
{
int i;
for (i = 0; i < MAX_DVFS_FREQS; i++) {
if (d->millivolts[i] == 0)
break;
if (d->freqs_mult)
d->freqs[i] *= d->freqs_mult;
/* If final frequencies are 0, pad with previous frequency */
if (d->freqs[i] == 0 && i > 1)
d->freqs[i] = d->freqs[i - 1];
}
/* Update num_freqs if not set at all, or set above cleaned max */
if (!d->num_freqs || (d->num_freqs > i))
d->num_freqs = i;
}
#ifdef CONFIG_TEGRA_CLK_DEBUG
static int dvfs_freq_offset_get(void *data, u64 *val)
{
struct dvfs *d = data;
*val = d->dbg_hz_offs;
return 0;
}
static int dvfs_freq_offset_set(void *data, u64 val)
{
struct dvfs *d = data;
int i, ret = 0;
long offs = (long)val - d->dbg_hz_offs;
unsigned long unit_rate = 1 * d->freqs_mult;
if (!offs || !d->num_freqs)
return 0;
mutex_lock(&dvfs_lock);
for (i = 0; i < d->num_freqs; i++) {
unsigned long rate = d->freqs[i];
if (rate <= unit_rate)
continue;
if ((offs < 0) && (rate <= unit_rate + (-offs))) {
ret = -EINVAL;
goto out;
}
d->freqs[i] = rate + offs;
}
d->dbg_hz_offs = (long)val;
out:
mutex_unlock(&dvfs_lock);
return ret;
}
DEFINE_SIMPLE_ATTRIBUTE(dvfs_freq_offset_fops, dvfs_freq_offset_get,
dvfs_freq_offset_set, "%lld\n");
#endif
int tegra_setup_dvfs(struct clk *c, struct dvfs *d)
{
cleanup_dvfs_table(d);
d->clk = c;
mutex_lock(&dvfs_lock);
list_add_tail(&d->reg_node, &d->dvfs_rail->dvfs);
mutex_unlock(&dvfs_lock);
#ifdef CONFIG_TEGRA_CLK_DEBUG
__clk_debugfs_add_file(c, "dvfs_freq_offs", S_IRUGO | S_IWUSR, d,
&dvfs_freq_offset_fops);
#endif
return 0;
}
int tegra_dvfs_add_alt_freqs(struct clk *c, struct dvfs *alt_d)
{
struct dvfs *d;
int err = 0;
mutex_lock(&dvfs_lock);
d = tegra_clk_to_dvfs(c);
if (!d) {
err = -EINVAL;
goto out;
}
cleanup_dvfs_table(alt_d);
if (alt_d->num_freqs < d->num_freqs) {
pr_err("tegra_dvfs: %s: %d alt freqs below %d main freqs\n",
d->clk_name, alt_d->num_freqs, d->num_freqs);
err = -EINVAL;
goto out;
}
d->alt_freqs = alt_d->freqs;
__clk_debugfs_add_file(c, "use_alt_freq", S_IRUGO | S_IWUSR, c,
&use_alt_freq_fops);
out:
mutex_unlock(&dvfs_lock);
return err;
}
static bool tegra_dvfs_all_rails_suspended(void)
{
struct dvfs_rail *rail;
list_for_each_entry(rail, &dvfs_rail_list, node)
if (!rail->suspended && !rail->disabled)
return false;
return true;
}
static bool tegra_dvfs_from_rails_suspended_or_solved(struct dvfs_rail *to)
{
struct dvfs_relationship *rel;
list_for_each_entry(rel, &to->relationships_from, from_node)
if ((!rel->from->suspended) &&
(!rel->from->disabled) &&
(!rel->solved_at_nominal))
return false;
return true;
}
static int tegra_dvfs_suspend_one(void)
{
struct dvfs_rail *rail;
int mv;
int ret = 0;
list_for_each_entry(rail, &dvfs_rail_list, node) {
if ((rail->suspended) ||
(rail->disabled) ||
(!tegra_dvfs_from_rails_suspended_or_solved(rail)))
continue;
mv = tegra_dvfs_rail_get_suspend_level(rail);
mv = dvfs_rail_apply_limits(rail, mv, false);
/* apply suspend limit only if it is above current mv */
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;
}
return -EINVAL;
}
static void tegra_dvfs_resume(void)
{
struct dvfs_rail *rail;
mutex_lock(&dvfs_lock);
list_for_each_entry(rail, &dvfs_rail_list, node)
rail->suspended = false;
list_for_each_entry(rail, &dvfs_rail_list, node)
dvfs_rail_update(rail);
mutex_unlock(&dvfs_lock);
}
static int tegra_dvfs_suspend(void)
{
int ret = 0;
mutex_lock(&dvfs_lock);
while (!tegra_dvfs_all_rails_suspended()) {
ret = tegra_dvfs_suspend_one();
if (ret)
break;
}
mutex_unlock(&dvfs_lock);
if (ret)
tegra_dvfs_resume();
return ret;
}
int tegra_dvfs_init_thermal_dvfs_voltages(int *therm_voltages,
int *peak_voltages, int freqs_num, int ranges_num, struct dvfs *d)
{
int *millivolts;
int freq_idx, therm_idx;
for (therm_idx = 0; therm_idx < ranges_num; therm_idx++) {
millivolts = therm_voltages + therm_idx * MAX_DVFS_FREQS;
for (freq_idx = 0; freq_idx < freqs_num; freq_idx++) {
int mv = millivolts[freq_idx];
if ((mv > d->dvfs_rail->max_millivolts) ||
(mv < d->dvfs_rail->min_millivolts) ||
(freq_idx && (mv < millivolts[freq_idx - 1]))) {
WARN(1, "%s: invalid thermal dvfs entry %d(%d, %d)\n",
d->clk_name, mv, freq_idx, therm_idx);
return -EINVAL;
}
if (mv > peak_voltages[freq_idx])
peak_voltages[freq_idx] = mv;
}
}
d->millivolts = therm_voltages;
d->peak_millivolts = peak_voltages;
d->therm_dvfs = ranges_num > 1;
return 0;
}
static int tegra_dvfs_pm_notifier_event(struct notifier_block *nb,
unsigned long event, void *data)
{
if (event == PM_SUSPEND_PREPARE) {
if (tegra_dvfs_suspend())
return NOTIFY_STOP;
pr_info("tegra_dvfs: suspended\n");
} else if (event == PM_POST_SUSPEND) {
tegra_dvfs_resume();
pr_info("tegra_dvfs: resumed\n");
}
return NOTIFY_OK;
};
static struct notifier_block tegra_dvfs_pm_nb = {
.notifier_call = tegra_dvfs_pm_notifier_event,
.priority = -1,
};
static int tegra_dvfs_reboot_notify(struct notifier_block *nb,
unsigned long event, void *data)
{
switch (event) {
case SYS_RESTART:
case SYS_HALT:
case SYS_POWER_OFF:
tegra_dvfs_suspend();
return NOTIFY_OK;
}
return NOTIFY_DONE;
}
static struct notifier_block tegra_dvfs_reboot_nb = {
.notifier_call = tegra_dvfs_reboot_notify,
};
static void __tegra_dvfs_rail_disable(struct dvfs_rail *rail)
{
int ret = -EPERM;
int mv;
if (rail->dfll_mode) {
rail->disabled = true;
return;
}
mv = tegra_dvfs_rail_get_disable_level(rail);
mv = dvfs_rail_apply_limits(rail, mv, false);
if (mv >= rail->millivolts)
ret = dvfs_rail_set_voltage(rail, mv);
if (ret) {
pr_err("tegra_dvfs: failed to disable %s at %d\n",
rail->reg_id, rail->millivolts);
return;
}
rail->disabled = true;
}
static void __tegra_dvfs_rail_enable(struct dvfs_rail *rail)
{
rail->disabled = false;
dvfs_rail_update(rail);
}
void tegra_dvfs_rail_enable(struct dvfs_rail *rail)
{
if (!rail)
return;
mutex_lock(&dvfs_lock);
if (rail->disabled)
__tegra_dvfs_rail_enable(rail);
mutex_unlock(&dvfs_lock);
}
void tegra_dvfs_rail_disable(struct dvfs_rail *rail)
{
if (!rail)
return;
mutex_lock(&dvfs_lock);
if (rail->disabled)
goto out;
__tegra_dvfs_rail_disable(rail);
out:
mutex_unlock(&dvfs_lock);
}
bool tegra_dvfs_is_dfll_range(struct clk *c, unsigned long rate)
{
struct dvfs *d;
d = tegra_clk_to_dvfs(c);
if (d == NULL) {
pr_err("Failed to get dvfs structure\n");
return false;
}
return dvfs_is_dfll_range(d, rate);
}
EXPORT_SYMBOL(tegra_dvfs_is_dfll_range);
int tegra_dvfs_set_dfll_range(struct clk *c, int range)
{
struct dvfs *d;
int ret = -EINVAL;
mutex_lock(&dvfs_lock);
d = tegra_clk_to_dvfs(c);
if (d == NULL) {
pr_err("Failed to get dvfs structure\n");
goto out;
}
if (!d->dfll_millivolts)
goto out;
if ((range < DFLL_RANGE_NONE) || (range > DFLL_RANGE_HIGH_RATES))
goto out;
d->range = range;
ret = 0;
out:
mutex_unlock(&dvfs_lock);
return ret;
}
EXPORT_SYMBOL(tegra_dvfs_set_dfll_range);
int tegra_dvfs_dfll_mode_set(struct clk *c, unsigned long rate)
{
struct dvfs *d;
mutex_lock(&dvfs_lock);
d = tegra_clk_to_dvfs(c);
if (d == NULL) {
pr_err("Failed to get dvfs structure\n");
mutex_unlock(&dvfs_lock);
return -EINVAL;
}
if (!d->dvfs_rail->dfll_mode) {
d->dvfs_rail->dfll_mode = true;
__tegra_dvfs_set_rate(d, rate);
}
mutex_unlock(&dvfs_lock);
return 0;
}
EXPORT_SYMBOL(tegra_dvfs_dfll_mode_set);
int tegra_dvfs_dfll_mode_clear(struct clk *c, unsigned long rate)
{
int ret = 0;
struct dvfs *d;
mutex_lock(&dvfs_lock);
d = tegra_clk_to_dvfs(c);
if (d == NULL) {
pr_err("Failed to get dvfs structure\n");
mutex_unlock(&dvfs_lock);
return -EINVAL;
}
if (d->dvfs_rail->dfll_mode) {
d->dvfs_rail->dfll_mode = false;
d->dvfs_rail->millivolts = regulator_get_voltage(
d->dvfs_rail->reg) / 1000;
if (d->dvfs_rail->disabled) {
d->dvfs_rail->disabled = false;
__tegra_dvfs_rail_disable(d->dvfs_rail);
}
ret = __tegra_dvfs_set_rate(d, rate);
}
mutex_unlock(&dvfs_lock);
return ret;
}
EXPORT_SYMBOL(tegra_dvfs_dfll_mode_clear);
int tegra_dvfs_get_dfll_threshold(struct clk *c, unsigned long *rate)
{
struct dvfs *d;
d = tegra_clk_to_dvfs(c);
if (d == NULL) {
pr_err("Failed to get dvfs structure\n");
return -EINVAL;
}
if (d->dvfs_rail && d->use_dfll_rate_min)
*rate = d->use_dfll_rate_min;
return 0;
}
EXPORT_SYMBOL(tegra_dvfs_get_dfll_threshold);
int tegra_dvfs_core_count_thermal_states(enum tegra_dvfs_core_thermal_type type)
{
if (IS_ERR_OR_NULL(tegra_core_rail) || !tegra_core_rail->is_ready)
return -EINVAL;
if (type == TEGRA_DVFS_CORE_THERMAL_FLOOR)
return tegra_core_rail->therm_floors_size;
else if (type == TEGRA_DVFS_CORE_THERMAL_CAP)
return tegra_core_rail->therm_caps_size;
else
return -EINVAL;
}
EXPORT_SYMBOL(tegra_dvfs_core_count_thermal_states);
int tegra_dvfs_core_get_thermal_index(enum tegra_dvfs_core_thermal_type type)
{
if (IS_ERR_OR_NULL(tegra_core_rail) || !tegra_core_rail->is_ready)
return -EINVAL;
if (type == TEGRA_DVFS_CORE_THERMAL_FLOOR)
return tegra_core_rail->therm_floor_idx;
else if (type == TEGRA_DVFS_CORE_THERMAL_CAP)
return tegra_core_rail->therm_cap_idx;
else
return -EINVAL;
}
EXPORT_SYMBOL(tegra_dvfs_core_get_thermal_index);
int tegra_dvfs_core_update_thermal_index(enum tegra_dvfs_core_thermal_type type,
unsigned long new_idx)
{
struct dvfs_rail *rail = tegra_core_rail;
int ret = 0;
if (IS_ERR_OR_NULL(tegra_core_rail) || !tegra_core_rail->is_ready)
return -EINVAL;
mutex_lock(&dvfs_lock);
if (type == TEGRA_DVFS_CORE_THERMAL_FLOOR) {
if (rail->therm_floor_idx != new_idx) {
rail->therm_floor_idx = new_idx;
dvfs_rail_update(rail);
}
} else if (type == TEGRA_DVFS_CORE_THERMAL_CAP) {
if (rail->therm_cap_idx != new_idx) {
rail->therm_cap_idx = new_idx;
dvfs_rail_update(rail);
}
} else {
ret = -EINVAL;
}
mutex_unlock(&dvfs_lock);
return ret;
}
EXPORT_SYMBOL(tegra_dvfs_core_update_thermal_index);
int tegra_dvfs_core_set_thermal_cap(struct clk *cap_clk,
unsigned long thermal_index)
{
int mv;
unsigned long rate = UINT_MAX;
struct dvfs_rail *rail = tegra_core_rail;
if (IS_ERR_OR_NULL(tegra_core_rail) || !tegra_core_rail->is_ready) {
pr_err("tegra_dvfs: not ready to set thermal cap on %s\n",
cap_clk ? __clk_get_name(cap_clk) : "Unknown");
return -EINVAL;
}
if (rail->therm_caps && thermal_index) {
mv = rail->therm_caps[thermal_index - 1].mv;
rate = tegra_dvfs_predict_hz_at_mv_max_tfloor(cap_clk, mv);
if (IS_ERR_VALUE(rate)) {
pr_err("tegra_dvfs: failed to get %s rate @ %dmV\n",
__clk_get_name(cap_clk), mv);
return -EINVAL;
}
}
pr_debug("tegra_dvfs: Set %lu on %s\n", rate, __clk_get_name(cap_clk));
if (clk_set_rate(cap_clk, rate)) {
pr_err("tegra_dvfs: failed to set cap rate %lu on %s\n",
rate, __clk_get_name(cap_clk));
return -EINVAL;
}
return 0;
}
EXPORT_SYMBOL(tegra_dvfs_core_set_thermal_cap);
struct dvfs_rail *tegra_dvfs_get_rail_by_name(char *name)
{
struct dvfs_rail *rail;
list_for_each_entry(rail, &dvfs_rail_list, node) {
if (!strcmp(rail->reg_id, name))
return rail;
}
return NULL;
}
EXPORT_SYMBOL(tegra_dvfs_get_rail_by_name);
bool tegra_dvfs_is_rail_up(struct dvfs_rail *rail)
{
bool ret = false;
if (!rail)
return false;
if (!rail->in_band_pm)
return true;
mutex_lock(&dvfs_lock);
if (rail->reg)
ret = regulator_is_enabled(rail->reg) > 0;
mutex_unlock(&dvfs_lock);
return ret;
}
EXPORT_SYMBOL(tegra_dvfs_is_rail_up);
int tegra_dvfs_rail_power_up(struct dvfs_rail *rail)
{
int ret = -ENOENT;
if (!rail || !rail->in_band_pm)
return -EINVAL;
mutex_lock(&dvfs_lock);
if (rail->reg) {
ret = regulator_enable(rail->reg);
if (!ret && !timekeeping_suspended) {
rail->stats.off = false;
dvfs_rail_stats_update(rail, rail->millivolts,
ktime_get());
}
}
mutex_unlock(&dvfs_lock);
return ret;
}
EXPORT_SYMBOL(tegra_dvfs_rail_power_up);
int tegra_dvfs_rail_power_down(struct dvfs_rail *rail)
{
int ret = -ENOENT;
if (!rail || !rail->in_band_pm)
return -EINVAL;
mutex_lock(&dvfs_lock);
if (rail->reg) {
ret = regulator_disable(rail->reg);
if (!ret && !timekeeping_suspended) {
dvfs_rail_stats_update(rail, 0, ktime_get());
rail->stats.off = true;
}
}
mutex_unlock(&dvfs_lock);
return ret;
}
EXPORT_SYMBOL(tegra_dvfs_rail_power_down);
unsigned long tegra_dvfs_get_fmax_at_vmin_safe_t(struct clk *c)
{
struct dvfs *d = tegra_clk_to_dvfs(c);
if (!d)
return 0;
return d->fmax_at_vmin_safe_t;
}
EXPORT_SYMBOL(tegra_dvfs_get_fmax_at_vmin_safe_t);
bool tegra_dvfs_is_rail_ready(struct dvfs_rail *rail)
{
return rail->is_ready;
}
EXPORT_SYMBOL(tegra_dvfs_is_rail_ready);
/*
* Validate rail thermal floors/caps, and get its size.
* Valid floors/caps:
* - voltage limits are descending with temperature increasing.
* - the lowest limit is above rail minimum voltage in pll and
* in dfll mode (if applicable).
* - the highest limit is below rail nominal voltage.
*/
static int get_thermal_limits_size(struct dvfs_rail *rail,
enum tegra_dvfs_core_thermal_type type)
{
const struct dvfs_therm_limits *limits;
int i;
if (type == TEGRA_DVFS_CORE_THERMAL_FLOOR)
limits = rail->therm_floors;
else if (type == TEGRA_DVFS_CORE_THERMAL_CAP)
limits = rail->therm_caps;
else
return -EINVAL;
if (!limits[0].mv) {
pr_warn("%s: Missing thermal limits\n", rail->reg_id);
return -EINVAL;
}
for (i = 0; i < MAX_THERMAL_LIMITS - 1; i++) {
if (!limits[i + 1].mv)
break;
if ((limits[i].temperature >= limits[i + 1].temperature) ||
(limits[i].mv < limits[i + 1].mv)) {
pr_warn("%s: Unordered thermal limits\n",
rail->reg_id);
return -EINVAL;
}
}
if (limits[i].mv < rail->min_millivolts) {
pr_warn("%s: Thermal floors below minimum voltage\n",
rail->reg_id);
return -EINVAL;
}
return i + 1;
}
void tegra_dvfs_core_init_therm_limits(struct dvfs_rail *rail)
{
int size;
size = get_thermal_limits_size(rail, TEGRA_DVFS_CORE_THERMAL_FLOOR);
if (size <= 0 || rail->therm_floors[0].mv > rail->nominal_millivolts) {
rail->therm_floors = NULL;
rail->therm_floors_size = 0;
pr_warn("%s: invalid Vmin thermal floors\n", rail->reg_id);
} else {
rail->therm_floors_size = size;
rail->therm_floor_idx = 0;
}
size = get_thermal_limits_size(rail, TEGRA_DVFS_CORE_THERMAL_CAP);
if (size <= 0) {
rail->therm_caps = NULL;
rail->therm_caps_size = 0;
pr_warn("%s: invalid Vmax thermal caps\n", rail->reg_id);
} else {
rail->therm_caps_size = size;
/*
* Core voltage and module rates are not throttled on boot until
* CORE_CDEV_TYPE_CAP is registered. In fact, core boot voltage
* is allowed above high temperature cap during boot.
*/
rail->therm_cap_idx = 0;
}
}
static int tegra_config_dvfs(struct dvfs_rail *rail)
{
int i;
struct dvfs *d;
list_for_each_entry(d, &rail->dvfs, reg_node) {
if (__clk_is_enabled(d->clk) || __clk_is_prepared(d->clk)) {
d->cur_rate = clk_get_rate(d->clk);
d->cur_millivolts = d->max_millivolts;
for (i = 0; i < d->num_freqs; i++)
if (d->cur_rate <= d->freqs[i])
break;
if (i != d->num_freqs)
d->cur_millivolts = d->millivolts[i];
}
mutex_unlock(&dvfs_lock);
clk_notifier_register(d->clk, &tegra_dvfs_nb);
mutex_lock(&dvfs_lock);
}
return 0;
}
static int tegra_dvfs_regulator_init(struct device *dev)
{
struct dvfs_rail *rail;
int err;
mutex_lock(&dvfs_lock);
list_for_each_entry(rail, &dvfs_rail_list, node) {
err = dvfs_rail_connect_to_regulator(dev, rail);
if (err) {
if (!rail->disabled)
__tegra_dvfs_rail_disable(rail);
mutex_unlock(&dvfs_lock);
return err;
}
}
list_for_each_entry(rail, &dvfs_rail_list, node) {
tegra_config_dvfs(rail);
if (rail->disabled) {
/* Overwrite boot voltage with nominal */
rail->disabled = false;
__tegra_dvfs_rail_disable(rail);
} else {
__tegra_dvfs_rail_enable(rail); /* update to clks */
}
}
core_dvfs_started = true;
mutex_unlock(&dvfs_lock);
register_pm_notifier(&tegra_dvfs_pm_nb);
register_reboot_notifier(&tegra_dvfs_reboot_nb);
return 0;
}
static int tegra_vts_get_max_state(struct thermal_cooling_device *cdev,
unsigned long *max_state)
{
struct dvfs_rail *rail = cdev->devdata;
*max_state = rail->vts_number_of_trips;
return 0;
}
static int tegra_vts_get_cur_state(struct thermal_cooling_device *cdev,
unsigned long *cur_state)
{
struct dvfs_rail *rail = cdev->devdata;
*cur_state = rail->therm_scale_idx;
return 0;
}
static int tegra_vts_set_cur_state(struct thermal_cooling_device *cdev,
unsigned long cur_state)
{
struct dvfs_rail *rail = cdev->devdata;
struct dvfs *d, *first;
int ret = 0;
mutex_lock(&dvfs_lock);
if (rail->therm_scale_idx == cur_state)
goto out;
rail->therm_scale_idx = cur_state;
first = list_first_entry(&rail->dvfs, struct dvfs, reg_node);
if (first->therm_dvfs && first->na_dvfs && first->cur_rate) {
/* only GPU thermal DVFS can be noise aware and this
* rail has only a single clock. Therefor we can just
* update the NA DVFS config by doing calling
* clk_set_rate_refresh and leave the normal DVFS notifier
* to handle the voltage update.
*/
mutex_unlock(&dvfs_lock);
return clk_set_rate_refresh(first->clk);
} else if ((!first->therm_dvfs || !first->na_dvfs) &&
first->dvfs_rail) {
list_for_each_entry(d, &rail->dvfs, reg_node) {
if (d->therm_dvfs)
d->cur_millivolts =
predict_mv_at_hz_cur_tfloor(d, d->cur_rate);
}
ret = dvfs_rail_update(first->dvfs_rail);
}
out:
mutex_unlock(&dvfs_lock);
return ret;
}
static struct thermal_cooling_device_ops tegra_vts_cooling_ops = {
.get_max_state = tegra_vts_get_max_state,
.get_cur_state = tegra_vts_get_cur_state,
.set_cur_state = tegra_vts_set_cur_state,
};
#ifdef CONFIG_DEBUG_FS
/* To emulate and show rail relations with 0 mV on dependent rail-to */
static struct dvfs_rail show_to;
static struct dvfs_relationship show_rel;
static int dvfs_tree_show(struct seq_file *s, void *data)
{
struct dvfs *d;
struct dvfs_rail *rail;
struct dvfs_relationship *rel;
int cur_max_millivolts = INT_MIN;
int num_clks;
seq_puts(s, " clock rate mV\n");
seq_puts(s, "-------------------------------------\n");
mutex_lock(&dvfs_lock);
list_for_each_entry(rail, &dvfs_rail_list, node) {
int therm_mv = 0;
seq_printf(s, "%s %d mV%s%s:\n", rail->reg_id, rail->millivolts,
rail->stats.off ? " OFF" : " ON",
rail->dfll_mode ? " dfll mode" :
rail->disabled ? " disabled" : "");
list_for_each_entry(rel, &rail->relationships_from, from_node) {
show_rel = *rel;
show_rel.to = &show_to;
show_to = *rel->to;
show_to.millivolts = show_to.new_millivolts = 0;
seq_printf(s, " %-10s %-7d mV %-4d mV .. %-4d mV\n",
rel->from->reg_id, rel->from->millivolts,
dvfs_solve_relationship(&show_rel),
dvfs_solve_relationship(rel));
}
seq_printf(s, " %-26s %-4d mV\n", "nominal",
rail->nominal_millivolts);
seq_printf(s, " %-26s %-4d mV\n", "minimum",
rail->min_millivolts);
seq_printf(s, " %-26s %-4d mV\n", "offset", rail->dbg_mv_offs);
seq_printf(s, " %-26s %-4d mV\n", "override",
rail->override_millivolts);
if (rail->dfll_mode) {
therm_mv = tegra_dfll_get_thermal_floor_mv();
} else if ((rail->therm_floors) &&
(rail->therm_floor_idx < rail->therm_floors_size)) {
therm_mv = rail->therm_floors[rail->therm_floor_idx].mv;
}
seq_printf(s, " %-26s %-4d mV\n", "therm_floor", therm_mv);
therm_mv = 0;
if (rail->dfll_mode) {
therm_mv = tegra_dfll_get_thermal_cap_mv();
} else if ((rail->therm_caps) &&
(rail->therm_cap_idx > 0)) {
therm_mv = rail->therm_caps[rail->therm_cap_idx - 1].mv;
}
seq_printf(s, " %-26s %-4d mV\n", "therm_cap", therm_mv);
num_clks = 0;
list_for_each_entry(d, &rail->dvfs, reg_node) {
num_clks++;
if (d->cur_millivolts > cur_max_millivolts)
cur_max_millivolts = d->cur_millivolts;
}
while (num_clks > 0) {
int next_max_millivolts = INT_MIN;
list_for_each_entry(d, &rail->dvfs, reg_node) {
if (d->cur_millivolts > next_max_millivolts &&
d->cur_millivolts < cur_max_millivolts)
next_max_millivolts = d->cur_millivolts;
if (d->cur_millivolts != cur_max_millivolts)
continue;
seq_printf(s, " %-15s %-10lu %-4d mV\n",
d->clk_name, d->cur_rate,
d->cur_millivolts);
num_clks--;
WARN_ON(num_clks < 0);
}
cur_max_millivolts = next_max_millivolts;
}
}
mutex_unlock(&dvfs_lock);
return 0;
}
static int dvfs_tree_open(struct inode *inode, struct file *file)
{
return single_open(file, dvfs_tree_show, inode->i_private);
}
static const struct file_operations dvfs_tree_fops = {
.open = dvfs_tree_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
static int dvfs_table_show(struct seq_file *s, void *data)
{
int i;
struct dvfs *d;
struct dvfs_rail *rail;
const int *v_pll, *last_v_pll = NULL;
const int *v_dfll, *last_v_dfll = NULL;
seq_puts(s, "DVFS tables: units mV/MHz\n");
mutex_lock(&dvfs_lock);
list_for_each_entry(rail, &dvfs_rail_list, node) {
seq_printf(s, "%-8s table version: %s\n",
rail->reg_id, rail->nvver ? : "N/A");
}
list_for_each_entry(rail, &dvfs_rail_list, node) {
list_for_each_entry(d, &rail->dvfs, reg_node) {
bool mv_done = false;
v_pll = dvfs_get_millivolts_pll(d);
v_dfll = d->dfll_millivolts;
if (v_pll && (last_v_pll != v_pll)) {
if (!mv_done) {
seq_puts(s, "\n");
mv_done = true;
}
last_v_pll = v_pll;
seq_printf(s, "%-16s", rail->reg_id);
for (i = 0; i < d->num_freqs; i++)
seq_printf(s, "%7d", v_pll[i]);
seq_puts(s, "\n");
}
if (v_dfll && (last_v_dfll != v_dfll)) {
if (!mv_done) {
seq_puts(s, "\n");
mv_done = true;
}
last_v_dfll = v_dfll;
seq_printf(s, "%-8s (dfll) ", rail->reg_id);
for (i = 0; i < d->num_freqs; i++)
seq_printf(s, "%7d", v_dfll[i]);
seq_puts(s, "\n");
}
seq_printf(s, "%-16s", d->clk_name);
for (i = 0; i < d->num_freqs; i++) {
unsigned int f = d->freqs[i]/100000;
seq_printf(s, " %4u.%u", f/10, f%10);
}
if (d->alt_freqs) {
seq_puts(s, "\n");
seq_printf(s, "%-10s (alt)", d->clk_name);
for (i = 0; i < d->num_freqs; i++) {
unsigned int f = d->alt_freqs[i]/100000;
seq_printf(s, " %4u.%u", f/10, f%10);
}
}
seq_puts(s, "\n");
}
}
mutex_unlock(&dvfs_lock);
return 0;
}
static int dvfs_table_open(struct inode *inode, struct file *file)
{
return single_open(file, dvfs_table_show, inode->i_private);
}
static const struct file_operations dvfs_table_fops = {
.open = dvfs_table_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
static int rail_stats_save_to_buf(char *buf, int len)
{
int i;
struct dvfs_rail *rail;
char *str = buf;
char *end = buf + len;
str += scnprintf(str, end - str, "%-12s %-10s\n", "millivolts", "time");
mutex_lock(&dvfs_lock);
list_for_each_entry(rail, &dvfs_rail_list, node) {
str += scnprintf(str, end - str, "%s (bin: %d.%dmV)\n",
rail->reg_id,
rail->stats.bin_uv / 1000,
(rail->stats.bin_uv / 10) % 100);
dvfs_rail_stats_update(rail, -1, ktime_get());
str += scnprintf(str, end - str, "%-12d %-10llu\n", 0,
cputime64_to_clock_t(msecs_to_jiffies(
ktime_to_ms(rail->stats.time_at_mv[0]))));
for (i = 1; i <= DVFS_RAIL_STATS_TOP_BIN; i++) {
ktime_t ktime_zero = ktime_set(0, 0);
if (ktime_equal(rail->stats.time_at_mv[i], ktime_zero))
continue;
str += scnprintf(str, end - str, "%-12d %-10llu\n",
rail->min_millivolts +
(i - 1) * rail->stats.bin_uv / 1000,
cputime64_to_clock_t(msecs_to_jiffies(
ktime_to_ms(rail->stats.time_at_mv[i])))
);
}
}
mutex_unlock(&dvfs_lock);
return str - buf;
}
static int rail_stats_show(struct seq_file *s, void *data)
{
char *buf = kzalloc(PAGE_SIZE, GFP_KERNEL);
int size = 0;
if (!buf)
return -ENOMEM;
size = rail_stats_save_to_buf(buf, PAGE_SIZE);
seq_write(s, buf, size);
kfree(buf);
return 0;
}
static int rail_stats_open(struct inode *inode, struct file *file)
{
return single_open(file, rail_stats_show, inode->i_private);
}
static const struct file_operations rail_stats_fops = {
.open = rail_stats_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
static int gpu_dvfs_t_show(struct seq_file *s, void *data)
{
int i, j;
int num_ranges = 1;
int *trips = NULL;
struct dvfs *d;
struct dvfs_rail *rail = tegra_gpu_rail;
int max_mv[MAX_DVFS_FREQS] = {};
if (!tegra_gpu_rail) {
seq_printf(s, "Only supported for T124 or higher\n");
return -ENOSYS;
}
mutex_lock(&dvfs_lock);
d = list_first_entry(&rail->dvfs, struct dvfs, reg_node);
if (rail->vts_cdev && d->therm_dvfs) {
num_ranges = rail->vts_number_of_trips + 1;
trips = rail->vts_trips_table;
}
seq_printf(s, "%-11s", "T(C)\\F(kHz)");
for (i = 0; i < d->num_freqs; i++) {
unsigned int f = d->freqs[i]/1000;
seq_printf(s, " %7u", f);
}
seq_printf(s, "\n");
for (j = 0; j < num_ranges; j++) {
seq_printf(s, "%s", j == rail->therm_scale_idx ? ">" : " ");
if (!trips || (num_ranges == 1))
seq_printf(s, "%4s..%-4s", "", "");
else if (j == 0)
seq_printf(s, "%4s..%-4d", "", trips[j]);
else if (j == num_ranges - 1)
seq_printf(s, "%4d..%-4s", trips[j], "");
else
seq_printf(s, "%4d..%-4d", trips[j-1], trips[j]);
for (i = 0; i < d->num_freqs; i++) {
int mv = *(d->millivolts + j * MAX_DVFS_FREQS + i);
seq_printf(s, " %7d", mv);
max_mv[i] = max(max_mv[i], mv);
}
seq_printf(s, " mV\n");
}
seq_printf(s, "%3s%-8s\n", "", "------");
seq_printf(s, "%3s%-8s", "", "max(T)");
for (i = 0; i < d->num_freqs; i++)
seq_printf(s, " %7d", max_mv[i]);
seq_printf(s, " mV\n");
mutex_unlock(&dvfs_lock);
return 0;
}
static int gpu_dvfs_t_open(struct inode *inode, struct file *file)
{
return single_open(file, gpu_dvfs_t_show, NULL);
}
static const struct file_operations gpu_dvfs_t_fops = {
.open = gpu_dvfs_t_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
static int dvfs_offset_get(void *data, u64 *val)
{
struct dvfs_rail *rail = data;
*val = rail->dbg_mv_offs;
return 0;
}
static int dvfs_offset_set(void *data, u64 val)
{
struct dvfs_rail *rail = data;
mutex_lock(&dvfs_lock);
rail->dbg_mv_offs = val;
dvfs_rail_update(rail);
mutex_unlock(&dvfs_lock);
return 0;
}
DEFINE_SIMPLE_ATTRIBUTE(dvfs_offset_fops, dvfs_offset_get, dvfs_offset_set, "%lld\n");
static int dvfs_override_get(void *data, u64 *val)
{
struct dvfs_rail *rail = data;
*val = rail->override_millivolts;
return 0;
}
static int dvfs_override_set(void *data, u64 val)
{
struct dvfs_rail *rail = data;
mutex_lock(&dvfs_lock);
rail->override_millivolts = val;
dvfs_rail_update(rail);
mutex_unlock(&dvfs_lock);
return 0;
}
DEFINE_SIMPLE_ATTRIBUTE(dvfs_override_fops,
dvfs_override_get, dvfs_override_set, "%lld\n");
static int dvfs_debugfs_init(void)
{
struct dentry *d_root, *d;
d_root = debugfs_create_dir("tegra_dvfs", NULL);
if (!d_root)
return -ENOMEM;
d = debugfs_create_file("dvfs", S_IRUGO, d_root, NULL,
&dvfs_tree_fops);
if (!d)
return -ENOMEM;
d = debugfs_create_file("dvfs_table", S_IRUGO, d_root, NULL,
&dvfs_table_fops);
if (!d)
return -ENOMEM;
d = debugfs_create_file("rails", S_IRUGO, d_root, NULL,
&rail_stats_fops);
if (!d)
return -ENOMEM;
d = debugfs_create_file("gpu_dvfs_t", S_IRUGO, d_root, NULL,
&gpu_dvfs_t_fops);
if (!d)
return -ENOMEM;
d = debugfs_create_file("vdd_core_offs", S_IRUGO | S_IWUSR, d_root,
tegra_core_rail, &dvfs_offset_fops);
if (!d)
return -ENOMEM;
d = debugfs_create_file("vdd_gpu_offs", S_IRUGO | S_IWUSR, d_root,
tegra_gpu_rail, &dvfs_offset_fops);
if (!d)
return -ENOMEM;
d = debugfs_create_file("vdd_core_override", S_IRUGO | S_IWUSR, d_root,
tegra_core_rail, &dvfs_override_fops);
if (!d)
return -ENOMEM;
d = debugfs_create_file("vdd_gpu_override", S_IRUGO | S_IWUSR, d_root,
tegra_gpu_rail, &dvfs_override_fops);
if (!d)
return -ENOMEM;
return 0;
}
#endif
typedef int (*dvfs_init_cb_t)(struct device *);
static const struct of_device_id tegra_dvfs_of_match[] = {
{ .compatible = "nvidia,tegra124-dvfs", .data = tegra124_init_dvfs },
{ .compatible = "nvidia,tegra210-dvfs", .data = tegra210_init_dvfs },
{ .compatible = "nvidia,tegra210b01-dvfs",
.data = tegra210b01_init_dvfs },
{},
};
static int tegra_dvfs_probe(struct platform_device *pdev)
{
const struct of_device_id *match;
dvfs_init_cb_t dvfs_init_cb;
struct dvfs_rail *rail;
int ret = -EINVAL;
match = of_match_node(tegra_dvfs_of_match, pdev->dev.of_node);
if (!match)
goto out;
dvfs_init_cb = (dvfs_init_cb_t)match->data;
ret = dvfs_init_cb(&pdev->dev);
if (ret)
goto out;
ret = tegra_dvfs_regulator_init(&pdev->dev);
if (ret)
goto out;
list_for_each_entry(rail, &dvfs_rail_list, node) {
rail->is_ready = true;
if (rail->vts_of_node) {
char *name;
name = kasprintf(GFP_KERNEL, "%s-vts", rail->reg_id);
rail->vts_cdev = thermal_of_cooling_device_register(
rail->vts_of_node, name, rail,
&tegra_vts_cooling_ops);
pr_info("tegra_dvfs: %s: %sregistered\n", name,
IS_ERR_OR_NULL(rail->vts_cdev) ? "not " : "");
kfree(name);
}
}
#ifdef CONFIG_DEBUG_FS
dvfs_debugfs_init();
#endif
return 0;
out:
return ret;
}
static int tegra_dvfs_remove(struct platform_device *pdev)
{
struct dvfs *d;
core_dvfs_started = false;
unregister_pm_notifier(&tegra_dvfs_reboot_nb);
unregister_pm_notifier(&tegra_dvfs_pm_nb);
list_for_each_entry(d, &tegra_core_rail->dvfs, reg_node) {
clk_notifier_unregister(d->clk, &tegra_dvfs_nb);
}
return 0;
}
static struct platform_driver tegra_dvfs_platdrv = {
.driver = {
.name = "tegra-dvfs",
.owner = THIS_MODULE,
.of_match_table = tegra_dvfs_of_match,
},
.probe = tegra_dvfs_probe,
.remove = tegra_dvfs_remove,
};
static int __init tegra_dvfs_platdrv_init(void)
{
return platform_driver_register(&tegra_dvfs_platdrv);
}
subsys_initcall_sync(tegra_dvfs_platdrv_init);
static void __exit tegra_dvfs_platdrv_exit(void)
{
platform_driver_unregister(&tegra_dvfs_platdrv);
}
module_exit(tegra_dvfs_platdrv_exit);