diff options
author | Andreas Heider <andreas@meetr.de> | 2012-08-17 12:17:04 -0400 |
---|---|---|
committer | Matthew Garrett <mjg@redhat.com> | 2012-08-17 17:34:40 -0400 |
commit | 76b487dd5187a4d7cc6eccd452f65467a8c7768b (patch) | |
tree | 1c4cc994322ae276fd50a1854ad53d6ba555215d /drivers/platform | |
parent | f94f0f103c440f1dbe5d89a28c92fd1a9bdf7012 (diff) |
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>
Diffstat (limited to 'drivers/platform')
-rw-r--r-- | drivers/platform/x86/apple-gmux.c | 224 |
1 files changed, 224 insertions, 0 deletions
diff --git a/drivers/platform/x86/apple-gmux.c b/drivers/platform/x86/apple-gmux.c index 85e1bfbd1121..dfb1a92ce949 100644 --- a/drivers/platform/x86/apple-gmux.c +++ b/drivers/platform/x86/apple-gmux.c | |||
@@ -2,6 +2,7 @@ | |||
2 | * Gmux driver for Apple laptops | 2 | * Gmux driver for Apple laptops |
3 | * | 3 | * |
4 | * Copyright (C) Canonical Ltd. <seth.forshee@canonical.com> | 4 | * Copyright (C) Canonical Ltd. <seth.forshee@canonical.com> |
5 | * Copyright (C) 2010-2012 Andreas Heider <andreas@meetr.de> | ||
5 | * | 6 | * |
6 | * This program is free software; you can redistribute it and/or modify | 7 | * This program is free software; you can redistribute it and/or modify |
7 | * it under the terms of the GNU General Public License version 2 as | 8 | * it under the terms of the GNU General Public License version 2 as |
@@ -19,6 +20,8 @@ | |||
19 | #include <linux/apple_bl.h> | 20 | #include <linux/apple_bl.h> |
20 | #include <linux/slab.h> | 21 | #include <linux/slab.h> |
21 | #include <linux/delay.h> | 22 | #include <linux/delay.h> |
23 | #include <linux/pci.h> | ||
24 | #include <linux/vga_switcheroo.h> | ||
22 | #include <acpi/video.h> | 25 | #include <acpi/video.h> |
23 | #include <asm/io.h> | 26 | #include <asm/io.h> |
24 | 27 | ||
@@ -29,8 +32,17 @@ struct apple_gmux_data { | |||
29 | struct mutex index_lock; | 32 | struct mutex index_lock; |
30 | 33 | ||
31 | struct backlight_device *bdev; | 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 | * gmux port offsets. Many of these are not yet used, but may be in the | 47 | * gmux port offsets. Many of these are not yet used, but may be in the |
36 | * future, and it's useful to have them documented here anyhow. | 48 | * future, and it's useful to have them documented here anyhow. |
@@ -257,6 +269,146 @@ static const struct backlight_ops gmux_bl_ops = { | |||
257 | .update_status = gmux_update_status, | 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 | static int __devinit gmux_probe(struct pnp_dev *pnp, | 412 | static int __devinit gmux_probe(struct pnp_dev *pnp, |
261 | const struct pnp_device_id *id) | 413 | const struct pnp_device_id *id) |
262 | { | 414 | { |
@@ -266,6 +418,11 @@ static int __devinit gmux_probe(struct pnp_dev *pnp, | |||
266 | struct backlight_device *bdev; | 418 | struct backlight_device *bdev; |
267 | u8 ver_major, ver_minor, ver_release; | 419 | u8 ver_major, ver_minor, ver_release; |
268 | int ret = -ENXIO; | 420 | int ret = -ENXIO; |
421 | acpi_status status; | ||
422 | unsigned long long gpe; | ||
423 | |||
424 | if (apple_gmux_data) | ||
425 | return -EBUSY; | ||
269 | 426 | ||
270 | gmux_data = kzalloc(sizeof(*gmux_data), GFP_KERNEL); | 427 | gmux_data = kzalloc(sizeof(*gmux_data), GFP_KERNEL); |
271 | if (!gmux_data) | 428 | if (!gmux_data) |
@@ -353,8 +510,62 @@ static int __devinit gmux_probe(struct pnp_dev *pnp, | |||
353 | #endif | 510 | #endif |
354 | apple_bl_unregister(); | 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 | return 0; | 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 | err_release: | 569 | err_release: |
359 | release_region(gmux_data->iostart, gmux_data->iolen); | 570 | release_region(gmux_data->iostart, gmux_data->iolen); |
360 | err_free: | 571 | err_free: |
@@ -366,8 +577,19 @@ static void __devexit gmux_remove(struct pnp_dev *pnp) | |||
366 | { | 577 | { |
367 | struct apple_gmux_data *gmux_data = pnp_get_drvdata(pnp); | 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 | backlight_device_unregister(gmux_data->bdev); | 589 | backlight_device_unregister(gmux_data->bdev); |
590 | |||
370 | release_region(gmux_data->iostart, gmux_data->iolen); | 591 | release_region(gmux_data->iostart, gmux_data->iolen); |
592 | apple_gmux_data = NULL; | ||
371 | kfree(gmux_data); | 593 | kfree(gmux_data); |
372 | 594 | ||
373 | acpi_video_dmi_demote_vendor(); | 595 | acpi_video_dmi_demote_vendor(); |
@@ -387,6 +609,8 @@ static struct pnp_driver gmux_pnp_driver = { | |||
387 | .probe = gmux_probe, | 609 | .probe = gmux_probe, |
388 | .remove = __devexit_p(gmux_remove), | 610 | .remove = __devexit_p(gmux_remove), |
389 | .id_table = gmux_device_ids, | 611 | .id_table = gmux_device_ids, |
612 | .suspend = gmux_suspend, | ||
613 | .resume = gmux_resume | ||
390 | }; | 614 | }; |
391 | 615 | ||
392 | static int __init apple_gmux_init(void) | 616 | static int __init apple_gmux_init(void) |