xref: /linux/drivers/video/fbdev/core/fb_logo.c (revision 8e07e0e3964ca4e23ce7b68e2096fe660a888942)
1 // SPDX-License-Identifier: GPL-2.0
2 
3 #include <linux/fb.h>
4 #include <linux/linux_logo.h>
5 
6 #include "fb_internal.h"
7 
8 bool fb_center_logo __read_mostly;
9 int fb_logo_count __read_mostly = -1;
10 
11 static inline unsigned int safe_shift(unsigned int d, int n)
12 {
13 	return n < 0 ? d >> -n : d << n;
14 }
15 
16 static void fb_set_logocmap(struct fb_info *info,
17 				   const struct linux_logo *logo)
18 {
19 	struct fb_cmap palette_cmap;
20 	u16 palette_green[16];
21 	u16 palette_blue[16];
22 	u16 palette_red[16];
23 	int i, j, n;
24 	const unsigned char *clut = logo->clut;
25 
26 	palette_cmap.start = 0;
27 	palette_cmap.len = 16;
28 	palette_cmap.red = palette_red;
29 	palette_cmap.green = palette_green;
30 	palette_cmap.blue = palette_blue;
31 	palette_cmap.transp = NULL;
32 
33 	for (i = 0; i < logo->clutsize; i += n) {
34 		n = logo->clutsize - i;
35 		/* palette_cmap provides space for only 16 colors at once */
36 		if (n > 16)
37 			n = 16;
38 		palette_cmap.start = 32 + i;
39 		palette_cmap.len = n;
40 		for (j = 0; j < n; ++j) {
41 			palette_cmap.red[j] = clut[0] << 8 | clut[0];
42 			palette_cmap.green[j] = clut[1] << 8 | clut[1];
43 			palette_cmap.blue[j] = clut[2] << 8 | clut[2];
44 			clut += 3;
45 		}
46 		fb_set_cmap(&palette_cmap, info);
47 	}
48 }
49 
50 static void  fb_set_logo_truepalette(struct fb_info *info,
51 					    const struct linux_logo *logo,
52 					    u32 *palette)
53 {
54 	static const unsigned char mask[] = {
55 		0, 0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe, 0xff
56 	};
57 	unsigned char redmask, greenmask, bluemask;
58 	int redshift, greenshift, blueshift;
59 	int i;
60 	const unsigned char *clut = logo->clut;
61 
62 	/*
63 	 * We have to create a temporary palette since console palette is only
64 	 * 16 colors long.
65 	 */
66 	/* Bug: Doesn't obey msb_right ... (who needs that?) */
67 	redmask   = mask[info->var.red.length   < 8 ? info->var.red.length   : 8];
68 	greenmask = mask[info->var.green.length < 8 ? info->var.green.length : 8];
69 	bluemask  = mask[info->var.blue.length  < 8 ? info->var.blue.length  : 8];
70 	redshift   = info->var.red.offset   - (8 - info->var.red.length);
71 	greenshift = info->var.green.offset - (8 - info->var.green.length);
72 	blueshift  = info->var.blue.offset  - (8 - info->var.blue.length);
73 
74 	for (i = 0; i < logo->clutsize; i++) {
75 		palette[i+32] = (safe_shift((clut[0] & redmask), redshift) |
76 				 safe_shift((clut[1] & greenmask), greenshift) |
77 				 safe_shift((clut[2] & bluemask), blueshift));
78 		clut += 3;
79 	}
80 }
81 
82 static void fb_set_logo_directpalette(struct fb_info *info,
83 					     const struct linux_logo *logo,
84 					     u32 *palette)
85 {
86 	int redshift, greenshift, blueshift;
87 	int i;
88 
89 	redshift = info->var.red.offset;
90 	greenshift = info->var.green.offset;
91 	blueshift = info->var.blue.offset;
92 
93 	for (i = 32; i < 32 + logo->clutsize; i++)
94 		palette[i] = i << redshift | i << greenshift | i << blueshift;
95 }
96 
97 static void fb_set_logo(struct fb_info *info,
98 			       const struct linux_logo *logo, u8 *dst,
99 			       int depth)
100 {
101 	int i, j, k;
102 	const u8 *src = logo->data;
103 	u8 xor = (info->fix.visual == FB_VISUAL_MONO01) ? 0xff : 0;
104 	u8 fg = 1, d;
105 
106 	switch (fb_get_color_depth(&info->var, &info->fix)) {
107 	case 1:
108 		fg = 1;
109 		break;
110 	case 2:
111 		fg = 3;
112 		break;
113 	default:
114 		fg = 7;
115 		break;
116 	}
117 
118 	if (info->fix.visual == FB_VISUAL_MONO01 ||
119 	    info->fix.visual == FB_VISUAL_MONO10)
120 		fg = ~((u8) (0xfff << info->var.green.length));
121 
122 	switch (depth) {
123 	case 4:
124 		for (i = 0; i < logo->height; i++)
125 			for (j = 0; j < logo->width; src++) {
126 				*dst++ = *src >> 4;
127 				j++;
128 				if (j < logo->width) {
129 					*dst++ = *src & 0x0f;
130 					j++;
131 				}
132 			}
133 		break;
134 	case 1:
135 		for (i = 0; i < logo->height; i++) {
136 			for (j = 0; j < logo->width; src++) {
137 				d = *src ^ xor;
138 				for (k = 7; k >= 0 && j < logo->width; k--) {
139 					*dst++ = ((d >> k) & 1) ? fg : 0;
140 					j++;
141 				}
142 			}
143 		}
144 		break;
145 	}
146 }
147 
148 /*
149  * Three (3) kinds of logo maps exist.  linux_logo_clut224 (>16 colors),
150  * linux_logo_vga16 (16 colors) and linux_logo_mono (2 colors).  Depending on
151  * the visual format and color depth of the framebuffer, the DAC, the
152  * pseudo_palette, and the logo data will be adjusted accordingly.
153  *
154  * Case 1 - linux_logo_clut224:
155  * Color exceeds the number of console colors (16), thus we set the hardware DAC
156  * using fb_set_cmap() appropriately.  The "needs_cmapreset"  flag will be set.
157  *
158  * For visuals that require color info from the pseudo_palette, we also construct
159  * one for temporary use. The "needs_directpalette" or "needs_truepalette" flags
160  * will be set.
161  *
162  * Case 2 - linux_logo_vga16:
163  * The number of colors just matches the console colors, thus there is no need
164  * to set the DAC or the pseudo_palette.  However, the bitmap is packed, ie,
165  * each byte contains color information for two pixels (upper and lower nibble).
166  * To be consistent with fb_imageblit() usage, we therefore separate the two
167  * nibbles into separate bytes. The "depth" flag will be set to 4.
168  *
169  * Case 3 - linux_logo_mono:
170  * This is similar with Case 2.  Each byte contains information for 8 pixels.
171  * We isolate each bit and expand each into a byte. The "depth" flag will
172  * be set to 1.
173  */
174 static struct logo_data {
175 	int depth;
176 	int needs_directpalette;
177 	int needs_truepalette;
178 	int needs_cmapreset;
179 	const struct linux_logo *logo;
180 } fb_logo __read_mostly;
181 
182 static void fb_rotate_logo_ud(const u8 *in, u8 *out, u32 width, u32 height)
183 {
184 	u32 size = width * height, i;
185 
186 	out += size - 1;
187 
188 	for (i = size; i--; )
189 		*out-- = *in++;
190 }
191 
192 static void fb_rotate_logo_cw(const u8 *in, u8 *out, u32 width, u32 height)
193 {
194 	int i, j, h = height - 1;
195 
196 	for (i = 0; i < height; i++)
197 		for (j = 0; j < width; j++)
198 			out[height * j + h - i] = *in++;
199 }
200 
201 static void fb_rotate_logo_ccw(const u8 *in, u8 *out, u32 width, u32 height)
202 {
203 	int i, j, w = width - 1;
204 
205 	for (i = 0; i < height; i++)
206 		for (j = 0; j < width; j++)
207 			out[height * (w - j) + i] = *in++;
208 }
209 
210 static void fb_rotate_logo(struct fb_info *info, u8 *dst,
211 			   struct fb_image *image, int rotate)
212 {
213 	u32 tmp;
214 
215 	if (rotate == FB_ROTATE_UD) {
216 		fb_rotate_logo_ud(image->data, dst, image->width,
217 				  image->height);
218 		image->dx = info->var.xres - image->width - image->dx;
219 		image->dy = info->var.yres - image->height - image->dy;
220 	} else if (rotate == FB_ROTATE_CW) {
221 		fb_rotate_logo_cw(image->data, dst, image->width,
222 				  image->height);
223 		swap(image->width, image->height);
224 		tmp = image->dy;
225 		image->dy = image->dx;
226 		image->dx = info->var.xres - image->width - tmp;
227 	} else if (rotate == FB_ROTATE_CCW) {
228 		fb_rotate_logo_ccw(image->data, dst, image->width,
229 				   image->height);
230 		swap(image->width, image->height);
231 		tmp = image->dx;
232 		image->dx = image->dy;
233 		image->dy = info->var.yres - image->height - tmp;
234 	}
235 
236 	image->data = dst;
237 }
238 
239 static void fb_do_show_logo(struct fb_info *info, struct fb_image *image,
240 			    int rotate, unsigned int num)
241 {
242 	unsigned int x;
243 
244 	if (image->width > info->var.xres || image->height > info->var.yres)
245 		return;
246 
247 	if (rotate == FB_ROTATE_UR) {
248 		for (x = 0;
249 		     x < num && image->dx + image->width <= info->var.xres;
250 		     x++) {
251 			info->fbops->fb_imageblit(info, image);
252 			image->dx += image->width + 8;
253 		}
254 	} else if (rotate == FB_ROTATE_UD) {
255 		u32 dx = image->dx;
256 
257 		for (x = 0; x < num && image->dx <= dx; x++) {
258 			info->fbops->fb_imageblit(info, image);
259 			image->dx -= image->width + 8;
260 		}
261 	} else if (rotate == FB_ROTATE_CW) {
262 		for (x = 0;
263 		     x < num && image->dy + image->height <= info->var.yres;
264 		     x++) {
265 			info->fbops->fb_imageblit(info, image);
266 			image->dy += image->height + 8;
267 		}
268 	} else if (rotate == FB_ROTATE_CCW) {
269 		u32 dy = image->dy;
270 
271 		for (x = 0; x < num && image->dy <= dy; x++) {
272 			info->fbops->fb_imageblit(info, image);
273 			image->dy -= image->height + 8;
274 		}
275 	}
276 }
277 
278 static int fb_show_logo_line(struct fb_info *info, int rotate,
279 			     const struct linux_logo *logo, int y,
280 			     unsigned int n)
281 {
282 	u32 *palette = NULL, *saved_pseudo_palette = NULL;
283 	unsigned char *logo_new = NULL, *logo_rotate = NULL;
284 	struct fb_image image;
285 
286 	/* Return if the frame buffer is not mapped or suspended */
287 	if (logo == NULL || info->state != FBINFO_STATE_RUNNING ||
288 	    info->fbops->owner)
289 		return 0;
290 
291 	image.depth = 8;
292 	image.data = logo->data;
293 
294 	if (fb_logo.needs_cmapreset)
295 		fb_set_logocmap(info, logo);
296 
297 	if (fb_logo.needs_truepalette ||
298 	    fb_logo.needs_directpalette) {
299 		palette = kmalloc(256 * 4, GFP_KERNEL);
300 		if (palette == NULL)
301 			return 0;
302 
303 		if (fb_logo.needs_truepalette)
304 			fb_set_logo_truepalette(info, logo, palette);
305 		else
306 			fb_set_logo_directpalette(info, logo, palette);
307 
308 		saved_pseudo_palette = info->pseudo_palette;
309 		info->pseudo_palette = palette;
310 	}
311 
312 	if (fb_logo.depth <= 4) {
313 		logo_new = kmalloc_array(logo->width, logo->height,
314 					 GFP_KERNEL);
315 		if (logo_new == NULL) {
316 			kfree(palette);
317 			if (saved_pseudo_palette)
318 				info->pseudo_palette = saved_pseudo_palette;
319 			return 0;
320 		}
321 		image.data = logo_new;
322 		fb_set_logo(info, logo, logo_new, fb_logo.depth);
323 	}
324 
325 	if (fb_center_logo) {
326 		int xres = info->var.xres;
327 		int yres = info->var.yres;
328 
329 		if (rotate == FB_ROTATE_CW || rotate == FB_ROTATE_CCW) {
330 			xres = info->var.yres;
331 			yres = info->var.xres;
332 		}
333 
334 		while (n && (n * (logo->width + 8) - 8 > xres))
335 			--n;
336 		image.dx = (xres - (n * (logo->width + 8) - 8)) / 2;
337 		image.dy = y ?: (yres - logo->height) / 2;
338 	} else {
339 		image.dx = 0;
340 		image.dy = y;
341 	}
342 
343 	image.width = logo->width;
344 	image.height = logo->height;
345 
346 	if (rotate) {
347 		logo_rotate = kmalloc_array(logo->width, logo->height,
348 					    GFP_KERNEL);
349 		if (logo_rotate)
350 			fb_rotate_logo(info, logo_rotate, &image, rotate);
351 	}
352 
353 	fb_do_show_logo(info, &image, rotate, n);
354 
355 	kfree(palette);
356 	if (saved_pseudo_palette != NULL)
357 		info->pseudo_palette = saved_pseudo_palette;
358 	kfree(logo_new);
359 	kfree(logo_rotate);
360 	return image.dy + logo->height;
361 }
362 
363 #ifdef CONFIG_FB_LOGO_EXTRA
364 
365 #define FB_LOGO_EX_NUM_MAX 10
366 static struct logo_data_extra {
367 	const struct linux_logo *logo;
368 	unsigned int n;
369 } fb_logo_ex[FB_LOGO_EX_NUM_MAX];
370 static unsigned int fb_logo_ex_num;
371 
372 void fb_append_extra_logo(const struct linux_logo *logo, unsigned int n)
373 {
374 	if (!n || fb_logo_ex_num == FB_LOGO_EX_NUM_MAX)
375 		return;
376 
377 	fb_logo_ex[fb_logo_ex_num].logo = logo;
378 	fb_logo_ex[fb_logo_ex_num].n = n;
379 	fb_logo_ex_num++;
380 }
381 
382 static int fb_prepare_extra_logos(struct fb_info *info, unsigned int height,
383 				  unsigned int yres)
384 {
385 	unsigned int i;
386 
387 	/* FIXME: logo_ex supports only truecolor fb. */
388 	if (info->fix.visual != FB_VISUAL_TRUECOLOR)
389 		fb_logo_ex_num = 0;
390 
391 	for (i = 0; i < fb_logo_ex_num; i++) {
392 		if (fb_logo_ex[i].logo->type != fb_logo.logo->type) {
393 			fb_logo_ex[i].logo = NULL;
394 			continue;
395 		}
396 		height += fb_logo_ex[i].logo->height;
397 		if (height > yres) {
398 			height -= fb_logo_ex[i].logo->height;
399 			fb_logo_ex_num = i;
400 			break;
401 		}
402 	}
403 	return height;
404 }
405 
406 static int fb_show_extra_logos(struct fb_info *info, int y, int rotate)
407 {
408 	unsigned int i;
409 
410 	for (i = 0; i < fb_logo_ex_num; i++)
411 		y = fb_show_logo_line(info, rotate,
412 				      fb_logo_ex[i].logo, y, fb_logo_ex[i].n);
413 
414 	return y;
415 }
416 #endif /* CONFIG_FB_LOGO_EXTRA */
417 
418 int fb_prepare_logo(struct fb_info *info, int rotate)
419 {
420 	int depth = fb_get_color_depth(&info->var, &info->fix);
421 	unsigned int yres;
422 	int height;
423 
424 	memset(&fb_logo, 0, sizeof(struct logo_data));
425 
426 	if (info->flags & FBINFO_MISC_TILEBLITTING ||
427 	    info->fbops->owner || !fb_logo_count)
428 		return 0;
429 
430 	if (info->fix.visual == FB_VISUAL_DIRECTCOLOR) {
431 		depth = info->var.blue.length;
432 		if (info->var.red.length < depth)
433 			depth = info->var.red.length;
434 		if (info->var.green.length < depth)
435 			depth = info->var.green.length;
436 	}
437 
438 	if (info->fix.visual == FB_VISUAL_STATIC_PSEUDOCOLOR && depth > 4) {
439 		/* assume console colormap */
440 		depth = 4;
441 	}
442 
443 	/* Return if no suitable logo was found */
444 	fb_logo.logo = fb_find_logo(depth);
445 
446 	if (!fb_logo.logo)
447 		return 0;
448 
449 	if (rotate == FB_ROTATE_UR || rotate == FB_ROTATE_UD)
450 		yres = info->var.yres;
451 	else
452 		yres = info->var.xres;
453 
454 	if (fb_logo.logo->height > yres) {
455 		fb_logo.logo = NULL;
456 		return 0;
457 	}
458 
459 	/* What depth we asked for might be different from what we get */
460 	if (fb_logo.logo->type == LINUX_LOGO_CLUT224)
461 		fb_logo.depth = 8;
462 	else if (fb_logo.logo->type == LINUX_LOGO_VGA16)
463 		fb_logo.depth = 4;
464 	else
465 		fb_logo.depth = 1;
466 
467 
468 	if (fb_logo.depth > 4 && depth > 4) {
469 		switch (info->fix.visual) {
470 		case FB_VISUAL_TRUECOLOR:
471 			fb_logo.needs_truepalette = 1;
472 			break;
473 		case FB_VISUAL_DIRECTCOLOR:
474 			fb_logo.needs_directpalette = 1;
475 			fb_logo.needs_cmapreset = 1;
476 			break;
477 		case FB_VISUAL_PSEUDOCOLOR:
478 			fb_logo.needs_cmapreset = 1;
479 			break;
480 		}
481 	}
482 
483 	height = fb_logo.logo->height;
484 	if (fb_center_logo)
485 		height += (yres - fb_logo.logo->height) / 2;
486 #ifdef CONFIG_FB_LOGO_EXTRA
487 	height = fb_prepare_extra_logos(info, height, yres);
488 #endif
489 
490 	return height;
491 }
492 
493 int fb_show_logo(struct fb_info *info, int rotate)
494 {
495 	unsigned int count;
496 	int y;
497 
498 	if (!fb_logo_count)
499 		return 0;
500 
501 	count = fb_logo_count < 0 ? num_online_cpus() : fb_logo_count;
502 	y = fb_show_logo_line(info, rotate, fb_logo.logo, 0, count);
503 #ifdef CONFIG_FB_LOGO_EXTRA
504 	y = fb_show_extra_logos(info, y, rotate);
505 #endif
506 
507 	return y;
508 }
509