[PATCH 06/12] net: phy: add phy_device_atomic_register helper

From: Marco Felsch
Date: Wed Apr 05 2023 - 05:31:32 EST


Currently the usually way to probe and setup a phy is done via:
1) get_phy_device()/phy_device_create()
2) phy_device_register.

During get_phy_device() the PHYID1/2 registers are read which assumes
that the phy is already accessible. This is not always the case, e.g.
- if the pre-running firmware did not initialize the phy or
- if the kernel does gate important clocks while booting and the phy
isn't accessible after the pre-running firmware anymore.

To fix this we need to:
- parse the phy's fwnode first,
- do some basic setup like: bring it out of the reset state and
- finally read the PHYID1/2 registers to probe the correct driver

This patch adds a new helper called phy_device_atomic_register() to not
break exisiting running systems based on the current mdio/phy handling.
This new helper bundles all required steps into a single function to
make it easier for driver developers.

To bundle the phy firmware parsing step within phx_device.c the commit
copies the required code from fwnode_mdio.c. After we converterd all
callers of fwnode_mdiobus_* to this new API we can remove the support
from fwnode_mdio.c.

Signed-off-by: Marco Felsch <m.felsch@xxxxxxxxxxxxxx>
---
drivers/net/phy/phy_device.c | 208 +++++++++++++++++++++++++++++++++++++++++++
include/linux/phy.h | 9 ++
2 files changed, 217 insertions(+)

diff --git a/drivers/net/phy/phy_device.c b/drivers/net/phy/phy_device.c
index 7e4b3b3caba9..a784ac06e6a9 100644
--- a/drivers/net/phy/phy_device.c
+++ b/drivers/net/phy/phy_device.c
@@ -3124,6 +3124,214 @@ struct fwnode_handle *fwnode_get_phy_node(const struct fwnode_handle *fwnode)
}
EXPORT_SYMBOL_GPL(fwnode_get_phy_node);

+static int fwnode_setup_phy_irq(struct phy_device *phydev, struct mii_bus *bus,
+ struct fwnode_handle *fwnode)
+{
+ u32 addr = phydev->mdio.addr;
+ int ret;
+
+ if (is_acpi_node(fwnode)) {
+ phydev->irq = bus->irq[addr];
+ return 0;
+ }
+
+ /* of_node */
+ ret = fwnode_irq_get(fwnode, 0);
+ /* Don't wait forever if the IRQ provider doesn't become available,
+ * just fall back to poll mode
+ */
+ if (ret == -EPROBE_DEFER)
+ ret = driver_deferred_probe_check_state(&phydev->mdio.dev);
+ if (ret == -EPROBE_DEFER)
+ return ret;
+
+ if (ret > 0) {
+ phydev->irq = ret;
+ bus->irq[addr] = ret;
+ } else {
+ phydev->irq = bus->irq[addr];
+ }
+
+ return 0;
+}
+
+static struct pse_control *
+fwnode_find_pse_control(struct fwnode_handle *fwnode)
+{
+ struct pse_control *psec;
+ struct device_node *np;
+
+ if (!IS_ENABLED(CONFIG_PSE_CONTROLLER))
+ return NULL;
+
+ np = to_of_node(fwnode);
+ if (!np)
+ return NULL;
+
+ psec = of_pse_control_get(np);
+ if (PTR_ERR(psec) == -ENOENT)
+ return NULL;
+
+ return psec;
+}
+
+static struct mii_timestamper *
+fwnode_find_mii_timestamper(struct fwnode_handle *fwnode)
+{
+ struct of_phandle_args arg;
+ int err;
+
+ if (is_acpi_node(fwnode))
+ return NULL;
+
+ err = of_parse_phandle_with_fixed_args(to_of_node(fwnode),
+ "timestamper", 1, 0, &arg);
+ if (err == -ENOENT)
+ return NULL;
+ else if (err)
+ return ERR_PTR(err);
+
+ if (arg.args_count != 1)
+ return ERR_PTR(-EINVAL);
+
+ return register_mii_timestamper(arg.np, arg.args[0]);
+}
+
+static int
+phy_device_parse_fwnode(struct phy_device *phydev,
+ struct phy_device_config *config)
+{
+ struct fwnode_handle *fwnode = config->fwnode;
+ struct mii_bus *bus = config->mii_bus;
+ u32 addr = phydev->mdio.addr;
+ int ret;
+
+ if (!fwnode)
+ return 0;
+
+ if (!is_acpi_node(fwnode) && !is_of_node(fwnode))
+ return 0;
+
+ ret = fwnode_setup_phy_irq(phydev, bus, fwnode);
+ if (ret)
+ return ret;
+
+ ret = fwnode_property_match_string(fwnode, "compatible",
+ "ethernet-phy-ieee802.3-c45");
+ if (ret >= 0)
+ config->is_c45 = true;
+
+ if (fwnode_property_read_bool(fwnode, "broken-turn-around"))
+ bus->phy_ignore_ta_mask |= 1 << addr;
+ fwnode_property_read_u32(fwnode, "reset-assert-us",
+ &phydev->mdio.reset_assert_delay);
+ fwnode_property_read_u32(fwnode, "reset-deassert-us",
+ &phydev->mdio.reset_deassert_delay);
+
+ fwnode_handle_get(fwnode);
+ if (is_acpi_node(fwnode))
+ phydev->mdio.dev.fwnode = fwnode;
+ else if (is_of_node(fwnode))
+ device_set_node(&phydev->mdio.dev, fwnode);
+
+ phydev->psec = fwnode_find_pse_control(fwnode);
+ if (IS_ERR(phydev->psec)) {
+ ret = PTR_ERR(phydev->psec);
+ goto put_fwnode;
+ }
+
+ /* A mii_timestamper probed via the device tree will have precedence. */
+ phydev->mii_ts = fwnode_find_mii_timestamper(fwnode);
+ if (IS_ERR(phydev->mii_ts)) {
+ ret = PTR_ERR(phydev->mii_ts);
+ goto put_pse;
+ }
+
+ return 0;
+
+put_pse:
+ pse_control_put(phydev->psec);
+put_fwnode:
+ fwnode_handle_put(phydev->mdio.dev.fwnode);
+
+ return ret;
+}
+
+/**
+ * phy_device_atomic_register - Setup, init and register a PHY on the MDIO bus
+ * @config: The PHY config
+ *
+ * Probe, initialise and register a PHY at @addr on @bus.
+ *
+ * Returns an allocated and registered &struct phy_device on success.
+ */
+struct phy_device *phy_device_atomic_register(struct phy_device_config *config)
+{
+ struct phy_c45_device_ids *c45_ids = &config->c45_ids;
+ struct phy_device *phydev;
+ int err;
+
+ phydev = phy_device_alloc(config);
+ if (IS_ERR(phydev))
+ return ERR_CAST(phydev);
+
+ err = phy_device_parse_fwnode(phydev, config);
+ if (err) {
+ phydev_err(phydev, "failed to parse fwnode\n");
+ goto err_free_phydev;
+ }
+
+ err = mdiobus_register_device(&phydev->mdio);
+ if (err) {
+ phydev_err(phydev, "pre-init step failed\n");
+ goto err_free_fwnode;
+ }
+
+ phy_device_reset(phydev, 0);
+
+ memset(c45_ids->device_ids, 0xff, sizeof(c45_ids->device_ids));
+
+ err = phy_device_detect(config);
+ if (err) {
+ phydev_err(phydev, "failed to query the phyid\n");
+ goto err_unregister_mdiodev;
+ }
+
+ err = phy_device_init(phydev, config);
+ if (err) {
+ phydev_err(phydev, "failed to initialize\n");
+ goto err_unregister_mdiodev;
+ }
+
+ err = phy_scan_fixups(phydev);
+ if (err) {
+ phydev_err(phydev, "failed to apply fixups\n");
+ goto err_unregister_mdiodev;
+ }
+
+ err = device_add(&phydev->mdio.dev);
+ if (err) {
+ phydev_err(phydev, "failed to add\n");
+ goto err_out;
+ }
+
+ return 0;
+
+err_out:
+ phy_device_reset(phydev, 1);
+err_unregister_mdiodev:
+ mdiobus_unregister_device(&phydev->mdio);
+err_free_fwnode:
+ unregister_mii_timestamper(phydev->mii_ts);
+ pse_control_put(phydev->psec);
+ fwnode_handle_put(phydev->mdio.dev.fwnode);
+err_free_phydev:
+ kfree(phydev);
+
+ return ERR_PTR(err);
+}
+EXPORT_SYMBOL(phy_device_atomic_register);
+
/**
* phy_probe - probe and init a PHY device
* @dev: device to probe and init
diff --git a/include/linux/phy.h b/include/linux/phy.h
index 0f0cb72a08ab..bdf6d27faefb 100644
--- a/include/linux/phy.h
+++ b/include/linux/phy.h
@@ -761,6 +761,7 @@ static inline struct phy_device *to_phy_device(const struct device *dev)
*
* @mii_bus: The target MII bus the PHY is connected to
* @phy_addr: PHY address on the MII bus
+ * @fwnode: The PHY firmware handle
* @phy_id: UID for this device found during discovery
* @c45_ids: 802.3-c45 Device Identifiers if is_c45.
* @is_c45: If true the PHY uses the 802.3 clause 45 protocol
@@ -774,6 +775,7 @@ static inline struct phy_device *to_phy_device(const struct device *dev)
struct phy_device_config {
struct mii_bus *mii_bus;
int phy_addr;
+ struct fwnode_handle *fwnode;
u32 phy_id;
struct phy_c45_device_ids c45_ids;
bool is_c45;
@@ -1573,6 +1575,7 @@ struct phy_device *device_phy_find_device(struct device *dev);
struct fwnode_handle *fwnode_get_phy_node(const struct fwnode_handle *fwnode);
struct phy_device *get_phy_device(struct phy_device_config *config);
int phy_device_register(struct phy_device *phy);
+struct phy_device *phy_device_atomic_register(struct phy_device_config *config);
void phy_device_free(struct phy_device *phydev);
#else
static inline int fwnode_get_phy_id(struct fwnode_handle *fwnode, u32 *phy_id)
@@ -1613,6 +1616,12 @@ static inline int phy_device_register(struct phy_device *phy)
return 0;
}

+static inline
+struct phy_device *phy_device_atomic_register(struct phy_device_config *config)
+{
+ return NULL;
+}
+
static inline void phy_device_free(struct phy_device *phydev) { }
#endif /* CONFIG_PHYLIB */
void phy_device_remove(struct phy_device *phydev);

--
2.39.2