diff mbox series

[net-next,v14,07/16] net: mdio: regmap: add support for C45 read/write

Message ID 20250408095139.51659-8-ansuelsmth@gmail.com (mailing list archive)
State New
Headers show
Series net: dsa: Add Airoha AN8855 support | expand

Commit Message

Christian Marangi April 8, 2025, 9:51 a.m. UTC
Add support for C45 read/write for mdio regmap. This can be done
by enabling the support_encoded_addr bool in mdio regmap config and by
using the new API devm_mdio_regmap_init to init a regmap.

To support C45, additional info needs to be appended to the regmap
address passed to regmap OPs.

The logic applied to the regmap address value:
- First the regnum value (20, 16)
- Second the devnum value (25, 21)
- A bit to signal if it's C45 (26)

devm_mdio_regmap_init MUST be used to register a regmap for this to
correctly handle internally the encode/decode of the address.

Drivers needs to define a mdio_regmap_init_config where an optional regmap
name can be defined and MUST define C22 OPs (mdio_read/write).
To support C45 operation also C45 OPs (mdio_read/write_c45).

The regmap from devm_mdio_regmap_init will internally decode the encoded
regmap address and extract the various info (addr, devnum if C45 and
regnum). It will then call the related OP and pass the extracted values to
the function.

Example for a C45 read operation:
- With an encoded address with C45 bit enabled, it will call the
  .mdio_read_c45 and addr, devnum and regnum will be passed.
  .mdio_read_c45 will then return the val and val will be stored in the
  regmap_read pointer and will return 0. If .mdio_read_c45 returns
  any error, then the regmap_read will return such error.

With support_encoded_addr enabled, also C22 will encode the address in
the regmap address and .mdio_read/write will called accordingly similar
to C45 operation.

Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
---
 drivers/net/mdio/mdio-regmap.c   | 170 +++++++++++++++++++++++++++++--
 include/linux/mdio/mdio-regmap.h |  14 +++
 2 files changed, 176 insertions(+), 8 deletions(-)

Comments

Maxime Chevallier April 9, 2025, 7:07 a.m. UTC | #1
Hi Christian,

On Tue,  8 Apr 2025 11:51:14 +0200
Christian Marangi <ansuelsmth@gmail.com> wrote:

> Add support for C45 read/write for mdio regmap. This can be done
> by enabling the support_encoded_addr bool in mdio regmap config and by
> using the new API devm_mdio_regmap_init to init a regmap.
> 
> To support C45, additional info needs to be appended to the regmap
> address passed to regmap OPs.
> 
> The logic applied to the regmap address value:
> - First the regnum value (20, 16)
> - Second the devnum value (25, 21)
> - A bit to signal if it's C45 (26)
> 
> devm_mdio_regmap_init MUST be used to register a regmap for this to
> correctly handle internally the encode/decode of the address.
> 
> Drivers needs to define a mdio_regmap_init_config where an optional regmap
> name can be defined and MUST define C22 OPs (mdio_read/write).
> To support C45 operation also C45 OPs (mdio_read/write_c45).
> 
> The regmap from devm_mdio_regmap_init will internally decode the encoded
> regmap address and extract the various info (addr, devnum if C45 and
> regnum). It will then call the related OP and pass the extracted values to
> the function.
> 
> Example for a C45 read operation:
> - With an encoded address with C45 bit enabled, it will call the
>   .mdio_read_c45 and addr, devnum and regnum will be passed.
>   .mdio_read_c45 will then return the val and val will be stored in the
>   regmap_read pointer and will return 0. If .mdio_read_c45 returns
>   any error, then the regmap_read will return such error.
> 
> With support_encoded_addr enabled, also C22 will encode the address in
> the regmap address and .mdio_read/write will called accordingly similar
> to C45 operation.

This driver's orginal goal is to address the case where we have a
PHY-like device that has the same register layout and behaviour as a
C22 PHY, but where the registers are not accesses through MDIO (MMIO
for example, as in altera-tse or dwmac-socfpga, or potentially SPI even
though  there's no example upstream).

What is done here is quite different, I guess it could work if we have
MMIO C45 phys that understand the proposed encoding, but I don't really
understand the dance where C45 accesses are wrapped by this mdio-regmap
driver into regmap accesss, but the regmap itself converts it back to
C45 accesses. Is it just so that it fits well with MFD ?

I'm not really against that, it still converts mdio access to regmap so
there's that, but is there a way to elaborate or document somewhere why
we need to do go through C45 -> regmap -> C45 instead of just
writing a mii_bus driver in the first place ?

As I said, I think this could work and even be re-used in other places,
so I'm ok with that, it's just not really clear from the commit log what
problem this solves.

Maxime
Christian Marangi April 9, 2025, 7:24 a.m. UTC | #2
On Wed, Apr 09, 2025 at 09:07:51AM +0200, Maxime Chevallier wrote:
> Hi Christian,
> 
> On Tue,  8 Apr 2025 11:51:14 +0200
> Christian Marangi <ansuelsmth@gmail.com> wrote:
> 
> > Add support for C45 read/write for mdio regmap. This can be done
> > by enabling the support_encoded_addr bool in mdio regmap config and by
> > using the new API devm_mdio_regmap_init to init a regmap.
> > 
> > To support C45, additional info needs to be appended to the regmap
> > address passed to regmap OPs.
> > 
> > The logic applied to the regmap address value:
> > - First the regnum value (20, 16)
> > - Second the devnum value (25, 21)
> > - A bit to signal if it's C45 (26)
> > 
> > devm_mdio_regmap_init MUST be used to register a regmap for this to
> > correctly handle internally the encode/decode of the address.
> > 
> > Drivers needs to define a mdio_regmap_init_config where an optional regmap
> > name can be defined and MUST define C22 OPs (mdio_read/write).
> > To support C45 operation also C45 OPs (mdio_read/write_c45).
> > 
> > The regmap from devm_mdio_regmap_init will internally decode the encoded
> > regmap address and extract the various info (addr, devnum if C45 and
> > regnum). It will then call the related OP and pass the extracted values to
> > the function.
> > 
> > Example for a C45 read operation:
> > - With an encoded address with C45 bit enabled, it will call the
> >   .mdio_read_c45 and addr, devnum and regnum will be passed.
> >   .mdio_read_c45 will then return the val and val will be stored in the
> >   regmap_read pointer and will return 0. If .mdio_read_c45 returns
> >   any error, then the regmap_read will return such error.
> > 
> > With support_encoded_addr enabled, also C22 will encode the address in
> > the regmap address and .mdio_read/write will called accordingly similar
> > to C45 operation.
> 
> This driver's orginal goal is to address the case where we have a
> PHY-like device that has the same register layout and behaviour as a
> C22 PHY, but where the registers are not accesses through MDIO (MMIO
> for example, as in altera-tse or dwmac-socfpga, or potentially SPI even
> though  there's no example upstream).
> 
> What is done here is quite different, I guess it could work if we have
> MMIO C45 phys that understand the proposed encoding, but I don't really
> understand the dance where C45 accesses are wrapped by this mdio-regmap
> driver into regmap accesss, but the regmap itself converts it back to
> C45 accesses. Is it just so that it fits well with MFD ?

The main task of this wrapping is to remove from the dev side having to
handle the encode/decode part. regmap address is still a single value
but if a phy is mmio mapped is difficult to support c45 since you need 3
different values (phy id, mmd and addr)

With this implementation a c45 that is mmio mapped can implement
whatever way he wants to configure each parameter for read/write
operation.

Example the ecoding might be on different mask and with the additional
function it can be reorganized following the specific mask.

> 
> I'm not really against that, it still converts mdio access to regmap so
> there's that, but is there a way to elaborate or document somewhere why
> we need to do go through C45 -> regmap -> C45 instead of just
> writing a mii_bus driver in the first place ?

This was askek to prevent creating additional ""trivial"" mdio driver
that would all do the same task. Since mdio-regmap was already in place
it could have been extended with a more generic approach.

Any hint on where to better document this?

> 
> As I said, I think this could work and even be re-used in other places,
> so I'm ok with that, it's just not really clear from the commit log what
> problem this solves.
> 
> Maxime
Maxime Chevallier April 10, 2025, 6:46 a.m. UTC | #3
On Wed, 9 Apr 2025 09:24:13 +0200
Christian Marangi <ansuelsmth@gmail.com> wrote:

> On Wed, Apr 09, 2025 at 09:07:51AM +0200, Maxime Chevallier wrote:
> > Hi Christian,
> > 
> > On Tue,  8 Apr 2025 11:51:14 +0200
> > Christian Marangi <ansuelsmth@gmail.com> wrote:
> >   
> > > Add support for C45 read/write for mdio regmap. This can be done
> > > by enabling the support_encoded_addr bool in mdio regmap config and by
> > > using the new API devm_mdio_regmap_init to init a regmap.
> > > 
> > > To support C45, additional info needs to be appended to the regmap
> > > address passed to regmap OPs.
> > > 
> > > The logic applied to the regmap address value:
> > > - First the regnum value (20, 16)
> > > - Second the devnum value (25, 21)
> > > - A bit to signal if it's C45 (26)
> > > 
> > > devm_mdio_regmap_init MUST be used to register a regmap for this to
> > > correctly handle internally the encode/decode of the address.
> > > 
> > > Drivers needs to define a mdio_regmap_init_config where an optional regmap
> > > name can be defined and MUST define C22 OPs (mdio_read/write).
> > > To support C45 operation also C45 OPs (mdio_read/write_c45).
> > > 
> > > The regmap from devm_mdio_regmap_init will internally decode the encoded
> > > regmap address and extract the various info (addr, devnum if C45 and
> > > regnum). It will then call the related OP and pass the extracted values to
> > > the function.
> > > 
> > > Example for a C45 read operation:
> > > - With an encoded address with C45 bit enabled, it will call the
> > >   .mdio_read_c45 and addr, devnum and regnum will be passed.
> > >   .mdio_read_c45 will then return the val and val will be stored in the
> > >   regmap_read pointer and will return 0. If .mdio_read_c45 returns
> > >   any error, then the regmap_read will return such error.
> > > 
> > > With support_encoded_addr enabled, also C22 will encode the address in
> > > the regmap address and .mdio_read/write will called accordingly similar
> > > to C45 operation.  
> > 
> > This driver's orginal goal is to address the case where we have a
> > PHY-like device that has the same register layout and behaviour as a
> > C22 PHY, but where the registers are not accesses through MDIO (MMIO
> > for example, as in altera-tse or dwmac-socfpga, or potentially SPI even
> > though  there's no example upstream).
> > 
> > What is done here is quite different, I guess it could work if we have
> > MMIO C45 phys that understand the proposed encoding, but I don't really
> > understand the dance where C45 accesses are wrapped by this mdio-regmap
> > driver into regmap accesss, but the regmap itself converts it back to
> > C45 accesses. Is it just so that it fits well with MFD ?  
> 
> The main task of this wrapping is to remove from the dev side having to
> handle the encode/decode part. regmap address is still a single value
> but if a phy is mmio mapped is difficult to support c45 since you need 3
> different values (phy id, mmd and addr)
> 
> With this implementation a c45 that is mmio mapped can implement
> whatever way he wants to configure each parameter for read/write
> operation.
> 
> Example the ecoding might be on different mask and with the additional
> function it can be reorganized following the specific mask.
> 
> > 
> > I'm not really against that, it still converts mdio access to regmap so
> > there's that, but is there a way to elaborate or document somewhere why
> > we need to do go through C45 -> regmap -> C45 instead of just
> > writing a mii_bus driver in the first place ?  
> 
> This was askek to prevent creating additional ""trivial"" mdio driver
> that would all do the same task. Since mdio-regmap was already in place
> it could have been extended with a more generic approach.

Ah yes that's the point I was missing, I've browsed more in depth and
indeed in V11 Vlad suggested to use this.

> Any hint on where to better document this?

Given the simplicity of the driver, I think this commit log is good
enough then :)

Let's go for it and hopefully this can be reused elsewhere !

Reviewed-by: Maxime Chevallier <maxime.chevallier@bootlin.com>

Maxime
Andrew Lunn April 10, 2025, 5:08 p.m. UTC | #4
On Tue, Apr 08, 2025 at 11:51:14AM +0200, Christian Marangi wrote:
> Add support for C45 read/write for mdio regmap. This can be done
> by enabling the support_encoded_addr bool in mdio regmap config and by
> using the new API devm_mdio_regmap_init to init a regmap.
> 
> To support C45, additional info needs to be appended to the regmap
> address passed to regmap OPs.
> 
> The logic applied to the regmap address value:
> - First the regnum value (20, 16)
> - Second the devnum value (25, 21)
> - A bit to signal if it's C45 (26)
> 
> devm_mdio_regmap_init MUST be used to register a regmap for this to
> correctly handle internally the encode/decode of the address.
> 
> Drivers needs to define a mdio_regmap_init_config where an optional regmap
> name can be defined and MUST define C22 OPs (mdio_read/write).
> To support C45 operation also C45 OPs (mdio_read/write_c45).
> 
> The regmap from devm_mdio_regmap_init will internally decode the encoded
> regmap address and extract the various info (addr, devnum if C45 and
> regnum). It will then call the related OP and pass the extracted values to
> the function.
> 
> Example for a C45 read operation:
> - With an encoded address with C45 bit enabled, it will call the
>   .mdio_read_c45 and addr, devnum and regnum will be passed.
>   .mdio_read_c45 will then return the val and val will be stored in the
>   regmap_read pointer and will return 0. If .mdio_read_c45 returns
>   any error, then the regmap_read will return such error.
> 
> With support_encoded_addr enabled, also C22 will encode the address in
> the regmap address and .mdio_read/write will called accordingly similar
> to C45 operation.

This patchset needs pulling apart, there are two many things going on.

You are adding at least two different features here. The current code
only supports a single device on the bus, and it assumes the regmap
provider knows what device that is. That is probably because all
current users only have a single device. You now appear to want to
pass that address to the regmap provider. I don't see the need for
that, since it is still a single device on the bus. So adding this
feature on its own, with a good commit message, will explain that.

You want to add C45 support. So that is another patch.

C22 and C45 are different address spaces. To me, it seems logical to
have different regmaps. That makes the regmap provider simpler. A C22
regmap provider probably is just a straight access. A C45 regmap
provider might need to handle the hardware having a sparse register
map, only some of these 32 block of 65536 are implemented, etc.

So i think:

struct mdio_regmap_config {
        struct device *parent;
        struct regmap *regmap;
        char name[MII_BUS_ID_SIZE];
        u8 valid_addr;
        bool autoscan;
};

should be extended with a second regmap, used for C45.

	Andrew
Christian Marangi April 10, 2025, 5:40 p.m. UTC | #5
On Thu, Apr 10, 2025 at 07:08:53PM +0200, Andrew Lunn wrote:
> On Tue, Apr 08, 2025 at 11:51:14AM +0200, Christian Marangi wrote:
> > Add support for C45 read/write for mdio regmap. This can be done
> > by enabling the support_encoded_addr bool in mdio regmap config and by
> > using the new API devm_mdio_regmap_init to init a regmap.
> > 
> > To support C45, additional info needs to be appended to the regmap
> > address passed to regmap OPs.
> > 
> > The logic applied to the regmap address value:
> > - First the regnum value (20, 16)
> > - Second the devnum value (25, 21)
> > - A bit to signal if it's C45 (26)
> > 
> > devm_mdio_regmap_init MUST be used to register a regmap for this to
> > correctly handle internally the encode/decode of the address.
> > 
> > Drivers needs to define a mdio_regmap_init_config where an optional regmap
> > name can be defined and MUST define C22 OPs (mdio_read/write).
> > To support C45 operation also C45 OPs (mdio_read/write_c45).
> > 
> > The regmap from devm_mdio_regmap_init will internally decode the encoded
> > regmap address and extract the various info (addr, devnum if C45 and
> > regnum). It will then call the related OP and pass the extracted values to
> > the function.
> > 
> > Example for a C45 read operation:
> > - With an encoded address with C45 bit enabled, it will call the
> >   .mdio_read_c45 and addr, devnum and regnum will be passed.
> >   .mdio_read_c45 will then return the val and val will be stored in the
> >   regmap_read pointer and will return 0. If .mdio_read_c45 returns
> >   any error, then the regmap_read will return such error.
> > 
> > With support_encoded_addr enabled, also C22 will encode the address in
> > the regmap address and .mdio_read/write will called accordingly similar
> > to C45 operation.
> 
> This patchset needs pulling apart, there are two many things going on.
> 
> You are adding at least two different features here. The current code
> only supports a single device on the bus, and it assumes the regmap
> provider knows what device that is. That is probably because all
> current users only have a single device. You now appear to want to
> pass that address to the regmap provider. I don't see the need for
> that, since it is still a single device on the bus. So adding this
> feature on its own, with a good commit message, will explain that.
>

Thing is that for C45 some kind of encoding/decoding is needed anyway
and with the suggested encoding (in previous patches) also C22 needs
special handling to extract the right address.

> You want to add C45 support. So that is another patch.
> 

I decided to implement C45 first as it would indirectly add support for
multiple register as for C45 you need to encode the PHY address anyway
(even if it's always the same) (making the next patch trivial as
everything will be already in place and just need to enable it by
passing a valid_addr_mask)

> C22 and C45 are different address spaces. To me, it seems logical to
> have different regmaps. That makes the regmap provider simpler. A C22
> regmap provider probably is just a straight access. A C45 regmap
> provider might need to handle the hardware having a sparse register
> map, only some of these 32 block of 65536 are implemented, etc.
> 
> So i think:
> 
> struct mdio_regmap_config {
>         struct device *parent;
>         struct regmap *regmap;
>         char name[MII_BUS_ID_SIZE];
>         u8 valid_addr;
>         bool autoscan;
> };
> 
> should be extended with a second regmap, used for C45.

So you are suggesting 2 regmap with dedicated read/write function.

The thing is that if the final target is to permit this driver to
support multiple PHY from a single regmap, and we also want to apply the
same encoding format, the regmap max_register will be the same for the 2
regmap making it redundant to have 2.

I think this is the blocking part that unlocks everything else.
Understand what is the preferable way to handle multiple PHY.

For C45 encoding is a MUST, and with encoding you get the side
effect/bonus feature that you can inject more info.

Hope you can give some guidance about this! Happy to split this once we
find a common point on how to proceed with this.
Andrew Lunn April 10, 2025, 8:56 p.m. UTC | #6
> Hope you can give some guidance about this! Happy to split this once we
> find a common point on how to proceed with this.

One thing i'm failing to understand is, why use a regmap at all. For a
single C22 device it make sense. 32 linear registers, nice and
simple. They could be memory mapped, I2C addresses, SPI addresses,
etc. The regmap implementer probably just adds a constant offset and
does a hardware access.

Multiple C22 devices gets us into a two dimensional problem. Multiple
C45 devices gives us a three dimensional problem. Mixing multiple C22
and C45 gets us a four dimensional problem. This is a long way from
regmaps nice simple model of linear registers.

What does regmap bring here?

	Andrew
diff mbox series

Patch

diff --git a/drivers/net/mdio/mdio-regmap.c b/drivers/net/mdio/mdio-regmap.c
index 810ba0a736f0..f263e4ae2477 100644
--- a/drivers/net/mdio/mdio-regmap.c
+++ b/drivers/net/mdio/mdio-regmap.c
@@ -15,22 +15,72 @@ 
 #include <linux/regmap.h>
 #include <linux/mdio/mdio-regmap.h>
 
+#define MDIO_REGMAP_C45			BIT(26)
+#define MDIO_REGMAP_ADDR		GENMASK(25, 21)
+#define MDIO_REGMAP_DEVNUM		GENMASK(20, 16)
+#define MDIO_REGMAP_REGNUM		GENMASK(15, 0)
+
 #define DRV_NAME "mdio-regmap"
 
 struct mdio_regmap_priv {
+	void *ctx;
+
+	const struct mdio_regmap_init_config *config;
+};
+
+struct mdio_regmap_mii_priv {
 	struct regmap *regmap;
 	u32 valid_addr_mask;
+	bool encode_addr;
 };
 
-static int mdio_regmap_read_c22(struct mii_bus *bus, int addr, int regnum)
+static int mdio_regmap_mii_read_c22(struct mii_bus *bus, int addr, int regnum)
+{
+	struct mdio_regmap_mii_priv *ctx = bus->priv;
+	unsigned int val;
+	int ret;
+
+	if (!(ctx->valid_addr_mask & BIT(addr)))
+		return -ENODEV;
+
+	if (ctx->encode_addr)
+		regnum |= FIELD_PREP(MDIO_REGMAP_ADDR, addr);
+
+	ret = regmap_read(ctx->regmap, regnum, &val);
+	if (ret < 0)
+		return ret;
+
+	return val;
+}
+
+static int mdio_regmap_mii_write_c22(struct mii_bus *bus, int addr, int regnum,
+				     u16 val)
 {
-	struct mdio_regmap_priv *ctx = bus->priv;
+	struct mdio_regmap_mii_priv *ctx = bus->priv;
+
+	if (!(ctx->valid_addr_mask & BIT(addr)))
+		return -ENODEV;
+
+	if (ctx->encode_addr)
+		regnum |= FIELD_PREP(MDIO_REGMAP_ADDR, addr);
+
+	return regmap_write(ctx->regmap, regnum, val);
+}
+
+static int mdio_regmap_mii_read_c45(struct mii_bus *bus, int addr, int devnum,
+				    int regnum)
+{
+	struct mdio_regmap_mii_priv *ctx = bus->priv;
 	unsigned int val;
 	int ret;
 
 	if (!(ctx->valid_addr_mask & BIT(addr)))
 		return -ENODEV;
 
+	regnum |= MDIO_REGMAP_C45;
+	regnum |= FIELD_PREP(MDIO_REGMAP_ADDR, addr);
+	regnum |= FIELD_PREP(MDIO_REGMAP_DEVNUM, devnum);
+
 	ret = regmap_read(ctx->regmap, regnum, &val);
 	if (ret < 0)
 		return ret;
@@ -38,21 +88,25 @@  static int mdio_regmap_read_c22(struct mii_bus *bus, int addr, int regnum)
 	return val;
 }
 
-static int mdio_regmap_write_c22(struct mii_bus *bus, int addr, int regnum,
-				 u16 val)
+static int mdio_regmap_mii_write_c45(struct mii_bus *bus, int addr, int devnum,
+				     int regnum, u16 val)
 {
-	struct mdio_regmap_priv *ctx = bus->priv;
+	struct mdio_regmap_mii_priv *ctx = bus->priv;
 
 	if (!(ctx->valid_addr_mask & BIT(addr)))
 		return -ENODEV;
 
+	regnum |= MDIO_REGMAP_C45;
+	regnum |= FIELD_PREP(MDIO_REGMAP_ADDR, addr);
+	regnum |= FIELD_PREP(MDIO_REGMAP_DEVNUM, devnum);
+
 	return regmap_write(ctx->regmap, regnum, val);
 }
 
 struct mii_bus *devm_mdio_regmap_register(struct device *dev,
 					  const struct mdio_regmap_config *config)
 {
-	struct mdio_regmap_priv *mr;
+	struct mdio_regmap_mii_priv *mr;
 	struct mii_bus *mii;
 	int rc;
 
@@ -66,12 +120,17 @@  struct mii_bus *devm_mdio_regmap_register(struct device *dev,
 	mr = mii->priv;
 	mr->regmap = config->regmap;
 	mr->valid_addr_mask = BIT(config->valid_addr);
+	mr->encode_addr = config->support_encoded_addr;
 
 	mii->name = DRV_NAME;
 	strscpy(mii->id, config->name, MII_BUS_ID_SIZE);
 	mii->parent = config->parent;
-	mii->read = mdio_regmap_read_c22;
-	mii->write = mdio_regmap_write_c22;
+	mii->read = mdio_regmap_mii_read_c22;
+	mii->write = mdio_regmap_mii_write_c22;
+	if (config->support_encoded_addr) {
+		mii->read_c45 = mdio_regmap_mii_read_c45;
+		mii->write_c45 = mdio_regmap_mii_write_c45;
+	}
 
 	if (config->autoscan)
 		mii->phy_mask = ~mr->valid_addr_mask;
@@ -88,6 +147,101 @@  struct mii_bus *devm_mdio_regmap_register(struct device *dev,
 }
 EXPORT_SYMBOL_GPL(devm_mdio_regmap_register);
 
+static int mdio_regmap_reg_read(void *context, unsigned int reg, unsigned int *val)
+{
+	const struct mdio_regmap_init_config *config;
+	struct mdio_regmap_priv *priv = context;
+	int addr, regnum;
+	int ret;
+
+	config = priv->config;
+
+	addr = FIELD_GET(MDIO_REGMAP_ADDR, reg);
+	regnum = FIELD_GET(MDIO_REGMAP_REGNUM, reg);
+
+	if (reg & MDIO_REGMAP_C45) {
+		int devnum;
+
+		if (!config->mdio_write_c45)
+			return -EOPNOTSUPP;
+
+		devnum = FIELD_GET(MDIO_REGMAP_DEVNUM, reg);
+		ret = config->mdio_read_c45(priv->ctx, addr, devnum, regnum);
+	} else {
+		ret = config->mdio_read(priv->ctx, addr, regnum);
+	}
+
+	if (ret < 0)
+		return ret;
+
+	*val = ret;
+	return 0;
+}
+
+static int mdio_regmap_reg_write(void *context, unsigned int reg, unsigned int val)
+{
+	const struct mdio_regmap_init_config *config;
+	struct mdio_regmap_priv *priv = context;
+	int addr, regnum;
+
+	config = priv->config;
+
+	addr = FIELD_GET(MDIO_REGMAP_ADDR, reg);
+	regnum = FIELD_GET(MDIO_REGMAP_REGNUM, reg);
+
+	if (reg & MDIO_REGMAP_C45) {
+		int devnum;
+
+		if (!config->mdio_write_c45)
+			return -EOPNOTSUPP;
+
+		devnum = FIELD_GET(MDIO_REGMAP_DEVNUM, reg);
+		return config->mdio_write_c45(priv->ctx, addr, devnum, regnum, val);
+	}
+
+	return config->mdio_write(priv->ctx, addr, regnum, val);
+}
+
+static const struct regmap_config mdio_regmap_default_config = {
+	.reg_bits = 26,
+	.val_bits = 16,
+	.reg_stride = 1,
+	.max_register = MDIO_REGMAP_C45 | MDIO_REGMAP_ADDR |
+			MDIO_REGMAP_DEVNUM | MDIO_REGMAP_REGNUM,
+	.reg_read = mdio_regmap_reg_read,
+	.reg_write = mdio_regmap_reg_write,
+	/* Locking MUST be handled in mdio_write/read(_c45) */
+	.disable_locking = true,
+};
+
+struct regmap *devm_mdio_regmap_init(struct device *dev, void *priv,
+				     const struct mdio_regmap_init_config *config)
+{
+	struct mdio_regmap_priv *mdio_regmap_priv;
+	struct regmap_config regmap_config;
+
+	/* Validate config */
+	if (!config->mdio_read || !config->mdio_write) {
+		dev_err(dev, ".mdio_read and .mdio_write MUST be defined in config\n");
+		return ERR_PTR(-EINVAL);
+	}
+
+	mdio_regmap_priv = devm_kzalloc(dev, sizeof(*mdio_regmap_priv),
+					GFP_KERNEL);
+	if (!mdio_regmap_priv)
+		return ERR_PTR(-ENOMEM);
+
+	memcpy(&regmap_config, &mdio_regmap_default_config, sizeof(regmap_config));
+	regmap_config.name = config->name;
+
+	mdio_regmap_priv->ctx = priv;
+	mdio_regmap_priv->config = config;
+
+	return devm_regmap_init(dev, NULL, mdio_regmap_priv,
+				&regmap_config);
+}
+EXPORT_SYMBOL_GPL(devm_mdio_regmap_init);
+
 MODULE_DESCRIPTION("MDIO API over regmap");
 MODULE_AUTHOR("Maxime Chevallier <maxime.chevallier@bootlin.com>");
 MODULE_LICENSE("GPL");
diff --git a/include/linux/mdio/mdio-regmap.h b/include/linux/mdio/mdio-regmap.h
index 679d9069846b..504fa2046043 100644
--- a/include/linux/mdio/mdio-regmap.h
+++ b/include/linux/mdio/mdio-regmap.h
@@ -17,10 +17,24 @@  struct mdio_regmap_config {
 	struct regmap *regmap;
 	char name[MII_BUS_ID_SIZE];
 	u8 valid_addr;
+	/* devm_mdio_regmap_init is required with this enabled */
+	bool support_encoded_addr;
 	bool autoscan;
 };
 
 struct mii_bus *devm_mdio_regmap_register(struct device *dev,
 					  const struct mdio_regmap_config *config);
 
+struct mdio_regmap_init_config {
+	const char *name;
+
+	int (*mdio_read)(void *ctx, int addr, int regnum);
+	int (*mdio_write)(void *ctx, int addr, int regnum, u16 val);
+	int (*mdio_read_c45)(void *ctx, int addr, int devnum, int regnum);
+	int (*mdio_write_c45)(void *ctx, int addr, int devnum, int regnum, u16 val);
+};
+
+struct regmap *devm_mdio_regmap_init(struct device *dev, void *priv,
+				     const struct mdio_regmap_init_config *config);
+
 #endif