diff options
Diffstat (limited to 'drivers/gpu/vga/vga_switcheroo.c')
-rw-r--r-- | drivers/gpu/vga/vga_switcheroo.c | 453 |
1 files changed, 453 insertions, 0 deletions
diff --git a/drivers/gpu/vga/vga_switcheroo.c b/drivers/gpu/vga/vga_switcheroo.c new file mode 100644 index 000000000000..a3f587a0aba9 --- /dev/null +++ b/drivers/gpu/vga/vga_switcheroo.c | |||
@@ -0,0 +1,453 @@ | |||
1 | /* | ||
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 | |||
28 | #include <acpi/acpi.h> | ||
29 | #include <acpi/acpi_bus.h> | ||
30 | |||
31 | #include <linux/pci.h> | ||
32 | #include <linux/vga_switcheroo.h> | ||
33 | |||
34 | struct vga_switcheroo_client { | ||
35 | struct pci_dev *pdev; | ||
36 | struct fb_info *fb_info; | ||
37 | int pwr_state; | ||
38 | void (*set_gpu_state)(struct pci_dev *pdev, enum vga_switcheroo_state); | ||
39 | bool (*can_switch)(struct pci_dev *pdev); | ||
40 | int id; | ||
41 | bool active; | ||
42 | }; | ||
43 | |||
44 | static DEFINE_MUTEX(vgasr_mutex); | ||
45 | |||
46 | struct vgasr_priv { | ||
47 | |||
48 | bool active; | ||
49 | bool delayed_switch_active; | ||
50 | enum vga_switcheroo_client_id delayed_client_id; | ||
51 | |||
52 | struct dentry *debugfs_root; | ||
53 | struct dentry *switch_file; | ||
54 | |||
55 | int registered_clients; | ||
56 | struct vga_switcheroo_client clients[VGA_SWITCHEROO_MAX_CLIENTS]; | ||
57 | |||
58 | struct vga_switcheroo_handler *handler; | ||
59 | }; | ||
60 | |||
61 | static int vga_switcheroo_debugfs_init(struct vgasr_priv *priv); | ||
62 | static void vga_switcheroo_debugfs_fini(struct vgasr_priv *priv); | ||
63 | |||
64 | /* only one switcheroo per system */ | ||
65 | static struct vgasr_priv vgasr_priv; | ||
66 | |||
67 | int vga_switcheroo_register_handler(struct vga_switcheroo_handler *handler) | ||
68 | { | ||
69 | mutex_lock(&vgasr_mutex); | ||
70 | if (vgasr_priv.handler) { | ||
71 | mutex_unlock(&vgasr_mutex); | ||
72 | return -EINVAL; | ||
73 | } | ||
74 | |||
75 | vgasr_priv.handler = handler; | ||
76 | mutex_unlock(&vgasr_mutex); | ||
77 | return 0; | ||
78 | } | ||
79 | EXPORT_SYMBOL(vga_switcheroo_register_handler); | ||
80 | |||
81 | void vga_switcheroo_unregister_handler(void) | ||
82 | { | ||
83 | mutex_lock(&vgasr_mutex); | ||
84 | vgasr_priv.handler = NULL; | ||
85 | mutex_unlock(&vgasr_mutex); | ||
86 | } | ||
87 | EXPORT_SYMBOL(vga_switcheroo_unregister_handler); | ||
88 | |||
89 | static void vga_switcheroo_enable(void) | ||
90 | { | ||
91 | int i; | ||
92 | int ret; | ||
93 | /* call the handler to init */ | ||
94 | vgasr_priv.handler->init(); | ||
95 | |||
96 | for (i = 0; i < VGA_SWITCHEROO_MAX_CLIENTS; i++) { | ||
97 | ret = vgasr_priv.handler->get_client_id(vgasr_priv.clients[i].pdev); | ||
98 | if (ret < 0) | ||
99 | return; | ||
100 | |||
101 | vgasr_priv.clients[i].id = ret; | ||
102 | } | ||
103 | vga_switcheroo_debugfs_init(&vgasr_priv); | ||
104 | vgasr_priv.active = true; | ||
105 | } | ||
106 | |||
107 | int vga_switcheroo_register_client(struct pci_dev *pdev, | ||
108 | void (*set_gpu_state)(struct pci_dev *pdev, enum vga_switcheroo_state), | ||
109 | bool (*can_switch)(struct pci_dev *pdev)) | ||
110 | { | ||
111 | int index; | ||
112 | |||
113 | mutex_lock(&vgasr_mutex); | ||
114 | /* don't do IGD vs DIS here */ | ||
115 | if (vgasr_priv.registered_clients & 1) | ||
116 | index = 1; | ||
117 | else | ||
118 | index = 0; | ||
119 | |||
120 | vgasr_priv.clients[index].pwr_state = VGA_SWITCHEROO_ON; | ||
121 | vgasr_priv.clients[index].pdev = pdev; | ||
122 | vgasr_priv.clients[index].set_gpu_state = set_gpu_state; | ||
123 | vgasr_priv.clients[index].can_switch = can_switch; | ||
124 | vgasr_priv.clients[index].id = -1; | ||
125 | if (pdev->resource[PCI_ROM_RESOURCE].flags & IORESOURCE_ROM_SHADOW) | ||
126 | vgasr_priv.clients[index].active = true; | ||
127 | |||
128 | vgasr_priv.registered_clients |= (1 << index); | ||
129 | |||
130 | /* if we get two clients + handler */ | ||
131 | if (vgasr_priv.registered_clients == 0x3 && vgasr_priv.handler) { | ||
132 | printk(KERN_INFO "vga_switcheroo: enabled\n"); | ||
133 | vga_switcheroo_enable(); | ||
134 | } | ||
135 | mutex_unlock(&vgasr_mutex); | ||
136 | return 0; | ||
137 | } | ||
138 | EXPORT_SYMBOL(vga_switcheroo_register_client); | ||
139 | |||
140 | void vga_switcheroo_unregister_client(struct pci_dev *pdev) | ||
141 | { | ||
142 | int i; | ||
143 | |||
144 | mutex_lock(&vgasr_mutex); | ||
145 | for (i = 0; i < VGA_SWITCHEROO_MAX_CLIENTS; i++) { | ||
146 | if (vgasr_priv.clients[i].pdev == pdev) { | ||
147 | vgasr_priv.registered_clients &= ~(1 << i); | ||
148 | break; | ||
149 | } | ||
150 | } | ||
151 | |||
152 | printk(KERN_INFO "vga_switcheroo: disabled\n"); | ||
153 | vga_switcheroo_debugfs_fini(&vgasr_priv); | ||
154 | vgasr_priv.active = false; | ||
155 | mutex_unlock(&vgasr_mutex); | ||
156 | } | ||
157 | EXPORT_SYMBOL(vga_switcheroo_unregister_client); | ||
158 | |||
159 | void vga_switcheroo_client_fb_set(struct pci_dev *pdev, | ||
160 | struct fb_info *info) | ||
161 | { | ||
162 | int i; | ||
163 | |||
164 | mutex_lock(&vgasr_mutex); | ||
165 | for (i = 0; i < VGA_SWITCHEROO_MAX_CLIENTS; i++) { | ||
166 | if (vgasr_priv.clients[i].pdev == pdev) { | ||
167 | vgasr_priv.clients[i].fb_info = info; | ||
168 | break; | ||
169 | } | ||
170 | } | ||
171 | mutex_unlock(&vgasr_mutex); | ||
172 | } | ||
173 | EXPORT_SYMBOL(vga_switcheroo_client_fb_set); | ||
174 | |||
175 | static int vga_switcheroo_show(struct seq_file *m, void *v) | ||
176 | { | ||
177 | int i; | ||
178 | mutex_lock(&vgasr_mutex); | ||
179 | for (i = 0; i < VGA_SWITCHEROO_MAX_CLIENTS; i++) { | ||
180 | seq_printf(m, "%d:%c:%s:%s\n", i, | ||
181 | vgasr_priv.clients[i].active ? '+' : ' ', | ||
182 | vgasr_priv.clients[i].pwr_state ? "Pwr" : "Off", | ||
183 | pci_name(vgasr_priv.clients[i].pdev)); | ||
184 | } | ||
185 | mutex_unlock(&vgasr_mutex); | ||
186 | return 0; | ||
187 | } | ||
188 | |||
189 | static int vga_switcheroo_debugfs_open(struct inode *inode, struct file *file) | ||
190 | { | ||
191 | return single_open(file, vga_switcheroo_show, NULL); | ||
192 | } | ||
193 | |||
194 | static int vga_switchon(struct vga_switcheroo_client *client) | ||
195 | { | ||
196 | int ret; | ||
197 | |||
198 | ret = vgasr_priv.handler->power_state(client->id, VGA_SWITCHEROO_ON); | ||
199 | /* call the driver callback to turn on device */ | ||
200 | client->set_gpu_state(client->pdev, VGA_SWITCHEROO_ON); | ||
201 | client->pwr_state = VGA_SWITCHEROO_ON; | ||
202 | return 0; | ||
203 | } | ||
204 | |||
205 | static int vga_switchoff(struct vga_switcheroo_client *client) | ||
206 | { | ||
207 | /* call the driver callback to turn off device */ | ||
208 | client->set_gpu_state(client->pdev, VGA_SWITCHEROO_OFF); | ||
209 | vgasr_priv.handler->power_state(client->id, VGA_SWITCHEROO_OFF); | ||
210 | client->pwr_state = VGA_SWITCHEROO_OFF; | ||
211 | return 0; | ||
212 | } | ||
213 | |||
214 | static int vga_switchto(struct vga_switcheroo_client *new_client) | ||
215 | { | ||
216 | int ret; | ||
217 | int i; | ||
218 | struct vga_switcheroo_client *active = NULL; | ||
219 | |||
220 | if (new_client->active == true) | ||
221 | return 0; | ||
222 | |||
223 | for (i = 0; i < VGA_SWITCHEROO_MAX_CLIENTS; i++) { | ||
224 | if (vgasr_priv.clients[i].active == true) { | ||
225 | active = &vgasr_priv.clients[i]; | ||
226 | break; | ||
227 | } | ||
228 | } | ||
229 | if (!active) | ||
230 | return 0; | ||
231 | |||
232 | /* power up the first device */ | ||
233 | ret = pci_enable_device(new_client->pdev); | ||
234 | if (ret) | ||
235 | return ret; | ||
236 | |||
237 | if (new_client->pwr_state == VGA_SWITCHEROO_OFF) | ||
238 | vga_switchon(new_client); | ||
239 | |||
240 | /* swap shadow resource to denote boot VGA device has changed so X starts on new device */ | ||
241 | active->active = false; | ||
242 | |||
243 | active->pdev->resource[PCI_ROM_RESOURCE].flags &= ~IORESOURCE_ROM_SHADOW; | ||
244 | new_client->pdev->resource[PCI_ROM_RESOURCE].flags |= IORESOURCE_ROM_SHADOW; | ||
245 | |||
246 | if (new_client->fb_info) { | ||
247 | struct fb_event event; | ||
248 | event.info = new_client->fb_info; | ||
249 | fb_notifier_call_chain(FB_EVENT_REMAP_ALL_CONSOLE, &event); | ||
250 | } | ||
251 | |||
252 | ret = vgasr_priv.handler->switchto(new_client->id); | ||
253 | if (ret) | ||
254 | return ret; | ||
255 | |||
256 | if (active->pwr_state == VGA_SWITCHEROO_ON) | ||
257 | vga_switchoff(active); | ||
258 | |||
259 | new_client->active = true; | ||
260 | return 0; | ||
261 | } | ||
262 | |||
263 | static ssize_t | ||
264 | vga_switcheroo_debugfs_write(struct file *filp, const char __user *ubuf, | ||
265 | size_t cnt, loff_t *ppos) | ||
266 | { | ||
267 | char usercmd[64]; | ||
268 | const char *pdev_name; | ||
269 | int i, ret; | ||
270 | bool delay = false, can_switch; | ||
271 | int client_id = -1; | ||
272 | struct vga_switcheroo_client *client = NULL; | ||
273 | |||
274 | if (cnt > 63) | ||
275 | cnt = 63; | ||
276 | |||
277 | if (copy_from_user(usercmd, ubuf, cnt)) | ||
278 | return -EFAULT; | ||
279 | |||
280 | mutex_lock(&vgasr_mutex); | ||
281 | |||
282 | if (!vgasr_priv.active) | ||
283 | return -EINVAL; | ||
284 | |||
285 | /* pwr off the device not in use */ | ||
286 | if (strncmp(usercmd, "OFF", 3) == 0) { | ||
287 | for (i = 0; i < VGA_SWITCHEROO_MAX_CLIENTS; i++) { | ||
288 | if (vgasr_priv.clients[i].active) | ||
289 | continue; | ||
290 | if (vgasr_priv.clients[i].pwr_state == VGA_SWITCHEROO_ON) | ||
291 | vga_switchoff(&vgasr_priv.clients[i]); | ||
292 | } | ||
293 | goto out; | ||
294 | } | ||
295 | /* pwr on the device not in use */ | ||
296 | if (strncmp(usercmd, "ON", 2) == 0) { | ||
297 | for (i = 0; i < VGA_SWITCHEROO_MAX_CLIENTS; i++) { | ||
298 | if (vgasr_priv.clients[i].active) | ||
299 | continue; | ||
300 | if (vgasr_priv.clients[i].pwr_state == VGA_SWITCHEROO_OFF) | ||
301 | vga_switchon(&vgasr_priv.clients[i]); | ||
302 | } | ||
303 | goto out; | ||
304 | } | ||
305 | |||
306 | /* request a delayed switch - test can we switch now */ | ||
307 | if (strncmp(usercmd, "DIGD", 4) == 0) { | ||
308 | client_id = VGA_SWITCHEROO_IGD; | ||
309 | delay = true; | ||
310 | } | ||
311 | |||
312 | if (strncmp(usercmd, "DDIS", 4) == 0) { | ||
313 | client_id = VGA_SWITCHEROO_DIS; | ||
314 | delay = true; | ||
315 | } | ||
316 | |||
317 | if (strncmp(usercmd, "IGD", 3) == 0) | ||
318 | client_id = VGA_SWITCHEROO_IGD; | ||
319 | |||
320 | if (strncmp(usercmd, "DIS", 3) == 0) | ||
321 | client_id = VGA_SWITCHEROO_DIS; | ||
322 | |||
323 | if (client_id == -1) | ||
324 | goto out; | ||
325 | |||
326 | for (i = 0; i < VGA_SWITCHEROO_MAX_CLIENTS; i++) { | ||
327 | if (vgasr_priv.clients[i].id == client_id) { | ||
328 | client = &vgasr_priv.clients[i]; | ||
329 | break; | ||
330 | } | ||
331 | } | ||
332 | |||
333 | vgasr_priv.delayed_switch_active = false; | ||
334 | /* okay we want a switch - test if devices are willing to switch */ | ||
335 | can_switch = true; | ||
336 | for (i = 0; i < VGA_SWITCHEROO_MAX_CLIENTS; i++) { | ||
337 | can_switch = vgasr_priv.clients[i].can_switch(vgasr_priv.clients[i].pdev); | ||
338 | if (can_switch == false) { | ||
339 | printk(KERN_ERR "vga_switcheroo: client %d refused switch\n", i); | ||
340 | break; | ||
341 | } | ||
342 | } | ||
343 | |||
344 | if (can_switch == false && delay == false) | ||
345 | goto out; | ||
346 | |||
347 | if (can_switch == true) { | ||
348 | pdev_name = pci_name(client->pdev); | ||
349 | ret = vga_switchto(client); | ||
350 | if (ret) | ||
351 | printk(KERN_ERR "vga_switcheroo: switching failed %d\n", ret); | ||
352 | } else { | ||
353 | printk(KERN_INFO "vga_switcheroo: setting delayed switch to client %d\n", client->id); | ||
354 | vgasr_priv.delayed_switch_active = true; | ||
355 | vgasr_priv.delayed_client_id = client_id; | ||
356 | |||
357 | /* we should at least power up the card to | ||
358 | make the switch faster */ | ||
359 | if (client->pwr_state == VGA_SWITCHEROO_OFF) | ||
360 | vga_switchon(client); | ||
361 | } | ||
362 | |||
363 | out: | ||
364 | mutex_unlock(&vgasr_mutex); | ||
365 | return cnt; | ||
366 | } | ||
367 | |||
368 | static const struct file_operations vga_switcheroo_debugfs_fops = { | ||
369 | .owner = THIS_MODULE, | ||
370 | .open = vga_switcheroo_debugfs_open, | ||
371 | .write = vga_switcheroo_debugfs_write, | ||
372 | .read = seq_read, | ||
373 | .llseek = seq_lseek, | ||
374 | .release = single_release, | ||
375 | }; | ||
376 | |||
377 | static void vga_switcheroo_debugfs_fini(struct vgasr_priv *priv) | ||
378 | { | ||
379 | if (priv->switch_file) { | ||
380 | debugfs_remove(priv->switch_file); | ||
381 | priv->switch_file = NULL; | ||
382 | } | ||
383 | if (priv->debugfs_root) { | ||
384 | debugfs_remove(priv->debugfs_root); | ||
385 | priv->debugfs_root = NULL; | ||
386 | } | ||
387 | } | ||
388 | |||
389 | static int vga_switcheroo_debugfs_init(struct vgasr_priv *priv) | ||
390 | { | ||
391 | /* already initialised */ | ||
392 | if (priv->debugfs_root) | ||
393 | return 0; | ||
394 | priv->debugfs_root = debugfs_create_dir("vgaswitcheroo", NULL); | ||
395 | |||
396 | if (!priv->debugfs_root) { | ||
397 | printk(KERN_ERR "vga_switcheroo: Cannot create /sys/kernel/debug/vgaswitcheroo\n"); | ||
398 | goto fail; | ||
399 | } | ||
400 | |||
401 | priv->switch_file = debugfs_create_file("switch", 0644, | ||
402 | priv->debugfs_root, NULL, &vga_switcheroo_debugfs_fops); | ||
403 | if (!priv->switch_file) { | ||
404 | printk(KERN_ERR "vga_switcheroo: cannot create /sys/kernel/debug/vgaswitcheroo/switch\n"); | ||
405 | goto fail; | ||
406 | } | ||
407 | return 0; | ||
408 | fail: | ||
409 | vga_switcheroo_debugfs_fini(priv); | ||
410 | return -1; | ||
411 | } | ||
412 | |||
413 | int vga_switcheroo_process_delayed_switch(void) | ||
414 | { | ||
415 | struct vga_switcheroo_client *client = NULL; | ||
416 | const char *pdev_name; | ||
417 | bool can_switch = true; | ||
418 | int i; | ||
419 | int ret; | ||
420 | int err = -EINVAL; | ||
421 | |||
422 | mutex_lock(&vgasr_mutex); | ||
423 | if (!vgasr_priv.delayed_switch_active) | ||
424 | goto err; | ||
425 | |||
426 | printk(KERN_INFO "vga_switcheroo: processing delayed switch to %d\n", vgasr_priv.delayed_client_id); | ||
427 | |||
428 | for (i = 0; i < VGA_SWITCHEROO_MAX_CLIENTS; i++) { | ||
429 | if (vgasr_priv.clients[i].id == vgasr_priv.delayed_client_id) | ||
430 | client = &vgasr_priv.clients[i]; | ||
431 | can_switch = vgasr_priv.clients[i].can_switch(vgasr_priv.clients[i].pdev); | ||
432 | if (can_switch == false) { | ||
433 | printk(KERN_ERR "vga_switcheroo: client %d refused switch\n", i); | ||
434 | break; | ||
435 | } | ||
436 | } | ||
437 | |||
438 | if (can_switch == false || client == NULL) | ||
439 | goto err; | ||
440 | |||
441 | pdev_name = pci_name(client->pdev); | ||
442 | ret = vga_switchto(client); | ||
443 | if (ret) | ||
444 | printk(KERN_ERR "vga_switcheroo: delayed switching failed %d\n", ret); | ||
445 | |||
446 | vgasr_priv.delayed_switch_active = false; | ||
447 | err = 0; | ||
448 | err: | ||
449 | mutex_unlock(&vgasr_mutex); | ||
450 | return err; | ||
451 | } | ||
452 | EXPORT_SYMBOL(vga_switcheroo_process_delayed_switch); | ||
453 | |||