]> git.ipfire.org Git - thirdparty/openwrt.git/commitdiff
realtek: pse: add patch for sysfs detection control
authorJonas Jelonek <jelonek.jonas@gmail.com>
Wed, 1 Jul 2026 10:47:27 +0000 (10:47 +0000)
committerJonas Jelonek <jelonek.jonas@gmail.com>
Tue, 14 Jul 2026 21:45:29 +0000 (23:45 +0200)
Add a patch which modifies the Realtek PSE MCU driver backport to add a
sysfs control to control detection mode per port. Some older,
bad-designed devices aren't detected properly in standard mode on some
switches, though vendor firmware powers them properly. Testing shows
that enabling the detection of legacy PDs by allowing wider resistance,
more input capacitance and using a wider detection window, solves this
issue.

The legacy detection can be activated by writing 1 to
portN/detection_legacy in the device's sysfs, e.g.

  echo 1 > /sys/bus/i2c/devices/3-0020/port0/detection_legacy

This patch is kept downstream for now, upstreaming will be attempted
though it is likely that upstream prefers a generic pse-pd approach.

Link: https://github.com/openwrt/openwrt/pull/23222
Signed-off-by: Jonas Jelonek <jelonek.jonas@gmail.com>
target/linux/realtek/patches-6.18/817-net-pse-pd-realtek-pse-mcu-add-per-por-legacy-detec.patch [new file with mode: 0644]

diff --git a/target/linux/realtek/patches-6.18/817-net-pse-pd-realtek-pse-mcu-add-per-por-legacy-detec.patch b/target/linux/realtek/patches-6.18/817-net-pse-pd-realtek-pse-mcu-add-per-por-legacy-detec.patch
new file mode 100644 (file)
index 0000000..a48228e
--- /dev/null
@@ -0,0 +1,276 @@
+From b69e39ba5853c587a594ed0475f873519301f326 Mon Sep 17 00:00:00 2001
+From: Jonas Jelonek <jelonek.jonas@gmail.com>
+Date: Tue, 30 Jun 2026 20:46:30 +0000
+Subject: [PATCH] net: pse-pd: realtek-pse-mcu: add per-port legacy-detection
+ sysfs control
+
+Some PDs are rejected during detection (the PSE reports Rlow) and never
+get powered, because the standard detection window is deliberately narrow:
+it is the safety check that keeps the PSE from energizing something that is
+not a compliant PD. Enabling legacy detection also accepts legacy/quirky
+PDs (higher signature resistance, larger input capacitance), at the cost of
+admitting the same signatures a non-PD or faulty port can present.
+
+Expose this as an opt-in, root-only, per-port boolean rather than a
+default. Each port gets a sysfs directory with a detection_legacy file:
+
+ <dev>/port<N>/detection_legacy
+
+Writing 1 widens detection to accept legacy PDs, 0 restores standard-only
+detection; reads report the current mode decoded from the port config.
+
+The detection-type command and its value encoding are dialect-specific, so
+both are taken from the dialect: the command opcode (0x09 on Realtek, 0x10
+on Broadcom) goes through the opcode table, and the standard/legacy byte
+values come from the dialect too. These differ - standard detection is 0x0
+on Realtek but 0x2 on Broadcom, where 0x0 instead disables detection - so a
+shared value would misprogram one of them. Finer modes the firmware offers
+(disabling detection, force-powering devices that fail classification) are
+deliberately kept out of this stable interface. Each port has its own
+attribute group so further per-port controls can be added later.
+
+Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
+Signed-off-by: Jonas Jelonek <jelonek.jonas@gmail.com>
+
+--- a/drivers/net/pse-pd/realtek-pse-mcu-core.c
++++ b/drivers/net/pse-pd/realtek-pse-mcu-core.c
+@@ -34,11 +34,13 @@
+ #include <linux/delay.h>
+ #include <linux/gpio/consumer.h>
+ #include <linux/jiffies.h>
++#include <linux/kstrtox.h>
+ #include <linux/minmax.h>
+ #include <linux/module.h>
+ #include <linux/property.h>
+ #include <linux/pse-pd/pse.h>
+ #include <linux/regulator/consumer.h>
++#include <linux/sysfs.h>
+ #include <linux/unaligned.h>
+ #include "realtek-pse-mcu.h"
+@@ -73,6 +75,7 @@ enum rtpse_mcu_cmd {
+       RTPSE_MCU_CMD_PORT_SET_POWER_LIMIT,
+       RTPSE_MCU_CMD_PORT_SET_POWER_LIMIT_EXT,
+       RTPSE_MCU_CMD_PORT_SET_PRIORITY,
++      RTPSE_MCU_CMD_PORT_SET_DETECTION_TYPE,
+       RTPSE_MCU_CMD_PORT_GET_STATUS,
+       RTPSE_MCU_CMD_PORT_GET_POWER_STATS,
+       RTPSE_MCU_CMD_PORT_GET_CONFIG,
+@@ -123,6 +126,7 @@ struct rtpse_mcu_port_measurement {
+ struct rtpse_mcu_port_config {
+       bool enable;
++      u8 detection_type;
+ };
+ struct rtpse_mcu_port_ext_config {
+@@ -142,6 +146,15 @@ struct rtpse_mcu_dialect {
+       void (*parse_system_info)(const u8 *payload, struct rtpse_mcu_info *info);
+       int (*parse_port_class)(const struct rtpse_mcu_port_status *status);
+       const char *(*mcu_type_str)(unsigned int mcu_type);
++
++      /*
++       * Detection-mode byte written by the per-port legacy-detection knob.
++       * The encoding is dialect-specific: standard detection is 0x0 on RTL
++       * but 0x2 on BCM (where 0x0 instead disables detection entirely), so
++       * these cannot be shared. Legacy acceptance is the odd value in both.
++       */
++      u8 detect_standard;
++      u8 detect_legacy;
+ };
+ struct rtpse_mcu_chip_info {
+@@ -440,6 +453,7 @@ static int rtpse_mcu_port_get_config(str
+               return ret;
+       config->enable = (resp.payload[1] == 1);
++      config->detection_type = resp.payload[3];
+       return 0;
+ }
+@@ -706,6 +720,138 @@ static const struct pse_controller_ops r
+       .pi_set_prio = rtpse_mcu_port_set_prio,
+ };
++/*
++ * Per-port controls, exposed under sysfs as one directory per port:
++ *
++ *   <dev>/port<N>/detection_legacy
++ *
++ * Each port gets its own attribute group so further per-port knobs can be
++ * added as files alongside detection_legacy later.
++ *
++ * detection_legacy widens the PD detection window. That window is normally
++ * kept narrow on purpose: it is the safety check that stops the PSE from
++ * energizing something that is not a compliant PD. Enabling legacy detection
++ * also admits legacy/quirky PDs (higher signature resistance, larger input
++ * capacitance), at the cost of admitting the same signatures a non-PD or
++ * faulty port can present. It is therefore an opt-in, root-only, per-port
++ * boolean and never a default.
++ *
++ * The MCU expresses this as a detection-mode byte whose opcode and encoding
++ * are dialect-specific (see rtpse_mcu_dialect.detect_*); only the standard and
++ * legacy endpoints are exposed here. Finer modes (e.g. disabling detection, or
++ * force-powering devices that fail classification) are deliberately left out
++ * of this stable interface.
++ */
++static int rtpse_mcu_port_set_detection_type(struct rtpse_mcu_ctrl *pse, unsigned int port,
++                                           u8 type)
++{
++      const struct rtpse_mcu_opcode *opc;
++
++      opc = &pse->dialect->opcode[RTPSE_MCU_CMD_PORT_SET_DETECTION_TYPE];
++      if (!opc->valid)
++              return -EOPNOTSUPP;
++
++      return rtpse_mcu_port_cmd(pse, port, opc->op, type);
++}
++
++/* A device_attribute that also carries the port it belongs to. */
++struct rtpse_mcu_port_attr {
++      struct device_attribute attr;
++      struct rtpse_mcu_ctrl *pse;
++      unsigned int port;
++};
++
++/* sysfs plumbing for one port: its files, the NULL-terminated attr list, and
++ * the per-port group (the "port<N>" directory). Grow rtpse_mcu_port_attr
++ * members and @attrs together when adding more per-port files.
++ */
++struct rtpse_mcu_port_sysfs {
++      struct rtpse_mcu_port_attr detection_legacy;
++      struct attribute *attrs[2];
++      struct attribute_group group;
++};
++
++static ssize_t detection_legacy_show(struct device *dev, struct device_attribute *attr,
++                                   char *buf)
++{
++      struct rtpse_mcu_port_attr *pa =
++              container_of(attr, struct rtpse_mcu_port_attr, attr);
++      struct rtpse_mcu_port_config config;
++      bool legacy;
++      int ret;
++
++      ret = rtpse_mcu_port_get_config(pa->pse, pa->port, &config);
++      if (ret)
++              return ret;
++
++      /* Legacy acceptance is the odd detection mode in both dialects. */
++      legacy = config.detection_type & 0x1;
++
++      return sysfs_emit(buf, "%u\n", legacy);
++}
++
++static ssize_t detection_legacy_store(struct device *dev, struct device_attribute *attr,
++                                    const char *buf, size_t count)
++{
++      struct rtpse_mcu_port_attr *pa =
++              container_of(attr, struct rtpse_mcu_port_attr, attr);
++      bool legacy;
++      int ret;
++
++      ret = kstrtobool(buf, &legacy);
++      if (ret)
++              return ret;
++
++      ret = rtpse_mcu_port_set_detection_type(pa->pse, pa->port,
++                                              legacy ? pa->pse->dialect->detect_legacy :
++                                                       pa->pse->dialect->detect_standard);
++      if (ret)
++              return ret;
++
++      return count;
++}
++
++static int rtpse_mcu_sysfs_init(struct rtpse_mcu_ctrl *pse)
++{
++      unsigned int n = pse->pcdev.nr_lines, i;
++      struct rtpse_mcu_port_sysfs *ports;
++      int ret;
++
++      /* Nothing to expose if the dialect has no detection-type command. */
++      if (!pse->dialect->opcode[RTPSE_MCU_CMD_PORT_SET_DETECTION_TYPE].valid)
++              return 0;
++
++      ports = devm_kcalloc(pse->dev, n, sizeof(*ports), GFP_KERNEL);
++      if (!ports)
++              return -ENOMEM;
++
++      for (i = 0; i < n; i++) {
++              struct rtpse_mcu_port_sysfs *p = &ports[i];
++
++              sysfs_attr_init(&p->detection_legacy.attr.attr);
++              p->detection_legacy.attr.attr.name = "detection_legacy";
++              p->detection_legacy.attr.attr.mode = 0600;
++              p->detection_legacy.attr.show = detection_legacy_show;
++              p->detection_legacy.attr.store = detection_legacy_store;
++              p->detection_legacy.pse = pse;
++              p->detection_legacy.port = i;
++
++              p->attrs[0] = &p->detection_legacy.attr.attr;
++              p->attrs[1] = NULL;
++
++              p->group.name = devm_kasprintf(pse->dev, GFP_KERNEL, "port%u", i);
++              if (!p->group.name)
++                      return -ENOMEM;
++              p->group.attrs = p->attrs;
++
++              ret = devm_device_add_group(pse->dev, &p->group);
++              if (ret)
++                      return ret;
++      }
++
++      return 0;
++}
++
+ static int rtpse_mcu_discover(struct rtpse_mcu_ctrl *pse, struct rtpse_mcu_info *info)
+ {
+       struct rtpse_mcu_ext_config ext_config;
+@@ -861,7 +1007,11 @@ int rtpse_mcu_register(struct rtpse_mcu_
+       pse->pcdev.pis_prio_max = RTPSE_MCU_PORT_MAX_PRIORITY;
+       pse->pcdev.supp_budget_eval_strategies = PSE_BUDGET_EVAL_STRAT_DYNAMIC;
+-      return devm_pse_controller_register(pse->dev, &pse->pcdev);
++      ret = devm_pse_controller_register(pse->dev, &pse->pcdev);
++      if (ret)
++              return ret;
++
++      return rtpse_mcu_sysfs_init(pse);
+ }
+ EXPORT_SYMBOL_GPL(rtpse_mcu_register);
+@@ -937,6 +1087,8 @@ static const struct rtpse_mcu_dialect rt
+       .parse_system_info = rtpse_mcu_gen2_parse_system_info,
+       .parse_port_class  = rtpse_mcu_gen2_parse_port_class,
+       .mcu_type_str      = rtpse_mcu_gen2_mcu_type_str,
++      .detect_standard   = 0x0,
++      .detect_legacy     = 0x1,
+       .opcode = {
+               [RTPSE_MCU_CMD_SET_GLOBAL_STATE]        = RTPSE_MCU_OP(0x00),
+               [RTPSE_MCU_CMD_GET_SYSTEM_INFO]         = RTPSE_MCU_OP(0x40),
+@@ -947,6 +1099,7 @@ static const struct rtpse_mcu_dialect rt
+               [RTPSE_MCU_CMD_PORT_SET_POWER_LIMIT]    = RTPSE_MCU_OP(0x13),
+               [RTPSE_MCU_CMD_PORT_SET_POWER_LIMIT_EXT] = RTPSE_MCU_OP(0x14),
+               [RTPSE_MCU_CMD_PORT_SET_PRIORITY]       = RTPSE_MCU_OP(0x15),
++              [RTPSE_MCU_CMD_PORT_SET_DETECTION_TYPE] = RTPSE_MCU_OP(0x09),
+               [RTPSE_MCU_CMD_PORT_GET_STATUS]         = RTPSE_MCU_OP(0x42),
+               [RTPSE_MCU_CMD_PORT_GET_POWER_STATS]    = RTPSE_MCU_OP(0x44),
+               [RTPSE_MCU_CMD_PORT_GET_CONFIG]         = RTPSE_MCU_OP(0x48),
+@@ -958,6 +1111,8 @@ static const struct rtpse_mcu_dialect rt
+       .parse_system_info = rtpse_mcu_gen1_parse_system_info,
+       .parse_port_class  = rtpse_mcu_gen1_parse_port_class,
+       .mcu_type_str      = rtpse_mcu_gen1_mcu_type_str,
++      .detect_standard   = 0x2,
++      .detect_legacy     = 0x3,
+       .opcode = {
+               [RTPSE_MCU_CMD_GET_SYSTEM_INFO]         = RTPSE_MCU_OP(0x20),
+               [RTPSE_MCU_CMD_GET_EXT_CONFIG]          = RTPSE_MCU_OP(0x2b),
+@@ -966,6 +1121,7 @@ static const struct rtpse_mcu_dialect rt
+               [RTPSE_MCU_CMD_PORT_SET_POWER_LIMIT_TYPE] = RTPSE_MCU_OP(0x15),
+               [RTPSE_MCU_CMD_PORT_SET_POWER_LIMIT]    = RTPSE_MCU_OP(0x16),
+               [RTPSE_MCU_CMD_PORT_SET_PRIORITY]       = RTPSE_MCU_OP(0x1a),
++              [RTPSE_MCU_CMD_PORT_SET_DETECTION_TYPE] = RTPSE_MCU_OP(0x10),
+               [RTPSE_MCU_CMD_PORT_GET_STATUS]         = RTPSE_MCU_OP(0x21),
+               [RTPSE_MCU_CMD_PORT_GET_POWER_STATS]    = RTPSE_MCU_OP(0x30),
+               [RTPSE_MCU_CMD_PORT_GET_CONFIG]         = RTPSE_MCU_OP(0x25),