arm: tegra: Set Core to 0.95V in LP1
Karthik Ramakrishnan [Tue, 14 Aug 2012 02:09:16 +0000 (19:09 -0700)]
When the device prepares for LP1, the Core voltage is set to the
highest value(1.2V for Enterprise and Kai, and 1.3V for AP37 and
Cardhu). This is to set for some of the driver suspend along the
sequence need a higher emc frequency and thus a higher Core voltage.

Since the sequence of drivers suspend depends on the sequence of
their registration in the table, which in turn is platform-dependent,
there is no right place in the LP1 entry path where the Core voltage
can be set to a lower voltage. Hence, the Core voltage remains high
in LP1 resulting in higher power.

Thus, the only safe location where the Core voltage can be lowered is
once all the drivers are suspended and the DRAM is set in self-refresh,
at the final point just before the system is suspended in the IRAM code.
This location at the assembly code ensures that no other module will be
running and thus that nothing will require a higher core voltage. The Core
is set to the lowest possible value since nothing requires it. It is then
restored to the highest voltage as soon as the LP1 resume code is started
so that all drivers are resumed safely.

At the execution point in IRAM during LP1 suspend path, even the I2C clocks
are gated. They must be reset first and then the I2C transaction is performed.
An I2C transaction involves 4 bytes of data, to send the slave address,
the Core voltage register address and 2 bytes of data which has the value
to set the voltage(the second byte is not required for this transaction).
Once these registers are set, the I2C transaction is performed by setting
the I2C transaction register to 0xA02. After sending the I2C transaction,
we wait for about 250us to check the status of the transaction and if not
updated, wait for more time to check again. If after 2ms and the transaction
fails to register, the transaction is aborted and the device is allowed
to enter at high voltage. Since the failure rate of I2C transaction is very
low at this point in execution where there will be no conflicts in the bus,
it is okay to have Core high for some of the LP1 cycles.

However, it is unacceptable for the I2C transaction to fail on the way
from LP1 resume since the device cannot come up with a lower Core
voltage. In this case, the transaction is retried again and again till
it is successful. There is no way but to keep trying as the device
would fail to resume with Core at 0.95V.

Each platform(or each PMU) has different values for the I2C transaction
ie. slave address, Core voltage register and the value to set the
voltage. For the device in IRAM, it cannot access anything in SDRAM
memory, these values needs to be pushed to IRAM memory before the device
starts execution in IRAM. This is done during initialization of suspend
code when it picks values from the board files and copies it to IRAM
part of code, before the whole memory is copied to IRAM.

This new feature is controlled by a KConfig variable TEGRA_LP1_950 which
should be enabled once the board file of the device is updated with the
right values. The device hangs when it does not have the right values for
the I2C transaction.

With this change in Core, LP1 power is reduced by 12mW in Enterprise,
20mW in AP37 and about 24mW in Kai.

Bug 1035684

Change-Id: I4318c66fd70ab227ef0786d6a13286e020e4541d
Signed-off-by: Karthik Ramakrishnan <karthikr@nvidia.com>
(cherry picked from commit ab476f287376fd0ae51a9f298659f5eba19f0296)
Reviewed-on: http://git-master/r/124779
Reviewed-by: Lokesh Pathak <lpathak@nvidia.com>
Tested-by: Lokesh Pathak <lpathak@nvidia.com>

arch/arm/mach-tegra/Kconfig
arch/arm/mach-tegra/pm.c
arch/arm/mach-tegra/pm.h
arch/arm/mach-tegra/sleep-t3.S
arch/arm/mach-tegra/sleep.h

index b962737..422ad20 100644 (file)
@@ -577,6 +577,13 @@ config TEGRA_SKIN_THROTTLE
        help
          Enable throttling to control the temperature of the skin/case
          of the device.
+
+config TEGRA_LP1_950
+       bool "LP1 low core voltage"
+       default n
+       depends on ARCH_TEGRA_3x_SOC
+       help
+               Enable support for LP1 Core voltage to set to lowest
 endif
 
 config TEGRA_DC_USE_HW_BPP
index d251e57..2cdfdfe 100644 (file)
@@ -1186,6 +1186,21 @@ out:
                plat->suspend_mode = TEGRA_SUSPEND_LP2;
        }
 
+#ifdef CONFIG_TEGRA_LP1_950
+       if (pdata->lp1_lowvolt_support) {
+               u32 lp1_core_lowvolt, lp1_core_highvolt;
+               memcpy(tegra_lp1_register_pmuslave_addr(), &pdata->pmuslave_addr, 4);
+               memcpy(tegra_lp1_register_i2c_base_addr(), &pdata->i2c_base_addr, 4);
+
+               lp1_core_lowvolt = 0;
+               lp1_core_lowvolt = (pdata->lp1_core_volt_low << 8) | pdata->core_reg_addr;
+               memcpy(tegra_lp1_register_core_lowvolt(), &lp1_core_lowvolt, 4);
+
+               lp1_core_highvolt = 0;
+               lp1_core_highvolt = (pdata->lp1_core_volt_high << 8) | pdata->core_reg_addr;
+               memcpy(tegra_lp1_register_core_highvolt(), &lp1_core_highvolt, 4);
+       }
+#endif
        /* !!!FIXME!!! THIS IS TEGRA2 ONLY */
        /* Initialize scratch registers used for CPU LP2 synchronization */
        writel(0, pmc + PMC_SCRATCH37);
index b78e9b1..5ea2b7f 100644 (file)
@@ -65,6 +65,14 @@ struct tegra_suspend_platform_data {
        /* lp_state = 0 for LP0 state, 1 for LP1 state, 2 for LP2 state */
        void (*board_resume)(int lp_state, enum resume_stage stg);
        unsigned int cpu_resume_boost;  /* CPU frequency resume boost in kHz */
+#ifdef CONFIG_TEGRA_LP1_950
+       bool lp1_lowvolt_support;
+       unsigned int i2c_base_addr;
+       unsigned int pmuslave_addr;
+       unsigned int core_reg_addr;
+       unsigned int lp1_core_volt_low;
+       unsigned int lp1_core_volt_high;
+#endif
 };
 
 /* clears io dpd settings before kernel code */
index 23e96c6..4ee73d5 100644 (file)
 #define PMC_PLLM_WB0_OVERRIDE          0x1dc
 
 #define CLK_RESET_CLK_SOURCE_MSELECT   0x3b4
+#define CLK_RESET_CLK_ENB_H_SET                0x328
+#define CLK_RESET_CLK_ENB_H_CLR                0x32c
+#define CLK_RESET_CLK_RST_DEV_H_SET            0x308
+#define CLK_RESET_CLK_RST_DEV_H_CLR            0x30c
+
+#define I2C_CNFG       0x0
+#define I2C_ADDR0      0x4
+#define I2C_DATA1      0xc
+#define I2C_DATA2      0x10
+#define I2C_STATUS     0x1c
 
 #define MSELECT_CLKM                   (0x3 << 30)
 
@@ -348,6 +358,66 @@ ENTRY(tegra3_lp1_reset)
        mov32   r4, ((1<<28) | (8))     @ burst policy is PLLX
        str     r4, [r0, #CLK_RESET_CCLK_BURST]
 
+#ifdef CONFIG_TEGRA_LP1_950
+lp1_voltset:
+       /* Restore the Core voltage to high on LP1 resume */
+       /* Reset(Enable/Disable) the DVC-I2C Controller*/
+       mov r1, #(1 << 15)
+       str r1, [r0, #CLK_RESET_CLK_RST_DEV_H_SET]
+
+       /* Wait for 2us */
+       mov32   r7, TEGRA_TMRUS_BASE
+       wait_for_us r1, r7, r9
+       add r1, r1, #2
+       wait_until r1, r7, r9
+
+       mov r1, #(1 << 15)
+       str r1, [r0, #CLK_RESET_CLK_RST_DEV_H_CLR]
+
+       /* Enable the DVC-I2C Controller */
+       mov r1, #(1 << 15)
+       str r1, [r0, #CLK_RESET_CLK_ENB_H_SET]
+
+
+       /* Same I2C transaction protocol as suspend */
+       ldr r1, lp1_register_pmuslave_addr
+       cmp r1, #0
+       beq lp1_voltskip_resume
+
+       ldr r4, lp1_register_i2c_base_addr
+       str r1, [r4, #I2C_ADDR0]
+
+       mov32 r1, 0x2
+       str r1, [r4, #I2C_CNFG]
+
+       ldr r1, lp1_register_core_highvolt
+       str r1, [r4, #I2C_DATA1]
+
+       mov32 r1, 0
+       str r1, [r4, #I2C_DATA2]
+
+       mov32 r1, 0xA02
+       str r1, [r4, #I2C_CNFG]
+
+       wait_for_us r1, r7, r9
+       mov32 r3, 0x7D0   /* Wait for 2ms and try transaction again */
+       add r0, r1, r3
+loop_i2c_status_resume:
+       add r1, r1, #0xFA /* Check status every 250us */
+       wait_until r1, r7, r9
+       cmp r0, r1
+       beq lp1_voltset
+
+       ldr r3, [r4, #I2C_STATUS]
+       cmp r3, #0
+       bne loop_i2c_status_resume
+
+lp1_voltskip_resume:
+       /* Disable the DVC-I2C Controller */
+       mov r0, #(1 << 15)
+       str r0, [r5, #CLK_RESET_CLK_ENB_H_CLR]
+#endif
+
 #if defined (CONFIG_CACHE_L2X0)
        /* power up L2 */
        ldr     r0, [r2, #PMC_PWRGATE_STATUS]
@@ -492,6 +562,21 @@ tegra3_sdram_pad_address:
 tegra3_sdram_pad_size:
        .word   tegra3_sdram_pad_address - tegra3_sdram_pad_save
 
+#ifdef CONFIG_TEGRA_LP1_950
+       .globl lp1_register_pmuslave_addr
+       .globl lp1_register_i2c_base_addr
+       .globl lp1_register_core_lowvolt
+       .globl lp1_register_core_highvolt
+lp1_register_pmuslave_addr:
+       .word   0
+lp1_register_i2c_base_addr:
+       .word   0
+lp1_register_core_lowvolt:
+       .word   0
+lp1_register_core_highvolt:
+       .word   0
+#endif
+
 /*
  * tegra3_tear_down_core
  *
@@ -526,9 +611,72 @@ tegra3_cpu_clk32k:
        str     r0, [r4, #PMC_PLLM_WB0_OVERRIDE]
        mov     pc, lr
 
+lp1_clocks_prepare:
+       /* Prepare to set the Core to the lowest voltage if supported.
+        * Start by setting the I2C clocks to make the I2C transfer */
+#ifdef CONFIG_TEGRA_LP1_950
+       /* Set up the PWR I2C GPIOs with the right masks*/
+
+       /* Reset(Set/Clr) the DVC-I2C Controller*/
+       mov r0, #(1 << 15)
+       str r0, [r5, #CLK_RESET_CLK_RST_DEV_H_SET]
+
+       /* Wait for 2us */
+       wait_for_us r1, r7, r9
+       mov32 r0, 0x7D0
+       add r1, r1, r0
+       wait_until r1, r7, r9
+
+       mov r0, #(1 << 15)
+       str r0, [r5, #CLK_RESET_CLK_RST_DEV_H_CLR]
+
+       /* Enable the DVC-I2C Controller */
+       mov r0, #(1 << 15)
+       str r0, [r5, #CLK_RESET_CLK_ENB_H_SET]
+
+       /* I2C transfer protocol:
+        * 4 packets: Slaveaddr + WriteConfigure + Data1 + Data2 */
+       ldr r0, lp1_register_pmuslave_addr
+       cmp r0, #0
+       beq lp1_volt_skip
+       ldr r1, lp1_register_i2c_base_addr
+       str r0, [r1, #I2C_ADDR0]
+
+       mov32 r0, 0x2
+       str r0, [r1, #I2C_CNFG]
+
+       ldr r0, lp1_register_core_lowvolt
+       str r0, [r1, #I2C_DATA1]
+
+       mov32 r0, 0
+       str r0, [r1, #I2C_DATA2]
+
+       /* Send I2C transaction */
+       mov32 r0, 0xA02
+       str r0, [r1, #I2C_CNFG]
+
+       /* Check the transaction status before proceeding */
+       wait_for_us r2, r7, r9
+       mov32 r3, 0x7D0 /* Wait for 2ms for I2C transaction */
+       add r3, r2, r3
+loop_i2c_status_suspend:
+       add r2, r2, #0xFA /* Check status every 250us */
+       cmp r3, r2
+       beq lp1_volt_skip  /* Waited for 2ms, I2C transaction didn't take place */
+       wait_until r2, r7, r9
+
+       ldr r0, [r1, #I2C_STATUS]
+       cmp r0, #0
+       bne loop_i2c_status_suspend
+lp1_volt_skip:
+
+       /* Disable the DVC-I2C Controller */
+       mov r0, #(1 << 15)
+       str r0, [r5, #CLK_RESET_CLK_ENB_H_CLR]
+
+#endif
        /* start by jumping to clkm to safely disable PLLs, then jump
         * to clks */
-lp1_clocks_prepare:
        mov     r0, #(1 << 28)
        str     r0, [r5, #CLK_RESET_SCLK_BURST]
        str     r0, [r5, #CLK_RESET_CCLK_BURST]
index c573999..48b2bb8 100644 (file)
@@ -223,6 +223,10 @@ void tegra2_sleep_wfi(unsigned long v2p);
 #else
 extern unsigned int tegra3_iram_start;
 extern unsigned int tegra3_iram_end;
+extern unsigned int lp1_register_pmuslave_addr;
+extern unsigned int lp1_register_i2c_base_addr;
+extern unsigned int lp1_register_core_lowvolt;
+extern unsigned int lp1_register_core_highvolt;
 void tegra3_sleep_core(unsigned long v2p);
 void tegra3_sleep_cpu_secondary(unsigned long v2p);
 void tegra3_hotplug_shutdown(void);
@@ -245,5 +249,41 @@ static inline void *tegra_iram_end(void)
        return &tegra3_iram_end;
 #endif
 }
+
+static inline void *tegra_lp1_register_pmuslave_addr(void)
+{
+#ifdef CONFIG_ARCH_TEGRA_2x_SOC
+       return NULL;
+#else
+       return &lp1_register_pmuslave_addr;
+#endif
+}
+
+static inline void *tegra_lp1_register_i2c_base_addr(void)
+{
+#ifdef CONFIG_ARCH_TEGRA_2x_SOC
+       return NULL;
+#else
+       return &lp1_register_i2c_base_addr;
+#endif
+}
+
+static inline void *tegra_lp1_register_core_lowvolt(void)
+{
+#ifdef CONFIG_ARCH_TEGRA_2x_SOC
+       return NULL;
+#else
+       return &lp1_register_core_lowvolt;
+#endif
+}
+
+static inline void *tegra_lp1_register_core_highvolt(void)
+{
+#ifdef CONFIG_ARCH_TEGRA_2x_SOC
+       return NULL;
+#else
+       return &lp1_register_core_highvolt;
+#endif
+}
 #endif
 #endif