]> git.ipfire.org Git - thirdparty/openwrt.git/commitdiff
realtek: add Hasivo MCU watchdog driver 23418/head
authorCarlo Szelinsky <github@szelinsky.de>
Mon, 18 May 2026 20:41:21 +0000 (22:41 +0200)
committerMarkus Stockhausen <markus.stockhausen@gmx.de>
Sun, 31 May 2026 08:57:26 +0000 (10:57 +0200)
Add a watchdog driver for the external management MCU on Hasivo /
Horaco network switches, reachable over I2C. Without periodic
keepalive the MCU resets the board every ~3 minutes.

The driver arms the MCU at probe and registers a struct
watchdog_device with WDOG_HW_RUNNING so the watchdog core feeds the
chip via a kernel timer until userspace opens the watchdog node.
Timeout is fixed at 15s; the hardware threshold is baked into MCU
firmware and is not software-configurable.

The I2C address is supplied per-board in the device tree via the
`reg` property. The driver does not constrain or probe a specific
address. Known addresses across current Hasivo / Horaco silicon:

  - 0x6F: Hasivo S1300WP-8XGT-4S+, Hasivo F5800W-12S+,
          Horaco ZX-SW82TS-L2P (default / most common)
  - 0x6E: alternate Hasivo / Horaco variant

The driver, its device-tree binding and the Kconfig/Makefile wiring
are added to the kernel tree as a realtek target patch and exposed as
the kmod-hasivo-mcu-wdt KernelPackage. Keeping the binding in the
kernel tree lets dt_binding_check exercise it during the build and
makes the whole driver easy to drop once it lands upstream.

Tested on Hasivo S1300WP-8XGT-4S+ (RTL9313). Unbinding the driver
causes the MCU to power-cycle the board within ~15s.

Signed-off-by: Carlo Szelinsky <github@szelinsky.de>
Link: https://github.com/openwrt/openwrt/pull/23418
Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
target/linux/realtek/modules.mk
target/linux/realtek/patches-6.18/810-add-hasivo-mcu-wdt.patch [new file with mode: 0644]
target/linux/realtek/rtl838x/config-6.18
target/linux/realtek/rtl839x/config-6.18
target/linux/realtek/rtl930x/config-6.18
target/linux/realtek/rtl930x_nand/config-6.18
target/linux/realtek/rtl931x/config-6.18
target/linux/realtek/rtl931x_nand/config-6.18

index c6065a9395da612781736b24f770f2f722009255..e3a85b75b067676a4d35216fe7077997caa5ffc8 100644 (file)
@@ -6,6 +6,24 @@
 #
 
 MFD_MENU:=MultiFunction Device (MFD) Support
+WATCHDOG_MENU:=Watchdog Timer Support
+
+define KernelPackage/hasivo-mcu-wdt
+  SUBMENU:=$(WATCHDOG_MENU)
+  TITLE:=Hasivo MCU watchdog driver
+  KCONFIG:=CONFIG_HASIVO_MCU_WATCHDOG
+  FILES:=$(LINUX_DIR)/drivers/watchdog/hasivo-mcu-wdt.ko
+  DEPENDS:=@TARGET_realtek +kmod-i2c-core
+  AUTOLOAD:=$(call AutoProbe,hasivo-mcu-wdt,1)
+endef
+
+define KernelPackage/hasivo-mcu-wdt/description
+ Hardware watchdog driver for the external management MCU found on
+ Hasivo / Horaco network switches. Registers a Linux watchdog device;
+ the kernel watchdog core feeds it automatically via its own timer.
+endef
+
+$(eval $(call KernelPackage,hasivo-mcu-wdt))
 
 define KernelPackage/mfd-hasivo-stc8
   SUBMENU:=$(MFD_MENU)
diff --git a/target/linux/realtek/patches-6.18/810-add-hasivo-mcu-wdt.patch b/target/linux/realtek/patches-6.18/810-add-hasivo-mcu-wdt.patch
new file mode 100644 (file)
index 0000000..8330b88
--- /dev/null
@@ -0,0 +1,277 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Carlo Szelinsky <github@szelinsky.de>
+Date: Sat, 30 May 2026 12:00:00 +0200
+Subject: watchdog: add Hasivo MCU watchdog driver
+
+Hardware watchdog driver for the external management MCU found on
+Hasivo / Horaco network switches, reachable over I2C. Without periodic
+keepalive the MCU resets the board every ~3 minutes.
+
+The driver arms the MCU at probe and registers a struct watchdog_device
+with WDOG_HW_RUNNING so the watchdog core feeds the chip via a kernel
+timer until userspace opens the watchdog node. Timeout is fixed at 15s;
+the hardware threshold is baked into MCU firmware and is not
+software-configurable.
+
+Signed-off-by: Carlo Szelinsky <github@szelinsky.de>
+Signed-off-by: Manuel Stocker <mensi@mensi.ch>
+---
+--- /dev/null
++++ b/Documentation/devicetree/bindings/watchdog/hasivo,mcu-wdt.yaml
+@@ -0,0 +1,45 @@
++# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
++%YAML 1.2
++---
++$id: http://devicetree.org/schemas/watchdog/hasivo,mcu-wdt.yaml#
++$schema: http://devicetree.org/meta-schemas/core.yaml#
++
++title: Hasivo / Horaco external management MCU watchdog
++
++maintainers:
++  - Carlo Szelinsky <github@szelinsky.de>
++  - Manuel Stocker <mensi@mensi.ch>
++
++description: |
++  Management microcontroller found on Hasivo and Horaco network switches,
++  exposed on the system I2C bus. Provides a hardware watchdog that must be
++  armed and periodically retriggered by the host via I2C writes.
++
++allOf:
++  - $ref: watchdog.yaml#
++
++properties:
++  compatible:
++    const: hasivo,mcu-wdt
++
++  reg:
++    maxItems: 1
++
++required:
++  - compatible
++  - reg
++
++unevaluatedProperties: false
++
++examples:
++  - |
++    i2c {
++        #address-cells = <1>;
++        #size-cells = <0>;
++
++        watchdog@6f {
++            compatible = "hasivo,mcu-wdt";
++            reg = <0x6f>;
++        };
++    };
++...
+--- a/drivers/watchdog/Kconfig
++++ b/drivers/watchdog/Kconfig
+@@ -302,6 +302,18 @@ config MENF21BMC_WATCHDOG
+         This driver can also be built as a module. If so the module
+         will be called menf21bmc_wdt.
++config HASIVO_MCU_WATCHDOG
++      tristate "Hasivo MCU Watchdog"
++      depends on I2C
++      select WATCHDOG_CORE
++      help
++        Hardware watchdog driver for the external management MCU found
++        on Hasivo and Horaco network switches, reachable over I2C. The
++        MCU resets the board unless it is periodically retriggered.
++
++        This driver can also be built as a module. If so the module
++        will be called hasivo-mcu-wdt.
++
+ config MENZ069_WATCHDOG
+       tristate "MEN 16Z069 Watchdog"
+       depends on MCB
+--- a/drivers/watchdog/Makefile
++++ b/drivers/watchdog/Makefile
+@@ -239,6 +239,7 @@ obj-$(CONFIG_NCT6694_WATCHDOG) += nct669
+ obj-$(CONFIG_ZIIRAVE_WATCHDOG) += ziirave_wdt.o
+ obj-$(CONFIG_SOFT_WATCHDOG) += softdog.o
+ obj-$(CONFIG_MENF21BMC_WATCHDOG) += menf21bmc_wdt.o
++obj-$(CONFIG_HASIVO_MCU_WATCHDOG) += hasivo-mcu-wdt.o
+ obj-$(CONFIG_MENZ069_WATCHDOG) += menz69_wdt.o
+ obj-$(CONFIG_RAVE_SP_WATCHDOG) += rave-sp-wdt.o
+ obj-$(CONFIG_STPMIC1_WATCHDOG) += stpmic1_wdt.o
+--- /dev/null
++++ b/drivers/watchdog/hasivo-mcu-wdt.c
+@@ -0,0 +1,177 @@
++// SPDX-License-Identifier: GPL-2.0-only
++/*
++ * Hasivo MCU Watchdog Driver
++ *
++ * Hardware watchdog driver for the external management MCU found on
++ * Hasivo / Horaco network switches. Communicates over I2C.
++ *
++ * Protocol reverse-engineered from the stock firmware ("imi" daemon +
++ * i2c-poe.ko kernel module):
++ *   - Arm:       write 0x4F -> reg 0x09 (unlock), 0x4F -> reg 0x0A
++ *   - Disarm:    write 0x4F -> reg 0x09 (unlock), 0x3F -> reg 0x0A
++ *   - Keepalive: write 0xEE -> reg 0x0B (no unlock needed)
++ *
++ * Copyright (C) 2026 Manuel Stocker <mensi@mensi.ch>
++ * Copyright (C) 2026 Carlo Szelinsky <github@szelinsky.de>
++ */
++
++#include <linux/i2c.h>
++#include <linux/module.h>
++#include <linux/watchdog.h>
++
++#define HASIVO_REG_WDT_UNLOCK         0x09
++  #define HASIVO_WDT_UNLOCK_MAGIC     0x4f
++
++#define HASIVO_REG_WDT_CMD            0x0a
++  #define HASIVO_WDT_CMD_ARM          0x4f
++  #define HASIVO_WDT_CMD_DISARM               0x3f
++
++#define HASIVO_REG_WDT_KEEPALIVE      0x0b
++  #define HASIVO_WDT_KEEPALIVE_MAGIC  0xee
++
++/*
++ * Hardware timeout is fixed in MCU firmware and not software-
++ * configurable. Stock keepalive cadence is 2 s; the actual reset
++ * threshold is empirically in the 10-30 s range. Pick a conservative
++ * value so the kernel-core auto-feed (which fires at timeout/2) leaves
++ * generous slack.
++ */
++#define HASIVO_WDT_TIMEOUT            15
++
++struct hasivo_mcu {
++      struct i2c_client *client;
++      struct watchdog_device wdd;
++};
++
++static int hasivo_mcu_wdt_send_cmd(struct hasivo_mcu *mcu, u8 cmd)
++{
++      int ret;
++
++      ret = i2c_smbus_write_byte_data(mcu->client, HASIVO_REG_WDT_UNLOCK,
++                                      HASIVO_WDT_UNLOCK_MAGIC);
++      if (ret)
++              return ret;
++
++      return i2c_smbus_write_byte_data(mcu->client, HASIVO_REG_WDT_CMD, cmd);
++}
++
++static int hasivo_mcu_wdt_start(struct watchdog_device *wdd)
++{
++      struct hasivo_mcu *mcu = watchdog_get_drvdata(wdd);
++
++      return hasivo_mcu_wdt_send_cmd(mcu, HASIVO_WDT_CMD_ARM);
++}
++
++static int hasivo_mcu_wdt_stop(struct watchdog_device *wdd)
++{
++      struct hasivo_mcu *mcu = watchdog_get_drvdata(wdd);
++
++      return hasivo_mcu_wdt_send_cmd(mcu, HASIVO_WDT_CMD_DISARM);
++}
++
++static int hasivo_mcu_wdt_ping(struct watchdog_device *wdd)
++{
++      struct hasivo_mcu *mcu = watchdog_get_drvdata(wdd);
++
++      return i2c_smbus_write_byte_data(mcu->client,
++                                       HASIVO_REG_WDT_KEEPALIVE,
++                                       HASIVO_WDT_KEEPALIVE_MAGIC);
++}
++
++static const struct watchdog_info hasivo_mcu_wdt_info = {
++      .identity       = "Hasivo MCU Watchdog",
++      .options        = WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
++};
++
++static const struct watchdog_ops hasivo_mcu_wdt_ops = {
++      .owner          = THIS_MODULE,
++      .start          = hasivo_mcu_wdt_start,
++      .stop           = hasivo_mcu_wdt_stop,
++      .ping           = hasivo_mcu_wdt_ping,
++};
++
++static int hasivo_mcu_probe(struct i2c_client *client)
++{
++      struct device *dev = &client->dev;
++      struct hasivo_mcu *mcu;
++      int ret;
++
++      mcu = devm_kzalloc(dev, sizeof(*mcu), GFP_KERNEL);
++      if (!mcu)
++              return -ENOMEM;
++
++      mcu->client = client;
++
++      /*
++       * Arm the watchdog. The stock bootloader normally leaves it
++       * already armed (and the chip's timer has been counting since
++       * then); re-arm explicitly so unexpected boot paths still leave
++       * it in a known state. It is not specified whether the arm
++       * sequence resets the chip's timeout counter, so send an
++       * explicit keepalive afterwards to guarantee a fresh window
++       * before the kernel-side feeder takes over at timeout/2.
++       */
++      ret = hasivo_mcu_wdt_send_cmd(mcu, HASIVO_WDT_CMD_ARM);
++      if (ret)
++              return dev_err_probe(dev, ret, "failed to arm watchdog\n");
++
++      ret = i2c_smbus_write_byte_data(client, HASIVO_REG_WDT_KEEPALIVE,
++                                      HASIVO_WDT_KEEPALIVE_MAGIC);
++      if (ret)
++              return dev_err_probe(dev, ret,
++                                   "initial watchdog ping failed\n");
++
++      mcu->wdd.info = &hasivo_mcu_wdt_info;
++      mcu->wdd.ops = &hasivo_mcu_wdt_ops;
++      mcu->wdd.parent = dev;
++      mcu->wdd.timeout = HASIVO_WDT_TIMEOUT;
++      mcu->wdd.min_timeout = HASIVO_WDT_TIMEOUT;
++      mcu->wdd.max_timeout = HASIVO_WDT_TIMEOUT;
++
++      /*
++       * Tell the watchdog core the hardware is already running so it
++       * pings via its own timer at timeout/2 until userspace (procd)
++       * opens /dev/watchdog0 and takes over.
++       */
++      set_bit(WDOG_HW_RUNNING, &mcu->wdd.status);
++
++      watchdog_set_drvdata(&mcu->wdd, mcu);
++      watchdog_stop_on_unregister(&mcu->wdd);
++
++      ret = devm_watchdog_register_device(dev, &mcu->wdd);
++      if (ret)
++              return dev_err_probe(dev, ret,
++                                   "failed to register watchdog\n");
++
++      dev_info(dev, "Hasivo MCU watchdog armed at 0x%02x (timeout %us)\n",
++               client->addr, mcu->wdd.timeout);
++
++      return 0;
++}
++
++static const struct of_device_id hasivo_mcu_of_match[] = {
++      { .compatible = "hasivo,mcu-wdt" },
++      { }
++};
++MODULE_DEVICE_TABLE(of, hasivo_mcu_of_match);
++
++static const struct i2c_device_id hasivo_mcu_id[] = {
++      { "hasivo-mcu-wdt" },
++      { }
++};
++MODULE_DEVICE_TABLE(i2c, hasivo_mcu_id);
++
++static struct i2c_driver hasivo_mcu_driver = {
++      .driver = {
++              .name           = "hasivo-mcu-wdt",
++              .of_match_table = hasivo_mcu_of_match,
++      },
++      .probe          = hasivo_mcu_probe,
++      .id_table       = hasivo_mcu_id,
++};
++module_i2c_driver(hasivo_mcu_driver);
++
++MODULE_DESCRIPTION("Hasivo MCU Watchdog Driver");
++MODULE_LICENSE("GPL");
++MODULE_AUTHOR("Manuel Stocker <mensi@mensi.ch>");
++MODULE_AUTHOR("Carlo Szelinsky <github@szelinsky.de>");
index 97c450830424ccf4fc042e1579f7efb98051f119..9020be7c04b587d790e5769bbbcba3dbfee3cdc3 100644 (file)
@@ -97,6 +97,7 @@ CONFIG_GPIO_WATCHDOG=y
 # CONFIG_GPIO_WATCHDOG_ARCH_INITCALL is not set
 CONFIG_GRO_CELLS=y
 CONFIG_HARDWARE_WATCHPOINTS=y
+# CONFIG_HASIVO_MCU_WATCHDOG is not set
 CONFIG_HAS_DMA=y
 CONFIG_HAS_IOMEM=y
 CONFIG_HAS_IOPORT=y
index c0912804c28f97ad5f9db6ce2d913bf737580db2..243e0ac5b297221486e44a6c31030b774179b3bb 100644 (file)
@@ -100,6 +100,7 @@ CONFIG_GPIO_WATCHDOG=y
 # CONFIG_GPIO_WATCHDOG_ARCH_INITCALL is not set
 CONFIG_GRO_CELLS=y
 CONFIG_HARDWARE_WATCHPOINTS=y
+# CONFIG_HASIVO_MCU_WATCHDOG is not set
 CONFIG_HAS_DMA=y
 CONFIG_HAS_IOMEM=y
 CONFIG_HAS_IOPORT=y
index 730b35e16f3c29e6ee531718e919127b79f8e9cd..ce48ee74d99e23ec9f3fcbb318fd8a3d7ffce81d 100644 (file)
@@ -86,6 +86,7 @@ CONFIG_GPIO_WATCHDOG=y
 # CONFIG_GPIO_WATCHDOG_ARCH_INITCALL is not set
 CONFIG_GRO_CELLS=y
 CONFIG_HARDWARE_WATCHPOINTS=y
+# CONFIG_HASIVO_MCU_WATCHDOG is not set
 CONFIG_HAS_DMA=y
 CONFIG_HAS_IOMEM=y
 CONFIG_HAS_IOPORT=y
index 8f0e183d44bd92cc0bfb442ad429bd31c8927250..2c0595f17ea9688f3d5c467d249c8e1f9099cd24 100644 (file)
@@ -88,6 +88,7 @@ CONFIG_GPIO_REALTEK_OTTO=y
 CONFIG_GPIO_REGMAP=y
 CONFIG_GRO_CELLS=y
 CONFIG_HARDWARE_WATCHPOINTS=y
+# CONFIG_HASIVO_MCU_WATCHDOG is not set
 CONFIG_HAS_DMA=y
 CONFIG_HAS_IOMEM=y
 CONFIG_HAS_IOPORT=y
index 3f5a7289c677b06674c29e984de009c5267217b2..8d97246e31b1537dab45030cbd3c6ca5ecbaeead 100644 (file)
@@ -88,6 +88,7 @@ CONFIG_GPIO_REALTEK_OTTO=y
 CONFIG_GPIO_REGMAP=y
 CONFIG_GRO_CELLS=y
 CONFIG_HARDWARE_WATCHPOINTS=y
+# CONFIG_HASIVO_MCU_WATCHDOG is not set
 CONFIG_HAS_DMA=y
 CONFIG_HAS_IOMEM=y
 CONFIG_HAS_IOPORT=y
index bc08c5f30e4120a61c52791929813e9151997d74..75129075f58fb6e9bab8ca81f70cbb4a03f1bacb 100644 (file)
@@ -90,6 +90,7 @@ CONFIG_GPIO_REALTEK_OTTO=y
 CONFIG_GPIO_REGMAP=y
 CONFIG_GRO_CELLS=y
 CONFIG_HARDWARE_WATCHPOINTS=y
+# CONFIG_HASIVO_MCU_WATCHDOG is not set
 CONFIG_HAS_DMA=y
 CONFIG_HAS_IOMEM=y
 CONFIG_HAS_IOPORT=y