--- /dev/null
+From 5550ceafb9bff5f0d362d402b7f274ded474cbf4 Mon Sep 17 00:00:00 2001
+From: Bevan Weiss <bevan.weiss@gmail.com>
+Date: Mon, 2 Mar 2026 23:31:43 +0100
+Subject: [PATCH] net: pse-pd: add Hasivo HS104 PoE PSE controller driver
+
+The HS104PTI/HS104PBI are single-chip PoE PSE controllers that manage 4
+power delivery channels and are accessed directly from the host over I2C.
+They are found on Hasivo managed PoE switches (e.g. S1100WP-8GT-SE, which
+carries two of them). Unlike the MCU-fronted realtek-pse-mcu family, the
+PSE silicon here is driven directly, so it needs its own controller
+driver.
+
+Add the driver, its device-tree binding and the Kconfig/Makefile entries.
+
+Signed-off-by: Bevan Weiss <bevan.weiss@gmail.com>
+Co-developed-by: Carlo Szelinsky <github@szelinsky.de>
+Signed-off-by: Carlo Szelinsky <github@szelinsky.de>
+---
+--- /dev/null
++++ b/Documentation/devicetree/bindings/net/pse-pd/hasivo,hs104.yaml
+@@ -0,0 +1,64 @@
++# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
++%YAML 1.2
++---
++$id: http://devicetree.org/schemas/net/pse-pd/hasivo,hs104.yaml#
++$schema: http://devicetree.org/meta-schemas/core.yaml#
++
++title: Hasivo HS104 Power Sourcing Equipment controller
++
++maintainers:
++ - Bevan Weiss <bevan.weiss@gmail.com>
++ - Carlo Szelinsky <github@szelinsky.de>
++
++description: |
++ The HS104PTI/HS104PBI are single-chip PoE PSE controllers managing 4
++ delivery channels, allowing them to supply 4 ports of 802.3af/at/bt power.
++ The HS104PTI can have 1x 802.3bt port and 3x 802.3at ports.
++ The HS104PBI can have 4x 802.3bt ports.
++
++allOf:
++ - $ref: pse-controller.yaml#
++
++properties:
++ compatible:
++ enum:
++ - hasivo,hs104
++ - hasivo,hs104pti
++ - hasivo,hs104pbi
++
++ reg:
++ maxItems: 1
++
++ pse-pis:
++ patternProperties:
++ '^pse-pi@[0-3]$':
++ type: object
++
++unevaluatedProperties: false
++
++required:
++ - compatible
++ - reg
++ - pse-pis
++
++examples:
++ - |
++ i2c {
++ #address-cells = <1>;
++ #size-cells = <0>;
++
++ ethernet-pse@d {
++ compatible = "hasivo,hs104";
++ reg = <0x0d>;
++
++ pse-pis {
++ #address-cells = <1>;
++ #size-cells = <0>;
++
++ pse-pi@0 {
++ reg = <0>;
++ #pse-cells = <0>;
++ };
++ };
++ };
++ };
+--- a/drivers/net/pse-pd/Kconfig
++++ b/drivers/net/pse-pd/Kconfig
+@@ -80,4 +80,12 @@ config PSE_TPS23881
+
+ To compile this driver as a module, choose M here: the
+ module will be called tps23881.
++
++config PSE_HASIVO_HS104
++ tristate "Hasivo HS104 PoE PSE Controller"
++ depends on I2C
++ help
++ Support for the Hasivo HS104 PoE PSE Controller chip.
++ This driver allows control and monitoring of PoE ports via I2C.
++
+ endif
+--- a/drivers/net/pse-pd/Makefile
++++ b/drivers/net/pse-pd/Makefile
+@@ -10,3 +10,4 @@ obj-$(CONFIG_PSE_REGULATOR) += pse_regul
+ obj-$(CONFIG_PSE_PD692X0) += pd692x0.o
+ obj-$(CONFIG_PSE_SI3474) += si3474.o
+ obj-$(CONFIG_PSE_TPS23881) += tps23881.o
++obj-$(CONFIG_PSE_HASIVO_HS104) += hasivo_hs104.o
+--- /dev/null
++++ b/drivers/net/pse-pd/hasivo_hs104.c
+@@ -0,0 +1,451 @@
++// SPDX-License-Identifier: GPL-2.0-only
++/*
++ * Hasivo HS104 PoE PSE controller driver
++ *
++ * The HS104PTI/HS104PBI are single-chip PoE PSE controllers managing 4
++ * delivery channels, allowing them to supply 4 ports of 802.3af/at/bt power.
++ * The HS104PTI can have 1x 802.3bt port and 3x 802.3at ports.
++ * The HS104PBI can have 4x 802.3bt ports.
++ *
++ * Copyright (c) 2025 Bevan Weiss <bevan.weiss@gmail.com>
++ * Copyright (c) 2026 Carlo Szelinsky <github@szelinsky.de>
++ */
++
++#include <linux/i2c.h>
++#include <linux/module.h>
++#include <linux/of.h>
++#include <linux/pse-pd/pse.h>
++#include <linux/regmap.h>
++
++#define HS104_MAX_PORTS 4
++
++/* Register map */
++#define HS104_REG_PW_STATUS 0x01 /* Power delivery status */
++#define HS104_REG_INPUT_V 0x02 /* Input voltage, 16-bit BE, 10mV */
++#define HS104_REG_PORT0_I 0x04 /* Port current, 16-bit BE, 1mA (not yet implemented) */
++#define HS104_REG_DEVID 0x0C /* Device ID */
++#define HS104_DEVICE_ID 0x91
++#define HS104_REG_PORT0_CLASS 0x0D /* Port power class */
++#define HS104_REG_PW_EN 0x14 /* Port enable control */
++#define HS104_EXECUTE 0x40 /* Execute bit for writes */
++#define HS104_REG_PROTOCOL 0x19 /* Protocol per port (2 bits each) */
++#define HS104_PROTO_MASK 0x3
++#define HS104_PROTO_BT 0 /* 802.3bt - 60W */
++#define HS104_PROTO_HIPO 1 /* Hi-PoE - 90W */
++#define HS104_PROTO_AT 2 /* 802.3at - 30W */
++#define HS104_PROTO_AF 3 /* 802.3af - 15.4W */
++#define HS104_REG_TOTAL_POWER 0x1D /* Total power, 16-bit BE, 10mW (not yet implemented) */
++#define HS104_REG_PORT0_POWER 0x21 /* Port power, 16-bit BE, 10mW */
++
++/*
++ * BIT-field registers (PW_EN, PW_STATUS) use reversed bit ordering
++ * relative to sequential PORT registers (PORT0_POWER, PORT0_CLASS).
++ * Map PSE PI index to the hardware bit position.
++ */
++#define HS104_PORT_BIT(id) (0x08 >> (id))
++
++/* Power limits in mW */
++#define HS104_PW_AF 15400
++#define HS104_PW_AT 30000
++#define HS104_PW_BT 60000
++#define HS104_PW_HIPO 90000
++
++/* Unit conversion steps */
++#define HS104_UV_STEP 10000 /* 10mV -> uV */
++#define HS104_UA_STEP 1000 /* 1mA -> uA */
++#define HS104_MW_STEP 10 /* 10mW -> mW */
++
++struct hs104_priv {
++ struct regmap *regmap;
++ struct pse_controller_dev pcdev;
++ unsigned int last_pw_status;
++};
++
++static inline struct hs104_priv *to_hs104(struct pse_controller_dev *pcdev)
++{
++ return container_of(pcdev, struct hs104_priv, pcdev);
++}
++
++/* Read 16-bit big-endian register and apply unit conversion */
++static int hs104_read_be16(struct hs104_priv *priv, unsigned int reg,
++ unsigned int step)
++{
++ __be16 val;
++ int ret;
++
++ ret = regmap_bulk_read(priv->regmap, reg, &val, sizeof(val));
++ if (ret)
++ return ret;
++
++ /* Hardware uses 14-bit data field, upper 2 bits are reserved */
++ return (be16_to_cpu(val) & 0x3fff) * step;
++}
++
++/* Convert protocol code to power limit in mW */
++static int hs104_proto_to_mw(unsigned int proto)
++{
++ switch (proto) {
++ case HS104_PROTO_AF: return HS104_PW_AF;
++ case HS104_PROTO_AT: return HS104_PW_AT;
++ case HS104_PROTO_BT: return HS104_PW_BT;
++ case HS104_PROTO_HIPO: return HS104_PW_HIPO;
++ default: return 0;
++ }
++}
++
++/* PSE controller operations */
++
++static int hs104_pi_enable(struct pse_controller_dev *pcdev, int id)
++{
++ struct hs104_priv *priv = to_hs104(pcdev);
++ unsigned int val;
++ int ret;
++
++ ret = regmap_read(priv->regmap, HS104_REG_PW_EN, &val);
++ if (ret)
++ return ret;
++
++ val = (val | HS104_PORT_BIT(id)) | HS104_EXECUTE;
++
++ dev_dbg(pcdev->dev, "PI %d: enable (0x%02x)\n", id, val);
++ return regmap_write(priv->regmap, HS104_REG_PW_EN, val);
++}
++
++static int hs104_pi_disable(struct pse_controller_dev *pcdev, int id)
++{
++ struct hs104_priv *priv = to_hs104(pcdev);
++ unsigned int val;
++ int ret;
++
++ ret = regmap_read(priv->regmap, HS104_REG_PW_EN, &val);
++ if (ret)
++ return ret;
++
++ val = (val & ~HS104_PORT_BIT(id)) | HS104_EXECUTE;
++
++ dev_dbg(pcdev->dev, "PI %d: disable (0x%02x)\n", id, val);
++ return regmap_write(priv->regmap, HS104_REG_PW_EN, val);
++}
++
++static int hs104_pi_get_voltage(struct pse_controller_dev *pcdev, int id)
++{
++ struct hs104_priv *priv = to_hs104(pcdev);
++
++ /* Input voltage is shared across all PIs */
++ return hs104_read_be16(priv, HS104_REG_INPUT_V, HS104_UV_STEP);
++}
++
++static int hs104_pi_get_pw_limit(struct pse_controller_dev *pcdev, int id)
++{
++ struct hs104_priv *priv = to_hs104(pcdev);
++ unsigned int val, proto;
++ int ret;
++
++ ret = regmap_read(priv->regmap, HS104_REG_PROTOCOL, &val);
++ if (ret)
++ return ret;
++
++ proto = (val >> (id * 2)) & HS104_PROTO_MASK;
++ return hs104_proto_to_mw(proto) ?: -ENODATA;
++}
++
++static int hs104_pi_set_pw_limit(struct pse_controller_dev *pcdev,
++ int id, int max_mw)
++{
++ struct hs104_priv *priv = to_hs104(pcdev);
++ unsigned int proto, mask;
++
++ if (max_mw <= HS104_PW_AF)
++ proto = HS104_PROTO_AF;
++ else if (max_mw <= HS104_PW_AT)
++ proto = HS104_PROTO_AT;
++ else if (max_mw <= HS104_PW_BT)
++ proto = HS104_PROTO_BT;
++ else if (max_mw <= HS104_PW_HIPO)
++ proto = HS104_PROTO_HIPO;
++ else
++ return -EINVAL;
++
++ mask = HS104_PROTO_MASK << (id * 2);
++ proto <<= (id * 2);
++
++ return regmap_update_bits(priv->regmap, HS104_REG_PROTOCOL, mask, proto);
++}
++
++static int hs104_pi_get_admin_state(struct pse_controller_dev *pcdev, int id,
++ struct pse_admin_state *admin_state)
++{
++ struct hs104_priv *priv = to_hs104(pcdev);
++ unsigned int val;
++ int ret;
++
++ ret = regmap_read(priv->regmap, HS104_REG_PW_EN, &val);
++ if (ret) {
++ admin_state->c33_admin_state =
++ ETHTOOL_C33_PSE_ADMIN_STATE_UNKNOWN;
++ return ret;
++ }
++
++ if (val & HS104_PORT_BIT(id))
++ admin_state->c33_admin_state = ETHTOOL_C33_PSE_ADMIN_STATE_ENABLED;
++ else
++ admin_state->c33_admin_state = ETHTOOL_C33_PSE_ADMIN_STATE_DISABLED;
++
++ return 0;
++}
++
++static int hs104_pi_get_pw_status(struct pse_controller_dev *pcdev, int id,
++ struct pse_pw_status *pw_status)
++{
++ struct hs104_priv *priv = to_hs104(pcdev);
++ unsigned int status, pw_en;
++ int ret;
++
++ ret = regmap_read(priv->regmap, HS104_REG_PW_STATUS, &status);
++ if (ret) {
++ pw_status->c33_pw_status = ETHTOOL_C33_PSE_PW_D_STATUS_UNKNOWN;
++ return ret;
++ }
++
++ if (status & HS104_PORT_BIT(id)) {
++ pw_status->c33_pw_status = ETHTOOL_C33_PSE_PW_D_STATUS_DELIVERING;
++ return 0;
++ }
++
++ /*
++ * Not delivering: tell an administratively disabled port apart from
++ * one that is enabled but has no valid PD attached yet (searching).
++ */
++ ret = regmap_read(priv->regmap, HS104_REG_PW_EN, &pw_en);
++ if (ret) {
++ pw_status->c33_pw_status = ETHTOOL_C33_PSE_PW_D_STATUS_UNKNOWN;
++ return ret;
++ }
++
++ if (pw_en & HS104_PORT_BIT(id))
++ pw_status->c33_pw_status = ETHTOOL_C33_PSE_PW_D_STATUS_SEARCHING;
++ else
++ pw_status->c33_pw_status = ETHTOOL_C33_PSE_PW_D_STATUS_DISABLED;
++
++ return 0;
++}
++
++static int hs104_pi_get_pw_class(struct pse_controller_dev *pcdev, int id)
++{
++ struct hs104_priv *priv = to_hs104(pcdev);
++ unsigned int val;
++ int ret;
++
++ ret = regmap_read(priv->regmap, HS104_REG_PORT0_CLASS + id, &val);
++ if (ret)
++ return ret;
++
++ return val;
++}
++
++static int hs104_pi_get_actual_pw(struct pse_controller_dev *pcdev, int id)
++{
++ struct hs104_priv *priv = to_hs104(pcdev);
++
++ return hs104_read_be16(priv, HS104_REG_PORT0_POWER + id * 2,
++ HS104_MW_STEP);
++}
++
++static const struct ethtool_c33_pse_pw_limit_range hs104_pw_ranges[] = {
++ { .min = HS104_PW_AF, .max = HS104_PW_AF },
++ { .min = HS104_PW_AT, .max = HS104_PW_AT },
++ { .min = HS104_PW_BT, .max = HS104_PW_BT },
++ { .min = HS104_PW_HIPO, .max = HS104_PW_HIPO },
++};
++
++static int hs104_pi_get_pw_limit_ranges(struct pse_controller_dev *pcdev,
++ int id,
++ struct pse_pw_limit_ranges *pw_limit_ranges)
++{
++ struct ethtool_c33_pse_pw_limit_range *c33_pw_limit_ranges;
++
++ c33_pw_limit_ranges = kmemdup(hs104_pw_ranges, sizeof(hs104_pw_ranges),
++ GFP_KERNEL);
++ if (!c33_pw_limit_ranges)
++ return -ENOMEM;
++
++ pw_limit_ranges->c33_pw_limit_ranges = c33_pw_limit_ranges;
++
++ /* Return number of ranges */
++ return ARRAY_SIZE(hs104_pw_ranges);
++}
++
++static int hs104_map_event(int irq, struct pse_controller_dev *pcdev,
++ unsigned long *notifs, unsigned long *notifs_mask)
++{
++ struct hs104_priv *priv = to_hs104(pcdev);
++ unsigned int pw_status, changed;
++ int ret, i;
++
++ ret = regmap_read(priv->regmap, HS104_REG_PW_STATUS, &pw_status);
++ if (ret)
++ return ret;
++
++ /* Report ports whose power-delivery state changed. The HS104 has no
++ * per-fault status, so a delivering transition is mapped to a
++ * detection/disconnection event rather than a bogus over-current.
++ */
++ changed = pw_status ^ priv->last_pw_status;
++ priv->last_pw_status = pw_status;
++
++ for (i = 0; i < HS104_MAX_PORTS; i++) {
++ if (!(changed & HS104_PORT_BIT(i)))
++ continue;
++
++ __set_bit(i, notifs_mask);
++ notifs[i] = (pw_status & HS104_PORT_BIT(i)) ?
++ ETHTOOL_C33_PSE_EVENT_DETECTION :
++ ETHTOOL_C33_PSE_EVENT_DISCONNECTION;
++ }
++
++ return 0;
++}
++
++static const struct pse_controller_ops hs104_ops = {
++ .pi_enable = hs104_pi_enable,
++ .pi_disable = hs104_pi_disable,
++ .pi_get_admin_state = hs104_pi_get_admin_state,
++ .pi_get_pw_status = hs104_pi_get_pw_status,
++ .pi_get_pw_class = hs104_pi_get_pw_class,
++ .pi_get_actual_pw = hs104_pi_get_actual_pw,
++ .pi_get_voltage = hs104_pi_get_voltage,
++ .pi_get_pw_limit = hs104_pi_get_pw_limit,
++ .pi_set_pw_limit = hs104_pi_set_pw_limit,
++ .pi_get_pw_limit_ranges = hs104_pi_get_pw_limit_ranges,
++};
++
++/* Driver initialization */
++
++static const struct regmap_range hs104_rd_ranges[] = {
++ regmap_reg_range(HS104_REG_PW_STATUS, HS104_REG_DEVID), /* 0x01-0x0C */
++ regmap_reg_range(HS104_REG_PORT0_CLASS,
++ HS104_REG_PORT0_CLASS + HS104_MAX_PORTS - 1), /* 0x0D-0x10 */
++ regmap_reg_range(HS104_REG_PW_EN, HS104_REG_PW_EN), /* 0x14 */
++ regmap_reg_range(HS104_REG_PROTOCOL, HS104_REG_PROTOCOL), /* 0x19 */
++ regmap_reg_range(HS104_REG_TOTAL_POWER,
++ HS104_REG_TOTAL_POWER + 1), /* 0x1D-0x1E */
++ regmap_reg_range(HS104_REG_PORT0_POWER,
++ HS104_REG_PORT0_POWER + HS104_MAX_PORTS * 2 - 1),/* 0x21-0x28 */
++};
++
++static const struct regmap_range hs104_wr_ranges[] = {
++ regmap_reg_range(HS104_REG_PW_EN, HS104_REG_PW_EN), /* 0x14 */
++ regmap_reg_range(HS104_REG_PROTOCOL, HS104_REG_PROTOCOL), /* 0x19 */
++};
++
++static const struct regmap_access_table hs104_rd_table = {
++ .yes_ranges = hs104_rd_ranges,
++ .n_yes_ranges = ARRAY_SIZE(hs104_rd_ranges),
++};
++
++static const struct regmap_access_table hs104_wr_table = {
++ .yes_ranges = hs104_wr_ranges,
++ .n_yes_ranges = ARRAY_SIZE(hs104_wr_ranges),
++};
++
++static const struct regmap_config hs104_regmap_config = {
++ .reg_bits = 8,
++ .val_bits = 8,
++ .max_register = HS104_REG_PORT0_POWER + HS104_MAX_PORTS * 2 - 1,
++ .rd_table = &hs104_rd_table,
++ .wr_table = &hs104_wr_table,
++};
++
++static int hs104_probe(struct i2c_client *client)
++{
++ struct device *dev = &client->dev;
++ struct hs104_priv *priv;
++ unsigned int devid;
++ int ret;
++
++ if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C))
++ return -ENXIO;
++
++ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
++ if (!priv)
++ return -ENOMEM;
++
++ priv->regmap = devm_regmap_init_i2c(client, &hs104_regmap_config);
++ if (IS_ERR(priv->regmap))
++ return PTR_ERR(priv->regmap);
++
++ /* Verify device ID */
++ ret = regmap_read(priv->regmap, HS104_REG_DEVID, &devid);
++ if (ret)
++ return ret;
++
++ if (devid != HS104_DEVICE_ID) {
++ dev_err(dev, "Unknown device ID: 0x%02x\n", devid);
++ return -ENODEV;
++ }
++
++ /*
++ * Enable all ports before registering the PSE controller, so that
++ * pse_pi_is_hw_enabled() sees the correct state when consumers
++ * request PSE controls. HS104 only delivers power to valid PDs.
++ */
++ ret = regmap_write(priv->regmap, HS104_REG_PW_EN,
++ HS104_EXECUTE | GENMASK(HS104_MAX_PORTS - 1, 0));
++ if (ret)
++ dev_warn(dev, "Failed to enable ports: %d\n", ret);
++
++ /* Register PSE controller */
++ priv->pcdev.ops = &hs104_ops;
++ priv->pcdev.dev = dev;
++ priv->pcdev.owner = THIS_MODULE;
++ priv->pcdev.nr_lines = HS104_MAX_PORTS;
++ priv->pcdev.types = ETHTOOL_PSE_C33;
++
++ /*
++ * Set up the poll helper before registering the controller. Since the
++ * upstream v6 poll path, pse_controller_register() arms the poll work
++ * as its last step, gated on pcdev->polling (which this helper sets).
++ * Registering first would leave the work unarmed and polling - and thus
++ * the LED-trigger state updates - would never start.
++ */
++ {
++ static const struct pse_irq_desc poll_desc = {
++ .name = "hs104-poll",
++ .map_event = hs104_map_event,
++ };
++
++ ret = devm_pse_poll_helper(&priv->pcdev, &poll_desc);
++ if (ret)
++ return dev_err_probe(dev, ret,
++ "Failed to register poll helper\n");
++ }
++
++ ret = devm_pse_controller_register(dev, &priv->pcdev);
++ if (ret)
++ return dev_err_probe(dev, ret, "Failed to register PSE controller\n");
++
++ dev_info(dev, "HS104 PSE controller initialized\n");
++ return 0;
++}
++
++static const struct of_device_id hs104_of_match[] = {
++ { .compatible = "hasivo,hs104" },
++ { .compatible = "hasivo,hs104pti" },
++ { .compatible = "hasivo,hs104pbi" },
++ { }
++};
++MODULE_DEVICE_TABLE(of, hs104_of_match);
++
++static struct i2c_driver hs104_driver = {
++ .driver = {
++ .name = "hasivo-hs104",
++ .of_match_table = hs104_of_match,
++ },
++ .probe = hs104_probe,
++};
++module_i2c_driver(hs104_driver);
++
++MODULE_AUTHOR("Bevan Weiss <bevan.weiss@gmail.com>");
++MODULE_AUTHOR("Carlo Szelinsky <github@szelinsky.de>");
++MODULE_DESCRIPTION("Hasivo HS104 PoE PSE Controller");
++MODULE_LICENSE("GPL");