xref: /linux/drivers/gpu/drm/sysfb/drm_sysfb_modeset.c (revision bdfa82f5b8998a6311a8ef0cf89ad413f5cd9ea4)
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 int drm_sysfb_plane_helper_atomic_check(struct drm_plane *plane,
51 					struct drm_atomic_state *new_state)
52 {
53 	struct drm_sysfb_device *sysfb = to_drm_sysfb_device(plane->dev);
54 	struct drm_plane_state *new_plane_state = drm_atomic_get_new_plane_state(new_state, plane);
55 	struct drm_shadow_plane_state *new_shadow_plane_state =
56 		to_drm_shadow_plane_state(new_plane_state);
57 	struct drm_framebuffer *new_fb = new_plane_state->fb;
58 	struct drm_crtc *new_crtc = new_plane_state->crtc;
59 	struct drm_crtc_state *new_crtc_state = NULL;
60 	struct drm_sysfb_crtc_state *new_sysfb_crtc_state;
61 	int ret;
62 
63 	if (new_crtc)
64 		new_crtc_state = drm_atomic_get_new_crtc_state(new_state, new_plane_state->crtc);
65 
66 	ret = drm_atomic_helper_check_plane_state(new_plane_state, new_crtc_state,
67 						  DRM_PLANE_NO_SCALING,
68 						  DRM_PLANE_NO_SCALING,
69 						  false, false);
70 	if (ret)
71 		return ret;
72 	else if (!new_plane_state->visible)
73 		return 0;
74 
75 	if (new_fb->format != sysfb->fb_format) {
76 		void *buf;
77 
78 		/* format conversion necessary; reserve buffer */
79 		buf = drm_format_conv_state_reserve(&new_shadow_plane_state->fmtcnv_state,
80 						    sysfb->fb_pitch, GFP_KERNEL);
81 		if (!buf)
82 			return -ENOMEM;
83 	}
84 
85 	new_crtc_state = drm_atomic_get_new_crtc_state(new_state, new_plane_state->crtc);
86 
87 	new_sysfb_crtc_state = to_drm_sysfb_crtc_state(new_crtc_state);
88 	new_sysfb_crtc_state->format = new_fb->format;
89 
90 	return 0;
91 }
92 EXPORT_SYMBOL(drm_sysfb_plane_helper_atomic_check);
93 
94 void drm_sysfb_plane_helper_atomic_update(struct drm_plane *plane, struct drm_atomic_state *state)
95 {
96 	struct drm_device *dev = plane->dev;
97 	struct drm_sysfb_device *sysfb = to_drm_sysfb_device(dev);
98 	struct drm_plane_state *plane_state = drm_atomic_get_new_plane_state(state, plane);
99 	struct drm_plane_state *old_plane_state = drm_atomic_get_old_plane_state(state, plane);
100 	struct drm_shadow_plane_state *shadow_plane_state = to_drm_shadow_plane_state(plane_state);
101 	struct drm_framebuffer *fb = plane_state->fb;
102 	unsigned int dst_pitch = sysfb->fb_pitch;
103 	const struct drm_format_info *dst_format = sysfb->fb_format;
104 	struct drm_atomic_helper_damage_iter iter;
105 	struct drm_rect damage;
106 	int ret, idx;
107 
108 	ret = drm_gem_fb_begin_cpu_access(fb, DMA_FROM_DEVICE);
109 	if (ret)
110 		return;
111 
112 	if (!drm_dev_enter(dev, &idx))
113 		goto out_drm_gem_fb_end_cpu_access;
114 
115 	drm_atomic_helper_damage_iter_init(&iter, old_plane_state, plane_state);
116 	drm_atomic_for_each_plane_damage(&iter, &damage) {
117 		struct iosys_map dst = sysfb->fb_addr;
118 		struct drm_rect dst_clip = plane_state->dst;
119 
120 		if (!drm_rect_intersect(&dst_clip, &damage))
121 			continue;
122 
123 		iosys_map_incr(&dst, drm_fb_clip_offset(dst_pitch, dst_format, &dst_clip));
124 		drm_fb_blit(&dst, &dst_pitch, dst_format->format, shadow_plane_state->data, fb,
125 			    &damage, &shadow_plane_state->fmtcnv_state);
126 	}
127 
128 	drm_dev_exit(idx);
129 out_drm_gem_fb_end_cpu_access:
130 	drm_gem_fb_end_cpu_access(fb, DMA_FROM_DEVICE);
131 }
132 EXPORT_SYMBOL(drm_sysfb_plane_helper_atomic_update);
133 
134 void drm_sysfb_plane_helper_atomic_disable(struct drm_plane *plane,
135 					   struct drm_atomic_state *state)
136 {
137 	struct drm_device *dev = plane->dev;
138 	struct drm_sysfb_device *sysfb = to_drm_sysfb_device(dev);
139 	struct iosys_map dst = sysfb->fb_addr;
140 	struct drm_plane_state *plane_state = drm_atomic_get_new_plane_state(state, plane);
141 	void __iomem *dst_vmap = dst.vaddr_iomem; /* TODO: Use mapping abstraction */
142 	unsigned int dst_pitch = sysfb->fb_pitch;
143 	const struct drm_format_info *dst_format = sysfb->fb_format;
144 	struct drm_rect dst_clip;
145 	unsigned long lines, linepixels, i;
146 	int idx;
147 
148 	drm_rect_init(&dst_clip,
149 		      plane_state->src_x >> 16, plane_state->src_y >> 16,
150 		      plane_state->src_w >> 16, plane_state->src_h >> 16);
151 
152 	lines = drm_rect_height(&dst_clip);
153 	linepixels = drm_rect_width(&dst_clip);
154 
155 	if (!drm_dev_enter(dev, &idx))
156 		return;
157 
158 	/* Clear buffer to black if disabled */
159 	dst_vmap += drm_fb_clip_offset(dst_pitch, dst_format, &dst_clip);
160 	for (i = 0; i < lines; ++i) {
161 		memset_io(dst_vmap, 0, linepixels * dst_format->cpp[0]);
162 		dst_vmap += dst_pitch;
163 	}
164 
165 	drm_dev_exit(idx);
166 }
167 EXPORT_SYMBOL(drm_sysfb_plane_helper_atomic_disable);
168 
169 int drm_sysfb_plane_helper_get_scanout_buffer(struct drm_plane *plane,
170 					      struct drm_scanout_buffer *sb)
171 {
172 	struct drm_sysfb_device *sysfb = to_drm_sysfb_device(plane->dev);
173 
174 	sb->width = sysfb->fb_mode.hdisplay;
175 	sb->height = sysfb->fb_mode.vdisplay;
176 	sb->format = sysfb->fb_format;
177 	sb->pitch[0] = sysfb->fb_pitch;
178 	sb->map[0] = sysfb->fb_addr;
179 
180 	return 0;
181 }
182 EXPORT_SYMBOL(drm_sysfb_plane_helper_get_scanout_buffer);
183 
184 /*
185  * CRTC
186  */
187 
188 static void drm_sysfb_crtc_state_destroy(struct drm_sysfb_crtc_state *sysfb_crtc_state)
189 {
190 	__drm_atomic_helper_crtc_destroy_state(&sysfb_crtc_state->base);
191 
192 	kfree(sysfb_crtc_state);
193 }
194 
195 enum drm_mode_status drm_sysfb_crtc_helper_mode_valid(struct drm_crtc *crtc,
196 						      const struct drm_display_mode *mode)
197 {
198 	struct drm_sysfb_device *sysfb = to_drm_sysfb_device(crtc->dev);
199 
200 	return drm_crtc_helper_mode_valid_fixed(crtc, mode, &sysfb->fb_mode);
201 }
202 EXPORT_SYMBOL(drm_sysfb_crtc_helper_mode_valid);
203 
204 int drm_sysfb_crtc_helper_atomic_check(struct drm_crtc *crtc, struct drm_atomic_state *new_state)
205 {
206 	struct drm_device *dev = crtc->dev;
207 	struct drm_sysfb_device *sysfb = to_drm_sysfb_device(dev);
208 	struct drm_crtc_state *new_crtc_state = drm_atomic_get_new_crtc_state(new_state, crtc);
209 	int ret;
210 
211 	if (!new_crtc_state->enable)
212 		return 0;
213 
214 	ret = drm_atomic_helper_check_crtc_primary_plane(new_crtc_state);
215 	if (ret)
216 		return ret;
217 
218 	if (new_crtc_state->color_mgmt_changed) {
219 		const size_t gamma_lut_length =
220 			sysfb->fb_gamma_lut_size * sizeof(struct drm_color_lut);
221 		const struct drm_property_blob *gamma_lut = new_crtc_state->gamma_lut;
222 
223 		if (gamma_lut && (gamma_lut->length != gamma_lut_length)) {
224 			drm_dbg(dev, "Incorrect gamma_lut length %zu\n", gamma_lut->length);
225 			return -EINVAL;
226 		}
227 	}
228 
229 	return 0;
230 }
231 EXPORT_SYMBOL(drm_sysfb_crtc_helper_atomic_check);
232 
233 void drm_sysfb_crtc_reset(struct drm_crtc *crtc)
234 {
235 	struct drm_sysfb_crtc_state *sysfb_crtc_state;
236 
237 	if (crtc->state)
238 		drm_sysfb_crtc_state_destroy(to_drm_sysfb_crtc_state(crtc->state));
239 
240 	sysfb_crtc_state = kzalloc(sizeof(*sysfb_crtc_state), GFP_KERNEL);
241 	if (sysfb_crtc_state)
242 		__drm_atomic_helper_crtc_reset(crtc, &sysfb_crtc_state->base);
243 	else
244 		__drm_atomic_helper_crtc_reset(crtc, NULL);
245 }
246 EXPORT_SYMBOL(drm_sysfb_crtc_reset);
247 
248 struct drm_crtc_state *drm_sysfb_crtc_atomic_duplicate_state(struct drm_crtc *crtc)
249 {
250 	struct drm_device *dev = crtc->dev;
251 	struct drm_crtc_state *crtc_state = crtc->state;
252 	struct drm_sysfb_crtc_state *new_sysfb_crtc_state;
253 	struct drm_sysfb_crtc_state *sysfb_crtc_state;
254 
255 	if (drm_WARN_ON(dev, !crtc_state))
256 		return NULL;
257 
258 	new_sysfb_crtc_state = kzalloc(sizeof(*new_sysfb_crtc_state), GFP_KERNEL);
259 	if (!new_sysfb_crtc_state)
260 		return NULL;
261 
262 	sysfb_crtc_state = to_drm_sysfb_crtc_state(crtc_state);
263 
264 	__drm_atomic_helper_crtc_duplicate_state(crtc, &new_sysfb_crtc_state->base);
265 	new_sysfb_crtc_state->format = sysfb_crtc_state->format;
266 
267 	return &new_sysfb_crtc_state->base;
268 }
269 EXPORT_SYMBOL(drm_sysfb_crtc_atomic_duplicate_state);
270 
271 void drm_sysfb_crtc_atomic_destroy_state(struct drm_crtc *crtc, struct drm_crtc_state *crtc_state)
272 {
273 	drm_sysfb_crtc_state_destroy(to_drm_sysfb_crtc_state(crtc_state));
274 }
275 EXPORT_SYMBOL(drm_sysfb_crtc_atomic_destroy_state);
276 
277 /*
278  * Connector
279  */
280 
281 static int drm_sysfb_get_edid_block(void *data, u8 *buf, unsigned int block, size_t len)
282 {
283 	struct drm_sysfb_device *sysfb = data;
284 	const u8 *edid = sysfb->edid;
285 	size_t off = block * EDID_LENGTH;
286 	size_t end = off + len;
287 
288 	if (!edid)
289 		return -EINVAL;
290 	if (end > EDID_LENGTH)
291 		return -EINVAL;
292 	memcpy(buf, &edid[off], len);
293 
294 	/*
295 	 * We don't have EDID extensions available and reporting them
296 	 * will upset DRM helpers. Thus clear the extension field and
297 	 * update the checksum. Adding the extension flag to the checksum
298 	 * does this.
299 	 */
300 	buf[127] += buf[126];
301 	buf[126] = 0;
302 
303 	return 0;
304 }
305 
306 int drm_sysfb_connector_helper_get_modes(struct drm_connector *connector)
307 {
308 	struct drm_sysfb_device *sysfb = to_drm_sysfb_device(connector->dev);
309 	const struct drm_edid *drm_edid;
310 
311 	if (sysfb->edid) {
312 		drm_edid = drm_edid_read_custom(connector, drm_sysfb_get_edid_block, sysfb);
313 		drm_edid_connector_update(connector, drm_edid);
314 		drm_edid_free(drm_edid);
315 	}
316 
317 	/* Return the fixed mode even with EDID */
318 	return drm_connector_helper_get_modes_fixed(connector, &sysfb->fb_mode);
319 }
320 EXPORT_SYMBOL(drm_sysfb_connector_helper_get_modes);
321