diff mbox series

[net-next,v1,5/5] net: tn40xx: add PHYLIB support

Message ID 20240415104352.4685-6-fujita.tomonori@gmail.com (mailing list archive)
State Superseded
Delegated to: Netdev Maintainers
Headers show
Series add ethernet driver for Tehuti Networks TN40xx chips | expand

Checks

Context Check Description
netdev/series_format success Posting correctly formatted
netdev/tree_selection success Clearly marked for net-next, async
netdev/ynl success Generated files up to date; no warnings/errors; no diff in generated;
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: 974 this patch: 974
netdev/build_tools success No tools touched, skip
netdev/cc_maintainers warning 3 maintainers not CCed: pabeni@redhat.com edumazet@google.com kuba@kernel.org
netdev/build_clang success Errors and warnings before: 943 this patch: 943
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: 981 this patch: 981
netdev/checkpatch warning WARNING: added, moved or deleted file(s), does MAINTAINERS need updating? WARNING: line length of 83 exceeds 80 columns WARNING: line length of 93 exceeds 80 columns WARNING: line length of 97 exceeds 80 columns
netdev/build_clang_rust success No Rust files in patch. Skipping build
netdev/kdoc success Errors and warnings before: 2 this patch: 2
netdev/source_inline success Was 0 now: 0

Commit Message

FUJITA Tomonori April 15, 2024, 10:43 a.m. UTC
This patch adds supports for multiple PHY hardware with PHYLIB. The
adapters with TN40xx chips use multiple PHY hardware; AMCC QT2025, TI
TLK10232, Aqrate AQR105, and Marvell 88X3120, 88X3310, and MV88E2010.

For now, the PCI ID table of this driver enables adapters using only
QT2025 PHY. I've tested this driver and the QT2025 PHY driver with
Edimax EN-9320 10G adapter.

Signed-off-by: FUJITA Tomonori <fujita.tomonori@gmail.com>
---
 drivers/net/ethernet/tehuti/Kconfig     |   1 +
 drivers/net/ethernet/tehuti/Makefile    |   2 +-
 drivers/net/ethernet/tehuti/tn40.c      |  32 ++++++
 drivers/net/ethernet/tehuti/tn40.h      |   5 +
 drivers/net/ethernet/tehuti/tn40_mdio.c | 141 ++++++++++++++++++++++++
 5 files changed, 180 insertions(+), 1 deletion(-)
 create mode 100644 drivers/net/ethernet/tehuti/tn40_mdio.c

Comments

Andrew Lunn April 15, 2024, 2:44 p.m. UTC | #1
On Mon, Apr 15, 2024 at 07:43:52PM +0900, FUJITA Tomonori wrote:
> This patch adds supports for multiple PHY hardware with PHYLIB. The
> adapters with TN40xx chips use multiple PHY hardware; AMCC QT2025, TI
> TLK10232, Aqrate AQR105, and Marvell 88X3120, 88X3310, and MV88E2010.
> 
> For now, the PCI ID table of this driver enables adapters using only
> QT2025 PHY. I've tested this driver and the QT2025 PHY driver with
> Edimax EN-9320 10G adapter.

Please split this up. Add the MDIO bus master in one patch. Then add
support for phylib in a second patch. They are logically different
things.

Are there variants of this device using SFP? It might be you actually
want to use phylink, not phylib. That is a bit messy for a PCI device,
look at drivers/net/ethernet/wangxun.

> diff --git a/drivers/net/ethernet/tehuti/Kconfig b/drivers/net/ethernet/tehuti/Kconfig
> index 4198fd59e42e..71f22471f9a0 100644
> --- a/drivers/net/ethernet/tehuti/Kconfig
> +++ b/drivers/net/ethernet/tehuti/Kconfig
> @@ -27,6 +27,7 @@ config TEHUTI_TN40
>  	tristate "Tehuti Networks TN40xx 10G Ethernet adapters"
>  	depends on PCI
>  	select FW_LOADER
> +	select AMCC_QT2025_PHY

That is pretty unusual, especially when you say there are a few
different choices.

> +static u32 bdx_mdio_get(struct bdx_priv *priv)
> +{
> +	void __iomem *regs = priv->regs;
> +
> +#define BDX_MAX_MDIO_BUSY_LOOPS 1024
> +	int tries = 0;
> +
> +	while (++tries < BDX_MAX_MDIO_BUSY_LOOPS) {
> +		u32 mdio_cmd_stat = readl(regs + REG_MDIO_CMD_STAT);
> +
> +		if (GET_MDIO_BUSY(mdio_cmd_stat) == 0)
> +			return mdio_cmd_stat;
> +	}
> +	dev_err(&priv->pdev->dev, "MDIO busy!\n");

include/linux/iopoll.h

> +	return 0xFFFFFFFF;

It is always better to use standard error codes. In this case,
-ETIMEDOUT.

> +static u16 bdx_mdio_read(struct bdx_priv *priv, int device, int port, u16 addr)
> +{
> +	void __iomem *regs = priv->regs;
> +	u32 tmp_reg, i;
> +	/* wait until MDIO is not busy */
> +	if (bdx_mdio_get(priv) == 0xFFFFFFFF)
> +		return -1;
> +
> +	i = ((device & 0x1F) | ((port & 0x1F) << 5));
> +	writel(i, regs + REG_MDIO_CMD);
> +	writel((u32)addr, regs + REG_MDIO_ADDR);
> +	tmp_reg = bdx_mdio_get(priv);
> +	if (tmp_reg == 0xFFFFFFFF)
> +		return -1;

This function has a return type of u16. So returning -1 makes no sense.

> +static int mdio_read_reg(struct mii_bus *mii_bus, int addr, int devnum, int regnum)
> +{
> +	return bdx_mdio_read(mii_bus->priv, devnum, addr, regnum);

I would probably change bdx_mdio_read() so that it takes the
parameters in the same order as mdio_read_reg().

There is also a reasonably common convention that the functions
performing C45 bus protocol operations have c45 in their name. It
appears this hardware does not support C22 at all. That makes it
unusual, and little hits like this are useful.

	 Andrew
FUJITA Tomonori April 16, 2024, 12:19 p.m. UTC | #2
Hi,

On Mon, 15 Apr 2024 16:44:31 +0200
Andrew Lunn <andrew@lunn.ch> wrote:

> On Mon, Apr 15, 2024 at 07:43:52PM +0900, FUJITA Tomonori wrote:
>> This patch adds supports for multiple PHY hardware with PHYLIB. The
>> adapters with TN40xx chips use multiple PHY hardware; AMCC QT2025, TI
>> TLK10232, Aqrate AQR105, and Marvell 88X3120, 88X3310, and MV88E2010.
>> 
>> For now, the PCI ID table of this driver enables adapters using only
>> QT2025 PHY. I've tested this driver and the QT2025 PHY driver with
>> Edimax EN-9320 10G adapter.
> 
> Please split this up. Add the MDIO bus master in one patch. Then add
> support for phylib in a second patch. They are logically different
> things.

Understood, I'll split this in v2.


> Are there variants of this device using SFP? It might be you actually
> want to use phylink, not phylib. That is a bit messy for a PCI device,
> look at drivers/net/ethernet/wangxun.

phylink is necessary if PHY is hot-pluggable, right? if so, the driver
doesn't need it. The PHYs that adapters with TN40XX use are

AMCC QT2025 PHY (SFP+)
- Tehuti TN9310
- DLink DXE-810S
- Asus XG-C100F
- Edimax EN-9320

Marvell MV88x3120 (10GBase-T)
- Tehuti TN9210

Marvell MV88X3310 (10GBase-T)
- Tehuti TN9710
- Edimax EN-9320TX-E
- Buffalo LGY-PCIE-MG
- IOI GE10
- LR-Link LREC6860BT
- QNAP PCIe Expansion Card

Marvell MV88E2010 (5GBase-T)
- Tehuti TN9710Q

TI TLK10232 (SFP+)
- Tehuti TN9610
- LR-Link LREC6860AF

Aquantia AQR105 (10GBase-T)
- Tehuti TN9510
- DLink DXE-810T
- Edimax EN-9320TX-E


>> diff --git a/drivers/net/ethernet/tehuti/Kconfig b/drivers/net/ethernet/tehuti/Kconfig
>> index 4198fd59e42e..71f22471f9a0 100644
>> --- a/drivers/net/ethernet/tehuti/Kconfig
>> +++ b/drivers/net/ethernet/tehuti/Kconfig
>> @@ -27,6 +27,7 @@ config TEHUTI_TN40
>>  	tristate "Tehuti Networks TN40xx 10G Ethernet adapters"
>>  	depends on PCI
>>  	select FW_LOADER
>> +	select AMCC_QT2025_PHY
> 
> That is pretty unusual, especially when you say there are a few
> different choices.

I should not put any 'select *_PHY' here?


>> +static u32 bdx_mdio_get(struct bdx_priv *priv)
>> +{
>> +	void __iomem *regs = priv->regs;
>> +
>> +#define BDX_MAX_MDIO_BUSY_LOOPS 1024
>> +	int tries = 0;
>> +
>> +	while (++tries < BDX_MAX_MDIO_BUSY_LOOPS) {
>> +		u32 mdio_cmd_stat = readl(regs + REG_MDIO_CMD_STAT);
>> +
>> +		if (GET_MDIO_BUSY(mdio_cmd_stat) == 0)
>> +			return mdio_cmd_stat;
>> +	}
>> +	dev_err(&priv->pdev->dev, "MDIO busy!\n");
> 
> include/linux/iopoll.h
> 
>> +	return 0xFFFFFFFF;
> 
> It is always better to use standard error codes. In this case,
> -ETIMEDOUT.

I'll


>> +static u16 bdx_mdio_read(struct bdx_priv *priv, int device, int port, u16 addr)
>> +{
>> +	void __iomem *regs = priv->regs;
>> +	u32 tmp_reg, i;
>> +	/* wait until MDIO is not busy */
>> +	if (bdx_mdio_get(priv) == 0xFFFFFFFF)
>> +		return -1;
>> +
>> +	i = ((device & 0x1F) | ((port & 0x1F) << 5));
>> +	writel(i, regs + REG_MDIO_CMD);
>> +	writel((u32)addr, regs + REG_MDIO_ADDR);
>> +	tmp_reg = bdx_mdio_get(priv);
>> +	if (tmp_reg == 0xFFFFFFFF)
>> +		return -1;
> 
> This function has a return type of u16. So returning -1 makes no sense.

Yeah, I thought the same but left it alone. I'll change in v2.


>> +static int mdio_read_reg(struct mii_bus *mii_bus, int addr, int devnum, int regnum)
>> +{
>> +	return bdx_mdio_read(mii_bus->priv, devnum, addr, regnum);
> 
> I would probably change bdx_mdio_read() so that it takes the
> parameters in the same order as mdio_read_reg().

Sure, I'll.


> There is also a reasonably common convention that the functions
> performing C45 bus protocol operations have c45 in their name. It
> appears this hardware does not support C22 at all. That makes it
> unusual, and little hits like this are useful.

I'm not sure the adapters supports C22 or not (probably do, I
guess). But the original driver uses C45 bus protocol operations.
Andrew Lunn April 16, 2024, 12:57 p.m. UTC | #3
> > Are there variants of this device using SFP? It might be you actually
> > want to use phylink, not phylib. That is a bit messy for a PCI device,
> > look at drivers/net/ethernet/wangxun.
> 
> phylink is necessary if PHY is hot-pluggable, right? if so, the driver
> doesn't need it. The PHYs that adapters with TN40XX use are

There is more to it than that. phylib has problems when the bandwidth
is > 1G and the MAC/PHY link becomes more problematic. Often the PHY
will change this link depending on what the media side is doing. If
you have a 1G SFP inserted, the QT2025 will change the MAC/PHY link to
1000BaseX. If it has a 10G SFP it will use XAUI. phylink knows how to
decode the SFP EEPROM to determine what sort of module it is, and how
the PHY should be configured.

To fully support this hardware you are going to need to use phylink.

> >> diff --git a/drivers/net/ethernet/tehuti/Kconfig b/drivers/net/ethernet/tehuti/Kconfig
> >> index 4198fd59e42e..71f22471f9a0 100644
> >> --- a/drivers/net/ethernet/tehuti/Kconfig
> >> +++ b/drivers/net/ethernet/tehuti/Kconfig
> >> @@ -27,6 +27,7 @@ config TEHUTI_TN40
> >>  	tristate "Tehuti Networks TN40xx 10G Ethernet adapters"
> >>  	depends on PCI
> >>  	select FW_LOADER
> >> +	select AMCC_QT2025_PHY
> > 
> > That is pretty unusual, especially when you say there are a few
> > different choices.
> 
> I should not put any 'select *_PHY' here?

Correct. Most distributions just package everything.

We are going to get into an odd corner case that since Rust is still
experimental, i doubt distributions are building Rust modules. So they
will end up with a MAC driver but no PHY driver, at least not for the
QT2025. The Marvell and Aquantia PHY should just work.

Anybody who does want to use the QT2025 will either need to
build there own kernel, or black list the in kernel MAC driver and use
the out of tree driver. But eventually, Rust will start to be
packaged, and then it should work out O.K.
 
	Andrew
FUJITA Tomonori April 25, 2024, 1:24 a.m. UTC | #4
Hi,

On Tue, 16 Apr 2024 14:57:58 +0200
Andrew Lunn <andrew@lunn.ch> wrote:

>> > Are there variants of this device using SFP? It might be you actually
>> > want to use phylink, not phylib. That is a bit messy for a PCI device,
>> > look at drivers/net/ethernet/wangxun.
>> 
>> phylink is necessary if PHY is hot-pluggable, right? if so, the driver
>> doesn't need it. The PHYs that adapters with TN40XX use are
> 
> There is more to it than that. phylib has problems when the bandwidth
> is > 1G and the MAC/PHY link becomes more problematic. Often the PHY
> will change this link depending on what the media side is doing. If
> you have a 1G SFP inserted, the QT2025 will change the MAC/PHY link to
> 1000BaseX. If it has a 10G SFP it will use XAUI. phylink knows how to
> decode the SFP EEPROM to determine what sort of module it is, and how
> the PHY should be configured.
> 
> To fully support this hardware you are going to need to use phylink.

I updated the code to use phylink and posted v2. At least seems that
it works with 10G SFP+ as before.

I suppose that more changes are necessary for full support. For
example, with 1G SFP inserted, the MAC driver has to configure the
hardware for 1G. I'll investigate once I get 1G SFP.

Note that the original driver supports only 10G SFP+.


>> >> diff --git a/drivers/net/ethernet/tehuti/Kconfig b/drivers/net/ethernet/tehuti/Kconfig
>> >> index 4198fd59e42e..71f22471f9a0 100644
>> >> --- a/drivers/net/ethernet/tehuti/Kconfig
>> >> +++ b/drivers/net/ethernet/tehuti/Kconfig
>> >> @@ -27,6 +27,7 @@ config TEHUTI_TN40
>> >>  	tristate "Tehuti Networks TN40xx 10G Ethernet adapters"
>> >>  	depends on PCI
>> >>  	select FW_LOADER
>> >> +	select AMCC_QT2025_PHY
>> > 
>> > That is pretty unusual, especially when you say there are a few
>> > different choices.
>> 
>> I should not put any 'select *_PHY' here?
> 
> Correct. Most distributions just package everything.
> 
> We are going to get into an odd corner case that since Rust is still
> experimental, i doubt distributions are building Rust modules. So they
> will end up with a MAC driver but no PHY driver, at least not for the
> QT2025. The Marvell and Aquantia PHY should just work.
> 
> Anybody who does want to use the QT2025 will either need to
> build there own kernel, or black list the in kernel MAC driver and use
> the out of tree driver. But eventually, Rust will start to be
> packaged, and then it should work out O.K.

Sure, I dropped the above in v2.
Andrew Lunn April 25, 2024, 2:37 p.m. UTC | #5
> I updated the code to use phylink and posted v2. At least seems that
> it works with 10G SFP+ as before.
> 
> I suppose that more changes are necessary for full support. For
> example, with 1G SFP inserted, the MAC driver has to configure the
> hardware for 1G. I'll investigate once I get 1G SFP.
> 
> Note that the original driver supports only 10G SFP+.

This is where the Rust PHY driver gets more interesting.

A PHY which is being used as a media converter needs to support a few
additional things. The marvel10g driver is a good example to follow
for some things. Look at mv3310_sfp_ops.

But there is more to it than that. phylink assumes it has access to
the i2c bus the module is on. The datasheet for the QT2025, shows a
couple of diagrams how an SFP is connected to it. The QT2025 has an
I2C bus which should be connected to the SFP cage. So you need to
export this I2C bus master in the PHY driver. So a Rust I2C bus
driver. Has anybody done that yet? However, it is not clear from my
reading of the datasheet if you get true access to the I2C bus. It
says:

XFP/SFP+ Module Access through MDIO

The MDIO interface can be used to access an XFP or
SFP+ module. The XFP/SFP+ module 2-wire interface
must be connected to the UC_SCL and UC_SDA clock
and data lines. The XFP module address is 1010000,
while the SFP+ module uses memory at addresses
1010000 and 1010001. The entire module address
space will be automatically read upon powerup, reset
or module hotplug by detection of the MOD_ABS
signal. A 400ms delay is observed before upload to
allow the module to initialize.
The memory at module address 1010000 is mapped to
MDIO register range 3.D000 - 3.D0FFh. Read/write
access to the module memory is controlled by MDIO
register 3.D100h. This applies to both module types.

The memory at module address 1010001 is mapped to
MDIO register range 1.8007 - 1.8106h. No read/write
access to the module memory is provided.

DOM Memory Access
The SFP+ DOM memory (A2) is mapped to MDIO
registers 1.8007-1.8106h (the NVR address space) or
alternatively to 1.A000-1.A0FF (this is firmware load
dependent; it may be configurable). If mapped to
1.A000-1.A0FF, DOM-related alarms in the SFP+
module will feed into the LASI alarm tree (see “Link
Alarm Status Interrupt Pin (LASI)” on page 74 for
details).

Later firmware versions implement a DOM periodic
polling feature, where the DOM memory is read at
every 1s. Optical alarms will then automatically alert
the host system through the LASI interrupt pins. Only a
subset of registers containing dynamically changing
values are polled on each update. Consult AMCC for
details on this feature.

So i guess you are going to need to fake the I2C bus, mapping I2C
transfer requests into MDIO reads of the mapped memory.

Additionally, phylink expects a few GPIO for Los of Signal, TX Enable,
TX Fault, etc. The PHY has these, so the PHY driver needs to export a
GPIO driver.

And then you need some glue, to bring all the parts together. The
wangxun Ethernet driver has mostly solved this, so you can take
inspiration from there.

You picked an interesting device to add Rust support for.

	Andrew
diff mbox series

Patch

diff --git a/drivers/net/ethernet/tehuti/Kconfig b/drivers/net/ethernet/tehuti/Kconfig
index 4198fd59e42e..71f22471f9a0 100644
--- a/drivers/net/ethernet/tehuti/Kconfig
+++ b/drivers/net/ethernet/tehuti/Kconfig
@@ -27,6 +27,7 @@  config TEHUTI_TN40
 	tristate "Tehuti Networks TN40xx 10G Ethernet adapters"
 	depends on PCI
 	select FW_LOADER
+	select AMCC_QT2025_PHY
 	help
 	  This driver supports 10G Ethernet adapters using Tehuti Networks
 	  TN40xx chips. Currently, adapters with Applied Micro Circuits
diff --git a/drivers/net/ethernet/tehuti/Makefile b/drivers/net/ethernet/tehuti/Makefile
index 1c468d99e476..7a0fe586a243 100644
--- a/drivers/net/ethernet/tehuti/Makefile
+++ b/drivers/net/ethernet/tehuti/Makefile
@@ -5,5 +5,5 @@ 
 
 obj-$(CONFIG_TEHUTI) += tehuti.o
 
-tn40xx-y := tn40.o
+tn40xx-y := tn40.o tn40_mdio.o
 obj-$(CONFIG_TEHUTI_TN40) += tn40xx.o
diff --git a/drivers/net/ethernet/tehuti/tn40.c b/drivers/net/ethernet/tehuti/tn40.c
index c8ed9b743753..2c50295f4e68 100644
--- a/drivers/net/ethernet/tehuti/tn40.c
+++ b/drivers/net/ethernet/tehuti/tn40.c
@@ -1285,18 +1285,26 @@  static void bdx_link_changed(struct bdx_priv *priv)
 		if (priv->link_loop_cnt++ > LINK_LOOP_MAX) {
 			/* MAC reset */
 			bdx_set_link_speed(priv, 0);
+			bdx_set_link_speed(priv, priv->phydev->speed);
 			priv->link_loop_cnt = 0;
 		}
 		write_reg(priv, 0x5150, 1000000);
 		return;
 	}
+
+	if (!netif_carrier_ok(priv->ndev)) {
+		netif_wake_queue(priv->ndev);
+		phy_print_status(priv->phydev);
+	}
 	priv->link = link;
+	netif_carrier_on(priv->ndev);
 }
 
 static inline void bdx_isr_extra(struct bdx_priv *priv, u32 isr)
 {
 	if (isr & (IR_LNKCHG0 | IR_LNKCHG1 | IR_TMR0)) {
 		netdev_dbg(priv->ndev, "isr = 0x%x\n", isr);
+		phy_mac_interrupt(priv->phydev);
 		bdx_link_changed(priv);
 	}
 }
@@ -1580,23 +1588,42 @@  static int bdx_close(struct net_device *ndev)
 
 	bdx_disable_interrupts(priv);
 	free_irq(priv->pdev->irq, priv->ndev);
+	phy_stop(priv->phydev);
+	phy_disconnect(priv->phydev);
 	bdx_sw_reset(priv);
 	destroy_rx_ring(priv);
 	destroy_tx_ring(priv);
 	return 0;
 }
 
+static void phy_handler(struct net_device *_dev)
+{
+}
+
 static int bdx_open(struct net_device *dev)
 {
 	struct bdx_priv *priv = netdev_priv(dev);
 	int ret;
 
 	bdx_sw_reset(priv);
+
+	ret = phy_connect_direct(priv->ndev, priv->phydev, phy_handler, PHY_INTERFACE_MODE_XAUI);
+	if (ret) {
+		netdev_err(dev, "failed to connect to phy %d\n", ret);
+		return ret;
+	}
+	phy_attached_info(priv->phydev);
+	phy_start(priv->phydev);
+
 	ret = bdx_start(priv);
 	if (ret) {
 		netdev_err(dev, "failed to start %d\n", ret);
+		phy_stop(priv->phydev);
+		phy_disconnect(priv->phydev);
 		return ret;
 	}
+	napi_enable(&priv->napi);
+	netif_start_queue(priv->ndev);
 	return 0;
 }
 
@@ -1872,6 +1899,11 @@  static int bdx_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
 
 	priv->stats_flag = ((read_reg(priv, FPGA_VER) & 0xFFF) != 308);
 
+	ret = bdx_mdiobus_init(priv);
+	if (ret) {
+		dev_err(&pdev->dev, "failed to find PHY.\n");
+		goto err_free_irq;
+	}
 	priv->isr_mask =
 	    IR_RX_FREE_0 | IR_LNKCHG0 | IR_PSE | IR_TMR0 | IR_RX_DESC_0 |
 	    IR_TX_FREE_0 | IR_TMR1;
diff --git a/drivers/net/ethernet/tehuti/tn40.h b/drivers/net/ethernet/tehuti/tn40.h
index fb43ebb5911f..06ab9a2cb42d 100644
--- a/drivers/net/ethernet/tehuti/tn40.h
+++ b/drivers/net/ethernet/tehuti/tn40.h
@@ -197,6 +197,9 @@  struct bdx_priv {
 	char *b0_va; /* Virtual address of buffer */
 
 	struct bdx_rx_page_table rx_page_table;
+
+	struct mii_bus *mdio;
+	struct phy_device *phydev;
 };
 
 /* RX FREE descriptor - 64bit */
@@ -283,4 +286,6 @@  static inline void write_reg(struct bdx_priv *priv, u32 reg, u32 val)
 	writel(val, priv->regs + reg);
 }
 
+int bdx_mdiobus_init(struct bdx_priv *priv);
+
 #endif /* _TN40XX_H */
diff --git a/drivers/net/ethernet/tehuti/tn40_mdio.c b/drivers/net/ethernet/tehuti/tn40_mdio.c
new file mode 100644
index 000000000000..f7f83c77e8b2
--- /dev/null
+++ b/drivers/net/ethernet/tehuti/tn40_mdio.c
@@ -0,0 +1,141 @@ 
+// SPDX-License-Identifier: GPL-2.0+
+/* Copyright (c) Tehuti Networks Ltd. */
+
+#include "tn40.h"
+
+static u32 bdx_mdio_get(struct bdx_priv *priv)
+{
+	void __iomem *regs = priv->regs;
+
+#define BDX_MAX_MDIO_BUSY_LOOPS 1024
+	int tries = 0;
+
+	while (++tries < BDX_MAX_MDIO_BUSY_LOOPS) {
+		u32 mdio_cmd_stat = readl(regs + REG_MDIO_CMD_STAT);
+
+		if (GET_MDIO_BUSY(mdio_cmd_stat) == 0)
+			return mdio_cmd_stat;
+	}
+	dev_err(&priv->pdev->dev, "MDIO busy!\n");
+	return 0xFFFFFFFF;
+}
+
+static u16 bdx_mdio_read(struct bdx_priv *priv, int device, int port, u16 addr)
+{
+	void __iomem *regs = priv->regs;
+	u32 tmp_reg, i;
+	/* wait until MDIO is not busy */
+	if (bdx_mdio_get(priv) == 0xFFFFFFFF)
+		return -1;
+
+	i = ((device & 0x1F) | ((port & 0x1F) << 5));
+	writel(i, regs + REG_MDIO_CMD);
+	writel((u32)addr, regs + REG_MDIO_ADDR);
+	tmp_reg = bdx_mdio_get(priv);
+	if (tmp_reg == 0xFFFFFFFF)
+		return -1;
+
+	writel(((1 << 15) | i), regs + REG_MDIO_CMD);
+	/* read CMD_STAT until not busy */
+	tmp_reg = bdx_mdio_get(priv);
+	if (tmp_reg == 0xFFFFFFFF)
+		return -1;
+
+	if (GET_MDIO_RD_ERR(tmp_reg)) {
+		dev_dbg(&priv->pdev->dev, "MDIO error after read command\n");
+		return -1;
+	}
+	tmp_reg = readl(regs + REG_MDIO_DATA);
+
+	return (tmp_reg & 0xFFFF);
+}
+
+static int bdx_mdio_write(struct bdx_priv *priv, int device, int port, u16 addr,
+			  u16 data)
+{
+	void __iomem *regs = priv->regs;
+	u32 tmp_reg;
+
+	/* wait until MDIO is not busy */
+	if (bdx_mdio_get(priv) == 0xFFFFFFFF)
+		return -1;
+	writel(((device & 0x1F) | ((port & 0x1F) << 5)), regs + REG_MDIO_CMD);
+	writel((u32)addr, regs + REG_MDIO_ADDR);
+	if (bdx_mdio_get(priv) == 0xFFFFFFFF)
+		return -1;
+	writel((u32)data, regs + REG_MDIO_DATA);
+	/* read CMD_STAT until not busy */
+	tmp_reg = bdx_mdio_get(priv);
+	if (tmp_reg == 0xFFFFFFFF)
+		return -1;
+
+	if (GET_MDIO_RD_ERR(tmp_reg)) {
+		dev_err(&priv->pdev->dev, "MDIO error after write command\n");
+		return -1;
+	}
+	return 0;
+}
+
+static void bdx_mdio_set_speed(struct bdx_priv *priv, u32 speed)
+{
+	void __iomem *regs = priv->regs;
+	int mdio_cfg;
+
+	mdio_cfg = readl(regs + REG_MDIO_CMD_STAT);
+	if (speed == 1)
+		mdio_cfg = (0x7d << 7) | 0x08;	/* 1MHz */
+	else
+		mdio_cfg = 0xA08;	/* 6MHz */
+	mdio_cfg |= (1 << 6);
+	writel(mdio_cfg, regs + REG_MDIO_CMD_STAT);
+	msleep(100);
+}
+
+static int mdio_read_reg(struct mii_bus *mii_bus, int addr, int devnum, int regnum)
+{
+	return bdx_mdio_read(mii_bus->priv, devnum, addr, regnum);
+}
+
+static int mdio_write_reg(struct mii_bus *mii_bus, int addr, int devnum, int regnum, u16 val)
+{
+	return bdx_mdio_write(mii_bus->priv, devnum, addr, regnum, val);
+}
+
+int bdx_mdiobus_init(struct bdx_priv *priv)
+{
+	struct pci_dev *pdev = priv->pdev;
+	struct mii_bus *bus;
+	struct phy_device *phydev;
+	int ret;
+
+	bus = devm_mdiobus_alloc(&pdev->dev);
+	if (!bus)
+		return -ENOMEM;
+
+	bus->name = BDX_DRV_NAME;
+	bus->parent = &pdev->dev;
+	snprintf(bus->id, MII_BUS_ID_SIZE, "tn40xx-%x-%x",
+		 pci_domain_nr(pdev->bus), pci_dev_id(pdev));
+	bus->priv = priv;
+
+	bus->read_c45 = mdio_read_reg;
+	bus->write_c45 = mdio_write_reg;
+
+	ret = devm_mdiobus_register(&pdev->dev, bus);
+	if (ret) {
+		dev_err(&pdev->dev, "failed to register mdiobus %d %u %u\n",
+			ret, bus->state, MDIOBUS_UNREGISTERED);
+		return ret;
+	}
+
+	phydev = phy_find_first(bus);
+	if (!phydev) {
+		dev_err(&pdev->dev, "failed to find phy\n");
+		return -1;
+	}
+	phydev->irq = PHY_MAC_INTERRUPT;
+	priv->mdio = bus;
+	priv->phydev = phydev;
+	bdx_mdio_set_speed(priv, MDIO_SPEED_6MHZ);
+	return 0;
+}