diff mbox series

mdio-bitbang: add support for lowlevel mdio read/write

Message ID 20191218162919.5293-1-m.grzeschik@pengutronix.de
State Changes Requested
Delegated to: David Miller
Headers show
Series mdio-bitbang: add support for lowlevel mdio read/write | expand

Commit Message

Michael Grzeschik Dec. 18, 2019, 4:29 p.m. UTC
Some phys support special opcode handling when communicating via mdio.
This patch introduces mdio_ll_read/write which makes it possible to set
the opcode. It implements these functions in the gpio-bitbang driver,
which is capable of setting the opcode on read and write.

Signed-off-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
---
Hi Andrew,

I worked on your suggestion moving the proprietary call to
mdio-ksz88x3.c which does not seem to work out very well.
I still end up having an MII_ADDR_SMI???? define in linux/phy.h.

Instead of having to support this special case in one extra file
what do you think of adding mdiobus_lowlevel_write/read to mdio_bus.
This way it would be possible to add the opcode directly as user.

Other controllers which have the possibility to set the op code in hardware
will also profit from that and can implement these functions.

Regards,
Michael

 drivers/net/phy/mdio-bitbang.c |  41 +++++++++---
 drivers/net/phy/mdio_bus.c     | 110 +++++++++++++++++++++++++++++++++
 include/linux/mdio.h           |   6 ++
 include/linux/phy.h            |   3 +
 4 files changed, 150 insertions(+), 10 deletions(-)

Comments

Andrew Lunn Dec. 19, 2019, 8:39 p.m. UTC | #1
On Wed, Dec 18, 2019 at 05:29:19PM +0100, Michael Grzeschik wrote:
> Some phys support special opcode handling when communicating via mdio.
> This patch introduces mdio_ll_read/write which makes it possible to set
> the opcode. It implements these functions in the gpio-bitbang driver,
> which is capable of setting the opcode on read and write.
> 
> Signed-off-by: Michael Grzeschik <m.grzeschik@pengutronix.de>

Hi Michael

It is normal to post the user of a new API at the same time as a new
API. I'm having trouble working out how this is supposed to be used.

     Andrew
Michael Grzeschik Dec. 19, 2019, 10:01 p.m. UTC | #2
On Thu, Dec 19, 2019 at 09:39:31PM +0100, Andrew Lunn wrote:
> On Wed, Dec 18, 2019 at 05:29:19PM +0100, Michael Grzeschik wrote:
> > Some phys support special opcode handling when communicating via mdio.
> > This patch introduces mdio_ll_read/write which makes it possible to set
> > the opcode. It implements these functions in the gpio-bitbang driver,
> > which is capable of setting the opcode on read and write.
> > 
> > Signed-off-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
> 
> Hi Michael
> 
> It is normal to post the user of a new API at the same time as a new
> API. I'm having trouble working out how this is supposed to be used.

Hi Andrew,

so this patch should have been in the series with the ksz8863 driver,
which is using the API. I will send the series again as v3 including
this patch, so it will be clear how this is ment.

Thanks,
Michael
Florian Fainelli Dec. 19, 2019, 10:05 p.m. UTC | #3
On 12/18/19 8:29 AM, Michael Grzeschik wrote:
> Some phys support special opcode handling when communicating via mdio.
> This patch introduces mdio_ll_read/write which makes it possible to set
> the opcode. It implements these functions in the gpio-bitbang driver,
> which is capable of setting the opcode on read and write.
> 
> Signed-off-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
> ---
> Hi Andrew,
> 
> I worked on your suggestion moving the proprietary call to
> mdio-ksz88x3.c which does not seem to work out very well.
> I still end up having an MII_ADDR_SMI???? define in linux/phy.h.
> 
> Instead of having to support this special case in one extra file
> what do you think of adding mdiobus_lowlevel_write/read to mdio_bus.
> This way it would be possible to add the opcode directly as user.
> 
> Other controllers which have the possibility to set the op code in hardware
> will also profit from that and can implement these functions.

I am not sure it makes sense for the entire struct mii_bus to gain two
new pointers, when you could just exported the mdiobb_ll_read() and
mdiobb_ll_write() to the kernel modules that needs those, and wrap them
however you need them to implement the mdiobus->read() and
mdiobus->write() operations?
Andrew Lunn Dec. 21, 2019, 4:41 p.m. UTC | #4
Hi Michael

In your V1 patch, you had this diagram.

+/* Serial Management Interface (SMI) uses the following frame format:
+ *
+ *       preamble|start|Read/Write|  PHY   |  REG  |TA|   Data bits      | Idle
+ *               |frame| OP code  |address |address|  |                  |
+ * read | 32x1´s | 01  |    00    | 1xRRR  | RRRRR |Z0| 00000000DDDDDDDD |  Z
+ * write| 32x1´s | 01  |    00    | 0xRRR  | RRRRR |10| xxxxxxxxDDDDDDDD |  Z
+ *
+ */

I just compared this to plain MDIO:

+ *       preamble|start|Read/Write|  PHY   |  REG  |TA|   Data bits      | Idle
+ *               |frame| OP code  |address |address|  |                  |
+ * read | 32x1´s | 01  |    10    | AAAA   | RRRRR |Z0| DDDDDDDDDDDDDDDD |  Z
+ * write| 32x1´s | 01  |    01    | AAAA   | RRRRR |10| DDDDDDDDDDDDDDDD |  Z

So the only real issue here is the OP code? The rest you can do with a
layer on top of the standard API.

How about something like this. Totally untested, probably does not
even compile.....

     Andrew

From 6051479b218fd19942d702e3e051c6355fe2a11f Mon Sep 17 00:00:00 2001
From: Andrew Lunn <andrew@lunn.ch>
Date: Sat, 21 Dec 2019 10:31:19 -0600
Subject: [PATCH] net: phy: Add support for microchip SMI0 MDIO bus.

SMI0 is a mangled version of MDIO. The main low level difference is
the MDIO C22 OP code is always 0, not 0x2 or 0x1 for Read/Write. The
read/write information is instead encoded in the PHY address.

Extend the bit-bang code to allow the op code to be overridden, but
default to normal C22 values. Add an extra compatible to the mdio-gpio
driver, and when this compatible is present, set the op codes to 0.

A higher level driver, sitting on top of the basic MDIO bus driver can
then implement the rest of the microchip SMI0 odderties.

Signed-off-by: Andrew Lunn <andrew@lunn.ch>
---
 drivers/net/phy/mdio-bitbang.c | 7 +++++--
 drivers/net/phy/mdio-gpio.c    | 7 +++++++
 include/linux/mdio-bitbang.h   | 2 ++
 3 files changed, 14 insertions(+), 2 deletions(-)

diff --git a/drivers/net/phy/mdio-bitbang.c b/drivers/net/phy/mdio-bitbang.c
index 5136275c8e73..01f620889c78 100644
--- a/drivers/net/phy/mdio-bitbang.c
+++ b/drivers/net/phy/mdio-bitbang.c
@@ -158,7 +158,7 @@ static int mdiobb_read(struct mii_bus *bus, int phy, int reg)
 		reg = mdiobb_cmd_addr(ctrl, phy, reg);
 		mdiobb_cmd(ctrl, MDIO_C45_READ, phy, reg);
 	} else
-		mdiobb_cmd(ctrl, MDIO_READ, phy, reg);
+		mdiobb_cmd(ctrl, ctrl->op_c22_read, phy, reg);
 
 	ctrl->ops->set_mdio_dir(ctrl, 0);
 
@@ -189,7 +189,7 @@ static int mdiobb_write(struct mii_bus *bus, int phy, int reg, u16 val)
 		reg = mdiobb_cmd_addr(ctrl, phy, reg);
 		mdiobb_cmd(ctrl, MDIO_C45_WRITE, phy, reg);
 	} else
-		mdiobb_cmd(ctrl, MDIO_WRITE, phy, reg);
+		mdiobb_cmd(ctrl, ctrl->op_c22_write, phy, reg);
 
 	/* send the turnaround (10) */
 	mdiobb_send_bit(ctrl, 1);
@@ -216,6 +216,9 @@ struct mii_bus *alloc_mdio_bitbang(struct mdiobb_ctrl *ctrl)
 	bus->write = mdiobb_write;
 	bus->priv = ctrl;
 
+	ctrl->op_c22_read = MDIO_READ;
+	ctrl->op_c22_write = MDIO_WRITE;
+
 	return bus;
 }
 EXPORT_SYMBOL(alloc_mdio_bitbang);
diff --git a/drivers/net/phy/mdio-gpio.c b/drivers/net/phy/mdio-gpio.c
index 1b00235d7dc5..282bc38331d7 100644
--- a/drivers/net/phy/mdio-gpio.c
+++ b/drivers/net/phy/mdio-gpio.c
@@ -132,6 +132,12 @@ static struct mii_bus *mdio_gpio_bus_init(struct device *dev,
 		new_bus->phy_ignore_ta_mask = pdata->phy_ignore_ta_mask;
 	}
 
+	if (dev->of_node &&
+	    of_device_is_compatible(dev->of_node, "microchip,mdio-smi0")) {
+		bitbang->ctrl->op_c22_read = 0;
+		bitbang->ctrl->op_c22_write = 0;
+	}
+
 	dev_set_drvdata(dev, new_bus);
 
 	return new_bus;
@@ -196,6 +202,7 @@ static int mdio_gpio_remove(struct platform_device *pdev)
 
 static const struct of_device_id mdio_gpio_of_match[] = {
 	{ .compatible = "virtual,mdio-gpio", },
+	{ .compatible = "microchip,mdio-smi0" },
 	{ /* sentinel */ }
 };
 MODULE_DEVICE_TABLE(of, mdio_gpio_of_match);
diff --git a/include/linux/mdio-bitbang.h b/include/linux/mdio-bitbang.h
index 5d71e8a8500f..8ae0b3835233 100644
--- a/include/linux/mdio-bitbang.h
+++ b/include/linux/mdio-bitbang.h
@@ -33,6 +33,8 @@ struct mdiobb_ops {
 
 struct mdiobb_ctrl {
 	const struct mdiobb_ops *ops;
+	u8 op_c22_read;
+	u8 op_c22_write;
 };
 
 /* The returned bus is not yet registered with the phy layer. */
Michael Grzeschik Jan. 29, 2020, 3:42 p.m. UTC | #5
Hi Andrew!

I tested your patch. But it works only partially. For the case that
the upper driver is directly communicating in SMI mode with the phy,
this works fine. But the regular MDIO connection does not work anymore
afterwards.

The normals MDIO communication still needs to work, as mdio-gpio is
calling of_mdiobus_register that on the other end calls get_phy_device
and tries to communicate via regular MDIO to the device.

Fixing the whole bus to the SMI opcode breaks the regular commands.

Do you have any ideas how to fix that?

Regards,
Michael

On Sat, Dec 21, 2019 at 05:41:10PM +0100, Andrew Lunn wrote:
> Hi Michael
> 
> In your V1 patch, you had this diagram.
> 
> +/* Serial Management Interface (SMI) uses the following frame format:
> + *
> + *       preamble|start|Read/Write|  PHY   |  REG  |TA|   Data bits      | Idle
> + *               |frame| OP code  |address |address|  |                  |
> + * read | 32x1´s | 01  |    00    | 1xRRR  | RRRRR |Z0| 00000000DDDDDDDD |  Z
> + * write| 32x1´s | 01  |    00    | 0xRRR  | RRRRR |10| xxxxxxxxDDDDDDDD |  Z
> + *
> + */
> 
> I just compared this to plain MDIO:
> 
> + *       preamble|start|Read/Write|  PHY   |  REG  |TA|   Data bits      | Idle
> + *               |frame| OP code  |address |address|  |                  |
> + * read | 32x1´s | 01  |    10    | AAAA   | RRRRR |Z0| DDDDDDDDDDDDDDDD |  Z
> + * write| 32x1´s | 01  |    01    | AAAA   | RRRRR |10| DDDDDDDDDDDDDDDD |  Z
> 
> So the only real issue here is the OP code? The rest you can do with a
> layer on top of the standard API.
> 
> How about something like this. Totally untested, probably does not
> even compile.....
> 
>      Andrew
> 
> From 6051479b218fd19942d702e3e051c6355fe2a11f Mon Sep 17 00:00:00 2001
> From: Andrew Lunn <andrew@lunn.ch>
> Date: Sat, 21 Dec 2019 10:31:19 -0600
> Subject: [PATCH] net: phy: Add support for microchip SMI0 MDIO bus.
> 
> SMI0 is a mangled version of MDIO. The main low level difference is
> the MDIO C22 OP code is always 0, not 0x2 or 0x1 for Read/Write. The
> read/write information is instead encoded in the PHY address.
> 
> Extend the bit-bang code to allow the op code to be overridden, but
> default to normal C22 values. Add an extra compatible to the mdio-gpio
> driver, and when this compatible is present, set the op codes to 0.
> 
> A higher level driver, sitting on top of the basic MDIO bus driver can
> then implement the rest of the microchip SMI0 odderties.
> 
> Signed-off-by: Andrew Lunn <andrew@lunn.ch>
> ---
>  drivers/net/phy/mdio-bitbang.c | 7 +++++--
>  drivers/net/phy/mdio-gpio.c    | 7 +++++++
>  include/linux/mdio-bitbang.h   | 2 ++
>  3 files changed, 14 insertions(+), 2 deletions(-)
> 
> diff --git a/drivers/net/phy/mdio-bitbang.c b/drivers/net/phy/mdio-bitbang.c
> index 5136275c8e73..01f620889c78 100644
> --- a/drivers/net/phy/mdio-bitbang.c
> +++ b/drivers/net/phy/mdio-bitbang.c
> @@ -158,7 +158,7 @@ static int mdiobb_read(struct mii_bus *bus, int phy, int reg)
>  		reg = mdiobb_cmd_addr(ctrl, phy, reg);
>  		mdiobb_cmd(ctrl, MDIO_C45_READ, phy, reg);
>  	} else
> -		mdiobb_cmd(ctrl, MDIO_READ, phy, reg);
> +		mdiobb_cmd(ctrl, ctrl->op_c22_read, phy, reg);
>  
>  	ctrl->ops->set_mdio_dir(ctrl, 0);
>  
> @@ -189,7 +189,7 @@ static int mdiobb_write(struct mii_bus *bus, int phy, int reg, u16 val)
>  		reg = mdiobb_cmd_addr(ctrl, phy, reg);
>  		mdiobb_cmd(ctrl, MDIO_C45_WRITE, phy, reg);
>  	} else
> -		mdiobb_cmd(ctrl, MDIO_WRITE, phy, reg);
> +		mdiobb_cmd(ctrl, ctrl->op_c22_write, phy, reg);
>  
>  	/* send the turnaround (10) */
>  	mdiobb_send_bit(ctrl, 1);
> @@ -216,6 +216,9 @@ struct mii_bus *alloc_mdio_bitbang(struct mdiobb_ctrl *ctrl)
>  	bus->write = mdiobb_write;
>  	bus->priv = ctrl;
>  
> +	ctrl->op_c22_read = MDIO_READ;
> +	ctrl->op_c22_write = MDIO_WRITE;
> +
>  	return bus;
>  }
>  EXPORT_SYMBOL(alloc_mdio_bitbang);
> diff --git a/drivers/net/phy/mdio-gpio.c b/drivers/net/phy/mdio-gpio.c
> index 1b00235d7dc5..282bc38331d7 100644
> --- a/drivers/net/phy/mdio-gpio.c
> +++ b/drivers/net/phy/mdio-gpio.c
> @@ -132,6 +132,12 @@ static struct mii_bus *mdio_gpio_bus_init(struct device *dev,
>  		new_bus->phy_ignore_ta_mask = pdata->phy_ignore_ta_mask;
>  	}
>  
> +	if (dev->of_node &&
> +	    of_device_is_compatible(dev->of_node, "microchip,mdio-smi0")) {
> +		bitbang->ctrl->op_c22_read = 0;
> +		bitbang->ctrl->op_c22_write = 0;
> +	}
> +
>  	dev_set_drvdata(dev, new_bus);
>  
>  	return new_bus;
> @@ -196,6 +202,7 @@ static int mdio_gpio_remove(struct platform_device *pdev)
>  
>  static const struct of_device_id mdio_gpio_of_match[] = {
>  	{ .compatible = "virtual,mdio-gpio", },
> +	{ .compatible = "microchip,mdio-smi0" },
>  	{ /* sentinel */ }
>  };
>  MODULE_DEVICE_TABLE(of, mdio_gpio_of_match);
> diff --git a/include/linux/mdio-bitbang.h b/include/linux/mdio-bitbang.h
> index 5d71e8a8500f..8ae0b3835233 100644
> --- a/include/linux/mdio-bitbang.h
> +++ b/include/linux/mdio-bitbang.h
> @@ -33,6 +33,8 @@ struct mdiobb_ops {
>  
>  struct mdiobb_ctrl {
>  	const struct mdiobb_ops *ops;
> +	u8 op_c22_read;
> +	u8 op_c22_write;
>  };
>  
>  /* The returned bus is not yet registered with the phy layer. */
> -- 
> 2.24.0
> 
>
Andrew Lunn Jan. 29, 2020, 3:53 p.m. UTC | #6
On Wed, Jan 29, 2020 at 04:42:01PM +0100, Michael Grzeschik wrote:
> Hi Andrew!
> 
> I tested your patch. But it works only partially. For the case that
> the upper driver is directly communicating in SMI mode with the phy,
> this works fine. But the regular MDIO connection does not work anymore
> afterwards.
> 
> The normals MDIO communication still needs to work, as mdio-gpio is
> calling of_mdiobus_register that on the other end calls get_phy_device
> and tries to communicate via regular MDIO to the device.

Do you mean you have a mix of devices on the bus, some standards
comformant, and others using this hacked up SMI0 mode?
You need to specify per device if SMI0 should be used?

    Andrew
Michael Grzeschik Jan. 29, 2020, 9:48 p.m. UTC | #7
On Wed, Jan 29, 2020 at 04:53:46PM +0100, Andrew Lunn wrote:
> On Wed, Jan 29, 2020 at 04:42:01PM +0100, Michael Grzeschik wrote:
> > Hi Andrew!
> > 
> > I tested your patch. But it works only partially. For the case that
> > the upper driver is directly communicating in SMI mode with the phy,
> > this works fine. But the regular MDIO connection does not work anymore
> > afterwards.
> > 
> > The normals MDIO communication still needs to work, as mdio-gpio is
> > calling of_mdiobus_register that on the other end calls get_phy_device
> > and tries to communicate via regular MDIO to the device.
> 
> Do you mean you have a mix of devices on the bus, some standards
> comformant, and others using this hacked up SMI0 mode?

Actually it is the same device used in both modes. The SMI0
mode is used by the switch driver to address the extended switch
functions. But on the same bus we have the fec connected to
the cpu bound fixed-phy (microchip,ks8863) via MDIO.

> You need to specify per device if SMI0 should be used?

Yes, we have to use the same bus fot both modes SMI0 and MDIO.

 Michael
Michael Grzeschik April 21, 2020, 2:31 p.m. UTC | #8
Hi Andrew,

I want to refresh this thread.

On Wed, Jan 29, 2020 at 10:48:05PM +0100, Michael Grzeschik wrote:
>On Wed, Jan 29, 2020 at 04:53:46PM +0100, Andrew Lunn wrote:
>> On Wed, Jan 29, 2020 at 04:42:01PM +0100, Michael Grzeschik wrote:
>> > Hi Andrew!
>> >
>> > I tested your patch. But it works only partially. For the case that
>> > the upper driver is directly communicating in SMI mode with the phy,
>> > this works fine. But the regular MDIO connection does not work anymore
>> > afterwards.
>> >
>> > The normals MDIO communication still needs to work, as mdio-gpio is
>> > calling of_mdiobus_register that on the other end calls get_phy_device
>> > and tries to communicate via regular MDIO to the device.
>>
>> Do you mean you have a mix of devices on the bus, some standards
>> comformant, and others using this hacked up SMI0 mode?
>
>Actually it is the same device used in both modes. The SMI0
>mode is used by the switch driver to address the extended switch
>functions. But on the same bus we have the fec connected to
>the cpu bound fixed-phy (microchip,ks8863) via MDIO.
>
>> You need to specify per device if SMI0 should be used?
>
>Yes, we have to use the same bus fot both modes SMI0 and MDIO.

In fact I for now used the cpu bound port with phy-handle to the fec.
This way it still used mdio for the initial probe.

But it should also work to use fixed-phy for it don't run into mdio
communication on the same bus. This way your patch should work.

In case you did not think of anything else, I will send the series
including your patch after I tested it with master.

Regards,
Michael
diff mbox series

Patch

diff --git a/drivers/net/phy/mdio-bitbang.c b/drivers/net/phy/mdio-bitbang.c
index 5136275c8e739..77fbc7eaadf51 100644
--- a/drivers/net/phy/mdio-bitbang.c
+++ b/drivers/net/phy/mdio-bitbang.c
@@ -149,16 +149,12 @@  static int mdiobb_cmd_addr(struct mdiobb_ctrl *ctrl, int phy, u32 addr)
 	return dev_addr;
 }
 
-static int mdiobb_read(struct mii_bus *bus, int phy, int reg)
+static int mdiobb_ll_read(struct mii_bus *bus, int op, int phy, int reg)
 {
 	struct mdiobb_ctrl *ctrl = bus->priv;
 	int ret, i;
 
-	if (reg & MII_ADDR_C45) {
-		reg = mdiobb_cmd_addr(ctrl, phy, reg);
-		mdiobb_cmd(ctrl, MDIO_C45_READ, phy, reg);
-	} else
-		mdiobb_cmd(ctrl, MDIO_READ, phy, reg);
+	mdiobb_cmd(ctrl, op, phy, reg);
 
 	ctrl->ops->set_mdio_dir(ctrl, 0);
 
@@ -181,15 +177,25 @@  static int mdiobb_read(struct mii_bus *bus, int phy, int reg)
 	return ret;
 }
 
-static int mdiobb_write(struct mii_bus *bus, int phy, int reg, u16 val)
+static int mdiobb_read(struct mii_bus *bus, int phy, int reg)
 {
 	struct mdiobb_ctrl *ctrl = bus->priv;
+	int op = MDIO_READ;
 
 	if (reg & MII_ADDR_C45) {
 		reg = mdiobb_cmd_addr(ctrl, phy, reg);
-		mdiobb_cmd(ctrl, MDIO_C45_WRITE, phy, reg);
-	} else
-		mdiobb_cmd(ctrl, MDIO_WRITE, phy, reg);
+		op = MDIO_C45_READ;
+	}
+
+	return mdiobb_ll_read(bus, op, phy, reg);
+}
+
+static int mdiobb_ll_write(struct mii_bus *bus, int op, int phy,
+			   int reg, u16 val)
+{
+	struct mdiobb_ctrl *ctrl = bus->priv;
+
+	mdiobb_cmd(ctrl, op, phy, reg);
 
 	/* send the turnaround (10) */
 	mdiobb_send_bit(ctrl, 1);
@@ -202,6 +208,19 @@  static int mdiobb_write(struct mii_bus *bus, int phy, int reg, u16 val)
 	return 0;
 }
 
+static int mdiobb_write(struct mii_bus *bus, int phy, int reg, u16 val)
+{
+	struct mdiobb_ctrl *ctrl = bus->priv;
+	int op = MDIO_WRITE;
+
+	if (reg & MII_ADDR_C45) {
+		reg = mdiobb_cmd_addr(ctrl, phy, reg);
+		op = MDIO_C45_WRITE;
+	}
+
+	return mdiobb_ll_write(bus, op, phy, reg, val);
+}
+
 struct mii_bus *alloc_mdio_bitbang(struct mdiobb_ctrl *ctrl)
 {
 	struct mii_bus *bus;
@@ -213,7 +232,9 @@  struct mii_bus *alloc_mdio_bitbang(struct mdiobb_ctrl *ctrl)
 	__module_get(ctrl->ops->owner);
 
 	bus->read = mdiobb_read;
+	bus->ll_read = mdiobb_ll_read;
 	bus->write = mdiobb_write;
+	bus->ll_write = mdiobb_ll_write;
 	bus->priv = ctrl;
 
 	return bus;
diff --git a/drivers/net/phy/mdio_bus.c b/drivers/net/phy/mdio_bus.c
index 229e480179ff1..57f4b7b9ce39a 100644
--- a/drivers/net/phy/mdio_bus.c
+++ b/drivers/net/phy/mdio_bus.c
@@ -560,6 +560,34 @@  int __mdiobus_read(struct mii_bus *bus, int addr, u32 regnum)
 }
 EXPORT_SYMBOL(__mdiobus_read);
 
+/**
+ * __mdiobus_ll_read - Unlocked version of the mdiobus_read function
+ * @bus: the mii_bus struct
+ * @op: opcode to use on transfer
+ * @addr: the phy address
+ * @regnum: register number to read
+ *
+ * Read a MDIO bus register. Caller must hold the mdio bus lock.
+ *
+ * NOTE: MUST NOT be called from interrupt context.
+ */
+int __mdiobus_ll_read(struct mii_bus *bus, int op, int addr, u32 regnum)
+{
+	int retval;
+
+	if (!bus->ll_write)
+		return -ENODEV;
+
+	WARN_ON_ONCE(!mutex_is_locked(&bus->mdio_lock));
+
+	retval = bus->ll_read(bus, op, addr, regnum);
+
+	trace_mdio_access(bus, 1, addr, regnum, retval, retval);
+
+	return retval;
+}
+EXPORT_SYMBOL(__mdiobus_ll_read);
+
 /**
  * __mdiobus_write - Unlocked version of the mdiobus_write function
  * @bus: the mii_bus struct
@@ -585,6 +613,36 @@  int __mdiobus_write(struct mii_bus *bus, int addr, u32 regnum, u16 val)
 }
 EXPORT_SYMBOL(__mdiobus_write);
 
+/**
+ * __mdiobus_ll_write - Unlocked version of the mdiobus_write function
+ * @bus: the mii_bus struct
+ * @op: opcode to use on transfer
+ * @addr: the phy address
+ * @regnum: register number to write
+ * @val: value to write to @regnum
+ *
+ * Write a MDIO bus register. Caller must hold the mdio bus lock.
+ *
+ * NOTE: MUST NOT be called from interrupt context.
+ */
+int __mdiobus_ll_write(struct mii_bus *bus, int op, int addr,
+		       u32 regnum, u16 val)
+{
+	int err;
+
+	if (!bus->ll_write)
+		return -ENODEV;
+
+	WARN_ON_ONCE(!mutex_is_locked(&bus->mdio_lock));
+
+	err = bus->ll_write(bus, op, addr, regnum, val);
+
+	trace_mdio_access(bus, 0, addr, regnum, val, err);
+
+	return err;
+}
+EXPORT_SYMBOL(__mdiobus_ll_write);
+
 /**
  * mdiobus_read_nested - Nested version of the mdiobus_read function
  * @bus: the mii_bus struct
@@ -636,6 +694,31 @@  int mdiobus_read(struct mii_bus *bus, int addr, u32 regnum)
 }
 EXPORT_SYMBOL(mdiobus_read);
 
+/**
+ * mdiobus_ll_read - Convenience function for reading a given MII mgmt register
+ * @bus: the mii_bus struct
+ * @op: opcode to use on transfer
+ * @addr: the phy address
+ * @regnum: register number to read
+ *
+ * NOTE: MUST NOT be called from interrupt context,
+ * because the bus read/write functions may wait for an interrupt
+ * to conclude the operation.
+ */
+int mdiobus_ll_read(struct mii_bus *bus, int op, int addr, u32 regnum)
+{
+	int retval;
+
+	BUG_ON(in_interrupt());
+
+	mutex_lock(&bus->mdio_lock);
+	retval = __mdiobus_ll_read(bus, op, addr, regnum);
+	mutex_unlock(&bus->mdio_lock);
+
+	return retval;
+}
+EXPORT_SYMBOL(mdiobus_ll_read);
+
 /**
  * mdiobus_write_nested - Nested version of the mdiobus_write function
  * @bus: the mii_bus struct
@@ -689,6 +772,33 @@  int mdiobus_write(struct mii_bus *bus, int addr, u32 regnum, u16 val)
 }
 EXPORT_SYMBOL(mdiobus_write);
 
+/**
+ * mdiobus_ll_write - Convenience function for writing a given MII mgmt register
+ * @bus: the mii_bus struct
+ * @op: opcode to use on transfer
+ * @addr: the phy address
+ * @regnum: register number to write
+ * @val: value to write to @regnum
+ *
+ * NOTE: MUST NOT be called from interrupt context,
+ * because the bus read/write functions may wait for an interrupt
+ * to conclude the operation.
+ */
+int mdiobus_ll_write(struct mii_bus *bus, int op, int addr,
+		     u32 regnum, u16 val)
+{
+	int err;
+
+	BUG_ON(in_interrupt());
+
+	mutex_lock(&bus->mdio_lock);
+	err = __mdiobus_ll_write(bus, op, addr, regnum, val);
+	mutex_unlock(&bus->mdio_lock);
+
+	return err;
+}
+EXPORT_SYMBOL(mdiobus_ll_write);
+
 /**
  * mdio_bus_match - determine if given MDIO driver supports the given
  *		    MDIO device
diff --git a/include/linux/mdio.h b/include/linux/mdio.h
index a7604248777b7..aafd24eb6d393 100644
--- a/include/linux/mdio.h
+++ b/include/linux/mdio.h
@@ -315,11 +315,17 @@  static inline void mii_10gbt_stat_mod_linkmode_lpa_t(unsigned long *advertising,
 }
 
 int __mdiobus_read(struct mii_bus *bus, int addr, u32 regnum);
+int __mdiobus_ll_read(struct mii_bus *bus, int op, int addr, u32 regnum);
 int __mdiobus_write(struct mii_bus *bus, int addr, u32 regnum, u16 val);
+int __mdiobus_ll_write(struct mii_bus *bus, int op, int addr,
+		       u32 regnum, u16 val);
 
 int mdiobus_read(struct mii_bus *bus, int addr, u32 regnum);
+int mdiobus_ll_read(struct mii_bus *bus, int op, int addr, u32 regnum);
 int mdiobus_read_nested(struct mii_bus *bus, int addr, u32 regnum);
 int mdiobus_write(struct mii_bus *bus, int addr, u32 regnum, u16 val);
+int mdiobus_ll_write(struct mii_bus *bus, int op, int addr,
+		     u32 regnum, u16 val);
 int mdiobus_write_nested(struct mii_bus *bus, int addr, u32 regnum, u16 val);
 
 int mdiobus_register_device(struct mdio_device *mdiodev);
diff --git a/include/linux/phy.h b/include/linux/phy.h
index 5032d453ac66a..3bb802cb03a8a 100644
--- a/include/linux/phy.h
+++ b/include/linux/phy.h
@@ -218,6 +218,9 @@  struct mii_bus {
 	void *priv;
 	int (*read)(struct mii_bus *bus, int addr, int regnum);
 	int (*write)(struct mii_bus *bus, int addr, int regnum, u16 val);
+	int (*ll_read)(struct mii_bus *bus, int op, int addr, int regnum);
+	int (*ll_write)(struct mii_bus *bus, int op, int addr,
+			int regnum, u16 val);
 	int (*reset)(struct mii_bus *bus);
 
 	/*