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); |
