Re: [PATCH RFT 1/2] phylib: add device reset GPIO support

From: Rob Herring
Date: Tue May 03 2016 - 13:03:48 EST


On Fri, Apr 29, 2016 at 01:12:54AM +0300, Sergei Shtylyov wrote:
> 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 as it made use of the reset GPIO for its own purposes...
>
> Signed-off-by: Sergei Shtylyov <sergei.shtylyov@xxxxxxxxxxxxxxxxxx>
>
> ---
> Changes in version 2:
> - reformatted the changelog;
> - resolved rejects, refreshed the patch.
>
> Documentation/devicetree/bindings/net/phy.txt | 2 +
> Documentation/devicetree/bindings/net/phy.txt | 2 +

Acked-by: Rob Herring <robh@xxxxxxxxxx>

> drivers/net/phy/at803x.c | 19 ++------------
> drivers/net/phy/mdio_bus.c | 4 +++
> drivers/net/phy/mdio_device.c | 27 +++++++++++++++++++--
> drivers/net/phy/phy_device.c | 33 ++++++++++++++++++++++++--
> drivers/of/of_mdio.c | 16 ++++++++++++
> include/linux/mdio.h | 3 ++
> include/linux/phy.h | 5 +++
> 8 files changed, 89 insertions(+), 20 deletions(-)
>
> Index: net-next/Documentation/devicetree/bindings/net/phy.txt
> ===================================================================
> --- net-next.orig/Documentation/devicetree/bindings/net/phy.txt
> +++ net-next/Documentation/devicetree/bindings/net/phy.txt
> @@ -35,6 +35,8 @@ Optional Properties:
> - broken-turn-around: If set, indicates the PHY device does not correctly
> release the turn around line low at the end of a MDIO transaction.
>
> +- reset-gpios: The GPIO phandle and specifier for the PHY reset signal.
> +
> Example:
>
> ethernet-phy@0 {
> Index: net-next/drivers/net/phy/at803x.c
> ===================================================================
> --- net-next.orig/drivers/net/phy/at803x.c
> +++ net-next/drivers/net/phy/at803x.c
> @@ -65,7 +65,6 @@ MODULE_LICENSE("GPL");
>
> struct at803x_priv {
> bool phy_reset:1;
> - struct gpio_desc *gpiod_reset;
> };
>
> struct at803x_context {
> @@ -271,22 +270,10 @@ static int at803x_probe(struct phy_devic
> {
> 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;
> @@ -361,14 +348,14 @@ static void at803x_link_change_notify(st
> */
> if (phydev->drv->phy_id == ATH8030_PHY_ID) {
> 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);
> Index: net-next/drivers/net/phy/mdio_bus.c
> ===================================================================
> --- net-next.orig/drivers/net/phy/mdio_bus.c
> +++ net-next/drivers/net/phy/mdio_bus.c
> @@ -35,6 +35,7 @@
> #include <linux/phy.h>
> #include <linux/io.h>
> #include <linux/uaccess.h>
> +#include <linux/gpio/consumer.h>
>
> #include <asm/irq.h>
>
> @@ -371,6 +372,9 @@ void mdiobus_unregister(struct mii_bus *
> if (!mdiodev)
> continue;
>
> + if (mdiodev->reset)
> + gpiod_put(mdiodev->reset);
> +
> mdiodev->device_remove(mdiodev);
> mdiodev->device_free(mdiodev);
> }
> Index: net-next/drivers/net/phy/mdio_device.c
> ===================================================================
> --- net-next.orig/drivers/net/phy/mdio_device.c
> +++ net-next/drivers/net/phy/mdio_device.c
> @@ -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>
> @@ -103,6 +105,13 @@ void mdio_device_remove(struct mdio_devi
> }
> 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
> @@ -117,9 +126,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);
>
> + /* Assert the reset signal */
> + mdio_device_reset(mdiodev, 1);
> + }
> +
> return err;
> }
>
> @@ -129,9 +145,16 @@ static int mdio_remove(struct device *de
> struct device_driver *drv = mdiodev->dev.driver;
> struct mdio_driver *mdiodrv = to_mdio_driver(drv);
>
> - if (mdiodrv->remove)
> + if (mdiodrv->remove) {
> + /* Deassert the reset signal */
> + mdio_device_reset(mdiodev, 0);
> +
> mdiodrv->remove(mdiodev);
>
> + /* Assert the reset signal */
> + mdio_device_reset(mdiodev, 1);
> + }
> +
> return 0;
> }
>
> Index: net-next/drivers/net/phy/phy_device.c
> ===================================================================
> --- net-next.orig/drivers/net/phy/phy_device.c
> +++ net-next/drivers/net/phy/phy_device.c
> @@ -589,6 +589,9 @@ int phy_device_register(struct phy_devic
> 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) {
> @@ -604,9 +607,15 @@ int phy_device_register(struct phy_devic
> goto out;
> }
>
> + /* Assert the reset signal */
> + phy_device_reset(phydev, 1);
> +
> return 0;
>
> out:
> + /* Assert the reset signal */
> + phy_device_reset(phydev, 1);
> +
> mdiobus_unregister_device(&phydev->mdio);
> return err;
> }
> @@ -792,6 +801,9 @@ int phy_init_hw(struct phy_device *phyde
> {
> int ret = 0;
>
> + /* Deassert the reset signal */
> + phy_device_reset(phydev, 0);
> +
> if (!phydev->drv || !phydev->drv->config_init)
> return 0;
>
> @@ -997,6 +1009,9 @@ void phy_detach(struct phy_device *phyde
>
> put_device(&phydev->mdio.dev);
> module_put(bus->owner);
> +
> + /* Assert the reset signal */
> + phy_device_reset(phydev, 1);
> }
> EXPORT_SYMBOL(phy_detach);
>
> @@ -1596,9 +1611,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);
>
> + /* Assert the reset signal */
> + phy_device_reset(phydev, 1);
> + }
> +
> mutex_unlock(&phydev->lock);
>
> return err;
> @@ -1612,8 +1634,15 @@ static int phy_remove(struct device *dev
> phydev->state = PHY_DOWN;
> mutex_unlock(&phydev->lock);
>
> - if (phydev->drv->remove)
> + if (phydev->drv->remove) {
> + /* Deassert the reset signal */
> + phy_device_reset(phydev, 0);
> +
> phydev->drv->remove(phydev);
> +
> + /* Assert the reset signal */
> + phy_device_reset(phydev, 1);
> + }
> phydev->drv = NULL;
>
> return 0;
> Index: net-next/drivers/of/of_mdio.c
> ===================================================================
> --- net-next.orig/drivers/of/of_mdio.c
> +++ net-next/drivers/of/of_mdio.c
> @@ -44,6 +44,7 @@ static int of_get_phy_id(struct device_n
> static void of_mdiobus_register_phy(struct mii_bus *mdio,
> struct device_node *child, u32 addr)
> {
> + struct gpio_desc *gpiod;
> struct phy_device *phy;
> bool is_c45;
> int rc;
> @@ -52,10 +53,17 @@ static void of_mdiobus_register_phy(stru
> is_c45 = of_device_is_compatible(child,
> "ethernet-phy-ieee802.3-c45");
>
> + gpiod = fwnode_get_named_gpiod(&child->fwnode, "reset-gpios");
> + /* Deassert the reset signal */
> + if (!IS_ERR(gpiod))
> + gpiod_direction_output(gpiod, 0);
> if (!is_c45 && !of_get_phy_id(child, &phy_id))
> phy = phy_device_create(mdio, addr, phy_id, 0, NULL);
> else
> phy = get_phy_device(mdio, addr, is_c45);
> + /* Assert the reset signal again */
> + if (!IS_ERR(gpiod))
> + gpiod_set_value(gpiod, 1);
> if (IS_ERR(phy))
> return;
>
> @@ -75,6 +83,9 @@ static void of_mdiobus_register_phy(stru
> of_node_get(child);
> phy->mdio.dev.of_node = child;
>
> + if (!IS_ERR(gpiod))
> + phy->mdio.reset = gpiod;
> +
> /* All data is now stored in the phy struct;
> * register it */
> rc = phy_device_register(phy);
> @@ -92,6 +103,7 @@ static void of_mdiobus_register_device(s
> struct device_node *child, u32 addr)
> {
> struct mdio_device *mdiodev;
> + struct gpio_desc *gpiod;
> int rc;
>
> mdiodev = mdio_device_create(mdio, addr);
> @@ -104,6 +116,10 @@ static void of_mdiobus_register_device(s
> of_node_get(child);
> mdiodev->dev.of_node = child;
>
> + gpiod = fwnode_get_named_gpiod(&child->fwnode, "reset-gpios");
> + if (!IS_ERR(gpiod))
> + mdiodev->reset = gpiod;
> +
> /* All data is now stored in the mdiodev struct; register it. */
> rc = mdio_device_register(mdiodev);
> if (rc) {
> Index: net-next/include/linux/mdio.h
> ===================================================================
> --- net-next.orig/include/linux/mdio.h
> +++ net-next/include/linux/mdio.h
> @@ -11,6 +11,7 @@
>
> #include <uapi/linux/mdio.h>
>
> +struct gpio_desc;
> struct mii_bus;
>
> /* Multiple levels of nesting are possible. However typically this is
> @@ -37,6 +38,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)
>
> @@ -69,6 +71,7 @@ void mdio_device_free(struct mdio_device
> 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);
>
> Index: net-next/include/linux/phy.h
> ===================================================================
> --- net-next.orig/include/linux/phy.h
> +++ net-next/include/linux/phy.h
> @@ -769,6 +769,11 @@ static inline int phy_read_status(struct
> return phydev->drv->read_status(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)
>
>