552 lines · c
1/*2 * linux/drivers/video/vfb.c -- Virtual frame buffer device3 *4 * Copyright (C) 2002 James Simmons5 *6 * Copyright (C) 1997 Geert Uytterhoeven7 *8 * This file is subject to the terms and conditions of the GNU General Public9 * License. See the file COPYING in the main directory of this archive for10 * more details.11 */12 13#include <linux/module.h>14#include <linux/kernel.h>15#include <linux/errno.h>16#include <linux/string.h>17#include <linux/mm.h>18#include <linux/vmalloc.h>19#include <linux/delay.h>20#include <linux/interrupt.h>21#include <linux/platform_device.h>22 23#include <linux/fb.h>24#include <linux/init.h>25 26 /*27 * RAM we reserve for the frame buffer. This defines the maximum screen28 * size29 *30 * The default can be overridden if the driver is compiled as a module31 */32 33#define VIDEOMEMSIZE (1*1024*1024) /* 1 MB */34 35static void *videomemory;36static u_long videomemorysize = VIDEOMEMSIZE;37module_param(videomemorysize, ulong, 0);38MODULE_PARM_DESC(videomemorysize, "RAM available to frame buffer (in bytes)");39 40static char *mode_option = NULL;41module_param(mode_option, charp, 0);42MODULE_PARM_DESC(mode_option, "Preferred video mode (e.g. 640x480-8@60)");43 44static const struct fb_videomode vfb_default = {45 .xres = 640,46 .yres = 480,47 .pixclock = 20000,48 .left_margin = 64,49 .right_margin = 64,50 .upper_margin = 32,51 .lower_margin = 32,52 .hsync_len = 64,53 .vsync_len = 2,54 .vmode = FB_VMODE_NONINTERLACED,55};56 57static struct fb_fix_screeninfo vfb_fix = {58 .id = "Virtual FB",59 .type = FB_TYPE_PACKED_PIXELS,60 .visual = FB_VISUAL_PSEUDOCOLOR,61 .xpanstep = 1,62 .ypanstep = 1,63 .ywrapstep = 1,64 .accel = FB_ACCEL_NONE,65};66 67static bool vfb_enable __initdata = 0; /* disabled by default */68module_param(vfb_enable, bool, 0);69MODULE_PARM_DESC(vfb_enable, "Enable Virtual FB driver");70 71static int vfb_check_var(struct fb_var_screeninfo *var,72 struct fb_info *info);73static int vfb_set_par(struct fb_info *info);74static int vfb_setcolreg(u_int regno, u_int red, u_int green, u_int blue,75 u_int transp, struct fb_info *info);76static int vfb_pan_display(struct fb_var_screeninfo *var,77 struct fb_info *info);78static int vfb_mmap(struct fb_info *info,79 struct vm_area_struct *vma);80 81static const struct fb_ops vfb_ops = {82 .owner = THIS_MODULE,83 __FB_DEFAULT_SYSMEM_OPS_RDWR,84 .fb_check_var = vfb_check_var,85 .fb_set_par = vfb_set_par,86 .fb_setcolreg = vfb_setcolreg,87 .fb_pan_display = vfb_pan_display,88 __FB_DEFAULT_SYSMEM_OPS_DRAW,89 .fb_mmap = vfb_mmap,90};91 92 /*93 * Internal routines94 */95 96static u_long get_line_length(int xres_virtual, int bpp)97{98 u_long length;99 100 length = xres_virtual * bpp;101 length = (length + 31) & ~31;102 length >>= 3;103 return (length);104}105 106 /*107 * Setting the video mode has been split into two parts.108 * First part, xxxfb_check_var, must not write anything109 * to hardware, it should only verify and adjust var.110 * This means it doesn't alter par but it does use hardware111 * data from it to check this var.112 */113 114static int vfb_check_var(struct fb_var_screeninfo *var,115 struct fb_info *info)116{117 u_long line_length;118 119 /*120 * FB_VMODE_CONUPDATE and FB_VMODE_SMOOTH_XPAN are equal!121 * as FB_VMODE_SMOOTH_XPAN is only used internally122 */123 124 if (var->vmode & FB_VMODE_CONUPDATE) {125 var->vmode |= FB_VMODE_YWRAP;126 var->xoffset = info->var.xoffset;127 var->yoffset = info->var.yoffset;128 }129 130 /*131 * Some very basic checks132 */133 if (!var->xres)134 var->xres = 1;135 if (!var->yres)136 var->yres = 1;137 if (var->xres > var->xres_virtual)138 var->xres_virtual = var->xres;139 if (var->yres > var->yres_virtual)140 var->yres_virtual = var->yres;141 if (var->bits_per_pixel <= 1)142 var->bits_per_pixel = 1;143 else if (var->bits_per_pixel <= 8)144 var->bits_per_pixel = 8;145 else if (var->bits_per_pixel <= 16)146 var->bits_per_pixel = 16;147 else if (var->bits_per_pixel <= 24)148 var->bits_per_pixel = 24;149 else if (var->bits_per_pixel <= 32)150 var->bits_per_pixel = 32;151 else152 return -EINVAL;153 154 if (var->xres_virtual < var->xoffset + var->xres)155 var->xres_virtual = var->xoffset + var->xres;156 if (var->yres_virtual < var->yoffset + var->yres)157 var->yres_virtual = var->yoffset + var->yres;158 159 /*160 * Memory limit161 */162 line_length =163 get_line_length(var->xres_virtual, var->bits_per_pixel);164 if (line_length * var->yres_virtual > videomemorysize)165 return -ENOMEM;166 167 /*168 * Now that we checked it we alter var. The reason being is that the video169 * mode passed in might not work but slight changes to it might make it170 * work. This way we let the user know what is acceptable.171 */172 switch (var->bits_per_pixel) {173 case 1:174 case 8:175 var->red.offset = 0;176 var->red.length = 8;177 var->green.offset = 0;178 var->green.length = 8;179 var->blue.offset = 0;180 var->blue.length = 8;181 var->transp.offset = 0;182 var->transp.length = 0;183 break;184 case 16: /* RGBA 5551 */185 if (var->transp.length) {186 var->red.offset = 0;187 var->red.length = 5;188 var->green.offset = 5;189 var->green.length = 5;190 var->blue.offset = 10;191 var->blue.length = 5;192 var->transp.offset = 15;193 var->transp.length = 1;194 } else { /* RGB 565 */195 var->red.offset = 0;196 var->red.length = 5;197 var->green.offset = 5;198 var->green.length = 6;199 var->blue.offset = 11;200 var->blue.length = 5;201 var->transp.offset = 0;202 var->transp.length = 0;203 }204 break;205 case 24: /* RGB 888 */206 var->red.offset = 0;207 var->red.length = 8;208 var->green.offset = 8;209 var->green.length = 8;210 var->blue.offset = 16;211 var->blue.length = 8;212 var->transp.offset = 0;213 var->transp.length = 0;214 break;215 case 32: /* RGBA 8888 */216 var->red.offset = 0;217 var->red.length = 8;218 var->green.offset = 8;219 var->green.length = 8;220 var->blue.offset = 16;221 var->blue.length = 8;222 var->transp.offset = 24;223 var->transp.length = 8;224 break;225 }226 var->red.msb_right = 0;227 var->green.msb_right = 0;228 var->blue.msb_right = 0;229 var->transp.msb_right = 0;230 231 return 0;232}233 234/* This routine actually sets the video mode. It's in here where we235 * the hardware state info->par and fix which can be affected by the236 * change in par. For this driver it doesn't do much.237 */238static int vfb_set_par(struct fb_info *info)239{240 switch (info->var.bits_per_pixel) {241 case 1:242 info->fix.visual = FB_VISUAL_MONO01;243 break;244 case 8:245 info->fix.visual = FB_VISUAL_PSEUDOCOLOR;246 break;247 case 16:248 case 24:249 case 32:250 info->fix.visual = FB_VISUAL_TRUECOLOR;251 break;252 }253 254 info->fix.line_length = get_line_length(info->var.xres_virtual,255 info->var.bits_per_pixel);256 257 return 0;258}259 260 /*261 * Set a single color register. The values supplied are already262 * rounded down to the hardware's capabilities (according to the263 * entries in the var structure). Return != 0 for invalid regno.264 */265 266static int vfb_setcolreg(u_int regno, u_int red, u_int green, u_int blue,267 u_int transp, struct fb_info *info)268{269 if (regno >= 256) /* no. of hw registers */270 return 1;271 /*272 * Program hardware... do anything you want with transp273 */274 275 /* grayscale works only partially under directcolor */276 if (info->var.grayscale) {277 /* grayscale = 0.30*R + 0.59*G + 0.11*B */278 red = green = blue =279 (red * 77 + green * 151 + blue * 28) >> 8;280 }281 282 /* Directcolor:283 * var->{color}.offset contains start of bitfield284 * var->{color}.length contains length of bitfield285 * {hardwarespecific} contains width of RAMDAC286 * cmap[X] is programmed to (X << red.offset) | (X << green.offset) | (X << blue.offset)287 * RAMDAC[X] is programmed to (red, green, blue)288 *289 * Pseudocolor:290 * var->{color}.offset is 0 unless the palette index takes less than291 * bits_per_pixel bits and is stored in the upper292 * bits of the pixel value293 * var->{color}.length is set so that 1 << length is the number of available294 * palette entries295 * cmap is not used296 * RAMDAC[X] is programmed to (red, green, blue)297 *298 * Truecolor:299 * does not use DAC. Usually 3 are present.300 * var->{color}.offset contains start of bitfield301 * var->{color}.length contains length of bitfield302 * cmap is programmed to (red << red.offset) | (green << green.offset) |303 * (blue << blue.offset) | (transp << transp.offset)304 * RAMDAC does not exist305 */306#define CNVT_TOHW(val,width) ((((val)<<(width))+0x7FFF-(val))>>16)307 switch (info->fix.visual) {308 case FB_VISUAL_TRUECOLOR:309 case FB_VISUAL_PSEUDOCOLOR:310 red = CNVT_TOHW(red, info->var.red.length);311 green = CNVT_TOHW(green, info->var.green.length);312 blue = CNVT_TOHW(blue, info->var.blue.length);313 transp = CNVT_TOHW(transp, info->var.transp.length);314 break;315 case FB_VISUAL_DIRECTCOLOR:316 red = CNVT_TOHW(red, 8); /* expect 8 bit DAC */317 green = CNVT_TOHW(green, 8);318 blue = CNVT_TOHW(blue, 8);319 /* hey, there is bug in transp handling... */320 transp = CNVT_TOHW(transp, 8);321 break;322 }323#undef CNVT_TOHW324 /* Truecolor has hardware independent palette */325 if (info->fix.visual == FB_VISUAL_TRUECOLOR) {326 u32 v;327 328 if (regno >= 16)329 return 1;330 331 v = (red << info->var.red.offset) |332 (green << info->var.green.offset) |333 (blue << info->var.blue.offset) |334 (transp << info->var.transp.offset);335 switch (info->var.bits_per_pixel) {336 case 8:337 break;338 case 16:339 ((u32 *) (info->pseudo_palette))[regno] = v;340 break;341 case 24:342 case 32:343 ((u32 *) (info->pseudo_palette))[regno] = v;344 break;345 }346 return 0;347 }348 return 0;349}350 351 /*352 * Pan or Wrap the Display353 *354 * This call looks only at xoffset, yoffset and the FB_VMODE_YWRAP flag355 */356 357static int vfb_pan_display(struct fb_var_screeninfo *var,358 struct fb_info *info)359{360 if (var->vmode & FB_VMODE_YWRAP) {361 if (var->yoffset >= info->var.yres_virtual ||362 var->xoffset)363 return -EINVAL;364 } else {365 if (var->xoffset + info->var.xres > info->var.xres_virtual ||366 var->yoffset + info->var.yres > info->var.yres_virtual)367 return -EINVAL;368 }369 info->var.xoffset = var->xoffset;370 info->var.yoffset = var->yoffset;371 if (var->vmode & FB_VMODE_YWRAP)372 info->var.vmode |= FB_VMODE_YWRAP;373 else374 info->var.vmode &= ~FB_VMODE_YWRAP;375 return 0;376}377 378 /*379 * Most drivers don't need their own mmap function380 */381 382static int vfb_mmap(struct fb_info *info,383 struct vm_area_struct *vma)384{385 vma->vm_page_prot = pgprot_decrypted(vma->vm_page_prot);386 387 return remap_vmalloc_range(vma, (void *)info->fix.smem_start, vma->vm_pgoff);388}389 390#ifndef MODULE391/*392 * The virtual framebuffer driver is only enabled if explicitly393 * requested by passing 'video=vfb:' (or any actual options).394 */395static int __init vfb_setup(char *options)396{397 char *this_opt;398 399 vfb_enable = 0;400 401 if (!options)402 return 1;403 404 vfb_enable = 1;405 406 if (!*options)407 return 1;408 409 while ((this_opt = strsep(&options, ",")) != NULL) {410 if (!*this_opt)411 continue;412 /* Test disable for backwards compatibility */413 if (!strcmp(this_opt, "disable"))414 vfb_enable = 0;415 else416 mode_option = this_opt;417 }418 return 1;419}420#endif /* MODULE */421 422 /*423 * Initialisation424 */425 426static int vfb_probe(struct platform_device *dev)427{428 struct fb_info *info;429 unsigned int size = PAGE_ALIGN(videomemorysize);430 int retval = -ENOMEM;431 432 /*433 * For real video cards we use ioremap.434 */435 if (!(videomemory = vmalloc_32_user(size)))436 return retval;437 438 info = framebuffer_alloc(sizeof(u32) * 256, &dev->dev);439 if (!info)440 goto err;441 442 info->flags |= FBINFO_VIRTFB;443 info->screen_buffer = videomemory;444 info->fbops = &vfb_ops;445 446 if (!fb_find_mode(&info->var, info, mode_option,447 NULL, 0, &vfb_default, 8)){448 fb_err(info, "Unable to find usable video mode.\n");449 retval = -EINVAL;450 goto err1;451 }452 453 vfb_fix.smem_start = (unsigned long) videomemory;454 vfb_fix.smem_len = videomemorysize;455 info->fix = vfb_fix;456 info->pseudo_palette = info->par;457 info->par = NULL;458 459 retval = fb_alloc_cmap(&info->cmap, 256, 0);460 if (retval < 0)461 goto err1;462 463 retval = register_framebuffer(info);464 if (retval < 0)465 goto err2;466 platform_set_drvdata(dev, info);467 468 vfb_set_par(info);469 470 fb_info(info, "Virtual frame buffer device, using %ldK of video memory\n",471 videomemorysize >> 10);472 return 0;473err2:474 fb_dealloc_cmap(&info->cmap);475err1:476 framebuffer_release(info);477err:478 vfree(videomemory);479 return retval;480}481 482static void vfb_remove(struct platform_device *dev)483{484 struct fb_info *info = platform_get_drvdata(dev);485 486 if (info) {487 unregister_framebuffer(info);488 vfree(videomemory);489 fb_dealloc_cmap(&info->cmap);490 framebuffer_release(info);491 }492}493 494static struct platform_driver vfb_driver = {495 .probe = vfb_probe,496 .remove = vfb_remove,497 .driver = {498 .name = "vfb",499 },500};501 502static struct platform_device *vfb_device;503 504static int __init vfb_init(void)505{506 int ret = 0;507 508#ifndef MODULE509 char *option = NULL;510 511 if (fb_get_options("vfb", &option))512 return -ENODEV;513 vfb_setup(option);514#endif515 516 if (!vfb_enable)517 return -ENXIO;518 519 ret = platform_driver_register(&vfb_driver);520 521 if (!ret) {522 vfb_device = platform_device_alloc("vfb", 0);523 524 if (vfb_device)525 ret = platform_device_add(vfb_device);526 else527 ret = -ENOMEM;528 529 if (ret) {530 platform_device_put(vfb_device);531 platform_driver_unregister(&vfb_driver);532 }533 }534 535 return ret;536}537 538module_init(vfb_init);539 540#ifdef MODULE541static void __exit vfb_exit(void)542{543 platform_device_unregister(vfb_device);544 platform_driver_unregister(&vfb_driver);545}546 547module_exit(vfb_exit);548 549MODULE_DESCRIPTION("Virtual Frame Buffer driver");550MODULE_LICENSE("GPL");551#endif /* MODULE */552