diff options
Diffstat (limited to 'drivers/net/usb/smsc95xx.c')
-rw-r--r-- | drivers/net/usb/smsc95xx.c | 160 |
1 files changed, 157 insertions, 3 deletions
diff --git a/drivers/net/usb/smsc95xx.c b/drivers/net/usb/smsc95xx.c index d9d2806a47b1..831aa33d078a 100644 --- a/drivers/net/usb/smsc95xx.c +++ b/drivers/net/usb/smsc95xx.c @@ -33,7 +33,7 @@ #include "smsc95xx.h" #define SMSC_CHIPNAME "smsc95xx" -#define SMSC_DRIVER_VERSION "1.0.4" +#define SMSC_DRIVER_VERSION "1.0.5" #define HS_USB_PKT_SIZE (512) #define FS_USB_PKT_SIZE (64) #define DEFAULT_HS_BURST_CAP_SIZE (16 * 1024 + 5 * HS_USB_PKT_SIZE) @@ -61,7 +61,10 @@ #define SUSPEND_ALLMODES (SUSPEND_SUSPEND0 | SUSPEND_SUSPEND1 | \ SUSPEND_SUSPEND2 | SUSPEND_SUSPEND3) +#define CARRIER_CHECK_DELAY (2 * HZ) + struct smsc95xx_priv { + u32 chip_id; u32 mac_cr; u32 hash_hi; u32 hash_lo; @@ -69,6 +72,10 @@ struct smsc95xx_priv { spinlock_t mac_cr_lock; u8 features; u8 suspend_flags; + u8 mdix_ctrl; + bool link_ok; + struct delayed_work carrier_check; + struct usbnet *dev; }; static bool turbo_mode = true; @@ -624,6 +631,44 @@ static void smsc95xx_status(struct usbnet *dev, struct urb *urb) intdata); } +static void set_carrier(struct usbnet *dev, bool link) +{ + struct smsc95xx_priv *pdata = (struct smsc95xx_priv *)(dev->data[0]); + + if (pdata->link_ok == link) + return; + + pdata->link_ok = link; + + if (link) + usbnet_link_change(dev, 1, 0); + else + usbnet_link_change(dev, 0, 0); +} + +static void check_carrier(struct work_struct *work) +{ + struct smsc95xx_priv *pdata = container_of(work, struct smsc95xx_priv, + carrier_check.work); + struct usbnet *dev = pdata->dev; + int ret; + + if (pdata->suspend_flags != 0) + return; + + ret = smsc95xx_mdio_read(dev->net, dev->mii.phy_id, MII_BMSR); + if (ret < 0) { + netdev_warn(dev->net, "Failed to read MII_BMSR\n"); + return; + } + if (ret & BMSR_LSTATUS) + set_carrier(dev, 1); + else + set_carrier(dev, 0); + + schedule_delayed_work(&pdata->carrier_check, CARRIER_CHECK_DELAY); +} + /* Enable or disable Tx & Rx checksum offload engines */ static int smsc95xx_set_features(struct net_device *netdev, netdev_features_t features) @@ -739,14 +784,113 @@ static int smsc95xx_ethtool_set_wol(struct net_device *net, return ret; } +static int get_mdix_status(struct net_device *net) +{ + struct usbnet *dev = netdev_priv(net); + u32 val; + int buf; + + buf = smsc95xx_mdio_read(dev->net, dev->mii.phy_id, SPECIAL_CTRL_STS); + if (buf & SPECIAL_CTRL_STS_OVRRD_AMDIX_) { + if (buf & SPECIAL_CTRL_STS_AMDIX_ENABLE_) + return ETH_TP_MDI_AUTO; + else if (buf & SPECIAL_CTRL_STS_AMDIX_STATE_) + return ETH_TP_MDI_X; + } else { + buf = smsc95xx_read_reg(dev, STRAP_STATUS, &val); + if (val & STRAP_STATUS_AMDIX_EN_) + return ETH_TP_MDI_AUTO; + } + + return ETH_TP_MDI; +} + +static void set_mdix_status(struct net_device *net, __u8 mdix_ctrl) +{ + struct usbnet *dev = netdev_priv(net); + struct smsc95xx_priv *pdata = (struct smsc95xx_priv *)(dev->data[0]); + int buf; + + if ((pdata->chip_id == ID_REV_CHIP_ID_9500A_) || + (pdata->chip_id == ID_REV_CHIP_ID_9530_) || + (pdata->chip_id == ID_REV_CHIP_ID_89530_) || + (pdata->chip_id == ID_REV_CHIP_ID_9730_)) { + /* Extend Manual AutoMDIX timer for 9500A/9500Ai */ + buf = smsc95xx_mdio_read(dev->net, dev->mii.phy_id, + PHY_EDPD_CONFIG); + buf |= PHY_EDPD_CONFIG_EXT_CROSSOVER_; + smsc95xx_mdio_write(dev->net, dev->mii.phy_id, + PHY_EDPD_CONFIG, buf); + } + + if (mdix_ctrl == ETH_TP_MDI) { + buf = smsc95xx_mdio_read(dev->net, dev->mii.phy_id, + SPECIAL_CTRL_STS); + buf |= SPECIAL_CTRL_STS_OVRRD_AMDIX_; + buf &= ~(SPECIAL_CTRL_STS_AMDIX_ENABLE_ | + SPECIAL_CTRL_STS_AMDIX_STATE_); + smsc95xx_mdio_write(dev->net, dev->mii.phy_id, + SPECIAL_CTRL_STS, buf); + } else if (mdix_ctrl == ETH_TP_MDI_X) { + buf = smsc95xx_mdio_read(dev->net, dev->mii.phy_id, + SPECIAL_CTRL_STS); + buf |= SPECIAL_CTRL_STS_OVRRD_AMDIX_; + buf &= ~(SPECIAL_CTRL_STS_AMDIX_ENABLE_ | + SPECIAL_CTRL_STS_AMDIX_STATE_); + buf |= SPECIAL_CTRL_STS_AMDIX_STATE_; + smsc95xx_mdio_write(dev->net, dev->mii.phy_id, + SPECIAL_CTRL_STS, buf); + } else if (mdix_ctrl == ETH_TP_MDI_AUTO) { + buf = smsc95xx_mdio_read(dev->net, dev->mii.phy_id, + SPECIAL_CTRL_STS); + buf &= ~SPECIAL_CTRL_STS_OVRRD_AMDIX_; + buf &= ~(SPECIAL_CTRL_STS_AMDIX_ENABLE_ | + SPECIAL_CTRL_STS_AMDIX_STATE_); + buf |= SPECIAL_CTRL_STS_AMDIX_ENABLE_; + smsc95xx_mdio_write(dev->net, dev->mii.phy_id, + SPECIAL_CTRL_STS, buf); + } + pdata->mdix_ctrl = mdix_ctrl; +} + +static int smsc95xx_get_settings(struct net_device *net, + struct ethtool_cmd *cmd) +{ + struct usbnet *dev = netdev_priv(net); + struct smsc95xx_priv *pdata = (struct smsc95xx_priv *)(dev->data[0]); + int retval; + + retval = usbnet_get_settings(net, cmd); + + cmd->eth_tp_mdix = pdata->mdix_ctrl; + cmd->eth_tp_mdix_ctrl = pdata->mdix_ctrl; + + return retval; +} + +static int smsc95xx_set_settings(struct net_device *net, + struct ethtool_cmd *cmd) +{ + struct usbnet *dev = netdev_priv(net); + struct smsc95xx_priv *pdata = (struct smsc95xx_priv *)(dev->data[0]); + int retval; + + if (pdata->mdix_ctrl != cmd->eth_tp_mdix_ctrl) + set_mdix_status(net, cmd->eth_tp_mdix_ctrl); + + retval = usbnet_set_settings(net, cmd); + + return retval; +} + static const struct ethtool_ops smsc95xx_ethtool_ops = { .get_link = usbnet_get_link, .nway_reset = usbnet_nway_reset, .get_drvinfo = usbnet_get_drvinfo, .get_msglevel = usbnet_get_msglevel, .set_msglevel = usbnet_set_msglevel, - .get_settings = usbnet_get_settings, - .set_settings = usbnet_set_settings, + .get_settings = smsc95xx_get_settings, + .set_settings = smsc95xx_set_settings, .get_eeprom_len = smsc95xx_ethtool_get_eeprom_len, .get_eeprom = smsc95xx_ethtool_get_eeprom, .set_eeprom = smsc95xx_ethtool_set_eeprom, @@ -1151,6 +1295,8 @@ static int smsc95xx_bind(struct usbnet *dev, struct usb_interface *intf) if (ret < 0) return ret; val >>= 16; + pdata->chip_id = val; + pdata->mdix_ctrl = get_mdix_status(dev->net); if ((val == ID_REV_CHIP_ID_9500A_) || (val == ID_REV_CHIP_ID_9530_) || (val == ID_REV_CHIP_ID_89530_) || (val == ID_REV_CHIP_ID_9730_)) @@ -1165,13 +1311,20 @@ static int smsc95xx_bind(struct usbnet *dev, struct usb_interface *intf) dev->net->flags |= IFF_MULTICAST; dev->net->hard_header_len += SMSC95XX_TX_OVERHEAD_CSUM; dev->hard_mtu = dev->net->mtu + dev->net->hard_header_len; + + pdata->dev = dev; + INIT_DELAYED_WORK(&pdata->carrier_check, check_carrier); + schedule_delayed_work(&pdata->carrier_check, CARRIER_CHECK_DELAY); + return 0; } static void smsc95xx_unbind(struct usbnet *dev, struct usb_interface *intf) { struct smsc95xx_priv *pdata = (struct smsc95xx_priv *)(dev->data[0]); + if (pdata) { + cancel_delayed_work(&pdata->carrier_check); netif_dbg(dev, ifdown, dev->net, "free pdata\n"); kfree(pdata); pdata = NULL; @@ -1695,6 +1848,7 @@ static int smsc95xx_resume(struct usb_interface *intf) /* do this first to ensure it's cleared even in error case */ pdata->suspend_flags = 0; + schedule_delayed_work(&pdata->carrier_check, CARRIER_CHECK_DELAY); if (suspend_flags & SUSPEND_ALLMODES) { /* clear wake-up sources */ |