xref: /linux/drivers/gpu/drm/sysfb/drm_sysfb_modeset.c (revision 44343e8b250abb2f6bfd615493ca07a7f11f3cc2)
1 // SPDX-License-Identifier: GPL-2.0-only
2 
3 #include <linux/export.h>
4 #include <linux/slab.h>
5 
6 #include <drm/drm_atomic.h>
7 #include <drm/drm_atomic_helper.h>
8 #include <drm/drm_atomic_state_helper.h>
9 #include <drm/drm_damage_helper.h>
10 #include <drm/drm_drv.h>
11 #include <drm/drm_edid.h>
12 #include <drm/drm_fourcc.h>
13 #include <drm/drm_framebuffer.h>
14 #include <drm/drm_gem_atomic_helper.h>
15 #include <drm/drm_gem_framebuffer_helper.h>
16 #include <drm/drm_panic.h>
17 #include <drm/drm_print.h>
18 #include <drm/drm_probe_helper.h>
19 
20 #include "drm_sysfb_helper.h"
21 
22 struct drm_display_mode drm_sysfb_mode(unsigned int width,
23 				       unsigned int height,
24 				       unsigned int width_mm,
25 				       unsigned int height_mm)
26 {
27 	/*
28 	 * Assume a monitor resolution of 96 dpi to
29 	 * get a somewhat reasonable screen size.
30 	 */
31 	if (!width_mm)
32 		width_mm = DRM_MODE_RES_MM(width, 96ul);
33 	if (!height_mm)
34 		height_mm = DRM_MODE_RES_MM(height, 96ul);
35 
36 	{
37 		const struct drm_display_mode mode = {
38 			DRM_MODE_INIT(60, width, height, width_mm, height_mm)
39 		};
40 
41 		return mode;
42 	}
43 }
44 EXPORT_SYMBOL(drm_sysfb_mode);
45 
46 /*
47  * Plane
48  */
49 
50 static u32 to_nonalpha_fourcc(u32 fourcc)
51 {
52 	/* only handle formats with depth != 0 and alpha channel */
53 	switch (fourcc) {
54 	case DRM_FORMAT_ARGB1555:
55 		return DRM_FORMAT_XRGB1555;
56 	case DRM_FORMAT_ABGR1555:
57 		return DRM_FORMAT_XBGR1555;
58 	case DRM_FORMAT_RGBA5551:
59 		return DRM_FORMAT_RGBX5551;
60 	case DRM_FORMAT_BGRA5551:
61 		return DRM_FORMAT_BGRX5551;
62 	case DRM_FORMAT_ARGB8888:
63 		return DRM_FORMAT_XRGB8888;
64 	case DRM_FORMAT_ABGR8888:
65 		return DRM_FORMAT_XBGR8888;
66 	case DRM_FORMAT_RGBA8888:
67 		return DRM_FORMAT_RGBX8888;
68 	case DRM_FORMAT_BGRA8888:
69 		return DRM_FORMAT_BGRX8888;
70 	case DRM_FORMAT_ARGB2101010:
71 		return DRM_FORMAT_XRGB2101010;
72 	case DRM_FORMAT_ABGR2101010:
73 		return DRM_FORMAT_XBGR2101010;
74 	case DRM_FORMAT_RGBA1010102:
75 		return DRM_FORMAT_RGBX1010102;
76 	case DRM_FORMAT_BGRA1010102:
77 		return DRM_FORMAT_BGRX1010102;
78 	}
79 
80 	return fourcc;
81 }
82 
83 static bool is_listed_fourcc(const u32 *fourccs, size_t nfourccs, u32 fourcc)
84 {
85 	const u32 *fourccs_end = fourccs + nfourccs;
86 
87 	while (fourccs < fourccs_end) {
88 		if (*fourccs == fourcc)
89 			return true;
90 		++fourccs;
91 	}
92 	return false;
93 }
94 
95 /**
96  * drm_sysfb_build_fourcc_list - Filters a list of supported color formats against
97  *                               the device's native formats
98  * @dev: DRM device
99  * @native_fourccs: 4CC codes of natively supported color formats
100  * @native_nfourccs: The number of entries in @native_fourccs
101  * @fourccs_out: Returns 4CC codes of supported color formats
102  * @nfourccs_out: The number of available entries in @fourccs_out
103  *
104  * This function create a list of supported color format from natively
105  * supported formats and additional emulated formats.
106  * At a minimum, most userspace programs expect at least support for
107  * XRGB8888 on the primary plane. Sysfb devices that have to emulate
108  * the format should use drm_sysfb_build_fourcc_list() to create a list
109  * of supported color formats. The returned list can be handed over to
110  * drm_universal_plane_init() et al. Native formats will go before
111  * emulated formats. Native formats with alpha channel will be replaced
112  * by equal formats without alpha channel, as primary planes usually
113  * don't support alpha. Other heuristics might be applied to optimize
114  * the sorting order. Formats near the beginning of the list are usually
115  * preferred over formats near the end of the list.
116  *
117  * Returns:
118  * The number of color-formats 4CC codes returned in @fourccs_out.
119  */
120 size_t drm_sysfb_build_fourcc_list(struct drm_device *dev,
121 				   const u32 *native_fourccs, size_t native_nfourccs,
122 				   u32 *fourccs_out, size_t nfourccs_out)
123 {
124 	/*
125 	 * XRGB8888 is the default fallback format for most of userspace
126 	 * and it's currently the only format that should be emulated for
127 	 * the primary plane. Only if there's ever another default fallback,
128 	 * it should be added here.
129 	 */
130 	static const u32 extra_fourccs[] = {
131 		DRM_FORMAT_XRGB8888,
132 	};
133 	static const size_t extra_nfourccs = ARRAY_SIZE(extra_fourccs);
134 
135 	u32 *fourccs = fourccs_out;
136 	const u32 *fourccs_end = fourccs_out + nfourccs_out;
137 	size_t i;
138 
139 	/*
140 	 * The device's native formats go first.
141 	 */
142 
143 	for (i = 0; i < native_nfourccs; ++i) {
144 		/*
145 		 * Several DTs, boot loaders and firmware report native
146 		 * alpha formats that are non-alpha formats instead. So
147 		 * replace alpha formats by non-alpha formats.
148 		 */
149 		u32 fourcc = to_nonalpha_fourcc(native_fourccs[i]);
150 
151 		if (is_listed_fourcc(fourccs_out, fourccs - fourccs_out, fourcc)) {
152 			continue; /* skip duplicate entries */
153 		} else if (fourccs == fourccs_end) {
154 			drm_warn(dev, "Ignoring native format %p4cc\n", &fourcc);
155 			continue; /* end of available output buffer */
156 		}
157 
158 		drm_dbg_kms(dev, "adding native format %p4cc\n", &fourcc);
159 
160 		*fourccs = fourcc;
161 		++fourccs;
162 	}
163 
164 	/*
165 	 * The extra formats, emulated by the driver, go second.
166 	 */
167 
168 	for (i = 0; (i < extra_nfourccs) && (fourccs < fourccs_end); ++i) {
169 		u32 fourcc = extra_fourccs[i];
170 
171 		if (is_listed_fourcc(fourccs_out, fourccs - fourccs_out, fourcc)) {
172 			continue; /* skip duplicate and native entries */
173 		} else if (fourccs == fourccs_end) {
174 			drm_warn(dev, "Ignoring emulated format %p4cc\n", &fourcc);
175 			continue; /* end of available output buffer */
176 		}
177 
178 		drm_dbg_kms(dev, "adding emulated format %p4cc\n", &fourcc);
179 
180 		*fourccs = fourcc;
181 		++fourccs;
182 	}
183 
184 	return fourccs - fourccs_out;
185 }
186 EXPORT_SYMBOL(drm_sysfb_build_fourcc_list);
187 
188 int drm_sysfb_plane_helper_atomic_check(struct drm_plane *plane,
189 					struct drm_atomic_state *new_state)
190 {
191 	struct drm_sysfb_device *sysfb = to_drm_sysfb_device(plane->dev);
192 	struct drm_plane_state *new_plane_state = drm_atomic_get_new_plane_state(new_state, plane);
193 	struct drm_shadow_plane_state *new_shadow_plane_state =
194 		to_drm_shadow_plane_state(new_plane_state);
195 	struct drm_framebuffer *new_fb = new_plane_state->fb;
196 	struct drm_crtc *new_crtc = new_plane_state->crtc;
197 	struct drm_crtc_state *new_crtc_state = NULL;
198 	struct drm_sysfb_crtc_state *new_sysfb_crtc_state;
199 	int ret;
200 
201 	if (new_crtc)
202 		new_crtc_state = drm_atomic_get_new_crtc_state(new_state, new_plane_state->crtc);
203 
204 	ret = drm_atomic_helper_check_plane_state(new_plane_state, new_crtc_state,
205 						  DRM_PLANE_NO_SCALING,
206 						  DRM_PLANE_NO_SCALING,
207 						  false, false);
208 	if (ret)
209 		return ret;
210 	else if (!new_plane_state->visible)
211 		return 0;
212 
213 	new_crtc_state = drm_atomic_get_new_crtc_state(new_state, new_plane_state->crtc);
214 
215 	new_sysfb_crtc_state = to_drm_sysfb_crtc_state(new_crtc_state);
216 	new_sysfb_crtc_state->format = sysfb->fb_format;
217 
218 	if (new_fb->format != new_sysfb_crtc_state->format) {
219 		void *buf;
220 
221 		/* format conversion necessary; reserve buffer */
222 		buf = drm_format_conv_state_reserve(&new_shadow_plane_state->fmtcnv_state,
223 						    sysfb->fb_pitch, GFP_KERNEL);
224 		if (!buf)
225 			return -ENOMEM;
226 	}
227 
228 	return 0;
229 }
230 EXPORT_SYMBOL(drm_sysfb_plane_helper_atomic_check);
231 
232 void drm_sysfb_plane_helper_atomic_update(struct drm_plane *plane, struct drm_atomic_state *state)
233 {
234 	struct drm_device *dev = plane->dev;
235 	struct drm_sysfb_device *sysfb = to_drm_sysfb_device(dev);
236 	struct drm_plane_state *plane_state = drm_atomic_get_new_plane_state(state, plane);
237 	struct drm_plane_state *old_plane_state = drm_atomic_get_old_plane_state(state, plane);
238 	struct drm_shadow_plane_state *shadow_plane_state = to_drm_shadow_plane_state(plane_state);
239 	struct drm_framebuffer *fb = plane_state->fb;
240 	unsigned int dst_pitch = sysfb->fb_pitch;
241 	struct drm_crtc_state *crtc_state = crtc_state =
242 		drm_atomic_get_new_crtc_state(state, plane_state->crtc);
243 	struct drm_sysfb_crtc_state *sysfb_crtc_state = to_drm_sysfb_crtc_state(crtc_state);
244 	const struct drm_format_info *dst_format = sysfb_crtc_state->format;
245 	struct drm_atomic_helper_damage_iter iter;
246 	struct drm_rect damage;
247 	int ret, idx;
248 
249 	ret = drm_gem_fb_begin_cpu_access(fb, DMA_FROM_DEVICE);
250 	if (ret)
251 		return;
252 
253 	if (!drm_dev_enter(dev, &idx))
254 		goto out_drm_gem_fb_end_cpu_access;
255 
256 	drm_atomic_helper_damage_iter_init(&iter, old_plane_state, plane_state);
257 	drm_atomic_for_each_plane_damage(&iter, &damage) {
258 		struct iosys_map dst = sysfb->fb_addr;
259 		struct drm_rect dst_clip = plane_state->dst;
260 
261 		if (!drm_rect_intersect(&dst_clip, &damage))
262 			continue;
263 
264 		iosys_map_incr(&dst, drm_fb_clip_offset(dst_pitch, dst_format, &dst_clip));
265 		drm_fb_blit(&dst, &dst_pitch, dst_format->format, shadow_plane_state->data, fb,
266 			    &damage, &shadow_plane_state->fmtcnv_state);
267 	}
268 
269 	drm_dev_exit(idx);
270 out_drm_gem_fb_end_cpu_access:
271 	drm_gem_fb_end_cpu_access(fb, DMA_FROM_DEVICE);
272 }
273 EXPORT_SYMBOL(drm_sysfb_plane_helper_atomic_update);
274 
275 void drm_sysfb_plane_helper_atomic_disable(struct drm_plane *plane,
276 					   struct drm_atomic_state *state)
277 {
278 	struct drm_device *dev = plane->dev;
279 	struct drm_sysfb_device *sysfb = to_drm_sysfb_device(dev);
280 	struct iosys_map dst = sysfb->fb_addr;
281 	struct drm_plane_state *plane_state = drm_atomic_get_new_plane_state(state, plane);
282 	void __iomem *dst_vmap = dst.vaddr_iomem; /* TODO: Use mapping abstraction */
283 	unsigned int dst_pitch = sysfb->fb_pitch;
284 	const struct drm_format_info *dst_format = sysfb->fb_format;
285 	struct drm_rect dst_clip;
286 	unsigned long lines, linepixels, i;
287 	int idx;
288 
289 	drm_rect_init(&dst_clip,
290 		      plane_state->src_x >> 16, plane_state->src_y >> 16,
291 		      plane_state->src_w >> 16, plane_state->src_h >> 16);
292 
293 	lines = drm_rect_height(&dst_clip);
294 	linepixels = drm_rect_width(&dst_clip);
295 
296 	if (!drm_dev_enter(dev, &idx))
297 		return;
298 
299 	/* Clear buffer to black if disabled */
300 	dst_vmap += drm_fb_clip_offset(dst_pitch, dst_format, &dst_clip);
301 	for (i = 0; i < lines; ++i) {
302 		memset_io(dst_vmap, 0, linepixels * dst_format->cpp[0]);
303 		dst_vmap += dst_pitch;
304 	}
305 
306 	drm_dev_exit(idx);
307 }
308 EXPORT_SYMBOL(drm_sysfb_plane_helper_atomic_disable);
309 
310 int drm_sysfb_plane_helper_get_scanout_buffer(struct drm_plane *plane,
311 					      struct drm_scanout_buffer *sb)
312 {
313 	struct drm_sysfb_device *sysfb = to_drm_sysfb_device(plane->dev);
314 
315 	sb->width = sysfb->fb_mode.hdisplay;
316 	sb->height = sysfb->fb_mode.vdisplay;
317 	sb->format = sysfb->fb_format;
318 	sb->pitch[0] = sysfb->fb_pitch;
319 	sb->map[0] = sysfb->fb_addr;
320 
321 	return 0;
322 }
323 EXPORT_SYMBOL(drm_sysfb_plane_helper_get_scanout_buffer);
324 
325 /*
326  * CRTC
327  */
328 
329 static void drm_sysfb_crtc_state_destroy(struct drm_sysfb_crtc_state *sysfb_crtc_state)
330 {
331 	__drm_atomic_helper_crtc_destroy_state(&sysfb_crtc_state->base);
332 
333 	kfree(sysfb_crtc_state);
334 }
335 
336 enum drm_mode_status drm_sysfb_crtc_helper_mode_valid(struct drm_crtc *crtc,
337 						      const struct drm_display_mode *mode)
338 {
339 	struct drm_sysfb_device *sysfb = to_drm_sysfb_device(crtc->dev);
340 
341 	return drm_crtc_helper_mode_valid_fixed(crtc, mode, &sysfb->fb_mode);
342 }
343 EXPORT_SYMBOL(drm_sysfb_crtc_helper_mode_valid);
344 
345 int drm_sysfb_crtc_helper_atomic_check(struct drm_crtc *crtc, struct drm_atomic_state *new_state)
346 {
347 	struct drm_device *dev = crtc->dev;
348 	struct drm_sysfb_device *sysfb = to_drm_sysfb_device(dev);
349 	struct drm_crtc_state *new_crtc_state = drm_atomic_get_new_crtc_state(new_state, crtc);
350 	int ret;
351 
352 	if (!new_crtc_state->enable)
353 		return 0;
354 
355 	ret = drm_atomic_helper_check_crtc_primary_plane(new_crtc_state);
356 	if (ret)
357 		return ret;
358 
359 	if (new_crtc_state->color_mgmt_changed) {
360 		const size_t gamma_lut_length =
361 			sysfb->fb_gamma_lut_size * sizeof(struct drm_color_lut);
362 		const struct drm_property_blob *gamma_lut = new_crtc_state->gamma_lut;
363 
364 		if (gamma_lut && (gamma_lut->length != gamma_lut_length)) {
365 			drm_dbg(dev, "Incorrect gamma_lut length %zu\n", gamma_lut->length);
366 			return -EINVAL;
367 		}
368 	}
369 
370 	return 0;
371 }
372 EXPORT_SYMBOL(drm_sysfb_crtc_helper_atomic_check);
373 
374 void drm_sysfb_crtc_reset(struct drm_crtc *crtc)
375 {
376 	struct drm_sysfb_device *sysfb = to_drm_sysfb_device(crtc->dev);
377 	struct drm_sysfb_crtc_state *sysfb_crtc_state;
378 
379 	if (crtc->state)
380 		drm_sysfb_crtc_state_destroy(to_drm_sysfb_crtc_state(crtc->state));
381 
382 	sysfb_crtc_state = kzalloc(sizeof(*sysfb_crtc_state), GFP_KERNEL);
383 	if (sysfb_crtc_state) {
384 		sysfb_crtc_state->format = sysfb->fb_format;
385 		__drm_atomic_helper_crtc_reset(crtc, &sysfb_crtc_state->base);
386 	} else {
387 		__drm_atomic_helper_crtc_reset(crtc, NULL);
388 	}
389 }
390 EXPORT_SYMBOL(drm_sysfb_crtc_reset);
391 
392 struct drm_crtc_state *drm_sysfb_crtc_atomic_duplicate_state(struct drm_crtc *crtc)
393 {
394 	struct drm_device *dev = crtc->dev;
395 	struct drm_crtc_state *crtc_state = crtc->state;
396 	struct drm_sysfb_crtc_state *new_sysfb_crtc_state;
397 	struct drm_sysfb_crtc_state *sysfb_crtc_state;
398 
399 	if (drm_WARN_ON(dev, !crtc_state))
400 		return NULL;
401 
402 	new_sysfb_crtc_state = kzalloc(sizeof(*new_sysfb_crtc_state), GFP_KERNEL);
403 	if (!new_sysfb_crtc_state)
404 		return NULL;
405 
406 	sysfb_crtc_state = to_drm_sysfb_crtc_state(crtc_state);
407 
408 	__drm_atomic_helper_crtc_duplicate_state(crtc, &new_sysfb_crtc_state->base);
409 	new_sysfb_crtc_state->format = sysfb_crtc_state->format;
410 
411 	return &new_sysfb_crtc_state->base;
412 }
413 EXPORT_SYMBOL(drm_sysfb_crtc_atomic_duplicate_state);
414 
415 void drm_sysfb_crtc_atomic_destroy_state(struct drm_crtc *crtc, struct drm_crtc_state *crtc_state)
416 {
417 	drm_sysfb_crtc_state_destroy(to_drm_sysfb_crtc_state(crtc_state));
418 }
419 EXPORT_SYMBOL(drm_sysfb_crtc_atomic_destroy_state);
420 
421 /*
422  * Connector
423  */
424 
425 static int drm_sysfb_get_edid_block(void *data, u8 *buf, unsigned int block, size_t len)
426 {
427 	struct drm_sysfb_device *sysfb = data;
428 	const u8 *edid = sysfb->edid;
429 	size_t off = block * EDID_LENGTH;
430 	size_t end = off + len;
431 
432 	if (!edid)
433 		return -EINVAL;
434 	if (end > EDID_LENGTH)
435 		return -EINVAL;
436 	memcpy(buf, &edid[off], len);
437 
438 	/*
439 	 * We don't have EDID extensions available and reporting them
440 	 * will upset DRM helpers. Thus clear the extension field and
441 	 * update the checksum. Adding the extension flag to the checksum
442 	 * does this.
443 	 */
444 	buf[127] += buf[126];
445 	buf[126] = 0;
446 
447 	return 0;
448 }
449 
450 int drm_sysfb_connector_helper_get_modes(struct drm_connector *connector)
451 {
452 	struct drm_sysfb_device *sysfb = to_drm_sysfb_device(connector->dev);
453 	const struct drm_edid *drm_edid;
454 
455 	if (sysfb->edid) {
456 		drm_edid = drm_edid_read_custom(connector, drm_sysfb_get_edid_block, sysfb);
457 		drm_edid_connector_update(connector, drm_edid);
458 		drm_edid_free(drm_edid);
459 	}
460 
461 	/* Return the fixed mode even with EDID */
462 	return drm_connector_helper_get_modes_fixed(connector, &sysfb->fb_mode);
463 }
464 EXPORT_SYMBOL(drm_sysfb_connector_helper_get_modes);
465