]> git.karo-electronics.de Git - karo-tx-linux.git/blob - drivers/platform/x86/apple-gmux.c
dfb1a92ce9497cb49e913fa23d8f8819300243e9
[karo-tx-linux.git] / drivers / platform / x86 / apple-gmux.c
1 /*
2  *  Gmux driver for Apple laptops
3  *
4  *  Copyright (C) Canonical Ltd. <seth.forshee@canonical.com>
5  *  Copyright (C) 2010-2012 Andreas Heider <andreas@meetr.de>
6  *
7  *  This program is free software; you can redistribute it and/or modify
8  *  it under the terms of the GNU General Public License version 2 as
9  *  published by the Free Software Foundation.
10  */
11
12 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
13
14 #include <linux/module.h>
15 #include <linux/kernel.h>
16 #include <linux/init.h>
17 #include <linux/backlight.h>
18 #include <linux/acpi.h>
19 #include <linux/pnp.h>
20 #include <linux/apple_bl.h>
21 #include <linux/slab.h>
22 #include <linux/delay.h>
23 #include <linux/pci.h>
24 #include <linux/vga_switcheroo.h>
25 #include <acpi/video.h>
26 #include <asm/io.h>
27
28 struct apple_gmux_data {
29         unsigned long iostart;
30         unsigned long iolen;
31         bool indexed;
32         struct mutex index_lock;
33
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;
42 };
43
44 static struct apple_gmux_data *apple_gmux_data;
45
46 /*
47  * gmux port offsets. Many of these are not yet used, but may be in the
48  * future, and it's useful to have them documented here anyhow.
49  */
50 #define GMUX_PORT_VERSION_MAJOR         0x04
51 #define GMUX_PORT_VERSION_MINOR         0x05
52 #define GMUX_PORT_VERSION_RELEASE       0x06
53 #define GMUX_PORT_SWITCH_DISPLAY        0x10
54 #define GMUX_PORT_SWITCH_GET_DISPLAY    0x11
55 #define GMUX_PORT_INTERRUPT_ENABLE      0x14
56 #define GMUX_PORT_INTERRUPT_STATUS      0x16
57 #define GMUX_PORT_SWITCH_DDC            0x28
58 #define GMUX_PORT_SWITCH_EXTERNAL       0x40
59 #define GMUX_PORT_SWITCH_GET_EXTERNAL   0x41
60 #define GMUX_PORT_DISCRETE_POWER        0x50
61 #define GMUX_PORT_MAX_BRIGHTNESS        0x70
62 #define GMUX_PORT_BRIGHTNESS            0x74
63 #define GMUX_PORT_VALUE                 0xc2
64 #define GMUX_PORT_READ                  0xd0
65 #define GMUX_PORT_WRITE                 0xd4
66
67 #define GMUX_MIN_IO_LEN                 (GMUX_PORT_BRIGHTNESS + 4)
68
69 #define GMUX_INTERRUPT_ENABLE           0xff
70 #define GMUX_INTERRUPT_DISABLE          0x00
71
72 #define GMUX_INTERRUPT_STATUS_ACTIVE    0
73 #define GMUX_INTERRUPT_STATUS_DISPLAY   (1 << 0)
74 #define GMUX_INTERRUPT_STATUS_POWER     (1 << 2)
75 #define GMUX_INTERRUPT_STATUS_HOTPLUG   (1 << 3)
76
77 #define GMUX_BRIGHTNESS_MASK            0x00ffffff
78 #define GMUX_MAX_BRIGHTNESS             GMUX_BRIGHTNESS_MASK
79
80 static u8 gmux_pio_read8(struct apple_gmux_data *gmux_data, int port)
81 {
82         return inb(gmux_data->iostart + port);
83 }
84
85 static void gmux_pio_write8(struct apple_gmux_data *gmux_data, int port,
86                                u8 val)
87 {
88         outb(val, gmux_data->iostart + port);
89 }
90
91 static u32 gmux_pio_read32(struct apple_gmux_data *gmux_data, int port)
92 {
93         return inl(gmux_data->iostart + port);
94 }
95
96 static void gmux_pio_write32(struct apple_gmux_data *gmux_data, int port,
97                              u32 val)
98 {
99         int i;
100         u8 tmpval;
101
102         for (i = 0; i < 4; i++) {
103                 tmpval = (val >> (i * 8)) & 0xff;
104                 outb(tmpval, port + i);
105         }
106 }
107
108 static int gmux_index_wait_ready(struct apple_gmux_data *gmux_data)
109 {
110         int i = 200;
111         u8 gwr = inb(gmux_data->iostart + GMUX_PORT_WRITE);
112
113         while (i && (gwr & 0x01)) {
114                 inb(gmux_data->iostart + GMUX_PORT_READ);
115                 gwr = inb(gmux_data->iostart + GMUX_PORT_WRITE);
116                 udelay(100);
117                 i--;
118         }
119
120         return !!i;
121 }
122
123 static int gmux_index_wait_complete(struct apple_gmux_data *gmux_data)
124 {
125         int i = 200;
126         u8 gwr = inb(gmux_data->iostart + GMUX_PORT_WRITE);
127
128         while (i && !(gwr & 0x01)) {
129                 gwr = inb(gmux_data->iostart + GMUX_PORT_WRITE);
130                 udelay(100);
131                 i--;
132         }
133
134         if (gwr & 0x01)
135                 inb(gmux_data->iostart + GMUX_PORT_READ);
136
137         return !!i;
138 }
139
140 static u8 gmux_index_read8(struct apple_gmux_data *gmux_data, int port)
141 {
142         u8 val;
143
144         mutex_lock(&gmux_data->index_lock);
145         outb((port & 0xff), gmux_data->iostart + GMUX_PORT_READ);
146         gmux_index_wait_ready(gmux_data);
147         val = inb(gmux_data->iostart + GMUX_PORT_VALUE);
148         mutex_unlock(&gmux_data->index_lock);
149
150         return val;
151 }
152
153 static void gmux_index_write8(struct apple_gmux_data *gmux_data, int port,
154                               u8 val)
155 {
156         mutex_lock(&gmux_data->index_lock);
157         outb(val, gmux_data->iostart + GMUX_PORT_VALUE);
158         gmux_index_wait_ready(gmux_data);
159         outb(port & 0xff, gmux_data->iostart + GMUX_PORT_WRITE);
160         gmux_index_wait_complete(gmux_data);
161         mutex_unlock(&gmux_data->index_lock);
162 }
163
164 static u32 gmux_index_read32(struct apple_gmux_data *gmux_data, int port)
165 {
166         u32 val;
167
168         mutex_lock(&gmux_data->index_lock);
169         outb((port & 0xff), gmux_data->iostart + GMUX_PORT_READ);
170         gmux_index_wait_ready(gmux_data);
171         val = inl(gmux_data->iostart + GMUX_PORT_VALUE);
172         mutex_unlock(&gmux_data->index_lock);
173
174         return val;
175 }
176
177 static void gmux_index_write32(struct apple_gmux_data *gmux_data, int port,
178                                u32 val)
179 {
180         int i;
181         u8 tmpval;
182
183         mutex_lock(&gmux_data->index_lock);
184
185         for (i = 0; i < 4; i++) {
186                 tmpval = (val >> (i * 8)) & 0xff;
187                 outb(tmpval, gmux_data->iostart + GMUX_PORT_VALUE + i);
188         }
189
190         gmux_index_wait_ready(gmux_data);
191         outb(port & 0xff, gmux_data->iostart + GMUX_PORT_WRITE);
192         gmux_index_wait_complete(gmux_data);
193         mutex_unlock(&gmux_data->index_lock);
194 }
195
196 static u8 gmux_read8(struct apple_gmux_data *gmux_data, int port)
197 {
198         if (gmux_data->indexed)
199                 return gmux_index_read8(gmux_data, port);
200         else
201                 return gmux_pio_read8(gmux_data, port);
202 }
203
204 static void gmux_write8(struct apple_gmux_data *gmux_data, int port, u8 val)
205 {
206         if (gmux_data->indexed)
207                 gmux_index_write8(gmux_data, port, val);
208         else
209                 gmux_pio_write8(gmux_data, port, val);
210 }
211
212 static u32 gmux_read32(struct apple_gmux_data *gmux_data, int port)
213 {
214         if (gmux_data->indexed)
215                 return gmux_index_read32(gmux_data, port);
216         else
217                 return gmux_pio_read32(gmux_data, port);
218 }
219
220 static void gmux_write32(struct apple_gmux_data *gmux_data, int port,
221                              u32 val)
222 {
223         if (gmux_data->indexed)
224                 gmux_index_write32(gmux_data, port, val);
225         else
226                 gmux_pio_write32(gmux_data, port, val);
227 }
228
229 static bool gmux_is_indexed(struct apple_gmux_data *gmux_data)
230 {
231         u16 val;
232
233         outb(0xaa, gmux_data->iostart + 0xcc);
234         outb(0x55, gmux_data->iostart + 0xcd);
235         outb(0x00, gmux_data->iostart + 0xce);
236
237         val = inb(gmux_data->iostart + 0xcc) |
238                 (inb(gmux_data->iostart + 0xcd) << 8);
239
240         if (val == 0x55aa)
241                 return true;
242
243         return false;
244 }
245
246 static int gmux_get_brightness(struct backlight_device *bd)
247 {
248         struct apple_gmux_data *gmux_data = bl_get_data(bd);
249         return gmux_read32(gmux_data, GMUX_PORT_BRIGHTNESS) &
250                GMUX_BRIGHTNESS_MASK;
251 }
252
253 static int gmux_update_status(struct backlight_device *bd)
254 {
255         struct apple_gmux_data *gmux_data = bl_get_data(bd);
256         u32 brightness = bd->props.brightness;
257
258         if (bd->props.state & BL_CORE_SUSPENDED)
259                 return 0;
260
261         gmux_write32(gmux_data, GMUX_PORT_BRIGHTNESS, brightness);
262
263         return 0;
264 }
265
266 static const struct backlight_ops gmux_bl_ops = {
267         .options = BL_CORE_SUSPENDRESUME,
268         .get_brightness = gmux_get_brightness,
269         .update_status = gmux_update_status,
270 };
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
412 static int __devinit gmux_probe(struct pnp_dev *pnp,
413                                 const struct pnp_device_id *id)
414 {
415         struct apple_gmux_data *gmux_data;
416         struct resource *res;
417         struct backlight_properties props;
418         struct backlight_device *bdev;
419         u8 ver_major, ver_minor, ver_release;
420         int ret = -ENXIO;
421         acpi_status status;
422         unsigned long long gpe;
423
424         if (apple_gmux_data)
425                 return -EBUSY;
426
427         gmux_data = kzalloc(sizeof(*gmux_data), GFP_KERNEL);
428         if (!gmux_data)
429                 return -ENOMEM;
430         pnp_set_drvdata(pnp, gmux_data);
431
432         res = pnp_get_resource(pnp, IORESOURCE_IO, 0);
433         if (!res) {
434                 pr_err("Failed to find gmux I/O resource\n");
435                 goto err_free;
436         }
437
438         gmux_data->iostart = res->start;
439         gmux_data->iolen = res->end - res->start;
440
441         if (gmux_data->iolen < GMUX_MIN_IO_LEN) {
442                 pr_err("gmux I/O region too small (%lu < %u)\n",
443                        gmux_data->iolen, GMUX_MIN_IO_LEN);
444                 goto err_free;
445         }
446
447         if (!request_region(gmux_data->iostart, gmux_data->iolen,
448                             "Apple gmux")) {
449                 pr_err("gmux I/O already in use\n");
450                 goto err_free;
451         }
452
453         /*
454          * Invalid version information may indicate either that the gmux
455          * device isn't present or that it's a new one that uses indexed
456          * io
457          */
458
459         ver_major = gmux_read8(gmux_data, GMUX_PORT_VERSION_MAJOR);
460         ver_minor = gmux_read8(gmux_data, GMUX_PORT_VERSION_MINOR);
461         ver_release = gmux_read8(gmux_data, GMUX_PORT_VERSION_RELEASE);
462         if (ver_major == 0xff && ver_minor == 0xff && ver_release == 0xff) {
463                 if (gmux_is_indexed(gmux_data)) {
464                         mutex_init(&gmux_data->index_lock);
465                         gmux_data->indexed = true;
466                 } else {
467                         pr_info("gmux device not present\n");
468                         ret = -ENODEV;
469                         goto err_release;
470                 }
471                 pr_info("Found indexed gmux\n");
472         } else {
473                 pr_info("Found gmux version %d.%d.%d\n", ver_major, ver_minor,
474                         ver_release);
475         }
476
477         memset(&props, 0, sizeof(props));
478         props.type = BACKLIGHT_PLATFORM;
479         props.max_brightness = gmux_read32(gmux_data, GMUX_PORT_MAX_BRIGHTNESS);
480
481         /*
482          * Currently it's assumed that the maximum brightness is less than
483          * 2^24 for compatibility with old gmux versions. Cap the max
484          * brightness at this value, but print a warning if the hardware
485          * reports something higher so that it can be fixed.
486          */
487         if (WARN_ON(props.max_brightness > GMUX_MAX_BRIGHTNESS))
488                 props.max_brightness = GMUX_MAX_BRIGHTNESS;
489
490         bdev = backlight_device_register("gmux_backlight", &pnp->dev,
491                                          gmux_data, &gmux_bl_ops, &props);
492         if (IS_ERR(bdev)) {
493                 ret = PTR_ERR(bdev);
494                 goto err_release;
495         }
496
497         gmux_data->bdev = bdev;
498         bdev->props.brightness = gmux_get_brightness(bdev);
499         backlight_update_status(bdev);
500
501         /*
502          * The backlight situation on Macs is complicated. If the gmux is
503          * present it's the best choice, because it always works for
504          * backlight control and supports more levels than other options.
505          * Disable the other backlight choices.
506          */
507         acpi_video_dmi_promote_vendor();
508 #if defined (CONFIG_ACPI_VIDEO) || defined (CONFIG_ACPI_VIDEO_MODULE)
509         acpi_video_unregister();
510 #endif
511         apple_bl_unregister();
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
557         return 0;
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);
569 err_release:
570         release_region(gmux_data->iostart, gmux_data->iolen);
571 err_free:
572         kfree(gmux_data);
573         return ret;
574 }
575
576 static void __devexit gmux_remove(struct pnp_dev *pnp)
577 {
578         struct apple_gmux_data *gmux_data = pnp_get_drvdata(pnp);
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
589         backlight_device_unregister(gmux_data->bdev);
590
591         release_region(gmux_data->iostart, gmux_data->iolen);
592         apple_gmux_data = NULL;
593         kfree(gmux_data);
594
595         acpi_video_dmi_demote_vendor();
596 #if defined (CONFIG_ACPI_VIDEO) || defined (CONFIG_ACPI_VIDEO_MODULE)
597         acpi_video_register();
598 #endif
599         apple_bl_register();
600 }
601
602 static const struct pnp_device_id gmux_device_ids[] = {
603         {"APP000B", 0},
604         {"", 0}
605 };
606
607 static struct pnp_driver gmux_pnp_driver = {
608         .name           = "apple-gmux",
609         .probe          = gmux_probe,
610         .remove         = __devexit_p(gmux_remove),
611         .id_table       = gmux_device_ids,
612         .suspend        = gmux_suspend,
613         .resume         = gmux_resume
614 };
615
616 static int __init apple_gmux_init(void)
617 {
618         return pnp_register_driver(&gmux_pnp_driver);
619 }
620
621 static void __exit apple_gmux_exit(void)
622 {
623         pnp_unregister_driver(&gmux_pnp_driver);
624 }
625
626 module_init(apple_gmux_init);
627 module_exit(apple_gmux_exit);
628
629 MODULE_AUTHOR("Seth Forshee <seth.forshee@canonical.com>");
630 MODULE_DESCRIPTION("Apple Gmux Driver");
631 MODULE_LICENSE("GPL");
632 MODULE_DEVICE_TABLE(pnp, gmux_device_ids);