aboutsummaryrefslogtreecommitdiff
path: root/drivers/gpu/drm/drm_virtual_encoder.c
blob: d5e24dfc27ea2c032eb82c02711ed9904d0b41bc (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
/*
 * Copyright (C) 2016 ARM Limited
 * Author: Liviu Dudau <Liviu.Dudau@arm.com>
 *
 * Dummy encoder and connector that use the OF to "discover" the attached
 * display timings. Can be used in situations where the encoder and connector's
 * functionality are emulated and no setup steps are needed, or to describe
 * attached panels for which no driver exists but can be used without
 * additional hardware setup.
 *
 * The encoder also uses the component framework so that it can be a quick
 * replacement for existing drivers when testing in an emulated environment.
 *
 * This file is subject to the terms and conditions of the GNU General Public
 * License.  See the file COPYING in the main directory of this archive
 * for more details.
 *
 */

#include <drm/drmP.h>
#include <drm/drm_crtc.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_crtc_helper.h>
#include <drm/drm_of.h>
#include <linux/component.h>
#include <video/display_timing.h>
#include <video/of_display_timing.h>
#include <video/videomode.h>

struct drm_virt_priv {
	struct drm_connector connector;
	struct drm_encoder encoder;
	struct display_timings *timings;
};

#define connector_to_drm_virt_priv(x) \
	container_of(x, struct drm_virt_priv, connector)

#define encoder_to_drm_virt_priv(x) \
	container_of(x, struct drm_virt_priv, encoder)

static void drm_virtcon_destroy(struct drm_connector *connector)
{
	struct drm_virt_priv *conn = connector_to_drm_virt_priv(connector);

	drm_connector_cleanup(connector);
	display_timings_release(conn->timings);
}

static enum drm_connector_status
drm_virtcon_detect(struct drm_connector *connector, bool force)
{
	return connector_status_connected;
}

static const struct drm_connector_funcs drm_virtcon_funcs = {
	.dpms = drm_atomic_helper_connector_dpms,
	.reset = drm_atomic_helper_connector_reset,
	.detect	= drm_virtcon_detect,
	.fill_modes = drm_helper_probe_single_connector_modes,
	.destroy = drm_virtcon_destroy,
	.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
};

static int drm_virtcon_get_modes(struct drm_connector *connector)
{
	struct drm_virt_priv *conn = connector_to_drm_virt_priv(connector);
	struct display_timings *timings = conn->timings;
	int i;

	for (i = 0; i < timings->num_timings; i++) {
		struct drm_display_mode *mode = drm_mode_create(connector->dev);
		struct videomode vm;

		if (videomode_from_timings(timings, &vm, i))
			break;

		drm_display_mode_from_videomode(&vm, mode);
		mode->type = DRM_MODE_TYPE_DRIVER;
		if (timings->native_mode == i)
			mode->type = DRM_MODE_TYPE_PREFERRED;

		drm_mode_set_name(mode);
		drm_mode_probed_add(connector, mode);
	}

	return i;
}

static int drm_virtcon_mode_valid(struct drm_connector *connector,
				   struct drm_display_mode *mode)
{
	return MODE_OK;
}

struct drm_encoder *drm_virtcon_best_encoder(struct drm_connector *connector)
{
	struct drm_virt_priv *priv = connector_to_drm_virt_priv(connector);

	return &priv->encoder;
}

struct drm_encoder *
drm_virtcon_atomic_best_encoder(struct drm_connector *connector,
				 struct drm_connector_state *connector_state)
{
	struct drm_virt_priv *priv = connector_to_drm_virt_priv(connector);

	return &priv->encoder;
}

static const struct drm_connector_helper_funcs drm_virtcon_helper_funcs = {
	.get_modes = drm_virtcon_get_modes,
	.mode_valid = drm_virtcon_mode_valid,
	.best_encoder = drm_virtcon_best_encoder,
	.atomic_best_encoder = drm_virtcon_atomic_best_encoder,
};

static void drm_vencoder_destroy(struct drm_encoder *encoder)
{
	drm_encoder_cleanup(encoder);
}

static const struct drm_encoder_funcs drm_vencoder_funcs = {
	.destroy = drm_vencoder_destroy,
};

static void drm_vencoder_dpms(struct drm_encoder *encoder, int mode)
{
	/* nothing needed */
}

static bool drm_vencoder_mode_fixup(struct drm_encoder *encoder,
				    const struct drm_display_mode *mode,
				    struct drm_display_mode *adjusted_mode)
{
	/* nothing needed */
	return true;
}

static void drm_vencoder_prepare(struct drm_encoder *encoder)
{
	drm_vencoder_dpms(encoder, DRM_MODE_DPMS_OFF);
}

static void drm_vencoder_commit(struct drm_encoder *encoder)
{
	drm_vencoder_dpms(encoder, DRM_MODE_DPMS_ON);
}

static void drm_vencoder_mode_set(struct drm_encoder *encoder,
				  struct drm_display_mode *mode,
				  struct drm_display_mode *adjusted_mode)
{
	/* nothing needed */
}

static const struct drm_encoder_helper_funcs drm_vencoder_helper_funcs = {
	.dpms		= drm_vencoder_dpms,
	.mode_fixup	= drm_vencoder_mode_fixup,
	.prepare	= drm_vencoder_prepare,
	.commit		= drm_vencoder_commit,
	.mode_set	= drm_vencoder_mode_set,
};

static int drm_vencoder_bind(struct device *dev, struct device *master,
			     void *data)
{
	struct drm_encoder *encoder;
	struct drm_virt_priv *con;
	struct drm_connector *connector;
	struct drm_device *drm = data;
	u32 crtcs = 0;
	int ret;

	con = devm_kzalloc(dev, sizeof(*con), GFP_KERNEL);
	if (!con)
		return -ENOMEM;

	dev_set_drvdata(dev, con);
	connector = &con->connector;
	encoder = &con->encoder;

	if (dev->of_node) {
		struct drm_bridge *bridge;
		crtcs = drm_of_find_possible_crtcs(drm, dev->of_node);
		bridge = of_drm_find_bridge(dev->of_node);
		if (bridge) {
			ret = drm_bridge_attach(encoder, bridge, NULL);
			if (ret) {
				DRM_ERROR("Failed to initialize bridge\n");
				return ret;
			}
			encoder->bridge = bridge;
		}
		con->timings = of_get_display_timings(dev->of_node);
		if (!con->timings) {
			dev_err(dev, "failed to get display panel timings\n");
			return ENXIO;
		}
	}

	/* If no CRTCs were found, fall back to the old encoder's behaviour */
	if (crtcs == 0) {
		dev_warn(dev, "Falling back to first CRTC\n");
		crtcs = 1 << 0;
	}

	encoder->possible_crtcs = crtcs ? crtcs : 1;
	encoder->possible_clones = 0;

	ret = drm_encoder_init(drm, encoder, &drm_vencoder_funcs,
			       DRM_MODE_ENCODER_VIRTUAL, "virtual-encoder");
	if (ret)
		goto encoder_init_err;

	drm_encoder_helper_add(encoder, &drm_vencoder_helper_funcs);

	/* bogus values, pretend we're a 24" screen for DPI calculations */
	connector->display_info.width_mm = 519;
	connector->display_info.height_mm = 324;
	connector->interlace_allowed = false;
	connector->doublescan_allowed = false;
	connector->polled = 0;

	ret = drm_connector_init(drm, connector, &drm_virtcon_funcs,
				 DRM_MODE_CONNECTOR_VIRTUAL);
	if (ret)
		goto connector_init_err;

	drm_connector_helper_add(connector, &drm_virtcon_helper_funcs);

	drm_connector_register(connector);

	ret = drm_mode_connector_attach_encoder(connector, encoder);
	if (ret)
		goto attach_err;

	return ret;

attach_err:
	drm_connector_unregister(connector);
	drm_connector_cleanup(connector);
connector_init_err:
	drm_encoder_cleanup(encoder);
encoder_init_err:
	display_timings_release(con->timings);

	return ret;
};

static void drm_vencoder_unbind(struct device *dev, struct device *master,
				void *data)
{
	struct drm_virt_priv *con = dev_get_drvdata(dev);

	drm_connector_unregister(&con->connector);
	drm_connector_cleanup(&con->connector);
	drm_encoder_cleanup(&con->encoder);
	display_timings_release(con->timings);
}

static const struct component_ops drm_vencoder_ops = {
	.bind = drm_vencoder_bind,
	.unbind = drm_vencoder_unbind,
};

static int drm_vencoder_probe(struct platform_device *pdev)
{
	return component_add(&pdev->dev, &drm_vencoder_ops);
}

static int drm_vencoder_remove(struct platform_device *pdev)
{
	component_del(&pdev->dev, &drm_vencoder_ops);
	return 0;
}

static const struct of_device_id drm_vencoder_of_match[] = {
	{ .compatible = "drm,virtual-encoder", },
	{},
};
MODULE_DEVICE_TABLE(of, drm_vencoder_of_match);

static struct platform_driver drm_vencoder_driver = {
	.probe = drm_vencoder_probe,
	.remove = drm_vencoder_remove,
	.driver = {
		.name = "drm_vencoder",
		.of_match_table = drm_vencoder_of_match,
	},
};

module_platform_driver(drm_vencoder_driver);

MODULE_AUTHOR("Liviu Dudau");
MODULE_DESCRIPTION("Virtual DRM Encoder");
MODULE_LICENSE("GPL v2");