net/mlx5e: Add ethtool support for dump module EEPROM
authorGal Pressman <galp@mellanox.com>
Sun, 24 Apr 2016 19:51:54 +0000 (22:51 +0300)
committerDavid S. Miller <davem@davemloft.net>
Tue, 26 Apr 2016 19:58:02 +0000 (15:58 -0400)
Add query MCIA, PMLP registers infrastructure and commands.
Add ethtool support for get_module_info() and get_module_eeprom()
callbacks.

Signed-off-by: Gal Pressman <galp@mellanox.com>
Signed-off-by: Saeed Mahameed <saeedm@mellanox.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
drivers/net/ethernet/mellanox/mlx5/core/en_ethtool.c
drivers/net/ethernet/mellanox/mlx5/core/port.c
include/linux/mlx5/driver.h
include/linux/mlx5/port.h

index a2c444ec191b9723fac808b290ea4330050ce907..0518c8658507dfeb3f412280b224805ca47a098a 100644 (file)
@@ -1159,6 +1159,84 @@ static int mlx5e_set_phys_id(struct net_device *dev,
        return mlx5_set_port_beacon(mdev, beacon_duration);
 }
 
+static int mlx5e_get_module_info(struct net_device *netdev,
+                                struct ethtool_modinfo *modinfo)
+{
+       struct mlx5e_priv *priv = netdev_priv(netdev);
+       struct mlx5_core_dev *dev = priv->mdev;
+       int size_read = 0;
+       u8 data[4];
+
+       size_read = mlx5_query_module_eeprom(dev, 0, 2, data);
+       if (size_read < 2)
+               return -EIO;
+
+       /* data[0] = identifier byte */
+       switch (data[0]) {
+       case MLX5_MODULE_ID_QSFP:
+               modinfo->type       = ETH_MODULE_SFF_8436;
+               modinfo->eeprom_len = ETH_MODULE_SFF_8436_LEN;
+               break;
+       case MLX5_MODULE_ID_QSFP_PLUS:
+       case MLX5_MODULE_ID_QSFP28:
+               /* data[1] = revision id */
+               if (data[0] == MLX5_MODULE_ID_QSFP28 || data[1] >= 0x3) {
+                       modinfo->type       = ETH_MODULE_SFF_8636;
+                       modinfo->eeprom_len = ETH_MODULE_SFF_8636_LEN;
+               } else {
+                       modinfo->type       = ETH_MODULE_SFF_8436;
+                       modinfo->eeprom_len = ETH_MODULE_SFF_8436_LEN;
+               }
+               break;
+       case MLX5_MODULE_ID_SFP:
+               modinfo->type       = ETH_MODULE_SFF_8472;
+               modinfo->eeprom_len = ETH_MODULE_SFF_8472_LEN;
+               break;
+       default:
+               netdev_err(priv->netdev, "%s: cable type not recognized:0x%x\n",
+                          __func__, data[0]);
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+static int mlx5e_get_module_eeprom(struct net_device *netdev,
+                                  struct ethtool_eeprom *ee,
+                                  u8 *data)
+{
+       struct mlx5e_priv *priv = netdev_priv(netdev);
+       struct mlx5_core_dev *mdev = priv->mdev;
+       int offset = ee->offset;
+       int size_read;
+       int i = 0;
+
+       if (!ee->len)
+               return -EINVAL;
+
+       memset(data, 0, ee->len);
+
+       while (i < ee->len) {
+               size_read = mlx5_query_module_eeprom(mdev, offset, ee->len - i,
+                                                    data + i);
+
+               if (!size_read)
+                       /* Done reading */
+                       return 0;
+
+               if (size_read < 0) {
+                       netdev_err(priv->netdev, "%s: mlx5_query_eeprom failed:0x%x\n",
+                                  __func__, size_read);
+                       return 0;
+               }
+
+               i += size_read;
+               offset += size_read;
+       }
+
+       return 0;
+}
+
 const struct ethtool_ops mlx5e_ethtool_ops = {
        .get_drvinfo       = mlx5e_get_drvinfo,
        .get_link          = ethtool_op_get_link,
@@ -1186,4 +1264,6 @@ const struct ethtool_ops mlx5e_ethtool_ops = {
        .set_phys_id       = mlx5e_set_phys_id,
        .get_wol           = mlx5e_get_wol,
        .set_wol           = mlx5e_set_wol,
+       .get_module_info   = mlx5e_get_module_info,
+       .get_module_eeprom = mlx5e_get_module_eeprom,
 };
index 446549f63b5ce677aef029c8f7fda1604458bb30..4cb2a44510fa32ddb4e0e6b04d803538437898c3 100644 (file)
@@ -310,6 +310,82 @@ void mlx5_query_port_oper_mtu(struct mlx5_core_dev *dev, int *oper_mtu,
 }
 EXPORT_SYMBOL_GPL(mlx5_query_port_oper_mtu);
 
+static int mlx5_query_module_num(struct mlx5_core_dev *dev, int *module_num)
+{
+       u32 out[MLX5_ST_SZ_DW(pmlp_reg)];
+       u32 in[MLX5_ST_SZ_DW(pmlp_reg)];
+       int module_mapping;
+       int err;
+
+       memset(in, 0, sizeof(in));
+
+       MLX5_SET(pmlp_reg, in, local_port, 1);
+
+       err = mlx5_core_access_reg(dev, in, sizeof(in), out, sizeof(out),
+                                  MLX5_REG_PMLP, 0, 0);
+       if (err)
+               return err;
+
+       module_mapping = MLX5_GET(pmlp_reg, out, lane0_module_mapping);
+       *module_num = module_mapping & MLX5_EEPROM_IDENTIFIER_BYTE_MASK;
+
+       return 0;
+}
+
+int mlx5_query_module_eeprom(struct mlx5_core_dev *dev,
+                            u16 offset, u16 size, u8 *data)
+{
+       u32 out[MLX5_ST_SZ_DW(mcia_reg)];
+       u32 in[MLX5_ST_SZ_DW(mcia_reg)];
+       int module_num;
+       u16 i2c_addr;
+       int status;
+       int err;
+       void *ptr = MLX5_ADDR_OF(mcia_reg, out, dword_0);
+
+       err = mlx5_query_module_num(dev, &module_num);
+       if (err)
+               return err;
+
+       memset(in, 0, sizeof(in));
+       size = min_t(int, size, MLX5_EEPROM_MAX_BYTES);
+
+       if (offset < MLX5_EEPROM_PAGE_LENGTH &&
+           offset + size > MLX5_EEPROM_PAGE_LENGTH)
+               /* Cross pages read, read until offset 256 in low page */
+               size -= offset + size - MLX5_EEPROM_PAGE_LENGTH;
+
+       i2c_addr = MLX5_I2C_ADDR_LOW;
+       if (offset >= MLX5_EEPROM_PAGE_LENGTH) {
+               i2c_addr = MLX5_I2C_ADDR_HIGH;
+               offset -= MLX5_EEPROM_PAGE_LENGTH;
+       }
+
+       MLX5_SET(mcia_reg, in, l, 0);
+       MLX5_SET(mcia_reg, in, module, module_num);
+       MLX5_SET(mcia_reg, in, i2c_device_address, i2c_addr);
+       MLX5_SET(mcia_reg, in, page_number, 0);
+       MLX5_SET(mcia_reg, in, device_address, offset);
+       MLX5_SET(mcia_reg, in, size, size);
+
+       err = mlx5_core_access_reg(dev, in, sizeof(in), out,
+                                  sizeof(out), MLX5_REG_MCIA, 0, 0);
+       if (err)
+               return err;
+
+       status = MLX5_GET(mcia_reg, out, status);
+       if (status) {
+               mlx5_core_err(dev, "query_mcia_reg failed: status: 0x%x\n",
+                             status);
+               return -EIO;
+       }
+
+       memcpy(data, ptr, size);
+
+       return size;
+}
+EXPORT_SYMBOL_GPL(mlx5_query_module_eeprom);
+
 static int mlx5_query_port_pvlc(struct mlx5_core_dev *dev, u32 *pvlc,
                                int pvlc_size,  u8 local_port)
 {
index 2e8758d1b19ed1006ea3b9e347fe05591a458766..1a170672c656fa4ca07e15bc4097a479243788bb 100644 (file)
@@ -113,9 +113,10 @@ enum {
        MLX5_REG_PELC            = 0x500e,
        MLX5_REG_PVLC            = 0x500f,
        MLX5_REG_PCMR            = 0x5041,
-       MLX5_REG_PMLP            = 0, /* TBD */
+       MLX5_REG_PMLP            = 0x5002,
        MLX5_REG_NODE_DESC       = 0x6001,
        MLX5_REG_HOST_ENDIANNESS = 0x7004,
+       MLX5_REG_MCIA            = 0x9014,
        MLX5_REG_MLCR            = 0x902b,
 };
 
index a364ab1737a05ce671b59f7c6368a89d7737e2cf..7391eb833253e933884eb17fb7530a8eb76b00ed 100644 (file)
@@ -40,6 +40,19 @@ enum mlx5_beacon_duration {
        MLX5_BEACON_DURATION_INF = 0xffff,
 };
 
+enum mlx5_module_id {
+       MLX5_MODULE_ID_SFP              = 0x3,
+       MLX5_MODULE_ID_QSFP             = 0xC,
+       MLX5_MODULE_ID_QSFP_PLUS        = 0xD,
+       MLX5_MODULE_ID_QSFP28           = 0x11,
+};
+
+#define MLX5_EEPROM_MAX_BYTES                  32
+#define MLX5_EEPROM_IDENTIFIER_BYTE_MASK       0x000000ff
+#define MLX5_I2C_ADDR_LOW              0x50
+#define MLX5_I2C_ADDR_HIGH             0x51
+#define MLX5_EEPROM_PAGE_LENGTH                256
+
 int mlx5_set_port_caps(struct mlx5_core_dev *dev, u8 port_num, u32 caps);
 int mlx5_query_port_ptys(struct mlx5_core_dev *dev, u32 *ptys,
                         int ptys_size, int proto_mask, u8 local_port);
@@ -93,5 +106,7 @@ int mlx5_query_port_wol(struct mlx5_core_dev *mdev, u8 *wol_mode);
 int mlx5_set_port_fcs(struct mlx5_core_dev *mdev, u8 enable);
 void mlx5_query_port_fcs(struct mlx5_core_dev *mdev, bool *supported,
                         bool *enabled);
+int mlx5_query_module_eeprom(struct mlx5_core_dev *dev,
+                            u16 offset, u16 size, u8 *data);
 
 #endif /* __MLX5_PORT_H__ */