2016-11-25 19:11:41 +03:00
/*
* Driver for the LID cover switch of the Surface 3
*
* Copyright ( c ) 2016 Red Hat Inc .
*/
/*
* 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 ; version 2 of the License .
*/
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/slab.h>
# include <linux/acpi.h>
# include <linux/dmi.h>
# include <linux/input.h>
# include <linux/mutex.h>
# include <linux/platform_device.h>
# include <linux/spi/spi.h>
MODULE_AUTHOR ( " Benjamin Tissoires <benjamin.tissoires@redhat.com> " ) ;
MODULE_DESCRIPTION ( " Surface 3 platform driver " ) ;
MODULE_LICENSE ( " GPL " ) ;
# define ACPI_BUTTON_HID_LID "PNP0C0D"
# define SPI_CTL_OBJ_NAME "SPI"
# define SPI_TS_OBJ_NAME "NTRG"
# define SURFACE3_LID_GUID "F7CC25EC-D20B-404C-8903-0ED4359C18AE"
MODULE_ALIAS ( " wmi: " SURFACE3_LID_GUID ) ;
static const struct dmi_system_id surface3_dmi_table [ ] = {
# if defined(CONFIG_X86)
{
. matches = {
DMI_MATCH ( DMI_SYS_VENDOR , " Microsoft Corporation " ) ,
DMI_MATCH ( DMI_PRODUCT_NAME , " Surface 3 " ) ,
} ,
} ,
# endif
{ }
} ;
struct surface3_wmi {
struct acpi_device * touchscreen_adev ;
struct acpi_device * pnp0c0d_adev ;
struct acpi_hotplug_context hp ;
struct input_dev * input ;
} ;
static struct platform_device * s3_wmi_pdev ;
static struct surface3_wmi s3_wmi ;
static DEFINE_MUTEX ( s3_wmi_lock ) ;
static int s3_wmi_query_block ( const char * guid , int instance , int * ret )
{
2016-12-15 04:10:02 +03:00
struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER , NULL } ;
2016-11-25 19:11:41 +03:00
acpi_status status ;
union acpi_object * obj ;
2016-12-15 04:10:02 +03:00
int error = 0 ;
2016-11-25 19:11:41 +03:00
mutex_lock ( & s3_wmi_lock ) ;
status = wmi_query_block ( guid , instance , & output ) ;
obj = output . pointer ;
if ( ! obj | | obj - > type ! = ACPI_TYPE_INTEGER ) {
if ( obj ) {
pr_err ( " query block returned object type: %d - buffer length:%d \n " ,
obj - > type ,
obj - > type = = ACPI_TYPE_BUFFER ?
obj - > buffer . length : 0 ) ;
}
2016-12-15 04:10:02 +03:00
error = - EINVAL ;
goto out_free_unlock ;
2016-11-25 19:11:41 +03:00
}
* ret = obj - > integer . value ;
2016-12-15 04:10:02 +03:00
out_free_unlock :
2016-11-25 19:11:41 +03:00
kfree ( obj ) ;
mutex_unlock ( & s3_wmi_lock ) ;
2016-12-15 04:10:02 +03:00
return error ;
2016-11-25 19:11:41 +03:00
}
static inline int s3_wmi_query_lid ( int * ret )
{
return s3_wmi_query_block ( SURFACE3_LID_GUID , 0 , ret ) ;
}
static int s3_wmi_send_lid_state ( void )
{
int ret , lid_sw ;
ret = s3_wmi_query_lid ( & lid_sw ) ;
if ( ret )
return ret ;
input_report_switch ( s3_wmi . input , SW_LID , lid_sw ) ;
input_sync ( s3_wmi . input ) ;
return 0 ;
}
static int s3_wmi_hp_notify ( struct acpi_device * adev , u32 value )
{
return s3_wmi_send_lid_state ( ) ;
}
static acpi_status s3_wmi_attach_spi_device ( acpi_handle handle ,
u32 level ,
void * data ,
void * * return_value )
{
struct acpi_device * adev , * * ts_adev ;
if ( acpi_bus_get_device ( handle , & adev ) )
return AE_OK ;
ts_adev = data ;
if ( strncmp ( acpi_device_bid ( adev ) , SPI_TS_OBJ_NAME ,
strlen ( SPI_TS_OBJ_NAME ) ) )
return AE_OK ;
if ( * ts_adev ) {
pr_err ( " duplicate entry %s \n " , SPI_TS_OBJ_NAME ) ;
return AE_OK ;
}
* ts_adev = adev ;
return AE_OK ;
}
static int s3_wmi_check_platform_device ( struct device * dev , void * data )
{
2017-01-18 11:13:46 +03:00
struct acpi_device * adev , * ts_adev = NULL ;
2016-11-25 19:11:41 +03:00
acpi_handle handle ;
acpi_status status ;
/* ignore non ACPI devices */
handle = ACPI_HANDLE ( dev ) ;
if ( ! handle | | acpi_bus_get_device ( handle , & adev ) )
return 0 ;
/* check for LID ACPI switch */
if ( ! strcmp ( ACPI_BUTTON_HID_LID , acpi_device_hid ( adev ) ) ) {
s3_wmi . pnp0c0d_adev = adev ;
return 0 ;
}
/* ignore non SPI controllers */
if ( strncmp ( acpi_device_bid ( adev ) , SPI_CTL_OBJ_NAME ,
strlen ( SPI_CTL_OBJ_NAME ) ) )
return 0 ;
status = acpi_walk_namespace ( ACPI_TYPE_DEVICE , handle , 1 ,
s3_wmi_attach_spi_device , NULL ,
& ts_adev , NULL ) ;
if ( ACPI_FAILURE ( status ) )
dev_warn ( dev , " failed to enumerate SPI slaves \n " ) ;
if ( ! ts_adev )
return 0 ;
s3_wmi . touchscreen_adev = ts_adev ;
return 0 ;
}
static int s3_wmi_create_and_register_input ( struct platform_device * pdev )
{
struct input_dev * input ;
int error ;
input = devm_input_allocate_device ( & pdev - > dev ) ;
if ( ! input )
return - ENOMEM ;
input - > name = " Lid Switch " ;
input - > phys = " button/input0 " ;
input - > id . bustype = BUS_HOST ;
input - > id . product = 0x0005 ;
input_set_capability ( input , EV_SW , SW_LID ) ;
error = input_register_device ( input ) ;
if ( error )
goto out_err ;
s3_wmi . input = input ;
return 0 ;
out_err :
input_free_device ( s3_wmi . input ) ;
return error ;
}
static int __init s3_wmi_probe ( struct platform_device * pdev )
{
int error ;
if ( ! dmi_check_system ( surface3_dmi_table ) )
return - ENODEV ;
memset ( & s3_wmi , 0 , sizeof ( s3_wmi ) ) ;
bus_for_each_dev ( & platform_bus_type , NULL , NULL ,
s3_wmi_check_platform_device ) ;
if ( ! s3_wmi . touchscreen_adev )
return - ENODEV ;
acpi_bus_trim ( s3_wmi . pnp0c0d_adev ) ;
error = s3_wmi_create_and_register_input ( pdev ) ;
if ( error )
goto restore_acpi_lid ;
acpi_initialize_hp_context ( s3_wmi . touchscreen_adev , & s3_wmi . hp ,
s3_wmi_hp_notify , NULL ) ;
s3_wmi_send_lid_state ( ) ;
return 0 ;
restore_acpi_lid :
acpi_bus_scan ( s3_wmi . pnp0c0d_adev - > handle ) ;
return error ;
}
static int s3_wmi_remove ( struct platform_device * device )
{
/* remove the hotplug context from the acpi device */
s3_wmi . touchscreen_adev - > hp = NULL ;
/* reinstall the actual PNPC0C0D LID default handle */
acpi_bus_scan ( s3_wmi . pnp0c0d_adev - > handle ) ;
return 0 ;
}
2017-01-10 18:28:47 +03:00
static int __maybe_unused s3_wmi_resume ( struct device * dev )
2016-11-25 19:11:41 +03:00
{
s3_wmi_send_lid_state ( ) ;
return 0 ;
}
static SIMPLE_DEV_PM_OPS ( s3_wmi_pm , NULL , s3_wmi_resume ) ;
static struct platform_driver s3_wmi_driver = {
. driver = {
. name = " surface3-wmi " ,
. pm = & s3_wmi_pm ,
} ,
. remove = s3_wmi_remove ,
} ;
static int __init s3_wmi_init ( void )
{
int error ;
s3_wmi_pdev = platform_device_alloc ( " surface3-wmi " , - 1 ) ;
if ( ! s3_wmi_pdev )
return - ENOMEM ;
error = platform_device_add ( s3_wmi_pdev ) ;
if ( error )
goto err_device_put ;
error = platform_driver_probe ( & s3_wmi_driver , s3_wmi_probe ) ;
if ( error )
goto err_device_del ;
pr_info ( " Surface 3 WMI Extras loaded \n " ) ;
return 0 ;
err_device_del :
platform_device_del ( s3_wmi_pdev ) ;
err_device_put :
platform_device_put ( s3_wmi_pdev ) ;
return error ;
}
static void __exit s3_wmi_exit ( void )
{
platform_device_unregister ( s3_wmi_pdev ) ;
platform_driver_unregister ( & s3_wmi_driver ) ;
}
module_init ( s3_wmi_init ) ;
module_exit ( s3_wmi_exit ) ;