--- /dev/null
+From 7e59fb5d3f2d8b4280ed0bc408c73c0aa9cd8934 Mon Sep 17 00:00:00 2001
+From: Bevan Weiss <bevan.weiss@gmail.com>
+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 <bevan.weiss@gmail.com>
+---
+ .../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 <linux/module.h>
++#include <linux/i2c.h>
++#include <linux/regmap.h>
++#include <linux/of.h>
++#include <linux/of_platform.h>
++#include <linux/slab.h>
++#include <linux/mfd/syscon.h>
++
++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 <bevan.weiss@gmail.com>");
++MODULE_DESCRIPTION("Hasivo STC8 MFD driver with configurable write masking");
++MODULE_LICENSE("GPL");