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 */
phy_port_alloc(void)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 */
phy_port_destroy(struct phy_port * port)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 */
phy_of_parse_port(struct device_node * dn)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;
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 port = phy_port_alloc();
89 if (!port)
90 return ERR_PTR(-ENOMEM);
91
92 port->pairs = pairs;
93 port->mediums = BIT(medium);
94
95 return port;
96 }
97 EXPORT_SYMBOL_GPL(phy_of_parse_port);
98
99 /**
100 * phy_port_update_supported() - Setup the port->supported field
101 * @port: the port to update
102 *
103 * Once the port's medium list and number of pairs has been configured based
104 * on firmware, straps and vendor-specific properties, this function may be
105 * called to update the port's supported linkmodes list.
106 *
107 * Any mode that was manually set in the port's supported list remains set.
108 */
phy_port_update_supported(struct phy_port * port)109 void phy_port_update_supported(struct phy_port *port)
110 {
111 __ETHTOOL_DECLARE_LINK_MODE_MASK(supported) = {0};
112 unsigned long mode;
113 int i;
114
115 /* If there's no pairs specified, we grab the default number of
116 * pairs as the max of the default pairs for each linkmode
117 */
118 if (!port->pairs)
119 for_each_set_bit(mode, port->supported,
120 __ETHTOOL_LINK_MODE_MASK_NBITS)
121 port->pairs = max_t(int, port->pairs,
122 ethtool_linkmode_n_pairs(mode));
123
124 for_each_set_bit(i, &port->mediums, __ETHTOOL_LINK_MEDIUM_LAST) {
125 __ETHTOOL_DECLARE_LINK_MODE_MASK(med_supported) = {0};
126
127 phy_caps_medium_get_supported(med_supported, i, port->pairs);
128 linkmode_or(supported, supported, med_supported);
129 }
130
131 /* If port->supported is already populated, filter it out with the
132 * medium/pair support. Otherwise, let's just use this medium-based
133 * support as the port's supported list.
134 */
135 if (linkmode_empty(port->supported))
136 linkmode_copy(port->supported, supported);
137 else
138 linkmode_and(port->supported, supported, port->supported);
139
140 /* Serdes ports supported through SFP may not have any medium set,
141 * as they will output PHY_INTERFACE_MODE_XXX modes. In that case, derive
142 * the supported list based on these interfaces
143 */
144 if (port->is_mii && !port->mediums) {
145 unsigned long interface, link_caps = 0;
146
147 /* Get each interface's caps */
148 for_each_set_bit(interface, port->interfaces,
149 PHY_INTERFACE_MODE_MAX)
150 link_caps |= phy_caps_from_interface(interface);
151
152 phy_caps_linkmodes(link_caps, port->supported);
153 }
154 }
155 EXPORT_SYMBOL_GPL(phy_port_update_supported);
156
157 /**
158 * phy_port_filter_supported() - Make sure that port->supported match port->mediums
159 * @port: The port to filter
160 *
161 * After updating a port's mediums to a more restricted subset, this helper will
162 * make sure that port->supported only contains linkmodes that are compatible
163 * with port->mediums.
164 */
phy_port_filter_supported(struct phy_port * port)165 static void phy_port_filter_supported(struct phy_port *port)
166 {
167 __ETHTOOL_DECLARE_LINK_MODE_MASK(supported) = { 0 };
168 int i;
169
170 for_each_set_bit(i, &port->mediums, __ETHTOOL_LINK_MEDIUM_LAST)
171 phy_caps_medium_get_supported(supported, i, port->pairs);
172
173 linkmode_and(port->supported, port->supported, supported);
174 }
175
176 /**
177 * phy_port_restrict_mediums - Mask away some of the port's supported mediums
178 * @port: The port to act upon
179 * @mediums: A mask of mediums to support on the port
180 *
181 * This helper allows removing some mediums from a port's list of supported
182 * mediums, which occurs once we have enough information about the port to
183 * know its nature.
184 *
185 * Returns: 0 if the change was donne correctly, a negative value otherwise.
186 */
phy_port_restrict_mediums(struct phy_port * port,unsigned long mediums)187 int phy_port_restrict_mediums(struct phy_port *port, unsigned long mediums)
188 {
189 /* We forbid ending-up with a port with empty mediums */
190 if (!(port->mediums & mediums))
191 return -EINVAL;
192
193 port->mediums &= mediums;
194
195 phy_port_filter_supported(port);
196
197 return 0;
198 }
199 EXPORT_SYMBOL_GPL(phy_port_restrict_mediums);
200
201 /**
202 * phy_port_get_type() - get the PORT_* attribute for that port.
203 * @port: The port we want the information from
204 *
205 * Returns: A PORT_XXX value.
206 */
phy_port_get_type(struct phy_port * port)207 int phy_port_get_type(struct phy_port *port)
208 {
209 if (port->mediums & BIT(ETHTOOL_LINK_MEDIUM_BASET))
210 return PORT_TP;
211
212 if (phy_port_is_fiber(port))
213 return PORT_FIBRE;
214
215 return PORT_OTHER;
216 }
217 EXPORT_SYMBOL_GPL(phy_port_get_type);
218