[PATCH net-next v18 09/13] ax88179_178a: Add Suspend/resume support for AX88179A/772D/279
flat view
WARM1d
REVIEWED: 8 (8M)
From: Birger Koblitz <hidden>
Date: 2026-10-08 03:51:33
Also in:
linux-usb, lkml, loongarch
Subsystem:
networking drivers, the rest, usb networking drivers · Maintainers:
Andrew Lunn, "David S. Miller", Eric Dumazet, Jakub Kicinski, Paolo Abeni, Linus Torvalds
1 review trailer.
The suspend and resume functions are called via the usb_driver structure, for which the driver has only a single instance. Add wrapper functions for the different implementations for the AX88179 and AX179A architecutres in ax88179_lib which calls the 2 implementations in ax88179_178a and ax88179a_devices, respectively. Signed-off-by: Birger Koblitz <redacted> Suggested-by: Jianhui Xu <redacted> Reviewed-by: Andrew Lunn <andrew@lunn.ch> --- drivers/net/usb/ax88179_178a.c | 9 ++- drivers/net/usb/ax88179_lib.c | 19 ++++++ drivers/net/usb/ax88179_lib.h | 4 ++ drivers/net/usb/ax88179a_devices.c | 119 ++++++++++++++++++++++++++++++++++++- 4 files changed, 146 insertions(+), 5 deletions(-)
diff --git a/drivers/net/usb/ax88179_178a.c b/drivers/net/usb/ax88179_178a.c
index 071b03b4f145c05fd2391546655cf569cf6ea4cd..ef0856627d08b456afb1569e8a4459b0e526ab46 100644
--- a/drivers/net/usb/ax88179_178a.c
+++ b/drivers/net/usb/ax88179_178a.c@@ -685,6 +685,9 @@ static int ax88179_bind(struct usbnet *dev, struct usb_interface *intf) dev->driver_priv = ax179_data; mutex_init(&ax179_data->medium_status_mutex); + ax179_data->resume = ax88179_resume; + ax179_data->suspend = ax88179_suspend; + ret = ax88179_read_cmd(dev, AX_ACCESS_MAC, AX_CHIP_STATUS, 1, 1, &ax179_data->chip_version); if (ret < 0)
@@ -1356,9 +1359,9 @@ static struct usb_driver ax88179_178a_driver = { .name = "ax88179_178a", .id_table = products, .probe = usbnet_probe, - .suspend = ax88179_suspend, - .resume = ax88179_resume, - .reset_resume = ax88179_resume, + .suspend = ax88179_suspend_wrapper, + .resume = ax88179_resume_wrapper, + .reset_resume = ax88179_resume_wrapper, .disconnect = ax88179_disconnect, .supports_autosuspend = 1, .disable_hub_initiated_lpm = 1,
diff --git a/drivers/net/usb/ax88179_lib.c b/drivers/net/usb/ax88179_lib.c
index ea19700c8230f7b51bd1f6edf44133c31b7298a1..6ccfd8c1c93ffcab7ede6cc00ce0bbd0d5fb30ba 100644
--- a/drivers/net/usb/ax88179_lib.c
+++ b/drivers/net/usb/ax88179_lib.c@@ -493,3 +493,22 @@ int ax88179_set_mac_addr(struct net_device *net, void *p) return 0; } +int ax88179_suspend_wrapper(struct usb_interface *intf, pm_message_t message) +{ + struct usbnet *dev = usb_get_intfdata(intf); + struct ax88179_data *priv; + + priv = dev->driver_priv; + + return priv->suspend(intf, message); +} + +int ax88179_resume_wrapper(struct usb_interface *intf) +{ + struct usbnet *dev = usb_get_intfdata(intf); + struct ax88179_data *priv; + + priv = dev->driver_priv; + return priv->resume(intf); +} +
diff --git a/drivers/net/usb/ax88179_lib.h b/drivers/net/usb/ax88179_lib.h
index adc2b4598bb52d80c18a6e2cc467144d3bd57a88..401c0bed3d28b4b94f0a0cad7509cd7087ef0639 100644
--- a/drivers/net/usb/ax88179_lib.h
+++ b/drivers/net/usb/ax88179_lib.h@@ -315,6 +315,8 @@ struct ax88179_data { struct phy_device *phydev; struct phylink *phylink; struct phylink_config phylink_config; + int (*resume)(struct usb_interface *intf); + int (*suspend)(struct usb_interface *intf, pm_message_t message); }; struct ax88179_int_data {
@@ -348,6 +350,8 @@ int ax88179_set_features(struct net_device *net, netdev_features_t features); void ax88179_get_mac_addr(struct usbnet *dev); int ax88179_change_mtu(struct net_device *net, int new_mtu); int ax88179_set_mac_addr(struct net_device *net, void *p); +int ax88179_suspend_wrapper(struct usb_interface *intf, pm_message_t message); +int ax88179_resume_wrapper(struct usb_interface *intf); extern const struct driver_info ax88179a_info; extern const struct driver_info ax88772d_info;
diff --git a/drivers/net/usb/ax88179a_devices.c b/drivers/net/usb/ax88179a_devices.c
index e86cbe3e4400fa2c8e1a6a864fc8e1d328f13724..e9913a7c703905c9422405c8158bf068f1d94083 100644
--- a/drivers/net/usb/ax88179a_devices.c
+++ b/drivers/net/usb/ax88179a_devices.c@@ -5,6 +5,8 @@ #include <linux/if_vlan.h> #include "ax88179_lib.h" +static int ax88179a_reset(struct usbnet *dev); + #define AX88279_EEPROM_LEN 0x4000 #define AX88179A_EEPROM_LEN (32 * 20)
@@ -126,6 +128,63 @@ static void ax88179a_status(struct usbnet *dev, struct urb *urb) phylink_mac_interrupt(data->phylink); } +static int ax88179a_suspend(struct usb_interface *intf, pm_message_t message) +{ + struct usbnet *dev = usb_get_intfdata(intf); + struct ax88179_data *priv; + u8 tmp8 = 0; + u16 tmp16; + int ret; + + priv = dev->driver_priv; + ax88179_set_pm_mode(dev, true); + + if (netif_running(dev->net)) { + rtnl_lock(); + phylink_suspend(priv->phylink, !!priv->wolopts); + rtnl_unlock(); + } + + ret = usbnet_suspend(intf, message); + if (ret && netif_running(dev->net)) { + rtnl_lock(); + phylink_resume(priv->phylink); + rtnl_unlock(); + ax88179_set_pm_mode(dev, false); + return ret; + } + + /* Enable WoL */ + if (priv->wolopts) { + ax88179_read_cmd(dev, AX_ACCESS_MAC, AX_MONITOR_MOD, 1, 1, &tmp8); + tmp8 &= ~(AX_MONITOR_MODE_RWLC | AX_MONITOR_MODE_RWMP); + if (priv->wolopts & WAKE_PHY) + tmp8 |= AX_MONITOR_MODE_RWLC; + if (priv->wolopts & WAKE_MAGIC) + tmp8 |= AX_MONITOR_MODE_RWMP; + + ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_MONITOR_MOD, 1, 1, &tmp8); + + ax88179_read_cmd(dev, AX_ACCESS_MAC, AX_MEDIUM_STATUS_MODE, 2, 2, &tmp16); + tmp16 |= AX_MEDIUM_RECEIVE_EN; + ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_MEDIUM_STATUS_MODE, 2, 2, &tmp16); + + if (priv->chip_version == AX_VERSION_AX88279) + ax88179_write_cmd(dev, AX88179A_WAKEUP_SETTING, 8, + EPHY_LOW_POWER_EN | S5_WOL_EN + | S5_WOL_LOW_POWER | 0x8000, 0, NULL); + else + ax88179_write_cmd(dev, AX88179A_WAKEUP_SETTING, 0, + EPHY_LOW_POWER_EN, 0, NULL); + + } else if (priv->chip_version == AX_VERSION_AX88279) { + ax88179_write_cmd(dev, AX88179A_WAKEUP_SETTING, 8, 0x8000, 0, NULL); + } + + ax88179_set_pm_mode(dev, false); + return 0; +} + static int ax88179a_auto_detach(struct usbnet *dev) { u16 tmp16;
@@ -135,6 +194,47 @@ static int ax88179a_auto_detach(struct usbnet *dev) return 0; } +static int ax88179a_resume(struct usb_interface *intf) +{ + struct usbnet *dev = usb_get_intfdata(intf); + struct ax88179_data *ax179_data; + u8 reg8 = 0; + int ret; + + ax179_data = dev->driver_priv; + ax88179_set_pm_mode(dev, true); + + ret = ax88179_read_cmd(dev, AX88179A_PHY_POWER, 0, 0, 1, ®8); + if (ret < 0) + goto err_out; + + if (!(reg8 & AX_PHY_POWER)) { + reg8 = AX_PHY_POWER; + ax88179_write_cmd(dev, AX88179A_PHY_POWER, 0, 0, 1, ®8); + msleep(250); + } + ax88179_write_cmd(dev, AX_FW_MODE, AX_FW_MODE_179A, 0, 0, NULL); + + /* Now, that AX_FW_MODE_179A is enabled, the PHY needs a power-cycle. + * PHY-power is re-enabled in ax88179a_reset() + */ + ax88179_write_u8(dev, AX88179A_PHY_POWER, 0, 0, 0); + msleep(250); + + ax88179a_reset(dev); + + if (netif_running(dev->net)) { + rtnl_lock(); + phylink_resume(ax179_data->phylink); + rtnl_unlock(); + } + +err_out: + ax88179_set_pm_mode(dev, false); + + return usbnet_resume(intf); +} + static void ax88179a_bulkin_config(struct usbnet *dev, u8 link_sts, u8 speed, bool full_duplex) { struct ax88179_data *ax179_data = dev->driver_priv;
@@ -749,6 +849,9 @@ static int ax88179a_bind(struct usbnet *dev, struct usb_interface *intf) ax179_data->eeprom_wen = 0; } + ax179_data->resume = ax88179a_resume; + ax179_data->suspend = ax88179a_suspend; + dev->net->netdev_ops = &ax88179a_netdev_ops; dev->net->ethtool_ops = &ax88179a_ethtool_ops; dev->net->needed_headroom = 8;
@@ -796,6 +899,13 @@ static int ax88179a_bind(struct usbnet *dev, struct usb_interface *intf) if (ret) goto initphy_err; + /* Keep this interface runtime-PM active by taking a usage ref. + * Prevents runtime suspend while bound and avoids resume paths + * that could deadlock (autoresume under RTNL while USB PM lock + * is held, phylink/MDIO wants RTNL). + */ + pm_runtime_get_noresume(&intf->dev); + return 0; initphy_err:
@@ -823,7 +933,8 @@ static void ax88179a_unbind(struct usbnet *dev, struct usb_interface *intf) ax88179a_mdio_unregister(ax179_data); ax88179_write_u8(dev, AX88179A_PHY_POWER, 0, 0, 0); - + /* Drop the PM usage ref taken in bind() */ + pm_runtime_put_noidle(&intf->dev); kfree(ax179_data); }
@@ -1108,7 +1219,11 @@ static int ax88179a_reset(struct usbnet *dev) /* Ensure EEE state is off, consistent with phylink setup */ ax88179_write_cmd(dev, AX_GPHY_CTL, AX_GPHY_EEE_CTRL, false, 0, NULL); - phylink_start(ax179_data->phylink); + /* ax88179a_reset() may also be called from resume context, + * phylink_resume() will start phylink, then. + */ + if (!ax179_data->in_pm) + phylink_start(ax179_data->phylink); return 0; }
--
2.47.3