Commit b037bba7 authored by Linus Torvalds's avatar Linus Torvalds

Merge branch 'for_linus' of...

Merge branch 'for_linus' of git://git.kernel.org/pub/scm/linux/kernel/git/mjg59/platform-drivers-x86

* 'for_linus' of git://git.kernel.org/pub/scm/linux/kernel/git/mjg59/platform-drivers-x86: (45 commits)
  compal-laptop: Make it depend on CONFIG_RFKILL
  classmate-laptop: Added some keys present in other devices
  MAINTAINERS: Add git tree to x86 Platform Drivers
  asus-acpi: remove duplicate comparison of asus_model strings
  toshiba-acpi: fix multimedia keys on some machines
  dell-laptop: Fix errors on failure and exit paths
  dell-laptop: Fix build error by making buffer_mutex static
  asus-laptop: fix style problems reported by checkpath.pl
  asus-laptop: use device_create_file() instead of platform_group
  asus-laptop: clean led code
  asus-laptop: add gps rfkill
  asus-laptop: set initial lcd state
  asus-laptop: leds, remove dead code and fix asus_led_exit()/asus_led_init()
  asus-laptop: add backlight changes notifications
  asus-laptop: add bluetooth keys found on M9V
  asus-laptop: switch to sparse keymap library
  asus-laptop: rename wireless_status to wlan_status to avoid confusion
  asus-laptop: add error check for write_acpi_int calls
  asus-laptop: stop using ASUS_HANDLE and use relative methods instead
  asus-laptop: rename function talking directly to acpi with asus_xxx scheme
  ...
parents a03696e9 51c1410b
What: /sys/devices/platform/asus-laptop/display What: /sys/devices/platform/asus_laptop/display
Date: January 2007 Date: January 2007
KernelVersion: 2.6.20 KernelVersion: 2.6.20
Contact: "Corentin Chary" <corentincj@iksaif.net> Contact: "Corentin Chary" <corentincj@iksaif.net>
...@@ -13,7 +13,7 @@ Description: ...@@ -13,7 +13,7 @@ Description:
Ex: - 0 (0000b) means no display Ex: - 0 (0000b) means no display
- 3 (0011b) CRT+LCD. - 3 (0011b) CRT+LCD.
What: /sys/devices/platform/asus-laptop/gps What: /sys/devices/platform/asus_laptop/gps
Date: January 2007 Date: January 2007
KernelVersion: 2.6.20 KernelVersion: 2.6.20
Contact: "Corentin Chary" <corentincj@iksaif.net> Contact: "Corentin Chary" <corentincj@iksaif.net>
...@@ -21,7 +21,7 @@ Description: ...@@ -21,7 +21,7 @@ Description:
Control the gps device. 1 means on, 0 means off. Control the gps device. 1 means on, 0 means off.
Users: Lapsus Users: Lapsus
What: /sys/devices/platform/asus-laptop/ledd What: /sys/devices/platform/asus_laptop/ledd
Date: January 2007 Date: January 2007
KernelVersion: 2.6.20 KernelVersion: 2.6.20
Contact: "Corentin Chary" <corentincj@iksaif.net> Contact: "Corentin Chary" <corentincj@iksaif.net>
...@@ -29,11 +29,11 @@ Description: ...@@ -29,11 +29,11 @@ Description:
Some models like the W1N have a LED display that can be Some models like the W1N have a LED display that can be
used to display several informations. used to display several informations.
To control the LED display, use the following : To control the LED display, use the following :
echo 0x0T000DDD > /sys/devices/platform/asus-laptop/ echo 0x0T000DDD > /sys/devices/platform/asus_laptop/
where T control the 3 letters display, and DDD the 3 digits display. where T control the 3 letters display, and DDD the 3 digits display.
The DDD table can be found in Documentation/laptops/asus-laptop.txt The DDD table can be found in Documentation/laptops/asus-laptop.txt
What: /sys/devices/platform/asus-laptop/bluetooth What: /sys/devices/platform/asus_laptop/bluetooth
Date: January 2007 Date: January 2007
KernelVersion: 2.6.20 KernelVersion: 2.6.20
Contact: "Corentin Chary" <corentincj@iksaif.net> Contact: "Corentin Chary" <corentincj@iksaif.net>
...@@ -42,7 +42,7 @@ Description: ...@@ -42,7 +42,7 @@ Description:
This may control the led, the device or both. This may control the led, the device or both.
Users: Lapsus Users: Lapsus
What: /sys/devices/platform/asus-laptop/wlan What: /sys/devices/platform/asus_laptop/wlan
Date: January 2007 Date: January 2007
KernelVersion: 2.6.20 KernelVersion: 2.6.20
Contact: "Corentin Chary" <corentincj@iksaif.net> Contact: "Corentin Chary" <corentincj@iksaif.net>
......
What: /sys/devices/platform/eeepc-laptop/disp What: /sys/devices/platform/eeepc/disp
Date: May 2008 Date: May 2008
KernelVersion: 2.6.26 KernelVersion: 2.6.26
Contact: "Corentin Chary" <corentincj@iksaif.net> Contact: "Corentin Chary" <corentincj@iksaif.net>
...@@ -9,21 +9,21 @@ Description: ...@@ -9,21 +9,21 @@ Description:
- 3 = LCD+CRT - 3 = LCD+CRT
If you run X11, you should use xrandr instead. If you run X11, you should use xrandr instead.
What: /sys/devices/platform/eeepc-laptop/camera What: /sys/devices/platform/eeepc/camera
Date: May 2008 Date: May 2008
KernelVersion: 2.6.26 KernelVersion: 2.6.26
Contact: "Corentin Chary" <corentincj@iksaif.net> Contact: "Corentin Chary" <corentincj@iksaif.net>
Description: Description:
Control the camera. 1 means on, 0 means off. Control the camera. 1 means on, 0 means off.
What: /sys/devices/platform/eeepc-laptop/cardr What: /sys/devices/platform/eeepc/cardr
Date: May 2008 Date: May 2008
KernelVersion: 2.6.26 KernelVersion: 2.6.26
Contact: "Corentin Chary" <corentincj@iksaif.net> Contact: "Corentin Chary" <corentincj@iksaif.net>
Description: Description:
Control the card reader. 1 means on, 0 means off. Control the card reader. 1 means on, 0 means off.
What: /sys/devices/platform/eeepc-laptop/cpufv What: /sys/devices/platform/eeepc/cpufv
Date: Jun 2009 Date: Jun 2009
KernelVersion: 2.6.31 KernelVersion: 2.6.31
Contact: "Corentin Chary" <corentincj@iksaif.net> Contact: "Corentin Chary" <corentincj@iksaif.net>
...@@ -42,7 +42,7 @@ Description: ...@@ -42,7 +42,7 @@ Description:
`------------ Availables modes `------------ Availables modes
For example, 0x301 means: mode 1 selected, 3 available modes. For example, 0x301 means: mode 1 selected, 3 available modes.
What: /sys/devices/platform/eeepc-laptop/available_cpufv What: /sys/devices/platform/eeepc/available_cpufv
Date: Jun 2009 Date: Jun 2009
KernelVersion: 2.6.31 KernelVersion: 2.6.31
Contact: "Corentin Chary" <corentincj@iksaif.net> Contact: "Corentin Chary" <corentincj@iksaif.net>
......
...@@ -650,6 +650,10 @@ LCD, CRT or DVI (if available). The following commands are available: ...@@ -650,6 +650,10 @@ LCD, CRT or DVI (if available). The following commands are available:
echo expand_toggle > /proc/acpi/ibm/video echo expand_toggle > /proc/acpi/ibm/video
echo video_switch > /proc/acpi/ibm/video echo video_switch > /proc/acpi/ibm/video
NOTE: Access to this feature is restricted to processes owning the
CAP_SYS_ADMIN capability for safety reasons, as it can interact badly
enough with some versions of X.org to crash it.
Each video output device can be enabled or disabled individually. Each video output device can be enabled or disabled individually.
Reading /proc/acpi/ibm/video shows the status of each device. Reading /proc/acpi/ibm/video shows the status of each device.
......
...@@ -6097,6 +6097,7 @@ F: arch/x86/ ...@@ -6097,6 +6097,7 @@ F: arch/x86/
X86 PLATFORM DRIVERS X86 PLATFORM DRIVERS
M: Matthew Garrett <mjg@redhat.com> M: Matthew Garrett <mjg@redhat.com>
L: platform-driver-x86@vger.kernel.org L: platform-driver-x86@vger.kernel.org
T: git git://git.kernel.org/pub/scm/linux/kernel/git/mjg59/platform-drivers-x86.git
S: Maintained S: Maintained
F: drivers/platform/x86 F: drivers/platform/x86
......
...@@ -59,6 +59,8 @@ config ASUS_LAPTOP ...@@ -59,6 +59,8 @@ config ASUS_LAPTOP
select NEW_LEDS select NEW_LEDS
select BACKLIGHT_CLASS_DEVICE select BACKLIGHT_CLASS_DEVICE
depends on INPUT depends on INPUT
depends on RFKILL || RFKILL = n
select INPUT_SPARSEKMAP
---help--- ---help---
This is the new Linux driver for Asus laptops. It may also support some This is the new Linux driver for Asus laptops. It may also support some
MEDION, JVC or VICTOR laptops. It makes all the extra buttons generate MEDION, JVC or VICTOR laptops. It makes all the extra buttons generate
...@@ -177,6 +179,7 @@ config COMPAL_LAPTOP ...@@ -177,6 +179,7 @@ config COMPAL_LAPTOP
tristate "Compal Laptop Extras" tristate "Compal Laptop Extras"
depends on ACPI depends on ACPI
depends on BACKLIGHT_CLASS_DEVICE depends on BACKLIGHT_CLASS_DEVICE
depends on RFKILL
---help--- ---help---
This is a driver for laptops built by Compal: This is a driver for laptops built by Compal:
...@@ -320,9 +323,15 @@ config THINKPAD_ACPI_VIDEO ...@@ -320,9 +323,15 @@ config THINKPAD_ACPI_VIDEO
server running, phase of the moon, and the current mood of server running, phase of the moon, and the current mood of
Schroedinger's cat. If you can use X.org's RandR to control Schroedinger's cat. If you can use X.org's RandR to control
your ThinkPad's video output ports instead of this feature, your ThinkPad's video output ports instead of this feature,
don't think twice: do it and say N here to save some memory. don't think twice: do it and say N here to save memory and avoid
bad interactions with X.org.
NOTE: access to this feature is limited to processes with the
CAP_SYS_ADMIN capability, to avoid local DoS issues in platforms
where it interacts badly with X.org.
If you are not sure, say Y here. If you are not sure, say Y here but do try to check if you could
be using X.org RandR instead.
config THINKPAD_ACPI_HOTKEY_POLL config THINKPAD_ACPI_HOTKEY_POLL
bool "Support NVRAM polling for hot keys" bool "Support NVRAM polling for hot keys"
......
...@@ -45,58 +45,23 @@ ...@@ -45,58 +45,23 @@
#include <linux/fb.h> #include <linux/fb.h>
#include <linux/leds.h> #include <linux/leds.h>
#include <linux/platform_device.h> #include <linux/platform_device.h>
#include <linux/uaccess.h>
#include <linux/input.h>
#include <linux/input/sparse-keymap.h>
#include <linux/rfkill.h>
#include <acpi/acpi_drivers.h> #include <acpi/acpi_drivers.h>
#include <acpi/acpi_bus.h> #include <acpi/acpi_bus.h>
#include <asm/uaccess.h>
#include <linux/input.h>
#define ASUS_LAPTOP_VERSION "0.42" #define ASUS_LAPTOP_VERSION "0.42"
#define ASUS_HOTK_NAME "Asus Laptop Support" #define ASUS_LAPTOP_NAME "Asus Laptop Support"
#define ASUS_HOTK_CLASS "hotkey" #define ASUS_LAPTOP_CLASS "hotkey"
#define ASUS_HOTK_DEVICE_NAME "Hotkey" #define ASUS_LAPTOP_DEVICE_NAME "Hotkey"
#define ASUS_HOTK_FILE KBUILD_MODNAME #define ASUS_LAPTOP_FILE KBUILD_MODNAME
#define ASUS_HOTK_PREFIX "\\_SB.ATKD." #define ASUS_LAPTOP_PREFIX "\\_SB.ATKD."
/*
* Some events we use, same for all Asus
*/
#define ATKD_BR_UP 0x10
#define ATKD_BR_DOWN 0x20
#define ATKD_LCD_ON 0x33
#define ATKD_LCD_OFF 0x34
/*
* Known bits returned by \_SB.ATKD.HWRS
*/
#define WL_HWRS 0x80
#define BT_HWRS 0x100
/*
* Flags for hotk status
* WL_ON and BT_ON are also used for wireless_status()
*/
#define WL_ON 0x01 /* internal Wifi */
#define BT_ON 0x02 /* internal Bluetooth */
#define MLED_ON 0x04 /* mail LED */
#define TLED_ON 0x08 /* touchpad LED */
#define RLED_ON 0x10 /* Record LED */
#define PLED_ON 0x20 /* Phone LED */
#define GLED_ON 0x40 /* Gaming LED */
#define LCD_ON 0x80 /* LCD backlight */
#define GPS_ON 0x100 /* GPS */
#define KEY_ON 0x200 /* Keyboard backlight */
#define ASUS_LOG ASUS_HOTK_FILE ": "
#define ASUS_ERR KERN_ERR ASUS_LOG
#define ASUS_WARNING KERN_WARNING ASUS_LOG
#define ASUS_NOTICE KERN_NOTICE ASUS_LOG
#define ASUS_INFO KERN_INFO ASUS_LOG
#define ASUS_DEBUG KERN_DEBUG ASUS_LOG
MODULE_AUTHOR("Julien Lerouge, Karol Kozimor, Corentin Chary"); MODULE_AUTHOR("Julien Lerouge, Karol Kozimor, Corentin Chary");
MODULE_DESCRIPTION(ASUS_HOTK_NAME); MODULE_DESCRIPTION(ASUS_LAPTOP_NAME);
MODULE_LICENSE("GPL"); MODULE_LICENSE("GPL");
/* /*
...@@ -113,46 +78,82 @@ static uint wapf = 1; ...@@ -113,46 +78,82 @@ static uint wapf = 1;
module_param(wapf, uint, 0644); module_param(wapf, uint, 0644);
MODULE_PARM_DESC(wapf, "WAPF value"); MODULE_PARM_DESC(wapf, "WAPF value");
#define ASUS_HANDLE(object, paths...) \ static uint wlan_status = 1;
static acpi_handle object##_handle = NULL; \ static uint bluetooth_status = 1;
static char *object##_paths[] = { paths }
module_param(wlan_status, uint, 0644);
MODULE_PARM_DESC(wlan_status, "Set the wireless status on boot "
"(0 = disabled, 1 = enabled, -1 = don't do anything). "
"default is 1");
module_param(bluetooth_status, uint, 0644);
MODULE_PARM_DESC(bluetooth_status, "Set the wireless status on boot "
"(0 = disabled, 1 = enabled, -1 = don't do anything). "
"default is 1");
/*
* 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_LCD_ON 0x33
#define ATKD_LCD_OFF 0x34
/*
* Known bits returned by \_SB.ATKD.HWRS
*/
#define WL_HWRS 0x80
#define BT_HWRS 0x100
/*
* Flags for hotk status
* WL_ON and BT_ON are also used for wireless_status()
*/
#define WL_RSTS 0x01 /* internal Wifi */
#define BT_RSTS 0x02 /* internal Bluetooth */
/* LED */ /* LED */
ASUS_HANDLE(mled_set, ASUS_HOTK_PREFIX "MLED"); #define METHOD_MLED "MLED"
ASUS_HANDLE(tled_set, ASUS_HOTK_PREFIX "TLED"); #define METHOD_TLED "TLED"
ASUS_HANDLE(rled_set, ASUS_HOTK_PREFIX "RLED"); /* W1JC */ #define METHOD_RLED "RLED" /* W1JC */
ASUS_HANDLE(pled_set, ASUS_HOTK_PREFIX "PLED"); /* A7J */ #define METHOD_PLED "PLED" /* A7J */
ASUS_HANDLE(gled_set, ASUS_HOTK_PREFIX "GLED"); /* G1, G2 (probably) */ #define METHOD_GLED "GLED" /* G1, G2 (probably) */
/* LEDD */ /* LEDD */
ASUS_HANDLE(ledd_set, ASUS_HOTK_PREFIX "SLCM"); #define METHOD_LEDD "SLCM"
/* /*
* Bluetooth and WLAN * Bluetooth and WLAN
* WLED and BLED are not handled like other XLED, because in some dsdt * WLED and BLED are not handled like other XLED, because in some dsdt
* they also control the WLAN/Bluetooth device. * they also control the WLAN/Bluetooth device.
*/ */
ASUS_HANDLE(wl_switch, ASUS_HOTK_PREFIX "WLED"); #define METHOD_WLAN "WLED"
ASUS_HANDLE(bt_switch, ASUS_HOTK_PREFIX "BLED"); #define METHOD_BLUETOOTH "BLED"
ASUS_HANDLE(wireless_status, ASUS_HOTK_PREFIX "RSTS"); /* All new models */ #define METHOD_WL_STATUS "RSTS"
/* Brightness */ /* Brightness */
ASUS_HANDLE(brightness_set, ASUS_HOTK_PREFIX "SPLV"); #define METHOD_BRIGHTNESS_SET "SPLV"
ASUS_HANDLE(brightness_get, ASUS_HOTK_PREFIX "GPLV"); #define METHOD_BRIGHTNESS_GET "GPLV"
/* Backlight */ /* Backlight */
ASUS_HANDLE(lcd_switch, "\\_SB.PCI0.SBRG.EC0._Q10", /* All new models */ static acpi_handle lcd_switch_handle;
static const char *lcd_switch_paths[] = {
"\\_SB.PCI0.SBRG.EC0._Q10", /* All new models */
"\\_SB.PCI0.ISA.EC0._Q10", /* A1x */ "\\_SB.PCI0.ISA.EC0._Q10", /* A1x */
"\\_SB.PCI0.PX40.ECD0._Q10", /* L3C */ "\\_SB.PCI0.PX40.ECD0._Q10", /* L3C */
"\\_SB.PCI0.PX40.EC0.Q10", /* M1A */ "\\_SB.PCI0.PX40.EC0.Q10", /* M1A */
"\\_SB.PCI0.LPCB.EC0._Q10", /* P30 */ "\\_SB.PCI0.LPCB.EC0._Q10", /* P30 */
"\\_SB.PCI0.LPCB.EC0._Q0E", /* P30/P35 */ "\\_SB.PCI0.LPCB.EC0._Q0E", /* P30/P35 */
"\\_SB.PCI0.PX40.Q10", /* S1x */ "\\_SB.PCI0.PX40.Q10", /* S1x */
"\\Q10"); /* A2x, L2D, L3D, M2E */ "\\Q10"}; /* A2x, L2D, L3D, M2E */
/* Display */ /* Display */
ASUS_HANDLE(display_set, ASUS_HOTK_PREFIX "SDSP"); #define METHOD_SWITCH_DISPLAY "SDSP"
ASUS_HANDLE(display_get,
static acpi_handle display_get_handle;
static const char *display_get_paths[] = {
/* A6B, A6K A6R A7D F3JM L4R M6R A3G M6A M6V VX-1 V6J V6V W3Z */ /* A6B, A6K A6R A7D F3JM L4R M6R A3G M6A M6V VX-1 V6J V6V W3Z */
"\\_SB.PCI0.P0P1.VGA.GETD", "\\_SB.PCI0.P0P1.VGA.GETD",
/* A3E A4K, A4D A4L A6J A7J A8J Z71V M9V S5A M5A z33A W1Jc W2V G1 */ /* A3E A4K, A4D A4L A6J A7J A8J Z71V M9V S5A M5A z33A W1Jc W2V G1 */
...@@ -174,164 +175,112 @@ ASUS_HANDLE(display_get, ...@@ -174,164 +175,112 @@ ASUS_HANDLE(display_get,
/* A2H D1 L2D L3D L3H L2E L5D L5C M1A M2E L4L W3V */ /* A2H D1 L2D L3D L3H L2E L5D L5C M1A M2E L4L W3V */
"\\INFB", "\\INFB",
/* A3F A6F A3N A3L M6N W3N W6A */ /* A3F A6F A3N A3L M6N W3N W6A */
"\\SSTE"); "\\SSTE"};
ASUS_HANDLE(ls_switch, ASUS_HOTK_PREFIX "ALSC"); /* Z71A Z71V */ #define METHOD_ALS_CONTROL "ALSC" /* Z71A Z71V */
ASUS_HANDLE(ls_level, ASUS_HOTK_PREFIX "ALSL"); /* Z71A Z71V */ #define METHOD_ALS_LEVEL "ALSL" /* Z71A Z71V */
/* GPS */ /* GPS */
/* R2H use different handle for GPS on/off */ /* R2H use different handle for GPS on/off */
ASUS_HANDLE(gps_on, ASUS_HOTK_PREFIX "SDON"); /* R2H */ #define METHOD_GPS_ON "SDON"
ASUS_HANDLE(gps_off, ASUS_HOTK_PREFIX "SDOF"); /* R2H */ #define METHOD_GPS_OFF "SDOF"
ASUS_HANDLE(gps_status, ASUS_HOTK_PREFIX "GPST"); #define METHOD_GPS_STATUS "GPST"
/* Keyboard light */ /* Keyboard light */
ASUS_HANDLE(kled_set, ASUS_HOTK_PREFIX "SLKB"); #define METHOD_KBD_LIGHT_SET "SLKB"
ASUS_HANDLE(kled_get, ASUS_HOTK_PREFIX "GLKB"); #define METHOD_KBD_LIGHT_GET "GLKB"
/* /*
* This is the main structure, we can use it to store anything interesting * Define a specific led structure to keep the main structure clean
* about the hotk device
*/ */
struct asus_hotk { struct asus_led {
char *name; /* laptop name */ int wk;
struct acpi_device *device; /* the device we are in */ struct work_struct work;
acpi_handle handle; /* the handle of the hotk device */ struct led_classdev led;
char status; /* status of the hotk, for LEDs, ... */ struct asus_laptop *asus;
u32 ledd_status; /* status of the LED display */ const char *method;
u8 light_level; /* light sensor level */
u8 light_switch; /* light sensor switch value */
u16 event_count[128]; /* count for each event TODO make this better */
struct input_dev *inputdev;
u16 *keycode_map;
}; };
/* /*
* This header is made available to allow proper configuration given model, * This is the main structure, we can use it to store anything interesting
* revision number , ... this info cannot go in struct asus_hotk because it is * about the hotk device
* available before the hotk
*/
static struct acpi_table_header *asus_info;
/* The actual device the driver binds to */
static struct asus_hotk *hotk;
/*
* The hotkey driver declaration
*/ */
static const struct acpi_device_id asus_device_ids[] = { struct asus_laptop {
{"ATK0100", 0}, char *name; /* laptop name */
{"ATK0101", 0},
{"", 0},
};
MODULE_DEVICE_TABLE(acpi, asus_device_ids);
static int asus_hotk_add(struct acpi_device *device); struct acpi_table_header *dsdt_info;
static int asus_hotk_remove(struct acpi_device *device, int type); struct platform_device *platform_device;
static void asus_hotk_notify(struct acpi_device *device, u32 event); struct acpi_device *device; /* the device we are in */
struct backlight_device *backlight_device;
static struct acpi_driver asus_hotk_driver = { struct input_dev *inputdev;
.name = ASUS_HOTK_NAME, struct key_entry *keymap;
.class = ASUS_HOTK_CLASS,
.owner = THIS_MODULE,
.ids = asus_device_ids,
.flags = ACPI_DRIVER_ALL_NOTIFY_EVENTS,
.ops = {
.add = asus_hotk_add,
.remove = asus_hotk_remove,
.notify = asus_hotk_notify,
},
};
/* The backlight device /sys/class/backlight */ struct asus_led mled;
static struct backlight_device *asus_backlight_device; struct asus_led tled;
struct asus_led rled;
struct asus_led pled;
struct asus_led gled;
struct asus_led kled;
struct workqueue_struct *led_workqueue;
/* int wireless_status;
* The backlight class declaration bool have_rsts;
*/ int lcd_state;
static int read_brightness(struct backlight_device *bd);
static int update_bl_status(struct backlight_device *bd);
static struct backlight_ops asusbl_ops = {
.get_brightness = read_brightness,
.update_status = update_bl_status,
};
/* struct rfkill *gps_rfkill;
* These functions actually update the LED's, and are called from a
* workqueue. By doing this as separate work rather than when the LED
* subsystem asks, we avoid messing with the Asus ACPI stuff during a
* potentially bad time, such as a timer interrupt.
*/
static struct workqueue_struct *led_workqueue;
#define ASUS_LED(object, ledname, max) \
static void object##_led_set(struct led_classdev *led_cdev, \
enum led_brightness value); \
static enum led_brightness object##_led_get( \
struct led_classdev *led_cdev); \
static void object##_led_update(struct work_struct *ignored); \
static int object##_led_wk; \
static DECLARE_WORK(object##_led_work, object##_led_update); \
static struct led_classdev object##_led = { \
.name = "asus::" ledname, \
.brightness_set = object##_led_set, \
.brightness_get = object##_led_get, \
.max_brightness = max \
}
ASUS_LED(mled, "mail", 1); acpi_handle handle; /* the handle of the hotk device */
ASUS_LED(tled, "touchpad", 1); u32 ledd_status; /* status of the LED display */
ASUS_LED(rled, "record", 1); u8 light_level; /* light sensor level */
ASUS_LED(pled, "phone", 1); u8 light_switch; /* light sensor switch value */
ASUS_LED(gled, "gaming", 1); u16 event_count[128]; /* count for each event TODO make this better */
ASUS_LED(kled, "kbd_backlight", 3); u16 *keycode_map;
struct key_entry {
char type;
u8 code;
u16 keycode;
}; };
enum { KE_KEY, KE_END }; static const struct key_entry asus_keymap[] = {
/* Lenovo SL Specific keycodes */
static struct key_entry asus_keymap[] = { {KE_KEY, 0x02, { KEY_SCREENLOCK } },
{KE_KEY, 0x02, KEY_SCREENLOCK}, {KE_KEY, 0x05, { KEY_WLAN } },
{KE_KEY, 0x05, KEY_WLAN}, {KE_KEY, 0x08, { KEY_F13 } },
{KE_KEY, 0x08, KEY_F13}, {KE_KEY, 0x17, { KEY_ZOOM } },
{KE_KEY, 0x17, KEY_ZOOM}, {KE_KEY, 0x1f, { KEY_BATTERY } },
{KE_KEY, 0x1f, KEY_BATTERY}, /* End of Lenovo SL Specific keycodes */
{KE_KEY, 0x30, KEY_VOLUMEUP}, {KE_KEY, 0x30, { KEY_VOLUMEUP } },
{KE_KEY, 0x31, KEY_VOLUMEDOWN}, {KE_KEY, 0x31, { KEY_VOLUMEDOWN } },
{KE_KEY, 0x32, KEY_MUTE}, {KE_KEY, 0x32, { KEY_MUTE } },
{KE_KEY, 0x33, KEY_SWITCHVIDEOMODE}, {KE_KEY, 0x33, { KEY_SWITCHVIDEOMODE } },
{KE_KEY, 0x34, KEY_SWITCHVIDEOMODE}, {KE_KEY, 0x34, { KEY_SWITCHVIDEOMODE } },
{KE_KEY, 0x40, KEY_PREVIOUSSONG}, {KE_KEY, 0x40, { KEY_PREVIOUSSONG } },
{KE_KEY, 0x41, KEY_NEXTSONG}, {KE_KEY, 0x41, { KEY_NEXTSONG } },
{KE_KEY, 0x43, KEY_STOPCD}, {KE_KEY, 0x43, { KEY_STOPCD } },
{KE_KEY, 0x45, KEY_PLAYPAUSE}, {KE_KEY, 0x45, { KEY_PLAYPAUSE } },
{KE_KEY, 0x4c, KEY_MEDIA}, {KE_KEY, 0x4c, { KEY_MEDIA } },
{KE_KEY, 0x50, KEY_EMAIL}, {KE_KEY, 0x50, { KEY_EMAIL } },
{KE_KEY, 0x51, KEY_WWW}, {KE_KEY, 0x51, { KEY_WWW } },
{KE_KEY, 0x55, KEY_CALC}, {KE_KEY, 0x55, { KEY_CALC } },
{KE_KEY, 0x5C, KEY_SCREENLOCK}, /* Screenlock */ {KE_KEY, 0x5C, { KEY_SCREENLOCK } }, /* Screenlock */
{KE_KEY, 0x5D, KEY_WLAN}, {KE_KEY, 0x5D, { KEY_WLAN } },
{KE_KEY, 0x5E, KEY_WLAN}, {KE_KEY, 0x5E, { KEY_WLAN } },
{KE_KEY, 0x5F, KEY_WLAN}, {KE_KEY, 0x5F, { KEY_WLAN } },
{KE_KEY, 0x60, KEY_SWITCHVIDEOMODE}, {KE_KEY, 0x60, { KEY_SWITCHVIDEOMODE } },
{KE_KEY, 0x61, KEY_SWITCHVIDEOMODE}, {KE_KEY, 0x61, { KEY_SWITCHVIDEOMODE } },
{KE_KEY, 0x62, KEY_SWITCHVIDEOMODE}, {KE_KEY, 0x62, { KEY_SWITCHVIDEOMODE } },
{KE_KEY, 0x63, KEY_SWITCHVIDEOMODE}, {KE_KEY, 0x63, { KEY_SWITCHVIDEOMODE } },
{KE_KEY, 0x6B, KEY_F13}, /* Lock Touchpad */ {KE_KEY, 0x6B, { KEY_F13 } }, /* Lock Touchpad */
{KE_KEY, 0x82, KEY_CAMERA}, {KE_KEY, 0x7E, { KEY_BLUETOOTH } },
{KE_KEY, 0x88, KEY_WLAN }, {KE_KEY, 0x7D, { KEY_BLUETOOTH } },
{KE_KEY, 0x8A, KEY_PROG1}, {KE_KEY, 0x82, { KEY_CAMERA } },
{KE_KEY, 0x95, KEY_MEDIA}, {KE_KEY, 0x88, { KEY_WLAN } },
{KE_KEY, 0x99, KEY_PHONE}, {KE_KEY, 0x8A, { KEY_PROG1 } },
{KE_KEY, 0xc4, KEY_KBDILLUMUP}, {KE_KEY, 0x95, { KEY_MEDIA } },
{KE_KEY, 0xc5, KEY_KBDILLUMDOWN}, {KE_KEY, 0x99, { KEY_PHONE } },
{KE_KEY, 0xc4, { KEY_KBDILLUMUP } },
{KE_KEY, 0xc5, { KEY_KBDILLUMDOWN } },
{KE_END, 0}, {KE_END, 0},
}; };
/* /*
* This function evaluates an ACPI method, given an int as parameter, the * This function evaluates an ACPI method, given an int as parameter, the
* method is searched within the scope of the handle, can be NULL. The output * method is searched within the scope of the handle, can be NULL. The output
...@@ -339,7 +288,7 @@ static struct key_entry asus_keymap[] = { ...@@ -339,7 +288,7 @@ static struct key_entry asus_keymap[] = {
* *
* returns 0 if write is successful, -1 else. * returns 0 if write is successful, -1 else.
*/ */
static int write_acpi_int(acpi_handle handle, const char *method, int val, static int write_acpi_int_ret(acpi_handle handle, const char *method, int val,
struct acpi_buffer *output) struct acpi_buffer *output)
{ {
struct acpi_object_list params; /* list of input parameters (an int) */ struct acpi_object_list params; /* list of input parameters (an int) */
...@@ -361,102 +310,82 @@ static int write_acpi_int(acpi_handle handle, const char *method, int val, ...@@ -361,102 +310,82 @@ static int write_acpi_int(acpi_handle handle, const char *method, int val,
return -1; return -1;
} }
static int read_wireless_status(int mask) static int write_acpi_int(acpi_handle handle, const char *method, int val)
{ {
unsigned long long status; return write_acpi_int_ret(handle, method, val, NULL);
acpi_status rv = AE_OK; }
if (!wireless_status_handle) static int acpi_check_handle(acpi_handle handle, const char *method,
return (hotk->status & mask) ? 1 : 0; acpi_handle *ret)
{
acpi_status status;
rv = acpi_evaluate_integer(wireless_status_handle, NULL, NULL, &status); if (method == NULL)
if (ACPI_FAILURE(rv)) return -ENODEV;
pr_warning("Error reading Wireless status\n");
else
return (status & mask) ? 1 : 0;
return (hotk->status & mask) ? 1 : 0; if (ret)
status = acpi_get_handle(handle, (char *)method,
ret);
else {
acpi_handle dummy;
status = acpi_get_handle(handle, (char *)method,
&dummy);
}
if (status != AE_OK) {
if (ret)
pr_warning("Error finding %s\n", method);
return -ENODEV;
}
return 0;
} }
static int read_gps_status(void) /* Generic LED function */
static int asus_led_set(struct asus_laptop *asus, const char *method,
int value)
{ {
unsigned long long status; if (!strcmp(method, METHOD_MLED))
acpi_status rv = AE_OK; value = !value;
else if (!strcmp(method, METHOD_GLED))
rv = acpi_evaluate_integer(gps_status_handle, NULL, NULL, &status); value = !value + 1;
if (ACPI_FAILURE(rv))
pr_warning("Error reading GPS status\n");
else else
return status ? 1 : 0; value = !!value;
return (hotk->status & GPS_ON) ? 1 : 0; return write_acpi_int(asus->handle, method, value);
} }
/* Generic LED functions */ /*
static int read_status(int mask) * LEDs
*/
/* /sys/class/led handlers */
static void asus_led_cdev_set(struct led_classdev *led_cdev,
enum led_brightness value)
{ {
/* There is a special method for both wireless devices */ struct asus_led *led = container_of(led_cdev, struct asus_led, led);
if (mask == BT_ON || mask == WL_ON) struct asus_laptop *asus = led->asus;
return read_wireless_status(mask);
else if (mask == GPS_ON)
return read_gps_status();
return (hotk->status & mask) ? 1 : 0; led->wk = !!value;
queue_work(asus->led_workqueue, &led->work);
} }
static void write_status(acpi_handle handle, int out, int mask) static void asus_led_cdev_update(struct work_struct *work)
{ {
hotk->status = (out) ? (hotk->status | mask) : (hotk->status & ~mask); struct asus_led *led = container_of(work, struct asus_led, work);
struct asus_laptop *asus = led->asus;
switch (mask) { asus_led_set(asus, led->method, led->wk);
case MLED_ON:
out = !(out & 0x1);
break;
case GLED_ON:
out = (out & 0x1) + 1;
break;
case GPS_ON:
handle = (out) ? gps_on_handle : gps_off_handle;
out = 0x02;
break;
default:
out &= 0x1;
break;
}
if (write_acpi_int(handle, NULL, out, NULL))
pr_warning(" write failed %x\n", mask);
} }
/* /sys/class/led handlers */ static enum led_brightness asus_led_cdev_get(struct led_classdev *led_cdev)
#define ASUS_LED_HANDLER(object, mask) \ {
static void object##_led_set(struct led_classdev *led_cdev, \ return led_cdev->brightness;
enum led_brightness value) \ }
{ \
object##_led_wk = (value > 0) ? 1 : 0; \
queue_work(led_workqueue, &object##_led_work); \
} \
static void object##_led_update(struct work_struct *ignored) \
{ \
int value = object##_led_wk; \
write_status(object##_set_handle, value, (mask)); \
} \
static enum led_brightness object##_led_get( \
struct led_classdev *led_cdev) \
{ \
return led_cdev->brightness; \
}
ASUS_LED_HANDLER(mled, MLED_ON);
ASUS_LED_HANDLER(pled, PLED_ON);
ASUS_LED_HANDLER(rled, RLED_ON);
ASUS_LED_HANDLER(tled, TLED_ON);
ASUS_LED_HANDLER(gled, GLED_ON);
/* /*
* Keyboard backlight * Keyboard backlight (also a LED)
*/ */
static int get_kled_lvl(void) static int asus_kled_lvl(struct asus_laptop *asus)
{ {
unsigned long long kblv; unsigned long long kblv;
struct acpi_object_list params; struct acpi_object_list params;
...@@ -468,75 +397,183 @@ static int get_kled_lvl(void) ...@@ -468,75 +397,183 @@ static int get_kled_lvl(void)
in_obj.type = ACPI_TYPE_INTEGER; in_obj.type = ACPI_TYPE_INTEGER;
in_obj.integer.value = 2; in_obj.integer.value = 2;
rv = acpi_evaluate_integer(kled_get_handle, NULL, &params, &kblv); rv = acpi_evaluate_integer(asus->handle, METHOD_KBD_LIGHT_GET,
&params, &kblv);
if (ACPI_FAILURE(rv)) { if (ACPI_FAILURE(rv)) {
pr_warning("Error reading kled level\n"); pr_warning("Error reading kled level\n");
return 0; return -ENODEV;
} }
return kblv; return kblv;
} }
static int set_kled_lvl(int kblv) static int asus_kled_set(struct asus_laptop *asus, int kblv)
{ {
if (kblv > 0) if (kblv > 0)
kblv = (1 << 7) | (kblv & 0x7F); kblv = (1 << 7) | (kblv & 0x7F);
else else
kblv = 0; kblv = 0;
if (write_acpi_int(kled_set_handle, NULL, kblv, NULL)) { if (write_acpi_int(asus->handle, METHOD_KBD_LIGHT_SET, kblv)) {
pr_warning("Keyboard LED display write failed\n"); pr_warning("Keyboard LED display write failed\n");
return -EINVAL; return -EINVAL;
} }
return 0; return 0;
} }
static void kled_led_set(struct led_classdev *led_cdev, static void asus_kled_cdev_set(struct led_classdev *led_cdev,
enum led_brightness value) enum led_brightness value)
{ {
kled_led_wk = value; struct asus_led *led = container_of(led_cdev, struct asus_led, led);
queue_work(led_workqueue, &kled_led_work); struct asus_laptop *asus = led->asus;
led->wk = value;
queue_work(asus->led_workqueue, &led->work);
}
static void asus_kled_cdev_update(struct work_struct *work)
{
struct asus_led *led = container_of(work, struct asus_led, work);
struct asus_laptop *asus = led->asus;
asus_kled_set(asus, led->wk);
} }
static void kled_led_update(struct work_struct *ignored) static enum led_brightness asus_kled_cdev_get(struct led_classdev *led_cdev)
{ {
set_kled_lvl(kled_led_wk); struct asus_led *led = container_of(led_cdev, struct asus_led, led);
struct asus_laptop *asus = led->asus;
return asus_kled_lvl(asus);
} }
static enum led_brightness kled_led_get(struct led_classdev *led_cdev) static void asus_led_exit(struct asus_laptop *asus)
{ {
return get_kled_lvl(); if (asus->mled.led.dev)
led_classdev_unregister(&asus->mled.led);
if (asus->tled.led.dev)
led_classdev_unregister(&asus->tled.led);
if (asus->pled.led.dev)
led_classdev_unregister(&asus->pled.led);
if (asus->rled.led.dev)
led_classdev_unregister(&asus->rled.led);
if (asus->gled.led.dev)
led_classdev_unregister(&asus->gled.led);
if (asus->kled.led.dev)
led_classdev_unregister(&asus->kled.led);
if (asus->led_workqueue) {
destroy_workqueue(asus->led_workqueue);
asus->led_workqueue = NULL;
}
} }
static int get_lcd_state(void) /* Ugly macro, need to fix that later */
static int asus_led_register(struct asus_laptop *asus,
struct asus_led *led,
const char *name, const char *method)
{
struct led_classdev *led_cdev = &led->led;
if (!method || acpi_check_handle(asus->handle, method, NULL))
return 0; /* Led not present */
led->asus = asus;
led->method = method;
INIT_WORK(&led->work, asus_led_cdev_update);
led_cdev->name = name;
led_cdev->brightness_set = asus_led_cdev_set;
led_cdev->brightness_get = asus_led_cdev_get;
led_cdev->max_brightness = 1;
return led_classdev_register(&asus->platform_device->dev, led_cdev);
}
static int asus_led_init(struct asus_laptop *asus)
{
int r;
/*
* Functions that actually update the LED's are called from a
* workqueue. By doing this as separate work rather than when the LED
* subsystem asks, we avoid messing with the Asus ACPI stuff during a
* potentially bad time, such as a timer interrupt.
*/
asus->led_workqueue = create_singlethread_workqueue("led_workqueue");
if (!asus->led_workqueue)
return -ENOMEM;
r = asus_led_register(asus, &asus->mled, "asus::mail", METHOD_MLED);
if (r)
goto error;
r = asus_led_register(asus, &asus->tled, "asus::touchpad", METHOD_TLED);
if (r)
goto error;
r = asus_led_register(asus, &asus->rled, "asus::record", METHOD_RLED);
if (r)
goto error;
r = asus_led_register(asus, &asus->pled, "asus::phone", METHOD_PLED);
if (r)
goto error;
r = asus_led_register(asus, &asus->gled, "asus::gaming", METHOD_GLED);
if (r)
goto error;
if (!acpi_check_handle(asus->handle, METHOD_KBD_LIGHT_SET, NULL) &&
!acpi_check_handle(asus->handle, METHOD_KBD_LIGHT_GET, NULL)) {
struct asus_led *led = &asus->kled;
struct led_classdev *cdev = &led->led;
led->asus = asus;
INIT_WORK(&led->work, asus_kled_cdev_update);
cdev->name = "asus::kbd_backlight";
cdev->brightness_set = asus_kled_cdev_set;
cdev->brightness_get = asus_kled_cdev_get;
cdev->max_brightness = 3;
r = led_classdev_register(&asus->platform_device->dev, cdev);
}
error:
if (r)
asus_led_exit(asus);
return r;
}
/*
* Backlight device
*/
static int asus_lcd_status(struct asus_laptop *asus)
{ {
return read_status(LCD_ON); return asus->lcd_state;
} }
static int set_lcd_state(int value) static int asus_lcd_set(struct asus_laptop *asus, int value)
{ {
int lcd = 0; int lcd = 0;
acpi_status status = 0; acpi_status status = 0;
lcd = value ? 1 : 0; lcd = !!value;
if (lcd == get_lcd_state()) if (lcd == asus_lcd_status(asus))
return 0; return 0;
if (lcd_switch_handle) { if (!lcd_switch_handle)
return -ENODEV;
status = acpi_evaluate_object(lcd_switch_handle, status = acpi_evaluate_object(lcd_switch_handle,
NULL, NULL, NULL); NULL, NULL, NULL);
if (ACPI_FAILURE(status)) if (ACPI_FAILURE(status)) {
pr_warning("Error switching LCD\n"); pr_warning("Error switching LCD\n");
return -ENODEV;
} }
write_status(NULL, lcd, LCD_ON); asus->lcd_state = lcd;
return 0; return 0;
} }
static void lcd_blank(int blank) static void lcd_blank(struct asus_laptop *asus, int blank)
{ {
struct backlight_device *bd = asus_backlight_device; struct backlight_device *bd = asus->backlight_device;
asus->lcd_state = (blank == FB_BLANK_UNBLANK);
if (bd) { if (bd) {
bd->props.power = blank; bd->props.power = blank;
...@@ -544,44 +581,91 @@ static void lcd_blank(int blank) ...@@ -544,44 +581,91 @@ static void lcd_blank(int blank)
} }
} }
static int read_brightness(struct backlight_device *bd) static int asus_read_brightness(struct backlight_device *bd)
{ {
struct asus_laptop *asus = bl_get_data(bd);
unsigned long long value; unsigned long long value;
acpi_status rv = AE_OK; acpi_status rv = AE_OK;
rv = acpi_evaluate_integer(brightness_get_handle, NULL, NULL, &value); rv = acpi_evaluate_integer(asus->handle, METHOD_BRIGHTNESS_GET,
NULL, &value);
if (ACPI_FAILURE(rv)) if (ACPI_FAILURE(rv))
pr_warning("Error reading brightness\n"); pr_warning("Error reading brightness\n");
return value; return value;
} }
static int set_brightness(struct backlight_device *bd, int value) static int asus_set_brightness(struct backlight_device *bd, int value)
{ {
int ret = 0; struct asus_laptop *asus = bl_get_data(bd);
value = (0 < value) ? ((15 < value) ? 15 : value) : 0;
/* 0 <= value <= 15 */
if (write_acpi_int(brightness_set_handle, NULL, value, NULL)) { if (write_acpi_int(asus->handle, METHOD_BRIGHTNESS_SET, value)) {
pr_warning("Error changing brightness\n"); pr_warning("Error changing brightness\n");
ret = -EIO; return -EIO;
} }
return 0;
return ret;
} }
static int update_bl_status(struct backlight_device *bd) static int update_bl_status(struct backlight_device *bd)
{ {
struct asus_laptop *asus = bl_get_data(bd);
int rv; int rv;
int value = bd->props.brightness; int value = bd->props.brightness;
rv = set_brightness(bd, value); rv = asus_set_brightness(bd, value);
if (rv) if (rv)
return rv; return rv;
value = (bd->props.power == FB_BLANK_UNBLANK) ? 1 : 0; value = (bd->props.power == FB_BLANK_UNBLANK) ? 1 : 0;
return set_lcd_state(value); return asus_lcd_set(asus, value);
}
static struct backlight_ops asusbl_ops = {
.get_brightness = asus_read_brightness,
.update_status = update_bl_status,
};
static int asus_backlight_notify(struct asus_laptop *asus)
{
struct backlight_device *bd = asus->backlight_device;
int old = bd->props.brightness;
backlight_force_update(bd, BACKLIGHT_UPDATE_HOTKEY);
return old;
}
static int asus_backlight_init(struct asus_laptop *asus)
{
struct backlight_device *bd;
struct device *dev = &asus->platform_device->dev;
if (!acpi_check_handle(asus->handle, METHOD_BRIGHTNESS_GET, NULL) &&
!acpi_check_handle(asus->handle, METHOD_BRIGHTNESS_SET, NULL) &&
lcd_switch_handle) {
bd = backlight_device_register(ASUS_LAPTOP_FILE, dev,
asus, &asusbl_ops);
if (IS_ERR(bd)) {
pr_err("Could not register asus backlight device\n");
asus->backlight_device = NULL;
return PTR_ERR(bd);
}
asus->backlight_device = bd;
bd->props.max_brightness = 15;
bd->props.power = FB_BLANK_UNBLANK;
bd->props.brightness = asus_read_brightness(bd);
backlight_update_status(bd);
}
return 0;
}
static void asus_backlight_exit(struct asus_laptop *asus)
{
if (asus->backlight_device)
backlight_device_unregister(asus->backlight_device);
asus->backlight_device = NULL;
} }
/* /*
...@@ -596,25 +680,26 @@ static int update_bl_status(struct backlight_device *bd) ...@@ -596,25 +680,26 @@ static int update_bl_status(struct backlight_device *bd)
static ssize_t show_infos(struct device *dev, static ssize_t show_infos(struct device *dev,
struct device_attribute *attr, char *page) struct device_attribute *attr, char *page)
{ {
struct asus_laptop *asus = dev_get_drvdata(dev);
int len = 0; int len = 0;
unsigned long long temp; unsigned long long temp;
char buf[16]; /* enough for all info */ char buf[16]; /* enough for all info */
acpi_status rv = AE_OK; acpi_status rv = AE_OK;
/* /*
* We use the easy way, we don't care of off and count, so we don't set eof * We use the easy way, we don't care of off and count,
* to 1 * so we don't set eof to 1
*/ */
len += sprintf(page, ASUS_HOTK_NAME " " ASUS_LAPTOP_VERSION "\n"); len += sprintf(page, ASUS_LAPTOP_NAME " " ASUS_LAPTOP_VERSION "\n");
len += sprintf(page + len, "Model reference : %s\n", hotk->name); len += sprintf(page + len, "Model reference : %s\n", asus->name);
/* /*
* The SFUN method probably allows the original driver to get the list * The SFUN method probably allows the original driver to get the list
* of features supported by a given model. For now, 0x0100 or 0x0800 * of features supported by a given model. For now, 0x0100 or 0x0800
* bit signifies that the laptop is equipped with a Wi-Fi MiniPCI card. * bit signifies that the laptop is equipped with a Wi-Fi MiniPCI card.
* The significance of others is yet to be found. * The significance of others is yet to be found.
*/ */
rv = acpi_evaluate_integer(hotk->handle, "SFUN", NULL, &temp); rv = acpi_evaluate_integer(asus->handle, "SFUN", NULL, &temp);
if (!ACPI_FAILURE(rv)) if (!ACPI_FAILURE(rv))
len += sprintf(page + len, "SFUN value : %#x\n", len += sprintf(page + len, "SFUN value : %#x\n",
(uint) temp); (uint) temp);
...@@ -624,7 +709,7 @@ static ssize_t show_infos(struct device *dev, ...@@ -624,7 +709,7 @@ static ssize_t show_infos(struct device *dev,
* The significance of others is yet to be found. * The significance of others is yet to be found.
* If we don't find the method, we assume the device are present. * If we don't find the method, we assume the device are present.
*/ */
rv = acpi_evaluate_integer(hotk->handle, "HRWS", NULL, &temp); rv = acpi_evaluate_integer(asus->handle, "HRWS", NULL, &temp);
if (!ACPI_FAILURE(rv)) if (!ACPI_FAILURE(rv))
len += sprintf(page + len, "HRWS value : %#x\n", len += sprintf(page + len, "HRWS value : %#x\n",
(uint) temp); (uint) temp);
...@@ -635,26 +720,26 @@ static ssize_t show_infos(struct device *dev, ...@@ -635,26 +720,26 @@ static ssize_t show_infos(struct device *dev,
* Note: since not all the laptops provide this method, errors are * Note: since not all the laptops provide this method, errors are
* silently ignored. * silently ignored.
*/ */
rv = acpi_evaluate_integer(hotk->handle, "ASYM", NULL, &temp); rv = acpi_evaluate_integer(asus->handle, "ASYM", NULL, &temp);
if (!ACPI_FAILURE(rv)) if (!ACPI_FAILURE(rv))
len += sprintf(page + len, "ASYM value : %#x\n", len += sprintf(page + len, "ASYM value : %#x\n",
(uint) temp); (uint) temp);
if (asus_info) { if (asus->dsdt_info) {
snprintf(buf, 16, "%d", asus_info->length); snprintf(buf, 16, "%d", asus->dsdt_info->length);
len += sprintf(page + len, "DSDT length : %s\n", buf); len += sprintf(page + len, "DSDT length : %s\n", buf);
snprintf(buf, 16, "%d", asus_info->checksum); snprintf(buf, 16, "%d", asus->dsdt_info->checksum);
len += sprintf(page + len, "DSDT checksum : %s\n", buf); len += sprintf(page + len, "DSDT checksum : %s\n", buf);
snprintf(buf, 16, "%d", asus_info->revision); snprintf(buf, 16, "%d", asus->dsdt_info->revision);
len += sprintf(page + len, "DSDT revision : %s\n", buf); len += sprintf(page + len, "DSDT revision : %s\n", buf);
snprintf(buf, 7, "%s", asus_info->oem_id); snprintf(buf, 7, "%s", asus->dsdt_info->oem_id);
len += sprintf(page + len, "OEM id : %s\n", buf); len += sprintf(page + len, "OEM id : %s\n", buf);
snprintf(buf, 9, "%s", asus_info->oem_table_id); snprintf(buf, 9, "%s", asus->dsdt_info->oem_table_id);
len += sprintf(page + len, "OEM table id : %s\n", buf); len += sprintf(page + len, "OEM table id : %s\n", buf);
snprintf(buf, 16, "%x", asus_info->oem_revision); snprintf(buf, 16, "%x", asus->dsdt_info->oem_revision);
len += sprintf(page + len, "OEM revision : 0x%s\n", buf); len += sprintf(page + len, "OEM revision : 0x%s\n", buf);
snprintf(buf, 5, "%s", asus_info->asl_compiler_id); snprintf(buf, 5, "%s", asus->dsdt_info->asl_compiler_id);
len += sprintf(page + len, "ASL comp vendor id : %s\n", buf); len += sprintf(page + len, "ASL comp vendor id : %s\n", buf);
snprintf(buf, 16, "%x", asus_info->asl_compiler_revision); snprintf(buf, 16, "%x", asus->dsdt_info->asl_compiler_revision);
len += sprintf(page + len, "ASL comp revision : 0x%s\n", buf); len += sprintf(page + len, "ASL comp revision : 0x%s\n", buf);
} }
...@@ -672,8 +757,9 @@ static int parse_arg(const char *buf, unsigned long count, int *val) ...@@ -672,8 +757,9 @@ static int parse_arg(const char *buf, unsigned long count, int *val)
return count; return count;
} }
static ssize_t store_status(const char *buf, size_t count, static ssize_t sysfs_acpi_set(struct asus_laptop *asus,
acpi_handle handle, int mask) const char *buf, size_t count,
const char *method)
{ {
int rv, value; int rv, value;
int out = 0; int out = 0;
...@@ -682,8 +768,8 @@ static ssize_t store_status(const char *buf, size_t count, ...@@ -682,8 +768,8 @@ static ssize_t store_status(const char *buf, size_t count,
if (rv > 0) if (rv > 0)
out = value ? 1 : 0; out = value ? 1 : 0;
write_status(handle, out, mask); if (write_acpi_int(asus->handle, method, value))
return -ENODEV;
return rv; return rv;
} }
...@@ -693,67 +779,116 @@ static ssize_t store_status(const char *buf, size_t count, ...@@ -693,67 +779,116 @@ static ssize_t store_status(const char *buf, size_t count,
static ssize_t show_ledd(struct device *dev, static ssize_t show_ledd(struct device *dev,
struct device_attribute *attr, char *buf) struct device_attribute *attr, char *buf)
{ {
return sprintf(buf, "0x%08x\n", hotk->ledd_status); struct asus_laptop *asus = dev_get_drvdata(dev);
return sprintf(buf, "0x%08x\n", asus->ledd_status);
} }
static ssize_t store_ledd(struct device *dev, struct device_attribute *attr, static ssize_t store_ledd(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count) const char *buf, size_t count)
{ {
struct asus_laptop *asus = dev_get_drvdata(dev);
int rv, value; int rv, value;
rv = parse_arg(buf, count, &value); rv = parse_arg(buf, count, &value);
if (rv > 0) { if (rv > 0) {
if (write_acpi_int(ledd_set_handle, NULL, value, NULL)) if (write_acpi_int(asus->handle, METHOD_LEDD, value))
pr_warning("LED display write failed\n"); pr_warning("LED display write failed\n");
else else
hotk->ledd_status = (u32) value; asus->ledd_status = (u32) value;
} }
return rv; return rv;
} }
/*
* Wireless
*/
static int asus_wireless_status(struct asus_laptop *asus, int mask)
{
unsigned long long status;
acpi_status rv = AE_OK;
if (!asus->have_rsts)
return (asus->wireless_status & mask) ? 1 : 0;
rv = acpi_evaluate_integer(asus->handle, METHOD_WL_STATUS,
NULL, &status);
if (ACPI_FAILURE(rv)) {
pr_warning("Error reading Wireless status\n");
return -EINVAL;
}
return !!(status & mask);
}
/* /*
* WLAN * WLAN
*/ */
static int asus_wlan_set(struct asus_laptop *asus, int status)
{
if (write_acpi_int(asus->handle, METHOD_WLAN, !!status)) {
pr_warning("Error setting wlan status to %d", status);
return -EIO;
}
return 0;
}
static ssize_t show_wlan(struct device *dev, static ssize_t show_wlan(struct device *dev,
struct device_attribute *attr, char *buf) struct device_attribute *attr, char *buf)
{ {
return sprintf(buf, "%d\n", read_status(WL_ON)); struct asus_laptop *asus = dev_get_drvdata(dev);
return sprintf(buf, "%d\n", asus_wireless_status(asus, WL_RSTS));
} }
static ssize_t store_wlan(struct device *dev, struct device_attribute *attr, static ssize_t store_wlan(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count) const char *buf, size_t count)
{ {
return store_status(buf, count, wl_switch_handle, WL_ON); struct asus_laptop *asus = dev_get_drvdata(dev);
return sysfs_acpi_set(asus, buf, count, METHOD_WLAN);
} }
/* /*
* Bluetooth * Bluetooth
*/ */
static int asus_bluetooth_set(struct asus_laptop *asus, int status)
{
if (write_acpi_int(asus->handle, METHOD_BLUETOOTH, !!status)) {
pr_warning("Error setting bluetooth status to %d", status);
return -EIO;
}
return 0;
}
static ssize_t show_bluetooth(struct device *dev, static ssize_t show_bluetooth(struct device *dev,
struct device_attribute *attr, char *buf) struct device_attribute *attr, char *buf)
{ {
return sprintf(buf, "%d\n", read_status(BT_ON)); struct asus_laptop *asus = dev_get_drvdata(dev);
return sprintf(buf, "%d\n", asus_wireless_status(asus, BT_RSTS));
} }
static ssize_t store_bluetooth(struct device *dev, static ssize_t store_bluetooth(struct device *dev,
struct device_attribute *attr, const char *buf, struct device_attribute *attr, const char *buf,
size_t count) size_t count)
{ {
return store_status(buf, count, bt_switch_handle, BT_ON); struct asus_laptop *asus = dev_get_drvdata(dev);
return sysfs_acpi_set(asus, buf, count, METHOD_BLUETOOTH);
} }
/* /*
* Display * Display
*/ */
static void set_display(int value) static void asus_set_display(struct asus_laptop *asus, int value)
{ {
/* no sanity check needed for now */ /* no sanity check needed for now */
if (write_acpi_int(display_set_handle, NULL, value, NULL)) if (write_acpi_int(asus->handle, METHOD_SWITCH_DISPLAY, value))
pr_warning("Error setting display\n"); pr_warning("Error setting display\n");
return; return;
} }
static int read_display(void) static int read_display(struct asus_laptop *asus)
{ {
unsigned long long value = 0; unsigned long long value = 0;
acpi_status rv = AE_OK; acpi_status rv = AE_OK;
...@@ -781,7 +916,11 @@ static int read_display(void) ...@@ -781,7 +916,11 @@ static int read_display(void)
static ssize_t show_disp(struct device *dev, static ssize_t show_disp(struct device *dev,
struct device_attribute *attr, char *buf) struct device_attribute *attr, char *buf)
{ {
return sprintf(buf, "%d\n", read_display()); struct asus_laptop *asus = dev_get_drvdata(dev);
if (!display_get_handle)
return -ENODEV;
return sprintf(buf, "%d\n", read_display(asus));
} }
/* /*
...@@ -794,65 +933,72 @@ static ssize_t show_disp(struct device *dev, ...@@ -794,65 +933,72 @@ static ssize_t show_disp(struct device *dev,
static ssize_t store_disp(struct device *dev, struct device_attribute *attr, static ssize_t store_disp(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count) const char *buf, size_t count)
{ {
struct asus_laptop *asus = dev_get_drvdata(dev);
int rv, value; int rv, value;
rv = parse_arg(buf, count, &value); rv = parse_arg(buf, count, &value);
if (rv > 0) if (rv > 0)
set_display(value); asus_set_display(asus, value);
return rv; return rv;
} }
/* /*
* Light Sens * Light Sens
*/ */
static void set_light_sens_switch(int value) static void asus_als_switch(struct asus_laptop *asus, int value)
{ {
if (write_acpi_int(ls_switch_handle, NULL, value, NULL)) if (write_acpi_int(asus->handle, METHOD_ALS_CONTROL, value))
pr_warning("Error setting light sensor switch\n"); pr_warning("Error setting light sensor switch\n");
hotk->light_switch = value; asus->light_switch = value;
} }
static ssize_t show_lssw(struct device *dev, static ssize_t show_lssw(struct device *dev,
struct device_attribute *attr, char *buf) struct device_attribute *attr, char *buf)
{ {
return sprintf(buf, "%d\n", hotk->light_switch); struct asus_laptop *asus = dev_get_drvdata(dev);
return sprintf(buf, "%d\n", asus->light_switch);
} }
static ssize_t store_lssw(struct device *dev, struct device_attribute *attr, static ssize_t store_lssw(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count) const char *buf, size_t count)
{ {
struct asus_laptop *asus = dev_get_drvdata(dev);
int rv, value; int rv, value;
rv = parse_arg(buf, count, &value); rv = parse_arg(buf, count, &value);
if (rv > 0) if (rv > 0)
set_light_sens_switch(value ? 1 : 0); asus_als_switch(asus, value ? 1 : 0);
return rv; return rv;
} }
static void set_light_sens_level(int value) static void asus_als_level(struct asus_laptop *asus, int value)
{ {
if (write_acpi_int(ls_level_handle, NULL, value, NULL)) if (write_acpi_int(asus->handle, METHOD_ALS_LEVEL, value))
pr_warning("Error setting light sensor level\n"); pr_warning("Error setting light sensor level\n");
hotk->light_level = value; asus->light_level = value;
} }
static ssize_t show_lslvl(struct device *dev, static ssize_t show_lslvl(struct device *dev,
struct device_attribute *attr, char *buf) struct device_attribute *attr, char *buf)
{ {
return sprintf(buf, "%d\n", hotk->light_level); struct asus_laptop *asus = dev_get_drvdata(dev);
return sprintf(buf, "%d\n", asus->light_level);
} }
static ssize_t store_lslvl(struct device *dev, struct device_attribute *attr, static ssize_t store_lslvl(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count) const char *buf, size_t count)
{ {
struct asus_laptop *asus = dev_get_drvdata(dev);
int rv, value; int rv, value;
rv = parse_arg(buf, count, &value); rv = parse_arg(buf, count, &value);
if (rv > 0) { if (rv > 0) {
value = (0 < value) ? ((15 < value) ? 15 : value) : 0; value = (0 < value) ? ((15 < value) ? 15 : value) : 0;
/* 0 <= value <= 15 */ /* 0 <= value <= 15 */
set_light_sens_level(value); asus_als_level(asus, value);
} }
return rv; return rv;
...@@ -861,197 +1007,309 @@ static ssize_t store_lslvl(struct device *dev, struct device_attribute *attr, ...@@ -861,197 +1007,309 @@ static ssize_t store_lslvl(struct device *dev, struct device_attribute *attr,
/* /*
* GPS * GPS
*/ */
static int asus_gps_status(struct asus_laptop *asus)
{
unsigned long long status;
acpi_status rv = AE_OK;
rv = acpi_evaluate_integer(asus->handle, METHOD_GPS_STATUS,
NULL, &status);
if (ACPI_FAILURE(rv)) {
pr_warning("Error reading GPS status\n");
return -ENODEV;
}
return !!status;
}
static int asus_gps_switch(struct asus_laptop *asus, int status)
{
const char *meth = status ? METHOD_GPS_ON : METHOD_GPS_OFF;
if (write_acpi_int(asus->handle, meth, 0x02))
return -ENODEV;
return 0;
}
static ssize_t show_gps(struct device *dev, static ssize_t show_gps(struct device *dev,
struct device_attribute *attr, char *buf) struct device_attribute *attr, char *buf)
{ {
return sprintf(buf, "%d\n", read_status(GPS_ON)); struct asus_laptop *asus = dev_get_drvdata(dev);
return sprintf(buf, "%d\n", asus_gps_status(asus));
} }
static ssize_t store_gps(struct device *dev, struct device_attribute *attr, static ssize_t store_gps(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count) const char *buf, size_t count)
{ {
return store_status(buf, count, NULL, GPS_ON); struct asus_laptop *asus = dev_get_drvdata(dev);
int rv, value;
int ret;
rv = parse_arg(buf, count, &value);
if (rv <= 0)
return -EINVAL;
ret = asus_gps_switch(asus, !!value);
if (ret)
return ret;
rfkill_set_sw_state(asus->gps_rfkill, !value);
return rv;
} }
/* /*
* Hotkey functions * rfkill
*/ */
static struct key_entry *asus_get_entry_by_scancode(int code) static int asus_gps_rfkill_set(void *data, bool blocked)
{ {
struct key_entry *key; acpi_handle handle = data;
for (key = asus_keymap; key->type != KE_END; key++) return asus_gps_switch(handle, !blocked);
if (code == key->code)
return key;
return NULL;
} }
static struct key_entry *asus_get_entry_by_keycode(int code) static const struct rfkill_ops asus_gps_rfkill_ops = {
{ .set_block = asus_gps_rfkill_set,
struct key_entry *key; };
for (key = asus_keymap; key->type != KE_END; key++)
if (code == key->keycode && key->type == KE_KEY)
return key;
return NULL; static void asus_rfkill_exit(struct asus_laptop *asus)
{
if (asus->gps_rfkill) {
rfkill_unregister(asus->gps_rfkill);
rfkill_destroy(asus->gps_rfkill);
asus->gps_rfkill = NULL;
}
} }
static int asus_getkeycode(struct input_dev *dev, int scancode, int *keycode) static int asus_rfkill_init(struct asus_laptop *asus)
{ {
struct key_entry *key = asus_get_entry_by_scancode(scancode); int result;
if (key && key->type == KE_KEY) { if (acpi_check_handle(asus->handle, METHOD_GPS_ON, NULL) ||
*keycode = key->keycode; acpi_check_handle(asus->handle, METHOD_GPS_OFF, NULL) ||
acpi_check_handle(asus->handle, METHOD_GPS_STATUS, NULL))
return 0; return 0;
}
asus->gps_rfkill = rfkill_alloc("asus-gps", &asus->platform_device->dev,
RFKILL_TYPE_GPS,
&asus_gps_rfkill_ops, NULL);
if (!asus->gps_rfkill)
return -EINVAL; return -EINVAL;
result = rfkill_register(asus->gps_rfkill);
if (result) {
rfkill_destroy(asus->gps_rfkill);
asus->gps_rfkill = NULL;
}
return result;
} }
static int asus_setkeycode(struct input_dev *dev, int scancode, int keycode) /*
* Input device (i.e. hotkeys)
*/
static void asus_input_notify(struct asus_laptop *asus, int event)
{ {
struct key_entry *key; if (asus->inputdev)
int old_keycode; sparse_keymap_report_event(asus->inputdev, event, 1, true);
}
if (keycode < 0 || keycode > KEY_MAX) static int asus_input_init(struct asus_laptop *asus)
return -EINVAL; {
struct input_dev *input;
int error;
key = asus_get_entry_by_scancode(scancode); input = input_allocate_device();
if (key && key->type == KE_KEY) { if (!input) {
old_keycode = key->keycode; pr_info("Unable to allocate input device\n");
key->keycode = keycode;
set_bit(keycode, dev->keybit);
if (!asus_get_entry_by_keycode(old_keycode))
clear_bit(old_keycode, dev->keybit);
return 0; return 0;
} }
input->name = "Asus Laptop extra buttons";
input->phys = ASUS_LAPTOP_FILE "/input0";
input->id.bustype = BUS_HOST;
input->dev.parent = &asus->platform_device->dev;
input_set_drvdata(input, asus);
error = sparse_keymap_setup(input, asus_keymap, NULL);
if (error) {
pr_err("Unable to setup input device keymap\n");
goto err_keymap;
}
error = input_register_device(input);
if (error) {
pr_info("Unable to register input device\n");
goto err_device;
}
return -EINVAL; asus->inputdev = input;
return 0;
err_keymap:
sparse_keymap_free(input);
err_device:
input_free_device(input);
return error;
} }
static void asus_hotk_notify(struct acpi_device *device, u32 event) static void asus_input_exit(struct asus_laptop *asus)
{ {
static struct key_entry *key; if (asus->inputdev) {
u16 count; sparse_keymap_free(asus->inputdev);
input_unregister_device(asus->inputdev);
}
}
/* TODO Find a better way to handle events count. */ /*
if (!hotk) * ACPI driver
return; */
static void asus_acpi_notify(struct acpi_device *device, u32 event)
{
struct asus_laptop *asus = acpi_driver_data(device);
u16 count;
/* /*
* We need to tell the backlight device when the backlight power is * We need to tell the backlight device when the backlight power is
* switched * switched
*/ */
if (event == ATKD_LCD_ON) { if (event == ATKD_LCD_ON)
write_status(NULL, 1, LCD_ON); lcd_blank(asus, FB_BLANK_UNBLANK);
lcd_blank(FB_BLANK_UNBLANK); else if (event == ATKD_LCD_OFF)
} else if (event == ATKD_LCD_OFF) { lcd_blank(asus, FB_BLANK_POWERDOWN);
write_status(NULL, 0, LCD_ON);
lcd_blank(FB_BLANK_POWERDOWN);
}
count = hotk->event_count[event % 128]++; /* TODO Find a better way to handle events count. */
acpi_bus_generate_proc_event(hotk->device, event, count); count = asus->event_count[event % 128]++;
acpi_bus_generate_netlink_event(hotk->device->pnp.device_class, acpi_bus_generate_proc_event(asus->device, event, count);
dev_name(&hotk->device->dev), event, acpi_bus_generate_netlink_event(asus->device->pnp.device_class,
dev_name(&asus->device->dev), event,
count); count);
if (hotk->inputdev) { /* Brightness events are special */
key = asus_get_entry_by_scancode(event); if (event >= ATKD_BR_MIN && event <= ATKD_BR_MAX) {
if (!key)
return ;
switch (key->type) { /* Ignore them completely if the acpi video driver is used */
case KE_KEY: if (asus->backlight_device != NULL) {
input_report_key(hotk->inputdev, key->keycode, 1); /* Update the backlight device. */
input_sync(hotk->inputdev); asus_backlight_notify(asus);
input_report_key(hotk->inputdev, key->keycode, 0);
input_sync(hotk->inputdev);
break;
} }
return ;
} }
asus_input_notify(asus, event);
} }
#define ASUS_CREATE_DEVICE_ATTR(_name) \ static DEVICE_ATTR(infos, S_IRUGO, show_infos, NULL);
struct device_attribute dev_attr_##_name = { \ static DEVICE_ATTR(wlan, S_IRUGO | S_IWUSR, show_wlan, store_wlan);
.attr = { \ static DEVICE_ATTR(bluetooth, S_IRUGO | S_IWUSR, show_bluetooth,
.name = __stringify(_name), \ store_bluetooth);
.mode = 0 }, \ static DEVICE_ATTR(display, S_IRUGO | S_IWUSR, show_disp, store_disp);
.show = NULL, \ static DEVICE_ATTR(ledd, S_IRUGO | S_IWUSR, show_ledd, store_ledd);
.store = NULL, \ static DEVICE_ATTR(ls_level, S_IRUGO | S_IWUSR, show_lslvl, store_lslvl);
static DEVICE_ATTR(ls_switch, S_IRUGO | S_IWUSR, show_lssw, store_lssw);
static DEVICE_ATTR(gps, S_IRUGO | S_IWUSR, show_gps, store_gps);
static void asus_sysfs_exit(struct asus_laptop *asus)
{
struct platform_device *device = asus->platform_device;
device_remove_file(&device->dev, &dev_attr_infos);
device_remove_file(&device->dev, &dev_attr_wlan);
device_remove_file(&device->dev, &dev_attr_bluetooth);
device_remove_file(&device->dev, &dev_attr_display);
device_remove_file(&device->dev, &dev_attr_ledd);
device_remove_file(&device->dev, &dev_attr_ls_switch);
device_remove_file(&device->dev, &dev_attr_ls_level);
device_remove_file(&device->dev, &dev_attr_gps);
}
static int asus_sysfs_init(struct asus_laptop *asus)
{
struct platform_device *device = asus->platform_device;
int err;
err = device_create_file(&device->dev, &dev_attr_infos);
if (err)
return err;
if (!acpi_check_handle(asus->handle, METHOD_WLAN, NULL)) {
err = device_create_file(&device->dev, &dev_attr_wlan);
if (err)
return err;
} }
#define ASUS_SET_DEVICE_ATTR(_name, _mode, _show, _store) \ if (!acpi_check_handle(asus->handle, METHOD_BLUETOOTH, NULL)) {
do { \ err = device_create_file(&device->dev, &dev_attr_bluetooth);
dev_attr_##_name.attr.mode = _mode; \ if (err)
dev_attr_##_name.show = _show; \ return err;
dev_attr_##_name.store = _store; \ }
} while(0)
static ASUS_CREATE_DEVICE_ATTR(infos);
static ASUS_CREATE_DEVICE_ATTR(wlan);
static ASUS_CREATE_DEVICE_ATTR(bluetooth);
static ASUS_CREATE_DEVICE_ATTR(display);
static ASUS_CREATE_DEVICE_ATTR(ledd);
static ASUS_CREATE_DEVICE_ATTR(ls_switch);
static ASUS_CREATE_DEVICE_ATTR(ls_level);
static ASUS_CREATE_DEVICE_ATTR(gps);
static struct attribute *asuspf_attributes[] = {
&dev_attr_infos.attr,
&dev_attr_wlan.attr,
&dev_attr_bluetooth.attr,
&dev_attr_display.attr,
&dev_attr_ledd.attr,
&dev_attr_ls_switch.attr,
&dev_attr_ls_level.attr,
&dev_attr_gps.attr,
NULL
};
static struct attribute_group asuspf_attribute_group = { if (!acpi_check_handle(asus->handle, METHOD_SWITCH_DISPLAY, NULL)) {
.attrs = asuspf_attributes err = device_create_file(&device->dev, &dev_attr_display);
}; if (err)
return err;
}
static struct platform_driver asuspf_driver = { if (!acpi_check_handle(asus->handle, METHOD_LEDD, NULL)) {
.driver = { err = device_create_file(&device->dev, &dev_attr_ledd);
.name = ASUS_HOTK_FILE, if (err)
.owner = THIS_MODULE, return err;
} }
};
static struct platform_device *asuspf_device; if (!acpi_check_handle(asus->handle, METHOD_ALS_CONTROL, NULL) &&
!acpi_check_handle(asus->handle, METHOD_ALS_LEVEL, NULL)) {
err = device_create_file(&device->dev, &dev_attr_ls_switch);
if (err)
return err;
err = device_create_file(&device->dev, &dev_attr_ls_level);
if (err)
return err;
}
static void asus_hotk_add_fs(void) if (!acpi_check_handle(asus->handle, METHOD_GPS_ON, NULL) &&
{ !acpi_check_handle(asus->handle, METHOD_GPS_OFF, NULL) &&
ASUS_SET_DEVICE_ATTR(infos, 0444, show_infos, NULL); !acpi_check_handle(asus->handle, METHOD_GPS_STATUS, NULL)) {
err = device_create_file(&device->dev, &dev_attr_gps);
if (err)
return err;
}
if (wl_switch_handle) return err;
ASUS_SET_DEVICE_ATTR(wlan, 0644, show_wlan, store_wlan); }
if (bt_switch_handle) static int asus_platform_init(struct asus_laptop *asus)
ASUS_SET_DEVICE_ATTR(bluetooth, 0644, {
show_bluetooth, store_bluetooth); int err;
if (display_set_handle && display_get_handle) asus->platform_device = platform_device_alloc(ASUS_LAPTOP_FILE, -1);
ASUS_SET_DEVICE_ATTR(display, 0644, show_disp, store_disp); if (!asus->platform_device)
else if (display_set_handle) return -ENOMEM;
ASUS_SET_DEVICE_ATTR(display, 0200, NULL, store_disp); platform_set_drvdata(asus->platform_device, asus);
if (ledd_set_handle) err = platform_device_add(asus->platform_device);
ASUS_SET_DEVICE_ATTR(ledd, 0644, show_ledd, store_ledd); if (err)
goto fail_platform_device;
if (ls_switch_handle && ls_level_handle) { err = asus_sysfs_init(asus);
ASUS_SET_DEVICE_ATTR(ls_level, 0644, show_lslvl, store_lslvl); if (err)
ASUS_SET_DEVICE_ATTR(ls_switch, 0644, show_lssw, store_lssw); goto fail_sysfs;
} return 0;
fail_sysfs:
asus_sysfs_exit(asus);
platform_device_del(asus->platform_device);
fail_platform_device:
platform_device_put(asus->platform_device);
return err;
}
if (gps_status_handle && gps_on_handle && gps_off_handle) static void asus_platform_exit(struct asus_laptop *asus)
ASUS_SET_DEVICE_ATTR(gps, 0644, show_gps, store_gps); {
asus_sysfs_exit(asus);
platform_device_unregister(asus->platform_device);
} }
static struct platform_driver platform_driver = {
.driver = {
.name = ASUS_LAPTOP_FILE,
.owner = THIS_MODULE,
}
};
static int asus_handle_init(char *name, acpi_handle * handle, static int asus_handle_init(char *name, acpi_handle * handle,
char **paths, int num_paths) char **paths, int num_paths)
{ {
...@@ -1073,10 +1331,11 @@ static int asus_handle_init(char *name, acpi_handle * handle, ...@@ -1073,10 +1331,11 @@ static int asus_handle_init(char *name, acpi_handle * handle,
ARRAY_SIZE(object##_paths)) ARRAY_SIZE(object##_paths))
/* /*
* This function is used to initialize the hotk with right values. In this * This function is used to initialize the context with right values. In this
* method, we can make all the detection we want, and modify the hotk struct * method, we can make all the detection we want, and modify the asus_laptop
* struct
*/ */
static int asus_hotk_get_info(void) static int asus_laptop_get_info(struct asus_laptop *asus)
{ {
struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
union acpi_object *model = NULL; union acpi_object *model = NULL;
...@@ -1089,22 +1348,21 @@ static int asus_hotk_get_info(void) ...@@ -1089,22 +1348,21 @@ static int asus_hotk_get_info(void)
* models, but late enough to allow acpi_bus_register_driver() to fail * models, but late enough to allow acpi_bus_register_driver() to fail
* before doing anything ACPI-specific. Should we encounter a machine, * before doing anything ACPI-specific. Should we encounter a machine,
* which needs special handling (i.e. its hotkey device has a different * which needs special handling (i.e. its hotkey device has a different
* HID), this bit will be moved. A global variable asus_info contains * HID), this bit will be moved.
* the DSDT header.
*/ */
status = acpi_get_table(ACPI_SIG_DSDT, 1, &asus_info); status = acpi_get_table(ACPI_SIG_DSDT, 1, &asus->dsdt_info);
if (ACPI_FAILURE(status)) if (ACPI_FAILURE(status))
pr_warning("Couldn't get the DSDT table header\n"); pr_warning("Couldn't get the DSDT table header\n");
/* We have to write 0 on init this far for all ASUS models */ /* We have to write 0 on init this far for all ASUS models */
if (write_acpi_int(hotk->handle, "INIT", 0, &buffer)) { if (write_acpi_int_ret(asus->handle, "INIT", 0, &buffer)) {
pr_err("Hotkey initialization failed\n"); pr_err("Hotkey initialization failed\n");
return -ENODEV; return -ENODEV;
} }
/* This needs to be called for some laptops to init properly */ /* This needs to be called for some laptops to init properly */
status = status =
acpi_evaluate_integer(hotk->handle, "BSTS", NULL, &bsts_result); acpi_evaluate_integer(asus->handle, "BSTS", NULL, &bsts_result);
if (ACPI_FAILURE(status)) if (ACPI_FAILURE(status))
pr_warning("Error calling BSTS\n"); pr_warning("Error calling BSTS\n");
else if (bsts_result) else if (bsts_result)
...@@ -1112,8 +1370,8 @@ static int asus_hotk_get_info(void) ...@@ -1112,8 +1370,8 @@ static int asus_hotk_get_info(void)
(uint) bsts_result); (uint) bsts_result);
/* This too ... */ /* This too ... */
write_acpi_int(hotk->handle, "CWAP", wapf, NULL); if (write_acpi_int(asus->handle, "CWAP", wapf))
pr_err("Error calling CWAP(%d)\n", wapf);
/* /*
* Try to match the object returned by INIT to the specific model. * Try to match the object returned by INIT to the specific model.
* Handle every possible object (or the lack of thereof) the DSDT * Handle every possible object (or the lack of thereof) the DSDT
...@@ -1134,397 +1392,210 @@ static int asus_hotk_get_info(void) ...@@ -1134,397 +1392,210 @@ static int asus_hotk_get_info(void)
break; break;
} }
} }
hotk->name = kstrdup(string, GFP_KERNEL); asus->name = kstrdup(string, GFP_KERNEL);
if (!hotk->name) if (!asus->name)
return -ENOMEM; return -ENOMEM;
if (*string) if (*string)
pr_notice(" %s model detected\n", string); pr_notice(" %s model detected\n", string);
ASUS_HANDLE_INIT(mled_set);
ASUS_HANDLE_INIT(tled_set);
ASUS_HANDLE_INIT(rled_set);
ASUS_HANDLE_INIT(pled_set);
ASUS_HANDLE_INIT(gled_set);
ASUS_HANDLE_INIT(ledd_set);
ASUS_HANDLE_INIT(kled_set);
ASUS_HANDLE_INIT(kled_get);
/* /*
* The HWRS method return informations about the hardware. * The HWRS method return informations about the hardware.
* 0x80 bit is for WLAN, 0x100 for Bluetooth. * 0x80 bit is for WLAN, 0x100 for Bluetooth.
* The significance of others is yet to be found. * The significance of others is yet to be found.
* If we don't find the method, we assume the device are present.
*/ */
status = status =
acpi_evaluate_integer(hotk->handle, "HRWS", NULL, &hwrs_result); acpi_evaluate_integer(asus->handle, "HRWS", NULL, &hwrs_result);
if (ACPI_FAILURE(status)) if (!ACPI_FAILURE(status))
hwrs_result = WL_HWRS | BT_HWRS; pr_notice(" HRWS returned %x", (int)hwrs_result);
if (hwrs_result & WL_HWRS) if (!acpi_check_handle(asus->handle, METHOD_WL_STATUS, NULL))
ASUS_HANDLE_INIT(wl_switch); asus->have_rsts = true;
if (hwrs_result & BT_HWRS)
ASUS_HANDLE_INIT(bt_switch);
ASUS_HANDLE_INIT(wireless_status);
ASUS_HANDLE_INIT(brightness_set);
ASUS_HANDLE_INIT(brightness_get);
/* Scheduled for removal */
ASUS_HANDLE_INIT(lcd_switch); ASUS_HANDLE_INIT(lcd_switch);
ASUS_HANDLE_INIT(display_set);
ASUS_HANDLE_INIT(display_get); ASUS_HANDLE_INIT(display_get);
/*
* There is a lot of models with "ALSL", but a few get
* a real light sens, so we need to check it.
*/
if (!ASUS_HANDLE_INIT(ls_switch))
ASUS_HANDLE_INIT(ls_level);
ASUS_HANDLE_INIT(gps_on);
ASUS_HANDLE_INIT(gps_off);
ASUS_HANDLE_INIT(gps_status);
kfree(model); kfree(model);
return AE_OK; return AE_OK;
} }
static int asus_input_init(void) static bool asus_device_present;
{
const struct key_entry *key;
int result;
hotk->inputdev = input_allocate_device(); static int __devinit asus_acpi_init(struct asus_laptop *asus)
if (!hotk->inputdev) {
pr_info("Unable to allocate input device\n");
return 0;
}
hotk->inputdev->name = "Asus Laptop extra buttons";
hotk->inputdev->phys = ASUS_HOTK_FILE "/input0";
hotk->inputdev->id.bustype = BUS_HOST;
hotk->inputdev->getkeycode = asus_getkeycode;
hotk->inputdev->setkeycode = asus_setkeycode;
for (key = asus_keymap; key->type != KE_END; key++) {
switch (key->type) {
case KE_KEY:
set_bit(EV_KEY, hotk->inputdev->evbit);
set_bit(key->keycode, hotk->inputdev->keybit);
break;
}
}
result = input_register_device(hotk->inputdev);
if (result) {
pr_info("Unable to register input device\n");
input_free_device(hotk->inputdev);
}
return result;
}
static int asus_hotk_check(void)
{ {
int result = 0; int result = 0;
result = acpi_bus_get_status(hotk->device); result = acpi_bus_get_status(asus->device);
if (result) if (result)
return result; return result;
if (!asus->device->status.present) {
if (hotk->device->status.present) {
result = asus_hotk_get_info();
} else {
pr_err("Hotkey device not present, aborting\n"); pr_err("Hotkey device not present, aborting\n");
return -EINVAL; return -ENODEV;
} }
return result; result = asus_laptop_get_info(asus);
}
static int asus_hotk_found;
static int asus_hotk_add(struct acpi_device *device)
{
int result;
pr_notice("Asus Laptop Support version %s\n",
ASUS_LAPTOP_VERSION);
hotk = kzalloc(sizeof(struct asus_hotk), GFP_KERNEL);
if (!hotk)
return -ENOMEM;
hotk->handle = device->handle;
strcpy(acpi_device_name(device), ASUS_HOTK_DEVICE_NAME);
strcpy(acpi_device_class(device), ASUS_HOTK_CLASS);
device->driver_data = hotk;
hotk->device = device;
result = asus_hotk_check();
if (result) if (result)
goto end; return result;
asus_hotk_add_fs();
asus_hotk_found = 1;
/* WLED and BLED are on by default */ /* WLED and BLED are on by default */
write_status(bt_switch_handle, 1, BT_ON); if (bluetooth_status >= 0)
write_status(wl_switch_handle, 1, WL_ON); asus_bluetooth_set(asus, !!bluetooth_status);
/* If the h/w switch is off, we need to check the real status */
write_status(NULL, read_status(BT_ON), BT_ON);
write_status(NULL, read_status(WL_ON), WL_ON);
/* LCD Backlight is on by default */ if (wlan_status >= 0)
write_status(NULL, 1, LCD_ON); asus_wlan_set(asus, !!wlan_status);
/* Keyboard Backlight is on by default */ /* Keyboard Backlight is on by default */
if (kled_set_handle) if (!acpi_check_handle(asus->handle, METHOD_KBD_LIGHT_SET, NULL))
set_kled_lvl(1); asus_kled_set(asus, 1);
/* LED display is off by default */ /* LED display is off by default */
hotk->ledd_status = 0xFFF; asus->ledd_status = 0xFFF;
/* Set initial values of light sensor and level */ /* Set initial values of light sensor and level */
hotk->light_switch = 0; /* Default to light sensor disabled */ asus->light_switch = 0; /* Default to light sensor disabled */
hotk->light_level = 5; /* level 5 for sensor sensitivity */ asus->light_level = 5; /* level 5 for sensor sensitivity */
if (ls_switch_handle) if (!acpi_check_handle(asus->handle, METHOD_ALS_CONTROL, NULL) &&
set_light_sens_switch(hotk->light_switch); !acpi_check_handle(asus->handle, METHOD_ALS_LEVEL, NULL)) {
asus_als_switch(asus, asus->light_switch);
if (ls_level_handle) asus_als_level(asus, asus->light_level);
set_light_sens_level(hotk->light_level);
/* GPS is on by default */
write_status(NULL, 1, GPS_ON);
end:
if (result) {
kfree(hotk->name);
kfree(hotk);
} }
asus->lcd_state = 1; /* LCD should be on when the module load */
return result; return result;
} }
static int asus_hotk_remove(struct acpi_device *device, int type) static int __devinit asus_acpi_add(struct acpi_device *device)
{
kfree(hotk->name);
kfree(hotk);
return 0;
}
static void asus_backlight_exit(void)
{ {
if (asus_backlight_device) struct asus_laptop *asus;
backlight_device_unregister(asus_backlight_device); int result;
}
#define ASUS_LED_UNREGISTER(object) \
if (object##_led.dev) \
led_classdev_unregister(&object##_led)
static void asus_led_exit(void) pr_notice("Asus Laptop Support version %s\n",
{ ASUS_LAPTOP_VERSION);
destroy_workqueue(led_workqueue); asus = kzalloc(sizeof(struct asus_laptop), GFP_KERNEL);
ASUS_LED_UNREGISTER(mled); if (!asus)
ASUS_LED_UNREGISTER(tled); return -ENOMEM;
ASUS_LED_UNREGISTER(pled); asus->handle = device->handle;
ASUS_LED_UNREGISTER(rled); strcpy(acpi_device_name(device), ASUS_LAPTOP_DEVICE_NAME);
ASUS_LED_UNREGISTER(gled); strcpy(acpi_device_class(device), ASUS_LAPTOP_CLASS);
ASUS_LED_UNREGISTER(kled); device->driver_data = asus;
} asus->device = device;
static void asus_input_exit(void) result = asus_acpi_init(asus);
{ if (result)
if (hotk->inputdev) goto fail_platform;
input_unregister_device(hotk->inputdev);
}
static void __exit asus_laptop_exit(void) /*
{ * Register the platform device first. It is used as a parent for the
asus_backlight_exit(); * sub-devices below.
asus_led_exit(); */
asus_input_exit(); result = asus_platform_init(asus);
if (result)
goto fail_platform;
acpi_bus_unregister_driver(&asus_hotk_driver); if (!acpi_video_backlight_support()) {
sysfs_remove_group(&asuspf_device->dev.kobj, &asuspf_attribute_group); result = asus_backlight_init(asus);
platform_device_unregister(asuspf_device); if (result)
platform_driver_unregister(&asuspf_driver); goto fail_backlight;
} } else
pr_info("Backlight controlled by ACPI video driver\n");
static int asus_backlight_init(struct device *dev) result = asus_input_init(asus);
{ if (result)
struct backlight_device *bd; goto fail_input;
if (brightness_set_handle && lcd_switch_handle) { result = asus_led_init(asus);
bd = backlight_device_register(ASUS_HOTK_FILE, dev, if (result)
NULL, &asusbl_ops); goto fail_led;
if (IS_ERR(bd)) {
pr_err("Could not register asus backlight device\n");
asus_backlight_device = NULL;
return PTR_ERR(bd);
}
asus_backlight_device = bd; result = asus_rfkill_init(asus);
if (result)
goto fail_rfkill;
bd->props.max_brightness = 15; asus_device_present = true;
bd->props.brightness = read_brightness(NULL);
bd->props.power = FB_BLANK_UNBLANK;
backlight_update_status(bd);
}
return 0; return 0;
}
static int asus_led_register(acpi_handle handle, fail_rfkill:
struct led_classdev *ldev, struct device *dev) asus_led_exit(asus);
{ fail_led:
if (!handle) asus_input_exit(asus);
return 0; fail_input:
asus_backlight_exit(asus);
fail_backlight:
asus_platform_exit(asus);
fail_platform:
kfree(asus->name);
kfree(asus);
return led_classdev_register(dev, ldev); return result;
} }
#define ASUS_LED_REGISTER(object, device) \ static int asus_acpi_remove(struct acpi_device *device, int type)
asus_led_register(object##_set_handle, &object##_led, device)
static int asus_led_init(struct device *dev)
{ {
int rv; struct asus_laptop *asus = acpi_driver_data(device);
rv = ASUS_LED_REGISTER(mled, dev);
if (rv)
goto out;
rv = ASUS_LED_REGISTER(tled, dev);
if (rv)
goto out1;
rv = ASUS_LED_REGISTER(rled, dev);
if (rv)
goto out2;
rv = ASUS_LED_REGISTER(pled, dev); asus_backlight_exit(asus);
if (rv) asus_rfkill_exit(asus);
goto out3; asus_led_exit(asus);
asus_input_exit(asus);
rv = ASUS_LED_REGISTER(gled, dev); asus_platform_exit(asus);
if (rv)
goto out4;
if (kled_set_handle && kled_get_handle)
rv = ASUS_LED_REGISTER(kled, dev);
if (rv)
goto out5;
led_workqueue = create_singlethread_workqueue("led_workqueue");
if (!led_workqueue)
goto out6;
kfree(asus->name);
kfree(asus);
return 0; return 0;
out6:
rv = -ENOMEM;
ASUS_LED_UNREGISTER(kled);
out5:
ASUS_LED_UNREGISTER(gled);
out4:
ASUS_LED_UNREGISTER(pled);
out3:
ASUS_LED_UNREGISTER(rled);
out2:
ASUS_LED_UNREGISTER(tled);
out1:
ASUS_LED_UNREGISTER(mled);
out:
return rv;
} }
static const struct acpi_device_id asus_device_ids[] = {
{"ATK0100", 0},
{"ATK0101", 0},
{"", 0},
};
MODULE_DEVICE_TABLE(acpi, asus_device_ids);
static struct acpi_driver asus_acpi_driver = {
.name = ASUS_LAPTOP_NAME,
.class = ASUS_LAPTOP_CLASS,
.owner = THIS_MODULE,
.ids = asus_device_ids,
.flags = ACPI_DRIVER_ALL_NOTIFY_EVENTS,
.ops = {
.add = asus_acpi_add,
.remove = asus_acpi_remove,
.notify = asus_acpi_notify,
},
};
static int __init asus_laptop_init(void) static int __init asus_laptop_init(void)
{ {
int result; int result;
result = acpi_bus_register_driver(&asus_hotk_driver); result = platform_driver_register(&platform_driver);
if (result < 0) if (result < 0)
return result; return result;
/* result = acpi_bus_register_driver(&asus_acpi_driver);
* This is a bit of a kludge. We only want this module loaded if (result < 0)
* for ASUS systems, but there's currently no way to probe the goto fail_acpi_driver;
* ACPI namespace for ASUS HIDs. So we just return failure if if (!asus_device_present) {
* we didn't find one, which will cause the module to be result = -ENODEV;
* unloaded. goto fail_no_device;
*/
if (!asus_hotk_found) {
acpi_bus_unregister_driver(&asus_hotk_driver);
return -ENODEV;
}
result = asus_input_init();
if (result)
goto fail_input;
/* Register platform stuff */
result = platform_driver_register(&asuspf_driver);
if (result)
goto fail_platform_driver;
asuspf_device = platform_device_alloc(ASUS_HOTK_FILE, -1);
if (!asuspf_device) {
result = -ENOMEM;
goto fail_platform_device1;
} }
result = platform_device_add(asuspf_device);
if (result)
goto fail_platform_device2;
result = sysfs_create_group(&asuspf_device->dev.kobj,
&asuspf_attribute_group);
if (result)
goto fail_sysfs;
result = asus_led_init(&asuspf_device->dev);
if (result)
goto fail_led;
if (!acpi_video_backlight_support()) {
result = asus_backlight_init(&asuspf_device->dev);
if (result)
goto fail_backlight;
} else
pr_info("Brightness ignored, must be controlled by "
"ACPI video driver\n");
return 0; return 0;
fail_backlight: fail_no_device:
asus_led_exit(); acpi_bus_unregister_driver(&asus_acpi_driver);
fail_acpi_driver:
fail_led: platform_driver_unregister(&platform_driver);
sysfs_remove_group(&asuspf_device->dev.kobj,
&asuspf_attribute_group);
fail_sysfs:
platform_device_del(asuspf_device);
fail_platform_device2:
platform_device_put(asuspf_device);
fail_platform_device1:
platform_driver_unregister(&asuspf_driver);
fail_platform_driver:
asus_input_exit();
fail_input:
return result; return result;
} }
static void __exit asus_laptop_exit(void)
{
acpi_bus_unregister_driver(&asus_acpi_driver);
platform_driver_unregister(&platform_driver);
}
module_init(asus_laptop_init); module_init(asus_laptop_init);
module_exit(asus_laptop_exit); module_exit(asus_laptop_exit);
...@@ -1225,9 +1225,8 @@ static int asus_model_match(char *model) ...@@ -1225,9 +1225,8 @@ static int asus_model_match(char *model)
else if (strncmp(model, "M2N", 3) == 0 || else if (strncmp(model, "M2N", 3) == 0 ||
strncmp(model, "M3N", 3) == 0 || strncmp(model, "M3N", 3) == 0 ||
strncmp(model, "M5N", 3) == 0 || strncmp(model, "M5N", 3) == 0 ||
strncmp(model, "M6N", 3) == 0 ||
strncmp(model, "S1N", 3) == 0 || strncmp(model, "S1N", 3) == 0 ||
strncmp(model, "S5N", 3) == 0 || strncmp(model, "W1N", 3) == 0) strncmp(model, "S5N", 3) == 0)
return xxN; return xxN;
else if (strncmp(model, "M1", 2) == 0) else if (strncmp(model, "M1", 2) == 0)
return M1A; return M1A;
......
...@@ -507,6 +507,10 @@ static int cmpc_keys_codes[] = { ...@@ -507,6 +507,10 @@ static int cmpc_keys_codes[] = {
KEY_BRIGHTNESSDOWN, KEY_BRIGHTNESSDOWN,
KEY_BRIGHTNESSUP, KEY_BRIGHTNESSUP,
KEY_VENDOR, KEY_VENDOR,
KEY_UNKNOWN,
KEY_CAMERA,
KEY_BACK,
KEY_FORWARD,
KEY_MAX KEY_MAX
}; };
......
...@@ -132,8 +132,8 @@ static struct dmi_system_id __devinitdata dell_blacklist[] = { ...@@ -132,8 +132,8 @@ static struct dmi_system_id __devinitdata dell_blacklist[] = {
}; };
static struct calling_interface_buffer *buffer; static struct calling_interface_buffer *buffer;
struct page *bufferpage; static struct page *bufferpage;
DEFINE_MUTEX(buffer_mutex); static DEFINE_MUTEX(buffer_mutex);
static int hwswitch_state; static int hwswitch_state;
...@@ -580,6 +580,7 @@ static int __init dell_init(void) ...@@ -580,6 +580,7 @@ static int __init dell_init(void)
fail_backlight: fail_backlight:
i8042_remove_filter(dell_laptop_i8042_filter); i8042_remove_filter(dell_laptop_i8042_filter);
cancel_delayed_work_sync(&dell_rfkill_work);
fail_filter: fail_filter:
dell_cleanup_rfkill(); dell_cleanup_rfkill();
fail_rfkill: fail_rfkill:
...@@ -597,12 +598,12 @@ static int __init dell_init(void) ...@@ -597,12 +598,12 @@ static int __init dell_init(void)
static void __exit dell_exit(void) static void __exit dell_exit(void)
{ {
cancel_delayed_work_sync(&dell_rfkill_work);
i8042_remove_filter(dell_laptop_i8042_filter); i8042_remove_filter(dell_laptop_i8042_filter);
cancel_delayed_work_sync(&dell_rfkill_work);
backlight_device_unregister(dell_backlight_device); backlight_device_unregister(dell_backlight_device);
dell_cleanup_rfkill(); dell_cleanup_rfkill();
if (platform_device) { if (platform_device) {
platform_device_del(platform_device); platform_device_unregister(platform_device);
platform_driver_unregister(&platform_driver); platform_driver_unregister(&platform_driver);
} }
kfree(da_tokens); kfree(da_tokens);
......
...@@ -578,6 +578,8 @@ static void eeepc_rfkill_hotplug(struct eeepc_laptop *eeepc) ...@@ -578,6 +578,8 @@ static void eeepc_rfkill_hotplug(struct eeepc_laptop *eeepc)
struct pci_dev *dev; struct pci_dev *dev;
struct pci_bus *bus; struct pci_bus *bus;
bool blocked = eeepc_wlan_rfkill_blocked(eeepc); bool blocked = eeepc_wlan_rfkill_blocked(eeepc);
bool absent;
u32 l;
if (eeepc->wlan_rfkill) if (eeepc->wlan_rfkill)
rfkill_set_sw_state(eeepc->wlan_rfkill, blocked); rfkill_set_sw_state(eeepc->wlan_rfkill, blocked);
...@@ -591,6 +593,22 @@ static void eeepc_rfkill_hotplug(struct eeepc_laptop *eeepc) ...@@ -591,6 +593,22 @@ static void eeepc_rfkill_hotplug(struct eeepc_laptop *eeepc)
goto out_unlock; goto out_unlock;
} }
if (pci_bus_read_config_dword(bus, 0, PCI_VENDOR_ID, &l)) {
pr_err("Unable to read PCI config space?\n");
goto out_unlock;
}
absent = (l == 0xffffffff);
if (blocked != absent) {
pr_warning("BIOS says wireless lan is %s, "
"but the pci device is %s\n",
blocked ? "blocked" : "unblocked",
absent ? "absent" : "present");
pr_warning("skipped wireless hotplug as probably "
"inappropriate for this model\n");
goto out_unlock;
}
if (!blocked) { if (!blocked) {
dev = pci_get_slot(bus, 0); dev = pci_get_slot(bus, 0);
if (dev) { if (dev) {
...@@ -1277,7 +1295,8 @@ static void eeepc_dmi_check(struct eeepc_laptop *eeepc) ...@@ -1277,7 +1295,8 @@ static void eeepc_dmi_check(struct eeepc_laptop *eeepc)
* hotplug code. In fact, current hotplug code seems to unplug another * hotplug code. In fact, current hotplug code seems to unplug another
* device... * device...
*/ */
if (strcmp(model, "1005HA") == 0 || strcmp(model, "1201N") == 0) { if (strcmp(model, "1005HA") == 0 || strcmp(model, "1201N") == 0 ||
strcmp(model, "1005PE") == 0) {
eeepc->hotplug_disabled = true; eeepc->hotplug_disabled = true;
pr_info("wlan hotplug disabled\n"); pr_info("wlan hotplug disabled\n");
} }
......
...@@ -286,6 +286,7 @@ struct ibm_init_struct { ...@@ -286,6 +286,7 @@ struct ibm_init_struct {
char param[32]; char param[32];
int (*init) (struct ibm_init_struct *); int (*init) (struct ibm_init_struct *);
mode_t base_procfs_mode;
struct ibm_struct *data; struct ibm_struct *data;
}; };
...@@ -2082,6 +2083,7 @@ static struct attribute_set *hotkey_dev_attributes; ...@@ -2082,6 +2083,7 @@ static struct attribute_set *hotkey_dev_attributes;
static void tpacpi_driver_event(const unsigned int hkey_event); static void tpacpi_driver_event(const unsigned int hkey_event);
static void hotkey_driver_event(const unsigned int scancode); static void hotkey_driver_event(const unsigned int scancode);
static void hotkey_poll_setup(const bool may_warn);
/* HKEY.MHKG() return bits */ /* HKEY.MHKG() return bits */
#define TP_HOTKEY_TABLET_MASK (1 << 3) #define TP_HOTKEY_TABLET_MASK (1 << 3)
...@@ -2264,6 +2266,8 @@ static int tpacpi_hotkey_driver_mask_set(const u32 mask) ...@@ -2264,6 +2266,8 @@ static int tpacpi_hotkey_driver_mask_set(const u32 mask)
rc = hotkey_mask_set((hotkey_acpi_mask | hotkey_driver_mask) & rc = hotkey_mask_set((hotkey_acpi_mask | hotkey_driver_mask) &
~hotkey_source_mask); ~hotkey_source_mask);
hotkey_poll_setup(true);
mutex_unlock(&hotkey_mutex); mutex_unlock(&hotkey_mutex);
return rc; return rc;
...@@ -2548,7 +2552,7 @@ static void hotkey_poll_stop_sync(void) ...@@ -2548,7 +2552,7 @@ static void hotkey_poll_stop_sync(void)
} }
/* call with hotkey_mutex held */ /* call with hotkey_mutex held */
static void hotkey_poll_setup(bool may_warn) static void hotkey_poll_setup(const bool may_warn)
{ {
const u32 poll_driver_mask = hotkey_driver_mask & hotkey_source_mask; const u32 poll_driver_mask = hotkey_driver_mask & hotkey_source_mask;
const u32 poll_user_mask = hotkey_user_mask & hotkey_source_mask; const u32 poll_user_mask = hotkey_user_mask & hotkey_source_mask;
...@@ -2579,7 +2583,7 @@ static void hotkey_poll_setup(bool may_warn) ...@@ -2579,7 +2583,7 @@ static void hotkey_poll_setup(bool may_warn)
} }
} }
static void hotkey_poll_setup_safe(bool may_warn) static void hotkey_poll_setup_safe(const bool may_warn)
{ {
mutex_lock(&hotkey_mutex); mutex_lock(&hotkey_mutex);
hotkey_poll_setup(may_warn); hotkey_poll_setup(may_warn);
...@@ -2597,7 +2601,11 @@ static void hotkey_poll_set_freq(unsigned int freq) ...@@ -2597,7 +2601,11 @@ static void hotkey_poll_set_freq(unsigned int freq)
#else /* CONFIG_THINKPAD_ACPI_HOTKEY_POLL */ #else /* CONFIG_THINKPAD_ACPI_HOTKEY_POLL */
static void hotkey_poll_setup_safe(bool __unused) static void hotkey_poll_setup(const bool __unused)
{
}
static void hotkey_poll_setup_safe(const bool __unused)
{ {
} }
...@@ -2607,16 +2615,11 @@ static int hotkey_inputdev_open(struct input_dev *dev) ...@@ -2607,16 +2615,11 @@ static int hotkey_inputdev_open(struct input_dev *dev)
{ {
switch (tpacpi_lifecycle) { switch (tpacpi_lifecycle) {
case TPACPI_LIFE_INIT: case TPACPI_LIFE_INIT:
/*
* hotkey_init will call hotkey_poll_setup_safe
* at the appropriate moment
*/
return 0;
case TPACPI_LIFE_EXITING:
return -EBUSY;
case TPACPI_LIFE_RUNNING: case TPACPI_LIFE_RUNNING:
hotkey_poll_setup_safe(false); hotkey_poll_setup_safe(false);
return 0; return 0;
case TPACPI_LIFE_EXITING:
return -EBUSY;
} }
/* Should only happen if tpacpi_lifecycle is corrupt */ /* Should only happen if tpacpi_lifecycle is corrupt */
...@@ -2627,7 +2630,7 @@ static int hotkey_inputdev_open(struct input_dev *dev) ...@@ -2627,7 +2630,7 @@ static int hotkey_inputdev_open(struct input_dev *dev)
static void hotkey_inputdev_close(struct input_dev *dev) static void hotkey_inputdev_close(struct input_dev *dev)
{ {
/* disable hotkey polling when possible */ /* disable hotkey polling when possible */
if (tpacpi_lifecycle == TPACPI_LIFE_RUNNING && if (tpacpi_lifecycle != TPACPI_LIFE_EXITING &&
!(hotkey_source_mask & hotkey_driver_mask)) !(hotkey_source_mask & hotkey_driver_mask))
hotkey_poll_setup_safe(false); hotkey_poll_setup_safe(false);
} }
...@@ -3655,13 +3658,19 @@ static void hotkey_notify(struct ibm_struct *ibm, u32 event) ...@@ -3655,13 +3658,19 @@ static void hotkey_notify(struct ibm_struct *ibm, u32 event)
break; break;
case 3: case 3:
/* 0x3000-0x3FFF: bay-related wakeups */ /* 0x3000-0x3FFF: bay-related wakeups */
if (hkey == TP_HKEY_EV_BAYEJ_ACK) { switch (hkey) {
case TP_HKEY_EV_BAYEJ_ACK:
hotkey_autosleep_ack = 1; hotkey_autosleep_ack = 1;
printk(TPACPI_INFO printk(TPACPI_INFO
"bay ejected\n"); "bay ejected\n");
hotkey_wakeup_hotunplug_complete_notify_change(); hotkey_wakeup_hotunplug_complete_notify_change();
known_ev = true; known_ev = true;
} else { break;
case TP_HKEY_EV_OPTDRV_EJ:
/* FIXME: kick libata if SATA link offline */
known_ev = true;
break;
default:
known_ev = false; known_ev = false;
} }
break; break;
...@@ -3870,7 +3879,7 @@ enum { ...@@ -3870,7 +3879,7 @@ enum {
TP_ACPI_BLUETOOTH_HWPRESENT = 0x01, /* Bluetooth hw available */ TP_ACPI_BLUETOOTH_HWPRESENT = 0x01, /* Bluetooth hw available */
TP_ACPI_BLUETOOTH_RADIOSSW = 0x02, /* Bluetooth radio enabled */ TP_ACPI_BLUETOOTH_RADIOSSW = 0x02, /* Bluetooth radio enabled */
TP_ACPI_BLUETOOTH_RESUMECTRL = 0x04, /* Bluetooth state at resume: TP_ACPI_BLUETOOTH_RESUMECTRL = 0x04, /* Bluetooth state at resume:
off / last state */ 0 = disable, 1 = enable */
}; };
enum { enum {
...@@ -3916,10 +3925,11 @@ static int bluetooth_set_status(enum tpacpi_rfkill_state state) ...@@ -3916,10 +3925,11 @@ static int bluetooth_set_status(enum tpacpi_rfkill_state state)
} }
#endif #endif
/* We make sure to keep TP_ACPI_BLUETOOTH_RESUMECTRL off */
status = TP_ACPI_BLUETOOTH_RESUMECTRL;
if (state == TPACPI_RFK_RADIO_ON) if (state == TPACPI_RFK_RADIO_ON)
status |= TP_ACPI_BLUETOOTH_RADIOSSW; status = TP_ACPI_BLUETOOTH_RADIOSSW
| TP_ACPI_BLUETOOTH_RESUMECTRL;
else
status = 0;
if (!acpi_evalf(hkey_handle, NULL, "SBDC", "vd", status)) if (!acpi_evalf(hkey_handle, NULL, "SBDC", "vd", status))
return -EIO; return -EIO;
...@@ -4070,7 +4080,7 @@ enum { ...@@ -4070,7 +4080,7 @@ enum {
TP_ACPI_WANCARD_HWPRESENT = 0x01, /* Wan hw available */ TP_ACPI_WANCARD_HWPRESENT = 0x01, /* Wan hw available */
TP_ACPI_WANCARD_RADIOSSW = 0x02, /* Wan radio enabled */ TP_ACPI_WANCARD_RADIOSSW = 0x02, /* Wan radio enabled */
TP_ACPI_WANCARD_RESUMECTRL = 0x04, /* Wan state at resume: TP_ACPI_WANCARD_RESUMECTRL = 0x04, /* Wan state at resume:
off / last state */ 0 = disable, 1 = enable */
}; };
#define TPACPI_RFK_WWAN_SW_NAME "tpacpi_wwan_sw" #define TPACPI_RFK_WWAN_SW_NAME "tpacpi_wwan_sw"
...@@ -4107,10 +4117,11 @@ static int wan_set_status(enum tpacpi_rfkill_state state) ...@@ -4107,10 +4117,11 @@ static int wan_set_status(enum tpacpi_rfkill_state state)
} }
#endif #endif
/* We make sure to set TP_ACPI_WANCARD_RESUMECTRL */
status = TP_ACPI_WANCARD_RESUMECTRL;
if (state == TPACPI_RFK_RADIO_ON) if (state == TPACPI_RFK_RADIO_ON)
status |= TP_ACPI_WANCARD_RADIOSSW; status = TP_ACPI_WANCARD_RADIOSSW
| TP_ACPI_WANCARD_RESUMECTRL;
else
status = 0;
if (!acpi_evalf(hkey_handle, NULL, "SWAN", "vd", status)) if (!acpi_evalf(hkey_handle, NULL, "SWAN", "vd", status))
return -EIO; return -EIO;
...@@ -4619,6 +4630,10 @@ static int video_read(struct seq_file *m) ...@@ -4619,6 +4630,10 @@ static int video_read(struct seq_file *m)
return 0; return 0;
} }
/* Even reads can crash X.org, so... */
if (!capable(CAP_SYS_ADMIN))
return -EPERM;
status = video_outputsw_get(); status = video_outputsw_get();
if (status < 0) if (status < 0)
return status; return status;
...@@ -4652,6 +4667,10 @@ static int video_write(char *buf) ...@@ -4652,6 +4667,10 @@ static int video_write(char *buf)
if (video_supported == TPACPI_VIDEO_NONE) if (video_supported == TPACPI_VIDEO_NONE)
return -ENODEV; return -ENODEV;
/* Even reads can crash X.org, let alone writes... */
if (!capable(CAP_SYS_ADMIN))
return -EPERM;
enable = 0; enable = 0;
disable = 0; disable = 0;
...@@ -6133,13 +6152,13 @@ static const struct tpacpi_quirk brightness_quirk_table[] __initconst = { ...@@ -6133,13 +6152,13 @@ static const struct tpacpi_quirk brightness_quirk_table[] __initconst = {
TPACPI_Q_IBM('1', 'Y', TPACPI_BRGHT_Q_EC), /* T43/p ATI */ TPACPI_Q_IBM('1', 'Y', TPACPI_BRGHT_Q_EC), /* T43/p ATI */
/* Models with ATI GPUs that can use ECNVRAM */ /* Models with ATI GPUs that can use ECNVRAM */
TPACPI_Q_IBM('1', 'R', TPACPI_BRGHT_Q_EC), TPACPI_Q_IBM('1', 'R', TPACPI_BRGHT_Q_EC), /* R50,51 T40-42 */
TPACPI_Q_IBM('1', 'Q', TPACPI_BRGHT_Q_ASK|TPACPI_BRGHT_Q_EC), TPACPI_Q_IBM('1', 'Q', TPACPI_BRGHT_Q_ASK|TPACPI_BRGHT_Q_EC),
TPACPI_Q_IBM('7', '6', TPACPI_BRGHT_Q_ASK|TPACPI_BRGHT_Q_EC), TPACPI_Q_IBM('7', '6', TPACPI_BRGHT_Q_EC), /* R52 */
TPACPI_Q_IBM('7', '8', TPACPI_BRGHT_Q_ASK|TPACPI_BRGHT_Q_EC), TPACPI_Q_IBM('7', '8', TPACPI_BRGHT_Q_ASK|TPACPI_BRGHT_Q_EC),
/* Models with Intel Extreme Graphics 2 */ /* Models with Intel Extreme Graphics 2 */
TPACPI_Q_IBM('1', 'U', TPACPI_BRGHT_Q_NOEC), TPACPI_Q_IBM('1', 'U', TPACPI_BRGHT_Q_NOEC), /* X40 */
TPACPI_Q_IBM('1', 'V', TPACPI_BRGHT_Q_ASK|TPACPI_BRGHT_Q_EC), TPACPI_Q_IBM('1', 'V', TPACPI_BRGHT_Q_ASK|TPACPI_BRGHT_Q_EC),
TPACPI_Q_IBM('1', 'W', TPACPI_BRGHT_Q_ASK|TPACPI_BRGHT_Q_EC), TPACPI_Q_IBM('1', 'W', TPACPI_BRGHT_Q_ASK|TPACPI_BRGHT_Q_EC),
...@@ -6522,7 +6541,8 @@ static int volume_set_status(const u8 status) ...@@ -6522,7 +6541,8 @@ static int volume_set_status(const u8 status)
return volume_set_status_ec(status); return volume_set_status_ec(status);
} }
static int volume_set_mute_ec(const bool mute) /* returns < 0 on error, 0 on no change, 1 on change */
static int __volume_set_mute_ec(const bool mute)
{ {
int rc; int rc;
u8 s, n; u8 s, n;
...@@ -6537,22 +6557,37 @@ static int volume_set_mute_ec(const bool mute) ...@@ -6537,22 +6557,37 @@ static int volume_set_mute_ec(const bool mute)
n = (mute) ? s | TP_EC_AUDIO_MUTESW_MSK : n = (mute) ? s | TP_EC_AUDIO_MUTESW_MSK :
s & ~TP_EC_AUDIO_MUTESW_MSK; s & ~TP_EC_AUDIO_MUTESW_MSK;
if (n != s) if (n != s) {
rc = volume_set_status_ec(n); rc = volume_set_status_ec(n);
if (!rc)
rc = 1;
}
unlock: unlock:
mutex_unlock(&volume_mutex); mutex_unlock(&volume_mutex);
return rc; return rc;
} }
static int volume_alsa_set_mute(const bool mute)
{
dbg_printk(TPACPI_DBG_MIXER, "ALSA: trying to %smute\n",
(mute) ? "" : "un");
return __volume_set_mute_ec(mute);
}
static int volume_set_mute(const bool mute) static int volume_set_mute(const bool mute)
{ {
int rc;
dbg_printk(TPACPI_DBG_MIXER, "trying to %smute\n", dbg_printk(TPACPI_DBG_MIXER, "trying to %smute\n",
(mute) ? "" : "un"); (mute) ? "" : "un");
return volume_set_mute_ec(mute);
rc = __volume_set_mute_ec(mute);
return (rc < 0) ? rc : 0;
} }
static int volume_set_volume_ec(const u8 vol) /* returns < 0 on error, 0 on no change, 1 on change */
static int __volume_set_volume_ec(const u8 vol)
{ {
int rc; int rc;
u8 s, n; u8 s, n;
...@@ -6569,19 +6604,22 @@ static int volume_set_volume_ec(const u8 vol) ...@@ -6569,19 +6604,22 @@ static int volume_set_volume_ec(const u8 vol)
n = (s & ~TP_EC_AUDIO_LVL_MSK) | vol; n = (s & ~TP_EC_AUDIO_LVL_MSK) | vol;
if (n != s) if (n != s) {
rc = volume_set_status_ec(n); rc = volume_set_status_ec(n);
if (!rc)
rc = 1;
}
unlock: unlock:
mutex_unlock(&volume_mutex); mutex_unlock(&volume_mutex);
return rc; return rc;
} }
static int volume_set_volume(const u8 vol) static int volume_alsa_set_volume(const u8 vol)
{ {
dbg_printk(TPACPI_DBG_MIXER, dbg_printk(TPACPI_DBG_MIXER,
"trying to set volume level to %hu\n", vol); "ALSA: trying to set volume level to %hu\n", vol);
return volume_set_volume_ec(vol); return __volume_set_volume_ec(vol);
} }
static void volume_alsa_notify_change(void) static void volume_alsa_notify_change(void)
...@@ -6628,7 +6666,7 @@ static int volume_alsa_vol_get(struct snd_kcontrol *kcontrol, ...@@ -6628,7 +6666,7 @@ static int volume_alsa_vol_get(struct snd_kcontrol *kcontrol,
static int volume_alsa_vol_put(struct snd_kcontrol *kcontrol, static int volume_alsa_vol_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol) struct snd_ctl_elem_value *ucontrol)
{ {
return volume_set_volume(ucontrol->value.integer.value[0]); return volume_alsa_set_volume(ucontrol->value.integer.value[0]);
} }
#define volume_alsa_mute_info snd_ctl_boolean_mono_info #define volume_alsa_mute_info snd_ctl_boolean_mono_info
...@@ -6651,7 +6689,7 @@ static int volume_alsa_mute_get(struct snd_kcontrol *kcontrol, ...@@ -6651,7 +6689,7 @@ static int volume_alsa_mute_get(struct snd_kcontrol *kcontrol,
static int volume_alsa_mute_put(struct snd_kcontrol *kcontrol, static int volume_alsa_mute_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol) struct snd_ctl_elem_value *ucontrol)
{ {
return volume_set_mute(!ucontrol->value.integer.value[0]); return volume_alsa_set_mute(!ucontrol->value.integer.value[0]);
} }
static struct snd_kcontrol_new volume_alsa_control_vol __devinitdata = { static struct snd_kcontrol_new volume_alsa_control_vol __devinitdata = {
...@@ -8477,8 +8515,9 @@ static int __init ibm_init(struct ibm_init_struct *iibm) ...@@ -8477,8 +8515,9 @@ static int __init ibm_init(struct ibm_init_struct *iibm)
"%s installed\n", ibm->name); "%s installed\n", ibm->name);
if (ibm->read) { if (ibm->read) {
mode_t mode; mode_t mode = iibm->base_procfs_mode;
if (!mode)
mode = S_IRUGO; mode = S_IRUGO;
if (ibm->write) if (ibm->write)
mode |= S_IWUSR; mode |= S_IWUSR;
...@@ -8670,6 +8709,7 @@ static struct ibm_init_struct ibms_init[] __initdata = { ...@@ -8670,6 +8709,7 @@ static struct ibm_init_struct ibms_init[] __initdata = {
#ifdef CONFIG_THINKPAD_ACPI_VIDEO #ifdef CONFIG_THINKPAD_ACPI_VIDEO
{ {
.init = video_init, .init = video_init,
.base_procfs_mode = S_IRUSR,
.data = &video_driver_data, .data = &video_driver_data,
}, },
#endif #endif
...@@ -9032,6 +9072,9 @@ static int __init thinkpad_acpi_module_init(void) ...@@ -9032,6 +9072,9 @@ static int __init thinkpad_acpi_module_init(void)
return ret; return ret;
} }
} }
tpacpi_lifecycle = TPACPI_LIFE_RUNNING;
ret = input_register_device(tpacpi_inputdev); ret = input_register_device(tpacpi_inputdev);
if (ret < 0) { if (ret < 0) {
printk(TPACPI_ERR "unable to register input device\n"); printk(TPACPI_ERR "unable to register input device\n");
...@@ -9041,7 +9084,6 @@ static int __init thinkpad_acpi_module_init(void) ...@@ -9041,7 +9084,6 @@ static int __init thinkpad_acpi_module_init(void)
tp_features.input_device_registered = 1; tp_features.input_device_registered = 1;
} }
tpacpi_lifecycle = TPACPI_LIFE_RUNNING;
return 0; return 0;
} }
......
...@@ -814,12 +814,15 @@ static void toshiba_acpi_notify(acpi_handle handle, u32 event, void *context) ...@@ -814,12 +814,15 @@ static void toshiba_acpi_notify(acpi_handle handle, u32 event, void *context)
if (hci_result == HCI_SUCCESS) { if (hci_result == HCI_SUCCESS) {
if (value == 0x100) if (value == 0x100)
continue; continue;
else if (value & 0x80) { /* act on key press; ignore key release */
if (value & 0x80)
continue;
key = toshiba_acpi_get_entry_by_scancode key = toshiba_acpi_get_entry_by_scancode
(value & ~0x80); (value);
if (!key) { if (!key) {
printk(MY_INFO "Unknown key %x\n", printk(MY_INFO "Unknown key %x\n",
value & ~0x80); value);
continue; continue;
} }
input_report_key(toshiba_acpi.hotkey_dev, input_report_key(toshiba_acpi.hotkey_dev,
...@@ -828,7 +831,6 @@ static void toshiba_acpi_notify(acpi_handle handle, u32 event, void *context) ...@@ -828,7 +831,6 @@ static void toshiba_acpi_notify(acpi_handle handle, u32 event, void *context)
input_report_key(toshiba_acpi.hotkey_dev, input_report_key(toshiba_acpi.hotkey_dev,
key->keycode, 0); key->keycode, 0);
input_sync(toshiba_acpi.hotkey_dev); input_sync(toshiba_acpi.hotkey_dev);
}
} else if (hci_result == HCI_NOT_SUPPORTED) { } else if (hci_result == HCI_NOT_SUPPORTED) {
/* This is a workaround for an unresolved issue on /* This is a workaround for an unresolved issue on
* some machines where system events sporadically * some machines where system events sporadically
......
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