| /* |
| * arch/arm/mach-feroceon-kw/cpuidle.c |
| * |
| * CPU idle Marvell Kirkwood SoCs |
| * |
| * This file is licensed under the terms of the GNU General Public |
| * License version 2. This program is licensed "as is" without any |
| * warranty of any kind, whether express or implied. |
| * |
| * The cpu idle uses wait-for-interrupt and DDR self refresh in order |
| * to implement two idle states - |
| * #1 wait-for-interrupt |
| * #2 wait-for-interrupt and DDR self refresh |
| */ |
| |
| #include <linux/kernel.h> |
| #include <linux/init.h> |
| #include <linux/platform_device.h> |
| #include <linux/cpuidle.h> |
| #include <linux/io.h> |
| #include <asm/proc-fns.h> |
| #include <linux/proc_fs.h> |
| #include <config/mvSysHwConfig.h> |
| #include "mvCommon.h" |
| #include "mvOs.h" |
| |
| #define CONFIG_CPUFREQ_TOGGLE_ON_DEEPIDLE |
| |
| #ifdef CONFIG_CPUFREQ_TOGGLE_ON_DEEPIDLE |
| #include "ctrlEnv/mvCtrlEnvLib.h" |
| #endif |
| |
| #define KIRKWOOD_MAX_STATES 2 |
| |
| extern void mv_kw2_cpu_idle_enter(void); |
| |
| #ifdef CONFIG_MV_PMU_PROC |
| extern struct proc_dir_entry *mv_pm_proc_entry; |
| struct proc_dir_entry *cpu_idle_proc; |
| #endif /* CONFIG_MV_PMU_PROC */ |
| |
| static struct cpuidle_device *kirkwood_cpu_idle_device; |
| |
| static struct cpuidle_driver kirkwood_idle_driver = { |
| .name = "kirkwood_idle", |
| .owner = THIS_MODULE, |
| }; |
| |
| static DEFINE_PER_CPU(struct cpuidle_device, kirkwood_cpuidle_device); |
| |
| static int device_registered; |
| int support_dram_self_refresh = 0; |
| |
| /* Actual code that puts the SoC in different idle states */ |
| static int kirkwood_enter_idle(struct cpuidle_device *dev, |
| struct cpuidle_state *state) |
| { |
| struct timeval before, after; |
| int idle_time; |
| MV_BOOL pwr_save_on; |
| |
| #ifdef CONFIG_JTAG_DEBUG |
| local_irq_enable(); |
| return 0; |
| #endif |
| |
| #ifdef CONFIG_CPUFREQ_TOGGLE_ON_DEEPIDLE |
| /* Set CPU to fast frequency */ |
| pwr_save_on = mvCtrlIsPwrSaveOn(); |
| mvCtrlPwrSaveOff(); |
| #endif /* CONFIG_CPUFREQ_TOGGLE_ON_DEEPIDLE */ |
| |
| local_irq_disable(); |
| do_gettimeofday(&before); |
| if (state == &dev->states[0]) { |
| /* Wait for interrupt state */ |
| cpu_do_idle(); |
| } else if (state == &dev->states[1]) { |
| #ifdef CONFIG_MV_PM_IDLE_WFI_SR |
| /* |
| * Following write will put DDR in self refresh. |
| * Note that we have 256 cycles before DDR puts it |
| * self in self-refresh, so the wait-for-interrupt |
| * call afterwards won't get the DDR from self refresh |
| * mode. |
| */ |
| writel(0x7, DDR_OPERATION_BASE); |
| cpu_do_idle(); |
| #endif |
| #ifdef CONFIG_MV_PM_IDLE_DEEPIDLE_SR |
| /* |
| * Following write will put DDR in self refresh. |
| * Note that we have 256 cycles before DDR puts it |
| * self in self-refresh, so the wait-for-interrupt |
| * call afterwards won't get the DDR from self refresh |
| * mode. |
| */ |
| mv_kw2_cpu_idle_enter(); |
| #endif |
| } |
| do_gettimeofday(&after); |
| local_irq_enable(); |
| |
| #ifdef CONFIG_CPUFREQ_TOGGLE_ON_DEEPIDLE |
| /* Set CPU to fast frequency */ |
| if (pwr_save_on == MV_TRUE) |
| mvCtrlPwrSaveOn(); |
| #endif /* CONFIG_CPUFREQ_TOGGLE_ON_DEEPIDLE */ |
| |
| idle_time = (after.tv_sec - before.tv_sec) * USEC_PER_SEC + |
| (after.tv_usec - before.tv_usec); |
| return idle_time; |
| } |
| |
| #ifdef CONFIG_MV_PMU_PROC |
| static int mv_cpu_idle_write(struct file *file, const char *buffer, |
| unsigned long count, void *data) |
| { |
| MV_U32 regs[4]; |
| |
| /* Reading / Writing from system controller internal registers */ |
| if (!strncmp (buffer, "enable", strlen("enable"))) { |
| if(device_registered == 0) { |
| device_registered = 1; |
| if (cpuidle_register_device(kirkwood_cpu_idle_device)) { |
| printk(KERN_ERR "mv_cpu_idle_write: Failed registering\n"); |
| return -EIO; |
| } |
| } |
| cpuidle_enable_device(kirkwood_cpu_idle_device); |
| } else if (!strncmp (buffer, "disable", strlen("disable"))) { |
| cpuidle_disable_device(kirkwood_cpu_idle_device); |
| } else if (!strncmp (buffer, "test", strlen("test"))) { |
| |
| buffer += strlen("test "); |
| /* Store Interrupt mask registers. */ |
| regs[0] = MV_REG_READ(MV_IRQ_MASK_LOW_REG); |
| regs[1] = MV_REG_READ(MV_IRQ_MASK_HIGH_REG); |
| regs[2] = MV_REG_READ(MV_IRQ_MASK_ERROR_REG); |
| |
| /* Disable all interrupts . */ |
| MV_REG_WRITE(MV_IRQ_MASK_LOW_REG, 0x0); |
| MV_REG_WRITE(MV_IRQ_MASK_HIGH_REG, 0x0); |
| MV_REG_WRITE(MV_IRQ_MASK_ERROR_REG, 0x0); |
| |
| /* Enable only the UART interrupt. */ |
| MV_REG_BIT_SET(MV_IRQ_MASK_HIGH_REG, 1 << (UART_IRQ_NUM(0) - 32)); |
| |
| if (!strncmp (buffer, "idle", strlen("idle"))) { |
| printk(KERN_INFO "Press any key to leave deep idle:"); |
| mv_kw2_cpu_idle_enter(); |
| } else if (!strncmp (buffer, "wfi", strlen("wfi"))) { |
| printk(KERN_INFO "Press any key to leave WFI:"); |
| cpu_do_idle(); |
| } |
| |
| /* Restore Interrupt mask registers. */ |
| MV_REG_WRITE(MV_IRQ_MASK_LOW_REG, regs[0]); |
| MV_REG_WRITE(MV_IRQ_MASK_HIGH_REG, regs[1]); |
| MV_REG_WRITE(MV_IRQ_MASK_ERROR_REG, regs[2]); |
| } else if (!strncmp (buffer, "sr_enable", strlen("sr_enable"))) { |
| local_irq_disable(); |
| support_dram_self_refresh = 1; |
| local_irq_enable(); |
| } else if (!strncmp (buffer, "sr_disable", strlen("sr_disable"))) { |
| local_irq_disable(); |
| support_dram_self_refresh = 0; |
| local_irq_enable(); |
| } |
| |
| |
| return count; |
| } |
| |
| |
| static int mv_cpu_idle_read(char *buffer, char **buffer_location, off_t offset, |
| int buffer_length, int *zero, void *ptr) |
| { |
| if (offset > 0) |
| return 0; |
| return sprintf(buffer, "enable - Enable CPU Idle framework.\n" |
| "disable - Disable CPU idle framework.\n" |
| "test (idle / wfi) - Manually enter CPU Idle state, exit by key stroke (DEBUG ONLY).\n" |
| "sr_enable / sr_disable - Enable / Disable DRAM Self-Refresh when entering deel-idle.\n"); |
| |
| } |
| |
| #endif /* CONFIG_MV_PMU_PROC */ |
| |
| /* Initialize CPU idle by registering the idle states */ |
| static int kw_cpuidle_probe(struct platform_device *pdev) |
| { |
| struct cpuidle_device *device; |
| |
| cpuidle_register_driver(&kirkwood_idle_driver); |
| |
| device = &per_cpu(kirkwood_cpuidle_device, smp_processor_id()); |
| device->state_count = KIRKWOOD_MAX_STATES; |
| |
| /* Wait for interrupt state */ |
| device->states[0].enter = kirkwood_enter_idle; |
| device->states[0].exit_latency = 1; |
| device->states[0].target_residency = 100; |
| device->states[0].flags = CPUIDLE_FLAG_TIME_VALID; |
| strcpy(device->states[0].name, "WFI"); |
| strcpy(device->states[0].desc, "Wait for interrupt"); |
| |
| #ifdef CONFIG_MV_PM_IDLE_WFI_SR |
| /* Wait for interrupt and DDR self refresh state */ |
| device->states[1].enter = kirkwood_enter_idle; |
| device->states[1].exit_latency = 10; |
| device->states[1].target_residency = 1000; |
| device->states[1].flags = CPUIDLE_FLAG_TIME_VALID; |
| strcpy(device->states[1].name, "DDR SR"); |
| strcpy(device->states[1].desc, "WFI and DDR Self Refresh"); |
| #endif |
| |
| #ifdef CONFIG_MV_PM_IDLE_DEEPIDLE_SR |
| /* CPU Deep Idle state */ |
| device->states[1].enter = kirkwood_enter_idle; |
| device->states[1].exit_latency = 10; |
| device->states[1].target_residency = 5000; |
| device->states[1].flags = CPUIDLE_FLAG_TIME_VALID; |
| strcpy(device->states[1].name, "DEEP IDLE"); |
| strcpy(device->states[1].desc, "CPU Deep Idle"); |
| #endif |
| |
| #if 0 |
| if (cpuidle_register_device(device)) { |
| printk(KERN_ERR "kirkwood_init_cpuidle: Failed registering\n"); |
| return -EIO; |
| } |
| #endif |
| kirkwood_cpu_idle_device = device; |
| |
| #ifdef CONFIG_MV_PMU_PROC |
| /* Create proc entry. */ |
| cpu_idle_proc = create_proc_entry("cpu_idle", 0666, mv_pm_proc_entry); |
| cpu_idle_proc->read_proc = mv_cpu_idle_read; |
| cpu_idle_proc->write_proc = mv_cpu_idle_write; |
| cpu_idle_proc->nlink = 1; |
| #endif /* CONFIG_MV_PMU_PROC */ |
| |
| return 0; |
| } |
| |
| static int kw_cpuidle_remove(struct platform_device *pdev) |
| { |
| #ifdef CONFIG_MV_PMU_PROC |
| remove_proc_entry("cpu_idle", cpu_idle_proc); |
| #endif |
| cpuidle_unregister_device(kirkwood_cpu_idle_device); |
| return 0; |
| } |
| |
| struct platform_driver kw_cpuidle_driver = { |
| .probe = kw_cpuidle_probe, |
| .remove = kw_cpuidle_remove, |
| .driver = { |
| .name = "kw_cpuidle", |
| .owner = THIS_MODULE, |
| }, |
| }; |
| |
| static int __init kw_cpuidle_drv_init(void) |
| { |
| /* |
| if (MV_6601_DEV_ID == mvCtrlModelGet()) |
| return 0; |
| */ |
| |
| device_registered = 0; |
| return platform_driver_register(&kw_cpuidle_driver); |
| |
| } |
| |
| static void __exit kw_cpuidle_drv_cleanup(void) |
| { |
| platform_driver_unregister(&kw_cpuidle_driver); |
| } |
| |
| module_init(kw_cpuidle_drv_init); |
| module_exit(kw_cpuidle_drv_cleanup); |