summaryrefslogtreecommitdiffstats
path: root/drivers/net/phy/phy_port.c
blob: e56e29d0e9faad91e79d3c4e0c11eb17fa64dd5a (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
// SPDX-License-Identifier: GPL-2.0+
/* Framework to drive Ethernet ports
 *
 * Copyright (c) 2024 Maxime Chevallier <maxime.chevallier@bootlin.com>
 */

#include <linux/linkmode.h>
#include <linux/of.h>
#include <linux/phy_port.h>

#include "phy-caps.h"

/**
 * phy_port_alloc() - Allocate a new phy_port
 *
 * Returns: a newly allocated struct phy_port, or NULL.
 */
struct phy_port *phy_port_alloc(void)
{
	struct phy_port *port;

	port = kzalloc_obj(*port);
	if (!port)
		return NULL;

	linkmode_zero(port->supported);
	INIT_LIST_HEAD(&port->head);

	return port;
}
EXPORT_SYMBOL_GPL(phy_port_alloc);

/**
 * phy_port_destroy() - Free a struct phy_port
 * @port: The port to destroy
 */
void phy_port_destroy(struct phy_port *port)
{
	kfree(port);
}
EXPORT_SYMBOL_GPL(phy_port_destroy);

/**
 * phy_of_parse_port() - Create a phy_port from a firmware representation
 * @dn: device_node representation of the port, following the
 *	ethernet-connector.yaml binding
 *
 * Returns: a newly allocated and initialized phy_port pointer, or an ERR_PTR.
 */
struct phy_port *phy_of_parse_port(struct device_node *dn)
{
	struct fwnode_handle *fwnode = of_fwnode_handle(dn);
	enum ethtool_link_medium medium;
	struct phy_port *port;
	const char *med_str;
	u32 pairs = 0;
	int ret;

	ret = fwnode_property_read_string(fwnode, "media", &med_str);
	if (ret)
		return ERR_PTR(ret);

	medium = ethtool_str_to_medium(med_str);
	if (medium == ETHTOOL_LINK_MEDIUM_NONE)
		return ERR_PTR(-EINVAL);

	if (medium == ETHTOOL_LINK_MEDIUM_BASET) {
		ret = fwnode_property_read_u32(fwnode, "pairs", &pairs);
		if (ret)
			return ERR_PTR(ret);

		switch (pairs) {
		case 1: /* BaseT1 */
		case 2: /* 100BaseTX */
		case 4:
			break;
		default:
			pr_err("%u is not a valid number of pairs\n", pairs);
			return ERR_PTR(-EINVAL);
		}
	}

	if (pairs && medium != ETHTOOL_LINK_MEDIUM_BASET) {
		pr_err("pairs property is only compatible with BaseT medium\n");
		return ERR_PTR(-EINVAL);
	}

	port = phy_port_alloc();
	if (!port)
		return ERR_PTR(-ENOMEM);

	port->pairs = pairs;
	port->mediums = BIT(medium);

	return port;
}
EXPORT_SYMBOL_GPL(phy_of_parse_port);

/**
 * phy_port_update_supported() - Setup the port->supported field
 * @port: the port to update
 *
 * Once the port's medium list and number of pairs has been configured based
 * on firmware, straps and vendor-specific properties, this function may be
 * called to update the port's supported linkmodes list.
 *
 * Any mode that was manually set in the port's supported list remains set.
 */
void phy_port_update_supported(struct phy_port *port)
{
	__ETHTOOL_DECLARE_LINK_MODE_MASK(supported) = {0};
	unsigned long mode;
	int i;

	/* If there's no pairs specified, we grab the default number of
	 * pairs as the max of the default pairs for each linkmode
	 */
	if (!port->pairs)
		for_each_set_bit(mode, port->supported,
				 __ETHTOOL_LINK_MODE_MASK_NBITS)
			port->pairs = max_t(int, port->pairs,
					    ethtool_linkmode_n_pairs(mode));

	for_each_set_bit(i, &port->mediums, __ETHTOOL_LINK_MEDIUM_LAST) {
		__ETHTOOL_DECLARE_LINK_MODE_MASK(med_supported) = {0};

		phy_caps_medium_get_supported(med_supported, i, port->pairs);
		linkmode_or(supported, supported, med_supported);
	}

	/* If port->supported is already populated, filter it out with the
	 * medium/pair support. Otherwise, let's just use this medium-based
	 * support as the port's supported list.
	 */
	if (linkmode_empty(port->supported))
		linkmode_copy(port->supported, supported);
	else
		linkmode_and(port->supported, supported, port->supported);

	/* Serdes ports supported through SFP may not have any medium set,
	 * as they will output PHY_INTERFACE_MODE_XXX modes. In that case, derive
	 * the supported list based on these interfaces
	 */
	if (port->is_mii && !port->mediums) {
		unsigned long interface, link_caps = 0;

		/* Get each interface's caps */
		for_each_set_bit(interface, port->interfaces,
				 PHY_INTERFACE_MODE_MAX)
			link_caps |= phy_caps_from_interface(interface);

		phy_caps_linkmodes(link_caps, port->supported);
	}
}
EXPORT_SYMBOL_GPL(phy_port_update_supported);

/**
 * phy_port_filter_supported() - Make sure that port->supported match port->mediums
 * @port: The port to filter
 *
 * After updating a port's mediums to a more restricted subset, this helper will
 * make sure that port->supported only contains linkmodes that are compatible
 * with port->mediums.
 */
static void phy_port_filter_supported(struct phy_port *port)
{
	__ETHTOOL_DECLARE_LINK_MODE_MASK(supported) = { 0 };
	int i;

	for_each_set_bit(i, &port->mediums, __ETHTOOL_LINK_MEDIUM_LAST)
		phy_caps_medium_get_supported(supported, i, port->pairs);

	linkmode_and(port->supported, port->supported, supported);
}

/**
 * phy_port_restrict_mediums - Mask away some of the port's supported mediums
 * @port: The port to act upon
 * @mediums: A mask of mediums to support on the port
 *
 * This helper allows removing some mediums from a port's list of supported
 * mediums, which occurs once we have enough information about the port to
 * know its nature.
 *
 * Returns: 0 if the change was donne correctly, a negative value otherwise.
 */
int phy_port_restrict_mediums(struct phy_port *port, unsigned long mediums)
{
	/* We forbid ending-up with a port with empty mediums */
	if (!(port->mediums & mediums))
		return -EINVAL;

	port->mediums &= mediums;

	phy_port_filter_supported(port);

	return 0;
}
EXPORT_SYMBOL_GPL(phy_port_restrict_mediums);

/**
 * phy_port_get_type() - get the PORT_* attribute for that port.
 * @port: The port we want the information from
 *
 * Returns: A PORT_XXX value.
 */
int phy_port_get_type(struct phy_port *port)
{
	if (port->mediums & BIT(ETHTOOL_LINK_MEDIUM_BASET))
		return PORT_TP;

	if (phy_port_is_fiber(port))
		return PORT_FIBRE;

	return PORT_OTHER;
}
EXPORT_SYMBOL_GPL(phy_port_get_type);