blob: 0d140e1879e7ff652d303ed6a153bb9d62da37d9 [file] [log] [blame]
Seth Forshee917ee752012-03-16 14:41:22 -05001/*
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>
Matthew Garrett96ff7052012-08-09 13:45:01 -040021#include <linux/delay.h>
Seth Forshee917ee752012-03-16 14:41:22 -050022#include <acpi/video.h>
23#include <asm/io.h>
24
25struct apple_gmux_data {
26 unsigned long iostart;
27 unsigned long iolen;
Matthew Garrett96ff7052012-08-09 13:45:01 -040028 bool indexed;
29 struct mutex index_lock;
Seth Forshee917ee752012-03-16 14:41:22 -050030
31 struct backlight_device *bdev;
32};
33
34/*
35 * 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.
37 */
38#define GMUX_PORT_VERSION_MAJOR 0x04
39#define GMUX_PORT_VERSION_MINOR 0x05
40#define GMUX_PORT_VERSION_RELEASE 0x06
41#define GMUX_PORT_SWITCH_DISPLAY 0x10
42#define GMUX_PORT_SWITCH_GET_DISPLAY 0x11
43#define GMUX_PORT_INTERRUPT_ENABLE 0x14
44#define GMUX_PORT_INTERRUPT_STATUS 0x16
45#define GMUX_PORT_SWITCH_DDC 0x28
46#define GMUX_PORT_SWITCH_EXTERNAL 0x40
47#define GMUX_PORT_SWITCH_GET_EXTERNAL 0x41
48#define GMUX_PORT_DISCRETE_POWER 0x50
49#define GMUX_PORT_MAX_BRIGHTNESS 0x70
50#define GMUX_PORT_BRIGHTNESS 0x74
Matthew Garrett96ff7052012-08-09 13:45:01 -040051#define GMUX_PORT_VALUE 0xc2
52#define GMUX_PORT_READ 0xd0
53#define GMUX_PORT_WRITE 0xd4
Seth Forshee917ee752012-03-16 14:41:22 -050054
55#define GMUX_MIN_IO_LEN (GMUX_PORT_BRIGHTNESS + 4)
56
57#define GMUX_INTERRUPT_ENABLE 0xff
58#define GMUX_INTERRUPT_DISABLE 0x00
59
60#define GMUX_INTERRUPT_STATUS_ACTIVE 0
61#define GMUX_INTERRUPT_STATUS_DISPLAY (1 << 0)
62#define GMUX_INTERRUPT_STATUS_POWER (1 << 2)
63#define GMUX_INTERRUPT_STATUS_HOTPLUG (1 << 3)
64
65#define GMUX_BRIGHTNESS_MASK 0x00ffffff
66#define GMUX_MAX_BRIGHTNESS GMUX_BRIGHTNESS_MASK
67
Matthew Garrett96ff7052012-08-09 13:45:01 -040068static u8 gmux_pio_read8(struct apple_gmux_data *gmux_data, int port)
Seth Forshee917ee752012-03-16 14:41:22 -050069{
70 return inb(gmux_data->iostart + port);
71}
72
Matthew Garrett96ff7052012-08-09 13:45:01 -040073static void gmux_pio_write8(struct apple_gmux_data *gmux_data, int port,
Seth Forshee917ee752012-03-16 14:41:22 -050074 u8 val)
75{
76 outb(val, gmux_data->iostart + port);
77}
78
Matthew Garrett96ff7052012-08-09 13:45:01 -040079static u32 gmux_pio_read32(struct apple_gmux_data *gmux_data, int port)
Seth Forshee917ee752012-03-16 14:41:22 -050080{
81 return inl(gmux_data->iostart + port);
82}
83
Matthew Garrett96ff7052012-08-09 13:45:01 -040084static void gmux_pio_write32(struct apple_gmux_data *gmux_data, int port,
85 u32 val)
Matthew Garrett7e30ed62012-08-09 12:47:00 -040086{
87 int i;
88 u8 tmpval;
89
90 for (i = 0; i < 4; i++) {
91 tmpval = (val >> (i * 8)) & 0xff;
92 outb(tmpval, port + i);
93 }
94}
95
Matthew Garrett96ff7052012-08-09 13:45:01 -040096static int gmux_index_wait_ready(struct apple_gmux_data *gmux_data)
97{
98 int i = 200;
99 u8 gwr = inb(gmux_data->iostart + GMUX_PORT_WRITE);
100
101 while (i && (gwr & 0x01)) {
102 inb(gmux_data->iostart + GMUX_PORT_READ);
103 gwr = inb(gmux_data->iostart + GMUX_PORT_WRITE);
104 udelay(100);
105 i--;
106 }
107
108 return !!i;
109}
110
111static int gmux_index_wait_complete(struct apple_gmux_data *gmux_data)
112{
113 int i = 200;
114 u8 gwr = inb(gmux_data->iostart + GMUX_PORT_WRITE);
115
116 while (i && !(gwr & 0x01)) {
117 gwr = inb(gmux_data->iostart + GMUX_PORT_WRITE);
118 udelay(100);
119 i--;
120 }
121
122 if (gwr & 0x01)
123 inb(gmux_data->iostart + GMUX_PORT_READ);
124
125 return !!i;
126}
127
128static u8 gmux_index_read8(struct apple_gmux_data *gmux_data, int port)
129{
130 u8 val;
131
132 mutex_lock(&gmux_data->index_lock);
133 outb((port & 0xff), gmux_data->iostart + GMUX_PORT_READ);
134 gmux_index_wait_ready(gmux_data);
135 val = inb(gmux_data->iostart + GMUX_PORT_VALUE);
136 mutex_unlock(&gmux_data->index_lock);
137
138 return val;
139}
140
141static void gmux_index_write8(struct apple_gmux_data *gmux_data, int port,
142 u8 val)
143{
144 mutex_lock(&gmux_data->index_lock);
145 outb(val, gmux_data->iostart + GMUX_PORT_VALUE);
146 gmux_index_wait_ready(gmux_data);
147 outb(port & 0xff, gmux_data->iostart + GMUX_PORT_WRITE);
148 gmux_index_wait_complete(gmux_data);
149 mutex_unlock(&gmux_data->index_lock);
150}
151
152static u32 gmux_index_read32(struct apple_gmux_data *gmux_data, int port)
153{
154 u32 val;
155
156 mutex_lock(&gmux_data->index_lock);
157 outb((port & 0xff), gmux_data->iostart + GMUX_PORT_READ);
158 gmux_index_wait_ready(gmux_data);
159 val = inl(gmux_data->iostart + GMUX_PORT_VALUE);
160 mutex_unlock(&gmux_data->index_lock);
161
162 return val;
163}
164
165static void gmux_index_write32(struct apple_gmux_data *gmux_data, int port,
166 u32 val)
167{
168 int i;
169 u8 tmpval;
170
171 mutex_lock(&gmux_data->index_lock);
172
173 for (i = 0; i < 4; i++) {
174 tmpval = (val >> (i * 8)) & 0xff;
175 outb(tmpval, gmux_data->iostart + GMUX_PORT_VALUE + i);
176 }
177
178 gmux_index_wait_ready(gmux_data);
179 outb(port & 0xff, gmux_data->iostart + GMUX_PORT_WRITE);
180 gmux_index_wait_complete(gmux_data);
181 mutex_unlock(&gmux_data->index_lock);
182}
183
184static u8 gmux_read8(struct apple_gmux_data *gmux_data, int port)
185{
186 if (gmux_data->indexed)
187 return gmux_index_read8(gmux_data, port);
188 else
189 return gmux_pio_read8(gmux_data, port);
190}
191
192static void gmux_write8(struct apple_gmux_data *gmux_data, int port, u8 val)
193{
194 if (gmux_data->indexed)
195 gmux_index_write8(gmux_data, port, val);
196 else
197 gmux_pio_write8(gmux_data, port, val);
198}
199
200static u32 gmux_read32(struct apple_gmux_data *gmux_data, int port)
201{
202 if (gmux_data->indexed)
203 return gmux_index_read32(gmux_data, port);
204 else
205 return gmux_pio_read32(gmux_data, port);
206}
207
208static void gmux_write32(struct apple_gmux_data *gmux_data, int port,
209 u32 val)
210{
211 if (gmux_data->indexed)
212 gmux_index_write32(gmux_data, port, val);
213 else
214 gmux_pio_write32(gmux_data, port, val);
215}
216
217static bool gmux_is_indexed(struct apple_gmux_data *gmux_data)
218{
219 u16 val;
220
221 outb(0xaa, gmux_data->iostart + 0xcc);
222 outb(0x55, gmux_data->iostart + 0xcd);
223 outb(0x00, gmux_data->iostart + 0xce);
224
225 val = inb(gmux_data->iostart + 0xcc) |
226 (inb(gmux_data->iostart + 0xcd) << 8);
227
228 if (val == 0x55aa)
229 return true;
230
231 return false;
232}
233
Seth Forshee917ee752012-03-16 14:41:22 -0500234static int gmux_get_brightness(struct backlight_device *bd)
235{
236 struct apple_gmux_data *gmux_data = bl_get_data(bd);
237 return gmux_read32(gmux_data, GMUX_PORT_BRIGHTNESS) &
238 GMUX_BRIGHTNESS_MASK;
239}
240
241static int gmux_update_status(struct backlight_device *bd)
242{
243 struct apple_gmux_data *gmux_data = bl_get_data(bd);
244 u32 brightness = bd->props.brightness;
245
Seth Forshee96960882012-04-19 10:55:35 -0500246 if (bd->props.state & BL_CORE_SUSPENDED)
Matthew Garretta2f01a82012-06-01 15:18:52 -0400247 return 0;
Seth Forshee96960882012-04-19 10:55:35 -0500248
Matthew Garrett7e30ed62012-08-09 12:47:00 -0400249 gmux_write32(gmux_data, GMUX_PORT_BRIGHTNESS, brightness);
Seth Forshee917ee752012-03-16 14:41:22 -0500250
251 return 0;
252}
253
254static const struct backlight_ops gmux_bl_ops = {
Seth Forshee96960882012-04-19 10:55:35 -0500255 .options = BL_CORE_SUSPENDRESUME,
Seth Forshee917ee752012-03-16 14:41:22 -0500256 .get_brightness = gmux_get_brightness,
257 .update_status = gmux_update_status,
258};
259
260static int __devinit gmux_probe(struct pnp_dev *pnp,
261 const struct pnp_device_id *id)
262{
263 struct apple_gmux_data *gmux_data;
264 struct resource *res;
265 struct backlight_properties props;
266 struct backlight_device *bdev;
267 u8 ver_major, ver_minor, ver_release;
268 int ret = -ENXIO;
269
270 gmux_data = kzalloc(sizeof(*gmux_data), GFP_KERNEL);
271 if (!gmux_data)
272 return -ENOMEM;
273 pnp_set_drvdata(pnp, gmux_data);
274
275 res = pnp_get_resource(pnp, IORESOURCE_IO, 0);
276 if (!res) {
277 pr_err("Failed to find gmux I/O resource\n");
278 goto err_free;
279 }
280
281 gmux_data->iostart = res->start;
282 gmux_data->iolen = res->end - res->start;
283
284 if (gmux_data->iolen < GMUX_MIN_IO_LEN) {
285 pr_err("gmux I/O region too small (%lu < %u)\n",
286 gmux_data->iolen, GMUX_MIN_IO_LEN);
287 goto err_free;
288 }
289
290 if (!request_region(gmux_data->iostart, gmux_data->iolen,
291 "Apple gmux")) {
292 pr_err("gmux I/O already in use\n");
293 goto err_free;
294 }
295
296 /*
Matthew Garrett96ff7052012-08-09 13:45:01 -0400297 * Invalid version information may indicate either that the gmux
298 * device isn't present or that it's a new one that uses indexed
299 * io
Seth Forshee917ee752012-03-16 14:41:22 -0500300 */
Matthew Garrett96ff7052012-08-09 13:45:01 -0400301
Seth Forshee917ee752012-03-16 14:41:22 -0500302 ver_major = gmux_read8(gmux_data, GMUX_PORT_VERSION_MAJOR);
303 ver_minor = gmux_read8(gmux_data, GMUX_PORT_VERSION_MINOR);
304 ver_release = gmux_read8(gmux_data, GMUX_PORT_VERSION_RELEASE);
305 if (ver_major == 0xff && ver_minor == 0xff && ver_release == 0xff) {
Matthew Garrett96ff7052012-08-09 13:45:01 -0400306 if (gmux_is_indexed(gmux_data)) {
307 mutex_init(&gmux_data->index_lock);
308 gmux_data->indexed = true;
309 } else {
310 pr_info("gmux device not present\n");
311 ret = -ENODEV;
312 goto err_release;
313 }
314 pr_info("Found indexed gmux\n");
315 } else {
316 pr_info("Found gmux version %d.%d.%d\n", ver_major, ver_minor,
317 ver_release);
Seth Forshee917ee752012-03-16 14:41:22 -0500318 }
319
Seth Forshee917ee752012-03-16 14:41:22 -0500320 memset(&props, 0, sizeof(props));
321 props.type = BACKLIGHT_PLATFORM;
322 props.max_brightness = gmux_read32(gmux_data, GMUX_PORT_MAX_BRIGHTNESS);
323
324 /*
325 * Currently it's assumed that the maximum brightness is less than
326 * 2^24 for compatibility with old gmux versions. Cap the max
327 * brightness at this value, but print a warning if the hardware
328 * reports something higher so that it can be fixed.
329 */
330 if (WARN_ON(props.max_brightness > GMUX_MAX_BRIGHTNESS))
331 props.max_brightness = GMUX_MAX_BRIGHTNESS;
332
333 bdev = backlight_device_register("gmux_backlight", &pnp->dev,
334 gmux_data, &gmux_bl_ops, &props);
335 if (IS_ERR(bdev)) {
336 ret = PTR_ERR(bdev);
337 goto err_release;
338 }
339
340 gmux_data->bdev = bdev;
341 bdev->props.brightness = gmux_get_brightness(bdev);
342 backlight_update_status(bdev);
343
344 /*
345 * The backlight situation on Macs is complicated. If the gmux is
346 * present it's the best choice, because it always works for
347 * backlight control and supports more levels than other options.
348 * Disable the other backlight choices.
349 */
Corentin Charya60b2172012-06-13 09:32:02 +0200350 acpi_video_dmi_promote_vendor();
351#ifdef CONFIG_ACPI_VIDEO
Seth Forshee917ee752012-03-16 14:41:22 -0500352 acpi_video_unregister();
Corentin Charya60b2172012-06-13 09:32:02 +0200353#endif
Seth Forshee917ee752012-03-16 14:41:22 -0500354 apple_bl_unregister();
355
356 return 0;
357
358err_release:
359 release_region(gmux_data->iostart, gmux_data->iolen);
360err_free:
361 kfree(gmux_data);
362 return ret;
363}
364
365static void __devexit gmux_remove(struct pnp_dev *pnp)
366{
367 struct apple_gmux_data *gmux_data = pnp_get_drvdata(pnp);
368
369 backlight_device_unregister(gmux_data->bdev);
370 release_region(gmux_data->iostart, gmux_data->iolen);
371 kfree(gmux_data);
372
Corentin Charya60b2172012-06-13 09:32:02 +0200373 acpi_video_dmi_demote_vendor();
374#ifdef CONFIG_ACPI_VIDEO
Seth Forshee917ee752012-03-16 14:41:22 -0500375 acpi_video_register();
Corentin Charya60b2172012-06-13 09:32:02 +0200376#endif
Seth Forshee917ee752012-03-16 14:41:22 -0500377 apple_bl_register();
378}
379
380static const struct pnp_device_id gmux_device_ids[] = {
381 {"APP000B", 0},
382 {"", 0}
383};
384
385static struct pnp_driver gmux_pnp_driver = {
386 .name = "apple-gmux",
387 .probe = gmux_probe,
388 .remove = __devexit_p(gmux_remove),
389 .id_table = gmux_device_ids,
390};
391
392static int __init apple_gmux_init(void)
393{
394 return pnp_register_driver(&gmux_pnp_driver);
395}
396
397static void __exit apple_gmux_exit(void)
398{
399 pnp_unregister_driver(&gmux_pnp_driver);
400}
401
402module_init(apple_gmux_init);
403module_exit(apple_gmux_exit);
404
405MODULE_AUTHOR("Seth Forshee <seth.forshee@canonical.com>");
406MODULE_DESCRIPTION("Apple Gmux Driver");
407MODULE_LICENSE("GPL");
408MODULE_DEVICE_TABLE(pnp, gmux_device_ids);