summaryrefslogtreecommitdiff
path: root/arch/arm/mach-tegra/tegra_cl_dvfs.c
diff options
context:
space:
mode:
authorAlex Frid <afrid@nvidia.com>2012-11-27 22:57:38 -0800
committerSimone Willett <swillett@nvidia.com>2012-12-03 16:20:07 -0800
commit684d5e0af77326432dda3a36ed13f5361bc91ddc (patch)
tree9e2c918e7dba583203a735274afb5bc5fd3fa40e /arch/arm/mach-tegra/tegra_cl_dvfs.c
parent6879f23e5ae93521a3e082a1dd749429f6ad446e (diff)
ARM: tegra11: dvfs: Add CL-DVFS dynamic tuning
Added mechanism for CL-DVFS dynamic tuning over two voltage ranges. Range threshold and tuning settings for low and high ranges are specified by the chip characterization. Low range setting is used whenever CL-DVFS is operating in the open loop mode, and in the voltage range at/below threshold when operating in closed loop mode. High tuning setting is used in the voltage range above threshold in closed loop mode only. High range tuning setting can be applied only when voltage is already above threshold. Therefore, when high voltage is requested: - CL-DVFS output minimum is set at threshold level - tuning timer is started to poll for completion of high voltage request - only then high range setting is actually loaded into CL-DVFS registers (and timer is stopped). Low range tuning setting is safe at any voltage, and can be applied immediately when the respective request is received, even if actual voltage is not yet scaled below threshold. If range threshold in the characterization data is not set (zero), dynamic tuning is disabled, and low range setting is applied in the entire voltage range. Bug 1178825 Change-Id: I42f336eaf2af6876b53c15ea57b896cdc7110596 Signed-off-by: Alex Frid <afrid@nvidia.com> Reviewed-on: http://git-master/r/167198 Reviewed-by: Automatic_Commit_Validation_User Reviewed-by: Yu-Huan Hsu <yhsu@nvidia.com>
Diffstat (limited to 'arch/arm/mach-tegra/tegra_cl_dvfs.c')
-rw-r--r--arch/arm/mach-tegra/tegra_cl_dvfs.c164
1 files changed, 155 insertions, 9 deletions
diff --git a/arch/arm/mach-tegra/tegra_cl_dvfs.c b/arch/arm/mach-tegra/tegra_cl_dvfs.c
index ce019bce8c4c..3f52b4866892 100644
--- a/arch/arm/mach-tegra/tegra_cl_dvfs.c
+++ b/arch/arm/mach-tegra/tegra_cl_dvfs.c
@@ -116,6 +116,7 @@
#define CL_DVFS_I2C_VDD_REG_ADDR 0x44
#define CL_DVFS_I2C_STS 0x48
+#define CL_DVFS_I2C_STS_I2C_LAST_SHIFT 1
#define CL_DVFS_I2C_STS_I2C_REQ_PENDING 0x1
#define CL_DVFS_INTR_STS 0x5c
@@ -131,6 +132,10 @@
#define CL_DVFS_OUTPUT_LUT 0x200
#define CL_DVFS_OUTPUT_PENDING_TIMEOUT 1000
+#define CL_DVFS_OUTPUT_RAMP_DELAY 100
+#define CL_DVFS_TUNE_HIGH_DELAY 2000
+
+#define CL_DVFS_TUNE_HIGH_MARGIN 20
enum tegra_cl_dvfs_ctrl_mode {
TEGRA_CL_DVFS_UNINITIALIZED = 0,
@@ -139,6 +144,12 @@ enum tegra_cl_dvfs_ctrl_mode {
TEGRA_CL_DVFS_CLOSED_LOOP = 3,
};
+enum tegra_cl_dvfs_tune_state {
+ TEGRA_CL_DVFS_TUNE_LOW = 0,
+ TEGRA_CL_DVFS_TUNE_HIGH_REQUEST,
+ TEGRA_CL_DVFS_TUNE_HIGH,
+};
+
struct dfll_rate_req {
u8 freq;
u8 scale;
@@ -153,6 +164,7 @@ struct tegra_cl_dvfs {
struct clk *soc_clk;
struct clk *ref_clk;
struct clk *i2c_clk;
+ struct clk *dfll_clk;
unsigned long ref_rate;
unsigned long i2c_rate;
@@ -164,10 +176,16 @@ struct tegra_cl_dvfs {
struct voltage_reg_map *out_map[MAX_CL_DVFS_VOLTAGES];
u8 num_voltages;
u8 safe_ouput;
+ u8 tune_high_out_start;
+ u8 tune_high_out_min;
+ unsigned long dfll_rate_min;
struct dfll_rate_req last_req;
- unsigned long dfll_rate_min;
+ enum tegra_cl_dvfs_tune_state tune_state;
enum tegra_cl_dvfs_ctrl_mode mode;
+
+ struct timer_list tune_timer;
+ unsigned long tune_delay;
};
/* Conversion macros (different scales for frequency request, and monitored
@@ -254,17 +272,94 @@ static inline void set_mode(struct tegra_cl_dvfs *cld,
cl_dvfs_wmb(cld);
}
-static void set_max_limit(struct tegra_cl_dvfs *cld, struct dfll_rate_req *req)
+#define set_tune_state(cld, state) \
+ do { \
+ cld->tune_state = state; \
+ pr_debug("%s: set tune state %d\n", __func__, state); \
+ } while (0)
+
+static void set_ol_config(struct tegra_cl_dvfs *cld)
{
u32 val;
+ if (cld->tune_state != TEGRA_CL_DVFS_TUNE_LOW) {
+ set_tune_state(cld, TEGRA_CL_DVFS_TUNE_LOW);
+ cl_dvfs_writel(cld, cld->safe_dvfs->dfll_data.tune0,
+ CL_DVFS_TUNE0);
+ cl_dvfs_wmb(cld);
+
+ val = cl_dvfs_readl(cld, CL_DVFS_OUTPUT_CFG);
+ val &= ~CL_DVFS_OUTPUT_CFG_MIN_MASK;
+ cl_dvfs_writel(cld, val, CL_DVFS_OUTPUT_CFG);
+ }
+}
+
+static void set_cl_config(struct tegra_cl_dvfs *cld, struct dfll_rate_req *req)
+{
+ u32 val, out_max, out_min;
+
val = cl_dvfs_readl(cld, CL_DVFS_OUTPUT_CFG);
- val &= ~CL_DVFS_OUTPUT_CFG_MAX_MASK;
- val |= max(req->output, (u8)(cld->safe_ouput + 1)) <<
- CL_DVFS_OUTPUT_CFG_MAX_SHIFT;
+ out_max = max(req->output, (u8)(cld->safe_ouput + 1));
+ out_min = (val >> CL_DVFS_OUTPUT_CFG_MIN_SHIFT) & OUT_MASK;
+
+ switch (cld->tune_state) {
+ case TEGRA_CL_DVFS_TUNE_LOW:
+ if (req->output > cld->tune_high_out_start) {
+ set_tune_state(cld, TEGRA_CL_DVFS_TUNE_HIGH_REQUEST);
+ out_min = cld->tune_high_out_min;
+ mod_timer(&cld->tune_timer, jiffies + cld->tune_delay);
+ }
+ break;
+
+ case TEGRA_CL_DVFS_TUNE_HIGH:
+ case TEGRA_CL_DVFS_TUNE_HIGH_REQUEST:
+ if (req->output <= cld->tune_high_out_start) {
+ set_tune_state(cld, TEGRA_CL_DVFS_TUNE_LOW);
+ cl_dvfs_writel(cld, cld->safe_dvfs->dfll_data.tune0,
+ CL_DVFS_TUNE0);
+ cl_dvfs_wmb(cld);
+ out_min = 0;
+ }
+ break;
+ default:
+ BUG();
+ }
+
+ out_max = max(out_max, out_min);
+ val &= ~(CL_DVFS_OUTPUT_CFG_MAX_MASK | CL_DVFS_OUTPUT_CFG_MIN_MASK);
+ val |= out_max << CL_DVFS_OUTPUT_CFG_MAX_SHIFT;
+ val |= out_min << CL_DVFS_OUTPUT_CFG_MIN_SHIFT;
cl_dvfs_writel(cld, val, CL_DVFS_OUTPUT_CFG);
}
+static void tune_timer_cb(unsigned long data)
+{
+ unsigned long flags;
+ u32 val, out_min, out_last;
+ struct tegra_cl_dvfs *cld = (struct tegra_cl_dvfs *)data;
+
+ clk_lock_save(cld->dfll_clk, &flags);
+
+ /* FIXME: PWM output control */
+ if (cld->tune_state == TEGRA_CL_DVFS_TUNE_HIGH_REQUEST) {
+ val = cl_dvfs_readl(cld, CL_DVFS_OUTPUT_CFG);
+ out_min = (val >> CL_DVFS_OUTPUT_CFG_MIN_SHIFT) & OUT_MASK;
+ val = cl_dvfs_readl(cld, CL_DVFS_I2C_STS);
+ out_last = (val >> CL_DVFS_I2C_STS_I2C_LAST_SHIFT) & OUT_MASK;
+
+ if ((out_last >= cld->tune_high_out_min) &&
+ (out_min >= cld->tune_high_out_min)) {
+ udelay(CL_DVFS_OUTPUT_RAMP_DELAY);
+ set_tune_state(cld, TEGRA_CL_DVFS_TUNE_HIGH);
+ val = cld->safe_dvfs->dfll_data.tune0_high_mv;
+ cl_dvfs_writel(cld, val, CL_DVFS_TUNE0);
+ } else {
+ mod_timer(&cld->tune_timer, jiffies + cld->tune_delay);
+ }
+ }
+ clk_unlock_restore(cld->dfll_clk, &flags);
+}
+
static void set_request(struct tegra_cl_dvfs *cld, struct dfll_rate_req *req)
{
u32 val;
@@ -283,6 +378,19 @@ static void set_request(struct tegra_cl_dvfs *cld, struct dfll_rate_req *req)
cl_dvfs_writel(cld, val, CL_DVFS_FREQ_REQ);
}
+static u8 find_mv_out_cap(struct tegra_cl_dvfs *cld, int mv)
+{
+ u8 cap;
+ int uv;
+
+ for (cap = 0; cap < cld->num_voltages; cap++) {
+ uv = cld->out_map[cap]->reg_uV;
+ if (uv >= mv * 1000)
+ return cap;
+ }
+ return cap - 1; /* maximum possible output */
+}
+
static int find_safe_output(
struct tegra_cl_dvfs *cld, unsigned long rate, u8 *safe_output)
{
@@ -402,6 +510,26 @@ static void cl_dvfs_init_i2c_if(struct tegra_cl_dvfs *cld)
cl_dvfs_wmb(cld);
}
+static void cl_dvfs_init_tuning_thresholds(struct tegra_cl_dvfs *cld)
+{
+ int mv;
+
+ /*
+ * 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.
+ */
+ cld->tune_high_out_min = cld->num_voltages - 1;
+ cld->tune_high_out_start = cld->num_voltages - 1;
+ mv = cld->safe_dvfs->dfll_data.tune_high_min_millivolts;
+ if (mv >= cld->safe_dvfs->dfll_data.min_millivolts) {
+ cld->tune_high_out_min = find_mv_out_cap(cld, mv);
+ cld->tune_high_out_start = find_mv_out_cap(
+ cld, mv + CL_DVFS_TUNE_HIGH_MARGIN);
+ }
+}
+
static void cl_dvfs_init_out_if(struct tegra_cl_dvfs *cld)
{
int i;
@@ -427,6 +555,9 @@ static void cl_dvfs_init_out_if(struct tegra_cl_dvfs *cld)
}
cl_dvfs_wmb(cld);
+ /* setup output range thresholds for dynamic tuning */
+ cl_dvfs_init_tuning_thresholds(cld);
+
/* configure transport */
if (cld->p_data->pmu_if == TEGRA_CL_DVFS_PMU_I2C)
cl_dvfs_init_i2c_if(cld);
@@ -455,6 +586,7 @@ static void cl_dvfs_init_cntrl_logic(struct tegra_cl_dvfs *cld)
cl_dvfs_writel(cld, cld->safe_dvfs->dfll_data.tune0, CL_DVFS_TUNE0);
cl_dvfs_writel(cld, cld->safe_dvfs->dfll_data.tune1, CL_DVFS_TUNE1);
+ cld->tune_state = TEGRA_CL_DVFS_TUNE_LOW;
/* configure droop (skipper 1) and scale (skipper 2) */
val = GET_DROOP_FREQ(cld->safe_dvfs->dfll_data.droop_rate_min,
@@ -540,6 +672,12 @@ static int cl_dvfs_init(struct tegra_cl_dvfs *cld)
cld->ref_rate = clk_get_rate(cld->ref_clk);
BUG_ON(!cld->ref_rate);
+ /* init tuning timer */
+ init_timer(&cld->tune_timer);
+ cld->tune_timer.function = tune_timer_cb;
+ cld->tune_timer.data = (unsigned long)cld;
+ cld->tune_delay = usecs_to_jiffies(CL_DVFS_TUNE_HIGH_DELAY);
+
/* Get ready ouput voltage mapping*/
cl_dvfs_init_maps(cld);
@@ -581,7 +719,7 @@ static int __init tegra_cl_dvfs_probe(struct platform_device *pdev)
struct tegra_cl_dvfs_platform_data *p_data;
struct resource *res;
struct tegra_cl_dvfs *cld;
- struct clk *ref_clk, *soc_clk, *i2c_clk, *safe_dvfs_clk;
+ struct clk *ref_clk, *soc_clk, *i2c_clk, *safe_dvfs_clk, *dfll_clk;
/* Get resources */
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
@@ -600,6 +738,7 @@ static int __init tegra_cl_dvfs_probe(struct platform_device *pdev)
soc_clk = clk_get(&pdev->dev, "soc");
i2c_clk = clk_get(&pdev->dev, "i2c");
safe_dvfs_clk = clk_get(&pdev->dev, "safe_dvfs");
+ dfll_clk = clk_get(&pdev->dev, p_data->dfll_clk_name);
if (IS_ERR(ref_clk) || IS_ERR(soc_clk) || IS_ERR(i2c_clk)) {
dev_err(&pdev->dev, "missing control clock\n");
return -ENODEV;
@@ -608,6 +747,10 @@ static int __init tegra_cl_dvfs_probe(struct platform_device *pdev)
dev_err(&pdev->dev, "missing safe dvfs source clock\n");
return PTR_ERR(safe_dvfs_clk);
}
+ if (IS_ERR(dfll_clk)) {
+ dev_err(&pdev->dev, "missing target dfll clock\n");
+ return PTR_ERR(dfll_clk);
+ }
/* Allocate cl_dvfs object and populate resource accessors */
@@ -622,6 +765,7 @@ static int __init tegra_cl_dvfs_probe(struct platform_device *pdev)
cld->ref_clk = ref_clk;
cld->soc_clk = soc_clk;
cld->i2c_clk = i2c_clk;
+ cld->dfll_clk = dfll_clk;
cld->safe_dvfs = safe_dvfs_clk->dvfs;
/* Initialize cl_dvfs */
@@ -666,8 +810,9 @@ void tegra_cl_dvfs_disable(struct tegra_cl_dvfs *cld)
(cld->mode == TEGRA_CL_DVFS_DISABLED))
return;
- output_disable(cld);
+ set_ol_config(cld);
set_mode(cld, TEGRA_CL_DVFS_DISABLED);
+ output_disable(cld);
cl_dvfs_disable_clocks(cld);
}
@@ -710,7 +855,7 @@ int tegra_cl_dvfs_lock(struct tegra_cl_dvfs *cld)
* below safe value.
*/
BUG_ON(!cld->safe_ouput);
- set_max_limit(cld, req);
+ set_cl_config(cld, req);
set_request(cld, req);
output_enable(cld);
set_mode(cld, TEGRA_CL_DVFS_CLOSED_LOOP);
@@ -737,6 +882,7 @@ int tegra_cl_dvfs_unlock(struct tegra_cl_dvfs *cld)
val |= (SCALE_MAX - 1) << CL_DVFS_FREQ_REQ_SCALE_SHIFT;
cl_dvfs_writel(cld, val, CL_DVFS_FREQ_REQ);
+ set_ol_config(cld);
set_mode(cld, TEGRA_CL_DVFS_OPEN_LOOP);
ret = output_disable(cld);
return ret;
@@ -805,7 +951,7 @@ int tegra_cl_dvfs_request_rate(struct tegra_cl_dvfs *cld, unsigned long rate)
cld->last_req = req;
if (cld->mode == TEGRA_CL_DVFS_CLOSED_LOOP) {
- set_max_limit(cld, &req);
+ set_cl_config(cld, &req);
set_request(cld, &req);
cl_dvfs_wmb(cld);
}