aboutsummaryrefslogtreecommitdiffstats
path: root/arch/arm/mach-exynos/cpuidle-exynos4.c
diff options
context:
space:
mode:
authorcodeworkx <daniel.hillenbrand@codeworkx.de>2012-06-02 13:09:29 +0200
committercodeworkx <daniel.hillenbrand@codeworkx.de>2012-06-02 13:09:29 +0200
commitc6da2cfeb05178a11c6d062a06f8078150ee492f (patch)
treef3b4021d252c52d6463a9b3c1bb7245e399b009c /arch/arm/mach-exynos/cpuidle-exynos4.c
parentc6d7c4dbff353eac7919342ae6b3299a378160a6 (diff)
downloadkernel_samsung_smdk4412-c6da2cfeb05178a11c6d062a06f8078150ee492f.zip
kernel_samsung_smdk4412-c6da2cfeb05178a11c6d062a06f8078150ee492f.tar.gz
kernel_samsung_smdk4412-c6da2cfeb05178a11c6d062a06f8078150ee492f.tar.bz2
samsung update 1
Diffstat (limited to 'arch/arm/mach-exynos/cpuidle-exynos4.c')
-rw-r--r--arch/arm/mach-exynos/cpuidle-exynos4.c1024
1 files changed, 1024 insertions, 0 deletions
diff --git a/arch/arm/mach-exynos/cpuidle-exynos4.c b/arch/arm/mach-exynos/cpuidle-exynos4.c
new file mode 100644
index 0000000..6afdacd
--- /dev/null
+++ b/arch/arm/mach-exynos/cpuidle-exynos4.c
@@ -0,0 +1,1024 @@
+/* linux/arch/arm/mach-exynos/cpuidle-exynos4.c
+ *
+ * Copyright (c) 2011 Samsung Electronics Co., Ltd.
+ * http://www.samsung.com
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+*/
+
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/cpuidle.h>
+#include <linux/io.h>
+#include <linux/suspend.h>
+#include <linux/platform_device.h>
+#include <linux/gpio.h>
+
+#include <asm/proc-fns.h>
+#include <asm/tlbflush.h>
+#include <asm/cacheflush.h>
+
+#include <mach/regs-clock.h>
+#include <mach/regs-pmu.h>
+#include <mach/pmu.h>
+#include <mach/gpio.h>
+#include <mach/smc.h>
+#include <mach/clock-domain.h>
+#include <mach/regs-audss.h>
+#include <mach/asv.h>
+#include <mach/regs-usb-phy.h>
+
+#include <plat/regs-otg.h>
+#include <plat/exynos4.h>
+#include <plat/pm.h>
+#include <plat/devs.h>
+#include <plat/cpu.h>
+#ifdef CONFIG_SEC_WATCHDOG_RESET
+#include <plat/regs-watchdog.h>
+#endif
+#include <mach/regs-usb-phy.h>
+#include <plat/usb-phy.h>
+
+
+#ifdef CONFIG_ARM_TRUSTZONE
+#define REG_DIRECTGO_ADDR (S5P_VA_SYSRAM_NS + 0x24)
+#define REG_DIRECTGO_FLAG (S5P_VA_SYSRAM_NS + 0x20)
+#else
+#define REG_DIRECTGO_ADDR (samsung_rev() < EXYNOS4210_REV_1_1 ?\
+ (S5P_VA_SYSRAM + 0x24) : S5P_INFORM7)
+#define REG_DIRECTGO_FLAG (samsung_rev() < EXYNOS4210_REV_1_1 ?\
+ (S5P_VA_SYSRAM + 0x20) : S5P_INFORM6)
+#endif
+
+#include <asm/hardware/gic.h>
+#include <plat/map-base.h>
+#include <plat/map-s5p.h>
+
+extern unsigned long sys_pwr_conf_addr;
+extern unsigned int l2x0_save[3];
+extern unsigned int scu_save[2];
+
+enum hc_type {
+ HC_SDHC,
+ HC_MSHC,
+};
+
+enum idle_clock_down {
+ HW_CLK_DWN,
+ SW_CLK_DWN,
+};
+
+unsigned int use_clock_down;
+
+struct check_device_op {
+ void __iomem *base;
+ struct platform_device *pdev;
+ enum hc_type type;
+};
+
+#ifdef CONFIG_MACH_MIDAS
+unsigned int log_en = 1;
+#else
+unsigned int log_en;
+#endif
+module_param_named(log_en, log_en, uint, 0644);
+
+#if defined(CONFIG_MACH_MIDAS) || defined(CONFIG_SLP)
+#define CPUDILE_ENABLE_MASK (ENABLE_LPA)
+#else
+#define CPUDILE_ENABLE_MASK (ENABLE_AFTR | ENABLE_LPA)
+#endif
+
+static enum {
+ ENABLE_IDLE = 0x0,
+ ENABLE_AFTR = 0x1,
+ ENABLE_LPA = 0x2
+} enable_mask = CPUDILE_ENABLE_MASK;
+module_param_named(enable_mask, enable_mask, uint, 0644);
+
+#define ENABLE_LOWPWRMASK (ENABLE_AFTR | ENABLE_LPA)
+
+static struct check_device_op chk_sdhc_op[] = {
+#if defined(CONFIG_EXYNOS4_DEV_DWMCI)
+ {.base = 0, .pdev = &exynos_device_dwmci, .type = HC_MSHC},
+#endif
+#if defined(CONFIG_EXYNOS4_DEV_MSHC)
+ {.base = 0, .pdev = &s3c_device_mshci, .type = HC_MSHC},
+#endif
+#if defined(CONFIG_S3C_DEV_HSMMC)
+ {.base = 0, .pdev = &s3c_device_hsmmc0, .type = HC_SDHC},
+#endif
+#if defined(CONFIG_S3C_DEV_HSMMC1)
+ {.base = 0, .pdev = &s3c_device_hsmmc1, .type = HC_SDHC},
+#endif
+#if defined(CONFIG_S3C_DEV_HSMMC2)
+ {.base = 0, .pdev = &s3c_device_hsmmc2, .type = HC_SDHC},
+#endif
+#if defined(CONFIG_S3C_DEV_HSMMC3)
+ {.base = 0, .pdev = &s3c_device_hsmmc3, .type = HC_SDHC},
+#endif
+};
+
+#if defined(CONFIG_USB_S3C_OTGD) && !defined(CONFIG_USB_EXYNOS_SWITCH)
+static struct check_device_op chk_usbotg_op = {
+ .base = 0, .pdev = &s3c_device_usbgadget, .type = 0
+};
+#endif
+
+#define S3C_HSMMC_PRNSTS (0x24)
+#define S3C_HSMMC_CLKCON (0x2c)
+#define S3C_HSMMC_CMD_INHIBIT 0x00000001
+#define S3C_HSMMC_DATA_INHIBIT 0x00000002
+#define S3C_HSMMC_CLOCK_CARD_EN 0x0004
+
+#define MSHCI_CLKENA (0x10) /* Clock enable */
+#define MSHCI_STATUS (0x48) /* Status */
+#define MSHCI_DATA_BUSY (0x1<<9)
+#define MSHCI_DATA_STAT_BUSY (0x1<<10)
+#define MSHCI_ENCLK (0x1)
+
+#define GPIO_OFFSET 0x20
+#define GPIO_PUD_OFFSET 0x08
+#define GPIO_CON_PDN_OFFSET 0x10
+#define GPIO_PUD_PDN_OFFSET 0x14
+#define GPIO_END_OFFSET 0x200
+
+/* GPIO_END_OFFSET value of exynos4212 */
+#define GPIO1_END_OFFSET 0x280
+#define GPIO2_END_OFFSET 0x200
+#define GPIO4_END_OFFSET 0xE0
+
+static void exynos4_gpio_conpdn_reg(void)
+{
+ void __iomem *gpio_base = S5P_VA_GPIO;
+ unsigned int val;
+
+ do {
+ /* Keep the previous state in didle mode */
+ __raw_writel(0xffff, gpio_base + GPIO_CON_PDN_OFFSET);
+
+ /* Pull up-down state in didle is same as normal */
+ val = __raw_readl(gpio_base + GPIO_PUD_OFFSET);
+ __raw_writel(val, gpio_base + GPIO_PUD_PDN_OFFSET);
+
+ gpio_base += GPIO_OFFSET;
+
+ if (gpio_base == S5P_VA_GPIO + GPIO_END_OFFSET)
+ gpio_base = S5P_VA_GPIO2;
+
+ } while (gpio_base <= S5P_VA_GPIO2 + GPIO_END_OFFSET);
+
+ /* set the GPZ */
+ gpio_base = S5P_VA_GPIO3;
+ __raw_writel(0xffff, gpio_base + GPIO_CON_PDN_OFFSET);
+
+ val = __raw_readl(gpio_base + GPIO_PUD_OFFSET);
+ __raw_writel(val, gpio_base + GPIO_PUD_PDN_OFFSET);
+}
+
+static void exynos4212_gpio_conpdn_reg(void)
+{
+ void __iomem *gpio_base = S5P_VA_GPIO;
+ unsigned int val;
+
+ do {
+ /* Keep the previous state in didle mode */
+ __raw_writel(0xffff, gpio_base + GPIO_CON_PDN_OFFSET);
+
+ /* Pull up-down state in didle is same as normal */
+ val = __raw_readl(gpio_base + GPIO_PUD_OFFSET);
+ __raw_writel(val, gpio_base + GPIO_PUD_PDN_OFFSET);
+
+ gpio_base += GPIO_OFFSET;
+
+ /* Skip gpio_base there aren't gpios in part1 & part4 of exynos4212 */
+ if (gpio_base == (S5P_VA_GPIO + 0xE0))
+ gpio_base = S5P_VA_GPIO + 0x180;
+ else if (gpio_base == (S5P_VA_GPIO + 0x200))
+ gpio_base = S5P_VA_GPIO + 0x240;
+ else if (gpio_base == (S5P_VA_GPIO4 + 0x40))
+ gpio_base = S5P_VA_GPIO4 + 0x60;
+ else if (gpio_base == (S5P_VA_GPIO4 + 0xA0))
+ gpio_base = S5P_VA_GPIO4 + 0xC0;
+
+ if (gpio_base == S5P_VA_GPIO + GPIO1_END_OFFSET)
+ gpio_base = S5P_VA_GPIO2 + 0x40; /* GPK0CON */
+
+ if (gpio_base == S5P_VA_GPIO2 + GPIO2_END_OFFSET)
+ gpio_base = S5P_VA_GPIO4;
+
+ } while (gpio_base <= S5P_VA_GPIO4 + GPIO4_END_OFFSET);
+
+ /* set the GPZ */
+ gpio_base = S5P_VA_GPIO3;
+ __raw_writel(0xffff, gpio_base + GPIO_CON_PDN_OFFSET);
+
+ val = __raw_readl(gpio_base + GPIO_PUD_OFFSET);
+ __raw_writel(val, gpio_base + GPIO_PUD_PDN_OFFSET);
+}
+
+static int check_power_domain(void)
+{
+ unsigned long tmp;
+
+ tmp = __raw_readl(S5P_PMU_LCD0_CONF);
+ if ((tmp & S5P_INT_LOCAL_PWR_EN) == S5P_INT_LOCAL_PWR_EN)
+ return 1;
+
+ tmp = __raw_readl(S5P_PMU_MFC_CONF);
+ if ((tmp & S5P_INT_LOCAL_PWR_EN) == S5P_INT_LOCAL_PWR_EN)
+ return 1;
+
+ tmp = __raw_readl(S5P_PMU_G3D_CONF);
+ if ((tmp & S5P_INT_LOCAL_PWR_EN) == S5P_INT_LOCAL_PWR_EN)
+ return 1;
+
+ tmp = __raw_readl(S5P_PMU_CAM_CONF);
+ if ((tmp & S5P_INT_LOCAL_PWR_EN) == S5P_INT_LOCAL_PWR_EN)
+ return 1;
+
+ tmp = __raw_readl(S5P_PMU_TV_CONF);
+ if ((tmp & S5P_INT_LOCAL_PWR_EN) == S5P_INT_LOCAL_PWR_EN)
+ return 1;
+
+ tmp = __raw_readl(S5P_PMU_GPS_CONF);
+ if ((tmp & S5P_INT_LOCAL_PWR_EN) == S5P_INT_LOCAL_PWR_EN)
+ return 1;
+
+ return 0;
+}
+
+static int __maybe_unused check_clock_gating(void)
+{
+ unsigned long tmp;
+
+ tmp = __raw_readl(EXYNOS4_CLKGATE_IP_IMAGE);
+ if (tmp & (EXYNOS4_CLKGATE_IP_IMAGE_MDMA | EXYNOS4_CLKGATE_IP_IMAGE_SMMUMDMA
+ | EXYNOS4_CLKGATE_IP_IMAGE_QEMDMA))
+ return 1;
+
+ tmp = __raw_readl(EXYNOS4_CLKGATE_IP_FSYS);
+ if (tmp & (EXYNOS4_CLKGATE_IP_FSYS_PDMA0 | EXYNOS4_CLKGATE_IP_FSYS_PDMA1))
+ return 1;
+
+ tmp = __raw_readl(EXYNOS4_CLKGATE_IP_PERIL);
+ if (tmp & EXYNOS4_CLKGATE_IP_PERIL_I2C0_7)
+ return 1;
+
+ return 0;
+}
+
+static int sdmmc_dev_num;
+/* If SD/MMC interface is working: return = 1 or not 0 */
+static int check_sdmmc_op(unsigned int ch)
+{
+ unsigned int reg1, reg2;
+ void __iomem *base_addr;
+
+ if (unlikely(ch >= sdmmc_dev_num)) {
+ printk(KERN_ERR "Invalid ch[%d] for SD/MMC\n", ch);
+ return 0;
+ }
+
+ if (chk_sdhc_op[ch].type == HC_SDHC) {
+ base_addr = chk_sdhc_op[ch].base;
+ /* Check CLKCON [2]: ENSDCLK */
+ reg2 = readl(base_addr + S3C_HSMMC_CLKCON);
+ return !!(reg2 & (S3C_HSMMC_CLOCK_CARD_EN));
+ } else if (chk_sdhc_op[ch].type == HC_MSHC) {
+ base_addr = chk_sdhc_op[ch].base;
+ /* Check STATUS [9] for data busy */
+ reg1 = readl(base_addr + MSHCI_STATUS);
+ return (reg1 & (MSHCI_DATA_BUSY)) ||
+ (reg1 & (MSHCI_DATA_STAT_BUSY));
+
+ }
+ /* should not be here */
+ return 0;
+}
+
+/* Check all sdmmc controller */
+static int loop_sdmmc_check(void)
+{
+ unsigned int iter;
+
+ for (iter = 0; iter < sdmmc_dev_num; iter++) {
+ if (check_sdmmc_op(iter))
+ return 1;
+ }
+ return 0;
+}
+
+/*
+ * Check USB Device and Host is working or not
+ * USB_S3C-OTGD can check GOTGCTL register
+ * GOTGCTL(0xEC000000)
+ * BSesVld (Indicates the Device mode transceiver status)
+ * BSesVld = 1b : B-session is valid
+ * 0b : B-session is not valiid
+ * USB_EXYNOS_SWITCH can check Both Host and Device status.
+ */
+static int check_usb_op(void)
+{
+#if defined(CONFIG_USB_S3C_OTGD) && !defined(CONFIG_USB_EXYNOS_SWITCH)
+ void __iomem *base_addr;
+ unsigned int val;
+
+ base_addr = chk_usbotg_op.base;
+ val = __raw_readl(base_addr + S3C_UDC_OTG_GOTGCTL);
+
+ return val & (A_SESSION_VALID | B_SESSION_VALID);
+#elif defined(CONFIG_USB_EXYNOS_SWITCH)
+ return exynos_check_usb_op();
+#else
+ return 0;
+#endif
+}
+
+#ifdef CONFIG_SND_SAMSUNG_RP
+extern int srp_get_op_level(void); /* By srp driver */
+#endif
+
+#if defined(CONFIG_BT)
+static inline int check_bt_op(void)
+{
+ extern int bt_is_running;
+
+ return bt_is_running;
+}
+#endif
+
+static int gps_is_running;
+
+void set_gps_uart_op(int onoff)
+{
+ pr_info("%s: %s\n", __func__, onoff ? "on" : "off");
+ gps_is_running = onoff;
+}
+
+static inline int check_gps_uart_op(void)
+{
+ return gps_is_running;
+}
+
+static int exynos4_check_operation(void)
+{
+ if (check_power_domain())
+ return 1;
+
+ if (clock_domain_enabled(LPA_DOMAIN))
+ return 1;
+
+ if (loop_sdmmc_check())
+ return 1;
+#ifdef CONFIG_SND_SAMSUNG_RP
+ if (srp_get_op_level())
+ return 1;
+#endif
+ if (check_usb_op())
+ return 1;
+
+#if defined(CONFIG_BT)
+ if (check_bt_op())
+ return 1;
+#endif
+
+ if (check_gps_uart_op())
+ return 1;
+
+ if (exynos4_check_usb_op())
+ return 1;
+
+ return 0;
+}
+
+#ifdef CONFIG_SEC_WATCHDOG_RESET
+static struct sleep_save exynos4_aftr_save[] = {
+ SAVE_ITEM(S3C2410_WTDAT),
+ SAVE_ITEM(S3C2410_WTCNT),
+ SAVE_ITEM(S3C2410_WTCON),
+};
+#endif
+
+static struct sleep_save exynos4_lpa_save[] = {
+ /* CMU side */
+ SAVE_ITEM(EXYNOS4_CLKSRC_MASK_TOP),
+ SAVE_ITEM(EXYNOS4_CLKSRC_MASK_CAM),
+ SAVE_ITEM(EXYNOS4_CLKSRC_MASK_TV),
+ SAVE_ITEM(EXYNOS4_CLKSRC_MASK_LCD0),
+ SAVE_ITEM(EXYNOS4_CLKSRC_MASK_LCD1),
+ SAVE_ITEM(EXYNOS4_CLKSRC_MASK_MAUDIO),
+ SAVE_ITEM(EXYNOS4_CLKSRC_MASK_FSYS),
+ SAVE_ITEM(EXYNOS4_CLKSRC_MASK_PERIL0),
+ SAVE_ITEM(EXYNOS4_CLKSRC_MASK_PERIL1),
+ SAVE_ITEM(EXYNOS4_CLKSRC_MASK_DMC),
+#ifdef CONFIG_SEC_WATCHDOG_RESET
+ SAVE_ITEM(S3C2410_WTDAT),
+ SAVE_ITEM(S3C2410_WTCNT),
+ SAVE_ITEM(S3C2410_WTCON),
+#endif
+};
+
+static struct sleep_save exynos4_set_clksrc[] = {
+ { .reg = EXYNOS4_CLKSRC_MASK_TOP , .val = 0x00000001, },
+ { .reg = EXYNOS4_CLKSRC_MASK_CAM , .val = 0x11111111, },
+ { .reg = EXYNOS4_CLKSRC_MASK_TV , .val = 0x00000111, },
+ { .reg = EXYNOS4_CLKSRC_MASK_LCD0 , .val = 0x00001111, },
+ { .reg = EXYNOS4_CLKSRC_MASK_MAUDIO , .val = 0x00000001, },
+ { .reg = EXYNOS4_CLKSRC_MASK_FSYS , .val = 0x01011111, },
+ { .reg = EXYNOS4_CLKSRC_MASK_PERIL0 , .val = 0x01111111, },
+ { .reg = EXYNOS4_CLKSRC_MASK_PERIL1 , .val = 0x01110111, },
+ { .reg = EXYNOS4_CLKSRC_MASK_DMC , .val = 0x00010000, },
+};
+
+static struct sleep_save exynos4210_set_clksrc[] = {
+ { .reg = EXYNOS4_CLKSRC_MASK_LCD1 , .val = 0x00001111, },
+};
+
+static int exynos4_check_enter(void)
+{
+ unsigned int ret;
+ unsigned int check_val;
+
+ ret = 0;
+
+ /* Check UART for console is empty */
+ check_val = __raw_readl(S5P_VA_UART(CONFIG_S3C_LOWLEVEL_UART_PORT) +
+ 0x18);
+
+ ret = ((check_val >> 16) & 0xff);
+
+ return ret;
+}
+
+void exynos4_flush_cache(void *addr, phys_addr_t phy_ttb_base)
+{
+ outer_clean_range(virt_to_phys(addr - 0x40),
+ virt_to_phys(addr + 0x40));
+ outer_clean_range(virt_to_phys(cpu_resume),
+ virt_to_phys(cpu_resume + 0x40));
+ outer_clean_range(phy_ttb_base, phy_ttb_base + 0xffff);
+ flush_cache_all();
+}
+
+static void exynos4_set_wakeupmask(void)
+{
+ __raw_writel(0x0000ff3e, S5P_WAKEUP_MASK);
+}
+
+static void vfp_enable(void *unused)
+{
+ u32 access = get_copro_access();
+
+ /*
+ * Enable full access to VFP (cp10 and cp11)
+ */
+ set_copro_access(access | CPACC_FULL(10) | CPACC_FULL(11));
+}
+
+static int exynos4_enter_core0_aftr(struct cpuidle_device *dev,
+ struct cpuidle_state *state)
+{
+ struct timeval before, after;
+ int idle_time;
+ unsigned long tmp, abb_val;
+
+#ifdef CONFIG_SEC_WATCHDOG_RESET
+ s3c_pm_do_save(exynos4_aftr_save, ARRAY_SIZE(exynos4_aftr_save));
+#endif
+
+ local_irq_disable();
+
+ if (log_en)
+ pr_info("+++aftr\n");
+
+ do_gettimeofday(&before);
+
+ exynos4_set_wakeupmask();
+
+ __raw_writel(virt_to_phys(exynos4_idle_resume), REG_DIRECTGO_ADDR);
+ __raw_writel(0xfcba0d10, REG_DIRECTGO_FLAG);
+
+ /* Set value of power down register for aftr mode */
+ exynos4_sys_powerdown_conf(SYS_AFTR);
+
+ if (!soc_is_exynos4210())
+ exynos4_reset_assert_ctrl(0);
+
+#ifdef CONFIG_EXYNOS4_CPUFREQ
+ if (!soc_is_exynos4210()) {
+ abb_val = exynos4x12_get_abb_member(ABB_ARM);
+ exynos4x12_set_abb_member(ABB_ARM, ABB_MODE_075V);
+ }
+#endif
+
+ if (exynos4_enter_lp(0, PLAT_PHYS_OFFSET - PAGE_OFFSET) == 0) {
+
+ /*
+ * Clear Central Sequence Register in exiting early wakeup
+ */
+ tmp = __raw_readl(S5P_CENTRAL_SEQ_CONFIGURATION);
+ tmp |= (S5P_CENTRAL_LOWPWR_CFG);
+ __raw_writel(tmp, S5P_CENTRAL_SEQ_CONFIGURATION);
+
+ goto early_wakeup;
+ }
+ flush_tlb_all();
+
+ cpu_init();
+
+ vfp_enable(NULL);
+
+#ifdef CONFIG_SEC_WATCHDOG_RESET
+ s3c_pm_do_restore_core(exynos4_aftr_save,
+ ARRAY_SIZE(exynos4_aftr_save));
+#endif
+
+early_wakeup:
+#ifdef CONFIG_EXYNOS4_CPUFREQ
+ if ((exynos_result_of_asv > 1) && !soc_is_exynos4210())
+ exynos4x12_set_abb_member(ABB_ARM, abb_val);
+#endif
+
+ if (!soc_is_exynos4210())
+ exynos4_reset_assert_ctrl(1);
+
+ /* Clear wakeup state register */
+ __raw_writel(0x0, S5P_WAKEUP_STAT);
+
+ do_gettimeofday(&after);
+
+ if (log_en)
+ pr_info("---aftr\n");
+
+ local_irq_enable();
+ idle_time = (after.tv_sec - before.tv_sec) * USEC_PER_SEC +
+ (after.tv_usec - before.tv_usec);
+
+ return idle_time;
+}
+
+extern void bt_uart_rts_ctrl(int flag);
+
+static int exynos4_enter_core0_lpa(struct cpuidle_device *dev,
+ struct cpuidle_state *state)
+{
+ struct timeval before, after;
+ int idle_time;
+ unsigned long tmp, abb_val, abb_val_int;
+
+ s3c_pm_do_save(exynos4_lpa_save, ARRAY_SIZE(exynos4_lpa_save));
+
+ /*
+ * Before enter central sequence mode, clock src register have to set
+ */
+ s3c_pm_do_restore_core(exynos4_set_clksrc,
+ ARRAY_SIZE(exynos4_set_clksrc));
+
+ if (soc_is_exynos4210())
+ s3c_pm_do_restore_core(exynos4210_set_clksrc, ARRAY_SIZE(exynos4210_set_clksrc));
+
+#if defined(CONFIG_BT)
+ /* BT-UART RTS Control (RTS High) */
+ bt_uart_rts_ctrl(1);
+#endif
+ local_irq_disable();
+
+ if (log_en)
+ pr_info("+++lpa\n");
+
+ do_gettimeofday(&before);
+
+ /*
+ * Unmasking all wakeup source.
+ */
+ __raw_writel(0x3ff0000, S5P_WAKEUP_MASK);
+
+ /* Configure GPIO Power down control register */
+#ifdef CONFIG_MIDAS_COMMON
+ if (exynos4_sleep_gpio_table_set)
+ exynos4_sleep_gpio_table_set();
+ else
+#endif
+ exynos4_gpio_conpdn_reg();
+
+ /* ensure at least INFORM0 has the resume address */
+ __raw_writel(virt_to_phys(exynos4_idle_resume), S5P_INFORM0);
+
+ __raw_writel(virt_to_phys(exynos4_idle_resume), REG_DIRECTGO_ADDR);
+ __raw_writel(0xfcba0d10, REG_DIRECTGO_FLAG);
+
+ __raw_writel(S5P_CHECK_LPA, S5P_INFORM1);
+ exynos4_sys_powerdown_conf(SYS_LPA);
+
+ /* Should be fixed on EVT1 */
+ if (!soc_is_exynos4210())
+ exynos4_reset_assert_ctrl(0);
+
+ do {
+ /* Waiting for flushing UART fifo */
+ } while (exynos4_check_enter());
+
+#ifdef CONFIG_EXYNOS4_CPUFREQ
+ if (!soc_is_exynos4210()) {
+ abb_val = exynos4x12_get_abb_member(ABB_ARM);
+ abb_val_int = exynos4x12_get_abb_member(ABB_INT);
+ exynos4x12_set_abb_member(ABB_ARM, ABB_MODE_075V);
+ exynos4x12_set_abb_member(ABB_INT, ABB_MODE_075V);
+ }
+#endif
+
+ if (exynos4_enter_lp(0, PLAT_PHYS_OFFSET - PAGE_OFFSET) == 0) {
+
+ /*
+ * Clear Central Sequence Register in exiting early wakeup
+ */
+ tmp = __raw_readl(S5P_CENTRAL_SEQ_CONFIGURATION);
+ tmp |= (S5P_CENTRAL_LOWPWR_CFG);
+ __raw_writel(tmp, S5P_CENTRAL_SEQ_CONFIGURATION);
+
+ goto early_wakeup;
+ }
+ flush_tlb_all();
+
+ cpu_init();
+
+ vfp_enable(NULL);
+
+ /* For release retention */
+ __raw_writel((1 << 28), S5P_PAD_RET_GPIO_OPTION);
+ __raw_writel((1 << 28), S5P_PAD_RET_UART_OPTION);
+ __raw_writel((1 << 28), S5P_PAD_RET_MMCA_OPTION);
+ __raw_writel((1 << 28), S5P_PAD_RET_MMCB_OPTION);
+ __raw_writel((1 << 28), S5P_PAD_RET_EBIA_OPTION);
+ __raw_writel((1 << 28), S5P_PAD_RET_EBIB_OPTION);
+
+early_wakeup:
+ s3c_pm_do_restore_core(exynos4_lpa_save,
+ ARRAY_SIZE(exynos4_lpa_save));
+
+#ifdef CONFIG_EXYNOS4_CPUFREQ
+ if ((exynos_result_of_asv > 1) && !soc_is_exynos4210()) {
+ exynos4x12_set_abb_member(ABB_ARM, abb_val);
+ exynos4x12_set_abb_member(ABB_INT, abb_val_int);
+ }
+#endif
+
+ if (!soc_is_exynos4210())
+ exynos4_reset_assert_ctrl(1);
+
+ /* Clear wakeup state register */
+ __raw_writel(0x0, S5P_WAKEUP_STAT);
+
+ __raw_writel(0x0, S5P_WAKEUP_MASK);
+
+ do_gettimeofday(&after);
+
+ if (log_en)
+ pr_info("---lpa\n");
+
+ local_irq_enable();
+ idle_time = (after.tv_sec - before.tv_sec) * USEC_PER_SEC +
+ (after.tv_usec - before.tv_usec);
+
+#if defined(CONFIG_BT)
+ /* BT-UART RTS Control (RTS Low) */
+ bt_uart_rts_ctrl(0);
+#endif
+
+ return idle_time;
+}
+
+static int exynos4_enter_idle(struct cpuidle_device *dev,
+ struct cpuidle_state *state);
+
+static int exynos4_enter_lowpower(struct cpuidle_device *dev,
+ struct cpuidle_state *state);
+
+static struct cpuidle_state exynos4_cpuidle_set[] = {
+ [0] = {
+ .enter = exynos4_enter_idle,
+ .exit_latency = 1,
+ .target_residency = 10000,
+ .flags = CPUIDLE_FLAG_TIME_VALID,
+ .name = "IDLE",
+ .desc = "ARM clock gating(WFI)",
+ },
+#ifdef CONFIG_EXYNOS4_LOWPWR_IDLE
+ [1] = {
+ .enter = exynos4_enter_lowpower,
+ .exit_latency = 300,
+ .target_residency = 10000,
+ .flags = CPUIDLE_FLAG_TIME_VALID,
+ .name = "LOW_POWER",
+ .desc = "ARM power down",
+ },
+#endif
+};
+
+static DEFINE_PER_CPU(struct cpuidle_device, exynos4_cpuidle_device);
+
+static struct cpuidle_driver exynos4_idle_driver = {
+ .name = "exynos4_idle",
+ .owner = THIS_MODULE,
+};
+
+static unsigned int cpu_core;
+static unsigned int old_div;
+static DEFINE_SPINLOCK(idle_lock);
+
+static int exynos4_enter_idle(struct cpuidle_device *dev,
+ struct cpuidle_state *state)
+{
+ struct timeval before, after;
+ int idle_time;
+ int cpu;
+ unsigned int tmp;
+
+ local_irq_disable();
+ do_gettimeofday(&before);
+
+ if (use_clock_down == SW_CLK_DWN) {
+ /* USE SW Clock Down */
+ cpu = get_cpu();
+
+ spin_lock(&idle_lock);
+ cpu_core |= (1 << cpu);
+
+ if ((cpu_core == 0x3) || (cpu_online(1) == 0)) {
+ old_div = __raw_readl(EXYNOS4_CLKDIV_CPU);
+ tmp = old_div;
+ tmp |= ((0x7 << 28) | (0x7 << 0));
+ __raw_writel(tmp, EXYNOS4_CLKDIV_CPU);
+
+ do {
+ tmp = __raw_readl(EXYNOS4_CLKDIV_STATCPU);
+ } while (tmp & 0x10000001);
+
+ }
+
+ spin_unlock(&idle_lock);
+
+ cpu_do_idle();
+
+ spin_lock(&idle_lock);
+
+ if ((cpu_core == 0x3) || (cpu_online(1) == 0)) {
+ __raw_writel(old_div, EXYNOS4_CLKDIV_CPU);
+
+ do {
+ tmp = __raw_readl(EXYNOS4_CLKDIV_STATCPU);
+ } while (tmp & 0x10000001);
+
+ }
+
+ cpu_core &= ~(1 << cpu);
+ spin_unlock(&idle_lock);
+
+ put_cpu();
+ } else
+ cpu_do_idle();
+
+ do_gettimeofday(&after);
+ local_irq_enable();
+ idle_time = (after.tv_sec - before.tv_sec) * USEC_PER_SEC +
+ (after.tv_usec - before.tv_usec);
+
+ return idle_time;
+}
+
+static int exynos4_check_entermode(void)
+{
+ unsigned int ret;
+ unsigned int mask = (enable_mask & ENABLE_LOWPWRMASK);
+
+ if (!mask)
+ return 0;
+
+ if ((mask & ENABLE_LPA) && !exynos4_check_operation())
+ ret = S5P_CHECK_LPA;
+ else if (mask & ENABLE_AFTR)
+ ret = S5P_CHECK_DIDLE;
+ else
+ ret = 0;
+
+ return ret;
+}
+
+static int exynos4_enter_lowpower(struct cpuidle_device *dev,
+ struct cpuidle_state *state)
+{
+ struct cpuidle_state *new_state = state;
+ unsigned int enter_mode;
+ unsigned int tmp;
+
+ /* This mode only can be entered when only Core0 is online */
+ if (num_online_cpus() != 1) {
+ BUG_ON(!dev->safe_state);
+ new_state = dev->safe_state;
+ }
+ dev->last_state = new_state;
+
+ if (!soc_is_exynos4210()) {
+ tmp = S5P_USE_STANDBY_WFI0 | S5P_USE_STANDBY_WFE0;
+ __raw_writel(tmp, S5P_CENTRAL_SEQ_OPTION);
+ }
+
+ if (new_state == &dev->states[0])
+ return exynos4_enter_idle(dev, new_state);
+
+ enter_mode = exynos4_check_entermode();
+ if (!enter_mode)
+ return exynos4_enter_idle(dev, new_state);
+ else if (enter_mode == S5P_CHECK_DIDLE)
+ return exynos4_enter_core0_aftr(dev, new_state);
+ else
+ return exynos4_enter_core0_lpa(dev, new_state);
+}
+
+static int exynos4_cpuidle_notifier_event(struct notifier_block *this,
+ unsigned long event,
+ void *ptr)
+{
+ switch (event) {
+ case PM_SUSPEND_PREPARE:
+ disable_hlt();
+ pr_debug("PM_SUSPEND_PREPARE for CPUIDLE\n");
+ return NOTIFY_OK;
+ case PM_POST_RESTORE:
+ case PM_POST_SUSPEND:
+ enable_hlt();
+ pr_debug("PM_POST_SUSPEND for CPUIDLE\n");
+ return NOTIFY_OK;
+ }
+ return NOTIFY_DONE;
+}
+
+static struct notifier_block exynos4_cpuidle_notifier = {
+ .notifier_call = exynos4_cpuidle_notifier_event,
+};
+
+#ifdef CONFIG_EXYNOS4_ENABLE_CLOCK_DOWN
+static void __init exynos4_core_down_clk(void)
+{
+ unsigned int tmp;
+
+ tmp = __raw_readl(EXYNOS4_PWR_CTRL1);
+
+ tmp &= ~(PWR_CTRL1_CORE2_DOWN_MASK | PWR_CTRL1_CORE1_DOWN_MASK);
+
+ /* set arm clock divider value on idle state */
+ tmp |= ((0x7 << PWR_CTRL1_CORE2_DOWN_RATIO) |
+ (0x7 << PWR_CTRL1_CORE1_DOWN_RATIO));
+
+ if (soc_is_exynos4212()) {
+ /* Set PWR_CTRL1 register to use clock down feature */
+ tmp |= (PWR_CTRL1_DIV2_DOWN_EN |
+ PWR_CTRL1_DIV1_DOWN_EN |
+ PWR_CTRL1_USE_CORE1_WFE |
+ PWR_CTRL1_USE_CORE0_WFE |
+ PWR_CTRL1_USE_CORE1_WFI |
+ PWR_CTRL1_USE_CORE0_WFI);
+ } else if (soc_is_exynos4412()) {
+ tmp |= (PWR_CTRL1_DIV2_DOWN_EN |
+ PWR_CTRL1_DIV1_DOWN_EN |
+ PWR_CTRL1_USE_CORE3_WFE |
+ PWR_CTRL1_USE_CORE2_WFE |
+ PWR_CTRL1_USE_CORE1_WFE |
+ PWR_CTRL1_USE_CORE0_WFE |
+ PWR_CTRL1_USE_CORE3_WFI |
+ PWR_CTRL1_USE_CORE2_WFI |
+ PWR_CTRL1_USE_CORE1_WFI |
+ PWR_CTRL1_USE_CORE0_WFI);
+ }
+
+ __raw_writel(tmp, EXYNOS4_PWR_CTRL1);
+
+ tmp = __raw_readl(EXYNOS4_PWR_CTRL2);
+
+ tmp &= ~(PWR_CTRL2_DUR_STANDBY2_MASK | PWR_CTRL2_DUR_STANDBY1_MASK |
+ PWR_CTRL2_CORE2_UP_MASK | PWR_CTRL2_CORE1_UP_MASK);
+
+ /* set duration value on middle wakeup step */
+ tmp |= ((0x1 << PWR_CTRL2_DUR_STANDBY2) |
+ (0x1 << PWR_CTRL2_DUR_STANDBY1));
+
+ /* set arm clock divier value on middle wakeup step */
+ tmp |= ((0x1 << PWR_CTRL2_CORE2_UP_RATIO) |
+ (0x1 << PWR_CTRL2_CORE1_UP_RATIO));
+
+ /* Set PWR_CTRL2 register to use step up for arm clock */
+ tmp |= (PWR_CTRL2_DIV2_UP_EN | PWR_CTRL2_DIV1_UP_EN);
+
+ __raw_writel(tmp, EXYNOS4_PWR_CTRL2);
+
+ printk(KERN_INFO "Exynos4 : ARM Clock down on idle mode is enabled\n");
+}
+#else
+#define exynos4_core_down_clk() do { } while (0)
+#endif
+
+static int __init exynos4_init_cpuidle(void)
+{
+ int i, max_cpuidle_state, cpu_id, ret;
+ struct cpuidle_device *device;
+ struct platform_device *pdev;
+ struct resource *res;
+
+ if (soc_is_exynos4210())
+ use_clock_down = SW_CLK_DWN;
+ else
+ use_clock_down = HW_CLK_DWN;
+
+ /* Clock down feature can use only EXYNOS4212 */
+ if (use_clock_down == HW_CLK_DWN)
+ exynos4_core_down_clk();
+
+ ret = cpuidle_register_driver(&exynos4_idle_driver);
+
+ if(ret < 0){
+ printk(KERN_ERR "exynos4 idle register driver failed\n");
+ return ret;
+ }
+
+ for_each_cpu(cpu_id, cpu_online_mask) {
+ device = &per_cpu(exynos4_cpuidle_device, cpu_id);
+ device->cpu = cpu_id;
+
+ if (cpu_id == 0)
+ device->state_count = ARRAY_SIZE(exynos4_cpuidle_set);
+ else
+ device->state_count = 1; /* Support IDLE only */
+
+ max_cpuidle_state = device->state_count;
+
+ for (i = 0; i < max_cpuidle_state; i++) {
+ memcpy(&device->states[i], &exynos4_cpuidle_set[i],
+ sizeof(struct cpuidle_state));
+ }
+
+ device->safe_state = &device->states[0];
+
+ if (cpuidle_register_device(device)) {
+ cpuidle_unregister_driver(&exynos4_idle_driver);
+ printk(KERN_ERR "CPUidle register device failed\n,");
+ return -EIO;
+ }
+ }
+
+ sdmmc_dev_num = ARRAY_SIZE(chk_sdhc_op);
+
+ for (i = 0; i < sdmmc_dev_num; i++) {
+
+ pdev = chk_sdhc_op[i].pdev;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res) {
+ printk(KERN_ERR "failed to get iomem region\n");
+ return -EINVAL;
+ }
+
+ chk_sdhc_op[i].base = ioremap(res->start, resource_size(res));
+
+ if (!chk_sdhc_op[i].base) {
+ printk(KERN_ERR "failed to map io region\n");
+ return -EINVAL;
+ }
+ }
+
+#if defined(CONFIG_USB_S3C_OTGD) && !defined(CONFIG_USB_EXYNOS_SWITCH)
+ pdev = chk_usbotg_op.pdev;
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res) {
+ printk(KERN_ERR "failed to get iomem region\n");
+ return -EINVAL;
+ }
+
+ chk_usbotg_op.base = ioremap(res->start, resource_size(res));
+
+ if (!chk_usbotg_op.base) {
+ printk(KERN_ERR "failed to map io region\n");
+ return -EINVAL;
+ }
+#endif
+ register_pm_notifier(&exynos4_cpuidle_notifier);
+ sys_pwr_conf_addr = (unsigned long)S5P_CENTRAL_SEQ_CONFIGURATION;
+
+ /* Save register value for SCU */
+ scu_save[0] = __raw_readl(S5P_VA_SCU + 0x30);
+ scu_save[1] = __raw_readl(S5P_VA_SCU + 0x0);
+
+ /* Save register value for L2X0 */
+ l2x0_save[0] = __raw_readl(S5P_VA_L2CC + 0x108);
+ l2x0_save[1] = __raw_readl(S5P_VA_L2CC + 0x10C);
+ l2x0_save[2] = __raw_readl(S5P_VA_L2CC + 0xF60);
+
+ flush_cache_all();
+ outer_clean_range(virt_to_phys(l2x0_save), ARRAY_SIZE(l2x0_save));
+ outer_clean_range(virt_to_phys(scu_save), ARRAY_SIZE(scu_save));
+
+ return 0;
+}
+device_initcall(exynos4_init_cpuidle);