@@ -40,10 +40,17 @@ struct pse_control {
* struct pse_power_domain - a PSE power domain
* @id: ID of the power domain
* @supply: Power supply the Power Domain
+ * @port_prio_mode: Current port priority mode of the power domain
+ * @discon_pol: Current disonnection policy of the power domain
+ * @pi_lrc_id: ID of the last recently connected PI. -1 if none. Relevant
+ * for static port priority mode.
*/
struct pse_power_domain {
int id;
struct regulator *supply;
+ u32 port_prio_mode;
+ u32 discon_pol;
+ int pi_lrc_id;
};
static int of_load_single_pse_pi_pairset(struct device_node *node,
@@ -222,6 +229,33 @@ static int of_load_pse_pis(struct pse_controller_dev *pcdev)
return ret;
}
+static void pse_pi_deallocate_pw_budget(struct pse_pi *pi)
+{
+ if (!pi->pw_d)
+ return;
+
+ regulator_free_power_budget(pi->pw_d->supply, pi->pw_allocated);
+}
+
+/* Helper returning true if the power control is managed from the software
+ * in the interrupt handler
+ */
+static bool pse_pw_d_is_sw_pw_control(struct pse_controller_dev *pcdev,
+ struct pse_power_domain *pw_d)
+{
+ if (!pw_d)
+ return false;
+
+ if (pw_d->port_prio_mode & ETHTOOL_PSE_PORT_PRIO_STATIC)
+ return true;
+ if (pw_d->port_prio_mode == ETHTOOL_PSE_PORT_PRIO_DISABLED &&
+ pcdev->ops->pi_enable && pcdev->ops->pi_get_pw_req &&
+ pcdev->irq)
+ return true;
+
+ return false;
+}
+
static int pse_pi_is_enabled(struct regulator_dev *rdev)
{
struct pse_controller_dev *pcdev = rdev_get_drvdata(rdev);
@@ -234,6 +268,13 @@ static int pse_pi_is_enabled(struct regulator_dev *rdev)
id = rdev_get_id(rdev);
mutex_lock(&pcdev->lock);
+ if (pse_pw_d_is_sw_pw_control(pcdev, pcdev->pi[id].pw_d)) {
+ ret = pcdev->pi[id].isr_enabled &&
+ pcdev->pi[id].admin_state_enabled;
+ mutex_unlock(&pcdev->lock);
+ return ret;
+ }
+
ret = ops->pi_is_enabled(pcdev, id);
mutex_unlock(&pcdev->lock);
@@ -244,7 +285,7 @@ static int pse_pi_enable(struct regulator_dev *rdev)
{
struct pse_controller_dev *pcdev = rdev_get_drvdata(rdev);
const struct pse_controller_ops *ops;
- int id, ret;
+ int id, ret = 0;
ops = pcdev->ops;
if (!ops->pi_enable)
@@ -252,6 +293,21 @@ static int pse_pi_enable(struct regulator_dev *rdev)
id = rdev_get_id(rdev);
mutex_lock(&pcdev->lock);
+ if (pse_pw_d_is_sw_pw_control(pcdev, pcdev->pi[id].pw_d)) {
+ /* Manage enabled status by software.
+ * Real enable process will happen if a port is connected.
+ */
+ if (pcdev->pi[id].isr_enabled) {
+ ret = ops->pi_enable(pcdev, id);
+ if (!ret)
+ pcdev->pi[id].admin_state_enabled = 1;
+ } else {
+ pcdev->pi[id].admin_state_enabled = 1;
+ }
+ mutex_unlock(&pcdev->lock);
+ return ret;
+ }
+
ret = ops->pi_enable(pcdev, id);
if (!ret)
pcdev->pi[id].admin_state_enabled = 1;
@@ -264,6 +320,7 @@ static int pse_pi_disable(struct regulator_dev *rdev)
{
struct pse_controller_dev *pcdev = rdev_get_drvdata(rdev);
const struct pse_controller_ops *ops;
+ struct pse_pi *pi;
int id, ret;
ops = pcdev->ops;
@@ -272,9 +329,16 @@ static int pse_pi_disable(struct regulator_dev *rdev)
id = rdev_get_id(rdev);
mutex_lock(&pcdev->lock);
+
+ pi = &pcdev->pi[id];
ret = ops->pi_disable(pcdev, id);
- if (!ret)
- pcdev->pi[id].admin_state_enabled = 0;
+ if (!ret) {
+ pse_pi_deallocate_pw_budget(pi);
+ pi->admin_state_enabled = 0;
+ pi->isr_enabled = 0;
+ if (pi->pw_d && pi->pw_d->pi_lrc_id == id)
+ pi->pw_d->pi_lrc_id = -1;
+ }
mutex_unlock(&pcdev->lock);
return ret;
@@ -516,6 +580,8 @@ static int pse_register_pw_ds(struct pse_controller_dev *pcdev)
}
pw_d->supply = supply;
+ pw_d->port_prio_mode = ETHTOOL_PSE_PORT_PRIO_DISABLED;
+ pw_d->discon_pol = ETHTOOL_PSE_DISCON_ROUND_ROBIN_IDX_LOWEST_FIRST;
pcdev->pi[i].pw_d = pw_d;
}
@@ -539,6 +605,7 @@ int pse_controller_register(struct pse_controller_dev *pcdev)
if (ret < 0)
return ret;
pcdev->id = ret;
+ pcdev->port_prio_supp_modes |= ETHTOOL_PSE_PORT_PRIO_DISABLED;
if (!pcdev->nr_lines)
pcdev->nr_lines = 1;
@@ -683,10 +750,279 @@ pse_control_find_phy_by_id(struct pse_controller_dev *pcdev, int id)
}
}
mutex_unlock(&pse_list_mutex);
-
return NULL;
}
+static int pse_pi_disable_isr(struct pse_controller_dev *pcdev, int id,
+ struct netlink_ext_ack *extack)
+{
+ const struct pse_controller_ops *ops = pcdev->ops;
+ struct pse_pi *pi = &pcdev->pi[id];
+ int ret;
+
+ if (!ops->pi_disable) {
+ NL_SET_ERR_MSG(extack, "PSE does not support disable control");
+ return -EOPNOTSUPP;
+ }
+
+ if (!pi->isr_enabled)
+ return 0;
+
+ if (pi->admin_state_enabled) {
+ ret = ops->pi_disable(pcdev, id);
+ if (ret) {
+ NL_SET_ERR_MSG_FMT(extack,
+ "PI %d: disable error %d",
+ id, ret);
+ return ret;
+ }
+ }
+
+ pse_pi_deallocate_pw_budget(pi);
+ pi->isr_enabled = 0;
+ if (pi->pw_d && pi->pw_d->pi_lrc_id == id)
+ pi->pw_d->pi_lrc_id = -1;
+
+ return 0;
+}
+
+static int _pse_disable_pi_pol(struct pse_controller_dev *pcdev, int id)
+{
+ unsigned long notifs = ETHTOOL_C33_PSE_EVENT_DISCONNECTION;
+ struct netlink_ext_ack extack = {};
+ struct phy_device *phydev;
+ int ret;
+
+ dev_dbg(pcdev->dev, "Disabling PI %d to free power budget\n", id);
+
+ NL_SET_ERR_MSG_FMT(&extack,
+ "Disabling PI %d to free power budget", id);
+
+ ret = pse_pi_disable_isr(pcdev, id, &extack);
+ if (ret)
+ notifs |= ETHTOOL_PSE_EVENT_SW_PW_CONTROL_ERROR;
+ phydev = pse_control_find_phy_by_id(pcdev, id);
+ if (phydev)
+ ethnl_pse_send_ntf(phydev, notifs, &extack);
+
+ return ret;
+}
+
+static int pse_disable_pi_prio_lrc(struct pse_controller_dev *pcdev,
+ struct pse_power_domain *pw_d,
+ int prio)
+{
+ int lrc_id = pw_d->pi_lrc_id;
+ int ret;
+
+ if (lrc_id < 0)
+ return 0;
+
+ if (pcdev->pi[lrc_id].prio != prio)
+ return 0;
+
+ ret = _pse_disable_pi_pol(pcdev, lrc_id);
+ if (ret)
+ return ret;
+
+ /* PI disabled */
+ return 1;
+}
+
+static int pse_disable_pi_prio_round_rob_low(struct pse_controller_dev *pcdev,
+ struct pse_power_domain *pw_d,
+ int prio)
+{
+ int i;
+
+ for (i = 0; i < pcdev->nr_lines; i++) {
+ int ret;
+
+ if (pcdev->pi[i].prio != prio ||
+ pcdev->pi[i].pw_d != pw_d ||
+ !pcdev->pi[i].isr_enabled)
+ continue;
+
+ ret = _pse_disable_pi_pol(pcdev, i);
+ if (ret)
+ return ret;
+
+ /* PI disabled */
+ return 1;
+ }
+
+ /* No PI disabled */
+ return 0;
+}
+
+static int pse_disable_pi_prio_pol(struct pse_controller_dev *pcdev,
+ struct pse_power_domain *pw_d,
+ int prio)
+{
+ int ret;
+
+ if (pw_d->discon_pol & ETHTOOL_PSE_DISCON_LRC) {
+ ret = pse_disable_pi_prio_lrc(pcdev, pw_d, prio);
+ if (ret)
+ return ret;
+ }
+
+ if (pw_d->discon_pol &
+ ETHTOOL_PSE_DISCON_ROUND_ROBIN_IDX_LOWEST_FIRST) {
+ ret = pse_disable_pi_prio_round_rob_low(pcdev, pw_d, prio);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int
+pse_pi_allocate_pw_budget_static_prio(struct pse_controller_dev *pcdev, int id,
+ int pw_req, struct netlink_ext_ack *extack)
+{
+ struct pse_pi *pi = &pcdev->pi[id];
+ int ret, _prio;
+
+ _prio = pcdev->nr_lines;
+ while (regulator_request_power_budget(pi->pw_d->supply, pw_req) == -ERANGE) {
+ ret = pse_disable_pi_prio_pol(pcdev, pi->pw_d, _prio);
+ if (ret < 0)
+ return ret;
+ /* No pi disabled, decrease priority value */
+ if (!ret)
+ _prio--;
+
+ if (_prio <= pi->prio) {
+ NL_SET_ERR_MSG_FMT(extack,
+ "PI %d: not enough power budget available",
+ id);
+ return -ERANGE;
+ }
+ }
+
+ pi->pw_allocated = pw_req;
+ return 0;
+}
+
+static int pse_pi_allocate_pw_budget(struct pse_controller_dev *pcdev, int id,
+ int pw_req, struct netlink_ext_ack *extack)
+{
+ struct pse_pi *pi = &pcdev->pi[id];
+ int ret;
+
+ if (!pi->pw_d)
+ return 0;
+
+ /* ETHTOOL_PSE_PORT_PRIO_STATIC */
+ if (pi->pw_d->port_prio_mode & ETHTOOL_PSE_PORT_PRIO_STATIC)
+ return pse_pi_allocate_pw_budget_static_prio(pcdev, id, pw_req,
+ extack);
+
+ /* ETHTOOL_PSE_PORT_PRIO_DISABLED */
+ ret = regulator_request_power_budget(pi->pw_d->supply, pw_req);
+ if (ret)
+ NL_SET_ERR_MSG_FMT(extack,
+ "PI %d: not enough power budget available",
+ id);
+ else
+ pi->pw_allocated = pw_req;
+
+ return ret;
+}
+
+static int pse_pi_enable_isr(struct pse_controller_dev *pcdev, int id,
+ struct netlink_ext_ack *extack)
+{
+ const struct pse_controller_ops *ops = pcdev->ops;
+ struct pse_pi *pi = &pcdev->pi[id];
+ int ret, pw_req;
+
+ if (!ops->pi_enable || !ops->pi_get_pw_req) {
+ NL_SET_ERR_MSG(extack, "PSE does not support enable control");
+ return -EOPNOTSUPP;
+ }
+
+ if (pi->isr_enabled)
+ return 0;
+
+ ret = ops->pi_get_pw_req(pcdev, id);
+ if (ret < 0)
+ return ret;
+
+ pw_req = ret;
+
+ /* Compare requested power with port power limit and use the lowest
+ * one.
+ */
+ if (ops->pi_get_current_limit && ops->pi_get_voltage) {
+ int uV, mW;
+ s64 tmp_64;
+
+ ret = ops->pi_get_voltage(pcdev, id);
+ if (ret < 0)
+ return ret;
+ uV = ret;
+
+ ret = ops->pi_get_current_limit(pcdev, id);
+ if (ret < 0)
+ return ret;
+
+ tmp_64 = ret;
+ tmp_64 *= uV;
+ /* mW = uV * uA / 1000000000 */
+ mW = DIV_ROUND_CLOSEST_ULL(tmp_64, 1000000000);
+ if (mW < pw_req)
+ pw_req = mW;
+ }
+
+ ret = pse_pi_allocate_pw_budget(pcdev, id, pw_req, extack);
+ if (ret)
+ return ret;
+
+ if (pi->admin_state_enabled) {
+ ret = ops->pi_enable(pcdev, id);
+ if (ret) {
+ pse_pi_deallocate_pw_budget(pi);
+ NL_SET_ERR_MSG_FMT(extack,
+ "PI %d: enable error %d",
+ id, ret);
+ return ret;
+ }
+ }
+
+ pi->isr_enabled = 1;
+ if (pi->pw_d)
+ pi->pw_d->pi_lrc_id = id;
+ return 0;
+}
+
+static int pse_set_config_isr(struct pse_controller_dev *pcdev, int id,
+ unsigned long notifs,
+ struct netlink_ext_ack *extack)
+{
+ int ret = 0;
+
+ if (notifs & ETHTOOL_PSE_PORT_PRIO_DYNAMIC)
+ return 0;
+
+ if ((notifs & ETHTOOL_C33_PSE_EVENT_DISCONNECTION) &&
+ ((notifs & ETHTOOL_C33_PSE_EVENT_DETECTION) ||
+ (notifs & ETHTOOL_C33_PSE_EVENT_CLASSIFICATION))) {
+ NL_SET_ERR_MSG_FMT(extack,
+ "PI %d: error, connection and disconnection reported simultaneously",
+ id);
+ return -EINVAL;
+ }
+
+ if (notifs & ETHTOOL_C33_PSE_EVENT_CLASSIFICATION)
+ ret = pse_pi_enable_isr(pcdev, id, extack);
+ else if (notifs & ETHTOOL_C33_PSE_EVENT_DISCONNECTION)
+ ret = pse_pi_disable_isr(pcdev, id, extack);
+
+ return ret;
+}
+
static irqreturn_t pse_isr(int irq, void *data)
{
struct netlink_ext_ack extack = {};
@@ -703,9 +1039,10 @@ static irqreturn_t pse_isr(int irq, void *data)
memset(h->notifs, 0, pcdev->nr_lines * sizeof(*h->notifs));
mutex_lock(&pcdev->lock);
ret = desc->map_event(irq, pcdev, h->notifs, ¬ifs_mask);
- mutex_unlock(&pcdev->lock);
- if (ret || !notifs_mask)
+ if (ret || !notifs_mask) {
+ mutex_unlock(&pcdev->lock);
return IRQ_NONE;
+ }
for_each_set_bit(i, ¬ifs_mask, pcdev->nr_lines) {
struct phy_device *phydev;
@@ -716,6 +1053,12 @@ static irqreturn_t pse_isr(int irq, void *data)
continue;
notifs = h->notifs[i];
+ if (pse_pw_d_is_sw_pw_control(pcdev, pcdev->pi[i].pw_d)) {
+ ret = pse_set_config_isr(pcdev, i, notifs, &extack);
+ if (ret)
+ notifs |= ETHTOOL_PSE_EVENT_SW_PW_CONTROL_ERROR;
+ }
+
dev_dbg(h->pcdev->dev,
"Sending PSE notification EVT 0x%lx\n", notifs);
@@ -727,6 +1070,8 @@ static irqreturn_t pse_isr(int irq, void *data)
NULL);
}
+ mutex_unlock(&pcdev->lock);
+
return IRQ_HANDLED;
}
@@ -971,6 +1316,7 @@ static int _pse_ethtool_get_status(struct pse_controller_dev *pcdev,
struct pse_control_status *status)
{
const struct pse_controller_ops *ops;
+ struct pse_pi *pi = &pcdev->pi[id];
ops = pcdev->ops;
if (!ops->ethtool_get_status) {
@@ -980,8 +1326,23 @@ static int _pse_ethtool_get_status(struct pse_controller_dev *pcdev,
}
status->pse_id = pcdev->id;
- if (pcdev->pi[id].pw_d)
- status->pw_d_id = pcdev->pi[id].pw_d->id;
+ if (pi->pw_d) {
+ status->pw_d_id = pi->pw_d->id;
+ status->c33_prio_mode = pi->pw_d->port_prio_mode;
+ status->c33_discon_pol = pi->pw_d->discon_pol;
+ switch (pi->pw_d->port_prio_mode) {
+ case ETHTOOL_PSE_PORT_PRIO_STATIC:
+ status->c33_prio_max = pcdev->nr_lines;
+ status->c33_prio = pi->prio;
+ break;
+ case ETHTOOL_PSE_PORT_PRIO_DYNAMIC:
+ status->c33_prio_max = pcdev->pis_prio_max;
+ break;
+ default:
+ break;
+ }
+ status->c33_prio_supp_modes = pcdev->port_prio_supp_modes;
+ }
return ops->ethtool_get_status(pcdev, id, extack, status);
}
@@ -1020,8 +1381,9 @@ static int pse_ethtool_c33_set_config(struct pse_control *psec,
case ETHTOOL_C33_PSE_ADMIN_STATE_ENABLED:
/* We could have mismatch between admin_state_enabled and
* state reported by regulator_is_enabled. This can occur when
- * the PI is forcibly turn off by the controller. Call
- * regulator_disable on that case to fix the counters state.
+ * the PI is forcibly turn off by the controller or by the
+ * interrupt context. Call regulator_disable on that case
+ * to fix the counters state.
*/
if (psec->pcdev->pi[psec->id].admin_state_enabled &&
!regulator_is_enabled(psec->ps)) {
@@ -1094,6 +1456,32 @@ int pse_ethtool_set_config(struct pse_control *psec,
}
EXPORT_SYMBOL_GPL(pse_ethtool_set_config);
+static int pse_pi_update_pw_budget(struct pse_pi *pi, int id,
+ const unsigned int pw_limit,
+ struct netlink_ext_ack *extack)
+{
+ int pw_diff, ret;
+
+ pw_diff = pw_limit - pi->pw_allocated;
+ if (!pw_diff) {
+ return 0;
+ } else if (pw_diff > 0) {
+ ret = regulator_request_power_budget(pi->pw_d->supply, pw_diff);
+ if (ret) {
+ NL_SET_ERR_MSG_FMT(extack,
+ "PI %d: not enough power budget available",
+ id);
+ return ret;
+ }
+
+ } else {
+ regulator_free_power_budget(pi->pw_d->supply, -pw_diff);
+ }
+ pi->pw_allocated = pw_limit;
+
+ return 0;
+}
+
/**
* pse_ethtool_set_pw_limit - set PSE control power limit
* @psec: PSE control pointer
@@ -1130,10 +1518,152 @@ int pse_ethtool_set_pw_limit(struct pse_control *psec,
/* uA = mW * 1000000000 / uV */
uA = DIV_ROUND_CLOSEST_ULL(tmp_64, uV);
+ if (psec->pcdev->pi[psec->id].pw_d) {
+ ret = pse_pi_update_pw_budget(&psec->pcdev->pi[psec->id],
+ psec->id, pw_limit, extack);
+ if (ret)
+ return ret;
+ }
+
return regulator_set_current_limit(psec->ps, 0, uA);
}
EXPORT_SYMBOL_GPL(pse_ethtool_set_pw_limit);
+int pse_ethtool_set_prio(struct pse_control *psec,
+ struct netlink_ext_ack *extack,
+ unsigned int prio)
+{
+ struct pse_controller_dev *pcdev = psec->pcdev;
+ const struct pse_controller_ops *ops;
+ int ret = 0;
+
+ if (!pcdev->pi[psec->id].pw_d) {
+ NL_SET_ERR_MSG(extack, "no power domain attached");
+ return -EOPNOTSUPP;
+ }
+
+ /* We don't want priority change in the middle of an
+ * enable/disable call or a priority mode change
+ */
+ mutex_lock(&pcdev->lock);
+ switch (pcdev->pi[psec->id].pw_d->port_prio_mode) {
+ case ETHTOOL_PSE_PORT_PRIO_STATIC:
+ if (prio > pcdev->nr_lines) {
+ NL_SET_ERR_MSG_FMT(extack,
+ "priority %d exceed priority max %d",
+ prio, pcdev->nr_lines);
+ ret = -ERANGE;
+ goto out;
+ }
+
+ pcdev->pi[psec->id].prio = prio;
+ break;
+
+ case ETHTOOL_PSE_PORT_PRIO_DYNAMIC:
+ ops = psec->pcdev->ops;
+ if (!ops->pi_set_prio) {
+ NL_SET_ERR_MSG(extack,
+ "pse driver does not support setting port priority");
+ ret = -EOPNOTSUPP;
+ goto out;
+ }
+
+ if (prio > pcdev->pis_prio_max) {
+ NL_SET_ERR_MSG_FMT(extack,
+ "priority %d exceed priority max %d",
+ prio, pcdev->pis_prio_max);
+ ret = -ERANGE;
+ goto out;
+ }
+
+ ret = ops->pi_set_prio(pcdev, psec->id, prio);
+ break;
+
+ default:
+ ret = -EOPNOTSUPP;
+ }
+
+out:
+ mutex_unlock(&pcdev->lock);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(pse_ethtool_set_prio);
+
+int pse_ethtool_set_prio_mode(struct pse_control *psec,
+ struct netlink_ext_ack *extack,
+ u32 prio_mode)
+{
+ struct pse_controller_dev *pcdev = psec->pcdev;
+ const struct pse_controller_ops *ops;
+ int ret = 0, i;
+
+ if (!(prio_mode & pcdev->port_prio_supp_modes)) {
+ NL_SET_ERR_MSG(extack, "priority mode not supported");
+ return -EOPNOTSUPP;
+ }
+
+ if (!pcdev->pi[psec->id].pw_d) {
+ NL_SET_ERR_MSG(extack, "no power domain attached");
+ return -EOPNOTSUPP;
+ }
+
+ /* ETHTOOL_PSE_PORT_PRIO_DISABLED can't be ORed with another mode */
+ if (prio_mode & ETHTOOL_PSE_PORT_PRIO_DISABLED &&
+ prio_mode & ~ETHTOOL_PSE_PORT_PRIO_DISABLED) {
+ NL_SET_ERR_MSG(extack,
+ "port priority can't be enabled and disabled simultaneously");
+ return -EINVAL;
+ }
+
+ ops = psec->pcdev->ops;
+
+ /* We don't want priority mode change in the middle of an
+ * enable/disable call
+ */
+ mutex_lock(&pcdev->lock);
+ pcdev->pi[psec->id].pw_d->port_prio_mode = prio_mode;
+
+ /* Reset all priorities of the Power Domain */
+ for (i = 0; i < psec->pcdev->nr_lines; i++) {
+ if (!pcdev->pi[i].rdev ||
+ pcdev->pi[i].pw_d != pcdev->pi[psec->id].pw_d)
+ continue;
+
+ pcdev->pi[i].prio = 0;
+
+ if (!ops->pi_set_prio)
+ continue;
+
+ if (pcdev->port_prio_supp_modes &
+ ETHTOOL_PSE_PORT_PRIO_DYNAMIC)
+ ret = ops->pi_set_prio(pcdev, psec->id, 0);
+ }
+
+ mutex_unlock(&psec->pcdev->lock);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(pse_ethtool_set_prio_mode);
+
+int pse_ethtool_set_discon_pol(struct pse_control *psec,
+ struct netlink_ext_ack *extack,
+ u32 pol)
+{
+ struct pse_controller_dev *pcdev = psec->pcdev;
+
+ if (!pcdev->pi[psec->id].pw_d) {
+ NL_SET_ERR_MSG(extack, "no power domain attached");
+ return -EOPNOTSUPP;
+ }
+
+ mutex_lock(&pcdev->lock);
+ pcdev->pi[psec->id].pw_d->discon_pol = pol;
+ mutex_unlock(&psec->pcdev->lock);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(pse_ethtool_set_discon_pol);
+
bool pse_has_podl(struct pse_control *psec)
{
return psec->pcdev->types & ETHTOOL_PSE_PODL;
@@ -9,6 +9,7 @@
#include <linux/list.h>
#include <uapi/linux/ethtool.h>
#include <linux/regulator/driver.h>
+#include <linux/workqueue.h>
/* Maximum current in uA according to IEEE 802.3-2022 Table 145-1 */
#define MAX_PI_CURRENT 1920000
@@ -62,6 +63,13 @@ struct pse_control_config {
* is in charge of the memory allocation.
* @c33_pw_limit_nb_ranges: number of supported power limit configuration
* ranges
+ * @c33_prio_supp_modes: PSE port priority modes supported. Set by PSE core.
+ * @c33_prio_mode: PSE port priority mode selected. Set by PSE core.
+ * @c33_prio_max: max priority allowed for the c33_prio variable value. Set
+ * by PSE core.
+ * @c33_prio: priority of the PSE. Set by PSE core in case of static port
+ * priority mode.
+ * @c33_discon_pol: PSE disconnection policy selected. Set by PSE core.
*/
struct pse_control_status {
u32 pse_id;
@@ -76,6 +84,11 @@ struct 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 c33_prio_supp_modes;
+ enum pse_port_prio_modes c33_prio_mode;
+ u32 c33_prio_max;
+ u32 c33_prio;
+ u32 c33_discon_pol;
};
/**
@@ -95,6 +108,10 @@ struct pse_control_status {
* set_current_limit regulator callback.
* Should not return an error in case of MAX_PI_CURRENT
* current value set.
+ * @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 (*ethtool_get_status)(struct pse_controller_dev *pcdev,
@@ -109,6 +126,9 @@ struct pse_controller_ops {
int id);
int (*pi_set_current_limit)(struct pse_controller_dev *pcdev,
int id, int max_uA);
+ 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;
@@ -143,6 +163,12 @@ struct pse_pi_pairset {
* @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 port priority mode
+ * @isr_enabled: PSE PI power status managed by the interruption handler.
+ * This variable is relevant when the power enabled management
+ * is a managed in software like the static port priority mode.
+ * @pw_allocated: Power allocated to a PSE PI to manage power budget in
+ * static port priority mode
*/
struct pse_pi {
struct pse_pi_pairset pairset[2];
@@ -150,6 +176,9 @@ struct pse_pi {
struct regulator_dev *rdev;
bool admin_state_enabled;
struct pse_power_domain *pw_d;
+ int prio;
+ bool isr_enabled;
+ int pw_allocated;
};
/**
@@ -168,6 +197,8 @@ struct pse_pi {
* @no_of_pse_pi: flag set if the pse_pis devicetree node is not used
* @id: Index of the PSE
* @irq: PSE interrupt
+ * @pis_prio_max: Maximum value allowed for the PSE PIs priority
+ * @port_prio_supp_modes: Bitfield of port priority mode supported by the PSE
*/
struct pse_controller_dev {
const struct pse_controller_ops *ops;
@@ -183,6 +214,8 @@ struct pse_controller_dev {
bool no_of_pse_pi;
u32 id;
int irq;
+ unsigned int pis_prio_max;
+ u32 port_prio_supp_modes;
};
#if IS_ENABLED(CONFIG_PSE_CONTROLLER)
@@ -207,6 +240,15 @@ 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);
+int pse_ethtool_set_prio_mode(struct pse_control *psec,
+ struct netlink_ext_ack *extack,
+ u32 prio_mode);
+int pse_ethtool_set_discon_pol(struct pse_control *psec,
+ struct netlink_ext_ack *extack,
+ u32 pol);
bool pse_has_podl(struct pse_control *psec);
bool pse_has_c33(struct pse_control *psec);
@@ -244,6 +286,27 @@ 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 int pse_ethtool_set_prio_mode(struct pse_control *psec,
+ struct netlink_ext_ack *extack,
+ u32 prio_mode)
+{
+ return -EOPNOTSUPP;
+}
+
+static inline int pse_ethtool_set_discon_pol(struct pse_control *psec,
+ struct netlink_ext_ack *extack,
+ u32 pol)
+{
+ return -EOPNOTSUPP;
+}
+
static inline bool pse_has_podl(struct pse_control *psec)
{
return false;
@@ -1002,11 +1002,84 @@ enum ethtool_c33_pse_pw_d_status {
* enum ethtool_pse_events - event list of 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 145.2.6 PSE detection of PDs
+ * @ETHTOOL_C33_PSE_EVENT_CLASSIFICATION: classification process occur on
+ * the PSE. IEEE 802.3-2022 145.2.8 PSE classification of PDs and
+ * mutual identification
+ * @ETHTOOL_C33_PSE_EVENT_DISCONNECTION: PD has been disconnected on the PSE.
+ * @ETHTOOL_PSE_EVENT_SW_PW_CONTROL_ERROR: PSE faced an error managing the
+ * power control from software.
*/
enum ethtool_pse_events {
ETHTOOL_PSE_EVENT_OVER_CURRENT = 1 << 0,
ETHTOOL_PSE_EVENT_OVER_TEMP = 1 << 1,
+ ETHTOOL_C33_PSE_EVENT_DETECTION = 1 << 2,
+ ETHTOOL_C33_PSE_EVENT_CLASSIFICATION = 1 << 3,
+ ETHTOOL_C33_PSE_EVENT_DISCONNECTION = 1 << 4,
+ ETHTOOL_PSE_EVENT_SW_PW_CONTROL_ERROR = 1 << 5,
+};
+
+/**
+ * enum pse_port_prio_modes - PSE port priority modes.
+ * @ETHTOOL_PSE_PORT_PRIO_DISABLED: Port priority disabled.
+ * @ETHTOOL_PSE_PORT_PRIO_STATIC: PSE static port priority. Port priority
+ * based on the power requested during PD classification. This mode
+ * is managed by the PSE core.
+ * @ETHTOOL_PSE_PORT_PRIO_DYNAMIC: PSE dynamic port priority. Port priority
+ * based on the current consumption per ports compared to the total
+ * power budget. This mode is managed by the PSE controller.
+ */
+
+enum pse_port_prio_modes {
+ ETHTOOL_PSE_PORT_PRIO_DISABLED = 1 << 0,
+ ETHTOOL_PSE_PORT_PRIO_STATIC = 1 << 1,
+ ETHTOOL_PSE_PORT_PRIO_DYNAMIC = 1 << 2,
+};
+
+/**
+ * enum ethtool_pse_disconnection_policy - Disconnection strategies for
+ * same-priority devices when power budget is exceeded, tailored to
+ * specific priority modes.
+ *
+ * @ETHTOOL_PSE_DISCON_LRC: Disconnect least recently connected device.
+ * Relevant for: ETHTOOL_PSE_PORT_PRIO_STATIC
+ * Behavior: When multiple devices share the same priority level, the
+ * system disconnects the device that was most recently connected.
+ * Rationale: This strategy favors stability for longer-standing
+ * connections, assuming that established devices may be more critical.
+ * Use Case: Suitable for systems prioritizing stable power allocation for
+ * existing static-priority connections, making newer devices suitable
+ * candidates for disconnection if limits are exceeded.
+ * @ETHTOOL_PSE_DISCON_ROUND_ROBIN_IDX_LOWEST_FIRST: Disconnect based on port
+ * index in a round-robin manner, starting with the lowest index.
+ * Relevant for: ETHTOOL_PSE_PORT_PRIO_STATIC
+ * Behavior: Disconnects devices sequentially based on port index, starting
+ * with the lowest. If multiple disconnections are required, the process
+ * continues in ascending order.
+ * Rationale: Provides a predictable, systematic approach for
+ * static-priority devices, making it clear which device will be
+ * disconnected next if power limits are reached.
+ * Use Case: Appropriate for systems where static-priority devices are
+ * equal in role, and fairness in disconnections is prioritized.
+ *
+ * Each device can have multiple disconnection policies set as an array of
+ * priorities. When the power budget is exceeded, the policies are executed
+ * in the order defined by the user. This allows for a more nuanced and
+ * flexible approach to handling power constraints across a range of devices
+ * with similar priorities or attributes.
+ *
+ * Example Usage:
+ * Users can specify an ordered list of policies, such as starting with
+ * `PSE_DISCON_STATIC_CLASS_HIGHEST_FIRST` to prioritize based on class,
+ * followed by `PSE_DISCON_LRC` to break ties based on connection time.
+ * This ordered execution ensures that power disconnections align closely
+ * with the system’s operational requirements and priorities.
+ */
+enum ethtool_pse_disconnection_policy {
+ ETHTOOL_PSE_DISCON_LRC = 1 << 0,
+ ETHTOOL_PSE_DISCON_ROUND_ROBIN_IDX_LOWEST_FIRST = 1 << 1,
};
/**