public inbox for drm-ai-reviews@public-inbox.freedesktop.org
 help / color / mirror / Atom feed
From: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
To: Maarten Lankhorst <maarten.lankhorst@linux.intel.com>,
	Maxime Ripard <mripard@kernel.org>,
	Thomas Zimmermann <tzimmermann@suse.de>,
	David Airlie <airlied@gmail.com>, Simona Vetter <simona@ffwll.ch>,
	Rob Herring <robh@kernel.org>,
	Krzysztof Kozlowski <krzk+dt@kernel.org>,
	Conor Dooley <conor+dt@kernel.org>, Lee Jones <lee@kernel.org>,
	Aradhya Bhatia <aradhya.bhatia@linux.dev>,
	Nishanth Menon <nm@ti.com>, Vignesh Raghavendra <vigneshr@ti.com>,
	Swamil Jain <s-jain1@ti.com>, Devarsh Thakkar <devarsht@ti.com>,
	Louis Chauvet <louis.chauvet@bootlin.com>
Cc: devicetree@vger.kernel.org, dri-devel@lists.freedesktop.org,
	linux-kernel@vger.kernel.org,
	linux-arm-kernel@lists.infradead.org,
	Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
Subject: [PATCH 12/15] drm/tidss: oldi: Convert OLDI to an aux driver
Date: Mon, 20 Apr 2026 15:54:19 +0300	[thread overview]
Message-ID: <20260420-beagley-ai-display-v1-12-f628543dfd14@ideasonboard.com> (raw)
In-Reply-To: <20260420-beagley-ai-display-v1-0-f628543dfd14@ideasonboard.com>

Currently in the DT, OLDI is defined in child nodes under the DSS node.
The tidss driver will parse the DT, and create DRM bridges for the
OLDIs, and there are no Linux devices for the OLDIs.

On new SoCs the OLDIs have their own power-domains which we need to
control. The cleanest way to do this in DT is to add the PDs to the OLDI
nodes. But this means the OLDI bridge code would somehow have to
manually manage the PDs, the PDs not being under a Linux device, and
there isn't much support for that kind of setup in the PD framework.

A solution to this is to convert the OLDI to an auxiliary device/driver,
created by tidss:

- At module load time the tidss module will, in addition to registering
  the tidss DRM driver, register an oldi auxiliary driver.
- At probe time tidss will parse the DT, and create an auxiliary device
  for each OLDI.

The aux driver will probe, and as its of_node points to the OLDI node
containing the PD, the driver framework will take care of enabling and
disabling the PD when OLDI is used ("used" as in pm_runtime context).

Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
---
 drivers/gpu/drm/tidss/tidss_drv.c  |  51 +++-
 drivers/gpu/drm/tidss/tidss_drv.h  |   5 +-
 drivers/gpu/drm/tidss/tidss_oldi.c | 493 ++++++++++++++++++++++++++-----------
 drivers/gpu/drm/tidss/tidss_oldi.h |   7 +-
 4 files changed, 400 insertions(+), 156 deletions(-)

diff --git a/drivers/gpu/drm/tidss/tidss_drv.c b/drivers/gpu/drm/tidss/tidss_drv.c
index 5cb3e746aeb3..aef945101be4 100644
--- a/drivers/gpu/drm/tidss/tidss_drv.c
+++ b/drivers/gpu/drm/tidss/tidss_drv.c
@@ -133,10 +133,6 @@ static int tidss_probe(struct platform_device *pdev)
 		return ret;
 	}
 
-	ret = tidss_oldi_init(tidss);
-	if (ret)
-		return dev_err_probe(dev, ret, "failed to init OLDI\n");
-
 	pm_runtime_enable(dev);
 
 	pm_runtime_set_autosuspend_delay(dev, 1000);
@@ -147,24 +143,30 @@ static int tidss_probe(struct platform_device *pdev)
 	dispc_runtime_resume(tidss->dispc);
 #endif
 
+	ret = tidss_oldi_create_devices(tidss);
+	if (ret) {
+		dev_err_probe(dev, ret, "failed to create OLDI devices\n");
+		goto err_runtime_suspend;
+	}
+
 	ret = tidss_modeset_init(tidss);
 	if (ret < 0) {
 		if (ret != -EPROBE_DEFER)
 			dev_err(dev, "failed to init DRM/KMS (%d)\n", ret);
-		goto err_runtime_suspend;
+		goto err_destroy_oldis;
 	}
 
 	irq = platform_get_irq(pdev, 0);
 	if (irq < 0) {
 		ret = irq;
-		goto err_runtime_suspend;
+		goto err_destroy_oldis;
 	}
 	tidss->irq = irq;
 
 	ret = tidss_irq_install(ddev, irq);
 	if (ret) {
 		dev_err(dev, "tidss_irq_install failed: %d\n", ret);
-		goto err_runtime_suspend;
+		goto err_destroy_oldis;
 	}
 
 	drm_kms_helper_poll_init(ddev);
@@ -194,6 +196,9 @@ static int tidss_probe(struct platform_device *pdev)
 err_irq_uninstall:
 	tidss_irq_uninstall(ddev);
 
+err_destroy_oldis:
+	tidss_oldi_destroy_devices(tidss);
+
 err_runtime_suspend:
 #ifndef CONFIG_PM
 	dispc_runtime_suspend(tidss->dispc);
@@ -201,8 +206,6 @@ static int tidss_probe(struct platform_device *pdev)
 	pm_runtime_dont_use_autosuspend(dev);
 	pm_runtime_disable(dev);
 
-	tidss_oldi_deinit(tidss);
-
 	return ret;
 }
 
@@ -218,6 +221,8 @@ static void tidss_remove(struct platform_device *pdev)
 
 	tidss_irq_uninstall(ddev);
 
+	tidss_oldi_destroy_devices(tidss);
+
 #ifndef CONFIG_PM
 	/* If we don't have PM, we need to call suspend manually */
 	dispc_runtime_suspend(tidss->dispc);
@@ -225,8 +230,6 @@ static void tidss_remove(struct platform_device *pdev)
 	pm_runtime_dont_use_autosuspend(dev);
 	pm_runtime_disable(dev);
 
-	tidss_oldi_deinit(tidss);
-
 	/* devm allocated dispc goes away with the dev so mark it NULL */
 	dispc_remove(tidss);
 
@@ -262,7 +265,31 @@ static struct platform_driver tidss_platform_driver = {
 	},
 };
 
-drm_module_platform_driver(tidss_platform_driver);
+static int __init tidss_platform_driver_init(void)
+{
+	int ret;
+
+	ret = tidss_oldi_register_driver();
+	if (ret)
+		return ret;
+
+	ret = drm_platform_driver_register(&tidss_platform_driver);
+	if (ret) {
+		tidss_oldi_unregister_driver();
+		return ret;
+	}
+
+	return 0;
+}
+module_init(tidss_platform_driver_init);
+
+static void __exit tidss_platform_driver_exit(void)
+{
+	platform_driver_unregister(&tidss_platform_driver);
+	tidss_oldi_unregister_driver();
+}
+module_exit(tidss_platform_driver_exit);
+
 
 MODULE_AUTHOR("Tomi Valkeinen <tomi.valkeinen@ti.com>");
 MODULE_DESCRIPTION("TI Keystone DSS Driver");
diff --git a/drivers/gpu/drm/tidss/tidss_drv.h b/drivers/gpu/drm/tidss/tidss_drv.h
index e1c1f41d8b4b..d3dba639b278 100644
--- a/drivers/gpu/drm/tidss/tidss_drv.h
+++ b/drivers/gpu/drm/tidss/tidss_drv.h
@@ -16,7 +16,8 @@
 #define TIDSS_MAX_OLDI_TXES 2
 
 typedef u32 dispc_irq_t;
-struct tidss_oldi;
+
+struct auxiliary_device;
 
 struct tidss_device {
 	struct drm_device ddev;		/* DRM device for DSS */
@@ -34,7 +35,7 @@ struct tidss_device {
 	struct drm_plane *planes[TIDSS_MAX_PLANES];
 
 	unsigned int num_oldis;
-	struct tidss_oldi *oldis[TIDSS_MAX_OLDI_TXES];
+	struct auxiliary_device *oldis[TIDSS_MAX_OLDI_TXES];
 
 	unsigned int irq;
 
diff --git a/drivers/gpu/drm/tidss/tidss_oldi.c b/drivers/gpu/drm/tidss/tidss_oldi.c
index e925ddaa4fd6..86a1e029fdb1 100644
--- a/drivers/gpu/drm/tidss/tidss_oldi.c
+++ b/drivers/gpu/drm/tidss/tidss_oldi.c
@@ -5,11 +5,13 @@
  * Aradhya Bhatia <a-bhatia1@ti.com>
  */
 
+#include <linux/auxiliary_bus.h>
 #include <linux/clk.h>
 #include <linux/of.h>
 #include <linux/of_graph.h>
 #include <linux/mfd/syscon.h>
 #include <linux/media-bus-format.h>
+#include <linux/pm_runtime.h>
 #include <linux/regmap.h>
 
 #include <drm/drm_atomic_helper.h>
@@ -20,6 +22,12 @@
 #include "tidss_dispc_regs.h"
 #include "tidss_oldi.h"
 
+static DEFINE_IDA(tidss_oldi_ida);
+
+struct tidss_oldi_platform_data {
+	struct tidss_device *tidss;
+};
+
 struct tidss_oldi {
 	struct tidss_device	*tidss;
 	struct device		*dev;
@@ -251,6 +259,8 @@ static void tidss_oldi_atomic_pre_enable(struct drm_bridge *bridge,
 	if (oldi->link_type == OLDI_MODE_SECONDARY_CLONE_SINGLE_LINK)
 		return;
 
+	WARN_ON(pm_runtime_get_sync(oldi->dev));
+
 	connector = drm_atomic_get_new_connector_for_encoder(state,
 							     bridge->encoder);
 	if (WARN_ON(!connector))
@@ -296,6 +306,8 @@ static void tidss_oldi_atomic_post_disable(struct drm_bridge *bridge,
 
 	/* Clear OLDI Config */
 	tidss_disable_oldi(oldi->tidss, oldi->parent_vp);
+
+	pm_runtime_put_autosuspend(oldi->dev);
 }
 
 #define MAX_INPUT_SEL_FORMATS	1
@@ -464,174 +476,375 @@ static int get_parent_dss_vp(struct device_node *oldi_tx, u32 *parent_vp)
 	return -ENODEV;
 }
 
-void tidss_oldi_deinit(struct tidss_device *tidss)
-{
-	for (int i = 0; i < tidss->num_oldis; i++) {
-		if (tidss->oldis[i]) {
-			drm_bridge_remove(&tidss->oldis[i]->bridge);
-			tidss->is_ext_vp_clk[tidss->oldis[i]->parent_vp] = false;
-			tidss->oldis[i] = NULL;
-		}
-	}
-}
-
-int tidss_oldi_init(struct tidss_device *tidss)
+static int tidss_oldi_probe(struct auxiliary_device *auxdev,
+			    const struct auxiliary_device_id *id)
 {
-	struct tidss_oldi *oldi;
-	struct device_node *child;
-	struct drm_bridge *bridge;
-	u32 parent_vp, oldi_instance;
-	int companion_instance = -1;
+	struct device *dev = &auxdev->dev;
+	struct tidss_oldi_platform_data *oldi_pdata = dev_get_platdata(dev);
+	struct tidss_device *tidss = oldi_pdata->tidss;
+	struct device_node *node = auxdev->dev.of_node;
 	enum tidss_oldi_link_type link_type = OLDI_MODE_UNSUPPORTED;
-	struct device_node *oldi_parent;
-	int ret = 0;
-
-	tidss->num_oldis = 0;
+	int companion_instance = -1;
+	struct drm_bridge *bridge;
+	struct device_link *link;
+	struct tidss_oldi *oldi;
+	u32 oldi_instance;
+	u32 parent_vp;
+	int ret;
 
-	oldi_parent = of_get_child_by_name(tidss->dev->of_node, "oldi-transmitters");
-	if (!oldi_parent)
-		/* Return gracefully */
-		return 0;
+	ret = of_property_read_u32(node, "reg", &oldi_instance);
+	if (ret)
+		return ret;
 
-	for_each_available_child_of_node(oldi_parent, child) {
-		ret = get_parent_dss_vp(child, &parent_vp);
-		if (ret) {
-			if (ret == -ENODEV) {
-				/*
-				 * ENODEV means that this particular OLDI node
-				 * is not connected with the DSS, which is not
-				 * a harmful case. There could be another OLDI
-				 * which may still be connected.
-				 * Continue to search for that.
-				 */
-				continue;
-			}
-			goto err_put_node;
-		}
+	ret = get_parent_dss_vp(node, &parent_vp);
+	if (ret)
+		return ret;
 
-		ret = of_property_read_u32(child, "reg", &oldi_instance);
-		if (ret)
-			goto err_put_node;
+	/*
+	 * Now that it's confirmed that OLDI is connected with DSS,
+	 * let's continue getting the OLDI sinks ahead and other OLDI
+	 * properties.
+	 */
+	bridge = devm_drm_of_get_bridge(dev, node, OLDI_OUTPUT_PORT, 0);
+	if (IS_ERR(bridge)) {
+		/*
+		 * Either there was no OLDI sink in the devicetree, or the OLDI
+		 * sink has not been added yet. In any case, return.
+		 */
+		ret = dev_err_probe(dev, PTR_ERR(bridge),
+				    "no panel/bridge for OLDI%u.\n",
+				    oldi_instance);
+		goto err_put_node;
+	}
 
+	link_type = get_oldi_mode(node, &companion_instance);
+	if (link_type == OLDI_MODE_UNSUPPORTED) {
+		ret = dev_err_probe(dev, -EINVAL,
+				    "OLDI%u: Unsupported OLDI connection.\n",
+				    oldi_instance);
+		goto err_put_node;
+	} else if ((link_type == OLDI_MODE_SECONDARY_CLONE_SINGLE_LINK) ||
+		   (link_type == OLDI_MODE_CLONE_SINGLE_LINK)) {
 		/*
-		 * Now that it's confirmed that OLDI is connected with DSS,
-		 * let's continue getting the OLDI sinks ahead and other OLDI
-		 * properties.
+		 * The OLDI driver cannot support OLDI clone mode properly at
+		 * present. The clone mode requires 2 working encoder-bridge
+		 * pipelines, generating from the same crtc. The DRM framework
+		 * does not support this at present. If there were to be, say, 2
+		 * OLDI sink bridges each connected to an OLDI TXes, they
+		 * couldn't both be supported simultaneously. This driver still
+		 * has some code pertaining to OLDI clone mode configuration in
+		 * DSS hardware for future, when there is a better
+		 * infrastructure in the DRM framework to support 2
+		 * encoder-bridge pipelines simultaneously. Till that time, this
+		 * driver shall error out if it detects a clone mode
+		 * configuration.
 		 */
-		bridge = devm_drm_of_get_bridge(tidss->dev, child,
-						OLDI_OUTPUT_PORT, 0);
-		if (IS_ERR(bridge)) {
-			/*
-			 * Either there was no OLDI sink in the devicetree, or
-			 * the OLDI sink has not been added yet. In any case,
-			 * return.
-			 * We don't want to have an OLDI node connected to DSS
-			 * but not to any sink.
-			 */
-			ret = dev_err_probe(tidss->dev, PTR_ERR(bridge),
-					    "no panel/bridge for OLDI%u.\n",
+		ret = dev_err_probe(dev, -EOPNOTSUPP,
+				    "The OLDI driver does not support Clone Mode at present.\n");
+		goto err_put_node;
+	}
+
+	oldi = devm_drm_bridge_alloc(dev, struct tidss_oldi, bridge,
+				     &tidss_oldi_bridge_funcs);
+	if (IS_ERR(oldi)) {
+		ret = PTR_ERR(oldi);
+		goto err_put_node;
+	}
+
+	oldi->parent_vp = parent_vp;
+	oldi->oldi_instance = oldi_instance;
+	oldi->companion_instance = companion_instance;
+	oldi->link_type = link_type;
+	oldi->dev = dev;
+	oldi->next_bridge = bridge;
+	oldi->tidss = tidss;
+
+	auxiliary_set_drvdata(auxdev, oldi);
+
+	/*
+	 * Only the primary OLDI needs to reference the io-ctrl system
+	 * registers, and the serial clock.
+	 * We don't require a check for secondary OLDI in dual-link mode
+	 * because the driver will not create a drm_bridge instance.
+	 * But the driver will need to create a drm_bridge instance,
+	 * for secondary OLDI in clone mode (once it is supported).
+	 */
+	if (link_type != OLDI_MODE_SECONDARY_DUAL_LINK &&
+	    link_type != OLDI_MODE_SECONDARY_CLONE_SINGLE_LINK) {
+		oldi->io_ctrl = syscon_regmap_lookup_by_phandle(node,
+								"ti,oldi-io-ctrl");
+		if (IS_ERR(oldi->io_ctrl)) {
+			ret = dev_err_probe(oldi->dev, PTR_ERR(oldi->io_ctrl),
+					    "OLDI%u: syscon_regmap_lookup_by_phandle failed.\n",
 					    oldi_instance);
 			goto err_put_node;
 		}
 
-		link_type = get_oldi_mode(child, &companion_instance);
-		if (link_type == OLDI_MODE_UNSUPPORTED) {
-			ret = dev_err_probe(tidss->dev, -EINVAL,
-					    "OLDI%u: Unsupported OLDI connection.\n",
+		oldi->serial = of_clk_get_by_name(node, "serial");
+		if (IS_ERR(oldi->serial)) {
+			ret = dev_err_probe(oldi->dev, PTR_ERR(oldi->serial),
+					    "OLDI%u: Failed to get serial clock.\n",
 					    oldi_instance);
 			goto err_put_node;
-		} else if ((link_type == OLDI_MODE_SECONDARY_CLONE_SINGLE_LINK) ||
-			   (link_type == OLDI_MODE_CLONE_SINGLE_LINK)) {
-			/*
-			 * The OLDI driver cannot support OLDI clone mode
-			 * properly at present.
-			 * The clone mode requires 2 working encoder-bridge
-			 * pipelines, generating from the same crtc. The DRM
-			 * framework does not support this at present. If
-			 * there were to be, say, 2 OLDI sink bridges each
-			 * connected to an OLDI TXes, they couldn't both be
-			 * supported simultaneously.
-			 * This driver still has some code pertaining to OLDI
-			 * clone mode configuration in DSS hardware for future,
-			 * when there is a better infrastructure in the DRM
-			 * framework to support 2 encoder-bridge pipelines
-			 * simultaneously.
-			 * Till that time, this driver shall error out if it
-			 * detects a clone mode configuration.
-			 */
-			ret = dev_err_probe(tidss->dev, -EOPNOTSUPP,
-					    "The OLDI driver does not support Clone Mode at present.\n");
-			goto err_put_node;
-		} else if (link_type == OLDI_MODE_SECONDARY_DUAL_LINK) {
-			/*
-			 * This is the secondary OLDI node, which serves as a
-			 * companion to the primary OLDI, when it is configured
-			 * for the dual-link mode. Since the primary OLDI will
-			 * be a part of bridge chain, no need to put this one
-			 * too. Continue onto the next OLDI node.
-			 */
-			continue;
 		}
+	}
 
-		oldi = devm_drm_bridge_alloc(tidss->dev, struct tidss_oldi, bridge,
-					     &tidss_oldi_bridge_funcs);
-		if (IS_ERR(oldi)) {
-			ret = PTR_ERR(oldi);
-			goto err_put_node;
-		}
+	if (link_type != OLDI_MODE_SECONDARY_DUAL_LINK) {
+		/* Register the bridge. */
+		oldi->bridge.of_node = node;
+		oldi->bridge.driver_private = oldi;
 
-		oldi->parent_vp = parent_vp;
-		oldi->oldi_instance = oldi_instance;
-		oldi->companion_instance = companion_instance;
-		oldi->link_type = link_type;
-		oldi->dev = tidss->dev;
-		oldi->next_bridge = bridge;
+		tidss->is_ext_vp_clk[oldi->parent_vp] = true;
 
-		/*
-		 * Only the primary OLDI needs to reference the io-ctrl system
-		 * registers, and the serial clock.
-		 * We don't require a check for secondary OLDI in dual-link mode
-		 * because the driver will not create a drm_bridge instance.
-		 * But the driver will need to create a drm_bridge instance,
-		 * for secondary OLDI in clone mode (once it is supported).
-		 */
-		if (link_type != OLDI_MODE_SECONDARY_CLONE_SINGLE_LINK) {
-			oldi->io_ctrl = syscon_regmap_lookup_by_phandle(child,
-									"ti,oldi-io-ctrl");
-			if (IS_ERR(oldi->io_ctrl)) {
-				ret = dev_err_probe(oldi->dev, PTR_ERR(oldi->io_ctrl),
-						    "OLDI%u: syscon_regmap_lookup_by_phandle failed.\n",
-						    oldi_instance);
-				goto err_put_node;
-			}
+		drm_bridge_add(&oldi->bridge);
+	}
+
+	link = device_link_add(&auxdev->dev, tidss->dev,
+			       DL_FLAG_PM_RUNTIME |
+				       DL_FLAG_AUTOREMOVE_CONSUMER);
+	if (!link) {
+		ret = -EINVAL;
+		goto err_bridge_remove;
+	}
+
+	pm_runtime_enable(dev);
+	pm_runtime_set_autosuspend_delay(dev, 500);
+	pm_runtime_use_autosuspend(dev);
+
+	return 0;
+
+err_bridge_remove:
+	if (link_type != OLDI_MODE_SECONDARY_DUAL_LINK) {
+		drm_bridge_remove(&oldi->bridge);
+		tidss->is_ext_vp_clk[oldi->parent_vp] = false;
+	}
+
+err_put_node:
+	return ret;
+}
+
+static void tidss_oldi_remove(struct auxiliary_device *auxdev)
+{
+	struct tidss_oldi *oldi = auxiliary_get_drvdata(auxdev);
+	struct tidss_device *tidss = oldi->tidss;
+	struct device *dev = &auxdev->dev;
+
+	pm_runtime_dont_use_autosuspend(dev);
+	pm_runtime_disable(dev);
+
+	if (oldi->link_type != OLDI_MODE_SECONDARY_DUAL_LINK) {
+		drm_bridge_remove(&oldi->bridge);
 
-			oldi->serial = of_clk_get_by_name(child, "serial");
-			if (IS_ERR(oldi->serial)) {
-				ret = dev_err_probe(oldi->dev, PTR_ERR(oldi->serial),
-						    "OLDI%u: Failed to get serial clock.\n",
-						    oldi_instance);
-				goto err_put_node;
+		tidss->is_ext_vp_clk[oldi->parent_vp] = false;
+	}
+}
+
+static const struct auxiliary_device_id tidss_oldi_aux_id_table[] = {
+	{ .name = "tidss.oldi" },
+	{}
+};
+
+static struct auxiliary_driver oldi_aux_driver = {
+	.name = "oldi",
+	.probe = tidss_oldi_probe,
+	.remove = tidss_oldi_remove,
+	.id_table = tidss_oldi_aux_id_table,
+};
+
+static void tidss_oldi_aux_device_release(struct device *dev)
+{
+	struct auxiliary_device *auxdev = to_auxiliary_dev(dev);
+
+	ida_free(&tidss_oldi_ida, auxdev->id);
+
+	of_node_put(auxdev->dev.of_node);
+
+	kfree(auxdev->dev.platform_data);
+	kfree(auxdev);
+}
+
+static struct auxiliary_device *
+tidss_oldi_create_device(struct tidss_device *tidss,
+			 struct device_node *oldi_tx)
+{
+	struct tidss_oldi_platform_data *oldi_pdata;
+	struct auxiliary_device *companion_auxdev;
+	struct auxiliary_device *auxdev;
+	u32 oldi_aux_id;
+	int ret;
+
+	/*
+	 * Allocate the ID first, so that we get a lower ID for the primary
+	 * OLDI, instead of the companion grabbing it in the call below. Note
+	 * that the ID allocated here often matches the OLDI hardware index,
+	 * but not always.
+	 * The OLDI hardware index cannot be used as an ID, as, say, OLDI 1 on
+	 * two DSS instances would produce the exact same device name
+	 * ("tidss.oldi.1").
+	 */
+	ret = ida_alloc(&tidss_oldi_ida, GFP_KERNEL);
+	if (ret < 0)
+		return ERR_PTR(ret);
+
+	oldi_aux_id = ret;
+
+	companion_auxdev = NULL;
+
+	/*
+	 * If this is the primary OLDI and there is a companion, create the
+	 * auxdev for the secondary OLDI first, as the secondary will act as
+	 * a supplier for the primary OLDI.
+	 */
+	if (!of_property_read_bool(oldi_tx, "ti,secondary-oldi")) {
+		struct device_node *companion_node;
+
+		companion_node = of_parse_phandle(oldi_tx, "ti,companion-oldi", 0);
+		if (companion_node) {
+			companion_auxdev =
+				tidss_oldi_create_device(tidss, companion_node);
+
+			of_node_put(companion_node);
+
+			if (IS_ERR(companion_auxdev)) {
+				dev_err(tidss->dev,
+					"Failed to create secondary oldi device\n");
+				ret = PTR_ERR(companion_auxdev);
+				goto err_free_ida;
 			}
 		}
+	}
 
-		/* Register the bridge. */
-		oldi->bridge.of_node = child;
-		oldi->bridge.driver_private = oldi;
+	oldi_pdata = kzalloc_obj(*oldi_pdata);
+	if (!oldi_pdata) {
+		ret = -ENOMEM;
+		goto err_free_ida;
+	}
 
-		tidss->oldis[tidss->num_oldis++] = oldi;
-		tidss->is_ext_vp_clk[oldi->parent_vp] = true;
-		oldi->tidss = tidss;
+	oldi_pdata->tidss = tidss;
 
-		drm_bridge_add(&oldi->bridge);
+	auxdev = kzalloc_obj(*auxdev);
+	if (!auxdev) {
+		ret = -ENOMEM;
+		goto err_free_pdata;
+	}
+
+	*auxdev = (struct auxiliary_device) {
+		.name = "oldi",
+		.id = oldi_aux_id,
+		.dev = {
+			.parent = tidss->dev,
+			.of_node = of_node_get(oldi_tx),
+			.release = tidss_oldi_aux_device_release,
+			.platform_data = oldi_pdata,
+		},
+	};
+
+	ret = auxiliary_device_init(auxdev);
+	if (ret) {
+		dev_err(tidss->dev, "OLDI auxiliary_device_init failed: %d\n",
+			ret);
+		goto err_free_auxdev;
+	}
+
+	/*
+	 * Create a device-link between the primary and the secondary, so that
+	 * the secondary will be powered on when the primary is used.
+	 */
+	if (companion_auxdev) {
+		struct device_link *link;
+
+		link = device_link_add(&auxdev->dev, &companion_auxdev->dev,
+				       DL_FLAG_PM_RUNTIME |
+					       DL_FLAG_AUTOREMOVE_CONSUMER |
+					       DL_FLAG_AUTOREMOVE_SUPPLIER);
+		if (!link) {
+			dev_err(tidss->dev,
+				"device_link_add failed between primary and secondary OLDI\n");
+			ret = -EINVAL;
+			goto err_uninit_auxdev;
+		}
+	}
+
+	ret = auxiliary_device_add(auxdev);
+	if (ret) {
+		dev_err(tidss->dev, "OLDI auxiliary_device_add failed: %d\n",
+			ret);
+		goto err_uninit_auxdev;
 	}
 
-	of_node_put(child);
-	of_node_put(oldi_parent);
+	tidss->oldis[tidss->num_oldis++] = auxdev;
+
+	return auxdev;
+
+err_uninit_auxdev:
+	auxiliary_device_uninit(auxdev);
+	/* return here, as the rest are done in auxdev's release */
+	return ERR_PTR(ret);
+
+err_free_auxdev:
+	kfree(auxdev);
+err_free_pdata:
+	kfree(oldi_pdata);
+err_free_ida:
+	ida_free(&tidss_oldi_ida, oldi_aux_id);
+
+	return ERR_PTR(ret);
+}
+
+int tidss_oldi_create_devices(struct tidss_device *tidss)
+{
+	struct device_node *oldi_txes;
+	struct device_node *oldi_tx;
+	int ret;
+
+	oldi_txes = of_get_child_by_name(tidss->dev->of_node,
+					 "oldi-transmitters");
+	if (!oldi_txes)
+		return 0;
+
+	/*
+	 * Look for primary OLDIs and create devices for them. For dual-link
+	 * cases, the primary's create_device call will also create the
+	 * secondary device.
+	 */
+	for_each_available_child_of_node(oldi_txes, oldi_tx) {
+		struct auxiliary_device *auxdev;
+
+		if (of_property_read_bool(oldi_tx, "ti,secondary-oldi"))
+			continue;
+
+		auxdev = tidss_oldi_create_device(tidss, oldi_tx);
+		if (IS_ERR(auxdev)) {
+			ret = PTR_ERR(auxdev);
+			goto err_destroy_oldis;
+		}
+	}
+
+	of_node_put(oldi_txes);
 
 	return 0;
 
-err_put_node:
-	of_node_put(child);
-	of_node_put(oldi_parent);
+err_destroy_oldis:
+	tidss_oldi_destroy_devices(tidss);
+
+	of_node_put(oldi_tx);
+	of_node_put(oldi_txes);
+
 	return ret;
 }
+
+void tidss_oldi_destroy_devices(struct tidss_device *tidss)
+{
+	for (unsigned int i = 0; i < tidss->num_oldis; ++i)
+		auxiliary_device_destroy(tidss->oldis[i]);
+}
+
+int tidss_oldi_register_driver(void)
+{
+	return auxiliary_driver_register(&oldi_aux_driver);
+}
+
+void tidss_oldi_unregister_driver(void)
+{
+	auxiliary_driver_unregister(&oldi_aux_driver);
+}
diff --git a/drivers/gpu/drm/tidss/tidss_oldi.h b/drivers/gpu/drm/tidss/tidss_oldi.h
index a361e6dbfce3..2069bd46aaae 100644
--- a/drivers/gpu/drm/tidss/tidss_oldi.h
+++ b/drivers/gpu/drm/tidss/tidss_oldi.h
@@ -36,7 +36,10 @@ enum tidss_oldi_link_type {
 	OLDI_MODE_SECONDARY_DUAL_LINK,
 };
 
-int tidss_oldi_init(struct tidss_device *tidss);
-void tidss_oldi_deinit(struct tidss_device *tidss);
+int tidss_oldi_create_devices(struct tidss_device *tidss);
+void tidss_oldi_destroy_devices(struct tidss_device *tidss);
+
+int tidss_oldi_register_driver(void);
+void tidss_oldi_unregister_driver(void);
 
 #endif /* __TIDSS_OLDI_H__ */

-- 
2.43.0


  parent reply	other threads:[~2026-04-20 12:54 UTC|newest]

Thread overview: 32+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-04-20 12:54 [PATCH 00/15] drm/tidss: Add BeagleY-AI display support (and some more) Tomi Valkeinen
2026-04-20 12:54 ` [PATCH 01/15] dt-bindings: display: ti: Move ti,am62l-dss binding to a new binding file Tomi Valkeinen
2026-04-22 23:45   ` Claude review: " Claude Code Review Bot
2026-04-20 12:54 ` [PATCH 02/15] dt-bindings: display: ti,am65x-dss: Simplify binding Tomi Valkeinen
2026-04-22 23:45   ` Claude review: " Claude Code Review Bot
2026-04-20 12:54 ` [PATCH 03/15] dt-bindings: mfd: syscon: Add ti,am625-dss-dpi0-clk-ctrl compatible Tomi Valkeinen
2026-04-22 23:45   ` Claude review: " Claude Code Review Bot
2026-04-20 12:54 ` [PATCH 04/15] dt-bindings: display: ti,am625-oldi: Add optional power-domain for OLDI Tomi Valkeinen
2026-04-22 23:45   ` Claude review: " Claude Code Review Bot
2026-04-20 12:54 ` [PATCH 05/15] dt-bindings: display: ti,am65x-dss: Add AM62P DSS Tomi Valkeinen
2026-04-22 23:45   ` Claude review: " Claude Code Review Bot
2026-04-20 12:54 ` [PATCH 06/15] drm/tidss: Remove extra pm_runtime_mark_last_busy Tomi Valkeinen
2026-04-22 23:45   ` Claude review: " Claude Code Review Bot
2026-04-20 12:54 ` [PATCH 07/15] drm/tidss: oldi: Remove define for unused register OLDI_LB_CTRL Tomi Valkeinen
2026-04-22 23:45   ` Claude review: " Claude Code Review Bot
2026-04-20 12:54 ` [PATCH 08/15] drm/tidss: Add mechanism to detect DPI output Tomi Valkeinen
2026-04-22 23:45   ` Claude review: " Claude Code Review Bot
2026-04-20 12:54 ` [PATCH 09/15] drm/tidss: Add external data and sync signal edge configuration Tomi Valkeinen
2026-04-22 23:45   ` Claude review: " Claude Code Review Bot
2026-04-20 12:54 ` [PATCH 10/15] drm/tidss: Add support for DPIENABLE bit Tomi Valkeinen
2026-04-22 23:45   ` Claude review: " Claude Code Review Bot
2026-04-20 12:54 ` [PATCH 11/15] drm/tidss: oldi: Fix OLDI signal polarities Tomi Valkeinen
2026-04-22 23:45   ` Claude review: " Claude Code Review Bot
2026-04-20 12:54 ` Tomi Valkeinen [this message]
2026-04-22 23:45   ` Claude review: drm/tidss: oldi: Convert OLDI to an aux driver Claude Code Review Bot
2026-04-20 12:54 ` [PATCH 13/15] drm/tidss: Add support for AM62P display subsystem Tomi Valkeinen
2026-04-22 23:46   ` Claude review: " Claude Code Review Bot
2026-04-20 12:54 ` [PATCH 14/15] arm64: dts: ti: k3-am62p-j722s-common-main: Add support for DSS Tomi Valkeinen
2026-04-22 23:46   ` Claude review: " Claude Code Review Bot
2026-04-20 12:54 ` [PATCH 15/15] arm64: dts: ti: beagley-ai: Enable HDMI display and audio Tomi Valkeinen
2026-04-22 23:46   ` Claude review: " Claude Code Review Bot
2026-04-22 23:45 ` Claude review: drm/tidss: Add BeagleY-AI display support (and some more) Claude Code Review Bot

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260420-beagley-ai-display-v1-12-f628543dfd14@ideasonboard.com \
    --to=tomi.valkeinen@ideasonboard.com \
    --cc=airlied@gmail.com \
    --cc=aradhya.bhatia@linux.dev \
    --cc=conor+dt@kernel.org \
    --cc=devarsht@ti.com \
    --cc=devicetree@vger.kernel.org \
    --cc=dri-devel@lists.freedesktop.org \
    --cc=krzk+dt@kernel.org \
    --cc=lee@kernel.org \
    --cc=linux-arm-kernel@lists.infradead.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=louis.chauvet@bootlin.com \
    --cc=maarten.lankhorst@linux.intel.com \
    --cc=mripard@kernel.org \
    --cc=nm@ti.com \
    --cc=robh@kernel.org \
    --cc=s-jain1@ti.com \
    --cc=simona@ffwll.ch \
    --cc=tzimmermann@suse.de \
    --cc=vigneshr@ti.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox