diff --git a/drivers/net/dsa/sja1105/Kconfig b/drivers/net/dsa/sja1105/Kconfig
index 1291bba3f3b6..55ce8f6a2758 100644
--- a/drivers/net/dsa/sja1105/Kconfig
+++ b/drivers/net/dsa/sja1105/Kconfig
@@ -7,6 +7,7 @@ tristate "NXP SJA1105 Ethernet switch family support"
select PCS_XPCS
select PACKING
select CRC32
+ select OF_DYNAMIC
help
This is the driver for the NXP SJA1105 (5-port) and SJA1110 (10-port)
automotive Ethernet switch family. These are managed over an SPI
diff --git a/drivers/net/dsa/sja1105/sja1105.h b/drivers/net/dsa/sja1105/sja1105.h
index cf718e7c2b7b..1b52beba62d4 100644
--- a/drivers/net/dsa/sja1105/sja1105.h
+++ b/drivers/net/dsa/sja1105/sja1105.h
@@ -113,6 +113,13 @@ enum sja1105_internal_phy_t {
SJA1105_PHY_BASE_T1,
};
+struct sja1105_pcs_resource {
+ struct resource res;
+ int port;
+ u32 tx_polarity;
+ const char *compatible;
+};
+
struct sja1105_info {
u64 device_id;
/* Needed for distinction between P and R, and between Q and S@@ -165,6 +172,8 @@ struct sja1105_info {
bool supports_2500basex[SJA1105_MAX_NUM_PORTS];
enum sja1105_internal_phy_t internal_phy[SJA1105_MAX_NUM_PORTS];
const u64 port_speed[SJA1105_SPEED_MAX];
+ const struct sja1105_pcs_resource *pcs_resources;
+ size_t num_pcs_resources;
};
enum sja1105_key_type {@@ -278,6 +287,8 @@ struct sja1105_private {
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;
struct sja1105_ptp_data ptp_data;
struct sja1105_tas_data tas_data;
};diff --git a/drivers/net/dsa/sja1105/sja1105_main.c b/drivers/net/dsa/sja1105/sja1105_main.c
index d3fb42772071..84c0f7c676e2 100644
--- a/drivers/net/dsa/sja1105/sja1105_main.c
+++ b/drivers/net/dsa/sja1105/sja1105_main.c
@@ -3330,6 +3330,13 @@ static int sja1105_probe(struct spi_device *spi)
return rc;
}
+ rc = devm_sja1105_fill_device_tree(ds);
+ if (rc) {
+ dev_err(ds->dev, "Failed to fill device tree: %pe\n",
+ ERR_PTR(rc));
+ return rc;
+ }
+
rc = devm_sja1105_add_subdevs(ds);
if (rc) {
dev_err(ds->dev, "Failed to create child devices: %pe\n",diff --git a/drivers/net/dsa/sja1105/sja1105_spi.c b/drivers/net/dsa/sja1105/sja1105_spi.c
index 20757e166b08..4d4da69b3c30 100644
--- a/drivers/net/dsa/sja1105/sja1105_spi.c
+++ b/drivers/net/dsa/sja1105/sja1105_spi.c
@@ -1,8 +1,9 @@
// SPDX-License-Identifier: BSD-3-Clause
-/* Copyright 2016-2018 NXP
+/* Copyright 2016-2018, 2026 NXP
* Copyright (c) 2018, Sensor-Technik Wiedemann GmbH
* Copyright (c) 2018-2019, Vladimir Oltean <olteanv@gmail.com>
*/
+#include <linux/phy/phy-common-props.h>
#include <linux/spi/spi.h>
#include <linux/packing.h>
#include "sja1105.h"
@@ -619,6 +620,28 @@ static const struct sja1105_regs sja1110_regs = {
SJA1105_RSV_ADDR, SJA1105_RSV_ADDR, SJA1105_RSV_ADDR},
};
+/* See port compatibility matrix in Documentation/networking/dsa/sja1105.rst */
+static const struct sja1105_pcs_resource sja1105rs_pcs_resources[] = {
+ { DEFINE_RES_REG_NAMED(0x0, 0x800000, "direct"), 4,
+ PHY_POL_INVERT, "nxp,sja1105-pcs"
+ },
+};
+
+static const struct sja1105_pcs_resource sja1110_pcs_resources[] = {
+ { DEFINE_RES_REG_NAMED(0x705000, 0x1000, "indirect"), 1,
+ PHY_POL_NORMAL, "nxp,sja1110-pcs"
+ },
+ { DEFINE_RES_REG_NAMED(0x706000, 0x1000, "indirect"), 2,
+ PHY_POL_NORMAL, "nxp,sja1110-pcs"
+ },
+ { DEFINE_RES_REG_NAMED(0x707000, 0x1000, "indirect"), 3,
+ PHY_POL_NORMAL, "nxp,sja1110-pcs"
+ },
+ { DEFINE_RES_REG_NAMED(0x708000, 0x1000, "indirect"), 4,
+ PHY_POL_NORMAL, "nxp,sja1110-pcs"
+ },
+};
+
const struct sja1105_info sja1105e_info = {
.device_id = SJA1105E_DEVICE_ID,
.part_no = SJA1105ET_PART_NO,@@ -782,6 +805,8 @@ const struct sja1105_info sja1105r_info = {
.supports_rmii = {true, true, true, true, true},
.supports_rgmii = {true, true, true, true, true},
.supports_sgmii = {false, false, false, false, true},
+ .pcs_resources = sja1105rs_pcs_resources,
+ .num_pcs_resources = ARRAY_SIZE(sja1105rs_pcs_resources),
.name = "SJA1105R",
};
@@ -818,6 +843,8 @@ const struct sja1105_info sja1105s_info = {
.supports_rmii = {true, true, true, true, true},
.supports_rgmii = {true, true, true, true, true},
.supports_sgmii = {false, false, false, false, true},
+ .pcs_resources = sja1105rs_pcs_resources,
+ .num_pcs_resources = ARRAY_SIZE(sja1105rs_pcs_resources),
.name = "SJA1105S",
};
@@ -869,6 +896,8 @@ const struct sja1105_info sja1110a_info = {
SJA1105_PHY_BASE_T1, SJA1105_PHY_BASE_T1,
SJA1105_PHY_BASE_T1, SJA1105_PHY_BASE_T1,
SJA1105_PHY_BASE_T1},
+ .pcs_resources = sja1110_pcs_resources,
+ .num_pcs_resources = ARRAY_SIZE(sja1110_pcs_resources),
.name = "SJA1110A",
};
@@ -920,6 +949,8 @@ const struct sja1105_info sja1110b_info = {
SJA1105_PHY_BASE_T1, SJA1105_PHY_BASE_T1,
SJA1105_PHY_BASE_T1, SJA1105_PHY_BASE_T1,
SJA1105_NO_PHY},
+ .pcs_resources = &sja1110_pcs_resources[2], /* ports 3 and 4 */
+ .num_pcs_resources = ARRAY_SIZE(sja1110_pcs_resources) - 2,
.name = "SJA1110B",
};
@@ -971,6 +1002,8 @@ const struct sja1105_info sja1110c_info = {
SJA1105_PHY_BASE_T1, SJA1105_PHY_BASE_T1,
SJA1105_NO_PHY, SJA1105_NO_PHY,
SJA1105_NO_PHY},
+ .pcs_resources = &sja1110_pcs_resources[3], /* port 4 */
+ .num_pcs_resources = ARRAY_SIZE(sja1110_pcs_resources) - 3,
.name = "SJA1110C",
};
@@ -1022,5 +1055,7 @@ const struct sja1105_info sja1110d_info = {
SJA1105_PHY_BASE_T1, SJA1105_PHY_BASE_T1,
SJA1105_NO_PHY, SJA1105_NO_PHY,
SJA1105_NO_PHY},
+ .pcs_resources = sja1110_pcs_resources,
+ .num_pcs_resources = ARRAY_SIZE(sja1110_pcs_resources),
.name = "SJA1110D",
};diff --git a/drivers/net/dsa/sja1105/sja1105_subdev.c b/drivers/net/dsa/sja1105/sja1105_subdev.c
index 06957d44f084..085d77947dc3 100644
--- a/drivers/net/dsa/sja1105/sja1105_subdev.c
+++ b/drivers/net/dsa/sja1105/sja1105_subdev.c
@@ -152,3 +152,193 @@ int devm_sja1105_add_subdevs(struct dsa_switch *ds)
return 0;
}
+
+static bool of_child_node_exists(struct device_node *np, const char *name)
+{
+ for_each_child_of_node_scoped(np, child)
+ if (!strcmp(of_node_full_name(child), name))
+ return true;
+
+ return false;
+}
+
+static int sja1105_create_pcs_nodes(struct sja1105_private *priv,
+ struct device_node *regs_node)
+{
+ struct dsa_switch *ds = priv->ds;
+ struct device *dev = ds->dev;
+ struct device_node *pcs_node;
+ char node_name[32];
+ u32 reg_props[2];
+ int rc;
+
+ for (int i = 0; i < priv->info->num_pcs_resources; i++) {
+ const struct sja1105_pcs_resource *pcs_res;
+
+ pcs_res = &priv->info->pcs_resources[i];
+
+ /* phys_addr_t has variable size depending on the value of
+ * CONFIG_PHYS_ADDR_T_64BIT, cast to the larger unsigned long
+ * long type for printf.
+ */
+ snprintf(node_name, sizeof(node_name), "ethernet-pcs@%llx",
+ (unsigned long long)pcs_res->res.start);
+
+ if (of_child_node_exists(regs_node, node_name))
+ continue;
+
+ pcs_node = of_changeset_create_node(&priv->of_cs, regs_node,
+ node_name);
+ if (!pcs_node) {
+ dev_err(dev, "Failed to create PCS node %s\n", node_name);
+ return -ENOMEM;
+ }
+
+ rc = of_changeset_add_prop_string(&priv->of_cs, pcs_node,
+ "compatible",
+ pcs_res->compatible);
+ if (rc) {
+ dev_err(dev, "Failed to add compatible property to %s: %pe\n",
+ node_name, ERR_PTR(rc));
+ return rc;
+ }
+
+ reg_props[0] = pcs_res->res.start;
+ reg_props[1] = resource_size(&pcs_res->res);
+ rc = of_changeset_add_prop_u32_array(&priv->of_cs, pcs_node,
+ "reg", reg_props, 2);
+ if (rc) {
+ dev_err(dev, "Failed to add reg property to %s: %pe\n",
+ node_name, ERR_PTR(rc));
+ return rc;
+ }
+
+ rc = of_changeset_add_prop_string(&priv->of_cs, pcs_node,
+ "reg-names",
+ pcs_res->res.name);
+ if (rc) {
+ dev_err(dev, "Failed to add reg-names property to %s: %pe\n",
+ node_name, ERR_PTR(rc));
+ return rc;
+ }
+
+ rc = of_changeset_add_prop_u32(&priv->of_cs, pcs_node,
+ "reg-io-width", 4);
+ if (rc) {
+ dev_err(dev, "Failed to add reg-io-width property to %s: %pe\n",
+ node_name, ERR_PTR(rc));
+ return rc;
+ }
+
+ /* The SJA1105 XPCS is integrated with a TX-inverting custom
+ * PMA. We need to invert the polarity in the PCS to obtain a
+ * non-inverted signal at the pins.
+ */
+ rc = of_changeset_add_prop_u32(&priv->of_cs, pcs_node, "tx-polarity",
+ pcs_res->tx_polarity);
+ if (rc) {
+ dev_err(dev, "Failed to add tx-polarity property to %s: %pe\n",
+ node_name, ERR_PTR(rc));
+ return rc;
+ }
+
+ dev_dbg(dev, "Created OF node %pOF\n", pcs_node);
+ priv->pcs_fwnode[pcs_res->port] = of_fwnode_handle(pcs_node);
+ }
+
+ return 0;
+}
+
+static struct device_node *sja1105_create_regs_node(struct sja1105_private *priv,
+ struct device_node *switch_node)
+{
+ struct device *dev = priv->ds->dev;
+ struct device_node *regs_node;
+ int rc;
+
+ regs_node = of_changeset_create_node(&priv->of_cs, switch_node, "regs");
+ if (!regs_node) {
+ dev_err(dev, "Failed to create 'regs' device tree node\n");
+ return ERR_PTR(-ENOMEM);
+ }
+
+ rc = of_changeset_add_prop_u32(&priv->of_cs, regs_node, "#address-cells", 1);
+ if (rc) {
+ dev_err(dev, "Failed to add #address-cells property: %pe\n",
+ ERR_PTR(rc));
+ return ERR_PTR(rc);
+ }
+
+ rc = of_changeset_add_prop_u32(&priv->of_cs, regs_node, "#size-cells", 1);
+ if (rc) {
+ dev_err(dev, "Failed to add #size-cells property: %pe\n",
+ ERR_PTR(rc));
+ return ERR_PTR(rc);
+ }
+
+ return regs_node;
+}
+
+static void sja1105_restore_device_tree(void *data)
+{
+ struct sja1105_private *priv = data;
+ struct device *dev = priv->ds->dev;
+ int rc;
+
+ rc = of_changeset_revert(&priv->of_cs);
+ if (rc) {
+ dev_err(dev, "Failed to revert device tree changeset: %pe\n",
+ ERR_PTR(rc));
+ }
+
+ of_changeset_destroy(&priv->of_cs);
+}
+
+int devm_sja1105_fill_device_tree(struct dsa_switch *ds)
+{
+ struct device_node *switch_node, *regs_node;
+ struct sja1105_private *priv = ds->priv;
+ bool regs_node_created = false;
+ struct device *dev = ds->dev;
+ int rc;
+
+ if (!priv->info->num_pcs_resources)
+ return 0;
+
+ switch_node = dev_of_node(dev);
+ of_changeset_init(&priv->of_cs);
+
+ regs_node = of_get_child_by_name(switch_node, "regs");
+ if (!regs_node) {
+ regs_node = sja1105_create_regs_node(priv, switch_node);
+ if (IS_ERR(regs_node)) {
+ rc = PTR_ERR(regs_node);
+ goto out_destroy_changeset;
+ }
+
+ regs_node_created = true;
+ dev_dbg(dev, "Created OF node %pOF\n", regs_node);
+ }
+
+ rc = sja1105_create_pcs_nodes(priv, regs_node);
+ if (rc)
+ goto out_destroy_changeset;
+
+ rc = of_changeset_apply(&priv->of_cs);
+ if (rc) {
+ dev_err(dev, "Failed to apply device tree changeset: %pe\n",
+ ERR_PTR(rc));
+ goto out_destroy_changeset;
+ }
+
+ rc = devm_add_action_or_reset(dev, sja1105_restore_device_tree, priv);
+ goto out_put_regs_node;
+
+out_destroy_changeset:
+ of_changeset_destroy(&priv->of_cs);
+out_put_regs_node:
+ if (!regs_node_created)
+ of_node_put(regs_node);
+
+ return rc;
+}diff --git a/drivers/net/dsa/sja1105/sja1105_subdev.h b/drivers/net/dsa/sja1105/sja1105_subdev.h
index 9b5a02401399..1507ff3c44d1 100644
--- a/drivers/net/dsa/sja1105/sja1105_subdev.h
+++ b/drivers/net/dsa/sja1105/sja1105_subdev.h
@@ -5,5 +5,6 @@
#define _SJA1105_SUBDEV_H
int devm_sja1105_add_subdevs(struct dsa_switch *ds);
+int devm_sja1105_fill_device_tree(struct dsa_switch *ds);
#endif
--
2.34.1