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