Commit 77849a55 authored by Yuechao Zhao's avatar Yuechao Zhao Committed by Guenter Roeck
Browse files

hwmon: (nct7904) Add watchdog function



Implement watchdog functionality for NCT7904.

Signed-off-by: default avatarYuechao Zhao <yuechao.zhao@advantech.com.cn>
Link: https://lore.kernel.org/r/20200331052850.5419-1-yuechao.zhao@advantech.com.cn


[groeck: Squashed fixup patch]
Signed-off-by: default avatarGuenter Roeck <linux@roeck-us.net>
parent b9bbe6ed
Loading
Loading
Loading
Loading
+4 −2
Original line number Diff line number Diff line
@@ -1340,10 +1340,12 @@ config SENSORS_NCT7802

config SENSORS_NCT7904
	tristate "Nuvoton NCT7904"
	depends on I2C
	depends on I2C && WATCHDOG
	select WATCHDOG_CORE
	help
	  If you say yes here you get support for the Nuvoton NCT7904
	  hardware monitoring chip, including manual fan speed control.
	  hardware monitoring chip, including manual fan speed control
	  and support for the integrated watchdog.

	  This driver can also be built as a module. If so, the module
	  will be called nct7904.
+137 −1
Original line number Diff line number Diff line
@@ -8,6 +8,9 @@
 * Copyright (c) 2019 Advantech
 * Author: Amy.Shih <amy.shih@advantech.com.tw>
 *
 * Copyright (c) 2020 Advantech
 * Author: Yuechao Zhao <yuechao.zhao@advantech.com.cn>
 *
 * Supports the following chips:
 *
 * Chip        #vin  #fan  #pwm  #temp  #dts  chip ID
@@ -20,6 +23,7 @@
#include <linux/i2c.h>
#include <linux/mutex.h>
#include <linux/hwmon.h>
#include <linux/watchdog.h>

#define VENDOR_ID_REG		0x7A	/* Any bank */
#define NUVOTON_ID		0x50
@@ -88,18 +92,42 @@
#define FANCTL1_FMR_REG		0x00	/* Bank 3; 1 reg per channel */
#define FANCTL1_OUT_REG		0x10	/* Bank 3; 1 reg per channel */

#define WDT_LOCK_REG		0xE0	/* W/O Lock Watchdog Register */
#define WDT_EN_REG		0xE1	/* R/O Watchdog Enable Register */
#define WDT_STS_REG		0xE2	/* R/O Watchdog Status Register */
#define WDT_TIMER_REG		0xE3	/* R/W Watchdog Timer Register */
#define WDT_SOFT_EN		0x55	/* Enable soft watchdog timer */
#define WDT_SOFT_DIS		0xAA	/* Disable soft watchdog timer */

#define VOLT_MONITOR_MODE	0x0
#define THERMAL_DIODE_MODE	0x1
#define THERMISTOR_MODE		0x3

#define ENABLE_TSI	BIT(1)

#define WATCHDOG_TIMEOUT	1	/* 1 minute default timeout */

/*The timeout range is 1-255 minutes*/
#define MIN_TIMEOUT		(1 * 60)
#define MAX_TIMEOUT		(255 * 60)

static int timeout = WATCHDOG_TIMEOUT;
module_param(timeout, int, 0);
MODULE_PARM_DESC(timeout, "Watchdog timeout in minutes. 1 <= timeout <= 255, default="
			__MODULE_STRING(WATCHODOG_TIMEOUT) ".");

static bool nowayout = WATCHDOG_NOWAYOUT;
module_param(nowayout, bool, 0);
MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started once started (default="
			__MODULE_STRING(WATCHDOG_NOWAYOUT) ")");

static const unsigned short normal_i2c[] = {
	0x2d, 0x2e, I2C_CLIENT_END
};

struct nct7904_data {
	struct i2c_client *client;
	struct watchdog_device wdt;
	struct mutex bank_lock;
	int bank_sel;
	u32 fanin_mask;
@@ -892,6 +920,95 @@ static const struct hwmon_chip_info nct7904_chip_info = {
	.info = nct7904_info,
};

/*
 * Watchdog Function
 */
static int nct7904_wdt_start(struct watchdog_device *wdt)
{
	struct nct7904_data *data = watchdog_get_drvdata(wdt);

	/* Enable soft watchdog timer */
	return nct7904_write_reg(data, BANK_0, WDT_LOCK_REG, WDT_SOFT_EN);
}

static int nct7904_wdt_stop(struct watchdog_device *wdt)
{
	struct nct7904_data *data = watchdog_get_drvdata(wdt);

	return nct7904_write_reg(data, BANK_0, WDT_LOCK_REG, WDT_SOFT_DIS);
}

static int nct7904_wdt_set_timeout(struct watchdog_device *wdt,
				   unsigned int timeout)
{
	struct nct7904_data *data = watchdog_get_drvdata(wdt);
	/*
	 * The NCT7904 is very special in watchdog function.
	 * Its minimum unit is minutes. And wdt->timeout needs
	 * to match the actual timeout selected. So, this needs
	 * to be: wdt->timeout = timeout / 60 * 60.
	 * For example, if the user configures a timeout of
	 * 119 seconds, the actual timeout will be 60 seconds.
	 * So, wdt->timeout must then be set to 60 seconds.
	 */
	wdt->timeout = timeout / 60 * 60;

	return nct7904_write_reg(data, BANK_0, WDT_TIMER_REG,
				 wdt->timeout / 60);
}

static int nct7904_wdt_ping(struct watchdog_device *wdt)
{
	/*
	 * Note:
	 * NCT7904 does not support refreshing WDT_TIMER_REG register when
	 * the watchdog is active. Please disable watchdog before feeding
	 * the watchdog and enable it again.
	 */
	struct nct7904_data *data = watchdog_get_drvdata(wdt);
	int ret;

	/* Disable soft watchdog timer */
	ret = nct7904_write_reg(data, BANK_0, WDT_LOCK_REG, WDT_SOFT_DIS);
	if (ret < 0)
		return ret;

	/* feed watchdog */
	ret = nct7904_write_reg(data, BANK_0, WDT_TIMER_REG, wdt->timeout / 60);
	if (ret < 0)
		return ret;

	/* Enable soft watchdog timer */
	return nct7904_write_reg(data, BANK_0, WDT_LOCK_REG, WDT_SOFT_EN);
}

static unsigned int nct7904_wdt_get_timeleft(struct watchdog_device *wdt)
{
	struct nct7904_data *data = watchdog_get_drvdata(wdt);
	int ret;

	ret = nct7904_read_reg(data, BANK_0, WDT_TIMER_REG);
	if (ret < 0)
		return 0;

	return ret * 60;
}

static const struct watchdog_info nct7904_wdt_info = {
	.options	= WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING |
				WDIOF_MAGICCLOSE,
	.identity	= "nct7904 watchdog",
};

static const struct watchdog_ops nct7904_wdt_ops = {
	.owner		= THIS_MODULE,
	.start		= nct7904_wdt_start,
	.stop		= nct7904_wdt_stop,
	.ping		= nct7904_wdt_ping,
	.set_timeout	= nct7904_wdt_set_timeout,
	.get_timeleft	= nct7904_wdt_get_timeleft,
};

static int nct7904_probe(struct i2c_client *client,
			 const struct i2c_device_id *id)
{
@@ -1022,7 +1139,26 @@ static int nct7904_probe(struct i2c_client *client,
	hwmon_dev =
		devm_hwmon_device_register_with_info(dev, client->name, data,
						     &nct7904_chip_info, NULL);
	return PTR_ERR_OR_ZERO(hwmon_dev);
	ret = PTR_ERR_OR_ZERO(hwmon_dev);
	if (ret)
		return ret;

	/* Watchdog initialization */
	data->wdt.ops = &nct7904_wdt_ops;
	data->wdt.info = &nct7904_wdt_info;

	data->wdt.timeout = timeout * 60; /* in seconds */
	data->wdt.min_timeout = MIN_TIMEOUT;
	data->wdt.max_timeout = MAX_TIMEOUT;
	data->wdt.parent = &client->dev;

	watchdog_init_timeout(&data->wdt, timeout * 60, &client->dev);
	watchdog_set_nowayout(&data->wdt, nowayout);
	watchdog_set_drvdata(&data->wdt, data);

	watchdog_stop_on_unregister(&data->wdt);

	return devm_watchdog_register_device(dev, &data->wdt);
}

static const struct i2c_device_id nct7904_id[] = {