kernel: pse-pd: add Hasivo HS104 PoE PSE controller driver
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>
This commit is contained in:
parent
7926c03b4d
commit
d3c5d345d2
2 changed files with 579 additions and 0 deletions
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
Loading…
Reference in a new issue