10bbce9ebSKuninori Morimoto// SPDX-License-Identifier: GPL-2.0+
24bf8e196SLaurent Pinchart/*
36978f123SLaurent Pinchart * rcar_du_encoder.c  --  R-Car Display Unit Encoder
44bf8e196SLaurent Pinchart *
536d50464SLaurent Pinchart * Copyright (C) 2013-2014 Renesas Electronics Corporation
64bf8e196SLaurent Pinchart *
74bf8e196SLaurent Pinchart * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
84bf8e196SLaurent Pinchart */
94bf8e196SLaurent Pinchart
1090374b5cSLaurent Pinchart#include <linux/export.h>
11f5f16725SLaurent Pinchart#include <linux/slab.h>
1290374b5cSLaurent Pinchart
13ee68c743SBoris Brezillon#include <drm/drm_bridge.h>
14c24110a8SLaurent Pinchart#include <drm/drm_bridge_connector.h>
154bf8e196SLaurent Pinchart#include <drm/drm_crtc.h>
16f5f16725SLaurent Pinchart#include <drm/drm_managed.h>
17fcd70cd3SDaniel Vetter#include <drm/drm_modeset_helper_vtables.h>
18bf7149f3SLaurent Pinchart#include <drm/drm_panel.h>
194bf8e196SLaurent Pinchart
204bf8e196SLaurent Pinchart#include "rcar_du_drv.h"
216978f123SLaurent Pinchart#include "rcar_du_encoder.h"
224bf8e196SLaurent Pinchart#include "rcar_du_kms.h"
238e8fddabSLaurent Pinchart#include "rcar_lvds.h"
244bf8e196SLaurent Pinchart
256978f123SLaurent Pinchart/* -----------------------------------------------------------------------------
266978f123SLaurent Pinchart * Encoder
276978f123SLaurent Pinchart */
286978f123SLaurent Pinchart
2973eb5476SLaurent Pinchartstatic unsigned int rcar_du_encoder_count_ports(struct device_node *node)
3073eb5476SLaurent Pinchart{
3173eb5476SLaurent Pinchart	struct device_node *ports;
3273eb5476SLaurent Pinchart	struct device_node *port;
3373eb5476SLaurent Pinchart	unsigned int num_ports = 0;
3473eb5476SLaurent Pinchart
3573eb5476SLaurent Pinchart	ports = of_get_child_by_name(node, "ports");
3673eb5476SLaurent Pinchart	if (!ports)
3773eb5476SLaurent Pinchart		ports = of_node_get(node);
3873eb5476SLaurent Pinchart
3973eb5476SLaurent Pinchart	for_each_child_of_node(ports, port) {
4073eb5476SLaurent Pinchart		if (of_node_name_eq(port, "port"))
4173eb5476SLaurent Pinchart			num_ports++;
4273eb5476SLaurent Pinchart	}
4373eb5476SLaurent Pinchart
4473eb5476SLaurent Pinchart	of_node_put(ports);
4573eb5476SLaurent Pinchart
4673eb5476SLaurent Pinchart	return num_ports;
4773eb5476SLaurent Pinchart}
4873eb5476SLaurent Pinchart
49f5f16725SLaurent Pinchartstatic const struct drm_encoder_funcs rcar_du_encoder_funcs = {
50f5f16725SLaurent Pinchart};
51f5f16725SLaurent Pinchart
526978f123SLaurent Pinchartint rcar_du_encoder_init(struct rcar_du_device *rcdu,
53ef67a902SLaurent Pinchart			 enum rcar_du_output output,
545aebc852SLaurent Pinchart			 struct device_node *enc_node)
554bf8e196SLaurent Pinchart{
564bf8e196SLaurent Pinchart	struct rcar_du_encoder *renc;
57c24110a8SLaurent Pinchart	struct drm_connector *connector;
5873eb5476SLaurent Pinchart	struct drm_bridge *bridge;
59c24110a8SLaurent Pinchart	int ret;
604bf8e196SLaurent Pinchart
6173eb5476SLaurent Pinchart	/*
6273eb5476SLaurent Pinchart	 * Locate the DRM bridge from the DT node. For the DPAD outputs, if the
6373eb5476SLaurent Pinchart	 * DT node has a single port, assume that it describes a panel and
6473eb5476SLaurent Pinchart	 * create a panel bridge.
6573eb5476SLaurent Pinchart	 */
6673eb5476SLaurent Pinchart	if ((output == RCAR_DU_OUTPUT_DPAD0 ||
6773eb5476SLaurent Pinchart	     output == RCAR_DU_OUTPUT_DPAD1) &&
6873eb5476SLaurent Pinchart	    rcar_du_encoder_count_ports(enc_node) == 1) {
6973eb5476SLaurent Pinchart		struct drm_panel *panel = of_drm_find_panel(enc_node);
7073eb5476SLaurent Pinchart
719a248605SLaurent Pinchart		if (IS_ERR(panel))
729a248605SLaurent Pinchart			return PTR_ERR(panel);
7373eb5476SLaurent Pinchart
7489958b7cSLaurent Pinchart		bridge = devm_drm_panel_bridge_add_typed(rcdu->dev, panel,
7589958b7cSLaurent Pinchart							 DRM_MODE_CONNECTOR_DPI);
769a248605SLaurent Pinchart		if (IS_ERR(bridge))
779a248605SLaurent Pinchart			return PTR_ERR(bridge);
7873eb5476SLaurent Pinchart	} else {
7973eb5476SLaurent Pinchart		bridge = of_drm_find_bridge(enc_node);
809a248605SLaurent Pinchart		if (!bridge)
819a248605SLaurent Pinchart			return -EPROBE_DEFER;
8253ced169SLaurent Pinchart
8353ced169SLaurent Pinchart		if (output == RCAR_DU_OUTPUT_LVDS0 ||
8453ced169SLaurent Pinchart		    output == RCAR_DU_OUTPUT_LVDS1)
8553ced169SLaurent Pinchart			rcdu->lvds[output - RCAR_DU_OUTPUT_LVDS0] = bridge;
86ef67a902SLaurent Pinchart	}
87ef67a902SLaurent Pinchart
888e8fddabSLaurent Pinchart	/*
89187502afSLaurent Pinchart	 * Create and initialize the encoder. On Gen3, skip the LVDS1 output if
909a248605SLaurent Pinchart	 * the LVDS1 encoder is used as a companion for LVDS0 in dual-link
91187502afSLaurent Pinchart	 * mode, or any LVDS output if it isn't connected. The latter may happen
92187502afSLaurent Pinchart	 * on D3 or E3 as the LVDS encoders are needed to provide the pixel
93187502afSLaurent Pinchart	 * clock to the DU, even when the LVDS outputs are not used.
948e8fddabSLaurent Pinchart	 */
95187502afSLaurent Pinchart	if (rcdu->info->gen >= 3) {
96187502afSLaurent Pinchart		if (output == RCAR_DU_OUTPUT_LVDS1 &&
97187502afSLaurent Pinchart		    rcar_lvds_dual_link(bridge))
98187502afSLaurent Pinchart			return -ENOLINK;
99187502afSLaurent Pinchart
100187502afSLaurent Pinchart		if ((output == RCAR_DU_OUTPUT_LVDS0 ||
101187502afSLaurent Pinchart		     output == RCAR_DU_OUTPUT_LVDS1) &&
102187502afSLaurent Pinchart		    !rcar_lvds_is_connected(bridge))
1039a248605SLaurent Pinchart			return -ENOLINK;
1048e8fddabSLaurent Pinchart	}
1058e8fddabSLaurent Pinchart
106206c5471SLaurent Pinchart	dev_dbg(rcdu->dev, "initializing encoder %pOF for output %s\n",
107206c5471SLaurent Pinchart		enc_node, rcar_du_output_name(output));
1089a248605SLaurent Pinchart
1097a1adbd2SKieran Bingham	renc = drmm_encoder_alloc(&rcdu->ddev, struct rcar_du_encoder, base,
1107a1adbd2SKieran Bingham				  &rcar_du_encoder_funcs, DRM_MODE_ENCODER_NONE,
1117a1adbd2SKieran Bingham				  NULL);
1127a1adbd2SKieran Bingham	if (!renc)
1137a1adbd2SKieran Bingham		return -ENOMEM;
114f5f16725SLaurent Pinchart
1157a1adbd2SKieran Bingham	renc->output = output;
1166978f123SLaurent Pinchart
117c24110a8SLaurent Pinchart	/* Attach the bridge to the encoder. */
118c24110a8SLaurent Pinchart	ret = drm_bridge_attach(&renc->base, bridge, NULL,
119c24110a8SLaurent Pinchart				DRM_BRIDGE_ATTACH_NO_CONNECTOR);
120c24110a8SLaurent Pinchart	if (ret) {
121206c5471SLaurent Pinchart		dev_err(rcdu->dev,
122206c5471SLaurent Pinchart			"failed to attach bridge %pOF for output %s (%d)\n",
123206c5471SLaurent Pinchart			bridge->of_node, rcar_du_output_name(output), ret);
124c24110a8SLaurent Pinchart		return ret;
125c24110a8SLaurent Pinchart	}
126c24110a8SLaurent Pinchart
127c24110a8SLaurent Pinchart	/* Create the connector for the chain of bridges. */
128c24110a8SLaurent Pinchart	connector = drm_bridge_connector_init(&rcdu->ddev, &renc->base);
129c24110a8SLaurent Pinchart	if (IS_ERR(connector)) {
130c24110a8SLaurent Pinchart		dev_err(rcdu->dev,
131206c5471SLaurent Pinchart			"failed to created connector for output %s (%ld)\n",
132206c5471SLaurent Pinchart			rcar_du_output_name(output), PTR_ERR(connector));
133c24110a8SLaurent Pinchart		return PTR_ERR(connector);
134c24110a8SLaurent Pinchart	}
135c24110a8SLaurent Pinchart
136c24110a8SLaurent Pinchart	return drm_connector_attach_encoder(connector, &renc->base);
1374bf8e196SLaurent Pinchart}
138