1 // SPDX-License-Identifier: GPL-2.0 or MIT
2 /*
3  * Copyright (C) 2016 Noralf Trønnes
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  */
10 
11 #include <linux/module.h>
12 #include <linux/slab.h>
13 #include <linux/io.h>
14 
15 #include <drm/drm_format_helper.h>
16 #include <drm/drm_framebuffer.h>
17 #include <drm/drm_fourcc.h>
18 #include <drm/drm_rect.h>
19 
20 static unsigned int clip_offset(struct drm_rect *clip,
21 				unsigned int pitch, unsigned int cpp)
22 {
23 	return clip->y1 * pitch + clip->x1 * cpp;
24 }
25 
26 /**
27  * drm_fb_memcpy - Copy clip buffer
28  * @dst: Destination buffer
29  * @vaddr: Source buffer
30  * @fb: DRM framebuffer
31  * @clip: Clip rectangle area to copy
32  *
33  * This function does not apply clipping on dst, i.e. the destination
34  * is a small buffer containing the clip rect only.
35  */
36 void drm_fb_memcpy(void *dst, void *vaddr, struct drm_framebuffer *fb,
37 		   struct drm_rect *clip)
38 {
39 	unsigned int cpp = fb->format->cpp[0];
40 	size_t len = (clip->x2 - clip->x1) * cpp;
41 	unsigned int y, lines = clip->y2 - clip->y1;
42 
43 	vaddr += clip_offset(clip, fb->pitches[0], cpp);
44 	for (y = 0; y < lines; y++) {
45 		memcpy(dst, vaddr, len);
46 		vaddr += fb->pitches[0];
47 		dst += len;
48 	}
49 }
50 EXPORT_SYMBOL(drm_fb_memcpy);
51 
52 /**
53  * drm_fb_memcpy_dstclip - Copy clip buffer
54  * @dst: Destination buffer (iomem)
55  * @dst_pitch: Number of bytes between two consecutive scanlines within dst
56  * @vaddr: Source buffer
57  * @fb: DRM framebuffer
58  * @clip: Clip rectangle area to copy
59  *
60  * This function applies clipping on dst, i.e. the destination is a
61  * full (iomem) framebuffer but only the clip rect content is copied over.
62  */
63 void drm_fb_memcpy_dstclip(void __iomem *dst, unsigned int dst_pitch,
64 			   void *vaddr, struct drm_framebuffer *fb,
65 			   struct drm_rect *clip)
66 {
67 	unsigned int cpp = fb->format->cpp[0];
68 	unsigned int offset = clip_offset(clip, dst_pitch, cpp);
69 	size_t len = (clip->x2 - clip->x1) * cpp;
70 	unsigned int y, lines = clip->y2 - clip->y1;
71 
72 	vaddr += offset;
73 	dst += offset;
74 	for (y = 0; y < lines; y++) {
75 		memcpy_toio(dst, vaddr, len);
76 		vaddr += fb->pitches[0];
77 		dst += dst_pitch;
78 	}
79 }
80 EXPORT_SYMBOL(drm_fb_memcpy_dstclip);
81 
82 /**
83  * drm_fb_swab - Swap bytes into clip buffer
84  * @dst: Destination buffer
85  * @src: Source buffer
86  * @fb: DRM framebuffer
87  * @clip: Clip rectangle area to copy
88  * @cached: Source buffer is mapped cached (eg. not write-combined)
89  *
90  * If @cached is false a temporary buffer is used to cache one pixel line at a
91  * time to speed up slow uncached reads.
92  *
93  * This function does not apply clipping on dst, i.e. the destination
94  * is a small buffer containing the clip rect only.
95  */
96 void drm_fb_swab(void *dst, void *src, struct drm_framebuffer *fb,
97 		 struct drm_rect *clip, bool cached)
98 {
99 	u8 cpp = fb->format->cpp[0];
100 	size_t len = drm_rect_width(clip) * cpp;
101 	u16 *src16, *dst16 = dst;
102 	u32 *src32, *dst32 = dst;
103 	unsigned int x, y;
104 	void *buf = NULL;
105 
106 	if (WARN_ON_ONCE(cpp != 2 && cpp != 4))
107 		return;
108 
109 	if (!cached)
110 		buf = kmalloc(len, GFP_KERNEL);
111 
112 	src += clip_offset(clip, fb->pitches[0], cpp);
113 
114 	for (y = clip->y1; y < clip->y2; y++) {
115 		if (buf) {
116 			memcpy(buf, src, len);
117 			src16 = buf;
118 			src32 = buf;
119 		} else {
120 			src16 = src;
121 			src32 = src;
122 		}
123 
124 		for (x = clip->x1; x < clip->x2; x++) {
125 			if (cpp == 4)
126 				*dst32++ = swab32(*src32++);
127 			else
128 				*dst16++ = swab16(*src16++);
129 		}
130 
131 		src += fb->pitches[0];
132 	}
133 
134 	kfree(buf);
135 }
136 EXPORT_SYMBOL(drm_fb_swab);
137 
138 static void drm_fb_xrgb8888_to_rgb332_line(u8 *dbuf, __le32 *sbuf, unsigned int pixels)
139 {
140 	unsigned int x;
141 	u32 pix;
142 
143 	for (x = 0; x < pixels; x++) {
144 		pix = le32_to_cpu(sbuf[x]);
145 		dbuf[x] = ((pix & 0x00e00000) >> 16) |
146 			  ((pix & 0x0000e000) >> 11) |
147 			  ((pix & 0x000000c0) >> 6);
148 	}
149 }
150 
151 /**
152  * drm_fb_xrgb8888_to_rgb332 - Convert XRGB8888 to RGB332 clip buffer
153  * @dst: RGB332 destination buffer
154  * @src: XRGB8888 source buffer
155  * @fb: DRM framebuffer
156  * @clip: Clip rectangle area to copy
157  *
158  * Drivers can use this function for RGB332 devices that don't natively support XRGB8888.
159  *
160  * This function does not apply clipping on dst, i.e. the destination is a small buffer
161  * containing the clip rect only.
162  */
163 void drm_fb_xrgb8888_to_rgb332(void *dst, void *src, struct drm_framebuffer *fb,
164 			       struct drm_rect *clip)
165 {
166 	size_t width = drm_rect_width(clip);
167 	size_t src_len = width * sizeof(u32);
168 	unsigned int y;
169 	void *sbuf;
170 
171 	/* Use a buffer to speed up access on buffers with uncached read mapping (i.e. WC) */
172 	sbuf = kmalloc(src_len, GFP_KERNEL);
173 	if (!sbuf)
174 		return;
175 
176 	src += clip_offset(clip, fb->pitches[0], sizeof(u32));
177 	for (y = 0; y < drm_rect_height(clip); y++) {
178 		memcpy(sbuf, src, src_len);
179 		drm_fb_xrgb8888_to_rgb332_line(dst, sbuf, width);
180 		src += fb->pitches[0];
181 		dst += width;
182 	}
183 
184 	kfree(sbuf);
185 }
186 EXPORT_SYMBOL(drm_fb_xrgb8888_to_rgb332);
187 
188 static void drm_fb_xrgb8888_to_rgb565_line(u16 *dbuf, u32 *sbuf,
189 					   unsigned int pixels,
190 					   bool swab)
191 {
192 	unsigned int x;
193 	u16 val16;
194 
195 	for (x = 0; x < pixels; x++) {
196 		val16 = ((sbuf[x] & 0x00F80000) >> 8) |
197 			((sbuf[x] & 0x0000FC00) >> 5) |
198 			((sbuf[x] & 0x000000F8) >> 3);
199 		if (swab)
200 			dbuf[x] = swab16(val16);
201 		else
202 			dbuf[x] = val16;
203 	}
204 }
205 
206 /**
207  * drm_fb_xrgb8888_to_rgb565 - Convert XRGB8888 to RGB565 clip buffer
208  * @dst: RGB565 destination buffer
209  * @vaddr: XRGB8888 source buffer
210  * @fb: DRM framebuffer
211  * @clip: Clip rectangle area to copy
212  * @swab: Swap bytes
213  *
214  * Drivers can use this function for RGB565 devices that don't natively
215  * support XRGB8888.
216  *
217  * This function does not apply clipping on dst, i.e. the destination
218  * is a small buffer containing the clip rect only.
219  */
220 void drm_fb_xrgb8888_to_rgb565(void *dst, void *vaddr,
221 			       struct drm_framebuffer *fb,
222 			       struct drm_rect *clip, bool swab)
223 {
224 	size_t linepixels = clip->x2 - clip->x1;
225 	size_t src_len = linepixels * sizeof(u32);
226 	size_t dst_len = linepixels * sizeof(u16);
227 	unsigned y, lines = clip->y2 - clip->y1;
228 	void *sbuf;
229 
230 	/*
231 	 * The cma memory is write-combined so reads are uncached.
232 	 * Speed up by fetching one line at a time.
233 	 */
234 	sbuf = kmalloc(src_len, GFP_KERNEL);
235 	if (!sbuf)
236 		return;
237 
238 	vaddr += clip_offset(clip, fb->pitches[0], sizeof(u32));
239 	for (y = 0; y < lines; y++) {
240 		memcpy(sbuf, vaddr, src_len);
241 		drm_fb_xrgb8888_to_rgb565_line(dst, sbuf, linepixels, swab);
242 		vaddr += fb->pitches[0];
243 		dst += dst_len;
244 	}
245 
246 	kfree(sbuf);
247 }
248 EXPORT_SYMBOL(drm_fb_xrgb8888_to_rgb565);
249 
250 /**
251  * drm_fb_xrgb8888_to_rgb565_dstclip - Convert XRGB8888 to RGB565 clip buffer
252  * @dst: RGB565 destination buffer (iomem)
253  * @dst_pitch: destination buffer pitch
254  * @vaddr: XRGB8888 source buffer
255  * @fb: DRM framebuffer
256  * @clip: Clip rectangle area to copy
257  * @swab: Swap bytes
258  *
259  * Drivers can use this function for RGB565 devices that don't natively
260  * support XRGB8888.
261  *
262  * This function applies clipping on dst, i.e. the destination is a
263  * full (iomem) framebuffer but only the clip rect content is copied over.
264  */
265 void drm_fb_xrgb8888_to_rgb565_dstclip(void __iomem *dst, unsigned int dst_pitch,
266 				       void *vaddr, struct drm_framebuffer *fb,
267 				       struct drm_rect *clip, bool swab)
268 {
269 	size_t linepixels = clip->x2 - clip->x1;
270 	size_t dst_len = linepixels * sizeof(u16);
271 	unsigned y, lines = clip->y2 - clip->y1;
272 	void *dbuf;
273 
274 	dbuf = kmalloc(dst_len, GFP_KERNEL);
275 	if (!dbuf)
276 		return;
277 
278 	vaddr += clip_offset(clip, fb->pitches[0], sizeof(u32));
279 	dst += clip_offset(clip, dst_pitch, sizeof(u16));
280 	for (y = 0; y < lines; y++) {
281 		drm_fb_xrgb8888_to_rgb565_line(dbuf, vaddr, linepixels, swab);
282 		memcpy_toio(dst, dbuf, dst_len);
283 		vaddr += fb->pitches[0];
284 		dst += dst_len;
285 	}
286 
287 	kfree(dbuf);
288 }
289 EXPORT_SYMBOL(drm_fb_xrgb8888_to_rgb565_dstclip);
290 
291 static void drm_fb_xrgb8888_to_rgb888_line(u8 *dbuf, u32 *sbuf,
292 					   unsigned int pixels)
293 {
294 	unsigned int x;
295 
296 	for (x = 0; x < pixels; x++) {
297 		*dbuf++ = (sbuf[x] & 0x000000FF) >>  0;
298 		*dbuf++ = (sbuf[x] & 0x0000FF00) >>  8;
299 		*dbuf++ = (sbuf[x] & 0x00FF0000) >> 16;
300 	}
301 }
302 
303 /**
304  * drm_fb_xrgb8888_to_rgb888 - Convert XRGB8888 to RGB888 clip buffer
305  * @dst: RGB888 destination buffer
306  * @src: XRGB8888 source buffer
307  * @fb: DRM framebuffer
308  * @clip: Clip rectangle area to copy
309  *
310  * Drivers can use this function for RGB888 devices that don't natively
311  * support XRGB8888.
312  *
313  * This function does not apply clipping on dst, i.e. the destination
314  * is a small buffer containing the clip rect only.
315  */
316 void drm_fb_xrgb8888_to_rgb888(void *dst, void *src, struct drm_framebuffer *fb,
317 			       struct drm_rect *clip)
318 {
319 	size_t width = drm_rect_width(clip);
320 	size_t src_len = width * sizeof(u32);
321 	unsigned int y;
322 	void *sbuf;
323 
324 	/* Use a buffer to speed up access on buffers with uncached read mapping (i.e. WC) */
325 	sbuf = kmalloc(src_len, GFP_KERNEL);
326 	if (!sbuf)
327 		return;
328 
329 	src += clip_offset(clip, fb->pitches[0], sizeof(u32));
330 	for (y = 0; y < drm_rect_height(clip); y++) {
331 		memcpy(sbuf, src, src_len);
332 		drm_fb_xrgb8888_to_rgb888_line(dst, sbuf, width);
333 		src += fb->pitches[0];
334 		dst += width * 3;
335 	}
336 
337 	kfree(sbuf);
338 }
339 EXPORT_SYMBOL(drm_fb_xrgb8888_to_rgb888);
340 
341 /**
342  * drm_fb_xrgb8888_to_rgb888_dstclip - Convert XRGB8888 to RGB888 clip buffer
343  * @dst: RGB565 destination buffer (iomem)
344  * @dst_pitch: destination buffer pitch
345  * @vaddr: XRGB8888 source buffer
346  * @fb: DRM framebuffer
347  * @clip: Clip rectangle area to copy
348  *
349  * Drivers can use this function for RGB888 devices that don't natively
350  * support XRGB8888.
351  *
352  * This function applies clipping on dst, i.e. the destination is a
353  * full (iomem) framebuffer but only the clip rect content is copied over.
354  */
355 void drm_fb_xrgb8888_to_rgb888_dstclip(void __iomem *dst, unsigned int dst_pitch,
356 				       void *vaddr, struct drm_framebuffer *fb,
357 				       struct drm_rect *clip)
358 {
359 	size_t linepixels = clip->x2 - clip->x1;
360 	size_t dst_len = linepixels * 3;
361 	unsigned y, lines = clip->y2 - clip->y1;
362 	void *dbuf;
363 
364 	dbuf = kmalloc(dst_len, GFP_KERNEL);
365 	if (!dbuf)
366 		return;
367 
368 	vaddr += clip_offset(clip, fb->pitches[0], sizeof(u32));
369 	dst += clip_offset(clip, dst_pitch, sizeof(u16));
370 	for (y = 0; y < lines; y++) {
371 		drm_fb_xrgb8888_to_rgb888_line(dbuf, vaddr, linepixels);
372 		memcpy_toio(dst, dbuf, dst_len);
373 		vaddr += fb->pitches[0];
374 		dst += dst_len;
375 	}
376 
377 	kfree(dbuf);
378 }
379 EXPORT_SYMBOL(drm_fb_xrgb8888_to_rgb888_dstclip);
380 
381 /**
382  * drm_fb_xrgb8888_to_gray8 - Convert XRGB8888 to grayscale
383  * @dst: 8-bit grayscale destination buffer
384  * @vaddr: XRGB8888 source buffer
385  * @fb: DRM framebuffer
386  * @clip: Clip rectangle area to copy
387  *
388  * Drm doesn't have native monochrome or grayscale support.
389  * Such drivers can announce the commonly supported XR24 format to userspace
390  * and use this function to convert to the native format.
391  *
392  * Monochrome drivers will use the most significant bit,
393  * where 1 means foreground color and 0 background color.
394  *
395  * ITU BT.601 is used for the RGB -> luma (brightness) conversion.
396  */
397 void drm_fb_xrgb8888_to_gray8(u8 *dst, void *vaddr, struct drm_framebuffer *fb,
398 			       struct drm_rect *clip)
399 {
400 	unsigned int len = (clip->x2 - clip->x1) * sizeof(u32);
401 	unsigned int x, y;
402 	void *buf;
403 	u32 *src;
404 
405 	if (WARN_ON(fb->format->format != DRM_FORMAT_XRGB8888))
406 		return;
407 	/*
408 	 * The cma memory is write-combined so reads are uncached.
409 	 * Speed up by fetching one line at a time.
410 	 */
411 	buf = kmalloc(len, GFP_KERNEL);
412 	if (!buf)
413 		return;
414 
415 	for (y = clip->y1; y < clip->y2; y++) {
416 		src = vaddr + (y * fb->pitches[0]);
417 		src += clip->x1;
418 		memcpy(buf, src, len);
419 		src = buf;
420 		for (x = clip->x1; x < clip->x2; x++) {
421 			u8 r = (*src & 0x00ff0000) >> 16;
422 			u8 g = (*src & 0x0000ff00) >> 8;
423 			u8 b =  *src & 0x000000ff;
424 
425 			/* ITU BT.601: Y = 0.299 R + 0.587 G + 0.114 B */
426 			*dst++ = (3 * r + 6 * g + b) / 10;
427 			src++;
428 		}
429 	}
430 
431 	kfree(buf);
432 }
433 EXPORT_SYMBOL(drm_fb_xrgb8888_to_gray8);
434 
435 /**
436  * drm_fb_blit_rect_dstclip - Copy parts of a framebuffer to display memory
437  * @dst:	The display memory to copy to
438  * @dst_pitch:	Number of bytes between two consecutive scanlines within dst
439  * @dst_format:	FOURCC code of the display's color format
440  * @vmap:	The framebuffer memory to copy from
441  * @fb:		The framebuffer to copy from
442  * @clip:	Clip rectangle area to copy
443  *
444  * This function copies parts of a framebuffer to display memory. If the
445  * formats of the display and the framebuffer mismatch, the blit function
446  * will attempt to convert between them.
447  *
448  * Use drm_fb_blit_dstclip() to copy the full framebuffer.
449  *
450  * Returns:
451  * 0 on success, or
452  * -EINVAL if the color-format conversion failed, or
453  * a negative error code otherwise.
454  */
455 int drm_fb_blit_rect_dstclip(void __iomem *dst, unsigned int dst_pitch,
456 			     uint32_t dst_format, void *vmap,
457 			     struct drm_framebuffer *fb,
458 			     struct drm_rect *clip)
459 {
460 	uint32_t fb_format = fb->format->format;
461 
462 	/* treat alpha channel like filler bits */
463 	if (fb_format == DRM_FORMAT_ARGB8888)
464 		fb_format = DRM_FORMAT_XRGB8888;
465 	if (dst_format == DRM_FORMAT_ARGB8888)
466 		dst_format = DRM_FORMAT_XRGB8888;
467 
468 	if (dst_format == fb_format) {
469 		drm_fb_memcpy_dstclip(dst, dst_pitch, vmap, fb, clip);
470 		return 0;
471 
472 	} else if (dst_format == DRM_FORMAT_RGB565) {
473 		if (fb_format == DRM_FORMAT_XRGB8888) {
474 			drm_fb_xrgb8888_to_rgb565_dstclip(dst, dst_pitch,
475 							  vmap, fb, clip,
476 							  false);
477 			return 0;
478 		}
479 	} else if (dst_format == DRM_FORMAT_RGB888) {
480 		if (fb_format == DRM_FORMAT_XRGB8888) {
481 			drm_fb_xrgb8888_to_rgb888_dstclip(dst, dst_pitch,
482 							  vmap, fb, clip);
483 			return 0;
484 		}
485 	}
486 
487 	return -EINVAL;
488 }
489 EXPORT_SYMBOL(drm_fb_blit_rect_dstclip);
490 
491 /**
492  * drm_fb_blit_dstclip - Copy framebuffer to display memory
493  * @dst:	The display memory to copy to
494  * @dst_pitch:	Number of bytes between two consecutive scanlines within dst
495  * @dst_format:	FOURCC code of the display's color format
496  * @vmap:	The framebuffer memory to copy from
497  * @fb:		The framebuffer to copy from
498  *
499  * This function copies a full framebuffer to display memory. If the formats
500  * of the display and the framebuffer mismatch, the copy function will
501  * attempt to convert between them.
502  *
503  * See drm_fb_blit_rect_dstclip() for more information.
504  *
505  * Returns:
506  * 0 on success, or a negative error code otherwise.
507  */
508 int drm_fb_blit_dstclip(void __iomem *dst, unsigned int dst_pitch,
509 			uint32_t dst_format, void *vmap,
510 			struct drm_framebuffer *fb)
511 {
512 	struct drm_rect fullscreen = {
513 		.x1 = 0,
514 		.x2 = fb->width,
515 		.y1 = 0,
516 		.y2 = fb->height,
517 	};
518 	return drm_fb_blit_rect_dstclip(dst, dst_pitch, dst_format, vmap, fb,
519 					&fullscreen);
520 }
521 EXPORT_SYMBOL(drm_fb_blit_dstclip);
522