2019-05-27 09:55:01 +03:00
// SPDX-License-Identifier: GPL-2.0-or-later
2014-08-27 21:52:06 +04:00
/*
* MEN 14F 021 P00 Board Management Controller ( BMC ) Watchdog Driver .
*
* Copyright ( C ) 2014 MEN Mikro Elektronik Nuernberg GmbH
*/
# include <linux/kernel.h>
# include <linux/device.h>
# include <linux/module.h>
# include <linux/watchdog.h>
# include <linux/platform_device.h>
# include <linux/i2c.h>
# define DEVNAME "menf21bmc_wdt"
# define BMC_CMD_WD_ON 0x11
# define BMC_CMD_WD_OFF 0x12
# define BMC_CMD_WD_TRIG 0x13
# define BMC_CMD_WD_TIME 0x14
# define BMC_CMD_WD_STATE 0x17
# define BMC_WD_OFF_VAL 0x69
# define BMC_CMD_RST_RSN 0x92
# define BMC_WD_TIMEOUT_MIN 1 /* in sec */
# define BMC_WD_TIMEOUT_MAX 6553 /* in sec */
static bool nowayout = WATCHDOG_NOWAYOUT ;
module_param ( nowayout , bool , 0 ) ;
MODULE_PARM_DESC ( nowayout , " Watchdog cannot be stopped once started (default= "
__MODULE_STRING ( WATCHDOG_NOWAYOUT ) " ) " ) ;
struct menf21bmc_wdt {
struct watchdog_device wdt ;
struct i2c_client * i2c_client ;
} ;
static int menf21bmc_wdt_set_bootstatus ( struct menf21bmc_wdt * data )
{
int rst_rsn ;
rst_rsn = i2c_smbus_read_byte_data ( data - > i2c_client , BMC_CMD_RST_RSN ) ;
if ( rst_rsn < 0 )
return rst_rsn ;
if ( rst_rsn = = 0x02 )
data - > wdt . bootstatus | = WDIOF_CARDRESET ;
else if ( rst_rsn = = 0x05 )
data - > wdt . bootstatus | = WDIOF_EXTERN1 ;
else if ( rst_rsn = = 0x06 )
data - > wdt . bootstatus | = WDIOF_EXTERN2 ;
else if ( rst_rsn = = 0x0A )
data - > wdt . bootstatus | = WDIOF_POWERUNDER ;
return 0 ;
}
static int menf21bmc_wdt_start ( struct watchdog_device * wdt )
{
struct menf21bmc_wdt * drv_data = watchdog_get_drvdata ( wdt ) ;
return i2c_smbus_write_byte ( drv_data - > i2c_client , BMC_CMD_WD_ON ) ;
}
static int menf21bmc_wdt_stop ( struct watchdog_device * wdt )
{
struct menf21bmc_wdt * drv_data = watchdog_get_drvdata ( wdt ) ;
return i2c_smbus_write_byte_data ( drv_data - > i2c_client ,
BMC_CMD_WD_OFF , BMC_WD_OFF_VAL ) ;
}
static int
menf21bmc_wdt_settimeout ( struct watchdog_device * wdt , unsigned int timeout )
{
int ret ;
struct menf21bmc_wdt * drv_data = watchdog_get_drvdata ( wdt ) ;
/*
* BMC Watchdog does have a resolution of 100 ms .
* Watchdog API defines the timeout in seconds , so we have to
* multiply the value .
*/
ret = i2c_smbus_write_word_data ( drv_data - > i2c_client ,
BMC_CMD_WD_TIME , timeout * 10 ) ;
if ( ret < 0 )
return ret ;
wdt - > timeout = timeout ;
return 0 ;
}
static int menf21bmc_wdt_ping ( struct watchdog_device * wdt )
{
struct menf21bmc_wdt * drv_data = watchdog_get_drvdata ( wdt ) ;
return i2c_smbus_write_byte ( drv_data - > i2c_client , BMC_CMD_WD_TRIG ) ;
}
static const struct watchdog_info menf21bmc_wdt_info = {
. options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING ,
. identity = DEVNAME ,
} ;
static const struct watchdog_ops menf21bmc_wdt_ops = {
. owner = THIS_MODULE ,
. start = menf21bmc_wdt_start ,
. stop = menf21bmc_wdt_stop ,
. ping = menf21bmc_wdt_ping ,
. set_timeout = menf21bmc_wdt_settimeout ,
} ;
static int menf21bmc_wdt_probe ( struct platform_device * pdev )
{
2019-04-09 20:23:41 +03:00
struct device * dev = & pdev - > dev ;
2014-08-27 21:52:06 +04:00
int ret , bmc_timeout ;
struct menf21bmc_wdt * drv_data ;
2019-04-09 20:23:41 +03:00
struct i2c_client * i2c_client = to_i2c_client ( dev - > parent ) ;
2014-08-27 21:52:06 +04:00
2019-04-09 20:23:41 +03:00
drv_data = devm_kzalloc ( dev , sizeof ( struct menf21bmc_wdt ) , GFP_KERNEL ) ;
2014-08-27 21:52:06 +04:00
if ( ! drv_data )
return - ENOMEM ;
drv_data - > wdt . ops = & menf21bmc_wdt_ops ;
drv_data - > wdt . info = & menf21bmc_wdt_info ;
drv_data - > wdt . min_timeout = BMC_WD_TIMEOUT_MIN ;
drv_data - > wdt . max_timeout = BMC_WD_TIMEOUT_MAX ;
2019-04-09 20:23:41 +03:00
drv_data - > wdt . parent = dev ;
2014-08-27 21:52:06 +04:00
drv_data - > i2c_client = i2c_client ;
/*
* Get the current wdt timeout value from the BMC because
* the BMC will save the value set before if the system restarts .
*/
bmc_timeout = i2c_smbus_read_word_data ( drv_data - > i2c_client ,
BMC_CMD_WD_TIME ) ;
if ( bmc_timeout < 0 ) {
2019-04-09 20:23:41 +03:00
dev_err ( dev , " failed to get current WDT timeout \n " ) ;
2014-08-27 21:52:06 +04:00
return bmc_timeout ;
}
2019-04-09 20:23:41 +03:00
watchdog_init_timeout ( & drv_data - > wdt , bmc_timeout / 10 , dev ) ;
2014-08-27 21:52:06 +04:00
watchdog_set_nowayout ( & drv_data - > wdt , nowayout ) ;
watchdog_set_drvdata ( & drv_data - > wdt , drv_data ) ;
platform_set_drvdata ( pdev , drv_data ) ;
ret = menf21bmc_wdt_set_bootstatus ( drv_data ) ;
if ( ret < 0 ) {
2019-04-09 20:23:41 +03:00
dev_err ( dev , " failed to set Watchdog bootstatus \n " ) ;
2014-08-27 21:52:06 +04:00
return ret ;
}
2019-04-09 20:23:41 +03:00
ret = devm_watchdog_register_device ( dev , & drv_data - > wdt ) ;
2019-05-19 00:27:39 +03:00
if ( ret )
2014-08-27 21:52:06 +04:00
return ret ;
2019-04-09 20:23:41 +03:00
dev_info ( dev , " MEN 14F021P00 BMC Watchdog device enabled \n " ) ;
2014-08-27 21:52:06 +04:00
return 0 ;
}
static void menf21bmc_wdt_shutdown ( struct platform_device * pdev )
{
struct menf21bmc_wdt * drv_data = platform_get_drvdata ( pdev ) ;
i2c_smbus_write_word_data ( drv_data - > i2c_client ,
BMC_CMD_WD_OFF , BMC_WD_OFF_VAL ) ;
}
static struct platform_driver menf21bmc_wdt = {
. driver = {
. name = DEVNAME ,
} ,
. probe = menf21bmc_wdt_probe ,
. shutdown = menf21bmc_wdt_shutdown ,
} ;
module_platform_driver ( menf21bmc_wdt ) ;
MODULE_DESCRIPTION ( " MEN 14F021P00 BMC Watchdog driver " ) ;
MODULE_AUTHOR ( " Andreas Werner <andreas.werner@men.de> " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;
MODULE_ALIAS ( " platform:menf21bmc_wdt " ) ;