e14a685dfa
The current logic for dmi matching in efifb does not allow efifb to load on all hardware that we can dmi match for. For a real world example, boot with elilo (3.7 or 3.8 vanilla) and on a Apple (MacBook) and EFI framebuffer driver will not load (you will have no video). This specific hardware is efi v1.10, so we have UGA and not GOP. Without special bootloader magic (i.e. extra elilo patches for UGA graphics detection) no screen info will be passed to the kernel and as a result efifb will not load. This patch allows the dmi match to happen by moving it to earlier in efifb_init, and sets the video type (in set_system) so that efifb can load when we have a valid dmi match and already know the specifics of the hardware. Without this patch the efifb driver will fail to load in the event screen info is not found and passed in by the bootloader, being that we will never get to look for a dmi match. A primary reason for matching with dmi is because not all bootloaders detect the video info properly. The solution is that in the event of a dmi match, we should set screen_info.orig_video_isVGA. Most bootloaders fail to set screen info on Apple hardware, and this is a big problem for people who use Apple hardware. Tested on a MacBook SantaRosa with elilo-3.8 (vanilla) and resolves the issue, the dmi match now works, EFI framebuffer now loads and video works. Signed-off-by: Brian Maly <bmaly@redhat.com> Acked-by: Huang Ying <ying.huang@intel.com> Cc: Krzysztof Helt <krzysztof.h1@poczta.fm> Cc: Chandramouli Narayanan <mouli@linux.intel.com> Acked-by: Peter Jones <pjones@redhat.com> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
406 lines
12 KiB
C
406 lines
12 KiB
C
/*
|
|
* Framebuffer driver for EFI/UEFI based system
|
|
*
|
|
* (c) 2006 Edgar Hucek <gimli@dark-green.com>
|
|
* Original efi driver written by Gerd Knorr <kraxel@goldbach.in-berlin.de>
|
|
*
|
|
*/
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/fb.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/screen_info.h>
|
|
#include <linux/dmi.h>
|
|
|
|
#include <video/vga.h>
|
|
|
|
static struct fb_var_screeninfo efifb_defined __initdata = {
|
|
.activate = FB_ACTIVATE_NOW,
|
|
.height = -1,
|
|
.width = -1,
|
|
.right_margin = 32,
|
|
.upper_margin = 16,
|
|
.lower_margin = 4,
|
|
.vsync_len = 4,
|
|
.vmode = FB_VMODE_NONINTERLACED,
|
|
};
|
|
|
|
static struct fb_fix_screeninfo efifb_fix __initdata = {
|
|
.id = "EFI VGA",
|
|
.type = FB_TYPE_PACKED_PIXELS,
|
|
.accel = FB_ACCEL_NONE,
|
|
.visual = FB_VISUAL_TRUECOLOR,
|
|
};
|
|
|
|
enum {
|
|
M_I17, /* 17-Inch iMac */
|
|
M_I20, /* 20-Inch iMac */
|
|
M_I20_SR, /* 20-Inch iMac (Santa Rosa) */
|
|
M_I24, /* 24-Inch iMac */
|
|
M_MINI, /* Mac Mini */
|
|
M_MB, /* MacBook */
|
|
M_MB_2, /* MacBook, 2nd rev. */
|
|
M_MB_3, /* MacBook, 3rd rev. */
|
|
M_MB_SR, /* MacBook, 2nd gen, (Santa Rosa) */
|
|
M_MBA, /* MacBook Air */
|
|
M_MBP, /* MacBook Pro */
|
|
M_MBP_2, /* MacBook Pro 2nd gen */
|
|
M_MBP_SR, /* MacBook Pro (Santa Rosa) */
|
|
M_MBP_4, /* MacBook Pro, 4th gen */
|
|
M_UNKNOWN /* placeholder */
|
|
};
|
|
|
|
static struct efifb_dmi_info {
|
|
char *optname;
|
|
unsigned long base;
|
|
int stride;
|
|
int width;
|
|
int height;
|
|
} dmi_list[] = {
|
|
[M_I17] = { "i17", 0x80010000, 1472 * 4, 1440, 900 },
|
|
[M_I20] = { "i20", 0x80010000, 1728 * 4, 1680, 1050 }, /* guess */
|
|
[M_I20_SR] = { "imac7", 0x40010000, 1728 * 4, 1680, 1050 },
|
|
[M_I24] = { "i24", 0x80010000, 2048 * 4, 1920, 1200 }, /* guess */
|
|
[M_MINI]= { "mini", 0x80000000, 2048 * 4, 1024, 768 },
|
|
[M_MB] = { "macbook", 0x80000000, 2048 * 4, 1280, 800 },
|
|
[M_MBA] = { "mba", 0x80000000, 2048 * 4, 1280, 800 },
|
|
[M_MBP] = { "mbp", 0x80010000, 1472 * 4, 1440, 900 },
|
|
[M_MBP_2] = { "mbp2", 0, 0, 0, 0 }, /* placeholder */
|
|
[M_MBP_SR] = { "mbp3", 0x80030000, 2048 * 4, 1440, 900 },
|
|
[M_MBP_4] = { "mbp4", 0xc0060000, 2048 * 4, 1920, 1200 },
|
|
[M_UNKNOWN] = { NULL, 0, 0, 0, 0 }
|
|
};
|
|
|
|
static int set_system(const struct dmi_system_id *id);
|
|
|
|
#define EFIFB_DMI_SYSTEM_ID(vendor, name, enumid) \
|
|
{ set_system, name, { \
|
|
DMI_MATCH(DMI_BIOS_VENDOR, vendor), \
|
|
DMI_MATCH(DMI_PRODUCT_NAME, name) }, \
|
|
&dmi_list[enumid] }
|
|
|
|
static struct dmi_system_id __initdata dmi_system_table[] = {
|
|
EFIFB_DMI_SYSTEM_ID("Apple Computer, Inc.", "iMac4,1", M_I17),
|
|
/* At least one of these two will be right; maybe both? */
|
|
EFIFB_DMI_SYSTEM_ID("Apple Computer, Inc.", "iMac5,1", M_I20),
|
|
EFIFB_DMI_SYSTEM_ID("Apple Inc.", "iMac5,1", M_I20),
|
|
/* At least one of these two will be right; maybe both? */
|
|
EFIFB_DMI_SYSTEM_ID("Apple Computer, Inc.", "iMac6,1", M_I24),
|
|
EFIFB_DMI_SYSTEM_ID("Apple Inc.", "iMac6,1", M_I24),
|
|
EFIFB_DMI_SYSTEM_ID("Apple Inc.", "iMac7,1", M_I20_SR),
|
|
EFIFB_DMI_SYSTEM_ID("Apple Computer, Inc.", "Macmini1,1", M_MINI),
|
|
EFIFB_DMI_SYSTEM_ID("Apple Computer, Inc.", "MacBook1,1", M_MB),
|
|
/* At least one of these two will be right; maybe both? */
|
|
EFIFB_DMI_SYSTEM_ID("Apple Computer, Inc.", "MacBook2,1", M_MB),
|
|
EFIFB_DMI_SYSTEM_ID("Apple Inc.", "MacBook2,1", M_MB),
|
|
/* At least one of these two will be right; maybe both? */
|
|
EFIFB_DMI_SYSTEM_ID("Apple Computer, Inc.", "MacBook3,1", M_MB),
|
|
EFIFB_DMI_SYSTEM_ID("Apple Inc.", "MacBook3,1", M_MB),
|
|
EFIFB_DMI_SYSTEM_ID("Apple Inc.", "MacBook4,1", M_MB),
|
|
EFIFB_DMI_SYSTEM_ID("Apple Inc.", "MacBookAir1,1", M_MBA),
|
|
EFIFB_DMI_SYSTEM_ID("Apple Computer, Inc.", "MacBookPro1,1", M_MBP),
|
|
EFIFB_DMI_SYSTEM_ID("Apple Computer, Inc.", "MacBookPro2,1", M_MBP_2),
|
|
EFIFB_DMI_SYSTEM_ID("Apple Inc.", "MacBookPro2,1", M_MBP_2),
|
|
EFIFB_DMI_SYSTEM_ID("Apple Computer, Inc.", "MacBookPro3,1", M_MBP_SR),
|
|
EFIFB_DMI_SYSTEM_ID("Apple Inc.", "MacBookPro3,1", M_MBP_SR),
|
|
EFIFB_DMI_SYSTEM_ID("Apple Inc.", "MacBookPro4,1", M_MBP_4),
|
|
{},
|
|
};
|
|
|
|
static int set_system(const struct dmi_system_id *id)
|
|
{
|
|
struct efifb_dmi_info *info = id->driver_data;
|
|
if (info->base == 0)
|
|
return -ENODEV;
|
|
|
|
printk(KERN_INFO "efifb: dmi detected %s - framebuffer at %p "
|
|
"(%dx%d, stride %d)\n", id->ident,
|
|
(void *)info->base, info->width, info->height,
|
|
info->stride);
|
|
|
|
/* Trust the bootloader over the DMI tables */
|
|
if (screen_info.lfb_base == 0)
|
|
screen_info.lfb_base = info->base;
|
|
if (screen_info.lfb_linelength == 0)
|
|
screen_info.lfb_linelength = info->stride;
|
|
if (screen_info.lfb_width == 0)
|
|
screen_info.lfb_width = info->width;
|
|
if (screen_info.lfb_height == 0)
|
|
screen_info.lfb_height = info->height;
|
|
if (screen_info.orig_video_isVGA == 0)
|
|
screen_info.orig_video_isVGA = VIDEO_TYPE_EFI;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int efifb_setcolreg(unsigned regno, unsigned red, unsigned green,
|
|
unsigned blue, unsigned transp,
|
|
struct fb_info *info)
|
|
{
|
|
/*
|
|
* Set a single color register. The values supplied are
|
|
* already rounded down to the hardware's capabilities
|
|
* (according to the entries in the `var' structure). Return
|
|
* != 0 for invalid regno.
|
|
*/
|
|
|
|
if (regno >= info->cmap.len)
|
|
return 1;
|
|
|
|
if (regno < 16) {
|
|
red >>= 8;
|
|
green >>= 8;
|
|
blue >>= 8;
|
|
((u32 *)(info->pseudo_palette))[regno] =
|
|
(red << info->var.red.offset) |
|
|
(green << info->var.green.offset) |
|
|
(blue << info->var.blue.offset);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static struct fb_ops efifb_ops = {
|
|
.owner = THIS_MODULE,
|
|
.fb_setcolreg = efifb_setcolreg,
|
|
.fb_fillrect = cfb_fillrect,
|
|
.fb_copyarea = cfb_copyarea,
|
|
.fb_imageblit = cfb_imageblit,
|
|
};
|
|
|
|
static int __init efifb_setup(char *options)
|
|
{
|
|
char *this_opt;
|
|
int i;
|
|
|
|
if (!options || !*options)
|
|
return 0;
|
|
|
|
while ((this_opt = strsep(&options, ",")) != NULL) {
|
|
if (!*this_opt) continue;
|
|
|
|
for (i = 0; i < M_UNKNOWN; i++) {
|
|
if (!strcmp(this_opt, dmi_list[i].optname) &&
|
|
dmi_list[i].base != 0) {
|
|
screen_info.lfb_base = dmi_list[i].base;
|
|
screen_info.lfb_linelength = dmi_list[i].stride;
|
|
screen_info.lfb_width = dmi_list[i].width;
|
|
screen_info.lfb_height = dmi_list[i].height;
|
|
}
|
|
}
|
|
if (!strncmp(this_opt, "base:", 5))
|
|
screen_info.lfb_base = simple_strtoul(this_opt+5, NULL, 0);
|
|
else if (!strncmp(this_opt, "stride:", 7))
|
|
screen_info.lfb_linelength = simple_strtoul(this_opt+7, NULL, 0) * 4;
|
|
else if (!strncmp(this_opt, "height:", 7))
|
|
screen_info.lfb_height = simple_strtoul(this_opt+7, NULL, 0);
|
|
else if (!strncmp(this_opt, "width:", 6))
|
|
screen_info.lfb_width = simple_strtoul(this_opt+6, NULL, 0);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int __init efifb_probe(struct platform_device *dev)
|
|
{
|
|
struct fb_info *info;
|
|
int err;
|
|
unsigned int size_vmode;
|
|
unsigned int size_remap;
|
|
unsigned int size_total;
|
|
int request_succeeded = 0;
|
|
|
|
printk(KERN_INFO "efifb: probing for efifb\n");
|
|
|
|
if (!screen_info.lfb_depth)
|
|
screen_info.lfb_depth = 32;
|
|
if (!screen_info.pages)
|
|
screen_info.pages = 1;
|
|
|
|
/* just assume they're all unset if any are */
|
|
if (!screen_info.blue_size) {
|
|
screen_info.blue_size = 8;
|
|
screen_info.blue_pos = 0;
|
|
screen_info.green_size = 8;
|
|
screen_info.green_pos = 8;
|
|
screen_info.red_size = 8;
|
|
screen_info.red_pos = 16;
|
|
screen_info.rsvd_size = 8;
|
|
screen_info.rsvd_pos = 24;
|
|
}
|
|
|
|
efifb_fix.smem_start = screen_info.lfb_base;
|
|
efifb_defined.bits_per_pixel = screen_info.lfb_depth;
|
|
efifb_defined.xres = screen_info.lfb_width;
|
|
efifb_defined.yres = screen_info.lfb_height;
|
|
efifb_fix.line_length = screen_info.lfb_linelength;
|
|
|
|
/* size_vmode -- that is the amount of memory needed for the
|
|
* used video mode, i.e. the minimum amount of
|
|
* memory we need. */
|
|
size_vmode = efifb_defined.yres * efifb_fix.line_length;
|
|
|
|
/* size_total -- all video memory we have. Used for
|
|
* entries, ressource allocation and bounds
|
|
* checking. */
|
|
size_total = screen_info.lfb_size;
|
|
if (size_total < size_vmode)
|
|
size_total = size_vmode;
|
|
|
|
/* size_remap -- the amount of video memory we are going to
|
|
* use for efifb. With modern cards it is no
|
|
* option to simply use size_total as that
|
|
* wastes plenty of kernel address space. */
|
|
size_remap = size_vmode * 2;
|
|
if (size_remap > size_total)
|
|
size_remap = size_total;
|
|
if (size_remap % PAGE_SIZE)
|
|
size_remap += PAGE_SIZE - (size_remap % PAGE_SIZE);
|
|
efifb_fix.smem_len = size_remap;
|
|
|
|
if (request_mem_region(efifb_fix.smem_start, size_remap, "efifb")) {
|
|
request_succeeded = 1;
|
|
} else {
|
|
/* We cannot make this fatal. Sometimes this comes from magic
|
|
spaces our resource handlers simply don't know about */
|
|
printk(KERN_WARNING
|
|
"efifb: cannot reserve video memory at 0x%lx\n",
|
|
efifb_fix.smem_start);
|
|
}
|
|
|
|
info = framebuffer_alloc(sizeof(u32) * 16, &dev->dev);
|
|
if (!info) {
|
|
printk(KERN_ERR "efifb: cannot allocate framebuffer\n");
|
|
err = -ENOMEM;
|
|
goto err_release_mem;
|
|
}
|
|
info->pseudo_palette = info->par;
|
|
info->par = NULL;
|
|
|
|
info->screen_base = ioremap(efifb_fix.smem_start, efifb_fix.smem_len);
|
|
if (!info->screen_base) {
|
|
printk(KERN_ERR "efifb: abort, cannot ioremap video memory "
|
|
"0x%x @ 0x%lx\n",
|
|
efifb_fix.smem_len, efifb_fix.smem_start);
|
|
err = -EIO;
|
|
goto err_release_fb;
|
|
}
|
|
|
|
printk(KERN_INFO "efifb: framebuffer at 0x%lx, mapped to 0x%p, "
|
|
"using %dk, total %dk\n",
|
|
efifb_fix.smem_start, info->screen_base,
|
|
size_remap/1024, size_total/1024);
|
|
printk(KERN_INFO "efifb: mode is %dx%dx%d, linelength=%d, pages=%d\n",
|
|
efifb_defined.xres, efifb_defined.yres,
|
|
efifb_defined.bits_per_pixel, efifb_fix.line_length,
|
|
screen_info.pages);
|
|
|
|
efifb_defined.xres_virtual = efifb_defined.xres;
|
|
efifb_defined.yres_virtual = efifb_fix.smem_len /
|
|
efifb_fix.line_length;
|
|
printk(KERN_INFO "efifb: scrolling: redraw\n");
|
|
efifb_defined.yres_virtual = efifb_defined.yres;
|
|
|
|
/* some dummy values for timing to make fbset happy */
|
|
efifb_defined.pixclock = 10000000 / efifb_defined.xres *
|
|
1000 / efifb_defined.yres;
|
|
efifb_defined.left_margin = (efifb_defined.xres / 8) & 0xf8;
|
|
efifb_defined.hsync_len = (efifb_defined.xres / 8) & 0xf8;
|
|
|
|
efifb_defined.red.offset = screen_info.red_pos;
|
|
efifb_defined.red.length = screen_info.red_size;
|
|
efifb_defined.green.offset = screen_info.green_pos;
|
|
efifb_defined.green.length = screen_info.green_size;
|
|
efifb_defined.blue.offset = screen_info.blue_pos;
|
|
efifb_defined.blue.length = screen_info.blue_size;
|
|
efifb_defined.transp.offset = screen_info.rsvd_pos;
|
|
efifb_defined.transp.length = screen_info.rsvd_size;
|
|
|
|
printk(KERN_INFO "efifb: %s: "
|
|
"size=%d:%d:%d:%d, shift=%d:%d:%d:%d\n",
|
|
"Truecolor",
|
|
screen_info.rsvd_size,
|
|
screen_info.red_size,
|
|
screen_info.green_size,
|
|
screen_info.blue_size,
|
|
screen_info.rsvd_pos,
|
|
screen_info.red_pos,
|
|
screen_info.green_pos,
|
|
screen_info.blue_pos);
|
|
|
|
efifb_fix.ypanstep = 0;
|
|
efifb_fix.ywrapstep = 0;
|
|
|
|
info->fbops = &efifb_ops;
|
|
info->var = efifb_defined;
|
|
info->fix = efifb_fix;
|
|
info->flags = FBINFO_FLAG_DEFAULT;
|
|
|
|
if ((err = fb_alloc_cmap(&info->cmap, 256, 0)) < 0) {
|
|
printk(KERN_ERR "efifb: cannot allocate colormap\n");
|
|
goto err_unmap;
|
|
}
|
|
if ((err = register_framebuffer(info)) < 0) {
|
|
printk(KERN_ERR "efifb: cannot register framebuffer\n");
|
|
goto err_fb_dealoc;
|
|
}
|
|
printk(KERN_INFO "fb%d: %s frame buffer device\n",
|
|
info->node, info->fix.id);
|
|
return 0;
|
|
|
|
err_fb_dealoc:
|
|
fb_dealloc_cmap(&info->cmap);
|
|
err_unmap:
|
|
iounmap(info->screen_base);
|
|
err_release_fb:
|
|
framebuffer_release(info);
|
|
err_release_mem:
|
|
if (request_succeeded)
|
|
release_mem_region(efifb_fix.smem_start, size_total);
|
|
return err;
|
|
}
|
|
|
|
static struct platform_driver efifb_driver = {
|
|
.probe = efifb_probe,
|
|
.driver = {
|
|
.name = "efifb",
|
|
},
|
|
};
|
|
|
|
static struct platform_device efifb_device = {
|
|
.name = "efifb",
|
|
};
|
|
|
|
static int __init efifb_init(void)
|
|
{
|
|
int ret;
|
|
char *option = NULL;
|
|
|
|
dmi_check_system(dmi_system_table);
|
|
|
|
if (screen_info.orig_video_isVGA != VIDEO_TYPE_EFI)
|
|
return -ENODEV;
|
|
|
|
if (fb_get_options("efifb", &option))
|
|
return -ENODEV;
|
|
efifb_setup(option);
|
|
|
|
/* We don't get linelength from UGA Draw Protocol, only from
|
|
* EFI Graphics Protocol. So if it's not in DMI, and it's not
|
|
* passed in from the user, we really can't use the framebuffer.
|
|
*/
|
|
if (!screen_info.lfb_linelength)
|
|
return -ENODEV;
|
|
|
|
ret = platform_driver_register(&efifb_driver);
|
|
|
|
if (!ret) {
|
|
ret = platform_device_register(&efifb_device);
|
|
if (ret)
|
|
platform_driver_unregister(&efifb_driver);
|
|
}
|
|
return ret;
|
|
}
|
|
module_init(efifb_init);
|
|
|
|
MODULE_LICENSE("GPL");
|