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
2020-09-12 11:35:32 +02:00
/* Returned when NOT in tablet mode on some HP Stream x360 11 models */
# define VGBS_TABLET_MODE_FLAG_ALT 0x10
2018-01-29 21:27:43 +01:00
/* When NOT in tablet mode, VGBS returns with the flag 0x40 */
2020-09-12 11:35:32 +02:00
# define VGBS_TABLET_MODE_FLAG 0x40
# define VGBS_DOCK_MODE_FLAG 0x80
# define VGBS_TABLET_MODE_FLAGS (VGBS_TABLET_MODE_FLAG | VGBS_TABLET_MODE_FLAG_ALT)
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 } ,
} ;
2020-01-28 12:26:21 +02:00
MODULE_DEVICE_TABLE ( acpi , intel_vbtn_ids ) ;
2016-07-01 09:51:49 +08:00
/* 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 */
2021-01-15 17:18:48 +01:00
{ KE_END }
2020-05-02 20:29:48 +02:00
} ;
static const struct key_entry intel_vbtn_switchmap [ ] = {
2021-03-21 17:35:13 +01:00
/*
* SW_DOCK should only be reported for docking stations , but DSDTs using the
* intel - vbtn code , always seem to use this for 2 - in - 1 s / convertibles and set
* SW_DOCK = 1 when in laptop - mode ( in tandem with setting SW_TABLET_MODE = 0 ) .
* This causes userspace to think the laptop is docked to a port - replicator
* and to disable suspend - on - lid - close , which is undesirable .
* Map the dock events to KEY_IGNORE to avoid this broken SW_DOCK reporting .
*/
{ KE_IGNORE , 0xCA , { . sw = { SW_DOCK , 1 } } } , /* Docked */
{ KE_IGNORE , 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 */
2021-01-15 17:18:48 +01:00
{ KE_END }
2016-07-01 09:51:49 +08:00
} ;
2020-05-02 20:29:48 +02:00
# define KEYMAP_LEN \
( ARRAY_SIZE ( intel_vbtn_keymap ) + ARRAY_SIZE ( intel_vbtn_switchmap ) + 1 )
2016-07-01 09:51:49 +08:00
struct intel_vbtn_priv {
2021-01-15 17:18:48 +01:00
struct input_dev * buttons_dev ;
struct input_dev * switches_dev ;
2020-05-02 20:29:51 +02:00
bool has_buttons ;
2020-05-02 20:29:49 +02:00
bool has_switches ;
2017-06-08 02:15:52 +02:00
bool wakeup_mode ;
2016-07-01 09:51:49 +08:00
} ;
2020-05-10 14:20:46 +02:00
static void detect_tablet_mode ( struct platform_device * device )
{
struct intel_vbtn_priv * priv = dev_get_drvdata ( & device - > dev ) ;
acpi_handle handle = ACPI_HANDLE ( & device - > dev ) ;
unsigned long long vgbs ;
acpi_status status ;
int m ;
status = acpi_evaluate_integer ( handle , " VGBS " , NULL , & vgbs ) ;
if ( ACPI_FAILURE ( status ) )
return ;
2020-09-12 11:35:32 +02:00
m = ! ( vgbs & VGBS_TABLET_MODE_FLAGS ) ;
2021-01-15 17:18:48 +01:00
input_report_switch ( priv - > switches_dev , SW_TABLET_MODE , m ) ;
2020-09-12 11:35:32 +02:00
m = ( vgbs & VGBS_DOCK_MODE_FLAG ) ? 1 : 0 ;
2021-01-15 17:18:48 +01:00
input_report_switch ( priv - > switches_dev , SW_DOCK , m ) ;
2020-05-10 14:20:46 +02:00
}
2021-01-15 17:18:48 +01:00
/*
* Note this unconditionally creates the 2 input_dev - s and sets up
* the sparse - keymaps . Only the registration is conditional on
* have_buttons / have_switches . This is done so that the notify
* handler can always call sparse_keymap_entry_from_scancode ( )
* on the input_dev - s do determine the event type .
*/
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 ) ;
2021-01-15 17:18:48 +01:00
int ret ;
2020-05-02 20:29:48 +02:00
2021-01-15 17:18:48 +01:00
priv - > buttons_dev = devm_input_allocate_device ( & device - > dev ) ;
if ( ! priv - > buttons_dev )
return - ENOMEM ;
2020-05-02 20:29:48 +02:00
2021-01-15 17:18:48 +01:00
ret = sparse_keymap_setup ( priv - > buttons_dev , intel_vbtn_keymap , NULL ) ;
if ( ret )
return ret ;
2020-05-02 20:29:48 +02:00
2021-01-15 17:18:48 +01:00
priv - > buttons_dev - > dev . parent = & device - > dev ;
priv - > buttons_dev - > name = " Intel Virtual Buttons " ;
priv - > buttons_dev - > id . bustype = BUS_HOST ;
if ( priv - > has_buttons ) {
ret = input_register_device ( priv - > buttons_dev ) ;
if ( ret )
return ret ;
}
2016-07-01 09:51:49 +08:00
2021-01-15 17:18:48 +01:00
priv - > switches_dev = devm_input_allocate_device ( & device - > dev ) ;
if ( ! priv - > switches_dev )
2016-07-01 09:51:49 +08:00
return - ENOMEM ;
2021-01-15 17:18:48 +01:00
ret = sparse_keymap_setup ( priv - > switches_dev , intel_vbtn_switchmap , NULL ) ;
2016-07-01 09:51:49 +08:00
if ( ret )
2016-09-20 17:01:24 +08:00
return ret ;
2016-07-01 09:51:49 +08:00
2021-01-15 17:18:48 +01:00
priv - > switches_dev - > dev . parent = & device - > dev ;
priv - > switches_dev - > name = " Intel Virtual Switches " ;
priv - > switches_dev - > id . bustype = BUS_HOST ;
2016-07-01 09:51:49 +08:00
2021-01-15 17:18:48 +01:00
if ( priv - > has_switches ) {
2020-05-10 14:20:47 +02:00
detect_tablet_mode ( device ) ;
2021-01-15 17:18:48 +01:00
ret = input_register_device ( priv - > switches_dev ) ;
if ( ret )
return ret ;
}
return 0 ;
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 ;
2021-01-15 17:18:48 +01:00
struct input_dev * input_dev ;
2017-11-09 23:44:33 +01:00
bool autorelease ;
2021-01-15 17:18:49 +01:00
int ret ;
2016-07-01 09:51:49 +08:00
2021-01-15 17:18:48 +01:00
if ( ( ke = sparse_keymap_entry_from_scancode ( priv - > buttons_dev , event ) ) ) {
if ( ! priv - > has_buttons ) {
dev_warn ( & device - > dev , " Warning: received a button event on a device without buttons, please report this. \n " ) ;
return ;
}
input_dev = priv - > buttons_dev ;
} else if ( ( ke = sparse_keymap_entry_from_scancode ( priv - > switches_dev , event ) ) ) {
if ( ! priv - > has_switches ) {
2021-01-15 17:18:49 +01:00
dev_info ( & device - > dev , " Registering Intel Virtual Switches input-dev after receiving a switch event \n " ) ;
ret = input_register_device ( priv - > switches_dev ) ;
if ( ret )
return ;
priv - > has_switches = true ;
2021-01-15 17:18:48 +01:00
}
input_dev = priv - > switches_dev ;
} else {
dev_dbg ( & device - > dev , " unknown event index 0x%x \n " , event ) ;
return ;
}
2021-01-15 17:18:47 +01:00
2021-01-15 17:18:48 +01:00
if ( priv - > wakeup_mode ) {
2021-01-15 17:18:47 +01:00
pm_wakeup_hard_event ( & device - > dev ) ;
/*
* Skip reporting an evdev event for button wake events ,
* mirroring how the drivers / acpi / button . c code skips this too .
*/
if ( ke - > type = = KE_KEY )
2017-06-08 02:15:52 +02:00
return ;
2017-12-08 14:57:54 -08:00
}
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 .
*/
2021-01-15 17:18:48 +01:00
ke_rel = sparse_keymap_entry_from_scancode ( input_dev , event | 1 ) ;
2017-12-08 14:57:54 -08:00
autorelease = val & & ( ! ke_rel | | ke_rel - > type = = KE_IGNORE ) ;
2017-11-09 23:44:33 +01:00
2021-01-15 17:18:48 +01:00
sparse_keymap_report_event ( input_dev , event , val , autorelease ) ;
2016-07-01 09:51:49 +08:00
}
2020-09-30 15:19:05 +02:00
/*
* There are several laptops ( non 2 - in - 1 ) models out there which support VGBS ,
* but simply always return 0 , which we translate to SW_TABLET_MODE = 1. This in
* turn causes userspace ( libinput ) to suppress events from the builtin
* keyboard and touchpad , making the laptop essentially unusable .
*
* Since the problem of wrongly reporting SW_TABLET_MODE = 1 in combination
* with libinput , leads to a non - usable system . Where as OTOH many people will
* not even notice when SW_TABLET_MODE is not being reported , a DMI based allow
* list is used here . This list mainly matches on the chassis - type of 2 - in - 1 s .
*
* There are also some 2 - in - 1 s which use the intel - vbtn ACPI interface to report
* SW_TABLET_MODE with a chassis - type of 8 ( " Portable " ) or 10 ( " Notebook " ) ,
* these are matched on a per model basis , since many normal laptops with a
* possible broken VGBS ACPI - method also use these chassis - types .
*/
static const struct dmi_system_id dmi_switches_allow_list [ ] = {
{
. matches = {
DMI_EXACT_MATCH ( DMI_CHASSIS_TYPE , " 31 " /* Convertible */ ) ,
} ,
} ,
{
. matches = {
DMI_EXACT_MATCH ( DMI_CHASSIS_TYPE , " 32 " /* Detachable */ ) ,
} ,
} ,
{
. matches = {
DMI_MATCH ( DMI_SYS_VENDOR , " Dell Inc. " ) ,
DMI_MATCH ( DMI_PRODUCT_NAME , " Venue 11 Pro 7130 " ) ,
} ,
} ,
2020-11-24 15:16:52 +02:00
{
. matches = {
DMI_MATCH ( DMI_SYS_VENDOR , " Hewlett-Packard " ) ,
DMI_MATCH ( DMI_PRODUCT_NAME , " HP Pavilion 13 x360 PC " ) ,
} ,
} ,
2020-12-01 14:57:27 +01:00
{
. matches = {
DMI_MATCH ( DMI_SYS_VENDOR , " Acer " ) ,
DMI_MATCH ( DMI_PRODUCT_NAME , " Switch SA5-271 " ) ,
} ,
} ,
2020-12-26 15:53:06 -05:00
{
. matches = {
DMI_MATCH ( DMI_SYS_VENDOR , " Dell Inc. " ) ,
DMI_MATCH ( DMI_PRODUCT_NAME , " Inspiron 7352 " ) ,
} ,
} ,
2020-09-30 15:19:05 +02:00
{ } /* Array terminator */
} ;
2020-05-02 20:29:49 +02:00
static bool intel_vbtn_has_switches ( acpi_handle handle )
{
unsigned long long vgbs ;
acpi_status status ;
2020-09-30 15:19:05 +02:00
if ( ! dmi_check_system ( dmi_switches_allow_list ) )
2020-05-02 20:29:50 +02:00
return false ;
2020-05-02 20:29:49 +02:00
status = acpi_evaluate_integer ( handle , " VGBS " , NULL , & vgbs ) ;
return ACPI_SUCCESS ( status ) ;
}
2018-02-22 13:58:42 -06:00
static int intel_vbtn_probe ( struct platform_device * device )
{
2016-07-01 09:51:49 +08:00
acpi_handle handle = ACPI_HANDLE ( & device - > dev ) ;
2020-05-02 20:29:51 +02:00
bool has_buttons , has_switches ;
2016-07-01 09:51:49 +08:00
struct intel_vbtn_priv * priv ;
acpi_status status ;
int err ;
2021-01-15 17:18:50 +01:00
has_buttons = acpi_has_method ( handle , " VBDL " ) ;
2020-05-02 20:29:51 +02:00
has_switches = intel_vbtn_has_switches ( handle ) ;
if ( ! has_buttons & & ! has_switches ) {
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 ) ;
2020-05-02 20:29:51 +02:00
priv - > has_buttons = has_buttons ;
priv - > has_switches = has_switches ;
2020-05-02 20:29:49 +02:00
2016-07-01 09:51:49 +08:00
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
2021-01-15 17:18:50 +01:00
if ( has_buttons ) {
status = acpi_evaluate_object ( handle , " VBDL " , NULL , NULL ) ;
if ( ACPI_FAILURE ( status ) )
dev_err ( & device - > dev , " Error VBDL failed with ACPI status %d \n " , status ) ;
}
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 ,
} ;
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 )
2020-07-10 17:30:18 +08:00
if ( ! IS_ERR_OR_NULL ( 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 ) ;