diff options
-rw-r--r-- | drivers/net/usb/smsc95xx.c | 151 |
1 files changed, 150 insertions, 1 deletions
diff --git a/drivers/net/usb/smsc95xx.c b/drivers/net/usb/smsc95xx.c index 124e67f7eed3..6a74a68220be 100644 --- a/drivers/net/usb/smsc95xx.c +++ b/drivers/net/usb/smsc95xx.c | |||
@@ -55,6 +55,13 @@ | |||
55 | #define FEATURE_PHY_NLP_CROSSOVER (0x02) | 55 | #define FEATURE_PHY_NLP_CROSSOVER (0x02) |
56 | #define FEATURE_AUTOSUSPEND (0x04) | 56 | #define FEATURE_AUTOSUSPEND (0x04) |
57 | 57 | ||
58 | #define SUSPEND_SUSPEND0 (0x01) | ||
59 | #define SUSPEND_SUSPEND1 (0x02) | ||
60 | #define SUSPEND_SUSPEND2 (0x04) | ||
61 | #define SUSPEND_SUSPEND3 (0x08) | ||
62 | #define SUSPEND_ALLMODES (SUSPEND_SUSPEND0 | SUSPEND_SUSPEND1 | \ | ||
63 | SUSPEND_SUSPEND2 | SUSPEND_SUSPEND3) | ||
64 | |||
58 | struct smsc95xx_priv { | 65 | struct smsc95xx_priv { |
59 | u32 mac_cr; | 66 | u32 mac_cr; |
60 | u32 hash_hi; | 67 | u32 hash_hi; |
@@ -62,6 +69,7 @@ struct smsc95xx_priv { | |||
62 | u32 wolopts; | 69 | u32 wolopts; |
63 | spinlock_t mac_cr_lock; | 70 | spinlock_t mac_cr_lock; |
64 | u8 features; | 71 | u8 features; |
72 | u8 suspend_flags; | ||
65 | }; | 73 | }; |
66 | 74 | ||
67 | static bool turbo_mode = true; | 75 | static bool turbo_mode = true; |
@@ -1242,6 +1250,8 @@ static int smsc95xx_enter_suspend0(struct usbnet *dev) | |||
1242 | /* read back PM_CTRL */ | 1250 | /* read back PM_CTRL */ |
1243 | ret = smsc95xx_read_reg_nopm(dev, PM_CTRL, &val); | 1251 | ret = smsc95xx_read_reg_nopm(dev, PM_CTRL, &val); |
1244 | 1252 | ||
1253 | pdata->suspend_flags |= SUSPEND_SUSPEND0; | ||
1254 | |||
1245 | return ret; | 1255 | return ret; |
1246 | } | 1256 | } |
1247 | 1257 | ||
@@ -1286,11 +1296,14 @@ static int smsc95xx_enter_suspend1(struct usbnet *dev) | |||
1286 | 1296 | ||
1287 | ret = smsc95xx_write_reg_nopm(dev, PM_CTRL, val); | 1297 | ret = smsc95xx_write_reg_nopm(dev, PM_CTRL, val); |
1288 | 1298 | ||
1299 | pdata->suspend_flags |= SUSPEND_SUSPEND1; | ||
1300 | |||
1289 | return ret; | 1301 | return ret; |
1290 | } | 1302 | } |
1291 | 1303 | ||
1292 | static int smsc95xx_enter_suspend2(struct usbnet *dev) | 1304 | static int smsc95xx_enter_suspend2(struct usbnet *dev) |
1293 | { | 1305 | { |
1306 | struct smsc95xx_priv *pdata = (struct smsc95xx_priv *)(dev->data[0]); | ||
1294 | u32 val; | 1307 | u32 val; |
1295 | int ret; | 1308 | int ret; |
1296 | 1309 | ||
@@ -1303,9 +1316,97 @@ static int smsc95xx_enter_suspend2(struct usbnet *dev) | |||
1303 | 1316 | ||
1304 | ret = smsc95xx_write_reg_nopm(dev, PM_CTRL, val); | 1317 | ret = smsc95xx_write_reg_nopm(dev, PM_CTRL, val); |
1305 | 1318 | ||
1319 | pdata->suspend_flags |= SUSPEND_SUSPEND2; | ||
1320 | |||
1306 | return ret; | 1321 | return ret; |
1307 | } | 1322 | } |
1308 | 1323 | ||
1324 | static int smsc95xx_enter_suspend3(struct usbnet *dev) | ||
1325 | { | ||
1326 | struct smsc95xx_priv *pdata = (struct smsc95xx_priv *)(dev->data[0]); | ||
1327 | u32 val; | ||
1328 | int ret; | ||
1329 | |||
1330 | ret = smsc95xx_read_reg_nopm(dev, RX_FIFO_INF, &val); | ||
1331 | if (ret < 0) | ||
1332 | return ret; | ||
1333 | |||
1334 | if (val & 0xFFFF) { | ||
1335 | netdev_info(dev->net, "rx fifo not empty in autosuspend\n"); | ||
1336 | return -EBUSY; | ||
1337 | } | ||
1338 | |||
1339 | ret = smsc95xx_read_reg_nopm(dev, PM_CTRL, &val); | ||
1340 | if (ret < 0) | ||
1341 | return ret; | ||
1342 | |||
1343 | val &= ~(PM_CTL_SUS_MODE_ | PM_CTL_WUPS_ | PM_CTL_PHY_RST_); | ||
1344 | val |= PM_CTL_SUS_MODE_3 | PM_CTL_RES_CLR_WKP_STS; | ||
1345 | |||
1346 | ret = smsc95xx_write_reg_nopm(dev, PM_CTRL, val); | ||
1347 | if (ret < 0) | ||
1348 | return ret; | ||
1349 | |||
1350 | /* clear wol status */ | ||
1351 | val &= ~PM_CTL_WUPS_; | ||
1352 | val |= PM_CTL_WUPS_WOL_; | ||
1353 | |||
1354 | ret = smsc95xx_write_reg_nopm(dev, PM_CTRL, val); | ||
1355 | if (ret < 0) | ||
1356 | return ret; | ||
1357 | |||
1358 | pdata->suspend_flags |= SUSPEND_SUSPEND3; | ||
1359 | |||
1360 | return 0; | ||
1361 | } | ||
1362 | |||
1363 | static int smsc95xx_autosuspend(struct usbnet *dev, u32 link_up) | ||
1364 | { | ||
1365 | struct smsc95xx_priv *pdata = (struct smsc95xx_priv *)(dev->data[0]); | ||
1366 | int ret; | ||
1367 | |||
1368 | if (!netif_running(dev->net)) { | ||
1369 | /* interface is ifconfig down so fully power down hw */ | ||
1370 | netdev_dbg(dev->net, "autosuspend entering SUSPEND2\n"); | ||
1371 | return smsc95xx_enter_suspend2(dev); | ||
1372 | } | ||
1373 | |||
1374 | if (!link_up) { | ||
1375 | /* link is down so enter EDPD mode, but only if device can | ||
1376 | * reliably resume from it. This check should be redundant | ||
1377 | * as current FEATURE_AUTOSUSPEND parts also support | ||
1378 | * FEATURE_PHY_NLP_CROSSOVER but it's included for clarity */ | ||
1379 | if (!(pdata->features & FEATURE_PHY_NLP_CROSSOVER)) { | ||
1380 | netdev_warn(dev->net, "EDPD not supported\n"); | ||
1381 | return -EBUSY; | ||
1382 | } | ||
1383 | |||
1384 | netdev_dbg(dev->net, "autosuspend entering SUSPEND1\n"); | ||
1385 | |||
1386 | /* enable PHY wakeup events for if cable is attached */ | ||
1387 | ret = smsc95xx_enable_phy_wakeup_interrupts(dev, | ||
1388 | PHY_INT_MASK_ANEG_COMP_); | ||
1389 | if (ret < 0) { | ||
1390 | netdev_warn(dev->net, "error enabling PHY wakeup ints\n"); | ||
1391 | return ret; | ||
1392 | } | ||
1393 | |||
1394 | netdev_info(dev->net, "entering SUSPEND1 mode\n"); | ||
1395 | return smsc95xx_enter_suspend1(dev); | ||
1396 | } | ||
1397 | |||
1398 | /* enable PHY wakeup events so we remote wakeup if cable is pulled */ | ||
1399 | ret = smsc95xx_enable_phy_wakeup_interrupts(dev, | ||
1400 | PHY_INT_MASK_LINK_DOWN_); | ||
1401 | if (ret < 0) { | ||
1402 | netdev_warn(dev->net, "error enabling PHY wakeup ints\n"); | ||
1403 | return ret; | ||
1404 | } | ||
1405 | |||
1406 | netdev_dbg(dev->net, "autosuspend entering SUSPEND3\n"); | ||
1407 | return smsc95xx_enter_suspend3(dev); | ||
1408 | } | ||
1409 | |||
1309 | static int smsc95xx_suspend(struct usb_interface *intf, pm_message_t message) | 1410 | static int smsc95xx_suspend(struct usb_interface *intf, pm_message_t message) |
1310 | { | 1411 | { |
1311 | struct usbnet *dev = usb_get_intfdata(intf); | 1412 | struct usbnet *dev = usb_get_intfdata(intf); |
@@ -1313,15 +1414,35 @@ static int smsc95xx_suspend(struct usb_interface *intf, pm_message_t message) | |||
1313 | u32 val, link_up; | 1414 | u32 val, link_up; |
1314 | int ret; | 1415 | int ret; |
1315 | 1416 | ||
1417 | /* TODO: don't indicate this feature to usb framework if | ||
1418 | * our current hardware doesn't have the capability | ||
1419 | */ | ||
1420 | if ((message.event == PM_EVENT_AUTO_SUSPEND) && | ||
1421 | (!(pdata->features & FEATURE_AUTOSUSPEND))) { | ||
1422 | netdev_warn(dev->net, "autosuspend not supported\n"); | ||
1423 | return -EBUSY; | ||
1424 | } | ||
1425 | |||
1316 | ret = usbnet_suspend(intf, message); | 1426 | ret = usbnet_suspend(intf, message); |
1317 | if (ret < 0) { | 1427 | if (ret < 0) { |
1318 | netdev_warn(dev->net, "usbnet_suspend error\n"); | 1428 | netdev_warn(dev->net, "usbnet_suspend error\n"); |
1319 | return ret; | 1429 | return ret; |
1320 | } | 1430 | } |
1321 | 1431 | ||
1432 | if (pdata->suspend_flags) { | ||
1433 | netdev_warn(dev->net, "error during last resume\n"); | ||
1434 | pdata->suspend_flags = 0; | ||
1435 | } | ||
1436 | |||
1322 | /* determine if link is up using only _nopm functions */ | 1437 | /* determine if link is up using only _nopm functions */ |
1323 | link_up = smsc95xx_link_ok_nopm(dev); | 1438 | link_up = smsc95xx_link_ok_nopm(dev); |
1324 | 1439 | ||
1440 | if (message.event == PM_EVENT_AUTO_SUSPEND) { | ||
1441 | ret = smsc95xx_autosuspend(dev, link_up); | ||
1442 | goto done; | ||
1443 | } | ||
1444 | |||
1445 | /* if we get this far we're not autosuspending */ | ||
1325 | /* if no wol options set, or if link is down and we're not waking on | 1446 | /* if no wol options set, or if link is down and we're not waking on |
1326 | * PHY activity, enter lowest power SUSPEND2 mode | 1447 | * PHY activity, enter lowest power SUSPEND2 mode |
1327 | */ | 1448 | */ |
@@ -1552,12 +1673,18 @@ static int smsc95xx_resume(struct usb_interface *intf) | |||
1552 | { | 1673 | { |
1553 | struct usbnet *dev = usb_get_intfdata(intf); | 1674 | struct usbnet *dev = usb_get_intfdata(intf); |
1554 | struct smsc95xx_priv *pdata = (struct smsc95xx_priv *)(dev->data[0]); | 1675 | struct smsc95xx_priv *pdata = (struct smsc95xx_priv *)(dev->data[0]); |
1676 | u8 suspend_flags = pdata->suspend_flags; | ||
1555 | int ret; | 1677 | int ret; |
1556 | u32 val; | 1678 | u32 val; |
1557 | 1679 | ||
1558 | BUG_ON(!dev); | 1680 | BUG_ON(!dev); |
1559 | 1681 | ||
1560 | if (pdata->wolopts) { | 1682 | netdev_dbg(dev->net, "resume suspend_flags=0x%02x\n", suspend_flags); |
1683 | |||
1684 | /* do this first to ensure it's cleared even in error case */ | ||
1685 | pdata->suspend_flags = 0; | ||
1686 | |||
1687 | if (suspend_flags & SUSPEND_ALLMODES) { | ||
1561 | /* clear wake-up sources */ | 1688 | /* clear wake-up sources */ |
1562 | ret = smsc95xx_read_reg_nopm(dev, WUCSR, &val); | 1689 | ret = smsc95xx_read_reg_nopm(dev, WUCSR, &val); |
1563 | if (ret < 0) | 1690 | if (ret < 0) |
@@ -1741,6 +1868,26 @@ static struct sk_buff *smsc95xx_tx_fixup(struct usbnet *dev, | |||
1741 | return skb; | 1868 | return skb; |
1742 | } | 1869 | } |
1743 | 1870 | ||
1871 | static int smsc95xx_manage_power(struct usbnet *dev, int on) | ||
1872 | { | ||
1873 | struct smsc95xx_priv *pdata = (struct smsc95xx_priv *)(dev->data[0]); | ||
1874 | |||
1875 | dev->intf->needs_remote_wakeup = on; | ||
1876 | |||
1877 | if (pdata->features & FEATURE_AUTOSUSPEND) | ||
1878 | return 0; | ||
1879 | |||
1880 | /* this chip revision doesn't support autosuspend */ | ||
1881 | netdev_info(dev->net, "hardware doesn't support USB autosuspend\n"); | ||
1882 | |||
1883 | if (on) | ||
1884 | usb_autopm_get_interface_no_resume(dev->intf); | ||
1885 | else | ||
1886 | usb_autopm_put_interface(dev->intf); | ||
1887 | |||
1888 | return 0; | ||
1889 | } | ||
1890 | |||
1744 | static const struct driver_info smsc95xx_info = { | 1891 | static const struct driver_info smsc95xx_info = { |
1745 | .description = "smsc95xx USB 2.0 Ethernet", | 1892 | .description = "smsc95xx USB 2.0 Ethernet", |
1746 | .bind = smsc95xx_bind, | 1893 | .bind = smsc95xx_bind, |
@@ -1750,6 +1897,7 @@ static const struct driver_info smsc95xx_info = { | |||
1750 | .rx_fixup = smsc95xx_rx_fixup, | 1897 | .rx_fixup = smsc95xx_rx_fixup, |
1751 | .tx_fixup = smsc95xx_tx_fixup, | 1898 | .tx_fixup = smsc95xx_tx_fixup, |
1752 | .status = smsc95xx_status, | 1899 | .status = smsc95xx_status, |
1900 | .manage_power = smsc95xx_manage_power, | ||
1753 | .flags = FLAG_ETHER | FLAG_SEND_ZLP | FLAG_LINK_INTR, | 1901 | .flags = FLAG_ETHER | FLAG_SEND_ZLP | FLAG_LINK_INTR, |
1754 | }; | 1902 | }; |
1755 | 1903 | ||
@@ -1857,6 +2005,7 @@ static struct usb_driver smsc95xx_driver = { | |||
1857 | .reset_resume = smsc95xx_resume, | 2005 | .reset_resume = smsc95xx_resume, |
1858 | .disconnect = usbnet_disconnect, | 2006 | .disconnect = usbnet_disconnect, |
1859 | .disable_hub_initiated_lpm = 1, | 2007 | .disable_hub_initiated_lpm = 1, |
2008 | .supports_autosuspend = 1, | ||
1860 | }; | 2009 | }; |
1861 | 2010 | ||
1862 | module_usb_driver(smsc95xx_driver); | 2011 | module_usb_driver(smsc95xx_driver); |