diff options
Diffstat (limited to 'drivers/platform/x86/alienware-wmi.c')
-rw-r--r-- | drivers/platform/x86/alienware-wmi.c | 565 |
1 files changed, 565 insertions, 0 deletions
diff --git a/drivers/platform/x86/alienware-wmi.c b/drivers/platform/x86/alienware-wmi.c new file mode 100644 index 000000000000..541f9514f76f --- /dev/null +++ b/drivers/platform/x86/alienware-wmi.c | |||
@@ -0,0 +1,565 @@ | |||
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 | if (!zone_dev_attrs) | ||
372 | return -ENOMEM; | ||
373 | |||
374 | zone_attrs = | ||
375 | kzalloc(sizeof(struct attribute *) * (quirks->num_zones + 2), | ||
376 | GFP_KERNEL); | ||
377 | if (!zone_attrs) | ||
378 | return -ENOMEM; | ||
379 | |||
380 | zone_data = | ||
381 | kzalloc(sizeof(struct platform_zone) * (quirks->num_zones), | ||
382 | GFP_KERNEL); | ||
383 | if (!zone_data) | ||
384 | return -ENOMEM; | ||
385 | |||
386 | for (i = 0; i < quirks->num_zones; i++) { | ||
387 | sprintf(buffer, "zone%02X", i); | ||
388 | name = kstrdup(buffer, GFP_KERNEL); | ||
389 | if (name == NULL) | ||
390 | return 1; | ||
391 | sysfs_attr_init(&zone_dev_attrs[i].attr); | ||
392 | zone_dev_attrs[i].attr.name = name; | ||
393 | zone_dev_attrs[i].attr.mode = 0644; | ||
394 | zone_dev_attrs[i].show = zone_show; | ||
395 | zone_dev_attrs[i].store = zone_set; | ||
396 | zone_data[i].location = i; | ||
397 | zone_attrs[i] = &zone_dev_attrs[i].attr; | ||
398 | zone_data[i].attr = &zone_dev_attrs[i]; | ||
399 | } | ||
400 | zone_attrs[quirks->num_zones] = &dev_attr_lighting_control_state.attr; | ||
401 | zone_attribute_group.attrs = zone_attrs; | ||
402 | |||
403 | led_classdev_register(&dev->dev, &global_led); | ||
404 | |||
405 | return sysfs_create_group(&dev->dev.kobj, &zone_attribute_group); | ||
406 | } | ||
407 | |||
408 | static void alienware_zone_exit(struct platform_device *dev) | ||
409 | { | ||
410 | sysfs_remove_group(&dev->dev.kobj, &zone_attribute_group); | ||
411 | led_classdev_unregister(&global_led); | ||
412 | if (zone_dev_attrs) { | ||
413 | int i; | ||
414 | for (i = 0; i < quirks->num_zones; i++) | ||
415 | kfree(zone_dev_attrs[i].attr.name); | ||
416 | } | ||
417 | kfree(zone_dev_attrs); | ||
418 | kfree(zone_data); | ||
419 | kfree(zone_attrs); | ||
420 | } | ||
421 | |||
422 | /* | ||
423 | The HDMI mux sysfs node indicates the status of the HDMI input mux. | ||
424 | It can toggle between standard system GPU output and HDMI input. | ||
425 | */ | ||
426 | static ssize_t show_hdmi(struct device *dev, struct device_attribute *attr, | ||
427 | char *buf) | ||
428 | { | ||
429 | acpi_status status; | ||
430 | struct acpi_buffer input; | ||
431 | union acpi_object *obj; | ||
432 | u32 tmp = 0; | ||
433 | struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; | ||
434 | struct hdmi_args in_args = { | ||
435 | .arg = 0, | ||
436 | }; | ||
437 | input.length = (acpi_size) sizeof(in_args); | ||
438 | input.pointer = &in_args; | ||
439 | status = wmi_evaluate_method(WMAX_CONTROL_GUID, 1, | ||
440 | WMAX_METHOD_HDMI_STATUS, &input, &output); | ||
441 | |||
442 | if (ACPI_SUCCESS(status)) { | ||
443 | obj = (union acpi_object *)output.pointer; | ||
444 | if (obj && obj->type == ACPI_TYPE_INTEGER) | ||
445 | tmp = (u32) obj->integer.value; | ||
446 | if (tmp == 1) | ||
447 | return scnprintf(buf, PAGE_SIZE, | ||
448 | "[input] gpu unknown\n"); | ||
449 | else if (tmp == 2) | ||
450 | return scnprintf(buf, PAGE_SIZE, | ||
451 | "input [gpu] unknown\n"); | ||
452 | } | ||
453 | pr_err("alienware-wmi: unknown HDMI status: %d\n", status); | ||
454 | return scnprintf(buf, PAGE_SIZE, "input gpu [unknown]\n"); | ||
455 | } | ||
456 | |||
457 | static ssize_t toggle_hdmi(struct device *dev, struct device_attribute *attr, | ||
458 | const char *buf, size_t count) | ||
459 | { | ||
460 | struct acpi_buffer input; | ||
461 | acpi_status status; | ||
462 | struct hdmi_args args; | ||
463 | if (strcmp(buf, "gpu\n") == 0) | ||
464 | args.arg = 1; | ||
465 | else if (strcmp(buf, "input\n") == 0) | ||
466 | args.arg = 2; | ||
467 | else | ||
468 | args.arg = 3; | ||
469 | pr_debug("alienware-wmi: setting hdmi to %d : %s", args.arg, buf); | ||
470 | input.length = (acpi_size) sizeof(args); | ||
471 | input.pointer = &args; | ||
472 | status = wmi_evaluate_method(WMAX_CONTROL_GUID, 1, | ||
473 | WMAX_METHOD_HDMI_SOURCE, &input, NULL); | ||
474 | if (ACPI_FAILURE(status)) | ||
475 | pr_err("alienware-wmi: HDMI toggle failed: results: %u\n", | ||
476 | status); | ||
477 | return count; | ||
478 | } | ||
479 | |||
480 | static DEVICE_ATTR(hdmi, S_IRUGO | S_IWUSR, show_hdmi, toggle_hdmi); | ||
481 | |||
482 | static void remove_hdmi(struct platform_device *device) | ||
483 | { | ||
484 | device_remove_file(&device->dev, &dev_attr_hdmi); | ||
485 | } | ||
486 | |||
487 | static int create_hdmi(void) | ||
488 | { | ||
489 | int ret = -ENOMEM; | ||
490 | ret = device_create_file(&platform_device->dev, &dev_attr_hdmi); | ||
491 | if (ret) | ||
492 | goto error_create_hdmi; | ||
493 | return 0; | ||
494 | |||
495 | error_create_hdmi: | ||
496 | remove_hdmi(platform_device); | ||
497 | return ret; | ||
498 | } | ||
499 | |||
500 | static int __init alienware_wmi_init(void) | ||
501 | { | ||
502 | int ret; | ||
503 | |||
504 | if (wmi_has_guid(LEGACY_CONTROL_GUID)) | ||
505 | interface = LEGACY; | ||
506 | else if (wmi_has_guid(WMAX_CONTROL_GUID)) | ||
507 | interface = WMAX; | ||
508 | else { | ||
509 | pr_warn("alienware-wmi: No known WMI GUID found\n"); | ||
510 | return -ENODEV; | ||
511 | } | ||
512 | |||
513 | dmi_check_system(alienware_quirks); | ||
514 | if (quirks == NULL) | ||
515 | quirks = &quirk_unknown; | ||
516 | |||
517 | ret = platform_driver_register(&platform_driver); | ||
518 | if (ret) | ||
519 | goto fail_platform_driver; | ||
520 | platform_device = platform_device_alloc("alienware-wmi", -1); | ||
521 | if (!platform_device) { | ||
522 | ret = -ENOMEM; | ||
523 | goto fail_platform_device1; | ||
524 | } | ||
525 | ret = platform_device_add(platform_device); | ||
526 | if (ret) | ||
527 | goto fail_platform_device2; | ||
528 | |||
529 | if (interface == WMAX) { | ||
530 | ret = create_hdmi(); | ||
531 | if (ret) | ||
532 | goto fail_prep_hdmi; | ||
533 | } | ||
534 | |||
535 | ret = alienware_zone_init(platform_device); | ||
536 | if (ret) | ||
537 | goto fail_prep_zones; | ||
538 | |||
539 | return 0; | ||
540 | |||
541 | fail_prep_zones: | ||
542 | alienware_zone_exit(platform_device); | ||
543 | fail_prep_hdmi: | ||
544 | platform_device_del(platform_device); | ||
545 | fail_platform_device2: | ||
546 | platform_device_put(platform_device); | ||
547 | fail_platform_device1: | ||
548 | platform_driver_unregister(&platform_driver); | ||
549 | fail_platform_driver: | ||
550 | return ret; | ||
551 | } | ||
552 | |||
553 | module_init(alienware_wmi_init); | ||
554 | |||
555 | static void __exit alienware_wmi_exit(void) | ||
556 | { | ||
557 | if (platform_device) { | ||
558 | alienware_zone_exit(platform_device); | ||
559 | remove_hdmi(platform_device); | ||
560 | platform_device_unregister(platform_device); | ||
561 | platform_driver_unregister(&platform_driver); | ||
562 | } | ||
563 | } | ||
564 | |||
565 | module_exit(alienware_wmi_exit); | ||