[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-Id: <20260122105654.105600-13-vladimir.oltean@nxp.com>
Date: Thu, 22 Jan 2026 12:56:51 +0200
From: Vladimir Oltean <vladimir.oltean@....com>
To: netdev@...r.kernel.org
Cc: Andrew Lunn <andrew@...n.ch>,
Heiner Kallweit <hkallweit1@...il.com>,
Russell King <linux@...linux.org.uk>,
"David S. Miller" <davem@...emloft.net>,
Eric Dumazet <edumazet@...gle.com>,
Jakub Kicinski <kuba@...nel.org>,
Paolo Abeni <pabeni@...hat.com>,
linux-kernel@...r.kernel.org,
Andy Shevchenko <andriy.shevchenko@...ux.intel.com>,
Herve Codina <herve.codina@...tlin.com>,
Mark Brown <broonie@...nel.org>,
Serge Semin <fancer.lancer@...il.com>,
Maxime Chevallier <maxime.chevallier@...tlin.com>,
Lee Jones <lee@...nel.org>,
Rob Herring <robh@...nel.org>,
Krzysztof Kozlowski <krzk+dt@...nel.org>,
Conor Dooley <conor+dt@...nel.org>,
devicetree@...r.kernel.org,
Choong Yong Liang <yong.liang.choong@...ux.intel.com>,
Jiawen Wu <jiawenwu@...stnetic.com>
Subject: [PATCH v2 net-next 12/15] net: dsa: sja1105: replace mdiobus-pcs with xpcs-plat driver
The following switches supported by the driver have at least one XPCS
sub-device:
- "nxp,sja1105r"
- "nxp,sja1105s"
- "nxp,sja1110a"
- "nxp,sja1110b"
- "nxp,sja1110c"
- "nxp,sja1110d"
For these switches, it is guaranteed that the XPCS blocks is described
in the "regs" OF subnode of the switch, either manually by the board DT
author or by sja1105_fill_device_tree().
So we can write some custom "bus" code to probe platform devices for
each child OF node of "regs", and that completely replaces the need for
the code in sja1105_mdio.c.
There were discussions about how to instantiate the XPCS sub-devices and
the MFD maintainer doesn't consider mfd_add_devices() to be used
canonically for this use case:
https://lore.kernel.org/netdev/20260116132345.GA882947@google.com/
So I am rolling my own custom code on top of devm_of_subdev_add() ->
platform_device_register_full() that is also used for the MDIO
sub-devices, but this time, instead of manually picking the MDIO nodes
and registering them one by one, the PCS nodes (as well as anything
else under "regs") will be automatically be picked up by the new
devm_of_subdevs_populate() method. The critical difference is that the
XPCS device tree binding is sufficiently detailed to be able to extract
the address space resources from its "reg" properties, whereas the MDIO
nodes were not (and had to be manually associated with resources).
This of_subdev_* "bus" code lives in the sja1105 driver for lack of a
better home, but can easily be moved to a more generic location, and is
written to permit that.
A small implementation note: priv->pcs_fwnode[] exists because
currently, the of_changeset API doesn't support creating phandles.
Thus, instead of also filling in 'pcs-handle' properties from ports to
the dynamic 'ethernet-pcs' OF nodes, the driver just saves them for
later use in sja1105_create_pcs().
This implies that when the PCS nodes do exist in DT, priv->pcs_fwnode[]
will be NULL. This is fine - xpcs_create_fwnode() is NULL-tolerant via
fwnode_device_is_available(), and this case currently returns -ENODEV
and will be handled by the next change.
Cc: Serge Semin <fancer.lancer@...il.com>
Cc: Andy Shevchenko <andriy.shevchenko@...ux.intel.com>
Cc: Herve Codina <herve.codina@...tlin.com>
Cc: Rob Herring <robh@...nel.org>
Cc: Krzysztof Kozlowski <krzk+dt@...nel.org>
Cc: Conor Dooley <conor+dt@...nel.org>
Cc: devicetree@...r.kernel.org
Signed-off-by: Vladimir Oltean <vladimir.oltean@....com>
---
v1->v2:
- mfd_add_devices() has been replaced with a completely new approach in
the form of devm_of_subdevs_populate()
drivers/net/dsa/sja1105/Makefile | 1 -
drivers/net/dsa/sja1105/sja1105.h | 20 --
drivers/net/dsa/sja1105/sja1105_main.c | 53 +++--
drivers/net/dsa/sja1105/sja1105_mdio.c | 239 -----------------------
drivers/net/dsa/sja1105/sja1105_spi.c | 15 --
drivers/net/dsa/sja1105/sja1105_subdev.c | 171 +++++++++++++++-
6 files changed, 212 insertions(+), 287 deletions(-)
delete mode 100644 drivers/net/dsa/sja1105/sja1105_mdio.c
diff --git a/drivers/net/dsa/sja1105/Makefile b/drivers/net/dsa/sja1105/Makefile
index 7b5537d67072..049d198f681e 100644
--- a/drivers/net/dsa/sja1105/Makefile
+++ b/drivers/net/dsa/sja1105/Makefile
@@ -4,7 +4,6 @@ obj-$(CONFIG_NET_DSA_SJA1105) += sja1105.o
sja1105-objs := \
sja1105_spi.o \
sja1105_main.o \
- sja1105_mdio.o \
sja1105_subdev.o \
sja1105_flower.o \
sja1105_ethtool.o \
diff --git a/drivers/net/dsa/sja1105/sja1105.h b/drivers/net/dsa/sja1105/sja1105.h
index 1b52beba62d4..7fce1507eb12 100644
--- a/drivers/net/dsa/sja1105/sja1105.h
+++ b/drivers/net/dsa/sja1105/sja1105.h
@@ -91,11 +91,6 @@ struct sja1105_regs {
u64 rmii_ref_clk[SJA1105_MAX_NUM_PORTS];
u64 rmii_ext_tx_clk[SJA1105_MAX_NUM_PORTS];
u64 stats[__MAX_SJA1105_STATS_AREA][SJA1105_MAX_NUM_PORTS];
- u64 pcs_base[SJA1105_MAX_NUM_PORTS];
-};
-
-struct sja1105_mdio_private {
- struct sja1105_private *priv;
};
enum {
@@ -159,10 +154,6 @@ struct sja1105_info {
bool (*rxtstamp)(struct dsa_switch *ds, int port, struct sk_buff *skb);
void (*txtstamp)(struct dsa_switch *ds, int port, struct sk_buff *skb);
int (*clocking_setup)(struct sja1105_private *priv);
- int (*pcs_mdio_read_c45)(struct mii_bus *bus, int phy, int mmd,
- int reg);
- int (*pcs_mdio_write_c45)(struct mii_bus *bus, int phy, int mmd,
- int reg, u16 val);
int (*disable_microcontroller)(struct sja1105_private *priv);
const char *name;
bool supports_mii[SJA1105_MAX_NUM_PORTS];
@@ -285,7 +276,6 @@ struct sja1105_private {
struct mutex dynamic_config_lock;
struct devlink_region **regions;
struct sja1105_cbs_entry *cbs;
- struct mii_bus *mdio_pcs;
struct phylink_pcs *pcs[SJA1105_MAX_NUM_PORTS];
struct fwnode_handle *pcs_fwnode[SJA1105_MAX_NUM_PORTS];
struct of_changeset of_cs;
@@ -316,16 +306,6 @@ int sja1105_vlan_filtering(struct dsa_switch *ds, int port, bool enabled,
struct netlink_ext_ack *extack);
void sja1105_frame_memory_partitioning(struct sja1105_private *priv);
-/* From sja1105_mdio.c */
-int sja1105_mdiobus_register(struct dsa_switch *ds);
-void sja1105_mdiobus_unregister(struct dsa_switch *ds);
-int sja1105_pcs_mdio_read_c45(struct mii_bus *bus, int phy, int mmd, int reg);
-int sja1105_pcs_mdio_write_c45(struct mii_bus *bus, int phy, int mmd, int reg,
- u16 val);
-int sja1110_pcs_mdio_read_c45(struct mii_bus *bus, int phy, int mmd, int reg);
-int sja1110_pcs_mdio_write_c45(struct mii_bus *bus, int phy, int mmd, int reg,
- u16 val);
-
/* From sja1105_devlink.c */
int sja1105_devlink_setup(struct dsa_switch *ds);
void sja1105_devlink_teardown(struct dsa_switch *ds);
diff --git a/drivers/net/dsa/sja1105/sja1105_main.c b/drivers/net/dsa/sja1105/sja1105_main.c
index 84c0f7c676e2..b60a890ba416 100644
--- a/drivers/net/dsa/sja1105/sja1105_main.c
+++ b/drivers/net/dsa/sja1105/sja1105_main.c
@@ -15,6 +15,7 @@
#include <linux/of.h>
#include <linux/of_net.h>
#include <linux/of_mdio.h>
+#include <linux/pcs/pcs-xpcs.h>
#include <linux/netdev_features.h>
#include <linux/netdevice.h>
#include <linux/if_bridge.h>
@@ -3005,6 +3006,44 @@ static int sja1105_port_bridge_flags(struct dsa_switch *ds, int port,
return 0;
}
+static int sja1105_create_pcs(struct dsa_switch *ds, int port)
+{
+ struct sja1105_private *priv = ds->priv;
+ struct phylink_pcs *pcs;
+
+ if (priv->phy_mode[port] != PHY_INTERFACE_MODE_SGMII &&
+ priv->phy_mode[port] != PHY_INTERFACE_MODE_2500BASEX)
+ return 0;
+
+ pcs = xpcs_create_pcs_fwnode(priv->pcs_fwnode[port]);
+ if (IS_ERR(pcs))
+ return PTR_ERR(pcs);
+
+ priv->pcs[port] = pcs;
+
+ return 0;
+}
+
+static void sja1105_destroy_pcs(struct dsa_switch *ds, int port)
+{
+ struct sja1105_private *priv = ds->priv;
+
+ if (priv->pcs[port]) {
+ xpcs_destroy_pcs(priv->pcs[port]);
+ priv->pcs[port] = NULL;
+ }
+}
+
+static int sja1105_port_setup(struct dsa_switch *ds, int port)
+{
+ return sja1105_create_pcs(ds, port);
+}
+
+static void sja1105_port_teardown(struct dsa_switch *ds, int port)
+{
+ sja1105_destroy_pcs(ds, port);
+}
+
/* The programming model for the SJA1105 switch is "all-at-once" via static
* configuration tables. Some of these can be dynamically modified at runtime,
* but not the xMII mode parameters table.
@@ -3059,16 +3098,9 @@ static int sja1105_setup(struct dsa_switch *ds)
goto out_flower_teardown;
}
- rc = sja1105_mdiobus_register(ds);
- if (rc < 0) {
- dev_err(ds->dev, "Failed to register MDIO bus: %pe\n",
- ERR_PTR(rc));
- goto out_ptp_clock_unregister;
- }
-
rc = sja1105_devlink_setup(ds);
if (rc < 0)
- goto out_mdiobus_unregister;
+ goto out_ptp_clock_unregister;
rtnl_lock();
rc = dsa_tag_8021q_register(ds, htons(ETH_P_8021Q));
@@ -3098,8 +3130,6 @@ static int sja1105_setup(struct dsa_switch *ds)
out_devlink_teardown:
sja1105_devlink_teardown(ds);
-out_mdiobus_unregister:
- sja1105_mdiobus_unregister(ds);
out_ptp_clock_unregister:
sja1105_ptp_clock_unregister(ds);
out_flower_teardown:
@@ -3120,7 +3150,6 @@ static void sja1105_teardown(struct dsa_switch *ds)
rtnl_unlock();
sja1105_devlink_teardown(ds);
- sja1105_mdiobus_unregister(ds);
sja1105_ptp_clock_unregister(ds);
sja1105_flower_teardown(ds);
sja1105_tas_teardown(ds);
@@ -3139,6 +3168,8 @@ static const struct dsa_switch_ops sja1105_switch_ops = {
.connect_tag_protocol = sja1105_connect_tag_protocol,
.setup = sja1105_setup,
.teardown = sja1105_teardown,
+ .port_setup = sja1105_port_setup,
+ .port_teardown = sja1105_port_teardown,
.set_ageing_time = sja1105_set_ageing_time,
.port_change_mtu = sja1105_change_mtu,
.port_max_mtu = sja1105_get_max_mtu,
diff --git a/drivers/net/dsa/sja1105/sja1105_mdio.c b/drivers/net/dsa/sja1105/sja1105_mdio.c
deleted file mode 100644
index b803ce71f5cc..000000000000
--- a/drivers/net/dsa/sja1105/sja1105_mdio.c
+++ /dev/null
@@ -1,239 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0
-/* Copyright 2021 NXP
- */
-#include <linux/pcs/pcs-xpcs.h>
-#include <linux/of_mdio.h>
-#include "sja1105.h"
-
-#define SJA1110_PCS_BANK_REG SJA1110_SPI_ADDR(0x3fc)
-
-int sja1105_pcs_mdio_read_c45(struct mii_bus *bus, int phy, int mmd, int reg)
-{
- struct sja1105_mdio_private *mdio_priv = bus->priv;
- struct sja1105_private *priv = mdio_priv->priv;
- u64 addr;
- u32 tmp;
- int rc;
-
- addr = (mmd << 16) | reg;
-
- if (mmd != MDIO_MMD_VEND1 && mmd != MDIO_MMD_VEND2)
- return 0xffff;
-
- if (mmd == MDIO_MMD_VEND2 && (reg & GENMASK(15, 0)) == MII_PHYSID1)
- return NXP_SJA1105_XPCS_ID >> 16;
- if (mmd == MDIO_MMD_VEND2 && (reg & GENMASK(15, 0)) == MII_PHYSID2)
- return NXP_SJA1105_XPCS_ID & GENMASK(15, 0);
-
- rc = sja1105_xfer_u32(priv, SPI_READ, addr, &tmp, NULL);
- if (rc < 0)
- return rc;
-
- return tmp & 0xffff;
-}
-
-int sja1105_pcs_mdio_write_c45(struct mii_bus *bus, int phy, int mmd,
- int reg, u16 val)
-{
- struct sja1105_mdio_private *mdio_priv = bus->priv;
- struct sja1105_private *priv = mdio_priv->priv;
- u64 addr;
- u32 tmp;
-
- addr = (mmd << 16) | reg;
- tmp = val;
-
- if (mmd != MDIO_MMD_VEND1 && mmd != MDIO_MMD_VEND2)
- return -EINVAL;
-
- return sja1105_xfer_u32(priv, SPI_WRITE, addr, &tmp, NULL);
-}
-
-int sja1110_pcs_mdio_read_c45(struct mii_bus *bus, int phy, int mmd, int reg)
-{
- struct sja1105_mdio_private *mdio_priv = bus->priv;
- struct sja1105_private *priv = mdio_priv->priv;
- const struct sja1105_regs *regs = priv->info->regs;
- int offset, bank;
- u64 addr;
- u32 tmp;
- int rc;
-
- if (regs->pcs_base[phy] == SJA1105_RSV_ADDR)
- return -ENODEV;
-
- addr = (mmd << 16) | reg;
-
- if (mmd == MDIO_MMD_VEND2 && (reg & GENMASK(15, 0)) == MII_PHYSID1)
- return NXP_SJA1110_XPCS_ID >> 16;
- if (mmd == MDIO_MMD_VEND2 && (reg & GENMASK(15, 0)) == MII_PHYSID2)
- return NXP_SJA1110_XPCS_ID & GENMASK(15, 0);
-
- bank = addr >> 8;
- offset = addr & GENMASK(7, 0);
-
- /* This addressing scheme reserves register 0xff for the bank address
- * register, so that can never be addressed.
- */
- if (WARN_ON(offset == 0xff))
- return -ENODEV;
-
- tmp = bank;
-
- rc = sja1105_xfer_u32(priv, SPI_WRITE,
- regs->pcs_base[phy] + SJA1110_PCS_BANK_REG,
- &tmp, NULL);
- if (rc < 0)
- return rc;
-
- rc = sja1105_xfer_u32(priv, SPI_READ, regs->pcs_base[phy] + offset,
- &tmp, NULL);
- if (rc < 0)
- return rc;
-
- return tmp & 0xffff;
-}
-
-int sja1110_pcs_mdio_write_c45(struct mii_bus *bus, int phy, int mmd, int reg,
- u16 val)
-{
- struct sja1105_mdio_private *mdio_priv = bus->priv;
- struct sja1105_private *priv = mdio_priv->priv;
- const struct sja1105_regs *regs = priv->info->regs;
- int offset, bank;
- u64 addr;
- u32 tmp;
- int rc;
-
- if (regs->pcs_base[phy] == SJA1105_RSV_ADDR)
- return -ENODEV;
-
- addr = (mmd << 16) | reg;
-
- bank = addr >> 8;
- offset = addr & GENMASK(7, 0);
-
- /* This addressing scheme reserves register 0xff for the bank address
- * register, so that can never be addressed.
- */
- if (WARN_ON(offset == 0xff))
- return -ENODEV;
-
- tmp = bank;
-
- rc = sja1105_xfer_u32(priv, SPI_WRITE,
- regs->pcs_base[phy] + SJA1110_PCS_BANK_REG,
- &tmp, NULL);
- if (rc < 0)
- return rc;
-
- tmp = val;
-
- return sja1105_xfer_u32(priv, SPI_WRITE, regs->pcs_base[phy] + offset,
- &tmp, NULL);
-}
-
-static int sja1105_mdiobus_pcs_register(struct sja1105_private *priv)
-{
- struct sja1105_mdio_private *mdio_priv;
- struct dsa_switch *ds = priv->ds;
- struct mii_bus *bus;
- int rc = 0;
- int port;
-
- if (!priv->info->pcs_mdio_read_c45 || !priv->info->pcs_mdio_write_c45)
- return 0;
-
- bus = mdiobus_alloc_size(sizeof(*mdio_priv));
- if (!bus)
- return -ENOMEM;
-
- bus->name = "SJA1105 PCS MDIO bus";
- snprintf(bus->id, MII_BUS_ID_SIZE, "%s-pcs",
- dev_name(ds->dev));
- bus->read_c45 = priv->info->pcs_mdio_read_c45;
- bus->write_c45 = priv->info->pcs_mdio_write_c45;
- bus->parent = ds->dev;
- /* There is no PHY on this MDIO bus => mask out all PHY addresses
- * from auto probing.
- */
- bus->phy_mask = ~0;
- mdio_priv = bus->priv;
- mdio_priv->priv = priv;
-
- rc = mdiobus_register(bus);
- if (rc) {
- mdiobus_free(bus);
- return rc;
- }
-
- for (port = 0; port < ds->num_ports; port++) {
- struct phylink_pcs *pcs;
-
- if (dsa_is_unused_port(ds, port))
- continue;
-
- if (priv->phy_mode[port] != PHY_INTERFACE_MODE_SGMII &&
- priv->phy_mode[port] != PHY_INTERFACE_MODE_2500BASEX)
- continue;
-
- pcs = xpcs_create_pcs_mdiodev(bus, port);
- if (IS_ERR(pcs)) {
- rc = PTR_ERR(pcs);
- goto out_pcs_free;
- }
-
- priv->pcs[port] = pcs;
- }
-
- priv->mdio_pcs = bus;
-
- return 0;
-
-out_pcs_free:
- for (port = 0; port < ds->num_ports; port++) {
- if (priv->pcs[port]) {
- xpcs_destroy_pcs(priv->pcs[port]);
- priv->pcs[port] = NULL;
- }
- }
-
- mdiobus_unregister(bus);
- mdiobus_free(bus);
-
- return rc;
-}
-
-static void sja1105_mdiobus_pcs_unregister(struct sja1105_private *priv)
-{
- struct dsa_switch *ds = priv->ds;
- int port;
-
- if (!priv->mdio_pcs)
- return;
-
- for (port = 0; port < ds->num_ports; port++) {
- if (priv->pcs[port]) {
- xpcs_destroy_pcs(priv->pcs[port]);
- priv->pcs[port] = NULL;
- }
- }
-
- mdiobus_unregister(priv->mdio_pcs);
- mdiobus_free(priv->mdio_pcs);
- priv->mdio_pcs = NULL;
-}
-
-int sja1105_mdiobus_register(struct dsa_switch *ds)
-{
- struct sja1105_private *priv = ds->priv;
-
- return sja1105_mdiobus_pcs_register(priv);
-}
-
-void sja1105_mdiobus_unregister(struct dsa_switch *ds)
-{
- struct sja1105_private *priv = ds->priv;
-
- sja1105_mdiobus_pcs_unregister(priv);
-}
diff --git a/drivers/net/dsa/sja1105/sja1105_spi.c b/drivers/net/dsa/sja1105/sja1105_spi.c
index 4d4da69b3c30..27cac00eba32 100644
--- a/drivers/net/dsa/sja1105/sja1105_spi.c
+++ b/drivers/net/dsa/sja1105/sja1105_spi.c
@@ -615,9 +615,6 @@ static const struct sja1105_regs sja1110_regs = {
.ptpclkrate = SJA1110_SPI_ADDR(0x74),
.ptpclkcorp = SJA1110_SPI_ADDR(0x80),
.ptpsyncts = SJA1110_SPI_ADDR(0x84),
- .pcs_base = {SJA1105_RSV_ADDR, 0x1c1400, 0x1c1800, 0x1c1c00, 0x1c2000,
- SJA1105_RSV_ADDR, SJA1105_RSV_ADDR, SJA1105_RSV_ADDR,
- SJA1105_RSV_ADDR, SJA1105_RSV_ADDR, SJA1105_RSV_ADDR},
};
/* See port compatibility matrix in Documentation/networking/dsa/sja1105.rst */
@@ -791,8 +788,6 @@ const struct sja1105_info sja1105r_info = {
.ptp_cmd_packing = sja1105pqrs_ptp_cmd_packing,
.rxtstamp = sja1105_rxtstamp,
.clocking_setup = sja1105_clocking_setup,
- .pcs_mdio_read_c45 = sja1105_pcs_mdio_read_c45,
- .pcs_mdio_write_c45 = sja1105_pcs_mdio_write_c45,
.regs = &sja1105pqrs_regs,
.port_speed = {
[SJA1105_SPEED_AUTO] = 0,
@@ -830,8 +825,6 @@ const struct sja1105_info sja1105s_info = {
.ptp_cmd_packing = sja1105pqrs_ptp_cmd_packing,
.rxtstamp = sja1105_rxtstamp,
.clocking_setup = sja1105_clocking_setup,
- .pcs_mdio_read_c45 = sja1105_pcs_mdio_read_c45,
- .pcs_mdio_write_c45 = sja1105_pcs_mdio_write_c45,
.port_speed = {
[SJA1105_SPEED_AUTO] = 0,
[SJA1105_SPEED_10MBPS] = 3,
@@ -871,8 +864,6 @@ const struct sja1105_info sja1110a_info = {
.rxtstamp = sja1110_rxtstamp,
.txtstamp = sja1110_txtstamp,
.disable_microcontroller = sja1110_disable_microcontroller,
- .pcs_mdio_read_c45 = sja1110_pcs_mdio_read_c45,
- .pcs_mdio_write_c45 = sja1110_pcs_mdio_write_c45,
.port_speed = {
[SJA1105_SPEED_AUTO] = 0,
[SJA1105_SPEED_10MBPS] = 4,
@@ -924,8 +915,6 @@ const struct sja1105_info sja1110b_info = {
.rxtstamp = sja1110_rxtstamp,
.txtstamp = sja1110_txtstamp,
.disable_microcontroller = sja1110_disable_microcontroller,
- .pcs_mdio_read_c45 = sja1110_pcs_mdio_read_c45,
- .pcs_mdio_write_c45 = sja1110_pcs_mdio_write_c45,
.port_speed = {
[SJA1105_SPEED_AUTO] = 0,
[SJA1105_SPEED_10MBPS] = 4,
@@ -977,8 +966,6 @@ const struct sja1105_info sja1110c_info = {
.rxtstamp = sja1110_rxtstamp,
.txtstamp = sja1110_txtstamp,
.disable_microcontroller = sja1110_disable_microcontroller,
- .pcs_mdio_read_c45 = sja1110_pcs_mdio_read_c45,
- .pcs_mdio_write_c45 = sja1110_pcs_mdio_write_c45,
.port_speed = {
[SJA1105_SPEED_AUTO] = 0,
[SJA1105_SPEED_10MBPS] = 4,
@@ -1030,8 +1017,6 @@ const struct sja1105_info sja1110d_info = {
.rxtstamp = sja1110_rxtstamp,
.txtstamp = sja1110_txtstamp,
.disable_microcontroller = sja1110_disable_microcontroller,
- .pcs_mdio_read_c45 = sja1110_pcs_mdio_read_c45,
- .pcs_mdio_write_c45 = sja1110_pcs_mdio_write_c45,
.port_speed = {
[SJA1105_SPEED_AUTO] = 0,
[SJA1105_SPEED_10MBPS] = 4,
diff --git a/drivers/net/dsa/sja1105/sja1105_subdev.c b/drivers/net/dsa/sja1105/sja1105_subdev.c
index 085d77947dc3..5ca613a4549d 100644
--- a/drivers/net/dsa/sja1105/sja1105_subdev.c
+++ b/drivers/net/dsa/sja1105/sja1105_subdev.c
@@ -78,6 +78,167 @@ static int devm_of_subdev_add(const struct platform_device_info *pdevinfo)
return devm_add_action_or_reset(parent, of_subdev_del, pdev);
}
+static int of_subdev_collect_resources(struct device *parent, struct device_node *np,
+ size_t address_cells, size_t size_cells,
+ struct resource **res, size_t *num_res)
+{
+ size_t reg_len;
+ u32 *reg;
+ int err;
+
+ err = of_property_count_u32_elems(np, "reg");
+ if (!err)
+ err = -EINVAL;
+ if (err < 0) {
+ dev_err(parent, "Failed to read subdev %pOF \"reg\" property: %pe\n",
+ np, ERR_PTR(err));
+ return err;
+ }
+ reg_len = err;
+
+ if (reg_len % (address_cells + size_cells)) {
+ dev_err(parent, "Invalid \"reg\" specifier for %pOF\n", np);
+ return -EINVAL;
+ }
+
+ *num_res = reg_len / (address_cells + size_cells);
+ *res = kcalloc(*num_res, sizeof(**res), GFP_KERNEL);
+ if (!*res)
+ return -ENOMEM;
+
+ reg = kcalloc(reg_len, sizeof(*reg), GFP_KERNEL);
+ if (!reg) {
+ kfree(*res);
+ return -ENOMEM;
+ }
+
+ err = of_property_read_u32_array(np, "reg", reg, reg_len);
+ if (err) {
+ kfree(reg);
+ kfree(*res);
+ return err;
+ }
+
+ for (int cur_res = 0; cur_res < *num_res; cur_res++) {
+ int idx, address_cell, size_cell;
+ phys_addr_t start = 0, size = 0;
+
+ for (address_cell = 0; address_cell < address_cells; address_cell++) {
+ idx = cur_res * (address_cells + size_cells) + address_cell;
+ start = (unsigned long long)start << 32 | reg[idx];
+ }
+ for (size_cell = 0; size_cell < size_cells; size_cell++) {
+ idx = cur_res * (address_cells + size_cells) + address_cells + size_cell;
+ size = (unsigned long long)size << 32 | reg[idx];
+ }
+
+ (*res)[cur_res].start = start;
+ (*res)[cur_res].end = start + size - 1;
+ (*res)[cur_res].flags = IORESOURCE_REG;
+ of_property_read_string_index(np, "reg-names", cur_res, &(*res)[cur_res].name);
+ }
+
+ return 0;
+}
+
+/* Custom version of of_device_make_bus_id() which derives the name from the
+ * parent device plus the subdev name and untranslatable address.
+ * We don't set the resource address in the platform ID because that would
+ * print it as decimal rather than hex.
+ */
+static void of_subdev_make_bus_id(char *name, size_t name_len,
+ const struct device *parent,
+ struct device_node *child,
+ const struct resource *res)
+{
+ if (res)
+ snprintf(name, name_len, "%s.%llx.%pOFn", dev_name(parent),
+ (unsigned long long)res->start, child);
+ else
+ snprintf(name, name_len, "%s.%pOFn", dev_name(parent), child);
+}
+
+/**
+ * devm_of_subdevs_populate() - Populate platform sub-devices from device tree
+ * @parent: Parent device for all created sub-devices
+ * @np: Device tree node containing child nodes to be converted to sub-devices
+ *
+ * The device tree node @np describes the (MMIO-like but untranslatable) linear
+ * address space of device @parent, as can sometimes be found when such device
+ * is accessed through a SPI-to-AHB bridge.
+ *
+ * This function parses the device tree node @np and creates platform devices
+ * for each available child node. It reads the #address-cells and #size-cells
+ * properties to properly parse the "reg" properties of child nodes.
+ *
+ * For each child node, the function:
+ * - Creates a platform device with a name based on the parent and child node
+ * - Auto-detects and attaches resources to sub-devices based on parsed device
+ * tree "reg" and "reg-names" properties
+ * - Uses the first resource's start address as the platform device ID
+ * - Registers the device with automatic cleanup via devres
+ *
+ * This is similar to of_platform_populate() except it expects to find
+ * IORESOURCE_REG resources rather than IORESOURCE_MEM/IORESOURCE_IO.
+ * It is also similar to mfd_add_devices() except we don't have to specify the
+ * mfd_cells[], but rather, the resources are embedded into the device tree.
+ * More importantly, this allows for the parent to have a hybrid function
+ * (MFD parent + the main function of the device) and a custom device tree
+ * binding, whereas MFD does not.
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+static int devm_of_subdevs_populate(struct device *parent, struct device_node *np)
+{
+ u32 address_cells, size_cells;
+ int err;
+
+ err = of_property_read_u32(np, "#address-cells", &address_cells);
+ if (err)
+ return err;
+
+ err = of_property_read_u32(np, "#size-cells", &size_cells);
+ if (err)
+ return err;
+
+ if (IS_ENABLED(CONFIG_PHYS_ADDR_T_64BIT) ?
+ (address_cells > 2 || size_cells > 2) :
+ (address_cells > 1 || size_cells > 1)) {
+ dev_err(parent, "Subdev address space exceeds phys_addr_t possibilities\n");
+ return -EINVAL;
+ }
+
+ for_each_available_child_of_node_scoped(np, child) {
+ struct platform_device_info subdev;
+ struct resource *res;
+ size_t num_res;
+ char name[64];
+
+ err = of_subdev_collect_resources(parent, child, address_cells,
+ size_cells, &res, &num_res);
+ if (err)
+ return err;
+
+ of_subdev_make_bus_id(name, sizeof(name), parent, child,
+ num_res ? &res[0] : NULL);
+ subdev = (struct platform_device_info) {
+ .parent = parent,
+ .fwnode = of_fwnode_handle(child),
+ .name = name,
+ .id = PLATFORM_DEVID_NONE,
+ .res = res,
+ .num_res = num_res,
+ };
+
+ err = devm_of_subdev_add(&subdev);
+ kfree(res);
+ if (err)
+ return err;
+ }
+
+ return 0;
+}
+
static int devm_sja1105_add_mdio_subdev(struct device *parent,
struct device_node *np,
const struct resource *res,
@@ -139,9 +300,17 @@ static int devm_sja1105_add_mdio_subdevs(struct dsa_switch *ds,
int devm_sja1105_add_subdevs(struct dsa_switch *ds)
{
struct device_node *switch_node = dev_of_node(ds->dev);
- struct device_node *mdio_node;
+ struct device_node *regs_node, *mdio_node;
int rc = 0;
+ regs_node = of_get_available_child_by_name(switch_node, "regs");
+ if (regs_node) {
+ rc = devm_of_subdevs_populate(ds->dev, regs_node);
+ of_node_put(regs_node);
+ if (rc)
+ return rc;
+ }
+
mdio_node = of_get_available_child_by_name(switch_node, "mdios");
if (mdio_node) {
rc = devm_sja1105_add_mdio_subdevs(ds, mdio_node);
--
2.34.1
Powered by blists - more mailing lists