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