2010-02-01 08:38:10 +03:00
/*
* Copyright ( c ) 2010 Red Hat Inc .
* Author : Dave Airlie < airlied @ redhat . com >
*
*
* Licensed under GPLv2
*
* vga_switcheroo . c - Support for laptop with dual GPU using one set of outputs
Switcher interface - methods require for ATPX and DCM
- switchto - this throws the output MUX switch
- discrete_set_power - sets the power state for the discrete card
GPU driver interface
- set_gpu_state - this should do the equiv of s / r for the card
- this should * not * set the discrete power state
- switch_check - check if the device is in a position to switch now
*/
# include <linux/module.h>
# include <linux/dmi.h>
# include <linux/seq_file.h>
# include <linux/uaccess.h>
# include <linux/fs.h>
# include <linux/debugfs.h>
# include <linux/fb.h>
# include <linux/pci.h>
# include <linux/vga_switcheroo.h>
struct vga_switcheroo_client {
struct pci_dev * pdev ;
struct fb_info * fb_info ;
int pwr_state ;
void ( * set_gpu_state ) ( struct pci_dev * pdev , enum vga_switcheroo_state ) ;
bool ( * can_switch ) ( struct pci_dev * pdev ) ;
int id ;
bool active ;
} ;
static DEFINE_MUTEX ( vgasr_mutex ) ;
struct vgasr_priv {
bool active ;
bool delayed_switch_active ;
enum vga_switcheroo_client_id delayed_client_id ;
struct dentry * debugfs_root ;
struct dentry * switch_file ;
int registered_clients ;
struct vga_switcheroo_client clients [ VGA_SWITCHEROO_MAX_CLIENTS ] ;
struct vga_switcheroo_handler * handler ;
} ;
static int vga_switcheroo_debugfs_init ( struct vgasr_priv * priv ) ;
static void vga_switcheroo_debugfs_fini ( struct vgasr_priv * priv ) ;
/* only one switcheroo per system */
static struct vgasr_priv vgasr_priv ;
int vga_switcheroo_register_handler ( struct vga_switcheroo_handler * handler )
{
mutex_lock ( & vgasr_mutex ) ;
if ( vgasr_priv . handler ) {
mutex_unlock ( & vgasr_mutex ) ;
return - EINVAL ;
}
vgasr_priv . handler = handler ;
mutex_unlock ( & vgasr_mutex ) ;
return 0 ;
}
EXPORT_SYMBOL ( vga_switcheroo_register_handler ) ;
void vga_switcheroo_unregister_handler ( void )
{
mutex_lock ( & vgasr_mutex ) ;
vgasr_priv . handler = NULL ;
mutex_unlock ( & vgasr_mutex ) ;
}
EXPORT_SYMBOL ( vga_switcheroo_unregister_handler ) ;
static void vga_switcheroo_enable ( void )
{
int i ;
int ret ;
/* call the handler to init */
vgasr_priv . handler - > init ( ) ;
for ( i = 0 ; i < VGA_SWITCHEROO_MAX_CLIENTS ; i + + ) {
ret = vgasr_priv . handler - > get_client_id ( vgasr_priv . clients [ i ] . pdev ) ;
if ( ret < 0 )
return ;
vgasr_priv . clients [ i ] . id = ret ;
}
vga_switcheroo_debugfs_init ( & vgasr_priv ) ;
vgasr_priv . active = true ;
}
int vga_switcheroo_register_client ( struct pci_dev * pdev ,
void ( * set_gpu_state ) ( struct pci_dev * pdev , enum vga_switcheroo_state ) ,
bool ( * can_switch ) ( struct pci_dev * pdev ) )
{
int index ;
mutex_lock ( & vgasr_mutex ) ;
/* don't do IGD vs DIS here */
if ( vgasr_priv . registered_clients & 1 )
index = 1 ;
else
index = 0 ;
vgasr_priv . clients [ index ] . pwr_state = VGA_SWITCHEROO_ON ;
vgasr_priv . clients [ index ] . pdev = pdev ;
vgasr_priv . clients [ index ] . set_gpu_state = set_gpu_state ;
vgasr_priv . clients [ index ] . can_switch = can_switch ;
vgasr_priv . clients [ index ] . id = - 1 ;
if ( pdev - > resource [ PCI_ROM_RESOURCE ] . flags & IORESOURCE_ROM_SHADOW )
vgasr_priv . clients [ index ] . active = true ;
vgasr_priv . registered_clients | = ( 1 < < index ) ;
/* if we get two clients + handler */
if ( vgasr_priv . registered_clients = = 0x3 & & vgasr_priv . handler ) {
printk ( KERN_INFO " vga_switcheroo: enabled \n " ) ;
vga_switcheroo_enable ( ) ;
}
mutex_unlock ( & vgasr_mutex ) ;
return 0 ;
}
EXPORT_SYMBOL ( vga_switcheroo_register_client ) ;
void vga_switcheroo_unregister_client ( struct pci_dev * pdev )
{
int i ;
mutex_lock ( & vgasr_mutex ) ;
for ( i = 0 ; i < VGA_SWITCHEROO_MAX_CLIENTS ; i + + ) {
if ( vgasr_priv . clients [ i ] . pdev = = pdev ) {
vgasr_priv . registered_clients & = ~ ( 1 < < i ) ;
break ;
}
}
printk ( KERN_INFO " vga_switcheroo: disabled \n " ) ;
vga_switcheroo_debugfs_fini ( & vgasr_priv ) ;
vgasr_priv . active = false ;
mutex_unlock ( & vgasr_mutex ) ;
}
EXPORT_SYMBOL ( vga_switcheroo_unregister_client ) ;
void vga_switcheroo_client_fb_set ( struct pci_dev * pdev ,
struct fb_info * info )
{
int i ;
mutex_lock ( & vgasr_mutex ) ;
for ( i = 0 ; i < VGA_SWITCHEROO_MAX_CLIENTS ; i + + ) {
if ( vgasr_priv . clients [ i ] . pdev = = pdev ) {
vgasr_priv . clients [ i ] . fb_info = info ;
break ;
}
}
mutex_unlock ( & vgasr_mutex ) ;
}
EXPORT_SYMBOL ( vga_switcheroo_client_fb_set ) ;
static int vga_switcheroo_show ( struct seq_file * m , void * v )
{
int i ;
mutex_lock ( & vgasr_mutex ) ;
for ( i = 0 ; i < VGA_SWITCHEROO_MAX_CLIENTS ; i + + ) {
2010-12-06 05:30:31 +03:00
seq_printf ( m , " %d:%s:%c:%s:%s \n " , i ,
vgasr_priv . clients [ i ] . id = = VGA_SWITCHEROO_DIS ? " DIS " : " IGD " ,
2010-02-01 08:38:10 +03:00
vgasr_priv . clients [ i ] . active ? ' + ' : ' ' ,
vgasr_priv . clients [ i ] . pwr_state ? " Pwr " : " Off " ,
pci_name ( vgasr_priv . clients [ i ] . pdev ) ) ;
}
mutex_unlock ( & vgasr_mutex ) ;
return 0 ;
}
static int vga_switcheroo_debugfs_open ( struct inode * inode , struct file * file )
{
return single_open ( file , vga_switcheroo_show , NULL ) ;
}
static int vga_switchon ( struct vga_switcheroo_client * client )
{
2010-12-06 05:31:50 +03:00
if ( vgasr_priv . handler - > power_state )
vgasr_priv . handler - > power_state ( client - > id , VGA_SWITCHEROO_ON ) ;
2010-02-01 08:38:10 +03:00
/* call the driver callback to turn on device */
client - > set_gpu_state ( client - > pdev , VGA_SWITCHEROO_ON ) ;
client - > pwr_state = VGA_SWITCHEROO_ON ;
return 0 ;
}
static int vga_switchoff ( struct vga_switcheroo_client * client )
{
/* call the driver callback to turn off device */
client - > set_gpu_state ( client - > pdev , VGA_SWITCHEROO_OFF ) ;
2010-12-06 05:31:50 +03:00
if ( vgasr_priv . handler - > power_state )
vgasr_priv . handler - > power_state ( client - > id , VGA_SWITCHEROO_OFF ) ;
2010-02-01 08:38:10 +03:00
client - > pwr_state = VGA_SWITCHEROO_OFF ;
return 0 ;
}
static int vga_switchto ( struct vga_switcheroo_client * new_client )
{
int ret ;
int i ;
struct vga_switcheroo_client * active = NULL ;
if ( new_client - > active = = true )
return 0 ;
for ( i = 0 ; i < VGA_SWITCHEROO_MAX_CLIENTS ; i + + ) {
if ( vgasr_priv . clients [ i ] . active = = true ) {
active = & vgasr_priv . clients [ i ] ;
break ;
}
}
if ( ! active )
return 0 ;
/* power up the first device */
ret = pci_enable_device ( new_client - > pdev ) ;
if ( ret )
return ret ;
if ( new_client - > pwr_state = = VGA_SWITCHEROO_OFF )
vga_switchon ( new_client ) ;
/* swap shadow resource to denote boot VGA device has changed so X starts on new device */
active - > active = false ;
active - > pdev - > resource [ PCI_ROM_RESOURCE ] . flags & = ~ IORESOURCE_ROM_SHADOW ;
new_client - > pdev - > resource [ PCI_ROM_RESOURCE ] . flags | = IORESOURCE_ROM_SHADOW ;
if ( new_client - > fb_info ) {
struct fb_event event ;
event . info = new_client - > fb_info ;
fb_notifier_call_chain ( FB_EVENT_REMAP_ALL_CONSOLE , & event ) ;
}
ret = vgasr_priv . handler - > switchto ( new_client - > id ) ;
if ( ret )
return ret ;
if ( active - > pwr_state = = VGA_SWITCHEROO_ON )
vga_switchoff ( active ) ;
new_client - > active = true ;
return 0 ;
}
static ssize_t
vga_switcheroo_debugfs_write ( struct file * filp , const char __user * ubuf ,
size_t cnt , loff_t * ppos )
{
char usercmd [ 64 ] ;
const char * pdev_name ;
int i , ret ;
bool delay = false , can_switch ;
2010-12-06 05:35:52 +03:00
bool just_mux = false ;
2010-02-01 08:38:10 +03:00
int client_id = - 1 ;
struct vga_switcheroo_client * client = NULL ;
if ( cnt > 63 )
cnt = 63 ;
if ( copy_from_user ( usercmd , ubuf , cnt ) )
return - EFAULT ;
mutex_lock ( & vgasr_mutex ) ;
2010-04-28 01:11:03 +04:00
if ( ! vgasr_priv . active ) {
cnt = - EINVAL ;
goto out ;
}
2010-02-01 08:38:10 +03:00
/* pwr off the device not in use */
if ( strncmp ( usercmd , " OFF " , 3 ) = = 0 ) {
for ( i = 0 ; i < VGA_SWITCHEROO_MAX_CLIENTS ; i + + ) {
if ( vgasr_priv . clients [ i ] . active )
continue ;
if ( vgasr_priv . clients [ i ] . pwr_state = = VGA_SWITCHEROO_ON )
vga_switchoff ( & vgasr_priv . clients [ i ] ) ;
}
goto out ;
}
/* pwr on the device not in use */
if ( strncmp ( usercmd , " ON " , 2 ) = = 0 ) {
for ( i = 0 ; i < VGA_SWITCHEROO_MAX_CLIENTS ; i + + ) {
if ( vgasr_priv . clients [ i ] . active )
continue ;
if ( vgasr_priv . clients [ i ] . pwr_state = = VGA_SWITCHEROO_OFF )
vga_switchon ( & vgasr_priv . clients [ i ] ) ;
}
goto out ;
}
/* request a delayed switch - test can we switch now */
if ( strncmp ( usercmd , " DIGD " , 4 ) = = 0 ) {
client_id = VGA_SWITCHEROO_IGD ;
delay = true ;
}
if ( strncmp ( usercmd , " DDIS " , 4 ) = = 0 ) {
client_id = VGA_SWITCHEROO_DIS ;
delay = true ;
}
if ( strncmp ( usercmd , " IGD " , 3 ) = = 0 )
client_id = VGA_SWITCHEROO_IGD ;
if ( strncmp ( usercmd , " DIS " , 3 ) = = 0 )
client_id = VGA_SWITCHEROO_DIS ;
2010-12-06 05:35:52 +03:00
if ( strncmp ( usercmd , " MIGD " , 3 ) = = 0 ) {
just_mux = true ;
client_id = VGA_SWITCHEROO_IGD ;
}
if ( strncmp ( usercmd , " MDIS " , 3 ) = = 0 ) {
just_mux = true ;
client_id = VGA_SWITCHEROO_DIS ;
}
2010-02-01 08:38:10 +03:00
if ( client_id = = - 1 )
goto out ;
for ( i = 0 ; i < VGA_SWITCHEROO_MAX_CLIENTS ; i + + ) {
if ( vgasr_priv . clients [ i ] . id = = client_id ) {
client = & vgasr_priv . clients [ i ] ;
break ;
}
}
vgasr_priv . delayed_switch_active = false ;
2010-12-06 05:35:52 +03:00
if ( just_mux ) {
ret = vgasr_priv . handler - > switchto ( client_id ) ;
goto out ;
}
2010-02-01 08:38:10 +03:00
/* okay we want a switch - test if devices are willing to switch */
can_switch = true ;
for ( i = 0 ; i < VGA_SWITCHEROO_MAX_CLIENTS ; i + + ) {
can_switch = vgasr_priv . clients [ i ] . can_switch ( vgasr_priv . clients [ i ] . pdev ) ;
if ( can_switch = = false ) {
printk ( KERN_ERR " vga_switcheroo: client %d refused switch \n " , i ) ;
break ;
}
}
if ( can_switch = = false & & delay = = false )
goto out ;
if ( can_switch = = true ) {
pdev_name = pci_name ( client - > pdev ) ;
ret = vga_switchto ( client ) ;
if ( ret )
printk ( KERN_ERR " vga_switcheroo: switching failed %d \n " , ret ) ;
} else {
printk ( KERN_INFO " vga_switcheroo: setting delayed switch to client %d \n " , client - > id ) ;
vgasr_priv . delayed_switch_active = true ;
vgasr_priv . delayed_client_id = client_id ;
/* we should at least power up the card to
make the switch faster */
if ( client - > pwr_state = = VGA_SWITCHEROO_OFF )
vga_switchon ( client ) ;
}
out :
mutex_unlock ( & vgasr_mutex ) ;
return cnt ;
}
static const struct file_operations vga_switcheroo_debugfs_fops = {
. owner = THIS_MODULE ,
. open = vga_switcheroo_debugfs_open ,
. write = vga_switcheroo_debugfs_write ,
. read = seq_read ,
. llseek = seq_lseek ,
. release = single_release ,
} ;
static void vga_switcheroo_debugfs_fini ( struct vgasr_priv * priv )
{
if ( priv - > switch_file ) {
debugfs_remove ( priv - > switch_file ) ;
priv - > switch_file = NULL ;
}
if ( priv - > debugfs_root ) {
debugfs_remove ( priv - > debugfs_root ) ;
priv - > debugfs_root = NULL ;
}
}
static int vga_switcheroo_debugfs_init ( struct vgasr_priv * priv )
{
/* already initialised */
if ( priv - > debugfs_root )
return 0 ;
priv - > debugfs_root = debugfs_create_dir ( " vgaswitcheroo " , NULL ) ;
if ( ! priv - > debugfs_root ) {
printk ( KERN_ERR " vga_switcheroo: Cannot create /sys/kernel/debug/vgaswitcheroo \n " ) ;
goto fail ;
}
priv - > switch_file = debugfs_create_file ( " switch " , 0644 ,
priv - > debugfs_root , NULL , & vga_switcheroo_debugfs_fops ) ;
if ( ! priv - > switch_file ) {
printk ( KERN_ERR " vga_switcheroo: cannot create /sys/kernel/debug/vgaswitcheroo/switch \n " ) ;
goto fail ;
}
return 0 ;
fail :
vga_switcheroo_debugfs_fini ( priv ) ;
return - 1 ;
}
int vga_switcheroo_process_delayed_switch ( void )
{
struct vga_switcheroo_client * client = NULL ;
const char * pdev_name ;
bool can_switch = true ;
int i ;
int ret ;
int err = - EINVAL ;
mutex_lock ( & vgasr_mutex ) ;
if ( ! vgasr_priv . delayed_switch_active )
goto err ;
printk ( KERN_INFO " vga_switcheroo: processing delayed switch to %d \n " , vgasr_priv . delayed_client_id ) ;
for ( i = 0 ; i < VGA_SWITCHEROO_MAX_CLIENTS ; i + + ) {
if ( vgasr_priv . clients [ i ] . id = = vgasr_priv . delayed_client_id )
client = & vgasr_priv . clients [ i ] ;
can_switch = vgasr_priv . clients [ i ] . can_switch ( vgasr_priv . clients [ i ] . pdev ) ;
if ( can_switch = = false ) {
printk ( KERN_ERR " vga_switcheroo: client %d refused switch \n " , i ) ;
break ;
}
}
if ( can_switch = = false | | client = = NULL )
goto err ;
pdev_name = pci_name ( client - > pdev ) ;
ret = vga_switchto ( client ) ;
if ( ret )
printk ( KERN_ERR " vga_switcheroo: delayed switching failed %d \n " , ret ) ;
vgasr_priv . delayed_switch_active = false ;
err = 0 ;
err :
mutex_unlock ( & vgasr_mutex ) ;
return err ;
}
EXPORT_SYMBOL ( vga_switcheroo_process_delayed_switch ) ;