diff mbox series

[net-next,6/7] net: txgbe: support copper NIC with external PHY

Message ID 20230724102341.10401-7-jiawenwu@trustnetic.com (mailing list archive)
State Changes Requested
Delegated to: Netdev Maintainers
Headers show
Series support more link mode for TXGBE | expand

Checks

Context Check Description
netdev/series_format success Posting correctly formatted
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: 1342 this patch: 1342
netdev/cc_maintainers warning 6 maintainers not CCed: kuba@kernel.org maciej.fijalkowski@intel.com piotr.raczynski@intel.com davem@davemloft.net pabeni@redhat.com edumazet@google.com
netdev/build_clang success Errors and warnings before: 1365 this patch: 1365
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: 1365 this patch: 1365
netdev/checkpatch success total: 0 errors, 0 warnings, 0 checks, 405 lines checked
netdev/kdoc success Errors and warnings before: 0 this patch: 0
netdev/source_inline success Was 0 now: 0

Commit Message

Jiawen Wu July 24, 2023, 10:23 a.m. UTC
Wangxun SP chip supports to connect with external PHY (marvell 88x3310),
which links to 10GBASE-T/1000BASE-T/100BASE-T. Add the identification of
media types from subsystem device IDs. For sp_media_copper, register mdio
bus for the external PHY.

Signed-off-by: Jiawen Wu <jiawenwu@trustnetic.com>
---
 drivers/net/ethernet/wangxun/Kconfig          |   1 +
 drivers/net/ethernet/wangxun/libwx/wx_type.h  |  26 +++
 .../ethernet/wangxun/txgbe/txgbe_ethtool.c    |   9 +
 drivers/net/ethernet/wangxun/txgbe/txgbe_hw.c |  13 +-
 .../net/ethernet/wangxun/txgbe/txgbe_main.c   |  83 +++++++--
 .../net/ethernet/wangxun/txgbe/txgbe_phy.c    | 169 ++++++++++++++++++
 6 files changed, 281 insertions(+), 20 deletions(-)

Comments

Russell King (Oracle) July 24, 2023, 10:43 a.m. UTC | #1
On Mon, Jul 24, 2023 at 06:23:40PM +0800, Jiawen Wu wrote:
> @@ -22,6 +25,9 @@ static int txgbe_get_link_ksettings(struct net_device *netdev,
>  {
>  	struct txgbe *txgbe = netdev_to_txgbe(netdev);
>  
> +	if (txgbe->wx->media_type == sp_media_copper)
> +		return phy_ethtool_get_link_ksettings(netdev, cmd);

Why? If a PHY is attached via phylink, then phylink will automatically
forward the call below to phylib.
> +
>  	return phylink_ethtool_ksettings_get(txgbe->phylink, cmd);

If you implement it correctly, you also don't need two entirely
separate paths to configure the MAC/PCS for the results of the PHY's
negotiation, because phylink gives you a _generic_ set of interfaces
between whatever is downstream from the MAC and the MAC.
Jiawen Wu July 25, 2023, 2:41 a.m. UTC | #2
On Monday, July 24, 2023 6:43 PM, Russell King (Oracle) wrote:
> On Mon, Jul 24, 2023 at 06:23:40PM +0800, Jiawen Wu wrote:
> > @@ -22,6 +25,9 @@ static int txgbe_get_link_ksettings(struct net_device *netdev,
> >  {
> >  	struct txgbe *txgbe = netdev_to_txgbe(netdev);
> >
> > +	if (txgbe->wx->media_type == sp_media_copper)
> > +		return phy_ethtool_get_link_ksettings(netdev, cmd);
> 
> Why? If a PHY is attached via phylink, then phylink will automatically
> forward the call below to phylib.

No, there is no phylink implemented for sp_media_copper.

> > +
> >  	return phylink_ethtool_ksettings_get(txgbe->phylink, cmd);
> 
> If you implement it correctly, you also don't need two entirely
> separate paths to configure the MAC/PCS for the results of the PHY's
> negotiation, because phylink gives you a _generic_ set of interfaces
> between whatever is downstream from the MAC and the MAC.

For sp_media_copper, only mii bus is registered for attaching PHY.
Most MAC/PCS configuration is done in firmware, so it is not necessary
to implement phylink as sp_media_fiber.
Russell King (Oracle) July 25, 2023, 8:02 a.m. UTC | #3
On Tue, Jul 25, 2023 at 10:41:46AM +0800, Jiawen Wu wrote:
> On Monday, July 24, 2023 6:43 PM, Russell King (Oracle) wrote:
> > On Mon, Jul 24, 2023 at 06:23:40PM +0800, Jiawen Wu wrote:
> > > @@ -22,6 +25,9 @@ static int txgbe_get_link_ksettings(struct net_device *netdev,
> > >  {
> > >  	struct txgbe *txgbe = netdev_to_txgbe(netdev);
> > >
> > > +	if (txgbe->wx->media_type == sp_media_copper)
> > > +		return phy_ethtool_get_link_ksettings(netdev, cmd);
> > 
> > Why? If a PHY is attached via phylink, then phylink will automatically
> > forward the call below to phylib.
> 
> No, there is no phylink implemented for sp_media_copper.
> 
> > > +
> > >  	return phylink_ethtool_ksettings_get(txgbe->phylink, cmd);
> > 
> > If you implement it correctly, you also don't need two entirely
> > separate paths to configure the MAC/PCS for the results of the PHY's
> > negotiation, because phylink gives you a _generic_ set of interfaces
> > between whatever is downstream from the MAC and the MAC.
> 
> For sp_media_copper, only mii bus is registered for attaching PHY.
> Most MAC/PCS configuration is done in firmware, so it is not necessary
> to implement phylink as sp_media_fiber.

If you do implement phylink for copper, then you don't need all these
conditionals and the additional adjust_link implementation. In other
words, you can re-use a lot of the code you've already added.

You don't have to provide a PCS to phylink provided you don't tell
phylink that it's "in-band".
Jiawen Wu July 25, 2023, 10:04 a.m. UTC | #4
On Tuesday, July 25, 2023 4:03 PM, Russell King (Oracle) wrote:
> On Tue, Jul 25, 2023 at 10:41:46AM +0800, Jiawen Wu wrote:
> > On Monday, July 24, 2023 6:43 PM, Russell King (Oracle) wrote:
> > > On Mon, Jul 24, 2023 at 06:23:40PM +0800, Jiawen Wu wrote:
> > > > @@ -22,6 +25,9 @@ static int txgbe_get_link_ksettings(struct net_device *netdev,
> > > >  {
> > > >  	struct txgbe *txgbe = netdev_to_txgbe(netdev);
> > > >
> > > > +	if (txgbe->wx->media_type == sp_media_copper)
> > > > +		return phy_ethtool_get_link_ksettings(netdev, cmd);
> > >
> > > Why? If a PHY is attached via phylink, then phylink will automatically
> > > forward the call below to phylib.
> >
> > No, there is no phylink implemented for sp_media_copper.
> >
> > > > +
> > > >  	return phylink_ethtool_ksettings_get(txgbe->phylink, cmd);
> > >
> > > If you implement it correctly, you also don't need two entirely
> > > separate paths to configure the MAC/PCS for the results of the PHY's
> > > negotiation, because phylink gives you a _generic_ set of interfaces
> > > between whatever is downstream from the MAC and the MAC.
> >
> > For sp_media_copper, only mii bus is registered for attaching PHY.
> > Most MAC/PCS configuration is done in firmware, so it is not necessary
> > to implement phylink as sp_media_fiber.
> 
> If you do implement phylink for copper, then you don't need all these
> conditionals and the additional adjust_link implementation. In other
> words, you can re-use a lot of the code you've already added.
> 
> You don't have to provide a PCS to phylink provided you don't tell
> phylink that it's "in-band".

Do I need to create a separate software node? That would seem to
break more code of fiber initialization flow. I could try, but I'd like to
keep the two flows separate.
Russell King (Oracle) July 25, 2023, 10:38 a.m. UTC | #5
On Tue, Jul 25, 2023 at 06:04:49PM +0800, Jiawen Wu wrote:
> On Tuesday, July 25, 2023 4:03 PM, Russell King (Oracle) wrote:
> > On Tue, Jul 25, 2023 at 10:41:46AM +0800, Jiawen Wu wrote:
> > > On Monday, July 24, 2023 6:43 PM, Russell King (Oracle) wrote:
> > > > On Mon, Jul 24, 2023 at 06:23:40PM +0800, Jiawen Wu wrote:
> > > > > @@ -22,6 +25,9 @@ static int txgbe_get_link_ksettings(struct net_device *netdev,
> > > > >  {
> > > > >  	struct txgbe *txgbe = netdev_to_txgbe(netdev);
> > > > >
> > > > > +	if (txgbe->wx->media_type == sp_media_copper)
> > > > > +		return phy_ethtool_get_link_ksettings(netdev, cmd);
> > > >
> > > > Why? If a PHY is attached via phylink, then phylink will automatically
> > > > forward the call below to phylib.
> > >
> > > No, there is no phylink implemented for sp_media_copper.
> > >
> > > > > +
> > > > >  	return phylink_ethtool_ksettings_get(txgbe->phylink, cmd);
> > > >
> > > > If you implement it correctly, you also don't need two entirely
> > > > separate paths to configure the MAC/PCS for the results of the PHY's
> > > > negotiation, because phylink gives you a _generic_ set of interfaces
> > > > between whatever is downstream from the MAC and the MAC.
> > >
> > > For sp_media_copper, only mii bus is registered for attaching PHY.
> > > Most MAC/PCS configuration is done in firmware, so it is not necessary
> > > to implement phylink as sp_media_fiber.
> > 
> > If you do implement phylink for copper, then you don't need all these
> > conditionals and the additional adjust_link implementation. In other
> > words, you can re-use a lot of the code you've already added.
> > 
> > You don't have to provide a PCS to phylink provided you don't tell
> > phylink that it's "in-band".
> 
> Do I need to create a separate software node? That would seem to
> break more code of fiber initialization flow. I could try, but I'd like to
> keep the two flows separate.

You don't need any of the swnodes to be registered, so
txgbe_swnodes_register() can be skipped. You also don't need
txgbe_mdio_pcs_init() as you said firmware will look after that.

You will need txgbe_phylink_init() to select phy_mode depending on
whether your configuration is for SFP or not, so something like:

	if (txgbe->wx->media_type == sp_media_copper) {
		phy_mode = PHY_INTERFACE_MODE_XAUI;
		fwnode = NULL;
	} else {
		phy_mode = PHY_INTERFACE_MODE_10GBASER;
		fwnode = software_node_fwnode(txgbe->nodes.group[SWNODE_PHYLINK]);
	}

	__set_bit(phy_mode, config->supported_interfaces);
	phylink = phylink_create(config, fwnode, phy_mode, &txgbe_mac_ops);

You can then use phylink_connect_phy() to add the phydev to phylink.

You'll probably need to make txgbe_phylink_mac_select() check whether
txgbe->xpcs is non-NULL to prevent a NULL pointer dereference as I
don't believe you have the XPCS in this setup - or alternatively:

	if (interface == PHY_INTERFACE_MODE_10GBASER)
		return &txgbe->xpcs->pcs;

	return NULL;

and that should be about all that would be required. phylink will
then forward all the appropriate calls onto phylib for you, take care
of reading the phy's status, and calling the mac_link_up/mac_link_down
functions as the PHY status indicates the link changes state.

Phylink will call mac_prepare()/mac_config()/mac_finish() when the
netdev is opened, and will also limit the PHY's advertisement
according to the capabilities supplied in mac_capabilities, so you
shouldn't need to remove unsupported link modes from the PHY.
Simon Horman July 26, 2023, 12:15 p.m. UTC | #6
On Mon, Jul 24, 2023 at 06:23:40PM +0800, Jiawen Wu wrote:

...

Hi Jiawen Wu,

a minor nit from my side.

> diff --git a/drivers/net/ethernet/wangxun/txgbe/txgbe_phy.c b/drivers/net/ethernet/wangxun/txgbe/txgbe_phy.c

...

> +
> +	/* remove unsupport link mode */

nit: unsupport -> unsupported

> +	phy_remove_link_mode(phydev, ETHTOOL_LINK_MODE_10baseT_Half_BIT);
> +	phy_remove_link_mode(phydev, ETHTOOL_LINK_MODE_10baseT_Full_BIT);
> +	phy_remove_link_mode(phydev, ETHTOOL_LINK_MODE_100baseT_Half_BIT);
> +	phy_remove_link_mode(phydev, ETHTOOL_LINK_MODE_1000baseT_Half_BIT);
> +	phy_remove_link_mode(phydev, ETHTOOL_LINK_MODE_2500baseT_Full_BIT);
> +	phy_remove_link_mode(phydev, ETHTOOL_LINK_MODE_5000baseT_Full_BIT);

...
diff mbox series

Patch

diff --git a/drivers/net/ethernet/wangxun/Kconfig b/drivers/net/ethernet/wangxun/Kconfig
index 39596cd13539..23cd610bd376 100644
--- a/drivers/net/ethernet/wangxun/Kconfig
+++ b/drivers/net/ethernet/wangxun/Kconfig
@@ -41,6 +41,7 @@  config TXGBE
 	tristate "Wangxun(R) 10GbE PCI Express adapters support"
 	depends on PCI
 	depends on COMMON_CLK
+	select MARVELL_10G_PHY
 	select REGMAP
 	select I2C
 	select I2C_DESIGNWARE_PLATFORM
diff --git a/drivers/net/ethernet/wangxun/libwx/wx_type.h b/drivers/net/ethernet/wangxun/libwx/wx_type.h
index 50b92cfb46a0..c5cbd177ef62 100644
--- a/drivers/net/ethernet/wangxun/libwx/wx_type.h
+++ b/drivers/net/ethernet/wangxun/libwx/wx_type.h
@@ -233,6 +233,24 @@ 
 #define WX_MAC_WDG_TIMEOUT           0x1100C
 #define WX_MAC_RX_FLOW_CTRL          0x11090
 #define WX_MAC_RX_FLOW_CTRL_RFE      BIT(0) /* receive fc enable */
+/* MDIO Registers */
+#define WX_MSCA                      0x11200
+#define WX_MSCA_RA(v)                FIELD_PREP(U16_MAX, v)
+#define WX_MSCA_PA(v)                FIELD_PREP(GENMASK(20, 16), v)
+#define WX_MSCA_DA(v)                FIELD_PREP(GENMASK(25, 21), v)
+#define WX_MSCC                      0x11204
+#define WX_MSCC_CMD(v)               FIELD_PREP(GENMASK(17, 16), v)
+
+enum WX_MSCA_CMD_value {
+	WX_MSCA_CMD_RSV = 0,
+	WX_MSCA_CMD_WRITE,
+	WX_MSCA_CMD_POST_READ,
+	WX_MSCA_CMD_READ,
+};
+
+#define WX_MSCC_SADDR                BIT(18)
+#define WX_MSCC_BUSY                 BIT(22)
+#define WX_MDIO_CLK(v)               FIELD_PREP(GENMASK(21, 19), v)
 #define WX_MMC_CONTROL               0x11800
 #define WX_MMC_CONTROL_RSTONRD       BIT(2) /* reset on read */
 
@@ -582,6 +600,13 @@  enum wx_mac_type {
 	wx_mac_em
 };
 
+enum sp_media_type {
+	sp_media_unknown = 0,
+	sp_media_fiber,
+	sp_media_copper,
+	sp_media_backplane
+};
+
 enum em_mac_type {
 	em_mac_type_unknown = 0,
 	em_mac_type_mdi,
@@ -829,6 +854,7 @@  struct wx {
 	struct wx_bus_info bus;
 	struct wx_mac_info mac;
 	enum em_mac_type mac_type;
+	enum sp_media_type media_type;
 	struct wx_eeprom_info eeprom;
 	struct wx_addr_filter_info addr_ctrl;
 	struct wx_mac_addr *mac_table;
diff --git a/drivers/net/ethernet/wangxun/txgbe/txgbe_ethtool.c b/drivers/net/ethernet/wangxun/txgbe/txgbe_ethtool.c
index 859da112586a..889eb8251adc 100644
--- a/drivers/net/ethernet/wangxun/txgbe/txgbe_ethtool.c
+++ b/drivers/net/ethernet/wangxun/txgbe/txgbe_ethtool.c
@@ -14,6 +14,9 @@  static int txgbe_nway_reset(struct net_device *netdev)
 {
 	struct txgbe *txgbe = netdev_to_txgbe(netdev);
 
+	if (txgbe->wx->media_type == sp_media_copper)
+		return phy_ethtool_nway_reset(netdev);
+
 	return phylink_ethtool_nway_reset(txgbe->phylink);
 }
 
@@ -22,6 +25,9 @@  static int txgbe_get_link_ksettings(struct net_device *netdev,
 {
 	struct txgbe *txgbe = netdev_to_txgbe(netdev);
 
+	if (txgbe->wx->media_type == sp_media_copper)
+		return phy_ethtool_get_link_ksettings(netdev, cmd);
+
 	return phylink_ethtool_ksettings_get(txgbe->phylink, cmd);
 }
 
@@ -30,6 +36,9 @@  static int txgbe_set_link_ksettings(struct net_device *netdev,
 {
 	struct txgbe *txgbe = netdev_to_txgbe(netdev);
 
+	if (txgbe->wx->media_type == sp_media_copper)
+		return phy_ethtool_set_link_ksettings(netdev, cmd);
+
 	return phylink_ethtool_ksettings_set(txgbe->phylink, cmd);
 }
 
diff --git a/drivers/net/ethernet/wangxun/txgbe/txgbe_hw.c b/drivers/net/ethernet/wangxun/txgbe/txgbe_hw.c
index 90168aab11ae..372745250270 100644
--- a/drivers/net/ethernet/wangxun/txgbe/txgbe_hw.c
+++ b/drivers/net/ethernet/wangxun/txgbe/txgbe_hw.c
@@ -285,17 +285,20 @@  static void txgbe_reset_misc(struct wx *wx)
 int txgbe_reset_hw(struct wx *wx)
 {
 	int status;
-	u32 val;
 
 	/* Call adapter stop to disable tx/rx and clear interrupts */
 	status = wx_stop_adapter(wx);
 	if (status != 0)
 		return status;
 
-	val = WX_MIS_RST_LAN_RST(wx->bus.func);
-	wr32(wx, WX_MIS_RST, val | rd32(wx, WX_MIS_RST));
-	WX_WRITE_FLUSH(wx);
-	usleep_range(10, 100);
+	if (wx->media_type != sp_media_copper) {
+		u32 val;
+
+		val = WX_MIS_RST_LAN_RST(wx->bus.func);
+		wr32(wx, WX_MIS_RST, val | rd32(wx, WX_MIS_RST));
+		WX_WRITE_FLUSH(wx);
+		usleep_range(10, 100);
+	}
 
 	status = wx_check_flash_load(wx, TXGBE_SPI_ILDR_STATUS_LAN_SW_RST(wx->bus.func));
 	if (status != 0)
diff --git a/drivers/net/ethernet/wangxun/txgbe/txgbe_main.c b/drivers/net/ethernet/wangxun/txgbe/txgbe_main.c
index 46eba6d6188b..acdbc1f19449 100644
--- a/drivers/net/ethernet/wangxun/txgbe/txgbe_main.c
+++ b/drivers/net/ethernet/wangxun/txgbe/txgbe_main.c
@@ -203,10 +203,31 @@  static int txgbe_request_irq(struct wx *wx)
 	return err;
 }
 
+static void txgbe_start_phy(struct wx *wx)
+{
+	if (wx->media_type == sp_media_fiber) {
+		struct txgbe *txgbe = wx->priv;
+
+		phylink_start(txgbe->phylink);
+	} else if (wx->media_type == sp_media_copper) {
+		phy_start(wx->phydev);
+	}
+}
+
+static void txgbe_stop_phy(struct wx *wx)
+{
+	if (wx->media_type == sp_media_fiber) {
+		struct txgbe *txgbe = wx->priv;
+
+		phylink_stop(txgbe->phylink);
+	} else if (wx->media_type == sp_media_copper) {
+		phy_stop(wx->phydev);
+	}
+}
+
 static void txgbe_up_complete(struct wx *wx)
 {
 	struct net_device *netdev = wx->netdev;
-	struct txgbe *txgbe;
 
 	wx_control_hw(wx, true);
 	wx_configure_vectors(wx);
@@ -215,8 +236,7 @@  static void txgbe_up_complete(struct wx *wx)
 	smp_mb__before_atomic();
 	wx_napi_enable_all(wx);
 
-	txgbe = netdev_to_txgbe(netdev);
-	phylink_start(txgbe->phylink);
+	txgbe_start_phy(wx);
 
 	/* clear any pending interrupts, may auto mask */
 	rd32(wx, WX_PX_IC(0));
@@ -290,16 +310,57 @@  static void txgbe_disable_device(struct wx *wx)
 
 static void txgbe_down(struct wx *wx)
 {
-	struct txgbe *txgbe = netdev_to_txgbe(wx->netdev);
-
 	txgbe_disable_device(wx);
 	txgbe_reset(wx);
-	phylink_stop(txgbe->phylink);
+	txgbe_stop_phy(wx);
 
 	wx_clean_all_tx_rings(wx);
 	wx_clean_all_rx_rings(wx);
 }
 
+/**
+ *  txgbe_init_type_code - Initialize the shared code
+ *  @wx: pointer to hardware structure
+ **/
+static void txgbe_init_type_code(struct wx *wx)
+{
+	u8 device_type = wx->subsystem_device_id & 0xF0;
+
+	switch (wx->device_id) {
+	case TXGBE_DEV_ID_SP1000:
+	case TXGBE_DEV_ID_WX1820:
+		wx->mac.type = wx_mac_sp;
+		break;
+	default:
+		wx->mac.type = wx_mac_unknown;
+		break;
+	}
+
+	switch (device_type) {
+	case TXGBE_ID_SFP:
+		wx->media_type = sp_media_fiber;
+		break;
+	case TXGBE_ID_XAUI:
+	case TXGBE_ID_SGMII:
+		wx->media_type = sp_media_copper;
+		break;
+	case TXGBE_ID_KR_KX_KX4:
+	case TXGBE_ID_MAC_XAUI:
+	case TXGBE_ID_MAC_SGMII:
+		wx->media_type = sp_media_backplane;
+		break;
+	case TXGBE_ID_SFI_XAUI:
+		if (wx->bus.func == 0)
+			wx->media_type = sp_media_fiber;
+		else
+			wx->media_type = sp_media_copper;
+		break;
+	default:
+		wx->media_type = sp_media_unknown;
+		break;
+	}
+}
+
 /**
  * txgbe_sw_init - Initialize general software structures (struct wx)
  * @wx: board private structure to initialize
@@ -324,15 +385,7 @@  static int txgbe_sw_init(struct wx *wx)
 		return err;
 	}
 
-	switch (wx->device_id) {
-	case TXGBE_DEV_ID_SP1000:
-	case TXGBE_DEV_ID_WX1820:
-		wx->mac.type = wx_mac_sp;
-		break;
-	default:
-		wx->mac.type = wx_mac_unknown;
-		break;
-	}
+	txgbe_init_type_code(wx);
 
 	/* Set common capability flags and settings */
 	wx->max_q_vectors = TXGBE_MAX_MSIX_VECTORS;
diff --git a/drivers/net/ethernet/wangxun/txgbe/txgbe_phy.c b/drivers/net/ethernet/wangxun/txgbe/txgbe_phy.c
index 30a5ed2ed316..233b1b0fa274 100644
--- a/drivers/net/ethernet/wangxun/txgbe/txgbe_phy.c
+++ b/drivers/net/ethernet/wangxun/txgbe/txgbe_phy.c
@@ -616,10 +616,174 @@  static int txgbe_sfp_register(struct txgbe *txgbe)
 	return 0;
 }
 
+static int txgbe_phy_read(struct mii_bus *bus, int phy_addr,
+			  int devnum, int regnum)
+{
+	struct wx *wx = bus->priv;
+	u32 val, command;
+	int ret;
+
+	/* setup and write the address cycle command */
+	command = WX_MSCA_RA(regnum) |
+		  WX_MSCA_PA(phy_addr) |
+		  WX_MSCA_DA(devnum);
+	wr32(wx, WX_MSCA, command);
+
+	command = WX_MSCC_CMD(WX_MSCA_CMD_READ) | WX_MSCC_BUSY;
+	wr32(wx, WX_MSCC, command);
+
+	/* wait to complete */
+	ret = read_poll_timeout(rd32, val, !(val & WX_MSCC_BUSY), 1000,
+				100000, false, wx, WX_MSCC);
+	if (ret) {
+		wx_err(wx, "Mdio read c45 command did not complete.\n");
+		return ret;
+	}
+
+	return (u16)rd32(wx, WX_MSCC);
+}
+
+static int txgbe_phy_write(struct mii_bus *bus, int phy_addr,
+			   int devnum, int regnum, u16 value)
+{
+	struct wx *wx = bus->priv;
+	int ret, command;
+	u16 val;
+
+	/* setup and write the address cycle command */
+	command = WX_MSCA_RA(regnum) |
+		  WX_MSCA_PA(phy_addr) |
+		  WX_MSCA_DA(devnum);
+	wr32(wx, WX_MSCA, command);
+
+	command = value | WX_MSCC_CMD(WX_MSCA_CMD_WRITE) | WX_MSCC_BUSY;
+	wr32(wx, WX_MSCC, command);
+
+	/* wait to complete */
+	ret = read_poll_timeout(rd32, val, !(val & WX_MSCC_BUSY), 1000,
+				100000, false, wx, WX_MSCC);
+	if (ret)
+		wx_err(wx, "Mdio write c45 command did not complete.\n");
+
+	return ret;
+}
+
+static void txgbe_handle_phy_link(struct net_device *dev)
+{
+	struct wx *wx = netdev_priv(dev);
+	struct phy_device *phydev;
+	u32 txcfg, wdg;
+
+	phydev = wx->phydev;
+	if (!(wx->link != phydev->link ||
+	      wx->speed != phydev->speed ||
+	      wx->duplex != phydev->duplex))
+		return;
+
+	wx->link = phydev->link;
+	wx->speed = phydev->speed;
+	wx->duplex = phydev->duplex;
+
+	if (!phydev->link)
+		goto out;
+
+	txcfg = rd32(wx, WX_MAC_TX_CFG);
+	txcfg &= ~WX_MAC_TX_CFG_SPEED_MASK;
+
+	switch (phydev->speed) {
+	case SPEED_10000:
+		txcfg |= WX_MAC_TX_CFG_SPEED_10G;
+		break;
+	case SPEED_1000:
+	case SPEED_100:
+	case SPEED_10:
+		txcfg |= WX_MAC_TX_CFG_SPEED_1G;
+		break;
+	default:
+		break;
+	}
+
+	wr32(wx, WX_MAC_TX_CFG, txcfg | WX_MAC_TX_CFG_TE);
+
+	/* Re configure MAC Rx */
+	wr32m(wx, WX_MAC_RX_CFG, WX_MAC_RX_CFG_RE, WX_MAC_RX_CFG_RE);
+	wr32(wx, WX_MAC_PKT_FLT, WX_MAC_PKT_FLT_PR);
+	wdg = rd32(wx, WX_MAC_WDG_TIMEOUT);
+	wr32(wx, WX_MAC_WDG_TIMEOUT, wdg);
+
+out:
+	phy_print_status(phydev);
+}
+
+static int txgbe_mdio_phy_init(struct txgbe *txgbe)
+{
+	struct phy_device *phydev;
+	struct mii_bus *mii_bus;
+	struct pci_dev *pdev;
+	struct wx *wx;
+	int ret = 0;
+
+	wx = txgbe->wx;
+	pdev = wx->pdev;
+
+	mii_bus = devm_mdiobus_alloc(&pdev->dev);
+	if (!mii_bus)
+		return -ENOMEM;
+
+	mii_bus->name = "txgbe_mii_bus";
+	mii_bus->read_c45 = &txgbe_phy_read;
+	mii_bus->write_c45 = &txgbe_phy_write;
+	mii_bus->parent = &pdev->dev;
+	mii_bus->phy_mask = GENMASK(31, 1);
+	mii_bus->priv = wx;
+	snprintf(mii_bus->id, MII_BUS_ID_SIZE, "txgbe-%x",
+		 (pdev->bus->number << 8) | pdev->devfn);
+
+	ret = devm_mdiobus_register(&pdev->dev, mii_bus);
+	if (ret) {
+		wx_err(wx, "failed to register MDIO bus: %d\n", ret);
+		return ret;
+	}
+
+	phydev = phy_find_first(mii_bus);
+	if (!phydev) {
+		wx_err(wx, "no PHY found\n");
+		return -ENODEV;
+	}
+
+	phy_attached_info(phydev);
+
+	/* remove unsupport link mode */
+	phy_remove_link_mode(phydev, ETHTOOL_LINK_MODE_10baseT_Half_BIT);
+	phy_remove_link_mode(phydev, ETHTOOL_LINK_MODE_10baseT_Full_BIT);
+	phy_remove_link_mode(phydev, ETHTOOL_LINK_MODE_100baseT_Half_BIT);
+	phy_remove_link_mode(phydev, ETHTOOL_LINK_MODE_1000baseT_Half_BIT);
+	phy_remove_link_mode(phydev, ETHTOOL_LINK_MODE_2500baseT_Full_BIT);
+	phy_remove_link_mode(phydev, ETHTOOL_LINK_MODE_5000baseT_Full_BIT);
+
+	wx->link = 0;
+	wx->speed = 0;
+	wx->duplex = 0;
+	wx->phydev = phydev;
+
+	ret = phy_connect_direct(wx->netdev, phydev,
+				 txgbe_handle_phy_link,
+				 PHY_INTERFACE_MODE_XAUI);
+	if (ret) {
+		wx_err(wx, "PHY connect failed: %d\n", ret);
+		return ret;
+	}
+
+	return 0;
+}
+
 int txgbe_init_phy(struct txgbe *txgbe)
 {
 	int ret;
 
+	if (txgbe->wx->media_type == sp_media_copper)
+		return txgbe_mdio_phy_init(txgbe);
+
 	ret = txgbe_swnodes_register(txgbe);
 	if (ret) {
 		wx_err(txgbe->wx, "failed to register software nodes\n");
@@ -681,6 +845,11 @@  int txgbe_init_phy(struct txgbe *txgbe)
 
 void txgbe_remove_phy(struct txgbe *txgbe)
 {
+	if (txgbe->wx->media_type == sp_media_copper) {
+		phy_disconnect(txgbe->wx->phydev);
+		return;
+	}
+
 	platform_device_unregister(txgbe->sfp_dev);
 	platform_device_unregister(txgbe->i2c_dev);
 	clkdev_drop(txgbe->clock);