diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2009-09-18 09:43:09 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2009-09-18 09:43:09 -0700 |
commit | 515b696b282f856c3ad1679ccd658120faa387d0 (patch) | |
tree | d9d7c1185c396617f128ca23463062308d11393b /arch/sh/kernel/cpu/shmobile/pm_runtime.c | |
parent | fa877c71e2136bd682b45022c96d5e073ced9f58 (diff) | |
parent | 064a16dc41be879d12bd5de5d2f9d38d890e0ee7 (diff) | |
download | linux-515b696b282f856c3ad1679ccd658120faa387d0.tar.bz2 |
Merge git://git.kernel.org/pub/scm/linux/kernel/git/lethal/sh-2.6
* git://git.kernel.org/pub/scm/linux/kernel/git/lethal/sh-2.6: (262 commits)
sh: mach-ecovec24: Add user debug switch support
sh: Kill off unused se_skipped in alignment trap notification code.
sh: Wire up HAVE_SYSCALL_TRACEPOINTS.
video: sh_mobile_lcdcfb: use both register sets for display panning
video: sh_mobile_lcdcfb: implement display panning
sh: Fix up sh7705 flush_dcache_page() build.
sh: kfr2r09: document the PLL/FLL <-> RF relationship.
sh: mach-ecovec24: need asm/clock.h.
sh: mach-ecovec24: deassert usb irq on boot.
sh: Add KEYSC support for EcoVec24
sh: add kycr2_delay for sh_keysc
sh: cpufreq: Include CPU id in info messages.
sh: multi-evt support for SH-X3 proto CPU.
sh: clkfwk: remove bogus set_bus_parent() from SH7709.
sh: Fix the indication point of the liquid crystal of AP-325RXA(AP3300)
sh: Add EcoVec24 romImage defconfig
sh: USB disable process is needed if romImage boot for EcoVec24
sh: EcoVec24: add HIZA setting for LED
sh: EcoVec24: write MAC address in boot
sh: Add romImage support for EcoVec24
...
Diffstat (limited to 'arch/sh/kernel/cpu/shmobile/pm_runtime.c')
-rw-r--r-- | arch/sh/kernel/cpu/shmobile/pm_runtime.c | 303 |
1 files changed, 303 insertions, 0 deletions
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..7c615b17e209 --- /dev/null +++ b/arch/sh/kernel/cpu/shmobile/pm_runtime.c @@ -0,0 +1,303 @@ +/* + * 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 <linux/init.h> +#include <linux/kernel.h> +#include <linux/io.h> +#include <linux/pm_runtime.h> +#include <linux/platform_device.h> +#include <linux/mutex.h> +#include <asm/hwblk.h> + +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 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); |