kernel-fxtec-pro1x/drivers/gpu/vga/vga_switcheroo.c
Florian Mickler a67b8887ce vga_switcheroo: don't toggle-switch devices
If the requested device is already active, ignore the request.

This restores the original behaviour of the interface. The change was
probably an unintended side effect of

commit 66b37c6777 vga_switcheroo: split switching into two stages

which did not take into account to duplicate the !active check in the split-off
stage2.

Fix this by factoring that check out of stage1 into the debugfs_write routine.

References: https://bugzilla.kernel.org/show_bug.cgi?id=34252
Reported-by: Igor Murzov <e-mail@date.by>
Tested-by: Igor Murzov <e-mail@date.by>
Signed-off-by: Florian Mickler <florian@mickler.org>
Signed-off-by: Dave Airlie <airlied@redhat.com>
2011-05-16 08:57:04 +10:00

498 lines
12 KiB
C

/*
* 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/dmi.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>
#include <linux/vga_switcheroo.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);
void (*reprobe)(struct pci_dev *pdev);
bool (*can_switch)(struct pci_dev *pdev);
int id;
bool active;
};
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;
struct vga_switcheroo_client clients[VGA_SWITCHEROO_MAX_CLIENTS];
struct vga_switcheroo_handler *handler;
};
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;
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)
{
mutex_lock(&vgasr_mutex);
vgasr_priv.handler = NULL;
mutex_unlock(&vgasr_mutex);
}
EXPORT_SYMBOL(vga_switcheroo_unregister_handler);
static void vga_switcheroo_enable(void)
{
int i;
int ret;
/* call the handler to init */
vgasr_priv.handler->init();
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;
}
int vga_switcheroo_register_client(struct pci_dev *pdev,
void (*set_gpu_state)(struct pci_dev *pdev, enum vga_switcheroo_state),
void (*reprobe)(struct pci_dev *pdev),
bool (*can_switch)(struct pci_dev *pdev))
{
int index;
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].reprobe = reprobe;
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) {
printk(KERN_INFO "vga_switcheroo: enabled\n");
vga_switcheroo_enable();
}
mutex_unlock(&vgasr_mutex);
return 0;
}
EXPORT_SYMBOL(vga_switcheroo_register_client);
void vga_switcheroo_unregister_client(struct pci_dev *pdev)
{
int i;
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;
}
}
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);
void vga_switcheroo_client_fb_set(struct pci_dev *pdev,
struct fb_info *info)
{
int i;
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;
}
}
mutex_unlock(&vgasr_mutex);
}
EXPORT_SYMBOL(vga_switcheroo_client_fb_set);
static int vga_switcheroo_show(struct seq_file *m, void *v)
{
int i;
mutex_lock(&vgasr_mutex);
for (i = 0; i < VGA_SWITCHEROO_MAX_CLIENTS; i++) {
seq_printf(m, "%d:%s:%c:%s:%s\n", i,
vgasr_priv.clients[i].id == VGA_SWITCHEROO_DIS ? "DIS" : "IGD",
vgasr_priv.clients[i].active ? '+' : ' ',
vgasr_priv.clients[i].pwr_state ? "Pwr" : "Off",
pci_name(vgasr_priv.clients[i].pdev));
}
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)
{
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->pwr_state = VGA_SWITCHEROO_ON;
return 0;
}
static int vga_switchoff(struct vga_switcheroo_client *client)
{
/* call the driver callback to turn off device */
client->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;
}
/* stage one happens before delay */
static int vga_switchto_stage1(struct vga_switcheroo_client *new_client)
{
int ret;
int i;
struct vga_switcheroo_client *active = NULL;
for (i = 0; i < VGA_SWITCHEROO_MAX_CLIENTS; i++) {
if (vgasr_priv.clients[i].active == true) {
active = &vgasr_priv.clients[i];
break;
}
}
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 */
active->pdev->resource[PCI_ROM_RESOURCE].flags &= ~IORESOURCE_ROM_SHADOW;
new_client->pdev->resource[PCI_ROM_RESOURCE].flags |= IORESOURCE_ROM_SHADOW;
return 0;
}
/* post delay */
static int vga_switchto_stage2(struct vga_switcheroo_client *new_client)
{
int ret;
int i;
struct vga_switcheroo_client *active = NULL;
for (i = 0; i < VGA_SWITCHEROO_MAX_CLIENTS; i++) {
if (vgasr_priv.clients[i].active == true) {
active = &vgasr_priv.clients[i];
break;
}
}
if (!active)
return 0;
active->active = false;
if (new_client->fb_info) {
struct fb_event event;
event.info = new_client->fb_info;
fb_notifier_call_chain(FB_EVENT_REMAP_ALL_CONSOLE, &event);
}
ret = vgasr_priv.handler->switchto(new_client->id);
if (ret)
return ret;
if (new_client->reprobe)
new_client->reprobe(new_client->pdev);
if (active->pwr_state == VGA_SWITCHEROO_ON)
vga_switchoff(active);
new_client->active = true;
return 0;
}
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;
bool delay = false, can_switch;
bool just_mux = false;
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);
if (!vgasr_priv.active) {
cnt = -EINVAL;
goto out;
}
/* 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)
continue;
if (vgasr_priv.clients[i].pwr_state == VGA_SWITCHEROO_ON)
vga_switchoff(&vgasr_priv.clients[i]);
}
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)
continue;
if (vgasr_priv.clients[i].pwr_state == VGA_SWITCHEROO_OFF)
vga_switchon(&vgasr_priv.clients[i]);
}
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;
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;
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;
}
if (client->active == true)
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;
}
}
if (can_switch == false && delay == false)
goto out;
if (can_switch == true) {
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);
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;
ret = vga_switchto_stage1(client);
if (ret)
printk(KERN_ERR "vga_switcheroo: delayed switching stage 1 failed %d\n", ret);
}
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)
{
struct vga_switcheroo_client *client = NULL;
const char *pdev_name;
bool can_switch = true;
int i;
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);
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)
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);
vgasr_priv.delayed_switch_active = false;
err = 0;
err:
mutex_unlock(&vgasr_mutex);
return err;
}
EXPORT_SYMBOL(vga_switcheroo_process_delayed_switch);