summaryrefslogtreecommitdiff
path: root/drivers/gpu/drm/vkms/vkms_writeback.c
blob: 84a51cd281b9cb47e50dc60200818dd214b7556c (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
// SPDX-License-Identifier: GPL-2.0+

#include <linux/iosys-map.h>

#include <drm/drm_atomic.h>
#include <drm/drm_edid.h>
#include <drm/drm_fourcc.h>
#include <drm/drm_writeback.h>
#include <drm/drm_probe_helper.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_gem_framebuffer_helper.h>
#include <drm/drm_gem_shmem_helper.h>

#include "vkms_drv.h"
#include "vkms_formats.h"

static const u32 vkms_wb_formats[] = {
	DRM_FORMAT_XRGB8888,
	DRM_FORMAT_XRGB16161616,
	DRM_FORMAT_ARGB16161616,
	DRM_FORMAT_RGB565
};

static const struct drm_connector_funcs vkms_wb_connector_funcs = {
	.fill_modes = drm_helper_probe_single_connector_modes,
	.destroy = drm_connector_cleanup,
	.reset = drm_atomic_helper_connector_reset,
	.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
};

static int vkms_wb_encoder_atomic_check(struct drm_encoder *encoder,
					struct drm_crtc_state *crtc_state,
					struct drm_connector_state *conn_state)
{
	struct drm_framebuffer *fb;
	const struct drm_display_mode *mode = &crtc_state->mode;
	int ret;

	if (!conn_state->writeback_job || !conn_state->writeback_job->fb)
		return 0;

	fb = conn_state->writeback_job->fb;
	if (fb->width != mode->hdisplay || fb->height != mode->vdisplay) {
		DRM_DEBUG_KMS("Invalid framebuffer size %ux%u\n",
			      fb->width, fb->height);
		return -EINVAL;
	}

	ret = drm_atomic_helper_check_wb_encoder_state(encoder, conn_state);
	if (ret < 0)
		return ret;

	return 0;
}

static const struct drm_encoder_helper_funcs vkms_wb_encoder_helper_funcs = {
	.atomic_check = vkms_wb_encoder_atomic_check,
};

static int vkms_wb_connector_get_modes(struct drm_connector *connector)
{
	struct drm_device *dev = connector->dev;

	return drm_add_modes_noedid(connector, dev->mode_config.max_width,
				    dev->mode_config.max_height);
}

static int vkms_wb_prepare_job(struct drm_writeback_connector *wb_connector,
			       struct drm_writeback_job *job)
{
	struct vkms_writeback_job *vkmsjob;
	int ret;

	if (!job->fb)
		return 0;

	vkmsjob = kzalloc(sizeof(*vkmsjob), GFP_KERNEL);
	if (!vkmsjob)
		return -ENOMEM;

	ret = drm_gem_fb_vmap(job->fb, vkmsjob->wb_frame_info.map, vkmsjob->data);
	if (ret) {
		DRM_ERROR("vmap failed: %d\n", ret);
		goto err_kfree;
	}

	vkmsjob->wb_frame_info.fb = job->fb;
	drm_framebuffer_get(vkmsjob->wb_frame_info.fb);

	job->priv = vkmsjob;

	return 0;

err_kfree:
	kfree(vkmsjob);
	return ret;
}

static void vkms_wb_cleanup_job(struct drm_writeback_connector *connector,
				struct drm_writeback_job *job)
{
	struct vkms_writeback_job *vkmsjob = job->priv;
	struct vkms_device *vkmsdev;

	if (!job->fb)
		return;

	drm_gem_fb_vunmap(job->fb, vkmsjob->wb_frame_info.map);

	drm_framebuffer_put(vkmsjob->wb_frame_info.fb);

	vkmsdev = drm_device_to_vkms_device(job->fb->dev);
	vkms_set_composer(&vkmsdev->output, false);
	kfree(vkmsjob);
}

static void vkms_wb_atomic_commit(struct drm_connector *conn,
				  struct drm_atomic_state *state)
{
	struct drm_connector_state *connector_state = drm_atomic_get_new_connector_state(state,
											 conn);
	struct vkms_device *vkmsdev = drm_device_to_vkms_device(conn->dev);
	struct vkms_output *output = &vkmsdev->output;
	struct drm_writeback_connector *wb_conn = &output->wb_connector;
	struct drm_connector_state *conn_state = wb_conn->base.state;
	struct vkms_crtc_state *crtc_state = output->composer_state;
	struct drm_framebuffer *fb = connector_state->writeback_job->fb;
	u16 crtc_height = crtc_state->base.crtc->mode.vdisplay;
	u16 crtc_width = crtc_state->base.crtc->mode.hdisplay;
	struct vkms_writeback_job *active_wb;
	struct vkms_frame_info *wb_frame_info;
	u32 wb_format = fb->format->format;

	if (!conn_state)
		return;

	vkms_set_composer(&vkmsdev->output, true);

	active_wb = conn_state->writeback_job->priv;
	wb_frame_info = &active_wb->wb_frame_info;

	spin_lock_irq(&output->composer_lock);
	crtc_state->active_writeback = active_wb;
	wb_frame_info->offset = fb->offsets[0];
	wb_frame_info->pitch = fb->pitches[0];
	wb_frame_info->cpp = fb->format->cpp[0];
	crtc_state->wb_pending = true;
	spin_unlock_irq(&output->composer_lock);
	drm_writeback_queue_job(wb_conn, connector_state);
	active_wb->wb_write = get_line_to_frame_function(wb_format);
	drm_rect_init(&wb_frame_info->src, 0, 0, crtc_width, crtc_height);
	drm_rect_init(&wb_frame_info->dst, 0, 0, crtc_width, crtc_height);
}

static const struct drm_connector_helper_funcs vkms_wb_conn_helper_funcs = {
	.get_modes = vkms_wb_connector_get_modes,
	.prepare_writeback_job = vkms_wb_prepare_job,
	.cleanup_writeback_job = vkms_wb_cleanup_job,
	.atomic_commit = vkms_wb_atomic_commit,
};

int vkms_enable_writeback_connector(struct vkms_device *vkmsdev)
{
	struct drm_writeback_connector *wb = &vkmsdev->output.wb_connector;

	drm_connector_helper_add(&wb->base, &vkms_wb_conn_helper_funcs);

	return drm_writeback_connector_init(&vkmsdev->drm, wb,
					    &vkms_wb_connector_funcs,
					    &vkms_wb_encoder_helper_funcs,
					    vkms_wb_formats,
					    ARRAY_SIZE(vkms_wb_formats),
					    1);
}