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 971309278SThierry Reding * 1071309278SThierry Reding * Switcher interface - methods require for ATPX and DCM 1171309278SThierry Reding * - switchto - this throws the output MUX switch 1271309278SThierry Reding * - discrete_set_power - sets the power state for the discrete card 1371309278SThierry Reding * 1471309278SThierry Reding * GPU driver interface 1571309278SThierry Reding * - set_gpu_state - this should do the equiv of s/r for the card 1671309278SThierry Reding * - this should *not* set the discrete power state 1771309278SThierry Reding * - switch_check - check if the device is in a position to switch now 186a9ee8afSDave Airlie */ 196a9ee8afSDave Airlie 209b0be1ebSThierry Reding #define pr_fmt(fmt) "vga_switcheroo: " fmt 219b0be1ebSThierry Reding 226a9ee8afSDave Airlie #include <linux/module.h> 236a9ee8afSDave Airlie #include <linux/seq_file.h> 246a9ee8afSDave Airlie #include <linux/uaccess.h> 256a9ee8afSDave Airlie #include <linux/fs.h> 266a9ee8afSDave Airlie #include <linux/debugfs.h> 276a9ee8afSDave Airlie #include <linux/fb.h> 286a9ee8afSDave Airlie 296a9ee8afSDave Airlie #include <linux/pci.h> 30054430e7SDave Airlie #include <linux/console.h> 316a9ee8afSDave Airlie #include <linux/vga_switcheroo.h> 320d69704aSDave Airlie #include <linux/pm_runtime.h> 336a9ee8afSDave Airlie 342fbe8c7cSMatthew Garrett #include <linux/vgaarb.h> 352fbe8c7cSMatthew Garrett 366a9ee8afSDave Airlie struct vga_switcheroo_client { 376a9ee8afSDave Airlie struct pci_dev *pdev; 386a9ee8afSDave Airlie struct fb_info *fb_info; 396a9ee8afSDave Airlie int pwr_state; 4026ec685fSTakashi Iwai const struct vga_switcheroo_client_ops *ops; 416a9ee8afSDave Airlie int id; 426a9ee8afSDave Airlie bool active; 430d69704aSDave Airlie bool driver_power_control; 4479721e0aSTakashi Iwai struct list_head list; 456a9ee8afSDave Airlie }; 466a9ee8afSDave Airlie 476a9ee8afSDave Airlie static DEFINE_MUTEX(vgasr_mutex); 486a9ee8afSDave Airlie 496a9ee8afSDave Airlie struct vgasr_priv { 506a9ee8afSDave Airlie 516a9ee8afSDave Airlie bool active; 526a9ee8afSDave Airlie bool delayed_switch_active; 536a9ee8afSDave Airlie enum vga_switcheroo_client_id delayed_client_id; 546a9ee8afSDave Airlie 556a9ee8afSDave Airlie struct dentry *debugfs_root; 566a9ee8afSDave Airlie struct dentry *switch_file; 576a9ee8afSDave Airlie 586a9ee8afSDave Airlie int registered_clients; 5979721e0aSTakashi Iwai struct list_head clients; 606a9ee8afSDave Airlie 616a9ee8afSDave Airlie struct vga_switcheroo_handler *handler; 626a9ee8afSDave Airlie }; 636a9ee8afSDave Airlie 643e9e63dbSTakashi Iwai #define ID_BIT_AUDIO 0x100 653e9e63dbSTakashi Iwai #define client_is_audio(c) ((c)->id & ID_BIT_AUDIO) 663e9e63dbSTakashi Iwai #define client_is_vga(c) ((c)->id == -1 || !client_is_audio(c)) 673e9e63dbSTakashi Iwai #define client_id(c) ((c)->id & ~ID_BIT_AUDIO) 683e9e63dbSTakashi Iwai 696a9ee8afSDave Airlie static int vga_switcheroo_debugfs_init(struct vgasr_priv *priv); 706a9ee8afSDave Airlie static void vga_switcheroo_debugfs_fini(struct vgasr_priv *priv); 716a9ee8afSDave Airlie 726a9ee8afSDave Airlie /* only one switcheroo per system */ 7379721e0aSTakashi Iwai static struct vgasr_priv vgasr_priv = { 7479721e0aSTakashi Iwai .clients = LIST_HEAD_INIT(vgasr_priv.clients), 7579721e0aSTakashi Iwai }; 766a9ee8afSDave Airlie 7736704c0cSSeth Forshee static bool vga_switcheroo_ready(void) 786a9ee8afSDave Airlie { 7936704c0cSSeth Forshee /* we're ready if we get two clients + handler */ 8036704c0cSSeth Forshee return !vgasr_priv.active && 8136704c0cSSeth Forshee vgasr_priv.registered_clients == 2 && vgasr_priv.handler; 826a9ee8afSDave Airlie } 836a9ee8afSDave Airlie 846a9ee8afSDave Airlie static void vga_switcheroo_enable(void) 856a9ee8afSDave Airlie { 866a9ee8afSDave Airlie int ret; 8779721e0aSTakashi Iwai struct vga_switcheroo_client *client; 8879721e0aSTakashi Iwai 896a9ee8afSDave Airlie /* call the handler to init */ 90e99eac5eSSeth Forshee if (vgasr_priv.handler->init) 916a9ee8afSDave Airlie vgasr_priv.handler->init(); 926a9ee8afSDave Airlie 9379721e0aSTakashi Iwai list_for_each_entry(client, &vgasr_priv.clients, list) { 943e9e63dbSTakashi Iwai if (client->id != -1) 953e9e63dbSTakashi Iwai continue; 9679721e0aSTakashi Iwai ret = vgasr_priv.handler->get_client_id(client->pdev); 976a9ee8afSDave Airlie if (ret < 0) 986a9ee8afSDave Airlie return; 996a9ee8afSDave Airlie 10079721e0aSTakashi Iwai client->id = ret; 1016a9ee8afSDave Airlie } 1026a9ee8afSDave Airlie vga_switcheroo_debugfs_init(&vgasr_priv); 1036a9ee8afSDave Airlie vgasr_priv.active = true; 1046a9ee8afSDave Airlie } 1056a9ee8afSDave Airlie 10636704c0cSSeth Forshee int vga_switcheroo_register_handler(struct vga_switcheroo_handler *handler) 10736704c0cSSeth Forshee { 10836704c0cSSeth Forshee mutex_lock(&vgasr_mutex); 10936704c0cSSeth Forshee if (vgasr_priv.handler) { 11036704c0cSSeth Forshee mutex_unlock(&vgasr_mutex); 11136704c0cSSeth Forshee return -EINVAL; 11236704c0cSSeth Forshee } 11336704c0cSSeth Forshee 11436704c0cSSeth Forshee vgasr_priv.handler = handler; 11536704c0cSSeth Forshee if (vga_switcheroo_ready()) { 1169b0be1ebSThierry Reding pr_info("enabled\n"); 11736704c0cSSeth Forshee vga_switcheroo_enable(); 11836704c0cSSeth Forshee } 11936704c0cSSeth Forshee mutex_unlock(&vgasr_mutex); 12036704c0cSSeth Forshee return 0; 12136704c0cSSeth Forshee } 12236704c0cSSeth Forshee EXPORT_SYMBOL(vga_switcheroo_register_handler); 12336704c0cSSeth Forshee 12436704c0cSSeth Forshee void vga_switcheroo_unregister_handler(void) 12536704c0cSSeth Forshee { 12636704c0cSSeth Forshee mutex_lock(&vgasr_mutex); 12736704c0cSSeth Forshee vgasr_priv.handler = NULL; 12836704c0cSSeth Forshee if (vgasr_priv.active) { 1299b0be1ebSThierry Reding pr_info("disabled\n"); 13036704c0cSSeth Forshee vga_switcheroo_debugfs_fini(&vgasr_priv); 13136704c0cSSeth Forshee vgasr_priv.active = false; 13236704c0cSSeth Forshee } 13336704c0cSSeth Forshee mutex_unlock(&vgasr_mutex); 13436704c0cSSeth Forshee } 13536704c0cSSeth Forshee EXPORT_SYMBOL(vga_switcheroo_unregister_handler); 13636704c0cSSeth Forshee 1373e9e63dbSTakashi Iwai static int register_client(struct pci_dev *pdev, 1383e9e63dbSTakashi Iwai const struct vga_switcheroo_client_ops *ops, 1390d69704aSDave Airlie int id, bool active, bool driver_power_control) 1406a9ee8afSDave Airlie { 14179721e0aSTakashi Iwai struct vga_switcheroo_client *client; 14279721e0aSTakashi Iwai 14379721e0aSTakashi Iwai client = kzalloc(sizeof(*client), GFP_KERNEL); 14479721e0aSTakashi Iwai if (!client) 14579721e0aSTakashi Iwai return -ENOMEM; 14679721e0aSTakashi Iwai 14779721e0aSTakashi Iwai client->pwr_state = VGA_SWITCHEROO_ON; 14879721e0aSTakashi Iwai client->pdev = pdev; 14926ec685fSTakashi Iwai client->ops = ops; 1503e9e63dbSTakashi Iwai client->id = id; 1513e9e63dbSTakashi Iwai client->active = active; 1520d69704aSDave Airlie client->driver_power_control = driver_power_control; 1536a9ee8afSDave Airlie 1546a9ee8afSDave Airlie mutex_lock(&vgasr_mutex); 15579721e0aSTakashi Iwai list_add_tail(&client->list, &vgasr_priv.clients); 1563e9e63dbSTakashi Iwai if (client_is_vga(client)) 15779721e0aSTakashi Iwai vgasr_priv.registered_clients++; 1586a9ee8afSDave Airlie 15936704c0cSSeth Forshee if (vga_switcheroo_ready()) { 1609b0be1ebSThierry Reding pr_info("enabled\n"); 1616a9ee8afSDave Airlie vga_switcheroo_enable(); 1626a9ee8afSDave Airlie } 1636a9ee8afSDave Airlie mutex_unlock(&vgasr_mutex); 1646a9ee8afSDave Airlie return 0; 1656a9ee8afSDave Airlie } 1663e9e63dbSTakashi Iwai 1673e9e63dbSTakashi Iwai int vga_switcheroo_register_client(struct pci_dev *pdev, 1680d69704aSDave Airlie const struct vga_switcheroo_client_ops *ops, 1690d69704aSDave Airlie bool driver_power_control) 1703e9e63dbSTakashi Iwai { 1713e9e63dbSTakashi Iwai return register_client(pdev, ops, -1, 1720d69704aSDave Airlie pdev == vga_default_device(), driver_power_control); 1733e9e63dbSTakashi Iwai } 1746a9ee8afSDave Airlie EXPORT_SYMBOL(vga_switcheroo_register_client); 1756a9ee8afSDave Airlie 1763e9e63dbSTakashi Iwai int vga_switcheroo_register_audio_client(struct pci_dev *pdev, 1773e9e63dbSTakashi Iwai const struct vga_switcheroo_client_ops *ops, 1783e9e63dbSTakashi Iwai int id, bool active) 1793e9e63dbSTakashi Iwai { 1800d69704aSDave Airlie return register_client(pdev, ops, id | ID_BIT_AUDIO, active, false); 1813e9e63dbSTakashi Iwai } 1823e9e63dbSTakashi Iwai EXPORT_SYMBOL(vga_switcheroo_register_audio_client); 1833e9e63dbSTakashi Iwai 18479721e0aSTakashi Iwai static struct vga_switcheroo_client * 18579721e0aSTakashi Iwai find_client_from_pci(struct list_head *head, struct pci_dev *pdev) 18679721e0aSTakashi Iwai { 18779721e0aSTakashi Iwai struct vga_switcheroo_client *client; 18879721e0aSTakashi Iwai list_for_each_entry(client, head, list) 18979721e0aSTakashi Iwai if (client->pdev == pdev) 19079721e0aSTakashi Iwai return client; 19179721e0aSTakashi Iwai return NULL; 19279721e0aSTakashi Iwai } 19379721e0aSTakashi Iwai 19479721e0aSTakashi Iwai static struct vga_switcheroo_client * 19579721e0aSTakashi Iwai find_client_from_id(struct list_head *head, int client_id) 19679721e0aSTakashi Iwai { 19779721e0aSTakashi Iwai struct vga_switcheroo_client *client; 19879721e0aSTakashi Iwai list_for_each_entry(client, head, list) 19979721e0aSTakashi Iwai if (client->id == client_id) 20079721e0aSTakashi Iwai return client; 20179721e0aSTakashi Iwai return NULL; 20279721e0aSTakashi Iwai } 20379721e0aSTakashi Iwai 20479721e0aSTakashi Iwai static struct vga_switcheroo_client * 20579721e0aSTakashi Iwai find_active_client(struct list_head *head) 20679721e0aSTakashi Iwai { 20779721e0aSTakashi Iwai struct vga_switcheroo_client *client; 20879721e0aSTakashi Iwai list_for_each_entry(client, head, list) 2093e9e63dbSTakashi Iwai if (client->active && client_is_vga(client)) 21079721e0aSTakashi Iwai return client; 21179721e0aSTakashi Iwai return NULL; 21279721e0aSTakashi Iwai } 21379721e0aSTakashi Iwai 214c8e9cf7bSTakashi Iwai int vga_switcheroo_get_client_state(struct pci_dev *pdev) 215c8e9cf7bSTakashi Iwai { 216c8e9cf7bSTakashi Iwai struct vga_switcheroo_client *client; 217c8e9cf7bSTakashi Iwai 218c8e9cf7bSTakashi Iwai client = find_client_from_pci(&vgasr_priv.clients, pdev); 219c8e9cf7bSTakashi Iwai if (!client) 220c8e9cf7bSTakashi Iwai return VGA_SWITCHEROO_NOT_FOUND; 221c8e9cf7bSTakashi Iwai if (!vgasr_priv.active) 222c8e9cf7bSTakashi Iwai return VGA_SWITCHEROO_INIT; 223c8e9cf7bSTakashi Iwai return client->pwr_state; 224c8e9cf7bSTakashi Iwai } 225c8e9cf7bSTakashi Iwai EXPORT_SYMBOL(vga_switcheroo_get_client_state); 226c8e9cf7bSTakashi Iwai 2276a9ee8afSDave Airlie void vga_switcheroo_unregister_client(struct pci_dev *pdev) 2286a9ee8afSDave Airlie { 22979721e0aSTakashi Iwai struct vga_switcheroo_client *client; 2306a9ee8afSDave Airlie 2316a9ee8afSDave Airlie mutex_lock(&vgasr_mutex); 23279721e0aSTakashi Iwai client = find_client_from_pci(&vgasr_priv.clients, pdev); 23379721e0aSTakashi Iwai if (client) { 2343e9e63dbSTakashi Iwai if (client_is_vga(client)) 2353e9e63dbSTakashi Iwai vgasr_priv.registered_clients--; 23679721e0aSTakashi Iwai list_del(&client->list); 23779721e0aSTakashi Iwai kfree(client); 2386a9ee8afSDave Airlie } 2393e9e63dbSTakashi Iwai if (vgasr_priv.active && vgasr_priv.registered_clients < 2) { 2409b0be1ebSThierry Reding pr_info("disabled\n"); 2416a9ee8afSDave Airlie vga_switcheroo_debugfs_fini(&vgasr_priv); 2426a9ee8afSDave Airlie vgasr_priv.active = false; 2433e9e63dbSTakashi Iwai } 2446a9ee8afSDave Airlie mutex_unlock(&vgasr_mutex); 2456a9ee8afSDave Airlie } 2466a9ee8afSDave Airlie EXPORT_SYMBOL(vga_switcheroo_unregister_client); 2476a9ee8afSDave Airlie 2486a9ee8afSDave Airlie void vga_switcheroo_client_fb_set(struct pci_dev *pdev, 2496a9ee8afSDave Airlie struct fb_info *info) 2506a9ee8afSDave Airlie { 25179721e0aSTakashi Iwai struct vga_switcheroo_client *client; 2526a9ee8afSDave Airlie 2536a9ee8afSDave Airlie mutex_lock(&vgasr_mutex); 25479721e0aSTakashi Iwai client = find_client_from_pci(&vgasr_priv.clients, pdev); 25579721e0aSTakashi Iwai if (client) 25679721e0aSTakashi Iwai client->fb_info = info; 2576a9ee8afSDave Airlie mutex_unlock(&vgasr_mutex); 2586a9ee8afSDave Airlie } 2596a9ee8afSDave Airlie EXPORT_SYMBOL(vga_switcheroo_client_fb_set); 2606a9ee8afSDave Airlie 2616a9ee8afSDave Airlie static int vga_switcheroo_show(struct seq_file *m, void *v) 2626a9ee8afSDave Airlie { 26379721e0aSTakashi Iwai struct vga_switcheroo_client *client; 26479721e0aSTakashi Iwai int i = 0; 2656a9ee8afSDave Airlie mutex_lock(&vgasr_mutex); 26679721e0aSTakashi Iwai list_for_each_entry(client, &vgasr_priv.clients, list) { 2670d69704aSDave Airlie seq_printf(m, "%d:%s%s:%c:%s%s:%s\n", i, 2683e9e63dbSTakashi Iwai client_id(client) == VGA_SWITCHEROO_DIS ? "DIS" : "IGD", 2693e9e63dbSTakashi Iwai client_is_vga(client) ? "" : "-Audio", 27079721e0aSTakashi Iwai client->active ? '+' : ' ', 2710d69704aSDave Airlie client->driver_power_control ? "Dyn" : "", 27279721e0aSTakashi Iwai client->pwr_state ? "Pwr" : "Off", 27379721e0aSTakashi Iwai pci_name(client->pdev)); 27479721e0aSTakashi Iwai i++; 2756a9ee8afSDave Airlie } 2766a9ee8afSDave Airlie mutex_unlock(&vgasr_mutex); 2776a9ee8afSDave Airlie return 0; 2786a9ee8afSDave Airlie } 2796a9ee8afSDave Airlie 2806a9ee8afSDave Airlie static int vga_switcheroo_debugfs_open(struct inode *inode, struct file *file) 2816a9ee8afSDave Airlie { 2826a9ee8afSDave Airlie return single_open(file, vga_switcheroo_show, NULL); 2836a9ee8afSDave Airlie } 2846a9ee8afSDave Airlie 2856a9ee8afSDave Airlie static int vga_switchon(struct vga_switcheroo_client *client) 2866a9ee8afSDave Airlie { 2870d69704aSDave Airlie if (client->driver_power_control) 2880d69704aSDave Airlie return 0; 2895cfb3c3aSDave Airlie if (vgasr_priv.handler->power_state) 2905cfb3c3aSDave Airlie vgasr_priv.handler->power_state(client->id, VGA_SWITCHEROO_ON); 2916a9ee8afSDave Airlie /* call the driver callback to turn on device */ 29226ec685fSTakashi Iwai client->ops->set_gpu_state(client->pdev, VGA_SWITCHEROO_ON); 2936a9ee8afSDave Airlie client->pwr_state = VGA_SWITCHEROO_ON; 2946a9ee8afSDave Airlie return 0; 2956a9ee8afSDave Airlie } 2966a9ee8afSDave Airlie 2976a9ee8afSDave Airlie static int vga_switchoff(struct vga_switcheroo_client *client) 2986a9ee8afSDave Airlie { 2990d69704aSDave Airlie if (client->driver_power_control) 3000d69704aSDave Airlie return 0; 3016a9ee8afSDave Airlie /* call the driver callback to turn off device */ 30226ec685fSTakashi Iwai client->ops->set_gpu_state(client->pdev, VGA_SWITCHEROO_OFF); 3035cfb3c3aSDave Airlie if (vgasr_priv.handler->power_state) 3046a9ee8afSDave Airlie vgasr_priv.handler->power_state(client->id, VGA_SWITCHEROO_OFF); 3056a9ee8afSDave Airlie client->pwr_state = VGA_SWITCHEROO_OFF; 3066a9ee8afSDave Airlie return 0; 3076a9ee8afSDave Airlie } 3086a9ee8afSDave Airlie 3093e9e63dbSTakashi Iwai static void set_audio_state(int id, int state) 3103e9e63dbSTakashi Iwai { 3113e9e63dbSTakashi Iwai struct vga_switcheroo_client *client; 3123e9e63dbSTakashi Iwai 3133e9e63dbSTakashi Iwai client = find_client_from_id(&vgasr_priv.clients, id | ID_BIT_AUDIO); 3143e9e63dbSTakashi Iwai if (client && client->pwr_state != state) { 3153e9e63dbSTakashi Iwai client->ops->set_gpu_state(client->pdev, state); 3163e9e63dbSTakashi Iwai client->pwr_state = state; 3173e9e63dbSTakashi Iwai } 3183e9e63dbSTakashi Iwai } 3193e9e63dbSTakashi Iwai 32066b37c67SDave Airlie /* stage one happens before delay */ 32166b37c67SDave Airlie static int vga_switchto_stage1(struct vga_switcheroo_client *new_client) 3226a9ee8afSDave Airlie { 32379721e0aSTakashi Iwai struct vga_switcheroo_client *active; 3246a9ee8afSDave Airlie 32579721e0aSTakashi Iwai active = find_active_client(&vgasr_priv.clients); 3266a9ee8afSDave Airlie if (!active) 3276a9ee8afSDave Airlie return 0; 3286a9ee8afSDave Airlie 3296a9ee8afSDave Airlie if (new_client->pwr_state == VGA_SWITCHEROO_OFF) 3306a9ee8afSDave Airlie vga_switchon(new_client); 3316a9ee8afSDave Airlie 3322fbe8c7cSMatthew Garrett vga_set_default_device(new_client->pdev); 33366b37c67SDave Airlie return 0; 33466b37c67SDave Airlie } 33566b37c67SDave Airlie 33666b37c67SDave Airlie /* post delay */ 33766b37c67SDave Airlie static int vga_switchto_stage2(struct vga_switcheroo_client *new_client) 33866b37c67SDave Airlie { 33966b37c67SDave Airlie int ret; 34079721e0aSTakashi Iwai struct vga_switcheroo_client *active; 34166b37c67SDave Airlie 34279721e0aSTakashi Iwai active = find_active_client(&vgasr_priv.clients); 34366b37c67SDave Airlie if (!active) 34466b37c67SDave Airlie return 0; 34566b37c67SDave Airlie 34666b37c67SDave Airlie active->active = false; 3476a9ee8afSDave Airlie 348c91c3faeSTakashi Iwai set_audio_state(active->id, VGA_SWITCHEROO_OFF); 349c91c3faeSTakashi Iwai 3506a9ee8afSDave Airlie if (new_client->fb_info) { 3516a9ee8afSDave Airlie struct fb_event event; 352054430e7SDave Airlie console_lock(); 3536a9ee8afSDave Airlie event.info = new_client->fb_info; 3546a9ee8afSDave Airlie fb_notifier_call_chain(FB_EVENT_REMAP_ALL_CONSOLE, &event); 355054430e7SDave Airlie console_unlock(); 3566a9ee8afSDave Airlie } 3576a9ee8afSDave Airlie 3586a9ee8afSDave Airlie ret = vgasr_priv.handler->switchto(new_client->id); 3596a9ee8afSDave Airlie if (ret) 3606a9ee8afSDave Airlie return ret; 3616a9ee8afSDave Airlie 36226ec685fSTakashi Iwai if (new_client->ops->reprobe) 36326ec685fSTakashi Iwai new_client->ops->reprobe(new_client->pdev); 3648d608aa6SDave Airlie 3656a9ee8afSDave Airlie if (active->pwr_state == VGA_SWITCHEROO_ON) 3666a9ee8afSDave Airlie vga_switchoff(active); 3676a9ee8afSDave Airlie 368c91c3faeSTakashi Iwai set_audio_state(new_client->id, VGA_SWITCHEROO_ON); 369c91c3faeSTakashi Iwai 3706a9ee8afSDave Airlie new_client->active = true; 3716a9ee8afSDave Airlie return 0; 3726a9ee8afSDave Airlie } 3736a9ee8afSDave Airlie 37479721e0aSTakashi Iwai static bool check_can_switch(void) 37579721e0aSTakashi Iwai { 37679721e0aSTakashi Iwai struct vga_switcheroo_client *client; 37779721e0aSTakashi Iwai 37879721e0aSTakashi Iwai list_for_each_entry(client, &vgasr_priv.clients, list) { 37926ec685fSTakashi Iwai if (!client->ops->can_switch(client->pdev)) { 3809b0be1ebSThierry Reding pr_err("client %x refused switch\n", client->id); 38179721e0aSTakashi Iwai return false; 38279721e0aSTakashi Iwai } 38379721e0aSTakashi Iwai } 38479721e0aSTakashi Iwai return true; 38579721e0aSTakashi Iwai } 38679721e0aSTakashi Iwai 3876a9ee8afSDave Airlie static ssize_t 3886a9ee8afSDave Airlie vga_switcheroo_debugfs_write(struct file *filp, const char __user *ubuf, 3896a9ee8afSDave Airlie size_t cnt, loff_t *ppos) 3906a9ee8afSDave Airlie { 3916a9ee8afSDave Airlie char usercmd[64]; 39279721e0aSTakashi Iwai int ret; 3936a9ee8afSDave Airlie bool delay = false, can_switch; 394851ab954SDave Airlie bool just_mux = false; 3956a9ee8afSDave Airlie int client_id = -1; 3966a9ee8afSDave Airlie struct vga_switcheroo_client *client = NULL; 3976a9ee8afSDave Airlie 3986a9ee8afSDave Airlie if (cnt > 63) 3996a9ee8afSDave Airlie cnt = 63; 4006a9ee8afSDave Airlie 4016a9ee8afSDave Airlie if (copy_from_user(usercmd, ubuf, cnt)) 4026a9ee8afSDave Airlie return -EFAULT; 4036a9ee8afSDave Airlie 4046a9ee8afSDave Airlie mutex_lock(&vgasr_mutex); 4056a9ee8afSDave Airlie 4068c88e50bSJiri Slaby if (!vgasr_priv.active) { 4078c88e50bSJiri Slaby cnt = -EINVAL; 4088c88e50bSJiri Slaby goto out; 4098c88e50bSJiri Slaby } 4106a9ee8afSDave Airlie 4116a9ee8afSDave Airlie /* pwr off the device not in use */ 4126a9ee8afSDave Airlie if (strncmp(usercmd, "OFF", 3) == 0) { 41379721e0aSTakashi Iwai list_for_each_entry(client, &vgasr_priv.clients, list) { 414c91c3faeSTakashi Iwai if (client->active || client_is_audio(client)) 4156a9ee8afSDave Airlie continue; 4160d69704aSDave Airlie if (client->driver_power_control) 4170d69704aSDave Airlie continue; 418c91c3faeSTakashi Iwai set_audio_state(client->id, VGA_SWITCHEROO_OFF); 41979721e0aSTakashi Iwai if (client->pwr_state == VGA_SWITCHEROO_ON) 42079721e0aSTakashi Iwai vga_switchoff(client); 4216a9ee8afSDave Airlie } 4226a9ee8afSDave Airlie goto out; 4236a9ee8afSDave Airlie } 4246a9ee8afSDave Airlie /* pwr on the device not in use */ 4256a9ee8afSDave Airlie if (strncmp(usercmd, "ON", 2) == 0) { 42679721e0aSTakashi Iwai list_for_each_entry(client, &vgasr_priv.clients, list) { 427c91c3faeSTakashi Iwai if (client->active || client_is_audio(client)) 4286a9ee8afSDave Airlie continue; 4290d69704aSDave Airlie if (client->driver_power_control) 4300d69704aSDave Airlie continue; 43179721e0aSTakashi Iwai if (client->pwr_state == VGA_SWITCHEROO_OFF) 43279721e0aSTakashi Iwai vga_switchon(client); 433c91c3faeSTakashi Iwai set_audio_state(client->id, VGA_SWITCHEROO_ON); 4346a9ee8afSDave Airlie } 4356a9ee8afSDave Airlie goto out; 4366a9ee8afSDave Airlie } 4376a9ee8afSDave Airlie 4386a9ee8afSDave Airlie /* request a delayed switch - test can we switch now */ 4396a9ee8afSDave Airlie if (strncmp(usercmd, "DIGD", 4) == 0) { 4406a9ee8afSDave Airlie client_id = VGA_SWITCHEROO_IGD; 4416a9ee8afSDave Airlie delay = true; 4426a9ee8afSDave Airlie } 4436a9ee8afSDave Airlie 4446a9ee8afSDave Airlie if (strncmp(usercmd, "DDIS", 4) == 0) { 4456a9ee8afSDave Airlie client_id = VGA_SWITCHEROO_DIS; 4466a9ee8afSDave Airlie delay = true; 4476a9ee8afSDave Airlie } 4486a9ee8afSDave Airlie 4496a9ee8afSDave Airlie if (strncmp(usercmd, "IGD", 3) == 0) 4506a9ee8afSDave Airlie client_id = VGA_SWITCHEROO_IGD; 4516a9ee8afSDave Airlie 4526a9ee8afSDave Airlie if (strncmp(usercmd, "DIS", 3) == 0) 4536a9ee8afSDave Airlie client_id = VGA_SWITCHEROO_DIS; 4546a9ee8afSDave Airlie 455fea6f330SDan Carpenter if (strncmp(usercmd, "MIGD", 4) == 0) { 456851ab954SDave Airlie just_mux = true; 457851ab954SDave Airlie client_id = VGA_SWITCHEROO_IGD; 458851ab954SDave Airlie } 459fea6f330SDan Carpenter if (strncmp(usercmd, "MDIS", 4) == 0) { 460851ab954SDave Airlie just_mux = true; 461851ab954SDave Airlie client_id = VGA_SWITCHEROO_DIS; 462851ab954SDave Airlie } 463851ab954SDave Airlie 4646a9ee8afSDave Airlie if (client_id == -1) 4656a9ee8afSDave Airlie goto out; 46679721e0aSTakashi Iwai client = find_client_from_id(&vgasr_priv.clients, client_id); 46779721e0aSTakashi Iwai if (!client) 46879721e0aSTakashi Iwai goto out; 4696a9ee8afSDave Airlie 4706a9ee8afSDave Airlie vgasr_priv.delayed_switch_active = false; 471851ab954SDave Airlie 472851ab954SDave Airlie if (just_mux) { 473851ab954SDave Airlie ret = vgasr_priv.handler->switchto(client_id); 474851ab954SDave Airlie goto out; 475851ab954SDave Airlie } 476851ab954SDave Airlie 47779721e0aSTakashi Iwai if (client->active) 478a67b8887SFlorian Mickler goto out; 479a67b8887SFlorian Mickler 4806a9ee8afSDave Airlie /* okay we want a switch - test if devices are willing to switch */ 48179721e0aSTakashi Iwai can_switch = check_can_switch(); 4826a9ee8afSDave Airlie 4836a9ee8afSDave Airlie if (can_switch == false && delay == false) 4846a9ee8afSDave Airlie goto out; 4856a9ee8afSDave Airlie 48679721e0aSTakashi Iwai if (can_switch) { 48766b37c67SDave Airlie ret = vga_switchto_stage1(client); 4886a9ee8afSDave Airlie if (ret) 4899b0be1ebSThierry Reding pr_err("switching failed stage 1 %d\n", ret); 49066b37c67SDave Airlie 49166b37c67SDave Airlie ret = vga_switchto_stage2(client); 49266b37c67SDave Airlie if (ret) 4939b0be1ebSThierry Reding pr_err("switching failed stage 2 %d\n", ret); 49466b37c67SDave Airlie 4956a9ee8afSDave Airlie } else { 4969b0be1ebSThierry Reding pr_info("setting delayed switch to client %d\n", client->id); 4976a9ee8afSDave Airlie vgasr_priv.delayed_switch_active = true; 4986a9ee8afSDave Airlie vgasr_priv.delayed_client_id = client_id; 4996a9ee8afSDave Airlie 50066b37c67SDave Airlie ret = vga_switchto_stage1(client); 50166b37c67SDave Airlie if (ret) 5029b0be1ebSThierry Reding pr_err("delayed switching stage 1 failed %d\n", ret); 5036a9ee8afSDave Airlie } 5046a9ee8afSDave Airlie 5056a9ee8afSDave Airlie out: 5066a9ee8afSDave Airlie mutex_unlock(&vgasr_mutex); 5076a9ee8afSDave Airlie return cnt; 5086a9ee8afSDave Airlie } 5096a9ee8afSDave Airlie 5106a9ee8afSDave Airlie static const struct file_operations vga_switcheroo_debugfs_fops = { 5116a9ee8afSDave Airlie .owner = THIS_MODULE, 5126a9ee8afSDave Airlie .open = vga_switcheroo_debugfs_open, 5136a9ee8afSDave Airlie .write = vga_switcheroo_debugfs_write, 5146a9ee8afSDave Airlie .read = seq_read, 5156a9ee8afSDave Airlie .llseek = seq_lseek, 5166a9ee8afSDave Airlie .release = single_release, 5176a9ee8afSDave Airlie }; 5186a9ee8afSDave Airlie 5196a9ee8afSDave Airlie static void vga_switcheroo_debugfs_fini(struct vgasr_priv *priv) 5206a9ee8afSDave Airlie { 5216a9ee8afSDave Airlie if (priv->switch_file) { 5226a9ee8afSDave Airlie debugfs_remove(priv->switch_file); 5236a9ee8afSDave Airlie priv->switch_file = NULL; 5246a9ee8afSDave Airlie } 5256a9ee8afSDave Airlie if (priv->debugfs_root) { 5266a9ee8afSDave Airlie debugfs_remove(priv->debugfs_root); 5276a9ee8afSDave Airlie priv->debugfs_root = NULL; 5286a9ee8afSDave Airlie } 5296a9ee8afSDave Airlie } 5306a9ee8afSDave Airlie 5316a9ee8afSDave Airlie static int vga_switcheroo_debugfs_init(struct vgasr_priv *priv) 5326a9ee8afSDave Airlie { 5339b0be1ebSThierry Reding static const char mp[] = "/sys/kernel/debug"; 5349b0be1ebSThierry Reding 5356a9ee8afSDave Airlie /* already initialised */ 5366a9ee8afSDave Airlie if (priv->debugfs_root) 5376a9ee8afSDave Airlie return 0; 5386a9ee8afSDave Airlie priv->debugfs_root = debugfs_create_dir("vgaswitcheroo", NULL); 5396a9ee8afSDave Airlie 5406a9ee8afSDave Airlie if (!priv->debugfs_root) { 5419b0be1ebSThierry Reding pr_err("Cannot create %s/vgaswitcheroo\n", mp); 5426a9ee8afSDave Airlie goto fail; 5436a9ee8afSDave Airlie } 5446a9ee8afSDave Airlie 5456a9ee8afSDave Airlie priv->switch_file = debugfs_create_file("switch", 0644, 5466a9ee8afSDave Airlie priv->debugfs_root, NULL, &vga_switcheroo_debugfs_fops); 5476a9ee8afSDave Airlie if (!priv->switch_file) { 5489b0be1ebSThierry Reding pr_err("cannot create %s/vgaswitcheroo/switch\n", mp); 5496a9ee8afSDave Airlie goto fail; 5506a9ee8afSDave Airlie } 5516a9ee8afSDave Airlie return 0; 5526a9ee8afSDave Airlie fail: 5536a9ee8afSDave Airlie vga_switcheroo_debugfs_fini(priv); 5546a9ee8afSDave Airlie return -1; 5556a9ee8afSDave Airlie } 5566a9ee8afSDave Airlie 5576a9ee8afSDave Airlie int vga_switcheroo_process_delayed_switch(void) 5586a9ee8afSDave Airlie { 55979721e0aSTakashi Iwai struct vga_switcheroo_client *client; 5606a9ee8afSDave Airlie int ret; 5616a9ee8afSDave Airlie int err = -EINVAL; 5626a9ee8afSDave Airlie 5636a9ee8afSDave Airlie mutex_lock(&vgasr_mutex); 5646a9ee8afSDave Airlie if (!vgasr_priv.delayed_switch_active) 5656a9ee8afSDave Airlie goto err; 5666a9ee8afSDave Airlie 5679b0be1ebSThierry Reding pr_info("processing delayed switch to %d\n", 5689b0be1ebSThierry Reding vgasr_priv.delayed_client_id); 5696a9ee8afSDave Airlie 57079721e0aSTakashi Iwai client = find_client_from_id(&vgasr_priv.clients, 57179721e0aSTakashi Iwai vgasr_priv.delayed_client_id); 57279721e0aSTakashi Iwai if (!client || !check_can_switch()) 5736a9ee8afSDave Airlie goto err; 5746a9ee8afSDave Airlie 57566b37c67SDave Airlie ret = vga_switchto_stage2(client); 5766a9ee8afSDave Airlie if (ret) 5779b0be1ebSThierry Reding pr_err("delayed switching failed stage 2 %d\n", ret); 5786a9ee8afSDave Airlie 5796a9ee8afSDave Airlie vgasr_priv.delayed_switch_active = false; 5806a9ee8afSDave Airlie err = 0; 5816a9ee8afSDave Airlie err: 5826a9ee8afSDave Airlie mutex_unlock(&vgasr_mutex); 5836a9ee8afSDave Airlie return err; 5846a9ee8afSDave Airlie } 5856a9ee8afSDave Airlie EXPORT_SYMBOL(vga_switcheroo_process_delayed_switch); 5860d69704aSDave Airlie 5870d69704aSDave Airlie static void vga_switcheroo_power_switch(struct pci_dev *pdev, enum vga_switcheroo_state state) 5880d69704aSDave Airlie { 5890d69704aSDave Airlie struct vga_switcheroo_client *client; 5900d69704aSDave Airlie 5910d69704aSDave Airlie if (!vgasr_priv.handler->power_state) 5920d69704aSDave Airlie return; 5930d69704aSDave Airlie 5940d69704aSDave Airlie client = find_client_from_pci(&vgasr_priv.clients, pdev); 5950d69704aSDave Airlie if (!client) 5960d69704aSDave Airlie return; 5970d69704aSDave Airlie 5980d69704aSDave Airlie if (!client->driver_power_control) 5990d69704aSDave Airlie return; 6000d69704aSDave Airlie 6010d69704aSDave Airlie vgasr_priv.handler->power_state(client->id, state); 6020d69704aSDave Airlie } 6030d69704aSDave Airlie 6040d69704aSDave Airlie /* force a PCI device to a certain state - mainly to turn off audio clients */ 6050d69704aSDave Airlie 6060d69704aSDave Airlie void vga_switcheroo_set_dynamic_switch(struct pci_dev *pdev, enum vga_switcheroo_state dynamic) 6070d69704aSDave Airlie { 6080d69704aSDave Airlie struct vga_switcheroo_client *client; 6090d69704aSDave Airlie 6100d69704aSDave Airlie client = find_client_from_pci(&vgasr_priv.clients, pdev); 6110d69704aSDave Airlie if (!client) 6120d69704aSDave Airlie return; 6130d69704aSDave Airlie 6140d69704aSDave Airlie if (!client->driver_power_control) 6150d69704aSDave Airlie return; 6160d69704aSDave Airlie 6170d69704aSDave Airlie client->pwr_state = dynamic; 6180d69704aSDave Airlie set_audio_state(client->id, dynamic); 6190d69704aSDave Airlie } 6200d69704aSDave Airlie EXPORT_SYMBOL(vga_switcheroo_set_dynamic_switch); 6210d69704aSDave Airlie 6220d69704aSDave Airlie /* switcheroo power domain */ 6230d69704aSDave Airlie static int vga_switcheroo_runtime_suspend(struct device *dev) 6240d69704aSDave Airlie { 6250d69704aSDave Airlie struct pci_dev *pdev = to_pci_dev(dev); 6260d69704aSDave Airlie int ret; 6270d69704aSDave Airlie 6280d69704aSDave Airlie ret = dev->bus->pm->runtime_suspend(dev); 6290d69704aSDave Airlie if (ret) 6300d69704aSDave Airlie return ret; 631f2bc5616SAlex Deucher if (vgasr_priv.handler->switchto) 632f2bc5616SAlex Deucher vgasr_priv.handler->switchto(VGA_SWITCHEROO_IGD); 6330d69704aSDave Airlie vga_switcheroo_power_switch(pdev, VGA_SWITCHEROO_OFF); 6340d69704aSDave Airlie return 0; 6350d69704aSDave Airlie } 6360d69704aSDave Airlie 6370d69704aSDave Airlie static int vga_switcheroo_runtime_resume(struct device *dev) 6380d69704aSDave Airlie { 6390d69704aSDave Airlie struct pci_dev *pdev = to_pci_dev(dev); 6400d69704aSDave Airlie int ret; 6410d69704aSDave Airlie 6420d69704aSDave Airlie vga_switcheroo_power_switch(pdev, VGA_SWITCHEROO_ON); 6430d69704aSDave Airlie ret = dev->bus->pm->runtime_resume(dev); 6440d69704aSDave Airlie if (ret) 6450d69704aSDave Airlie return ret; 6460d69704aSDave Airlie 6470d69704aSDave Airlie return 0; 6480d69704aSDave Airlie } 6490d69704aSDave Airlie 6500d69704aSDave Airlie /* this version is for the case where the power switch is separate 6510d69704aSDave Airlie to the device being powered down. */ 6520d69704aSDave Airlie int vga_switcheroo_init_domain_pm_ops(struct device *dev, struct dev_pm_domain *domain) 6530d69704aSDave Airlie { 6540d69704aSDave Airlie /* copy over all the bus versions */ 6550d69704aSDave Airlie if (dev->bus && dev->bus->pm) { 6560d69704aSDave Airlie domain->ops = *dev->bus->pm; 6570d69704aSDave Airlie domain->ops.runtime_suspend = vga_switcheroo_runtime_suspend; 6580d69704aSDave Airlie domain->ops.runtime_resume = vga_switcheroo_runtime_resume; 6590d69704aSDave Airlie 6600d69704aSDave Airlie dev->pm_domain = domain; 6610d69704aSDave Airlie return 0; 6620d69704aSDave Airlie } 6630d69704aSDave Airlie dev->pm_domain = NULL; 6640d69704aSDave Airlie return -EINVAL; 6650d69704aSDave Airlie } 6660d69704aSDave Airlie EXPORT_SYMBOL(vga_switcheroo_init_domain_pm_ops); 6670d69704aSDave Airlie 668766a53d0SAlex Deucher void vga_switcheroo_fini_domain_pm_ops(struct device *dev) 669766a53d0SAlex Deucher { 670766a53d0SAlex Deucher dev->pm_domain = NULL; 671766a53d0SAlex Deucher } 672766a53d0SAlex Deucher EXPORT_SYMBOL(vga_switcheroo_fini_domain_pm_ops); 673766a53d0SAlex Deucher 6740d69704aSDave Airlie static int vga_switcheroo_runtime_resume_hdmi_audio(struct device *dev) 6750d69704aSDave Airlie { 6760d69704aSDave Airlie struct pci_dev *pdev = to_pci_dev(dev); 6770d69704aSDave Airlie int ret; 6780d69704aSDave Airlie struct vga_switcheroo_client *client, *found = NULL; 6790d69704aSDave Airlie 6800d69704aSDave Airlie /* we need to check if we have to switch back on the video 6810d69704aSDave Airlie device so the audio device can come back */ 6820d69704aSDave Airlie list_for_each_entry(client, &vgasr_priv.clients, list) { 6830d69704aSDave Airlie if (PCI_SLOT(client->pdev->devfn) == PCI_SLOT(pdev->devfn) && client_is_vga(client)) { 6840d69704aSDave Airlie found = client; 6850d69704aSDave Airlie ret = pm_runtime_get_sync(&client->pdev->dev); 6860d69704aSDave Airlie if (ret) { 6870d69704aSDave Airlie if (ret != 1) 6880d69704aSDave Airlie return ret; 6890d69704aSDave Airlie } 6900d69704aSDave Airlie break; 6910d69704aSDave Airlie } 6920d69704aSDave Airlie } 6930d69704aSDave Airlie ret = dev->bus->pm->runtime_resume(dev); 6940d69704aSDave Airlie 6950d69704aSDave Airlie /* put the reference for the gpu */ 6960d69704aSDave Airlie if (found) { 6970d69704aSDave Airlie pm_runtime_mark_last_busy(&found->pdev->dev); 6980d69704aSDave Airlie pm_runtime_put_autosuspend(&found->pdev->dev); 6990d69704aSDave Airlie } 7000d69704aSDave Airlie return ret; 7010d69704aSDave Airlie } 7020d69704aSDave Airlie 7030d69704aSDave Airlie int vga_switcheroo_init_domain_pm_optimus_hdmi_audio(struct device *dev, struct dev_pm_domain *domain) 7040d69704aSDave Airlie { 7050d69704aSDave Airlie /* copy over all the bus versions */ 7060d69704aSDave Airlie if (dev->bus && dev->bus->pm) { 7070d69704aSDave Airlie domain->ops = *dev->bus->pm; 7080d69704aSDave Airlie domain->ops.runtime_resume = vga_switcheroo_runtime_resume_hdmi_audio; 7090d69704aSDave Airlie 7100d69704aSDave Airlie dev->pm_domain = domain; 7110d69704aSDave Airlie return 0; 7120d69704aSDave Airlie } 7130d69704aSDave Airlie dev->pm_domain = NULL; 7140d69704aSDave Airlie return -EINVAL; 7150d69704aSDave Airlie } 7160d69704aSDave Airlie EXPORT_SYMBOL(vga_switcheroo_init_domain_pm_optimus_hdmi_audio); 717