aboutsummaryrefslogtreecommitdiff
path: root/drivers/gpu/vga
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/gpu/vga')
-rw-r--r--drivers/gpu/vga/Kconfig2
-rw-r--r--drivers/gpu/vga/vga_switcheroo.c216
-rw-r--r--drivers/gpu/vga/vgaarb.c80
3 files changed, 217 insertions, 81 deletions
diff --git a/drivers/gpu/vga/Kconfig b/drivers/gpu/vga/Kconfig
index f34838839b0..29437eabe09 100644
--- a/drivers/gpu/vga/Kconfig
+++ b/drivers/gpu/vga/Kconfig
@@ -1,7 +1,7 @@
config VGA_ARB
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
diff --git a/drivers/gpu/vga/vga_switcheroo.c b/drivers/gpu/vga/vga_switcheroo.c
index 5b3c7d135dc..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,7 +25,9 @@
#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>
@@ -37,6 +38,7 @@ struct vga_switcheroo_client {
const struct vga_switcheroo_client_ops *ops;
int id;
bool active;
+ bool driver_power_control;
struct list_head list;
};
@@ -70,27 +72,12 @@ static struct vgasr_priv vgasr_priv = {
.clients = LIST_HEAD_INIT(vgasr_priv.clients),
};
-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;
- mutex_unlock(&vgasr_mutex);
- return 0;
-}
-EXPORT_SYMBOL(vga_switcheroo_register_handler);
-
-void vga_switcheroo_unregister_handler(void)
+static bool vga_switcheroo_ready(void)
{
- mutex_lock(&vgasr_mutex);
- vgasr_priv.handler = NULL;
- mutex_unlock(&vgasr_mutex);
+ /* we're ready if we get two clients + handler */
+ return !vgasr_priv.active &&
+ vgasr_priv.registered_clients == 2 && vgasr_priv.handler;
}
-EXPORT_SYMBOL(vga_switcheroo_unregister_handler);
static void vga_switcheroo_enable(void)
{
@@ -98,7 +85,8 @@ static void vga_switcheroo_enable(void)
struct vga_switcheroo_client *client;
/* call the handler to init */
- vgasr_priv.handler->init();
+ if (vgasr_priv.handler->init)
+ vgasr_priv.handler->init();
list_for_each_entry(client, &vgasr_priv.clients, list) {
if (client->id != -1)
@@ -113,9 +101,40 @@ static void vga_switcheroo_enable(void)
vgasr_priv.active = true;
}
+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);
+
static int register_client(struct pci_dev *pdev,
const struct vga_switcheroo_client_ops *ops,
- int id, bool active)
+ int id, bool active, bool driver_power_control)
{
struct vga_switcheroo_client *client;
@@ -128,15 +147,14 @@ static int register_client(struct pci_dev *pdev,
client->ops = ops;
client->id = id;
client->active = active;
+ client->driver_power_control = driver_power_control;
mutex_lock(&vgasr_mutex);
list_add_tail(&client->list, &vgasr_priv.clients);
if (client_is_vga(client))
vgasr_priv.registered_clients++;
- /* if we get two clients + handler */
- if (!vgasr_priv.active &&
- vgasr_priv.registered_clients == 2 && vgasr_priv.handler) {
+ if (vga_switcheroo_ready()) {
printk(KERN_INFO "vga_switcheroo: enabled\n");
vga_switcheroo_enable();
}
@@ -145,10 +163,11 @@ static int register_client(struct pci_dev *pdev,
}
int vga_switcheroo_register_client(struct pci_dev *pdev,
- const struct vga_switcheroo_client_ops *ops)
+ const struct vga_switcheroo_client_ops *ops,
+ bool driver_power_control)
{
return register_client(pdev, ops, -1,
- pdev == vga_default_device());
+ pdev == vga_default_device(), driver_power_control);
}
EXPORT_SYMBOL(vga_switcheroo_register_client);
@@ -156,7 +175,7 @@ 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);
+ return register_client(pdev, ops, id | ID_BIT_AUDIO, active, false);
}
EXPORT_SYMBOL(vga_switcheroo_register_audio_client);
@@ -243,10 +262,11 @@ static int vga_switcheroo_show(struct seq_file *m, void *v)
int i = 0;
mutex_lock(&vgasr_mutex);
list_for_each_entry(client, &vgasr_priv.clients, list) {
- seq_printf(m, "%d:%s%s:%c:%s:%s\n", i,
+ 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++;
@@ -262,6 +282,8 @@ static int vga_switcheroo_debugfs_open(struct inode *inode, struct file *file)
static int vga_switchon(struct vga_switcheroo_client *client)
{
+ 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 */
@@ -272,6 +294,8 @@ static int vga_switchon(struct vga_switcheroo_client *client)
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->ops->set_gpu_state(client->pdev, VGA_SWITCHEROO_OFF);
if (vgasr_priv.handler->power_state)
@@ -323,8 +347,10 @@ static int vga_switchto_stage2(struct vga_switcheroo_client *new_client)
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);
@@ -361,7 +387,6 @@ 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 ret;
bool delay = false, can_switch;
bool just_mux = false;
@@ -386,6 +411,8 @@ vga_switcheroo_debugfs_write(struct file *filp, const char __user *ubuf,
list_for_each_entry(client, &vgasr_priv.clients, list) {
if (client->active || client_is_audio(client))
continue;
+ if (client->driver_power_control)
+ continue;
set_audio_state(client->id, VGA_SWITCHEROO_OFF);
if (client->pwr_state == VGA_SWITCHEROO_ON)
vga_switchoff(client);
@@ -397,6 +424,8 @@ vga_switcheroo_debugfs_write(struct file *filp, const char __user *ubuf,
list_for_each_entry(client, &vgasr_priv.clients, list) {
if (client->active || client_is_audio(client))
continue;
+ if (client->driver_power_control)
+ continue;
if (client->pwr_state == VGA_SWITCHEROO_OFF)
vga_switchon(client);
set_audio_state(client->id, VGA_SWITCHEROO_ON);
@@ -453,7 +482,6 @@ vga_switcheroo_debugfs_write(struct file *filp, const char __user *ubuf,
goto out;
if (can_switch) {
- pdev_name = pci_name(client->pdev);
ret = vga_switchto_stage1(client);
if (ret)
printk(KERN_ERR "vga_switcheroo: switching failed stage 1 %d\n", ret);
@@ -525,7 +553,6 @@ fail:
int vga_switcheroo_process_delayed_switch(void)
{
struct vga_switcheroo_client *client;
- const char *pdev_name;
int ret;
int err = -EINVAL;
@@ -540,7 +567,6 @@ int vga_switcheroo_process_delayed_switch(void)
if (!client || !check_can_switch())
goto err;
- pdev_name = pci_name(client->pdev);
ret = vga_switchto_stage2(client);
if (ret)
printk(KERN_ERR "vga_switcheroo: delayed switching failed stage 2 %d\n", ret);
@@ -553,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 3df8fc0ec01..af025970835 100644
--- a/drivers/gpu/vga/vgaarb.c
+++ b/drivers/gpu/vga/vgaarb.c
@@ -141,7 +141,11 @@ EXPORT_SYMBOL_GPL(vga_default_device);
void vga_set_default_device(struct pci_dev *pdev)
{
- vga_default = pdev;
+ if (vga_default == pdev)
+ return;
+
+ pci_dev_put(vga_default);
+ vga_default = pci_dev_get(pdev);
}
#endif
@@ -253,9 +257,9 @@ static struct vga_device *__vga_tryget(struct vga_device *vgadev,
if (!conflict->bridge_has_one_vga) {
vga_irq_set_state(conflict, false);
flags |= PCI_VGA_STATE_CHANGE_DECODES;
- if (lwants & (VGA_RSRC_LEGACY_MEM|VGA_RSRC_NORMAL_MEM))
+ if (match & (VGA_RSRC_LEGACY_MEM|VGA_RSRC_NORMAL_MEM))
pci_bits |= PCI_COMMAND_MEMORY;
- if (lwants & (VGA_RSRC_LEGACY_IO|VGA_RSRC_NORMAL_IO))
+ if (match & (VGA_RSRC_LEGACY_IO|VGA_RSRC_NORMAL_IO))
pci_bits |= PCI_COMMAND_IO;
}
@@ -263,11 +267,11 @@ static struct vga_device *__vga_tryget(struct vga_device *vgadev,
flags |= PCI_VGA_STATE_CHANGE_BRIDGE;
pci_set_vga_state(conflict->pdev, false, pci_bits, flags);
- conflict->owns &= ~lwants;
+ 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;
}
@@ -577,7 +581,7 @@ 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);
@@ -613,10 +617,8 @@ static bool vga_arbiter_del_pci_device(struct pci_dev *pdev)
}
#ifndef __ARCH_HAS_VGA_DEFAULT_DEVICE
- if (vga_default == pdev) {
- pci_dev_put(vga_default);
- vga_default = NULL;
- }
+ if (vga_default == pdev)
+ vga_set_default_device(NULL);
#endif
if (vgadev->decodes & (VGA_RSRC_LEGACY_IO | VGA_RSRC_LEGACY_MEM))
@@ -642,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",
@@ -654,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);
}
@@ -1066,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;
@@ -1085,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;
}