--- /dev/null
+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),