// SPDX-License-Identifier: GPL-2.0-only//// Framework for Ethernet Power Sourcing Equipment//// Copyright (c) 2022 Pengutronix, Oleksij Rempel <kernel@pengutronix.de>// #include <linux/device.h>#include <linux/ethtool.h>#include <linux/ethtool_netlink.h>#include <linux/of.h>#include <linux/phy.h>#include <linux/pse-pd/pse.h>#include <linux/regulator/driver.h>#include <linux/regulator/machine.h>#include <linux/rtnetlink.h>#include <net/net_trackers.h> #define PSE_PW_D_LIMIT INT_MAX static DEFINE_MUTEX(pse_list_mutex);static LIST_HEAD(pse_controller_list);static DEFINE_XARRAY_ALLOC(pse_pw_d_map);static DEFINE_MUTEX(pse_pw_d_mutex); /** * struct pse_control - a PSE control * @pcdev: a pointer to the PSE controller device * this PSE control belongs to * @ps: PSE PI supply of the PSE control * @list: list entry for the pcdev's PSE controller list * @id: ID of the PSE line in the PSE controller device * @refcnt: Number of gets of this pse_control * @attached_phydev: PHY device pointer attached by the PSE control */struct pse_control { struct pse_controller_dev *pcdev; struct regulator *ps; struct list_head list; unsigned int id; struct kref refcnt; struct phy_device *attached_phydev;}; /** * struct pse_power_domain - a PSE power domain * @id: ID of the power domain * @supply: Power supply the Power Domain * @refcnt: Number of gets of this pse_power_domain * @budget_eval_strategy: Current power budget evaluation strategy of the * power domain */struct pse_power_domain { int id; struct regulator *supply; struct kref refcnt; u32 budget_eval_strategy;}; static int of_load_single_pse_pi_pairset(struct device_node *node, struct pse_pi *pi, int pairset_num){ struct device_node *pairset_np; const char *name; int ret; ret = of_property_read_string_index(node, "pairset-names", pairset_num, &name); if (ret) return ret; if (!strcmp(name, "alternative-a")) { pi->pairset[pairset_num].pinout = ALTERNATIVE_A; } else if (!strcmp(name, "alternative-b")) { pi->pairset[pairset_num].pinout = ALTERNATIVE_B; } else { pr_err("pse: wrong pairset-names value %s (%pOF)\n", name, node); return -EINVAL; } pairset_np = of_parse_phandle(node, "pairsets", pairset_num); if (!pairset_np) return -ENODEV; pi->pairset[pairset_num].np = pairset_np; return 0;} /** * of_load_pse_pi_pairsets - load PSE PI pairsets pinout and polarity * @node: a pointer of the device node * @pi: a pointer of the PSE PI to fill * @npairsets: the number of pairsets (1 or 2) used by the PI * * Return: 0 on success and failure value on error */static int of_load_pse_pi_pairsets(struct device_node *node, struct pse_pi *pi, int npairsets){ int i, ret; ret = of_property_count_strings(node, "pairset-names"); if (ret != npairsets) { pr_err("pse: amount of pairsets and pairset-names is not equal %d != %d (%pOF)\n", npairsets, ret, node); return -EINVAL; } for (i = 0; i < npairsets; i++) { ret = of_load_single_pse_pi_pairset(node, pi, i); if (ret) goto out; } if (npairsets == 2 && pi->pairset[0].pinout == pi->pairset[1].pinout) { pr_err("pse: two PI pairsets can not have identical pinout (%pOF)", node); ret = -EINVAL; } out: /* If an error appears, release all the pairset device node kref */ if (ret) { of_node_put(pi->pairset[0].np); pi->pairset[0].np = NULL; of_node_put(pi->pairset[1].np); pi->pairset[1].np = NULL; } return ret;} static void pse_release_pis(struct pse_controller_dev *pcdev){ int i; for (i = 0; i < pcdev->nr_lines; i++) { of_node_put(pcdev->pi[i].pairset[0].np); of_node_put(pcdev->pi[i].pairset[1].np); of_node_put(pcdev->pi[i].np); } kfree(pcdev->pi);} /** * of_load_pse_pis - load all the PSE PIs * @pcdev: a pointer to the PSE controller device * * Return: 0 on success and failure value on error */static int of_load_pse_pis(struct pse_controller_dev *pcdev){ struct device_node *np = pcdev->dev->of_node; struct device_node *node, *pis; int ret; if (!np) return -ENODEV; pcdev->pi = kzalloc_objs(*pcdev->pi, pcdev->nr_lines); if (!pcdev->pi) return -ENOMEM; pis = of_get_child_by_name(np, "pse-pis"); if (!pis) { /* no description of PSE PIs */ pcdev->no_of_pse_pi = true; return 0; } for_each_child_of_node(pis, node) { struct pse_pi pi = {0}; u32 id; if (!of_node_name_eq(node, "pse-pi")) continue; ret = of_property_read_u32(node, "reg", &id); if (ret) { dev_err(pcdev->dev, "can't get reg property for node '%pOF'", node); goto out; } if (id >= pcdev->nr_lines) { dev_err(pcdev->dev, "reg value (%u) is out of range (%u) (%pOF)\n", id, pcdev->nr_lines, node); ret = -EINVAL; goto out; } if (pcdev->pi[id].np) { dev_err(pcdev->dev, "other node with same reg value was already registered. %pOF : %pOF\n", pcdev->pi[id].np, node); ret = -EINVAL; goto out; } ret = of_count_phandle_with_args(node, "pairsets", NULL); /* npairsets is limited to value one or two */ if (ret == 1 || ret == 2) { ret = of_load_pse_pi_pairsets(node, &pi, ret); if (ret) goto out; } else if (ret != -ENOENT) { dev_err(pcdev->dev, "error: wrong number of pairsets. Should be 1 or 2, got %d (%pOF)\n", ret, node); ret = -EINVAL; goto out; } of_node_get(node); pi.np = node; memcpy(&pcdev->pi[id], &pi, sizeof(pi)); } of_node_put(pis); return 0; out: pse_release_pis(pcdev); of_node_put(node); of_node_put(pis); return ret;} /** * pse_control_find_by_id - Find pse_control from an id * @pcdev: a pointer to the PSE * @id: index of the PSE control * * Return: pse_control pointer or NULL. The device returned has had a * reference added and the pointer is safe until the user calls * pse_control_put() to indicate they have finished with it. */static struct pse_control *pse_control_find_by_id(struct pse_controller_dev *pcdev, int id){ struct pse_control *psec; mutex_lock(&pse_list_mutex); list_for_each_entry(psec, &pcdev->pse_control_head, list) { if (psec->id == id) { kref_get(&psec->refcnt); mutex_unlock(&pse_list_mutex); return psec; } } mutex_unlock(&pse_list_mutex); return NULL;} /** * pse_control_get_netdev - Return netdev associated to a PSE control * @psec: PSE control pointer * * Return: netdev pointer or NULL */static struct net_device *pse_control_get_netdev(struct pse_control *psec){ ASSERT_RTNL(); if (!psec || !psec->attached_phydev) return NULL; return psec->attached_phydev->attached_dev;} /** * pse_pi_is_hw_enabled - Is PI enabled at the hardware level * @pcdev: a pointer to the PSE controller device * @id: Index of the PI * * Return: 1 if the PI is enabled at the hardware level, 0 if not, and * a failure value on error */static int pse_pi_is_hw_enabled(struct pse_controller_dev *pcdev, int id){ struct pse_admin_state admin_state = {0}; int ret; ret = pcdev->ops->pi_get_admin_state(pcdev, id, &admin_state); if (ret < 0) return ret; /* PI is well enabled at the hardware level */ if (admin_state.podl_admin_state == ETHTOOL_PODL_PSE_ADMIN_STATE_ENABLED || admin_state.c33_admin_state == ETHTOOL_C33_PSE_ADMIN_STATE_ENABLED) return 1; return 0;} /** * pse_pi_is_admin_enable_pending - Check if PI is in admin enable pending state * which mean the power is not yet being * delivered * @pcdev: a pointer to the PSE controller device * @id: Index of the PI * * Detects if a PI is enabled in software with a PD detected, but the hardware * admin state hasn't been applied yet. * * This function is used in the power delivery and retry mechanisms to determine * which PIs need to have power delivery attempted again. * * Return: true if the PI has admin enable flag set in software but not yet * reflected in the hardware admin state, false otherwise. */static boolpse_pi_is_admin_enable_pending(struct pse_controller_dev *pcdev, int id){ int ret; /* PI not enabled or nothing is plugged */ if (!pcdev->pi[id].admin_state_enabled || !pcdev->pi[id].isr_pd_detected) return false; ret = pse_pi_is_hw_enabled(pcdev, id); /* PSE PI is already enabled at hardware level */ if (ret == 1) return false; return true;} static int _pse_pi_delivery_power_sw_pw_ctrl(struct pse_controller_dev *pcdev, int id, struct netlink_ext_ack *extack); /** * pse_pw_d_retry_power_delivery - Retry power delivery for pending ports in a * PSE power domain * @pcdev: a pointer to the PSE controller device * @pw_d: a pointer to the PSE power domain * * Scans all ports in the specified power domain and attempts to enable power * delivery to any ports that have admin enable state set but don't yet have * hardware power enabled. Used when there are changes in connection status, * admin state, or priority that might allow previously unpowered ports to * receive power, especially in over-budget conditions. */static void pse_pw_d_retry_power_delivery(struct pse_controller_dev *pcdev, struct pse_power_domain *pw_d){ int i, ret = 0; for (i = 0; i < pcdev->nr_lines; i++) { int prio_max = pcdev->nr_lines; struct netlink_ext_ack extack; if (pcdev->pi[i].pw_d != pw_d) continue; if (!pse_pi_is_admin_enable_pending(pcdev, i)) continue; /* Do not try to enable PI with a lower prio (higher value) * than one which already can't be enabled. */ if (pcdev->pi[i].prio > prio_max) continue; ret = _pse_pi_delivery_power_sw_pw_ctrl(pcdev, i, &extack); if (ret == -ERANGE) prio_max = pcdev->pi[i].prio; }} /** * pse_pw_d_is_sw_pw_control - Determine if power control is software managed * @pcdev: a pointer to the PSE controller device * @pw_d: a pointer to the PSE power domain * * This function determines whether the power control for a specific power * domain is managed by software in the interrupt handler rather than directly * by hardware. * * Software power control is active in the following cases: * - When the budget evaluation strategy is set to static * - When the budget evaluation strategy is disabled but the PSE controller * has an interrupt handler that can report if a Powered Device is connected * * Return: true if the power control of the power domain is managed by software, * false otherwise */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->budget_eval_strategy == PSE_BUDGET_EVAL_STRAT_STATIC) return true; if (pw_d->budget_eval_strategy == PSE_BUDGET_EVAL_STRAT_DISABLED && pcdev->ops->pi_enable && 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); const struct pse_controller_ops *ops; int id, ret; ops = pcdev->ops; if (!ops->pi_get_admin_state) return -EOPNOTSUPP; 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].admin_state_enabled; goto out; } ret = pse_pi_is_hw_enabled(pcdev, id); out: mutex_unlock(&pcdev->lock); return ret;} /** * pse_pi_deallocate_pw_budget - Deallocate power budget of the PI * @pi: a pointer to the PSE PI */static void pse_pi_deallocate_pw_budget(struct pse_pi *pi){ if (!pi->pw_d || !pi->pw_allocated_mW) return; regulator_free_power_budget(pi->pw_d->supply, pi->pw_allocated_mW); pi->pw_allocated_mW = 0;} /** * _pse_pi_disable - Call disable operation. Assumes the PSE lock has been * acquired. * @pcdev: a pointer to the PSE * @id: index of the PSE control * * Return: 0 on success and failure value on error */static int _pse_pi_disable(struct pse_controller_dev *pcdev, int id){ const struct pse_controller_ops *ops = pcdev->ops; int ret; if (!ops->pi_disable) return -EOPNOTSUPP; ret = ops->pi_disable(pcdev, id); if (ret) return ret; pse_pi_deallocate_pw_budget(&pcdev->pi[id]); if (pse_pw_d_is_sw_pw_control(pcdev, pcdev->pi[id].pw_d)) pse_pw_d_retry_power_delivery(pcdev, pcdev->pi[id].pw_d); return 0;} /** * pse_disable_pi_pol - Disable a PI on a power budget policy * @pcdev: a pointer to the PSE * @id: index of the PSE PI * * Return: 0 on success and failure value on error */static int pse_disable_pi_pol(struct pse_controller_dev *pcdev, int id){ unsigned long notifs = ETHTOOL_PSE_EVENT_OVER_BUDGET; struct pse_ntf ntf = {}; int ret; dev_dbg(pcdev->dev, "Disabling PI %d to free power budget\n", id); ret = _pse_pi_disable(pcdev, id); if (ret) notifs |= ETHTOOL_PSE_EVENT_SW_PW_CONTROL_ERROR; ntf.notifs = notifs; ntf.id = id; kfifo_in_spinlocked(&pcdev->ntf_fifo, &ntf, 1, &pcdev->ntf_fifo_lock); schedule_work(&pcdev->ntf_work);Lines 1–500 of 1983. Load more
// SPDX-License-Identifier: GPL-2.0-only // // Framework for Ethernet Power Sourcing Equipment // // Copyright (c) 2022 Pengutronix, Oleksij Rempel <kernel@pengutronix.de> // #include <linux/device.h> #include <linux/ethtool.h> #include <linux/ethtool_netlink.h> #include <linux/of.h> #include <linux/phy.h> #include <linux/pse-pd/pse.h> #include <linux/regulator/driver.h> #include <linux/regulator/machine.h> #include <linux/rtnetlink.h> #include <net/net_trackers.h> #define PSE_PW_D_LIMIT INT_MAX static DEFINE_MUTEX(pse_list_mutex); static LIST_HEAD(pse_controller_list); static DEFINE_XARRAY_ALLOC(pse_pw_d_map); static DEFINE_MUTEX(pse_pw_d_mutex); /** * struct pse_control - a PSE control * @pcdev: a pointer to the PSE controller device * this PSE control belongs to * @ps: PSE PI supply of the PSE control * @list: list entry for the pcdev's PSE controller list * @id: ID of the PSE line in the PSE controller device * @refcnt: Number of gets of this pse_control * @attached_phydev: PHY device pointer attached by the PSE control */ struct pse_control { struct pse_controller_dev *pcdev; struct regulator *ps; struct list_head list; unsigned int id; struct kref refcnt; struct phy_device *attached_phydev; }; /** * struct pse_power_domain - a PSE power domain * @id: ID of the power domain * @supply: Power supply the Power Domain * @refcnt: Number of gets of this pse_power_domain * @budget_eval_strategy: Current power budget evaluation strategy of the * power domain */ struct pse_power_domain { int id; struct regulator *supply; struct kref refcnt; u32 budget_eval_strategy; }; static int of_load_single_pse_pi_pairset(struct device_node *node, struct pse_pi *pi, int pairset_num) { struct device_node *pairset_np; const char *name; int ret; ret = of_property_read_string_index(node, "pairset-names", pairset_num, &name); if (ret) return ret; if (!strcmp(name, "alternative-a")) { pi->pairset[pairset_num].pinout = ALTERNATIVE_A; } else if (!strcmp(name, "alternative-b")) { pi->pairset[pairset_num].pinout = ALTERNATIVE_B; } else { pr_err("pse: wrong pairset-names value %s (%pOF)\n", name, node); return -EINVAL; } pairset_np = of_parse_phandle(node, "pairsets", pairset_num); if (!pairset_np) return -ENODEV; pi->pairset[pairset_num].np = pairset_np; return 0; } /** * of_load_pse_pi_pairsets - load PSE PI pairsets pinout and polarity * @node: a pointer of the device node * @pi: a pointer of the PSE PI to fill * @npairsets: the number of pairsets (1 or 2) used by the PI * * Return: 0 on success and failure value on error */ static int of_load_pse_pi_pairsets(struct device_node *node, struct pse_pi *pi, int npairsets) { int i, ret; ret = of_property_count_strings(node, "pairset-names"); if (ret != npairsets) { pr_err("pse: amount of pairsets and pairset-names is not equal %d != %d (%pOF)\n", npairsets, ret, node); return -EINVAL; } for (i = 0; i < npairsets; i++) { ret = of_load_single_pse_pi_pairset(node, pi, i); if (ret) goto out; } if (npairsets == 2 && pi->pairset[0].pinout == pi->pairset[1].pinout) { pr_err("pse: two PI pairsets can not have identical pinout (%pOF)", node); ret = -EINVAL; } out: /* If an error appears, release all the pairset device node kref */ if (ret) { of_node_put(pi->pairset[0].np); pi->pairset[0].np = NULL; of_node_put(pi->pairset[1].np); pi->pairset[1].np = NULL; } return ret; } static void pse_release_pis(struct pse_controller_dev *pcdev) { int i; for (i = 0; i < pcdev->nr_lines; i++) { of_node_put(pcdev->pi[i].pairset[0].np); of_node_put(pcdev->pi[i].pairset[1].np); of_node_put(pcdev->pi[i].np); } kfree(pcdev->pi); } /** * of_load_pse_pis - load all the PSE PIs * @pcdev: a pointer to the PSE controller device * * Return: 0 on success and failure value on error */ static int of_load_pse_pis(struct pse_controller_dev *pcdev) { struct device_node *np = pcdev->dev->of_node; struct device_node *node, *pis; int ret; if (!np) return -ENODEV; pcdev->pi = kzalloc_objs(*pcdev->pi, pcdev->nr_lines); if (!pcdev->pi) return -ENOMEM; pis = of_get_child_by_name(np, "pse-pis"); if (!pis) { /* no description of PSE PIs */ pcdev->no_of_pse_pi = true; return 0; } for_each_child_of_node(pis, node) { struct pse_pi pi = {0}; u32 id; if (!of_node_name_eq(node, "pse-pi")) continue; ret = of_property_read_u32(node, "reg", &id); if (ret) { dev_err(pcdev->dev, "can't get reg property for node '%pOF'", node); goto out; } if (id >= pcdev->nr_lines) { dev_err(pcdev->dev, "reg value (%u) is out of range (%u) (%pOF)\n", id, pcdev->nr_lines, node); ret = -EINVAL; goto out; } if (pcdev->pi[id].np) { dev_err(pcdev->dev, "other node with same reg value was already registered. %pOF : %pOF\n", pcdev->pi[id].np, node); ret = -EINVAL; goto out; } ret = of_count_phandle_with_args(node, "pairsets", NULL); /* npairsets is limited to value one or two */ if (ret == 1 || ret == 2) { ret = of_load_pse_pi_pairsets(node, &pi, ret); if (ret) goto out; } else if (ret != -ENOENT) { dev_err(pcdev->dev, "error: wrong number of pairsets. Should be 1 or 2, got %d (%pOF)\n", ret, node); ret = -EINVAL; goto out; } of_node_get(node); pi.np = node; memcpy(&pcdev->pi[id], &pi, sizeof(pi)); } of_node_put(pis); return 0; out: pse_release_pis(pcdev); of_node_put(node); of_node_put(pis); return ret; } /** * pse_control_find_by_id - Find pse_control from an id * @pcdev: a pointer to the PSE * @id: index of the PSE control * * Return: pse_control pointer or NULL. The device returned has had a * reference added and the pointer is safe until the user calls * pse_control_put() to indicate they have finished with it. */ static struct pse_control * pse_control_find_by_id(struct pse_controller_dev *pcdev, int id) { struct pse_control *psec; mutex_lock(&pse_list_mutex); list_for_each_entry(psec, &pcdev->pse_control_head, list) { if (psec->id == id) { kref_get(&psec->refcnt); mutex_unlock(&pse_list_mutex); return psec; } } mutex_unlock(&pse_list_mutex); return NULL; } /** * pse_control_get_netdev - Return netdev associated to a PSE control * @psec: PSE control pointer * * Return: netdev pointer or NULL */ static struct net_device *pse_control_get_netdev(struct pse_control *psec) { ASSERT_RTNL(); if (!psec || !psec->attached_phydev) return NULL; return psec->attached_phydev->attached_dev; } /** * pse_pi_is_hw_enabled - Is PI enabled at the hardware level * @pcdev: a pointer to the PSE controller device * @id: Index of the PI * * Return: 1 if the PI is enabled at the hardware level, 0 if not, and * a failure value on error */ static int pse_pi_is_hw_enabled(struct pse_controller_dev *pcdev, int id) { struct pse_admin_state admin_state = {0}; int ret; ret = pcdev->ops->pi_get_admin_state(pcdev, id, &admin_state); if (ret < 0) return ret; /* PI is well enabled at the hardware level */ if (admin_state.podl_admin_state == ETHTOOL_PODL_PSE_ADMIN_STATE_ENABLED || admin_state.c33_admin_state == ETHTOOL_C33_PSE_ADMIN_STATE_ENABLED) return 1; return 0; } /** * pse_pi_is_admin_enable_pending - Check if PI is in admin enable pending state * which mean the power is not yet being * delivered * @pcdev: a pointer to the PSE controller device * @id: Index of the PI * * Detects if a PI is enabled in software with a PD detected, but the hardware * admin state hasn't been applied yet. * * This function is used in the power delivery and retry mechanisms to determine * which PIs need to have power delivery attempted again. * * Return: true if the PI has admin enable flag set in software but not yet * reflected in the hardware admin state, false otherwise. */ static bool pse_pi_is_admin_enable_pending(struct pse_controller_dev *pcdev, int id) { int ret; /* PI not enabled or nothing is plugged */ if (!pcdev->pi[id].admin_state_enabled || !pcdev->pi[id].isr_pd_detected) return false; ret = pse_pi_is_hw_enabled(pcdev, id); /* PSE PI is already enabled at hardware level */ if (ret == 1) return false; return true; } static int _pse_pi_delivery_power_sw_pw_ctrl(struct pse_controller_dev *pcdev, int id, struct netlink_ext_ack *extack); /** * pse_pw_d_retry_power_delivery - Retry power delivery for pending ports in a * PSE power domain * @pcdev: a pointer to the PSE controller device * @pw_d: a pointer to the PSE power domain * * Scans all ports in the specified power domain and attempts to enable power * delivery to any ports that have admin enable state set but don't yet have * hardware power enabled. Used when there are changes in connection status, * admin state, or priority that might allow previously unpowered ports to * receive power, especially in over-budget conditions. */ static void pse_pw_d_retry_power_delivery(struct pse_controller_dev *pcdev, struct pse_power_domain *pw_d) { int i, ret = 0; for (i = 0; i < pcdev->nr_lines; i++) { int prio_max = pcdev->nr_lines; struct netlink_ext_ack extack; if (pcdev->pi[i].pw_d != pw_d) continue; if (!pse_pi_is_admin_enable_pending(pcdev, i)) continue; /* Do not try to enable PI with a lower prio (higher value) * than one which already can't be enabled. */ if (pcdev->pi[i].prio > prio_max) continue; ret = _pse_pi_delivery_power_sw_pw_ctrl(pcdev, i, &extack); if (ret == -ERANGE) prio_max = pcdev->pi[i].prio; } } /** * pse_pw_d_is_sw_pw_control - Determine if power control is software managed * @pcdev: a pointer to the PSE controller device * @pw_d: a pointer to the PSE power domain * * This function determines whether the power control for a specific power * domain is managed by software in the interrupt handler rather than directly * by hardware. * * Software power control is active in the following cases: * - When the budget evaluation strategy is set to static * - When the budget evaluation strategy is disabled but the PSE controller * has an interrupt handler that can report if a Powered Device is connected * * Return: true if the power control of the power domain is managed by software, * false otherwise */ 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->budget_eval_strategy == PSE_BUDGET_EVAL_STRAT_STATIC) return true; if (pw_d->budget_eval_strategy == PSE_BUDGET_EVAL_STRAT_DISABLED && pcdev->ops->pi_enable && 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); const struct pse_controller_ops *ops; int id, ret; ops = pcdev->ops; if (!ops->pi_get_admin_state) return -EOPNOTSUPP; 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].admin_state_enabled; goto out; } ret = pse_pi_is_hw_enabled(pcdev, id); out: mutex_unlock(&pcdev->lock); return ret; } /** * pse_pi_deallocate_pw_budget - Deallocate power budget of the PI * @pi: a pointer to the PSE PI */ static void pse_pi_deallocate_pw_budget(struct pse_pi *pi) { if (!pi->pw_d || !pi->pw_allocated_mW) return; regulator_free_power_budget(pi->pw_d->supply, pi->pw_allocated_mW); pi->pw_allocated_mW = 0; } /** * _pse_pi_disable - Call disable operation. Assumes the PSE lock has been * acquired. * @pcdev: a pointer to the PSE * @id: index of the PSE control * * Return: 0 on success and failure value on error */ static int _pse_pi_disable(struct pse_controller_dev *pcdev, int id) { const struct pse_controller_ops *ops = pcdev->ops; int ret; if (!ops->pi_disable) return -EOPNOTSUPP; ret = ops->pi_disable(pcdev, id); if (ret) return ret; pse_pi_deallocate_pw_budget(&pcdev->pi[id]); if (pse_pw_d_is_sw_pw_control(pcdev, pcdev->pi[id].pw_d)) pse_pw_d_retry_power_delivery(pcdev, pcdev->pi[id].pw_d); return 0; } /** * pse_disable_pi_pol - Disable a PI on a power budget policy * @pcdev: a pointer to the PSE * @id: index of the PSE PI * * Return: 0 on success and failure value on error */ static int pse_disable_pi_pol(struct pse_controller_dev *pcdev, int id) { unsigned long notifs = ETHTOOL_PSE_EVENT_OVER_BUDGET; struct pse_ntf ntf = {}; int ret; dev_dbg(pcdev->dev, "Disabling PI %d to free power budget\n", id); ret = _pse_pi_disable(pcdev, id); if (ret) notifs |= ETHTOOL_PSE_EVENT_SW_PW_CONTROL_ERROR; ntf.notifs = notifs; ntf.id = id; kfifo_in_spinlocked(&pcdev->ntf_fifo, &ntf, 1, &pcdev->ntf_fifo_lock); schedule_work(&pcdev->ntf_work);