blob: da29da6aadace0d6bdadaf777a1f23c1a0d3d4ab [file] [log] [blame]
Dave Airlie6a9ee8a2010-02-01 15:38:10 +10001/*
2 * Copyright (c) 2010 Red Hat Inc.
3 * Author : Dave Airlie <airlied@redhat.com>
4 *
5 *
6 * Licensed under GPLv2
7 *
8 * vga_switcheroo.c - Support for laptop with dual GPU using one set of outputs
9
10 Switcher interface - methods require for ATPX and DCM
11 - switchto - this throws the output MUX switch
12 - discrete_set_power - sets the power state for the discrete card
13
14 GPU driver interface
15 - set_gpu_state - this should do the equiv of s/r for the card
16 - this should *not* set the discrete power state
17 - switch_check - check if the device is in a position to switch now
18 */
19
20#include <linux/module.h>
21#include <linux/dmi.h>
22#include <linux/seq_file.h>
23#include <linux/uaccess.h>
24#include <linux/fs.h>
25#include <linux/debugfs.h>
26#include <linux/fb.h>
27
Dave Airlie6a9ee8a2010-02-01 15:38:10 +100028#include <linux/pci.h>
29#include <linux/vga_switcheroo.h>
30
Matthew Garrett2fbe8c72012-04-16 16:26:03 -040031#include <linux/vgaarb.h>
32
Dave Airlie6a9ee8a2010-02-01 15:38:10 +100033struct vga_switcheroo_client {
34 struct pci_dev *pdev;
35 struct fb_info *fb_info;
36 int pwr_state;
37 void (*set_gpu_state)(struct pci_dev *pdev, enum vga_switcheroo_state);
Dave Airlie8d608aa2010-12-07 08:57:57 +100038 void (*reprobe)(struct pci_dev *pdev);
Dave Airlie6a9ee8a2010-02-01 15:38:10 +100039 bool (*can_switch)(struct pci_dev *pdev);
40 int id;
41 bool active;
Takashi Iwai79721e02012-04-26 12:55:59 +020042 struct list_head list;
Dave Airlie6a9ee8a2010-02-01 15:38:10 +100043};
44
45static DEFINE_MUTEX(vgasr_mutex);
46
47struct vgasr_priv {
48
49 bool active;
50 bool delayed_switch_active;
51 enum vga_switcheroo_client_id delayed_client_id;
52
53 struct dentry *debugfs_root;
54 struct dentry *switch_file;
55
56 int registered_clients;
Takashi Iwai79721e02012-04-26 12:55:59 +020057 struct list_head clients;
Dave Airlie6a9ee8a2010-02-01 15:38:10 +100058
59 struct vga_switcheroo_handler *handler;
60};
61
62static int vga_switcheroo_debugfs_init(struct vgasr_priv *priv);
63static void vga_switcheroo_debugfs_fini(struct vgasr_priv *priv);
64
65/* only one switcheroo per system */
Takashi Iwai79721e02012-04-26 12:55:59 +020066static struct vgasr_priv vgasr_priv = {
67 .clients = LIST_HEAD_INIT(vgasr_priv.clients),
68};
Dave Airlie6a9ee8a2010-02-01 15:38:10 +100069
70int vga_switcheroo_register_handler(struct vga_switcheroo_handler *handler)
71{
72 mutex_lock(&vgasr_mutex);
73 if (vgasr_priv.handler) {
74 mutex_unlock(&vgasr_mutex);
75 return -EINVAL;
76 }
77
78 vgasr_priv.handler = handler;
79 mutex_unlock(&vgasr_mutex);
80 return 0;
81}
82EXPORT_SYMBOL(vga_switcheroo_register_handler);
83
84void vga_switcheroo_unregister_handler(void)
85{
86 mutex_lock(&vgasr_mutex);
87 vgasr_priv.handler = NULL;
88 mutex_unlock(&vgasr_mutex);
89}
90EXPORT_SYMBOL(vga_switcheroo_unregister_handler);
91
92static void vga_switcheroo_enable(void)
93{
Dave Airlie6a9ee8a2010-02-01 15:38:10 +100094 int ret;
Takashi Iwai79721e02012-04-26 12:55:59 +020095 struct vga_switcheroo_client *client;
96
Dave Airlie6a9ee8a2010-02-01 15:38:10 +100097 /* call the handler to init */
98 vgasr_priv.handler->init();
99
Takashi Iwai79721e02012-04-26 12:55:59 +0200100 list_for_each_entry(client, &vgasr_priv.clients, list) {
101 ret = vgasr_priv.handler->get_client_id(client->pdev);
Dave Airlie6a9ee8a2010-02-01 15:38:10 +1000102 if (ret < 0)
103 return;
104
Takashi Iwai79721e02012-04-26 12:55:59 +0200105 client->id = ret;
Dave Airlie6a9ee8a2010-02-01 15:38:10 +1000106 }
107 vga_switcheroo_debugfs_init(&vgasr_priv);
108 vgasr_priv.active = true;
109}
110
111int vga_switcheroo_register_client(struct pci_dev *pdev,
112 void (*set_gpu_state)(struct pci_dev *pdev, enum vga_switcheroo_state),
Dave Airlie8d608aa2010-12-07 08:57:57 +1000113 void (*reprobe)(struct pci_dev *pdev),
Dave Airlie6a9ee8a2010-02-01 15:38:10 +1000114 bool (*can_switch)(struct pci_dev *pdev))
115{
Takashi Iwai79721e02012-04-26 12:55:59 +0200116 struct vga_switcheroo_client *client;
117
118 client = kzalloc(sizeof(*client), GFP_KERNEL);
119 if (!client)
120 return -ENOMEM;
121
122 client->pwr_state = VGA_SWITCHEROO_ON;
123 client->pdev = pdev;
124 client->set_gpu_state = set_gpu_state;
125 client->reprobe = reprobe;
126 client->can_switch = can_switch;
127 client->id = -1;
128 if (pdev == vga_default_device())
129 client->active = true;
Dave Airlie6a9ee8a2010-02-01 15:38:10 +1000130
131 mutex_lock(&vgasr_mutex);
Takashi Iwai79721e02012-04-26 12:55:59 +0200132 list_add_tail(&client->list, &vgasr_priv.clients);
133 vgasr_priv.registered_clients++;
Dave Airlie6a9ee8a2010-02-01 15:38:10 +1000134
135 /* if we get two clients + handler */
Takashi Iwai79721e02012-04-26 12:55:59 +0200136 if (vgasr_priv.registered_clients == 2 && vgasr_priv.handler) {
Dave Airlie6a9ee8a2010-02-01 15:38:10 +1000137 printk(KERN_INFO "vga_switcheroo: enabled\n");
138 vga_switcheroo_enable();
139 }
140 mutex_unlock(&vgasr_mutex);
141 return 0;
142}
143EXPORT_SYMBOL(vga_switcheroo_register_client);
144
Takashi Iwai79721e02012-04-26 12:55:59 +0200145static struct vga_switcheroo_client *
146find_client_from_pci(struct list_head *head, struct pci_dev *pdev)
147{
148 struct vga_switcheroo_client *client;
149 list_for_each_entry(client, head, list)
150 if (client->pdev == pdev)
151 return client;
152 return NULL;
153}
154
155static struct vga_switcheroo_client *
156find_client_from_id(struct list_head *head, int client_id)
157{
158 struct vga_switcheroo_client *client;
159 list_for_each_entry(client, head, list)
160 if (client->id == client_id)
161 return client;
162 return NULL;
163}
164
165static struct vga_switcheroo_client *
166find_active_client(struct list_head *head)
167{
168 struct vga_switcheroo_client *client;
169 list_for_each_entry(client, head, list)
170 if (client->active == true)
171 return client;
172 return NULL;
173}
174
Dave Airlie6a9ee8a2010-02-01 15:38:10 +1000175void vga_switcheroo_unregister_client(struct pci_dev *pdev)
176{
Takashi Iwai79721e02012-04-26 12:55:59 +0200177 struct vga_switcheroo_client *client;
Dave Airlie6a9ee8a2010-02-01 15:38:10 +1000178
179 mutex_lock(&vgasr_mutex);
Takashi Iwai79721e02012-04-26 12:55:59 +0200180 client = find_client_from_pci(&vgasr_priv.clients, pdev);
181 if (client) {
182 list_del(&client->list);
183 kfree(client);
184 vgasr_priv.registered_clients--;
Dave Airlie6a9ee8a2010-02-01 15:38:10 +1000185 }
Dave Airlie6a9ee8a2010-02-01 15:38:10 +1000186 printk(KERN_INFO "vga_switcheroo: disabled\n");
187 vga_switcheroo_debugfs_fini(&vgasr_priv);
188 vgasr_priv.active = false;
189 mutex_unlock(&vgasr_mutex);
190}
191EXPORT_SYMBOL(vga_switcheroo_unregister_client);
192
193void vga_switcheroo_client_fb_set(struct pci_dev *pdev,
194 struct fb_info *info)
195{
Takashi Iwai79721e02012-04-26 12:55:59 +0200196 struct vga_switcheroo_client *client;
Dave Airlie6a9ee8a2010-02-01 15:38:10 +1000197
198 mutex_lock(&vgasr_mutex);
Takashi Iwai79721e02012-04-26 12:55:59 +0200199 client = find_client_from_pci(&vgasr_priv.clients, pdev);
200 if (client)
201 client->fb_info = info;
Dave Airlie6a9ee8a2010-02-01 15:38:10 +1000202 mutex_unlock(&vgasr_mutex);
203}
204EXPORT_SYMBOL(vga_switcheroo_client_fb_set);
205
206static int vga_switcheroo_show(struct seq_file *m, void *v)
207{
Takashi Iwai79721e02012-04-26 12:55:59 +0200208 struct vga_switcheroo_client *client;
209 int i = 0;
Dave Airlie6a9ee8a2010-02-01 15:38:10 +1000210 mutex_lock(&vgasr_mutex);
Takashi Iwai79721e02012-04-26 12:55:59 +0200211 list_for_each_entry(client, &vgasr_priv.clients, list) {
Dave Airlie6c2df402010-12-06 12:30:31 +1000212 seq_printf(m, "%d:%s:%c:%s:%s\n", i,
Takashi Iwai79721e02012-04-26 12:55:59 +0200213 client->id == VGA_SWITCHEROO_DIS ? "DIS" : "IGD",
214 client->active ? '+' : ' ',
215 client->pwr_state ? "Pwr" : "Off",
216 pci_name(client->pdev));
217 i++;
Dave Airlie6a9ee8a2010-02-01 15:38:10 +1000218 }
219 mutex_unlock(&vgasr_mutex);
220 return 0;
221}
222
223static int vga_switcheroo_debugfs_open(struct inode *inode, struct file *file)
224{
225 return single_open(file, vga_switcheroo_show, NULL);
226}
227
228static int vga_switchon(struct vga_switcheroo_client *client)
229{
Dave Airlie5cfb3c32010-12-06 12:31:50 +1000230 if (vgasr_priv.handler->power_state)
231 vgasr_priv.handler->power_state(client->id, VGA_SWITCHEROO_ON);
Dave Airlie6a9ee8a2010-02-01 15:38:10 +1000232 /* call the driver callback to turn on device */
233 client->set_gpu_state(client->pdev, VGA_SWITCHEROO_ON);
234 client->pwr_state = VGA_SWITCHEROO_ON;
235 return 0;
236}
237
238static int vga_switchoff(struct vga_switcheroo_client *client)
239{
240 /* call the driver callback to turn off device */
241 client->set_gpu_state(client->pdev, VGA_SWITCHEROO_OFF);
Dave Airlie5cfb3c32010-12-06 12:31:50 +1000242 if (vgasr_priv.handler->power_state)
243 vgasr_priv.handler->power_state(client->id, VGA_SWITCHEROO_OFF);
Dave Airlie6a9ee8a2010-02-01 15:38:10 +1000244 client->pwr_state = VGA_SWITCHEROO_OFF;
245 return 0;
246}
247
Dave Airlie66b37c62010-12-07 14:24:25 +1000248/* stage one happens before delay */
249static int vga_switchto_stage1(struct vga_switcheroo_client *new_client)
Dave Airlie6a9ee8a2010-02-01 15:38:10 +1000250{
Takashi Iwai79721e02012-04-26 12:55:59 +0200251 struct vga_switcheroo_client *active;
Dave Airlie6a9ee8a2010-02-01 15:38:10 +1000252
Takashi Iwai79721e02012-04-26 12:55:59 +0200253 active = find_active_client(&vgasr_priv.clients);
Dave Airlie6a9ee8a2010-02-01 15:38:10 +1000254 if (!active)
255 return 0;
256
Dave Airlie6a9ee8a2010-02-01 15:38:10 +1000257 if (new_client->pwr_state == VGA_SWITCHEROO_OFF)
258 vga_switchon(new_client);
259
Matthew Garrett2fbe8c72012-04-16 16:26:03 -0400260 vga_set_default_device(new_client->pdev);
261
Dave Airlie66b37c62010-12-07 14:24:25 +1000262 return 0;
263}
264
265/* post delay */
266static int vga_switchto_stage2(struct vga_switcheroo_client *new_client)
267{
268 int ret;
Takashi Iwai79721e02012-04-26 12:55:59 +0200269 struct vga_switcheroo_client *active;
Dave Airlie66b37c62010-12-07 14:24:25 +1000270
Takashi Iwai79721e02012-04-26 12:55:59 +0200271 active = find_active_client(&vgasr_priv.clients);
Dave Airlie66b37c62010-12-07 14:24:25 +1000272 if (!active)
273 return 0;
274
275 active->active = false;
Dave Airlie6a9ee8a2010-02-01 15:38:10 +1000276
277 if (new_client->fb_info) {
278 struct fb_event event;
279 event.info = new_client->fb_info;
280 fb_notifier_call_chain(FB_EVENT_REMAP_ALL_CONSOLE, &event);
281 }
282
283 ret = vgasr_priv.handler->switchto(new_client->id);
284 if (ret)
285 return ret;
286
Dave Airlie8d608aa2010-12-07 08:57:57 +1000287 if (new_client->reprobe)
288 new_client->reprobe(new_client->pdev);
289
Dave Airlie6a9ee8a2010-02-01 15:38:10 +1000290 if (active->pwr_state == VGA_SWITCHEROO_ON)
291 vga_switchoff(active);
292
293 new_client->active = true;
294 return 0;
295}
296
Takashi Iwai79721e02012-04-26 12:55:59 +0200297static bool check_can_switch(void)
298{
299 struct vga_switcheroo_client *client;
300
301 list_for_each_entry(client, &vgasr_priv.clients, list) {
302 if (!client->can_switch(client->pdev)) {
303 printk(KERN_ERR "vga_switcheroo: client %x refused switch\n", client->id);
304 return false;
305 }
306 }
307 return true;
308}
309
Dave Airlie6a9ee8a2010-02-01 15:38:10 +1000310static ssize_t
311vga_switcheroo_debugfs_write(struct file *filp, const char __user *ubuf,
312 size_t cnt, loff_t *ppos)
313{
314 char usercmd[64];
315 const char *pdev_name;
Takashi Iwai79721e02012-04-26 12:55:59 +0200316 int ret;
Dave Airlie6a9ee8a2010-02-01 15:38:10 +1000317 bool delay = false, can_switch;
Dave Airlie851ab952010-12-06 12:35:52 +1000318 bool just_mux = false;
Dave Airlie6a9ee8a2010-02-01 15:38:10 +1000319 int client_id = -1;
320 struct vga_switcheroo_client *client = NULL;
321
322 if (cnt > 63)
323 cnt = 63;
324
325 if (copy_from_user(usercmd, ubuf, cnt))
326 return -EFAULT;
327
328 mutex_lock(&vgasr_mutex);
329
Jiri Slaby8c88e502010-04-27 14:11:03 -0700330 if (!vgasr_priv.active) {
331 cnt = -EINVAL;
332 goto out;
333 }
Dave Airlie6a9ee8a2010-02-01 15:38:10 +1000334
335 /* pwr off the device not in use */
336 if (strncmp(usercmd, "OFF", 3) == 0) {
Takashi Iwai79721e02012-04-26 12:55:59 +0200337 list_for_each_entry(client, &vgasr_priv.clients, list) {
338 if (client->active)
Dave Airlie6a9ee8a2010-02-01 15:38:10 +1000339 continue;
Takashi Iwai79721e02012-04-26 12:55:59 +0200340 if (client->pwr_state == VGA_SWITCHEROO_ON)
341 vga_switchoff(client);
Dave Airlie6a9ee8a2010-02-01 15:38:10 +1000342 }
343 goto out;
344 }
345 /* pwr on the device not in use */
346 if (strncmp(usercmd, "ON", 2) == 0) {
Takashi Iwai79721e02012-04-26 12:55:59 +0200347 list_for_each_entry(client, &vgasr_priv.clients, list) {
348 if (client->active)
Dave Airlie6a9ee8a2010-02-01 15:38:10 +1000349 continue;
Takashi Iwai79721e02012-04-26 12:55:59 +0200350 if (client->pwr_state == VGA_SWITCHEROO_OFF)
351 vga_switchon(client);
Dave Airlie6a9ee8a2010-02-01 15:38:10 +1000352 }
353 goto out;
354 }
355
356 /* request a delayed switch - test can we switch now */
357 if (strncmp(usercmd, "DIGD", 4) == 0) {
358 client_id = VGA_SWITCHEROO_IGD;
359 delay = true;
360 }
361
362 if (strncmp(usercmd, "DDIS", 4) == 0) {
363 client_id = VGA_SWITCHEROO_DIS;
364 delay = true;
365 }
366
367 if (strncmp(usercmd, "IGD", 3) == 0)
368 client_id = VGA_SWITCHEROO_IGD;
369
370 if (strncmp(usercmd, "DIS", 3) == 0)
371 client_id = VGA_SWITCHEROO_DIS;
372
Dan Carpenterfea6f332011-01-07 08:12:27 +0300373 if (strncmp(usercmd, "MIGD", 4) == 0) {
Dave Airlie851ab952010-12-06 12:35:52 +1000374 just_mux = true;
375 client_id = VGA_SWITCHEROO_IGD;
376 }
Dan Carpenterfea6f332011-01-07 08:12:27 +0300377 if (strncmp(usercmd, "MDIS", 4) == 0) {
Dave Airlie851ab952010-12-06 12:35:52 +1000378 just_mux = true;
379 client_id = VGA_SWITCHEROO_DIS;
380 }
381
Dave Airlie6a9ee8a2010-02-01 15:38:10 +1000382 if (client_id == -1)
383 goto out;
Takashi Iwai79721e02012-04-26 12:55:59 +0200384 client = find_client_from_id(&vgasr_priv.clients, client_id);
385 if (!client)
386 goto out;
Dave Airlie6a9ee8a2010-02-01 15:38:10 +1000387
388 vgasr_priv.delayed_switch_active = false;
Dave Airlie851ab952010-12-06 12:35:52 +1000389
390 if (just_mux) {
391 ret = vgasr_priv.handler->switchto(client_id);
392 goto out;
393 }
394
Takashi Iwai79721e02012-04-26 12:55:59 +0200395 if (client->active)
Florian Micklera67b8882011-05-15 16:32:50 +0200396 goto out;
397
Dave Airlie6a9ee8a2010-02-01 15:38:10 +1000398 /* okay we want a switch - test if devices are willing to switch */
Takashi Iwai79721e02012-04-26 12:55:59 +0200399 can_switch = check_can_switch();
Dave Airlie6a9ee8a2010-02-01 15:38:10 +1000400
401 if (can_switch == false && delay == false)
402 goto out;
403
Takashi Iwai79721e02012-04-26 12:55:59 +0200404 if (can_switch) {
Dave Airlie6a9ee8a2010-02-01 15:38:10 +1000405 pdev_name = pci_name(client->pdev);
Dave Airlie66b37c62010-12-07 14:24:25 +1000406 ret = vga_switchto_stage1(client);
Dave Airlie6a9ee8a2010-02-01 15:38:10 +1000407 if (ret)
Dave Airlie66b37c62010-12-07 14:24:25 +1000408 printk(KERN_ERR "vga_switcheroo: switching failed stage 1 %d\n", ret);
409
410 ret = vga_switchto_stage2(client);
411 if (ret)
412 printk(KERN_ERR "vga_switcheroo: switching failed stage 2 %d\n", ret);
413
Dave Airlie6a9ee8a2010-02-01 15:38:10 +1000414 } else {
415 printk(KERN_INFO "vga_switcheroo: setting delayed switch to client %d\n", client->id);
416 vgasr_priv.delayed_switch_active = true;
417 vgasr_priv.delayed_client_id = client_id;
418
Dave Airlie66b37c62010-12-07 14:24:25 +1000419 ret = vga_switchto_stage1(client);
420 if (ret)
421 printk(KERN_ERR "vga_switcheroo: delayed switching stage 1 failed %d\n", ret);
Dave Airlie6a9ee8a2010-02-01 15:38:10 +1000422 }
423
424out:
425 mutex_unlock(&vgasr_mutex);
426 return cnt;
427}
428
429static const struct file_operations vga_switcheroo_debugfs_fops = {
430 .owner = THIS_MODULE,
431 .open = vga_switcheroo_debugfs_open,
432 .write = vga_switcheroo_debugfs_write,
433 .read = seq_read,
434 .llseek = seq_lseek,
435 .release = single_release,
436};
437
438static void vga_switcheroo_debugfs_fini(struct vgasr_priv *priv)
439{
440 if (priv->switch_file) {
441 debugfs_remove(priv->switch_file);
442 priv->switch_file = NULL;
443 }
444 if (priv->debugfs_root) {
445 debugfs_remove(priv->debugfs_root);
446 priv->debugfs_root = NULL;
447 }
448}
449
450static int vga_switcheroo_debugfs_init(struct vgasr_priv *priv)
451{
452 /* already initialised */
453 if (priv->debugfs_root)
454 return 0;
455 priv->debugfs_root = debugfs_create_dir("vgaswitcheroo", NULL);
456
457 if (!priv->debugfs_root) {
458 printk(KERN_ERR "vga_switcheroo: Cannot create /sys/kernel/debug/vgaswitcheroo\n");
459 goto fail;
460 }
461
462 priv->switch_file = debugfs_create_file("switch", 0644,
463 priv->debugfs_root, NULL, &vga_switcheroo_debugfs_fops);
464 if (!priv->switch_file) {
465 printk(KERN_ERR "vga_switcheroo: cannot create /sys/kernel/debug/vgaswitcheroo/switch\n");
466 goto fail;
467 }
468 return 0;
469fail:
470 vga_switcheroo_debugfs_fini(priv);
471 return -1;
472}
473
474int vga_switcheroo_process_delayed_switch(void)
475{
Takashi Iwai79721e02012-04-26 12:55:59 +0200476 struct vga_switcheroo_client *client;
Dave Airlie6a9ee8a2010-02-01 15:38:10 +1000477 const char *pdev_name;
Dave Airlie6a9ee8a2010-02-01 15:38:10 +1000478 int ret;
479 int err = -EINVAL;
480
481 mutex_lock(&vgasr_mutex);
482 if (!vgasr_priv.delayed_switch_active)
483 goto err;
484
485 printk(KERN_INFO "vga_switcheroo: processing delayed switch to %d\n", vgasr_priv.delayed_client_id);
486
Takashi Iwai79721e02012-04-26 12:55:59 +0200487 client = find_client_from_id(&vgasr_priv.clients,
488 vgasr_priv.delayed_client_id);
489 if (!client || !check_can_switch())
Dave Airlie6a9ee8a2010-02-01 15:38:10 +1000490 goto err;
491
492 pdev_name = pci_name(client->pdev);
Dave Airlie66b37c62010-12-07 14:24:25 +1000493 ret = vga_switchto_stage2(client);
Dave Airlie6a9ee8a2010-02-01 15:38:10 +1000494 if (ret)
Dave Airlie66b37c62010-12-07 14:24:25 +1000495 printk(KERN_ERR "vga_switcheroo: delayed switching failed stage 2 %d\n", ret);
Dave Airlie6a9ee8a2010-02-01 15:38:10 +1000496
497 vgasr_priv.delayed_switch_active = false;
498 err = 0;
499err:
500 mutex_unlock(&vgasr_mutex);
501 return err;
502}
503EXPORT_SYMBOL(vga_switcheroo_process_delayed_switch);
504