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 Suggested-by: Jianhui Xu Reviewed-by: Andrew Lunn --- 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 | 115 ++++++++++++++++++++++++++++++++++++- 4 files changed, 143 insertions(+), 4 deletions(-) diff --git a/drivers/net/usb/ax88179_178a.c b/drivers/net/usb/ax88179_178a.c index 5864cf5bd5361469ad23935848a559b6fd66ed45..245ccf6896033922adcef79bd395b347918f57e3 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; + ret = ax88179_read_cmd(dev, AX_ACCESS_MAC, AX_CHIP_STATUS, 1, 1, &ax179_data->chip_version); if (ret < 0) @@ -1355,9 +1358,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 6ec662780483e94f9606f05f253106b8a6856d43..6f32d4da601ca11d19cf428ecb79206ab4460f3e 100644 --- a/drivers/net/usb/ax88179_lib.c +++ b/drivers/net/usb/ax88179_lib.c @@ -479,3 +479,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 41d57803491ac6dfa3c896d36b3257fc3d4503db..a9235ada982b87ae570b8d921cf22ad7d9255b42 100644 --- a/drivers/net/usb/ax88179_lib.h +++ b/drivers/net/usb/ax88179_lib.h @@ -314,6 +314,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 { @@ -347,6 +349,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 53ae62f2c3b06a613e118a7f05ff63213c048e3a..baf91ebee9e35a855b1aa902ba099169277330a7 100644 --- a/drivers/net/usb/ax88179a_devices.c +++ b/drivers/net/usb/ax88179a_devices.c @@ -5,6 +5,8 @@ #include #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,62 @@ 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) { + rtnl_lock(); + phylink_resume(priv->phylink); + rtnl_unlock(); + 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 +193,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) { + ax88179_set_pm_mode(dev, false); + return ret; + } + 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(); + } + + 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; @@ -737,6 +836,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; @@ -784,6 +886,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: @@ -1097,7 +1206,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