Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 6a0712f6 authored by Viresh Kumar's avatar Viresh Kumar Committed by Rafael J. Wysocki
Browse files

PM / OPP: Add dev_pm_opp_set_rate()



This adds a routine, dev_pm_opp_set_rate(), responsible for configuring
power-supply and clock source for an OPP.

The OPP is found by matching against the target_freq passed to the
routine. This shall replace similar code present in most of the OPP
users and help simplify them a lot.

Signed-off-by: default avatarViresh Kumar <viresh.kumar@linaro.org>
Reviewed-by: default avatarStephen Boyd <sboyd@codeaurora.org>
Signed-off-by: default avatarRafael J. Wysocki <rafael.j.wysocki@intel.com>
parent d54974c2
Loading
Loading
Loading
Loading
+176 −0
Original line number Original line Diff line number Diff line
@@ -529,6 +529,182 @@ struct dev_pm_opp *dev_pm_opp_find_freq_floor(struct device *dev,
}
}
EXPORT_SYMBOL_GPL(dev_pm_opp_find_freq_floor);
EXPORT_SYMBOL_GPL(dev_pm_opp_find_freq_floor);


/*
 * The caller needs to ensure that device_opp (and hence the clk) isn't freed,
 * while clk returned here is used.
 */
static struct clk *_get_opp_clk(struct device *dev)
{
	struct device_opp *dev_opp;
	struct clk *clk;

	rcu_read_lock();

	dev_opp = _find_device_opp(dev);
	if (IS_ERR(dev_opp)) {
		dev_err(dev, "%s: device opp doesn't exist\n", __func__);
		clk = ERR_CAST(dev_opp);
		goto unlock;
	}

	clk = dev_opp->clk;
	if (IS_ERR(clk))
		dev_err(dev, "%s: No clock available for the device\n",
			__func__);

unlock:
	rcu_read_unlock();
	return clk;
}

static int _set_opp_voltage(struct device *dev, struct regulator *reg,
			    unsigned long u_volt, unsigned long u_volt_min,
			    unsigned long u_volt_max)
{
	int ret;

	/* Regulator not available for device */
	if (IS_ERR(reg)) {
		dev_dbg(dev, "%s: regulator not available: %ld\n", __func__,
			PTR_ERR(reg));
		return 0;
	}

	dev_dbg(dev, "%s: voltages (mV): %lu %lu %lu\n", __func__, u_volt_min,
		u_volt, u_volt_max);

	ret = regulator_set_voltage_triplet(reg, u_volt_min, u_volt,
					    u_volt_max);
	if (ret)
		dev_err(dev, "%s: failed to set voltage (%lu %lu %lu mV): %d\n",
			__func__, u_volt_min, u_volt, u_volt_max, ret);

	return ret;
}

/**
 * dev_pm_opp_set_rate() - Configure new OPP based on frequency
 * @dev:	 device for which we do this operation
 * @target_freq: frequency to achieve
 *
 * This configures the power-supplies and clock source to the levels specified
 * by the OPP corresponding to the target_freq.
 *
 * Locking: This function takes rcu_read_lock().
 */
int dev_pm_opp_set_rate(struct device *dev, unsigned long target_freq)
{
	struct device_opp *dev_opp;
	struct dev_pm_opp *old_opp, *opp;
	struct regulator *reg;
	struct clk *clk;
	unsigned long freq, old_freq;
	unsigned long u_volt, u_volt_min, u_volt_max;
	unsigned long ou_volt, ou_volt_min, ou_volt_max;
	int ret;

	if (unlikely(!target_freq)) {
		dev_err(dev, "%s: Invalid target frequency %lu\n", __func__,
			target_freq);
		return -EINVAL;
	}

	clk = _get_opp_clk(dev);
	if (IS_ERR(clk))
		return PTR_ERR(clk);

	freq = clk_round_rate(clk, target_freq);
	if ((long)freq <= 0)
		freq = target_freq;

	old_freq = clk_get_rate(clk);

	/* Return early if nothing to do */
	if (old_freq == freq) {
		dev_dbg(dev, "%s: old/new frequencies (%lu Hz) are same, nothing to do\n",
			__func__, freq);
		return 0;
	}

	rcu_read_lock();

	dev_opp = _find_device_opp(dev);
	if (IS_ERR(dev_opp)) {
		dev_err(dev, "%s: device opp doesn't exist\n", __func__);
		rcu_read_unlock();
		return PTR_ERR(dev_opp);
	}

	old_opp = dev_pm_opp_find_freq_ceil(dev, &old_freq);
	if (!IS_ERR(old_opp)) {
		ou_volt = old_opp->u_volt;
		ou_volt_min = old_opp->u_volt_min;
		ou_volt_max = old_opp->u_volt_max;
	} else {
		dev_err(dev, "%s: failed to find current OPP for freq %lu (%ld)\n",
			__func__, old_freq, PTR_ERR(old_opp));
	}

	opp = dev_pm_opp_find_freq_ceil(dev, &freq);
	if (IS_ERR(opp)) {
		ret = PTR_ERR(opp);
		dev_err(dev, "%s: failed to find OPP for freq %lu (%d)\n",
			__func__, freq, ret);
		rcu_read_unlock();
		return ret;
	}

	u_volt = opp->u_volt;
	u_volt_min = opp->u_volt_min;
	u_volt_max = opp->u_volt_max;

	reg = dev_opp->regulator;

	rcu_read_unlock();

	/* Scaling up? Scale voltage before frequency */
	if (freq > old_freq) {
		ret = _set_opp_voltage(dev, reg, u_volt, u_volt_min,
				       u_volt_max);
		if (ret)
			goto restore_voltage;
	}

	/* Change frequency */

	dev_dbg(dev, "%s: switching OPP: %lu Hz --> %lu Hz\n",
		__func__, old_freq, freq);

	ret = clk_set_rate(clk, freq);
	if (ret) {
		dev_err(dev, "%s: failed to set clock rate: %d\n", __func__,
			ret);
		goto restore_voltage;
	}

	/* Scaling down? Scale voltage after frequency */
	if (freq < old_freq) {
		ret = _set_opp_voltage(dev, reg, u_volt, u_volt_min,
				       u_volt_max);
		if (ret)
			goto restore_freq;
	}

	return 0;

restore_freq:
	if (clk_set_rate(clk, old_freq))
		dev_err(dev, "%s: failed to restore old-freq (%lu Hz)\n",
			__func__, old_freq);
restore_voltage:
	/* This shouldn't harm even if the voltages weren't updated earlier */
	if (!IS_ERR(old_opp))
		_set_opp_voltage(dev, reg, ou_volt, ou_volt_min, ou_volt_max);

	return ret;
}
EXPORT_SYMBOL_GPL(dev_pm_opp_set_rate);

/* List-dev Helpers */
/* List-dev Helpers */
static void _kfree_list_dev_rcu(struct rcu_head *head)
static void _kfree_list_dev_rcu(struct rcu_head *head)
{
{
+6 −0
Original line number Original line Diff line number Diff line
@@ -64,6 +64,7 @@ int dev_pm_opp_set_prop_name(struct device *dev, const char *name);
void dev_pm_opp_put_prop_name(struct device *dev);
void dev_pm_opp_put_prop_name(struct device *dev);
int dev_pm_opp_set_regulator(struct device *dev, const char *name);
int dev_pm_opp_set_regulator(struct device *dev, const char *name);
void dev_pm_opp_put_regulator(struct device *dev);
void dev_pm_opp_put_regulator(struct device *dev);
int dev_pm_opp_set_rate(struct device *dev, unsigned long target_freq);
#else
#else
static inline unsigned long dev_pm_opp_get_voltage(struct dev_pm_opp *opp)
static inline unsigned long dev_pm_opp_get_voltage(struct dev_pm_opp *opp)
{
{
@@ -172,6 +173,11 @@ static inline int dev_pm_opp_set_regulator(struct device *dev, const char *name)


static inline void dev_pm_opp_put_regulator(struct device *dev) {}
static inline void dev_pm_opp_put_regulator(struct device *dev) {}


static inline int dev_pm_opp_set_rate(struct device *dev, unsigned long target_freq)
{
	return -EINVAL;
}

#endif		/* CONFIG_PM_OPP */
#endif		/* CONFIG_PM_OPP */


#if defined(CONFIG_PM_OPP) && defined(CONFIG_OF)
#if defined(CONFIG_PM_OPP) && defined(CONFIG_OF)