diff --git a/drivers/clk/tegra/clk-dfll.c b/drivers/clk/tegra/clk-dfll.c index 97cf3c834cf0..445a1b281fc8 100644 --- a/drivers/clk/tegra/clk-dfll.c +++ b/drivers/clk/tegra/clk-dfll.c @@ -29,12 +29,15 @@ */ #include +#include #include #include +#include #include #include #include #include +#include #include #include #include @@ -45,6 +48,11 @@ #include #include #include +#include +#include +#include + +#include #include "clk-dfll.h" #include "cvb.h" @@ -92,7 +100,7 @@ #define DFLL_FREQ_REQ_SCALE_MAX 256 #define DFLL_FREQ_REQ_FREQ_VALID (0x1 << 7) #define DFLL_FREQ_REQ_MULT_SHIFT 0 -#define DFLL_FREQ_REG_MULT_MASK (0x7f << DFLL_FREQ_REQ_MULT_SHIFT) +#define DFLL_FREQ_REQ_MULT_MASK (0x7f << DFLL_FREQ_REQ_MULT_SHIFT) #define FREQ_MAX 127 /* DFLL_DROOP_CTRL: droop prevention control */ @@ -127,7 +135,6 @@ /* DFLL_MONITOR_CTRL: internal monitor data source control */ #define DFLL_MONITOR_CTRL 0x28 -#define DFLL_MONITOR_CTRL_FREQ 6 /* DFLL_MONITOR_DATA: internal monitor data output */ #define DFLL_MONITOR_DATA 0x2c @@ -167,6 +174,14 @@ #define DFLL_INTR_MIN_MASK 0x1 #define DFLL_INTR_MAX_MASK 0x2 +#define DFLL_CC4_HVC 0x74 +#define DFLL_CC4_HVC_CTRL_SHIFT 0 +#define DFLL_CC4_HVC_CTRL_MASK (0x3 << DFLL_CC4_HVC_CTRL_SHIFT) +#define DFLL_CC4_HVC_FORCE_VAL_SHIFT 2 +#define DFLL_CC4_HVC_FORCE_VAL_MASK \ + (OUT_MASK << DFLL_CC4_HVC_FORCE_VAL_SHIFT) +#define DFLL_CC4_HVC_FORCE_EN (0x1 << 8) + /* * Integrated I2C controller registers - relative to td->i2c_controller_base */ @@ -193,14 +208,78 @@ */ #define REF_CLK_CYC_PER_DVCO_SAMPLE 4 +/* + * DFLL_TUNE_HIGH_DELAY: number of microseconds to wait between tests + * to see if the high voltage has been reached yet, during a + * transition from the low-voltage range to the high-voltage range + */ +#define DFLL_TUNE_HIGH_DELAY 2000 + +/* + * DFLL_TUNE_HIGH_MARGIN_STEPS: attempt to initially program the DFLL + * voltage target to a (DFLL_TUNE_HIGH_MARGIN_STEPS * 10 millivolt) + * margin above the high voltage floor, in closed-loop mode in the + * high-voltage range + */ +#define DFLL_TUNE_HIGH_MARGIN_STEPS 3 + +/* + * DFLL_CAP_GUARD_BAND_STEPS: the minimum volage is at least + * DFLL_CAP_GUARD_BAND_STEPS below maximum voltage + */ +#define DFLL_CAP_GUARD_BAND_STEPS 2 + +/* + * I2C_OUTPUT_ACTIVE_TEST_US: mandatory minimum interval (in + * microseconds) between testing whether the I2C controller is + * currently sending a voltage-set command. Some comments list this + * as being a worst-case margin for "disable propagation." + */ +#define I2C_OUTPUT_ACTIVE_TEST_US 2 + /* * REF_CLOCK_RATE: the DFLL reference clock rate currently supported by this * driver, in Hz */ #define REF_CLOCK_RATE 51000000UL +/* + * DFLL_CALIBR_TIME: number of microseconds to wait between tests + * to see if the DVCO rate at Vmin changed. + */ +#define DFLL_CALIBR_TIME 40000 + +/* + * DFLL_ONE_SHOT_SETTLE_TIME: number of microseconds to wait after target + * voltage is set before starting one-shot calibration + */ +#define DFLL_ONE_SHOT_SETTLE_TIME 500 + +/* DFLL_ONE_SHOT_AVG_SAMPLES: number of samples for one-shot calibration */ +#define DFLL_ONE_SHOT_AVG_SAMPLES 5 + +/* DFLL_ONE_SHOT_DELIVERY_RETRY: number of retries for one-shot calibration */ +#define DFLL_ONE_SHOT_DELIVERY_RETRY 4 + +/* DFLL_ONE_SHOT_INVALID_INTERVAL: seconds to invalidate one-shot calibration */ +#define DFLL_ONE_SHOT_INVALID_TIME 864000 /* 10 days */ + #define DVCO_RATE_TO_MULT(rate, ref_rate) ((rate) / ((ref_rate) / 2)) #define MULT_TO_DVCO_RATE(mult, ref_rate) ((mult) * ((ref_rate) / 2)) +#define ROUND_DVCO_MIN_RATE(rate, ref_rate) \ + (DIV_ROUND_UP(rate, (ref_rate) / 2) * ((ref_rate) / 2)) +#define ROUND_DVCO_MAX_RATE(rate, ref_rate) MULT_TO_DVCO_RATE( \ + min(DVCO_RATE_TO_MULT((rate), (ref_rate)), (ulong)FREQ_MAX), (ref_rate)) +#define READ_LAST_I2C_VAL(td) ((dfll_i2c_readl((td), DFLL_I2C_STS) >> \ + DFLL_I2C_STS_I2C_LAST_SHIFT) & OUT_MASK) + +/* + * DT configuration flags + */ +#define DFLL_CALIBRATE_FORCE_VMIN BIT(0) +#define DFLL_DEFER_FORCE_CALIBRATE BIT(1) +#define DFLL_ONE_SHOT_CALIBRATE BIT(2) +#define DFLL_HAS_IDLE_OVERRIDE BIT(3) /** * enum dfll_ctrl_mode - DFLL hardware operating mode @@ -224,6 +303,9 @@ enum dfll_ctrl_mode { * enum dfll_tune_range - voltage range that the driver believes it's in * @DFLL_TUNE_UNINITIALIZED: DFLL tuning not yet programmed * @DFLL_TUNE_LOW: DFLL in the low-voltage range (or open-loop mode) + * @DFLL_TUNE_WAIT_DFLL: waiting for DFLL voltage output to reach high + * @DFLL_TUNE_WAIT_PMIC: waiting for PMIC to react to DFLL output + * @DFLL_TUNE_HIGH: DFLL in the high-voltage range * * Some DFLL tuning parameters may need to change depending on the * DVCO's voltage; these states represent the ranges that the driver @@ -232,7 +314,10 @@ enum dfll_ctrl_mode { */ enum dfll_tune_range { DFLL_TUNE_UNINITIALIZED = 0, - DFLL_TUNE_LOW = 1, + DFLL_TUNE_LOW, + DFLL_TUNE_WAIT_DFLL, + DFLL_TUNE_WAIT_PMIC, + DFLL_TUNE_HIGH, }; @@ -271,13 +356,17 @@ struct tegra_dfll { struct clk *ref_clk; struct clk *i2c_clk; struct clk *dfll_clk; - struct reset_control *dfll_rst; + struct clk *cclk_g_clk; struct reset_control *dvco_rst; unsigned long ref_rate; unsigned long i2c_clk_rate; unsigned long dvco_rate_min; + unsigned long dvco_calibration_max; + unsigned long out_rate_min; + unsigned long out_rate_max; enum dfll_ctrl_mode mode; + enum dfll_ctrl_mode resume_mode; enum dfll_tune_range tune_range; struct dentry *debugfs_dir; struct clk_hw dfll_clk_hw; @@ -285,14 +374,20 @@ struct tegra_dfll { struct dfll_rate_req last_req; unsigned long last_unrounded_rate; + struct hrtimer tune_timer; + ktime_t tune_delay; + ktime_t tune_ramp_delay; + /* Parameters from DT */ u32 droop_ctrl; u32 sample_rate; u32 force_mode; u32 cf; u32 ci; - u32 cg; + s32 cg; bool cg_scale; + u32 reg_init_uV; + u32 cfg_flags; /* I2C interface parameters */ u32 i2c_fs_rate; @@ -301,9 +396,18 @@ struct tegra_dfll { /* lut array entries are regulator framework selectors or PWM values*/ unsigned lut[MAX_DFLL_VOLTAGES]; - unsigned long lut_uv[MAX_DFLL_VOLTAGES]; + unsigned lut_uv[MAX_DFLL_VOLTAGES]; int lut_size; u8 lut_bottom, lut_min, lut_max, lut_safe; + u8 lut_force_min; + + /* tuning parameters */ + u8 tune_high_out_start; + u8 tune_high_out_min; + u8 tune_out_last; + unsigned long *tune_high_dvco_rate_floors; + unsigned long tune_high_target_rate_min; + bool tune_high_calibrated; /* PWM interface */ enum tegra_dfll_pmu_if pmu_if; @@ -311,10 +415,48 @@ struct tegra_dfll { struct pinctrl *pwm_pin; struct pinctrl_state *pwm_enable_state; struct pinctrl_state *pwm_disable_state; - u32 reg_init_uV; + + /* spinlock protecting register accesses */ + spinlock_t lock; + + /* Vmin set from external rail connected to dfll */ + unsigned int external_floor_output; + + /* Thermal parameters */ + unsigned int thermal_floor_output; + unsigned int thermal_floor_index; + unsigned int thermal_cap_output; + unsigned int thermal_cap_index; + unsigned long *dvco_rate_floors; + bool dvco_cold_floor_done; + + /* PMIC undershoot */ + int pmu_undershoot_gb; + + /* Vmin calibration */ + struct timer_list calibration_timer; + unsigned long calibration_delay; + ktime_t last_calibration; + unsigned long calibration_range_min; + unsigned long calibration_range_max; + u32 one_shot_settle_time; + ktime_t one_shot_invalid_time_start; + int one_shot_invalid_time; + + /* Child cclk_g rate change notifier */ + struct notifier_block cclk_g_parent_nb; }; +enum dfll_monitor_mode { + DFLL_OUTPUT_VALUE = 5, + DFLL_FREQ = 6, +}; + +static struct tegra_dfll *tegra_dfll_dev; + #define clk_hw_to_dfll(_hw) container_of(_hw, struct tegra_dfll, dfll_clk_hw) +#define cclk_g_nb_to_dfll(_nb) \ + container_of(_nb, struct tegra_dfll, cclk_g_parent_nb) /* mode_name: map numeric DFLL modes to names for friendly console messages */ static const char * const mode_name[] = { @@ -324,6 +466,10 @@ static const char * const mode_name[] = { [DFLL_CLOSED_LOOP] = "closed_loop", }; +static void dfll_load_i2c_lut(struct tegra_dfll *td); +static u8 find_mv_out_cap(struct tegra_dfll *td, int mv); +static u8 find_mv_out_floor(struct tegra_dfll *td, int mv); + /* * Register accessors */ @@ -335,7 +481,7 @@ static inline u32 dfll_readl(struct tegra_dfll *td, u32 offs) static inline void dfll_writel(struct tegra_dfll *td, u32 val, u32 offs) { - WARN_ON(offs >= DFLL_I2C_CFG); + WARN_ON(offs >= DFLL_I2C_CFG && offs <= DFLL_I2C_STS); __raw_writel(val, td->base + offs); } @@ -361,6 +507,70 @@ static inline void dfll_i2c_wmb(struct tegra_dfll *td) dfll_i2c_readl(td, DFLL_I2C_CFG); } +static inline void dfll_set_monitor_mode(struct tegra_dfll *td, + enum dfll_monitor_mode mode) +{ + dfll_writel(td, mode, DFLL_MONITOR_CTRL); + dfll_wmb(td); + udelay(1); +} + +/** + * is_output_i2c_req_pending - is an I2C voltage-set command in progress? + * @pdev: DFLL instance + * + * Returns 1 if an I2C request is in progress, or 0 if not. The DFLL + * IP block requires two back-to-back reads of the I2C_REQ_PENDING + * field to return 0 before the software can be sure that no I2C + * request is currently pending. Also, a minimum time interval + * between DFLL_I2C_STS reads is required by the IP block. + */ +static int is_output_i2c_req_pending(struct tegra_dfll *td) +{ + u32 sts; + + if (td->pmu_if == TEGRA_DFLL_PMU_PWM) + return 0; + + sts = dfll_i2c_readl(td, DFLL_I2C_STS); + if (sts & DFLL_I2C_STS_I2C_REQ_PENDING) + return 1; + + udelay(I2C_OUTPUT_ACTIVE_TEST_US); + + sts = dfll_i2c_readl(td, DFLL_I2C_STS); + if (sts & DFLL_I2C_STS_I2C_REQ_PENDING) + return 1; + + return 0; +} + +static u8 dfll_get_output_min(struct tegra_dfll *td) +{ + u32 tune_min; + + tune_min = td->tune_range == DFLL_TUNE_LOW ? + td->lut_bottom : td->tune_high_out_min; + return max_t(unsigned int, max(tune_min, td->thermal_floor_output), + td->external_floor_output); +} + +static void set_force_out_min(struct tegra_dfll *td) +{ + u8 lut_force_min; + int force_mv_min = td->pmu_undershoot_gb; + + if (!force_mv_min) + return; + + lut_force_min = dfll_get_output_min(td); + force_mv_min += td->lut_uv[lut_force_min] / 1000; + lut_force_min = find_mv_out_cap(td, force_mv_min); + if (lut_force_min == td->lut_safe) + lut_force_min++; + td->lut_force_min = lut_force_min; +} + /** * dfll_is_running - is the DFLL currently generating a clock? * @td: DFLL instance @@ -435,6 +645,52 @@ int tegra_dfll_runtime_suspend(struct device *dev) } EXPORT_SYMBOL(tegra_dfll_runtime_suspend); +/** + * dfll_set_output_limits - set DFLL output min and max limits + * @td: DFLL instance + * @out_min: min lut index + * @out_max: max lut index + * + * Set the minimum and maximum lut index into DFLL output config + * register. + */ +static void dfll_set_output_limits(struct tegra_dfll *td, + u32 out_min, u32 out_max) +{ + u32 val; + + td->lut_min = out_min; + td->lut_max = out_max; + + if (td->pmu_if == TEGRA_DFLL_PMU_PWM) + val = dfll_readl(td, DFLL_OUTPUT_CFG); + else + val = dfll_i2c_readl(td, DFLL_OUTPUT_CFG); + + val &= ~DFLL_OUTPUT_CFG_MAX_MASK & + ~DFLL_OUTPUT_CFG_MIN_MASK; + + val |= (td->lut_max << DFLL_OUTPUT_CFG_MAX_SHIFT) | + (td->lut_min << DFLL_OUTPUT_CFG_MIN_SHIFT); + + if (td->pmu_if == TEGRA_DFLL_PMU_PWM) { + dfll_writel(td, val, DFLL_OUTPUT_CFG); + dfll_wmb(td); + } else { + dfll_i2c_writel(td, val, DFLL_OUTPUT_CFG); + dfll_i2c_wmb(td); + } + + if (td->cfg_flags & DFLL_HAS_IDLE_OVERRIDE) { + /* Override mode force value follows active mode Vmin */ + val = dfll_readl(td, DFLL_CC4_HVC); + val &= ~DFLL_CC4_HVC_FORCE_VAL_MASK; + val |= td->lut_min << DFLL_CC4_HVC_FORCE_VAL_SHIFT; + dfll_writel(td, val, DFLL_CC4_HVC); + dfll_wmb(td); + } +} + /* * DFLL tuning operations (per-voltage-range tuning settings) */ @@ -451,30 +707,296 @@ static void dfll_tune_low(struct tegra_dfll *td) { td->tune_range = DFLL_TUNE_LOW; - dfll_writel(td, td->soc->cvb->cpu_dfll_data.tune0_low, DFLL_TUNE0); - dfll_writel(td, td->soc->cvb->cpu_dfll_data.tune1_low, DFLL_TUNE1); + dfll_writel(td, td->soc->tune0_low, DFLL_TUNE0); + dfll_writel(td, td->soc->tune1_low, DFLL_TUNE1); dfll_wmb(td); if (td->soc->set_clock_trimmers_low) td->soc->set_clock_trimmers_low(); } -/* - * Output clock scaler helpers +/** + * dfll_tune_high - tune DFLL and CPU clock shaper for high voltages + * @td: DFLL instance + * + * Tune the DFLL oscillator parameters and the CPU clock shaper + * for the high-voltage range. The bottom end of the high-voltage + * range is represented by the index td->soc->tune_high_min_millivolts. + * Used in closed-loop mode. No return value. */ +static void dfll_tune_high(struct tegra_dfll *td) +{ + u32 tune0_high; + u32 tune1_high; + + td->tune_range = DFLL_TUNE_HIGH; + + tune0_high = td->soc->tune0_high; + if (!tune0_high) + tune0_high = td->soc->tune0_low; + dfll_writel(td, tune0_high, DFLL_TUNE0); + + tune1_high = td->soc->tune1_high; + if (!tune1_high) + tune1_high = td->soc->tune1_low; + dfll_writel(td, tune1_high, DFLL_TUNE1); + + dfll_wmb(td); + + if (td->soc->set_clock_trimmers_high) + td->soc->set_clock_trimmers_high(); +} + +static enum dfll_tune_range dfll_tune_target(struct tegra_dfll *td, + unsigned long rate) +{ + if (rate >= td->tune_high_target_rate_min) + return DFLL_TUNE_HIGH; + + return DFLL_TUNE_LOW; +} /** - * dfll_scale_dvco_rate - calculate scaled rate from the DVCO rate - * @scale_bits: clock scaler value (bits in the DFLL_FREQ_REQ_SCALE field) - * @dvco_rate: the DVCO rate + * dfll_set_open_loop_config - prepare to switch to open-loop mode + * @td: DFLL instance * - * Apply the same scaling formula that the DFLL hardware uses to scale - * the DVCO rate. + * Prepare to switch the DFLL to open-loop mode. This switches the + * DFLL to the low-voltage tuning range, ensures that I2C output + * forcing is disabled, and disables the output clock rate scaler. + * The DFLL's low-voltage tuning range parameters must be + * characterized to keep the downstream device stable at any DVCO + * input voltage. No return value. */ -static unsigned long dfll_scale_dvco_rate(int scale_bits, - unsigned long dvco_rate) +static void dfll_set_open_loop_config(struct tegra_dfll *td) { - return (u64)dvco_rate * (scale_bits + 1) / DFLL_FREQ_REQ_SCALE_MAX; + u32 val; + u32 out_min, out_max; + + out_min = td->lut_min; + out_max = td->lut_max; + /* always tune low (safe) in open loop */ + if (td->tune_range != DFLL_TUNE_LOW) { + dfll_tune_low(td); + out_min = dfll_get_output_min(td); + } + + dfll_set_output_limits(td, out_min, out_max); + + val = dfll_readl(td, DFLL_FREQ_REQ); + val |= DFLL_FREQ_REQ_SCALE_MASK; + val &= ~DFLL_FREQ_REQ_FORCE_ENABLE; + dfll_writel(td, val, DFLL_FREQ_REQ); + dfll_wmb(td); +} + +/** + * dfll_set_close_loop_config - prepare to switch to closed-loop mode + * @pdev: DFLL instance + * @req: requested output rate + * + * Prepare to switch the DFLL to closed-loop mode. This involves + * switching the DFLL's tuning voltage regime (if necessary), and + * rewriting the LUT to restrict the minimum and maximum voltages. No + * return value. + */ +static void dfll_set_close_loop_config(struct tegra_dfll *td, + struct dfll_rate_req *req) +{ + bool sample_tune_out_last = false; + u8 out_min, out_max; + + switch (td->tune_range) { + case DFLL_TUNE_LOW: + if (dfll_tune_target(td, req->rate) > DFLL_TUNE_LOW) { + td->tune_range = DFLL_TUNE_WAIT_DFLL; + if (!timekeeping_suspended) + hrtimer_start(&td->tune_timer, td->tune_delay, + HRTIMER_MODE_REL); + set_force_out_min(td); + sample_tune_out_last = true; + } + break; + + case DFLL_TUNE_HIGH: + case DFLL_TUNE_WAIT_DFLL: + case DFLL_TUNE_WAIT_PMIC: + if (dfll_tune_target(td, req->rate) == DFLL_TUNE_LOW) + dfll_tune_low(td); + set_force_out_min(td); + break; + default: + BUG(); + } + + out_min = dfll_get_output_min(td); + + if (td->thermal_cap_output > out_min + DFLL_CAP_GUARD_BAND_STEPS) + out_max = td->thermal_cap_output; + else + out_max = out_min + DFLL_CAP_GUARD_BAND_STEPS; + out_max = max(out_max, td->lut_force_min); + + if ((td->lut_min != out_min) || (td->lut_max != out_max)) + dfll_set_output_limits(td, out_min, out_max); + + /* Must be sampled after new out_min is set */ + if (sample_tune_out_last && (td->pmu_if == TEGRA_DFLL_PMU_I2C)) + td->tune_out_last = READ_LAST_I2C_VAL(td); +} + +/** + * dfll_tune_timer_cb - timer callback while tuning low to high + * @data: struct platform_device * of the DFLL instance + * + * Timer callback, used when switching from TUNE_LOW to TUNE_HIGH in + * closed-loop mode. Waits for DFLL I2C voltage command output to + * reach tune_high_out_min, then waits for the PMIC to react to the + * command. No return value. + */ +static enum hrtimer_restart dfll_tune_timer_cb(struct hrtimer *timer) +{ + struct tegra_dfll *td; + u32 out_min, out_last; + bool use_ramp_delay; + unsigned long flags; + + td = container_of(timer, struct tegra_dfll, tune_timer); + + spin_lock_irqsave(&td->lock, flags); + + if (td->tune_range == DFLL_TUNE_WAIT_DFLL) { + out_min = td->lut_min; + + use_ramp_delay = !is_output_i2c_req_pending(td); + + if (td->pmu_if == TEGRA_DFLL_PMU_I2C) { + out_last = READ_LAST_I2C_VAL(td); + } else { + out_last = out_min; + } + use_ramp_delay |= td->tune_out_last != out_last; + + if (use_ramp_delay && + (out_last >= td->tune_high_out_min) && + (out_min >= td->tune_high_out_min)) { + td->tune_range = DFLL_TUNE_WAIT_PMIC; + hrtimer_start(&td->tune_timer, td->tune_ramp_delay, + HRTIMER_MODE_REL); + } else { + hrtimer_start(&td->tune_timer, td->tune_delay, + HRTIMER_MODE_REL); + } + } else if (td->tune_range == DFLL_TUNE_WAIT_PMIC) { + dfll_tune_high(td); + } + pr_debug("%s: dvco tuning state %d\n", __func__, td->tune_range); + + spin_unlock_irqrestore(&td->lock, flags); + + return HRTIMER_NORESTART; +} + +/* + * 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 min_uv, 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; +} + +static unsigned long get_dvco_rate_above(struct tegra_dfll *td, u8 out_min) +{ + struct dev_pm_opp *opp; + unsigned long rate; + int uv, min_uv; + + min_uv = td->lut_uv[out_min]; + for (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 rate; + } + + return rate ? --rate : 0; +} + +/** + * set_dvco_rate_min - set the minimum DVCO output rate & interval + * @td: DFLL instance + * + * Find and cache the "minimum" DVCO output frequency. No return value. + */ +static void set_dvco_rate_min(struct tegra_dfll *td, struct dfll_rate_req *req) +{ + unsigned long rate; + unsigned long tune_high_range_min = 0; + unsigned long range = 32 * (td->ref_rate / 2); + + rate = td->dvco_rate_floors[td->thermal_floor_index]; + if (!rate) { + if (td->thermal_floor_index + < td->soc->thermal_floor_table_size) + rate = get_dvco_rate_below(td, + td->thermal_floor_output); + else + rate = td->out_rate_min; + } + + if (dfll_tune_target(td, req->rate) > DFLL_TUNE_LOW) { + unsigned int s = td->soc->thermal_floor_table_size; + unsigned long tune_floor = + td->tune_high_dvco_rate_floors[td->thermal_floor_index]; + + tune_floor = tune_floor ? : td->tune_high_dvco_rate_floors[s]; + rate = max(rate, tune_floor); + tune_high_range_min = td->tune_high_target_rate_min; + } + + /* round minimum rate to request unit (ref_rate/2) boundary */ + td->dvco_rate_min = ROUND_DVCO_MIN_RATE(rate, td->ref_rate); + pr_debug("%s: dvco rate min = %lu\n", __func__, td->dvco_rate_min); + + /* set symmetrical calibration boundaries */ + td->calibration_range_min = td->dvco_rate_min > range ? + td->dvco_rate_min - range : 0; + if (td->calibration_range_min < tune_high_range_min) + td->calibration_range_min = tune_high_range_min; + if (td->calibration_range_min < td->out_rate_min) + td->calibration_range_min = td->out_rate_min; + + td->calibration_range_max = td->dvco_rate_min + range; + if (td->calibration_range_max > td->dvco_calibration_max) + td->calibration_range_max = td->dvco_calibration_max; } /* @@ -494,37 +1016,22 @@ static void dfll_set_mode(struct tegra_dfll *td, { td->mode = mode; dfll_writel(td, mode - 1, DFLL_CTRL); - dfll_wmb(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; - unsigned long uv, min_uv; - - min_uv = td->lut_uv[out_min]; - for (rate = 0, prev_rate = 0; ; rate++) { - opp = dev_pm_opp_find_freq_ceil(td->soc->dev, &rate); - if (IS_ERR(opp)) - break; - - uv = dev_pm_opp_get_voltage(opp); - dev_pm_opp_put(opp); - - if (uv && uv > min_uv) - return prev_rate; - - prev_rate = rate; + if (td->cfg_flags & DFLL_HAS_IDLE_OVERRIDE) { + /* Override mode follows active mode up to open loop */ + u32 val = dfll_readl(td, DFLL_CC4_HVC); + val &= ~(DFLL_CC4_HVC_CTRL_MASK | DFLL_CC4_HVC_FORCE_EN); + if (mode >= DFLL_OPEN_LOOP) { + val |= DFLL_OPEN_LOOP - 1; + val |= DFLL_CC4_HVC_FORCE_EN; + } + dfll_writel(td, val, DFLL_CC4_HVC); } - - return prev_rate; + dfll_wmb(td); + udelay(1); } + /* * DFLL-to-I2C controller interface */ @@ -554,7 +1061,6 @@ static int dfll_i2c_set_output_enabled(struct tegra_dfll *td, bool enable) return 0; } - /* * DFLL-to-PWM controller interface */ @@ -566,7 +1072,7 @@ static int dfll_i2c_set_output_enabled(struct tegra_dfll *td, bool enable) * * 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. + * to the approriate state. */ static int dfll_pwm_set_output_enabled(struct tegra_dfll *td, bool enable) { @@ -609,7 +1115,7 @@ static int dfll_pwm_set_output_enabled(struct tegra_dfll *td, bool enable) * @td: DFLL instance * @out_val: value to force output * - * Set the fixed value for force output, DFLL will output this value when + * 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) @@ -644,11 +1150,12 @@ static void dfll_set_force_output_enabled(struct tegra_dfll *td, bool enable) } /** - * dfll_force_output - force output a fixed value + * dfll_i2c_set_output_enabled - enable/disable I2C PMIC voltage requests * @td: DFLL instance - * @out_sel: value to force output + * @enable: whether to enable or disable the I2C voltage requests * - * Set the fixed value for force output, DFLL will output this value. + * 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) { @@ -666,8 +1173,439 @@ static int dfll_force_output(struct tegra_dfll *td, unsigned int out_sel) return 0; } +/* + * Reading monitor data concurrently with the update may render intermediate + * (neither "old" nor "new") values. Synchronization with the "rising edge" + * of DATA_NEW makes it very unlikely, but still possible. Use simple filter: + * compare 2 consecutive readings for data consistency within 2 LSb range. + * Return error otherwise. On the platform that does not allow to use DATA_NEW + * at all check for consistency of consecutive reads is the only protection. + */ + +static int dfll_wait_monitor_data(struct tegra_dfll *td, u32 *reg) +{ + int sample_period; + + sample_period = DIV_ROUND_UP(1000000, td->sample_rate); + + return readl_relaxed_poll_timeout_atomic(td->base + DFLL_MONITOR_DATA, + *reg, + *reg & DFLL_MONITOR_DATA_NEW_MASK, 1, + sample_period * 2); +} + +static int dfll_get_monitor_data(struct tegra_dfll *td, u32 *reg) +{ + u32 val; + + dfll_wait_monitor_data(td, reg); + *reg &= DFLL_MONITOR_DATA_VAL_MASK; + + val = dfll_readl(td, DFLL_MONITOR_DATA) & DFLL_MONITOR_DATA_VAL_MASK; + if (abs(*reg - val) <= 2) + return 0; + + *reg = dfll_readl(td, DFLL_MONITOR_DATA) & DFLL_MONITOR_DATA_VAL_MASK; + if (abs(*reg - val) <= 2) + return 0; + + return -EINVAL; +} + /** - * dfll_load_i2c_lut - load the voltage lookup table + * dfll_calc_monitored_rate - convert DFLL_MONITOR_DATA_VAL rate into real freq + * @monitor_data: value read from the DFLL_MONITOR_DATA_VAL bitfield + * @ref_rate: DFLL reference clock rate + * + * Convert @monitor_data from DFLL_MONITOR_DATA_VAL units into cycles + * per second. Returns the converted value. + */ +static u64 dfll_calc_monitored_rate(u32 monitor_data, + unsigned long ref_rate) +{ + return monitor_data * (ref_rate / REF_CLK_CYC_PER_DVCO_SAMPLE); +} + +/* + * Calibrate DFLL minimum rate + */ +static inline void calibration_timer_update(struct tegra_dfll *td) +{ + /* + * Forced output must be disabled in closed loop mode outside of + * calibration. It may be temporarily enabled during calibration; + * use timer update to clean up. + */ + if (td->calibration_delay) { + dfll_set_force_output_enabled(td, false); + mod_timer(&td->calibration_timer, + jiffies + td->calibration_delay + 1); + } +} + +static void dfll_invalidate_cold_floor(struct tegra_dfll *td) +{ + if (!td->dvco_cold_floor_done && !td->thermal_floor_index && + (td->mode == DFLL_CLOSED_LOOP)) { + td->dvco_cold_floor_done = true; + td->dvco_rate_floors[0] = 0; + td->tune_high_dvco_rate_floors[0] = 0; + } +} + +static void dfll_invalidate_one_shot(struct tegra_dfll *td) +{ + int i; + + for (i = 0; i < td->soc->thermal_floor_table_size; i++) { + td->dvco_rate_floors[i] = 0; + td->tune_high_dvco_rate_floors[i] = 0; + } + td->dvco_rate_floors[i] = 0; + td->tune_high_calibrated = false; + td->dvco_cold_floor_done = false; +} + +/* + * Opportunistic calibrate implements s/w closed loop that updates calibrate + * targets if DFLL is already at floor voltage because of low frequency request. + */ +static void dfll_calibrate(struct tegra_dfll *td) +{ + u32 val, data; + ktime_t now; + unsigned long rate; + unsigned long step = td->ref_rate / 2; + unsigned long rate_min = td->dvco_rate_min; + u8 out_min = dfll_get_output_min(td); + + if (!td->calibration_delay || (td->cfg_flags & DFLL_ONE_SHOT_CALIBRATE)) + return; + /* + * Enter calibration procedure only if + * - closed loop operations + * - last request engaged clock skipper + * - at least specified time after the last calibration attempt + */ + if ((td->mode != DFLL_CLOSED_LOOP) || + (td->last_req.dvco_target_rate > rate_min)) + return; + + now = ktime_get(); + if (ktime_us_delta(now, td->last_calibration) < + jiffies_to_usecs(td->calibration_delay)) + return; + + td->last_calibration = now; + + /* Defer calibration if in the middle of tuning transition */ + if ((td->tune_range > DFLL_TUNE_LOW) && + (td->tune_range < DFLL_TUNE_HIGH)) { + calibration_timer_update(td); + return; + } + + /* Defer calibration if forced output was left enabled */ + val = dfll_readl(td, DFLL_OUTPUT_FORCE); + if (val & DFLL_OUTPUT_FORCE_ENABLE) { + calibration_timer_update(td); + return; + } + + /* + * Check if we need to force minimum output during calibration. + * + * Considerations for selecting TEGRA_CL_DVFS_CALIBRATE_FORCE_VMIN. + * - if there is no voltage enforcement underneath this driver, no need + * to select defer option. + * + * - if SoC has internal pm controller that controls voltage while CPU + * cluster is idle, and restores force_val on idle exit, the following + * trade-offs applied: + * + * a) force: DVCO calibration is accurate, but calibration time is + * increased by 2 sample periods and target module maybe under-clocked + * during that time, + * b) don't force: calibration results depend on whether flag + * TEGRA_CL_DVFS_DEFER_FORCE_CALIBRATE is set -- see description below. + */ + if (td->cfg_flags & DFLL_CALIBRATE_FORCE_VMIN) { + int delay = 2 * DIV_ROUND_UP(1000000, td->sample_rate); + dfll_set_force_output_value(td, out_min); + dfll_set_force_output_enabled(td, true); + udelay(delay); + } + + /* Synchronize with sample period, and get rate measurements */ + dfll_set_monitor_mode(td, DFLL_FREQ); + + /* Defer calibration if data reading is not consistent */ + dfll_get_monitor_data(td, &data); + if (dfll_get_monitor_data(td, &data) < 0) { + calibration_timer_update(td); + return; + } + + /* Get output (voltage) measurements */ + if (td->pmu_if == TEGRA_DFLL_PMU_I2C) { + /* Defer calibration if I2C transaction is pending */ + val = dfll_i2c_readl(td, DFLL_I2C_STS); + if (val & DFLL_I2C_STS_I2C_REQ_PENDING) { + calibration_timer_update(td); + return; + } + val = (val >> DFLL_I2C_STS_I2C_LAST_SHIFT) & OUT_MASK; + } else if (td->cfg_flags & DFLL_CALIBRATE_FORCE_VMIN) { + /* Use forced value (cannot read it back from PWM interface) */ + val = out_min; + } else { + /* Get last output (there is no such thing as pending PWM) */ + /* Defer calibration if data reading is not consistent */ + dfll_set_monitor_mode(td, DFLL_OUTPUT_VALUE); + if (dfll_get_monitor_data(td, &val) < 0) { + calibration_timer_update(td); + return; + } + } + + if (td->cfg_flags & DFLL_CALIBRATE_FORCE_VMIN) { + /* Defer calibration if forced and read outputs do not match */ + if (val != out_min) { + calibration_timer_update(td); + return; + } + dfll_set_force_output_enabled(td, false); + } + + /* + * Check if we need to defer calibration when voltage is matching + * request force_val. + * + * Considerations for selecting TEGRA_CL_DVFS_DEFER_FORCE_CALIBRATE. + * - if there is no voltage enforcement underneath this driver, no need + * to select defer option. + * + * - if SoC has internal pm controller that controls voltage while CPU + * cluster is idle, and restores force_val on idle exit, the following + * trade-offs applied: + * + * a) defer: DVCO minimum maybe slightly over-estimated, all frequencies + * below DVCO minimum are skipped-to accurately, but voltage at low + * frequencies would fluctuate between Vmin and Vmin + 1 LUT/PWM step. + * b) don't defer: DVCO minimum rate is underestimated, maybe down to + * calibration_range_min, respectively actual frequencies below DVCO + * minimum are configured higher than requested, but voltage at low + * frequencies is saturated at Vmin. + */ + if ((val == td->last_req.lut_index) && + (td->cfg_flags & DFLL_DEFER_FORCE_CALIBRATE)) { + calibration_timer_update(td); + return; + } + + /* Adjust minimum rate */ + rate = dfll_calc_monitored_rate(data, td->ref_rate); + if ((val > out_min) || (rate < (rate_min - step))) + rate_min -= step; + else if (rate > (rate_min + step)) + rate_min += step; + else { + int t_floor_out, t_floor_idx = td->thermal_floor_index; + struct thermal_tv tv; + + if ((td->tune_range == DFLL_TUNE_HIGH) && + (td->tune_high_out_min == out_min)) { + td->tune_high_dvco_rate_floors[t_floor_idx] = rate_min; + td->tune_high_calibrated = true; + return; + } + + tv = td->soc->thermal_floor_table[t_floor_idx]; + t_floor_out = find_mv_out_cap(td, tv.millivolts); + if (t_floor_out == out_min) { + td->dvco_rate_floors[t_floor_idx] = rate_min; + return; + } + calibration_timer_update(td); + return; + } + + td->dvco_rate_min = clamp(rate_min, + td->calibration_range_min, td->calibration_range_max); + calibration_timer_update(td); + pr_debug("%s: calibrated dvco_rate_min %lu (%lu), measured %lu\n", + __func__, td->dvco_rate_min, rate_min, rate); +} + +/* + * One-shot calibrate forces and calibrates each target floor once when DFLL is + * locked or when temperature crosses thermal range threshold. + */ +static bool is_out_target_delivered(struct tegra_dfll *td, u8 out_target) +{ + int i; + u8 out_start, out_cur; + + if (td->pmu_if != TEGRA_DFLL_PMU_I2C) + return true; + + out_cur = out_start = READ_LAST_I2C_VAL(td); + + /* + * Make sure that I2C transaction that might be in flight when this + * function is called is completed, and last sent I2C value matches + * the target. + */ + for (i = 0; i < DFLL_ONE_SHOT_DELIVERY_RETRY; i++) { + if (!is_output_i2c_req_pending(td) || (out_cur != out_start)) { + out_cur = READ_LAST_I2C_VAL(td); + if (out_cur == out_target) + return true; + } + udelay(DIV_ROUND_UP(1000000, td->sample_rate)); + out_cur = READ_LAST_I2C_VAL(td); + } + pr_debug("%s: delivery of dvco out target %u failed (i2c val %u)\n", + __func__, out_target, out_cur); + return false; +} + +static long dfll_one_shot_calibrate_mv(struct tegra_dfll *td, int mv, + bool tune_high) +{ + int i, n = 0; + u32 data, avg_data = 0; + long rate; + u8 out_min = find_mv_out_cap(td, mv); + + /* Set calibration target voltage */ + dfll_set_force_output_value(td, out_min); + dfll_set_force_output_enabled(td, true); + + /* Switch to rate measurement, and synchronize with sample period */ + dfll_set_monitor_mode(td, DFLL_FREQ); + dfll_get_monitor_data(td, &data); + + /* Confirm voltage is delivered and settled */ + if (!is_out_target_delivered(td, out_min)) { + rate = -EBUSY; + goto _out; + } + udelay(td->one_shot_settle_time); + + /* Tune high during calibration */ + if (tune_high) + dfll_tune_high(td); + + /* Average measurements. Take the last one "as is" if all unstable */ + for (i = 0; i < DFLL_ONE_SHOT_AVG_SAMPLES; i++) { + if (dfll_get_monitor_data(td, &data) < 0) { + if ((i + 1 < DFLL_ONE_SHOT_AVG_SAMPLES) || n) + continue; + dev_err(td->dev, "%s: use unstable monitor output %u\n", + __func__, data); + } + avg_data += data; + n++; + } + + /* Restore low tuning after calibration */ + if (tune_high) + dfll_tune_low(td); + + /* Get average monitor rate rounded to request unit (=2*monitor unit) */ + avg_data = DIV_ROUND_CLOSEST(avg_data, n) / 2; + rate = MULT_TO_DVCO_RATE(avg_data, td->ref_rate); + pr_debug("%s: calibrated dvco_rate_min %lu at %d mV over %d samples\n", + __func__, rate, mv, n); +_out: + dfll_set_force_output_enabled(td, false); + return rate; +} + +static bool dfll_one_shot_calibrate_floors(struct tegra_dfll *td) +{ + bool ret = false; + int mv, therm_mv = 0, tune_mv = 0; + int i = td->thermal_floor_index; + long rate; + enum dfll_tune_range range = td->tune_range; + + if (!(td->cfg_flags & DFLL_ONE_SHOT_CALIBRATE) || + (td->mode != DFLL_CLOSED_LOOP)) + return ret; + + /* Don't calibrate in range transition */ + if (((range > DFLL_TUNE_LOW) && (range < DFLL_TUNE_HIGH)) || + timekeeping_suspended) { + calibration_timer_update(td); + return ret; + } + + if (td->soc->thermal_floor_table_size && + (i < td->soc->thermal_floor_table_size)) + therm_mv = td->soc->thermal_floor_table[i].millivolts; + + if (td->tune_high_target_rate_min != ULONG_MAX) + tune_mv = td->lut_uv[td->tune_high_out_min] / 1000; + + /* Thermal floors */ + if (therm_mv && !td->dvco_rate_floors[i]) { + if ((range == DFLL_TUNE_LOW) || (therm_mv >= tune_mv)) { + rate = dfll_one_shot_calibrate_mv(td, therm_mv, false); + if (!IS_ERR_VALUE(rate)) { + td->dvco_rate_floors[i] = + clamp((unsigned long)rate, td->out_rate_min, + td->dvco_calibration_max); + ret = true; + } + calibration_timer_update(td); + return ret; + } + } + + /* Tune high Vmin if specified */ + if (tune_mv && (!td->tune_high_calibrated || + !td->tune_high_dvco_rate_floors[i])) { + if (tune_mv >= therm_mv) { + rate = dfll_one_shot_calibrate_mv( + td, tune_mv, range == DFLL_TUNE_LOW); + if (!IS_ERR_VALUE(rate)) { + td->tune_high_dvco_rate_floors[i] = + clamp((unsigned long)rate, + td->tune_high_target_rate_min, + td->dvco_calibration_max); + td->tune_high_calibrated = true; + ret = true; + } + calibration_timer_update(td); + return ret; + } + } + + /* Absolute Vmin matters only when no thermal floors */ + if (!therm_mv) { + i = td->soc->thermal_floor_table_size; + if (!td->dvco_rate_floors[i] && (range == DFLL_TUNE_LOW)) { + mv = td->lut_uv[td->lut_bottom] / 1000; + rate = dfll_one_shot_calibrate_mv(td, mv, false); + if (!IS_ERR_VALUE(rate)) { + td->dvco_rate_floors[i] = + clamp((unsigned long)rate, + td->out_rate_min, + td->dvco_calibration_max); + ret = true; + } + calibration_timer_update(td); + return ret; + } + } + + return ret; +} + +/** + * dfll_load_lut - load the voltage lookup table * @td: struct tegra_dfll * * * Load the voltage-to-PMIC register value lookup table into the DFLL @@ -679,10 +1617,10 @@ 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; @@ -741,30 +1679,58 @@ static void dfll_init_i2c_if(struct tegra_dfll *td) static void dfll_init_out_if(struct tegra_dfll *td) { u32 val; + int index, mv; - td->lut_min = td->lut_bottom; - td->lut_max = td->lut_size - 1; + td->external_floor_output = 0; + td->thermal_floor_output = 0; + if (td->soc->thermal_floor_table_size) { + index = 0; + mv = td->soc->thermal_floor_table[index].millivolts; + td->thermal_floor_output = find_mv_out_cap(td, mv); + td->thermal_floor_index = index; + } + + td->thermal_cap_output = td->lut_size - 1; + if (td->soc->thermal_cap_table_size) { + index = td->soc->thermal_cap_table_size - 1; + mv = td->soc->thermal_cap_table[index].millivolts; + td->thermal_cap_output = find_mv_out_floor(td, mv); + td->thermal_cap_index = index; + } + + set_dvco_rate_min(td, &td->last_req); + set_force_out_min(td); + if (td->soc->cvb->cpu_dfll_data.dvco_calibration_max) + td->dvco_calibration_max = + ROUND_DVCO_MAX_RATE( + td->soc->cvb->cpu_dfll_data.dvco_calibration_max, + td->ref_rate); + else + td->dvco_calibration_max = + ROUND_DVCO_MAX_RATE(td->out_rate_max, td->ref_rate); + + td->lut_min = td->thermal_floor_output; + td->lut_max = td->thermal_cap_output; td->lut_safe = td->lut_min + (td->lut_min < td->lut_max ? 1 : 0); - /* clear DFLL_OUTPUT_CFG before setting new value */ - dfll_writel(td, 0, DFLL_OUTPUT_CFG); - dfll_wmb(td); - - 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); - if (td->pmu_if == TEGRA_DFLL_PMU_PWM) { - u32 vinit = td->reg_init_uV; + int vinit = td->reg_init_uV; int vstep = td->soc->alignment.step_uv; - unsigned long vmin = td->lut_uv[0]; + 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 = (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) { @@ -774,9 +1740,28 @@ static void dfll_init_out_if(struct tegra_dfll *td) dfll_force_output(td, vsel); } } else { + dfll_writel(td, 0, DFLL_OUTPUT_CFG); + dfll_wmb(td); + 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); + dfll_load_i2c_lut(td); dfll_init_i2c_if(td); } + + if (td->cfg_flags & DFLL_HAS_IDLE_OVERRIDE) { + val = td->lut_min << DFLL_CC4_HVC_FORCE_VAL_SHIFT; + dfll_writel(td, val, DFLL_CC4_HVC); + dfll_wmb(td); + } } /* @@ -796,23 +1781,76 @@ static void dfll_init_out_if(struct tegra_dfll *td) static int find_lut_index_for_rate(struct tegra_dfll *td, unsigned long rate) { struct dev_pm_opp *opp; - int i, align_step; + int i, uv; + + rcu_read_lock(); opp = dev_pm_opp_find_freq_ceil(td->soc->dev, &rate); - if (IS_ERR(opp)) + if (IS_ERR(opp)) { + rcu_read_unlock(); return PTR_ERR(opp); + } + uv = dev_pm_opp_get_voltage(opp) / td->soc->alignment.step_uv; - align_step = dev_pm_opp_get_voltage(opp) / td->soc->alignment.step_uv; - dev_pm_opp_put(opp); + rcu_read_unlock(); for (i = td->lut_bottom; i < td->lut_size; i++) { - if ((td->lut_uv[i] / td->soc->alignment.step_uv) >= align_step) + if ((td->lut_uv[i] / td->soc->alignment.step_uv) >= uv) return i; } return -ENOENT; } +/** + * find_mv_out_cap - find the out_map index with voltage >= @mv + * @td: DFLL instance + * @mv: millivolts + * + * Find the lut index with voltage greater than or equal to @mv, + * and return it. If all of the voltages in out_map are less than + * @mv, then return the lut index * corresponding to the highest + * possible voltage, even though it's less than @mv. + */ +static u8 find_mv_out_cap(struct tegra_dfll *td, int mv) +{ + u8 i; + + for (i = td->lut_bottom; i < td->lut_size; i++) { + if (td->lut_uv[i] >= mv * 1000) + return i; + } + + return i - 1; /* maximum possible output */ +} + +/** + * find_mv_out_floor - find the largest out_map index with voltage < @mv + * @pdev: DFLL instance + * @mv: millivolts + * + * Find the largest out_map index with voltage lesser to @mv, + * and return it. If all of the voltages in out_map are greater than + * @mv, then return the out_map index * corresponding to the minimum + * possible voltage, even though it's greater than @mv. + */ +static u8 find_mv_out_floor(struct tegra_dfll *td, int mv) +{ + u8 i; + + for (i = td->lut_bottom; i < td->lut_size; i++) { + if (td->lut_uv[i] > mv * 1000) { + if (!i) + /* minimum possible output */ + return 0; + else + break; + } + } + + return i - 1; +} + /** * dfll_calculate_rate_request - calculate DFLL parameters for a given rate * @td: DFLL instance @@ -831,11 +1869,16 @@ static int dfll_calculate_rate_request(struct tegra_dfll *td, 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. + * Requested rate must be below output maximum, i.e. maximum CPU DVFS + * rate. It can, however, be below output minimum as long as it is + * reached with DFLL output scaler from the minimum DVCO rate that + * depends on temperature and tuning mode. */ + if (rate > td->out_rate_max) { + pr_debug("%s: dfll request %lu is too high\n", __func__, rate); + return -EINVAL; + } + req->scale_bits = DFLL_FREQ_REQ_SCALE_MAX - 1; if (rate < td->dvco_rate_min) { int scale; @@ -851,7 +1894,12 @@ static int dfll_calculate_rate_request(struct tegra_dfll *td, rate = td->dvco_rate_min; } - /* Convert requested rate into frequency request and scale settings */ + /* + * Convert requested rate into frequency request and scale settings. + * LUT voltage index is set to match CPU DVFS voltage for DVCO target + * rate. It is possible for DVCO target to exceed output maximum. + * In this case LUT voltage index is set to maximum voltage. + */ 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", @@ -860,11 +1908,12 @@ static int dfll_calculate_rate_request(struct tegra_dfll *td, } 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) + rate = min(req->dvco_target_rate, td->out_rate_max); + req->lut_index = find_lut_index_for_rate(td, rate); + if (req->lut_index < 0) { + pr_debug("%s: dvco target %lu is too high\n", __func__, rate); return req->lut_index; + } return 0; } @@ -880,14 +1929,28 @@ static int dfll_calculate_rate_request(struct tegra_dfll *td, static void dfll_set_frequency_request(struct tegra_dfll *td, struct dfll_rate_req *req) { - u32 val = 0; + u32 val; int force_val; int coef = 128; /* FIXME: td->cg_scale? */; - force_val = (req->lut_index - td->lut_safe) * coef / td->cg; + force_val = req->lut_index - td->lut_safe; + if (td->lut_force_min > req->lut_index) { + int f; + + /* respect force output floor when new rate is lower */ + val = dfll_readl(td, DFLL_FREQ_REQ); + f = val & DFLL_FREQ_REQ_MULT_MASK; + if (!(val & DFLL_FREQ_REQ_FREQ_VALID) + || (f > req->mult_bits)) + force_val = td->lut_force_min - td->lut_safe; + else + force_val = req->lut_index - td->lut_safe; + } + + force_val = force_val * coef / td->cg; force_val = clamp(force_val, FORCE_MIN, FORCE_MAX); - val |= req->mult_bits << DFLL_FREQ_REQ_MULT_SHIFT; + 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; @@ -898,7 +1961,7 @@ static void dfll_set_frequency_request(struct tegra_dfll *td, } /** - * dfll_request_rate - set the next rate for the DFLL to tune to + * tegra_dfll_request_rate - set the next rate for the DFLL to tune to * @td: DFLL instance * @rate: clock rate to target * @@ -913,6 +1976,7 @@ static int dfll_request_rate(struct tegra_dfll *td, unsigned long rate) { int ret; struct dfll_rate_req req; + bool dvco_min_crossed, dvco_min_updated; if (td->mode == DFLL_UNINITIALIZED) { dev_err(td->dev, "%s: Cannot set DFLL rate in %s mode\n", @@ -920,19 +1984,78 @@ static int dfll_request_rate(struct tegra_dfll *td, unsigned long rate) return -EPERM; } + req.rate = rate; + + /* Calibrate dfll minimum rate */ + dfll_calibrate(td); + dvco_min_updated = dfll_one_shot_calibrate_floors(td); + + /* Update minimum dvco rate if we are crossing tuning threshold */ + if ((dfll_tune_target(td, rate) != td->tune_range) || + (dfll_tune_target(td, rate) != + dfll_tune_target(td, td->last_req.rate))) + dvco_min_updated = true; + + if (dvco_min_updated) + set_dvco_rate_min(td, &req); + + /* Calculate DVCO target and skipper settings */ ret = dfll_calculate_rate_request(td, &req, rate); - if (ret) + if (ret) { + set_dvco_rate_min(td, &td->last_req); return ret; + } + + dvco_min_crossed = (rate == td->dvco_rate_min) && + (td->last_req.rate > td->dvco_rate_min); td->last_unrounded_rate = rate; td->last_req = req; - if (td->mode == DFLL_CLOSED_LOOP) + if (td->mode == DFLL_CLOSED_LOOP) { + dfll_set_close_loop_config(td, &td->last_req); dfll_set_frequency_request(td, &td->last_req); + if (dvco_min_updated || dvco_min_crossed) + calibration_timer_update(td); + } return 0; } +static unsigned long dfll_request_get(struct tegra_dfll *td) +{ + /* + * If running below dvco minimum rate with skipper resolution: + * dvco min rate / 256 - return last requested rate rounded to 1kHz. + * If running above dvco minimum, with closed loop resolution: + * ref rate / 2 - return cl_dvfs target rate. + */ + if ((td->last_req.scale_bits + 1) < DFLL_FREQ_REQ_SCALE_MAX) + return (td->last_req.rate / 1000) * 1000; + + return td->last_req.dvco_target_rate; +} + +static void calibration_timer_cb(struct timer_list *t) +{ + unsigned long rate_min, flags; + struct tegra_dfll *td = container_of(t, struct tegra_dfll, calibration_timer); + + spin_lock_irqsave(&td->lock, flags); + + rate_min = td->dvco_rate_min; + dfll_calibrate(td); + + if ((rate_min != td->dvco_rate_min) || + (td->cfg_flags & DFLL_ONE_SHOT_CALIBRATE)) + dfll_request_rate(td, dfll_request_get(td)); + + pr_debug("%s: dvco min in %lu / out %lu\n", __func__, rate_min, + td->dvco_rate_min); + + spin_unlock_irqrestore(&td->lock, flags); +} + /* * DFLL enable/disable & open-loop <-> closed-loop transitions */ @@ -946,15 +2069,21 @@ static int dfll_request_rate(struct tegra_dfll *td, unsigned long rate) */ static int dfll_disable(struct tegra_dfll *td) { + unsigned long flags; + if (td->mode != DFLL_OPEN_LOOP) { dev_err(td->dev, "cannot disable DFLL in %s mode\n", mode_name[td->mode]); return -EINVAL; } + spin_lock_irqsave(&td->lock, flags); dfll_set_mode(td, DFLL_DISABLED); + spin_unlock_irqrestore(&td->lock, flags); + pm_runtime_put_sync(td->dev); + pr_debug("%s: done\n", __func__); return 0; } @@ -967,6 +2096,8 @@ static int dfll_disable(struct tegra_dfll *td) */ static int dfll_enable(struct tegra_dfll *td) { + unsigned long flags; + if (td->mode != DFLL_DISABLED) { dev_err(td->dev, "cannot enable DFLL in %s mode\n", mode_name[td->mode]); @@ -974,46 +2105,24 @@ static int dfll_enable(struct tegra_dfll *td) } pm_runtime_get_sync(td->dev); - dfll_set_mode(td, DFLL_OPEN_LOOP); + spin_lock_irqsave(&td->lock, flags); + dfll_set_mode(td, DFLL_OPEN_LOOP); + spin_unlock_irqrestore(&td->lock, flags); + + pr_debug("%s: done\n", __func__); return 0; } /** - * dfll_set_open_loop_config - prepare to switch to open-loop mode - * @td: DFLL instance - * - * Prepare to switch the DFLL to open-loop mode. This switches the - * DFLL to the low-voltage tuning range, ensures that I2C output - * forcing is disabled, and disables the output clock rate scaler. - * The DFLL's low-voltage tuning range parameters must be - * characterized to keep the downstream device stable at any DVCO - * input voltage. No return value. - */ -static void dfll_set_open_loop_config(struct tegra_dfll *td) -{ - u32 val; - - /* always tune low (safe) in open loop */ - if (td->tune_range != DFLL_TUNE_LOW) - dfll_tune_low(td); - - val = dfll_readl(td, DFLL_FREQ_REQ); - val |= DFLL_FREQ_REQ_SCALE_MASK; - val &= ~DFLL_FREQ_REQ_FORCE_ENABLE; - dfll_writel(td, val, DFLL_FREQ_REQ); - dfll_wmb(td); -} - -/** - * dfll_lock - switch from open-loop to closed-loop mode + * 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) +static int _dfll_lock(struct tegra_dfll *td) { struct dfll_rate_req *req = &td->last_req; @@ -1034,8 +2143,15 @@ static int dfll_lock(struct tegra_dfll *td) dfll_i2c_set_output_enabled(td, true); dfll_set_mode(td, DFLL_CLOSED_LOOP); + if (dfll_one_shot_calibrate_floors(td)) { + set_dvco_rate_min(td, req); + dfll_request_rate(td, dfll_request_get(td)); + } + dfll_set_close_loop_config(td, req); dfll_set_frequency_request(td, req); dfll_set_force_output_enabled(td, false); + calibration_timer_update(td); + return 0; default: @@ -1047,13 +2163,13 @@ static int dfll_lock(struct tegra_dfll *td) } /** - * dfll_unlock - switch from closed-loop to open-loop mode + * dfll_enable - switch a disabled DFLL 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. + * Switch from DISABLED state to OPEN_LOOP state. Returns 0 upon success + * or -EPERM if the DFLL is not currently disabled. */ -static int dfll_unlock(struct tegra_dfll *td) +static int _dfll_unlock(struct tegra_dfll *td) { switch (td->mode) { case DFLL_CLOSED_LOOP: @@ -1063,6 +2179,7 @@ static int dfll_unlock(struct tegra_dfll *td) dfll_pwm_set_output_enabled(td, false); else dfll_i2c_set_output_enabled(td, false); + return 0; case DFLL_OPEN_LOOP: @@ -1076,6 +2193,45 @@ static int dfll_unlock(struct tegra_dfll *td) } } +static int dfll_lock(struct tegra_dfll *td) +{ + int ret; + unsigned long flags; + + spin_lock_irqsave(&td->lock, flags); + + ret = _dfll_lock(td); + + spin_unlock_irqrestore(&td->lock, flags); + + if (!ret) + pr_debug("%s: done\n", __func__); + return ret; +} + +/** + * 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) +{ + int ret; + unsigned long flags; + + spin_lock_irqsave(&td->lock, flags); + + ret = _dfll_unlock(td); + + spin_unlock_irqrestore(&td->lock, flags); + + if (!ret) + pr_debug("%s: done\n", __func__); + return ret; +} + /* * Clock framework integration * @@ -1095,27 +2251,56 @@ 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; - ret = dfll_enable(td); - if (ret) - return ret; - - ret = dfll_lock(td); - if (ret) - dfll_disable(td); - - return ret; + return dfll_enable(td); } static void dfll_clk_disable(struct clk_hw *hw) { struct tegra_dfll *td = clk_hw_to_dfll(hw); - int ret; - ret = dfll_unlock(td); - if (!ret) - dfll_disable(td); + dfll_disable(td); +} + +static int cclk_g_parent_event(struct notifier_block *nb, + unsigned long action, void *data) +{ + struct clk_notifier_data *cnd = data; + struct clk *cclk_g_parent = clk_get_parent(cnd->clk); + struct tegra_dfll *td = cclk_g_nb_to_dfll(nb); + + switch (action) { + case PRE_PARENT_CHANGE: + if (cclk_g_parent == td->dfll_clk) { + /* Unlock DFLL before switching to PLL parent */ + if(dfll_unlock(td)) + return NOTIFY_BAD; + dev_info(td->dev, "exited closed loop mode\n"); + } else if (!td->last_req.rate) { + dev_err(td->dev, "%s: Don't switch to DFLL at rate 0\n", + __func__); + return NOTIFY_BAD; + dev_info(td->dev, "entered closed loop mode\n"); + } + break; + + case ABORT_PARENT_CHANGE: + /* fall thru to re-lock DFLL if switch to PLL was aborted */ + + case POST_PARENT_CHANGE: + if (cclk_g_parent == td->dfll_clk) { + /* + * DFLL is enabled (i.e. running in open loop) by CCF + * set parent code before the parent switch. Now, after + * the switch DFLL can be locked. + */ + if(dfll_lock(td)) + return NOTIFY_BAD; + } + break; + } + + return NOTIFY_DONE; } static unsigned long dfll_clk_recalc_rate(struct clk_hw *hw, @@ -1151,14 +2336,22 @@ 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); + unsigned long flags; + int err; - return dfll_request_rate(td, rate); + spin_lock_irqsave(&td->lock, flags); + + err = dfll_request_rate(td, rate); + + spin_unlock_irqrestore(&td->lock, flags); + + return err; } static const struct clk_ops dfll_clk_ops = { .is_enabled = dfll_clk_is_enabled, - .enable = dfll_clk_enable, - .disable = dfll_clk_disable, + .prepare = dfll_clk_enable, + .unprepare = dfll_clk_disable, .recalc_rate = dfll_clk_recalc_rate, .determine_rate = dfll_clk_determine_rate, .set_rate = dfll_clk_set_rate, @@ -1199,6 +2392,9 @@ static int dfll_register_clk(struct tegra_dfll *td) return ret; } + clk_register_clkdev(td->dfll_clk, td->output_clock_name, + "tegra-clk-debug"); + return 0; } @@ -1217,169 +2413,272 @@ static void dfll_unregister_clk(struct tegra_dfll *td) } /* - * Debugfs interface + * External floor interface */ -#ifdef CONFIG_DEBUG_FS +/** + * tegra_dfll_set_external_floor_mv - get Vmin setting from external + * rail, which is physically connected to cpu dfll rail + * @external_floor_mv: Vmin requested by connected external rail + */ +int tegra_dfll_set_external_floor_mv(int external_floor_mv) +{ + unsigned long flags; + unsigned int max; + u8 new_output; + + if (!tegra_dfll_dev) { + pr_err("%s: null tegra dfll dev.\n", __func__); + return -EINVAL; + } + + max = tegra_dfll_dev->lut_uv[tegra_dfll_dev->lut_max] / 1000; + if (external_floor_mv < 0 || external_floor_mv > max) { + pr_err("%s: invalid external vmin requested %d\n", + __func__, external_floor_mv); + return -EINVAL; + } + + spin_lock_irqsave(&tegra_dfll_dev->lock, flags); + + new_output = find_mv_out_cap(tegra_dfll_dev, external_floor_mv); + if (tegra_dfll_dev->external_floor_output != new_output) { + tegra_dfll_dev->external_floor_output = new_output; + if (tegra_dfll_dev->mode == DFLL_CLOSED_LOOP) + dfll_request_rate(tegra_dfll_dev, + dfll_request_get(tegra_dfll_dev)); + } + + spin_unlock_irqrestore(&tegra_dfll_dev->lock, flags); + + /* Add delay to ensure new Vmin delivery is finished before return */ + udelay(2 * DIV_ROUND_UP(1000000, tegra_dfll_dev->sample_rate)); + + return 0; +} + /* - * Monitor control + * Thermal interface */ /** - * dfll_calc_monitored_rate - convert DFLL_MONITOR_DATA_VAL rate into real freq - * @monitor_data: value read from the DFLL_MONITOR_DATA_VAL bitfield - * @ref_rate: DFLL reference clock rate + * tegra_dfll_update_thermal_index - tell the DFLL how hot it is + * @pdev: DFLL instance + * @type: type of thermal floor or cap + * @new_index: current DFLL temperature index * - * Convert @monitor_data from DFLL_MONITOR_DATA_VAL units into cycles - * per second. Returns the converted value. + * Update the DFLL driver's sense of what temperature the DFLL is + * running at. Intended to be called by the function supplied to the struct + * thermal_cooling_device_ops.set_cur_state function pointer. Returns + * 0 upon success or -ERANGE if @new_index is out of range. */ -static u64 dfll_calc_monitored_rate(u32 monitor_data, - unsigned long ref_rate) +int tegra_dfll_update_thermal_index(struct tegra_dfll *td, + enum tegra_dfll_thermal_type type, + unsigned long new_index) { - return monitor_data * (ref_rate / REF_CLK_CYC_PER_DVCO_SAMPLE); -} + int mv; + unsigned long flags; -/** - * dfll_read_monitor_rate - return the DFLL's output rate from internal monitor - * @td: DFLL instance - * - * If the DFLL is enabled, return the last rate reported by the DFLL's - * internal monitoring hardware. This works in both open-loop and - * closed-loop mode, and takes the output scaler setting into account. - * Assumes that the monitor was programmed to monitor frequency before - * the sample period started. If the driver believes that the DFLL is - * currently uninitialized or disabled, it will return 0, since - * otherwise the DFLL monitor data register will return the last - * measured rate from when the DFLL was active. - */ -static u64 dfll_read_monitor_rate(struct tegra_dfll *td) -{ - u32 v, s; - u64 pre_scaler_rate, post_scaler_rate; + if (type == TEGRA_DFLL_THERMAL_FLOOR && td->soc->thermal_floor_table) { + if (new_index >= td->soc->thermal_floor_table_size) + return -ERANGE; - if (!dfll_is_running(td)) - return 0; + spin_lock_irqsave(&td->lock, flags); + mv = td->soc->thermal_floor_table[new_index].millivolts; + td->thermal_floor_output = find_mv_out_cap(td, mv); + td->thermal_floor_index = new_index; - v = dfll_readl(td, DFLL_MONITOR_DATA); - v = (v & DFLL_MONITOR_DATA_VAL_MASK) >> DFLL_MONITOR_DATA_VAL_SHIFT; - pre_scaler_rate = dfll_calc_monitored_rate(v, td->ref_rate); + /* + * Cold floors may be calibrated during boot or SC7 exit, when + * actual temperature is not known. Make sure cold floors are + * re-calibrated after cooling device is engaged. + */ + dfll_invalidate_cold_floor(td); + set_dvco_rate_min(td, &td->last_req); + set_force_out_min(td); - s = dfll_readl(td, DFLL_FREQ_REQ); - s = (s & DFLL_FREQ_REQ_SCALE_MASK) >> DFLL_FREQ_REQ_SCALE_SHIFT; - post_scaler_rate = dfll_scale_dvco_rate(s, pre_scaler_rate); + if (td->mode == DFLL_CLOSED_LOOP) + dfll_request_rate(td, dfll_request_get(td)); - return post_scaler_rate; -} + spin_unlock_irqrestore(&td->lock, flags); + } else if (type == TEGRA_DFLL_THERMAL_CAP && + td->soc->thermal_cap_table) { + if (new_index >= td->soc->thermal_cap_table_size) + return -ERANGE; -static int attr_enable_get(void *data, u64 *val) -{ - struct tegra_dfll *td = data; + spin_lock_irqsave(&td->lock, flags); + mv = td->soc->thermal_cap_table[new_index].millivolts; + td->thermal_cap_output = find_mv_out_floor(td, mv); + td->thermal_cap_index = new_index; - *val = dfll_is_running(td); + if (td->mode == DFLL_CLOSED_LOOP) + dfll_request_rate(td, dfll_request_get(td)); - return 0; -} -static int attr_enable_set(void *data, u64 val) -{ - struct tegra_dfll *td = data; - - return val ? dfll_enable(td) : dfll_disable(td); -} -DEFINE_DEBUGFS_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_DEBUGFS_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; - - *val = dfll_read_monitor_rate(td); - - return 0; -} - -static int attr_rate_set(void *data, u64 val) -{ - struct tegra_dfll *td = data; - - return dfll_request_rate(td, val); -} -DEFINE_DEBUGFS_ATTRIBUTE(rate_fops, attr_rate_get, attr_rate_set, "%llu\n"); - -static int attr_registers_show(struct seq_file *s, void *data) -{ - u32 val, offs; - struct tegra_dfll *td = s->private; - - seq_puts(s, "CONTROL REGISTERS:\n"); - for (offs = 0; offs <= DFLL_MONITOR_DATA; offs += 4) { - if (offs == DFLL_OUTPUT_CFG) - val = dfll_i2c_readl(td, offs); - else - val = dfll_readl(td, offs); - seq_printf(s, "[0x%02x] = 0x%08x\n", offs, val); - } - - seq_puts(s, "\nI2C and INTR REGISTERS:\n"); - for (offs = DFLL_I2C_CFG; offs <= DFLL_I2C_STS; offs += 4) - seq_printf(s, "[0x%02x] = 0x%08x\n", offs, - dfll_i2c_readl(td, offs)); - for (offs = DFLL_INTR_STS; offs <= DFLL_INTR_EN; offs += 4) - seq_printf(s, "[0x%02x] = 0x%08x\n", offs, - dfll_i2c_readl(td, offs)); - - 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->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)); + spin_unlock_irqrestore(&td->lock, flags); } return 0; } +EXPORT_SYMBOL(tegra_dfll_update_thermal_index); -DEFINE_SHOW_ATTRIBUTE(attr_registers); - -static void dfll_debug_init(struct tegra_dfll *td) +/** + * tegra_dfll_get_thermal_index - return the DFLL's current thermal states + * @pdev: DFLL instance + * @type: type of thermal floor or cap + * + * Return the DFLL driver's copy of the DFLL's current temperature + * index, set by tegra_dfll_update_thermal_index(). Intended to be + * called by the function supplied to the struct + * thermal_cooling_device_ops.get_cur_state function pointer. + */ +int tegra_dfll_get_thermal_index(struct tegra_dfll *td, + enum tegra_dfll_thermal_type type) { - struct dentry *root; + int index; - if (!td || (td->mode == DFLL_UNINITIALIZED)) - return; + switch (type) { + case TEGRA_DFLL_THERMAL_FLOOR: + index = td->thermal_floor_index; + break; + case TEGRA_DFLL_THERMAL_CAP: + index = td->thermal_cap_index; + break; + default: + index = -EINVAL; + } - root = debugfs_create_dir("tegra_dfll_fcpu", NULL); - td->debugfs_dir = root; - - debugfs_create_file_unsafe("enable", 0644, root, td, - &enable_fops); - debugfs_create_file_unsafe("lock", 0444, root, td, &lock_fops); - debugfs_create_file_unsafe("rate", 0444, root, td, &rate_fops); - debugfs_create_file("registers", 0444, root, td, &attr_registers_fops); + return index; } +EXPORT_SYMBOL(tegra_dfll_get_thermal_index); -#else -static inline void dfll_debug_init(struct tegra_dfll *td) { } -#endif /* CONFIG_DEBUG_FS */ +/** + * tegra_dfll_count_thermal_states - return the number of thermal states + * @pdev: DFLL instance + * @type: type of thermal floor or cap + * + * Return the number of thermal states passed into the DFLL driver + * from the SoC data. Intended to be called by the function supplied + * to the struct thermal_cooling_device_ops.get_max_state function + * pointer, and by the integration code that binds a thermal zone to + * the DFLL thermal reaction driver. + */ +int tegra_dfll_count_thermal_states(struct tegra_dfll *td, + enum tegra_dfll_thermal_type type) +{ + int size; + + switch (type) { + case TEGRA_DFLL_THERMAL_FLOOR: + size = td->soc->thermal_floor_table_size; + break; + case TEGRA_DFLL_THERMAL_CAP: + size = td->soc->thermal_cap_table_size; + break; + default: + size = -EINVAL; + } + + return size; +} +EXPORT_SYMBOL(tegra_dfll_count_thermal_states); + +/** + * tegra_dfll_get_by_phandle - get DFLL device from a phandle + * @np: device node + * @prop: property name + * + * Returns the DFLL instance referred to by the phandle @prop in node @np + * or an ERR_PTR() on failure. + */ +struct tegra_dfll *tegra_dfll_get_by_phandle(struct device_node *np, + const char *prop) +{ + struct device_node *dfll_np; + struct tegra_dfll *td; + + dfll_np = of_parse_phandle(np, prop, 0); + if (!dfll_np) + return ERR_PTR(-ENOENT); + + if (!tegra_dfll_dev) { + td = ERR_PTR(-EPROBE_DEFER); + goto error; + } + + if (tegra_dfll_dev->dev->of_node != dfll_np) { + td = ERR_PTR(-EINVAL); + goto error; + } + + td = tegra_dfll_dev; + +error: + of_node_put(dfll_np); + return td; +} +EXPORT_SYMBOL(tegra_dfll_get_by_phandle); + +/** + * tegra_dfll_get_thermal_floor_mv - return millivolts of thermal floor + */ +u32 tegra_dfll_get_thermal_floor_mv(void) +{ + return tegra_dfll_dev->lut_uv[tegra_dfll_dev->thermal_floor_output] + / 1000; +} +EXPORT_SYMBOL(tegra_dfll_get_thermal_floor_mv); + +/** + * tegra_dfll_get_thermal_cap_mv - return millivolts of thermal cap + */ +u32 tegra_dfll_get_thermal_cap_mv(void) +{ + return tegra_dfll_dev->lut_uv[tegra_dfll_dev->thermal_cap_output] + / 1000; +} +EXPORT_SYMBOL(tegra_dfll_get_thermal_cap_mv); + +/** + * tegra_dfll_get_peak_thermal_floor_mv - get millivolts of peak thermal floor + */ +u32 tegra_dfll_get_peak_thermal_floor_mv(void) +{ + int mv = tegra_dfll_dev->soc->thermal_floor_table[0].millivolts; + + return tegra_round_voltage(mv, &tegra_dfll_dev->soc->alignment, 1); +} +EXPORT_SYMBOL(tegra_dfll_get_peak_thermal_floor_mv); + +/** + * tegra_dfll_get_min_millivolts - return DFLL min millivolts + */ +u32 tegra_dfll_get_min_millivolts(void) +{ + return tegra_dfll_dev->soc->min_millivolts; +} +EXPORT_SYMBOL(tegra_dfll_get_min_millivolts); + +/** + * tegra_dfll_get_alignment - return DFLL alignment + */ +struct rail_alignment *tegra_dfll_get_alignment(void) +{ + if (!tegra_dfll_dev) + return ERR_PTR(-EPROBE_DEFER); + return &tegra_dfll_dev->soc->alignment; +} +EXPORT_SYMBOL(tegra_dfll_get_alignment); + +/** + * tegra_dfll_get_cvb_version - return DFLL CVB version + */ +const char *tegra_dfll_get_cvb_version(void) +{ + if (!tegra_dfll_dev) + return ERR_PTR(-EPROBE_DEFER); + return tegra_dfll_dev->soc->cvb->cvb_version; +} +EXPORT_SYMBOL(tegra_dfll_get_cvb_version); /* * DFLL initialization @@ -1410,7 +2709,7 @@ static void dfll_set_default_params(struct tegra_dfll *td) dfll_tune_low(td); dfll_writel(td, td->droop_ctrl, DFLL_DROOP_CTRL); - dfll_writel(td, DFLL_MONITOR_CTRL_FREQ, DFLL_MONITOR_CTRL); + dfll_set_monitor_mode(td, DFLL_FREQ); } /** @@ -1423,6 +2722,8 @@ static void dfll_set_default_params(struct tegra_dfll *td) */ static int dfll_init_clks(struct tegra_dfll *td) { + int ret; + td->ref_clk = devm_clk_get(td->dev, "ref"); if (IS_ERR(td->ref_clk)) { dev_err(td->dev, "missing ref clock\n"); @@ -1442,9 +2743,79 @@ static int dfll_init_clks(struct tegra_dfll *td) } td->i2c_clk_rate = clk_get_rate(td->i2c_clk); + td->cclk_g_clk = devm_clk_get(td->dev, "cclk_g"); + if (IS_ERR(td->cclk_g_clk)) { + dev_err(td->dev, "missing cclk_g clock\n"); + return PTR_ERR(td->cclk_g_clk); + } + td->cclk_g_parent_nb.notifier_call = cclk_g_parent_event; + ret = clk_notifier_register(td->cclk_g_clk, &td->cclk_g_parent_nb); + if (ret) { + dev_err(td->dev, "failed to register cclk_g notifier\n"); + return ret; + } + return 0; } +/** + * dfll_init_tuning_thresholds - set up the high voltage range, if possible + * @td: DFLL instance + * + * Determine whether the DFLL tuning parameters need to be + * reprogrammed when the DFLL voltage reaches a certain minimum + * threshold. No return value. + */ +static void dfll_init_tuning_thresholds(struct tegra_dfll *td) +{ + u8 out_min, out_start, max_voltage_index; + unsigned int s = td->soc->thermal_floor_table_size; + + max_voltage_index = td->lut_size - 1; + + /* + * Convert high tuning voltage threshold into output LUT + * index, and add necessary margin. If voltage threshold is + * outside operating range set it at maximum output level to + * effectively disable tuning parameters adjustment. + */ + td->tune_high_out_min = max_voltage_index; + td->tune_high_out_start = max_voltage_index; + td->tune_high_dvco_rate_floors[s] = ULONG_MAX; + td->tune_high_target_rate_min = ULONG_MAX; + + if (td->soc->tune_high_min_millivolts < td->soc->min_millivolts) + return; /* no difference between low & high voltage range */ + + out_min = find_mv_out_cap(td, td->soc->tune_high_min_millivolts); + if ((out_min + DFLL_CAP_GUARD_BAND_STEPS) > max_voltage_index) + return; + + if (td->soc->tune_high_margin_millivolts) { + unsigned int mv; + + mv = td->soc->tune_high_min_millivolts + + td->soc->tune_high_margin_millivolts; + if (mv * 1000 > td->lut_uv[max_voltage_index]) + return; + out_start = max((u8)(out_min + 1), find_mv_out_cap(td, mv)); + } else { + out_start = out_min + DFLL_TUNE_HIGH_MARGIN_STEPS; + } + if (out_start > max_voltage_index) + return; + + /* + * Store target rate tuning threshold, and estimated DVCO rate at high + * tuning voltage threshold. DVCO rate tuning floors will be calibrated + * separately for each thermal range. + */ + td->tune_high_out_min = out_min; + td->tune_high_out_start = out_start; + td->tune_high_dvco_rate_floors[s] = get_dvco_rate_above(td, out_start); + td->tune_high_target_rate_min = get_dvco_rate_above(td, out_min); +} + /** * dfll_init - Prepare the DFLL IP block for use * @td: DFLL instance @@ -1465,7 +2836,6 @@ static int dfll_init(struct tegra_dfll *td) return -EINVAL; } - reset_control_deassert(td->dfll_rst); reset_control_deassert(td->dvco_rst); ret = clk_prepare(td->ref_clk); @@ -1501,6 +2871,8 @@ static int dfll_init(struct tegra_dfll *td) dfll_init_out_if(td); + dfll_init_tuning_thresholds(td); + pm_runtime_put_sync(td->dev); return 0; @@ -1511,68 +2883,10 @@ di_err1: clk_unprepare(td->ref_clk); reset_control_assert(td->dvco_rst); - reset_control_assert(td->dfll_rst); return ret; } -/** - * tegra_dfll_suspend - check DFLL is disabled - * @dev: DFLL instance - * - * DFLL clock should be disabled by the CPUFreq driver. So, make - * sure it is disabled and disable all clocks needed by the DFLL. - */ -int tegra_dfll_suspend(struct device *dev) -{ - struct tegra_dfll *td = dev_get_drvdata(dev); - - if (dfll_is_running(td)) { - dev_err(td->dev, "DFLL still enabled while suspending\n"); - return -EBUSY; - } - - reset_control_assert(td->dvco_rst); - reset_control_assert(td->dfll_rst); - - return 0; -} -EXPORT_SYMBOL(tegra_dfll_suspend); - -/** - * tegra_dfll_resume - reinitialize DFLL on resume - * @dev: DFLL instance - * - * DFLL is disabled and reset during suspend and resume. - * So, reinitialize the DFLL IP block back for use. - * DFLL clock is enabled later in closed loop mode by CPUFreq - * driver before switching its clock source to DFLL output. - */ -int tegra_dfll_resume(struct device *dev) -{ - struct tegra_dfll *td = dev_get_drvdata(dev); - - reset_control_deassert(td->dfll_rst); - reset_control_deassert(td->dvco_rst); - - pm_runtime_get_sync(td->dev); - - dfll_set_mode(td, DFLL_DISABLED); - dfll_set_default_params(td); - - if (td->soc->init_clock_trimmers) - td->soc->init_clock_trimmers(); - - dfll_set_open_loop_config(td); - - dfll_init_out_if(td); - - pm_runtime_put_sync(td->dev); - - return 0; -} -EXPORT_SYMBOL(tegra_dfll_resume); - /* * DT data fetch */ @@ -1583,21 +2897,17 @@ EXPORT_SYMBOL(tegra_dfll_resume); */ static int find_vdd_map_entry_exact(struct tegra_dfll *td, int uV) { - int i, n_voltages, reg_uV,reg_volt_id, align_step; + int i, n_voltages, reg_mult, align_mult; - if (WARN_ON(td->pmu_if == TEGRA_DFLL_PMU_PWM)) - return -EINVAL; - - align_step = uV / td->soc->alignment.step_uv; + 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; - reg_volt_id = reg_uV / td->soc->alignment.step_uv; - - if (align_step == reg_volt_id) + if (align_mult == reg_mult) return i; } @@ -1611,21 +2921,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, reg_volt_id, align_step; + int i, n_voltages, reg_mult, align_mult; - if (WARN_ON(td->pmu_if == TEGRA_DFLL_PMU_PWM)) - return -EINVAL; - - align_step = uV / td->soc->alignment.step_uv; + 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; - reg_volt_id = reg_uV / td->soc->alignment.step_uv; - - if (align_step <= reg_volt_id) + if (align_mult <= reg_mult) return i; } @@ -1634,20 +2940,16 @@ static int find_vdd_map_entry_min(struct tegra_dfll *td, int uV) } /* - * dfll_build_pwm_lut - build the PWM regulator lookup table - * @td: DFLL instance - * @v_max: Vmax from OPP table - * * 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_pwm_lut(struct tegra_dfll *td, unsigned long v_max) +static int dfll_build_lut_pwm(struct tegra_dfll *td, int v_max) { - int i; - unsigned long rate, reg_volt; + int i, reg_volt; + unsigned long rate; u8 lut_bottom = MAX_DFLL_VOLTAGES; - int v_min = td->soc->cvb->min_millivolts * 1000; + int v_min = td->soc->min_millivolts * 1000; for (i = 0; i < MAX_DFLL_VOLTAGES; i++) { reg_volt = td->lut_uv[i]; @@ -1667,7 +2969,7 @@ static int dfll_build_pwm_lut(struct tegra_dfll *td, unsigned long v_max) 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); + td->soc->min_millivolts); return -EINVAL; } td->lut_bottom = lut_bottom; @@ -1676,10 +2978,12 @@ static int dfll_build_pwm_lut(struct tegra_dfll *td, unsigned long v_max) 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); + td->soc->min_millivolts); return -EINVAL; } - td->dvco_rate_min = rate; + td->dvco_rate_min = td->out_rate_min = rate; + rate = get_dvco_rate_below(td, td->lut_size - 1); + td->out_rate_max = rate; return 0; } @@ -1687,7 +2991,6 @@ static int dfll_build_pwm_lut(struct tegra_dfll *td, unsigned long v_max) /** * dfll_build_i2c_lut - build the I2C voltage register lookup table * @td: DFLL instance - * @v_max: Vmax from OPP table * * 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. @@ -1695,21 +2998,28 @@ static int dfll_build_pwm_lut(struct tegra_dfll *td, unsigned long v_max) * 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->lut and returns 0, or -err on failure. + * On success, fills in td->i2c_lut and returns 0, or -err on failure. */ -static int dfll_build_i2c_lut(struct tegra_dfll *td, unsigned long v_max) +static int dfll_build_i2c_lut(struct tegra_dfll *td, int v_max) { - unsigned long rate, v, v_opp; + unsigned long rate; int ret = -EINVAL; - int j, selector, lut; + int j, v, v_opp, v_min_align; + int selector; + int lut; - v = td->soc->cvb->min_millivolts * 1000; + rcu_read_lock(); + + v = td->soc->min_millivolts * 1000; lut = find_vdd_map_entry_exact(td, v); if (lut < 0) goto out; td->lut[0] = lut; td->lut_bottom = 0; + v_min_align = DIV_ROUND_UP(td->soc->min_millivolts * 1000, + td->soc->alignment.step_uv) * td->soc->alignment.step_uv; + for (j = 1, rate = 0; ; rate++) { struct dev_pm_opp *opp; @@ -1717,14 +3027,14 @@ static int dfll_build_i2c_lut(struct tegra_dfll *td, unsigned long v_max) if (IS_ERR(opp)) break; v_opp = dev_pm_opp_get_voltage(opp); + if (v_opp <= v_min_align) + td->out_rate_min = dev_pm_opp_get_freq(opp); - if (v_opp <= td->soc->cvb->min_millivolts * 1000) - td->dvco_rate_min = dev_pm_opp_get_freq(opp); - - dev_pm_opp_put(opp); + if (rate > td->out_rate_max) + td->out_rate_max = rate; for (;;) { - v += max(1UL, (v_max - v) / (MAX_DFLL_VOLTAGES - j)); + v += max(1, (v_max - v) / (MAX_DFLL_VOLTAGES - j)); if (v >= v_opp) break; @@ -1747,25 +3057,31 @@ static int dfll_build_i2c_lut(struct tegra_dfll *td, unsigned long v_max) } td->lut_size = j; - if (!td->dvco_rate_min) + if (!td->out_rate_min) { dev_err(td->dev, "no opp above DFLL minimum voltage %d mV\n", - td->soc->cvb->min_millivolts); - else { + td->soc->min_millivolts); + } else { ret = 0; for (j = 0; j < td->lut_size; j++) td->lut_uv[j] = regulator_list_voltage(td->vdd_reg, td->lut[j]); + td->dvco_rate_min = td->out_rate_min; } out: + rcu_read_unlock(); + return ret; } static int dfll_build_lut(struct tegra_dfll *td) { - unsigned long rate, v_max; + 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); @@ -1774,14 +3090,602 @@ static int dfll_build_lut(struct tegra_dfll *td) return -EINVAL; } v_max = dev_pm_opp_get_voltage(opp); - dev_pm_opp_put(opp); - if (td->pmu_if == TEGRA_DFLL_PMU_PWM) - return dfll_build_pwm_lut(td, v_max); - else + 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); } +/* + * Debugfs interface + */ + +#ifdef CONFIG_DEBUG_FS +/* + * Output clock scaler helpers + */ + +/** + * dfll_scale_dvco_rate - calculate scaled rate from the DVCO rate + * @scale_bits: clock scaler value (bits in the DFLL_FREQ_REQ_SCALE field) + * @dvco_rate: the DVCO rate + * + * Apply the same scaling formula that the DFLL hardware uses to scale + * the DVCO rate. + */ +static unsigned long dfll_scale_dvco_rate(int scale_bits, + unsigned long dvco_rate) +{ + return (u64)dvco_rate * (scale_bits + 1) / DFLL_FREQ_REQ_SCALE_MAX; +} + + +/* + * Monitor control + */ + +/** + * dfll_read_monitor_rate - return the DFLL's output rate from internal monitor + * @td: DFLL instance + * + * If the DFLL is enabled, return the last rate reported by the DFLL's + * internal monitoring hardware. This works in both open-loop and + * closed-loop mode, and takes the output scaler setting into account. + * Assumes that the monitor was programmed to monitor frequency before + * the sample period started. If the driver believes that the DFLL is + * currently uninitialized or disabled, it will return 0, since + * otherwise the DFLL monitor data register will return the last + * measured rate from when the DFLL was active. + */ +static u64 dfll_read_monitor_rate(struct tegra_dfll *td) +{ + u32 v, s; + u64 pre_scaler_rate, post_scaler_rate; + unsigned long flags; + + if (!dfll_is_running(td)) + return 0; + + spin_lock_irqsave(&td->lock, flags); + + dfll_set_monitor_mode(td, DFLL_FREQ); + dfll_get_monitor_data(td, &v); + pre_scaler_rate = dfll_calc_monitored_rate(v, td->ref_rate); + + s = dfll_readl(td, DFLL_FREQ_REQ); + s = (s & DFLL_FREQ_REQ_SCALE_MASK) >> DFLL_FREQ_REQ_SCALE_SHIFT; + post_scaler_rate = dfll_scale_dvco_rate(s, pre_scaler_rate); + + spin_unlock_irqrestore(&td->lock, flags); + + return post_scaler_rate; +} + +static int enable_get(void *data, u64 *val) +{ + struct tegra_dfll *td = data; + + *val = dfll_is_running(td); + + return 0; +} +static int enable_set(void *data, u64 val) +{ + struct tegra_dfll *td = data; + + return val ? dfll_enable(td) : dfll_disable(td); +} +DEFINE_SIMPLE_ATTRIBUTE(enable_fops, enable_get, enable_set, "%llu\n"); + +static int lock_get(void *data, u64 *val) +{ + struct tegra_dfll *td = data; + + *val = (td->mode == DFLL_CLOSED_LOOP); + + return 0; +} +static int lock_set(void *data, u64 val) +{ + struct tegra_dfll *td = data; + + return val ? dfll_lock(td) : dfll_unlock(td); +} +DEFINE_SIMPLE_ATTRIBUTE(lock_fops, lock_get, lock_set, "%llu\n"); + +static int rate_get(void *data, u64 *val) +{ + struct tegra_dfll *td = data; + + *val = dfll_read_monitor_rate(td); + + return 0; +} + +static int rate_set(void *data, u64 val) +{ + struct tegra_dfll *td = data; + int err; + unsigned long flags; + + spin_lock_irqsave(&td->lock, flags); + + err = dfll_request_rate(td, val); + + spin_unlock_irqrestore(&td->lock, flags); + + return err; +} +DEFINE_SIMPLE_ATTRIBUTE(rate_fops, rate_get, rate_set, "%llu\n"); + +static int dvco_rate_min_get(void *data, u64 *val) +{ + struct tegra_dfll *td = data; + + *val = td->dvco_rate_min; + + return 0; +} +DEFINE_SIMPLE_ATTRIBUTE(dvco_rate_min_fops, dvco_rate_min_get, NULL, "%llu\n"); + +static int vmin_get(void *data, u64 *val) +{ + struct tegra_dfll *td = data; + + *val = td->lut_uv[td->lut_min] / 1000; + + return 0; +} +DEFINE_SIMPLE_ATTRIBUTE(vmin_fops, vmin_get, NULL, "%llu\n"); + +static int vmax_get(void *data, u64 *val) +{ + struct tegra_dfll *td = data; + + *val = td->lut_uv[td->lut_max] / 1000; + + return 0; +} +DEFINE_SIMPLE_ATTRIBUTE(vmax_fops, vmax_get, NULL, "%llu\n"); + +static int output_get(void *data, u64 *val) +{ + struct tegra_dfll *td = data; + u32 reg; + unsigned long flags; + + spin_lock_irqsave(&td->lock, flags); + + reg = dfll_readl(td, DFLL_OUTPUT_FORCE); + if (reg & DFLL_OUTPUT_FORCE_ENABLE) { + *val = td->lut_uv[reg & DFLL_OUTPUT_FORCE_VALUE_MASK] / 1000; + goto out; + } + + dfll_set_monitor_mode(td, DFLL_OUTPUT_VALUE); + dfll_get_monitor_data(td, ®); + + *val = td->lut_uv[reg] / 1000; + +out: + spin_unlock_irqrestore(&td->lock, flags); + + return 0; +} +DEFINE_SIMPLE_ATTRIBUTE(output_fops, output_get, NULL, "%llu\n"); + +static int fout_mv_get(void *data, u64 *val) +{ + struct tegra_dfll *td = data; + u32 reg; + + reg = dfll_readl(td, DFLL_OUTPUT_FORCE); + *val = td->lut_uv[reg & DFLL_OUTPUT_FORCE_VALUE_MASK] / 1000; + + return 0; +} + +static int fout_mv_set(void *data, u64 val) +{ + struct tegra_dfll *td = data; + unsigned long flags; + + spin_lock_irqsave(&td->lock, flags); + + if (val) { + u8 out_value; + + out_value = find_mv_out_cap(td, val); + dfll_set_force_output_value(td, out_value); + dfll_set_force_output_enabled(td, true); + } else { + dfll_set_force_output_enabled(td, false); + } + + spin_unlock_irqrestore(&td->lock, flags); + + return 0; +} +DEFINE_SIMPLE_ATTRIBUTE(fout_mv_fops, fout_mv_get, fout_mv_set, "%llu\n"); + +static int external_floor_mv_get(void *data, u64 *val) +{ + struct tegra_dfll *td = data; + + *val = td->lut_uv[td->external_floor_output] / 1000; + + return 0; +} + +static int external_floor_mv_set(void *data, u64 val) +{ + tegra_dfll_set_external_floor_mv((int)val); + + return 0; +} +DEFINE_SIMPLE_ATTRIBUTE(external_floor_mv_fops, external_floor_mv_get, + external_floor_mv_set, "%llu\n"); + +static int undershoot_get(void *data, u64 *val) +{ + struct tegra_dfll *td = data; + + *val = td->pmu_undershoot_gb; + + return 0; +} + +static int undershoot_set(void *data, u64 val) +{ + struct tegra_dfll *td = data; + unsigned long flags; + + spin_lock_irqsave(&td->lock, flags); + + td->pmu_undershoot_gb = val; + set_force_out_min(td); + + spin_unlock_irqrestore(&td->lock, flags); + + return 0; +} +DEFINE_SIMPLE_ATTRIBUTE(undershoot_fops, undershoot_get, undershoot_set, + "%llu\n"); + +static int calibr_delay_get(void *data, u64 *val) +{ + struct tegra_dfll *td = data; + + *val = jiffies_to_msecs(td->calibration_delay); + + return 0; +} + +static int calibr_delay_set(void *data, u64 val) +{ + struct tegra_dfll *td = data; + unsigned long flags; + + spin_lock_irqsave(&td->lock, flags); + td->calibration_delay = msecs_to_jiffies(val); + spin_unlock_irqrestore(&td->lock, flags); + + return 0; +} +DEFINE_SIMPLE_ATTRIBUTE(calibr_delay_fops, calibr_delay_get, calibr_delay_set, + "%llu\n"); + +static int tune_high_mv_get(void *data, u64 *val) +{ + struct tegra_dfll *td = data; + + *val = td->soc->tune_high_min_millivolts; + + return 0; +} + +static int tune_high_mv_set(void *data, u64 val) +{ + struct tegra_dfll *td = data; + unsigned long flags; + + spin_lock_irqsave(&td->lock, flags); + td->soc->tune_high_min_millivolts = val; + dfll_init_tuning_thresholds(td); + if (td->mode == DFLL_CLOSED_LOOP) + dfll_set_frequency_request(td, &td->last_req); + spin_unlock_irqrestore(&td->lock, flags); + + return clk_set_rate(td->dfll_clk, clk_get_rate(td->dfll_clk)); +} + +DEFINE_SIMPLE_ATTRIBUTE(tune_high_mv_fops, tune_high_mv_get, tune_high_mv_set, + "%llu\n"); + +#define DFLL_CALIBRATE_FLOOR_RETRY 10 + +static bool dfll_one_shot_log_time(struct tegra_dfll *td) +{ + ktime_t start, end; + bool ret; + + start = ktime_get(); + ret = dfll_one_shot_calibrate_floors(td); + end = ktime_get(); + + pr_debug("%s: time_us: %lld\n", __func__, ktime_us_delta(end, start)); + return ret; +} + +static int calibrate_floors_set(void *data, u64 val) +{ + struct tegra_dfll *td = data; + unsigned long flags, rate; + int i, cnt; + + if (!val) + return 0; + + if (!(td->cfg_flags & DFLL_ONE_SHOT_CALIBRATE)) { + calibration_timer_update(td); + return 0; + } + + spin_lock_irqsave(&td->lock, flags); + + rate = dfll_request_get(td); + dfll_request_rate(td, td->out_rate_min); + + dfll_invalidate_one_shot(td); + for (i = cnt = 0; i < DFLL_CALIBRATE_FLOOR_RETRY && cnt <= 1; i++) + cnt += dfll_one_shot_log_time(td) ? 1 : 0; + + set_dvco_rate_min(td, &td->last_req); + dfll_request_rate(td, rate); + + spin_unlock_irqrestore(&td->lock, flags); + + return 0; +} +DEFINE_SIMPLE_ATTRIBUTE(calibrate_floors_fops, NULL, calibrate_floors_set, + "%llu\n"); + +static int registers_show(struct seq_file *s, void *data) +{ + u32 val, offs; + struct tegra_dfll *td = s->private; + unsigned long flags; + + spin_lock_irqsave(&td->lock, flags); + + seq_puts(s, "CONTROL REGISTERS:\n"); + for (offs = 0; offs <= DFLL_MONITOR_DATA; offs += 4) { + if (offs == DFLL_OUTPUT_CFG) + val = dfll_i2c_readl(td, offs); + else + val = dfll_readl(td, offs); + seq_printf(s, "[0x%02x] = 0x%08x\n", offs, val); + } + + seq_puts(s, "\nI2C and INTR REGISTERS:\n"); + for (offs = DFLL_I2C_CFG; offs <= DFLL_I2C_STS; offs += 4) + seq_printf(s, "[0x%02x] = 0x%08x\n", offs, + dfll_i2c_readl(td, offs)); + for (offs = DFLL_INTR_STS; offs <= DFLL_INTR_EN; offs += 4) + seq_printf(s, "[0x%02x] = 0x%08x\n", offs, + dfll_i2c_readl(td, offs)); + + if (td->cfg_flags & DFLL_HAS_IDLE_OVERRIDE) { + seq_puts(s, "\nOVERRIDE REGISTERS:\n"); + offs = DFLL_CC4_HVC; + if (td->pmu_if == TEGRA_DFLL_PMU_I2C) + val = dfll_i2c_readl(td, offs); + else + val = dfll_readl(td, offs); + seq_printf(s, "[0x%02x] = 0x%08x\n", offs, val); + } + + 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->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)); + } + + spin_unlock_irqrestore(&td->lock, flags); + + return 0; +} + +static ssize_t register_write(struct file *file, + const char __user *userbuf, size_t count, loff_t *ppos) +{ + char buf[80]; + u32 offs; + u32 val; + struct tegra_dfll *td = file->f_path.dentry->d_inode->i_private; + unsigned long flags; + + if (sizeof(buf) <= count) + return -EINVAL; + + if (copy_from_user(buf, userbuf, count)) + return -EFAULT; + + /* terminate buffer and trim - white spaces may be appended + * at the end when invoked from shell command line */ + buf[count] = '\0'; + strim(buf); + + if (sscanf(buf, "[0x%x] = 0x%x", &offs, &val) != 2) + return -EINVAL; + + if (offs >= 0x400) + return -EINVAL; + + clk_enable(td->soc_clk); + spin_lock_irqsave(&td->lock, flags); + dfll_writel(td, val, offs & (~0x3)); + spin_unlock_irqrestore(&td->lock, flags); + clk_disable(td->soc_clk); + + return count; +} + +static int registers_open(struct inode *inode, struct file *file) +{ + return single_open(file, registers_show, inode->i_private); +} + +static const struct file_operations registers_fops = { + .open = registers_open, + .read = seq_read, + .write = register_write, + .llseek = seq_lseek, + .release = single_release, +}; + +static int profiles_show(struct seq_file *s, void *data) +{ + struct thermal_tv tv; + int i, size; + unsigned long r, r_default; + struct tegra_dfll *td = s->private; + u8 v; + + size = td->soc->thermal_cap_table_size; + seq_printf(s, "THERM CAPS:%s\n", size ? "" : " NONE"); + for (i = 0; i < size; i++) { + tv = td->soc->thermal_cap_table[i]; + if (tv.temp == DFLL_THERMAL_CAP_NOCAP / 1000) + continue; + v = find_mv_out_floor(td, tv.millivolts); + seq_printf(s, "%3dC.. %5dmV\n", + tv.temp, tegra_dfll_dev->lut_uv[v] / 1000); + } + + if (td->tune_high_target_rate_min == ULONG_MAX) { + seq_puts(s, "TUNE HIGH: NONE\n"); + } else { + size = td->soc->thermal_floor_table_size; + r_default = td->tune_high_dvco_rate_floors[size]; + + seq_puts(s, "TUNE HIGH:\n"); + for (i = 0; i < size; i++) { + tv = td->soc->thermal_floor_table[i]; + r = td->tune_high_dvco_rate_floors[i]; + v = td->tune_high_out_min; + seq_printf(s, " ..%3dC%5dmV%9lukHz%s\n", + tv.temp, tegra_dfll_dev->lut_uv[v] / 1000, + (r ? : r_default) / 1000, + r ? " (calibrated)" : ""); + } + seq_printf(s, "%-14s%9lukHz\n", "rate threshold", + td->tune_high_target_rate_min / 1000); + } + + size = td->soc->thermal_floor_table_size; + seq_printf(s, "THERM FLOORS:%s\n", size ? "" : " NONE"); + for (i = 0; i < size; i++) { + tv = td->soc->thermal_floor_table[i]; + r = td->dvco_rate_floors[i]; + v = find_mv_out_cap(td, tv.millivolts); + seq_printf(s, " ..%3dC%5dmV%9lukHz%s\n", + tv.temp, tegra_dfll_dev->lut_uv[v] / 1000, + (r ? : get_dvco_rate_below(td, v)) / 1000, + r ? " (calibrated)" : ""); + } + r = td->dvco_rate_floors[i]; + seq_printf(s, " vmin:%5dmV%9lukHz%s\n", td->lut_uv[0] / 1000, + (r ? : td->out_rate_min) / 1000, + r ? " (calibrated)" : ""); + + seq_printf(s, "DVCO_CALIBRATION_MAX: %lukHz\n", + td->dvco_calibration_max / 1000); + return 0; +} + +static int profiles_open(struct inode *inode, struct file *file) +{ + return single_open(file, profiles_show, inode->i_private); +} + +static const struct file_operations profiles_fops = { + .open = profiles_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static struct { + char *name; + umode_t mode; + const struct file_operations *fops; +} dfll_debugfs_nodes[] = { + { "enable", S_IRUGO | S_IWUSR, &enable_fops }, + { "lock", S_IRUGO | S_IWUSR, &lock_fops }, + { "force_out_mv", S_IRUGO | S_IWUSR, &fout_mv_fops }, + { "external_floor_mv", S_IRUGO | S_IWUSR, + &external_floor_mv_fops }, + { "pmu_undershoot_gb", S_IRUGO | S_IWUSR, &undershoot_fops }, + { "tune_high_mv", S_IRUGO | S_IWUSR, &tune_high_mv_fops }, + { "calibr_delay", S_IRUGO | S_IWUSR, &calibr_delay_fops }, + { "rate", S_IRUGO, &rate_fops }, + { "dvco_rate_min", S_IRUGO, &dvco_rate_min_fops }, + { "registers", S_IRUGO, ®isters_fops }, + { "vmin_mv", S_IRUGO, &vmin_fops }, + { "vmax_mv", S_IRUGO, &vmax_fops }, + { "output_mv", S_IRUGO, &output_fops }, + { "profiles", S_IRUGO, &profiles_fops }, + { "calibrate_floors", S_IWUSR, &calibrate_floors_fops }, +}; + +static int dfll_debug_init(struct tegra_dfll *td) +{ + int i; + + if (!td || (td->mode == DFLL_UNINITIALIZED)) + return 0; + + td->debugfs_dir = debugfs_create_dir("tegra_dfll_fcpu", NULL); + if (!td->debugfs_dir) + return -ENOMEM; + + for (i = 0; i < ARRAY_SIZE(dfll_debugfs_nodes); i++) { + if (!debugfs_create_file(dfll_debugfs_nodes[i].name, + dfll_debugfs_nodes[i].mode, + td->debugfs_dir, td, + dfll_debugfs_nodes[i].fops)) + goto err_out; + } + + debugfs_create_x32("flags", S_IRUGO, td->debugfs_dir, &td->cfg_flags); + + debugfs_create_u32("one_shot_invalid_time", S_IRUGO | S_IWUSR, + td->debugfs_dir, &td->one_shot_invalid_time); + + debugfs_create_symlink("monitor", td->debugfs_dir, "rate"); + debugfs_create_symlink("dvco_rate", td->debugfs_dir, "dvco_rate_min"); + + clk_register_clkdev(td->dfll_clk, td->output_clock_name, + "tegra-clk-debug"); + return 0; + +err_out: + debugfs_remove_recursive(td->debugfs_dir); + return -ENOMEM; +} + +#endif /* CONFIG_DEBUG_FS */ + /** * read_dt_param - helper function for reading required parameters from the DT * @td: DFLL instance @@ -1824,6 +3728,8 @@ static int dfll_fetch_i2c_params(struct tegra_dfll *td) if (!read_dt_param(td, "nvidia,i2c-fs-rate", &td->i2c_fs_rate)) return -EINVAL; + read_dt_param(td, "nvidia,pmic-undershoot-gb", &td->pmu_undershoot_gb); + regmap = regulator_get_regmap(td->vdd_reg); i2c_dev = regmap_get_device(regmap); i2c_client = to_i2c_client(i2c_dev); @@ -1849,8 +3755,7 @@ static int dfll_fetch_pwm_params(struct tegra_dfll *td) 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"); + dev_err(td->dev, "Missing step or alignment info for PWM regulator"); return -EINVAL; } for (i = 0; i < MAX_DFLL_VOLTAGES; i++) @@ -1861,13 +3766,13 @@ static int dfll_fetch_pwm_params(struct tegra_dfll *td) &td->reg_init_uV); if (!ret) { dev_err(td->dev, "couldn't get initialized voltage\n"); - return -EINVAL; + return ret; } ret = read_dt_param(td, "nvidia,pwm-period-nanoseconds", &pwm_period); if (!ret) { dev_err(td->dev, "couldn't get PWM period\n"); - return -EINVAL; + return ret; } td->pwm_rate = (NSEC_PER_SEC / pwm_period) * (MAX_DFLL_VOLTAGES - 1); @@ -1882,9 +3787,9 @@ static int dfll_fetch_pwm_params(struct tegra_dfll *td) 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, + 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"); @@ -1893,7 +3798,6 @@ static int dfll_fetch_pwm_params(struct tegra_dfll *td) return 0; } - /** * dfll_fetch_common_params - read DFLL parameters from the device tree * @td: DFLL instance @@ -1904,6 +3808,7 @@ static int dfll_fetch_pwm_params(struct tegra_dfll *td) static int dfll_fetch_common_params(struct tegra_dfll *td) { bool ok = true; + struct device_node *dn = td->dev->of_node; ok &= read_dt_param(td, "nvidia,droop-ctrl", &td->droop_ctrl); ok &= read_dt_param(td, "nvidia,sample-rate", &td->sample_rate); @@ -1914,7 +3819,23 @@ static int dfll_fetch_common_params(struct tegra_dfll *td) 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", + if (of_property_read_bool(dn, "nvidia,calibrate-force-vmin")) + td->cfg_flags |= DFLL_CALIBRATE_FORCE_VMIN; + + if (of_property_read_bool(dn, "nvidia,defer-force-calibrate")) + td->cfg_flags |= DFLL_DEFER_FORCE_CALIBRATE; + + if (of_property_read_bool(dn, "nvidia,one-shot-calibrate")) + td->cfg_flags |= DFLL_ONE_SHOT_CALIBRATE; + + if (of_property_read_bool(dn, "nvidia,idle-override")) + td->cfg_flags |= DFLL_HAS_IDLE_OVERRIDE; + + td->one_shot_settle_time = DFLL_ONE_SHOT_SETTLE_TIME; + of_property_read_u32(dn, "nvidia,one-shot-settle-time", + &td->one_shot_settle_time); + + if (of_property_read_string(dn, "clock-output-names", &td->output_clock_name)) { dev_err(td->dev, "missing clock-output-names property\n"); ok = false; @@ -1923,6 +3844,129 @@ static int dfll_fetch_common_params(struct tegra_dfll *td) return ok ? 0 : -EINVAL; } + +/* + * tegra_dfll_suspend + * @pdev: DFLL instance + * + * dfll controls clock/voltage to other devices, including CPU. Therefore, + * dfll driver pm suspend callback does not stop cl-dvfs operations. It is + * only used to enforce cold voltage limit, since SoC may cool down during + * suspend without waking up. The correct temperature zone after suspend will + * be updated via dfll cooling device interface during resume of temperature + * sensor. + */ +void tegra_dfll_suspend(struct platform_device *pdev) +{ + struct tegra_dfll *td = dev_get_drvdata(&pdev->dev); + + if (!td) + return; + + if (td->mode <= DFLL_DISABLED) + return; + + td->thermal_cap_index = + tegra_dfll_count_thermal_states(td, TEGRA_DFLL_THERMAL_CAP); + td->thermal_floor_index = 0; + set_dvco_rate_min(td, &td->last_req); + + td->resume_mode = td->mode; + switch (td->mode) { + case DFLL_CLOSED_LOOP: + dfll_set_close_loop_config(td, &td->last_req); + dfll_set_frequency_request(td, &td->last_req); + + _dfll_unlock(td); + break; + default: + break; + } +} + +/** + * tegra_dfll_resume - reprogram the DFLL after context-loss + * @pdev: DFLL instance + * + * Re-initialize and enable target device clock in open loop mode. Called + * directly from SoC clock resume syscore operation. Closed loop will be + * re-entered in platform syscore ops as well after CPU clock source is + * switched to DFLL in open loop. + */ +void tegra_dfll_resume(struct platform_device *pdev, bool on_dfll) +{ + struct tegra_dfll *td = dev_get_drvdata(&pdev->dev); + + if (!td) + return; + + if (on_dfll) { + if (td->resume_mode == DFLL_CLOSED_LOOP) + _dfll_lock(td); + td->resume_mode = DFLL_DISABLED; + return; + } + + reset_control_deassert(td->dvco_rst); + + pm_runtime_get(td->dev); + + /* Re-init DFLL */ + dfll_init_out_if(td); + dfll_set_default_params(td); + dfll_set_open_loop_config(td); + + pm_runtime_put(td->dev); + + /* Restore last request and mode up to open loop */ + switch (td->resume_mode) { + case DFLL_CLOSED_LOOP: + case DFLL_OPEN_LOOP: + dfll_set_mode(td, DFLL_OPEN_LOOP); + if (td->pmu_if == TEGRA_DFLL_PMU_I2C) + dfll_i2c_set_output_enabled(td, false); + break; + default: + break; + } +} + +/** + * tegra_dfll_resume_tuning - restart DFLL tuning timer + * @dev: DFLL instance + * + * Re-start DFLL tuning timer if DFLL resume has moved DFLL out + * of low range but has not reached high range, yet. Timer + * cannot be restarted by DFLL resume itself, since it is + * happening before timekeeping resume. + */ +int tegra_dfll_resume_tuning(struct device *dev) +{ + struct tegra_dfll *td = dev_get_drvdata(dev); + unsigned long flags; + + spin_lock_irqsave(&td->lock, flags); + + if ((td->tune_range > DFLL_TUNE_LOW) && + (td->tune_range < DFLL_TUNE_HIGH)) { + hrtimer_start(&td->tune_timer, td->tune_delay, + HRTIMER_MODE_REL); + } + + if (td->cfg_flags & DFLL_ONE_SHOT_CALIBRATE) { + ktime_t now = ktime_get(); + + if (ktime_ms_delta(now, td->one_shot_invalid_time_start) > + td->one_shot_invalid_time * 1000L) { + td->one_shot_invalid_time_start = now; + dfll_invalidate_one_shot(td); + } + } + spin_unlock_irqrestore(&td->lock, flags); + + return 0; +} + /* * API exported to per-SoC platform drivers */ @@ -1941,7 +3985,7 @@ int tegra_dfll_register(struct platform_device *pdev, { struct resource *mem; struct tegra_dfll *td; - int ret; + int ret, t_floor_size; if (!soc) { dev_err(&pdev->dev, "no tegra_dfll_soc_data provided\n"); @@ -1951,17 +3995,25 @@ int tegra_dfll_register(struct platform_device *pdev, td = devm_kzalloc(&pdev->dev, sizeof(*td), GFP_KERNEL); if (!td) return -ENOMEM; + + t_floor_size = sizeof(unsigned long) * + (soc->thermal_floor_table_size + 1); + td->dvco_rate_floors = devm_kzalloc(&pdev->dev, t_floor_size, + GFP_KERNEL); + if (!td->dvco_rate_floors) + return -ENOMEM; + + td->tune_high_dvco_rate_floors = devm_kzalloc(&pdev->dev, t_floor_size, + GFP_KERNEL); + if (!td->dvco_rate_floors) + return -ENOMEM; + + td->dev = &pdev->dev; platform_set_drvdata(pdev, td); td->soc = soc; - td->dfll_rst = devm_reset_control_get_optional(td->dev, "dfll"); - if (IS_ERR(td->dfll_rst)) { - dev_err(td->dev, "couldn't get dfll reset\n"); - return PTR_ERR(td->dfll_rst); - } - 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"); @@ -1995,6 +4047,12 @@ int tegra_dfll_register(struct platform_device *pdev, return ret; } + ret = find_lut_index_for_rate(td, td->out_rate_max); + if (ret < 0) { + dev_err(td->dev, "built invalid LUT\n"); + return ret; + } + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!mem) { dev_err(td->dev, "no control register resource\n"); @@ -2057,13 +4115,32 @@ int tegra_dfll_register(struct platform_device *pdev, if (ret) return ret; + spin_lock_init(&td->lock); + ret = dfll_register_clk(td); if (ret) { dev_err(&pdev->dev, "DFLL clk registration failed\n"); return ret; } + /* Initialize tuning timer */ + hrtimer_setup(&td->tune_timer, &dfll_tune_timer_cb, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + td->tune_delay = ktime_set(0, DFLL_TUNE_HIGH_DELAY * 1000); + td->tune_ramp_delay = ktime_set(0, td->one_shot_settle_time * 1000); + + /* Initialize Vmin calibration timer */ + timer_setup(&td->calibration_timer, calibration_timer_cb, TIMER_DEFERRABLE); + td->calibration_delay = usecs_to_jiffies(DFLL_CALIBR_TIME); + + td->one_shot_invalid_time_start = ktime_get(); + td->one_shot_invalid_time = DFLL_ONE_SHOT_INVALID_TIME; + +#ifdef CONFIG_DEBUG_FS dfll_debug_init(td); +#endif + //tegra_clk_debugfs_add(td->dfll_clk); + + tegra_dfll_dev = td; return 0; } @@ -2081,16 +4158,15 @@ struct tegra_dfll_soc_data *tegra_dfll_unregister(struct platform_device *pdev) { struct tegra_dfll *td = platform_get_drvdata(pdev); - /* - * Note that exiting early here doesn't prevent unbinding the driver. - * Exiting early here only leaks some resources. - */ + /* Try to prevent removal while the DFLL is active */ if (td->mode != DFLL_DISABLED) { dev_err(&pdev->dev, "must disable DFLL before removing driver\n"); return ERR_PTR(-EBUSY); } + tegra_dfll_dev = NULL; + debugfs_remove_recursive(td->debugfs_dir); dfll_unregister_clk(td); @@ -2101,7 +4177,6 @@ struct tegra_dfll_soc_data *tegra_dfll_unregister(struct platform_device *pdev) clk_unprepare(td->i2c_clk); reset_control_assert(td->dvco_rst); - reset_control_assert(td->dfll_rst); return td->soc; } diff --git a/drivers/clk/tegra/clk-dfll.h b/drivers/clk/tegra/clk-dfll.h index fb209eb5f365..69260357ade1 100644 --- a/drivers/clk/tegra/clk-dfll.h +++ b/drivers/clk/tegra/clk-dfll.h @@ -13,36 +13,64 @@ #include #include #include +#include #include "cvb.h" +struct thermal_tv; + /** * struct tegra_dfll_soc_data - SoC-specific hooks/integration for the DFLL driver * @dev: struct device * that holds the OPP table for the DFLL * @max_freq: maximum frequency supported on this SoC * @cvb: CPU frequency table for this SoC - * @alignment: parameters of the regulator step and offset - * @init_clock_trimmers: callback to initialize clock trimmers - * @set_clock_trimmers_high: callback to tune clock trimmers for high voltage * @set_clock_trimmers_low: callback to tune clock trimmers for low voltage + * @tune0_low: DFLL tuning register 0 (low voltage range) + * @tune0_high: DFLL tuning register 0 (high voltage range) + * @tune1: DFLL tuning register 1 + * @set_clock_trimmers_high: fn ptr to tune clock trimmers for high voltage + * @set_clock_trimmers_low: fn ptr to tune clock trimmers for low voltage + * @thermal_floor_table: table mapping a given temperature to a minimum voltage + * @thermal_cap_table: table mapping a given temperature to a maximum voltage + * @thermal_floor_table_size: size of thermal_floor_table + * @thermal_cap_table_size: size of thermal_cap_table */ 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_low; + u32 tune1_high; + unsigned int tune_high_margin_millivolts; void (*init_clock_trimmers)(void); void (*set_clock_trimmers_high)(void); void (*set_clock_trimmers_low)(void); + const struct thermal_tv *thermal_floor_table; + const struct thermal_tv *thermal_cap_table; + unsigned int thermal_floor_table_size; + unsigned int thermal_cap_table_size; }; + +/* + * These thermal boundaries are not set in thermal zone as trip-points, but + * must be below/above all other actually set DFLL thermal trip-points. + */ +#define DFLL_THERMAL_CAP_NOCAP 0 +#define DFLL_THERMAL_FLOOR_NOFLOOR 125000 + int tegra_dfll_register(struct platform_device *pdev, struct tegra_dfll_soc_data *soc); struct tegra_dfll_soc_data *tegra_dfll_unregister(struct platform_device *pdev); +void tegra_dfll_suspend(struct platform_device *pdev); +void tegra_dfll_resume(struct platform_device *pdev, bool on_dfll); +int tegra_dfll_resume_tuning(struct device *dev); int tegra_dfll_runtime_suspend(struct device *dev); int tegra_dfll_runtime_resume(struct device *dev); -int tegra_dfll_suspend(struct device *dev); -int tegra_dfll_resume(struct device *dev); #endif /* __DRIVERS_CLK_TEGRA_CLK_DFLL_H */ diff --git a/drivers/clk/tegra/clk-tegra124-dfll-fcpu.c b/drivers/clk/tegra/clk-tegra124-dfll-fcpu.c index d12fd170d6bf..bdc8b29bbea9 100644 --- a/drivers/clk/tegra/clk-tegra124-dfll-fcpu.c +++ b/drivers/clk/tegra/clk-tegra124-dfll-fcpu.c @@ -17,15 +17,18 @@ #include #include +#include +#include + #include "clk.h" #include "clk-dfll.h" #include "cvb.h" - struct dfll_fcpu_data { const unsigned long *cpu_max_freq_table; unsigned int cpu_max_freq_table_size; const struct cvb_table *cpu_cvb_tables; unsigned int cpu_cvb_tables_size; + const struct thermal_table *cpu_thermal_table; }; /* Maximum CPU frequency, indexed by CPU speedo id */ @@ -42,6 +45,9 @@ static const struct cvb_table tegra124_cpu_cvb_tables[] = { .process_id = -1, .min_millivolts = 900, .max_millivolts = 1260, + .alignment = { + .step_uv = 10000, /* 10mV */ + }, .speedo_scale = 100, .voltage_scale = 1000, .entries = { @@ -97,142 +103,142 @@ static const unsigned long tegra210_cpu_max_freq_table[] = { .speedo_scale = 100, \ .voltage_scale = 1000, \ .entries = { \ - { 204000000UL, { 1007452, -23865, 370 } }, \ - { 306000000UL, { 1052709, -24875, 370 } }, \ - { 408000000UL, { 1099069, -25895, 370 } }, \ - { 510000000UL, { 1146534, -26905, 370 } }, \ - { 612000000UL, { 1195102, -27915, 370 } }, \ - { 714000000UL, { 1244773, -28925, 370 } }, \ - { 816000000UL, { 1295549, -29935, 370 } }, \ - { 918000000UL, { 1347428, -30955, 370 } }, \ - { 1020000000UL, { 1400411, -31965, 370 } }, \ - { 1122000000UL, { 1454497, -32975, 370 } }, \ - { 1224000000UL, { 1509687, -33985, 370 } }, \ - { 1326000000UL, { 1565981, -35005, 370 } }, \ - { 1428000000UL, { 1623379, -36015, 370 } }, \ - { 1530000000UL, { 1681880, -37025, 370 } }, \ - { 1632000000UL, { 1741485, -38035, 370 } }, \ - { 1734000000UL, { 1802194, -39055, 370 } }, \ - { 1836000000UL, { 1864006, -40065, 370 } }, \ - { 1912500000UL, { 1910780, -40815, 370 } }, \ - { 2014500000UL, { 1227000, 0, 0 } }, \ - { 2218500000UL, { 1227000, 0, 0 } }, \ - { 0UL, { 0, 0, 0 } }, \ + {204000000UL, {1007452, -23865, 370} }, \ + {306000000UL, {1052709, -24875, 370} }, \ + {408000000UL, {1099069, -25895, 370} }, \ + {510000000UL, {1146534, -26905, 370} }, \ + {612000000UL, {1195102, -27915, 370} }, \ + {714000000UL, {1244773, -28925, 370} }, \ + {816000000UL, {1295549, -29935, 370} }, \ + {918000000UL, {1347428, -30955, 370} }, \ + {1020000000UL, {1400411, -31965, 370} }, \ + {1122000000UL, {1454497, -32975, 370} }, \ + {1224000000UL, {1509687, -33985, 370} }, \ + {1326000000UL, {1565981, -35005, 370} }, \ + {1428000000UL, {1623379, -36015, 370} }, \ + {1530000000UL, {1681880, -37025, 370} }, \ + {1632000000UL, {1741485, -38035, 370} }, \ + {1734000000UL, {1802194, -39055, 370} }, \ + {1836000000UL, {1864006, -40065, 370} }, \ + {1912500000UL, {1910780, -40815, 370} }, \ + {2014500000UL, {1227000, 0, 0} }, \ + {2218500000UL, {1227000, 0, 0} }, \ + {0, { 0, 0, 0} }, \ } #define CPU_CVB_TABLE_XA \ .speedo_scale = 100, \ .voltage_scale = 1000, \ .entries = { \ - { 204000000UL, { 1250024, -39785, 565 } }, \ - { 306000000UL, { 1297556, -41145, 565 } }, \ - { 408000000UL, { 1346718, -42505, 565 } }, \ - { 510000000UL, { 1397511, -43855, 565 } }, \ - { 612000000UL, { 1449933, -45215, 565 } }, \ - { 714000000UL, { 1503986, -46575, 565 } }, \ - { 816000000UL, { 1559669, -47935, 565 } }, \ - { 918000000UL, { 1616982, -49295, 565 } }, \ - { 1020000000UL, { 1675926, -50645, 565 } }, \ - { 1122000000UL, { 1736500, -52005, 565 } }, \ - { 1224000000UL, { 1798704, -53365, 565 } }, \ - { 1326000000UL, { 1862538, -54725, 565 } }, \ - { 1428000000UL, { 1928003, -56085, 565 } }, \ - { 1530000000UL, { 1995097, -57435, 565 } }, \ - { 1606500000UL, { 2046149, -58445, 565 } }, \ - { 1632000000UL, { 2063822, -58795, 565 } }, \ - { 0UL, { 0, 0, 0 } }, \ + {204000000UL, {1250024, -39785, 565} }, \ + {306000000UL, {1297556, -41145, 565} }, \ + {408000000UL, {1346718, -42505, 565} }, \ + {510000000UL, {1397511, -43855, 565} }, \ + {612000000UL, {1449933, -45215, 565} }, \ + {714000000UL, {1503986, -46575, 565} }, \ + {816000000UL, {1559669, -47935, 565} }, \ + {918000000UL, {1616982, -49295, 565} }, \ + {1020000000UL, {1675926, -50645, 565} }, \ + {1122000000UL, {1736500, -52005, 565} }, \ + {1224000000UL, {1798704, -53365, 565} }, \ + {1326000000UL, {1862538, -54725, 565} }, \ + {1428000000UL, {1928003, -56085, 565} }, \ + {1530000000UL, {1995097, -57435, 565} }, \ + {1606500000UL, {2046149, -58445, 565} }, \ + {1632000000UL, {2063822, -58795, 565} }, \ + {0, { 0, 0, 0} }, \ } #define CPU_CVB_TABLE_EUCM1 \ .speedo_scale = 100, \ .voltage_scale = 1000, \ .entries = { \ - { 204000000UL, { 734429, 0, 0 } }, \ - { 306000000UL, { 768191, 0, 0 } }, \ - { 408000000UL, { 801953, 0, 0 } }, \ - { 510000000UL, { 835715, 0, 0 } }, \ - { 612000000UL, { 869477, 0, 0 } }, \ - { 714000000UL, { 903239, 0, 0 } }, \ - { 816000000UL, { 937001, 0, 0 } }, \ - { 918000000UL, { 970763, 0, 0 } }, \ - { 1020000000UL, { 1004525, 0, 0 } }, \ - { 1122000000UL, { 1038287, 0, 0 } }, \ - { 1224000000UL, { 1072049, 0, 0 } }, \ - { 1326000000UL, { 1105811, 0, 0 } }, \ - { 1428000000UL, { 1130000, 0, 0 } }, \ - { 1555500000UL, { 1130000, 0, 0 } }, \ - { 1632000000UL, { 1170000, 0, 0 } }, \ - { 1734000000UL, { 1227500, 0, 0 } }, \ - { 0UL, { 0, 0, 0 } }, \ + {204000000UL, {734429, 0, 0} }, \ + {306000000UL, {768191, 0, 0} }, \ + {408000000UL, {801953, 0, 0} }, \ + {510000000UL, {835715, 0, 0} }, \ + {612000000UL, {869477, 0, 0} }, \ + {714000000UL, {903239, 0, 0} }, \ + {816000000UL, {937001, 0, 0} }, \ + {918000000UL, {970763, 0, 0} }, \ + {1020000000UL, {1004525, 0, 0} }, \ + {1122000000UL, {1038287, 0, 0} }, \ + {1224000000UL, {1072049, 0, 0} }, \ + {1326000000UL, {1105811, 0, 0} }, \ + {1428000000UL, {1130000, 0, 0} }, \ + {1555500000UL, {1130000, 0, 0} }, \ + {1632000000UL, {1170000, 0, 0} }, \ + {1734000000UL, {1227500, 0, 0} }, \ + {0, { 0, 0, 0} }, \ } #define CPU_CVB_TABLE_EUCM2 \ .speedo_scale = 100, \ .voltage_scale = 1000, \ .entries = { \ - { 204000000UL, { 742283, 0, 0 } }, \ - { 306000000UL, { 776249, 0, 0 } }, \ - { 408000000UL, { 810215, 0, 0 } }, \ - { 510000000UL, { 844181, 0, 0 } }, \ - { 612000000UL, { 878147, 0, 0 } }, \ - { 714000000UL, { 912113, 0, 0 } }, \ - { 816000000UL, { 946079, 0, 0 } }, \ - { 918000000UL, { 980045, 0, 0 } }, \ - { 1020000000UL, { 1014011, 0, 0 } }, \ - { 1122000000UL, { 1047977, 0, 0 } }, \ - { 1224000000UL, { 1081943, 0, 0 } }, \ - { 1326000000UL, { 1090000, 0, 0 } }, \ - { 1479000000UL, { 1090000, 0, 0 } }, \ - { 1555500000UL, { 1162000, 0, 0 } }, \ - { 1683000000UL, { 1195000, 0, 0 } }, \ - { 0UL, { 0, 0, 0 } }, \ + {204000000UL, {742283, 0, 0} }, \ + {306000000UL, {776249, 0, 0} }, \ + {408000000UL, {810215, 0, 0} }, \ + {510000000UL, {844181, 0, 0} }, \ + {612000000UL, {878147, 0, 0} }, \ + {714000000UL, {912113, 0, 0} }, \ + {816000000UL, {946079, 0, 0} }, \ + {918000000UL, {980045, 0, 0} }, \ + {1020000000UL, {1014011, 0, 0} }, \ + {1122000000UL, {1047977, 0, 0} }, \ + {1224000000UL, {1081943, 0, 0} }, \ + {1326000000UL, {1090000, 0, 0} }, \ + {1479000000UL, {1090000, 0, 0} }, \ + {1555500000UL, {1162000, 0, 0} }, \ + {1683000000UL, {1195000, 0, 0} }, \ + {0, { 0, 0, 0} }, \ } #define CPU_CVB_TABLE_EUCM2_JOINT_RAIL \ .speedo_scale = 100, \ .voltage_scale = 1000, \ .entries = { \ - { 204000000UL, { 742283, 0, 0 } }, \ - { 306000000UL, { 776249, 0, 0 } }, \ - { 408000000UL, { 810215, 0, 0 } }, \ - { 510000000UL, { 844181, 0, 0 } }, \ - { 612000000UL, { 878147, 0, 0 } }, \ - { 714000000UL, { 912113, 0, 0 } }, \ - { 816000000UL, { 946079, 0, 0 } }, \ - { 918000000UL, { 980045, 0, 0 } }, \ - { 1020000000UL, { 1014011, 0, 0 } }, \ - { 1122000000UL, { 1047977, 0, 0 } }, \ - { 1224000000UL, { 1081943, 0, 0 } }, \ - { 1326000000UL, { 1090000, 0, 0 } }, \ - { 1479000000UL, { 1090000, 0, 0 } }, \ - { 1504500000UL, { 1120000, 0, 0 } }, \ - { 0UL, { 0, 0, 0 } }, \ + {204000000UL, {742283, 0, 0} }, \ + {306000000UL, {776249, 0, 0} }, \ + {408000000UL, {810215, 0, 0} }, \ + {510000000UL, {844181, 0, 0} }, \ + {612000000UL, {878147, 0, 0} }, \ + {714000000UL, {912113, 0, 0} }, \ + {816000000UL, {946079, 0, 0} }, \ + {918000000UL, {980045, 0, 0} }, \ + {1020000000UL, {1014011, 0, 0} }, \ + {1122000000UL, {1047977, 0, 0} }, \ + {1224000000UL, {1081943, 0, 0} }, \ + {1326000000UL, {1090000, 0, 0} }, \ + {1479000000UL, {1090000, 0, 0} }, \ + {1504500000UL, {1120000, 0, 0} }, \ + {0, { 0, 0, 0} }, \ } #define CPU_CVB_TABLE_ODN \ .speedo_scale = 100, \ .voltage_scale = 1000, \ .entries = { \ - { 204000000UL, { 721094, 0, 0 } }, \ - { 306000000UL, { 754040, 0, 0 } }, \ - { 408000000UL, { 786986, 0, 0 } }, \ - { 510000000UL, { 819932, 0, 0 } }, \ - { 612000000UL, { 852878, 0, 0 } }, \ - { 714000000UL, { 885824, 0, 0 } }, \ - { 816000000UL, { 918770, 0, 0 } }, \ - { 918000000UL, { 915716, 0, 0 } }, \ - { 1020000000UL, { 984662, 0, 0 } }, \ - { 1122000000UL, { 1017608, 0, 0 } }, \ - { 1224000000UL, { 1050554, 0, 0 } }, \ - { 1326000000UL, { 1083500, 0, 0 } }, \ - { 1428000000UL, { 1116446, 0, 0 } }, \ - { 1581000000UL, { 1130000, 0, 0 } }, \ - { 1683000000UL, { 1168000, 0, 0 } }, \ - { 1785000000UL, { 1227500, 0, 0 } }, \ - { 0UL, { 0, 0, 0 } }, \ + {204000000UL, {721094, 0, 0} }, \ + {306000000UL, {754040, 0, 0} }, \ + {408000000UL, {786986, 0, 0} }, \ + {510000000UL, {819932, 0, 0} }, \ + {612000000UL, {852878, 0, 0} }, \ + {714000000UL, {885824, 0, 0} }, \ + {816000000UL, {918770, 0, 0} }, \ + {918000000UL, {915716, 0, 0} }, \ + {1020000000UL, {984662, 0, 0} }, \ + {1122000000UL, {1017608, 0, 0} }, \ + {1224000000UL, {1050554, 0, 0} }, \ + {1326000000UL, {1083500, 0, 0} }, \ + {1428000000UL, {1116446, 0, 0} }, \ + {1581000000UL, {1130000, 0, 0} }, \ + {1683000000UL, {1168000, 0, 0} }, \ + {1785000000UL, {1227500, 0, 0} }, \ + {0, { 0, 0, 0} }, \ } -static struct cvb_table tegra210_cpu_cvb_tables[] = { +struct cvb_table tegra210_cpu_cvb_tables[] = { { .speedo_id = 10, .process_id = 0, @@ -505,94 +511,116 @@ static const unsigned long tegra210b01_cpu_max_freq_table[] = { .speedo_scale = 100, \ .voltage_scale = 1000, \ .entries = { \ - { 204000000UL, { 732856, -17335, 113 } }, \ - { 306000000UL, { 760024, -18195, 113 } }, \ - { 408000000UL, { 789258, -19055, 113 } }, \ - { 510000000UL, { 820558, -19915, 113 } }, \ - { 612000000UL, { 853926, -20775, 113 } }, \ - { 714000000UL, { 889361, -21625, 113 } }, \ - { 816000000UL, { 926862, -22485, 113 } }, \ - { 918000000UL, { 966431, -23345, 113 } }, \ - { 1020000000UL, { 1008066, -24205, 113 } }, \ - { 1122000000UL, { 1051768, -25065, 113 } }, \ - { 1224000000UL, { 1097537, -25925, 113 } }, \ - { 1326000000UL, { 1145373, -26785, 113 } }, \ - { 1428000000UL, { 1195276, -27645, 113 } }, \ - { 1581000000UL, { 1274006, -28935, 113 } }, \ - { 1683000000UL, { 1329076, -29795, 113 } }, \ - { 1785000000UL, { 1386213, -30655, 113 } }, \ - { 1887000000UL, { 1445416, -31515, 113 } }, \ - { 1963500000UL, { 1490873, -32155, 113 } }, \ - { 2065500000UL, { 1553683, -33015, 113 } }, \ - { 2091000000UL, { 1580725, -33235, 113 } }, \ - { 0UL, { 0, 0, 0 } }, \ - } + /* f c0, c1, c2 */ \ + { 204000000UL, { 732856, -17335, 113 } }, \ + { 306000000UL, { 760024, -18195, 113 } }, \ + { 408000000UL, { 789258, -19055, 113 } }, \ + { 510000000UL, { 820558, -19915, 113 } }, \ + { 612000000UL, { 853926, -20775, 113 } }, \ + { 714000000UL, { 889361, -21625, 113 } }, \ + { 816000000UL, { 926862, -22485, 113 } }, \ + { 918000000UL, { 966431, -23345, 113 } }, \ + { 1020000000UL, { 1008066, -24205, 113 } }, \ + { 1122000000UL, { 1051768, -25065, 113 } }, \ + { 1224000000UL, { 1097537, -25925, 113 } }, \ + { 1326000000UL, { 1145373, -26785, 113 } }, \ + { 1428000000UL, { 1195276, -27645, 113 } }, \ + { 1581000000UL, { 1274006, -28935, 113 } }, \ + { 1683000000UL, { 1329076, -29795, 113 } }, \ + { 1785000000UL, { 1386213, -30655, 113 } }, \ + { 1887000000UL, { 1445416, -31515, 113 } }, \ + { 1963500000UL, { 1490873, -32155, 113 } }, \ + { 2065500000UL, { 1553683, -33015, 113 } }, \ + { 2091000000UL, { 1580725, -33235, 113 } }, \ + { 0, { } }, \ + }, \ + .vmin_coefficients = { 600000, 0, 0 }, \ + .cpu_dfll_data = { \ + .tune0_low = 0x0000FFA0, \ + .tune0_high = 0x0000FFFF, \ + .tune1_low = 0x21107FF, \ + .tune_high_min_millivolts = 850, \ + .tune_high_margin_millivolts = 38, \ + .dvco_calibration_max = ULONG_MAX, \ + }, \ + .cvb_version = "FCPU Table - p4v3-AggressiveSLT" #define CPUB01_CVB_TABLE_SLT_B0 \ .speedo_scale = 100, \ .voltage_scale = 1000, \ .entries = { \ - { 204000000UL, { 732856, -17335, 113 } }, \ - { 306000000UL, { 760024, -18195, 113 } }, \ - { 408000000UL, { 789258, -19055, 113 } }, \ - { 510000000UL, { 820558, -19915, 113 } }, \ - { 612000000UL, { 853926, -20775, 113 } }, \ - { 714000000UL, { 889361, -21625, 113 } }, \ - { 816000000UL, { 926862, -22485, 113 } }, \ - { 918000000UL, { 966431, -23345, 113 } }, \ - { 1020000000UL, { 1008066, -24205, 113 } }, \ - { 1122000000UL, { 1051768, -25065, 113 } }, \ - { 1224000000UL, { 1097537, -25925, 113 } }, \ - { 1326000000UL, { 1145373, -26785, 113 } }, \ - { 1428000000UL, { 1195276, -27645, 113 } }, \ - { 1581000000UL, { 1274006, -28935, 113 } }, \ - { 1683000000UL, { 1329076, -29795, 113 } }, \ - { 1785000000UL, { 1386213, -30655, 113 } }, \ - { 1887000000UL, { 1445416, -31515, 113 } }, \ - { 1963500000UL, { 1490873, -32155, 113 } }, \ - { 2065500000UL, { 1553683, -33015, 113 } }, \ - { 2091000000UL, { 1580725, -33235, 113 } }, \ - { 0UL, { 0, 0, 0 } }, \ - } + /* f c0, c1, c2 */ \ + { 204000000UL, { 732856, -17335, 113 } }, \ + { 306000000UL, { 760024, -18195, 113 } }, \ + { 408000000UL, { 789258, -19055, 113 } }, \ + { 510000000UL, { 820558, -19915, 113 } }, \ + { 612000000UL, { 853926, -20775, 113 } }, \ + { 714000000UL, { 889361, -21625, 113 } }, \ + { 816000000UL, { 926862, -22485, 113 } }, \ + { 918000000UL, { 966431, -23345, 113 } }, \ + { 1020000000UL, { 1008066, -24205, 113 } }, \ + { 1122000000UL, { 1051768, -25065, 113 } }, \ + { 1224000000UL, { 1097537, -25925, 113 } }, \ + { 1326000000UL, { 1145373, -26785, 113 } }, \ + { 1428000000UL, { 1195276, -27645, 113 } }, \ + { 1581000000UL, { 1274006, -28935, 113 } }, \ + { 1683000000UL, { 1329076, -29795, 113 } }, \ + { 1785000000UL, { 1386213, -30655, 113 } }, \ + { 1887000000UL, { 1445416, -31515, 113 } }, \ + { 1963500000UL, { 1490873, -32155, 113 } }, \ + { 2065500000UL, { 1553683, -33015, 113 } }, \ + { 2091000000UL, { 1580725, -33235, 113 } }, \ + { 0, { } }, \ + }, \ + .vmin_coefficients = { 600000, 0, 0 }, \ + .cpu_dfll_data = { \ + .tune0_low = 0x0000FF90, \ + .tune0_high = 0x0000FFFF, \ + .tune1_low = 0x21107FF, \ + .tune_high_min_millivolts = 850, \ + .tune_high_margin_millivolts = 38, \ + .dvco_calibration_max = ULONG_MAX, \ + }, \ + .cvb_version = "FCPU Table - p4v3-AggressiveSLT" #define CPUB01_CVB_TABLE \ .speedo_scale = 100, \ .voltage_scale = 1000, \ .entries = { \ - { 204000000UL, { 721589, -12695, 27 } }, \ - { 306000000UL, { 747134, -14195, 27 } }, \ - { 408000000UL, { 776324, -15705, 27 } }, \ - { 510000000UL, { 809160, -17205, 27 } }, \ - { 612000000UL, { 845641, -18715, 27 } }, \ - { 714000000UL, { 885768, -20215, 27 } }, \ - { 816000000UL, { 929540, -21725, 27 } }, \ - { 918000000UL, { 976958, -23225, 27 } }, \ - { 1020000000UL, { 1028021, -24725, 27 } }, \ - { 1122000000UL, { 1082730, -26235, 27 } }, \ - { 1224000000UL, { 1141084, -27735, 27 } }, \ - { 1326000000UL, { 1203084, -29245, 27 } }, \ - { 1428000000UL, { 1268729, -30745, 27 } }, \ - { 1581000000UL, { 1374032, -33005, 27 } }, \ - { 1683000000UL, { 1448791, -34505, 27 } }, \ - { 1785000000UL, { 1527196, -36015, 27 } }, \ - { 1887000000UL, { 1609246, -37515, 27 } }, \ - { 1963500000UL, { 1675751, -38635, 27 } }, \ - { 2014500000UL, { 1716501, -39395, 27 } }, \ - { 0UL, { 0, 0, 0 } }, \ - } + /* f c0, c1, c2 */ \ + { 204000000UL, { 721589, -12695, 27 } }, \ + { 306000000UL, { 747134, -14195, 27 } }, \ + { 408000000UL, { 776324, -15705, 27 } }, \ + { 510000000UL, { 809160, -17205, 27 } }, \ + { 612000000UL, { 845641, -18715, 27 } }, \ + { 714000000UL, { 885768, -20215, 27 } }, \ + { 816000000UL, { 929540, -21725, 27 } }, \ + { 918000000UL, { 976958, -23225, 27 } }, \ + { 1020000000UL, { 1028021, -24725, 27 } }, \ + { 1122000000UL, { 1082730, -26235, 27 } }, \ + { 1224000000UL, { 1141084, -27735, 27 } }, \ + { 1326000000UL, { 1203084, -29245, 27 } }, \ + { 1428000000UL, { 1268729, -30745, 27 } }, \ + { 1581000000UL, { 1374032, -33005, 27 } }, \ + { 1683000000UL, { 1448791, -34505, 27 } }, \ + { 1785000000UL, { 1527196, -36015, 27 } }, \ + { 1887000000UL, { 1609246, -37515, 27 } }, \ + { 1963500000UL, { 1675751, -38635, 27 } }, \ + { 2014500000UL, { 1716501, -39395, 27 } }, \ + { 0, { } }, \ + }, \ + .vmin_coefficients = { 620000, 0, 0 }, \ + .cpu_dfll_data = { \ + .tune0_low = 0x0000FFCF, \ + .tune1_low = 0x012207FF, \ + .tune1_high = 0x03FFF7FF, \ + .tune_high_min_millivolts = 850, \ + .tune_high_margin_millivolts = 38, \ + .dvco_calibration_max = ULONG_MAX, \ + }, \ + .cvb_version = "FCPU Table - p4v3" -static struct cvb_table tegra210b01_cpu_cvb_tables[] = { - { - .speedo_id = 3, - .process_id = -1, - .max_millivolts = 1120, - CPUB01_CVB_TABLE, - .cpu_dfll_data = { - .tune0_low = 0x0000ffcf, - .tune1_low = 0x012207ff, - .tune_high_min_millivolts = 850, - } - }, +struct cvb_table tegra210b01_cpu_cvb_tables[] = { { .speedo_id = 3, .process_id = -1, @@ -604,37 +632,75 @@ static struct cvb_table tegra210b01_cpu_cvb_tables[] = { .process_id = 1, .max_millivolts = 1120, CPUB01_CVB_TABLE_SLT_B1, - .cpu_dfll_data = { - .tune0_low = 0x0000ffa0, - .tune0_high = 0x0000ffff, - .tune1_low = 0x021107ff, - .tune_high_min_millivolts = 850, - } }, { .speedo_id = 2, .process_id = 0, .max_millivolts = 1120, CPUB01_CVB_TABLE_SLT_B0, - .cpu_dfll_data = { - .tune0_low = 0x0000ff90, - .tune0_high = 0x0000ffff, - .tune1_low = 0x021107ff, - .tune_high_min_millivolts = 850, - } }, { .speedo_id = -1, .process_id = -1, .max_millivolts = 1120, CPUB01_CVB_TABLE, - .cpu_dfll_data = { - .tune0_low = 0x0000ffcf, - .tune1_low = 0x012207ff, - .tune_high_min_millivolts = 850, - } }, }; + +static struct thermal_tv tegra210_thermal_floor_table[] = { + {TEGRA210_DFLL_THERMAL_FLOOR_0 / 1000, 950}, + {DFLL_THERMAL_FLOOR_NOFLOOR / 1000, 0}, +}; + +static const struct thermal_tv tegra210_thermal_cap_table[] = { + {DFLL_THERMAL_CAP_NOCAP / 1000, INT_MAX}, + {TEGRA210_DFLL_THERMAL_CAP_0 / 1000, 1170}, + {TEGRA210_DFLL_THERMAL_CAP_1 / 1000, 1132}, +}; + +static const struct thermal_tv tegra210_thermal_cap_ucm2_table[] = { + {DFLL_THERMAL_CAP_NOCAP / 1000, INT_MAX}, + {TEGRA210_DFLL_THERMAL_CAP_0 / 1000, 1162}, + {TEGRA210_DFLL_THERMAL_CAP_1 / 1000, 1090}, +}; + +static const struct thermal_table tegra210_cpu_thermal_table = { + .thermal_floor_table = tegra210_thermal_floor_table, + .thermal_floor_table_size = ARRAY_SIZE(tegra210_thermal_floor_table), + .coefficients = { {800000, 0, 0}, 0, 0, 0 }, + .speedo_scale = 100, + .voltage_scale = 1000, + .temp_scale = 10, + .thermal_cap_table = tegra210_thermal_cap_table, + .thermal_cap_table_size = ARRAY_SIZE(tegra210_thermal_cap_table), + .thermal_cap_ucm2_table = tegra210_thermal_cap_ucm2_table, + .thermal_cap_ucm2_table_size = ARRAY_SIZE(tegra210_thermal_cap_ucm2_table), +}; + +static struct thermal_tv tegra210b01_thermal_floor_table[] = { + {TEGRA210B01_DFLL_THERMAL_FLOOR_0 / 1000, 800}, + {TEGRA210B01_DFLL_THERMAL_FLOOR_1 / 1000, 0}, + {DFLL_THERMAL_FLOOR_NOFLOOR / 1000, 0}, +}; + +static const struct thermal_tv tegra210b01_thermal_cap_table[] = { + {DFLL_THERMAL_CAP_NOCAP / 1000, INT_MAX}, + {TEGRA210B01_DFLL_THERMAL_CAP_0 / 1000, 1060}, + {TEGRA210B01_DFLL_THERMAL_CAP_1 / 1000, 1010}, +}; + +static const struct thermal_table tegra210b01_cpu_thermal_table = { + .thermal_floor_table = tegra210b01_thermal_floor_table, + .thermal_floor_table_size = ARRAY_SIZE(tegra210b01_thermal_floor_table), + .speedo_scale = 100, + .voltage_scale = 1000, + .temp_scale = 10, + .thermal_cap_table = tegra210b01_thermal_cap_table, + .thermal_cap_table_size = ARRAY_SIZE(tegra210b01_thermal_cap_table), + .thermal_cap_ucm2_table = tegra210b01_thermal_cap_table, + .thermal_cap_ucm2_table_size = ARRAY_SIZE(tegra210b01_thermal_cap_table) +}; + static const struct dfll_fcpu_data tegra124_dfll_fcpu_data = { .cpu_max_freq_table = tegra124_cpu_max_freq_table, .cpu_max_freq_table_size = ARRAY_SIZE(tegra124_cpu_max_freq_table), @@ -647,6 +713,7 @@ static const struct dfll_fcpu_data tegra210_dfll_fcpu_data = { .cpu_max_freq_table_size = ARRAY_SIZE(tegra210_cpu_max_freq_table), .cpu_cvb_tables = tegra210_cpu_cvb_tables, .cpu_cvb_tables_size = ARRAY_SIZE(tegra210_cpu_cvb_tables), + .cpu_thermal_table = &tegra210_cpu_thermal_table }; static const struct dfll_fcpu_data tegra210b01_dfll_fcpu_data = { @@ -654,6 +721,7 @@ static const struct dfll_fcpu_data tegra210b01_dfll_fcpu_data = { .cpu_max_freq_table_size = ARRAY_SIZE(tegra210b01_cpu_max_freq_table), .cpu_cvb_tables = tegra210b01_cpu_cvb_tables, .cpu_cvb_tables_size = ARRAY_SIZE(tegra210b01_cpu_cvb_tables), + .cpu_thermal_table = &tegra210b01_cpu_thermal_table }; static const struct of_device_id tegra124_dfll_fcpu_of_match[] = { @@ -661,7 +729,7 @@ static const struct of_device_id tegra124_dfll_fcpu_of_match[] = { .compatible = "nvidia,tegra124-dfll", .data = &tegra124_dfll_fcpu_data, }, - { + { .compatible = "nvidia,tegra210-dfll", .data = &tegra210_dfll_fcpu_data }, @@ -675,44 +743,66 @@ static const struct of_device_id tegra124_dfll_fcpu_of_match[] = { static void get_alignment_from_dt(struct device *dev, struct rail_alignment *align) { + align->step_uv = 0; + align->offset_uv = 0; + if (of_property_read_u32(dev->of_node, "nvidia,pwm-voltage-step-microvolts", - &align->step_uv)) + &align->step_uv)) align->step_uv = 0; if (of_property_read_u32(dev->of_node, - "nvidia,pwm-min-microvolts", - &align->offset_uv)) + "nvidia,pwm-min-microvolts", &align->offset_uv)) align->offset_uv = 0; } static int get_alignment_from_regulator(struct device *dev, struct rail_alignment *align) { - struct regulator *reg = regulator_get(dev, "vdd-cpu"); + int min_uV, max_uV, n_voltages, ret; + struct regulator *reg = devm_regulator_get(dev, "vdd-cpu"); if (IS_ERR(reg)) return PTR_ERR(reg); - align->offset_uv = regulator_list_voltage(reg, 0); - align->step_uv = regulator_get_linear_step(reg); + ret = regulator_get_constraint_voltages(reg, &min_uV, &max_uV); + if (!ret) + align->offset_uv = min_uV; - regulator_put(reg); + align->step_uv = regulator_get_linear_step(reg); + if (!align->step_uv && !ret) { + n_voltages = regulator_count_voltages(reg); + if (n_voltages > 1) + align->step_uv = (max_uV - min_uV) / (n_voltages - 1); + } + devm_regulator_put(reg); return 0; } +#define INIT_TUNE_PRAM(p) \ +do { \ + if (of_property_read_u32(pdev->dev.of_node, \ + "nvidia,dfll-override-" #p, &soc->p)) \ + soc->p = soc->cvb->cpu_dfll_data.p; \ +} while (0) + static int tegra124_dfll_fcpu_probe(struct platform_device *pdev) { int process_id, speedo_id, speedo_value, err; struct tegra_dfll_soc_data *soc; const struct dfll_fcpu_data *fcpu_data; struct rail_alignment align; + const struct thermal_table *thermal; + unsigned long max_freq; + u32 f; + bool ucm2; fcpu_data = of_device_get_match_data(&pdev->dev); if (!fcpu_data) return -ENODEV; + ucm2 = tegra_sku_info.ucm == TEGRA_UCM2; process_id = tegra_sku_info.cpu_process_id; speedo_id = tegra_sku_info.cpu_speedo_id; speedo_value = tegra_sku_info.cpu_speedo_value; @@ -722,6 +812,10 @@ static int tegra124_dfll_fcpu_probe(struct platform_device *pdev) speedo_id); return -ENODEV; } + max_freq = fcpu_data->cpu_max_freq_table[speedo_id]; + if (!of_property_read_u32(pdev->dev.of_node, "nvidia,dfll-max-freq-khz", + &f)) + max_freq = min(max_freq, f * 1000UL); soc = devm_kzalloc(&pdev->dev, sizeof(*soc), GFP_KERNEL); if (!soc) @@ -733,20 +827,33 @@ static int tegra124_dfll_fcpu_probe(struct platform_device *pdev) return -ENODEV; } - if (of_property_read_bool(pdev->dev.of_node, "nvidia,pwm-to-pmic")) { - get_alignment_from_dt(&pdev->dev, &align); + get_alignment_from_dt(&pdev->dev, &align); + if (of_property_read_bool(pdev->dev.of_node, "nvidia,pwm-to-pmic") + && (!align.step_uv || !align.offset_uv)) { + dev_info(&pdev->dev, "Missing required align data in DT"); + return -EINVAL; } else { - err = get_alignment_from_regulator(&pdev->dev, &align); - if (err) - return err; + if (!align.step_uv) { + dev_info(&pdev->dev, "no align data in DT, try from vdd-cpu\n"); + err = get_alignment_from_regulator(&pdev->dev, &align); + if (err == -EPROBE_DEFER) { + dev_info(&pdev->dev, "defer probe to get vdd-cpu\n"); + return -EPROBE_DEFER; + } + } } - soc->max_freq = fcpu_data->cpu_max_freq_table[speedo_id]; + if (!align.step_uv) { + dev_err(&pdev->dev, "missing step uv\n"); + return -EINVAL; + } + soc->max_freq = max_freq; soc->cvb = tegra_cvb_add_opp_table(soc->dev, fcpu_data->cpu_cvb_tables, fcpu_data->cpu_cvb_tables_size, &align, process_id, speedo_id, - speedo_value, soc->max_freq); + speedo_value, soc->max_freq, + &soc->min_millivolts); soc->alignment = align; if (IS_ERR(soc->cvb)) { @@ -755,6 +862,33 @@ static int tegra124_dfll_fcpu_probe(struct platform_device *pdev) return PTR_ERR(soc->cvb); } + INIT_TUNE_PRAM(tune0_low); + INIT_TUNE_PRAM(tune0_high); + INIT_TUNE_PRAM(tune1_low); + INIT_TUNE_PRAM(tune1_high); + INIT_TUNE_PRAM(tune_high_min_millivolts); + INIT_TUNE_PRAM(tune_high_margin_millivolts); + + thermal = fcpu_data->cpu_thermal_table; + err = tegra_cvb_build_thermal_table(thermal, speedo_value, + soc->min_millivolts); + if (err < 0) { + pr_warn("couldn't build thermal floor table\n"); + } else { + soc->thermal_floor_table = thermal->thermal_floor_table; + soc->thermal_floor_table_size = thermal->thermal_floor_table_size; + } + + if (thermal && thermal->thermal_cap_table && !ucm2) { + soc->thermal_cap_table = thermal->thermal_cap_table; + soc->thermal_cap_table_size = thermal->thermal_cap_table_size; + } else if (thermal && thermal->thermal_cap_ucm2_table && ucm2) { + soc->thermal_cap_table = thermal->thermal_cap_ucm2_table; + soc->thermal_cap_table_size = thermal->thermal_cap_ucm2_table_size; + } else { + pr_warn("couldn't get thermal cap table\n"); + } + err = tegra_dfll_register(pdev, soc); if (err < 0) { tegra_cvb_remove_opp_table(soc->dev, soc->cvb, soc->max_freq); @@ -768,13 +902,10 @@ static void tegra124_dfll_fcpu_remove(struct platform_device *pdev) { struct tegra_dfll_soc_data *soc; - /* - * Note that exiting early here is dangerous as after this function - * returns *soc is freed. - */ soc = tegra_dfll_unregister(pdev); if (IS_ERR(soc)) - return; + dev_err(&pdev->dev, "failed to unregister DFLL: %ld\n", + PTR_ERR(soc)); tegra_cvb_remove_opp_table(soc->dev, soc->cvb, soc->max_freq); } @@ -782,7 +913,7 @@ static void tegra124_dfll_fcpu_remove(struct platform_device *pdev) static const struct dev_pm_ops tegra124_dfll_pm_ops = { SET_RUNTIME_PM_OPS(tegra_dfll_runtime_suspend, tegra_dfll_runtime_resume, NULL) - SET_SYSTEM_SLEEP_PM_OPS(tegra_dfll_suspend, tegra_dfll_resume) + SET_LATE_SYSTEM_SLEEP_PM_OPS(NULL, tegra_dfll_resume_tuning) }; static struct platform_driver tegra124_dfll_fcpu_driver = { diff --git a/drivers/clk/tegra/cvb.c b/drivers/clk/tegra/cvb.c index a7fdc7622913..f78cde20d9d6 100644 --- a/drivers/clk/tegra/cvb.c +++ b/drivers/clk/tegra/cvb.c @@ -11,8 +11,8 @@ #include "cvb.h" /* cvb_mv = ((c2 * speedo / s_scale + c1) * speedo / s_scale + c0) */ -static inline int get_cvb_voltage(int speedo, int s_scale, - const struct cvb_coefficients *cvb) +int tegra_get_cvb_voltage(int speedo, int s_scale, + const struct cvb_coefficients *cvb) { int mv; @@ -22,8 +22,21 @@ static inline int get_cvb_voltage(int speedo, int s_scale, return mv; } -static int round_cvb_voltage(int mv, int v_scale, - const struct rail_alignment *align) +/* cvb_t_mv = + ((c3 * speedo / s_scale + c4 + c5 * T / t_scale) * T / t_scale) / v_scale */ +int tegra_get_cvb_t_voltage(int speedo, int s_scale, int t, int t_scale, + struct cvb_coefficients *cvb) +{ + /* apply speedo & temperature scales: output mv = cvb_t_mv * v_scale */ + int mv; + mv = DIV_ROUND_CLOSEST(cvb->c3 * speedo, s_scale) + cvb->c4 + + DIV_ROUND_CLOSEST(cvb->c5 * t, t_scale); + mv = DIV_ROUND_CLOSEST(mv * t, t_scale); + return mv; +} + +int tegra_round_cvb_voltage(int mv, int v_scale, + const struct rail_alignment *align) { /* combined: apply voltage scale and round to cvb alignment step */ int uv; @@ -40,7 +53,7 @@ enum { UP }; -static int round_voltage(int mv, const struct rail_alignment *align, int up) +int tegra_round_voltage(int mv, const struct rail_alignment *align, int up) { if (align->step_uv) { int uv; @@ -52,14 +65,46 @@ static int round_voltage(int mv, const struct rail_alignment *align, int up) return mv; } +/** + * cvb_t_mv = + * ((c2 * speedo / s_scale + c1) * speedo / s_scale + c0) + + * ((c3 * speedo / s_scale + c4 + c5 * T / t_scale) * T / t_scale) + */ +static inline int get_cvb_thermal_floor(int speedo, int temp, + int s_scale, int t_scale, + const struct thermal_coefficients *coef) +{ + int cvb_mv, mv; + + cvb_mv = tegra_get_cvb_voltage(speedo, s_scale, &coef->cvb_coef); + + mv = DIV_ROUND_CLOSEST(coef->c3 * speedo, s_scale) + coef->c4 + + DIV_ROUND_CLOSEST(coef->c5 * temp, t_scale); + mv = DIV_ROUND_CLOSEST(mv * temp, t_scale) + cvb_mv; + return mv; +} + static int build_opp_table(struct device *dev, const struct cvb_table *table, struct rail_alignment *align, - int speedo_value, unsigned long max_freq) + int speedo_value, unsigned long max_freq, int *vmin) { int i, ret, dfll_mv, min_mv, max_mv; - min_mv = round_voltage(table->min_millivolts, align, UP); - max_mv = round_voltage(table->max_millivolts, align, DOWN); + if (!align->step_uv) + align->step_uv = table->alignment.step_uv; + if (!align->step_uv) + return -EINVAL; + + if (!align->offset_uv) + align->offset_uv = table->alignment.offset_uv; + + min_mv = tegra_round_voltage(table->min_millivolts, align, UP); + max_mv = tegra_round_voltage(table->max_millivolts, align, DOWN); + + dfll_mv = tegra_get_cvb_voltage( + speedo_value, table->speedo_scale, &table->vmin_coefficients); + dfll_mv = tegra_round_cvb_voltage(dfll_mv, table->voltage_scale, align); + min_mv = max(min_mv, dfll_mv); for (i = 0; i < MAX_DVFS_FREQS; i++) { const struct cvb_table_freq_entry *entry = &table->entries[i]; @@ -67,10 +112,9 @@ static int build_opp_table(struct device *dev, const struct cvb_table *table, if (!entry->freq || (entry->freq > max_freq)) break; - dfll_mv = get_cvb_voltage(speedo_value, table->speedo_scale, - &entry->coefficients); - dfll_mv = round_cvb_voltage(dfll_mv, table->voltage_scale, - align); + dfll_mv = tegra_get_cvb_voltage( + speedo_value, table->speedo_scale, &entry->coefficients); + dfll_mv = tegra_round_cvb_voltage(dfll_mv, table->voltage_scale, align); dfll_mv = clamp(dfll_mv, min_mv, max_mv); ret = dev_pm_opp_add(dev, entry->freq, dfll_mv * 1000); @@ -78,32 +122,35 @@ static int build_opp_table(struct device *dev, const struct cvb_table *table, return ret; } + if (vmin) + *vmin = min_mv; + return 0; } /** * tegra_cvb_add_opp_table - build OPP table from Tegra CVB tables - * @dev: the struct device * for which the OPP table is built - * @tables: array of CVB tables - * @count: size of the previously mentioned array - * @align: parameters of the regulator step and offset + * @cvb_tables: array of CVB tables + * @sz: size of the previously mentioned array * @process_id: process id of the HW module * @speedo_id: speedo id of the HW module * @speedo_value: speedo value of the HW module - * @max_freq: highest safe clock rate + * @max_rate: highest safe clock rate + * @opp_dev: the struct device * for which the OPP table is built + * @vmin: final minimum voltage returned to the caller * * On Tegra, a CVB table encodes the relationship between operating voltage * and safe maximal frequency for a given module (e.g. GPU or CPU). This * function calculates the optimal voltage-frequency operating points * for the given arguments and exports them via the OPP library for the - * given @dev. Returns a pointer to the struct cvb_table that matched + * given @opp_dev. Returns a pointer to the struct cvb_table that matched * or an ERR_PTR on failure. */ const struct cvb_table * tegra_cvb_add_opp_table(struct device *dev, const struct cvb_table *tables, size_t count, struct rail_alignment *align, int process_id, int speedo_id, int speedo_value, - unsigned long max_freq) + unsigned long max_freq, int *vmin) { size_t i; int ret; @@ -118,7 +165,7 @@ tegra_cvb_add_opp_table(struct device *dev, const struct cvb_table *tables, continue; ret = build_opp_table(dev, table, align, speedo_value, - max_freq); + max_freq, vmin); return ret ? ERR_PTR(ret) : table; } @@ -140,3 +187,41 @@ void tegra_cvb_remove_opp_table(struct device *dev, dev_pm_opp_remove(dev, entry->freq); } } + +/** + * tegra_cvb_build_thermal_table - build thermal table from Tegra CVB tables + * @table: the hardware characterization thermal table + * @speedo_value: speedo value of the HW module + * @soc_min_mv: minimum voltage applied across all temperature ranges + * + * The minimum voltage for the IP blocks inside Tegra SoCs might depend on + * the current temperature. This function calculates the voltage-thermal + * relations according to the given coefficients. Note that if the + * coefficients are not defined, the fixed thermal floors in the @table will + * be used. Returns 0 on success or a negative error code on failure. + */ +int tegra_cvb_build_thermal_table(const struct thermal_table *table, + int speedo_value, unsigned int soc_min_mv) +{ + int i; + + if (!table) + return -EINVAL; + + /* The vmin for the lowest trip point is fixed */ + for (i = 1; i < table->thermal_floor_table_size; i++) { + unsigned int mv; + + mv = get_cvb_thermal_floor(speedo_value, + table->thermal_floor_table[i-1].temp, + table->speedo_scale, + table->temp_scale, + &table->coefficients); + mv = DIV_ROUND_UP(mv, table->voltage_scale); + mv = max(mv, soc_min_mv); + table->thermal_floor_table[i].millivolts = max(mv, + table->thermal_floor_table[i].millivolts); + } + + return 0; +} diff --git a/drivers/clk/tegra/cvb.h b/drivers/clk/tegra/cvb.h index c2ff6379b35d..51df4a7b4ee0 100644 --- a/drivers/clk/tegra/cvb.h +++ b/drivers/clk/tegra/cvb.h @@ -21,6 +21,9 @@ struct cvb_coefficients { int c0; int c1; int c2; + int c3; + int c4; + int c5; }; struct cvb_table_freq_entry { @@ -32,7 +35,23 @@ struct cvb_cpu_dfll_data { u32 tune0_low; u32 tune0_high; u32 tune1_low; + u32 tune1_high; unsigned int tune_high_min_millivolts; + unsigned int tune_high_margin_millivolts; + unsigned long dvco_calibration_max; +}; + +struct thermal_coefficients { + struct cvb_coefficients cvb_coef; + int c3; + int c4; + int c5; +}; + +/* Thermal trips and voltages */ +struct thermal_tv { + int temp; + unsigned int millivolts; }; struct cvb_table { @@ -41,20 +60,57 @@ struct cvb_table { int min_millivolts; int max_millivolts; + struct rail_alignment alignment; int speedo_scale; int voltage_scale; struct cvb_table_freq_entry entries[MAX_DVFS_FREQS]; struct cvb_cpu_dfll_data cpu_dfll_data; + struct cvb_coefficients vmin_coefficients; + const char *cvb_version; }; const struct cvb_table * tegra_cvb_add_opp_table(struct device *dev, const struct cvb_table *cvb_tables, size_t count, struct rail_alignment *align, int process_id, int speedo_id, int speedo_value, - unsigned long max_freq); + unsigned long max_freq, int *vmin); void tegra_cvb_remove_opp_table(struct device *dev, const struct cvb_table *table, unsigned long max_freq); +struct thermal_table { + struct thermal_tv *thermal_floor_table; + unsigned int thermal_floor_table_size; + struct thermal_coefficients coefficients; + unsigned int speedo_scale; + unsigned int voltage_scale; + unsigned int temp_scale; + + const struct thermal_tv *thermal_cap_table; + unsigned int thermal_cap_table_size; + const struct thermal_tv *thermal_cap_ucm2_table; + unsigned int thermal_cap_ucm2_table_size; +}; + +const struct cvb_table *tegra_cvb_build_opp_table( + const struct cvb_table *cvb_tables, + size_t sz, + const struct rail_alignment *align, + int process_id, + int speedo_id, + int speedo_value, + unsigned long max_rate, + struct device *opp_dev); + +int tegra_get_cvb_voltage(int speedo, int s_scale, + const struct cvb_coefficients *cvb); +int tegra_round_cvb_voltage(int mv, int v_scale, + const struct rail_alignment *align); +int tegra_round_voltage(int mv, const struct rail_alignment *align, int up); +int tegra_get_cvb_t_voltage(int speedo, int s_scale, int t, int t_scale, + struct cvb_coefficients *cvb); +int tegra_cvb_build_thermal_table(const struct thermal_table *table, + int speedo_value, unsigned int soc_min_mv); + #endif diff --git a/include/dt-bindings/clock/tegra210-car.h b/include/dt-bindings/clock/tegra210-car.h index 27485d9b80f6..0b8451204cd5 100644 --- a/include/dt-bindings/clock/tegra210-car.h +++ b/include/dt-bindings/clock/tegra210-car.h @@ -409,9 +409,105 @@ #define TEGRA210_CLK_DMIC3_SYNC_CLK 392 #define TEGRA210_CLK_DMIC3_SYNC_CLK_MUX 393 +#define TEGRA210_CLK_C2BUS 401 +#define TEGRA210_CLK_C3BUS 402 +#define TEGRA210_CLK_VIC03_CBUS 403 +#define TEGRA210_CLK_NVJPG_CBUS 404 +#define TEGRA210_CLK_SE_CBUS 405 +#define TEGRA210_CLK_TSECB_CBUS 406 +#define TEGRA210_CLK_CAP_C2BUS 407 +#define TEGRA210_CLK_CAP_VCORE_C2BUS 408 +#define TEGRA210_CLK_CAP_THROTTLE_C2BUS 409 +#define TEGRA210_CLK_FLOOR_C2BUS 410 +#define TEGRA210_CLK_OVERRIDE_C2BUS 411 +#define TEGRA210_CLK_EDP_C2BUS 412 +#define TEGRA210_CLK_NVENC_CBUS 413 +#define TEGRA210_CLK_NVDEC_CBUS 414 +#define TEGRA210_CLK_VIC_FLOOR_CBUS 415 +#define TEGRA210_CLK_CAP_C3BUS 416 +#define TEGRA210_CLK_CAP_VCORE_C3BUS 417 +#define TEGRA210_CLK_CAP_THROTTLE_C3BUS 418 +#define TEGRA210_CLK_FLOOR_C3BUS 419 +#define TEGRA210_CLK_OVERRIDE_C3BUS 420 +#define TEGRA210_CLK_VI_CBUS 421 +#define TEGRA210_CLK_ISP_CBUS 422 +#define TEGRA210_CLK_OVERRIDE_CBUS 423 +#define TEGRA210_CLK_CAP_VCORE_CBUS 424 +#define TEGRA210_CLK_VIA_VI_CBUS 425 +#define TEGRA210_CLK_VIB_VI_CBUS 426 +#define TEGRA210_CLK_ISPA_ISP_CBUS 427 +#define TEGRA210_CLK_ISPB_ISP_CBUS 428 +#define TEGRA210_CLK_SBUS 429 +#define TEGRA210_CLK_AVP_SCLK 430 +#define TEGRA210_CLK_BSEA_SCLK 431 +#define TEGRA210_CLK_USBD_SCLK 432 +#define TEGRA210_CLK_USB1_SCLK 433 +#define TEGRA210_CLK_USB2_SCLK 434 +#define TEGRA210_CLK_USB3_SCLK 435 +#define TEGRA210_CLK_WAKE_SCLK 436 +#define TEGRA210_CLK_CAMERA_SCLK 437 +#define TEGRA210_CLK_MON_AVP 438 +#define TEGRA210_CLK_CAP_SCLK 439 +#define TEGRA210_CLK_CAP_VCORE_SCLK 440 +#define TEGRA210_CLK_CAP_THROTTLE_SCLK 441 +#define TEGRA210_CLK_FLOOR_SCLK 442 +#define TEGRA210_CLK_OVERRIDE_SCLK 443 +#define TEGRA210_CLK_SBC1_SCLK 444 +#define TEGRA210_CLK_SBC2_SCLK 445 +#define TEGRA210_CLK_SBC3_SCLK 446 +#define TEGRA210_CLK_SBC4_SCLK 447 +#define TEGRA210_CLK_QSPI_SCLK 448 +#define TEGRA210_CLK_BOOT_APB_SCLK 449 +#define TEGRA210_CLK_EMC_MASTER 450 + +#define TEGRA210_CLK_GBUS 487 +#define TEGRA210_CLK_GM20B_GBUS 488 +#define TEGRA210_CLK_CAP_GBUS 489 +#define TEGRA210_CLK_EDP_GBUS 490 +#define TEGRA210_CLK_CAP_VGPU_GBUS 491 +#define TEGRA210_CLK_CAP_THROTTLE_GBUS 492 +#define TEGRA210_CLK_CAP_PROFILE_GBUS 493 +#define TEGRA210_CLK_OVERRIDE_GBUS 494 +#define TEGRA210_CLK_FLOOR_GBUS 495 +#define TEGRA210_CLK_FLOOR_PROFILE_GBUS 496 +#define TEGRA210_CLK_HOST1X_MASTER 497 +#define TEGRA210_CLK_NV_HOST1X 498 +#define TEGRA210_CLK_VI_HOST1X 499 +#define TEGRA210_CLK_VII2C_HOST1X 500 +#define TEGRA210_CLK_CAP_HOST1X 501 +#define TEGRA210_CLK_CAP_VCORE_HOST1X 502 +#define TEGRA210_CLK_FLOOR_HOST1X 503 +#define TEGRA210_CLK_OVERRIDE_HOST1X 504 +#define TEGRA210_CLK_MSELECT_MASTER 505 +#define TEGRA210_CLK_CPU_MSELECT 506 +#define TEGRA210_CLK_PCIE_MSELECT 507 +#define TEGRA210_CLK_CAP_VCORE_MSELECT 508 +#define TEGRA210_CLK_OVERRIDE_MSELECT 509 +#define TEGRA210_CLK_APE_MASTER 510 +#define TEGRA210_CLK_ADMA_APE 511 +#define TEGRA210_CLK_ADSP_APE 512 +#define TEGRA210_CLK_XBAR_APE 513 +#define TEGRA210_CLK_CAP_VCORE_APE 514 +#define TEGRA210_CLK_OVERRIDE_APE 515 +#define TEGRA210_CLK_ABUS 516 +#define TEGRA210_CLK_ADSP_CPU_ABUS 517 +#define TEGRA210_CLK_CAP_VCORE_ABUS 518 +#define TEGRA210_CLK_OVERRIDE_ABUS 519 +#define TEGRA210_CLK_VCM_SCLK 520 +#define TEGRA210_CLK_VCM_AHB_SCLK 521 +#define TEGRA210_CLK_VCM_APB_SCLK 522 +#define TEGRA210_CLK_AHB_SCLK 523 +#define TEGRA210_CLK_APB_SCLK 524 +#define TEGRA210_CLK_SDMMC4_AHB_SCLK 525 +/* 526 */ +#define TEGRA210_CLK_CBUS 527 +#define TEGRA210_CLK_VI_V4L2_CBUS 528 +#define TEGRA210_CLK_VI_BYPASS_CBUS 529 +#define TEGRA210_CLK_BWMGR_EMC 530 #define TEGRA210_CLK_UTMIPLL_60M 531 #define TEGRA210_CLK_PLL_P_UPHY_OUT 532 +#define TEGRA210_CLK_WIFI_SCLK 533 -#define TEGRA210_CLK_CLK_MAX 533 +#define TEGRA210_CLK_CLK_MAX 534 #endif /* _DT_BINDINGS_CLOCK_TEGRA210_CAR_H */ diff --git a/include/dt-bindings/thermal/tegra210-dfll-trips.h b/include/dt-bindings/thermal/tegra210-dfll-trips.h new file mode 100644 index 000000000000..2ea0204ac46f --- /dev/null +++ b/include/dt-bindings/thermal/tegra210-dfll-trips.h @@ -0,0 +1,16 @@ +/* + * This header defines the trip temperatures for Tegra210 + */ +#ifndef _DT_BINDINGS_THERMAL_TEGRA210_DFLL_TRIPS_H +#define _DT_BINDINGS_THERMAL_TEGRA210_DFLL_TRIPS_H + +#define TEGRA210_DFLL_THERMAL_FLOOR_0 15000 +#define TEGRA210_DFLL_THERMAL_FLOOR_1 30000 +#define TEGRA210_DFLL_THERMAL_FLOOR_2 50000 +#define TEGRA210_DFLL_THERMAL_FLOOR_3 70000 +#define TEGRA210_DFLL_THERMAL_FLOOR_4 120000 + +#define TEGRA210_DFLL_THERMAL_CAP_0 66000 +#define TEGRA210_DFLL_THERMAL_CAP_1 86000 + +#endif /* _DT_BINDINGS_THERMAL_TEGRA210_DFLL_TRIPS_H */ diff --git a/include/dt-bindings/thermal/tegra210b01-trips.h b/include/dt-bindings/thermal/tegra210b01-trips.h new file mode 100644 index 000000000000..f4cec8423dfa --- /dev/null +++ b/include/dt-bindings/thermal/tegra210b01-trips.h @@ -0,0 +1,28 @@ +/* + * This header defines the trip temperatures for Tegra210b01 + */ +#ifndef _DT_BINDINGS_THERMAL_TEGRA210B01_TRIPS_H +#define _DT_BINDINGS_THERMAL_TEGRA210B01_TRIPS_H + +/* DFLL trips, in millicelsius */ +#define TEGRA210B01_DFLL_THERMAL_FLOOR_0 20000 +#define TEGRA210B01_DFLL_THERMAL_FLOOR_1 70000 + +#define TEGRA210B01_DFLL_THERMAL_CAP_0 64000 +#define TEGRA210B01_DFLL_THERMAL_CAP_1 84000 + +/* GPU DVFS thermal trips, in millicelsius */ +#define TEGRA210B01_GPU_DVFS_THERMAL_MIN -25000 +#define TEGRA210B01_GPU_DVFS_THERMAL_TRIP_1 20000 +#define TEGRA210B01_GPU_DVFS_THERMAL_TRIP_2 30000 +#define TEGRA210B01_GPU_DVFS_THERMAL_TRIP_3 50000 +#define TEGRA210B01_GPU_DVFS_THERMAL_TRIP_4 70000 +#define TEGRA210B01_GPU_DVFS_THERMAL_TRIP_5 90000 + +#define TEGRA210B01_GPU_DVFS_THERMAL_CAP_1 83000 + +/* SoC DVFS thermal trips, in millicelsius */ +#define TEGRA210B01_SOC_THERMAL_FLOOR_0 20000 +#define TEGRA210B01_SOC_THERMAL_CAP_0 84000 + +#endif /* _DT_BINDINGS_THERMAL_TEGRA210B01_TRIPS_H */ diff --git a/include/soc/tegra/tegra-dfll.h b/include/soc/tegra/tegra-dfll.h new file mode 100644 index 000000000000..070847fa97cd --- /dev/null +++ b/include/soc/tegra/tegra-dfll.h @@ -0,0 +1,36 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2014-2017, NVIDIA CORPORATION. All rights reserved + */ + + +#ifndef _TEGRA_DFLL_H_ +#define _TEGRA_DFLL_H_ + +#include +#include + +enum tegra_dfll_thermal_type { + TEGRA_DFLL_THERMAL_FLOOR = 0, + TEGRA_DFLL_THERMAL_CAP, +}; + +struct tegra_dfll; + +extern struct tegra_dfll *tegra_dfll_get_by_phandle(struct device_node *np, + const char *prop); +extern int tegra_dfll_update_thermal_index(struct tegra_dfll *td, + enum tegra_dfll_thermal_type type, + unsigned long new_index); +extern int tegra_dfll_get_thermal_index(struct tegra_dfll *td, + enum tegra_dfll_thermal_type type); +extern int tegra_dfll_count_thermal_states(struct tegra_dfll *td, + enum tegra_dfll_thermal_type type); +int tegra_dfll_set_external_floor_mv(int external_floor_mv); +u32 tegra_dfll_get_thermal_floor_mv(void); +u32 tegra_dfll_get_peak_thermal_floor_mv(void); +u32 tegra_dfll_get_thermal_cap_mv(void); +u32 tegra_dfll_get_min_millivolts(void); +struct rail_alignment *tegra_dfll_get_alignment(void); +const char *tegra_dfll_get_cvb_version(void); +#endif