diff mbox series

[net-next,1/5] net: wangxun: add flow control support

Message ID 20231122102226.986265-2-jiawenwu@trustnetic.com (mailing list archive)
State Superseded
Delegated to: Netdev Maintainers
Headers show
Series Implement more ethtool_ops for Wangxun | expand

Checks

Context Check Description
netdev/series_format success Posting correctly formatted
netdev/codegen success Generated files up to date
netdev/tree_selection success Clearly marked for net-next
netdev/fixes_present success Fixes tag not required for -next series
netdev/header_inline success No static functions without inline keyword in header files
netdev/build_32bit success Errors and warnings before: 1127 this patch: 1127
netdev/cc_maintainers warning 2 maintainers not CCed: piotr.raczynski@intel.com maciej.fijalkowski@intel.com
netdev/build_clang success Errors and warnings before: 1154 this patch: 1154
netdev/verify_signedoff success Signed-off-by tag matches author and committer
netdev/deprecated_api success None detected
netdev/check_selftest success No net selftest shell script
netdev/verify_fixes success No Fixes tag
netdev/build_allmodconfig_warn success Errors and warnings before: 1154 this patch: 1154
netdev/checkpatch warning WARNING: line length of 81 exceeds 80 columns WARNING: line length of 85 exceeds 80 columns
netdev/build_clang_rust success No Rust files in patch. Skipping build
netdev/kdoc success Errors and warnings before: 0 this patch: 0
netdev/source_inline success Was 0 now: 0

Commit Message

Jiawen Wu Nov. 22, 2023, 10:22 a.m. UTC
Add support to set pause params with ethtool -A and get pause
params with ethtool -a, for ethernet driver txgbe and ngbe.

Signed-off-by: Jiawen Wu <jiawenwu@trustnetic.com>
---
 drivers/net/ethernet/wangxun/libwx/wx_hw.c    | 186 ++++++++++++++++++
 drivers/net/ethernet/wangxun/libwx/wx_hw.h    |   1 +
 drivers/net/ethernet/wangxun/libwx/wx_type.h  |  43 ++++
 .../net/ethernet/wangxun/ngbe/ngbe_ethtool.c  |  32 +++
 drivers/net/ethernet/wangxun/ngbe/ngbe_mdio.c |   2 +
 .../ethernet/wangxun/txgbe/txgbe_ethtool.c    |  18 ++
 .../net/ethernet/wangxun/txgbe/txgbe_phy.c    |   4 +
 7 files changed, 286 insertions(+)

Comments

Andrew Lunn Nov. 22, 2023, 4:15 p.m. UTC | #1
> +	/* Calculate max LAN frame size */
> +	link = dev->mtu + ETH_HLEN + ETH_FCS_LEN + WX_ETH_FRAMING;
> +	tc = link;
> +
> +	/* Calculate delay value for device */
> +	dv_id = WX_DV(link, tc);

That looks odd. tc == link. So why pass both? Or is it a typo?

> +/* BitTimes (BT) conversion */
> +#define WX_BT2KB(BT)         (((BT) + (8 * 1024 - 1)) / (8 * 1024))
> +#define WX_B2BT(BT)          ((BT) * 8)
> +/* Calculate Interface Delay */
> +#define WX_PHY_D     12800
> +#define WX_MAC_D     4096
> +#define WX_XAUI_D    (2 * 1024)
> +#define WX_ID        (WX_MAC_D + WX_XAUI_D + WX_PHY_D)
> +/* Calculate PCI Bus delay for low thresholds */
> +#define WX_PCI_DELAY 10000
> +
> +/* Calculate delay value in bit times */
> +#define WX_DV(_max_frame_link, _max_frame_tc) \
> +	((36 * (WX_B2BT(_max_frame_link) + 672 + (2 * 5556) + (2 * WX_ID) + 6144) / \
> +	  25 + 1) +  2 * WX_B2BT(_max_frame_tc))
> +
> +/* Calculate low threshold delay values */
> +#define WX_LOW_DV(_max_frame_tc) \
> +	(2 * (2 * WX_B2BT(_max_frame_tc) + (36 * WX_PCI_DELAY / 25) + 1))

There is too much magic here. Please make these functions, and add
comments explaining what is going on here.

> +static void ngbe_get_pauseparam(struct net_device *netdev,
> +				struct ethtool_pauseparam *pause)
> +{
> +	struct wx *wx = netdev_priv(netdev);
> +
> +	pause->autoneg = !wx->fc.disable_fc_autoneg;

Maybe call it enable_fs_autoneg, since that is what the kAPI uses?

> +static int ngbe_set_pauseparam(struct net_device *netdev,
> +			       struct ethtool_pauseparam *pause)
> +{
> +	struct wx *wx = netdev_priv(netdev);
> +
> +	if (!wx->phydev)
> +		return -ENODEV;
> +
> +	if (!phy_validate_pause(wx->phydev, pause))
> +		return -EINVAL;
> +
> +	wx->fc.disable_fc_autoneg = !pause->autoneg;
> +	wx->fc.tx_pause = pause->tx_pause;
> +	wx->fc.rx_pause = pause->rx_pause;
> +
> +	phy_set_asym_pause(wx->phydev, pause->rx_pause, pause->tx_pause);

You should only be doing this if pause->autoneg is true. If it is
false, you ignore the results from autoneg, and just configure the
hardware as indicated by pause->{tr}x_pause.

phylink makes this a lot easier, because it hides away all these
details. You might want to convert to phylink. Everybody gets is wrong
with phylib, but correct with phylink.

	 Andrew
Russell King (Oracle) Nov. 22, 2023, 4:30 p.m. UTC | #2
On Wed, Nov 22, 2023 at 05:15:05PM +0100, Andrew Lunn wrote:
> > +static void ngbe_get_pauseparam(struct net_device *netdev,
> > +				struct ethtool_pauseparam *pause)
> > +{
> > +	struct wx *wx = netdev_priv(netdev);
> > +
> > +	pause->autoneg = !wx->fc.disable_fc_autoneg;
> 
> Maybe call it enable_fs_autoneg, since that is what the kAPI uses?

fs?

> > +	wx->fc.disable_fc_autoneg = !pause->autoneg;
> > +	wx->fc.tx_pause = pause->tx_pause;
> > +	wx->fc.rx_pause = pause->rx_pause;
> > +
> > +	phy_set_asym_pause(wx->phydev, pause->rx_pause, pause->tx_pause);
> 
> You should only be doing this if pause->autoneg is true. If it is
> false, you ignore the results from autoneg, and just configure the
> hardware as indicated by pause->{tr}x_pause.

However, if autoneg is supported, but pause autoneg is disabled, there
is still the need to update the PHY's advertisement so the remote end
knows what's going on, as documented in the user API:

" * If the link is autonegotiated, drivers should use
  * mii_advertise_flowctrl() or similar code to set the advertised
  * pause frame capabilities based on the @rx_pause and @tx_pause flags,
  * even if @autoneg is zero. ... "

You are correct that when !pause->autoneg, tx_pause/rx_pause are to be
used in place of the negotiated versions.

Also... when getting the pause parameters, tx_pause/rx_pause _should_
reflect what was set for these parameters via the set function, *not*
the current state affected by negotiation.
Andrew Lunn Nov. 22, 2023, 5:24 p.m. UTC | #3
> However, if autoneg is supported, but pause autoneg is disabled, there
> is still the need to update the PHY's advertisement so the remote end
> knows what's going on, as documented in the user API:
> 
> " * If the link is autonegotiated, drivers should use
>   * mii_advertise_flowctrl() or similar code to set the advertised
>   * pause frame capabilities based on the @rx_pause and @tx_pause flags,
>   * even if @autoneg is zero. ... "
> 
> You are correct that when !pause->autoneg, tx_pause/rx_pause are to be
> used in place of the negotiated versions.
> 
> Also... when getting the pause parameters, tx_pause/rx_pause _should_
> reflect what was set for these parameters via the set function, *not*
> the current state affected by negotiation.

All good reasons to just use phylink which handles all this for you.

    Andrew
Russell King (Oracle) Nov. 22, 2023, 5:26 p.m. UTC | #4
On Wed, Nov 22, 2023 at 06:24:22PM +0100, Andrew Lunn wrote:
> > However, if autoneg is supported, but pause autoneg is disabled, there
> > is still the need to update the PHY's advertisement so the remote end
> > knows what's going on, as documented in the user API:
> > 
> > " * If the link is autonegotiated, drivers should use
> >   * mii_advertise_flowctrl() or similar code to set the advertised
> >   * pause frame capabilities based on the @rx_pause and @tx_pause flags,
> >   * even if @autoneg is zero. ... "
> > 
> > You are correct that when !pause->autoneg, tx_pause/rx_pause are to be
> > used in place of the negotiated versions.
> > 
> > Also... when getting the pause parameters, tx_pause/rx_pause _should_
> > reflect what was set for these parameters via the set function, *not*
> > the current state affected by negotiation.
> 
> All good reasons to just use phylink which handles all this for you.

Indeed... consistent implementation and therefore behaviour (assuming
users of phylink are implemented correctly!) :)
Jiawen Wu Nov. 23, 2023, 9:20 a.m. UTC | #5
On Thursday, November 23, 2023 12:15 AM, Andrew Lunn wrote:
> > +	/* Calculate max LAN frame size */
> > +	link = dev->mtu + ETH_HLEN + ETH_FCS_LEN + WX_ETH_FRAMING;
> > +	tc = link;
> > +
> > +	/* Calculate delay value for device */
> > +	dv_id = WX_DV(link, tc);
> 
> That looks odd. tc == link. So why pass both? Or is it a typo?

This is because I've temporarily removed the FCOE related code,
as something to support later. So now tc == link, which looks odd.
diff mbox series

Patch

diff --git a/drivers/net/ethernet/wangxun/libwx/wx_hw.c b/drivers/net/ethernet/wangxun/libwx/wx_hw.c
index 533e912af089..40897419a970 100644
--- a/drivers/net/ethernet/wangxun/libwx/wx_hw.c
+++ b/drivers/net/ethernet/wangxun/libwx/wx_hw.c
@@ -1158,6 +1158,81 @@  static void wx_set_rxpba(struct wx *wx)
 	wr32(wx, WX_TDM_PB_THRE(0), txpbthresh);
 }
 
+#define WX_ETH_FRAMING 20
+
+/**
+ * wx_hpbthresh - calculate high water mark for flow control
+ *
+ * @wx: board private structure to calculate for
+ **/
+static int wx_hpbthresh(struct wx *wx)
+{
+	struct net_device *dev = wx->netdev;
+	int link, tc, kb, marker;
+	u32 dv_id, rx_pba;
+
+	/* Calculate max LAN frame size */
+	link = dev->mtu + ETH_HLEN + ETH_FCS_LEN + WX_ETH_FRAMING;
+	tc = link;
+
+	/* Calculate delay value for device */
+	dv_id = WX_DV(link, tc);
+
+	/* Delay value is calculated in bit times convert to KB */
+	kb = WX_BT2KB(dv_id);
+	rx_pba = rd32(wx, WX_RDB_PB_SZ(0)) >> WX_RDB_PB_SZ_SHIFT;
+
+	marker = rx_pba - kb;
+
+	/* It is possible that the packet buffer is not large enough
+	 * to provide required headroom. In this case throw an error
+	 * to user and a do the best we can.
+	 */
+	if (marker < 0) {
+		dev_warn(&wx->pdev->dev,
+			 "Packet Buffer can not provide enough headroom to support flow control. Decrease MTU or number of traffic classes\n");
+		marker = tc + 1;
+	}
+
+	return marker;
+}
+
+/**
+ * wx_lpbthresh - calculate low water mark for flow control
+ *
+ * @wx: board private structure to calculate for
+ **/
+static int wx_lpbthresh(struct wx *wx)
+{
+	struct net_device *dev = wx->netdev;
+	u32 dv_id;
+	int tc;
+
+	/* Calculate max LAN frame size */
+	tc = dev->mtu + ETH_HLEN + ETH_FCS_LEN;
+
+	/* Calculate delay value for device */
+	dv_id = WX_LOW_DV(tc);
+
+	/* Delay value is calculated in bit times convert to KB */
+	return WX_BT2KB(dv_id);
+}
+
+/**
+ * wx_pbthresh_setup - calculate and setup high low water marks
+ *
+ * @wx: board private structure to calculate for
+ **/
+static void wx_pbthresh_setup(struct wx *wx)
+{
+	wx->fc.high_water = wx_hpbthresh(wx);
+	wx->fc.low_water = wx_lpbthresh(wx);
+
+	/* Low water marks must not be larger than high water marks */
+	if (wx->fc.low_water > wx->fc.high_water)
+		wx->fc.low_water = 0;
+}
+
 static void wx_configure_port(struct wx *wx)
 {
 	u32 value, i;
@@ -1584,6 +1659,7 @@  static void wx_configure_isb(struct wx *wx)
 void wx_configure(struct wx *wx)
 {
 	wx_set_rxpba(wx);
+	wx_pbthresh_setup(wx);
 	wx_configure_port(wx);
 
 	wx_set_rx_mode(wx->netdev);
@@ -1785,6 +1861,11 @@  int wx_sw_init(struct wx *wx)
 		return -ENOMEM;
 	}
 
+	wx->fc.pause_time = WX_DEFAULT_FCPAUSE;
+	wx->fc.disable_fc_autoneg = false;
+	wx->fc.rx_pause = true;
+	wx->fc.tx_pause = true;
+
 	return 0;
 }
 EXPORT_SYMBOL(wx_sw_init);
@@ -2003,6 +2084,111 @@  int wx_vlan_rx_kill_vid(struct net_device *netdev, __be16 proto, u16 vid)
 }
 EXPORT_SYMBOL(wx_vlan_rx_kill_vid);
 
+static void wx_enable_rx_drop(struct wx *wx, struct wx_ring *ring)
+{
+	u16 reg_idx = ring->reg_idx;
+	u32 srrctl;
+
+	srrctl = rd32(wx, WX_PX_RR_CFG(reg_idx));
+	srrctl |= WX_PX_RR_CFG_DROP_EN;
+
+	wr32(wx, WX_PX_RR_CFG(reg_idx), srrctl);
+}
+
+static void wx_disable_rx_drop(struct wx *wx, struct wx_ring *ring)
+{
+	u16 reg_idx = ring->reg_idx;
+	u32 srrctl;
+
+	srrctl = rd32(wx, WX_PX_RR_CFG(reg_idx));
+	srrctl &= ~WX_PX_RR_CFG_DROP_EN;
+
+	wr32(wx, WX_PX_RR_CFG(reg_idx), srrctl);
+}
+
+/**
+ *  wx_fc_enable - Enable flow control
+ *  @wx: pointer to hardware structure
+ *
+ *  Enable flow control according to the current settings.
+ **/
+int wx_fc_enable(struct wx *wx)
+{
+	u32 mflcn_reg, fccfg_reg, reg;
+	u32 fcrtl, fcrth;
+	int i;
+
+	/* Validate the water mark configuration */
+	if (!wx->fc.pause_time)
+		return -EINVAL;
+
+	/* Low water mark of zero causes XOFF floods */
+	if (wx->fc.tx_pause && wx->fc.high_water) {
+		if (!wx->fc.low_water || wx->fc.low_water >= wx->fc.high_water) {
+			wx_err(wx, "Invalid water mark configuration\n");
+			return -EINVAL;
+		}
+	}
+
+	/* Disable any previous flow control settings */
+	mflcn_reg = rd32(wx, WX_MAC_RX_FLOW_CTRL);
+	mflcn_reg &= ~WX_MAC_RX_FLOW_CTRL_RFE;
+
+	fccfg_reg = rd32(wx, WX_RDB_RFCC);
+	fccfg_reg &= ~WX_RDB_RFCC_RFCE_802_3X;
+
+	if (wx->fc.rx_pause)
+		mflcn_reg |= WX_MAC_RX_FLOW_CTRL_RFE;
+	if (wx->fc.tx_pause)
+		fccfg_reg |= WX_RDB_RFCC_RFCE_802_3X;
+
+	/* Set 802.3x based flow control settings. */
+	wr32(wx, WX_MAC_RX_FLOW_CTRL, mflcn_reg);
+	wr32(wx, WX_RDB_RFCC, fccfg_reg);
+
+	/* Set up and enable Rx high/low water mark thresholds, enable XON. */
+	if (wx->fc.tx_pause && wx->fc.high_water) {
+		fcrtl = (wx->fc.low_water << 10) | WX_RDB_RFCL_XONE;
+		wr32(wx, WX_RDB_RFCL, fcrtl);
+		fcrth = (wx->fc.high_water << 10) | WX_RDB_RFCH_XOFFE;
+	} else {
+		wr32(wx, WX_RDB_RFCL, 0);
+		/* In order to prevent Tx hangs when the internal Tx
+		 * switch is enabled we must set the high water mark
+		 * to the Rx packet buffer size - 24KB.  This allows
+		 * the Tx switch to function even under heavy Rx
+		 * workloads.
+		 */
+		fcrth = rd32(wx, WX_RDB_PB_SZ(0)) - 24576;
+	}
+
+	wr32(wx, WX_RDB_RFCH, fcrth);
+
+	/* Configure pause time */
+	reg = wx->fc.pause_time * 0x00010001;
+	wr32(wx, WX_RDB_RFCV, reg);
+
+	/* Configure flow control refresh threshold value */
+	wr32(wx, WX_RDB_RFCRT, wx->fc.pause_time / 2);
+
+	/*  We should set the drop enable bit if:
+	 *  Number of Rx queues > 1 and flow control is disabled
+	 *
+	 *  This allows us to avoid head of line blocking for security
+	 *  and performance reasons.
+	 */
+	if (wx->num_rx_queues > 1 && !wx->fc.tx_pause) {
+		for (i = 0; i < wx->num_rx_queues; i++)
+			wx_enable_rx_drop(wx, wx->rx_ring[i]);
+	} else {
+		for (i = 0; i < wx->num_rx_queues; i++)
+			wx_disable_rx_drop(wx, wx->rx_ring[i]);
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL(wx_fc_enable);
+
 /**
  * wx_update_stats - Update the board statistics counters.
  * @wx: board private structure
diff --git a/drivers/net/ethernet/wangxun/libwx/wx_hw.h b/drivers/net/ethernet/wangxun/libwx/wx_hw.h
index 12c20a7c364d..480d596cb70f 100644
--- a/drivers/net/ethernet/wangxun/libwx/wx_hw.h
+++ b/drivers/net/ethernet/wangxun/libwx/wx_hw.h
@@ -41,6 +41,7 @@  int wx_get_pcie_msix_counts(struct wx *wx, u16 *msix_count, u16 max_msix_count);
 int wx_sw_init(struct wx *wx);
 int wx_vlan_rx_add_vid(struct net_device *netdev, __be16 proto, u16 vid);
 int wx_vlan_rx_kill_vid(struct net_device *netdev, __be16 proto, u16 vid);
+int wx_fc_enable(struct wx *wx);
 void wx_update_stats(struct wx *wx);
 void wx_clear_hw_cntrs(struct wx *wx);
 
diff --git a/drivers/net/ethernet/wangxun/libwx/wx_type.h b/drivers/net/ethernet/wangxun/libwx/wx_type.h
index 165e82de772e..dc217437ec56 100644
--- a/drivers/net/ethernet/wangxun/libwx/wx_type.h
+++ b/drivers/net/ethernet/wangxun/libwx/wx_type.h
@@ -130,6 +130,15 @@ 
 #define WX_RDB_PFCMACDAH             0x19214
 #define WX_RDB_LXOFFTXC              0x19218
 #define WX_RDB_LXONTXC               0x1921C
+/* Flow Control Registers */
+#define WX_RDB_RFCV                  0x19200
+#define WX_RDB_RFCL                  0x19220
+#define WX_RDB_RFCL_XONE             BIT(31)
+#define WX_RDB_RFCH                  0x19260
+#define WX_RDB_RFCH_XOFFE            BIT(31)
+#define WX_RDB_RFCRT                 0x192A0
+#define WX_RDB_RFCC                  0x192A4
+#define WX_RDB_RFCC_RFCE_802_3X      BIT(3)
 /* ring assignment */
 #define WX_RDB_PL_CFG(_i)            (0x19300 + ((_i) * 4))
 #define WX_RDB_PL_CFG_L4HDR          BIT(1)
@@ -330,6 +339,7 @@  enum WX_MSCA_CMD_value {
 #define WX_PX_MPRC(_i)               (0x01020 + ((_i) * 0x40))
 /* PX_RR_CFG bit definitions */
 #define WX_PX_RR_CFG_VLAN            BIT(31)
+#define WX_PX_RR_CFG_DROP_EN         BIT(30)
 #define WX_PX_RR_CFG_SPLIT_MODE      BIT(26)
 #define WX_PX_RR_CFG_RR_THER_SHIFT   16
 #define WX_PX_RR_CFG_RR_HDR_SZ       GENMASK(15, 12)
@@ -367,6 +377,29 @@  enum WX_MSCA_CMD_value {
 #define WX_MAC_STATE_MODIFIED        0x2
 #define WX_MAC_STATE_IN_USE          0x4
 
+/* BitTimes (BT) conversion */
+#define WX_BT2KB(BT)         (((BT) + (8 * 1024 - 1)) / (8 * 1024))
+#define WX_B2BT(BT)          ((BT) * 8)
+/* Calculate Interface Delay */
+#define WX_PHY_D     12800
+#define WX_MAC_D     4096
+#define WX_XAUI_D    (2 * 1024)
+#define WX_ID        (WX_MAC_D + WX_XAUI_D + WX_PHY_D)
+/* Calculate PCI Bus delay for low thresholds */
+#define WX_PCI_DELAY 10000
+
+/* Calculate delay value in bit times */
+#define WX_DV(_max_frame_link, _max_frame_tc) \
+	((36 * (WX_B2BT(_max_frame_link) + 672 + (2 * 5556) + (2 * WX_ID) + 6144) / \
+	  25 + 1) +  2 * WX_B2BT(_max_frame_tc))
+
+/* Calculate low threshold delay values */
+#define WX_LOW_DV(_max_frame_tc) \
+	(2 * (2 * WX_B2BT(_max_frame_tc) + (36 * WX_PCI_DELAY / 25) + 1))
+
+/* flow control */
+#define WX_DEFAULT_FCPAUSE           0xFFFF
+
 #define WX_MAX_RXD                   8192
 #define WX_MAX_TXD                   8192
 
@@ -880,6 +913,15 @@  enum wx_isb_idx {
 	WX_ISB_MAX
 };
 
+struct wx_fc_info {
+	u32 high_water; /* Flow Ctrl High-water */
+	u32 low_water; /* Flow Ctrl Low-water */
+	u16 pause_time; /* Flow Control Pause timer */
+	bool disable_fc_autoneg; /* Do not autonegotiate FC */
+	bool tx_pause;
+	bool rx_pause;
+};
+
 /* Statistics counters collected by the MAC */
 struct wx_hw_stats {
 	u64 gprc;
@@ -920,6 +962,7 @@  struct wx {
 	enum sp_media_type media_type;
 	struct wx_eeprom_info eeprom;
 	struct wx_addr_filter_info addr_ctrl;
+	struct wx_fc_info fc;
 	struct wx_mac_addr *mac_table;
 	u16 device_id;
 	u16 vendor_id;
diff --git a/drivers/net/ethernet/wangxun/ngbe/ngbe_ethtool.c b/drivers/net/ethernet/wangxun/ngbe/ngbe_ethtool.c
index afbdf6919071..5baa89118600 100644
--- a/drivers/net/ethernet/wangxun/ngbe/ngbe_ethtool.c
+++ b/drivers/net/ethernet/wangxun/ngbe/ngbe_ethtool.c
@@ -41,6 +41,36 @@  static int ngbe_set_wol(struct net_device *netdev,
 	return 0;
 }
 
+static void ngbe_get_pauseparam(struct net_device *netdev,
+				struct ethtool_pauseparam *pause)
+{
+	struct wx *wx = netdev_priv(netdev);
+
+	pause->autoneg = !wx->fc.disable_fc_autoneg;
+	pause->tx_pause = wx->fc.tx_pause;
+	pause->rx_pause = wx->fc.rx_pause;
+}
+
+static int ngbe_set_pauseparam(struct net_device *netdev,
+			       struct ethtool_pauseparam *pause)
+{
+	struct wx *wx = netdev_priv(netdev);
+
+	if (!wx->phydev)
+		return -ENODEV;
+
+	if (!phy_validate_pause(wx->phydev, pause))
+		return -EINVAL;
+
+	wx->fc.disable_fc_autoneg = !pause->autoneg;
+	wx->fc.tx_pause = pause->tx_pause;
+	wx->fc.rx_pause = pause->rx_pause;
+
+	phy_set_asym_pause(wx->phydev, pause->rx_pause, pause->tx_pause);
+
+	return 0;
+}
+
 static const struct ethtool_ops ngbe_ethtool_ops = {
 	.get_drvinfo		= wx_get_drvinfo,
 	.get_link		= ethtool_op_get_link,
@@ -54,6 +84,8 @@  static const struct ethtool_ops ngbe_ethtool_ops = {
 	.get_ethtool_stats	= wx_get_ethtool_stats,
 	.get_eth_mac_stats	= wx_get_mac_stats,
 	.get_pause_stats	= wx_get_pause_stats,
+	.get_pauseparam		= ngbe_get_pauseparam,
+	.set_pauseparam		= ngbe_set_pauseparam,
 };
 
 void ngbe_set_ethtool_ops(struct net_device *netdev)
diff --git a/drivers/net/ethernet/wangxun/ngbe/ngbe_mdio.c b/drivers/net/ethernet/wangxun/ngbe/ngbe_mdio.c
index 6302ecca71bb..2fc8e526e983 100644
--- a/drivers/net/ethernet/wangxun/ngbe/ngbe_mdio.c
+++ b/drivers/net/ethernet/wangxun/ngbe/ngbe_mdio.c
@@ -62,6 +62,8 @@  static void ngbe_handle_link_change(struct net_device *dev)
 	struct phy_device *phydev;
 	u32 lan_speed, reg;
 
+	wx_fc_enable(wx);
+
 	phydev = wx->phydev;
 	if (!(wx->link != phydev->link ||
 	      wx->speed != phydev->speed ||
diff --git a/drivers/net/ethernet/wangxun/txgbe/txgbe_ethtool.c b/drivers/net/ethernet/wangxun/txgbe/txgbe_ethtool.c
index 3f336a088e43..8812d92d8b58 100644
--- a/drivers/net/ethernet/wangxun/txgbe/txgbe_ethtool.c
+++ b/drivers/net/ethernet/wangxun/txgbe/txgbe_ethtool.c
@@ -33,6 +33,22 @@  static int txgbe_set_link_ksettings(struct net_device *netdev,
 	return phylink_ethtool_ksettings_set(txgbe->phylink, cmd);
 }
 
+static void txgbe_get_pauseparam(struct net_device *netdev,
+				 struct ethtool_pauseparam *pause)
+{
+	struct txgbe *txgbe = netdev_to_txgbe(netdev);
+
+	phylink_ethtool_get_pauseparam(txgbe->phylink, pause);
+}
+
+static int txgbe_set_pauseparam(struct net_device *netdev,
+				struct ethtool_pauseparam *pause)
+{
+	struct txgbe *txgbe = netdev_to_txgbe(netdev);
+
+	return phylink_ethtool_set_pauseparam(txgbe->phylink, pause);
+}
+
 static const struct ethtool_ops txgbe_ethtool_ops = {
 	.get_drvinfo		= wx_get_drvinfo,
 	.nway_reset		= txgbe_nway_reset,
@@ -44,6 +60,8 @@  static const struct ethtool_ops txgbe_ethtool_ops = {
 	.get_ethtool_stats	= wx_get_ethtool_stats,
 	.get_eth_mac_stats	= wx_get_mac_stats,
 	.get_pause_stats	= wx_get_pause_stats,
+	.get_pauseparam		= txgbe_get_pauseparam,
+	.set_pauseparam		= txgbe_set_pauseparam,
 };
 
 void txgbe_set_ethtool_ops(struct net_device *netdev)
diff --git a/drivers/net/ethernet/wangxun/txgbe/txgbe_phy.c b/drivers/net/ethernet/wangxun/txgbe/txgbe_phy.c
index b6c06adb8656..472523f4a8e4 100644
--- a/drivers/net/ethernet/wangxun/txgbe/txgbe_phy.c
+++ b/drivers/net/ethernet/wangxun/txgbe/txgbe_phy.c
@@ -189,6 +189,10 @@  static void txgbe_mac_link_up(struct phylink_config *config,
 	struct wx *wx = netdev_priv(to_net_dev(config->dev));
 	u32 txcfg, wdg;
 
+	wx->fc.tx_pause = tx_pause;
+	wx->fc.rx_pause = rx_pause;
+	wx_fc_enable(wx);
+
 	txcfg = rd32(wx, WX_MAC_TX_CFG);
 	txcfg &= ~WX_MAC_TX_CFG_SPEED_MASK;