I40E_PRIV_FLAG("disable-source-pruning",
                       I40E_FLAG_SOURCE_PRUNING_DISABLED, 0),
        I40E_PRIV_FLAG("disable-fw-lldp", I40E_FLAG_DISABLE_FW_LLDP, 0),
+       I40E_PRIV_FLAG("rs-fec", I40E_FLAG_RS_FEC, 0),
+       I40E_PRIV_FLAG("base-r-fec", I40E_FLAG_BASE_R_FEC, 0),
 };
 
 #define I40E_PRIV_FLAGS_STR_LEN ARRAY_SIZE(i40e_gstrings_priv_flags)
                        ethtool_link_ksettings_add_link_mode(ks, advertising,
                                                             25000baseCR_Full);
        }
+       if (phy_types & I40E_CAP_PHY_TYPE_25GBASE_KR ||
+           phy_types & I40E_CAP_PHY_TYPE_25GBASE_CR ||
+           phy_types & I40E_CAP_PHY_TYPE_25GBASE_SR ||
+           phy_types & I40E_CAP_PHY_TYPE_25GBASE_LR ||
+           phy_types & I40E_CAP_PHY_TYPE_25GBASE_AOC ||
+           phy_types & I40E_CAP_PHY_TYPE_25GBASE_ACC) {
+               ethtool_link_ksettings_add_link_mode(ks, supported, FEC_NONE);
+               ethtool_link_ksettings_add_link_mode(ks, supported, FEC_RS);
+               ethtool_link_ksettings_add_link_mode(ks, supported, FEC_BASER);
+               if (hw_link_info->requested_speeds & I40E_LINK_SPEED_25GB) {
+                       ethtool_link_ksettings_add_link_mode(ks, advertising,
+                                                            FEC_NONE);
+                       ethtool_link_ksettings_add_link_mode(ks, advertising,
+                                                            FEC_RS);
+                       ethtool_link_ksettings_add_link_mode(ks, advertising,
+                                                            FEC_BASER);
+               }
+       }
        /* need to add new 10G PHY types */
        if (phy_types & I40E_CAP_PHY_TYPE_10GBASE_CR1 ||
            phy_types & I40E_CAP_PHY_TYPE_10GBASE_CR1_CU) {
                                                     25000baseSR_Full);
                ethtool_link_ksettings_add_link_mode(ks, advertising,
                                                     25000baseSR_Full);
+               ethtool_link_ksettings_add_link_mode(ks, supported, FEC_NONE);
+               ethtool_link_ksettings_add_link_mode(ks, supported, FEC_RS);
+               ethtool_link_ksettings_add_link_mode(ks, supported, FEC_BASER);
+               ethtool_link_ksettings_add_link_mode(ks, advertising, FEC_NONE);
+               ethtool_link_ksettings_add_link_mode(ks, advertising, FEC_RS);
+               ethtool_link_ksettings_add_link_mode(ks, advertising,
+                                                    FEC_BASER);
                ethtool_link_ksettings_add_link_mode(ks, supported,
                                                     10000baseSR_Full);
                ethtool_link_ksettings_add_link_mode(ks, advertising,
                                                     40000baseKR4_Full);
                ethtool_link_ksettings_add_link_mode(ks, supported,
                                                     25000baseKR_Full);
+               ethtool_link_ksettings_add_link_mode(ks, supported, FEC_NONE);
+               ethtool_link_ksettings_add_link_mode(ks, supported, FEC_RS);
+               ethtool_link_ksettings_add_link_mode(ks, supported, FEC_BASER);
                ethtool_link_ksettings_add_link_mode(ks, supported,
                                                     20000baseKR2_Full);
                ethtool_link_ksettings_add_link_mode(ks, supported,
                                                     40000baseKR4_Full);
                ethtool_link_ksettings_add_link_mode(ks, advertising,
                                                     25000baseKR_Full);
+               ethtool_link_ksettings_add_link_mode(ks, advertising, FEC_NONE);
+               ethtool_link_ksettings_add_link_mode(ks, advertising, FEC_RS);
+               ethtool_link_ksettings_add_link_mode(ks, advertising,
+                                                    FEC_BASER);
                ethtool_link_ksettings_add_link_mode(ks, advertising,
                                                     20000baseKR2_Full);
                ethtool_link_ksettings_add_link_mode(ks, advertising,
                                                     25000baseCR_Full);
                ethtool_link_ksettings_add_link_mode(ks, advertising,
                                                     25000baseCR_Full);
+               ethtool_link_ksettings_add_link_mode(ks, supported, FEC_NONE);
+               ethtool_link_ksettings_add_link_mode(ks, supported, FEC_RS);
+               ethtool_link_ksettings_add_link_mode(ks, supported, FEC_BASER);
+               ethtool_link_ksettings_add_link_mode(ks, advertising, FEC_NONE);
+               ethtool_link_ksettings_add_link_mode(ks, advertising, FEC_RS);
+               ethtool_link_ksettings_add_link_mode(ks, advertising,
+                                                    FEC_BASER);
                break;
        case I40E_PHY_TYPE_25GBASE_AOC:
        case I40E_PHY_TYPE_25GBASE_ACC:
                ethtool_link_ksettings_add_link_mode(ks, advertising, Autoneg);
                ethtool_link_ksettings_add_link_mode(ks, supported,
                                                     25000baseCR_Full);
-
                ethtool_link_ksettings_add_link_mode(ks, advertising,
                                                     25000baseCR_Full);
+               ethtool_link_ksettings_add_link_mode(ks, supported, FEC_NONE);
+               ethtool_link_ksettings_add_link_mode(ks, supported, FEC_RS);
+               ethtool_link_ksettings_add_link_mode(ks, supported, FEC_BASER);
+               ethtool_link_ksettings_add_link_mode(ks, advertising, FEC_NONE);
+               ethtool_link_ksettings_add_link_mode(ks, advertising, FEC_RS);
+               ethtool_link_ksettings_add_link_mode(ks, advertising,
+                                                    FEC_BASER);
                ethtool_link_ksettings_add_link_mode(ks, supported,
                                                     10000baseCR_Full);
                ethtool_link_ksettings_add_link_mode(ks, advertising,
        return err;
 }
 
+static int i40e_set_fec_cfg(struct net_device *netdev, u8 fec_cfg)
+{
+       struct i40e_netdev_priv *np = netdev_priv(netdev);
+       struct i40e_aq_get_phy_abilities_resp abilities;
+       struct i40e_pf *pf = np->vsi->back;
+       struct i40e_hw *hw = &pf->hw;
+       i40e_status status = 0;
+       u32 flags = 0;
+       int err = 0;
+
+       flags = READ_ONCE(pf->flags);
+       i40e_set_fec_in_flags(fec_cfg, &flags);
+
+       /* Get the current phy config */
+       memset(&abilities, 0, sizeof(abilities));
+       status = i40e_aq_get_phy_capabilities(hw, false, false, &abilities,
+                                             NULL);
+       if (status) {
+               err = -EAGAIN;
+               goto done;
+       }
+
+       if (abilities.fec_cfg_curr_mod_ext_info != fec_cfg) {
+               struct i40e_aq_set_phy_config config;
+
+               memset(&config, 0, sizeof(config));
+               config.phy_type = abilities.phy_type;
+               config.abilities = abilities.abilities;
+               config.phy_type_ext = abilities.phy_type_ext;
+               config.link_speed = abilities.link_speed;
+               config.eee_capability = abilities.eee_capability;
+               config.eeer = abilities.eeer_val;
+               config.low_power_ctrl = abilities.d3_lpan;
+               config.fec_config = fec_cfg & I40E_AQ_PHY_FEC_CONFIG_MASK;
+               status = i40e_aq_set_phy_config(hw, &config, NULL);
+               if (status) {
+                       netdev_info(netdev,
+                                   "Set phy config failed, err %s aq_err %s\n",
+                                   i40e_stat_str(hw, status),
+                                   i40e_aq_str(hw, hw->aq.asq_last_status));
+                       err = -EAGAIN;
+                       goto done;
+               }
+               pf->flags = flags;
+               status = i40e_update_link_info(hw);
+               if (status)
+                       /* debug level message only due to relation to the link
+                        * itself rather than to the FEC settings
+                        * (e.g. no physical connection etc.)
+                        */
+                       netdev_dbg(netdev,
+                                  "Updating link info failed with err %s aq_err %s\n",
+                                  i40e_stat_str(hw, status),
+                                  i40e_aq_str(hw, hw->aq.asq_last_status));
+       }
+
+done:
+       return err;
+}
+
+static int i40e_get_fec_param(struct net_device *netdev,
+                             struct ethtool_fecparam *fecparam)
+{
+       struct i40e_netdev_priv *np = netdev_priv(netdev);
+       struct i40e_aq_get_phy_abilities_resp abilities;
+       struct i40e_pf *pf = np->vsi->back;
+       struct i40e_hw *hw = &pf->hw;
+       i40e_status status = 0;
+       int err = 0;
+
+       /* Get the current phy config */
+       memset(&abilities, 0, sizeof(abilities));
+       status = i40e_aq_get_phy_capabilities(hw, false, false, &abilities,
+                                             NULL);
+       if (status) {
+               err = -EAGAIN;
+               goto done;
+       }
+
+       fecparam->fec = 0;
+       if (abilities.fec_cfg_curr_mod_ext_info & I40E_AQ_SET_FEC_AUTO)
+               fecparam->fec |= ETHTOOL_FEC_AUTO;
+       if ((abilities.fec_cfg_curr_mod_ext_info &
+            I40E_AQ_SET_FEC_REQUEST_RS) ||
+           (abilities.fec_cfg_curr_mod_ext_info &
+            I40E_AQ_SET_FEC_ABILITY_RS))
+               fecparam->fec |= ETHTOOL_FEC_RS;
+       if ((abilities.fec_cfg_curr_mod_ext_info &
+            I40E_AQ_SET_FEC_REQUEST_KR) ||
+           (abilities.fec_cfg_curr_mod_ext_info & I40E_AQ_SET_FEC_ABILITY_KR))
+               fecparam->fec |= ETHTOOL_FEC_BASER;
+       if (abilities.fec_cfg_curr_mod_ext_info == 0)
+               fecparam->fec |= ETHTOOL_FEC_OFF;
+
+       if (hw->phy.link_info.fec_info & I40E_AQ_CONFIG_FEC_KR_ENA)
+               fecparam->active_fec = ETHTOOL_FEC_BASER;
+       else if (hw->phy.link_info.fec_info & I40E_AQ_CONFIG_FEC_RS_ENA)
+               fecparam->active_fec = ETHTOOL_FEC_RS;
+       else
+               fecparam->active_fec = ETHTOOL_FEC_OFF;
+done:
+       return err;
+}
+
+static int i40e_set_fec_param(struct net_device *netdev,
+                             struct ethtool_fecparam *fecparam)
+{
+       struct i40e_netdev_priv *np = netdev_priv(netdev);
+       struct i40e_pf *pf = np->vsi->back;
+       struct i40e_hw *hw = &pf->hw;
+       u8 fec_cfg = 0;
+       int err = 0;
+
+       if (hw->device_id != I40E_DEV_ID_25G_SFP28 &&
+           hw->device_id != I40E_DEV_ID_25G_B) {
+               err = -EPERM;
+               goto done;
+       }
+
+       switch (fecparam->fec) {
+       case ETHTOOL_FEC_AUTO:
+               fec_cfg = I40E_AQ_SET_FEC_AUTO;
+               break;
+       case ETHTOOL_FEC_RS:
+               fec_cfg = (I40E_AQ_SET_FEC_REQUEST_RS |
+                            I40E_AQ_SET_FEC_ABILITY_RS);
+               break;
+       case ETHTOOL_FEC_BASER:
+               fec_cfg = (I40E_AQ_SET_FEC_REQUEST_KR |
+                            I40E_AQ_SET_FEC_ABILITY_KR);
+               break;
+       case ETHTOOL_FEC_OFF:
+       case ETHTOOL_FEC_NONE:
+               fec_cfg = 0;
+               break;
+       default:
+               dev_warn(&pf->pdev->dev, "Unsupported FEC mode: %d",
+                        fecparam->fec);
+               err = -EINVAL;
+               goto done;
+       }
+
+       err = i40e_set_fec_cfg(netdev, fec_cfg);
+
+done:
+       return err;
+}
+
 static int i40e_nway_reset(struct net_device *netdev)
 {
        /* restart autonegotiation */
        else if (!pause->rx_pause && !pause->tx_pause)
                hw->fc.requested_mode = I40E_FC_NONE;
        else
-                return -EINVAL;
+               return -EINVAL;
 
        /* Tell the OS link is going down, the link will go back up when fw
         * says it is ready asynchronously
                }
        }
 
+       if (((changed_flags & I40E_FLAG_RS_FEC) ||
+            (changed_flags & I40E_FLAG_BASE_R_FEC)) &&
+           pf->hw.device_id != I40E_DEV_ID_25G_SFP28 &&
+           pf->hw.device_id != I40E_DEV_ID_25G_B) {
+               dev_warn(&pf->pdev->dev,
+                        "Device does not support changing FEC configuration\n");
+               return -EOPNOTSUPP;
+       }
+
        /* Now that we've checked to ensure that the new flags are valid, load
         * them into place. Since we only modify flags either (a) during
         * initialization or (b) while holding the RTNL lock, we don't need
                }
        }
 
+       if ((changed_flags & I40E_FLAG_RS_FEC) ||
+           (changed_flags & I40E_FLAG_BASE_R_FEC)) {
+               u8 fec_cfg = 0;
+
+               if (pf->flags & I40E_FLAG_RS_FEC &&
+                   pf->flags & I40E_FLAG_BASE_R_FEC) {
+                       fec_cfg = I40E_AQ_SET_FEC_AUTO;
+               } else if (pf->flags & I40E_FLAG_RS_FEC) {
+                       fec_cfg = (I40E_AQ_SET_FEC_REQUEST_RS |
+                                  I40E_AQ_SET_FEC_ABILITY_RS);
+               } else if (pf->flags & I40E_FLAG_BASE_R_FEC) {
+                       fec_cfg = (I40E_AQ_SET_FEC_REQUEST_KR |
+                                  I40E_AQ_SET_FEC_ABILITY_KR);
+               }
+               if (i40e_set_fec_cfg(dev, fec_cfg))
+                       dev_warn(&pf->pdev->dev, "Cannot change FEC config\n");
+       }
+
        if ((changed_flags & pf->flags &
             I40E_FLAG_LINK_DOWN_ON_CLOSE_ENABLED) &&
            (pf->flags & I40E_FLAG_MFP_ENABLED))
        .set_per_queue_coalesce = i40e_set_per_queue_coalesce,
        .get_link_ksettings     = i40e_get_link_ksettings,
        .set_link_ksettings     = i40e_set_link_ksettings,
+       .get_fecparam = i40e_get_fec_param,
+       .set_fecparam = i40e_set_fec_param,
 };
 
 void i40e_set_ethtool_ops(struct net_device *netdev)