Skip to content

Instantly share code, notes, and snippets.

@Oberon00
Last active August 29, 2015 14:11
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Oberon00/6813e32273995b97d9d4 to your computer and use it in GitHub Desktop.
Save Oberon00/6813e32273995b97d9d4 to your computer and use it in GitHub Desktop.
/* 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>
#include <mach/regs-usb-phy.h>
#include <plat/usb-phy.h>
#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)
#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;
};
unsigned int log_en;
module_param_named(log_en, log_en, uint, 0644);
#define CPUDILE_ENABLE_MASK (ENABLE_AFTR | ENABLE_LPA)
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_MSHC)
{.base = 0, .pdev = &s3c_device_mshci, .type = HC_MSHC},
#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);
#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 atomic_t sromc_use_count;
void set_sromc_access(bool access)
{
if (access)
atomic_set(&sromc_use_count, 1);
else
atomic_set(&sromc_use_count, 0);
}
EXPORT_SYMBOL(set_sromc_access);
static inline int check_sromc_access(void)
{
return atomic_read(&sromc_use_count);
}
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;
if (check_sromc_access()) {
pr_info("%s: SROMC is in use!!!\n", __func__);
return 1;
}
return 0;
}
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),
};
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;
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);
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_debug("+++lpa\n");
do_gettimeofday(&before);
/*
* Unmasking all wakeup source.
*/
__raw_writel(0x3ff0000, S5P_WAKEUP_MASK);
/* Configure GPIO Power down control register */
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 (!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_debug("---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;
int ret;
/* 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)
ret = exynos4_enter_core0_aftr(dev, new_state);
else
ret = exynos4_enter_core0_lpa(dev, new_state);
}
return ret;
}
static int exynos4_cpuidle_notifier_event(struct notifier_block *this,
unsigned long event,
void *ptr)
{
switch (event) {
case PM_SUSPEND_PREPARE:
case PM_HIBERNATION_PREPARE:
case PM_RESTORE_PREPARE:
disable_hlt();
pr_debug("PM_SUSPEND_PREPARE for CPUIDLE\n");
return NOTIFY_OK;
case PM_POST_HIBERNATION:
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,
};
#define exynos4_core_down_clk() do { } while (0)
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);
# Checked with KERNEL_OBJ/include/generated/autoconf.h
unifdef \
-UCONFIG_ARM_TRUSTZONE \
-UCONFIG_CORESIGHT_ETM \
-UCONFIG_EXYNOS4_DEV_DWMCI \
-UCONFIG_EXYNOS4_ENABLE_CLOCK_DOWN \
-UCONFIG_FAST_RESUME \
-UCONFIG_INTERNAL_MODEM_IF \
-UCONFIG_ISDBT \
-UCONFIG_MACH_MIDAS \
-UCONFIG_MACH_U1_NA_SPR \
-UCONFIG_MACH_U1_NA_USCC \
-UCONFIG_MIDAS_COMMON \
-UCONFIG_S3C_DEV_HSMMC \
-UCONFIG_S3C_DEV_HSMMC1 \
-UCONFIG_SAMSUNG_PHONE_TTY \
-UCONFIG_SEC_WATCHDOG_RESET \
-UCONFIG_SLP \
-UCONFIG_SND_SAMSUNG_ALP \
-UCONFIG_USB_EXYNOS_SWITCH \
\
cpuidle-exynos4.c -ocpuidle-exynos4.unifdefed.c
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment