diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2019-09-17 19:15:14 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2019-09-17 19:15:14 -0700 |
commit | 77dcfe2b9edc98286cf18e03c243c9b999f955d9 (patch) | |
tree | 0ba3c4002b6c26c715bf03fac81d63de13c01d96 /drivers/base/power | |
parent | 04cbfba6208592999d7bfe6609ec01dc3fde73f5 (diff) | |
parent | fc6763a2d7e0a7f49ccec97a46e92e9fb1f3f9dd (diff) |
Merge tag 'pm-5.4-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/rafael/linux-pm
Pull power management updates from Rafael Wysocki:
"These include a rework of the main suspend-to-idle code flow (related
to the handling of spurious wakeups), a switch over of several users
of cpufreq notifiers to QoS-based limits, a new devfreq driver for
Tegra20, a new cpuidle driver and governor for virtualized guests, an
extension of the wakeup sources framework to expose wakeup sources as
device objects in sysfs, and more.
Specifics:
- Rework the main suspend-to-idle control flow to avoid repeating
"noirq" device resume and suspend operations in case of spurious
wakeups from the ACPI EC and decouple the ACPI EC wakeups support
from the LPS0 _DSM support (Rafael Wysocki).
- Extend the wakeup sources framework to expose wakeup sources as
device objects in sysfs (Tri Vo, Stephen Boyd).
- Expose system suspend statistics in sysfs (Kalesh Singh).
- Introduce a new haltpoll cpuidle driver and a new matching governor
for virtualized guests wanting to do guest-side polling in the idle
loop (Marcelo Tosatti, Joao Martins, Wanpeng Li, Stephen Rothwell).
- Fix the menu and teo cpuidle governors to allow the scheduler tick
to be stopped if PM QoS is used to limit the CPU idle state exit
latency in some cases (Rafael Wysocki).
- Increase the resolution of the play_idle() argument to microseconds
for more fine-grained injection of CPU idle cycles (Daniel
Lezcano).
- Switch over some users of cpuidle notifiers to the new QoS-based
frequency limits and drop the CPUFREQ_ADJUST and CPUFREQ_NOTIFY
policy notifier events (Viresh Kumar).
- Add new cpufreq driver based on nvmem for sun50i (Yangtao Li).
- Add support for MT8183 and MT8516 to the mediatek cpufreq driver
(Andrew-sh.Cheng, Fabien Parent).
- Add i.MX8MN support to the imx-cpufreq-dt cpufreq driver (Anson
Huang).
- Add qcs404 to cpufreq-dt-platdev blacklist (Jorge Ramirez-Ortiz).
- Update the qcom cpufreq driver (among other things, to make it
easier to extend and to use kryo cpufreq for other nvmem-based
SoCs) and add qcs404 support to it (Niklas Cassel, Douglas
RAILLARD, Sibi Sankar, Sricharan R).
- Fix assorted issues and make assorted minor improvements in the
cpufreq code (Colin Ian King, Douglas RAILLARD, Florian Fainelli,
Gustavo Silva, Hariprasad Kelam).
- Add new devfreq driver for NVidia Tegra20 (Dmitry Osipenko, Arnd
Bergmann).
- Add new Exynos PPMU events to devfreq events and extend that
mechanism (Lukasz Luba).
- Fix and clean up the exynos-bus devfreq driver (Kamil Konieczny).
- Improve devfreq documentation and governor code, fix spelling typos
in devfreq (Ezequiel Garcia, Krzysztof Kozlowski, Leonard Crestez,
MyungJoo Ham, Gaël PORTAY).
- Add regulators enable and disable to the OPP (operating performance
points) framework (Kamil Konieczny).
- Update the OPP framework to support multiple opp-suspend properties
(Anson Huang).
- Fix assorted issues and make assorted minor improvements in the OPP
code (Niklas Cassel, Viresh Kumar, Yue Hu).
- Clean up the generic power domains (genpd) framework (Ulf Hansson).
- Clean up assorted pieces of power management code and documentation
(Akinobu Mita, Amit Kucheria, Chuhong Yuan).
- Update the pm-graph tool to version 5.5 including multiple fixes
and improvements (Todd Brandt).
- Update the cpupower utility (Benjamin Weis, Geert Uytterhoeven,
Sébastien Szymanski)"
* tag 'pm-5.4-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/rafael/linux-pm: (126 commits)
cpuidle-haltpoll: Enable kvm guest polling when dedicated physical CPUs are available
cpuidle-haltpoll: do not set an owner to allow modunload
cpuidle-haltpoll: return -ENODEV on modinit failure
cpuidle-haltpoll: set haltpoll as preferred governor
cpuidle: allow governor switch on cpuidle_register_driver()
PM: runtime: Documentation: add runtime_status ABI document
pm-graph: make setVal unbuffered again for python2 and python3
powercap: idle_inject: Use higher resolution for idle injection
cpuidle: play_idle: Increase the resolution to usec
cpuidle-haltpoll: vcpu hotplug support
cpufreq: Add qcs404 to cpufreq-dt-platdev blacklist
cpufreq: qcom: Add support for qcs404 on nvmem driver
cpufreq: qcom: Refactor the driver to make it easier to extend
cpufreq: qcom: Re-organise kryo cpufreq to use it for other nvmem based qcom socs
dt-bindings: opp: Add qcom-opp bindings with properties needed for CPR
dt-bindings: opp: qcom-nvmem: Support pstates provided by a power domain
Documentation: cpufreq: Update policy notifier documentation
cpufreq: Remove CPUFREQ_ADJUST and CPUFREQ_NOTIFY policy notifier events
PM / Domains: Verify PM domain type in dev_pm_genpd_set_performance_state()
PM / Domains: Simplify genpd_lookup_dev()
...
Diffstat (limited to 'drivers/base/power')
-rw-r--r-- | drivers/base/power/Makefile | 2 | ||||
-rw-r--r-- | drivers/base/power/domain.c | 25 | ||||
-rw-r--r-- | drivers/base/power/main.c | 35 | ||||
-rw-r--r-- | drivers/base/power/power.h | 18 | ||||
-rw-r--r-- | drivers/base/power/sysfs.c | 6 | ||||
-rw-r--r-- | drivers/base/power/wakeup.c | 72 | ||||
-rw-r--r-- | drivers/base/power/wakeup_stats.c | 214 |
7 files changed, 307 insertions, 65 deletions
diff --git a/drivers/base/power/Makefile b/drivers/base/power/Makefile index e1bb691cf8f1..ec5bb190b9d0 100644 --- a/drivers/base/power/Makefile +++ b/drivers/base/power/Makefile @@ -1,6 +1,6 @@ # SPDX-License-Identifier: GPL-2.0 obj-$(CONFIG_PM) += sysfs.o generic_ops.o common.o qos.o runtime.o wakeirq.o -obj-$(CONFIG_PM_SLEEP) += main.o wakeup.o +obj-$(CONFIG_PM_SLEEP) += main.o wakeup.o wakeup_stats.o obj-$(CONFIG_PM_TRACE_RTC) += trace.o obj-$(CONFIG_PM_GENERIC_DOMAINS) += domain.o domain_governor.o obj-$(CONFIG_HAVE_CLK) += clock_ops.o diff --git a/drivers/base/power/domain.c b/drivers/base/power/domain.c index b063bc41b0a9..cc85e87eaf05 100644 --- a/drivers/base/power/domain.c +++ b/drivers/base/power/domain.c @@ -149,29 +149,24 @@ static inline bool irq_safe_dev_in_no_sleep_domain(struct device *dev, return ret; } +static int genpd_runtime_suspend(struct device *dev); + /* * Get the generic PM domain for a particular struct device. * This validates the struct device pointer, the PM domain pointer, * and checks that the PM domain pointer is a real generic PM domain. * Any failure results in NULL being returned. */ -static struct generic_pm_domain *genpd_lookup_dev(struct device *dev) +static struct generic_pm_domain *dev_to_genpd_safe(struct device *dev) { - struct generic_pm_domain *genpd = NULL, *gpd; - if (IS_ERR_OR_NULL(dev) || IS_ERR_OR_NULL(dev->pm_domain)) return NULL; - mutex_lock(&gpd_list_lock); - list_for_each_entry(gpd, &gpd_list, gpd_list_node) { - if (&gpd->domain == dev->pm_domain) { - genpd = gpd; - break; - } - } - mutex_unlock(&gpd_list_lock); + /* A genpd's always have its ->runtime_suspend() callback assigned. */ + if (dev->pm_domain->ops.runtime_suspend == genpd_runtime_suspend) + return pd_to_genpd(dev->pm_domain); - return genpd; + return NULL; } /* @@ -385,8 +380,8 @@ int dev_pm_genpd_set_performance_state(struct device *dev, unsigned int state) unsigned int prev; int ret; - genpd = dev_to_genpd(dev); - if (IS_ERR(genpd)) + genpd = dev_to_genpd_safe(dev); + if (!genpd) return -ENODEV; if (unlikely(!genpd->set_performance_state)) @@ -1610,7 +1605,7 @@ static int genpd_remove_device(struct generic_pm_domain *genpd, */ int pm_genpd_remove_device(struct device *dev) { - struct generic_pm_domain *genpd = genpd_lookup_dev(dev); + struct generic_pm_domain *genpd = dev_to_genpd_safe(dev); if (!genpd) return -EINVAL; diff --git a/drivers/base/power/main.c b/drivers/base/power/main.c index 7fb2c39bc725..134a8af51511 100644 --- a/drivers/base/power/main.c +++ b/drivers/base/power/main.c @@ -716,7 +716,7 @@ static void async_resume_noirq(void *data, async_cookie_t cookie) put_device(dev); } -void dpm_noirq_resume_devices(pm_message_t state) +static void dpm_noirq_resume_devices(pm_message_t state) { struct device *dev; ktime_t starttime = ktime_get(); @@ -760,13 +760,6 @@ void dpm_noirq_resume_devices(pm_message_t state) trace_suspend_resume(TPS("dpm_resume_noirq"), state.event, false); } -void dpm_noirq_end(void) -{ - resume_device_irqs(); - device_wakeup_disarm_wake_irqs(); - cpuidle_resume(); -} - /** * dpm_resume_noirq - Execute "noirq resume" callbacks for all devices. * @state: PM transition of the system being carried out. @@ -777,7 +770,11 @@ void dpm_noirq_end(void) void dpm_resume_noirq(pm_message_t state) { dpm_noirq_resume_devices(state); - dpm_noirq_end(); + + resume_device_irqs(); + device_wakeup_disarm_wake_irqs(); + + cpuidle_resume(); } static pm_callback_t dpm_subsys_resume_early_cb(struct device *dev, @@ -1291,11 +1288,6 @@ static int __device_suspend_noirq(struct device *dev, pm_message_t state, bool a if (async_error) goto Complete; - if (pm_wakeup_pending()) { - async_error = -EBUSY; - goto Complete; - } - if (dev->power.syscore || dev->power.direct_complete) goto Complete; @@ -1362,14 +1354,7 @@ static int device_suspend_noirq(struct device *dev) return __device_suspend_noirq(dev, pm_transition, false); } -void dpm_noirq_begin(void) -{ - cpuidle_pause(); - device_wakeup_arm_wake_irqs(); - suspend_device_irqs(); -} - -int dpm_noirq_suspend_devices(pm_message_t state) +static int dpm_noirq_suspend_devices(pm_message_t state) { ktime_t starttime = ktime_get(); int error = 0; @@ -1426,7 +1411,11 @@ int dpm_suspend_noirq(pm_message_t state) { int ret; - dpm_noirq_begin(); + cpuidle_pause(); + + device_wakeup_arm_wake_irqs(); + suspend_device_irqs(); + ret = dpm_noirq_suspend_devices(state); if (ret) dpm_resume_noirq(resume_event(state)); diff --git a/drivers/base/power/power.h b/drivers/base/power/power.h index ec33fbdb919b..39a06a0cfdaa 100644 --- a/drivers/base/power/power.h +++ b/drivers/base/power/power.h @@ -149,3 +149,21 @@ static inline void device_pm_init(struct device *dev) device_pm_sleep_init(dev); pm_runtime_init(dev); } + +#ifdef CONFIG_PM_SLEEP + +/* drivers/base/power/wakeup_stats.c */ +extern int wakeup_source_sysfs_add(struct device *parent, + struct wakeup_source *ws); +extern void wakeup_source_sysfs_remove(struct wakeup_source *ws); + +extern int pm_wakeup_source_sysfs_add(struct device *parent); + +#else /* !CONFIG_PM_SLEEP */ + +static inline int pm_wakeup_source_sysfs_add(struct device *parent) +{ + return 0; +} + +#endif /* CONFIG_PM_SLEEP */ diff --git a/drivers/base/power/sysfs.c b/drivers/base/power/sysfs.c index 1b9c281cbe41..d7d82db2e4bc 100644 --- a/drivers/base/power/sysfs.c +++ b/drivers/base/power/sysfs.c @@ -5,6 +5,7 @@ #include <linux/export.h> #include <linux/pm_qos.h> #include <linux/pm_runtime.h> +#include <linux/pm_wakeup.h> #include <linux/atomic.h> #include <linux/jiffies.h> #include "power.h" @@ -667,8 +668,13 @@ int dpm_sysfs_add(struct device *dev) if (rc) goto err_wakeup; } + rc = pm_wakeup_source_sysfs_add(dev); + if (rc) + goto err_latency; return 0; + err_latency: + sysfs_unmerge_group(&dev->kobj, &pm_qos_latency_tolerance_attr_group); err_wakeup: sysfs_unmerge_group(&dev->kobj, &pm_wakeup_attr_group); err_runtime: diff --git a/drivers/base/power/wakeup.c b/drivers/base/power/wakeup.c index ee31d4f8d856..5817b51d2b15 100644 --- a/drivers/base/power/wakeup.c +++ b/drivers/base/power/wakeup.c @@ -72,22 +72,7 @@ static struct wakeup_source deleted_ws = { .lock = __SPIN_LOCK_UNLOCKED(deleted_ws.lock), }; -/** - * wakeup_source_prepare - Prepare a new wakeup source for initialization. - * @ws: Wakeup source to prepare. - * @name: Pointer to the name of the new wakeup source. - * - * Callers must ensure that the @name string won't be freed when @ws is still in - * use. - */ -void wakeup_source_prepare(struct wakeup_source *ws, const char *name) -{ - if (ws) { - memset(ws, 0, sizeof(*ws)); - ws->name = name; - } -} -EXPORT_SYMBOL_GPL(wakeup_source_prepare); +static DEFINE_IDA(wakeup_ida); /** * wakeup_source_create - Create a struct wakeup_source object. @@ -96,13 +81,31 @@ EXPORT_SYMBOL_GPL(wakeup_source_prepare); struct wakeup_source *wakeup_source_create(const char *name) { struct wakeup_source *ws; + const char *ws_name; + int id; - ws = kmalloc(sizeof(*ws), GFP_KERNEL); + ws = kzalloc(sizeof(*ws), GFP_KERNEL); if (!ws) - return NULL; + goto err_ws; + + ws_name = kstrdup_const(name, GFP_KERNEL); + if (!ws_name) + goto err_name; + ws->name = ws_name; + + id = ida_alloc(&wakeup_ida, GFP_KERNEL); + if (id < 0) + goto err_id; + ws->id = id; - wakeup_source_prepare(ws, name ? kstrdup_const(name, GFP_KERNEL) : NULL); return ws; + +err_id: + kfree_const(ws->name); +err_name: + kfree(ws); +err_ws: + return NULL; } EXPORT_SYMBOL_GPL(wakeup_source_create); @@ -134,6 +137,13 @@ static void wakeup_source_record(struct wakeup_source *ws) spin_unlock_irqrestore(&deleted_ws.lock, flags); } +static void wakeup_source_free(struct wakeup_source *ws) +{ + ida_free(&wakeup_ida, ws->id); + kfree_const(ws->name); + kfree(ws); +} + /** * wakeup_source_destroy - Destroy a struct wakeup_source object. * @ws: Wakeup source to destroy. @@ -147,8 +157,7 @@ void wakeup_source_destroy(struct wakeup_source *ws) __pm_relax(ws); wakeup_source_record(ws); - kfree_const(ws->name); - kfree(ws); + wakeup_source_free(ws); } EXPORT_SYMBOL_GPL(wakeup_source_destroy); @@ -200,16 +209,26 @@ EXPORT_SYMBOL_GPL(wakeup_source_remove); /** * wakeup_source_register - Create wakeup source and add it to the list. + * @dev: Device this wakeup source is associated with (or NULL if virtual). * @name: Name of the wakeup source to register. */ -struct wakeup_source *wakeup_source_register(const char *name) +struct wakeup_source *wakeup_source_register(struct device *dev, + const char *name) { struct wakeup_source *ws; + int ret; ws = wakeup_source_create(name); - if (ws) + if (ws) { + if (!dev || device_is_registered(dev)) { + ret = wakeup_source_sysfs_add(dev, ws); + if (ret) { + wakeup_source_free(ws); + return NULL; + } + } wakeup_source_add(ws); - + } return ws; } EXPORT_SYMBOL_GPL(wakeup_source_register); @@ -222,6 +241,7 @@ void wakeup_source_unregister(struct wakeup_source *ws) { if (ws) { wakeup_source_remove(ws); + wakeup_source_sysfs_remove(ws); wakeup_source_destroy(ws); } } @@ -265,7 +285,7 @@ int device_wakeup_enable(struct device *dev) if (pm_suspend_target_state != PM_SUSPEND_ON) dev_dbg(dev, "Suspicious %s() during system transition!\n", __func__); - ws = wakeup_source_register(dev_name(dev)); + ws = wakeup_source_register(dev, dev_name(dev)); if (!ws) return -ENOMEM; @@ -859,7 +879,7 @@ EXPORT_SYMBOL_GPL(pm_system_wakeup); void pm_system_cancel_wakeup(void) { - atomic_dec(&pm_abort_suspend); + atomic_dec_if_positive(&pm_abort_suspend); } void pm_wakeup_clear(bool reset) diff --git a/drivers/base/power/wakeup_stats.c b/drivers/base/power/wakeup_stats.c new file mode 100644 index 000000000000..c7734914d914 --- /dev/null +++ b/drivers/base/power/wakeup_stats.c @@ -0,0 +1,214 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Wakeup statistics in sysfs + * + * Copyright (c) 2019 Linux Foundation + * Copyright (c) 2019 Greg Kroah-Hartman <gregkh@linuxfoundation.org> + * Copyright (c) 2019 Google Inc. + */ + +#include <linux/device.h> +#include <linux/idr.h> +#include <linux/init.h> +#include <linux/kdev_t.h> +#include <linux/kernel.h> +#include <linux/kobject.h> +#include <linux/slab.h> +#include <linux/timekeeping.h> + +#include "power.h" + +static struct class *wakeup_class; + +#define wakeup_attr(_name) \ +static ssize_t _name##_show(struct device *dev, \ + struct device_attribute *attr, char *buf) \ +{ \ + struct wakeup_source *ws = dev_get_drvdata(dev); \ + \ + return sprintf(buf, "%lu\n", ws->_name); \ +} \ +static DEVICE_ATTR_RO(_name) + +wakeup_attr(active_count); +wakeup_attr(event_count); +wakeup_attr(wakeup_count); +wakeup_attr(expire_count); + +static ssize_t active_time_ms_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct wakeup_source *ws = dev_get_drvdata(dev); + ktime_t active_time = + ws->active ? ktime_sub(ktime_get(), ws->last_time) : 0; + + return sprintf(buf, "%lld\n", ktime_to_ms(active_time)); +} +static DEVICE_ATTR_RO(active_time_ms); + +static ssize_t total_time_ms_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct wakeup_source *ws = dev_get_drvdata(dev); + ktime_t active_time; + ktime_t total_time = ws->total_time; + + if (ws->active) { + active_time = ktime_sub(ktime_get(), ws->last_time); + total_time = ktime_add(total_time, active_time); + } + return sprintf(buf, "%lld\n", ktime_to_ms(total_time)); +} +static DEVICE_ATTR_RO(total_time_ms); + +static ssize_t max_time_ms_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct wakeup_source *ws = dev_get_drvdata(dev); + ktime_t active_time; + ktime_t max_time = ws->max_time; + + if (ws->active) { + active_time = ktime_sub(ktime_get(), ws->last_time); + if (active_time > max_time) + max_time = active_time; + } + return sprintf(buf, "%lld\n", ktime_to_ms(max_time)); +} +static DEVICE_ATTR_RO(max_time_ms); + +static ssize_t last_change_ms_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct wakeup_source *ws = dev_get_drvdata(dev); + + return sprintf(buf, "%lld\n", ktime_to_ms(ws->last_time)); +} +static DEVICE_ATTR_RO(last_change_ms); + +static ssize_t name_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct wakeup_source *ws = dev_get_drvdata(dev); + + return sprintf(buf, "%s\n", ws->name); +} +static DEVICE_ATTR_RO(name); + +static ssize_t prevent_suspend_time_ms_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct wakeup_source *ws = dev_get_drvdata(dev); + ktime_t prevent_sleep_time = ws->prevent_sleep_time; + + if (ws->active && ws->autosleep_enabled) { + prevent_sleep_time = ktime_add(prevent_sleep_time, + ktime_sub(ktime_get(), ws->start_prevent_time)); + } + return sprintf(buf, "%lld\n", ktime_to_ms(prevent_sleep_time)); +} +static DEVICE_ATTR_RO(prevent_suspend_time_ms); + +static struct attribute *wakeup_source_attrs[] = { + &dev_attr_name.attr, + &dev_attr_active_count.attr, + &dev_attr_event_count.attr, + &dev_attr_wakeup_count.attr, + &dev_attr_expire_count.attr, + &dev_attr_active_time_ms.attr, + &dev_attr_total_time_ms.attr, + &dev_attr_max_time_ms.attr, + &dev_attr_last_change_ms.attr, + &dev_attr_prevent_suspend_time_ms.attr, + NULL, +}; +ATTRIBUTE_GROUPS(wakeup_source); + +static void device_create_release(struct device *dev) +{ + kfree(dev); +} + +static struct device *wakeup_source_device_create(struct device *parent, + struct wakeup_source *ws) +{ + struct device *dev = NULL; + int retval = -ENODEV; + + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) { + retval = -ENOMEM; + goto error; + } + + device_initialize(dev); + dev->devt = MKDEV(0, 0); + dev->class = wakeup_class; + dev->parent = parent; + dev->groups = wakeup_source_groups; + dev->release = device_create_release; + dev_set_drvdata(dev, ws); + device_set_pm_not_required(dev); + + retval = kobject_set_name(&dev->kobj, "wakeup%d", ws->id); + if (retval) + goto error; + + retval = device_add(dev); + if (retval) + goto error; + + return dev; + +error: + put_device(dev); + return ERR_PTR(retval); +} + +/** + * wakeup_source_sysfs_add - Add wakeup_source attributes to sysfs. + * @parent: Device given wakeup source is associated with (or NULL if virtual). + * @ws: Wakeup source to be added in sysfs. + */ +int wakeup_source_sysfs_add(struct device *parent, struct wakeup_source *ws) +{ + struct device *dev; + + dev = wakeup_source_device_create(parent, ws); + if (IS_ERR(dev)) + return PTR_ERR(dev); + ws->dev = dev; + + return 0; +} + +/** + * pm_wakeup_source_sysfs_add - Add wakeup_source attributes to sysfs + * for a device if they're missing. + * @parent: Device given wakeup source is associated with + */ +int pm_wakeup_source_sysfs_add(struct device *parent) +{ + if (!parent->power.wakeup || parent->power.wakeup->dev) + return 0; + + return wakeup_source_sysfs_add(parent, parent->power.wakeup); +} + +/** + * wakeup_source_sysfs_remove - Remove wakeup_source attributes from sysfs. + * @ws: Wakeup source to be removed from sysfs. + */ +void wakeup_source_sysfs_remove(struct wakeup_source *ws) +{ + device_unregister(ws->dev); +} + +static int __init wakeup_sources_sysfs_init(void) +{ + wakeup_class = class_create(THIS_MODULE, "wakeup"); + + return PTR_ERR_OR_ZERO(wakeup_class); +} +postcore_initcall(wakeup_sources_sysfs_init); |