xref: /linux/drivers/net/phy/phy_port.c (revision d8f87aa5fa0a4276491fa8ef436cd22605a3f9ba)
1 // SPDX-License-Identifier: GPL-2.0+
2 /* Framework to drive Ethernet ports
3  *
4  * Copyright (c) 2024 Maxime Chevallier <maxime.chevallier@bootlin.com>
5  */
6 
7 #include <linux/linkmode.h>
8 #include <linux/of.h>
9 #include <linux/phy_port.h>
10 
11 #include "phy-caps.h"
12 
13 /**
14  * phy_port_alloc() - Allocate a new phy_port
15  *
16  * Returns: a newly allocated struct phy_port, or NULL.
17  */
18 struct phy_port *phy_port_alloc(void)
19 {
20 	struct phy_port *port;
21 
22 	port = kzalloc(sizeof(*port), GFP_KERNEL);
23 	if (!port)
24 		return NULL;
25 
26 	linkmode_zero(port->supported);
27 	INIT_LIST_HEAD(&port->head);
28 
29 	return port;
30 }
31 EXPORT_SYMBOL_GPL(phy_port_alloc);
32 
33 /**
34  * phy_port_destroy() - Free a struct phy_port
35  * @port: The port to destroy
36  */
37 void phy_port_destroy(struct phy_port *port)
38 {
39 	kfree(port);
40 }
41 EXPORT_SYMBOL_GPL(phy_port_destroy);
42 
43 /**
44  * phy_of_parse_port() - Create a phy_port from a firmware representation
45  * @dn: device_node representation of the port, following the
46  *	ethernet-connector.yaml binding
47  *
48  * Returns: a newly allocated and initialized phy_port pointer, or an ERR_PTR.
49  */
50 struct phy_port *phy_of_parse_port(struct device_node *dn)
51 {
52 	struct fwnode_handle *fwnode = of_fwnode_handle(dn);
53 	enum ethtool_link_medium medium;
54 	struct phy_port *port;
55 	const char *med_str;
56 	u32 pairs = 0, mediums = 0;
57 	int ret;
58 
59 	ret = fwnode_property_read_string(fwnode, "media", &med_str);
60 	if (ret)
61 		return ERR_PTR(ret);
62 
63 	medium = ethtool_str_to_medium(med_str);
64 	if (medium == ETHTOOL_LINK_MEDIUM_NONE)
65 		return ERR_PTR(-EINVAL);
66 
67 	if (medium == ETHTOOL_LINK_MEDIUM_BASET) {
68 		ret = fwnode_property_read_u32(fwnode, "pairs", &pairs);
69 		if (ret)
70 			return ERR_PTR(ret);
71 
72 		switch (pairs) {
73 		case 1: /* BaseT1 */
74 		case 2: /* 100BaseTX */
75 		case 4:
76 			break;
77 		default:
78 			pr_err("%u is not a valid number of pairs\n", pairs);
79 			return ERR_PTR(-EINVAL);
80 		}
81 	}
82 
83 	if (pairs && medium != ETHTOOL_LINK_MEDIUM_BASET) {
84 		pr_err("pairs property is only compatible with BaseT medium\n");
85 		return ERR_PTR(-EINVAL);
86 	}
87 
88 	mediums |= BIT(medium);
89 
90 	if (!mediums)
91 		return ERR_PTR(-EINVAL);
92 
93 	port = phy_port_alloc();
94 	if (!port)
95 		return ERR_PTR(-ENOMEM);
96 
97 	port->pairs = pairs;
98 	port->mediums = mediums;
99 
100 	return port;
101 }
102 EXPORT_SYMBOL_GPL(phy_of_parse_port);
103 
104 /**
105  * phy_port_update_supported() - Setup the port->supported field
106  * @port: the port to update
107  *
108  * Once the port's medium list and number of pairs has been configured based
109  * on firmware, straps and vendor-specific properties, this function may be
110  * called to update the port's supported linkmodes list.
111  *
112  * Any mode that was manually set in the port's supported list remains set.
113  */
114 void phy_port_update_supported(struct phy_port *port)
115 {
116 	__ETHTOOL_DECLARE_LINK_MODE_MASK(supported) = { 0 };
117 	unsigned long mode;
118 	int i;
119 
120 	for_each_set_bit(i, &port->mediums, __ETHTOOL_LINK_MEDIUM_LAST) {
121 		linkmode_zero(supported);
122 		phy_caps_medium_get_supported(supported, i, port->pairs);
123 		linkmode_or(port->supported, port->supported, supported);
124 	}
125 
126 	/* If there's no pairs specified, we grab the default number of
127 	 * pairs as the max of the default pairs for each linkmode
128 	 */
129 	if (!port->pairs)
130 		for_each_set_bit(mode, port->supported,
131 				 __ETHTOOL_LINK_MODE_MASK_NBITS)
132 			port->pairs = max_t(int, port->pairs,
133 					    ethtool_linkmode_n_pairs(mode));
134 
135 	/* Serdes ports supported through SFP may not have any medium set,
136 	 * as they will output PHY_INTERFACE_MODE_XXX modes. In that case, derive
137 	 * the supported list based on these interfaces
138 	 */
139 	if (port->is_mii && !port->mediums) {
140 		unsigned long interface, link_caps = 0;
141 
142 		/* Get each interface's caps */
143 		for_each_set_bit(interface, port->interfaces,
144 				 PHY_INTERFACE_MODE_MAX)
145 			link_caps |= phy_caps_from_interface(interface);
146 
147 		phy_caps_linkmodes(link_caps, port->supported);
148 	}
149 }
150 EXPORT_SYMBOL_GPL(phy_port_update_supported);
151 
152 /**
153  * phy_port_filter_supported() - Make sure that port->supported match port->mediums
154  * @port: The port to filter
155  *
156  * After updating a port's mediums to a more restricted subset, this helper will
157  * make sure that port->supported only contains linkmodes that are compatible
158  * with port->mediums.
159  */
160 static void phy_port_filter_supported(struct phy_port *port)
161 {
162 	__ETHTOOL_DECLARE_LINK_MODE_MASK(supported) = { 0 };
163 	int i;
164 
165 	for_each_set_bit(i, &port->mediums, __ETHTOOL_LINK_MEDIUM_LAST)
166 		phy_caps_medium_get_supported(supported, i, port->pairs);
167 
168 	linkmode_and(port->supported, port->supported, supported);
169 }
170 
171 /**
172  * phy_port_restrict_mediums - Mask away some of the port's supported mediums
173  * @port: The port to act upon
174  * @mediums: A mask of mediums to support on the port
175  *
176  * This helper allows removing some mediums from a port's list of supported
177  * mediums, which occurs once we have enough information about the port to
178  * know its nature.
179  *
180  * Returns: 0 if the change was donne correctly, a negative value otherwise.
181  */
182 int phy_port_restrict_mediums(struct phy_port *port, unsigned long mediums)
183 {
184 	/* We forbid ending-up with a port with empty mediums */
185 	if (!(port->mediums & mediums))
186 		return -EINVAL;
187 
188 	port->mediums &= mediums;
189 
190 	phy_port_filter_supported(port);
191 
192 	return 0;
193 }
194 EXPORT_SYMBOL_GPL(phy_port_restrict_mediums);
195 
196 /**
197  * phy_port_get_type() - get the PORT_* attribute for that port.
198  * @port: The port we want the information from
199  *
200  * Returns: A PORT_XXX value.
201  */
202 int phy_port_get_type(struct phy_port *port)
203 {
204 	if (port->mediums & BIT(ETHTOOL_LINK_MEDIUM_BASET))
205 		return PORT_TP;
206 
207 	if (phy_port_is_fiber(port))
208 		return PORT_FIBRE;
209 
210 	return PORT_OTHER;
211 }
212 EXPORT_SYMBOL_GPL(phy_port_get_type);
213