Commit 0d69704a authored by Dave Airlie's avatar Dave Airlie Committed by Dave Airlie

gpu/vga_switcheroo: add driver control power feature. (v3)

For optimus and powerxpress muxless we really want the GPU
driver deciding when to power up/down the GPU, not userspace.

This adds the ability for a driver to dynamically power up/down
the GPU and remove the switcheroo from controlling it, the
switcheroo reports the dynamic state to userspace also.

It also adds 2 power domains, one for machine where the power
switch is controlled outside the GPU D3 state, so the powerdown
ordering is done correctly, and the second for the hdmi audio
device to make sure it can resume for PCI config space accesses.

v1.1: fix build with switcheroo off

v2: add power domain support for radeon and v1 nvidia dsms
v2.1: fix typo in off case

v3: add audio power domain for hdmi audio + misc audio fixes

v4: use PCI_SLOT macro, drop power reference on hdmi audio resume
failure also.
Signed-off-by: default avatarDave Airlie <airlied@redhat.com>
parent e906d7bd
...@@ -1293,7 +1293,7 @@ static int i915_load_modeset_init(struct drm_device *dev) ...@@ -1293,7 +1293,7 @@ static int i915_load_modeset_init(struct drm_device *dev)
intel_register_dsm_handler(); intel_register_dsm_handler();
ret = vga_switcheroo_register_client(dev->pdev, &i915_switcheroo_ops); ret = vga_switcheroo_register_client(dev->pdev, &i915_switcheroo_ops, false);
if (ret) if (ret)
goto cleanup_vga_client; goto cleanup_vga_client;
......
...@@ -79,7 +79,7 @@ nouveau_vga_init(struct nouveau_drm *drm) ...@@ -79,7 +79,7 @@ nouveau_vga_init(struct nouveau_drm *drm)
{ {
struct drm_device *dev = drm->dev; struct drm_device *dev = drm->dev;
vga_client_register(dev->pdev, dev, NULL, nouveau_vga_set_decode); vga_client_register(dev->pdev, dev, NULL, nouveau_vga_set_decode);
vga_switcheroo_register_client(dev->pdev, &nouveau_switcheroo_ops); vga_switcheroo_register_client(dev->pdev, &nouveau_switcheroo_ops, false);
} }
void void
......
...@@ -1269,7 +1269,7 @@ int radeon_device_init(struct radeon_device *rdev, ...@@ -1269,7 +1269,7 @@ int radeon_device_init(struct radeon_device *rdev,
/* this will fail for cards that aren't VGA class devices, just /* this will fail for cards that aren't VGA class devices, just
* ignore it */ * ignore it */
vga_client_register(rdev->pdev, rdev, NULL, radeon_vga_set_decode); vga_client_register(rdev->pdev, rdev, NULL, radeon_vga_set_decode);
vga_switcheroo_register_client(rdev->pdev, &radeon_switcheroo_ops); vga_switcheroo_register_client(rdev->pdev, &radeon_switcheroo_ops, false);
r = radeon_init(rdev); r = radeon_init(rdev);
if (r) if (r)
......
...@@ -27,6 +27,7 @@ ...@@ -27,6 +27,7 @@
#include <linux/pci.h> #include <linux/pci.h>
#include <linux/console.h> #include <linux/console.h>
#include <linux/vga_switcheroo.h> #include <linux/vga_switcheroo.h>
#include <linux/pm_runtime.h>
#include <linux/vgaarb.h> #include <linux/vgaarb.h>
...@@ -37,6 +38,7 @@ struct vga_switcheroo_client { ...@@ -37,6 +38,7 @@ struct vga_switcheroo_client {
const struct vga_switcheroo_client_ops *ops; const struct vga_switcheroo_client_ops *ops;
int id; int id;
bool active; bool active;
bool driver_power_control;
struct list_head list; struct list_head list;
}; };
...@@ -132,7 +134,7 @@ EXPORT_SYMBOL(vga_switcheroo_unregister_handler); ...@@ -132,7 +134,7 @@ EXPORT_SYMBOL(vga_switcheroo_unregister_handler);
static int register_client(struct pci_dev *pdev, static int register_client(struct pci_dev *pdev,
const struct vga_switcheroo_client_ops *ops, const struct vga_switcheroo_client_ops *ops,
int id, bool active) int id, bool active, bool driver_power_control)
{ {
struct vga_switcheroo_client *client; struct vga_switcheroo_client *client;
...@@ -145,6 +147,7 @@ static int register_client(struct pci_dev *pdev, ...@@ -145,6 +147,7 @@ static int register_client(struct pci_dev *pdev,
client->ops = ops; client->ops = ops;
client->id = id; client->id = id;
client->active = active; client->active = active;
client->driver_power_control = driver_power_control;
mutex_lock(&vgasr_mutex); mutex_lock(&vgasr_mutex);
list_add_tail(&client->list, &vgasr_priv.clients); list_add_tail(&client->list, &vgasr_priv.clients);
...@@ -160,10 +163,11 @@ static int register_client(struct pci_dev *pdev, ...@@ -160,10 +163,11 @@ static int register_client(struct pci_dev *pdev,
} }
int vga_switcheroo_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, return register_client(pdev, ops, -1,
pdev == vga_default_device()); pdev == vga_default_device(), driver_power_control);
} }
EXPORT_SYMBOL(vga_switcheroo_register_client); EXPORT_SYMBOL(vga_switcheroo_register_client);
...@@ -171,7 +175,7 @@ int vga_switcheroo_register_audio_client(struct pci_dev *pdev, ...@@ -171,7 +175,7 @@ int vga_switcheroo_register_audio_client(struct pci_dev *pdev,
const struct vga_switcheroo_client_ops *ops, const struct vga_switcheroo_client_ops *ops,
int id, bool active) 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); EXPORT_SYMBOL(vga_switcheroo_register_audio_client);
...@@ -258,10 +262,11 @@ static int vga_switcheroo_show(struct seq_file *m, void *v) ...@@ -258,10 +262,11 @@ static int vga_switcheroo_show(struct seq_file *m, void *v)
int i = 0; int i = 0;
mutex_lock(&vgasr_mutex); mutex_lock(&vgasr_mutex);
list_for_each_entry(client, &vgasr_priv.clients, list) { 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_id(client) == VGA_SWITCHEROO_DIS ? "DIS" : "IGD",
client_is_vga(client) ? "" : "-Audio", client_is_vga(client) ? "" : "-Audio",
client->active ? '+' : ' ', client->active ? '+' : ' ',
client->driver_power_control ? "Dyn" : "",
client->pwr_state ? "Pwr" : "Off", client->pwr_state ? "Pwr" : "Off",
pci_name(client->pdev)); pci_name(client->pdev));
i++; i++;
...@@ -277,6 +282,8 @@ static int vga_switcheroo_debugfs_open(struct inode *inode, struct file *file) ...@@ -277,6 +282,8 @@ static int vga_switcheroo_debugfs_open(struct inode *inode, struct file *file)
static int vga_switchon(struct vga_switcheroo_client *client) static int vga_switchon(struct vga_switcheroo_client *client)
{ {
if (client->driver_power_control)
return 0;
if (vgasr_priv.handler->power_state) if (vgasr_priv.handler->power_state)
vgasr_priv.handler->power_state(client->id, VGA_SWITCHEROO_ON); vgasr_priv.handler->power_state(client->id, VGA_SWITCHEROO_ON);
/* call the driver callback to turn on device */ /* call the driver callback to turn on device */
...@@ -287,6 +294,8 @@ static int vga_switchon(struct vga_switcheroo_client *client) ...@@ -287,6 +294,8 @@ static int vga_switchon(struct vga_switcheroo_client *client)
static int vga_switchoff(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 */ /* call the driver callback to turn off device */
client->ops->set_gpu_state(client->pdev, VGA_SWITCHEROO_OFF); client->ops->set_gpu_state(client->pdev, VGA_SWITCHEROO_OFF);
if (vgasr_priv.handler->power_state) if (vgasr_priv.handler->power_state)
...@@ -402,6 +411,8 @@ vga_switcheroo_debugfs_write(struct file *filp, const char __user *ubuf, ...@@ -402,6 +411,8 @@ vga_switcheroo_debugfs_write(struct file *filp, const char __user *ubuf,
list_for_each_entry(client, &vgasr_priv.clients, list) { list_for_each_entry(client, &vgasr_priv.clients, list) {
if (client->active || client_is_audio(client)) if (client->active || client_is_audio(client))
continue; continue;
if (client->driver_power_control)
continue;
set_audio_state(client->id, VGA_SWITCHEROO_OFF); set_audio_state(client->id, VGA_SWITCHEROO_OFF);
if (client->pwr_state == VGA_SWITCHEROO_ON) if (client->pwr_state == VGA_SWITCHEROO_ON)
vga_switchoff(client); vga_switchoff(client);
...@@ -413,6 +424,8 @@ vga_switcheroo_debugfs_write(struct file *filp, const char __user *ubuf, ...@@ -413,6 +424,8 @@ vga_switcheroo_debugfs_write(struct file *filp, const char __user *ubuf,
list_for_each_entry(client, &vgasr_priv.clients, list) { list_for_each_entry(client, &vgasr_priv.clients, list) {
if (client->active || client_is_audio(client)) if (client->active || client_is_audio(client))
continue; continue;
if (client->driver_power_control)
continue;
if (client->pwr_state == VGA_SWITCHEROO_OFF) if (client->pwr_state == VGA_SWITCHEROO_OFF)
vga_switchon(client); vga_switchon(client);
set_audio_state(client->id, VGA_SWITCHEROO_ON); set_audio_state(client->id, VGA_SWITCHEROO_ON);
...@@ -565,3 +578,127 @@ int vga_switcheroo_process_delayed_switch(void) ...@@ -565,3 +578,127 @@ int vga_switcheroo_process_delayed_switch(void)
return err; return err;
} }
EXPORT_SYMBOL(vga_switcheroo_process_delayed_switch); 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;
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);
...@@ -45,7 +45,8 @@ struct vga_switcheroo_client_ops { ...@@ -45,7 +45,8 @@ struct vga_switcheroo_client_ops {
#if defined(CONFIG_VGA_SWITCHEROO) #if defined(CONFIG_VGA_SWITCHEROO)
void vga_switcheroo_unregister_client(struct pci_dev *dev); void vga_switcheroo_unregister_client(struct pci_dev *dev);
int vga_switcheroo_register_client(struct pci_dev *dev, int vga_switcheroo_register_client(struct pci_dev *dev,
const struct vga_switcheroo_client_ops *ops); const struct vga_switcheroo_client_ops *ops,
bool driver_power_control);
int vga_switcheroo_register_audio_client(struct pci_dev *pdev, int vga_switcheroo_register_audio_client(struct pci_dev *pdev,
const struct vga_switcheroo_client_ops *ops, const struct vga_switcheroo_client_ops *ops,
int id, bool active); int id, bool active);
...@@ -60,11 +61,15 @@ int vga_switcheroo_process_delayed_switch(void); ...@@ -60,11 +61,15 @@ int vga_switcheroo_process_delayed_switch(void);
int vga_switcheroo_get_client_state(struct pci_dev *dev); int vga_switcheroo_get_client_state(struct pci_dev *dev);
void vga_switcheroo_set_dynamic_switch(struct pci_dev *pdev, enum vga_switcheroo_state dynamic);
int vga_switcheroo_init_domain_pm_ops(struct device *dev, struct dev_pm_domain *domain);
int vga_switcheroo_init_domain_pm_optimus_hdmi_audio(struct device *dev, struct dev_pm_domain *domain);
#else #else
static inline void vga_switcheroo_unregister_client(struct pci_dev *dev) {} static inline void vga_switcheroo_unregister_client(struct pci_dev *dev) {}
static inline int vga_switcheroo_register_client(struct pci_dev *dev, static inline int vga_switcheroo_register_client(struct pci_dev *dev,
const struct vga_switcheroo_client_ops *ops) { return 0; } const struct vga_switcheroo_client_ops *ops, bool driver_power_control) { return 0; }
static inline void vga_switcheroo_client_fb_set(struct pci_dev *dev, struct fb_info *info) {} static inline void vga_switcheroo_client_fb_set(struct pci_dev *dev, struct fb_info *info) {}
static inline int vga_switcheroo_register_handler(struct vga_switcheroo_handler *handler) { return 0; } static inline int vga_switcheroo_register_handler(struct vga_switcheroo_handler *handler) { return 0; }
static inline int vga_switcheroo_register_audio_client(struct pci_dev *pdev, static inline int vga_switcheroo_register_audio_client(struct pci_dev *pdev,
...@@ -74,6 +79,10 @@ static inline void vga_switcheroo_unregister_handler(void) {} ...@@ -74,6 +79,10 @@ static inline void vga_switcheroo_unregister_handler(void) {}
static inline int vga_switcheroo_process_delayed_switch(void) { return 0; } static inline int vga_switcheroo_process_delayed_switch(void) { return 0; }
static inline int vga_switcheroo_get_client_state(struct pci_dev *dev) { return VGA_SWITCHEROO_ON; } static inline int vga_switcheroo_get_client_state(struct pci_dev *dev) { return VGA_SWITCHEROO_ON; }
static inline void vga_switcheroo_set_dynamic_switch(struct pci_dev *pdev, enum vga_switcheroo_state dynamic) {}
static inline int vga_switcheroo_init_domain_pm_ops(struct device *dev, struct dev_pm_domain *domain) { return -EINVAL; }
static inline int vga_switcheroo_init_domain_pm_optimus_hdmi_audio(struct device *dev, struct dev_pm_domain *domain) { return -EINVAL; }
#endif #endif
#endif /* _LINUX_VGA_SWITCHEROO_H_ */ #endif /* _LINUX_VGA_SWITCHEROO_H_ */
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment