1c8377adfSTri Vo // SPDX-License-Identifier: GPL-2.0
2c8377adfSTri Vo /*
3c8377adfSTri Vo * Wakeup statistics in sysfs
4c8377adfSTri Vo *
5c8377adfSTri Vo * Copyright (c) 2019 Linux Foundation
6c8377adfSTri Vo * Copyright (c) 2019 Greg Kroah-Hartman <gregkh@linuxfoundation.org>
7c8377adfSTri Vo * Copyright (c) 2019 Google Inc.
8c8377adfSTri Vo */
9c8377adfSTri Vo
10c8377adfSTri Vo #include <linux/device.h>
11c8377adfSTri Vo #include <linux/idr.h>
12c8377adfSTri Vo #include <linux/init.h>
13c8377adfSTri Vo #include <linux/kdev_t.h>
14c8377adfSTri Vo #include <linux/kernel.h>
15c8377adfSTri Vo #include <linux/kobject.h>
16c8377adfSTri Vo #include <linux/slab.h>
17c8377adfSTri Vo #include <linux/timekeeping.h>
18c8377adfSTri Vo
19c8377adfSTri Vo #include "power.h"
20c8377adfSTri Vo
21c8377adfSTri Vo static struct class *wakeup_class;
22c8377adfSTri Vo
23c8377adfSTri Vo #define wakeup_attr(_name) \
24c8377adfSTri Vo static ssize_t _name##_show(struct device *dev, \
25c8377adfSTri Vo struct device_attribute *attr, char *buf) \
26c8377adfSTri Vo { \
27c8377adfSTri Vo struct wakeup_source *ws = dev_get_drvdata(dev); \
28c8377adfSTri Vo \
29948b3edbSJoe Perches return sysfs_emit(buf, "%lu\n", ws->_name); \
30c8377adfSTri Vo } \
31c8377adfSTri Vo static DEVICE_ATTR_RO(_name)
32c8377adfSTri Vo
33c8377adfSTri Vo wakeup_attr(active_count);
34c8377adfSTri Vo wakeup_attr(event_count);
35c8377adfSTri Vo wakeup_attr(wakeup_count);
36c8377adfSTri Vo wakeup_attr(expire_count);
37c8377adfSTri Vo
active_time_ms_show(struct device * dev,struct device_attribute * attr,char * buf)38c8377adfSTri Vo static ssize_t active_time_ms_show(struct device *dev,
39c8377adfSTri Vo struct device_attribute *attr, char *buf)
40c8377adfSTri Vo {
41c8377adfSTri Vo struct wakeup_source *ws = dev_get_drvdata(dev);
42c8377adfSTri Vo ktime_t active_time =
43c8377adfSTri Vo ws->active ? ktime_sub(ktime_get(), ws->last_time) : 0;
44c8377adfSTri Vo
45aa838896SJoe Perches return sysfs_emit(buf, "%lld\n", ktime_to_ms(active_time));
46c8377adfSTri Vo }
47c8377adfSTri Vo static DEVICE_ATTR_RO(active_time_ms);
48c8377adfSTri Vo
total_time_ms_show(struct device * dev,struct device_attribute * attr,char * buf)49c8377adfSTri Vo static ssize_t total_time_ms_show(struct device *dev,
50c8377adfSTri Vo struct device_attribute *attr, char *buf)
51c8377adfSTri Vo {
52c8377adfSTri Vo struct wakeup_source *ws = dev_get_drvdata(dev);
53c8377adfSTri Vo ktime_t active_time;
54c8377adfSTri Vo ktime_t total_time = ws->total_time;
55c8377adfSTri Vo
56c8377adfSTri Vo if (ws->active) {
57c8377adfSTri Vo active_time = ktime_sub(ktime_get(), ws->last_time);
58c8377adfSTri Vo total_time = ktime_add(total_time, active_time);
59c8377adfSTri Vo }
60948b3edbSJoe Perches
61aa838896SJoe Perches return sysfs_emit(buf, "%lld\n", ktime_to_ms(total_time));
62c8377adfSTri Vo }
63c8377adfSTri Vo static DEVICE_ATTR_RO(total_time_ms);
64c8377adfSTri Vo
max_time_ms_show(struct device * dev,struct device_attribute * attr,char * buf)65c8377adfSTri Vo static ssize_t max_time_ms_show(struct device *dev,
66c8377adfSTri Vo struct device_attribute *attr, char *buf)
67c8377adfSTri Vo {
68c8377adfSTri Vo struct wakeup_source *ws = dev_get_drvdata(dev);
69c8377adfSTri Vo ktime_t active_time;
70c8377adfSTri Vo ktime_t max_time = ws->max_time;
71c8377adfSTri Vo
72c8377adfSTri Vo if (ws->active) {
73c8377adfSTri Vo active_time = ktime_sub(ktime_get(), ws->last_time);
74c8377adfSTri Vo if (active_time > max_time)
75c8377adfSTri Vo max_time = active_time;
76c8377adfSTri Vo }
77948b3edbSJoe Perches
78aa838896SJoe Perches return sysfs_emit(buf, "%lld\n", ktime_to_ms(max_time));
79c8377adfSTri Vo }
80c8377adfSTri Vo static DEVICE_ATTR_RO(max_time_ms);
81c8377adfSTri Vo
last_change_ms_show(struct device * dev,struct device_attribute * attr,char * buf)82c8377adfSTri Vo static ssize_t last_change_ms_show(struct device *dev,
83c8377adfSTri Vo struct device_attribute *attr, char *buf)
84c8377adfSTri Vo {
85c8377adfSTri Vo struct wakeup_source *ws = dev_get_drvdata(dev);
86c8377adfSTri Vo
87aa838896SJoe Perches return sysfs_emit(buf, "%lld\n", ktime_to_ms(ws->last_time));
88c8377adfSTri Vo }
89c8377adfSTri Vo static DEVICE_ATTR_RO(last_change_ms);
90c8377adfSTri Vo
name_show(struct device * dev,struct device_attribute * attr,char * buf)91c8377adfSTri Vo static ssize_t name_show(struct device *dev, struct device_attribute *attr,
92c8377adfSTri Vo char *buf)
93c8377adfSTri Vo {
94c8377adfSTri Vo struct wakeup_source *ws = dev_get_drvdata(dev);
95c8377adfSTri Vo
96aa838896SJoe Perches return sysfs_emit(buf, "%s\n", ws->name);
97c8377adfSTri Vo }
98c8377adfSTri Vo static DEVICE_ATTR_RO(name);
99c8377adfSTri Vo
prevent_suspend_time_ms_show(struct device * dev,struct device_attribute * attr,char * buf)100c8377adfSTri Vo static ssize_t prevent_suspend_time_ms_show(struct device *dev,
101c8377adfSTri Vo struct device_attribute *attr,
102c8377adfSTri Vo char *buf)
103c8377adfSTri Vo {
104c8377adfSTri Vo struct wakeup_source *ws = dev_get_drvdata(dev);
105c8377adfSTri Vo ktime_t prevent_sleep_time = ws->prevent_sleep_time;
106c8377adfSTri Vo
107c8377adfSTri Vo if (ws->active && ws->autosleep_enabled) {
108c8377adfSTri Vo prevent_sleep_time = ktime_add(prevent_sleep_time,
109c8377adfSTri Vo ktime_sub(ktime_get(), ws->start_prevent_time));
110c8377adfSTri Vo }
111948b3edbSJoe Perches
112aa838896SJoe Perches return sysfs_emit(buf, "%lld\n", ktime_to_ms(prevent_sleep_time));
113c8377adfSTri Vo }
114c8377adfSTri Vo static DEVICE_ATTR_RO(prevent_suspend_time_ms);
115c8377adfSTri Vo
116c8377adfSTri Vo static struct attribute *wakeup_source_attrs[] = {
117c8377adfSTri Vo &dev_attr_name.attr,
118c8377adfSTri Vo &dev_attr_active_count.attr,
119c8377adfSTri Vo &dev_attr_event_count.attr,
120c8377adfSTri Vo &dev_attr_wakeup_count.attr,
121c8377adfSTri Vo &dev_attr_expire_count.attr,
122c8377adfSTri Vo &dev_attr_active_time_ms.attr,
123c8377adfSTri Vo &dev_attr_total_time_ms.attr,
124c8377adfSTri Vo &dev_attr_max_time_ms.attr,
125c8377adfSTri Vo &dev_attr_last_change_ms.attr,
126c8377adfSTri Vo &dev_attr_prevent_suspend_time_ms.attr,
127c8377adfSTri Vo NULL,
128c8377adfSTri Vo };
129c8377adfSTri Vo ATTRIBUTE_GROUPS(wakeup_source);
130c8377adfSTri Vo
device_create_release(struct device * dev)131c8377adfSTri Vo static void device_create_release(struct device *dev)
132c8377adfSTri Vo {
133c8377adfSTri Vo kfree(dev);
134c8377adfSTri Vo }
135c8377adfSTri Vo
wakeup_source_device_create(struct device * parent,struct wakeup_source * ws)136c8377adfSTri Vo static struct device *wakeup_source_device_create(struct device *parent,
137c8377adfSTri Vo struct wakeup_source *ws)
138c8377adfSTri Vo {
139c8377adfSTri Vo struct device *dev = NULL;
140e4880233SColin Ian King int retval;
141c8377adfSTri Vo
142c8377adfSTri Vo dev = kzalloc(sizeof(*dev), GFP_KERNEL);
143c8377adfSTri Vo if (!dev) {
144c8377adfSTri Vo retval = -ENOMEM;
145c8377adfSTri Vo goto error;
146c8377adfSTri Vo }
147c8377adfSTri Vo
148c8377adfSTri Vo device_initialize(dev);
149c8377adfSTri Vo dev->devt = MKDEV(0, 0);
150c8377adfSTri Vo dev->class = wakeup_class;
151c8377adfSTri Vo dev->parent = parent;
152c8377adfSTri Vo dev->groups = wakeup_source_groups;
153c8377adfSTri Vo dev->release = device_create_release;
154c8377adfSTri Vo dev_set_drvdata(dev, ws);
155c8377adfSTri Vo device_set_pm_not_required(dev);
156c8377adfSTri Vo
1574da6d76fSAndy Shevchenko retval = dev_set_name(dev, "wakeup%d", ws->id);
158c8377adfSTri Vo if (retval)
159c8377adfSTri Vo goto error;
160c8377adfSTri Vo
161c8377adfSTri Vo retval = device_add(dev);
162c8377adfSTri Vo if (retval)
163c8377adfSTri Vo goto error;
164c8377adfSTri Vo
165c8377adfSTri Vo return dev;
166c8377adfSTri Vo
167c8377adfSTri Vo error:
168c8377adfSTri Vo put_device(dev);
169c8377adfSTri Vo return ERR_PTR(retval);
170c8377adfSTri Vo }
171c8377adfSTri Vo
172c8377adfSTri Vo /**
173c8377adfSTri Vo * wakeup_source_sysfs_add - Add wakeup_source attributes to sysfs.
174c8377adfSTri Vo * @parent: Device given wakeup source is associated with (or NULL if virtual).
175c8377adfSTri Vo * @ws: Wakeup source to be added in sysfs.
176c8377adfSTri Vo */
wakeup_source_sysfs_add(struct device * parent,struct wakeup_source * ws)177c8377adfSTri Vo int wakeup_source_sysfs_add(struct device *parent, struct wakeup_source *ws)
178c8377adfSTri Vo {
179c8377adfSTri Vo struct device *dev;
180c8377adfSTri Vo
181c8377adfSTri Vo dev = wakeup_source_device_create(parent, ws);
182c8377adfSTri Vo if (IS_ERR(dev))
183c8377adfSTri Vo return PTR_ERR(dev);
184c8377adfSTri Vo ws->dev = dev;
185c8377adfSTri Vo
186c8377adfSTri Vo return 0;
187c8377adfSTri Vo }
188c8377adfSTri Vo
189c8377adfSTri Vo /**
1902ca3d1ecSStephen Boyd * pm_wakeup_source_sysfs_add - Add wakeup_source attributes to sysfs
1912ca3d1ecSStephen Boyd * for a device if they're missing.
1922ca3d1ecSStephen Boyd * @parent: Device given wakeup source is associated with
1932ca3d1ecSStephen Boyd */
pm_wakeup_source_sysfs_add(struct device * parent)1942ca3d1ecSStephen Boyd int pm_wakeup_source_sysfs_add(struct device *parent)
1952ca3d1ecSStephen Boyd {
1962ca3d1ecSStephen Boyd if (!parent->power.wakeup || parent->power.wakeup->dev)
1972ca3d1ecSStephen Boyd return 0;
1982ca3d1ecSStephen Boyd
1992ca3d1ecSStephen Boyd return wakeup_source_sysfs_add(parent, parent->power.wakeup);
2002ca3d1ecSStephen Boyd }
2012ca3d1ecSStephen Boyd
2022ca3d1ecSStephen Boyd /**
203c8377adfSTri Vo * wakeup_source_sysfs_remove - Remove wakeup_source attributes from sysfs.
204c8377adfSTri Vo * @ws: Wakeup source to be removed from sysfs.
205c8377adfSTri Vo */
wakeup_source_sysfs_remove(struct wakeup_source * ws)206c8377adfSTri Vo void wakeup_source_sysfs_remove(struct wakeup_source *ws)
207c8377adfSTri Vo {
208c8377adfSTri Vo device_unregister(ws->dev);
209c8377adfSTri Vo }
210c8377adfSTri Vo
wakeup_sources_sysfs_init(void)211c8377adfSTri Vo static int __init wakeup_sources_sysfs_init(void)
212c8377adfSTri Vo {
213*1aaba11dSGreg Kroah-Hartman wakeup_class = class_create("wakeup");
214c8377adfSTri Vo
215c8377adfSTri Vo return PTR_ERR_OR_ZERO(wakeup_class);
216c8377adfSTri Vo }
217c8377adfSTri Vo postcore_initcall(wakeup_sources_sysfs_init);
218