Merge branch 'w5100-restore-gpio-based-link-detection'

Arthur Crepin Leblond says:

====================
w5100: restore GPIO-based link detection

In the previous version of the w5100 driver, the LINKLED pin could be
wired to a GPIO to detect the link status changes via an interrupt.
This only works on w5500 as it is the only one documenting its LINKLED
pin to hold the link status (on w5100 the LINKLED pin is changing with
RX/TX activity).

This series of patches is bringing back the link status detection on
w5500 using the Link Status bit of the PHY Configuration Register.
Additionally, the LINKLED pin can also be wired to detect link status
changes and read the register in the interrupt handler.
====================

Link: https://patch.msgid.link/20260924-wiznet-link-gpio-v11-0-42a332517b30@marmottus.net
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
Jakub Kicinski 2026-09-29 17:31:24 -07:00
commit 7ab4bbdca9
4 changed files with 299 additions and 80 deletions

View File

@ -0,0 +1,88 @@
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
%YAML 1.2
---
$id: http://devicetree.org/schemas/net/wiznet,w5100.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: Wiznet w5100/w5200/w5500 SPI Ethernet Controller
maintainers:
- Arthur Crepin Leblond <arthur@marmottus.net>
description: |
This is a standalone 10/100 MBit Ethernet controller with SPI interface.
properties:
compatible:
enum:
- wiznet,w5100
- wiznet,w5200
- wiznet,w5500
interrupts:
minItems: 1
description:
The main interrupt (INT pin) is mandatory on all devices. The w5500
additionally supports an optional second interrupt for link status changes
(LINKLED pin). If it is not specified, link state changes will not be
reported.
items:
- description: Main interrupt (INT pin on the device).
- description:
Optional interrupt for link status changes (LINKLED pin on w5500).
reg:
maxItems: 1
spi-max-frequency:
description:
According to the datasheets, w5500 and w5200 allow a maximum of 80 MHz
while w5100 is reported by users to allow a maximum of 14 MHz. However,
board designs may need to limit this value.
maximum: 80000000
required:
- compatible
- reg
- interrupts
allOf:
- $ref: /schemas/net/ethernet-controller.yaml#
- $ref: /schemas/spi/spi-peripheral-props.yaml#
- if:
not:
properties:
compatible:
contains:
enum:
- wiznet,w5500
then:
properties:
interrupts:
maxItems: 1
unevaluatedProperties: false
examples:
- |
/* Example (for Raspberry Pi with pin control stuff for GPIO irq) */
#include <dt-bindings/interrupt-controller/irq.h>
spi {
#address-cells = <1>;
#size-cells = <0>;
ethernet@0 {
compatible = "wiznet,w5500";
reg = <0>;
interrupt-parent = <&gpio>;
interrupts = <25 IRQ_TYPE_LEVEL_LOW>,
<24 IRQ_TYPE_EDGE_BOTH>;
local-mac-address = [ a0 b0 c0 d0 e0 f0 ];
pinctrl-0 = <&eth1_pins>;
pinctrl-names = "default";
spi-max-frequency = <30000000>;
};
};
...

View File

@ -1,50 +0,0 @@
* Wiznet w5x00
This is a standalone 10/100 MBit Ethernet controller with SPI interface.
For each device connected to a SPI bus, define a child node within
the SPI master node.
Required properties:
- compatible: Should be one of the following strings:
"wiznet,w5100"
"wiznet,w5200"
"wiznet,w5500"
- reg: Specify the SPI chip select the chip is wired to.
- interrupts: Specify the interrupt index within the interrupt controller (referred
to above in interrupt-parent) and interrupt type. w5x00 natively
generates falling edge interrupts, however, additional board logic
might invert the signal.
- pinctrl-names: List of assigned state names, see pinctrl binding documentation.
- pinctrl-0: List of phandles to configure the GPIO pin used as interrupt line,
see also generic and your platform specific pinctrl binding
documentation.
Optional properties:
- spi-max-frequency: Maximum frequency of the SPI bus when accessing the w5500.
According to the w5500 datasheet, the chip allows a maximum of 80 MHz, however,
board designs may need to limit this value.
- local-mac-address: See ethernet.txt in the same directory.
Example (for Raspberry Pi with pin control stuff for GPIO irq):
&spi {
ethernet@0: w5500@0 {
compatible = "wiznet,w5500";
reg = <0>;
pinctrl-names = "default";
pinctrl-0 = <&eth1_pins>;
interrupt-parent = <&gpio>;
interrupts = <25 IRQ_TYPE_EDGE_FALLING>;
spi-max-frequency = <30000000>;
};
};
&gpio {
eth1_pins: eth1_pins {
brcm,pins = <25>;
brcm,function = <0>; /* in */
brcm,pull = <0>; /* none */
};
};

View File

@ -29455,6 +29455,13 @@ M: Miloslav Trmac <mitr@volny.cz>
S: Maintained
F: drivers/input/misc/wistron_btns.c
WIZNET W5100 ETHERNET DRIVER
M: Arthur Crepin Leblond <arthur@marmottus.net>
L: netdev@vger.kernel.org
S: Maintained
F: Documentation/devicetree/bindings/net/wiznet,w5100.yaml
F: drivers/net/ethernet/wiznet/
WMI BINARY MOF DRIVER
M: Armin Wolf <W_Armin@gmx.de>
R: Thomas Weißschuh <linux@weissschuh.net>

View File

@ -18,9 +18,11 @@
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/mutex.h>
#include <linux/io.h>
#include <linux/ioport.h>
#include <linux/interrupt.h>
#include <linux/property.h>
#include <linux/irq.h>
#include "w5100.h"
@ -124,6 +126,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 +158,9 @@ struct w5100_priv {
u16 s0_rx_buf_size;
int irq;
int link_irq;
/* Protects link state and carrier updates */
struct mutex link_lock;
struct napi_struct napi;
struct net_device *ndev;
@ -345,6 +352,82 @@ static void w5500_memory_configure(struct w5100_priv *priv)
}
}
static int w5500_get_phycfgr_lnk(struct net_device *ndev)
{
struct w5100_priv *priv = netdev_priv(ndev);
int retries = 3;
int ret;
do {
ret = w5100_read(priv, W5500_PHYCFGR);
if (ret < 0)
netif_err(priv, link, ndev,
"failed to read link status: %d\n", ret);
} while (ret < 0 && --retries);
if (ret < 0)
return ret;
return ret & PHYCFGR_LNK;
}
static void w5500_report_carrier_state(struct net_device *ndev)
{
struct w5100_priv *priv = netdev_priv(ndev);
int state;
mutex_lock(&priv->link_lock);
state = w5500_get_phycfgr_lnk(ndev);
if (state > 0) {
netif_info(priv, link, ndev, "link is up\n");
netif_carrier_on(ndev);
} else if (state == 0) {
netif_info(priv, link, ndev, "link is down\n");
netif_carrier_off(ndev);
}
mutex_unlock(&priv->link_lock);
}
static irqreturn_t w5500_detect_link_interrupt(int irq, void *ndev_instance)
{
struct net_device *ndev = ndev_instance;
if (netif_running(ndev))
w5500_report_carrier_state(ndev);
return IRQ_HANDLED;
}
static u32 w5500_get_link(struct net_device *ndev)
{
struct w5100_priv *priv = netdev_priv(ndev);
int state;
bool link;
mutex_lock(&priv->link_lock);
if (!netif_device_present(ndev)) {
link = netif_carrier_ok(ndev);
goto out;
}
state = w5500_get_phycfgr_lnk(ndev);
if (state < 0) {
link = netif_carrier_ok(ndev);
goto out;
}
link = state > 0;
out:
mutex_unlock(&priv->link_lock);
return link;
}
static int w5100_hw_reset(struct w5100_priv *priv)
{
u32 rtr;
@ -448,12 +531,28 @@ static void w5100_restart(struct net_device *ndev)
{
struct w5100_priv *priv = netdev_priv(ndev);
if (!netif_running(ndev) || !netif_device_present(ndev))
return;
disable_irq(priv->irq);
if (priv->link_irq > 0)
disable_irq(priv->link_irq);
netif_stop_queue(ndev);
w5100_hw_close(priv);
flush_work(&priv->rx_work);
flush_work(&priv->tx_work);
w5100_hw_reset(priv);
enable_irq(priv->irq);
w5100_hw_start(priv);
ndev->stats.tx_errors++;
netif_trans_update(ndev);
netif_wake_queue(ndev);
if (priv->link_irq > 0) {
enable_irq(priv->link_irq);
w5500_report_carrier_state(ndev);
}
}
static void w5100_restart_work(struct work_struct *work)
@ -468,10 +567,7 @@ static void w5100_tx_timeout(struct net_device *ndev, unsigned int txqueue)
{
struct w5100_priv *priv = netdev_priv(ndev);
if (priv->ops->may_sleep)
schedule_work(&priv->restart_work);
else
w5100_restart(ndev);
schedule_work(&priv->restart_work);
}
static void w5100_tx_skb(struct net_device *ndev, struct sk_buff *skb)
@ -564,7 +660,8 @@ static void w5100_rx_work(struct work_struct *work)
while ((skb = w5100_rx_skb(priv->ndev)))
netif_rx(skb);
w5100_enable_intr(priv);
if (netif_running(priv->ndev))
w5100_enable_intr(priv);
}
static int w5100_napi_poll(struct napi_struct *napi, int budget)
@ -656,9 +753,17 @@ static int w5100_open(struct net_device *ndev)
struct w5100_priv *priv = netdev_priv(ndev);
netif_info(priv, ifup, ndev, "enabling\n");
w5100_hw_reset(priv);
enable_irq(priv->irq);
w5100_hw_start(priv);
napi_enable(&priv->napi);
netif_start_queue(ndev);
if (priv->link_irq > 0) {
enable_irq(priv->link_irq);
w5500_report_carrier_state(ndev);
}
return 0;
}
@ -667,13 +772,40 @@ static int w5100_stop(struct net_device *ndev)
struct w5100_priv *priv = netdev_priv(ndev);
netif_info(priv, ifdown, ndev, "shutting down\n");
w5100_hw_close(priv);
netif_carrier_off(ndev);
disable_irq(priv->irq);
if (priv->link_irq > 0)
disable_irq(priv->link_irq);
netif_stop_queue(ndev);
napi_disable(&priv->napi);
cancel_work_sync(&priv->restart_work);
cancel_work_sync(&priv->setrx_work);
w5100_hw_close(priv);
flush_work(&priv->rx_work);
flush_work(&priv->tx_work);
if (priv->link_irq > 0) {
mutex_lock(&priv->link_lock);
netif_carrier_off(ndev);
mutex_unlock(&priv->link_lock);
}
return 0;
}
static const struct ethtool_ops w5500_ethtool_ops = {
.get_drvinfo = w5100_get_drvinfo,
.get_msglevel = w5100_get_msglevel,
.set_msglevel = w5100_set_msglevel,
.get_link = w5500_get_link,
.get_regs_len = w5100_get_regs_len,
.get_regs = w5100_get_regs,
};
static const struct ethtool_ops w5100_ethtool_ops = {
.get_drvinfo = w5100_get_drvinfo,
.get_msglevel = w5100_get_msglevel,
@ -721,6 +853,8 @@ int w5100_probe(struct device *dev, const struct w5100_ops *ops,
dev_set_drvdata(dev, ndev);
priv = netdev_priv(ndev);
mutex_init(&priv->link_lock);
switch (ops->chip_id) {
case W5100:
priv->s0_regs = W5100_S0_REGS;
@ -745,15 +879,27 @@ int w5100_probe(struct device *dev, const struct w5100_ops *ops,
break;
default:
err = -EINVAL;
goto err_register;
goto err_mutex;
}
priv->ndev = ndev;
priv->ops = ops;
priv->irq = irq;
priv->link_irq = -EINVAL;
if (ops->chip_id == W5500) {
priv->link_irq = fwnode_irq_get(dev_fwnode(dev), 1);
if (priv->link_irq == -EPROBE_DEFER) {
err = dev_err_probe(dev, priv->link_irq,
"failed to get link irq\n");
goto err_mutex;
}
}
ndev->netdev_ops = &w5100_netdev_ops;
ndev->ethtool_ops = &w5100_ethtool_ops;
ndev->ethtool_ops = priv->link_irq > 0 ? &w5500_ethtool_ops :
&w5100_ethtool_ops;
netif_napi_add_weight(ndev, &priv->napi, w5100_napi_poll, 16);
/* This chip doesn't support VLAN packets with normal MTU,
@ -761,15 +907,11 @@ int w5100_probe(struct device *dev, const struct w5100_ops *ops,
*/
ndev->features |= NETIF_F_VLAN_CHALLENGED;
err = register_netdev(ndev);
if (err < 0)
goto err_register;
priv->xfer_wq = alloc_workqueue("%s", WQ_MEM_RECLAIM | WQ_PERCPU, 0,
netdev_name(ndev));
dev_name(dev));
if (!priv->xfer_wq) {
err = -ENOMEM;
goto err_wq;
goto err_mutex;
}
INIT_WORK(&priv->rx_work, w5100_rx_work);
@ -794,22 +936,45 @@ int w5100_probe(struct device *dev, const struct w5100_ops *ops,
if (ops->may_sleep) {
err = request_threaded_irq(priv->irq, NULL, w5100_interrupt,
IRQF_TRIGGER_LOW | IRQF_ONESHOT,
netdev_name(ndev), ndev);
IRQF_TRIGGER_LOW | IRQF_ONESHOT |
IRQF_NO_AUTOEN,
dev_name(dev), ndev);
} else {
err = request_irq(priv->irq, w5100_interrupt,
IRQF_TRIGGER_LOW, netdev_name(ndev), ndev);
IRQF_TRIGGER_LOW | IRQF_NO_AUTOEN,
dev_name(dev), ndev);
}
if (err)
goto err_hw;
if (priv->link_irq > 0) {
err = request_threaded_irq(priv->link_irq, NULL,
w5500_detect_link_interrupt,
IRQF_TRIGGER_RISING |
IRQF_TRIGGER_FALLING |
IRQF_ONESHOT | IRQF_NO_AUTOEN,
"w5100-link", ndev);
if (err < 0)
goto err_irq;
netif_carrier_off(ndev);
}
err = register_netdev(ndev);
if (err < 0)
goto err_link_irq;
return 0;
err_link_irq:
if (priv->link_irq > 0)
free_irq(priv->link_irq, ndev);
err_irq:
free_irq(priv->irq, ndev);
err_hw:
destroy_workqueue(priv->xfer_wq);
err_wq:
unregister_netdev(ndev);
err_register:
err_mutex:
mutex_destroy(&priv->link_lock);
free_netdev(ndev);
return err;
}
@ -820,14 +985,21 @@ void w5100_remove(struct device *dev)
struct net_device *ndev = dev_get_drvdata(dev);
struct w5100_priv *priv = netdev_priv(ndev);
w5100_hw_reset(priv);
unregister_netdev(ndev);
if (priv->link_irq > 0)
free_irq(priv->link_irq, ndev);
free_irq(priv->irq, ndev);
flush_work(&priv->setrx_work);
flush_work(&priv->restart_work);
destroy_workqueue(priv->xfer_wq);
cancel_work_sync(&priv->setrx_work);
cancel_work_sync(&priv->restart_work);
flush_work(&priv->rx_work);
flush_work(&priv->tx_work);
unregister_netdev(ndev);
w5100_hw_reset(priv);
destroy_workqueue(priv->xfer_wq);
mutex_destroy(&priv->link_lock);
free_netdev(ndev);
}
EXPORT_SYMBOL_GPL(w5100_remove);
@ -839,10 +1011,11 @@ static int w5100_suspend(struct device *dev)
struct w5100_priv *priv = netdev_priv(ndev);
if (netif_running(ndev)) {
netif_carrier_off(ndev);
mutex_lock(&priv->link_lock);
netif_device_detach(ndev);
mutex_unlock(&priv->link_lock);
w5100_hw_close(priv);
w5100_stop(ndev);
}
return 0;
}
@ -853,10 +1026,11 @@ static int w5100_resume(struct device *dev)
struct w5100_priv *priv = netdev_priv(ndev);
if (netif_running(ndev)) {
w5100_hw_reset(priv);
w5100_hw_start(priv);
w5100_open(ndev);
mutex_lock(&priv->link_lock);
netif_device_attach(ndev);
mutex_unlock(&priv->link_lock);
}
return 0;
}