123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320 |
- /*
- * 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) {
- hwblk_enable(hwblk_info, hwblk);
- ret = 0;
- if (test_bit(PDEV_ARCHDATA_FLAG_SUSP, &ad->flags)) {
- if (d->driver->pm && d->driver->pm->runtime_resume)
- 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) {
- BUG_ON(!test_bit(PDEV_ARCHDATA_FLAG_IDLE, &ad->flags));
- ret = 0;
- if (d->driver->pm && d->driver->pm->runtime_suspend) {
- 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);
- }
- static int default_platform_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, "%s() [%d]\n", __func__, 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, &ad->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(&ad->entry, &hwblk_idle_list);
- __set_bit(PDEV_ARCHDATA_FLAG_IDLE, &ad->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, "%s() [%d] returns %d\n",
- __func__, hwblk, ret);
- return ret;
- }
- static int default_platform_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, "%s() [%d]\n", __func__, 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, "%s() [%d] returns %d\n",
- __func__, hwblk, ret);
- return ret;
- }
- static int default_platform_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, "%s() [%d]\n", __func__, 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, "%s() [%d] done!\n", __func__, hwblk);
- return ret;
- }
- static struct dev_power_domain default_power_domain = {
- .ops = {
- .runtime_suspend = default_platform_runtime_suspend,
- .runtime_resume = default_platform_runtime_resume,
- .runtime_idle = default_platform_runtime_idle,
- USE_PLATFORM_PM_SLEEP_OPS
- },
- };
- 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);
- dev->pwr_domain = &default_power_domain;
- 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:
- dev->pwr_domain = NULL;
- 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);
|