| 1 | |
| 2 | * linux/drivers/video/vfb.c -- Virtual frame buffer device
|
| 3 | *
|
| 4 | * Copyright (C) 2002 James Simmons
|
| 5 | *
|
| 6 | * Copyright (C) 1997 Geert Uytterhoeven
|
| 7 | *
|
| 8 | * This file is subject to the terms and conditions of the GNU General Public
|
| 9 | * License. See the file COPYING in the main directory of this archive for
|
| 10 | * 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 screen
|
| 28 | * size
|
| 29 | *
|
| 30 | * The default can be overridden if the driver is compiled as a module
|
| 31 |
|
| 32 |
|
| 33 | #define VIDEOMEMSIZE (1*1024*1024)
|
| 34 |
|
| 35 | static void *videomemory;
|
| 36 | static u_long videomemorysize = VIDEOMEMSIZE;
|
| 37 | module_param(videomemorysize, ulong, 0);
|
| 38 | MODULE_PARM_DESC(videomemorysize, "RAM available to frame buffer (in bytes)");
|
| 39 |
|
| 40 | static char *mode_option = NULL;
|
| 41 | module_param(mode_option, charp, 0);
|
| 42 | MODULE_PARM_DESC(mode_option, "Preferred video mode (e.g. 640x480-8@60)");
|
| 43 |
|
| 44 | static 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 |
|
| 57 | static 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 |
|
| 67 | static bool vfb_enable __initdata = 0;
|
| 68 | module_param(vfb_enable, bool, 0);
|
| 69 | MODULE_PARM_DESC(vfb_enable, "Enable Virtual FB driver");
|
| 70 |
|
| 71 | static int vfb_check_var(struct fb_var_screeninfo *var,
|
| 72 | struct fb_info *info);
|
| 73 | static int vfb_set_par(struct fb_info *info);
|
| 74 | static int vfb_setcolreg(u_int regno, u_int red, u_int green, u_int blue,
|
| 75 | u_int transp, struct fb_info *info);
|
| 76 | static int vfb_pan_display(struct fb_var_screeninfo *var,
|
| 77 | struct fb_info *info);
|
| 78 | static int vfb_mmap(struct fb_info *info,
|
| 79 | struct vm_area_struct *vma);
|
| 80 |
|
| 81 | static void vfb_destroy(struct fb_info *info)
|
| 82 | {
|
| 83 | vfree(info->screen_buffer);
|
| 84 | fb_dealloc_cmap(&info->cmap);
|
| 85 | framebuffer_release(info);
|
| 86 | }
|
| 87 |
|
| 88 | static const struct fb_ops vfb_ops = {
|
| 89 | .owner = THIS_MODULE,
|
| 90 | __FB_DEFAULT_SYSMEM_OPS_RDWR,
|
| 91 | .fb_check_var = vfb_check_var,
|
| 92 | .fb_set_par = vfb_set_par,
|
| 93 | .fb_setcolreg = vfb_setcolreg,
|
| 94 | .fb_pan_display = vfb_pan_display,
|
| 95 | __FB_DEFAULT_SYSMEM_OPS_DRAW,
|
| 96 | .fb_mmap = vfb_mmap,
|
| 97 | .fb_destroy = vfb_destroy,
|
| 98 | };
|
| 99 |
|
| 100 | |
| 101 | * Internal routines
|
| 102 |
|
| 103 |
|
| 104 | static u_long get_line_length(int xres_virtual, int bpp)
|
| 105 | {
|
| 106 | u_long length;
|
| 107 |
|
| 108 | length = xres_virtual * bpp;
|
| 109 | length = (length + 31) & ~31;
|
| 110 | length >>= 3;
|
| 111 | return (length);
|
| 112 | }
|
| 113 |
|
| 114 | |
| 115 | * Setting the video mode has been split into two parts.
|
| 116 | * First part, xxxfb_check_var, must not write anything
|
| 117 | * to hardware, it should only verify and adjust var.
|
| 118 | * This means it doesn't alter par but it does use hardware
|
| 119 | * data from it to check this var.
|
| 120 |
|
| 121 |
|
| 122 | static int vfb_check_var(struct fb_var_screeninfo *var,
|
| 123 | struct fb_info *info)
|
| 124 | {
|
| 125 | u_long line_length;
|
| 126 |
|
| 127 | |
| 128 | * FB_VMODE_CONUPDATE and FB_VMODE_SMOOTH_XPAN are equal!
|
| 129 | * as FB_VMODE_SMOOTH_XPAN is only used internally
|
| 130 |
|
| 131 |
|
| 132 | if (var->vmode & FB_VMODE_CONUPDATE) {
|
| 133 | var->vmode |= FB_VMODE_YWRAP;
|
| 134 | var->xoffset = info->var.xoffset;
|
| 135 | var->yoffset = info->var.yoffset;
|
| 136 | }
|
| 137 |
|
| 138 | |
| 139 | * Some very basic checks
|
| 140 |
|
| 141 | if (!var->xres)
|
| 142 | var->xres = 1;
|
| 143 | if (!var->yres)
|
| 144 | var->yres = 1;
|
| 145 | if (var->xres > var->xres_virtual)
|
| 146 | var->xres_virtual = var->xres;
|
| 147 | if (var->yres > var->yres_virtual)
|
| 148 | var->yres_virtual = var->yres;
|
| 149 | if (var->bits_per_pixel <= 1)
|
| 150 | var->bits_per_pixel = 1;
|
| 151 | else if (var->bits_per_pixel <= 8)
|
| 152 | var->bits_per_pixel = 8;
|
| 153 | else if (var->bits_per_pixel <= 16)
|
| 154 | var->bits_per_pixel = 16;
|
| 155 | else if (var->bits_per_pixel <= 24)
|
| 156 | var->bits_per_pixel = 24;
|
| 157 | else if (var->bits_per_pixel <= 32)
|
| 158 | var->bits_per_pixel = 32;
|
| 159 | else
|
| 160 | return -EINVAL;
|
| 161 |
|
| 162 | if (var->xres_virtual < var->xoffset + var->xres)
|
| 163 | var->xres_virtual = var->xoffset + var->xres;
|
| 164 | if (var->yres_virtual < var->yoffset + var->yres)
|
| 165 | var->yres_virtual = var->yoffset + var->yres;
|
| 166 |
|
| 167 | |
| 168 | * Memory limit
|
| 169 |
|
| 170 | line_length =
|
| 171 | get_line_length(var->xres_virtual, var->bits_per_pixel);
|
| 172 | if (line_length * var->yres_virtual > videomemorysize)
|
| 173 | return -ENOMEM;
|
| 174 |
|
| 175 | |
| 176 | * Now that we checked it we alter var. The reason being is that the video
|
| 177 | * mode passed in might not work but slight changes to it might make it
|
| 178 | * work. This way we let the user know what is acceptable.
|
| 179 |
|
| 180 | switch (var->bits_per_pixel) {
|
| 181 | case 1:
|
| 182 | case 8:
|
| 183 | var->red.offset = 0;
|
| 184 | var->red.length = 8;
|
| 185 | var->green.offset = 0;
|
| 186 | var->green.length = 8;
|
| 187 | var->blue.offset = 0;
|
| 188 | var->blue.length = 8;
|
| 189 | var->transp.offset = 0;
|
| 190 | var->transp.length = 0;
|
| 191 | break;
|
| 192 | case 16:
|
| 193 | if (var->transp.length) {
|
| 194 | var->red.offset = 0;
|
| 195 | var->red.length = 5;
|
| 196 | var->green.offset = 5;
|
| 197 | var->green.length = 5;
|
| 198 | var->blue.offset = 10;
|
| 199 | var->blue.length = 5;
|
| 200 | var->transp.offset = 15;
|
| 201 | var->transp.length = 1;
|
| 202 | } else {
|
| 203 | var->red.offset = 0;
|
| 204 | var->red.length = 5;
|
| 205 | var->green.offset = 5;
|
| 206 | var->green.length = 6;
|
| 207 | var->blue.offset = 11;
|
| 208 | var->blue.length = 5;
|
| 209 | var->transp.offset = 0;
|
| 210 | var->transp.length = 0;
|
| 211 | }
|
| 212 | break;
|
| 213 | case 24:
|
| 214 | var->red.offset = 0;
|
| 215 | var->red.length = 8;
|
| 216 | var->green.offset = 8;
|
| 217 | var->green.length = 8;
|
| 218 | var->blue.offset = 16;
|
| 219 | var->blue.length = 8;
|
| 220 | var->transp.offset = 0;
|
| 221 | var->transp.length = 0;
|
| 222 | break;
|
| 223 | case 32:
|
| 224 | var->red.offset = 0;
|
| 225 | var->red.length = 8;
|
| 226 | var->green.offset = 8;
|
| 227 | var->green.length = 8;
|
| 228 | var->blue.offset = 16;
|
| 229 | var->blue.length = 8;
|
| 230 | var->transp.offset = 24;
|
| 231 | var->transp.length = 8;
|
| 232 | break;
|
| 233 | }
|
| 234 | var->red.msb_right = 0;
|
| 235 | var->green.msb_right = 0;
|
| 236 | var->blue.msb_right = 0;
|
| 237 | var->transp.msb_right = 0;
|
| 238 |
|
| 239 | return 0;
|
| 240 | }
|
| 241 |
|
| 242 | |
| 243 | * the hardware state info->par and fix which can be affected by the
|
| 244 | * change in par. For this driver it doesn't do much.
|
| 245 |
|
| 246 | static int vfb_set_par(struct fb_info *info)
|
| 247 | {
|
| 248 | switch (info->var.bits_per_pixel) {
|
| 249 | case 1:
|
| 250 | info->fix.visual = FB_VISUAL_MONO01;
|
| 251 | break;
|
| 252 | case 8:
|
| 253 | info->fix.visual = FB_VISUAL_PSEUDOCOLOR;
|
| 254 | break;
|
| 255 | case 16:
|
| 256 | case 24:
|
| 257 | case 32:
|
| 258 | info->fix.visual = FB_VISUAL_TRUECOLOR;
|
| 259 | break;
|
| 260 | }
|
| 261 |
|
| 262 | info->fix.line_length = get_line_length(info->var.xres_virtual,
|
| 263 | info->var.bits_per_pixel);
|
| 264 |
|
| 265 | return 0;
|
| 266 | }
|
| 267 |
|
| 268 | |
| 269 | * Set a single color register. The values supplied are already
|
| 270 | * rounded down to the hardware's capabilities (according to the
|
| 271 | * entries in the var structure). Return != 0 for invalid regno.
|
| 272 |
|
| 273 |
|
| 274 | static int vfb_setcolreg(u_int regno, u_int red, u_int green, u_int blue,
|
| 275 | u_int transp, struct fb_info *info)
|
| 276 | {
|
| 277 | if (regno >= 256)
|
| 278 | return 1;
|
| 279 | |
| 280 | * Program hardware... do anything you want with transp
|
| 281 |
|
| 282 |
|
| 283 |
|
| 284 | if (info->var.grayscale) {
|
| 285 |
|
| 286 | red = green = blue =
|
| 287 | (red * 77 + green * 151 + blue * 28) >> 8;
|
| 288 | }
|
| 289 |
|
| 290 | |
| 291 | * var->{color}.offset contains start of bitfield
|
| 292 | * var->{color}.length contains length of bitfield
|
| 293 | * {hardwarespecific} contains width of RAMDAC
|
| 294 | * cmap[X] is programmed to (X << red.offset) | (X << green.offset) | (X << blue.offset)
|
| 295 | * RAMDAC[X] is programmed to (red, green, blue)
|
| 296 | *
|
| 297 | * Pseudocolor:
|
| 298 | * var->{color}.offset is 0 unless the palette index takes less than
|
| 299 | * bits_per_pixel bits and is stored in the upper
|
| 300 | * bits of the pixel value
|
| 301 | * var->{color}.length is set so that 1 << length is the number of available
|
| 302 | * palette entries
|
| 303 | * cmap is not used
|
| 304 | * RAMDAC[X] is programmed to (red, green, blue)
|
| 305 | *
|
| 306 | * Truecolor:
|
| 307 | * does not use DAC. Usually 3 are present.
|
| 308 | * var->{color}.offset contains start of bitfield
|
| 309 | * var->{color}.length contains length of bitfield
|
| 310 | * cmap is programmed to (red << red.offset) | (green << green.offset) |
|
| 311 | * (blue << blue.offset) | (transp << transp.offset)
|
| 312 | * RAMDAC does not exist
|
| 313 |
|
| 314 | #define CNVT_TOHW(val,width) ((((val)<<(width))+0x7FFF-(val))>>16)
|
| 315 | switch (info->fix.visual) {
|
| 316 | case FB_VISUAL_TRUECOLOR:
|
| 317 | case FB_VISUAL_PSEUDOCOLOR:
|
| 318 | red = CNVT_TOHW(red, info->var.red.length);
|
| 319 | green = CNVT_TOHW(green, info->var.green.length);
|
| 320 | blue = CNVT_TOHW(blue, info->var.blue.length);
|
| 321 | transp = CNVT_TOHW(transp, info->var.transp.length);
|
| 322 | break;
|
| 323 | case FB_VISUAL_DIRECTCOLOR:
|
| 324 | red = CNVT_TOHW(red, 8);
|
| 325 | green = CNVT_TOHW(green, 8);
|
| 326 | blue = CNVT_TOHW(blue, 8);
|
| 327 |
|
| 328 | transp = CNVT_TOHW(transp, 8);
|
| 329 | break;
|
| 330 | }
|
| 331 | #undef CNVT_TOHW
|
| 332 |
|
| 333 | if (info->fix.visual == FB_VISUAL_TRUECOLOR) {
|
| 334 | u32 v;
|
| 335 |
|
| 336 | if (regno >= 16)
|
| 337 | return 1;
|
| 338 |
|
| 339 | v = (red << info->var.red.offset) |
|
| 340 | (green << info->var.green.offset) |
|
| 341 | (blue << info->var.blue.offset) |
|
| 342 | (transp << info->var.transp.offset);
|
| 343 | switch (info->var.bits_per_pixel) {
|
| 344 | case 8:
|
| 345 | break;
|
| 346 | case 16:
|
| 347 | ((u32 *) (info->pseudo_palette))[regno] = v;
|
| 348 | break;
|
| 349 | case 24:
|
| 350 | case 32:
|
| 351 | ((u32 *) (info->pseudo_palette))[regno] = v;
|
| 352 | break;
|
| 353 | }
|
| 354 | return 0;
|
| 355 | }
|
| 356 | return 0;
|
| 357 | }
|
| 358 |
|
| 359 | |
| 360 | * Pan or Wrap the Display
|
| 361 | *
|
| 362 | * This call looks only at xoffset, yoffset and the FB_VMODE_YWRAP flag
|
| 363 |
|
| 364 |
|
| 365 | static int vfb_pan_display(struct fb_var_screeninfo *var,
|
| 366 | struct fb_info *info)
|
| 367 | {
|
| 368 | if (var->vmode & FB_VMODE_YWRAP) {
|
| 369 | if (var->yoffset >= info->var.yres_virtual ||
|
| 370 | var->xoffset)
|
| 371 | return -EINVAL;
|
| 372 | } else {
|
| 373 | if (var->xoffset + info->var.xres > info->var.xres_virtual ||
|
| 374 | var->yoffset + info->var.yres > info->var.yres_virtual)
|
| 375 | return -EINVAL;
|
| 376 | }
|
| 377 | info->var.xoffset = var->xoffset;
|
| 378 | info->var.yoffset = var->yoffset;
|
| 379 | if (var->vmode & FB_VMODE_YWRAP)
|
| 380 | info->var.vmode |= FB_VMODE_YWRAP;
|
| 381 | else
|
| 382 | info->var.vmode &= ~FB_VMODE_YWRAP;
|
| 383 | return 0;
|
| 384 | }
|
| 385 |
|
| 386 | |
| 387 | * Most drivers don't need their own mmap function
|
| 388 |
|
| 389 |
|
| 390 | static int vfb_mmap(struct fb_info *info,
|
| 391 | struct vm_area_struct *vma)
|
| 392 | {
|
| 393 | vma->vm_page_prot = pgprot_decrypted(vma->vm_page_prot);
|
| 394 |
|
| 395 | return remap_vmalloc_range(vma, (void *)info->fix.smem_start, vma->vm_pgoff);
|
| 396 | }
|
| 397 |
|
| 398 | #ifndef MODULE
|
| 399 | |
| 400 | * The virtual framebuffer driver is only enabled if explicitly
|
| 401 | * requested by passing 'video=vfb:' (or any actual options).
|
| 402 |
|
| 403 | static int __init vfb_setup(char *options)
|
| 404 | {
|
| 405 | char *this_opt;
|
| 406 |
|
| 407 | vfb_enable = 0;
|
| 408 |
|
| 409 | if (!options)
|
| 410 | return 1;
|
| 411 |
|
| 412 | vfb_enable = 1;
|
| 413 |
|
| 414 | if (!*options)
|
| 415 | return 1;
|
| 416 |
|
| 417 | while ((this_opt = strsep(&options, ",")) != NULL) {
|
| 418 | if (!*this_opt)
|
| 419 | continue;
|
| 420 |
|
| 421 | if (!strcmp(this_opt, "disable"))
|
| 422 | vfb_enable = 0;
|
| 423 | else
|
| 424 | mode_option = this_opt;
|
| 425 | }
|
| 426 | return 1;
|
| 427 | }
|
| 428 | #endif
|
| 429 |
|
| 430 | |
| 431 | * Initialisation
|
| 432 |
|
| 433 |
|
| 434 | static int vfb_probe(struct platform_device *dev)
|
| 435 | {
|
| 436 | struct fb_info *info;
|
| 437 | unsigned int size = PAGE_ALIGN(videomemorysize);
|
| 438 | int retval = -ENOMEM;
|
| 439 |
|
| 440 | |
| 441 | * For real video cards we use ioremap.
|
| 442 |
|
| 443 | if (!(videomemory = vmalloc_32_user(size)))
|
| 444 | return retval;
|
| 445 |
|
| 446 | info = framebuffer_alloc(sizeof(u32) * 256, &dev->dev);
|
| 447 | if (!info)
|
| 448 | goto err;
|
| 449 |
|
| 450 | info->flags |= FBINFO_VIRTFB;
|
| 451 | info->screen_buffer = videomemory;
|
| 452 | info->fbops = &vfb_ops;
|
| 453 |
|
| 454 | if (!fb_find_mode(&info->var, info, mode_option,
|
| 455 | NULL, 0, &vfb_default, 8)){
|
| 456 | fb_err(info, "Unable to find usable video mode.\n");
|
| 457 | retval = -EINVAL;
|
| 458 | goto err1;
|
| 459 | }
|
| 460 |
|
| 461 | vfb_fix.smem_start = (unsigned long) videomemory;
|
| 462 | vfb_fix.smem_len = videomemorysize;
|
| 463 | info->fix = vfb_fix;
|
| 464 | info->pseudo_palette = info->par;
|
| 465 | info->par = NULL;
|
| 466 |
|
| 467 | retval = fb_alloc_cmap(&info->cmap, 256, 0);
|
| 468 | if (retval < 0)
|
| 469 | goto err1;
|
| 470 |
|
| 471 | retval = register_framebuffer(info);
|
| 472 | if (retval < 0)
|
| 473 | goto err2;
|
| 474 | platform_set_drvdata(dev, info);
|
| 475 |
|
| 476 | vfb_set_par(info);
|
| 477 |
|
| 478 | fb_info(info, "Virtual frame buffer device, using %ldK of video memory\n",
|
| 479 | videomemorysize >> 10);
|
| 480 | return 0;
|
| 481 | err2:
|
| 482 | fb_dealloc_cmap(&info->cmap);
|
| 483 | err1:
|
| 484 | framebuffer_release(info);
|
| 485 | err:
|
| 486 | vfree(videomemory);
|
| 487 | return retval;
|
| 488 | }
|
| 489 |
|
| 490 | static void vfb_remove(struct platform_device *dev)
|
| 491 | {
|
| 492 | struct fb_info *info = platform_get_drvdata(dev);
|
| 493 |
|
| 494 | if (info) {
|
| 495 | unregister_framebuffer(info);
|
| 496 | }
|
| 497 | }
|
| 498 |
|
| 499 | static struct platform_driver vfb_driver = {
|
| 500 | .probe = vfb_probe,
|
| 501 | .remove = vfb_remove,
|
| 502 | .driver = {
|
| 503 | .name = "vfb",
|
| 504 | },
|
| 505 | };
|
| 506 |
|
| 507 | static struct platform_device *vfb_device;
|
| 508 |
|
| 509 | static int __init vfb_init(void)
|
| 510 | {
|
| 511 | int ret = 0;
|
| 512 |
|
| 513 | #ifndef MODULE
|
| 514 | char *option = NULL;
|
| 515 |
|
| 516 | if (fb_get_options("vfb", &option))
|
| 517 | return -ENODEV;
|
| 518 | vfb_setup(option);
|
| 519 | #endif
|
| 520 |
|
| 521 | if (!vfb_enable)
|
| 522 | return -ENXIO;
|
| 523 |
|
| 524 | ret = platform_driver_register(&vfb_driver);
|
| 525 |
|
| 526 | if (!ret) {
|
| 527 | vfb_device = platform_device_alloc("vfb", 0);
|
| 528 |
|
| 529 | if (vfb_device)
|
| 530 | ret = platform_device_add(vfb_device);
|
| 531 | else
|
| 532 | ret = -ENOMEM;
|
| 533 |
|
| 534 | if (ret) {
|
| 535 | platform_device_put(vfb_device);
|
| 536 | platform_driver_unregister(&vfb_driver);
|
| 537 | }
|
| 538 | }
|
| 539 |
|
| 540 | return ret;
|
| 541 | }
|
| 542 |
|
| 543 | module_init(vfb_init);
|
| 544 |
|
| 545 | #ifdef MODULE
|
| 546 | static void __exit vfb_exit(void)
|
| 547 | {
|
| 548 | platform_device_unregister(vfb_device);
|
| 549 | platform_driver_unregister(&vfb_driver);
|
| 550 | }
|
| 551 |
|
| 552 | module_exit(vfb_exit);
|
| 553 |
|
| 554 | MODULE_DESCRIPTION("Virtual Frame Buffer driver");
|
| 555 | MODULE_LICENSE("GPL");
|
| 556 | #endif
|