clk: tegra: Add closed loop support for the DFLL
With closed loop support, the clock rate of the DFLL can be adjusted. The oscillator itself in the DFLL is a free-running oscillator whose rate is directly determined the supply voltage. However, the DFLL module contains logic to compare the DFLL output rate to a fixed reference clock (51 MHz) and make a decision to either lower or raise the DFLL supply voltage. The DFLL module can then autonomously change the supply voltage by communicating with an off-chip PMIC via either I2C or PWM signals. This driver currently supports only I2C. Signed-off-by: Tuomas Tynkkynen <ttynkkynen@nvidia.com> Signed-off-by: Mikko Perttunen <mikko.perttunen@kapsi.fi> Acked-by: Peter De Schrijver <pdeschrijver@nvidia.com> Acked-by: Michael Turquette <mturquette@linaro.org> Signed-off-by: Thierry Reding <treding@nvidia.com>
This commit is contained in:
Родитель
d8d7a08fa8
Коммит
c4fe70ada4
|
@ -206,12 +206,16 @@
|
|||
*/
|
||||
#define REF_CLOCK_RATE 51000000UL
|
||||
|
||||
#define DVCO_RATE_TO_MULT(rate, ref_rate) ((rate) / ((ref_rate) / 2))
|
||||
#define MULT_TO_DVCO_RATE(mult, ref_rate) ((mult) * ((ref_rate) / 2))
|
||||
|
||||
/**
|
||||
* enum dfll_ctrl_mode - DFLL hardware operating mode
|
||||
* @DFLL_UNINITIALIZED: (uninitialized state - not in hardware bitfield)
|
||||
* @DFLL_DISABLED: DFLL not generating an output clock
|
||||
* @DFLL_OPEN_LOOP: DVCO running, but DFLL not adjusting voltage
|
||||
* @DFLL_CLOSED_LOOP: DVCO running, and DFLL adjusting voltage to match
|
||||
* the requested rate
|
||||
*
|
||||
* The integer corresponding to the last two states, minus one, is
|
||||
* written to the DFLL hardware to change operating modes.
|
||||
|
@ -220,6 +224,7 @@ enum dfll_ctrl_mode {
|
|||
DFLL_UNINITIALIZED = 0,
|
||||
DFLL_DISABLED = 1,
|
||||
DFLL_OPEN_LOOP = 2,
|
||||
DFLL_CLOSED_LOOP = 3,
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -237,6 +242,22 @@ enum dfll_tune_range {
|
|||
DFLL_TUNE_LOW = 1,
|
||||
};
|
||||
|
||||
/**
|
||||
* struct dfll_rate_req - target DFLL rate request data
|
||||
* @rate: target frequency, after the postscaling
|
||||
* @dvco_target_rate: target frequency, after the postscaling
|
||||
* @lut_index: LUT index at which voltage the dvco_target_rate will be reached
|
||||
* @mult_bits: value to program to the MULT bits of the DFLL_FREQ_REQ register
|
||||
* @scale_bits: value to program to the SCALE bits of the DFLL_FREQ_REQ register
|
||||
*/
|
||||
struct dfll_rate_req {
|
||||
unsigned long rate;
|
||||
unsigned long dvco_target_rate;
|
||||
int lut_index;
|
||||
u8 mult_bits;
|
||||
u8 scale_bits;
|
||||
};
|
||||
|
||||
struct tegra_dfll {
|
||||
struct device *dev;
|
||||
struct tegra_dfll_soc_data *soc;
|
||||
|
@ -261,9 +282,27 @@ struct tegra_dfll {
|
|||
struct dentry *debugfs_dir;
|
||||
struct clk_hw dfll_clk_hw;
|
||||
const char *output_clock_name;
|
||||
struct dfll_rate_req last_req;
|
||||
unsigned long last_unrounded_rate;
|
||||
|
||||
/* Parameters from DT */
|
||||
u32 droop_ctrl;
|
||||
u32 sample_rate;
|
||||
u32 force_mode;
|
||||
u32 cf;
|
||||
u32 ci;
|
||||
u32 cg;
|
||||
bool cg_scale;
|
||||
|
||||
/* 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;
|
||||
};
|
||||
|
||||
#define clk_hw_to_dfll(_hw) container_of(_hw, struct tegra_dfll, dfll_clk_hw)
|
||||
|
@ -273,6 +312,7 @@ static const char * const mode_name[] = {
|
|||
[DFLL_UNINITIALIZED] = "uninitialized",
|
||||
[DFLL_DISABLED] = "disabled",
|
||||
[DFLL_OPEN_LOOP] = "open_loop",
|
||||
[DFLL_CLOSED_LOOP] = "closed_loop",
|
||||
};
|
||||
|
||||
/*
|
||||
|
@ -498,6 +538,283 @@ static void dfll_set_mode(struct tegra_dfll *td,
|
|||
dfll_wmb(td);
|
||||
}
|
||||
|
||||
/*
|
||||
* DFLL-to-I2C controller interface
|
||||
*/
|
||||
|
||||
/**
|
||||
* 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_i2c_set_output_enabled(struct tegra_dfll *td, bool enable)
|
||||
{
|
||||
u32 val;
|
||||
|
||||
val = dfll_i2c_readl(td, DFLL_OUTPUT_CFG);
|
||||
|
||||
if (enable)
|
||||
val |= DFLL_OUTPUT_CFG_I2C_ENABLE;
|
||||
else
|
||||
val &= ~DFLL_OUTPUT_CFG_I2C_ENABLE;
|
||||
|
||||
dfll_i2c_writel(td, val, DFLL_OUTPUT_CFG);
|
||||
dfll_i2c_wmb(td);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* dfll_load_lut - load the voltage lookup table
|
||||
* @td: struct tegra_dfll *
|
||||
*
|
||||
* Load the voltage-to-PMIC register value lookup table into the DFLL
|
||||
* IP block memory. Look-up tables can be loaded at any time.
|
||||
*/
|
||||
static void dfll_load_i2c_lut(struct tegra_dfll *td)
|
||||
{
|
||||
int i, lut_index;
|
||||
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;
|
||||
else
|
||||
lut_index = i;
|
||||
|
||||
val = regulator_list_hardware_vsel(td->vdd_reg,
|
||||
td->i2c_lut[lut_index]);
|
||||
__raw_writel(val, td->lut_base + i * 4);
|
||||
}
|
||||
|
||||
dfll_i2c_wmb(td);
|
||||
}
|
||||
|
||||
/**
|
||||
* dfll_init_i2c_if - set up the DFLL's DFLL-I2C interface
|
||||
* @td: DFLL instance
|
||||
*
|
||||
* During DFLL driver initialization, program the DFLL-I2C interface
|
||||
* with the PMU slave address, vdd register offset, and transfer mode.
|
||||
* This data is used by the DFLL to automatically construct I2C
|
||||
* voltage-set commands, which are then passed to the DFLL's internal
|
||||
* I2C controller.
|
||||
*/
|
||||
static void dfll_init_i2c_if(struct tegra_dfll *td)
|
||||
{
|
||||
u32 val;
|
||||
|
||||
if (td->i2c_slave_addr > 0x7f) {
|
||||
val = td->i2c_slave_addr << DFLL_I2C_CFG_SLAVE_ADDR_SHIFT_10BIT;
|
||||
val |= DFLL_I2C_CFG_SLAVE_ADDR_10;
|
||||
} else {
|
||||
val = td->i2c_slave_addr << DFLL_I2C_CFG_SLAVE_ADDR_SHIFT_7BIT;
|
||||
}
|
||||
val |= DFLL_I2C_CFG_SIZE_MASK;
|
||||
val |= DFLL_I2C_CFG_ARB_ENABLE;
|
||||
dfll_i2c_writel(td, val, DFLL_I2C_CFG);
|
||||
|
||||
dfll_i2c_writel(td, td->i2c_reg, DFLL_I2C_VDD_REG_ADDR);
|
||||
|
||||
val = DIV_ROUND_UP(td->i2c_clk_rate, td->i2c_fs_rate * 8);
|
||||
BUG_ON(!val || (val > DFLL_I2C_CLK_DIVISOR_MASK));
|
||||
val = (val - 1) << DFLL_I2C_CLK_DIVISOR_FS_SHIFT;
|
||||
|
||||
/* default hs divisor just in case */
|
||||
val |= 1 << DFLL_I2C_CLK_DIVISOR_HS_SHIFT;
|
||||
__raw_writel(val, td->i2c_controller_base + DFLL_I2C_CLK_DIVISOR);
|
||||
dfll_i2c_wmb(td);
|
||||
}
|
||||
|
||||
/**
|
||||
* dfll_init_out_if - prepare DFLL-to-PMIC interface
|
||||
* @td: DFLL instance
|
||||
*
|
||||
* During DFLL driver initialization or resume from context loss,
|
||||
* disable the I2C command output to the PMIC, set safe voltage and
|
||||
* output limits, and disable and clear limit interrupts.
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
/*
|
||||
* Set/get the DFLL's targeted output clock rate
|
||||
*/
|
||||
|
||||
/**
|
||||
* find_lut_index_for_rate - determine I2C LUT index for given DFLL rate
|
||||
* @td: DFLL instance
|
||||
* @rate: clock rate
|
||||
*
|
||||
* Determines the index of a I2C LUT entry for a voltage that approximately
|
||||
* produces the given DFLL clock rate. This is used when forcing a value
|
||||
* to the integrator during rate changes. Returns -ENOENT if a suitable
|
||||
* LUT index is not found.
|
||||
*/
|
||||
static int find_lut_index_for_rate(struct tegra_dfll *td, unsigned long rate)
|
||||
{
|
||||
struct dev_pm_opp *opp;
|
||||
int i, uv;
|
||||
|
||||
opp = dev_pm_opp_find_freq_ceil(td->soc->opp_dev, &rate);
|
||||
if (IS_ERR(opp))
|
||||
return PTR_ERR(opp);
|
||||
uv = dev_pm_opp_get_voltage(opp);
|
||||
|
||||
for (i = 0; i < td->i2c_lut_size; i++) {
|
||||
if (regulator_list_voltage(td->vdd_reg, td->i2c_lut[i]) == uv)
|
||||
return i;
|
||||
}
|
||||
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
/**
|
||||
* dfll_calculate_rate_request - calculate DFLL parameters for a given rate
|
||||
* @td: DFLL instance
|
||||
* @req: DFLL-rate-request structure
|
||||
* @rate: the desired DFLL rate
|
||||
*
|
||||
* Populate the DFLL-rate-request record @req fields with the scale_bits
|
||||
* and mult_bits fields, based on the target input rate. Returns 0 upon
|
||||
* success, or -EINVAL if the requested rate in req->rate is too high
|
||||
* or low for the DFLL to generate.
|
||||
*/
|
||||
static int dfll_calculate_rate_request(struct tegra_dfll *td,
|
||||
struct dfll_rate_req *req,
|
||||
unsigned long rate)
|
||||
{
|
||||
u32 val;
|
||||
|
||||
/*
|
||||
* If requested rate is below the minimum DVCO rate, active the scaler.
|
||||
* In the future the DVCO minimum voltage should be selected based on
|
||||
* chip temperature and the actual minimum rate should be calibrated
|
||||
* at runtime.
|
||||
*/
|
||||
req->scale_bits = DFLL_FREQ_REQ_SCALE_MAX - 1;
|
||||
if (rate < td->dvco_rate_min) {
|
||||
int scale;
|
||||
|
||||
scale = DIV_ROUND_CLOSEST(rate / 1000 * DFLL_FREQ_REQ_SCALE_MAX,
|
||||
td->dvco_rate_min / 1000);
|
||||
if (!scale) {
|
||||
dev_err(td->dev, "%s: Rate %lu is too low\n",
|
||||
__func__, rate);
|
||||
return -EINVAL;
|
||||
}
|
||||
req->scale_bits = scale - 1;
|
||||
rate = td->dvco_rate_min;
|
||||
}
|
||||
|
||||
/* Convert requested rate into frequency request and scale settings */
|
||||
val = DVCO_RATE_TO_MULT(rate, td->ref_rate);
|
||||
if (val > FREQ_MAX) {
|
||||
dev_err(td->dev, "%s: Rate %lu is above dfll range\n",
|
||||
__func__, rate);
|
||||
return -EINVAL;
|
||||
}
|
||||
req->mult_bits = val;
|
||||
req->dvco_target_rate = MULT_TO_DVCO_RATE(req->mult_bits, td->ref_rate);
|
||||
req->rate = dfll_scale_dvco_rate(req->scale_bits,
|
||||
req->dvco_target_rate);
|
||||
req->lut_index = find_lut_index_for_rate(td, req->dvco_target_rate);
|
||||
if (req->lut_index < 0)
|
||||
return req->lut_index;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* dfll_set_frequency_request - start the frequency change operation
|
||||
* @td: DFLL instance
|
||||
* @req: rate request structure
|
||||
*
|
||||
* Tell the DFLL to try to change its output frequency to the
|
||||
* frequency represented by @req. DFLL must be in closed-loop mode.
|
||||
*/
|
||||
static void dfll_set_frequency_request(struct tegra_dfll *td,
|
||||
struct dfll_rate_req *req)
|
||||
{
|
||||
u32 val = 0;
|
||||
int force_val;
|
||||
int coef = 128; /* FIXME: td->cg_scale? */;
|
||||
|
||||
force_val = (req->lut_index - td->lut_safe) * coef / td->cg;
|
||||
force_val = clamp(force_val, FORCE_MIN, FORCE_MAX);
|
||||
|
||||
val |= req->mult_bits << DFLL_FREQ_REQ_MULT_SHIFT;
|
||||
val |= req->scale_bits << DFLL_FREQ_REQ_SCALE_SHIFT;
|
||||
val |= ((u32)force_val << DFLL_FREQ_REQ_FORCE_SHIFT) &
|
||||
DFLL_FREQ_REQ_FORCE_MASK;
|
||||
val |= DFLL_FREQ_REQ_FREQ_VALID | DFLL_FREQ_REQ_FORCE_ENABLE;
|
||||
|
||||
dfll_writel(td, val, DFLL_FREQ_REQ);
|
||||
dfll_wmb(td);
|
||||
}
|
||||
|
||||
/**
|
||||
* tegra_dfll_request_rate - set the next rate for the DFLL to tune to
|
||||
* @td: DFLL instance
|
||||
* @rate: clock rate to target
|
||||
*
|
||||
* Convert the requested clock rate @rate into the DFLL control logic
|
||||
* settings. In closed-loop mode, update new settings immediately to
|
||||
* adjust DFLL output rate accordingly. Otherwise, just save them
|
||||
* until the next switch to closed loop. Returns 0 upon success,
|
||||
* -EPERM if the DFLL driver has not yet been initialized, or -EINVAL
|
||||
* if @rate is outside the DFLL's tunable range.
|
||||
*/
|
||||
static int dfll_request_rate(struct tegra_dfll *td, unsigned long rate)
|
||||
{
|
||||
int ret;
|
||||
struct dfll_rate_req req;
|
||||
|
||||
if (td->mode == DFLL_UNINITIALIZED) {
|
||||
dev_err(td->dev, "%s: Cannot set DFLL rate in %s mode\n",
|
||||
__func__, mode_name[td->mode]);
|
||||
return -EPERM;
|
||||
}
|
||||
|
||||
ret = dfll_calculate_rate_request(td, &req, rate);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
td->last_unrounded_rate = rate;
|
||||
td->last_req = req;
|
||||
|
||||
if (td->mode == DFLL_CLOSED_LOOP)
|
||||
dfll_set_frequency_request(td, &td->last_req);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* DFLL enable/disable & open-loop <-> closed-loop transitions
|
||||
*/
|
||||
|
@ -570,8 +887,76 @@ static void dfll_set_open_loop_config(struct tegra_dfll *td)
|
|||
dfll_wmb(td);
|
||||
}
|
||||
|
||||
/**
|
||||
* tegra_dfll_lock - switch from open-loop to closed-loop mode
|
||||
* @td: DFLL instance
|
||||
*
|
||||
* Switch from OPEN_LOOP state to CLOSED_LOOP state. Returns 0 upon success,
|
||||
* -EINVAL if the DFLL's target rate hasn't been set yet, or -EPERM if the
|
||||
* DFLL is not currently in open-loop mode.
|
||||
*/
|
||||
static int dfll_lock(struct tegra_dfll *td)
|
||||
{
|
||||
struct dfll_rate_req *req = &td->last_req;
|
||||
|
||||
switch (td->mode) {
|
||||
case DFLL_CLOSED_LOOP:
|
||||
return 0;
|
||||
|
||||
case DFLL_OPEN_LOOP:
|
||||
if (req->rate == 0) {
|
||||
dev_err(td->dev, "%s: Cannot lock DFLL at rate 0\n",
|
||||
__func__);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
dfll_i2c_set_output_enabled(td, true);
|
||||
dfll_set_mode(td, DFLL_CLOSED_LOOP);
|
||||
dfll_set_frequency_request(td, req);
|
||||
return 0;
|
||||
|
||||
default:
|
||||
BUG_ON(td->mode > DFLL_CLOSED_LOOP);
|
||||
dev_err(td->dev, "%s: Cannot lock DFLL in %s mode\n",
|
||||
__func__, mode_name[td->mode]);
|
||||
return -EPERM;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* tegra_dfll_unlock - switch from closed-loop to open-loop mode
|
||||
* @td: DFLL instance
|
||||
*
|
||||
* Switch from CLOSED_LOOP state to OPEN_LOOP state. Returns 0 upon success,
|
||||
* or -EPERM if the DFLL is not currently in open-loop mode.
|
||||
*/
|
||||
static int dfll_unlock(struct tegra_dfll *td)
|
||||
{
|
||||
switch (td->mode) {
|
||||
case DFLL_CLOSED_LOOP:
|
||||
dfll_set_open_loop_config(td);
|
||||
dfll_set_mode(td, DFLL_OPEN_LOOP);
|
||||
dfll_i2c_set_output_enabled(td, false);
|
||||
return 0;
|
||||
|
||||
case DFLL_OPEN_LOOP:
|
||||
return 0;
|
||||
|
||||
default:
|
||||
BUG_ON(td->mode > DFLL_CLOSED_LOOP);
|
||||
dev_err(td->dev, "%s: Cannot unlock DFLL in %s mode\n",
|
||||
__func__, mode_name[td->mode]);
|
||||
return -EPERM;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Clock framework integration
|
||||
*
|
||||
* When the DFLL is being controlled by the CCF, always enter closed loop
|
||||
* mode when the clk is enabled. This requires that a DFLL rate request
|
||||
* has been set beforehand, which implies that a clk_set_rate() call is
|
||||
* always required before a clk_enable().
|
||||
*/
|
||||
|
||||
static int dfll_clk_is_enabled(struct clk_hw *hw)
|
||||
|
@ -584,21 +969,72 @@ static int dfll_clk_is_enabled(struct clk_hw *hw)
|
|||
static int dfll_clk_enable(struct clk_hw *hw)
|
||||
{
|
||||
struct tegra_dfll *td = clk_hw_to_dfll(hw);
|
||||
int ret;
|
||||
|
||||
return dfll_enable(td);
|
||||
ret = dfll_enable(td);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = dfll_lock(td);
|
||||
if (ret)
|
||||
dfll_disable(td);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void dfll_clk_disable(struct clk_hw *hw)
|
||||
{
|
||||
struct tegra_dfll *td = clk_hw_to_dfll(hw);
|
||||
int ret;
|
||||
|
||||
dfll_disable(td);
|
||||
ret = dfll_unlock(td);
|
||||
if (!ret)
|
||||
dfll_disable(td);
|
||||
}
|
||||
|
||||
static unsigned long dfll_clk_recalc_rate(struct clk_hw *hw,
|
||||
unsigned long parent_rate)
|
||||
{
|
||||
struct tegra_dfll *td = clk_hw_to_dfll(hw);
|
||||
|
||||
return td->last_unrounded_rate;
|
||||
}
|
||||
|
||||
static long dfll_clk_round_rate(struct clk_hw *hw,
|
||||
unsigned long rate,
|
||||
unsigned long *parent_rate)
|
||||
{
|
||||
struct tegra_dfll *td = clk_hw_to_dfll(hw);
|
||||
struct dfll_rate_req req;
|
||||
int ret;
|
||||
|
||||
ret = dfll_calculate_rate_request(td, &req, rate);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/*
|
||||
* Don't return the rounded rate, since it doesn't really matter as
|
||||
* the output rate will be voltage controlled anyway, and cpufreq
|
||||
* freaks out if any rounding happens.
|
||||
*/
|
||||
return rate;
|
||||
}
|
||||
|
||||
static int dfll_clk_set_rate(struct clk_hw *hw, unsigned long rate,
|
||||
unsigned long parent_rate)
|
||||
{
|
||||
struct tegra_dfll *td = clk_hw_to_dfll(hw);
|
||||
|
||||
return dfll_request_rate(td, rate);
|
||||
}
|
||||
|
||||
static const struct clk_ops dfll_clk_ops = {
|
||||
.is_enabled = dfll_clk_is_enabled,
|
||||
.enable = dfll_clk_enable,
|
||||
.disable = dfll_clk_disable,
|
||||
.recalc_rate = dfll_clk_recalc_rate,
|
||||
.round_rate = dfll_clk_round_rate,
|
||||
.set_rate = dfll_clk_set_rate,
|
||||
};
|
||||
|
||||
static struct clk_init_data dfll_clk_init_data = {
|
||||
|
@ -677,6 +1113,23 @@ static int attr_enable_set(void *data, u64 val)
|
|||
DEFINE_SIMPLE_ATTRIBUTE(enable_fops, attr_enable_get, attr_enable_set,
|
||||
"%llu\n");
|
||||
|
||||
static int attr_lock_get(void *data, u64 *val)
|
||||
{
|
||||
struct tegra_dfll *td = data;
|
||||
|
||||
*val = (td->mode == DFLL_CLOSED_LOOP);
|
||||
|
||||
return 0;
|
||||
}
|
||||
static int attr_lock_set(void *data, u64 val)
|
||||
{
|
||||
struct tegra_dfll *td = data;
|
||||
|
||||
return val ? dfll_lock(td) : dfll_unlock(td);
|
||||
}
|
||||
DEFINE_SIMPLE_ATTRIBUTE(lock_fops, attr_lock_get, attr_lock_set,
|
||||
"%llu\n");
|
||||
|
||||
static int attr_rate_get(void *data, u64 *val)
|
||||
{
|
||||
struct tegra_dfll *td = data;
|
||||
|
@ -685,7 +1138,14 @@ static int attr_rate_get(void *data, u64 *val)
|
|||
|
||||
return 0;
|
||||
}
|
||||
DEFINE_SIMPLE_ATTRIBUTE(rate_fops, attr_rate_get, NULL, "%llu\n");
|
||||
|
||||
static int attr_rate_set(void *data, u64 val)
|
||||
{
|
||||
struct tegra_dfll *td = data;
|
||||
|
||||
return dfll_request_rate(td, val);
|
||||
}
|
||||
DEFINE_SIMPLE_ATTRIBUTE(rate_fops, attr_rate_get, attr_rate_set, "%llu\n");
|
||||
|
||||
static int attr_registers_show(struct seq_file *s, void *data)
|
||||
{
|
||||
|
@ -751,6 +1211,10 @@ static int dfll_debug_init(struct tegra_dfll *td)
|
|||
td->debugfs_dir, td, &enable_fops))
|
||||
goto err_out;
|
||||
|
||||
if (!debugfs_create_file("lock", S_IRUGO,
|
||||
td->debugfs_dir, td, &lock_fops))
|
||||
goto err_out;
|
||||
|
||||
if (!debugfs_create_file("rate", S_IRUGO,
|
||||
td->debugfs_dir, td, &rate_fops))
|
||||
goto err_out;
|
||||
|
@ -782,6 +1246,19 @@ err_out:
|
|||
*/
|
||||
static void dfll_set_default_params(struct tegra_dfll *td)
|
||||
{
|
||||
u32 val;
|
||||
|
||||
val = DIV_ROUND_UP(td->ref_rate, td->sample_rate * 32);
|
||||
BUG_ON(val > DFLL_CONFIG_DIV_MASK);
|
||||
dfll_writel(td, val, DFLL_CONFIG);
|
||||
|
||||
val = (td->force_mode << DFLL_PARAMS_FORCE_MODE_SHIFT) |
|
||||
(td->cf << DFLL_PARAMS_CF_PARAM_SHIFT) |
|
||||
(td->ci << DFLL_PARAMS_CI_PARAM_SHIFT) |
|
||||
(td->cg << DFLL_PARAMS_CG_PARAM_SHIFT) |
|
||||
(td->cg_scale ? DFLL_PARAMS_CG_SCALE : 0);
|
||||
dfll_writel(td, val, DFLL_PARAMS);
|
||||
|
||||
dfll_tune_low(td);
|
||||
dfll_writel(td, td->droop_ctrl, DFLL_DROOP_CTRL);
|
||||
dfll_writel(td, DFLL_MONITOR_CTRL_FREQ, DFLL_MONITOR_CTRL);
|
||||
|
@ -859,6 +1336,8 @@ static int dfll_init(struct tegra_dfll *td)
|
|||
goto di_err2;
|
||||
}
|
||||
|
||||
td->last_unrounded_rate = 0;
|
||||
|
||||
pm_runtime_enable(td->dev);
|
||||
pm_runtime_get_sync(td->dev);
|
||||
|
||||
|
@ -870,6 +1349,8 @@ static int dfll_init(struct tegra_dfll *td)
|
|||
|
||||
dfll_set_open_loop_config(td);
|
||||
|
||||
dfll_init_out_if(td);
|
||||
|
||||
pm_runtime_put_sync(td->dev);
|
||||
|
||||
return 0;
|
||||
|
@ -888,6 +1369,130 @@ di_err1:
|
|||
* DT data fetch
|
||||
*/
|
||||
|
||||
/*
|
||||
* Find a PMIC voltage register-to-voltage mapping for the given voltage.
|
||||
* An exact voltage match is required.
|
||||
*/
|
||||
static int find_vdd_map_entry_exact(struct tegra_dfll *td, int uV)
|
||||
{
|
||||
int i, n_voltages, reg_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)
|
||||
break;
|
||||
|
||||
if (uV == reg_uV)
|
||||
return i;
|
||||
}
|
||||
|
||||
dev_err(td->dev, "no voltage map entry for %d uV\n", uV);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Find a PMIC voltage register-to-voltage mapping for the given voltage,
|
||||
* rounding up to the closest supported voltage.
|
||||
* */
|
||||
static int find_vdd_map_entry_min(struct tegra_dfll *td, int uV)
|
||||
{
|
||||
int i, n_voltages, reg_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)
|
||||
break;
|
||||
|
||||
if (uV <= reg_uV)
|
||||
return i;
|
||||
}
|
||||
|
||||
dev_err(td->dev, "no voltage map entry rounding to %d uV\n", uV);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/**
|
||||
* dfll_build_i2c_lut - build the I2C voltage register lookup table
|
||||
* @td: DFLL instance
|
||||
*
|
||||
* The DFLL hardware has 33 bytes of look-up table RAM that must be filled with
|
||||
* PMIC voltage register values that span the entire DFLL operating range.
|
||||
* This function builds the look-up table based on the OPP table provided by
|
||||
* the soc-specific platform driver (td->soc->opp_dev) and the PMIC
|
||||
* register-to-voltage mapping queried from the regulator framework.
|
||||
*
|
||||
* On success, fills in td->i2c_lut and returns 0, or -err on failure.
|
||||
*/
|
||||
static int dfll_build_i2c_lut(struct tegra_dfll *td)
|
||||
{
|
||||
int ret = -EINVAL;
|
||||
int j, v, v_max, v_opp;
|
||||
int selector;
|
||||
unsigned long rate;
|
||||
struct dev_pm_opp *opp;
|
||||
|
||||
rcu_read_lock();
|
||||
|
||||
rate = ULONG_MAX;
|
||||
opp = dev_pm_opp_find_freq_floor(td->soc->opp_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);
|
||||
|
||||
v = td->soc->min_millivolts * 1000;
|
||||
td->i2c_lut[0] = find_vdd_map_entry_exact(td, v);
|
||||
if (td->i2c_lut[0] < 0)
|
||||
goto out;
|
||||
|
||||
for (j = 1, rate = 0; ; rate++) {
|
||||
opp = dev_pm_opp_find_freq_ceil(td->soc->opp_dev, &rate);
|
||||
if (IS_ERR(opp))
|
||||
break;
|
||||
v_opp = dev_pm_opp_get_voltage(opp);
|
||||
|
||||
if (v_opp <= td->soc->min_millivolts * 1000)
|
||||
td->dvco_rate_min = dev_pm_opp_get_freq(opp);
|
||||
|
||||
for (;;) {
|
||||
v += max(1, (v_max - v) / (MAX_DFLL_VOLTAGES - j));
|
||||
if (v >= v_opp)
|
||||
break;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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 (v >= v_max)
|
||||
break;
|
||||
}
|
||||
td->i2c_lut_size = j;
|
||||
|
||||
if (!td->dvco_rate_min)
|
||||
dev_err(td->dev, "no opp above DFLL minimum voltage %d mV\n",
|
||||
td->soc->min_millivolts);
|
||||
else
|
||||
ret = 0;
|
||||
|
||||
out:
|
||||
rcu_read_unlock();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* read_dt_param - helper function for reading required parameters from the DT
|
||||
* @td: DFLL instance
|
||||
|
@ -911,6 +1516,50 @@ static bool read_dt_param(struct tegra_dfll *td, const char *param, u32 *dest)
|
|||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* dfll_fetch_i2c_params - query PMIC I2C params from DT & regulator subsystem
|
||||
* @td: DFLL instance
|
||||
*
|
||||
* Read all the parameters required for operation in I2C mode. The parameters
|
||||
* can originate from the device tree or the regulator subsystem.
|
||||
* Returns 0 on success or -err on failure.
|
||||
*/
|
||||
static int dfll_fetch_i2c_params(struct tegra_dfll *td)
|
||||
{
|
||||
struct regmap *regmap;
|
||||
struct device *i2c_dev;
|
||||
struct i2c_client *i2c_client;
|
||||
int vsel_reg, vsel_mask;
|
||||
int ret;
|
||||
|
||||
if (!read_dt_param(td, "nvidia,i2c-fs-rate", &td->i2c_fs_rate))
|
||||
return -EINVAL;
|
||||
|
||||
regmap = regulator_get_regmap(td->vdd_reg);
|
||||
i2c_dev = regmap_get_device(regmap);
|
||||
i2c_client = to_i2c_client(i2c_dev);
|
||||
|
||||
td->i2c_slave_addr = i2c_client->addr;
|
||||
|
||||
ret = regulator_get_hardware_vsel_register(td->vdd_reg,
|
||||
&vsel_reg,
|
||||
&vsel_mask);
|
||||
if (ret < 0) {
|
||||
dev_err(td->dev,
|
||||
"regulator unsuitable for DFLL I2C operation\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
td->i2c_reg = vsel_reg;
|
||||
|
||||
ret = dfll_build_i2c_lut(td);
|
||||
if (ret) {
|
||||
dev_err(td->dev, "couldn't build I2C LUT\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* dfll_fetch_common_params - read DFLL parameters from the device tree
|
||||
* @td: DFLL instance
|
||||
|
@ -923,6 +1572,13 @@ static int dfll_fetch_common_params(struct tegra_dfll *td)
|
|||
bool ok = true;
|
||||
|
||||
ok &= read_dt_param(td, "nvidia,droop-ctrl", &td->droop_ctrl);
|
||||
ok &= read_dt_param(td, "nvidia,sample-rate", &td->sample_rate);
|
||||
ok &= read_dt_param(td, "nvidia,force-mode", &td->force_mode);
|
||||
ok &= read_dt_param(td, "nvidia,cf", &td->cf);
|
||||
ok &= read_dt_param(td, "nvidia,ci", &td->ci);
|
||||
ok &= read_dt_param(td, "nvidia,cg", &td->cg);
|
||||
td->cg_scale = of_property_read_bool(td->dev->of_node,
|
||||
"nvidia,cg-scale");
|
||||
|
||||
if (of_property_read_string(td->dev->of_node, "clock-output-names",
|
||||
&td->output_clock_name)) {
|
||||
|
@ -984,6 +1640,10 @@ int tegra_dfll_register(struct platform_device *pdev,
|
|||
return ret;
|
||||
}
|
||||
|
||||
ret = dfll_fetch_i2c_params(td);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
if (!mem) {
|
||||
dev_err(td->dev, "no control register resource\n");
|
||||
|
|
Загрузка…
Ссылка в новой задаче