16a9ee8afSDave Airlie /* 26a9ee8afSDave Airlie * Copyright (c) 2010 Red Hat Inc. 36a9ee8afSDave Airlie * Author : Dave Airlie <airlied@redhat.com> 46a9ee8afSDave Airlie * 56a9ee8afSDave Airlie * 66a9ee8afSDave Airlie * Licensed under GPLv2 76a9ee8afSDave Airlie * 86a9ee8afSDave Airlie * vga_switcheroo.c - Support for laptop with dual GPU using one set of outputs 96a9ee8afSDave Airlie 106a9ee8afSDave Airlie Switcher interface - methods require for ATPX and DCM 116a9ee8afSDave Airlie - switchto - this throws the output MUX switch 126a9ee8afSDave Airlie - discrete_set_power - sets the power state for the discrete card 136a9ee8afSDave Airlie 146a9ee8afSDave Airlie GPU driver interface 156a9ee8afSDave Airlie - set_gpu_state - this should do the equiv of s/r for the card 166a9ee8afSDave Airlie - this should *not* set the discrete power state 176a9ee8afSDave Airlie - switch_check - check if the device is in a position to switch now 186a9ee8afSDave Airlie */ 196a9ee8afSDave Airlie 206a9ee8afSDave Airlie #include <linux/module.h> 216a9ee8afSDave Airlie #include <linux/dmi.h> 226a9ee8afSDave Airlie #include <linux/seq_file.h> 236a9ee8afSDave Airlie #include <linux/uaccess.h> 246a9ee8afSDave Airlie #include <linux/fs.h> 256a9ee8afSDave Airlie #include <linux/debugfs.h> 266a9ee8afSDave Airlie #include <linux/fb.h> 276a9ee8afSDave Airlie 286a9ee8afSDave Airlie #include <linux/pci.h> 296a9ee8afSDave Airlie #include <linux/vga_switcheroo.h> 306a9ee8afSDave Airlie 312fbe8c7cSMatthew Garrett #include <linux/vgaarb.h> 322fbe8c7cSMatthew Garrett 336a9ee8afSDave Airlie struct vga_switcheroo_client { 346a9ee8afSDave Airlie struct pci_dev *pdev; 356a9ee8afSDave Airlie struct fb_info *fb_info; 366a9ee8afSDave Airlie int pwr_state; 3726ec685fSTakashi Iwai const struct vga_switcheroo_client_ops *ops; 386a9ee8afSDave Airlie int id; 396a9ee8afSDave Airlie bool active; 4079721e0aSTakashi Iwai struct list_head list; 416a9ee8afSDave Airlie }; 426a9ee8afSDave Airlie 436a9ee8afSDave Airlie static DEFINE_MUTEX(vgasr_mutex); 446a9ee8afSDave Airlie 456a9ee8afSDave Airlie struct vgasr_priv { 466a9ee8afSDave Airlie 476a9ee8afSDave Airlie bool active; 486a9ee8afSDave Airlie bool delayed_switch_active; 496a9ee8afSDave Airlie enum vga_switcheroo_client_id delayed_client_id; 506a9ee8afSDave Airlie 516a9ee8afSDave Airlie struct dentry *debugfs_root; 526a9ee8afSDave Airlie struct dentry *switch_file; 536a9ee8afSDave Airlie 546a9ee8afSDave Airlie int registered_clients; 5579721e0aSTakashi Iwai struct list_head clients; 566a9ee8afSDave Airlie 576a9ee8afSDave Airlie struct vga_switcheroo_handler *handler; 586a9ee8afSDave Airlie }; 596a9ee8afSDave Airlie 603e9e63dbSTakashi Iwai #define ID_BIT_AUDIO 0x100 613e9e63dbSTakashi Iwai #define client_is_audio(c) ((c)->id & ID_BIT_AUDIO) 623e9e63dbSTakashi Iwai #define client_is_vga(c) ((c)->id == -1 || !client_is_audio(c)) 633e9e63dbSTakashi Iwai #define client_id(c) ((c)->id & ~ID_BIT_AUDIO) 643e9e63dbSTakashi Iwai 656a9ee8afSDave Airlie static int vga_switcheroo_debugfs_init(struct vgasr_priv *priv); 666a9ee8afSDave Airlie static void vga_switcheroo_debugfs_fini(struct vgasr_priv *priv); 676a9ee8afSDave Airlie 686a9ee8afSDave Airlie /* only one switcheroo per system */ 6979721e0aSTakashi Iwai static struct vgasr_priv vgasr_priv = { 7079721e0aSTakashi Iwai .clients = LIST_HEAD_INIT(vgasr_priv.clients), 7179721e0aSTakashi Iwai }; 726a9ee8afSDave Airlie 736a9ee8afSDave Airlie int vga_switcheroo_register_handler(struct vga_switcheroo_handler *handler) 746a9ee8afSDave Airlie { 756a9ee8afSDave Airlie mutex_lock(&vgasr_mutex); 766a9ee8afSDave Airlie if (vgasr_priv.handler) { 776a9ee8afSDave Airlie mutex_unlock(&vgasr_mutex); 786a9ee8afSDave Airlie return -EINVAL; 796a9ee8afSDave Airlie } 806a9ee8afSDave Airlie 816a9ee8afSDave Airlie vgasr_priv.handler = handler; 826a9ee8afSDave Airlie mutex_unlock(&vgasr_mutex); 836a9ee8afSDave Airlie return 0; 846a9ee8afSDave Airlie } 856a9ee8afSDave Airlie EXPORT_SYMBOL(vga_switcheroo_register_handler); 866a9ee8afSDave Airlie 876a9ee8afSDave Airlie void vga_switcheroo_unregister_handler(void) 886a9ee8afSDave Airlie { 896a9ee8afSDave Airlie mutex_lock(&vgasr_mutex); 906a9ee8afSDave Airlie vgasr_priv.handler = NULL; 916a9ee8afSDave Airlie mutex_unlock(&vgasr_mutex); 926a9ee8afSDave Airlie } 936a9ee8afSDave Airlie EXPORT_SYMBOL(vga_switcheroo_unregister_handler); 946a9ee8afSDave Airlie 956a9ee8afSDave Airlie static void vga_switcheroo_enable(void) 966a9ee8afSDave Airlie { 976a9ee8afSDave Airlie int ret; 9879721e0aSTakashi Iwai struct vga_switcheroo_client *client; 9979721e0aSTakashi Iwai 1006a9ee8afSDave Airlie /* call the handler to init */ 1016a9ee8afSDave Airlie vgasr_priv.handler->init(); 1026a9ee8afSDave Airlie 10379721e0aSTakashi Iwai list_for_each_entry(client, &vgasr_priv.clients, list) { 1043e9e63dbSTakashi Iwai if (client->id != -1) 1053e9e63dbSTakashi Iwai continue; 10679721e0aSTakashi Iwai ret = vgasr_priv.handler->get_client_id(client->pdev); 1076a9ee8afSDave Airlie if (ret < 0) 1086a9ee8afSDave Airlie return; 1096a9ee8afSDave Airlie 11079721e0aSTakashi Iwai client->id = ret; 1116a9ee8afSDave Airlie } 1126a9ee8afSDave Airlie vga_switcheroo_debugfs_init(&vgasr_priv); 1136a9ee8afSDave Airlie vgasr_priv.active = true; 1146a9ee8afSDave Airlie } 1156a9ee8afSDave Airlie 1163e9e63dbSTakashi Iwai static int register_client(struct pci_dev *pdev, 1173e9e63dbSTakashi Iwai const struct vga_switcheroo_client_ops *ops, 1183e9e63dbSTakashi Iwai int id, bool active) 1196a9ee8afSDave Airlie { 12079721e0aSTakashi Iwai struct vga_switcheroo_client *client; 12179721e0aSTakashi Iwai 12279721e0aSTakashi Iwai client = kzalloc(sizeof(*client), GFP_KERNEL); 12379721e0aSTakashi Iwai if (!client) 12479721e0aSTakashi Iwai return -ENOMEM; 12579721e0aSTakashi Iwai 12679721e0aSTakashi Iwai client->pwr_state = VGA_SWITCHEROO_ON; 12779721e0aSTakashi Iwai client->pdev = pdev; 12826ec685fSTakashi Iwai client->ops = ops; 1293e9e63dbSTakashi Iwai client->id = id; 1303e9e63dbSTakashi Iwai client->active = active; 1316a9ee8afSDave Airlie 1326a9ee8afSDave Airlie mutex_lock(&vgasr_mutex); 13379721e0aSTakashi Iwai list_add_tail(&client->list, &vgasr_priv.clients); 1343e9e63dbSTakashi Iwai if (client_is_vga(client)) 13579721e0aSTakashi Iwai vgasr_priv.registered_clients++; 1366a9ee8afSDave Airlie 1376a9ee8afSDave Airlie /* if we get two clients + handler */ 1383e9e63dbSTakashi Iwai if (!vgasr_priv.active && 1393e9e63dbSTakashi Iwai vgasr_priv.registered_clients == 2 && vgasr_priv.handler) { 1406a9ee8afSDave Airlie printk(KERN_INFO "vga_switcheroo: enabled\n"); 1416a9ee8afSDave Airlie vga_switcheroo_enable(); 1426a9ee8afSDave Airlie } 1436a9ee8afSDave Airlie mutex_unlock(&vgasr_mutex); 1446a9ee8afSDave Airlie return 0; 1456a9ee8afSDave Airlie } 1463e9e63dbSTakashi Iwai 1473e9e63dbSTakashi Iwai int vga_switcheroo_register_client(struct pci_dev *pdev, 1483e9e63dbSTakashi Iwai const struct vga_switcheroo_client_ops *ops) 1493e9e63dbSTakashi Iwai { 1503e9e63dbSTakashi Iwai return register_client(pdev, ops, -1, 1513e9e63dbSTakashi Iwai pdev == vga_default_device()); 1523e9e63dbSTakashi Iwai } 1536a9ee8afSDave Airlie EXPORT_SYMBOL(vga_switcheroo_register_client); 1546a9ee8afSDave Airlie 1553e9e63dbSTakashi Iwai int vga_switcheroo_register_audio_client(struct pci_dev *pdev, 1563e9e63dbSTakashi Iwai const struct vga_switcheroo_client_ops *ops, 1573e9e63dbSTakashi Iwai int id, bool active) 1583e9e63dbSTakashi Iwai { 1593e9e63dbSTakashi Iwai return register_client(pdev, ops, id | ID_BIT_AUDIO, active); 1603e9e63dbSTakashi Iwai } 1613e9e63dbSTakashi Iwai EXPORT_SYMBOL(vga_switcheroo_register_audio_client); 1623e9e63dbSTakashi Iwai 16379721e0aSTakashi Iwai static struct vga_switcheroo_client * 16479721e0aSTakashi Iwai find_client_from_pci(struct list_head *head, struct pci_dev *pdev) 16579721e0aSTakashi Iwai { 16679721e0aSTakashi Iwai struct vga_switcheroo_client *client; 16779721e0aSTakashi Iwai list_for_each_entry(client, head, list) 16879721e0aSTakashi Iwai if (client->pdev == pdev) 16979721e0aSTakashi Iwai return client; 17079721e0aSTakashi Iwai return NULL; 17179721e0aSTakashi Iwai } 17279721e0aSTakashi Iwai 17379721e0aSTakashi Iwai static struct vga_switcheroo_client * 17479721e0aSTakashi Iwai find_client_from_id(struct list_head *head, int client_id) 17579721e0aSTakashi Iwai { 17679721e0aSTakashi Iwai struct vga_switcheroo_client *client; 17779721e0aSTakashi Iwai list_for_each_entry(client, head, list) 17879721e0aSTakashi Iwai if (client->id == client_id) 17979721e0aSTakashi Iwai return client; 18079721e0aSTakashi Iwai return NULL; 18179721e0aSTakashi Iwai } 18279721e0aSTakashi Iwai 18379721e0aSTakashi Iwai static struct vga_switcheroo_client * 18479721e0aSTakashi Iwai find_active_client(struct list_head *head) 18579721e0aSTakashi Iwai { 18679721e0aSTakashi Iwai struct vga_switcheroo_client *client; 18779721e0aSTakashi Iwai list_for_each_entry(client, head, list) 1883e9e63dbSTakashi Iwai if (client->active && client_is_vga(client)) 18979721e0aSTakashi Iwai return client; 19079721e0aSTakashi Iwai return NULL; 19179721e0aSTakashi Iwai } 19279721e0aSTakashi Iwai 193c8e9cf7bSTakashi Iwai int vga_switcheroo_get_client_state(struct pci_dev *pdev) 194c8e9cf7bSTakashi Iwai { 195c8e9cf7bSTakashi Iwai struct vga_switcheroo_client *client; 196c8e9cf7bSTakashi Iwai 197c8e9cf7bSTakashi Iwai client = find_client_from_pci(&vgasr_priv.clients, pdev); 198c8e9cf7bSTakashi Iwai if (!client) 199c8e9cf7bSTakashi Iwai return VGA_SWITCHEROO_NOT_FOUND; 200c8e9cf7bSTakashi Iwai if (!vgasr_priv.active) 201c8e9cf7bSTakashi Iwai return VGA_SWITCHEROO_INIT; 202c8e9cf7bSTakashi Iwai return client->pwr_state; 203c8e9cf7bSTakashi Iwai } 204c8e9cf7bSTakashi Iwai EXPORT_SYMBOL(vga_switcheroo_get_client_state); 205c8e9cf7bSTakashi Iwai 2066a9ee8afSDave Airlie void vga_switcheroo_unregister_client(struct pci_dev *pdev) 2076a9ee8afSDave Airlie { 20879721e0aSTakashi Iwai struct vga_switcheroo_client *client; 2096a9ee8afSDave Airlie 2106a9ee8afSDave Airlie mutex_lock(&vgasr_mutex); 21179721e0aSTakashi Iwai client = find_client_from_pci(&vgasr_priv.clients, pdev); 21279721e0aSTakashi Iwai if (client) { 2133e9e63dbSTakashi Iwai if (client_is_vga(client)) 2143e9e63dbSTakashi Iwai vgasr_priv.registered_clients--; 21579721e0aSTakashi Iwai list_del(&client->list); 21679721e0aSTakashi Iwai kfree(client); 2176a9ee8afSDave Airlie } 2183e9e63dbSTakashi Iwai if (vgasr_priv.active && vgasr_priv.registered_clients < 2) { 2196a9ee8afSDave Airlie printk(KERN_INFO "vga_switcheroo: disabled\n"); 2206a9ee8afSDave Airlie vga_switcheroo_debugfs_fini(&vgasr_priv); 2216a9ee8afSDave Airlie vgasr_priv.active = false; 2223e9e63dbSTakashi Iwai } 2236a9ee8afSDave Airlie mutex_unlock(&vgasr_mutex); 2246a9ee8afSDave Airlie } 2256a9ee8afSDave Airlie EXPORT_SYMBOL(vga_switcheroo_unregister_client); 2266a9ee8afSDave Airlie 2276a9ee8afSDave Airlie void vga_switcheroo_client_fb_set(struct pci_dev *pdev, 2286a9ee8afSDave Airlie struct fb_info *info) 2296a9ee8afSDave Airlie { 23079721e0aSTakashi Iwai struct vga_switcheroo_client *client; 2316a9ee8afSDave Airlie 2326a9ee8afSDave Airlie mutex_lock(&vgasr_mutex); 23379721e0aSTakashi Iwai client = find_client_from_pci(&vgasr_priv.clients, pdev); 23479721e0aSTakashi Iwai if (client) 23579721e0aSTakashi Iwai client->fb_info = info; 2366a9ee8afSDave Airlie mutex_unlock(&vgasr_mutex); 2376a9ee8afSDave Airlie } 2386a9ee8afSDave Airlie EXPORT_SYMBOL(vga_switcheroo_client_fb_set); 2396a9ee8afSDave Airlie 2406a9ee8afSDave Airlie static int vga_switcheroo_show(struct seq_file *m, void *v) 2416a9ee8afSDave Airlie { 24279721e0aSTakashi Iwai struct vga_switcheroo_client *client; 24379721e0aSTakashi Iwai int i = 0; 2446a9ee8afSDave Airlie mutex_lock(&vgasr_mutex); 24579721e0aSTakashi Iwai list_for_each_entry(client, &vgasr_priv.clients, list) { 2463e9e63dbSTakashi Iwai seq_printf(m, "%d:%s%s:%c:%s:%s\n", i, 2473e9e63dbSTakashi Iwai client_id(client) == VGA_SWITCHEROO_DIS ? "DIS" : "IGD", 2483e9e63dbSTakashi Iwai client_is_vga(client) ? "" : "-Audio", 24979721e0aSTakashi Iwai client->active ? '+' : ' ', 25079721e0aSTakashi Iwai client->pwr_state ? "Pwr" : "Off", 25179721e0aSTakashi Iwai pci_name(client->pdev)); 25279721e0aSTakashi Iwai i++; 2536a9ee8afSDave Airlie } 2546a9ee8afSDave Airlie mutex_unlock(&vgasr_mutex); 2556a9ee8afSDave Airlie return 0; 2566a9ee8afSDave Airlie } 2576a9ee8afSDave Airlie 2586a9ee8afSDave Airlie static int vga_switcheroo_debugfs_open(struct inode *inode, struct file *file) 2596a9ee8afSDave Airlie { 2606a9ee8afSDave Airlie return single_open(file, vga_switcheroo_show, NULL); 2616a9ee8afSDave Airlie } 2626a9ee8afSDave Airlie 2636a9ee8afSDave Airlie static int vga_switchon(struct vga_switcheroo_client *client) 2646a9ee8afSDave Airlie { 2655cfb3c3aSDave Airlie if (vgasr_priv.handler->power_state) 2665cfb3c3aSDave Airlie vgasr_priv.handler->power_state(client->id, VGA_SWITCHEROO_ON); 2676a9ee8afSDave Airlie /* call the driver callback to turn on device */ 26826ec685fSTakashi Iwai client->ops->set_gpu_state(client->pdev, VGA_SWITCHEROO_ON); 2696a9ee8afSDave Airlie client->pwr_state = VGA_SWITCHEROO_ON; 2706a9ee8afSDave Airlie return 0; 2716a9ee8afSDave Airlie } 2726a9ee8afSDave Airlie 2736a9ee8afSDave Airlie static int vga_switchoff(struct vga_switcheroo_client *client) 2746a9ee8afSDave Airlie { 2756a9ee8afSDave Airlie /* call the driver callback to turn off device */ 27626ec685fSTakashi Iwai client->ops->set_gpu_state(client->pdev, VGA_SWITCHEROO_OFF); 2775cfb3c3aSDave Airlie if (vgasr_priv.handler->power_state) 2786a9ee8afSDave Airlie vgasr_priv.handler->power_state(client->id, VGA_SWITCHEROO_OFF); 2796a9ee8afSDave Airlie client->pwr_state = VGA_SWITCHEROO_OFF; 2806a9ee8afSDave Airlie return 0; 2816a9ee8afSDave Airlie } 2826a9ee8afSDave Airlie 2833e9e63dbSTakashi Iwai static void set_audio_state(int id, int state) 2843e9e63dbSTakashi Iwai { 2853e9e63dbSTakashi Iwai struct vga_switcheroo_client *client; 2863e9e63dbSTakashi Iwai 2873e9e63dbSTakashi Iwai client = find_client_from_id(&vgasr_priv.clients, id | ID_BIT_AUDIO); 2883e9e63dbSTakashi Iwai if (client && client->pwr_state != state) { 2893e9e63dbSTakashi Iwai client->ops->set_gpu_state(client->pdev, state); 2903e9e63dbSTakashi Iwai client->pwr_state = state; 2913e9e63dbSTakashi Iwai } 2923e9e63dbSTakashi Iwai } 2933e9e63dbSTakashi Iwai 29466b37c67SDave Airlie /* stage one happens before delay */ 29566b37c67SDave Airlie static int vga_switchto_stage1(struct vga_switcheroo_client *new_client) 2966a9ee8afSDave Airlie { 29779721e0aSTakashi Iwai struct vga_switcheroo_client *active; 2986a9ee8afSDave Airlie 29979721e0aSTakashi Iwai active = find_active_client(&vgasr_priv.clients); 3006a9ee8afSDave Airlie if (!active) 3016a9ee8afSDave Airlie return 0; 3026a9ee8afSDave Airlie 3036a9ee8afSDave Airlie if (new_client->pwr_state == VGA_SWITCHEROO_OFF) 3046a9ee8afSDave Airlie vga_switchon(new_client); 3056a9ee8afSDave Airlie 3062fbe8c7cSMatthew Garrett vga_set_default_device(new_client->pdev); 3073e9e63dbSTakashi Iwai set_audio_state(new_client->id, VGA_SWITCHEROO_ON); 3082fbe8c7cSMatthew Garrett 30966b37c67SDave Airlie return 0; 31066b37c67SDave Airlie } 31166b37c67SDave Airlie 31266b37c67SDave Airlie /* post delay */ 31366b37c67SDave Airlie static int vga_switchto_stage2(struct vga_switcheroo_client *new_client) 31466b37c67SDave Airlie { 31566b37c67SDave Airlie int ret; 31679721e0aSTakashi Iwai struct vga_switcheroo_client *active; 31766b37c67SDave Airlie 31879721e0aSTakashi Iwai active = find_active_client(&vgasr_priv.clients); 31966b37c67SDave Airlie if (!active) 32066b37c67SDave Airlie return 0; 32166b37c67SDave Airlie 32266b37c67SDave Airlie active->active = false; 3236a9ee8afSDave Airlie 3246a9ee8afSDave Airlie if (new_client->fb_info) { 3256a9ee8afSDave Airlie struct fb_event event; 3266a9ee8afSDave Airlie event.info = new_client->fb_info; 3276a9ee8afSDave Airlie fb_notifier_call_chain(FB_EVENT_REMAP_ALL_CONSOLE, &event); 3286a9ee8afSDave Airlie } 3296a9ee8afSDave Airlie 3306a9ee8afSDave Airlie ret = vgasr_priv.handler->switchto(new_client->id); 3316a9ee8afSDave Airlie if (ret) 3326a9ee8afSDave Airlie return ret; 3336a9ee8afSDave Airlie 33426ec685fSTakashi Iwai if (new_client->ops->reprobe) 33526ec685fSTakashi Iwai new_client->ops->reprobe(new_client->pdev); 3368d608aa6SDave Airlie 3373e9e63dbSTakashi Iwai set_audio_state(active->id, VGA_SWITCHEROO_OFF); 3383e9e63dbSTakashi Iwai 3396a9ee8afSDave Airlie if (active->pwr_state == VGA_SWITCHEROO_ON) 3406a9ee8afSDave Airlie vga_switchoff(active); 3416a9ee8afSDave Airlie 3426a9ee8afSDave Airlie new_client->active = true; 3436a9ee8afSDave Airlie return 0; 3446a9ee8afSDave Airlie } 3456a9ee8afSDave Airlie 34679721e0aSTakashi Iwai static bool check_can_switch(void) 34779721e0aSTakashi Iwai { 34879721e0aSTakashi Iwai struct vga_switcheroo_client *client; 34979721e0aSTakashi Iwai 35079721e0aSTakashi Iwai list_for_each_entry(client, &vgasr_priv.clients, list) { 35126ec685fSTakashi Iwai if (!client->ops->can_switch(client->pdev)) { 35279721e0aSTakashi Iwai printk(KERN_ERR "vga_switcheroo: client %x refused switch\n", client->id); 35379721e0aSTakashi Iwai return false; 35479721e0aSTakashi Iwai } 35579721e0aSTakashi Iwai } 35679721e0aSTakashi Iwai return true; 35779721e0aSTakashi Iwai } 35879721e0aSTakashi Iwai 3596a9ee8afSDave Airlie static ssize_t 3606a9ee8afSDave Airlie vga_switcheroo_debugfs_write(struct file *filp, const char __user *ubuf, 3616a9ee8afSDave Airlie size_t cnt, loff_t *ppos) 3626a9ee8afSDave Airlie { 3636a9ee8afSDave Airlie char usercmd[64]; 3646a9ee8afSDave Airlie const char *pdev_name; 36579721e0aSTakashi Iwai int ret; 3666a9ee8afSDave Airlie bool delay = false, can_switch; 367851ab954SDave Airlie bool just_mux = false; 3686a9ee8afSDave Airlie int client_id = -1; 3696a9ee8afSDave Airlie struct vga_switcheroo_client *client = NULL; 3706a9ee8afSDave Airlie 3716a9ee8afSDave Airlie if (cnt > 63) 3726a9ee8afSDave Airlie cnt = 63; 3736a9ee8afSDave Airlie 3746a9ee8afSDave Airlie if (copy_from_user(usercmd, ubuf, cnt)) 3756a9ee8afSDave Airlie return -EFAULT; 3766a9ee8afSDave Airlie 3776a9ee8afSDave Airlie mutex_lock(&vgasr_mutex); 3786a9ee8afSDave Airlie 3798c88e50bSJiri Slaby if (!vgasr_priv.active) { 3808c88e50bSJiri Slaby cnt = -EINVAL; 3818c88e50bSJiri Slaby goto out; 3828c88e50bSJiri Slaby } 3836a9ee8afSDave Airlie 3846a9ee8afSDave Airlie /* pwr off the device not in use */ 3856a9ee8afSDave Airlie if (strncmp(usercmd, "OFF", 3) == 0) { 38679721e0aSTakashi Iwai list_for_each_entry(client, &vgasr_priv.clients, list) { 38779721e0aSTakashi Iwai if (client->active) 3886a9ee8afSDave Airlie continue; 38979721e0aSTakashi Iwai if (client->pwr_state == VGA_SWITCHEROO_ON) 39079721e0aSTakashi Iwai vga_switchoff(client); 3916a9ee8afSDave Airlie } 3926a9ee8afSDave Airlie goto out; 3936a9ee8afSDave Airlie } 3946a9ee8afSDave Airlie /* pwr on the device not in use */ 3956a9ee8afSDave Airlie if (strncmp(usercmd, "ON", 2) == 0) { 39679721e0aSTakashi Iwai list_for_each_entry(client, &vgasr_priv.clients, list) { 39779721e0aSTakashi Iwai if (client->active) 3986a9ee8afSDave Airlie continue; 39979721e0aSTakashi Iwai if (client->pwr_state == VGA_SWITCHEROO_OFF) 40079721e0aSTakashi Iwai vga_switchon(client); 4016a9ee8afSDave Airlie } 4026a9ee8afSDave Airlie goto out; 4036a9ee8afSDave Airlie } 4046a9ee8afSDave Airlie 4056a9ee8afSDave Airlie /* request a delayed switch - test can we switch now */ 4066a9ee8afSDave Airlie if (strncmp(usercmd, "DIGD", 4) == 0) { 4076a9ee8afSDave Airlie client_id = VGA_SWITCHEROO_IGD; 4086a9ee8afSDave Airlie delay = true; 4096a9ee8afSDave Airlie } 4106a9ee8afSDave Airlie 4116a9ee8afSDave Airlie if (strncmp(usercmd, "DDIS", 4) == 0) { 4126a9ee8afSDave Airlie client_id = VGA_SWITCHEROO_DIS; 4136a9ee8afSDave Airlie delay = true; 4146a9ee8afSDave Airlie } 4156a9ee8afSDave Airlie 4166a9ee8afSDave Airlie if (strncmp(usercmd, "IGD", 3) == 0) 4176a9ee8afSDave Airlie client_id = VGA_SWITCHEROO_IGD; 4186a9ee8afSDave Airlie 4196a9ee8afSDave Airlie if (strncmp(usercmd, "DIS", 3) == 0) 4206a9ee8afSDave Airlie client_id = VGA_SWITCHEROO_DIS; 4216a9ee8afSDave Airlie 422fea6f330SDan Carpenter if (strncmp(usercmd, "MIGD", 4) == 0) { 423851ab954SDave Airlie just_mux = true; 424851ab954SDave Airlie client_id = VGA_SWITCHEROO_IGD; 425851ab954SDave Airlie } 426fea6f330SDan Carpenter if (strncmp(usercmd, "MDIS", 4) == 0) { 427851ab954SDave Airlie just_mux = true; 428851ab954SDave Airlie client_id = VGA_SWITCHEROO_DIS; 429851ab954SDave Airlie } 430851ab954SDave Airlie 4316a9ee8afSDave Airlie if (client_id == -1) 4326a9ee8afSDave Airlie goto out; 43379721e0aSTakashi Iwai client = find_client_from_id(&vgasr_priv.clients, client_id); 43479721e0aSTakashi Iwai if (!client) 43579721e0aSTakashi Iwai goto out; 4366a9ee8afSDave Airlie 4376a9ee8afSDave Airlie vgasr_priv.delayed_switch_active = false; 438851ab954SDave Airlie 439851ab954SDave Airlie if (just_mux) { 440851ab954SDave Airlie ret = vgasr_priv.handler->switchto(client_id); 441851ab954SDave Airlie goto out; 442851ab954SDave Airlie } 443851ab954SDave Airlie 44479721e0aSTakashi Iwai if (client->active) 445a67b8887SFlorian Mickler goto out; 446a67b8887SFlorian Mickler 4476a9ee8afSDave Airlie /* okay we want a switch - test if devices are willing to switch */ 44879721e0aSTakashi Iwai can_switch = check_can_switch(); 4496a9ee8afSDave Airlie 4506a9ee8afSDave Airlie if (can_switch == false && delay == false) 4516a9ee8afSDave Airlie goto out; 4526a9ee8afSDave Airlie 45379721e0aSTakashi Iwai if (can_switch) { 4546a9ee8afSDave Airlie pdev_name = pci_name(client->pdev); 45566b37c67SDave Airlie ret = vga_switchto_stage1(client); 4566a9ee8afSDave Airlie if (ret) 45766b37c67SDave Airlie printk(KERN_ERR "vga_switcheroo: switching failed stage 1 %d\n", ret); 45866b37c67SDave Airlie 45966b37c67SDave Airlie ret = vga_switchto_stage2(client); 46066b37c67SDave Airlie if (ret) 46166b37c67SDave Airlie printk(KERN_ERR "vga_switcheroo: switching failed stage 2 %d\n", ret); 46266b37c67SDave Airlie 4636a9ee8afSDave Airlie } else { 4646a9ee8afSDave Airlie printk(KERN_INFO "vga_switcheroo: setting delayed switch to client %d\n", client->id); 4656a9ee8afSDave Airlie vgasr_priv.delayed_switch_active = true; 4666a9ee8afSDave Airlie vgasr_priv.delayed_client_id = client_id; 4676a9ee8afSDave Airlie 46866b37c67SDave Airlie ret = vga_switchto_stage1(client); 46966b37c67SDave Airlie if (ret) 47066b37c67SDave Airlie printk(KERN_ERR "vga_switcheroo: delayed switching stage 1 failed %d\n", ret); 4716a9ee8afSDave Airlie } 4726a9ee8afSDave Airlie 4736a9ee8afSDave Airlie out: 4746a9ee8afSDave Airlie mutex_unlock(&vgasr_mutex); 4756a9ee8afSDave Airlie return cnt; 4766a9ee8afSDave Airlie } 4776a9ee8afSDave Airlie 4786a9ee8afSDave Airlie static const struct file_operations vga_switcheroo_debugfs_fops = { 4796a9ee8afSDave Airlie .owner = THIS_MODULE, 4806a9ee8afSDave Airlie .open = vga_switcheroo_debugfs_open, 4816a9ee8afSDave Airlie .write = vga_switcheroo_debugfs_write, 4826a9ee8afSDave Airlie .read = seq_read, 4836a9ee8afSDave Airlie .llseek = seq_lseek, 4846a9ee8afSDave Airlie .release = single_release, 4856a9ee8afSDave Airlie }; 4866a9ee8afSDave Airlie 4876a9ee8afSDave Airlie static void vga_switcheroo_debugfs_fini(struct vgasr_priv *priv) 4886a9ee8afSDave Airlie { 4896a9ee8afSDave Airlie if (priv->switch_file) { 4906a9ee8afSDave Airlie debugfs_remove(priv->switch_file); 4916a9ee8afSDave Airlie priv->switch_file = NULL; 4926a9ee8afSDave Airlie } 4936a9ee8afSDave Airlie if (priv->debugfs_root) { 4946a9ee8afSDave Airlie debugfs_remove(priv->debugfs_root); 4956a9ee8afSDave Airlie priv->debugfs_root = NULL; 4966a9ee8afSDave Airlie } 4976a9ee8afSDave Airlie } 4986a9ee8afSDave Airlie 4996a9ee8afSDave Airlie static int vga_switcheroo_debugfs_init(struct vgasr_priv *priv) 5006a9ee8afSDave Airlie { 5016a9ee8afSDave Airlie /* already initialised */ 5026a9ee8afSDave Airlie if (priv->debugfs_root) 5036a9ee8afSDave Airlie return 0; 5046a9ee8afSDave Airlie priv->debugfs_root = debugfs_create_dir("vgaswitcheroo", NULL); 5056a9ee8afSDave Airlie 5066a9ee8afSDave Airlie if (!priv->debugfs_root) { 5076a9ee8afSDave Airlie printk(KERN_ERR "vga_switcheroo: Cannot create /sys/kernel/debug/vgaswitcheroo\n"); 5086a9ee8afSDave Airlie goto fail; 5096a9ee8afSDave Airlie } 5106a9ee8afSDave Airlie 5116a9ee8afSDave Airlie priv->switch_file = debugfs_create_file("switch", 0644, 5126a9ee8afSDave Airlie priv->debugfs_root, NULL, &vga_switcheroo_debugfs_fops); 5136a9ee8afSDave Airlie if (!priv->switch_file) { 5146a9ee8afSDave Airlie printk(KERN_ERR "vga_switcheroo: cannot create /sys/kernel/debug/vgaswitcheroo/switch\n"); 5156a9ee8afSDave Airlie goto fail; 5166a9ee8afSDave Airlie } 5176a9ee8afSDave Airlie return 0; 5186a9ee8afSDave Airlie fail: 5196a9ee8afSDave Airlie vga_switcheroo_debugfs_fini(priv); 5206a9ee8afSDave Airlie return -1; 5216a9ee8afSDave Airlie } 5226a9ee8afSDave Airlie 5236a9ee8afSDave Airlie int vga_switcheroo_process_delayed_switch(void) 5246a9ee8afSDave Airlie { 52579721e0aSTakashi Iwai struct vga_switcheroo_client *client; 5266a9ee8afSDave Airlie const char *pdev_name; 5276a9ee8afSDave Airlie int ret; 5286a9ee8afSDave Airlie int err = -EINVAL; 5296a9ee8afSDave Airlie 5306a9ee8afSDave Airlie mutex_lock(&vgasr_mutex); 5316a9ee8afSDave Airlie if (!vgasr_priv.delayed_switch_active) 5326a9ee8afSDave Airlie goto err; 5336a9ee8afSDave Airlie 5346a9ee8afSDave Airlie printk(KERN_INFO "vga_switcheroo: processing delayed switch to %d\n", vgasr_priv.delayed_client_id); 5356a9ee8afSDave Airlie 53679721e0aSTakashi Iwai client = find_client_from_id(&vgasr_priv.clients, 53779721e0aSTakashi Iwai vgasr_priv.delayed_client_id); 53879721e0aSTakashi Iwai if (!client || !check_can_switch()) 5396a9ee8afSDave Airlie goto err; 5406a9ee8afSDave Airlie 5416a9ee8afSDave Airlie pdev_name = pci_name(client->pdev); 54266b37c67SDave Airlie ret = vga_switchto_stage2(client); 5436a9ee8afSDave Airlie if (ret) 54466b37c67SDave Airlie printk(KERN_ERR "vga_switcheroo: delayed switching failed stage 2 %d\n", ret); 5456a9ee8afSDave Airlie 5466a9ee8afSDave Airlie vgasr_priv.delayed_switch_active = false; 5476a9ee8afSDave Airlie err = 0; 5486a9ee8afSDave Airlie err: 5496a9ee8afSDave Airlie mutex_unlock(&vgasr_mutex); 5506a9ee8afSDave Airlie return err; 5516a9ee8afSDave Airlie } 5526a9ee8afSDave Airlie EXPORT_SYMBOL(vga_switcheroo_process_delayed_switch); 5536a9ee8afSDave Airlie 554