2007-10-16 12:28:26 +04:00
/*
* A framebuffer driver for VBE 2.0 + compliant video cards
*
* ( c ) 2007 Michal Januszewski < spock @ gentoo . org >
* Loosely based upon the vesafb driver .
*
*/
# include <linux/init.h>
# include <linux/module.h>
# include <linux/moduleparam.h>
# include <linux/skbuff.h>
# include <linux/timer.h>
# include <linux/completion.h>
# include <linux/connector.h>
# include <linux/random.h>
# include <linux/platform_device.h>
# include <linux/limits.h>
# include <linux/fb.h>
# include <linux/io.h>
# include <linux/mutex.h>
# include <video/edid.h>
# include <video/uvesafb.h>
# ifdef CONFIG_X86
# include <video/vga.h>
# endif
# ifdef CONFIG_MTRR
# include <asm/mtrr.h>
# endif
# include "edid.h"
static struct cb_id uvesafb_cn_id = {
. idx = CN_IDX_V86D ,
. val = CN_VAL_V86D_UVESAFB
} ;
static char v86d_path [ PATH_MAX ] = " /sbin/v86d " ;
static char v86d_started ; /* has v86d been started by uvesafb? */
static struct fb_fix_screeninfo uvesafb_fix __devinitdata = {
. id = " VESA VGA " ,
. type = FB_TYPE_PACKED_PIXELS ,
. accel = FB_ACCEL_NONE ,
. visual = FB_VISUAL_TRUECOLOR ,
} ;
static int mtrr __devinitdata = 3 ; /* enable mtrr by default */
2008-01-14 11:55:24 +03:00
static int blank = 1 ; /* enable blanking by default */
2008-02-24 02:23:53 +03:00
static int ypan = 1 ; /* 0: scroll, 1: ypan, 2: ywrap */
2007-10-16 12:28:26 +04:00
static int pmi_setpal __devinitdata = 1 ; /* use PMI for palette changes */
static int nocrtc __devinitdata ; /* ignore CRTC settings */
static int noedid __devinitdata ; /* don't try DDC transfers */
static int vram_remap __devinitdata ; /* set amt. of memory to be used */
static int vram_total __devinitdata ; /* set total amount of memory */
static u16 maxclk __devinitdata ; /* maximum pixel clock */
static u16 maxvf __devinitdata ; /* maximum vertical frequency */
static u16 maxhf __devinitdata ; /* maximum horizontal frequency */
static u16 vbemode __devinitdata ; /* force use of a specific VBE mode */
static char * mode_option __devinitdata ;
static struct uvesafb_ktask * uvfb_tasks [ UVESAFB_TASKS_MAX ] ;
static DEFINE_MUTEX ( uvfb_lock ) ;
/*
* A handler for replies from userspace .
*
* Make sure each message passes consistency checks and if it does ,
* find the kernel part of the task struct , copy the registers and
* the buffer contents and then complete the task .
*/
static void uvesafb_cn_callback ( void * data )
{
struct cn_msg * msg = data ;
struct uvesafb_task * utask ;
struct uvesafb_ktask * task ;
if ( msg - > seq > = UVESAFB_TASKS_MAX )
return ;
mutex_lock ( & uvfb_lock ) ;
task = uvfb_tasks [ msg - > seq ] ;
if ( ! task | | msg - > ack ! = task - > ack ) {
mutex_unlock ( & uvfb_lock ) ;
return ;
}
utask = ( struct uvesafb_task * ) msg - > data ;
/* Sanity checks for the buffer length. */
if ( task - > t . buf_len < utask - > buf_len | |
utask - > buf_len > msg - > len - sizeof ( * utask ) ) {
mutex_unlock ( & uvfb_lock ) ;
return ;
}
uvfb_tasks [ msg - > seq ] = NULL ;
mutex_unlock ( & uvfb_lock ) ;
memcpy ( & task - > t , utask , sizeof ( * utask ) ) ;
if ( task - > t . buf_len & & task - > buf )
memcpy ( task - > buf , utask + 1 , task - > t . buf_len ) ;
complete ( task - > done ) ;
return ;
}
static int uvesafb_helper_start ( void )
{
char * envp [ ] = {
" HOME=/ " ,
" PATH=/sbin:/bin " ,
NULL ,
} ;
char * argv [ ] = {
v86d_path ,
NULL ,
} ;
return call_usermodehelper ( v86d_path , argv , envp , 1 ) ;
}
/*
* Execute a uvesafb task .
*
* Returns 0 if the task is executed successfully .
*
* A message sent to the userspace consists of the uvesafb_task
* struct and ( optionally ) a buffer . The uvesafb_task struct is
* a simplified version of uvesafb_ktask ( its kernel counterpart )
* containing only the register values , flags and the length of
* the buffer .
*
* Each message is assigned a sequence number ( increased linearly )
* and a random ack number . The sequence number is used as a key
* for the uvfb_tasks array which holds pointers to uvesafb_ktask
* structs for all requests .
*/
static int uvesafb_exec ( struct uvesafb_ktask * task )
{
static int seq ;
struct cn_msg * m ;
int err ;
int len = sizeof ( task - > t ) + task - > t . buf_len ;
/*
* Check whether the message isn ' t longer than the maximum
* allowed by connector .
*/
if ( sizeof ( * m ) + len > CONNECTOR_MAX_MSG_SIZE ) {
printk ( KERN_WARNING " uvesafb: message too long (%d), "
" can't execute task \n " , ( int ) ( sizeof ( * m ) + len ) ) ;
return - E2BIG ;
}
m = kzalloc ( sizeof ( * m ) + len , GFP_KERNEL ) ;
if ( ! m )
return - ENOMEM ;
init_completion ( task - > done ) ;
memcpy ( & m - > id , & uvesafb_cn_id , sizeof ( m - > id ) ) ;
m - > seq = seq ;
m - > len = len ;
m - > ack = random32 ( ) ;
/* uvesafb_task structure */
memcpy ( m + 1 , & task - > t , sizeof ( task - > t ) ) ;
/* Buffer */
memcpy ( ( u8 * ) ( m + 1 ) + sizeof ( task - > t ) , task - > buf , task - > t . buf_len ) ;
/*
* Save the message ack number so that we can find the kernel
* part of this task when a reply is received from userspace .
*/
task - > ack = m - > ack ;
mutex_lock ( & uvfb_lock ) ;
/* If all slots are taken -- bail out. */
if ( uvfb_tasks [ seq ] ) {
mutex_unlock ( & uvfb_lock ) ;
2008-04-28 13:15:41 +04:00
err = - EBUSY ;
goto out ;
2007-10-16 12:28:26 +04:00
}
/* Save a pointer to the kernel part of the task struct. */
uvfb_tasks [ seq ] = task ;
mutex_unlock ( & uvfb_lock ) ;
err = cn_netlink_send ( m , 0 , gfp_any ( ) ) ;
if ( err = = - ESRCH ) {
/*
* Try to start the userspace helper if sending
* the request failed the first time .
*/
err = uvesafb_helper_start ( ) ;
if ( err ) {
printk ( KERN_ERR " uvesafb: failed to execute %s \n " ,
v86d_path ) ;
printk ( KERN_ERR " uvesafb: make sure that the v86d "
" helper is installed and executable \n " ) ;
} else {
v86d_started = 1 ;
err = cn_netlink_send ( m , 0 , gfp_any ( ) ) ;
}
}
if ( ! err & & ! ( task - > t . flags & TF_EXIT ) )
err = ! wait_for_completion_timeout ( task - > done ,
msecs_to_jiffies ( UVESAFB_TIMEOUT ) ) ;
mutex_lock ( & uvfb_lock ) ;
uvfb_tasks [ seq ] = NULL ;
mutex_unlock ( & uvfb_lock ) ;
seq + + ;
if ( seq > = UVESAFB_TASKS_MAX )
seq = 0 ;
2008-04-28 13:15:41 +04:00
out :
kfree ( m ) ;
2007-10-16 12:28:26 +04:00
return err ;
}
/*
* Free a uvesafb_ktask struct .
*/
static void uvesafb_free ( struct uvesafb_ktask * task )
{
if ( task ) {
if ( task - > done )
kfree ( task - > done ) ;
kfree ( task ) ;
}
}
/*
* Prepare a uvesafb_ktask struct to be used again .
*/
static void uvesafb_reset ( struct uvesafb_ktask * task )
{
struct completion * cpl = task - > done ;
memset ( task , 0 , sizeof ( * task ) ) ;
task - > done = cpl ;
}
/*
* Allocate and prepare a uvesafb_ktask struct .
*/
static struct uvesafb_ktask * uvesafb_prep ( void )
{
struct uvesafb_ktask * task ;
task = kzalloc ( sizeof ( * task ) , GFP_KERNEL ) ;
if ( task ) {
task - > done = kzalloc ( sizeof ( * task - > done ) , GFP_KERNEL ) ;
if ( ! task - > done ) {
kfree ( task ) ;
task = NULL ;
}
}
return task ;
}
static void uvesafb_setup_var ( struct fb_var_screeninfo * var ,
struct fb_info * info , struct vbe_mode_ib * mode )
{
struct uvesafb_par * par = info - > par ;
var - > vmode = FB_VMODE_NONINTERLACED ;
var - > sync = FB_SYNC_VERT_HIGH_ACT ;
var - > xres = mode - > x_res ;
var - > yres = mode - > y_res ;
var - > xres_virtual = mode - > x_res ;
var - > yres_virtual = ( par - > ypan ) ?
info - > fix . smem_len / mode - > bytes_per_scan_line :
mode - > y_res ;
var - > xoffset = 0 ;
var - > yoffset = 0 ;
var - > bits_per_pixel = mode - > bits_per_pixel ;
if ( var - > bits_per_pixel = = 15 )
var - > bits_per_pixel = 16 ;
if ( var - > bits_per_pixel > 8 ) {
var - > red . offset = mode - > red_off ;
var - > red . length = mode - > red_len ;
var - > green . offset = mode - > green_off ;
var - > green . length = mode - > green_len ;
var - > blue . offset = mode - > blue_off ;
var - > blue . length = mode - > blue_len ;
var - > transp . offset = mode - > rsvd_off ;
var - > transp . length = mode - > rsvd_len ;
} else {
var - > red . offset = 0 ;
var - > green . offset = 0 ;
var - > blue . offset = 0 ;
var - > transp . offset = 0 ;
/*
* We ' re assuming that we can switch the DAC to 8 bits . If
* this proves to be incorrect , we ' ll update the fields
* later in set_par ( ) .
*/
if ( par - > vbe_ib . capabilities & VBE_CAP_CAN_SWITCH_DAC ) {
var - > red . length = 8 ;
var - > green . length = 8 ;
var - > blue . length = 8 ;
var - > transp . length = 0 ;
} else {
var - > red . length = 6 ;
var - > green . length = 6 ;
var - > blue . length = 6 ;
var - > transp . length = 0 ;
}
}
}
static int uvesafb_vbe_find_mode ( struct uvesafb_par * par ,
int xres , int yres , int depth , unsigned char flags )
{
int i , match = - 1 , h = 0 , d = 0x7fffffff ;
for ( i = 0 ; i < par - > vbe_modes_cnt ; i + + ) {
h = abs ( par - > vbe_modes [ i ] . x_res - xres ) +
abs ( par - > vbe_modes [ i ] . y_res - yres ) +
abs ( depth - par - > vbe_modes [ i ] . depth ) ;
/*
* We have an exact match in terms of resolution
* and depth .
*/
if ( h = = 0 )
return i ;
if ( h < d | | ( h = = d & & par - > vbe_modes [ i ] . depth > depth ) ) {
d = h ;
match = i ;
}
}
i = 1 ;
if ( flags & UVESAFB_EXACT_DEPTH & &
par - > vbe_modes [ match ] . depth ! = depth )
i = 0 ;
if ( flags & UVESAFB_EXACT_RES & & d > 24 )
i = 0 ;
if ( i ! = 0 )
return match ;
else
return - 1 ;
}
static u8 * uvesafb_vbe_state_save ( struct uvesafb_par * par )
{
struct uvesafb_ktask * task ;
u8 * state ;
int err ;
if ( ! par - > vbe_state_size )
return NULL ;
state = kmalloc ( par - > vbe_state_size , GFP_KERNEL ) ;
if ( ! state )
return NULL ;
task = uvesafb_prep ( ) ;
if ( ! task ) {
kfree ( state ) ;
return NULL ;
}
task - > t . regs . eax = 0x4f04 ;
task - > t . regs . ecx = 0x000f ;
task - > t . regs . edx = 0x0001 ;
task - > t . flags = TF_BUF_RET | TF_BUF_ESBX ;
task - > t . buf_len = par - > vbe_state_size ;
task - > buf = state ;
err = uvesafb_exec ( task ) ;
if ( err | | ( task - > t . regs . eax & 0xffff ) ! = 0x004f ) {
printk ( KERN_WARNING " uvesafb: VBE get state call "
" failed (eax=0x%x, err=%d) \n " ,
task - > t . regs . eax , err ) ;
kfree ( state ) ;
state = NULL ;
}
uvesafb_free ( task ) ;
return state ;
}
static void uvesafb_vbe_state_restore ( struct uvesafb_par * par , u8 * state_buf )
{
struct uvesafb_ktask * task ;
int err ;
if ( ! state_buf )
return ;
task = uvesafb_prep ( ) ;
if ( ! task )
return ;
task - > t . regs . eax = 0x4f04 ;
task - > t . regs . ecx = 0x000f ;
task - > t . regs . edx = 0x0002 ;
task - > t . buf_len = par - > vbe_state_size ;
task - > t . flags = TF_BUF_ESBX ;
task - > buf = state_buf ;
err = uvesafb_exec ( task ) ;
if ( err | | ( task - > t . regs . eax & 0xffff ) ! = 0x004f )
printk ( KERN_WARNING " uvesafb: VBE state restore call "
" failed (eax=0x%x, err=%d) \n " ,
task - > t . regs . eax , err ) ;
uvesafb_free ( task ) ;
}
static int __devinit uvesafb_vbe_getinfo ( struct uvesafb_ktask * task ,
struct uvesafb_par * par )
{
int err ;
task - > t . regs . eax = 0x4f00 ;
task - > t . flags = TF_VBEIB ;
task - > t . buf_len = sizeof ( struct vbe_ib ) ;
task - > buf = & par - > vbe_ib ;
strncpy ( par - > vbe_ib . vbe_signature , " VBE2 " , 4 ) ;
err = uvesafb_exec ( task ) ;
if ( err | | ( task - > t . regs . eax & 0xffff ) ! = 0x004f ) {
printk ( KERN_ERR " uvesafb: Getting VBE info block failed "
" (eax=0x%x, err=%d) \n " , ( u32 ) task - > t . regs . eax ,
err ) ;
return - EINVAL ;
}
if ( par - > vbe_ib . vbe_version < 0x0200 ) {
printk ( KERN_ERR " uvesafb: Sorry, pre-VBE 2.0 cards are "
" not supported. \n " ) ;
return - EINVAL ;
}
if ( ! par - > vbe_ib . mode_list_ptr ) {
printk ( KERN_ERR " uvesafb: Missing mode list! \n " ) ;
return - EINVAL ;
}
printk ( KERN_INFO " uvesafb: " ) ;
/*
* Convert string pointers and the mode list pointer into
* usable addresses . Print informational messages about the
* video adapter and its vendor .
*/
if ( par - > vbe_ib . oem_vendor_name_ptr )
printk ( " %s, " ,
( ( char * ) task - > buf ) + par - > vbe_ib . oem_vendor_name_ptr ) ;
if ( par - > vbe_ib . oem_product_name_ptr )
printk ( " %s, " ,
( ( char * ) task - > buf ) + par - > vbe_ib . oem_product_name_ptr ) ;
if ( par - > vbe_ib . oem_product_rev_ptr )
printk ( " %s, " ,
( ( char * ) task - > buf ) + par - > vbe_ib . oem_product_rev_ptr ) ;
if ( par - > vbe_ib . oem_string_ptr )
printk ( " OEM: %s, " ,
( ( char * ) task - > buf ) + par - > vbe_ib . oem_string_ptr ) ;
printk ( " VBE v%d.%d \n " , ( ( par - > vbe_ib . vbe_version & 0xff00 ) > > 8 ) ,
par - > vbe_ib . vbe_version & 0xff ) ;
return 0 ;
}
static int __devinit uvesafb_vbe_getmodes ( struct uvesafb_ktask * task ,
struct uvesafb_par * par )
{
int off = 0 , err ;
u16 * mode ;
par - > vbe_modes_cnt = 0 ;
/* Count available modes. */
mode = ( u16 * ) ( ( ( u8 * ) & par - > vbe_ib ) + par - > vbe_ib . mode_list_ptr ) ;
while ( * mode ! = 0xffff ) {
par - > vbe_modes_cnt + + ;
mode + + ;
}
par - > vbe_modes = kzalloc ( sizeof ( struct vbe_mode_ib ) *
par - > vbe_modes_cnt , GFP_KERNEL ) ;
if ( ! par - > vbe_modes )
return - ENOMEM ;
/* Get info about all available modes. */
mode = ( u16 * ) ( ( ( u8 * ) & par - > vbe_ib ) + par - > vbe_ib . mode_list_ptr ) ;
while ( * mode ! = 0xffff ) {
struct vbe_mode_ib * mib ;
uvesafb_reset ( task ) ;
task - > t . regs . eax = 0x4f01 ;
task - > t . regs . ecx = ( u32 ) * mode ;
task - > t . flags = TF_BUF_RET | TF_BUF_ESDI ;
task - > t . buf_len = sizeof ( struct vbe_mode_ib ) ;
task - > buf = par - > vbe_modes + off ;
err = uvesafb_exec ( task ) ;
if ( err | | ( task - > t . regs . eax & 0xffff ) ! = 0x004f ) {
2008-10-16 09:03:47 +04:00
printk ( KERN_WARNING " uvesafb: Getting mode info block "
2007-10-16 12:28:26 +04:00
" for mode 0x%x failed (eax=0x%x, err=%d) \n " ,
* mode , ( u32 ) task - > t . regs . eax , err ) ;
2008-10-16 09:03:47 +04:00
mode + + ;
par - > vbe_modes_cnt - - ;
continue ;
2007-10-16 12:28:26 +04:00
}
mib = task - > buf ;
mib - > mode_id = * mode ;
/*
* We only want modes that are supported with the current
* hardware configuration , color , graphics and that have
* support for the LFB .
*/
if ( ( mib - > mode_attr & VBE_MODE_MASK ) = = VBE_MODE_MASK & &
mib - > bits_per_pixel > = 8 )
off + + ;
else
par - > vbe_modes_cnt - - ;
mode + + ;
mib - > depth = mib - > red_len + mib - > green_len + mib - > blue_len ;
/*
* Handle 8 bpp modes and modes with broken color component
* lengths .
*/
if ( mib - > depth = = 0 | | ( mib - > depth = = 24 & &
mib - > bits_per_pixel = = 32 ) )
mib - > depth = mib - > bits_per_pixel ;
}
2008-10-16 09:03:47 +04:00
if ( par - > vbe_modes_cnt > 0 )
return 0 ;
else
return - EINVAL ;
2007-10-16 12:28:26 +04:00
}
/*
* The Protected Mode Interface is 32 - bit x86 code , so we only run it on
* x86 and not x86_64 .
*/
# ifdef CONFIG_X86_32
static int __devinit uvesafb_vbe_getpmi ( struct uvesafb_ktask * task ,
struct uvesafb_par * par )
{
int i , err ;
uvesafb_reset ( task ) ;
task - > t . regs . eax = 0x4f0a ;
task - > t . regs . ebx = 0x0 ;
err = uvesafb_exec ( task ) ;
if ( ( task - > t . regs . eax & 0xffff ) ! = 0x4f | | task - > t . regs . es < 0xc000 ) {
par - > pmi_setpal = par - > ypan = 0 ;
} else {
par - > pmi_base = ( u16 * ) phys_to_virt ( ( ( u32 ) task - > t . regs . es < < 4 )
+ task - > t . regs . edi ) ;
par - > pmi_start = ( u8 * ) par - > pmi_base + par - > pmi_base [ 1 ] ;
par - > pmi_pal = ( u8 * ) par - > pmi_base + par - > pmi_base [ 2 ] ;
printk ( KERN_INFO " uvesafb: protected mode interface info at "
" %04x:%04x \n " ,
( u16 ) task - > t . regs . es , ( u16 ) task - > t . regs . edi ) ;
printk ( KERN_INFO " uvesafb: pmi: set display start = %p, "
" set palette = %p \n " , par - > pmi_start ,
par - > pmi_pal ) ;
if ( par - > pmi_base [ 3 ] ) {
printk ( KERN_INFO " uvesafb: pmi: ports = " ) ;
for ( i = par - > pmi_base [ 3 ] / 2 ;
par - > pmi_base [ i ] ! = 0xffff ; i + + )
printk ( " %x " , par - > pmi_base [ i ] ) ;
printk ( " \n " ) ;
if ( par - > pmi_base [ i ] ! = 0xffff ) {
printk ( KERN_INFO " uvesafb: can't handle memory "
" requests, pmi disabled \n " ) ;
par - > ypan = par - > pmi_setpal = 0 ;
}
}
}
return 0 ;
}
# endif /* CONFIG_X86_32 */
/*
* Check whether a video mode is supported by the Video BIOS and is
* compatible with the monitor limits .
*/
static int __devinit uvesafb_is_valid_mode ( struct fb_videomode * mode ,
struct fb_info * info )
{
if ( info - > monspecs . gtf ) {
fb_videomode_to_var ( & info - > var , mode ) ;
if ( fb_validate_mode ( & info - > var , info ) )
return 0 ;
}
if ( uvesafb_vbe_find_mode ( info - > par , mode - > xres , mode - > yres , 8 ,
UVESAFB_EXACT_RES ) = = - 1 )
return 0 ;
return 1 ;
}
static int __devinit uvesafb_vbe_getedid ( struct uvesafb_ktask * task ,
struct fb_info * info )
{
struct uvesafb_par * par = info - > par ;
int err = 0 ;
if ( noedid | | par - > vbe_ib . vbe_version < 0x0300 )
return - EINVAL ;
task - > t . regs . eax = 0x4f15 ;
task - > t . regs . ebx = 0 ;
task - > t . regs . ecx = 0 ;
task - > t . buf_len = 0 ;
task - > t . flags = 0 ;
err = uvesafb_exec ( task ) ;
if ( ( task - > t . regs . eax & 0xffff ) ! = 0x004f | | err )
return - EINVAL ;
if ( ( task - > t . regs . ebx & 0x3 ) = = 3 ) {
printk ( KERN_INFO " uvesafb: VBIOS/hardware supports both "
" DDC1 and DDC2 transfers \n " ) ;
} else if ( ( task - > t . regs . ebx & 0x3 ) = = 2 ) {
printk ( KERN_INFO " uvesafb: VBIOS/hardware supports DDC2 "
" transfers \n " ) ;
} else if ( ( task - > t . regs . ebx & 0x3 ) = = 1 ) {
printk ( KERN_INFO " uvesafb: VBIOS/hardware supports DDC1 "
" transfers \n " ) ;
} else {
printk ( KERN_INFO " uvesafb: VBIOS/hardware doesn't support "
" DDC transfers \n " ) ;
return - EINVAL ;
}
task - > t . regs . eax = 0x4f15 ;
task - > t . regs . ebx = 1 ;
task - > t . regs . ecx = task - > t . regs . edx = 0 ;
task - > t . flags = TF_BUF_RET | TF_BUF_ESDI ;
task - > t . buf_len = EDID_LENGTH ;
task - > buf = kzalloc ( EDID_LENGTH , GFP_KERNEL ) ;
err = uvesafb_exec ( task ) ;
if ( ( task - > t . regs . eax & 0xffff ) = = 0x004f & & ! err ) {
fb_edid_to_monspecs ( task - > buf , & info - > monspecs ) ;
if ( info - > monspecs . vfmax & & info - > monspecs . hfmax ) {
/*
* If the maximum pixel clock wasn ' t specified in
* the EDID block , set it to 300 MHz .
*/
if ( info - > monspecs . dclkmax = = 0 )
info - > monspecs . dclkmax = 300 * 1000000 ;
info - > monspecs . gtf = 1 ;
}
} else {
err = - EINVAL ;
}
kfree ( task - > buf ) ;
return err ;
}
static void __devinit uvesafb_vbe_getmonspecs ( struct uvesafb_ktask * task ,
struct fb_info * info )
{
struct uvesafb_par * par = info - > par ;
int i ;
memset ( & info - > monspecs , 0 , sizeof ( info - > monspecs ) ) ;
/*
* If we don ' t get all necessary data from the EDID block ,
* mark it as incompatible with the GTF and set nocrtc so
* that we always use the default BIOS refresh rate .
*/
if ( uvesafb_vbe_getedid ( task , info ) ) {
info - > monspecs . gtf = 0 ;
par - > nocrtc = 1 ;
}
/* Kernel command line overrides. */
if ( maxclk )
info - > monspecs . dclkmax = maxclk * 1000000 ;
if ( maxvf )
info - > monspecs . vfmax = maxvf ;
if ( maxhf )
info - > monspecs . hfmax = maxhf * 1000 ;
/*
* In case DDC transfers are not supported , the user can provide
* monitor limits manually . Lower limits are set to " safe " values .
*/
if ( info - > monspecs . gtf = = 0 & & maxclk & & maxvf & & maxhf ) {
info - > monspecs . dclkmin = 0 ;
info - > monspecs . vfmin = 60 ;
info - > monspecs . hfmin = 29000 ;
info - > monspecs . gtf = 1 ;
par - > nocrtc = 0 ;
}
if ( info - > monspecs . gtf )
printk ( KERN_INFO
" uvesafb: monitor limits: vf = %d Hz, hf = %d kHz, "
" clk = %d MHz \n " , info - > monspecs . vfmax ,
( int ) ( info - > monspecs . hfmax / 1000 ) ,
( int ) ( info - > monspecs . dclkmax / 1000000 ) ) ;
else
printk ( KERN_INFO " uvesafb: no monitor limits have been set, "
" default refresh rate will be used \n " ) ;
/* Add VBE modes to the modelist. */
for ( i = 0 ; i < par - > vbe_modes_cnt ; i + + ) {
struct fb_var_screeninfo var ;
struct vbe_mode_ib * mode ;
struct fb_videomode vmode ;
mode = & par - > vbe_modes [ i ] ;
memset ( & var , 0 , sizeof ( var ) ) ;
var . xres = mode - > x_res ;
var . yres = mode - > y_res ;
fb_get_mode ( FB_VSYNCTIMINGS | FB_IGNOREMON , 60 , & var , info ) ;
fb_var_to_videomode ( & vmode , & var ) ;
fb_add_videomode ( & vmode , & info - > modelist ) ;
}
/* Add valid VESA modes to our modelist. */
for ( i = 0 ; i < VESA_MODEDB_SIZE ; i + + ) {
if ( uvesafb_is_valid_mode ( ( struct fb_videomode * )
& vesa_modes [ i ] , info ) )
fb_add_videomode ( & vesa_modes [ i ] , & info - > modelist ) ;
}
for ( i = 0 ; i < info - > monspecs . modedb_len ; i + + ) {
if ( uvesafb_is_valid_mode ( & info - > monspecs . modedb [ i ] , info ) )
fb_add_videomode ( & info - > monspecs . modedb [ i ] ,
& info - > modelist ) ;
}
return ;
}
static void __devinit uvesafb_vbe_getstatesize ( struct uvesafb_ktask * task ,
struct uvesafb_par * par )
{
int err ;
uvesafb_reset ( task ) ;
/*
* Get the VBE state buffer size . We want all available
* hardware state data ( CL = 0x0f ) .
*/
task - > t . regs . eax = 0x4f04 ;
task - > t . regs . ecx = 0x000f ;
task - > t . regs . edx = 0x0000 ;
task - > t . flags = 0 ;
err = uvesafb_exec ( task ) ;
if ( err | | ( task - > t . regs . eax & 0xffff ) ! = 0x004f ) {
printk ( KERN_WARNING " uvesafb: VBE state buffer size "
" cannot be determined (eax=0x%x, err=%d) \n " ,
task - > t . regs . eax , err ) ;
par - > vbe_state_size = 0 ;
return ;
}
par - > vbe_state_size = 64 * ( task - > t . regs . ebx & 0xffff ) ;
}
static int __devinit uvesafb_vbe_init ( struct fb_info * info )
{
struct uvesafb_ktask * task = NULL ;
struct uvesafb_par * par = info - > par ;
int err ;
task = uvesafb_prep ( ) ;
if ( ! task )
return - ENOMEM ;
err = uvesafb_vbe_getinfo ( task , par ) ;
if ( err )
goto out ;
err = uvesafb_vbe_getmodes ( task , par ) ;
if ( err )
goto out ;
par - > nocrtc = nocrtc ;
# ifdef CONFIG_X86_32
par - > pmi_setpal = pmi_setpal ;
par - > ypan = ypan ;
if ( par - > pmi_setpal | | par - > ypan )
uvesafb_vbe_getpmi ( task , par ) ;
# else
/* The protected mode interface is not available on non-x86. */
par - > pmi_setpal = par - > ypan = 0 ;
# endif
INIT_LIST_HEAD ( & info - > modelist ) ;
uvesafb_vbe_getmonspecs ( task , info ) ;
uvesafb_vbe_getstatesize ( task , par ) ;
out : uvesafb_free ( task ) ;
return err ;
}
static int __devinit uvesafb_vbe_init_mode ( struct fb_info * info )
{
struct list_head * pos ;
struct fb_modelist * modelist ;
struct fb_videomode * mode ;
struct uvesafb_par * par = info - > par ;
int i , modeid ;
/* Has the user requested a specific VESA mode? */
if ( vbemode ) {
for ( i = 0 ; i < par - > vbe_modes_cnt ; i + + ) {
if ( par - > vbe_modes [ i ] . mode_id = = vbemode ) {
fb_get_mode ( FB_VSYNCTIMINGS | FB_IGNOREMON , 60 ,
& info - > var , info ) ;
/*
* With pixclock set to 0 , the default BIOS
* timings will be used in set_par ( ) .
*/
info - > var . pixclock = 0 ;
modeid = i ;
goto gotmode ;
}
}
printk ( KERN_INFO " uvesafb: requested VBE mode 0x%x is "
" unavailable \n " , vbemode ) ;
vbemode = 0 ;
}
/* Count the modes in the modelist */
i = 0 ;
list_for_each ( pos , & info - > modelist )
i + + ;
/*
* Convert the modelist into a modedb so that we can use it with
* fb_find_mode ( ) .
*/
mode = kzalloc ( i * sizeof ( * mode ) , GFP_KERNEL ) ;
if ( mode ) {
i = 0 ;
list_for_each ( pos , & info - > modelist ) {
modelist = list_entry ( pos , struct fb_modelist , list ) ;
mode [ i ] = modelist - > mode ;
i + + ;
}
if ( ! mode_option )
mode_option = UVESAFB_DEFAULT_MODE ;
i = fb_find_mode ( & info - > var , info , mode_option , mode , i ,
NULL , 8 ) ;
kfree ( mode ) ;
}
/* fb_find_mode() failed */
2008-04-28 13:15:15 +04:00
if ( i = = 0 ) {
2007-10-16 12:28:26 +04:00
info - > var . xres = 640 ;
info - > var . yres = 480 ;
mode = ( struct fb_videomode * )
fb_find_best_mode ( & info - > var , & info - > modelist ) ;
if ( mode ) {
fb_videomode_to_var ( & info - > var , mode ) ;
} else {
modeid = par - > vbe_modes [ 0 ] . mode_id ;
fb_get_mode ( FB_VSYNCTIMINGS | FB_IGNOREMON , 60 ,
& info - > var , info ) ;
goto gotmode ;
}
}
/* Look for a matching VBE mode. */
modeid = uvesafb_vbe_find_mode ( par , info - > var . xres , info - > var . yres ,
info - > var . bits_per_pixel , UVESAFB_EXACT_RES ) ;
if ( modeid = = - 1 )
return - EINVAL ;
gotmode :
uvesafb_setup_var ( & info - > var , info , & par - > vbe_modes [ modeid ] ) ;
/*
* If we are not VBE3 .0 + compliant , we ' re done - - the BIOS will
* ignore our timings anyway .
*/
if ( par - > vbe_ib . vbe_version < 0x0300 | | par - > nocrtc )
fb_get_mode ( FB_VSYNCTIMINGS | FB_IGNOREMON , 60 ,
& info - > var , info ) ;
return modeid ;
}
static int uvesafb_setpalette ( struct uvesafb_pal_entry * entries , int count ,
int start , struct fb_info * info )
{
struct uvesafb_ktask * task ;
2007-11-15 03:58:47 +03:00
# ifdef CONFIG_X86
2007-10-16 12:28:26 +04:00
struct uvesafb_par * par = info - > par ;
int i = par - > mode_idx ;
2007-11-15 03:58:47 +03:00
# endif
2007-10-16 12:28:26 +04:00
int err = 0 ;
/*
* We support palette modifications for 8 bpp modes only , so
* there can never be more than 256 entries .
*/
if ( start + count > 256 )
return - EINVAL ;
# ifdef CONFIG_X86
/* Use VGA registers if mode is VGA-compatible. */
if ( i > = 0 & & i < par - > vbe_modes_cnt & &
par - > vbe_modes [ i ] . mode_attr & VBE_MODE_VGACOMPAT ) {
for ( i = 0 ; i < count ; i + + ) {
outb_p ( start + i , dac_reg ) ;
outb_p ( entries [ i ] . red , dac_val ) ;
outb_p ( entries [ i ] . green , dac_val ) ;
outb_p ( entries [ i ] . blue , dac_val ) ;
}
}
# ifdef CONFIG_X86_32
else if ( par - > pmi_setpal ) {
__asm__ __volatile__ (
" call *(%%esi) "
: /* no return value */
: " a " ( 0x4f09 ) , /* EAX */
" b " ( 0 ) , /* EBX */
" c " ( count ) , /* ECX */
" d " ( start ) , /* EDX */
" D " ( entries ) , /* EDI */
" S " ( & par - > pmi_pal ) ) ; /* ESI */
}
# endif /* CONFIG_X86_32 */
else
# endif /* CONFIG_X86 */
{
task = uvesafb_prep ( ) ;
if ( ! task )
return - ENOMEM ;
task - > t . regs . eax = 0x4f09 ;
task - > t . regs . ebx = 0x0 ;
task - > t . regs . ecx = count ;
task - > t . regs . edx = start ;
task - > t . flags = TF_BUF_ESDI ;
task - > t . buf_len = sizeof ( struct uvesafb_pal_entry ) * count ;
task - > buf = entries ;
err = uvesafb_exec ( task ) ;
if ( ( task - > t . regs . eax & 0xffff ) ! = 0x004f )
err = 1 ;
uvesafb_free ( task ) ;
}
return err ;
}
static int uvesafb_setcolreg ( unsigned regno , unsigned red , unsigned green ,
unsigned blue , unsigned transp ,
struct fb_info * info )
{
struct uvesafb_pal_entry entry ;
int shift = 16 - info - > var . green . length ;
int err = 0 ;
if ( regno > = info - > cmap . len )
return - EINVAL ;
if ( info - > var . bits_per_pixel = = 8 ) {
entry . red = red > > shift ;
entry . green = green > > shift ;
entry . blue = blue > > shift ;
entry . pad = 0 ;
err = uvesafb_setpalette ( & entry , 1 , regno , info ) ;
} else if ( regno < 16 ) {
switch ( info - > var . bits_per_pixel ) {
case 16 :
if ( info - > var . red . offset = = 10 ) {
/* 1:5:5:5 */
( ( u32 * ) ( info - > pseudo_palette ) ) [ regno ] =
( ( red & 0xf800 ) > > 1 ) |
( ( green & 0xf800 ) > > 6 ) |
( ( blue & 0xf800 ) > > 11 ) ;
} else {
/* 0:5:6:5 */
( ( u32 * ) ( info - > pseudo_palette ) ) [ regno ] =
( ( red & 0xf800 ) ) |
( ( green & 0xfc00 ) > > 5 ) |
( ( blue & 0xf800 ) > > 11 ) ;
}
break ;
case 24 :
case 32 :
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 ) ;
break ;
}
}
return err ;
}
static int uvesafb_setcmap ( struct fb_cmap * cmap , struct fb_info * info )
{
struct uvesafb_pal_entry * entries ;
int shift = 16 - info - > var . green . length ;
int i , err = 0 ;
if ( info - > var . bits_per_pixel = = 8 ) {
if ( cmap - > start + cmap - > len > info - > cmap . start +
info - > cmap . len | | cmap - > start < info - > cmap . start )
return - EINVAL ;
entries = kmalloc ( sizeof ( * entries ) * cmap - > len , GFP_KERNEL ) ;
if ( ! entries )
return - ENOMEM ;
for ( i = 0 ; i < cmap - > len ; i + + ) {
entries [ i ] . red = cmap - > red [ i ] > > shift ;
entries [ i ] . green = cmap - > green [ i ] > > shift ;
entries [ i ] . blue = cmap - > blue [ i ] > > shift ;
entries [ i ] . pad = 0 ;
}
err = uvesafb_setpalette ( entries , cmap - > len , cmap - > start , info ) ;
kfree ( entries ) ;
} else {
/*
* For modes with bpp > 8 , we only set the pseudo palette in
* the fb_info struct . We rely on uvesafb_setcolreg to do all
* sanity checking .
*/
for ( i = 0 ; i < cmap - > len ; i + + ) {
err | = uvesafb_setcolreg ( cmap - > start + i , cmap - > red [ i ] ,
cmap - > green [ i ] , cmap - > blue [ i ] ,
0 , info ) ;
}
}
return err ;
}
static int uvesafb_pan_display ( struct fb_var_screeninfo * var ,
struct fb_info * info )
{
# ifdef CONFIG_X86_32
int offset ;
struct uvesafb_par * par = info - > par ;
offset = ( var - > yoffset * info - > fix . line_length + var - > xoffset ) / 4 ;
/*
* It turns out it ' s not the best idea to do panning via vm86 ,
* so we only allow it if we have a PMI .
*/
if ( par - > pmi_start ) {
__asm__ __volatile__ (
" call *(%%edi) "
: /* no return value */
: " a " ( 0x4f07 ) , /* EAX */
" b " ( 0 ) , /* EBX */
" c " ( offset ) , /* ECX */
" d " ( offset > > 16 ) , /* EDX */
" D " ( & par - > pmi_start ) ) ; /* EDI */
}
# endif
return 0 ;
}
static int uvesafb_blank ( int blank , struct fb_info * info )
{
struct uvesafb_ktask * task ;
int err = 1 ;
# ifdef CONFIG_X86
2007-11-15 03:58:47 +03:00
struct uvesafb_par * par = info - > par ;
2007-10-16 12:28:26 +04:00
if ( par - > vbe_ib . capabilities & VBE_CAP_VGACOMPAT ) {
int loop = 10000 ;
u8 seq = 0 , crtc17 = 0 ;
if ( blank = = FB_BLANK_POWERDOWN ) {
seq = 0x20 ;
crtc17 = 0x00 ;
err = 0 ;
} else {
seq = 0x00 ;
crtc17 = 0x80 ;
err = ( blank = = FB_BLANK_UNBLANK ) ? 0 : - EINVAL ;
}
vga_wseq ( NULL , 0x00 , 0x01 ) ;
seq | = vga_rseq ( NULL , 0x01 ) & ~ 0x20 ;
vga_wseq ( NULL , 0x00 , seq ) ;
crtc17 | = vga_rcrt ( NULL , 0x17 ) & ~ 0x80 ;
while ( loop - - ) ;
vga_wcrt ( NULL , 0x17 , crtc17 ) ;
vga_wseq ( NULL , 0x00 , 0x03 ) ;
} else
# endif /* CONFIG_X86 */
{
task = uvesafb_prep ( ) ;
if ( ! task )
return - ENOMEM ;
task - > t . regs . eax = 0x4f10 ;
switch ( blank ) {
case FB_BLANK_UNBLANK :
task - > t . regs . ebx = 0x0001 ;
break ;
case FB_BLANK_NORMAL :
task - > t . regs . ebx = 0x0101 ; /* standby */
break ;
case FB_BLANK_POWERDOWN :
task - > t . regs . ebx = 0x0401 ; /* powerdown */
break ;
default :
goto out ;
}
err = uvesafb_exec ( task ) ;
if ( err | | ( task - > t . regs . eax & 0xffff ) ! = 0x004f )
err = 1 ;
out : uvesafb_free ( task ) ;
}
return err ;
}
static int uvesafb_open ( struct fb_info * info , int user )
{
struct uvesafb_par * par = info - > par ;
int cnt = atomic_read ( & par - > ref_count ) ;
if ( ! cnt & & par - > vbe_state_size )
par - > vbe_state_orig = uvesafb_vbe_state_save ( par ) ;
atomic_inc ( & par - > ref_count ) ;
return 0 ;
}
static int uvesafb_release ( struct fb_info * info , int user )
{
struct uvesafb_ktask * task = NULL ;
struct uvesafb_par * par = info - > par ;
int cnt = atomic_read ( & par - > ref_count ) ;
if ( ! cnt )
return - EINVAL ;
if ( cnt ! = 1 )
goto out ;
task = uvesafb_prep ( ) ;
if ( ! task )
goto out ;
/* First, try to set the standard 80x25 text mode. */
task - > t . regs . eax = 0x0003 ;
uvesafb_exec ( task ) ;
/*
* Now try to restore whatever hardware state we might have
* saved when the fb device was first opened .
*/
uvesafb_vbe_state_restore ( par , par - > vbe_state_orig ) ;
out :
atomic_dec ( & par - > ref_count ) ;
if ( task )
uvesafb_free ( task ) ;
return 0 ;
}
static int uvesafb_set_par ( struct fb_info * info )
{
struct uvesafb_par * par = info - > par ;
struct uvesafb_ktask * task = NULL ;
struct vbe_crtc_ib * crtc = NULL ;
struct vbe_mode_ib * mode = NULL ;
int i , err = 0 , depth = info - > var . bits_per_pixel ;
if ( depth > 8 & & depth ! = 32 )
depth = info - > var . red . length + info - > var . green . length +
info - > var . blue . length ;
i = uvesafb_vbe_find_mode ( par , info - > var . xres , info - > var . yres , depth ,
UVESAFB_EXACT_RES | UVESAFB_EXACT_DEPTH ) ;
if ( i > = 0 )
mode = & par - > vbe_modes [ i ] ;
else
return - EINVAL ;
task = uvesafb_prep ( ) ;
if ( ! task )
return - ENOMEM ;
setmode :
task - > t . regs . eax = 0x4f02 ;
task - > t . regs . ebx = mode - > mode_id | 0x4000 ; /* use LFB */
if ( par - > vbe_ib . vbe_version > = 0x0300 & & ! par - > nocrtc & &
info - > var . pixclock ! = 0 ) {
task - > t . regs . ebx | = 0x0800 ; /* use CRTC data */
task - > t . flags = TF_BUF_ESDI ;
crtc = kzalloc ( sizeof ( struct vbe_crtc_ib ) , GFP_KERNEL ) ;
if ( ! crtc ) {
err = - ENOMEM ;
goto out ;
}
crtc - > horiz_start = info - > var . xres + info - > var . right_margin ;
crtc - > horiz_end = crtc - > horiz_start + info - > var . hsync_len ;
crtc - > horiz_total = crtc - > horiz_end + info - > var . left_margin ;
crtc - > vert_start = info - > var . yres + info - > var . lower_margin ;
crtc - > vert_end = crtc - > vert_start + info - > var . vsync_len ;
crtc - > vert_total = crtc - > vert_end + info - > var . upper_margin ;
crtc - > pixel_clock = PICOS2KHZ ( info - > var . pixclock ) * 1000 ;
crtc - > refresh_rate = ( u16 ) ( 100 * ( crtc - > pixel_clock /
( crtc - > vert_total * crtc - > horiz_total ) ) ) ;
if ( info - > var . vmode & FB_VMODE_DOUBLE )
crtc - > flags | = 0x1 ;
if ( info - > var . vmode & FB_VMODE_INTERLACED )
crtc - > flags | = 0x2 ;
if ( ! ( info - > var . sync & FB_SYNC_HOR_HIGH_ACT ) )
crtc - > flags | = 0x4 ;
if ( ! ( info - > var . sync & FB_SYNC_VERT_HIGH_ACT ) )
crtc - > flags | = 0x8 ;
memcpy ( & par - > crtc , crtc , sizeof ( * crtc ) ) ;
} else {
memset ( & par - > crtc , 0 , sizeof ( * crtc ) ) ;
}
task - > t . buf_len = sizeof ( struct vbe_crtc_ib ) ;
task - > buf = & par - > crtc ;
err = uvesafb_exec ( task ) ;
if ( err | | ( task - > t . regs . eax & 0xffff ) ! = 0x004f ) {
/*
* The mode switch might have failed because we tried to
* use our own timings . Try again with the default timings .
*/
if ( crtc ! = NULL ) {
printk ( KERN_WARNING " uvesafb: mode switch failed "
" (eax=0x%x, err=%d). Trying again with "
" default timings. \n " , task - > t . regs . eax , err ) ;
uvesafb_reset ( task ) ;
kfree ( crtc ) ;
crtc = NULL ;
info - > var . pixclock = 0 ;
goto setmode ;
} else {
printk ( KERN_ERR " uvesafb: mode switch failed (eax= "
" 0x%x, err=%d) \n " , task - > t . regs . eax , err ) ;
err = - EINVAL ;
goto out ;
}
}
par - > mode_idx = i ;
/* For 8bpp modes, always try to set the DAC to 8 bits. */
if ( par - > vbe_ib . capabilities & VBE_CAP_CAN_SWITCH_DAC & &
mode - > bits_per_pixel < = 8 ) {
uvesafb_reset ( task ) ;
task - > t . regs . eax = 0x4f08 ;
task - > t . regs . ebx = 0x0800 ;
err = uvesafb_exec ( task ) ;
if ( err | | ( task - > t . regs . eax & 0xffff ) ! = 0x004f | |
( ( task - > t . regs . ebx & 0xff00 ) > > 8 ) ! = 8 ) {
/*
* We ' ve failed to set the DAC palette format -
* time to correct var .
*/
info - > var . red . length = 6 ;
info - > var . green . length = 6 ;
info - > var . blue . length = 6 ;
}
}
info - > fix . visual = ( info - > var . bits_per_pixel = = 8 ) ?
FB_VISUAL_PSEUDOCOLOR : FB_VISUAL_TRUECOLOR ;
info - > fix . line_length = mode - > bytes_per_scan_line ;
out : if ( crtc ! = NULL )
kfree ( crtc ) ;
uvesafb_free ( task ) ;
return err ;
}
static void uvesafb_check_limits ( struct fb_var_screeninfo * var ,
struct fb_info * info )
{
const struct fb_videomode * mode ;
struct uvesafb_par * par = info - > par ;
/*
* If pixclock is set to 0 , then we ' re using default BIOS timings
* and thus don ' t have to perform any checks here .
*/
if ( ! var - > pixclock )
return ;
if ( par - > vbe_ib . vbe_version < 0x0300 ) {
fb_get_mode ( FB_VSYNCTIMINGS | FB_IGNOREMON , 60 , var , info ) ;
return ;
}
if ( ! fb_validate_mode ( var , info ) )
return ;
mode = fb_find_best_mode ( var , & info - > modelist ) ;
if ( mode ) {
if ( mode - > xres = = var - > xres & & mode - > yres = = var - > yres & &
! ( mode - > vmode & ( FB_VMODE_INTERLACED | FB_VMODE_DOUBLE ) ) ) {
fb_videomode_to_var ( var , mode ) ;
return ;
}
}
if ( info - > monspecs . gtf & & ! fb_get_mode ( FB_MAXTIMINGS , 0 , var , info ) )
return ;
/* Use default refresh rate */
var - > pixclock = 0 ;
}
static int uvesafb_check_var ( struct fb_var_screeninfo * var ,
struct fb_info * info )
{
struct uvesafb_par * par = info - > par ;
struct vbe_mode_ib * mode = NULL ;
int match = - 1 ;
int depth = var - > red . length + var - > green . length + var - > blue . length ;
/*
* Various apps will use bits_per_pixel to set the color depth ,
* which is theoretically incorrect , but which we ' ll try to handle
* here .
*/
if ( depth = = 0 | | abs ( depth - var - > bits_per_pixel ) > = 8 )
depth = var - > bits_per_pixel ;
match = uvesafb_vbe_find_mode ( par , var - > xres , var - > yres , depth ,
UVESAFB_EXACT_RES ) ;
if ( match = = - 1 )
return - EINVAL ;
mode = & par - > vbe_modes [ match ] ;
uvesafb_setup_var ( var , info , mode ) ;
/*
* Check whether we have remapped enough memory for this mode .
* We might be called at an early stage , when we haven ' t remapped
* any memory yet , in which case we simply skip the check .
*/
if ( var - > yres * mode - > bytes_per_scan_line > info - > fix . smem_len
& & info - > fix . smem_len )
return - EINVAL ;
if ( ( var - > vmode & FB_VMODE_DOUBLE ) & &
! ( par - > vbe_modes [ match ] . mode_attr & 0x100 ) )
var - > vmode & = ~ FB_VMODE_DOUBLE ;
if ( ( var - > vmode & FB_VMODE_INTERLACED ) & &
! ( par - > vbe_modes [ match ] . mode_attr & 0x200 ) )
var - > vmode & = ~ FB_VMODE_INTERLACED ;
uvesafb_check_limits ( var , info ) ;
var - > xres_virtual = var - > xres ;
var - > yres_virtual = ( par - > ypan ) ?
info - > fix . smem_len / mode - > bytes_per_scan_line :
var - > yres ;
return 0 ;
}
static void uvesafb_save_state ( struct fb_info * info )
{
struct uvesafb_par * par = info - > par ;
if ( par - > vbe_state_saved )
kfree ( par - > vbe_state_saved ) ;
par - > vbe_state_saved = uvesafb_vbe_state_save ( par ) ;
}
static void uvesafb_restore_state ( struct fb_info * info )
{
struct uvesafb_par * par = info - > par ;
uvesafb_vbe_state_restore ( par , par - > vbe_state_saved ) ;
}
static struct fb_ops uvesafb_ops = {
. owner = THIS_MODULE ,
. fb_open = uvesafb_open ,
. fb_release = uvesafb_release ,
. fb_setcolreg = uvesafb_setcolreg ,
. fb_setcmap = uvesafb_setcmap ,
. fb_pan_display = uvesafb_pan_display ,
. fb_blank = uvesafb_blank ,
. fb_fillrect = cfb_fillrect ,
. fb_copyarea = cfb_copyarea ,
. fb_imageblit = cfb_imageblit ,
. fb_check_var = uvesafb_check_var ,
. fb_set_par = uvesafb_set_par ,
. fb_save_state = uvesafb_save_state ,
. fb_restore_state = uvesafb_restore_state ,
} ;
static void __devinit uvesafb_init_info ( struct fb_info * info ,
struct vbe_mode_ib * mode )
{
unsigned int size_vmode ;
unsigned int size_remap ;
unsigned int size_total ;
struct uvesafb_par * par = info - > par ;
int i , h ;
info - > pseudo_palette = ( ( u8 * ) info - > par + sizeof ( struct uvesafb_par ) ) ;
info - > fix = uvesafb_fix ;
info - > fix . ypanstep = par - > ypan ? 1 : 0 ;
info - > fix . ywrapstep = ( par - > ypan > 1 ) ? 1 : 0 ;
/*
* If we were unable to get the state buffer size , disable
* functions for saving and restoring the hardware state .
*/
if ( par - > vbe_state_size = = 0 ) {
info - > fbops - > fb_save_state = NULL ;
info - > fbops - > fb_restore_state = NULL ;
}
/* Disable blanking if the user requested so. */
if ( ! blank )
info - > fbops - > fb_blank = NULL ;
/*
* Find out how much IO memory is required for the mode with
* the highest resolution .
*/
size_remap = 0 ;
for ( i = 0 ; i < par - > vbe_modes_cnt ; i + + ) {
h = par - > vbe_modes [ i ] . bytes_per_scan_line *
par - > vbe_modes [ i ] . y_res ;
if ( h > size_remap )
size_remap = h ;
}
size_remap * = 2 ;
/*
* size_vmode - - that is the amount of memory needed for the
* used video mode , i . e . the minimum amount of
* memory we need .
*/
if ( mode ! = NULL ) {
size_vmode = info - > var . yres * mode - > bytes_per_scan_line ;
} else {
size_vmode = info - > var . yres * info - > var . xres *
( ( info - > var . bits_per_pixel + 7 ) > > 3 ) ;
}
/*
* size_total - - all video memory we have . Used for mtrr
* entries , resource allocation and bounds
* checking .
*/
size_total = par - > vbe_ib . total_memory * 65536 ;
if ( vram_total )
size_total = vram_total * 1024 * 1024 ;
if ( size_total < size_vmode )
size_total = size_vmode ;
/*
* size_remap - - the amount of video memory we are going to
* use for vesafb . With modern cards it is no
* option to simply use size_total as th
* wastes plenty of kernel address space .
*/
if ( vram_remap )
size_remap = vram_remap * 1024 * 1024 ;
if ( size_remap < size_vmode )
size_remap = size_vmode ;
if ( size_remap > size_total )
size_remap = size_total ;
info - > fix . smem_len = size_remap ;
info - > fix . smem_start = mode - > phys_base_ptr ;
/*
* We have to set yres_virtual here because when setup_var ( ) was
* called , smem_len wasn ' t defined yet .
*/
info - > var . yres_virtual = info - > fix . smem_len /
mode - > bytes_per_scan_line ;
if ( par - > ypan & & info - > var . yres_virtual > info - > var . yres ) {
printk ( KERN_INFO " uvesafb: scrolling: %s "
" using protected mode interface, "
" yres_virtual=%d \n " ,
( par - > ypan > 1 ) ? " ywrap " : " ypan " ,
info - > var . yres_virtual ) ;
} else {
printk ( KERN_INFO " uvesafb: scrolling: redraw \n " ) ;
info - > var . yres_virtual = info - > var . yres ;
par - > ypan = 0 ;
}
info - > flags = FBINFO_FLAG_DEFAULT |
( par - > ypan ) ? FBINFO_HWACCEL_YPAN : 0 ;
if ( ! par - > ypan )
info - > fbops - > fb_pan_display = NULL ;
}
2008-01-14 11:55:24 +03:00
static void __devinit uvesafb_init_mtrr ( struct fb_info * info )
2007-10-16 12:28:26 +04:00
{
# ifdef CONFIG_MTRR
if ( mtrr & & ! ( info - > fix . smem_start & ( PAGE_SIZE - 1 ) ) ) {
int temp_size = info - > fix . smem_len ;
unsigned int type = 0 ;
switch ( mtrr ) {
case 1 :
type = MTRR_TYPE_UNCACHABLE ;
break ;
case 2 :
type = MTRR_TYPE_WRBACK ;
break ;
case 3 :
type = MTRR_TYPE_WRCOMB ;
break ;
case 4 :
type = MTRR_TYPE_WRTHROUGH ;
break ;
default :
type = 0 ;
break ;
}
if ( type ) {
int rc ;
/* Find the largest power-of-two */
while ( temp_size & ( temp_size - 1 ) )
temp_size & = ( temp_size - 1 ) ;
/* Try and find a power of two to add */
do {
rc = mtrr_add ( info - > fix . smem_start ,
temp_size , type , 1 ) ;
temp_size > > = 1 ;
} while ( temp_size > = PAGE_SIZE & & rc = = - EINVAL ) ;
}
}
# endif /* CONFIG_MTRR */
}
static ssize_t uvesafb_show_vbe_ver ( struct device * dev ,
struct device_attribute * attr , char * buf )
{
struct fb_info * info = platform_get_drvdata ( to_platform_device ( dev ) ) ;
struct uvesafb_par * par = info - > par ;
return snprintf ( buf , PAGE_SIZE , " %.4x \n " , par - > vbe_ib . vbe_version ) ;
}
static DEVICE_ATTR ( vbe_version , S_IRUGO , uvesafb_show_vbe_ver , NULL ) ;
static ssize_t uvesafb_show_vbe_modes ( struct device * dev ,
struct device_attribute * attr , char * buf )
{
struct fb_info * info = platform_get_drvdata ( to_platform_device ( dev ) ) ;
struct uvesafb_par * par = info - > par ;
int ret = 0 , i ;
for ( i = 0 ; i < par - > vbe_modes_cnt & & ret < PAGE_SIZE ; i + + ) {
ret + = snprintf ( buf + ret , PAGE_SIZE - ret ,
" %dx%d-%d, 0x%.4x \n " ,
par - > vbe_modes [ i ] . x_res , par - > vbe_modes [ i ] . y_res ,
par - > vbe_modes [ i ] . depth , par - > vbe_modes [ i ] . mode_id ) ;
}
return ret ;
}
static DEVICE_ATTR ( vbe_modes , S_IRUGO , uvesafb_show_vbe_modes , NULL ) ;
static ssize_t uvesafb_show_vendor ( struct device * dev ,
struct device_attribute * attr , char * buf )
{
struct fb_info * info = platform_get_drvdata ( to_platform_device ( dev ) ) ;
struct uvesafb_par * par = info - > par ;
if ( par - > vbe_ib . oem_vendor_name_ptr )
return snprintf ( buf , PAGE_SIZE , " %s \n " , ( char * )
( & par - > vbe_ib ) + par - > vbe_ib . oem_vendor_name_ptr ) ;
else
return 0 ;
}
static DEVICE_ATTR ( oem_vendor , S_IRUGO , uvesafb_show_vendor , NULL ) ;
static ssize_t uvesafb_show_product_name ( struct device * dev ,
struct device_attribute * attr , char * buf )
{
struct fb_info * info = platform_get_drvdata ( to_platform_device ( dev ) ) ;
struct uvesafb_par * par = info - > par ;
if ( par - > vbe_ib . oem_product_name_ptr )
return snprintf ( buf , PAGE_SIZE , " %s \n " , ( char * )
( & par - > vbe_ib ) + par - > vbe_ib . oem_product_name_ptr ) ;
else
return 0 ;
}
static DEVICE_ATTR ( oem_product_name , S_IRUGO , uvesafb_show_product_name , NULL ) ;
static ssize_t uvesafb_show_product_rev ( struct device * dev ,
struct device_attribute * attr , char * buf )
{
struct fb_info * info = platform_get_drvdata ( to_platform_device ( dev ) ) ;
struct uvesafb_par * par = info - > par ;
if ( par - > vbe_ib . oem_product_rev_ptr )
return snprintf ( buf , PAGE_SIZE , " %s \n " , ( char * )
( & par - > vbe_ib ) + par - > vbe_ib . oem_product_rev_ptr ) ;
else
return 0 ;
}
static DEVICE_ATTR ( oem_product_rev , S_IRUGO , uvesafb_show_product_rev , NULL ) ;
static ssize_t uvesafb_show_oem_string ( struct device * dev ,
struct device_attribute * attr , char * buf )
{
struct fb_info * info = platform_get_drvdata ( to_platform_device ( dev ) ) ;
struct uvesafb_par * par = info - > par ;
if ( par - > vbe_ib . oem_string_ptr )
return snprintf ( buf , PAGE_SIZE , " %s \n " ,
( char * ) ( & par - > vbe_ib ) + par - > vbe_ib . oem_string_ptr ) ;
else
return 0 ;
}
static DEVICE_ATTR ( oem_string , S_IRUGO , uvesafb_show_oem_string , NULL ) ;
static ssize_t uvesafb_show_nocrtc ( struct device * dev ,
struct device_attribute * attr , char * buf )
{
struct fb_info * info = platform_get_drvdata ( to_platform_device ( dev ) ) ;
struct uvesafb_par * par = info - > par ;
return snprintf ( buf , PAGE_SIZE , " %d \n " , par - > nocrtc ) ;
}
static ssize_t uvesafb_store_nocrtc ( struct device * dev ,
struct device_attribute * attr , const char * buf , size_t count )
{
struct fb_info * info = platform_get_drvdata ( to_platform_device ( dev ) ) ;
struct uvesafb_par * par = info - > par ;
if ( count > 0 ) {
if ( buf [ 0 ] = = ' 0 ' )
par - > nocrtc = 0 ;
else
par - > nocrtc = 1 ;
}
return count ;
}
static DEVICE_ATTR ( nocrtc , S_IRUGO | S_IWUSR , uvesafb_show_nocrtc ,
uvesafb_store_nocrtc ) ;
static struct attribute * uvesafb_dev_attrs [ ] = {
& dev_attr_vbe_version . attr ,
& dev_attr_vbe_modes . attr ,
& dev_attr_oem_vendor . attr ,
& dev_attr_oem_product_name . attr ,
& dev_attr_oem_product_rev . attr ,
& dev_attr_oem_string . attr ,
& dev_attr_nocrtc . attr ,
NULL ,
} ;
static struct attribute_group uvesafb_dev_attgrp = {
. name = NULL ,
. attrs = uvesafb_dev_attrs ,
} ;
static int __devinit uvesafb_probe ( struct platform_device * dev )
{
struct fb_info * info ;
struct vbe_mode_ib * mode = NULL ;
struct uvesafb_par * par ;
int err = 0 , i ;
info = framebuffer_alloc ( sizeof ( * par ) + sizeof ( u32 ) * 256 , & dev - > dev ) ;
if ( ! info )
return - ENOMEM ;
par = info - > par ;
err = uvesafb_vbe_init ( info ) ;
if ( err ) {
printk ( KERN_ERR " uvesafb: vbe_init() failed with %d \n " , err ) ;
goto out ;
}
info - > fbops = & uvesafb_ops ;
i = uvesafb_vbe_init_mode ( info ) ;
if ( i < 0 ) {
err = - EINVAL ;
goto out ;
} else {
mode = & par - > vbe_modes [ i ] ;
}
if ( fb_alloc_cmap ( & info - > cmap , 256 , 0 ) < 0 ) {
err = - ENXIO ;
goto out ;
}
uvesafb_init_info ( info , mode ) ;
if ( ! request_mem_region ( info - > fix . smem_start , info - > fix . smem_len ,
" uvesafb " ) ) {
printk ( KERN_ERR " uvesafb: cannot reserve video memory at "
" 0x%lx \n " , info - > fix . smem_start ) ;
err = - EIO ;
goto out_mode ;
}
info - > screen_base = ioremap ( info - > fix . smem_start , info - > fix . smem_len ) ;
if ( ! info - > screen_base ) {
printk ( KERN_ERR
" uvesafb: abort, cannot ioremap 0x%x bytes of video "
" memory at 0x%lx \n " ,
info - > fix . smem_len , info - > fix . smem_start ) ;
err = - EIO ;
goto out_mem ;
}
if ( ! request_region ( 0x3c0 , 32 , " uvesafb " ) ) {
printk ( KERN_ERR " uvesafb: request region 0x3c0-0x3e0 failed \n " ) ;
err = - EIO ;
goto out_unmap ;
}
uvesafb_init_mtrr ( info ) ;
platform_set_drvdata ( dev , info ) ;
if ( register_framebuffer ( info ) < 0 ) {
printk ( KERN_ERR
" uvesafb: failed to register framebuffer device \n " ) ;
err = - EINVAL ;
goto out_reg ;
}
printk ( KERN_INFO " uvesafb: framebuffer at 0x%lx, mapped to 0x%p, "
" using %dk, total %dk \n " , info - > fix . smem_start ,
info - > screen_base , info - > fix . smem_len / 1024 ,
par - > vbe_ib . total_memory * 64 ) ;
printk ( KERN_INFO " fb%d: %s frame buffer device \n " , info - > node ,
info - > fix . id ) ;
err = sysfs_create_group ( & dev - > dev . kobj , & uvesafb_dev_attgrp ) ;
if ( err ! = 0 )
printk ( KERN_WARNING " fb%d: failed to register attributes \n " ,
info - > node ) ;
return 0 ;
out_reg :
release_region ( 0x3c0 , 32 ) ;
out_unmap :
iounmap ( info - > screen_base ) ;
out_mem :
release_mem_region ( info - > fix . smem_start , info - > fix . smem_len ) ;
out_mode :
if ( ! list_empty ( & info - > modelist ) )
fb_destroy_modelist ( & info - > modelist ) ;
fb_destroy_modedb ( info - > monspecs . modedb ) ;
fb_dealloc_cmap ( & info - > cmap ) ;
out :
if ( par - > vbe_modes )
kfree ( par - > vbe_modes ) ;
framebuffer_release ( info ) ;
return err ;
}
static int uvesafb_remove ( struct platform_device * dev )
{
struct fb_info * info = platform_get_drvdata ( dev ) ;
if ( info ) {
struct uvesafb_par * par = info - > par ;
sysfs_remove_group ( & dev - > dev . kobj , & uvesafb_dev_attgrp ) ;
unregister_framebuffer ( info ) ;
release_region ( 0x3c0 , 32 ) ;
iounmap ( info - > screen_base ) ;
release_mem_region ( info - > fix . smem_start , info - > fix . smem_len ) ;
fb_destroy_modedb ( info - > monspecs . modedb ) ;
fb_dealloc_cmap ( & info - > cmap ) ;
if ( par ) {
if ( par - > vbe_modes )
kfree ( par - > vbe_modes ) ;
if ( par - > vbe_state_orig )
kfree ( par - > vbe_state_orig ) ;
if ( par - > vbe_state_saved )
kfree ( par - > vbe_state_saved ) ;
}
framebuffer_release ( info ) ;
}
return 0 ;
}
static struct platform_driver uvesafb_driver = {
. probe = uvesafb_probe ,
. remove = uvesafb_remove ,
. driver = {
. name = " uvesafb " ,
} ,
} ;
static struct platform_device * uvesafb_device ;
# ifndef MODULE
static int __devinit uvesafb_setup ( char * options )
{
char * this_opt ;
if ( ! options | | ! * options )
return 0 ;
while ( ( this_opt = strsep ( & options , " , " ) ) ! = NULL ) {
if ( ! * this_opt ) continue ;
if ( ! strcmp ( this_opt , " redraw " ) )
ypan = 0 ;
else if ( ! strcmp ( this_opt , " ypan " ) )
ypan = 1 ;
else if ( ! strcmp ( this_opt , " ywrap " ) )
ypan = 2 ;
else if ( ! strcmp ( this_opt , " vgapal " ) )
pmi_setpal = 0 ;
else if ( ! strcmp ( this_opt , " pmipal " ) )
pmi_setpal = 1 ;
else if ( ! strncmp ( this_opt , " mtrr: " , 5 ) )
mtrr = simple_strtoul ( this_opt + 5 , NULL , 0 ) ;
else if ( ! strcmp ( this_opt , " nomtrr " ) )
mtrr = 0 ;
else if ( ! strcmp ( this_opt , " nocrtc " ) )
nocrtc = 1 ;
else if ( ! strcmp ( this_opt , " noedid " ) )
noedid = 1 ;
else if ( ! strcmp ( this_opt , " noblank " ) )
blank = 0 ;
else if ( ! strncmp ( this_opt , " vtotal: " , 7 ) )
vram_total = simple_strtoul ( this_opt + 7 , NULL , 0 ) ;
else if ( ! strncmp ( this_opt , " vremap: " , 7 ) )
vram_remap = simple_strtoul ( this_opt + 7 , NULL , 0 ) ;
else if ( ! strncmp ( this_opt , " maxhf: " , 6 ) )
maxhf = simple_strtoul ( this_opt + 6 , NULL , 0 ) ;
else if ( ! strncmp ( this_opt , " maxvf: " , 6 ) )
maxvf = simple_strtoul ( this_opt + 6 , NULL , 0 ) ;
else if ( ! strncmp ( this_opt , " maxclk: " , 7 ) )
maxclk = simple_strtoul ( this_opt + 7 , NULL , 0 ) ;
else if ( ! strncmp ( this_opt , " vbemode: " , 8 ) )
vbemode = simple_strtoul ( this_opt + 8 , NULL , 0 ) ;
else if ( this_opt [ 0 ] > = ' 0 ' & & this_opt [ 0 ] < = ' 9 ' ) {
mode_option = this_opt ;
} else {
printk ( KERN_WARNING
" uvesafb: unrecognized option %s \n " , this_opt ) ;
}
}
return 0 ;
}
# endif /* !MODULE */
static ssize_t show_v86d ( struct device_driver * dev , char * buf )
{
return snprintf ( buf , PAGE_SIZE , " %s \n " , v86d_path ) ;
}
static ssize_t store_v86d ( struct device_driver * dev , const char * buf ,
size_t count )
{
strncpy ( v86d_path , buf , PATH_MAX ) ;
return count ;
}
static DRIVER_ATTR ( v86d , S_IRUGO | S_IWUSR , show_v86d , store_v86d ) ;
static int __devinit uvesafb_init ( void )
{
int err ;
# ifndef MODULE
char * option = NULL ;
if ( fb_get_options ( " uvesafb " , & option ) )
return - ENODEV ;
uvesafb_setup ( option ) ;
# endif
err = cn_add_callback ( & uvesafb_cn_id , " uvesafb " , uvesafb_cn_callback ) ;
if ( err )
return err ;
err = platform_driver_register ( & uvesafb_driver ) ;
if ( ! err ) {
uvesafb_device = platform_device_alloc ( " uvesafb " , 0 ) ;
if ( uvesafb_device )
err = platform_device_add ( uvesafb_device ) ;
else
err = - ENOMEM ;
if ( err ) {
platform_device_put ( uvesafb_device ) ;
platform_driver_unregister ( & uvesafb_driver ) ;
cn_del_callback ( & uvesafb_cn_id ) ;
return err ;
}
err = driver_create_file ( & uvesafb_driver . driver ,
& driver_attr_v86d ) ;
if ( err ) {
printk ( KERN_WARNING " uvesafb: failed to register "
" attributes \n " ) ;
err = 0 ;
}
}
return err ;
}
module_init ( uvesafb_init ) ;
static void __devexit uvesafb_exit ( void )
{
struct uvesafb_ktask * task ;
if ( v86d_started ) {
task = uvesafb_prep ( ) ;
if ( task ) {
task - > t . flags = TF_EXIT ;
uvesafb_exec ( task ) ;
uvesafb_free ( task ) ;
}
}
cn_del_callback ( & uvesafb_cn_id ) ;
driver_remove_file ( & uvesafb_driver . driver , & driver_attr_v86d ) ;
platform_device_unregister ( uvesafb_device ) ;
platform_driver_unregister ( & uvesafb_driver ) ;
}
module_exit ( uvesafb_exit ) ;
2008-02-06 12:39:22 +03:00
static int param_get_scroll ( char * buffer , struct kernel_param * kp )
2007-10-16 12:28:26 +04:00
{
return 0 ;
}
2008-02-06 12:39:22 +03:00
static int param_set_scroll ( const char * val , struct kernel_param * kp )
2007-10-16 12:28:26 +04:00
{
ypan = 0 ;
if ( ! strcmp ( val , " redraw " ) )
ypan = 0 ;
else if ( ! strcmp ( val , " ypan " ) )
ypan = 1 ;
else if ( ! strcmp ( val , " ywrap " ) )
ypan = 2 ;
return 0 ;
}
2008-02-06 12:39:22 +03:00
# define param_check_scroll(name, p) __param_check(name, p, void)
2007-10-16 12:28:26 +04:00
module_param_named ( scroll , ypan , scroll , 0 ) ;
MODULE_PARM_DESC ( scroll ,
2008-02-06 12:39:22 +03:00
" Scrolling mode, set to 'redraw', 'ypan', or 'ywrap' " ) ;
2007-10-16 12:28:26 +04:00
module_param_named ( vgapal , pmi_setpal , invbool , 0 ) ;
MODULE_PARM_DESC ( vgapal , " Set palette using VGA registers " ) ;
module_param_named ( pmipal , pmi_setpal , bool , 0 ) ;
MODULE_PARM_DESC ( pmipal , " Set palette using PMI calls " ) ;
module_param ( mtrr , uint , 0 ) ;
MODULE_PARM_DESC ( mtrr ,
" Memory Type Range Registers setting. Use 0 to disable. " ) ;
module_param ( blank , bool , 0 ) ;
MODULE_PARM_DESC ( blank , " Enable hardware blanking " ) ;
module_param ( nocrtc , bool , 0 ) ;
MODULE_PARM_DESC ( nocrtc , " Ignore CRTC timings when setting modes " ) ;
module_param ( noedid , bool , 0 ) ;
MODULE_PARM_DESC ( noedid ,
" Ignore EDID-provided monitor limits when setting modes " ) ;
module_param ( vram_remap , uint , 0 ) ;
MODULE_PARM_DESC ( vram_remap , " Set amount of video memory to be used [MiB] " ) ;
module_param ( vram_total , uint , 0 ) ;
MODULE_PARM_DESC ( vram_total , " Set total amount of video memoery [MiB] " ) ;
module_param ( maxclk , ushort , 0 ) ;
MODULE_PARM_DESC ( maxclk , " Maximum pixelclock [MHz], overrides EDID data " ) ;
module_param ( maxhf , ushort , 0 ) ;
MODULE_PARM_DESC ( maxhf ,
" Maximum horizontal frequency [kHz], overrides EDID data " ) ;
module_param ( maxvf , ushort , 0 ) ;
MODULE_PARM_DESC ( maxvf ,
" Maximum vertical frequency [Hz], overrides EDID data " ) ;
2008-07-24 08:31:21 +04:00
module_param ( mode_option , charp , 0 ) ;
MODULE_PARM_DESC ( mode_option ,
2007-10-16 12:28:26 +04:00
" Specify initial video mode as \" <xres>x<yres>[-<bpp>][@<refresh>] \" " ) ;
module_param ( vbemode , ushort , 0 ) ;
MODULE_PARM_DESC ( vbemode ,
" VBE mode number to set, overrides the 'mode' option " ) ;
module_param_string ( v86d , v86d_path , PATH_MAX , 0660 ) ;
MODULE_PARM_DESC ( v86d , " Path to the v86d userspace helper. " ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_AUTHOR ( " Michal Januszewski <spock@gentoo.org> " ) ;
MODULE_DESCRIPTION ( " Framebuffer driver for VBE2.0+ compliant graphics boards " ) ;