1// SPDX-License-Identifier: GPL-2.0-only
2/*
3 * Copyright (C) 2011 Texas Instruments Incorporated - http://www.ti.com/
4 * Author: Rob Clark <rob@ti.com>
5 */
6
7#include <drm/drm_atomic_helper.h>
8#include <drm/drm_crtc.h>
9#include <drm/drm_probe_helper.h>
10
11#include "omap_drv.h"
12
13/*
14 * connector funcs
15 */
16
17#define to_omap_connector(x) container_of(x, struct omap_connector, base)
18
19struct omap_connector {
20	struct drm_connector base;
21	struct omap_dss_device *output;
22};
23
24static enum drm_connector_status omap_connector_detect(
25		struct drm_connector *connector, bool force)
26{
27	return connector_status_connected;
28}
29
30static void omap_connector_destroy(struct drm_connector *connector)
31{
32	struct omap_connector *omap_connector = to_omap_connector(connector);
33
34	DBG("%s", connector->name);
35
36	drm_connector_unregister(connector);
37	drm_connector_cleanup(connector);
38
39	omapdss_device_put(omap_connector->output);
40
41	kfree(omap_connector);
42}
43
44static int omap_connector_get_modes(struct drm_connector *connector)
45{
46	struct omap_connector *omap_connector = to_omap_connector(connector);
47	struct omap_dss_device *dssdev = NULL;
48	struct omap_dss_device *d;
49
50	DBG("%s", connector->name);
51
52	/*
53	 * If the display pipeline reports modes (e.g. with a fixed resolution
54	 * panel or an analog TV output), query it.
55	 */
56	for (d = omap_connector->output; d; d = d->next) {
57		if (d->ops_flags & OMAP_DSS_DEVICE_OP_MODES)
58			dssdev = d;
59	}
60
61	if (dssdev)
62		return dssdev->ops->get_modes(dssdev, connector);
63
64	/* We can't retrieve modes. The KMS core will add the default modes. */
65	return 0;
66}
67
68enum drm_mode_status omap_connector_mode_fixup(struct omap_dss_device *dssdev,
69					const struct drm_display_mode *mode,
70					struct drm_display_mode *adjusted_mode)
71{
72	int ret;
73
74	drm_mode_copy(adjusted_mode, mode);
75
76	for (; dssdev; dssdev = dssdev->next) {
77		if (!dssdev->ops || !dssdev->ops->check_timings)
78			continue;
79
80		ret = dssdev->ops->check_timings(dssdev, adjusted_mode);
81		if (ret)
82			return MODE_BAD;
83	}
84
85	return MODE_OK;
86}
87
88static enum drm_mode_status omap_connector_mode_valid(struct drm_connector *connector,
89				 struct drm_display_mode *mode)
90{
91	struct omap_connector *omap_connector = to_omap_connector(connector);
92	struct drm_display_mode new_mode = {};
93	enum drm_mode_status status;
94
95	status = omap_connector_mode_fixup(omap_connector->output, mode,
96					   &new_mode);
97	if (status != MODE_OK)
98		goto done;
99
100	/* Check if vrefresh is still valid. */
101	if (drm_mode_vrefresh(mode) != drm_mode_vrefresh(&new_mode))
102		status = MODE_NOCLOCK;
103
104done:
105	DBG("connector: mode %s: " DRM_MODE_FMT,
106			(status == MODE_OK) ? "valid" : "invalid",
107			DRM_MODE_ARG(mode));
108
109	return status;
110}
111
112static const struct drm_connector_funcs omap_connector_funcs = {
113	.reset = drm_atomic_helper_connector_reset,
114	.detect = omap_connector_detect,
115	.fill_modes = drm_helper_probe_single_connector_modes,
116	.destroy = omap_connector_destroy,
117	.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
118	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
119};
120
121static const struct drm_connector_helper_funcs omap_connector_helper_funcs = {
122	.get_modes = omap_connector_get_modes,
123	.mode_valid = omap_connector_mode_valid,
124};
125
126/* initialize connector */
127struct drm_connector *omap_connector_init(struct drm_device *dev,
128					  struct omap_dss_device *output,
129					  struct drm_encoder *encoder)
130{
131	struct drm_connector *connector = NULL;
132	struct omap_connector *omap_connector;
133
134	DBG("%s", output->name);
135
136	omap_connector = kzalloc(sizeof(*omap_connector), GFP_KERNEL);
137	if (!omap_connector)
138		goto fail;
139
140	omap_connector->output = omapdss_device_get(output);
141
142	connector = &omap_connector->base;
143	connector->interlace_allowed = 1;
144	connector->doublescan_allowed = 0;
145
146	drm_connector_init(dev, connector, &omap_connector_funcs,
147			   DRM_MODE_CONNECTOR_DSI);
148	drm_connector_helper_add(connector, &omap_connector_helper_funcs);
149
150	return connector;
151
152fail:
153	if (connector)
154		omap_connector_destroy(connector);
155
156	return NULL;
157}
158