Detect the link status on the w5500 using the Link Status bit from the PHY Configuration register. Also add an optional GPIO line that can be wired to the LINKLED pin of the w5500 as an interrupt to detect link status changes and read the register. On chip variants other than the w5500, which lack this register, the link is always reported as up. Commit dacf281771a9 ("w5100: remove unused gpio link detection") dropped the link_gpio/link_irq handling on the grounds that no devicetree user passed a "link" GPIO at the time and that it used the old gpio interface. This isn't a plain revert of that removal. It uses the new managed GPIO descriptors so that the resources are released automatically on driver removal or probe failure. Signed-off-by: Arthur Crépin Leblond --- drivers/net/ethernet/wiznet/w5100.c | 70 +++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/drivers/net/ethernet/wiznet/w5100.c b/drivers/net/ethernet/wiznet/w5100.c index 53d8dc642fbd..6707a6f27395 100644 --- a/drivers/net/ethernet/wiznet/w5100.c +++ b/drivers/net/ethernet/wiznet/w5100.c @@ -22,6 +22,7 @@ #include #include #include +#include #include "w5100.h" @@ -124,6 +125,8 @@ MODULE_LICENSE("GPL"); */ #define W5500_SIMR 0x0018 /* Socket Interrupt Mask Register */ #define W5500_RTR 0x0019 /* Retry Time-value Register */ +#define W5500_PHYCFGR 0x002e /* PHY Configuration Register */ +#define PHYCFGR_LNK 0x01 /* link status */ #define W5500_S0_REGS 0x10000 @@ -154,6 +157,7 @@ struct w5100_priv { u16 s0_rx_buf_size; int irq; + struct gpio_desc *link_gpio; struct napi_struct napi; struct net_device *ndev; @@ -414,6 +418,16 @@ static void w5100_get_drvinfo(struct net_device *ndev, sizeof(info->bus_info)); } +static u32 w5100_get_link(struct net_device *ndev) +{ + struct w5100_priv *priv = netdev_priv(ndev); + + if (priv->ops->chip_id == W5500) + return w5100_read(priv, W5500_PHYCFGR) & PHYCFGR_LNK; + + return 1; +} + static u32 w5100_get_msglevel(struct net_device *ndev) { struct w5100_priv *priv = netdev_priv(ndev); @@ -616,6 +630,24 @@ static irqreturn_t w5100_interrupt(int irq, void *ndev_instance) return IRQ_HANDLED; } +static irqreturn_t w5100_detect_link(int irq, void *ndev_instance) +{ + struct net_device *ndev = ndev_instance; + struct w5100_priv *priv = netdev_priv(ndev); + + if (netif_running(ndev)) { + if (w5100_get_link(ndev)) { + netif_info(priv, link, ndev, "link is up\n"); + netif_carrier_on(ndev); + } else { + netif_info(priv, link, ndev, "link is down\n"); + netif_carrier_off(ndev); + } + } + + return IRQ_HANDLED; +} + static void w5100_setrx_work(struct work_struct *work) { struct w5100_priv *priv = container_of(work, struct w5100_priv, @@ -659,6 +691,12 @@ static int w5100_open(struct net_device *ndev) w5100_hw_start(priv); napi_enable(&priv->napi); netif_start_queue(ndev); + + if (w5100_get_link(ndev)) + netif_carrier_on(ndev); + else + netif_carrier_off(ndev); + return 0; } @@ -678,6 +716,7 @@ static const struct ethtool_ops w5100_ethtool_ops = { .get_drvinfo = w5100_get_drvinfo, .get_msglevel = w5100_get_msglevel, .set_msglevel = w5100_set_msglevel, + .get_link = w5100_get_link, .get_regs_len = w5100_get_regs_len, .get_regs = w5100_get_regs, }; @@ -751,6 +790,13 @@ int w5100_probe(struct device *dev, const struct w5100_ops *ops, priv->ndev = ndev; priv->ops = ops; priv->irq = irq; + priv->link_gpio = devm_gpiod_get_optional(dev, "link", GPIOD_IN); + if (IS_ERR(priv->link_gpio)) { + err = dev_err_probe(dev, PTR_ERR(priv->link_gpio), + "failed to get link GPIO\n"); + priv->link_gpio = NULL; + goto err_register; + } ndev->netdev_ops = &w5100_netdev_ops; ndev->ethtool_ops = &w5100_ethtool_ops; @@ -803,8 +849,29 @@ int w5100_probe(struct device *dev, const struct w5100_ops *ops, if (err) goto err_hw; + if (priv->link_gpio) { + int link_irq = gpiod_to_irq(priv->link_gpio); + + if (link_irq < 0) { + err = dev_err_probe(dev, link_irq, + "No corresponding irq for link gpio\n"); + goto err_gpio; + } + + err = devm_request_threaded_irq(dev, link_irq, NULL, + w5100_detect_link, + IRQF_TRIGGER_RISING | + IRQF_TRIGGER_FALLING | + IRQF_ONESHOT, + "w5100-link", priv->ndev); + if (err < 0) + goto err_gpio; + } + return 0; +err_gpio: + free_irq(priv->irq, ndev); err_hw: destroy_workqueue(priv->xfer_wq); err_wq: @@ -857,6 +924,9 @@ static int w5100_resume(struct device *dev) w5100_hw_start(priv); netif_device_attach(ndev); + + if (w5100_get_link(ndev)) + netif_carrier_on(ndev); } return 0; } -- 2.55.0