diff --git a/drivers/net/usb/smsc75xx.c b/drivers/net/usb/smsc75xx.c index 649bf2a4262a0b65b78a6851bff93b4944faeede..b852c483a2eb13fb969ca72818a0488f0990f093 100644 --- a/drivers/net/usb/smsc75xx.c +++ b/drivers/net/usb/smsc75xx.c @@ -57,6 +57,14 @@ #define SUPPORTED_WAKE (WAKE_PHY | WAKE_UCAST | WAKE_BCAST | \ WAKE_MCAST | WAKE_ARP | WAKE_MAGIC) +#define SUSPEND_SUSPEND0 (0x01) +#define SUSPEND_SUSPEND1 (0x02) +#define SUSPEND_SUSPEND2 (0x04) +#define SUSPEND_SUSPEND3 (0x08) +#define SUSPEND_REMOTEWAKE (0x10) +#define SUSPEND_ALLMODES (SUSPEND_SUSPEND0 | SUSPEND_SUSPEND1 | \ + SUSPEND_SUSPEND2 | SUSPEND_SUSPEND3) + #define check_warn(ret, fmt, args...) \ ({ if (ret < 0) netdev_warn(dev->net, fmt, ##args); }) @@ -74,6 +82,7 @@ struct smsc75xx_priv { struct mutex dataport_mutex; spinlock_t rfe_ctl_lock; struct work_struct set_multicast; + u8 suspend_flags; }; struct usb_context { @@ -1241,6 +1250,7 @@ static int smsc75xx_write_wuff(struct usbnet *dev, int filter, u32 wuf_cfg, static int smsc75xx_enter_suspend0(struct usbnet *dev) { + struct smsc75xx_priv *pdata = (struct smsc75xx_priv *)(dev->data[0]); u32 val; int ret; @@ -1255,11 +1265,14 @@ static int smsc75xx_enter_suspend0(struct usbnet *dev) smsc75xx_set_feature(dev, USB_DEVICE_REMOTE_WAKEUP); + pdata->suspend_flags |= SUSPEND_SUSPEND0 | SUSPEND_REMOTEWAKE; + return 0; } static int smsc75xx_enter_suspend1(struct usbnet *dev) { + struct smsc75xx_priv *pdata = (struct smsc75xx_priv *)(dev->data[0]); u32 val; int ret; @@ -1281,11 +1294,14 @@ static int smsc75xx_enter_suspend1(struct usbnet *dev) smsc75xx_set_feature(dev, USB_DEVICE_REMOTE_WAKEUP); + pdata->suspend_flags |= SUSPEND_SUSPEND1 | SUSPEND_REMOTEWAKE; + return 0; } static int smsc75xx_enter_suspend2(struct usbnet *dev) { + struct smsc75xx_priv *pdata = (struct smsc75xx_priv *)(dev->data[0]); u32 val; int ret; @@ -1298,6 +1314,45 @@ static int smsc75xx_enter_suspend2(struct usbnet *dev) ret = smsc75xx_write_reg_nopm(dev, PMT_CTL, val); check_warn_return(ret, "Error writing PMT_CTL\n"); + pdata->suspend_flags |= SUSPEND_SUSPEND2; + + return 0; +} + +static int smsc75xx_enter_suspend3(struct usbnet *dev) +{ + struct smsc75xx_priv *pdata = (struct smsc75xx_priv *)(dev->data[0]); + u32 val; + int ret; + + ret = smsc75xx_read_reg_nopm(dev, FCT_RX_CTL, &val); + check_warn_return(ret, "Error reading FCT_RX_CTL\n"); + + if (val & FCT_RX_CTL_RXUSED) { + netdev_dbg(dev->net, "rx fifo not empty in autosuspend\n"); + return -EBUSY; + } + + ret = smsc75xx_read_reg_nopm(dev, PMT_CTL, &val); + check_warn_return(ret, "Error reading PMT_CTL\n"); + + val &= ~(PMT_CTL_SUS_MODE | PMT_CTL_WUPS | PMT_CTL_PHY_RST); + val |= PMT_CTL_SUS_MODE_3 | PMT_CTL_RES_CLR_WKP_EN; + + ret = smsc75xx_write_reg_nopm(dev, PMT_CTL, val); + check_warn_return(ret, "Error writing PMT_CTL\n"); + + /* clear wol status */ + val &= ~PMT_CTL_WUPS; + val |= PMT_CTL_WUPS_WOL; + + ret = smsc75xx_write_reg_nopm(dev, PMT_CTL, val); + check_warn_return(ret, "Error writing PMT_CTL\n"); + + smsc75xx_set_feature(dev, USB_DEVICE_REMOTE_WAKEUP); + + pdata->suspend_flags |= SUSPEND_SUSPEND3 | SUSPEND_REMOTEWAKE; + return 0; } @@ -1338,6 +1393,38 @@ static int smsc75xx_link_ok_nopm(struct usbnet *dev) return !!(ret & BMSR_LSTATUS); } +static int smsc75xx_autosuspend(struct usbnet *dev, u32 link_up) +{ + int ret; + + if (!netif_running(dev->net)) { + /* interface is ifconfig down so fully power down hw */ + netdev_dbg(dev->net, "autosuspend entering SUSPEND2\n"); + return smsc75xx_enter_suspend2(dev); + } + + if (!link_up) { + /* link is down so enter EDPD mode */ + netdev_dbg(dev->net, "autosuspend entering SUSPEND1\n"); + + /* enable PHY wakeup events for if cable is attached */ + ret = smsc75xx_enable_phy_wakeup_interrupts(dev, + PHY_INT_MASK_ANEG_COMP); + check_warn_return(ret, "error enabling PHY wakeup ints\n"); + + netdev_info(dev->net, "entering SUSPEND1 mode\n"); + return smsc75xx_enter_suspend1(dev); + } + + /* enable PHY wakeup events so we remote wakeup if cable is pulled */ + ret = smsc75xx_enable_phy_wakeup_interrupts(dev, + PHY_INT_MASK_LINK_DOWN); + check_warn_return(ret, "error enabling PHY wakeup ints\n"); + + netdev_dbg(dev->net, "autosuspend entering SUSPEND3\n"); + return smsc75xx_enter_suspend3(dev); +} + static int smsc75xx_suspend(struct usb_interface *intf, pm_message_t message) { struct usbnet *dev = usb_get_intfdata(intf); @@ -1348,9 +1435,20 @@ static int smsc75xx_suspend(struct usb_interface *intf, pm_message_t message) ret = usbnet_suspend(intf, message); check_warn_goto_done(ret, "usbnet_suspend error\n"); + if (pdata->suspend_flags) { + netdev_warn(dev->net, "error during last resume\n"); + pdata->suspend_flags = 0; + } + /* determine if link is up using only _nopm functions */ link_up = smsc75xx_link_ok_nopm(dev); + if (message.event == PM_EVENT_AUTO_SUSPEND) { + ret = smsc75xx_autosuspend(dev, link_up); + goto done; + } + + /* if we get this far we're not autosuspending */ /* if no wol options set, or if link is down and we're not waking on * PHY activity, enter lowest power SUSPEND2 mode */ @@ -1544,14 +1642,21 @@ static int smsc75xx_resume(struct usb_interface *intf) { struct usbnet *dev = usb_get_intfdata(intf); struct smsc75xx_priv *pdata = (struct smsc75xx_priv *)(dev->data[0]); + u8 suspend_flags = pdata->suspend_flags; int ret; u32 val; - if (pdata->wolopts) { - netdev_info(dev->net, "resuming from SUSPEND0\n"); + netdev_dbg(dev->net, "resume suspend_flags=0x%02x\n", suspend_flags); - smsc75xx_clear_feature(dev, USB_DEVICE_REMOTE_WAKEUP); + /* do this first to ensure it's cleared even in error case */ + pdata->suspend_flags = 0; + + if (suspend_flags & SUSPEND_REMOTEWAKE) { + ret = smsc75xx_clear_feature(dev, USB_DEVICE_REMOTE_WAKEUP); + check_warn_return(ret, "Error disabling remote wakeup\n"); + } + if (suspend_flags & SUSPEND_ALLMODES) { /* Disable wakeup sources */ ret = smsc75xx_read_reg_nopm(dev, WUCSR, &val); check_warn_return(ret, "Error reading WUCSR\n"); @@ -1571,7 +1676,9 @@ static int smsc75xx_resume(struct usb_interface *intf) ret = smsc75xx_write_reg_nopm(dev, PMT_CTL, val); check_warn_return(ret, "Error writing PMT_CTL\n"); - } else { + } + + if (suspend_flags & SUSPEND_SUSPEND2) { netdev_info(dev->net, "resuming from SUSPEND2\n"); ret = smsc75xx_read_reg_nopm(dev, PMT_CTL, &val); @@ -1727,6 +1834,12 @@ static struct sk_buff *smsc75xx_tx_fixup(struct usbnet *dev, return skb; } +static int smsc75xx_manage_power(struct usbnet *dev, int on) +{ + dev->intf->needs_remote_wakeup = on; + return 0; +} + static const struct driver_info smsc75xx_info = { .description = "smsc75xx USB 2.0 Gigabit Ethernet", .bind = smsc75xx_bind, @@ -1736,6 +1849,7 @@ static const struct driver_info smsc75xx_info = { .rx_fixup = smsc75xx_rx_fixup, .tx_fixup = smsc75xx_tx_fixup, .status = smsc75xx_status, + .manage_power = smsc75xx_manage_power, .flags = FLAG_ETHER | FLAG_SEND_ZLP | FLAG_LINK_INTR, }; @@ -1763,6 +1877,7 @@ static struct usb_driver smsc75xx_driver = { .reset_resume = smsc75xx_resume, .disconnect = usbnet_disconnect, .disable_hub_initiated_lpm = 1, + .supports_autosuspend = 1, }; module_usb_driver(smsc75xx_driver);