[PATCHv2 04/19] ARM: OMAP4: PM: save/restore all DPLL settings in OFF mode

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



From: Rajendra Nayak <rnayak@xxxxxx>

SAR/ROM code restores only CORE DPLL to its original state
post wakeup from OFF mode.
The rest of the DPLL's in OMAP4 platform (MPU/IVA/ABE/USB/PER)
are saved and restored here during an OFF transition.

[nm@xxxxxx: minor cleanups]
Signed-off-by: Nishanth Menon <nm@xxxxxx>
Signed-off-by: Rajendra Nayak <rnayak@xxxxxx>
Signed-off-by: Santosh Shilimkar <santosh.shilimkar@xxxxxx>
Signed-off-by: Tero Kristo <t-kristo@xxxxxx>
---
 arch/arm/mach-omap2/cm44xx.h              |    5 +
 arch/arm/mach-omap2/dpll44xx.c            |  271 +++++++++++++++++++++++++++++
 arch/arm/mach-omap2/omap-mpuss-lowpower.c |   14 +-
 3 files changed, 285 insertions(+), 5 deletions(-)

diff --git a/arch/arm/mach-omap2/cm44xx.h b/arch/arm/mach-omap2/cm44xx.h
index 3380bee..5fba0fa 100644
--- a/arch/arm/mach-omap2/cm44xx.h
+++ b/arch/arm/mach-omap2/cm44xx.h
@@ -23,4 +23,9 @@
 #define OMAP4_CM_CLKSTCTRL				0x0000
 #define OMAP4_CM_STATICDEP				0x0004
 
+#ifndef __ASSEMBLER__
+extern void omap4_dpll_prepare_off(void);
+extern void omap4_dpll_resume_off(void);
+#endif
+
 #endif
diff --git a/arch/arm/mach-omap2/dpll44xx.c b/arch/arm/mach-omap2/dpll44xx.c
index 9c6a296..d9ec05d 100644
--- a/arch/arm/mach-omap2/dpll44xx.c
+++ b/arch/arm/mach-omap2/dpll44xx.c
@@ -14,6 +14,7 @@
 #include <linux/clk.h>
 #include <linux/io.h>
 #include <linux/bitops.h>
+#include <linux/delay.h>
 
 #include <plat/cpu.h>
 #include <plat/clock.h>
@@ -21,6 +22,96 @@
 #include "clock.h"
 #include "clock44xx.h"
 #include "cm-regbits-44xx.h"
+#include "cm1_44xx.h"
+#include "cm2_44xx.h"
+#include "prcm44xx.h"
+#include "cminst44xx.h"
+#include "cm44xx.h"
+
+#define MAX_DPLL_WAIT_TRIES	1000000
+
+struct dpll_reg {
+	u16 offset;
+	u32 val;
+};
+
+struct omap4_dpll_regs {
+	char *name;
+	u32 mod_partition;
+	u32 mod_inst;
+	struct dpll_reg clkmode;
+	struct dpll_reg autoidle;
+	struct dpll_reg idlest;
+	struct dpll_reg clksel;
+	struct dpll_reg div_m2;
+	struct dpll_reg div_m3;
+	struct dpll_reg div_m4;
+	struct dpll_reg div_m5;
+	struct dpll_reg div_m6;
+	struct dpll_reg div_m7;
+	struct dpll_reg clkdcoldo;
+};
+
+static struct omap4_dpll_regs dpll_regs[] = {
+	/* MPU DPLL */
+	{ .name		= "mpu",
+	  .mod_partition = OMAP4430_CM1_PARTITION,
+	  .mod_inst	= OMAP4430_CM1_CKGEN_INST,
+	  .clkmode	= {.offset = OMAP4_CM_CLKMODE_DPLL_MPU_OFFSET},
+	  .autoidle	= {.offset = OMAP4_CM_AUTOIDLE_DPLL_MPU_OFFSET},
+	  .idlest	= {.offset = OMAP4_CM_IDLEST_DPLL_MPU_OFFSET},
+	  .clksel	= {.offset = OMAP4_CM_CLKSEL_DPLL_MPU_OFFSET},
+	  .div_m2	= {.offset = OMAP4_CM_DIV_M2_DPLL_MPU_OFFSET},
+	},
+	/* IVA DPLL */
+	{ .name		= "iva",
+	  .mod_partition = OMAP4430_CM1_PARTITION,
+	  .mod_inst	= OMAP4430_CM1_CKGEN_INST,
+	  .clkmode	= {.offset = OMAP4_CM_CLKMODE_DPLL_IVA_OFFSET},
+	  .autoidle	= {.offset = OMAP4_CM_AUTOIDLE_DPLL_IVA_OFFSET},
+	  .idlest	= {.offset = OMAP4_CM_IDLEST_DPLL_IVA_OFFSET},
+	  .clksel	= {.offset = OMAP4_CM_CLKSEL_DPLL_IVA_OFFSET},
+	  .div_m4	= {.offset = OMAP4_CM_DIV_M4_DPLL_IVA_OFFSET},
+	  .div_m5	= {.offset = OMAP4_CM_DIV_M5_DPLL_IVA_OFFSET},
+	},
+	/* ABE DPLL */
+	{ .name		= "abe",
+	  .mod_partition = OMAP4430_CM1_PARTITION,
+	  .mod_inst	= OMAP4430_CM1_CKGEN_INST,
+	  .clkmode	= {.offset = OMAP4_CM_CLKMODE_DPLL_ABE_OFFSET},
+	  .autoidle	= {.offset = OMAP4_CM_AUTOIDLE_DPLL_ABE_OFFSET},
+	  .idlest	= {.offset = OMAP4_CM_IDLEST_DPLL_ABE_OFFSET},
+	  .clksel	= {.offset = OMAP4_CM_CLKSEL_DPLL_ABE_OFFSET},
+	  .div_m2	= {.offset = OMAP4_CM_DIV_M2_DPLL_ABE_OFFSET},
+	  .div_m3	= {.offset = OMAP4_CM_DIV_M3_DPLL_ABE_OFFSET},
+	},
+	/* USB DPLL */
+	{ .name		= "usb",
+	  .mod_partition = OMAP4430_CM2_PARTITION,
+	  .mod_inst	= OMAP4430_CM2_CKGEN_INST,
+	  .clkmode	= {.offset = OMAP4_CM_CLKMODE_DPLL_USB_OFFSET},
+	  .autoidle	= {.offset = OMAP4_CM_AUTOIDLE_DPLL_USB_OFFSET},
+	  .idlest	= {.offset = OMAP4_CM_IDLEST_DPLL_USB_OFFSET},
+	  .clksel	= {.offset = OMAP4_CM_CLKSEL_DPLL_USB_OFFSET},
+	  .div_m2	= {.offset = OMAP4_CM_DIV_M2_DPLL_USB_OFFSET},
+	  .clkdcoldo	= {.offset = OMAP4_CM_CLKDCOLDO_DPLL_USB_OFFSET},
+	 },
+	/* PER DPLL */
+	{ .name		= "per",
+	  .mod_partition = OMAP4430_CM2_PARTITION,
+	  .mod_inst	= OMAP4430_CM2_CKGEN_INST,
+	  .clkmode	= {.offset = OMAP4_CM_CLKMODE_DPLL_PER_OFFSET},
+	  .autoidle	= {.offset = OMAP4_CM_AUTOIDLE_DPLL_PER_OFFSET},
+	  .idlest	= {.offset = OMAP4_CM_IDLEST_DPLL_PER_OFFSET},
+	  .clksel	= {.offset = OMAP4_CM_CLKSEL_DPLL_PER_OFFSET},
+	  .div_m2	= {.offset = OMAP4_CM_DIV_M2_DPLL_PER_OFFSET},
+	  .div_m3	= {.offset = OMAP4_CM_DIV_M3_DPLL_PER_OFFSET},
+	  .div_m4	= {.offset = OMAP4_CM_DIV_M4_DPLL_PER_OFFSET},
+	  .div_m5	= {.offset = OMAP4_CM_DIV_M5_DPLL_PER_OFFSET},
+	  .div_m6	= {.offset = OMAP4_CM_DIV_M6_DPLL_PER_OFFSET},
+	  .div_m7	= {.offset = OMAP4_CM_DIV_M7_DPLL_PER_OFFSET},
+	},
+};
 
 /* Supported only on OMAP4 */
 int omap4_dpllmx_gatectrl_read(struct clk *clk)
@@ -151,3 +242,183 @@ long omap4_dpll_regm4xen_round_rate(struct clk *clk, unsigned long target_rate)
 
 	return clk->dpll_data->last_rounded_rate;
 }
+
+/**
+ * omap4_dpll_read_reg - reads DPLL register value
+ * @dpll_reg: DPLL register to read
+ *
+ * Reads the value of a single DPLL register.
+ */
+static inline u32 omap4_dpll_read_reg(struct omap4_dpll_regs *dpll_reg,
+				      struct dpll_reg *tuple)
+{
+	if (tuple->offset)
+		return omap4_cminst_read_inst_reg(dpll_reg->mod_partition,
+						  dpll_reg->mod_inst,
+						  tuple->offset);
+	return 0;
+}
+
+/**
+ * omap4_dpll_store_reg - stores DPLL register value to memory location
+ * @dpll_reg: DPLL register to save
+ * @tuple: save address
+ *
+ * Saves a single DPLL register content to memory location defined by
+ * @tuple before entering device off mode.
+ */
+static inline void omap4_dpll_store_reg(struct omap4_dpll_regs *dpll_reg,
+					struct dpll_reg *tuple)
+{
+	tuple->val = omap4_dpll_read_reg(dpll_reg, tuple);
+}
+
+/**
+ * omap4_dpll_prepare_off - stores DPLL settings before off mode
+ *
+ * Saves all DPLL register settings. This must be called before
+ * entering device off.
+ */
+void omap4_dpll_prepare_off(void)
+{
+	u32 i;
+	struct omap4_dpll_regs *dpll_reg = dpll_regs;
+
+	for (i = 0; i < ARRAY_SIZE(dpll_regs); i++, dpll_reg++) {
+		omap4_dpll_store_reg(dpll_reg, &dpll_reg->clkmode);
+		omap4_dpll_store_reg(dpll_reg, &dpll_reg->autoidle);
+		omap4_dpll_store_reg(dpll_reg, &dpll_reg->clksel);
+		omap4_dpll_store_reg(dpll_reg, &dpll_reg->div_m2);
+		omap4_dpll_store_reg(dpll_reg, &dpll_reg->div_m3);
+		omap4_dpll_store_reg(dpll_reg, &dpll_reg->div_m4);
+		omap4_dpll_store_reg(dpll_reg, &dpll_reg->div_m5);
+		omap4_dpll_store_reg(dpll_reg, &dpll_reg->div_m6);
+		omap4_dpll_store_reg(dpll_reg, &dpll_reg->div_m7);
+		omap4_dpll_store_reg(dpll_reg, &dpll_reg->clkdcoldo);
+		omap4_dpll_store_reg(dpll_reg, &dpll_reg->idlest);
+	}
+}
+
+/**
+ * omap4_dpll_print_reg - dump out a single DPLL register value
+ * @dpll_reg: register to dump
+ * @name: name of the register
+ * @tuple: content of the register
+ *
+ * Helper dump function to print out a DPLL register value in case
+ * of restore failures.
+ */
+static void omap4_dpll_print_reg(struct omap4_dpll_regs *dpll_reg, char *name,
+				 struct dpll_reg *tuple)
+{
+	if (tuple->offset)
+		pr_warn("%s - offset = 0x%04x, value = 0x%08x\n", name,
+			tuple->offset, tuple->val);
+}
+
+/*
+ * omap4_dpll_dump_regs - dump out DPLL registers
+ * @dpll_reg: DPLL to dump
+ *
+ * Dump out the contents of the registers for a DPLL. Called if a
+ * restore for DPLL fails to lock.
+ */
+static void omap4_dpll_dump_regs(struct omap4_dpll_regs *dpll_reg)
+{
+	pr_warn("%s: Unable to lock dpll %s[part=%x inst=%x]:\n",
+		__func__, dpll_reg->name, dpll_reg->mod_partition,
+		dpll_reg->mod_inst);
+	omap4_dpll_print_reg(dpll_reg, "clksel", &dpll_reg->clksel);
+	omap4_dpll_print_reg(dpll_reg, "div_m2", &dpll_reg->div_m2);
+	omap4_dpll_print_reg(dpll_reg, "div_m3", &dpll_reg->div_m3);
+	omap4_dpll_print_reg(dpll_reg, "div_m4", &dpll_reg->div_m4);
+	omap4_dpll_print_reg(dpll_reg, "div_m5", &dpll_reg->div_m5);
+	omap4_dpll_print_reg(dpll_reg, "div_m6", &dpll_reg->div_m6);
+	omap4_dpll_print_reg(dpll_reg, "div_m7", &dpll_reg->div_m7);
+	omap4_dpll_print_reg(dpll_reg, "clkdcoldo", &dpll_reg->clkdcoldo);
+	omap4_dpll_print_reg(dpll_reg, "clkmode", &dpll_reg->clkmode);
+	omap4_dpll_print_reg(dpll_reg, "autoidle", &dpll_reg->autoidle);
+	if (dpll_reg->idlest.offset)
+		pr_warn("idlest[0x%04x] - before val = 0x%08x"
+			" after val = 0x%08x\n", dpll_reg->idlest.offset,
+			dpll_reg->idlest.val,
+			omap4_dpll_read_reg(dpll_reg, &dpll_reg->idlest));
+}
+
+/**
+ * omap4_wait_dpll_lock - wait for a DPLL lock
+ * @dpll_reg: DPLL to wait for
+ *
+ * Waits for a DPLL lock after restore.
+ */
+static void omap4_wait_dpll_lock(struct omap4_dpll_regs *dpll_reg)
+{
+	int j = 0;
+	u32 status;
+
+	/* Return if we dont need to lock. */
+	if ((dpll_reg->clkmode.val & OMAP4430_DPLL_EN_MASK) !=
+	     DPLL_LOCKED << OMAP4430_DPLL_EN_SHIFT)
+		return;
+
+	while (1) {
+		status = (omap4_dpll_read_reg(dpll_reg, &dpll_reg->idlest)
+			  & OMAP4430_ST_DPLL_CLK_MASK)
+			 >> OMAP4430_ST_DPLL_CLK_SHIFT;
+		if (status == 0x1)
+			break;
+		if (j == MAX_DPLL_WAIT_TRIES) {
+			/* If we are unable to lock, warn and move on.. */
+			omap4_dpll_dump_regs(dpll_reg);
+			break;
+		}
+		j++;
+		udelay(1);
+	}
+}
+
+/**
+ * omap4_dpll_restore_reg - restores a single register for a DPLL
+ * @dpll_reg: DPLL to restore
+ * @tuple: register value to restore
+ *
+ * Restores a single register for a DPLL.
+ */
+static inline void omap4_dpll_restore_reg(struct omap4_dpll_regs *dpll_reg,
+					  struct dpll_reg *tuple)
+{
+	if (tuple->offset)
+		omap4_cminst_write_inst_reg(tuple->val, dpll_reg->mod_partition,
+					    dpll_reg->mod_inst, tuple->offset);
+}
+
+/**
+ * omap4_dpll_resume_off - restore DPLL settings after device off
+ *
+ * Restores all DPLL settings. Must be called after wakeup from device
+ * off.
+ */
+void omap4_dpll_resume_off(void)
+{
+	u32 i;
+	struct omap4_dpll_regs *dpll_reg = dpll_regs;
+
+	for (i = 0; i < ARRAY_SIZE(dpll_regs); i++, dpll_reg++) {
+		omap4_dpll_restore_reg(dpll_reg, &dpll_reg->clksel);
+		omap4_dpll_restore_reg(dpll_reg, &dpll_reg->div_m2);
+		omap4_dpll_restore_reg(dpll_reg, &dpll_reg->div_m3);
+		omap4_dpll_restore_reg(dpll_reg, &dpll_reg->div_m4);
+		omap4_dpll_restore_reg(dpll_reg, &dpll_reg->div_m5);
+		omap4_dpll_restore_reg(dpll_reg, &dpll_reg->div_m6);
+		omap4_dpll_restore_reg(dpll_reg, &dpll_reg->div_m7);
+		omap4_dpll_restore_reg(dpll_reg, &dpll_reg->clkdcoldo);
+
+		/* Restore clkmode after the above registers are restored */
+		omap4_dpll_restore_reg(dpll_reg, &dpll_reg->clkmode);
+
+		omap4_wait_dpll_lock(dpll_reg);
+
+		/* Restore autoidle settings after the dpll is locked */
+		omap4_dpll_restore_reg(dpll_reg, &dpll_reg->autoidle);
+	}
+}
diff --git a/arch/arm/mach-omap2/omap-mpuss-lowpower.c b/arch/arm/mach-omap2/omap-mpuss-lowpower.c
index 7418e7c..0a1d5c5 100644
--- a/arch/arm/mach-omap2/omap-mpuss-lowpower.c
+++ b/arch/arm/mach-omap2/omap-mpuss-lowpower.c
@@ -264,13 +264,15 @@ int omap4_enter_lowpower(unsigned int cpu, unsigned int power_state)
 	 * In MPUSS OSWR or device OFF, interrupt controller  contest is lost.
 	 */
 	mpuss_clear_prev_logic_pwrst();
-	if (omap4_device_next_state_off())
+	if (omap4_device_next_state_off()) {
+		omap4_dpll_prepare_off();
 		save_state = 3;
-	else if ((pwrdm_read_next_pwrst(mpuss_pd) == PWRDM_POWER_RET) &&
-		(pwrdm_read_logic_retst(mpuss_pd) == PWRDM_POWER_OFF))
+	} else if ((pwrdm_read_next_pwrst(mpuss_pd) == PWRDM_POWER_RET) &&
+		(pwrdm_read_logic_retst(mpuss_pd) == PWRDM_POWER_OFF)) {
 		save_state = 2;
-	else if (pwrdm_read_next_pwrst(mpuss_pd) == PWRDM_POWER_OFF)
+	} else if (pwrdm_read_next_pwrst(mpuss_pd) == PWRDM_POWER_OFF) {
 		save_state = 3;
+	}
 
 	cpu_clear_prev_logic_pwrst(cpu);
 	set_cpu_next_pwrst(cpu, power_state);
@@ -293,8 +295,10 @@ int omap4_enter_lowpower(unsigned int cpu, unsigned int power_state)
 	wakeup_cpu = smp_processor_id();
 	set_cpu_next_pwrst(wakeup_cpu, PWRDM_POWER_ON);
 
-	if (omap4_device_prev_state_off())
+	if (omap4_device_prev_state_off()) {
+		omap4_dpll_resume_off();
 		omap4_device_clear_prev_off_state();
+	}
 
 	pwrdm_post_transition();
 
-- 
1.7.4.1

--
To unsubscribe from this list: send the line "unsubscribe linux-omap" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[Index of Archives]     [Linux Arm (vger)]     [ARM Kernel]     [ARM MSM]     [Linux Tegra]     [Linux WPAN Networking]     [Linux Wireless Networking]     [Maemo Users]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite Trails]     [Linux Kernel]     [Linux SCSI]

  Powered by Linux