2018-01-31 19:54:07 +02:00
// SPDX-License-Identifier: GPL-2.0+
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 >
*/
2018-01-31 19:54:06 +02:00
# include <linux/acpi.h>
2018-02-22 13:58:42 -06:00
# include <linux/dmi.h>
2018-01-31 19:54:06 +02:00
# include <linux/input.h>
# include <linux/input/sparse-keymap.h>
2016-07-01 09:51:49 +08:00
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/platform_device.h>
2017-06-08 02:15:52 +02:00
# include <linux/suspend.h>
2016-07-01 09:51:49 +08:00
2018-01-29 21:27:43 +01:00
/* When NOT in tablet mode, VGBS returns with the flag 0x40 */
# define TABLET_MODE_FLAG 0x40
2018-06-24 17:29:40 -07:00
# define DOCK_MODE_FLAG 0x80
2018-01-29 21:27:43 +01:00
2016-07-01 09:51:49 +08:00
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-11-09 23:44:36 +01:00
{ KE_KEY , 0xC2 , { KEY_LEFTMETA } } , /* 'Windows' key press */
{ KE_KEY , 0xC3 , { KEY_LEFTMETA } } , /* 'Windows' 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:35 +01:00
{ KE_KEY , 0xC8 , { KEY_ROTATE_LOCK_TOGGLE } } , /* rotate-lock key press */
{ KE_KEY , 0xC9 , { KEY_ROTATE_LOCK_TOGGLE } } , /* rotate-lock key release */
2018-06-24 17:29:40 -07:00
{ KE_SW , 0xCA , { . sw = { SW_DOCK , 1 } } } , /* Docked */
{ KE_SW , 0xCB , { . sw = { SW_DOCK , 0 } } } , /* Undocked */
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-12-08 14:57:54 -08:00
unsigned int val = ! ( event & 1 ) ; /* Even=press, Odd=release */
2019-05-20 16:41:24 -06:00
const struct key_entry * ke , * ke_rel ;
2017-11-09 23:44:33 +01:00
bool autorelease ;
2016-07-01 09:51:49 +08:00
2017-06-08 02:15:52 +02:00
if ( priv - > wakeup_mode ) {
2019-05-20 16:41:24 -06:00
ke = sparse_keymap_entry_from_scancode ( priv - > input_dev , event ) ;
if ( ke ) {
2017-06-08 02:15:52 +02:00
pm_wakeup_hard_event ( & device - > dev ) ;
2019-05-20 16:41:24 -06:00
/*
* Switch events like tablet mode will wake the device
* and report the new switch position to the input
* subsystem .
*/
if ( ke - > type = = KE_SW )
sparse_keymap_report_event ( priv - > input_dev ,
event ,
val ,
0 ) ;
2017-06-08 02:15:52 +02:00
return ;
}
2017-12-08 14:57:54 -08:00
goto out_unknown ;
}
2017-11-09 23:44:33 +01:00
2017-12-08 14:57:54 -08:00
/*
* Even press events are autorelease if there is no corresponding odd
* release event , or if the odd event is KE_IGNORE .
*/
ke_rel = sparse_keymap_entry_from_scancode ( priv - > input_dev , event | 1 ) ;
autorelease = val & & ( ! ke_rel | | ke_rel - > type = = KE_IGNORE ) ;
2017-11-09 23:44:33 +01:00
2017-12-08 14:57:54 -08:00
if ( sparse_keymap_report_event ( priv - > input_dev , event , val , autorelease ) )
return ;
out_unknown :
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
}
2018-02-22 13:58:42 -06:00
static void detect_tablet_mode ( struct platform_device * device )
2016-07-01 09:51:49 +08:00
{
2018-02-22 13:58:42 -06:00
const char * chassis_type = dmi_get_system_info ( DMI_CHASSIS_TYPE ) ;
struct intel_vbtn_priv * priv = dev_get_drvdata ( & device - > dev ) ;
acpi_handle handle = ACPI_HANDLE ( & device - > dev ) ;
2018-01-29 21:27:43 +01:00
struct acpi_buffer vgbs_output = { ACPI_ALLOCATE_BUFFER , NULL } ;
2018-02-22 13:58:42 -06:00
union acpi_object * obj ;
acpi_status status ;
int m ;
if ( ! ( chassis_type & & strcmp ( chassis_type , " 31 " ) = = 0 ) )
goto out ;
status = acpi_evaluate_object ( handle , " VGBS " , NULL , & vgbs_output ) ;
if ( ACPI_FAILURE ( status ) )
goto out ;
obj = vgbs_output . pointer ;
if ( ! ( obj & & obj - > type = = ACPI_TYPE_INTEGER ) )
goto out ;
m = ! ( obj - > integer . value & TABLET_MODE_FLAG ) ;
input_report_switch ( priv - > input_dev , SW_TABLET_MODE , m ) ;
2018-06-24 17:29:40 -07:00
m = ( obj - > integer . value & DOCK_MODE_FLAG ) ? 1 : 0 ;
input_report_switch ( priv - > input_dev , SW_DOCK , m ) ;
2018-02-22 13:58:42 -06:00
out :
kfree ( vgbs_output . pointer ) ;
}
static int intel_vbtn_probe ( struct platform_device * device )
{
2016-07-01 09:51:49 +08:00
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 ;
}
2018-02-22 13:58:42 -06:00
detect_tablet_mode ( device ) ;
2018-01-29 21:27:43 +01:00
2016-07-01 09:51:49 +08:00
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 ) ;
2019-07-30 11:55:59 +02:00
/*
* In order for system wakeup to work , the EC GPE has to be marked as
* a wakeup one , so do that here ( this setting will persist , but it has
* no effect until the wakeup mask is set for the EC GPE ) .
*/
acpi_ec_mark_gpe_for_wake ( ) ;
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 ) ;
2018-02-28 12:09:56 +01:00
device_init_wakeup ( & device - > dev , false ) ;
2016-07-01 09:51:49 +08:00
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 )
{
2019-07-30 11:55:59 +02:00
if ( device_may_wakeup ( dev ) ) {
struct intel_vbtn_priv * priv = dev_get_drvdata ( dev ) ;
2017-06-08 02:15:52 +02:00
2019-07-30 11:55:59 +02:00
priv - > wakeup_mode = true ;
}
2017-06-08 02:15:52 +02:00
return 0 ;
}
2019-08-08 11:39:17 +02:00
static void intel_vbtn_pm_complete ( struct device * dev )
2017-06-08 02:15:52 +02:00
{
2019-08-08 11:39:17 +02:00
struct intel_vbtn_priv * priv = dev_get_drvdata ( dev ) ;
2017-06-08 02:15:52 +02:00
2019-08-21 11:40:19 +02:00
priv - > wakeup_mode = false ;
2019-08-08 11:39:17 +02:00
}
static int intel_vbtn_pm_resume ( struct device * dev )
{
intel_vbtn_pm_complete ( dev ) ;
2017-06-08 02:15:52 +02:00
return 0 ;
}
static const struct dev_pm_ops intel_vbtn_pm_ops = {
. prepare = intel_vbtn_pm_prepare ,
2019-08-08 11:39:17 +02:00
. complete = intel_vbtn_pm_complete ,
2017-06-08 02:15:52 +02:00
. 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 ) ;