linux/drivers/platform/x86/msi-laptop.c
<<
>>
Prefs
   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
  81static int msi_laptop_resume(struct platform_device *device);
  82
  83#define MSI_STANDARD_EC_DEVICES_EXISTS_ADDRESS  0x2f
  84
  85static int force;
  86module_param(force, bool, 0);
  87MODULE_PARM_DESC(force, "Force driver load, ignore DMI data");
  88
  89static int auto_brightness;
  90module_param(auto_brightness, int, 0);
  91MODULE_PARM_DESC(auto_brightness, "Enable automatic brightness control (0: disabled; 1: enabled; 2: don't touch)");
  92
  93static bool old_ec_model;
  94static int wlan_s, bluetooth_s, threeg_s;
  95static 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 */
 107static bool load_scm_model;
 108static struct rfkill *rfk_wlan, *rfk_bluetooth, *rfk_threeg;
 109
 110/* Hardware access */
 111
 112static 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
 126static 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
 139static 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
 152static 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
 171static 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
 200static 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
 218static 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
 236static 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
 252static int bl_get_brightness(struct backlight_device *b)
 253{
 254        return get_lcd_level();
 255}
 256
 257
 258static int bl_update_status(struct backlight_device *b)
 259{
 260        return set_lcd_level(b->props.brightness);
 261}
 262
 263static const struct backlight_ops msibl_ops = {
 264        .get_brightness = bl_get_brightness,
 265        .update_status  = bl_update_status,
 266};
 267
 268static struct backlight_device *msibl_device;
 269
 270/* Platform device */
 271
 272static 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
 290static 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
 296static 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
 314static 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
 320static 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
 337static 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
 343static 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
 356static 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
 373static 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
 386static 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
 402static DEVICE_ATTR(lcd_level, 0644, show_lcd_level, store_lcd_level);
 403static DEVICE_ATTR(auto_brightness, 0644, show_auto_brightness,
 404                   store_auto_brightness);
 405static DEVICE_ATTR(bluetooth, 0444, show_bluetooth, NULL);
 406static DEVICE_ATTR(wlan, 0444, show_wlan, NULL);
 407static DEVICE_ATTR(threeg, 0444, show_threeg, NULL);
 408
 409static 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
 417static struct attribute_group msipf_attribute_group = {
 418        .attrs = msipf_attributes
 419};
 420
 421static struct platform_driver msipf_driver = {
 422        .driver = {
 423                .name = "msi-laptop-pf",
 424                .owner = THIS_MODULE,
 425        },
 426        .resume = msi_laptop_resume,
 427};
 428
 429static struct platform_device *msipf_device;
 430
 431/* Initialization */
 432
 433static 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
 440static 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
 486static 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
 530static 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
 545static 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
 555static 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
 565static const struct rfkill_ops rfkill_bluetooth_ops = {
 566        .set_block = rfkill_bluetooth_set
 567};
 568
 569static const struct rfkill_ops rfkill_wlan_ops = {
 570        .set_block = rfkill_wlan_set
 571};
 572
 573static const struct rfkill_ops rfkill_threeg_ops = {
 574        .set_block = rfkill_threeg_set
 575};
 576
 577static 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
 595static 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}
 606static DECLARE_DELAYED_WORK(msi_rfkill_work, msi_update_rfkill);
 607
 608static 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
 635static 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}
 650static DECLARE_DELAYED_WORK(msi_rfkill_init, msi_init_rfkill);
 651
 652static 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
 699err_threeg:
 700        rfkill_destroy(rfk_threeg);
 701        if (rfk_wlan)
 702                rfkill_unregister(rfk_wlan);
 703err_wlan:
 704        rfkill_destroy(rfk_wlan);
 705        if (rfk_bluetooth)
 706                rfkill_unregister(rfk_bluetooth);
 707err_bluetooth:
 708        rfkill_destroy(rfk_bluetooth);
 709
 710        return retval;
 711}
 712
 713static 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
 734static 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
 771fail_filter:
 772        rfkill_cleanup();
 773
 774fail_rfkill:
 775
 776        return result;
 777
 778}
 779
 780static 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.max_brightness = MSI_LCD_LEVEL_MAX - 1;
 808                msibl_device = backlight_device_register("msi-laptop-bl", NULL,
 809                                                         NULL, &msibl_ops,
 810                                                         &props);
 811                if (IS_ERR(msibl_device))
 812                        return PTR_ERR(msibl_device);
 813        }
 814
 815        ret = platform_driver_register(&msipf_driver);
 816        if (ret)
 817                goto fail_backlight;
 818
 819        /* Register platform stuff */
 820
 821        msipf_device = platform_device_alloc("msi-laptop-pf", -1);
 822        if (!msipf_device) {
 823                ret = -ENOMEM;
 824                goto fail_platform_driver;
 825        }
 826
 827        ret = platform_device_add(msipf_device);
 828        if (ret)
 829                goto fail_platform_device1;
 830
 831        if (load_scm_model && (load_scm_model_init(msipf_device) < 0)) {
 832                ret = -EINVAL;
 833                goto fail_platform_device1;
 834        }
 835
 836        ret = sysfs_create_group(&msipf_device->dev.kobj,
 837                                 &msipf_attribute_group);
 838        if (ret)
 839                goto fail_platform_device2;
 840
 841        if (!old_ec_model) {
 842                if (threeg_exists)
 843                        ret = device_create_file(&msipf_device->dev,
 844                                                &dev_attr_threeg);
 845                if (ret)
 846                        goto fail_platform_device2;
 847        }
 848
 849        /* Disable automatic brightness control by default because
 850         * this module was probably loaded to do brightness control in
 851         * software. */
 852
 853        if (auto_brightness != 2)
 854                set_auto_brightness(auto_brightness);
 855
 856        printk(KERN_INFO "msi-laptop: driver "MSI_DRIVER_VERSION" successfully loaded.\n");
 857
 858        return 0;
 859
 860fail_platform_device2:
 861
 862        if (load_scm_model) {
 863                i8042_remove_filter(msi_laptop_i8042_filter);
 864                cancel_delayed_work_sync(&msi_rfkill_work);
 865                rfkill_cleanup();
 866        }
 867        platform_device_del(msipf_device);
 868
 869fail_platform_device1:
 870
 871        platform_device_put(msipf_device);
 872
 873fail_platform_driver:
 874
 875        platform_driver_unregister(&msipf_driver);
 876
 877fail_backlight:
 878
 879        backlight_device_unregister(msibl_device);
 880
 881        return ret;
 882}
 883
 884static void __exit msi_cleanup(void)
 885{
 886        if (load_scm_model) {
 887                i8042_remove_filter(msi_laptop_i8042_filter);
 888                cancel_delayed_work_sync(&msi_rfkill_work);
 889                rfkill_cleanup();
 890        }
 891
 892        sysfs_remove_group(&msipf_device->dev.kobj, &msipf_attribute_group);
 893        if (!old_ec_model && threeg_exists)
 894                device_remove_file(&msipf_device->dev, &dev_attr_threeg);
 895        platform_device_unregister(msipf_device);
 896        platform_driver_unregister(&msipf_driver);
 897        backlight_device_unregister(msibl_device);
 898
 899        /* Enable automatic brightness control again */
 900        if (auto_brightness != 2)
 901                set_auto_brightness(1);
 902
 903        printk(KERN_INFO "msi-laptop: driver unloaded.\n");
 904}
 905
 906module_init(msi_init);
 907module_exit(msi_cleanup);
 908
 909MODULE_AUTHOR("Lennart Poettering");
 910MODULE_DESCRIPTION("MSI Laptop Support");
 911MODULE_VERSION(MSI_DRIVER_VERSION);
 912MODULE_LICENSE("GPL");
 913
 914MODULE_ALIAS("dmi:*:svnMICRO-STARINT'LCO.,LTD:pnMS-1013:pvr0131*:cvnMICRO-STARINT'LCO.,LTD:ct10:*");
 915MODULE_ALIAS("dmi:*:svnMicro-StarInternational:pnMS-1058:pvr0581:rvnMSI:rnMS-1058:*:ct10:*");
 916MODULE_ALIAS("dmi:*:svnMicro-StarInternational:pnMS-1412:*:rvnMSI:rnMS-1412:*:cvnMICRO-STARINT'LCO.,LTD:ct10:*");
 917MODULE_ALIAS("dmi:*:svnNOTEBOOK:pnSAM2000:pvr0131*:cvnMICRO-STARINT'LCO.,LTD:ct10:*");
 918MODULE_ALIAS("dmi:*:svnMICRO-STARINTERNATIONAL*:pnMS-N034:*");
 919MODULE_ALIAS("dmi:*:svnMICRO-STARINTERNATIONAL*:pnMS-N051:*");
 920MODULE_ALIAS("dmi:*:svnMICRO-STARINTERNATIONAL*:pnMS-N014:*");
 921MODULE_ALIAS("dmi:*:svnMicro-StarInternational*:pnCR620:*");
 922