]> Gitweb @ Texas Instruments - Open Source Git Repositories - git.TI.com/gitweb - sitara-epos/sitara-epos-kernel.git/blobdiff - arch/arm/mach-omap2/pm33xx.c
ARM: OMAP: AM33XX: PM: Manipulate MPU voltage in suspend-resume
[sitara-epos/sitara-epos-kernel.git] / arch / arm / mach-omap2 / pm33xx.c
index bdb08645c8a5dab5b5fc0808876462805d89c270..38e96b56863af306082a99e560a538577e6201a2 100644 (file)
@@ -24,6 +24,8 @@
 #include <linux/sched.h>
 #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>
@@ -37,6 +39,7 @@
 
 #include "pm.h"
 #include "pm33xx.h"
+#include "control.h"
 #include "clockdomain.h"
 #include "powerdomain.h"
 
@@ -54,10 +57,14 @@ 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;
 
+static struct am33xx_padconf lp_padconf;
+static int gmii_sel;
+
 static int core_suspend_stat = -1;
 static int m3_state = M3_STATE_UNKNOWN;
 
@@ -67,6 +74,128 @@ 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));
+       lp_padconf.mii1_crs     = readl(AM33XX_CTRL_REGADDR(0x090c));
+       lp_padconf.mii1_rxerr   = readl(AM33XX_CTRL_REGADDR(0x0910));
+       lp_padconf.mii1_txen    = readl(AM33XX_CTRL_REGADDR(0x0914));
+       lp_padconf.mii1_rxdv    = readl(AM33XX_CTRL_REGADDR(0x0918));
+       lp_padconf.mii1_txd3    = readl(AM33XX_CTRL_REGADDR(0x091c));
+       lp_padconf.mii1_txd2    = readl(AM33XX_CTRL_REGADDR(0x0920));
+       lp_padconf.mii1_txd1    = readl(AM33XX_CTRL_REGADDR(0x0924));
+       lp_padconf.mii1_txd0    = readl(AM33XX_CTRL_REGADDR(0x0928));
+       lp_padconf.mii1_txclk   = readl(AM33XX_CTRL_REGADDR(0x092c));
+       lp_padconf.mii1_rxclk   = readl(AM33XX_CTRL_REGADDR(0x0930));
+       lp_padconf.mii1_rxd3    = readl(AM33XX_CTRL_REGADDR(0x0934));
+       lp_padconf.mii1_rxd2    = readl(AM33XX_CTRL_REGADDR(0x0938));
+       lp_padconf.mii1_rxd1    = readl(AM33XX_CTRL_REGADDR(0x093c));
+       lp_padconf.mii1_rxd0    = readl(AM33XX_CTRL_REGADDR(0x0940));
+       lp_padconf.rmii1_refclk = readl(AM33XX_CTRL_REGADDR(0x0944));
+       lp_padconf.mdio_data    = readl(AM33XX_CTRL_REGADDR(0x0948));
+       lp_padconf.mdio_clk     = readl(AM33XX_CTRL_REGADDR(0x094c));
+       gmii_sel                = readl(AM33XX_CTRL_REGADDR(0x0650));
+}
+
+static void restore_padconf(void)
+{
+       writel(lp_padconf.mii1_col, AM33XX_CTRL_REGADDR(0x0908));
+       writel(lp_padconf.mii1_crs, AM33XX_CTRL_REGADDR(0x090c));
+       writel(lp_padconf.mii1_rxerr, AM33XX_CTRL_REGADDR(0x0910));
+       writel(lp_padconf.mii1_txen, AM33XX_CTRL_REGADDR(0x0914));
+       writel(lp_padconf.mii1_rxdv, AM33XX_CTRL_REGADDR(0x0918));
+       writel(lp_padconf.mii1_txd3, AM33XX_CTRL_REGADDR(0x091c));
+       writel(lp_padconf.mii1_txd2, AM33XX_CTRL_REGADDR(0x0920));
+       writel(lp_padconf.mii1_txd1, AM33XX_CTRL_REGADDR(0x0924));
+       writel(lp_padconf.mii1_txd0, AM33XX_CTRL_REGADDR(0x0928));
+       writel(lp_padconf.mii1_txclk, AM33XX_CTRL_REGADDR(0x092c));
+       writel(lp_padconf.mii1_rxclk, AM33XX_CTRL_REGADDR(0x0930));
+       writel(lp_padconf.mii1_rxd3, AM33XX_CTRL_REGADDR(0x0934));
+       writel(lp_padconf.mii1_rxd2, AM33XX_CTRL_REGADDR(0x0938));
+       writel(lp_padconf.mii1_rxd1, AM33XX_CTRL_REGADDR(0x093c));
+       writel(lp_padconf.mii1_rxd0, AM33XX_CTRL_REGADDR(0x0940));
+       writel(lp_padconf.rmii1_refclk, AM33XX_CTRL_REGADDR(0x0944));
+       writel(lp_padconf.mdio_data, AM33XX_CTRL_REGADDR(0x0948));
+       writel(lp_padconf.mdio_clk, AM33XX_CTRL_REGADDR(0x094c));
+       writel(gmii_sel, AM33XX_CTRL_REGADDR(0x0650));
+}
+
+
+static int am33xx_pm_prepare_late(void)
+{
+       int ret = 0;
+
+       save_padconf();
+
+       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();
+}
+
 static int am33xx_do_sram_idle(long unsigned int state)
 {
        am33xx_do_wfi_sram();
@@ -145,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;
@@ -193,6 +325,8 @@ static void am33xx_m3_state_machine_reset(void)
        ret = omap_mbox_msg_send(m3_mbox, 0xABCDABCD);
        if (!ret) {
                pr_debug("Message sent for resetting M3 state machine\n");
+               if (!wait_for_completion_timeout(&a8_m3_sync, msecs_to_jiffies(5000)))
+                       pr_err("A8<->CM3 sync failure\n");
        } else {
                pr_debug("Could not reset M3 state machine!!!\n");
                m3_state = M3_STATE_UNKNOWN;
@@ -211,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;
@@ -221,6 +358,8 @@ static const struct platform_suspend_ops am33xx_pm_ops = {
        .end            = am33xx_pm_end,
        .enter          = am33xx_pm_enter,
        .valid          = suspend_valid_only_mem,
+       .prepare        = am33xx_pm_prepare_late,
+       .finish         = am33xx_pm_finish,
 };
 
 int am33xx_ipc_cmd(struct a8_wkup_m3_ipc_data *data)
@@ -256,7 +395,7 @@ static int am33xx_verify_lp_state(void)
                ret = -1;
                goto clear_old_status;
        } else {
-               pr_info("Status = %0x\n", status);
+               pr_info("Something went wrong :(\nStatus = %0x\n", status);
                ret = -1;
        }
 
@@ -295,6 +434,7 @@ static irqreturn_t wkup_m3_txev_handler(int irq, void *unused)
                omap_mbox_msg_rx_flush(m3_mbox);
                if (m3_mbox->ops->ack_irq)
                        m3_mbox->ops->ack_irq(m3_mbox, IRQ_RX);
+               complete(&a8_m3_sync);
        } else if (m3_state == M3_STATE_MSG_FOR_LP) {
                /* Read back the MBOX and disable the interrupt to M3 since we are going down */
                omap_mbox_msg_rx_flush(m3_mbox);
@@ -371,8 +511,10 @@ static int wkup_m3_init(void)
                goto err5;
        }
 
+       pr_info("Trying to load am335x-pm-firmware.bin\n");
+
        /* Now try to load the firware */
-       ret = request_firmware(&firmware, "cm3-firmware.bin", mpu_dev);
+       ret = request_firmware(&firmware, "am335x-pm-firmware.bin", mpu_dev);
        if (ret < 0) {
                dev_err(mpu_dev, "request_firmware failed\n");
                goto err6;
@@ -416,15 +558,6 @@ err1:
 exit:
        return ret;
 }
-#endif /* CONFIG_SUSPEND */
-
-/*
- * Push the minimal suspend-resume code to SRAM
- */
-void am33xx_push_sram_idle(void)
-{
-       am33xx_do_wfi_sram = omap_sram_push(am33xx_do_wfi, am33xx_do_wfi_sz);
-}
 
 /*
  * Enable hardware supervised mode for all clockdomains if it's
@@ -438,10 +571,19 @@ static int __init clkdms_setup(struct clockdomain *clkdm, void *unused)
                clkdm_sleep(clkdm);
        return 0;
 }
+#endif /* CONFIG_SUSPEND */
+
+/*
+ * Push the minimal suspend-resume code to SRAM
+ */
+void am33xx_push_sram_idle(void)
+{
+       am33xx_do_wfi_sram = omap_sram_push(am33xx_do_wfi, am33xx_do_wfi_sz);
+}
 
 static int __init am33xx_pm_init(void)
 {
-       int ret;
+       int ret, i;
 
        if (!cpu_is_am33xx())
                return -ENODEV;
@@ -477,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) {