aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSeth Forshee <seth.forshee@canonical.com>2012-03-16 15:41:22 -0400
committerMatthew Garrett <mjg@redhat.com>2012-03-26 15:05:25 -0400
commit917ee75a59160fe3518c1672feb4562f11a18fbc (patch)
tree48d1bd5beca5cff9db05bfbdc34e47031c6b7188
parent83e72dd97a25a831ff270ce4437416943a1e4b36 (diff)
platform/x86: Add driver for Apple gmux device
Apple laptops with hybrid graphics have a device named gmux that controls the muxing of the LVDS panel between the GPUs as well as screen brightness. This driver adds support for the gmux device. Only backlight control is supported initially. Signed-off-by: Seth Forshee <seth.forshee@canonical.com> Signed-off-by: Matthew Garrett <mjg@redhat.com> Tested-by: Grant Likely <grant.likely@secretlab.ca>
-rw-r--r--drivers/platform/x86/Kconfig10
-rw-r--r--drivers/platform/x86/Makefile1
-rw-r--r--drivers/platform/x86/apple-gmux.c244
3 files changed, 255 insertions, 0 deletions
diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
index ce10f0313961..c5b4bfed7bb4 100644
--- a/drivers/platform/x86/Kconfig
+++ b/drivers/platform/x86/Kconfig
@@ -752,4 +752,14 @@ config SAMSUNG_Q10
752 This driver provides support for backlight control on Samsung Q10 752 This driver provides support for backlight control on Samsung Q10
753 and related laptops, including Dell Latitude X200. 753 and related laptops, including Dell Latitude X200.
754 754
755config APPLE_GMUX
756 tristate "Apple Gmux Driver"
757 depends on PNP
758 select BACKLIGHT_CLASS_DEVICE
759 ---help---
760 This driver provides support for the gmux device found on many
761 Apple laptops, which controls the display mux for the hybrid
762 graphics as well as the backlight. Currently only backlight
763 control is supported by the driver.
764
755endif # X86_PLATFORM_DEVICES 765endif # X86_PLATFORM_DEVICES
diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
index dcfee6b2606d..bf7e4f935b17 100644
--- a/drivers/platform/x86/Makefile
+++ b/drivers/platform/x86/Makefile
@@ -49,3 +49,4 @@ obj-$(CONFIG_MXM_WMI) += mxm-wmi.o
49obj-$(CONFIG_INTEL_MID_POWER_BUTTON) += intel_mid_powerbtn.o 49obj-$(CONFIG_INTEL_MID_POWER_BUTTON) += intel_mid_powerbtn.o
50obj-$(CONFIG_INTEL_OAKTRAIL) += intel_oaktrail.o 50obj-$(CONFIG_INTEL_OAKTRAIL) += intel_oaktrail.o
51obj-$(CONFIG_SAMSUNG_Q10) += samsung-q10.o 51obj-$(CONFIG_SAMSUNG_Q10) += samsung-q10.o
52obj-$(CONFIG_APPLE_GMUX) += apple-gmux.o
diff --git a/drivers/platform/x86/apple-gmux.c b/drivers/platform/x86/apple-gmux.c
new file mode 100644
index 000000000000..8a582bdfdc76
--- /dev/null
+++ b/drivers/platform/x86/apple-gmux.c
@@ -0,0 +1,244 @@
1/*
2 * Gmux driver for Apple laptops
3 *
4 * Copyright (C) Canonical Ltd. <seth.forshee@canonical.com>
5 *
6 * 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 * published by the Free Software Foundation.
9 */
10
11#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
12
13#include <linux/module.h>
14#include <linux/kernel.h>
15#include <linux/init.h>
16#include <linux/backlight.h>
17#include <linux/acpi.h>
18#include <linux/pnp.h>
19#include <linux/apple_bl.h>
20#include <linux/slab.h>
21#include <acpi/video.h>
22#include <asm/io.h>
23
24struct apple_gmux_data {
25 unsigned long iostart;
26 unsigned long iolen;
27
28 struct backlight_device *bdev;
29};
30
31/*
32 * gmux port offsets. Many of these are not yet used, but may be in the
33 * future, and it's useful to have them documented here anyhow.
34 */
35#define GMUX_PORT_VERSION_MAJOR 0x04
36#define GMUX_PORT_VERSION_MINOR 0x05
37#define GMUX_PORT_VERSION_RELEASE 0x06
38#define GMUX_PORT_SWITCH_DISPLAY 0x10
39#define GMUX_PORT_SWITCH_GET_DISPLAY 0x11
40#define GMUX_PORT_INTERRUPT_ENABLE 0x14
41#define GMUX_PORT_INTERRUPT_STATUS 0x16
42#define GMUX_PORT_SWITCH_DDC 0x28
43#define GMUX_PORT_SWITCH_EXTERNAL 0x40
44#define GMUX_PORT_SWITCH_GET_EXTERNAL 0x41
45#define GMUX_PORT_DISCRETE_POWER 0x50
46#define GMUX_PORT_MAX_BRIGHTNESS 0x70
47#define GMUX_PORT_BRIGHTNESS 0x74
48
49#define GMUX_MIN_IO_LEN (GMUX_PORT_BRIGHTNESS + 4)
50
51#define GMUX_INTERRUPT_ENABLE 0xff
52#define GMUX_INTERRUPT_DISABLE 0x00
53
54#define GMUX_INTERRUPT_STATUS_ACTIVE 0
55#define GMUX_INTERRUPT_STATUS_DISPLAY (1 << 0)
56#define GMUX_INTERRUPT_STATUS_POWER (1 << 2)
57#define GMUX_INTERRUPT_STATUS_HOTPLUG (1 << 3)
58
59#define GMUX_BRIGHTNESS_MASK 0x00ffffff
60#define GMUX_MAX_BRIGHTNESS GMUX_BRIGHTNESS_MASK
61
62static inline u8 gmux_read8(struct apple_gmux_data *gmux_data, int port)
63{
64 return inb(gmux_data->iostart + port);
65}
66
67static inline void gmux_write8(struct apple_gmux_data *gmux_data, int port,
68 u8 val)
69{
70 outb(val, gmux_data->iostart + port);
71}
72
73static inline u32 gmux_read32(struct apple_gmux_data *gmux_data, int port)
74{
75 return inl(gmux_data->iostart + port);
76}
77
78static int gmux_get_brightness(struct backlight_device *bd)
79{
80 struct apple_gmux_data *gmux_data = bl_get_data(bd);
81 return gmux_read32(gmux_data, GMUX_PORT_BRIGHTNESS) &
82 GMUX_BRIGHTNESS_MASK;
83}
84
85static int gmux_update_status(struct backlight_device *bd)
86{
87 struct apple_gmux_data *gmux_data = bl_get_data(bd);
88 u32 brightness = bd->props.brightness;
89
90 /*
91 * Older gmux versions require writing out lower bytes first then
92 * setting the upper byte to 0 to flush the values. Newer versions
93 * accept a single u32 write, but the old method also works, so we
94 * just use the old method for all gmux versions.
95 */
96 gmux_write8(gmux_data, GMUX_PORT_BRIGHTNESS, brightness);
97 gmux_write8(gmux_data, GMUX_PORT_BRIGHTNESS + 1, brightness >> 8);
98 gmux_write8(gmux_data, GMUX_PORT_BRIGHTNESS + 2, brightness >> 16);
99 gmux_write8(gmux_data, GMUX_PORT_BRIGHTNESS + 3, 0);
100
101 return 0;
102}
103
104static const struct backlight_ops gmux_bl_ops = {
105 .get_brightness = gmux_get_brightness,
106 .update_status = gmux_update_status,
107};
108
109static int __devinit gmux_probe(struct pnp_dev *pnp,
110 const struct pnp_device_id *id)
111{
112 struct apple_gmux_data *gmux_data;
113 struct resource *res;
114 struct backlight_properties props;
115 struct backlight_device *bdev;
116 u8 ver_major, ver_minor, ver_release;
117 int ret = -ENXIO;
118
119 gmux_data = kzalloc(sizeof(*gmux_data), GFP_KERNEL);
120 if (!gmux_data)
121 return -ENOMEM;
122 pnp_set_drvdata(pnp, gmux_data);
123
124 res = pnp_get_resource(pnp, IORESOURCE_IO, 0);
125 if (!res) {
126 pr_err("Failed to find gmux I/O resource\n");
127 goto err_free;
128 }
129
130 gmux_data->iostart = res->start;
131 gmux_data->iolen = res->end - res->start;
132
133 if (gmux_data->iolen < GMUX_MIN_IO_LEN) {
134 pr_err("gmux I/O region too small (%lu < %u)\n",
135 gmux_data->iolen, GMUX_MIN_IO_LEN);
136 goto err_free;
137 }
138
139 if (!request_region(gmux_data->iostart, gmux_data->iolen,
140 "Apple gmux")) {
141 pr_err("gmux I/O already in use\n");
142 goto err_free;
143 }
144
145 /*
146 * On some machines the gmux is in ACPI even thought the machine
147 * doesn't really have a gmux. Check for invalid version information
148 * to detect this.
149 */
150 ver_major = gmux_read8(gmux_data, GMUX_PORT_VERSION_MAJOR);
151 ver_minor = gmux_read8(gmux_data, GMUX_PORT_VERSION_MINOR);
152 ver_release = gmux_read8(gmux_data, GMUX_PORT_VERSION_RELEASE);
153 if (ver_major == 0xff && ver_minor == 0xff && ver_release == 0xff) {
154 pr_info("gmux device not present\n");
155 ret = -ENODEV;
156 goto err_release;
157 }
158
159 pr_info("Found gmux version %d.%d.%d\n", ver_major, ver_minor,
160 ver_release);
161
162 memset(&props, 0, sizeof(props));
163 props.type = BACKLIGHT_PLATFORM;
164 props.max_brightness = gmux_read32(gmux_data, GMUX_PORT_MAX_BRIGHTNESS);
165
166 /*
167 * Currently it's assumed that the maximum brightness is less than
168 * 2^24 for compatibility with old gmux versions. Cap the max
169 * brightness at this value, but print a warning if the hardware
170 * reports something higher so that it can be fixed.
171 */
172 if (WARN_ON(props.max_brightness > GMUX_MAX_BRIGHTNESS))
173 props.max_brightness = GMUX_MAX_BRIGHTNESS;
174
175 bdev = backlight_device_register("gmux_backlight", &pnp->dev,
176 gmux_data, &gmux_bl_ops, &props);
177 if (IS_ERR(bdev)) {
178 ret = PTR_ERR(bdev);
179 goto err_release;
180 }
181
182 gmux_data->bdev = bdev;
183 bdev->props.brightness = gmux_get_brightness(bdev);
184 backlight_update_status(bdev);
185
186 /*
187 * The backlight situation on Macs is complicated. If the gmux is
188 * present it's the best choice, because it always works for
189 * backlight control and supports more levels than other options.
190 * Disable the other backlight choices.
191 */
192 acpi_video_unregister();
193 apple_bl_unregister();
194
195 return 0;
196
197err_release:
198 release_region(gmux_data->iostart, gmux_data->iolen);
199err_free:
200 kfree(gmux_data);
201 return ret;
202}
203
204static void __devexit gmux_remove(struct pnp_dev *pnp)
205{
206 struct apple_gmux_data *gmux_data = pnp_get_drvdata(pnp);
207
208 backlight_device_unregister(gmux_data->bdev);
209 release_region(gmux_data->iostart, gmux_data->iolen);
210 kfree(gmux_data);
211
212 acpi_video_register();
213 apple_bl_register();
214}
215
216static const struct pnp_device_id gmux_device_ids[] = {
217 {"APP000B", 0},
218 {"", 0}
219};
220
221static struct pnp_driver gmux_pnp_driver = {
222 .name = "apple-gmux",
223 .probe = gmux_probe,
224 .remove = __devexit_p(gmux_remove),
225 .id_table = gmux_device_ids,
226};
227
228static int __init apple_gmux_init(void)
229{
230 return pnp_register_driver(&gmux_pnp_driver);
231}
232
233static void __exit apple_gmux_exit(void)
234{
235 pnp_unregister_driver(&gmux_pnp_driver);
236}
237
238module_init(apple_gmux_init);
239module_exit(apple_gmux_exit);
240
241MODULE_AUTHOR("Seth Forshee <seth.forshee@canonical.com>");
242MODULE_DESCRIPTION("Apple Gmux Driver");
243MODULE_LICENSE("GPL");
244MODULE_DEVICE_TABLE(pnp, gmux_device_ids);