@@ -139,6 +139,47 @@ bool of_mdiobus_child_is_phy(struct device_node *child)
}
EXPORT_SYMBOL(of_mdiobus_child_is_phy);
+static int __of_mdiobus_parse_phys(struct mii_bus *mdio, struct device_node *np,
+ bool *scanphys)
+{
+ struct device_node *child;
+ int addr, rc = 0;
+
+ /* Loop over the child nodes and register a phy_device for each phy */
+ for_each_available_child_of_node(np, child) {
+ if (of_node_name_prefix(child, "ethernet-phy-package")) {
+ rc = __of_mdiobus_parse_phys(mdio, child, scanphys);
+ if (rc && rc != -ENODEV)
+ goto exit;
+
+ continue;
+ }
+
+ addr = of_mdio_parse_addr(&mdio->dev, child);
+ if (addr < 0) {
+ *scanphys = true;
+ continue;
+ }
+
+ if (of_mdiobus_child_is_phy(child))
+ rc = of_mdiobus_register_phy(mdio, child, addr);
+ else
+ rc = of_mdiobus_register_device(mdio, child, addr);
+
+ if (rc == -ENODEV)
+ dev_err(&mdio->dev,
+ "MDIO device at address %d is missing.\n",
+ addr);
+ else if (rc)
+ goto exit;
+ }
+
+ return 0;
+exit:
+ of_node_put(child);
+ return rc;
+}
+
/**
* __of_mdiobus_register - Register mii_bus and create PHYs from the device tree
* @mdio: pointer to mii_bus structure
@@ -180,25 +221,9 @@ int __of_mdiobus_register(struct mii_bus *mdio, struct device_node *np,
return rc;
/* Loop over the child nodes and register a phy_device for each phy */
- for_each_available_child_of_node(np, child) {
- addr = of_mdio_parse_addr(&mdio->dev, child);
- if (addr < 0) {
- scanphys = true;
- continue;
- }
-
- if (of_mdiobus_child_is_phy(child))
- rc = of_mdiobus_register_phy(mdio, child, addr);
- else
- rc = of_mdiobus_register_device(mdio, child, addr);
-
- if (rc == -ENODEV)
- dev_err(&mdio->dev,
- "MDIO device at address %d is missing.\n",
- addr);
- else if (rc)
- goto unregister;
- }
+ rc = __of_mdiobus_parse_phys(mdio, np, &scanphys);
+ if (rc)
+ goto unregister;
if (!scanphys)
return 0;
@@ -227,15 +252,16 @@ int __of_mdiobus_register(struct mii_bus *mdio, struct device_node *np,
if (!rc)
break;
if (rc != -ENODEV)
- goto unregister;
+ goto put_unregister;
}
}
}
return 0;
-unregister:
+put_unregister:
of_node_put(child);
+unregister:
mdiobus_unregister(mdio);
return rc;
}
@@ -455,19 +455,25 @@ EXPORT_SYMBOL(of_mdio_find_bus);
* found, set the of_node pointer for the mdio device. This allows
* auto-probed phy devices to be supplied with information passed in
* via DT.
+ * If a PHY package is found, PHY is searched also there.
*/
-static void of_mdiobus_link_mdiodev(struct mii_bus *bus,
- struct mdio_device *mdiodev)
+static int of_mdiobus_find_phy(struct device *dev, struct mdio_device *mdiodev,
+ struct device_node *np)
{
- struct device *dev = &mdiodev->dev;
struct device_node *child;
- if (dev->of_node || !bus->dev.of_node)
- return;
-
- for_each_available_child_of_node(bus->dev.of_node, child) {
+ for_each_available_child_of_node(np, child) {
int addr;
+ if (of_node_name_prefix(child, "ethernet-phy-package")) {
+ if (!of_mdiobus_find_phy(dev, mdiodev, child)) {
+ of_node_put(child);
+ return 0;
+ }
+
+ continue;
+ }
+
addr = of_mdio_parse_addr(dev, child);
if (addr < 0)
continue;
@@ -477,9 +483,22 @@ static void of_mdiobus_link_mdiodev(struct mii_bus *bus,
/* The refcount on "child" is passed to the mdio
* device. Do _not_ use of_node_put(child) here.
*/
- return;
+ return 0;
}
}
+
+ return -ENODEV;
+}
+
+static void of_mdiobus_link_mdiodev(struct mii_bus *bus,
+ struct mdio_device *mdiodev)
+{
+ struct device *dev = &mdiodev->dev;
+
+ if (dev->of_node || !bus->dev.of_node)
+ return;
+
+ of_mdiobus_find_phy(dev, mdiodev, bus->dev.of_node);
}
#else /* !IS_ENABLED(CONFIG_OF_MDIO) */
static inline void of_mdiobus_link_mdiodev(struct mii_bus *mdio,
@@ -3193,6 +3193,62 @@ static int of_phy_leds(struct phy_device *phydev)
return 0;
}
+static int of_phy_package(struct phy_device *phydev)
+{
+ struct device_node *node = phydev->mdio.dev.of_node;
+ struct device_node *package_node;
+ const u8 *global_phys_offset;
+ int *global_phys_addr;
+ u8 global_phys_num;
+ u32 base_addr;
+ int i, ret;
+
+ if (!node)
+ return 0;
+
+ package_node = of_get_parent(node);
+ if (!package_node)
+ return 0;
+
+ if (!of_node_name_prefix(package_node, "ethernet-phy-package"))
+ return 0;
+
+ if (of_property_read_u32(package_node, "reg", &base_addr))
+ return -EINVAL;
+
+ global_phys_num = phydev->drv->phy_package_global_phys_num;
+ global_phys_offset = phydev->drv->phy_package_global_phys_offset;
+ if (!global_phys_num || !global_phys_offset)
+ return -EINVAL;
+
+ global_phys_addr = kmalloc_array(global_phys_num, sizeof(*global_phys_addr),
+ GFP_KERNEL);
+ if (!global_phys_addr)
+ return -ENOMEM;
+
+ for (i = 0; i < global_phys_num; i++) {
+ int addr = base_addr + global_phys_offset[i];
+
+ /* Make sure the calculated address is valid */
+ if (unlikely(addr >= PHY_MAX_ADDR))
+ return -EINVAL;
+
+ global_phys_addr[i] = addr;
+ }
+
+ ret = devm_phy_package_join(&phydev->mdio.dev, phydev, global_phys_addr,
+ global_phys_num, 0);
+ if (ret)
+ goto exit;
+
+ phydev->shared->np = package_node;
+
+exit:
+ kfree(global_phys_addr);
+
+ return ret;
+}
+
/**
* fwnode_mdio_find_device - Given a fwnode, find the mdio_device
* @fwnode: pointer to the mdio_device's fwnode
@@ -3301,6 +3357,11 @@ static int phy_probe(struct device *dev)
if (phydrv->flags & PHY_IS_INTERNAL)
phydev->is_internal = true;
+ /* Parse DT to detect PHY package and join them */
+ err = of_phy_package(phydev);
+ if (err)
+ goto out;
+
/* Deassert the reset signal */
phy_device_reset(phydev, 0);
@@ -327,6 +327,7 @@ struct mdio_bus_stats {
/**
* struct phy_package_shared - Shared information in PHY packages
+ * @np: Pointer to the Device Node if PHY package defined in DT
* @addrs: List of common PHY addresses used to combine PHYs in one package
* @addrs_num: Number of common PHY addresses in addrs list
* @refcnt: Number of PHYs connected to this shared data
@@ -339,6 +340,8 @@ struct mdio_bus_stats {
* phy_package_leave().
*/
struct phy_package_shared {
+ /* With PHY package defined in DT this points to the PHY package node */
+ struct device_node *np;
/* addrs list pointer */
/* note that this pointer is shared between different phydevs.
* It is allocated and freed automatically by phy_package_join() and
@@ -888,6 +891,10 @@ struct phy_led {
* @flags: A bitfield defining certain other features this PHY
* supports (like interrupts)
* @driver_data: Static driver data
+ * @phy_package_global_phys_offset: Table of offset of the required
+ * global PHYs for PHY package global configuration.
+ * @phy_package_global_phys_num: Num of the required global PHYs
+ * for PHY package global configuration.
*
* All functions are optional. If config_aneg or read_status
* are not implemented, the phy core uses the genphy versions.
@@ -905,6 +912,8 @@ struct phy_driver {
const unsigned long * const features;
u32 flags;
const void *driver_data;
+ const u8 *phy_package_global_phys_offset;
+ const u8 phy_package_global_phys_num;
/**
* @soft_reset: Called to issue a PHY software reset