1589e934dSMaxime Chevallier // SPDX-License-Identifier: GPL-2.0+
2589e934dSMaxime Chevallier /* Framework to drive Ethernet ports
3589e934dSMaxime Chevallier *
4589e934dSMaxime Chevallier * Copyright (c) 2024 Maxime Chevallier <maxime.chevallier@bootlin.com>
5589e934dSMaxime Chevallier */
6589e934dSMaxime Chevallier
7589e934dSMaxime Chevallier #include <linux/linkmode.h>
8589e934dSMaxime Chevallier #include <linux/of.h>
9589e934dSMaxime Chevallier #include <linux/phy_port.h>
10589e934dSMaxime Chevallier
11589e934dSMaxime Chevallier #include "phy-caps.h"
12589e934dSMaxime Chevallier
13589e934dSMaxime Chevallier /**
14589e934dSMaxime Chevallier * phy_port_alloc() - Allocate a new phy_port
15589e934dSMaxime Chevallier *
16589e934dSMaxime Chevallier * Returns: a newly allocated struct phy_port, or NULL.
17589e934dSMaxime Chevallier */
phy_port_alloc(void)18589e934dSMaxime Chevallier struct phy_port *phy_port_alloc(void)
19589e934dSMaxime Chevallier {
20589e934dSMaxime Chevallier struct phy_port *port;
21589e934dSMaxime Chevallier
22589e934dSMaxime Chevallier port = kzalloc(sizeof(*port), GFP_KERNEL);
23589e934dSMaxime Chevallier if (!port)
24589e934dSMaxime Chevallier return NULL;
25589e934dSMaxime Chevallier
26589e934dSMaxime Chevallier linkmode_zero(port->supported);
27589e934dSMaxime Chevallier INIT_LIST_HEAD(&port->head);
28589e934dSMaxime Chevallier
29589e934dSMaxime Chevallier return port;
30589e934dSMaxime Chevallier }
31589e934dSMaxime Chevallier EXPORT_SYMBOL_GPL(phy_port_alloc);
32589e934dSMaxime Chevallier
33589e934dSMaxime Chevallier /**
34589e934dSMaxime Chevallier * phy_port_destroy() - Free a struct phy_port
35589e934dSMaxime Chevallier * @port: The port to destroy
36589e934dSMaxime Chevallier */
phy_port_destroy(struct phy_port * port)37589e934dSMaxime Chevallier void phy_port_destroy(struct phy_port *port)
38589e934dSMaxime Chevallier {
39589e934dSMaxime Chevallier kfree(port);
40589e934dSMaxime Chevallier }
41589e934dSMaxime Chevallier EXPORT_SYMBOL_GPL(phy_port_destroy);
42589e934dSMaxime Chevallier
43589e934dSMaxime Chevallier /**
44589e934dSMaxime Chevallier * phy_of_parse_port() - Create a phy_port from a firmware representation
45589e934dSMaxime Chevallier * @dn: device_node representation of the port, following the
46589e934dSMaxime Chevallier * ethernet-connector.yaml binding
47589e934dSMaxime Chevallier *
48589e934dSMaxime Chevallier * Returns: a newly allocated and initialized phy_port pointer, or an ERR_PTR.
49589e934dSMaxime Chevallier */
phy_of_parse_port(struct device_node * dn)50589e934dSMaxime Chevallier struct phy_port *phy_of_parse_port(struct device_node *dn)
51589e934dSMaxime Chevallier {
52589e934dSMaxime Chevallier struct fwnode_handle *fwnode = of_fwnode_handle(dn);
53589e934dSMaxime Chevallier enum ethtool_link_medium medium;
54589e934dSMaxime Chevallier struct phy_port *port;
55589e934dSMaxime Chevallier const char *med_str;
566248f3dcSMaxime Chevallier u32 pairs = 0;
57589e934dSMaxime Chevallier int ret;
58589e934dSMaxime Chevallier
59589e934dSMaxime Chevallier ret = fwnode_property_read_string(fwnode, "media", &med_str);
60589e934dSMaxime Chevallier if (ret)
61589e934dSMaxime Chevallier return ERR_PTR(ret);
62589e934dSMaxime Chevallier
63589e934dSMaxime Chevallier medium = ethtool_str_to_medium(med_str);
64589e934dSMaxime Chevallier if (medium == ETHTOOL_LINK_MEDIUM_NONE)
65589e934dSMaxime Chevallier return ERR_PTR(-EINVAL);
66589e934dSMaxime Chevallier
67589e934dSMaxime Chevallier if (medium == ETHTOOL_LINK_MEDIUM_BASET) {
68589e934dSMaxime Chevallier ret = fwnode_property_read_u32(fwnode, "pairs", &pairs);
69589e934dSMaxime Chevallier if (ret)
70589e934dSMaxime Chevallier return ERR_PTR(ret);
71589e934dSMaxime Chevallier
72589e934dSMaxime Chevallier switch (pairs) {
73589e934dSMaxime Chevallier case 1: /* BaseT1 */
74589e934dSMaxime Chevallier case 2: /* 100BaseTX */
75589e934dSMaxime Chevallier case 4:
76589e934dSMaxime Chevallier break;
77589e934dSMaxime Chevallier default:
78589e934dSMaxime Chevallier pr_err("%u is not a valid number of pairs\n", pairs);
79589e934dSMaxime Chevallier return ERR_PTR(-EINVAL);
80589e934dSMaxime Chevallier }
81589e934dSMaxime Chevallier }
82589e934dSMaxime Chevallier
83589e934dSMaxime Chevallier if (pairs && medium != ETHTOOL_LINK_MEDIUM_BASET) {
84589e934dSMaxime Chevallier pr_err("pairs property is only compatible with BaseT medium\n");
85589e934dSMaxime Chevallier return ERR_PTR(-EINVAL);
86589e934dSMaxime Chevallier }
87589e934dSMaxime Chevallier
88589e934dSMaxime Chevallier port = phy_port_alloc();
89589e934dSMaxime Chevallier if (!port)
90589e934dSMaxime Chevallier return ERR_PTR(-ENOMEM);
91589e934dSMaxime Chevallier
92589e934dSMaxime Chevallier port->pairs = pairs;
936248f3dcSMaxime Chevallier port->mediums = BIT(medium);
94589e934dSMaxime Chevallier
95589e934dSMaxime Chevallier return port;
96589e934dSMaxime Chevallier }
97589e934dSMaxime Chevallier EXPORT_SYMBOL_GPL(phy_of_parse_port);
98589e934dSMaxime Chevallier
99589e934dSMaxime Chevallier /**
100589e934dSMaxime Chevallier * phy_port_update_supported() - Setup the port->supported field
101589e934dSMaxime Chevallier * @port: the port to update
102589e934dSMaxime Chevallier *
103589e934dSMaxime Chevallier * Once the port's medium list and number of pairs has been configured based
104589e934dSMaxime Chevallier * on firmware, straps and vendor-specific properties, this function may be
105589e934dSMaxime Chevallier * called to update the port's supported linkmodes list.
106589e934dSMaxime Chevallier *
107589e934dSMaxime Chevallier * Any mode that was manually set in the port's supported list remains set.
108589e934dSMaxime Chevallier */
phy_port_update_supported(struct phy_port * port)109589e934dSMaxime Chevallier void phy_port_update_supported(struct phy_port *port)
110589e934dSMaxime Chevallier {
111589e934dSMaxime Chevallier __ETHTOOL_DECLARE_LINK_MODE_MASK(supported) = {0};
112589e934dSMaxime Chevallier unsigned long mode;
113589e934dSMaxime Chevallier int i;
114589e934dSMaxime Chevallier
115589e934dSMaxime Chevallier /* If there's no pairs specified, we grab the default number of
116589e934dSMaxime Chevallier * pairs as the max of the default pairs for each linkmode
117589e934dSMaxime Chevallier */
118589e934dSMaxime Chevallier if (!port->pairs)
119589e934dSMaxime Chevallier for_each_set_bit(mode, port->supported,
120589e934dSMaxime Chevallier __ETHTOOL_LINK_MODE_MASK_NBITS)
121589e934dSMaxime Chevallier port->pairs = max_t(int, port->pairs,
122589e934dSMaxime Chevallier ethtool_linkmode_n_pairs(mode));
12307f3ca9eSMaxime Chevallier
124*4cebb26aSMaxime Chevallier for_each_set_bit(i, &port->mediums, __ETHTOOL_LINK_MEDIUM_LAST) {
125*4cebb26aSMaxime Chevallier __ETHTOOL_DECLARE_LINK_MODE_MASK(med_supported) = {0};
126*4cebb26aSMaxime Chevallier
127*4cebb26aSMaxime Chevallier phy_caps_medium_get_supported(med_supported, i, port->pairs);
128*4cebb26aSMaxime Chevallier linkmode_or(supported, supported, med_supported);
129*4cebb26aSMaxime Chevallier }
130*4cebb26aSMaxime Chevallier
131*4cebb26aSMaxime Chevallier /* If port->supported is already populated, filter it out with the
132*4cebb26aSMaxime Chevallier * medium/pair support. Otherwise, let's just use this medium-based
133*4cebb26aSMaxime Chevallier * support as the port's supported list.
134*4cebb26aSMaxime Chevallier */
135*4cebb26aSMaxime Chevallier if (linkmode_empty(port->supported))
136*4cebb26aSMaxime Chevallier linkmode_copy(port->supported, supported);
137*4cebb26aSMaxime Chevallier else
138*4cebb26aSMaxime Chevallier linkmode_and(port->supported, supported, port->supported);
139*4cebb26aSMaxime Chevallier
14007f3ca9eSMaxime Chevallier /* Serdes ports supported through SFP may not have any medium set,
14107f3ca9eSMaxime Chevallier * as they will output PHY_INTERFACE_MODE_XXX modes. In that case, derive
14207f3ca9eSMaxime Chevallier * the supported list based on these interfaces
14307f3ca9eSMaxime Chevallier */
14407f3ca9eSMaxime Chevallier if (port->is_mii && !port->mediums) {
14507f3ca9eSMaxime Chevallier unsigned long interface, link_caps = 0;
14607f3ca9eSMaxime Chevallier
14707f3ca9eSMaxime Chevallier /* Get each interface's caps */
14807f3ca9eSMaxime Chevallier for_each_set_bit(interface, port->interfaces,
14907f3ca9eSMaxime Chevallier PHY_INTERFACE_MODE_MAX)
15007f3ca9eSMaxime Chevallier link_caps |= phy_caps_from_interface(interface);
15107f3ca9eSMaxime Chevallier
15207f3ca9eSMaxime Chevallier phy_caps_linkmodes(link_caps, port->supported);
15307f3ca9eSMaxime Chevallier }
154589e934dSMaxime Chevallier }
155589e934dSMaxime Chevallier EXPORT_SYMBOL_GPL(phy_port_update_supported);
156589e934dSMaxime Chevallier
157589e934dSMaxime Chevallier /**
15835d1a546SMaxime Chevallier * phy_port_filter_supported() - Make sure that port->supported match port->mediums
15935d1a546SMaxime Chevallier * @port: The port to filter
16035d1a546SMaxime Chevallier *
16135d1a546SMaxime Chevallier * After updating a port's mediums to a more restricted subset, this helper will
16235d1a546SMaxime Chevallier * make sure that port->supported only contains linkmodes that are compatible
16335d1a546SMaxime Chevallier * with port->mediums.
16435d1a546SMaxime Chevallier */
phy_port_filter_supported(struct phy_port * port)16535d1a546SMaxime Chevallier static void phy_port_filter_supported(struct phy_port *port)
16635d1a546SMaxime Chevallier {
16735d1a546SMaxime Chevallier __ETHTOOL_DECLARE_LINK_MODE_MASK(supported) = { 0 };
16835d1a546SMaxime Chevallier int i;
16935d1a546SMaxime Chevallier
17035d1a546SMaxime Chevallier for_each_set_bit(i, &port->mediums, __ETHTOOL_LINK_MEDIUM_LAST)
17135d1a546SMaxime Chevallier phy_caps_medium_get_supported(supported, i, port->pairs);
17235d1a546SMaxime Chevallier
17335d1a546SMaxime Chevallier linkmode_and(port->supported, port->supported, supported);
17435d1a546SMaxime Chevallier }
17535d1a546SMaxime Chevallier
17635d1a546SMaxime Chevallier /**
17735d1a546SMaxime Chevallier * phy_port_restrict_mediums - Mask away some of the port's supported mediums
17835d1a546SMaxime Chevallier * @port: The port to act upon
17935d1a546SMaxime Chevallier * @mediums: A mask of mediums to support on the port
18035d1a546SMaxime Chevallier *
18135d1a546SMaxime Chevallier * This helper allows removing some mediums from a port's list of supported
18235d1a546SMaxime Chevallier * mediums, which occurs once we have enough information about the port to
18335d1a546SMaxime Chevallier * know its nature.
18435d1a546SMaxime Chevallier *
18535d1a546SMaxime Chevallier * Returns: 0 if the change was donne correctly, a negative value otherwise.
18635d1a546SMaxime Chevallier */
phy_port_restrict_mediums(struct phy_port * port,unsigned long mediums)18735d1a546SMaxime Chevallier int phy_port_restrict_mediums(struct phy_port *port, unsigned long mediums)
18835d1a546SMaxime Chevallier {
18935d1a546SMaxime Chevallier /* We forbid ending-up with a port with empty mediums */
19035d1a546SMaxime Chevallier if (!(port->mediums & mediums))
19135d1a546SMaxime Chevallier return -EINVAL;
19235d1a546SMaxime Chevallier
19335d1a546SMaxime Chevallier port->mediums &= mediums;
19435d1a546SMaxime Chevallier
19535d1a546SMaxime Chevallier phy_port_filter_supported(port);
19635d1a546SMaxime Chevallier
19735d1a546SMaxime Chevallier return 0;
19835d1a546SMaxime Chevallier }
19935d1a546SMaxime Chevallier EXPORT_SYMBOL_GPL(phy_port_restrict_mediums);
20035d1a546SMaxime Chevallier
20135d1a546SMaxime Chevallier /**
202589e934dSMaxime Chevallier * phy_port_get_type() - get the PORT_* attribute for that port.
203589e934dSMaxime Chevallier * @port: The port we want the information from
204589e934dSMaxime Chevallier *
205589e934dSMaxime Chevallier * Returns: A PORT_XXX value.
206589e934dSMaxime Chevallier */
phy_port_get_type(struct phy_port * port)207589e934dSMaxime Chevallier int phy_port_get_type(struct phy_port *port)
208589e934dSMaxime Chevallier {
209589e934dSMaxime Chevallier if (port->mediums & BIT(ETHTOOL_LINK_MEDIUM_BASET))
210589e934dSMaxime Chevallier return PORT_TP;
211589e934dSMaxime Chevallier
212589e934dSMaxime Chevallier if (phy_port_is_fiber(port))
213589e934dSMaxime Chevallier return PORT_FIBRE;
214589e934dSMaxime Chevallier
215589e934dSMaxime Chevallier return PORT_OTHER;
216589e934dSMaxime Chevallier }
217589e934dSMaxime Chevallier EXPORT_SYMBOL_GPL(phy_port_get_type);
218