Linux Audio

Check our new training course

Loading...
Note: File does not exist in v3.1.
  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	if (bd->props.state & BL_CORE_SUSPENDED)
 91		return 0;
 92
 93	/*
 94	 * Older gmux versions require writing out lower bytes first then
 95	 * setting the upper byte to 0 to flush the values. Newer versions
 96	 * accept a single u32 write, but the old method also works, so we
 97	 * just use the old method for all gmux versions.
 98	 */
 99	gmux_write8(gmux_data, GMUX_PORT_BRIGHTNESS, brightness);
100	gmux_write8(gmux_data, GMUX_PORT_BRIGHTNESS + 1, brightness >> 8);
101	gmux_write8(gmux_data, GMUX_PORT_BRIGHTNESS + 2, brightness >> 16);
102	gmux_write8(gmux_data, GMUX_PORT_BRIGHTNESS + 3, 0);
103
104	return 0;
105}
106
107static const struct backlight_ops gmux_bl_ops = {
108	.options = BL_CORE_SUSPENDRESUME,
109	.get_brightness = gmux_get_brightness,
110	.update_status = gmux_update_status,
111};
112
113static int __devinit gmux_probe(struct pnp_dev *pnp,
114				const struct pnp_device_id *id)
115{
116	struct apple_gmux_data *gmux_data;
117	struct resource *res;
118	struct backlight_properties props;
119	struct backlight_device *bdev;
120	u8 ver_major, ver_minor, ver_release;
121	int ret = -ENXIO;
122
123	gmux_data = kzalloc(sizeof(*gmux_data), GFP_KERNEL);
124	if (!gmux_data)
125		return -ENOMEM;
126	pnp_set_drvdata(pnp, gmux_data);
127
128	res = pnp_get_resource(pnp, IORESOURCE_IO, 0);
129	if (!res) {
130		pr_err("Failed to find gmux I/O resource\n");
131		goto err_free;
132	}
133
134	gmux_data->iostart = res->start;
135	gmux_data->iolen = res->end - res->start;
136
137	if (gmux_data->iolen < GMUX_MIN_IO_LEN) {
138		pr_err("gmux I/O region too small (%lu < %u)\n",
139		       gmux_data->iolen, GMUX_MIN_IO_LEN);
140		goto err_free;
141	}
142
143	if (!request_region(gmux_data->iostart, gmux_data->iolen,
144			    "Apple gmux")) {
145		pr_err("gmux I/O already in use\n");
146		goto err_free;
147	}
148
149	/*
150	 * On some machines the gmux is in ACPI even thought the machine
151	 * doesn't really have a gmux. Check for invalid version information
152	 * to detect this.
153	 */
154	ver_major = gmux_read8(gmux_data, GMUX_PORT_VERSION_MAJOR);
155	ver_minor = gmux_read8(gmux_data, GMUX_PORT_VERSION_MINOR);
156	ver_release = gmux_read8(gmux_data, GMUX_PORT_VERSION_RELEASE);
157	if (ver_major == 0xff && ver_minor == 0xff && ver_release == 0xff) {
158		pr_info("gmux device not present\n");
159		ret = -ENODEV;
160		goto err_release;
161	}
162
163	pr_info("Found gmux version %d.%d.%d\n", ver_major, ver_minor,
164		ver_release);
165
166	memset(&props, 0, sizeof(props));
167	props.type = BACKLIGHT_PLATFORM;
168	props.max_brightness = gmux_read32(gmux_data, GMUX_PORT_MAX_BRIGHTNESS);
169
170	/*
171	 * Currently it's assumed that the maximum brightness is less than
172	 * 2^24 for compatibility with old gmux versions. Cap the max
173	 * brightness at this value, but print a warning if the hardware
174	 * reports something higher so that it can be fixed.
175	 */
176	if (WARN_ON(props.max_brightness > GMUX_MAX_BRIGHTNESS))
177		props.max_brightness = GMUX_MAX_BRIGHTNESS;
178
179	bdev = backlight_device_register("gmux_backlight", &pnp->dev,
180					 gmux_data, &gmux_bl_ops, &props);
181	if (IS_ERR(bdev)) {
182		ret = PTR_ERR(bdev);
183		goto err_release;
184	}
185
186	gmux_data->bdev = bdev;
187	bdev->props.brightness = gmux_get_brightness(bdev);
188	backlight_update_status(bdev);
189
190	/*
191	 * The backlight situation on Macs is complicated. If the gmux is
192	 * present it's the best choice, because it always works for
193	 * backlight control and supports more levels than other options.
194	 * Disable the other backlight choices.
195	 */
196	acpi_video_unregister();
197	apple_bl_unregister();
198
199	return 0;
200
201err_release:
202	release_region(gmux_data->iostart, gmux_data->iolen);
203err_free:
204	kfree(gmux_data);
205	return ret;
206}
207
208static void __devexit gmux_remove(struct pnp_dev *pnp)
209{
210	struct apple_gmux_data *gmux_data = pnp_get_drvdata(pnp);
211
212	backlight_device_unregister(gmux_data->bdev);
213	release_region(gmux_data->iostart, gmux_data->iolen);
214	kfree(gmux_data);
215
216	acpi_video_register();
217	apple_bl_register();
218}
219
220static const struct pnp_device_id gmux_device_ids[] = {
221	{"APP000B", 0},
222	{"", 0}
223};
224
225static struct pnp_driver gmux_pnp_driver = {
226	.name		= "apple-gmux",
227	.probe		= gmux_probe,
228	.remove		= __devexit_p(gmux_remove),
229	.id_table	= gmux_device_ids,
230};
231
232static int __init apple_gmux_init(void)
233{
234	return pnp_register_driver(&gmux_pnp_driver);
235}
236
237static void __exit apple_gmux_exit(void)
238{
239	pnp_unregister_driver(&gmux_pnp_driver);
240}
241
242module_init(apple_gmux_init);
243module_exit(apple_gmux_exit);
244
245MODULE_AUTHOR("Seth Forshee <seth.forshee@canonical.com>");
246MODULE_DESCRIPTION("Apple Gmux Driver");
247MODULE_LICENSE("GPL");
248MODULE_DEVICE_TABLE(pnp, gmux_device_ids);