From: Jonas Jelonek <jonas@jonasjelonek.de>
To: Oleksij Rempel <o.rempel@pengutronix.de>,
Kory Maincent <kory.maincent@bootlin.com>,
Andrew Lunn <andrew+netdev@lunn.ch>,
"David S . Miller" <davem@davemloft.net>,
Eric Dumazet <edumazet@kernel.org>,
Jakub Kicinski <kuba@kernel.org>, Paolo Abeni <pabeni@redhat.com>,
netdev@vger.kernel.org, Rob Herring <robh@kernel.org>,
Krzysztof Kozlowski <krzk+dt@kernel.org>,
Conor Dooley <conor+dt@kernel.org>
Cc: linux-kernel@vger.kernel.org, devicetree@vger.kernel.org,
Sander Vanheule <sander@svanheule.net>,
Russell Senior <russell@personaltelco.net>,
Jonas Jelonek <jonas@jonasjelonek.de>
Subject: [PATCH net-next 3/4] net: pse-pd: realtek-pse-mcu: configure global power budget
Date: Tue, 3 Nov 2026 22:03:11 +0000 [thread overview]
Message-ID: <20261103220312.860629-4-jonas@jonasjelonek.de> (raw)
In-Reply-To: <20261103220312.860629-1-jonas@jonasjelonek.de>
Use the optional VMAIN regulator power budget to configure the MCU's
aggregate PoE power limit during probe. Quantize the available budget to
the protocol's 100 mW units and reserve exactly that amount through the
regulator core.
The regulator budget represents the usable PoE allowance. Program zero
reserved power so the previous firmware guard band does not further reduce
that allowance.
Require a finite budget of at least one protocol unit when vmain-supply is
provided. Program the budget before selecting dynamic power management and
ungating the ports, so enforcement starts with the regulator-derived limit.
Enable VMAIN before hardware initialization and register one managed cleanup
action to free any reserved budget and release the enable reference on probe
failure and driver removal. Record each successful reservation immediately
so subsequent initialization failures are covered by this action.
Read the current budget index before programming the MCU. Retain the
programmed MCU limit on probe failure and driver removal, as reducing that
limit to zero would interrupt ongoing power delivery.
Boards without vmain-supply retain their existing firmware-managed
budget.
Signed-off-by: Jonas Jelonek <jonas@jonasjelonek.de>
---
drivers/net/pse-pd/realtek-pse-mcu-core.c | 129 +++++++++++++++++++++-
drivers/net/pse-pd/realtek-pse-mcu.h | 3 +
2 files changed, 129 insertions(+), 3 deletions(-)
diff --git a/drivers/net/pse-pd/realtek-pse-mcu-core.c b/drivers/net/pse-pd/realtek-pse-mcu-core.c
index f3fc67fe29d3..a90e19fb90e0 100644
--- a/drivers/net/pse-pd/realtek-pse-mcu-core.c
+++ b/drivers/net/pse-pd/realtek-pse-mcu-core.c
@@ -34,10 +34,12 @@
#include <linux/delay.h>
#include <linux/gpio/consumer.h>
#include <linux/jiffies.h>
+#include <linux/math.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/unaligned.h>
#include "realtek-pse-mcu.h"
@@ -62,6 +64,8 @@
#define RTPSE_MCU_MAX_PORTS 48
#define RTPSE_MCU_PORT_MAX_PRIORITY 3
+#define RTPSE_MCU_GLOBAL_BUDGET_LSB_MW 100
+
/* Bounded resends when the MCU replies NOT_READY (busy). */
#define RTPSE_MCU_NOT_READY_RETRIES 3
@@ -76,6 +80,8 @@ enum rtpse_mcu_cmd {
RTPSE_MCU_CMD_GET_SYSTEM_INFO,
RTPSE_MCU_CMD_GET_EXT_CONFIG,
RTPSE_MCU_CMD_SET_POWER_MANAGEMENT_MODE,
+ RTPSE_MCU_CMD_GET_POWER_STATUS,
+ RTPSE_MCU_CMD_SET_GLOBAL_BUDGET,
RTPSE_MCU_CMD_PORT_ENABLE,
RTPSE_MCU_CMD_PORT_SET_POWER_LIMIT_TYPE,
@@ -393,6 +399,84 @@ static int rtpse_mcu_set_power_management_mode(struct rtpse_mcu_ctrl *pse, u8 va
return (resp.payload[0] == 0x0) ? 0 : -EIO;
}
+static int rtpse_mcu_get_global_budget_idx(struct rtpse_mcu_ctrl *pse, u8 *budget_idx)
+{
+ struct rtpse_mcu_msg req, resp;
+ const struct rtpse_mcu_opcode *opc;
+ int ret;
+
+ opc = &pse->dialect->opcode[RTPSE_MCU_CMD_GET_POWER_STATUS];
+ if (!opc->valid)
+ return -EOPNOTSUPP;
+
+ rtpse_mcu_msg_init(&req, opc->op);
+ ret = rtpse_mcu_do_xfer(pse, &req, &resp);
+ if (ret)
+ return ret;
+
+ if (resp.payload[4] > 0x7)
+ return -ERANGE;
+
+ *budget_idx = resp.payload[4];
+ return 0;
+}
+
+static int rtpse_mcu_set_global_budget(struct rtpse_mcu_ctrl *pse, u8 budget_idx,
+ u16 budget_raw)
+{
+ const struct rtpse_mcu_opcode *opc;
+ struct rtpse_mcu_msg req, resp;
+ int ret;
+
+ opc = &pse->dialect->opcode[RTPSE_MCU_CMD_SET_GLOBAL_BUDGET];
+ if (!opc->valid)
+ return -EOPNOTSUPP;
+
+ rtpse_mcu_msg_init(&req, opc->op);
+ req.payload[0] = budget_idx;
+ put_unaligned_be16(budget_raw, &req.payload[1]);
+ put_unaligned_be16(0, &req.payload[3]);
+
+ ret = rtpse_mcu_do_xfer(pse, &req, &resp);
+ if (ret)
+ return ret;
+ if (resp.payload[0] != budget_idx || resp.payload[1] != 0)
+ return -EIO;
+
+ return 0;
+}
+
+static int rtpse_mcu_apply_global_budget(struct rtpse_mcu_ctrl *pse,
+ struct regulator *vmain)
+{
+ int budget, ret;
+ u16 budget_raw;
+ u8 budget_idx;
+
+ budget = regulator_get_unclaimed_power_budget(vmain);
+ if (budget == INT_MAX)
+ return -ENODATA;
+ if (budget < 0 || budget > U16_MAX * RTPSE_MCU_GLOBAL_BUDGET_LSB_MW)
+ return -ERANGE;
+
+ budget = rounddown(budget, RTPSE_MCU_GLOBAL_BUDGET_LSB_MW);
+ if (!budget)
+ return -ERANGE;
+ budget_raw = budget / RTPSE_MCU_GLOBAL_BUDGET_LSB_MW;
+
+ ret = regulator_request_power_budget(vmain, budget);
+ if (ret)
+ return ret;
+ pse->requested_budget_mW = budget;
+
+ ret = rtpse_mcu_get_global_budget_idx(pse, &budget_idx);
+ if (ret)
+ return ret;
+
+ /* Retain the programmed MCU limit even if later probe steps fail. */
+ return rtpse_mcu_set_global_budget(pse, budget_idx, budget_raw);
+}
+
/* Port operations */
static int rtpse_mcu_port_get_status(struct rtpse_mcu_ctrl *pse, unsigned int port,
@@ -801,6 +885,15 @@ static int rtpse_mcu_discover(struct rtpse_mcu_ctrl *pse, struct rtpse_mcu_info
return 0;
}
+static void rtpse_mcu_cleanup_vmain(void *data)
+{
+ struct rtpse_mcu_ctrl *pse = data;
+
+ if (pse->requested_budget_mW)
+ regulator_free_power_budget(pse->vmain, pse->requested_budget_mW);
+ regulator_disable(pse->vmain);
+}
+
static void rtpse_mcu_global_disable(void *data)
{
struct rtpse_mcu_ctrl *pse = data;
@@ -813,6 +906,7 @@ int rtpse_mcu_register(struct rtpse_mcu_ctrl *pse)
const struct rtpse_mcu_match_data *match;
struct rtpse_mcu_info info;
struct gpio_desc *gpiod;
+ struct regulator *vmain;
int ret;
BUILD_BUG_ON(sizeof(struct rtpse_mcu_msg) != RTPSE_MCU_MSG_SIZE);
@@ -837,6 +931,25 @@ int rtpse_mcu_register(struct rtpse_mcu_ctrl *pse)
return dev_err_probe(pse->dev, -EINVAL,
"dialect for chip is incomplete\n");
+ vmain = devm_regulator_get_optional(pse->dev, "vmain");
+ if (PTR_ERR(vmain) == -ENODEV)
+ vmain = NULL;
+ else if (IS_ERR(vmain))
+ return dev_err_probe(pse->dev, PTR_ERR(vmain),
+ "failed to get vmain supply\n");
+
+ if (vmain) {
+ ret = regulator_enable(vmain);
+ if (ret)
+ return dev_err_probe(pse->dev, ret,
+ "failed to enable vmain supply\n");
+
+ pse->vmain = vmain;
+ ret = devm_add_action_or_reset(pse->dev, rtpse_mcu_cleanup_vmain, pse);
+ if (ret)
+ return ret;
+ }
+
/*
* Release the MCU from reset before the first transaction; the
* boot-retry loop in discover() waits for it to answer.
@@ -850,6 +963,13 @@ int rtpse_mcu_register(struct rtpse_mcu_ctrl *pse)
if (ret)
return ret;
+ if (vmain) {
+ ret = rtpse_mcu_apply_global_budget(pse, vmain);
+ if (ret)
+ return dev_err_probe(pse->dev, ret,
+ "failed to apply global power budget\n");
+ }
+
/* ensure the MCU does dynamic power management with priorities */
ret = rtpse_mcu_set_power_management_mode(pse, RTPSE_MCU_POWER_MGMT_DYNAMIC_PRIO);
if (ret)
@@ -858,9 +978,8 @@ int rtpse_mcu_register(struct rtpse_mcu_ctrl *pse)
/*
* Some boards gate all ports through a hardware line; deassert it only
- * after the MCU is confirmed, so a discover failure never ungates the
- * ports. It is then left to the MCU - not re-gated on unbind or a later
- * probe error - so a driver reload doesn't black out PoE.
+ * after discovery, budget setup and management-mode selection succeed.
+ * Do not reassert it on unbind or a later probe error.
*/
gpiod = devm_gpiod_get_optional(pse->dev, "disable-ports", GPIOD_OUT_LOW);
if (IS_ERR(gpiod))
@@ -975,6 +1094,8 @@ static const struct rtpse_mcu_dialect rtpse_mcu_dialect_gen2 = {
[RTPSE_MCU_CMD_GET_SYSTEM_INFO] = RTPSE_MCU_OP(0x40),
[RTPSE_MCU_CMD_GET_EXT_CONFIG] = RTPSE_MCU_OP(0x4a),
[RTPSE_MCU_CMD_SET_POWER_MANAGEMENT_MODE] = RTPSE_MCU_OP(0x10),
+ [RTPSE_MCU_CMD_GET_POWER_STATUS] = RTPSE_MCU_OP(0x41),
+ [RTPSE_MCU_CMD_SET_GLOBAL_BUDGET] = RTPSE_MCU_OP(0x04),
[RTPSE_MCU_CMD_PORT_ENABLE] = RTPSE_MCU_OP(0x01),
[RTPSE_MCU_CMD_PORT_SET_POWER_LIMIT_TYPE] = RTPSE_MCU_OP(0x12),
@@ -996,6 +1117,8 @@ static const struct rtpse_mcu_dialect rtpse_mcu_dialect_gen1 = {
[RTPSE_MCU_CMD_GET_SYSTEM_INFO] = RTPSE_MCU_OP(0x20),
[RTPSE_MCU_CMD_GET_EXT_CONFIG] = RTPSE_MCU_OP(0x2b),
[RTPSE_MCU_CMD_SET_POWER_MANAGEMENT_MODE] = RTPSE_MCU_OP(0x17),
+ [RTPSE_MCU_CMD_GET_POWER_STATUS] = RTPSE_MCU_OP(0x23),
+ [RTPSE_MCU_CMD_SET_GLOBAL_BUDGET] = RTPSE_MCU_OP(0x18),
[RTPSE_MCU_CMD_PORT_ENABLE] = RTPSE_MCU_OP(0x00),
[RTPSE_MCU_CMD_PORT_SET_POWER_LIMIT_TYPE] = RTPSE_MCU_OP(0x15),
diff --git a/drivers/net/pse-pd/realtek-pse-mcu.h b/drivers/net/pse-pd/realtek-pse-mcu.h
index 5e31dd0993dd..56899ec4d76f 100644
--- a/drivers/net/pse-pd/realtek-pse-mcu.h
+++ b/drivers/net/pse-pd/realtek-pse-mcu.h
@@ -62,6 +62,7 @@ static inline bool rtpse_mcu_resp_is_final(const struct rtpse_mcu_msg *req,
struct rtpse_mcu_dialect;
struct rtpse_mcu_chip_info;
struct rtpse_mcu_ctrl;
+struct regulator;
/* Per-compatible match data (the of_match .data). */
struct rtpse_mcu_match_data {
@@ -82,6 +83,8 @@ struct rtpse_mcu_ctrl {
const struct rtpse_mcu_dialect *dialect;
const struct rtpse_mcu_chip_info *chip;
const struct rtpse_mcu_transport_ops *transport;
+ struct regulator *vmain;
+ unsigned int requested_budget_mW;
u8 seq; /* rolling request seq_num, echoed by the MCU */
};
--
2.53.0
next prev parent reply other threads:[~2026-11-03 22:03 UTC|newest]
Thread overview: 5+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-03 22:03 [PATCH net-next 0/4] net: pse-pd: realtek-pse-mcu: establish power management configuration Jonas Jelonek
2026-11-03 22:03 ` [PATCH net-next 1/4] net: pse-pd: realtek-pse-mcu: ensure MCU does dynamic power management Jonas Jelonek
2026-11-03 22:03 ` [PATCH net-next 2/4] dt-bindings: net: pse-pd: realtek,pse-mcu: add VMAIN supply Jonas Jelonek
2026-11-03 22:03 ` Jonas Jelonek [this message]
2026-11-03 22:03 ` [PATCH net-next 4/4] net: pse-pd: realtek-pse-mcu: avoid global disable on teardown Jonas Jelonek
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261103220312.860629-4-jonas@jonasjelonek.de \
--to=jonas@jonasjelonek.de \
--cc=andrew+netdev@lunn.ch \
--cc=conor+dt@kernel.org \
--cc=davem@davemloft.net \
--cc=devicetree@vger.kernel.org \
--cc=edumazet@kernel.org \
--cc=kory.maincent@bootlin.com \
--cc=krzk+dt@kernel.org \
--cc=kuba@kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=netdev@vger.kernel.org \
--cc=o.rempel@pengutronix.de \
--cc=pabeni@redhat.com \
--cc=robh@kernel.org \
--cc=russell@personaltelco.net \
--cc=sander@svanheule.net \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®