efifb.c 9.8 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14
/*
 * 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>
15
#include <linux/dmi.h>
16
#include <linux/pci.h>
17
#include <video/vga.h>
18
#include <asm/sysfb.h>
19

20 21
static bool request_mem_succeeded = false;

22 23
static struct pci_dev *default_vga;

24
static struct fb_var_screeninfo efifb_defined = {
25 26 27 28 29 30 31 32 33 34
	.activate		= FB_ACTIVATE_NOW,
	.height			= -1,
	.width			= -1,
	.right_margin		= 32,
	.upper_margin		= 16,
	.lower_margin		= 4,
	.vsync_len		= 4,
	.vmode			= FB_VMODE_NONINTERLACED,
};

35
static struct fb_fix_screeninfo efifb_fix = {
36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
	.id			= "EFI VGA",
	.type			= FB_TYPE_PACKED_PIXELS,
	.accel			= FB_ACCEL_NONE,
	.visual			= FB_VISUAL_TRUECOLOR,
};

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;
}

M
Marcin Slusarz 已提交
68 69 70 71
static void efifb_destroy(struct fb_info *info)
{
	if (info->screen_base)
		iounmap(info->screen_base);
72 73 74
	if (request_mem_succeeded)
		release_mem_region(info->apertures->ranges[0].base,
				   info->apertures->ranges[0].size);
75
	fb_dealloc_cmap(&info->cmap);
M
Marcin Slusarz 已提交
76 77
}

78 79
static struct fb_ops efifb_ops = {
	.owner		= THIS_MODULE,
M
Marcin Slusarz 已提交
80
	.fb_destroy	= efifb_destroy,
81 82 83 84 85 86
	.fb_setcolreg	= efifb_setcolreg,
	.fb_fillrect	= cfb_fillrect,
	.fb_copyarea	= cfb_copyarea,
	.fb_imageblit	= cfb_imageblit,
};

87 88 89 90 91
struct pci_dev *vga_default_device(void)
{
	return default_vga;
}

92 93
EXPORT_SYMBOL_GPL(vga_default_device);

94 95 96 97 98
void vga_set_default_device(struct pci_dev *pdev)
{
	default_vga = pdev;
}

99
static int efifb_setup(char *options)
100 101 102
{
	char *this_opt;
	int i;
103
	struct pci_dev *dev = NULL;
104

105 106 107
	if (options && *options) {
		while ((this_opt = strsep(&options, ",")) != NULL) {
			if (!*this_opt) continue;
108

109
			for (i = 0; i < M_UNKNOWN; i++) {
110 111
				if (efifb_dmi_list[i].base != 0 &&
				    !strcmp(this_opt, efifb_dmi_list[i].optname)) {
112 113 114 115
					screen_info.lfb_base = efifb_dmi_list[i].base;
					screen_info.lfb_linelength = efifb_dmi_list[i].stride;
					screen_info.lfb_width = efifb_dmi_list[i].width;
					screen_info.lfb_height = efifb_dmi_list[i].height;
116
				}
117
			}
118 119 120 121 122 123 124 125
			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);
126 127
		}
	}
128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152

	for_each_pci_dev(dev) {
		int i;

		if ((dev->class >> 8) != PCI_CLASS_DISPLAY_VGA)
			continue;

		for (i=0; i < DEVICE_COUNT_RESOURCE; i++) {
			resource_size_t start, end;

			if (!(pci_resource_flags(dev, i) & IORESOURCE_MEM))
				continue;

			start = pci_resource_start(dev, i);
			end  = pci_resource_end(dev, i);

			if (!start || !end)
				continue;

			if (screen_info.lfb_base >= start &&
			    (screen_info.lfb_base + screen_info.lfb_size) < end)
				default_vga = dev;
		}
	}

153 154 155
	return 0;
}

156
static int efifb_probe(struct platform_device *dev)
157 158 159 160 161 162
{
	struct fb_info *info;
	int err;
	unsigned int size_vmode;
	unsigned int size_remap;
	unsigned int size_total;
163 164 165 166 167 168 169 170 171 172 173 174 175 176 177
	char *option = NULL;

	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;
178 179 180 181 182

	if (!screen_info.lfb_depth)
		screen_info.lfb_depth = 32;
	if (!screen_info.pages)
		screen_info.pages = 1;
183 184 185 186 187
	if (!screen_info.lfb_base) {
		printk(KERN_DEBUG "efifb: invalid framebuffer address\n");
		return -ENODEV;
	}
	printk(KERN_INFO "efifb: probing for efifb\n");
188 189 190 191 192 193 194 195 196 197 198 199

	/* 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;
	}
200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225

	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;
226 227
	if (size_remap % PAGE_SIZE)
		size_remap += PAGE_SIZE - (size_remap % PAGE_SIZE);
228 229
	efifb_fix.smem_len = size_remap;

230
	if (request_mem_region(efifb_fix.smem_start, size_remap, "efifb")) {
231
		request_mem_succeeded = true;
232
	} else {
233 234 235 236 237
		/* 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);
238
	}
239 240 241

	info = framebuffer_alloc(sizeof(u32) * 16, &dev->dev);
	if (!info) {
242
		printk(KERN_ERR "efifb: cannot allocate framebuffer\n");
243 244 245
		err = -ENOMEM;
		goto err_release_mem;
	}
246
	platform_set_drvdata(dev, info);
247 248 249
	info->pseudo_palette = info->par;
	info->par = NULL;

250 251 252 253 254 255 256
	info->apertures = alloc_apertures(1);
	if (!info->apertures) {
		err = -ENOMEM;
		goto err_release_fb;
	}
	info->apertures->ranges[0].base = efifb_fix.smem_start;
	info->apertures->ranges[0].size = size_remap;
257

A
Andy Lutomirski 已提交
258
	info->screen_base = ioremap_wc(efifb_fix.smem_start, efifb_fix.smem_len);
259 260 261 262 263
	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;
264
		goto err_release_fb;
265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314
	}

	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;
315
	info->flags = FBINFO_FLAG_DEFAULT | FBINFO_MISC_FIRMWARE;
316

317 318
	if ((err = fb_alloc_cmap(&info->cmap, 256, 0)) < 0) {
		printk(KERN_ERR "efifb: cannot allocate colormap\n");
319 320
		goto err_unmap;
	}
321 322
	if ((err = register_framebuffer(info)) < 0) {
		printk(KERN_ERR "efifb: cannot register framebuffer\n");
323 324
		goto err_fb_dealoc;
	}
J
Joe Perches 已提交
325
	fb_info(info, "%s frame buffer device\n", info->fix.id);
326 327 328 329 330 331
	return 0;

err_fb_dealoc:
	fb_dealloc_cmap(&info->cmap);
err_unmap:
	iounmap(info->screen_base);
332
err_release_fb:
333 334
	framebuffer_release(info);
err_release_mem:
335
	if (request_mem_succeeded)
336
		release_mem_region(efifb_fix.smem_start, size_total);
337 338 339
	return err;
}

340 341 342 343 344 345 346 347 348 349
static int efifb_remove(struct platform_device *pdev)
{
	struct fb_info *info = platform_get_drvdata(pdev);

	unregister_framebuffer(info);
	framebuffer_release(info);

	return 0;
}

350
static struct platform_driver efifb_driver = {
351 352 353
	.driver = {
		.name = "efi-framebuffer",
		.owner = THIS_MODULE,
354
	},
355
	.probe = efifb_probe,
356
	.remove = efifb_remove,
357 358
};

359
module_platform_driver(efifb_driver);
360
MODULE_LICENSE("GPL");