2011-02-17 21:44:09 +03:00
/*
* WMI hotkeys support for Dell All - In - One series
*
* 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
*/
2011-03-30 02:21:37 +04:00
2011-02-17 21:44:09 +03:00
# define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/init.h>
# include <linux/types.h>
# include <linux/input.h>
# include <linux/input/sparse-keymap.h>
# include <linux/acpi.h>
# include <linux/string.h>
MODULE_DESCRIPTION ( " WMI hotkeys driver for Dell All-In-One series " ) ;
MODULE_LICENSE ( " GPL " ) ;
# define EVENT_GUID1 "284A0E6B-380E-472A-921F-E52786257FB4"
# define EVENT_GUID2 "02314822-307C-4F66-BF0E-48AEAEB26CC8"
2013-03-08 11:44:30 +04:00
struct dell_wmi_event {
u16 length ;
/* 0x000: A hot key pressed or an event occurred
* 0x00F : A sequence of hot keys are pressed */
u16 type ;
u16 event [ ] ;
} ;
2011-02-17 21:44:09 +03:00
static const char * dell_wmi_aio_guids [ ] = {
EVENT_GUID1 ,
EVENT_GUID2 ,
NULL
} ;
MODULE_ALIAS ( " wmi: " EVENT_GUID1 ) ;
MODULE_ALIAS ( " wmi: " EVENT_GUID2 ) ;
static const struct key_entry dell_wmi_aio_keymap [ ] = {
{ KE_KEY , 0xc0 , { KEY_VOLUMEUP } } ,
{ KE_KEY , 0xc1 , { KEY_VOLUMEDOWN } } ,
2013-03-08 11:44:30 +04:00
{ KE_KEY , 0xe030 , { KEY_VOLUMEUP } } ,
{ KE_KEY , 0xe02e , { KEY_VOLUMEDOWN } } ,
{ KE_KEY , 0xe020 , { KEY_MUTE } } ,
{ KE_KEY , 0xe027 , { KEY_DISPLAYTOGGLE } } ,
{ KE_KEY , 0xe006 , { KEY_BRIGHTNESSUP } } ,
{ KE_KEY , 0xe005 , { KEY_BRIGHTNESSDOWN } } ,
{ KE_KEY , 0xe00b , { KEY_SWITCHVIDEOMODE } } ,
2011-02-17 21:44:09 +03:00
{ KE_END , 0 }
} ;
static struct input_dev * dell_wmi_aio_input_dev ;
2013-03-08 11:44:30 +04:00
/*
* The new WMI event data format will follow the dell_wmi_event structure
* So , we will check if the buffer matches the format
*/
static bool dell_wmi_aio_event_check ( u8 * buffer , int length )
{
struct dell_wmi_event * event = ( struct dell_wmi_event * ) buffer ;
if ( event = = NULL | | length < 6 )
return false ;
if ( ( event - > type = = 0 | | event - > type = = 0xf ) & &
event - > length > = 2 )
return true ;
return false ;
}
2011-02-17 21:44:09 +03:00
static void dell_wmi_aio_notify ( u32 value , void * context )
{
struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER , NULL } ;
union acpi_object * obj ;
2013-03-08 11:44:30 +04:00
struct dell_wmi_event * event ;
2011-02-17 21:44:09 +03:00
acpi_status status ;
status = wmi_get_event_data ( value , & response ) ;
if ( status ! = AE_OK ) {
pr_info ( " bad event status 0x%x \n " , status ) ;
return ;
}
obj = ( union acpi_object * ) response . pointer ;
if ( obj ) {
2013-03-08 11:44:30 +04:00
unsigned int scancode = 0 ;
2011-02-17 21:44:09 +03:00
switch ( obj - > type ) {
case ACPI_TYPE_INTEGER :
/* Most All-In-One correctly return integer scancode */
scancode = obj - > integer . value ;
sparse_keymap_report_event ( dell_wmi_aio_input_dev ,
scancode , 1 , true ) ;
break ;
case ACPI_TYPE_BUFFER :
2013-03-08 11:44:30 +04:00
if ( dell_wmi_aio_event_check ( obj - > buffer . pointer ,
obj - > buffer . length ) ) {
event = ( struct dell_wmi_event * )
obj - > buffer . pointer ;
scancode = event - > event [ 0 ] ;
} else {
/* Broken machines return the scancode in a
buffer */
if ( obj - > buffer . pointer & &
obj - > buffer . length > 0 )
scancode = obj - > buffer . pointer [ 0 ] ;
}
if ( scancode )
2011-02-17 21:44:09 +03:00
sparse_keymap_report_event (
dell_wmi_aio_input_dev ,
scancode , 1 , true ) ;
break ;
}
}
kfree ( obj ) ;
}
static int __init dell_wmi_aio_input_setup ( void )
{
int err ;
dell_wmi_aio_input_dev = input_allocate_device ( ) ;
if ( ! dell_wmi_aio_input_dev )
return - ENOMEM ;
dell_wmi_aio_input_dev - > name = " Dell AIO WMI hotkeys " ;
dell_wmi_aio_input_dev - > phys = " wmi/input0 " ;
dell_wmi_aio_input_dev - > id . bustype = BUS_HOST ;
err = sparse_keymap_setup ( dell_wmi_aio_input_dev ,
dell_wmi_aio_keymap , NULL ) ;
if ( err ) {
pr_err ( " Unable to setup input device keymap \n " ) ;
goto err_free_dev ;
}
err = input_register_device ( dell_wmi_aio_input_dev ) ;
if ( err ) {
pr_info ( " Unable to register input device \n " ) ;
goto err_free_keymap ;
}
return 0 ;
err_free_keymap :
sparse_keymap_free ( dell_wmi_aio_input_dev ) ;
err_free_dev :
input_free_device ( dell_wmi_aio_input_dev ) ;
return err ;
}
static const char * dell_wmi_aio_find ( void )
{
int i ;
for ( i = 0 ; dell_wmi_aio_guids [ i ] ! = NULL ; i + + )
if ( wmi_has_guid ( dell_wmi_aio_guids [ i ] ) )
return dell_wmi_aio_guids [ i ] ;
return NULL ;
}
static int __init dell_wmi_aio_init ( void )
{
int err ;
const char * guid ;
guid = dell_wmi_aio_find ( ) ;
if ( ! guid ) {
2011-03-30 02:21:37 +04:00
pr_warn ( " No known WMI GUID found \n " ) ;
2011-02-17 21:44:09 +03:00
return - ENXIO ;
}
err = dell_wmi_aio_input_setup ( ) ;
if ( err )
return err ;
err = wmi_install_notify_handler ( guid , dell_wmi_aio_notify , NULL ) ;
if ( err ) {
pr_err ( " Unable to register notify handler - %d \n " , err ) ;
sparse_keymap_free ( dell_wmi_aio_input_dev ) ;
input_unregister_device ( dell_wmi_aio_input_dev ) ;
return err ;
}
return 0 ;
}
static void __exit dell_wmi_aio_exit ( void )
{
const char * guid ;
guid = dell_wmi_aio_find ( ) ;
wmi_remove_notify_handler ( guid ) ;
sparse_keymap_free ( dell_wmi_aio_input_dev ) ;
input_unregister_device ( dell_wmi_aio_input_dev ) ;
}
module_init ( dell_wmi_aio_init ) ;
module_exit ( dell_wmi_aio_exit ) ;