1 /*-*-linux-c-*-*/
2 
3 /*
4   Copyright (C) 2006 Lennart Poettering <mzxreary (at) 0pointer (dot) de>
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, but
12   WITHOUT ANY WARRANTY; without even the implied warranty of
13   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14   General Public License for more details.
15 
16   You should have received a copy of the GNU General Public License
17   along with this program; if not, write to the Free Software
18   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19   02110-1301, USA.
20  */
21 
22 /*
23  * msi-laptop.c - MSI S270 laptop support. This laptop is sold under
24  * various brands, including "Cytron/TCM/Medion/Tchibo MD96100".
25  *
26  * Driver also supports S271, S420 models.
27  *
28  * This driver exports a few files in /sys/devices/platform/msi-laptop-pf/:
29  *
30  *   lcd_level - Screen brightness: contains a single integer in the
31  *   range 0..8. (rw)
32  *
33  *   auto_brightness - Enable automatic brightness control: contains
34  *   either 0 or 1. If set to 1 the hardware adjusts the screen
35  *   brightness automatically when the power cord is
36  *   plugged/unplugged. (rw)
37  *
38  *   wlan - WLAN subsystem enabled: contains either 0 or 1. (ro)
39  *
40  *   bluetooth - Bluetooth subsystem enabled: contains either 0 or 1
41  *   Please note that this file is constantly 0 if no Bluetooth
42  *   hardware is available. (ro)
43  *
44  * In addition to these platform device attributes the driver
45  * registers itself in the Linux backlight control subsystem and is
46  * available to userspace under /sys/class/backlight/msi-laptop-bl/.
47  *
48  * This driver might work on other laptops produced by MSI. If you
49  * want to try it you can pass force=1 as argument to the module which
50  * will force it to load even when the DMI data doesn't identify the
51  * laptop as MSI S270. YMMV.
52  */
53 
54 #include <linux/module.h>
55 #include <linux/kernel.h>
56 #include <linux/init.h>
57 #include <linux/acpi.h>
58 #include <linux/dmi.h>
59 #include <linux/backlight.h>
60 #include <linux/platform_device.h>
61 #include <linux/rfkill.h>
62 #include <linux/i8042.h>
63 
64 #define MSI_DRIVER_VERSION "0.5"
65 
66 #define MSI_LCD_LEVEL_MAX 9
67 
68 #define MSI_EC_COMMAND_WIRELESS 0x10
69 #define MSI_EC_COMMAND_LCD_LEVEL 0x11
70 
71 #define MSI_STANDARD_EC_COMMAND_ADDRESS	0x2e
72 #define MSI_STANDARD_EC_BLUETOOTH_MASK	(1 << 0)
73 #define MSI_STANDARD_EC_WEBCAM_MASK	(1 << 1)
74 #define MSI_STANDARD_EC_WLAN_MASK	(1 << 3)
75 #define MSI_STANDARD_EC_3G_MASK		(1 << 4)
76 
77 /* For set SCM load flag to disable BIOS fn key */
78 #define MSI_STANDARD_EC_SCM_LOAD_ADDRESS	0x2d
79 #define MSI_STANDARD_EC_SCM_LOAD_MASK		(1 << 0)
80 
81 static int msi_laptop_resume(struct platform_device *device);
82 
83 #define MSI_STANDARD_EC_DEVICES_EXISTS_ADDRESS	0x2f
84 
85 static int force;
86 module_param(force, bool, 0);
87 MODULE_PARM_DESC(force, "Force driver load, ignore DMI data");
88 
89 static int auto_brightness;
90 module_param(auto_brightness, int, 0);
91 MODULE_PARM_DESC(auto_brightness, "Enable automatic brightness control (0: disabled; 1: enabled; 2: don't touch)");
92 
93 static bool old_ec_model;
94 static int wlan_s, bluetooth_s, threeg_s;
95 static int threeg_exists;
96 
97 /* Some MSI 3G netbook only have one fn key to control Wlan/Bluetooth/3G,
98  * those netbook will load the SCM (windows app) to disable the original
99  * Wlan/Bluetooth control by BIOS when user press fn key, then control
100  * Wlan/Bluetooth/3G by SCM (software control by OS). Without SCM, user
101  * cann't on/off 3G module on those 3G netbook.
102  * On Linux, msi-laptop driver will do the same thing to disable the
103  * original BIOS control, then might need use HAL or other userland
104  * application to do the software control that simulate with SCM.
105  * e.g. MSI N034 netbook
106  */
107 static bool load_scm_model;
108 static struct rfkill *rfk_wlan, *rfk_bluetooth, *rfk_threeg;
109 
110 /* Hardware access */
111 
112 static int set_lcd_level(int level)
113 {
114 	u8 buf[2];
115 
116 	if (level < 0 || level >= MSI_LCD_LEVEL_MAX)
117 		return -EINVAL;
118 
119 	buf[0] = 0x80;
120 	buf[1] = (u8) (level*31);
121 
122 	return ec_transaction(MSI_EC_COMMAND_LCD_LEVEL, buf, sizeof(buf),
123 			      NULL, 0, 1);
124 }
125 
126 static int get_lcd_level(void)
127 {
128 	u8 wdata = 0, rdata;
129 	int result;
130 
131 	result = ec_transaction(MSI_EC_COMMAND_LCD_LEVEL, &wdata, 1,
132 				&rdata, 1, 1);
133 	if (result < 0)
134 		return result;
135 
136 	return (int) rdata / 31;
137 }
138 
139 static int get_auto_brightness(void)
140 {
141 	u8 wdata = 4, rdata;
142 	int result;
143 
144 	result = ec_transaction(MSI_EC_COMMAND_LCD_LEVEL, &wdata, 1,
145 				&rdata, 1, 1);
146 	if (result < 0)
147 		return result;
148 
149 	return !!(rdata & 8);
150 }
151 
152 static int set_auto_brightness(int enable)
153 {
154 	u8 wdata[2], rdata;
155 	int result;
156 
157 	wdata[0] = 4;
158 
159 	result = ec_transaction(MSI_EC_COMMAND_LCD_LEVEL, wdata, 1,
160 				&rdata, 1, 1);
161 	if (result < 0)
162 		return result;
163 
164 	wdata[0] = 0x84;
165 	wdata[1] = (rdata & 0xF7) | (enable ? 8 : 0);
166 
167 	return ec_transaction(MSI_EC_COMMAND_LCD_LEVEL, wdata, 2,
168 			      NULL, 0, 1);
169 }
170 
171 static ssize_t set_device_state(const char *buf, size_t count, u8 mask)
172 {
173 	int status;
174 	u8 wdata = 0, rdata;
175 	int result;
176 
177 	if (sscanf(buf, "%i", &status) != 1 || (status < 0 || status > 1))
178 		return -EINVAL;
179 
180 	/* read current device state */
181 	result = ec_read(MSI_STANDARD_EC_COMMAND_ADDRESS, &rdata);
182 	if (result < 0)
183 		return -EINVAL;
184 
185 	if (!!(rdata & mask) != status) {
186 		/* reverse device bit */
187 		if (rdata & mask)
188 			wdata = rdata & ~mask;
189 		else
190 			wdata = rdata | mask;
191 
192 		result = ec_write(MSI_STANDARD_EC_COMMAND_ADDRESS, wdata);
193 		if (result < 0)
194 			return -EINVAL;
195 	}
196 
197 	return count;
198 }
199 
200 static int get_wireless_state(int *wlan, int *bluetooth)
201 {
202 	u8 wdata = 0, rdata;
203 	int result;
204 
205 	result = ec_transaction(MSI_EC_COMMAND_WIRELESS, &wdata, 1, &rdata, 1, 1);
206 	if (result < 0)
207 		return -1;
208 
209 	if (wlan)
210 		*wlan = !!(rdata & 8);
211 
212 	if (bluetooth)
213 		*bluetooth = !!(rdata & 128);
214 
215 	return 0;
216 }
217 
218 static int get_wireless_state_ec_standard(void)
219 {
220 	u8 rdata;
221 	int result;
222 
223 	result = ec_read(MSI_STANDARD_EC_COMMAND_ADDRESS, &rdata);
224 	if (result < 0)
225 		return -1;
226 
227 	wlan_s = !!(rdata & MSI_STANDARD_EC_WLAN_MASK);
228 
229 	bluetooth_s = !!(rdata & MSI_STANDARD_EC_BLUETOOTH_MASK);
230 
231 	threeg_s = !!(rdata & MSI_STANDARD_EC_3G_MASK);
232 
233 	return 0;
234 }
235 
236 static int get_threeg_exists(void)
237 {
238 	u8 rdata;
239 	int result;
240 
241 	result = ec_read(MSI_STANDARD_EC_DEVICES_EXISTS_ADDRESS, &rdata);
242 	if (result < 0)
243 		return -1;
244 
245 	threeg_exists = !!(rdata & MSI_STANDARD_EC_3G_MASK);
246 
247 	return 0;
248 }
249 
250 /* Backlight device stuff */
251 
252 static int bl_get_brightness(struct backlight_device *b)
253 {
254 	return get_lcd_level();
255 }
256 
257 
258 static int bl_update_status(struct backlight_device *b)
259 {
260 	return set_lcd_level(b->props.brightness);
261 }
262 
263 static const struct backlight_ops msibl_ops = {
264 	.get_brightness = bl_get_brightness,
265 	.update_status  = bl_update_status,
266 };
267 
268 static struct backlight_device *msibl_device;
269 
270 /* Platform device */
271 
272 static ssize_t show_wlan(struct device *dev,
273 	struct device_attribute *attr, char *buf)
274 {
275 
276 	int ret, enabled;
277 
278 	if (old_ec_model) {
279 		ret = get_wireless_state(&enabled, NULL);
280 	} else {
281 		ret = get_wireless_state_ec_standard();
282 		enabled = wlan_s;
283 	}
284 	if (ret < 0)
285 		return ret;
286 
287 	return sprintf(buf, "%i\n", enabled);
288 }
289 
290 static ssize_t store_wlan(struct device *dev,
291 	struct device_attribute *attr, const char *buf, size_t count)
292 {
293 	return set_device_state(buf, count, MSI_STANDARD_EC_WLAN_MASK);
294 }
295 
296 static ssize_t show_bluetooth(struct device *dev,
297 	struct device_attribute *attr, char *buf)
298 {
299 
300 	int ret, enabled;
301 
302 	if (old_ec_model) {
303 		ret = get_wireless_state(NULL, &enabled);
304 	} else {
305 		ret = get_wireless_state_ec_standard();
306 		enabled = bluetooth_s;
307 	}
308 	if (ret < 0)
309 		return ret;
310 
311 	return sprintf(buf, "%i\n", enabled);
312 }
313 
314 static ssize_t store_bluetooth(struct device *dev,
315 	struct device_attribute *attr, const char *buf, size_t count)
316 {
317 	return set_device_state(buf, count, MSI_STANDARD_EC_BLUETOOTH_MASK);
318 }
319 
320 static ssize_t show_threeg(struct device *dev,
321 	struct device_attribute *attr, char *buf)
322 {
323 
324 	int ret;
325 
326 	/* old msi ec not support 3G */
327 	if (old_ec_model)
328 		return -1;
329 
330 	ret = get_wireless_state_ec_standard();
331 	if (ret < 0)
332 		return ret;
333 
334 	return sprintf(buf, "%i\n", threeg_s);
335 }
336 
337 static ssize_t store_threeg(struct device *dev,
338 	struct device_attribute *attr, const char *buf, size_t count)
339 {
340 	return set_device_state(buf, count, MSI_STANDARD_EC_3G_MASK);
341 }
342 
343 static ssize_t show_lcd_level(struct device *dev,
344 	struct device_attribute *attr, char *buf)
345 {
346 
347 	int ret;
348 
349 	ret = get_lcd_level();
350 	if (ret < 0)
351 		return ret;
352 
353 	return sprintf(buf, "%i\n", ret);
354 }
355 
356 static ssize_t store_lcd_level(struct device *dev,
357 	struct device_attribute *attr, const char *buf, size_t count)
358 {
359 
360 	int level, ret;
361 
362 	if (sscanf(buf, "%i", &level) != 1 ||
363 	    (level < 0 || level >= MSI_LCD_LEVEL_MAX))
364 		return -EINVAL;
365 
366 	ret = set_lcd_level(level);
367 	if (ret < 0)
368 		return ret;
369 
370 	return count;
371 }
372 
373 static ssize_t show_auto_brightness(struct device *dev,
374 	struct device_attribute *attr, char *buf)
375 {
376 
377 	int ret;
378 
379 	ret = get_auto_brightness();
380 	if (ret < 0)
381 		return ret;
382 
383 	return sprintf(buf, "%i\n", ret);
384 }
385 
386 static ssize_t store_auto_brightness(struct device *dev,
387 	struct device_attribute *attr, const char *buf, size_t count)
388 {
389 
390 	int enable, ret;
391 
392 	if (sscanf(buf, "%i", &enable) != 1 || (enable != (enable & 1)))
393 		return -EINVAL;
394 
395 	ret = set_auto_brightness(enable);
396 	if (ret < 0)
397 		return ret;
398 
399 	return count;
400 }
401 
402 static DEVICE_ATTR(lcd_level, 0644, show_lcd_level, store_lcd_level);
403 static DEVICE_ATTR(auto_brightness, 0644, show_auto_brightness,
404 		   store_auto_brightness);
405 static DEVICE_ATTR(bluetooth, 0444, show_bluetooth, NULL);
406 static DEVICE_ATTR(wlan, 0444, show_wlan, NULL);
407 static DEVICE_ATTR(threeg, 0444, show_threeg, NULL);
408 
409 static struct attribute *msipf_attributes[] = {
410 	&dev_attr_lcd_level.attr,
411 	&dev_attr_auto_brightness.attr,
412 	&dev_attr_bluetooth.attr,
413 	&dev_attr_wlan.attr,
414 	NULL
415 };
416 
417 static struct attribute_group msipf_attribute_group = {
418 	.attrs = msipf_attributes
419 };
420 
421 static struct platform_driver msipf_driver = {
422 	.driver = {
423 		.name = "msi-laptop-pf",
424 		.owner = THIS_MODULE,
425 	},
426 	.resume = msi_laptop_resume,
427 };
428 
429 static struct platform_device *msipf_device;
430 
431 /* Initialization */
432 
433 static int dmi_check_cb(const struct dmi_system_id *id)
434 {
435 	printk(KERN_INFO "msi-laptop: Identified laptop model '%s'.\n",
436 	       id->ident);
437 	return 1;
438 }
439 
440 static struct dmi_system_id __initdata msi_dmi_table[] = {
441 	{
442 		.ident = "MSI S270",
443 		.matches = {
444 			DMI_MATCH(DMI_SYS_VENDOR, "MICRO-STAR INT'L CO.,LTD"),
445 			DMI_MATCH(DMI_PRODUCT_NAME, "MS-1013"),
446 			DMI_MATCH(DMI_PRODUCT_VERSION, "0131"),
447 			DMI_MATCH(DMI_CHASSIS_VENDOR,
448 				  "MICRO-STAR INT'L CO.,LTD")
449 		},
450 		.callback = dmi_check_cb
451 	},
452 	{
453 		.ident = "MSI S271",
454 		.matches = {
455 			DMI_MATCH(DMI_SYS_VENDOR, "Micro-Star International"),
456 			DMI_MATCH(DMI_PRODUCT_NAME, "MS-1058"),
457 			DMI_MATCH(DMI_PRODUCT_VERSION, "0581"),
458 			DMI_MATCH(DMI_BOARD_NAME, "MS-1058")
459 		},
460 		.callback = dmi_check_cb
461 	},
462 	{
463 		.ident = "MSI S420",
464 		.matches = {
465 			DMI_MATCH(DMI_SYS_VENDOR, "Micro-Star International"),
466 			DMI_MATCH(DMI_PRODUCT_NAME, "MS-1412"),
467 			DMI_MATCH(DMI_BOARD_VENDOR, "MSI"),
468 			DMI_MATCH(DMI_BOARD_NAME, "MS-1412")
469 		},
470 		.callback = dmi_check_cb
471 	},
472 	{
473 		.ident = "Medion MD96100",
474 		.matches = {
475 			DMI_MATCH(DMI_SYS_VENDOR, "NOTEBOOK"),
476 			DMI_MATCH(DMI_PRODUCT_NAME, "SAM2000"),
477 			DMI_MATCH(DMI_PRODUCT_VERSION, "0131"),
478 			DMI_MATCH(DMI_CHASSIS_VENDOR,
479 				  "MICRO-STAR INT'L CO.,LTD")
480 		},
481 		.callback = dmi_check_cb
482 	},
483 	{ }
484 };
485 
486 static struct dmi_system_id __initdata msi_load_scm_models_dmi_table[] = {
487 	{
488 		.ident = "MSI N034",
489 		.matches = {
490 			DMI_MATCH(DMI_SYS_VENDOR,
491 				"MICRO-STAR INTERNATIONAL CO., LTD"),
492 			DMI_MATCH(DMI_PRODUCT_NAME, "MS-N034"),
493 			DMI_MATCH(DMI_CHASSIS_VENDOR,
494 			"MICRO-STAR INTERNATIONAL CO., LTD")
495 		},
496 		.callback = dmi_check_cb
497 	},
498 	{
499 		.ident = "MSI N051",
500 		.matches = {
501 			DMI_MATCH(DMI_SYS_VENDOR,
502 				"MICRO-STAR INTERNATIONAL CO., LTD"),
503 			DMI_MATCH(DMI_PRODUCT_NAME, "MS-N051"),
504 			DMI_MATCH(DMI_CHASSIS_VENDOR,
505 			"MICRO-STAR INTERNATIONAL CO., LTD")
506 		},
507 		.callback = dmi_check_cb
508 	},
509 	{
510 		.ident = "MSI N014",
511 		.matches = {
512 			DMI_MATCH(DMI_SYS_VENDOR,
513 				"MICRO-STAR INTERNATIONAL CO., LTD"),
514 			DMI_MATCH(DMI_PRODUCT_NAME, "MS-N014"),
515 		},
516 		.callback = dmi_check_cb
517 	},
518 	{
519 		.ident = "MSI CR620",
520 		.matches = {
521 			DMI_MATCH(DMI_SYS_VENDOR,
522 				"Micro-Star International"),
523 			DMI_MATCH(DMI_PRODUCT_NAME, "CR620"),
524 		},
525 		.callback = dmi_check_cb
526 	},
527 	{ }
528 };
529 
530 static int rfkill_bluetooth_set(void *data, bool blocked)
531 {
532 	/* Do something with blocked...*/
533 	/*
534 	 * blocked == false is on
535 	 * blocked == true is off
536 	 */
537 	if (blocked)
538 		set_device_state("0", 0, MSI_STANDARD_EC_BLUETOOTH_MASK);
539 	else
540 		set_device_state("1", 0, MSI_STANDARD_EC_BLUETOOTH_MASK);
541 
542 	return 0;
543 }
544 
545 static int rfkill_wlan_set(void *data, bool blocked)
546 {
547 	if (blocked)
548 		set_device_state("0", 0, MSI_STANDARD_EC_WLAN_MASK);
549 	else
550 		set_device_state("1", 0, MSI_STANDARD_EC_WLAN_MASK);
551 
552 	return 0;
553 }
554 
555 static int rfkill_threeg_set(void *data, bool blocked)
556 {
557 	if (blocked)
558 		set_device_state("0", 0, MSI_STANDARD_EC_3G_MASK);
559 	else
560 		set_device_state("1", 0, MSI_STANDARD_EC_3G_MASK);
561 
562 	return 0;
563 }
564 
565 static const struct rfkill_ops rfkill_bluetooth_ops = {
566 	.set_block = rfkill_bluetooth_set
567 };
568 
569 static const struct rfkill_ops rfkill_wlan_ops = {
570 	.set_block = rfkill_wlan_set
571 };
572 
573 static const struct rfkill_ops rfkill_threeg_ops = {
574 	.set_block = rfkill_threeg_set
575 };
576 
577 static void rfkill_cleanup(void)
578 {
579 	if (rfk_bluetooth) {
580 		rfkill_unregister(rfk_bluetooth);
581 		rfkill_destroy(rfk_bluetooth);
582 	}
583 
584 	if (rfk_threeg) {
585 		rfkill_unregister(rfk_threeg);
586 		rfkill_destroy(rfk_threeg);
587 	}
588 
589 	if (rfk_wlan) {
590 		rfkill_unregister(rfk_wlan);
591 		rfkill_destroy(rfk_wlan);
592 	}
593 }
594 
595 static void msi_update_rfkill(struct work_struct *ignored)
596 {
597 	get_wireless_state_ec_standard();
598 
599 	if (rfk_wlan)
600 		rfkill_set_sw_state(rfk_wlan, !wlan_s);
601 	if (rfk_bluetooth)
602 		rfkill_set_sw_state(rfk_bluetooth, !bluetooth_s);
603 	if (rfk_threeg)
604 		rfkill_set_sw_state(rfk_threeg, !threeg_s);
605 }
606 static DECLARE_DELAYED_WORK(msi_rfkill_work, msi_update_rfkill);
607 
608 static bool msi_laptop_i8042_filter(unsigned char data, unsigned char str,
609 				struct serio *port)
610 {
611 	static bool extended;
612 
613 	if (str & 0x20)
614 		return false;
615 
616 	/* 0x54 wwan, 0x62 bluetooth, 0x76 wlan*/
617 	if (unlikely(data == 0xe0)) {
618 		extended = true;
619 		return false;
620 	} else if (unlikely(extended)) {
621 		switch (data) {
622 		case 0x54:
623 		case 0x62:
624 		case 0x76:
625 			schedule_delayed_work(&msi_rfkill_work,
626 				round_jiffies_relative(0.5 * HZ));
627 			break;
628 		}
629 		extended = false;
630 	}
631 
632 	return false;
633 }
634 
635 static void msi_init_rfkill(struct work_struct *ignored)
636 {
637 	if (rfk_wlan) {
638 		rfkill_set_sw_state(rfk_wlan, !wlan_s);
639 		rfkill_wlan_set(NULL, !wlan_s);
640 	}
641 	if (rfk_bluetooth) {
642 		rfkill_set_sw_state(rfk_bluetooth, !bluetooth_s);
643 		rfkill_bluetooth_set(NULL, !bluetooth_s);
644 	}
645 	if (rfk_threeg) {
646 		rfkill_set_sw_state(rfk_threeg, !threeg_s);
647 		rfkill_threeg_set(NULL, !threeg_s);
648 	}
649 }
650 static DECLARE_DELAYED_WORK(msi_rfkill_init, msi_init_rfkill);
651 
652 static int rfkill_init(struct platform_device *sdev)
653 {
654 	/* add rfkill */
655 	int retval;
656 
657 	/* keep the hardware wireless state */
658 	get_wireless_state_ec_standard();
659 
660 	rfk_bluetooth = rfkill_alloc("msi-bluetooth", &sdev->dev,
661 				RFKILL_TYPE_BLUETOOTH,
662 				&rfkill_bluetooth_ops, NULL);
663 	if (!rfk_bluetooth) {
664 		retval = -ENOMEM;
665 		goto err_bluetooth;
666 	}
667 	retval = rfkill_register(rfk_bluetooth);
668 	if (retval)
669 		goto err_bluetooth;
670 
671 	rfk_wlan = rfkill_alloc("msi-wlan", &sdev->dev, RFKILL_TYPE_WLAN,
672 				&rfkill_wlan_ops, NULL);
673 	if (!rfk_wlan) {
674 		retval = -ENOMEM;
675 		goto err_wlan;
676 	}
677 	retval = rfkill_register(rfk_wlan);
678 	if (retval)
679 		goto err_wlan;
680 
681 	if (threeg_exists) {
682 		rfk_threeg = rfkill_alloc("msi-threeg", &sdev->dev,
683 				RFKILL_TYPE_WWAN, &rfkill_threeg_ops, NULL);
684 		if (!rfk_threeg) {
685 			retval = -ENOMEM;
686 			goto err_threeg;
687 		}
688 		retval = rfkill_register(rfk_threeg);
689 		if (retval)
690 			goto err_threeg;
691 	}
692 
693 	/* schedule to run rfkill state initial */
694 	schedule_delayed_work(&msi_rfkill_init,
695 				round_jiffies_relative(1 * HZ));
696 
697 	return 0;
698 
699 err_threeg:
700 	rfkill_destroy(rfk_threeg);
701 	if (rfk_wlan)
702 		rfkill_unregister(rfk_wlan);
703 err_wlan:
704 	rfkill_destroy(rfk_wlan);
705 	if (rfk_bluetooth)
706 		rfkill_unregister(rfk_bluetooth);
707 err_bluetooth:
708 	rfkill_destroy(rfk_bluetooth);
709 
710 	return retval;
711 }
712 
713 static int msi_laptop_resume(struct platform_device *device)
714 {
715 	u8 data;
716 	int result;
717 
718 	if (!load_scm_model)
719 		return 0;
720 
721 	/* set load SCM to disable hardware control by fn key */
722 	result = ec_read(MSI_STANDARD_EC_SCM_LOAD_ADDRESS, &data);
723 	if (result < 0)
724 		return result;
725 
726 	result = ec_write(MSI_STANDARD_EC_SCM_LOAD_ADDRESS,
727 		data | MSI_STANDARD_EC_SCM_LOAD_MASK);
728 	if (result < 0)
729 		return result;
730 
731 	return 0;
732 }
733 
734 static int load_scm_model_init(struct platform_device *sdev)
735 {
736 	u8 data;
737 	int result;
738 
739 	/* allow userland write sysfs file  */
740 	dev_attr_bluetooth.store = store_bluetooth;
741 	dev_attr_wlan.store = store_wlan;
742 	dev_attr_threeg.store = store_threeg;
743 	dev_attr_bluetooth.attr.mode |= S_IWUSR;
744 	dev_attr_wlan.attr.mode |= S_IWUSR;
745 	dev_attr_threeg.attr.mode |= S_IWUSR;
746 
747 	/* disable hardware control by fn key */
748 	result = ec_read(MSI_STANDARD_EC_SCM_LOAD_ADDRESS, &data);
749 	if (result < 0)
750 		return result;
751 
752 	result = ec_write(MSI_STANDARD_EC_SCM_LOAD_ADDRESS,
753 		data | MSI_STANDARD_EC_SCM_LOAD_MASK);
754 	if (result < 0)
755 		return result;
756 
757 	/* initial rfkill */
758 	result = rfkill_init(sdev);
759 	if (result < 0)
760 		goto fail_rfkill;
761 
762 	result = i8042_install_filter(msi_laptop_i8042_filter);
763 	if (result) {
764 		printk(KERN_ERR
765 			"msi-laptop: Unable to install key filter\n");
766 		goto fail_filter;
767 	}
768 
769 	return 0;
770 
771 fail_filter:
772 	rfkill_cleanup();
773 
774 fail_rfkill:
775 
776 	return result;
777 
778 }
779 
780 static int __init msi_init(void)
781 {
782 	int ret;
783 
784 	if (acpi_disabled)
785 		return -ENODEV;
786 
787 	if (force || dmi_check_system(msi_dmi_table))
788 		old_ec_model = 1;
789 
790 	if (!old_ec_model)
791 		get_threeg_exists();
792 
793 	if (!old_ec_model && dmi_check_system(msi_load_scm_models_dmi_table))
794 		load_scm_model = 1;
795 
796 	if (auto_brightness < 0 || auto_brightness > 2)
797 		return -EINVAL;
798 
799 	/* Register backlight stuff */
800 
801 	if (acpi_video_backlight_support()) {
802 		printk(KERN_INFO "MSI: Brightness ignored, must be controlled "
803 		       "by ACPI video driver\n");
804 	} else {
805 		struct backlight_properties props;
806 		memset(&props, 0, sizeof(struct backlight_properties));
807 		props.type = BACKLIGHT_PLATFORM;
808 		props.max_brightness = MSI_LCD_LEVEL_MAX - 1;
809 		msibl_device = backlight_device_register("msi-laptop-bl", NULL,
810 							 NULL, &msibl_ops,
811 							 &props);
812 		if (IS_ERR(msibl_device))
813 			return PTR_ERR(msibl_device);
814 	}
815 
816 	ret = platform_driver_register(&msipf_driver);
817 	if (ret)
818 		goto fail_backlight;
819 
820 	/* Register platform stuff */
821 
822 	msipf_device = platform_device_alloc("msi-laptop-pf", -1);
823 	if (!msipf_device) {
824 		ret = -ENOMEM;
825 		goto fail_platform_driver;
826 	}
827 
828 	ret = platform_device_add(msipf_device);
829 	if (ret)
830 		goto fail_platform_device1;
831 
832 	if (load_scm_model && (load_scm_model_init(msipf_device) < 0)) {
833 		ret = -EINVAL;
834 		goto fail_platform_device1;
835 	}
836 
837 	ret = sysfs_create_group(&msipf_device->dev.kobj,
838 				 &msipf_attribute_group);
839 	if (ret)
840 		goto fail_platform_device2;
841 
842 	if (!old_ec_model) {
843 		if (threeg_exists)
844 			ret = device_create_file(&msipf_device->dev,
845 						&dev_attr_threeg);
846 		if (ret)
847 			goto fail_platform_device2;
848 	}
849 
850 	/* Disable automatic brightness control by default because
851 	 * this module was probably loaded to do brightness control in
852 	 * software. */
853 
854 	if (auto_brightness != 2)
855 		set_auto_brightness(auto_brightness);
856 
857 	printk(KERN_INFO "msi-laptop: driver "MSI_DRIVER_VERSION" successfully loaded.\n");
858 
859 	return 0;
860 
861 fail_platform_device2:
862 
863 	if (load_scm_model) {
864 		i8042_remove_filter(msi_laptop_i8042_filter);
865 		cancel_delayed_work_sync(&msi_rfkill_work);
866 		rfkill_cleanup();
867 	}
868 	platform_device_del(msipf_device);
869 
870 fail_platform_device1:
871 
872 	platform_device_put(msipf_device);
873 
874 fail_platform_driver:
875 
876 	platform_driver_unregister(&msipf_driver);
877 
878 fail_backlight:
879 
880 	backlight_device_unregister(msibl_device);
881 
882 	return ret;
883 }
884 
885 static void __exit msi_cleanup(void)
886 {
887 	if (load_scm_model) {
888 		i8042_remove_filter(msi_laptop_i8042_filter);
889 		cancel_delayed_work_sync(&msi_rfkill_work);
890 		rfkill_cleanup();
891 	}
892 
893 	sysfs_remove_group(&msipf_device->dev.kobj, &msipf_attribute_group);
894 	if (!old_ec_model && threeg_exists)
895 		device_remove_file(&msipf_device->dev, &dev_attr_threeg);
896 	platform_device_unregister(msipf_device);
897 	platform_driver_unregister(&msipf_driver);
898 	backlight_device_unregister(msibl_device);
899 
900 	/* Enable automatic brightness control again */
901 	if (auto_brightness != 2)
902 		set_auto_brightness(1);
903 
904 	printk(KERN_INFO "msi-laptop: driver unloaded.\n");
905 }
906 
907 module_init(msi_init);
908 module_exit(msi_cleanup);
909 
910 MODULE_AUTHOR("Lennart Poettering");
911 MODULE_DESCRIPTION("MSI Laptop Support");
912 MODULE_VERSION(MSI_DRIVER_VERSION);
913 MODULE_LICENSE("GPL");
914 
915 MODULE_ALIAS("dmi:*:svnMICRO-STARINT'LCO.,LTD:pnMS-1013:pvr0131*:cvnMICRO-STARINT'LCO.,LTD:ct10:*");
916 MODULE_ALIAS("dmi:*:svnMicro-StarInternational:pnMS-1058:pvr0581:rvnMSI:rnMS-1058:*:ct10:*");
917 MODULE_ALIAS("dmi:*:svnMicro-StarInternational:pnMS-1412:*:rvnMSI:rnMS-1412:*:cvnMICRO-STARINT'LCO.,LTD:ct10:*");
918 MODULE_ALIAS("dmi:*:svnNOTEBOOK:pnSAM2000:pvr0131*:cvnMICRO-STARINT'LCO.,LTD:ct10:*");
919 MODULE_ALIAS("dmi:*:svnMICRO-STARINTERNATIONAL*:pnMS-N034:*");
920 MODULE_ALIAS("dmi:*:svnMICRO-STARINTERNATIONAL*:pnMS-N051:*");
921 MODULE_ALIAS("dmi:*:svnMICRO-STARINTERNATIONAL*:pnMS-N014:*");
922 MODULE_ALIAS("dmi:*:svnMicro-StarInternational*:pnCR620:*");
923