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

From: Peter Rosin
Date: Tue Apr 17 2018 - 09:11:54 EST


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@xxxxxxxxxx>
---
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 --git a/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.c b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.c
index c1ea5c36b006..8523c40fac94 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;
@@ -617,6 +631,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;
@@ -751,7 +768,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;
@@ -779,7 +796,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);

@@ -790,6 +807,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 ab32d5b268d2..cae77c245661 100644
--- a/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.h
+++ b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.h
@@ -370,6 +370,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;
@@ -386,6 +390,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;
@@ -455,4 +465,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@xxxxxxxxxxxxxxx>
* Author: Boris BREZILLON <boris.brezillon@xxxxxxxxxxxxxxxxxx>
*
+ * Handling of external components adapted from the tilcdc driver
+ * by Peter Rosin <peda@xxxxxxxxxx>. That original code had:
+ * Copyright (C) 2015 Texas Instruments
+ * Author: Jyri Sarha <jsarha@xxxxxx>
+ *
* 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;
+}
--
2.11.0