diff options
author | Mario Limonciello <mario_limonciello@dell.com> | 2014-04-04 14:15:42 -0400 |
---|---|---|
committer | Matthew Garrett <matthew.garrett@nebula.com> | 2014-04-06 12:58:15 -0400 |
commit | a46ad0f13bc32a9601f3c5dff43fafdc2c598814 (patch) | |
tree | 7eaebe1c76a15bae513fb9e68ee34794c708a883 | |
parent | 71db1183d4c661eaedc299b721526160bf304bd3 (diff) |
Add WMI driver for controlling AlienFX features on some Alienware products
Signed-off-by: Mario Limonciello <mario_limonciello@dell.com>
Signed-off-by: Matthew Garrett <matthew.garrett@nebula.com>
-rw-r--r-- | drivers/platform/x86/Kconfig | 12 | ||||
-rw-r--r-- | drivers/platform/x86/Makefile | 1 | ||||
-rw-r--r-- | drivers/platform/x86/alienware-wmi.c | 557 |
3 files changed, 570 insertions, 0 deletions
diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 34e3025e25ba..27df2c533b09 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig | |||
@@ -53,6 +53,18 @@ config ACERHDF | |||
53 | If you have an Acer Aspire One netbook, say Y or M | 53 | If you have an Acer Aspire One netbook, say Y or M |
54 | here. | 54 | here. |
55 | 55 | ||
56 | config ALIENWARE_WMI | ||
57 | tristate "Alienware Special feature control" | ||
58 | depends on ACPI | ||
59 | depends on LEDS_CLASS | ||
60 | depends on NEW_LEDS | ||
61 | depends on ACPI_WMI | ||
62 | ---help--- | ||
63 | This is a driver for controlling Alienware BIOS driven | ||
64 | features. It exposes an interface for controlling the AlienFX | ||
65 | zones on Alienware machines that don't contain a dedicated AlienFX | ||
66 | USB MCU such as the X51 and X51-R2. | ||
67 | |||
56 | config ASUS_LAPTOP | 68 | config ASUS_LAPTOP |
57 | tristate "Asus Laptop Extras" | 69 | tristate "Asus Laptop Extras" |
58 | depends on ACPI | 70 | depends on ACPI |
diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index b8c36f785312..1a2eafc9d48e 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile | |||
@@ -55,3 +55,4 @@ obj-$(CONFIG_INTEL_RST) += intel-rst.o | |||
55 | obj-$(CONFIG_INTEL_SMARTCONNECT) += intel-smartconnect.o | 55 | obj-$(CONFIG_INTEL_SMARTCONNECT) += intel-smartconnect.o |
56 | 56 | ||
57 | obj-$(CONFIG_PVPANIC) += pvpanic.o | 57 | obj-$(CONFIG_PVPANIC) += pvpanic.o |
58 | obj-$(CONFIG_ALIENWARE_WMI) += alienware-wmi.o | ||
diff --git a/drivers/platform/x86/alienware-wmi.c b/drivers/platform/x86/alienware-wmi.c new file mode 100644 index 000000000000..3e17e996021c --- /dev/null +++ b/drivers/platform/x86/alienware-wmi.c | |||
@@ -0,0 +1,557 @@ | |||
1 | /* | ||
2 | * Alienware AlienFX control | ||
3 | * | ||
4 | * Copyright (C) 2014 Dell Inc <mario_limonciello@dell.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 as published by | ||
8 | * the Free Software Foundation; either version 2 of the License, or | ||
9 | * (at your option) any later version. | ||
10 | * | ||
11 | * This program is distributed in the hope that it will be useful, | ||
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
14 | * GNU General Public License for more details. | ||
15 | * | ||
16 | */ | ||
17 | |||
18 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt | ||
19 | |||
20 | #include <linux/acpi.h> | ||
21 | #include <linux/module.h> | ||
22 | #include <linux/platform_device.h> | ||
23 | #include <linux/dmi.h> | ||
24 | #include <linux/acpi.h> | ||
25 | #include <linux/leds.h> | ||
26 | |||
27 | #define LEGACY_CONTROL_GUID "A90597CE-A997-11DA-B012-B622A1EF5492" | ||
28 | #define LEGACY_POWER_CONTROL_GUID "A80593CE-A997-11DA-B012-B622A1EF5492" | ||
29 | #define WMAX_CONTROL_GUID "A70591CE-A997-11DA-B012-B622A1EF5492" | ||
30 | |||
31 | #define WMAX_METHOD_HDMI_SOURCE 0x1 | ||
32 | #define WMAX_METHOD_HDMI_STATUS 0x2 | ||
33 | #define WMAX_METHOD_BRIGHTNESS 0x3 | ||
34 | #define WMAX_METHOD_ZONE_CONTROL 0x4 | ||
35 | |||
36 | MODULE_AUTHOR("Mario Limonciello <mario_limonciello@dell.com>"); | ||
37 | MODULE_DESCRIPTION("Alienware special feature control"); | ||
38 | MODULE_LICENSE("GPL"); | ||
39 | MODULE_ALIAS("wmi:" LEGACY_CONTROL_GUID); | ||
40 | MODULE_ALIAS("wmi:" WMAX_CONTROL_GUID); | ||
41 | |||
42 | enum INTERFACE_FLAGS { | ||
43 | LEGACY, | ||
44 | WMAX, | ||
45 | }; | ||
46 | |||
47 | enum LEGACY_CONTROL_STATES { | ||
48 | LEGACY_RUNNING = 1, | ||
49 | LEGACY_BOOTING = 0, | ||
50 | LEGACY_SUSPEND = 3, | ||
51 | }; | ||
52 | |||
53 | enum WMAX_CONTROL_STATES { | ||
54 | WMAX_RUNNING = 0xFF, | ||
55 | WMAX_BOOTING = 0, | ||
56 | WMAX_SUSPEND = 3, | ||
57 | }; | ||
58 | |||
59 | struct quirk_entry { | ||
60 | u8 num_zones; | ||
61 | }; | ||
62 | |||
63 | static struct quirk_entry *quirks; | ||
64 | |||
65 | static struct quirk_entry quirk_unknown = { | ||
66 | .num_zones = 2, | ||
67 | }; | ||
68 | |||
69 | static struct quirk_entry quirk_x51_family = { | ||
70 | .num_zones = 3, | ||
71 | }; | ||
72 | |||
73 | static int dmi_matched(const struct dmi_system_id *dmi) | ||
74 | { | ||
75 | quirks = dmi->driver_data; | ||
76 | return 1; | ||
77 | } | ||
78 | |||
79 | static struct dmi_system_id alienware_quirks[] = { | ||
80 | { | ||
81 | .callback = dmi_matched, | ||
82 | .ident = "Alienware X51 R1", | ||
83 | .matches = { | ||
84 | DMI_MATCH(DMI_SYS_VENDOR, "Alienware"), | ||
85 | DMI_MATCH(DMI_PRODUCT_NAME, "Alienware X51"), | ||
86 | }, | ||
87 | .driver_data = &quirk_x51_family, | ||
88 | }, | ||
89 | { | ||
90 | .callback = dmi_matched, | ||
91 | .ident = "Alienware X51 R2", | ||
92 | .matches = { | ||
93 | DMI_MATCH(DMI_SYS_VENDOR, "Alienware"), | ||
94 | DMI_MATCH(DMI_PRODUCT_NAME, "Alienware X51 R2"), | ||
95 | }, | ||
96 | .driver_data = &quirk_x51_family, | ||
97 | }, | ||
98 | {} | ||
99 | }; | ||
100 | |||
101 | struct color_platform { | ||
102 | u8 blue; | ||
103 | u8 green; | ||
104 | u8 red; | ||
105 | } __packed; | ||
106 | |||
107 | struct platform_zone { | ||
108 | u8 location; | ||
109 | struct device_attribute *attr; | ||
110 | struct color_platform colors; | ||
111 | }; | ||
112 | |||
113 | struct wmax_brightness_args { | ||
114 | u32 led_mask; | ||
115 | u32 percentage; | ||
116 | }; | ||
117 | |||
118 | struct hdmi_args { | ||
119 | u8 arg; | ||
120 | }; | ||
121 | |||
122 | struct legacy_led_args { | ||
123 | struct color_platform colors; | ||
124 | u8 brightness; | ||
125 | u8 state; | ||
126 | } __packed; | ||
127 | |||
128 | struct wmax_led_args { | ||
129 | u32 led_mask; | ||
130 | struct color_platform colors; | ||
131 | u8 state; | ||
132 | } __packed; | ||
133 | |||
134 | static struct platform_device *platform_device; | ||
135 | static struct device_attribute *zone_dev_attrs; | ||
136 | static struct attribute **zone_attrs; | ||
137 | static struct platform_zone *zone_data; | ||
138 | |||
139 | static struct platform_driver platform_driver = { | ||
140 | .driver = { | ||
141 | .name = "alienware-wmi", | ||
142 | .owner = THIS_MODULE, | ||
143 | } | ||
144 | }; | ||
145 | |||
146 | static struct attribute_group zone_attribute_group = { | ||
147 | .name = "rgb_zones", | ||
148 | }; | ||
149 | |||
150 | static u8 interface; | ||
151 | static u8 lighting_control_state; | ||
152 | static u8 global_brightness; | ||
153 | |||
154 | /* | ||
155 | * Helpers used for zone control | ||
156 | */ | ||
157 | static int parse_rgb(const char *buf, struct platform_zone *zone) | ||
158 | { | ||
159 | long unsigned int rgb; | ||
160 | int ret; | ||
161 | union color_union { | ||
162 | struct color_platform cp; | ||
163 | int package; | ||
164 | } repackager; | ||
165 | |||
166 | ret = kstrtoul(buf, 16, &rgb); | ||
167 | if (ret) | ||
168 | return ret; | ||
169 | |||
170 | /* RGB triplet notation is 24-bit hexadecimal */ | ||
171 | if (rgb > 0xFFFFFF) | ||
172 | return -EINVAL; | ||
173 | |||
174 | repackager.package = rgb & 0x0f0f0f0f; | ||
175 | pr_debug("alienware-wmi: r: %d g:%d b: %d\n", | ||
176 | repackager.cp.red, repackager.cp.green, repackager.cp.blue); | ||
177 | zone->colors = repackager.cp; | ||
178 | return 0; | ||
179 | } | ||
180 | |||
181 | static struct platform_zone *match_zone(struct device_attribute *attr) | ||
182 | { | ||
183 | int i; | ||
184 | for (i = 0; i < quirks->num_zones; i++) { | ||
185 | if ((struct device_attribute *)zone_data[i].attr == attr) { | ||
186 | pr_debug("alienware-wmi: matched zone location: %d\n", | ||
187 | zone_data[i].location); | ||
188 | return &zone_data[i]; | ||
189 | } | ||
190 | } | ||
191 | return NULL; | ||
192 | } | ||
193 | |||
194 | /* | ||
195 | * Individual RGB zone control | ||
196 | */ | ||
197 | static int alienware_update_led(struct platform_zone *zone) | ||
198 | { | ||
199 | int method_id; | ||
200 | acpi_status status; | ||
201 | char *guid; | ||
202 | struct acpi_buffer input; | ||
203 | struct legacy_led_args legacy_args; | ||
204 | struct wmax_led_args wmax_args; | ||
205 | if (interface == WMAX) { | ||
206 | wmax_args.led_mask = 1 << zone->location; | ||
207 | wmax_args.colors = zone->colors; | ||
208 | wmax_args.state = lighting_control_state; | ||
209 | guid = WMAX_CONTROL_GUID; | ||
210 | method_id = WMAX_METHOD_ZONE_CONTROL; | ||
211 | |||
212 | input.length = (acpi_size) sizeof(wmax_args); | ||
213 | input.pointer = &wmax_args; | ||
214 | } else { | ||
215 | legacy_args.colors = zone->colors; | ||
216 | legacy_args.brightness = global_brightness; | ||
217 | legacy_args.state = 0; | ||
218 | if (lighting_control_state == LEGACY_BOOTING || | ||
219 | lighting_control_state == LEGACY_SUSPEND) { | ||
220 | guid = LEGACY_POWER_CONTROL_GUID; | ||
221 | legacy_args.state = lighting_control_state; | ||
222 | } else | ||
223 | guid = LEGACY_CONTROL_GUID; | ||
224 | method_id = zone->location + 1; | ||
225 | |||
226 | input.length = (acpi_size) sizeof(legacy_args); | ||
227 | input.pointer = &legacy_args; | ||
228 | } | ||
229 | pr_debug("alienware-wmi: guid %s method %d\n", guid, method_id); | ||
230 | |||
231 | status = wmi_evaluate_method(guid, 1, method_id, &input, NULL); | ||
232 | if (ACPI_FAILURE(status)) | ||
233 | pr_err("alienware-wmi: zone set failure: %u\n", status); | ||
234 | return ACPI_FAILURE(status); | ||
235 | } | ||
236 | |||
237 | static ssize_t zone_show(struct device *dev, struct device_attribute *attr, | ||
238 | char *buf) | ||
239 | { | ||
240 | struct platform_zone *target_zone; | ||
241 | target_zone = match_zone(attr); | ||
242 | if (target_zone == NULL) | ||
243 | return sprintf(buf, "red: -1, green: -1, blue: -1\n"); | ||
244 | return sprintf(buf, "red: %d, green: %d, blue: %d\n", | ||
245 | target_zone->colors.red, | ||
246 | target_zone->colors.green, target_zone->colors.blue); | ||
247 | |||
248 | } | ||
249 | |||
250 | static ssize_t zone_set(struct device *dev, struct device_attribute *attr, | ||
251 | const char *buf, size_t count) | ||
252 | { | ||
253 | struct platform_zone *target_zone; | ||
254 | int ret; | ||
255 | target_zone = match_zone(attr); | ||
256 | if (target_zone == NULL) { | ||
257 | pr_err("alienware-wmi: invalid target zone\n"); | ||
258 | return 1; | ||
259 | } | ||
260 | ret = parse_rgb(buf, target_zone); | ||
261 | if (ret) | ||
262 | return ret; | ||
263 | ret = alienware_update_led(target_zone); | ||
264 | return ret ? ret : count; | ||
265 | } | ||
266 | |||
267 | /* | ||
268 | * LED Brightness (Global) | ||
269 | */ | ||
270 | static int wmax_brightness(int brightness) | ||
271 | { | ||
272 | acpi_status status; | ||
273 | struct acpi_buffer input; | ||
274 | struct wmax_brightness_args args = { | ||
275 | .led_mask = 0xFF, | ||
276 | .percentage = brightness, | ||
277 | }; | ||
278 | input.length = (acpi_size) sizeof(args); | ||
279 | input.pointer = &args; | ||
280 | status = wmi_evaluate_method(WMAX_CONTROL_GUID, 1, | ||
281 | WMAX_METHOD_BRIGHTNESS, &input, NULL); | ||
282 | if (ACPI_FAILURE(status)) | ||
283 | pr_err("alienware-wmi: brightness set failure: %u\n", status); | ||
284 | return ACPI_FAILURE(status); | ||
285 | } | ||
286 | |||
287 | static void global_led_set(struct led_classdev *led_cdev, | ||
288 | enum led_brightness brightness) | ||
289 | { | ||
290 | int ret; | ||
291 | global_brightness = brightness; | ||
292 | if (interface == WMAX) | ||
293 | ret = wmax_brightness(brightness); | ||
294 | else | ||
295 | ret = alienware_update_led(&zone_data[0]); | ||
296 | if (ret) | ||
297 | pr_err("LED brightness update failed\n"); | ||
298 | } | ||
299 | |||
300 | static enum led_brightness global_led_get(struct led_classdev *led_cdev) | ||
301 | { | ||
302 | return global_brightness; | ||
303 | } | ||
304 | |||
305 | static struct led_classdev global_led = { | ||
306 | .brightness_set = global_led_set, | ||
307 | .brightness_get = global_led_get, | ||
308 | .name = "alienware::global_brightness", | ||
309 | }; | ||
310 | |||
311 | /* | ||
312 | * Lighting control state device attribute (Global) | ||
313 | */ | ||
314 | static ssize_t show_control_state(struct device *dev, | ||
315 | struct device_attribute *attr, char *buf) | ||
316 | { | ||
317 | if (lighting_control_state == LEGACY_BOOTING) | ||
318 | return scnprintf(buf, PAGE_SIZE, "[booting] running suspend\n"); | ||
319 | else if (lighting_control_state == LEGACY_SUSPEND) | ||
320 | return scnprintf(buf, PAGE_SIZE, "booting running [suspend]\n"); | ||
321 | return scnprintf(buf, PAGE_SIZE, "booting [running] suspend\n"); | ||
322 | } | ||
323 | |||
324 | static ssize_t store_control_state(struct device *dev, | ||
325 | struct device_attribute *attr, | ||
326 | const char *buf, size_t count) | ||
327 | { | ||
328 | long unsigned int val; | ||
329 | if (strcmp(buf, "booting\n") == 0) | ||
330 | val = LEGACY_BOOTING; | ||
331 | else if (strcmp(buf, "suspend\n") == 0) | ||
332 | val = LEGACY_SUSPEND; | ||
333 | else if (interface == LEGACY) | ||
334 | val = LEGACY_RUNNING; | ||
335 | else | ||
336 | val = WMAX_RUNNING; | ||
337 | lighting_control_state = val; | ||
338 | pr_debug("alienware-wmi: updated control state to %d\n", | ||
339 | lighting_control_state); | ||
340 | return count; | ||
341 | } | ||
342 | |||
343 | static DEVICE_ATTR(lighting_control_state, 0644, show_control_state, | ||
344 | store_control_state); | ||
345 | |||
346 | static int alienware_zone_init(struct platform_device *dev) | ||
347 | { | ||
348 | int i; | ||
349 | char buffer[10]; | ||
350 | char *name; | ||
351 | |||
352 | if (interface == WMAX) { | ||
353 | global_led.max_brightness = 100; | ||
354 | lighting_control_state = WMAX_RUNNING; | ||
355 | } else if (interface == LEGACY) { | ||
356 | global_led.max_brightness = 0x0F; | ||
357 | lighting_control_state = LEGACY_RUNNING; | ||
358 | } | ||
359 | global_brightness = global_led.max_brightness; | ||
360 | |||
361 | /* | ||
362 | * - zone_dev_attrs num_zones + 1 is for individual zones and then | ||
363 | * null terminated | ||
364 | * - zone_attrs num_zones + 2 is for all attrs in zone_dev_attrs + | ||
365 | * the lighting control + null terminated | ||
366 | * - zone_data num_zones is for the distinct zones | ||
367 | */ | ||
368 | zone_dev_attrs = | ||
369 | kzalloc(sizeof(struct device_attribute) * (quirks->num_zones + 1), | ||
370 | GFP_KERNEL); | ||
371 | zone_attrs = | ||
372 | kzalloc(sizeof(struct attribute *) * (quirks->num_zones + 2), | ||
373 | GFP_KERNEL); | ||
374 | zone_data = | ||
375 | kzalloc(sizeof(struct platform_zone) * (quirks->num_zones), | ||
376 | GFP_KERNEL); | ||
377 | |||
378 | for (i = 0; i < quirks->num_zones; i++) { | ||
379 | sprintf(buffer, "zone%02X", i); | ||
380 | name = kstrdup(buffer, GFP_KERNEL); | ||
381 | if (name == NULL) | ||
382 | return 1; | ||
383 | sysfs_attr_init(&zone_dev_attrs[i].attr); | ||
384 | zone_dev_attrs[i].attr.name = name; | ||
385 | zone_dev_attrs[i].attr.mode = 0644; | ||
386 | zone_dev_attrs[i].show = zone_show; | ||
387 | zone_dev_attrs[i].store = zone_set; | ||
388 | zone_data[i].location = i; | ||
389 | zone_attrs[i] = &zone_dev_attrs[i].attr; | ||
390 | zone_data[i].attr = &zone_dev_attrs[i]; | ||
391 | } | ||
392 | zone_attrs[quirks->num_zones] = &dev_attr_lighting_control_state.attr; | ||
393 | zone_attribute_group.attrs = zone_attrs; | ||
394 | |||
395 | led_classdev_register(&dev->dev, &global_led); | ||
396 | |||
397 | return sysfs_create_group(&dev->dev.kobj, &zone_attribute_group); | ||
398 | } | ||
399 | |||
400 | static void alienware_zone_exit(struct platform_device *dev) | ||
401 | { | ||
402 | sysfs_remove_group(&dev->dev.kobj, &zone_attribute_group); | ||
403 | led_classdev_unregister(&global_led); | ||
404 | if (zone_dev_attrs) { | ||
405 | int i; | ||
406 | for (i = 0; i < quirks->num_zones; i++) | ||
407 | kfree(zone_dev_attrs[i].attr.name); | ||
408 | } | ||
409 | kfree(zone_dev_attrs); | ||
410 | kfree(zone_data); | ||
411 | kfree(zone_attrs); | ||
412 | } | ||
413 | |||
414 | /* | ||
415 | The HDMI mux sysfs node indicates the status of the HDMI input mux. | ||
416 | It can toggle between standard system GPU output and HDMI input. | ||
417 | */ | ||
418 | static ssize_t show_hdmi(struct device *dev, struct device_attribute *attr, | ||
419 | char *buf) | ||
420 | { | ||
421 | acpi_status status; | ||
422 | struct acpi_buffer input; | ||
423 | union acpi_object *obj; | ||
424 | u32 tmp = 0; | ||
425 | struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; | ||
426 | struct hdmi_args in_args = { | ||
427 | .arg = 0, | ||
428 | }; | ||
429 | input.length = (acpi_size) sizeof(in_args); | ||
430 | input.pointer = &in_args; | ||
431 | status = wmi_evaluate_method(WMAX_CONTROL_GUID, 1, | ||
432 | WMAX_METHOD_HDMI_STATUS, &input, &output); | ||
433 | |||
434 | if (ACPI_SUCCESS(status)) { | ||
435 | obj = (union acpi_object *)output.pointer; | ||
436 | if (obj && obj->type == ACPI_TYPE_INTEGER) | ||
437 | tmp = (u32) obj->integer.value; | ||
438 | if (tmp == 1) | ||
439 | return scnprintf(buf, PAGE_SIZE, | ||
440 | "[input] gpu unknown\n"); | ||
441 | else if (tmp == 2) | ||
442 | return scnprintf(buf, PAGE_SIZE, | ||
443 | "input [gpu] unknown\n"); | ||
444 | } | ||
445 | pr_err("alienware-wmi: unknown HDMI status: %d\n", status); | ||
446 | return scnprintf(buf, PAGE_SIZE, "input gpu [unknown]\n"); | ||
447 | } | ||
448 | |||
449 | static ssize_t toggle_hdmi(struct device *dev, struct device_attribute *attr, | ||
450 | const char *buf, size_t count) | ||
451 | { | ||
452 | struct acpi_buffer input; | ||
453 | acpi_status status; | ||
454 | struct hdmi_args args; | ||
455 | if (strcmp(buf, "gpu\n") == 0) | ||
456 | args.arg = 1; | ||
457 | else if (strcmp(buf, "input\n") == 0) | ||
458 | args.arg = 2; | ||
459 | else | ||
460 | args.arg = 3; | ||
461 | pr_debug("alienware-wmi: setting hdmi to %d : %s", args.arg, buf); | ||
462 | input.length = (acpi_size) sizeof(args); | ||
463 | input.pointer = &args; | ||
464 | status = wmi_evaluate_method(WMAX_CONTROL_GUID, 1, | ||
465 | WMAX_METHOD_HDMI_SOURCE, &input, NULL); | ||
466 | if (ACPI_FAILURE(status)) | ||
467 | pr_err("alienware-wmi: HDMI toggle failed: results: %u\n", | ||
468 | status); | ||
469 | return count; | ||
470 | } | ||
471 | |||
472 | static DEVICE_ATTR(hdmi, S_IRUGO | S_IWUSR, show_hdmi, toggle_hdmi); | ||
473 | |||
474 | static void remove_hdmi(struct platform_device *device) | ||
475 | { | ||
476 | device_remove_file(&device->dev, &dev_attr_hdmi); | ||
477 | } | ||
478 | |||
479 | static int create_hdmi(void) | ||
480 | { | ||
481 | int ret = -ENOMEM; | ||
482 | ret = device_create_file(&platform_device->dev, &dev_attr_hdmi); | ||
483 | if (ret) | ||
484 | goto error_create_hdmi; | ||
485 | return 0; | ||
486 | |||
487 | error_create_hdmi: | ||
488 | remove_hdmi(platform_device); | ||
489 | return ret; | ||
490 | } | ||
491 | |||
492 | static int __init alienware_wmi_init(void) | ||
493 | { | ||
494 | int ret; | ||
495 | |||
496 | if (wmi_has_guid(LEGACY_CONTROL_GUID)) | ||
497 | interface = LEGACY; | ||
498 | else if (wmi_has_guid(WMAX_CONTROL_GUID)) | ||
499 | interface = WMAX; | ||
500 | else { | ||
501 | pr_warn("alienware-wmi: No known WMI GUID found\n"); | ||
502 | return -ENODEV; | ||
503 | } | ||
504 | |||
505 | dmi_check_system(alienware_quirks); | ||
506 | if (quirks == NULL) | ||
507 | quirks = &quirk_unknown; | ||
508 | |||
509 | ret = platform_driver_register(&platform_driver); | ||
510 | if (ret) | ||
511 | goto fail_platform_driver; | ||
512 | platform_device = platform_device_alloc("alienware-wmi", -1); | ||
513 | if (!platform_device) { | ||
514 | ret = -ENOMEM; | ||
515 | goto fail_platform_device1; | ||
516 | } | ||
517 | ret = platform_device_add(platform_device); | ||
518 | if (ret) | ||
519 | goto fail_platform_device2; | ||
520 | |||
521 | if (interface == WMAX) { | ||
522 | ret = create_hdmi(); | ||
523 | if (ret) | ||
524 | goto fail_prep_hdmi; | ||
525 | } | ||
526 | |||
527 | ret = alienware_zone_init(platform_device); | ||
528 | if (ret) | ||
529 | goto fail_prep_zones; | ||
530 | |||
531 | return 0; | ||
532 | |||
533 | fail_prep_zones: | ||
534 | alienware_zone_exit(platform_device); | ||
535 | fail_prep_hdmi: | ||
536 | platform_device_del(platform_device); | ||
537 | fail_platform_device2: | ||
538 | platform_device_put(platform_device); | ||
539 | fail_platform_device1: | ||
540 | platform_driver_unregister(&platform_driver); | ||
541 | fail_platform_driver: | ||
542 | return ret; | ||
543 | } | ||
544 | |||
545 | module_init(alienware_wmi_init); | ||
546 | |||
547 | static void __exit alienware_wmi_exit(void) | ||
548 | { | ||
549 | alienware_zone_exit(platform_device); | ||
550 | remove_hdmi(platform_device); | ||
551 | if (platform_device) { | ||
552 | platform_device_unregister(platform_device); | ||
553 | platform_driver_unregister(&platform_driver); | ||
554 | } | ||
555 | } | ||
556 | |||
557 | module_exit(alienware_wmi_exit); | ||