162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Framebuffer driver for EFI/UEFI based system
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * (c) 2006 Edgar Hucek <gimli@dark-green.com>
662306a36Sopenharmony_ci * Original efi driver written by Gerd Knorr <kraxel@goldbach.in-berlin.de>
762306a36Sopenharmony_ci *
862306a36Sopenharmony_ci */
962306a36Sopenharmony_ci
1062306a36Sopenharmony_ci#include <linux/aperture.h>
1162306a36Sopenharmony_ci#include <linux/kernel.h>
1262306a36Sopenharmony_ci#include <linux/efi.h>
1362306a36Sopenharmony_ci#include <linux/efi-bgrt.h>
1462306a36Sopenharmony_ci#include <linux/errno.h>
1562306a36Sopenharmony_ci#include <linux/fb.h>
1662306a36Sopenharmony_ci#include <linux/pci.h>
1762306a36Sopenharmony_ci#include <linux/platform_device.h>
1862306a36Sopenharmony_ci#include <linux/printk.h>
1962306a36Sopenharmony_ci#include <linux/screen_info.h>
2062306a36Sopenharmony_ci#include <linux/pm_runtime.h>
2162306a36Sopenharmony_ci#include <video/vga.h>
2262306a36Sopenharmony_ci#include <asm/efi.h>
2362306a36Sopenharmony_ci#include <drm/drm_utils.h> /* For drm_get_panel_orientation_quirk */
2462306a36Sopenharmony_ci#include <drm/drm_connector.h>  /* For DRM_MODE_PANEL_ORIENTATION_* */
2562306a36Sopenharmony_ci
2662306a36Sopenharmony_cistruct bmp_file_header {
2762306a36Sopenharmony_ci	u16 id;
2862306a36Sopenharmony_ci	u32 file_size;
2962306a36Sopenharmony_ci	u32 reserved;
3062306a36Sopenharmony_ci	u32 bitmap_offset;
3162306a36Sopenharmony_ci} __packed;
3262306a36Sopenharmony_ci
3362306a36Sopenharmony_cistruct bmp_dib_header {
3462306a36Sopenharmony_ci	u32 dib_header_size;
3562306a36Sopenharmony_ci	s32 width;
3662306a36Sopenharmony_ci	s32 height;
3762306a36Sopenharmony_ci	u16 planes;
3862306a36Sopenharmony_ci	u16 bpp;
3962306a36Sopenharmony_ci	u32 compression;
4062306a36Sopenharmony_ci	u32 bitmap_size;
4162306a36Sopenharmony_ci	u32 horz_resolution;
4262306a36Sopenharmony_ci	u32 vert_resolution;
4362306a36Sopenharmony_ci	u32 colors_used;
4462306a36Sopenharmony_ci	u32 colors_important;
4562306a36Sopenharmony_ci} __packed;
4662306a36Sopenharmony_ci
4762306a36Sopenharmony_cistatic bool use_bgrt = true;
4862306a36Sopenharmony_cistatic bool request_mem_succeeded = false;
4962306a36Sopenharmony_cistatic u64 mem_flags = EFI_MEMORY_WC | EFI_MEMORY_UC;
5062306a36Sopenharmony_ci
5162306a36Sopenharmony_cistatic struct pci_dev *efifb_pci_dev;	/* dev with BAR covering the efifb */
5262306a36Sopenharmony_ci
5362306a36Sopenharmony_cistruct efifb_par {
5462306a36Sopenharmony_ci	u32 pseudo_palette[16];
5562306a36Sopenharmony_ci	resource_size_t base;
5662306a36Sopenharmony_ci	resource_size_t size;
5762306a36Sopenharmony_ci};
5862306a36Sopenharmony_ci
5962306a36Sopenharmony_cistatic struct fb_var_screeninfo efifb_defined = {
6062306a36Sopenharmony_ci	.activate		= FB_ACTIVATE_NOW,
6162306a36Sopenharmony_ci	.height			= -1,
6262306a36Sopenharmony_ci	.width			= -1,
6362306a36Sopenharmony_ci	.right_margin		= 32,
6462306a36Sopenharmony_ci	.upper_margin		= 16,
6562306a36Sopenharmony_ci	.lower_margin		= 4,
6662306a36Sopenharmony_ci	.vsync_len		= 4,
6762306a36Sopenharmony_ci	.vmode			= FB_VMODE_NONINTERLACED,
6862306a36Sopenharmony_ci};
6962306a36Sopenharmony_ci
7062306a36Sopenharmony_cistatic struct fb_fix_screeninfo efifb_fix = {
7162306a36Sopenharmony_ci	.id			= "EFI VGA",
7262306a36Sopenharmony_ci	.type			= FB_TYPE_PACKED_PIXELS,
7362306a36Sopenharmony_ci	.accel			= FB_ACCEL_NONE,
7462306a36Sopenharmony_ci	.visual			= FB_VISUAL_TRUECOLOR,
7562306a36Sopenharmony_ci};
7662306a36Sopenharmony_ci
7762306a36Sopenharmony_cistatic int efifb_setcolreg(unsigned regno, unsigned red, unsigned green,
7862306a36Sopenharmony_ci			   unsigned blue, unsigned transp,
7962306a36Sopenharmony_ci			   struct fb_info *info)
8062306a36Sopenharmony_ci{
8162306a36Sopenharmony_ci	/*
8262306a36Sopenharmony_ci	 *  Set a single color register. The values supplied are
8362306a36Sopenharmony_ci	 *  already rounded down to the hardware's capabilities
8462306a36Sopenharmony_ci	 *  (according to the entries in the `var' structure). Return
8562306a36Sopenharmony_ci	 *  != 0 for invalid regno.
8662306a36Sopenharmony_ci	 */
8762306a36Sopenharmony_ci
8862306a36Sopenharmony_ci	if (regno >= info->cmap.len)
8962306a36Sopenharmony_ci		return 1;
9062306a36Sopenharmony_ci
9162306a36Sopenharmony_ci	if (regno < 16) {
9262306a36Sopenharmony_ci		red   >>= 16 - info->var.red.length;
9362306a36Sopenharmony_ci		green >>= 16 - info->var.green.length;
9462306a36Sopenharmony_ci		blue  >>= 16 - info->var.blue.length;
9562306a36Sopenharmony_ci		((u32 *)(info->pseudo_palette))[regno] =
9662306a36Sopenharmony_ci			(red   << info->var.red.offset)   |
9762306a36Sopenharmony_ci			(green << info->var.green.offset) |
9862306a36Sopenharmony_ci			(blue  << info->var.blue.offset);
9962306a36Sopenharmony_ci	}
10062306a36Sopenharmony_ci	return 0;
10162306a36Sopenharmony_ci}
10262306a36Sopenharmony_ci
10362306a36Sopenharmony_ci/*
10462306a36Sopenharmony_ci * If fbcon deffered console takeover is configured, the intent is for the
10562306a36Sopenharmony_ci * framebuffer to show the boot graphics (e.g. vendor logo) until there is some
10662306a36Sopenharmony_ci * (error) message to display. But the boot graphics may have been destroyed by
10762306a36Sopenharmony_ci * e.g. option ROM output, detect this and restore the boot graphics.
10862306a36Sopenharmony_ci */
10962306a36Sopenharmony_ci#if defined CONFIG_FRAMEBUFFER_CONSOLE_DEFERRED_TAKEOVER && \
11062306a36Sopenharmony_ci    defined CONFIG_ACPI_BGRT
11162306a36Sopenharmony_cistatic void efifb_copy_bmp(u8 *src, u32 *dst, int width, struct screen_info *si)
11262306a36Sopenharmony_ci{
11362306a36Sopenharmony_ci	u8 r, g, b;
11462306a36Sopenharmony_ci
11562306a36Sopenharmony_ci	while (width--) {
11662306a36Sopenharmony_ci		b = *src++;
11762306a36Sopenharmony_ci		g = *src++;
11862306a36Sopenharmony_ci		r = *src++;
11962306a36Sopenharmony_ci		*dst++ = (r << si->red_pos)   |
12062306a36Sopenharmony_ci			 (g << si->green_pos) |
12162306a36Sopenharmony_ci			 (b << si->blue_pos);
12262306a36Sopenharmony_ci	}
12362306a36Sopenharmony_ci}
12462306a36Sopenharmony_ci
12562306a36Sopenharmony_ci#ifdef CONFIG_X86
12662306a36Sopenharmony_ci/*
12762306a36Sopenharmony_ci * On x86 some firmwares use a low non native resolution for the display when
12862306a36Sopenharmony_ci * they have shown some text messages. While keeping the bgrt filled with info
12962306a36Sopenharmony_ci * for the native resolution. If the bgrt image intended for the native
13062306a36Sopenharmony_ci * resolution still fits, it will be displayed very close to the right edge of
13162306a36Sopenharmony_ci * the display looking quite bad. This function checks for this.
13262306a36Sopenharmony_ci */
13362306a36Sopenharmony_cistatic bool efifb_bgrt_sanity_check(struct screen_info *si, u32 bmp_width)
13462306a36Sopenharmony_ci{
13562306a36Sopenharmony_ci	/*
13662306a36Sopenharmony_ci	 * All x86 firmwares horizontally center the image (the yoffset
13762306a36Sopenharmony_ci	 * calculations differ between boards, but xoffset is predictable).
13862306a36Sopenharmony_ci	 */
13962306a36Sopenharmony_ci	u32 expected_xoffset = (si->lfb_width - bmp_width) / 2;
14062306a36Sopenharmony_ci
14162306a36Sopenharmony_ci	return bgrt_tab.image_offset_x == expected_xoffset;
14262306a36Sopenharmony_ci}
14362306a36Sopenharmony_ci#else
14462306a36Sopenharmony_cistatic bool efifb_bgrt_sanity_check(struct screen_info *si, u32 bmp_width)
14562306a36Sopenharmony_ci{
14662306a36Sopenharmony_ci	return true;
14762306a36Sopenharmony_ci}
14862306a36Sopenharmony_ci#endif
14962306a36Sopenharmony_ci
15062306a36Sopenharmony_cistatic void efifb_show_boot_graphics(struct fb_info *info)
15162306a36Sopenharmony_ci{
15262306a36Sopenharmony_ci	u32 bmp_width, bmp_height, bmp_pitch, dst_x, y, src_y;
15362306a36Sopenharmony_ci	struct screen_info *si = &screen_info;
15462306a36Sopenharmony_ci	struct bmp_file_header *file_header;
15562306a36Sopenharmony_ci	struct bmp_dib_header *dib_header;
15662306a36Sopenharmony_ci	void *bgrt_image = NULL;
15762306a36Sopenharmony_ci	u8 *dst = info->screen_base;
15862306a36Sopenharmony_ci
15962306a36Sopenharmony_ci	if (!use_bgrt)
16062306a36Sopenharmony_ci		return;
16162306a36Sopenharmony_ci
16262306a36Sopenharmony_ci	if (!bgrt_tab.image_address) {
16362306a36Sopenharmony_ci		pr_info("efifb: No BGRT, not showing boot graphics\n");
16462306a36Sopenharmony_ci		return;
16562306a36Sopenharmony_ci	}
16662306a36Sopenharmony_ci
16762306a36Sopenharmony_ci	if (bgrt_tab.status & 0x06) {
16862306a36Sopenharmony_ci		pr_info("efifb: BGRT rotation bits set, not showing boot graphics\n");
16962306a36Sopenharmony_ci		return;
17062306a36Sopenharmony_ci	}
17162306a36Sopenharmony_ci
17262306a36Sopenharmony_ci	/* Avoid flashing the logo if we're going to print std probe messages */
17362306a36Sopenharmony_ci	if (console_loglevel > CONSOLE_LOGLEVEL_QUIET)
17462306a36Sopenharmony_ci		return;
17562306a36Sopenharmony_ci
17662306a36Sopenharmony_ci	/* bgrt_tab.status is unreliable, so we don't check it */
17762306a36Sopenharmony_ci
17862306a36Sopenharmony_ci	if (si->lfb_depth != 32) {
17962306a36Sopenharmony_ci		pr_info("efifb: not 32 bits, not showing boot graphics\n");
18062306a36Sopenharmony_ci		return;
18162306a36Sopenharmony_ci	}
18262306a36Sopenharmony_ci
18362306a36Sopenharmony_ci	bgrt_image = memremap(bgrt_tab.image_address, bgrt_image_size,
18462306a36Sopenharmony_ci			      MEMREMAP_WB);
18562306a36Sopenharmony_ci	if (!bgrt_image) {
18662306a36Sopenharmony_ci		pr_warn("efifb: Ignoring BGRT: failed to map image memory\n");
18762306a36Sopenharmony_ci		return;
18862306a36Sopenharmony_ci	}
18962306a36Sopenharmony_ci
19062306a36Sopenharmony_ci	if (bgrt_image_size < (sizeof(*file_header) + sizeof(*dib_header)))
19162306a36Sopenharmony_ci		goto error;
19262306a36Sopenharmony_ci
19362306a36Sopenharmony_ci	file_header = bgrt_image;
19462306a36Sopenharmony_ci	if (file_header->id != 0x4d42 || file_header->reserved != 0)
19562306a36Sopenharmony_ci		goto error;
19662306a36Sopenharmony_ci
19762306a36Sopenharmony_ci	dib_header = bgrt_image + sizeof(*file_header);
19862306a36Sopenharmony_ci	if (dib_header->dib_header_size != 40 || dib_header->width < 0 ||
19962306a36Sopenharmony_ci	    dib_header->planes != 1 || dib_header->bpp != 24 ||
20062306a36Sopenharmony_ci	    dib_header->compression != 0)
20162306a36Sopenharmony_ci		goto error;
20262306a36Sopenharmony_ci
20362306a36Sopenharmony_ci	bmp_width = dib_header->width;
20462306a36Sopenharmony_ci	bmp_height = abs(dib_header->height);
20562306a36Sopenharmony_ci	bmp_pitch = round_up(3 * bmp_width, 4);
20662306a36Sopenharmony_ci
20762306a36Sopenharmony_ci	if ((file_header->bitmap_offset + bmp_pitch * bmp_height) >
20862306a36Sopenharmony_ci				bgrt_image_size)
20962306a36Sopenharmony_ci		goto error;
21062306a36Sopenharmony_ci
21162306a36Sopenharmony_ci	if ((bgrt_tab.image_offset_x + bmp_width) > si->lfb_width ||
21262306a36Sopenharmony_ci	    (bgrt_tab.image_offset_y + bmp_height) > si->lfb_height)
21362306a36Sopenharmony_ci		goto error;
21462306a36Sopenharmony_ci
21562306a36Sopenharmony_ci	if (!efifb_bgrt_sanity_check(si, bmp_width))
21662306a36Sopenharmony_ci		goto error;
21762306a36Sopenharmony_ci
21862306a36Sopenharmony_ci	pr_info("efifb: showing boot graphics\n");
21962306a36Sopenharmony_ci
22062306a36Sopenharmony_ci	for (y = 0; y < si->lfb_height; y++, dst += si->lfb_linelength) {
22162306a36Sopenharmony_ci		/* Only background? */
22262306a36Sopenharmony_ci		if (y < bgrt_tab.image_offset_y ||
22362306a36Sopenharmony_ci		    y >= (bgrt_tab.image_offset_y + bmp_height)) {
22462306a36Sopenharmony_ci			memset(dst, 0, 4 * si->lfb_width);
22562306a36Sopenharmony_ci			continue;
22662306a36Sopenharmony_ci		}
22762306a36Sopenharmony_ci
22862306a36Sopenharmony_ci		src_y = y - bgrt_tab.image_offset_y;
22962306a36Sopenharmony_ci		/* Positive header height means upside down row order */
23062306a36Sopenharmony_ci		if (dib_header->height > 0)
23162306a36Sopenharmony_ci			src_y = (bmp_height - 1) - src_y;
23262306a36Sopenharmony_ci
23362306a36Sopenharmony_ci		memset(dst, 0, bgrt_tab.image_offset_x * 4);
23462306a36Sopenharmony_ci		dst_x = bgrt_tab.image_offset_x;
23562306a36Sopenharmony_ci		efifb_copy_bmp(bgrt_image + file_header->bitmap_offset +
23662306a36Sopenharmony_ci					    src_y * bmp_pitch,
23762306a36Sopenharmony_ci			       (u32 *)dst + dst_x, bmp_width, si);
23862306a36Sopenharmony_ci		dst_x += bmp_width;
23962306a36Sopenharmony_ci		memset((u32 *)dst + dst_x, 0, (si->lfb_width - dst_x) * 4);
24062306a36Sopenharmony_ci	}
24162306a36Sopenharmony_ci
24262306a36Sopenharmony_ci	memunmap(bgrt_image);
24362306a36Sopenharmony_ci	return;
24462306a36Sopenharmony_ci
24562306a36Sopenharmony_cierror:
24662306a36Sopenharmony_ci	memunmap(bgrt_image);
24762306a36Sopenharmony_ci	pr_warn("efifb: Ignoring BGRT: unexpected or invalid BMP data\n");
24862306a36Sopenharmony_ci}
24962306a36Sopenharmony_ci#else
25062306a36Sopenharmony_cistatic inline void efifb_show_boot_graphics(struct fb_info *info) {}
25162306a36Sopenharmony_ci#endif
25262306a36Sopenharmony_ci
25362306a36Sopenharmony_ci/*
25462306a36Sopenharmony_ci * fb_ops.fb_destroy is called by the last put_fb_info() call at the end
25562306a36Sopenharmony_ci * of unregister_framebuffer() or fb_release(). Do any cleanup here.
25662306a36Sopenharmony_ci */
25762306a36Sopenharmony_cistatic void efifb_destroy(struct fb_info *info)
25862306a36Sopenharmony_ci{
25962306a36Sopenharmony_ci	struct efifb_par *par = info->par;
26062306a36Sopenharmony_ci
26162306a36Sopenharmony_ci	if (efifb_pci_dev)
26262306a36Sopenharmony_ci		pm_runtime_put(&efifb_pci_dev->dev);
26362306a36Sopenharmony_ci
26462306a36Sopenharmony_ci	if (info->screen_base) {
26562306a36Sopenharmony_ci		if (mem_flags & (EFI_MEMORY_UC | EFI_MEMORY_WC))
26662306a36Sopenharmony_ci			iounmap(info->screen_base);
26762306a36Sopenharmony_ci		else
26862306a36Sopenharmony_ci			memunmap(info->screen_base);
26962306a36Sopenharmony_ci	}
27062306a36Sopenharmony_ci
27162306a36Sopenharmony_ci	if (request_mem_succeeded)
27262306a36Sopenharmony_ci		release_mem_region(par->base, par->size);
27362306a36Sopenharmony_ci	fb_dealloc_cmap(&info->cmap);
27462306a36Sopenharmony_ci
27562306a36Sopenharmony_ci	framebuffer_release(info);
27662306a36Sopenharmony_ci}
27762306a36Sopenharmony_ci
27862306a36Sopenharmony_cistatic const struct fb_ops efifb_ops = {
27962306a36Sopenharmony_ci	.owner		= THIS_MODULE,
28062306a36Sopenharmony_ci	FB_DEFAULT_IOMEM_OPS,
28162306a36Sopenharmony_ci	.fb_destroy	= efifb_destroy,
28262306a36Sopenharmony_ci	.fb_setcolreg	= efifb_setcolreg,
28362306a36Sopenharmony_ci};
28462306a36Sopenharmony_ci
28562306a36Sopenharmony_cistatic int efifb_setup(char *options)
28662306a36Sopenharmony_ci{
28762306a36Sopenharmony_ci	char *this_opt;
28862306a36Sopenharmony_ci
28962306a36Sopenharmony_ci	if (options && *options) {
29062306a36Sopenharmony_ci		while ((this_opt = strsep(&options, ",")) != NULL) {
29162306a36Sopenharmony_ci			if (!*this_opt) continue;
29262306a36Sopenharmony_ci
29362306a36Sopenharmony_ci			efifb_setup_from_dmi(&screen_info, this_opt);
29462306a36Sopenharmony_ci
29562306a36Sopenharmony_ci			if (!strncmp(this_opt, "base:", 5))
29662306a36Sopenharmony_ci				screen_info.lfb_base = simple_strtoul(this_opt+5, NULL, 0);
29762306a36Sopenharmony_ci			else if (!strncmp(this_opt, "stride:", 7))
29862306a36Sopenharmony_ci				screen_info.lfb_linelength = simple_strtoul(this_opt+7, NULL, 0) * 4;
29962306a36Sopenharmony_ci			else if (!strncmp(this_opt, "height:", 7))
30062306a36Sopenharmony_ci				screen_info.lfb_height = simple_strtoul(this_opt+7, NULL, 0);
30162306a36Sopenharmony_ci			else if (!strncmp(this_opt, "width:", 6))
30262306a36Sopenharmony_ci				screen_info.lfb_width = simple_strtoul(this_opt+6, NULL, 0);
30362306a36Sopenharmony_ci			else if (!strcmp(this_opt, "nowc"))
30462306a36Sopenharmony_ci				mem_flags &= ~EFI_MEMORY_WC;
30562306a36Sopenharmony_ci			else if (!strcmp(this_opt, "nobgrt"))
30662306a36Sopenharmony_ci				use_bgrt = false;
30762306a36Sopenharmony_ci		}
30862306a36Sopenharmony_ci	}
30962306a36Sopenharmony_ci
31062306a36Sopenharmony_ci	return 0;
31162306a36Sopenharmony_ci}
31262306a36Sopenharmony_ci
31362306a36Sopenharmony_cistatic inline bool fb_base_is_valid(void)
31462306a36Sopenharmony_ci{
31562306a36Sopenharmony_ci	if (screen_info.lfb_base)
31662306a36Sopenharmony_ci		return true;
31762306a36Sopenharmony_ci
31862306a36Sopenharmony_ci	if (!(screen_info.capabilities & VIDEO_CAPABILITY_64BIT_BASE))
31962306a36Sopenharmony_ci		return false;
32062306a36Sopenharmony_ci
32162306a36Sopenharmony_ci	if (screen_info.ext_lfb_base)
32262306a36Sopenharmony_ci		return true;
32362306a36Sopenharmony_ci
32462306a36Sopenharmony_ci	return false;
32562306a36Sopenharmony_ci}
32662306a36Sopenharmony_ci
32762306a36Sopenharmony_ci#define efifb_attr_decl(name, fmt)					\
32862306a36Sopenharmony_cistatic ssize_t name##_show(struct device *dev,				\
32962306a36Sopenharmony_ci			   struct device_attribute *attr,		\
33062306a36Sopenharmony_ci			   char *buf)					\
33162306a36Sopenharmony_ci{									\
33262306a36Sopenharmony_ci	return sprintf(buf, fmt "\n", (screen_info.lfb_##name));	\
33362306a36Sopenharmony_ci}									\
33462306a36Sopenharmony_cistatic DEVICE_ATTR_RO(name)
33562306a36Sopenharmony_ci
33662306a36Sopenharmony_ciefifb_attr_decl(base, "0x%x");
33762306a36Sopenharmony_ciefifb_attr_decl(linelength, "%u");
33862306a36Sopenharmony_ciefifb_attr_decl(height, "%u");
33962306a36Sopenharmony_ciefifb_attr_decl(width, "%u");
34062306a36Sopenharmony_ciefifb_attr_decl(depth, "%u");
34162306a36Sopenharmony_ci
34262306a36Sopenharmony_cistatic struct attribute *efifb_attrs[] = {
34362306a36Sopenharmony_ci	&dev_attr_base.attr,
34462306a36Sopenharmony_ci	&dev_attr_linelength.attr,
34562306a36Sopenharmony_ci	&dev_attr_width.attr,
34662306a36Sopenharmony_ci	&dev_attr_height.attr,
34762306a36Sopenharmony_ci	&dev_attr_depth.attr,
34862306a36Sopenharmony_ci	NULL
34962306a36Sopenharmony_ci};
35062306a36Sopenharmony_ciATTRIBUTE_GROUPS(efifb);
35162306a36Sopenharmony_ci
35262306a36Sopenharmony_cistatic bool pci_dev_disabled;	/* FB base matches BAR of a disabled device */
35362306a36Sopenharmony_ci
35462306a36Sopenharmony_cistatic struct resource *bar_resource;
35562306a36Sopenharmony_cistatic u64 bar_offset;
35662306a36Sopenharmony_ci
35762306a36Sopenharmony_cistatic int efifb_probe(struct platform_device *dev)
35862306a36Sopenharmony_ci{
35962306a36Sopenharmony_ci	struct fb_info *info;
36062306a36Sopenharmony_ci	struct efifb_par *par;
36162306a36Sopenharmony_ci	int err, orientation;
36262306a36Sopenharmony_ci	unsigned int size_vmode;
36362306a36Sopenharmony_ci	unsigned int size_remap;
36462306a36Sopenharmony_ci	unsigned int size_total;
36562306a36Sopenharmony_ci	char *option = NULL;
36662306a36Sopenharmony_ci	efi_memory_desc_t md;
36762306a36Sopenharmony_ci
36862306a36Sopenharmony_ci	if (screen_info.orig_video_isVGA != VIDEO_TYPE_EFI || pci_dev_disabled)
36962306a36Sopenharmony_ci		return -ENODEV;
37062306a36Sopenharmony_ci
37162306a36Sopenharmony_ci	if (fb_get_options("efifb", &option))
37262306a36Sopenharmony_ci		return -ENODEV;
37362306a36Sopenharmony_ci	efifb_setup(option);
37462306a36Sopenharmony_ci
37562306a36Sopenharmony_ci	/* We don't get linelength from UGA Draw Protocol, only from
37662306a36Sopenharmony_ci	 * EFI Graphics Protocol.  So if it's not in DMI, and it's not
37762306a36Sopenharmony_ci	 * passed in from the user, we really can't use the framebuffer.
37862306a36Sopenharmony_ci	 */
37962306a36Sopenharmony_ci	if (!screen_info.lfb_linelength)
38062306a36Sopenharmony_ci		return -ENODEV;
38162306a36Sopenharmony_ci
38262306a36Sopenharmony_ci	if (!screen_info.lfb_depth)
38362306a36Sopenharmony_ci		screen_info.lfb_depth = 32;
38462306a36Sopenharmony_ci	if (!screen_info.pages)
38562306a36Sopenharmony_ci		screen_info.pages = 1;
38662306a36Sopenharmony_ci	if (!fb_base_is_valid()) {
38762306a36Sopenharmony_ci		printk(KERN_DEBUG "efifb: invalid framebuffer address\n");
38862306a36Sopenharmony_ci		return -ENODEV;
38962306a36Sopenharmony_ci	}
39062306a36Sopenharmony_ci	printk(KERN_INFO "efifb: probing for efifb\n");
39162306a36Sopenharmony_ci
39262306a36Sopenharmony_ci	/* just assume they're all unset if any are */
39362306a36Sopenharmony_ci	if (!screen_info.blue_size) {
39462306a36Sopenharmony_ci		screen_info.blue_size = 8;
39562306a36Sopenharmony_ci		screen_info.blue_pos = 0;
39662306a36Sopenharmony_ci		screen_info.green_size = 8;
39762306a36Sopenharmony_ci		screen_info.green_pos = 8;
39862306a36Sopenharmony_ci		screen_info.red_size = 8;
39962306a36Sopenharmony_ci		screen_info.red_pos = 16;
40062306a36Sopenharmony_ci		screen_info.rsvd_size = 8;
40162306a36Sopenharmony_ci		screen_info.rsvd_pos = 24;
40262306a36Sopenharmony_ci	}
40362306a36Sopenharmony_ci
40462306a36Sopenharmony_ci	efifb_fix.smem_start = screen_info.lfb_base;
40562306a36Sopenharmony_ci
40662306a36Sopenharmony_ci	if (screen_info.capabilities & VIDEO_CAPABILITY_64BIT_BASE) {
40762306a36Sopenharmony_ci		u64 ext_lfb_base;
40862306a36Sopenharmony_ci
40962306a36Sopenharmony_ci		ext_lfb_base = (u64)(unsigned long)screen_info.ext_lfb_base << 32;
41062306a36Sopenharmony_ci		efifb_fix.smem_start |= ext_lfb_base;
41162306a36Sopenharmony_ci	}
41262306a36Sopenharmony_ci
41362306a36Sopenharmony_ci	if (bar_resource &&
41462306a36Sopenharmony_ci	    bar_resource->start + bar_offset != efifb_fix.smem_start) {
41562306a36Sopenharmony_ci		dev_info(&efifb_pci_dev->dev,
41662306a36Sopenharmony_ci			 "BAR has moved, updating efifb address\n");
41762306a36Sopenharmony_ci		efifb_fix.smem_start = bar_resource->start + bar_offset;
41862306a36Sopenharmony_ci	}
41962306a36Sopenharmony_ci
42062306a36Sopenharmony_ci	efifb_defined.bits_per_pixel = screen_info.lfb_depth;
42162306a36Sopenharmony_ci	efifb_defined.xres = screen_info.lfb_width;
42262306a36Sopenharmony_ci	efifb_defined.yres = screen_info.lfb_height;
42362306a36Sopenharmony_ci	efifb_fix.line_length = screen_info.lfb_linelength;
42462306a36Sopenharmony_ci
42562306a36Sopenharmony_ci	/*   size_vmode -- that is the amount of memory needed for the
42662306a36Sopenharmony_ci	 *                 used video mode, i.e. the minimum amount of
42762306a36Sopenharmony_ci	 *                 memory we need. */
42862306a36Sopenharmony_ci	size_vmode = efifb_defined.yres * efifb_fix.line_length;
42962306a36Sopenharmony_ci
43062306a36Sopenharmony_ci	/*   size_total -- all video memory we have. Used for
43162306a36Sopenharmony_ci	 *                 entries, ressource allocation and bounds
43262306a36Sopenharmony_ci	 *                 checking. */
43362306a36Sopenharmony_ci	size_total = screen_info.lfb_size;
43462306a36Sopenharmony_ci	if (size_total < size_vmode)
43562306a36Sopenharmony_ci		size_total = size_vmode;
43662306a36Sopenharmony_ci
43762306a36Sopenharmony_ci	/*   size_remap -- the amount of video memory we are going to
43862306a36Sopenharmony_ci	 *                 use for efifb.  With modern cards it is no
43962306a36Sopenharmony_ci	 *                 option to simply use size_total as that
44062306a36Sopenharmony_ci	 *                 wastes plenty of kernel address space. */
44162306a36Sopenharmony_ci	size_remap  = size_vmode * 2;
44262306a36Sopenharmony_ci	if (size_remap > size_total)
44362306a36Sopenharmony_ci		size_remap = size_total;
44462306a36Sopenharmony_ci	if (size_remap % PAGE_SIZE)
44562306a36Sopenharmony_ci		size_remap += PAGE_SIZE - (size_remap % PAGE_SIZE);
44662306a36Sopenharmony_ci	efifb_fix.smem_len = size_remap;
44762306a36Sopenharmony_ci
44862306a36Sopenharmony_ci	if (request_mem_region(efifb_fix.smem_start, size_remap, "efifb")) {
44962306a36Sopenharmony_ci		request_mem_succeeded = true;
45062306a36Sopenharmony_ci	} else {
45162306a36Sopenharmony_ci		/* We cannot make this fatal. Sometimes this comes from magic
45262306a36Sopenharmony_ci		   spaces our resource handlers simply don't know about */
45362306a36Sopenharmony_ci		pr_warn("efifb: cannot reserve video memory at 0x%lx\n",
45462306a36Sopenharmony_ci			efifb_fix.smem_start);
45562306a36Sopenharmony_ci	}
45662306a36Sopenharmony_ci
45762306a36Sopenharmony_ci	info = framebuffer_alloc(sizeof(*par), &dev->dev);
45862306a36Sopenharmony_ci	if (!info) {
45962306a36Sopenharmony_ci		err = -ENOMEM;
46062306a36Sopenharmony_ci		goto err_release_mem;
46162306a36Sopenharmony_ci	}
46262306a36Sopenharmony_ci	platform_set_drvdata(dev, info);
46362306a36Sopenharmony_ci	par = info->par;
46462306a36Sopenharmony_ci	info->pseudo_palette = par->pseudo_palette;
46562306a36Sopenharmony_ci
46662306a36Sopenharmony_ci	par->base = efifb_fix.smem_start;
46762306a36Sopenharmony_ci	par->size = size_remap;
46862306a36Sopenharmony_ci
46962306a36Sopenharmony_ci	if (efi_enabled(EFI_MEMMAP) &&
47062306a36Sopenharmony_ci	    !efi_mem_desc_lookup(efifb_fix.smem_start, &md)) {
47162306a36Sopenharmony_ci		if ((efifb_fix.smem_start + efifb_fix.smem_len) >
47262306a36Sopenharmony_ci		    (md.phys_addr + (md.num_pages << EFI_PAGE_SHIFT))) {
47362306a36Sopenharmony_ci			pr_err("efifb: video memory @ 0x%lx spans multiple EFI memory regions\n",
47462306a36Sopenharmony_ci			       efifb_fix.smem_start);
47562306a36Sopenharmony_ci			err = -EIO;
47662306a36Sopenharmony_ci			goto err_release_fb;
47762306a36Sopenharmony_ci		}
47862306a36Sopenharmony_ci		/*
47962306a36Sopenharmony_ci		 * If the UEFI memory map covers the efifb region, we may only
48062306a36Sopenharmony_ci		 * remap it using the attributes the memory map prescribes.
48162306a36Sopenharmony_ci		 */
48262306a36Sopenharmony_ci		md.attribute &= EFI_MEMORY_UC | EFI_MEMORY_WC |
48362306a36Sopenharmony_ci				EFI_MEMORY_WT | EFI_MEMORY_WB;
48462306a36Sopenharmony_ci		if (md.attribute) {
48562306a36Sopenharmony_ci			mem_flags |= EFI_MEMORY_WT | EFI_MEMORY_WB;
48662306a36Sopenharmony_ci			mem_flags &= md.attribute;
48762306a36Sopenharmony_ci		}
48862306a36Sopenharmony_ci	}
48962306a36Sopenharmony_ci	if (mem_flags & EFI_MEMORY_WC)
49062306a36Sopenharmony_ci		info->screen_base = ioremap_wc(efifb_fix.smem_start,
49162306a36Sopenharmony_ci					       efifb_fix.smem_len);
49262306a36Sopenharmony_ci	else if (mem_flags & EFI_MEMORY_UC)
49362306a36Sopenharmony_ci		info->screen_base = ioremap(efifb_fix.smem_start,
49462306a36Sopenharmony_ci					    efifb_fix.smem_len);
49562306a36Sopenharmony_ci	else if (mem_flags & EFI_MEMORY_WT)
49662306a36Sopenharmony_ci		info->screen_base = memremap(efifb_fix.smem_start,
49762306a36Sopenharmony_ci					     efifb_fix.smem_len, MEMREMAP_WT);
49862306a36Sopenharmony_ci	else if (mem_flags & EFI_MEMORY_WB)
49962306a36Sopenharmony_ci		info->screen_base = memremap(efifb_fix.smem_start,
50062306a36Sopenharmony_ci					     efifb_fix.smem_len, MEMREMAP_WB);
50162306a36Sopenharmony_ci	if (!info->screen_base) {
50262306a36Sopenharmony_ci		pr_err("efifb: abort, cannot remap video memory 0x%x @ 0x%lx\n",
50362306a36Sopenharmony_ci			efifb_fix.smem_len, efifb_fix.smem_start);
50462306a36Sopenharmony_ci		err = -EIO;
50562306a36Sopenharmony_ci		goto err_release_fb;
50662306a36Sopenharmony_ci	}
50762306a36Sopenharmony_ci
50862306a36Sopenharmony_ci	efifb_show_boot_graphics(info);
50962306a36Sopenharmony_ci
51062306a36Sopenharmony_ci	pr_info("efifb: framebuffer at 0x%lx, using %dk, total %dk\n",
51162306a36Sopenharmony_ci	       efifb_fix.smem_start, size_remap/1024, size_total/1024);
51262306a36Sopenharmony_ci	pr_info("efifb: mode is %dx%dx%d, linelength=%d, pages=%d\n",
51362306a36Sopenharmony_ci	       efifb_defined.xres, efifb_defined.yres,
51462306a36Sopenharmony_ci	       efifb_defined.bits_per_pixel, efifb_fix.line_length,
51562306a36Sopenharmony_ci	       screen_info.pages);
51662306a36Sopenharmony_ci
51762306a36Sopenharmony_ci	efifb_defined.xres_virtual = efifb_defined.xres;
51862306a36Sopenharmony_ci	efifb_defined.yres_virtual = efifb_fix.smem_len /
51962306a36Sopenharmony_ci					efifb_fix.line_length;
52062306a36Sopenharmony_ci	pr_info("efifb: scrolling: redraw\n");
52162306a36Sopenharmony_ci	efifb_defined.yres_virtual = efifb_defined.yres;
52262306a36Sopenharmony_ci
52362306a36Sopenharmony_ci	/* some dummy values for timing to make fbset happy */
52462306a36Sopenharmony_ci	efifb_defined.pixclock     = 10000000 / efifb_defined.xres *
52562306a36Sopenharmony_ci					1000 / efifb_defined.yres;
52662306a36Sopenharmony_ci	efifb_defined.left_margin  = (efifb_defined.xres / 8) & 0xf8;
52762306a36Sopenharmony_ci	efifb_defined.hsync_len    = (efifb_defined.xres / 8) & 0xf8;
52862306a36Sopenharmony_ci
52962306a36Sopenharmony_ci	efifb_defined.red.offset    = screen_info.red_pos;
53062306a36Sopenharmony_ci	efifb_defined.red.length    = screen_info.red_size;
53162306a36Sopenharmony_ci	efifb_defined.green.offset  = screen_info.green_pos;
53262306a36Sopenharmony_ci	efifb_defined.green.length  = screen_info.green_size;
53362306a36Sopenharmony_ci	efifb_defined.blue.offset   = screen_info.blue_pos;
53462306a36Sopenharmony_ci	efifb_defined.blue.length   = screen_info.blue_size;
53562306a36Sopenharmony_ci	efifb_defined.transp.offset = screen_info.rsvd_pos;
53662306a36Sopenharmony_ci	efifb_defined.transp.length = screen_info.rsvd_size;
53762306a36Sopenharmony_ci
53862306a36Sopenharmony_ci	pr_info("efifb: %s: "
53962306a36Sopenharmony_ci	       "size=%d:%d:%d:%d, shift=%d:%d:%d:%d\n",
54062306a36Sopenharmony_ci	       "Truecolor",
54162306a36Sopenharmony_ci	       screen_info.rsvd_size,
54262306a36Sopenharmony_ci	       screen_info.red_size,
54362306a36Sopenharmony_ci	       screen_info.green_size,
54462306a36Sopenharmony_ci	       screen_info.blue_size,
54562306a36Sopenharmony_ci	       screen_info.rsvd_pos,
54662306a36Sopenharmony_ci	       screen_info.red_pos,
54762306a36Sopenharmony_ci	       screen_info.green_pos,
54862306a36Sopenharmony_ci	       screen_info.blue_pos);
54962306a36Sopenharmony_ci
55062306a36Sopenharmony_ci	efifb_fix.ypanstep  = 0;
55162306a36Sopenharmony_ci	efifb_fix.ywrapstep = 0;
55262306a36Sopenharmony_ci
55362306a36Sopenharmony_ci	info->fbops = &efifb_ops;
55462306a36Sopenharmony_ci	info->var = efifb_defined;
55562306a36Sopenharmony_ci	info->fix = efifb_fix;
55662306a36Sopenharmony_ci
55762306a36Sopenharmony_ci	orientation = drm_get_panel_orientation_quirk(efifb_defined.xres,
55862306a36Sopenharmony_ci						      efifb_defined.yres);
55962306a36Sopenharmony_ci	switch (orientation) {
56062306a36Sopenharmony_ci	default:
56162306a36Sopenharmony_ci		info->fbcon_rotate_hint = FB_ROTATE_UR;
56262306a36Sopenharmony_ci		break;
56362306a36Sopenharmony_ci	case DRM_MODE_PANEL_ORIENTATION_BOTTOM_UP:
56462306a36Sopenharmony_ci		info->fbcon_rotate_hint = FB_ROTATE_UD;
56562306a36Sopenharmony_ci		break;
56662306a36Sopenharmony_ci	case DRM_MODE_PANEL_ORIENTATION_LEFT_UP:
56762306a36Sopenharmony_ci		info->fbcon_rotate_hint = FB_ROTATE_CCW;
56862306a36Sopenharmony_ci		break;
56962306a36Sopenharmony_ci	case DRM_MODE_PANEL_ORIENTATION_RIGHT_UP:
57062306a36Sopenharmony_ci		info->fbcon_rotate_hint = FB_ROTATE_CW;
57162306a36Sopenharmony_ci		break;
57262306a36Sopenharmony_ci	}
57362306a36Sopenharmony_ci
57462306a36Sopenharmony_ci	err = sysfs_create_groups(&dev->dev.kobj, efifb_groups);
57562306a36Sopenharmony_ci	if (err) {
57662306a36Sopenharmony_ci		pr_err("efifb: cannot add sysfs attrs\n");
57762306a36Sopenharmony_ci		goto err_unmap;
57862306a36Sopenharmony_ci	}
57962306a36Sopenharmony_ci	err = fb_alloc_cmap(&info->cmap, 256, 0);
58062306a36Sopenharmony_ci	if (err < 0) {
58162306a36Sopenharmony_ci		pr_err("efifb: cannot allocate colormap\n");
58262306a36Sopenharmony_ci		goto err_groups;
58362306a36Sopenharmony_ci	}
58462306a36Sopenharmony_ci
58562306a36Sopenharmony_ci	if (efifb_pci_dev)
58662306a36Sopenharmony_ci		WARN_ON(pm_runtime_get_sync(&efifb_pci_dev->dev) < 0);
58762306a36Sopenharmony_ci
58862306a36Sopenharmony_ci	err = devm_aperture_acquire_for_platform_device(dev, par->base, par->size);
58962306a36Sopenharmony_ci	if (err) {
59062306a36Sopenharmony_ci		pr_err("efifb: cannot acquire aperture\n");
59162306a36Sopenharmony_ci		goto err_put_rpm_ref;
59262306a36Sopenharmony_ci	}
59362306a36Sopenharmony_ci	err = register_framebuffer(info);
59462306a36Sopenharmony_ci	if (err < 0) {
59562306a36Sopenharmony_ci		pr_err("efifb: cannot register framebuffer\n");
59662306a36Sopenharmony_ci		goto err_put_rpm_ref;
59762306a36Sopenharmony_ci	}
59862306a36Sopenharmony_ci	fb_info(info, "%s frame buffer device\n", info->fix.id);
59962306a36Sopenharmony_ci	return 0;
60062306a36Sopenharmony_ci
60162306a36Sopenharmony_cierr_put_rpm_ref:
60262306a36Sopenharmony_ci	if (efifb_pci_dev)
60362306a36Sopenharmony_ci		pm_runtime_put(&efifb_pci_dev->dev);
60462306a36Sopenharmony_ci
60562306a36Sopenharmony_ci	fb_dealloc_cmap(&info->cmap);
60662306a36Sopenharmony_cierr_groups:
60762306a36Sopenharmony_ci	sysfs_remove_groups(&dev->dev.kobj, efifb_groups);
60862306a36Sopenharmony_cierr_unmap:
60962306a36Sopenharmony_ci	if (mem_flags & (EFI_MEMORY_UC | EFI_MEMORY_WC))
61062306a36Sopenharmony_ci		iounmap(info->screen_base);
61162306a36Sopenharmony_ci	else
61262306a36Sopenharmony_ci		memunmap(info->screen_base);
61362306a36Sopenharmony_cierr_release_fb:
61462306a36Sopenharmony_ci	framebuffer_release(info);
61562306a36Sopenharmony_cierr_release_mem:
61662306a36Sopenharmony_ci	if (request_mem_succeeded)
61762306a36Sopenharmony_ci		release_mem_region(efifb_fix.smem_start, size_total);
61862306a36Sopenharmony_ci	return err;
61962306a36Sopenharmony_ci}
62062306a36Sopenharmony_ci
62162306a36Sopenharmony_cistatic void efifb_remove(struct platform_device *pdev)
62262306a36Sopenharmony_ci{
62362306a36Sopenharmony_ci	struct fb_info *info = platform_get_drvdata(pdev);
62462306a36Sopenharmony_ci
62562306a36Sopenharmony_ci	/* efifb_destroy takes care of info cleanup */
62662306a36Sopenharmony_ci	unregister_framebuffer(info);
62762306a36Sopenharmony_ci	sysfs_remove_groups(&pdev->dev.kobj, efifb_groups);
62862306a36Sopenharmony_ci}
62962306a36Sopenharmony_ci
63062306a36Sopenharmony_cistatic struct platform_driver efifb_driver = {
63162306a36Sopenharmony_ci	.driver = {
63262306a36Sopenharmony_ci		.name = "efi-framebuffer",
63362306a36Sopenharmony_ci	},
63462306a36Sopenharmony_ci	.probe = efifb_probe,
63562306a36Sopenharmony_ci	.remove_new = efifb_remove,
63662306a36Sopenharmony_ci};
63762306a36Sopenharmony_ci
63862306a36Sopenharmony_cibuiltin_platform_driver(efifb_driver);
63962306a36Sopenharmony_ci
64062306a36Sopenharmony_ci#if defined(CONFIG_PCI)
64162306a36Sopenharmony_ci
64262306a36Sopenharmony_cistatic void record_efifb_bar_resource(struct pci_dev *dev, int idx, u64 offset)
64362306a36Sopenharmony_ci{
64462306a36Sopenharmony_ci	u16 word;
64562306a36Sopenharmony_ci
64662306a36Sopenharmony_ci	efifb_pci_dev = dev;
64762306a36Sopenharmony_ci
64862306a36Sopenharmony_ci	pci_read_config_word(dev, PCI_COMMAND, &word);
64962306a36Sopenharmony_ci	if (!(word & PCI_COMMAND_MEMORY)) {
65062306a36Sopenharmony_ci		pci_dev_disabled = true;
65162306a36Sopenharmony_ci		dev_err(&dev->dev,
65262306a36Sopenharmony_ci			"BAR %d: assigned to efifb but device is disabled!\n",
65362306a36Sopenharmony_ci			idx);
65462306a36Sopenharmony_ci		return;
65562306a36Sopenharmony_ci	}
65662306a36Sopenharmony_ci
65762306a36Sopenharmony_ci	bar_resource = &dev->resource[idx];
65862306a36Sopenharmony_ci	bar_offset = offset;
65962306a36Sopenharmony_ci
66062306a36Sopenharmony_ci	dev_info(&dev->dev, "BAR %d: assigned to efifb\n", idx);
66162306a36Sopenharmony_ci}
66262306a36Sopenharmony_ci
66362306a36Sopenharmony_cistatic void efifb_fixup_resources(struct pci_dev *dev)
66462306a36Sopenharmony_ci{
66562306a36Sopenharmony_ci	u64 base = screen_info.lfb_base;
66662306a36Sopenharmony_ci	u64 size = screen_info.lfb_size;
66762306a36Sopenharmony_ci	int i;
66862306a36Sopenharmony_ci
66962306a36Sopenharmony_ci	if (efifb_pci_dev || screen_info.orig_video_isVGA != VIDEO_TYPE_EFI)
67062306a36Sopenharmony_ci		return;
67162306a36Sopenharmony_ci
67262306a36Sopenharmony_ci	if (screen_info.capabilities & VIDEO_CAPABILITY_64BIT_BASE)
67362306a36Sopenharmony_ci		base |= (u64)screen_info.ext_lfb_base << 32;
67462306a36Sopenharmony_ci
67562306a36Sopenharmony_ci	if (!base)
67662306a36Sopenharmony_ci		return;
67762306a36Sopenharmony_ci
67862306a36Sopenharmony_ci	for (i = 0; i < PCI_STD_NUM_BARS; i++) {
67962306a36Sopenharmony_ci		struct resource *res = &dev->resource[i];
68062306a36Sopenharmony_ci
68162306a36Sopenharmony_ci		if (!(res->flags & IORESOURCE_MEM))
68262306a36Sopenharmony_ci			continue;
68362306a36Sopenharmony_ci
68462306a36Sopenharmony_ci		if (res->start <= base && res->end >= base + size - 1) {
68562306a36Sopenharmony_ci			record_efifb_bar_resource(dev, i, base - res->start);
68662306a36Sopenharmony_ci			break;
68762306a36Sopenharmony_ci		}
68862306a36Sopenharmony_ci	}
68962306a36Sopenharmony_ci}
69062306a36Sopenharmony_ciDECLARE_PCI_FIXUP_CLASS_HEADER(PCI_ANY_ID, PCI_ANY_ID, PCI_BASE_CLASS_DISPLAY,
69162306a36Sopenharmony_ci			       16, efifb_fixup_resources);
69262306a36Sopenharmony_ci
69362306a36Sopenharmony_ci#endif
694