From 77f827de07432a74821cf0f831d699544b2d474f Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Mon, 6 Aug 2012 01:39:57 +0200 Subject: PM / Domains: Add power off/on function for system core suspend stage Introduce function pm_genpd_syscore_switch() and two wrappers around it, pm_genpd_syscore_poweroff() and pm_genpd_syscore_poweron(), allowing the callers to let the generic PM domains framework know that the given device is not necessary any more and its PM domain can be turned off (the former) or that the given device will be required immediately, so its PM domain has to be turned on (the latter) during the system core (syscore) stage of system suspend (or hibernation) and resume. These functions will be used for handling devices registered as clock sources and clock event devices that belong to PM domains. Signed-off-by: Rafael J. Wysocki --- include/linux/pm_domain.h | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) (limited to 'include/linux') diff --git a/include/linux/pm_domain.h b/include/linux/pm_domain.h index a7d6172922d4..ab83cf3dfaac 100644 --- a/include/linux/pm_domain.h +++ b/include/linux/pm_domain.h @@ -258,4 +258,20 @@ static inline void genpd_queue_power_off_work(struct generic_pm_domain *gpd) {} static inline void pm_genpd_poweroff_unused(void) {} #endif +#ifdef CONFIG_PM_GENERIC_DOMAINS_SLEEP +extern void pm_genpd_syscore_switch(struct device *dev, bool suspend); +#else +static inline void pm_genpd_syscore_switch(struct device *dev, bool suspend) {} +#endif + +static inline void pm_genpd_syscore_poweroff(struct device *dev) +{ + pm_genpd_syscore_switch(dev, true); +} + +static inline void pm_genpd_syscore_poweron(struct device *dev) +{ + pm_genpd_syscore_switch(dev, false); +} + #endif /* _LINUX_PM_DOMAIN_H */ -- cgit v1.2.3 From adc78e6b9946a4b22e22403d961f3b03c469e5d3 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Mon, 6 Aug 2012 01:40:41 +0200 Subject: timekeeping: Add suspend and resume of clock event devices Some clock event devices, for example such that belong to PM domains, need to be handled in a spcial way during the timekeeping suspend and resume (which takes place in the system core, or "syscore", stages of system power transitions) in analogy with clock sources. Introduce .suspend() and .resume() callbacks for clock event devices that will be executed by timekeeping_suspend/_resume(), respectively, next the the clock sources' .suspend() and .resume() callbacks. Signed-off-by: Rafael J. Wysocki --- include/linux/clockchips.h | 8 ++++++++ kernel/time/clockevents.c | 24 ++++++++++++++++++++++++ kernel/time/timekeeping.c | 2 ++ 3 files changed, 34 insertions(+) (limited to 'include/linux') diff --git a/include/linux/clockchips.h b/include/linux/clockchips.h index acba894374a1..8a7096fcb01e 100644 --- a/include/linux/clockchips.h +++ b/include/linux/clockchips.h @@ -97,6 +97,8 @@ struct clock_event_device { void (*broadcast)(const struct cpumask *mask); void (*set_mode)(enum clock_event_mode mode, struct clock_event_device *); + void (*suspend)(struct clock_event_device *); + void (*resume)(struct clock_event_device *); unsigned long min_delta_ticks; unsigned long max_delta_ticks; @@ -156,6 +158,9 @@ clockevents_calc_mult_shift(struct clock_event_device *ce, u32 freq, u32 minsec) freq, minsec); } +extern void clockevents_suspend(void); +extern void clockevents_resume(void); + #ifdef CONFIG_GENERIC_CLOCKEVENTS extern void clockevents_notify(unsigned long reason, void *arg); #else @@ -164,6 +169,9 @@ extern void clockevents_notify(unsigned long reason, void *arg); #else /* CONFIG_GENERIC_CLOCKEVENTS_BUILD */ +static inline void clockevents_suspend(void) {} +static inline void clockevents_resume(void) {} + #define clockevents_notify(reason, arg) do { } while (0) #endif diff --git a/kernel/time/clockevents.c b/kernel/time/clockevents.c index 7e1ce012a851..30b6de0d977c 100644 --- a/kernel/time/clockevents.c +++ b/kernel/time/clockevents.c @@ -397,6 +397,30 @@ void clockevents_exchange_device(struct clock_event_device *old, local_irq_restore(flags); } +/** + * clockevents_suspend - suspend clock devices + */ +void clockevents_suspend(void) +{ + struct clock_event_device *dev; + + list_for_each_entry_reverse(dev, &clockevent_devices, list) + if (dev->suspend) + dev->suspend(dev); +} + +/** + * clockevents_resume - resume clock devices + */ +void clockevents_resume(void) +{ + struct clock_event_device *dev; + + list_for_each_entry(dev, &clockevent_devices, list) + if (dev->resume) + dev->resume(dev); +} + #ifdef CONFIG_GENERIC_CLOCKEVENTS /** * clockevents_notify - notification about relevant events diff --git a/kernel/time/timekeeping.c b/kernel/time/timekeeping.c index 34e5eac81424..312a675cb240 100644 --- a/kernel/time/timekeeping.c +++ b/kernel/time/timekeeping.c @@ -773,6 +773,7 @@ static void timekeeping_resume(void) read_persistent_clock(&ts); + clockevents_resume(); clocksource_resume(); write_seqlock_irqsave(&tk->lock, flags); @@ -832,6 +833,7 @@ static int timekeeping_suspend(void) clockevents_notify(CLOCK_EVT_NOTIFY_SUSPEND, NULL); clocksource_suspend(); + clockevents_suspend(); return 0; } -- cgit v1.2.3 From bed2b42d9f0b411f384c5619870ab0fea5dd116b Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Mon, 6 Aug 2012 01:45:11 +0200 Subject: PM / Runtime: Allow helpers to be called by early platform drivers Runtime PM helper functions, like pm_runtime_get_sync(), cannot be called by early platform device drivers, because the devices' power management locks are not initialized at that time. This is quite inconvenient, so modify early_platform_add_devices() to initialize the devices power management locks as appropriate and make sure that they won't be initialized more than once if an early platform device is going to be used as a regular one later. Signed-off-by: Rafael J. Wysocki --- drivers/base/platform.c | 2 ++ drivers/base/power/power.h | 18 ++++++++++++++++-- include/linux/pm.h | 1 + 3 files changed, 19 insertions(+), 2 deletions(-) (limited to 'include/linux') diff --git a/drivers/base/platform.c b/drivers/base/platform.c index a1a722502587..d51514b79efe 100644 --- a/drivers/base/platform.c +++ b/drivers/base/platform.c @@ -22,6 +22,7 @@ #include #include "base.h" +#include "power/power.h" #define to_platform_driver(drv) (container_of((drv), struct platform_driver, \ driver)) @@ -948,6 +949,7 @@ void __init early_platform_add_devices(struct platform_device **devs, int num) dev = &devs[i]->dev; if (!dev->devres_head.next) { + pm_runtime_early_init(dev); INIT_LIST_HEAD(&dev->devres_head); list_add_tail(&dev->devres_head, &early_platform_device_list); diff --git a/drivers/base/power/power.h b/drivers/base/power/power.h index 8a0dcc7f98f9..0dbfdf4419af 100644 --- a/drivers/base/power/power.h +++ b/drivers/base/power/power.h @@ -2,17 +2,31 @@ static inline void device_pm_init_common(struct device *dev) { - spin_lock_init(&dev->power.lock); - dev->power.power_state = PMSG_INVALID; + if (!dev->power.early_init) { + spin_lock_init(&dev->power.lock); + dev->power.power_state = PMSG_INVALID; + dev->power.early_init = true; + } } #ifdef CONFIG_PM_RUNTIME +static inline void pm_runtime_early_init(struct device *dev) +{ + dev->power.disable_depth = 1; + device_pm_init_common(dev); +} + extern void pm_runtime_init(struct device *dev); extern void pm_runtime_remove(struct device *dev); #else /* !CONFIG_PM_RUNTIME */ +static inline void pm_runtime_early_init(struct device *dev) +{ + device_pm_init_common(dev); +} + static inline void pm_runtime_init(struct device *dev) {} static inline void pm_runtime_remove(struct device *dev) {} diff --git a/include/linux/pm.h b/include/linux/pm.h index f067e60a3832..716517af1543 100644 --- a/include/linux/pm.h +++ b/include/linux/pm.h @@ -510,6 +510,7 @@ struct dev_pm_info { bool is_prepared:1; /* Owned by the PM core */ bool is_suspended:1; /* Ditto */ bool ignore_children:1; + bool early_init:1; /* Owned by the PM core */ spinlock_t lock; #ifdef CONFIG_PM_SLEEP struct list_head entry; -- cgit v1.2.3 From 6fb28badf207a6d8a78906353772e1c3f560a977 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Mon, 6 Aug 2012 01:45:54 +0200 Subject: PM / Domains: Rename the always_on device flag to syscore The always_on device flag is used to mark the devices (belonging to a PM domain) that should never be turned off, except for the system core (syscore) suspend/hibernation and resume stages. Change name of that flag to "syscore" to better reflect its purpose. Signed-off-by: Rafael J. Wysocki Acked-by: Magnus Damm --- drivers/base/power/domain.c | 24 ++++++++++++------------ drivers/clocksource/sh_cmt.c | 2 +- drivers/clocksource/sh_mtu2.c | 2 +- drivers/clocksource/sh_tmu.c | 2 +- include/linux/pm_domain.h | 6 +++--- 5 files changed, 18 insertions(+), 18 deletions(-) (limited to 'include/linux') diff --git a/drivers/base/power/domain.c b/drivers/base/power/domain.c index 515c8ecf01ce..15234ecd7edb 100644 --- a/drivers/base/power/domain.c +++ b/drivers/base/power/domain.c @@ -436,7 +436,7 @@ static int pm_genpd_poweroff(struct generic_pm_domain *genpd) not_suspended = 0; list_for_each_entry(pdd, &genpd->dev_list, list_node) if (pdd->dev->driver && (!pm_runtime_suspended(pdd->dev) - || pdd->dev->power.irq_safe || to_gpd_data(pdd)->always_on)) + || pdd->dev->power.irq_safe || to_gpd_data(pdd)->syscore)) not_suspended++; if (not_suspended > genpd->in_progress) @@ -578,7 +578,7 @@ static int pm_genpd_runtime_suspend(struct device *dev) might_sleep_if(!genpd->dev_irq_safe); - if (dev_gpd_data(dev)->always_on) + if (dev_gpd_data(dev)->syscore) return -EBUSY; stop_ok = genpd->gov ? genpd->gov->stop_ok : NULL; @@ -983,7 +983,7 @@ static int pm_genpd_suspend_noirq(struct device *dev) if (IS_ERR(genpd)) return -EINVAL; - if (genpd->suspend_power_off || dev_gpd_data(dev)->always_on + if (genpd->suspend_power_off || dev_gpd_data(dev)->syscore || (dev->power.wakeup_path && genpd_dev_active_wakeup(genpd, dev))) return 0; @@ -1016,7 +1016,7 @@ static int pm_genpd_resume_noirq(struct device *dev) if (IS_ERR(genpd)) return -EINVAL; - if (genpd->suspend_power_off || dev_gpd_data(dev)->always_on + if (genpd->suspend_power_off || dev_gpd_data(dev)->syscore || (dev->power.wakeup_path && genpd_dev_active_wakeup(genpd, dev))) return 0; @@ -1136,7 +1136,7 @@ static int pm_genpd_freeze_noirq(struct device *dev) if (IS_ERR(genpd)) return -EINVAL; - return genpd->suspend_power_off || dev_gpd_data(dev)->always_on ? + return genpd->suspend_power_off || dev_gpd_data(dev)->syscore ? 0 : genpd_stop_dev(genpd, dev); } @@ -1157,7 +1157,7 @@ static int pm_genpd_thaw_noirq(struct device *dev) if (IS_ERR(genpd)) return -EINVAL; - return genpd->suspend_power_off || dev_gpd_data(dev)->always_on ? + return genpd->suspend_power_off || dev_gpd_data(dev)->syscore ? 0 : genpd_start_dev(genpd, dev); } @@ -1253,7 +1253,7 @@ static int pm_genpd_restore_noirq(struct device *dev) pm_genpd_sync_poweron(genpd); - return dev_gpd_data(dev)->always_on ? 0 : genpd_start_dev(genpd, dev); + return dev_gpd_data(dev)->syscore ? 0 : genpd_start_dev(genpd, dev); } /** @@ -1526,11 +1526,11 @@ int pm_genpd_remove_device(struct generic_pm_domain *genpd, } /** - * pm_genpd_dev_always_on - Set/unset the "always on" flag for a given device. + * pm_genpd_dev_syscore - Set/unset the "syscore" flag for a given device. * @dev: Device to set/unset the flag for. - * @val: The new value of the device's "always on" flag. + * @val: The new value of the device's "syscore" flag. */ -void pm_genpd_dev_always_on(struct device *dev, bool val) +void pm_genpd_dev_syscore(struct device *dev, bool val) { struct pm_subsys_data *psd; unsigned long flags; @@ -1539,11 +1539,11 @@ void pm_genpd_dev_always_on(struct device *dev, bool val) psd = dev_to_psd(dev); if (psd && psd->domain_data) - to_gpd_data(psd->domain_data)->always_on = val; + to_gpd_data(psd->domain_data)->syscore = val; spin_unlock_irqrestore(&dev->power.lock, flags); } -EXPORT_SYMBOL_GPL(pm_genpd_dev_always_on); +EXPORT_SYMBOL_GPL(pm_genpd_dev_syscore); /** * pm_genpd_dev_need_restore - Set/unset the device's "need restore" flag. diff --git a/drivers/clocksource/sh_cmt.c b/drivers/clocksource/sh_cmt.c index c06d27fc59e8..c6fbb9f71911 100644 --- a/drivers/clocksource/sh_cmt.c +++ b/drivers/clocksource/sh_cmt.c @@ -717,7 +717,7 @@ static int __devinit sh_cmt_probe(struct platform_device *pdev) struct sh_timer_config *cfg = pdev->dev.platform_data; if (cfg->clocksource_rating || cfg->clockevent_rating) - pm_genpd_dev_always_on(&pdev->dev, true); + pm_genpd_dev_syscore(&pdev->dev, true); } if (p) { diff --git a/drivers/clocksource/sh_mtu2.c b/drivers/clocksource/sh_mtu2.c index a55bb905812e..278c18abb2ae 100644 --- a/drivers/clocksource/sh_mtu2.c +++ b/drivers/clocksource/sh_mtu2.c @@ -323,7 +323,7 @@ static int __devinit sh_mtu2_probe(struct platform_device *pdev) struct sh_timer_config *cfg = pdev->dev.platform_data; if (cfg->clockevent_rating) - pm_genpd_dev_always_on(&pdev->dev, true); + pm_genpd_dev_syscore(&pdev->dev, true); } if (p) { diff --git a/drivers/clocksource/sh_tmu.c b/drivers/clocksource/sh_tmu.c index 7d700829bb41..5319689c579c 100644 --- a/drivers/clocksource/sh_tmu.c +++ b/drivers/clocksource/sh_tmu.c @@ -453,7 +453,7 @@ static int __devinit sh_tmu_probe(struct platform_device *pdev) struct sh_timer_config *cfg = pdev->dev.platform_data; if (cfg->clocksource_rating || cfg->clockevent_rating) - pm_genpd_dev_always_on(&pdev->dev, true); + pm_genpd_dev_syscore(&pdev->dev, true); } if (p) { diff --git a/include/linux/pm_domain.h b/include/linux/pm_domain.h index ab83cf3dfaac..dab0938603fa 100644 --- a/include/linux/pm_domain.h +++ b/include/linux/pm_domain.h @@ -114,7 +114,7 @@ struct generic_pm_domain_data { struct mutex lock; unsigned int refcount; bool need_restore; - bool always_on; + bool syscore; }; #ifdef CONFIG_PM_GENERIC_DOMAINS @@ -153,7 +153,7 @@ static inline int pm_genpd_of_add_device(struct device_node *genpd_node, extern int pm_genpd_remove_device(struct generic_pm_domain *genpd, struct device *dev); -extern void pm_genpd_dev_always_on(struct device *dev, bool val); +extern void pm_genpd_dev_syscore(struct device *dev, bool val); extern void pm_genpd_dev_need_restore(struct device *dev, bool val); extern int pm_genpd_add_subdomain(struct generic_pm_domain *genpd, struct generic_pm_domain *new_subdomain); @@ -199,7 +199,7 @@ static inline int pm_genpd_remove_device(struct generic_pm_domain *genpd, { return -ENOSYS; } -static inline void pm_genpd_dev_always_on(struct device *dev, bool val) {} +static inline void pm_genpd_dev_syscore(struct device *dev, bool val) {} static inline void pm_genpd_dev_need_restore(struct device *dev, bool val) {} static inline int pm_genpd_add_subdomain(struct generic_pm_domain *genpd, struct generic_pm_domain *new_sd) -- cgit v1.2.3 From dbf374142dd7a3c394ec124ebe7339a6c412d9b6 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Mon, 6 Aug 2012 01:46:39 +0200 Subject: PM / Domains: Move syscore flag from subsys data to struct device The syscore device PM flag is used to mark the devices (belonging to a PM domain) that should never be turned off, except for the system core (syscore) suspend/hibernation and resume stages. That flag is stored in the device's struct pm_subsys_data object whose address is available from struct device. However, in some situations it may be convenient to set that flag before the device is added to a PM domain, so it is better to move it directly to the "power" member of struct device. Then, it can be checked by the routines in drivers/base/power/runtime.c and drivers/base/power/main.c, which is more straightforward. This also reduces the number of dev_gpd_data() invocations in the generic PM domains framework, so the overhead related to the syscore flag is slightly smaller. Signed-off-by: Rafael J. Wysocki Acked-by: Magnus Damm --- drivers/base/power/common.c | 15 +++++++++++++++ drivers/base/power/domain.c | 37 ++++++------------------------------- drivers/base/power/main.c | 28 ++++++++++++++++++++++++++++ drivers/base/power/runtime.c | 2 +- drivers/clocksource/sh_cmt.c | 2 +- drivers/clocksource/sh_mtu2.c | 2 +- drivers/clocksource/sh_tmu.c | 2 +- include/linux/pm.h | 5 +++++ include/linux/pm_domain.h | 3 --- 9 files changed, 58 insertions(+), 38 deletions(-) (limited to 'include/linux') diff --git a/drivers/base/power/common.c b/drivers/base/power/common.c index 39c32529b833..cf7a85134730 100644 --- a/drivers/base/power/common.c +++ b/drivers/base/power/common.c @@ -83,3 +83,18 @@ int dev_pm_put_subsys_data(struct device *dev) return ret; } EXPORT_SYMBOL_GPL(dev_pm_put_subsys_data); + +/** + * dev_pm_syscore_device - Set/unset the given device's power.syscore flag. + * @dev: Device whose flag is to be modified. + * @val: New value of the flag. + */ +void dev_pm_syscore_device(struct device *dev, bool val) +{ + unsigned long flags; + + spin_lock_irqsave(&dev->power.lock, flags); + dev->power.syscore = val; + spin_unlock_irqrestore(&dev->power.lock, flags); +} +EXPORT_SYMBOL_GPL(dev_pm_syscore_device); diff --git a/drivers/base/power/domain.c b/drivers/base/power/domain.c index 15234ecd7edb..52172754ff78 100644 --- a/drivers/base/power/domain.c +++ b/drivers/base/power/domain.c @@ -436,7 +436,7 @@ static int pm_genpd_poweroff(struct generic_pm_domain *genpd) not_suspended = 0; list_for_each_entry(pdd, &genpd->dev_list, list_node) if (pdd->dev->driver && (!pm_runtime_suspended(pdd->dev) - || pdd->dev->power.irq_safe || to_gpd_data(pdd)->syscore)) + || pdd->dev->power.irq_safe || pdd->dev->power.syscore)) not_suspended++; if (not_suspended > genpd->in_progress) @@ -578,9 +578,6 @@ static int pm_genpd_runtime_suspend(struct device *dev) might_sleep_if(!genpd->dev_irq_safe); - if (dev_gpd_data(dev)->syscore) - return -EBUSY; - stop_ok = genpd->gov ? genpd->gov->stop_ok : NULL; if (stop_ok && !stop_ok(dev)) return -EBUSY; @@ -983,7 +980,7 @@ static int pm_genpd_suspend_noirq(struct device *dev) if (IS_ERR(genpd)) return -EINVAL; - if (genpd->suspend_power_off || dev_gpd_data(dev)->syscore + if (genpd->suspend_power_off || (dev->power.wakeup_path && genpd_dev_active_wakeup(genpd, dev))) return 0; @@ -1016,7 +1013,7 @@ static int pm_genpd_resume_noirq(struct device *dev) if (IS_ERR(genpd)) return -EINVAL; - if (genpd->suspend_power_off || dev_gpd_data(dev)->syscore + if (genpd->suspend_power_off || (dev->power.wakeup_path && genpd_dev_active_wakeup(genpd, dev))) return 0; @@ -1136,8 +1133,7 @@ static int pm_genpd_freeze_noirq(struct device *dev) if (IS_ERR(genpd)) return -EINVAL; - return genpd->suspend_power_off || dev_gpd_data(dev)->syscore ? - 0 : genpd_stop_dev(genpd, dev); + return genpd->suspend_power_off ? 0 : genpd_stop_dev(genpd, dev); } /** @@ -1157,8 +1153,7 @@ static int pm_genpd_thaw_noirq(struct device *dev) if (IS_ERR(genpd)) return -EINVAL; - return genpd->suspend_power_off || dev_gpd_data(dev)->syscore ? - 0 : genpd_start_dev(genpd, dev); + return genpd->suspend_power_off ? 0 : genpd_start_dev(genpd, dev); } /** @@ -1253,7 +1248,7 @@ static int pm_genpd_restore_noirq(struct device *dev) pm_genpd_sync_poweron(genpd); - return dev_gpd_data(dev)->syscore ? 0 : genpd_start_dev(genpd, dev); + return genpd_start_dev(genpd, dev); } /** @@ -1525,26 +1520,6 @@ int pm_genpd_remove_device(struct generic_pm_domain *genpd, return ret; } -/** - * pm_genpd_dev_syscore - Set/unset the "syscore" flag for a given device. - * @dev: Device to set/unset the flag for. - * @val: The new value of the device's "syscore" flag. - */ -void pm_genpd_dev_syscore(struct device *dev, bool val) -{ - struct pm_subsys_data *psd; - unsigned long flags; - - spin_lock_irqsave(&dev->power.lock, flags); - - psd = dev_to_psd(dev); - if (psd && psd->domain_data) - to_gpd_data(psd->domain_data)->syscore = val; - - spin_unlock_irqrestore(&dev->power.lock, flags); -} -EXPORT_SYMBOL_GPL(pm_genpd_dev_syscore); - /** * pm_genpd_dev_need_restore - Set/unset the device's "need restore" flag. * @dev: Device to set/unset the flag for. diff --git a/drivers/base/power/main.c b/drivers/base/power/main.c index 7bd1fe400549..57f5814c2732 100644 --- a/drivers/base/power/main.c +++ b/drivers/base/power/main.c @@ -405,6 +405,9 @@ static int device_resume_noirq(struct device *dev, pm_message_t state) TRACE_DEVICE(dev); TRACE_RESUME(0); + if (dev->power.syscore) + goto Out; + if (dev->pm_domain) { info = "noirq power domain "; callback = pm_noirq_op(&dev->pm_domain->ops, state); @@ -426,6 +429,7 @@ static int device_resume_noirq(struct device *dev, pm_message_t state) error = dpm_run_callback(callback, dev, state, info); + Out: TRACE_RESUME(error); return error; } @@ -483,6 +487,9 @@ static int device_resume_early(struct device *dev, pm_message_t state) TRACE_DEVICE(dev); TRACE_RESUME(0); + if (dev->power.syscore) + goto Out; + if (dev->pm_domain) { info = "early power domain "; callback = pm_late_early_op(&dev->pm_domain->ops, state); @@ -504,6 +511,7 @@ static int device_resume_early(struct device *dev, pm_message_t state) error = dpm_run_callback(callback, dev, state, info); + Out: TRACE_RESUME(error); return error; } @@ -567,6 +575,9 @@ static int device_resume(struct device *dev, pm_message_t state, bool async) TRACE_DEVICE(dev); TRACE_RESUME(0); + if (dev->power.syscore) + goto Complete; + dpm_wait(dev->parent, async); device_lock(dev); @@ -629,6 +640,8 @@ static int device_resume(struct device *dev, pm_message_t state, bool async) Unlock: device_unlock(dev); + + Complete: complete_all(&dev->power.completion); TRACE_RESUME(error); @@ -719,6 +732,9 @@ static void device_complete(struct device *dev, pm_message_t state) void (*callback)(struct device *) = NULL; char *info = NULL; + if (dev->power.syscore) + return; + device_lock(dev); if (dev->pm_domain) { @@ -831,6 +847,9 @@ static int device_suspend_noirq(struct device *dev, pm_message_t state) pm_callback_t callback = NULL; char *info = NULL; + if (dev->power.syscore) + return 0; + if (dev->pm_domain) { info = "noirq power domain "; callback = pm_noirq_op(&dev->pm_domain->ops, state); @@ -914,6 +933,9 @@ static int device_suspend_late(struct device *dev, pm_message_t state) pm_callback_t callback = NULL; char *info = NULL; + if (dev->power.syscore) + return 0; + if (dev->pm_domain) { info = "late power domain "; callback = pm_late_early_op(&dev->pm_domain->ops, state); @@ -1050,6 +1072,9 @@ static int __device_suspend(struct device *dev, pm_message_t state, bool async) goto Complete; } + if (dev->power.syscore) + goto Complete; + device_lock(dev); if (dev->pm_domain) { @@ -1206,6 +1231,9 @@ static int device_prepare(struct device *dev, pm_message_t state) char *info = NULL; int error = 0; + if (dev->power.syscore) + return 0; + device_lock(dev); dev->power.wakeup_path = device_may_wakeup(dev); diff --git a/drivers/base/power/runtime.c b/drivers/base/power/runtime.c index 7d9c1cb1c39a..bd1de3980919 100644 --- a/drivers/base/power/runtime.c +++ b/drivers/base/power/runtime.c @@ -134,7 +134,7 @@ static int rpm_check_suspend_allowed(struct device *dev) if (dev->power.runtime_error) retval = -EINVAL; - else if (dev->power.disable_depth > 0) + else if (dev->power.disable_depth > 0 || dev->power.syscore) retval = -EACCES; else if (atomic_read(&dev->power.usage_count) > 0) retval = -EAGAIN; diff --git a/drivers/clocksource/sh_cmt.c b/drivers/clocksource/sh_cmt.c index c6fbb9f71911..a515605bf8f5 100644 --- a/drivers/clocksource/sh_cmt.c +++ b/drivers/clocksource/sh_cmt.c @@ -717,7 +717,7 @@ static int __devinit sh_cmt_probe(struct platform_device *pdev) struct sh_timer_config *cfg = pdev->dev.platform_data; if (cfg->clocksource_rating || cfg->clockevent_rating) - pm_genpd_dev_syscore(&pdev->dev, true); + dev_pm_syscore_device(&pdev->dev, true); } if (p) { diff --git a/drivers/clocksource/sh_mtu2.c b/drivers/clocksource/sh_mtu2.c index 278c18abb2ae..1a95cad96819 100644 --- a/drivers/clocksource/sh_mtu2.c +++ b/drivers/clocksource/sh_mtu2.c @@ -323,7 +323,7 @@ static int __devinit sh_mtu2_probe(struct platform_device *pdev) struct sh_timer_config *cfg = pdev->dev.platform_data; if (cfg->clockevent_rating) - pm_genpd_dev_syscore(&pdev->dev, true); + dev_pm_syscore_device(&pdev->dev, true); } if (p) { diff --git a/drivers/clocksource/sh_tmu.c b/drivers/clocksource/sh_tmu.c index 5319689c579c..81b0239718ee 100644 --- a/drivers/clocksource/sh_tmu.c +++ b/drivers/clocksource/sh_tmu.c @@ -453,7 +453,7 @@ static int __devinit sh_tmu_probe(struct platform_device *pdev) struct sh_timer_config *cfg = pdev->dev.platform_data; if (cfg->clocksource_rating || cfg->clockevent_rating) - pm_genpd_dev_syscore(&pdev->dev, true); + dev_pm_syscore_device(&pdev->dev, true); } if (p) { diff --git a/include/linux/pm.h b/include/linux/pm.h index 716517af1543..b79a0dd3bc6d 100644 --- a/include/linux/pm.h +++ b/include/linux/pm.h @@ -43,8 +43,12 @@ struct device; #ifdef CONFIG_PM extern const char power_group_name[]; /* = "power" */ + +extern void dev_pm_syscore_device(struct device *dev, bool val); #else #define power_group_name NULL + +static inline void dev_pm_syscore_device(struct device *dev, bool val) {} #endif typedef struct pm_message { @@ -511,6 +515,7 @@ struct dev_pm_info { bool is_suspended:1; /* Ditto */ bool ignore_children:1; bool early_init:1; /* Owned by the PM core */ + bool syscore:1; spinlock_t lock; #ifdef CONFIG_PM_SLEEP struct list_head entry; diff --git a/include/linux/pm_domain.h b/include/linux/pm_domain.h index dab0938603fa..08adf8e5a80e 100644 --- a/include/linux/pm_domain.h +++ b/include/linux/pm_domain.h @@ -114,7 +114,6 @@ struct generic_pm_domain_data { struct mutex lock; unsigned int refcount; bool need_restore; - bool syscore; }; #ifdef CONFIG_PM_GENERIC_DOMAINS @@ -153,7 +152,6 @@ static inline int pm_genpd_of_add_device(struct device_node *genpd_node, extern int pm_genpd_remove_device(struct generic_pm_domain *genpd, struct device *dev); -extern void pm_genpd_dev_syscore(struct device *dev, bool val); extern void pm_genpd_dev_need_restore(struct device *dev, bool val); extern int pm_genpd_add_subdomain(struct generic_pm_domain *genpd, struct generic_pm_domain *new_subdomain); @@ -199,7 +197,6 @@ static inline int pm_genpd_remove_device(struct generic_pm_domain *genpd, { return -ENOSYS; } -static inline void pm_genpd_dev_syscore(struct device *dev, bool val) {} static inline void pm_genpd_dev_need_restore(struct device *dev, bool val) {} static inline int pm_genpd_add_subdomain(struct generic_pm_domain *genpd, struct generic_pm_domain *new_sd) -- cgit v1.2.3 From feb70af0e3ac6817327be70b47731039ea135dbc Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Mon, 13 Aug 2012 14:00:25 +0200 Subject: PM: Do not use the syscore flag for runtime PM The syscore device PM flag used to mark the devices (belonging to PM domains) that should never be turned off, except for the system core (syscore) suspend/hibernation and resume stages, need not be accessed by the runtime PM core functions, because all of the devices it is set for need to be marked as "irq safe" anyway and are protected from being turned off by runtime PM by ensuring that their usage counters are always set. For this reason, make the syscore flag system-wide PM-specific and simplify the code used for manipulating it, because it need not acquire the device's power.lock any more. Signed-off-by: Rafael J. Wysocki --- drivers/base/power/common.c | 15 --------------- drivers/base/power/domain.c | 2 +- drivers/base/power/runtime.c | 2 +- include/linux/device.h | 7 +++++++ include/linux/pm.h | 6 +----- 5 files changed, 10 insertions(+), 22 deletions(-) (limited to 'include/linux') diff --git a/drivers/base/power/common.c b/drivers/base/power/common.c index cf7a85134730..39c32529b833 100644 --- a/drivers/base/power/common.c +++ b/drivers/base/power/common.c @@ -83,18 +83,3 @@ int dev_pm_put_subsys_data(struct device *dev) return ret; } EXPORT_SYMBOL_GPL(dev_pm_put_subsys_data); - -/** - * dev_pm_syscore_device - Set/unset the given device's power.syscore flag. - * @dev: Device whose flag is to be modified. - * @val: New value of the flag. - */ -void dev_pm_syscore_device(struct device *dev, bool val) -{ - unsigned long flags; - - spin_lock_irqsave(&dev->power.lock, flags); - dev->power.syscore = val; - spin_unlock_irqrestore(&dev->power.lock, flags); -} -EXPORT_SYMBOL_GPL(dev_pm_syscore_device); diff --git a/drivers/base/power/domain.c b/drivers/base/power/domain.c index d7e71b5b080e..5f4606f13be6 100644 --- a/drivers/base/power/domain.c +++ b/drivers/base/power/domain.c @@ -442,7 +442,7 @@ static int pm_genpd_poweroff(struct generic_pm_domain *genpd) not_suspended = 0; list_for_each_entry(pdd, &genpd->dev_list, list_node) if (pdd->dev->driver && (!pm_runtime_suspended(pdd->dev) - || pdd->dev->power.irq_safe || pdd->dev->power.syscore)) + || pdd->dev->power.irq_safe)) not_suspended++; if (not_suspended > genpd->in_progress) diff --git a/drivers/base/power/runtime.c b/drivers/base/power/runtime.c index bd1de3980919..7d9c1cb1c39a 100644 --- a/drivers/base/power/runtime.c +++ b/drivers/base/power/runtime.c @@ -134,7 +134,7 @@ static int rpm_check_suspend_allowed(struct device *dev) if (dev->power.runtime_error) retval = -EINVAL; - else if (dev->power.disable_depth > 0 || dev->power.syscore) + else if (dev->power.disable_depth > 0) retval = -EACCES; else if (atomic_read(&dev->power.usage_count) > 0) retval = -EAGAIN; diff --git a/include/linux/device.h b/include/linux/device.h index 52a5f15a2223..86529e642d6c 100644 --- a/include/linux/device.h +++ b/include/linux/device.h @@ -772,6 +772,13 @@ static inline void pm_suspend_ignore_children(struct device *dev, bool enable) dev->power.ignore_children = enable; } +static inline void dev_pm_syscore_device(struct device *dev, bool val) +{ +#ifdef CONFIG_PM_SLEEP + dev->power.syscore = val; +#endif +} + static inline void device_lock(struct device *dev) { mutex_lock(&dev->mutex); diff --git a/include/linux/pm.h b/include/linux/pm.h index b79a0dd3bc6d..44d1f2307dbc 100644 --- a/include/linux/pm.h +++ b/include/linux/pm.h @@ -43,12 +43,8 @@ struct device; #ifdef CONFIG_PM extern const char power_group_name[]; /* = "power" */ - -extern void dev_pm_syscore_device(struct device *dev, bool val); #else #define power_group_name NULL - -static inline void dev_pm_syscore_device(struct device *dev, bool val) {} #endif typedef struct pm_message { @@ -515,13 +511,13 @@ struct dev_pm_info { bool is_suspended:1; /* Ditto */ bool ignore_children:1; bool early_init:1; /* Owned by the PM core */ - bool syscore:1; spinlock_t lock; #ifdef CONFIG_PM_SLEEP struct list_head entry; struct completion completion; struct wakeup_source *wakeup; bool wakeup_path:1; + bool syscore:1; #else unsigned int should_wakeup:1; #endif -- cgit v1.2.3 From b5abb085f5540a612b0b7a6326ae2a07de2330dd Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Tue, 7 Aug 2012 01:06:11 +0200 Subject: PM / Domains: Make it possible to use domain names when adding devices Add a new helper function __pm_genpd_name_add_device() allowing a device to be added to a (registered) generic PM domain identified by name. Add a wrapper around it, pm_genpd_name_add_device(), passing NULL as the last argument and reorganize pm_domains.h for the new functions to be defined consistently with the existing ones. These functions are useful for adding devices to PM domains whose representations are stored in tables, when the caller doesn't know the index of the domain to add the device to, but it knows the domain's name. Signed-off-by: Rafael J. Wysocki --- drivers/base/power/domain.c | 27 +++++++++++++++++++++++++++ include/linux/pm_domain.h | 43 ++++++++++++++++++++++++++++++------------- 2 files changed, 57 insertions(+), 13 deletions(-) (limited to 'include/linux') diff --git a/drivers/base/power/domain.c b/drivers/base/power/domain.c index 5f4606f13be6..ac06d0280b42 100644 --- a/drivers/base/power/domain.c +++ b/drivers/base/power/domain.c @@ -1465,6 +1465,33 @@ int __pm_genpd_of_add_device(struct device_node *genpd_node, struct device *dev, return __pm_genpd_add_device(genpd, dev, td); } + +/** + * __pm_genpd_name_add_device - Find I/O PM domain and add a device to it. + * @domain_name: Name of the PM domain to add the device to. + * @dev: Device to be added. + * @td: Set of PM QoS timing parameters to attach to the device. + */ +int __pm_genpd_name_add_device(const char *domain_name, struct device *dev, + struct gpd_timing_data *td) +{ + struct generic_pm_domain *genpd = NULL, *gpd; + + if (IS_ERR_OR_NULL(domain_name) || IS_ERR_OR_NULL(dev)) + return -EINVAL; + + mutex_lock(&gpd_list_lock); + list_for_each_entry(gpd, &gpd_list, gpd_list_node) { + if (!strcmp(gpd->name, domain_name)) { + genpd = gpd; + break; + } + } + mutex_unlock(&gpd_list_lock); + + return __pm_genpd_add_device(genpd, dev, td); +} + /** * pm_genpd_remove_device - Remove a device from an I/O PM domain. * @genpd: PM domain to remove the device from. diff --git a/include/linux/pm_domain.h b/include/linux/pm_domain.h index 08adf8e5a80e..1991a92b0567 100644 --- a/include/linux/pm_domain.h +++ b/include/linux/pm_domain.h @@ -138,17 +138,9 @@ extern int __pm_genpd_of_add_device(struct device_node *genpd_node, struct device *dev, struct gpd_timing_data *td); -static inline int pm_genpd_add_device(struct generic_pm_domain *genpd, - struct device *dev) -{ - return __pm_genpd_add_device(genpd, dev, NULL); -} - -static inline int pm_genpd_of_add_device(struct device_node *genpd_node, - struct device *dev) -{ - return __pm_genpd_of_add_device(genpd_node, dev, NULL); -} +extern int __pm_genpd_name_add_device(const char *domain_name, + struct device *dev, + struct gpd_timing_data *td); extern int pm_genpd_remove_device(struct generic_pm_domain *genpd, struct device *dev); @@ -187,8 +179,15 @@ static inline int __pm_genpd_add_device(struct generic_pm_domain *genpd, { return -ENOSYS; } -static inline int pm_genpd_add_device(struct generic_pm_domain *genpd, - struct device *dev) +static inline int __pm_genpd_of_add_device(struct device_node *genpd_node, + struct device *dev, + struct gpd_timing_data *td) +{ + return -ENOSYS; +} +static inline int __pm_genpd_name_add_device(const char *domain_name, + struct device *dev, + struct gpd_timing_data *td) { return -ENOSYS; } @@ -242,6 +241,24 @@ static inline bool default_stop_ok(struct device *dev) #define pm_domain_always_on_gov NULL #endif +static inline int pm_genpd_add_device(struct generic_pm_domain *genpd, + struct device *dev) +{ + return __pm_genpd_add_device(genpd, dev, NULL); +} + +static inline int pm_genpd_of_add_device(struct device_node *genpd_node, + struct device *dev) +{ + return __pm_genpd_of_add_device(genpd_node, dev, NULL); +} + +static inline int pm_genpd_name_add_device(const char *domain_name, + struct device *dev) +{ + return __pm_genpd_name_add_device(domain_name, dev, NULL); +} + static inline int pm_genpd_remove_callbacks(struct device *dev) { return __pm_genpd_remove_callbacks(dev, true); -- cgit v1.2.3 From fb7268be9f72bed6ae48554f00f2dcb2ef333bfc Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Tue, 7 Aug 2012 01:08:37 +0200 Subject: PM / Domains: Make it possible to use names when adding subdomains Add a new helper function, pm_genpd_add_subdomain_names(), allowing the caller to add a subdomain to a generic PM domain using names for domain identification (both domains have to be initialized before). This function is useful for adding subdomains to PM domains whose representations are stored in tables, when the caller doesn't know the indices of the domain to add the subdomain to and of the subdomain itself, but it knows the domains' names. Signed-off-by: Rafael J. Wysocki --- drivers/base/power/domain.c | 32 +++++++++++++++++++++++++++++++- include/linux/pm_domain.h | 7 +++++++ 2 files changed, 38 insertions(+), 1 deletion(-) (limited to 'include/linux') diff --git a/drivers/base/power/domain.c b/drivers/base/power/domain.c index ac06d0280b42..cddf818f493c 100644 --- a/drivers/base/power/domain.c +++ b/drivers/base/power/domain.c @@ -1584,7 +1584,8 @@ int pm_genpd_add_subdomain(struct generic_pm_domain *genpd, struct gpd_link *link; int ret = 0; - if (IS_ERR_OR_NULL(genpd) || IS_ERR_OR_NULL(subdomain)) + if (IS_ERR_OR_NULL(genpd) || IS_ERR_OR_NULL(subdomain) + || genpd == subdomain) return -EINVAL; start: @@ -1630,6 +1631,35 @@ int pm_genpd_add_subdomain(struct generic_pm_domain *genpd, return ret; } +/** + * pm_genpd_add_subdomain_names - Add a subdomain to an I/O PM domain. + * @master_name: Name of the master PM domain to add the subdomain to. + * @subdomain_name: Name of the subdomain to be added. + */ +int pm_genpd_add_subdomain_names(const char *master_name, + const char *subdomain_name) +{ + struct generic_pm_domain *master = NULL, *subdomain = NULL, *gpd; + + if (IS_ERR_OR_NULL(master_name) || IS_ERR_OR_NULL(subdomain_name)) + return -EINVAL; + + mutex_lock(&gpd_list_lock); + list_for_each_entry(gpd, &gpd_list, gpd_list_node) { + if (!master && !strcmp(gpd->name, master_name)) + master = gpd; + + if (!subdomain && !strcmp(gpd->name, subdomain_name)) + subdomain = gpd; + + if (master && subdomain) + break; + } + mutex_unlock(&gpd_list_lock); + + return pm_genpd_add_subdomain(master, subdomain); +} + /** * pm_genpd_remove_subdomain - Remove a subdomain from an I/O PM domain. * @genpd: Master PM domain to remove the subdomain from. diff --git a/include/linux/pm_domain.h b/include/linux/pm_domain.h index 1991a92b0567..8dbf48b8336f 100644 --- a/include/linux/pm_domain.h +++ b/include/linux/pm_domain.h @@ -147,6 +147,8 @@ extern int pm_genpd_remove_device(struct generic_pm_domain *genpd, extern void pm_genpd_dev_need_restore(struct device *dev, bool val); extern int pm_genpd_add_subdomain(struct generic_pm_domain *genpd, struct generic_pm_domain *new_subdomain); +extern int pm_genpd_add_subdomain_names(const char *master_name, + const char *subdomain_name); extern int pm_genpd_remove_subdomain(struct generic_pm_domain *genpd, struct generic_pm_domain *target); extern int pm_genpd_add_callbacks(struct device *dev, @@ -202,6 +204,11 @@ static inline int pm_genpd_add_subdomain(struct generic_pm_domain *genpd, { return -ENOSYS; } +static inline int pm_genpd_add_subdomain_names(const char *master_name, + const char *subdomain_name) +{ + return -ENOSYS; +} static inline int pm_genpd_remove_subdomain(struct generic_pm_domain *genpd, struct generic_pm_domain *target) { -- cgit v1.2.3 From 8bc0251de2932e603f8ed73b76ba2d64b2dc1d18 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Tue, 7 Aug 2012 01:11:14 +0200 Subject: PM / Domains: Add power-on function using names to identify domains It sometimes is necessary to turn on a given PM domain when only the name of it is known and the domain pointer is not readily available. For this reason, add a new helper function, pm_genpd_name_poweron(), allowing the caller to turn on a PM domain using its name for identification. To avoid code duplication, move the domain lookup code to a separate function. Signed-off-by: Rafael J. Wysocki --- drivers/base/power/domain.c | 46 ++++++++++++++++++++++++++++++--------------- include/linux/pm_domain.h | 5 +++++ 2 files changed, 36 insertions(+), 15 deletions(-) (limited to 'include/linux') diff --git a/drivers/base/power/domain.c b/drivers/base/power/domain.c index cddf818f493c..4d633406f375 100644 --- a/drivers/base/power/domain.c +++ b/drivers/base/power/domain.c @@ -53,6 +53,24 @@ static LIST_HEAD(gpd_list); static DEFINE_MUTEX(gpd_list_lock); +static struct generic_pm_domain *pm_genpd_lookup_name(const char *domain_name) +{ + struct generic_pm_domain *genpd = NULL, *gpd; + + if (IS_ERR_OR_NULL(domain_name)) + return NULL; + + mutex_lock(&gpd_list_lock); + list_for_each_entry(gpd, &gpd_list, gpd_list_node) { + if (!strcmp(gpd->name, domain_name)) { + genpd = gpd; + break; + } + } + mutex_unlock(&gpd_list_lock); + return genpd; +} + #ifdef CONFIG_PM struct generic_pm_domain *dev_to_genpd(struct device *dev) @@ -262,6 +280,18 @@ int pm_genpd_poweron(struct generic_pm_domain *genpd) return ret; } +/** + * pm_genpd_name_poweron - Restore power to a given PM domain and its masters. + * @domain_name: Name of the PM domain to power up. + */ +int pm_genpd_name_poweron(const char *domain_name) +{ + struct generic_pm_domain *genpd; + + genpd = pm_genpd_lookup_name(domain_name); + return genpd ? pm_genpd_poweron(genpd) : -EINVAL; +} + #endif /* CONFIG_PM */ #ifdef CONFIG_PM_RUNTIME @@ -1475,21 +1505,7 @@ int __pm_genpd_of_add_device(struct device_node *genpd_node, struct device *dev, int __pm_genpd_name_add_device(const char *domain_name, struct device *dev, struct gpd_timing_data *td) { - struct generic_pm_domain *genpd = NULL, *gpd; - - if (IS_ERR_OR_NULL(domain_name) || IS_ERR_OR_NULL(dev)) - return -EINVAL; - - mutex_lock(&gpd_list_lock); - list_for_each_entry(gpd, &gpd_list, gpd_list_node) { - if (!strcmp(gpd->name, domain_name)) { - genpd = gpd; - break; - } - } - mutex_unlock(&gpd_list_lock); - - return __pm_genpd_add_device(genpd, dev, td); + return __pm_genpd_add_device(pm_genpd_lookup_name(domain_name), dev, td); } /** diff --git a/include/linux/pm_domain.h b/include/linux/pm_domain.h index 8dbf48b8336f..d9d60835c29b 100644 --- a/include/linux/pm_domain.h +++ b/include/linux/pm_domain.h @@ -161,6 +161,7 @@ extern void pm_genpd_init(struct generic_pm_domain *genpd, struct dev_power_governor *gov, bool is_off); extern int pm_genpd_poweron(struct generic_pm_domain *genpd); +extern int pm_genpd_name_poweron(const char *domain_name); extern bool default_stop_ok(struct device *dev); @@ -240,6 +241,10 @@ static inline int pm_genpd_poweron(struct generic_pm_domain *genpd) { return -ENOSYS; } +static inline int pm_genpd_name_poweron(const char *domain_name) +{ + return -ENOSYS; +} static inline bool default_stop_ok(struct device *dev) { return false; -- cgit v1.2.3 From 40114447a7f89860b46a64e5504f313656cb5f27 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Wed, 15 Aug 2012 20:32:43 +0200 Subject: PM / Domains: Document cpuidle-related functions and change their names The names of the cpuidle-related functions in drivers/base/power/domain.c are inconsistent with the names of the other exported functions in that file (the "pm_" prefix is missing from them) and they are missing kerneldoc comments. Fix that by adding the missing "pm_" prefix to the names of those functions and add kerneldoc comments documenting them. Signed-off-by: Rafael J. Wysocki --- drivers/base/power/domain.c | 20 ++++++++++++++++++-- include/linux/pm_domain.h | 8 ++++---- 2 files changed, 22 insertions(+), 6 deletions(-) (limited to 'include/linux') diff --git a/drivers/base/power/domain.c b/drivers/base/power/domain.c index 4d633406f375..e44e1a8e8376 100644 --- a/drivers/base/power/domain.c +++ b/drivers/base/power/domain.c @@ -1829,7 +1829,16 @@ int __pm_genpd_remove_callbacks(struct device *dev, bool clear_td) } EXPORT_SYMBOL_GPL(__pm_genpd_remove_callbacks); -int genpd_attach_cpuidle(struct generic_pm_domain *genpd, int state) +/** + * pm_genpd_attach_cpuidle - Connect the given PM domain with cpuidle. + * @genpd: PM domain to be connected with cpuidle. + * @state: cpuidle state this domain can disable/enable. + * + * Make a PM domain behave as though it contained a CPU core, that is, instead + * of calling its power down routine it will enable the given cpuidle state so + * that the cpuidle subsystem can power it down (if possible and desirable). + */ +int pm_genpd_attach_cpuidle(struct generic_pm_domain *genpd, int state) { struct cpuidle_driver *cpuidle_drv; struct gpd_cpu_data *cpu_data; @@ -1878,7 +1887,14 @@ int genpd_attach_cpuidle(struct generic_pm_domain *genpd, int state) goto out; } -int genpd_detach_cpuidle(struct generic_pm_domain *genpd) +/** + * pm_genpd_detach_cpuidle - Remove the cpuidle connection from a PM domain. + * @genpd: PM domain to remove the cpuidle connection from. + * + * Remove the cpuidle connection set up by pm_genpd_attach_cpuidle() from the + * given PM domain. + */ +int pm_genpd_detach_cpuidle(struct generic_pm_domain *genpd) { struct gpd_cpu_data *cpu_data; struct cpuidle_state *idle_state; diff --git a/include/linux/pm_domain.h b/include/linux/pm_domain.h index d9d60835c29b..f2a633a913e7 100644 --- a/include/linux/pm_domain.h +++ b/include/linux/pm_domain.h @@ -155,8 +155,8 @@ extern int pm_genpd_add_callbacks(struct device *dev, struct gpd_dev_ops *ops, struct gpd_timing_data *td); extern int __pm_genpd_remove_callbacks(struct device *dev, bool clear_td); -extern int genpd_attach_cpuidle(struct generic_pm_domain *genpd, int state); -extern int genpd_detach_cpuidle(struct generic_pm_domain *genpd); +extern int pm_genpd_attach_cpuidle(struct generic_pm_domain *genpd, int state); +extern int pm_genpd_detach_cpuidle(struct generic_pm_domain *genpd); extern void pm_genpd_init(struct generic_pm_domain *genpd, struct dev_power_governor *gov, bool is_off); @@ -225,11 +225,11 @@ static inline int __pm_genpd_remove_callbacks(struct device *dev, bool clear_td) { return -ENOSYS; } -static inline int genpd_attach_cpuidle(struct generic_pm_domain *genpd, int st) +static inline int pm_genpd_attach_cpuidle(struct generic_pm_domain *genpd, int st) { return -ENOSYS; } -static inline int genpd_detach_cpuidle(struct generic_pm_domain *genpd) +static inline int pm_genpd_detach_cpuidle(struct generic_pm_domain *genpd) { return -ENOSYS; } -- cgit v1.2.3 From 74a2799ab51acec9410f467fef8678ebb1125d7d Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Wed, 15 Aug 2012 20:32:59 +0200 Subject: PM / Domains: Operations related to cpuidle using domain names Make it possible to use domain names in operations connecting cpuidle to and disconnecting it from a PM domain. This is useful on platforms where PM domain objects are organized in such a way that the names of the domains are easier to use than the addresses of those objects. Signed-off-by: Rafael J. Wysocki --- drivers/base/power/domain.c | 19 +++++++++++++++++++ include/linux/pm_domain.h | 10 ++++++++++ 2 files changed, 29 insertions(+) (limited to 'include/linux') diff --git a/drivers/base/power/domain.c b/drivers/base/power/domain.c index e44e1a8e8376..12ad070c244f 100644 --- a/drivers/base/power/domain.c +++ b/drivers/base/power/domain.c @@ -1887,6 +1887,16 @@ int pm_genpd_attach_cpuidle(struct generic_pm_domain *genpd, int state) goto out; } +/** + * pm_genpd_name_attach_cpuidle - Find PM domain and connect cpuidle to it. + * @name: Name of the domain to connect to cpuidle. + * @state: cpuidle state this domain can manipulate. + */ +int pm_genpd_name_attach_cpuidle(const char *name, int state) +{ + return pm_genpd_attach_cpuidle(pm_genpd_lookup_name(name), state); +} + /** * pm_genpd_detach_cpuidle - Remove the cpuidle connection from a PM domain. * @genpd: PM domain to remove the cpuidle connection from. @@ -1925,6 +1935,15 @@ int pm_genpd_detach_cpuidle(struct generic_pm_domain *genpd) return ret; } +/** + * pm_genpd_name_detach_cpuidle - Find PM domain and disconnect cpuidle from it. + * @name: Name of the domain to disconnect cpuidle from. + */ +int pm_genpd_name_detach_cpuidle(const char *name) +{ + return pm_genpd_detach_cpuidle(pm_genpd_lookup_name(name)); +} + /* Default device callbacks for generic PM domains. */ /** diff --git a/include/linux/pm_domain.h b/include/linux/pm_domain.h index f2a633a913e7..7c1d252b20c0 100644 --- a/include/linux/pm_domain.h +++ b/include/linux/pm_domain.h @@ -156,7 +156,9 @@ extern int pm_genpd_add_callbacks(struct device *dev, struct gpd_timing_data *td); extern int __pm_genpd_remove_callbacks(struct device *dev, bool clear_td); extern int pm_genpd_attach_cpuidle(struct generic_pm_domain *genpd, int state); +extern int pm_genpd_name_attach_cpuidle(const char *name, int state); extern int pm_genpd_detach_cpuidle(struct generic_pm_domain *genpd); +extern int pm_genpd_name_detach_cpuidle(const char *name); extern void pm_genpd_init(struct generic_pm_domain *genpd, struct dev_power_governor *gov, bool is_off); @@ -229,10 +231,18 @@ static inline int pm_genpd_attach_cpuidle(struct generic_pm_domain *genpd, int s { return -ENOSYS; } +static inline int pm_genpd_name_attach_cpuidle(const char *name, int state) +{ + return -ENOSYS; +} static inline int pm_genpd_detach_cpuidle(struct generic_pm_domain *genpd) { return -ENOSYS; } +static inline int pm_genpd_name_detach_cpuidle(const char *name) +{ + return -ENOSYS; +} static inline void pm_genpd_init(struct generic_pm_domain *genpd, struct dev_power_governor *gov, bool is_off) { -- cgit v1.2.3 From b496dfbc94ab86f970ef0167eaabe51f930aa5fb Mon Sep 17 00:00:00 2001 From: Shawn Guo Date: Wed, 5 Sep 2012 01:09:12 +0200 Subject: PM / OPP: Initialize OPP table from device tree With a lot of devices booting from device tree nowadays, it requires that OPP table can be initialized from device tree. The patch adds a helper function of_init_opp_table together with a binding doc for that purpose. Signed-off-by: Shawn Guo Acked-by: Rob Herring Signed-off-by: Rafael J. Wysocki --- Documentation/devicetree/bindings/power/opp.txt | 25 +++++++++++++ drivers/base/power/opp.c | 47 +++++++++++++++++++++++++ include/linux/opp.h | 8 +++++ 3 files changed, 80 insertions(+) create mode 100644 Documentation/devicetree/bindings/power/opp.txt (limited to 'include/linux') diff --git a/Documentation/devicetree/bindings/power/opp.txt b/Documentation/devicetree/bindings/power/opp.txt new file mode 100644 index 000000000000..74499e5033fc --- /dev/null +++ b/Documentation/devicetree/bindings/power/opp.txt @@ -0,0 +1,25 @@ +* Generic OPP Interface + +SoCs have a standard set of tuples consisting of frequency and +voltage pairs that the device will support per voltage domain. These +are called Operating Performance Points or OPPs. + +Properties: +- operating-points: An array of 2-tuples items, and each item consists + of frequency and voltage like . + freq: clock frequency in kHz + vol: voltage in microvolt + +Examples: + +cpu@0 { + compatible = "arm,cortex-a9"; + reg = <0>; + next-level-cache = <&L2>; + operating-points = < + /* kHz uV */ + 792000 1100000 + 396000 950000 + 198000 850000 + >; +}; diff --git a/drivers/base/power/opp.c b/drivers/base/power/opp.c index ac993eafec82..d9468642fc41 100644 --- a/drivers/base/power/opp.c +++ b/drivers/base/power/opp.c @@ -22,6 +22,7 @@ #include #include #include +#include /* * Internal data structure organization with the OPP layer library is as @@ -674,3 +675,49 @@ struct srcu_notifier_head *opp_get_notifier(struct device *dev) return &dev_opp->head; } + +#ifdef CONFIG_OF +/** + * of_init_opp_table() - Initialize opp table from device tree + * @dev: device pointer used to lookup device OPPs. + * + * Register the initial OPP table with the OPP library for given device. + */ +int of_init_opp_table(struct device *dev) +{ + const struct property *prop; + const __be32 *val; + int nr; + + prop = of_find_property(dev->of_node, "operating-points", NULL); + if (!prop) + return -ENODEV; + if (!prop->value) + return -ENODATA; + + /* + * Each OPP is a set of tuples consisting of frequency and + * voltage like . + */ + nr = prop->length / sizeof(u32); + if (nr % 2) { + dev_err(dev, "%s: Invalid OPP list\n", __func__); + return -EINVAL; + } + + val = prop->value; + while (nr) { + unsigned long freq = be32_to_cpup(val++) * 1000; + unsigned long volt = be32_to_cpup(val++); + + if (opp_add(dev, freq, volt)) { + dev_warn(dev, "%s: Failed to add OPP %ld\n", + __func__, freq); + continue; + } + nr -= 2; + } + + return 0; +} +#endif diff --git a/include/linux/opp.h b/include/linux/opp.h index 2a4e5faee904..214e0ebcb84d 100644 --- a/include/linux/opp.h +++ b/include/linux/opp.h @@ -48,6 +48,14 @@ int opp_disable(struct device *dev, unsigned long freq); struct srcu_notifier_head *opp_get_notifier(struct device *dev); +#ifdef CONFIG_OF +int of_init_opp_table(struct device *dev); +#else +static inline int of_init_opp_table(struct device *dev) +{ + return -EINVAL; +} +#endif /* CONFIG_OF */ #else static inline unsigned long opp_get_voltage(struct opp *opp) { -- cgit v1.2.3