]> git.ipfire.org Git - thirdparty/openwrt.git/blob
2ef6242d707e51f24b16e84c65deaa90bb531f15
[thirdparty/openwrt.git] /
1 From f69e0a731ab471f3a57c48258ad2d9990820c173 Mon Sep 17 00:00:00 2001
2 From: =?UTF-8?q?Marek=20Beh=C3=BAn?= <kabel@kernel.org>
3 Date: Mon, 1 Jul 2024 13:30:06 +0200
4 Subject: [PATCH 04/11] platform: cznic: turris-omnia-mcu: Add support for
5 poweroff and wakeup
6 MIME-Version: 1.0
7 Content-Type: text/plain; charset=UTF-8
8 Content-Transfer-Encoding: 8bit
9
10 Add support for true board poweroff (MCU can disable all unnecessary
11 voltage regulators) and wakeup at a specified time, implemented via a
12 RTC driver so that the rtcwake utility can be used to configure it.
13
14 Signed-off-by: Marek Behún <kabel@kernel.org>
15 Reviewed-by: Andy Shevchenko <andy@kernel.org>
16 Acked-by: Alexandre Belloni <alexandre.belloni@bootlin.com>
17 Acked-by: Bartosz Golaszewski <bartosz.golaszewski@linaro.org>
18 Link: https://lore.kernel.org/r/20240701113010.16447-5-kabel@kernel.org
19 Signed-off-by: Arnd Bergmann <arnd@arndb.de>
20 ---
21 .../sysfs-bus-i2c-devices-turris-omnia-mcu | 16 ++
22 drivers/platform/cznic/Kconfig | 4 +
23 drivers/platform/cznic/Makefile | 1 +
24 .../platform/cznic/turris-omnia-mcu-base.c | 5 +
25 .../cznic/turris-omnia-mcu-sys-off-wakeup.c | 260 ++++++++++++++++++
26 drivers/platform/cznic/turris-omnia-mcu.h | 20 ++
27 6 files changed, 306 insertions(+)
28 create mode 100644 drivers/platform/cznic/turris-omnia-mcu-sys-off-wakeup.c
29
30 --- a/Documentation/ABI/testing/sysfs-bus-i2c-devices-turris-omnia-mcu
31 +++ b/Documentation/ABI/testing/sysfs-bus-i2c-devices-turris-omnia-mcu
32 @@ -38,6 +38,22 @@ Description: (RW) The front button on th
33
34 Format: %s.
35
36 +What: /sys/bus/i2c/devices/<mcu_device>/front_button_poweron
37 +Date: September 2024
38 +KernelVersion: 6.11
39 +Contact: Marek Behún <kabel@kernel.org>
40 +Description: (RW) Newer versions of the microcontroller firmware of the
41 + Turris Omnia router support powering off the router into true
42 + low power mode. The router can be powered on by pressing the
43 + front button.
44 +
45 + This file configures whether front button power on is enabled.
46 +
47 + This file is present only if the power off feature is supported
48 + by the firmware.
49 +
50 + Format: %i.
51 +
52 What: /sys/bus/i2c/devices/<mcu_device>/fw_features
53 Date: September 2024
54 KernelVersion: 6.11
55 --- a/drivers/platform/cznic/Kconfig
56 +++ b/drivers/platform/cznic/Kconfig
57 @@ -18,10 +18,14 @@ config TURRIS_OMNIA_MCU
58 depends on I2C
59 select GPIOLIB
60 select GPIOLIB_IRQCHIP
61 + select RTC_CLASS
62 help
63 Say Y here to add support for the features implemented by the
64 microcontroller on the CZ.NIC's Turris Omnia SOHO router.
65 The features include:
66 + - board poweroff into true low power mode (with voltage regulators
67 + disabled) and the ability to configure wake up from this mode (via
68 + rtcwake)
69 - GPIO pins
70 - to get front button press events (the front button can be
71 configured either to generate press events to the CPU or to change
72 --- a/drivers/platform/cznic/Makefile
73 +++ b/drivers/platform/cznic/Makefile
74 @@ -3,3 +3,4 @@
75 obj-$(CONFIG_TURRIS_OMNIA_MCU) += turris-omnia-mcu.o
76 turris-omnia-mcu-y := turris-omnia-mcu-base.o
77 turris-omnia-mcu-y += turris-omnia-mcu-gpio.o
78 +turris-omnia-mcu-y += turris-omnia-mcu-sys-off-wakeup.o
79 --- a/drivers/platform/cznic/turris-omnia-mcu-base.c
80 +++ b/drivers/platform/cznic/turris-omnia-mcu-base.c
81 @@ -197,6 +197,7 @@ static const struct attribute_group omni
82 static const struct attribute_group *omnia_mcu_groups[] = {
83 &omnia_mcu_base_group,
84 &omnia_mcu_gpio_group,
85 + &omnia_mcu_poweroff_group,
86 NULL
87 };
88
89 @@ -371,6 +372,10 @@ static int omnia_mcu_probe(struct i2c_cl
90 "Cannot read board info\n");
91 }
92
93 + err = omnia_mcu_register_sys_off_and_wakeup(mcu);
94 + if (err)
95 + return err;
96 +
97 return omnia_mcu_register_gpiochip(mcu);
98 }
99
100 --- /dev/null
101 +++ b/drivers/platform/cznic/turris-omnia-mcu-sys-off-wakeup.c
102 @@ -0,0 +1,260 @@
103 +// SPDX-License-Identifier: GPL-2.0
104 +/*
105 + * CZ.NIC's Turris Omnia MCU system off and RTC wakeup driver
106 + *
107 + * This is not a true RTC driver (in the sense that it does not provide a
108 + * real-time clock), rather the MCU implements a wakeup from powered off state
109 + * at a specified time relative to MCU boot, and we expose this feature via RTC
110 + * alarm, so that it can be used via the rtcwake command, which is the standard
111 + * Linux command for this.
112 + *
113 + * 2024 by Marek Behún <kabel@kernel.org>
114 + */
115 +
116 +#include <linux/crc32.h>
117 +#include <linux/delay.h>
118 +#include <linux/device.h>
119 +#include <linux/err.h>
120 +#include <linux/i2c.h>
121 +#include <linux/kstrtox.h>
122 +#include <linux/reboot.h>
123 +#include <linux/rtc.h>
124 +#include <linux/sysfs.h>
125 +#include <linux/types.h>
126 +
127 +#include <linux/turris-omnia-mcu-interface.h>
128 +#include "turris-omnia-mcu.h"
129 +
130 +static int omnia_get_uptime_wakeup(const struct i2c_client *client, u32 *uptime,
131 + u32 *wakeup)
132 +{
133 + __le32 reply[2];
134 + int err;
135 +
136 + err = omnia_cmd_read(client, OMNIA_CMD_GET_UPTIME_AND_WAKEUP, reply,
137 + sizeof(reply));
138 + if (err)
139 + return err;
140 +
141 + if (uptime)
142 + *uptime = le32_to_cpu(reply[0]);
143 +
144 + if (wakeup)
145 + *wakeup = le32_to_cpu(reply[1]);
146 +
147 + return 0;
148 +}
149 +
150 +static int omnia_read_time(struct device *dev, struct rtc_time *tm)
151 +{
152 + u32 uptime;
153 + int err;
154 +
155 + err = omnia_get_uptime_wakeup(to_i2c_client(dev), &uptime, NULL);
156 + if (err)
157 + return err;
158 +
159 + rtc_time64_to_tm(uptime, tm);
160 +
161 + return 0;
162 +}
163 +
164 +static int omnia_read_alarm(struct device *dev, struct rtc_wkalrm *alrm)
165 +{
166 + struct i2c_client *client = to_i2c_client(dev);
167 + struct omnia_mcu *mcu = i2c_get_clientdata(client);
168 + u32 wakeup;
169 + int err;
170 +
171 + err = omnia_get_uptime_wakeup(client, NULL, &wakeup);
172 + if (err)
173 + return err;
174 +
175 + alrm->enabled = !!wakeup;
176 + rtc_time64_to_tm(wakeup ?: mcu->rtc_alarm, &alrm->time);
177 +
178 + return 0;
179 +}
180 +
181 +static int omnia_set_alarm(struct device *dev, struct rtc_wkalrm *alrm)
182 +{
183 + struct i2c_client *client = to_i2c_client(dev);
184 + struct omnia_mcu *mcu = i2c_get_clientdata(client);
185 +
186 + mcu->rtc_alarm = rtc_tm_to_time64(&alrm->time);
187 +
188 + if (alrm->enabled)
189 + return omnia_cmd_write_u32(client, OMNIA_CMD_SET_WAKEUP,
190 + mcu->rtc_alarm);
191 +
192 + return 0;
193 +}
194 +
195 +static int omnia_alarm_irq_enable(struct device *dev, unsigned int enabled)
196 +{
197 + struct i2c_client *client = to_i2c_client(dev);
198 + struct omnia_mcu *mcu = i2c_get_clientdata(client);
199 +
200 + return omnia_cmd_write_u32(client, OMNIA_CMD_SET_WAKEUP,
201 + enabled ? mcu->rtc_alarm : 0);
202 +}
203 +
204 +static const struct rtc_class_ops omnia_rtc_ops = {
205 + .read_time = omnia_read_time,
206 + .read_alarm = omnia_read_alarm,
207 + .set_alarm = omnia_set_alarm,
208 + .alarm_irq_enable = omnia_alarm_irq_enable,
209 +};
210 +
211 +static int omnia_power_off(struct sys_off_data *data)
212 +{
213 + struct omnia_mcu *mcu = data->cb_data;
214 + __be32 tmp;
215 + u8 cmd[9];
216 + u16 arg;
217 + int err;
218 +
219 + if (mcu->front_button_poweron)
220 + arg = OMNIA_CMD_POWER_OFF_POWERON_BUTTON;
221 + else
222 + arg = 0;
223 +
224 + cmd[0] = OMNIA_CMD_POWER_OFF;
225 + put_unaligned_le16(OMNIA_CMD_POWER_OFF_MAGIC, &cmd[1]);
226 + put_unaligned_le16(arg, &cmd[3]);
227 +
228 + /*
229 + * Although all values from and to MCU are passed in little-endian, the
230 + * MCU's CRC unit uses big-endian CRC32 polynomial (0x04c11db7), so we
231 + * need to use crc32_be() here.
232 + */
233 + tmp = cpu_to_be32(get_unaligned_le32(&cmd[1]));
234 + put_unaligned_le32(crc32_be(~0, (void *)&tmp, sizeof(tmp)), &cmd[5]);
235 +
236 + err = omnia_cmd_write(mcu->client, cmd, sizeof(cmd));
237 + if (err)
238 + dev_err(&mcu->client->dev,
239 + "Unable to send the poweroff command: %d\n", err);
240 +
241 + return NOTIFY_DONE;
242 +}
243 +
244 +static int omnia_restart(struct sys_off_data *data)
245 +{
246 + struct omnia_mcu *mcu = data->cb_data;
247 + u8 cmd[3];
248 + int err;
249 +
250 + cmd[0] = OMNIA_CMD_GENERAL_CONTROL;
251 +
252 + if (reboot_mode == REBOOT_HARD)
253 + cmd[1] = cmd[2] = OMNIA_CTL_HARD_RST;
254 + else
255 + cmd[1] = cmd[2] = OMNIA_CTL_LIGHT_RST;
256 +
257 + err = omnia_cmd_write(mcu->client, cmd, sizeof(cmd));
258 + if (err)
259 + dev_err(&mcu->client->dev,
260 + "Unable to send the restart command: %d\n", err);
261 +
262 + /*
263 + * MCU needs a little bit to process the I2C command, otherwise it will
264 + * do a light reset based on SOC SYSRES_OUT pin.
265 + */
266 + mdelay(1);
267 +
268 + return NOTIFY_DONE;
269 +}
270 +
271 +static ssize_t front_button_poweron_show(struct device *dev,
272 + struct device_attribute *a, char *buf)
273 +{
274 + struct omnia_mcu *mcu = dev_get_drvdata(dev);
275 +
276 + return sysfs_emit(buf, "%d\n", mcu->front_button_poweron);
277 +}
278 +
279 +static ssize_t front_button_poweron_store(struct device *dev,
280 + struct device_attribute *a,
281 + const char *buf, size_t count)
282 +{
283 + struct omnia_mcu *mcu = dev_get_drvdata(dev);
284 + bool val;
285 + int err;
286 +
287 + err = kstrtobool(buf, &val);
288 + if (err)
289 + return err;
290 +
291 + mcu->front_button_poweron = val;
292 +
293 + return count;
294 +}
295 +static DEVICE_ATTR_RW(front_button_poweron);
296 +
297 +static struct attribute *omnia_mcu_poweroff_attrs[] = {
298 + &dev_attr_front_button_poweron.attr,
299 + NULL
300 +};
301 +
302 +static umode_t poweroff_attrs_visible(struct kobject *kobj, struct attribute *a,
303 + int n)
304 +{
305 + struct device *dev = kobj_to_dev(kobj);
306 + struct omnia_mcu *mcu = dev_get_drvdata(dev);
307 +
308 + if (mcu->features & OMNIA_FEAT_POWEROFF_WAKEUP)
309 + return a->mode;
310 +
311 + return 0;
312 +}
313 +
314 +const struct attribute_group omnia_mcu_poweroff_group = {
315 + .attrs = omnia_mcu_poweroff_attrs,
316 + .is_visible = poweroff_attrs_visible,
317 +};
318 +
319 +int omnia_mcu_register_sys_off_and_wakeup(struct omnia_mcu *mcu)
320 +{
321 + struct device *dev = &mcu->client->dev;
322 + int err;
323 +
324 + /* MCU restart is always available */
325 + err = devm_register_sys_off_handler(dev, SYS_OFF_MODE_RESTART,
326 + SYS_OFF_PRIO_FIRMWARE,
327 + omnia_restart, mcu);
328 + if (err)
329 + return dev_err_probe(dev, err,
330 + "Cannot register system restart handler\n");
331 +
332 + /*
333 + * Poweroff and wakeup are available only if POWEROFF_WAKEUP feature is
334 + * present.
335 + */
336 + if (!(mcu->features & OMNIA_FEAT_POWEROFF_WAKEUP))
337 + return 0;
338 +
339 + err = devm_register_sys_off_handler(dev, SYS_OFF_MODE_POWER_OFF,
340 + SYS_OFF_PRIO_FIRMWARE,
341 + omnia_power_off, mcu);
342 + if (err)
343 + return dev_err_probe(dev, err,
344 + "Cannot register system power off handler\n");
345 +
346 + mcu->rtcdev = devm_rtc_allocate_device(dev);
347 + if (IS_ERR(mcu->rtcdev))
348 + return dev_err_probe(dev, PTR_ERR(mcu->rtcdev),
349 + "Cannot allocate RTC device\n");
350 +
351 + mcu->rtcdev->ops = &omnia_rtc_ops;
352 + mcu->rtcdev->range_max = U32_MAX;
353 + set_bit(RTC_FEATURE_ALARM_WAKEUP_ONLY, mcu->rtcdev->features);
354 +
355 + err = devm_rtc_register_device(mcu->rtcdev);
356 + if (err)
357 + return dev_err_probe(dev, err, "Cannot register RTC device\n");
358 +
359 + mcu->front_button_poweron = true;
360 +
361 + return 0;
362 +}
363 --- a/drivers/platform/cznic/turris-omnia-mcu.h
364 +++ b/drivers/platform/cznic/turris-omnia-mcu.h
365 @@ -15,8 +15,10 @@
366 #include <linux/types.h>
367 #include <linux/workqueue.h>
368 #include <asm/byteorder.h>
369 +#include <asm/unaligned.h>
370
371 struct i2c_client;
372 +struct rtc_device;
373
374 struct omnia_mcu {
375 struct i2c_client *client;
376 @@ -36,6 +38,11 @@ struct omnia_mcu {
377 struct delayed_work button_release_emul_work;
378 unsigned long last_status;
379 bool button_pressed_emul;
380 +
381 + /* RTC device for configuring wake-up */
382 + struct rtc_device *rtcdev;
383 + u32 rtc_alarm;
384 + bool front_button_poweron;
385 };
386
387 int omnia_cmd_write_read(const struct i2c_client *client,
388 @@ -48,6 +55,17 @@ static inline int omnia_cmd_write(const
389 return omnia_cmd_write_read(client, cmd, len, NULL, 0);
390 }
391
392 +static inline int omnia_cmd_write_u32(const struct i2c_client *client, u8 cmd,
393 + u32 val)
394 +{
395 + u8 buf[5];
396 +
397 + buf[0] = cmd;
398 + put_unaligned_le32(val, &buf[1]);
399 +
400 + return omnia_cmd_write(client, buf, sizeof(buf));
401 +}
402 +
403 static inline int omnia_cmd_read(const struct i2c_client *client, u8 cmd,
404 void *reply, unsigned int len)
405 {
406 @@ -136,7 +154,9 @@ static inline int omnia_cmd_read_u8(cons
407 }
408
409 extern const struct attribute_group omnia_mcu_gpio_group;
410 +extern const struct attribute_group omnia_mcu_poweroff_group;
411
412 int omnia_mcu_register_gpiochip(struct omnia_mcu *mcu);
413 +int omnia_mcu_register_sys_off_and_wakeup(struct omnia_mcu *mcu);
414
415 #endif /* __TURRIS_OMNIA_MCU_H */