From 7426394f20c2e74b7c560bcd266cec1b327a269b Mon Sep 17 00:00:00 2001 From: Magnus Damm Date: Fri, 3 Jul 2009 10:28:00 +0000 Subject: 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: Magnus Damm Signed-off-by: Paul Mundt --- arch/sh/kernel/cpu/shmobile/Makefile | 1 + arch/sh/kernel/cpu/shmobile/cpuidle.c | 102 ++++++++++++++++++++++++++++++++++ arch/sh/kernel/cpu/shmobile/pm.c | 26 ++++----- 3 files changed, 116 insertions(+), 13 deletions(-) create mode 100644 arch/sh/kernel/cpu/shmobile/cpuidle.c (limited to 'arch/sh/kernel/cpu/shmobile') diff --git a/arch/sh/kernel/cpu/shmobile/Makefile b/arch/sh/kernel/cpu/shmobile/Makefile index 08bfa7c7db29..e8a5111e848a 100644 --- a/arch/sh/kernel/cpu/shmobile/Makefile +++ b/arch/sh/kernel/cpu/shmobile/Makefile @@ -4,3 +4,4 @@ # Power Management & Sleep mode obj-$(CONFIG_PM) += pm.o sleep.o +obj-$(CONFIG_CPU_IDLE) += cpuidle.o diff --git a/arch/sh/kernel/cpu/shmobile/cpuidle.c b/arch/sh/kernel/cpu/shmobile/cpuidle.c new file mode 100644 index 000000000000..4afdd975cc66 --- /dev/null +++ b/arch/sh/kernel/cpu/shmobile/cpuidle.c @@ -0,0 +1,102 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include +#include + +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); +} diff --git a/arch/sh/kernel/cpu/shmobile/pm.c b/arch/sh/kernel/cpu/shmobile/pm.c index 8c067adf6830..de078d24ce56 100644 --- a/arch/sh/kernel/cpu/shmobile/pm.c +++ b/arch/sh/kernel/cpu/shmobile/pm.c @@ -1,5 +1,5 @@ /* - * arch/sh/kernel/cpu/sh4a/pm-sh_mobile.c + * arch/sh/kernel/cpu/shmobile/pm.c * * Power management support code for SuperH Mobile * @@ -32,20 +32,17 @@ * * R-standby mode is unsupported, but will be added in the future * 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 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; - void *onchip_mem = (void *)0xe5200000; /* ILRAM */ + void *onchip_mem = (void *)ILRAM_BASE; void (*standby_onchip_mem)(unsigned long) = onchip_mem; /* Note: Wake up from sleep may generate exceptions! @@ -55,11 +52,6 @@ static void sh_mobile_call_standby(unsigned long mode) if (mode & SUSP_SH_SF) 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 */ standby_onchip_mem(mode); @@ -85,7 +77,15 @@ static struct platform_suspend_ops sh_pm_ops = { 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); + sh_mobile_setup_cpuidle(); return 0; } -- cgit v1.2.2 From 309214af53f030e0664aea011a9f628c5eb9cc2d Mon Sep 17 00:00:00 2001 From: Magnus Damm Date: Mon, 17 Aug 2009 09:27:29 +0000 Subject: sh: rework SuperH Mobile sleep code exception handling This patch updates the exception handling in the sleep code for SuperH Mobile. With the patch applied the sleep code always rewrites the VBR and resumes from the exception vector, re-initializes hardware and jumps straight to the original interrupt vector. Tested on sh7722 and sh7724 with "Sleep Mode", "Sleep Mode + SF" and "Software Standby Mode + SF" with CONFIG_SUSPEND. Signed-off-by: Magnus Damm Signed-off-by: Paul Mundt --- arch/sh/kernel/cpu/shmobile/pm.c | 16 +--- arch/sh/kernel/cpu/shmobile/sleep.S | 155 ++++++++++++++++++++++++------------ 2 files changed, 107 insertions(+), 64 deletions(-) (limited to 'arch/sh/kernel/cpu/shmobile') diff --git a/arch/sh/kernel/cpu/shmobile/pm.c b/arch/sh/kernel/cpu/shmobile/pm.c index de078d24ce56..ee3c2aaf66fb 100644 --- a/arch/sh/kernel/cpu/shmobile/pm.c +++ b/arch/sh/kernel/cpu/shmobile/pm.c @@ -41,23 +41,11 @@ extern const unsigned int sh_mobile_standby_size; void sh_mobile_call_standby(unsigned long mode) { - extern void *vbr_base; void *onchip_mem = (void *)ILRAM_BASE; - void (*standby_onchip_mem)(unsigned long) = onchip_mem; - - /* Note: Wake up from sleep may generate exceptions! - * Setup VBR to point to on-chip ram if self-refresh is - * going to be used. - */ - if (mode & SUSP_SH_SF) - asm volatile("ldc %0, vbr" : : "r" (onchip_mem) : "memory"); + void (*standby_onchip_mem)(unsigned long, unsigned long) = onchip_mem; /* Let assembly snippet in on-chip memory handle the rest */ - standby_onchip_mem(mode); - - /* Put VBR back in System RAM again */ - if (mode & SUSP_SH_SF) - asm volatile("ldc %0, vbr" : : "r" (&vbr_base) : "memory"); + standby_onchip_mem(mode, ILRAM_BASE); } static int sh_pm_enter(suspend_state_t state) diff --git a/arch/sh/kernel/cpu/shmobile/sleep.S b/arch/sh/kernel/cpu/shmobile/sleep.S index baf2d7d46b05..a439e6c7824f 100644 --- a/arch/sh/kernel/cpu/shmobile/sleep.S +++ b/arch/sh/kernel/cpu/shmobile/sleep.S @@ -16,19 +16,52 @@ #include #include +/* + * Kernel mode register usage, see entry.S: + * k0 scratch + * k1 scratch + * k4 scratch + */ +#define k0 r0 +#define k1 r1 +#define k4 r4 + /* manage self-refresh and enter standby mode. * this code will be copied to on-chip memory and executed from there. */ .balign 4096,0,4096 ENTRY(sh_mobile_standby) + + /* save original vbr */ + stc vbr, r1 + mova saved_vbr, r0 + mov.l r1, @r0 + + /* point vbr to our on-chip memory page */ + ldc r5, vbr + + /* save return address */ + mova saved_spc, r0 + sts pr, r5 + mov.l r5, @r0 + + /* save sr */ + mova saved_sr, r0 + stc sr, r5 + mov.l r5, @r0 + + /* save mode flags */ + mova saved_mode, r0 + mov.l r4, @r0 + + /* put mode flags in r0 */ mov r4, r0 tst #SUSP_SH_SF, r0 bt skip_set_sf #ifdef CONFIG_CPU_SUBTYPE_SH7724 /* DBSC: put memory in self-refresh mode */ - mov.l dben_reg, r4 mov.l dben_data0, r1 mov.l r1, @r4 @@ -60,14 +93,6 @@ ENTRY(sh_mobile_standby) #endif skip_set_sf: - tst #SUSP_SH_SLEEP, r0 - bt test_standby - - /* set mode to "sleep mode" */ - bra do_sleep - mov #0x00, r1 - -test_standby: tst #SUSP_SH_STANDBY, r0 bt test_rstandby @@ -85,77 +110,107 @@ test_rstandby: test_ustandby: tst #SUSP_SH_USTANDBY, r0 - bt done_sleep + bt force_sleep /* set mode to "u-standby mode" */ - mov #0x10, r1 + bra do_sleep + mov #0x10, r1 - /* fall-through */ +force_sleep: + + /* set mode to "sleep mode" */ + mov #0x00, r1 do_sleep: /* setup and enter selected standby mode */ mov.l 5f, r4 mov.l r1, @r4 +again: sleep + bra again + nop + +restore_jump_vbr: + /* setup spc with return address to c code */ + mov.l saved_spc, k0 + ldc k0, spc + + /* restore vbr */ + mov.l saved_vbr, k0 + ldc k0, vbr + + /* setup ssr with saved sr */ + mov.l saved_sr, k0 + ldc k0, ssr + + /* get mode flags */ + mov.l saved_mode, k0 done_sleep: /* reset standby mode to sleep mode */ - mov.l 5f, r4 - mov #0x00, r1 - mov.l r1, @r4 + mov.l 5f, k4 + mov #0x00, k1 + mov.l k1, @k4 - tst #SUSP_SH_SF, r0 + tst #SUSP_SH_SF, k0 bt skip_restore_sf #ifdef CONFIG_CPU_SUBTYPE_SH7724 /* DBSC: put memory in auto-refresh mode */ + mov.l dbrfpdn0_reg, k4 + mov.l dbrfpdn0_data0, k1 + mov.l k1, @k4 - mov.l dbrfpdn0_reg, r4 - mov.l dbrfpdn0_data0, r1 - mov.l r1, @r4 - - /* sleep 140 ns */ - nop + nop /* sleep 140 ns */ nop nop nop - mov.l dbcmdcnt_reg, r4 - mov.l dbcmdcnt_data0, r1 - mov.l r1, @r4 + mov.l dbcmdcnt_reg, k4 + mov.l dbcmdcnt_data0, k1 + mov.l k1, @k4 - mov.l dbcmdcnt_reg, r4 - mov.l dbcmdcnt_data1, r1 - mov.l r1, @r4 + mov.l dbcmdcnt_reg, k4 + mov.l dbcmdcnt_data1, k1 + mov.l k1, @k4 - mov.l dben_reg, r4 - mov.l dben_data1, r1 - mov.l r1, @r4 + mov.l dben_reg, k4 + mov.l dben_data1, k1 + mov.l k1, @k4 - mov.l dbrfpdn0_reg, r4 - mov.l dbrfpdn0_data2, r1 - mov.l r1, @r4 + mov.l dbrfpdn0_reg, k4 + mov.l dbrfpdn0_data2, k1 + mov.l k1, @k4 #else /* SBSC: set auto-refresh mode */ - mov.l 1f, r4 - mov.l @r4, r2 - mov.l 4f, r3 - and r3, r2 - mov.l r2, @r4 - mov.l 6f, r4 - mov.l 7f, r1 - mov.l 8f, r2 - mov.l @r4, r3 - mov #-1, r4 - add r4, r3 - or r2, r3 - mov.l r3, @r1 + mov.l 1f, k4 + mov.l @k4, k0 + mov.l 4f, k1 + and k1, k0 + mov.l k0, @k4 + mov.l 6f, k4 + mov.l 8f, k0 + mov.l @k4, k1 + mov #-1, k4 + add k4, k1 + or k1, k0 + mov.l 7f, k1 + mov.l k0, @k1 #endif skip_restore_sf: - rts + /* jump to vbr vector */ + mov.l saved_vbr, k0 + mov.l offset_vbr, k4 + add k4, k0 + jmp @k0 nop .balign 4 +saved_mode: .long 0 +saved_spc: .long 0 +saved_sr: .long 0 +saved_vbr: .long 0 +offset_vbr: .long 0x600 #ifdef CONFIG_CPU_SUBTYPE_SH7724 dben_reg: .long 0xfd000010 /* DBEN */ dben_data0: .long 0 @@ -178,12 +233,12 @@ dbcmdcnt_data1: .long 4 7: .long 0xfe400018 /* RTCNT */ 8: .long 0xa55a0000 + /* interrupt vector @ 0x600 */ .balign 0x400,0,0x400 .long 0xdeadbeef .balign 0x200,0,0x200 - /* sh7722 will end up here in sleep mode */ - rte + bra restore_jump_vbr nop sh_mobile_standby_end: -- cgit v1.2.2 From 63cd91dfd4976cb9687b07e9a9864912631d2e08 Mon Sep 17 00:00:00 2001 From: Magnus Damm Date: Mon, 17 Aug 2009 09:41:40 +0000 Subject: sh: SuperH Mobile Software Standby support for cpuidle This patch adds "SuperH Mobile Standby Mode [SF]" to the list of cpuidle sleep modes. If the software latency requirements from cpuidle are met together with fulfilled hardware requirements then deep sleep modes can be entered. Tested on sh7722 and sh7724 with "Sleep Mode", "Sleep Mode + SF" and "Software Standby Mode + SF" together with a multimedia work load and flood ping without packet drop. Signed-off-by: Magnus Damm Signed-off-by: Paul Mundt --- arch/sh/kernel/cpu/shmobile/cpuidle.c | 11 +++++++++++ 1 file changed, 11 insertions(+) (limited to 'arch/sh/kernel/cpu/shmobile') diff --git a/arch/sh/kernel/cpu/shmobile/cpuidle.c b/arch/sh/kernel/cpu/shmobile/cpuidle.c index 4afdd975cc66..1c504bd972c3 100644 --- a/arch/sh/kernel/cpu/shmobile/cpuidle.c +++ b/arch/sh/kernel/cpu/shmobile/cpuidle.c @@ -21,6 +21,7 @@ static unsigned long cpuidle_mode[] = { SUSP_SH_SLEEP, /* regular sleep mode */ SUSP_SH_SLEEP | SUSP_SH_SF, /* sleep mode + self refresh */ + SUSP_SH_STANDBY | SUSP_SH_SF, /* software standby mode + self refresh */ }; static int cpuidle_sleep_enter(struct cpuidle_device *dev, @@ -96,6 +97,16 @@ void sh_mobile_setup_cpuidle(void) state->flags |= CPUIDLE_FLAG_TIME_VALID; state->enter = cpuidle_sleep_enter; + state = &dev->states[i++]; + snprintf(state->name, CPUIDLE_NAME_LEN, "C2"); + strncpy(state->desc, "SuperH Mobile Standby Mode [SF]", CPUIDLE_DESC_LEN); + state->exit_latency = 2300; + 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); -- cgit v1.2.2 From 6a93dde1e8216f7af9b2551a60fb1a5eeac4a89f Mon Sep 17 00:00:00 2001 From: Magnus Damm Date: Fri, 14 Aug 2009 10:48:16 +0000 Subject: sh: Runtime PM for SuperH Mobile platform bus devices This patch is V3 of the SuperH Mobile Runtime PM platform bus implentation matching Rafael's Runtime PM v16. The code gets invoked from the SuperH specific Runtime PM platform bus functions that override the weak symbols for: - platform_pm_runtime_suspend() - platform_pm_runtime_resume() - platform_pm_runtime_idle() This Runtime PM implementation performs two levels of power management. At the time of platform bus runtime suspend the clock to the device is stopped instantly. Later on if all devices within the power domain has their clocks stopped then the device driver ->runtime_suspend() callbacks are used to save hardware register state for each device. Device driver ->runtime_suspend() calls are scheduled from cpuidle context using platform_pm_runtime_suspend_idle(). When all devices have been fully suspended the processor is allowed to enter deep sleep from cpuidle. The runtime resume operation turns on clocks and also restores registers if needed. It is worth noting that the devices start in a suspended state and the device driver is responsible for calling runtime resume before accessing the actual hardware. In this particular platform bus implementation runtime resume is not allowed from interrupt context. Runtime suspend is however allowed from interrupt context as long as the synchronous functions are avoided. [ updated for v17 -- PFM. ] Signed-off-by: Magnus Damm Signed-off-by: Paul Mundt --- arch/sh/kernel/cpu/shmobile/Makefile | 1 + arch/sh/kernel/cpu/shmobile/pm_runtime.c | 304 +++++++++++++++++++++++++++++++ 2 files changed, 305 insertions(+) create mode 100644 arch/sh/kernel/cpu/shmobile/pm_runtime.c (limited to 'arch/sh/kernel/cpu/shmobile') diff --git a/arch/sh/kernel/cpu/shmobile/Makefile b/arch/sh/kernel/cpu/shmobile/Makefile index e8a5111e848a..a39f88ea1a85 100644 --- a/arch/sh/kernel/cpu/shmobile/Makefile +++ b/arch/sh/kernel/cpu/shmobile/Makefile @@ -5,3 +5,4 @@ # Power Management & Sleep mode obj-$(CONFIG_PM) += pm.o sleep.o obj-$(CONFIG_CPU_IDLE) += cpuidle.o +obj-$(CONFIG_PM_RUNTIME) += pm_runtime.o diff --git a/arch/sh/kernel/cpu/shmobile/pm_runtime.c b/arch/sh/kernel/cpu/shmobile/pm_runtime.c new file mode 100644 index 000000000000..d5f5285a2253 --- /dev/null +++ b/arch/sh/kernel/cpu/shmobile/pm_runtime.c @@ -0,0 +1,304 @@ +/* + * arch/sh/kernel/cpu/shmobile/pm_runtime.c + * + * Runtime PM 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 +#include +#include +#include +#include +#include +#include + +static DEFINE_SPINLOCK(hwblk_lock); +static LIST_HEAD(hwblk_idle_list); +static struct work_struct hwblk_work; + +extern struct hwblk_info *hwblk_info; + +static void platform_pm_runtime_not_idle(struct platform_device *pdev) +{ + unsigned long flags; + + /* remove device from idle list */ + spin_lock_irqsave(&hwblk_lock, flags); + if (test_bit(PDEV_ARCHDATA_FLAG_IDLE, &pdev->archdata.flags)) { + list_del(&pdev->archdata.entry); + __clear_bit(PDEV_ARCHDATA_FLAG_IDLE, &pdev->archdata.flags); + } + spin_unlock_irqrestore(&hwblk_lock, flags); +} + +static int __platform_pm_runtime_resume(struct platform_device *pdev) +{ + struct device *d = &pdev->dev; + struct pdev_archdata *ad = &pdev->archdata; + int hwblk = ad->hwblk_id; + int ret = -ENOSYS; + + dev_dbg(d, "__platform_pm_runtime_resume() [%d]\n", hwblk); + + if (d->driver && d->driver->pm && d->driver->pm->runtime_resume) { + hwblk_enable(hwblk_info, hwblk); + ret = 0; + + if (test_bit(PDEV_ARCHDATA_FLAG_SUSP, &ad->flags)) { + ret = d->driver->pm->runtime_resume(d); + if (!ret) + clear_bit(PDEV_ARCHDATA_FLAG_SUSP, &ad->flags); + else + hwblk_disable(hwblk_info, hwblk); + } + } + + dev_dbg(d, "__platform_pm_runtime_resume() [%d] - returns %d\n", + hwblk, ret); + + return ret; +} + +static int __platform_pm_runtime_suspend(struct platform_device *pdev) +{ + struct device *d = &pdev->dev; + struct pdev_archdata *ad = &pdev->archdata; + int hwblk = ad->hwblk_id; + int ret = -ENOSYS; + + dev_dbg(d, "__platform_pm_runtime_suspend() [%d]\n", hwblk); + + if (d->driver && d->driver->pm && d->driver->pm->runtime_suspend) { + BUG_ON(!test_bit(PDEV_ARCHDATA_FLAG_IDLE, &ad->flags)); + + hwblk_enable(hwblk_info, hwblk); + ret = d->driver->pm->runtime_suspend(d); + hwblk_disable(hwblk_info, hwblk); + + if (!ret) { + set_bit(PDEV_ARCHDATA_FLAG_SUSP, &ad->flags); + platform_pm_runtime_not_idle(pdev); + hwblk_cnt_dec(hwblk_info, hwblk, HWBLK_CNT_IDLE); + } + } + + dev_dbg(d, "__platform_pm_runtime_suspend() [%d] - returns %d\n", + hwblk, ret); + + return ret; +} + +static void platform_pm_runtime_work(struct work_struct *work) +{ + struct platform_device *pdev; + unsigned long flags; + int ret; + + /* go through the idle list and suspend one device at a time */ + do { + spin_lock_irqsave(&hwblk_lock, flags); + if (list_empty(&hwblk_idle_list)) + pdev = NULL; + else + pdev = list_first_entry(&hwblk_idle_list, + struct platform_device, + archdata.entry); + spin_unlock_irqrestore(&hwblk_lock, flags); + + if (pdev) { + mutex_lock(&pdev->archdata.mutex); + ret = __platform_pm_runtime_suspend(pdev); + + /* at this point the platform device may be: + * suspended: ret = 0, FLAG_SUSP set, clock stopped + * failed: ret < 0, FLAG_IDLE set, clock stopped + */ + mutex_unlock(&pdev->archdata.mutex); + } else { + ret = -ENODEV; + } + } while (!ret); +} + +/* this function gets called from cpuidle context when all devices in the + * main power domain are unused but some are counted as idle, ie the hwblk + * counter values are (HWBLK_CNT_USAGE == 0) && (HWBLK_CNT_IDLE != 0) + */ +void platform_pm_runtime_suspend_idle(void) +{ + queue_work(pm_wq, &hwblk_work); +} + +int platform_pm_runtime_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct pdev_archdata *ad = &pdev->archdata; + unsigned long flags; + int hwblk = ad->hwblk_id; + int ret = 0; + + dev_dbg(dev, "platform_pm_runtime_suspend() [%d]\n", hwblk); + + /* ignore off-chip platform devices */ + if (!hwblk) + goto out; + + /* interrupt context not allowed */ + might_sleep(); + + /* catch misconfigured drivers not starting with resume */ + if (test_bit(PDEV_ARCHDATA_FLAG_INIT, &pdev->archdata.flags)) { + ret = -EINVAL; + goto out; + } + + /* serialize */ + mutex_lock(&ad->mutex); + + /* disable clock */ + hwblk_disable(hwblk_info, hwblk); + + /* put device on idle list */ + spin_lock_irqsave(&hwblk_lock, flags); + list_add_tail(&pdev->archdata.entry, &hwblk_idle_list); + __set_bit(PDEV_ARCHDATA_FLAG_IDLE, &pdev->archdata.flags); + spin_unlock_irqrestore(&hwblk_lock, flags); + + /* increase idle count */ + hwblk_cnt_inc(hwblk_info, hwblk, HWBLK_CNT_IDLE); + + /* at this point the platform device is: + * idle: ret = 0, FLAG_IDLE set, clock stopped + */ + mutex_unlock(&ad->mutex); + +out: + dev_dbg(dev, "platform_pm_runtime_suspend() [%d] returns %d\n", + hwblk, ret); + + return ret; +} + +int platform_pm_runtime_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct pdev_archdata *ad = &pdev->archdata; + int hwblk = ad->hwblk_id; + int ret = 0; + + dev_dbg(dev, "platform_pm_runtime_resume() [%d]\n", hwblk); + + /* ignore off-chip platform devices */ + if (!hwblk) + goto out; + + /* interrupt context not allowed */ + might_sleep(); + + /* serialize */ + mutex_lock(&ad->mutex); + + /* make sure device is removed from idle list */ + platform_pm_runtime_not_idle(pdev); + + /* decrease idle count */ + if (!test_bit(PDEV_ARCHDATA_FLAG_INIT, &pdev->archdata.flags) && + !test_bit(PDEV_ARCHDATA_FLAG_SUSP, &pdev->archdata.flags)) + hwblk_cnt_dec(hwblk_info, hwblk, HWBLK_CNT_IDLE); + + /* resume the device if needed */ + ret = __platform_pm_runtime_resume(pdev); + + /* the driver has been initialized now, so clear the init flag */ + clear_bit(PDEV_ARCHDATA_FLAG_INIT, &pdev->archdata.flags); + + /* at this point the platform device may be: + * resumed: ret = 0, flags = 0, clock started + * failed: ret < 0, FLAG_SUSP set, clock stopped + */ + mutex_unlock(&ad->mutex); +out: + dev_dbg(dev, "platform_pm_runtime_resume() [%d] returns %d\n", + hwblk, ret); + + return ret; +} + +int platform_pm_runtime_idle(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + int hwblk = pdev->archdata.hwblk_id; + int ret = 0; + + dev_dbg(dev, "platform_pm_runtime_idle() [%d]\n", hwblk); + + /* ignore off-chip platform devices */ + if (!hwblk) + goto out; + + /* interrupt context not allowed, use pm_runtime_put()! */ + might_sleep(); + + /* suspend synchronously to disable clocks immediately */ + ret = pm_runtime_suspend(dev); +out: + dev_dbg(dev, "platform_pm_runtime_idle() [%d] done!\n", hwblk); + return ret; +} + +static int __devinit platform_bus_notify(struct notifier_block *nb, + unsigned long action, void *data) +{ + struct device *dev = data; + struct platform_device *pdev = to_platform_device(dev); + int hwblk = pdev->archdata.hwblk_id; + + /* ignore off-chip platform devices */ + if (!hwblk) + return 0; + + switch (action) { + case BUS_NOTIFY_ADD_DEVICE: + INIT_LIST_HEAD(&pdev->archdata.entry); + mutex_init(&pdev->archdata.mutex); + /* platform devices without drivers should be disabled */ + hwblk_enable(hwblk_info, hwblk); + hwblk_disable(hwblk_info, hwblk); + /* make sure driver re-inits itself once */ + __set_bit(PDEV_ARCHDATA_FLAG_INIT, &pdev->archdata.flags); + break; + /* TODO: add BUS_NOTIFY_BIND_DRIVER and increase idle count */ + case BUS_NOTIFY_BOUND_DRIVER: + /* keep track of number of devices in use per hwblk */ + hwblk_cnt_inc(hwblk_info, hwblk, HWBLK_CNT_DEVICES); + break; + case BUS_NOTIFY_UNBOUND_DRIVER: + /* keep track of number of devices in use per hwblk */ + hwblk_cnt_dec(hwblk_info, hwblk, HWBLK_CNT_DEVICES); + /* make sure driver re-inits itself once */ + __set_bit(PDEV_ARCHDATA_FLAG_INIT, &pdev->archdata.flags); + break; + case BUS_NOTIFY_DEL_DEVICE: + break; + } + return 0; +} + +static struct notifier_block platform_bus_notifier = { + .notifier_call = platform_bus_notify +}; + +static int __init sh_pm_runtime_init(void) +{ + INIT_WORK(&hwblk_work, platform_pm_runtime_work); + + bus_register_notifier(&platform_bus_type, &platform_bus_notifier); + return 0; +} + +core_initcall(sh_pm_runtime_init); -- cgit v1.2.2 From a62926fe4b76d539b77ee53197040153d43916f6 Mon Sep 17 00:00:00 2001 From: Paul Mundt Date: Tue, 18 Aug 2009 10:41:06 +0900 Subject: sh: Fix section mismatch in platform bus notifier. The runtime PM for SH-Mobile code had platform_bus_notify() as __devinit, which is rather bogus. Kill off the annotation, which subsequently silences the section mismatch warnings. Signed-off-by: Paul Mundt --- arch/sh/kernel/cpu/shmobile/pm_runtime.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'arch/sh/kernel/cpu/shmobile') diff --git a/arch/sh/kernel/cpu/shmobile/pm_runtime.c b/arch/sh/kernel/cpu/shmobile/pm_runtime.c index d5f5285a2253..7c615b17e209 100644 --- a/arch/sh/kernel/cpu/shmobile/pm_runtime.c +++ b/arch/sh/kernel/cpu/shmobile/pm_runtime.c @@ -251,8 +251,8 @@ out: return ret; } -static int __devinit platform_bus_notify(struct notifier_block *nb, - unsigned long action, void *data) +static int platform_bus_notify(struct notifier_block *nb, + unsigned long action, void *data) { struct device *dev = data; struct platform_device *pdev = to_platform_device(dev); @@ -300,5 +300,4 @@ static int __init sh_pm_runtime_init(void) bus_register_notifier(&platform_bus_type, &platform_bus_notifier); return 0; } - core_initcall(sh_pm_runtime_init); -- cgit v1.2.2