diff options
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/platform/x86/msi-laptop.c | 238 |
1 files changed, 238 insertions, 0 deletions
diff --git a/drivers/platform/x86/msi-laptop.c b/drivers/platform/x86/msi-laptop.c index 1784d5588d41..ff21d1acf3be 100644 --- a/drivers/platform/x86/msi-laptop.c +++ b/drivers/platform/x86/msi-laptop.c | |||
@@ -58,6 +58,7 @@ | |||
58 | #include <linux/dmi.h> | 58 | #include <linux/dmi.h> |
59 | #include <linux/backlight.h> | 59 | #include <linux/backlight.h> |
60 | #include <linux/platform_device.h> | 60 | #include <linux/platform_device.h> |
61 | #include <linux/rfkill.h> | ||
61 | 62 | ||
62 | #define MSI_DRIVER_VERSION "0.5" | 63 | #define MSI_DRIVER_VERSION "0.5" |
63 | 64 | ||
@@ -72,6 +73,10 @@ | |||
72 | #define MSI_STANDARD_EC_WLAN_MASK (1 << 3) | 73 | #define MSI_STANDARD_EC_WLAN_MASK (1 << 3) |
73 | #define MSI_STANDARD_EC_3G_MASK (1 << 4) | 74 | #define MSI_STANDARD_EC_3G_MASK (1 << 4) |
74 | 75 | ||
76 | /* For set SCM load flag to disable BIOS fn key */ | ||
77 | #define MSI_STANDARD_EC_SCM_LOAD_ADDRESS 0x2d | ||
78 | #define MSI_STANDARD_EC_SCM_LOAD_MASK (1 << 0) | ||
79 | |||
75 | static int force; | 80 | static int force; |
76 | module_param(force, bool, 0); | 81 | module_param(force, bool, 0); |
77 | MODULE_PARM_DESC(force, "Force driver load, ignore DMI data"); | 82 | MODULE_PARM_DESC(force, "Force driver load, ignore DMI data"); |
@@ -83,6 +88,19 @@ MODULE_PARM_DESC(auto_brightness, "Enable automatic brightness control (0: disab | |||
83 | static bool old_ec_model; | 88 | static bool old_ec_model; |
84 | static int wlan_s, bluetooth_s, threeg_s; | 89 | static int wlan_s, bluetooth_s, threeg_s; |
85 | 90 | ||
91 | /* Some MSI 3G netbook only have one fn key to control Wlan/Bluetooth/3G, | ||
92 | * those netbook will load the SCM (windows app) to disable the original | ||
93 | * Wlan/Bluetooth control by BIOS when user press fn key, then control | ||
94 | * Wlan/Bluetooth/3G by SCM (software control by OS). Without SCM, user | ||
95 | * cann't on/off 3G module on those 3G netbook. | ||
96 | * On Linux, msi-laptop driver will do the same thing to disable the | ||
97 | * original BIOS control, then might need use HAL or other userland | ||
98 | * application to do the software control that simulate with SCM. | ||
99 | * e.g. MSI N034 netbook | ||
100 | */ | ||
101 | static bool load_scm_model; | ||
102 | static struct rfkill *rfk_wlan, *rfk_bluetooth, *rfk_threeg; | ||
103 | |||
86 | /* Hardware access */ | 104 | /* Hardware access */ |
87 | 105 | ||
88 | static int set_lcd_level(int level) | 106 | static int set_lcd_level(int level) |
@@ -139,6 +157,35 @@ static int set_auto_brightness(int enable) | |||
139 | return ec_transaction(MSI_EC_COMMAND_LCD_LEVEL, wdata, 2, NULL, 0, 1); | 157 | return ec_transaction(MSI_EC_COMMAND_LCD_LEVEL, wdata, 2, NULL, 0, 1); |
140 | } | 158 | } |
141 | 159 | ||
160 | static ssize_t set_device_state(const char *buf, size_t count, u8 mask) | ||
161 | { | ||
162 | int status; | ||
163 | u8 wdata = 0, rdata; | ||
164 | int result; | ||
165 | |||
166 | if (sscanf(buf, "%i", &status) != 1 || (status < 0 || status > 1)) | ||
167 | return -EINVAL; | ||
168 | |||
169 | /* read current device state */ | ||
170 | result = ec_read(MSI_STANDARD_EC_COMMAND_ADDRESS, &rdata); | ||
171 | if (result < 0) | ||
172 | return -EINVAL; | ||
173 | |||
174 | if (!!(rdata & mask) != status) { | ||
175 | /* reverse device bit */ | ||
176 | if (rdata & mask) | ||
177 | wdata = rdata & ~mask; | ||
178 | else | ||
179 | wdata = rdata | mask; | ||
180 | |||
181 | result = ec_write(MSI_STANDARD_EC_COMMAND_ADDRESS, wdata); | ||
182 | if (result < 0) | ||
183 | return -EINVAL; | ||
184 | } | ||
185 | |||
186 | return count; | ||
187 | } | ||
188 | |||
142 | static int get_wireless_state(int *wlan, int *bluetooth) | 189 | static int get_wireless_state(int *wlan, int *bluetooth) |
143 | { | 190 | { |
144 | u8 wdata = 0, rdata; | 191 | u8 wdata = 0, rdata; |
@@ -215,6 +262,12 @@ static ssize_t show_wlan(struct device *dev, | |||
215 | return sprintf(buf, "%i\n", enabled); | 262 | return sprintf(buf, "%i\n", enabled); |
216 | } | 263 | } |
217 | 264 | ||
265 | static ssize_t store_wlan(struct device *dev, | ||
266 | struct device_attribute *attr, const char *buf, size_t count) | ||
267 | { | ||
268 | return set_device_state(buf, count, MSI_STANDARD_EC_WLAN_MASK); | ||
269 | } | ||
270 | |||
218 | static ssize_t show_bluetooth(struct device *dev, | 271 | static ssize_t show_bluetooth(struct device *dev, |
219 | struct device_attribute *attr, char *buf) | 272 | struct device_attribute *attr, char *buf) |
220 | { | 273 | { |
@@ -233,6 +286,12 @@ static ssize_t show_bluetooth(struct device *dev, | |||
233 | return sprintf(buf, "%i\n", enabled); | 286 | return sprintf(buf, "%i\n", enabled); |
234 | } | 287 | } |
235 | 288 | ||
289 | static ssize_t store_bluetooth(struct device *dev, | ||
290 | struct device_attribute *attr, const char *buf, size_t count) | ||
291 | { | ||
292 | return set_device_state(buf, count, MSI_STANDARD_EC_BLUETOOTH_MASK); | ||
293 | } | ||
294 | |||
236 | static ssize_t show_threeg(struct device *dev, | 295 | static ssize_t show_threeg(struct device *dev, |
237 | struct device_attribute *attr, char *buf) | 296 | struct device_attribute *attr, char *buf) |
238 | { | 297 | { |
@@ -250,6 +309,12 @@ static ssize_t show_threeg(struct device *dev, | |||
250 | return sprintf(buf, "%i\n", threeg_s); | 309 | return sprintf(buf, "%i\n", threeg_s); |
251 | } | 310 | } |
252 | 311 | ||
312 | static ssize_t store_threeg(struct device *dev, | ||
313 | struct device_attribute *attr, const char *buf, size_t count) | ||
314 | { | ||
315 | return set_device_state(buf, count, MSI_STANDARD_EC_3G_MASK); | ||
316 | } | ||
317 | |||
253 | static ssize_t show_lcd_level(struct device *dev, | 318 | static ssize_t show_lcd_level(struct device *dev, |
254 | struct device_attribute *attr, char *buf) | 319 | struct device_attribute *attr, char *buf) |
255 | { | 320 | { |
@@ -387,6 +452,169 @@ static struct dmi_system_id __initdata msi_dmi_table[] = { | |||
387 | { } | 452 | { } |
388 | }; | 453 | }; |
389 | 454 | ||
455 | static struct dmi_system_id __initdata msi_load_scm_models_dmi_table[] = { | ||
456 | { | ||
457 | .ident = "MSI N034", | ||
458 | .matches = { | ||
459 | DMI_MATCH(DMI_SYS_VENDOR, | ||
460 | "MICRO-STAR INTERNATIONAL CO., LTD"), | ||
461 | DMI_MATCH(DMI_PRODUCT_NAME, "MS-N034"), | ||
462 | DMI_MATCH(DMI_CHASSIS_VENDOR, | ||
463 | "MICRO-STAR INTERNATIONAL CO., LTD") | ||
464 | }, | ||
465 | .callback = dmi_check_cb | ||
466 | }, | ||
467 | { } | ||
468 | }; | ||
469 | |||
470 | static int rfkill_bluetooth_set(void *data, bool blocked) | ||
471 | { | ||
472 | /* Do something with blocked...*/ | ||
473 | /* | ||
474 | * blocked == false is on | ||
475 | * blocked == true is off | ||
476 | */ | ||
477 | if (blocked) | ||
478 | set_device_state("0", 0, MSI_STANDARD_EC_BLUETOOTH_MASK); | ||
479 | else | ||
480 | set_device_state("1", 0, MSI_STANDARD_EC_BLUETOOTH_MASK); | ||
481 | |||
482 | return 0; | ||
483 | } | ||
484 | |||
485 | static int rfkill_wlan_set(void *data, bool blocked) | ||
486 | { | ||
487 | if (blocked) | ||
488 | set_device_state("0", 0, MSI_STANDARD_EC_WLAN_MASK); | ||
489 | else | ||
490 | set_device_state("1", 0, MSI_STANDARD_EC_WLAN_MASK); | ||
491 | |||
492 | return 0; | ||
493 | } | ||
494 | |||
495 | static int rfkill_threeg_set(void *data, bool blocked) | ||
496 | { | ||
497 | if (blocked) | ||
498 | set_device_state("0", 0, MSI_STANDARD_EC_3G_MASK); | ||
499 | else | ||
500 | set_device_state("1", 0, MSI_STANDARD_EC_3G_MASK); | ||
501 | |||
502 | return 0; | ||
503 | } | ||
504 | |||
505 | static struct rfkill_ops rfkill_bluetooth_ops = { | ||
506 | .set_block = rfkill_bluetooth_set | ||
507 | }; | ||
508 | |||
509 | static struct rfkill_ops rfkill_wlan_ops = { | ||
510 | .set_block = rfkill_wlan_set | ||
511 | }; | ||
512 | |||
513 | static struct rfkill_ops rfkill_threeg_ops = { | ||
514 | .set_block = rfkill_threeg_set | ||
515 | }; | ||
516 | |||
517 | static void rfkill_cleanup(void) | ||
518 | { | ||
519 | if (rfk_bluetooth) { | ||
520 | rfkill_unregister(rfk_bluetooth); | ||
521 | rfkill_destroy(rfk_bluetooth); | ||
522 | } | ||
523 | |||
524 | if (rfk_threeg) { | ||
525 | rfkill_unregister(rfk_threeg); | ||
526 | rfkill_destroy(rfk_threeg); | ||
527 | } | ||
528 | |||
529 | if (rfk_wlan) { | ||
530 | rfkill_unregister(rfk_wlan); | ||
531 | rfkill_destroy(rfk_wlan); | ||
532 | } | ||
533 | } | ||
534 | |||
535 | static int rfkill_init(struct platform_device *sdev) | ||
536 | { | ||
537 | /* add rfkill */ | ||
538 | int retval; | ||
539 | |||
540 | rfk_bluetooth = rfkill_alloc("msi-bluetooth", &sdev->dev, | ||
541 | RFKILL_TYPE_BLUETOOTH, | ||
542 | &rfkill_bluetooth_ops, NULL); | ||
543 | if (!rfk_bluetooth) { | ||
544 | retval = -ENOMEM; | ||
545 | goto err_bluetooth; | ||
546 | } | ||
547 | retval = rfkill_register(rfk_bluetooth); | ||
548 | if (retval) | ||
549 | goto err_bluetooth; | ||
550 | |||
551 | rfk_wlan = rfkill_alloc("msi-wlan", &sdev->dev, RFKILL_TYPE_WLAN, | ||
552 | &rfkill_wlan_ops, NULL); | ||
553 | if (!rfk_wlan) { | ||
554 | retval = -ENOMEM; | ||
555 | goto err_wlan; | ||
556 | } | ||
557 | retval = rfkill_register(rfk_wlan); | ||
558 | if (retval) | ||
559 | goto err_wlan; | ||
560 | |||
561 | rfk_threeg = rfkill_alloc("msi-threeg", &sdev->dev, RFKILL_TYPE_WWAN, | ||
562 | &rfkill_threeg_ops, NULL); | ||
563 | if (!rfk_threeg) { | ||
564 | retval = -ENOMEM; | ||
565 | goto err_threeg; | ||
566 | } | ||
567 | retval = rfkill_register(rfk_threeg); | ||
568 | if (retval) | ||
569 | goto err_threeg; | ||
570 | |||
571 | return 0; | ||
572 | |||
573 | err_threeg: | ||
574 | rfkill_destroy(rfk_threeg); | ||
575 | if (rfk_wlan) | ||
576 | rfkill_unregister(rfk_wlan); | ||
577 | err_wlan: | ||
578 | rfkill_destroy(rfk_wlan); | ||
579 | if (rfk_bluetooth) | ||
580 | rfkill_unregister(rfk_bluetooth); | ||
581 | err_bluetooth: | ||
582 | rfkill_destroy(rfk_bluetooth); | ||
583 | |||
584 | return retval; | ||
585 | } | ||
586 | |||
587 | static int load_scm_model_init(struct platform_device *sdev) | ||
588 | { | ||
589 | u8 data; | ||
590 | int result; | ||
591 | |||
592 | /* allow userland write sysfs file */ | ||
593 | dev_attr_bluetooth.store = store_bluetooth; | ||
594 | dev_attr_wlan.store = store_wlan; | ||
595 | dev_attr_threeg.store = store_threeg; | ||
596 | dev_attr_bluetooth.attr.mode |= S_IWUSR; | ||
597 | dev_attr_wlan.attr.mode |= S_IWUSR; | ||
598 | dev_attr_threeg.attr.mode |= S_IWUSR; | ||
599 | |||
600 | /* disable hardware control by fn key */ | ||
601 | result = ec_read(MSI_STANDARD_EC_SCM_LOAD_ADDRESS, &data); | ||
602 | if (result < 0) | ||
603 | return result; | ||
604 | |||
605 | result = ec_write(MSI_STANDARD_EC_SCM_LOAD_ADDRESS, | ||
606 | data | MSI_STANDARD_EC_SCM_LOAD_MASK); | ||
607 | if (result < 0) | ||
608 | return result; | ||
609 | |||
610 | /* initial rfkill */ | ||
611 | result = rfkill_init(sdev); | ||
612 | if (result < 0) | ||
613 | return result; | ||
614 | |||
615 | return 0; | ||
616 | } | ||
617 | |||
390 | static int __init msi_init(void) | 618 | static int __init msi_init(void) |
391 | { | 619 | { |
392 | int ret; | 620 | int ret; |
@@ -397,6 +625,9 @@ static int __init msi_init(void) | |||
397 | if (force || dmi_check_system(msi_dmi_table)) | 625 | if (force || dmi_check_system(msi_dmi_table)) |
398 | old_ec_model = 1; | 626 | old_ec_model = 1; |
399 | 627 | ||
628 | if (!old_ec_model && dmi_check_system(msi_load_scm_models_dmi_table)) | ||
629 | load_scm_model = 1; | ||
630 | |||
400 | if (auto_brightness < 0 || auto_brightness > 2) | 631 | if (auto_brightness < 0 || auto_brightness > 2) |
401 | return -EINVAL; | 632 | return -EINVAL; |
402 | 633 | ||
@@ -429,6 +660,11 @@ static int __init msi_init(void) | |||
429 | if (ret) | 660 | if (ret) |
430 | goto fail_platform_device1; | 661 | goto fail_platform_device1; |
431 | 662 | ||
663 | if (load_scm_model && (load_scm_model_init(msipf_device) < 0)) { | ||
664 | ret = -EINVAL; | ||
665 | goto fail_platform_device1; | ||
666 | } | ||
667 | |||
432 | ret = sysfs_create_group(&msipf_device->dev.kobj, &msipf_attribute_group); | 668 | ret = sysfs_create_group(&msipf_device->dev.kobj, &msipf_attribute_group); |
433 | if (ret) | 669 | if (ret) |
434 | goto fail_platform_device2; | 670 | goto fail_platform_device2; |
@@ -479,6 +715,8 @@ static void __exit msi_cleanup(void) | |||
479 | platform_driver_unregister(&msipf_driver); | 715 | platform_driver_unregister(&msipf_driver); |
480 | backlight_device_unregister(msibl_device); | 716 | backlight_device_unregister(msibl_device); |
481 | 717 | ||
718 | rfkill_cleanup(); | ||
719 | |||
482 | /* Enable automatic brightness control again */ | 720 | /* Enable automatic brightness control again */ |
483 | if (auto_brightness != 2) | 721 | if (auto_brightness != 2) |
484 | set_auto_brightness(1); | 722 | set_auto_brightness(1); |