linux/arch/arm/mach-exynos/cpuidle.c
<<
>>
Prefs
   1/* linux/arch/arm/mach-exynos4/cpuidle.c
   2 *
   3 * Copyright (c) 2011 Samsung Electronics Co., Ltd.
   4 *              http://www.samsung.com
   5 *
   6 * This program is free software; you can redistribute it and/or modify
   7 * it under the terms of the GNU General Public License version 2 as
   8 * published by the Free Software Foundation.
   9*/
  10
  11#include <linux/kernel.h>
  12#include <linux/init.h>
  13#include <linux/cpuidle.h>
  14#include <linux/cpu_pm.h>
  15#include <linux/io.h>
  16#include <linux/export.h>
  17#include <linux/time.h>
  18
  19#include <asm/proc-fns.h>
  20#include <asm/smp_scu.h>
  21#include <asm/suspend.h>
  22#include <asm/unified.h>
  23#include <asm/cpuidle.h>
  24#include <mach/regs-pmu.h>
  25#include <mach/pmu.h>
  26
  27#include <plat/cpu.h>
  28
  29#define REG_DIRECTGO_ADDR       (samsung_rev() == EXYNOS4210_REV_1_1 ? \
  30                        S5P_INFORM7 : (samsung_rev() == EXYNOS4210_REV_1_0 ? \
  31                        (S5P_VA_SYSRAM + 0x24) : S5P_INFORM0))
  32#define REG_DIRECTGO_FLAG       (samsung_rev() == EXYNOS4210_REV_1_1 ? \
  33                        S5P_INFORM6 : (samsung_rev() == EXYNOS4210_REV_1_0 ? \
  34                        (S5P_VA_SYSRAM + 0x20) : S5P_INFORM1))
  35
  36#define S5P_CHECK_AFTR          0xFCBA0D10
  37
  38static int exynos4_enter_lowpower(struct cpuidle_device *dev,
  39                                struct cpuidle_driver *drv,
  40                                int index);
  41
  42static struct cpuidle_state exynos4_cpuidle_set[] __initdata = {
  43        [0] = ARM_CPUIDLE_WFI_STATE,
  44        [1] = {
  45                .enter                  = exynos4_enter_lowpower,
  46                .exit_latency           = 300,
  47                .target_residency       = 100000,
  48                .flags                  = CPUIDLE_FLAG_TIME_VALID,
  49                .name                   = "C1",
  50                .desc                   = "ARM power down",
  51        },
  52};
  53
  54static DEFINE_PER_CPU(struct cpuidle_device, exynos4_cpuidle_device);
  55
  56static struct cpuidle_driver exynos4_idle_driver = {
  57        .name                   = "exynos4_idle",
  58        .owner                  = THIS_MODULE,
  59        .en_core_tk_irqen       = 1,
  60};
  61
  62/* Ext-GIC nIRQ/nFIQ is the only wakeup source in AFTR */
  63static void exynos4_set_wakeupmask(void)
  64{
  65        __raw_writel(0x0000ff3e, S5P_WAKEUP_MASK);
  66}
  67
  68static unsigned int g_pwr_ctrl, g_diag_reg;
  69
  70static void save_cpu_arch_register(void)
  71{
  72        /*read power control register*/
  73        asm("mrc p15, 0, %0, c15, c0, 0" : "=r"(g_pwr_ctrl) : : "cc");
  74        /*read diagnostic register*/
  75        asm("mrc p15, 0, %0, c15, c0, 1" : "=r"(g_diag_reg) : : "cc");
  76        return;
  77}
  78
  79static void restore_cpu_arch_register(void)
  80{
  81        /*write power control register*/
  82        asm("mcr p15, 0, %0, c15, c0, 0" : : "r"(g_pwr_ctrl) : "cc");
  83        /*write diagnostic register*/
  84        asm("mcr p15, 0, %0, c15, c0, 1" : : "r"(g_diag_reg) : "cc");
  85        return;
  86}
  87
  88static int idle_finisher(unsigned long flags)
  89{
  90        cpu_do_idle();
  91        return 1;
  92}
  93
  94static int exynos4_enter_core0_aftr(struct cpuidle_device *dev,
  95                                struct cpuidle_driver *drv,
  96                                int index)
  97{
  98        unsigned long tmp;
  99
 100        exynos4_set_wakeupmask();
 101
 102        /* Set value of power down register for aftr mode */
 103        exynos_sys_powerdown_conf(SYS_AFTR);
 104
 105        __raw_writel(virt_to_phys(s3c_cpu_resume), REG_DIRECTGO_ADDR);
 106        __raw_writel(S5P_CHECK_AFTR, REG_DIRECTGO_FLAG);
 107
 108        save_cpu_arch_register();
 109
 110        /* Setting Central Sequence Register for power down mode */
 111        tmp = __raw_readl(S5P_CENTRAL_SEQ_CONFIGURATION);
 112        tmp &= ~S5P_CENTRAL_LOWPWR_CFG;
 113        __raw_writel(tmp, S5P_CENTRAL_SEQ_CONFIGURATION);
 114
 115        cpu_pm_enter();
 116        cpu_suspend(0, idle_finisher);
 117
 118#ifdef CONFIG_SMP
 119        scu_enable(S5P_VA_SCU);
 120#endif
 121        cpu_pm_exit();
 122
 123        restore_cpu_arch_register();
 124
 125        /*
 126         * If PMU failed while entering sleep mode, WFI will be
 127         * ignored by PMU and then exiting cpu_do_idle().
 128         * S5P_CENTRAL_LOWPWR_CFG bit will not be set automatically
 129         * in this situation.
 130         */
 131        tmp = __raw_readl(S5P_CENTRAL_SEQ_CONFIGURATION);
 132        if (!(tmp & S5P_CENTRAL_LOWPWR_CFG)) {
 133                tmp |= S5P_CENTRAL_LOWPWR_CFG;
 134                __raw_writel(tmp, S5P_CENTRAL_SEQ_CONFIGURATION);
 135        }
 136
 137        /* Clear wakeup state register */
 138        __raw_writel(0x0, S5P_WAKEUP_STAT);
 139
 140        return index;
 141}
 142
 143static int exynos4_enter_lowpower(struct cpuidle_device *dev,
 144                                struct cpuidle_driver *drv,
 145                                int index)
 146{
 147        int new_index = index;
 148
 149        /* This mode only can be entered when other core's are offline */
 150        if (num_online_cpus() > 1)
 151                new_index = drv->safe_state_index;
 152
 153        if (new_index == 0)
 154                return arm_cpuidle_simple_enter(dev, drv, new_index);
 155        else
 156                return exynos4_enter_core0_aftr(dev, drv, new_index);
 157}
 158
 159static int __init exynos4_init_cpuidle(void)
 160{
 161        int i, max_cpuidle_state, cpu_id;
 162        struct cpuidle_device *device;
 163        struct cpuidle_driver *drv = &exynos4_idle_driver;
 164
 165        /* Setup cpuidle driver */
 166        drv->state_count = (sizeof(exynos4_cpuidle_set) /
 167                                       sizeof(struct cpuidle_state));
 168        max_cpuidle_state = drv->state_count;
 169        for (i = 0; i < max_cpuidle_state; i++) {
 170                memcpy(&drv->states[i], &exynos4_cpuidle_set[i],
 171                                sizeof(struct cpuidle_state));
 172        }
 173        drv->safe_state_index = 0;
 174        cpuidle_register_driver(&exynos4_idle_driver);
 175
 176        for_each_cpu(cpu_id, cpu_online_mask) {
 177                device = &per_cpu(exynos4_cpuidle_device, cpu_id);
 178                device->cpu = cpu_id;
 179
 180                if (cpu_id == 0)
 181                        device->state_count = (sizeof(exynos4_cpuidle_set) /
 182                                               sizeof(struct cpuidle_state));
 183                else
 184                        device->state_count = 1;        /* Support IDLE only */
 185
 186                if (cpuidle_register_device(device)) {
 187                        printk(KERN_ERR "CPUidle register device failed\n,");
 188                        return -EIO;
 189                }
 190        }
 191
 192        return 0;
 193}
 194device_initcall(exynos4_init_cpuidle);
 195