amd-xgbe: Add ethtool support to retrieve SFP module info
authorTom Lendacky <thomas.lendacky@amd.com>
Wed, 23 May 2018 16:38:46 +0000 (11:38 -0500)
committerDavid S. Miller <davem@davemloft.net>
Wed, 23 May 2018 20:33:00 +0000 (16:33 -0400)
Add support to get SFP module information using ethtool.

Signed-off-by: Tom Lendacky <thomas.lendacky@amd.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
drivers/net/ethernet/amd/xgbe/xgbe-ethtool.c
drivers/net/ethernet/amd/xgbe/xgbe-mdio.c
drivers/net/ethernet/amd/xgbe/xgbe-phy-v2.c
drivers/net/ethernet/amd/xgbe/xgbe.h

index ff397bb..57394b7 100644 (file)
@@ -626,6 +626,22 @@ static int xgbe_get_ts_info(struct net_device *netdev,
        return 0;
 }
 
+static int xgbe_get_module_info(struct net_device *netdev,
+                               struct ethtool_modinfo *modinfo)
+{
+       struct xgbe_prv_data *pdata = netdev_priv(netdev);
+
+       return pdata->phy_if.module_info(pdata, modinfo);
+}
+
+static int xgbe_get_module_eeprom(struct net_device *netdev,
+                                 struct ethtool_eeprom *eeprom, u8 *data)
+{
+       struct xgbe_prv_data *pdata = netdev_priv(netdev);
+
+       return pdata->phy_if.module_eeprom(pdata, eeprom, data);
+}
+
 static const struct ethtool_ops xgbe_ethtool_ops = {
        .get_drvinfo = xgbe_get_drvinfo,
        .get_msglevel = xgbe_get_msglevel,
@@ -646,6 +662,8 @@ static const struct ethtool_ops xgbe_ethtool_ops = {
        .get_ts_info = xgbe_get_ts_info,
        .get_link_ksettings = xgbe_get_link_ksettings,
        .set_link_ksettings = xgbe_set_link_ksettings,
+       .get_module_info = xgbe_get_module_info,
+       .get_module_eeprom = xgbe_get_module_eeprom,
 };
 
 const struct ethtool_ops *xgbe_get_ethtool_ops(void)
index 1b45cd7..9c39c72 100644 (file)
 #include "xgbe.h"
 #include "xgbe-common.h"
 
+static int xgbe_phy_module_eeprom(struct xgbe_prv_data *pdata,
+                                 struct ethtool_eeprom *eeprom, u8 *data)
+{
+       if (!pdata->phy_if.phy_impl.module_eeprom)
+               return -ENXIO;
+
+       return pdata->phy_if.phy_impl.module_eeprom(pdata, eeprom, data);
+}
+
+static int xgbe_phy_module_info(struct xgbe_prv_data *pdata,
+                               struct ethtool_modinfo *modinfo)
+{
+       if (!pdata->phy_if.phy_impl.module_info)
+               return -ENXIO;
+
+       return pdata->phy_if.phy_impl.module_info(pdata, modinfo);
+}
+
 static void xgbe_an37_clear_interrupts(struct xgbe_prv_data *pdata)
 {
        int reg;
@@ -1639,4 +1657,7 @@ void xgbe_init_function_ptrs_phy(struct xgbe_phy_if *phy_if)
        phy_if->phy_valid_speed = xgbe_phy_valid_speed;
 
        phy_if->an_isr          = xgbe_an_combined_isr;
+
+       phy_if->module_info     = xgbe_phy_module_info;
+       phy_if->module_eeprom   = xgbe_phy_module_eeprom;
 }
index cb15caf..141bb13 100644 (file)
 #include <linux/kmod.h>
 #include <linux/mdio.h>
 #include <linux/phy.h>
+#include <linux/ethtool.h>
 
 #include "xgbe.h"
 #include "xgbe-common.h"
@@ -270,6 +271,15 @@ struct xgbe_sfp_eeprom {
        u8 vendor[32];
 };
 
+#define XGBE_SFP_DIAGS_SUPPORTED(_x)                   \
+       ((_x)->extd[XGBE_SFP_EXTD_SFF_8472] &&          \
+        !((_x)->extd[XGBE_SFP_EXTD_DIAG] & XGBE_SFP_EXTD_DIAG_ADDR_CHANGE))
+
+#define XGBE_SFP_EEPROM_BASE_LEN       256
+#define XGBE_SFP_EEPROM_DIAG_LEN       256
+#define XGBE_SFP_EEPROM_MAX            (XGBE_SFP_EEPROM_BASE_LEN +     \
+                                        XGBE_SFP_EEPROM_DIAG_LEN)
+
 #define XGBE_BEL_FUSE_VENDOR   "BEL-FUSE        "
 #define XGBE_BEL_FUSE_PARTNO   "1GBT-SFP06      "
 
@@ -1301,6 +1311,130 @@ put:
        xgbe_phy_put_comm_ownership(pdata);
 }
 
+static int xgbe_phy_module_eeprom(struct xgbe_prv_data *pdata,
+                                 struct ethtool_eeprom *eeprom, u8 *data)
+{
+       struct xgbe_phy_data *phy_data = pdata->phy_data;
+       u8 eeprom_addr, eeprom_data[XGBE_SFP_EEPROM_MAX];
+       struct xgbe_sfp_eeprom *sfp_eeprom;
+       unsigned int i, j, rem;
+       int ret;
+
+       rem = eeprom->len;
+
+       if (!eeprom->len) {
+               ret = -EINVAL;
+               goto done;
+       }
+
+       if ((eeprom->offset + eeprom->len) > XGBE_SFP_EEPROM_MAX) {
+               ret = -EINVAL;
+               goto done;
+       }
+
+       if (phy_data->port_mode != XGBE_PORT_MODE_SFP) {
+               ret = -ENXIO;
+               goto done;
+       }
+
+       if (!netif_running(pdata->netdev)) {
+               ret = -EIO;
+               goto done;
+       }
+
+       if (phy_data->sfp_mod_absent) {
+               ret = -EIO;
+               goto done;
+       }
+
+       ret = xgbe_phy_get_comm_ownership(pdata);
+       if (ret) {
+               ret = -EIO;
+               goto done;
+       }
+
+       ret = xgbe_phy_sfp_get_mux(pdata);
+       if (ret) {
+               netdev_err(pdata->netdev, "I2C error setting SFP MUX\n");
+               ret = -EIO;
+               goto put_own;
+       }
+
+       /* Read the SFP serial ID eeprom */
+       eeprom_addr = 0;
+       ret = xgbe_phy_i2c_read(pdata, XGBE_SFP_SERIAL_ID_ADDRESS,
+                               &eeprom_addr, sizeof(eeprom_addr),
+                               eeprom_data, XGBE_SFP_EEPROM_BASE_LEN);
+       if (ret) {
+               netdev_err(pdata->netdev,
+                          "I2C error reading SFP EEPROM\n");
+               ret = -EIO;
+               goto put_mux;
+       }
+
+       sfp_eeprom = (struct xgbe_sfp_eeprom *)eeprom_data;
+
+       if (XGBE_SFP_DIAGS_SUPPORTED(sfp_eeprom)) {
+               /* Read the SFP diagnostic eeprom */
+               eeprom_addr = 0;
+               ret = xgbe_phy_i2c_read(pdata, XGBE_SFP_DIAG_INFO_ADDRESS,
+                                       &eeprom_addr, sizeof(eeprom_addr),
+                                       eeprom_data + XGBE_SFP_EEPROM_BASE_LEN,
+                                       XGBE_SFP_EEPROM_DIAG_LEN);
+               if (ret) {
+                       netdev_err(pdata->netdev,
+                                  "I2C error reading SFP DIAGS\n");
+                       ret = -EIO;
+                       goto put_mux;
+               }
+       }
+
+       for (i = 0, j = eeprom->offset; i < eeprom->len; i++, j++) {
+               if ((j >= XGBE_SFP_EEPROM_BASE_LEN) &&
+                   !XGBE_SFP_DIAGS_SUPPORTED(sfp_eeprom))
+                       break;
+
+               data[i] = eeprom_data[j];
+               rem--;
+       }
+
+put_mux:
+       xgbe_phy_sfp_put_mux(pdata);
+
+put_own:
+       xgbe_phy_put_comm_ownership(pdata);
+
+done:
+       eeprom->len -= rem;
+
+       return ret;
+}
+
+static int xgbe_phy_module_info(struct xgbe_prv_data *pdata,
+                               struct ethtool_modinfo *modinfo)
+{
+       struct xgbe_phy_data *phy_data = pdata->phy_data;
+
+       if (phy_data->port_mode != XGBE_PORT_MODE_SFP)
+               return -ENXIO;
+
+       if (!netif_running(pdata->netdev))
+               return -EIO;
+
+       if (phy_data->sfp_mod_absent)
+               return -EIO;
+
+       if (XGBE_SFP_DIAGS_SUPPORTED(&phy_data->sfp_eeprom)) {
+               modinfo->type = ETH_MODULE_SFF_8472;
+               modinfo->eeprom_len = ETH_MODULE_SFF_8472_LEN;
+       } else {
+               modinfo->type = ETH_MODULE_SFF_8079;
+               modinfo->eeprom_len = ETH_MODULE_SFF_8079_LEN;
+       }
+
+       return 0;
+}
+
 static void xgbe_phy_phydev_flowctrl(struct xgbe_prv_data *pdata)
 {
        struct ethtool_link_ksettings *lks = &pdata->phy.lks;
@@ -3196,4 +3330,7 @@ void xgbe_init_function_ptrs_phy_v2(struct xgbe_phy_if *phy_if)
 
        phy_impl->kr_training_pre       = xgbe_phy_kr_training_pre;
        phy_impl->kr_training_post      = xgbe_phy_kr_training_post;
+
+       phy_impl->module_info           = xgbe_phy_module_info;
+       phy_impl->module_eeprom         = xgbe_phy_module_eeprom;
 }
index 54e43ad..f0f455b 100644 (file)
@@ -835,6 +835,7 @@ struct xgbe_hw_if {
  *   Optional routines:
  *     an_pre, an_post
  *     kr_training_pre, kr_training_post
+ *     module_info, module_eeprom
  */
 struct xgbe_phy_impl_if {
        /* Perform Setup/teardown actions */
@@ -883,6 +884,12 @@ struct xgbe_phy_impl_if {
        /* Pre/Post KR training enablement support */
        void (*kr_training_pre)(struct xgbe_prv_data *);
        void (*kr_training_post)(struct xgbe_prv_data *);
+
+       /* SFP module related info */
+       int (*module_info)(struct xgbe_prv_data *pdata,
+                          struct ethtool_modinfo *modinfo);
+       int (*module_eeprom)(struct xgbe_prv_data *pdata,
+                            struct ethtool_eeprom *eeprom, u8 *data);
 };
 
 struct xgbe_phy_if {
@@ -905,6 +912,12 @@ struct xgbe_phy_if {
        /* For single interrupt support */
        irqreturn_t (*an_isr)(struct xgbe_prv_data *);
 
+       /* For ethtool PHY support */
+       int (*module_info)(struct xgbe_prv_data *pdata,
+                          struct ethtool_modinfo *modinfo);
+       int (*module_eeprom)(struct xgbe_prv_data *pdata,
+                            struct ethtool_eeprom *eeprom, u8 *data);
+
        /* PHY implementation specific services */
        struct xgbe_phy_impl_if phy_impl;
 };