mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-05-14 02:59:19 -04:00
Merge branch 'add-support-for-pse-budget-evaluation-strategy'
Kory Maincent says: ==================== Add support for PSE budget evaluation strategy This series brings support for budget evaluation strategy in the PSE subsystem. PSE controllers can set priorities to decide which ports should be turned off in case of special events like over-current. This patch series adds support for two budget evaluation strategy. 1. Static Method: This method involves distributing power based on PD classification. It’s straightforward and stable, the PSE core keeping track of the budget and subtracting the power requested by each PD’s class. Advantages: Every PD gets its promised power at any time, which guarantees reliability. Disadvantages: PD classification steps are large, meaning devices request much more power than they actually need. As a result, the power supply may only operate at, say, 50% capacity, which is inefficient and wastes money. 2. Dynamic Method: To address the inefficiencies of the static method, vendors like Microchip have introduced dynamic power budgeting, as seen in the PD692x0 firmware. This method monitors the current consumption per port and subtracts it from the available power budget. When the budget is exceeded, lower-priority ports are shut down. Advantages: This method optimizes resource utilization, saving costs. Disadvantages: Low-priority devices may experience instability. The UAPI allows adding support for software port priority mode managed from userspace later if needed. Patches 1-2: Add support for interrupt event report in PSE core, ethtool and ethtool specs. Patch 3: Adds support for interrupt and event report in TPS23881 driver. Patches 4,5: Add support for PSE power domain in PSE core and ethtool. Patches 6-8: Add support for budget evaluation strategy in PSE core, ethtool and ethtool specs. Patches 9-11: Add support for port priority and power supplies in PD692x0 drivers. Patches 12,13: Add support for port priority in TPS23881 drivers. ==================== Link: https://patch.msgid.link/20250617-feature_poe_port_prio-v14-0-78a1a645e2ee@bootlin.com Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
@@ -22,6 +22,12 @@ properties:
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
vdd-supply:
|
||||
description: Regulator that provides 3.3V VDD power supply.
|
||||
|
||||
vdda-supply:
|
||||
description: Regulator that provides 3.3V VDDA power supply.
|
||||
|
||||
managers:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
@@ -68,6 +74,15 @@ properties:
|
||||
"#size-cells":
|
||||
const: 0
|
||||
|
||||
vmain-supply:
|
||||
description: Regulator that provides 44-57V VMAIN power supply.
|
||||
|
||||
vaux5-supply:
|
||||
description: Regulator that provides 5V VAUX5 power supply.
|
||||
|
||||
vaux3p3-supply:
|
||||
description: Regulator that provides 3.3V VAUX3P3 power supply.
|
||||
|
||||
patternProperties:
|
||||
'^port@[0-7]$':
|
||||
type: object
|
||||
@@ -106,10 +121,11 @@ examples:
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
manager@0 {
|
||||
manager0: manager@0 {
|
||||
reg = <0>;
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
vmain-supply = <&pse1_supply>;
|
||||
|
||||
phys0: port@0 {
|
||||
reg = <0>;
|
||||
@@ -161,7 +177,7 @@ examples:
|
||||
pairset-names = "alternative-a", "alternative-b";
|
||||
pairsets = <&phys0>, <&phys1>;
|
||||
polarity-supported = "MDI", "S";
|
||||
vpwr-supply = <&vpwr1>;
|
||||
vpwr-supply = <&manager0>;
|
||||
};
|
||||
pse_pi1: pse-pi@1 {
|
||||
reg = <1>;
|
||||
@@ -169,7 +185,7 @@ examples:
|
||||
pairset-names = "alternative-a";
|
||||
pairsets = <&phys2>;
|
||||
polarity-supported = "MDI";
|
||||
vpwr-supply = <&vpwr2>;
|
||||
vpwr-supply = <&manager0>;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
@@ -20,6 +20,9 @@ properties:
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
interrupts:
|
||||
maxItems: 1
|
||||
|
||||
'#pse-cells':
|
||||
const: 1
|
||||
|
||||
@@ -62,9 +65,12 @@ unevaluatedProperties: false
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
- interrupts
|
||||
|
||||
examples:
|
||||
- |
|
||||
#include <dt-bindings/interrupt-controller/irq.h>
|
||||
|
||||
i2c {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
@@ -72,6 +78,8 @@ examples:
|
||||
ethernet-pse@20 {
|
||||
compatible = "ti,tps23881";
|
||||
reg = <0x20>;
|
||||
interrupts = <8 IRQ_TYPE_LEVEL_HIGH>;
|
||||
interrupt-parent = <&gpiog>;
|
||||
|
||||
channels {
|
||||
#address-cells = <1>;
|
||||
|
||||
@@ -118,6 +118,43 @@ definitions:
|
||||
doc: |
|
||||
Hardware timestamp comes from one PHY device
|
||||
of the network topology
|
||||
-
|
||||
name: pse-event
|
||||
doc: PSE event list for the PSE controller
|
||||
type: flags
|
||||
name-prefix: ethtool-
|
||||
entries:
|
||||
-
|
||||
name: pse-event-over-current
|
||||
doc: PSE output current is too high
|
||||
-
|
||||
name: pse-event-over-temp
|
||||
doc: PSE in over temperature state
|
||||
-
|
||||
name: c33-pse-event-detection
|
||||
doc: |
|
||||
detection process occur on the PSE. IEEE 802.3-2022 33.2.5 and
|
||||
145.2.6 PSE detection of PDs. IEEE 802.3-202 30.9.1.1.5
|
||||
aPSEPowerDetectionStatus
|
||||
-
|
||||
name: c33-pse-event-classification
|
||||
doc: |
|
||||
classification process occur on the PSE. IEEE 802.3-2022 33.2.6
|
||||
and 145.2.8 classification of PDs mutual identification.
|
||||
IEEE 802.3-2022 30.9.1.1.8 aPSEPowerClassification.
|
||||
-
|
||||
name: c33-pse-event-disconnection
|
||||
doc: |
|
||||
PD has been disconnected on the PSE. IEEE 802.3-2022 33.3.8
|
||||
and 145.3.9 PD Maintain Power Signature. IEEE 802.3-2022
|
||||
33.5.1.2.9 MPS Absent. IEEE 802.3-2022 30.9.1.1.20
|
||||
aPSEMPSAbsentCounter.
|
||||
-
|
||||
name: pse-event-over-budget
|
||||
doc: PSE turned off due to over budget situation
|
||||
-
|
||||
name: pse-event-sw-pw-control-error
|
||||
doc: PSE faced an error managing the power control from software
|
||||
|
||||
attribute-sets:
|
||||
-
|
||||
@@ -1395,6 +1432,18 @@ attribute-sets:
|
||||
type: nest
|
||||
multi-attr: true
|
||||
nested-attributes: c33-pse-pw-limit
|
||||
-
|
||||
name: pse-pw-d-id
|
||||
type: u32
|
||||
name-prefix: ethtool-a-
|
||||
-
|
||||
name: pse-prio-max
|
||||
type: u32
|
||||
name-prefix: ethtool-a-
|
||||
-
|
||||
name: pse-prio
|
||||
type: u32
|
||||
name-prefix: ethtool-a-
|
||||
-
|
||||
name: rss
|
||||
attr-cnt-name: __ethtool-a-rss-cnt
|
||||
@@ -1555,6 +1604,19 @@ attribute-sets:
|
||||
name: hwtstamp-flags
|
||||
type: nest
|
||||
nested-attributes: bitset
|
||||
-
|
||||
name: pse-ntf
|
||||
attr-cnt-name: --ethtool-a-pse-ntf-cnt
|
||||
attributes:
|
||||
-
|
||||
name: header
|
||||
type: nest
|
||||
nested-attributes: header
|
||||
-
|
||||
name: events
|
||||
type: uint
|
||||
enum: pse-event
|
||||
doc: List of events reported by the PSE controller
|
||||
|
||||
operations:
|
||||
enum-model: directional
|
||||
@@ -2205,6 +2267,9 @@ operations:
|
||||
- c33-pse-ext-substate
|
||||
- c33-pse-avail-pw-limit
|
||||
- c33-pse-pw-limit-ranges
|
||||
- pse-pw-d-id
|
||||
- pse-prio-max
|
||||
- pse-prio
|
||||
dump: *pse-get-op
|
||||
-
|
||||
name: pse-set
|
||||
@@ -2219,6 +2284,7 @@ operations:
|
||||
- podl-pse-admin-control
|
||||
- c33-pse-admin-control
|
||||
- c33-pse-avail-pw-limit
|
||||
- pse-prio
|
||||
-
|
||||
name: rss-get
|
||||
doc: Get RSS params.
|
||||
@@ -2413,3 +2479,13 @@ operations:
|
||||
attributes: *tsconfig
|
||||
reply:
|
||||
attributes: *tsconfig
|
||||
-
|
||||
name: pse-ntf
|
||||
doc: Notification for PSE events.
|
||||
|
||||
attribute-set: pse-ntf
|
||||
|
||||
event:
|
||||
attributes:
|
||||
- header
|
||||
- events
|
||||
|
||||
@@ -290,6 +290,7 @@ Kernel to userspace:
|
||||
``ETHTOOL_MSG_PHY_NTF`` Ethernet PHY information change
|
||||
``ETHTOOL_MSG_TSCONFIG_GET_REPLY`` hw timestamping configuration
|
||||
``ETHTOOL_MSG_TSCONFIG_SET_REPLY`` new hw timestamping configuration
|
||||
``ETHTOOL_MSG_PSE_NTF`` PSE events notification
|
||||
======================================== =================================
|
||||
|
||||
``GET`` requests are sent by userspace applications to retrieve device
|
||||
@@ -1788,6 +1789,11 @@ Kernel response contents:
|
||||
limit of the PoE PSE.
|
||||
``ETHTOOL_A_C33_PSE_PW_LIMIT_RANGES`` nested Supported power limit
|
||||
configuration ranges.
|
||||
``ETHTOOL_A_PSE_PW_D_ID`` u32 Index of the PSE power domain
|
||||
``ETHTOOL_A_PSE_PRIO_MAX`` u32 Priority maximum configurable
|
||||
on the PoE PSE
|
||||
``ETHTOOL_A_PSE_PRIO`` u32 Priority of the PoE PSE
|
||||
currently configured
|
||||
========================================== ====== =============================
|
||||
|
||||
When set, the optional ``ETHTOOL_A_PODL_PSE_ADMIN_STATE`` attribute identifies
|
||||
@@ -1861,6 +1867,15 @@ identifies the C33 PSE power limit ranges through
|
||||
If the controller works with fixed classes, the min and max values will be
|
||||
equal.
|
||||
|
||||
The ``ETHTOOL_A_PSE_PW_D_ID`` attribute identifies the index of PSE power
|
||||
domain.
|
||||
|
||||
When set, the optional ``ETHTOOL_A_PSE_PRIO_MAX`` attribute identifies
|
||||
the PSE maximum priority value.
|
||||
When set, the optional ``ETHTOOL_A_PSE_PRIO`` attributes is used to
|
||||
identifies the currently configured PSE priority.
|
||||
For a description of PSE priority attributes, see ``PSE_SET``.
|
||||
|
||||
PSE_SET
|
||||
=======
|
||||
|
||||
@@ -1874,6 +1889,8 @@ Request contents:
|
||||
``ETHTOOL_A_C33_PSE_ADMIN_CONTROL`` u32 Control PSE Admin state
|
||||
``ETHTOOL_A_C33_PSE_AVAIL_PWR_LIMIT`` u32 Control PoE PSE available
|
||||
power limit
|
||||
``ETHTOOL_A_PSE_PRIO`` u32 Control priority of the
|
||||
PoE PSE
|
||||
====================================== ====== =============================
|
||||
|
||||
When set, the optional ``ETHTOOL_A_PODL_PSE_ADMIN_CONTROL`` attribute is used
|
||||
@@ -1896,6 +1913,38 @@ various existing products that document power consumption in watts rather than
|
||||
classes. If power limit configuration based on classes is needed, the
|
||||
conversion can be done in user space, for example by ethtool.
|
||||
|
||||
When set, the optional ``ETHTOOL_A_PSE_PRIO`` attributes is used to
|
||||
control the PSE priority. Allowed priority value are between zero and
|
||||
the value of ``ETHTOOL_A_PSE_PRIO_MAX`` attribute.
|
||||
|
||||
A lower value indicates a higher priority, meaning that a priority value
|
||||
of 0 corresponds to the highest port priority.
|
||||
Port priority serves two functions:
|
||||
|
||||
- Power-up Order: After a reset, ports are powered up in order of their
|
||||
priority from highest to lowest. Ports with higher priority
|
||||
(lower values) power up first.
|
||||
- Shutdown Order: When the power budget is exceeded, ports with lower
|
||||
priority (higher values) are turned off first.
|
||||
|
||||
PSE_NTF
|
||||
=======
|
||||
|
||||
Notify PSE events.
|
||||
|
||||
Notification contents:
|
||||
|
||||
=============================== ====== ========================
|
||||
``ETHTOOL_A_PSE_HEADER`` nested request header
|
||||
``ETHTOOL_A_PSE_EVENTS`` bitset PSE events
|
||||
=============================== ====== ========================
|
||||
|
||||
When set, the optional ``ETHTOOL_A_PSE_EVENTS`` attribute identifies the
|
||||
PSE events.
|
||||
|
||||
.. kernel-doc:: include/uapi/linux/ethtool_netlink_generated.h
|
||||
:identifiers: ethtool_pse_event
|
||||
|
||||
RSS_GET
|
||||
=======
|
||||
|
||||
|
||||
@@ -18,7 +18,8 @@ MODULE_LICENSE("GPL");
|
||||
MODULE_DESCRIPTION("FWNODE MDIO bus (Ethernet PHY) accessors");
|
||||
|
||||
static struct pse_control *
|
||||
fwnode_find_pse_control(struct fwnode_handle *fwnode)
|
||||
fwnode_find_pse_control(struct fwnode_handle *fwnode,
|
||||
struct phy_device *phydev)
|
||||
{
|
||||
struct pse_control *psec;
|
||||
struct device_node *np;
|
||||
@@ -30,7 +31,7 @@ fwnode_find_pse_control(struct fwnode_handle *fwnode)
|
||||
if (!np)
|
||||
return NULL;
|
||||
|
||||
psec = of_pse_control_get(np);
|
||||
psec = of_pse_control_get(np, phydev);
|
||||
if (PTR_ERR(psec) == -ENOENT)
|
||||
return NULL;
|
||||
|
||||
@@ -128,15 +129,9 @@ int fwnode_mdiobus_register_phy(struct mii_bus *bus,
|
||||
u32 phy_id;
|
||||
int rc;
|
||||
|
||||
psec = fwnode_find_pse_control(child);
|
||||
if (IS_ERR(psec))
|
||||
return PTR_ERR(psec);
|
||||
|
||||
mii_ts = fwnode_find_mii_timestamper(child);
|
||||
if (IS_ERR(mii_ts)) {
|
||||
rc = PTR_ERR(mii_ts);
|
||||
goto clean_pse;
|
||||
}
|
||||
if (IS_ERR(mii_ts))
|
||||
return PTR_ERR(mii_ts);
|
||||
|
||||
is_c45 = fwnode_device_is_compatible(child, "ethernet-phy-ieee802.3-c45");
|
||||
if (is_c45 || fwnode_get_phy_id(child, &phy_id))
|
||||
@@ -169,6 +164,12 @@ int fwnode_mdiobus_register_phy(struct mii_bus *bus,
|
||||
goto clean_phy;
|
||||
}
|
||||
|
||||
psec = fwnode_find_pse_control(child, phy);
|
||||
if (IS_ERR(psec)) {
|
||||
rc = PTR_ERR(psec);
|
||||
goto unregister_phy;
|
||||
}
|
||||
|
||||
phy->psec = psec;
|
||||
|
||||
/* phy->mii_ts may already be defined by the PHY driver. A
|
||||
@@ -180,12 +181,13 @@ int fwnode_mdiobus_register_phy(struct mii_bus *bus,
|
||||
|
||||
return 0;
|
||||
|
||||
unregister_phy:
|
||||
if (is_acpi_node(child) || is_of_node(child))
|
||||
phy_device_remove(phy);
|
||||
clean_phy:
|
||||
phy_device_free(phy);
|
||||
clean_mii_ts:
|
||||
unregister_mii_timestamper(mii_ts);
|
||||
clean_pse:
|
||||
pse_control_put(psec);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
#include <linux/of.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/pse-pd/pse.h>
|
||||
#include <linux/regulator/driver.h>
|
||||
#include <linux/regulator/machine.h>
|
||||
|
||||
#define PD692X0_PSE_NAME "pd692x0_pse"
|
||||
|
||||
@@ -76,6 +78,8 @@ enum {
|
||||
PD692X0_MSG_GET_PORT_CLASS,
|
||||
PD692X0_MSG_GET_PORT_MEAS,
|
||||
PD692X0_MSG_GET_PORT_PARAM,
|
||||
PD692X0_MSG_GET_POWER_BANK,
|
||||
PD692X0_MSG_SET_POWER_BANK,
|
||||
|
||||
/* add new message above here */
|
||||
PD692X0_MSG_CNT
|
||||
@@ -95,6 +99,8 @@ struct pd692x0_priv {
|
||||
unsigned long last_cmd_key_time;
|
||||
|
||||
enum ethtool_c33_pse_admin_state admin_state[PD692X0_MAX_PIS];
|
||||
struct regulator_dev *manager_reg[PD692X0_MAX_MANAGERS];
|
||||
int manager_pw_budget[PD692X0_MAX_MANAGERS];
|
||||
};
|
||||
|
||||
/* Template list of communication messages. The non-null bytes defined here
|
||||
@@ -170,6 +176,16 @@ static const struct pd692x0_msg pd692x0_msg_template_list[PD692X0_MSG_CNT] = {
|
||||
.data = {0x4e, 0x4e, 0x4e, 0x4e,
|
||||
0x4e, 0x4e, 0x4e, 0x4e},
|
||||
},
|
||||
[PD692X0_MSG_GET_POWER_BANK] = {
|
||||
.key = PD692X0_KEY_REQ,
|
||||
.sub = {0x07, 0x0b, 0x57},
|
||||
.data = { 0, 0x4e, 0x4e, 0x4e,
|
||||
0x4e, 0x4e, 0x4e, 0x4e},
|
||||
},
|
||||
[PD692X0_MSG_SET_POWER_BANK] = {
|
||||
.key = PD692X0_KEY_CMD,
|
||||
.sub = {0x07, 0x0b, 0x57},
|
||||
},
|
||||
};
|
||||
|
||||
static u8 pd692x0_build_msg(struct pd692x0_msg *msg, u8 echo)
|
||||
@@ -739,6 +755,29 @@ pd692x0_pi_get_actual_pw(struct pse_controller_dev *pcdev, int id)
|
||||
return (buf.data[0] << 4 | buf.data[1]) * 100;
|
||||
}
|
||||
|
||||
static int
|
||||
pd692x0_pi_get_prio(struct pse_controller_dev *pcdev, int id)
|
||||
{
|
||||
struct pd692x0_priv *priv = to_pd692x0_priv(pcdev);
|
||||
struct pd692x0_msg msg, buf = {0};
|
||||
int ret;
|
||||
|
||||
ret = pd692x0_fw_unavailable(priv);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
msg = pd692x0_msg_template_list[PD692X0_MSG_GET_PORT_PARAM];
|
||||
msg.sub[2] = id;
|
||||
ret = pd692x0_sendrecv_msg(priv, &msg, &buf);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
if (!buf.data[2] || buf.data[2] > pcdev->pis_prio_max + 1)
|
||||
return -ERANGE;
|
||||
|
||||
/* PSE core priority start at 0 */
|
||||
return buf.data[2] - 1;
|
||||
}
|
||||
|
||||
static struct pd692x0_msg_ver pd692x0_get_sw_version(struct pd692x0_priv *priv)
|
||||
{
|
||||
struct device *dev = &priv->client->dev;
|
||||
@@ -766,6 +805,7 @@ static struct pd692x0_msg_ver pd692x0_get_sw_version(struct pd692x0_priv *priv)
|
||||
|
||||
struct pd692x0_manager {
|
||||
struct device_node *port_node[PD692X0_MAX_MANAGER_PORTS];
|
||||
struct device_node *node;
|
||||
int nports;
|
||||
};
|
||||
|
||||
@@ -857,6 +897,8 @@ pd692x0_of_get_managers(struct pd692x0_priv *priv,
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
of_node_get(node);
|
||||
manager[manager_id].node = node;
|
||||
nmanagers++;
|
||||
}
|
||||
|
||||
@@ -869,6 +911,8 @@ pd692x0_of_get_managers(struct pd692x0_priv *priv,
|
||||
of_node_put(manager[i].port_node[j]);
|
||||
manager[i].port_node[j] = NULL;
|
||||
}
|
||||
of_node_put(manager[i].node);
|
||||
manager[i].node = NULL;
|
||||
}
|
||||
|
||||
of_node_put(node);
|
||||
@@ -876,6 +920,143 @@ pd692x0_of_get_managers(struct pd692x0_priv *priv,
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct regulator_ops dummy_ops;
|
||||
|
||||
static struct regulator_dev *
|
||||
pd692x0_register_manager_regulator(struct device *dev, char *reg_name,
|
||||
struct device_node *node)
|
||||
{
|
||||
struct regulator_init_data *rinit_data;
|
||||
struct regulator_config rconfig = {0};
|
||||
struct regulator_desc *rdesc;
|
||||
struct regulator_dev *rdev;
|
||||
|
||||
rinit_data = devm_kzalloc(dev, sizeof(*rinit_data),
|
||||
GFP_KERNEL);
|
||||
if (!rinit_data)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
rdesc = devm_kzalloc(dev, sizeof(*rdesc), GFP_KERNEL);
|
||||
if (!rdesc)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
rdesc->name = reg_name;
|
||||
rdesc->type = REGULATOR_VOLTAGE;
|
||||
rdesc->ops = &dummy_ops;
|
||||
rdesc->owner = THIS_MODULE;
|
||||
|
||||
rinit_data->supply_regulator = "vmain";
|
||||
|
||||
rconfig.dev = dev;
|
||||
rconfig.init_data = rinit_data;
|
||||
rconfig.of_node = node;
|
||||
|
||||
rdev = devm_regulator_register(dev, rdesc, &rconfig);
|
||||
if (IS_ERR(rdev)) {
|
||||
dev_err_probe(dev, PTR_ERR(rdev),
|
||||
"Failed to register regulator\n");
|
||||
return rdev;
|
||||
}
|
||||
|
||||
return rdev;
|
||||
}
|
||||
|
||||
static int
|
||||
pd692x0_register_managers_regulator(struct pd692x0_priv *priv,
|
||||
const struct pd692x0_manager *manager,
|
||||
int nmanagers)
|
||||
{
|
||||
struct device *dev = &priv->client->dev;
|
||||
size_t reg_name_len;
|
||||
int i;
|
||||
|
||||
/* Each regulator name len is dev name + 12 char +
|
||||
* int max digit number (10) + 1
|
||||
*/
|
||||
reg_name_len = strlen(dev_name(dev)) + 23;
|
||||
|
||||
for (i = 0; i < nmanagers; i++) {
|
||||
static const char * const regulators[] = { "vaux5", "vaux3p3" };
|
||||
struct regulator_dev *rdev;
|
||||
char *reg_name;
|
||||
int ret;
|
||||
|
||||
reg_name = devm_kzalloc(dev, reg_name_len, GFP_KERNEL);
|
||||
if (!reg_name)
|
||||
return -ENOMEM;
|
||||
snprintf(reg_name, 26, "pse-%s-manager%d", dev_name(dev), i);
|
||||
rdev = pd692x0_register_manager_regulator(dev, reg_name,
|
||||
manager[i].node);
|
||||
if (IS_ERR(rdev))
|
||||
return PTR_ERR(rdev);
|
||||
|
||||
/* VMAIN is described as main supply for the manager.
|
||||
* Add other VAUX power supplies and link them to the
|
||||
* virtual device rdev->dev.
|
||||
*/
|
||||
ret = devm_regulator_bulk_get_enable(&rdev->dev,
|
||||
ARRAY_SIZE(regulators),
|
||||
regulators);
|
||||
if (ret)
|
||||
return dev_err_probe(&rdev->dev, ret,
|
||||
"Failed to enable regulators\n");
|
||||
|
||||
priv->manager_reg[i] = rdev;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
pd692x0_conf_manager_power_budget(struct pd692x0_priv *priv, int id, int pw)
|
||||
{
|
||||
struct pd692x0_msg msg, buf;
|
||||
int ret, pw_mW = pw / 1000;
|
||||
|
||||
msg = pd692x0_msg_template_list[PD692X0_MSG_GET_POWER_BANK];
|
||||
msg.data[0] = id;
|
||||
ret = pd692x0_sendrecv_msg(priv, &msg, &buf);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
msg = pd692x0_msg_template_list[PD692X0_MSG_SET_POWER_BANK];
|
||||
msg.data[0] = id;
|
||||
msg.data[1] = pw_mW >> 8;
|
||||
msg.data[2] = pw_mW & 0xff;
|
||||
msg.data[3] = buf.sub[2];
|
||||
msg.data[4] = buf.data[0];
|
||||
msg.data[5] = buf.data[1];
|
||||
msg.data[6] = buf.data[2];
|
||||
msg.data[7] = buf.data[3];
|
||||
return pd692x0_sendrecv_msg(priv, &msg, &buf);
|
||||
}
|
||||
|
||||
static int
|
||||
pd692x0_configure_managers(struct pd692x0_priv *priv, int nmanagers)
|
||||
{
|
||||
int i, ret;
|
||||
|
||||
for (i = 0; i < nmanagers; i++) {
|
||||
struct regulator *supply = priv->manager_reg[i]->supply;
|
||||
int pw_budget;
|
||||
|
||||
pw_budget = regulator_get_unclaimed_power_budget(supply);
|
||||
/* Max power budget per manager */
|
||||
if (pw_budget > 6000000)
|
||||
pw_budget = 6000000;
|
||||
ret = regulator_request_power_budget(supply, pw_budget);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
priv->manager_pw_budget[i] = pw_budget;
|
||||
ret = pd692x0_conf_manager_power_budget(priv, i, pw_budget);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
pd692x0_set_port_matrix(const struct pse_pi_pairset *pairset,
|
||||
const struct pd692x0_manager *manager,
|
||||
@@ -998,6 +1179,14 @@ static int pd692x0_setup_pi_matrix(struct pse_controller_dev *pcdev)
|
||||
return ret;
|
||||
|
||||
nmanagers = ret;
|
||||
ret = pd692x0_register_managers_regulator(priv, manager, nmanagers);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
ret = pd692x0_configure_managers(priv, nmanagers);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
ret = pd692x0_set_ports_matrix(priv, manager, nmanagers, port_matrix);
|
||||
if (ret)
|
||||
goto out;
|
||||
@@ -1008,8 +1197,14 @@ static int pd692x0_setup_pi_matrix(struct pse_controller_dev *pcdev)
|
||||
|
||||
out:
|
||||
for (i = 0; i < nmanagers; i++) {
|
||||
struct regulator *supply = priv->manager_reg[i]->supply;
|
||||
|
||||
regulator_free_power_budget(supply,
|
||||
priv->manager_pw_budget[i]);
|
||||
|
||||
for (j = 0; j < manager[i].nports; j++)
|
||||
of_node_put(manager[i].port_node[j]);
|
||||
of_node_put(manager[i].node);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
@@ -1071,6 +1266,25 @@ static int pd692x0_pi_set_pw_limit(struct pse_controller_dev *pcdev,
|
||||
return pd692x0_sendrecv_msg(priv, &msg, &buf);
|
||||
}
|
||||
|
||||
static int pd692x0_pi_set_prio(struct pse_controller_dev *pcdev, int id,
|
||||
unsigned int prio)
|
||||
{
|
||||
struct pd692x0_priv *priv = to_pd692x0_priv(pcdev);
|
||||
struct pd692x0_msg msg, buf = {0};
|
||||
int ret;
|
||||
|
||||
ret = pd692x0_fw_unavailable(priv);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
msg = pd692x0_msg_template_list[PD692X0_MSG_SET_PORT_PARAM];
|
||||
msg.sub[2] = id;
|
||||
/* Controller priority from 1 to 3 */
|
||||
msg.data[4] = prio + 1;
|
||||
|
||||
return pd692x0_sendrecv_msg(priv, &msg, &buf);
|
||||
}
|
||||
|
||||
static const struct pse_controller_ops pd692x0_ops = {
|
||||
.setup_pi_matrix = pd692x0_setup_pi_matrix,
|
||||
.pi_get_admin_state = pd692x0_pi_get_admin_state,
|
||||
@@ -1084,6 +1298,8 @@ static const struct pse_controller_ops pd692x0_ops = {
|
||||
.pi_get_pw_limit = pd692x0_pi_get_pw_limit,
|
||||
.pi_set_pw_limit = pd692x0_pi_set_pw_limit,
|
||||
.pi_get_pw_limit_ranges = pd692x0_pi_get_pw_limit_ranges,
|
||||
.pi_get_prio = pd692x0_pi_get_prio,
|
||||
.pi_set_prio = pd692x0_pi_set_prio,
|
||||
};
|
||||
|
||||
#define PD692X0_FW_LINE_MAX_SZ 0xff
|
||||
@@ -1437,6 +1653,7 @@ static const struct fw_upload_ops pd692x0_fw_ops = {
|
||||
|
||||
static int pd692x0_i2c_probe(struct i2c_client *client)
|
||||
{
|
||||
static const char * const regulators[] = { "vdd", "vdda" };
|
||||
struct pd692x0_msg msg, buf = {0}, zero = {0};
|
||||
struct device *dev = &client->dev;
|
||||
struct pd692x0_msg_ver ver;
|
||||
@@ -1444,6 +1661,12 @@ static int pd692x0_i2c_probe(struct i2c_client *client)
|
||||
struct fw_upload *fwl;
|
||||
int ret;
|
||||
|
||||
ret = devm_regulator_bulk_get_enable(dev, ARRAY_SIZE(regulators),
|
||||
regulators);
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret,
|
||||
"Failed to enable regulators\n");
|
||||
|
||||
if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
|
||||
dev_err(dev, "i2c check functionality failed\n");
|
||||
return -ENXIO;
|
||||
@@ -1500,6 +1723,8 @@ static int pd692x0_i2c_probe(struct i2c_client *client)
|
||||
priv->pcdev.ops = &pd692x0_ops;
|
||||
priv->pcdev.dev = dev;
|
||||
priv->pcdev.types = ETHTOOL_PSE_C33;
|
||||
priv->pcdev.supp_budget_eval_strategies = PSE_BUDGET_EVAL_STRAT_DYNAMIC;
|
||||
priv->pcdev.pis_prio_max = 2;
|
||||
ret = devm_pse_controller_register(dev, &priv->pcdev);
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -16,15 +16,34 @@
|
||||
#include <linux/pse-pd/pse.h>
|
||||
|
||||
#define TPS23881_MAX_CHANS 8
|
||||
#define TPS23881_MAX_IRQ_RETRIES 10
|
||||
|
||||
#define TPS23881_REG_IT 0x0
|
||||
#define TPS23881_REG_IT_MASK 0x1
|
||||
#define TPS23881_REG_IT_DISF BIT(2)
|
||||
#define TPS23881_REG_IT_DETC BIT(3)
|
||||
#define TPS23881_REG_IT_CLASC BIT(4)
|
||||
#define TPS23881_REG_IT_IFAULT BIT(5)
|
||||
#define TPS23881_REG_IT_SUPF BIT(7)
|
||||
#define TPS23881_REG_DET_EVENT 0x5
|
||||
#define TPS23881_REG_FAULT 0x7
|
||||
#define TPS23881_REG_SUPF_EVENT 0xb
|
||||
#define TPS23881_REG_TSD BIT(7)
|
||||
#define TPS23881_REG_DISC 0xc
|
||||
#define TPS23881_REG_PW_STATUS 0x10
|
||||
#define TPS23881_REG_OP_MODE 0x12
|
||||
#define TPS23881_REG_DISC_EN 0x13
|
||||
#define TPS23881_OP_MODE_SEMIAUTO 0xaaaa
|
||||
#define TPS23881_REG_DIS_EN 0x13
|
||||
#define TPS23881_REG_DET_CLA_EN 0x14
|
||||
#define TPS23881_REG_GEN_MASK 0x17
|
||||
#define TPS23881_REG_CLCHE BIT(2)
|
||||
#define TPS23881_REG_DECHE BIT(3)
|
||||
#define TPS23881_REG_NBITACC BIT(5)
|
||||
#define TPS23881_REG_INTEN BIT(7)
|
||||
#define TPS23881_REG_PW_EN 0x19
|
||||
#define TPS23881_REG_RESET 0x1a
|
||||
#define TPS23881_REG_CLRAIN BIT(7)
|
||||
#define TPS23881_REG_2PAIR_POL1 0x1e
|
||||
#define TPS23881_REG_PORT_MAP 0x26
|
||||
#define TPS23881_REG_PORT_POWER 0x29
|
||||
@@ -51,6 +70,7 @@ struct tps23881_port_desc {
|
||||
u8 chan[2];
|
||||
bool is_4p;
|
||||
int pw_pol;
|
||||
bool exist;
|
||||
};
|
||||
|
||||
struct tps23881_priv {
|
||||
@@ -168,6 +188,7 @@ static int tps23881_pi_enable(struct pse_controller_dev *pcdev, int id)
|
||||
struct i2c_client *client = priv->client;
|
||||
u8 chan;
|
||||
u16 val;
|
||||
int ret;
|
||||
|
||||
if (id >= TPS23881_MAX_CHANS)
|
||||
return -ERANGE;
|
||||
@@ -181,7 +202,22 @@ static int tps23881_pi_enable(struct pse_controller_dev *pcdev, int id)
|
||||
BIT(chan % 4));
|
||||
}
|
||||
|
||||
return i2c_smbus_write_word_data(client, TPS23881_REG_PW_EN, val);
|
||||
ret = i2c_smbus_write_word_data(client, TPS23881_REG_PW_EN, val);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Enable DC disconnect*/
|
||||
chan = priv->port[id].chan[0];
|
||||
ret = i2c_smbus_read_word_data(client, TPS23881_REG_DISC_EN);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
val = tps23881_set_val(ret, chan, 0, BIT(chan % 4), BIT(chan % 4));
|
||||
ret = i2c_smbus_write_word_data(client, TPS23881_REG_DISC_EN, val);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tps23881_pi_disable(struct pse_controller_dev *pcdev, int id)
|
||||
@@ -214,6 +250,17 @@ static int tps23881_pi_disable(struct pse_controller_dev *pcdev, int id)
|
||||
*/
|
||||
mdelay(5);
|
||||
|
||||
/* Disable DC disconnect*/
|
||||
chan = priv->port[id].chan[0];
|
||||
ret = i2c_smbus_read_word_data(client, TPS23881_REG_DISC_EN);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
val = tps23881_set_val(ret, chan, 0, 0, BIT(chan % 4));
|
||||
ret = i2c_smbus_write_word_data(client, TPS23881_REG_DISC_EN, val);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Enable detection and classification */
|
||||
ret = i2c_smbus_read_word_data(client, TPS23881_REG_DET_CLA_EN);
|
||||
if (ret < 0)
|
||||
@@ -782,8 +829,10 @@ tps23881_write_port_matrix(struct tps23881_priv *priv,
|
||||
hw_chan = port_matrix[i].hw_chan[0] % 4;
|
||||
|
||||
/* Set software port matrix for existing ports */
|
||||
if (port_matrix[i].exist)
|
||||
if (port_matrix[i].exist) {
|
||||
priv->port[pi_id].chan[0] = lgcl_chan;
|
||||
priv->port[pi_id].exist = true;
|
||||
}
|
||||
|
||||
/* Initialize power policy internal value */
|
||||
priv->port[pi_id].pw_pol = -1;
|
||||
@@ -907,6 +956,47 @@ static int tps23881_setup_pi_matrix(struct pse_controller_dev *pcdev)
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int tps23881_power_class_table[] = {
|
||||
-ERANGE,
|
||||
4000,
|
||||
7000,
|
||||
15500,
|
||||
30000,
|
||||
15500,
|
||||
15500,
|
||||
-ERANGE,
|
||||
45000,
|
||||
60000,
|
||||
75000,
|
||||
90000,
|
||||
15500,
|
||||
45000,
|
||||
-ERANGE,
|
||||
-ERANGE,
|
||||
};
|
||||
|
||||
static int tps23881_pi_get_pw_req(struct pse_controller_dev *pcdev, int id)
|
||||
{
|
||||
struct tps23881_priv *priv = to_tps23881_priv(pcdev);
|
||||
struct i2c_client *client = priv->client;
|
||||
u8 reg, chan;
|
||||
int ret;
|
||||
u16 val;
|
||||
|
||||
/* For a 4-pair the classification need 5ms to be completed */
|
||||
if (priv->port[id].is_4p)
|
||||
mdelay(5);
|
||||
|
||||
chan = priv->port[id].chan[0];
|
||||
reg = TPS23881_REG_DISC + (chan % 4);
|
||||
ret = i2c_smbus_read_word_data(client, reg);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
val = tps23881_calc_val(ret, chan, 4, 0xf);
|
||||
return tps23881_power_class_table[val];
|
||||
}
|
||||
|
||||
static const struct pse_controller_ops tps23881_ops = {
|
||||
.setup_pi_matrix = tps23881_setup_pi_matrix,
|
||||
.pi_enable = tps23881_pi_enable,
|
||||
@@ -919,6 +1009,7 @@ static const struct pse_controller_ops tps23881_ops = {
|
||||
.pi_get_pw_limit = tps23881_pi_get_pw_limit,
|
||||
.pi_set_pw_limit = tps23881_pi_set_pw_limit,
|
||||
.pi_get_pw_limit_ranges = tps23881_pi_get_pw_limit_ranges,
|
||||
.pi_get_pw_req = tps23881_pi_get_pw_req,
|
||||
};
|
||||
|
||||
static const char fw_parity_name[] = "ti/tps23881/tps23881-parity-14.bin";
|
||||
@@ -1017,6 +1108,307 @@ static int tps23881_flash_sram_fw(struct i2c_client *client)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Convert interrupt events to 0xff to be aligned with the chan
|
||||
* number.
|
||||
*/
|
||||
static u8 tps23881_irq_export_chans_helper(u16 reg_val, u8 field_offset)
|
||||
{
|
||||
u8 val;
|
||||
|
||||
val = (reg_val >> (4 + field_offset) & 0xf0) |
|
||||
(reg_val >> field_offset & 0x0f);
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
/* Convert chan number to port number */
|
||||
static void tps23881_set_notifs_helper(struct tps23881_priv *priv,
|
||||
u8 chans,
|
||||
unsigned long *notifs,
|
||||
unsigned long *notifs_mask,
|
||||
enum ethtool_pse_event event)
|
||||
{
|
||||
u8 chan;
|
||||
int i;
|
||||
|
||||
if (!chans)
|
||||
return;
|
||||
|
||||
for (i = 0; i < TPS23881_MAX_CHANS; i++) {
|
||||
if (!priv->port[i].exist)
|
||||
continue;
|
||||
/* No need to look at the 2nd channel in case of PoE4 as
|
||||
* both registers are set.
|
||||
*/
|
||||
chan = priv->port[i].chan[0];
|
||||
|
||||
if (BIT(chan) & chans) {
|
||||
*notifs_mask |= BIT(i);
|
||||
notifs[i] |= event;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void tps23881_irq_event_over_temp(struct tps23881_priv *priv,
|
||||
u16 reg_val,
|
||||
unsigned long *notifs,
|
||||
unsigned long *notifs_mask)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (reg_val & TPS23881_REG_TSD) {
|
||||
for (i = 0; i < TPS23881_MAX_CHANS; i++) {
|
||||
if (!priv->port[i].exist)
|
||||
continue;
|
||||
|
||||
*notifs_mask |= BIT(i);
|
||||
notifs[i] |= ETHTOOL_PSE_EVENT_OVER_TEMP;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int tps23881_irq_event_over_current(struct tps23881_priv *priv,
|
||||
u16 reg_val,
|
||||
unsigned long *notifs,
|
||||
unsigned long *notifs_mask)
|
||||
{
|
||||
int i, ret;
|
||||
u8 chans;
|
||||
|
||||
chans = tps23881_irq_export_chans_helper(reg_val, 0);
|
||||
if (!chans)
|
||||
return 0;
|
||||
|
||||
tps23881_set_notifs_helper(priv, chans, notifs, notifs_mask,
|
||||
ETHTOOL_PSE_EVENT_OVER_CURRENT |
|
||||
ETHTOOL_C33_PSE_EVENT_DISCONNECTION);
|
||||
|
||||
/* Over Current event resets the power limit registers so we need
|
||||
* to configured it again.
|
||||
*/
|
||||
for_each_set_bit(i, notifs_mask, priv->pcdev.nr_lines) {
|
||||
if (priv->port[i].pw_pol < 0)
|
||||
continue;
|
||||
|
||||
ret = tps23881_pi_enable_manual_pol(priv, i);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/* Set power policy */
|
||||
ret = tps23881_pi_set_pw_pol_limit(priv, i,
|
||||
priv->port[i].pw_pol,
|
||||
priv->port[i].is_4p);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void tps23881_irq_event_disconnection(struct tps23881_priv *priv,
|
||||
u16 reg_val,
|
||||
unsigned long *notifs,
|
||||
unsigned long *notifs_mask)
|
||||
{
|
||||
u8 chans;
|
||||
|
||||
chans = tps23881_irq_export_chans_helper(reg_val, 4);
|
||||
if (chans)
|
||||
tps23881_set_notifs_helper(priv, chans, notifs, notifs_mask,
|
||||
ETHTOOL_C33_PSE_EVENT_DISCONNECTION);
|
||||
}
|
||||
|
||||
static int tps23881_irq_event_detection(struct tps23881_priv *priv,
|
||||
u16 reg_val,
|
||||
unsigned long *notifs,
|
||||
unsigned long *notifs_mask)
|
||||
{
|
||||
enum ethtool_pse_event event;
|
||||
int reg, ret, i, val;
|
||||
unsigned long chans;
|
||||
|
||||
chans = tps23881_irq_export_chans_helper(reg_val, 0);
|
||||
for_each_set_bit(i, &chans, TPS23881_MAX_CHANS) {
|
||||
reg = TPS23881_REG_DISC + (i % 4);
|
||||
ret = i2c_smbus_read_word_data(priv->client, reg);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
val = tps23881_calc_val(ret, i, 0, 0xf);
|
||||
/* If detection valid */
|
||||
if (val == 0x4)
|
||||
event = ETHTOOL_C33_PSE_EVENT_DETECTION;
|
||||
else
|
||||
event = ETHTOOL_C33_PSE_EVENT_DISCONNECTION;
|
||||
|
||||
tps23881_set_notifs_helper(priv, BIT(i), notifs,
|
||||
notifs_mask, event);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tps23881_irq_event_classification(struct tps23881_priv *priv,
|
||||
u16 reg_val,
|
||||
unsigned long *notifs,
|
||||
unsigned long *notifs_mask)
|
||||
{
|
||||
int reg, ret, val, i;
|
||||
unsigned long chans;
|
||||
|
||||
chans = tps23881_irq_export_chans_helper(reg_val, 4);
|
||||
for_each_set_bit(i, &chans, TPS23881_MAX_CHANS) {
|
||||
reg = TPS23881_REG_DISC + (i % 4);
|
||||
ret = i2c_smbus_read_word_data(priv->client, reg);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
val = tps23881_calc_val(ret, i, 4, 0xf);
|
||||
/* Do not report classification event for unknown class */
|
||||
if (!val || val == 0x8 || val == 0xf)
|
||||
continue;
|
||||
|
||||
tps23881_set_notifs_helper(priv, BIT(i), notifs,
|
||||
notifs_mask,
|
||||
ETHTOOL_C33_PSE_EVENT_CLASSIFICATION);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tps23881_irq_event_handler(struct tps23881_priv *priv, u16 reg,
|
||||
unsigned long *notifs,
|
||||
unsigned long *notifs_mask)
|
||||
{
|
||||
struct i2c_client *client = priv->client;
|
||||
int ret, val;
|
||||
|
||||
/* The Supply event bit is repeated twice so we only need to read
|
||||
* the one from the first byte.
|
||||
*/
|
||||
if (reg & TPS23881_REG_IT_SUPF) {
|
||||
ret = i2c_smbus_read_word_data(client, TPS23881_REG_SUPF_EVENT);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
tps23881_irq_event_over_temp(priv, ret, notifs, notifs_mask);
|
||||
}
|
||||
|
||||
if (reg & (TPS23881_REG_IT_IFAULT | TPS23881_REG_IT_IFAULT << 8 |
|
||||
TPS23881_REG_IT_DISF | TPS23881_REG_IT_DISF << 8)) {
|
||||
ret = i2c_smbus_read_word_data(client, TPS23881_REG_FAULT);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
ret = tps23881_irq_event_over_current(priv, ret, notifs,
|
||||
notifs_mask);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
tps23881_irq_event_disconnection(priv, ret, notifs, notifs_mask);
|
||||
}
|
||||
|
||||
if (reg & (TPS23881_REG_IT_DETC | TPS23881_REG_IT_DETC << 8 |
|
||||
TPS23881_REG_IT_CLASC | TPS23881_REG_IT_CLASC << 8)) {
|
||||
ret = i2c_smbus_read_word_data(client, TPS23881_REG_DET_EVENT);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
val = ret;
|
||||
ret = tps23881_irq_event_detection(priv, val, notifs,
|
||||
notifs_mask);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = tps23881_irq_event_classification(priv, val, notifs,
|
||||
notifs_mask);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tps23881_irq_handler(int irq, struct pse_controller_dev *pcdev,
|
||||
unsigned long *notifs,
|
||||
unsigned long *notifs_mask)
|
||||
{
|
||||
struct tps23881_priv *priv = to_tps23881_priv(pcdev);
|
||||
struct i2c_client *client = priv->client;
|
||||
int ret, it_mask, retry;
|
||||
|
||||
/* Get interruption mask */
|
||||
ret = i2c_smbus_read_word_data(client, TPS23881_REG_IT_MASK);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
it_mask = ret;
|
||||
|
||||
/* Read interrupt register until it frees the interruption pin. */
|
||||
retry = 0;
|
||||
while (true) {
|
||||
if (retry > TPS23881_MAX_IRQ_RETRIES) {
|
||||
dev_err(&client->dev, "interrupt never freed");
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
|
||||
ret = i2c_smbus_read_word_data(client, TPS23881_REG_IT);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/* No more relevant interruption */
|
||||
if (!(ret & it_mask))
|
||||
return 0;
|
||||
|
||||
ret = tps23881_irq_event_handler(priv, (u16)ret, notifs,
|
||||
notifs_mask);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
retry++;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tps23881_setup_irq(struct tps23881_priv *priv, int irq)
|
||||
{
|
||||
struct i2c_client *client = priv->client;
|
||||
struct pse_irq_desc irq_desc = {
|
||||
.name = "tps23881-irq",
|
||||
.map_event = tps23881_irq_handler,
|
||||
};
|
||||
int ret;
|
||||
u16 val;
|
||||
|
||||
if (!irq) {
|
||||
dev_err(&client->dev, "interrupt is missing");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
val = TPS23881_REG_IT_IFAULT | TPS23881_REG_IT_SUPF |
|
||||
TPS23881_REG_IT_DETC | TPS23881_REG_IT_CLASC |
|
||||
TPS23881_REG_IT_DISF;
|
||||
val |= val << 8;
|
||||
ret = i2c_smbus_write_word_data(client, TPS23881_REG_IT_MASK, val);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = i2c_smbus_read_word_data(client, TPS23881_REG_GEN_MASK);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
val = TPS23881_REG_INTEN | TPS23881_REG_CLCHE | TPS23881_REG_DECHE;
|
||||
val |= val << 8;
|
||||
val |= (u16)ret;
|
||||
ret = i2c_smbus_write_word_data(client, TPS23881_REG_GEN_MASK, val);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/* Reset interrupts registers */
|
||||
ret = i2c_smbus_write_word_data(client, TPS23881_REG_RESET,
|
||||
TPS23881_REG_CLRAIN);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return devm_pse_irq_helper(&priv->pcdev, irq, 0, &irq_desc);
|
||||
}
|
||||
|
||||
static int tps23881_i2c_probe(struct i2c_client *client)
|
||||
{
|
||||
struct device *dev = &client->dev;
|
||||
@@ -1091,12 +1483,17 @@ static int tps23881_i2c_probe(struct i2c_client *client)
|
||||
priv->pcdev.dev = dev;
|
||||
priv->pcdev.types = ETHTOOL_PSE_C33;
|
||||
priv->pcdev.nr_lines = TPS23881_MAX_CHANS;
|
||||
priv->pcdev.supp_budget_eval_strategies = PSE_BUDGET_EVAL_STRAT_STATIC;
|
||||
ret = devm_pse_controller_register(dev, &priv->pcdev);
|
||||
if (ret) {
|
||||
return dev_err_probe(dev, ret,
|
||||
"failed to register PSE controller\n");
|
||||
}
|
||||
|
||||
ret = tps23881_setup_irq(priv, client->irq);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
@@ -43,6 +43,8 @@ void ethtool_aggregate_rmon_stats(struct net_device *dev,
|
||||
struct ethtool_rmon_stats *rmon_stats);
|
||||
bool ethtool_dev_mm_supported(struct net_device *dev);
|
||||
|
||||
void ethnl_pse_send_ntf(struct net_device *netdev, unsigned long notif);
|
||||
|
||||
#else
|
||||
static inline int ethnl_cable_test_alloc(struct phy_device *phydev, u8 cmd)
|
||||
{
|
||||
@@ -120,6 +122,11 @@ static inline bool ethtool_dev_mm_supported(struct net_device *dev)
|
||||
return false;
|
||||
}
|
||||
|
||||
static inline void ethnl_pse_send_ntf(struct phy_device *phydev,
|
||||
unsigned long notif)
|
||||
{
|
||||
}
|
||||
|
||||
#endif /* IS_ENABLED(CONFIG_ETHTOOL_NETLINK) */
|
||||
|
||||
static inline int ethnl_cable_test_result(struct phy_device *phydev, u8 pair,
|
||||
|
||||
@@ -6,13 +6,18 @@
|
||||
#define _LINUX_PSE_CONTROLLER_H
|
||||
|
||||
#include <linux/list.h>
|
||||
#include <linux/netlink.h>
|
||||
#include <linux/kfifo.h>
|
||||
#include <uapi/linux/ethtool.h>
|
||||
#include <uapi/linux/ethtool_netlink_generated.h>
|
||||
#include <linux/regulator/driver.h>
|
||||
|
||||
/* Maximum current in uA according to IEEE 802.3-2022 Table 145-1 */
|
||||
#define MAX_PI_CURRENT 1920000
|
||||
/* Maximum power in mW according to IEEE 802.3-2022 Table 145-16 */
|
||||
#define MAX_PI_PW 99900
|
||||
|
||||
struct net_device;
|
||||
struct phy_device;
|
||||
struct pse_controller_dev;
|
||||
struct netlink_ext_ack;
|
||||
@@ -37,6 +42,19 @@ struct ethtool_c33_pse_pw_limit_range {
|
||||
u32 max;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct pse_irq_desc - notification sender description for IRQ based events.
|
||||
*
|
||||
* @name: the visible name for the IRQ
|
||||
* @map_event: driver callback to map IRQ status into PSE devices with events.
|
||||
*/
|
||||
struct pse_irq_desc {
|
||||
const char *name;
|
||||
int (*map_event)(int irq, struct pse_controller_dev *pcdev,
|
||||
unsigned long *notifs,
|
||||
unsigned long *notifs_mask);
|
||||
};
|
||||
|
||||
/**
|
||||
* struct pse_control_config - PSE control/channel configuration.
|
||||
*
|
||||
@@ -98,6 +116,7 @@ struct pse_pw_limit_ranges {
|
||||
/**
|
||||
* struct ethtool_pse_control_status - PSE control/channel status.
|
||||
*
|
||||
* @pw_d_id: PSE power domain index.
|
||||
* @podl_admin_state: operational state of the PoDL PSE
|
||||
* functions. IEEE 802.3-2018 30.15.1.1.2 aPoDLPSEAdminState
|
||||
* @podl_pw_status: power detection status of the PoDL PSE.
|
||||
@@ -117,8 +136,12 @@ struct pse_pw_limit_ranges {
|
||||
* is in charge of the memory allocation
|
||||
* @c33_pw_limit_nb_ranges: number of supported power limit configuration
|
||||
* ranges
|
||||
* @prio_max: max priority allowed for the c33_prio variable value.
|
||||
* @prio: priority of the PSE. Managed by PSE core in case of static budget
|
||||
* evaluation strategy.
|
||||
*/
|
||||
struct ethtool_pse_control_status {
|
||||
u32 pw_d_id;
|
||||
enum ethtool_podl_pse_admin_state podl_admin_state;
|
||||
enum ethtool_podl_pse_pw_d_status podl_pw_status;
|
||||
enum ethtool_c33_pse_admin_state c33_admin_state;
|
||||
@@ -129,6 +152,8 @@ struct ethtool_pse_control_status {
|
||||
u32 c33_avail_pw_limit;
|
||||
struct ethtool_c33_pse_pw_limit_range *c33_pw_limit_ranges;
|
||||
u32 c33_pw_limit_nb_ranges;
|
||||
u32 prio_max;
|
||||
u32 prio;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -152,6 +177,11 @@ struct ethtool_pse_control_status {
|
||||
* range. The driver is in charge of the memory
|
||||
* allocation and should return the number of
|
||||
* ranges.
|
||||
* @pi_get_prio: Get the PSE PI priority.
|
||||
* @pi_set_prio: Configure the PSE PI priority.
|
||||
* @pi_get_pw_req: Get the power requested by a PD before enabling the PSE PI.
|
||||
* This is only relevant when an interrupt is registered using
|
||||
* devm_pse_irq_helper helper.
|
||||
*/
|
||||
struct pse_controller_ops {
|
||||
int (*setup_pi_matrix)(struct pse_controller_dev *pcdev);
|
||||
@@ -172,6 +202,10 @@ struct pse_controller_ops {
|
||||
int id, int max_mW);
|
||||
int (*pi_get_pw_limit_ranges)(struct pse_controller_dev *pcdev, int id,
|
||||
struct pse_pw_limit_ranges *pw_limit_ranges);
|
||||
int (*pi_get_prio)(struct pse_controller_dev *pcdev, int id);
|
||||
int (*pi_set_prio)(struct pse_controller_dev *pcdev, int id,
|
||||
unsigned int prio);
|
||||
int (*pi_get_pw_req)(struct pse_controller_dev *pcdev, int id);
|
||||
};
|
||||
|
||||
struct module;
|
||||
@@ -206,12 +240,35 @@ struct pse_pi_pairset {
|
||||
* @np: device node pointer of the PSE PI node
|
||||
* @rdev: regulator represented by the PSE PI
|
||||
* @admin_state_enabled: PI enabled state
|
||||
* @pw_d: Power domain of the PSE PI
|
||||
* @prio: Priority of the PSE PI. Used in static budget evaluation strategy
|
||||
* @isr_pd_detected: PSE PI detection status managed by the interruption
|
||||
* handler. This variable is relevant when the power enabled
|
||||
* management is managed in software like the static
|
||||
* budget evaluation strategy.
|
||||
* @pw_allocated_mW: Power allocated to a PSE PI to manage power budget in
|
||||
* static budget evaluation strategy.
|
||||
*/
|
||||
struct pse_pi {
|
||||
struct pse_pi_pairset pairset[2];
|
||||
struct device_node *np;
|
||||
struct regulator_dev *rdev;
|
||||
bool admin_state_enabled;
|
||||
struct pse_power_domain *pw_d;
|
||||
int prio;
|
||||
bool isr_pd_detected;
|
||||
int pw_allocated_mW;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct pse_ntf - PSE notification element
|
||||
*
|
||||
* @id: ID of the PSE control
|
||||
* @notifs: PSE notifications to be reported
|
||||
*/
|
||||
struct pse_ntf {
|
||||
int id;
|
||||
unsigned long notifs;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -228,6 +285,13 @@ struct pse_pi {
|
||||
* @types: types of the PSE controller
|
||||
* @pi: table of PSE PIs described in this controller device
|
||||
* @no_of_pse_pi: flag set if the pse_pis devicetree node is not used
|
||||
* @irq: PSE interrupt
|
||||
* @pis_prio_max: Maximum value allowed for the PSE PIs priority
|
||||
* @supp_budget_eval_strategies: budget evaluation strategies supported
|
||||
* by the PSE
|
||||
* @ntf_work: workqueue for PSE notification management
|
||||
* @ntf_fifo: PSE notifications FIFO
|
||||
* @ntf_fifo_lock: protect @ntf_fifo writer
|
||||
*/
|
||||
struct pse_controller_dev {
|
||||
const struct pse_controller_ops *ops;
|
||||
@@ -241,6 +305,30 @@ struct pse_controller_dev {
|
||||
enum ethtool_pse_types types;
|
||||
struct pse_pi *pi;
|
||||
bool no_of_pse_pi;
|
||||
int irq;
|
||||
unsigned int pis_prio_max;
|
||||
u32 supp_budget_eval_strategies;
|
||||
struct work_struct ntf_work;
|
||||
DECLARE_KFIFO_PTR(ntf_fifo, struct pse_ntf);
|
||||
spinlock_t ntf_fifo_lock; /* Protect @ntf_fifo writer */
|
||||
};
|
||||
|
||||
/**
|
||||
* enum pse_budget_eval_strategies - PSE budget evaluation strategies.
|
||||
* @PSE_BUDGET_EVAL_STRAT_DISABLED: Budget evaluation strategy disabled.
|
||||
* @PSE_BUDGET_EVAL_STRAT_STATIC: PSE static budget evaluation strategy.
|
||||
* Budget evaluation strategy based on the power requested during PD
|
||||
* classification. This strategy is managed by the PSE core.
|
||||
* @PSE_BUDGET_EVAL_STRAT_DYNAMIC: PSE dynamic budget evaluation
|
||||
* strategy. Budget evaluation strategy based on the current consumption
|
||||
* per ports compared to the total power budget. This mode is managed by
|
||||
* the PSE controller.
|
||||
*/
|
||||
|
||||
enum pse_budget_eval_strategies {
|
||||
PSE_BUDGET_EVAL_STRAT_DISABLED = 1 << 0,
|
||||
PSE_BUDGET_EVAL_STRAT_STATIC = 1 << 1,
|
||||
PSE_BUDGET_EVAL_STRAT_DYNAMIC = 1 << 2,
|
||||
};
|
||||
|
||||
#if IS_ENABLED(CONFIG_PSE_CONTROLLER)
|
||||
@@ -249,8 +337,11 @@ void pse_controller_unregister(struct pse_controller_dev *pcdev);
|
||||
struct device;
|
||||
int devm_pse_controller_register(struct device *dev,
|
||||
struct pse_controller_dev *pcdev);
|
||||
int devm_pse_irq_helper(struct pse_controller_dev *pcdev, int irq,
|
||||
int irq_flags, const struct pse_irq_desc *d);
|
||||
|
||||
struct pse_control *of_pse_control_get(struct device_node *node);
|
||||
struct pse_control *of_pse_control_get(struct device_node *node,
|
||||
struct phy_device *phydev);
|
||||
void pse_control_put(struct pse_control *psec);
|
||||
|
||||
int pse_ethtool_get_status(struct pse_control *psec,
|
||||
@@ -262,13 +353,17 @@ int pse_ethtool_set_config(struct pse_control *psec,
|
||||
int pse_ethtool_set_pw_limit(struct pse_control *psec,
|
||||
struct netlink_ext_ack *extack,
|
||||
const unsigned int pw_limit);
|
||||
int pse_ethtool_set_prio(struct pse_control *psec,
|
||||
struct netlink_ext_ack *extack,
|
||||
unsigned int prio);
|
||||
|
||||
bool pse_has_podl(struct pse_control *psec);
|
||||
bool pse_has_c33(struct pse_control *psec);
|
||||
|
||||
#else
|
||||
|
||||
static inline struct pse_control *of_pse_control_get(struct device_node *node)
|
||||
static inline struct pse_control *of_pse_control_get(struct device_node *node,
|
||||
struct phy_device *phydev)
|
||||
{
|
||||
return ERR_PTR(-ENOENT);
|
||||
}
|
||||
@@ -298,6 +393,13 @@ static inline int pse_ethtool_set_pw_limit(struct pse_control *psec,
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
static inline int pse_ethtool_set_prio(struct pse_control *psec,
|
||||
struct netlink_ext_ack *extack,
|
||||
unsigned int prio)
|
||||
{
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
static inline bool pse_has_podl(struct pse_control *psec)
|
||||
{
|
||||
return false;
|
||||
|
||||
@@ -49,6 +49,34 @@ enum hwtstamp_source {
|
||||
HWTSTAMP_SOURCE_PHYLIB,
|
||||
};
|
||||
|
||||
/**
|
||||
* enum ethtool_pse_event - PSE event list for the PSE controller
|
||||
* @ETHTOOL_PSE_EVENT_OVER_CURRENT: PSE output current is too high
|
||||
* @ETHTOOL_PSE_EVENT_OVER_TEMP: PSE in over temperature state
|
||||
* @ETHTOOL_C33_PSE_EVENT_DETECTION: detection process occur on the PSE. IEEE
|
||||
* 802.3-2022 33.2.5 and 145.2.6 PSE detection of PDs. IEEE 802.3-202
|
||||
* 30.9.1.1.5 aPSEPowerDetectionStatus
|
||||
* @ETHTOOL_C33_PSE_EVENT_CLASSIFICATION: classification process occur on the
|
||||
* PSE. IEEE 802.3-2022 33.2.6 and 145.2.8 classification of PDs mutual
|
||||
* identification. IEEE 802.3-2022 30.9.1.1.8 aPSEPowerClassification.
|
||||
* @ETHTOOL_C33_PSE_EVENT_DISCONNECTION: PD has been disconnected on the PSE.
|
||||
* IEEE 802.3-2022 33.3.8 and 145.3.9 PD Maintain Power Signature. IEEE
|
||||
* 802.3-2022 33.5.1.2.9 MPS Absent. IEEE 802.3-2022 30.9.1.1.20
|
||||
* aPSEMPSAbsentCounter.
|
||||
* @ETHTOOL_PSE_EVENT_OVER_BUDGET: PSE turned off due to over budget situation
|
||||
* @ETHTOOL_PSE_EVENT_SW_PW_CONTROL_ERROR: PSE faced an error managing the
|
||||
* power control from software
|
||||
*/
|
||||
enum ethtool_pse_event {
|
||||
ETHTOOL_PSE_EVENT_OVER_CURRENT = 1,
|
||||
ETHTOOL_PSE_EVENT_OVER_TEMP = 2,
|
||||
ETHTOOL_C33_PSE_EVENT_DETECTION = 4,
|
||||
ETHTOOL_C33_PSE_EVENT_CLASSIFICATION = 8,
|
||||
ETHTOOL_C33_PSE_EVENT_DISCONNECTION = 16,
|
||||
ETHTOOL_PSE_EVENT_OVER_BUDGET = 32,
|
||||
ETHTOOL_PSE_EVENT_SW_PW_CONTROL_ERROR = 64,
|
||||
};
|
||||
|
||||
enum {
|
||||
ETHTOOL_A_HEADER_UNSPEC,
|
||||
ETHTOOL_A_HEADER_DEV_INDEX,
|
||||
@@ -642,6 +670,9 @@ enum {
|
||||
ETHTOOL_A_C33_PSE_EXT_SUBSTATE,
|
||||
ETHTOOL_A_C33_PSE_AVAIL_PW_LIMIT,
|
||||
ETHTOOL_A_C33_PSE_PW_LIMIT_RANGES,
|
||||
ETHTOOL_A_PSE_PW_D_ID,
|
||||
ETHTOOL_A_PSE_PRIO_MAX,
|
||||
ETHTOOL_A_PSE_PRIO,
|
||||
|
||||
__ETHTOOL_A_PSE_CNT,
|
||||
ETHTOOL_A_PSE_MAX = (__ETHTOOL_A_PSE_CNT - 1)
|
||||
@@ -718,6 +749,14 @@ enum {
|
||||
ETHTOOL_A_TSCONFIG_MAX = (__ETHTOOL_A_TSCONFIG_CNT - 1)
|
||||
};
|
||||
|
||||
enum {
|
||||
ETHTOOL_A_PSE_NTF_HEADER = 1,
|
||||
ETHTOOL_A_PSE_NTF_EVENTS,
|
||||
|
||||
__ETHTOOL_A_PSE_NTF_CNT,
|
||||
ETHTOOL_A_PSE_NTF_MAX = (__ETHTOOL_A_PSE_NTF_CNT - 1)
|
||||
};
|
||||
|
||||
enum {
|
||||
ETHTOOL_MSG_USER_NONE = 0,
|
||||
ETHTOOL_MSG_STRSET_GET = 1,
|
||||
@@ -822,6 +861,7 @@ enum {
|
||||
ETHTOOL_MSG_PHY_NTF,
|
||||
ETHTOOL_MSG_TSCONFIG_GET_REPLY,
|
||||
ETHTOOL_MSG_TSCONFIG_SET_REPLY,
|
||||
ETHTOOL_MSG_PSE_NTF,
|
||||
|
||||
__ETHTOOL_MSG_KERNEL_CNT,
|
||||
ETHTOOL_MSG_KERNEL_MAX = (__ETHTOOL_MSG_KERNEL_CNT - 1)
|
||||
|
||||
@@ -83,6 +83,8 @@ static int pse_reply_size(const struct ethnl_req_info *req_base,
|
||||
const struct ethtool_pse_control_status *st = &data->status;
|
||||
int len = 0;
|
||||
|
||||
if (st->pw_d_id)
|
||||
len += nla_total_size(sizeof(u32)); /* _PSE_PW_D_ID */
|
||||
if (st->podl_admin_state > 0)
|
||||
len += nla_total_size(sizeof(u32)); /* _PODL_PSE_ADMIN_STATE */
|
||||
if (st->podl_pw_status > 0)
|
||||
@@ -109,6 +111,9 @@ static int pse_reply_size(const struct ethnl_req_info *req_base,
|
||||
len += st->c33_pw_limit_nb_ranges *
|
||||
(nla_total_size(0) +
|
||||
nla_total_size(sizeof(u32)) * 2);
|
||||
if (st->prio_max)
|
||||
/* _PSE_PRIO_MAX + _PSE_PRIO */
|
||||
len += nla_total_size(sizeof(u32)) * 2;
|
||||
|
||||
return len;
|
||||
}
|
||||
@@ -148,6 +153,11 @@ static int pse_fill_reply(struct sk_buff *skb,
|
||||
const struct pse_reply_data *data = PSE_REPDATA(reply_base);
|
||||
const struct ethtool_pse_control_status *st = &data->status;
|
||||
|
||||
if (st->pw_d_id &&
|
||||
nla_put_u32(skb, ETHTOOL_A_PSE_PW_D_ID,
|
||||
st->pw_d_id))
|
||||
return -EMSGSIZE;
|
||||
|
||||
if (st->podl_admin_state > 0 &&
|
||||
nla_put_u32(skb, ETHTOOL_A_PODL_PSE_ADMIN_STATE,
|
||||
st->podl_admin_state))
|
||||
@@ -198,6 +208,11 @@ static int pse_fill_reply(struct sk_buff *skb,
|
||||
pse_put_pw_limit_ranges(skb, st))
|
||||
return -EMSGSIZE;
|
||||
|
||||
if (st->prio_max &&
|
||||
(nla_put_u32(skb, ETHTOOL_A_PSE_PRIO_MAX, st->prio_max) ||
|
||||
nla_put_u32(skb, ETHTOOL_A_PSE_PRIO, st->prio)))
|
||||
return -EMSGSIZE;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -219,6 +234,7 @@ const struct nla_policy ethnl_pse_set_policy[ETHTOOL_A_PSE_MAX + 1] = {
|
||||
NLA_POLICY_RANGE(NLA_U32, ETHTOOL_C33_PSE_ADMIN_STATE_DISABLED,
|
||||
ETHTOOL_C33_PSE_ADMIN_STATE_ENABLED),
|
||||
[ETHTOOL_A_C33_PSE_AVAIL_PW_LIMIT] = { .type = NLA_U32 },
|
||||
[ETHTOOL_A_PSE_PRIO] = { .type = NLA_U32 },
|
||||
};
|
||||
|
||||
static int
|
||||
@@ -267,6 +283,15 @@ ethnl_set_pse(struct ethnl_req_info *req_info, struct genl_info *info)
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (tb[ETHTOOL_A_PSE_PRIO]) {
|
||||
unsigned int prio;
|
||||
|
||||
prio = nla_get_u32(tb[ETHTOOL_A_PSE_PRIO]);
|
||||
ret = pse_ethtool_set_prio(phydev->psec, info->extack, prio);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (tb[ETHTOOL_A_C33_PSE_AVAIL_PW_LIMIT]) {
|
||||
unsigned int pw_limit;
|
||||
|
||||
@@ -315,3 +340,42 @@ const struct ethnl_request_ops ethnl_pse_request_ops = {
|
||||
.set = ethnl_set_pse,
|
||||
/* PSE has no notification */
|
||||
};
|
||||
|
||||
void ethnl_pse_send_ntf(struct net_device *netdev, unsigned long notifs)
|
||||
{
|
||||
void *reply_payload;
|
||||
struct sk_buff *skb;
|
||||
int reply_len;
|
||||
int ret;
|
||||
|
||||
ASSERT_RTNL();
|
||||
|
||||
if (!netdev || !notifs)
|
||||
return;
|
||||
|
||||
reply_len = ethnl_reply_header_size() +
|
||||
nla_total_size(sizeof(u32)); /* _PSE_NTF_EVENTS */
|
||||
|
||||
skb = genlmsg_new(reply_len, GFP_KERNEL);
|
||||
if (!skb)
|
||||
return;
|
||||
|
||||
reply_payload = ethnl_bcastmsg_put(skb, ETHTOOL_MSG_PSE_NTF);
|
||||
if (!reply_payload)
|
||||
goto err_skb;
|
||||
|
||||
ret = ethnl_fill_reply_header(skb, netdev, ETHTOOL_A_PSE_NTF_HEADER);
|
||||
if (ret < 0)
|
||||
goto err_skb;
|
||||
|
||||
if (nla_put_uint(skb, ETHTOOL_A_PSE_NTF_EVENTS, notifs))
|
||||
goto err_skb;
|
||||
|
||||
genlmsg_end(skb, reply_payload);
|
||||
ethnl_multicast(skb, netdev);
|
||||
return;
|
||||
|
||||
err_skb:
|
||||
nlmsg_free(skb);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(ethnl_pse_send_ntf);
|
||||
|
||||
Reference in New Issue
Block a user