diff options
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/platform/x86/Kconfig | 10 | ||||
-rw-r--r-- | drivers/platform/x86/Makefile | 1 | ||||
-rw-r--r-- | drivers/platform/x86/apple-gmux.c | 244 |
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 | ||
755 | config 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 | |||
755 | endif # X86_PLATFORM_DEVICES | 765 | endif # 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 | |||
49 | obj-$(CONFIG_INTEL_MID_POWER_BUTTON) += intel_mid_powerbtn.o | 49 | obj-$(CONFIG_INTEL_MID_POWER_BUTTON) += intel_mid_powerbtn.o |
50 | obj-$(CONFIG_INTEL_OAKTRAIL) += intel_oaktrail.o | 50 | obj-$(CONFIG_INTEL_OAKTRAIL) += intel_oaktrail.o |
51 | obj-$(CONFIG_SAMSUNG_Q10) += samsung-q10.o | 51 | obj-$(CONFIG_SAMSUNG_Q10) += samsung-q10.o |
52 | obj-$(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 | |||
24 | struct 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 | |||
62 | static inline u8 gmux_read8(struct apple_gmux_data *gmux_data, int port) | ||
63 | { | ||
64 | return inb(gmux_data->iostart + port); | ||
65 | } | ||
66 | |||
67 | static 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 | |||
73 | static inline u32 gmux_read32(struct apple_gmux_data *gmux_data, int port) | ||
74 | { | ||
75 | return inl(gmux_data->iostart + port); | ||
76 | } | ||
77 | |||
78 | static 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 | |||
85 | static 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 | |||
104 | static const struct backlight_ops gmux_bl_ops = { | ||
105 | .get_brightness = gmux_get_brightness, | ||
106 | .update_status = gmux_update_status, | ||
107 | }; | ||
108 | |||
109 | static 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 | |||
197 | err_release: | ||
198 | release_region(gmux_data->iostart, gmux_data->iolen); | ||
199 | err_free: | ||
200 | kfree(gmux_data); | ||
201 | return ret; | ||
202 | } | ||
203 | |||
204 | static 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 | |||
216 | static const struct pnp_device_id gmux_device_ids[] = { | ||
217 | {"APP000B", 0}, | ||
218 | {"", 0} | ||
219 | }; | ||
220 | |||
221 | static 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 | |||
228 | static int __init apple_gmux_init(void) | ||
229 | { | ||
230 | return pnp_register_driver(&gmux_pnp_driver); | ||
231 | } | ||
232 | |||
233 | static void __exit apple_gmux_exit(void) | ||
234 | { | ||
235 | pnp_unregister_driver(&gmux_pnp_driver); | ||
236 | } | ||
237 | |||
238 | module_init(apple_gmux_init); | ||
239 | module_exit(apple_gmux_exit); | ||
240 | |||
241 | MODULE_AUTHOR("Seth Forshee <seth.forshee@canonical.com>"); | ||
242 | MODULE_DESCRIPTION("Apple Gmux Driver"); | ||
243 | MODULE_LICENSE("GPL"); | ||
244 | MODULE_DEVICE_TABLE(pnp, gmux_device_ids); | ||