Message ID | 20231026161803.16750-5-kabel@kernel.org (mailing list archive) |
---|---|
State | Not Applicable |
Delegated to: | Arnd Bergmann |
Headers | show |
Series | Turris Omnia MCU driver | expand |
On 26/10/2023 18:18:00+0200, Marek Behún wrote: > +static int omnia_get_uptime_wakeup(const struct i2c_client *client, u32 *uptime, > + u32 *wakeup) > +{ > + __le32 reply[2]; > + int err; > + > + err = omnia_cmd_read(client, CMD_GET_UPTIME_AND_WAKEUP, reply, > + sizeof(reply)); > + if (err) > + return err; > + > + if (uptime) > + *uptime = le32_to_cpu(reply[0]); > + > + if (wakeup) > + *wakeup = le32_to_cpu(reply[1]); > + > + return 0; > +} > + > +static int omnia_read_time(struct device *dev, struct rtc_time *tm) > +{ > + u32 uptime; > + int err; > + > + err = omnia_get_uptime_wakeup(to_i2c_client(dev), &uptime, NULL); > + if (err) > + return err; Does this get the real time or the board uptime? > + > + rtc_time64_to_tm(uptime, tm); > + > + return 0; > +} > + > +static int omnia_read_alarm(struct device *dev, struct rtc_wkalrm *alrm) > +{ > + struct i2c_client *client = to_i2c_client(dev); > + struct omnia_mcu *mcu = i2c_get_clientdata(client); > + u32 wakeup; > + int err; > + > + err = omnia_get_uptime_wakeup(client, NULL, &wakeup); > + if (err) > + return err; > + > + alrm->enabled = !!wakeup; > + rtc_time64_to_tm(wakeup ?: mcu->rtc_alarm, &alrm->time); I don't think this works properly as on boot, mcu->rtc_alarm will not be set whereas wakeup could be. Also, is wakeup actually an absolute time? I'm not sure I get how the MCU works then. > + > + return 0; > +} > + > +static int omnia_set_alarm(struct device *dev, struct rtc_wkalrm *alrm) > +{ > + struct i2c_client *client = to_i2c_client(dev); > + struct omnia_mcu *mcu = i2c_get_clientdata(client); > + > + mcu->rtc_alarm = rtc_tm_to_time64(&alrm->time); > + > + if (alrm->enabled) > + return omnia_cmd_write_u32(client, CMD_SET_WAKEUP, > + mcu->rtc_alarm); > + > + return 0; > +} > + > +static int omnia_alarm_irq_enable(struct device *dev, unsigned int enabled) > +{ > + struct i2c_client *client = to_i2c_client(dev); > + struct omnia_mcu *mcu = i2c_get_clientdata(client); > + > + return omnia_cmd_write_u32(client, CMD_SET_WAKEUP, > + enabled ? mcu->rtc_alarm : 0); > +} > + > +static const struct rtc_class_ops omnia_rtc_ops = { > + .read_time = omnia_read_time, > + .read_alarm = omnia_read_alarm, > + .set_alarm = omnia_set_alarm, > + .alarm_irq_enable = omnia_alarm_irq_enable, > +}; > + > +static int omnia_power_off(struct sys_off_data *data) > +{ > + struct omnia_mcu *mcu = data->cb_data; > + __be32 tmp; > + u8 cmd[9]; > + u16 arg; > + int err; > + > + if (mcu->front_button_poweron) > + arg = CMD_POWER_OFF_POWERON_BUTTON; > + else > + arg = 0; > + > + cmd[0] = CMD_POWER_OFF; > + put_unaligned_le16(CMD_POWER_OFF_MAGIC, &cmd[1]); > + put_unaligned_le16(arg, &cmd[3]); > + > + /* > + * Although all values from and to MCU are passed in little-endian, the > + * MCU's CRC unit uses big-endian CRC32 polynomial (0x04c11db7), so we > + * need to use crc32_be() here. > + */ > + tmp = cpu_to_be32(get_unaligned_le32(&cmd[1])); > + put_unaligned_le32(crc32_be(0xffffffff, (void *)&tmp, sizeof(tmp)), > + &cmd[5]); > + > + err = omnia_cmd_write(mcu->client, cmd, sizeof(cmd)); > + if (err) > + dev_err(&mcu->client->dev, > + "Unable to send the poweroff command: %d\n", err); > + > + return NOTIFY_DONE; > +} > + > +static int omnia_restart(struct sys_off_data *data) > +{ > + struct omnia_mcu *mcu = data->cb_data; > + u8 cmd[3]; > + int err; > + > + cmd[0] = CMD_GENERAL_CONTROL; > + > + if (reboot_mode == REBOOT_HARD) > + cmd[1] = cmd[2] = CTL_HARD_RST; > + else > + cmd[1] = cmd[2] = CTL_LIGHT_RST; > + > + err = omnia_cmd_write(mcu->client, cmd, sizeof(cmd)); > + if (err) > + dev_err(&mcu->client->dev, > + "Unable to send the restart command: %d\n", err); > + > + /* > + * MCU needs a little bit to process the I2C command, otherwise it will > + * do a light reset based on SOC SYSRES_OUT pin. > + */ > + mdelay(1); > + > + return NOTIFY_DONE; > +} > + > +static ssize_t front_button_poweron_show(struct device *dev, > + struct device_attribute *a, char *buf) > +{ > + struct omnia_mcu *mcu = i2c_get_clientdata(to_i2c_client(dev)); > + > + return sysfs_emit(buf, "%d\n", mcu->front_button_poweron); > +} > + > +static ssize_t front_button_poweron_store(struct device *dev, > + struct device_attribute *a, > + const char *buf, size_t count) > +{ > + struct omnia_mcu *mcu = i2c_get_clientdata(to_i2c_client(dev)); > + bool val; > + int err; > + > + err = kstrtobool(buf, &val); > + if (err) > + return err; > + > + mcu->front_button_poweron = val; > + > + return count; > +} > +static DEVICE_ATTR_RW(front_button_poweron); > + > +static struct attribute *omnia_mcu_poweroff_attrs[] = { > + &dev_attr_front_button_poweron.attr, > + NULL > +}; > + > +static umode_t poweroff_attrs_visible(struct kobject *kobj, struct attribute *a, > + int n) > +{ > + struct device *dev = kobj_to_dev(kobj); > + struct omnia_mcu *mcu = i2c_get_clientdata(to_i2c_client(dev)); > + > + if (mcu->features & FEAT_POWEROFF_WAKEUP) > + return a->mode; > + > + return 0; > +} > + > +const struct attribute_group omnia_mcu_poweroff_group = { > + .attrs = omnia_mcu_poweroff_attrs, > + .is_visible = poweroff_attrs_visible, > +}; > + > +int omnia_mcu_register_sys_off_and_wakeup(struct omnia_mcu *mcu) > +{ > + struct device *dev = &mcu->client->dev; > + int err; > + > + /* MCU restart is always available */ > + err = devm_register_sys_off_handler(dev, SYS_OFF_MODE_RESTART, > + SYS_OFF_PRIO_FIRMWARE, > + omnia_restart, mcu); > + if (err) > + return dev_err_probe(dev, err, > + "Cannot register system restart handler\n"); > + > + /* > + * Poweroff and wakeup are available only if POWEROFF_WAKEUP feature is > + * present. > + */ > + if (!(mcu->features & FEAT_POWEROFF_WAKEUP)) > + return 0; > + > + err = devm_register_sys_off_handler(dev, SYS_OFF_MODE_POWER_OFF, > + SYS_OFF_PRIO_FIRMWARE, > + omnia_power_off, mcu); > + if (err) > + return dev_err_probe(dev, err, > + "Cannot register system power off handler\n"); > + > + mcu->rtcdev = devm_rtc_allocate_device(dev); > + if (IS_ERR(mcu->rtcdev)) > + return dev_err_probe(dev, PTR_ERR(mcu->rtcdev), > + "Cannot allocate RTC device\n"); > + > + mcu->rtcdev->ops = &omnia_rtc_ops; > + mcu->rtcdev->range_max = U32_MAX; You probably need to add: set_bit(RTC_FEATURE_ALARM_WAKEUP_ONLY, mcu->rtcdev->features);
On Fri, 3 Nov 2023 20:23:16 +0100 Alexandre Belloni <alexandre.belloni@bootlin.com> wrote: > On 26/10/2023 18:18:00+0200, Marek Behún wrote: > > +static int omnia_get_uptime_wakeup(const struct i2c_client *client, u32 *uptime, > > + u32 *wakeup) > > +{ > > + __le32 reply[2]; > > + int err; > > + > > + err = omnia_cmd_read(client, CMD_GET_UPTIME_AND_WAKEUP, reply, > > + sizeof(reply)); > > + if (err) > > + return err; > > + > > + if (uptime) > > + *uptime = le32_to_cpu(reply[0]); > > + > > + if (wakeup) > > + *wakeup = le32_to_cpu(reply[1]); > > + > > + return 0; > > +} > > + > > +static int omnia_read_time(struct device *dev, struct rtc_time *tm) > > +{ > > + u32 uptime; > > + int err; > > + > > + err = omnia_get_uptime_wakeup(to_i2c_client(dev), &uptime, NULL); > > + if (err) > > + return err; > > Does this get the real time or the board uptime? Hi Alexandre, sorry I did not notice your email sooner. This returns board uptime. The MCU does not remember real time. > > + > > + rtc_time64_to_tm(uptime, tm); > > + > > + return 0; > > +} > > + > > +static int omnia_read_alarm(struct device *dev, struct rtc_wkalrm *alrm) > > +{ > > + struct i2c_client *client = to_i2c_client(dev); > > + struct omnia_mcu *mcu = i2c_get_clientdata(client); > > + u32 wakeup; > > + int err; > > + > > + err = omnia_get_uptime_wakeup(client, NULL, &wakeup); > > + if (err) > > + return err; > > + > > + alrm->enabled = !!wakeup; > > + rtc_time64_to_tm(wakeup ?: mcu->rtc_alarm, &alrm->time); > > I don't think this works properly as on boot, mcu->rtc_alarm will not be > set whereas wakeup could be. mcu->rtc_alarm is the value that is to be written to MCU wakeup register when we enable the RTC alarm with .alarm_irq_enable(). When the alarm is enabled by other means than the driver (prior boot or by a userspace utility), the omnia_read_alarm() will determine it correctly, since it will read via I2C the MCU wakeup value. If it is non-zero, it will return that the alarm is enabled and the alarm time is determined by this non-zero value. If the alarm is disabled on the MCU, we return that the alarm is disabled, but we fill in the alrm->time with the cached value of mcu->rtc_alarm which was set in previous call to the .set_alarm method. This is because we want to support both the new IOCTLs (RTC_WKALM_RD, RTC_WKALM_SET) and the old (RTC_ALM_SET, RTC_AIE_ON, RTC_AIE_OFF). > > Also, is wakeup actually an absolute time? I'm not sure I get how the > MCU works then. The wakeup value is in the same units as uptime: seconds elapsed since MCU started. Example: - uptime is 2000 seconds (MCU has been powered on 2000 seconds ago) - we are going to power off the board, but we want it to power on in one hour - we will therefore set wakeup to uptime + 3600 = 2000 + 3600 = 5600 > > + mcu->rtcdev->ops = &omnia_rtc_ops; > > + mcu->rtcdev->range_max = U32_MAX; > > You probably need to add: > > set_bit(RTC_FEATURE_ALARM_WAKEUP_ONLY, mcu->rtcdev->features); > Thx, will do. Is this bit documented? Could only find its definition and one usage with git grep. Marek
diff --git a/Documentation/ABI/testing/sysfs-bus-i2c-devices-turris-omnia-mcu b/Documentation/ABI/testing/sysfs-bus-i2c-devices-turris-omnia-mcu index e635ba45ef9a..a69172d5b998 100644 --- a/Documentation/ABI/testing/sysfs-bus-i2c-devices-turris-omnia-mcu +++ b/Documentation/ABI/testing/sysfs-bus-i2c-devices-turris-omnia-mcu @@ -14,6 +14,22 @@ Description: (RW) The front button on the Turris Omnia router can be Format: %s. +What: /sys/bus/i2c/devices/<mcu_device>/front_button_poweron +Date: August 2023 +KernelVersion: 6.6 +Contact: Marek Behún <kabel@kernel.org> +Description: (RW) Newer versions of the microcontroller firmware of the + Turris Omnia router support powering off the router into true + low power mode. The router can be powered on by pressing the + front button. + + This file configures whether front button power on is enabled. + + This file is present only if the power off feature is supported + by the firmware. + + Format: %i. + What: /sys/bus/i2c/devices/<mcu_device>/fw_features Date: November 2023 KernelVersion: 6.7 diff --git a/drivers/platform/cznic/Kconfig b/drivers/platform/cznic/Kconfig index 3a8c3edcd7e6..0a752aa654fa 100644 --- a/drivers/platform/cznic/Kconfig +++ b/drivers/platform/cznic/Kconfig @@ -19,10 +19,14 @@ config TURRIS_OMNIA_MCU depends on I2C select GPIOLIB select GPIOLIB_IRQCHIP + select RTC_CLASS help Say Y here to add support for the features implemented by the microcontroller on the CZ.NIC's Turris Omnia SOHO router. The features include: + - board poweroff into true low power mode (with voltage regulators + disabled) and the ability to configure wake up from this mode (via + rtcwake) - GPIO pins - to get front button press events (the front button can be configured either to generate press events to the CPU or to change diff --git a/drivers/platform/cznic/Makefile b/drivers/platform/cznic/Makefile index a6177f5b4fff..6f1470d1f673 100644 --- a/drivers/platform/cznic/Makefile +++ b/drivers/platform/cznic/Makefile @@ -3,3 +3,4 @@ obj-$(CONFIG_TURRIS_OMNIA_MCU) += turris-omnia-mcu.o turris-omnia-mcu-objs := turris-omnia-mcu-base.o turris-omnia-mcu-objs += turris-omnia-mcu-gpio.o +turris-omnia-mcu-objs += turris-omnia-mcu-sys-off-wakeup.o diff --git a/drivers/platform/cznic/turris-omnia-mcu-base.c b/drivers/platform/cznic/turris-omnia-mcu-base.c index 52d34660a9a5..7764107c6e19 100644 --- a/drivers/platform/cznic/turris-omnia-mcu-base.c +++ b/drivers/platform/cznic/turris-omnia-mcu-base.c @@ -113,6 +113,7 @@ static const struct attribute_group omnia_mcu_base_group = { static const struct attribute_group *omnia_mcu_groups[] = { &omnia_mcu_base_group, &omnia_mcu_gpio_group, + &omnia_mcu_poweroff_group, NULL }; @@ -238,6 +239,10 @@ static int omnia_mcu_probe(struct i2c_client *client) return dev_err_probe(dev, err, "Cannot determine MCU supported features\n"); + err = omnia_mcu_register_sys_off_and_wakeup(mcu); + if (err) + return err; + return omnia_mcu_register_gpiochip(mcu); } diff --git a/drivers/platform/cznic/turris-omnia-mcu-sys-off-wakeup.c b/drivers/platform/cznic/turris-omnia-mcu-sys-off-wakeup.c new file mode 100644 index 000000000000..a2c88a1c5a28 --- /dev/null +++ b/drivers/platform/cznic/turris-omnia-mcu-sys-off-wakeup.c @@ -0,0 +1,257 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * CZ.NIC's Turris Omnia MCU system off and RTC wakeup driver + * + * This is not a true RTC driver (in the sense that it does not provide a + * real-time clock), rather the MCU implements a wakeup from powered off state + * at a specified time relative to MCU boot, and we expose this feature via RTC + * alarm, so that it can be used via the rtcwake command, which is the standard + * Linux command for this. + * + * 2023 by Marek Behún <kabel@kernel.org> + */ + +#include <linux/crc32.h> +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/reboot.h> +#include <linux/rtc.h> +#include <linux/sysfs.h> +#include <linux/turris-omnia-mcu-interface.h> +#include <linux/types.h> + +#include "turris-omnia-mcu.h" + +static int omnia_get_uptime_wakeup(const struct i2c_client *client, u32 *uptime, + u32 *wakeup) +{ + __le32 reply[2]; + int err; + + err = omnia_cmd_read(client, CMD_GET_UPTIME_AND_WAKEUP, reply, + sizeof(reply)); + if (err) + return err; + + if (uptime) + *uptime = le32_to_cpu(reply[0]); + + if (wakeup) + *wakeup = le32_to_cpu(reply[1]); + + return 0; +} + +static int omnia_read_time(struct device *dev, struct rtc_time *tm) +{ + u32 uptime; + int err; + + err = omnia_get_uptime_wakeup(to_i2c_client(dev), &uptime, NULL); + if (err) + return err; + + rtc_time64_to_tm(uptime, tm); + + return 0; +} + +static int omnia_read_alarm(struct device *dev, struct rtc_wkalrm *alrm) +{ + struct i2c_client *client = to_i2c_client(dev); + struct omnia_mcu *mcu = i2c_get_clientdata(client); + u32 wakeup; + int err; + + err = omnia_get_uptime_wakeup(client, NULL, &wakeup); + if (err) + return err; + + alrm->enabled = !!wakeup; + rtc_time64_to_tm(wakeup ?: mcu->rtc_alarm, &alrm->time); + + return 0; +} + +static int omnia_set_alarm(struct device *dev, struct rtc_wkalrm *alrm) +{ + struct i2c_client *client = to_i2c_client(dev); + struct omnia_mcu *mcu = i2c_get_clientdata(client); + + mcu->rtc_alarm = rtc_tm_to_time64(&alrm->time); + + if (alrm->enabled) + return omnia_cmd_write_u32(client, CMD_SET_WAKEUP, + mcu->rtc_alarm); + + return 0; +} + +static int omnia_alarm_irq_enable(struct device *dev, unsigned int enabled) +{ + struct i2c_client *client = to_i2c_client(dev); + struct omnia_mcu *mcu = i2c_get_clientdata(client); + + return omnia_cmd_write_u32(client, CMD_SET_WAKEUP, + enabled ? mcu->rtc_alarm : 0); +} + +static const struct rtc_class_ops omnia_rtc_ops = { + .read_time = omnia_read_time, + .read_alarm = omnia_read_alarm, + .set_alarm = omnia_set_alarm, + .alarm_irq_enable = omnia_alarm_irq_enable, +}; + +static int omnia_power_off(struct sys_off_data *data) +{ + struct omnia_mcu *mcu = data->cb_data; + __be32 tmp; + u8 cmd[9]; + u16 arg; + int err; + + if (mcu->front_button_poweron) + arg = CMD_POWER_OFF_POWERON_BUTTON; + else + arg = 0; + + cmd[0] = CMD_POWER_OFF; + put_unaligned_le16(CMD_POWER_OFF_MAGIC, &cmd[1]); + put_unaligned_le16(arg, &cmd[3]); + + /* + * Although all values from and to MCU are passed in little-endian, the + * MCU's CRC unit uses big-endian CRC32 polynomial (0x04c11db7), so we + * need to use crc32_be() here. + */ + tmp = cpu_to_be32(get_unaligned_le32(&cmd[1])); + put_unaligned_le32(crc32_be(0xffffffff, (void *)&tmp, sizeof(tmp)), + &cmd[5]); + + err = omnia_cmd_write(mcu->client, cmd, sizeof(cmd)); + if (err) + dev_err(&mcu->client->dev, + "Unable to send the poweroff command: %d\n", err); + + return NOTIFY_DONE; +} + +static int omnia_restart(struct sys_off_data *data) +{ + struct omnia_mcu *mcu = data->cb_data; + u8 cmd[3]; + int err; + + cmd[0] = CMD_GENERAL_CONTROL; + + if (reboot_mode == REBOOT_HARD) + cmd[1] = cmd[2] = CTL_HARD_RST; + else + cmd[1] = cmd[2] = CTL_LIGHT_RST; + + err = omnia_cmd_write(mcu->client, cmd, sizeof(cmd)); + if (err) + dev_err(&mcu->client->dev, + "Unable to send the restart command: %d\n", err); + + /* + * MCU needs a little bit to process the I2C command, otherwise it will + * do a light reset based on SOC SYSRES_OUT pin. + */ + mdelay(1); + + return NOTIFY_DONE; +} + +static ssize_t front_button_poweron_show(struct device *dev, + struct device_attribute *a, char *buf) +{ + struct omnia_mcu *mcu = i2c_get_clientdata(to_i2c_client(dev)); + + return sysfs_emit(buf, "%d\n", mcu->front_button_poweron); +} + +static ssize_t front_button_poweron_store(struct device *dev, + struct device_attribute *a, + const char *buf, size_t count) +{ + struct omnia_mcu *mcu = i2c_get_clientdata(to_i2c_client(dev)); + bool val; + int err; + + err = kstrtobool(buf, &val); + if (err) + return err; + + mcu->front_button_poweron = val; + + return count; +} +static DEVICE_ATTR_RW(front_button_poweron); + +static struct attribute *omnia_mcu_poweroff_attrs[] = { + &dev_attr_front_button_poweron.attr, + NULL +}; + +static umode_t poweroff_attrs_visible(struct kobject *kobj, struct attribute *a, + int n) +{ + struct device *dev = kobj_to_dev(kobj); + struct omnia_mcu *mcu = i2c_get_clientdata(to_i2c_client(dev)); + + if (mcu->features & FEAT_POWEROFF_WAKEUP) + return a->mode; + + return 0; +} + +const struct attribute_group omnia_mcu_poweroff_group = { + .attrs = omnia_mcu_poweroff_attrs, + .is_visible = poweroff_attrs_visible, +}; + +int omnia_mcu_register_sys_off_and_wakeup(struct omnia_mcu *mcu) +{ + struct device *dev = &mcu->client->dev; + int err; + + /* MCU restart is always available */ + err = devm_register_sys_off_handler(dev, SYS_OFF_MODE_RESTART, + SYS_OFF_PRIO_FIRMWARE, + omnia_restart, mcu); + if (err) + return dev_err_probe(dev, err, + "Cannot register system restart handler\n"); + + /* + * Poweroff and wakeup are available only if POWEROFF_WAKEUP feature is + * present. + */ + if (!(mcu->features & FEAT_POWEROFF_WAKEUP)) + return 0; + + err = devm_register_sys_off_handler(dev, SYS_OFF_MODE_POWER_OFF, + SYS_OFF_PRIO_FIRMWARE, + omnia_power_off, mcu); + if (err) + return dev_err_probe(dev, err, + "Cannot register system power off handler\n"); + + mcu->rtcdev = devm_rtc_allocate_device(dev); + if (IS_ERR(mcu->rtcdev)) + return dev_err_probe(dev, PTR_ERR(mcu->rtcdev), + "Cannot allocate RTC device\n"); + + mcu->rtcdev->ops = &omnia_rtc_ops; + mcu->rtcdev->range_max = U32_MAX; + + err = devm_rtc_register_device(mcu->rtcdev); + if (err) + return dev_err_probe(dev, err, "Cannot register RTC device\n"); + + mcu->front_button_poweron = true; + + return 0; +} diff --git a/drivers/platform/cznic/turris-omnia-mcu.h b/drivers/platform/cznic/turris-omnia-mcu.h index 9b7d01dafd60..04de7e7cd693 100644 --- a/drivers/platform/cznic/turris-omnia-mcu.h +++ b/drivers/platform/cznic/turris-omnia-mcu.h @@ -12,9 +12,11 @@ #include <linux/gpio/driver.h> #include <linux/i2c.h> #include <linux/mutex.h> +#include <linux/rtc.h> #include <linux/types.h> #include <linux/workqueue.h> #include <asm/byteorder.h> +#include <asm/unaligned.h> struct omnia_mcu { struct i2c_client *client; @@ -29,6 +31,11 @@ struct omnia_mcu { struct delayed_work button_release_emul_work; u16 last_status; bool button_pressed_emul; + + /* RTC device for configuring wake-up */ + struct rtc_device *rtcdev; + u32 rtc_alarm; + bool front_button_poweron; }; static inline int omnia_cmd_write(const struct i2c_client *client, void *cmd, @@ -41,6 +48,17 @@ static inline int omnia_cmd_write(const struct i2c_client *client, void *cmd, return ret < 0 ? ret : 0; } +static inline int omnia_cmd_write_u32(const struct i2c_client *client, u8 cmd, + u32 val) +{ + u8 buf[5]; + + buf[0] = cmd; + put_unaligned_le32(val, &buf[1]); + + return omnia_cmd_write(client, buf, sizeof(buf)); +} + static inline int omnia_cmd_read(const struct i2c_client *client, u8 cmd, void *reply, unsigned int len) { @@ -120,7 +138,9 @@ static inline int omnia_cmd_read_u8(const struct i2c_client *client, u8 cmd) } extern const struct attribute_group omnia_mcu_gpio_group; +extern const struct attribute_group omnia_mcu_poweroff_group; int omnia_mcu_register_gpiochip(struct omnia_mcu *mcu); +int omnia_mcu_register_sys_off_and_wakeup(struct omnia_mcu *mcu); #endif /* __TURRIS_OMNIA_MCU_H */
Add support for true board poweroff (MCU can disable all unnecessary voltage regulators) and wakeup at a specified time, implemented via a RTC driver so that the rtcwake utility can be used to configure it. Signed-off-by: Marek Behún <kabel@kernel.org> --- .../sysfs-bus-i2c-devices-turris-omnia-mcu | 16 ++ drivers/platform/cznic/Kconfig | 4 + drivers/platform/cznic/Makefile | 1 + .../platform/cznic/turris-omnia-mcu-base.c | 5 + .../cznic/turris-omnia-mcu-sys-off-wakeup.c | 257 ++++++++++++++++++ drivers/platform/cznic/turris-omnia-mcu.h | 20 ++ 6 files changed, 303 insertions(+) create mode 100644 drivers/platform/cznic/turris-omnia-mcu-sys-off-wakeup.c