diff options
Diffstat (limited to 'drivers/net/phy/phy_device.c')
| -rw-r--r-- | drivers/net/phy/phy_device.c | 427 |
1 files changed, 346 insertions, 81 deletions
diff --git a/drivers/net/phy/phy_device.c b/drivers/net/phy/phy_device.c index 81984d4ebb7c..8a3eb1839a3d 100644 --- a/drivers/net/phy/phy_device.c +++ b/drivers/net/phy/phy_device.c @@ -30,6 +30,7 @@ #include <linux/phylib_stubs.h> #include <linux/phy_led_triggers.h> #include <linux/phy_link_topology.h> +#include <linux/phy_port.h> #include <linux/pse-pd/pse.h> #include <linux/property.h> #include <linux/ptp_clock_kernel.h> @@ -48,9 +49,6 @@ MODULE_DESCRIPTION("PHY library"); MODULE_AUTHOR("Andy Fleming"); MODULE_LICENSE("GPL"); -#define PHY_ANY_ID "MATCH ANY PHY" -#define PHY_ANY_UID 0xffffffff - struct phy_fixup { struct list_head list; char bus_id[MII_BUS_ID_SIZE + 3]; @@ -431,11 +429,10 @@ static SIMPLE_DEV_PM_OPS(mdio_bus_phy_pm_ops, mdio_bus_phy_suspend, /** * phy_register_fixup - creates a new phy_fixup and adds it to the list - * @bus_id: A string which matches phydev->mdio.dev.bus_id (or PHY_ANY_ID) + * @bus_id: A string which matches phydev->mdio.dev.bus_id (or NULL) * @phy_uid: Used to match against phydev->phy_id (the UID of the PHY) - * It can also be PHY_ANY_UID * @phy_uid_mask: Applied to phydev->phy_id and fixup->phy_uid before - * comparison + * comparison (or 0 to disable id-based matching) * @run: The actual code to be run when a matching PHY is found */ static int phy_register_fixup(const char *bus_id, u32 phy_uid, u32 phy_uid_mask, @@ -446,7 +443,8 @@ static int phy_register_fixup(const char *bus_id, u32 phy_uid, u32 phy_uid_mask, if (!fixup) return -ENOMEM; - strscpy(fixup->bus_id, bus_id, sizeof(fixup->bus_id)); + if (bus_id) + strscpy(fixup->bus_id, bus_id, sizeof(fixup->bus_id)); fixup->phy_uid = phy_uid; fixup->phy_uid_mask = phy_uid_mask; fixup->run = run; @@ -462,7 +460,7 @@ static int phy_register_fixup(const char *bus_id, u32 phy_uid, u32 phy_uid_mask, int phy_register_fixup_for_uid(u32 phy_uid, u32 phy_uid_mask, int (*run)(struct phy_device *)) { - return phy_register_fixup(PHY_ANY_ID, phy_uid, phy_uid_mask, run); + return phy_register_fixup(NULL, phy_uid, phy_uid_mask, run); } EXPORT_SYMBOL(phy_register_fixup_for_uid); @@ -470,71 +468,20 @@ EXPORT_SYMBOL(phy_register_fixup_for_uid); int phy_register_fixup_for_id(const char *bus_id, int (*run)(struct phy_device *)) { - return phy_register_fixup(bus_id, PHY_ANY_UID, 0xffffffff, run); + return phy_register_fixup(bus_id, 0, 0, run); } EXPORT_SYMBOL(phy_register_fixup_for_id); -/** - * phy_unregister_fixup - remove a phy_fixup from the list - * @bus_id: A string matches fixup->bus_id (or PHY_ANY_ID) in phy_fixup_list - * @phy_uid: A phy id matches fixup->phy_id (or PHY_ANY_UID) in phy_fixup_list - * @phy_uid_mask: Applied to phy_uid and fixup->phy_uid before comparison - */ -int phy_unregister_fixup(const char *bus_id, u32 phy_uid, u32 phy_uid_mask) +static bool phy_needs_fixup(struct phy_device *phydev, struct phy_fixup *fixup) { - struct list_head *pos, *n; - struct phy_fixup *fixup; - int ret; - - ret = -ENODEV; - - mutex_lock(&phy_fixup_lock); - list_for_each_safe(pos, n, &phy_fixup_list) { - fixup = list_entry(pos, struct phy_fixup, list); - - if ((!strcmp(fixup->bus_id, bus_id)) && - phy_id_compare(fixup->phy_uid, phy_uid, phy_uid_mask)) { - list_del(&fixup->list); - kfree(fixup); - ret = 0; - break; - } - } - mutex_unlock(&phy_fixup_lock); + if (!strcmp(fixup->bus_id, phydev_name(phydev))) + return true; - return ret; -} -EXPORT_SYMBOL(phy_unregister_fixup); - -/* Unregisters a fixup of any PHY with the UID in phy_uid */ -int phy_unregister_fixup_for_uid(u32 phy_uid, u32 phy_uid_mask) -{ - return phy_unregister_fixup(PHY_ANY_ID, phy_uid, phy_uid_mask); -} -EXPORT_SYMBOL(phy_unregister_fixup_for_uid); + if (fixup->phy_uid_mask && + phy_id_compare(phydev->phy_id, fixup->phy_uid, fixup->phy_uid_mask)) + return true; -/* Unregisters a fixup of the PHY with id string bus_id */ -int phy_unregister_fixup_for_id(const char *bus_id) -{ - return phy_unregister_fixup(bus_id, PHY_ANY_UID, 0xffffffff); -} -EXPORT_SYMBOL(phy_unregister_fixup_for_id); - -/* Returns 1 if fixup matches phydev in bus_id and phy_uid. - * Fixups can be set to match any in one or more fields. - */ -static int phy_needs_fixup(struct phy_device *phydev, struct phy_fixup *fixup) -{ - if (strcmp(fixup->bus_id, phydev_name(phydev)) != 0) - if (strcmp(fixup->bus_id, PHY_ANY_ID) != 0) - return 0; - - if (!phy_id_compare(phydev->phy_id, fixup->phy_uid, - fixup->phy_uid_mask)) - if (fixup->phy_uid != PHY_ANY_UID) - return 0; - - return 1; + return false; } /* Runs any matching fixups for this phydev */ @@ -845,6 +792,13 @@ struct phy_device *phy_device_create(struct mii_bus *bus, int addr, u32 phy_id, dev->state = PHY_DOWN; INIT_LIST_HEAD(&dev->leds); + INIT_LIST_HEAD(&dev->ports); + + /* The driver's probe function must change that to the real number + * of ports possible on the PHY. We assume by default we are dealing + * with a single-port PHY + */ + dev->max_n_ports = 1; mutex_init(&dev->lock); INIT_DELAYED_WORK(&dev->state_queue, phy_state_machine); @@ -1524,7 +1478,7 @@ static DEVICE_ATTR_RO(phy_standalone); * * Return: 0 on success, otherwise a negative error code. */ -int phy_sfp_connect_phy(void *upstream, struct phy_device *phy) +static int phy_sfp_connect_phy(void *upstream, struct phy_device *phy) { struct phy_device *phydev = upstream; struct net_device *dev = phydev->attached_dev; @@ -1534,7 +1488,6 @@ int phy_sfp_connect_phy(void *upstream, struct phy_device *phy) return 0; } -EXPORT_SYMBOL(phy_sfp_connect_phy); /** * phy_sfp_disconnect_phy - Disconnect the SFP module's PHY from the upstream PHY @@ -1546,7 +1499,7 @@ EXPORT_SYMBOL(phy_sfp_connect_phy); * will be destroyed, re-inserting the same module will add a new phy with a * new index. */ -void phy_sfp_disconnect_phy(void *upstream, struct phy_device *phy) +static void phy_sfp_disconnect_phy(void *upstream, struct phy_device *phy) { struct phy_device *phydev = upstream; struct net_device *dev = phydev->attached_dev; @@ -1554,7 +1507,6 @@ void phy_sfp_disconnect_phy(void *upstream, struct phy_device *phy) if (dev) phy_link_topo_del_phy(dev, phy); } -EXPORT_SYMBOL(phy_sfp_disconnect_phy); /** * phy_sfp_attach - attach the SFP bus to the PHY upstream network device @@ -1563,7 +1515,7 @@ EXPORT_SYMBOL(phy_sfp_disconnect_phy); * * This is used to fill in the sfp_upstream_ops .attach member. */ -void phy_sfp_attach(void *upstream, struct sfp_bus *bus) +static void phy_sfp_attach(void *upstream, struct sfp_bus *bus) { struct phy_device *phydev = upstream; @@ -1571,7 +1523,6 @@ void phy_sfp_attach(void *upstream, struct sfp_bus *bus) phydev->attached_dev->sfp_bus = bus; phydev->sfp_bus_attached = true; } -EXPORT_SYMBOL(phy_sfp_attach); /** * phy_sfp_detach - detach the SFP bus from the PHY upstream network device @@ -1580,7 +1531,7 @@ EXPORT_SYMBOL(phy_sfp_attach); * * This is used to fill in the sfp_upstream_ops .detach member. */ -void phy_sfp_detach(void *upstream, struct sfp_bus *bus) +static void phy_sfp_detach(void *upstream, struct sfp_bus *bus) { struct phy_device *phydev = upstream; @@ -1588,15 +1539,164 @@ void phy_sfp_detach(void *upstream, struct sfp_bus *bus) phydev->attached_dev->sfp_bus = NULL; phydev->sfp_bus_attached = false; } -EXPORT_SYMBOL(phy_sfp_detach); + +static int phy_sfp_module_insert(void *upstream, const struct sfp_eeprom_id *id) +{ + __ETHTOOL_DECLARE_LINK_MODE_MASK(sfp_support); + struct phy_device *phydev = upstream; + const struct sfp_module_caps *caps; + struct phy_port *port; + + phy_interface_t iface; + + linkmode_zero(sfp_support); + + port = phy_get_sfp_port(phydev); + if (!port) + return -EINVAL; + + caps = sfp_get_module_caps(phydev->sfp_bus); + + linkmode_and(sfp_support, port->supported, caps->link_modes); + if (linkmode_empty(sfp_support)) { + dev_err(&phydev->mdio.dev, "incompatible SFP module inserted, no common linkmode\n"); + return -EINVAL; + } + + iface = sfp_select_interface(phydev->sfp_bus, sfp_support); + if (iface == PHY_INTERFACE_MODE_NA) { + dev_err(&phydev->mdio.dev, "PHY %s does not support the SFP module's requested MII interfaces\n", + phydev_name(phydev)); + return -EINVAL; + } + + if (phydev->n_ports == 1) + phydev->port = caps->port; + + if (port->ops && port->ops->configure_mii) + return port->ops->configure_mii(port, true, iface); + + return 0; +} + +static void phy_sfp_module_remove(void *upstream) +{ + struct phy_device *phydev = upstream; + struct phy_port *port = phy_get_sfp_port(phydev); + + if (port && port->ops && port->ops->configure_mii) + port->ops->configure_mii(port, false, PHY_INTERFACE_MODE_NA); + + if (phydev->n_ports == 1) + phydev->port = PORT_NONE; +} + +static void phy_sfp_link_up(void *upstream) +{ + struct phy_device *phydev = upstream; + struct phy_port *port = phy_get_sfp_port(phydev); + + if (port && port->ops && port->ops->link_up) + port->ops->link_up(port); +} + +static void phy_sfp_link_down(void *upstream) +{ + struct phy_device *phydev = upstream; + struct phy_port *port = phy_get_sfp_port(phydev); + + if (port && port->ops && port->ops->link_down) + port->ops->link_down(port); +} + +static const struct sfp_upstream_ops sfp_phydev_ops = { + .attach = phy_sfp_attach, + .detach = phy_sfp_detach, + .module_insert = phy_sfp_module_insert, + .module_remove = phy_sfp_module_remove, + .link_up = phy_sfp_link_up, + .link_down = phy_sfp_link_down, + .connect_phy = phy_sfp_connect_phy, + .disconnect_phy = phy_sfp_disconnect_phy, +}; + +static int phy_add_port(struct phy_device *phydev, struct phy_port *port) +{ + int ret = 0; + + if (phydev->n_ports == phydev->max_n_ports) + return -EBUSY; + + /* We set all ports as active by default, PHY drivers may deactivate + * them (when unused) + */ + port->active = true; + + if (port->is_mii) { + if (phydev->drv && phydev->drv->attach_mii_port) + ret = phydev->drv->attach_mii_port(phydev, port); + } else { + if (phydev->drv && phydev->drv->attach_mdi_port) + ret = phydev->drv->attach_mdi_port(phydev, port); + } + + if (ret) + return ret; + + /* The PHY driver might have added, removed or set medium/pairs info, + * so update the port supported accordingly. + */ + phy_port_update_supported(port); + + list_add(&port->head, &phydev->ports); + + phydev->n_ports++; + + return 0; +} + +static void phy_del_port(struct phy_device *phydev, struct phy_port *port) +{ + if (!phydev->n_ports) + return; + + list_del(&port->head); + + phydev->n_ports--; +} + +static int phy_setup_sfp_port(struct phy_device *phydev) +{ + struct phy_port *port = phy_port_alloc(); + int ret; + + if (!port) + return -ENOMEM; + + port->parent_type = PHY_PORT_PHY; + port->phy = phydev; + + /* The PHY is a media converter, the port connected to the SFP cage + * is a MII port. + */ + port->is_mii = true; + port->is_sfp = true; + + /* The port->supported and port->interfaces list will be populated + * when attaching the port to the phydev. + */ + ret = phy_add_port(phydev, port); + if (ret) + phy_port_destroy(port); + + return ret; +} /** * phy_sfp_probe - probe for a SFP cage attached to this PHY device * @phydev: Pointer to phy_device - * @ops: SFP's upstream operations */ -int phy_sfp_probe(struct phy_device *phydev, - const struct sfp_upstream_ops *ops) +static int phy_sfp_probe(struct phy_device *phydev) { struct sfp_bus *bus; int ret = 0; @@ -1608,12 +1708,15 @@ int phy_sfp_probe(struct phy_device *phydev, phydev->sfp_bus = bus; - ret = sfp_bus_add_upstream(bus, phydev, ops); + ret = sfp_bus_add_upstream(bus, phydev, &sfp_phydev_ops); sfp_bus_put(bus); } + + if (!ret && phydev->sfp_bus) + ret = phy_setup_sfp_port(phydev); + return ret; } -EXPORT_SYMBOL(phy_sfp_probe); static bool phy_drv_supports_irq(const struct phy_driver *phydrv) { @@ -2369,7 +2472,7 @@ int genphy_update_link(struct phy_device *phydev) /* The link state is latched low so that momentary link * drops can be detected. Do not double-read the status * in polling mode to detect such short link drops except - * the link was already down. + * if the link was already down. */ if (!phy_polling_mode(phydev) || !phydev->link) { status = phy_read(phydev, MII_BMSR); @@ -3325,6 +3428,161 @@ exit: return 0; } +static void phy_cleanup_ports(struct phy_device *phydev) +{ + struct phy_port *tmp, *port; + + list_for_each_entry_safe(port, tmp, &phydev->ports, head) { + phy_del_port(phydev, port); + phy_port_destroy(port); + } +} + +static int phy_default_setup_single_port(struct phy_device *phydev) +{ + struct phy_port *port = phy_port_alloc(); + unsigned long mode; + + if (!port) + return -ENOMEM; + + port->parent_type = PHY_PORT_PHY; + port->phy = phydev; + + /* Let the PHY driver know that this port was never described anywhere. + * This is the usual case, where we assume single-port PHY devices with + * no SFP. In that case, the port supports exactly the same thing as + * the PHY itself. + * + * However, this can also be because we have a combo-port PHY, with + * only one port described in DT, through SFP for example. + * + * In that case, the PHY driver will be in charge of saying what we can + * do on that non-represented port. + */ + port->not_described = true; + linkmode_copy(port->supported, phydev->supported); + port->mediums = phy_caps_mediums_from_linkmodes(port->supported); + + for_each_set_bit(mode, port->supported, __ETHTOOL_LINK_MODE_MASK_NBITS) + port->pairs = max_t(int, port->pairs, + ethtool_linkmode_n_pairs(mode)); + + phy_add_port(phydev, port); + + return 0; +} + +static int of_phy_ports(struct phy_device *phydev) +{ + struct device_node *node = phydev->mdio.dev.of_node; + struct device_node *mdi; + struct phy_port *port; + int err; + + if (!IS_ENABLED(CONFIG_OF_MDIO)) + return 0; + + if (!node) + return 0; + + mdi = of_get_child_by_name(node, "mdi"); + if (!mdi) + return 0; + + for_each_available_child_of_node_scoped(mdi, port_node) { + port = phy_of_parse_port(port_node); + if (IS_ERR(port)) { + err = PTR_ERR(port); + goto out_err; + } + + port->parent_type = PHY_PORT_PHY; + port->phy = phydev; + err = phy_add_port(phydev, port); + if (err) { + phy_port_destroy(port); + goto out_err; + } + } + of_node_put(mdi); + + return 0; + +out_err: + phy_cleanup_ports(phydev); + of_node_put(mdi); + return err; +} + +static int phy_setup_ports(struct phy_device *phydev) +{ + __ETHTOOL_DECLARE_LINK_MODE_MASK(ports_supported); + struct phy_port *port; + int ret; + + ret = of_phy_ports(phydev); + if (ret) + return ret; + + ret = phy_sfp_probe(phydev); + if (ret) + goto out; + + if (phydev->n_ports < phydev->max_n_ports) { + ret = phy_default_setup_single_port(phydev); + if (ret) + goto out; + } + + linkmode_zero(ports_supported); + + /* Aggregate the supported modes, which are made-up of : + * - What the PHY itself supports + * - What the sum of all ports support + */ + list_for_each_entry(port, &phydev->ports, head) + if (port->active) + linkmode_or(ports_supported, ports_supported, + port->supported); + + if (!linkmode_empty(ports_supported)) + linkmode_and(phydev->supported, phydev->supported, + ports_supported); + + /* For now, the phy->port field is set as the first active port's type */ + list_for_each_entry(port, &phydev->ports, head) + if (port->active) { + phydev->port = phy_port_get_type(port); + break; + } + + return 0; + +out: + phy_cleanup_ports(phydev); + return ret; +} + +/** + * phy_get_sfp_port() - Returns the first valid SFP port of a PHY + * @phydev: pointer to the PHY device to get the SFP port from + * + * Returns: The first active SFP (serdes) port of a PHY device, NULL if none + * exist. + */ +struct phy_port *phy_get_sfp_port(struct phy_device *phydev) +{ + struct phy_port *port; + + list_for_each_entry(port, &phydev->ports, head) + if (port->active && port->is_sfp) + return port; + + return NULL; +} +EXPORT_SYMBOL_GPL(phy_get_sfp_port); + /** * fwnode_mdio_find_device - Given a fwnode, find the mdio_device * @fwnode: pointer to the mdio_device's fwnode @@ -3462,6 +3720,11 @@ static int phy_probe(struct device *dev) phydev->is_gigabit_capable = 1; of_set_phy_supported(phydev); + + err = phy_setup_ports(phydev); + if (err) + goto out; + phy_advertise_supported(phydev); /* Get PHY default EEE advertising modes and handle them as potentially @@ -3537,6 +3800,8 @@ static int phy_remove(struct device *dev) phydev->state = PHY_DOWN; + phy_cleanup_ports(phydev); + sfp_bus_del_upstream(phydev->sfp_bus); phydev->sfp_bus = NULL; |
