2018-11-23 11:44:36 +03:00
// SPDX-License-Identifier: GPL-2.0
# include <linux/bitops.h>
# include <linux/interrupt.h>
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/of.h>
# include <linux/property.h>
# include <linux/platform_device.h>
# include <linux/regmap.h>
# include <linux/watchdog.h>
2022-06-29 11:48:15 +03:00
# define PON_POFF_REASON1 0x0c
# define PON_POFF_REASON1_PMIC_WD BIT(2)
# define PON_POFF_REASON2 0x0d
# define PON_POFF_REASON2_UVLO BIT(5)
# define PON_POFF_REASON2_OTST3 BIT(6)
2018-11-23 11:44:36 +03:00
# define PON_INT_RT_STS 0x10
# define PMIC_WD_BARK_STS_BIT BIT(6)
# define PON_PMIC_WD_RESET_S1_TIMER 0x54
# define PON_PMIC_WD_RESET_S2_TIMER 0x55
# define PON_PMIC_WD_RESET_S2_CTL 0x56
# define RESET_TYPE_WARM 0x01
# define RESET_TYPE_SHUTDOWN 0x04
# define RESET_TYPE_HARD 0x07
# define PON_PMIC_WD_RESET_S2_CTL2 0x57
# define S2_RESET_EN_BIT BIT(7)
# define PON_PMIC_WD_RESET_PET 0x58
# define WATCHDOG_PET_BIT BIT(0)
# define PM8916_WDT_DEFAULT_TIMEOUT 32
# define PM8916_WDT_MIN_TIMEOUT 1
# define PM8916_WDT_MAX_TIMEOUT 127
struct pm8916_wdt {
struct regmap * regmap ;
struct watchdog_device wdev ;
u32 baseaddr ;
} ;
static int pm8916_wdt_start ( struct watchdog_device * wdev )
{
struct pm8916_wdt * wdt = watchdog_get_drvdata ( wdev ) ;
return regmap_update_bits ( wdt - > regmap ,
wdt - > baseaddr + PON_PMIC_WD_RESET_S2_CTL2 ,
S2_RESET_EN_BIT , S2_RESET_EN_BIT ) ;
}
static int pm8916_wdt_stop ( struct watchdog_device * wdev )
{
struct pm8916_wdt * wdt = watchdog_get_drvdata ( wdev ) ;
return regmap_update_bits ( wdt - > regmap ,
wdt - > baseaddr + PON_PMIC_WD_RESET_S2_CTL2 ,
S2_RESET_EN_BIT , 0 ) ;
}
static int pm8916_wdt_ping ( struct watchdog_device * wdev )
{
struct pm8916_wdt * wdt = watchdog_get_drvdata ( wdev ) ;
2022-06-29 11:48:14 +03:00
return regmap_write ( wdt - > regmap , wdt - > baseaddr + PON_PMIC_WD_RESET_PET ,
WATCHDOG_PET_BIT ) ;
2018-11-23 11:44:36 +03:00
}
static int pm8916_wdt_configure_timers ( struct watchdog_device * wdev )
{
struct pm8916_wdt * wdt = watchdog_get_drvdata ( wdev ) ;
int err ;
err = regmap_write ( wdt - > regmap ,
wdt - > baseaddr + PON_PMIC_WD_RESET_S1_TIMER ,
wdev - > timeout - wdev - > pretimeout ) ;
if ( err )
return err ;
return regmap_write ( wdt - > regmap ,
wdt - > baseaddr + PON_PMIC_WD_RESET_S2_TIMER ,
wdev - > pretimeout ) ;
}
static int pm8916_wdt_set_timeout ( struct watchdog_device * wdev ,
unsigned int timeout )
{
wdev - > timeout = timeout ;
return pm8916_wdt_configure_timers ( wdev ) ;
}
static int pm8916_wdt_set_pretimeout ( struct watchdog_device * wdev ,
unsigned int pretimeout )
{
wdev - > pretimeout = pretimeout ;
return pm8916_wdt_configure_timers ( wdev ) ;
}
static irqreturn_t pm8916_wdt_isr ( int irq , void * arg )
{
struct pm8916_wdt * wdt = arg ;
int err , sts ;
err = regmap_read ( wdt - > regmap , wdt - > baseaddr + PON_INT_RT_STS , & sts ) ;
if ( err )
return IRQ_HANDLED ;
if ( sts & PMIC_WD_BARK_STS_BIT )
watchdog_notify_pretimeout ( & wdt - > wdev ) ;
return IRQ_HANDLED ;
}
static const struct watchdog_info pm8916_wdt_ident = {
2022-06-29 11:48:15 +03:00
. options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE |
WDIOF_OVERHEAT | WDIOF_CARDRESET | WDIOF_POWERUNDER ,
2018-11-23 11:44:36 +03:00
. identity = " QCOM PM8916 PON WDT " ,
} ;
static const struct watchdog_info pm8916_wdt_pt_ident = {
. options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE |
2022-06-29 11:48:15 +03:00
WDIOF_OVERHEAT | WDIOF_CARDRESET | WDIOF_POWERUNDER |
2018-11-23 11:44:36 +03:00
WDIOF_PRETIMEOUT ,
. identity = " QCOM PM8916 PON WDT " ,
} ;
static const struct watchdog_ops pm8916_wdt_ops = {
. owner = THIS_MODULE ,
. start = pm8916_wdt_start ,
. stop = pm8916_wdt_stop ,
. ping = pm8916_wdt_ping ,
. set_timeout = pm8916_wdt_set_timeout ,
. set_pretimeout = pm8916_wdt_set_pretimeout ,
} ;
static int pm8916_wdt_probe ( struct platform_device * pdev )
{
2019-04-09 20:23:49 +03:00
struct device * dev = & pdev - > dev ;
2018-11-23 11:44:36 +03:00
struct pm8916_wdt * wdt ;
struct device * parent ;
2022-06-29 11:48:16 +03:00
unsigned int val ;
2018-11-23 11:44:36 +03:00
int err , irq ;
2022-06-29 11:48:15 +03:00
u8 poff [ 2 ] ;
2018-11-23 11:44:36 +03:00
2019-04-09 20:23:49 +03:00
wdt = devm_kzalloc ( dev , sizeof ( * wdt ) , GFP_KERNEL ) ;
2018-11-23 11:44:36 +03:00
if ( ! wdt )
return - ENOMEM ;
2019-04-09 20:23:49 +03:00
parent = dev - > parent ;
2018-11-23 11:44:36 +03:00
/*
* The pm8916 - pon - wdt is a child of the pon device , which is a child
* of the pm8916 mfd device . We want access to the pm8916 registers .
* Retrieve regmap from pm8916 ( parent - > parent ) and base address
* from pm8916 - pon ( pon ) .
*/
wdt - > regmap = dev_get_regmap ( parent - > parent , NULL ) ;
if ( ! wdt - > regmap ) {
2019-04-09 20:23:49 +03:00
dev_err ( dev , " failed to locate regmap \n " ) ;
2018-11-23 11:44:36 +03:00
return - ENODEV ;
}
err = device_property_read_u32 ( parent , " reg " , & wdt - > baseaddr ) ;
if ( err ) {
2019-04-09 20:23:49 +03:00
dev_err ( dev , " failed to get pm8916-pon address \n " ) ;
2018-11-23 11:44:36 +03:00
return err ;
}
irq = platform_get_irq ( pdev , 0 ) ;
if ( irq > 0 ) {
2019-09-06 23:30:53 +03:00
err = devm_request_irq ( dev , irq , pm8916_wdt_isr , 0 ,
" pm8916_wdt " , wdt ) ;
if ( err )
return err ;
wdt - > wdev . info = & pm8916_wdt_pt_ident ;
} else {
if ( irq = = - EPROBE_DEFER )
return - EPROBE_DEFER ;
wdt - > wdev . info = & pm8916_wdt_ident ;
2018-11-23 11:44:36 +03:00
}
2022-06-29 11:48:15 +03:00
err = regmap_bulk_read ( wdt - > regmap , wdt - > baseaddr + PON_POFF_REASON1 ,
& poff , ARRAY_SIZE ( poff ) ) ;
if ( err ) {
dev_err ( dev , " failed to read POFF reason: %d \n " , err ) ;
return err ;
}
dev_dbg ( dev , " POFF reason: %#x %#x \n " , poff [ 0 ] , poff [ 1 ] ) ;
if ( poff [ 0 ] & PON_POFF_REASON1_PMIC_WD )
wdt - > wdev . bootstatus | = WDIOF_CARDRESET ;
if ( poff [ 1 ] & PON_POFF_REASON2_UVLO )
wdt - > wdev . bootstatus | = WDIOF_POWERUNDER ;
if ( poff [ 1 ] & PON_POFF_REASON2_OTST3 )
wdt - > wdev . bootstatus | = WDIOF_OVERHEAT ;
2022-06-29 11:48:16 +03:00
err = regmap_read ( wdt - > regmap , wdt - > baseaddr + PON_PMIC_WD_RESET_S2_CTL2 ,
& val ) ;
if ( err ) {
dev_err ( dev , " failed to check if watchdog is active: %d \n " , err ) ;
return err ;
}
if ( val & S2_RESET_EN_BIT )
set_bit ( WDOG_HW_RUNNING , & wdt - > wdev . status ) ;
2018-11-23 11:44:36 +03:00
/* Configure watchdog to hard-reset mode */
err = regmap_write ( wdt - > regmap ,
wdt - > baseaddr + PON_PMIC_WD_RESET_S2_CTL ,
RESET_TYPE_HARD ) ;
if ( err ) {
2019-04-09 20:23:49 +03:00
dev_err ( dev , " failed configure watchdog \n " ) ;
2018-11-23 11:44:36 +03:00
return err ;
}
wdt - > wdev . ops = & pm8916_wdt_ops ,
2019-04-09 20:23:49 +03:00
wdt - > wdev . parent = dev ;
2018-11-23 11:44:36 +03:00
wdt - > wdev . min_timeout = PM8916_WDT_MIN_TIMEOUT ;
wdt - > wdev . max_timeout = PM8916_WDT_MAX_TIMEOUT ;
wdt - > wdev . timeout = PM8916_WDT_DEFAULT_TIMEOUT ;
wdt - > wdev . pretimeout = 0 ;
watchdog_set_drvdata ( & wdt - > wdev , wdt ) ;
2020-02-11 18:38:03 +03:00
platform_set_drvdata ( pdev , wdt ) ;
2018-11-23 11:44:36 +03:00
2019-04-09 20:23:49 +03:00
watchdog_init_timeout ( & wdt - > wdev , 0 , dev ) ;
2018-11-23 11:44:36 +03:00
pm8916_wdt_configure_timers ( & wdt - > wdev ) ;
2019-04-09 20:23:49 +03:00
return devm_watchdog_register_device ( dev , & wdt - > wdev ) ;
2018-11-23 11:44:36 +03:00
}
2020-02-11 18:38:03 +03:00
static int __maybe_unused pm8916_wdt_suspend ( struct device * dev )
{
struct pm8916_wdt * wdt = dev_get_drvdata ( dev ) ;
if ( watchdog_active ( & wdt - > wdev ) )
return pm8916_wdt_stop ( & wdt - > wdev ) ;
return 0 ;
}
static int __maybe_unused pm8916_wdt_resume ( struct device * dev )
{
struct pm8916_wdt * wdt = dev_get_drvdata ( dev ) ;
if ( watchdog_active ( & wdt - > wdev ) )
return pm8916_wdt_start ( & wdt - > wdev ) ;
return 0 ;
}
static SIMPLE_DEV_PM_OPS ( pm8916_wdt_pm_ops , pm8916_wdt_suspend ,
pm8916_wdt_resume ) ;
2018-11-23 11:44:36 +03:00
static const struct of_device_id pm8916_wdt_id_table [ ] = {
{ . compatible = " qcom,pm8916-wdt " } ,
{ }
} ;
MODULE_DEVICE_TABLE ( of , pm8916_wdt_id_table ) ;
static struct platform_driver pm8916_wdt_driver = {
. probe = pm8916_wdt_probe ,
. driver = {
. name = " pm8916-wdt " ,
2023-08-11 09:27:07 +03:00
. of_match_table = pm8916_wdt_id_table ,
2020-02-11 18:38:03 +03:00
. pm = & pm8916_wdt_pm_ops ,
2018-11-23 11:44:36 +03:00
} ,
} ;
module_platform_driver ( pm8916_wdt_driver ) ;
MODULE_AUTHOR ( " Loic Poulain <loic.poulain@linaro.org> " ) ;
MODULE_DESCRIPTION ( " Qualcomm pm8916 watchdog driver " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;