]> git.ipfire.org Git - thirdparty/openwrt.git/commitdiff
kernel: pse-pd: add Hasivo HS104 PoE PSE controller driver
authorCarlo Szelinsky <github@szelinsky.de>
Sat, 18 Jul 2026 13:38:39 +0000 (15:38 +0200)
committerJonas Jelonek <jelonek.jonas@gmail.com>
Tue, 21 Jul 2026 16:59:26 +0000 (18:59 +0200)
Direct-I2C driver for the HS104 PSE chip (unlike the MCU-fronted
realtek-pse-mcu family). Packaged as kmod-pse-hasivo-hs104.

This driver will be submitted upstream once the pending PSE-core
patches it builds on (above) have landed.

Signed-off-by: Carlo Szelinsky <github@szelinsky.de>
Link: https://github.com/openwrt/openwrt/pull/22245
Signed-off-by: Jonas Jelonek <jelonek.jonas@gmail.com>
package/kernel/linux/modules/pse-pd.mk
target/linux/realtek/patches-6.18/818-net-pse-pd-add-hasivo-hs104-driver.patch [new file with mode: 0644]

index 5190d6ebf970bd2ddc8b1eac9814bb301939e4ff..95294fde0f4fe529a0f105cf24f8792489849a91 100644 (file)
@@ -143,3 +143,21 @@ define KernelPackage/pse-realtek-mcu-uart/description
 endef
 
 $(eval $(call KernelPackage,pse-realtek-mcu-uart))
+
+define KernelPackage/pse-hasivo-hs104
+  SUBMENU:=$(PSE_MENU)
+  TITLE:=Hasivo HS104 PSE controller support
+  KCONFIG:=CONFIG_PSE_HASIVO_HS104
+  DEPENDS:=+kmod-i2c-core
+  FILES:=$(LINUX_DIR)/drivers/net/pse-pd/hasivo_hs104.ko
+  AUTOLOAD:=$(call AutoProbe,hasivo_hs104)
+  $(call AddDepends/pse-pd)
+endef
+
+define KernelPackage/pse-hasivo-hs104/description
+ Kernel module for the Hasivo HS104 PoE PSE controller chips.
+ Supports the HS104PTI/HS104PBI single-chip PoE PSE controllers
+ managing 4 delivery channels for 802.3af/at/bt power over I2C.
+endef
+
+$(eval $(call KernelPackage,pse-hasivo-hs104))
diff --git a/target/linux/realtek/patches-6.18/818-net-pse-pd-add-hasivo-hs104-driver.patch b/target/linux/realtek/patches-6.18/818-net-pse-pd-add-hasivo-hs104-driver.patch
new file mode 100644 (file)
index 0000000..cf6a22c
--- /dev/null
@@ -0,0 +1,561 @@
+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");