diff mbox

[4/5] drm/atmel-hlcdc: add support for connecting to tda998x HDMI encoder

Message ID 20180409105918.20792-5-peda@axentia.se (mailing list archive)
State New, archived
Headers show

Commit Message

Peter Rosin April 9, 2018, 10:59 a.m. UTC
When the of-graph points to a tda998x-compatible HDMI encoder, register
as a component master and bind to the encoder/connector provided by
the tda998x driver.

Signed-off-by: Peter Rosin <peda@axentia.se>
---
 drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.c     |  81 ++++++++++++--
 drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.h     |  15 +++
 drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_output.c | 130 +++++++++++++++++++++++
 3 files changed, 220 insertions(+), 6 deletions(-)
diff mbox

Patch

diff --git a/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.c b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.c
index dccd0be548a9..b411038890a9 100644
--- a/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.c
+++ b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.c
@@ -20,6 +20,7 @@ 
  */
 
 #include <linux/clk.h>
+#include <linux/component.h>
 #include <linux/irq.h>
 #include <linux/irqchip.h>
 #include <linux/module.h>
@@ -568,10 +569,13 @@  static int atmel_hlcdc_dc_modeset_init(struct drm_device *dev)
 
 	drm_mode_config_init(dev);
 
-	ret = atmel_hlcdc_create_outputs(dev);
-	if (ret) {
-		dev_err(dev->dev, "failed to create HLCDC outputs: %d\n", ret);
-		return ret;
+	if (!dc->is_componentized) {
+		ret = atmel_hlcdc_create_outputs(dev);
+		if (ret) {
+			dev_err(dev->dev,
+				"failed to create HLCDC outputs: %d\n", ret);
+			return ret;
+		}
 	}
 
 	ret = atmel_hlcdc_create_planes(dev);
@@ -586,6 +590,16 @@  static int atmel_hlcdc_dc_modeset_init(struct drm_device *dev)
 		return ret;
 	}
 
+	if (dc->is_componentized) {
+		ret = component_bind_all(dev->dev, dev);
+		if (ret < 0)
+			return ret;
+
+		ret = atmel_hlcdc_add_component_encoder(dev);
+		if (ret < 0)
+			return ret;
+	}
+
 	dev->mode_config.min_width = dc->desc->min_width;
 	dev->mode_config.min_height = dc->desc->min_height;
 	dev->mode_config.max_width = dc->desc->max_width;
@@ -618,6 +632,9 @@  static int atmel_hlcdc_dc_load(struct drm_device *dev)
 	if (!dc)
 		return -ENOMEM;
 
+	dc->is_componentized =
+		atmel_hlcdc_get_external_components(dev->dev, NULL) > 0;
+
 	dc->wq = alloc_ordered_workqueue("atmel-hlcdc-dc", 0);
 	if (!dc->wq)
 		return -ENOMEM;
@@ -770,7 +787,7 @@  static struct drm_driver atmel_hlcdc_dc_driver = {
 	.minor = 0,
 };
 
-static int atmel_hlcdc_dc_drm_probe(struct platform_device *pdev)
+static int atmel_hlcdc_dc_drm_init(struct platform_device *pdev)
 {
 	struct drm_device *ddev;
 	int ret;
@@ -798,7 +815,7 @@  static int atmel_hlcdc_dc_drm_probe(struct platform_device *pdev)
 	return ret;
 }
 
-static int atmel_hlcdc_dc_drm_remove(struct platform_device *pdev)
+static int atmel_hlcdc_dc_drm_fini(struct platform_device *pdev)
 {
 	struct drm_device *ddev = platform_get_drvdata(pdev);
 
@@ -809,6 +826,58 @@  static int atmel_hlcdc_dc_drm_remove(struct platform_device *pdev)
 	return 0;
 }
 
+static int atmel_hlcdc_bind(struct device *dev)
+{
+	return atmel_hlcdc_dc_drm_init(to_platform_device(dev));
+}
+
+static void atmel_hlcdc_unbind(struct device *dev)
+{
+	struct drm_device *ddev = dev_get_drvdata(dev);
+
+	/* Check if a subcomponent has already triggered the unloading. */
+	if (!ddev->dev_private)
+		return;
+
+	atmel_hlcdc_dc_drm_fini(to_platform_device(dev));
+}
+
+static const struct component_master_ops atmel_hlcdc_comp_ops = {
+	.bind = atmel_hlcdc_bind,
+	.unbind = atmel_hlcdc_unbind,
+};
+
+static int atmel_hlcdc_dc_drm_probe(struct platform_device *pdev)
+{
+	struct component_match *match = NULL;
+	int ret;
+
+	ret = atmel_hlcdc_get_external_components(&pdev->dev, &match);
+	if (ret < 0)
+		return ret;
+	else if (ret)
+		return component_master_add_with_match(&pdev->dev,
+						       &atmel_hlcdc_comp_ops,
+						       match);
+	else
+		return atmel_hlcdc_dc_drm_init(pdev);
+}
+
+static int atmel_hlcdc_dc_drm_remove(struct platform_device *pdev)
+{
+	int ret;
+
+	ret = atmel_hlcdc_get_external_components(&pdev->dev, NULL);
+	if (ret < 0)
+		return ret;
+	else if (ret)
+		component_master_del(&pdev->dev, &atmel_hlcdc_comp_ops);
+	else
+		atmel_hlcdc_dc_drm_fini(pdev);
+
+	return 0;
+}
+
 #ifdef CONFIG_PM_SLEEP
 static int atmel_hlcdc_dc_drm_suspend(struct device *dev)
 {
diff --git a/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.h b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.h
index a810171b9353..53f780827889 100644
--- a/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.h
+++ b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.h
@@ -377,6 +377,10 @@  struct atmel_hlcdc_plane_properties {
  * @wq: display controller workqueue
  * @suspend: used to store the HLCDC state when entering suspend
  * @commit: used for async commit handling
+ * @external_encoder: used encoder when componentized
+ * @external_connector: used connector when componentized
+ * @connector_funcs: original helper funcs of the external connector
+ * @is_componentized: operating mode
  */
 struct atmel_hlcdc_dc {
 	const struct atmel_hlcdc_dc_desc *desc;
@@ -394,6 +398,12 @@  struct atmel_hlcdc_dc {
 		wait_queue_head_t wait;
 		bool pending;
 	} commit;
+
+	struct drm_encoder *external_encoder;
+	struct drm_connector *external_connector;
+	const struct drm_connector_helper_funcs *connector_funcs;
+
+	bool is_componentized;
 };
 
 extern struct atmel_hlcdc_formats atmel_hlcdc_plane_rgb_formats;
@@ -463,4 +473,9 @@  int atmel_hlcdc_crtc_create(struct drm_device *dev);
 
 int atmel_hlcdc_create_outputs(struct drm_device *dev);
 
+struct component_match;
+int atmel_hlcdc_add_component_encoder(struct drm_device *dev);
+int atmel_hlcdc_get_external_components(struct device *dev,
+					struct component_match **match);
+
 #endif /* DRM_ATMEL_HLCDC_H */
diff --git a/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_output.c b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_output.c
index 8db51fb131db..3f86527e0473 100644
--- a/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_output.c
+++ b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_output.c
@@ -6,6 +6,11 @@ 
  * Author: Jean-Jacques Hiblot <jjhiblot@traphandler.com>
  * Author: Boris BREZILLON <boris.brezillon@free-electrons.com>
  *
+ * Handling of external components adapted from the tilcdc driver
+ * by Peter Rosin <peda@axentia.se>. That original code had:
+ *   Copyright (C) 2015 Texas Instruments
+ *   Author: Jyri Sarha <jsarha@ti.com>
+ *
  * This program is free software; you can redistribute it and/or modify it
  * under the terms of the GNU General Public License version 2 as published by
  * the Free Software Foundation.
@@ -19,6 +24,7 @@ 
  * this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
+#include <linux/component.h>
 #include <linux/of_graph.h>
 
 #include <drm/drmP.h>
@@ -88,3 +94,127 @@  int atmel_hlcdc_create_outputs(struct drm_device *dev)
 
 	return ret;
 }
+
+static int atmel_hlcdc_external_mode_valid(struct drm_connector *connector,
+					   struct drm_display_mode *mode)
+{
+	struct atmel_hlcdc_dc *dc = connector->dev->dev_private;
+	int ret;
+
+	ret = atmel_hlcdc_dc_mode_valid(dc, mode);
+	if (ret != MODE_OK)
+		return ret;
+
+	if (WARN_ON(dc->external_connector != connector))
+		return MODE_ERROR;
+	if (WARN_ON(!dc->connector_funcs))
+		return MODE_ERROR;
+
+	if (IS_ERR(dc->connector_funcs) || !dc->connector_funcs->mode_valid)
+		return MODE_OK;
+
+	/* The connector has its own mode_valid, call it. */
+	return dc->connector_funcs->mode_valid(connector, mode);
+}
+
+static int atmel_hlcdc_add_external_connector(struct drm_device *dev,
+					      struct drm_connector *connector)
+{
+	struct atmel_hlcdc_dc *dc = dev->dev_private;
+	struct drm_connector_helper_funcs *connector_funcs;
+
+	/* There should never be more than one connector. */
+	if (WARN_ON(dc->external_connector))
+		return -EINVAL;
+
+	dc->external_connector = connector;
+	connector_funcs = devm_kzalloc(dev->dev, sizeof(*connector_funcs),
+				       GFP_KERNEL);
+	if (!connector_funcs)
+		return -ENOMEM;
+
+	/*
+	 * connector->helper_private always contains the struct
+	 * connector_helper_funcs pointer. For the atmel-hlcdc crtc
+	 * to have a say if a specific mode is Ok, we install our
+	 * own helper functions. In our helper functions we copy
+	 * everything else but use our own mode_valid() (above).
+	 */
+	if (connector->helper_private) {
+		dc->connector_funcs = connector->helper_private;
+		*connector_funcs = *dc->connector_funcs;
+	} else {
+		dc->connector_funcs = ERR_PTR(-ENOENT);
+	}
+	connector_funcs->mode_valid = atmel_hlcdc_external_mode_valid;
+	drm_connector_helper_add(connector, connector_funcs);
+
+	dev_dbg(dev->dev, "External connector '%s' connected\n",
+		connector->name);
+
+	return 0;
+}
+
+static struct drm_connector *
+atmel_hlcdc_encoder_find_connector(struct drm_device *dev,
+				   struct drm_encoder *encoder)
+{
+	struct drm_connector *connector;
+	int i;
+
+	list_for_each_entry(connector, &dev->mode_config.connector_list, head)
+		for (i = 0; i < DRM_CONNECTOR_MAX_ENCODER; i++)
+			if (connector->encoder_ids[i] == encoder->base.id)
+				return connector;
+
+	dev_err(dev->dev, "No connector found for %s encoder (id %d)\n",
+		encoder->name, encoder->base.id);
+
+	return NULL;
+}
+
+int atmel_hlcdc_add_component_encoder(struct drm_device *dev)
+{
+	struct atmel_hlcdc_dc *dc = dev->dev_private;
+	struct drm_connector *connector;
+	struct drm_encoder *encoder;
+
+	list_for_each_entry(encoder, &dev->mode_config.encoder_list, head) {
+		if (encoder->possible_crtcs & (1 << dc->crtc->index))
+			break;
+	}
+
+	if (!encoder) {
+		dev_err(dev->dev, "%s: No suitable encoder found\n", __func__);
+		return -ENODEV;
+	}
+
+	connector = atmel_hlcdc_encoder_find_connector(dev, encoder);
+	if (!connector)
+		return -ENODEV;
+
+	return atmel_hlcdc_add_external_connector(dev, connector);
+}
+
+static int dev_match_of(struct device *dev, void *data)
+{
+	return dev->of_node == data;
+}
+
+int atmel_hlcdc_get_external_components(struct device *dev,
+					struct component_match **match)
+{
+	struct device_node *node;
+
+	node = of_graph_get_remote_node(dev->of_node, 0, 0);
+
+	if (!of_device_is_compatible(node, "nxp,tda998x")) {
+		of_node_put(node);
+		return 0;
+	}
+
+	if (match)
+		drm_of_component_match_add(dev, match, dev_match_of, node);
+	of_node_put(node);
+	return 1;
+}