diff mbox

[1/6] bus: Add shared MDIO bus framework

Message ID 1461230323-27891-2-git-send-email-pramod.kumar@broadcom.com (mailing list archive)
State New, archived
Headers show

Commit Message

Pramod Kumar April 21, 2016, 9:18 a.m. UTC
Add a common shared MDIO bus framework for sharing single (or few) MDIO
bus across IO subsystems such as SATA, PCIe, USB, and Ethernet.

The IO specific PHY drivers will register to common shared MDIO
bus as shared MDIO drivers and access the MDIO bus only using
shared MDIO APIs.

Signed-off-by: Pramod Kumar <pramod.kumar@broadcom.com>
Signed-off-by: Anup Patel <anup.patel@broadcom.com>
Reviewed-by: Ray Jui <ray.jui@broadcom.com>
Reviewed-by: Scott Branden <scott.branden@broadcom.com>
Reviewed-by: Vikram Prakash <vikram.prakash@broadcom.com>
---
 drivers/bus/Kconfig         |  11 +++
 drivers/bus/Makefile        |   1 +
 drivers/bus/shared_mdio.c   | 179 ++++++++++++++++++++++++++++++++++++++++++++
 include/linux/shared_mdio.h | 123 ++++++++++++++++++++++++++++++
 4 files changed, 314 insertions(+)
 create mode 100644 drivers/bus/shared_mdio.c
 create mode 100644 include/linux/shared_mdio.h

Comments

David Miller April 24, 2016, 6:18 p.m. UTC | #1
From: Pramod Kumar <pramod.kumar@broadcom.com>
Date: Thu, 21 Apr 2016 14:48:38 +0530

> +struct shared_mdio_master *shared_mdio_alloc_master(struct device *parent,
> +						    struct device_node *node)
> +{
> +	int ret = 0;
> +	struct shared_mdio_master *master;

Always order local variable declarations in reverse christmas tree
(longest to shortest line) order.

> +static int shared_mdio_driver_probe(struct device *dev)
> +{
> +	int rc;
> +	struct shared_mdio_master *master = to_shared_mdio_master(dev);
> +	struct shared_mdio_driver *drv = to_shared_mdio_driver(dev->driver);

Likewise.

Please audit your entire submission for this issue.
Pramod Kumar April 25, 2016, 4:09 a.m. UTC | #2
Hi David,

Thanks for providing input over the patch.  Will address the comment as
described below.

> -----Original Message-----
> From: David Miller [mailto:davem@davemloft.net]
> Sent: 24 April 2016 23:48
> To: pramod.kumar@broadcom.com
> Cc: robh+dt@kernel.org; catalin.marinas@arm.com; will.deacon@arm.com;
> yamada.masahiro@socionext.com; wens@csie.org; bcm-kernel-feedback-
> list@broadcom.com; pawel.moll@arm.com; mark.rutland@arm.com;
> arnd@arndb.de; suzuki.poulose@arm.com; punit.agrawal@arm.com;
> devicetree@vger.kernel.org; linux-arm-kernel@lists.infradead.org; linux-
> kernel@vger.kernel.org; netdev@vger.kernel.org; anup.patel@broadcom.com
> Subject: Re: [PATCH 1/6] bus: Add shared MDIO bus framework
>
> From: Pramod Kumar <pramod.kumar@broadcom.com>
> Date: Thu, 21 Apr 2016 14:48:38 +0530
>
> > +struct shared_mdio_master *shared_mdio_alloc_master(struct device
> *parent,
> > +						    struct device_node
*node)
> > +{
> > +	int ret = 0;
> > +	struct shared_mdio_master *master;
>
> Always order local variable declarations in reverse christmas tree
(longest to
> shortest line) order.
>

Sure. Next patch will address this.

> > +static int shared_mdio_driver_probe(struct device *dev) {
> > +	int rc;
> > +	struct shared_mdio_master *master = to_shared_mdio_master(dev);
> > +	struct shared_mdio_driver *drv =
to_shared_mdio_driver(dev->driver);
>
> Likewise.

Sure.

> Please audit your entire submission for this issue.

Sure. I'll audit the entire patch set for above issue.

Regards,
Pramod
Andrew Lunn April 25, 2016, 8:56 p.m. UTC | #3
Hi Pramod

I took a closer look. I don't see why the current MDIO code should not
be used, rather than adding a new framework.

What you need for your Non Ethernet PHYs is that they are somehow
probed. The current MDIO code will do that, based on the compatible
string. An mdio device gets passed a struct mdio_device * to its probe
function, giving you the bus and address on the bus for the
device. Your PHY driver can then register itself using
devm_of_phy_provider_register(). The user of the PHY then needs to use
devm_phy_get() to get a handle on the phy, and can then use
phy_power_on()/phy_power_off().

There is a very simple example here for an MDIO device driver:

http://thread.gmane.org/gmane.linux.network/393532

The muxing of the MDIO busses looks a little tricky. At the moment you have:

    writel(cmd, base + MDIO_PARAM_OFFSET);

which mixes together the muxing parameters and the write value. Can
this register be accessed as two 16 bit registers? If it can be, you
can cleanly separate out the muxing.

Take a look at mdio-mux-gpio.c and mdio-mux-mmioreg.c for examples of
MDIO muxes.

     Andrew
Pramod Kumar April 26, 2016, 8:33 a.m. UTC | #4
Hi Andrew,

Thanks for reviewing. I really appreciate your effort it.

I am already aware of MDIO mux framework but did not see it fit for our
use case due to below limitations:

1.  Current MDIO mux framework is Ethernet centric and it is only meant to
mux multiple MII buses using same MDIO controller.  This means it is only
meant for MII-compliant PHY devices (i.e. PHY devices having registers
as-per MII specs). This is not the case with Broadcom SATA PHYs, PCIe
PHYs, and USB PHYs even if we are sharing same MDIO controller for
accessing these PHYs.

2.  The MDIO mux framework registers each child bus as MII bus. The Linux
Ethernet MDIO framework will scan for all attached PHY devices on given
MII bus and try to read MII PHY_ID register which is not present in all
Broadcom non-ethernet PHYs.

3. Let's say we ignore point1 and point2 above and go ahead and use MDIO
mux framework then we will still have to emulated MII PHY_ID read for
non-ethernet PHYs. Let's say we also emulate MII PHY_ID read in
non-ethernet PHYs then next thing is non-ethernet PHYs don't follow MII
state machine so all other MII PHY register read/write will not work.

4. Apart from these, by using MDIO mux framework we are making our
non-ethernet PHYs dependent on Linux network drivers which is not
acceptable. What if some product line does not need network subsystem at
all?

As you can see from above points, trying to re-use Linux Ethernet MDIO mux
framework for non-Ethernet PHYs is not the right way. The sole reason
being Linux MDIO mux framework is not generic enough to be shared across
IO subsystems. Due to this reason we had to come-up with "Shared MDIO
framework" which is very simple, generic  and independent of I/O
subsystems.

I'll add PCIe PHYs driver based on Shared MDIO framework in next patch
revision to get a feel of its need.

Regards,
Pramod

> -----Original Message-----
> From: Andrew Lunn [mailto:andrew@lunn.ch]
> Sent: 26 April 2016 02:27
> To: Pramod Kumar
> Cc: Rob Herring; Catalin Marinas; Will Deacon; Masahiro Yamada; Chen-Yu
Tsai;
> Mark Rutland; devicetree@vger.kernel.org; Pawel Moll; Arnd Bergmann;
Suzuki
> K Poulose; netdev@vger.kernel.org; Punit Agrawal; linux-
> kernel@vger.kernel.org; BCM Kernel Feedback; linux-arm-
> kernel@lists.infradead.org; Anup Patel
> Subject: Re: [PATCH 1/6] bus: Add shared MDIO bus framework
>
> Hi Pramod
>
> I took a closer look. I don't see why the current MDIO code should not
be used,
> rather than adding a new framework.
>
> What you need for your Non Ethernet PHYs is that they are somehow
probed.
> The current MDIO code will do that, based on the compatible string. An
mdio
> device gets passed a struct mdio_device * to its probe function, giving
you the
> bus and address on the bus for the device. Your PHY driver can then
register
> itself using devm_of_phy_provider_register(). The user of the PHY then
needs to
> use
> devm_phy_get() to get a handle on the phy, and can then use
> phy_power_on()/phy_power_off().
>
> There is a very simple example here for an MDIO device driver:
>
> http://thread.gmane.org/gmane.linux.network/393532
>
> The muxing of the MDIO busses looks a little tricky. At the moment you
have:
>
>     writel(cmd, base + MDIO_PARAM_OFFSET);
>
> which mixes together the muxing parameters and the write value. Can this
> register be accessed as two 16 bit registers? If it can be, you can
cleanly
> separate out the muxing.
>
> Take a look at mdio-mux-gpio.c and mdio-mux-mmioreg.c for examples of
> MDIO muxes.
>
>      Andrew
Andrew Lunn April 26, 2016, 12:13 p.m. UTC | #5
On Tue, Apr 26, 2016 at 02:03:27PM +0530, Pramod Kumar wrote:
> Hi Andrew,
> 
> Thanks for reviewing. I really appreciate your effort it.
> 
> I am already aware of MDIO mux framework but did not see it fit for our
> use case due to below limitations:
> 
> 1.  Current MDIO mux framework is Ethernet centric and it is only meant to
> mux multiple MII buses using same MDIO controller.  This means it is only
> meant for MII-compliant PHY devices (i.e. PHY devices having registers
> as-per MII specs).

Nope, this is wrong. You can have a mixture of PHYs and other MDIO
devices on the MII bus. The framework does not care. A PHY is a
special case of an MDIO device.

> 2.  The MDIO mux framework registers each child bus as MII bus. The Linux
> Ethernet MDIO framework will scan for all attached PHY devices on given
> MII bus and try to read MII PHY_ID register which is not present in all
> Broadcom non-ethernet PHYs.

It only performs a scan if you don't list the devices in the device
tree. If you do list devices, and include the address on the bus, it
never scans.

A good example of this is Ethernet switches. They occupy multiple
addresses on the MDIO bus, and also do not implement the PHY_ID
register at each address. Yet the MDIO layer is happy with this.

> 3. Let's say we ignore point1 and point2 above  and go ahead and use MDIO
> mux framework then we will still have to emulated MII PHY_ID read for
> non-ethernet PHYs.

Nope. Not at all. You have an MDIO device on the bus, not an Ethernet
PHY. Hence the device is not liked to the PHY state machine, etc.

The key concept to get here is that there are MDIO devices, and a
subset of MDIO devices are Ethernet PHYs.....

> 4. Apart from these, by using MDIO mux framework we are making our
> non-ethernet PHYs dependent on Linux network drivers which is not
> acceptable. What if some product line does not need network subsystem at
> all?

This is your only valid point. However, does Broadcom have a product
line which does not include networking? Is not Broadcom a network SoC
vendor?

> 
> As you can see from above points, trying to re-use Linux Ethernet MDIO mux
> framework for non-Ethernet PHYs is not the right way.

And as i pointed out, all your arguments are wrong, bar one. And i
doubt that one argument is sufficient to duplicate a lot of code which
already exists and does 95% of what you need.

> I'll add PCIe PHYs driver based on Shared MDIO framework in next patch
> revision to get a feel of its need.

Great. Lets then see what is needed to turn it into an MDIO device.

       Andrew
David Miller April 26, 2016, 4:26 p.m. UTC | #6
From: Andrew Lunn <andrew@lunn.ch>
Date: Tue, 26 Apr 2016 14:13:35 +0200

> On Tue, Apr 26, 2016 at 02:03:27PM +0530, Pramod Kumar wrote:
>> As you can see from above points, trying to re-use Linux Ethernet MDIO mux
>> framework for non-Ethernet PHYs is not the right way.
> 
> And as i pointed out, all your arguments are wrong, bar one. And i
> doubt that one argument is sufficient to duplicate a lot of code which
> already exists and does 95% of what you need.

+1
Florian Fainelli April 26, 2016, 5:23 p.m. UTC | #7
On 26/04/16 05:13, Andrew Lunn wrote:
>> 4. Apart from these, by using MDIO mux framework we are making our
>> non-ethernet PHYs dependent on Linux network drivers which is not
>> acceptable. What if some product line does not need network subsystem at
>> all?
> 
> This is your only valid point. However, does Broadcom have a product
> line which does not include networking? Is not Broadcom a network SoC
> vendor?

But even with that, there is no reason why we could not decouple the
PHYLIB MDIO framework from PHYLIB and make it available as a more
standalone subsystem which can be utilized when you have a mix of MDIO
devices like here.

I am not clear on how common a shared MDIO bus is on other SoCs, but the
other Broadcom SoCs I am familiar with have dedicated MDIO buses
instances per type of PHY (PCIe, BUSB, Ethernet), thus making the split
a ton easier.
Andrew Lunn April 26, 2016, 5:45 p.m. UTC | #8
> I am not clear on how common a shared MDIO bus is on other SoCs, but the
> other Broadcom SoCs I am familiar with have dedicated MDIO buses
> instances per type of PHY (PCIe, BUSB, Ethernet), thus making the split
> a ton easier.

I don't actually see this shared bus being an issue, once the mux it
implemented. With the mux, you see N MDIO busses, each acting like a
normal MDIO bus. I've used the GPIO variety and had no issues:

http://marc.info/?l=linux-arm-kernel&m=145910090401796&w=2

	Andrew
Arnd Bergmann April 26, 2016, 5:53 p.m. UTC | #9
On Tuesday 26 April 2016 10:23:02 Florian Fainelli wrote:
> On 26/04/16 05:13, Andrew Lunn wrote:
> >> 4. Apart from these, by using MDIO mux framework we are making our
> >> non-ethernet PHYs dependent on Linux network drivers which is not
> >> acceptable. What if some product line does not need network subsystem at
> >> all?
> > 
> > This is your only valid point. However, does Broadcom have a product
> > line which does not include networking? Is not Broadcom a network SoC
> > vendor?
> 
> But even with that, there is no reason why we could not decouple the
> PHYLIB MDIO framework from PHYLIB and make it available as a more
> standalone subsystem which can be utilized when you have a mix of MDIO
> devices like here.

[adding Kishon Vijay Abraham]

We should also consider how this fits in with drivers/phy/, which is
the generic framework for all PHY devices that are not for ethernet.

The most straightforward way that you mention would be to allow
generic PHY devices to be probed on an MIO bus or mux. This should
just work using mdio_module_driver(), as Andrew already explained.

A more complex problem would be having a PHY driver for a device
that can be either an ethernet phy or some other phy. With today's
frameworks that would require two separate drivers, one in drivers/phy
and one in drivers/net/phy/. If that turns out to be a common problem,
we might want to come up with a way to nest one on top of the other,
or merge two two device structures (struct phy_device and struct phy).

> I am not clear on how common a shared MDIO bus is on other SoCs, but the
> other Broadcom SoCs I am familiar with have dedicated MDIO buses
> instances per type of PHY (PCIe, BUSB, Ethernet), thus making the split
> a ton easier.

I think most commonly, the other PHYs are not on MDIO at all, but are
integrated inside of the SoC as an MMIO based device.

	Arnd
Andrew Lunn April 26, 2016, 6:23 p.m. UTC | #10
> A more complex problem would be having a PHY driver for a device
> that can be either an ethernet phy or some other phy.

I doubt that ever happens. You can have up to 32 different devices on
an MDIO bus. Since an Ethernet PHY and a "some other sort of PHY" are
completely different things, why would a hardware engineer place them
on the same address? It is like saying your ATA controller and VGA
controller share the same slot on the PCI bus...

   Andrew
Arnd Bergmann April 26, 2016, 7:24 p.m. UTC | #11
On Tuesday 26 April 2016 20:23:35 Andrew Lunn wrote:
> > A more complex problem would be having a PHY driver for a device
> > that can be either an ethernet phy or some other phy.
> 
> I doubt that ever happens. You can have up to 32 different devices on
> an MDIO bus. Since an Ethernet PHY and a "some other sort of PHY" are
> completely different things, why would a hardware engineer place them
> on the same address? It is like saying your ATA controller and VGA
> controller share the same slot on the PCI bus...

To clarify: what I meant is a device that is designed as a PHY for
similar hardware (e.g. SATA, USB3 and PCIe) and that has a common
register set and a single driver, but that driver can operate
in multiple modes. You typically have multiple instances of
such hardware, with each instance linked to exactly one host
device, but one driver for all of them.

See Documentation/devicetree/bindings/phy/apm-xgene-phy.txt
and drivers/phy/phy-xgene.c for one such example.

	Arnd
Andrew Lunn April 26, 2016, 7:41 p.m. UTC | #12
On Tue, Apr 26, 2016 at 09:24:34PM +0200, Arnd Bergmann wrote:
> On Tuesday 26 April 2016 20:23:35 Andrew Lunn wrote:
> > > A more complex problem would be having a PHY driver for a device
> > > that can be either an ethernet phy or some other phy.
> > 
> > I doubt that ever happens. You can have up to 32 different devices on
> > an MDIO bus. Since an Ethernet PHY and a "some other sort of PHY" are
> > completely different things, why would a hardware engineer place them
> > on the same address? It is like saying your ATA controller and VGA
> > controller share the same slot on the PCI bus...
> 
> To clarify: what I meant is a device that is designed as a PHY for
> similar hardware (e.g. SATA, USB3 and PCIe) and that has a common
> register set and a single driver, but that driver can operate
> in multiple modes. You typically have multiple instances of
> such hardware, with each instance linked to exactly one host
> device, but one driver for all of them.
> 
> See Documentation/devicetree/bindings/phy/apm-xgene-phy.txt
> and drivers/phy/phy-xgene.c for one such example.

Interesting. Also, that this lists SGMII. I assume this is a phy in
the MAC in order to talk to the Ethernet PHY.

I still don't see it being a big problem if a phy driver implements an
Ethernet PHY. It just needs to call phy_device_create() and
phy_device_register().

	Andrew
Anup Patel April 27, 2016, 4:46 a.m. UTC | #13
On Wed, Apr 27, 2016 at 1:11 AM, Andrew Lunn <andrew@lunn.ch> wrote:
> On Tue, Apr 26, 2016 at 09:24:34PM +0200, Arnd Bergmann wrote:
>> On Tuesday 26 April 2016 20:23:35 Andrew Lunn wrote:
>> > > A more complex problem would be having a PHY driver for a device
>> > > that can be either an ethernet phy or some other phy.
>> >
>> > I doubt that ever happens. You can have up to 32 different devices on
>> > an MDIO bus. Since an Ethernet PHY and a "some other sort of PHY" are
>> > completely different things, why would a hardware engineer place them
>> > on the same address? It is like saying your ATA controller and VGA
>> > controller share the same slot on the PCI bus...
>>
>> To clarify: what I meant is a device that is designed as a PHY for
>> similar hardware (e.g. SATA, USB3 and PCIe) and that has a common
>> register set and a single driver, but that driver can operate
>> in multiple modes. You typically have multiple instances of
>> such hardware, with each instance linked to exactly one host
>> device, but one driver for all of them.
>>
>> See Documentation/devicetree/bindings/phy/apm-xgene-phy.txt
>> and drivers/phy/phy-xgene.c for one such example.
>
> Interesting. Also, that this lists SGMII. I assume this is a phy in
> the MAC in order to talk to the Ethernet PHY.
>
> I still don't see it being a big problem if a phy driver implements an
> Ethernet PHY. It just needs to call phy_device_create() and
> phy_device_register().
>
>         Andrew

It is really interesting to see the evolution of MDIO bus:

1. Traditionally, MDIO controller used to be part of each ethernet controller
itself so each ethernet controller used to have it's own 2 wire MDIO bus

2. Next, we saw SoC with multiple ethernet controllers sharing same MDIO
bus. In other words, we saw multiple MDIO bus being muxed over single
MDIO bus with additional bus select lines (I think this is when
drivers/net/phy/mdio-mux.c APIs were implemented but at this point all
PHYs on muxed MDIO bus were ethernet PHYs).

3. Then, we saw SoC with ethernet switch devices also accessible over
shared MDIO bus (or Muxed MDIO bus) along with ethernet PHYs (I guess
this is why we have drivers/net/phy/mdio_device.c which adds
"mdio_device" for non-ethernet-PHY devices on MDIO bus).

4. Now, we have SoC with SATA PHYs, PCIe PHYs, USB PHYs, and Ethernet
PHYs all accessible over same shared MDIO bus (or Muxed MDIO bus). The
SATA PHYs and PCIe PHYs are registered to "Generic PHY framework". For
USB PHYs, we can either register to "Generic PHY framework" or "USB PHY
framework". For Ethernet PHYs, we register MDIO bus instance to "Ethernet
MDIO framework".

The devices on ARM64 SoC has to be within first 4GB and RAM has to start
from first 4GB to be ARM compliant because ARM64 CPUs have 32bit mode and
all devices and RAM should be available to 32bit mode with MMU disabled.
This means that we only have 4GB to fit all devices registers and some
portion of RAM. Some of these non-Ethernet PHYs have tons of registers so
as number of PHYs increase in a SoC they will eat-up lot of first 4GB
address space. Using same MDIO bus for all types of PHYs (SATA, PCIe, USB,
and Ethernet) is actually a good approach because it actually saves lot of
first 4GB address space. In future, more devices will be moved to a shared
MDIO bus which are less frequently accessed.

For Broadcom iProc SoCs, the design choice has already been made to use
shared MDIO bus for all PHYs. In fact, Broadcom iProc NS2 SoC already has
a shared MDIO bus for SATA PHYs, USB PHYs, PCIe PHYs, and Ethernet
PHYs and more Broadcom iProc SoCs are on their way. Of course, there are
few exceptions in iProc SoCs such as SATA PHYs where we also have memory
mapped registers to access PHYs but other PHYs don't have such memory
mapped registers.

Clearly from above, the traditional 2 wire MDIO bus is now a shared MDIO
bus with 2-wire plus additional select lines. Also, now we have SoCs (such
as Broadcom iProc SoCs) which has such shared MDIO bus and I think
in-future we will have SoCs with a shared MDIO bus for variety of devices.

For long term, we really need a clean solution to fit shared MDIO based
PHY drivers in Linux kernel. Also, shared MDIO based PHY drivers should
not be dependent on any particular IO subsystem (such as Linux Ethernet)
because there are lot of use-cases where people want strip down kernel
image by not-compiling IO subsystems which are not required.

IMHO, we have several options in front of us:
1. Use some light-weight framework (such as shared_mdio.c implemented
by this patchset) under drivers/bus
2. Extend "Generic PHY framework" to allow something like shared MDIO
bus (as-per Arnd's suggestion)
3. Move-out "MDIO-mux APIs" from drivers/net/phy to something like
drivers/mdio-mux and make it independent of "Linux Ethernet subsystem".
(... may be more options ...)

Regards,
Anup
diff mbox

Patch

diff --git a/drivers/bus/Kconfig b/drivers/bus/Kconfig
index d4a3a31..1b51b1a 100644
--- a/drivers/bus/Kconfig
+++ b/drivers/bus/Kconfig
@@ -107,6 +107,17 @@  config OMAP_OCP2SCP
 	  OCP2SCP and in OMAP5, both USB PHY and SATA PHY is connected via
 	  OCP2SCP.
 
+config SHARED_MDIO
+	bool "Shared MDIO bus"
+	depends on OF
+
+	help
+	  Shared MDIO bus implementation for SoCs having common MDIO bus
+	  for different IO subsystems. All masters should be registered to
+	  this bus via a platform driver using shared framework API.
+	  Respective drivers would be called matching a compatible string
+	  provided in master node.
+
 config SIMPLE_PM_BUS
 	bool "Simple Power-Managed Bus Driver"
 	depends on OF && PM
diff --git a/drivers/bus/Makefile b/drivers/bus/Makefile
index ccff007..2b6d6e9 100644
--- a/drivers/bus/Makefile
+++ b/drivers/bus/Makefile
@@ -19,3 +19,4 @@  obj-$(CONFIG_SUNXI_RSB)		+= sunxi-rsb.o
 obj-$(CONFIG_SIMPLE_PM_BUS)	+= simple-pm-bus.o
 obj-$(CONFIG_UNIPHIER_SYSTEM_BUS)	+= uniphier-system-bus.o
 obj-$(CONFIG_VEXPRESS_CONFIG)	+= vexpress-config.o
+obj-$(CONFIG_SHARED_MDIO)	+= shared_mdio.o
diff --git a/drivers/bus/shared_mdio.c b/drivers/bus/shared_mdio.c
new file mode 100644
index 0000000..909af73
--- /dev/null
+++ b/drivers/bus/shared_mdio.c
@@ -0,0 +1,179 @@ 
+/*
+ * Copyright (C) 2016 Broadcom
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation version 2.
+ *
+ * This program is distributed "as is" WITHOUT ANY WARRANTY of any
+ * kind, whether express or implied; without even the implied warranty
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * Shared MDIO Bus
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/errno.h>
+#include <linux/idr.h>
+#include <linux/of_device.h>
+#include <linux/stddef.h>
+#include <linux/module.h>
+#include <linux/shared_mdio.h>
+#include <linux/slab.h>
+
+static DEFINE_IDA(shared_mdio_ida);
+
+static void shared_mdio_release_master(struct device *dev)
+{
+	struct shared_mdio_master *master = to_shared_mdio_master(dev);
+
+	ida_simple_remove(&shared_mdio_ida, master->dev_num);
+	kfree(master);
+}
+
+struct shared_mdio_master *shared_mdio_alloc_master(struct device *parent,
+						    struct device_node *node)
+{
+	int ret = 0;
+	struct shared_mdio_master *master;
+
+	master = kzalloc(sizeof(*master), GFP_KERNEL);
+	if (!master) {
+		ret = -ENOMEM;
+		goto fail1;
+	}
+
+	master->dev.parent = parent;
+	master->dev.bus = &shared_mdio_bus;
+	master->dev.release = shared_mdio_release_master;
+
+	device_initialize(&master->dev);
+
+	ret = ida_simple_get(&shared_mdio_ida, 0, 0, GFP_KERNEL);
+	if (ret < 0)
+		goto fail2;
+	master->dev_num = ret;
+
+	dev_set_name(&master->dev, "shared-mdio-master%d", master->dev_num);
+
+	of_node_get(node);
+	master->dev.of_node = node;
+
+	return master;
+
+fail2:
+	kfree(master);
+fail1:
+	return ERR_PTR(ret);
+}
+EXPORT_SYMBOL(shared_mdio_alloc_master);
+
+int shared_mdio_add_master(struct shared_mdio_master *master)
+{
+	if (!master)
+		return -EINVAL;
+
+	return device_add(&master->dev);
+}
+EXPORT_SYMBOL(shared_mdio_add_master);
+
+static int shared_mdio_driver_probe(struct device *dev)
+{
+	int rc;
+	struct shared_mdio_master *master = to_shared_mdio_master(dev);
+	struct shared_mdio_driver *drv = to_shared_mdio_driver(dev->driver);
+
+	if (!drv->probe)
+		return -ENODEV;
+
+	rc = drv->probe(master);
+	if (rc)
+		return rc;
+
+	return 0;
+}
+
+static int shared_mdio_driver_remove(struct device *dev)
+{
+	struct shared_mdio_driver *drv = to_shared_mdio_driver(dev->driver);
+
+	if (drv->remove)
+		return drv->remove(to_shared_mdio_master(dev));
+
+	return 0;
+}
+
+static void shared_mdio_driver_shutdown(struct device *dev)
+{
+	struct shared_mdio_driver *drv = to_shared_mdio_driver(dev->driver);
+
+	if (drv->shutdown)
+		drv->shutdown(to_shared_mdio_master(dev));
+}
+
+int __shared_mdio_register_driver(struct shared_mdio_driver *drv,
+				  struct module *owner)
+{
+	int rc;
+
+	drv->driver.bus = &shared_mdio_bus;
+	drv->driver.owner = owner;
+	drv->driver.probe = shared_mdio_driver_probe;
+	drv->driver.remove = shared_mdio_driver_remove;
+	drv->driver.shutdown = shared_mdio_driver_shutdown;
+
+	rc = driver_register(&drv->driver);
+	if (rc) {
+		pr_err("driver_register() failed for %s, error: %d\n",
+			drv->driver.name, rc);
+		return rc;
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL(__shared_mdio_register_driver);
+
+static int shared_mdio_bus_match(struct device *dev, struct device_driver *drv)
+{
+	/* Attempt an OF style match */
+	if (of_driver_match_device(dev, drv))
+		return 1;
+
+	return 0;
+}
+
+struct bus_type shared_mdio_bus = {
+	.name	= "shared_mdio",
+	.match	= shared_mdio_bus_match,
+};
+EXPORT_SYMBOL(shared_mdio_bus);
+
+static int __init shared_mdio_init(void)
+{
+	int rc;
+
+	rc = bus_register(&shared_mdio_bus);
+	if (rc) {
+		pr_err("Failed to register shared_mdio bus, error: %d\n", rc);
+		return rc;
+	}
+
+	return 0;
+}
+
+static void __exit shared_mdio_exit(void)
+{
+	bus_unregister(&shared_mdio_bus);
+
+	ida_destroy(&shared_mdio_ida);
+}
+
+subsys_initcall(shared_mdio_init);
+module_exit(shared_mdio_exit);
+
+MODULE_DESCRIPTION("Shared MDIO Bus");
+MODULE_AUTHOR("Anup Patel <anup.patel@broadcom.com>");
+MODULE_AUTHOR("Pramod Kumar <pramod.kumar@broadcom.com>");
+MODULE_LICENSE("GPL v2");
diff --git a/include/linux/shared_mdio.h b/include/linux/shared_mdio.h
new file mode 100644
index 0000000..db6e442
--- /dev/null
+++ b/include/linux/shared_mdio.h
@@ -0,0 +1,123 @@ 
+/*
+ * Copyright (C) 2016 Broadcom
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation version 2.
+ *
+ * This program is distributed "as is" WITHOUT ANY WARRANTY of any
+ * kind, whether express or implied; without even the implied warranty
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef __SHARED_MDIO_H__
+#define __SHARED_MDIO_H__
+
+#include <linux/device.h>
+#include <linux/types.h>
+
+extern struct bus_type shared_mdio_bus;
+
+/*
+ * This structure represets the mdio master that control the MDIO bus
+ * to access the phy attached on it.
+ * @dev Underlying device for mdio master
+ * @dev_num Unique device number of mdio master
+ * @mdio_write Write callback of mdio master
+ * @mdio_read Read callback for mdio master
+ */
+struct shared_mdio_master {
+	struct device dev;
+	int dev_num;
+	void *priv;
+
+	int (*mdio_write)(struct shared_mdio_master *master,
+				u16 phyid, u16 reg, u16 data);
+	int (*mdio_read)(struct shared_mdio_master *master, u16 phyid, u16 reg);
+};
+#define to_shared_mdio_master(d)		\
+			container_of(d, struct shared_mdio_master, dev)
+
+struct shared_mdio_driver {
+	int (*probe)(struct shared_mdio_master *master);
+	int (*remove)(struct shared_mdio_master *master);
+	void (*shutdown)(struct shared_mdio_master *master);
+
+	struct device_driver driver;
+};
+#define to_shared_mdio_driver(d)	\
+			container_of(d, struct shared_mdio_driver, driver)
+
+struct shared_mdio_master *shared_mdio_alloc_master(struct device *parent,
+						    struct device_node *node);
+
+int shared_mdio_add_master(struct shared_mdio_master *master);
+
+static inline void shared_mdio_remove_master(struct shared_mdio_master *master)
+{
+		device_unregister(&master->dev);
+}
+
+int __shared_mdio_register_driver(struct shared_mdio_driver *drv,
+				  struct module *owner);
+
+/* use a define to avoid include chaining to get THIS_MODULE & friends */
+#define shared_mdio_register_driver(drv) \
+	__shared_mdio_register_driver(drv, THIS_MODULE)
+
+static inline void shared_mdio_unregister_driver(
+					struct shared_mdio_driver *drv)
+{
+		driver_unregister(&drv->driver);
+}
+
+/**
+ * module_shared_mdio_driver() - Helper macro for registering a shared_mdio
+ * driver
+ * @__shared_mdio_driver: shared_mdio_driver struct
+ *
+ * Helper macro for shared mdio drivers which do not do anything special in
+ * module init/exit. This eliminates a lot of boilerplate. Each module
+ * may only use this macro once, and calling it replaces module_init()
+ * and module_exit().
+ */
+#define module_shared_mdio_driver(__shared_mdio_driver) \
+	module_driver(__shared_mdio_driver, shared_mdio_register_driver, \
+		       shared_mdio_unregister_driver)
+
+
+static inline int shared_mdio_write(struct shared_mdio_master *master,
+						u16 phy, u16 reg, u16 data)
+{
+	if (master->mdio_write)
+		return master->mdio_write(master, phy, reg, data);
+
+	return -ENOTSUPP;
+}
+
+static inline int shared_mdio_read(struct shared_mdio_master *master,
+							u16 phy_id, u16 reg)
+{
+	if (master->mdio_read)
+		return master->mdio_read(master, phy_id, reg);
+
+	return -ENOTSUPP;
+}
+
+/*
+ * Use the following functions to manipulate shared_mdio's per-master
+ * driver-specific data.
+ */
+static inline void *shared_mdio_get_drvdata(struct shared_mdio_master *master)
+{
+	return dev_get_drvdata(&master->dev);
+}
+
+static inline void shared_mdio_set_drvdata(struct shared_mdio_master *master,
+					   void *data)
+{
+	dev_set_drvdata(&master->dev, data);
+}
+
+#endif