2019-06-04 10:11:33 +02:00
// SPDX-License-Identifier: GPL-2.0-only
2016-06-09 18:59:05 +05:30
/*
* Maxim MAX77620 Watchdog Driver
*
* Copyright ( C ) 2016 NVIDIA CORPORATION . All rights reserved .
2022-02-23 18:59:06 +01:00
* Copyright ( C ) 2022 Luca Ceresoli
2016-06-09 18:59:05 +05:30
*
* Author : Laxman Dewangan < ldewangan @ nvidia . com >
2022-02-23 18:59:06 +01:00
* Author : Luca Ceresoli < luca @ lucaceresoli . net >
2016-06-09 18:59:05 +05:30
*/
# include <linux/err.h>
# include <linux/init.h>
# include <linux/kernel.h>
# include <linux/module.h>
2018-06-19 22:47:28 -07:00
# include <linux/mod_devicetable.h>
2016-06-09 18:59:05 +05:30
# include <linux/mfd/max77620.h>
2022-02-23 18:59:06 +01:00
# include <linux/mfd/max77714.h>
2016-06-09 18:59:05 +05:30
# include <linux/platform_device.h>
# include <linux/regmap.h>
# include <linux/slab.h>
# include <linux/watchdog.h>
static bool nowayout = WATCHDOG_NOWAYOUT ;
2022-02-23 18:59:06 +01:00
/**
* struct max77620_variant - Data specific to a chip variant
* @ wdt_info : watchdog descriptor
* @ reg_onoff_cnfg2 : ONOFF_CNFG2 register offset
* @ reg_cnfg_glbl2 : CNFG_GLBL2 register offset
* @ reg_cnfg_glbl3 : CNFG_GLBL3 register offset
* @ wdtc_mask : WDTC bit mask in CNFG_GLBL3 ( = bits to update to ping the watchdog )
* @ bit_wd_rst_wk : WD_RST_WK bit offset within ONOFF_CNFG2
* @ cnfg_glbl2_cfg_bits : configuration bits to enable in CNFG_GLBL2 register
*/
struct max77620_variant {
u8 reg_onoff_cnfg2 ;
u8 reg_cnfg_glbl2 ;
u8 reg_cnfg_glbl3 ;
u8 wdtc_mask ;
u8 bit_wd_rst_wk ;
u8 cnfg_glbl2_cfg_bits ;
} ;
2016-06-09 18:59:05 +05:30
struct max77620_wdt {
struct device * dev ;
struct regmap * rmap ;
2022-02-23 18:59:06 +01:00
const struct max77620_variant * drv_data ;
2016-06-09 18:59:05 +05:30
struct watchdog_device wdt_dev ;
} ;
2022-02-23 18:59:06 +01:00
static const struct max77620_variant max77620_wdt_data = {
. reg_onoff_cnfg2 = MAX77620_REG_ONOFFCNFG2 ,
. reg_cnfg_glbl2 = MAX77620_REG_CNFGGLBL2 ,
. reg_cnfg_glbl3 = MAX77620_REG_CNFGGLBL3 ,
. wdtc_mask = MAX77620_WDTC_MASK ,
. bit_wd_rst_wk = MAX77620_ONOFFCNFG2_WD_RST_WK ,
/* Set WDT clear in OFF and sleep mode */
. cnfg_glbl2_cfg_bits = MAX77620_WDTSLPC | MAX77620_WDTOFFC ,
} ;
static const struct max77620_variant max77714_wdt_data = {
. reg_onoff_cnfg2 = MAX77714_CNFG2_ONOFF ,
. reg_cnfg_glbl2 = MAX77714_CNFG_GLBL2 ,
. reg_cnfg_glbl3 = MAX77714_CNFG_GLBL3 ,
. wdtc_mask = MAX77714_WDTC ,
. bit_wd_rst_wk = MAX77714_WD_RST_WK ,
/* Set WDT clear in sleep mode (there is no WDTOFFC on MAX77714) */
. cnfg_glbl2_cfg_bits = MAX77714_WDTSLPC ,
} ;
2016-06-09 18:59:05 +05:30
static int max77620_wdt_start ( struct watchdog_device * wdt_dev )
{
struct max77620_wdt * wdt = watchdog_get_drvdata ( wdt_dev ) ;
2022-02-23 18:59:06 +01:00
return regmap_update_bits ( wdt - > rmap , wdt - > drv_data - > reg_cnfg_glbl2 ,
2016-06-09 18:59:05 +05:30
MAX77620_WDTEN , MAX77620_WDTEN ) ;
}
static int max77620_wdt_stop ( struct watchdog_device * wdt_dev )
{
struct max77620_wdt * wdt = watchdog_get_drvdata ( wdt_dev ) ;
2022-02-23 18:59:06 +01:00
return regmap_update_bits ( wdt - > rmap , wdt - > drv_data - > reg_cnfg_glbl2 ,
2016-06-09 18:59:05 +05:30
MAX77620_WDTEN , 0 ) ;
}
static int max77620_wdt_ping ( struct watchdog_device * wdt_dev )
{
struct max77620_wdt * wdt = watchdog_get_drvdata ( wdt_dev ) ;
2022-02-23 18:59:06 +01:00
return regmap_update_bits ( wdt - > rmap , wdt - > drv_data - > reg_cnfg_glbl3 ,
wdt - > drv_data - > wdtc_mask , 0x1 ) ;
2016-06-09 18:59:05 +05:30
}
static int max77620_wdt_set_timeout ( struct watchdog_device * wdt_dev ,
unsigned int timeout )
{
struct max77620_wdt * wdt = watchdog_get_drvdata ( wdt_dev ) ;
unsigned int wdt_timeout ;
u8 regval ;
int ret ;
switch ( timeout ) {
case 0 . . . 2 :
regval = MAX77620_TWD_2s ;
wdt_timeout = 2 ;
break ;
case 3 . . . 16 :
regval = MAX77620_TWD_16s ;
wdt_timeout = 16 ;
break ;
case 17 . . . 64 :
regval = MAX77620_TWD_64s ;
wdt_timeout = 64 ;
break ;
default :
regval = MAX77620_TWD_128s ;
wdt_timeout = 128 ;
break ;
}
2022-02-23 18:59:07 +01:00
/*
* " If the value of TWD needs to be changed, clear the system
* watchdog timer first [ . . . ] , then change the value of TWD . "
* ( MAX77714 datasheet but applies to MAX77620 too )
*/
2022-02-23 18:59:06 +01:00
ret = regmap_update_bits ( wdt - > rmap , wdt - > drv_data - > reg_cnfg_glbl3 ,
wdt - > drv_data - > wdtc_mask , 0x1 ) ;
2016-06-09 18:59:05 +05:30
if ( ret < 0 )
return ret ;
2022-02-23 18:59:06 +01:00
ret = regmap_update_bits ( wdt - > rmap , wdt - > drv_data - > reg_cnfg_glbl2 ,
2016-06-09 18:59:05 +05:30
MAX77620_TWD_MASK , regval ) ;
if ( ret < 0 )
return ret ;
wdt_dev - > timeout = wdt_timeout ;
return 0 ;
}
static const struct watchdog_info max77620_wdt_info = {
. identity = " max77620-watchdog " ,
. options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE ,
} ;
static const struct watchdog_ops max77620_wdt_ops = {
. start = max77620_wdt_start ,
. stop = max77620_wdt_stop ,
. ping = max77620_wdt_ping ,
. set_timeout = max77620_wdt_set_timeout ,
} ;
static int max77620_wdt_probe ( struct platform_device * pdev )
{
2022-02-23 18:59:06 +01:00
const struct platform_device_id * id = platform_get_device_id ( pdev ) ;
2019-04-09 10:23:39 -07:00
struct device * dev = & pdev - > dev ;
2016-06-09 18:59:05 +05:30
struct max77620_wdt * wdt ;
struct watchdog_device * wdt_dev ;
unsigned int regval ;
int ret ;
2019-04-09 10:23:39 -07:00
wdt = devm_kzalloc ( dev , sizeof ( * wdt ) , GFP_KERNEL ) ;
2016-06-09 18:59:05 +05:30
if ( ! wdt )
return - ENOMEM ;
2019-04-09 10:23:39 -07:00
wdt - > dev = dev ;
2022-02-23 18:59:06 +01:00
wdt - > drv_data = ( const struct max77620_variant * ) id - > driver_data ;
2019-04-09 10:23:39 -07:00
wdt - > rmap = dev_get_regmap ( dev - > parent , NULL ) ;
2016-06-09 18:59:05 +05:30
if ( ! wdt - > rmap ) {
dev_err ( wdt - > dev , " Failed to get parent regmap \n " ) ;
return - ENODEV ;
}
wdt_dev = & wdt - > wdt_dev ;
wdt_dev - > info = & max77620_wdt_info ;
wdt_dev - > ops = & max77620_wdt_ops ;
wdt_dev - > min_timeout = 2 ;
wdt_dev - > max_timeout = 128 ;
wdt_dev - > max_hw_heartbeat_ms = 128 * 1000 ;
platform_set_drvdata ( pdev , wdt ) ;
/* Enable WD_RST_WK - WDT expire results in a restart */
2022-02-23 18:59:06 +01:00
ret = regmap_update_bits ( wdt - > rmap , wdt - > drv_data - > reg_onoff_cnfg2 ,
wdt - > drv_data - > bit_wd_rst_wk ,
wdt - > drv_data - > bit_wd_rst_wk ) ;
2016-06-09 18:59:05 +05:30
if ( ret < 0 ) {
dev_err ( wdt - > dev , " Failed to set WD_RST_WK: %d \n " , ret ) ;
return ret ;
}
2022-02-23 18:59:06 +01:00
/* Set the "auto WDT clear" bits available on the chip */
ret = regmap_update_bits ( wdt - > rmap , wdt - > drv_data - > reg_cnfg_glbl2 ,
wdt - > drv_data - > cnfg_glbl2_cfg_bits ,
wdt - > drv_data - > cnfg_glbl2_cfg_bits ) ;
2016-06-09 18:59:05 +05:30
if ( ret < 0 ) {
dev_err ( wdt - > dev , " Failed to set WDT OFF mode: %d \n " , ret ) ;
return ret ;
}
/* Check if WDT running and if yes then set flags properly */
2022-02-23 18:59:06 +01:00
ret = regmap_read ( wdt - > rmap , wdt - > drv_data - > reg_cnfg_glbl2 , & regval ) ;
2016-06-09 18:59:05 +05:30
if ( ret < 0 ) {
dev_err ( wdt - > dev , " Failed to read WDT CFG register: %d \n " , ret ) ;
return ret ;
}
switch ( regval & MAX77620_TWD_MASK ) {
case MAX77620_TWD_2s :
wdt_dev - > timeout = 2 ;
break ;
case MAX77620_TWD_16s :
wdt_dev - > timeout = 16 ;
break ;
case MAX77620_TWD_64s :
wdt_dev - > timeout = 64 ;
break ;
default :
wdt_dev - > timeout = 128 ;
break ;
}
if ( regval & MAX77620_WDTEN )
set_bit ( WDOG_HW_RUNNING , & wdt_dev - > status ) ;
watchdog_set_nowayout ( wdt_dev , nowayout ) ;
watchdog_set_drvdata ( wdt_dev , wdt ) ;
2019-04-09 10:23:39 -07:00
watchdog_stop_on_unregister ( wdt_dev ) ;
2019-05-18 23:27:36 +02:00
return devm_watchdog_register_device ( dev , wdt_dev ) ;
2016-06-09 18:59:05 +05:30
}
2017-08-13 16:58:28 +05:30
static const struct platform_device_id max77620_wdt_devtype [ ] = {
2022-02-23 18:59:06 +01:00
{ " max77620-watchdog " , ( kernel_ulong_t ) & max77620_wdt_data } ,
{ " max77714-watchdog " , ( kernel_ulong_t ) & max77714_wdt_data } ,
2016-06-09 18:59:05 +05:30
{ } ,
} ;
2016-10-14 12:23:51 -03:00
MODULE_DEVICE_TABLE ( platform , max77620_wdt_devtype ) ;
2016-06-09 18:59:05 +05:30
static struct platform_driver max77620_wdt_driver = {
. driver = {
. name = " max77620-watchdog " ,
} ,
. probe = max77620_wdt_probe ,
. id_table = max77620_wdt_devtype ,
} ;
module_platform_driver ( max77620_wdt_driver ) ;
MODULE_DESCRIPTION ( " Max77620 watchdog timer driver " ) ;
module_param ( nowayout , bool , 0 ) ;
MODULE_PARM_DESC ( nowayout , " Watchdog cannot be stopped once started "
" (default= " __MODULE_STRING ( WATCHDOG_NOWAYOUT ) " ) " ) ;
MODULE_AUTHOR ( " Laxman Dewangan <ldewangan@nvidia.com> " ) ;
2022-02-23 18:59:06 +01:00
MODULE_AUTHOR ( " Luca Ceresoli <luca@lucaceresoli.net> " ) ;
2016-06-09 18:59:05 +05:30
MODULE_LICENSE ( " GPL v2 " ) ;