| // 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 = kcalloc(pcdev->nr_lines, sizeof(*pcdev->pi), GFP_KERNEL); | |
| 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_net_by_id - Find net attached to the pse control 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); | |
| return ret; | |
| } | |
| /** | |
| * pse_disable_pi_prio - Disable all PIs of a given priority inside a PSE | |
| * power domain | |
| * @pcdev: a pointer to the PSE | |
| * @pw_d: a pointer to the PSE power domain | |
| * @prio: priority | |
| * | |
| * Return: 0 on success and failure value on error | |
| */ | |
| static int pse_disable_pi_prio(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 || | |
| pse_pi_is_hw_enabled(pcdev, i) <= 0) | |
| continue; | |
| ret = pse_disable_pi_pol(pcdev, i); | |
| if (ret) | |
| return ret; | |
| } | |
| return 0; | |
| } | |
| /** | |
| * pse_pi_allocate_pw_budget_static_prio - Allocate power budget for the PI | |
| * when the budget eval strategy is | |
| * static | |
| * @pcdev: a pointer to the PSE | |
| * @id: index of the PSE control | |
| * @pw_req: power requested in mW | |
| * @extack: extack for error reporting | |
| * | |
| * Allocates power using static budget evaluation strategy, where allocation | |
| * is based on PD classification. When insufficient budget is available, | |
| * lower-priority ports (higher priority numbers) are turned off first. | |
| * | |
| * Return: 0 on success and failure value on error | |
| */ | |
| 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) { | |
| if (_prio <= pi->prio) { | |
| NL_SET_ERR_MSG_FMT(extack, | |
| "PI %d: not enough power budget available", | |
| id); | |
| return -ERANGE; | |
| } | |
| ret = pse_disable_pi_prio(pcdev, pi->pw_d, _prio); | |
| if (ret < 0) | |
| return ret; | |
| _prio--; | |
| } | |
| pi->pw_allocated_mW = pw_req; | |
| return 0; | |
| } | |
| /** | |
| * pse_pi_allocate_pw_budget - Allocate power budget for the PI | |
| * @pcdev: a pointer to the PSE | |
| * @id: index of the PSE control | |
| * @pw_req: power requested in mW | |
| * @extack: extack for error reporting | |
| * | |
| * Return: 0 on success and failure value on error | |
| */ | |
| 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]; | |
| if (!pi->pw_d) | |
| return 0; | |
| /* PSE_BUDGET_EVAL_STRAT_STATIC */ | |
| if (pi->pw_d->budget_eval_strategy == PSE_BUDGET_EVAL_STRAT_STATIC) | |
| return pse_pi_allocate_pw_budget_static_prio(pcdev, id, pw_req, | |
| extack); | |
| return 0; | |
| } | |
| /** | |
| * _pse_pi_delivery_power_sw_pw_ctrl - Enable PSE PI in case of software power | |
| * control. Assumes the PSE lock has been | |
| * acquired. | |
| * @pcdev: a pointer to the PSE | |
| * @id: index of the PSE control | |
| * @extack: extack for error reporting | |
| * | |
| * Return: 0 on success and failure value on error | |
| */ | |
| static int _pse_pi_delivery_power_sw_pw_ctrl(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_get_pw_req) { | |
| /* No power allocation management */ | |
| ret = ops->pi_enable(pcdev, id); | |
| if (ret) | |
| NL_SET_ERR_MSG_FMT(extack, | |
| "PI %d: enable error %d", | |
| id, ret); | |
| return ret; | |
| } | |
| 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_pw_limit) { | |
| ret = ops->pi_get_pw_limit(pcdev, id); | |
| if (ret < 0) | |
| return ret; | |
| if (ret < pw_req) | |
| pw_req = ret; | |
| } | |
| ret = pse_pi_allocate_pw_budget(pcdev, id, pw_req, extack); | |
| if (ret) | |
| return ret; | |
| 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; | |
| } | |
| return 0; | |
| } | |
| 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 = 0; | |
| ops = pcdev->ops; | |
| if (!ops->pi_enable) | |
| 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)) { | |
| /* Manage enabled status by software. | |
| * Real enable process will happen if a port is connected. | |
| */ | |
| if (pcdev->pi[id].isr_pd_detected) { | |
| struct netlink_ext_ack extack; | |
| ret = _pse_pi_delivery_power_sw_pw_ctrl(pcdev, id, &extack); | |
| } | |
| if (!ret || ret == -ERANGE) { | |
| pcdev->pi[id].admin_state_enabled = 1; | |
| ret = 0; | |
| } | |
| mutex_unlock(&pcdev->lock); | |
| return ret; | |
| } | |
| ret = ops->pi_enable(pcdev, id); | |
| if (!ret) | |
| pcdev->pi[id].admin_state_enabled = 1; | |
| mutex_unlock(&pcdev->lock); | |
| return ret; | |
| } | |
| static int pse_pi_disable(struct regulator_dev *rdev) | |
| { | |
| struct pse_controller_dev *pcdev = rdev_get_drvdata(rdev); | |
| struct pse_pi *pi; | |
| int id, ret; | |
| id = rdev_get_id(rdev); | |
| pi = &pcdev->pi[id]; | |
| mutex_lock(&pcdev->lock); | |
| ret = _pse_pi_disable(pcdev, id); | |
| if (!ret) | |
| pi->admin_state_enabled = 0; | |
| mutex_unlock(&pcdev->lock); | |
| return 0; | |
| } | |
| static int _pse_pi_get_voltage(struct regulator_dev *rdev) | |
| { | |
| struct pse_controller_dev *pcdev = rdev_get_drvdata(rdev); | |
| const struct pse_controller_ops *ops; | |
| int id; | |
| ops = pcdev->ops; | |
| if (!ops->pi_get_voltage) | |
| return -EOPNOTSUPP; | |
| id = rdev_get_id(rdev); | |
| return ops->pi_get_voltage(pcdev, id); | |
| } | |
| static int pse_pi_get_voltage(struct regulator_dev *rdev) | |
| { | |
| struct pse_controller_dev *pcdev = rdev_get_drvdata(rdev); | |
| int ret; | |
| mutex_lock(&pcdev->lock); | |
| ret = _pse_pi_get_voltage(rdev); | |
| mutex_unlock(&pcdev->lock); | |
| return ret; | |
| } | |
| static int pse_pi_get_current_limit(struct regulator_dev *rdev) | |
| { | |
| struct pse_controller_dev *pcdev = rdev_get_drvdata(rdev); | |
| const struct pse_controller_ops *ops; | |
| int id, uV, mW, ret; | |
| s64 tmp_64; | |
| ops = pcdev->ops; | |
| id = rdev_get_id(rdev); | |
| if (!ops->pi_get_pw_limit || !ops->pi_get_voltage) | |
| return -EOPNOTSUPP; | |
| mutex_lock(&pcdev->lock); | |
| ret = ops->pi_get_pw_limit(pcdev, id); | |
| if (ret < 0) | |
| goto out; | |
| mW = ret; | |
| ret = _pse_pi_get_voltage(rdev); | |
| if (!ret) { | |
| dev_err(pcdev->dev, "Voltage null\n"); | |
| ret = -ERANGE; | |
| goto out; | |
| } | |
| if (ret < 0) | |
| goto out; | |
| uV = ret; | |
| tmp_64 = mW; | |
| tmp_64 *= 1000000000ull; | |
| /* uA = mW * 1000000000 / uV */ | |
| ret = DIV_ROUND_CLOSEST_ULL(tmp_64, uV); | |
| out: | |
| mutex_unlock(&pcdev->lock); | |
| return ret; | |
| } | |
| static int pse_pi_set_current_limit(struct regulator_dev *rdev, int min_uA, | |
| int max_uA) | |
| { | |
| struct pse_controller_dev *pcdev = rdev_get_drvdata(rdev); | |
| const struct pse_controller_ops *ops; | |
| int id, mW, ret; | |
| s64 tmp_64; | |
| ops = pcdev->ops; | |
| if (!ops->pi_set_pw_limit || !ops->pi_get_voltage) | |
| return -EOPNOTSUPP; | |
| if (max_uA > MAX_PI_CURRENT) | |
| return -ERANGE; | |
| id = rdev_get_id(rdev); | |
| mutex_lock(&pcdev->lock); | |
| ret = _pse_pi_get_voltage(rdev); | |
| if (!ret) { | |
| dev_err(pcdev->dev, "Voltage null\n"); | |
| ret = -ERANGE; | |
| goto out; | |
| } | |
| if (ret < 0) | |
| goto out; | |
| tmp_64 = ret; | |
| tmp_64 *= max_uA; | |
| /* mW = uA * uV / 1000000000 */ | |
| mW = DIV_ROUND_CLOSEST_ULL(tmp_64, 1000000000); | |
| ret = ops->pi_set_pw_limit(pcdev, id, mW); | |
| out: | |
| mutex_unlock(&pcdev->lock); | |
| return ret; | |
| } | |
| static const struct regulator_ops pse_pi_ops = { | |
| .is_enabled = pse_pi_is_enabled, | |
| .enable = pse_pi_enable, | |
| .disable = pse_pi_disable, | |
| .get_voltage = pse_pi_get_voltage, | |
| .get_current_limit = pse_pi_get_current_limit, | |
| .set_current_limit = pse_pi_set_current_limit, | |
| }; | |
| static int | |
| devm_pse_pi_regulator_register(struct pse_controller_dev *pcdev, | |
| char *name, int id) | |
| { | |
| struct regulator_init_data *rinit_data; | |
| struct regulator_config rconfig = {0}; | |
| struct regulator_desc *rdesc; | |
| struct regulator_dev *rdev; | |
| rinit_data = devm_kzalloc(pcdev->dev, sizeof(*rinit_data), | |
| GFP_KERNEL); | |
| if (!rinit_data) | |
| return -ENOMEM; | |
| rdesc = devm_kzalloc(pcdev->dev, sizeof(*rdesc), GFP_KERNEL); | |
| if (!rdesc) | |
| return -ENOMEM; | |
| /* Regulator descriptor id have to be the same as its associated | |
| * PSE PI id for the well functioning of the PSE controls. | |
| */ | |
| rdesc->id = id; | |
| rdesc->name = name; | |
| rdesc->type = REGULATOR_VOLTAGE; | |
| rdesc->ops = &pse_pi_ops; | |
| rdesc->owner = pcdev->owner; | |
| rinit_data->constraints.valid_ops_mask = REGULATOR_CHANGE_STATUS; | |
| if (pcdev->ops->pi_set_pw_limit) | |
| rinit_data->constraints.valid_ops_mask |= | |
| REGULATOR_CHANGE_CURRENT; | |
| rinit_data->supply_regulator = "vpwr"; | |
| rconfig.dev = pcdev->dev; | |
| rconfig.driver_data = pcdev; | |
| rconfig.init_data = rinit_data; | |
| rconfig.of_node = pcdev->pi[id].np; | |
| rdev = devm_regulator_register(pcdev->dev, rdesc, &rconfig); | |
| if (IS_ERR(rdev)) { | |
| dev_err_probe(pcdev->dev, PTR_ERR(rdev), | |
| "Failed to register regulator\n"); | |
| return PTR_ERR(rdev); | |
| } | |
| pcdev->pi[id].rdev = rdev; | |
| return 0; | |
| } | |
| static void __pse_pw_d_release(struct kref *kref) | |
| { | |
| struct pse_power_domain *pw_d = container_of(kref, | |
| struct pse_power_domain, | |
| refcnt); | |
| regulator_put(pw_d->supply); | |
| xa_erase(&pse_pw_d_map, pw_d->id); | |
| mutex_unlock(&pse_pw_d_mutex); | |
| } | |
| /** | |
| * pse_flush_pw_ds - flush all PSE power domains of a PSE | |
| * @pcdev: a pointer to the initialized PSE controller device | |
| */ | |
| static void pse_flush_pw_ds(struct pse_controller_dev *pcdev) | |
| { | |
| struct pse_power_domain *pw_d; | |
| int i; | |
| for (i = 0; i < pcdev->nr_lines; i++) { | |
| if (!pcdev->pi[i].pw_d) | |
| continue; | |
| pw_d = xa_load(&pse_pw_d_map, pcdev->pi[i].pw_d->id); | |
| if (!pw_d) | |
| continue; | |
| kref_put_mutex(&pw_d->refcnt, __pse_pw_d_release, | |
| &pse_pw_d_mutex); | |
| } | |
| } | |
| /** | |
| * devm_pse_alloc_pw_d - allocate a new PSE power domain for a device | |
| * @dev: device that is registering this PSE power domain | |
| * | |
| * Return: Pointer to the newly allocated PSE power domain or error pointers | |
| */ | |
| static struct pse_power_domain *devm_pse_alloc_pw_d(struct device *dev) | |
| { | |
| struct pse_power_domain *pw_d; | |
| int index, ret; | |
| pw_d = devm_kzalloc(dev, sizeof(*pw_d), GFP_KERNEL); | |
| if (!pw_d) | |
| return ERR_PTR(-ENOMEM); | |
| ret = xa_alloc(&pse_pw_d_map, &index, pw_d, XA_LIMIT(1, PSE_PW_D_LIMIT), | |
| GFP_KERNEL); | |
| if (ret) | |
| return ERR_PTR(ret); | |
| kref_init(&pw_d->refcnt); | |
| pw_d->id = index; | |
| return pw_d; | |
| } | |
| /** | |
| * pse_register_pw_ds - register the PSE power domains for a PSE | |
| * @pcdev: a pointer to the PSE controller device | |
| * | |
| * Return: 0 on success and failure value on error | |
| */ | |
| static int pse_register_pw_ds(struct pse_controller_dev *pcdev) | |
| { | |
| int i, ret = 0; | |
| mutex_lock(&pse_pw_d_mutex); | |
| for (i = 0; i < pcdev->nr_lines; i++) { | |
| struct regulator_dev *rdev = pcdev->pi[i].rdev; | |
| struct pse_power_domain *pw_d; | |
| struct regulator *supply; | |
| bool present = false; | |
| unsigned long index; | |
| /* No regulator or regulator parent supply registered. | |
| * We need a regulator parent to register a PSE power domain | |
| */ | |
| if (!rdev || !rdev->supply) | |
| continue; | |
| xa_for_each(&pse_pw_d_map, index, pw_d) { | |
| /* Power supply already registered as a PSE power | |
| * domain. | |
| */ | |
| if (regulator_is_equal(pw_d->supply, rdev->supply)) { | |
| present = true; | |
| pcdev->pi[i].pw_d = pw_d; | |
| break; | |
| } | |
| } | |
| if (present) { | |
| kref_get(&pw_d->refcnt); | |
| continue; | |
| } | |
| pw_d = devm_pse_alloc_pw_d(pcdev->dev); | |
| if (IS_ERR(pw_d)) { | |
| ret = PTR_ERR(pw_d); | |
| goto out; | |
| } | |
| supply = regulator_get(&rdev->dev, rdev->supply_name); | |
| if (IS_ERR(supply)) { | |
| xa_erase(&pse_pw_d_map, pw_d->id); | |
| ret = PTR_ERR(supply); | |
| goto out; | |
| } | |
| pw_d->supply = supply; | |
| if (pcdev->supp_budget_eval_strategies) | |
| pw_d->budget_eval_strategy = pcdev->supp_budget_eval_strategies; | |
| else | |
| pw_d->budget_eval_strategy = PSE_BUDGET_EVAL_STRAT_DISABLED; | |
| kref_init(&pw_d->refcnt); | |
| pcdev->pi[i].pw_d = pw_d; | |
| } | |
| out: | |
| mutex_unlock(&pse_pw_d_mutex); | |
| return ret; | |
| } | |
| /** | |
| * pse_send_ntf_worker - Worker to send PSE notifications | |
| * @work: work object | |
| * | |
| * Manage and send PSE netlink notifications using a workqueue to avoid | |
| * deadlock between pcdev_lock and pse_list_mutex. | |
| */ | |
| static void pse_send_ntf_worker(struct work_struct *work) | |
| { | |
| struct pse_controller_dev *pcdev; | |
| struct pse_ntf ntf; | |
| pcdev = container_of(work, struct pse_controller_dev, ntf_work); | |
| while (kfifo_out(&pcdev->ntf_fifo, &ntf, 1)) { | |
| struct net_device *netdev; | |
| struct pse_control *psec; | |
| psec = pse_control_find_by_id(pcdev, ntf.id); | |
| rtnl_lock(); | |
| netdev = pse_control_get_netdev(psec); | |
| if (netdev) | |
| ethnl_pse_send_ntf(netdev, ntf.notifs); | |
| rtnl_unlock(); | |
| pse_control_put(psec); | |
| } | |
| } | |
| /** | |
| * pse_controller_register - register a PSE controller device | |
| * @pcdev: a pointer to the initialized PSE controller device | |
| * | |
| * Return: 0 on success and failure value on error | |
| */ | |
| int pse_controller_register(struct pse_controller_dev *pcdev) | |
| { | |
| size_t reg_name_len; | |
| int ret, i; | |
| mutex_init(&pcdev->lock); | |
| INIT_LIST_HEAD(&pcdev->pse_control_head); | |
| spin_lock_init(&pcdev->ntf_fifo_lock); | |
| ret = kfifo_alloc(&pcdev->ntf_fifo, pcdev->nr_lines, GFP_KERNEL); | |
| if (ret) { | |
| dev_err(pcdev->dev, "failed to allocate kfifo notifications\n"); | |
| return ret; | |
| } | |
| INIT_WORK(&pcdev->ntf_work, pse_send_ntf_worker); | |
| if (!pcdev->nr_lines) | |
| pcdev->nr_lines = 1; | |
| if (!pcdev->ops->pi_get_admin_state || | |
| !pcdev->ops->pi_get_pw_status) { | |
| dev_err(pcdev->dev, | |
| "Mandatory status report callbacks are missing"); | |
| return -EINVAL; | |
| } | |
| ret = of_load_pse_pis(pcdev); | |
| if (ret) | |
| return ret; | |
| if (pcdev->ops->setup_pi_matrix) { | |
| ret = pcdev->ops->setup_pi_matrix(pcdev); | |
| if (ret) | |
| return ret; | |
| } | |
| /* Each regulator name len is pcdev dev name + 7 char + | |
| * int max digit number (10) + 1 | |
| */ | |
| reg_name_len = strlen(dev_name(pcdev->dev)) + 18; | |
| /* Register PI regulators */ | |
| for (i = 0; i < pcdev->nr_lines; i++) { | |
| char *reg_name; | |
| /* Do not register regulator for PIs not described */ | |
| if (!pcdev->no_of_pse_pi && !pcdev->pi[i].np) | |
| continue; | |
| reg_name = devm_kzalloc(pcdev->dev, reg_name_len, GFP_KERNEL); | |
| if (!reg_name) | |
| return -ENOMEM; | |
| snprintf(reg_name, reg_name_len, "pse-%s_pi%d", | |
| dev_name(pcdev->dev), i); | |
| ret = devm_pse_pi_regulator_register(pcdev, reg_name, i); | |
| if (ret) | |
| return ret; | |
| } | |
| ret = pse_register_pw_ds(pcdev); | |
| if (ret) | |
| return ret; | |
| mutex_lock(&pse_list_mutex); | |
| list_add(&pcdev->list, &pse_controller_list); | |
| mutex_unlock(&pse_list_mutex); | |
| return 0; | |
| } | |
| EXPORT_SYMBOL_GPL(pse_controller_register); | |
| /** | |
| * pse_controller_unregister - unregister a PSE controller device | |
| * @pcdev: a pointer to the PSE controller device | |
| */ | |
| void pse_controller_unregister(struct pse_controller_dev *pcdev) | |
| { | |
| pse_flush_pw_ds(pcdev); | |
| pse_release_pis(pcdev); | |
| if (pcdev->irq) | |
| disable_irq(pcdev->irq); | |
| cancel_work_sync(&pcdev->ntf_work); | |
| kfifo_free(&pcdev->ntf_fifo); | |
| mutex_lock(&pse_list_mutex); | |
| list_del(&pcdev->list); | |
| mutex_unlock(&pse_list_mutex); | |
| } | |
| EXPORT_SYMBOL_GPL(pse_controller_unregister); | |
| static void devm_pse_controller_release(struct device *dev, void *res) | |
| { | |
| pse_controller_unregister(*(struct pse_controller_dev **)res); | |
| } | |
| /** | |
| * devm_pse_controller_register - resource managed pse_controller_register() | |
| * @dev: device that is registering this PSE controller | |
| * @pcdev: a pointer to the initialized PSE controller device | |
| * | |
| * Managed pse_controller_register(). For PSE controllers registered by | |
| * this function, pse_controller_unregister() is automatically called on | |
| * driver detach. See pse_controller_register() for more information. | |
| * | |
| * Return: 0 on success and failure value on error | |
| */ | |
| int devm_pse_controller_register(struct device *dev, | |
| struct pse_controller_dev *pcdev) | |
| { | |
| struct pse_controller_dev **pcdevp; | |
| int ret; | |
| pcdevp = devres_alloc(devm_pse_controller_release, sizeof(*pcdevp), | |
| GFP_KERNEL); | |
| if (!pcdevp) | |
| return -ENOMEM; | |
| ret = pse_controller_register(pcdev); | |
| if (ret) { | |
| devres_free(pcdevp); | |
| return ret; | |
| } | |
| *pcdevp = pcdev; | |
| devres_add(dev, pcdevp); | |
| return 0; | |
| } | |
| EXPORT_SYMBOL_GPL(devm_pse_controller_register); | |
| struct pse_irq { | |
| struct pse_controller_dev *pcdev; | |
| struct pse_irq_desc desc; | |
| unsigned long *notifs; | |
| }; | |
| /** | |
| * pse_to_regulator_notifs - Convert PSE notifications to Regulator | |
| * notifications | |
| * @notifs: PSE notifications | |
| * | |
| * Return: Regulator notifications | |
| */ | |
| static unsigned long pse_to_regulator_notifs(unsigned long notifs) | |
| { | |
| unsigned long rnotifs = 0; | |
| if (notifs & ETHTOOL_PSE_EVENT_OVER_CURRENT) | |
| rnotifs |= REGULATOR_EVENT_OVER_CURRENT; | |
| if (notifs & ETHTOOL_PSE_EVENT_OVER_TEMP) | |
| rnotifs |= REGULATOR_EVENT_OVER_TEMP; | |
| return rnotifs; | |
| } | |
| /** | |
| * pse_set_config_isr - Set PSE control config according to the PSE | |
| * notifications | |
| * @pcdev: a pointer to the PSE | |
| * @id: index of the PSE control | |
| * @notifs: PSE event notifications | |
| * | |
| * Return: 0 on success and failure value on error | |
| */ | |
| static int pse_set_config_isr(struct pse_controller_dev *pcdev, int id, | |
| unsigned long notifs) | |
| { | |
| int ret = 0; | |
| if (notifs & PSE_BUDGET_EVAL_STRAT_DYNAMIC) | |
| return 0; | |
| if ((notifs & ETHTOOL_C33_PSE_EVENT_DISCONNECTION) && | |
| ((notifs & ETHTOOL_C33_PSE_EVENT_DETECTION) || | |
| (notifs & ETHTOOL_C33_PSE_EVENT_CLASSIFICATION))) { | |
| dev_dbg(pcdev->dev, | |
| "PI %d: error, connection and disconnection reported simultaneously", | |
| id); | |
| return -EINVAL; | |
| } | |
| if (notifs & ETHTOOL_C33_PSE_EVENT_CLASSIFICATION) { | |
| struct netlink_ext_ack extack; | |
| pcdev->pi[id].isr_pd_detected = true; | |
| if (pcdev->pi[id].admin_state_enabled) { | |
| ret = _pse_pi_delivery_power_sw_pw_ctrl(pcdev, id, | |
| &extack); | |
| if (ret == -ERANGE) | |
| ret = 0; | |
| } | |
| } else if (notifs & ETHTOOL_C33_PSE_EVENT_DISCONNECTION) { | |
| if (pcdev->pi[id].admin_state_enabled && | |
| pcdev->pi[id].isr_pd_detected) | |
| ret = _pse_pi_disable(pcdev, id); | |
| pcdev->pi[id].isr_pd_detected = false; | |
| } | |
| return ret; | |
| } | |
| /** | |
| * pse_isr - IRQ handler for PSE | |
| * @irq: irq number | |
| * @data: pointer to user interrupt structure | |
| * | |
| * Return: irqreturn_t - status of IRQ | |
| */ | |
| static irqreturn_t pse_isr(int irq, void *data) | |
| { | |
| struct pse_controller_dev *pcdev; | |
| unsigned long notifs_mask = 0; | |
| struct pse_irq_desc *desc; | |
| struct pse_irq *h = data; | |
| int ret, i; | |
| desc = &h->desc; | |
| pcdev = h->pcdev; | |
| /* Clear notifs mask */ | |
| memset(h->notifs, 0, pcdev->nr_lines * sizeof(*h->notifs)); | |
| mutex_lock(&pcdev->lock); | |
| ret = desc->map_event(irq, pcdev, h->notifs, ¬ifs_mask); | |
| if (ret || !notifs_mask) { | |
| mutex_unlock(&pcdev->lock); | |
| return IRQ_NONE; | |
| } | |
| for_each_set_bit(i, ¬ifs_mask, pcdev->nr_lines) { | |
| unsigned long notifs, rnotifs; | |
| struct pse_ntf ntf = {}; | |
| /* Do nothing PI not described */ | |
| if (!pcdev->pi[i].rdev) | |
| 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); | |
| if (ret) | |
| notifs |= ETHTOOL_PSE_EVENT_SW_PW_CONTROL_ERROR; | |
| } | |
| dev_dbg(h->pcdev->dev, | |
| "Sending PSE notification EVT 0x%lx\n", notifs); | |
| ntf.notifs = notifs; | |
| ntf.id = i; | |
| kfifo_in_spinlocked(&pcdev->ntf_fifo, &ntf, 1, | |
| &pcdev->ntf_fifo_lock); | |
| schedule_work(&pcdev->ntf_work); | |
| rnotifs = pse_to_regulator_notifs(notifs); | |
| regulator_notifier_call_chain(pcdev->pi[i].rdev, rnotifs, | |
| NULL); | |
| } | |
| mutex_unlock(&pcdev->lock); | |
| return IRQ_HANDLED; | |
| } | |
| /** | |
| * devm_pse_irq_helper - Register IRQ based PSE event notifier | |
| * @pcdev: a pointer to the PSE | |
| * @irq: the irq value to be passed to request_irq | |
| * @irq_flags: the flags to be passed to request_irq | |
| * @d: PSE interrupt description | |
| * | |
| * Return: 0 on success and errno on failure | |
| */ | |
| int devm_pse_irq_helper(struct pse_controller_dev *pcdev, int irq, | |
| int irq_flags, const struct pse_irq_desc *d) | |
| { | |
| struct device *dev = pcdev->dev; | |
| size_t irq_name_len; | |
| struct pse_irq *h; | |
| char *irq_name; | |
| int ret; | |
| if (!d || !d->map_event || !d->name) | |
| return -EINVAL; | |
| h = devm_kzalloc(dev, sizeof(*h), GFP_KERNEL); | |
| if (!h) | |
| return -ENOMEM; | |
| h->pcdev = pcdev; | |
| h->desc = *d; | |
| /* IRQ name len is pcdev dev name + 5 char + irq desc name + 1 */ | |
| irq_name_len = strlen(dev_name(pcdev->dev)) + 5 + strlen(d->name) + 1; | |
| irq_name = devm_kzalloc(dev, irq_name_len, GFP_KERNEL); | |
| if (!irq_name) | |
| return -ENOMEM; | |
| snprintf(irq_name, irq_name_len, "pse-%s:%s", dev_name(pcdev->dev), | |
| d->name); | |
| h->notifs = devm_kcalloc(dev, pcdev->nr_lines, | |
| sizeof(*h->notifs), GFP_KERNEL); | |
| if (!h->notifs) | |
| return -ENOMEM; | |
| ret = devm_request_threaded_irq(dev, irq, NULL, pse_isr, | |
| IRQF_ONESHOT | irq_flags, | |
| irq_name, h); | |
| if (ret) | |
| dev_err(pcdev->dev, "Failed to request IRQ %d\n", irq); | |
| pcdev->irq = irq; | |
| return ret; | |
| } | |
| EXPORT_SYMBOL_GPL(devm_pse_irq_helper); | |
| /* PSE control section */ | |
| static void __pse_control_release(struct kref *kref) | |
| { | |
| struct pse_control *psec = container_of(kref, struct pse_control, | |
| refcnt); | |
| lockdep_assert_held(&pse_list_mutex); | |
| if (psec->pcdev->pi[psec->id].admin_state_enabled) | |
| regulator_disable(psec->ps); | |
| regulator_put(psec->ps); | |
| module_put(psec->pcdev->owner); | |
| list_del(&psec->list); | |
| kfree(psec); | |
| } | |
| static void __pse_control_put_internal(struct pse_control *psec) | |
| { | |
| lockdep_assert_held(&pse_list_mutex); | |
| kref_put(&psec->refcnt, __pse_control_release); | |
| } | |
| /** | |
| * pse_control_put - free the PSE control | |
| * @psec: PSE control pointer | |
| */ | |
| void pse_control_put(struct pse_control *psec) | |
| { | |
| if (IS_ERR_OR_NULL(psec)) | |
| return; | |
| mutex_lock(&pse_list_mutex); | |
| __pse_control_put_internal(psec); | |
| mutex_unlock(&pse_list_mutex); | |
| } | |
| EXPORT_SYMBOL_GPL(pse_control_put); | |
| static struct pse_control * | |
| pse_control_get_internal(struct pse_controller_dev *pcdev, unsigned int index, | |
| struct phy_device *phydev) | |
| { | |
| struct pse_control *psec; | |
| int ret; | |
| lockdep_assert_held(&pse_list_mutex); | |
| list_for_each_entry(psec, &pcdev->pse_control_head, list) { | |
| if (psec->id == index) { | |
| kref_get(&psec->refcnt); | |
| return psec; | |
| } | |
| } | |
| psec = kzalloc(sizeof(*psec), GFP_KERNEL); | |
| if (!psec) | |
| return ERR_PTR(-ENOMEM); | |
| if (!try_module_get(pcdev->owner)) { | |
| ret = -ENODEV; | |
| goto free_psec; | |
| } | |
| if (!pcdev->ops->pi_get_admin_state) { | |
| ret = -EOPNOTSUPP; | |
| goto free_psec; | |
| } | |
| /* Initialize admin_state_enabled before the regulator_get. This | |
| * aims to have the right value reported in the first is_enabled | |
| * call in case of control managed by software. | |
| */ | |
| ret = pse_pi_is_hw_enabled(pcdev, index); | |
| if (ret < 0) | |
| goto free_psec; | |
| pcdev->pi[index].admin_state_enabled = ret; | |
| psec->ps = regulator_get_exclusive(pcdev->dev, | |
| rdev_get_name(pcdev->pi[index].rdev)); | |
| if (IS_ERR(psec->ps)) { | |
| ret = PTR_ERR(psec->ps); | |
| goto put_module; | |
| } | |
| psec->pcdev = pcdev; | |
| list_add(&psec->list, &pcdev->pse_control_head); | |
| psec->id = index; | |
| psec->attached_phydev = phydev; | |
| kref_init(&psec->refcnt); | |
| return psec; | |
| put_module: | |
| module_put(pcdev->owner); | |
| free_psec: | |
| kfree(psec); | |
| return ERR_PTR(ret); | |
| } | |
| /** | |
| * of_pse_match_pi - Find the PSE PI id matching the device node phandle | |
| * @pcdev: a pointer to the PSE controller device | |
| * @np: a pointer to the device node | |
| * | |
| * Return: id of the PSE PI, -EINVAL if not found | |
| */ | |
| static int of_pse_match_pi(struct pse_controller_dev *pcdev, | |
| struct device_node *np) | |
| { | |
| int i; | |
| for (i = 0; i < pcdev->nr_lines; i++) { | |
| if (pcdev->pi[i].np == np) | |
| return i; | |
| } | |
| return -EINVAL; | |
| } | |
| /** | |
| * psec_id_xlate - translate pse_spec to the PSE line number according | |
| * to the number of pse-cells in case of no pse_pi node | |
| * @pcdev: a pointer to the PSE controller device | |
| * @pse_spec: PSE line specifier as found in the device tree | |
| * | |
| * Return: 0 if #pse-cells = <0>. Return PSE line number otherwise. | |
| */ | |
| static int psec_id_xlate(struct pse_controller_dev *pcdev, | |
| const struct of_phandle_args *pse_spec) | |
| { | |
| if (!pcdev->of_pse_n_cells) | |
| return 0; | |
| if (pcdev->of_pse_n_cells > 1 || | |
| pse_spec->args[0] >= pcdev->nr_lines) | |
| return -EINVAL; | |
| return pse_spec->args[0]; | |
| } | |
| struct pse_control *of_pse_control_get(struct device_node *node, | |
| struct phy_device *phydev) | |
| { | |
| struct pse_controller_dev *r, *pcdev; | |
| struct of_phandle_args args; | |
| struct pse_control *psec; | |
| int psec_id; | |
| int ret; | |
| if (!node) | |
| return ERR_PTR(-EINVAL); | |
| ret = of_parse_phandle_with_args(node, "pses", "#pse-cells", 0, &args); | |
| if (ret) | |
| return ERR_PTR(ret); | |
| mutex_lock(&pse_list_mutex); | |
| pcdev = NULL; | |
| list_for_each_entry(r, &pse_controller_list, list) { | |
| if (!r->no_of_pse_pi) { | |
| ret = of_pse_match_pi(r, args.np); | |
| if (ret >= 0) { | |
| pcdev = r; | |
| psec_id = ret; | |
| break; | |
| } | |
| } else if (args.np == r->dev->of_node) { | |
| pcdev = r; | |
| break; | |
| } | |
| } | |
| if (!pcdev) { | |
| psec = ERR_PTR(-EPROBE_DEFER); | |
| goto out; | |
| } | |
| if (WARN_ON(args.args_count != pcdev->of_pse_n_cells)) { | |
| psec = ERR_PTR(-EINVAL); | |
| goto out; | |
| } | |
| if (pcdev->no_of_pse_pi) { | |
| psec_id = psec_id_xlate(pcdev, &args); | |
| if (psec_id < 0) { | |
| psec = ERR_PTR(psec_id); | |
| goto out; | |
| } | |
| } | |
| /* pse_list_mutex also protects the pcdev's pse_control list */ | |
| psec = pse_control_get_internal(pcdev, psec_id, phydev); | |
| out: | |
| mutex_unlock(&pse_list_mutex); | |
| of_node_put(args.np); | |
| return psec; | |
| } | |
| EXPORT_SYMBOL_GPL(of_pse_control_get); | |
| /** | |
| * pse_get_sw_admin_state - Convert the software admin state to c33 or podl | |
| * admin state value used in the standard | |
| * @psec: PSE control pointer | |
| * @admin_state: a pointer to the admin_state structure | |
| */ | |
| static void pse_get_sw_admin_state(struct pse_control *psec, | |
| struct pse_admin_state *admin_state) | |
| { | |
| struct pse_pi *pi = &psec->pcdev->pi[psec->id]; | |
| if (pse_has_podl(psec)) { | |
| if (pi->admin_state_enabled) | |
| admin_state->podl_admin_state = | |
| ETHTOOL_PODL_PSE_ADMIN_STATE_ENABLED; | |
| else | |
| admin_state->podl_admin_state = | |
| ETHTOOL_PODL_PSE_ADMIN_STATE_DISABLED; | |
| } | |
| if (pse_has_c33(psec)) { | |
| if (pi->admin_state_enabled) | |
| admin_state->c33_admin_state = | |
| ETHTOOL_C33_PSE_ADMIN_STATE_ENABLED; | |
| else | |
| admin_state->c33_admin_state = | |
| ETHTOOL_C33_PSE_ADMIN_STATE_DISABLED; | |
| } | |
| } | |
| /** | |
| * pse_ethtool_get_status - get status of PSE control | |
| * @psec: PSE control pointer | |
| * @extack: extack for reporting useful error messages | |
| * @status: struct to store PSE status | |
| * | |
| * Return: 0 on success and failure value on error | |
| */ | |
| int pse_ethtool_get_status(struct pse_control *psec, | |
| struct netlink_ext_ack *extack, | |
| struct ethtool_pse_control_status *status) | |
| { | |
| struct pse_admin_state admin_state = {0}; | |
| struct pse_pw_status pw_status = {0}; | |
| const struct pse_controller_ops *ops; | |
| struct pse_controller_dev *pcdev; | |
| struct pse_pi *pi; | |
| int ret; | |
| pcdev = psec->pcdev; | |
| ops = pcdev->ops; | |
| pi = &pcdev->pi[psec->id]; | |
| mutex_lock(&pcdev->lock); | |
| if (pi->pw_d) { | |
| status->pw_d_id = pi->pw_d->id; | |
| if (pse_pw_d_is_sw_pw_control(pcdev, pi->pw_d)) { | |
| pse_get_sw_admin_state(psec, &admin_state); | |
| } else { | |
| ret = ops->pi_get_admin_state(pcdev, psec->id, | |
| &admin_state); | |
| if (ret) | |
| goto out; | |
| } | |
| status->podl_admin_state = admin_state.podl_admin_state; | |
| status->c33_admin_state = admin_state.c33_admin_state; | |
| switch (pi->pw_d->budget_eval_strategy) { | |
| case PSE_BUDGET_EVAL_STRAT_STATIC: | |
| status->prio_max = pcdev->nr_lines - 1; | |
| status->prio = pi->prio; | |
| break; | |
| case PSE_BUDGET_EVAL_STRAT_DYNAMIC: | |
| status->prio_max = pcdev->pis_prio_max; | |
| if (ops->pi_get_prio) { | |
| ret = ops->pi_get_prio(pcdev, psec->id); | |
| if (ret < 0) | |
| goto out; | |
| status->prio = ret; | |
| } | |
| break; | |
| default: | |
| break; | |
| } | |
| } | |
| ret = ops->pi_get_pw_status(pcdev, psec->id, &pw_status); | |
| if (ret) | |
| goto out; | |
| status->podl_pw_status = pw_status.podl_pw_status; | |
| status->c33_pw_status = pw_status.c33_pw_status; | |
| if (ops->pi_get_ext_state) { | |
| struct pse_ext_state_info ext_state_info = {0}; | |
| ret = ops->pi_get_ext_state(pcdev, psec->id, | |
| &ext_state_info); | |
| if (ret) | |
| goto out; | |
| memcpy(&status->c33_ext_state_info, | |
| &ext_state_info.c33_ext_state_info, | |
| sizeof(status->c33_ext_state_info)); | |
| } | |
| if (ops->pi_get_pw_class) { | |
| ret = ops->pi_get_pw_class(pcdev, psec->id); | |
| if (ret < 0) | |
| goto out; | |
| status->c33_pw_class = ret; | |
| } | |
| if (ops->pi_get_actual_pw) { | |
| ret = ops->pi_get_actual_pw(pcdev, psec->id); | |
| if (ret < 0) | |
| goto out; | |
| status->c33_actual_pw = ret; | |
| } | |
| if (ops->pi_get_pw_limit) { | |
| ret = ops->pi_get_pw_limit(pcdev, psec->id); | |
| if (ret < 0) | |
| goto out; | |
| status->c33_avail_pw_limit = ret; | |
| } | |
| if (ops->pi_get_pw_limit_ranges) { | |
| struct pse_pw_limit_ranges pw_limit_ranges = {0}; | |
| ret = ops->pi_get_pw_limit_ranges(pcdev, psec->id, | |
| &pw_limit_ranges); | |
| if (ret < 0) | |
| goto out; | |
| status->c33_pw_limit_ranges = | |
| pw_limit_ranges.c33_pw_limit_ranges; | |
| status->c33_pw_limit_nb_ranges = ret; | |
| } | |
| out: | |
| mutex_unlock(&psec->pcdev->lock); | |
| return ret; | |
| } | |
| EXPORT_SYMBOL_GPL(pse_ethtool_get_status); | |
| static int pse_ethtool_c33_set_config(struct pse_control *psec, | |
| const struct pse_control_config *config) | |
| { | |
| int err = 0; | |
| /* Look at admin_state_enabled status to not call regulator_enable | |
| * or regulator_disable twice creating a regulator counter mismatch | |
| */ | |
| switch (config->c33_admin_control) { | |
| 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. | |
| */ | |
| if (psec->pcdev->pi[psec->id].admin_state_enabled && | |
| !regulator_is_enabled(psec->ps)) { | |
| err = regulator_disable(psec->ps); | |
| if (err) | |
| break; | |
| } | |
| if (!psec->pcdev->pi[psec->id].admin_state_enabled) | |
| err = regulator_enable(psec->ps); | |
| break; | |
| case ETHTOOL_C33_PSE_ADMIN_STATE_DISABLED: | |
| if (psec->pcdev->pi[psec->id].admin_state_enabled) | |
| err = regulator_disable(psec->ps); | |
| break; | |
| default: | |
| err = -EOPNOTSUPP; | |
| } | |
| return err; | |
| } | |
| static int pse_ethtool_podl_set_config(struct pse_control *psec, | |
| const struct pse_control_config *config) | |
| { | |
| int err = 0; | |
| /* Look at admin_state_enabled status to not call regulator_enable | |
| * or regulator_disable twice creating a regulator counter mismatch | |
| */ | |
| switch (config->podl_admin_control) { | |
| case ETHTOOL_PODL_PSE_ADMIN_STATE_ENABLED: | |
| if (!psec->pcdev->pi[psec->id].admin_state_enabled) | |
| err = regulator_enable(psec->ps); | |
| break; | |
| case ETHTOOL_PODL_PSE_ADMIN_STATE_DISABLED: | |
| if (psec->pcdev->pi[psec->id].admin_state_enabled) | |
| err = regulator_disable(psec->ps); | |
| break; | |
| default: | |
| err = -EOPNOTSUPP; | |
| } | |
| return err; | |
| } | |
| /** | |
| * pse_ethtool_set_config - set PSE control configuration | |
| * @psec: PSE control pointer | |
| * @extack: extack for reporting useful error messages | |
| * @config: Configuration of the test to run | |
| * | |
| * Return: 0 on success and failure value on error | |
| */ | |
| int pse_ethtool_set_config(struct pse_control *psec, | |
| struct netlink_ext_ack *extack, | |
| const struct pse_control_config *config) | |
| { | |
| int err = 0; | |
| if (pse_has_c33(psec) && config->c33_admin_control) { | |
| err = pse_ethtool_c33_set_config(psec, config); | |
| if (err) | |
| return err; | |
| } | |
| if (pse_has_podl(psec) && config->podl_admin_control) | |
| err = pse_ethtool_podl_set_config(psec, config); | |
| return err; | |
| } | |
| EXPORT_SYMBOL_GPL(pse_ethtool_set_config); | |
| /** | |
| * pse_pi_update_pw_budget - Update PSE power budget allocated with new | |
| * power in mW | |
| * @pcdev: a pointer to the PSE controller device | |
| * @id: index of the PSE PI | |
| * @pw_req: power requested | |
| * @extack: extack for reporting useful error messages | |
| * | |
| * Return: Previous power allocated on success and failure value on error | |
| */ | |
| static int pse_pi_update_pw_budget(struct pse_controller_dev *pcdev, int id, | |
| const unsigned int pw_req, | |
| struct netlink_ext_ack *extack) | |
| { | |
| struct pse_pi *pi = &pcdev->pi[id]; | |
| int previous_pw_allocated; | |
| int pw_diff, ret = 0; | |
| /* We don't want pw_allocated_mW value change in the middle of an | |
| * power budget update | |
| */ | |
| mutex_lock(&pcdev->lock); | |
| previous_pw_allocated = pi->pw_allocated_mW; | |
| pw_diff = pw_req - previous_pw_allocated; | |
| if (!pw_diff) { | |
| goto out; | |
| } 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); | |
| goto out; | |
| } | |
| } else { | |
| regulator_free_power_budget(pi->pw_d->supply, -pw_diff); | |
| } | |
| pi->pw_allocated_mW = pw_req; | |
| ret = previous_pw_allocated; | |
| out: | |
| mutex_unlock(&pcdev->lock); | |
| return ret; | |
| } | |
| /** | |
| * pse_ethtool_set_pw_limit - set PSE control power limit | |
| * @psec: PSE control pointer | |
| * @extack: extack for reporting useful error messages | |
| * @pw_limit: power limit value in mW | |
| * | |
| * Return: 0 on success and failure value on error | |
| */ | |
| int pse_ethtool_set_pw_limit(struct pse_control *psec, | |
| struct netlink_ext_ack *extack, | |
| const unsigned int pw_limit) | |
| { | |
| int uV, uA, ret, previous_pw_allocated = 0; | |
| s64 tmp_64; | |
| if (pw_limit > MAX_PI_PW) | |
| return -ERANGE; | |
| ret = regulator_get_voltage(psec->ps); | |
| if (!ret) { | |
| NL_SET_ERR_MSG(extack, | |
| "Can't calculate the current, PSE voltage read is 0"); | |
| return -ERANGE; | |
| } | |
| if (ret < 0) { | |
| NL_SET_ERR_MSG(extack, | |
| "Error reading PSE voltage"); | |
| return ret; | |
| } | |
| uV = ret; | |
| tmp_64 = pw_limit; | |
| tmp_64 *= 1000000000ull; | |
| /* uA = mW * 1000000000 / uV */ | |
| uA = DIV_ROUND_CLOSEST_ULL(tmp_64, uV); | |
| /* Update power budget only in software power control case and | |
| * if a Power Device is powered. | |
| */ | |
| if (pse_pw_d_is_sw_pw_control(psec->pcdev, | |
| psec->pcdev->pi[psec->id].pw_d) && | |
| psec->pcdev->pi[psec->id].admin_state_enabled && | |
| psec->pcdev->pi[psec->id].isr_pd_detected) { | |
| ret = pse_pi_update_pw_budget(psec->pcdev, psec->id, | |
| pw_limit, extack); | |
| if (ret < 0) | |
| return ret; | |
| previous_pw_allocated = ret; | |
| } | |
| ret = regulator_set_current_limit(psec->ps, 0, uA); | |
| if (ret < 0 && previous_pw_allocated) { | |
| pse_pi_update_pw_budget(psec->pcdev, psec->id, | |
| previous_pw_allocated, extack); | |
| } | |
| return ret; | |
| } | |
| EXPORT_SYMBOL_GPL(pse_ethtool_set_pw_limit); | |
| /** | |
| * pse_ethtool_set_prio - Set PSE PI priority according to the budget | |
| * evaluation strategy | |
| * @psec: PSE control pointer | |
| * @extack: extack for reporting useful error messages | |
| * @prio: priovity value | |
| * | |
| * Return: 0 on success and failure value on error | |
| */ | |
| 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->budget_eval_strategy) { | |
| case PSE_BUDGET_EVAL_STRAT_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; | |
| pse_pw_d_retry_power_delivery(pcdev, pcdev->pi[psec->id].pw_d); | |
| break; | |
| case PSE_BUDGET_EVAL_STRAT_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); | |
| bool pse_has_podl(struct pse_control *psec) | |
| { | |
| return psec->pcdev->types & ETHTOOL_PSE_PODL; | |
| } | |
| EXPORT_SYMBOL_GPL(pse_has_podl); | |
| bool pse_has_c33(struct pse_control *psec) | |
| { | |
| return psec->pcdev->types & ETHTOOL_PSE_C33; | |
| } | |
| EXPORT_SYMBOL_GPL(pse_has_c33); |