| 
									
										
										
										
											2010-02-01 15:38:10 +10:00
										 |  |  | /*
 | 
					
						
							|  |  |  |  * Copyright (c) 2010 Red Hat Inc. | 
					
						
							|  |  |  |  * Author : Dave Airlie <airlied@redhat.com> | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * Licensed under GPLv2 | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * vga_switcheroo.c - Support for laptop with dual GPU using one set of outputs | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |  Switcher interface - methods require for ATPX and DCM | 
					
						
							|  |  |  |  - switchto - this throws the output MUX switch | 
					
						
							|  |  |  |  - discrete_set_power - sets the power state for the discrete card | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |  GPU driver interface | 
					
						
							|  |  |  |  - set_gpu_state - this should do the equiv of s/r for the card | 
					
						
							|  |  |  | 		  - this should *not* set the discrete power state | 
					
						
							|  |  |  |  - switch_check  - check if the device is in a position to switch now | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include <linux/module.h>
 | 
					
						
							|  |  |  | #include <linux/seq_file.h>
 | 
					
						
							|  |  |  | #include <linux/uaccess.h>
 | 
					
						
							|  |  |  | #include <linux/fs.h>
 | 
					
						
							|  |  |  | #include <linux/debugfs.h>
 | 
					
						
							|  |  |  | #include <linux/fb.h>
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include <linux/pci.h>
 | 
					
						
							| 
									
										
										
										
											2013-01-25 11:38:56 +10:00
										 |  |  | #include <linux/console.h>
 | 
					
						
							| 
									
										
										
										
											2010-02-01 15:38:10 +10:00
										 |  |  | #include <linux/vga_switcheroo.h>
 | 
					
						
							| 
									
										
										
										
											2012-09-10 12:28:36 +10:00
										 |  |  | #include <linux/pm_runtime.h>
 | 
					
						
							| 
									
										
										
										
											2010-02-01 15:38:10 +10:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-04-16 16:26:03 -04:00
										 |  |  | #include <linux/vgaarb.h>
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-02-01 15:38:10 +10:00
										 |  |  | struct vga_switcheroo_client { | 
					
						
							|  |  |  | 	struct pci_dev *pdev; | 
					
						
							|  |  |  | 	struct fb_info *fb_info; | 
					
						
							|  |  |  | 	int pwr_state; | 
					
						
							| 
									
										
										
										
											2012-05-11 07:51:17 +02:00
										 |  |  | 	const struct vga_switcheroo_client_ops *ops; | 
					
						
							| 
									
										
										
										
											2010-02-01 15:38:10 +10:00
										 |  |  | 	int id; | 
					
						
							|  |  |  | 	bool active; | 
					
						
							| 
									
										
										
										
											2012-09-10 12:28:36 +10:00
										 |  |  | 	bool driver_power_control; | 
					
						
							| 
									
										
										
										
											2012-04-26 12:55:59 +02:00
										 |  |  | 	struct list_head list; | 
					
						
							| 
									
										
										
										
											2010-02-01 15:38:10 +10:00
										 |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static DEFINE_MUTEX(vgasr_mutex); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | struct vgasr_priv { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	bool active; | 
					
						
							|  |  |  | 	bool delayed_switch_active; | 
					
						
							|  |  |  | 	enum vga_switcheroo_client_id delayed_client_id; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	struct dentry *debugfs_root; | 
					
						
							|  |  |  | 	struct dentry *switch_file; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	int registered_clients; | 
					
						
							| 
									
										
										
										
											2012-04-26 12:55:59 +02:00
										 |  |  | 	struct list_head clients; | 
					
						
							| 
									
										
										
										
											2010-02-01 15:38:10 +10:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	struct vga_switcheroo_handler *handler; | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-04-26 14:29:48 +02:00
										 |  |  | #define ID_BIT_AUDIO		0x100
 | 
					
						
							|  |  |  | #define client_is_audio(c)	((c)->id & ID_BIT_AUDIO)
 | 
					
						
							|  |  |  | #define client_is_vga(c)	((c)->id == -1 || !client_is_audio(c))
 | 
					
						
							|  |  |  | #define client_id(c)		((c)->id & ~ID_BIT_AUDIO)
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-02-01 15:38:10 +10:00
										 |  |  | static int vga_switcheroo_debugfs_init(struct vgasr_priv *priv); | 
					
						
							|  |  |  | static void vga_switcheroo_debugfs_fini(struct vgasr_priv *priv); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* only one switcheroo per system */ | 
					
						
							| 
									
										
										
										
											2012-04-26 12:55:59 +02:00
										 |  |  | static struct vgasr_priv vgasr_priv = { | 
					
						
							|  |  |  | 	.clients = LIST_HEAD_INIT(vgasr_priv.clients), | 
					
						
							|  |  |  | }; | 
					
						
							| 
									
										
										
										
											2010-02-01 15:38:10 +10:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-08-17 11:17:03 -05:00
										 |  |  | static bool vga_switcheroo_ready(void) | 
					
						
							| 
									
										
										
										
											2010-02-01 15:38:10 +10:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2012-08-17 11:17:03 -05:00
										 |  |  | 	/* we're ready if we get two clients + handler */ | 
					
						
							|  |  |  | 	return !vgasr_priv.active && | 
					
						
							|  |  |  | 	       vgasr_priv.registered_clients == 2 && vgasr_priv.handler; | 
					
						
							| 
									
										
										
										
											2010-02-01 15:38:10 +10:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static void vga_switcheroo_enable(void) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	int ret; | 
					
						
							| 
									
										
										
										
											2012-04-26 12:55:59 +02:00
										 |  |  | 	struct vga_switcheroo_client *client; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-02-01 15:38:10 +10:00
										 |  |  | 	/* call the handler to init */ | 
					
						
							| 
									
										
										
										
											2012-08-17 11:17:02 -05:00
										 |  |  | 	if (vgasr_priv.handler->init) | 
					
						
							|  |  |  | 		vgasr_priv.handler->init(); | 
					
						
							| 
									
										
										
										
											2010-02-01 15:38:10 +10:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-04-26 12:55:59 +02:00
										 |  |  | 	list_for_each_entry(client, &vgasr_priv.clients, list) { | 
					
						
							| 
									
										
										
										
											2012-04-26 14:29:48 +02:00
										 |  |  | 		if (client->id != -1) | 
					
						
							|  |  |  | 			continue; | 
					
						
							| 
									
										
										
										
											2012-04-26 12:55:59 +02:00
										 |  |  | 		ret = vgasr_priv.handler->get_client_id(client->pdev); | 
					
						
							| 
									
										
										
										
											2010-02-01 15:38:10 +10:00
										 |  |  | 		if (ret < 0) | 
					
						
							|  |  |  | 			return; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-04-26 12:55:59 +02:00
										 |  |  | 		client->id = ret; | 
					
						
							| 
									
										
										
										
											2010-02-01 15:38:10 +10:00
										 |  |  | 	} | 
					
						
							|  |  |  | 	vga_switcheroo_debugfs_init(&vgasr_priv); | 
					
						
							|  |  |  | 	vgasr_priv.active = true; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-08-17 11:17:03 -05:00
										 |  |  | int vga_switcheroo_register_handler(struct vga_switcheroo_handler *handler) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	mutex_lock(&vgasr_mutex); | 
					
						
							|  |  |  | 	if (vgasr_priv.handler) { | 
					
						
							|  |  |  | 		mutex_unlock(&vgasr_mutex); | 
					
						
							|  |  |  | 		return -EINVAL; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	vgasr_priv.handler = handler; | 
					
						
							|  |  |  | 	if (vga_switcheroo_ready()) { | 
					
						
							|  |  |  | 		printk(KERN_INFO "vga_switcheroo: enabled\n"); | 
					
						
							|  |  |  | 		vga_switcheroo_enable(); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	mutex_unlock(&vgasr_mutex); | 
					
						
							|  |  |  | 	return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | EXPORT_SYMBOL(vga_switcheroo_register_handler); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void vga_switcheroo_unregister_handler(void) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	mutex_lock(&vgasr_mutex); | 
					
						
							|  |  |  | 	vgasr_priv.handler = NULL; | 
					
						
							|  |  |  | 	if (vgasr_priv.active) { | 
					
						
							|  |  |  | 		pr_info("vga_switcheroo: disabled\n"); | 
					
						
							|  |  |  | 		vga_switcheroo_debugfs_fini(&vgasr_priv); | 
					
						
							|  |  |  | 		vgasr_priv.active = false; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	mutex_unlock(&vgasr_mutex); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | EXPORT_SYMBOL(vga_switcheroo_unregister_handler); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-04-26 14:29:48 +02:00
										 |  |  | static int register_client(struct pci_dev *pdev, | 
					
						
							|  |  |  | 			   const struct vga_switcheroo_client_ops *ops, | 
					
						
							| 
									
										
										
										
											2012-09-10 12:28:36 +10:00
										 |  |  | 			   int id, bool active, bool driver_power_control) | 
					
						
							| 
									
										
										
										
											2010-02-01 15:38:10 +10:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2012-04-26 12:55:59 +02:00
										 |  |  | 	struct vga_switcheroo_client *client; | 
					
						
							| 
									
										
										
										
											2010-02-01 15:38:10 +10:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-04-26 12:55:59 +02:00
										 |  |  | 	client = kzalloc(sizeof(*client), GFP_KERNEL); | 
					
						
							|  |  |  | 	if (!client) | 
					
						
							|  |  |  | 		return -ENOMEM; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	client->pwr_state = VGA_SWITCHEROO_ON; | 
					
						
							|  |  |  | 	client->pdev = pdev; | 
					
						
							| 
									
										
										
										
											2012-05-11 07:51:17 +02:00
										 |  |  | 	client->ops = ops; | 
					
						
							| 
									
										
										
										
											2012-04-26 14:29:48 +02:00
										 |  |  | 	client->id = id; | 
					
						
							|  |  |  | 	client->active = active; | 
					
						
							| 
									
										
										
										
											2012-09-10 12:28:36 +10:00
										 |  |  | 	client->driver_power_control = driver_power_control; | 
					
						
							| 
									
										
										
										
											2010-02-01 15:38:10 +10:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-04-26 12:55:59 +02:00
										 |  |  | 	mutex_lock(&vgasr_mutex); | 
					
						
							|  |  |  | 	list_add_tail(&client->list, &vgasr_priv.clients); | 
					
						
							| 
									
										
										
										
											2012-04-26 14:29:48 +02:00
										 |  |  | 	if (client_is_vga(client)) | 
					
						
							|  |  |  | 		vgasr_priv.registered_clients++; | 
					
						
							| 
									
										
										
										
											2010-02-01 15:38:10 +10:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-08-17 11:17:03 -05:00
										 |  |  | 	if (vga_switcheroo_ready()) { | 
					
						
							| 
									
										
										
										
											2010-02-01 15:38:10 +10:00
										 |  |  | 		printk(KERN_INFO "vga_switcheroo: enabled\n"); | 
					
						
							|  |  |  | 		vga_switcheroo_enable(); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	mutex_unlock(&vgasr_mutex); | 
					
						
							|  |  |  | 	return 0; | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2012-04-26 14:29:48 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | int vga_switcheroo_register_client(struct pci_dev *pdev, | 
					
						
							| 
									
										
										
										
											2012-09-10 12:28:36 +10:00
										 |  |  | 				   const struct vga_switcheroo_client_ops *ops, | 
					
						
							|  |  |  | 				   bool driver_power_control) | 
					
						
							| 
									
										
										
										
											2012-04-26 14:29:48 +02:00
										 |  |  | { | 
					
						
							|  |  |  | 	return register_client(pdev, ops, -1, | 
					
						
							| 
									
										
										
										
											2012-09-10 12:28:36 +10:00
										 |  |  | 			       pdev == vga_default_device(), driver_power_control); | 
					
						
							| 
									
										
										
										
											2012-04-26 14:29:48 +02:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2010-02-01 15:38:10 +10:00
										 |  |  | EXPORT_SYMBOL(vga_switcheroo_register_client); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-04-26 14:29:48 +02:00
										 |  |  | int vga_switcheroo_register_audio_client(struct pci_dev *pdev, | 
					
						
							|  |  |  | 					 const struct vga_switcheroo_client_ops *ops, | 
					
						
							|  |  |  | 					 int id, bool active) | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2012-09-10 12:28:36 +10:00
										 |  |  | 	return register_client(pdev, ops, id | ID_BIT_AUDIO, active, false); | 
					
						
							| 
									
										
										
										
											2012-04-26 14:29:48 +02:00
										 |  |  | } | 
					
						
							|  |  |  | EXPORT_SYMBOL(vga_switcheroo_register_audio_client); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-04-26 12:55:59 +02:00
										 |  |  | static struct vga_switcheroo_client * | 
					
						
							|  |  |  | find_client_from_pci(struct list_head *head, struct pci_dev *pdev) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct vga_switcheroo_client *client; | 
					
						
							|  |  |  | 	list_for_each_entry(client, head, list) | 
					
						
							|  |  |  | 		if (client->pdev == pdev) | 
					
						
							|  |  |  | 			return client; | 
					
						
							|  |  |  | 	return NULL; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static struct vga_switcheroo_client * | 
					
						
							|  |  |  | find_client_from_id(struct list_head *head, int client_id) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct vga_switcheroo_client *client; | 
					
						
							|  |  |  | 	list_for_each_entry(client, head, list) | 
					
						
							|  |  |  | 		if (client->id == client_id) | 
					
						
							|  |  |  | 			return client; | 
					
						
							|  |  |  | 	return NULL; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static struct vga_switcheroo_client * | 
					
						
							|  |  |  | find_active_client(struct list_head *head) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct vga_switcheroo_client *client; | 
					
						
							|  |  |  | 	list_for_each_entry(client, head, list) | 
					
						
							| 
									
										
										
										
											2012-04-26 14:29:48 +02:00
										 |  |  | 		if (client->active && client_is_vga(client)) | 
					
						
							| 
									
										
										
										
											2012-04-26 12:55:59 +02:00
										 |  |  | 			return client; | 
					
						
							|  |  |  | 	return NULL; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-06-07 12:15:15 +02:00
										 |  |  | int vga_switcheroo_get_client_state(struct pci_dev *pdev) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct vga_switcheroo_client *client; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	client = find_client_from_pci(&vgasr_priv.clients, pdev); | 
					
						
							|  |  |  | 	if (!client) | 
					
						
							|  |  |  | 		return VGA_SWITCHEROO_NOT_FOUND; | 
					
						
							|  |  |  | 	if (!vgasr_priv.active) | 
					
						
							|  |  |  | 		return VGA_SWITCHEROO_INIT; | 
					
						
							|  |  |  | 	return client->pwr_state; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | EXPORT_SYMBOL(vga_switcheroo_get_client_state); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-02-01 15:38:10 +10:00
										 |  |  | void vga_switcheroo_unregister_client(struct pci_dev *pdev) | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2012-04-26 12:55:59 +02:00
										 |  |  | 	struct vga_switcheroo_client *client; | 
					
						
							| 
									
										
										
										
											2010-02-01 15:38:10 +10:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	mutex_lock(&vgasr_mutex); | 
					
						
							| 
									
										
										
										
											2012-04-26 12:55:59 +02:00
										 |  |  | 	client = find_client_from_pci(&vgasr_priv.clients, pdev); | 
					
						
							|  |  |  | 	if (client) { | 
					
						
							| 
									
										
										
										
											2012-04-26 14:29:48 +02:00
										 |  |  | 		if (client_is_vga(client)) | 
					
						
							|  |  |  | 			vgasr_priv.registered_clients--; | 
					
						
							| 
									
										
										
										
											2012-04-26 12:55:59 +02:00
										 |  |  | 		list_del(&client->list); | 
					
						
							|  |  |  | 		kfree(client); | 
					
						
							| 
									
										
										
										
											2010-02-01 15:38:10 +10:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2012-04-26 14:29:48 +02:00
										 |  |  | 	if (vgasr_priv.active && vgasr_priv.registered_clients < 2) { | 
					
						
							|  |  |  | 		printk(KERN_INFO "vga_switcheroo: disabled\n"); | 
					
						
							|  |  |  | 		vga_switcheroo_debugfs_fini(&vgasr_priv); | 
					
						
							|  |  |  | 		vgasr_priv.active = false; | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2010-02-01 15:38:10 +10:00
										 |  |  | 	mutex_unlock(&vgasr_mutex); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | EXPORT_SYMBOL(vga_switcheroo_unregister_client); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void vga_switcheroo_client_fb_set(struct pci_dev *pdev, | 
					
						
							|  |  |  | 				 struct fb_info *info) | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2012-04-26 12:55:59 +02:00
										 |  |  | 	struct vga_switcheroo_client *client; | 
					
						
							| 
									
										
										
										
											2010-02-01 15:38:10 +10:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	mutex_lock(&vgasr_mutex); | 
					
						
							| 
									
										
										
										
											2012-04-26 12:55:59 +02:00
										 |  |  | 	client = find_client_from_pci(&vgasr_priv.clients, pdev); | 
					
						
							|  |  |  | 	if (client) | 
					
						
							|  |  |  | 		client->fb_info = info; | 
					
						
							| 
									
										
										
										
											2010-02-01 15:38:10 +10:00
										 |  |  | 	mutex_unlock(&vgasr_mutex); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | EXPORT_SYMBOL(vga_switcheroo_client_fb_set); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int vga_switcheroo_show(struct seq_file *m, void *v) | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2012-04-26 12:55:59 +02:00
										 |  |  | 	struct vga_switcheroo_client *client; | 
					
						
							|  |  |  | 	int i = 0; | 
					
						
							| 
									
										
										
										
											2010-02-01 15:38:10 +10:00
										 |  |  | 	mutex_lock(&vgasr_mutex); | 
					
						
							| 
									
										
										
										
											2012-04-26 12:55:59 +02:00
										 |  |  | 	list_for_each_entry(client, &vgasr_priv.clients, list) { | 
					
						
							| 
									
										
										
										
											2012-09-10 12:28:36 +10:00
										 |  |  | 		seq_printf(m, "%d:%s%s:%c:%s%s:%s\n", i, | 
					
						
							| 
									
										
										
										
											2012-04-26 14:29:48 +02:00
										 |  |  | 			   client_id(client) == VGA_SWITCHEROO_DIS ? "DIS" : "IGD", | 
					
						
							|  |  |  | 			   client_is_vga(client) ? "" : "-Audio", | 
					
						
							| 
									
										
										
										
											2012-04-26 12:55:59 +02:00
										 |  |  | 			   client->active ? '+' : ' ', | 
					
						
							| 
									
										
										
										
											2012-09-10 12:28:36 +10:00
										 |  |  | 			   client->driver_power_control ? "Dyn" : "", | 
					
						
							| 
									
										
										
										
											2012-04-26 12:55:59 +02:00
										 |  |  | 			   client->pwr_state ? "Pwr" : "Off", | 
					
						
							|  |  |  | 			   pci_name(client->pdev)); | 
					
						
							|  |  |  | 		i++; | 
					
						
							| 
									
										
										
										
											2010-02-01 15:38:10 +10:00
										 |  |  | 	} | 
					
						
							|  |  |  | 	mutex_unlock(&vgasr_mutex); | 
					
						
							|  |  |  | 	return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int vga_switcheroo_debugfs_open(struct inode *inode, struct file *file) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	return single_open(file, vga_switcheroo_show, NULL); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int vga_switchon(struct vga_switcheroo_client *client) | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2012-09-10 12:28:36 +10:00
										 |  |  | 	if (client->driver_power_control) | 
					
						
							|  |  |  | 		return 0; | 
					
						
							| 
									
										
										
										
											2010-12-06 12:31:50 +10:00
										 |  |  | 	if (vgasr_priv.handler->power_state) | 
					
						
							|  |  |  | 		vgasr_priv.handler->power_state(client->id, VGA_SWITCHEROO_ON); | 
					
						
							| 
									
										
										
										
											2010-02-01 15:38:10 +10:00
										 |  |  | 	/* call the driver callback to turn on device */ | 
					
						
							| 
									
										
										
										
											2012-05-11 07:51:17 +02:00
										 |  |  | 	client->ops->set_gpu_state(client->pdev, VGA_SWITCHEROO_ON); | 
					
						
							| 
									
										
										
										
											2010-02-01 15:38:10 +10:00
										 |  |  | 	client->pwr_state = VGA_SWITCHEROO_ON; | 
					
						
							|  |  |  | 	return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int vga_switchoff(struct vga_switcheroo_client *client) | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2012-09-10 12:28:36 +10:00
										 |  |  | 	if (client->driver_power_control) | 
					
						
							|  |  |  | 		return 0; | 
					
						
							| 
									
										
										
										
											2010-02-01 15:38:10 +10:00
										 |  |  | 	/* call the driver callback to turn off device */ | 
					
						
							| 
									
										
										
										
											2012-05-11 07:51:17 +02:00
										 |  |  | 	client->ops->set_gpu_state(client->pdev, VGA_SWITCHEROO_OFF); | 
					
						
							| 
									
										
										
										
											2010-12-06 12:31:50 +10:00
										 |  |  | 	if (vgasr_priv.handler->power_state) | 
					
						
							|  |  |  | 		vgasr_priv.handler->power_state(client->id, VGA_SWITCHEROO_OFF); | 
					
						
							| 
									
										
										
										
											2010-02-01 15:38:10 +10:00
										 |  |  | 	client->pwr_state = VGA_SWITCHEROO_OFF; | 
					
						
							|  |  |  | 	return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-04-26 14:29:48 +02:00
										 |  |  | static void set_audio_state(int id, int state) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct vga_switcheroo_client *client; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	client = find_client_from_id(&vgasr_priv.clients, id | ID_BIT_AUDIO); | 
					
						
							|  |  |  | 	if (client && client->pwr_state != state) { | 
					
						
							|  |  |  | 		client->ops->set_gpu_state(client->pdev, state); | 
					
						
							|  |  |  | 		client->pwr_state = state; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-12-07 14:24:25 +10:00
										 |  |  | /* stage one happens before delay */ | 
					
						
							|  |  |  | static int vga_switchto_stage1(struct vga_switcheroo_client *new_client) | 
					
						
							| 
									
										
										
										
											2010-02-01 15:38:10 +10:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2012-04-26 12:55:59 +02:00
										 |  |  | 	struct vga_switcheroo_client *active; | 
					
						
							| 
									
										
										
										
											2010-02-01 15:38:10 +10:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-04-26 12:55:59 +02:00
										 |  |  | 	active = find_active_client(&vgasr_priv.clients); | 
					
						
							| 
									
										
										
										
											2010-02-01 15:38:10 +10:00
										 |  |  | 	if (!active) | 
					
						
							|  |  |  | 		return 0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (new_client->pwr_state == VGA_SWITCHEROO_OFF) | 
					
						
							|  |  |  | 		vga_switchon(new_client); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-04-16 16:26:03 -04:00
										 |  |  | 	vga_set_default_device(new_client->pdev); | 
					
						
							| 
									
										
										
										
											2010-12-07 14:24:25 +10:00
										 |  |  | 	return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* post delay */ | 
					
						
							|  |  |  | static int vga_switchto_stage2(struct vga_switcheroo_client *new_client) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	int ret; | 
					
						
							| 
									
										
										
										
											2012-04-26 12:55:59 +02:00
										 |  |  | 	struct vga_switcheroo_client *active; | 
					
						
							| 
									
										
										
										
											2010-12-07 14:24:25 +10:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-04-26 12:55:59 +02:00
										 |  |  | 	active = find_active_client(&vgasr_priv.clients); | 
					
						
							| 
									
										
										
										
											2010-12-07 14:24:25 +10:00
										 |  |  | 	if (!active) | 
					
						
							|  |  |  | 		return 0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	active->active = false; | 
					
						
							| 
									
										
										
										
											2010-02-01 15:38:10 +10:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-06-09 08:46:42 +02:00
										 |  |  | 	set_audio_state(active->id, VGA_SWITCHEROO_OFF); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-02-01 15:38:10 +10:00
										 |  |  | 	if (new_client->fb_info) { | 
					
						
							|  |  |  | 		struct fb_event event; | 
					
						
							| 
									
										
										
										
											2013-01-25 11:38:56 +10:00
										 |  |  | 		console_lock(); | 
					
						
							| 
									
										
										
										
											2010-02-01 15:38:10 +10:00
										 |  |  | 		event.info = new_client->fb_info; | 
					
						
							|  |  |  | 		fb_notifier_call_chain(FB_EVENT_REMAP_ALL_CONSOLE, &event); | 
					
						
							| 
									
										
										
										
											2013-01-25 11:38:56 +10:00
										 |  |  | 		console_unlock(); | 
					
						
							| 
									
										
										
										
											2010-02-01 15:38:10 +10:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ret = vgasr_priv.handler->switchto(new_client->id); | 
					
						
							|  |  |  | 	if (ret) | 
					
						
							|  |  |  | 		return ret; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-05-11 07:51:17 +02:00
										 |  |  | 	if (new_client->ops->reprobe) | 
					
						
							|  |  |  | 		new_client->ops->reprobe(new_client->pdev); | 
					
						
							| 
									
										
										
										
											2010-12-07 08:57:57 +10:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-02-01 15:38:10 +10:00
										 |  |  | 	if (active->pwr_state == VGA_SWITCHEROO_ON) | 
					
						
							|  |  |  | 		vga_switchoff(active); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-06-09 08:46:42 +02:00
										 |  |  | 	set_audio_state(new_client->id, VGA_SWITCHEROO_ON); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-02-01 15:38:10 +10:00
										 |  |  | 	new_client->active = true; | 
					
						
							|  |  |  | 	return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-04-26 12:55:59 +02:00
										 |  |  | static bool check_can_switch(void) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct vga_switcheroo_client *client; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	list_for_each_entry(client, &vgasr_priv.clients, list) { | 
					
						
							| 
									
										
										
										
											2012-05-11 07:51:17 +02:00
										 |  |  | 		if (!client->ops->can_switch(client->pdev)) { | 
					
						
							| 
									
										
										
										
											2012-04-26 12:55:59 +02:00
										 |  |  | 			printk(KERN_ERR "vga_switcheroo: client %x refused switch\n", client->id); | 
					
						
							|  |  |  | 			return false; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return true; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-02-01 15:38:10 +10:00
										 |  |  | static ssize_t | 
					
						
							|  |  |  | vga_switcheroo_debugfs_write(struct file *filp, const char __user *ubuf, | 
					
						
							|  |  |  | 			     size_t cnt, loff_t *ppos) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	char usercmd[64]; | 
					
						
							| 
									
										
										
										
											2012-04-26 12:55:59 +02:00
										 |  |  | 	int ret; | 
					
						
							| 
									
										
										
										
											2010-02-01 15:38:10 +10:00
										 |  |  | 	bool delay = false, can_switch; | 
					
						
							| 
									
										
										
										
											2010-12-06 12:35:52 +10:00
										 |  |  | 	bool just_mux = false; | 
					
						
							| 
									
										
										
										
											2010-02-01 15:38:10 +10:00
										 |  |  | 	int client_id = -1; | 
					
						
							|  |  |  | 	struct vga_switcheroo_client *client = NULL; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (cnt > 63) | 
					
						
							|  |  |  | 		cnt = 63; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (copy_from_user(usercmd, ubuf, cnt)) | 
					
						
							|  |  |  | 		return -EFAULT; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	mutex_lock(&vgasr_mutex); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-04-27 14:11:03 -07:00
										 |  |  | 	if (!vgasr_priv.active) { | 
					
						
							|  |  |  | 		cnt = -EINVAL; | 
					
						
							|  |  |  | 		goto out; | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2010-02-01 15:38:10 +10:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	/* pwr off the device not in use */ | 
					
						
							|  |  |  | 	if (strncmp(usercmd, "OFF", 3) == 0) { | 
					
						
							| 
									
										
										
										
											2012-04-26 12:55:59 +02:00
										 |  |  | 		list_for_each_entry(client, &vgasr_priv.clients, list) { | 
					
						
							| 
									
										
										
										
											2012-06-09 08:46:42 +02:00
										 |  |  | 			if (client->active || client_is_audio(client)) | 
					
						
							| 
									
										
										
										
											2010-02-01 15:38:10 +10:00
										 |  |  | 				continue; | 
					
						
							| 
									
										
										
										
											2012-09-10 12:28:36 +10:00
										 |  |  | 			if (client->driver_power_control) | 
					
						
							|  |  |  | 				continue; | 
					
						
							| 
									
										
										
										
											2012-06-09 08:46:42 +02:00
										 |  |  | 			set_audio_state(client->id, VGA_SWITCHEROO_OFF); | 
					
						
							| 
									
										
										
										
											2012-04-26 12:55:59 +02:00
										 |  |  | 			if (client->pwr_state == VGA_SWITCHEROO_ON) | 
					
						
							|  |  |  | 				vga_switchoff(client); | 
					
						
							| 
									
										
										
										
											2010-02-01 15:38:10 +10:00
										 |  |  | 		} | 
					
						
							|  |  |  | 		goto out; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	/* pwr on the device not in use */ | 
					
						
							|  |  |  | 	if (strncmp(usercmd, "ON", 2) == 0) { | 
					
						
							| 
									
										
										
										
											2012-04-26 12:55:59 +02:00
										 |  |  | 		list_for_each_entry(client, &vgasr_priv.clients, list) { | 
					
						
							| 
									
										
										
										
											2012-06-09 08:46:42 +02:00
										 |  |  | 			if (client->active || client_is_audio(client)) | 
					
						
							| 
									
										
										
										
											2010-02-01 15:38:10 +10:00
										 |  |  | 				continue; | 
					
						
							| 
									
										
										
										
											2012-09-10 12:28:36 +10:00
										 |  |  | 			if (client->driver_power_control) | 
					
						
							|  |  |  | 				continue; | 
					
						
							| 
									
										
										
										
											2012-04-26 12:55:59 +02:00
										 |  |  | 			if (client->pwr_state == VGA_SWITCHEROO_OFF) | 
					
						
							|  |  |  | 				vga_switchon(client); | 
					
						
							| 
									
										
										
										
											2012-06-09 08:46:42 +02:00
										 |  |  | 			set_audio_state(client->id, VGA_SWITCHEROO_ON); | 
					
						
							| 
									
										
										
										
											2010-02-01 15:38:10 +10:00
										 |  |  | 		} | 
					
						
							|  |  |  | 		goto out; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* request a delayed switch - test can we switch now */ | 
					
						
							|  |  |  | 	if (strncmp(usercmd, "DIGD", 4) == 0) { | 
					
						
							|  |  |  | 		client_id = VGA_SWITCHEROO_IGD; | 
					
						
							|  |  |  | 		delay = true; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (strncmp(usercmd, "DDIS", 4) == 0) { | 
					
						
							|  |  |  | 		client_id = VGA_SWITCHEROO_DIS; | 
					
						
							|  |  |  | 		delay = true; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (strncmp(usercmd, "IGD", 3) == 0) | 
					
						
							|  |  |  | 		client_id = VGA_SWITCHEROO_IGD; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (strncmp(usercmd, "DIS", 3) == 0) | 
					
						
							|  |  |  | 		client_id = VGA_SWITCHEROO_DIS; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2011-01-07 08:12:27 +03:00
										 |  |  | 	if (strncmp(usercmd, "MIGD", 4) == 0) { | 
					
						
							| 
									
										
										
										
											2010-12-06 12:35:52 +10:00
										 |  |  | 		just_mux = true; | 
					
						
							|  |  |  | 		client_id = VGA_SWITCHEROO_IGD; | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2011-01-07 08:12:27 +03:00
										 |  |  | 	if (strncmp(usercmd, "MDIS", 4) == 0) { | 
					
						
							| 
									
										
										
										
											2010-12-06 12:35:52 +10:00
										 |  |  | 		just_mux = true; | 
					
						
							|  |  |  | 		client_id = VGA_SWITCHEROO_DIS; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-02-01 15:38:10 +10:00
										 |  |  | 	if (client_id == -1) | 
					
						
							|  |  |  | 		goto out; | 
					
						
							| 
									
										
										
										
											2012-04-26 12:55:59 +02:00
										 |  |  | 	client = find_client_from_id(&vgasr_priv.clients, client_id); | 
					
						
							|  |  |  | 	if (!client) | 
					
						
							|  |  |  | 		goto out; | 
					
						
							| 
									
										
										
										
											2010-02-01 15:38:10 +10:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	vgasr_priv.delayed_switch_active = false; | 
					
						
							| 
									
										
										
										
											2010-12-06 12:35:52 +10:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	if (just_mux) { | 
					
						
							|  |  |  | 		ret = vgasr_priv.handler->switchto(client_id); | 
					
						
							|  |  |  | 		goto out; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-04-26 12:55:59 +02:00
										 |  |  | 	if (client->active) | 
					
						
							| 
									
										
										
										
											2011-05-15 16:32:50 +02:00
										 |  |  | 		goto out; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-02-01 15:38:10 +10:00
										 |  |  | 	/* okay we want a switch - test if devices are willing to switch */ | 
					
						
							| 
									
										
										
										
											2012-04-26 12:55:59 +02:00
										 |  |  | 	can_switch = check_can_switch(); | 
					
						
							| 
									
										
										
										
											2010-02-01 15:38:10 +10:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	if (can_switch == false && delay == false) | 
					
						
							|  |  |  | 		goto out; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-04-26 12:55:59 +02:00
										 |  |  | 	if (can_switch) { | 
					
						
							| 
									
										
										
										
											2010-12-07 14:24:25 +10:00
										 |  |  | 		ret = vga_switchto_stage1(client); | 
					
						
							|  |  |  | 		if (ret) | 
					
						
							|  |  |  | 			printk(KERN_ERR "vga_switcheroo: switching failed stage 1 %d\n", ret); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		ret = vga_switchto_stage2(client); | 
					
						
							| 
									
										
										
										
											2010-02-01 15:38:10 +10:00
										 |  |  | 		if (ret) | 
					
						
							| 
									
										
										
										
											2010-12-07 14:24:25 +10:00
										 |  |  | 			printk(KERN_ERR "vga_switcheroo: switching failed stage 2 %d\n", ret); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-02-01 15:38:10 +10:00
										 |  |  | 	} else { | 
					
						
							|  |  |  | 		printk(KERN_INFO "vga_switcheroo: setting delayed switch to client %d\n", client->id); | 
					
						
							|  |  |  | 		vgasr_priv.delayed_switch_active = true; | 
					
						
							|  |  |  | 		vgasr_priv.delayed_client_id = client_id; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-12-07 14:24:25 +10:00
										 |  |  | 		ret = vga_switchto_stage1(client); | 
					
						
							|  |  |  | 		if (ret) | 
					
						
							|  |  |  | 			printk(KERN_ERR "vga_switcheroo: delayed switching stage 1 failed %d\n", ret); | 
					
						
							| 
									
										
										
										
											2010-02-01 15:38:10 +10:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | out: | 
					
						
							|  |  |  | 	mutex_unlock(&vgasr_mutex); | 
					
						
							|  |  |  | 	return cnt; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static const struct file_operations vga_switcheroo_debugfs_fops = { | 
					
						
							|  |  |  | 	.owner = THIS_MODULE, | 
					
						
							|  |  |  | 	.open = vga_switcheroo_debugfs_open, | 
					
						
							|  |  |  | 	.write = vga_switcheroo_debugfs_write, | 
					
						
							|  |  |  | 	.read = seq_read, | 
					
						
							|  |  |  | 	.llseek = seq_lseek, | 
					
						
							|  |  |  | 	.release = single_release, | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static void vga_switcheroo_debugfs_fini(struct vgasr_priv *priv) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	if (priv->switch_file) { | 
					
						
							|  |  |  | 		debugfs_remove(priv->switch_file); | 
					
						
							|  |  |  | 		priv->switch_file = NULL; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if (priv->debugfs_root) { | 
					
						
							|  |  |  | 		debugfs_remove(priv->debugfs_root); | 
					
						
							|  |  |  | 		priv->debugfs_root = NULL; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int vga_switcheroo_debugfs_init(struct vgasr_priv *priv) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	/* already initialised */ | 
					
						
							|  |  |  | 	if (priv->debugfs_root) | 
					
						
							|  |  |  | 		return 0; | 
					
						
							|  |  |  | 	priv->debugfs_root = debugfs_create_dir("vgaswitcheroo", NULL); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (!priv->debugfs_root) { | 
					
						
							|  |  |  | 		printk(KERN_ERR "vga_switcheroo: Cannot create /sys/kernel/debug/vgaswitcheroo\n"); | 
					
						
							|  |  |  | 		goto fail; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	priv->switch_file = debugfs_create_file("switch", 0644, | 
					
						
							|  |  |  | 						priv->debugfs_root, NULL, &vga_switcheroo_debugfs_fops); | 
					
						
							|  |  |  | 	if (!priv->switch_file) { | 
					
						
							|  |  |  | 		printk(KERN_ERR "vga_switcheroo: cannot create /sys/kernel/debug/vgaswitcheroo/switch\n"); | 
					
						
							|  |  |  | 		goto fail; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return 0; | 
					
						
							|  |  |  | fail: | 
					
						
							|  |  |  | 	vga_switcheroo_debugfs_fini(priv); | 
					
						
							|  |  |  | 	return -1; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | int vga_switcheroo_process_delayed_switch(void) | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2012-04-26 12:55:59 +02:00
										 |  |  | 	struct vga_switcheroo_client *client; | 
					
						
							| 
									
										
										
										
											2010-02-01 15:38:10 +10:00
										 |  |  | 	int ret; | 
					
						
							|  |  |  | 	int err = -EINVAL; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	mutex_lock(&vgasr_mutex); | 
					
						
							|  |  |  | 	if (!vgasr_priv.delayed_switch_active) | 
					
						
							|  |  |  | 		goto err; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	printk(KERN_INFO "vga_switcheroo: processing delayed switch to %d\n", vgasr_priv.delayed_client_id); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-04-26 12:55:59 +02:00
										 |  |  | 	client = find_client_from_id(&vgasr_priv.clients, | 
					
						
							|  |  |  | 				     vgasr_priv.delayed_client_id); | 
					
						
							|  |  |  | 	if (!client || !check_can_switch()) | 
					
						
							| 
									
										
										
										
											2010-02-01 15:38:10 +10:00
										 |  |  | 		goto err; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-12-07 14:24:25 +10:00
										 |  |  | 	ret = vga_switchto_stage2(client); | 
					
						
							| 
									
										
										
										
											2010-02-01 15:38:10 +10:00
										 |  |  | 	if (ret) | 
					
						
							| 
									
										
										
										
											2010-12-07 14:24:25 +10:00
										 |  |  | 		printk(KERN_ERR "vga_switcheroo: delayed switching failed stage 2 %d\n", ret); | 
					
						
							| 
									
										
										
										
											2010-02-01 15:38:10 +10:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	vgasr_priv.delayed_switch_active = false; | 
					
						
							|  |  |  | 	err = 0; | 
					
						
							|  |  |  | err: | 
					
						
							|  |  |  | 	mutex_unlock(&vgasr_mutex); | 
					
						
							|  |  |  | 	return err; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | EXPORT_SYMBOL(vga_switcheroo_process_delayed_switch); | 
					
						
							| 
									
										
										
										
											2012-09-10 12:28:36 +10:00
										 |  |  | 
 | 
					
						
							|  |  |  | static void vga_switcheroo_power_switch(struct pci_dev *pdev, enum vga_switcheroo_state state) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct vga_switcheroo_client *client; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (!vgasr_priv.handler->power_state) | 
					
						
							|  |  |  | 		return; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	client = find_client_from_pci(&vgasr_priv.clients, pdev); | 
					
						
							|  |  |  | 	if (!client) | 
					
						
							|  |  |  | 		return; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (!client->driver_power_control) | 
					
						
							|  |  |  | 		return; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	vgasr_priv.handler->power_state(client->id, state); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* force a PCI device to a certain state - mainly to turn off audio clients */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void vga_switcheroo_set_dynamic_switch(struct pci_dev *pdev, enum vga_switcheroo_state dynamic) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct vga_switcheroo_client *client; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	client = find_client_from_pci(&vgasr_priv.clients, pdev); | 
					
						
							|  |  |  | 	if (!client) | 
					
						
							|  |  |  | 		return; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (!client->driver_power_control) | 
					
						
							|  |  |  | 		return; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	client->pwr_state = dynamic; | 
					
						
							|  |  |  | 	set_audio_state(client->id, dynamic); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | EXPORT_SYMBOL(vga_switcheroo_set_dynamic_switch); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* switcheroo power domain */ | 
					
						
							|  |  |  | static int vga_switcheroo_runtime_suspend(struct device *dev) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct pci_dev *pdev = to_pci_dev(dev); | 
					
						
							|  |  |  | 	int ret; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ret = dev->bus->pm->runtime_suspend(dev); | 
					
						
							|  |  |  | 	if (ret) | 
					
						
							|  |  |  | 		return ret; | 
					
						
							| 
									
										
										
										
											2014-05-19 14:04:43 -04:00
										 |  |  | 	if (vgasr_priv.handler->switchto) | 
					
						
							|  |  |  | 		vgasr_priv.handler->switchto(VGA_SWITCHEROO_IGD); | 
					
						
							| 
									
										
										
										
											2012-09-10 12:28:36 +10:00
										 |  |  | 	vga_switcheroo_power_switch(pdev, VGA_SWITCHEROO_OFF); | 
					
						
							|  |  |  | 	return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int vga_switcheroo_runtime_resume(struct device *dev) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct pci_dev *pdev = to_pci_dev(dev); | 
					
						
							|  |  |  | 	int ret; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	vga_switcheroo_power_switch(pdev, VGA_SWITCHEROO_ON); | 
					
						
							|  |  |  | 	ret = dev->bus->pm->runtime_resume(dev); | 
					
						
							|  |  |  | 	if (ret) | 
					
						
							|  |  |  | 		return ret; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* this version is for the case where the power switch is separate
 | 
					
						
							|  |  |  |    to the device being powered down. */ | 
					
						
							|  |  |  | int vga_switcheroo_init_domain_pm_ops(struct device *dev, struct dev_pm_domain *domain) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	/* copy over all the bus versions */ | 
					
						
							|  |  |  | 	if (dev->bus && dev->bus->pm) { | 
					
						
							|  |  |  | 		domain->ops = *dev->bus->pm; | 
					
						
							|  |  |  | 		domain->ops.runtime_suspend = vga_switcheroo_runtime_suspend; | 
					
						
							|  |  |  | 		domain->ops.runtime_resume = vga_switcheroo_runtime_resume; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		dev->pm_domain = domain; | 
					
						
							|  |  |  | 		return 0; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	dev->pm_domain = NULL; | 
					
						
							|  |  |  | 	return -EINVAL; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | EXPORT_SYMBOL(vga_switcheroo_init_domain_pm_ops); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-09-12 17:51:29 -04:00
										 |  |  | void vga_switcheroo_fini_domain_pm_ops(struct device *dev) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	dev->pm_domain = NULL; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | EXPORT_SYMBOL(vga_switcheroo_fini_domain_pm_ops); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-09-10 12:28:36 +10:00
										 |  |  | static int vga_switcheroo_runtime_resume_hdmi_audio(struct device *dev) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct pci_dev *pdev = to_pci_dev(dev); | 
					
						
							|  |  |  | 	int ret; | 
					
						
							|  |  |  | 	struct vga_switcheroo_client *client, *found = NULL; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* we need to check if we have to switch back on the video
 | 
					
						
							|  |  |  | 	   device so the audio device can come back */ | 
					
						
							|  |  |  | 	list_for_each_entry(client, &vgasr_priv.clients, list) { | 
					
						
							|  |  |  | 		if (PCI_SLOT(client->pdev->devfn) == PCI_SLOT(pdev->devfn) && client_is_vga(client)) { | 
					
						
							|  |  |  | 			found = client; | 
					
						
							|  |  |  | 			ret = pm_runtime_get_sync(&client->pdev->dev); | 
					
						
							|  |  |  | 			if (ret) { | 
					
						
							|  |  |  | 				if (ret != 1) | 
					
						
							|  |  |  | 					return ret; | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			break; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	ret = dev->bus->pm->runtime_resume(dev); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* put the reference for the gpu */ | 
					
						
							|  |  |  | 	if (found) { | 
					
						
							|  |  |  | 		pm_runtime_mark_last_busy(&found->pdev->dev); | 
					
						
							|  |  |  | 		pm_runtime_put_autosuspend(&found->pdev->dev); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return ret; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | int vga_switcheroo_init_domain_pm_optimus_hdmi_audio(struct device *dev, struct dev_pm_domain *domain) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	/* copy over all the bus versions */ | 
					
						
							|  |  |  | 	if (dev->bus && dev->bus->pm) { | 
					
						
							|  |  |  | 		domain->ops = *dev->bus->pm; | 
					
						
							|  |  |  | 		domain->ops.runtime_resume = vga_switcheroo_runtime_resume_hdmi_audio; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		dev->pm_domain = domain; | 
					
						
							|  |  |  | 		return 0; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	dev->pm_domain = NULL; | 
					
						
							|  |  |  | 	return -EINVAL; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | EXPORT_SYMBOL(vga_switcheroo_init_domain_pm_optimus_hdmi_audio); |