platform: cznic: turris-omnia-mcu: Add support for MCU watchdog
authorMarek Behún <kabel@kernel.org>
Mon, 1 Jul 2024 11:30:07 +0000 (13:30 +0200)
committerArnd Bergmann <arnd@arndb.de>
Mon, 1 Jul 2024 13:46:37 +0000 (15:46 +0200)
Add support for the watchdog mechanism provided by the MCU.

Signed-off-by: Marek Behún <kabel@kernel.org>
Reviewed-by: Andy Shevchenko <andy@kernel.org>
Reviewed-by: Guenter Roeck <linux@roeck-us.net>
Acked-by: Bartosz Golaszewski <bartosz.golaszewski@linaro.org>
Link: https://lore.kernel.org/r/20240701113010.16447-6-kabel@kernel.org
Signed-off-by: Arnd Bergmann <arnd@arndb.de>
drivers/platform/cznic/Kconfig
drivers/platform/cznic/Makefile
drivers/platform/cznic/turris-omnia-mcu-base.c
drivers/platform/cznic/turris-omnia-mcu-watchdog.c [new file with mode: 0644]
drivers/platform/cznic/turris-omnia-mcu.h

index c1e7192355174bd9a95a46a5e7bde52a692f105c..e262930b3faf68a7f4fa75c57cb5afaba8ab5c40 100644 (file)
@@ -19,6 +19,7 @@ config TURRIS_OMNIA_MCU
        select GPIOLIB
        select GPIOLIB_IRQCHIP
        select RTC_CLASS
+       select WATCHDOG_CORE
        help
          Say Y here to add support for the features implemented by the
          microcontroller on the CZ.NIC's Turris Omnia SOHO router.
@@ -26,6 +27,7 @@ config TURRIS_OMNIA_MCU
          - board poweroff into true low power mode (with voltage regulators
            disabled) and the ability to configure wake up from this mode (via
            rtcwake)
+         - MCU watchdog
          - 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
index a185ef882e44bf6b98cab3ba994fa32a6e31c913..687f7718c0a16f994016157c79a908516f10e41b 100644 (file)
@@ -4,3 +4,4 @@ obj-$(CONFIG_TURRIS_OMNIA_MCU)  += turris-omnia-mcu.o
 turris-omnia-mcu-y             := turris-omnia-mcu-base.o
 turris-omnia-mcu-y             += turris-omnia-mcu-gpio.o
 turris-omnia-mcu-y             += turris-omnia-mcu-sys-off-wakeup.o
+turris-omnia-mcu-y             += turris-omnia-mcu-watchdog.o
index 338a7ab12bd09f76e37c5ba6485ac4bef6edad72..2d8e5c9a8dd104011964b7127b76c2cdac6d1fb4 100644 (file)
@@ -377,6 +377,10 @@ static int omnia_mcu_probe(struct i2c_client *client)
        if (err)
                return err;
 
+       err = omnia_mcu_register_watchdog(mcu);
+       if (err)
+               return err;
+
        return omnia_mcu_register_gpiochip(mcu);
 }
 
diff --git a/drivers/platform/cznic/turris-omnia-mcu-watchdog.c b/drivers/platform/cznic/turris-omnia-mcu-watchdog.c
new file mode 100644 (file)
index 0000000..3ad146e
--- /dev/null
@@ -0,0 +1,130 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * CZ.NIC's Turris Omnia MCU watchdog driver
+ *
+ * 2024 by Marek Behún <kabel@kernel.org>
+ */
+
+#include <linux/bitops.h>
+#include <linux/device.h>
+#include <linux/i2c.h>
+#include <linux/moduleparam.h>
+#include <linux/types.h>
+#include <linux/units.h>
+#include <linux/watchdog.h>
+
+#include <linux/turris-omnia-mcu-interface.h>
+#include "turris-omnia-mcu.h"
+
+#define WATCHDOG_TIMEOUT               120
+
+static unsigned int timeout;
+module_param(timeout, int, 0);
+MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds");
+
+static bool nowayout = WATCHDOG_NOWAYOUT;
+module_param(nowayout, bool, 0);
+MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default="
+                          __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
+
+static int omnia_wdt_start(struct watchdog_device *wdt)
+{
+       struct omnia_mcu *mcu = watchdog_get_drvdata(wdt);
+
+       return omnia_cmd_write_u8(mcu->client, OMNIA_CMD_SET_WATCHDOG_STATE, 1);
+}
+
+static int omnia_wdt_stop(struct watchdog_device *wdt)
+{
+       struct omnia_mcu *mcu = watchdog_get_drvdata(wdt);
+
+       return omnia_cmd_write_u8(mcu->client, OMNIA_CMD_SET_WATCHDOG_STATE, 0);
+}
+
+static int omnia_wdt_ping(struct watchdog_device *wdt)
+{
+       struct omnia_mcu *mcu = watchdog_get_drvdata(wdt);
+
+       return omnia_cmd_write_u8(mcu->client, OMNIA_CMD_SET_WATCHDOG_STATE, 1);
+}
+
+static int omnia_wdt_set_timeout(struct watchdog_device *wdt,
+                                unsigned int timeout)
+{
+       struct omnia_mcu *mcu = watchdog_get_drvdata(wdt);
+
+       return omnia_cmd_write_u16(mcu->client, OMNIA_CMD_SET_WDT_TIMEOUT,
+                                  timeout * DECI);
+}
+
+static unsigned int omnia_wdt_get_timeleft(struct watchdog_device *wdt)
+{
+       struct omnia_mcu *mcu = watchdog_get_drvdata(wdt);
+       u16 timeleft;
+       int err;
+
+       err = omnia_cmd_read_u16(mcu->client, OMNIA_CMD_GET_WDT_TIMELEFT,
+                                &timeleft);
+       if (err) {
+               dev_err(&mcu->client->dev, "Cannot get watchdog timeleft: %d\n",
+                       err);
+               return 0;
+       }
+
+       return timeleft / DECI;
+}
+
+static const struct watchdog_info omnia_wdt_info = {
+       .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
+       .identity = "Turris Omnia MCU Watchdog",
+};
+
+static const struct watchdog_ops omnia_wdt_ops = {
+       .owner          = THIS_MODULE,
+       .start          = omnia_wdt_start,
+       .stop           = omnia_wdt_stop,
+       .ping           = omnia_wdt_ping,
+       .set_timeout    = omnia_wdt_set_timeout,
+       .get_timeleft   = omnia_wdt_get_timeleft,
+};
+
+int omnia_mcu_register_watchdog(struct omnia_mcu *mcu)
+{
+       struct device *dev = &mcu->client->dev;
+       u8 state;
+       int err;
+
+       if (!(mcu->features & OMNIA_FEAT_WDT_PING))
+               return 0;
+
+       mcu->wdt.info = &omnia_wdt_info;
+       mcu->wdt.ops = &omnia_wdt_ops;
+       mcu->wdt.parent = dev;
+       mcu->wdt.min_timeout = 1;
+       mcu->wdt.max_timeout = 65535 / DECI;
+
+       mcu->wdt.timeout = WATCHDOG_TIMEOUT;
+       watchdog_init_timeout(&mcu->wdt, timeout, dev);
+
+       watchdog_set_drvdata(&mcu->wdt, mcu);
+
+       omnia_wdt_set_timeout(&mcu->wdt, mcu->wdt.timeout);
+
+       err = omnia_cmd_read_u8(mcu->client, OMNIA_CMD_GET_WATCHDOG_STATE,
+                               &state);
+       if (err)
+               return dev_err_probe(dev, err,
+                                    "Cannot get MCU watchdog state\n");
+
+       if (state)
+               set_bit(WDOG_HW_RUNNING, &mcu->wdt.status);
+
+       watchdog_set_nowayout(&mcu->wdt, nowayout);
+       watchdog_stop_on_reboot(&mcu->wdt);
+       err = devm_watchdog_register_device(dev, &mcu->wdt);
+       if (err)
+               return dev_err_probe(dev, err,
+                                    "Cannot register MCU watchdog\n");
+
+       return 0;
+}
index 2a1c68ba9b7f76f2ce1cdaeb0e01001dccf88ae0..dc59f415f611035e3e54117dbc5f12a0b8fb2563 100644 (file)
@@ -13,6 +13,7 @@
 #include <linux/if_ether.h>
 #include <linux/mutex.h>
 #include <linux/types.h>
+#include <linux/watchdog.h>
 #include <linux/workqueue.h>
 #include <asm/byteorder.h>
 #include <asm/unaligned.h>
@@ -43,6 +44,9 @@ struct omnia_mcu {
        struct rtc_device *rtcdev;
        u32 rtc_alarm;
        bool front_button_poweron;
+
+       /* MCU watchdog */
+       struct watchdog_device wdt;
 };
 
 int omnia_cmd_write_read(const struct i2c_client *client,
@@ -55,6 +59,25 @@ static inline int omnia_cmd_write(const struct i2c_client *client, void *cmd,
        return omnia_cmd_write_read(client, cmd, len, NULL, 0);
 }
 
+static inline int omnia_cmd_write_u8(const struct i2c_client *client, u8 cmd,
+                                    u8 val)
+{
+       u8 buf[2] = { cmd, val };
+
+       return omnia_cmd_write(client, buf, sizeof(buf));
+}
+
+static inline int omnia_cmd_write_u16(const struct i2c_client *client, u8 cmd,
+                                     u16 val)
+{
+       u8 buf[3];
+
+       buf[0] = cmd;
+       put_unaligned_le16(val, &buf[1]);
+
+       return omnia_cmd_write(client, buf, sizeof(buf));
+}
+
 static inline int omnia_cmd_write_u32(const struct i2c_client *client, u8 cmd,
                                      u32 val)
 {
@@ -158,5 +181,6 @@ 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);
+int omnia_mcu_register_watchdog(struct omnia_mcu *mcu);
 
 #endif /* __TURRIS_OMNIA_MCU_H */