Commit 76b487dd5187a4d7cc6eccd452f65467a8c7768b

Authored by Andreas Heider
Committed by Matthew Garrett
1 parent f94f0f103c

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)