diff options
Diffstat (limited to 'drivers/rtc')
-rw-r--r-- | drivers/rtc/rtc-sysfs.c | 103 |
1 files changed, 101 insertions, 2 deletions
diff --git a/drivers/rtc/rtc-sysfs.c b/drivers/rtc/rtc-sysfs.c index 2ddd0cf07140..899ab8c514fa 100644 --- a/drivers/rtc/rtc-sysfs.c +++ b/drivers/rtc/rtc-sysfs.c | |||
@@ -78,6 +78,92 @@ static struct attribute_group rtc_attr_group = { | |||
78 | .attrs = rtc_attrs, | 78 | .attrs = rtc_attrs, |
79 | }; | 79 | }; |
80 | 80 | ||
81 | |||
82 | static ssize_t | ||
83 | rtc_sysfs_show_wakealarm(struct class_device *dev, char *buf) | ||
84 | { | ||
85 | ssize_t retval; | ||
86 | unsigned long alarm; | ||
87 | struct rtc_wkalrm alm; | ||
88 | |||
89 | /* Don't show disabled alarms; but the RTC could leave the | ||
90 | * alarm enabled after it's already triggered. Alarms are | ||
91 | * conceptually one-shot, even though some common hardware | ||
92 | * (PCs) doesn't actually work that way. | ||
93 | * | ||
94 | * REVISIT maybe we should require RTC implementations to | ||
95 | * disable the RTC alarm after it triggers, for uniformity. | ||
96 | */ | ||
97 | retval = rtc_read_alarm(dev, &alm); | ||
98 | if (retval == 0 && alm.enabled) { | ||
99 | rtc_tm_to_time(&alm.time, &alarm); | ||
100 | retval = sprintf(buf, "%lu\n", alarm); | ||
101 | } | ||
102 | |||
103 | return retval; | ||
104 | } | ||
105 | |||
106 | static ssize_t | ||
107 | rtc_sysfs_set_wakealarm(struct class_device *dev, const char *buf, size_t n) | ||
108 | { | ||
109 | ssize_t retval; | ||
110 | unsigned long now, alarm; | ||
111 | struct rtc_wkalrm alm; | ||
112 | |||
113 | /* Only request alarms that trigger in the future. Disable them | ||
114 | * by writing another time, e.g. 0 meaning Jan 1 1970 UTC. | ||
115 | */ | ||
116 | retval = rtc_read_time(dev, &alm.time); | ||
117 | if (retval < 0) | ||
118 | return retval; | ||
119 | rtc_tm_to_time(&alm.time, &now); | ||
120 | |||
121 | alarm = simple_strtoul(buf, NULL, 0); | ||
122 | if (alarm > now) { | ||
123 | /* Avoid accidentally clobbering active alarms; we can't | ||
124 | * entirely prevent that here, without even the minimal | ||
125 | * locking from the /dev/rtcN api. | ||
126 | */ | ||
127 | retval = rtc_read_alarm(dev, &alm); | ||
128 | if (retval < 0) | ||
129 | return retval; | ||
130 | if (alm.enabled) | ||
131 | return -EBUSY; | ||
132 | |||
133 | alm.enabled = 1; | ||
134 | } else { | ||
135 | alm.enabled = 0; | ||
136 | |||
137 | /* Provide a valid future alarm time. Linux isn't EFI, | ||
138 | * this time won't be ignored when disabling the alarm. | ||
139 | */ | ||
140 | alarm = now + 300; | ||
141 | } | ||
142 | rtc_time_to_tm(alarm, &alm.time); | ||
143 | |||
144 | retval = rtc_set_alarm(dev, &alm); | ||
145 | return (retval < 0) ? retval : n; | ||
146 | } | ||
147 | static const CLASS_DEVICE_ATTR(wakealarm, S_IRUGO | S_IWUSR, | ||
148 | rtc_sysfs_show_wakealarm, rtc_sysfs_set_wakealarm); | ||
149 | |||
150 | |||
151 | /* The reason to trigger an alarm with no process watching it (via sysfs) | ||
152 | * is its side effect: waking from a system state like suspend-to-RAM or | ||
153 | * suspend-to-disk. So: no attribute unless that side effect is possible. | ||
154 | * (Userspace may disable that mechanism later.) | ||
155 | */ | ||
156 | static inline int rtc_does_wakealarm(struct class_device *class_dev) | ||
157 | { | ||
158 | struct rtc_device *rtc; | ||
159 | |||
160 | if (!device_can_wakeup(class_dev->dev)) | ||
161 | return 0; | ||
162 | rtc = to_rtc_device(class_dev); | ||
163 | return rtc->ops->set_alarm != NULL; | ||
164 | } | ||
165 | |||
166 | |||
81 | static int rtc_sysfs_add_device(struct class_device *class_dev, | 167 | static int rtc_sysfs_add_device(struct class_device *class_dev, |
82 | struct class_interface *class_intf) | 168 | struct class_interface *class_intf) |
83 | { | 169 | { |
@@ -87,8 +173,18 @@ static int rtc_sysfs_add_device(struct class_device *class_dev, | |||
87 | 173 | ||
88 | err = sysfs_create_group(&class_dev->kobj, &rtc_attr_group); | 174 | err = sysfs_create_group(&class_dev->kobj, &rtc_attr_group); |
89 | if (err) | 175 | if (err) |
90 | dev_err(class_dev->dev, | 176 | dev_err(class_dev->dev, "failed to create %s\n", |
91 | "failed to create sysfs attributes\n"); | 177 | "sysfs attributes"); |
178 | else if (rtc_does_wakealarm(class_dev)) { | ||
179 | /* not all RTCs support both alarms and wakeup */ | ||
180 | err = class_device_create_file(class_dev, | ||
181 | &class_device_attr_wakealarm); | ||
182 | if (err) { | ||
183 | dev_err(class_dev->dev, "failed to create %s\n", | ||
184 | "alarm attribute"); | ||
185 | sysfs_remove_group(&class_dev->kobj, &rtc_attr_group); | ||
186 | } | ||
187 | } | ||
92 | 188 | ||
93 | return err; | 189 | return err; |
94 | } | 190 | } |
@@ -96,6 +192,9 @@ static int rtc_sysfs_add_device(struct class_device *class_dev, | |||
96 | static void rtc_sysfs_remove_device(struct class_device *class_dev, | 192 | static void rtc_sysfs_remove_device(struct class_device *class_dev, |
97 | struct class_interface *class_intf) | 193 | struct class_interface *class_intf) |
98 | { | 194 | { |
195 | if (rtc_does_wakealarm(class_dev)) | ||
196 | class_device_remove_file(class_dev, | ||
197 | &class_device_attr_wakealarm); | ||
99 | sysfs_remove_group(&class_dev->kobj, &rtc_attr_group); | 198 | sysfs_remove_group(&class_dev->kobj, &rtc_attr_group); |
100 | } | 199 | } |
101 | 200 | ||