Merge branch 'for_linus' of git://cavan.codon.org.uk/platform-drivers-x86
authorLinus Torvalds <torvalds@linux-foundation.org>
Sun, 3 Mar 2013 18:16:19 +0000 (10:16 -0800)
committerLinus Torvalds <torvalds@linux-foundation.org>
Sun, 3 Mar 2013 18:16:19 +0000 (10:16 -0800)
Pull x86 platform driver updates from Matthew Garrett:
 "Mostly relatively small updates, along with some hardware enablement
  for Sony hardware and a pile of updates to Google's Chromebook driver"

* 'for_linus' of git://cavan.codon.org.uk/platform-drivers-x86: (49 commits)
  ideapad-laptop: Depend on BACKLIGHT_CLASS_DEVICE instead of selecting it
  ideapad: depends on backlight subsystem and update comment
  Platform: x86: chromeos_laptop - add i915 gmbuses to adapter names
  Platform: x86: chromeos_laptop - Add isl light sensor for Pixel
  Platform: x86: chromeos_laptop - Add a more general add_i2c_device
  Platform: x86: chromeos_laptop - Add Pixel Touchscreen
  Platform: x86: chromeos_laptop - Add support for probing devices
  Platform: x86: chromeos_laptop - Add Pixel Trackpad
  hp-wmi: fix handling of platform device
  sony-laptop: leak in error handling sony_nc_lid_resume_setup()
  hp-wmi: Add support for SMBus hotkeys
  asus-wmi: Fix unused function build warning
  acer-wmi: avoid the warning of 'devices' may be used uninitialized
  drivers/platform/x86/thinkpad_acpi.c: Handle HKEY event 0x6040
  Platform: x86: chromeos_laptop - Add HP Pavilion 14
  Platform: x86: chromeos_laptop - Add Taos tsl2583 device
  Platform: x86: chromeos_laptop - Add Taos tsl2563 device
  Platform: x86: chromeos_laptop - Add Acer C7 trackpad
  Platform: x86: chromeos_laptop - Rename setup_lumpy_tp to setup_cyapa_smbus_tp
  asus-laptop: always report brightness key events
  ...

15 files changed:
Documentation/ABI/testing/sysfs-platform-msi-laptop [new file with mode: 0644]
drivers/platform/x86/Kconfig
drivers/platform/x86/Makefile
drivers/platform/x86/acer-wmi.c
drivers/platform/x86/asus-laptop.c
drivers/platform/x86/asus-nb-wmi.c
drivers/platform/x86/asus-wmi.c
drivers/platform/x86/asus-wmi.h
drivers/platform/x86/chromeos_laptop.c [new file with mode: 0644]
drivers/platform/x86/eeepc-wmi.c
drivers/platform/x86/hp-wmi.c
drivers/platform/x86/msi-laptop.c
drivers/platform/x86/msi-wmi.c
drivers/platform/x86/sony-laptop.c
drivers/platform/x86/thinkpad_acpi.c

diff --git a/Documentation/ABI/testing/sysfs-platform-msi-laptop b/Documentation/ABI/testing/sysfs-platform-msi-laptop
new file mode 100644 (file)
index 0000000..307a247
--- /dev/null
@@ -0,0 +1,83 @@
+What:          /sys/devices/platform/msi-laptop-pf/lcd_level
+Date:          Oct 2006
+KernelVersion: 2.6.19
+Contact:       "Lennart Poettering <mzxreary@0pointer.de>"
+Description:
+               Screen brightness: contains a single integer in the range 0..8.
+
+What:          /sys/devices/platform/msi-laptop-pf/auto_brightness
+Date:          Oct 2006
+KernelVersion: 2.6.19
+Contact:       "Lennart Poettering <mzxreary@0pointer.de>"
+Description:
+               Enable automatic brightness control: contains either 0 or 1. If
+               set to 1 the hardware adjusts the screen brightness
+               automatically when the power cord is plugged/unplugged.
+
+What:          /sys/devices/platform/msi-laptop-pf/wlan
+Date:          Oct 2006
+KernelVersion: 2.6.19
+Contact:       "Lennart Poettering <mzxreary@0pointer.de>"
+Description:
+               WLAN subsystem enabled: contains either 0 or 1.
+
+What:          /sys/devices/platform/msi-laptop-pf/bluetooth
+Date:          Oct 2006
+KernelVersion: 2.6.19
+Contact:       "Lennart Poettering <mzxreary@0pointer.de>"
+Description:
+               Bluetooth subsystem enabled: contains either 0 or 1. Please
+               note that this file is constantly 0 if no Bluetooth hardware is
+               available.
+
+What:          /sys/devices/platform/msi-laptop-pf/touchpad
+Date:          Nov 2012
+KernelVersion: 3.8
+Contact:       "Maxim Mikityanskiy <maxtram95@gmail.com>"
+Description:
+               Contains either 0 or 1 and indicates if touchpad is turned on.
+               Touchpad state can only be toggled by pressing Fn+F3.
+
+What:          /sys/devices/platform/msi-laptop-pf/turbo_mode
+Date:          Nov 2012
+KernelVersion: 3.8
+Contact:       "Maxim Mikityanskiy <maxtram95@gmail.com>"
+Description:
+               Contains either 0 or 1 and indicates if turbo mode is turned
+               on. In turbo mode power LED is orange and processor is
+               overclocked. Turbo mode is available only if charging. It is
+               only possible to toggle turbo mode state by pressing Fn+F10,
+               and there is a few seconds cooldown between subsequent toggles.
+               If user presses Fn+F10 too frequent, turbo mode state is not
+               changed.
+
+What:          /sys/devices/platform/msi-laptop-pf/eco_mode
+Date:          Nov 2012
+KernelVersion: 3.8
+Contact:       "Maxim Mikityanskiy <maxtram95@gmail.com>"
+Description:
+               Contains either 0 or 1 and indicates if ECO mode is turned on.
+               In ECO mode power LED is green and userspace should do some
+               powersaving actions. ECO mode is available only on battery
+               power. ECO mode can only be toggled by pressing Fn+F10.
+
+What:          /sys/devices/platform/msi-laptop-pf/turbo_cooldown
+Date:          Nov 2012
+KernelVersion: 3.8
+Contact:       "Maxim Mikityanskiy <maxtram95@gmail.com>"
+Description:
+               Contains value in range 0..3:
+                       * 0 -> Turbo mode is off
+                       * 1 -> Turbo mode is on, cannot be turned off yet
+                       * 2 -> Turbo mode is off, cannot be turned on yet
+                       * 3 -> Turbo mode is on
+
+What:          /sys/devices/platform/msi-laptop-pf/auto_fan
+Date:          Nov 2012
+KernelVersion: 3.8
+Contact:       "Maxim Mikityanskiy <maxtram95@gmail.com>"
+Description:
+               Contains either 0 or 1 and indicates if fan speed is controlled
+               automatically (1) or fan runs at maximal speed (0). Can be
+               toggled in software.
+
index 7ab0b2f..3338437 100644 (file)
@@ -79,6 +79,17 @@ config ASUS_LAPTOP
 
          If you have an ACPI-compatible ASUS laptop, say Y or M here.
 
+config CHROMEOS_LAPTOP
+       tristate "Chrome OS Laptop"
+       depends on I2C
+       depends on DMI
+       ---help---
+         This driver instantiates i2c and smbus devices such as
+         light sensors and touchpads.
+
+         If you have a supported Chromebook, choose Y or M here.
+         The module will be called chromeos_laptop.
+
 config DELL_LAPTOP
        tristate "Dell Laptop Extras"
        depends on X86
@@ -288,9 +299,11 @@ config IDEAPAD_LAPTOP
        depends on ACPI
        depends on RFKILL && INPUT
        depends on SERIO_I8042
+       depends on BACKLIGHT_CLASS_DEVICE
        select INPUT_SPARSEKMAP
        help
-         This is a driver for the rfkill switches on Lenovo IdeaPad netbooks.
+         This is a driver for Lenovo IdeaPad netbooks contains drivers for
+         rfkill switch, hotkey, fan control and backlight control.
 
 config THINKPAD_ACPI
        tristate "ThinkPad ACPI Laptop Extras"
index bf7e4f9..ace2b38 100644 (file)
@@ -50,3 +50,4 @@ obj-$(CONFIG_INTEL_MID_POWER_BUTTON)  += intel_mid_powerbtn.o
 obj-$(CONFIG_INTEL_OAKTRAIL)   += intel_oaktrail.o
 obj-$(CONFIG_SAMSUNG_Q10)      += samsung-q10.o
 obj-$(CONFIG_APPLE_GMUX)       += apple-gmux.o
+obj-$(CONFIG_CHROMEOS_LAPTOP)  += chromeos_laptop.o
index afed701..c9076bd 100644 (file)
@@ -511,6 +511,24 @@ static struct dmi_system_id acer_quirks[] = {
                },
                .driver_data = &quirk_fujitsu_amilo_li_1718,
        },
+       {
+               .callback = dmi_matched,
+               .ident = "Lenovo Ideapad S205-10382JG",
+               .matches = {
+                       DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+                       DMI_MATCH(DMI_PRODUCT_NAME, "10382JG"),
+               },
+               .driver_data = &quirk_lenovo_ideapad_s205,
+       },
+       {
+               .callback = dmi_matched,
+               .ident = "Lenovo Ideapad S205-1038DPG",
+               .matches = {
+                       DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+                       DMI_MATCH(DMI_PRODUCT_NAME, "1038DPG"),
+               },
+               .driver_data = &quirk_lenovo_ideapad_s205,
+       },
        {}
 };
 
@@ -1204,6 +1222,9 @@ static acpi_status WMID_set_capabilities(void)
                        devices = *((u32 *) obj->buffer.pointer);
                } else if (obj->type == ACPI_TYPE_INTEGER) {
                        devices = (u32) obj->integer.value;
+               } else {
+                       kfree(out.pointer);
+                       return AE_ERROR;
                }
        } else {
                kfree(out.pointer);
index d9f9a0d..0eea09c 100644 (file)
@@ -128,10 +128,12 @@ MODULE_PARM_DESC(als_status, "Set the ALS status on boot "
 /*
  * Some events we use, same for all Asus
  */
-#define ATKD_BR_UP     0x10    /* (event & ~ATKD_BR_UP) = brightness level */
-#define ATKD_BR_DOWN   0x20    /* (event & ~ATKD_BR_DOWN) = britghness level */
-#define ATKD_BR_MIN    ATKD_BR_UP
-#define ATKD_BR_MAX    (ATKD_BR_DOWN | 0xF)    /* 0x2f */
+#define ATKD_BRNUP_MIN         0x10
+#define ATKD_BRNUP_MAX         0x1f
+#define ATKD_BRNDOWN_MIN       0x20
+#define ATKD_BRNDOWN_MAX       0x2f
+#define ATKD_BRNDOWN           0x20
+#define ATKD_BRNUP             0x2f
 #define ATKD_LCD_ON    0x33
 #define ATKD_LCD_OFF   0x34
 
@@ -301,40 +303,65 @@ static const struct key_entry asus_keymap[] = {
        {KE_KEY, 0x17, { KEY_ZOOM } },
        {KE_KEY, 0x1f, { KEY_BATTERY } },
        /* End of Lenovo SL Specific keycodes */
+       {KE_KEY, ATKD_BRNDOWN, { KEY_BRIGHTNESSDOWN } },
+       {KE_KEY, ATKD_BRNUP, { KEY_BRIGHTNESSUP } },
        {KE_KEY, 0x30, { KEY_VOLUMEUP } },
        {KE_KEY, 0x31, { KEY_VOLUMEDOWN } },
        {KE_KEY, 0x32, { KEY_MUTE } },
-       {KE_KEY, 0x33, { KEY_SWITCHVIDEOMODE } },
-       {KE_KEY, 0x34, { KEY_SWITCHVIDEOMODE } },
+       {KE_KEY, 0x33, { KEY_DISPLAYTOGGLE } }, /* LCD on */
+       {KE_KEY, 0x34, { KEY_DISPLAY_OFF } }, /* LCD off */
        {KE_KEY, 0x40, { KEY_PREVIOUSSONG } },
        {KE_KEY, 0x41, { KEY_NEXTSONG } },
-       {KE_KEY, 0x43, { KEY_STOPCD } },
+       {KE_KEY, 0x43, { KEY_STOPCD } }, /* Stop/Eject */
        {KE_KEY, 0x45, { KEY_PLAYPAUSE } },
-       {KE_KEY, 0x4c, { KEY_MEDIA } },
+       {KE_KEY, 0x4c, { KEY_MEDIA } }, /* WMP Key */
        {KE_KEY, 0x50, { KEY_EMAIL } },
        {KE_KEY, 0x51, { KEY_WWW } },
        {KE_KEY, 0x55, { KEY_CALC } },
+       {KE_IGNORE, 0x57, },  /* Battery mode */
+       {KE_IGNORE, 0x58, },  /* AC mode */
        {KE_KEY, 0x5C, { KEY_SCREENLOCK } },  /* Screenlock */
-       {KE_KEY, 0x5D, { KEY_WLAN } },
-       {KE_KEY, 0x5E, { KEY_WLAN } },
-       {KE_KEY, 0x5F, { KEY_WLAN } },
-       {KE_KEY, 0x60, { KEY_SWITCHVIDEOMODE } },
-       {KE_KEY, 0x61, { KEY_SWITCHVIDEOMODE } },
-       {KE_KEY, 0x62, { KEY_SWITCHVIDEOMODE } },
-       {KE_KEY, 0x63, { KEY_SWITCHVIDEOMODE } },
-       {KE_KEY, 0x6B, { KEY_F13 } }, /* Lock Touchpad */
+       {KE_KEY, 0x5D, { KEY_WLAN } }, /* WLAN Toggle */
+       {KE_KEY, 0x5E, { KEY_WLAN } }, /* WLAN Enable */
+       {KE_KEY, 0x5F, { KEY_WLAN } }, /* WLAN Disable */
+       {KE_KEY, 0x60, { KEY_TOUCHPAD_ON } },
+       {KE_KEY, 0x61, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD only */
+       {KE_KEY, 0x62, { KEY_SWITCHVIDEOMODE } }, /* SDSP CRT only */
+       {KE_KEY, 0x63, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + CRT */
+       {KE_KEY, 0x64, { KEY_SWITCHVIDEOMODE } }, /* SDSP TV */
+       {KE_KEY, 0x65, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + TV */
+       {KE_KEY, 0x66, { KEY_SWITCHVIDEOMODE } }, /* SDSP CRT + TV */
+       {KE_KEY, 0x67, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + CRT + TV */
+       {KE_KEY, 0x6B, { KEY_TOUCHPAD_TOGGLE } }, /* Lock Touchpad */
        {KE_KEY, 0x6C, { KEY_SLEEP } }, /* Suspend */
        {KE_KEY, 0x6D, { KEY_SLEEP } }, /* Hibernate */
-       {KE_KEY, 0x7E, { KEY_BLUETOOTH } },
-       {KE_KEY, 0x7D, { KEY_BLUETOOTH } },
+       {KE_IGNORE, 0x6E, },  /* Low Battery notification */
+       {KE_KEY, 0x7D, { KEY_BLUETOOTH } }, /* Bluetooth Enable */
+       {KE_KEY, 0x7E, { KEY_BLUETOOTH } }, /* Bluetooth Disable */
        {KE_KEY, 0x82, { KEY_CAMERA } },
-       {KE_KEY, 0x88, { KEY_WLAN  } },
-       {KE_KEY, 0x8A, { KEY_PROG1 } },
+       {KE_KEY, 0x88, { KEY_RFKILL  } }, /* Radio Toggle Key */
+       {KE_KEY, 0x8A, { KEY_PROG1 } }, /* Color enhancement mode */
+       {KE_KEY, 0x8C, { KEY_SWITCHVIDEOMODE } }, /* SDSP DVI only */
+       {KE_KEY, 0x8D, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + DVI */
+       {KE_KEY, 0x8E, { KEY_SWITCHVIDEOMODE } }, /* SDSP CRT + DVI */
+       {KE_KEY, 0x8F, { KEY_SWITCHVIDEOMODE } }, /* SDSP TV + DVI */
+       {KE_KEY, 0x90, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + CRT + DVI */
+       {KE_KEY, 0x91, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + TV + DVI */
+       {KE_KEY, 0x92, { KEY_SWITCHVIDEOMODE } }, /* SDSP CRT + TV + DVI */
+       {KE_KEY, 0x93, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + CRT + TV + DVI */
        {KE_KEY, 0x95, { KEY_MEDIA } },
        {KE_KEY, 0x99, { KEY_PHONE } },
-       {KE_KEY, 0xc4, { KEY_KBDILLUMUP } },
-       {KE_KEY, 0xc5, { KEY_KBDILLUMDOWN } },
-       {KE_KEY, 0xb5, { KEY_CALC } },
+       {KE_KEY, 0xA0, { KEY_SWITCHVIDEOMODE } }, /* SDSP HDMI only */
+       {KE_KEY, 0xA1, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + HDMI */
+       {KE_KEY, 0xA2, { KEY_SWITCHVIDEOMODE } }, /* SDSP CRT + HDMI */
+       {KE_KEY, 0xA3, { KEY_SWITCHVIDEOMODE } }, /* SDSP TV + HDMI */
+       {KE_KEY, 0xA4, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + CRT + HDMI */
+       {KE_KEY, 0xA5, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + TV + HDMI */
+       {KE_KEY, 0xA6, { KEY_SWITCHVIDEOMODE } }, /* SDSP CRT + TV + HDMI */
+       {KE_KEY, 0xA7, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + CRT + TV + HDMI */
+       {KE_KEY, 0xB5, { KEY_CALC } },
+       {KE_KEY, 0xC4, { KEY_KBDILLUMUP } },
+       {KE_KEY, 0xC5, { KEY_KBDILLUMDOWN } },
        {KE_END, 0},
 };
 
@@ -1521,15 +1548,19 @@ static void asus_acpi_notify(struct acpi_device *device, u32 event)
                                        dev_name(&asus->device->dev), event,
                                        count);
 
-       /* Brightness events are special */
-       if (event >= ATKD_BR_MIN && event <= ATKD_BR_MAX) {
+       if (event >= ATKD_BRNUP_MIN && event <= ATKD_BRNUP_MAX)
+               event = ATKD_BRNUP;
+       else if (event >= ATKD_BRNDOWN_MIN &&
+                event <= ATKD_BRNDOWN_MAX)
+               event = ATKD_BRNDOWN;
 
-               /* Ignore them completely if the acpi video driver is used */
+       /* Brightness events are special */
+       if (event == ATKD_BRNDOWN || event == ATKD_BRNUP) {
                if (asus->backlight_device != NULL) {
                        /* Update the backlight device. */
                        asus_backlight_notify(asus);
+                       return ;
                }
-               return ;
        }
 
        /* Accelerometer "coarse orientation change" event */
index be79040..210b5b8 100644 (file)
@@ -59,6 +59,17 @@ static struct quirk_entry quirk_asus_unknown = {
        .wapf = 0,
 };
 
+/*
+ * For those machines that need software to control bt/wifi status
+ * and can't adjust brightness through ACPI interface
+ * and have duplicate events(ACPI and WMI) for display toggle
+ */
+static struct quirk_entry quirk_asus_x55u = {
+       .wapf = 4,
+       .wmi_backlight_power = true,
+       .no_display_toggle = true,
+};
+
 static struct quirk_entry quirk_asus_x401u = {
        .wapf = 4,
 };
@@ -77,6 +88,15 @@ static struct dmi_system_id asus_quirks[] = {
                        DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."),
                        DMI_MATCH(DMI_PRODUCT_NAME, "X401U"),
                },
+               .driver_data = &quirk_asus_x55u,
+       },
+       {
+               .callback = dmi_matched,
+               .ident = "ASUSTeK COMPUTER INC. X401A",
+               .matches = {
+                       DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."),
+                       DMI_MATCH(DMI_PRODUCT_NAME, "X401A"),
+               },
                .driver_data = &quirk_asus_x401u,
        },
        {
@@ -95,6 +115,15 @@ static struct dmi_system_id asus_quirks[] = {
                        DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."),
                        DMI_MATCH(DMI_PRODUCT_NAME, "X501U"),
                },
+               .driver_data = &quirk_asus_x55u,
+       },
+       {
+               .callback = dmi_matched,
+               .ident = "ASUSTeK COMPUTER INC. X501A",
+               .matches = {
+                       DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."),
+                       DMI_MATCH(DMI_PRODUCT_NAME, "X501A"),
+               },
                .driver_data = &quirk_asus_x401u,
        },
        {
@@ -131,7 +160,7 @@ static struct dmi_system_id asus_quirks[] = {
                        DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."),
                        DMI_MATCH(DMI_PRODUCT_NAME, "X55U"),
                },
-               .driver_data = &quirk_asus_x401u,
+               .driver_data = &quirk_asus_x55u,
        },
        {
                .callback = dmi_matched,
@@ -161,6 +190,8 @@ static void asus_nb_wmi_quirks(struct asus_wmi_driver *driver)
 }
 
 static const struct key_entry asus_nb_wmi_keymap[] = {
+       { KE_KEY, ASUS_WMI_BRN_DOWN, { KEY_BRIGHTNESSDOWN } },
+       { KE_KEY, ASUS_WMI_BRN_UP, { KEY_BRIGHTNESSUP } },
        { KE_KEY, 0x30, { KEY_VOLUMEUP } },
        { KE_KEY, 0x31, { KEY_VOLUMEDOWN } },
        { KE_KEY, 0x32, { KEY_MUTE } },
@@ -168,9 +199,9 @@ static const struct key_entry asus_nb_wmi_keymap[] = {
        { KE_KEY, 0x34, { KEY_DISPLAY_OFF } }, /* LCD off */
        { KE_KEY, 0x40, { KEY_PREVIOUSSONG } },
        { KE_KEY, 0x41, { KEY_NEXTSONG } },
-       { KE_KEY, 0x43, { KEY_STOPCD } },
+       { KE_KEY, 0x43, { KEY_STOPCD } }, /* Stop/Eject */
        { KE_KEY, 0x45, { KEY_PLAYPAUSE } },
-       { KE_KEY, 0x4c, { KEY_MEDIA } },
+       { KE_KEY, 0x4c, { KEY_MEDIA } }, /* WMP Key */
        { KE_KEY, 0x50, { KEY_EMAIL } },
        { KE_KEY, 0x51, { KEY_WWW } },
        { KE_KEY, 0x55, { KEY_CALC } },
@@ -180,25 +211,42 @@ static const struct key_entry asus_nb_wmi_keymap[] = {
        { KE_KEY, 0x5D, { KEY_WLAN } }, /* Wireless console Toggle */
        { KE_KEY, 0x5E, { KEY_WLAN } }, /* Wireless console Enable */
        { KE_KEY, 0x5F, { KEY_WLAN } }, /* Wireless console Disable */
-       { KE_KEY, 0x60, { KEY_SWITCHVIDEOMODE } },
-       { KE_KEY, 0x61, { KEY_SWITCHVIDEOMODE } },
-       { KE_KEY, 0x62, { KEY_SWITCHVIDEOMODE } },
-       { KE_KEY, 0x63, { KEY_SWITCHVIDEOMODE } },
+       { KE_KEY, 0x60, { KEY_TOUCHPAD_ON } },
+       { KE_KEY, 0x61, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD only */
+       { KE_KEY, 0x62, { KEY_SWITCHVIDEOMODE } }, /* SDSP CRT only */
+       { KE_KEY, 0x63, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + CRT */
+       { KE_KEY, 0x64, { KEY_SWITCHVIDEOMODE } }, /* SDSP TV */
+       { KE_KEY, 0x65, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + TV */
+       { KE_KEY, 0x66, { KEY_SWITCHVIDEOMODE } }, /* SDSP CRT + TV */
+       { KE_KEY, 0x67, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + CRT + TV */
        { KE_KEY, 0x6B, { KEY_TOUCHPAD_TOGGLE } },
-       { KE_KEY, 0x7D, { KEY_BLUETOOTH } },
-       { KE_KEY, 0x7E, { KEY_BLUETOOTH } },
+       { KE_IGNORE, 0x6E, },  /* Low Battery notification */
+       { KE_KEY, 0x7D, { KEY_BLUETOOTH } }, /* Bluetooth Enable */
+       { KE_KEY, 0x7E, { KEY_BLUETOOTH } }, /* Bluetooth Disable */
        { KE_KEY, 0x82, { KEY_CAMERA } },
-       { KE_KEY, 0x88, { KEY_RFKILL  } },
-       { KE_KEY, 0x8A, { KEY_PROG1 } },
+       { KE_KEY, 0x88, { KEY_RFKILL  } }, /* Radio Toggle Key */
+       { KE_KEY, 0x8A, { KEY_PROG1 } }, /* Color enhancement mode */
+       { KE_KEY, 0x8C, { KEY_SWITCHVIDEOMODE } }, /* SDSP DVI only */
+       { KE_KEY, 0x8D, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + DVI */
+       { KE_KEY, 0x8E, { KEY_SWITCHVIDEOMODE } }, /* SDSP CRT + DVI */
+       { KE_KEY, 0x8F, { KEY_SWITCHVIDEOMODE } }, /* SDSP TV + DVI */
+       { KE_KEY, 0x90, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + CRT + DVI */
+       { KE_KEY, 0x91, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + TV + DVI */
+       { KE_KEY, 0x92, { KEY_SWITCHVIDEOMODE } }, /* SDSP CRT + TV + DVI */
+       { KE_KEY, 0x93, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + CRT + TV + DVI */
        { KE_KEY, 0x95, { KEY_MEDIA } },
        { KE_KEY, 0x99, { KEY_PHONE } },
        { KE_KEY, 0xA0, { KEY_SWITCHVIDEOMODE } }, /* SDSP HDMI only */
        { KE_KEY, 0xA1, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + HDMI */
        { KE_KEY, 0xA2, { KEY_SWITCHVIDEOMODE } }, /* SDSP CRT + HDMI */
        { KE_KEY, 0xA3, { KEY_SWITCHVIDEOMODE } }, /* SDSP TV + HDMI */
-       { KE_KEY, 0xb5, { KEY_CALC } },
-       { KE_KEY, 0xc4, { KEY_KBDILLUMUP } },
-       { KE_KEY, 0xc5, { KEY_KBDILLUMDOWN } },
+       { KE_KEY, 0xA4, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + CRT + HDMI */
+       { KE_KEY, 0xA5, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + TV + HDMI */
+       { KE_KEY, 0xA6, { KEY_SWITCHVIDEOMODE } }, /* SDSP CRT + TV + HDMI */
+       { KE_KEY, 0xA7, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + CRT + TV + HDMI */
+       { KE_KEY, 0xB5, { KEY_CALC } },
+       { KE_KEY, 0xC4, { KEY_KBDILLUMUP } },
+       { KE_KEY, 0xC5, { KEY_KBDILLUMDOWN } },
        { KE_END, 0},
 };
 
index f80ae4d..c11b242 100644 (file)
@@ -187,6 +187,8 @@ struct asus_wmi {
        struct device *hwmon_device;
        struct platform_device *platform_device;
 
+       struct led_classdev wlan_led;
+       int wlan_led_wk;
        struct led_classdev tpd_led;
        int tpd_led_wk;
        struct led_classdev kbd_led;
@@ -194,6 +196,7 @@ struct asus_wmi {
        struct workqueue_struct *led_workqueue;
        struct work_struct tpd_led_work;
        struct work_struct kbd_led_work;
+       struct work_struct wlan_led_work;
 
        struct asus_rfkill wlan;
        struct asus_rfkill bluetooth;
@@ -456,12 +459,65 @@ static enum led_brightness kbd_led_get(struct led_classdev *led_cdev)
        return value;
 }
 
+static int wlan_led_unknown_state(struct asus_wmi *asus)
+{
+       u32 result;
+
+       asus_wmi_get_devstate(asus, ASUS_WMI_DEVID_WIRELESS_LED, &result);
+
+       return result & ASUS_WMI_DSTS_UNKNOWN_BIT;
+}
+
+static int wlan_led_presence(struct asus_wmi *asus)
+{
+       u32 result;
+
+       asus_wmi_get_devstate(asus, ASUS_WMI_DEVID_WIRELESS_LED, &result);
+
+       return result & ASUS_WMI_DSTS_PRESENCE_BIT;
+}
+
+static void wlan_led_update(struct work_struct *work)
+{
+       int ctrl_param;
+       struct asus_wmi *asus;
+
+       asus = container_of(work, struct asus_wmi, wlan_led_work);
+
+       ctrl_param = asus->wlan_led_wk;
+       asus_wmi_set_devstate(ASUS_WMI_DEVID_WIRELESS_LED, ctrl_param, NULL);
+}
+
+static void wlan_led_set(struct led_classdev *led_cdev,
+                        enum led_brightness value)
+{
+       struct asus_wmi *asus;
+
+       asus = container_of(led_cdev, struct asus_wmi, wlan_led);
+
+       asus->wlan_led_wk = !!value;
+       queue_work(asus->led_workqueue, &asus->wlan_led_work);
+}
+
+static enum led_brightness wlan_led_get(struct led_classdev *led_cdev)
+{
+       struct asus_wmi *asus;
+       u32 result;
+
+       asus = container_of(led_cdev, struct asus_wmi, wlan_led);
+       asus_wmi_get_devstate(asus, ASUS_WMI_DEVID_WIRELESS_LED, &result);
+
+       return result & ASUS_WMI_DSTS_BRIGHTNESS_MASK;
+}
+
 static void asus_wmi_led_exit(struct asus_wmi *asus)
 {
        if (!IS_ERR_OR_NULL(asus->kbd_led.dev))
                led_classdev_unregister(&asus->kbd_led);
        if (!IS_ERR_OR_NULL(asus->tpd_led.dev))
                led_classdev_unregister(&asus->tpd_led);
+       if (!IS_ERR_OR_NULL(asus->wlan_led.dev))
+               led_classdev_unregister(&asus->wlan_led);
        if (asus->led_workqueue)
                destroy_workqueue(asus->led_workqueue);
 }
@@ -498,6 +554,23 @@ static int asus_wmi_led_init(struct asus_wmi *asus)
 
                rv = led_classdev_register(&asus->platform_device->dev,
                                           &asus->kbd_led);
+               if (rv)
+                       goto error;
+       }
+
+       if (wlan_led_presence(asus)) {
+               INIT_WORK(&asus->wlan_led_work, wlan_led_update);
+
+               asus->wlan_led.name = "asus::wlan";
+               asus->wlan_led.brightness_set = wlan_led_set;
+               if (!wlan_led_unknown_state(asus))
+                       asus->wlan_led.brightness_get = wlan_led_get;
+               asus->wlan_led.flags = LED_CORE_SUSPENDRESUME;
+               asus->wlan_led.max_brightness = 1;
+               asus->wlan_led.default_trigger = "asus-wlan";
+
+               rv = led_classdev_register(&asus->platform_device->dev,
+                                          &asus->wlan_led);
        }
 
 error:
@@ -813,6 +886,9 @@ static int asus_new_rfkill(struct asus_wmi *asus,
        if (!*rfkill)
                return -EINVAL;
 
+       if (dev_id == ASUS_WMI_DEVID_WLAN)
+               rfkill_set_led_trigger_name(*rfkill, "asus-wlan");
+
        rfkill_init_sw_state(*rfkill, !result);
        result = rfkill_register(*rfkill);
        if (result) {
@@ -1265,6 +1341,18 @@ static void asus_wmi_backlight_exit(struct asus_wmi *asus)
        asus->backlight_device = NULL;
 }
 
+static int is_display_toggle(int code)
+{
+       /* display toggle keys */
+       if ((code >= 0x61 && code <= 0x67) ||
+           (code >= 0x8c && code <= 0x93) ||
+           (code >= 0xa0 && code <= 0xa7) ||
+           (code >= 0xd0 && code <= 0xd5))
+               return 1;
+
+       return 0;
+}
+
 static void asus_wmi_notify(u32 value, void *context)
 {
        struct asus_wmi *asus = context;
@@ -1298,16 +1386,24 @@ static void asus_wmi_notify(u32 value, void *context)
        }
 
        if (code >= NOTIFY_BRNUP_MIN && code <= NOTIFY_BRNUP_MAX)
-               code = NOTIFY_BRNUP_MIN;
+               code = ASUS_WMI_BRN_UP;
        else if (code >= NOTIFY_BRNDOWN_MIN &&
                 code <= NOTIFY_BRNDOWN_MAX)
-               code = NOTIFY_BRNDOWN_MIN;
+               code = ASUS_WMI_BRN_DOWN;
 
-       if (code == NOTIFY_BRNUP_MIN || code == NOTIFY_BRNDOWN_MIN) {
-               if (!acpi_video_backlight_support())
+       if (code == ASUS_WMI_BRN_DOWN || code == ASUS_WMI_BRN_UP) {
+               if (!acpi_video_backlight_support()) {
                        asus_wmi_backlight_notify(asus, orig_code);
-       } else if (!sparse_keymap_report_event(asus->inputdev, code,
-                                              key_value, autorelease))
+                       goto exit;
+               }
+       }
+
+       if (is_display_toggle(code) &&
+           asus->driver->quirks->no_display_toggle)
+               goto exit;
+
+       if (!sparse_keymap_report_event(asus->inputdev, code,
+                                       key_value, autorelease))
                pr_info("Unknown key %x pressed\n", code);
 
 exit:
index 4c9bd38..4da4c8b 100644 (file)
@@ -30,6 +30,8 @@
 #include <linux/platform_device.h>
 
 #define ASUS_WMI_KEY_IGNORE (-1)
+#define ASUS_WMI_BRN_DOWN      0x20
+#define ASUS_WMI_BRN_UP                0x2f
 
 struct module;
 struct key_entry;
@@ -41,6 +43,13 @@ struct quirk_entry {
        bool store_backlight_power;
        bool wmi_backlight_power;
        int wapf;
+       /*
+        * For machines with AMD graphic chips, it will send out WMI event
+        * and ACPI interrupt at the same time while hitting the hotkey.
+        * To simplify the problem, we just have to ignore the WMI event,
+        * and let the ACPI interrupt to send out the key event.
+        */
+       int no_display_toggle;
 };
 
 struct asus_wmi_driver {
diff --git a/drivers/platform/x86/chromeos_laptop.c b/drivers/platform/x86/chromeos_laptop.c
new file mode 100644 (file)
index 0000000..93d6680
--- /dev/null
@@ -0,0 +1,371 @@
+/*
+ *  chromeos_laptop.c - Driver to instantiate Chromebook i2c/smbus devices.
+ *
+ *  Author : Benson Leung <bleung@chromium.org>
+ *
+ *  Copyright (C) 2012 Google, Inc.
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ */
+
+#include <linux/dmi.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+
+#define ATMEL_TP_I2C_ADDR      0x4b
+#define ATMEL_TP_I2C_BL_ADDR   0x25
+#define ATMEL_TS_I2C_ADDR      0x4a
+#define ATMEL_TS_I2C_BL_ADDR   0x26
+#define CYAPA_TP_I2C_ADDR      0x67
+#define ISL_ALS_I2C_ADDR       0x44
+#define TAOS_ALS_I2C_ADDR      0x29
+
+static struct i2c_client *als;
+static struct i2c_client *tp;
+static struct i2c_client *ts;
+
+const char *i2c_adapter_names[] = {
+       "SMBus I801 adapter",
+       "i915 gmbus vga",
+       "i915 gmbus panel",
+};
+
+/* Keep this enum consistent with i2c_adapter_names */
+enum i2c_adapter_type {
+       I2C_ADAPTER_SMBUS = 0,
+       I2C_ADAPTER_VGADDC,
+       I2C_ADAPTER_PANEL,
+};
+
+static struct i2c_board_info __initdata cyapa_device = {
+       I2C_BOARD_INFO("cyapa", CYAPA_TP_I2C_ADDR),
+       .flags          = I2C_CLIENT_WAKE,
+};
+
+static struct i2c_board_info __initdata isl_als_device = {
+       I2C_BOARD_INFO("isl29018", ISL_ALS_I2C_ADDR),
+};
+
+static struct i2c_board_info __initdata tsl2583_als_device = {
+       I2C_BOARD_INFO("tsl2583", TAOS_ALS_I2C_ADDR),
+};
+
+static struct i2c_board_info __initdata tsl2563_als_device = {
+       I2C_BOARD_INFO("tsl2563", TAOS_ALS_I2C_ADDR),
+};
+
+static struct i2c_board_info __initdata atmel_224s_tp_device = {
+       I2C_BOARD_INFO("atmel_mxt_tp", ATMEL_TP_I2C_ADDR),
+       .platform_data = NULL,
+       .flags          = I2C_CLIENT_WAKE,
+};
+
+static struct i2c_board_info __initdata atmel_1664s_device = {
+       I2C_BOARD_INFO("atmel_mxt_ts", ATMEL_TS_I2C_ADDR),
+       .platform_data = NULL,
+       .flags          = I2C_CLIENT_WAKE,
+};
+
+static struct i2c_client __init *__add_probed_i2c_device(
+               const char *name,
+               int bus,
+               struct i2c_board_info *info,
+               const unsigned short *addrs)
+{
+       const struct dmi_device *dmi_dev;
+       const struct dmi_dev_onboard *dev_data;
+       struct i2c_adapter *adapter;
+       struct i2c_client *client;
+
+       if (bus < 0)
+               return NULL;
+       /*
+        * If a name is specified, look for irq platform information stashed
+        * in DMI_DEV_TYPE_DEV_ONBOARD by the Chrome OS custom system firmware.
+        */
+       if (name) {
+               dmi_dev = dmi_find_device(DMI_DEV_TYPE_DEV_ONBOARD, name, NULL);
+               if (!dmi_dev) {
+                       pr_err("%s failed to dmi find device %s.\n",
+                              __func__,
+                              name);
+                       return NULL;
+               }
+               dev_data = (struct dmi_dev_onboard *)dmi_dev->device_data;
+               if (!dev_data) {
+                       pr_err("%s failed to get data from dmi for %s.\n",
+                              __func__, name);
+                       return NULL;
+               }
+               info->irq = dev_data->instance;
+       }
+
+       adapter = i2c_get_adapter(bus);
+       if (!adapter) {
+               pr_err("%s failed to get i2c adapter %d.\n", __func__, bus);
+               return NULL;
+       }
+
+       /* add the i2c device */
+       client = i2c_new_probed_device(adapter, info, addrs, NULL);
+       if (!client)
+               pr_err("%s failed to register device %d-%02x\n",
+                      __func__, bus, info->addr);
+       else
+               pr_debug("%s added i2c device %d-%02x\n",
+                        __func__, bus, info->addr);
+
+       i2c_put_adapter(adapter);
+       return client;
+}
+
+static int __init __find_i2c_adap(struct device *dev, void *data)
+{
+       const char *name = data;
+       static const char *prefix = "i2c-";
+       struct i2c_adapter *adapter;
+       if (strncmp(dev_name(dev), prefix, strlen(prefix)) != 0)
+               return 0;
+       adapter = to_i2c_adapter(dev);
+       return (strncmp(adapter->name, name, strlen(name)) == 0);
+}
+
+static int __init find_i2c_adapter_num(enum i2c_adapter_type type)
+{
+       struct device *dev = NULL;
+       struct i2c_adapter *adapter;
+       const char *name = i2c_adapter_names[type];
+       /* find the adapter by name */
+       dev = bus_find_device(&i2c_bus_type, NULL, (void *)name,
+                             __find_i2c_adap);
+       if (!dev) {
+               pr_err("%s: i2c adapter %s not found on system.\n", __func__,
+                      name);
+               return -ENODEV;
+       }
+       adapter = to_i2c_adapter(dev);
+       return adapter->nr;
+}
+
+/*
+ * Takes a list of addresses in addrs as such :
+ * { addr1, ... , addrn, I2C_CLIENT_END };
+ * add_probed_i2c_device will use i2c_new_probed_device
+ * and probe for devices at all of the addresses listed.
+ * Returns NULL if no devices found.
+ * See Documentation/i2c/instantiating-devices for more information.
+ */
+static __init struct i2c_client *add_probed_i2c_device(
+               const char *name,
+               enum i2c_adapter_type type,
+               struct i2c_board_info *info,
+               const unsigned short *addrs)
+{
+       return __add_probed_i2c_device(name,
+                                      find_i2c_adapter_num(type),
+                                      info,
+                                      addrs);
+}
+
+/*
+ * Probes for a device at a single address, the one provided by
+ * info->addr.
+ * Returns NULL if no device found.
+ */
+static __init struct i2c_client *add_i2c_device(const char *name,
+                                               enum i2c_adapter_type type,
+                                               struct i2c_board_info *info)
+{
+       const unsigned short addr_list[] = { info->addr, I2C_CLIENT_END };
+       return __add_probed_i2c_device(name,
+                                      find_i2c_adapter_num(type),
+                                      info,
+                                      addr_list);
+}
+
+
+static struct i2c_client __init *add_smbus_device(const char *name,
+                                                 struct i2c_board_info *info)
+{
+       return add_i2c_device(name, I2C_ADAPTER_SMBUS, info);
+}
+
+static int __init setup_cyapa_smbus_tp(const struct dmi_system_id *id)
+{
+       /* add cyapa touchpad on smbus */
+       tp = add_smbus_device("trackpad", &cyapa_device);
+       return 0;
+}
+
+static int __init setup_atmel_224s_tp(const struct dmi_system_id *id)
+{
+       const unsigned short addr_list[] = { ATMEL_TP_I2C_BL_ADDR,
+                                            ATMEL_TP_I2C_ADDR,
+                                            I2C_CLIENT_END };
+
+       /* add atmel mxt touchpad on VGA DDC GMBus */
+       tp = add_probed_i2c_device("trackpad", I2C_ADAPTER_VGADDC,
+                                  &atmel_224s_tp_device, addr_list);
+       return 0;
+}
+
+static int __init setup_atmel_1664s_ts(const struct dmi_system_id *id)
+{
+       const unsigned short addr_list[] = { ATMEL_TS_I2C_BL_ADDR,
+                                            ATMEL_TS_I2C_ADDR,
+                                            I2C_CLIENT_END };
+
+       /* add atmel mxt touch device on PANEL GMBus */
+       ts = add_probed_i2c_device("touchscreen", I2C_ADAPTER_PANEL,
+                                  &atmel_1664s_device, addr_list);
+       return 0;
+}
+
+
+static int __init setup_isl29018_als(const struct dmi_system_id *id)
+{
+       /* add isl29018 light sensor */
+       als = add_smbus_device("lightsensor", &isl_als_device);
+       return 0;
+}
+
+static int __init setup_isl29023_als(const struct dmi_system_id *id)
+{
+       /* add isl29023 light sensor on Panel GMBus */
+       als = add_i2c_device("lightsensor", I2C_ADAPTER_PANEL,
+                            &isl_als_device);
+       return 0;
+}
+
+static int __init setup_tsl2583_als(const struct dmi_system_id *id)
+{
+       /* add tsl2583 light sensor on smbus */
+       als = add_smbus_device(NULL, &tsl2583_als_device);
+       return 0;
+}
+
+static int __init setup_tsl2563_als(const struct dmi_system_id *id)
+{
+       /* add tsl2563 light sensor on smbus */
+       als = add_smbus_device(NULL, &tsl2563_als_device);
+       return 0;
+}
+
+static struct dmi_system_id __initdata chromeos_laptop_dmi_table[] = {
+       {
+               .ident = "Samsung Series 5 550 - Touchpad",
+               .matches = {
+                       DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG"),
+                       DMI_MATCH(DMI_PRODUCT_NAME, "Lumpy"),
+               },
+               .callback = setup_cyapa_smbus_tp,
+       },
+       {
+               .ident = "Chromebook Pixel - Touchscreen",
+               .matches = {
+                       DMI_MATCH(DMI_SYS_VENDOR, "GOOGLE"),
+                       DMI_MATCH(DMI_PRODUCT_NAME, "Link"),
+               },
+               .callback = setup_atmel_1664s_ts,
+       },
+       {
+               .ident = "Chromebook Pixel - Touchpad",
+               .matches = {
+                       DMI_MATCH(DMI_SYS_VENDOR, "GOOGLE"),
+                       DMI_MATCH(DMI_PRODUCT_NAME, "Link"),
+               },
+               .callback = setup_atmel_224s_tp,
+       },
+       {
+               .ident = "Samsung Series 5 550 - Light Sensor",
+               .matches = {
+                       DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG"),
+                       DMI_MATCH(DMI_PRODUCT_NAME, "Lumpy"),
+               },
+               .callback = setup_isl29018_als,
+       },
+       {
+               .ident = "Chromebook Pixel - Light Sensor",
+               .matches = {
+                       DMI_MATCH(DMI_SYS_VENDOR, "GOOGLE"),
+                       DMI_MATCH(DMI_PRODUCT_NAME, "Link"),
+               },
+               .callback = setup_isl29023_als,
+       },
+       {
+               .ident = "Acer C7 Chromebook - Touchpad",
+               .matches = {
+                       DMI_MATCH(DMI_PRODUCT_NAME, "Parrot"),
+               },
+               .callback = setup_cyapa_smbus_tp,
+       },
+       {
+               .ident = "HP Pavilion 14 Chromebook - Touchpad",
+               .matches = {
+                       DMI_MATCH(DMI_PRODUCT_NAME, "Butterfly"),
+               },
+               .callback = setup_cyapa_smbus_tp,
+       },
+       {
+               .ident = "Samsung Series 5 - Light Sensor",
+               .matches = {
+                       DMI_MATCH(DMI_PRODUCT_NAME, "Alex"),
+               },
+               .callback = setup_tsl2583_als,
+       },
+       {
+               .ident = "Cr-48 - Light Sensor",
+               .matches = {
+                       DMI_MATCH(DMI_PRODUCT_NAME, "Mario"),
+               },
+               .callback = setup_tsl2563_als,
+       },
+       {
+               .ident = "Acer AC700 - Light Sensor",
+               .matches = {
+                       DMI_MATCH(DMI_PRODUCT_NAME, "ZGB"),
+               },
+               .callback = setup_tsl2563_als,
+       },
+       { }
+};
+MODULE_DEVICE_TABLE(dmi, chromeos_laptop_dmi_table);
+
+static int __init chromeos_laptop_init(void)
+{
+       if (!dmi_check_system(chromeos_laptop_dmi_table)) {
+               pr_debug("%s unsupported system.\n", __func__);
+               return -ENODEV;
+       }
+       return 0;
+}
+
+static void __exit chromeos_laptop_exit(void)
+{
+       if (als)
+               i2c_unregister_device(als);
+       if (tp)
+               i2c_unregister_device(tp);
+       if (ts)
+               i2c_unregister_device(ts);
+}
+
+module_init(chromeos_laptop_init);
+module_exit(chromeos_laptop_exit);
+
+MODULE_DESCRIPTION("Chrome OS Laptop driver");
+MODULE_AUTHOR("Benson Leung <bleung@chromium.org>");
+MODULE_LICENSE("GPL");
index 60cb76a..af67e6e 100644 (file)
@@ -63,6 +63,8 @@ MODULE_PARM_DESC(hotplug_wireless,
 #define HOME_RELEASE   0xe5
 
 static const struct key_entry eeepc_wmi_keymap[] = {
+       { KE_KEY, ASUS_WMI_BRN_DOWN, { KEY_BRIGHTNESSDOWN } },
+       { KE_KEY, ASUS_WMI_BRN_UP, { KEY_BRIGHTNESSUP } },
        /* Sleep already handled via generic ACPI code */
        { KE_KEY, 0x30, { KEY_VOLUMEUP } },
        { KE_KEY, 0x31, { KEY_VOLUMEDOWN } },
index 1dde7ac..45cacf7 100644 (file)
@@ -60,6 +60,7 @@ enum hp_wmi_radio {
        HPWMI_WIFI = 0,
        HPWMI_BLUETOOTH = 1,
        HPWMI_WWAN = 2,
+       HPWMI_GPS = 3,
 };
 
 enum hp_wmi_event_ids {
@@ -72,10 +73,6 @@ enum hp_wmi_event_ids {
        HPWMI_LOCK_SWITCH = 7,
 };
 
-static int hp_wmi_bios_setup(struct platform_device *device);
-static int __exit hp_wmi_bios_remove(struct platform_device *device);
-static int hp_wmi_resume_handler(struct device *device);
-
 struct bios_args {
        u32 signature;
        u32 command;
@@ -137,6 +134,7 @@ static const struct key_entry hp_wmi_keymap[] = {
        { KE_KEY, 0x2142, { KEY_MEDIA } },
        { KE_KEY, 0x213b, { KEY_INFO } },
        { KE_KEY, 0x2169, { KEY_DIRECTION } },
+       { KE_KEY, 0x216a, { KEY_SETUP } },
        { KE_KEY, 0x231b, { KEY_HELP } },
        { KE_END, 0 }
 };
@@ -147,6 +145,7 @@ static struct platform_device *hp_wmi_platform_dev;
 static struct rfkill *wifi_rfkill;
 static struct rfkill *bluetooth_rfkill;
 static struct rfkill *wwan_rfkill;
+static struct rfkill *gps_rfkill;
 
 struct rfkill2_device {
        u8 id;
@@ -157,21 +156,6 @@ struct rfkill2_device {
 static int rfkill2_count;
 static struct rfkill2_device rfkill2[HPWMI_MAX_RFKILL2_DEVICES];
 
-static const struct dev_pm_ops hp_wmi_pm_ops = {
-       .resume  = hp_wmi_resume_handler,
-       .restore  = hp_wmi_resume_handler,
-};
-
-static struct platform_driver hp_wmi_driver = {
-       .driver = {
-               .name = "hp-wmi",
-               .owner = THIS_MODULE,
-               .pm = &hp_wmi_pm_ops,
-       },
-       .probe = hp_wmi_bios_setup,
-       .remove = hp_wmi_bios_remove,
-};
-
 /*
  * hp_wmi_perform_query
  *
@@ -543,6 +527,10 @@ static void hp_wmi_notify(u32 value, void *context)
                        rfkill_set_states(wwan_rfkill,
                                          hp_wmi_get_sw_state(HPWMI_WWAN),
                                          hp_wmi_get_hw_state(HPWMI_WWAN));
+               if (gps_rfkill)
+                       rfkill_set_states(gps_rfkill,
+                                         hp_wmi_get_sw_state(HPWMI_GPS),
+                                         hp_wmi_get_hw_state(HPWMI_GPS));
                break;
        case HPWMI_CPU_BATTERY_THROTTLE:
                pr_info("Unimplemented CPU throttle because of 3 Cell battery event detected\n");
@@ -670,7 +658,7 @@ static int hp_wmi_rfkill_setup(struct platform_device *device)
                                           (void *) HPWMI_WWAN);
                if (!wwan_rfkill) {
                        err = -ENOMEM;
-                       goto register_bluetooth_error;
+                       goto register_gps_error;
                }
                rfkill_init_sw_state(wwan_rfkill,
                                     hp_wmi_get_sw_state(HPWMI_WWAN));
@@ -681,10 +669,33 @@ static int hp_wmi_rfkill_setup(struct platform_device *device)
                        goto register_wwan_err;
        }
 
+       if (wireless & 0x8) {
+               gps_rfkill = rfkill_alloc("hp-gps", &device->dev,
+                                               RFKILL_TYPE_GPS,
+                                               &hp_wmi_rfkill_ops,
+                                               (void *) HPWMI_GPS);
+               if (!gps_rfkill) {
+                       err = -ENOMEM;
+                       goto register_bluetooth_error;
+               }
+               rfkill_init_sw_state(gps_rfkill,
+                                    hp_wmi_get_sw_state(HPWMI_GPS));
+               rfkill_set_hw_state(bluetooth_rfkill,
+                                   hp_wmi_get_hw_state(HPWMI_GPS));
+               err = rfkill_register(gps_rfkill);
+               if (err)
+                       goto register_gps_error;
+       }
+
        return 0;
 register_wwan_err:
        rfkill_destroy(wwan_rfkill);
        wwan_rfkill = NULL;
+       if (gps_rfkill)
+               rfkill_unregister(gps_rfkill);
+register_gps_error:
+       rfkill_destroy(gps_rfkill);
+       gps_rfkill = NULL;
        if (bluetooth_rfkill)
                rfkill_unregister(bluetooth_rfkill);
 register_bluetooth_error:
@@ -729,6 +740,10 @@ static int hp_wmi_rfkill2_setup(struct platform_device *device)
                        type = RFKILL_TYPE_WWAN;
                        name = "hp-wwan";
                        break;
+               case HPWMI_GPS:
+                       type = RFKILL_TYPE_GPS;
+                       name = "hp-gps";
+                       break;
                default:
                        pr_warn("unknown device type 0x%x\n",
                                state.device[i].radio_type);
@@ -778,7 +793,7 @@ fail:
        return err;
 }
 
-static int hp_wmi_bios_setup(struct platform_device *device)
+static int __init hp_wmi_bios_setup(struct platform_device *device)
 {
        int err;
 
@@ -786,6 +801,7 @@ static int hp_wmi_bios_setup(struct platform_device *device)
        wifi_rfkill = NULL;
        bluetooth_rfkill = NULL;
        wwan_rfkill = NULL;
+       gps_rfkill = NULL;
        rfkill2_count = 0;
 
        if (hp_wmi_rfkill_setup(device))
@@ -835,6 +851,10 @@ static int __exit hp_wmi_bios_remove(struct platform_device *device)
                rfkill_unregister(wwan_rfkill);
                rfkill_destroy(wwan_rfkill);
        }
+       if (gps_rfkill) {
+               rfkill_unregister(gps_rfkill);
+               rfkill_destroy(gps_rfkill);
+       }
 
        return 0;
 }
@@ -870,51 +890,70 @@ static int hp_wmi_resume_handler(struct device *device)
                rfkill_set_states(wwan_rfkill,
                                  hp_wmi_get_sw_state(HPWMI_WWAN),
                                  hp_wmi_get_hw_state(HPWMI_WWAN));
+       if (gps_rfkill)
+               rfkill_set_states(gps_rfkill,
+                                 hp_wmi_get_sw_state(HPWMI_GPS),
+                                 hp_wmi_get_hw_state(HPWMI_GPS));
 
        return 0;
 }
 
+static const struct dev_pm_ops hp_wmi_pm_ops = {
+       .resume  = hp_wmi_resume_handler,
+       .restore  = hp_wmi_resume_handler,
+};
+
+static struct platform_driver hp_wmi_driver = {
+       .driver = {
+               .name = "hp-wmi",
+               .owner = THIS_MODULE,
+               .pm = &hp_wmi_pm_ops,
+       },
+       .remove = __exit_p(hp_wmi_bios_remove),
+};
+
 static int __init hp_wmi_init(void)
 {
        int err;
        int event_capable = wmi_has_guid(HPWMI_EVENT_GUID);
        int bios_capable = wmi_has_guid(HPWMI_BIOS_GUID);
 
+       if (!bios_capable && !event_capable)
+               return -ENODEV;
+
        if (event_capable) {
                err = hp_wmi_input_setup();
                if (err)
                        return err;
+               
+               //Enable magic for hotkeys that run on the SMBus
+               ec_write(0xe6,0x6e);
        }
 
        if (bios_capable) {
-               err = platform_driver_register(&hp_wmi_driver);
-               if (err)
-                       goto err_driver_reg;
-               hp_wmi_platform_dev = platform_device_alloc("hp-wmi", -1);
-               if (!hp_wmi_platform_dev) {
-                       err = -ENOMEM;
-                       goto err_device_alloc;
+               hp_wmi_platform_dev =
+                       platform_device_register_simple("hp-wmi", -1, NULL, 0);
+               if (IS_ERR(hp_wmi_platform_dev)) {
+                       err = PTR_ERR(hp_wmi_platform_dev);
+                       goto err_destroy_input;
                }
-               err = platform_device_add(hp_wmi_platform_dev);
+
+               err = platform_driver_probe(&hp_wmi_driver, hp_wmi_bios_setup);
                if (err)
-                       goto err_device_add;
+                       goto err_unregister_device;
        }
 
-       if (!bios_capable && !event_capable)
-               return -ENODEV;
-
        return 0;
 
-err_device_add:
-       platform_device_put(hp_wmi_platform_dev);
-err_device_alloc:
-       platform_driver_unregister(&hp_wmi_driver);
-err_driver_reg:
+err_unregister_device:
+       platform_device_unregister(hp_wmi_platform_dev);
+err_destroy_input:
        if (event_capable)
                hp_wmi_input_destroy();
 
        return err;
 }
+module_init(hp_wmi_init);
 
 static void __exit hp_wmi_exit(void)
 {
@@ -926,6 +965,4 @@ static void __exit hp_wmi_exit(void)
                platform_driver_unregister(&hp_wmi_driver);
        }
 }
-
-module_init(hp_wmi_init);
 module_exit(hp_wmi_exit);
index 2111dbb..6b22938 100644 (file)
 #define MSI_STANDARD_EC_SCM_LOAD_ADDRESS       0x2d
 #define MSI_STANDARD_EC_SCM_LOAD_MASK          (1 << 0)
 
-#define MSI_STANDARD_EC_TOUCHPAD_ADDRESS       0xe4
+#define MSI_STANDARD_EC_FUNCTIONS_ADDRESS      0xe4
+/* Power LED is orange - Turbo mode */
+#define MSI_STANDARD_EC_TURBO_MASK             (1 << 1)
+/* Power LED is green - ECO mode */
+#define MSI_STANDARD_EC_ECO_MASK               (1 << 3)
+/* Touchpad is turned on */
 #define MSI_STANDARD_EC_TOUCHPAD_MASK          (1 << 4)
+/* If this bit != bit 1, turbo mode can't be toggled */
+#define MSI_STANDARD_EC_TURBO_COOLDOWN_MASK    (1 << 7)
+
+#define MSI_STANDARD_EC_FAN_ADDRESS            0x33
+/* If zero, fan rotates at maximal speed */
+#define MSI_STANDARD_EC_AUTOFAN_MASK           (1 << 0)
 
 #ifdef CONFIG_PM_SLEEP
 static int msi_laptop_resume(struct device *device);
@@ -108,23 +119,38 @@ static const struct key_entry msi_laptop_keymap[] = {
 
 static struct input_dev *msi_laptop_input_dev;
 
-static bool old_ec_model;
 static int wlan_s, bluetooth_s, threeg_s;
 static int threeg_exists;
-
-/* Some MSI 3G netbook only have one fn key to control Wlan/Bluetooth/3G,
- * those netbook will load the SCM (windows app) to disable the original
- * Wlan/Bluetooth control by BIOS when user press fn key, then control
- * Wlan/Bluetooth/3G by SCM (software control by OS). Without SCM, user
- * cann't on/off 3G module on those 3G netbook.
- * On Linux, msi-laptop driver will do the same thing to disable the
- * original BIOS control, then might need use HAL or other userland
- * application to do the software control that simulate with SCM.
- * e.g. MSI N034 netbook
- */
-static bool load_scm_model;
 static struct rfkill *rfk_wlan, *rfk_bluetooth, *rfk_threeg;
 
+/* MSI laptop quirks */
+struct quirk_entry {
+       bool old_ec_model;
+
+       /* Some MSI 3G netbook only have one fn key to control
+        * Wlan/Bluetooth/3G, those netbook will load the SCM (windows app) to
+        * disable the original Wlan/Bluetooth control by BIOS when user press
+        * fn key, then control Wlan/Bluetooth/3G by SCM (software control by
+        * OS). Without SCM, user cann't on/off 3G module on those 3G netbook.
+        * On Linux, msi-laptop driver will do the same thing to disable the
+        * original BIOS control, then might need use HAL or other userland
+        * application to do the software control that simulate with SCM.
+        * e.g. MSI N034 netbook
+        */
+       bool load_scm_model;
+
+       /* Some MSI laptops need delay before reading from EC */
+       bool ec_delay;
+
+       /* Some MSI Wind netbooks (e.g. MSI Wind U100) need loading SCM to get
+        * some features working (e.g. ECO mode), but we cannot change
+        * Wlan/Bluetooth state in software and we can only read its state.
+        */
+       bool ec_read_only;
+};
+
+static struct quirk_entry *quirks;
+
 /* Hardware access */
 
 static int set_lcd_level(int level)
@@ -195,10 +221,13 @@ static ssize_t set_device_state(const char *buf, size_t count, u8 mask)
        if (sscanf(buf, "%i", &status) != 1 || (status < 0 || status > 1))
                return -EINVAL;
 
+       if (quirks->ec_read_only)
+               return -EOPNOTSUPP;
+
        /* read current device state */
        result = ec_read(MSI_STANDARD_EC_COMMAND_ADDRESS, &rdata);
        if (result < 0)
-               return -EINVAL;
+               return result;
 
        if (!!(rdata & mask) != status) {
                /* reverse device bit */
@@ -209,7 +238,7 @@ static ssize_t set_device_state(const char *buf, size_t count, u8 mask)
 
                result = ec_write(MSI_STANDARD_EC_COMMAND_ADDRESS, wdata);
                if (result < 0)
-                       return -EINVAL;
+                       return result;
        }
 
        return count;
@@ -222,7 +251,7 @@ static int get_wireless_state(int *wlan, int *bluetooth)
 
        result = ec_transaction(MSI_EC_COMMAND_WIRELESS, &wdata, 1, &rdata, 1);
        if (result < 0)
-               return -1;
+               return result;
 
        if (wlan)
                *wlan = !!(rdata & 8);
@@ -240,7 +269,7 @@ static int get_wireless_state_ec_standard(void)
 
        result = ec_read(MSI_STANDARD_EC_COMMAND_ADDRESS, &rdata);
        if (result < 0)
-               return -1;
+               return result;
 
        wlan_s = !!(rdata & MSI_STANDARD_EC_WLAN_MASK);
 
@@ -258,7 +287,7 @@ static int get_threeg_exists(void)
 
        result = ec_read(MSI_STANDARD_EC_DEVICES_EXISTS_ADDRESS, &rdata);
        if (result < 0)
-               return -1;
+               return result;
 
        threeg_exists = !!(rdata & MSI_STANDARD_EC_3G_MASK);
 
@@ -291,9 +320,9 @@ static ssize_t show_wlan(struct device *dev,
        struct device_attribute *attr, char *buf)
 {
 
-       int ret, enabled;
+       int ret, enabled = 0;
 
-       if (old_ec_model) {
+       if (quirks->old_ec_model) {
                ret = get_wireless_state(&enabled, NULL);
        } else {
                ret = get_wireless_state_ec_standard();
@@ -315,9 +344,9 @@ static ssize_t show_bluetooth(struct device *dev,
        struct device_attribute *attr, char *buf)
 {
 
-       int ret, enabled;
+       int ret, enabled = 0;
 
-       if (old_ec_model) {
+       if (quirks->old_ec_model) {
                ret = get_wireless_state(NULL, &enabled);
        } else {
                ret = get_wireless_state_ec_standard();
@@ -342,8 +371,8 @@ static ssize_t show_threeg(struct device *dev,
        int ret;
 
        /* old msi ec not support 3G */
-       if (old_ec_model)
-               return -1;
+       if (quirks->old_ec_model)
+               return -ENODEV;
 
        ret = get_wireless_state_ec_standard();
        if (ret < 0)
@@ -417,18 +446,119 @@ static ssize_t store_auto_brightness(struct device *dev,
        return count;
 }
 
+static ssize_t show_touchpad(struct device *dev,
+       struct device_attribute *attr, char *buf)
+{
+
+       u8 rdata;
+       int result;
+
+       result = ec_read(MSI_STANDARD_EC_FUNCTIONS_ADDRESS, &rdata);
+       if (result < 0)
+               return result;
+
+       return sprintf(buf, "%i\n", !!(rdata & MSI_STANDARD_EC_TOUCHPAD_MASK));
+}
+
+static ssize_t show_turbo(struct device *dev,
+       struct device_attribute *attr, char *buf)
+{
+
+       u8 rdata;
+       int result;
+
+       result = ec_read(MSI_STANDARD_EC_FUNCTIONS_ADDRESS, &rdata);
+       if (result < 0)
+               return result;
+
+       return sprintf(buf, "%i\n", !!(rdata & MSI_STANDARD_EC_TURBO_MASK));
+}
+
+static ssize_t show_eco(struct device *dev,
+       struct device_attribute *attr, char *buf)
+{
+
+       u8 rdata;
+       int result;
+
+       result = ec_read(MSI_STANDARD_EC_FUNCTIONS_ADDRESS, &rdata);
+       if (result < 0)
+               return result;
+
+       return sprintf(buf, "%i\n", !!(rdata & MSI_STANDARD_EC_ECO_MASK));
+}
+
+static ssize_t show_turbo_cooldown(struct device *dev,
+       struct device_attribute *attr, char *buf)
+{
+
+       u8 rdata;
+       int result;
+
+       result = ec_read(MSI_STANDARD_EC_FUNCTIONS_ADDRESS, &rdata);
+       if (result < 0)
+               return result;
+
+       return sprintf(buf, "%i\n", (!!(rdata & MSI_STANDARD_EC_TURBO_MASK)) |
+               (!!(rdata & MSI_STANDARD_EC_TURBO_COOLDOWN_MASK) << 1));
+}
+
+static ssize_t show_auto_fan(struct device *dev,
+       struct device_attribute *attr, char *buf)
+{
+
+       u8 rdata;
+       int result;
+
+       result = ec_read(MSI_STANDARD_EC_FAN_ADDRESS, &rdata);
+       if (result < 0)
+               return result;
+
+       return sprintf(buf, "%i\n", !!(rdata & MSI_STANDARD_EC_AUTOFAN_MASK));
+}
+
+static ssize_t store_auto_fan(struct device *dev,
+       struct device_attribute *attr, const char *buf, size_t count)
+{
+
+       int enable, result;
+
+       if (sscanf(buf, "%i", &enable) != 1 || (enable != (enable & 1)))
+               return -EINVAL;
+
+       result = ec_write(MSI_STANDARD_EC_FAN_ADDRESS, enable);
+       if (result < 0)
+               return result;
+
+       return count;
+}
+
 static DEVICE_ATTR(lcd_level, 0644, show_lcd_level, store_lcd_level);
 static DEVICE_ATTR(auto_brightness, 0644, show_auto_brightness,
                   store_auto_brightness);
 static DEVICE_ATTR(bluetooth, 0444, show_bluetooth, NULL);
 static DEVICE_ATTR(wlan, 0444, show_wlan, NULL);
 static DEVICE_ATTR(threeg, 0444, show_threeg, NULL);
+static DEVICE_ATTR(touchpad, 0444, show_touchpad, NULL);
+static DEVICE_ATTR(turbo_mode, 0444, show_turbo, NULL);
+static DEVICE_ATTR(eco_mode, 0444, show_eco, NULL);
+static DEVICE_ATTR(turbo_cooldown, 0444, show_turbo_cooldown, NULL);
+static DEVICE_ATTR(auto_fan, 0644, show_auto_fan, store_auto_fan);
 
 static struct attribute *msipf_attributes[] = {
-       &dev_attr_lcd_level.attr,
-       &dev_attr_auto_brightness.attr,
        &dev_attr_bluetooth.attr,
        &dev_attr_wlan.attr,
+       &dev_attr_touchpad.attr,
+       &dev_attr_turbo_mode.attr,
+       &dev_attr_eco_mode.attr,
+       &dev_attr_turbo_cooldown.attr,
+       &dev_attr_auto_fan.attr,
+       NULL
+};
+
+static struct attribute *msipf_old_attributes[] = {
+       &dev_attr_lcd_level.attr,
+       &dev_attr_auto_brightness.attr,
        NULL
 };
 
@@ -436,6 +566,10 @@ static struct attribute_group msipf_attribute_group = {
        .attrs = msipf_attributes
 };
 
+static struct attribute_group msipf_old_attribute_group = {
+       .attrs = msipf_old_attributes
+};
+
 static struct platform_driver msipf_driver = {
        .driver = {
                .name = "msi-laptop-pf",
@@ -448,9 +582,26 @@ static struct platform_device *msipf_device;
 
 /* Initialization */
 
-static int dmi_check_cb(const struct dmi_system_id *id)
+static struct quirk_entry quirk_old_ec_model = {
+       .old_ec_model = true,
+};
+
+static struct quirk_entry quirk_load_scm_model = {
+       .load_scm_model = true,
+       .ec_delay = true,
+};
+
+static struct quirk_entry quirk_load_scm_ro_model = {
+       .load_scm_model = true,
+       .ec_read_only = true,
+};
+
+static int dmi_check_cb(const struct dmi_system_id *dmi)
 {
-       pr_info("Identified laptop model '%s'\n", id->ident);
+       pr_info("Identified laptop model '%s'\n", dmi->ident);
+
+       quirks = dmi->driver_data;
+
        return 1;
 }
 
@@ -464,6 +615,7 @@ static struct dmi_system_id __initdata msi_dmi_table[] = {
                        DMI_MATCH(DMI_CHASSIS_VENDOR,
                                  "MICRO-STAR INT'L CO.,LTD")
                },
+               .driver_data = &quirk_old_ec_model,
                .callback = dmi_check_cb
        },
        {
@@ -474,6 +626,7 @@ static struct dmi_system_id __initdata msi_dmi_table[] = {
                        DMI_MATCH(DMI_PRODUCT_VERSION, "0581"),
                        DMI_MATCH(DMI_BOARD_NAME, "MS-1058")
                },
+               .driver_data = &quirk_old_ec_model,
                .callback = dmi_check_cb
        },
        {
@@ -484,6 +637,7 @@ static struct dmi_system_id __initdata msi_dmi_table[] = {
                        DMI_MATCH(DMI_BOARD_VENDOR, "MSI"),
                        DMI_MATCH(DMI_BOARD_NAME, "MS-1412")
                },
+               .driver_data = &quirk_old_ec_model,
                .callback = dmi_check_cb
        },
        {
@@ -495,12 +649,9 @@ static struct dmi_system_id __initdata msi_dmi_table[] = {
                        DMI_MATCH(DMI_CHASSIS_VENDOR,
                                  "MICRO-STAR INT'L CO.,LTD")
                },
+               .driver_data = &quirk_old_ec_model,
                .callback = dmi_check_cb
        },
-       { }
-};
-
-static struct dmi_system_id __initdata msi_load_scm_models_dmi_table[] = {
        {
                .ident = "MSI N034",
                .matches = {
@@ -510,6 +661,7 @@ static struct dmi_system_id __initdata msi_load_scm_models_dmi_table[] = {
                        DMI_MATCH(DMI_CHASSIS_VENDOR,
                        "MICRO-STAR INTERNATIONAL CO., LTD")
                },
+               .driver_data = &quirk_load_scm_model,
                .callback = dmi_check_cb
        },
        {
@@ -521,6 +673,7 @@ static struct dmi_system_id __initdata msi_load_scm_models_dmi_table[] = {
                        DMI_MATCH(DMI_CHASSIS_VENDOR,
                        "MICRO-STAR INTERNATIONAL CO., LTD")
                },
+               .driver_data = &quirk_load_scm_model,
                .callback = dmi_check_cb
        },
        {
@@ -530,6 +683,7 @@ static struct dmi_system_id __initdata msi_load_scm_models_dmi_table[] = {
                                "MICRO-STAR INTERNATIONAL CO., LTD"),
                        DMI_MATCH(DMI_PRODUCT_NAME, "MS-N014"),
                },
+               .driver_data = &quirk_load_scm_model,
                .callback = dmi_check_cb
        },
        {
@@ -539,6 +693,7 @@ static struct dmi_system_id __initdata msi_load_scm_models_dmi_table[] = {
                                "Micro-Star International"),
                        DMI_MATCH(DMI_PRODUCT_NAME, "CR620"),
                },
+               .driver_data = &quirk_load_scm_model,
                .callback = dmi_check_cb
        },
        {
@@ -548,6 +703,17 @@ static struct dmi_system_id __initdata msi_load_scm_models_dmi_table[] = {
                                "Micro-Star International Co., Ltd."),
                        DMI_MATCH(DMI_PRODUCT_NAME, "U270 series"),
                },
+               .driver_data = &quirk_load_scm_model,
+               .callback = dmi_check_cb
+       },
+       {
+               .ident = "MSI U90/U100",
+               .matches = {
+                       DMI_MATCH(DMI_SYS_VENDOR,
+                               "MICRO-STAR INTERNATIONAL CO., LTD"),
+                       DMI_MATCH(DMI_PRODUCT_NAME, "U90/U100"),
+               },
+               .driver_data = &quirk_load_scm_ro_model,
                .callback = dmi_check_cb
        },
        { }
@@ -560,32 +726,26 @@ static int rfkill_bluetooth_set(void *data, bool blocked)
         * blocked == false is on
         * blocked == true is off
         */
-       if (blocked)
-               set_device_state("0", 0, MSI_STANDARD_EC_BLUETOOTH_MASK);
-       else
-               set_device_state("1", 0, MSI_STANDARD_EC_BLUETOOTH_MASK);
+       int result = set_device_state(blocked ? "0" : "1", 0,
+                       MSI_STANDARD_EC_BLUETOOTH_MASK);
 
-       return 0;
+       return min(result, 0);
 }
 
 static int rfkill_wlan_set(void *data, bool blocked)
 {
-       if (blocked)
-               set_device_state("0", 0, MSI_STANDARD_EC_WLAN_MASK);
-       else
-               set_device_state("1", 0, MSI_STANDARD_EC_WLAN_MASK);
+       int result = set_device_state(blocked ? "0" : "1", 0,
+                       MSI_STANDARD_EC_WLAN_MASK);
 
-       return 0;
+       return min(result, 0);
 }
 
 static int rfkill_threeg_set(void *data, bool blocked)
 {
-       if (blocked)
-               set_device_state("0", 0, MSI_STANDARD_EC_3G_MASK);
-       else
-               set_device_state("1", 0, MSI_STANDARD_EC_3G_MASK);
+       int result = set_device_state(blocked ? "0" : "1", 0,
+                       MSI_STANDARD_EC_3G_MASK);
 
-       return 0;
+       return min(result, 0);
 }
 
 static const struct rfkill_ops rfkill_bluetooth_ops = {
@@ -618,25 +778,34 @@ static void rfkill_cleanup(void)
        }
 }
 
+static bool msi_rfkill_set_state(struct rfkill *rfkill, bool blocked)
+{
+       if (quirks->ec_read_only)
+               return rfkill_set_hw_state(rfkill, blocked);
+       else
+               return rfkill_set_sw_state(rfkill, blocked);
+}
+
 static void msi_update_rfkill(struct work_struct *ignored)
 {
        get_wireless_state_ec_standard();
 
        if (rfk_wlan)
-               rfkill_set_sw_state(rfk_wlan, !wlan_s);
+               msi_rfkill_set_state(rfk_wlan, !wlan_s);
        if (rfk_bluetooth)
-               rfkill_set_sw_state(rfk_bluetooth, !bluetooth_s);
+               msi_rfkill_set_state(rfk_bluetooth, !bluetooth_s);
        if (rfk_threeg)
-               rfkill_set_sw_state(rfk_threeg, !threeg_s);
+               msi_rfkill_set_state(rfk_threeg, !threeg_s);
 }
-static DECLARE_DELAYED_WORK(msi_rfkill_work, msi_update_rfkill);
+static DECLARE_DELAYED_WORK(msi_rfkill_dwork, msi_update_rfkill);
+static DECLARE_WORK(msi_rfkill_work, msi_update_rfkill);
 
 static void msi_send_touchpad_key(struct work_struct *ignored)
 {
        u8 rdata;
        int result;
 
-       result = ec_read(MSI_STANDARD_EC_TOUCHPAD_ADDRESS, &rdata);
+       result = ec_read(MSI_STANDARD_EC_FUNCTIONS_ADDRESS, &rdata);
        if (result < 0)
                return;
 
@@ -644,7 +813,8 @@ static void msi_send_touchpad_key(struct work_struct *ignored)
                (rdata & MSI_STANDARD_EC_TOUCHPAD_MASK) ?
                KEY_TOUCHPAD_ON : KEY_TOUCHPAD_OFF, 1, true);
 }
-static DECLARE_DELAYED_WORK(msi_touchpad_work, msi_send_touchpad_key);
+static DECLARE_DELAYED_WORK(msi_touchpad_dwork, msi_send_touchpad_key);
+static DECLARE_WORK(msi_touchpad_work, msi_send_touchpad_key);
 
 static bool msi_laptop_i8042_filter(unsigned char data, unsigned char str,
                                struct serio *port)
@@ -662,14 +832,20 @@ static bool msi_laptop_i8042_filter(unsigned char data, unsigned char str,
                extended = false;
                switch (data) {
                case 0xE4:
-                       schedule_delayed_work(&msi_touchpad_work,
-                               round_jiffies_relative(0.5 * HZ));
+                       if (quirks->ec_delay) {
+                               schedule_delayed_work(&msi_touchpad_dwork,
+                                       round_jiffies_relative(0.5 * HZ));
+                       } else
+                               schedule_work(&msi_touchpad_work);
                        break;
                case 0x54:
                case 0x62:
                case 0x76:
-                       schedule_delayed_work(&msi_rfkill_work,
-                               round_jiffies_relative(0.5 * HZ));
+                       if (quirks->ec_delay) {
+                               schedule_delayed_work(&msi_rfkill_dwork,
+                                       round_jiffies_relative(0.5 * HZ));
+                       } else
+                               schedule_work(&msi_rfkill_work);
                        break;
                }
        }
@@ -736,8 +912,11 @@ static int rfkill_init(struct platform_device *sdev)
        }
 
        /* schedule to run rfkill state initial */
-       schedule_delayed_work(&msi_rfkill_init,
-                               round_jiffies_relative(1 * HZ));
+       if (quirks->ec_delay) {
+               schedule_delayed_work(&msi_rfkill_init,
+                       round_jiffies_relative(1 * HZ));
+       } else
+               schedule_work(&msi_rfkill_work);
 
        return 0;
 
@@ -761,7 +940,7 @@ static int msi_laptop_resume(struct device *device)
        u8 data;
        int result;
 
-       if (!load_scm_model)
+       if (!quirks->load_scm_model)
                return 0;
 
        /* set load SCM to disable hardware control by fn key */
@@ -819,13 +998,15 @@ static int __init load_scm_model_init(struct platform_device *sdev)
        u8 data;
        int result;
 
-       /* allow userland write sysfs file  */
-       dev_attr_bluetooth.store = store_bluetooth;
-       dev_attr_wlan.store = store_wlan;
-       dev_attr_threeg.store = store_threeg;
-       dev_attr_bluetooth.attr.mode |= S_IWUSR;
-       dev_attr_wlan.attr.mode |= S_IWUSR;
-       dev_attr_threeg.attr.mode |= S_IWUSR;
+       if (!quirks->ec_read_only) {
+               /* allow userland write sysfs file  */
+               dev_attr_bluetooth.store = store_bluetooth;
+               dev_attr_wlan.store = store_wlan;
+               dev_attr_threeg.store = store_threeg;
+               dev_attr_bluetooth.attr.mode |= S_IWUSR;
+               dev_attr_wlan.attr.mode |= S_IWUSR;
+               dev_attr_threeg.attr.mode |= S_IWUSR;
+       }
 
        /* disable hardware control by fn key */
        result = ec_read(MSI_STANDARD_EC_SCM_LOAD_ADDRESS, &data);
@@ -874,21 +1055,22 @@ static int __init msi_init(void)
        if (acpi_disabled)
                return -ENODEV;
 
-       if (force || dmi_check_system(msi_dmi_table))
-               old_ec_model = 1;
+       dmi_check_system(msi_dmi_table);
+       if (!quirks)
+               /* quirks may be NULL if no match in DMI table */
+               quirks = &quirk_load_scm_model;
+       if (force)
+               quirks = &quirk_old_ec_model;
 
-       if (!old_ec_model)
+       if (!quirks->old_ec_model)
                get_threeg_exists();
 
-       if (!old_ec_model && dmi_check_system(msi_load_scm_models_dmi_table))
-               load_scm_model = 1;
-
        if (auto_brightness < 0 || auto_brightness > 2)
                return -EINVAL;
 
        /* Register backlight stuff */
 
-       if (acpi_video_backlight_support()) {
+       if (!quirks->old_ec_model || acpi_video_backlight_support()) {
                pr_info("Brightness ignored, must be controlled by ACPI video driver\n");
        } else {
                struct backlight_properties props;
@@ -918,7 +1100,7 @@ static int __init msi_init(void)
        if (ret)
                goto fail_platform_device1;
 
-       if (load_scm_model && (load_scm_model_init(msipf_device) < 0)) {
+       if (quirks->load_scm_model && (load_scm_model_init(msipf_device) < 0)) {
                ret = -EINVAL;
                goto fail_platform_device1;
        }
@@ -928,20 +1110,25 @@ static int __init msi_init(void)
        if (ret)
                goto fail_platform_device2;
 
-       if (!old_ec_model) {
+       if (!quirks->old_ec_model) {
                if (threeg_exists)
                        ret = device_create_file(&msipf_device->dev,
                                                &dev_attr_threeg);
                if (ret)
                        goto fail_platform_device2;
-       }
+       } else {
+               ret = sysfs_create_group(&msipf_device->dev.kobj,
+                                        &msipf_old_attribute_group);
+               if (ret)
+                       goto fail_platform_device2;
 
-       /* Disable automatic brightness control by default because
-        * this module was probably loaded to do brightness control in
-        * software. */
+               /* Disable automatic brightness control by default because
+                * this module was probably loaded to do brightness control in
+                * software. */
 
-       if (auto_brightness != 2)
-               set_auto_brightness(auto_brightness);
+               if (auto_brightness != 2)
+                       set_auto_brightness(auto_brightness);
+       }
 
        pr_info("driver " MSI_DRIVER_VERSION " successfully loaded\n");
 
@@ -949,9 +1136,10 @@ static int __init msi_init(void)
 
 fail_platform_device2:
 
-       if (load_scm_model) {
+       if (quirks->load_scm_model) {
                i8042_remove_filter(msi_laptop_i8042_filter);
-               cancel_delayed_work_sync(&msi_rfkill_work);
+               cancel_delayed_work_sync(&msi_rfkill_dwork);
+               cancel_work_sync(&msi_rfkill_work);
                rfkill_cleanup();
        }
        platform_device_del(msipf_device);
@@ -973,23 +1161,26 @@ fail_backlight:
 
 static void __exit msi_cleanup(void)
 {
-       if (load_scm_model) {
+       if (quirks->load_scm_model) {
                i8042_remove_filter(msi_laptop_i8042_filter);
                msi_laptop_input_destroy();
-               cancel_delayed_work_sync(&msi_rfkill_work);
+               cancel_delayed_work_sync(&msi_rfkill_dwork);
+               cancel_work_sync(&msi_rfkill_work);
                rfkill_cleanup();
        }
 
        sysfs_remove_group(&msipf_device->dev.kobj, &msipf_attribute_group);
-       if (!old_ec_model && threeg_exists)
+       if (!quirks->old_ec_model && threeg_exists)
                device_remove_file(&msipf_device->dev, &dev_attr_threeg);
        platform_device_unregister(msipf_device);
        platform_driver_unregister(&msipf_driver);
        backlight_device_unregister(msibl_device);
 
-       /* Enable automatic brightness control again */
-       if (auto_brightness != 2)
-               set_auto_brightness(1);
+       if (quirks->old_ec_model) {
+               /* Enable automatic brightness control again */
+               if (auto_brightness != 2)
+                       set_auto_brightness(1);
+       }
 
        pr_info("driver unloaded\n");
 }
@@ -1011,3 +1202,4 @@ MODULE_ALIAS("dmi:*:svnMICRO-STARINTERNATIONAL*:pnMS-N051:*");
 MODULE_ALIAS("dmi:*:svnMICRO-STARINTERNATIONAL*:pnMS-N014:*");
 MODULE_ALIAS("dmi:*:svnMicro-StarInternational*:pnCR620:*");
 MODULE_ALIAS("dmi:*:svnMicro-StarInternational*:pnU270series:*");
+MODULE_ALIAS("dmi:*:svnMICRO-STARINTERNATIONAL*:pnU90/U100:*");
index 2264331..70222f2 100644 (file)
@@ -34,29 +34,65 @@ MODULE_AUTHOR("Thomas Renninger <trenn@suse.de>");
 MODULE_DESCRIPTION("MSI laptop WMI hotkeys driver");
 MODULE_LICENSE("GPL");
 
-MODULE_ALIAS("wmi:551A1F84-FBDD-4125-91DB-3EA8F44F1D45");
-MODULE_ALIAS("wmi:B6F3EEF2-3D2F-49DC-9DE3-85BCE18C62F2");
-
 #define DRV_NAME "msi-wmi"
 
 #define MSIWMI_BIOS_GUID "551A1F84-FBDD-4125-91DB-3EA8F44F1D45"
-#define MSIWMI_EVENT_GUID "B6F3EEF2-3D2F-49DC-9DE3-85BCE18C62F2"
-
-#define SCANCODE_BASE 0xD0
-#define MSI_WMI_BRIGHTNESSUP   SCANCODE_BASE
-#define MSI_WMI_BRIGHTNESSDOWN (SCANCODE_BASE + 1)
-#define MSI_WMI_VOLUMEUP       (SCANCODE_BASE + 2)
-#define MSI_WMI_VOLUMEDOWN     (SCANCODE_BASE + 3)
-#define MSI_WMI_MUTE           (SCANCODE_BASE + 4)
+#define MSIWMI_MSI_EVENT_GUID "B6F3EEF2-3D2F-49DC-9DE3-85BCE18C62F2"
+#define MSIWMI_WIND_EVENT_GUID "5B3CC38A-40D9-7245-8AE6-1145B751BE3F"
+
+MODULE_ALIAS("wmi:" MSIWMI_BIOS_GUID);
+MODULE_ALIAS("wmi:" MSIWMI_MSI_EVENT_GUID);
+MODULE_ALIAS("wmi:" MSIWMI_WIND_EVENT_GUID);
+
+enum msi_scancodes {
+       /* Generic MSI keys (not present on MSI Wind) */
+       MSI_KEY_BRIGHTNESSUP    = 0xD0,
+       MSI_KEY_BRIGHTNESSDOWN,
+       MSI_KEY_VOLUMEUP,
+       MSI_KEY_VOLUMEDOWN,
+       MSI_KEY_MUTE,
+       /* MSI Wind keys */
+       WIND_KEY_TOUCHPAD       = 0x08, /* Fn+F3 touchpad toggle */
+       WIND_KEY_BLUETOOTH      = 0x56, /* Fn+F11 Bluetooth toggle */
+       WIND_KEY_CAMERA,                /* Fn+F6 webcam toggle */
+       WIND_KEY_WLAN           = 0x5f, /* Fn+F11 Wi-Fi toggle */
+       WIND_KEY_TURBO,                 /* Fn+F10 turbo mode toggle */
+       WIND_KEY_ECO            = 0x69, /* Fn+F10 ECO mode toggle */
+};
 static struct key_entry msi_wmi_keymap[] = {
-       { KE_KEY, MSI_WMI_BRIGHTNESSUP,   {KEY_BRIGHTNESSUP} },
-       { KE_KEY, MSI_WMI_BRIGHTNESSDOWN, {KEY_BRIGHTNESSDOWN} },
-       { KE_KEY, MSI_WMI_VOLUMEUP,       {KEY_VOLUMEUP} },
-       { KE_KEY, MSI_WMI_VOLUMEDOWN,     {KEY_VOLUMEDOWN} },
-       { KE_KEY, MSI_WMI_MUTE,           {KEY_MUTE} },
-       { KE_END, 0}
+       { KE_KEY, MSI_KEY_BRIGHTNESSUP,         {KEY_BRIGHTNESSUP} },
+       { KE_KEY, MSI_KEY_BRIGHTNESSDOWN,       {KEY_BRIGHTNESSDOWN} },
+       { KE_KEY, MSI_KEY_VOLUMEUP,             {KEY_VOLUMEUP} },
+       { KE_KEY, MSI_KEY_VOLUMEDOWN,           {KEY_VOLUMEDOWN} },
+       { KE_KEY, MSI_KEY_MUTE,                 {KEY_MUTE} },
+
+       /* These keys work without WMI. Ignore them to avoid double keycodes */
+       { KE_IGNORE, WIND_KEY_TOUCHPAD,         {KEY_TOUCHPAD_TOGGLE} },
+       { KE_IGNORE, WIND_KEY_BLUETOOTH,        {KEY_BLUETOOTH} },
+       { KE_IGNORE, WIND_KEY_CAMERA,           {KEY_CAMERA} },
+       { KE_IGNORE, WIND_KEY_WLAN,             {KEY_WLAN} },
+
+       /* These are unknown WMI events found on MSI Wind */
+       { KE_IGNORE, 0x00 },
+       { KE_IGNORE, 0x62 },
+       { KE_IGNORE, 0x63 },
+
+       /* These are MSI Wind keys that should be handled via WMI */
+       { KE_KEY, WIND_KEY_TURBO,               {KEY_PROG1} },
+       { KE_KEY, WIND_KEY_ECO,                 {KEY_PROG2} },
+
+       { KE_END, 0 }
+};
+
+static ktime_t last_pressed;
+
+static const struct {
+       const char *guid;
+       bool quirk_last_pressed;
+} *event_wmi, event_wmis[] = {
+       { MSIWMI_MSI_EVENT_GUID, true },
+       { MSIWMI_WIND_EVENT_GUID, false },
 };
-static ktime_t last_pressed[ARRAY_SIZE(msi_wmi_keymap) - 1];
 
 static struct backlight_device *backlight;
 
@@ -149,7 +185,6 @@ static void msi_wmi_notify(u32 value, void *context)
        struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL };
        static struct key_entry *key;
        union acpi_object *obj;
-       ktime_t cur;
        acpi_status status;
 
        status = wmi_get_event_data(value, &response);
@@ -165,39 +200,67 @@ static void msi_wmi_notify(u32 value, void *context)
                pr_debug("Eventcode: 0x%x\n", eventcode);
                key = sparse_keymap_entry_from_scancode(msi_wmi_input_dev,
                                eventcode);
-               if (key) {
-                       ktime_t diff;
-                       cur = ktime_get_real();
-                       diff = ktime_sub(cur, last_pressed[key->code -
-                                       SCANCODE_BASE]);
-                       /* Ignore event if the same event happened in a 50 ms
+               if (!key) {
+                       pr_info("Unknown key pressed - %x\n", eventcode);
+                       goto msi_wmi_notify_exit;
+               }
+
+               if (event_wmi->quirk_last_pressed) {
+                       ktime_t cur = ktime_get_real();
+                       ktime_t diff = ktime_sub(cur, last_pressed);
+                       /* Ignore event if any event happened in a 50 ms
                           timeframe -> Key press may result in 10-20 GPEs */
                        if (ktime_to_us(diff) < 1000 * 50) {
                                pr_debug("Suppressed key event 0x%X - "
                                         "Last press was %lld us ago\n",
                                         key->code, ktime_to_us(diff));
-                               return;
-                       }
-                       last_pressed[key->code - SCANCODE_BASE] = cur;
-
-                       if (key->type == KE_KEY &&
-                       /* Brightness is served via acpi video driver */
-                       (!acpi_video_backlight_support() ||
-                       (key->code != MSI_WMI_BRIGHTNESSUP &&
-                       key->code != MSI_WMI_BRIGHTNESSDOWN))) {
-                               pr_debug("Send key: 0x%X - "
-                                        "Input layer keycode: %d\n",
-                                        key->code, key->keycode);
-                               sparse_keymap_report_entry(msi_wmi_input_dev,
-                                               key, 1, true);
+                               goto msi_wmi_notify_exit;
                        }
-               } else
-                       pr_info("Unknown key pressed - %x\n", eventcode);
+                       last_pressed = cur;
+               }
+
+               if (key->type == KE_KEY &&
+               /* Brightness is served via acpi video driver */
+               (backlight ||
+               (key->code != MSI_KEY_BRIGHTNESSUP &&
+               key->code != MSI_KEY_BRIGHTNESSDOWN))) {
+                       pr_debug("Send key: 0x%X - Input layer keycode: %d\n",
+                                key->code, key->keycode);
+                       sparse_keymap_report_entry(msi_wmi_input_dev, key, 1,
+                                                  true);
+               }
        } else
                pr_info("Unknown event received\n");
+
+msi_wmi_notify_exit:
        kfree(response.pointer);
 }
 
+static int __init msi_wmi_backlight_setup(void)
+{
+       int err;
+       struct backlight_properties props;
+
+       memset(&props, 0, sizeof(struct backlight_properties));
+       props.type = BACKLIGHT_PLATFORM;
+       props.max_brightness = ARRAY_SIZE(backlight_map) - 1;
+       backlight = backlight_device_register(DRV_NAME, NULL, NULL,
+                                             &msi_backlight_ops,
+                                             &props);
+       if (IS_ERR(backlight))
+               return PTR_ERR(backlight);
+
+       err = bl_get(NULL);
+       if (err < 0) {
+               backlight_device_unregister(backlight);
+               return err;
+       }
+
+       backlight->props.brightness = err;
+
+       return 0;
+}
+
 static int __init msi_wmi_input_setup(void)
 {
        int err;
@@ -219,7 +282,7 @@ static int __init msi_wmi_input_setup(void)
        if (err)
                goto err_free_keymap;
 
-       memset(last_pressed, 0, sizeof(last_pressed));
+       last_pressed = ktime_set(0, 0);
 
        return 0;
 
@@ -233,61 +296,66 @@ err_free_dev:
 static int __init msi_wmi_init(void)
 {
        int err;
+       int i;
 
-       if (!wmi_has_guid(MSIWMI_EVENT_GUID)) {
-               pr_err("This machine doesn't have MSI-hotkeys through WMI\n");
-               return -ENODEV;
-       }
-       err = wmi_install_notify_handler(MSIWMI_EVENT_GUID,
-                       msi_wmi_notify, NULL);
-       if (ACPI_FAILURE(err))
-               return -EINVAL;
+       for (i = 0; i < ARRAY_SIZE(event_wmis); i++) {
+               if (!wmi_has_guid(event_wmis[i].guid))
+                       continue;
 
-       err = msi_wmi_input_setup();
-       if (err)
-               goto err_uninstall_notifier;
-
-       if (!acpi_video_backlight_support()) {
-               struct backlight_properties props;
-               memset(&props, 0, sizeof(struct backlight_properties));
-               props.type = BACKLIGHT_PLATFORM;
-               props.max_brightness = ARRAY_SIZE(backlight_map) - 1;
-               backlight = backlight_device_register(DRV_NAME, NULL, NULL,
-                                                     &msi_backlight_ops,
-                                                     &props);
-               if (IS_ERR(backlight)) {
-                       err = PTR_ERR(backlight);
+               err = msi_wmi_input_setup();
+               if (err) {
+                       pr_err("Unable to setup input device\n");
+                       return err;
+               }
+
+               err = wmi_install_notify_handler(event_wmis[i].guid,
+                       msi_wmi_notify, NULL);
+               if (ACPI_FAILURE(err)) {
+                       pr_err("Unable to setup WMI notify handler\n");
                        goto err_free_input;
                }
 
-               err = bl_get(NULL);
-               if (err < 0)
-                       goto err_free_backlight;
+               pr_debug("Event handler installed\n");
+               event_wmi = &event_wmis[i];
+               break;
+       }
 
-               backlight->props.brightness = err;
+       if (wmi_has_guid(MSIWMI_BIOS_GUID) && !acpi_video_backlight_support()) {
+               err = msi_wmi_backlight_setup();
+               if (err) {
+                       pr_err("Unable to setup backlight device\n");
+                       goto err_uninstall_handler;
+               }
+               pr_debug("Backlight device created\n");
+       }
+
+       if (!event_wmi && !backlight) {
+               pr_err("This machine doesn't have neither MSI-hotkeys nor backlight through WMI\n");
+               return -ENODEV;
        }
-       pr_debug("Event handler installed\n");
 
        return 0;
 
-err_free_backlight:
-       backlight_device_unregister(backlight);
+err_uninstall_handler:
+       if (event_wmi)
+               wmi_remove_notify_handler(event_wmi->guid);
 err_free_input:
-       sparse_keymap_free(msi_wmi_input_dev);
-       input_unregister_device(msi_wmi_input_dev);
-err_uninstall_notifier:
-       wmi_remove_notify_handler(MSIWMI_EVENT_GUID);
+       if (event_wmi) {
+               sparse_keymap_free(msi_wmi_input_dev);
+               input_unregister_device(msi_wmi_input_dev);
+       }
        return err;
 }
 
 static void __exit msi_wmi_exit(void)
 {
-       if (wmi_has_guid(MSIWMI_EVENT_GUID)) {
-               wmi_remove_notify_handler(MSIWMI_EVENT_GUID);
+       if (event_wmi) {
+               wmi_remove_notify_handler(event_wmi->guid);
                sparse_keymap_free(msi_wmi_input_dev);
                input_unregister_device(msi_wmi_input_dev);
-               backlight_device_unregister(backlight);
        }
+       if (backlight)
+               backlight_device_unregister(backlight);
 }
 
 module_init(msi_wmi_init);
index 8da2187..14d4dce 100644 (file)
@@ -158,6 +158,11 @@ static void sony_nc_thermal_cleanup(struct platform_device *pd);
 static int sony_nc_lid_resume_setup(struct platform_device *pd);
 static void sony_nc_lid_resume_cleanup(struct platform_device *pd);
 
+static int sony_nc_gfx_switch_setup(struct platform_device *pd,
+               unsigned int handle);
+static void sony_nc_gfx_switch_cleanup(struct platform_device *pd);
+static int __sony_nc_gfx_switch_status_get(void);
+
 static int sony_nc_highspeed_charging_setup(struct platform_device *pd);
 static void sony_nc_highspeed_charging_cleanup(struct platform_device *pd);
 
@@ -1241,17 +1246,13 @@ static void sony_nc_notify(struct acpi_device *device, u32 event)
                        /* Hybrid GFX switching */
                        sony_call_snc_handle(handle, 0x0000, &result);
                        dprintk("GFX switch event received (reason: %s)\n",
-                                       (result & 0x01) ?
-                                       "switch change" : "unknown");
-
-                       /* verify the switch state
-                        * 1: discrete GFX
-                        * 0: integrated GFX
-                        */
-                       sony_call_snc_handle(handle, 0x0100, &result);
+                                       (result == 0x1) ? "switch change" :
+                                       (result == 0x2) ? "output switch" :
+                                       (result == 0x3) ? "output switch" :
+                                       "");
 
                        ev_type = GFX_SWITCH;
-                       real_ev = result & 0xff;
+                       real_ev = __sony_nc_gfx_switch_status_get();
                        break;
 
                default:
@@ -1350,6 +1351,13 @@ static void sony_nc_function_setup(struct acpi_device *device,
                                pr_err("couldn't set up thermal profile function (%d)\n",
                                                result);
                        break;
+               case 0x0128:
+               case 0x0146:
+                       result = sony_nc_gfx_switch_setup(pf_device, handle);
+                       if (result)
+                               pr_err("couldn't set up GFX Switch status (%d)\n",
+                                               result);
+                       break;
                case 0x0131:
                        result = sony_nc_highspeed_charging_setup(pf_device);
                        if (result)
@@ -1365,6 +1373,8 @@ static void sony_nc_function_setup(struct acpi_device *device,
                        break;
                case 0x0137:
                case 0x0143:
+               case 0x014b:
+               case 0x014c:
                        result = sony_nc_kbd_backlight_setup(pf_device, handle);
                        if (result)
                                pr_err("couldn't set up keyboard backlight function (%d)\n",
@@ -1414,6 +1424,10 @@ static void sony_nc_function_cleanup(struct platform_device *pd)
                case 0x0122:
                        sony_nc_thermal_cleanup(pd);
                        break;
+               case 0x0128:
+               case 0x0146:
+                       sony_nc_gfx_switch_cleanup(pd);
+                       break;
                case 0x0131:
                        sony_nc_highspeed_charging_cleanup(pd);
                        break;
@@ -1423,6 +1437,8 @@ static void sony_nc_function_cleanup(struct platform_device *pd)
                        break;
                case 0x0137:
                case 0x0143:
+               case 0x014b:
+               case 0x014c:
                        sony_nc_kbd_backlight_cleanup(pd);
                        break;
                default:
@@ -1467,6 +1483,8 @@ static void sony_nc_function_resume(void)
                        break;
                case 0x0137:
                case 0x0143:
+               case 0x014b:
+               case 0x014c:
                        sony_nc_kbd_backlight_resume();
                        break;
                default:
@@ -1534,7 +1552,7 @@ static int sony_nc_rfkill_set(void *data, bool blocked)
        int argument = sony_rfkill_address[(long) data] + 0x100;
 
        if (!blocked)
-               argument |= 0x030000;
+               argument |= 0x070000;
 
        return sony_call_snc_handle(sony_rfkill_handle, argument, &result);
 }
@@ -2333,7 +2351,7 @@ static int sony_nc_lid_resume_setup(struct platform_device *pd)
        return 0;
 
 liderror:
-       for (; i > 0; i--)
+       for (i--; i >= 0; i--)
                device_remove_file(&pd->dev, &lid_ctl->attrs[i]);
 
        kfree(lid_ctl);
@@ -2355,6 +2373,97 @@ static void sony_nc_lid_resume_cleanup(struct platform_device *pd)
        }
 }
 
+/* GFX Switch position */
+enum gfx_switch {
+       SPEED,
+       STAMINA,
+       AUTO
+};
+struct snc_gfx_switch_control {
+       struct device_attribute attr;
+       unsigned int handle;
+};
+static struct snc_gfx_switch_control *gfxs_ctl;
+
+/* returns 0 for speed, 1 for stamina */
+static int __sony_nc_gfx_switch_status_get(void)
+{
+       unsigned int result;
+
+       if (sony_call_snc_handle(gfxs_ctl->handle, 0x0100, &result))
+               return -EIO;
+
+       switch (gfxs_ctl->handle) {
+       case 0x0146:
+               /* 1: discrete GFX (speed)
+                * 0: integrated GFX (stamina)
+                */
+               return result & 0x1 ? SPEED : STAMINA;
+               break;
+       case 0x0128:
+               /* it's a more elaborated bitmask, for now:
+                * 2: integrated GFX (stamina)
+                * 0: discrete GFX (speed)
+                */
+               dprintk("GFX Status: 0x%x\n", result);
+               return result & 0x80 ? AUTO :
+                       result & 0x02 ? STAMINA : SPEED;
+               break;
+       }
+       return -EINVAL;
+}
+
+static ssize_t sony_nc_gfx_switch_status_show(struct device *dev,
+                                      struct device_attribute *attr,
+                                      char *buffer)
+{
+       int pos = __sony_nc_gfx_switch_status_get();
+
+       if (pos < 0)
+               return pos;
+
+       return snprintf(buffer, PAGE_SIZE, "%s\n", pos ? "speed" : "stamina");
+}
+
+static int sony_nc_gfx_switch_setup(struct platform_device *pd,
+               unsigned int handle)
+{
+       unsigned int result;
+
+       gfxs_ctl = kzalloc(sizeof(struct snc_gfx_switch_control), GFP_KERNEL);
+       if (!gfxs_ctl)
+               return -ENOMEM;
+
+       gfxs_ctl->handle = handle;
+
+       sysfs_attr_init(&gfxs_ctl->attr.attr);
+       gfxs_ctl->attr.attr.name = "gfx_switch_status";
+       gfxs_ctl->attr.attr.mode = S_IRUGO;
+       gfxs_ctl->attr.show = sony_nc_gfx_switch_status_show;
+
+       result = device_create_file(&pd->dev, &gfxs_ctl->attr);
+       if (result)
+               goto gfxerror;
+
+       return 0;
+
+gfxerror:
+       kfree(gfxs_ctl);
+       gfxs_ctl = NULL;
+
+       return result;
+}
+
+static void sony_nc_gfx_switch_cleanup(struct platform_device *pd)
+{
+       if (gfxs_ctl) {
+               device_remove_file(&pd->dev, &gfxs_ctl->attr);
+
+               kfree(gfxs_ctl);
+               gfxs_ctl = NULL;
+       }
+}
+
 /* High speed charging function */
 static struct device_attribute *hsc_handle;
 
@@ -2533,6 +2642,8 @@ static void sony_nc_backlight_ng_read_limits(int handle,
                lvl_table_len = 9;
                break;
        case 0x143:
+       case 0x14b:
+       case 0x14c:
                lvl_table_len = 16;
                break;
        }
@@ -2584,6 +2695,18 @@ static void sony_nc_backlight_setup(void)
                sony_nc_backlight_ng_read_limits(0x143, &sony_bl_props);
                max_brightness = sony_bl_props.maxlvl - sony_bl_props.offset;
 
+       } else if (sony_find_snc_handle(0x14b) >= 0) {
+               ops = &sony_backlight_ng_ops;
+               sony_bl_props.cmd_base = 0x3000;
+               sony_nc_backlight_ng_read_limits(0x14b, &sony_bl_props);
+               max_brightness = sony_bl_props.maxlvl - sony_bl_props.offset;
+
+       } else if (sony_find_snc_handle(0x14c) >= 0) {
+               ops = &sony_backlight_ng_ops;
+               sony_bl_props.cmd_base = 0x3000;
+               sony_nc_backlight_ng_read_limits(0x14c, &sony_bl_props);
+               max_brightness = sony_bl_props.maxlvl - sony_bl_props.offset;
+
        } else if (ACPI_SUCCESS(acpi_get_handle(sony_nc_acpi_handle, "GBRT",
                                                &unused))) {
                ops = &sony_backlight_ops;
index f4f8408..9a90756 100644 (file)
@@ -209,9 +209,8 @@ enum tpacpi_hkey_event_t {
        TP_HKEY_EV_ALARM_SENSOR_XHOT    = 0x6022, /* sensor critically hot */
        TP_HKEY_EV_THM_TABLE_CHANGED    = 0x6030, /* thermal table changed */
 
-       TP_HKEY_EV_UNK_6040             = 0x6040, /* Related to AC change?
-                                                    some sort of APM hint,
-                                                    W520 */
+       /* AC-related events */
+       TP_HKEY_EV_AC_CHANGED           = 0x6040, /* AC status changed */
 
        /* Misc */
        TP_HKEY_EV_RFKILL_CHANGED       = 0x7000, /* rfkill switch changed */
@@ -3629,6 +3628,12 @@ static bool hotkey_notify_6xxx(const u32 hkey,
                         "a sensor reports something is extremely hot!\n");
                /* recommended action: immediate sleep/hibernate */
                break;
+       case TP_HKEY_EV_AC_CHANGED:
+               /* X120e, X121e, X220, X220i, X220t, X230, T420, T420s, W520:
+                * AC status changed; can be triggered by plugging or
+                * unplugging AC adapter, docking or undocking. */
+
+               /* fallthrough */
 
        case TP_HKEY_EV_KEY_NUMLOCK:
        case TP_HKEY_EV_KEY_FN:
@@ -8574,7 +8579,8 @@ static bool __pure __init tpacpi_is_valid_fw_id(const char* const s,
        return s && strlen(s) >= 8 &&
                tpacpi_is_fw_digit(s[0]) &&
                tpacpi_is_fw_digit(s[1]) &&
-               s[2] == t && s[3] == 'T' &&
+               s[2] == t &&
+               (s[3] == 'T' || s[3] == 'N') &&
                tpacpi_is_fw_digit(s[4]) &&
                tpacpi_is_fw_digit(s[5]);
 }
@@ -8607,7 +8613,8 @@ static int __must_check __init get_thinkpad_model_data(
                return -ENOMEM;
 
        /* Really ancient ThinkPad 240X will fail this, which is fine */
-       if (!tpacpi_is_valid_fw_id(tp->bios_version_str, 'E'))
+       if (!(tpacpi_is_valid_fw_id(tp->bios_version_str, 'E') ||
+             tpacpi_is_valid_fw_id(tp->bios_version_str, 'C')))
                return 0;
 
        tp->bios_model = tp->bios_version_str[0]