Commit 23caaeea authored by Linus Torvalds's avatar Linus Torvalds

Merge branch 'for_linus' of git://cavan.codon.org.uk/platform-drivers-x86

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
  ...
parents a7c1120d 445e8d00
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.
......@@ -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"
......
......@@ -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
......@@ -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);
......
......@@ -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 */
......
......@@ -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},
};
......
......@@ -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:
......
......@@ -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 {
......
/*
* 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");
......@@ -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 } },
......
......@@ -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 @@ static int hp_wmi_rfkill2_setup(struct platform_device *device)
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);
This diff is collapsed.
......@@ -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 @@ static int __init msi_wmi_input_setup(void)
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);
......
......@@ -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;
......
......@@ -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]
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment