blob: a049b743cad09cc657e19678d0c02b5c49c51fef [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;
Takashi Iwai26ec6852012-05-11 07:51:17 +020037 const struct vga_switcheroo_client_ops *ops;
Dave Airlie6a9ee8a2010-02-01 15:38:10 +100038 int id;
39 bool active;
Takashi Iwai79721e02012-04-26 12:55:59 +020040 struct list_head list;
Dave Airlie6a9ee8a2010-02-01 15:38:10 +100041};
42
43static DEFINE_MUTEX(vgasr_mutex);
44
45struct vgasr_priv {
46
47 bool active;
48 bool delayed_switch_active;
49 enum vga_switcheroo_client_id delayed_client_id;
50
51 struct dentry *debugfs_root;
52 struct dentry *switch_file;
53
54 int registered_clients;
Takashi Iwai79721e02012-04-26 12:55:59 +020055 struct list_head clients;
Dave Airlie6a9ee8a2010-02-01 15:38:10 +100056
57 struct vga_switcheroo_handler *handler;
58};
59
60static int vga_switcheroo_debugfs_init(struct vgasr_priv *priv);
61static void vga_switcheroo_debugfs_fini(struct vgasr_priv *priv);
62
63/* only one switcheroo per system */
Takashi Iwai79721e02012-04-26 12:55:59 +020064static struct vgasr_priv vgasr_priv = {
65 .clients = LIST_HEAD_INIT(vgasr_priv.clients),
66};
Dave Airlie6a9ee8a2010-02-01 15:38:10 +100067
68int vga_switcheroo_register_handler(struct vga_switcheroo_handler *handler)
69{
70 mutex_lock(&vgasr_mutex);
71 if (vgasr_priv.handler) {
72 mutex_unlock(&vgasr_mutex);
73 return -EINVAL;
74 }
75
76 vgasr_priv.handler = handler;
77 mutex_unlock(&vgasr_mutex);
78 return 0;
79}
80EXPORT_SYMBOL(vga_switcheroo_register_handler);
81
82void vga_switcheroo_unregister_handler(void)
83{
84 mutex_lock(&vgasr_mutex);
85 vgasr_priv.handler = NULL;
86 mutex_unlock(&vgasr_mutex);
87}
88EXPORT_SYMBOL(vga_switcheroo_unregister_handler);
89
90static void vga_switcheroo_enable(void)
91{
Dave Airlie6a9ee8a2010-02-01 15:38:10 +100092 int ret;
Takashi Iwai79721e02012-04-26 12:55:59 +020093 struct vga_switcheroo_client *client;
94
Dave Airlie6a9ee8a2010-02-01 15:38:10 +100095 /* call the handler to init */
96 vgasr_priv.handler->init();
97
Takashi Iwai79721e02012-04-26 12:55:59 +020098 list_for_each_entry(client, &vgasr_priv.clients, list) {
99 ret = vgasr_priv.handler->get_client_id(client->pdev);
Dave Airlie6a9ee8a2010-02-01 15:38:10 +1000100 if (ret < 0)
101 return;
102
Takashi Iwai79721e02012-04-26 12:55:59 +0200103 client->id = ret;
Dave Airlie6a9ee8a2010-02-01 15:38:10 +1000104 }
105 vga_switcheroo_debugfs_init(&vgasr_priv);
106 vgasr_priv.active = true;
107}
108
109int vga_switcheroo_register_client(struct pci_dev *pdev,
Takashi Iwai26ec6852012-05-11 07:51:17 +0200110 const struct vga_switcheroo_client_ops *ops)
Dave Airlie6a9ee8a2010-02-01 15:38:10 +1000111{
Takashi Iwai79721e02012-04-26 12:55:59 +0200112 struct vga_switcheroo_client *client;
113
114 client = kzalloc(sizeof(*client), GFP_KERNEL);
115 if (!client)
116 return -ENOMEM;
117
118 client->pwr_state = VGA_SWITCHEROO_ON;
119 client->pdev = pdev;
Takashi Iwai26ec6852012-05-11 07:51:17 +0200120 client->ops = ops;
Takashi Iwai79721e02012-04-26 12:55:59 +0200121 client->id = -1;
122 if (pdev == vga_default_device())
123 client->active = true;
Dave Airlie6a9ee8a2010-02-01 15:38:10 +1000124
125 mutex_lock(&vgasr_mutex);
Takashi Iwai79721e02012-04-26 12:55:59 +0200126 list_add_tail(&client->list, &vgasr_priv.clients);
127 vgasr_priv.registered_clients++;
Dave Airlie6a9ee8a2010-02-01 15:38:10 +1000128
129 /* if we get two clients + handler */
Takashi Iwai79721e02012-04-26 12:55:59 +0200130 if (vgasr_priv.registered_clients == 2 && vgasr_priv.handler) {
Dave Airlie6a9ee8a2010-02-01 15:38:10 +1000131 printk(KERN_INFO "vga_switcheroo: enabled\n");
132 vga_switcheroo_enable();
133 }
134 mutex_unlock(&vgasr_mutex);
135 return 0;
136}
137EXPORT_SYMBOL(vga_switcheroo_register_client);
138
Takashi Iwai79721e02012-04-26 12:55:59 +0200139static struct vga_switcheroo_client *
140find_client_from_pci(struct list_head *head, struct pci_dev *pdev)
141{
142 struct vga_switcheroo_client *client;
143 list_for_each_entry(client, head, list)
144 if (client->pdev == pdev)
145 return client;
146 return NULL;
147}
148
149static struct vga_switcheroo_client *
150find_client_from_id(struct list_head *head, int client_id)
151{
152 struct vga_switcheroo_client *client;
153 list_for_each_entry(client, head, list)
154 if (client->id == client_id)
155 return client;
156 return NULL;
157}
158
159static struct vga_switcheroo_client *
160find_active_client(struct list_head *head)
161{
162 struct vga_switcheroo_client *client;
163 list_for_each_entry(client, head, list)
164 if (client->active == true)
165 return client;
166 return NULL;
167}
168
Dave Airlie6a9ee8a2010-02-01 15:38:10 +1000169void vga_switcheroo_unregister_client(struct pci_dev *pdev)
170{
Takashi Iwai79721e02012-04-26 12:55:59 +0200171 struct vga_switcheroo_client *client;
Dave Airlie6a9ee8a2010-02-01 15:38:10 +1000172
173 mutex_lock(&vgasr_mutex);
Takashi Iwai79721e02012-04-26 12:55:59 +0200174 client = find_client_from_pci(&vgasr_priv.clients, pdev);
175 if (client) {
176 list_del(&client->list);
177 kfree(client);
178 vgasr_priv.registered_clients--;
Dave Airlie6a9ee8a2010-02-01 15:38:10 +1000179 }
Dave Airlie6a9ee8a2010-02-01 15:38:10 +1000180 printk(KERN_INFO "vga_switcheroo: disabled\n");
181 vga_switcheroo_debugfs_fini(&vgasr_priv);
182 vgasr_priv.active = false;
183 mutex_unlock(&vgasr_mutex);
184}
185EXPORT_SYMBOL(vga_switcheroo_unregister_client);
186
187void vga_switcheroo_client_fb_set(struct pci_dev *pdev,
188 struct fb_info *info)
189{
Takashi Iwai79721e02012-04-26 12:55:59 +0200190 struct vga_switcheroo_client *client;
Dave Airlie6a9ee8a2010-02-01 15:38:10 +1000191
192 mutex_lock(&vgasr_mutex);
Takashi Iwai79721e02012-04-26 12:55:59 +0200193 client = find_client_from_pci(&vgasr_priv.clients, pdev);
194 if (client)
195 client->fb_info = info;
Dave Airlie6a9ee8a2010-02-01 15:38:10 +1000196 mutex_unlock(&vgasr_mutex);
197}
198EXPORT_SYMBOL(vga_switcheroo_client_fb_set);
199
200static int vga_switcheroo_show(struct seq_file *m, void *v)
201{
Takashi Iwai79721e02012-04-26 12:55:59 +0200202 struct vga_switcheroo_client *client;
203 int i = 0;
Dave Airlie6a9ee8a2010-02-01 15:38:10 +1000204 mutex_lock(&vgasr_mutex);
Takashi Iwai79721e02012-04-26 12:55:59 +0200205 list_for_each_entry(client, &vgasr_priv.clients, list) {
Dave Airlie6c2df402010-12-06 12:30:31 +1000206 seq_printf(m, "%d:%s:%c:%s:%s\n", i,
Takashi Iwai79721e02012-04-26 12:55:59 +0200207 client->id == VGA_SWITCHEROO_DIS ? "DIS" : "IGD",
208 client->active ? '+' : ' ',
209 client->pwr_state ? "Pwr" : "Off",
210 pci_name(client->pdev));
211 i++;
Dave Airlie6a9ee8a2010-02-01 15:38:10 +1000212 }
213 mutex_unlock(&vgasr_mutex);
214 return 0;
215}
216
217static int vga_switcheroo_debugfs_open(struct inode *inode, struct file *file)
218{
219 return single_open(file, vga_switcheroo_show, NULL);
220}
221
222static int vga_switchon(struct vga_switcheroo_client *client)
223{
Dave Airlie5cfb3c32010-12-06 12:31:50 +1000224 if (vgasr_priv.handler->power_state)
225 vgasr_priv.handler->power_state(client->id, VGA_SWITCHEROO_ON);
Dave Airlie6a9ee8a2010-02-01 15:38:10 +1000226 /* call the driver callback to turn on device */
Takashi Iwai26ec6852012-05-11 07:51:17 +0200227 client->ops->set_gpu_state(client->pdev, VGA_SWITCHEROO_ON);
Dave Airlie6a9ee8a2010-02-01 15:38:10 +1000228 client->pwr_state = VGA_SWITCHEROO_ON;
229 return 0;
230}
231
232static int vga_switchoff(struct vga_switcheroo_client *client)
233{
234 /* call the driver callback to turn off device */
Takashi Iwai26ec6852012-05-11 07:51:17 +0200235 client->ops->set_gpu_state(client->pdev, VGA_SWITCHEROO_OFF);
Dave Airlie5cfb3c32010-12-06 12:31:50 +1000236 if (vgasr_priv.handler->power_state)
237 vgasr_priv.handler->power_state(client->id, VGA_SWITCHEROO_OFF);
Dave Airlie6a9ee8a2010-02-01 15:38:10 +1000238 client->pwr_state = VGA_SWITCHEROO_OFF;
239 return 0;
240}
241
Dave Airlie66b37c62010-12-07 14:24:25 +1000242/* stage one happens before delay */
243static int vga_switchto_stage1(struct vga_switcheroo_client *new_client)
Dave Airlie6a9ee8a2010-02-01 15:38:10 +1000244{
Takashi Iwai79721e02012-04-26 12:55:59 +0200245 struct vga_switcheroo_client *active;
Dave Airlie6a9ee8a2010-02-01 15:38:10 +1000246
Takashi Iwai79721e02012-04-26 12:55:59 +0200247 active = find_active_client(&vgasr_priv.clients);
Dave Airlie6a9ee8a2010-02-01 15:38:10 +1000248 if (!active)
249 return 0;
250
Dave Airlie6a9ee8a2010-02-01 15:38:10 +1000251 if (new_client->pwr_state == VGA_SWITCHEROO_OFF)
252 vga_switchon(new_client);
253
Matthew Garrett2fbe8c72012-04-16 16:26:03 -0400254 vga_set_default_device(new_client->pdev);
255
Dave Airlie66b37c62010-12-07 14:24:25 +1000256 return 0;
257}
258
259/* post delay */
260static int vga_switchto_stage2(struct vga_switcheroo_client *new_client)
261{
262 int ret;
Takashi Iwai79721e02012-04-26 12:55:59 +0200263 struct vga_switcheroo_client *active;
Dave Airlie66b37c62010-12-07 14:24:25 +1000264
Takashi Iwai79721e02012-04-26 12:55:59 +0200265 active = find_active_client(&vgasr_priv.clients);
Dave Airlie66b37c62010-12-07 14:24:25 +1000266 if (!active)
267 return 0;
268
269 active->active = false;
Dave Airlie6a9ee8a2010-02-01 15:38:10 +1000270
271 if (new_client->fb_info) {
272 struct fb_event event;
273 event.info = new_client->fb_info;
274 fb_notifier_call_chain(FB_EVENT_REMAP_ALL_CONSOLE, &event);
275 }
276
277 ret = vgasr_priv.handler->switchto(new_client->id);
278 if (ret)
279 return ret;
280
Takashi Iwai26ec6852012-05-11 07:51:17 +0200281 if (new_client->ops->reprobe)
282 new_client->ops->reprobe(new_client->pdev);
Dave Airlie8d608aa2010-12-07 08:57:57 +1000283
Dave Airlie6a9ee8a2010-02-01 15:38:10 +1000284 if (active->pwr_state == VGA_SWITCHEROO_ON)
285 vga_switchoff(active);
286
287 new_client->active = true;
288 return 0;
289}
290
Takashi Iwai79721e02012-04-26 12:55:59 +0200291static bool check_can_switch(void)
292{
293 struct vga_switcheroo_client *client;
294
295 list_for_each_entry(client, &vgasr_priv.clients, list) {
Takashi Iwai26ec6852012-05-11 07:51:17 +0200296 if (!client->ops->can_switch(client->pdev)) {
Takashi Iwai79721e02012-04-26 12:55:59 +0200297 printk(KERN_ERR "vga_switcheroo: client %x refused switch\n", client->id);
298 return false;
299 }
300 }
301 return true;
302}
303
Dave Airlie6a9ee8a2010-02-01 15:38:10 +1000304static ssize_t
305vga_switcheroo_debugfs_write(struct file *filp, const char __user *ubuf,
306 size_t cnt, loff_t *ppos)
307{
308 char usercmd[64];
309 const char *pdev_name;
Takashi Iwai79721e02012-04-26 12:55:59 +0200310 int ret;
Dave Airlie6a9ee8a2010-02-01 15:38:10 +1000311 bool delay = false, can_switch;
Dave Airlie851ab952010-12-06 12:35:52 +1000312 bool just_mux = false;
Dave Airlie6a9ee8a2010-02-01 15:38:10 +1000313 int client_id = -1;
314 struct vga_switcheroo_client *client = NULL;
315
316 if (cnt > 63)
317 cnt = 63;
318
319 if (copy_from_user(usercmd, ubuf, cnt))
320 return -EFAULT;
321
322 mutex_lock(&vgasr_mutex);
323
Jiri Slaby8c88e502010-04-27 14:11:03 -0700324 if (!vgasr_priv.active) {
325 cnt = -EINVAL;
326 goto out;
327 }
Dave Airlie6a9ee8a2010-02-01 15:38:10 +1000328
329 /* pwr off the device not in use */
330 if (strncmp(usercmd, "OFF", 3) == 0) {
Takashi Iwai79721e02012-04-26 12:55:59 +0200331 list_for_each_entry(client, &vgasr_priv.clients, list) {
332 if (client->active)
Dave Airlie6a9ee8a2010-02-01 15:38:10 +1000333 continue;
Takashi Iwai79721e02012-04-26 12:55:59 +0200334 if (client->pwr_state == VGA_SWITCHEROO_ON)
335 vga_switchoff(client);
Dave Airlie6a9ee8a2010-02-01 15:38:10 +1000336 }
337 goto out;
338 }
339 /* pwr on the device not in use */
340 if (strncmp(usercmd, "ON", 2) == 0) {
Takashi Iwai79721e02012-04-26 12:55:59 +0200341 list_for_each_entry(client, &vgasr_priv.clients, list) {
342 if (client->active)
Dave Airlie6a9ee8a2010-02-01 15:38:10 +1000343 continue;
Takashi Iwai79721e02012-04-26 12:55:59 +0200344 if (client->pwr_state == VGA_SWITCHEROO_OFF)
345 vga_switchon(client);
Dave Airlie6a9ee8a2010-02-01 15:38:10 +1000346 }
347 goto out;
348 }
349
350 /* request a delayed switch - test can we switch now */
351 if (strncmp(usercmd, "DIGD", 4) == 0) {
352 client_id = VGA_SWITCHEROO_IGD;
353 delay = true;
354 }
355
356 if (strncmp(usercmd, "DDIS", 4) == 0) {
357 client_id = VGA_SWITCHEROO_DIS;
358 delay = true;
359 }
360
361 if (strncmp(usercmd, "IGD", 3) == 0)
362 client_id = VGA_SWITCHEROO_IGD;
363
364 if (strncmp(usercmd, "DIS", 3) == 0)
365 client_id = VGA_SWITCHEROO_DIS;
366
Dan Carpenterfea6f332011-01-07 08:12:27 +0300367 if (strncmp(usercmd, "MIGD", 4) == 0) {
Dave Airlie851ab952010-12-06 12:35:52 +1000368 just_mux = true;
369 client_id = VGA_SWITCHEROO_IGD;
370 }
Dan Carpenterfea6f332011-01-07 08:12:27 +0300371 if (strncmp(usercmd, "MDIS", 4) == 0) {
Dave Airlie851ab952010-12-06 12:35:52 +1000372 just_mux = true;
373 client_id = VGA_SWITCHEROO_DIS;
374 }
375
Dave Airlie6a9ee8a2010-02-01 15:38:10 +1000376 if (client_id == -1)
377 goto out;
Takashi Iwai79721e02012-04-26 12:55:59 +0200378 client = find_client_from_id(&vgasr_priv.clients, client_id);
379 if (!client)
380 goto out;
Dave Airlie6a9ee8a2010-02-01 15:38:10 +1000381
382 vgasr_priv.delayed_switch_active = false;
Dave Airlie851ab952010-12-06 12:35:52 +1000383
384 if (just_mux) {
385 ret = vgasr_priv.handler->switchto(client_id);
386 goto out;
387 }
388
Takashi Iwai79721e02012-04-26 12:55:59 +0200389 if (client->active)
Florian Micklera67b8882011-05-15 16:32:50 +0200390 goto out;
391
Dave Airlie6a9ee8a2010-02-01 15:38:10 +1000392 /* okay we want a switch - test if devices are willing to switch */
Takashi Iwai79721e02012-04-26 12:55:59 +0200393 can_switch = check_can_switch();
Dave Airlie6a9ee8a2010-02-01 15:38:10 +1000394
395 if (can_switch == false && delay == false)
396 goto out;
397
Takashi Iwai79721e02012-04-26 12:55:59 +0200398 if (can_switch) {
Dave Airlie6a9ee8a2010-02-01 15:38:10 +1000399 pdev_name = pci_name(client->pdev);
Dave Airlie66b37c62010-12-07 14:24:25 +1000400 ret = vga_switchto_stage1(client);
Dave Airlie6a9ee8a2010-02-01 15:38:10 +1000401 if (ret)
Dave Airlie66b37c62010-12-07 14:24:25 +1000402 printk(KERN_ERR "vga_switcheroo: switching failed stage 1 %d\n", ret);
403
404 ret = vga_switchto_stage2(client);
405 if (ret)
406 printk(KERN_ERR "vga_switcheroo: switching failed stage 2 %d\n", ret);
407
Dave Airlie6a9ee8a2010-02-01 15:38:10 +1000408 } else {
409 printk(KERN_INFO "vga_switcheroo: setting delayed switch to client %d\n", client->id);
410 vgasr_priv.delayed_switch_active = true;
411 vgasr_priv.delayed_client_id = client_id;
412
Dave Airlie66b37c62010-12-07 14:24:25 +1000413 ret = vga_switchto_stage1(client);
414 if (ret)
415 printk(KERN_ERR "vga_switcheroo: delayed switching stage 1 failed %d\n", ret);
Dave Airlie6a9ee8a2010-02-01 15:38:10 +1000416 }
417
418out:
419 mutex_unlock(&vgasr_mutex);
420 return cnt;
421}
422
423static const struct file_operations vga_switcheroo_debugfs_fops = {
424 .owner = THIS_MODULE,
425 .open = vga_switcheroo_debugfs_open,
426 .write = vga_switcheroo_debugfs_write,
427 .read = seq_read,
428 .llseek = seq_lseek,
429 .release = single_release,
430};
431
432static void vga_switcheroo_debugfs_fini(struct vgasr_priv *priv)
433{
434 if (priv->switch_file) {
435 debugfs_remove(priv->switch_file);
436 priv->switch_file = NULL;
437 }
438 if (priv->debugfs_root) {
439 debugfs_remove(priv->debugfs_root);
440 priv->debugfs_root = NULL;
441 }
442}
443
444static int vga_switcheroo_debugfs_init(struct vgasr_priv *priv)
445{
446 /* already initialised */
447 if (priv->debugfs_root)
448 return 0;
449 priv->debugfs_root = debugfs_create_dir("vgaswitcheroo", NULL);
450
451 if (!priv->debugfs_root) {
452 printk(KERN_ERR "vga_switcheroo: Cannot create /sys/kernel/debug/vgaswitcheroo\n");
453 goto fail;
454 }
455
456 priv->switch_file = debugfs_create_file("switch", 0644,
457 priv->debugfs_root, NULL, &vga_switcheroo_debugfs_fops);
458 if (!priv->switch_file) {
459 printk(KERN_ERR "vga_switcheroo: cannot create /sys/kernel/debug/vgaswitcheroo/switch\n");
460 goto fail;
461 }
462 return 0;
463fail:
464 vga_switcheroo_debugfs_fini(priv);
465 return -1;
466}
467
468int vga_switcheroo_process_delayed_switch(void)
469{
Takashi Iwai79721e02012-04-26 12:55:59 +0200470 struct vga_switcheroo_client *client;
Dave Airlie6a9ee8a2010-02-01 15:38:10 +1000471 const char *pdev_name;
Dave Airlie6a9ee8a2010-02-01 15:38:10 +1000472 int ret;
473 int err = -EINVAL;
474
475 mutex_lock(&vgasr_mutex);
476 if (!vgasr_priv.delayed_switch_active)
477 goto err;
478
479 printk(KERN_INFO "vga_switcheroo: processing delayed switch to %d\n", vgasr_priv.delayed_client_id);
480
Takashi Iwai79721e02012-04-26 12:55:59 +0200481 client = find_client_from_id(&vgasr_priv.clients,
482 vgasr_priv.delayed_client_id);
483 if (!client || !check_can_switch())
Dave Airlie6a9ee8a2010-02-01 15:38:10 +1000484 goto err;
485
486 pdev_name = pci_name(client->pdev);
Dave Airlie66b37c62010-12-07 14:24:25 +1000487 ret = vga_switchto_stage2(client);
Dave Airlie6a9ee8a2010-02-01 15:38:10 +1000488 if (ret)
Dave Airlie66b37c62010-12-07 14:24:25 +1000489 printk(KERN_ERR "vga_switcheroo: delayed switching failed stage 2 %d\n", ret);
Dave Airlie6a9ee8a2010-02-01 15:38:10 +1000490
491 vgasr_priv.delayed_switch_active = false;
492 err = 0;
493err:
494 mutex_unlock(&vgasr_mutex);
495 return err;
496}
497EXPORT_SYMBOL(vga_switcheroo_process_delayed_switch);
498