2008-07-25 01:45:39 -07:00
/*
* HP WMI hotkeys
*
* Copyright ( C ) 2008 Red Hat < mjg @ redhat . com >
*
* 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
*/
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/init.h>
# include <linux/types.h>
# include <linux/input.h>
# include <acpi/acpi_drivers.h>
# include <linux/platform_device.h>
# include <linux/acpi.h>
# include <linux/rfkill.h>
# include <linux/string.h>
MODULE_AUTHOR ( " Matthew Garrett <mjg59@srcf.ucam.org> " ) ;
MODULE_DESCRIPTION ( " HP laptop WMI hotkeys driver " ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_ALIAS ( " wmi:95F24279-4D7B-4334-9387-ACCDC67EF61C " ) ;
MODULE_ALIAS ( " wmi:5FB7F034-2C63-45e9-BE91-3D44E2C707E4 " ) ;
# define HPWMI_EVENT_GUID "95F24279-4D7B-4334-9387-ACCDC67EF61C"
# define HPWMI_BIOS_GUID "5FB7F034-2C63-45e9-BE91-3D44E2C707E4"
# define HPWMI_DISPLAY_QUERY 0x1
# define HPWMI_HDDTEMP_QUERY 0x2
# define HPWMI_ALS_QUERY 0x3
# define HPWMI_DOCK_QUERY 0x4
# define HPWMI_WIRELESS_QUERY 0x5
2008-09-02 14:36:03 -07:00
# define HPWMI_HOTKEY_QUERY 0xc
2008-07-25 01:45:39 -07:00
static int __init hp_wmi_bios_setup ( struct platform_device * device ) ;
static int __exit hp_wmi_bios_remove ( struct platform_device * device ) ;
struct bios_args {
u32 signature ;
u32 command ;
u32 commandtype ;
u32 datasize ;
u32 data ;
} ;
struct bios_return {
u32 sigpass ;
u32 return_code ;
u32 value ;
} ;
struct key_entry {
char type ; /* See KE_* below */
2008-09-02 14:36:03 -07:00
u16 code ;
2008-07-25 01:45:39 -07:00
u16 keycode ;
} ;
enum { KE_KEY , KE_SW , KE_END } ;
static struct key_entry hp_wmi_keymap [ ] = {
{ KE_SW , 0x01 , SW_DOCK } ,
{ KE_KEY , 0x02 , KEY_BRIGHTNESSUP } ,
{ KE_KEY , 0x03 , KEY_BRIGHTNESSDOWN } ,
2008-09-02 14:36:03 -07:00
{ KE_KEY , 0x20e6 , KEY_PROG1 } ,
{ KE_KEY , 0x2142 , KEY_MEDIA } ,
2008-10-18 20:27:28 -07:00
{ KE_KEY , 0x213b , KEY_INFO } ,
2008-09-02 14:36:03 -07:00
{ KE_KEY , 0x231b , KEY_HELP } ,
2008-07-25 01:45:39 -07:00
{ KE_END , 0 }
} ;
static struct input_dev * hp_wmi_input_dev ;
static struct platform_device * hp_wmi_platform_dev ;
static struct rfkill * wifi_rfkill ;
static struct rfkill * bluetooth_rfkill ;
static struct rfkill * wwan_rfkill ;
static struct platform_driver hp_wmi_driver = {
. driver = {
. name = " hp-wmi " ,
. owner = THIS_MODULE ,
} ,
. probe = hp_wmi_bios_setup ,
. remove = hp_wmi_bios_remove ,
} ;
static int hp_wmi_perform_query ( int query , int write , int value )
{
struct bios_return bios_return ;
acpi_status status ;
union acpi_object * obj ;
struct bios_args args = {
. signature = 0x55434553 ,
. command = write ? 0x2 : 0x1 ,
. commandtype = query ,
. datasize = write ? 0x4 : 0 ,
. data = value ,
} ;
struct acpi_buffer input = { sizeof ( struct bios_args ) , & args } ;
struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER , NULL } ;
status = wmi_evaluate_method ( HPWMI_BIOS_GUID , 0 , 0x3 , & input , & output ) ;
obj = output . pointer ;
if ( ! obj | | obj - > type ! = ACPI_TYPE_BUFFER )
return - EINVAL ;
bios_return = * ( ( struct bios_return * ) obj - > buffer . pointer ) ;
if ( bios_return . return_code > 0 )
return bios_return . return_code * - 1 ;
else
return bios_return . value ;
}
static int hp_wmi_display_state ( void )
{
return hp_wmi_perform_query ( HPWMI_DISPLAY_QUERY , 0 , 0 ) ;
}
static int hp_wmi_hddtemp_state ( void )
{
return hp_wmi_perform_query ( HPWMI_HDDTEMP_QUERY , 0 , 0 ) ;
}
static int hp_wmi_als_state ( void )
{
return hp_wmi_perform_query ( HPWMI_ALS_QUERY , 0 , 0 ) ;
}
static int hp_wmi_dock_state ( void )
{
return hp_wmi_perform_query ( HPWMI_DOCK_QUERY , 0 , 0 ) ;
}
static int hp_wmi_wifi_set ( void * data , enum rfkill_state state )
{
if ( state )
return hp_wmi_perform_query ( HPWMI_WIRELESS_QUERY , 1 , 0x101 ) ;
else
return hp_wmi_perform_query ( HPWMI_WIRELESS_QUERY , 1 , 0x100 ) ;
}
static int hp_wmi_bluetooth_set ( void * data , enum rfkill_state state )
{
if ( state )
return hp_wmi_perform_query ( HPWMI_WIRELESS_QUERY , 1 , 0x202 ) ;
else
return hp_wmi_perform_query ( HPWMI_WIRELESS_QUERY , 1 , 0x200 ) ;
}
static int hp_wmi_wwan_set ( void * data , enum rfkill_state state )
{
if ( state )
return hp_wmi_perform_query ( HPWMI_WIRELESS_QUERY , 1 , 0x404 ) ;
else
return hp_wmi_perform_query ( HPWMI_WIRELESS_QUERY , 1 , 0x400 ) ;
}
static int hp_wmi_wifi_state ( void )
{
int wireless = hp_wmi_perform_query ( HPWMI_WIRELESS_QUERY , 0 , 0 ) ;
if ( wireless & 0x100 )
2008-09-02 14:36:00 -07:00
return RFKILL_STATE_UNBLOCKED ;
2008-07-25 01:45:39 -07:00
else
2008-09-02 14:36:00 -07:00
return RFKILL_STATE_SOFT_BLOCKED ;
2008-07-25 01:45:39 -07:00
}
static int hp_wmi_bluetooth_state ( void )
{
int wireless = hp_wmi_perform_query ( HPWMI_WIRELESS_QUERY , 0 , 0 ) ;
if ( wireless & 0x10000 )
2008-09-02 14:36:00 -07:00
return RFKILL_STATE_UNBLOCKED ;
2008-07-25 01:45:39 -07:00
else
2008-09-02 14:36:00 -07:00
return RFKILL_STATE_SOFT_BLOCKED ;
2008-07-25 01:45:39 -07:00
}
static int hp_wmi_wwan_state ( void )
{
int wireless = hp_wmi_perform_query ( HPWMI_WIRELESS_QUERY , 0 , 0 ) ;
if ( wireless & 0x1000000 )
2008-09-02 14:36:00 -07:00
return RFKILL_STATE_UNBLOCKED ;
2008-07-25 01:45:39 -07:00
else
2008-09-02 14:36:00 -07:00
return RFKILL_STATE_SOFT_BLOCKED ;
2008-07-25 01:45:39 -07:00
}
static ssize_t show_display ( struct device * dev , struct device_attribute * attr ,
char * buf )
{
int value = hp_wmi_display_state ( ) ;
if ( value < 0 )
return - EINVAL ;
return sprintf ( buf , " %d \n " , value ) ;
}
static ssize_t show_hddtemp ( struct device * dev , struct device_attribute * attr ,
char * buf )
{
int value = hp_wmi_hddtemp_state ( ) ;
if ( value < 0 )
return - EINVAL ;
return sprintf ( buf , " %d \n " , value ) ;
}
static ssize_t show_als ( struct device * dev , struct device_attribute * attr ,
char * buf )
{
int value = hp_wmi_als_state ( ) ;
if ( value < 0 )
return - EINVAL ;
return sprintf ( buf , " %d \n " , value ) ;
}
static ssize_t show_dock ( struct device * dev , struct device_attribute * attr ,
char * buf )
{
int value = hp_wmi_dock_state ( ) ;
if ( value < 0 )
return - EINVAL ;
return sprintf ( buf , " %d \n " , value ) ;
}
static ssize_t set_als ( struct device * dev , struct device_attribute * attr ,
const char * buf , size_t count )
{
u32 tmp = simple_strtoul ( buf , NULL , 10 ) ;
hp_wmi_perform_query ( HPWMI_ALS_QUERY , 1 , tmp ) ;
return count ;
}
static DEVICE_ATTR ( display , S_IRUGO , show_display , NULL ) ;
static DEVICE_ATTR ( hddtemp , S_IRUGO , show_hddtemp , NULL ) ;
static DEVICE_ATTR ( als , S_IRUGO | S_IWUSR , show_als , set_als ) ;
static DEVICE_ATTR ( dock , S_IRUGO , show_dock , NULL ) ;
static struct key_entry * hp_wmi_get_entry_by_scancode ( int code )
{
struct key_entry * key ;
for ( key = hp_wmi_keymap ; key - > type ! = KE_END ; key + + )
if ( code = = key - > code )
return key ;
return NULL ;
}
static struct key_entry * hp_wmi_get_entry_by_keycode ( int keycode )
{
struct key_entry * key ;
for ( key = hp_wmi_keymap ; key - > type ! = KE_END ; key + + )
if ( key - > type = = KE_KEY & & keycode = = key - > keycode )
return key ;
return NULL ;
}
static int hp_wmi_getkeycode ( struct input_dev * dev , int scancode , int * keycode )
{
struct key_entry * key = hp_wmi_get_entry_by_scancode ( scancode ) ;
if ( key & & key - > type = = KE_KEY ) {
* keycode = key - > keycode ;
return 0 ;
}
return - EINVAL ;
}
static int hp_wmi_setkeycode ( struct input_dev * dev , int scancode , int keycode )
{
struct key_entry * key ;
int old_keycode ;
if ( keycode < 0 | | keycode > KEY_MAX )
return - EINVAL ;
key = hp_wmi_get_entry_by_scancode ( scancode ) ;
if ( key & & key - > type = = KE_KEY ) {
old_keycode = key - > keycode ;
key - > keycode = keycode ;
set_bit ( keycode , dev - > keybit ) ;
if ( ! hp_wmi_get_entry_by_keycode ( old_keycode ) )
clear_bit ( old_keycode , dev - > keybit ) ;
return 0 ;
}
return - EINVAL ;
}
2008-10-15 22:05:17 -07:00
static void hp_wmi_notify ( u32 value , void * context )
2008-07-25 01:45:39 -07:00
{
struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER , NULL } ;
static struct key_entry * key ;
union acpi_object * obj ;
wmi_get_event_data ( value , & response ) ;
obj = ( union acpi_object * ) response . pointer ;
if ( obj & & obj - > type = = ACPI_TYPE_BUFFER & & obj - > buffer . length = = 8 ) {
int eventcode = * ( ( u8 * ) obj - > buffer . pointer ) ;
2008-09-02 14:36:03 -07:00
if ( eventcode = = 0x4 )
eventcode = hp_wmi_perform_query ( HPWMI_HOTKEY_QUERY , 0 ,
0 ) ;
2008-07-25 01:45:39 -07:00
key = hp_wmi_get_entry_by_scancode ( eventcode ) ;
if ( key ) {
switch ( key - > type ) {
case KE_KEY :
input_report_key ( hp_wmi_input_dev ,
key - > keycode , 1 ) ;
input_sync ( hp_wmi_input_dev ) ;
input_report_key ( hp_wmi_input_dev ,
key - > keycode , 0 ) ;
input_sync ( hp_wmi_input_dev ) ;
break ;
case KE_SW :
input_report_switch ( hp_wmi_input_dev ,
key - > keycode ,
hp_wmi_dock_state ( ) ) ;
input_sync ( hp_wmi_input_dev ) ;
break ;
}
} else if ( eventcode = = 0x5 ) {
if ( wifi_rfkill )
2008-09-02 14:36:00 -07:00
rfkill_force_state ( wifi_rfkill ,
hp_wmi_wifi_state ( ) ) ;
2008-07-25 01:45:39 -07:00
if ( bluetooth_rfkill )
2008-09-02 14:36:00 -07:00
rfkill_force_state ( bluetooth_rfkill ,
hp_wmi_bluetooth_state ( ) ) ;
2008-07-25 01:45:39 -07:00
if ( wwan_rfkill )
2008-09-02 14:36:00 -07:00
rfkill_force_state ( wwan_rfkill ,
hp_wmi_wwan_state ( ) ) ;
2008-07-25 01:45:39 -07:00
} else
printk ( KERN_INFO " HP WMI: Unknown key pressed - %x \n " ,
eventcode ) ;
} else
printk ( KERN_INFO " HP WMI: Unknown response received \n " ) ;
}
static int __init hp_wmi_input_setup ( void )
{
struct key_entry * key ;
int err ;
hp_wmi_input_dev = input_allocate_device ( ) ;
hp_wmi_input_dev - > name = " HP WMI hotkeys " ;
hp_wmi_input_dev - > phys = " wmi/input0 " ;
hp_wmi_input_dev - > id . bustype = BUS_HOST ;
hp_wmi_input_dev - > getkeycode = hp_wmi_getkeycode ;
hp_wmi_input_dev - > setkeycode = hp_wmi_setkeycode ;
for ( key = hp_wmi_keymap ; key - > type ! = KE_END ; key + + ) {
switch ( key - > type ) {
case KE_KEY :
set_bit ( EV_KEY , hp_wmi_input_dev - > evbit ) ;
set_bit ( key - > keycode , hp_wmi_input_dev - > keybit ) ;
break ;
case KE_SW :
set_bit ( EV_SW , hp_wmi_input_dev - > evbit ) ;
set_bit ( key - > keycode , hp_wmi_input_dev - > swbit ) ;
break ;
}
}
err = input_register_device ( hp_wmi_input_dev ) ;
if ( err ) {
input_free_device ( hp_wmi_input_dev ) ;
return err ;
}
return 0 ;
}
static void cleanup_sysfs ( struct platform_device * device )
{
device_remove_file ( & device - > dev , & dev_attr_display ) ;
device_remove_file ( & device - > dev , & dev_attr_hddtemp ) ;
device_remove_file ( & device - > dev , & dev_attr_als ) ;
device_remove_file ( & device - > dev , & dev_attr_dock ) ;
}
static int __init hp_wmi_bios_setup ( struct platform_device * device )
{
int err ;
2008-09-02 14:36:00 -07:00
int wireless = hp_wmi_perform_query ( HPWMI_WIRELESS_QUERY , 0 , 0 ) ;
2008-07-25 01:45:39 -07:00
err = device_create_file ( & device - > dev , & dev_attr_display ) ;
if ( err )
goto add_sysfs_error ;
err = device_create_file ( & device - > dev , & dev_attr_hddtemp ) ;
if ( err )
goto add_sysfs_error ;
err = device_create_file ( & device - > dev , & dev_attr_als ) ;
if ( err )
goto add_sysfs_error ;
err = device_create_file ( & device - > dev , & dev_attr_dock ) ;
if ( err )
goto add_sysfs_error ;
2008-09-02 14:36:00 -07:00
if ( wireless & 0x1 ) {
wifi_rfkill = rfkill_allocate ( & device - > dev , RFKILL_TYPE_WLAN ) ;
wifi_rfkill - > name = " hp-wifi " ;
wifi_rfkill - > state = hp_wmi_wifi_state ( ) ;
wifi_rfkill - > toggle_radio = hp_wmi_wifi_set ;
wifi_rfkill - > user_claim_unsupported = 1 ;
rfkill_register ( wifi_rfkill ) ;
}
if ( wireless & 0x2 ) {
bluetooth_rfkill = rfkill_allocate ( & device - > dev ,
RFKILL_TYPE_BLUETOOTH ) ;
bluetooth_rfkill - > name = " hp-bluetooth " ;
bluetooth_rfkill - > state = hp_wmi_bluetooth_state ( ) ;
bluetooth_rfkill - > toggle_radio = hp_wmi_bluetooth_set ;
bluetooth_rfkill - > user_claim_unsupported = 1 ;
rfkill_register ( bluetooth_rfkill ) ;
}
if ( wireless & 0x4 ) {
wwan_rfkill = rfkill_allocate ( & device - > dev , RFKILL_TYPE_WWAN ) ;
wwan_rfkill - > name = " hp-wwan " ;
wwan_rfkill - > state = hp_wmi_wwan_state ( ) ;
wwan_rfkill - > toggle_radio = hp_wmi_wwan_set ;
wwan_rfkill - > user_claim_unsupported = 1 ;
rfkill_register ( wwan_rfkill ) ;
}
2008-07-25 01:45:39 -07:00
return 0 ;
add_sysfs_error :
cleanup_sysfs ( device ) ;
return err ;
}
static int __exit hp_wmi_bios_remove ( struct platform_device * device )
{
cleanup_sysfs ( device ) ;
2008-09-02 14:36:00 -07:00
if ( wifi_rfkill )
rfkill_unregister ( wifi_rfkill ) ;
if ( bluetooth_rfkill )
rfkill_unregister ( bluetooth_rfkill ) ;
if ( wwan_rfkill )
rfkill_unregister ( wwan_rfkill ) ;
2008-07-25 01:45:39 -07:00
return 0 ;
}
static int __init hp_wmi_init ( void )
{
int err ;
if ( wmi_has_guid ( HPWMI_EVENT_GUID ) ) {
err = wmi_install_notify_handler ( HPWMI_EVENT_GUID ,
hp_wmi_notify , NULL ) ;
if ( ! err )
hp_wmi_input_setup ( ) ;
}
if ( wmi_has_guid ( HPWMI_BIOS_GUID ) ) {
err = platform_driver_register ( & hp_wmi_driver ) ;
if ( err )
return 0 ;
hp_wmi_platform_dev = platform_device_alloc ( " hp-wmi " , - 1 ) ;
if ( ! hp_wmi_platform_dev ) {
platform_driver_unregister ( & hp_wmi_driver ) ;
return 0 ;
}
platform_device_add ( hp_wmi_platform_dev ) ;
}
return 0 ;
}
static void __exit hp_wmi_exit ( void )
{
if ( wmi_has_guid ( HPWMI_EVENT_GUID ) ) {
wmi_remove_notify_handler ( HPWMI_EVENT_GUID ) ;
input_unregister_device ( hp_wmi_input_dev ) ;
}
if ( hp_wmi_platform_dev ) {
platform_device_del ( hp_wmi_platform_dev ) ;
platform_driver_unregister ( & hp_wmi_driver ) ;
}
}
module_init ( hp_wmi_init ) ;
module_exit ( hp_wmi_exit ) ;