igb: Add new feature Media Auto Sense for 82580 devices only
authorCarolyn Wyborny <carolyn.wyborny@intel.com>
Thu, 17 Oct 2013 05:36:26 +0000 (05:36 +0000)
committerJeff Kirsher <jeffrey.t.kirsher@intel.com>
Tue, 10 Dec 2013 09:27:35 +0000 (01:27 -0800)
This patch adds support for the hardware feature Media Auto Sense.  This
feature requires a custom EEPROM image provided by our customer support
team.  The feature allows hardware designed with dual PHY's, fiber and
copper to be used with either media without additional EEPROM changes.
Fiber is preferred and driver will swap and configure for fiber media if
sensed by the device at any time. Device will swap back to copper if it
is the only media detected.

Signed-off-by: Carolyn Wyborny <carolyn.wyborny@intel.com>
Tested-by: Jeff Pieper <jeffrey.e.pieper@intel.com>
Signed-off-by: Jeff Kirsher <jeffrey.t.kirsher@intel.com>
drivers/net/ethernet/intel/igb/e1000_82575.c
drivers/net/ethernet/intel/igb/e1000_defines.h
drivers/net/ethernet/intel/igb/e1000_hw.h
drivers/net/ethernet/intel/igb/igb.h
drivers/net/ethernet/intel/igb/igb_ethtool.c
drivers/net/ethernet/intel/igb/igb_main.c

index fe9db48f9084e0b1374de3d3d966c8317cce0ca2..06df6928f44c2942f3e9b51647d047d790a2d6af 100644 (file)
@@ -441,6 +441,19 @@ static s32 igb_init_mac_params_82575(struct e1000_hw *hw)
                        ? igb_setup_copper_link_82575
                        : igb_setup_serdes_link_82575;
 
+       if (mac->type == e1000_82580) {
+               switch (hw->device_id) {
+               /* feature not supported on these id's */
+               case E1000_DEV_ID_DH89XXCC_SGMII:
+               case E1000_DEV_ID_DH89XXCC_SERDES:
+               case E1000_DEV_ID_DH89XXCC_BACKPLANE:
+               case E1000_DEV_ID_DH89XXCC_SFP:
+                       break;
+               default:
+                       hw->dev_spec._82575.mas_capable = true;
+                       break;
+               }
+       }
        return 0;
 }
 
index 378ca21aa647e390c972e7f812e703f4ce2553e0..0571b973be80d357bbbe4fec67c0fef0b3859546 100644 (file)
  */
 
 #define E1000_CONNSW_ENRGSRC             0x4
+#define E1000_CONNSW_PHYSD             0x400
+#define E1000_CONNSW_PHY_PDN           0x800
+#define E1000_CONNSW_SERDESD           0x200
+#define E1000_CONNSW_AUTOSENSE_CONF    0x2
+#define E1000_CONNSW_AUTOSENSE_EN      0x1
 #define E1000_PCS_CFG_PCS_EN             8
 #define E1000_PCS_LCTL_FLV_LINK_UP       1
 #define E1000_PCS_LCTL_FSV_100           2
index 5f9758f3206e314120ae210636292bd6dd42050c..ab99e2b582a85c7b445d54ec85f867c431cf1e29 100644 (file)
@@ -535,6 +535,7 @@ struct e1000_dev_spec_82575 {
        bool module_plugged;
        u8 media_port;
        bool media_changed;
+       bool mas_capable;
 };
 
 struct e1000_hw {
index 6c807927171f677c96e49b6268bc5e481cdb72f0..11173f46f436ee456a2eb5908ce4477bca346009 100644 (file)
@@ -450,6 +450,8 @@ struct igb_adapter {
        u8 rss_indir_tbl[IGB_RETA_SIZE];
 
        unsigned long link_check_timeout;
+       int copper_tries;
+       struct e1000_info ei;
 };
 
 #define IGB_FLAG_HAS_MSI               (1 << 0)
@@ -463,6 +465,14 @@ struct igb_adapter {
 #define IGB_FLAG_WOL_SUPPORTED         (1 << 8)
 #define IGB_FLAG_NEED_LINK_UPDATE      (1 << 9)
 #define IGB_FLAG_MEDIA_RESET           (1 << 10)
+#define IGB_FLAG_MAS_CAPABLE           (1 << 11)
+#define IGB_FLAG_MAS_ENABLE            (1 << 12)
+
+/* Media Auto Sense */
+#define IGB_MAS_ENABLE_0               0X0001
+#define IGB_MAS_ENABLE_1               0X0002
+#define IGB_MAS_ENABLE_2               0X0004
+#define IGB_MAS_ENABLE_3               0X0008
 
 /* DMA Coalescing defines */
 #define IGB_MIN_TXPBSIZE       20408
index c3143da497c82be85c4f580776290872e647e317..1c7d2381af8c626e7f5175223597257c323be2ba 100644 (file)
@@ -1983,6 +1983,10 @@ static void igb_diag_test(struct net_device *netdev,
        bool if_running = netif_running(netdev);
 
        set_bit(__IGB_TESTING, &adapter->state);
+
+       /* can't do offline tests on media switching devices */
+       if (adapter->hw.dev_spec._82575.mas_capable)
+               eth_test->flags &= ~ETH_TEST_FL_OFFLINE;
        if (eth_test->flags == ETH_TEST_FL_OFFLINE) {
                /* Offline tests */
 
index ed129f1c5c3e326cb7371e4ad8a99433775004ed..3bc10bd5bbc199ef3ade30d5ec28dea7b683392e 100644 (file)
@@ -1606,6 +1606,73 @@ static void igb_power_down_link(struct igb_adapter *adapter)
                igb_shutdown_serdes_link_82575(&adapter->hw);
 }
 
+/**
+ * Detect and switch function for Media Auto Sense
+ * @adapter: address of the board private structure
+ **/
+static void igb_check_swap_media(struct igb_adapter *adapter)
+{
+       struct e1000_hw *hw = &adapter->hw;
+       u32 ctrl_ext, connsw;
+       bool swap_now = false;
+
+       ctrl_ext = rd32(E1000_CTRL_EXT);
+       connsw = rd32(E1000_CONNSW);
+
+       /* need to live swap if current media is copper and we have fiber/serdes
+        * to go to.
+        */
+
+       if ((hw->phy.media_type == e1000_media_type_copper) &&
+           (!(connsw & E1000_CONNSW_AUTOSENSE_EN))) {
+               swap_now = true;
+       } else if (!(connsw & E1000_CONNSW_SERDESD)) {
+               /* copper signal takes time to appear */
+               if (adapter->copper_tries < 4) {
+                       adapter->copper_tries++;
+                       connsw |= E1000_CONNSW_AUTOSENSE_CONF;
+                       wr32(E1000_CONNSW, connsw);
+                       return;
+               } else {
+                       adapter->copper_tries = 0;
+                       if ((connsw & E1000_CONNSW_PHYSD) &&
+                           (!(connsw & E1000_CONNSW_PHY_PDN))) {
+                               swap_now = true;
+                               connsw &= ~E1000_CONNSW_AUTOSENSE_CONF;
+                               wr32(E1000_CONNSW, connsw);
+                       }
+               }
+       }
+
+       if (!swap_now)
+               return;
+
+       switch (hw->phy.media_type) {
+       case e1000_media_type_copper:
+               netdev_info(adapter->netdev,
+                       "MAS: changing media to fiber/serdes\n");
+               ctrl_ext |=
+                       E1000_CTRL_EXT_LINK_MODE_PCIE_SERDES;
+               adapter->flags |= IGB_FLAG_MEDIA_RESET;
+               adapter->copper_tries = 0;
+               break;
+       case e1000_media_type_internal_serdes:
+       case e1000_media_type_fiber:
+               netdev_info(adapter->netdev,
+                       "MAS: changing media to copper\n");
+               ctrl_ext &=
+                       ~E1000_CTRL_EXT_LINK_MODE_PCIE_SERDES;
+               adapter->flags |= IGB_FLAG_MEDIA_RESET;
+               break;
+       default:
+               /* shouldn't get here during regular operation */
+               netdev_err(adapter->netdev,
+                       "AMS: Invalid media type found, returning\n");
+               break;
+       }
+       wr32(E1000_CTRL_EXT, ctrl_ext);
+}
+
 /**
  *  igb_up - Open the interface and prepare it to handle traffic
  *  @adapter: board private structure
@@ -1719,6 +1786,37 @@ void igb_reinit_locked(struct igb_adapter *adapter)
        clear_bit(__IGB_RESETTING, &adapter->state);
 }
 
+/** igb_enable_mas - Media Autosense re-enable after swap
+ *
+ * @adapter: adapter struct
+ **/
+static s32 igb_enable_mas(struct igb_adapter *adapter)
+{
+       struct e1000_hw *hw = &adapter->hw;
+       u32 connsw;
+       s32 ret_val = 0;
+
+       connsw = rd32(E1000_CONNSW);
+       if (!(hw->phy.media_type == e1000_media_type_copper))
+               return ret_val;
+
+       /* configure for SerDes media detect */
+       if (!(connsw & E1000_CONNSW_SERDESD)) {
+               connsw |= E1000_CONNSW_ENRGSRC;
+               connsw |= E1000_CONNSW_AUTOSENSE_EN;
+               wr32(E1000_CONNSW, connsw);
+               wrfl();
+       } else if (connsw & E1000_CONNSW_SERDESD) {
+               /* already SerDes, no need to enable anything */
+               return ret_val;
+       } else {
+               netdev_info(adapter->netdev,
+                       "MAS: Unable to configure feature, disabling..\n");
+               adapter->flags &= ~IGB_FLAG_MAS_ENABLE;
+       }
+       return ret_val;
+}
+
 void igb_reset(struct igb_adapter *adapter)
 {
        struct pci_dev *pdev = adapter->pdev;
@@ -1830,6 +1928,16 @@ void igb_reset(struct igb_adapter *adapter)
        hw->mac.ops.reset_hw(hw);
        wr32(E1000_WUC, 0);
 
+       if (adapter->flags & IGB_FLAG_MEDIA_RESET) {
+               /* need to resetup here after media swap */
+               adapter->ei.get_invariants(hw);
+               adapter->flags &= ~IGB_FLAG_MEDIA_RESET;
+       }
+       if (adapter->flags & IGB_FLAG_MAS_ENABLE) {
+               if (igb_enable_mas(adapter))
+                       dev_err(&pdev->dev,
+                               "Error enabling Media Auto Sense\n");
+       }
        if (hw->mac.ops.init_hw(hw))
                dev_err(&pdev->dev, "Hardware Error\n");
 
@@ -1975,6 +2083,58 @@ void igb_set_fw_version(struct igb_adapter *adapter)
        return;
 }
 
+/**
+ * igb_init_mas - init Media Autosense feature if enabled in the NVM
+ *
+ * @adapter: adapter struct
+ **/
+static void igb_init_mas(struct igb_adapter *adapter)
+{
+       struct e1000_hw *hw = &adapter->hw;
+       u16 eeprom_data;
+
+       hw->nvm.ops.read(hw, NVM_COMPAT, 1, &eeprom_data);
+       switch (hw->bus.func) {
+       case E1000_FUNC_0:
+               if (eeprom_data & IGB_MAS_ENABLE_0) {
+                       adapter->flags |= IGB_FLAG_MAS_ENABLE;
+                       netdev_info(adapter->netdev,
+                               "MAS: Enabling Media Autosense for port %d\n",
+                               hw->bus.func);
+               }
+               break;
+       case E1000_FUNC_1:
+               if (eeprom_data & IGB_MAS_ENABLE_1) {
+                       adapter->flags |= IGB_FLAG_MAS_ENABLE;
+                       netdev_info(adapter->netdev,
+                               "MAS: Enabling Media Autosense for port %d\n",
+                               hw->bus.func);
+               }
+               break;
+       case E1000_FUNC_2:
+               if (eeprom_data & IGB_MAS_ENABLE_2) {
+                       adapter->flags |= IGB_FLAG_MAS_ENABLE;
+                       netdev_info(adapter->netdev,
+                               "MAS: Enabling Media Autosense for port %d\n",
+                               hw->bus.func);
+               }
+               break;
+       case E1000_FUNC_3:
+               if (eeprom_data & IGB_MAS_ENABLE_3) {
+                       adapter->flags |= IGB_FLAG_MAS_ENABLE;
+                       netdev_info(adapter->netdev,
+                               "MAS: Enabling Media Autosense for port %d\n",
+                               hw->bus.func);
+               }
+               break;
+       default:
+               /* Shouldn't get here */
+               netdev_err(adapter->netdev,
+                       "MAS: Invalid port configuration, returning\n");
+               break;
+       }
+}
+
 /**
  *  igb_init_i2c - Init I2C interface
  *  @adapter: pointer to adapter structure
@@ -2346,6 +2506,11 @@ static int igb_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
                adapter->ets = false;
        }
 #endif
+       /* Check if Media Autosense is enabled */
+       adapter->ei = *ei;
+       if (hw->dev_spec._82575.mas_capable)
+               igb_init_mas(adapter);
+
        /* do hw tstamp init after resetting */
        igb_ptp_init(adapter);
 
@@ -3931,6 +4096,7 @@ static void igb_watchdog_task(struct work_struct *work)
        struct net_device *netdev = adapter->netdev;
        u32 link;
        int i;
+       u32 connsw;
 
        link = igb_has_link(adapter);
 
@@ -3941,6 +4107,14 @@ static void igb_watchdog_task(struct work_struct *work)
                        link = false;
        }
 
+       /* Force link down if we have fiber to swap to */
+       if (adapter->flags & IGB_FLAG_MAS_ENABLE) {
+               if (hw->phy.media_type == e1000_media_type_copper) {
+                       connsw = rd32(E1000_CONNSW);
+                       if (!(connsw & E1000_CONNSW_AUTOSENSE_EN))
+                               link = 0;
+               }
+       }
        if (link) {
                /* Perform a reset if the media type changed. */
                if (hw->dev_spec._82575.media_changed) {
@@ -4028,8 +4202,27 @@ static void igb_watchdog_task(struct work_struct *work)
                                mod_timer(&adapter->phy_info_timer,
                                          round_jiffies(jiffies + 2 * HZ));
 
+                       /* link is down, time to check for alternate media */
+                       if (adapter->flags & IGB_FLAG_MAS_ENABLE) {
+                               igb_check_swap_media(adapter);
+                               if (adapter->flags & IGB_FLAG_MEDIA_RESET) {
+                                       schedule_work(&adapter->reset_task);
+                                       /* return immediately */
+                                       return;
+                               }
+                       }
                        pm_schedule_suspend(netdev->dev.parent,
                                            MSEC_PER_SEC * 5);
+
+               /* also check for alternate media here */
+               } else if (!netif_carrier_ok(netdev) &&
+                          (adapter->flags & IGB_FLAG_MAS_ENABLE)) {
+                       igb_check_swap_media(adapter);
+                       if (adapter->flags & IGB_FLAG_MEDIA_RESET) {
+                               schedule_work(&adapter->reset_task);
+                               /* return immediately */
+                               return;
+                       }
                }
        }