From 3b8c4ebb76302af86873158d192d76ba3b260ca8 Mon Sep 17 00:00:00 2001 From: Tony Lindgren Date: Mon, 5 May 2014 17:27:35 -0700 Subject: ARM: OMAP3: Fix idle mode signaling for sys_clkreq and sys_off_mode While debugging legacy mode vs device tree booted PM regressions, I noticed that omap3 is not toggling sys_clkreq and sys_off_mode pins like it should. The sys_clkreq and sys_off_mode pins are not toggling because of the following issues: 1. The default polarity for the sys_off_mode pin is wrong. OFFMODE_POL needs to be cleared for sys_off_mode to go down when hitting off-idle, while CLKREQ_POL needs to be set so sys_clkreq goes down when hitting retention. 2. The values for voltctrl register need to be updated dynamically. We need to set either the retention idle bits, or off idle bits in the voltctrl register depending the idle mode we're targeting to hit. Let's fix these two issues as otherwise the system will just hang if any twl4030 PMIC idle scripts are loaded. The only case where the system does not hang is if only retention idle over I2C4 is configured by the bootloader. Note that even without the twl4030 PMIC scripts, these fixes will do the proper signaling of sys_clkreq and sys_off_mode pins, so the fixes are needed to fix monitoring of PM states with LEDs or an oscilloscope. Cc: Kevin Hilman Cc: Nishanth Menon Cc: Paul Walmsley Cc: Tero Kristo Signed-off-by: Tony Lindgren --- arch/arm/mach-omap2/vc.c | 84 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 83 insertions(+), 1 deletion(-) (limited to 'arch/arm/mach-omap2/vc.c') diff --git a/arch/arm/mach-omap2/vc.c b/arch/arm/mach-omap2/vc.c index 49ac797..705eb35 100644 --- a/arch/arm/mach-omap2/vc.c +++ b/arch/arm/mach-omap2/vc.c @@ -220,6 +220,87 @@ static inline u32 omap_usec_to_32k(u32 usec) return DIV_ROUND_UP_ULL(32768ULL * (u64)usec, 1000000ULL); } +struct omap3_vc { + struct voltagedomain *vd; + u32 voltctrl; +}; +static struct omap3_vc vc; + +void omap3_vc_set_pmic_signaling(int core_next_state) +{ + struct voltagedomain *vd = vc.vd; + u32 voltctrl; + + voltctrl = vc.voltctrl; + switch (core_next_state) { + case PWRDM_POWER_OFF: + voltctrl &= ~(OMAP3430_PRM_VOLTCTRL_AUTO_RET | + OMAP3430_PRM_VOLTCTRL_AUTO_SLEEP); + voltctrl |= OMAP3430_PRM_VOLTCTRL_AUTO_OFF; + break; + case PWRDM_POWER_RET: + default: + voltctrl &= ~(OMAP3430_PRM_VOLTCTRL_AUTO_OFF | + OMAP3430_PRM_VOLTCTRL_AUTO_SLEEP); + voltctrl |= OMAP3430_PRM_VOLTCTRL_AUTO_RET; + break; + } + if (voltctrl != vc.voltctrl) { + vd->write(voltctrl, OMAP3_PRM_VOLTCTRL_OFFSET); + vc.voltctrl = voltctrl; + } +} + +#define PRM_POLCTRL_TWL_MASK (OMAP3430_PRM_POLCTRL_CLKREQ_POL | \ + OMAP3430_PRM_POLCTRL_CLKREQ_POL) +#define PRM_POLCTRL_TWL_VAL OMAP3430_PRM_POLCTRL_CLKREQ_POL + +/* + * Configure signal polarity for sys_clkreq and sys_off_mode pins + * as the default values are wrong and can cause the system to hang + * if any twl4030 scripts are loaded. + */ +static void __init omap3_vc_init_pmic_signaling(struct voltagedomain *voltdm) +{ + u32 val; + + if (vc.vd) + return; + + vc.vd = voltdm; + + val = voltdm->read(OMAP3_PRM_POLCTRL_OFFSET); + if (!(val & OMAP3430_PRM_POLCTRL_CLKREQ_POL) || + (val & OMAP3430_PRM_POLCTRL_CLKREQ_POL)) { + val |= OMAP3430_PRM_POLCTRL_CLKREQ_POL; + val &= ~OMAP3430_PRM_POLCTRL_OFFMODE_POL; + pr_debug("PM: fixing sys_clkreq and sys_off_mode polarity to 0x%x\n", + val); + voltdm->write(val, OMAP3_PRM_POLCTRL_OFFSET); + } + + /* + * By default let's use I2C4 signaling for retention idle + * and sys_off_mode pin signaling for off idle. This way we + * have sys_clk_req pin go down for retention and both + * sys_clk_req and sys_off_mode pins will go down for off + * idle. And we can also scale voltages to zero for off-idle. + * Note that no actual voltage scaling during off-idle will + * happen unless the board specific twl4030 PMIC scripts are + * loaded. + */ + val = voltdm->read(OMAP3_PRM_VOLTCTRL_OFFSET); + if (!(val & OMAP3430_PRM_VOLTCTRL_SEL_OFF)) { + val |= OMAP3430_PRM_VOLTCTRL_SEL_OFF; + pr_debug("PM: setting voltctrl sys_off_mode signaling to 0x%x\n", + val); + voltdm->write(val, OMAP3_PRM_VOLTCTRL_OFFSET); + } + vc.voltctrl = val; + + omap3_vc_set_pmic_signaling(PWRDM_POWER_ON); +} + /* Set oscillator setup time for omap3 */ static void omap3_set_clksetup(u32 usec, struct voltagedomain *voltdm) { @@ -292,7 +373,7 @@ static void omap3_set_off_timings(struct voltagedomain *voltdm) /* check if sys_off_mode is used to control off-mode voltages */ val = voltdm->read(OMAP3_PRM_VOLTCTRL_OFFSET); - if (!(val & OMAP3430_SEL_OFF_MASK)) { + if (!(val & OMAP3430_PRM_VOLTCTRL_SEL_OFF)) { /* No, omap is controlling them over I2C */ omap3_set_i2c_timings(voltdm, true); return; @@ -337,6 +418,7 @@ static void omap3_set_off_timings(struct voltagedomain *voltdm) static void __init omap3_vc_init_channel(struct voltagedomain *voltdm) { + omap3_vc_init_pmic_signaling(voltdm); omap3_set_off_timings(voltdm); } -- cgit v1.1 From 9eca2837dd69eeae0dd36ed0853d04b4de6c23bd Mon Sep 17 00:00:00 2001 From: Tony Lindgren Date: Mon, 5 May 2014 17:27:35 -0700 Subject: ARM: OMAP3: Disable broken omap3_set_off_timings function Commit c589eb3869a8 (ARM: OMAP3: VC: calculate ramp times) started using regulator slew rates for calculating the idle mode start-up times. This works fine for I2C4 controlled regulator scaling as the regulators are never completely turned off. For sys_off_mode pin controlled PMIC scripts, the slew rate based calculations won't work at all as the regulators are completely turned off and the start-up time is much longer. This means currently omap3_set_off_timings currently has zero chance of working on any real hardare. The current code is broken in at least the following ways: 1. It attempts to use the default ULONG_MAX value for the oscillator start-up value as we're currently never initializing the start-up value. 2. It relies on a magic number potentially set by the bootloader for volsetup2 register. 3. If no magic value is passed, it attempts to calculate voltsetup2 register based on the regulator slew rate. This won't work as there is roughly at least five times the delay needed for turning on vdd1 and vdd2 regulators. 4. It does duplicate register write to OMAP3_PRM_VOLTOFFSET 5. It duplicates the code for omap_usec_to_32k unnecessarily 6. It initialized global registers twice, once for each channel Let's just remove the broken code and call omap3_set_i2c_timings directly, we're better off with this function doing nothing until it's fixed. And otherwise further fixes to omap3_set_off_timings will be unreadable. And let's get rid of omap3_set_clksetup as that's not needed for off-idle controlled by I2C4 as in that case the oscillator is never shut down. Cc: Kevin Hilman Cc: Nishanth Menon Cc: Paul Walmsley Cc: Tero Kristo Signed-off-by: Tony Lindgren --- arch/arm/mach-omap2/vc.c | 62 +----------------------------------------------- 1 file changed, 1 insertion(+), 61 deletions(-) (limited to 'arch/arm/mach-omap2/vc.c') diff --git a/arch/arm/mach-omap2/vc.c b/arch/arm/mach-omap2/vc.c index 705eb35..728d64da 100644 --- a/arch/arm/mach-omap2/vc.c +++ b/arch/arm/mach-omap2/vc.c @@ -301,12 +301,6 @@ static void __init omap3_vc_init_pmic_signaling(struct voltagedomain *voltdm) omap3_vc_set_pmic_signaling(PWRDM_POWER_ON); } -/* Set oscillator setup time for omap3 */ -static void omap3_set_clksetup(u32 usec, struct voltagedomain *voltdm) -{ - voltdm->write(omap_usec_to_32k(usec), OMAP3_PRM_CLKSETUP_OFFSET); -} - /** * omap3_set_i2c_timings - sets i2c sleep timings for a channel * @voltdm: channel to configure @@ -323,12 +317,6 @@ static void omap3_set_i2c_timings(struct voltagedomain *voltdm, bool off_mode) unsigned long voltsetup1; u32 tgt_volt; - /* - * Oscillator is shut down only if we are using sys_off_mode pad, - * thus we set a minimal setup time here - */ - omap3_set_clksetup(1, voltdm); - if (off_mode) tgt_volt = voltdm->vc_param->off; else @@ -365,61 +353,13 @@ static void omap3_set_i2c_timings(struct voltagedomain *voltdm, bool off_mode) */ static void omap3_set_off_timings(struct voltagedomain *voltdm) { - unsigned long clksetup; - unsigned long voltsetup2; - unsigned long voltsetup2_old; - u32 val; - u32 tstart, tshut; - - /* check if sys_off_mode is used to control off-mode voltages */ - val = voltdm->read(OMAP3_PRM_VOLTCTRL_OFFSET); - if (!(val & OMAP3430_PRM_VOLTCTRL_SEL_OFF)) { - /* No, omap is controlling them over I2C */ - omap3_set_i2c_timings(voltdm, true); - return; - } - - omap_pm_get_oscillator(&tstart, &tshut); - omap3_set_clksetup(tstart, voltdm); - - clksetup = voltdm->read(OMAP3_PRM_CLKSETUP_OFFSET); - - /* voltsetup 2 in us */ - voltsetup2 = voltdm->vc_param->on / voltdm->pmic->slew_rate; - - /* convert to 32k clk cycles */ - voltsetup2 = DIV_ROUND_UP(voltsetup2 * 32768, 1000000); - - voltsetup2_old = voltdm->read(OMAP3_PRM_VOLTSETUP2_OFFSET); - - /* - * Update voltsetup2 if higher than current value (needed because - * we have multiple channels with different ramp times), also - * update voltoffset always to value recommended by TRM - */ - if (voltsetup2 > voltsetup2_old) { - voltdm->write(voltsetup2, OMAP3_PRM_VOLTSETUP2_OFFSET); - voltdm->write(clksetup - voltsetup2, - OMAP3_PRM_VOLTOFFSET_OFFSET); - } else - voltdm->write(clksetup - voltsetup2_old, - OMAP3_PRM_VOLTOFFSET_OFFSET); - - /* - * omap is not controlling voltage scaling during off-mode, - * thus set voltsetup1 to 0 - */ - voltdm->rmw(voltdm->vfsm->voltsetup_mask, 0, - voltdm->vfsm->voltsetup_reg); - - /* voltoffset must be clksetup minus voltsetup2 according to TRM */ - voltdm->write(clksetup - voltsetup2, OMAP3_PRM_VOLTOFFSET_OFFSET); } static void __init omap3_vc_init_channel(struct voltagedomain *voltdm) { omap3_vc_init_pmic_signaling(voltdm); omap3_set_off_timings(voltdm); + omap3_set_i2c_timings(voltdm, true); } /** -- cgit v1.1 From c46f601cc2a0e8edc97953c0b5d2acb709be71ba Mon Sep 17 00:00:00 2001 From: Tony Lindgren Date: Mon, 5 May 2014 17:27:36 -0700 Subject: ARM: OMAP3: Fix voltage control for deeper idle states Currently we're attempting to use a static value for the voltctrl register that only works for controlling the PMIC over I2C4. For using sys_off_mode signaling, we need to update update clksetup, voltsetup1, voltsetup2 and voltctrl registers dynamically depending on the idle state. So let's fix this by configuring things for I2C4 controlled idle and sys_off_mode pin controlled idle, and then write the configured register values depending on the idle state. This is similar what N900 kernel is doing too. Cc: Kevin Hilman Cc: Nishanth Menon Cc: Paul Walmsley Cc: Tero Kristo Signed-off-by: Tony Lindgren --- arch/arm/mach-omap2/vc.c | 112 +++++++++++++++++++++++++++++++++++------------ 1 file changed, 85 insertions(+), 27 deletions(-) (limited to 'arch/arm/mach-omap2/vc.c') diff --git a/arch/arm/mach-omap2/vc.c b/arch/arm/mach-omap2/vc.c index 728d64da..0d1629c 100644 --- a/arch/arm/mach-omap2/vc.c +++ b/arch/arm/mach-omap2/vc.c @@ -220,35 +220,64 @@ static inline u32 omap_usec_to_32k(u32 usec) return DIV_ROUND_UP_ULL(32768ULL * (u64)usec, 1000000ULL); } +struct omap3_vc_timings { + u32 voltsetup1; + u32 voltsetup2; +}; + struct omap3_vc { struct voltagedomain *vd; u32 voltctrl; + u32 voltsetup1; + u32 voltsetup2; + struct omap3_vc_timings timings[2]; }; static struct omap3_vc vc; void omap3_vc_set_pmic_signaling(int core_next_state) { struct voltagedomain *vd = vc.vd; - u32 voltctrl; + struct omap3_vc_timings *c = vc.timings; + u32 voltctrl, voltsetup1, voltsetup2; voltctrl = vc.voltctrl; + voltsetup1 = vc.voltsetup1; + voltsetup2 = vc.voltsetup2; + switch (core_next_state) { case PWRDM_POWER_OFF: voltctrl &= ~(OMAP3430_PRM_VOLTCTRL_AUTO_RET | OMAP3430_PRM_VOLTCTRL_AUTO_SLEEP); voltctrl |= OMAP3430_PRM_VOLTCTRL_AUTO_OFF; + if (voltctrl & OMAP3430_PRM_VOLTCTRL_SEL_OFF) + voltsetup2 = c->voltsetup2; + else + voltsetup1 = c->voltsetup1; break; case PWRDM_POWER_RET: default: + c++; voltctrl &= ~(OMAP3430_PRM_VOLTCTRL_AUTO_OFF | OMAP3430_PRM_VOLTCTRL_AUTO_SLEEP); voltctrl |= OMAP3430_PRM_VOLTCTRL_AUTO_RET; + voltsetup1 = c->voltsetup1; break; } + if (voltctrl != vc.voltctrl) { vd->write(voltctrl, OMAP3_PRM_VOLTCTRL_OFFSET); vc.voltctrl = voltctrl; } + if (voltsetup1 != vc.voltsetup1) { + vd->write(c->voltsetup1, + OMAP3_PRM_VOLTSETUP1_OFFSET); + vc.voltsetup1 = voltsetup1; + } + if (voltsetup2 != vc.voltsetup2) { + vd->write(c->voltsetup2, + OMAP3_PRM_VOLTSETUP2_OFFSET); + vc.voltsetup2 = voltsetup2; + } } #define PRM_POLCTRL_TWL_MASK (OMAP3430_PRM_POLCTRL_CLKREQ_POL | \ @@ -301,6 +330,18 @@ static void __init omap3_vc_init_pmic_signaling(struct voltagedomain *voltdm) omap3_vc_set_pmic_signaling(PWRDM_POWER_ON); } +static void omap3_init_voltsetup1(struct voltagedomain *voltdm, + struct omap3_vc_timings *c, u32 idle) +{ + unsigned long val; + + val = (voltdm->vc_param->on - idle) / voltdm->pmic->slew_rate; + val *= voltdm->sys_clk.rate / 8 / 1000000 + 1; + val <<= __ffs(voltdm->vfsm->voltsetup_mask); + c->voltsetup1 &= ~voltdm->vfsm->voltsetup_mask; + c->voltsetup1 |= val; +} + /** * omap3_set_i2c_timings - sets i2c sleep timings for a channel * @voltdm: channel to configure @@ -311,31 +352,21 @@ static void __init omap3_vc_init_pmic_signaling(struct voltagedomain *voltdm) * or retention. Off mode has additionally an option to use sys_off_mode * pad, which uses a global signal to program the whole power IC to * off-mode. + * + * Note that pmic is not controlling the voltage scaling during + * retention signaled over I2C4, so we can keep voltsetup2 as 0. + * And the oscillator is not shut off over I2C4, so no need to + * set clksetup. */ -static void omap3_set_i2c_timings(struct voltagedomain *voltdm, bool off_mode) +static void omap3_set_i2c_timings(struct voltagedomain *voltdm) { - unsigned long voltsetup1; - u32 tgt_volt; - - if (off_mode) - tgt_volt = voltdm->vc_param->off; - else - tgt_volt = voltdm->vc_param->ret; - - voltsetup1 = (voltdm->vc_param->on - tgt_volt) / - voltdm->pmic->slew_rate; + struct omap3_vc_timings *c = vc.timings; - voltsetup1 = voltsetup1 * voltdm->sys_clk.rate / 8 / 1000000 + 1; - - voltdm->rmw(voltdm->vfsm->voltsetup_mask, - voltsetup1 << __ffs(voltdm->vfsm->voltsetup_mask), - voltdm->vfsm->voltsetup_reg); - - /* - * pmic is not controlling the voltage scaling during retention, - * thus set voltsetup2 to 0 - */ - voltdm->write(0, OMAP3_PRM_VOLTSETUP2_OFFSET); + /* Configure PRWDM_POWER_OFF over I2C4 */ + omap3_init_voltsetup1(voltdm, c, voltdm->vc_param->off); + c++; + /* Configure PRWDM_POWER_RET over I2C4 */ + omap3_init_voltsetup1(voltdm, c, voltdm->vc_param->ret); } /** @@ -344,22 +375,49 @@ static void omap3_set_i2c_timings(struct voltagedomain *voltdm, bool off_mode) * * Calculates and sets up off-mode timings for a channel. Off-mode * can use either I2C based voltage scaling, or alternatively - * sys_off_mode pad can be used to send a global command to power IC. - * This function first checks which mode is being used, and calls - * omap3_set_i2c_timings() if the system is using I2C control mode. + * sys_off_mode pad can be used to send a global command to power IC.n, * sys_off_mode has the additional benefit that voltages can be * scaled to zero volt level with TWL4030 / TWL5030, I2C can only * scale to 600mV. + * + * Note that omap is not controlling the voltage scaling during + * off idle signaled by sys_off_mode, so we can keep voltsetup1 + * as 0. */ static void omap3_set_off_timings(struct voltagedomain *voltdm) { + struct omap3_vc_timings *c = vc.timings; + u32 tstart, tshut, clksetup, voltoffset; + + if (c->voltsetup2) + return; + + omap_pm_get_oscillator(&tstart, &tshut); + if (tstart == ULONG_MAX) { + pr_debug("PM: oscillator start-up time not initialized, using 10ms\n"); + clksetup = omap_usec_to_32k(10000); + } else { + clksetup = omap_usec_to_32k(tstart); + } + + /* + * For twl4030 errata 27, we need to allow minimum ~488.32 us wait to + * switch from HFCLKIN to internal oscillator. That means timings + * have voltoffset fixed to 0xa in rounded up 32 KiHz cycles. And + * that means we can calculate the value based on the oscillator + * start-up time since voltoffset2 = clksetup - voltoffset. + */ + voltoffset = omap_usec_to_32k(488); + c->voltsetup2 = clksetup - voltoffset; + voltdm->write(clksetup, OMAP3_PRM_CLKSETUP_OFFSET); + voltdm->write(voltoffset, OMAP3_PRM_VOLTOFFSET_OFFSET); } static void __init omap3_vc_init_channel(struct voltagedomain *voltdm) { omap3_vc_init_pmic_signaling(voltdm); omap3_set_off_timings(voltdm); - omap3_set_i2c_timings(voltdm, true); + omap3_set_i2c_timings(voltdm); } /** -- cgit v1.1