Skip to content

Commit

Permalink
phylib: Add device reset GPIO support
Browse files Browse the repository at this point in the history
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: Sergei Shtylyov <sergei.shtylyov@cogentembedded.com>
Acked-by: Rob 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: Geert Uytterhoeven <geert+renesas@glider.be>
Tested-by: Richard Leitner <richard.leitner@skidata.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
  • Loading branch information
Sergei Shtylyov authored and David S. Miller committed Dec 5, 2017
1 parent 62b3237 commit bafbdd5
Show file tree
Hide file tree
Showing 7 changed files with 87 additions and 19 deletions.
2 changes: 2 additions & 0 deletions Documentation/devicetree/bindings/net/phy.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
18 changes: 3 additions & 15 deletions drivers/net/phy/at803x.c
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,6 @@ MODULE_LICENSE("GPL");

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

struct at803x_context {
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down
21 changes: 21 additions & 0 deletions drivers/net/phy/mdio_bus.c
Original file line number Diff line number Diff line change
Expand Up @@ -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>

Expand All @@ -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;
Expand Down Expand Up @@ -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);
}
Expand Down
25 changes: 23 additions & 2 deletions drivers/net/phy/mdio_device.c
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down Expand Up @@ -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
Expand All @@ -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;
}
Expand All @@ -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;
}

Expand Down
32 changes: 30 additions & 2 deletions drivers/net/phy/phy_device.c
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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;
}
Expand All @@ -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);
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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);

Expand All @@ -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;
Expand Down
3 changes: 3 additions & 0 deletions include/linux/mdio.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include <uapi/linux/mdio.h>
#include <linux/mod_devicetable.h>

struct gpio_desc;
struct mii_bus;

/* Multiple levels of nesting are possible. However typically this is
Expand Down Expand Up @@ -39,6 +40,7 @@ struct mdio_device {
/* Bus address of the MDIO device (0-31) */
int addr;
int flags;
struct gpio_desc *reset;
};
#define to_mdio_device(d) container_of(d, struct mdio_device, dev)

Expand Down Expand Up @@ -71,6 +73,7 @@ void mdio_device_free(struct mdio_device *mdiodev);
struct mdio_device *mdio_device_create(struct mii_bus *bus, int addr);
int mdio_device_register(struct mdio_device *mdiodev);
void mdio_device_remove(struct mdio_device *mdiodev);
void mdio_device_reset(struct mdio_device *mdiodev, int value);
int mdio_driver_register(struct mdio_driver *drv);
void mdio_driver_unregister(struct mdio_driver *drv);
int mdio_device_bus_match(struct device *dev, struct device_driver *drv);
Expand Down
5 changes: 5 additions & 0 deletions include/linux/phy.h
Original file line number Diff line number Diff line change
Expand Up @@ -854,6 +854,11 @@ int phy_aneg_done(struct phy_device *phydev);
int phy_stop_interrupts(struct phy_device *phydev);
int phy_restart_aneg(struct phy_device *phydev);

static inline void phy_device_reset(struct phy_device *phydev, int value)
{
mdio_device_reset(&phydev->mdio, value);
}

#define phydev_err(_phydev, format, args...) \
dev_err(&_phydev->mdio.dev, format, ##args)

Expand Down

0 comments on commit bafbdd5

Please sign in to comment.