diff options
Diffstat (limited to 'drivers/gpu/vga')
| -rw-r--r-- | drivers/gpu/vga/Kconfig | 5 | ||||
| -rw-r--r-- | drivers/gpu/vga/vga_switcheroo.c | 537 | ||||
| -rw-r--r-- | drivers/gpu/vga/vgaarb.c | 221 | 
3 files changed, 550 insertions, 213 deletions
diff --git a/drivers/gpu/vga/Kconfig b/drivers/gpu/vga/Kconfig index 8d0e31a2202..29437eabe09 100644 --- a/drivers/gpu/vga/Kconfig +++ b/drivers/gpu/vga/Kconfig @@ -1,7 +1,7 @@  config VGA_ARB -	bool "VGA Arbitration" if EMBEDDED +	bool "VGA Arbitration" if EXPERT  	default y -	depends on PCI +	depends on (PCI && !S390)  	help  	  Some "legacy" VGA devices implemented on PCI typically have the same  	  hard-decoded addresses as they did on ISA. When multiple PCI devices @@ -21,6 +21,7 @@ config VGA_SWITCHEROO  	bool "Laptop Hybrid Graphics - GPU switching support"  	depends on X86  	depends on ACPI +	select VGA_ARB  	help  	  Many laptops released in 2008/9/10 have two GPUs with a multiplexer  	  to switch between them. This adds support for dynamic switching when diff --git a/drivers/gpu/vga/vga_switcheroo.c b/drivers/gpu/vga/vga_switcheroo.c index c8768f38511..6866448083b 100644 --- a/drivers/gpu/vga/vga_switcheroo.c +++ b/drivers/gpu/vga/vga_switcheroo.c @@ -18,7 +18,6 @@   */  #include <linux/module.h> -#include <linux/dmi.h>  #include <linux/seq_file.h>  #include <linux/uaccess.h>  #include <linux/fs.h> @@ -26,16 +25,21 @@  #include <linux/fb.h>  #include <linux/pci.h> +#include <linux/console.h>  #include <linux/vga_switcheroo.h> +#include <linux/pm_runtime.h> + +#include <linux/vgaarb.h>  struct vga_switcheroo_client {  	struct pci_dev *pdev;  	struct fb_info *fb_info;  	int pwr_state; -	void (*set_gpu_state)(struct pci_dev *pdev, enum vga_switcheroo_state); -	bool (*can_switch)(struct pci_dev *pdev); +	const struct vga_switcheroo_client_ops *ops;  	int id;  	bool active; +	bool driver_power_control; +	struct list_head list;  };  static DEFINE_MUTEX(vgasr_mutex); @@ -50,16 +54,52 @@ struct vgasr_priv {  	struct dentry *switch_file;  	int registered_clients; -	struct vga_switcheroo_client clients[VGA_SWITCHEROO_MAX_CLIENTS]; +	struct list_head clients;  	struct vga_switcheroo_handler *handler;  }; +#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) +  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 */ -static struct vgasr_priv vgasr_priv; +static struct vgasr_priv vgasr_priv = { +	.clients = LIST_HEAD_INIT(vgasr_priv.clients), +}; + +static bool vga_switcheroo_ready(void) +{ +	/* we're ready if we get two clients + handler */ +	return !vgasr_priv.active && +	       vgasr_priv.registered_clients == 2 && vgasr_priv.handler; +} + +static void vga_switcheroo_enable(void) +{ +	int ret; +	struct vga_switcheroo_client *client; + +	/* call the handler to init */ +	if (vgasr_priv.handler->init) +		vgasr_priv.handler->init(); + +	list_for_each_entry(client, &vgasr_priv.clients, list) { +		if (client->id != -1) +			continue; +		ret = vgasr_priv.handler->get_client_id(client->pdev); +		if (ret < 0) +			return; + +		client->id = ret; +	} +	vga_switcheroo_debugfs_init(&vgasr_priv); +	vgasr_priv.active = true; +}  int vga_switcheroo_register_handler(struct vga_switcheroo_handler *handler)  { @@ -70,6 +110,10 @@ int vga_switcheroo_register_handler(struct vga_switcheroo_handler *handler)  	}  	vgasr_priv.handler = handler; +	if (vga_switcheroo_ready()) { +		printk(KERN_INFO "vga_switcheroo: enabled\n"); +		vga_switcheroo_enable(); +	}  	mutex_unlock(&vgasr_mutex);  	return 0;  } @@ -79,76 +123,122 @@ 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); -static void vga_switcheroo_enable(void) +static int register_client(struct pci_dev *pdev, +			   const struct vga_switcheroo_client_ops *ops, +			   int id, bool active, bool driver_power_control)  { -	int i; -	int ret; -	/* call the handler to init */ -	vgasr_priv.handler->init(); +	struct vga_switcheroo_client *client; -	for (i = 0; i < VGA_SWITCHEROO_MAX_CLIENTS; i++) { -		ret = vgasr_priv.handler->get_client_id(vgasr_priv.clients[i].pdev); -		if (ret < 0) -			return; - -		vgasr_priv.clients[i].id = ret; -	} -	vga_switcheroo_debugfs_init(&vgasr_priv); -	vgasr_priv.active = true; -} +	client = kzalloc(sizeof(*client), GFP_KERNEL); +	if (!client) +		return -ENOMEM; -int vga_switcheroo_register_client(struct pci_dev *pdev, -				   void (*set_gpu_state)(struct pci_dev *pdev, enum vga_switcheroo_state), -				   bool (*can_switch)(struct pci_dev *pdev)) -{ -	int index; +	client->pwr_state = VGA_SWITCHEROO_ON; +	client->pdev = pdev; +	client->ops = ops; +	client->id = id; +	client->active = active; +	client->driver_power_control = driver_power_control;  	mutex_lock(&vgasr_mutex); -	/* don't do IGD vs DIS here */ -	if (vgasr_priv.registered_clients & 1) -		index = 1; -	else -		index = 0; - -	vgasr_priv.clients[index].pwr_state = VGA_SWITCHEROO_ON; -	vgasr_priv.clients[index].pdev = pdev; -	vgasr_priv.clients[index].set_gpu_state = set_gpu_state; -	vgasr_priv.clients[index].can_switch = can_switch; -	vgasr_priv.clients[index].id = -1; -	if (pdev->resource[PCI_ROM_RESOURCE].flags & IORESOURCE_ROM_SHADOW) -		vgasr_priv.clients[index].active = true; - -	vgasr_priv.registered_clients |= (1 << index); - -	/* if we get two clients + handler */ -	if (vgasr_priv.registered_clients == 0x3 && vgasr_priv.handler) { +	list_add_tail(&client->list, &vgasr_priv.clients); +	if (client_is_vga(client)) +		vgasr_priv.registered_clients++; + +	if (vga_switcheroo_ready()) {  		printk(KERN_INFO "vga_switcheroo: enabled\n");  		vga_switcheroo_enable();  	}  	mutex_unlock(&vgasr_mutex);  	return 0;  } + +int vga_switcheroo_register_client(struct pci_dev *pdev, +				   const struct vga_switcheroo_client_ops *ops, +				   bool driver_power_control) +{ +	return register_client(pdev, ops, -1, +			       pdev == vga_default_device(), driver_power_control); +}  EXPORT_SYMBOL(vga_switcheroo_register_client); +int vga_switcheroo_register_audio_client(struct pci_dev *pdev, +					 const struct vga_switcheroo_client_ops *ops, +					 int id, bool active) +{ +	return register_client(pdev, ops, id | ID_BIT_AUDIO, active, false); +} +EXPORT_SYMBOL(vga_switcheroo_register_audio_client); + +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) +		if (client->active && client_is_vga(client)) +			return client; +	return NULL; +} + +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); +  void vga_switcheroo_unregister_client(struct pci_dev *pdev)  { -	int i; +	struct vga_switcheroo_client *client;  	mutex_lock(&vgasr_mutex); -	for (i = 0; i < VGA_SWITCHEROO_MAX_CLIENTS; i++) { -		if (vgasr_priv.clients[i].pdev == pdev) { -			vgasr_priv.registered_clients &= ~(1 << i); -			break; -		} +	client = find_client_from_pci(&vgasr_priv.clients, pdev); +	if (client) { +		if (client_is_vga(client)) +			vgasr_priv.registered_clients--; +		list_del(&client->list); +		kfree(client); +	} +	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;  	} - -	printk(KERN_INFO "vga_switcheroo: disabled\n"); -	vga_switcheroo_debugfs_fini(&vgasr_priv); -	vgasr_priv.active = false;  	mutex_unlock(&vgasr_mutex);  }  EXPORT_SYMBOL(vga_switcheroo_unregister_client); @@ -156,28 +246,30 @@ EXPORT_SYMBOL(vga_switcheroo_unregister_client);  void vga_switcheroo_client_fb_set(struct pci_dev *pdev,  				 struct fb_info *info)  { -	int i; +	struct vga_switcheroo_client *client;  	mutex_lock(&vgasr_mutex); -	for (i = 0; i < VGA_SWITCHEROO_MAX_CLIENTS; i++) { -		if (vgasr_priv.clients[i].pdev == pdev) { -			vgasr_priv.clients[i].fb_info = info; -			break; -		} -	} +	client = find_client_from_pci(&vgasr_priv.clients, pdev); +	if (client) +		client->fb_info = info;  	mutex_unlock(&vgasr_mutex);  }  EXPORT_SYMBOL(vga_switcheroo_client_fb_set);  static int vga_switcheroo_show(struct seq_file *m, void *v)  { -	int i; +	struct vga_switcheroo_client *client; +	int i = 0;  	mutex_lock(&vgasr_mutex); -	for (i = 0; i < VGA_SWITCHEROO_MAX_CLIENTS; i++) { -		seq_printf(m, "%d:%c:%s:%s\n", i, -			   vgasr_priv.clients[i].active ? '+' : ' ', -			   vgasr_priv.clients[i].pwr_state ? "Pwr" : "Off", -			   pci_name(vgasr_priv.clients[i].pdev)); +	list_for_each_entry(client, &vgasr_priv.clients, list) { +		seq_printf(m, "%d:%s%s:%c:%s%s:%s\n", i, +			   client_id(client) == VGA_SWITCHEROO_DIS ? "DIS" : "IGD", +			   client_is_vga(client) ? "" : "-Audio", +			   client->active ? '+' : ' ', +			   client->driver_power_control ? "Dyn" : "", +			   client->pwr_state ? "Pwr" : "Off", +			   pci_name(client->pdev)); +		i++;  	}  	mutex_unlock(&vgasr_mutex);  	return 0; @@ -190,81 +282,114 @@ static int vga_switcheroo_debugfs_open(struct inode *inode, struct file *file)  static int vga_switchon(struct vga_switcheroo_client *client)  { -	int ret; - -	ret = vgasr_priv.handler->power_state(client->id, VGA_SWITCHEROO_ON); +	if (client->driver_power_control) +		return 0; +	if (vgasr_priv.handler->power_state) +		vgasr_priv.handler->power_state(client->id, VGA_SWITCHEROO_ON);  	/* call the driver callback to turn on device */ -	client->set_gpu_state(client->pdev, VGA_SWITCHEROO_ON); +	client->ops->set_gpu_state(client->pdev, VGA_SWITCHEROO_ON);  	client->pwr_state = VGA_SWITCHEROO_ON;  	return 0;  }  static int vga_switchoff(struct vga_switcheroo_client *client)  { +	if (client->driver_power_control) +		return 0;  	/* call the driver callback to turn off device */ -	client->set_gpu_state(client->pdev, VGA_SWITCHEROO_OFF); -	vgasr_priv.handler->power_state(client->id, VGA_SWITCHEROO_OFF); +	client->ops->set_gpu_state(client->pdev, VGA_SWITCHEROO_OFF); +	if (vgasr_priv.handler->power_state) +		vgasr_priv.handler->power_state(client->id, VGA_SWITCHEROO_OFF);  	client->pwr_state = VGA_SWITCHEROO_OFF;  	return 0;  } -static int vga_switchto(struct vga_switcheroo_client *new_client) +static void set_audio_state(int id, int state)  { -	int ret; -	int i; -	struct vga_switcheroo_client *active = NULL; - -	if (new_client->active == true) -		return 0; +	struct vga_switcheroo_client *client; -	for (i = 0; i < VGA_SWITCHEROO_MAX_CLIENTS; i++) { -		if (vgasr_priv.clients[i].active == true) { -			active = &vgasr_priv.clients[i]; -			break; -		} +	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;  	} +} + +/* stage one happens before delay */ +static int vga_switchto_stage1(struct vga_switcheroo_client *new_client) +{ +	struct vga_switcheroo_client *active; + +	active = find_active_client(&vgasr_priv.clients);  	if (!active)  		return 0; -	/* power up the first device */ -	ret = pci_enable_device(new_client->pdev); -	if (ret) -		return ret; -  	if (new_client->pwr_state == VGA_SWITCHEROO_OFF)  		vga_switchon(new_client); -	/* swap shadow resource to denote boot VGA device has changed so X starts on new device */ +	vga_set_default_device(new_client->pdev); +	return 0; +} + +/* post delay */ +static int vga_switchto_stage2(struct vga_switcheroo_client *new_client) +{ +	int ret; +	struct vga_switcheroo_client *active; + +	active = find_active_client(&vgasr_priv.clients); +	if (!active) +		return 0; +  	active->active = false; -	active->pdev->resource[PCI_ROM_RESOURCE].flags &= ~IORESOURCE_ROM_SHADOW; -	new_client->pdev->resource[PCI_ROM_RESOURCE].flags |= IORESOURCE_ROM_SHADOW; +	set_audio_state(active->id, VGA_SWITCHEROO_OFF);  	if (new_client->fb_info) {  		struct fb_event event; +		console_lock();  		event.info = new_client->fb_info;  		fb_notifier_call_chain(FB_EVENT_REMAP_ALL_CONSOLE, &event); +		console_unlock();  	}  	ret = vgasr_priv.handler->switchto(new_client->id);  	if (ret)  		return ret; +	if (new_client->ops->reprobe) +		new_client->ops->reprobe(new_client->pdev); +  	if (active->pwr_state == VGA_SWITCHEROO_ON)  		vga_switchoff(active); +	set_audio_state(new_client->id, VGA_SWITCHEROO_ON); +  	new_client->active = true;  	return 0;  } +static bool check_can_switch(void) +{ +	struct vga_switcheroo_client *client; + +	list_for_each_entry(client, &vgasr_priv.clients, list) { +		if (!client->ops->can_switch(client->pdev)) { +			printk(KERN_ERR "vga_switcheroo: client %x refused switch\n", client->id); +			return false; +		} +	} +	return true; +} +  static ssize_t  vga_switcheroo_debugfs_write(struct file *filp, const char __user *ubuf,  			     size_t cnt, loff_t *ppos)  {  	char usercmd[64]; -	const char *pdev_name; -	int i, ret; +	int ret;  	bool delay = false, can_switch; +	bool just_mux = false;  	int client_id = -1;  	struct vga_switcheroo_client *client = NULL; @@ -283,21 +408,27 @@ vga_switcheroo_debugfs_write(struct file *filp, const char __user *ubuf,  	/* pwr off the device not in use */  	if (strncmp(usercmd, "OFF", 3) == 0) { -		for (i = 0; i < VGA_SWITCHEROO_MAX_CLIENTS; i++) { -			if (vgasr_priv.clients[i].active) +		list_for_each_entry(client, &vgasr_priv.clients, list) { +			if (client->active || client_is_audio(client))  				continue; -			if (vgasr_priv.clients[i].pwr_state == VGA_SWITCHEROO_ON) -				vga_switchoff(&vgasr_priv.clients[i]); +			if (client->driver_power_control) +				continue; +			set_audio_state(client->id, VGA_SWITCHEROO_OFF); +			if (client->pwr_state == VGA_SWITCHEROO_ON) +				vga_switchoff(client);  		}  		goto out;  	}  	/* pwr on the device not in use */  	if (strncmp(usercmd, "ON", 2) == 0) { -		for (i = 0; i < VGA_SWITCHEROO_MAX_CLIENTS; i++) { -			if (vgasr_priv.clients[i].active) +		list_for_each_entry(client, &vgasr_priv.clients, list) { +			if (client->active || client_is_audio(client)) +				continue; +			if (client->driver_power_control)  				continue; -			if (vgasr_priv.clients[i].pwr_state == VGA_SWITCHEROO_OFF) -				vga_switchon(&vgasr_priv.clients[i]); +			if (client->pwr_state == VGA_SWITCHEROO_OFF) +				vga_switchon(client); +			set_audio_state(client->id, VGA_SWITCHEROO_ON);  		}  		goto out;  	} @@ -319,44 +450,54 @@ vga_switcheroo_debugfs_write(struct file *filp, const char __user *ubuf,  	if (strncmp(usercmd, "DIS", 3) == 0)  		client_id = VGA_SWITCHEROO_DIS; +	if (strncmp(usercmd, "MIGD", 4) == 0) { +		just_mux = true; +		client_id = VGA_SWITCHEROO_IGD; +	} +	if (strncmp(usercmd, "MDIS", 4) == 0) { +		just_mux = true; +		client_id = VGA_SWITCHEROO_DIS; +	} +  	if (client_id == -1)  		goto out; +	client = find_client_from_id(&vgasr_priv.clients, client_id); +	if (!client) +		goto out; -	for (i = 0; i < VGA_SWITCHEROO_MAX_CLIENTS; i++) { -		if (vgasr_priv.clients[i].id == client_id) { -			client = &vgasr_priv.clients[i]; -			break; -		} +	vgasr_priv.delayed_switch_active = false; + +	if (just_mux) { +		ret = vgasr_priv.handler->switchto(client_id); +		goto out;  	} -	vgasr_priv.delayed_switch_active = false; +	if (client->active) +		goto out; +  	/* okay we want a switch - test if devices are willing to switch */ -	can_switch = true; -	for (i = 0; i < VGA_SWITCHEROO_MAX_CLIENTS; i++) { -		can_switch = vgasr_priv.clients[i].can_switch(vgasr_priv.clients[i].pdev); -		if (can_switch == false) { -			printk(KERN_ERR "vga_switcheroo: client %d refused switch\n", i); -			break; -		} -	} +	can_switch = check_can_switch();  	if (can_switch == false && delay == false)  		goto out; -	if (can_switch == true) { -		pdev_name = pci_name(client->pdev); -		ret = vga_switchto(client); +	if (can_switch) { +		ret = vga_switchto_stage1(client);  		if (ret) -			printk(KERN_ERR "vga_switcheroo: switching failed %d\n", ret); +			printk(KERN_ERR "vga_switcheroo: switching failed stage 1 %d\n", ret); + +		ret = vga_switchto_stage2(client); +		if (ret) +			printk(KERN_ERR "vga_switcheroo: switching failed stage 2 %d\n", ret); +  	} 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; -		/* we should at least power up the card to -		   make the switch faster */ -		if (client->pwr_state == VGA_SWITCHEROO_OFF) -			vga_switchon(client); +		ret = vga_switchto_stage1(client); +		if (ret) +			printk(KERN_ERR "vga_switcheroo: delayed switching stage 1 failed %d\n", ret);  	}  out: @@ -411,10 +552,7 @@ fail:  int vga_switcheroo_process_delayed_switch(void)  { -	struct vga_switcheroo_client *client = NULL; -	const char *pdev_name; -	bool can_switch = true; -	int i; +	struct vga_switcheroo_client *client;  	int ret;  	int err = -EINVAL; @@ -424,23 +562,14 @@ int vga_switcheroo_process_delayed_switch(void)  	printk(KERN_INFO "vga_switcheroo: processing delayed switch to %d\n", vgasr_priv.delayed_client_id); -	for (i = 0; i < VGA_SWITCHEROO_MAX_CLIENTS; i++) { -		if (vgasr_priv.clients[i].id == vgasr_priv.delayed_client_id) -			client = &vgasr_priv.clients[i]; -		can_switch = vgasr_priv.clients[i].can_switch(vgasr_priv.clients[i].pdev); -		if (can_switch == false) { -			printk(KERN_ERR "vga_switcheroo: client %d refused switch\n", i); -			break; -		} -	} - -	if (can_switch == false || client == NULL) +	client = find_client_from_id(&vgasr_priv.clients, +				     vgasr_priv.delayed_client_id); +	if (!client || !check_can_switch())  		goto err; -	pdev_name = pci_name(client->pdev); -	ret = vga_switchto(client); +	ret = vga_switchto_stage2(client);  	if (ret) -		printk(KERN_ERR "vga_switcheroo: delayed switching failed %d\n", ret); +		printk(KERN_ERR "vga_switcheroo: delayed switching failed stage 2 %d\n", ret);  	vgasr_priv.delayed_switch_active = false;  	err = 0; @@ -450,3 +579,127 @@ err:  }  EXPORT_SYMBOL(vga_switcheroo_process_delayed_switch); +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; +	if (vgasr_priv.handler->switchto) +		vgasr_priv.handler->switchto(VGA_SWITCHEROO_IGD); +	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); + +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); diff --git a/drivers/gpu/vga/vgaarb.c b/drivers/gpu/vga/vgaarb.c index c380c65da41..af025970835 100644 --- a/drivers/gpu/vga/vgaarb.c +++ b/drivers/gpu/vga/vgaarb.c @@ -61,7 +61,7 @@ struct vga_device {  	unsigned int mem_lock_cnt;	/* legacy MEM lock count */  	unsigned int io_norm_cnt;	/* normal IO count */  	unsigned int mem_norm_cnt;	/* normal MEM count */ - +	bool bridge_has_one_vga;  	/* allow IRQ enable/disable hook */  	void *cookie;  	void (*irq_set_state)(void *cookie, bool enable); @@ -136,6 +136,17 @@ struct pci_dev *vga_default_device(void)  {  	return vga_default;  } + +EXPORT_SYMBOL_GPL(vga_default_device); + +void vga_set_default_device(struct pci_dev *pdev) +{ +	if (vga_default == pdev) +		return; + +	pci_dev_put(vga_default); +	vga_default = pci_dev_get(pdev); +}  #endif  static inline void vga_irq_set_state(struct vga_device *vgadev, bool state) @@ -151,7 +162,7 @@ static inline void vga_irq_set_state(struct vga_device *vgadev, bool state)  static void vga_check_first_use(void)  {  	/* we should inform all GPUs in the system that -	 * VGA arb has occured and to try and disable resources +	 * VGA arb has occurred and to try and disable resources  	 * if they can */  	if (!vga_arbiter_used) {  		vga_arbiter_used = true; @@ -165,6 +176,8 @@ static struct vga_device *__vga_tryget(struct vga_device *vgadev,  	unsigned int wants, legacy_wants, match;  	struct vga_device *conflict;  	unsigned int pci_bits; +	u32 flags = 0; +  	/* Account for "normal" resources to lock. If we decode the legacy,  	 * counterpart, we need to request it as well  	 */ @@ -237,21 +250,28 @@ static struct vga_device *__vga_tryget(struct vga_device *vgadev,  		/* looks like he doesn't have a lock, we can steal  		 * them from him  		 */ -		vga_irq_set_state(conflict, false); +		flags = 0;  		pci_bits = 0; -		if (lwants & (VGA_RSRC_LEGACY_MEM|VGA_RSRC_NORMAL_MEM)) -			pci_bits |= PCI_COMMAND_MEMORY; -		if (lwants & (VGA_RSRC_LEGACY_IO|VGA_RSRC_NORMAL_IO)) -			pci_bits |= PCI_COMMAND_IO; -		pci_set_vga_state(conflict->pdev, false, pci_bits, -				  change_bridge); -		conflict->owns &= ~lwants; +		if (!conflict->bridge_has_one_vga) { +			vga_irq_set_state(conflict, false); +			flags |= PCI_VGA_STATE_CHANGE_DECODES; +			if (match & (VGA_RSRC_LEGACY_MEM|VGA_RSRC_NORMAL_MEM)) +				pci_bits |= PCI_COMMAND_MEMORY; +			if (match & (VGA_RSRC_LEGACY_IO|VGA_RSRC_NORMAL_IO)) +				pci_bits |= PCI_COMMAND_IO; +		} + +		if (change_bridge) +			flags |= PCI_VGA_STATE_CHANGE_BRIDGE; + +		pci_set_vga_state(conflict->pdev, false, pci_bits, flags); +		conflict->owns &= ~match;  		/* If he also owned non-legacy, that is no longer the case */ -		if (lwants & VGA_RSRC_LEGACY_MEM) +		if (match & VGA_RSRC_LEGACY_MEM)  			conflict->owns &= ~VGA_RSRC_NORMAL_MEM; -		if (lwants & VGA_RSRC_LEGACY_IO) +		if (match & VGA_RSRC_LEGACY_IO)  			conflict->owns &= ~VGA_RSRC_NORMAL_IO;  	} @@ -261,14 +281,24 @@ enable_them:  	 * also have in "decodes". We can lock resources we don't decode but  	 * not own them.  	 */ +	flags = 0;  	pci_bits = 0; -	if (wants & (VGA_RSRC_LEGACY_MEM|VGA_RSRC_NORMAL_MEM)) -		pci_bits |= PCI_COMMAND_MEMORY; -	if (wants & (VGA_RSRC_LEGACY_IO|VGA_RSRC_NORMAL_IO)) -		pci_bits |= PCI_COMMAND_IO; -	pci_set_vga_state(vgadev->pdev, true, pci_bits, !!(wants & VGA_RSRC_LEGACY_MASK)); -	vga_irq_set_state(vgadev, true); +	if (!vgadev->bridge_has_one_vga) { +		flags |= PCI_VGA_STATE_CHANGE_DECODES; +		if (wants & (VGA_RSRC_LEGACY_MEM|VGA_RSRC_NORMAL_MEM)) +			pci_bits |= PCI_COMMAND_MEMORY; +		if (wants & (VGA_RSRC_LEGACY_IO|VGA_RSRC_NORMAL_IO)) +			pci_bits |= PCI_COMMAND_IO; +	} +	if (!!(wants & VGA_RSRC_LEGACY_MASK)) +		flags |= PCI_VGA_STATE_CHANGE_BRIDGE; + +	pci_set_vga_state(vgadev->pdev, true, pci_bits, flags); + +	if (!vgadev->bridge_has_one_vga) { +		vga_irq_set_state(vgadev, true); +	}  	vgadev->owns |= (wants & vgadev->decodes);  lock_them:  	vgadev->locks |= (rsrc & VGA_RSRC_LEGACY_MASK); @@ -421,6 +451,60 @@ bail:  }  EXPORT_SYMBOL(vga_put); +/* Rules for using a bridge to control a VGA descendant decoding: +   if a bridge has only one VGA descendant then it can be used +   to control the VGA routing for that device. +   It should always use the bridge closest to the device to control it. +   If a bridge has a direct VGA descendant, but also have a sub-bridge +   VGA descendant then we cannot use that bridge to control the direct VGA descendant. +   So for every device we register, we need to iterate all its parent bridges +   so we can invalidate any devices using them properly. +*/ +static void vga_arbiter_check_bridge_sharing(struct vga_device *vgadev) +{ +	struct vga_device *same_bridge_vgadev; +	struct pci_bus *new_bus, *bus; +	struct pci_dev *new_bridge, *bridge; + +	vgadev->bridge_has_one_vga = true; + +	if (list_empty(&vga_list)) +		return; + +	/* okay iterate the new devices bridge hierarachy */ +	new_bus = vgadev->pdev->bus; +	while (new_bus) { +		new_bridge = new_bus->self; + +		/* go through list of devices already registered */ +		list_for_each_entry(same_bridge_vgadev, &vga_list, list) { +			bus = same_bridge_vgadev->pdev->bus; +			bridge = bus->self; + +			/* see if the share a bridge with this device */ +			if (new_bridge == bridge) { +				/* if their direct parent bridge is the same +				   as any bridge of this device then it can't be used +				   for that device */ +				same_bridge_vgadev->bridge_has_one_vga = false; +			} + +			/* now iterate the previous devices bridge hierarchy */ +			/* if the new devices parent bridge is in the other devices +			   hierarchy then we can't use it to control this device */ +			while (bus) { +				bridge = bus->self; +				if (bridge) { +					if (bridge == vgadev->pdev->bus->self) +						vgadev->bridge_has_one_vga = false; +				} +				bus = bus->parent; +			} +		} +		new_bus = new_bus->parent; +	} +} +  /*   * Currently, we assume that the "initial" setup of the system is   * not sane, that is we come up with conflicting devices and let @@ -497,9 +581,11 @@ static bool vga_arbiter_add_pci_device(struct pci_dev *pdev)  #ifndef __ARCH_HAS_VGA_DEFAULT_DEVICE  	if (vga_default == NULL &&  	    ((vgadev->owns & VGA_RSRC_LEGACY_MASK) == VGA_RSRC_LEGACY_MASK)) -		vga_default = pci_dev_get(pdev); +		vga_set_default_device(pdev);  #endif +	vga_arbiter_check_bridge_sharing(vgadev); +  	/* Add to the list */  	list_add(&vgadev->list, &vga_list);  	vga_count++; @@ -530,10 +616,10 @@ static bool vga_arbiter_del_pci_device(struct pci_dev *pdev)  		goto bail;  	} -	if (vga_default == pdev) { -		pci_dev_put(vga_default); -		vga_default = NULL; -	} +#ifndef __ARCH_HAS_VGA_DEFAULT_DEVICE +	if (vga_default == pdev) +		vga_set_default_device(NULL); +#endif  	if (vgadev->decodes & (VGA_RSRC_LEGACY_IO | VGA_RSRC_LEGACY_MEM))  		vga_decode_count--; @@ -558,10 +644,12 @@ bail:  static inline void vga_update_device_decodes(struct vga_device *vgadev,  					     int new_decodes)  { -	int old_decodes; -	struct vga_device *new_vgadev, *conflict; +	int old_decodes, decodes_removed, decodes_unlocked;  	old_decodes = vgadev->decodes; +	decodes_removed = ~new_decodes & old_decodes; +	decodes_unlocked = vgadev->locks & decodes_removed; +	vgadev->owns &= ~decodes_removed;  	vgadev->decodes = new_decodes;  	pr_info("vgaarb: device changed decodes: PCI:%s,olddecodes=%s,decodes=%s:owns=%s\n", @@ -570,31 +658,22 @@ static inline void vga_update_device_decodes(struct vga_device *vgadev,  		vga_iostate_to_str(vgadev->decodes),  		vga_iostate_to_str(vgadev->owns)); - -	/* if we own the decodes we should move them along to -	   another card */ -	if ((vgadev->owns & old_decodes) && (vga_count > 1)) { -		/* set us to own nothing */ -		vgadev->owns &= ~old_decodes; -		list_for_each_entry(new_vgadev, &vga_list, list) { -			if ((new_vgadev != vgadev) && -			    (new_vgadev->decodes & VGA_RSRC_LEGACY_MASK)) { -				pr_info("vgaarb: transferring owner from PCI:%s to PCI:%s\n", pci_name(vgadev->pdev), pci_name(new_vgadev->pdev)); -				conflict = __vga_tryget(new_vgadev, VGA_RSRC_LEGACY_MASK); -				if (!conflict) -					__vga_put(new_vgadev, VGA_RSRC_LEGACY_MASK); -				break; -			} -		} +	/* if we removed locked decodes, lock count goes to zero, and release */ +	if (decodes_unlocked) { +		if (decodes_unlocked & VGA_RSRC_LEGACY_IO) +			vgadev->io_lock_cnt = 0; +		if (decodes_unlocked & VGA_RSRC_LEGACY_MEM) +			vgadev->mem_lock_cnt = 0; +		__vga_put(vgadev, decodes_unlocked);  	}  	/* change decodes counter */ -	if (old_decodes != new_decodes) { -		if (new_decodes & (VGA_RSRC_LEGACY_IO | VGA_RSRC_LEGACY_MEM)) -			vga_decode_count++; -		else -			vga_decode_count--; -	} +	if (old_decodes & VGA_RSRC_LEGACY_MASK && +	    !(new_decodes & VGA_RSRC_LEGACY_MASK)) +		vga_decode_count--; +	if (!(old_decodes & VGA_RSRC_LEGACY_MASK) && +	    new_decodes & VGA_RSRC_LEGACY_MASK) +		vga_decode_count++;  	pr_debug("vgaarb: decoding count now is: %d\n", vga_decode_count);  } @@ -636,7 +715,7 @@ int vga_client_register(struct pci_dev *pdev, void *cookie,  			void (*irq_set_state)(void *cookie, bool state),  			unsigned int (*set_vga_decode)(void *cookie, bool decode))  { -	int ret = -1; +	int ret = -ENODEV;  	struct vga_device *vgadev;  	unsigned long flags; @@ -774,7 +853,7 @@ static ssize_t vga_arb_read(struct file *file, char __user * buf,  	 */  	spin_lock_irqsave(&vga_lock, flags); -	/* If we are targetting the default, use it */ +	/* If we are targeting the default, use it */  	pdev = priv->target;  	if (pdev == NULL || pdev == PCI_INVALID_CARD) {  		spin_unlock_irqrestore(&vga_lock, flags); @@ -916,14 +995,20 @@ static ssize_t vga_arb_write(struct file *file, const char __user * buf,  				uc = &priv->cards[i];  		} -		if (!uc) -			return -EINVAL; +		if (!uc) { +			ret_val = -EINVAL; +			goto done; +		} -		if (io_state & VGA_RSRC_LEGACY_IO && uc->io_cnt == 0) -			return -EINVAL; +		if (io_state & VGA_RSRC_LEGACY_IO && uc->io_cnt == 0) { +			ret_val = -EINVAL; +			goto done; +		} -		if (io_state & VGA_RSRC_LEGACY_MEM && uc->mem_cnt == 0) -			return -EINVAL; +		if (io_state & VGA_RSRC_LEGACY_MEM && uc->mem_cnt == 0) { +			ret_val = -EINVAL; +			goto done; +		}  		vga_put(pdev, io_state); @@ -976,7 +1061,6 @@ static ssize_t vga_arb_write(struct file *file, const char __user * buf,  		}  	} else if (strncmp(curr_pos, "target ", 7) == 0) { -		struct pci_bus *pbus;  		unsigned int domain, bus, devfn;  		struct vga_device *vgadev; @@ -995,19 +1079,11 @@ static ssize_t vga_arb_write(struct file *file, const char __user * buf,  			pr_debug("vgaarb: %s ==> %x:%x:%x.%x\n", curr_pos,  				domain, bus, PCI_SLOT(devfn), PCI_FUNC(devfn)); -			pbus = pci_find_bus(domain, bus); -			pr_debug("vgaarb: pbus %p\n", pbus); -			if (pbus == NULL) { -				pr_err("vgaarb: invalid PCI domain and/or bus address %x:%x\n", -					domain, bus); -				ret_val = -ENODEV; -				goto done; -			} -			pdev = pci_get_slot(pbus, devfn); +			pdev = pci_get_domain_bus_and_slot(domain, bus, devfn);  			pr_debug("vgaarb: pdev %p\n", pdev);  			if (!pdev) { -				pr_err("vgaarb: invalid PCI address %x:%x\n", -					bus, devfn); +				pr_err("vgaarb: invalid PCI address %x:%x:%x\n", +					domain, bus, devfn);  				ret_val = -ENODEV;  				goto done;  			} @@ -1094,10 +1170,9 @@ static int vga_arb_open(struct inode *inode, struct file *file)  	pr_debug("%s\n", __func__); -	priv = kmalloc(sizeof(struct vga_arb_private), GFP_KERNEL); +	priv = kzalloc(sizeof(*priv), GFP_KERNEL);  	if (priv == NULL)  		return -ENOMEM; -	memset(priv, 0, sizeof(*priv));  	spin_lock_init(&priv->lock);  	file->private_data = priv; @@ -1222,6 +1297,7 @@ static int __init vga_arb_device_init(void)  {  	int rc;  	struct pci_dev *pdev; +	struct vga_device *vgadev;  	rc = misc_register(&vga_arb_device);  	if (rc < 0) @@ -1238,6 +1314,13 @@ static int __init vga_arb_device_init(void)  		vga_arbiter_add_pci_device(pdev);  	pr_info("vgaarb: loaded\n"); + +	list_for_each_entry(vgadev, &vga_list, list) { +		if (vgadev->bridge_has_one_vga) +			pr_info("vgaarb: bridge control possible %s\n", pci_name(vgadev->pdev)); +		else +			pr_info("vgaarb: no bridge control possible %s\n", pci_name(vgadev->pdev)); +	}  	return rc;  }  subsys_initcall(vga_arb_device_init);  | 
