summaryrefslogtreecommitdiffstats
path: root/drivers/net/phy/phy_caps.c
blob: 7033216897264e31d7bd3c85bdec066cc983497c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
// SPDX-License-Identifier: GPL-2.0-or-later

#include <linux/ethtool.h>
#include <linux/linkmode.h>
#include <linux/phy.h>

#include "phy-caps.h"

static struct link_capabilities link_caps[__LINK_CAPA_MAX] __ro_after_init = {
	{ SPEED_10, DUPLEX_HALF, {0} }, /* LINK_CAPA_10HD */
	{ SPEED_10, DUPLEX_FULL, {0} }, /* LINK_CAPA_10FD */
	{ SPEED_100, DUPLEX_HALF, {0} }, /* LINK_CAPA_100HD */
	{ SPEED_100, DUPLEX_FULL, {0} }, /* LINK_CAPA_100FD */
	{ SPEED_1000, DUPLEX_HALF, {0} }, /* LINK_CAPA_1000HD */
	{ SPEED_1000, DUPLEX_FULL, {0} }, /* LINK_CAPA_1000FD */
	{ SPEED_2500, DUPLEX_FULL, {0} }, /* LINK_CAPA_2500FD */
	{ SPEED_5000, DUPLEX_FULL, {0} }, /* LINK_CAPA_5000FD */
	{ SPEED_10000, DUPLEX_FULL, {0} }, /* LINK_CAPA_10000FD */
	{ SPEED_20000, DUPLEX_FULL, {0} }, /* LINK_CAPA_20000FD */
	{ SPEED_25000, DUPLEX_FULL, {0} }, /* LINK_CAPA_25000FD */
	{ SPEED_40000, DUPLEX_FULL, {0} }, /* LINK_CAPA_40000FD */
	{ SPEED_50000, DUPLEX_FULL, {0} }, /* LINK_CAPA_50000FD */
	{ SPEED_56000, DUPLEX_FULL, {0} }, /* LINK_CAPA_56000FD */
	{ SPEED_100000, DUPLEX_FULL, {0} }, /* LINK_CAPA_100000FD */
	{ SPEED_200000, DUPLEX_FULL, {0} }, /* LINK_CAPA_200000FD */
	{ SPEED_400000, DUPLEX_FULL, {0} }, /* LINK_CAPA_400000FD */
	{ SPEED_800000, DUPLEX_FULL, {0} }, /* LINK_CAPA_800000FD */
};

static int speed_duplex_to_capa(int speed, unsigned int duplex)
{
	if (duplex == DUPLEX_UNKNOWN ||
	    (speed > SPEED_1000 && duplex != DUPLEX_FULL))
		return -EINVAL;

	switch (speed) {
	case SPEED_10: return duplex == DUPLEX_FULL ?
			      LINK_CAPA_10FD : LINK_CAPA_10HD;
	case SPEED_100: return duplex == DUPLEX_FULL ?
			       LINK_CAPA_100FD : LINK_CAPA_100HD;
	case SPEED_1000: return duplex == DUPLEX_FULL ?
				LINK_CAPA_1000FD : LINK_CAPA_1000HD;
	case SPEED_2500: return LINK_CAPA_2500FD;
	case SPEED_5000: return LINK_CAPA_5000FD;
	case SPEED_10000: return LINK_CAPA_10000FD;
	case SPEED_20000: return LINK_CAPA_20000FD;
	case SPEED_25000: return LINK_CAPA_25000FD;
	case SPEED_40000: return LINK_CAPA_40000FD;
	case SPEED_50000: return LINK_CAPA_50000FD;
	case SPEED_56000: return LINK_CAPA_56000FD;
	case SPEED_100000: return LINK_CAPA_100000FD;
	case SPEED_200000: return LINK_CAPA_200000FD;
	case SPEED_400000: return LINK_CAPA_400000FD;
	case SPEED_800000: return LINK_CAPA_800000FD;
	}

	return -EINVAL;
}

#define for_each_link_caps_asc_speed(cap) \
	for (cap = link_caps; cap < &link_caps[__LINK_CAPA_MAX]; cap++)

#define for_each_link_caps_desc_speed(cap) \
	for (cap = &link_caps[__LINK_CAPA_MAX - 1]; cap >= link_caps; cap--)

/**
 * phy_caps_init() - Initializes the link_caps array from the link_mode_params.
 *
 * Returns: 0 if phy caps init was successful, -EINVAL if we found an
 *	    unexpected linkmode setting that requires LINK_CAPS update.
 *
 */
int phy_caps_init(void)
{
	const struct link_mode_info *linkmode;
	int i, capa;

	/* Fill the caps array from net/ethtool/common.c */
	for (i = 0; i < __ETHTOOL_LINK_MODE_MASK_NBITS; i++) {
		linkmode = &link_mode_params[i];
		capa = speed_duplex_to_capa(linkmode->speed, linkmode->duplex);

		if (capa < 0) {
			if (linkmode->speed != SPEED_UNKNOWN) {
				pr_err("Unknown speed %d, please update LINK_CAPS\n",
				       linkmode->speed);
				return -EINVAL;
			}
			continue;
		}

		__set_bit(i, link_caps[capa].linkmodes);
	}

	return 0;
}

/**
 * phy_caps_speeds() - Fill an array of supported SPEED_* values for given modes
 * @speeds: Output array to store the speeds list into
 * @size: Size of the output array
 * @linkmodes: Linkmodes to get the speeds from
 *
 * Fills the speeds array with all possible speeds that can be achieved with
 * the specified linkmodes.
 *
 * Returns: The number of speeds filled into the array. If the input array isn't
 *	    big enough to store all speeds, fill it as much as possible.
 */
size_t phy_caps_speeds(unsigned int *speeds, size_t size,
		       unsigned long *linkmodes)
{
	struct link_capabilities *lcap;
	size_t count = 0;

	for_each_link_caps_asc_speed(lcap) {
		if (linkmode_intersects(lcap->linkmodes, linkmodes) &&
		    (count == 0 || speeds[count - 1] != lcap->speed)) {
			speeds[count++] = lcap->speed;
			if (count >= size)
				break;
		}
	}

	return count;
}

/**
 * phy_caps_lookup_by_linkmode() - Lookup the fastest matching link_capabilities
 * @linkmodes: Linkmodes to match against
 *
 * Returns: The highest-speed link_capabilities that intersects the given
 *	    linkmodes. In case several DUPLEX_ options exist at that speed,
 *	    DUPLEX_FULL is matched first. NULL is returned if no match.
 */
const struct link_capabilities *
phy_caps_lookup_by_linkmode(const unsigned long *linkmodes)
{
	struct link_capabilities *lcap;

	for_each_link_caps_desc_speed(lcap)
		if (linkmode_intersects(lcap->linkmodes, linkmodes))
			return lcap;

	return NULL;
}

/**
 * phy_caps_lookup_by_linkmode_rev() - Lookup the slowest matching link_capabilities
 * @linkmodes: Linkmodes to match against
 * @fdx_only: Full duplex match only when set
 *
 * Returns: The lowest-speed link_capabilities that intersects the given
 *	    linkmodes. When set, fdx_only will ignore half-duplex matches.
 *	    NULL is returned if no match.
 */
const struct link_capabilities *
phy_caps_lookup_by_linkmode_rev(const unsigned long *linkmodes, bool fdx_only)
{
	struct link_capabilities *lcap;

	for_each_link_caps_asc_speed(lcap) {
		if (fdx_only && lcap->duplex != DUPLEX_FULL)
			continue;

		if (linkmode_intersects(lcap->linkmodes, linkmodes))
			return lcap;
	}

	return NULL;
}

/**
 * phy_caps_lookup() - Lookup capabilities by speed/duplex that matches a mask
 * @speed: Speed to match
 * @duplex: Duplex to match
 * @supported: Mask of linkmodes to match
 * @exact: Perform an exact match or not.
 *
 * Lookup a link_capabilities entry that intersect the supported linkmodes mask,
 * and that matches the passed speed and duplex.
 *
 * When @exact is set, an exact match is performed on speed and duplex, meaning
 * that if the linkmodes for the given speed and duplex intersect the supported
 * mask, this capability is returned, otherwise we don't have a match and return
 * NULL.
 *
 * When @exact is not set, we return either an exact match, or matching capabilities
 * at lower speed, or the lowest matching speed, or NULL.
 *
 * Returns: a matched link_capabilities according to the above process, NULL
 *	    otherwise.
 */
const struct link_capabilities *
phy_caps_lookup(int speed, unsigned int duplex, const unsigned long *supported,
		bool exact)
{
	const struct link_capabilities *lcap, *last = NULL;

	for_each_link_caps_desc_speed(lcap) {
		if (linkmode_intersects(lcap->linkmodes, supported)) {
			last = lcap;
			/* exact match on speed and duplex*/
			if (lcap->speed == speed && lcap->duplex == duplex) {
				return lcap;
			} else if (!exact) {
				if (lcap->speed <= speed)
					return lcap;
			}
		}
	}

	if (!exact)
		return last;

	return NULL;
}
EXPORT_SYMBOL_GPL(phy_caps_lookup);

/**
 * phy_caps_linkmode_max_speed() - Clamp a linkmodes set to a max speed
 * @max_speed: Speed limit for the linkmode set
 * @linkmodes: Linkmodes to limit
 */
void phy_caps_linkmode_max_speed(u32 max_speed, unsigned long *linkmodes)
{
	struct link_capabilities *lcap;

	for_each_link_caps_desc_speed(lcap)
		if (lcap->speed > max_speed)
			linkmode_andnot(linkmodes, linkmodes, lcap->linkmodes);
		else
			break;
}

/**
 * phy_caps_valid() - Validate a linkmodes set agains given speed and duplex
 * @speed: input speed to validate
 * @duplex: input duplex to validate. Passing DUPLEX_UNKNOWN is always not valid
 * @linkmodes: The linkmodes to validate
 *
 * Returns: True if at least one of the linkmodes in @linkmodes can function at
 *          the given speed and duplex, false otherwise.
 */
bool phy_caps_valid(int speed, int duplex, const unsigned long *linkmodes)
{
	int capa = speed_duplex_to_capa(speed, duplex);

	if (capa < 0)
		return false;

	return linkmode_intersects(link_caps[capa].linkmodes, linkmodes);
}

/**
 * phy_caps_linkmodes() - Convert a bitfield of capabilities into linkmodes
 * @caps: The list of caps, each bit corresponding to a LINK_CAPA value
 * @linkmodes: The set of linkmodes to fill. Must be previously initialized.
 */
void phy_caps_linkmodes(unsigned long caps, unsigned long *linkmodes)
{
	unsigned long capa;

	for_each_set_bit(capa, &caps, __LINK_CAPA_MAX)
		linkmode_or(linkmodes, linkmodes, link_caps[capa].linkmodes);
}
EXPORT_SYMBOL_GPL(phy_caps_linkmodes);

/**
 * phy_caps_from_interface() - Get the link capa from a given PHY interface
 * @interface: The PHY interface we want to get the possible Speed/Duplex from
 *
 * Returns: A bitmask of LINK_CAPA_xxx values that can be achieved with the
 *          provided interface.
 */
unsigned long phy_caps_from_interface(phy_interface_t interface)
{
	unsigned long link_caps = 0;

	switch (interface) {
	case PHY_INTERFACE_MODE_USXGMII:
		link_caps |= BIT(LINK_CAPA_10000FD) | BIT(LINK_CAPA_5000FD);
		fallthrough;

	case PHY_INTERFACE_MODE_10G_QXGMII:
		link_caps |= BIT(LINK_CAPA_2500FD);
		fallthrough;

	case PHY_INTERFACE_MODE_RGMII_TXID:
	case PHY_INTERFACE_MODE_RGMII_RXID:
	case PHY_INTERFACE_MODE_RGMII_ID:
	case PHY_INTERFACE_MODE_RGMII:
	case PHY_INTERFACE_MODE_PSGMII:
	case PHY_INTERFACE_MODE_QSGMII:
	case PHY_INTERFACE_MODE_QUSGMII:
	case PHY_INTERFACE_MODE_SGMII:
	case PHY_INTERFACE_MODE_GMII:
		link_caps |= BIT(LINK_CAPA_1000HD) | BIT(LINK_CAPA_1000FD);
		fallthrough;

	case PHY_INTERFACE_MODE_REVRMII:
	case PHY_INTERFACE_MODE_RMII:
	case PHY_INTERFACE_MODE_SMII:
	case PHY_INTERFACE_MODE_REVMII:
	case PHY_INTERFACE_MODE_MII:
		link_caps |= BIT(LINK_CAPA_10HD) | BIT(LINK_CAPA_10FD);
		fallthrough;

	case PHY_INTERFACE_MODE_100BASEX:
		link_caps |= BIT(LINK_CAPA_100HD) | BIT(LINK_CAPA_100FD);
		break;

	case PHY_INTERFACE_MODE_TBI:
	case PHY_INTERFACE_MODE_MOCA:
	case PHY_INTERFACE_MODE_RTBI:
	case PHY_INTERFACE_MODE_1000BASEX:
		link_caps |= BIT(LINK_CAPA_1000HD);
		fallthrough;
	case PHY_INTERFACE_MODE_1000BASEKX:
	case PHY_INTERFACE_MODE_TRGMII:
		link_caps |= BIT(LINK_CAPA_1000FD);
		break;

	case PHY_INTERFACE_MODE_2500BASEX:
		link_caps |= BIT(LINK_CAPA_2500FD);
		break;

	case PHY_INTERFACE_MODE_5GBASER:
		link_caps |= BIT(LINK_CAPA_5000FD);
		break;

	case PHY_INTERFACE_MODE_XGMII:
	case PHY_INTERFACE_MODE_RXAUI:
	case PHY_INTERFACE_MODE_XAUI:
	case PHY_INTERFACE_MODE_10GBASER:
	case PHY_INTERFACE_MODE_10GKR:
		link_caps |= BIT(LINK_CAPA_10000FD);
		break;

	case PHY_INTERFACE_MODE_25GBASER:
		link_caps |= BIT(LINK_CAPA_25000FD);
		break;

	case PHY_INTERFACE_MODE_XLGMII:
		link_caps |= BIT(LINK_CAPA_40000FD);
		break;

	case PHY_INTERFACE_MODE_INTERNAL:
		link_caps |= LINK_CAPA_ALL;
		break;

	case PHY_INTERFACE_MODE_NA:
	case PHY_INTERFACE_MODE_MAX:
		break;
	}

	return link_caps;
}
EXPORT_SYMBOL_GPL(phy_caps_from_interface);