ARM: OMAP: AM33XX: PM: Manipulate MPU voltage in suspend-resume
authorVaibhav Bedia <vaibhav.bedia@ti.com>
Mon, 12 Mar 2012 18:15:27 +0000 (23:45 +0530)
committerVaibhav Bedia <vaibhav.bedia@ti.com>
Tue, 13 Mar 2012 04:25:44 +0000 (09:55 +0530)
As per the AM335x datasheet, when going to the lowest power
mode, DeepSleep0 in the docs, MPU and CORE voltages should be
scaled down to 0.95V. This is currently done via clk and regulator
APIs in the suspend-resume code.

Since the I2C driver implements aggressive RPM, we also need
to disable this during the suspend process.

Note: We are a little bit paranoid and currently scale down the
voltage to 0.965V instead of 0.95V to accomodate for some IR drop
that was earlier seen on the EVMs.

TODO:
1. Add some more error handling here
2. Check if MPU change can be moved into the cpufreq driver
3. Check if there's a better way of keeping I2C enabled very late
   in the suspend process.
4. Scale the CORE voltage also.

Signed-off-by: AnilKumar Ch <anilkumar@ti.com>
Signed-off-by: Vaibhav Bedia <vaibhav.bedia@ti.com>
arch/arm/mach-omap2/pm33xx.c

index ee56a0f879fcc51da706b592af82983cf94911d0..38e96b56863af306082a99e560a538577e6201a2 100644 (file)
@@ -25,6 +25,7 @@
 #include <linux/suspend.h>
 #include <linux/completion.h>
 #include <linux/regulator/consumer.h>
+#include <linux/pm_runtime.h>
 
 #include <plat/prcm.h>
 #include <plat/mailbox.h>
@@ -56,6 +57,7 @@ bool enable_deep_sleep = true;
 static suspend_state_t suspend_state = PM_SUSPEND_ON;
 
 static struct device *mpu_dev;
+static struct device *i2c_dev;
 static struct omap_mbox *m3_mbox;
 static struct powerdomain *cefuse_pwrdm, *gfx_pwrdm;
 static struct clockdomain *gfx_l3_clkdm, *gfx_l4ls_clkdm;
@@ -72,6 +74,28 @@ static void am33xx_m3_state_machine_reset(void);
 
 static DECLARE_COMPLETION(a8_m3_sync);
 
+#define VOLT_TOLERANCE 12500 /* 12.5mV */
+
+struct suspend_opp_change {
+       char *clk_name;
+       char *reg_name;
+       unsigned long freq1;    /* Frequency in suspend mode */
+       unsigned long freq2;    /* Frequency in active mode */
+       unsigned int volt1;     /* Voltage in suspend mode */
+       unsigned int volt2;     /* Voltage in active mode */
+       struct clk *clk;
+       struct regulator *reg;
+};
+
+static struct suspend_opp_change opp[] = {
+       {
+               .clk_name       = "dpll_mpu_ck",
+               .reg_name       = "vdd_mpu",
+               .freq1          = 275000000,
+               .volt1          = 965000,
+       },
+};
+
 static void save_padconf(void)
 {
        lp_padconf.mii1_col     = readl(AM33XX_CTRL_REGADDR(0x0908));
@@ -121,12 +145,54 @@ static void restore_padconf(void)
 
 static int am33xx_pm_prepare_late(void)
 {
+       int ret = 0;
+
        save_padconf();
-       return 0;
+
+       if(!opp[0].clk || !opp[0].reg || !i2c_dev) {
+               pr_debug("Not attempting OPP change in suspend\n");
+               return ret;
+       }
+
+       opp[0].freq2 = clk_get_rate(opp[0].clk);
+       ret = clk_set_rate(opp[0].clk, opp[0].freq1);
+       if (ret) {
+               pr_debug("Failed to set %s frequency to %luMHz\n",
+                                       opp[0].clk_name, opp[0].freq1/1000000);
+       } else {
+               opp[0].volt2 = regulator_get_voltage(opp[0].reg);
+               ret = regulator_set_voltage(opp[0].reg, opp[0].volt1,
+                                       opp[0].volt1 + VOLT_TOLERANCE);
+               if (ret)
+                       pr_debug("Failed to set %s voltage to %dmV\n",
+                                       opp[0].reg_name, opp[0].volt1/1000);
+       }
+
+       return ret;
 }
 
 static void am33xx_pm_finish(void)
 {
+       int ret = 0;
+
+       if(!opp[0].clk || !opp[0].reg || !i2c_dev) {
+               pr_debug("Not attempting OPP change in resume\n");
+               goto padconf;
+       }
+
+       ret = regulator_set_voltage(opp[0].reg, opp[0].volt2,
+                                       opp[0].volt2 + VOLT_TOLERANCE);
+       if (ret) {
+               pr_warning("Failed to restore %s voltage to %dmV\n",
+                                       opp[0].reg_name, opp[0].volt2/1000);
+       } else {
+               ret = clk_set_rate(opp[0].clk, opp[0].freq2);
+               if (ret)
+                       pr_warning("Failed to restore %s frequency to %luMHz\n",
+                                       opp[0].clk_name, opp[0].freq2/1000000);
+       }
+
+padconf:
        restore_padconf();
 }
 
@@ -208,6 +274,9 @@ static int am33xx_pm_begin(suspend_state_t state)
 
        disable_hlt();
 
+       if(i2c_dev)
+               pm_runtime_forbid(i2c_dev);
+
        am33xx_lp_ipc.resume_addr = DS_RESUME_ADDR;
        am33xx_lp_ipc.sleep_mode  = DS_MODE;
        am33xx_lp_ipc.ipc_data1   = DS_IPC_DEFAULT;
@@ -276,6 +345,9 @@ static void am33xx_pm_end(void)
 
        am33xx_m3_state_machine_reset();
 
+       if(i2c_dev)
+               pm_runtime_allow(i2c_dev);
+
        enable_hlt();
 
        return;
@@ -511,7 +583,7 @@ void am33xx_push_sram_idle(void)
 
 static int __init am33xx_pm_init(void)
 {
-       int ret;
+       int ret, i;
 
        if (!cpu_is_am33xx())
                return -ENODEV;
@@ -547,6 +619,26 @@ static int __init am33xx_pm_init(void)
                return -EINVAL;
        }
 
+       i2c_dev = omap_device_get_by_hwmod_name("i2c1");
+
+       if (!i2c_dev) {
+               pr_warning("%s: unable to get the i2c device\n", __func__);
+               return -EINVAL;
+       }
+
+       for (i = 0; i < ARRAY_SIZE(opp); i++) {
+               opp[i].clk = clk_get(NULL, opp[i].clk_name);
+               if (IS_ERR(opp[i].clk))
+                       pr_warning("Could not get %s clock\n", opp[i].clk_name);
+
+               if (opp[i].reg_name) {
+                       opp[i].reg = regulator_get(NULL, opp[i].reg_name);
+                       if (IS_ERR(opp[i].reg))
+                               pr_warning("Could not get %s regulator\n",
+                                                       opp[i].reg_name);
+               }
+       }
+
        ret = wkup_m3_init();
 
        if (ret) {