2005-04-17 02:20:36 +04:00
/*
* linux / drivers / video / vfb . c - - Virtual frame buffer device
*
* Copyright ( C ) 2002 James Simmons
*
* Copyright ( C ) 1997 Geert Uytterhoeven
*
* This file is subject to the terms and conditions of the GNU General Public
* License . See the file COPYING in the main directory of this archive for
* more details .
*/
# include <linux/module.h>
# include <linux/kernel.h>
# include <linux/errno.h>
# include <linux/string.h>
# include <linux/mm.h>
# include <linux/vmalloc.h>
# include <linux/delay.h>
# include <linux/interrupt.h>
2005-10-29 22:07:23 +04:00
# include <linux/platform_device.h>
2005-04-17 02:20:36 +04:00
# include <linux/fb.h>
# include <linux/init.h>
/*
* RAM we reserve for the frame buffer . This defines the maximum screen
* size
*
* The default can be overridden if the driver is compiled as a module
*/
# define VIDEOMEMSIZE (1*1024*1024) /* 1 MB */
static void * videomemory ;
static u_long videomemorysize = VIDEOMEMSIZE ;
module_param ( videomemorysize , ulong , 0 ) ;
2007-10-16 12:29:17 +04:00
/**********************************************************************
*
* Memory management
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
static void * rvmalloc ( unsigned long size )
{
void * mem ;
unsigned long adr ;
size = PAGE_ALIGN ( size ) ;
mem = vmalloc_32 ( size ) ;
if ( ! mem )
return NULL ;
memset ( mem , 0 , size ) ; /* Clear the ram out, no junk to the user */
adr = ( unsigned long ) mem ;
while ( size > 0 ) {
SetPageReserved ( vmalloc_to_page ( ( void * ) adr ) ) ;
adr + = PAGE_SIZE ;
size - = PAGE_SIZE ;
}
return mem ;
}
static void rvfree ( void * mem , unsigned long size )
{
unsigned long adr ;
if ( ! mem )
return ;
adr = ( unsigned long ) mem ;
while ( ( long ) size > 0 ) {
ClearPageReserved ( vmalloc_to_page ( ( void * ) adr ) ) ;
adr + = PAGE_SIZE ;
size - = PAGE_SIZE ;
}
vfree ( mem ) ;
}
2010-05-25 01:33:57 +04:00
static struct fb_var_screeninfo vfb_default __devinitdata = {
2005-04-17 02:20:36 +04:00
. xres = 640 ,
. yres = 480 ,
. xres_virtual = 640 ,
. yres_virtual = 480 ,
. bits_per_pixel = 8 ,
. red = { 0 , 8 , 0 } ,
. green = { 0 , 8 , 0 } ,
. blue = { 0 , 8 , 0 } ,
. activate = FB_ACTIVATE_TEST ,
. height = - 1 ,
. width = - 1 ,
. pixclock = 20000 ,
. left_margin = 64 ,
. right_margin = 64 ,
. upper_margin = 32 ,
. lower_margin = 32 ,
. hsync_len = 64 ,
. vsync_len = 2 ,
. vmode = FB_VMODE_NONINTERLACED ,
} ;
2010-05-25 01:33:57 +04:00
static struct fb_fix_screeninfo vfb_fix __devinitdata = {
2005-04-17 02:20:36 +04:00
. id = " Virtual FB " ,
. type = FB_TYPE_PACKED_PIXELS ,
. visual = FB_VISUAL_PSEUDOCOLOR ,
. xpanstep = 1 ,
. ypanstep = 1 ,
. ywrapstep = 1 ,
. accel = FB_ACCEL_NONE ,
} ;
static int vfb_enable __initdata = 0 ; /* disabled by default */
module_param ( vfb_enable , bool , 0 ) ;
static int vfb_check_var ( struct fb_var_screeninfo * var ,
struct fb_info * info ) ;
static int vfb_set_par ( struct fb_info * info ) ;
static int vfb_setcolreg ( u_int regno , u_int red , u_int green , u_int blue ,
u_int transp , struct fb_info * info ) ;
static int vfb_pan_display ( struct fb_var_screeninfo * var ,
struct fb_info * info ) ;
2006-01-15 00:21:25 +03:00
static int vfb_mmap ( struct fb_info * info ,
2005-04-17 02:20:36 +04:00
struct vm_area_struct * vma ) ;
static struct fb_ops vfb_ops = {
2007-05-08 11:39:07 +04:00
. fb_read = fb_sys_read ,
. fb_write = fb_sys_write ,
2005-04-17 02:20:36 +04:00
. fb_check_var = vfb_check_var ,
. fb_set_par = vfb_set_par ,
. fb_setcolreg = vfb_setcolreg ,
. fb_pan_display = vfb_pan_display ,
2007-05-08 11:39:01 +04:00
. fb_fillrect = sys_fillrect ,
. fb_copyarea = sys_copyarea ,
. fb_imageblit = sys_imageblit ,
2005-04-17 02:20:36 +04:00
. fb_mmap = vfb_mmap ,
} ;
/*
* Internal routines
*/
static u_long get_line_length ( int xres_virtual , int bpp )
{
u_long length ;
length = xres_virtual * bpp ;
length = ( length + 31 ) & ~ 31 ;
length > > = 3 ;
return ( length ) ;
}
/*
* Setting the video mode has been split into two parts .
* First part , xxxfb_check_var , must not write anything
* to hardware , it should only verify and adjust var .
* This means it doesn ' t alter par but it does use hardware
* data from it to check this var .
*/
static int vfb_check_var ( struct fb_var_screeninfo * var ,
struct fb_info * info )
{
u_long line_length ;
/*
* FB_VMODE_CONUPDATE and FB_VMODE_SMOOTH_XPAN are equal !
* as FB_VMODE_SMOOTH_XPAN is only used internally
*/
if ( var - > vmode & FB_VMODE_CONUPDATE ) {
var - > vmode | = FB_VMODE_YWRAP ;
var - > xoffset = info - > var . xoffset ;
var - > yoffset = info - > var . yoffset ;
}
/*
* Some very basic checks
*/
if ( ! var - > xres )
var - > xres = 1 ;
if ( ! var - > yres )
var - > yres = 1 ;
if ( var - > xres > var - > xres_virtual )
var - > xres_virtual = var - > xres ;
if ( var - > yres > var - > yres_virtual )
var - > yres_virtual = var - > yres ;
if ( var - > bits_per_pixel < = 1 )
var - > bits_per_pixel = 1 ;
else if ( var - > bits_per_pixel < = 8 )
var - > bits_per_pixel = 8 ;
else if ( var - > bits_per_pixel < = 16 )
var - > bits_per_pixel = 16 ;
else if ( var - > bits_per_pixel < = 24 )
var - > bits_per_pixel = 24 ;
else if ( var - > bits_per_pixel < = 32 )
var - > bits_per_pixel = 32 ;
else
return - EINVAL ;
if ( var - > xres_virtual < var - > xoffset + var - > xres )
var - > xres_virtual = var - > xoffset + var - > xres ;
if ( var - > yres_virtual < var - > yoffset + var - > yres )
var - > yres_virtual = var - > yoffset + var - > yres ;
/*
* Memory limit
*/
line_length =
get_line_length ( var - > xres_virtual , var - > bits_per_pixel ) ;
if ( line_length * var - > yres_virtual > videomemorysize )
return - ENOMEM ;
/*
* Now that we checked it we alter var . The reason being is that the video
* mode passed in might not work but slight changes to it might make it
* work . This way we let the user know what is acceptable .
*/
switch ( var - > bits_per_pixel ) {
case 1 :
case 8 :
var - > red . offset = 0 ;
var - > red . length = 8 ;
var - > green . offset = 0 ;
var - > green . length = 8 ;
var - > blue . offset = 0 ;
var - > blue . length = 8 ;
var - > transp . offset = 0 ;
var - > transp . length = 0 ;
break ;
case 16 : /* RGBA 5551 */
if ( var - > transp . length ) {
var - > red . offset = 0 ;
var - > red . length = 5 ;
var - > green . offset = 5 ;
var - > green . length = 5 ;
var - > blue . offset = 10 ;
var - > blue . length = 5 ;
var - > transp . offset = 15 ;
var - > transp . length = 1 ;
} else { /* RGB 565 */
var - > red . offset = 0 ;
var - > red . length = 5 ;
var - > green . offset = 5 ;
var - > green . length = 6 ;
var - > blue . offset = 11 ;
var - > blue . length = 5 ;
var - > transp . offset = 0 ;
var - > transp . length = 0 ;
}
break ;
case 24 : /* RGB 888 */
var - > red . offset = 0 ;
var - > red . length = 8 ;
var - > green . offset = 8 ;
var - > green . length = 8 ;
var - > blue . offset = 16 ;
var - > blue . length = 8 ;
var - > transp . offset = 0 ;
var - > transp . length = 0 ;
break ;
case 32 : /* RGBA 8888 */
var - > red . offset = 0 ;
var - > red . length = 8 ;
var - > green . offset = 8 ;
var - > green . length = 8 ;
var - > blue . offset = 16 ;
var - > blue . length = 8 ;
var - > transp . offset = 24 ;
var - > transp . length = 8 ;
break ;
}
var - > red . msb_right = 0 ;
var - > green . msb_right = 0 ;
var - > blue . msb_right = 0 ;
var - > transp . msb_right = 0 ;
return 0 ;
}
/* This routine actually sets the video mode. It's in here where we
* the hardware state info - > par and fix which can be affected by the
* change in par . For this driver it doesn ' t do much .
*/
static int vfb_set_par ( struct fb_info * info )
{
info - > fix . line_length = get_line_length ( info - > var . xres_virtual ,
info - > var . bits_per_pixel ) ;
return 0 ;
}
/*
* 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 .
*/
static int vfb_setcolreg ( u_int regno , u_int red , u_int green , u_int blue ,
u_int transp , struct fb_info * info )
{
if ( regno > = 256 ) /* no. of hw registers */
return 1 ;
/*
* Program hardware . . . do anything you want with transp
*/
/* grayscale works only partially under directcolor */
if ( info - > var . grayscale ) {
/* grayscale = 0.30*R + 0.59*G + 0.11*B */
red = green = blue =
( red * 77 + green * 151 + blue * 28 ) > > 8 ;
}
/* Directcolor:
* var - > { color } . offset contains start of bitfield
* var - > { color } . length contains length of bitfield
* { hardwarespecific } contains width of RAMDAC
* cmap [ X ] is programmed to ( X < < red . offset ) | ( X < < green . offset ) | ( X < < blue . offset )
* RAMDAC [ X ] is programmed to ( red , green , blue )
2009-04-14 01:39:41 +04:00
*
2005-04-17 02:20:36 +04:00
* Pseudocolor :
2009-04-14 01:39:41 +04:00
* var - > { color } . offset is 0 unless the palette index takes less than
* bits_per_pixel bits and is stored in the upper
* bits of the pixel value
* var - > { color } . length is set so that 1 < < length is the number of available
* palette entries
2005-04-17 02:20:36 +04:00
* cmap is not used
* RAMDAC [ X ] is programmed to ( red , green , blue )
2009-04-14 01:39:41 +04:00
*
2005-04-17 02:20:36 +04:00
* Truecolor :
* does not use DAC . Usually 3 are present .
* var - > { color } . offset contains start of bitfield
* var - > { color } . length contains length of bitfield
* cmap is programmed to ( red < < red . offset ) | ( green < < green . offset ) |
* ( blue < < blue . offset ) | ( transp < < transp . offset )
* RAMDAC does not exist
*/
# define CNVT_TOHW(val,width) ((((val)<<(width))+0x7FFF-(val))>>16)
switch ( info - > fix . visual ) {
case FB_VISUAL_TRUECOLOR :
case FB_VISUAL_PSEUDOCOLOR :
red = CNVT_TOHW ( red , info - > var . red . length ) ;
green = CNVT_TOHW ( green , info - > var . green . length ) ;
blue = CNVT_TOHW ( blue , info - > var . blue . length ) ;
transp = CNVT_TOHW ( transp , info - > var . transp . length ) ;
break ;
case FB_VISUAL_DIRECTCOLOR :
red = CNVT_TOHW ( red , 8 ) ; /* expect 8 bit DAC */
green = CNVT_TOHW ( green , 8 ) ;
blue = CNVT_TOHW ( blue , 8 ) ;
/* hey, there is bug in transp handling... */
transp = CNVT_TOHW ( transp , 8 ) ;
break ;
}
# undef CNVT_TOHW
/* Truecolor has hardware independent palette */
if ( info - > fix . visual = = FB_VISUAL_TRUECOLOR ) {
u32 v ;
if ( regno > = 16 )
return 1 ;
v = ( red < < info - > var . red . offset ) |
( green < < info - > var . green . offset ) |
( blue < < info - > var . blue . offset ) |
( transp < < info - > var . transp . offset ) ;
switch ( info - > var . bits_per_pixel ) {
case 8 :
break ;
case 16 :
( ( u32 * ) ( info - > pseudo_palette ) ) [ regno ] = v ;
break ;
case 24 :
case 32 :
( ( u32 * ) ( info - > pseudo_palette ) ) [ regno ] = v ;
break ;
}
return 0 ;
}
return 0 ;
}
/*
* Pan or Wrap the Display
*
* This call looks only at xoffset , yoffset and the FB_VMODE_YWRAP flag
*/
static int vfb_pan_display ( struct fb_var_screeninfo * var ,
struct fb_info * info )
{
if ( var - > vmode & FB_VMODE_YWRAP ) {
if ( var - > yoffset < 0
| | var - > yoffset > = info - > var . yres_virtual
| | var - > xoffset )
return - EINVAL ;
} else {
if ( var - > xoffset + var - > xres > info - > var . xres_virtual | |
var - > yoffset + var - > yres > info - > var . yres_virtual )
return - EINVAL ;
}
info - > var . xoffset = var - > xoffset ;
info - > var . yoffset = var - > yoffset ;
if ( var - > vmode & FB_VMODE_YWRAP )
info - > var . vmode | = FB_VMODE_YWRAP ;
else
info - > var . vmode & = ~ FB_VMODE_YWRAP ;
return 0 ;
}
/*
* Most drivers don ' t need their own mmap function
*/
2006-01-15 00:21:25 +03:00
static int vfb_mmap ( struct fb_info * info ,
2005-04-17 02:20:36 +04:00
struct vm_area_struct * vma )
{
2007-10-16 12:29:17 +04:00
unsigned long start = vma - > vm_start ;
unsigned long size = vma - > vm_end - vma - > vm_start ;
unsigned long offset = vma - > vm_pgoff < < PAGE_SHIFT ;
unsigned long page , pos ;
if ( offset + size > info - > fix . smem_len ) {
return - EINVAL ;
}
pos = ( unsigned long ) info - > fix . smem_start + offset ;
while ( size > 0 ) {
page = vmalloc_to_pfn ( ( void * ) pos ) ;
if ( remap_pfn_range ( vma , start , page , PAGE_SIZE , PAGE_SHARED ) ) {
return - EAGAIN ;
}
start + = PAGE_SIZE ;
pos + = PAGE_SIZE ;
if ( size > PAGE_SIZE )
size - = PAGE_SIZE ;
else
size = 0 ;
}
vma - > vm_flags | = VM_RESERVED ; /* avoid to swap out this VMA */
return 0 ;
2005-04-17 02:20:36 +04:00
}
# ifndef MODULE
2008-07-24 08:31:26 +04:00
/*
* The virtual framebuffer driver is only enabled if explicitly
* requested by passing ' video = vfb : ' ( or any actual options ) .
*/
2005-04-17 02:20:36 +04:00
static int __init vfb_setup ( char * options )
{
char * this_opt ;
2008-07-24 08:31:26 +04:00
vfb_enable = 0 ;
if ( ! options )
return 1 ;
2005-04-17 02:20:36 +04:00
vfb_enable = 1 ;
2008-07-24 08:31:26 +04:00
if ( ! * options )
2005-04-17 02:20:36 +04:00
return 1 ;
while ( ( this_opt = strsep ( & options , " , " ) ) ! = NULL ) {
if ( ! * this_opt )
continue ;
2008-07-24 08:31:26 +04:00
/* Test disable for backwards compatibility */
if ( ! strcmp ( this_opt , " disable " ) )
2005-04-17 02:20:36 +04:00
vfb_enable = 0 ;
}
return 1 ;
}
# endif /* MODULE */
/*
* Initialisation
*/
2010-02-04 22:56:51 +03:00
static int __devinit vfb_probe ( struct platform_device * dev )
2005-04-17 02:20:36 +04:00
{
struct fb_info * info ;
int retval = - ENOMEM ;
/*
* For real video cards we use ioremap .
*/
2007-10-16 12:29:17 +04:00
if ( ! ( videomemory = rvmalloc ( videomemorysize ) ) )
2005-04-17 02:20:36 +04:00
return retval ;
/*
* VFB must clear memory to prevent kernel info
* leakage into userspace
* VGA - based drivers MUST NOT clear memory if
* they want to be able to take over vgacon
*/
memset ( videomemory , 0 , videomemorysize ) ;
info = framebuffer_alloc ( sizeof ( u32 ) * 256 , & dev - > dev ) ;
if ( ! info )
goto err ;
info - > screen_base = ( char __iomem * ) videomemory ;
info - > fbops = & vfb_ops ;
retval = fb_find_mode ( & info - > var , info , NULL ,
NULL , 0 , NULL , 8 ) ;
if ( ! retval | | ( retval = = 4 ) )
info - > var = vfb_default ;
2007-10-16 12:29:17 +04:00
vfb_fix . smem_start = ( unsigned long ) videomemory ;
vfb_fix . smem_len = videomemorysize ;
2005-04-17 02:20:36 +04:00
info - > fix = vfb_fix ;
info - > pseudo_palette = info - > par ;
info - > par = NULL ;
info - > flags = FBINFO_FLAG_DEFAULT ;
retval = fb_alloc_cmap ( & info - > cmap , 256 , 0 ) ;
if ( retval < 0 )
goto err1 ;
retval = register_framebuffer ( info ) ;
if ( retval < 0 )
goto err2 ;
2005-11-10 01:32:44 +03:00
platform_set_drvdata ( dev , info ) ;
2005-04-17 02:20:36 +04:00
printk ( KERN_INFO
" fb%d: Virtual frame buffer device, using %ldK of video memory \n " ,
info - > node , videomemorysize > > 10 ) ;
return 0 ;
err2 :
fb_dealloc_cmap ( & info - > cmap ) ;
err1 :
framebuffer_release ( info ) ;
err :
2007-10-16 12:29:17 +04:00
rvfree ( videomemory , videomemorysize ) ;
2005-04-17 02:20:36 +04:00
return retval ;
}
2005-11-10 01:32:44 +03:00
static int vfb_remove ( struct platform_device * dev )
2005-04-17 02:20:36 +04:00
{
2005-11-10 01:32:44 +03:00
struct fb_info * info = platform_get_drvdata ( dev ) ;
2005-04-17 02:20:36 +04:00
if ( info ) {
unregister_framebuffer ( info ) ;
2007-10-16 12:29:17 +04:00
rvfree ( videomemory , videomemorysize ) ;
2009-04-01 02:25:22 +04:00
fb_dealloc_cmap ( & info - > cmap ) ;
2005-04-17 02:20:36 +04:00
framebuffer_release ( info ) ;
}
return 0 ;
}
2005-11-10 01:32:44 +03:00
static struct platform_driver vfb_driver = {
2005-04-17 02:20:36 +04:00
. probe = vfb_probe ,
. remove = vfb_remove ,
2005-11-10 01:32:44 +03:00
. driver = {
. name = " vfb " ,
} ,
2005-04-17 02:20:36 +04:00
} ;
2006-06-26 11:26:34 +04:00
static struct platform_device * vfb_device ;
2005-04-17 02:20:36 +04:00
static int __init vfb_init ( void )
{
int ret = 0 ;
# ifndef MODULE
char * option = NULL ;
if ( fb_get_options ( " vfb " , & option ) )
return - ENODEV ;
vfb_setup ( option ) ;
# endif
if ( ! vfb_enable )
return - ENXIO ;
2005-11-10 01:32:44 +03:00
ret = platform_driver_register ( & vfb_driver ) ;
2005-04-17 02:20:36 +04:00
if ( ! ret ) {
2006-06-26 11:26:34 +04:00
vfb_device = platform_device_alloc ( " vfb " , 0 ) ;
if ( vfb_device )
ret = platform_device_add ( vfb_device ) ;
else
ret = - ENOMEM ;
if ( ret ) {
platform_device_put ( vfb_device ) ;
2005-11-10 01:32:44 +03:00
platform_driver_unregister ( & vfb_driver ) ;
2006-06-26 11:26:34 +04:00
}
2005-04-17 02:20:36 +04:00
}
2006-06-26 11:26:34 +04:00
2005-04-17 02:20:36 +04:00
return ret ;
}
module_init ( vfb_init ) ;
# ifdef MODULE
static void __exit vfb_exit ( void )
{
2006-06-26 11:26:34 +04:00
platform_device_unregister ( vfb_device ) ;
2005-11-10 01:32:44 +03:00
platform_driver_unregister ( & vfb_driver ) ;
2005-04-17 02:20:36 +04:00
}
module_exit ( vfb_exit ) ;
MODULE_LICENSE ( " GPL " ) ;
# endif /* MODULE */