2014-03-31 10:12:00 +04:00
/*
* Supports for the button array on SoC tablets originally running
* Windows 8.
*
* ( C ) Copyright 2014 Intel Corporation
*
* 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/module.h>
# include <linux/input.h>
# include <linux/init.h>
# include <linux/kernel.h>
# include <linux/acpi.h>
# include <linux/gpio/consumer.h>
# include <linux/gpio_keys.h>
# include <linux/platform_device.h>
2014-09-22 21:31:14 +04:00
# include <linux/acpi.h>
2014-03-31 10:12:00 +04:00
/*
* Definition of buttons on the tablet . The ACPI index of each button
* is defined in section 2.8 .7 .2 of " Windows ACPI Design Guide for SoC
* Platforms "
*/
# define MAX_NBUTTONS 5
struct soc_button_info {
const char * name ;
int acpi_index ;
unsigned int event_type ;
unsigned int event_code ;
bool autorepeat ;
bool wakeup ;
} ;
/*
* Some of the buttons like volume up / down are auto repeat , while others
* are not . To support both , we register two platform devices , and put
* buttons into them based on whether the key should be auto repeat .
*/
# define BUTTON_TYPES 2
struct soc_button_data {
struct platform_device * children [ BUTTON_TYPES ] ;
} ;
/*
* Get the Nth GPIO number from the ACPI object .
*/
static int soc_button_lookup_gpio ( struct device * dev , int acpi_index )
{
struct gpio_desc * desc ;
int gpio ;
2014-10-23 19:53:13 +04:00
desc = gpiod_get_index ( dev , KBUILD_MODNAME , acpi_index , GPIOD_ASIS ) ;
2014-03-31 10:12:00 +04:00
if ( IS_ERR ( desc ) )
return PTR_ERR ( desc ) ;
gpio = desc_to_gpio ( desc ) ;
gpiod_put ( desc ) ;
return gpio ;
}
static struct platform_device *
2014-09-22 21:31:14 +04:00
soc_button_device_create ( struct platform_device * pdev ,
2014-03-31 10:12:00 +04:00
const struct soc_button_info * button_info ,
bool autorepeat )
{
const struct soc_button_info * info ;
struct platform_device * pd ;
struct gpio_keys_button * gpio_keys ;
struct gpio_keys_platform_data * gpio_keys_pdata ;
int n_buttons = 0 ;
int gpio ;
int error ;
gpio_keys_pdata = devm_kzalloc ( & pdev - > dev ,
sizeof ( * gpio_keys_pdata ) +
sizeof ( * gpio_keys ) * MAX_NBUTTONS ,
GFP_KERNEL ) ;
2014-07-26 05:41:39 +04:00
if ( ! gpio_keys_pdata )
return ERR_PTR ( - ENOMEM ) ;
2014-03-31 10:12:00 +04:00
gpio_keys = ( void * ) ( gpio_keys_pdata + 1 ) ;
for ( info = button_info ; info - > name ; info + + ) {
if ( info - > autorepeat ! = autorepeat )
continue ;
gpio = soc_button_lookup_gpio ( & pdev - > dev , info - > acpi_index ) ;
if ( gpio < 0 )
continue ;
gpio_keys [ n_buttons ] . type = info - > event_type ;
gpio_keys [ n_buttons ] . code = info - > event_code ;
gpio_keys [ n_buttons ] . gpio = gpio ;
gpio_keys [ n_buttons ] . active_low = 1 ;
gpio_keys [ n_buttons ] . desc = info - > name ;
gpio_keys [ n_buttons ] . wakeup = info - > wakeup ;
n_buttons + + ;
}
if ( n_buttons = = 0 ) {
error = - ENODEV ;
goto err_free_mem ;
}
gpio_keys_pdata - > buttons = gpio_keys ;
gpio_keys_pdata - > nbuttons = n_buttons ;
gpio_keys_pdata - > rep = autorepeat ;
pd = platform_device_alloc ( " gpio-keys " , PLATFORM_DEVID_AUTO ) ;
if ( ! pd ) {
error = - ENOMEM ;
goto err_free_mem ;
}
error = platform_device_add_data ( pd , gpio_keys_pdata ,
sizeof ( * gpio_keys_pdata ) ) ;
if ( error )
goto err_free_pdev ;
error = platform_device_add ( pd ) ;
if ( error )
goto err_free_pdev ;
return pd ;
err_free_pdev :
platform_device_put ( pd ) ;
err_free_mem :
devm_kfree ( & pdev - > dev , gpio_keys_pdata ) ;
return ERR_PTR ( error ) ;
}
2014-09-22 21:31:14 +04:00
static int soc_button_remove ( struct platform_device * pdev )
2014-03-31 10:12:00 +04:00
{
2014-09-22 21:31:14 +04:00
struct soc_button_data * priv = platform_get_drvdata ( pdev ) ;
2014-03-31 10:12:00 +04:00
int i ;
for ( i = 0 ; i < BUTTON_TYPES ; i + + )
if ( priv - > children [ i ] )
platform_device_unregister ( priv - > children [ i ] ) ;
2014-09-22 21:31:14 +04:00
return 0 ;
2014-03-31 10:12:00 +04:00
}
2014-09-22 21:31:14 +04:00
static int soc_button_probe ( struct platform_device * pdev )
2014-03-31 10:12:00 +04:00
{
2014-09-22 21:31:14 +04:00
struct device * dev = & pdev - > dev ;
const struct acpi_device_id * id ;
struct soc_button_info * button_info ;
2014-03-31 10:12:00 +04:00
struct soc_button_data * priv ;
struct platform_device * pd ;
int i ;
int error ;
2014-09-22 21:31:14 +04:00
id = acpi_match_device ( dev - > driver - > acpi_match_table , dev ) ;
if ( ! id )
return - ENODEV ;
button_info = ( struct soc_button_info * ) id - > driver_data ;
2014-03-31 10:12:00 +04:00
priv = devm_kzalloc ( & pdev - > dev , sizeof ( * priv ) , GFP_KERNEL ) ;
if ( ! priv )
return - ENOMEM ;
2014-09-22 21:31:14 +04:00
platform_set_drvdata ( pdev , priv ) ;
2014-03-31 10:12:00 +04:00
for ( i = 0 ; i < BUTTON_TYPES ; i + + ) {
pd = soc_button_device_create ( pdev , button_info , i = = 0 ) ;
if ( IS_ERR ( pd ) ) {
error = PTR_ERR ( pd ) ;
if ( error ! = - ENODEV ) {
soc_button_remove ( pdev ) ;
return error ;
}
2014-04-23 09:47:13 +04:00
continue ;
2014-03-31 10:12:00 +04:00
}
priv - > children [ i ] = pd ;
}
if ( ! priv - > children [ 0 ] & & ! priv - > children [ 1 ] )
return - ENODEV ;
return 0 ;
}
static struct soc_button_info soc_button_PNP0C40 [ ] = {
{ " power " , 0 , EV_KEY , KEY_POWER , false , true } ,
2015-02-11 02:42:21 +03:00
{ " home " , 1 , EV_KEY , KEY_LEFTMETA , false , true } ,
2014-03-31 10:12:00 +04:00
{ " volume_up " , 2 , EV_KEY , KEY_VOLUMEUP , true , false } ,
{ " volume_down " , 3 , EV_KEY , KEY_VOLUMEDOWN , true , false } ,
{ " rotation_lock " , 4 , EV_SW , SW_ROTATE_LOCK , false , false } ,
{ }
} ;
2014-09-22 21:31:14 +04:00
static const struct acpi_device_id soc_button_acpi_match [ ] = {
{ " PNP0C40 " , ( unsigned long ) soc_button_PNP0C40 } ,
{ }
2014-03-31 10:12:00 +04:00
} ;
2014-09-22 21:31:14 +04:00
MODULE_DEVICE_TABLE ( acpi , soc_button_acpi_match ) ;
static struct platform_driver soc_button_driver = {
. probe = soc_button_probe ,
2014-03-31 10:12:00 +04:00
. remove = soc_button_remove ,
2014-09-22 21:31:14 +04:00
. driver = {
. name = KBUILD_MODNAME ,
. acpi_match_table = ACPI_PTR ( soc_button_acpi_match ) ,
} ,
2014-03-31 10:12:00 +04:00
} ;
2014-09-22 21:31:14 +04:00
module_platform_driver ( soc_button_driver ) ;
2014-03-31 10:12:00 +04:00
MODULE_LICENSE ( " GPL " ) ;