2015-03-21 07:45:59 +03:00
/*
* Copyright ( c ) 2010 - 2011 , Code Aurora Forum . All rights reserved .
* Copyright ( c ) 2014 , Sony Mobile Communications Inc .
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*/
# include <linux/delay.h>
# include <linux/errno.h>
# include <linux/input.h>
# include <linux/interrupt.h>
# include <linux/kernel.h>
# include <linux/log2.h>
# include <linux/module.h>
# include <linux/of.h>
2018-07-31 08:57:33 +03:00
# include <linux/of_device.h>
2015-03-21 07:45:59 +03:00
# include <linux/platform_device.h>
# include <linux/reboot.h>
# include <linux/regmap.h>
# define PON_REV2 0x01
# define PON_RT_STS 0x10
# define PON_KPDPWR_N_SET BIT(0)
2018-07-31 09:05:49 +03:00
# define PON_RESIN_N_SET BIT(1)
2015-03-21 07:45:59 +03:00
# define PON_PS_HOLD_RST_CTL 0x5a
# define PON_PS_HOLD_RST_CTL2 0x5b
# define PON_PS_HOLD_ENABLE BIT(7)
# define PON_PS_HOLD_TYPE_MASK 0x0f
# define PON_PS_HOLD_TYPE_SHUTDOWN 4
# define PON_PS_HOLD_TYPE_HARD_RESET 7
# define PON_PULL_CTL 0x70
# define PON_KPDPWR_PULL_UP BIT(1)
2018-07-31 09:05:49 +03:00
# define PON_RESIN_PULL_UP BIT(0)
2015-03-21 07:45:59 +03:00
# define PON_DBC_CTL 0x71
# define PON_DBC_DELAY_MASK 0x7
2018-07-31 08:57:33 +03:00
struct pm8941_data {
unsigned int pull_up_bit ;
unsigned int status_bit ;
} ;
2015-03-21 07:45:59 +03:00
struct pm8941_pwrkey {
struct device * dev ;
int irq ;
u32 baseaddr ;
struct regmap * regmap ;
struct input_dev * input ;
unsigned int revision ;
struct notifier_block reboot_notifier ;
2018-07-31 08:57:33 +03:00
u32 code ;
const struct pm8941_data * data ;
2015-03-21 07:45:59 +03:00
} ;
static int pm8941_reboot_notify ( struct notifier_block * nb ,
unsigned long code , void * unused )
{
struct pm8941_pwrkey * pwrkey = container_of ( nb , struct pm8941_pwrkey ,
reboot_notifier ) ;
unsigned int enable_reg ;
unsigned int reset_type ;
int error ;
/* PMICs with revision 0 have the enable bit in same register as ctrl */
if ( pwrkey - > revision = = 0 )
enable_reg = PON_PS_HOLD_RST_CTL ;
else
enable_reg = PON_PS_HOLD_RST_CTL2 ;
error = regmap_update_bits ( pwrkey - > regmap ,
pwrkey - > baseaddr + enable_reg ,
PON_PS_HOLD_ENABLE ,
0 ) ;
if ( error )
dev_err ( pwrkey - > dev ,
" unable to clear ps hold reset enable: %d \n " ,
error ) ;
/*
* Updates of PON_PS_HOLD_ENABLE requires 3 sleep cycles between
* writes .
*/
usleep_range ( 100 , 1000 ) ;
switch ( code ) {
case SYS_HALT :
case SYS_POWER_OFF :
reset_type = PON_PS_HOLD_TYPE_SHUTDOWN ;
break ;
case SYS_RESTART :
default :
reset_type = PON_PS_HOLD_TYPE_HARD_RESET ;
break ;
2015-09-14 20:38:31 +03:00
}
2015-03-21 07:45:59 +03:00
error = regmap_update_bits ( pwrkey - > regmap ,
pwrkey - > baseaddr + PON_PS_HOLD_RST_CTL ,
PON_PS_HOLD_TYPE_MASK ,
reset_type ) ;
if ( error )
dev_err ( pwrkey - > dev , " unable to set ps hold reset type: %d \n " ,
error ) ;
error = regmap_update_bits ( pwrkey - > regmap ,
pwrkey - > baseaddr + enable_reg ,
PON_PS_HOLD_ENABLE ,
PON_PS_HOLD_ENABLE ) ;
if ( error )
dev_err ( pwrkey - > dev , " unable to re-set enable: %d \n " , error ) ;
return NOTIFY_DONE ;
}
static irqreturn_t pm8941_pwrkey_irq ( int irq , void * _data )
{
struct pm8941_pwrkey * pwrkey = _data ;
unsigned int sts ;
int error ;
error = regmap_read ( pwrkey - > regmap ,
pwrkey - > baseaddr + PON_RT_STS , & sts ) ;
if ( error )
return IRQ_HANDLED ;
2018-07-31 08:57:33 +03:00
input_report_key ( pwrkey - > input , pwrkey - > code ,
sts & pwrkey - > data - > status_bit ) ;
2015-03-21 07:45:59 +03:00
input_sync ( pwrkey - > input ) ;
return IRQ_HANDLED ;
}
static int __maybe_unused pm8941_pwrkey_suspend ( struct device * dev )
{
struct pm8941_pwrkey * pwrkey = dev_get_drvdata ( dev ) ;
if ( device_may_wakeup ( dev ) )
enable_irq_wake ( pwrkey - > irq ) ;
return 0 ;
}
static int __maybe_unused pm8941_pwrkey_resume ( struct device * dev )
{
struct pm8941_pwrkey * pwrkey = dev_get_drvdata ( dev ) ;
if ( device_may_wakeup ( dev ) )
disable_irq_wake ( pwrkey - > irq ) ;
return 0 ;
}
static SIMPLE_DEV_PM_OPS ( pm8941_pwr_key_pm_ops ,
pm8941_pwrkey_suspend , pm8941_pwrkey_resume ) ;
static int pm8941_pwrkey_probe ( struct platform_device * pdev )
{
struct pm8941_pwrkey * pwrkey ;
bool pull_up ;
2018-07-31 08:57:33 +03:00
struct device * parent ;
2015-03-21 07:45:59 +03:00
u32 req_delay ;
int error ;
if ( of_property_read_u32 ( pdev - > dev . of_node , " debounce " , & req_delay ) )
req_delay = 15625 ;
if ( req_delay > 2000000 | | req_delay = = 0 ) {
dev_err ( & pdev - > dev , " invalid debounce time: %u \n " , req_delay ) ;
return - EINVAL ;
}
pull_up = of_property_read_bool ( pdev - > dev . of_node , " bias-pull-up " ) ;
pwrkey = devm_kzalloc ( & pdev - > dev , sizeof ( * pwrkey ) , GFP_KERNEL ) ;
if ( ! pwrkey )
return - ENOMEM ;
pwrkey - > dev = & pdev - > dev ;
2018-07-31 08:57:33 +03:00
pwrkey - > data = of_device_get_match_data ( & pdev - > dev ) ;
2015-03-21 07:45:59 +03:00
2018-07-31 08:57:33 +03:00
parent = pdev - > dev . parent ;
pwrkey - > regmap = dev_get_regmap ( parent , NULL ) ;
2015-03-21 07:45:59 +03:00
if ( ! pwrkey - > regmap ) {
2018-07-31 08:57:33 +03:00
/*
* We failed to get regmap for parent . Let ' s see if we are
* a child of pon node and read regmap and reg from its
* parent .
*/
pwrkey - > regmap = dev_get_regmap ( parent - > parent , NULL ) ;
if ( ! pwrkey - > regmap ) {
dev_err ( & pdev - > dev , " failed to locate regmap \n " ) ;
return - ENODEV ;
}
error = of_property_read_u32 ( parent - > of_node ,
" reg " , & pwrkey - > baseaddr ) ;
} else {
error = of_property_read_u32 ( pdev - > dev . of_node , " reg " ,
& pwrkey - > baseaddr ) ;
2015-03-21 07:45:59 +03:00
}
2018-07-31 08:57:33 +03:00
if ( error )
return error ;
2015-03-21 07:45:59 +03:00
pwrkey - > irq = platform_get_irq ( pdev , 0 ) ;
if ( pwrkey - > irq < 0 ) {
dev_err ( & pdev - > dev , " failed to get irq \n " ) ;
return pwrkey - > irq ;
}
error = regmap_read ( pwrkey - > regmap , pwrkey - > baseaddr + PON_REV2 ,
& pwrkey - > revision ) ;
if ( error ) {
dev_err ( & pdev - > dev , " failed to set debounce: %d \n " , error ) ;
return error ;
}
2018-07-31 08:57:33 +03:00
error = of_property_read_u32 ( pdev - > dev . of_node , " linux,code " ,
& pwrkey - > code ) ;
if ( error ) {
dev_dbg ( & pdev - > dev ,
" no linux,code assuming power (%d) \n " , error ) ;
pwrkey - > code = KEY_POWER ;
}
2015-03-21 07:45:59 +03:00
pwrkey - > input = devm_input_allocate_device ( & pdev - > dev ) ;
if ( ! pwrkey - > input ) {
dev_dbg ( & pdev - > dev , " unable to allocate input device \n " ) ;
return - ENOMEM ;
}
2018-07-31 08:57:33 +03:00
input_set_capability ( pwrkey - > input , EV_KEY , pwrkey - > code ) ;
2015-03-21 07:45:59 +03:00
pwrkey - > input - > name = " pm8941_pwrkey " ;
pwrkey - > input - > phys = " pm8941_pwrkey/input0 " ;
req_delay = ( req_delay < < 6 ) / USEC_PER_SEC ;
req_delay = ilog2 ( req_delay ) ;
error = regmap_update_bits ( pwrkey - > regmap ,
pwrkey - > baseaddr + PON_DBC_CTL ,
PON_DBC_DELAY_MASK ,
req_delay ) ;
if ( error ) {
dev_err ( & pdev - > dev , " failed to set debounce: %d \n " , error ) ;
return error ;
}
error = regmap_update_bits ( pwrkey - > regmap ,
pwrkey - > baseaddr + PON_PULL_CTL ,
2018-07-31 08:57:33 +03:00
pwrkey - > data - > pull_up_bit ,
pull_up ? pwrkey - > data - > pull_up_bit : 0 ) ;
2015-03-21 07:45:59 +03:00
if ( error ) {
dev_err ( & pdev - > dev , " failed to set pull: %d \n " , error ) ;
return error ;
}
error = devm_request_threaded_irq ( & pdev - > dev , pwrkey - > irq ,
NULL , pm8941_pwrkey_irq ,
IRQF_ONESHOT ,
" pm8941_pwrkey " , pwrkey ) ;
if ( error ) {
dev_err ( & pdev - > dev , " failed requesting IRQ: %d \n " , error ) ;
return error ;
}
error = input_register_device ( pwrkey - > input ) ;
if ( error ) {
dev_err ( & pdev - > dev , " failed to register input device: %d \n " ,
error ) ;
return error ;
}
pwrkey - > reboot_notifier . notifier_call = pm8941_reboot_notify ,
error = register_reboot_notifier ( & pwrkey - > reboot_notifier ) ;
if ( error ) {
dev_err ( & pdev - > dev , " failed to register reboot notifier: %d \n " ,
error ) ;
return error ;
}
platform_set_drvdata ( pdev , pwrkey ) ;
device_init_wakeup ( & pdev - > dev , 1 ) ;
return 0 ;
}
static int pm8941_pwrkey_remove ( struct platform_device * pdev )
{
struct pm8941_pwrkey * pwrkey = platform_get_drvdata ( pdev ) ;
unregister_reboot_notifier ( & pwrkey - > reboot_notifier ) ;
return 0 ;
}
2018-07-31 08:57:33 +03:00
static const struct pm8941_data pwrkey_data = {
. pull_up_bit = PON_KPDPWR_PULL_UP ,
. status_bit = PON_KPDPWR_N_SET ,
} ;
2018-07-31 09:05:49 +03:00
static const struct pm8941_data resin_data = {
. pull_up_bit = PON_RESIN_PULL_UP ,
. status_bit = PON_RESIN_N_SET ,
} ;
2015-03-21 07:45:59 +03:00
static const struct of_device_id pm8941_pwr_key_id_table [ ] = {
2018-07-31 08:57:33 +03:00
{ . compatible = " qcom,pm8941-pwrkey " , . data = & pwrkey_data } ,
2018-07-31 09:05:49 +03:00
{ . compatible = " qcom,pm8941-resin " , . data = & resin_data } ,
2015-03-21 07:45:59 +03:00
{ }
} ;
MODULE_DEVICE_TABLE ( of , pm8941_pwr_key_id_table ) ;
static struct platform_driver pm8941_pwrkey_driver = {
. probe = pm8941_pwrkey_probe ,
. remove = pm8941_pwrkey_remove ,
. driver = {
. name = " pm8941-pwrkey " ,
. pm = & pm8941_pwr_key_pm_ops ,
. of_match_table = of_match_ptr ( pm8941_pwr_key_id_table ) ,
} ,
} ;
module_platform_driver ( pm8941_pwrkey_driver ) ;
MODULE_DESCRIPTION ( " PM8941 Power Key driver " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;