[PATCH net-next v9 11/15] ax88179_178a: Add Suspend/resume support for AX88179A/772D/279
From: Birger Koblitz <hidden>
Date: 2026-09-02 16:57:40
Also in:
linux-usb, lkml
Subsystem:
networking drivers, the rest, usb networking drivers · Maintainers:
Andrew Lunn, "David S. Miller", Eric Dumazet, Jakub Kicinski, Paolo Abeni, Linus Torvalds
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 | 95 +++++++++++++++++++++++++++++++++++++- 4 files changed, 123 insertions(+), 4 deletions(-)
diff --git a/drivers/net/usb/ax88179_178a.c b/drivers/net/usb/ax88179_178a.c
index 33be9ef73e73dd550402692e96605d22c481c7ec..44a768c5808ac1f7071e186d9c86cd6149601971 100644
--- a/drivers/net/usb/ax88179_178a.c
+++ b/drivers/net/usb/ax88179_178a.c@@ -684,6 +684,9 @@ static int ax88179_bind(struct usbnet *dev, struct usb_interface *intf) dev->driver_priv = ax179_data; + ax179_data->resume = ax88179_resume; + ax179_data->suspend = ax88179_suspend; + dev->net->netdev_ops = &ax88179_netdev_ops; dev->net->ethtool_ops = &ax88179_ethtool_ops; dev->net->needed_headroom = 8;
@@ -1342,9 +1345,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 708b0b23f1363daf90f329a3e6b6a2721d110387..3b498206c50d5b5d9d5cd489114ef28bf280fe97 100644
--- a/drivers/net/usb/ax88179_lib.c
+++ b/drivers/net/usb/ax88179_lib.c@@ -455,3 +455,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 2e4d35c74ee8b32cd2916924b85d5427860bb08c..16a6dcbd6e66e37d3da0e57d2800c29d8a95a34a 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 {
@@ -346,6 +348,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 6ade76679629adcbb33e780900a38b44383da735..5ef55f29d64a534747e5480faf192b06817a61f4 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)
@@ -121,6 +123,53 @@ 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; + u16 tmp16; + u8 tmp8; + + 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(); + } + + /* Enable WoL */ + if (priv->wolopts) { + ax88179_read_cmd(dev, AX_ACCESS_MAC, AX_MONITOR_MOD, 1, 1, &tmp8); + 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); + } + + usbnet_suspend(intf, message); + ax88179_set_pm_mode(dev, false); + return 0; +} + static int ax88179a_auto_detach(struct usbnet *dev) { u16 tmp16;
@@ -130,6 +179,43 @@ 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; + + ax179_data = dev->driver_priv; + ax88179_set_pm_mode(dev, true); + + ax88179_read_cmd(dev, AX88179A_PHY_POWER, 0, 0, 1, ®8); + 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 ax88179_reset() + */ + reg8 = 0; + ax88179_write_cmd(dev, AX88179A_PHY_POWER, 0, 0, 1, ®8); + msleep(250); + + ax88179a_reset(dev); + + if (netif_running(dev->net)) { + rtnl_lock(); + phylink_resume(ax179_data->phylink); + rtnl_unlock(); + } + + 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;
@@ -678,6 +764,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;
@@ -1037,7 +1126,11 @@ static int ax88179a_reset(struct usbnet *dev) 1, 1, &tmp) > 0) ax179_data->wol_supported = WAKE_MAGIC | WAKE_PHY; - phylink_start(ax179_data->phylink); + /* ax88179a_reset() may also be called from resume context, phylink + * is already started, then. + */ + if (!ax179_data->in_pm) + phylink_start(ax179_data->phylink); usbnet_link_change(dev, 0, 0);
--
2.47.3