2009-12-10 14:18:13 +01:00
/*
* MSI WMI hotkeys
*
* Copyright ( C ) 2009 Novell < trenn @ suse . de >
*
* Most stuff taken over from hp - wmi
*
* 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/input.h>
2009-12-10 14:18:19 +01:00
# include <linux/input/sparse-keymap.h>
2009-12-10 14:18:13 +01:00
# include <linux/acpi.h>
# include <linux/backlight.h>
MODULE_AUTHOR ( " Thomas Renninger <trenn@suse.de> " ) ;
MODULE_DESCRIPTION ( " MSI laptop WMI hotkeys driver " ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_ALIAS ( " wmi:551A1F84-FBDD-4125-91DB-3EA8F44F1D45 " ) ;
MODULE_ALIAS ( " wmi:B6F3EEF2-3D2F-49DC-9DE3-85BCE18C62F2 " ) ;
# define DRV_NAME "msi-wmi"
# define DRV_PFX DRV_NAME ": "
# define MSIWMI_BIOS_GUID "551A1F84-FBDD-4125-91DB-3EA8F44F1D45"
# define MSIWMI_EVENT_GUID "B6F3EEF2-3D2F-49DC-9DE3-85BCE18C62F2"
2009-12-10 14:18:16 +01:00
# define dprintk(msg...) pr_debug(DRV_PFX msg)
2009-12-10 14:18:13 +01:00
2009-12-10 14:18:19 +01:00
# define KEYCODE_BASE 0xD0
2009-12-14 10:21:39 +01:00
# define MSI_WMI_BRIGHTNESSUP KEYCODE_BASE
# define MSI_WMI_BRIGHTNESSDOWN (KEYCODE_BASE + 1)
# define MSI_WMI_VOLUMEUP (KEYCODE_BASE + 2)
# define MSI_WMI_VOLUMEDOWN (KEYCODE_BASE + 3)
2009-12-10 14:18:13 +01:00
static struct key_entry msi_wmi_keymap [ ] = {
2009-12-14 10:21:39 +01:00
{ KE_KEY , MSI_WMI_BRIGHTNESSUP , { KEY_BRIGHTNESSUP } } ,
{ KE_KEY , MSI_WMI_BRIGHTNESSDOWN , { KEY_BRIGHTNESSDOWN } } ,
{ KE_KEY , MSI_WMI_VOLUMEUP , { KEY_VOLUMEUP } } ,
{ KE_KEY , MSI_WMI_VOLUMEDOWN , { KEY_VOLUMEDOWN } } ,
2009-12-10 14:18:13 +01:00
{ KE_END , 0 }
} ;
2009-12-10 14:18:19 +01:00
static ktime_t last_pressed [ ARRAY_SIZE ( msi_wmi_keymap ) - 1 ] ;
2009-12-10 14:18:13 +01:00
struct backlight_device * backlight ;
static int backlight_map [ ] = { 0x00 , 0x33 , 0x66 , 0x99 , 0xCC , 0xFF } ;
static struct input_dev * msi_wmi_input_dev ;
static int msi_wmi_query_block ( int instance , int * ret )
{
acpi_status status ;
union acpi_object * obj ;
struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER , NULL } ;
status = wmi_query_block ( MSIWMI_BIOS_GUID , instance , & output ) ;
obj = output . pointer ;
if ( ! obj | | obj - > type ! = ACPI_TYPE_INTEGER ) {
if ( obj ) {
printk ( KERN_ERR DRV_PFX " query block returned object "
" type: %d - buffer length:%d \n " , obj - > type ,
obj - > type = = ACPI_TYPE_BUFFER ?
obj - > buffer . length : 0 ) ;
}
kfree ( obj ) ;
return - EINVAL ;
}
* ret = obj - > integer . value ;
kfree ( obj ) ;
return 0 ;
}
static int msi_wmi_set_block ( int instance , int value )
{
acpi_status status ;
struct acpi_buffer input = { sizeof ( int ) , & value } ;
dprintk ( " Going to set block of instance: %d - value: %d \n " ,
instance , value ) ;
status = wmi_set_block ( MSIWMI_BIOS_GUID , instance , & input ) ;
return ACPI_SUCCESS ( status ) ? 0 : 1 ;
}
static int bl_get ( struct backlight_device * bd )
{
2009-12-14 10:21:39 +01:00
int level , err , ret ;
2009-12-10 14:18:13 +01:00
/* Instance 1 is "get backlight", cmp with DSDT */
err = msi_wmi_query_block ( 1 , & ret ) ;
2009-12-14 10:21:39 +01:00
if ( err ) {
2009-12-10 14:18:13 +01:00
printk ( KERN_ERR DRV_PFX " Could not query backlight: %d \n " , err ) ;
2009-12-14 10:21:39 +01:00
return - EINVAL ;
}
2009-12-10 14:18:13 +01:00
dprintk ( " Get: Query block returned: %d \n " , ret ) ;
for ( level = 0 ; level < ARRAY_SIZE ( backlight_map ) ; level + + ) {
if ( backlight_map [ level ] = = ret ) {
dprintk ( " Current backlight level: 0x%X - index: %d \n " ,
backlight_map [ level ] , level ) ;
break ;
}
}
if ( level = = ARRAY_SIZE ( backlight_map ) ) {
printk ( KERN_ERR DRV_PFX " get: Invalid brightness value: 0x%X \n " ,
ret ) ;
return - EINVAL ;
}
return level ;
}
static int bl_set_status ( struct backlight_device * bd )
{
int bright = bd - > props . brightness ;
if ( bright > = ARRAY_SIZE ( backlight_map ) | | bright < 0 )
return - EINVAL ;
/* Instance 0 is "set backlight" */
return msi_wmi_set_block ( 0 , backlight_map [ bright ] ) ;
}
static struct backlight_ops msi_backlight_ops = {
. get_brightness = bl_get ,
. update_status = bl_set_status ,
} ;
static void msi_wmi_notify ( u32 value , void * context )
{
struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER , NULL } ;
static struct key_entry * key ;
union acpi_object * obj ;
ktime_t cur ;
2009-12-26 23:02:24 -05:00
acpi_status status ;
2009-12-10 14:18:13 +01:00
2009-12-26 23:02:24 -05:00
status = wmi_get_event_data ( value , & response ) ;
if ( status ! = AE_OK ) {
printk ( KERN_INFO DRV_PFX " bad event status 0x%x \n " , status ) ;
return ;
}
2009-12-10 14:18:13 +01:00
obj = ( union acpi_object * ) response . pointer ;
if ( obj & & obj - > type = = ACPI_TYPE_INTEGER ) {
int eventcode = obj - > integer . value ;
dprintk ( " Eventcode: 0x%x \n " , eventcode ) ;
2009-12-10 14:18:19 +01:00
key = sparse_keymap_entry_from_scancode ( msi_wmi_input_dev ,
eventcode ) ;
2009-12-10 14:18:13 +01:00
if ( key ) {
2009-12-10 14:18:19 +01:00
ktime_t diff ;
2009-12-10 14:18:13 +01:00
cur = ktime_get_real ( ) ;
2009-12-10 14:18:19 +01:00
diff = ktime_sub ( cur , last_pressed [ key - > code -
KEYCODE_BASE ] ) ;
2009-12-10 14:18:13 +01:00
/* Ignore event if the same event happened in a 50 ms
timeframe - > Key press may result in 10 - 20 GPEs */
2009-12-10 14:18:19 +01:00
if ( ktime_to_us ( diff ) < 1000 * 50 ) {
2009-12-10 14:18:13 +01:00
dprintk ( " Suppressed key event 0x%X - "
" Last press was %lld us ago \n " ,
2009-12-10 14:18:19 +01:00
key - > code , ktime_to_us ( diff ) ) ;
2009-12-10 14:18:13 +01:00
return ;
}
2009-12-10 14:18:19 +01:00
last_pressed [ key - > code - KEYCODE_BASE ] = cur ;
2009-12-10 14:18:13 +01:00
2009-12-10 14:18:18 +01:00
if ( key - > type = = KE_KEY & &
/* Brightness is served via acpi video driver */
2009-12-14 10:21:39 +01:00
( ! acpi_video_backlight_support ( ) | |
( key - > code ! = MSI_WMI_BRIGHTNESSUP & &
key - > code ! = MSI_WMI_BRIGHTNESSDOWN ) ) ) {
2009-12-10 14:18:13 +01:00
dprintk ( " Send key: 0x%X - "
" Input layer keycode: %d \n " , key - > code ,
key - > keycode ) ;
2009-12-10 14:18:19 +01:00
sparse_keymap_report_entry ( msi_wmi_input_dev ,
key , 1 , true ) ;
2009-12-10 14:18:13 +01:00
}
} else
printk ( KERN_INFO " Unknown key pressed - %x \n " ,
eventcode ) ;
} else
printk ( KERN_INFO DRV_PFX " Unknown event received \n " ) ;
kfree ( response . pointer ) ;
}
static int __init msi_wmi_input_setup ( void )
{
int err ;
msi_wmi_input_dev = input_allocate_device ( ) ;
2009-12-10 14:18:15 +01:00
if ( ! msi_wmi_input_dev )
return - ENOMEM ;
2009-12-10 14:18:13 +01:00
msi_wmi_input_dev - > name = " MSI WMI hotkeys " ;
msi_wmi_input_dev - > phys = " wmi/input0 " ;
msi_wmi_input_dev - > id . bustype = BUS_HOST ;
2009-12-10 14:18:19 +01:00
err = sparse_keymap_setup ( msi_wmi_input_dev , msi_wmi_keymap , NULL ) ;
if ( err )
goto err_free_dev ;
2009-12-10 14:18:13 +01:00
err = input_register_device ( msi_wmi_input_dev ) ;
2009-12-10 14:18:19 +01:00
if ( err )
goto err_free_keymap ;
memset ( last_pressed , 0 , sizeof ( last_pressed ) ) ;
2009-12-10 14:18:13 +01:00
return 0 ;
2009-12-10 14:18:19 +01:00
err_free_keymap :
sparse_keymap_free ( msi_wmi_input_dev ) ;
err_free_dev :
input_free_device ( msi_wmi_input_dev ) ;
return err ;
2009-12-10 14:18:13 +01:00
}
static int __init msi_wmi_init ( void )
{
int err ;
2009-12-10 14:18:15 +01:00
if ( ! wmi_has_guid ( MSIWMI_EVENT_GUID ) ) {
printk ( KERN_ERR
" This machine doesn't have MSI-hotkeys through WMI \n " ) ;
return - ENODEV ;
}
err = wmi_install_notify_handler ( MSIWMI_EVENT_GUID ,
msi_wmi_notify , NULL ) ;
2009-12-26 22:04:03 -05:00
if ( ACPI_FAILURE ( err ) )
2009-12-10 14:18:15 +01:00
return - EINVAL ;
2009-12-10 14:18:13 +01:00
2009-12-10 14:18:15 +01:00
err = msi_wmi_input_setup ( ) ;
if ( err )
goto err_uninstall_notifier ;
2009-12-10 14:18:13 +01:00
2009-12-10 14:18:15 +01:00
if ( ! acpi_video_backlight_support ( ) ) {
backlight = backlight_device_register ( DRV_NAME ,
NULL , NULL , & msi_backlight_ops ) ;
if ( IS_ERR ( backlight ) )
goto err_free_input ;
backlight - > props . max_brightness = ARRAY_SIZE ( backlight_map ) - 1 ;
err = bl_get ( NULL ) ;
if ( err < 0 )
goto err_free_backlight ;
backlight - > props . brightness = err ;
2009-12-10 14:18:13 +01:00
}
2009-12-10 14:18:16 +01:00
dprintk ( " Event handler installed \n " ) ;
2009-12-10 14:18:15 +01:00
2009-12-10 14:18:13 +01:00
return 0 ;
2009-12-10 14:18:15 +01:00
err_free_backlight :
backlight_device_unregister ( backlight ) ;
err_free_input :
input_unregister_device ( msi_wmi_input_dev ) ;
err_uninstall_notifier :
wmi_remove_notify_handler ( MSIWMI_EVENT_GUID ) ;
return err ;
2009-12-10 14:18:13 +01:00
}
static void __exit msi_wmi_exit ( void )
{
if ( wmi_has_guid ( MSIWMI_EVENT_GUID ) ) {
wmi_remove_notify_handler ( MSIWMI_EVENT_GUID ) ;
2009-12-10 14:18:19 +01:00
sparse_keymap_free ( msi_wmi_input_dev ) ;
2009-12-10 14:18:13 +01:00
input_unregister_device ( msi_wmi_input_dev ) ;
backlight_device_unregister ( backlight ) ;
}
}
module_init ( msi_wmi_init ) ;
module_exit ( msi_wmi_exit ) ;