net/mlx4_en: Use PTYS register to set ethtool settings (Speed)
[cascardo/linux.git] / drivers / net / ethernet / mellanox / mlx4 / en_ethtool.c
index ae83da9..144d6bd 100644 (file)
@@ -35,6 +35,7 @@
 #include <linux/ethtool.h>
 #include <linux/netdevice.h>
 #include <linux/mlx4/driver.h>
+#include <linux/mlx4/device.h>
 #include <linux/in.h>
 #include <net/ip.h>
 
@@ -374,7 +375,302 @@ static void mlx4_en_get_strings(struct net_device *dev,
        }
 }
 
-static int mlx4_en_get_settings(struct net_device *dev, struct ethtool_cmd *cmd)
+static u32 mlx4_en_autoneg_get(struct net_device *dev)
+{
+       struct mlx4_en_priv *priv = netdev_priv(dev);
+       struct mlx4_en_dev *mdev = priv->mdev;
+       u32 autoneg = AUTONEG_DISABLE;
+
+       if ((mdev->dev->caps.flags2 & MLX4_DEV_CAP_FLAG2_ETH_BACKPL_AN_REP) &&
+           (priv->port_state.flags & MLX4_EN_PORT_ANE))
+               autoneg = AUTONEG_ENABLE;
+
+       return autoneg;
+}
+
+static u32 ptys_get_supported_port(struct mlx4_ptys_reg *ptys_reg)
+{
+       u32 eth_proto = be32_to_cpu(ptys_reg->eth_proto_cap);
+
+       if (eth_proto & (MLX4_PROT_MASK(MLX4_10GBASE_T)
+                        | MLX4_PROT_MASK(MLX4_1000BASE_T)
+                        | MLX4_PROT_MASK(MLX4_100BASE_TX))) {
+                       return SUPPORTED_TP;
+       }
+
+       if (eth_proto & (MLX4_PROT_MASK(MLX4_10GBASE_CR)
+                        | MLX4_PROT_MASK(MLX4_10GBASE_SR)
+                        | MLX4_PROT_MASK(MLX4_56GBASE_SR4)
+                        | MLX4_PROT_MASK(MLX4_40GBASE_CR4)
+                        | MLX4_PROT_MASK(MLX4_40GBASE_SR4)
+                        | MLX4_PROT_MASK(MLX4_1000BASE_CX_SGMII))) {
+                       return SUPPORTED_FIBRE;
+       }
+
+       if (eth_proto & (MLX4_PROT_MASK(MLX4_56GBASE_KR4)
+                        | MLX4_PROT_MASK(MLX4_40GBASE_KR4)
+                        | MLX4_PROT_MASK(MLX4_20GBASE_KR2)
+                        | MLX4_PROT_MASK(MLX4_10GBASE_KR)
+                        | MLX4_PROT_MASK(MLX4_10GBASE_KX4)
+                        | MLX4_PROT_MASK(MLX4_1000BASE_KX))) {
+                       return SUPPORTED_Backplane;
+       }
+       return 0;
+}
+
+static u32 ptys_get_active_port(struct mlx4_ptys_reg *ptys_reg)
+{
+       u32 eth_proto = be32_to_cpu(ptys_reg->eth_proto_oper);
+
+       if (!eth_proto) /* link down */
+               eth_proto = be32_to_cpu(ptys_reg->eth_proto_cap);
+
+       if (eth_proto & (MLX4_PROT_MASK(MLX4_10GBASE_T)
+                        | MLX4_PROT_MASK(MLX4_1000BASE_T)
+                        | MLX4_PROT_MASK(MLX4_100BASE_TX))) {
+                       return PORT_TP;
+       }
+
+       if (eth_proto & (MLX4_PROT_MASK(MLX4_10GBASE_SR)
+                        | MLX4_PROT_MASK(MLX4_56GBASE_SR4)
+                        | MLX4_PROT_MASK(MLX4_40GBASE_SR4)
+                        | MLX4_PROT_MASK(MLX4_1000BASE_CX_SGMII))) {
+                       return PORT_FIBRE;
+       }
+
+       if (eth_proto & (MLX4_PROT_MASK(MLX4_10GBASE_CR)
+                        | MLX4_PROT_MASK(MLX4_56GBASE_CR4)
+                        | MLX4_PROT_MASK(MLX4_40GBASE_CR4))) {
+                       return PORT_DA;
+       }
+
+       if (eth_proto & (MLX4_PROT_MASK(MLX4_56GBASE_KR4)
+                        | MLX4_PROT_MASK(MLX4_40GBASE_KR4)
+                        | MLX4_PROT_MASK(MLX4_20GBASE_KR2)
+                        | MLX4_PROT_MASK(MLX4_10GBASE_KR)
+                        | MLX4_PROT_MASK(MLX4_10GBASE_KX4)
+                        | MLX4_PROT_MASK(MLX4_1000BASE_KX))) {
+                       return PORT_NONE;
+       }
+       return PORT_OTHER;
+}
+
+#define MLX4_LINK_MODES_SZ \
+       (FIELD_SIZEOF(struct mlx4_ptys_reg, eth_proto_cap) * 8)
+
+enum ethtool_report {
+       SUPPORTED = 0,
+       ADVERTISED = 1,
+       SPEED = 2
+};
+
+/* Translates mlx4 link mode to equivalent ethtool Link modes/speed */
+static u32 ptys2ethtool_map[MLX4_LINK_MODES_SZ][3] = {
+       [MLX4_100BASE_TX] = {
+               SUPPORTED_100baseT_Full,
+               ADVERTISED_100baseT_Full,
+               SPEED_100
+               },
+
+       [MLX4_1000BASE_T] = {
+               SUPPORTED_1000baseT_Full,
+               ADVERTISED_1000baseT_Full,
+               SPEED_1000
+               },
+       [MLX4_1000BASE_CX_SGMII] = {
+               SUPPORTED_1000baseKX_Full,
+               ADVERTISED_1000baseKX_Full,
+               SPEED_1000
+               },
+       [MLX4_1000BASE_KX] = {
+               SUPPORTED_1000baseKX_Full,
+               ADVERTISED_1000baseKX_Full,
+               SPEED_1000
+               },
+
+       [MLX4_10GBASE_T] = {
+               SUPPORTED_10000baseT_Full,
+               ADVERTISED_10000baseT_Full,
+               SPEED_10000
+               },
+       [MLX4_10GBASE_CX4] = {
+               SUPPORTED_10000baseKX4_Full,
+               ADVERTISED_10000baseKX4_Full,
+               SPEED_10000
+               },
+       [MLX4_10GBASE_KX4] = {
+               SUPPORTED_10000baseKX4_Full,
+               ADVERTISED_10000baseKX4_Full,
+               SPEED_10000
+               },
+       [MLX4_10GBASE_KR] = {
+               SUPPORTED_10000baseKR_Full,
+               ADVERTISED_10000baseKR_Full,
+               SPEED_10000
+               },
+       [MLX4_10GBASE_CR] = {
+               SUPPORTED_10000baseKR_Full,
+               ADVERTISED_10000baseKR_Full,
+               SPEED_10000
+               },
+       [MLX4_10GBASE_SR] = {
+               SUPPORTED_10000baseKR_Full,
+               ADVERTISED_10000baseKR_Full,
+               SPEED_10000
+               },
+
+       [MLX4_20GBASE_KR2] = {
+               SUPPORTED_20000baseMLD2_Full | SUPPORTED_20000baseKR2_Full,
+               ADVERTISED_20000baseMLD2_Full | ADVERTISED_20000baseKR2_Full,
+               SPEED_20000
+               },
+
+       [MLX4_40GBASE_CR4] = {
+               SUPPORTED_40000baseCR4_Full,
+               ADVERTISED_40000baseCR4_Full,
+               SPEED_40000
+               },
+       [MLX4_40GBASE_KR4] = {
+               SUPPORTED_40000baseKR4_Full,
+               ADVERTISED_40000baseKR4_Full,
+               SPEED_40000
+               },
+       [MLX4_40GBASE_SR4] = {
+               SUPPORTED_40000baseSR4_Full,
+               ADVERTISED_40000baseSR4_Full,
+               SPEED_40000
+               },
+
+       [MLX4_56GBASE_KR4] = {
+               SUPPORTED_56000baseKR4_Full,
+               ADVERTISED_56000baseKR4_Full,
+               SPEED_56000
+               },
+       [MLX4_56GBASE_CR4] = {
+               SUPPORTED_56000baseCR4_Full,
+               ADVERTISED_56000baseCR4_Full,
+               SPEED_56000
+               },
+       [MLX4_56GBASE_SR4] = {
+               SUPPORTED_56000baseSR4_Full,
+               ADVERTISED_56000baseSR4_Full,
+               SPEED_56000
+               },
+};
+
+static u32 ptys2ethtool_link_modes(u32 eth_proto, enum ethtool_report report)
+{
+       int i;
+       u32 link_modes = 0;
+
+       for (i = 0; i < MLX4_LINK_MODES_SZ; i++) {
+               if (eth_proto & MLX4_PROT_MASK(i))
+                       link_modes |= ptys2ethtool_map[i][report];
+       }
+       return link_modes;
+}
+
+static u32 ethtool2ptys_link_modes(u32 link_modes, enum ethtool_report report)
+{
+       int i;
+       u32 ptys_modes = 0;
+
+       for (i = 0; i < MLX4_LINK_MODES_SZ; i++) {
+               if (ptys2ethtool_map[i][report] & link_modes)
+                       ptys_modes |= 1 << i;
+       }
+       return ptys_modes;
+}
+
+/* Convert actual speed (SPEED_XXX) to ptys link modes */
+static u32 speed2ptys_link_modes(u32 speed)
+{
+       int i;
+       u32 ptys_modes = 0;
+
+       for (i = 0; i < MLX4_LINK_MODES_SZ; i++) {
+               if (ptys2ethtool_map[i][SPEED] == speed)
+                       ptys_modes |= 1 << i;
+       }
+       return ptys_modes;
+}
+
+static int ethtool_get_ptys_settings(struct net_device *dev,
+                                    struct ethtool_cmd *cmd)
+{
+       struct mlx4_en_priv *priv = netdev_priv(dev);
+       struct mlx4_ptys_reg ptys_reg;
+       u32 eth_proto;
+       int ret;
+
+       memset(&ptys_reg, 0, sizeof(ptys_reg));
+       ptys_reg.local_port = priv->port;
+       ptys_reg.proto_mask = MLX4_PTYS_EN;
+       ret = mlx4_ACCESS_PTYS_REG(priv->mdev->dev,
+                                  MLX4_ACCESS_REG_QUERY, &ptys_reg);
+       if (ret) {
+               en_warn(priv, "Failed to run mlx4_ACCESS_PTYS_REG status(%x)",
+                       ret);
+               return ret;
+       }
+       en_dbg(DRV, priv, "ptys_reg.proto_mask       %x\n",
+              ptys_reg.proto_mask);
+       en_dbg(DRV, priv, "ptys_reg.eth_proto_cap    %x\n",
+              be32_to_cpu(ptys_reg.eth_proto_cap));
+       en_dbg(DRV, priv, "ptys_reg.eth_proto_admin  %x\n",
+              be32_to_cpu(ptys_reg.eth_proto_admin));
+       en_dbg(DRV, priv, "ptys_reg.eth_proto_oper   %x\n",
+              be32_to_cpu(ptys_reg.eth_proto_oper));
+       en_dbg(DRV, priv, "ptys_reg.eth_proto_lp_adv %x\n",
+              be32_to_cpu(ptys_reg.eth_proto_lp_adv));
+
+       cmd->supported = 0;
+       cmd->advertising = 0;
+
+       cmd->supported |= ptys_get_supported_port(&ptys_reg);
+
+       eth_proto = be32_to_cpu(ptys_reg.eth_proto_cap);
+       cmd->supported |= ptys2ethtool_link_modes(eth_proto, SUPPORTED);
+
+       eth_proto = be32_to_cpu(ptys_reg.eth_proto_admin);
+       cmd->advertising |= ptys2ethtool_link_modes(eth_proto, ADVERTISED);
+
+       cmd->supported |= SUPPORTED_Pause | SUPPORTED_Asym_Pause;
+       cmd->advertising |= (priv->prof->tx_pause) ? ADVERTISED_Pause : 0;
+
+       cmd->advertising |= (priv->prof->tx_pause ^ priv->prof->rx_pause) ?
+               ADVERTISED_Asym_Pause : 0;
+
+       cmd->port = ptys_get_active_port(&ptys_reg);
+       cmd->transceiver = (SUPPORTED_TP & cmd->supported) ?
+               XCVR_EXTERNAL : XCVR_INTERNAL;
+
+       if (mlx4_en_autoneg_get(dev)) {
+               cmd->supported |= SUPPORTED_Autoneg;
+               cmd->advertising |= ADVERTISED_Autoneg;
+       }
+
+       cmd->autoneg = (priv->port_state.flags & MLX4_EN_PORT_ANC) ?
+               AUTONEG_ENABLE : AUTONEG_DISABLE;
+
+       eth_proto = be32_to_cpu(ptys_reg.eth_proto_lp_adv);
+       cmd->lp_advertising = ptys2ethtool_link_modes(eth_proto, ADVERTISED);
+
+       cmd->lp_advertising |= (priv->port_state.flags & MLX4_EN_PORT_ANC) ?
+                       ADVERTISED_Autoneg : 0;
+
+       cmd->phy_address = 0;
+       cmd->mdio_support = 0;
+       cmd->maxtxpkt = 0;
+       cmd->maxrxpkt = 0;
+       cmd->eth_tp_mdix = ETH_TP_MDI_INVALID;
+       cmd->eth_tp_mdix_ctrl = ETH_TP_MDI_AUTO;
+
+       return ret;
+}
+
+static void ethtool_get_default_settings(struct net_device *dev,
+                                        struct ethtool_cmd *cmd)
 {
        struct mlx4_en_priv *priv = netdev_priv(dev);
        int trans_type;
@@ -382,18 +678,7 @@ static int mlx4_en_get_settings(struct net_device *dev, struct ethtool_cmd *cmd)
        cmd->autoneg = AUTONEG_DISABLE;
        cmd->supported = SUPPORTED_10000baseT_Full;
        cmd->advertising = ADVERTISED_10000baseT_Full;
-
-       if (mlx4_en_QUERY_PORT(priv->mdev, priv->port))
-               return -ENOMEM;
-
-       trans_type = priv->port_state.transciver;
-       if (netif_carrier_ok(dev)) {
-               ethtool_cmd_speed_set(cmd, priv->port_state.link_speed);
-               cmd->duplex = DUPLEX_FULL;
-       } else {
-               ethtool_cmd_speed_set(cmd, SPEED_UNKNOWN);
-               cmd->duplex = DUPLEX_UNKNOWN;
-       }
+       trans_type = priv->port_state.transceiver;
 
        if (trans_type > 0 && trans_type <= 0xC) {
                cmd->port = PORT_FIBRE;
@@ -409,17 +694,118 @@ static int mlx4_en_get_settings(struct net_device *dev, struct ethtool_cmd *cmd)
                cmd->port = -1;
                cmd->transceiver = -1;
        }
+}
+
+static int mlx4_en_get_settings(struct net_device *dev, struct ethtool_cmd *cmd)
+{
+       struct mlx4_en_priv *priv = netdev_priv(dev);
+       int ret = -EINVAL;
+
+       if (mlx4_en_QUERY_PORT(priv->mdev, priv->port))
+               return -ENOMEM;
+
+       en_dbg(DRV, priv, "query port state.flags ANC(%x) ANE(%x)\n",
+              priv->port_state.flags & MLX4_EN_PORT_ANC,
+              priv->port_state.flags & MLX4_EN_PORT_ANE);
+
+       if (priv->mdev->dev->caps.flags2 & MLX4_DEV_CAP_FLAG2_ETH_PROT_CTRL)
+               ret = ethtool_get_ptys_settings(dev, cmd);
+       if (ret) /* ETH PROT CRTL is not supported or PTYS CMD failed */
+               ethtool_get_default_settings(dev, cmd);
+
+       if (netif_carrier_ok(dev)) {
+               ethtool_cmd_speed_set(cmd, priv->port_state.link_speed);
+               cmd->duplex = DUPLEX_FULL;
+       } else {
+               ethtool_cmd_speed_set(cmd, SPEED_UNKNOWN);
+               cmd->duplex = DUPLEX_UNKNOWN;
+       }
        return 0;
 }
 
+/* Calculate PTYS admin according ethtool speed (SPEED_XXX) */
+static __be32 speed_set_ptys_admin(struct mlx4_en_priv *priv, u32 speed,
+                                  __be32 proto_cap)
+{
+       __be32 proto_admin = 0;
+
+       if (!speed) { /* Speed = 0 ==> Reset Link modes */
+               proto_admin = proto_cap;
+               en_info(priv, "Speed was set to 0, Reset advertised Link Modes to default (%x)\n",
+                       be32_to_cpu(proto_cap));
+       } else {
+               u32 ptys_link_modes = speed2ptys_link_modes(speed);
+
+               proto_admin = cpu_to_be32(ptys_link_modes) & proto_cap;
+               en_info(priv, "Setting Speed to %d\n", speed);
+       }
+       return proto_admin;
+}
+
 static int mlx4_en_set_settings(struct net_device *dev, struct ethtool_cmd *cmd)
 {
-       if ((cmd->autoneg == AUTONEG_ENABLE) ||
-           (ethtool_cmd_speed(cmd) != SPEED_10000) ||
-           (cmd->duplex != DUPLEX_FULL))
+       struct mlx4_en_priv *priv = netdev_priv(dev);
+       struct mlx4_ptys_reg ptys_reg;
+       __be32 proto_admin;
+       int ret;
+
+       u32 ptys_adv = ethtool2ptys_link_modes(cmd->advertising, ADVERTISED);
+       int speed = ethtool_cmd_speed(cmd);
+
+       en_dbg(DRV, priv, "Set Speed=%d adv=0x%x autoneg=%d duplex=%d\n",
+              speed, cmd->advertising, cmd->autoneg, cmd->duplex);
+
+       if (!(priv->mdev->dev->caps.flags2 & MLX4_DEV_CAP_FLAG2_ETH_PROT_CTRL) ||
+           (cmd->autoneg == AUTONEG_ENABLE) || (cmd->duplex == DUPLEX_HALF))
                return -EINVAL;
 
-       /* Nothing to change */
+       memset(&ptys_reg, 0, sizeof(ptys_reg));
+       ptys_reg.local_port = priv->port;
+       ptys_reg.proto_mask = MLX4_PTYS_EN;
+       ret = mlx4_ACCESS_PTYS_REG(priv->mdev->dev,
+                                  MLX4_ACCESS_REG_QUERY, &ptys_reg);
+       if (ret) {
+               en_warn(priv, "Failed to QUERY mlx4_ACCESS_PTYS_REG status(%x)\n",
+                       ret);
+               return 0;
+       }
+
+       proto_admin = cpu_to_be32(ptys_adv);
+       if (speed >= 0 && speed != priv->port_state.link_speed)
+               /* If speed was set then speed decides :-) */
+               proto_admin = speed_set_ptys_admin(priv, speed,
+                                                  ptys_reg.eth_proto_cap);
+
+       proto_admin &= ptys_reg.eth_proto_cap;
+
+       if (proto_admin == ptys_reg.eth_proto_admin)
+               return 0; /* Nothing to change */
+
+       if (!proto_admin) {
+               en_warn(priv, "Not supported link mode(s) requested, check supported link modes.\n");
+               return -EINVAL; /* nothing to change due to bad input */
+       }
+
+       en_dbg(DRV, priv, "mlx4_ACCESS_PTYS_REG SET: ptys_reg.eth_proto_admin = 0x%x\n",
+              be32_to_cpu(proto_admin));
+
+       ptys_reg.eth_proto_admin = proto_admin;
+       ret = mlx4_ACCESS_PTYS_REG(priv->mdev->dev, MLX4_ACCESS_REG_WRITE,
+                                  &ptys_reg);
+       if (ret) {
+               en_warn(priv, "Failed to write mlx4_ACCESS_PTYS_REG eth_proto_admin(0x%x) status(0x%x)",
+                       be32_to_cpu(ptys_reg.eth_proto_admin), ret);
+               return ret;
+       }
+
+       en_warn(priv, "Port link mode changed, restarting port...\n");
+       mutex_lock(&priv->mdev->state_lock);
+       if (priv->port_up) {
+               mlx4_en_stop_port(dev, 1);
+               if (mlx4_en_start_port(dev))
+                       en_err(priv, "Failed restarting port %d\n", priv->port);
+       }
+       mutex_unlock(&priv->mdev->state_lock);
        return 0;
 }
 
@@ -1309,6 +1695,86 @@ static int mlx4_en_set_tunable(struct net_device *dev,
        return ret;
 }
 
+static int mlx4_en_get_module_info(struct net_device *dev,
+                                  struct ethtool_modinfo *modinfo)
+{
+       struct mlx4_en_priv *priv = netdev_priv(dev);
+       struct mlx4_en_dev *mdev = priv->mdev;
+       int ret;
+       u8 data[4];
+
+       /* Read first 2 bytes to get Module & REV ID */
+       ret = mlx4_get_module_info(mdev->dev, priv->port,
+                                  0/*offset*/, 2/*size*/, data);
+       if (ret < 2)
+               return -EIO;
+
+       switch (data[0] /* identifier */) {
+       case MLX4_MODULE_ID_QSFP:
+               modinfo->type = ETH_MODULE_SFF_8436;
+               modinfo->eeprom_len = ETH_MODULE_SFF_8436_LEN;
+               break;
+       case MLX4_MODULE_ID_QSFP_PLUS:
+               if (data[1] >= 0x3) { /* revision id */
+                       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 MLX4_MODULE_ID_QSFP28:
+               modinfo->type = ETH_MODULE_SFF_8636;
+               modinfo->eeprom_len = ETH_MODULE_SFF_8636_LEN;
+               break;
+       case MLX4_MODULE_ID_SFP:
+               modinfo->type = ETH_MODULE_SFF_8472;
+               modinfo->eeprom_len = ETH_MODULE_SFF_8472_LEN;
+               break;
+       default:
+               return -ENOSYS;
+       }
+
+       return 0;
+}
+
+static int mlx4_en_get_module_eeprom(struct net_device *dev,
+                                    struct ethtool_eeprom *ee,
+                                    u8 *data)
+{
+       struct mlx4_en_priv *priv = netdev_priv(dev);
+       struct mlx4_en_dev *mdev = priv->mdev;
+       int offset = ee->offset;
+       int i = 0, ret;
+
+       if (ee->len == 0)
+               return -EINVAL;
+
+       memset(data, 0, ee->len);
+
+       while (i < ee->len) {
+               en_dbg(DRV, priv,
+                      "mlx4_get_module_info i(%d) offset(%d) len(%d)\n",
+                      i, offset, ee->len - i);
+
+               ret = mlx4_get_module_info(mdev->dev, priv->port,
+                                          offset, ee->len - i, data + i);
+
+               if (!ret) /* Done reading */
+                       return 0;
+
+               if (ret < 0) {
+                       en_err(priv,
+                              "mlx4_get_module_info i(%d) offset(%d) bytes_to_read(%d) - FAILED (0x%x)\n",
+                              i, offset, ee->len - i, ret);
+                       return 0;
+               }
+
+               i += ret;
+               offset += ret;
+       }
+       return 0;
+}
 
 const struct ethtool_ops mlx4_en_ethtool_ops = {
        .get_drvinfo = mlx4_en_get_drvinfo,
@@ -1341,6 +1807,8 @@ const struct ethtool_ops mlx4_en_ethtool_ops = {
        .get_priv_flags = mlx4_en_get_priv_flags,
        .get_tunable            = mlx4_en_get_tunable,
        .set_tunable            = mlx4_en_set_tunable,
+       .get_module_info = mlx4_en_get_module_info,
+       .get_module_eeprom = mlx4_en_get_module_eeprom
 };