xref: /openbmc/linux/drivers/gpu/vga/vga_switcheroo.c (revision 9b0be1eb)
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