2018-03-16 18:14:11 +03:00
// SPDX-License-Identifier: GPL-2.0+
2015-05-28 16:33:31 +03:00
/*
2016-11-14 11:22:45 +03:00
* Watchdog device driver for DA9062 and DA9061 PMICs
2015-05-28 16:33:31 +03:00
* Copyright ( C ) 2015 Dialog Semiconductor Ltd .
*
*/
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/watchdog.h>
# include <linux/platform_device.h>
# include <linux/uaccess.h>
# include <linux/slab.h>
2020-01-15 19:23:07 +03:00
# include <linux/i2c.h>
2015-05-28 16:33:31 +03:00
# include <linux/delay.h>
# include <linux/jiffies.h>
# include <linux/mfd/da9062/registers.h>
# include <linux/mfd/da9062/core.h>
2020-02-07 10:15:18 +03:00
# include <linux/property.h>
2015-05-28 16:33:31 +03:00
# include <linux/regmap.h>
# include <linux/of.h>
static const unsigned int wdt_timeout [ ] = { 0 , 2 , 4 , 8 , 16 , 32 , 65 , 131 } ;
# define DA9062_TWDSCALE_DISABLE 0
# define DA9062_TWDSCALE_MIN 1
# define DA9062_TWDSCALE_MAX (ARRAY_SIZE(wdt_timeout) - 1)
# define DA9062_WDT_MIN_TIMEOUT wdt_timeout[DA9062_TWDSCALE_MIN]
# define DA9062_WDT_MAX_TIMEOUT wdt_timeout[DA9062_TWDSCALE_MAX]
# define DA9062_WDG_DEFAULT_TIMEOUT wdt_timeout[DA9062_TWDSCALE_MAX-1]
# define DA9062_RESET_PROTECTION_MS 300
struct da9062_watchdog {
struct da9062 * hw ;
struct watchdog_device wdtdev ;
2020-02-07 10:15:18 +03:00
bool use_sw_pm ;
2015-05-28 16:33:31 +03:00
} ;
2020-04-03 16:07:26 +03:00
static unsigned int da9062_wdt_read_timeout ( struct da9062_watchdog * wdt )
{
unsigned int val ;
regmap_read ( wdt - > hw - > regmap , DA9062AA_CONTROL_D , & val ) ;
return wdt_timeout [ val & DA9062AA_TWDSCALE_MASK ] ;
}
2015-05-28 16:33:31 +03:00
static unsigned int da9062_wdt_timeout_to_sel ( unsigned int secs )
{
unsigned int i ;
for ( i = DA9062_TWDSCALE_MIN ; i < = DA9062_TWDSCALE_MAX ; i + + ) {
if ( wdt_timeout [ i ] > = secs )
return i ;
}
return DA9062_TWDSCALE_MAX ;
}
static int da9062_reset_watchdog_timer ( struct da9062_watchdog * wdt )
{
2019-04-08 22:38:35 +03:00
return regmap_update_bits ( wdt - > hw - > regmap , DA9062AA_CONTROL_F ,
DA9062AA_WATCHDOG_MASK ,
DA9062AA_WATCHDOG_MASK ) ;
2015-05-28 16:33:31 +03:00
}
static int da9062_wdt_update_timeout_register ( struct da9062_watchdog * wdt ,
unsigned int regval )
{
struct da9062 * chip = wdt - > hw ;
2017-10-17 18:30:25 +03:00
regmap_update_bits ( chip - > regmap ,
DA9062AA_CONTROL_D ,
DA9062AA_TWDSCALE_MASK ,
DA9062_TWDSCALE_DISABLE ) ;
usleep_range ( 150 , 300 ) ;
2015-05-28 16:33:31 +03:00
return regmap_update_bits ( chip - > regmap ,
DA9062AA_CONTROL_D ,
DA9062AA_TWDSCALE_MASK ,
regval ) ;
}
static int da9062_wdt_start ( struct watchdog_device * wdd )
{
struct da9062_watchdog * wdt = watchdog_get_drvdata ( wdd ) ;
unsigned int selector ;
int ret ;
selector = da9062_wdt_timeout_to_sel ( wdt - > wdtdev . timeout ) ;
ret = da9062_wdt_update_timeout_register ( wdt , selector ) ;
if ( ret )
dev_err ( wdt - > hw - > dev , " Watchdog failed to start (err = %d) \n " ,
ret ) ;
return ret ;
}
static int da9062_wdt_stop ( struct watchdog_device * wdd )
{
struct da9062_watchdog * wdt = watchdog_get_drvdata ( wdd ) ;
int ret ;
ret = regmap_update_bits ( wdt - > hw - > regmap ,
DA9062AA_CONTROL_D ,
DA9062AA_TWDSCALE_MASK ,
DA9062_TWDSCALE_DISABLE ) ;
if ( ret )
dev_err ( wdt - > hw - > dev , " Watchdog failed to stop (err = %d) \n " ,
ret ) ;
return ret ;
}
static int da9062_wdt_ping ( struct watchdog_device * wdd )
{
struct da9062_watchdog * wdt = watchdog_get_drvdata ( wdd ) ;
int ret ;
2021-07-08 11:21:28 +03:00
/*
* Prevent pings from occurring late in system poweroff / reboot sequence
* and possibly locking out restart handler from accessing i2c bus .
*/
if ( system_state > SYSTEM_RUNNING )
return 0 ;
2015-05-28 16:33:31 +03:00
ret = da9062_reset_watchdog_timer ( wdt ) ;
if ( ret )
dev_err ( wdt - > hw - > dev , " Failed to ping the watchdog (err = %d) \n " ,
ret ) ;
return ret ;
}
static int da9062_wdt_set_timeout ( struct watchdog_device * wdd ,
unsigned int timeout )
{
struct da9062_watchdog * wdt = watchdog_get_drvdata ( wdd ) ;
unsigned int selector ;
int ret ;
selector = da9062_wdt_timeout_to_sel ( timeout ) ;
ret = da9062_wdt_update_timeout_register ( wdt , selector ) ;
if ( ret )
dev_err ( wdt - > hw - > dev , " Failed to set watchdog timeout (err = %d) \n " ,
ret ) ;
else
wdd - > timeout = wdt_timeout [ selector ] ;
return ret ;
}
2017-10-17 18:30:22 +03:00
static int da9062_wdt_restart ( struct watchdog_device * wdd , unsigned long action ,
void * data )
{
struct da9062_watchdog * wdt = watchdog_get_drvdata ( wdd ) ;
2020-01-15 19:23:07 +03:00
struct i2c_client * client = to_i2c_client ( wdt - > hw - > dev ) ;
2017-10-17 18:30:22 +03:00
int ret ;
2020-01-15 19:23:07 +03:00
/* Don't use regmap because it is not atomic safe */
ret = i2c_smbus_write_byte_data ( client , DA9062AA_CONTROL_F ,
DA9062AA_SHUTDOWN_MASK ) ;
if ( ret < 0 )
2017-10-17 18:30:22 +03:00
dev_alert ( wdt - > hw - > dev , " Failed to shutdown (err = %d) \n " ,
ret ) ;
/* wait for reset to assert... */
mdelay ( 500 ) ;
return ret ;
}
2015-05-28 16:33:31 +03:00
static const struct watchdog_info da9062_watchdog_info = {
. options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING ,
. identity = " DA9062 WDT " ,
} ;
static const struct watchdog_ops da9062_watchdog_ops = {
. owner = THIS_MODULE ,
. start = da9062_wdt_start ,
. stop = da9062_wdt_stop ,
. ping = da9062_wdt_ping ,
. set_timeout = da9062_wdt_set_timeout ,
2017-10-17 18:30:22 +03:00
. restart = da9062_wdt_restart ,
2015-05-28 16:33:31 +03:00
} ;
2016-11-14 11:22:45 +03:00
static const struct of_device_id da9062_compatible_id_table [ ] = {
{ . compatible = " dlg,da9062-watchdog " , } ,
{ } ,
} ;
MODULE_DEVICE_TABLE ( of , da9062_compatible_id_table ) ;
2015-05-28 16:33:31 +03:00
static int da9062_wdt_probe ( struct platform_device * pdev )
{
2019-04-08 22:38:35 +03:00
struct device * dev = & pdev - > dev ;
2020-04-03 16:07:26 +03:00
unsigned int timeout ;
2015-05-28 16:33:31 +03:00
struct da9062 * chip ;
struct da9062_watchdog * wdt ;
2019-04-08 22:38:35 +03:00
chip = dev_get_drvdata ( dev - > parent ) ;
2015-05-28 16:33:31 +03:00
if ( ! chip )
return - EINVAL ;
2019-04-08 22:38:35 +03:00
wdt = devm_kzalloc ( dev , sizeof ( * wdt ) , GFP_KERNEL ) ;
2015-05-28 16:33:31 +03:00
if ( ! wdt )
return - ENOMEM ;
2020-02-07 10:15:18 +03:00
wdt - > use_sw_pm = device_property_present ( dev , " dlg,use-sw-pm " ) ;
2015-05-28 16:33:31 +03:00
wdt - > hw = chip ;
wdt - > wdtdev . info = & da9062_watchdog_info ;
wdt - > wdtdev . ops = & da9062_watchdog_ops ;
wdt - > wdtdev . min_timeout = DA9062_WDT_MIN_TIMEOUT ;
wdt - > wdtdev . max_timeout = DA9062_WDT_MAX_TIMEOUT ;
2017-10-17 18:30:26 +03:00
wdt - > wdtdev . min_hw_heartbeat_ms = DA9062_RESET_PROTECTION_MS ;
2015-05-28 16:33:31 +03:00
wdt - > wdtdev . timeout = DA9062_WDG_DEFAULT_TIMEOUT ;
wdt - > wdtdev . status = WATCHDOG_NOWAYOUT_INIT_STATUS ;
2019-04-08 22:38:35 +03:00
wdt - > wdtdev . parent = dev ;
2015-05-28 16:33:31 +03:00
2017-10-17 18:30:22 +03:00
watchdog_set_restart_priority ( & wdt - > wdtdev , 128 ) ;
2015-05-28 16:33:31 +03:00
watchdog_set_drvdata ( & wdt - > wdtdev , wdt ) ;
2019-11-28 20:19:31 +03:00
dev_set_drvdata ( dev , & wdt - > wdtdev ) ;
2015-05-28 16:33:31 +03:00
2020-04-03 16:07:26 +03:00
timeout = da9062_wdt_read_timeout ( wdt ) ;
if ( timeout )
wdt - > wdtdev . timeout = timeout ;
/* Set timeout from DT value if available */
watchdog_init_timeout ( & wdt - > wdtdev , 0 , dev ) ;
if ( timeout ) {
da9062_wdt_set_timeout ( & wdt - > wdtdev , wdt - > wdtdev . timeout ) ;
set_bit ( WDOG_HW_RUNNING , & wdt - > wdtdev . status ) ;
}
2015-05-28 16:33:31 +03:00
2020-04-03 16:07:26 +03:00
return devm_watchdog_register_device ( dev , & wdt - > wdtdev ) ;
2015-05-28 16:33:31 +03:00
}
2019-11-28 20:19:31 +03:00
static int __maybe_unused da9062_wdt_suspend ( struct device * dev )
{
struct watchdog_device * wdd = dev_get_drvdata ( dev ) ;
2020-02-07 10:15:18 +03:00
struct da9062_watchdog * wdt = watchdog_get_drvdata ( wdd ) ;
if ( ! wdt - > use_sw_pm )
return 0 ;
2019-11-28 20:19:31 +03:00
if ( watchdog_active ( wdd ) )
return da9062_wdt_stop ( wdd ) ;
return 0 ;
}
static int __maybe_unused da9062_wdt_resume ( struct device * dev )
{
struct watchdog_device * wdd = dev_get_drvdata ( dev ) ;
2020-02-07 10:15:18 +03:00
struct da9062_watchdog * wdt = watchdog_get_drvdata ( wdd ) ;
if ( ! wdt - > use_sw_pm )
return 0 ;
2019-11-28 20:19:31 +03:00
if ( watchdog_active ( wdd ) )
return da9062_wdt_start ( wdd ) ;
return 0 ;
}
static SIMPLE_DEV_PM_OPS ( da9062_wdt_pm_ops ,
da9062_wdt_suspend , da9062_wdt_resume ) ;
2015-05-28 16:33:31 +03:00
static struct platform_driver da9062_wdt_driver = {
. probe = da9062_wdt_probe ,
. driver = {
. name = " da9062-watchdog " ,
2019-11-28 20:19:31 +03:00
. pm = & da9062_wdt_pm_ops ,
2016-11-14 11:22:45 +03:00
. of_match_table = da9062_compatible_id_table ,
2015-05-28 16:33:31 +03:00
} ,
} ;
module_platform_driver ( da9062_wdt_driver ) ;
MODULE_AUTHOR ( " S Twiss <stwiss.opensource@diasemi.com> " ) ;
2016-11-14 11:22:45 +03:00
MODULE_DESCRIPTION ( " WDT device driver for Dialog DA9062 and DA9061 " ) ;
2015-05-28 16:33:31 +03:00
MODULE_LICENSE ( " GPL " ) ;
MODULE_ALIAS ( " platform:da9062-watchdog " ) ;