Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit bafbdd52 authored by Sergei Shtylyov's avatar Sergei Shtylyov Committed by David S. Miller
Browse files

phylib: Add device reset GPIO support



The PHY devices sometimes do have their reset signal (maybe even power
supply?) tied to some GPIO and sometimes it also does happen that a boot
loader does not leave it deasserted. So far this issue has been attacked
from (as I believe) a wrong angle: by teaching the MAC driver to manipulate
the GPIO in question; that solution, when applied to the device trees, led
to adding the PHY reset GPIO properties to the MAC device node, with one
exception: Cadence MACB driver which could handle the "reset-gpios" prop
in a PHY device subnode. I believe that the correct approach is to teach
the 'phylib' to get the MDIO device reset GPIO from the device tree node
corresponding to this device -- which this patch is doing...

Note that I had to modify the AT803x PHY driver as it would stop working
otherwise -- it made use of the reset GPIO for its own purposes...

Signed-off-by: default avatarSergei Shtylyov <sergei.shtylyov@cogentembedded.com>
Acked-by: default avatarRob Herring <robh@kernel.org>
[geert: Propagate actual errors from fwnode_get_named_gpiod()]
[geert: Avoid destroying initial setup]
[geert: Consolidate GPIO descriptor acquiring code]
Signed-off-by: default avatarGeert Uytterhoeven <geert+renesas@glider.be>
Tested-by: default avatarRichard Leitner <richard.leitner@skidata.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 62b32379
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -53,6 +53,8 @@ Optional Properties:
  to ensure the integrated PHY is used. The absence of this property indicates
  the muxers should be configured so that the external PHY is used.

- reset-gpios: The GPIO phandle and specifier for the PHY reset signal.

Example:

ethernet-phy@0 {
+3 −15
Original line number Diff line number Diff line
@@ -71,7 +71,6 @@ MODULE_LICENSE("GPL");

struct at803x_priv {
	bool phy_reset:1;
	struct gpio_desc *gpiod_reset;
};

struct at803x_context {
@@ -254,22 +253,11 @@ static int at803x_probe(struct phy_device *phydev)
{
	struct device *dev = &phydev->mdio.dev;
	struct at803x_priv *priv;
	struct gpio_desc *gpiod_reset;

	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
	if (!priv)
		return -ENOMEM;

	if (phydev->drv->phy_id != ATH8030_PHY_ID)
		goto does_not_require_reset_workaround;

	gpiod_reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW);
	if (IS_ERR(gpiod_reset))
		return PTR_ERR(gpiod_reset);

	priv->gpiod_reset = gpiod_reset;

does_not_require_reset_workaround:
	phydev->priv = priv;

	return 0;
@@ -343,14 +331,14 @@ static void at803x_link_change_notify(struct phy_device *phydev)
	 * cannot recover from by software.
	 */
	if (phydev->state == PHY_NOLINK) {
		if (priv->gpiod_reset && !priv->phy_reset) {
		if (phydev->mdio.reset && !priv->phy_reset) {
			struct at803x_context context;

			at803x_context_save(phydev, &context);

			gpiod_set_value(priv->gpiod_reset, 1);
			phy_device_reset(phydev, 1);
			msleep(1);
			gpiod_set_value(priv->gpiod_reset, 0);
			phy_device_reset(phydev, 0);
			msleep(1);

			at803x_context_restore(phydev, &context);
+21 −0
Original line number Diff line number Diff line
@@ -38,6 +38,7 @@
#include <linux/phy.h>
#include <linux/io.h>
#include <linux/uaccess.h>
#include <linux/gpio/consumer.h>

#include <asm/irq.h>

@@ -48,9 +49,26 @@

int mdiobus_register_device(struct mdio_device *mdiodev)
{
	struct gpio_desc *gpiod = NULL;

	if (mdiodev->bus->mdio_map[mdiodev->addr])
		return -EBUSY;

	/* Deassert the optional reset signal */
	if (mdiodev->dev.of_node)
		gpiod = fwnode_get_named_gpiod(&mdiodev->dev.of_node->fwnode,
					       "reset-gpios", 0, GPIOD_OUT_LOW,
					       "PHY reset");
	if (PTR_ERR(gpiod) == -ENOENT)
		gpiod = NULL;
	else if (IS_ERR(gpiod))
		return PTR_ERR(gpiod);

	mdiodev->reset = gpiod;

	/* Assert the reset signal again */
	mdio_device_reset(mdiodev, 1);

	mdiodev->bus->mdio_map[mdiodev->addr] = mdiodev;

	return 0;
@@ -420,6 +438,9 @@ void mdiobus_unregister(struct mii_bus *bus)
		if (!mdiodev)
			continue;

		if (mdiodev->reset)
			gpiod_put(mdiodev->reset);

		mdiodev->device_remove(mdiodev);
		mdiodev->device_free(mdiodev);
	}
+23 −2
Original line number Diff line number Diff line
@@ -12,6 +12,8 @@
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt

#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/gpio/consumer.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/kernel.h>
@@ -114,6 +116,13 @@ void mdio_device_remove(struct mdio_device *mdiodev)
}
EXPORT_SYMBOL(mdio_device_remove);

void mdio_device_reset(struct mdio_device *mdiodev, int value)
{
	if (mdiodev->reset)
		gpiod_set_value(mdiodev->reset, value);
}
EXPORT_SYMBOL(mdio_device_reset);

/**
 * mdio_probe - probe an MDIO device
 * @dev: device to probe
@@ -128,8 +137,16 @@ static int mdio_probe(struct device *dev)
	struct mdio_driver *mdiodrv = to_mdio_driver(drv);
	int err = 0;

	if (mdiodrv->probe)
	if (mdiodrv->probe) {
		/* Deassert the reset signal */
		mdio_device_reset(mdiodev, 0);

		err = mdiodrv->probe(mdiodev);
		if (err) {
			/* Assert the reset signal */
			mdio_device_reset(mdiodev, 1);
		}
	}

	return err;
}
@@ -140,9 +157,13 @@ static int mdio_remove(struct device *dev)
	struct device_driver *drv = mdiodev->dev.driver;
	struct mdio_driver *mdiodrv = to_mdio_driver(drv);

	if (mdiodrv->remove)
	if (mdiodrv->remove) {
		mdiodrv->remove(mdiodev);

		/* Assert the reset signal */
		mdio_device_reset(mdiodev, 1);
	}

	return 0;
}

+30 −2
Original line number Diff line number Diff line
@@ -632,6 +632,9 @@ int phy_device_register(struct phy_device *phydev)
	if (err)
		return err;

	/* Deassert the reset signal */
	phy_device_reset(phydev, 0);

	/* Run all of the fixups for this PHY */
	err = phy_scan_fixups(phydev);
	if (err) {
@@ -650,6 +653,9 @@ int phy_device_register(struct phy_device *phydev)
	return 0;

 out:
	/* Assert the reset signal */
	phy_device_reset(phydev, 1);

	mdiobus_unregister_device(&phydev->mdio);
	return err;
}
@@ -666,6 +672,10 @@ EXPORT_SYMBOL(phy_device_register);
void phy_device_remove(struct phy_device *phydev)
{
	device_del(&phydev->mdio.dev);

	/* Assert the reset signal */
	phy_device_reset(phydev, 1);

	mdiobus_unregister_device(&phydev->mdio);
}
EXPORT_SYMBOL(phy_device_remove);
@@ -849,6 +859,9 @@ int phy_init_hw(struct phy_device *phydev)
{
	int ret = 0;

	/* Deassert the reset signal */
	phy_device_reset(phydev, 0);

	if (!phydev->drv || !phydev->drv->config_init)
		return 0;

@@ -1126,6 +1139,9 @@ void phy_detach(struct phy_device *phydev)
	put_device(&phydev->mdio.dev);
	if (ndev_owner != bus->owner)
		module_put(bus->owner);

	/* Assert the reset signal */
	phy_device_reset(phydev, 1);
}
EXPORT_SYMBOL(phy_detach);

@@ -1811,8 +1827,16 @@ static int phy_probe(struct device *dev)
	/* Set the state to READY by default */
	phydev->state = PHY_READY;

	if (phydev->drv->probe)
	if (phydev->drv->probe) {
		/* Deassert the reset signal */
		phy_device_reset(phydev, 0);

		err = phydev->drv->probe(phydev);
		if (err) {
			/* Assert the reset signal */
			phy_device_reset(phydev, 1);
		}
	}

	mutex_unlock(&phydev->lock);

@@ -1829,8 +1853,12 @@ static int phy_remove(struct device *dev)
	phydev->state = PHY_DOWN;
	mutex_unlock(&phydev->lock);

	if (phydev->drv && phydev->drv->remove)
	if (phydev->drv && phydev->drv->remove) {
		phydev->drv->remove(phydev);

		/* Assert the reset signal */
		phy_device_reset(phydev, 1);
	}
	phydev->drv = NULL;

	return 0;
Loading