2010-03-21 05:26:34 +03:00
/*
* Eee PC WMI hotkey driver
*
* Copyright ( C ) 2010 Intel Corporation .
2010-11-29 10:14:11 +03:00
* Copyright ( C ) 2010 Corentin Chary < corentin . chary @ gmail . com >
2010-03-21 05:26:34 +03:00
*
* Portions based on wistron_btns . c :
* Copyright ( C ) 2005 Miloslav Trmac < mitr @ volny . cz >
* Copyright ( C ) 2005 Bernhard Rosenkraenzer < bero @ arklinux . org >
* Copyright ( C ) 2005 Dmitry Torokhov < dtor @ mail . ru >
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation ; either version 2 of the License , or
* ( at your option ) any later version .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
* You should have received a copy of the GNU General Public License
* along with this program ; if not , write to the Free Software
* Foundation , Inc . , 59 Temple Place , Suite 330 , Boston , MA 02111 - 1307 USA
*/
2010-04-11 05:26:33 +04:00
# define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
2010-03-21 05:26:34 +03:00
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/init.h>
# include <linux/types.h>
2010-04-05 06:37:59 +04:00
# include <linux/slab.h>
2010-03-21 05:26:34 +03:00
# include <linux/input.h>
# include <linux/input/sparse-keymap.h>
2010-04-11 05:27:54 +04:00
# include <linux/fb.h>
# include <linux/backlight.h>
2010-11-29 10:14:06 +03:00
# include <linux/leds.h>
2010-11-29 10:14:07 +03:00
# include <linux/rfkill.h>
2011-02-06 15:28:28 +03:00
# include <linux/pci.h>
# include <linux/pci_hotplug.h>
2010-11-29 10:14:09 +03:00
# include <linux/debugfs.h>
# include <linux/seq_file.h>
2010-04-11 05:27:19 +04:00
# include <linux/platform_device.h>
2011-02-06 15:28:28 +03:00
# include <linux/dmi.h>
2010-03-21 05:26:34 +03:00
# include <acpi/acpi_bus.h>
# include <acpi/acpi_drivers.h>
2010-04-11 05:27:19 +04:00
# define EEEPC_WMI_FILE "eeepc-wmi"
2010-03-21 05:26:34 +03:00
MODULE_AUTHOR ( " Yong Wang <yong.y.wang@intel.com> " ) ;
MODULE_DESCRIPTION ( " Eee PC WMI Hotkey Driver " ) ;
MODULE_LICENSE ( " GPL " ) ;
2010-11-29 10:14:14 +03:00
# define EEEPC_ACPI_HID "ASUS010" /* old _HID used in eeepc-laptop */
2010-03-21 05:26:34 +03:00
# define EEEPC_WMI_EVENT_GUID "ABBC0F72-8EA1-11D1-00A0-C90629100000"
2010-04-11 05:27:54 +04:00
# define EEEPC_WMI_MGMT_GUID "97845ED0-4E6D-11DE-8A39-0800200C9A66"
2010-03-21 05:26:34 +03:00
MODULE_ALIAS ( " wmi: " EEEPC_WMI_EVENT_GUID ) ;
2010-04-11 05:27:54 +04:00
MODULE_ALIAS ( " wmi: " EEEPC_WMI_MGMT_GUID ) ;
2010-03-21 05:26:34 +03:00
# define NOTIFY_BRNUP_MIN 0x11
# define NOTIFY_BRNUP_MAX 0x1f
# define NOTIFY_BRNDOWN_MIN 0x20
# define NOTIFY_BRNDOWN_MAX 0x2e
2010-04-11 05:27:54 +04:00
# define EEEPC_WMI_METHODID_DEVS 0x53564544
# define EEEPC_WMI_METHODID_DSTS 0x53544344
2010-10-12 03:47:18 +04:00
# define EEEPC_WMI_METHODID_CFVS 0x53564643
2010-04-11 05:27:54 +04:00
# define EEEPC_WMI_DEVID_BACKLIGHT 0x00050012
2010-11-29 10:14:06 +03:00
# define EEEPC_WMI_DEVID_TPDLED 0x00100011
2010-11-29 10:14:07 +03:00
# define EEEPC_WMI_DEVID_WLAN 0x00010011
# define EEEPC_WMI_DEVID_BLUETOOTH 0x00010013
# define EEEPC_WMI_DEVID_WWAN3G 0x00010019
2010-04-11 05:27:54 +04:00
2011-02-06 15:28:28 +03:00
static bool hotplug_wireless ;
module_param ( hotplug_wireless , bool , 0444 ) ;
MODULE_PARM_DESC ( hotplug_wireless ,
" Enable hotplug for wireless device. "
" If your laptop needs that, please report to "
" acpi4asus-user@lists.sourceforge.net. " ) ;
2010-03-21 05:26:34 +03:00
static const struct key_entry eeepc_wmi_keymap [ ] = {
/* Sleep already handled via generic ACPI code */
{ KE_IGNORE , NOTIFY_BRNDOWN_MIN , { KEY_BRIGHTNESSDOWN } } ,
{ KE_IGNORE , NOTIFY_BRNUP_MIN , { KEY_BRIGHTNESSUP } } ,
2011-02-06 15:28:26 +03:00
{ KE_KEY , 0x30 , { KEY_VOLUMEUP } } ,
{ KE_KEY , 0x31 , { KEY_VOLUMEDOWN } } ,
{ KE_KEY , 0x32 , { KEY_MUTE } } ,
{ KE_KEY , 0x5c , { KEY_F15 } } ,
{ KE_KEY , 0x5d , { KEY_WLAN } } ,
2010-10-12 03:47:17 +04:00
{ KE_KEY , 0x6b , { KEY_F13 } } , /* Disable Touchpad */
2011-02-06 15:28:27 +03:00
{ KE_KEY , 0x88 , { KEY_WLAN } } ,
2011-02-06 15:28:26 +03:00
{ KE_KEY , 0xcc , { KEY_SWITCHVIDEOMODE } } ,
{ KE_KEY , 0xe0 , { KEY_PROG1 } } ,
2010-10-12 03:47:17 +04:00
{ KE_KEY , 0xe1 , { KEY_F14 } } ,
{ KE_KEY , 0xe9 , { KEY_DISPLAY_OFF } } ,
2010-03-21 05:26:34 +03:00
{ KE_END , 0 } ,
} ;
2010-04-11 05:27:54 +04:00
struct bios_args {
u32 dev_id ;
u32 ctrl_param ;
} ;
2010-11-29 10:14:09 +03:00
/*
* eeepc - wmi / - debugfs root directory
* dev_id - current dev_id
* ctrl_param - current ctrl_param
* devs - call DEVS ( dev_id , ctrl_param ) and print result
* dsts - call DSTS ( dev_id ) and print result
*/
struct eeepc_wmi_debug {
struct dentry * root ;
u32 dev_id ;
u32 ctrl_param ;
} ;
2010-04-11 05:26:33 +04:00
struct eeepc_wmi {
2011-02-06 15:28:28 +03:00
bool hotplug_wireless ;
2010-04-11 05:26:33 +04:00
struct input_dev * inputdev ;
2010-04-11 05:27:54 +04:00
struct backlight_device * backlight_device ;
2010-11-29 10:14:05 +03:00
struct platform_device * platform_device ;
2010-11-29 10:14:06 +03:00
struct led_classdev tpd_led ;
int tpd_led_wk ;
struct workqueue_struct * led_workqueue ;
struct work_struct tpd_led_work ;
2010-11-29 10:14:07 +03:00
struct rfkill * wlan_rfkill ;
struct rfkill * bluetooth_rfkill ;
struct rfkill * wwan3g_rfkill ;
2010-11-29 10:14:09 +03:00
2011-02-06 15:28:28 +03:00
struct hotplug_slot * hotplug_slot ;
struct mutex hotplug_lock ;
2010-11-29 10:14:09 +03:00
struct eeepc_wmi_debug debug ;
2010-04-11 05:26:33 +04:00
} ;
2010-11-29 10:14:05 +03:00
/* Only used in eeepc_wmi_init() and eeepc_wmi_exit() */
2010-04-11 05:27:19 +04:00
static struct platform_device * platform_device ;
2010-03-21 05:26:34 +03:00
2010-04-11 05:26:33 +04:00
static int eeepc_wmi_input_init ( struct eeepc_wmi * eeepc )
2010-03-21 05:26:34 +03:00
{
int err ;
2010-04-11 05:26:33 +04:00
eeepc - > inputdev = input_allocate_device ( ) ;
if ( ! eeepc - > inputdev )
2010-03-21 05:26:34 +03:00
return - ENOMEM ;
2010-04-11 05:26:33 +04:00
eeepc - > inputdev - > name = " Eee PC WMI hotkeys " ;
2010-04-11 05:27:19 +04:00
eeepc - > inputdev - > phys = EEEPC_WMI_FILE " /input0 " ;
2010-04-11 05:26:33 +04:00
eeepc - > inputdev - > id . bustype = BUS_HOST ;
2010-11-29 10:14:05 +03:00
eeepc - > inputdev - > dev . parent = & eeepc - > platform_device - > dev ;
2010-03-21 05:26:34 +03:00
2010-04-11 05:26:33 +04:00
err = sparse_keymap_setup ( eeepc - > inputdev , eeepc_wmi_keymap , NULL ) ;
2010-03-21 05:26:34 +03:00
if ( err )
goto err_free_dev ;
2010-04-11 05:26:33 +04:00
err = input_register_device ( eeepc - > inputdev ) ;
2010-03-21 05:26:34 +03:00
if ( err )
goto err_free_keymap ;
return 0 ;
err_free_keymap :
2010-04-11 05:26:33 +04:00
sparse_keymap_free ( eeepc - > inputdev ) ;
2010-03-21 05:26:34 +03:00
err_free_dev :
2010-04-11 05:26:33 +04:00
input_free_device ( eeepc - > inputdev ) ;
2010-03-21 05:26:34 +03:00
return err ;
}
2010-04-11 05:26:33 +04:00
static void eeepc_wmi_input_exit ( struct eeepc_wmi * eeepc )
{
if ( eeepc - > inputdev ) {
sparse_keymap_free ( eeepc - > inputdev ) ;
input_unregister_device ( eeepc - > inputdev ) ;
}
eeepc - > inputdev = NULL ;
}
2010-11-29 10:14:10 +03:00
static acpi_status eeepc_wmi_get_devstate ( u32 dev_id , u32 * retval )
2010-04-11 05:27:54 +04:00
{
struct acpi_buffer input = { ( acpi_size ) sizeof ( u32 ) , & dev_id } ;
struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER , NULL } ;
union acpi_object * obj ;
acpi_status status ;
u32 tmp ;
status = wmi_evaluate_method ( EEEPC_WMI_MGMT_GUID ,
2011-02-06 15:28:28 +03:00
1 , EEEPC_WMI_METHODID_DSTS ,
& input , & output ) ;
2010-04-11 05:27:54 +04:00
if ( ACPI_FAILURE ( status ) )
return status ;
obj = ( union acpi_object * ) output . pointer ;
if ( obj & & obj - > type = = ACPI_TYPE_INTEGER )
tmp = ( u32 ) obj - > integer . value ;
else
tmp = 0 ;
2010-11-29 10:14:10 +03:00
if ( retval )
* retval = tmp ;
2010-04-11 05:27:54 +04:00
kfree ( obj ) ;
return status ;
}
2010-11-29 10:14:09 +03:00
static acpi_status eeepc_wmi_set_devstate ( u32 dev_id , u32 ctrl_param ,
u32 * retval )
2010-04-11 05:27:54 +04:00
{
struct bios_args args = {
. dev_id = dev_id ,
. ctrl_param = ctrl_param ,
} ;
struct acpi_buffer input = { ( acpi_size ) sizeof ( args ) , & args } ;
acpi_status status ;
2010-11-29 10:14:09 +03:00
if ( ! retval ) {
status = wmi_evaluate_method ( EEEPC_WMI_MGMT_GUID , 1 ,
EEEPC_WMI_METHODID_DEVS ,
& input , NULL ) ;
} else {
struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER , NULL } ;
union acpi_object * obj ;
u32 tmp ;
status = wmi_evaluate_method ( EEEPC_WMI_MGMT_GUID , 1 ,
EEEPC_WMI_METHODID_DEVS ,
& input , & output ) ;
if ( ACPI_FAILURE ( status ) )
return status ;
obj = ( union acpi_object * ) output . pointer ;
if ( obj & & obj - > type = = ACPI_TYPE_INTEGER )
tmp = ( u32 ) obj - > integer . value ;
else
tmp = 0 ;
* retval = tmp ;
kfree ( obj ) ;
}
2010-04-11 05:27:54 +04:00
return status ;
}
2010-11-29 10:14:06 +03:00
/*
* LEDs
*/
/*
* 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 Eeepc ACPI stuff during a
* potentially bad time , such as a timer interrupt .
*/
static void tpd_led_update ( struct work_struct * work )
{
int ctrl_param ;
struct eeepc_wmi * eeepc ;
eeepc = container_of ( work , struct eeepc_wmi , tpd_led_work ) ;
ctrl_param = eeepc - > tpd_led_wk ;
2010-11-29 10:14:09 +03:00
eeepc_wmi_set_devstate ( EEEPC_WMI_DEVID_TPDLED , ctrl_param , NULL ) ;
2010-11-29 10:14:06 +03:00
}
static void tpd_led_set ( struct led_classdev * led_cdev ,
enum led_brightness value )
{
struct eeepc_wmi * eeepc ;
eeepc = container_of ( led_cdev , struct eeepc_wmi , tpd_led ) ;
eeepc - > tpd_led_wk = ! ! value ;
queue_work ( eeepc - > led_workqueue , & eeepc - > tpd_led_work ) ;
}
static int read_tpd_state ( struct eeepc_wmi * eeepc )
{
2010-11-29 10:14:12 +03:00
u32 retval ;
2010-11-29 10:14:06 +03:00
acpi_status status ;
2010-11-29 10:14:10 +03:00
status = eeepc_wmi_get_devstate ( EEEPC_WMI_DEVID_TPDLED , & retval ) ;
2010-11-29 10:14:06 +03:00
if ( ACPI_FAILURE ( status ) )
return - 1 ;
2010-11-29 10:14:10 +03:00
else if ( ! retval | | retval = = 0x00060000 )
2010-11-29 10:14:06 +03:00
/*
* if touchpad led is present , DSTS will set some bits ,
* usually 0x00020000 .
* 0x00060000 means that the device is not supported
*/
return - ENODEV ;
else
/* Status is stored in the first bit */
2010-11-29 10:14:10 +03:00
return retval & 0x1 ;
2010-11-29 10:14:06 +03:00
}
static enum led_brightness tpd_led_get ( struct led_classdev * led_cdev )
{
struct eeepc_wmi * eeepc ;
eeepc = container_of ( led_cdev , struct eeepc_wmi , tpd_led ) ;
return read_tpd_state ( eeepc ) ;
}
static int eeepc_wmi_led_init ( struct eeepc_wmi * eeepc )
{
int rv ;
if ( read_tpd_state ( eeepc ) < 0 )
return 0 ;
eeepc - > led_workqueue = create_singlethread_workqueue ( " led_workqueue " ) ;
if ( ! eeepc - > led_workqueue )
return - ENOMEM ;
INIT_WORK ( & eeepc - > tpd_led_work , tpd_led_update ) ;
eeepc - > tpd_led . name = " eeepc::touchpad " ;
eeepc - > tpd_led . brightness_set = tpd_led_set ;
eeepc - > tpd_led . brightness_get = tpd_led_get ;
eeepc - > tpd_led . max_brightness = 1 ;
rv = led_classdev_register ( & eeepc - > platform_device - > dev ,
& eeepc - > tpd_led ) ;
if ( rv ) {
destroy_workqueue ( eeepc - > led_workqueue ) ;
return rv ;
}
return 0 ;
}
static void eeepc_wmi_led_exit ( struct eeepc_wmi * eeepc )
{
if ( eeepc - > tpd_led . dev )
led_classdev_unregister ( & eeepc - > tpd_led ) ;
if ( eeepc - > led_workqueue )
destroy_workqueue ( eeepc - > led_workqueue ) ;
}
2011-02-06 15:28:28 +03:00
/*
* PCI hotplug ( for wlan rfkill )
*/
static bool eeepc_wlan_rfkill_blocked ( struct eeepc_wmi * eeepc )
{
u32 retval ;
acpi_status status ;
status = eeepc_wmi_get_devstate ( EEEPC_WMI_DEVID_WLAN , & retval ) ;
if ( ACPI_FAILURE ( status ) )
return false ;
return ! ( retval & 0x1 ) ;
}
static void eeepc_rfkill_hotplug ( struct eeepc_wmi * eeepc )
{
struct pci_dev * dev ;
struct pci_bus * bus ;
bool blocked = eeepc_wlan_rfkill_blocked ( eeepc ) ;
bool absent ;
u32 l ;
if ( eeepc - > wlan_rfkill )
rfkill_set_sw_state ( eeepc - > wlan_rfkill , blocked ) ;
mutex_lock ( & eeepc - > hotplug_lock ) ;
if ( eeepc - > hotplug_slot ) {
bus = pci_find_bus ( 0 , 1 ) ;
if ( ! bus ) {
pr_warning ( " Unable to find PCI bus 1? \n " ) ;
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 ) {
dev = pci_get_slot ( bus , 0 ) ;
if ( dev ) {
/* Device already present */
pci_dev_put ( dev ) ;
goto out_unlock ;
}
dev = pci_scan_single_device ( bus , 0 ) ;
if ( dev ) {
pci_bus_assign_resources ( bus ) ;
if ( pci_bus_add_device ( dev ) )
pr_err ( " Unable to hotplug wifi \n " ) ;
}
} else {
dev = pci_get_slot ( bus , 0 ) ;
if ( dev ) {
pci_remove_bus_device ( dev ) ;
pci_dev_put ( dev ) ;
}
}
}
out_unlock :
mutex_unlock ( & eeepc - > hotplug_lock ) ;
}
static void eeepc_rfkill_notify ( acpi_handle handle , u32 event , void * data )
{
struct eeepc_wmi * eeepc = data ;
if ( event ! = ACPI_NOTIFY_BUS_CHECK )
return ;
eeepc_rfkill_hotplug ( eeepc ) ;
}
static int eeepc_register_rfkill_notifier ( struct eeepc_wmi * eeepc ,
char * node )
{
acpi_status status ;
acpi_handle handle ;
status = acpi_get_handle ( NULL , node , & handle ) ;
if ( ACPI_SUCCESS ( status ) ) {
status = acpi_install_notify_handler ( handle ,
ACPI_SYSTEM_NOTIFY ,
eeepc_rfkill_notify ,
eeepc ) ;
if ( ACPI_FAILURE ( status ) )
pr_warning ( " Failed to register notify on %s \n " , node ) ;
} else
return - ENODEV ;
return 0 ;
}
static void eeepc_unregister_rfkill_notifier ( struct eeepc_wmi * eeepc ,
char * node )
{
acpi_status status = AE_OK ;
acpi_handle handle ;
status = acpi_get_handle ( NULL , node , & handle ) ;
if ( ACPI_SUCCESS ( status ) ) {
status = acpi_remove_notify_handler ( handle ,
ACPI_SYSTEM_NOTIFY ,
eeepc_rfkill_notify ) ;
if ( ACPI_FAILURE ( status ) )
pr_err ( " Error removing rfkill notify handler %s \n " ,
node ) ;
}
}
static int eeepc_get_adapter_status ( struct hotplug_slot * hotplug_slot ,
u8 * value )
{
u32 retval ;
acpi_status status ;
status = eeepc_wmi_get_devstate ( EEEPC_WMI_DEVID_WLAN , & retval ) ;
if ( ACPI_FAILURE ( status ) )
return - EIO ;
if ( ! retval | | retval = = 0x00060000 )
return - ENODEV ;
else
* value = ( retval & 0x1 ) ;
return 0 ;
}
static void eeepc_cleanup_pci_hotplug ( struct hotplug_slot * hotplug_slot )
{
kfree ( hotplug_slot - > info ) ;
kfree ( hotplug_slot ) ;
}
static struct hotplug_slot_ops eeepc_hotplug_slot_ops = {
. owner = THIS_MODULE ,
. get_adapter_status = eeepc_get_adapter_status ,
. get_power_status = eeepc_get_adapter_status ,
} ;
static int eeepc_setup_pci_hotplug ( struct eeepc_wmi * eeepc )
{
int ret = - ENOMEM ;
struct pci_bus * bus = pci_find_bus ( 0 , 1 ) ;
if ( ! bus ) {
pr_err ( " Unable to find wifi PCI bus \n " ) ;
return - ENODEV ;
}
eeepc - > hotplug_slot = kzalloc ( sizeof ( struct hotplug_slot ) , GFP_KERNEL ) ;
if ( ! eeepc - > hotplug_slot )
goto error_slot ;
eeepc - > hotplug_slot - > info = kzalloc ( sizeof ( struct hotplug_slot_info ) ,
GFP_KERNEL ) ;
if ( ! eeepc - > hotplug_slot - > info )
goto error_info ;
eeepc - > hotplug_slot - > private = eeepc ;
eeepc - > hotplug_slot - > release = & eeepc_cleanup_pci_hotplug ;
eeepc - > hotplug_slot - > ops = & eeepc_hotplug_slot_ops ;
eeepc_get_adapter_status ( eeepc - > hotplug_slot ,
& eeepc - > hotplug_slot - > info - > adapter_status ) ;
ret = pci_hp_register ( eeepc - > hotplug_slot , bus , 0 , " eeepc-wifi " ) ;
if ( ret ) {
pr_err ( " Unable to register hotplug slot - %d \n " , ret ) ;
goto error_register ;
}
return 0 ;
error_register :
kfree ( eeepc - > hotplug_slot - > info ) ;
error_info :
kfree ( eeepc - > hotplug_slot ) ;
eeepc - > hotplug_slot = NULL ;
error_slot :
return ret ;
}
2010-11-29 10:14:07 +03:00
/*
* Rfkill devices
*/
static int eeepc_rfkill_set ( void * data , bool blocked )
{
int dev_id = ( unsigned long ) data ;
u32 ctrl_param = ! blocked ;
2010-11-29 10:14:09 +03:00
return eeepc_wmi_set_devstate ( dev_id , ctrl_param , NULL ) ;
2010-11-29 10:14:07 +03:00
}
static void eeepc_rfkill_query ( struct rfkill * rfkill , void * data )
{
int dev_id = ( unsigned long ) data ;
2010-11-29 10:14:10 +03:00
u32 retval ;
2010-11-29 10:14:07 +03:00
acpi_status status ;
2010-11-29 10:14:10 +03:00
status = eeepc_wmi_get_devstate ( dev_id , & retval ) ;
2010-11-29 10:14:07 +03:00
if ( ACPI_FAILURE ( status ) )
return ;
2010-11-29 10:14:10 +03:00
rfkill_set_sw_state ( rfkill , ! ( retval & 0x1 ) ) ;
2010-11-29 10:14:07 +03:00
}
static const struct rfkill_ops eeepc_rfkill_ops = {
. set_block = eeepc_rfkill_set ,
. query = eeepc_rfkill_query ,
} ;
static int eeepc_new_rfkill ( struct eeepc_wmi * eeepc ,
struct rfkill * * rfkill ,
const char * name ,
enum rfkill_type type , int dev_id )
{
int result ;
2010-11-29 10:14:10 +03:00
u32 retval ;
2010-11-29 10:14:07 +03:00
acpi_status status ;
2010-11-29 10:14:10 +03:00
status = eeepc_wmi_get_devstate ( dev_id , & retval ) ;
2010-11-29 10:14:07 +03:00
if ( ACPI_FAILURE ( status ) )
return - 1 ;
/* If the device is present, DSTS will always set some bits
* 0x00070000 - 1110000000000000000 - device supported
* 0x00060000 - 1100000000000000000 - not supported
* 0x00020000 - 0100000000000000000 - device supported
* 0x00010000 - 0010000000000000000 - not supported / special mode ?
*/
2010-11-29 10:14:10 +03:00
if ( ! retval | | retval = = 0x00060000 )
2010-11-29 10:14:07 +03:00
return - ENODEV ;
* rfkill = rfkill_alloc ( name , & eeepc - > platform_device - > dev , type ,
& eeepc_rfkill_ops , ( void * ) ( long ) dev_id ) ;
if ( ! * rfkill )
return - EINVAL ;
2010-11-29 10:14:10 +03:00
rfkill_init_sw_state ( * rfkill , ! ( retval & 0x1 ) ) ;
2010-11-29 10:14:07 +03:00
result = rfkill_register ( * rfkill ) ;
if ( result ) {
rfkill_destroy ( * rfkill ) ;
* rfkill = NULL ;
return result ;
}
return 0 ;
}
static void eeepc_wmi_rfkill_exit ( struct eeepc_wmi * eeepc )
{
2011-02-06 15:28:28 +03:00
eeepc_unregister_rfkill_notifier ( eeepc , " \\ _SB.PCI0.P0P5 " ) ;
eeepc_unregister_rfkill_notifier ( eeepc , " \\ _SB.PCI0.P0P6 " ) ;
eeepc_unregister_rfkill_notifier ( eeepc , " \\ _SB.PCI0.P0P7 " ) ;
2010-11-29 10:14:07 +03:00
if ( eeepc - > wlan_rfkill ) {
rfkill_unregister ( eeepc - > wlan_rfkill ) ;
rfkill_destroy ( eeepc - > wlan_rfkill ) ;
eeepc - > wlan_rfkill = NULL ;
}
2011-02-06 15:28:28 +03:00
/*
* Refresh pci hotplug in case the rfkill state was changed after
* eeepc_unregister_rfkill_notifier ( )
*/
eeepc_rfkill_hotplug ( eeepc ) ;
if ( eeepc - > hotplug_slot )
pci_hp_deregister ( eeepc - > hotplug_slot ) ;
2010-11-29 10:14:07 +03:00
if ( eeepc - > bluetooth_rfkill ) {
rfkill_unregister ( eeepc - > bluetooth_rfkill ) ;
rfkill_destroy ( eeepc - > bluetooth_rfkill ) ;
eeepc - > bluetooth_rfkill = NULL ;
}
if ( eeepc - > wwan3g_rfkill ) {
rfkill_unregister ( eeepc - > wwan3g_rfkill ) ;
rfkill_destroy ( eeepc - > wwan3g_rfkill ) ;
eeepc - > wwan3g_rfkill = NULL ;
}
}
static int eeepc_wmi_rfkill_init ( struct eeepc_wmi * eeepc )
{
int result = 0 ;
2011-02-06 15:28:28 +03:00
mutex_init ( & eeepc - > hotplug_lock ) ;
2010-11-29 10:14:07 +03:00
result = eeepc_new_rfkill ( eeepc , & eeepc - > wlan_rfkill ,
" eeepc-wlan " , RFKILL_TYPE_WLAN ,
EEEPC_WMI_DEVID_WLAN ) ;
if ( result & & result ! = - ENODEV )
goto exit ;
result = eeepc_new_rfkill ( eeepc , & eeepc - > bluetooth_rfkill ,
" eeepc-bluetooth " , RFKILL_TYPE_BLUETOOTH ,
EEEPC_WMI_DEVID_BLUETOOTH ) ;
if ( result & & result ! = - ENODEV )
goto exit ;
result = eeepc_new_rfkill ( eeepc , & eeepc - > wwan3g_rfkill ,
" eeepc-wwan3g " , RFKILL_TYPE_WWAN ,
EEEPC_WMI_DEVID_WWAN3G ) ;
if ( result & & result ! = - ENODEV )
goto exit ;
2011-02-06 15:28:28 +03:00
result = eeepc_setup_pci_hotplug ( eeepc ) ;
/*
* If we get - EBUSY then something else is handling the PCI hotplug -
* don ' t fail in this case
*/
if ( result = = - EBUSY )
result = 0 ;
eeepc_register_rfkill_notifier ( eeepc , " \\ _SB.PCI0.P0P5 " ) ;
eeepc_register_rfkill_notifier ( eeepc , " \\ _SB.PCI0.P0P6 " ) ;
eeepc_register_rfkill_notifier ( eeepc , " \\ _SB.PCI0.P0P7 " ) ;
/*
* Refresh pci hotplug in case the rfkill state was changed during
* setup .
*/
eeepc_rfkill_hotplug ( eeepc ) ;
2010-11-29 10:14:07 +03:00
exit :
if ( result & & result ! = - ENODEV )
eeepc_wmi_rfkill_exit ( eeepc ) ;
if ( result = = - ENODEV )
result = 0 ;
return result ;
}
2010-11-29 10:14:06 +03:00
/*
* Backlight
*/
2010-04-11 05:27:54 +04:00
static int read_brightness ( struct backlight_device * bd )
{
2010-11-29 10:14:12 +03:00
u32 retval ;
2010-04-11 05:27:54 +04:00
acpi_status status ;
2010-11-29 10:14:10 +03:00
status = eeepc_wmi_get_devstate ( EEEPC_WMI_DEVID_BACKLIGHT , & retval ) ;
2010-04-11 05:27:54 +04:00
if ( ACPI_FAILURE ( status ) )
return - 1 ;
else
2010-11-29 10:14:10 +03:00
return retval & 0xFF ;
2010-04-11 05:27:54 +04:00
}
static int update_bl_status ( struct backlight_device * bd )
{
2010-11-29 10:14:12 +03:00
u32 ctrl_param ;
2010-04-11 05:27:54 +04:00
acpi_status status ;
ctrl_param = bd - > props . brightness ;
2010-11-29 10:14:09 +03:00
status = eeepc_wmi_set_devstate ( EEEPC_WMI_DEVID_BACKLIGHT ,
ctrl_param , NULL ) ;
2010-04-11 05:27:54 +04:00
if ( ACPI_FAILURE ( status ) )
return - 1 ;
else
return 0 ;
}
static const struct backlight_ops eeepc_wmi_bl_ops = {
. get_brightness = read_brightness ,
. update_status = update_bl_status ,
} ;
static int eeepc_wmi_backlight_notify ( struct eeepc_wmi * eeepc , int code )
{
struct backlight_device * bd = eeepc - > backlight_device ;
int old = bd - > props . brightness ;
2010-05-19 14:37:01 +04:00
int new = old ;
2010-04-11 05:27:54 +04:00
if ( code > = NOTIFY_BRNUP_MIN & & code < = NOTIFY_BRNUP_MAX )
new = code - NOTIFY_BRNUP_MIN + 1 ;
else if ( code > = NOTIFY_BRNDOWN_MIN & & code < = NOTIFY_BRNDOWN_MAX )
new = code - NOTIFY_BRNDOWN_MIN ;
bd - > props . brightness = new ;
backlight_update_status ( bd ) ;
backlight_force_update ( bd , BACKLIGHT_UPDATE_HOTKEY ) ;
return old ;
}
static int eeepc_wmi_backlight_init ( struct eeepc_wmi * eeepc )
{
struct backlight_device * bd ;
struct backlight_properties props ;
memset ( & props , 0 , sizeof ( struct backlight_properties ) ) ;
props . max_brightness = 15 ;
bd = backlight_device_register ( EEEPC_WMI_FILE ,
2010-11-29 10:14:05 +03:00
& eeepc - > platform_device - > dev , eeepc ,
2010-04-11 05:27:54 +04:00
& eeepc_wmi_bl_ops , & props ) ;
if ( IS_ERR ( bd ) ) {
pr_err ( " Could not register backlight device \n " ) ;
return PTR_ERR ( bd ) ;
}
eeepc - > backlight_device = bd ;
bd - > props . brightness = read_brightness ( bd ) ;
bd - > props . power = FB_BLANK_UNBLANK ;
backlight_update_status ( bd ) ;
return 0 ;
}
static void eeepc_wmi_backlight_exit ( struct eeepc_wmi * eeepc )
{
if ( eeepc - > backlight_device )
backlight_device_unregister ( eeepc - > backlight_device ) ;
eeepc - > backlight_device = NULL ;
}
static void eeepc_wmi_notify ( u32 value , void * context )
{
struct eeepc_wmi * eeepc = context ;
struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER , NULL } ;
union acpi_object * obj ;
acpi_status status ;
int code ;
int orig_code ;
status = wmi_get_event_data ( value , & response ) ;
if ( status ! = AE_OK ) {
pr_err ( " bad event status 0x%x \n " , status ) ;
return ;
}
obj = ( union acpi_object * ) response . pointer ;
if ( obj & & obj - > type = = ACPI_TYPE_INTEGER ) {
code = obj - > integer . value ;
orig_code = code ;
if ( code > = NOTIFY_BRNUP_MIN & & code < = NOTIFY_BRNUP_MAX )
code = NOTIFY_BRNUP_MIN ;
else if ( code > = NOTIFY_BRNDOWN_MIN & &
code < = NOTIFY_BRNDOWN_MAX )
code = NOTIFY_BRNDOWN_MIN ;
if ( code = = NOTIFY_BRNUP_MIN | | code = = NOTIFY_BRNDOWN_MIN ) {
if ( ! acpi_video_backlight_support ( ) )
eeepc_wmi_backlight_notify ( eeepc , orig_code ) ;
}
if ( ! sparse_keymap_report_event ( eeepc - > inputdev ,
code , 1 , true ) )
pr_info ( " Unknown key %x pressed \n " , code ) ;
}
kfree ( obj ) ;
}
2010-11-03 21:14:01 +03:00
static ssize_t store_cpufv ( struct device * dev , struct device_attribute * attr ,
const char * buf , size_t count )
2010-10-12 03:47:18 +04:00
{
int value ;
struct acpi_buffer input = { ( acpi_size ) sizeof ( value ) , & value } ;
acpi_status status ;
if ( ! count | | sscanf ( buf , " %i " , & value ) ! = 1 )
return - EINVAL ;
if ( value < 0 | | value > 2 )
return - EINVAL ;
status = wmi_evaluate_method ( EEEPC_WMI_MGMT_GUID ,
1 , EEEPC_WMI_METHODID_CFVS , & input , NULL ) ;
if ( ACPI_FAILURE ( status ) )
return - EIO ;
else
return count ;
}
static DEVICE_ATTR ( cpufv , S_IRUGO | S_IWUSR , NULL , store_cpufv ) ;
2010-11-29 10:14:08 +03:00
static struct attribute * platform_attributes [ ] = {
& dev_attr_cpufv . attr ,
NULL
} ;
static struct attribute_group platform_attribute_group = {
. attrs = platform_attributes
} ;
2010-10-12 03:47:18 +04:00
static void eeepc_wmi_sysfs_exit ( struct platform_device * device )
{
2010-11-29 10:14:08 +03:00
sysfs_remove_group ( & device - > dev . kobj , & platform_attribute_group ) ;
2010-10-12 03:47:18 +04:00
}
static int eeepc_wmi_sysfs_init ( struct platform_device * device )
{
2010-11-29 10:14:08 +03:00
return sysfs_create_group ( & device - > dev . kobj , & platform_attribute_group ) ;
2010-10-12 03:47:18 +04:00
}
2010-11-29 10:14:05 +03:00
/*
* Platform device
*/
static int __init eeepc_wmi_platform_init ( struct eeepc_wmi * eeepc )
2010-03-21 05:26:34 +03:00
{
int err ;
2010-11-29 10:14:05 +03:00
eeepc - > platform_device = platform_device_alloc ( EEEPC_WMI_FILE , - 1 ) ;
if ( ! eeepc - > platform_device )
return - ENOMEM ;
platform_set_drvdata ( eeepc - > platform_device , eeepc ) ;
err = platform_device_add ( eeepc - > platform_device ) ;
if ( err )
goto fail_platform_device ;
err = eeepc_wmi_sysfs_init ( eeepc - > platform_device ) ;
if ( err )
goto fail_sysfs ;
return 0 ;
fail_sysfs :
platform_device_del ( eeepc - > platform_device ) ;
fail_platform_device :
platform_device_put ( eeepc - > platform_device ) ;
return err ;
}
static void eeepc_wmi_platform_exit ( struct eeepc_wmi * eeepc )
{
eeepc_wmi_sysfs_exit ( eeepc - > platform_device ) ;
platform_device_unregister ( eeepc - > platform_device ) ;
}
2010-11-29 10:14:09 +03:00
/*
* debugfs
*/
struct eeepc_wmi_debugfs_node {
struct eeepc_wmi * eeepc ;
char * name ;
int ( * show ) ( struct seq_file * m , void * data ) ;
} ;
static int show_dsts ( struct seq_file * m , void * data )
{
struct eeepc_wmi * eeepc = m - > private ;
acpi_status status ;
u32 retval = - 1 ;
status = eeepc_wmi_get_devstate ( eeepc - > debug . dev_id , & retval ) ;
if ( ACPI_FAILURE ( status ) )
return - EIO ;
seq_printf ( m , " DSTS(%x) = %x \n " , eeepc - > debug . dev_id , retval ) ;
return 0 ;
}
static int show_devs ( struct seq_file * m , void * data )
{
struct eeepc_wmi * eeepc = m - > private ;
acpi_status status ;
u32 retval = - 1 ;
status = eeepc_wmi_set_devstate ( eeepc - > debug . dev_id ,
eeepc - > debug . ctrl_param , & retval ) ;
if ( ACPI_FAILURE ( status ) )
return - EIO ;
seq_printf ( m , " DEVS(%x, %x) = %x \n " , eeepc - > debug . dev_id ,
eeepc - > debug . ctrl_param , retval ) ;
return 0 ;
}
static struct eeepc_wmi_debugfs_node eeepc_wmi_debug_files [ ] = {
{ NULL , " devs " , show_devs } ,
{ NULL , " dsts " , show_dsts } ,
} ;
static int eeepc_wmi_debugfs_open ( struct inode * inode , struct file * file )
{
struct eeepc_wmi_debugfs_node * node = inode - > i_private ;
return single_open ( file , node - > show , node - > eeepc ) ;
}
static const struct file_operations eeepc_wmi_debugfs_io_ops = {
. owner = THIS_MODULE ,
. open = eeepc_wmi_debugfs_open ,
. read = seq_read ,
. llseek = seq_lseek ,
. release = single_release ,
} ;
static void eeepc_wmi_debugfs_exit ( struct eeepc_wmi * eeepc )
{
debugfs_remove_recursive ( eeepc - > debug . root ) ;
}
static int eeepc_wmi_debugfs_init ( struct eeepc_wmi * eeepc )
{
struct dentry * dent ;
int i ;
eeepc - > debug . root = debugfs_create_dir ( EEEPC_WMI_FILE , NULL ) ;
if ( ! eeepc - > debug . root ) {
pr_err ( " failed to create debugfs directory " ) ;
goto error_debugfs ;
}
dent = debugfs_create_x32 ( " dev_id " , S_IRUGO | S_IWUSR ,
eeepc - > debug . root , & eeepc - > debug . dev_id ) ;
if ( ! dent )
goto error_debugfs ;
dent = debugfs_create_x32 ( " ctrl_param " , S_IRUGO | S_IWUSR ,
eeepc - > debug . root , & eeepc - > debug . ctrl_param ) ;
if ( ! dent )
goto error_debugfs ;
for ( i = 0 ; i < ARRAY_SIZE ( eeepc_wmi_debug_files ) ; i + + ) {
struct eeepc_wmi_debugfs_node * node = & eeepc_wmi_debug_files [ i ] ;
node - > eeepc = eeepc ;
dent = debugfs_create_file ( node - > name , S_IFREG | S_IRUGO ,
eeepc - > debug . root , node ,
& eeepc_wmi_debugfs_io_ops ) ;
if ( ! dent ) {
pr_err ( " failed to create debug file: %s \n " , node - > name ) ;
goto error_debugfs ;
}
}
return 0 ;
error_debugfs :
eeepc_wmi_debugfs_exit ( eeepc ) ;
return - ENOMEM ;
}
2010-11-29 10:14:05 +03:00
/*
* WMI Driver
*/
2011-02-06 15:28:28 +03:00
static void eeepc_dmi_check ( struct eeepc_wmi * eeepc )
{
const char * model ;
model = dmi_get_system_info ( DMI_PRODUCT_NAME ) ;
if ( ! model )
return ;
/*
* Whitelist for wlan hotplug
*
* Eeepc 1000 H needs the current hotplug code to handle
* Fn + F2 correctly . We may add other Eeepc here later , but
* it seems that most of the laptops supported by eeepc - wmi
* don ' t need to be on this list
*/
if ( strcmp ( model , " 1000H " ) = = 0 ) {
eeepc - > hotplug_wireless = true ;
pr_info ( " wlan hotplug enabled \n " ) ;
}
}
2010-11-29 10:14:05 +03:00
static struct platform_device * __init eeepc_wmi_add ( void )
{
struct eeepc_wmi * eeepc ;
2010-03-21 05:26:34 +03:00
acpi_status status ;
2010-11-29 10:14:05 +03:00
int err ;
2010-03-21 05:26:34 +03:00
2010-11-29 10:14:05 +03:00
eeepc = kzalloc ( sizeof ( struct eeepc_wmi ) , GFP_KERNEL ) ;
if ( ! eeepc )
return ERR_PTR ( - ENOMEM ) ;
2011-02-06 15:28:28 +03:00
eeepc - > hotplug_wireless = hotplug_wireless ;
eeepc_dmi_check ( eeepc ) ;
2010-11-29 10:14:05 +03:00
/*
* Register the platform device first . It is used as a parent for the
* sub - devices below .
*/
err = eeepc_wmi_platform_init ( eeepc ) ;
if ( err )
goto fail_platform ;
2010-04-11 05:27:19 +04:00
err = eeepc_wmi_input_init ( eeepc ) ;
if ( err )
2010-11-29 10:14:05 +03:00
goto fail_input ;
2010-04-11 05:27:54 +04:00
2010-11-29 10:14:06 +03:00
err = eeepc_wmi_led_init ( eeepc ) ;
if ( err )
goto fail_leds ;
2010-11-29 10:14:07 +03:00
err = eeepc_wmi_rfkill_init ( eeepc ) ;
if ( err )
goto fail_rfkill ;
2010-04-11 05:27:54 +04:00
if ( ! acpi_video_backlight_support ( ) ) {
err = eeepc_wmi_backlight_init ( eeepc ) ;
if ( err )
2010-11-29 10:14:05 +03:00
goto fail_backlight ;
2010-04-11 05:27:54 +04:00
} else
pr_info ( " Backlight controlled by ACPI video driver \n " ) ;
2010-04-11 05:27:19 +04:00
status = wmi_install_notify_handler ( EEEPC_WMI_EVENT_GUID ,
2010-11-29 10:14:05 +03:00
eeepc_wmi_notify , eeepc ) ;
2010-04-11 05:27:19 +04:00
if ( ACPI_FAILURE ( status ) ) {
pr_err ( " Unable to register notify handler - %d \n " ,
status ) ;
err = - ENODEV ;
2010-11-29 10:14:05 +03:00
goto fail_wmi_handler ;
2010-04-11 05:27:19 +04:00
}
2010-11-29 10:14:09 +03:00
err = eeepc_wmi_debugfs_init ( eeepc ) ;
if ( err )
goto fail_debugfs ;
2010-11-29 10:14:05 +03:00
return eeepc - > platform_device ;
2010-04-11 05:27:19 +04:00
2010-11-29 10:14:09 +03:00
fail_debugfs :
wmi_remove_notify_handler ( EEEPC_WMI_EVENT_GUID ) ;
2010-11-29 10:14:05 +03:00
fail_wmi_handler :
2010-04-11 05:27:54 +04:00
eeepc_wmi_backlight_exit ( eeepc ) ;
2010-11-29 10:14:05 +03:00
fail_backlight :
2010-11-29 10:14:07 +03:00
eeepc_wmi_rfkill_exit ( eeepc ) ;
fail_rfkill :
2010-11-29 10:14:06 +03:00
eeepc_wmi_led_exit ( eeepc ) ;
fail_leds :
2010-04-11 05:27:19 +04:00
eeepc_wmi_input_exit ( eeepc ) ;
2010-11-29 10:14:05 +03:00
fail_input :
eeepc_wmi_platform_exit ( eeepc ) ;
fail_platform :
kfree ( eeepc ) ;
return ERR_PTR ( err ) ;
2010-04-11 05:27:19 +04:00
}
2010-11-29 10:14:05 +03:00
static int eeepc_wmi_remove ( struct platform_device * device )
2010-04-11 05:27:19 +04:00
{
struct eeepc_wmi * eeepc ;
eeepc = platform_get_drvdata ( device ) ;
wmi_remove_notify_handler ( EEEPC_WMI_EVENT_GUID ) ;
2010-04-11 05:27:54 +04:00
eeepc_wmi_backlight_exit ( eeepc ) ;
2010-04-11 05:27:19 +04:00
eeepc_wmi_input_exit ( eeepc ) ;
2010-11-29 10:14:06 +03:00
eeepc_wmi_led_exit ( eeepc ) ;
2010-11-29 10:14:07 +03:00
eeepc_wmi_rfkill_exit ( eeepc ) ;
2010-11-29 10:14:09 +03:00
eeepc_wmi_debugfs_exit ( eeepc ) ;
2010-11-29 10:14:05 +03:00
eeepc_wmi_platform_exit ( eeepc ) ;
2010-04-11 05:27:19 +04:00
2010-11-29 10:14:05 +03:00
kfree ( eeepc ) ;
2010-04-11 05:27:19 +04:00
return 0 ;
}
static struct platform_driver platform_driver = {
. driver = {
. name = EEEPC_WMI_FILE ,
. owner = THIS_MODULE ,
} ,
} ;
2010-11-29 10:14:14 +03:00
static acpi_status __init eeepc_wmi_parse_device ( acpi_handle handle , u32 level ,
void * context , void * * retval )
{
pr_warning ( " Found legacy ATKD device (%s) " , EEEPC_ACPI_HID ) ;
* ( bool * ) context = true ;
return AE_CTRL_TERMINATE ;
}
static int __init eeepc_wmi_check_atkd ( void )
{
acpi_status status ;
bool found = false ;
status = acpi_get_devices ( EEEPC_ACPI_HID , eeepc_wmi_parse_device ,
& found , NULL ) ;
if ( ACPI_FAILURE ( status ) | | ! found )
return 0 ;
return - 1 ;
}
2010-04-11 05:27:19 +04:00
static int __init eeepc_wmi_init ( void )
{
int err ;
2010-04-11 05:27:54 +04:00
if ( ! wmi_has_guid ( EEEPC_WMI_EVENT_GUID ) | |
! wmi_has_guid ( EEEPC_WMI_MGMT_GUID ) ) {
2010-04-11 05:26:33 +04:00
pr_warning ( " No known WMI GUID found \n " ) ;
2010-03-21 05:26:34 +03:00
return - ENODEV ;
}
2010-11-29 10:14:14 +03:00
if ( eeepc_wmi_check_atkd ( ) ) {
pr_warning ( " WMI device present, but legacy ATKD device is also "
" present and enabled. " ) ;
pr_warning ( " You probably booted with acpi_osi= \" Linux \" or "
" acpi_osi= \" !Windows 2009 \" " ) ;
pr_warning ( " Can't load eeepc-wmi, use default acpi_osi "
" (preferred) or eeepc-laptop " ) ;
return - ENODEV ;
}
2010-11-29 10:14:05 +03:00
platform_device = eeepc_wmi_add ( ) ;
if ( IS_ERR ( platform_device ) ) {
err = PTR_ERR ( platform_device ) ;
goto fail_eeepc_wmi ;
2010-04-11 05:27:19 +04:00
}
err = platform_driver_register ( & platform_driver ) ;
if ( err ) {
pr_warning ( " Unable to register platform driver \n " ) ;
2010-11-29 10:14:05 +03:00
goto fail_platform_driver ;
2010-03-21 05:26:34 +03:00
}
return 0 ;
2010-04-11 05:27:19 +04:00
2010-11-29 10:14:05 +03:00
fail_platform_driver :
eeepc_wmi_remove ( platform_device ) ;
fail_eeepc_wmi :
2010-04-11 05:27:19 +04:00
return err ;
2010-03-21 05:26:34 +03:00
}
static void __exit eeepc_wmi_exit ( void )
{
2010-11-29 10:14:05 +03:00
eeepc_wmi_remove ( platform_device ) ;
2010-04-11 05:27:19 +04:00
platform_driver_unregister ( & platform_driver ) ;
2010-03-21 05:26:34 +03:00
}
module_init ( eeepc_wmi_init ) ;
module_exit ( eeepc_wmi_exit ) ;