|
|
|
@@ -0,0 +1,345 @@
|
|
|
|
|
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");
|