linux/drivers/net/mdio/fwnode_mdio.c

185 lines
4.2 KiB
C
Raw Normal View History

// SPDX-License-Identifier: GPL-2.0-only
/*
* fwnode helpers for the MDIO (Ethernet PHY) API
*
* This file provides helper functions for extracting PHY device information
* out of the fwnode and using it to populate an mii_bus.
*/
#include <linux/acpi.h>
#include <linux/fwnode_mdio.h>
#include <linux/of.h>
#include <linux/phy.h>
#include <linux/pse-pd/pse.h>
MODULE_AUTHOR("Calvin Johnson <calvin.johnson@oss.nxp.com>");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("FWNODE MDIO bus (Ethernet PHY) accessors");
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]);
}
int fwnode_mdiobus_phy_device_register(struct mii_bus *mdio,
struct phy_device *phy,
struct fwnode_handle *child, u32 addr)
{
int rc;
rc = fwnode_irq_get(child, 0);
net: mdio: don't defer probe forever if PHY IRQ provider is missing When a driver for an interrupt controller is missing, of_irq_get() returns -EPROBE_DEFER ad infinitum, causing fwnode_mdiobus_phy_device_register(), and ultimately, the entire of_mdiobus_register() call, to fail. In turn, any phy_connect() call towards a PHY on this MDIO bus will also fail. This is not what is expected to happen, because the PHY library falls back to poll mode when of_irq_get() returns a hard error code, and the MDIO bus, PHY and attached Ethernet controller work fine, albeit suboptimally, when the PHY library polls for link status. However, -EPROBE_DEFER has special handling given the assumption that at some point probe deferral will stop, and the driver for the supplier will kick in and create the IRQ domain. Reasons for which the interrupt controller may be missing: - It is not yet written. This may happen if a more recent DT blob (with an interrupt-parent for the PHY) is used to boot an old kernel where the driver didn't exist, and that kernel worked with the vintage-correct DT blob using poll mode. - It is compiled out. Behavior is the same as above. - It is compiled as a module. The kernel will wait for a number of seconds specified in the "deferred_probe_timeout" boot parameter for user space to load the required module. The current default is 0, which times out at the end of initcalls. It is possible that this might cause regressions unless users adjust this boot parameter. The proposed solution is to use the driver_deferred_probe_check_state() helper function provided by the driver core, which gives up after some -EPROBE_DEFER attempts, taking "deferred_probe_timeout" into consideration. The return code is changed from -EPROBE_DEFER into -ENODEV or -ETIMEDOUT, depending on whether the kernel is compiled with support for modules or not. Fixes: 66bdede495c7 ("of_mdio: Fix broken PHY IRQ in case of probe deferral") Suggested-by: Robin Murphy <robin.murphy@arm.com> Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com> Acked-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> Reviewed-by: Florian Fainelli <f.fainelli@gmail.com> Link: https://lore.kernel.org/r/20220407165538.4084809-1-vladimir.oltean@nxp.com Signed-off-by: Jakub Kicinski <kuba@kernel.org>
2022-04-07 16:55:38 +00:00
/* Don't wait forever if the IRQ provider doesn't become available,
* just fall back to poll mode
*/
if (rc == -EPROBE_DEFER)
rc = driver_deferred_probe_check_state(&phy->mdio.dev);
if (rc == -EPROBE_DEFER)
return rc;
if (rc > 0) {
phy->irq = rc;
mdio->irq[addr] = rc;
} else {
phy->irq = mdio->irq[addr];
}
if (fwnode_property_read_bool(child, "broken-turn-around"))
mdio->phy_ignore_ta_mask |= 1 << addr;
fwnode_property_read_u32(child, "reset-assert-us",
&phy->mdio.reset_assert_delay);
fwnode_property_read_u32(child, "reset-deassert-us",
&phy->mdio.reset_deassert_delay);
/* Associate the fwnode with the device structure so it
* can be looked up later
*/
fwnode_handle_get(child);
device_set_node(&phy->mdio.dev, child);
/* All data is now stored in the phy struct;
* register it
*/
rc = phy_device_register(phy);
if (rc) {
device_set_node(&phy->mdio.dev, NULL);
fwnode_handle_put(child);
return rc;
}
dev_dbg(&mdio->dev, "registered phy %p fwnode at address %i\n",
child, addr);
return 0;
}
EXPORT_SYMBOL(fwnode_mdiobus_phy_device_register);
int fwnode_mdiobus_register_phy(struct mii_bus *bus,
struct fwnode_handle *child, u32 addr)
{
struct mii_timestamper *mii_ts = NULL;
struct pse_control *psec = NULL;
struct phy_device *phy;
bool is_c45;
u32 phy_id;
int rc;
psec = fwnode_find_pse_control(child);
if (IS_ERR(psec))
return PTR_ERR(psec);
mii_ts = fwnode_find_mii_timestamper(child);
if (IS_ERR(mii_ts)) {
rc = PTR_ERR(mii_ts);
goto clean_pse;
}
is_c45 = fwnode_device_is_compatible(child, "ethernet-phy-ieee802.3-c45");
if (is_c45 || fwnode_get_phy_id(child, &phy_id))
phy = get_phy_device(bus, addr, is_c45);
else
phy = phy_device_create(bus, addr, phy_id, 0, NULL);
if (IS_ERR(phy)) {
rc = PTR_ERR(phy);
goto clean_mii_ts;
}
if (is_acpi_node(child)) {
phy->irq = bus->irq[addr];
/* Associate the fwnode with the device structure so it
* can be looked up later.
*/
phy->mdio.dev.fwnode = fwnode_handle_get(child);
/* All data is now stored in the phy struct, so register it */
rc = phy_device_register(phy);
if (rc) {
phy->mdio.dev.fwnode = NULL;
fwnode_handle_put(child);
goto clean_phy;
}
} else if (is_of_node(child)) {
rc = fwnode_mdiobus_phy_device_register(bus, phy, child, addr);
if (rc)
goto clean_phy;
}
phy->psec = psec;
/* phy->mii_ts may already be defined by the PHY driver. A
* mii_timestamper probed via the device tree will still have
* precedence.
*/
if (mii_ts)
phy->mii_ts = mii_ts;
return 0;
clean_phy:
phy_device_free(phy);
clean_mii_ts:
unregister_mii_timestamper(mii_ts);
clean_pse:
pse_control_put(psec);
return rc;
}
EXPORT_SYMBOL(fwnode_mdiobus_register_phy);