Commit 7426394f authored by Magnus Damm's avatar Magnus Damm Committed by Paul Mundt

sh: cpuidle for SuperH Mobile using hwblk

This patch adds cpuidle support for SuperH Mobile.

The sleep mode selected by cpuidle is compared with
the mode selected by the hwblk sleep code and the
best allowed mode is entered.

At this point "Sleep mode" and "Sleep mode + SF" are
supported. This code can easily be extended to support
"Software suspend mode", but the assembly code must
first be updated to avoid loosing interrupts.

Also, update the code to only copy the assembly snippet
into internal memory once at bootup.
Signed-off-by: default avatarMagnus Damm <damm@igel.co.jp>
Signed-off-by: default avatarPaul Mundt <lethal@linux-sh.org>
parent a61c1a63
...@@ -10,6 +10,15 @@ struct swsusp_arch_regs { ...@@ -10,6 +10,15 @@ struct swsusp_arch_regs {
struct pt_regs user_regs; struct pt_regs user_regs;
unsigned long bank1_regs[8]; unsigned long bank1_regs[8];
}; };
void sh_mobile_call_standby(unsigned long mode);
#ifdef CONFIG_CPU_IDLE
void sh_mobile_setup_cpuidle(void);
#else
static inline void sh_mobile_setup_cpuidle(void) {}
#endif
#endif #endif
/* flags passed to assembly suspend code */ /* flags passed to assembly suspend code */
......
...@@ -4,3 +4,4 @@ ...@@ -4,3 +4,4 @@
# Power Management & Sleep mode # Power Management & Sleep mode
obj-$(CONFIG_PM) += pm.o sleep.o obj-$(CONFIG_PM) += pm.o sleep.o
obj-$(CONFIG_CPU_IDLE) += cpuidle.o
/*
* arch/sh/kernel/cpu/shmobile/cpuidle.c
*
* Cpuidle support code for SuperH Mobile
*
* Copyright (C) 2009 Magnus Damm
*
* This file is subject to the terms and conditions of the GNU General Public
* License. See the file "COPYING" in the main directory of this archive
* for more details.
*/
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/io.h>
#include <linux/suspend.h>
#include <linux/cpuidle.h>
#include <asm/suspend.h>
#include <asm/uaccess.h>
#include <asm/hwblk.h>
static unsigned long cpuidle_mode[] = {
SUSP_SH_SLEEP, /* regular sleep mode */
SUSP_SH_SLEEP | SUSP_SH_SF, /* sleep mode + self refresh */
};
static int cpuidle_sleep_enter(struct cpuidle_device *dev,
struct cpuidle_state *state)
{
unsigned long allowed_mode = arch_hwblk_sleep_mode();
ktime_t before, after;
int requested_state = state - &dev->states[0];
int allowed_state;
int k;
/* convert allowed mode to allowed state */
for (k = ARRAY_SIZE(cpuidle_mode) - 1; k > 0; k--)
if (cpuidle_mode[k] == allowed_mode)
break;
allowed_state = k;
/* take the following into account for sleep mode selection:
* - allowed_state: best mode allowed by hardware (clock deps)
* - requested_state: best mode allowed by software (latencies)
*/
k = min_t(int, allowed_state, requested_state);
dev->last_state = &dev->states[k];
before = ktime_get();
sh_mobile_call_standby(cpuidle_mode[k]);
after = ktime_get();
return ktime_to_ns(ktime_sub(after, before)) >> 10;
}
static struct cpuidle_device cpuidle_dev;
static struct cpuidle_driver cpuidle_driver = {
.name = "sh_idle",
.owner = THIS_MODULE,
};
void sh_mobile_setup_cpuidle(void)
{
struct cpuidle_device *dev = &cpuidle_dev;
struct cpuidle_state *state;
int i;
cpuidle_register_driver(&cpuidle_driver);
for (i = 0; i < CPUIDLE_STATE_MAX; i++) {
dev->states[i].name[0] = '\0';
dev->states[i].desc[0] = '\0';
}
i = CPUIDLE_DRIVER_STATE_START;
state = &dev->states[i++];
snprintf(state->name, CPUIDLE_NAME_LEN, "C0");
strncpy(state->desc, "SuperH Sleep Mode", CPUIDLE_DESC_LEN);
state->exit_latency = 1;
state->target_residency = 1 * 2;
state->power_usage = 3;
state->flags = 0;
state->flags |= CPUIDLE_FLAG_SHALLOW;
state->flags |= CPUIDLE_FLAG_TIME_VALID;
state->enter = cpuidle_sleep_enter;
dev->safe_state = state;
state = &dev->states[i++];
snprintf(state->name, CPUIDLE_NAME_LEN, "C1");
strncpy(state->desc, "SuperH Sleep Mode [SF]", CPUIDLE_DESC_LEN);
state->exit_latency = 100;
state->target_residency = 1 * 2;
state->power_usage = 1;
state->flags = 0;
state->flags |= CPUIDLE_FLAG_TIME_VALID;
state->enter = cpuidle_sleep_enter;
dev->state_count = i;
cpuidle_register_device(dev);
}
/* /*
* arch/sh/kernel/cpu/sh4a/pm-sh_mobile.c * arch/sh/kernel/cpu/shmobile/pm.c
* *
* Power management support code for SuperH Mobile * Power management support code for SuperH Mobile
* *
...@@ -32,20 +32,17 @@ ...@@ -32,20 +32,17 @@
* *
* R-standby mode is unsupported, but will be added in the future * R-standby mode is unsupported, but will be added in the future
* U-standby mode is low priority since it needs bootloader hacks * U-standby mode is low priority since it needs bootloader hacks
*
* All modes should be tied in with cpuidle. But before that can
* happen we need to keep track of enabled hardware blocks so we
* can avoid entering sleep modes that stop clocks to hardware
* blocks that are in use even though the cpu core is idle.
*/ */
#define ILRAM_BASE 0xe5200000
extern const unsigned char sh_mobile_standby[]; extern const unsigned char sh_mobile_standby[];
extern const unsigned int sh_mobile_standby_size; extern const unsigned int sh_mobile_standby_size;
static void sh_mobile_call_standby(unsigned long mode) void sh_mobile_call_standby(unsigned long mode)
{ {
extern void *vbr_base; extern void *vbr_base;
void *onchip_mem = (void *)0xe5200000; /* ILRAM */ void *onchip_mem = (void *)ILRAM_BASE;
void (*standby_onchip_mem)(unsigned long) = onchip_mem; void (*standby_onchip_mem)(unsigned long) = onchip_mem;
/* Note: Wake up from sleep may generate exceptions! /* Note: Wake up from sleep may generate exceptions!
...@@ -55,11 +52,6 @@ static void sh_mobile_call_standby(unsigned long mode) ...@@ -55,11 +52,6 @@ static void sh_mobile_call_standby(unsigned long mode)
if (mode & SUSP_SH_SF) if (mode & SUSP_SH_SF)
asm volatile("ldc %0, vbr" : : "r" (onchip_mem) : "memory"); asm volatile("ldc %0, vbr" : : "r" (onchip_mem) : "memory");
/* Copy the assembly snippet to the otherwise ununsed ILRAM */
memcpy(onchip_mem, sh_mobile_standby, sh_mobile_standby_size);
wmb();
ctrl_barrier();
/* Let assembly snippet in on-chip memory handle the rest */ /* Let assembly snippet in on-chip memory handle the rest */
standby_onchip_mem(mode); standby_onchip_mem(mode);
...@@ -85,7 +77,15 @@ static struct platform_suspend_ops sh_pm_ops = { ...@@ -85,7 +77,15 @@ static struct platform_suspend_ops sh_pm_ops = {
static int __init sh_pm_init(void) static int __init sh_pm_init(void)
{ {
void *onchip_mem = (void *)ILRAM_BASE;
/* Copy the assembly snippet to the otherwise ununsed ILRAM */
memcpy(onchip_mem, sh_mobile_standby, sh_mobile_standby_size);
wmb();
ctrl_barrier();
suspend_set_ops(&sh_pm_ops); suspend_set_ops(&sh_pm_ops);
sh_mobile_setup_cpuidle();
return 0; return 0;
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment