| // 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); |
| devm_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 = devm_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); |