[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-Id: <20230405-net-next-topic-net-phy-reset-v1-6-7e5329f08002@pengutronix.de>
Date: Wed, 05 Apr 2023 11:26:57 +0200
From: Marco Felsch <m.felsch@...gutronix.de>
To: Andrew Lunn <andrew@...n.ch>,
Heiner Kallweit <hkallweit1@...il.com>,
Russell King <linux@...linux.org.uk>,
"David S. Miller" <davem@...emloft.net>,
Eric Dumazet <edumazet@...gle.com>,
Jakub Kicinski <kuba@...nel.org>,
Paolo Abeni <pabeni@...hat.com>,
Florian Fainelli <f.fainelli@...il.com>,
Broadcom internal kernel review list
<bcm-kernel-feedback-list@...adcom.com>,
Richard Cochran <richardcochran@...il.com>,
Radu Pirea <radu-nicolae.pirea@....nxp.com>,
Shyam Sundar S K <Shyam-sundar.S-k@....com>,
Yisen Zhuang <yisen.zhuang@...wei.com>,
Salil Mehta <salil.mehta@...wei.com>,
Jassi Brar <jaswinder.singh@...aro.org>,
Ilias Apalodimas <ilias.apalodimas@...aro.org>,
Iyappan Subramanian <iyappan@...amperecomputing.com>,
Keyur Chudgar <keyur@...amperecomputing.com>,
Quan Nguyen <quan@...amperecomputing.com>,
"Rafael J. Wysocki" <rafael@...nel.org>,
Len Brown <lenb@...nel.org>, Rob Herring <robh+dt@...nel.org>,
Frank Rowand <frowand.list@...il.com>
Cc: netdev@...r.kernel.org, linux-kernel@...r.kernel.org,
linux-acpi@...r.kernel.org, devicetree@...r.kernel.org,
kernel@...gutronix.de
Subject: [PATCH 06/12] net: phy: add phy_device_atomic_register helper
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@...gutronix.de>
---
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
Powered by blists - more mailing lists