diff --git a/drivers/net/dsa/sja1105/sja1105.h b/drivers/net/dsa/sja1105/sja1105.h
index 2f62942692ec..6b4fad1216bb 100644
--- a/drivers/net/dsa/sja1105/sja1105.h
+++ b/drivers/net/dsa/sja1105/sja1105.h
@@ -127,6 +127,14 @@ struct sja1105_flow_block {
 	bool l2_policer_used[SJA1105_NUM_L2_POLICERS];
 };
 
+struct sja1105_crosschip_info {
+	struct list_head list;
+	int tree_index;
+	int sw_index;
+	int other_port;
+	struct net_device *br;
+};
+
 struct sja1105_private {
 	struct sja1105_static_config static_config;
 	bool rgmii_rx_delay[SJA1105_NUM_PORTS];
@@ -135,6 +143,7 @@ struct sja1105_private {
 	struct gpio_desc *reset_gpio;
 	struct spi_device *spidev;
 	struct dsa_switch *ds;
+	struct list_head crosschip_ports;
 	struct sja1105_flow_block flow_block;
 	struct sja1105_port ports[SJA1105_NUM_PORTS];
 	/* Serializes transmission of management frames so that
diff --git a/drivers/net/dsa/sja1105/sja1105_main.c b/drivers/net/dsa/sja1105/sja1105_main.c
index 472f4eb20c49..95e00e03d05b 100644
--- a/drivers/net/dsa/sja1105/sja1105_main.c
+++ b/drivers/net/dsa/sja1105/sja1105_main.c
@@ -25,6 +25,8 @@
 #include "sja1105_sgmii.h"
 #include "sja1105_tas.h"
 
+static const struct dsa_switch_ops sja1105_switch_ops;
+
 static void sja1105_hw_reset(struct gpio_desc *gpio, unsigned int pulse_len,
 			     unsigned int startup_delay)
 {
@@ -1790,6 +1792,176 @@ static int sja1105_vlan_apply(struct sja1105_private *priv, int port, u16 vid,
 	return 0;
 }
 
+static int sja1105_crosschip_bridge_member(struct dsa_switch *ds,
+					   int tree_index, int sw_index,
+					   int other_port,
+					   struct net_device *br, bool enabled)
+{
+	struct dsa_switch *other_ds = dsa_switch_find(tree_index, sw_index);
+	struct sja1105_private *priv = ds->priv;
+	struct sja1105_private *other_priv;
+	u16 rx_vid, other_rx_vid;
+	int cpu = -1, other_cpu;
+	int port, rc;
+
+	if (other_ds->ops != &sja1105_switch_ops)
+		return 0;
+
+	other_cpu = dsa_upstream_port(other_ds, other_port);
+	other_priv = other_ds->priv;
+
+	/* Make traffic from all local ports enslaved to @br be received
+	 * by @other_ds. This means that our @rx_vid needs to be installed
+	 * on @other_ds's CPU port and user ports. The user ports should be
+	 * egress-untagged so that the can pop the dsa_8021q VLAN. But the
+	 * @other_cpu can be either egress-tagged or untagged: it doesn't
+	 * matter, since it should never egress a frame having our @rx_vid.
+	 */
+	for (port = 0; port < SJA1105_NUM_PORTS; port++) {
+		if (!dsa_is_user_port(ds, port))
+			continue;
+		if (dsa_to_port(ds, port)->bridge_dev != br)
+			continue;
+
+		cpu = dsa_upstream_port(ds, port);
+
+		rx_vid = dsa_8021q_rx_vid(ds, port);
+
+		/* The VLANs on @other_cpu port should be refcounted, because
+		 * we don't want to remove our @rx_vid just yet when a single
+		 * remote port leaves. But that's tricky to implement, so just
+		 * keep it simple and leave the VLANs installed on the CPU
+		 * ports when leaving. Forwarding will still be denied, as it
+		 * should be, because the @rx_vid has been removed from the
+		 * remote switch's external port.
+		 */
+		if (enabled) {
+			/* @rx_vid of local @ds port @port goes to @other_cpu
+			 * of @other_ds
+			 */
+			rc = sja1105_vlan_apply(other_priv, other_cpu, rx_vid,
+						enabled, false);
+			if (rc < 0)
+				return rc;
+		}
+
+		/* @rx_vid of local @ds port @port goes to @other_port of
+		 * @other_ds
+		 */
+		rc = sja1105_vlan_apply(other_priv, other_port,
+					rx_vid, enabled, true);
+		if (rc < 0)
+			return rc;
+	}
+
+	/* Shorthand way of saying that we had no ports enslaved to @br. Also a
+	 * convenient way of getting a reference to the CPU port via
+	 * dsa_upstream_port(), which needs a valid user port as argument.
+	 */
+	if (cpu == -1)
+		return 0;
+
+	/* Make traffic from the remote @other_ds switch's @other_port be
+	 * accepted by the local @ds. This means that its @rx_vid needs to be
+	 * installed on our local CPU port as well as local ports that are
+	 * members of @br.
+	 */
+	other_rx_vid = dsa_8021q_rx_vid(other_ds, other_port);
+
+	for (port = 0; port < SJA1105_NUM_PORTS; port++) {
+		if (!dsa_is_user_port(ds, port))
+			continue;
+		if (dsa_to_port(ds, port)->bridge_dev != br)
+			continue;
+
+		/* @other_rx_vid of @other_ds port @other_port goes to port
+		 * @port of local @ds.
+		 */
+		rc = sja1105_vlan_apply(priv, port, other_rx_vid, enabled,
+					true);
+		if (rc < 0)
+			return rc;
+	}
+
+	/* The comment above regarding VLAN refcounting applies here too */
+	if (!enabled)
+		return 0;
+
+	/* @other_rx_vid of @other_ds port @other_port goes to port @cpu of
+	 * local @ds
+	 */
+	return sja1105_vlan_apply(priv, cpu, other_rx_vid, enabled, false);
+}
+
+static int sja1105_crosschip_bridge_join(struct dsa_switch *ds,
+					 int tree_index, int sw_index,
+					 int other_port, struct net_device *br)
+{
+	struct sja1105_private *priv = ds->priv;
+	struct sja1105_crosschip_info *c;
+	int rc;
+
+	if (!br_vlan_enabled(br)) {
+		rc = sja1105_crosschip_bridge_member(ds, tree_index, sw_index,
+						     other_port, br, true);
+		if (rc)
+			return rc;
+	}
+
+	c = kzalloc(sizeof(*c), GFP_KERNEL);
+	if (!c)
+		return -ENOMEM;
+
+	c->tree_index = tree_index;
+	c->sw_index = sw_index;
+	c->other_port = other_port;
+	c->br = br;
+
+	list_add(&c->list, &priv->crosschip_ports);
+
+	return 0;
+}
+
+static void sja1105_crosschip_bridge_leave(struct dsa_switch *ds,
+					   int tree_index, int sw_index,
+					   int other_port,
+					   struct net_device *br)
+{
+	struct sja1105_private *priv = ds->priv;
+	struct sja1105_crosschip_info *c, *n;
+
+	if (!br_vlan_enabled(br))
+		sja1105_crosschip_bridge_member(ds, tree_index, sw_index,
+						other_port, br, false);
+
+	list_for_each_entry_safe(c, n, &priv->crosschip_ports, list) {
+		if (c->tree_index == tree_index && c->sw_index == sw_index &&
+		    c->other_port == other_port && c->br == br) {
+			list_del(&c->list);
+			kfree(c);
+			break;
+		}
+	}
+}
+
+static int sja1105_apply_crosschip_vlans(struct dsa_switch *ds, bool enabled)
+{
+	struct sja1105_private *priv = ds->priv;
+	struct sja1105_crosschip_info *c;
+	int rc;
+
+	list_for_each_entry(c, &priv->crosschip_ports, list) {
+		rc = sja1105_crosschip_bridge_member(ds, c->tree_index,
+						     c->sw_index,
+						     c->other_port, c->br,
+						     enabled);
+		if (rc)
+			break;
+	}
+
+	return rc;
+}
+
 static int sja1105_setup_8021q_tagging(struct dsa_switch *ds, bool enabled)
 {
 	int rc, i;
@@ -1802,6 +1974,13 @@ static int sja1105_setup_8021q_tagging(struct dsa_switch *ds, bool enabled)
 			return rc;
 		}
 	}
+	/* Replay the crosschip operations whenever we need to enable or
+	 * disable DSA tagging.
+	 */
+	rc = sja1105_apply_crosschip_vlans(ds, enabled);
+	if (rc)
+		return rc;
+
 	dev_info(ds->dev, "%s switch tagging\n",
 		 enabled ? "Enabled" : "Disabled");
 	return 0;
@@ -2359,6 +2538,8 @@ static const struct dsa_switch_ops sja1105_switch_ops = {
 	.port_policer_del	= sja1105_port_policer_del,
 	.cls_flower_add		= sja1105_cls_flower_add,
 	.cls_flower_del		= sja1105_cls_flower_del,
+	.crosschip_bridge_join	= sja1105_crosschip_bridge_join,
+	.crosschip_bridge_leave	= sja1105_crosschip_bridge_leave,
 };
 
 static int sja1105_check_device_id(struct sja1105_private *priv)
@@ -2461,6 +2642,8 @@ static int sja1105_probe(struct spi_device *spi)
 	mutex_init(&priv->ptp_data.lock);
 	mutex_init(&priv->mgmt_lock);
 
+	INIT_LIST_HEAD(&priv->crosschip_ports);
+
 	sja1105_tas_setup(ds);
 	sja1105_flower_setup(ds);
 
