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