diff options
Diffstat (limited to 'drivers/net/usb/smsc95xx.c')
-rw-r--r-- | drivers/net/usb/smsc95xx.c | 441 |
1 files changed, 219 insertions, 222 deletions
diff --git a/drivers/net/usb/smsc95xx.c b/drivers/net/usb/smsc95xx.c index 9b736701f85..ff4fa37dfd1 100644 --- a/drivers/net/usb/smsc95xx.c +++ b/drivers/net/usb/smsc95xx.c @@ -55,6 +55,13 @@ #define FEATURE_PHY_NLP_CROSSOVER (0x02) #define FEATURE_AUTOSUSPEND (0x04) +#define SUSPEND_SUSPEND0 (0x01) +#define SUSPEND_SUSPEND1 (0x02) +#define SUSPEND_SUSPEND2 (0x04) +#define SUSPEND_SUSPEND3 (0x08) +#define SUSPEND_ALLMODES (SUSPEND_SUSPEND0 | SUSPEND_SUSPEND1 | \ + SUSPEND_SUSPEND2 | SUSPEND_SUSPEND3) + struct smsc95xx_priv { u32 mac_cr; u32 hash_hi; @@ -62,6 +69,7 @@ struct smsc95xx_priv { u32 wolopts; spinlock_t mac_cr_lock; u8 features; + u8 suspend_flags; }; static bool turbo_mode = true; @@ -513,10 +521,8 @@ static int smsc95xx_phy_update_flowcontrol(struct usbnet *dev, u8 duplex, u32 flow, afc_cfg = 0; int ret = smsc95xx_read_reg(dev, AFC_CFG, &afc_cfg); - if (ret < 0) { - netdev_warn(dev->net, "Error reading AFC_CFG\n"); + if (ret < 0) return ret; - } if (duplex == DUPLEX_FULL) { u8 cap = mii_resolve_flowctrl_fdx(lcladv, rmtadv); @@ -541,16 +547,10 @@ static int smsc95xx_phy_update_flowcontrol(struct usbnet *dev, u8 duplex, } ret = smsc95xx_write_reg(dev, FLOW, flow); - if (ret < 0) { - netdev_warn(dev->net, "Error writing FLOW\n"); - return ret; - } - - ret = smsc95xx_write_reg(dev, AFC_CFG, afc_cfg); if (ret < 0) - netdev_warn(dev->net, "Error writing AFC_CFG\n"); + return ret; - return ret; + return smsc95xx_write_reg(dev, AFC_CFG, afc_cfg); } static int smsc95xx_link_reset(struct usbnet *dev) @@ -564,16 +564,12 @@ static int smsc95xx_link_reset(struct usbnet *dev) /* clear interrupt status */ ret = smsc95xx_mdio_read(dev->net, mii->phy_id, PHY_INT_SRC); - if (ret < 0) { - netdev_warn(dev->net, "Error reading PHY_INT_SRC\n"); + if (ret < 0) return ret; - } ret = smsc95xx_write_reg(dev, INT_STS, INT_STS_CLEAR_ALL_); - if (ret < 0) { - netdev_warn(dev->net, "Error writing INT_STS\n"); + if (ret < 0) return ret; - } mii_check_media(mii, 1, 1); mii_ethtool_gset(&dev->mii, &ecmd); @@ -595,10 +591,8 @@ static int smsc95xx_link_reset(struct usbnet *dev) spin_unlock_irqrestore(&pdata->mac_cr_lock, flags); ret = smsc95xx_write_reg(dev, MAC_CR, pdata->mac_cr); - if (ret < 0) { - netdev_warn(dev->net, "Error writing MAC_CR\n"); + if (ret < 0) return ret; - } ret = smsc95xx_phy_update_flowcontrol(dev, ecmd.duplex, lcladv, rmtadv); if (ret < 0) @@ -638,10 +632,8 @@ static int smsc95xx_set_features(struct net_device *netdev, int ret; ret = smsc95xx_read_reg(dev, COE_CR, &read_buf); - if (ret < 0) { - netdev_warn(dev->net, "Failed to read COE_CR: %d\n", ret); + if (ret < 0) return ret; - } if (features & NETIF_F_HW_CSUM) read_buf |= Tx_COE_EN_; @@ -654,10 +646,8 @@ static int smsc95xx_set_features(struct net_device *netdev, read_buf &= ~Rx_COE_EN_; ret = smsc95xx_write_reg(dev, COE_CR, read_buf); - if (ret < 0) { - netdev_warn(dev->net, "Failed to write COE_CR: %d\n", ret); + if (ret < 0) return ret; - } netif_dbg(dev, hw, dev->net, "COE_CR = 0x%08x\n", read_buf); return 0; @@ -800,16 +790,10 @@ static int smsc95xx_set_mac_address(struct usbnet *dev) int ret; ret = smsc95xx_write_reg(dev, ADDRL, addr_lo); - if (ret < 0) { - netdev_warn(dev->net, "Failed to write ADDRL: %d\n", ret); - return ret; - } - - ret = smsc95xx_write_reg(dev, ADDRH, addr_hi); if (ret < 0) - netdev_warn(dev->net, "Failed to write ADDRH: %d\n", ret); + return ret; - return ret; + return smsc95xx_write_reg(dev, ADDRH, addr_hi); } /* starts the TX path */ @@ -825,17 +809,11 @@ static int smsc95xx_start_tx_path(struct usbnet *dev) spin_unlock_irqrestore(&pdata->mac_cr_lock, flags); ret = smsc95xx_write_reg(dev, MAC_CR, pdata->mac_cr); - if (ret < 0) { - netdev_warn(dev->net, "Failed to write MAC_CR: %d\n", ret); + if (ret < 0) return ret; - } /* Enable Tx at SCSRs */ - ret = smsc95xx_write_reg(dev, TX_CFG, TX_CFG_ON_); - if (ret < 0) - netdev_warn(dev->net, "Failed to write TX_CFG: %d\n", ret); - - return ret; + return smsc95xx_write_reg(dev, TX_CFG, TX_CFG_ON_); } /* Starts the Receive path */ @@ -843,17 +821,12 @@ static int smsc95xx_start_rx_path(struct usbnet *dev, int in_pm) { struct smsc95xx_priv *pdata = (struct smsc95xx_priv *)(dev->data[0]); unsigned long flags; - int ret; spin_lock_irqsave(&pdata->mac_cr_lock, flags); pdata->mac_cr |= MAC_CR_RXEN_; spin_unlock_irqrestore(&pdata->mac_cr_lock, flags); - ret = __smsc95xx_write_reg(dev, MAC_CR, pdata->mac_cr, in_pm); - if (ret < 0) - netdev_warn(dev->net, "Failed to write MAC_CR: %d\n", ret); - - return ret; + return __smsc95xx_write_reg(dev, MAC_CR, pdata->mac_cr, in_pm); } static int smsc95xx_phy_initialize(struct usbnet *dev) @@ -910,19 +883,15 @@ static int smsc95xx_reset(struct usbnet *dev) netif_dbg(dev, ifup, dev->net, "entering smsc95xx_reset\n"); ret = smsc95xx_write_reg(dev, HW_CFG, HW_CFG_LRST_); - if (ret < 0) { - netdev_warn(dev->net, "Failed to write HW_CFG_LRST_ bit in HW_CFG\n"); + if (ret < 0) return ret; - } timeout = 0; do { msleep(10); ret = smsc95xx_read_reg(dev, HW_CFG, &read_buf); - if (ret < 0) { - netdev_warn(dev->net, "Failed to read HW_CFG: %d\n", ret); + if (ret < 0) return ret; - } timeout++; } while ((read_buf & HW_CFG_LRST_) && (timeout < 100)); @@ -932,19 +901,15 @@ static int smsc95xx_reset(struct usbnet *dev) } ret = smsc95xx_write_reg(dev, PM_CTRL, PM_CTL_PHY_RST_); - if (ret < 0) { - netdev_warn(dev->net, "Failed to write PM_CTRL: %d\n", ret); + if (ret < 0) return ret; - } timeout = 0; do { msleep(10); ret = smsc95xx_read_reg(dev, PM_CTRL, &read_buf); - if (ret < 0) { - netdev_warn(dev->net, "Failed to read PM_CTRL: %d\n", ret); + if (ret < 0) return ret; - } timeout++; } while ((read_buf & PM_CTL_PHY_RST_) && (timeout < 100)); @@ -961,10 +926,8 @@ static int smsc95xx_reset(struct usbnet *dev) dev->net->dev_addr); ret = smsc95xx_read_reg(dev, HW_CFG, &read_buf); - if (ret < 0) { - netdev_warn(dev->net, "Failed to read HW_CFG: %d\n", ret); + if (ret < 0) return ret; - } netif_dbg(dev, ifup, dev->net, "Read Value from HW_CFG : 0x%08x\n", read_buf); @@ -972,16 +935,12 @@ static int smsc95xx_reset(struct usbnet *dev) read_buf |= HW_CFG_BIR_; ret = smsc95xx_write_reg(dev, HW_CFG, read_buf); - if (ret < 0) { - netdev_warn(dev->net, "Failed to write HW_CFG_BIR_ bit in HW_CFG\n"); + if (ret < 0) return ret; - } ret = smsc95xx_read_reg(dev, HW_CFG, &read_buf); - if (ret < 0) { - netdev_warn(dev->net, "Failed to read HW_CFG: %d\n", ret); + if (ret < 0) return ret; - } netif_dbg(dev, ifup, dev->net, "Read Value from HW_CFG after writing HW_CFG_BIR_: 0x%08x\n", @@ -1002,42 +961,32 @@ static int smsc95xx_reset(struct usbnet *dev) (ulong)dev->rx_urb_size); ret = smsc95xx_write_reg(dev, BURST_CAP, burst_cap); - if (ret < 0) { - netdev_warn(dev->net, "Failed to write BURST_CAP: %d\n", ret); + if (ret < 0) return ret; - } ret = smsc95xx_read_reg(dev, BURST_CAP, &read_buf); - if (ret < 0) { - netdev_warn(dev->net, "Failed to read BURST_CAP: %d\n", ret); + if (ret < 0) return ret; - } netif_dbg(dev, ifup, dev->net, "Read Value from BURST_CAP after writing: 0x%08x\n", read_buf); ret = smsc95xx_write_reg(dev, BULK_IN_DLY, DEFAULT_BULK_IN_DELAY); - if (ret < 0) { - netdev_warn(dev->net, "Failed to write BULK_IN_DLY: %d\n", ret); + if (ret < 0) return ret; - } ret = smsc95xx_read_reg(dev, BULK_IN_DLY, &read_buf); - if (ret < 0) { - netdev_warn(dev->net, "Failed to read BULK_IN_DLY: %d\n", ret); + if (ret < 0) return ret; - } netif_dbg(dev, ifup, dev->net, "Read Value from BULK_IN_DLY after writing: 0x%08x\n", read_buf); ret = smsc95xx_read_reg(dev, HW_CFG, &read_buf); - if (ret < 0) { - netdev_warn(dev->net, "Failed to read HW_CFG: %d\n", ret); + if (ret < 0) return ret; - } netif_dbg(dev, ifup, dev->net, "Read Value from HW_CFG: 0x%08x\n", read_buf); @@ -1051,69 +1000,51 @@ static int smsc95xx_reset(struct usbnet *dev) read_buf |= NET_IP_ALIGN << 9; ret = smsc95xx_write_reg(dev, HW_CFG, read_buf); - if (ret < 0) { - netdev_warn(dev->net, "Failed to write HW_CFG: %d\n", ret); + if (ret < 0) return ret; - } ret = smsc95xx_read_reg(dev, HW_CFG, &read_buf); - if (ret < 0) { - netdev_warn(dev->net, "Failed to read HW_CFG: %d\n", ret); + if (ret < 0) return ret; - } netif_dbg(dev, ifup, dev->net, "Read Value from HW_CFG after writing: 0x%08x\n", read_buf); ret = smsc95xx_write_reg(dev, INT_STS, INT_STS_CLEAR_ALL_); - if (ret < 0) { - netdev_warn(dev->net, "Failed to write INT_STS: %d\n", ret); + if (ret < 0) return ret; - } ret = smsc95xx_read_reg(dev, ID_REV, &read_buf); - if (ret < 0) { - netdev_warn(dev->net, "Failed to read ID_REV: %d\n", ret); + if (ret < 0) return ret; - } netif_dbg(dev, ifup, dev->net, "ID_REV = 0x%08x\n", read_buf); /* Configure GPIO pins as LED outputs */ write_buf = LED_GPIO_CFG_SPD_LED | LED_GPIO_CFG_LNK_LED | LED_GPIO_CFG_FDX_LED; ret = smsc95xx_write_reg(dev, LED_GPIO_CFG, write_buf); - if (ret < 0) { - netdev_warn(dev->net, "Failed to write LED_GPIO_CFG: %d\n", ret); + if (ret < 0) return ret; - } /* Init Tx */ ret = smsc95xx_write_reg(dev, FLOW, 0); - if (ret < 0) { - netdev_warn(dev->net, "Failed to write FLOW: %d\n", ret); + if (ret < 0) return ret; - } ret = smsc95xx_write_reg(dev, AFC_CFG, AFC_CFG_DEFAULT); - if (ret < 0) { - netdev_warn(dev->net, "Failed to write AFC_CFG: %d\n", ret); + if (ret < 0) return ret; - } /* Don't need mac_cr_lock during initialisation */ ret = smsc95xx_read_reg(dev, MAC_CR, &pdata->mac_cr); - if (ret < 0) { - netdev_warn(dev->net, "Failed to read MAC_CR: %d\n", ret); + if (ret < 0) return ret; - } /* Init Rx */ /* Set Vlan */ ret = smsc95xx_write_reg(dev, VLAN1, (u32)ETH_P_8021Q); - if (ret < 0) { - netdev_warn(dev->net, "Failed to write VLAN1: %d\n", ret); + if (ret < 0) return ret; - } /* Enable or disable checksum offload engines */ ret = smsc95xx_set_features(dev->net, dev->net->features); @@ -1131,19 +1062,15 @@ static int smsc95xx_reset(struct usbnet *dev) } ret = smsc95xx_read_reg(dev, INT_EP_CTL, &read_buf); - if (ret < 0) { - netdev_warn(dev->net, "Failed to read INT_EP_CTL: %d\n", ret); + if (ret < 0) return ret; - } /* enable PHY interrupts */ read_buf |= INT_EP_CTL_PHY_INT_; ret = smsc95xx_write_reg(dev, INT_EP_CTL, read_buf); - if (ret < 0) { - netdev_warn(dev->net, "Failed to write INT_EP_CTL: %d\n", ret); + if (ret < 0) return ret; - } ret = smsc95xx_start_tx_path(dev); if (ret < 0) { @@ -1189,13 +1116,11 @@ static int smsc95xx_bind(struct usbnet *dev, struct usb_interface *intf) } dev->data[0] = (unsigned long)kzalloc(sizeof(struct smsc95xx_priv), - GFP_KERNEL); + GFP_KERNEL); pdata = (struct smsc95xx_priv *)(dev->data[0]); - if (!pdata) { - netdev_warn(dev->net, "Unable to allocate struct smsc95xx_priv\n"); + if (!pdata) return -ENOMEM; - } spin_lock_init(&pdata->mac_cr_lock); @@ -1213,10 +1138,8 @@ static int smsc95xx_bind(struct usbnet *dev, struct usb_interface *intf) /* detect device revision as different features may be available */ ret = smsc95xx_read_reg(dev, ID_REV, &val); - if (ret < 0) { - netdev_warn(dev->net, "Failed to read ID_REV: %d\n", ret); + if (ret < 0) return ret; - } val >>= 16; if ((val == ID_REV_CHIP_ID_9500A_) || (val == ID_REV_CHIP_ID_9530_) || @@ -1261,17 +1184,13 @@ static int smsc95xx_enable_phy_wakeup_interrupts(struct usbnet *dev, u16 mask) /* read to clear */ ret = smsc95xx_mdio_read_nopm(dev->net, mii->phy_id, PHY_INT_SRC); - if (ret < 0) { - netdev_warn(dev->net, "Error reading PHY_INT_SRC\n"); + if (ret < 0) return ret; - } /* enable interrupt source */ ret = smsc95xx_mdio_read_nopm(dev->net, mii->phy_id, PHY_INT_MASK); - if (ret < 0) { - netdev_warn(dev->net, "Error reading PHY_INT_MASK\n"); + if (ret < 0) return ret; - } ret |= mask; @@ -1287,16 +1206,12 @@ static int smsc95xx_link_ok_nopm(struct usbnet *dev) /* first, a dummy read, needed to latch some MII phys */ ret = smsc95xx_mdio_read_nopm(dev->net, mii->phy_id, MII_BMSR); - if (ret < 0) { - netdev_warn(dev->net, "Error reading MII_BMSR\n"); + if (ret < 0) return ret; - } ret = smsc95xx_mdio_read_nopm(dev->net, mii->phy_id, MII_BMSR); - if (ret < 0) { - netdev_warn(dev->net, "Error reading MII_BMSR\n"); + if (ret < 0) return ret; - } return !!(ret & BMSR_LSTATUS); } @@ -1308,19 +1223,15 @@ static int smsc95xx_enter_suspend0(struct usbnet *dev) int ret; ret = smsc95xx_read_reg_nopm(dev, PM_CTRL, &val); - if (ret < 0) { - netdev_warn(dev->net, "Error reading PM_CTRL\n"); + if (ret < 0) return ret; - } val &= (~(PM_CTL_SUS_MODE_ | PM_CTL_WUPS_ | PM_CTL_PHY_RST_)); val |= PM_CTL_SUS_MODE_0; ret = smsc95xx_write_reg_nopm(dev, PM_CTRL, val); - if (ret < 0) { - netdev_warn(dev->net, "Error writing PM_CTRL\n"); + if (ret < 0) return ret; - } /* clear wol status */ val &= ~PM_CTL_WUPS_; @@ -1331,15 +1242,13 @@ static int smsc95xx_enter_suspend0(struct usbnet *dev) val |= PM_CTL_WUPS_ED_; ret = smsc95xx_write_reg_nopm(dev, PM_CTRL, val); - if (ret < 0) { - netdev_warn(dev->net, "Error writing PM_CTRL\n"); + if (ret < 0) return ret; - } /* read back PM_CTRL */ ret = smsc95xx_read_reg_nopm(dev, PM_CTRL, &val); - if (ret < 0) - netdev_warn(dev->net, "Error reading PM_CTRL\n"); + + pdata->suspend_flags |= SUSPEND_SUSPEND0; return ret; } @@ -1360,10 +1269,8 @@ static int smsc95xx_enter_suspend1(struct usbnet *dev) /* enable energy detect power-down mode */ ret = smsc95xx_mdio_read_nopm(dev->net, mii->phy_id, PHY_MODE_CTRL_STS); - if (ret < 0) { - netdev_warn(dev->net, "Error reading PHY_MODE_CTRL_STS\n"); + if (ret < 0) return ret; - } ret |= MODE_CTRL_STS_EDPWRDOWN_; @@ -1371,52 +1278,133 @@ static int smsc95xx_enter_suspend1(struct usbnet *dev) /* enter SUSPEND1 mode */ ret = smsc95xx_read_reg_nopm(dev, PM_CTRL, &val); - if (ret < 0) { - netdev_warn(dev->net, "Error reading PM_CTRL\n"); + if (ret < 0) return ret; - } val &= ~(PM_CTL_SUS_MODE_ | PM_CTL_WUPS_ | PM_CTL_PHY_RST_); val |= PM_CTL_SUS_MODE_1; ret = smsc95xx_write_reg_nopm(dev, PM_CTRL, val); - if (ret < 0) { - netdev_warn(dev->net, "Error writing PM_CTRL\n"); + if (ret < 0) return ret; - } /* clear wol status, enable energy detection */ val &= ~PM_CTL_WUPS_; val |= (PM_CTL_WUPS_ED_ | PM_CTL_ED_EN_); ret = smsc95xx_write_reg_nopm(dev, PM_CTRL, val); - if (ret < 0) - netdev_warn(dev->net, "Error writing PM_CTRL\n"); + + pdata->suspend_flags |= SUSPEND_SUSPEND1; return ret; } static int smsc95xx_enter_suspend2(struct usbnet *dev) { + struct smsc95xx_priv *pdata = (struct smsc95xx_priv *)(dev->data[0]); u32 val; int ret; ret = smsc95xx_read_reg_nopm(dev, PM_CTRL, &val); - if (ret < 0) { - netdev_warn(dev->net, "Error reading PM_CTRL\n"); + if (ret < 0) return ret; - } val &= ~(PM_CTL_SUS_MODE_ | PM_CTL_WUPS_ | PM_CTL_PHY_RST_); val |= PM_CTL_SUS_MODE_2; ret = smsc95xx_write_reg_nopm(dev, PM_CTRL, val); - if (ret < 0) - netdev_warn(dev->net, "Error writing PM_CTRL\n"); + + pdata->suspend_flags |= SUSPEND_SUSPEND2; return ret; } +static int smsc95xx_enter_suspend3(struct usbnet *dev) +{ + struct smsc95xx_priv *pdata = (struct smsc95xx_priv *)(dev->data[0]); + u32 val; + int ret; + + ret = smsc95xx_read_reg_nopm(dev, RX_FIFO_INF, &val); + if (ret < 0) + return ret; + + if (val & 0xFFFF) { + netdev_info(dev->net, "rx fifo not empty in autosuspend\n"); + return -EBUSY; + } + + ret = smsc95xx_read_reg_nopm(dev, PM_CTRL, &val); + if (ret < 0) + return ret; + + val &= ~(PM_CTL_SUS_MODE_ | PM_CTL_WUPS_ | PM_CTL_PHY_RST_); + val |= PM_CTL_SUS_MODE_3 | PM_CTL_RES_CLR_WKP_STS; + + ret = smsc95xx_write_reg_nopm(dev, PM_CTRL, val); + if (ret < 0) + return ret; + + /* clear wol status */ + val &= ~PM_CTL_WUPS_; + val |= PM_CTL_WUPS_WOL_; + + ret = smsc95xx_write_reg_nopm(dev, PM_CTRL, val); + if (ret < 0) + return ret; + + pdata->suspend_flags |= SUSPEND_SUSPEND3; + + return 0; +} + +static int smsc95xx_autosuspend(struct usbnet *dev, u32 link_up) +{ + struct smsc95xx_priv *pdata = (struct smsc95xx_priv *)(dev->data[0]); + 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 smsc95xx_enter_suspend2(dev); + } + + if (!link_up) { + /* link is down so enter EDPD mode, but only if device can + * reliably resume from it. This check should be redundant + * as current FEATURE_AUTOSUSPEND parts also support + * FEATURE_PHY_NLP_CROSSOVER but it's included for clarity */ + if (!(pdata->features & FEATURE_PHY_NLP_CROSSOVER)) { + netdev_warn(dev->net, "EDPD not supported\n"); + return -EBUSY; + } + + netdev_dbg(dev->net, "autosuspend entering SUSPEND1\n"); + + /* enable PHY wakeup events for if cable is attached */ + ret = smsc95xx_enable_phy_wakeup_interrupts(dev, + PHY_INT_MASK_ANEG_COMP_); + if (ret < 0) { + netdev_warn(dev->net, "error enabling PHY wakeup ints\n"); + return ret; + } + + netdev_info(dev->net, "entering SUSPEND1 mode\n"); + return smsc95xx_enter_suspend1(dev); + } + + /* enable PHY wakeup events so we remote wakeup if cable is pulled */ + ret = smsc95xx_enable_phy_wakeup_interrupts(dev, + PHY_INT_MASK_LINK_DOWN_); + if (ret < 0) { + netdev_warn(dev->net, "error enabling PHY wakeup ints\n"); + return ret; + } + + netdev_dbg(dev->net, "autosuspend entering SUSPEND3\n"); + return smsc95xx_enter_suspend3(dev); +} + static int smsc95xx_suspend(struct usb_interface *intf, pm_message_t message) { struct usbnet *dev = usb_get_intfdata(intf); @@ -1424,15 +1412,35 @@ static int smsc95xx_suspend(struct usb_interface *intf, pm_message_t message) u32 val, link_up; int ret; + /* TODO: don't indicate this feature to usb framework if + * our current hardware doesn't have the capability + */ + if ((message.event == PM_EVENT_AUTO_SUSPEND) && + (!(pdata->features & FEATURE_AUTOSUSPEND))) { + netdev_warn(dev->net, "autosuspend not supported\n"); + return -EBUSY; + } + ret = usbnet_suspend(intf, message); if (ret < 0) { netdev_warn(dev->net, "usbnet_suspend error\n"); return ret; } + 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 = smsc95xx_link_ok_nopm(dev); + if (message.event == PM_EVENT_AUTO_SUSPEND) { + ret = smsc95xx_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 */ @@ -1442,32 +1450,24 @@ static int smsc95xx_suspend(struct usb_interface *intf, pm_message_t message) /* disable energy detect (link up) & wake up events */ ret = smsc95xx_read_reg_nopm(dev, WUCSR, &val); - if (ret < 0) { - netdev_warn(dev->net, "Error reading WUCSR\n"); + if (ret < 0) goto done; - } val &= ~(WUCSR_MPEN_ | WUCSR_WAKE_EN_); ret = smsc95xx_write_reg_nopm(dev, WUCSR, val); - if (ret < 0) { - netdev_warn(dev->net, "Error writing WUCSR\n"); + if (ret < 0) goto done; - } ret = smsc95xx_read_reg_nopm(dev, PM_CTRL, &val); - if (ret < 0) { - netdev_warn(dev->net, "Error reading PM_CTRL\n"); + if (ret < 0) goto done; - } val &= ~(PM_CTL_ED_EN_ | PM_CTL_WOL_EN_); ret = smsc95xx_write_reg_nopm(dev, PM_CTRL, val); - if (ret < 0) { - netdev_warn(dev->net, "Error writing PM_CTRL\n"); + if (ret < 0) goto done; - } ret = smsc95xx_enter_suspend2(dev); goto done; @@ -1565,7 +1565,6 @@ static int smsc95xx_suspend(struct usb_interface *intf, pm_message_t message) for (i = 0; i < (wuff_filter_count * 4); i++) { ret = smsc95xx_write_reg_nopm(dev, WUFF, filter_mask[i]); if (ret < 0) { - netdev_warn(dev->net, "Error writing WUFF\n"); kfree(filter_mask); goto done; } @@ -1574,67 +1573,51 @@ static int smsc95xx_suspend(struct usb_interface *intf, pm_message_t message) for (i = 0; i < (wuff_filter_count / 4); i++) { ret = smsc95xx_write_reg_nopm(dev, WUFF, command[i]); - if (ret < 0) { - netdev_warn(dev->net, "Error writing WUFF\n"); + if (ret < 0) goto done; - } } for (i = 0; i < (wuff_filter_count / 4); i++) { ret = smsc95xx_write_reg_nopm(dev, WUFF, offset[i]); - if (ret < 0) { - netdev_warn(dev->net, "Error writing WUFF\n"); + if (ret < 0) goto done; - } } for (i = 0; i < (wuff_filter_count / 2); i++) { ret = smsc95xx_write_reg_nopm(dev, WUFF, crc[i]); - if (ret < 0) { - netdev_warn(dev->net, "Error writing WUFF\n"); + if (ret < 0) goto done; - } } /* clear any pending pattern match packet status */ ret = smsc95xx_read_reg_nopm(dev, WUCSR, &val); - if (ret < 0) { - netdev_warn(dev->net, "Error reading WUCSR\n"); + if (ret < 0) goto done; - } val |= WUCSR_WUFR_; ret = smsc95xx_write_reg_nopm(dev, WUCSR, val); - if (ret < 0) { - netdev_warn(dev->net, "Error writing WUCSR\n"); + if (ret < 0) goto done; - } } if (pdata->wolopts & WAKE_MAGIC) { /* clear any pending magic packet status */ ret = smsc95xx_read_reg_nopm(dev, WUCSR, &val); - if (ret < 0) { - netdev_warn(dev->net, "Error reading WUCSR\n"); + if (ret < 0) goto done; - } val |= WUCSR_MPR_; ret = smsc95xx_write_reg_nopm(dev, WUCSR, val); - if (ret < 0) { - netdev_warn(dev->net, "Error writing WUCSR\n"); + if (ret < 0) goto done; - } } /* enable/disable wakeup sources */ ret = smsc95xx_read_reg_nopm(dev, WUCSR, &val); - if (ret < 0) { - netdev_warn(dev->net, "Error reading WUCSR\n"); + if (ret < 0) goto done; - } if (pdata->wolopts & (WAKE_BCAST | WAKE_MCAST | WAKE_ARP | WAKE_UCAST)) { netdev_info(dev->net, "enabling pattern match wakeup\n"); @@ -1653,17 +1636,13 @@ static int smsc95xx_suspend(struct usb_interface *intf, pm_message_t message) } ret = smsc95xx_write_reg_nopm(dev, WUCSR, val); - if (ret < 0) { - netdev_warn(dev->net, "Error writing WUCSR\n"); + if (ret < 0) goto done; - } /* enable wol wakeup source */ ret = smsc95xx_read_reg_nopm(dev, PM_CTRL, &val); - if (ret < 0) { - netdev_warn(dev->net, "Error reading PM_CTRL\n"); + if (ret < 0) goto done; - } val |= PM_CTL_WOL_EN_; @@ -1672,10 +1651,8 @@ static int smsc95xx_suspend(struct usb_interface *intf, pm_message_t message) val |= PM_CTL_ED_EN_; ret = smsc95xx_write_reg_nopm(dev, PM_CTRL, val); - if (ret < 0) { - netdev_warn(dev->net, "Error writing PM_CTRL\n"); + if (ret < 0) goto done; - } /* enable receiver to enable frame reception */ smsc95xx_start_rx_path(dev, 1); @@ -1694,42 +1671,40 @@ static int smsc95xx_resume(struct usb_interface *intf) { struct usbnet *dev = usb_get_intfdata(intf); struct smsc95xx_priv *pdata = (struct smsc95xx_priv *)(dev->data[0]); + u8 suspend_flags = pdata->suspend_flags; int ret; u32 val; BUG_ON(!dev); - if (pdata->wolopts) { + netdev_dbg(dev->net, "resume suspend_flags=0x%02x\n", suspend_flags); + + /* do this first to ensure it's cleared even in error case */ + pdata->suspend_flags = 0; + + if (suspend_flags & SUSPEND_ALLMODES) { /* clear wake-up sources */ ret = smsc95xx_read_reg_nopm(dev, WUCSR, &val); - if (ret < 0) { - netdev_warn(dev->net, "Error reading WUCSR\n"); + if (ret < 0) return ret; - } val &= ~(WUCSR_WAKE_EN_ | WUCSR_MPEN_); ret = smsc95xx_write_reg_nopm(dev, WUCSR, val); - if (ret < 0) { - netdev_warn(dev->net, "Error writing WUCSR\n"); + if (ret < 0) return ret; - } /* clear wake-up status */ ret = smsc95xx_read_reg_nopm(dev, PM_CTRL, &val); - if (ret < 0) { - netdev_warn(dev->net, "Error reading PM_CTRL\n"); + if (ret < 0) return ret; - } val &= ~PM_CTL_WOL_EN_; val |= PM_CTL_WUPS_; ret = smsc95xx_write_reg_nopm(dev, PM_CTRL, val); - if (ret < 0) { - netdev_warn(dev->net, "Error writing PM_CTRL\n"); + if (ret < 0) return ret; - } } ret = usbnet_resume(intf); @@ -1891,6 +1866,26 @@ static struct sk_buff *smsc95xx_tx_fixup(struct usbnet *dev, return skb; } +static int smsc95xx_manage_power(struct usbnet *dev, int on) +{ + struct smsc95xx_priv *pdata = (struct smsc95xx_priv *)(dev->data[0]); + + dev->intf->needs_remote_wakeup = on; + + if (pdata->features & FEATURE_AUTOSUSPEND) + return 0; + + /* this chip revision doesn't support autosuspend */ + netdev_info(dev->net, "hardware doesn't support USB autosuspend\n"); + + if (on) + usb_autopm_get_interface_no_resume(dev->intf); + else + usb_autopm_put_interface(dev->intf); + + return 0; +} + static const struct driver_info smsc95xx_info = { .description = "smsc95xx USB 2.0 Ethernet", .bind = smsc95xx_bind, @@ -1900,6 +1895,7 @@ static const struct driver_info smsc95xx_info = { .rx_fixup = smsc95xx_rx_fixup, .tx_fixup = smsc95xx_tx_fixup, .status = smsc95xx_status, + .manage_power = smsc95xx_manage_power, .flags = FLAG_ETHER | FLAG_SEND_ZLP | FLAG_LINK_INTR, }; @@ -2007,6 +2003,7 @@ static struct usb_driver smsc95xx_driver = { .reset_resume = smsc95xx_resume, .disconnect = usbnet_disconnect, .disable_hub_initiated_lpm = 1, + .supports_autosuspend = 1, }; module_usb_driver(smsc95xx_driver); |