2024-01-31 14:16:40 +03:00
// SPDX-License-Identifier: GPL-2.0-or-later
/*
2024-03-28 00:52:08 +03:00
* ACPI Direct App Launch driver
2024-01-31 14:16:40 +03:00
*
* Copyright ( C ) 2024 Armin Wolf < W_Armin @ gmx . de >
* Copyright ( C ) 2022 Arvid Norlander < lkml @ vorapal . se >
* Copyright ( C ) 2007 - 2010 Angelo Arrifano < miknix @ gmail . com >
*
* Information gathered from disassembled dsdt and from here :
* < https : //archive.org/details/microsoft-acpi-dirapplaunch>
*/
# include <linux/acpi.h>
2024-03-28 00:52:08 +03:00
# include <linux/device.h>
# include <linux/errno.h>
2024-01-31 14:16:40 +03:00
# include <linux/init.h>
# include <linux/input.h>
# include <linux/input/sparse-keymap.h>
2024-03-28 00:52:08 +03:00
# include <linux/mod_devicetable.h>
2024-01-31 14:16:40 +03:00
# include <linux/module.h>
2024-03-28 00:45:24 +03:00
# include <linux/mutex.h>
2024-01-31 14:16:40 +03:00
# include <linux/platform_device.h>
2024-03-28 00:52:08 +03:00
# include <linux/pm_wakeup.h>
# include <linux/printk.h>
# include <linux/slab.h>
2024-01-31 14:16:40 +03:00
# include <linux/sysfs.h>
# include <linux/types.h>
# include <asm/unaligned.h>
# define DRIVER_NAME "quickstart"
/*
* There will be two events :
* 0x02 - Button was pressed while device was off / sleeping .
* 0x80 - Button was pressed while device was up .
*/
# define QUICKSTART_EVENT_RUNTIME 0x80
struct quickstart_data {
struct device * dev ;
2024-03-28 00:45:24 +03:00
struct mutex input_lock ; /* Protects input sequence during notify */
2024-01-31 14:16:40 +03:00
struct input_dev * input_device ;
char input_name [ 32 ] ;
char phys [ 32 ] ;
u32 id ;
} ;
/*
* Knowing what these buttons do require system specific knowledge .
* This could be done by matching on DMI data in a long quirk table .
* However , it is easier to leave it up to user space to figure this out .
*
* Using for example udev hwdb the scancode 0x1 can be remapped suitably .
*/
static const struct key_entry quickstart_keymap [ ] = {
{ KE_KEY , 0x1 , { KEY_UNKNOWN } } ,
{ KE_END , 0 } ,
} ;
static ssize_t button_id_show ( struct device * dev , struct device_attribute * attr , char * buf )
{
struct quickstart_data * data = dev_get_drvdata ( dev ) ;
return sysfs_emit ( buf , " %u \n " , data - > id ) ;
}
static DEVICE_ATTR_RO ( button_id ) ;
static struct attribute * quickstart_attrs [ ] = {
& dev_attr_button_id . attr ,
NULL
} ;
ATTRIBUTE_GROUPS ( quickstart ) ;
static void quickstart_notify ( acpi_handle handle , u32 event , void * context )
{
struct quickstart_data * data = context ;
switch ( event ) {
case QUICKSTART_EVENT_RUNTIME :
2024-03-28 00:45:24 +03:00
mutex_lock ( & data - > input_lock ) ;
2024-01-31 14:16:40 +03:00
sparse_keymap_report_event ( data - > input_device , 0x1 , 1 , true ) ;
2024-03-28 00:45:24 +03:00
mutex_unlock ( & data - > input_lock ) ;
2024-01-31 14:16:40 +03:00
acpi_bus_generate_netlink_event ( DRIVER_NAME , dev_name ( data - > dev ) , event , 0 ) ;
break ;
default :
dev_err ( data - > dev , FW_INFO " Unexpected ACPI notify event (%u) \n " , event ) ;
break ;
}
}
/*
* The GHID ACPI method is used to indicate the " role " of the button .
* However , all the meanings of these values are vendor defined .
*
* We do however expose this value to user space .
*/
static int quickstart_get_ghid ( struct quickstart_data * data )
{
struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER , NULL } ;
acpi_handle handle = ACPI_HANDLE ( data - > dev ) ;
union acpi_object * obj ;
acpi_status status ;
int ret = 0 ;
/*
* This returns a buffer telling the button usage ID ,
* and triggers pending notify events ( The ones before booting ) .
*/
status = acpi_evaluate_object_typed ( handle , " GHID " , NULL , & buffer , ACPI_TYPE_BUFFER ) ;
if ( ACPI_FAILURE ( status ) )
return - EIO ;
obj = buffer . pointer ;
if ( ! obj )
return - ENODATA ;
/*
* Quoting the specification :
* " The GHID method can return a BYTE, WORD, or DWORD.
* The value must be encoded in little - endian byte
* order ( least significant byte first ) . "
*/
switch ( obj - > buffer . length ) {
case 1 :
data - > id = obj - > buffer . pointer [ 0 ] ;
break ;
case 2 :
data - > id = get_unaligned_le16 ( obj - > buffer . pointer ) ;
break ;
case 4 :
data - > id = get_unaligned_le32 ( obj - > buffer . pointer ) ;
break ;
default :
dev_err ( data - > dev ,
FW_BUG " GHID method returned buffer of unexpected length %u \n " ,
obj - > buffer . length ) ;
ret = - EIO ;
break ;
}
kfree ( obj ) ;
return ret ;
}
static void quickstart_notify_remove ( void * context )
{
struct quickstart_data * data = context ;
acpi_handle handle ;
handle = ACPI_HANDLE ( data - > dev ) ;
acpi_remove_notify_handler ( handle , ACPI_DEVICE_NOTIFY , quickstart_notify ) ;
}
2024-03-28 00:45:24 +03:00
static void quickstart_mutex_destroy ( void * data )
{
struct mutex * lock = data ;
mutex_destroy ( lock ) ;
}
2024-01-31 14:16:40 +03:00
static int quickstart_probe ( struct platform_device * pdev )
{
struct quickstart_data * data ;
acpi_handle handle ;
acpi_status status ;
int ret ;
handle = ACPI_HANDLE ( & pdev - > dev ) ;
if ( ! handle )
return - ENODEV ;
data = devm_kzalloc ( & pdev - > dev , sizeof ( * data ) , GFP_KERNEL ) ;
if ( ! data )
return - ENOMEM ;
data - > dev = & pdev - > dev ;
dev_set_drvdata ( & pdev - > dev , data ) ;
2024-03-28 00:45:24 +03:00
mutex_init ( & data - > input_lock ) ;
ret = devm_add_action_or_reset ( & pdev - > dev , quickstart_mutex_destroy , & data - > input_lock ) ;
if ( ret < 0 )
return ret ;
2024-03-28 00:52:08 +03:00
/*
* We have to initialize the device wakeup before evaluating GHID because
2024-01-31 14:16:40 +03:00
* doing so will notify the device if the button was used to wake the machine
* from S5 .
*/
device_init_wakeup ( & pdev - > dev , true ) ;
ret = quickstart_get_ghid ( data ) ;
if ( ret < 0 )
return ret ;
data - > input_device = devm_input_allocate_device ( & pdev - > dev ) ;
if ( ! data - > input_device )
return - ENOMEM ;
ret = sparse_keymap_setup ( data - > input_device , quickstart_keymap , NULL ) ;
if ( ret < 0 )
return ret ;
snprintf ( data - > input_name , sizeof ( data - > input_name ) , " Quickstart Button %u " , data - > id ) ;
snprintf ( data - > phys , sizeof ( data - > phys ) , DRIVER_NAME " /input%u " , data - > id ) ;
data - > input_device - > name = data - > input_name ;
data - > input_device - > phys = data - > phys ;
data - > input_device - > id . bustype = BUS_HOST ;
ret = input_register_device ( data - > input_device ) ;
if ( ret < 0 )
return ret ;
status = acpi_install_notify_handler ( handle , ACPI_DEVICE_NOTIFY , quickstart_notify , data ) ;
if ( ACPI_FAILURE ( status ) )
return - EIO ;
return devm_add_action_or_reset ( & pdev - > dev , quickstart_notify_remove , data ) ;
}
static const struct acpi_device_id quickstart_device_ids [ ] = {
2024-03-28 00:52:08 +03:00
{ " PNP0C32 " } ,
2024-01-31 14:16:40 +03:00
{ }
} ;
MODULE_DEVICE_TABLE ( acpi , quickstart_device_ids ) ;
static struct platform_driver quickstart_platform_driver = {
. driver = {
. name = DRIVER_NAME ,
. dev_groups = quickstart_groups ,
. probe_type = PROBE_PREFER_ASYNCHRONOUS ,
. acpi_match_table = quickstart_device_ids ,
} ,
. probe = quickstart_probe ,
} ;
module_platform_driver ( quickstart_platform_driver ) ;
MODULE_AUTHOR ( " Armin Wolf <W_Armin@gmx.de> " ) ;
MODULE_AUTHOR ( " Arvid Norlander <lkml@vorpal.se> " ) ;
MODULE_AUTHOR ( " Angelo Arrifano " ) ;
MODULE_DESCRIPTION ( " ACPI Direct App Launch driver " ) ;
MODULE_LICENSE ( " GPL " ) ;