mirror of
https://github.com/torvalds/linux.git
synced 2026-11-07 06:17:02 +01:00
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:
commit
7ab4bbdca9
88
Documentation/devicetree/bindings/net/wiznet,w5100.yaml
Normal file
88
Documentation/devicetree/bindings/net/wiznet,w5100.yaml
Normal 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 = <ð1_pins>;
|
||||
pinctrl-names = "default";
|
||||
spi-max-frequency = <30000000>;
|
||||
};
|
||||
};
|
||||
...
|
||||
|
|
@ -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 */
|
||||
};
|
||||
};
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user