realtek: add Hasivo MCU watchdog driver
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>
This commit is contained in:
parent
c9406df918
commit
d048137e85
@ -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)
|
||||
|
||||
277
target/linux/realtek/patches-6.18/810-add-hasivo-mcu-wdt.patch
Normal file
277
target/linux/realtek/patches-6.18/810-add-hasivo-mcu-wdt.patch
Normal file
@ -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>");
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
Reference in New Issue
Block a user