509 lines
13 KiB
C
509 lines
13 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
|
|
#include <linux/fb.h>
|
|
#include <linux/linux_logo.h>
|
|
|
|
#include "fb_internal.h"
|
|
|
|
bool fb_center_logo __read_mostly;
|
|
int fb_logo_count __read_mostly = -1;
|
|
|
|
static inline unsigned int safe_shift(unsigned int d, int n)
|
|
{
|
|
return n < 0 ? d >> -n : d << n;
|
|
}
|
|
|
|
static void fb_set_logocmap(struct fb_info *info,
|
|
const struct linux_logo *logo)
|
|
{
|
|
struct fb_cmap palette_cmap;
|
|
u16 palette_green[16];
|
|
u16 palette_blue[16];
|
|
u16 palette_red[16];
|
|
int i, j, n;
|
|
const unsigned char *clut = logo->clut;
|
|
|
|
palette_cmap.start = 0;
|
|
palette_cmap.len = 16;
|
|
palette_cmap.red = palette_red;
|
|
palette_cmap.green = palette_green;
|
|
palette_cmap.blue = palette_blue;
|
|
palette_cmap.transp = NULL;
|
|
|
|
for (i = 0; i < logo->clutsize; i += n) {
|
|
n = logo->clutsize - i;
|
|
/* palette_cmap provides space for only 16 colors at once */
|
|
if (n > 16)
|
|
n = 16;
|
|
palette_cmap.start = 32 + i;
|
|
palette_cmap.len = n;
|
|
for (j = 0; j < n; ++j) {
|
|
palette_cmap.red[j] = clut[0] << 8 | clut[0];
|
|
palette_cmap.green[j] = clut[1] << 8 | clut[1];
|
|
palette_cmap.blue[j] = clut[2] << 8 | clut[2];
|
|
clut += 3;
|
|
}
|
|
fb_set_cmap(&palette_cmap, info);
|
|
}
|
|
}
|
|
|
|
static void fb_set_logo_truepalette(struct fb_info *info,
|
|
const struct linux_logo *logo,
|
|
u32 *palette)
|
|
{
|
|
static const unsigned char mask[] = {
|
|
0, 0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe, 0xff
|
|
};
|
|
unsigned char redmask, greenmask, bluemask;
|
|
int redshift, greenshift, blueshift;
|
|
int i;
|
|
const unsigned char *clut = logo->clut;
|
|
|
|
/*
|
|
* We have to create a temporary palette since console palette is only
|
|
* 16 colors long.
|
|
*/
|
|
/* Bug: Doesn't obey msb_right ... (who needs that?) */
|
|
redmask = mask[info->var.red.length < 8 ? info->var.red.length : 8];
|
|
greenmask = mask[info->var.green.length < 8 ? info->var.green.length : 8];
|
|
bluemask = mask[info->var.blue.length < 8 ? info->var.blue.length : 8];
|
|
redshift = info->var.red.offset - (8 - info->var.red.length);
|
|
greenshift = info->var.green.offset - (8 - info->var.green.length);
|
|
blueshift = info->var.blue.offset - (8 - info->var.blue.length);
|
|
|
|
for (i = 0; i < logo->clutsize; i++) {
|
|
palette[i+32] = (safe_shift((clut[0] & redmask), redshift) |
|
|
safe_shift((clut[1] & greenmask), greenshift) |
|
|
safe_shift((clut[2] & bluemask), blueshift));
|
|
clut += 3;
|
|
}
|
|
}
|
|
|
|
static void fb_set_logo_directpalette(struct fb_info *info,
|
|
const struct linux_logo *logo,
|
|
u32 *palette)
|
|
{
|
|
int redshift, greenshift, blueshift;
|
|
int i;
|
|
|
|
redshift = info->var.red.offset;
|
|
greenshift = info->var.green.offset;
|
|
blueshift = info->var.blue.offset;
|
|
|
|
for (i = 32; i < 32 + logo->clutsize; i++)
|
|
palette[i] = i << redshift | i << greenshift | i << blueshift;
|
|
}
|
|
|
|
static void fb_set_logo(struct fb_info *info,
|
|
const struct linux_logo *logo, u8 *dst,
|
|
int depth)
|
|
{
|
|
int i, j, k;
|
|
const u8 *src = logo->data;
|
|
u8 xor = (info->fix.visual == FB_VISUAL_MONO01) ? 0xff : 0;
|
|
u8 fg = 1, d;
|
|
|
|
switch (fb_get_color_depth(&info->var, &info->fix)) {
|
|
case 1:
|
|
fg = 1;
|
|
break;
|
|
case 2:
|
|
fg = 3;
|
|
break;
|
|
default:
|
|
fg = 7;
|
|
break;
|
|
}
|
|
|
|
if (info->fix.visual == FB_VISUAL_MONO01 ||
|
|
info->fix.visual == FB_VISUAL_MONO10)
|
|
fg = ~((u8) (0xfff << info->var.green.length));
|
|
|
|
switch (depth) {
|
|
case 4:
|
|
for (i = 0; i < logo->height; i++)
|
|
for (j = 0; j < logo->width; src++) {
|
|
*dst++ = *src >> 4;
|
|
j++;
|
|
if (j < logo->width) {
|
|
*dst++ = *src & 0x0f;
|
|
j++;
|
|
}
|
|
}
|
|
break;
|
|
case 1:
|
|
for (i = 0; i < logo->height; i++) {
|
|
for (j = 0; j < logo->width; src++) {
|
|
d = *src ^ xor;
|
|
for (k = 7; k >= 0 && j < logo->width; k--) {
|
|
*dst++ = ((d >> k) & 1) ? fg : 0;
|
|
j++;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Three (3) kinds of logo maps exist. linux_logo_clut224 (>16 colors),
|
|
* linux_logo_vga16 (16 colors) and linux_logo_mono (2 colors). Depending on
|
|
* the visual format and color depth of the framebuffer, the DAC, the
|
|
* pseudo_palette, and the logo data will be adjusted accordingly.
|
|
*
|
|
* Case 1 - linux_logo_clut224:
|
|
* Color exceeds the number of console colors (16), thus we set the hardware DAC
|
|
* using fb_set_cmap() appropriately. The "needs_cmapreset" flag will be set.
|
|
*
|
|
* For visuals that require color info from the pseudo_palette, we also construct
|
|
* one for temporary use. The "needs_directpalette" or "needs_truepalette" flags
|
|
* will be set.
|
|
*
|
|
* Case 2 - linux_logo_vga16:
|
|
* The number of colors just matches the console colors, thus there is no need
|
|
* to set the DAC or the pseudo_palette. However, the bitmap is packed, ie,
|
|
* each byte contains color information for two pixels (upper and lower nibble).
|
|
* To be consistent with fb_imageblit() usage, we therefore separate the two
|
|
* nibbles into separate bytes. The "depth" flag will be set to 4.
|
|
*
|
|
* Case 3 - linux_logo_mono:
|
|
* This is similar with Case 2. Each byte contains information for 8 pixels.
|
|
* We isolate each bit and expand each into a byte. The "depth" flag will
|
|
* be set to 1.
|
|
*/
|
|
static struct logo_data {
|
|
int depth;
|
|
int needs_directpalette;
|
|
int needs_truepalette;
|
|
int needs_cmapreset;
|
|
const struct linux_logo *logo;
|
|
} fb_logo __read_mostly;
|
|
|
|
static void fb_rotate_logo_ud(const u8 *in, u8 *out, u32 width, u32 height)
|
|
{
|
|
u32 size = width * height, i;
|
|
|
|
out += size - 1;
|
|
|
|
for (i = size; i--; )
|
|
*out-- = *in++;
|
|
}
|
|
|
|
static void fb_rotate_logo_cw(const u8 *in, u8 *out, u32 width, u32 height)
|
|
{
|
|
int i, j, h = height - 1;
|
|
|
|
for (i = 0; i < height; i++)
|
|
for (j = 0; j < width; j++)
|
|
out[height * j + h - i] = *in++;
|
|
}
|
|
|
|
static void fb_rotate_logo_ccw(const u8 *in, u8 *out, u32 width, u32 height)
|
|
{
|
|
int i, j, w = width - 1;
|
|
|
|
for (i = 0; i < height; i++)
|
|
for (j = 0; j < width; j++)
|
|
out[height * (w - j) + i] = *in++;
|
|
}
|
|
|
|
static void fb_rotate_logo(struct fb_info *info, u8 *dst,
|
|
struct fb_image *image, int rotate)
|
|
{
|
|
u32 tmp;
|
|
|
|
if (rotate == FB_ROTATE_UD) {
|
|
fb_rotate_logo_ud(image->data, dst, image->width,
|
|
image->height);
|
|
image->dx = info->var.xres - image->width - image->dx;
|
|
image->dy = info->var.yres - image->height - image->dy;
|
|
} else if (rotate == FB_ROTATE_CW) {
|
|
fb_rotate_logo_cw(image->data, dst, image->width,
|
|
image->height);
|
|
swap(image->width, image->height);
|
|
tmp = image->dy;
|
|
image->dy = image->dx;
|
|
image->dx = info->var.xres - image->width - tmp;
|
|
} else if (rotate == FB_ROTATE_CCW) {
|
|
fb_rotate_logo_ccw(image->data, dst, image->width,
|
|
image->height);
|
|
swap(image->width, image->height);
|
|
tmp = image->dx;
|
|
image->dx = image->dy;
|
|
image->dy = info->var.yres - image->height - tmp;
|
|
}
|
|
|
|
image->data = dst;
|
|
}
|
|
|
|
static void fb_do_show_logo(struct fb_info *info, struct fb_image *image,
|
|
int rotate, unsigned int num)
|
|
{
|
|
unsigned int x;
|
|
|
|
if (image->width > info->var.xres || image->height > info->var.yres)
|
|
return;
|
|
|
|
if (rotate == FB_ROTATE_UR) {
|
|
for (x = 0;
|
|
x < num && image->dx + image->width <= info->var.xres;
|
|
x++) {
|
|
info->fbops->fb_imageblit(info, image);
|
|
image->dx += image->width + 8;
|
|
}
|
|
} else if (rotate == FB_ROTATE_UD) {
|
|
u32 dx = image->dx;
|
|
|
|
for (x = 0; x < num && image->dx <= dx; x++) {
|
|
info->fbops->fb_imageblit(info, image);
|
|
image->dx -= image->width + 8;
|
|
}
|
|
} else if (rotate == FB_ROTATE_CW) {
|
|
for (x = 0;
|
|
x < num && image->dy + image->height <= info->var.yres;
|
|
x++) {
|
|
info->fbops->fb_imageblit(info, image);
|
|
image->dy += image->height + 8;
|
|
}
|
|
} else if (rotate == FB_ROTATE_CCW) {
|
|
u32 dy = image->dy;
|
|
|
|
for (x = 0; x < num && image->dy <= dy; x++) {
|
|
info->fbops->fb_imageblit(info, image);
|
|
image->dy -= image->height + 8;
|
|
}
|
|
}
|
|
}
|
|
|
|
static int fb_show_logo_line(struct fb_info *info, int rotate,
|
|
const struct linux_logo *logo, int y,
|
|
unsigned int n)
|
|
{
|
|
u32 *palette = NULL, *saved_pseudo_palette = NULL;
|
|
unsigned char *logo_new = NULL, *logo_rotate = NULL;
|
|
struct fb_image image;
|
|
|
|
/* Return if the frame buffer is not mapped or suspended */
|
|
if (logo == NULL || info->state != FBINFO_STATE_RUNNING ||
|
|
info->fbops->owner)
|
|
return 0;
|
|
|
|
image.depth = 8;
|
|
image.data = logo->data;
|
|
|
|
if (fb_logo.needs_cmapreset)
|
|
fb_set_logocmap(info, logo);
|
|
|
|
if (fb_logo.needs_truepalette ||
|
|
fb_logo.needs_directpalette) {
|
|
palette = kmalloc(256 * 4, GFP_KERNEL);
|
|
if (palette == NULL)
|
|
return 0;
|
|
|
|
if (fb_logo.needs_truepalette)
|
|
fb_set_logo_truepalette(info, logo, palette);
|
|
else
|
|
fb_set_logo_directpalette(info, logo, palette);
|
|
|
|
saved_pseudo_palette = info->pseudo_palette;
|
|
info->pseudo_palette = palette;
|
|
}
|
|
|
|
if (fb_logo.depth <= 4) {
|
|
logo_new = kmalloc_array(logo->width, logo->height,
|
|
GFP_KERNEL);
|
|
if (logo_new == NULL) {
|
|
kfree(palette);
|
|
if (saved_pseudo_palette)
|
|
info->pseudo_palette = saved_pseudo_palette;
|
|
return 0;
|
|
}
|
|
image.data = logo_new;
|
|
fb_set_logo(info, logo, logo_new, fb_logo.depth);
|
|
}
|
|
|
|
if (fb_center_logo) {
|
|
int xres = info->var.xres;
|
|
int yres = info->var.yres;
|
|
|
|
if (rotate == FB_ROTATE_CW || rotate == FB_ROTATE_CCW) {
|
|
xres = info->var.yres;
|
|
yres = info->var.xres;
|
|
}
|
|
|
|
while (n && (n * (logo->width + 8) - 8 > xres))
|
|
--n;
|
|
image.dx = (xres - (n * (logo->width + 8) - 8)) / 2;
|
|
image.dy = y ?: (yres - logo->height) / 2;
|
|
} else {
|
|
image.dx = 0;
|
|
image.dy = y;
|
|
}
|
|
|
|
image.width = logo->width;
|
|
image.height = logo->height;
|
|
|
|
if (rotate) {
|
|
logo_rotate = kmalloc_array(logo->width, logo->height,
|
|
GFP_KERNEL);
|
|
if (logo_rotate)
|
|
fb_rotate_logo(info, logo_rotate, &image, rotate);
|
|
}
|
|
|
|
fb_do_show_logo(info, &image, rotate, n);
|
|
|
|
kfree(palette);
|
|
if (saved_pseudo_palette != NULL)
|
|
info->pseudo_palette = saved_pseudo_palette;
|
|
kfree(logo_new);
|
|
kfree(logo_rotate);
|
|
return image.dy + logo->height;
|
|
}
|
|
|
|
#ifdef CONFIG_FB_LOGO_EXTRA
|
|
|
|
#define FB_LOGO_EX_NUM_MAX 10
|
|
static struct logo_data_extra {
|
|
const struct linux_logo *logo;
|
|
unsigned int n;
|
|
} fb_logo_ex[FB_LOGO_EX_NUM_MAX];
|
|
static unsigned int fb_logo_ex_num;
|
|
|
|
void fb_append_extra_logo(const struct linux_logo *logo, unsigned int n)
|
|
{
|
|
if (!n || fb_logo_ex_num == FB_LOGO_EX_NUM_MAX)
|
|
return;
|
|
|
|
fb_logo_ex[fb_logo_ex_num].logo = logo;
|
|
fb_logo_ex[fb_logo_ex_num].n = n;
|
|
fb_logo_ex_num++;
|
|
}
|
|
|
|
static int fb_prepare_extra_logos(struct fb_info *info, unsigned int height,
|
|
unsigned int yres)
|
|
{
|
|
unsigned int i;
|
|
|
|
/* FIXME: logo_ex supports only truecolor fb. */
|
|
if (info->fix.visual != FB_VISUAL_TRUECOLOR)
|
|
fb_logo_ex_num = 0;
|
|
|
|
for (i = 0; i < fb_logo_ex_num; i++) {
|
|
if (fb_logo_ex[i].logo->type != fb_logo.logo->type) {
|
|
fb_logo_ex[i].logo = NULL;
|
|
continue;
|
|
}
|
|
height += fb_logo_ex[i].logo->height;
|
|
if (height > yres) {
|
|
height -= fb_logo_ex[i].logo->height;
|
|
fb_logo_ex_num = i;
|
|
break;
|
|
}
|
|
}
|
|
return height;
|
|
}
|
|
|
|
static int fb_show_extra_logos(struct fb_info *info, int y, int rotate)
|
|
{
|
|
unsigned int i;
|
|
|
|
for (i = 0; i < fb_logo_ex_num; i++)
|
|
y = fb_show_logo_line(info, rotate,
|
|
fb_logo_ex[i].logo, y, fb_logo_ex[i].n);
|
|
|
|
return y;
|
|
}
|
|
#endif /* CONFIG_FB_LOGO_EXTRA */
|
|
|
|
int fb_prepare_logo(struct fb_info *info, int rotate)
|
|
{
|
|
int depth = fb_get_color_depth(&info->var, &info->fix);
|
|
unsigned int yres;
|
|
int height;
|
|
|
|
memset(&fb_logo, 0, sizeof(struct logo_data));
|
|
|
|
if (info->flags & FBINFO_MISC_TILEBLITTING ||
|
|
info->fbops->owner || !fb_logo_count)
|
|
return 0;
|
|
|
|
if (info->fix.visual == FB_VISUAL_DIRECTCOLOR) {
|
|
depth = info->var.blue.length;
|
|
if (info->var.red.length < depth)
|
|
depth = info->var.red.length;
|
|
if (info->var.green.length < depth)
|
|
depth = info->var.green.length;
|
|
}
|
|
|
|
if (info->fix.visual == FB_VISUAL_STATIC_PSEUDOCOLOR && depth > 4) {
|
|
/* assume console colormap */
|
|
depth = 4;
|
|
}
|
|
|
|
/* Return if no suitable logo was found */
|
|
fb_logo.logo = fb_find_logo(depth);
|
|
|
|
if (!fb_logo.logo)
|
|
return 0;
|
|
|
|
if (rotate == FB_ROTATE_UR || rotate == FB_ROTATE_UD)
|
|
yres = info->var.yres;
|
|
else
|
|
yres = info->var.xres;
|
|
|
|
if (fb_logo.logo->height > yres) {
|
|
fb_logo.logo = NULL;
|
|
return 0;
|
|
}
|
|
|
|
/* What depth we asked for might be different from what we get */
|
|
if (fb_logo.logo->type == LINUX_LOGO_CLUT224)
|
|
fb_logo.depth = 8;
|
|
else if (fb_logo.logo->type == LINUX_LOGO_VGA16)
|
|
fb_logo.depth = 4;
|
|
else
|
|
fb_logo.depth = 1;
|
|
|
|
|
|
if (fb_logo.depth > 4 && depth > 4) {
|
|
switch (info->fix.visual) {
|
|
case FB_VISUAL_TRUECOLOR:
|
|
fb_logo.needs_truepalette = 1;
|
|
break;
|
|
case FB_VISUAL_DIRECTCOLOR:
|
|
fb_logo.needs_directpalette = 1;
|
|
fb_logo.needs_cmapreset = 1;
|
|
break;
|
|
case FB_VISUAL_PSEUDOCOLOR:
|
|
fb_logo.needs_cmapreset = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
height = fb_logo.logo->height;
|
|
if (fb_center_logo)
|
|
height += (yres - fb_logo.logo->height) / 2;
|
|
#ifdef CONFIG_FB_LOGO_EXTRA
|
|
height = fb_prepare_extra_logos(info, height, yres);
|
|
#endif
|
|
|
|
return height;
|
|
}
|
|
|
|
int fb_show_logo(struct fb_info *info, int rotate)
|
|
{
|
|
unsigned int count;
|
|
int y;
|
|
|
|
if (!fb_logo_count)
|
|
return 0;
|
|
|
|
count = fb_logo_count < 0 ? num_online_cpus() : fb_logo_count;
|
|
y = fb_show_logo_line(info, rotate, fb_logo.logo, 0, count);
|
|
#ifdef CONFIG_FB_LOGO_EXTRA
|
|
y = fb_show_extra_logos(info, y, rotate);
|
|
#endif
|
|
|
|
return y;
|
|
}
|