Code:
From ccaed3441a380b2fe342000f9ff53e7570c221f6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ren=C3=A9=20K=C3=B6cher?= <shirk@bitspin.org>
Date: Sun, 22 May 2011 15:02:53 +0200
Subject: [PATCH] Add apple_gmux driver and provide support to use it with
vga_switcheroo.
This commit provides a modified version of the apple_gmux driver created
by Andreas Heider (http://andreas.meetr.de/gmux/apple_gmux/apple_gmux.c).
I Stripped out the backlight driver and ported the code to 2.6.39
as well as made a little modification to vga switcheroo to recognize it.
---
drivers/gpu/vga/Kconfig | 7 +
drivers/gpu/vga/Makefile | 2 +
drivers/gpu/vga/apple_gmux.c | 370 ++++++++++++++++++++++++++++++++++++++
drivers/gpu/vga/vga_switcheroo.c | 7 +
4 files changed, 386 insertions(+), 0 deletions(-)
create mode 100644 drivers/gpu/vga/apple_gmux.c
diff --git a/drivers/gpu/vga/Kconfig b/drivers/gpu/vga/Kconfig
index 96c83a9..46ee883 100644
--- a/drivers/gpu/vga/Kconfig
+++ b/drivers/gpu/vga/Kconfig
@@ -27,3 +27,10 @@ config VGA_SWITCHEROO
X isn't running and delayed switching until the next logoff. This
feature is called hybrid graphics, ATI PowerXpress, and Nvidia
HybridPower.
+
+config VGA_APPLE_GMUX
+ bool "Apple GMUX handler for Hybrid Graphics"
+ depends on VGA_SWITCHEROO
+ help
+ Include GMUX handler for switching hybrid graphics on newer MacBookPros
+
diff --git a/drivers/gpu/vga/Makefile b/drivers/gpu/vga/Makefile
index 14ca30b..921b70b 100644
--- a/drivers/gpu/vga/Makefile
+++ b/drivers/gpu/vga/Makefile
@@ -1,2 +1,4 @@
obj-$(CONFIG_VGA_ARB) += vgaarb.o
obj-$(CONFIG_VGA_SWITCHEROO) += vga_switcheroo.o
+obj-$(CONFIG_VGA_APPLE_GMUX) += apple_gmux.o
+
diff --git a/drivers/gpu/vga/apple_gmux.c b/drivers/gpu/vga/apple_gmux.c
new file mode 100644
index 0000000..b003d69
--- /dev/null
+++ b/drivers/gpu/vga/apple_gmux.c
@@ -0,0 +1,370 @@
+/*
+ * Copyright 2010 Andreas Heider
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * PRECISION INSIGHT AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/completion.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/io.h>
+#include <linux/pci.h>
+#include <linux/pnp.h>
+#include <linux/vga_switcheroo.h>
+#include <linux/backlight.h>
+#include <acpi/acpi.h>
+#include <acpi/actypes.h>
+
+struct gmux_priv {
+ acpi_handle gmux_handle;
+
+ struct backlight_device *gmux_backlight_device;
+ struct backlight_properties props;
+};
+
+static struct gmux_priv gmux_priv;
+
+DECLARE_COMPLETION(powerchange_done);
+
+#define PORT_VER_MAJOR 0x704
+#define PORT_VER_MINOR 0x705
+#define PORT_VER_RELEASE 0x706
+
+#define PORT_SWITCH_DISPLAY 0x710
+#define PORT_SWITCH_UNK 0x728
+#define PORT_SWITCH_DDC 0x740
+
+#define PORT_DISCRETE_POWER 0x750
+
+#define PORT_BACKLIGHT 0x774
+#define MAX_BRIGHTNESS 0x1af40
+#define BRIGHTNESS_STEPS 20
+
+#define PORT_INTERRUPT_ENABLE 0x714
+#define PORT_INTERRUPT_STATUS 0x716
+
+#define INTERRUPT_ENABLE 0xFF
+#define INTERRUPT_DISABLE 0x0
+
+#define INTERRUPT_STATUS_ACTIVE 0
+/* display switch complete */
+#define INTERRUPT_STATUS_DISPLAY 1
+/* dedicated card powered up/down */
+#define INTERRUPT_STATUS_POWER 4
+/* unknown, set after boot */
+#define INTERRUPT_STATUS_UNK 5
+
+static int gmux_switchto(enum vga_switcheroo_client_id id)
+{
+ if (id == VGA_SWITCHEROO_IGD) {
+ outb(1, PORT_SWITCH_UNK);
+ outb(2, PORT_SWITCH_DISPLAY);
+ outb(2, PORT_SWITCH_DDC);
+ } else {
+ outb(2, PORT_SWITCH_UNK);
+ outb(3, PORT_SWITCH_DISPLAY);
+ outb(3, PORT_SWITCH_DDC);
+ }
+
+ return 0;
+}
+
+//static int gmux_switchddc(enum vga_switcheroo_client_id id)
+//{
+// if (id == VGA_SWITCHEROO_IGD) {
+// outb(1, PORT_SWITCH_UNK);
+// outb(2, PORT_SWITCH_DDC);
+// } else {
+// outb(2, PORT_SWITCH_UNK);
+// outb(3, PORT_SWITCH_DDC);
+// }
+//
+// return 0;
+//}
+
+static int gmux_set_discrete_state(enum vga_switcheroo_state state)
+{
+ /* TODO: locking for completions needed? */
+ init_completion(&powerchange_done);
+
+ if (state == VGA_SWITCHEROO_ON) {
+ outb(1, PORT_DISCRETE_POWER);
+ outb(3, PORT_DISCRETE_POWER);
+ printk("gmux: discrete powered up\n");
+ } else {
+ outb(0, PORT_DISCRETE_POWER);
+ printk("gmux: discrete powered down\n");
+ }
+
+ /* TODO: add timeout */
+ printk("vga_switcheroo: before completion\n");
+ wait_for_completion(&powerchange_done);
+ printk("vga_switcheroo: after completion\n");
+
+ return 0;
+}
+
+
+static int gmux_set_power_state(enum vga_switcheroo_client_id id,
+ enum vga_switcheroo_state state)
+{
+ if (id == VGA_SWITCHEROO_IGD)
+ return 0;
+
+ return gmux_set_discrete_state(state);
+}
+
+static int gmux_init(void)
+{
+ return 0;
+}
+
+static int gmux_get_client_id(struct pci_dev *pdev)
+{
+ if (pdev->vendor == 0x8086) /* TODO: better detection */
+ return VGA_SWITCHEROO_IGD;
+ else
+ return VGA_SWITCHEROO_DIS;
+}
+
+static struct vga_switcheroo_handler gmux_handler = {
+ .switchto = gmux_switchto,
+// .switchddc = gmux_switchddc,
+ .power_state = gmux_set_power_state,
+ .init = gmux_init,
+ .get_client_id = gmux_get_client_id,
+};
+
+static void disable_interrupts(void)
+{
+ outb(INTERRUPT_DISABLE, PORT_INTERRUPT_ENABLE);
+}
+
+static void enable_interrupts(void)
+{
+ outb(INTERRUPT_ENABLE, PORT_INTERRUPT_ENABLE);
+}
+
+static int interrupt_get_status(void)
+{
+ return inb(PORT_INTERRUPT_STATUS);
+}
+
+static void interrupt_activate_status(void)
+{
+ int old_status;
+ int new_status;
+
+ /* to reactivate interrupts write back current status */
+ old_status = inb(PORT_INTERRUPT_STATUS);
+ outb(old_status, PORT_INTERRUPT_STATUS);
+ new_status = inb(PORT_INTERRUPT_STATUS);
+
+ /* status = 0 indicates active interrupts */
+ if (new_status)
+ printk("gmux: error: activate_status, old_status %d new_status %d\n", old_status, new_status);
+}
+
+static u32 gpe_handler(acpi_handle gpe_device, u32 gpe_number, void *context)
+{
+ int status;
+
+ status = interrupt_get_status();
+ disable_interrupts();
+ printk("gmux: gpe handler called: status %d\n", status);
+
+ interrupt_activate_status();
+ enable_interrupts();
+
+ if (status == INTERRUPT_STATUS_POWER)
+ complete(&powerchange_done);
+
+ return 0;
+}
+
+static bool gmux_detect(void)
+{
+ int ver_major, ver_minor, ver_release;
+
+ ver_major = inb(PORT_VER_MAJOR);
+ ver_minor = inb(PORT_VER_MINOR);
+ ver_release = inb(PORT_VER_RELEASE);
+
+ if (!(ver_major == 0x1 && ver_minor == 0x9)) {
+ printk(KERN_INFO "gmux: detected unknown gmux HW version: %x.%x.%x\n", ver_major, ver_minor, ver_release);
+ return false;
+ }
+ else {
+ printk(KERN_INFO "gmux: detected gmux HW version: %x.%x.%x\n", ver_major, ver_minor, ver_release);
+ return true;
+ }
+}
+
+//static int set_brightness(struct backlight_device *bd)
+//{
+// int brightness = bd->props.brightness;
+//
+// if (bd->props.state & BL_CORE_FBBLANK)
+// brightness = 0;
+// else if (bd->props.state & BL_CORE_SUSPENDED)
+// brightness = 0;
+//
+// outl((MAX_BRIGHTNESS / BRIGHTNESS_STEPS) * brightness, PORT_BACKLIGHT);
+//
+// printk("gmux: set brightness %d\n", brightness);
+// return 0;
+//}
+//
+//static int get_brightness(struct backlight_device *bd)
+//{
+// printk("gmux: get brightness\n");
+// return inl(PORT_BACKLIGHT) / (MAX_BRIGHTNESS / BRIGHTNESS_STEPS);
+//}
+//
+//const struct backlight_ops backlight_ops = {
+// .options = BL_CORE_SUSPENDRESUME,
+// .get_brightness = get_brightness,
+// .update_status = set_brightness
+//};
+//
+//int init_backlight(void)
+//{
+// memset(&gmux_priv.props, 0, sizeof(struct backlight_properties));
+// gmux_priv.props.max_brightness = BRIGHTNESS_STEPS;
+// gmux_priv.gmux_backlight_device = backlight_device_register("gmux", NULL,
+// NULL,
+// &backlight_ops,
+// &gmux_priv.props);
+//
+// if (IS_ERR(gmux_priv.gmux_backlight_device)) {
+// return PTR_ERR(gmux_priv.gmux_backlight_device);
+// }
+//
+// gmux_priv.gmux_backlight_device->props.brightness =
+// backlight_ops.get_brightness(gmux_priv.gmux_backlight_device);
+// backlight_update_status(gmux_priv.gmux_backlight_device);
+//
+// return 0;
+//}
+
+static int __devinit gmux_pnp_probe(struct pnp_dev *dev, const struct pnp_device_id *dev_id)
+{
+ acpi_status status;
+
+ /* TODO: get ioport region and gpe num from acpi */
+// if (! request_region(0x700, 0xff, "gmux")) {
+ if (! request_region(0x700, 0x51, "gmux")) {
+ printk("gmux: request ioport region failed\n");
+ goto err;
+ }
+
+
+ /* TODO: add more checks if it's really a gmux */
+
+ if (! gmux_detect())
+ goto err;
+
+ status = acpi_install_gpe_handler(NULL, 0x16, ACPI_GPE_LEVEL_TRIGGERED, &gpe_handler, &gmux_priv.gmux_handle);
+ if (ACPI_FAILURE(status)) {
+ printk("gmux: install gpe handler failed: %s\n", acpi_format_exception(status));
+ goto err_detect;
+ }
+
+// if (init_backlight())
+// goto err_backlight;
+
+ status = acpi_enable_gpe(NULL, 0x16);
+ if (ACPI_FAILURE(status)) {
+ printk("gmux: enable gpe failed: %s\n", acpi_format_exception(status));
+ goto err_enable_gpe;
+ }
+
+ enable_interrupts();
+
+ if (vga_switcheroo_register_handler(&gmux_handler))
+ goto err_switcheroo;
+
+ printk("gmux: loaded successfully\n");
+ return 0;
+
+err_switcheroo:
+ acpi_disable_gpe(NULL, 0x16);
+err_enable_gpe:
+// backlight_device_unregister(gmux_priv.gmux_backlight_device);
+//err_backlight:
+ acpi_remove_gpe_handler(NULL, 0x16, &gpe_handler);
+err_detect:
+// release_region(0x700, 0xff);
+ release_region(0x700, 0x51);
+err:
+ return -ENODEV;
+}
+
+static void gmux_pnp_remove(struct pnp_dev * dev)
+{
+ vga_switcheroo_unregister_handler();
+
+// backlight_device_unregister(gmux_priv.gmux_backlight_device);
+
+ disable_interrupts();
+
+ acpi_remove_gpe_handler(NULL, 0x16, &gpe_handler);
+
+ acpi_disable_gpe(NULL, 0x16);
+
+// release_region(0x700, 0xff);
+ release_region(0x700, 0x51);
+
+ printk("gmux: unloaded\n");
+}
+
+static const struct pnp_device_id pnp_dev_table[] = {
+ { "APP000b", 0 },
+ { "", 0 }
+};
+
+static struct pnp_driver gmux_pnp_driver = {
+ .name = "gmux",
+ .id_table = pnp_dev_table,
+ .probe = gmux_pnp_probe,
+ .remove = gmux_pnp_remove,
+};
+
+static int __init init_gmux(void)
+{
+ return pnp_register_driver(&gmux_pnp_driver);
+}
+
+
+static void unload_gmux(void)
+{
+ pnp_unregister_driver(&gmux_pnp_driver);
+}
+
+module_init(init_gmux);
+module_exit(unload_gmux);
+
+MODULE_AUTHOR("Andreas Heider");
+MODULE_DESCRIPTION("Support for Macbook Pro graphics switching");
+MODULE_LICENSE("GPL and additional rights");
+
diff --git a/drivers/gpu/vga/vga_switcheroo.c b/drivers/gpu/vga/vga_switcheroo.c
index 498b284..afc412d 100644
--- a/drivers/gpu/vga/vga_switcheroo.c
+++ b/drivers/gpu/vga/vga_switcheroo.c
@@ -62,6 +62,8 @@ static void vga_switcheroo_debugfs_fini(struct vgasr_priv *priv);
/* only one switcheroo per system */
static struct vgasr_priv vgasr_priv;
+static void vga_switcheroo_enable(void);
+
int vga_switcheroo_register_handler(struct vga_switcheroo_handler *handler)
{
mutex_lock(&vgasr_mutex);
@@ -71,6 +73,11 @@ int vga_switcheroo_register_handler(struct vga_switcheroo_handler *handler)
}
vgasr_priv.handler = handler;
+ /* if we get two clients + handler */
+ if (vgasr_priv.registered_clients == 0x3 && vgasr_priv.handler) {
+ printk(KERN_INFO "vga_switcheroo: enabled\n");
+ vga_switcheroo_enable();
+ }
mutex_unlock(&vgasr_mutex);
return 0;
}
--
1.7.5.rc3
To get my graphics working I also applied the i915 dual-link and