2018-06-19 11:19:14 -07:00
// SPDX-License-Identifier: GPL-2.0+
//
// Driver for the IMX SNVS ON/OFF Power Key
// Copyright (C) 2015 Freescale Semiconductor, Inc. All Rights Reserved.
2015-05-27 00:26:00 +08:00
# include <linux/device.h>
# include <linux/err.h>
# include <linux/init.h>
# include <linux/input.h>
# include <linux/interrupt.h>
# include <linux/io.h>
# include <linux/jiffies.h>
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/of.h>
# include <linux/of_address.h>
# include <linux/platform_device.h>
2019-04-03 15:15:44 -07:00
# include <linux/pm_wakeirq.h>
2015-05-27 00:26:00 +08:00
# include <linux/mfd/syscon.h>
# include <linux/regmap.h>
2019-11-25 12:40:47 -08:00
# define SNVS_HPVIDR1_REG 0xF8
# define SNVS_LPSR_REG 0x4C /* LP Status Register */
# define SNVS_LPCR_REG 0x38 /* LP Control Register */
# define SNVS_HPSR_REG 0x14
# define SNVS_HPSR_BTN BIT(6)
# define SNVS_LPSR_SPO BIT(18)
# define SNVS_LPCR_DEP_EN BIT(5)
2015-05-27 00:26:00 +08:00
2019-11-25 12:40:47 -08:00
# define DEBOUNCE_TIME 30
# define REPEAT_INTERVAL 60
2015-05-27 00:26:00 +08:00
struct pwrkey_drv_data {
struct regmap * snvs ;
int irq ;
int keycode ;
int keystate ; /* 1:pressed */
int wakeup ;
struct timer_list check_timer ;
struct input_dev * input ;
2019-11-25 12:40:47 -08:00
u8 minor_rev ;
2015-05-27 00:26:00 +08:00
} ;
2017-10-24 09:40:57 -07:00
static void imx_imx_snvs_check_for_events ( struct timer_list * t )
2015-05-27 00:26:00 +08:00
{
2017-10-24 09:40:57 -07:00
struct pwrkey_drv_data * pdata = from_timer ( pdata , t , check_timer ) ;
2015-05-27 00:26:00 +08:00
struct input_dev * input = pdata - > input ;
u32 state ;
regmap_read ( pdata - > snvs , SNVS_HPSR_REG , & state ) ;
state = state & SNVS_HPSR_BTN ? 1 : 0 ;
/* only report new event if status changed */
if ( state ^ pdata - > keystate ) {
pdata - > keystate = state ;
input_event ( input , EV_KEY , pdata - > keycode , state ) ;
input_sync ( input ) ;
pm_relax ( pdata - > input - > dev . parent ) ;
}
/* repeat check if pressed long */
if ( state ) {
mod_timer ( & pdata - > check_timer ,
jiffies + msecs_to_jiffies ( REPEAT_INTERVAL ) ) ;
}
}
static irqreturn_t imx_snvs_pwrkey_interrupt ( int irq , void * dev_id )
{
struct platform_device * pdev = dev_id ;
struct pwrkey_drv_data * pdata = platform_get_drvdata ( pdev ) ;
2019-11-25 12:40:47 -08:00
struct input_dev * input = pdata - > input ;
2015-05-27 00:26:00 +08:00
u32 lp_status ;
2019-11-25 12:40:47 -08:00
pm_wakeup_event ( input - > dev . parent , 0 ) ;
2015-05-27 00:26:00 +08:00
regmap_read ( pdata - > snvs , SNVS_LPSR_REG , & lp_status ) ;
2019-11-25 12:40:47 -08:00
if ( lp_status & SNVS_LPSR_SPO ) {
if ( pdata - > minor_rev = = 0 ) {
/*
* The first generation i . MX6 SoCs only sends an
* interrupt on button release . To mimic power - key
* usage , we ' ll prepend a press event .
*/
input_report_key ( input , pdata - > keycode , 1 ) ;
input_sync ( input ) ;
input_report_key ( input , pdata - > keycode , 0 ) ;
input_sync ( input ) ;
pm_relax ( input - > dev . parent ) ;
} else {
mod_timer ( & pdata - > check_timer ,
jiffies + msecs_to_jiffies ( DEBOUNCE_TIME ) ) ;
}
}
2015-05-27 00:26:00 +08:00
/* clear SPO status */
regmap_write ( pdata - > snvs , SNVS_LPSR_REG , SNVS_LPSR_SPO ) ;
return IRQ_HANDLED ;
}
static void imx_snvs_pwrkey_act ( void * pdata )
{
struct pwrkey_drv_data * pd = pdata ;
del_timer_sync ( & pd - > check_timer ) ;
}
static int imx_snvs_pwrkey_probe ( struct platform_device * pdev )
{
2019-12-04 17:56:24 -08:00
struct pwrkey_drv_data * pdata ;
struct input_dev * input ;
2015-05-27 00:26:00 +08:00
struct device_node * np ;
int error ;
2019-11-25 12:40:47 -08:00
u32 vid ;
2015-05-27 00:26:00 +08:00
/* Get SNVS register Page */
np = pdev - > dev . of_node ;
if ( ! np )
return - ENODEV ;
pdata = devm_kzalloc ( & pdev - > dev , sizeof ( * pdata ) , GFP_KERNEL ) ;
if ( ! pdata )
return - ENOMEM ;
2015-10-26 01:45:01 -07:00
pdata - > snvs = syscon_regmap_lookup_by_phandle ( np , " regmap " ) ;
2016-03-08 11:06:39 -08:00
if ( IS_ERR ( pdata - > snvs ) ) {
2015-05-27 00:26:00 +08:00
dev_err ( & pdev - > dev , " Can't get snvs syscon \n " ) ;
2016-03-08 11:06:39 -08:00
return PTR_ERR ( pdata - > snvs ) ;
2015-05-27 00:26:00 +08:00
}
if ( of_property_read_u32 ( np , " linux,keycode " , & pdata - > keycode ) ) {
pdata - > keycode = KEY_POWER ;
dev_warn ( & pdev - > dev , " KEY_POWER without setting in dts \n " ) ;
}
2015-07-15 10:36:37 +08:00
pdata - > wakeup = of_property_read_bool ( np , " wakeup-source " ) ;
2015-05-27 00:26:00 +08:00
pdata - > irq = platform_get_irq ( pdev , 0 ) ;
2019-08-14 10:46:38 -07:00
if ( pdata - > irq < 0 )
2015-05-27 00:26:00 +08:00
return - EINVAL ;
2019-11-25 12:40:47 -08:00
regmap_read ( pdata - > snvs , SNVS_HPVIDR1_REG , & vid ) ;
pdata - > minor_rev = vid & 0xff ;
2015-05-27 00:26:00 +08:00
regmap_update_bits ( pdata - > snvs , SNVS_LPCR_REG , SNVS_LPCR_DEP_EN , SNVS_LPCR_DEP_EN ) ;
/* clear the unexpected interrupt before driver ready */
regmap_write ( pdata - > snvs , SNVS_LPSR_REG , SNVS_LPSR_SPO ) ;
2017-10-24 09:40:57 -07:00
timer_setup ( & pdata - > check_timer , imx_imx_snvs_check_for_events , 0 ) ;
2015-05-27 00:26:00 +08:00
input = devm_input_allocate_device ( & pdev - > dev ) ;
if ( ! input ) {
dev_err ( & pdev - > dev , " failed to allocate the input device \n " ) ;
return - ENOMEM ;
}
input - > name = pdev - > name ;
input - > phys = " snvs-pwrkey/input0 " ;
input - > id . bustype = BUS_HOST ;
input_set_capability ( input , EV_KEY , pdata - > keycode ) ;
/* input customer action to cancel release timer */
error = devm_add_action ( & pdev - > dev , imx_snvs_pwrkey_act , pdata ) ;
if ( error ) {
dev_err ( & pdev - > dev , " failed to register remove action \n " ) ;
return error ;
}
2019-04-03 15:14:44 -07:00
pdata - > input = input ;
platform_set_drvdata ( pdev , pdata ) ;
2015-05-27 00:26:00 +08:00
error = devm_request_irq ( & pdev - > dev , pdata - > irq ,
imx_snvs_pwrkey_interrupt ,
0 , pdev - > name , pdev ) ;
if ( error ) {
dev_err ( & pdev - > dev , " interrupt not available. \n " ) ;
return error ;
}
error = input_register_device ( input ) ;
if ( error < 0 ) {
dev_err ( & pdev - > dev , " failed to register input device \n " ) ;
return error ;
}
device_init_wakeup ( & pdev - > dev , pdata - > wakeup ) ;
2019-04-03 15:15:44 -07:00
error = dev_pm_set_wake_irq ( & pdev - > dev , pdata - > irq ) ;
if ( error )
dev_err ( & pdev - > dev , " irq wake enable failed. \n " ) ;
2015-05-27 00:26:00 +08:00
return 0 ;
}
static const struct of_device_id imx_snvs_pwrkey_ids [ ] = {
{ . compatible = " fsl,sec-v4.0-pwrkey " } ,
{ /* sentinel */ }
} ;
MODULE_DEVICE_TABLE ( of , imx_snvs_pwrkey_ids ) ;
static struct platform_driver imx_snvs_pwrkey_driver = {
. driver = {
. name = " snvs_pwrkey " ,
. of_match_table = imx_snvs_pwrkey_ids ,
} ,
. probe = imx_snvs_pwrkey_probe ,
} ;
module_platform_driver ( imx_snvs_pwrkey_driver ) ;
MODULE_AUTHOR ( " Freescale Semiconductor " ) ;
MODULE_DESCRIPTION ( " i.MX snvs power key Driver " ) ;
MODULE_LICENSE ( " GPL " ) ;