Path: blob/master/arch/sh/kernel/cpu/shmobile/pm_runtime.c
17292 views
/*1* arch/sh/kernel/cpu/shmobile/pm_runtime.c2*3* Runtime PM support code for SuperH Mobile4*5* Copyright (C) 2009 Magnus Damm6*7* This file is subject to the terms and conditions of the GNU General Public8* License. See the file "COPYING" in the main directory of this archive9* for more details.10*/11#include <linux/init.h>12#include <linux/kernel.h>13#include <linux/io.h>14#include <linux/pm_runtime.h>15#include <linux/platform_device.h>16#include <linux/mutex.h>17#include <asm/hwblk.h>1819static DEFINE_SPINLOCK(hwblk_lock);20static LIST_HEAD(hwblk_idle_list);21static struct work_struct hwblk_work;2223extern struct hwblk_info *hwblk_info;2425static void platform_pm_runtime_not_idle(struct platform_device *pdev)26{27unsigned long flags;2829/* remove device from idle list */30spin_lock_irqsave(&hwblk_lock, flags);31if (test_bit(PDEV_ARCHDATA_FLAG_IDLE, &pdev->archdata.flags)) {32list_del(&pdev->archdata.entry);33__clear_bit(PDEV_ARCHDATA_FLAG_IDLE, &pdev->archdata.flags);34}35spin_unlock_irqrestore(&hwblk_lock, flags);36}3738static int __platform_pm_runtime_resume(struct platform_device *pdev)39{40struct device *d = &pdev->dev;41struct pdev_archdata *ad = &pdev->archdata;42int hwblk = ad->hwblk_id;43int ret = -ENOSYS;4445dev_dbg(d, "__platform_pm_runtime_resume() [%d]\n", hwblk);4647if (d->driver) {48hwblk_enable(hwblk_info, hwblk);49ret = 0;5051if (test_bit(PDEV_ARCHDATA_FLAG_SUSP, &ad->flags)) {52if (d->driver->pm && d->driver->pm->runtime_resume)53ret = d->driver->pm->runtime_resume(d);5455if (!ret)56clear_bit(PDEV_ARCHDATA_FLAG_SUSP, &ad->flags);57else58hwblk_disable(hwblk_info, hwblk);59}60}6162dev_dbg(d, "__platform_pm_runtime_resume() [%d] - returns %d\n",63hwblk, ret);6465return ret;66}6768static int __platform_pm_runtime_suspend(struct platform_device *pdev)69{70struct device *d = &pdev->dev;71struct pdev_archdata *ad = &pdev->archdata;72int hwblk = ad->hwblk_id;73int ret = -ENOSYS;7475dev_dbg(d, "__platform_pm_runtime_suspend() [%d]\n", hwblk);7677if (d->driver) {78BUG_ON(!test_bit(PDEV_ARCHDATA_FLAG_IDLE, &ad->flags));79ret = 0;8081if (d->driver->pm && d->driver->pm->runtime_suspend) {82hwblk_enable(hwblk_info, hwblk);83ret = d->driver->pm->runtime_suspend(d);84hwblk_disable(hwblk_info, hwblk);85}8687if (!ret) {88set_bit(PDEV_ARCHDATA_FLAG_SUSP, &ad->flags);89platform_pm_runtime_not_idle(pdev);90hwblk_cnt_dec(hwblk_info, hwblk, HWBLK_CNT_IDLE);91}92}9394dev_dbg(d, "__platform_pm_runtime_suspend() [%d] - returns %d\n",95hwblk, ret);9697return ret;98}99100static void platform_pm_runtime_work(struct work_struct *work)101{102struct platform_device *pdev;103unsigned long flags;104int ret;105106/* go through the idle list and suspend one device at a time */107do {108spin_lock_irqsave(&hwblk_lock, flags);109if (list_empty(&hwblk_idle_list))110pdev = NULL;111else112pdev = list_first_entry(&hwblk_idle_list,113struct platform_device,114archdata.entry);115spin_unlock_irqrestore(&hwblk_lock, flags);116117if (pdev) {118mutex_lock(&pdev->archdata.mutex);119ret = __platform_pm_runtime_suspend(pdev);120121/* at this point the platform device may be:122* suspended: ret = 0, FLAG_SUSP set, clock stopped123* failed: ret < 0, FLAG_IDLE set, clock stopped124*/125mutex_unlock(&pdev->archdata.mutex);126} else {127ret = -ENODEV;128}129} while (!ret);130}131132/* this function gets called from cpuidle context when all devices in the133* main power domain are unused but some are counted as idle, ie the hwblk134* counter values are (HWBLK_CNT_USAGE == 0) && (HWBLK_CNT_IDLE != 0)135*/136void platform_pm_runtime_suspend_idle(void)137{138queue_work(pm_wq, &hwblk_work);139}140141static int default_platform_runtime_suspend(struct device *dev)142{143struct platform_device *pdev = to_platform_device(dev);144struct pdev_archdata *ad = &pdev->archdata;145unsigned long flags;146int hwblk = ad->hwblk_id;147int ret = 0;148149dev_dbg(dev, "%s() [%d]\n", __func__, hwblk);150151/* ignore off-chip platform devices */152if (!hwblk)153goto out;154155/* interrupt context not allowed */156might_sleep();157158/* catch misconfigured drivers not starting with resume */159if (test_bit(PDEV_ARCHDATA_FLAG_INIT, &ad->flags)) {160ret = -EINVAL;161goto out;162}163164/* serialize */165mutex_lock(&ad->mutex);166167/* disable clock */168hwblk_disable(hwblk_info, hwblk);169170/* put device on idle list */171spin_lock_irqsave(&hwblk_lock, flags);172list_add_tail(&ad->entry, &hwblk_idle_list);173__set_bit(PDEV_ARCHDATA_FLAG_IDLE, &ad->flags);174spin_unlock_irqrestore(&hwblk_lock, flags);175176/* increase idle count */177hwblk_cnt_inc(hwblk_info, hwblk, HWBLK_CNT_IDLE);178179/* at this point the platform device is:180* idle: ret = 0, FLAG_IDLE set, clock stopped181*/182mutex_unlock(&ad->mutex);183184out:185dev_dbg(dev, "%s() [%d] returns %d\n",186__func__, hwblk, ret);187188return ret;189}190191static int default_platform_runtime_resume(struct device *dev)192{193struct platform_device *pdev = to_platform_device(dev);194struct pdev_archdata *ad = &pdev->archdata;195int hwblk = ad->hwblk_id;196int ret = 0;197198dev_dbg(dev, "%s() [%d]\n", __func__, hwblk);199200/* ignore off-chip platform devices */201if (!hwblk)202goto out;203204/* interrupt context not allowed */205might_sleep();206207/* serialize */208mutex_lock(&ad->mutex);209210/* make sure device is removed from idle list */211platform_pm_runtime_not_idle(pdev);212213/* decrease idle count */214if (!test_bit(PDEV_ARCHDATA_FLAG_INIT, &pdev->archdata.flags) &&215!test_bit(PDEV_ARCHDATA_FLAG_SUSP, &pdev->archdata.flags))216hwblk_cnt_dec(hwblk_info, hwblk, HWBLK_CNT_IDLE);217218/* resume the device if needed */219ret = __platform_pm_runtime_resume(pdev);220221/* the driver has been initialized now, so clear the init flag */222clear_bit(PDEV_ARCHDATA_FLAG_INIT, &pdev->archdata.flags);223224/* at this point the platform device may be:225* resumed: ret = 0, flags = 0, clock started226* failed: ret < 0, FLAG_SUSP set, clock stopped227*/228mutex_unlock(&ad->mutex);229out:230dev_dbg(dev, "%s() [%d] returns %d\n",231__func__, hwblk, ret);232233return ret;234}235236static int default_platform_runtime_idle(struct device *dev)237{238struct platform_device *pdev = to_platform_device(dev);239int hwblk = pdev->archdata.hwblk_id;240int ret = 0;241242dev_dbg(dev, "%s() [%d]\n", __func__, hwblk);243244/* ignore off-chip platform devices */245if (!hwblk)246goto out;247248/* interrupt context not allowed, use pm_runtime_put()! */249might_sleep();250251/* suspend synchronously to disable clocks immediately */252ret = pm_runtime_suspend(dev);253out:254dev_dbg(dev, "%s() [%d] done!\n", __func__, hwblk);255return ret;256}257258static struct dev_power_domain default_power_domain = {259.ops = {260.runtime_suspend = default_platform_runtime_suspend,261.runtime_resume = default_platform_runtime_resume,262.runtime_idle = default_platform_runtime_idle,263USE_PLATFORM_PM_SLEEP_OPS264},265};266267static int platform_bus_notify(struct notifier_block *nb,268unsigned long action, void *data)269{270struct device *dev = data;271struct platform_device *pdev = to_platform_device(dev);272int hwblk = pdev->archdata.hwblk_id;273274/* ignore off-chip platform devices */275if (!hwblk)276return 0;277278switch (action) {279case BUS_NOTIFY_ADD_DEVICE:280INIT_LIST_HEAD(&pdev->archdata.entry);281mutex_init(&pdev->archdata.mutex);282/* platform devices without drivers should be disabled */283hwblk_enable(hwblk_info, hwblk);284hwblk_disable(hwblk_info, hwblk);285/* make sure driver re-inits itself once */286__set_bit(PDEV_ARCHDATA_FLAG_INIT, &pdev->archdata.flags);287dev->pwr_domain = &default_power_domain;288break;289/* TODO: add BUS_NOTIFY_BIND_DRIVER and increase idle count */290case BUS_NOTIFY_BOUND_DRIVER:291/* keep track of number of devices in use per hwblk */292hwblk_cnt_inc(hwblk_info, hwblk, HWBLK_CNT_DEVICES);293break;294case BUS_NOTIFY_UNBOUND_DRIVER:295/* keep track of number of devices in use per hwblk */296hwblk_cnt_dec(hwblk_info, hwblk, HWBLK_CNT_DEVICES);297/* make sure driver re-inits itself once */298__set_bit(PDEV_ARCHDATA_FLAG_INIT, &pdev->archdata.flags);299break;300case BUS_NOTIFY_DEL_DEVICE:301dev->pwr_domain = NULL;302break;303}304return 0;305}306307static struct notifier_block platform_bus_notifier = {308.notifier_call = platform_bus_notify309};310311static int __init sh_pm_runtime_init(void)312{313INIT_WORK(&hwblk_work, platform_pm_runtime_work);314315bus_register_notifier(&platform_bus_type, &platform_bus_notifier);316return 0;317}318core_initcall(sh_pm_runtime_init);319320321