2016-07-01 09:51:49 +08:00
/*
* Intel Virtual Button driver for Windows 8.1 +
*
* Copyright ( C ) 2016 AceLan Kao < acelan . kao @ canonical . com >
* Copyright ( C ) 2016 Alex Hung < alex . hung @ canonical . com >
*
* 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 .
*
*/
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/init.h>
# include <linux/input.h>
# include <linux/platform_device.h>
# include <linux/input/sparse-keymap.h>
# include <linux/acpi.h>
2017-06-08 02:15:52 +02:00
# include <linux/suspend.h>
2016-07-01 09:51:49 +08:00
# include <acpi/acpi_bus.h>
MODULE_LICENSE ( " GPL " ) ;
MODULE_AUTHOR ( " AceLan Kao " ) ;
static const struct acpi_device_id intel_vbtn_ids [ ] = {
{ " INT33D6 " , 0 } ,
{ " " , 0 } ,
} ;
/* In theory, these are HID usages. */
static const struct key_entry intel_vbtn_keymap [ ] = {
2017-08-04 12:00:06 -05:00
{ KE_KEY , 0xC0 , { KEY_POWER } } , /* power key press */
{ KE_IGNORE , 0xC1 , { KEY_POWER } } , /* power key release */
2017-04-24 23:35:21 +02:00
{ KE_KEY , 0xC4 , { KEY_VOLUMEUP } } , /* volume-up key press */
{ KE_IGNORE , 0xC5 , { KEY_VOLUMEUP } } , /* volume-up key release */
{ KE_KEY , 0xC6 , { KEY_VOLUMEDOWN } } , /* volume-down key press */
{ KE_IGNORE , 0xC7 , { KEY_VOLUMEDOWN } } , /* volume-down key release */
2017-11-09 23:44:32 +01:00
{ KE_SW , 0xCC , { . sw = { SW_TABLET_MODE , 1 } } } , /* Tablet */
{ KE_SW , 0xCD , { . sw = { SW_TABLET_MODE , 0 } } } , /* Laptop */
2016-07-01 09:51:49 +08:00
{ KE_END } ,
} ;
struct intel_vbtn_priv {
struct input_dev * input_dev ;
2017-06-08 02:15:52 +02:00
bool wakeup_mode ;
2016-07-01 09:51:49 +08:00
} ;
static int intel_vbtn_input_setup ( struct platform_device * device )
{
struct intel_vbtn_priv * priv = dev_get_drvdata ( & device - > dev ) ;
int ret ;
2016-09-20 17:01:24 +08:00
priv - > input_dev = devm_input_allocate_device ( & device - > dev ) ;
2016-07-01 09:51:49 +08:00
if ( ! priv - > input_dev )
return - ENOMEM ;
ret = sparse_keymap_setup ( priv - > input_dev , intel_vbtn_keymap , NULL ) ;
if ( ret )
2016-09-20 17:01:24 +08:00
return ret ;
2016-07-01 09:51:49 +08:00
priv - > input_dev - > dev . parent = & device - > dev ;
priv - > input_dev - > name = " Intel Virtual Button driver " ;
priv - > input_dev - > id . bustype = BUS_HOST ;
2016-09-20 17:01:24 +08:00
return input_register_device ( priv - > input_dev ) ;
2016-07-01 09:51:49 +08:00
}
static void notify_handler ( acpi_handle handle , u32 event , void * context )
{
struct platform_device * device = context ;
struct intel_vbtn_priv * priv = dev_get_drvdata ( & device - > dev ) ;
2017-11-09 23:44:33 +01:00
const struct key_entry * ke_rel ;
bool autorelease ;
2016-07-01 09:51:49 +08:00
2017-06-08 02:15:52 +02:00
if ( priv - > wakeup_mode ) {
if ( sparse_keymap_entry_from_scancode ( priv - > input_dev , event ) ) {
pm_wakeup_hard_event ( & device - > dev ) ;
return ;
}
2017-11-09 23:44:33 +01:00
} else {
/* Use the fact press/release come in even/odd pairs */
if ( ( event & 1 ) & & sparse_keymap_report_event ( priv - > input_dev ,
event , 0 , false ) )
return ;
ke_rel = sparse_keymap_entry_from_scancode ( priv - > input_dev ,
event | 1 ) ;
autorelease = ! ke_rel | | ke_rel - > type = = KE_IGNORE ;
if ( sparse_keymap_report_event ( priv - > input_dev , event , 1 ,
autorelease ) )
return ;
2017-06-08 02:15:52 +02:00
}
2017-07-20 20:56:48 -07:00
dev_dbg ( & device - > dev , " unknown event index 0x%x \n " , event ) ;
2016-07-01 09:51:49 +08:00
}
static int intel_vbtn_probe ( struct platform_device * device )
{
acpi_handle handle = ACPI_HANDLE ( & device - > dev ) ;
struct intel_vbtn_priv * priv ;
acpi_status status ;
int err ;
status = acpi_evaluate_object ( handle , " VBDL " , NULL , NULL ) ;
2016-09-19 09:33:51 +08:00
if ( ACPI_FAILURE ( status ) ) {
2016-07-01 09:51:49 +08:00
dev_warn ( & device - > dev , " failed to read Intel Virtual Button driver \n " ) ;
return - ENODEV ;
}
priv = devm_kzalloc ( & device - > dev , sizeof ( * priv ) , GFP_KERNEL ) ;
if ( ! priv )
return - ENOMEM ;
dev_set_drvdata ( & device - > dev , priv ) ;
err = intel_vbtn_input_setup ( device ) ;
if ( err ) {
pr_err ( " Failed to setup Intel Virtual Button \n " ) ;
return err ;
}
status = acpi_install_notify_handler ( handle ,
ACPI_DEVICE_NOTIFY ,
notify_handler ,
device ) ;
2016-09-20 17:01:24 +08:00
if ( ACPI_FAILURE ( status ) )
return - EBUSY ;
2016-07-01 09:51:49 +08:00
2017-06-08 02:15:52 +02:00
device_init_wakeup ( & device - > dev , true ) ;
2016-07-01 09:51:49 +08:00
return 0 ;
}
static int intel_vbtn_remove ( struct platform_device * device )
{
acpi_handle handle = ACPI_HANDLE ( & device - > dev ) ;
acpi_remove_notify_handler ( handle , ACPI_DEVICE_NOTIFY , notify_handler ) ;
/*
* Even if we failed to shut off the event stream , we can still
* safely detach from the device .
*/
return 0 ;
}
2017-06-08 02:15:52 +02:00
static int intel_vbtn_pm_prepare ( struct device * dev )
{
struct intel_vbtn_priv * priv = dev_get_drvdata ( dev ) ;
priv - > wakeup_mode = true ;
return 0 ;
}
static int intel_vbtn_pm_resume ( struct device * dev )
{
struct intel_vbtn_priv * priv = dev_get_drvdata ( dev ) ;
priv - > wakeup_mode = false ;
return 0 ;
}
static const struct dev_pm_ops intel_vbtn_pm_ops = {
. prepare = intel_vbtn_pm_prepare ,
. resume = intel_vbtn_pm_resume ,
. restore = intel_vbtn_pm_resume ,
. thaw = intel_vbtn_pm_resume ,
} ;
2016-07-01 09:51:49 +08:00
static struct platform_driver intel_vbtn_pl_driver = {
. driver = {
. name = " intel-vbtn " ,
. acpi_match_table = intel_vbtn_ids ,
2017-06-08 02:15:52 +02:00
. pm = & intel_vbtn_pm_ops ,
2016-07-01 09:51:49 +08:00
} ,
. probe = intel_vbtn_probe ,
. remove = intel_vbtn_remove ,
} ;
MODULE_DEVICE_TABLE ( acpi , intel_vbtn_ids ) ;
static acpi_status __init
check_acpi_dev ( acpi_handle handle , u32 lvl , void * context , void * * rv )
{
const struct acpi_device_id * ids = context ;
struct acpi_device * dev ;
if ( acpi_bus_get_device ( handle , & dev ) ! = 0 )
return AE_OK ;
if ( acpi_match_device_ids ( dev , ids ) = = 0 )
2016-11-03 16:21:26 +02:00
if ( acpi_create_platform_device ( dev , NULL ) )
2016-07-01 09:51:49 +08:00
dev_info ( & dev - > dev ,
" intel-vbtn: created platform device \n " ) ;
return AE_OK ;
}
static int __init intel_vbtn_init ( void )
{
acpi_walk_namespace ( ACPI_TYPE_DEVICE , ACPI_ROOT_OBJECT ,
ACPI_UINT32_MAX , check_acpi_dev , NULL ,
( void * ) intel_vbtn_ids , NULL ) ;
return platform_driver_register ( & intel_vbtn_pl_driver ) ;
}
module_init ( intel_vbtn_init ) ;
static void __exit intel_vbtn_exit ( void )
{
platform_driver_unregister ( & intel_vbtn_pl_driver ) ;
}
module_exit ( intel_vbtn_exit ) ;