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

Lines 1–500 of 1983.