* [PATCH net-next v7 0/3] w5100: restore GPIO-based link detection
@ 2026-09-04 12:35 Arthur Crepin Leblond
2026-09-04 12:35 ` [PATCH net-next v7 1/3] dt-bindings: net: wiznet,w5100: convert to DT schema Arthur Crepin Leblond
` (2 more replies)
0 siblings, 3 replies; 9+ messages in thread
From: Arthur Crepin Leblond @ 2026-09-04 12:35 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley
Cc: Arnd Bergmann, netdev, devicetree, linux-kernel,
Arthur Crepin Leblond, Krzysztof Kozlowski
Hi,
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.
Arthur Crepin Leblond
Signed-off-by: Arthur Crepin Leblond <arthur@marmottus.net>
---
- Link to v6: https://patch.msgid.link/20260831-wiznet-link-gpio-v6-0-abee6c5c966e@marmottus.net
- Link to v5: https://patch.msgid.link/20260813-wiznet-link-gpio-v5-0-ac8046f24413@marmottus.net
- Link to v4: https://patch.msgid.link/20260811-wiznet-link-gpio-v4-0-4d5f7da885a8@marmottus.net
- Link to v3: https://patch.msgid.link/20260806-wiznet-link-gpio-v3-0-532d4a143805@marmottus.net
- Link to v2: https://patch.msgid.link/20260804-wiznet-link-gpio-v2-0-3b1d0c870f35@marmottus.net
- Link to v1: https://patch.msgid.link/20260804-wiznet-link-gpio-v1-1-b626fd4f7ccb@marmottus.net
---
Arthur Crepin Leblond (3):
dt-bindings: net: wiznet,w5100: convert to DT schema
dt-bindings: net: wiznet,w5100: add link status interrupt
w5100: detect carrier state using link status bit and optional interrupt
.../devicetree/bindings/net/wiznet,w5100.yaml | 71 ++++++++++++
.../devicetree/bindings/net/wiznet,w5x00.txt | 50 ---------
drivers/net/ethernet/wiznet/w5100.c | 123 +++++++++++++++++++--
3 files changed, 183 insertions(+), 61 deletions(-)
^ permalink raw reply [flat|nested] 9+ messages in thread* [PATCH net-next v7 1/3] dt-bindings: net: wiznet,w5100: convert to DT schema 2026-09-04 12:35 [PATCH net-next v7 0/3] w5100: restore GPIO-based link detection Arthur Crepin Leblond @ 2026-09-04 12:35 ` Arthur Crepin Leblond 2026-09-09 12:38 ` netdev-bot+sashiko 2026-09-04 12:35 ` [PATCH net-next v7 2/3] dt-bindings: net: wiznet,w5100: add link status interrupt Arthur Crepin Leblond 2026-09-04 12:36 ` [PATCH net-next v7 3/3] w5100: detect carrier state using link status bit and optional interrupt Arthur Crepin Leblond 2 siblings, 1 reply; 9+ messages in thread From: Arthur Crepin Leblond @ 2026-09-04 12:35 UTC (permalink / raw) To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley Cc: Arnd Bergmann, netdev, devicetree, linux-kernel, Arthur Crepin Leblond, Krzysztof Kozlowski Convert the Wiznet w5x00 SPI Ethernet controller binding from plain text to DT schema. Signed-off-by: Arthur Crepin Leblond <arthur@marmottus.net> Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com> --- v6: - Put back the reviewed by tag from Krzysztof Kozlowski from v4 v5: - Remove gpio/gpio.h include from DT example - Make the DT interrupts property dynamic from the compatible one - Fix DT bindings errors v4: - Update interrupts in DT bindings v3: - Fix DT binding errors v2: - Convert device tree binding to YAML --- .../devicetree/bindings/net/wiznet,w5100.yaml | 66 ++++++++++++++++++++++ .../devicetree/bindings/net/wiznet,w5x00.txt | 50 ---------------- 2 files changed, 66 insertions(+), 50 deletions(-) diff --git a/Documentation/devicetree/bindings/net/wiznet,w5100.yaml b/Documentation/devicetree/bindings/net/wiznet,w5100.yaml new file mode 100644 index 000000000000..601d828bae92 --- /dev/null +++ b/Documentation/devicetree/bindings/net/wiznet,w5100.yaml @@ -0,0 +1,66 @@ +# 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. + +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 + + reg: + maxItems: 1 + + spi-max-frequency: + description: + According to the w5500 datasheet, the chip allows a maximum of 80 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 <dt-bindings/interrupt-controller/irq.h> + + spi { + #address-cells = <1>; + #size-cells = <0>; + + ethernet@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>; + local-mac-address = [ a0 b0 c0 d0 e0 f0 ]; + }; + }; +... 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 */ - }; -}; -- 2.55.0 ^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH net-next v7 1/3] dt-bindings: net: wiznet,w5100: convert to DT schema 2026-09-04 12:35 ` [PATCH net-next v7 1/3] dt-bindings: net: wiznet,w5100: convert to DT schema Arthur Crepin Leblond @ 2026-09-09 12:38 ` netdev-bot+sashiko 0 siblings, 0 replies; 9+ messages in thread From: netdev-bot+sashiko @ 2026-09-09 12:38 UTC (permalink / raw) To: arthur Cc: andrew+netdev, davem, edumazet, kuba, pabeni, robh, krzk+dt, conor+dt, arnd, netdev, devicetree, linux-kernel, krzysztof.kozlowski Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: Critical: 0 · High: 0 · Medium: 0 · Low: 1 (1) - [Low] In the newly added Documentation/devicetree/bindings/net/wiznet,w5100.yaml, `spi-max-… Pre-existing issues: - [Low] Documentation/devicetree/bindings/net/wiznet,w5100.yaml (added by this patch) constrains `interrupts` only with a count (`maxItems: 1`… -- dt-bindings: net: wiznet,w5100: convert to DT schema This converts the Wiznet w5x00 SPI Ethernet controller binding from plain text to a YAML schema, adding the file wiznet,w5100.yaml and deleting wiznet,w5x00.txt. The new schema enumerates the three compatibles, constrains reg/interrupts/spi-max-frequency, and carries over the Raspberry Pi example. A couple of questions below about the interrupt and spi-max-frequency descriptions. > diff --git a/Documentation/devicetree/bindings/net/wiznet,w5100.yaml b/Documentation/devicetree/bindings/net/wiznet,w5100.yaml > new file mode 100644 > index 0000000000000..601d828bae927 > --- /dev/null > +++ b/Documentation/devicetree/bindings/net/wiznet,w5100.yaml > @@ -0,0 +1,66 @@ [ ... ] > +properties: > + compatible: > + enum: > + - wiznet,w5100 > + - wiznet,w5200 > + - wiznet,w5500 > + > + interrupts: > + maxItems: 1 [Severity: Low] This isn't an issue introduced by this patch, but the interrupt polarity disagreement between the binding and the driver is now the only in-tree record of it, and the wording that described it is being dropped here. The deleted wiznet,w5x00.txt said: w5x00 natively generates falling edge interrupts, however, additional board logic might invert the signal. The new schema has no description and no type constraint under interrupts, so that information disappears from the tree. Meanwhile w5100_probe() in drivers/net/ethernet/wiznet/w5100.c hardcodes a level-low trigger in both registration paths: err = request_threaded_irq(priv->irq, NULL, w5100_interrupt, IRQF_TRIGGER_LOW | IRQF_ONESHOT, netdev_name(ndev), ndev); } else { err = request_irq(priv->irq, w5100_interrupt, IRQF_TRIGGER_LOW, netdev_name(ndev), ndev); and __setup_irq() in kernel/irq/manage.c applies the driver flags over whatever the DT node asked for: if (new->flags & IRQF_TRIGGER_MASK) { ret = __irq_set_trigger(desc, new->flags & IRQF_TRIGGER_MASK); So no interrupts are lost, but the type in a node copied from the example is never the one the line is programmed with, and request_irq() fails outright on an irqchip that cannot do level triggering. Would it make sense to either describe/constrain the type as level-low in the schema, or have the driver pass IRQF_TRIGGER_NONE and honour the DT type? > + > + reg: > + maxItems: 1 > + > + spi-max-frequency: > + description: > + According to the w5500 datasheet, the chip allows a maximum of 80 MHz, > + however, board designs may need to limit this value. > + maximum: 80000000 [Severity: Low] Should this ceiling be per-compatible? The description scopes the 80 MHz figure to the w5500 only ("According to the w5500 datasheet"), and the deleted .txt scoped it the same way ("when accessing the w5500"), but the constraint sits in the shared properties block so wiznet,w5100 and wiznet,w5200 inherit it too. The older parts have lower SCLK limits, so a node like: ethernet@0 { compatible = "wiznet,w5100"; spi-max-frequency = <80000000>; ... }; passes make dt_binding_check with no diagnostic, and w5100-spi.c then clocks the bus at that rate. Other net SPI bindings in the same directory express this per variant inside a compatible conditional, for example the spi-max-frequency block in Documentation/devicetree/bindings/net/qca,qca7000.yaml: spi-max-frequency: default: 8000000 maximum: 16000000 minimum: 1000000 Would an allOf/if:properties:compatible:contains: block per part be better here, or a description explaining why 80 MHz is safe for all three? > + > +required: > + - compatible > + - reg > + - interrupts > + > +unevaluatedProperties: false > + > +examples: [ ... ] > + ethernet@0 { > + compatible = "wiznet,w5500"; > + reg = <0>; > + pinctrl-names = "default"; > + pinctrl-0 = <ð1_pins>; > + interrupt-parent = <&gpio>; > + interrupts = <25 IRQ_TYPE_EDGE_FALLING>; [Severity: Low] This is the same pre-existing mismatch noted above and not something this patch introduces; the edge-falling type was in the old .txt example verbatim. Since this example is now the only surviving statement of the expected trigger type, is IRQ_TYPE_LEVEL_LOW the value to show, given w5100_probe() requests IRQF_TRIGGER_LOW? Nothing in-tree uses these compatibles today (a grep for wiznet,w5 matches only the binding and w5100-spi.c), so no shipped board DT is affected either way. > + spi-max-frequency = <30000000>; > + local-mac-address = [ a0 b0 c0 d0 e0 f0 ]; > + }; > + }; > +... [ ... ] -- Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260904-wiznet-link-gpio-v7-0-69f01b70c96f%40marmottus.net ^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH net-next v7 2/3] dt-bindings: net: wiznet,w5100: add link status interrupt 2026-09-04 12:35 [PATCH net-next v7 0/3] w5100: restore GPIO-based link detection Arthur Crepin Leblond 2026-09-04 12:35 ` [PATCH net-next v7 1/3] dt-bindings: net: wiznet,w5100: convert to DT schema Arthur Crepin Leblond @ 2026-09-04 12:35 ` Arthur Crepin Leblond 2026-09-09 12:39 ` netdev-bot+sashiko 2026-09-04 12:36 ` [PATCH net-next v7 3/3] w5100: detect carrier state using link status bit and optional interrupt Arthur Crepin Leblond 2 siblings, 1 reply; 9+ messages in thread From: Arthur Crepin Leblond @ 2026-09-04 12:35 UTC (permalink / raw) To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley Cc: Arnd Bergmann, netdev, devicetree, linux-kernel, Arthur Crepin Leblond, Krzysztof Kozlowski Add an optional link status interrupt which can be connected to the LINKLED pin on the device to detect link status changes. Signed-off-by: Arthur Crepin Leblond <arthur@marmottus.net> Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com> --- v6: - Fix DT error: use minItems - Remove conditional properties v5: - Make the DT interrupts property dynamic from the compatible one - Fix DT bindings errors v4: - Update interrupts in DT bindings v3: - Fix DT binding errors v2: - Convert device tree binding to YAML --- Documentation/devicetree/bindings/net/wiznet,w5100.yaml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Documentation/devicetree/bindings/net/wiznet,w5100.yaml b/Documentation/devicetree/bindings/net/wiznet,w5100.yaml index 601d828bae92..d0717ab45de6 100644 --- a/Documentation/devicetree/bindings/net/wiznet,w5100.yaml +++ b/Documentation/devicetree/bindings/net/wiznet,w5100.yaml @@ -24,7 +24,11 @@ properties: - wiznet,w5500 interrupts: - maxItems: 1 + minItems: 1 + items: + - description: Main interrupt (INT pin on the device). + - description: + Optional interrupt for link status changes (LINKLED pin on w5500). reg: maxItems: 1 @@ -58,7 +62,8 @@ examples: pinctrl-names = "default"; pinctrl-0 = <ð1_pins>; interrupt-parent = <&gpio>; - interrupts = <25 IRQ_TYPE_EDGE_FALLING>; + interrupts = <25 IRQ_TYPE_EDGE_FALLING>, + <24 IRQ_TYPE_EDGE_BOTH>; spi-max-frequency = <30000000>; local-mac-address = [ a0 b0 c0 d0 e0 f0 ]; }; -- 2.55.0 ^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH net-next v7 2/3] dt-bindings: net: wiznet,w5100: add link status interrupt 2026-09-04 12:35 ` [PATCH net-next v7 2/3] dt-bindings: net: wiznet,w5100: add link status interrupt Arthur Crepin Leblond @ 2026-09-09 12:39 ` netdev-bot+sashiko 0 siblings, 0 replies; 9+ messages in thread From: netdev-bot+sashiko @ 2026-09-09 12:39 UTC (permalink / raw) To: arthur Cc: andrew+netdev, davem, edumazet, kuba, pabeni, robh, krzk+dt, conor+dt, arnd, netdev, devicetree, linux-kernel, krzysztof.kozlowski Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider: Critical: 0 · High: 0 · Medium: 0 · Low: 2 - [Low] Documentation/devicetree/bindings/net/wiznet,w5100.yaml: the newly added second `interrupts` entry is described as being… - [Low] Concern (assessed as a FALSE POSITIVE; retained only because requires_validation=true): the claim that the newly documented second… -- dt-bindings: net: wiznet,w5100: add link status interrupt The wiznet,w5100 binding is extended so that the interrupts property can carry a second entry for a link status interrupt wired to the LINKLED pin, and the example is updated to show both interrupts. > diff --git a/Documentation/devicetree/bindings/net/wiznet,w5100.yaml b/Documentation/devicetree/bindings/net/wiznet,w5100.yaml > index 601d828bae927..d0717ab45de6c 100644 > --- a/Documentation/devicetree/bindings/net/wiznet,w5100.yaml > +++ b/Documentation/devicetree/bindings/net/wiznet,w5100.yaml > @@ -24,7 +24,11 @@ properties: > - wiznet,w5500 > > interrupts: > - maxItems: 1 > + minItems: 1 > + items: > + - description: Main interrupt (INT pin on the device). > + - description: > + Optional interrupt for link status changes (LINKLED pin on w5500). [Severity: Low] The description of the second entry says the pin is on the w5500, but the constraint sits in the top-level properties block with no allOf/if-then keyed on compatible. The compatible enum in this same file is: compatible: enum: - wiznet,w5100 - wiznet,w5200 - wiznet,w5500 so a node such as: ethernet@0 { compatible = "wiznet,w5100"; interrupts = <25 IRQ_TYPE_EDGE_FALLING>, <24 IRQ_TYPE_EDGE_BOTH>; ... }; passes make dt_binding_check even though the schema text says the pin only exists on the w5500. Should the item count be tied to the compatible string, for example: allOf: - if: properties: compatible: contains: enum: - wiznet,w5100 - wiznet,w5200 then: properties: interrupts: maxItems: 1 The driver side of the series treats index 1 as w5500-only: drivers/net/ethernet/wiznet/w5100.c:w5100_probe() { ... priv->link_irq = ops->chip_id == W5500 ? fwnode_irq_get(dev_fwnode(dev), 1) : -ENODEV; ... } with w5100_get_link() returning a hardcoded 1 for the parts that have no PHYCFGR link-status register. So for w5100/w5200 a second interrupt in DT would validate but then be silently ignored, since fwnode_irq_get() is never called for index 1 on those chip ids. Alternatively, if LINKLED wiring is equally describable on w5100 and w5200, should the description be widened instead of naming only the w5500? Nothing in the rest of the series changes this block, so the published schema stays permissive for all three compatibles. Adding the conditional later would tighten the schema rather than break the ABI, so it seems it could still be done, but it seems better to get it right here. -- Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260904-wiznet-link-gpio-v7-0-69f01b70c96f%40marmottus.net ^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH net-next v7 3/3] w5100: detect carrier state using link status bit and optional interrupt 2026-09-04 12:35 [PATCH net-next v7 0/3] w5100: restore GPIO-based link detection Arthur Crepin Leblond 2026-09-04 12:35 ` [PATCH net-next v7 1/3] dt-bindings: net: wiznet,w5100: convert to DT schema Arthur Crepin Leblond 2026-09-04 12:35 ` [PATCH net-next v7 2/3] dt-bindings: net: wiznet,w5100: add link status interrupt Arthur Crepin Leblond @ 2026-09-04 12:36 ` Arthur Crepin Leblond 2026-09-09 12:39 ` netdev-bot+sashiko 2 siblings, 1 reply; 9+ messages in thread From: Arthur Crepin Leblond @ 2026-09-04 12:36 UTC (permalink / raw) To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley Cc: Arnd Bergmann, netdev, devicetree, linux-kernel, Arthur Crepin Leblond Detect the link status on the w5500 using the Link Status bit from the PHY Configuration register (PHYCFGR). On w5100/w5200, which lack this register, the link is always reported as up. Also add an optional interrupt, wired to the LINKLED pin of the w5500, to detect link status changes and read the PHYCFGR register. The initial link state is always probed from PHYCFGR regardless of whether the link interrupt is available. The interrupt is only needed to detect subsequent link changes. Make sure that the netdev is registered last in the probe function so no operation can be performed before the probing is finished. The w5500 interrupt is only enabled when the netdev is opened and disabled on stop. Same behavior for suspend/resume. 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. Signed-off-by: Arthur Crepin Leblond <arthur@marmottus.net> --- v7: - Address Sashiko reviews - Propagate spi read error to the caller - Do not change the state of the carrier on SPI read error - Enable the link IRQ before reading the PHYCFGR bit - Call unregister_netdev before reset - Update commit message v6: - Revert to the reviewed v4 version by Arnd Bergmann with a few changes based on Sashiko's review: - call netif_carrier_off in open if there is no link_irq or get_link returns false - call register_netdev at the very end of probe - call unregister_netdev before destroying work queue - handle link irq probe defer error - call netif_device_attach in resume before reenabling the link irq - disable the link irq first in suspend - log netif_err when PHYCFGR cannot be read - enable link irq in open - disable link irq in stop - free irq and invalidate it in remove v5: - Read the link status register only on w5500 instead of checking link_irq - Address sashiko review - call netif_carrier_off on w5500 link status off in open - enable/disable the link irq in open/stop - enable/disable the link irq in resume/suspend - handle link irq probe defer error - register the netdev last v4: - Use directly an interrupt line instead of gpio -> irq - Address sashiko reviews - drop devm_ on request_threaded_irq to avoid use after free - disable/enable the link_irq in the suspend/resume - only call netif_carrier_on|off if the link interrupt is present v3: - Use the Link Status bit of the PHY Configuration Register - Use the LINKLED gpio binding for change detection only v2: - Use devm_request_threaded_irq instead of request_any_context_irq - Use devm_gpiod_get_optional instead of gpiod_get_optional - Call dev_err_probe on gpiod_to_irq failure - Remove link_irq from priv - Use a fixed string for the IRQ name - Remove empty new lines --- drivers/net/ethernet/wiznet/w5100.c | 123 ++++++++++++++++++++++++++++++++---- 1 file changed, 112 insertions(+), 11 deletions(-) diff --git a/drivers/net/ethernet/wiznet/w5100.c b/drivers/net/ethernet/wiznet/w5100.c index 53d8dc642fbd..ecb99621d0a1 100644 --- a/drivers/net/ethernet/wiznet/w5100.c +++ b/drivers/net/ethernet/wiznet/w5100.c @@ -21,6 +21,7 @@ #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 +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; + int link_irq; struct napi_struct napi; struct net_device *ndev; @@ -345,6 +349,44 @@ 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 ret = w5100_read(priv, W5500_PHYCFGR); + + if (ret < 0) { + netif_err(priv, link, ndev, + "failed to read link status: %d\n", ret); + return ret; + } + + return ret & PHYCFGR_LNK; +} + +static irqreturn_t w5500_detect_link(int irq, void *ndev_instance) +{ + struct net_device *ndev = ndev_instance; + struct w5100_priv *priv = netdev_priv(ndev); + int state; + + if (!netif_running(ndev)) + return IRQ_HANDLED; + + state = w5500_get_phycfgr_lnk(ndev); + if (state < 0) + return IRQ_HANDLED; + + if (state) { + 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 int w5100_hw_reset(struct w5100_priv *priv) { u32 rtr; @@ -414,6 +456,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 w5500_get_phycfgr_lnk(ndev) > 0; + + return 1; +} + static u32 w5100_get_msglevel(struct net_device *ndev) { struct w5100_priv *priv = netdev_priv(ndev); @@ -659,6 +711,15 @@ static int w5100_open(struct net_device *ndev) w5100_hw_start(priv); napi_enable(&priv->napi); netif_start_queue(ndev); + + if (priv->link_irq >= 0) + enable_irq(priv->link_irq); + + if (w5100_get_link(ndev)) + netif_carrier_on(ndev); + else + netif_carrier_off(ndev); + return 0; } @@ -667,6 +728,10 @@ static int w5100_stop(struct net_device *ndev) struct w5100_priv *priv = netdev_priv(ndev); netif_info(priv, ifdown, ndev, "shutting down\n"); + + if (priv->link_irq >= 0) + disable_irq(priv->link_irq); + w5100_hw_close(priv); netif_carrier_off(ndev); netif_stop_queue(ndev); @@ -678,6 +743,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, }; @@ -745,13 +811,21 @@ int w5100_probe(struct device *dev, const struct w5100_ops *ops, break; default: err = -EINVAL; - goto err_register; + goto err_wq; } priv->ndev = ndev; priv->ops = ops; priv->irq = irq; + priv->link_irq = ops->chip_id == W5500 ? + fwnode_irq_get(dev_fwnode(dev), 1) : -ENODEV; + if (priv->link_irq == -EPROBE_DEFER) { + err = dev_err_probe(dev, priv->link_irq, + "failed to get link irq\n"); + goto err_wq; + } + ndev->netdev_ops = &w5100_netdev_ops; ndev->ethtool_ops = &w5100_ethtool_ops; netif_napi_add_weight(ndev, &priv->napi, w5100_napi_poll, 16); @@ -761,12 +835,8 @@ 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; @@ -795,21 +865,39 @@ 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); + dev_name(dev), ndev); } else { err = request_irq(priv->irq, w5100_interrupt, - IRQF_TRIGGER_LOW, netdev_name(ndev), ndev); + IRQF_TRIGGER_LOW, 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, + IRQF_TRIGGER_RISING | + IRQF_TRIGGER_FALLING | + IRQF_ONESHOT | IRQF_NO_AUTOEN, + "w5100-link", priv->ndev); + if (err < 0) + goto err_link_irq; + } + + err = register_netdev(ndev); + if (err < 0) + goto err_register; + return 0; +err_register: + if (priv->link_irq >= 0) + free_irq(priv->link_irq, ndev); +err_link_irq: + free_irq(priv->irq, ndev); err_hw: destroy_workqueue(priv->xfer_wq); err_wq: - unregister_netdev(ndev); -err_register: free_netdev(ndev); return err; } @@ -820,6 +908,11 @@ void w5100_remove(struct device *dev) struct net_device *ndev = dev_get_drvdata(dev); struct w5100_priv *priv = netdev_priv(ndev); + unregister_netdev(ndev); + + if (priv->link_irq >= 0) + free_irq(priv->link_irq, ndev); + w5100_hw_reset(priv); free_irq(priv->irq, ndev); @@ -827,7 +920,6 @@ void w5100_remove(struct device *dev) flush_work(&priv->restart_work); destroy_workqueue(priv->xfer_wq); - unregister_netdev(ndev); free_netdev(ndev); } EXPORT_SYMBOL_GPL(w5100_remove); @@ -839,6 +931,9 @@ static int w5100_suspend(struct device *dev) struct w5100_priv *priv = netdev_priv(ndev); if (netif_running(ndev)) { + if (priv->link_irq >= 0) + disable_irq(priv->link_irq); + netif_carrier_off(ndev); netif_device_detach(ndev); @@ -857,6 +952,12 @@ static int w5100_resume(struct device *dev) w5100_hw_start(priv); netif_device_attach(ndev); + + if (priv->link_irq >= 0) + enable_irq(priv->link_irq); + + if (w5100_get_link(ndev)) + netif_carrier_on(ndev); } return 0; } -- 2.55.0 ^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH net-next v7 3/3] w5100: detect carrier state using link status bit and optional interrupt 2026-09-04 12:36 ` [PATCH net-next v7 3/3] w5100: detect carrier state using link status bit and optional interrupt Arthur Crepin Leblond @ 2026-09-09 12:39 ` netdev-bot+sashiko 2026-09-10 11:27 ` Paolo Abeni 0 siblings, 1 reply; 9+ messages in thread From: netdev-bot+sashiko @ 2026-09-09 12:39 UTC (permalink / raw) To: arthur Cc: andrew+netdev, davem, edumazet, kuba, pabeni, robh, krzk+dt, conor+dt, arnd, netdev, devicetree, linux-kernel, krzysztof.kozlowski Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider: Critical: 0 · High: 1 · Medium: 2 · Low: 1 - [High] On w5500 the carrier state is now sampled exactly once per ifup (and once per resume) from PHYCFGR, with no polling fallback and no… - [Medium] `w5500_get_phycfgr_lnk()` returns a tri-state value (<0 on SPI/register read error, 0 link down, 1 link up), and its consumers… - [Medium] The carrier state is computed and published by two unsynchronized contexts. - [Low] The YAML binding added earlier in this series (Documentation/devicetree/bindings/net/wiznet,w5100.yaml, commit "dt-… -- w5100: detect carrier state using link status bit and optional interrupt This adds link detection on the w5500 by reading the Link Status bit of the PHY Configuration register, and adds an optional second interrupt (LINKLED) to notice link changes. The netdev is now registered as the last step of w5100_probe(), and the link interrupt is enabled/disabled in open/stop and resume/suspend. A few questions about the carrier handling below. > diff --git a/drivers/net/ethernet/wiznet/w5100.c b/drivers/net/ethernet/wiznet/w5100.c > index 53d8dc642fbd6..ecb99621d0a1e 100644 > --- a/drivers/net/ethernet/wiznet/w5100.c > +++ b/drivers/net/ethernet/wiznet/w5100.c [ ... ] > @@ -345,6 +349,44 @@ 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 ret = w5100_read(priv, W5500_PHYCFGR); > + > + if (ret < 0) { > + netif_err(priv, link, ndev, > + "failed to read link status: %d\n", ret); > + return ret; > + } > + > + return ret & PHYCFGR_LNK; > +} > + > +static irqreturn_t w5500_detect_link(int irq, void *ndev_instance) > +{ > + struct net_device *ndev = ndev_instance; > + struct w5100_priv *priv = netdev_priv(ndev); > + int state; > + > + if (!netif_running(ndev)) > + return IRQ_HANDLED; > + > + state = w5500_get_phycfgr_lnk(ndev); > + if (state < 0) > + return IRQ_HANDLED; > + > + if (state) { > + 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; > +} > + [ ... ] > @@ -414,6 +456,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 w5500_get_phycfgr_lnk(ndev) > 0; [Severity: Medium] Does the "> 0" here turn a register read failure into a definite "link down"? w5500_get_phycfgr_lnk() returns three distinct things: a negative errno, 0 for link down and 1 for link up. w5500_spi_read() can really return an error, since it propagates spi_write_then_read(): return ret ? ret : data; w5500_detect_link() deliberately leaves the carrier alone in that case: state = w5500_get_phycfgr_lnk(ndev); if (state < 0) return IRQ_HANDLED; but w5100_get_link() folds the errno into 0, so w5100_open() will call netif_carrier_off() on a transient SPI failure even when the link is up, and w5100_resume() (which has no else branch) will leave the carrier off. The same lossy mapping is what the newly added ethtool .get_link reports, so an SPI error shows up as "Link detected: no". Would it be better to keep the error case out of the carrier decision, the way the interrupt handler does? > + > + return 1; > +} > + [ ... ] > @@ -659,6 +711,15 @@ static int w5100_open(struct net_device *ndev) > w5100_hw_start(priv); > napi_enable(&priv->napi); > netif_start_queue(ndev); > + > + if (priv->link_irq >= 0) > + enable_irq(priv->link_irq); > + > + if (w5100_get_link(ndev)) > + netif_carrier_on(ndev); > + else > + netif_carrier_off(ndev); [Severity: High] Can the interface end up permanently NO-CARRIER on a w5500 board whose device tree only declares the single INT interrupt? The second (LINKLED) interrupt is optional and is introduced by this series, so on existing device trees w5100_probe() does: priv->link_irq = ops->chip_id == W5500 ? fwnode_irq_get(dev_fwnode(dev), 1) : -ENODEV; fwnode_irq_get() returns a negative value for index 1, no link interrupt is requested, and every "if (priv->link_irq >= 0)" site is skipped. That leaves the sample taken here as the only carrier update for the whole lifetime of the ifup. If PHYCFGR.LNK reads 0 at that moment (cable not plugged, or auto-negotiation not finished yet after the reset done in w5100_probe() -> w5100_hw_reset()), netif_carrier_off() is called and nothing turns it back on: the only netif_carrier_on() call sites are w5500_detect_link(), w5100_open() and w5100_resume(), and there is no timer or delayed work polling PHYCFGR. Before this patch the w5500 carrier was never cleared at ifup, so is this a behaviour change for existing w5500 device trees, where a link that comes up after ifup is never noticed until the user bounces the interface? The commit message says: "The interrupt is only needed to detect subsequent link changes." Could this state that without the interrupt there is no detection of subsequent link changes at all, including the initial link-up? [Severity: Medium] Separately, does the ordering of enable_irq() and the PHYCFGR sample here allow the threaded handler's newer verdict to be overwritten by an older one? __dev_open() sets __LINK_STATE_START before calling ndo_open, so netif_running() is already true and the early bail in w5500_detect_link() does not exclude a concurrent run. enable_irq() can also replay an edge that arrived while the line was masked: w5100_open() enable_irq(priv->link_irq); w5100_get_link() /* sleeping SPI read, old value */ w5500_detect_link() /* newer PHYCFGR value */ netif_carrier_on(ndev); netif_carrier_off(ndev); /* overwrites with the stale sample */ Nothing serializes the read-and-publish pair: the handler takes no lock, open holds only RTNL which the IRQ thread never takes, and struct w5100_priv has no lock covering the carrier update. With edge-only notification and no polling, the wrong state then persists until the link toggles again. w5100_resume() has the same enable-then-sample order and only ever calls netif_carrier_on(), so a "down" seen by the handler can be discarded. Note the teardown paths do it the other way around and disable_irq() first, which does synchronize with the threaded handler. > + > return 0; > } > [ ... ] > @@ -745,13 +811,21 @@ int w5100_probe(struct device *dev, const struct w5100_ops *ops, > break; > default: > err = -EINVAL; > - goto err_register; > + goto err_wq; > } > > priv->ndev = ndev; > priv->ops = ops; > priv->irq = irq; > > + priv->link_irq = ops->chip_id == W5500 ? > + fwnode_irq_get(dev_fwnode(dev), 1) : -ENODEV; [Severity: Low] This isn't a bug in the C code, but does the binding added earlier in the series match this restriction? Documentation/devicetree/bindings/net/wiznet,w5100.yaml describes the interrupts as: interrupts: minItems: 1 items: - description: Main interrupt (INT pin on the device). - description: Optional interrupt for link status changes (LINKLED pin on w5500). with no allOf/if-then keyed on the compatible string, so wiznet,w5100 and wiznet,w5200 nodes may declare two interrupts and still pass dt_binding_check. Here index 1 is only looked up for chip_id == W5500, so such a line is silently ignored (priv->link_irq stays -ENODEV). Would an if-then block limiting the second interrupt to wiznet,w5500 (or honouring the second interrupt for all parts) express this better? The w5500-only restriction is currently only in free-form description text, which validation does not enforce. > + if (priv->link_irq == -EPROBE_DEFER) { > + err = dev_err_probe(dev, priv->link_irq, > + "failed to get link irq\n"); > + goto err_wq; > + } > + [ ... ] > @@ -839,6 +931,9 @@ static int w5100_suspend(struct device *dev) > struct w5100_priv *priv = netdev_priv(ndev); > > if (netif_running(ndev)) { > + if (priv->link_irq >= 0) > + disable_irq(priv->link_irq); > + > netif_carrier_off(ndev); > netif_device_detach(ndev); > > @@ -857,6 +952,12 @@ static int w5100_resume(struct device *dev) > w5100_hw_start(priv); > > netif_device_attach(ndev); > + > + if (priv->link_irq >= 0) > + enable_irq(priv->link_irq); > + > + if (w5100_get_link(ndev)) > + netif_carrier_on(ndev); > } > return 0; > } Since suspend unconditionally clears the carrier and resume only sets it when this single read reports link, is there any path that re-asserts the carrier later when there is no link interrupt or when the PHYCFGR read fails here? -- Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260904-wiznet-link-gpio-v7-0-69f01b70c96f%40marmottus.net ^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH net-next v7 3/3] w5100: detect carrier state using link status bit and optional interrupt 2026-09-09 12:39 ` netdev-bot+sashiko @ 2026-09-10 11:27 ` Paolo Abeni 2026-09-10 12:28 ` Arthur Crepin Leblond 0 siblings, 1 reply; 9+ messages in thread From: Paolo Abeni @ 2026-09-10 11:27 UTC (permalink / raw) To: arthur Cc: andrew+netdev, davem, edumazet, kuba, robh, krzk+dt, conor+dt, arnd, netdev, devicetree, linux-kernel, krzysztof.kozlowski On 9/9/26 2:39 PM, netdev-bot+sashiko@kernel.org wrote: > Thank you for your contribution! Sashiko AI review found 4 potential > issue(s) to consider: > > Critical: 0 · High: 1 · Medium: 2 · Low: 1 > > - [High] On w5500 the carrier state is now sampled exactly once per ifup > (and once per resume) from PHYCFGR, with no polling fallback and no… I read this one as 'with a bad config/devtree the eth could non functional' which does look a big deal to me... > - [Medium] `w5500_get_phycfgr_lnk()` returns a tri-state value (<0 on > SPI/register read error, 0 link down, 1 link up), and its consumers… > - [Medium] The carrier state is computed and published by two > unsynchronized contexts. ... but the above 2 looks relevant to me. Note that ordering the irq enabling and initial link detection is not enough, you need explicit synchronization. Finally please note commit c82ff94592fb68f529afe63ca7f5ddb7dae4ba83, you should explicitly handle sashiko's comments. /P ^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH net-next v7 3/3] w5100: detect carrier state using link status bit and optional interrupt 2026-09-10 11:27 ` Paolo Abeni @ 2026-09-10 12:28 ` Arthur Crepin Leblond 0 siblings, 0 replies; 9+ messages in thread From: Arthur Crepin Leblond @ 2026-09-10 12:28 UTC (permalink / raw) To: Paolo Abeni Cc: andrew+netdev, davem, edumazet, kuba, robh, krzk+dt, conor+dt, arnd, netdev, devicetree, linux-kernel, krzysztof.kozlowski On Thu, Sep 10, 2026 at 01:27:42PM +0200, Paolo Abeni wrote: >On 9/9/26 2:39 PM, netdev-bot+sashiko@kernel.org wrote: Hi Paolo, >> Thank you for your contribution! Sashiko AI review found 4 potential >> issue(s) to consider: >> >> Critical: 0 · High: 1 · Medium: 2 · Low: 1 >> >> - [High] On w5500 the carrier state is now sampled exactly once per ifup >> (and once per resume) from PHYCFGR, with no polling fallback and no… > >I read this one as 'with a bad config/devtree the eth could non functional' >which does look a big deal to me... Yeah I would also say so, wtihout an additional interrupt configured you cannot really get notified of the carrier state changes. Only .get_link is useful (and only for w5500). But where Sashiko might be right is that the previous implementation was not calling netif_carrier_on/off on w5100_open and did not have .get_link set. So maybe .get_link should only be set on w5500 and netif_carrier_on/off called only if the link irq is set. > >> - [Medium] `w5500_get_phycfgr_lnk()` returns a tri-state value (<0 on >> SPI/register read error, 0 link down, 1 link up), and its consumers… >> - [Medium] The carrier state is computed and published by two >> unsynchronized contexts. > >... but the above 2 looks relevant to me. Note that ordering the irq >enabling and initial link detection is not enough, you need explicit >synchronization. I'll try to use rtnl_lock. > >Finally please note commit c82ff94592fb68f529afe63ca7f5ddb7dae4ba83, you >should explicitly handle sashiko's comments. > >/P > Thank you for pointing this out, I'll address the AI reviews. Arthur ^ permalink raw reply [flat|nested] 9+ messages in thread
end of thread, other threads:[~2026-09-10 12:28 UTC | newest] Thread overview: 9+ messages (download: mbox.gz / follow: Atom feed) -- links below jump to the message on this page -- 2026-09-04 12:35 [PATCH net-next v7 0/3] w5100: restore GPIO-based link detection Arthur Crepin Leblond 2026-09-04 12:35 ` [PATCH net-next v7 1/3] dt-bindings: net: wiznet,w5100: convert to DT schema Arthur Crepin Leblond 2026-09-09 12:38 ` netdev-bot+sashiko 2026-09-04 12:35 ` [PATCH net-next v7 2/3] dt-bindings: net: wiznet,w5100: add link status interrupt Arthur Crepin Leblond 2026-09-09 12:39 ` netdev-bot+sashiko 2026-09-04 12:36 ` [PATCH net-next v7 3/3] w5100: detect carrier state using link status bit and optional interrupt Arthur Crepin Leblond 2026-09-09 12:39 ` netdev-bot+sashiko 2026-09-10 11:27 ` Paolo Abeni 2026-09-10 12:28 ` Arthur Crepin Leblond
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®