lkml.org 
[lkml]   [2022]   [Mar]   [25]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
Patch in this message
/
From
Subject[net-next 1/5] net: mdio: fwnode: add fwnode_mdiobus_register()
Date
In order to support software node description transparently, add fwnode
support with fwnode_mdiobus_register(). This function behaves exactly
like of_mdiobus_register() function but using the fwnode node agnostic
API. This support might also be used to merge ACPI mdiobus support
which is quite similar to the fwnode one.

Some part such as the whitelist matching are kept exclusively for OF
nodes since it uses an of_device_id struct and seems tightly coupled
with OF. Other parts are generic and will allow to move the existing
OF support on top of this fwnode version.

Signed-off-by: Clément Léger <clement.leger@bootlin.com>
---
drivers/net/mdio/fwnode_mdio.c | 210 +++++++++++++++++++++++++++++++++
include/linux/fwnode_mdio.h | 13 ++
2 files changed, 223 insertions(+)

diff --git a/drivers/net/mdio/fwnode_mdio.c b/drivers/net/mdio/fwnode_mdio.c
index 1becb1a731f6..f9ec3818041a 100644
--- a/drivers/net/mdio/fwnode_mdio.c
+++ b/drivers/net/mdio/fwnode_mdio.c
@@ -11,6 +11,8 @@
#include <linux/of.h>
#include <linux/phy.h>

+#define DEFAULT_GPIO_RESET_DELAY 10 /* in microseconds */
+
MODULE_AUTHOR("Calvin Johnson <calvin.johnson@oss.nxp.com>");
MODULE_LICENSE("GPL");

@@ -142,3 +144,211 @@ int fwnode_mdiobus_register_phy(struct mii_bus *bus,
return 0;
}
EXPORT_SYMBOL(fwnode_mdiobus_register_phy);
+
+/* The following is a list of PHY compatible strings which appear in
+ * some DTBs. The compatible string is never matched against a PHY
+ * driver, so is pointless. We only expect devices which are not PHYs
+ * to have a compatible string, so they can be matched to an MDIO
+ * driver. Encourage users to upgrade their DT blobs to remove these.
+ */
+static const struct of_device_id whitelist_phys[] = {
+ { .compatible = "brcm,40nm-ephy" },
+ { .compatible = "broadcom,bcm5241" },
+ { .compatible = "marvell,88E1111", },
+ { .compatible = "marvell,88e1116", },
+ { .compatible = "marvell,88e1118", },
+ { .compatible = "marvell,88e1145", },
+ { .compatible = "marvell,88e1149r", },
+ { .compatible = "marvell,88e1310", },
+ { .compatible = "marvell,88E1510", },
+ { .compatible = "marvell,88E1514", },
+ { .compatible = "moxa,moxart-rtl8201cp", },
+ {}
+};
+
+/* Return true if the child node is for a phy. It must either:
+ * o Compatible string of "ethernet-phy-idX.X"
+ * o Compatible string of "ethernet-phy-ieee802.3-c45"
+ * o Compatible string of "ethernet-phy-ieee802.3-c22"
+ * o No compatibility string
+ *
+ * A device which is not a phy is expected to have a compatible string
+ * indicating what sort of device it is.
+ */
+bool fwnode_mdiobus_child_is_phy(struct fwnode_handle *child)
+{
+ u32 phy_id;
+
+ if (fwnode_get_phy_id(child, &phy_id) != -EINVAL)
+ return true;
+
+ if (fwnode_property_match_string(child, "compatible",
+ "ethernet-phy-ieee802.3-c45") >= 0)
+ return true;
+
+ if (fwnode_property_match_string(child, "compatible",
+ "ethernet-phy-ieee802.3-c22") >= 0)
+ return true;
+
+ if (is_of_node(child) &&
+ of_match_node(whitelist_phys, to_of_node(child))) {
+ pr_warn(FW_WARN
+ "%s: Whitelisted compatible string. Please remove\n",
+ fwnode_get_name(child));
+ return true;
+ }
+
+ if (!fwnode_property_present(child, "compatible"))
+ return true;
+
+ return false;
+}
+EXPORT_SYMBOL(fwnode_mdiobus_child_is_phy);
+
+static int fwnode_mdiobus_register_device(struct mii_bus *mdio,
+ struct fwnode_handle *child,
+ u32 addr)
+{
+ struct mdio_device *mdiodev;
+ int rc;
+
+ mdiodev = mdio_device_create(mdio, addr);
+ if (IS_ERR(mdiodev))
+ return PTR_ERR(mdiodev);
+
+ fwnode_handle_get(child);
+ device_set_node(&mdiodev->dev, child);
+
+ /* All data is now stored in the mdiodev struct; register it. */
+ rc = mdio_device_register(mdiodev);
+ if (rc) {
+ mdio_device_free(mdiodev);
+ fwnode_handle_put(child);
+ return rc;
+ }
+
+ dev_dbg(&mdio->dev, "registered mdio device %s at address %i\n",
+ fwnode_get_name(child), addr);
+ return 0;
+}
+
+static inline int fwnode_mdio_parse_addr(struct device *dev,
+ const struct fwnode_handle *fwnode)
+{
+ u32 addr;
+ int ret;
+
+ ret = fwnode_property_read_u32(fwnode, "reg", &addr);
+ if (ret < 0) {
+ dev_err(dev, "%s has invalid PHY address\n",
+ fwnode_get_name(fwnode));
+ return ret;
+ }
+
+ /* A PHY must have a reg property in the range [0-31] */
+ if (addr >= PHY_MAX_ADDR) {
+ dev_err(dev, "%s PHY address %i is too large\n",
+ fwnode_get_name(fwnode), addr);
+ return -EINVAL;
+ }
+
+ return addr;
+}
+
+/**
+ * fwnode_mdiobus_register - Register mii_bus and create PHYs from fwnode
+ * @mdio: pointer to mii_bus structure
+ * @fwnode: pointer to fwnode of MDIO bus.
+ *
+ */
+int fwnode_mdiobus_register(struct mii_bus *mdio, struct fwnode_handle *fwnode)
+{
+ struct fwnode_handle *child;
+ bool scanphys = false;
+ int addr, rc;
+
+ if (!fwnode)
+ return mdiobus_register(mdio);
+
+ if (!fwnode_device_is_available(fwnode))
+ return -ENODEV;
+
+ /* Mask out all PHYs from auto probing. Instead the PHYs listed in
+ * the device tree are populated after the bus has been registered
+ */
+ mdio->phy_mask = ~0;
+
+ device_set_node(&mdio->dev, fwnode);
+
+ /* Get bus level PHY reset GPIO details */
+ mdio->reset_delay_us = DEFAULT_GPIO_RESET_DELAY;
+ fwnode_property_read_u32(fwnode, "reset-delay-us",
+ &mdio->reset_delay_us);
+ mdio->reset_post_delay_us = 0;
+ fwnode_property_read_u32(fwnode, "reset-post-delay-us",
+ &mdio->reset_post_delay_us);
+
+ /* Register the MDIO bus */
+ rc = mdiobus_register(mdio);
+ if (rc)
+ return rc;
+
+ /* Loop over the child nodes and register a phy_device for each phy */
+ fwnode_for_each_available_child_node(fwnode, child) {
+ addr = fwnode_mdio_parse_addr(&mdio->dev, child);
+ if (addr < 0) {
+ scanphys = true;
+ continue;
+ }
+
+ if (fwnode_mdiobus_child_is_phy(child))
+ rc = fwnode_mdiobus_register_phy(mdio, child, addr);
+ else
+ rc = fwnode_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;
+ }
+
+ if (!scanphys)
+ return 0;
+
+ /* auto scan for PHYs with empty reg property */
+ fwnode_for_each_available_child_node(fwnode, child) {
+ /* Skip PHYs with reg property set */
+ if (fwnode_property_present(child, "reg"))
+ continue;
+
+ for (addr = 0; addr < PHY_MAX_ADDR; addr++) {
+ /* skip already registered PHYs */
+ if (mdiobus_is_registered_device(mdio, addr))
+ continue;
+
+ /* be noisy to encourage people to set reg property */
+ dev_info(&mdio->dev, "scan phy %s at address %i\n",
+ fwnode_get_name(child), addr);
+
+ if (fwnode_mdiobus_child_is_phy(child)) {
+ /* -ENODEV is the return code that PHYLIB has
+ * standardized on to indicate that bus
+ * scanning should continue.
+ */
+ rc = fwnode_mdiobus_register_phy(mdio, child,
+ addr);
+ if (!rc)
+ break;
+ if (rc != -ENODEV)
+ goto unregister;
+ }
+ }
+ }
+
+unregister:
+ mdiobus_unregister(mdio);
+ return rc;
+}
+EXPORT_SYMBOL(fwnode_mdiobus_register);
diff --git a/include/linux/fwnode_mdio.h b/include/linux/fwnode_mdio.h
index faf603c48c86..ca380050056d 100644
--- a/include/linux/fwnode_mdio.h
+++ b/include/linux/fwnode_mdio.h
@@ -9,6 +9,7 @@
#include <linux/phy.h>

#if IS_ENABLED(CONFIG_FWNODE_MDIO)
+bool fwnode_mdiobus_child_is_phy(struct fwnode_handle *child);
int fwnode_mdiobus_phy_device_register(struct mii_bus *mdio,
struct phy_device *phy,
struct fwnode_handle *child, u32 addr);
@@ -16,7 +17,13 @@ int fwnode_mdiobus_phy_device_register(struct mii_bus *mdio,
int fwnode_mdiobus_register_phy(struct mii_bus *bus,
struct fwnode_handle *child, u32 addr);

+int fwnode_mdiobus_register(struct mii_bus *mdio, struct fwnode_handle *fwnode);
#else /* CONFIG_FWNODE_MDIO */
+static inline bool fwnode_mdiobus_child_is_phy(struct fwnode_handle *child)
+{
+ return false;
+}
+
int fwnode_mdiobus_phy_device_register(struct mii_bus *mdio,
struct phy_device *phy,
struct fwnode_handle *child, u32 addr)
@@ -30,6 +37,12 @@ static inline int fwnode_mdiobus_register_phy(struct mii_bus *bus,
{
return -EINVAL;
}
+
+static int fwnode_mdiobus_register(struct mii_bus *mdio,
+ struct fwnode_handle *fwnode)
+{
+ return -EINVAL;
+}
#endif

#endif /* __LINUX_FWNODE_MDIO_H */
--
2.34.1
\
 
 \ /
  Last update: 2022-03-25 20:40    [W:0.135 / U:0.924 seconds]
©2003-2020 Jasper Spaans|hosted at Digital Ocean and TransIP|Read the blog|Advertise on this site