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; 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 */ 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 */ 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 */ 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 */ 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