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
7 Content-Type: text/plain; charset=UTF-8
8 Content-Transfer-Encoding: 8bit
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.
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>
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
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
36 +What: /sys/bus/i2c/devices/<mcu_device>/front_button_poweron
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
45 + This file configures whether front button power on is enabled.
47 + This file is present only if the power off feature is supported
52 What: /sys/bus/i2c/devices/<mcu_device>/fw_features
55 --- a/drivers/platform/cznic/Kconfig
56 +++ b/drivers/platform/cznic/Kconfig
57 @@ -18,10 +18,14 @@ config TURRIS_OMNIA_MCU
60 select GPIOLIB_IRQCHIP
63 Say Y here to add support for the features implemented by the
64 microcontroller on the CZ.NIC's Turris Omnia SOHO router.
66 + - board poweroff into true low power mode (with voltage regulators
67 + disabled) and the ability to configure wake up from this mode (via
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
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,
89 @@ -371,6 +372,10 @@ static int omnia_mcu_probe(struct i2c_cl
90 "Cannot read board info\n");
93 + err = omnia_mcu_register_sys_off_and_wakeup(mcu);
97 return omnia_mcu_register_gpiochip(mcu);
101 +++ b/drivers/platform/cznic/turris-omnia-mcu-sys-off-wakeup.c
103 +// SPDX-License-Identifier: GPL-2.0
105 + * CZ.NIC's Turris Omnia MCU system off and RTC wakeup driver
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.
113 + * 2024 by Marek Behún <kabel@kernel.org>
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>
127 +#include <linux/turris-omnia-mcu-interface.h>
128 +#include "turris-omnia-mcu.h"
130 +static int omnia_get_uptime_wakeup(const struct i2c_client *client, u32 *uptime,
136 + err = omnia_cmd_read(client, OMNIA_CMD_GET_UPTIME_AND_WAKEUP, reply,
142 + *uptime = le32_to_cpu(reply[0]);
145 + *wakeup = le32_to_cpu(reply[1]);
150 +static int omnia_read_time(struct device *dev, struct rtc_time *tm)
155 + err = omnia_get_uptime_wakeup(to_i2c_client(dev), &uptime, NULL);
159 + rtc_time64_to_tm(uptime, tm);
164 +static int omnia_read_alarm(struct device *dev, struct rtc_wkalrm *alrm)
166 + struct i2c_client *client = to_i2c_client(dev);
167 + struct omnia_mcu *mcu = i2c_get_clientdata(client);
171 + err = omnia_get_uptime_wakeup(client, NULL, &wakeup);
175 + alrm->enabled = !!wakeup;
176 + rtc_time64_to_tm(wakeup ?: mcu->rtc_alarm, &alrm->time);
181 +static int omnia_set_alarm(struct device *dev, struct rtc_wkalrm *alrm)
183 + struct i2c_client *client = to_i2c_client(dev);
184 + struct omnia_mcu *mcu = i2c_get_clientdata(client);
186 + mcu->rtc_alarm = rtc_tm_to_time64(&alrm->time);
189 + return omnia_cmd_write_u32(client, OMNIA_CMD_SET_WAKEUP,
195 +static int omnia_alarm_irq_enable(struct device *dev, unsigned int enabled)
197 + struct i2c_client *client = to_i2c_client(dev);
198 + struct omnia_mcu *mcu = i2c_get_clientdata(client);
200 + return omnia_cmd_write_u32(client, OMNIA_CMD_SET_WAKEUP,
201 + enabled ? mcu->rtc_alarm : 0);
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,
211 +static int omnia_power_off(struct sys_off_data *data)
213 + struct omnia_mcu *mcu = data->cb_data;
219 + if (mcu->front_button_poweron)
220 + arg = OMNIA_CMD_POWER_OFF_POWERON_BUTTON;
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]);
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.
233 + tmp = cpu_to_be32(get_unaligned_le32(&cmd[1]));
234 + put_unaligned_le32(crc32_be(~0, (void *)&tmp, sizeof(tmp)), &cmd[5]);
236 + err = omnia_cmd_write(mcu->client, cmd, sizeof(cmd));
238 + dev_err(&mcu->client->dev,
239 + "Unable to send the poweroff command: %d\n", err);
241 + return NOTIFY_DONE;
244 +static int omnia_restart(struct sys_off_data *data)
246 + struct omnia_mcu *mcu = data->cb_data;
250 + cmd[0] = OMNIA_CMD_GENERAL_CONTROL;
252 + if (reboot_mode == REBOOT_HARD)
253 + cmd[1] = cmd[2] = OMNIA_CTL_HARD_RST;
255 + cmd[1] = cmd[2] = OMNIA_CTL_LIGHT_RST;
257 + err = omnia_cmd_write(mcu->client, cmd, sizeof(cmd));
259 + dev_err(&mcu->client->dev,
260 + "Unable to send the restart command: %d\n", err);
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.
268 + return NOTIFY_DONE;
271 +static ssize_t front_button_poweron_show(struct device *dev,
272 + struct device_attribute *a, char *buf)
274 + struct omnia_mcu *mcu = dev_get_drvdata(dev);
276 + return sysfs_emit(buf, "%d\n", mcu->front_button_poweron);
279 +static ssize_t front_button_poweron_store(struct device *dev,
280 + struct device_attribute *a,
281 + const char *buf, size_t count)
283 + struct omnia_mcu *mcu = dev_get_drvdata(dev);
287 + err = kstrtobool(buf, &val);
291 + mcu->front_button_poweron = val;
295 +static DEVICE_ATTR_RW(front_button_poweron);
297 +static struct attribute *omnia_mcu_poweroff_attrs[] = {
298 + &dev_attr_front_button_poweron.attr,
302 +static umode_t poweroff_attrs_visible(struct kobject *kobj, struct attribute *a,
305 + struct device *dev = kobj_to_dev(kobj);
306 + struct omnia_mcu *mcu = dev_get_drvdata(dev);
308 + if (mcu->features & OMNIA_FEAT_POWEROFF_WAKEUP)
314 +const struct attribute_group omnia_mcu_poweroff_group = {
315 + .attrs = omnia_mcu_poweroff_attrs,
316 + .is_visible = poweroff_attrs_visible,
319 +int omnia_mcu_register_sys_off_and_wakeup(struct omnia_mcu *mcu)
321 + struct device *dev = &mcu->client->dev;
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);
329 + return dev_err_probe(dev, err,
330 + "Cannot register system restart handler\n");
333 + * Poweroff and wakeup are available only if POWEROFF_WAKEUP feature is
336 + if (!(mcu->features & OMNIA_FEAT_POWEROFF_WAKEUP))
339 + err = devm_register_sys_off_handler(dev, SYS_OFF_MODE_POWER_OFF,
340 + SYS_OFF_PRIO_FIRMWARE,
341 + omnia_power_off, mcu);
343 + return dev_err_probe(dev, err,
344 + "Cannot register system power off handler\n");
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");
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);
355 + err = devm_rtc_register_device(mcu->rtcdev);
357 + return dev_err_probe(dev, err, "Cannot register RTC device\n");
359 + mcu->front_button_poweron = true;
363 --- a/drivers/platform/cznic/turris-omnia-mcu.h
364 +++ b/drivers/platform/cznic/turris-omnia-mcu.h
366 #include <linux/types.h>
367 #include <linux/workqueue.h>
368 #include <asm/byteorder.h>
369 +#include <asm/unaligned.h>
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;
381 + /* RTC device for configuring wake-up */
382 + struct rtc_device *rtcdev;
384 + bool front_button_poweron;
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);
392 +static inline int omnia_cmd_write_u32(const struct i2c_client *client, u8 cmd,
398 + put_unaligned_le32(val, &buf[1]);
400 + return omnia_cmd_write(client, buf, sizeof(buf));
403 static inline int omnia_cmd_read(const struct i2c_client *client, u8 cmd,
404 void *reply, unsigned int len)
406 @@ -136,7 +154,9 @@ static inline int omnia_cmd_read_u8(cons
409 extern const struct attribute_group omnia_mcu_gpio_group;
410 +extern const struct attribute_group omnia_mcu_poweroff_group;
412 int omnia_mcu_register_gpiochip(struct omnia_mcu *mcu);
413 +int omnia_mcu_register_sys_off_and_wakeup(struct omnia_mcu *mcu);
415 #endif /* __TURRIS_OMNIA_MCU_H */