diff mbox

[v2,4/6] clk: tegra: dfll: support PWM regulator control

Message ID 1516797938-32044-6-git-send-email-pdeschrijver@nvidia.com (mailing list archive)
State Superseded, archived
Headers show

Commit Message

Peter De Schrijver Jan. 24, 2018, 12:45 p.m. UTC
The DFLL can directly generate a PWM signal to control the regulator IC
rather then sending i2c messages. This patch adds support for this mode.
In this mode the LUT is not used and also there is no regulator object
involved because there is no way to control the regulator voltage without
also changing the DFLL output frequency. Also the register debugfs file
is slightly reworked to only show the i2c registers when i2c mode is in
use.

Signed-off-by: Peter De Schrijver <pdeschrijver@nvidia.com>
---
 drivers/clk/tegra/clk-dfll.c | 481 ++++++++++++++++++++++++++++++++++++-------
 drivers/clk/tegra/clk-dfll.h |   7 +
 2 files changed, 410 insertions(+), 78 deletions(-)

Comments

Jon Hunter Jan. 31, 2018, 11:26 a.m. UTC | #1
On 24/01/18 12:45, Peter De Schrijver wrote:
> The DFLL can directly generate a PWM signal to control the regulator IC
> rather then sending i2c messages. This patch adds support for this mode.
> In this mode the LUT is not used and also there is no regulator object
> involved because there is no way to control the regulator voltage without
> also changing the DFLL output frequency. Also the register debugfs file
> is slightly reworked to only show the i2c registers when i2c mode is in
> use.
> 
> Signed-off-by: Peter De Schrijver <pdeschrijver@nvidia.com>
> ---
>  drivers/clk/tegra/clk-dfll.c | 481 ++++++++++++++++++++++++++++++++++++-------
>  drivers/clk/tegra/clk-dfll.h |   7 +
>  2 files changed, 410 insertions(+), 78 deletions(-)
> 
> diff --git a/drivers/clk/tegra/clk-dfll.c b/drivers/clk/tegra/clk-dfll.c
> index 0a7deee..204717c 100644
> --- a/drivers/clk/tegra/clk-dfll.c
> +++ b/drivers/clk/tegra/clk-dfll.c
> @@ -243,6 +243,12 @@ enum dfll_tune_range {
>  	DFLL_TUNE_LOW = 1,
>  };
>  
> +
> +enum tegra_dfll_pmu_if {
> +	TEGRA_DFLL_PMU_I2C = 0,
> +	TEGRA_DFLL_PMU_PWM = 1,
> +};
> +
>  /**
>   * struct dfll_rate_req - target DFLL rate request data
>   * @rate: target frequency, after the postscaling
> @@ -292,18 +298,27 @@ struct tegra_dfll {
>  	u32				force_mode;
>  	u32				cf;
>  	u32				ci;
> -	u32				cg;
> +	s32				cg;

Why has the type been changed?

>  	bool				cg_scale;
> +	u32				reg_init_uV;
>  
>  	/* I2C interface parameters */
>  	u32				i2c_fs_rate;
>  	u32				i2c_reg;
>  	u32				i2c_slave_addr;
>  
> -	/* i2c_lut array entries are regulator framework selectors */
> -	unsigned			i2c_lut[MAX_DFLL_VOLTAGES];
> -	int				i2c_lut_size;
> -	u8				lut_min, lut_max, lut_safe;
> +	/* lut array entries are regulator framework selectors or PWM values*/
> +	unsigned int			lut[MAX_DFLL_VOLTAGES];
> +	unsigned int			lut_uv[MAX_DFLL_VOLTAGES];
> +	int				lut_size;
> +	u8				lut_bottom, lut_min, lut_max, lut_safe;

You say the LUT is not used for PWM, but you are changing the name here
and it appears to be used to some degree in PWM and so I am a bit
confused here.

> +
> +	/* PWM interface */
> +	enum tegra_dfll_pmu_if		pmu_if;
> +	unsigned long			pwm_rate;
> +	struct pinctrl			*pwm_pin;
> +	struct pinctrl_state		*pwm_enable_state;
> +	struct pinctrl_state		*pwm_disable_state;
>  };
>  
>  #define clk_hw_to_dfll(_hw) container_of(_hw, struct tegra_dfll, dfll_clk_hw)
> @@ -490,6 +505,36 @@ static void dfll_set_mode(struct tegra_dfll *td,
>  }
>  
>  /*
> + * DVCO rate control
> + */
> +
> +static unsigned long get_dvco_rate_below(struct tegra_dfll *td, u8 out_min)
> +{
> +	struct dev_pm_opp *opp;
> +	unsigned long rate, prev_rate;
> +	int uv, min_uv;
> +
> +	min_uv = td->lut_uv[out_min];
> +	for (rate = 0, prev_rate = 0; ; rate++) {
> +		rcu_read_lock();
> +		opp = dev_pm_opp_find_freq_ceil(td->soc->dev, &rate);
> +		if (IS_ERR(opp)) {
> +			rcu_read_unlock();
> +			break;

'prev_rate' could be uninitialised here and so you may return a bogus value.

> +		}
> +		uv = dev_pm_opp_get_voltage(opp);
> +		rcu_read_unlock();
> +
> +		if (uv && uv > min_uv)
> +			return prev_rate;
> +
> +		prev_rate = rate;
> +	}
> +
> +	return prev_rate;
> +}
> +
> +/*
>   * DFLL-to-I2C controller interface
>   */
>  
> @@ -518,6 +563,118 @@ static int dfll_i2c_set_output_enabled(struct tegra_dfll *td, bool enable)
>  	return 0;
>  }
>  
> +
> +/*
> + * DFLL-to-PWM controller interface
> + */
> +
> +/**
> + * dfll_pwm_set_output_enabled - enable/disable PWM voltage requests
> + * @td: DFLL instance
> + * @enable: whether to enable or disable the PWM voltage requests
> + *
> + * Set the master enable control for PWM control value updates. If disabled,
> + * then the PWM signal is not driven. Also configure the PWM output pad
> + * to the appropriate state.
> + */
> +static int dfll_pwm_set_output_enabled(struct tegra_dfll *td, bool enable)
> +{
> +	int ret;
> +	u32 val, div;
> +
> +	if (enable) {
> +		ret = pinctrl_select_state(td->pwm_pin, td->pwm_enable_state);
> +		if (ret < 0) {
> +			dev_err(td->dev, "setting enable state failed\n");
> +			return -EINVAL;

Why not return ret?

> +		}
> +		val = dfll_readl(td, DFLL_OUTPUT_CFG);
> +		val &= ~DFLL_OUTPUT_CFG_PWM_DIV_MASK;
> +		div = DIV_ROUND_UP(td->ref_rate, td->pwm_rate);
> +		val |= (div << DFLL_OUTPUT_CFG_PWM_DIV_SHIFT)
> +				& DFLL_OUTPUT_CFG_PWM_DIV_MASK;
> +		dfll_writel(td, val, DFLL_OUTPUT_CFG);
> +		dfll_wmb(td);
> +
> +		val |= DFLL_OUTPUT_CFG_PWM_ENABLE;
> +		dfll_writel(td, val, DFLL_OUTPUT_CFG);
> +		dfll_wmb(td);
> +	} else {
> +		ret = pinctrl_select_state(td->pwm_pin, td->pwm_disable_state);
> +		if (ret < 0)
> +			dev_warn(td->dev, "setting disable state failed\n");
> +
> +		val = dfll_readl(td, DFLL_OUTPUT_CFG);
> +		val &= ~DFLL_OUTPUT_CFG_PWM_ENABLE;
> +		dfll_writel(td, val, DFLL_OUTPUT_CFG);
> +		dfll_wmb(td);
> +	}
> +
> +	return 0;
> +}
> +/**
> + * dfll_set_force_output_value - set fixed value for force output
> + * @td: DFLL instance
> + * @out_val: value to force output
> + *
> + * Set the fixec value for force output, DFLL will output this value when

s/fixec/fixed/

> + * force output is enabled.
> + */
> +static u32 dfll_set_force_output_value(struct tegra_dfll *td, u8 out_val)
> +{
> +	u32 val = dfll_readl(td, DFLL_OUTPUT_FORCE);
> +
> +	val = (val & DFLL_OUTPUT_FORCE_ENABLE) | (out_val & OUT_MASK);

Don't you need to clear the OUT_MASK bits first?

> +	dfll_writel(td, val, DFLL_OUTPUT_FORCE);
> +	dfll_wmb(td);
> +
> +	return dfll_readl(td, DFLL_OUTPUT_FORCE);
> +}
> +
> +/**
> + * dfll_set_force_output_enabled - enable/disable force output
> + * @td: DFLL instance
> + * @enable: whether to enable or disable the force output
> + *
> + * Set the enable control for fouce output with fixed value.
> + */
> +static void dfll_set_force_output_enabled(struct tegra_dfll *td, bool enable)
> +{
> +	u32 val = dfll_readl(td, DFLL_OUTPUT_FORCE);
> +
> +	if (enable)
> +		val |= DFLL_OUTPUT_FORCE_ENABLE;
> +	else
> +		val &= ~DFLL_OUTPUT_FORCE_ENABLE;
> +
> +	dfll_writel(td, val, DFLL_OUTPUT_FORCE);
> +	dfll_wmb(td);
> +}
> +
> +/**
> + * dfll_i2c_set_output_enabled - enable/disable I2C PMIC voltage requests
> + * @td: DFLL instance
> + * @enable: whether to enable or disable the I2C voltage requests
> + *
> + * Set the master enable control for I2C control value updates. If disabled,
> + * then I2C control messages are inhibited, regardless of the DFLL mode.
> + */
> +static int dfll_force_output(struct tegra_dfll *td, unsigned int out_sel)
> +{
> +	u32 val;
> +
> +	if (out_sel > OUT_MASK)
> +		return -EINVAL;
> +
> +	val = dfll_set_force_output_value(td, out_sel);
> +	if ((td->mode < DFLL_CLOSED_LOOP) &&
> +	    !(val & DFLL_OUTPUT_FORCE_ENABLE)) {
> +		dfll_set_force_output_enabled(td, true);
> +	}
> +
> +	return 0;
> +}
> +
>  /**
>   * dfll_load_lut - load the voltage lookup table
>   * @td: struct tegra_dfll *
> @@ -531,15 +688,15 @@ static void dfll_load_i2c_lut(struct tegra_dfll *td)
>  	u32 val;
>  
>  	for (i = 0; i < MAX_DFLL_VOLTAGES; i++) {
> -		if (i < td->lut_min)
> -			lut_index = td->lut_min;
> -		else if (i > td->lut_max)
> -			lut_index = td->lut_max;
> +		if (i < td->lut_bottom)
> +			lut_index = td->lut_bottom;
> +		else if (i > td->lut_size - 1)
> +			lut_index = td->lut_size - 1;
>  		else
>  			lut_index = i;
>  
>  		val = regulator_list_hardware_vsel(td->vdd_reg,
> -						     td->i2c_lut[lut_index]);
> +						     td->lut[lut_index]);
>  		__raw_writel(val, td->lut_base + i * 4);
>  	}
>  
> @@ -595,23 +752,53 @@ static void dfll_init_out_if(struct tegra_dfll *td)
>  	u32 val;
>  
>  	td->lut_min = 0;
> -	td->lut_max = td->i2c_lut_size - 1;
> -	td->lut_safe = td->lut_min + 1;
> -
> -	dfll_i2c_writel(td, 0, DFLL_OUTPUT_CFG);
> -	val = (td->lut_safe << DFLL_OUTPUT_CFG_SAFE_SHIFT) |
> -		(td->lut_max << DFLL_OUTPUT_CFG_MAX_SHIFT) |
> -		(td->lut_min << DFLL_OUTPUT_CFG_MIN_SHIFT);
> -	dfll_i2c_writel(td, val, DFLL_OUTPUT_CFG);
> -	dfll_i2c_wmb(td);
> -
> -	dfll_writel(td, 0, DFLL_OUTPUT_FORCE);
> -	dfll_i2c_writel(td, 0, DFLL_INTR_EN);
> -	dfll_i2c_writel(td, DFLL_INTR_MAX_MASK | DFLL_INTR_MIN_MASK,
> -			DFLL_INTR_STS);
> -
> -	dfll_load_i2c_lut(td);
> -	dfll_init_i2c_if(td);
> +	td->lut_max = td->lut_size - 1;
> +	td->lut_safe = td->lut_min + (td->lut_min < td->lut_max ? 1 : 0);
> +
> +	if (td->pmu_if == TEGRA_DFLL_PMU_PWM) {
> +		int vinit = td->reg_init_uV;
> +		int vstep = td->soc->alignment.step_uv;
> +		int vmin = td->lut_uv[0];
> +
> +		/* clear DFLL_OUTPUT_CFG before setting new value */
> +		dfll_writel(td, 0, DFLL_OUTPUT_CFG);
> +		dfll_wmb(td);
> +
> +		val = dfll_readl(td, DFLL_OUTPUT_CFG);
> +		val |= (td->lut_safe << DFLL_OUTPUT_CFG_SAFE_SHIFT) |
> +		       (td->lut_max << DFLL_OUTPUT_CFG_MAX_SHIFT) |
> +		       (td->lut_min << DFLL_OUTPUT_CFG_MIN_SHIFT);
> +		dfll_writel(td, val, DFLL_OUTPUT_CFG);
> +		dfll_wmb(td);
> +
> +		dfll_writel(td, 0, DFLL_OUTPUT_FORCE);
> +		dfll_i2c_writel(td, 0, DFLL_INTR_EN);
> +		dfll_i2c_writel(td, DFLL_INTR_MAX_MASK | DFLL_INTR_MIN_MASK,
> +				DFLL_INTR_STS);
> +
> +		/* set initial voltage */
> +		if ((vinit >= vmin) && vstep) {
> +			unsigned int vsel;
> +
> +			vsel = DIV_ROUND_UP((vinit - vmin), vstep);
> +			dfll_force_output(td, vsel);
> +		}
> +	} else {
> +		dfll_i2c_writel(td, 0, DFLL_OUTPUT_CFG);
> +		val = (td->lut_safe << DFLL_OUTPUT_CFG_SAFE_SHIFT) |
> +			(td->lut_max << DFLL_OUTPUT_CFG_MAX_SHIFT) |
> +			(td->lut_min << DFLL_OUTPUT_CFG_MIN_SHIFT);
> +		dfll_i2c_writel(td, val, DFLL_OUTPUT_CFG);
> +		dfll_i2c_wmb(td);
> +
> +		dfll_writel(td, 0, DFLL_OUTPUT_FORCE);
> +		dfll_i2c_writel(td, 0, DFLL_INTR_EN);
> +		dfll_i2c_writel(td, DFLL_INTR_MAX_MASK | DFLL_INTR_MIN_MASK,
> +				DFLL_INTR_STS);
> +
> +		dfll_load_i2c_lut(td);
> +		dfll_init_i2c_if(td);
> +	}
>  }
>  
>  /*
> @@ -637,11 +824,12 @@ static int find_lut_index_for_rate(struct tegra_dfll *td, unsigned long rate)
>  	if (IS_ERR(opp))
>  		return PTR_ERR(opp);
>  
> -	uv = dev_pm_opp_get_voltage(opp);
> +	uv = dev_pm_opp_get_voltage(opp) / td->soc->alignment.step_uv;
> +
>  	dev_pm_opp_put(opp);
>  
> -	for (i = 0; i < td->i2c_lut_size; i++) {
> -		if (regulator_list_voltage(td->vdd_reg, td->i2c_lut[i]) == uv)
> +	for (i = td->lut_bottom; i < td->lut_size; i++) {
> +		if ((td->lut_uv[i] / td->soc->alignment.step_uv) >= uv)
>  			return i;
>  	}
>  
> @@ -863,9 +1051,14 @@ static int dfll_lock(struct tegra_dfll *td)
>  			return -EINVAL;
>  		}
>  
> -		dfll_i2c_set_output_enabled(td, true);
> +		if (td->pmu_if == TEGRA_DFLL_PMU_PWM)
> +			dfll_pwm_set_output_enabled(td, true);
> +		else
> +			dfll_i2c_set_output_enabled(td, true);
> +
>  		dfll_set_mode(td, DFLL_CLOSED_LOOP);
>  		dfll_set_frequency_request(td, req);
> +		dfll_set_force_output_enabled(td, false);
>  		return 0;
>  
>  	default:
> @@ -889,7 +1082,10 @@ static int dfll_unlock(struct tegra_dfll *td)
>  	case DFLL_CLOSED_LOOP:
>  		dfll_set_open_loop_config(td);
>  		dfll_set_mode(td, DFLL_OPEN_LOOP);
> -		dfll_i2c_set_output_enabled(td, false);
> +		if (td->pmu_if == TEGRA_DFLL_PMU_PWM)
> +			dfll_pwm_set_output_enabled(td, false);
> +		else
> +			dfll_i2c_set_output_enabled(td, false);
>  		return 0;
>  
>  	case DFLL_OPEN_LOOP:
> @@ -1171,15 +1367,17 @@ static int attr_registers_show(struct seq_file *s, void *data)
>  		seq_printf(s, "[0x%02x] = 0x%08x\n", offs,
>  			   dfll_i2c_readl(td, offs));
>  
> -	seq_puts(s, "\nINTEGRATED I2C CONTROLLER REGISTERS:\n");
> -	offs = DFLL_I2C_CLK_DIVISOR;
> -	seq_printf(s, "[0x%02x] = 0x%08x\n", offs,
> -		   __raw_readl(td->i2c_controller_base + offs));
> -
> -	seq_puts(s, "\nLUT:\n");
> -	for (offs = 0; offs <  4 * MAX_DFLL_VOLTAGES; offs += 4)
> +	if (td->pmu_if == TEGRA_DFLL_PMU_I2C) {
> +		seq_puts(s, "\nINTEGRATED I2C CONTROLLER REGISTERS:\n");
> +		offs = DFLL_I2C_CLK_DIVISOR;
>  		seq_printf(s, "[0x%02x] = 0x%08x\n", offs,
> -			   __raw_readl(td->lut_base + offs));
> +			   __raw_readl(td->i2c_controller_base + offs));
> +
> +		seq_puts(s, "\nLUT:\n");
> +		for (offs = 0; offs <  4 * MAX_DFLL_VOLTAGES; offs += 4)
> +			seq_printf(s, "[0x%02x] = 0x%08x\n", offs,
> +				   __raw_readl(td->lut_base + offs));
> +	}
>  
>  	return 0;
>  }
> @@ -1377,15 +1575,17 @@ static int dfll_init(struct tegra_dfll *td)
>   */
>  static int find_vdd_map_entry_exact(struct tegra_dfll *td, int uV)
>  {
> -	int i, n_voltages, reg_uV;
> +	int i, n_voltages, reg_mult, align_mult;
>  
> +	align_mult = uV / td->soc->alignment.step_uv;
>  	n_voltages = regulator_count_voltages(td->vdd_reg);
>  	for (i = 0; i < n_voltages; i++) {
> -		reg_uV = regulator_list_voltage(td->vdd_reg, i);
> -		if (reg_uV < 0)
> +		reg_mult = regulator_list_voltage(td->vdd_reg, i) /
> +					td->soc->alignment.step_uv;
> +		if (reg_mult < 0)
>  			break;
>  
> -		if (uV == reg_uV)
> +		if (align_mult == reg_mult)
>  			return i;
>  	}
>  
> @@ -1399,15 +1599,17 @@ static int find_vdd_map_entry_exact(struct tegra_dfll *td, int uV)
>   * */
>  static int find_vdd_map_entry_min(struct tegra_dfll *td, int uV)
>  {
> -	int i, n_voltages, reg_uV;
> +	int i, n_voltages, reg_mult, align_mult;
>  
> +	align_mult = uV / td->soc->alignment.step_uv;
>  	n_voltages = regulator_count_voltages(td->vdd_reg);
>  	for (i = 0; i < n_voltages; i++) {
> -		reg_uV = regulator_list_voltage(td->vdd_reg, i);
> -		if (reg_uV < 0)
> +		reg_mult = regulator_list_voltage(td->vdd_reg, i) /
> +					td->soc->alignment.step_uv;
> +		if (reg_mult < 0)
>  			break;
>  
> -		if (uV <= reg_uV)
> +		if (align_mult <= reg_mult)
>  			return i;
>  	}
>  
> @@ -1415,6 +1617,53 @@ static int find_vdd_map_entry_min(struct tegra_dfll *td, int uV)
>  	return -EINVAL;
>  }
>  
> +/*
> + * Look-up table in h/w is ignored when PWM is used as DFLL interface to PMIC.
> + * In this case closed loop output is controlling duty cycle directly. The s/w
> + * look-up that maps PWM duty cycle to voltage is still built by this function.
> + */
> +static int dfll_build_lut_pwm(struct tegra_dfll *td, int v_max)
> +{
> +	int i, reg_volt;
> +	unsigned long rate;
> +	u8 lut_bottom = MAX_DFLL_VOLTAGES;
> +	int v_min = td->soc->cvb->min_millivolts * 1000;
> +
> +	for (i = 0; i < MAX_DFLL_VOLTAGES; i++) {
> +		reg_volt = td->lut_uv[i];
> +
> +		/* since opp voltage is exact mv */
> +		reg_volt = (reg_volt / 1000) * 1000;
> +		if (reg_volt > v_max)
> +			break;
> +
> +		td->lut[i] = i;
> +		if ((lut_bottom == MAX_DFLL_VOLTAGES) && (reg_volt >= v_min))
> +			lut_bottom = i;
> +	}
> +
> +	/* determine voltage boundaries */
> +	td->lut_size = i;
> +	if ((lut_bottom == MAX_DFLL_VOLTAGES) ||
> +	    (lut_bottom + 1 >= td->lut_size)) {
> +		dev_err(td->dev, "no voltage above DFLL minimum %d mV\n",
> +			td->soc->cvb->min_millivolts);
> +		return -EINVAL;
> +	}
> +	td->lut_bottom = lut_bottom;
> +
> +	/* determine rate boundaries */
> +	rate = get_dvco_rate_below(td, td->lut_bottom);
> +	if (!rate) {
> +		dev_err(td->dev, "no opp below DFLL minimum voltage %d mV\n",
> +			td->soc->cvb->min_millivolts);
> +		return -EINVAL;
> +	}
> +	td->dvco_rate_min = rate;
> +
> +	return 0;
> +}
> +
>  /**
>   * dfll_build_i2c_lut - build the I2C voltage register lookup table
>   * @td: DFLL instance
> @@ -1427,31 +1676,24 @@ static int find_vdd_map_entry_min(struct tegra_dfll *td, int uV)
>   *
>   * On success, fills in td->i2c_lut and returns 0, or -err on failure.
>   */
> -static int dfll_build_i2c_lut(struct tegra_dfll *td)
> +static int dfll_build_i2c_lut(struct tegra_dfll *td, int v_max)
>  {
> +	unsigned long rate;
>  	int ret = -EINVAL;
> -	int j, v, v_max, v_opp;
> +	int j, v, v_opp;
>  	int selector;
> -	unsigned long rate;
> -	struct dev_pm_opp *opp;
>  	int lut;
>  
> -	rate = ULONG_MAX;
> -	opp = dev_pm_opp_find_freq_floor(td->soc->dev, &rate);
> -	if (IS_ERR(opp)) {
> -		dev_err(td->dev, "couldn't get vmax opp, empty opp table?\n");
> -		goto out;
> -	}
> -	v_max = dev_pm_opp_get_voltage(opp);
> -	dev_pm_opp_put(opp);
> -
>  	v = td->soc->cvb->min_millivolts * 1000;
>  	lut = find_vdd_map_entry_exact(td, v);
>  	if (lut < 0)
>  		goto out;
> -	td->i2c_lut[0] = lut;
> +	td->lut[0] = lut;
> +	td->lut_bottom = 0;
>  
>  	for (j = 1, rate = 0; ; rate++) {
> +		struct dev_pm_opp *opp;
> +
>  		opp = dev_pm_opp_find_freq_ceil(td->soc->dev, &rate);
>  		if (IS_ERR(opp))
>  			break;
> @@ -1470,32 +1712,61 @@ static int dfll_build_i2c_lut(struct tegra_dfll *td)
>  			selector = find_vdd_map_entry_min(td, v);
>  			if (selector < 0)
>  				goto out;
> -			if (selector != td->i2c_lut[j - 1])
> -				td->i2c_lut[j++] = selector;
> +			if (selector != td->lut[j - 1])
> +				td->lut[j++] = selector;
>  		}
>  
>  		v = (j == MAX_DFLL_VOLTAGES - 1) ? v_max : v_opp;
>  		selector = find_vdd_map_entry_exact(td, v);
>  		if (selector < 0)
>  			goto out;
> -		if (selector != td->i2c_lut[j - 1])
> -			td->i2c_lut[j++] = selector;
> +		if (selector != td->lut[j - 1])
> +			td->lut[j++] = selector;
>  
>  		if (v >= v_max)
>  			break;
>  	}
> -	td->i2c_lut_size = j;
> +	td->lut_size = j;
>  
>  	if (!td->dvco_rate_min)
>  		dev_err(td->dev, "no opp above DFLL minimum voltage %d mV\n",
>  			td->soc->cvb->min_millivolts);
> -	else
> +	else {
>  		ret = 0;
> +		for (j = 0; j < td->lut_size; j++)
> +			td->lut_uv[j] =
> +				regulator_list_voltage(td->vdd_reg,
> +						       td->lut[j]);
> +	}
>  
>  out:
>  	return ret;
>  }
>  
> +static int dfll_build_lut(struct tegra_dfll *td)
> +{
> +	unsigned long rate;
> +	struct dev_pm_opp *opp;
> +	int v_max;
> +
> +	rcu_read_lock();
> +
> +	rate = ULONG_MAX;
> +	opp = dev_pm_opp_find_freq_floor(td->soc->dev, &rate);
> +	if (IS_ERR(opp)) {
> +		dev_err(td->dev, "couldn't get vmax opp, empty opp table?\n");
> +		return -EINVAL;
> +	}
> +	v_max = dev_pm_opp_get_voltage(opp);
> +
> +	rcu_read_unlock();
> +
> +	if (td->pmu_if == TEGRA_DFLL_PMU_PWM)
> +		return dfll_build_lut_pwm(td, v_max);
> +	else
> +		return dfll_build_i2c_lut(td, v_max);
> +}
> +
>  /**
>   * read_dt_param - helper function for reading required parameters from the DT
>   * @td: DFLL instance
> @@ -1554,12 +1825,55 @@ static int dfll_fetch_i2c_params(struct tegra_dfll *td)
>  	}
>  	td->i2c_reg = vsel_reg;
>  
> -	ret = dfll_build_i2c_lut(td);
> -	if (ret) {
> -		dev_err(td->dev, "couldn't build I2C LUT\n");
> +	return 0;
> +}
> +
> +static int dfll_fetch_pwm_params(struct tegra_dfll *td)
> +{
> +	int ret, i;
> +	u32 pwm_period;
> +
> +	if (!td->soc->alignment.step_uv || !td->soc->alignment.offset_uv) {
> +		dev_err(td->dev, "Missing step or alignment info for PWM regulator");
> +		return -EINVAL;
> +	}
> +	for (i = 0; i < MAX_DFLL_VOLTAGES; i++)
> +		td->lut_uv[i] = td->soc->alignment.offset_uv +
> +				i * td->soc->alignment.step_uv;
> +
> +	ret = read_dt_param(td, "nvidia,init-uv", &td->reg_init_uV);
> +	if (!ret) {
> +		dev_err(td->dev, "couldn't get initialized voltage\n");
>  		return ret;
>  	}
>  
> +	ret = read_dt_param(td, "nvidia,pwm-period", &pwm_period);
> +	if (!ret) {
> +		dev_err(td->dev, "couldn't get PWM period\n");
> +		return ret;
> +	}
> +	td->pwm_rate = (NSEC_PER_SEC / pwm_period) * (MAX_DFLL_VOLTAGES - 1);
> +
> +	td->pwm_pin = devm_pinctrl_get(td->dev);
> +	if (IS_ERR(td->pwm_pin)) {
> +		dev_err(td->dev, "DT: missing pinctrl device\n");
> +		return PTR_ERR(td->pwm_pin);
> +	}
> +
> +	td->pwm_enable_state = pinctrl_lookup_state(td->pwm_pin,
> +						    "dvfs_pwm_enable");
> +	if (IS_ERR(td->pwm_enable_state)) {
> +		dev_err(td->dev, "DT: missing pwm enabled state\n");
> +		return PTR_ERR(td->pwm_enable_state);
> +	}
> +
> +	td->pwm_disable_state = pinctrl_lookup_state(td->pwm_pin,
> +						     "dvfs_pwm_disable");
> +	if (IS_ERR(td->pwm_disable_state)) {
> +		dev_err(td->dev, "DT: missing pwm disabled state\n");
> +		return PTR_ERR(td->pwm_disable_state);
> +	}
> +
>  	return 0;
>  }
>  
> @@ -1625,12 +1939,6 @@ int tegra_dfll_register(struct platform_device *pdev,
>  
>  	td->soc = soc;
>  
> -	td->vdd_reg = devm_regulator_get(td->dev, "vdd-cpu");
> -	if (IS_ERR(td->vdd_reg)) {
> -		dev_err(td->dev, "couldn't get vdd_cpu regulator\n");
> -		return PTR_ERR(td->vdd_reg);
> -	}
> -
>  	td->dvco_rst = devm_reset_control_get(td->dev, "dvco");
>  	if (IS_ERR(td->dvco_rst)) {
>  		dev_err(td->dev, "couldn't get dvco reset\n");
> @@ -1643,10 +1951,27 @@ int tegra_dfll_register(struct platform_device *pdev,
>  		return ret;
>  	}
>  
> -	ret = dfll_fetch_i2c_params(td);
> +	if (of_property_read_bool(td->dev->of_node, "nvidia,pwm-to-pmic")) {
> +		td->pmu_if = TEGRA_DFLL_PMU_PWM;
> +		ret = dfll_fetch_pwm_params(td);
> +	} else  {
> +		td->vdd_reg = devm_regulator_get(td->dev, "vdd-cpu");
> +		if (IS_ERR(td->vdd_reg)) {
> +			dev_err(td->dev, "couldn't get vdd_cpu regulator\n");
> +			return PTR_ERR(td->vdd_reg);
> +		}
> +		td->pmu_if = TEGRA_DFLL_PMU_I2C;
> +		ret = dfll_fetch_i2c_params(td);
> +	}
>  	if (ret)
>  		return ret;
>  
> +	ret = dfll_build_lut(td);
> +	if (ret) {
> +		dev_err(td->dev, "couldn't build LUT\n");
> +		return ret;
> +	}
> +
>  	mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
>  	if (!mem) {
>  		dev_err(td->dev, "no control register resource\n");
> diff --git a/drivers/clk/tegra/clk-dfll.h b/drivers/clk/tegra/clk-dfll.h
> index 83352c8..228f54a 100644
> --- a/drivers/clk/tegra/clk-dfll.h
> +++ b/drivers/clk/tegra/clk-dfll.h
> @@ -21,6 +21,7 @@
>  #include <linux/platform_device.h>
>  #include <linux/reset.h>
>  #include <linux/types.h>
> +#include "cvb.h"
>  
>  /**
>   * struct tegra_dfll_soc_data - SoC-specific hooks/integration for the DFLL driver
> @@ -35,6 +36,12 @@ struct tegra_dfll_soc_data {
>  	struct device *dev;
>  	unsigned long max_freq;
>  	const struct cvb_table *cvb;
> +	struct rail_alignment alignment;
> +	unsigned int min_millivolts;
> +	unsigned int tune_high_min_millivolts;
> +	u32 tune0_low;
> +	u32 tune0_high;
> +	u32 tune1;
>  
>  	void (*init_clock_trimmers)(void);
>  	void (*set_clock_trimmers_high)(void);
> 

Overall this is quite a big change. I wonder if it is worth splitting
into a preparatory patch (with all the renaming, etc) for the existing
i2c mode and then add the pwm support. It is hard to see how the
existing i2c code has been changed.

Cheers
Jon
Peter De Schrijver Feb. 1, 2018, 10:57 a.m. UTC | #2
On Wed, Jan 31, 2018 at 11:26:16AM +0000, Jon Hunter wrote:
> 
> On 24/01/18 12:45, Peter De Schrijver wrote:
> > The DFLL can directly generate a PWM signal to control the regulator IC
> > rather then sending i2c messages. This patch adds support for this mode.
> > In this mode the LUT is not used and also there is no regulator object
> > involved because there is no way to control the regulator voltage without
> > also changing the DFLL output frequency. Also the register debugfs file
> > is slightly reworked to only show the i2c registers when i2c mode is in
> > use.
> > 
> > Signed-off-by: Peter De Schrijver <pdeschrijver@nvidia.com>
> > ---
> >  drivers/clk/tegra/clk-dfll.c | 481 ++++++++++++++++++++++++++++++++++++-------
> >  drivers/clk/tegra/clk-dfll.h |   7 +
> >  2 files changed, 410 insertions(+), 78 deletions(-)
> > 
> > diff --git a/drivers/clk/tegra/clk-dfll.c b/drivers/clk/tegra/clk-dfll.c
> > index 0a7deee..204717c 100644
> > --- a/drivers/clk/tegra/clk-dfll.c
> > +++ b/drivers/clk/tegra/clk-dfll.c
> > @@ -243,6 +243,12 @@ enum dfll_tune_range {
> >  	DFLL_TUNE_LOW = 1,
> >  };
> >  
> > +
> > +enum tegra_dfll_pmu_if {
> > +	TEGRA_DFLL_PMU_I2C = 0,
> > +	TEGRA_DFLL_PMU_PWM = 1,
> > +};
> > +
> >  /**
> >   * struct dfll_rate_req - target DFLL rate request data
> >   * @rate: target frequency, after the postscaling
> > @@ -292,18 +298,27 @@ struct tegra_dfll {
> >  	u32				force_mode;
> >  	u32				cf;
> >  	u32				ci;
> > -	u32				cg;
> > +	s32				cg;
> 
> Why has the type been changed?
> 
> >  	bool				cg_scale;
> > +	u32				reg_init_uV;
> >  
> >  	/* I2C interface parameters */
> >  	u32				i2c_fs_rate;
> >  	u32				i2c_reg;
> >  	u32				i2c_slave_addr;
> >  
> > -	/* i2c_lut array entries are regulator framework selectors */
> > -	unsigned			i2c_lut[MAX_DFLL_VOLTAGES];
> > -	int				i2c_lut_size;
> > -	u8				lut_min, lut_max, lut_safe;
> > +	/* lut array entries are regulator framework selectors or PWM values*/
> > +	unsigned int			lut[MAX_DFLL_VOLTAGES];
> > +	unsigned int			lut_uv[MAX_DFLL_VOLTAGES];
> > +	int				lut_size;
> > +	u8				lut_bottom, lut_min, lut_max, lut_safe;
> 
> You say the LUT is not used for PWM, but you are changing the name here
> and it appears to be used to some degree in PWM and so I am a bit
> confused here.
> 

lut itself is not used. lut_uv, mapping voltages to PWM duty cycle, still is.
For i2c the 'PWM duty cycle' then gets mapped to an actual voltage ID sent
over i2c to the regulator by lut. So maybe i2c_lut should keep its name, and
the others should be renamed to uv_map or something?

> > +
> > +	/* PWM interface */
> > +	enum tegra_dfll_pmu_if		pmu_if;
> > +	unsigned long			pwm_rate;
> > +	struct pinctrl			*pwm_pin;
> > +	struct pinctrl_state		*pwm_enable_state;
> > +	struct pinctrl_state		*pwm_disable_state;
> >  };
> >  
> >  #define clk_hw_to_dfll(_hw) container_of(_hw, struct tegra_dfll, dfll_clk_hw)
> > @@ -490,6 +505,36 @@ static void dfll_set_mode(struct tegra_dfll *td,
> >  }
> >  
> >  /*
> > + * DVCO rate control
> > + */
> > +
> > +static unsigned long get_dvco_rate_below(struct tegra_dfll *td, u8 out_min)
> > +{
> > +	struct dev_pm_opp *opp;
> > +	unsigned long rate, prev_rate;
> > +	int uv, min_uv;
> > +
> > +	min_uv = td->lut_uv[out_min];
> > +	for (rate = 0, prev_rate = 0; ; rate++) {
> > +		rcu_read_lock();
> > +		opp = dev_pm_opp_find_freq_ceil(td->soc->dev, &rate);
> > +		if (IS_ERR(opp)) {
> > +			rcu_read_unlock();
> > +			break;
> 
> 'prev_rate' could be uninitialised here and so you may return a bogus value.
> 

prev_rate is set to 0 in the loop initialization?

> > +		}
> > +		uv = dev_pm_opp_get_voltage(opp);
> > +		rcu_read_unlock();
> > +
> > +		if (uv && uv > min_uv)
> > +			return prev_rate;
> > +
> > +		prev_rate = rate;
> > +	}
> > +
> > +	return prev_rate;
> > +}
> > +
> > +/*
> >   * DFLL-to-I2C controller interface
> >   */
> >  
> > @@ -518,6 +563,118 @@ static int dfll_i2c_set_output_enabled(struct tegra_dfll *td, bool enable)
> >  	return 0;
> >  }
> >  
> > +
> > +/*
> > + * DFLL-to-PWM controller interface
> > + */
> > +
> > +/**
> > + * dfll_pwm_set_output_enabled - enable/disable PWM voltage requests
> > + * @td: DFLL instance
> > + * @enable: whether to enable or disable the PWM voltage requests
> > + *
> > + * Set the master enable control for PWM control value updates. If disabled,
> > + * then the PWM signal is not driven. Also configure the PWM output pad
> > + * to the appropriate state.
> > + */
> > +static int dfll_pwm_set_output_enabled(struct tegra_dfll *td, bool enable)
> > +{
> > +	int ret;
> > +	u32 val, div;
> > +
> > +	if (enable) {
> > +		ret = pinctrl_select_state(td->pwm_pin, td->pwm_enable_state);
> > +		if (ret < 0) {
> > +			dev_err(td->dev, "setting enable state failed\n");
> > +			return -EINVAL;
> 
> Why not return ret?
> 

Will do that in the next version.

> > +		}
> > +		val = dfll_readl(td, DFLL_OUTPUT_CFG);
> > +		val &= ~DFLL_OUTPUT_CFG_PWM_DIV_MASK;
> > +		div = DIV_ROUND_UP(td->ref_rate, td->pwm_rate);
> > +		val |= (div << DFLL_OUTPUT_CFG_PWM_DIV_SHIFT)
> > +				& DFLL_OUTPUT_CFG_PWM_DIV_MASK;
> > +		dfll_writel(td, val, DFLL_OUTPUT_CFG);
> > +		dfll_wmb(td);
> > +
> > +		val |= DFLL_OUTPUT_CFG_PWM_ENABLE;
> > +		dfll_writel(td, val, DFLL_OUTPUT_CFG);
> > +		dfll_wmb(td);
> > +	} else {
> > +		ret = pinctrl_select_state(td->pwm_pin, td->pwm_disable_state);
> > +		if (ret < 0)
> > +			dev_warn(td->dev, "setting disable state failed\n");
> > +
> > +		val = dfll_readl(td, DFLL_OUTPUT_CFG);
> > +		val &= ~DFLL_OUTPUT_CFG_PWM_ENABLE;
> > +		dfll_writel(td, val, DFLL_OUTPUT_CFG);
> > +		dfll_wmb(td);
> > +	}
> > +
> > +	return 0;
> > +}
> > +/**
> > + * dfll_set_force_output_value - set fixed value for force output
> > + * @td: DFLL instance
> > + * @out_val: value to force output
> > + *
> > + * Set the fixec value for force output, DFLL will output this value when
> 
> s/fixec/fixed/
> 

Ok.

> > + * force output is enabled.
> > + */
> > +static u32 dfll_set_force_output_value(struct tegra_dfll *td, u8 out_val)
> > +{
> > +	u32 val = dfll_readl(td, DFLL_OUTPUT_FORCE);
> > +
> > +	val = (val & DFLL_OUTPUT_FORCE_ENABLE) | (out_val & OUT_MASK);
> 
> Don't you need to clear the OUT_MASK bits first?
> 

Probably :)

> > +	dfll_writel(td, val, DFLL_OUTPUT_FORCE);
> > +	dfll_wmb(td);
> > +
> > +	return dfll_readl(td, DFLL_OUTPUT_FORCE);
> > +}
> > +
> > +/**
> > + * dfll_set_force_output_enabled - enable/disable force output
> > + * @td: DFLL instance
> > + * @enable: whether to enable or disable the force output
> > + *
> > + * Set the enable control for fouce output with fixed value.
> > + */
> > +static void dfll_set_force_output_enabled(struct tegra_dfll *td, bool enable)
> > +{
> > +	u32 val = dfll_readl(td, DFLL_OUTPUT_FORCE);
> > +
> > +	if (enable)
> > +		val |= DFLL_OUTPUT_FORCE_ENABLE;
> > +	else
> > +		val &= ~DFLL_OUTPUT_FORCE_ENABLE;
> > +
> > +	dfll_writel(td, val, DFLL_OUTPUT_FORCE);
> > +	dfll_wmb(td);
> > +}
> > +
> > +/**
> > + * dfll_i2c_set_output_enabled - enable/disable I2C PMIC voltage requests
> > + * @td: DFLL instance
> > + * @enable: whether to enable or disable the I2C voltage requests
> > + *
> > + * Set the master enable control for I2C control value updates. If disabled,
> > + * then I2C control messages are inhibited, regardless of the DFLL mode.
> > + */
> > +static int dfll_force_output(struct tegra_dfll *td, unsigned int out_sel)
> > +{
> > +	u32 val;
> > +
> > +	if (out_sel > OUT_MASK)
> > +		return -EINVAL;
> > +
> > +	val = dfll_set_force_output_value(td, out_sel);
> > +	if ((td->mode < DFLL_CLOSED_LOOP) &&
> > +	    !(val & DFLL_OUTPUT_FORCE_ENABLE)) {
> > +		dfll_set_force_output_enabled(td, true);
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> >  /**
> >   * dfll_load_lut - load the voltage lookup table
> >   * @td: struct tegra_dfll *
> > @@ -531,15 +688,15 @@ static void dfll_load_i2c_lut(struct tegra_dfll *td)
> >  	u32 val;
> >  
> >  	for (i = 0; i < MAX_DFLL_VOLTAGES; i++) {
> > -		if (i < td->lut_min)
> > -			lut_index = td->lut_min;
> > -		else if (i > td->lut_max)
> > -			lut_index = td->lut_max;
> > +		if (i < td->lut_bottom)
> > +			lut_index = td->lut_bottom;
> > +		else if (i > td->lut_size - 1)
> > +			lut_index = td->lut_size - 1;
> >  		else
> >  			lut_index = i;
> >  
> >  		val = regulator_list_hardware_vsel(td->vdd_reg,
> > -						     td->i2c_lut[lut_index]);
> > +						     td->lut[lut_index]);
> >  		__raw_writel(val, td->lut_base + i * 4);
> >  	}
> >  
> > @@ -595,23 +752,53 @@ static void dfll_init_out_if(struct tegra_dfll *td)
> >  	u32 val;
> >  
> >  	td->lut_min = 0;
> > -	td->lut_max = td->i2c_lut_size - 1;
> > -	td->lut_safe = td->lut_min + 1;
> > -
> > -	dfll_i2c_writel(td, 0, DFLL_OUTPUT_CFG);
> > -	val = (td->lut_safe << DFLL_OUTPUT_CFG_SAFE_SHIFT) |
> > -		(td->lut_max << DFLL_OUTPUT_CFG_MAX_SHIFT) |
> > -		(td->lut_min << DFLL_OUTPUT_CFG_MIN_SHIFT);
> > -	dfll_i2c_writel(td, val, DFLL_OUTPUT_CFG);
> > -	dfll_i2c_wmb(td);
> > -
> > -	dfll_writel(td, 0, DFLL_OUTPUT_FORCE);
> > -	dfll_i2c_writel(td, 0, DFLL_INTR_EN);
> > -	dfll_i2c_writel(td, DFLL_INTR_MAX_MASK | DFLL_INTR_MIN_MASK,
> > -			DFLL_INTR_STS);
> > -
> > -	dfll_load_i2c_lut(td);
> > -	dfll_init_i2c_if(td);
> > +	td->lut_max = td->lut_size - 1;
> > +	td->lut_safe = td->lut_min + (td->lut_min < td->lut_max ? 1 : 0);
> > +
> > +	if (td->pmu_if == TEGRA_DFLL_PMU_PWM) {
> > +		int vinit = td->reg_init_uV;
> > +		int vstep = td->soc->alignment.step_uv;
> > +		int vmin = td->lut_uv[0];
> > +
> > +		/* clear DFLL_OUTPUT_CFG before setting new value */
> > +		dfll_writel(td, 0, DFLL_OUTPUT_CFG);
> > +		dfll_wmb(td);
> > +
> > +		val = dfll_readl(td, DFLL_OUTPUT_CFG);
> > +		val |= (td->lut_safe << DFLL_OUTPUT_CFG_SAFE_SHIFT) |
> > +		       (td->lut_max << DFLL_OUTPUT_CFG_MAX_SHIFT) |
> > +		       (td->lut_min << DFLL_OUTPUT_CFG_MIN_SHIFT);
> > +		dfll_writel(td, val, DFLL_OUTPUT_CFG);
> > +		dfll_wmb(td);
> > +
> > +		dfll_writel(td, 0, DFLL_OUTPUT_FORCE);
> > +		dfll_i2c_writel(td, 0, DFLL_INTR_EN);
> > +		dfll_i2c_writel(td, DFLL_INTR_MAX_MASK | DFLL_INTR_MIN_MASK,
> > +				DFLL_INTR_STS);
> > +
> > +		/* set initial voltage */
> > +		if ((vinit >= vmin) && vstep) {
> > +			unsigned int vsel;
> > +
> > +			vsel = DIV_ROUND_UP((vinit - vmin), vstep);
> > +			dfll_force_output(td, vsel);
> > +		}
> > +	} else {
> > +		dfll_i2c_writel(td, 0, DFLL_OUTPUT_CFG);
> > +		val = (td->lut_safe << DFLL_OUTPUT_CFG_SAFE_SHIFT) |
> > +			(td->lut_max << DFLL_OUTPUT_CFG_MAX_SHIFT) |
> > +			(td->lut_min << DFLL_OUTPUT_CFG_MIN_SHIFT);
> > +		dfll_i2c_writel(td, val, DFLL_OUTPUT_CFG);
> > +		dfll_i2c_wmb(td);
> > +
> > +		dfll_writel(td, 0, DFLL_OUTPUT_FORCE);
> > +		dfll_i2c_writel(td, 0, DFLL_INTR_EN);
> > +		dfll_i2c_writel(td, DFLL_INTR_MAX_MASK | DFLL_INTR_MIN_MASK,
> > +				DFLL_INTR_STS);
> > +
> > +		dfll_load_i2c_lut(td);
> > +		dfll_init_i2c_if(td);
> > +	}
> >  }
> >  
> >  /*
> > @@ -637,11 +824,12 @@ static int find_lut_index_for_rate(struct tegra_dfll *td, unsigned long rate)
> >  	if (IS_ERR(opp))
> >  		return PTR_ERR(opp);
> >  
> > -	uv = dev_pm_opp_get_voltage(opp);
> > +	uv = dev_pm_opp_get_voltage(opp) / td->soc->alignment.step_uv;
> > +
> >  	dev_pm_opp_put(opp);
> >  
> > -	for (i = 0; i < td->i2c_lut_size; i++) {
> > -		if (regulator_list_voltage(td->vdd_reg, td->i2c_lut[i]) == uv)
> > +	for (i = td->lut_bottom; i < td->lut_size; i++) {
> > +		if ((td->lut_uv[i] / td->soc->alignment.step_uv) >= uv)
> >  			return i;
> >  	}
> >  
> > @@ -863,9 +1051,14 @@ static int dfll_lock(struct tegra_dfll *td)
> >  			return -EINVAL;
> >  		}
> >  
> > -		dfll_i2c_set_output_enabled(td, true);
> > +		if (td->pmu_if == TEGRA_DFLL_PMU_PWM)
> > +			dfll_pwm_set_output_enabled(td, true);
> > +		else
> > +			dfll_i2c_set_output_enabled(td, true);
> > +
> >  		dfll_set_mode(td, DFLL_CLOSED_LOOP);
> >  		dfll_set_frequency_request(td, req);
> > +		dfll_set_force_output_enabled(td, false);
> >  		return 0;
> >  
> >  	default:
> > @@ -889,7 +1082,10 @@ static int dfll_unlock(struct tegra_dfll *td)
> >  	case DFLL_CLOSED_LOOP:
> >  		dfll_set_open_loop_config(td);
> >  		dfll_set_mode(td, DFLL_OPEN_LOOP);
> > -		dfll_i2c_set_output_enabled(td, false);
> > +		if (td->pmu_if == TEGRA_DFLL_PMU_PWM)
> > +			dfll_pwm_set_output_enabled(td, false);
> > +		else
> > +			dfll_i2c_set_output_enabled(td, false);
> >  		return 0;
> >  
> >  	case DFLL_OPEN_LOOP:
> > @@ -1171,15 +1367,17 @@ static int attr_registers_show(struct seq_file *s, void *data)
> >  		seq_printf(s, "[0x%02x] = 0x%08x\n", offs,
> >  			   dfll_i2c_readl(td, offs));
> >  
> > -	seq_puts(s, "\nINTEGRATED I2C CONTROLLER REGISTERS:\n");
> > -	offs = DFLL_I2C_CLK_DIVISOR;
> > -	seq_printf(s, "[0x%02x] = 0x%08x\n", offs,
> > -		   __raw_readl(td->i2c_controller_base + offs));
> > -
> > -	seq_puts(s, "\nLUT:\n");
> > -	for (offs = 0; offs <  4 * MAX_DFLL_VOLTAGES; offs += 4)
> > +	if (td->pmu_if == TEGRA_DFLL_PMU_I2C) {
> > +		seq_puts(s, "\nINTEGRATED I2C CONTROLLER REGISTERS:\n");
> > +		offs = DFLL_I2C_CLK_DIVISOR;
> >  		seq_printf(s, "[0x%02x] = 0x%08x\n", offs,
> > -			   __raw_readl(td->lut_base + offs));
> > +			   __raw_readl(td->i2c_controller_base + offs));
> > +
> > +		seq_puts(s, "\nLUT:\n");
> > +		for (offs = 0; offs <  4 * MAX_DFLL_VOLTAGES; offs += 4)
> > +			seq_printf(s, "[0x%02x] = 0x%08x\n", offs,
> > +				   __raw_readl(td->lut_base + offs));
> > +	}
> >  
> >  	return 0;
> >  }
> > @@ -1377,15 +1575,17 @@ static int dfll_init(struct tegra_dfll *td)
> >   */
> >  static int find_vdd_map_entry_exact(struct tegra_dfll *td, int uV)
> >  {
> > -	int i, n_voltages, reg_uV;
> > +	int i, n_voltages, reg_mult, align_mult;
> >  
> > +	align_mult = uV / td->soc->alignment.step_uv;
> >  	n_voltages = regulator_count_voltages(td->vdd_reg);
> >  	for (i = 0; i < n_voltages; i++) {
> > -		reg_uV = regulator_list_voltage(td->vdd_reg, i);
> > -		if (reg_uV < 0)
> > +		reg_mult = regulator_list_voltage(td->vdd_reg, i) /
> > +					td->soc->alignment.step_uv;
> > +		if (reg_mult < 0)
> >  			break;
> >  
> > -		if (uV == reg_uV)
> > +		if (align_mult == reg_mult)
> >  			return i;
> >  	}
> >  
> > @@ -1399,15 +1599,17 @@ static int find_vdd_map_entry_exact(struct tegra_dfll *td, int uV)
> >   * */
> >  static int find_vdd_map_entry_min(struct tegra_dfll *td, int uV)
> >  {
> > -	int i, n_voltages, reg_uV;
> > +	int i, n_voltages, reg_mult, align_mult;
> >  
> > +	align_mult = uV / td->soc->alignment.step_uv;
> >  	n_voltages = regulator_count_voltages(td->vdd_reg);
> >  	for (i = 0; i < n_voltages; i++) {
> > -		reg_uV = regulator_list_voltage(td->vdd_reg, i);
> > -		if (reg_uV < 0)
> > +		reg_mult = regulator_list_voltage(td->vdd_reg, i) /
> > +					td->soc->alignment.step_uv;
> > +		if (reg_mult < 0)
> >  			break;
> >  
> > -		if (uV <= reg_uV)
> > +		if (align_mult <= reg_mult)
> >  			return i;
> >  	}
> >  
> > @@ -1415,6 +1617,53 @@ static int find_vdd_map_entry_min(struct tegra_dfll *td, int uV)
> >  	return -EINVAL;
> >  }
> >  
> > +/*
> > + * Look-up table in h/w is ignored when PWM is used as DFLL interface to PMIC.
> > + * In this case closed loop output is controlling duty cycle directly. The s/w
> > + * look-up that maps PWM duty cycle to voltage is still built by this function.
> > + */
> > +static int dfll_build_lut_pwm(struct tegra_dfll *td, int v_max)
> > +{
> > +	int i, reg_volt;
> > +	unsigned long rate;
> > +	u8 lut_bottom = MAX_DFLL_VOLTAGES;
> > +	int v_min = td->soc->cvb->min_millivolts * 1000;
> > +
> > +	for (i = 0; i < MAX_DFLL_VOLTAGES; i++) {
> > +		reg_volt = td->lut_uv[i];
> > +
> > +		/* since opp voltage is exact mv */
> > +		reg_volt = (reg_volt / 1000) * 1000;
> > +		if (reg_volt > v_max)
> > +			break;
> > +
> > +		td->lut[i] = i;
> > +		if ((lut_bottom == MAX_DFLL_VOLTAGES) && (reg_volt >= v_min))
> > +			lut_bottom = i;
> > +	}
> > +
> > +	/* determine voltage boundaries */
> > +	td->lut_size = i;
> > +	if ((lut_bottom == MAX_DFLL_VOLTAGES) ||
> > +	    (lut_bottom + 1 >= td->lut_size)) {
> > +		dev_err(td->dev, "no voltage above DFLL minimum %d mV\n",
> > +			td->soc->cvb->min_millivolts);
> > +		return -EINVAL;
> > +	}
> > +	td->lut_bottom = lut_bottom;
> > +
> > +	/* determine rate boundaries */
> > +	rate = get_dvco_rate_below(td, td->lut_bottom);
> > +	if (!rate) {
> > +		dev_err(td->dev, "no opp below DFLL minimum voltage %d mV\n",
> > +			td->soc->cvb->min_millivolts);
> > +		return -EINVAL;
> > +	}
> > +	td->dvco_rate_min = rate;
> > +
> > +	return 0;
> > +}
> > +
> >  /**
> >   * dfll_build_i2c_lut - build the I2C voltage register lookup table
> >   * @td: DFLL instance
> > @@ -1427,31 +1676,24 @@ static int find_vdd_map_entry_min(struct tegra_dfll *td, int uV)
> >   *
> >   * On success, fills in td->i2c_lut and returns 0, or -err on failure.
> >   */
> > -static int dfll_build_i2c_lut(struct tegra_dfll *td)
> > +static int dfll_build_i2c_lut(struct tegra_dfll *td, int v_max)
> >  {
> > +	unsigned long rate;
> >  	int ret = -EINVAL;
> > -	int j, v, v_max, v_opp;
> > +	int j, v, v_opp;
> >  	int selector;
> > -	unsigned long rate;
> > -	struct dev_pm_opp *opp;
> >  	int lut;
> >  
> > -	rate = ULONG_MAX;
> > -	opp = dev_pm_opp_find_freq_floor(td->soc->dev, &rate);
> > -	if (IS_ERR(opp)) {
> > -		dev_err(td->dev, "couldn't get vmax opp, empty opp table?\n");
> > -		goto out;
> > -	}
> > -	v_max = dev_pm_opp_get_voltage(opp);
> > -	dev_pm_opp_put(opp);
> > -
> >  	v = td->soc->cvb->min_millivolts * 1000;
> >  	lut = find_vdd_map_entry_exact(td, v);
> >  	if (lut < 0)
> >  		goto out;
> > -	td->i2c_lut[0] = lut;
> > +	td->lut[0] = lut;
> > +	td->lut_bottom = 0;
> >  
> >  	for (j = 1, rate = 0; ; rate++) {
> > +		struct dev_pm_opp *opp;
> > +
> >  		opp = dev_pm_opp_find_freq_ceil(td->soc->dev, &rate);
> >  		if (IS_ERR(opp))
> >  			break;
> > @@ -1470,32 +1712,61 @@ static int dfll_build_i2c_lut(struct tegra_dfll *td)
> >  			selector = find_vdd_map_entry_min(td, v);
> >  			if (selector < 0)
> >  				goto out;
> > -			if (selector != td->i2c_lut[j - 1])
> > -				td->i2c_lut[j++] = selector;
> > +			if (selector != td->lut[j - 1])
> > +				td->lut[j++] = selector;
> >  		}
> >  
> >  		v = (j == MAX_DFLL_VOLTAGES - 1) ? v_max : v_opp;
> >  		selector = find_vdd_map_entry_exact(td, v);
> >  		if (selector < 0)
> >  			goto out;
> > -		if (selector != td->i2c_lut[j - 1])
> > -			td->i2c_lut[j++] = selector;
> > +		if (selector != td->lut[j - 1])
> > +			td->lut[j++] = selector;
> >  
> >  		if (v >= v_max)
> >  			break;
> >  	}
> > -	td->i2c_lut_size = j;
> > +	td->lut_size = j;
> >  
> >  	if (!td->dvco_rate_min)
> >  		dev_err(td->dev, "no opp above DFLL minimum voltage %d mV\n",
> >  			td->soc->cvb->min_millivolts);
> > -	else
> > +	else {
> >  		ret = 0;
> > +		for (j = 0; j < td->lut_size; j++)
> > +			td->lut_uv[j] =
> > +				regulator_list_voltage(td->vdd_reg,
> > +						       td->lut[j]);
> > +	}
> >  
> >  out:
> >  	return ret;
> >  }
> >  
> > +static int dfll_build_lut(struct tegra_dfll *td)
> > +{
> > +	unsigned long rate;
> > +	struct dev_pm_opp *opp;
> > +	int v_max;
> > +
> > +	rcu_read_lock();
> > +
> > +	rate = ULONG_MAX;
> > +	opp = dev_pm_opp_find_freq_floor(td->soc->dev, &rate);
> > +	if (IS_ERR(opp)) {
> > +		dev_err(td->dev, "couldn't get vmax opp, empty opp table?\n");
> > +		return -EINVAL;
> > +	}
> > +	v_max = dev_pm_opp_get_voltage(opp);
> > +
> > +	rcu_read_unlock();
> > +
> > +	if (td->pmu_if == TEGRA_DFLL_PMU_PWM)
> > +		return dfll_build_lut_pwm(td, v_max);
> > +	else
> > +		return dfll_build_i2c_lut(td, v_max);
> > +}
> > +
> >  /**
> >   * read_dt_param - helper function for reading required parameters from the DT
> >   * @td: DFLL instance
> > @@ -1554,12 +1825,55 @@ static int dfll_fetch_i2c_params(struct tegra_dfll *td)
> >  	}
> >  	td->i2c_reg = vsel_reg;
> >  
> > -	ret = dfll_build_i2c_lut(td);
> > -	if (ret) {
> > -		dev_err(td->dev, "couldn't build I2C LUT\n");
> > +	return 0;
> > +}
> > +
> > +static int dfll_fetch_pwm_params(struct tegra_dfll *td)
> > +{
> > +	int ret, i;
> > +	u32 pwm_period;
> > +
> > +	if (!td->soc->alignment.step_uv || !td->soc->alignment.offset_uv) {
> > +		dev_err(td->dev, "Missing step or alignment info for PWM regulator");
> > +		return -EINVAL;
> > +	}
> > +	for (i = 0; i < MAX_DFLL_VOLTAGES; i++)
> > +		td->lut_uv[i] = td->soc->alignment.offset_uv +
> > +				i * td->soc->alignment.step_uv;
> > +
> > +	ret = read_dt_param(td, "nvidia,init-uv", &td->reg_init_uV);
> > +	if (!ret) {
> > +		dev_err(td->dev, "couldn't get initialized voltage\n");
> >  		return ret;
> >  	}
> >  
> > +	ret = read_dt_param(td, "nvidia,pwm-period", &pwm_period);
> > +	if (!ret) {
> > +		dev_err(td->dev, "couldn't get PWM period\n");
> > +		return ret;
> > +	}
> > +	td->pwm_rate = (NSEC_PER_SEC / pwm_period) * (MAX_DFLL_VOLTAGES - 1);
> > +
> > +	td->pwm_pin = devm_pinctrl_get(td->dev);
> > +	if (IS_ERR(td->pwm_pin)) {
> > +		dev_err(td->dev, "DT: missing pinctrl device\n");
> > +		return PTR_ERR(td->pwm_pin);
> > +	}
> > +
> > +	td->pwm_enable_state = pinctrl_lookup_state(td->pwm_pin,
> > +						    "dvfs_pwm_enable");
> > +	if (IS_ERR(td->pwm_enable_state)) {
> > +		dev_err(td->dev, "DT: missing pwm enabled state\n");
> > +		return PTR_ERR(td->pwm_enable_state);
> > +	}
> > +
> > +	td->pwm_disable_state = pinctrl_lookup_state(td->pwm_pin,
> > +						     "dvfs_pwm_disable");
> > +	if (IS_ERR(td->pwm_disable_state)) {
> > +		dev_err(td->dev, "DT: missing pwm disabled state\n");
> > +		return PTR_ERR(td->pwm_disable_state);
> > +	}
> > +
> >  	return 0;
> >  }
> >  
> > @@ -1625,12 +1939,6 @@ int tegra_dfll_register(struct platform_device *pdev,
> >  
> >  	td->soc = soc;
> >  
> > -	td->vdd_reg = devm_regulator_get(td->dev, "vdd-cpu");
> > -	if (IS_ERR(td->vdd_reg)) {
> > -		dev_err(td->dev, "couldn't get vdd_cpu regulator\n");
> > -		return PTR_ERR(td->vdd_reg);
> > -	}
> > -
> >  	td->dvco_rst = devm_reset_control_get(td->dev, "dvco");
> >  	if (IS_ERR(td->dvco_rst)) {
> >  		dev_err(td->dev, "couldn't get dvco reset\n");
> > @@ -1643,10 +1951,27 @@ int tegra_dfll_register(struct platform_device *pdev,
> >  		return ret;
> >  	}
> >  
> > -	ret = dfll_fetch_i2c_params(td);
> > +	if (of_property_read_bool(td->dev->of_node, "nvidia,pwm-to-pmic")) {
> > +		td->pmu_if = TEGRA_DFLL_PMU_PWM;
> > +		ret = dfll_fetch_pwm_params(td);
> > +	} else  {
> > +		td->vdd_reg = devm_regulator_get(td->dev, "vdd-cpu");
> > +		if (IS_ERR(td->vdd_reg)) {
> > +			dev_err(td->dev, "couldn't get vdd_cpu regulator\n");
> > +			return PTR_ERR(td->vdd_reg);
> > +		}
> > +		td->pmu_if = TEGRA_DFLL_PMU_I2C;
> > +		ret = dfll_fetch_i2c_params(td);
> > +	}
> >  	if (ret)
> >  		return ret;
> >  
> > +	ret = dfll_build_lut(td);
> > +	if (ret) {
> > +		dev_err(td->dev, "couldn't build LUT\n");
> > +		return ret;
> > +	}
> > +
> >  	mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> >  	if (!mem) {
> >  		dev_err(td->dev, "no control register resource\n");
> > diff --git a/drivers/clk/tegra/clk-dfll.h b/drivers/clk/tegra/clk-dfll.h
> > index 83352c8..228f54a 100644
> > --- a/drivers/clk/tegra/clk-dfll.h
> > +++ b/drivers/clk/tegra/clk-dfll.h
> > @@ -21,6 +21,7 @@
> >  #include <linux/platform_device.h>
> >  #include <linux/reset.h>
> >  #include <linux/types.h>
> > +#include "cvb.h"
> >  
> >  /**
> >   * struct tegra_dfll_soc_data - SoC-specific hooks/integration for the DFLL driver
> > @@ -35,6 +36,12 @@ struct tegra_dfll_soc_data {
> >  	struct device *dev;
> >  	unsigned long max_freq;
> >  	const struct cvb_table *cvb;
> > +	struct rail_alignment alignment;
> > +	unsigned int min_millivolts;
> > +	unsigned int tune_high_min_millivolts;
> > +	u32 tune0_low;
> > +	u32 tune0_high;
> > +	u32 tune1;
> >  
> >  	void (*init_clock_trimmers)(void);
> >  	void (*set_clock_trimmers_high)(void);
> > 
> 
> Overall this is quite a big change. I wonder if it is worth splitting
> into a preparatory patch (with all the renaming, etc) for the existing
> i2c mode and then add the pwm support. It is hard to see how the
> existing i2c code has been changed.
> 

Yes, this is largely because the original code didn't make a good split between
the 'PWM duty cycle' and the i2c regulator VID mapping. If you look at the hw,
it's obviously designed to use PWM regulators. Then i2c regulator support was
added by adding the LUT which maps a 'PWM duty cycle' to a regulator VID.

Peter.
--
To unsubscribe from this list: send the line "unsubscribe linux-clk" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
diff mbox

Patch

diff --git a/drivers/clk/tegra/clk-dfll.c b/drivers/clk/tegra/clk-dfll.c
index 0a7deee..204717c 100644
--- a/drivers/clk/tegra/clk-dfll.c
+++ b/drivers/clk/tegra/clk-dfll.c
@@ -243,6 +243,12 @@  enum dfll_tune_range {
 	DFLL_TUNE_LOW = 1,
 };
 
+
+enum tegra_dfll_pmu_if {
+	TEGRA_DFLL_PMU_I2C = 0,
+	TEGRA_DFLL_PMU_PWM = 1,
+};
+
 /**
  * struct dfll_rate_req - target DFLL rate request data
  * @rate: target frequency, after the postscaling
@@ -292,18 +298,27 @@  struct tegra_dfll {
 	u32				force_mode;
 	u32				cf;
 	u32				ci;
-	u32				cg;
+	s32				cg;
 	bool				cg_scale;
+	u32				reg_init_uV;
 
 	/* I2C interface parameters */
 	u32				i2c_fs_rate;
 	u32				i2c_reg;
 	u32				i2c_slave_addr;
 
-	/* i2c_lut array entries are regulator framework selectors */
-	unsigned			i2c_lut[MAX_DFLL_VOLTAGES];
-	int				i2c_lut_size;
-	u8				lut_min, lut_max, lut_safe;
+	/* lut array entries are regulator framework selectors or PWM values*/
+	unsigned int			lut[MAX_DFLL_VOLTAGES];
+	unsigned int			lut_uv[MAX_DFLL_VOLTAGES];
+	int				lut_size;
+	u8				lut_bottom, lut_min, lut_max, lut_safe;
+
+	/* PWM interface */
+	enum tegra_dfll_pmu_if		pmu_if;
+	unsigned long			pwm_rate;
+	struct pinctrl			*pwm_pin;
+	struct pinctrl_state		*pwm_enable_state;
+	struct pinctrl_state		*pwm_disable_state;
 };
 
 #define clk_hw_to_dfll(_hw) container_of(_hw, struct tegra_dfll, dfll_clk_hw)
@@ -490,6 +505,36 @@  static void dfll_set_mode(struct tegra_dfll *td,
 }
 
 /*
+ * DVCO rate control
+ */
+
+static unsigned long get_dvco_rate_below(struct tegra_dfll *td, u8 out_min)
+{
+	struct dev_pm_opp *opp;
+	unsigned long rate, prev_rate;
+	int uv, min_uv;
+
+	min_uv = td->lut_uv[out_min];
+	for (rate = 0, prev_rate = 0; ; rate++) {
+		rcu_read_lock();
+		opp = dev_pm_opp_find_freq_ceil(td->soc->dev, &rate);
+		if (IS_ERR(opp)) {
+			rcu_read_unlock();
+			break;
+		}
+		uv = dev_pm_opp_get_voltage(opp);
+		rcu_read_unlock();
+
+		if (uv && uv > min_uv)
+			return prev_rate;
+
+		prev_rate = rate;
+	}
+
+	return prev_rate;
+}
+
+/*
  * DFLL-to-I2C controller interface
  */
 
@@ -518,6 +563,118 @@  static int dfll_i2c_set_output_enabled(struct tegra_dfll *td, bool enable)
 	return 0;
 }
 
+
+/*
+ * DFLL-to-PWM controller interface
+ */
+
+/**
+ * dfll_pwm_set_output_enabled - enable/disable PWM voltage requests
+ * @td: DFLL instance
+ * @enable: whether to enable or disable the PWM voltage requests
+ *
+ * Set the master enable control for PWM control value updates. If disabled,
+ * then the PWM signal is not driven. Also configure the PWM output pad
+ * to the appropriate state.
+ */
+static int dfll_pwm_set_output_enabled(struct tegra_dfll *td, bool enable)
+{
+	int ret;
+	u32 val, div;
+
+	if (enable) {
+		ret = pinctrl_select_state(td->pwm_pin, td->pwm_enable_state);
+		if (ret < 0) {
+			dev_err(td->dev, "setting enable state failed\n");
+			return -EINVAL;
+		}
+		val = dfll_readl(td, DFLL_OUTPUT_CFG);
+		val &= ~DFLL_OUTPUT_CFG_PWM_DIV_MASK;
+		div = DIV_ROUND_UP(td->ref_rate, td->pwm_rate);
+		val |= (div << DFLL_OUTPUT_CFG_PWM_DIV_SHIFT)
+				& DFLL_OUTPUT_CFG_PWM_DIV_MASK;
+		dfll_writel(td, val, DFLL_OUTPUT_CFG);
+		dfll_wmb(td);
+
+		val |= DFLL_OUTPUT_CFG_PWM_ENABLE;
+		dfll_writel(td, val, DFLL_OUTPUT_CFG);
+		dfll_wmb(td);
+	} else {
+		ret = pinctrl_select_state(td->pwm_pin, td->pwm_disable_state);
+		if (ret < 0)
+			dev_warn(td->dev, "setting disable state failed\n");
+
+		val = dfll_readl(td, DFLL_OUTPUT_CFG);
+		val &= ~DFLL_OUTPUT_CFG_PWM_ENABLE;
+		dfll_writel(td, val, DFLL_OUTPUT_CFG);
+		dfll_wmb(td);
+	}
+
+	return 0;
+}
+/**
+ * dfll_set_force_output_value - set fixed value for force output
+ * @td: DFLL instance
+ * @out_val: value to force output
+ *
+ * Set the fixec value for force output, DFLL will output this value when
+ * force output is enabled.
+ */
+static u32 dfll_set_force_output_value(struct tegra_dfll *td, u8 out_val)
+{
+	u32 val = dfll_readl(td, DFLL_OUTPUT_FORCE);
+
+	val = (val & DFLL_OUTPUT_FORCE_ENABLE) | (out_val & OUT_MASK);
+	dfll_writel(td, val, DFLL_OUTPUT_FORCE);
+	dfll_wmb(td);
+
+	return dfll_readl(td, DFLL_OUTPUT_FORCE);
+}
+
+/**
+ * dfll_set_force_output_enabled - enable/disable force output
+ * @td: DFLL instance
+ * @enable: whether to enable or disable the force output
+ *
+ * Set the enable control for fouce output with fixed value.
+ */
+static void dfll_set_force_output_enabled(struct tegra_dfll *td, bool enable)
+{
+	u32 val = dfll_readl(td, DFLL_OUTPUT_FORCE);
+
+	if (enable)
+		val |= DFLL_OUTPUT_FORCE_ENABLE;
+	else
+		val &= ~DFLL_OUTPUT_FORCE_ENABLE;
+
+	dfll_writel(td, val, DFLL_OUTPUT_FORCE);
+	dfll_wmb(td);
+}
+
+/**
+ * dfll_i2c_set_output_enabled - enable/disable I2C PMIC voltage requests
+ * @td: DFLL instance
+ * @enable: whether to enable or disable the I2C voltage requests
+ *
+ * Set the master enable control for I2C control value updates. If disabled,
+ * then I2C control messages are inhibited, regardless of the DFLL mode.
+ */
+static int dfll_force_output(struct tegra_dfll *td, unsigned int out_sel)
+{
+	u32 val;
+
+	if (out_sel > OUT_MASK)
+		return -EINVAL;
+
+	val = dfll_set_force_output_value(td, out_sel);
+	if ((td->mode < DFLL_CLOSED_LOOP) &&
+	    !(val & DFLL_OUTPUT_FORCE_ENABLE)) {
+		dfll_set_force_output_enabled(td, true);
+	}
+
+	return 0;
+}
+
 /**
  * dfll_load_lut - load the voltage lookup table
  * @td: struct tegra_dfll *
@@ -531,15 +688,15 @@  static void dfll_load_i2c_lut(struct tegra_dfll *td)
 	u32 val;
 
 	for (i = 0; i < MAX_DFLL_VOLTAGES; i++) {
-		if (i < td->lut_min)
-			lut_index = td->lut_min;
-		else if (i > td->lut_max)
-			lut_index = td->lut_max;
+		if (i < td->lut_bottom)
+			lut_index = td->lut_bottom;
+		else if (i > td->lut_size - 1)
+			lut_index = td->lut_size - 1;
 		else
 			lut_index = i;
 
 		val = regulator_list_hardware_vsel(td->vdd_reg,
-						     td->i2c_lut[lut_index]);
+						     td->lut[lut_index]);
 		__raw_writel(val, td->lut_base + i * 4);
 	}
 
@@ -595,23 +752,53 @@  static void dfll_init_out_if(struct tegra_dfll *td)
 	u32 val;
 
 	td->lut_min = 0;
-	td->lut_max = td->i2c_lut_size - 1;
-	td->lut_safe = td->lut_min + 1;
-
-	dfll_i2c_writel(td, 0, DFLL_OUTPUT_CFG);
-	val = (td->lut_safe << DFLL_OUTPUT_CFG_SAFE_SHIFT) |
-		(td->lut_max << DFLL_OUTPUT_CFG_MAX_SHIFT) |
-		(td->lut_min << DFLL_OUTPUT_CFG_MIN_SHIFT);
-	dfll_i2c_writel(td, val, DFLL_OUTPUT_CFG);
-	dfll_i2c_wmb(td);
-
-	dfll_writel(td, 0, DFLL_OUTPUT_FORCE);
-	dfll_i2c_writel(td, 0, DFLL_INTR_EN);
-	dfll_i2c_writel(td, DFLL_INTR_MAX_MASK | DFLL_INTR_MIN_MASK,
-			DFLL_INTR_STS);
-
-	dfll_load_i2c_lut(td);
-	dfll_init_i2c_if(td);
+	td->lut_max = td->lut_size - 1;
+	td->lut_safe = td->lut_min + (td->lut_min < td->lut_max ? 1 : 0);
+
+	if (td->pmu_if == TEGRA_DFLL_PMU_PWM) {
+		int vinit = td->reg_init_uV;
+		int vstep = td->soc->alignment.step_uv;
+		int vmin = td->lut_uv[0];
+
+		/* clear DFLL_OUTPUT_CFG before setting new value */
+		dfll_writel(td, 0, DFLL_OUTPUT_CFG);
+		dfll_wmb(td);
+
+		val = dfll_readl(td, DFLL_OUTPUT_CFG);
+		val |= (td->lut_safe << DFLL_OUTPUT_CFG_SAFE_SHIFT) |
+		       (td->lut_max << DFLL_OUTPUT_CFG_MAX_SHIFT) |
+		       (td->lut_min << DFLL_OUTPUT_CFG_MIN_SHIFT);
+		dfll_writel(td, val, DFLL_OUTPUT_CFG);
+		dfll_wmb(td);
+
+		dfll_writel(td, 0, DFLL_OUTPUT_FORCE);
+		dfll_i2c_writel(td, 0, DFLL_INTR_EN);
+		dfll_i2c_writel(td, DFLL_INTR_MAX_MASK | DFLL_INTR_MIN_MASK,
+				DFLL_INTR_STS);
+
+		/* set initial voltage */
+		if ((vinit >= vmin) && vstep) {
+			unsigned int vsel;
+
+			vsel = DIV_ROUND_UP((vinit - vmin), vstep);
+			dfll_force_output(td, vsel);
+		}
+	} else {
+		dfll_i2c_writel(td, 0, DFLL_OUTPUT_CFG);
+		val = (td->lut_safe << DFLL_OUTPUT_CFG_SAFE_SHIFT) |
+			(td->lut_max << DFLL_OUTPUT_CFG_MAX_SHIFT) |
+			(td->lut_min << DFLL_OUTPUT_CFG_MIN_SHIFT);
+		dfll_i2c_writel(td, val, DFLL_OUTPUT_CFG);
+		dfll_i2c_wmb(td);
+
+		dfll_writel(td, 0, DFLL_OUTPUT_FORCE);
+		dfll_i2c_writel(td, 0, DFLL_INTR_EN);
+		dfll_i2c_writel(td, DFLL_INTR_MAX_MASK | DFLL_INTR_MIN_MASK,
+				DFLL_INTR_STS);
+
+		dfll_load_i2c_lut(td);
+		dfll_init_i2c_if(td);
+	}
 }
 
 /*
@@ -637,11 +824,12 @@  static int find_lut_index_for_rate(struct tegra_dfll *td, unsigned long rate)
 	if (IS_ERR(opp))
 		return PTR_ERR(opp);
 
-	uv = dev_pm_opp_get_voltage(opp);
+	uv = dev_pm_opp_get_voltage(opp) / td->soc->alignment.step_uv;
+
 	dev_pm_opp_put(opp);
 
-	for (i = 0; i < td->i2c_lut_size; i++) {
-		if (regulator_list_voltage(td->vdd_reg, td->i2c_lut[i]) == uv)
+	for (i = td->lut_bottom; i < td->lut_size; i++) {
+		if ((td->lut_uv[i] / td->soc->alignment.step_uv) >= uv)
 			return i;
 	}
 
@@ -863,9 +1051,14 @@  static int dfll_lock(struct tegra_dfll *td)
 			return -EINVAL;
 		}
 
-		dfll_i2c_set_output_enabled(td, true);
+		if (td->pmu_if == TEGRA_DFLL_PMU_PWM)
+			dfll_pwm_set_output_enabled(td, true);
+		else
+			dfll_i2c_set_output_enabled(td, true);
+
 		dfll_set_mode(td, DFLL_CLOSED_LOOP);
 		dfll_set_frequency_request(td, req);
+		dfll_set_force_output_enabled(td, false);
 		return 0;
 
 	default:
@@ -889,7 +1082,10 @@  static int dfll_unlock(struct tegra_dfll *td)
 	case DFLL_CLOSED_LOOP:
 		dfll_set_open_loop_config(td);
 		dfll_set_mode(td, DFLL_OPEN_LOOP);
-		dfll_i2c_set_output_enabled(td, false);
+		if (td->pmu_if == TEGRA_DFLL_PMU_PWM)
+			dfll_pwm_set_output_enabled(td, false);
+		else
+			dfll_i2c_set_output_enabled(td, false);
 		return 0;
 
 	case DFLL_OPEN_LOOP:
@@ -1171,15 +1367,17 @@  static int attr_registers_show(struct seq_file *s, void *data)
 		seq_printf(s, "[0x%02x] = 0x%08x\n", offs,
 			   dfll_i2c_readl(td, offs));
 
-	seq_puts(s, "\nINTEGRATED I2C CONTROLLER REGISTERS:\n");
-	offs = DFLL_I2C_CLK_DIVISOR;
-	seq_printf(s, "[0x%02x] = 0x%08x\n", offs,
-		   __raw_readl(td->i2c_controller_base + offs));
-
-	seq_puts(s, "\nLUT:\n");
-	for (offs = 0; offs <  4 * MAX_DFLL_VOLTAGES; offs += 4)
+	if (td->pmu_if == TEGRA_DFLL_PMU_I2C) {
+		seq_puts(s, "\nINTEGRATED I2C CONTROLLER REGISTERS:\n");
+		offs = DFLL_I2C_CLK_DIVISOR;
 		seq_printf(s, "[0x%02x] = 0x%08x\n", offs,
-			   __raw_readl(td->lut_base + offs));
+			   __raw_readl(td->i2c_controller_base + offs));
+
+		seq_puts(s, "\nLUT:\n");
+		for (offs = 0; offs <  4 * MAX_DFLL_VOLTAGES; offs += 4)
+			seq_printf(s, "[0x%02x] = 0x%08x\n", offs,
+				   __raw_readl(td->lut_base + offs));
+	}
 
 	return 0;
 }
@@ -1377,15 +1575,17 @@  static int dfll_init(struct tegra_dfll *td)
  */
 static int find_vdd_map_entry_exact(struct tegra_dfll *td, int uV)
 {
-	int i, n_voltages, reg_uV;
+	int i, n_voltages, reg_mult, align_mult;
 
+	align_mult = uV / td->soc->alignment.step_uv;
 	n_voltages = regulator_count_voltages(td->vdd_reg);
 	for (i = 0; i < n_voltages; i++) {
-		reg_uV = regulator_list_voltage(td->vdd_reg, i);
-		if (reg_uV < 0)
+		reg_mult = regulator_list_voltage(td->vdd_reg, i) /
+					td->soc->alignment.step_uv;
+		if (reg_mult < 0)
 			break;
 
-		if (uV == reg_uV)
+		if (align_mult == reg_mult)
 			return i;
 	}
 
@@ -1399,15 +1599,17 @@  static int find_vdd_map_entry_exact(struct tegra_dfll *td, int uV)
  * */
 static int find_vdd_map_entry_min(struct tegra_dfll *td, int uV)
 {
-	int i, n_voltages, reg_uV;
+	int i, n_voltages, reg_mult, align_mult;
 
+	align_mult = uV / td->soc->alignment.step_uv;
 	n_voltages = regulator_count_voltages(td->vdd_reg);
 	for (i = 0; i < n_voltages; i++) {
-		reg_uV = regulator_list_voltage(td->vdd_reg, i);
-		if (reg_uV < 0)
+		reg_mult = regulator_list_voltage(td->vdd_reg, i) /
+					td->soc->alignment.step_uv;
+		if (reg_mult < 0)
 			break;
 
-		if (uV <= reg_uV)
+		if (align_mult <= reg_mult)
 			return i;
 	}
 
@@ -1415,6 +1617,53 @@  static int find_vdd_map_entry_min(struct tegra_dfll *td, int uV)
 	return -EINVAL;
 }
 
+/*
+ * Look-up table in h/w is ignored when PWM is used as DFLL interface to PMIC.
+ * In this case closed loop output is controlling duty cycle directly. The s/w
+ * look-up that maps PWM duty cycle to voltage is still built by this function.
+ */
+static int dfll_build_lut_pwm(struct tegra_dfll *td, int v_max)
+{
+	int i, reg_volt;
+	unsigned long rate;
+	u8 lut_bottom = MAX_DFLL_VOLTAGES;
+	int v_min = td->soc->cvb->min_millivolts * 1000;
+
+	for (i = 0; i < MAX_DFLL_VOLTAGES; i++) {
+		reg_volt = td->lut_uv[i];
+
+		/* since opp voltage is exact mv */
+		reg_volt = (reg_volt / 1000) * 1000;
+		if (reg_volt > v_max)
+			break;
+
+		td->lut[i] = i;
+		if ((lut_bottom == MAX_DFLL_VOLTAGES) && (reg_volt >= v_min))
+			lut_bottom = i;
+	}
+
+	/* determine voltage boundaries */
+	td->lut_size = i;
+	if ((lut_bottom == MAX_DFLL_VOLTAGES) ||
+	    (lut_bottom + 1 >= td->lut_size)) {
+		dev_err(td->dev, "no voltage above DFLL minimum %d mV\n",
+			td->soc->cvb->min_millivolts);
+		return -EINVAL;
+	}
+	td->lut_bottom = lut_bottom;
+
+	/* determine rate boundaries */
+	rate = get_dvco_rate_below(td, td->lut_bottom);
+	if (!rate) {
+		dev_err(td->dev, "no opp below DFLL minimum voltage %d mV\n",
+			td->soc->cvb->min_millivolts);
+		return -EINVAL;
+	}
+	td->dvco_rate_min = rate;
+
+	return 0;
+}
+
 /**
  * dfll_build_i2c_lut - build the I2C voltage register lookup table
  * @td: DFLL instance
@@ -1427,31 +1676,24 @@  static int find_vdd_map_entry_min(struct tegra_dfll *td, int uV)
  *
  * On success, fills in td->i2c_lut and returns 0, or -err on failure.
  */
-static int dfll_build_i2c_lut(struct tegra_dfll *td)
+static int dfll_build_i2c_lut(struct tegra_dfll *td, int v_max)
 {
+	unsigned long rate;
 	int ret = -EINVAL;
-	int j, v, v_max, v_opp;
+	int j, v, v_opp;
 	int selector;
-	unsigned long rate;
-	struct dev_pm_opp *opp;
 	int lut;
 
-	rate = ULONG_MAX;
-	opp = dev_pm_opp_find_freq_floor(td->soc->dev, &rate);
-	if (IS_ERR(opp)) {
-		dev_err(td->dev, "couldn't get vmax opp, empty opp table?\n");
-		goto out;
-	}
-	v_max = dev_pm_opp_get_voltage(opp);
-	dev_pm_opp_put(opp);
-
 	v = td->soc->cvb->min_millivolts * 1000;
 	lut = find_vdd_map_entry_exact(td, v);
 	if (lut < 0)
 		goto out;
-	td->i2c_lut[0] = lut;
+	td->lut[0] = lut;
+	td->lut_bottom = 0;
 
 	for (j = 1, rate = 0; ; rate++) {
+		struct dev_pm_opp *opp;
+
 		opp = dev_pm_opp_find_freq_ceil(td->soc->dev, &rate);
 		if (IS_ERR(opp))
 			break;
@@ -1470,32 +1712,61 @@  static int dfll_build_i2c_lut(struct tegra_dfll *td)
 			selector = find_vdd_map_entry_min(td, v);
 			if (selector < 0)
 				goto out;
-			if (selector != td->i2c_lut[j - 1])
-				td->i2c_lut[j++] = selector;
+			if (selector != td->lut[j - 1])
+				td->lut[j++] = selector;
 		}
 
 		v = (j == MAX_DFLL_VOLTAGES - 1) ? v_max : v_opp;
 		selector = find_vdd_map_entry_exact(td, v);
 		if (selector < 0)
 			goto out;
-		if (selector != td->i2c_lut[j - 1])
-			td->i2c_lut[j++] = selector;
+		if (selector != td->lut[j - 1])
+			td->lut[j++] = selector;
 
 		if (v >= v_max)
 			break;
 	}
-	td->i2c_lut_size = j;
+	td->lut_size = j;
 
 	if (!td->dvco_rate_min)
 		dev_err(td->dev, "no opp above DFLL minimum voltage %d mV\n",
 			td->soc->cvb->min_millivolts);
-	else
+	else {
 		ret = 0;
+		for (j = 0; j < td->lut_size; j++)
+			td->lut_uv[j] =
+				regulator_list_voltage(td->vdd_reg,
+						       td->lut[j]);
+	}
 
 out:
 	return ret;
 }
 
+static int dfll_build_lut(struct tegra_dfll *td)
+{
+	unsigned long rate;
+	struct dev_pm_opp *opp;
+	int v_max;
+
+	rcu_read_lock();
+
+	rate = ULONG_MAX;
+	opp = dev_pm_opp_find_freq_floor(td->soc->dev, &rate);
+	if (IS_ERR(opp)) {
+		dev_err(td->dev, "couldn't get vmax opp, empty opp table?\n");
+		return -EINVAL;
+	}
+	v_max = dev_pm_opp_get_voltage(opp);
+
+	rcu_read_unlock();
+
+	if (td->pmu_if == TEGRA_DFLL_PMU_PWM)
+		return dfll_build_lut_pwm(td, v_max);
+	else
+		return dfll_build_i2c_lut(td, v_max);
+}
+
 /**
  * read_dt_param - helper function for reading required parameters from the DT
  * @td: DFLL instance
@@ -1554,12 +1825,55 @@  static int dfll_fetch_i2c_params(struct tegra_dfll *td)
 	}
 	td->i2c_reg = vsel_reg;
 
-	ret = dfll_build_i2c_lut(td);
-	if (ret) {
-		dev_err(td->dev, "couldn't build I2C LUT\n");
+	return 0;
+}
+
+static int dfll_fetch_pwm_params(struct tegra_dfll *td)
+{
+	int ret, i;
+	u32 pwm_period;
+
+	if (!td->soc->alignment.step_uv || !td->soc->alignment.offset_uv) {
+		dev_err(td->dev, "Missing step or alignment info for PWM regulator");
+		return -EINVAL;
+	}
+	for (i = 0; i < MAX_DFLL_VOLTAGES; i++)
+		td->lut_uv[i] = td->soc->alignment.offset_uv +
+				i * td->soc->alignment.step_uv;
+
+	ret = read_dt_param(td, "nvidia,init-uv", &td->reg_init_uV);
+	if (!ret) {
+		dev_err(td->dev, "couldn't get initialized voltage\n");
 		return ret;
 	}
 
+	ret = read_dt_param(td, "nvidia,pwm-period", &pwm_period);
+	if (!ret) {
+		dev_err(td->dev, "couldn't get PWM period\n");
+		return ret;
+	}
+	td->pwm_rate = (NSEC_PER_SEC / pwm_period) * (MAX_DFLL_VOLTAGES - 1);
+
+	td->pwm_pin = devm_pinctrl_get(td->dev);
+	if (IS_ERR(td->pwm_pin)) {
+		dev_err(td->dev, "DT: missing pinctrl device\n");
+		return PTR_ERR(td->pwm_pin);
+	}
+
+	td->pwm_enable_state = pinctrl_lookup_state(td->pwm_pin,
+						    "dvfs_pwm_enable");
+	if (IS_ERR(td->pwm_enable_state)) {
+		dev_err(td->dev, "DT: missing pwm enabled state\n");
+		return PTR_ERR(td->pwm_enable_state);
+	}
+
+	td->pwm_disable_state = pinctrl_lookup_state(td->pwm_pin,
+						     "dvfs_pwm_disable");
+	if (IS_ERR(td->pwm_disable_state)) {
+		dev_err(td->dev, "DT: missing pwm disabled state\n");
+		return PTR_ERR(td->pwm_disable_state);
+	}
+
 	return 0;
 }
 
@@ -1625,12 +1939,6 @@  int tegra_dfll_register(struct platform_device *pdev,
 
 	td->soc = soc;
 
-	td->vdd_reg = devm_regulator_get(td->dev, "vdd-cpu");
-	if (IS_ERR(td->vdd_reg)) {
-		dev_err(td->dev, "couldn't get vdd_cpu regulator\n");
-		return PTR_ERR(td->vdd_reg);
-	}
-
 	td->dvco_rst = devm_reset_control_get(td->dev, "dvco");
 	if (IS_ERR(td->dvco_rst)) {
 		dev_err(td->dev, "couldn't get dvco reset\n");
@@ -1643,10 +1951,27 @@  int tegra_dfll_register(struct platform_device *pdev,
 		return ret;
 	}
 
-	ret = dfll_fetch_i2c_params(td);
+	if (of_property_read_bool(td->dev->of_node, "nvidia,pwm-to-pmic")) {
+		td->pmu_if = TEGRA_DFLL_PMU_PWM;
+		ret = dfll_fetch_pwm_params(td);
+	} else  {
+		td->vdd_reg = devm_regulator_get(td->dev, "vdd-cpu");
+		if (IS_ERR(td->vdd_reg)) {
+			dev_err(td->dev, "couldn't get vdd_cpu regulator\n");
+			return PTR_ERR(td->vdd_reg);
+		}
+		td->pmu_if = TEGRA_DFLL_PMU_I2C;
+		ret = dfll_fetch_i2c_params(td);
+	}
 	if (ret)
 		return ret;
 
+	ret = dfll_build_lut(td);
+	if (ret) {
+		dev_err(td->dev, "couldn't build LUT\n");
+		return ret;
+	}
+
 	mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 	if (!mem) {
 		dev_err(td->dev, "no control register resource\n");
diff --git a/drivers/clk/tegra/clk-dfll.h b/drivers/clk/tegra/clk-dfll.h
index 83352c8..228f54a 100644
--- a/drivers/clk/tegra/clk-dfll.h
+++ b/drivers/clk/tegra/clk-dfll.h
@@ -21,6 +21,7 @@ 
 #include <linux/platform_device.h>
 #include <linux/reset.h>
 #include <linux/types.h>
+#include "cvb.h"
 
 /**
  * struct tegra_dfll_soc_data - SoC-specific hooks/integration for the DFLL driver
@@ -35,6 +36,12 @@  struct tegra_dfll_soc_data {
 	struct device *dev;
 	unsigned long max_freq;
 	const struct cvb_table *cvb;
+	struct rail_alignment alignment;
+	unsigned int min_millivolts;
+	unsigned int tune_high_min_millivolts;
+	u32 tune0_low;
+	u32 tune0_high;
+	u32 tune1;
 
 	void (*init_clock_trimmers)(void);
 	void (*set_clock_trimmers_high)(void);