]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
bng_en: add ethtool link settings, get_link, and nway_reset
authorBhargava Marreddy <bhargava.marreddy@broadcom.com>
Mon, 6 Apr 2026 18:04:13 +0000 (23:34 +0530)
committerJakub Kicinski <kuba@kernel.org>
Sun, 12 Apr 2026 18:09:36 +0000 (11:09 -0700)
Add get/set_link_ksettings, get_link, and nway_reset support.
Report supported, advertised, and link-partner speeds across NRZ,
PAM4, and PAM4-112 signaling modes. Enable lane count reporting.

Signed-off-by: Bhargava Marreddy <bhargava.marreddy@broadcom.com>
Reviewed-by: Vikas Gupta <vikas.gupta@broadcom.com>
Reviewed-by: Rajashekar Hudumula <rajashekar.hudumula@broadcom.com>
Reviewed-by: Ajit Kumar Khaparde <ajit.khaparde@broadcom.com>
Signed-off-by: Vikas Gupta <vikas.gupta@broadcom.com>
Link: https://patch.msgid.link/20260406180420.279470-4-bhargava.marreddy@broadcom.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
drivers/net/ethernet/broadcom/bnge/bnge.h
drivers/net/ethernet/broadcom/bnge/bnge_core.c
drivers/net/ethernet/broadcom/bnge/bnge_ethtool.c
drivers/net/ethernet/broadcom/bnge/bnge_link.c
drivers/net/ethernet/broadcom/bnge/bnge_link.h

index eddc5a4a514856ae460bc6f1889ff20d4f19331e..f21cff651fd44aa78b13a5738a0c4e1a397e7584 100644 (file)
@@ -96,6 +96,8 @@ struct bnge_queue_info {
 
 #define BNGE_PHY_FLAGS2_SHIFT          8
 #define BNGE_PHY_FL_NO_FCS             PORT_PHY_QCAPS_RESP_FLAGS_NO_FCS
+#define BNGE_PHY_FL_NO_PAUSE           \
+       (PORT_PHY_QCAPS_RESP_FLAGS2_PAUSE_UNSUPPORTED << 8)
 
 struct bnge_dev {
        struct device   *dev;
index b4090283df0f290f4409835dd7749200b2f0608a..1c14c5fe8d613231b38e9767c819fa363c365d81 100644 (file)
@@ -10,6 +10,7 @@
 #include "bnge_devlink.h"
 #include "bnge_hwrm.h"
 #include "bnge_hwrm_lib.h"
+#include "bnge_link.h"
 
 MODULE_LICENSE("GPL");
 MODULE_DESCRIPTION(DRV_SUMMARY);
index 569371c1b4f26f5d4ebe984669529bb3d9b2bce9..6bb74a84ea03c676a450bf48f7fc1f7a361f2c52 100644 (file)
 
 #include "bnge.h"
 #include "bnge_ethtool.h"
+#include "bnge_hwrm_lib.h"
+
+static int bnge_nway_reset(struct net_device *dev)
+{
+       struct bnge_net *bn = netdev_priv(dev);
+       struct bnge_dev *bd = bn->bd;
+       bool set_pause = false;
+       int rc = 0;
+
+       if (!BNGE_PHY_CFG_ABLE(bd))
+               return -EOPNOTSUPP;
+
+       if (!(bn->eth_link_info.autoneg & BNGE_AUTONEG_SPEED))
+               return -EINVAL;
+
+       if (!(bd->phy_flags & BNGE_PHY_FL_NO_PAUSE))
+               set_pause = true;
+
+       if (netif_running(dev))
+               rc = bnge_hwrm_set_link_setting(bn, set_pause);
+
+       return rc;
+}
 
 static void bnge_get_drvinfo(struct net_device *dev,
                             struct ethtool_drvinfo *info)
@@ -24,7 +47,12 @@ static void bnge_get_drvinfo(struct net_device *dev,
 }
 
 static const struct ethtool_ops bnge_ethtool_ops = {
+       .cap_link_lanes_supported       = 1,
+       .get_link_ksettings     = bnge_get_link_ksettings,
+       .set_link_ksettings     = bnge_set_link_ksettings,
        .get_drvinfo            = bnge_get_drvinfo,
+       .get_link               = bnge_get_link,
+       .nway_reset             = bnge_nway_reset,
 };
 
 void bnge_set_ethtool_ops(struct net_device *dev)
index 6eeb1c521535b0050beced26b0db6e82de6dea61..2e2aaa7644430b303756a0b7be66313a79a05a25 100644 (file)
@@ -7,6 +7,51 @@
 #include "bnge_link.h"
 #include "bnge_hwrm_lib.h"
 
+enum bnge_media_type {
+       BNGE_MEDIA_UNKNOWN = 0,
+       BNGE_MEDIA_CR,
+       BNGE_MEDIA_SR,
+       BNGE_MEDIA_LR_ER_FR,
+       BNGE_MEDIA_KR,
+       __BNGE_MEDIA_END,
+};
+
+static const enum bnge_media_type bnge_phy_types[] = {
+       [PORT_PHY_QCFG_RESP_PHY_TYPE_100G_BASECR4] = BNGE_MEDIA_CR,
+       [PORT_PHY_QCFG_RESP_PHY_TYPE_100G_BASESR4] = BNGE_MEDIA_SR,
+       [PORT_PHY_QCFG_RESP_PHY_TYPE_100G_BASELR4] = BNGE_MEDIA_LR_ER_FR,
+       [PORT_PHY_QCFG_RESP_PHY_TYPE_100G_BASEER4] = BNGE_MEDIA_LR_ER_FR,
+       [PORT_PHY_QCFG_RESP_PHY_TYPE_100G_BASESR10] = BNGE_MEDIA_SR,
+       [PORT_PHY_QCFG_RESP_PHY_TYPE_200G_BASECR4] = BNGE_MEDIA_CR,
+       [PORT_PHY_QCFG_RESP_PHY_TYPE_200G_BASESR4] = BNGE_MEDIA_SR,
+       [PORT_PHY_QCFG_RESP_PHY_TYPE_200G_BASELR4] = BNGE_MEDIA_LR_ER_FR,
+       [PORT_PHY_QCFG_RESP_PHY_TYPE_200G_BASEER4] = BNGE_MEDIA_LR_ER_FR,
+       [PORT_PHY_QCFG_RESP_PHY_TYPE_50G_BASECR] = BNGE_MEDIA_CR,
+       [PORT_PHY_QCFG_RESP_PHY_TYPE_50G_BASESR] = BNGE_MEDIA_SR,
+       [PORT_PHY_QCFG_RESP_PHY_TYPE_50G_BASELR] = BNGE_MEDIA_LR_ER_FR,
+       [PORT_PHY_QCFG_RESP_PHY_TYPE_50G_BASEER] = BNGE_MEDIA_LR_ER_FR,
+       [PORT_PHY_QCFG_RESP_PHY_TYPE_100G_BASECR2] = BNGE_MEDIA_CR,
+       [PORT_PHY_QCFG_RESP_PHY_TYPE_100G_BASESR2] = BNGE_MEDIA_SR,
+       [PORT_PHY_QCFG_RESP_PHY_TYPE_100G_BASELR2] = BNGE_MEDIA_LR_ER_FR,
+       [PORT_PHY_QCFG_RESP_PHY_TYPE_100G_BASEER2] = BNGE_MEDIA_LR_ER_FR,
+       [PORT_PHY_QCFG_RESP_PHY_TYPE_100G_BASECR] = BNGE_MEDIA_CR,
+       [PORT_PHY_QCFG_RESP_PHY_TYPE_100G_BASESR] = BNGE_MEDIA_SR,
+       [PORT_PHY_QCFG_RESP_PHY_TYPE_100G_BASELR] = BNGE_MEDIA_LR_ER_FR,
+       [PORT_PHY_QCFG_RESP_PHY_TYPE_100G_BASEER] = BNGE_MEDIA_LR_ER_FR,
+       [PORT_PHY_QCFG_RESP_PHY_TYPE_200G_BASECR2] = BNGE_MEDIA_CR,
+       [PORT_PHY_QCFG_RESP_PHY_TYPE_200G_BASESR2] = BNGE_MEDIA_SR,
+       [PORT_PHY_QCFG_RESP_PHY_TYPE_200G_BASELR2] = BNGE_MEDIA_LR_ER_FR,
+       [PORT_PHY_QCFG_RESP_PHY_TYPE_200G_BASEER2] = BNGE_MEDIA_LR_ER_FR,
+       [PORT_PHY_QCFG_RESP_PHY_TYPE_400G_BASECR8] = BNGE_MEDIA_CR,
+       [PORT_PHY_QCFG_RESP_PHY_TYPE_400G_BASESR8] = BNGE_MEDIA_SR,
+       [PORT_PHY_QCFG_RESP_PHY_TYPE_400G_BASELR8] = BNGE_MEDIA_LR_ER_FR,
+       [PORT_PHY_QCFG_RESP_PHY_TYPE_400G_BASEER8] = BNGE_MEDIA_LR_ER_FR,
+       [PORT_PHY_QCFG_RESP_PHY_TYPE_400G_BASECR4] = BNGE_MEDIA_CR,
+       [PORT_PHY_QCFG_RESP_PHY_TYPE_400G_BASESR4] = BNGE_MEDIA_SR,
+       [PORT_PHY_QCFG_RESP_PHY_TYPE_400G_BASELR4] = BNGE_MEDIA_LR_ER_FR,
+       [PORT_PHY_QCFG_RESP_PHY_TYPE_400G_BASEER4] = BNGE_MEDIA_LR_ER_FR,
+};
+
 static u32 bnge_fw_to_ethtool_speed(u16 fw_link_speed)
 {
        switch (fw_link_speed) {
@@ -302,6 +347,14 @@ void bnge_get_port_module_status(struct bnge_net *bn)
        }
 }
 
+static void bnge_set_default_adv_speeds(struct bnge_net *bn)
+{
+       struct bnge_ethtool_link_info *elink_info = &bn->eth_link_info;
+       struct bnge_link_info *link_info = &bn->bd->link_info;
+
+       elink_info->advertising = link_info->support_auto_speeds2;
+}
+
 static bool bnge_support_dropped(u16 advertising, u16 supported)
 {
        return (advertising & ~supported) != 0;
@@ -405,3 +458,657 @@ void bnge_report_link(struct bnge_dev *bd)
                netdev_info(bd->netdev, "NIC Link is Down\n");
        }
 }
+
+static void bnge_get_ethtool_modes(struct bnge_net *bn,
+                                  struct ethtool_link_ksettings *lk_ksettings)
+{
+       struct bnge_ethtool_link_info *elink_info;
+       struct bnge_link_info *link_info;
+       struct bnge_dev *bd = bn->bd;
+
+       elink_info = &bn->eth_link_info;
+       link_info = &bd->link_info;
+
+       if (!(bd->phy_flags & BNGE_PHY_FL_NO_PAUSE)) {
+               linkmode_set_bit(ETHTOOL_LINK_MODE_Pause_BIT,
+                                lk_ksettings->link_modes.supported);
+               linkmode_set_bit(ETHTOOL_LINK_MODE_Asym_Pause_BIT,
+                                lk_ksettings->link_modes.supported);
+       }
+
+       if (link_info->support_auto_speeds2)
+               linkmode_set_bit(ETHTOOL_LINK_MODE_Autoneg_BIT,
+                                lk_ksettings->link_modes.supported);
+
+       if (~elink_info->autoneg & BNGE_AUTONEG_FLOW_CTRL)
+               return;
+
+       if (link_info->auto_pause_setting & BNGE_LINK_PAUSE_RX)
+               linkmode_set_bit(ETHTOOL_LINK_MODE_Pause_BIT,
+                                lk_ksettings->link_modes.advertising);
+       if (hweight8(link_info->auto_pause_setting & BNGE_LINK_PAUSE_BOTH) == 1)
+               linkmode_set_bit(ETHTOOL_LINK_MODE_Asym_Pause_BIT,
+                                lk_ksettings->link_modes.advertising);
+       if (link_info->lp_pause & BNGE_LINK_PAUSE_RX)
+               linkmode_set_bit(ETHTOOL_LINK_MODE_Pause_BIT,
+                                lk_ksettings->link_modes.lp_advertising);
+       if (hweight8(link_info->lp_pause & BNGE_LINK_PAUSE_BOTH) == 1)
+               linkmode_set_bit(ETHTOOL_LINK_MODE_Asym_Pause_BIT,
+                                lk_ksettings->link_modes.lp_advertising);
+}
+
+u32 bnge_get_link(struct net_device *dev)
+{
+       struct bnge_net *bn = netdev_priv(dev);
+
+       return BNGE_LINK_IS_UP(bn->bd);
+}
+
+static enum bnge_media_type
+bnge_get_media(struct bnge_link_info *link_info)
+{
+       switch (link_info->media_type) {
+       case PORT_PHY_QCFG_RESP_MEDIA_TYPE_DAC:
+               return BNGE_MEDIA_CR;
+       default:
+               if (link_info->phy_type < ARRAY_SIZE(bnge_phy_types))
+                       return bnge_phy_types[link_info->phy_type];
+               return BNGE_MEDIA_UNKNOWN;
+       }
+}
+
+enum bnge_link_speed_indices {
+       BNGE_LINK_SPEED_UNKNOWN = 0,
+       BNGE_LINK_SPEED_50GB_IDX,
+       BNGE_LINK_SPEED_100GB_IDX,
+       BNGE_LINK_SPEED_200GB_IDX,
+       BNGE_LINK_SPEED_400GB_IDX,
+       BNGE_LINK_SPEED_800GB_IDX,
+       __BNGE_LINK_SPEED_END
+};
+
+static enum bnge_link_speed_indices bnge_fw_speed_idx(u16 speed)
+{
+       switch (speed) {
+       case BNGE_LINK_SPEED_50GB:
+       case BNGE_LINK_SPEED_50GB_PAM4:
+               return BNGE_LINK_SPEED_50GB_IDX;
+       case BNGE_LINK_SPEED_100GB:
+       case BNGE_LINK_SPEED_100GB_PAM4:
+       case BNGE_LINK_SPEED_100GB_PAM4_112:
+               return BNGE_LINK_SPEED_100GB_IDX;
+       case BNGE_LINK_SPEED_200GB:
+       case BNGE_LINK_SPEED_200GB_PAM4:
+       case BNGE_LINK_SPEED_200GB_PAM4_112:
+               return BNGE_LINK_SPEED_200GB_IDX;
+       case BNGE_LINK_SPEED_400GB:
+       case BNGE_LINK_SPEED_400GB_PAM4:
+       case BNGE_LINK_SPEED_400GB_PAM4_112:
+               return BNGE_LINK_SPEED_400GB_IDX;
+       case BNGE_LINK_SPEED_800GB:
+       case BNGE_LINK_SPEED_800GB_PAM4_112:
+               return BNGE_LINK_SPEED_800GB_IDX;
+       default:
+               return BNGE_LINK_SPEED_UNKNOWN;
+       }
+}
+
+/* Compile-time link mode mapping table.
+ * Indexed by [speed_idx][sig_mode][media].
+ */
+#define BNGE_LINK_M(speed, sig, media, lm)     \
+       [BNGE_LINK_SPEED_##speed##_IDX] \
+       [BNGE_SIG_MODE_##sig]           \
+       [BNGE_MEDIA_##media] = ETHTOOL_LINK_MODE_##lm##_Full_BIT
+
+static const enum ethtool_link_mode_bit_indices
+bnge_link_modes[__BNGE_LINK_SPEED_END]
+              [BNGE_SIG_MODE_MAX]
+              [__BNGE_MEDIA_END] = {
+       /* 50GB PAM4 */
+       BNGE_LINK_M(50GB,  PAM4, CR,        50000baseCR),
+       BNGE_LINK_M(50GB,  PAM4, SR,        50000baseSR),
+       BNGE_LINK_M(50GB,  PAM4, LR_ER_FR,  50000baseLR_ER_FR),
+       BNGE_LINK_M(50GB,  PAM4, KR,        50000baseKR),
+
+       /* 100GB NRZ */
+       BNGE_LINK_M(100GB, NRZ,  CR,        100000baseCR4),
+       BNGE_LINK_M(100GB, NRZ,  SR,        100000baseSR4),
+       BNGE_LINK_M(100GB, NRZ,  LR_ER_FR,  100000baseLR4_ER4),
+       BNGE_LINK_M(100GB, NRZ,  KR,        100000baseKR4),
+
+       /* 100GB PAM4 */
+       BNGE_LINK_M(100GB, PAM4, CR,        100000baseCR2),
+       BNGE_LINK_M(100GB, PAM4, SR,        100000baseSR2),
+       BNGE_LINK_M(100GB, PAM4, LR_ER_FR,  100000baseLR2_ER2_FR2),
+       BNGE_LINK_M(100GB, PAM4, KR,        100000baseKR2),
+
+       /* 100GB PAM4_112 */
+       BNGE_LINK_M(100GB, PAM4_112, CR,        100000baseCR),
+       BNGE_LINK_M(100GB, PAM4_112, SR,        100000baseSR),
+       BNGE_LINK_M(100GB, PAM4_112, LR_ER_FR,  100000baseLR_ER_FR),
+       BNGE_LINK_M(100GB, PAM4_112, KR,        100000baseKR),
+
+       /* 200GB PAM4 */
+       BNGE_LINK_M(200GB, PAM4, CR,        200000baseCR4),
+       BNGE_LINK_M(200GB, PAM4, SR,        200000baseSR4),
+       BNGE_LINK_M(200GB, PAM4, LR_ER_FR,  200000baseLR4_ER4_FR4),
+       BNGE_LINK_M(200GB, PAM4, KR,        200000baseKR4),
+
+       /* 200GB PAM4_112 */
+       BNGE_LINK_M(200GB, PAM4_112, CR,        200000baseCR2),
+       BNGE_LINK_M(200GB, PAM4_112, SR,        200000baseSR2),
+       BNGE_LINK_M(200GB, PAM4_112, LR_ER_FR,  200000baseLR2_ER2_FR2),
+       BNGE_LINK_M(200GB, PAM4_112, KR,        200000baseKR2),
+
+       /* 400GB PAM4 */
+       BNGE_LINK_M(400GB, PAM4, CR,        400000baseCR8),
+       BNGE_LINK_M(400GB, PAM4, SR,        400000baseSR8),
+       BNGE_LINK_M(400GB, PAM4, LR_ER_FR,  400000baseLR8_ER8_FR8),
+       BNGE_LINK_M(400GB, PAM4, KR,        400000baseKR8),
+
+       /* 400GB PAM4_112 */
+       BNGE_LINK_M(400GB, PAM4_112, CR,        400000baseCR4),
+       BNGE_LINK_M(400GB, PAM4_112, SR,        400000baseSR4),
+       BNGE_LINK_M(400GB, PAM4_112, LR_ER_FR,  400000baseLR4_ER4_FR4),
+       BNGE_LINK_M(400GB, PAM4_112, KR,        400000baseKR4),
+
+       /* 800GB PAM4_112 */
+       BNGE_LINK_M(800GB, PAM4_112, CR,        800000baseCR8),
+       BNGE_LINK_M(800GB, PAM4_112, SR,        800000baseSR8),
+       BNGE_LINK_M(800GB, PAM4_112, KR,        800000baseKR8),
+};
+
+#define BNGE_LINK_MODE_UNKNOWN -1
+
+static enum ethtool_link_mode_bit_indices
+bnge_get_link_mode(struct bnge_net *bn)
+{
+       enum ethtool_link_mode_bit_indices link_mode;
+       struct bnge_ethtool_link_info *elink_info;
+       enum bnge_link_speed_indices speed;
+       struct bnge_link_info *link_info;
+       struct bnge_dev *bd = bn->bd;
+       enum bnge_media_type media;
+       u8 sig_mode;
+
+       elink_info = &bn->eth_link_info;
+       link_info = &bd->link_info;
+
+       if (link_info->phy_link_status != BNGE_LINK_LINK)
+               return BNGE_LINK_MODE_UNKNOWN;
+
+       media = bnge_get_media(link_info);
+       if (BNGE_AUTO_MODE(link_info->auto_mode)) {
+               speed = bnge_fw_speed_idx(link_info->link_speed);
+               sig_mode = link_info->active_fec_sig_mode &
+                       PORT_PHY_QCFG_RESP_SIGNAL_MODE_MASK;
+       } else {
+               speed = bnge_fw_speed_idx(elink_info->req_link_speed);
+               sig_mode = elink_info->req_signal_mode;
+       }
+       if (sig_mode >= BNGE_SIG_MODE_MAX)
+               return BNGE_LINK_MODE_UNKNOWN;
+
+       /* Since ETHTOOL_LINK_MODE_10baseT_Half_BIT is defined as 0 and
+        * not actually supported, the zeroes in this map can be safely
+        * used to represent unknown link modes.
+        */
+       link_mode = bnge_link_modes[speed][sig_mode][media];
+       if (!link_mode)
+               return BNGE_LINK_MODE_UNKNOWN;
+
+       return link_mode;
+}
+
+static const u16 bnge_nrz_speeds2_masks[__BNGE_LINK_SPEED_END] = {
+       [BNGE_LINK_SPEED_100GB_IDX] = BNGE_LINK_SPEEDS2_MSK_100GB,
+};
+
+static const u16 bnge_pam4_speeds2_masks[__BNGE_LINK_SPEED_END] = {
+       [BNGE_LINK_SPEED_50GB_IDX] = BNGE_LINK_SPEEDS2_MSK_50GB_PAM4,
+       [BNGE_LINK_SPEED_100GB_IDX] = BNGE_LINK_SPEEDS2_MSK_100GB_PAM4,
+       [BNGE_LINK_SPEED_200GB_IDX] = BNGE_LINK_SPEEDS2_MSK_200GB_PAM4,
+       [BNGE_LINK_SPEED_400GB_IDX] = BNGE_LINK_SPEEDS2_MSK_400GB_PAM4,
+};
+
+static const u16 bnge_pam4_112_speeds2_masks[__BNGE_LINK_SPEED_END] = {
+       [BNGE_LINK_SPEED_100GB_IDX] = BNGE_LINK_SPEEDS2_MSK_100GB_PAM4_112,
+       [BNGE_LINK_SPEED_200GB_IDX] = BNGE_LINK_SPEEDS2_MSK_200GB_PAM4_112,
+       [BNGE_LINK_SPEED_400GB_IDX] = BNGE_LINK_SPEEDS2_MSK_400GB_PAM4_112,
+       [BNGE_LINK_SPEED_800GB_IDX] = BNGE_LINK_SPEEDS2_MSK_800GB_PAM4_112,
+};
+
+static enum bnge_link_speed_indices
+bnge_encoding_speed_idx(u8 sig_mode, u16 speed_msk)
+{
+       const u16 *speeds;
+       int idx, len;
+
+       switch (sig_mode) {
+       case BNGE_SIG_MODE_NRZ:
+               speeds = bnge_nrz_speeds2_masks;
+               len = ARRAY_SIZE(bnge_nrz_speeds2_masks);
+               break;
+       case BNGE_SIG_MODE_PAM4:
+               speeds = bnge_pam4_speeds2_masks;
+               len = ARRAY_SIZE(bnge_pam4_speeds2_masks);
+               break;
+       case BNGE_SIG_MODE_PAM4_112:
+               speeds = bnge_pam4_112_speeds2_masks;
+               len = ARRAY_SIZE(bnge_pam4_112_speeds2_masks);
+               break;
+       default:
+               return BNGE_LINK_SPEED_UNKNOWN;
+       }
+
+       for (idx = 0; idx < len; idx++) {
+               if (speeds[idx] == speed_msk)
+                       return idx;
+       }
+
+       return BNGE_LINK_SPEED_UNKNOWN;
+}
+
+#define BNGE_FW_SPEED_MSK_BITS 16
+
+static void
+__bnge_get_ethtool_speeds(unsigned long fw_mask, enum bnge_media_type media,
+                         u8 sig_mode, unsigned long *et_mask)
+{
+       enum ethtool_link_mode_bit_indices link_mode;
+       enum bnge_link_speed_indices speed;
+       u8 bit;
+
+       for_each_set_bit(bit, &fw_mask, BNGE_FW_SPEED_MSK_BITS) {
+               speed = bnge_encoding_speed_idx(sig_mode, 1 << bit);
+               if (!speed)
+                       continue;
+
+               link_mode = bnge_link_modes[speed][sig_mode][media];
+               if (!link_mode)
+                       continue;
+
+               linkmode_set_bit(link_mode, et_mask);
+       }
+}
+
+static void
+bnge_get_ethtool_speeds(unsigned long fw_mask, enum bnge_media_type media,
+                       u8 sig_mode, unsigned long *et_mask)
+{
+       if (media) {
+               __bnge_get_ethtool_speeds(fw_mask, media, sig_mode, et_mask);
+               return;
+       }
+
+       /* list speeds for all media if unknown */
+       for (media = 1; media < __BNGE_MEDIA_END; media++)
+               __bnge_get_ethtool_speeds(fw_mask, media, sig_mode, et_mask);
+}
+
+static void
+bnge_get_all_ethtool_support_speeds(struct bnge_dev *bd,
+                                   enum bnge_media_type media,
+                                   struct ethtool_link_ksettings *lk_ksettings)
+{
+       u16 sp = bd->link_info.support_speeds2;
+
+       bnge_get_ethtool_speeds(sp, media, BNGE_SIG_MODE_NRZ,
+                               lk_ksettings->link_modes.supported);
+       bnge_get_ethtool_speeds(sp, media, BNGE_SIG_MODE_PAM4,
+                               lk_ksettings->link_modes.supported);
+       bnge_get_ethtool_speeds(sp, media, BNGE_SIG_MODE_PAM4_112,
+                               lk_ksettings->link_modes.supported);
+}
+
+static void
+bnge_get_all_ethtool_adv_speeds(struct bnge_net *bn,
+                               enum bnge_media_type media,
+                               struct ethtool_link_ksettings *lk_ksettings)
+{
+       u16 sp = bn->eth_link_info.advertising;
+
+       bnge_get_ethtool_speeds(sp, media, BNGE_SIG_MODE_NRZ,
+                               lk_ksettings->link_modes.advertising);
+       bnge_get_ethtool_speeds(sp, media, BNGE_SIG_MODE_PAM4,
+                               lk_ksettings->link_modes.advertising);
+       bnge_get_ethtool_speeds(sp, media, BNGE_SIG_MODE_PAM4_112,
+                               lk_ksettings->link_modes.advertising);
+}
+
+static void
+bnge_get_all_ethtool_lp_speeds(struct bnge_dev *bd,
+                              enum bnge_media_type media,
+                              struct ethtool_link_ksettings *lk_ksettings)
+{
+       u16 sp = bd->link_info.lp_auto_link_speeds;
+
+       bnge_get_ethtool_speeds(sp, media, BNGE_SIG_MODE_NRZ,
+                               lk_ksettings->link_modes.lp_advertising);
+       bnge_get_ethtool_speeds(sp, media, BNGE_SIG_MODE_PAM4,
+                               lk_ksettings->link_modes.lp_advertising);
+       bnge_get_ethtool_speeds(sp, media, BNGE_SIG_MODE_PAM4_112,
+                               lk_ksettings->link_modes.lp_advertising);
+}
+
+static void bnge_update_speed(u32 *delta, bool installed_media, u16 *speeds,
+                             u16 speed_msk, const unsigned long *et_mask,
+                             enum ethtool_link_mode_bit_indices mode)
+{
+       bool mode_desired = linkmode_test_bit(mode, et_mask);
+
+       if (!mode || !mode_desired)
+               return;
+
+       /* installed media takes priority; for non-installed media, only allow
+        * one change per fw_speed bit (many to one mapping).
+        */
+       if (installed_media || !(*delta & speed_msk)) {
+               *speeds |= speed_msk;
+               *delta |= speed_msk;
+       }
+}
+
+static void bnge_set_ethtool_speeds(struct bnge_net *bn,
+                                   const unsigned long *et_mask)
+{
+       struct bnge_ethtool_link_info *elink_info = &bn->eth_link_info;
+       enum bnge_media_type media;
+       u32 delta_pam4_112 = 0;
+       u32 delta_pam4 = 0;
+       u32 delta_nrz = 0;
+       int i, m;
+
+       elink_info->advertising = 0;
+
+       media = bnge_get_media(&bn->bd->link_info);
+       for (i = 1; i < __BNGE_LINK_SPEED_END; i++) {
+               /* accept any legal media from user */
+               for (m = 1; m < __BNGE_MEDIA_END; m++) {
+                       bnge_update_speed(&delta_nrz, m == media,
+                                         &elink_info->advertising,
+                                         bnge_nrz_speeds2_masks[i], et_mask,
+                                         bnge_link_modes[i][BNGE_SIG_MODE_NRZ][m]);
+                       bnge_update_speed(&delta_pam4, m == media,
+                                         &elink_info->advertising,
+                                         bnge_pam4_speeds2_masks[i], et_mask,
+                                         bnge_link_modes[i][BNGE_SIG_MODE_PAM4][m]);
+                       bnge_update_speed(&delta_pam4_112, m == media,
+                                         &elink_info->advertising,
+                                         bnge_pam4_112_speeds2_masks[i],
+                                         et_mask,
+                                         bnge_link_modes[i][BNGE_SIG_MODE_PAM4_112][m]);
+               }
+       }
+}
+
+static void
+bnge_fw_to_ethtool_advertised_fec(struct bnge_link_info *link_info,
+                                 struct ethtool_link_ksettings *lk_ksettings)
+{
+       u16 fec_cfg = link_info->fec_cfg;
+
+       if ((fec_cfg & BNGE_FEC_NONE) || !(fec_cfg & BNGE_FEC_AUTONEG)) {
+               linkmode_set_bit(ETHTOOL_LINK_MODE_FEC_NONE_BIT,
+                                lk_ksettings->link_modes.advertising);
+               return;
+       }
+       if (fec_cfg & BNGE_FEC_ENC_BASE_R)
+               linkmode_set_bit(ETHTOOL_LINK_MODE_FEC_BASER_BIT,
+                                lk_ksettings->link_modes.advertising);
+       if (fec_cfg & BNGE_FEC_ENC_RS)
+               linkmode_set_bit(ETHTOOL_LINK_MODE_FEC_RS_BIT,
+                                lk_ksettings->link_modes.advertising);
+       if (fec_cfg & BNGE_FEC_ENC_LLRS)
+               linkmode_set_bit(ETHTOOL_LINK_MODE_FEC_LLRS_BIT,
+                                lk_ksettings->link_modes.advertising);
+}
+
+static void
+bnge_fw_to_ethtool_support_fec(struct bnge_link_info *link_info,
+                              struct ethtool_link_ksettings *lk_ksettings)
+{
+       u16 fec_cfg = link_info->fec_cfg;
+
+       if (fec_cfg & BNGE_FEC_NONE) {
+               linkmode_set_bit(ETHTOOL_LINK_MODE_FEC_NONE_BIT,
+                                lk_ksettings->link_modes.supported);
+               return;
+       }
+       if (fec_cfg & BNGE_FEC_ENC_BASE_R_CAP)
+               linkmode_set_bit(ETHTOOL_LINK_MODE_FEC_BASER_BIT,
+                                lk_ksettings->link_modes.supported);
+       if (fec_cfg & BNGE_FEC_ENC_RS_CAP)
+               linkmode_set_bit(ETHTOOL_LINK_MODE_FEC_RS_BIT,
+                                lk_ksettings->link_modes.supported);
+       if (fec_cfg & BNGE_FEC_ENC_LLRS_CAP)
+               linkmode_set_bit(ETHTOOL_LINK_MODE_FEC_LLRS_BIT,
+                                lk_ksettings->link_modes.supported);
+}
+
+static void bnge_get_default_speeds(struct bnge_net *bn,
+                                   struct ethtool_link_ksettings *lk_ksettings)
+{
+       struct bnge_ethtool_link_info *elink_info = &bn->eth_link_info;
+       struct ethtool_link_settings *base = &lk_ksettings->base;
+       struct bnge_link_info *link_info;
+       struct bnge_dev *bd = bn->bd;
+
+       link_info = &bd->link_info;
+
+       if (link_info->link_state == BNGE_LINK_STATE_UP) {
+               base->speed = bnge_fw_to_ethtool_speed(link_info->link_speed);
+               base->duplex = DUPLEX_HALF;
+               if (link_info->duplex & BNGE_LINK_DUPLEX_FULL)
+                       base->duplex = DUPLEX_FULL;
+               lk_ksettings->lanes = link_info->active_lanes;
+       } else if (!elink_info->autoneg) {
+               base->speed =
+                       bnge_fw_to_ethtool_speed(elink_info->req_link_speed);
+               base->duplex = DUPLEX_HALF;
+               if (elink_info->req_duplex == BNGE_LINK_DUPLEX_FULL)
+                       base->duplex = DUPLEX_FULL;
+       }
+}
+
+int bnge_get_link_ksettings(struct net_device *dev,
+                           struct ethtool_link_ksettings *lk_ksettings)
+{
+       struct ethtool_link_settings *base = &lk_ksettings->base;
+       enum ethtool_link_mode_bit_indices link_mode;
+       struct bnge_net *bn = netdev_priv(dev);
+       struct bnge_link_info *link_info;
+       struct bnge_dev *bd = bn->bd;
+       enum bnge_media_type media;
+
+       ethtool_link_ksettings_zero_link_mode(lk_ksettings, lp_advertising);
+       ethtool_link_ksettings_zero_link_mode(lk_ksettings, advertising);
+       ethtool_link_ksettings_zero_link_mode(lk_ksettings, supported);
+       base->duplex = DUPLEX_UNKNOWN;
+       base->speed = SPEED_UNKNOWN;
+       link_info = &bd->link_info;
+
+       bnge_get_ethtool_modes(bn, lk_ksettings);
+       media = bnge_get_media(link_info);
+       bnge_get_all_ethtool_support_speeds(bd, media, lk_ksettings);
+       bnge_fw_to_ethtool_support_fec(link_info, lk_ksettings);
+       link_mode = bnge_get_link_mode(bn);
+       if (link_mode != BNGE_LINK_MODE_UNKNOWN)
+               ethtool_params_from_link_mode(lk_ksettings, link_mode);
+       else
+               bnge_get_default_speeds(bn, lk_ksettings);
+
+       if (bn->eth_link_info.autoneg) {
+               bnge_fw_to_ethtool_advertised_fec(link_info, lk_ksettings);
+               linkmode_set_bit(ETHTOOL_LINK_MODE_Autoneg_BIT,
+                                lk_ksettings->link_modes.advertising);
+               base->autoneg = AUTONEG_ENABLE;
+               bnge_get_all_ethtool_adv_speeds(bn, media, lk_ksettings);
+               if (link_info->phy_link_status == BNGE_LINK_LINK)
+                       bnge_get_all_ethtool_lp_speeds(bd, media, lk_ksettings);
+       } else {
+               base->autoneg = AUTONEG_DISABLE;
+       }
+
+       linkmode_set_bit(ETHTOOL_LINK_MODE_FIBRE_BIT,
+                        lk_ksettings->link_modes.supported);
+       linkmode_set_bit(ETHTOOL_LINK_MODE_FIBRE_BIT,
+                        lk_ksettings->link_modes.advertising);
+
+       if (link_info->media_type == PORT_PHY_QCFG_RESP_MEDIA_TYPE_DAC)
+               base->port = PORT_DA;
+       else
+               base->port = PORT_FIBRE;
+       base->phy_address = link_info->phy_addr;
+
+       return 0;
+}
+
+static bool bnge_lanes_match(u32 user_lanes, u32 supported_lanes)
+{
+       /* 0 means lanes unspecified (auto) */
+       return !user_lanes || user_lanes == supported_lanes;
+}
+
+static int
+bnge_force_link_speed(struct net_device *dev, u32 ethtool_speed, u32 user_lanes)
+{
+       struct bnge_ethtool_link_info *elink_info;
+       struct bnge_net *bn = netdev_priv(dev);
+       u8 sig_mode = BNGE_SIG_MODE_NRZ;
+       u16 support_spds2;
+       u16 fw_speed = 0;
+
+       elink_info = &bn->eth_link_info;
+       support_spds2 = bn->bd->link_info.support_speeds2;
+
+       switch (ethtool_speed) {
+       case SPEED_50000:
+               if (bnge_lanes_match(user_lanes, 1) &&
+                   (support_spds2 & BNGE_LINK_SPEEDS2_MSK_50GB_PAM4)) {
+                       fw_speed = BNGE_LINK_SPEED_50GB_PAM4;
+                       sig_mode = BNGE_SIG_MODE_PAM4;
+               }
+               break;
+       case SPEED_100000:
+               if (bnge_lanes_match(user_lanes, 4) &&
+                   (support_spds2 & BNGE_LINK_SPEEDS2_MSK_100GB)) {
+                       fw_speed = PORT_PHY_CFG_REQ_FORCE_LINK_SPEED_100GB;
+               } else if (bnge_lanes_match(user_lanes, 2) &&
+                          (support_spds2 & BNGE_LINK_SPEEDS2_MSK_100GB_PAM4)) {
+                       fw_speed = BNGE_LINK_SPEED_100GB_PAM4;
+                       sig_mode = BNGE_SIG_MODE_PAM4;
+               } else if (bnge_lanes_match(user_lanes, 1) &&
+                          (support_spds2 & BNGE_LINK_SPEEDS2_MSK_100GB_PAM4_112)) {
+                       fw_speed = BNGE_LINK_SPEED_100GB_PAM4_112;
+                       sig_mode = BNGE_SIG_MODE_PAM4_112;
+               }
+               break;
+       case SPEED_200000:
+               if (bnge_lanes_match(user_lanes, 4) &&
+                   (support_spds2 & BNGE_LINK_SPEEDS2_MSK_200GB_PAM4)) {
+                       fw_speed = BNGE_LINK_SPEED_200GB_PAM4;
+                       sig_mode = BNGE_SIG_MODE_PAM4;
+               } else if (bnge_lanes_match(user_lanes, 2) &&
+                          (support_spds2 & BNGE_LINK_SPEEDS2_MSK_200GB_PAM4_112)) {
+                       fw_speed = BNGE_LINK_SPEED_200GB_PAM4_112;
+                       sig_mode = BNGE_SIG_MODE_PAM4_112;
+               }
+               break;
+       case SPEED_400000:
+               if (bnge_lanes_match(user_lanes, 8) &&
+                   (support_spds2 & BNGE_LINK_SPEEDS2_MSK_400GB_PAM4)) {
+                       fw_speed = BNGE_LINK_SPEED_400GB_PAM4;
+                       sig_mode = BNGE_SIG_MODE_PAM4;
+               } else if (bnge_lanes_match(user_lanes, 4) &&
+                          (support_spds2 & BNGE_LINK_SPEEDS2_MSK_400GB_PAM4_112)) {
+                       fw_speed = BNGE_LINK_SPEED_400GB_PAM4_112;
+                       sig_mode = BNGE_SIG_MODE_PAM4_112;
+               }
+               break;
+       case SPEED_800000:
+               if (bnge_lanes_match(user_lanes, 8) &&
+                   (support_spds2 & BNGE_LINK_SPEEDS2_MSK_800GB_PAM4_112)) {
+                       fw_speed = BNGE_LINK_SPEED_800GB_PAM4_112;
+                       sig_mode = BNGE_SIG_MODE_PAM4_112;
+               }
+               break;
+       default:
+               break;
+       }
+
+       if (!fw_speed) {
+               if (user_lanes)
+                       netdev_err(dev, "unsupported speed or number of lanes!\n");
+               else
+                       netdev_err(dev, "unsupported speed!\n");
+               return -EINVAL;
+       }
+
+       if (elink_info->req_link_speed == fw_speed &&
+           elink_info->req_signal_mode == sig_mode &&
+           elink_info->autoneg == 0)
+               return -EALREADY;
+
+       elink_info->req_link_speed = fw_speed;
+       elink_info->req_signal_mode = sig_mode;
+       elink_info->req_duplex = BNGE_LINK_DUPLEX_FULL;
+       elink_info->autoneg = 0;
+       elink_info->advertising = 0;
+
+       return 0;
+}
+
+int bnge_set_link_ksettings(struct net_device *dev,
+                           const struct ethtool_link_ksettings *lk_ksettings)
+{
+       const struct ethtool_link_settings *base = &lk_ksettings->base;
+       struct bnge_ethtool_link_info old_elink_info;
+       struct bnge_ethtool_link_info *elink_info;
+       struct bnge_net *bn = netdev_priv(dev);
+       struct bnge_dev *bd = bn->bd;
+       bool set_pause = false;
+       int rc = 0;
+
+       if (!BNGE_PHY_CFG_ABLE(bd))
+               return -EOPNOTSUPP;
+
+       elink_info = &bn->eth_link_info;
+       old_elink_info = *elink_info;
+
+       if (base->autoneg == AUTONEG_ENABLE) {
+               bnge_set_ethtool_speeds(bn,
+                                       lk_ksettings->link_modes.advertising);
+               elink_info->autoneg |= BNGE_AUTONEG_SPEED;
+               if (!elink_info->advertising)
+                       bnge_set_default_adv_speeds(bn);
+               /* any change to autoneg will cause link change, therefore the
+                * driver should put back the original pause setting in autoneg
+                */
+               if (!(bd->phy_flags & BNGE_PHY_FL_NO_PAUSE))
+                       set_pause = true;
+       } else {
+               if (base->duplex == DUPLEX_HALF) {
+                       netdev_err(dev, "HALF DUPLEX is not supported!\n");
+                       rc = -EINVAL;
+                       goto set_setting_exit;
+               }
+               rc = bnge_force_link_speed(dev, base->speed,
+                                          lk_ksettings->lanes);
+               if (rc) {
+                       if (rc == -EALREADY)
+                               rc = 0;
+                       goto set_setting_exit;
+               }
+       }
+
+       if (netif_running(dev)) {
+               rc = bnge_hwrm_set_link_setting(bn, set_pause);
+               if (rc)
+                       *elink_info = old_elink_info;
+       }
+
+set_setting_exit:
+       return rc;
+}
index 68eaa229fffe2c7b2620f9fcef48c8533bf15cf6..a0e6e62e03d84b3ce4b6e8b82f330405b2e78157 100644 (file)
@@ -4,6 +4,8 @@
 #ifndef _BNGE_LINK_H_
 #define _BNGE_LINK_H_
 
+#include <linux/ethtool.h>
+
 #define BNGE_PHY_CFG_ABLE(bd)          \
        ((bd)->link_info.phy_enabled)
 
@@ -162,4 +164,9 @@ void bnge_report_link(struct bnge_dev *bd);
 bool bnge_support_speed_dropped(struct bnge_net *bn);
 void bnge_init_ethtool_link_settings(struct bnge_net *bn);
 int bnge_probe_phy(struct bnge_net *bn, bool fw_dflt);
+int bnge_set_link_ksettings(struct net_device *dev,
+                           const struct ethtool_link_ksettings *lk_ksettings);
+int bnge_get_link_ksettings(struct net_device *dev,
+                           struct ethtool_link_ksettings *lk_ksettings);
+u32 bnge_get_link(struct net_device *dev);
 #endif /* _BNGE_LINK_H_ */