linux/drivers/video/fbdev/vfb.c
<<
>>
Prefs
   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)   /* 1 MB */
  34
  35static void *videomemory;
  36static u_long videomemorysize = VIDEOMEMSIZE;
  37module_param(videomemorysize, ulong, 0);
  38
  39/**********************************************************************
  40 *
  41 * Memory management
  42 *
  43 **********************************************************************/
  44static void *rvmalloc(unsigned long size)
  45{
  46        void *mem;
  47        unsigned long adr;
  48
  49        size = PAGE_ALIGN(size);
  50        mem = vmalloc_32(size);
  51        if (!mem)
  52                return NULL;
  53
  54        /*
  55         * VFB must clear memory to prevent kernel info
  56         * leakage into userspace
  57         * VGA-based drivers MUST NOT clear memory if
  58         * they want to be able to take over vgacon
  59         */
  60
  61        memset(mem, 0, size);
  62        adr = (unsigned long) mem;
  63        while (size > 0) {
  64                SetPageReserved(vmalloc_to_page((void *)adr));
  65                adr += PAGE_SIZE;
  66                size -= PAGE_SIZE;
  67        }
  68
  69        return mem;
  70}
  71
  72static void rvfree(void *mem, unsigned long size)
  73{
  74        unsigned long adr;
  75
  76        if (!mem)
  77                return;
  78
  79        adr = (unsigned long) mem;
  80        while ((long) size > 0) {
  81                ClearPageReserved(vmalloc_to_page((void *)adr));
  82                adr += PAGE_SIZE;
  83                size -= PAGE_SIZE;
  84        }
  85        vfree(mem);
  86}
  87
  88static struct fb_var_screeninfo vfb_default = {
  89        .xres =         640,
  90        .yres =         480,
  91        .xres_virtual = 640,
  92        .yres_virtual = 480,
  93        .bits_per_pixel = 8,
  94        .red =          { 0, 8, 0 },
  95        .green =        { 0, 8, 0 },
  96        .blue =         { 0, 8, 0 },
  97        .activate =     FB_ACTIVATE_TEST,
  98        .height =       -1,
  99        .width =        -1,
 100        .pixclock =     20000,
 101        .left_margin =  64,
 102        .right_margin = 64,
 103        .upper_margin = 32,
 104        .lower_margin = 32,
 105        .hsync_len =    64,
 106        .vsync_len =    2,
 107        .vmode =        FB_VMODE_NONINTERLACED,
 108};
 109
 110static struct fb_fix_screeninfo vfb_fix = {
 111        .id =           "Virtual FB",
 112        .type =         FB_TYPE_PACKED_PIXELS,
 113        .visual =       FB_VISUAL_PSEUDOCOLOR,
 114        .xpanstep =     1,
 115        .ypanstep =     1,
 116        .ywrapstep =    1,
 117        .accel =        FB_ACCEL_NONE,
 118};
 119
 120static bool vfb_enable __initdata = 0;  /* disabled by default */
 121module_param(vfb_enable, bool, 0);
 122
 123static int vfb_check_var(struct fb_var_screeninfo *var,
 124                         struct fb_info *info);
 125static int vfb_set_par(struct fb_info *info);
 126static int vfb_setcolreg(u_int regno, u_int red, u_int green, u_int blue,
 127                         u_int transp, struct fb_info *info);
 128static int vfb_pan_display(struct fb_var_screeninfo *var,
 129                           struct fb_info *info);
 130static int vfb_mmap(struct fb_info *info,
 131                    struct vm_area_struct *vma);
 132
 133static struct fb_ops vfb_ops = {
 134        .fb_read        = fb_sys_read,
 135        .fb_write       = fb_sys_write,
 136        .fb_check_var   = vfb_check_var,
 137        .fb_set_par     = vfb_set_par,
 138        .fb_setcolreg   = vfb_setcolreg,
 139        .fb_pan_display = vfb_pan_display,
 140        .fb_fillrect    = sys_fillrect,
 141        .fb_copyarea    = sys_copyarea,
 142        .fb_imageblit   = sys_imageblit,
 143        .fb_mmap        = vfb_mmap,
 144};
 145
 146    /*
 147     *  Internal routines
 148     */
 149
 150static u_long get_line_length(int xres_virtual, int bpp)
 151{
 152        u_long length;
 153
 154        length = xres_virtual * bpp;
 155        length = (length + 31) & ~31;
 156        length >>= 3;
 157        return (length);
 158}
 159
 160    /*
 161     *  Setting the video mode has been split into two parts.
 162     *  First part, xxxfb_check_var, must not write anything
 163     *  to hardware, it should only verify and adjust var.
 164     *  This means it doesn't alter par but it does use hardware
 165     *  data from it to check this var. 
 166     */
 167
 168static int vfb_check_var(struct fb_var_screeninfo *var,
 169                         struct fb_info *info)
 170{
 171        u_long line_length;
 172
 173        /*
 174         *  FB_VMODE_CONUPDATE and FB_VMODE_SMOOTH_XPAN are equal!
 175         *  as FB_VMODE_SMOOTH_XPAN is only used internally
 176         */
 177
 178        if (var->vmode & FB_VMODE_CONUPDATE) {
 179                var->vmode |= FB_VMODE_YWRAP;
 180                var->xoffset = info->var.xoffset;
 181                var->yoffset = info->var.yoffset;
 182        }
 183
 184        /*
 185         *  Some very basic checks
 186         */
 187        if (!var->xres)
 188                var->xres = 1;
 189        if (!var->yres)
 190                var->yres = 1;
 191        if (var->xres > var->xres_virtual)
 192                var->xres_virtual = var->xres;
 193        if (var->yres > var->yres_virtual)
 194                var->yres_virtual = var->yres;
 195        if (var->bits_per_pixel <= 1)
 196                var->bits_per_pixel = 1;
 197        else if (var->bits_per_pixel <= 8)
 198                var->bits_per_pixel = 8;
 199        else if (var->bits_per_pixel <= 16)
 200                var->bits_per_pixel = 16;
 201        else if (var->bits_per_pixel <= 24)
 202                var->bits_per_pixel = 24;
 203        else if (var->bits_per_pixel <= 32)
 204                var->bits_per_pixel = 32;
 205        else
 206                return -EINVAL;
 207
 208        if (var->xres_virtual < var->xoffset + var->xres)
 209                var->xres_virtual = var->xoffset + var->xres;
 210        if (var->yres_virtual < var->yoffset + var->yres)
 211                var->yres_virtual = var->yoffset + var->yres;
 212
 213        /*
 214         *  Memory limit
 215         */
 216        line_length =
 217            get_line_length(var->xres_virtual, var->bits_per_pixel);
 218        if (line_length * var->yres_virtual > videomemorysize)
 219                return -ENOMEM;
 220
 221        /*
 222         * Now that we checked it we alter var. The reason being is that the video
 223         * mode passed in might not work but slight changes to it might make it 
 224         * work. This way we let the user know what is acceptable.
 225         */
 226        switch (var->bits_per_pixel) {
 227        case 1:
 228        case 8:
 229                var->red.offset = 0;
 230                var->red.length = 8;
 231                var->green.offset = 0;
 232                var->green.length = 8;
 233                var->blue.offset = 0;
 234                var->blue.length = 8;
 235                var->transp.offset = 0;
 236                var->transp.length = 0;
 237                break;
 238        case 16:                /* RGBA 5551 */
 239                if (var->transp.length) {
 240                        var->red.offset = 0;
 241                        var->red.length = 5;
 242                        var->green.offset = 5;
 243                        var->green.length = 5;
 244                        var->blue.offset = 10;
 245                        var->blue.length = 5;
 246                        var->transp.offset = 15;
 247                        var->transp.length = 1;
 248                } else {        /* RGB 565 */
 249                        var->red.offset = 0;
 250                        var->red.length = 5;
 251                        var->green.offset = 5;
 252                        var->green.length = 6;
 253                        var->blue.offset = 11;
 254                        var->blue.length = 5;
 255                        var->transp.offset = 0;
 256                        var->transp.length = 0;
 257                }
 258                break;
 259        case 24:                /* RGB 888 */
 260                var->red.offset = 0;
 261                var->red.length = 8;
 262                var->green.offset = 8;
 263                var->green.length = 8;
 264                var->blue.offset = 16;
 265                var->blue.length = 8;
 266                var->transp.offset = 0;
 267                var->transp.length = 0;
 268                break;
 269        case 32:                /* RGBA 8888 */
 270                var->red.offset = 0;
 271                var->red.length = 8;
 272                var->green.offset = 8;
 273                var->green.length = 8;
 274                var->blue.offset = 16;
 275                var->blue.length = 8;
 276                var->transp.offset = 24;
 277                var->transp.length = 8;
 278                break;
 279        }
 280        var->red.msb_right = 0;
 281        var->green.msb_right = 0;
 282        var->blue.msb_right = 0;
 283        var->transp.msb_right = 0;
 284
 285        return 0;
 286}
 287
 288/* This routine actually sets the video mode. It's in here where we
 289 * the hardware state info->par and fix which can be affected by the 
 290 * change in par. For this driver it doesn't do much. 
 291 */
 292static int vfb_set_par(struct fb_info *info)
 293{
 294        info->fix.line_length = get_line_length(info->var.xres_virtual,
 295                                                info->var.bits_per_pixel);
 296        return 0;
 297}
 298
 299    /*
 300     *  Set a single color register. The values supplied are already
 301     *  rounded down to the hardware's capabilities (according to the
 302     *  entries in the var structure). Return != 0 for invalid regno.
 303     */
 304
 305static int vfb_setcolreg(u_int regno, u_int red, u_int green, u_int blue,
 306                         u_int transp, struct fb_info *info)
 307{
 308        if (regno >= 256)       /* no. of hw registers */
 309                return 1;
 310        /*
 311         * Program hardware... do anything you want with transp
 312         */
 313
 314        /* grayscale works only partially under directcolor */
 315        if (info->var.grayscale) {
 316                /* grayscale = 0.30*R + 0.59*G + 0.11*B */
 317                red = green = blue =
 318                    (red * 77 + green * 151 + blue * 28) >> 8;
 319        }
 320
 321        /* Directcolor:
 322         *   var->{color}.offset contains start of bitfield
 323         *   var->{color}.length contains length of bitfield
 324         *   {hardwarespecific} contains width of RAMDAC
 325         *   cmap[X] is programmed to (X << red.offset) | (X << green.offset) | (X << blue.offset)
 326         *   RAMDAC[X] is programmed to (red, green, blue)
 327         *
 328         * Pseudocolor:
 329         *    var->{color}.offset is 0 unless the palette index takes less than
 330         *                        bits_per_pixel bits and is stored in the upper
 331         *                        bits of the pixel value
 332         *    var->{color}.length is set so that 1 << length is the number of available
 333         *                        palette entries
 334         *    cmap is not used
 335         *    RAMDAC[X] is programmed to (red, green, blue)
 336         *
 337         * Truecolor:
 338         *    does not use DAC. Usually 3 are present.
 339         *    var->{color}.offset contains start of bitfield
 340         *    var->{color}.length contains length of bitfield
 341         *    cmap is programmed to (red << red.offset) | (green << green.offset) |
 342         *                      (blue << blue.offset) | (transp << transp.offset)
 343         *    RAMDAC does not exist
 344         */
 345#define CNVT_TOHW(val,width) ((((val)<<(width))+0x7FFF-(val))>>16)
 346        switch (info->fix.visual) {
 347        case FB_VISUAL_TRUECOLOR:
 348        case FB_VISUAL_PSEUDOCOLOR:
 349                red = CNVT_TOHW(red, info->var.red.length);
 350                green = CNVT_TOHW(green, info->var.green.length);
 351                blue = CNVT_TOHW(blue, info->var.blue.length);
 352                transp = CNVT_TOHW(transp, info->var.transp.length);
 353                break;
 354        case FB_VISUAL_DIRECTCOLOR:
 355                red = CNVT_TOHW(red, 8);        /* expect 8 bit DAC */
 356                green = CNVT_TOHW(green, 8);
 357                blue = CNVT_TOHW(blue, 8);
 358                /* hey, there is bug in transp handling... */
 359                transp = CNVT_TOHW(transp, 8);
 360                break;
 361        }
 362#undef CNVT_TOHW
 363        /* Truecolor has hardware independent palette */
 364        if (info->fix.visual == FB_VISUAL_TRUECOLOR) {
 365                u32 v;
 366
 367                if (regno >= 16)
 368                        return 1;
 369
 370                v = (red << info->var.red.offset) |
 371                    (green << info->var.green.offset) |
 372                    (blue << info->var.blue.offset) |
 373                    (transp << info->var.transp.offset);
 374                switch (info->var.bits_per_pixel) {
 375                case 8:
 376                        break;
 377                case 16:
 378                        ((u32 *) (info->pseudo_palette))[regno] = v;
 379                        break;
 380                case 24:
 381                case 32:
 382                        ((u32 *) (info->pseudo_palette))[regno] = v;
 383                        break;
 384                }
 385                return 0;
 386        }
 387        return 0;
 388}
 389
 390    /*
 391     *  Pan or Wrap the Display
 392     *
 393     *  This call looks only at xoffset, yoffset and the FB_VMODE_YWRAP flag
 394     */
 395
 396static int vfb_pan_display(struct fb_var_screeninfo *var,
 397                           struct fb_info *info)
 398{
 399        if (var->vmode & FB_VMODE_YWRAP) {
 400                if (var->yoffset >= info->var.yres_virtual ||
 401                    var->xoffset)
 402                        return -EINVAL;
 403        } else {
 404                if (var->xoffset + info->var.xres > info->var.xres_virtual ||
 405                    var->yoffset + info->var.yres > info->var.yres_virtual)
 406                        return -EINVAL;
 407        }
 408        info->var.xoffset = var->xoffset;
 409        info->var.yoffset = var->yoffset;
 410        if (var->vmode & FB_VMODE_YWRAP)
 411                info->var.vmode |= FB_VMODE_YWRAP;
 412        else
 413                info->var.vmode &= ~FB_VMODE_YWRAP;
 414        return 0;
 415}
 416
 417    /*
 418     *  Most drivers don't need their own mmap function 
 419     */
 420
 421static int vfb_mmap(struct fb_info *info,
 422                    struct vm_area_struct *vma)
 423{
 424        unsigned long start = vma->vm_start;
 425        unsigned long size = vma->vm_end - vma->vm_start;
 426        unsigned long offset = vma->vm_pgoff << PAGE_SHIFT;
 427        unsigned long page, pos;
 428
 429        if (vma->vm_pgoff > (~0UL >> PAGE_SHIFT))
 430                return -EINVAL;
 431        if (size > info->fix.smem_len)
 432                return -EINVAL;
 433        if (offset > info->fix.smem_len - size)
 434                return -EINVAL;
 435
 436        pos = (unsigned long)info->fix.smem_start + offset;
 437
 438        while (size > 0) {
 439                page = vmalloc_to_pfn((void *)pos);
 440                if (remap_pfn_range(vma, start, page, PAGE_SIZE, PAGE_SHARED)) {
 441                        return -EAGAIN;
 442                }
 443                start += PAGE_SIZE;
 444                pos += PAGE_SIZE;
 445                if (size > PAGE_SIZE)
 446                        size -= PAGE_SIZE;
 447                else
 448                        size = 0;
 449        }
 450
 451        return 0;
 452
 453}
 454
 455#ifndef MODULE
 456/*
 457 * The virtual framebuffer driver is only enabled if explicitly
 458 * requested by passing 'video=vfb:' (or any actual options).
 459 */
 460static int __init vfb_setup(char *options)
 461{
 462        char *this_opt;
 463
 464        vfb_enable = 0;
 465
 466        if (!options)
 467                return 1;
 468
 469        vfb_enable = 1;
 470
 471        if (!*options)
 472                return 1;
 473
 474        while ((this_opt = strsep(&options, ",")) != NULL) {
 475                if (!*this_opt)
 476                        continue;
 477                /* Test disable for backwards compatibility */
 478                if (!strcmp(this_opt, "disable"))
 479                        vfb_enable = 0;
 480        }
 481        return 1;
 482}
 483#endif  /*  MODULE  */
 484
 485    /*
 486     *  Initialisation
 487     */
 488
 489static int vfb_probe(struct platform_device *dev)
 490{
 491        struct fb_info *info;
 492        int retval = -ENOMEM;
 493
 494        /*
 495         * For real video cards we use ioremap.
 496         */
 497        if (!(videomemory = rvmalloc(videomemorysize)))
 498                return retval;
 499
 500        info = framebuffer_alloc(sizeof(u32) * 256, &dev->dev);
 501        if (!info)
 502                goto err;
 503
 504        info->screen_base = (char __iomem *)videomemory;
 505        info->fbops = &vfb_ops;
 506
 507        retval = fb_find_mode(&info->var, info, NULL,
 508                              NULL, 0, NULL, 8);
 509
 510        if (!retval || (retval == 4))
 511                info->var = vfb_default;
 512        vfb_fix.smem_start = (unsigned long) videomemory;
 513        vfb_fix.smem_len = videomemorysize;
 514        info->fix = vfb_fix;
 515        info->pseudo_palette = info->par;
 516        info->par = NULL;
 517        info->flags = FBINFO_FLAG_DEFAULT;
 518
 519        retval = fb_alloc_cmap(&info->cmap, 256, 0);
 520        if (retval < 0)
 521                goto err1;
 522
 523        retval = register_framebuffer(info);
 524        if (retval < 0)
 525                goto err2;
 526        platform_set_drvdata(dev, info);
 527
 528        fb_info(info, "Virtual frame buffer device, using %ldK of video memory\n",
 529                videomemorysize >> 10);
 530        return 0;
 531err2:
 532        fb_dealloc_cmap(&info->cmap);
 533err1:
 534        framebuffer_release(info);
 535err:
 536        rvfree(videomemory, videomemorysize);
 537        return retval;
 538}
 539
 540static int vfb_remove(struct platform_device *dev)
 541{
 542        struct fb_info *info = platform_get_drvdata(dev);
 543
 544        if (info) {
 545                unregister_framebuffer(info);
 546                rvfree(videomemory, videomemorysize);
 547                fb_dealloc_cmap(&info->cmap);
 548                framebuffer_release(info);
 549        }
 550        return 0;
 551}
 552
 553static struct platform_driver vfb_driver = {
 554        .probe  = vfb_probe,
 555        .remove = vfb_remove,
 556        .driver = {
 557                .name   = "vfb",
 558        },
 559};
 560
 561static struct platform_device *vfb_device;
 562
 563static int __init vfb_init(void)
 564{
 565        int ret = 0;
 566
 567#ifndef MODULE
 568        char *option = NULL;
 569
 570        if (fb_get_options("vfb", &option))
 571                return -ENODEV;
 572        vfb_setup(option);
 573#endif
 574
 575        if (!vfb_enable)
 576                return -ENXIO;
 577
 578        ret = platform_driver_register(&vfb_driver);
 579
 580        if (!ret) {
 581                vfb_device = platform_device_alloc("vfb", 0);
 582
 583                if (vfb_device)
 584                        ret = platform_device_add(vfb_device);
 585                else
 586                        ret = -ENOMEM;
 587
 588                if (ret) {
 589                        platform_device_put(vfb_device);
 590                        platform_driver_unregister(&vfb_driver);
 591                }
 592        }
 593
 594        return ret;
 595}
 596
 597module_init(vfb_init);
 598
 599#ifdef MODULE
 600static void __exit vfb_exit(void)
 601{
 602        platform_device_unregister(vfb_device);
 603        platform_driver_unregister(&vfb_driver);
 604}
 605
 606module_exit(vfb_exit);
 607
 608MODULE_LICENSE("GPL");
 609#endif                          /* MODULE */
 610