From 018ba6e5b8c8a87873440a740f60311d5979f108 Mon Sep 17 00:00:00 2001 From: Bevan Weiss Date: Tue, 6 Jan 2026 20:50:34 +1100 Subject: [PATCH] kernel: mfd: Add Hasivo STC8 mfd This STC8 microcontroller is used on a range of Hasivo managed switches. It typically performs some fan/thermal control, and also has some discrete IO hanging off of it. The fan/thermal control is still somewhat unknown at this stage, but the LED / gpio control has been determined as being two I2C registers which need to be written to with a 'typical' Hasivo 0x40 execute mask set, to change values. Rather than having this expose the LED functionality / thermal control directly, just represent it as an mfd, with some configurable OR'ing of an execute-bit to certain registers (execute-bit-regs). This way different STC8 arrangements can hopefully be handled by devicetree configs rather than needing new driver code. Signed-off-by: Bevan Weiss Link: https://github.com/openwrt/openwrt/pull/21578 Signed-off-by: Hauke Mehrtens --- target/linux/realtek/modules.mk | 23 ++ .../809-add-hasivo-stc8-mfd.patch | 345 ++++++++++++++++++ target/linux/realtek/rtl838x/config-6.12 | 1 + target/linux/realtek/rtl839x/config-6.12 | 1 + target/linux/realtek/rtl930x/config-6.12 | 1 + target/linux/realtek/rtl930x_nand/config-6.12 | 1 + target/linux/realtek/rtl931x/config-6.12 | 1 + target/linux/realtek/rtl931x_nand/config-6.12 | 1 + 8 files changed, 374 insertions(+) create mode 100644 target/linux/realtek/modules.mk create mode 100644 target/linux/realtek/patches-6.12/809-add-hasivo-stc8-mfd.patch diff --git a/target/linux/realtek/modules.mk b/target/linux/realtek/modules.mk new file mode 100644 index 00000000000..c6065a9395d --- /dev/null +++ b/target/linux/realtek/modules.mk @@ -0,0 +1,23 @@ +# +# Copyright (C) 2011 OpenWrt.org +# +# This is free software, licensed under the GNU General Public License v2. +# See /LICENSE for more information. +# + +MFD_MENU:=MultiFunction Device (MFD) Support + +define KernelPackage/mfd-hasivo-stc8 + SUBMENU:=$(MFD_MENU) + TITLE:=Hasivo STC8 microcontroller support + KCONFIG:=CONFIG_MFD_HASIVO_STC8 + FILES:=$(LINUX_DIR)/drivers/mfd/hasivo-stc8-mfd.ko + DEPENDS:=@TARGET_realtek +kmod-mfd +kmod-regmap-i2c + AUTOLOAD:=$(call AutoProbe,hasivo-stc8-mfd) +endef + +define KernelPackage/mfd-hasivo-stc8/description + Kernel module for Hasivo STC8 microcontroller support +endef + +$(eval $(call KernelPackage,mfd-hasivo-stc8)) diff --git a/target/linux/realtek/patches-6.12/809-add-hasivo-stc8-mfd.patch b/target/linux/realtek/patches-6.12/809-add-hasivo-stc8-mfd.patch new file mode 100644 index 00000000000..d02bae45bbb --- /dev/null +++ b/target/linux/realtek/patches-6.12/809-add-hasivo-stc8-mfd.patch @@ -0,0 +1,345 @@ +From 7e59fb5d3f2d8b4280ed0bc408c73c0aa9cd8934 Mon Sep 17 00:00:00 2001 +From: Bevan Weiss +Date: Mon, 9 Feb 2026 18:31:46 +1100 +Subject: mfd: Add Hasivo STC8 mfd + +This STC8 microcontroller is used on a range of Hasivo managed switches. +It typically performs some fan/thermal control, and also has some +discrete IO hanging off of it. +The fan/thermal control is still somewhat unknown at this stage, but the +LED / gpio control has been determined as being two I2C registers which +need to be written to with a 'typical' Hasivo 0x40 execute mask set, to +change values. + +Rather than having this expose the LED functionality / thermal control +directly, just represent it as an mfd, with some configurable OR'ing of +an execute-bit to certain registers (execute-bit-regs). This way different +STC8 arrangements can hopefully be handled by devicetree configs rather +than needing new driver code. + + +Signed-off-by: Bevan Weiss +--- + .../bindings/mfd/hasivo,stc8-mfd.yaml | 99 ++++++++++ + drivers/mfd/Kconfig | 11 ++ + drivers/mfd/Makefile | 2 + + drivers/mfd/hasivo-stc8-mfd.c | 182 ++++++++++++++++++ + 4 files changed, 294 insertions(+) + create mode 100644 Documentation/devicetree/bindings/mfd/hasivo,stc8-mfd.yaml + create mode 100644 drivers/mfd/hasivo-stc8-mfd.c + +--- /dev/null ++++ b/Documentation/devicetree/bindings/mfd/hasivo,stc8-mfd.yaml +@@ -0,0 +1,99 @@ ++# SPDX-License-Identifier: GPL-2.0 ++# STC8 MFD binding for Hasivo STC8 microcontroller used in managed switches ++ ++title: "STC8 multi-function device" ++description: | ++ This binding describes the Hasivo STC8 multi-function device. ++ This is an STC8 microcontroller with custom firmware used by Hasivo to ++ provide various functions on their managed switch products. ++ The main known functionality currently is to allow for discrete GPIO control ++ which turns on/off LEDs and controls PWM for fan speed. ++ It is known that for the discrete IO however, the host must send an execute ++ flag (0x40) along with the register write to have the STC8 apply the change. ++ This binding allows for specifying which registers require this execute flag ++ to be set automatically by the MFD driver. ++ ++maintainers: ++ - bevan.weiss@gmail.com ++ ++properties: ++ compatible: ++ description: > ++ Must be "hasivo,stc8-mfd" for the MFD driver to bind. ++ Child devices will require that this also expose the "syscon" binding, ++ such that registers can be mapped through this MFD. ++ type: string ++ required: true ++ ++ reg: ++ description: > ++ I2C address of the STC8 device on the bus. ++ type: integer ++ required: true ++ ++ hasivo,execute-bit: ++ description: > ++ The mask that will be automatically ORd on writes to the registers ++ specified in hasivo,execute-bit-regs. This mask is used to signal to ++ the STC8 that the write should be executed. ++ If not specified, defaults to 0x40. ++ type: unsigned integer ++ defaults: 0x40 ++ required: false ++ ++ hasivo,execute-bit-regs: ++ description: > ++ Array of registers for which the execute-bit mask should be ++ applied. Writes to other registers will not have the execute-bit mask ++ applied. ++ If not specified, no registers will have the execute-bit mask applied. ++ type: array of unsigned integers ++ required: false ++ ++required: ++ - compatible ++ - reg ++ ++children: ++ ++examples: ++ - | ++ &i2c0 { ++ status = "okay"; ++ ++ stc8: stc8-mfd@0x4d { ++ compatible = "hasivo,stc8-mfd", "syscon"; ++ reg = <0x4d>; ++ hasivo,execute-bit = <0x40>; ++ hasivo,execute-bit-regs = <0x01 0x02>; ++ ++ poe_led_lan1: led@1,1 { ++ compatible = "register-bit-led"; ++ offset = <0x01>; ++ mask = <0x01>; ++ label = "orange:poe_led_lan1"; ++ default-state = "off"; ++ }; ++ poe_led_lan2: led@1,2 { ++ compatible = "register-bit-led"; ++ offset = <0x01>; ++ mask = <0x02>; ++ label = "orange:poe_led_lan2"; ++ default-state = "off"; ++ }; ++ poe_led_lan3: led@1,3 { ++ compatible = "register-bit-led"; ++ offset = <0x01>; ++ mask = <0x04>; ++ label = "orange:poe_led_lan3"; ++ default-state = "off"; ++ }; ++ poe_led_lan4: led@1,4 { ++ compatible = "register-bit-led"; ++ offset = <0x01>; ++ mask = <0x08>; ++ label = "orange:poe_led_lan4"; ++ default-state = "off"; ++ }; ++ }; ++ }; +--- a/drivers/mfd/Kconfig ++++ b/drivers/mfd/Kconfig +@@ -546,6 +546,17 @@ config MFD_MX25_TSADC + i.MX25 processors. They consist of a conversion queue for general + purpose ADC and a queue for Touchscreens. + ++config MFD_HASIVO_STC8 ++ tristate "Hasivo STC8 Multifunction Device" ++ select MFD_CORE ++ select REGMAP_I2C ++ depends on I2C ++ help ++ Support for the Hasivo STC8 multifunction device over I2C. ++ This driver provides common support for accessing the device, ++ additional drivers must be enabled in order to use the ++ functionality of the device. ++ + config MFD_HI6421_PMIC + tristate "HiSilicon Hi6421 PMU/Codec IC" + depends on OF +--- a/drivers/mfd/Makefile ++++ b/drivers/mfd/Makefile +@@ -290,3 +290,5 @@ obj-$(CONFIG_MFD_ATC260X_I2C) += atc260x + + obj-$(CONFIG_MFD_RSMU_I2C) += rsmu_i2c.o rsmu_core.o + obj-$(CONFIG_MFD_RSMU_SPI) += rsmu_spi.o rsmu_core.o ++ ++obj-$(CONFIG_MFD_HASIVO_STC8) += hasivo-stc8-mfd.o +--- /dev/null ++++ b/drivers/mfd/hasivo-stc8-mfd.c +@@ -0,0 +1,182 @@ ++// SPDX-License-Identifier: GPL-2.0-only ++/* ++ * Hasivo STC8 MFD driver with configurable write ORing for execute bit ++ * I2C implementation ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++struct stc8_mfd { ++ struct device *dev; ++ struct regmap *parent_regmap; ++ struct regmap *child_regmap; ++ u32 exec_bit; ++ u32 *exec_regs; ++ size_t num_exec_regs; ++}; ++ ++/* Check if register requires execute bit */ ++static bool is_exec_reg(struct stc8_mfd *mfd, unsigned int reg) ++{ ++ for (size_t i = 0; i < mfd->num_exec_regs; i++) { ++ if (mfd->exec_regs[i] == reg) ++ return true; ++ } ++ return false; ++} ++ ++/* Custom regmap write wrapper */ ++static int stc8_child_reg_write(void *context, unsigned int reg, unsigned int val) ++{ ++ struct stc8_mfd *mfd = context; ++ const unsigned int orig_val = val; ++ ++ /* Apply execute bit if this register is in the list */ ++ if (is_exec_reg(mfd, reg)) { ++ val |= mfd->exec_bit; ++ dev_dbg(mfd->dev, "Applying exec bit to reg 0x%02x, orig_val=0x%02x, new_val=0x%02x\n", reg, orig_val, val); ++ } ++ ++ /* Forward to parent regmap (I2C bus) */ ++ return regmap_write(mfd->parent_regmap, reg, val); ++} ++ ++/* Custom regmap read - transparent passthrough */ ++static int stc8_child_reg_read(void *context, unsigned int reg, unsigned int *val) ++{ ++ struct stc8_mfd *mfd = context; ++ ++ return regmap_read(mfd->parent_regmap, reg, val); ++} ++ ++static const struct regmap_config stc8_parent_regmap_config = { ++ .name = "stc8-mfd-parent", ++ .reg_bits = 8, ++ .val_bits = 8, ++}; ++ ++static const struct regmap_config stc8_child_regmap_config = { ++ .name = "stc8-mfd-child", ++ .reg_bits = 8, ++ .val_bits = 8, ++ .reg_read = stc8_child_reg_read, ++ .reg_write = stc8_child_reg_write, ++}; ++ ++static int stc8_parse_dt(struct stc8_mfd *mfd, struct device_node *np) ++{ ++ int ret, count; ++ ++ /* Get execute bit value (default 0x40) */ ++ mfd->exec_bit = 0x40; ++ ret = of_property_read_u32(np, "hasivo,execute-bit", &mfd->exec_bit); ++ ++ /* Get count of execute registers */ ++ count = of_property_count_u32_elems(np, "hasivo,execute-bit-registers"); ++ if (count <= 0) { ++ mfd->num_exec_regs = 0; ++ } ++ else { ++ mfd->num_exec_regs = count; ++ mfd->exec_regs = devm_kcalloc(mfd->dev, count, sizeof(u32), ++ GFP_KERNEL); ++ if (!mfd->exec_regs) ++ return -ENOMEM; ++ ++ ret = of_property_read_u32_array(np, "hasivo,execute-bit-registers", ++ mfd->exec_regs, count); ++ if (ret) { ++ dev_err(mfd->dev, "Failed to read execute-bit-registers: %d\n", ret); ++ return ret; ++ } ++ } ++ ++ dev_info(mfd->dev, "execute-bit=0x%02x, %zu execute-bit-registers\n", ++ mfd->exec_bit, mfd->num_exec_regs); ++ ++ return 0; ++} ++ ++static int stc8_i2c_probe(struct i2c_client *client) ++{ ++ struct stc8_mfd *mfd; ++ int ret; ++ ++ dev_dbg(&client->dev, "Hasivo STC8 MFD driver probed started\n"); ++ ++ mfd = devm_kzalloc(&client->dev, sizeof(struct stc8_mfd), GFP_KERNEL); ++ if (!mfd) ++ return -ENOMEM; ++ ++ mfd->dev = &client->dev; ++ i2c_set_clientdata(client, mfd); ++ ++ /* Parse device tree properties */ ++ ret = stc8_parse_dt(mfd, mfd->dev->of_node); ++ if (ret) ++ return ret; ++ ++ /* Create parent regmap for direct I2C access */ ++ mfd->parent_regmap = devm_regmap_init_i2c(client, ++ &stc8_parent_regmap_config); ++ if (IS_ERR(mfd->parent_regmap)) { ++ dev_err(&client->dev, "Failed to init parent regmap\n"); ++ return PTR_ERR(mfd->parent_regmap); ++ } ++ ++ /* Create child regmap with custom read/write for masking */ ++ mfd->child_regmap = devm_regmap_init(&client->dev, NULL, mfd, ++ &stc8_child_regmap_config); ++ if (IS_ERR(mfd->child_regmap)) { ++ dev_err(&client->dev, "Failed to init child regmap\n"); ++ return PTR_ERR(mfd->child_regmap); ++ } ++ /* Set the child regmap as the syscon regmap */ ++ ret = of_syscon_register_regmap(mfd->dev->of_node, mfd->child_regmap); ++ if (ret) ++ return ret; ++ ++ /* Automatically populate child devices from device tree */ ++ ret = of_platform_populate(client->dev.of_node, NULL, NULL, ++ &client->dev); ++ if (ret) { ++ dev_err(&client->dev, "Failed to add child devices: %d\n", ret); ++ return ret; ++ } ++ ++ dev_dbg(&client->dev, "Hasivo STC8 MFD driver probed successfully\n"); ++ ++ return 0; ++} ++ ++static void stc8_i2c_remove(struct i2c_client *client) ++{ ++ of_platform_depopulate(&client->dev); ++ dev_dbg(&client->dev, "Hasivo STC8 MFD driver removed\n"); ++} ++ ++static const struct of_device_id stc8_of_match[] = { ++ { .compatible = "hasivo,stc8-mfd" }, ++ { } ++}; ++MODULE_DEVICE_TABLE(of, stc8_of_match); ++ ++static struct i2c_driver stc8_i2c_driver = { ++ .driver = { ++ .name = "hasivo-stc8-mfd", ++ .of_match_table = stc8_of_match, ++ }, ++ .probe = stc8_i2c_probe, ++ .remove = stc8_i2c_remove, ++}; ++module_i2c_driver(stc8_i2c_driver); ++ ++MODULE_AUTHOR("Bevan Weiss "); ++MODULE_DESCRIPTION("Hasivo STC8 MFD driver with configurable write masking"); ++MODULE_LICENSE("GPL"); diff --git a/target/linux/realtek/rtl838x/config-6.12 b/target/linux/realtek/rtl838x/config-6.12 index 71f5fdc6c83..4f098a75d93 100644 --- a/target/linux/realtek/rtl838x/config-6.12 +++ b/target/linux/realtek/rtl838x/config-6.12 @@ -137,6 +137,7 @@ CONFIG_MDIO_REALTEK_OTTO_AUX=y CONFIG_MDIO_REALTEK_OTTO_SERDES=y CONFIG_MDIO_SMBUS=y CONFIG_MFD_CORE=y +# CONFIG_MFD_HASIVO_STC8 is not set CONFIG_MFD_RTL8231=y CONFIG_MFD_SYSCON=y CONFIG_MIGRATION=y diff --git a/target/linux/realtek/rtl839x/config-6.12 b/target/linux/realtek/rtl839x/config-6.12 index 3a02b7778ab..300d5a6a9a0 100644 --- a/target/linux/realtek/rtl839x/config-6.12 +++ b/target/linux/realtek/rtl839x/config-6.12 @@ -138,6 +138,7 @@ CONFIG_MDIO_REALTEK_OTTO_AUX=y CONFIG_MDIO_REALTEK_OTTO_SERDES=y CONFIG_MDIO_SMBUS=y CONFIG_MFD_CORE=y +# CONFIG_MFD_HASIVO_STC8 is not set CONFIG_MFD_RTL8231=y CONFIG_MFD_SYSCON=y CONFIG_MIGRATION=y diff --git a/target/linux/realtek/rtl930x/config-6.12 b/target/linux/realtek/rtl930x/config-6.12 index 9baf234524a..f0befa65ecc 100644 --- a/target/linux/realtek/rtl930x/config-6.12 +++ b/target/linux/realtek/rtl930x/config-6.12 @@ -120,6 +120,7 @@ CONFIG_MDIO_REALTEK_OTTO=y CONFIG_MDIO_REALTEK_OTTO_AUX=y CONFIG_MDIO_REALTEK_OTTO_SERDES=y CONFIG_MDIO_SMBUS=y +# CONFIG_MFD_HASIVO_STC8 is not set CONFIG_MFD_RTL8231=y CONFIG_MFD_SYSCON=y CONFIG_MIGRATION=y diff --git a/target/linux/realtek/rtl930x_nand/config-6.12 b/target/linux/realtek/rtl930x_nand/config-6.12 index 054beb57919..5fa736be1ff 100644 --- a/target/linux/realtek/rtl930x_nand/config-6.12 +++ b/target/linux/realtek/rtl930x_nand/config-6.12 @@ -120,6 +120,7 @@ CONFIG_MDIO_REALTEK_OTTO=y CONFIG_MDIO_REALTEK_OTTO_AUX=y CONFIG_MDIO_REALTEK_OTTO_SERDES=y CONFIG_MDIO_SMBUS=y +# CONFIG_MFD_HASIVO_STC8 is not set CONFIG_MFD_RTL8231=y CONFIG_MFD_SYSCON=y CONFIG_MIGRATION=y diff --git a/target/linux/realtek/rtl931x/config-6.12 b/target/linux/realtek/rtl931x/config-6.12 index 30e03db3c20..b5c2ba630d5 100644 --- a/target/linux/realtek/rtl931x/config-6.12 +++ b/target/linux/realtek/rtl931x/config-6.12 @@ -125,6 +125,7 @@ CONFIG_MDIO_REALTEK_OTTO=y CONFIG_MDIO_REALTEK_OTTO_AUX=y CONFIG_MDIO_REALTEK_OTTO_SERDES=y CONFIG_MDIO_SMBUS=y +# CONFIG_MFD_HASIVO_STC8 is not set CONFIG_MFD_RTL8231=y CONFIG_MFD_SYSCON=y CONFIG_MIGRATION=y diff --git a/target/linux/realtek/rtl931x_nand/config-6.12 b/target/linux/realtek/rtl931x_nand/config-6.12 index 38d7dfdea7e..19797397b40 100644 --- a/target/linux/realtek/rtl931x_nand/config-6.12 +++ b/target/linux/realtek/rtl931x_nand/config-6.12 @@ -125,6 +125,7 @@ CONFIG_MDIO_REALTEK_OTTO=y CONFIG_MDIO_REALTEK_OTTO_AUX=y CONFIG_MDIO_REALTEK_OTTO_SERDES=y CONFIG_MDIO_SMBUS=y +# CONFIG_MFD_HASIVO_STC8 is not set CONFIG_MFD_RTL8231=y CONFIG_MFD_SYSCON=y CONFIG_MIGRATION=y