brintos

brintos / linux-shallow public Read only

0
0
Text · 20.2 KiB · f5ee62c Raw
843 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*3 * Alienware AlienFX control4 *5 * Copyright (C) 2014 Dell Inc <Dell.Client.Kernel@dell.com>6 */7 8#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt9 10#include <linux/acpi.h>11#include <linux/module.h>12#include <linux/platform_device.h>13#include <linux/dmi.h>14#include <linux/leds.h>15 16#define LEGACY_CONTROL_GUID		"A90597CE-A997-11DA-B012-B622A1EF5492"17#define LEGACY_POWER_CONTROL_GUID	"A80593CE-A997-11DA-B012-B622A1EF5492"18#define WMAX_CONTROL_GUID		"A70591CE-A997-11DA-B012-B622A1EF5492"19 20#define WMAX_METHOD_HDMI_SOURCE		0x121#define WMAX_METHOD_HDMI_STATUS		0x222#define WMAX_METHOD_BRIGHTNESS		0x323#define WMAX_METHOD_ZONE_CONTROL	0x424#define WMAX_METHOD_HDMI_CABLE		0x525#define WMAX_METHOD_AMPLIFIER_CABLE	0x626#define WMAX_METHOD_DEEP_SLEEP_CONTROL	0x0B27#define WMAX_METHOD_DEEP_SLEEP_STATUS	0x0C28 29MODULE_AUTHOR("Mario Limonciello <mario.limonciello@outlook.com>");30MODULE_DESCRIPTION("Alienware special feature control");31MODULE_LICENSE("GPL");32MODULE_ALIAS("wmi:" LEGACY_CONTROL_GUID);33MODULE_ALIAS("wmi:" WMAX_CONTROL_GUID);34 35enum INTERFACE_FLAGS {36	LEGACY,37	WMAX,38};39 40enum LEGACY_CONTROL_STATES {41	LEGACY_RUNNING = 1,42	LEGACY_BOOTING = 0,43	LEGACY_SUSPEND = 3,44};45 46enum WMAX_CONTROL_STATES {47	WMAX_RUNNING = 0xFF,48	WMAX_BOOTING = 0,49	WMAX_SUSPEND = 3,50};51 52struct quirk_entry {53	u8 num_zones;54	u8 hdmi_mux;55	u8 amplifier;56	u8 deepslp;57};58 59static struct quirk_entry *quirks;60 61 62static struct quirk_entry quirk_inspiron5675 = {63	.num_zones = 2,64	.hdmi_mux = 0,65	.amplifier = 0,66	.deepslp = 0,67};68 69static struct quirk_entry quirk_unknown = {70	.num_zones = 2,71	.hdmi_mux = 0,72	.amplifier = 0,73	.deepslp = 0,74};75 76static struct quirk_entry quirk_x51_r1_r2 = {77	.num_zones = 3,78	.hdmi_mux = 0,79	.amplifier = 0,80	.deepslp = 0,81};82 83static struct quirk_entry quirk_x51_r3 = {84	.num_zones = 4,85	.hdmi_mux = 0,86	.amplifier = 1,87	.deepslp = 0,88};89 90static struct quirk_entry quirk_asm100 = {91	.num_zones = 2,92	.hdmi_mux = 1,93	.amplifier = 0,94	.deepslp = 0,95};96 97static struct quirk_entry quirk_asm200 = {98	.num_zones = 2,99	.hdmi_mux = 1,100	.amplifier = 0,101	.deepslp = 1,102};103 104static struct quirk_entry quirk_asm201 = {105	.num_zones = 2,106	.hdmi_mux = 1,107	.amplifier = 1,108	.deepslp = 1,109};110 111static int __init dmi_matched(const struct dmi_system_id *dmi)112{113	quirks = dmi->driver_data;114	return 1;115}116 117static const struct dmi_system_id alienware_quirks[] __initconst = {118	{119	 .callback = dmi_matched,120	 .ident = "Alienware X51 R3",121	 .matches = {122		     DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),123		     DMI_MATCH(DMI_PRODUCT_NAME, "Alienware X51 R3"),124		     },125	 .driver_data = &quirk_x51_r3,126	 },127	{128	 .callback = dmi_matched,129	 .ident = "Alienware X51 R2",130	 .matches = {131		     DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),132		     DMI_MATCH(DMI_PRODUCT_NAME, "Alienware X51 R2"),133		     },134	 .driver_data = &quirk_x51_r1_r2,135	 },136	{137	 .callback = dmi_matched,138	 .ident = "Alienware X51 R1",139	 .matches = {140		     DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),141		     DMI_MATCH(DMI_PRODUCT_NAME, "Alienware X51"),142		     },143	 .driver_data = &quirk_x51_r1_r2,144	 },145	{146	 .callback = dmi_matched,147	 .ident = "Alienware ASM100",148	 .matches = {149		     DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),150		     DMI_MATCH(DMI_PRODUCT_NAME, "ASM100"),151		     },152	 .driver_data = &quirk_asm100,153	 },154	{155	 .callback = dmi_matched,156	 .ident = "Alienware ASM200",157	 .matches = {158		     DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),159		     DMI_MATCH(DMI_PRODUCT_NAME, "ASM200"),160		     },161	 .driver_data = &quirk_asm200,162	 },163	{164	 .callback = dmi_matched,165	 .ident = "Alienware ASM201",166	 .matches = {167		     DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),168		     DMI_MATCH(DMI_PRODUCT_NAME, "ASM201"),169		     },170	 .driver_data = &quirk_asm201,171	 },172	 {173	 .callback = dmi_matched,174	 .ident = "Dell Inc. Inspiron 5675",175	 .matches = {176		     DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),177		     DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 5675"),178		     },179	 .driver_data = &quirk_inspiron5675,180	 },181	{}182};183 184struct color_platform {185	u8 blue;186	u8 green;187	u8 red;188} __packed;189 190struct platform_zone {191	u8 location;192	struct device_attribute *attr;193	struct color_platform colors;194};195 196struct wmax_brightness_args {197	u32 led_mask;198	u32 percentage;199};200 201struct wmax_basic_args {202	u8 arg;203};204 205struct legacy_led_args {206	struct color_platform colors;207	u8 brightness;208	u8 state;209} __packed;210 211struct wmax_led_args {212	u32 led_mask;213	struct color_platform colors;214	u8 state;215} __packed;216 217static struct platform_device *platform_device;218static struct device_attribute *zone_dev_attrs;219static struct attribute **zone_attrs;220static struct platform_zone *zone_data;221 222static struct platform_driver platform_driver = {223	.driver = {224		   .name = "alienware-wmi",225		   }226};227 228static struct attribute_group zone_attribute_group = {229	.name = "rgb_zones",230};231 232static u8 interface;233static u8 lighting_control_state;234static u8 global_brightness;235 236/*237 * Helpers used for zone control238 */239static int parse_rgb(const char *buf, struct platform_zone *zone)240{241	long unsigned int rgb;242	int ret;243	union color_union {244		struct color_platform cp;245		int package;246	} repackager;247 248	ret = kstrtoul(buf, 16, &rgb);249	if (ret)250		return ret;251 252	/* RGB triplet notation is 24-bit hexadecimal */253	if (rgb > 0xFFFFFF)254		return -EINVAL;255 256	repackager.package = rgb & 0x0f0f0f0f;257	pr_debug("alienware-wmi: r: %d g:%d b: %d\n",258		 repackager.cp.red, repackager.cp.green, repackager.cp.blue);259	zone->colors = repackager.cp;260	return 0;261}262 263static struct platform_zone *match_zone(struct device_attribute *attr)264{265	u8 zone;266 267	for (zone = 0; zone < quirks->num_zones; zone++) {268		if ((struct device_attribute *)zone_data[zone].attr == attr) {269			pr_debug("alienware-wmi: matched zone location: %d\n",270				 zone_data[zone].location);271			return &zone_data[zone];272		}273	}274	return NULL;275}276 277/*278 * Individual RGB zone control279 */280static int alienware_update_led(struct platform_zone *zone)281{282	int method_id;283	acpi_status status;284	char *guid;285	struct acpi_buffer input;286	struct legacy_led_args legacy_args;287	struct wmax_led_args wmax_basic_args;288	if (interface == WMAX) {289		wmax_basic_args.led_mask = 1 << zone->location;290		wmax_basic_args.colors = zone->colors;291		wmax_basic_args.state = lighting_control_state;292		guid = WMAX_CONTROL_GUID;293		method_id = WMAX_METHOD_ZONE_CONTROL;294 295		input.length = (acpi_size) sizeof(wmax_basic_args);296		input.pointer = &wmax_basic_args;297	} else {298		legacy_args.colors = zone->colors;299		legacy_args.brightness = global_brightness;300		legacy_args.state = 0;301		if (lighting_control_state == LEGACY_BOOTING ||302		    lighting_control_state == LEGACY_SUSPEND) {303			guid = LEGACY_POWER_CONTROL_GUID;304			legacy_args.state = lighting_control_state;305		} else306			guid = LEGACY_CONTROL_GUID;307		method_id = zone->location + 1;308 309		input.length = (acpi_size) sizeof(legacy_args);310		input.pointer = &legacy_args;311	}312	pr_debug("alienware-wmi: guid %s method %d\n", guid, method_id);313 314	status = wmi_evaluate_method(guid, 0, method_id, &input, NULL);315	if (ACPI_FAILURE(status))316		pr_err("alienware-wmi: zone set failure: %u\n", status);317	return ACPI_FAILURE(status);318}319 320static ssize_t zone_show(struct device *dev, struct device_attribute *attr,321			 char *buf)322{323	struct platform_zone *target_zone;324	target_zone = match_zone(attr);325	if (target_zone == NULL)326		return sprintf(buf, "red: -1, green: -1, blue: -1\n");327	return sprintf(buf, "red: %d, green: %d, blue: %d\n",328		       target_zone->colors.red,329		       target_zone->colors.green, target_zone->colors.blue);330 331}332 333static ssize_t zone_set(struct device *dev, struct device_attribute *attr,334			const char *buf, size_t count)335{336	struct platform_zone *target_zone;337	int ret;338	target_zone = match_zone(attr);339	if (target_zone == NULL) {340		pr_err("alienware-wmi: invalid target zone\n");341		return 1;342	}343	ret = parse_rgb(buf, target_zone);344	if (ret)345		return ret;346	ret = alienware_update_led(target_zone);347	return ret ? ret : count;348}349 350/*351 * LED Brightness (Global)352 */353static int wmax_brightness(int brightness)354{355	acpi_status status;356	struct acpi_buffer input;357	struct wmax_brightness_args args = {358		.led_mask = 0xFF,359		.percentage = brightness,360	};361	input.length = (acpi_size) sizeof(args);362	input.pointer = &args;363	status = wmi_evaluate_method(WMAX_CONTROL_GUID, 0,364				     WMAX_METHOD_BRIGHTNESS, &input, NULL);365	if (ACPI_FAILURE(status))366		pr_err("alienware-wmi: brightness set failure: %u\n", status);367	return ACPI_FAILURE(status);368}369 370static void global_led_set(struct led_classdev *led_cdev,371			   enum led_brightness brightness)372{373	int ret;374	global_brightness = brightness;375	if (interface == WMAX)376		ret = wmax_brightness(brightness);377	else378		ret = alienware_update_led(&zone_data[0]);379	if (ret)380		pr_err("LED brightness update failed\n");381}382 383static enum led_brightness global_led_get(struct led_classdev *led_cdev)384{385	return global_brightness;386}387 388static struct led_classdev global_led = {389	.brightness_set = global_led_set,390	.brightness_get = global_led_get,391	.name = "alienware::global_brightness",392};393 394/*395 * Lighting control state device attribute (Global)396 */397static ssize_t show_control_state(struct device *dev,398				  struct device_attribute *attr, char *buf)399{400	if (lighting_control_state == LEGACY_BOOTING)401		return sysfs_emit(buf, "[booting] running suspend\n");402	else if (lighting_control_state == LEGACY_SUSPEND)403		return sysfs_emit(buf, "booting running [suspend]\n");404	return sysfs_emit(buf, "booting [running] suspend\n");405}406 407static ssize_t store_control_state(struct device *dev,408				   struct device_attribute *attr,409				   const char *buf, size_t count)410{411	long unsigned int val;412	if (strcmp(buf, "booting\n") == 0)413		val = LEGACY_BOOTING;414	else if (strcmp(buf, "suspend\n") == 0)415		val = LEGACY_SUSPEND;416	else if (interface == LEGACY)417		val = LEGACY_RUNNING;418	else419		val = WMAX_RUNNING;420	lighting_control_state = val;421	pr_debug("alienware-wmi: updated control state to %d\n",422		 lighting_control_state);423	return count;424}425 426static DEVICE_ATTR(lighting_control_state, 0644, show_control_state,427		   store_control_state);428 429static int alienware_zone_init(struct platform_device *dev)430{431	u8 zone;432	char *name;433 434	if (interface == WMAX) {435		lighting_control_state = WMAX_RUNNING;436	} else if (interface == LEGACY) {437		lighting_control_state = LEGACY_RUNNING;438	}439	global_led.max_brightness = 0x0F;440	global_brightness = global_led.max_brightness;441 442	/*443	 *      - zone_dev_attrs num_zones + 1 is for individual zones and then444	 *        null terminated445	 *      - zone_attrs num_zones + 2 is for all attrs in zone_dev_attrs +446	 *        the lighting control + null terminated447	 *      - zone_data num_zones is for the distinct zones448	 */449	zone_dev_attrs =450	    kcalloc(quirks->num_zones + 1, sizeof(struct device_attribute),451		    GFP_KERNEL);452	if (!zone_dev_attrs)453		return -ENOMEM;454 455	zone_attrs =456	    kcalloc(quirks->num_zones + 2, sizeof(struct attribute *),457		    GFP_KERNEL);458	if (!zone_attrs)459		return -ENOMEM;460 461	zone_data =462	    kcalloc(quirks->num_zones, sizeof(struct platform_zone),463		    GFP_KERNEL);464	if (!zone_data)465		return -ENOMEM;466 467	for (zone = 0; zone < quirks->num_zones; zone++) {468		name = kasprintf(GFP_KERNEL, "zone%02hhX", zone);469		if (name == NULL)470			return 1;471		sysfs_attr_init(&zone_dev_attrs[zone].attr);472		zone_dev_attrs[zone].attr.name = name;473		zone_dev_attrs[zone].attr.mode = 0644;474		zone_dev_attrs[zone].show = zone_show;475		zone_dev_attrs[zone].store = zone_set;476		zone_data[zone].location = zone;477		zone_attrs[zone] = &zone_dev_attrs[zone].attr;478		zone_data[zone].attr = &zone_dev_attrs[zone];479	}480	zone_attrs[quirks->num_zones] = &dev_attr_lighting_control_state.attr;481	zone_attribute_group.attrs = zone_attrs;482 483	led_classdev_register(&dev->dev, &global_led);484 485	return sysfs_create_group(&dev->dev.kobj, &zone_attribute_group);486}487 488static void alienware_zone_exit(struct platform_device *dev)489{490	u8 zone;491 492	sysfs_remove_group(&dev->dev.kobj, &zone_attribute_group);493	led_classdev_unregister(&global_led);494	if (zone_dev_attrs) {495		for (zone = 0; zone < quirks->num_zones; zone++)496			kfree(zone_dev_attrs[zone].attr.name);497	}498	kfree(zone_dev_attrs);499	kfree(zone_data);500	kfree(zone_attrs);501}502 503static acpi_status alienware_wmax_command(struct wmax_basic_args *in_args,504					  u32 command, int *out_data)505{506	acpi_status status;507	union acpi_object *obj;508	struct acpi_buffer input;509	struct acpi_buffer output;510 511	input.length = (acpi_size) sizeof(*in_args);512	input.pointer = in_args;513	if (out_data) {514		output.length = ACPI_ALLOCATE_BUFFER;515		output.pointer = NULL;516		status = wmi_evaluate_method(WMAX_CONTROL_GUID, 0,517					     command, &input, &output);518		if (ACPI_SUCCESS(status)) {519			obj = (union acpi_object *)output.pointer;520			if (obj && obj->type == ACPI_TYPE_INTEGER)521				*out_data = (u32)obj->integer.value;522		}523		kfree(output.pointer);524	} else {525		status = wmi_evaluate_method(WMAX_CONTROL_GUID, 0,526					     command, &input, NULL);527	}528	return status;529}530 531/*532 *	The HDMI mux sysfs node indicates the status of the HDMI input mux.533 *	It can toggle between standard system GPU output and HDMI input.534 */535static ssize_t show_hdmi_cable(struct device *dev,536			       struct device_attribute *attr, char *buf)537{538	acpi_status status;539	u32 out_data;540	struct wmax_basic_args in_args = {541		.arg = 0,542	};543	status =544	    alienware_wmax_command(&in_args, WMAX_METHOD_HDMI_CABLE,545				   (u32 *) &out_data);546	if (ACPI_SUCCESS(status)) {547		if (out_data == 0)548			return sysfs_emit(buf, "[unconnected] connected unknown\n");549		else if (out_data == 1)550			return sysfs_emit(buf, "unconnected [connected] unknown\n");551	}552	pr_err("alienware-wmi: unknown HDMI cable status: %d\n", status);553	return sysfs_emit(buf, "unconnected connected [unknown]\n");554}555 556static ssize_t show_hdmi_source(struct device *dev,557				struct device_attribute *attr, char *buf)558{559	acpi_status status;560	u32 out_data;561	struct wmax_basic_args in_args = {562		.arg = 0,563	};564	status =565	    alienware_wmax_command(&in_args, WMAX_METHOD_HDMI_STATUS,566				   (u32 *) &out_data);567 568	if (ACPI_SUCCESS(status)) {569		if (out_data == 1)570			return sysfs_emit(buf, "[input] gpu unknown\n");571		else if (out_data == 2)572			return sysfs_emit(buf, "input [gpu] unknown\n");573	}574	pr_err("alienware-wmi: unknown HDMI source status: %u\n", status);575	return sysfs_emit(buf, "input gpu [unknown]\n");576}577 578static ssize_t toggle_hdmi_source(struct device *dev,579				  struct device_attribute *attr,580				  const char *buf, size_t count)581{582	acpi_status status;583	struct wmax_basic_args args;584	if (strcmp(buf, "gpu\n") == 0)585		args.arg = 1;586	else if (strcmp(buf, "input\n") == 0)587		args.arg = 2;588	else589		args.arg = 3;590	pr_debug("alienware-wmi: setting hdmi to %d : %s", args.arg, buf);591 592	status = alienware_wmax_command(&args, WMAX_METHOD_HDMI_SOURCE, NULL);593 594	if (ACPI_FAILURE(status))595		pr_err("alienware-wmi: HDMI toggle failed: results: %u\n",596		       status);597	return count;598}599 600static DEVICE_ATTR(cable, S_IRUGO, show_hdmi_cable, NULL);601static DEVICE_ATTR(source, S_IRUGO | S_IWUSR, show_hdmi_source,602		   toggle_hdmi_source);603 604static struct attribute *hdmi_attrs[] = {605	&dev_attr_cable.attr,606	&dev_attr_source.attr,607	NULL,608};609 610static const struct attribute_group hdmi_attribute_group = {611	.name = "hdmi",612	.attrs = hdmi_attrs,613};614 615static void remove_hdmi(struct platform_device *dev)616{617	if (quirks->hdmi_mux > 0)618		sysfs_remove_group(&dev->dev.kobj, &hdmi_attribute_group);619}620 621static int create_hdmi(struct platform_device *dev)622{623	int ret;624 625	ret = sysfs_create_group(&dev->dev.kobj, &hdmi_attribute_group);626	if (ret)627		remove_hdmi(dev);628	return ret;629}630 631/*632 * Alienware GFX amplifier support633 * - Currently supports reading cable status634 * - Leaving expansion room to possibly support dock/undock events later635 */636static ssize_t show_amplifier_status(struct device *dev,637				     struct device_attribute *attr, char *buf)638{639	acpi_status status;640	u32 out_data;641	struct wmax_basic_args in_args = {642		.arg = 0,643	};644	status =645	    alienware_wmax_command(&in_args, WMAX_METHOD_AMPLIFIER_CABLE,646				   (u32 *) &out_data);647	if (ACPI_SUCCESS(status)) {648		if (out_data == 0)649			return sysfs_emit(buf, "[unconnected] connected unknown\n");650		else if (out_data == 1)651			return sysfs_emit(buf, "unconnected [connected] unknown\n");652	}653	pr_err("alienware-wmi: unknown amplifier cable status: %d\n", status);654	return sysfs_emit(buf, "unconnected connected [unknown]\n");655}656 657static DEVICE_ATTR(status, S_IRUGO, show_amplifier_status, NULL);658 659static struct attribute *amplifier_attrs[] = {660	&dev_attr_status.attr,661	NULL,662};663 664static const struct attribute_group amplifier_attribute_group = {665	.name = "amplifier",666	.attrs = amplifier_attrs,667};668 669static void remove_amplifier(struct platform_device *dev)670{671	if (quirks->amplifier > 0)672		sysfs_remove_group(&dev->dev.kobj, &amplifier_attribute_group);673}674 675static int create_amplifier(struct platform_device *dev)676{677	int ret;678 679	ret = sysfs_create_group(&dev->dev.kobj, &amplifier_attribute_group);680	if (ret)681		remove_amplifier(dev);682	return ret;683}684 685/*686 * Deep Sleep Control support687 * - Modifies BIOS setting for deep sleep control allowing extra wakeup events688 */689static ssize_t show_deepsleep_status(struct device *dev,690				     struct device_attribute *attr, char *buf)691{692	acpi_status status;693	u32 out_data;694	struct wmax_basic_args in_args = {695		.arg = 0,696	};697	status = alienware_wmax_command(&in_args, WMAX_METHOD_DEEP_SLEEP_STATUS,698					(u32 *) &out_data);699	if (ACPI_SUCCESS(status)) {700		if (out_data == 0)701			return sysfs_emit(buf, "[disabled] s5 s5_s4\n");702		else if (out_data == 1)703			return sysfs_emit(buf, "disabled [s5] s5_s4\n");704		else if (out_data == 2)705			return sysfs_emit(buf, "disabled s5 [s5_s4]\n");706	}707	pr_err("alienware-wmi: unknown deep sleep status: %d\n", status);708	return sysfs_emit(buf, "disabled s5 s5_s4 [unknown]\n");709}710 711static ssize_t toggle_deepsleep(struct device *dev,712				struct device_attribute *attr,713				const char *buf, size_t count)714{715	acpi_status status;716	struct wmax_basic_args args;717 718	if (strcmp(buf, "disabled\n") == 0)719		args.arg = 0;720	else if (strcmp(buf, "s5\n") == 0)721		args.arg = 1;722	else723		args.arg = 2;724	pr_debug("alienware-wmi: setting deep sleep to %d : %s", args.arg, buf);725 726	status = alienware_wmax_command(&args, WMAX_METHOD_DEEP_SLEEP_CONTROL,727					NULL);728 729	if (ACPI_FAILURE(status))730		pr_err("alienware-wmi: deep sleep control failed: results: %u\n",731			status);732	return count;733}734 735static DEVICE_ATTR(deepsleep, S_IRUGO | S_IWUSR, show_deepsleep_status, toggle_deepsleep);736 737static struct attribute *deepsleep_attrs[] = {738	&dev_attr_deepsleep.attr,739	NULL,740};741 742static const struct attribute_group deepsleep_attribute_group = {743	.name = "deepsleep",744	.attrs = deepsleep_attrs,745};746 747static void remove_deepsleep(struct platform_device *dev)748{749	if (quirks->deepslp > 0)750		sysfs_remove_group(&dev->dev.kobj, &deepsleep_attribute_group);751}752 753static int create_deepsleep(struct platform_device *dev)754{755	int ret;756 757	ret = sysfs_create_group(&dev->dev.kobj, &deepsleep_attribute_group);758	if (ret)759		remove_deepsleep(dev);760	return ret;761}762 763static int __init alienware_wmi_init(void)764{765	int ret;766 767	if (wmi_has_guid(LEGACY_CONTROL_GUID))768		interface = LEGACY;769	else if (wmi_has_guid(WMAX_CONTROL_GUID))770		interface = WMAX;771	else {772		pr_warn("alienware-wmi: No known WMI GUID found\n");773		return -ENODEV;774	}775 776	dmi_check_system(alienware_quirks);777	if (quirks == NULL)778		quirks = &quirk_unknown;779 780	ret = platform_driver_register(&platform_driver);781	if (ret)782		goto fail_platform_driver;783	platform_device = platform_device_alloc("alienware-wmi", PLATFORM_DEVID_NONE);784	if (!platform_device) {785		ret = -ENOMEM;786		goto fail_platform_device1;787	}788	ret = platform_device_add(platform_device);789	if (ret)790		goto fail_platform_device2;791 792	if (quirks->hdmi_mux > 0) {793		ret = create_hdmi(platform_device);794		if (ret)795			goto fail_prep_hdmi;796	}797 798	if (quirks->amplifier > 0) {799		ret = create_amplifier(platform_device);800		if (ret)801			goto fail_prep_amplifier;802	}803 804	if (quirks->deepslp > 0) {805		ret = create_deepsleep(platform_device);806		if (ret)807			goto fail_prep_deepsleep;808	}809 810	ret = alienware_zone_init(platform_device);811	if (ret)812		goto fail_prep_zones;813 814	return 0;815 816fail_prep_zones:817	alienware_zone_exit(platform_device);818fail_prep_deepsleep:819fail_prep_amplifier:820fail_prep_hdmi:821	platform_device_del(platform_device);822fail_platform_device2:823	platform_device_put(platform_device);824fail_platform_device1:825	platform_driver_unregister(&platform_driver);826fail_platform_driver:827	return ret;828}829 830module_init(alienware_wmi_init);831 832static void __exit alienware_wmi_exit(void)833{834	if (platform_device) {835		alienware_zone_exit(platform_device);836		remove_hdmi(platform_device);837		platform_device_unregister(platform_device);838		platform_driver_unregister(&platform_driver);839	}840}841 842module_exit(alienware_wmi_exit);843