Commit 76b487dd5187a4d7cc6eccd452f65467a8c7768b
Committed by
Matthew Garrett
1 parent
f94f0f103c
Exists in
smarc-l5.0.0_1.0.0-ga
and in
5 other branches
apple-gmux: Add display mux support
Add support for the gmux display muxing functionality and register a mux handler with vga_switcheroo. Signed-off-by: Andreas Heider <andreas@meetr.de> Signed-off-by: Seth Forshee <seth.forshee@canonical.com> Signed-off-by: Matthew Garrett <mjg@redhat.com>
Showing 1 changed file with 224 additions and 0 deletions Side-by-side Diff
drivers/platform/x86/apple-gmux.c
... | ... | @@ -2,6 +2,7 @@ |
2 | 2 | * Gmux driver for Apple laptops |
3 | 3 | * |
4 | 4 | * Copyright (C) Canonical Ltd. <seth.forshee@canonical.com> |
5 | + * Copyright (C) 2010-2012 Andreas Heider <andreas@meetr.de> | |
5 | 6 | * |
6 | 7 | * This program is free software; you can redistribute it and/or modify |
7 | 8 | * it under the terms of the GNU General Public License version 2 as |
... | ... | @@ -19,6 +20,8 @@ |
19 | 20 | #include <linux/apple_bl.h> |
20 | 21 | #include <linux/slab.h> |
21 | 22 | #include <linux/delay.h> |
23 | +#include <linux/pci.h> | |
24 | +#include <linux/vga_switcheroo.h> | |
22 | 25 | #include <acpi/video.h> |
23 | 26 | #include <asm/io.h> |
24 | 27 | |
25 | 28 | |
... | ... | @@ -29,8 +32,17 @@ |
29 | 32 | struct mutex index_lock; |
30 | 33 | |
31 | 34 | struct backlight_device *bdev; |
35 | + | |
36 | + /* switcheroo data */ | |
37 | + acpi_handle dhandle; | |
38 | + int gpe; | |
39 | + enum vga_switcheroo_client_id resume_client_id; | |
40 | + enum vga_switcheroo_state power_state; | |
41 | + struct completion powerchange_done; | |
32 | 42 | }; |
33 | 43 | |
44 | +static struct apple_gmux_data *apple_gmux_data; | |
45 | + | |
34 | 46 | /* |
35 | 47 | * gmux port offsets. Many of these are not yet used, but may be in the |
36 | 48 | * future, and it's useful to have them documented here anyhow. |
... | ... | @@ -257,6 +269,146 @@ |
257 | 269 | .update_status = gmux_update_status, |
258 | 270 | }; |
259 | 271 | |
272 | +static int gmux_switchto(enum vga_switcheroo_client_id id) | |
273 | +{ | |
274 | + if (id == VGA_SWITCHEROO_IGD) { | |
275 | + gmux_write8(apple_gmux_data, GMUX_PORT_SWITCH_DDC, 1); | |
276 | + gmux_write8(apple_gmux_data, GMUX_PORT_SWITCH_DISPLAY, 2); | |
277 | + gmux_write8(apple_gmux_data, GMUX_PORT_SWITCH_EXTERNAL, 2); | |
278 | + } else { | |
279 | + gmux_write8(apple_gmux_data, GMUX_PORT_SWITCH_DDC, 2); | |
280 | + gmux_write8(apple_gmux_data, GMUX_PORT_SWITCH_DISPLAY, 3); | |
281 | + gmux_write8(apple_gmux_data, GMUX_PORT_SWITCH_EXTERNAL, 3); | |
282 | + } | |
283 | + | |
284 | + return 0; | |
285 | +} | |
286 | + | |
287 | +static int gmux_set_discrete_state(struct apple_gmux_data *gmux_data, | |
288 | + enum vga_switcheroo_state state) | |
289 | +{ | |
290 | + INIT_COMPLETION(gmux_data->powerchange_done); | |
291 | + | |
292 | + if (state == VGA_SWITCHEROO_ON) { | |
293 | + gmux_write8(gmux_data, GMUX_PORT_DISCRETE_POWER, 1); | |
294 | + gmux_write8(gmux_data, GMUX_PORT_DISCRETE_POWER, 3); | |
295 | + pr_debug("Discrete card powered up\n"); | |
296 | + } else { | |
297 | + gmux_write8(gmux_data, GMUX_PORT_DISCRETE_POWER, 1); | |
298 | + gmux_write8(gmux_data, GMUX_PORT_DISCRETE_POWER, 0); | |
299 | + pr_debug("Discrete card powered down\n"); | |
300 | + } | |
301 | + | |
302 | + gmux_data->power_state = state; | |
303 | + | |
304 | + if (gmux_data->gpe >= 0 && | |
305 | + !wait_for_completion_interruptible_timeout(&gmux_data->powerchange_done, | |
306 | + msecs_to_jiffies(200))) | |
307 | + pr_warn("Timeout waiting for gmux switch to complete\n"); | |
308 | + | |
309 | + return 0; | |
310 | +} | |
311 | + | |
312 | +static int gmux_set_power_state(enum vga_switcheroo_client_id id, | |
313 | + enum vga_switcheroo_state state) | |
314 | +{ | |
315 | + if (id == VGA_SWITCHEROO_IGD) | |
316 | + return 0; | |
317 | + | |
318 | + return gmux_set_discrete_state(apple_gmux_data, state); | |
319 | +} | |
320 | + | |
321 | +static int gmux_get_client_id(struct pci_dev *pdev) | |
322 | +{ | |
323 | + /* | |
324 | + * Early Macbook Pros with switchable graphics use nvidia | |
325 | + * integrated graphics. Hardcode that the 9400M is integrated. | |
326 | + */ | |
327 | + if (pdev->vendor == PCI_VENDOR_ID_INTEL) | |
328 | + return VGA_SWITCHEROO_IGD; | |
329 | + else if (pdev->vendor == PCI_VENDOR_ID_NVIDIA && | |
330 | + pdev->device == 0x0863) | |
331 | + return VGA_SWITCHEROO_IGD; | |
332 | + else | |
333 | + return VGA_SWITCHEROO_DIS; | |
334 | +} | |
335 | + | |
336 | +static enum vga_switcheroo_client_id | |
337 | +gmux_active_client(struct apple_gmux_data *gmux_data) | |
338 | +{ | |
339 | + if (gmux_read8(gmux_data, GMUX_PORT_SWITCH_DISPLAY) == 2) | |
340 | + return VGA_SWITCHEROO_IGD; | |
341 | + | |
342 | + return VGA_SWITCHEROO_DIS; | |
343 | +} | |
344 | + | |
345 | +static struct vga_switcheroo_handler gmux_handler = { | |
346 | + .switchto = gmux_switchto, | |
347 | + .power_state = gmux_set_power_state, | |
348 | + .get_client_id = gmux_get_client_id, | |
349 | +}; | |
350 | + | |
351 | +static inline void gmux_disable_interrupts(struct apple_gmux_data *gmux_data) | |
352 | +{ | |
353 | + gmux_write8(gmux_data, GMUX_PORT_INTERRUPT_ENABLE, | |
354 | + GMUX_INTERRUPT_DISABLE); | |
355 | +} | |
356 | + | |
357 | +static inline void gmux_enable_interrupts(struct apple_gmux_data *gmux_data) | |
358 | +{ | |
359 | + gmux_write8(gmux_data, GMUX_PORT_INTERRUPT_ENABLE, | |
360 | + GMUX_INTERRUPT_ENABLE); | |
361 | +} | |
362 | + | |
363 | +static inline u8 gmux_interrupt_get_status(struct apple_gmux_data *gmux_data) | |
364 | +{ | |
365 | + return gmux_read8(gmux_data, GMUX_PORT_INTERRUPT_STATUS); | |
366 | +} | |
367 | + | |
368 | +static void gmux_clear_interrupts(struct apple_gmux_data *gmux_data) | |
369 | +{ | |
370 | + u8 status; | |
371 | + | |
372 | + /* to clear interrupts write back current status */ | |
373 | + status = gmux_interrupt_get_status(gmux_data); | |
374 | + gmux_write8(gmux_data, GMUX_PORT_INTERRUPT_STATUS, status); | |
375 | +} | |
376 | + | |
377 | +static void gmux_notify_handler(acpi_handle device, u32 value, void *context) | |
378 | +{ | |
379 | + u8 status; | |
380 | + struct pnp_dev *pnp = (struct pnp_dev *)context; | |
381 | + struct apple_gmux_data *gmux_data = pnp_get_drvdata(pnp); | |
382 | + | |
383 | + status = gmux_interrupt_get_status(gmux_data); | |
384 | + gmux_disable_interrupts(gmux_data); | |
385 | + pr_debug("Notify handler called: status %d\n", status); | |
386 | + | |
387 | + gmux_clear_interrupts(gmux_data); | |
388 | + gmux_enable_interrupts(gmux_data); | |
389 | + | |
390 | + if (status & GMUX_INTERRUPT_STATUS_POWER) | |
391 | + complete(&gmux_data->powerchange_done); | |
392 | +} | |
393 | + | |
394 | +static int gmux_suspend(struct pnp_dev *pnp, pm_message_t state) | |
395 | +{ | |
396 | + struct apple_gmux_data *gmux_data = pnp_get_drvdata(pnp); | |
397 | + gmux_data->resume_client_id = gmux_active_client(gmux_data); | |
398 | + gmux_disable_interrupts(gmux_data); | |
399 | + return 0; | |
400 | +} | |
401 | + | |
402 | +static int gmux_resume(struct pnp_dev *pnp) | |
403 | +{ | |
404 | + struct apple_gmux_data *gmux_data = pnp_get_drvdata(pnp); | |
405 | + gmux_enable_interrupts(gmux_data); | |
406 | + gmux_switchto(gmux_data->resume_client_id); | |
407 | + if (gmux_data->power_state == VGA_SWITCHEROO_OFF) | |
408 | + gmux_set_discrete_state(gmux_data, gmux_data->power_state); | |
409 | + return 0; | |
410 | +} | |
411 | + | |
260 | 412 | static int __devinit gmux_probe(struct pnp_dev *pnp, |
261 | 413 | const struct pnp_device_id *id) |
262 | 414 | { |
263 | 415 | |
... | ... | @@ -266,7 +418,12 @@ |
266 | 418 | struct backlight_device *bdev; |
267 | 419 | u8 ver_major, ver_minor, ver_release; |
268 | 420 | int ret = -ENXIO; |
421 | + acpi_status status; | |
422 | + unsigned long long gpe; | |
269 | 423 | |
424 | + if (apple_gmux_data) | |
425 | + return -EBUSY; | |
426 | + | |
270 | 427 | gmux_data = kzalloc(sizeof(*gmux_data), GFP_KERNEL); |
271 | 428 | if (!gmux_data) |
272 | 429 | return -ENOMEM; |
273 | 430 | |
... | ... | @@ -353,8 +510,62 @@ |
353 | 510 | #endif |
354 | 511 | apple_bl_unregister(); |
355 | 512 | |
513 | + gmux_data->power_state = VGA_SWITCHEROO_ON; | |
514 | + | |
515 | + gmux_data->dhandle = DEVICE_ACPI_HANDLE(&pnp->dev); | |
516 | + if (!gmux_data->dhandle) { | |
517 | + pr_err("Cannot find acpi handle for pnp device %s\n", | |
518 | + dev_name(&pnp->dev)); | |
519 | + ret = -ENODEV; | |
520 | + goto err_notify; | |
521 | + } | |
522 | + | |
523 | + status = acpi_evaluate_integer(gmux_data->dhandle, "GMGP", NULL, &gpe); | |
524 | + if (ACPI_SUCCESS(status)) { | |
525 | + gmux_data->gpe = (int)gpe; | |
526 | + | |
527 | + status = acpi_install_notify_handler(gmux_data->dhandle, | |
528 | + ACPI_DEVICE_NOTIFY, | |
529 | + &gmux_notify_handler, pnp); | |
530 | + if (ACPI_FAILURE(status)) { | |
531 | + pr_err("Install notify handler failed: %s\n", | |
532 | + acpi_format_exception(status)); | |
533 | + ret = -ENODEV; | |
534 | + goto err_notify; | |
535 | + } | |
536 | + | |
537 | + status = acpi_enable_gpe(NULL, gmux_data->gpe); | |
538 | + if (ACPI_FAILURE(status)) { | |
539 | + pr_err("Cannot enable gpe: %s\n", | |
540 | + acpi_format_exception(status)); | |
541 | + goto err_enable_gpe; | |
542 | + } | |
543 | + } else { | |
544 | + pr_warn("No GPE found for gmux\n"); | |
545 | + gmux_data->gpe = -1; | |
546 | + } | |
547 | + | |
548 | + if (vga_switcheroo_register_handler(&gmux_handler)) { | |
549 | + ret = -ENODEV; | |
550 | + goto err_register_handler; | |
551 | + } | |
552 | + | |
553 | + init_completion(&gmux_data->powerchange_done); | |
554 | + apple_gmux_data = gmux_data; | |
555 | + gmux_enable_interrupts(gmux_data); | |
556 | + | |
356 | 557 | return 0; |
357 | 558 | |
559 | +err_register_handler: | |
560 | + if (gmux_data->gpe >= 0) | |
561 | + acpi_disable_gpe(NULL, gmux_data->gpe); | |
562 | +err_enable_gpe: | |
563 | + if (gmux_data->gpe >= 0) | |
564 | + acpi_remove_notify_handler(gmux_data->dhandle, | |
565 | + ACPI_DEVICE_NOTIFY, | |
566 | + &gmux_notify_handler); | |
567 | +err_notify: | |
568 | + backlight_device_unregister(bdev); | |
358 | 569 | err_release: |
359 | 570 | release_region(gmux_data->iostart, gmux_data->iolen); |
360 | 571 | err_free: |
361 | 572 | |
362 | 573 | |
... | ... | @@ -366,8 +577,19 @@ |
366 | 577 | { |
367 | 578 | struct apple_gmux_data *gmux_data = pnp_get_drvdata(pnp); |
368 | 579 | |
580 | + vga_switcheroo_unregister_handler(); | |
581 | + gmux_disable_interrupts(gmux_data); | |
582 | + if (gmux_data->gpe >= 0) { | |
583 | + acpi_disable_gpe(NULL, gmux_data->gpe); | |
584 | + acpi_remove_notify_handler(gmux_data->dhandle, | |
585 | + ACPI_DEVICE_NOTIFY, | |
586 | + &gmux_notify_handler); | |
587 | + } | |
588 | + | |
369 | 589 | backlight_device_unregister(gmux_data->bdev); |
590 | + | |
370 | 591 | release_region(gmux_data->iostart, gmux_data->iolen); |
592 | + apple_gmux_data = NULL; | |
371 | 593 | kfree(gmux_data); |
372 | 594 | |
373 | 595 | acpi_video_dmi_demote_vendor(); |
... | ... | @@ -387,6 +609,8 @@ |
387 | 609 | .probe = gmux_probe, |
388 | 610 | .remove = __devexit_p(gmux_remove), |
389 | 611 | .id_table = gmux_device_ids, |
612 | + .suspend = gmux_suspend, | |
613 | + .resume = gmux_resume | |
390 | 614 | }; |
391 | 615 | |
392 | 616 | static int __init apple_gmux_init(void) |