From be15d99d8ebc44b676c85737a1c09f5f7df317bd Mon Sep 17 00:00:00 2001 From: Arthur Crepin Leblond Date: Thu, 24 Sep 2026 11:48:11 +0200 Subject: [PATCH 1/3] dt-bindings: net: wiznet,w5100: convert to DT schema Convert the Wiznet w5x00 SPI Ethernet controller binding from plain text to DT schema. Change the trigger type from edge-falling to low to match the driver implementation. Add wiznet,w5100.yaml entry in MAINTAINERS. Signed-off-by: Arthur Crepin Leblond Reviewed-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260924-wiznet-link-gpio-v11-1-42a332517b30@marmottus.net Signed-off-by: Jakub Kicinski --- .../devicetree/bindings/net/wiznet,w5100.yaml | 68 +++++++++++++++++++ .../devicetree/bindings/net/wiznet,w5x00.txt | 50 -------------- MAINTAINERS | 6 ++ 3 files changed, 74 insertions(+), 50 deletions(-) create mode 100644 Documentation/devicetree/bindings/net/wiznet,w5100.yaml delete mode 100644 Documentation/devicetree/bindings/net/wiznet,w5x00.txt diff --git a/Documentation/devicetree/bindings/net/wiznet,w5100.yaml b/Documentation/devicetree/bindings/net/wiznet,w5100.yaml new file mode 100644 index 000000000000..df83f90b3919 --- /dev/null +++ b/Documentation/devicetree/bindings/net/wiznet,w5100.yaml @@ -0,0 +1,68 @@ +# 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 + +description: | + This is a standalone 10/100 MBit Ethernet controller with SPI interface. + +allOf: + - $ref: /schemas/net/ethernet-controller.yaml# + - $ref: /schemas/spi/spi-peripheral-props.yaml# + +properties: + compatible: + enum: + - wiznet,w5100 + - wiznet,w5200 + - wiznet,w5500 + + interrupts: + maxItems: 1 + description: Main interrupt (INT pin on the device). + + 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 + +unevaluatedProperties: false + +examples: + - | + /* Example (for Raspberry Pi with pin control stuff for GPIO irq) */ + + #include + + spi { + #address-cells = <1>; + #size-cells = <0>; + + ethernet@0 { + compatible = "wiznet,w5500"; + reg = <0>; + interrupt-parent = <&gpio>; + interrupts = <25 IRQ_TYPE_LEVEL_LOW>; + local-mac-address = [ a0 b0 c0 d0 e0 f0 ]; + pinctrl-0 = <ð1_pins>; + pinctrl-names = "default"; + spi-max-frequency = <30000000>; + }; + }; +... diff --git a/Documentation/devicetree/bindings/net/wiznet,w5x00.txt b/Documentation/devicetree/bindings/net/wiznet,w5x00.txt deleted file mode 100644 index e9665798c4be..000000000000 --- a/Documentation/devicetree/bindings/net/wiznet,w5x00.txt +++ /dev/null @@ -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 = <ð1_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 */ - }; -}; diff --git a/MAINTAINERS b/MAINTAINERS index 6de1ff058db6..737f076f31c7 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -29455,6 +29455,12 @@ M: Miloslav Trmac S: Maintained F: drivers/input/misc/wistron_btns.c +WIZNET W5100 ETHERNET BINDINGS +M: Arthur Crepin Leblond +L: netdev@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/net/wiznet,w5100.yaml + WMI BINARY MOF DRIVER M: Armin Wolf R: Thomas Weißschuh From 50c4bbaa0d36fb924649943159b6f4b73586e256 Mon Sep 17 00:00:00 2001 From: Arthur Crepin Leblond Date: Thu, 24 Sep 2026 11:48:12 +0200 Subject: [PATCH 2/3] dt-bindings: net: wiznet,w5100: add link status interrupt Add an optional link status interrupt which can be connected to the LINKLED pin on the device to detect link status changes. This second interrupt is only supported on wiznet,w5500. Signed-off-by: Arthur Crepin Leblond Acked-by: Conor Dooley Link: https://patch.msgid.link/20260924-wiznet-link-gpio-v11-2-42a332517b30@marmottus.net Signed-off-by: Jakub Kicinski --- .../devicetree/bindings/net/wiznet,w5100.yaml | 34 +++++++++++++++---- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/Documentation/devicetree/bindings/net/wiznet,w5100.yaml b/Documentation/devicetree/bindings/net/wiznet,w5100.yaml index df83f90b3919..b0d1e1d699dc 100644 --- a/Documentation/devicetree/bindings/net/wiznet,w5100.yaml +++ b/Documentation/devicetree/bindings/net/wiznet,w5100.yaml @@ -12,10 +12,6 @@ maintainers: description: | This is a standalone 10/100 MBit Ethernet controller with SPI interface. -allOf: - - $ref: /schemas/net/ethernet-controller.yaml# - - $ref: /schemas/spi/spi-peripheral-props.yaml# - properties: compatible: enum: @@ -24,8 +20,16 @@ properties: - wiznet,w5500 interrupts: - maxItems: 1 - description: Main interrupt (INT pin on the device). + 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 @@ -42,6 +46,21 @@ required: - 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: @@ -58,7 +77,8 @@ examples: compatible = "wiznet,w5500"; reg = <0>; interrupt-parent = <&gpio>; - interrupts = <25 IRQ_TYPE_LEVEL_LOW>; + interrupts = <25 IRQ_TYPE_LEVEL_LOW>, + <24 IRQ_TYPE_EDGE_BOTH>; local-mac-address = [ a0 b0 c0 d0 e0 f0 ]; pinctrl-0 = <ð1_pins>; pinctrl-names = "default"; From 323767fa31a284b37703b59a1aaabb88162bfa37 Mon Sep 17 00:00:00 2001 From: Arthur Crepin Leblond Date: Thu, 24 Sep 2026 11:48:13 +0200 Subject: [PATCH 3/3] w5100: detect carrier state using link status bit and optional interrupt Detect the carrier state on the w5500 Ethernet controller using the Link Status bit from the PHY Configuration register (PHYCFGR). On w5100/w5200, which lack this register, get_link() is not implemented and the carrier state is not reported (netif_carrier_(on|off) is not called). For the w5500, add an optional interrupt, wired to the LINKLED pin of the device, to detect link status changes with a read of the PHYCFGR register and update of the carrier state with netif_carrier_(on|off). If this interrupt is not set in the DT binding, get_link() is not implemented and the carrier state is not reported (netif_carrier_(on|off) is not called). The interrupt handler and the PHYCFGR register readings are synchronized using a mutex. A few changes are also brought for the bring-up/teardown synchronization: - The net device is registered last in the probe() function so no operation can be performed before the probing is finished - The net device is unregistered first in remove() - dev_name is now used instead of netdev_name - w5100_stop() calls cancel_work_sync() and flush_work() to make sure there is no race condition or unfinished work - w5100_suspend() calls w5100_stop() - w5100_resume() calls w5100_open() - The main irq is not automatically enabled in w5100_probe() but in w5100_open() and disabled in w5100_stop() 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: link detection is now done using a second interrupt rather than a gpio with a documented DT binding. Set the WIZNET entry as DRIVER and add drivers/net/ethernet/wiznet in MAINTAINERS file. Signed-off-by: Arthur Crepin Leblond Fixes: dacf281771a9 ("w5100: remove unused gpio link detection") Link: https://patch.msgid.link/20260924-wiznet-link-gpio-v11-3-42a332517b30@marmottus.net Signed-off-by: Jakub Kicinski --- MAINTAINERS | 3 +- drivers/net/ethernet/wiznet/w5100.c | 234 ++++++++++++++++++++++++---- 2 files changed, 206 insertions(+), 31 deletions(-) diff --git a/MAINTAINERS b/MAINTAINERS index 737f076f31c7..51873349ba91 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -29455,11 +29455,12 @@ M: Miloslav Trmac S: Maintained F: drivers/input/misc/wistron_btns.c -WIZNET W5100 ETHERNET BINDINGS +WIZNET W5100 ETHERNET DRIVER M: Arthur Crepin Leblond 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 diff --git a/drivers/net/ethernet/wiznet/w5100.c b/drivers/net/ethernet/wiznet/w5100.c index 53d8dc642fbd..dff39ba8103c 100644 --- a/drivers/net/ethernet/wiznet/w5100.c +++ b/drivers/net/ethernet/wiznet/w5100.c @@ -18,9 +18,11 @@ #include #include #include +#include #include #include #include +#include #include #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; }