12874c5fdSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later 250332639SAndreas Werner /* 350332639SAndreas Werner * MEN 14F021P00 Board Management Controller (BMC) Watchdog Driver. 450332639SAndreas Werner * 550332639SAndreas Werner * Copyright (C) 2014 MEN Mikro Elektronik Nuernberg GmbH 650332639SAndreas Werner */ 750332639SAndreas Werner 850332639SAndreas Werner #include <linux/kernel.h> 950332639SAndreas Werner #include <linux/device.h> 1050332639SAndreas Werner #include <linux/module.h> 1150332639SAndreas Werner #include <linux/watchdog.h> 1250332639SAndreas Werner #include <linux/platform_device.h> 1350332639SAndreas Werner #include <linux/i2c.h> 1450332639SAndreas Werner 1550332639SAndreas Werner #define DEVNAME "menf21bmc_wdt" 1650332639SAndreas Werner 1750332639SAndreas Werner #define BMC_CMD_WD_ON 0x11 1850332639SAndreas Werner #define BMC_CMD_WD_OFF 0x12 1950332639SAndreas Werner #define BMC_CMD_WD_TRIG 0x13 2050332639SAndreas Werner #define BMC_CMD_WD_TIME 0x14 2150332639SAndreas Werner #define BMC_CMD_WD_STATE 0x17 2250332639SAndreas Werner #define BMC_WD_OFF_VAL 0x69 2350332639SAndreas Werner #define BMC_CMD_RST_RSN 0x92 2450332639SAndreas Werner 2550332639SAndreas Werner #define BMC_WD_TIMEOUT_MIN 1 /* in sec */ 2650332639SAndreas Werner #define BMC_WD_TIMEOUT_MAX 6553 /* in sec */ 2750332639SAndreas Werner 2850332639SAndreas Werner static bool nowayout = WATCHDOG_NOWAYOUT; 2950332639SAndreas Werner module_param(nowayout, bool, 0); 3050332639SAndreas Werner MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" 3150332639SAndreas Werner __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); 3250332639SAndreas Werner 3350332639SAndreas Werner struct menf21bmc_wdt { 3450332639SAndreas Werner struct watchdog_device wdt; 3550332639SAndreas Werner struct i2c_client *i2c_client; 3650332639SAndreas Werner }; 3750332639SAndreas Werner 3850332639SAndreas Werner static int menf21bmc_wdt_set_bootstatus(struct menf21bmc_wdt *data) 3950332639SAndreas Werner { 4050332639SAndreas Werner int rst_rsn; 4150332639SAndreas Werner 4250332639SAndreas Werner rst_rsn = i2c_smbus_read_byte_data(data->i2c_client, BMC_CMD_RST_RSN); 4350332639SAndreas Werner if (rst_rsn < 0) 4450332639SAndreas Werner return rst_rsn; 4550332639SAndreas Werner 4650332639SAndreas Werner if (rst_rsn == 0x02) 4750332639SAndreas Werner data->wdt.bootstatus |= WDIOF_CARDRESET; 4850332639SAndreas Werner else if (rst_rsn == 0x05) 4950332639SAndreas Werner data->wdt.bootstatus |= WDIOF_EXTERN1; 5050332639SAndreas Werner else if (rst_rsn == 0x06) 5150332639SAndreas Werner data->wdt.bootstatus |= WDIOF_EXTERN2; 5250332639SAndreas Werner else if (rst_rsn == 0x0A) 5350332639SAndreas Werner data->wdt.bootstatus |= WDIOF_POWERUNDER; 5450332639SAndreas Werner 5550332639SAndreas Werner return 0; 5650332639SAndreas Werner } 5750332639SAndreas Werner 5850332639SAndreas Werner static int menf21bmc_wdt_start(struct watchdog_device *wdt) 5950332639SAndreas Werner { 6050332639SAndreas Werner struct menf21bmc_wdt *drv_data = watchdog_get_drvdata(wdt); 6150332639SAndreas Werner 6250332639SAndreas Werner return i2c_smbus_write_byte(drv_data->i2c_client, BMC_CMD_WD_ON); 6350332639SAndreas Werner } 6450332639SAndreas Werner 6550332639SAndreas Werner static int menf21bmc_wdt_stop(struct watchdog_device *wdt) 6650332639SAndreas Werner { 6750332639SAndreas Werner struct menf21bmc_wdt *drv_data = watchdog_get_drvdata(wdt); 6850332639SAndreas Werner 6950332639SAndreas Werner return i2c_smbus_write_byte_data(drv_data->i2c_client, 7050332639SAndreas Werner BMC_CMD_WD_OFF, BMC_WD_OFF_VAL); 7150332639SAndreas Werner } 7250332639SAndreas Werner 7350332639SAndreas Werner static int 7450332639SAndreas Werner menf21bmc_wdt_settimeout(struct watchdog_device *wdt, unsigned int timeout) 7550332639SAndreas Werner { 7650332639SAndreas Werner int ret; 7750332639SAndreas Werner struct menf21bmc_wdt *drv_data = watchdog_get_drvdata(wdt); 7850332639SAndreas Werner 7950332639SAndreas Werner /* 8050332639SAndreas Werner * BMC Watchdog does have a resolution of 100ms. 8150332639SAndreas Werner * Watchdog API defines the timeout in seconds, so we have to 8250332639SAndreas Werner * multiply the value. 8350332639SAndreas Werner */ 8450332639SAndreas Werner ret = i2c_smbus_write_word_data(drv_data->i2c_client, 8550332639SAndreas Werner BMC_CMD_WD_TIME, timeout * 10); 8650332639SAndreas Werner if (ret < 0) 8750332639SAndreas Werner return ret; 8850332639SAndreas Werner 8950332639SAndreas Werner wdt->timeout = timeout; 9050332639SAndreas Werner 9150332639SAndreas Werner return 0; 9250332639SAndreas Werner } 9350332639SAndreas Werner 9450332639SAndreas Werner static int menf21bmc_wdt_ping(struct watchdog_device *wdt) 9550332639SAndreas Werner { 9650332639SAndreas Werner struct menf21bmc_wdt *drv_data = watchdog_get_drvdata(wdt); 9750332639SAndreas Werner 9850332639SAndreas Werner return i2c_smbus_write_byte(drv_data->i2c_client, BMC_CMD_WD_TRIG); 9950332639SAndreas Werner } 10050332639SAndreas Werner 10150332639SAndreas Werner static const struct watchdog_info menf21bmc_wdt_info = { 10250332639SAndreas Werner .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING, 10350332639SAndreas Werner .identity = DEVNAME, 10450332639SAndreas Werner }; 10550332639SAndreas Werner 10650332639SAndreas Werner static const struct watchdog_ops menf21bmc_wdt_ops = { 10750332639SAndreas Werner .owner = THIS_MODULE, 10850332639SAndreas Werner .start = menf21bmc_wdt_start, 10950332639SAndreas Werner .stop = menf21bmc_wdt_stop, 11050332639SAndreas Werner .ping = menf21bmc_wdt_ping, 11150332639SAndreas Werner .set_timeout = menf21bmc_wdt_settimeout, 11250332639SAndreas Werner }; 11350332639SAndreas Werner 11450332639SAndreas Werner static int menf21bmc_wdt_probe(struct platform_device *pdev) 11550332639SAndreas Werner { 1160c4ece9bSGuenter Roeck struct device *dev = &pdev->dev; 11750332639SAndreas Werner int ret, bmc_timeout; 11850332639SAndreas Werner struct menf21bmc_wdt *drv_data; 1190c4ece9bSGuenter Roeck struct i2c_client *i2c_client = to_i2c_client(dev->parent); 12050332639SAndreas Werner 1210c4ece9bSGuenter Roeck drv_data = devm_kzalloc(dev, sizeof(struct menf21bmc_wdt), GFP_KERNEL); 12250332639SAndreas Werner if (!drv_data) 12350332639SAndreas Werner return -ENOMEM; 12450332639SAndreas Werner 12550332639SAndreas Werner drv_data->wdt.ops = &menf21bmc_wdt_ops; 12650332639SAndreas Werner drv_data->wdt.info = &menf21bmc_wdt_info; 12750332639SAndreas Werner drv_data->wdt.min_timeout = BMC_WD_TIMEOUT_MIN; 12850332639SAndreas Werner drv_data->wdt.max_timeout = BMC_WD_TIMEOUT_MAX; 1290c4ece9bSGuenter Roeck drv_data->wdt.parent = dev; 13050332639SAndreas Werner drv_data->i2c_client = i2c_client; 13150332639SAndreas Werner 13250332639SAndreas Werner /* 13350332639SAndreas Werner * Get the current wdt timeout value from the BMC because 13450332639SAndreas Werner * the BMC will save the value set before if the system restarts. 13550332639SAndreas Werner */ 13650332639SAndreas Werner bmc_timeout = i2c_smbus_read_word_data(drv_data->i2c_client, 13750332639SAndreas Werner BMC_CMD_WD_TIME); 13850332639SAndreas Werner if (bmc_timeout < 0) { 1390c4ece9bSGuenter Roeck dev_err(dev, "failed to get current WDT timeout\n"); 14050332639SAndreas Werner return bmc_timeout; 14150332639SAndreas Werner } 14250332639SAndreas Werner 1430c4ece9bSGuenter Roeck watchdog_init_timeout(&drv_data->wdt, bmc_timeout / 10, dev); 14450332639SAndreas Werner watchdog_set_nowayout(&drv_data->wdt, nowayout); 14550332639SAndreas Werner watchdog_set_drvdata(&drv_data->wdt, drv_data); 14650332639SAndreas Werner platform_set_drvdata(pdev, drv_data); 14750332639SAndreas Werner 14850332639SAndreas Werner ret = menf21bmc_wdt_set_bootstatus(drv_data); 14950332639SAndreas Werner if (ret < 0) { 1500c4ece9bSGuenter Roeck dev_err(dev, "failed to set Watchdog bootstatus\n"); 15150332639SAndreas Werner return ret; 15250332639SAndreas Werner } 15350332639SAndreas Werner 1540c4ece9bSGuenter Roeck ret = devm_watchdog_register_device(dev, &drv_data->wdt); 155*86fc1865SWolfram Sang if (ret) 15650332639SAndreas Werner return ret; 15750332639SAndreas Werner 1580c4ece9bSGuenter Roeck dev_info(dev, "MEN 14F021P00 BMC Watchdog device enabled\n"); 15950332639SAndreas Werner 16050332639SAndreas Werner return 0; 16150332639SAndreas Werner } 16250332639SAndreas Werner 16350332639SAndreas Werner static void menf21bmc_wdt_shutdown(struct platform_device *pdev) 16450332639SAndreas Werner { 16550332639SAndreas Werner struct menf21bmc_wdt *drv_data = platform_get_drvdata(pdev); 16650332639SAndreas Werner 16750332639SAndreas Werner i2c_smbus_write_word_data(drv_data->i2c_client, 16850332639SAndreas Werner BMC_CMD_WD_OFF, BMC_WD_OFF_VAL); 16950332639SAndreas Werner } 17050332639SAndreas Werner 17150332639SAndreas Werner static struct platform_driver menf21bmc_wdt = { 17250332639SAndreas Werner .driver = { 17350332639SAndreas Werner .name = DEVNAME, 17450332639SAndreas Werner }, 17550332639SAndreas Werner .probe = menf21bmc_wdt_probe, 17650332639SAndreas Werner .shutdown = menf21bmc_wdt_shutdown, 17750332639SAndreas Werner }; 17850332639SAndreas Werner 17950332639SAndreas Werner module_platform_driver(menf21bmc_wdt); 18050332639SAndreas Werner 18150332639SAndreas Werner MODULE_DESCRIPTION("MEN 14F021P00 BMC Watchdog driver"); 18250332639SAndreas Werner MODULE_AUTHOR("Andreas Werner <andreas.werner@men.de>"); 18350332639SAndreas Werner MODULE_LICENSE("GPL v2"); 18450332639SAndreas Werner MODULE_ALIAS("platform:menf21bmc_wdt"); 185