diff mbox series

hwmon: add in watchdog for nct6686

Message ID 20230815115515.286142-1-dober6023@gmail.com (mailing list archive)
State Changes Requested
Headers show
Series hwmon: add in watchdog for nct6686 | expand

Commit Message

David Ober Aug. 15, 2023, 11:55 a.m. UTC
This change adds in the watchdog timer support for the nct6686
chip so that it can be used on the Lenovo m90n IOT device

Signed-off-by: David Ober <dober6023@gmail.com>
---
 Documentation/hwmon/nct6683.rst |   5 +-
 drivers/hwmon/nct6683.c         | 247 ++++++++++++++++++++++++++++++--
 2 files changed, 242 insertions(+), 10 deletions(-)

Comments

Guenter Roeck Aug. 15, 2023, 3:37 p.m. UTC | #1
On Tue, Aug 15, 2023 at 07:55:15AM -0400, David Ober wrote:
> This change adds in the watchdog timer support for the nct6686
> chip so that it can be used on the Lenovo m90n IOT device
> 
> Signed-off-by: David Ober <dober6023@gmail.com>
> ---
>  Documentation/hwmon/nct6683.rst |   5 +-
>  drivers/hwmon/nct6683.c         | 247 ++++++++++++++++++++++++++++++--

This should be a separate driver in drivers/watchdog/, and sneaking
in support for a new vendor with the above description is inappropriate
anyway. Besides, why would the watchdog only work for Lenovo ?

Guenter

>  2 files changed, 242 insertions(+), 10 deletions(-)
> 
> diff --git a/Documentation/hwmon/nct6683.rst b/Documentation/hwmon/nct6683.rst
> index 2e1408d174bd..7421bc444365 100644
> --- a/Documentation/hwmon/nct6683.rst
> +++ b/Documentation/hwmon/nct6683.rst
> @@ -3,7 +3,7 @@ Kernel driver nct6683
>  
>  Supported chips:
>  
> -  * Nuvoton NCT6683D/NCT6687D
> +  * Nuvoton NCT6683D/NCT6686D/NCT6687D
>  
>      Prefix: 'nct6683'
>  
> @@ -49,6 +49,8 @@ The driver has only been tested with the Intel firmware, and by default
>  only instantiates on Intel boards. To enable it on non-Intel boards,
>  set the 'force' module parameter to 1.
>  
> +Implement the watchdog functionality of the NCT6686D eSIO chip
> +
>  Tested Boards and Firmware Versions
>  -----------------------------------
>  
> @@ -63,4 +65,5 @@ Intel DH87MC	NCT6683D EC firmware version 1.0 build 04/03/13
>  Intel DB85FL	NCT6683D EC firmware version 1.0 build 04/03/13
>  ASRock X570	NCT6683D EC firmware version 1.0 build 06/28/19
>  MSI B550	NCT6687D EC firmware version 1.0 build 05/07/20
> +LENOVO M90n-1	NCT6686D EC firmware version 9.0 build 04/21/21
>  =============== ===============================================
> diff --git a/drivers/hwmon/nct6683.c b/drivers/hwmon/nct6683.c
> index f673f7d07941..eb95b91c4d39 100644
> --- a/drivers/hwmon/nct6683.c
> +++ b/drivers/hwmon/nct6683.c
> @@ -24,15 +24,16 @@
>  #include <linux/acpi.h>
>  #include <linux/delay.h>
>  #include <linux/err.h>
> +#include <linux/hwmon.h>
> +#include <linux/hwmon-sysfs.h>
>  #include <linux/init.h>
>  #include <linux/io.h>
>  #include <linux/jiffies.h>
> -#include <linux/hwmon.h>
> -#include <linux/hwmon-sysfs.h>
>  #include <linux/module.h>
>  #include <linux/mutex.h>
>  #include <linux/platform_device.h>
>  #include <linux/slab.h>
> +#include <linux/watchdog.h>
>  
>  enum kinds { nct6683, nct6686, nct6687 };
>  
> @@ -73,6 +74,34 @@ static const char * const nct6683_chip_names[] = {
>  #define SIO_NCT6687_ID		0xd590
>  #define SIO_ID_MASK		0xFFF0
>  
> +#define WDT_CFG			0x828    /* W/O Lock Watchdog Register */
> +#define WDT_CNT			0x829    /* R/W Watchdog Timer Register */
> +#define WDT_STS			0x82A    /* R/O Watchdog Status Register */
> +#define WDT_STS_EVT_POS		(0)
> +#define WDT_STS_EVT_MSK		(0x3 << WDT_STS_EVT_POS)
> +#define WDT_SOFT_EN		0x87    /* Enable soft watchdog timer */
> +#define WDT_SOFT_DIS		0xAA    /* Disable soft watchdog timer */
> +
> +#define WATCHDOG_TIMEOUT	60      /* 1 minute default timeout */
> +
> +/* The timeout range is 1-255 seconds */
> +#define MIN_TIMEOUT		1
> +#define MAX_TIMEOUT		255
> +
> +static int timeout;
> +module_param(timeout, int, 0);
> +MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds. 1 <= timeout <= 255, default="
> +		 __MODULE_STRING(WATCHDOG_TIMEOUT) ".");
> +
> +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) ")");
> +
> +static int early_disable;
> +module_param(early_disable, int, 0);
> +MODULE_PARM_DESC(early_disable, "Disable watchdog at boot time (default=0)");
> +
>  static inline void
>  superio_outb(int ioreg, int reg, int val)
>  {
> @@ -171,10 +200,10 @@ superio_exit(int ioreg)
>  
>  #define NCT6683_REG_CUSTOMER_ID		0x602
>  #define NCT6683_CUSTOMER_ID_INTEL	0x805
> +#define NCT6683_CUSTOMER_ID_LENOVO	0x1101
>  #define NCT6683_CUSTOMER_ID_MITAC	0xa0e
>  #define NCT6683_CUSTOMER_ID_MSI		0x201
> -#define NCT6683_CUSTOMER_ID_MSI2	0x200
> -#define NCT6683_CUSTOMER_ID_ASROCK		0xe2c
> +#define NCT6683_CUSTOMER_ID_ASROCK	0xe2c
>  #define NCT6683_CUSTOMER_ID_ASROCK2	0xe1b
>  
>  #define NCT6683_REG_BUILD_YEAR		0x604
> @@ -183,6 +212,9 @@ superio_exit(int ioreg)
>  #define NCT6683_REG_SERIAL		0x607
>  #define NCT6683_REG_VERSION_HI		0x608
>  #define NCT6683_REG_VERSION_LO		0x609
> +#define NCT6686_PAGE_REG_OFFSET		0
> +#define NCT6686_ADDR_REG_OFFSET		1
> +#define NCT6686_DATA_REG_OFFSET		2
>  
>  #define NCT6683_REG_CR_CASEOPEN		0xe8
>  #define NCT6683_CR_CASEOPEN_MASK	(1 << 7)
> @@ -304,6 +336,7 @@ struct nct6683_data {
>  
>  	struct device *hwmon_dev;
>  	const struct attribute_group *groups[6];
> +	struct watchdog_device wdt;
>  
>  	int temp_num;			/* number of temperature attributes */
>  	u8 temp_index[NCT6683_NUM_REG_MON];
> @@ -518,6 +551,39 @@ static void nct6683_write(struct nct6683_data *data, u16 reg, u16 value)
>  	outb_p(value & 0xff, data->addr + EC_DATA_REG);
>  }
>  
> +static inline void nct6686_wdt_set_bank(int base_addr, u16 reg)
> +{
> +	outb_p(0xFF, base_addr + NCT6686_PAGE_REG_OFFSET);
> +	outb_p(reg >> 8, base_addr + NCT6686_PAGE_REG_OFFSET);
> +}
> +
> +/* Not strictly necessary, but play it safe for now */
> +static inline void nct6686_wdt_reset_bank(int base_addr, u16 reg)
> +{
> +	if (reg & 0xff00)
> +		outb_p(0xFF, base_addr + NCT6686_PAGE_REG_OFFSET);
> +}
> +
> +static u8 nct6686_read(struct nct6683_data *data, u16 reg)
> +{
> +	u8 res;
> +
> +	nct6686_wdt_set_bank(data->addr, reg);
> +	outb_p(reg & 0xff, data->addr + NCT6686_ADDR_REG_OFFSET);
> +	res = inb_p(data->addr + NCT6686_DATA_REG_OFFSET);
> +
> +	nct6686_wdt_reset_bank(data->addr, reg);
> +	return res;
> +}
> +
> +static void nct6686_write(struct nct6683_data *data, u16 reg, u8 value)
> +{
> +	nct6686_wdt_set_bank(data->addr, reg);
> +	outb_p(reg & 0xff, data->addr + NCT6686_ADDR_REG_OFFSET);
> +	outb_p(value & 0xff, data->addr + NCT6686_DATA_REG_OFFSET);
> +	nct6686_wdt_reset_bank(data->addr, reg);
> +}
> +
>  static int get_in_reg(struct nct6683_data *data, int nr, int index)
>  {
>  	int ch = data->in_index[index];
> @@ -680,11 +746,12 @@ static umode_t nct6683_in_is_visible(struct kobject *kobj,
>  	int nr = index % 4;	/* attribute */
>  
>  	/*
> -	 * Voltage limits exist for Intel boards,
> +	 * Voltage limits exist for Intel and Lenovo boards,
>  	 * but register location and encoding is unknown
>  	 */
>  	if ((nr == 2 || nr == 3) &&
> -	    data->customer_id == NCT6683_CUSTOMER_ID_INTEL)
> +	    (data->customer_id == NCT6683_CUSTOMER_ID_INTEL ||
> +	     data->customer_id == NCT6683_CUSTOMER_ID_LENOVO))
>  		return 0;
>  
>  	return attr->mode;
> @@ -1186,6 +1253,139 @@ static void nct6683_setup_sensors(struct nct6683_data *data)
>  	}
>  }
>  
> +/*
> + * Watchdog Functions
> + */
> +static int nct6686_wdt_enable(struct watchdog_device *wdog, bool enable)
> +{
> +	struct nct6683_data *data = watchdog_get_drvdata(wdog);
> +
> +	u_char reg;
> +
> +	mutex_lock(&data->update_lock);
> +	reg = nct6686_read(data, WDT_CFG);
> +
> +	if (enable) {
> +		nct6686_write(data, WDT_CFG, reg | 0x3);
> +		mutex_unlock(&data->update_lock);
> +		return 0;
> +	}
> +
> +	nct6686_write(data, WDT_CFG, reg & ~BIT(0));
> +	mutex_unlock(&data->update_lock);
> +
> +	return 0;
> +}
> +
> +static int nct6686_wdt_set_time(struct watchdog_device *wdog)
> +{
> +	struct nct6683_data *data = watchdog_get_drvdata(wdog);
> +
> +	mutex_lock(&data->update_lock);
> +	nct6686_write(data, WDT_CNT, wdog->timeout);
> +	mutex_unlock(&data->update_lock);
> +
> +	if (wdog->timeout) {
> +		nct6686_wdt_enable(wdog, true);
> +		return 0;
> +	}
> +
> +	nct6686_wdt_enable(wdog, false);
> +	return 0;
> +}
> +
> +static int nct6686_wdt_start(struct watchdog_device *wdt)
> +{
> +	struct nct6683_data *data = watchdog_get_drvdata(wdt);
> +	u_char reg;
> +
> +	nct6686_wdt_set_time(wdt);
> +
> +	/* Enable soft watchdog timer */
> +	mutex_lock(&data->update_lock);
> +	/* reset trigger status */
> +	reg = nct6686_read(data, WDT_STS);
> +	nct6686_write(data, WDT_STS, reg & ~WDT_STS_EVT_MSK);
> +	mutex_unlock(&data->update_lock);
> +	return 0;
> +}
> +
> +static int nct6686_wdt_stop(struct watchdog_device *wdt)
> +{
> +	struct nct6683_data *data = watchdog_get_drvdata(wdt);
> +
> +	mutex_lock(&data->update_lock);
> +	nct6686_write(data, WDT_CFG, WDT_SOFT_DIS);
> +	mutex_unlock(&data->update_lock);
> +	return 0;
> +}
> +
> +static int nct6686_wdt_set_timeout(struct watchdog_device *wdt,
> +				   unsigned int timeout)
> +{
> +	struct nct6683_data *data = watchdog_get_drvdata(wdt);
> +
> +	wdt->timeout = timeout;
> +	mutex_lock(&data->update_lock);
> +	nct6686_write(data, WDT_CNT, timeout);
> +	mutex_unlock(&data->update_lock);
> +	return 0;
> +}
> +
> +static int nct6686_wdt_ping(struct watchdog_device *wdt)
> +{
> +	struct nct6683_data *data = watchdog_get_drvdata(wdt);
> +	int timeout;
> +
> +	/*
> +	 * Note:
> +	 * NCT6686 does not support refreshing WDT_TIMER_REG register when
> +	 * the watchdog is active. Please disable watchdog before feeding
> +	 * the watchdog and enable it again.
> +	 */
> +	/* Disable soft watchdog timer */
> +	nct6686_wdt_enable(wdt, false);
> +
> +	/* feed watchdog */
> +	timeout = wdt->timeout;
> +	mutex_lock(&data->update_lock);
> +	nct6686_write(data, WDT_CNT, timeout);
> +	mutex_unlock(&data->update_lock);
> +
> +	/* Enable soft watchdog timer */
> +	nct6686_wdt_enable(wdt, true);
> +	return 0;
> +}
> +
> +static unsigned int nct6686_wdt_get_timeleft(struct watchdog_device *wdt)
> +{
> +	struct nct6683_data *data = watchdog_get_drvdata(wdt);
> +	int ret;
> +
> +	mutex_lock(&data->update_lock);
> +	ret = nct6686_read(data, WDT_CNT);
> +	mutex_unlock(&data->update_lock);
> +	if (ret < 0)
> +		return 0;
> +
> +	return ret;
> +}
> +
> +static const struct watchdog_info nct6686_wdt_info = {
> +	.options        = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING |
> +			  WDIOF_MAGICCLOSE,
> +	.identity       = "nct6686 watchdog",
> +};
> +
> +static const struct watchdog_ops nct6686_wdt_ops = {
> +	.owner          = THIS_MODULE,
> +	.start          = nct6686_wdt_start,
> +	.stop           = nct6686_wdt_stop,
> +	.ping           = nct6686_wdt_ping,
> +	.set_timeout    = nct6686_wdt_set_timeout,
> +	.get_timeleft   = nct6686_wdt_get_timeleft,
> +};
> +
>  static int nct6683_probe(struct platform_device *pdev)
>  {
>  	struct device *dev = &pdev->dev;
> @@ -1195,7 +1395,9 @@ static int nct6683_probe(struct platform_device *pdev)
>  	struct device *hwmon_dev;
>  	struct resource *res;
>  	int groups = 0;
> +	int ret;
>  	char build[16];
> +	u_char reg;
>  
>  	res = platform_get_resource(pdev, IORESOURCE_IO, 0);
>  	if (!devm_request_region(dev, res->start, IOREGION_LENGTH, DRVNAME))
> @@ -1215,14 +1417,14 @@ static int nct6683_probe(struct platform_device *pdev)
>  
>  	/* By default only instantiate driver if the customer ID is known */
>  	switch (data->customer_id) {
> +	case NCT6683_CUSTOMER_ID_LENOVO:
> +		break;
>  	case NCT6683_CUSTOMER_ID_INTEL:
>  		break;
>  	case NCT6683_CUSTOMER_ID_MITAC:
>  		break;
>  	case NCT6683_CUSTOMER_ID_MSI:
>  		break;
> -	case NCT6683_CUSTOMER_ID_MSI2:
> -		break;
>  	case NCT6683_CUSTOMER_ID_ASROCK:
>  		break;
>  	case NCT6683_CUSTOMER_ID_ASROCK2:
> @@ -1294,7 +1496,34 @@ static int nct6683_probe(struct platform_device *pdev)
>  
>  	hwmon_dev = devm_hwmon_device_register_with_groups(dev,
>  			nct6683_device_names[data->kind], data, data->groups);
> -	return PTR_ERR_OR_ZERO(hwmon_dev);
> +
> +	ret = PTR_ERR_OR_ZERO(hwmon_dev);
> +	if (ret)
> +		return ret;
> +
> +	if (data->kind == nct6686 && data->customer_id == NCT6683_CUSTOMER_ID_LENOVO) {
> +		/* Watchdog initialization */
> +		data->wdt.ops = &nct6686_wdt_ops;
> +		data->wdt.info = &nct6686_wdt_info;
> +
> +		data->wdt.timeout = WATCHDOG_TIMEOUT; /* Set default timeout */
> +		data->wdt.min_timeout = MIN_TIMEOUT;
> +		data->wdt.max_timeout = MAX_TIMEOUT;
> +		data->wdt.parent = &pdev->dev;
> +
> +		watchdog_init_timeout(&data->wdt, timeout, &pdev->dev);
> +		watchdog_set_nowayout(&data->wdt, nowayout);
> +		watchdog_set_drvdata(&data->wdt, data);
> +
> +		/* reset trigger status */
> +		reg = nct6686_read(data, WDT_STS);
> +		nct6686_write(data, WDT_STS, reg & ~WDT_STS_EVT_MSK);
> +
> +		watchdog_stop_on_unregister(&data->wdt);
> +
> +		return devm_watchdog_register_device(dev, &data->wdt);
> +	}
> +	return ret;
>  }
>  
>  #ifdef CONFIG_PM
> -- 
> 2.34.1
>
kernel test robot Aug. 15, 2023, 3:58 p.m. UTC | #2
Hi David,

kernel test robot noticed the following build errors:

[auto build test ERROR on groeck-staging/hwmon-next]
[also build test ERROR on linus/master v6.5-rc6 next-20230815]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url:    https://github.com/intel-lab-lkp/linux/commits/David-Ober/hwmon-add-in-watchdog-for-nct6686/20230815-195946
base:   https://git.kernel.org/pub/scm/linux/kernel/git/groeck/linux-staging.git hwmon-next
patch link:    https://lore.kernel.org/r/20230815115515.286142-1-dober6023%40gmail.com
patch subject: [PATCH] hwmon: add in watchdog for nct6686
config: x86_64-randconfig-r006-20230815 (https://download.01.org/0day-ci/archive/20230815/202308152354.OmOTyX2r-lkp@intel.com/config)
compiler: clang version 16.0.4 (https://github.com/llvm/llvm-project.git ae42196bc493ffe877a7e3dff8be32035dea4d07)
reproduce: (https://download.01.org/0day-ci/archive/20230815/202308152354.OmOTyX2r-lkp@intel.com/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202308152354.OmOTyX2r-lkp@intel.com/

All errors (new ones prefixed by >>):

>> ld.lld: error: undefined symbol: watchdog_init_timeout
   >>> referenced by nct6683.c:1514 (drivers/hwmon/nct6683.c:1514)
   >>>               drivers/hwmon/nct6683.o:(nct6683_probe) in archive vmlinux.a
--
>> ld.lld: error: undefined symbol: devm_watchdog_register_device
   >>> referenced by nct6683.c:1524 (drivers/hwmon/nct6683.c:1524)
   >>>               drivers/hwmon/nct6683.o:(nct6683_probe) in archive vmlinux.a
kernel test robot Aug. 15, 2023, 3:58 p.m. UTC | #3
Hi David,

kernel test robot noticed the following build errors:

[auto build test ERROR on groeck-staging/hwmon-next]
[also build test ERROR on linus/master v6.5-rc6 next-20230815]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url:    https://github.com/intel-lab-lkp/linux/commits/David-Ober/hwmon-add-in-watchdog-for-nct6686/20230815-195946
base:   https://git.kernel.org/pub/scm/linux/kernel/git/groeck/linux-staging.git hwmon-next
patch link:    https://lore.kernel.org/r/20230815115515.286142-1-dober6023%40gmail.com
patch subject: [PATCH] hwmon: add in watchdog for nct6686
config: i386-randconfig-i015-20230815 (https://download.01.org/0day-ci/archive/20230815/202308152310.lu9rTudo-lkp@intel.com/config)
compiler: gcc-12 (Debian 12.2.0-14) 12.2.0
reproduce: (https://download.01.org/0day-ci/archive/20230815/202308152310.lu9rTudo-lkp@intel.com/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202308152310.lu9rTudo-lkp@intel.com/

All errors (new ones prefixed by >>):

   ld: drivers/hwmon/nct6683.o: in function `nct6683_probe':
>> drivers/hwmon/nct6683.c:1514: undefined reference to `watchdog_init_timeout'
>> ld: drivers/hwmon/nct6683.c:1524: undefined reference to `devm_watchdog_register_device'


vim +1514 drivers/hwmon/nct6683.c

  1388	
  1389	static int nct6683_probe(struct platform_device *pdev)
  1390	{
  1391		struct device *dev = &pdev->dev;
  1392		struct nct6683_sio_data *sio_data = dev->platform_data;
  1393		struct attribute_group *group;
  1394		struct nct6683_data *data;
  1395		struct device *hwmon_dev;
  1396		struct resource *res;
  1397		int groups = 0;
  1398		int ret;
  1399		char build[16];
  1400		u_char reg;
  1401	
  1402		res = platform_get_resource(pdev, IORESOURCE_IO, 0);
  1403		if (!devm_request_region(dev, res->start, IOREGION_LENGTH, DRVNAME))
  1404			return -EBUSY;
  1405	
  1406		data = devm_kzalloc(dev, sizeof(struct nct6683_data), GFP_KERNEL);
  1407		if (!data)
  1408			return -ENOMEM;
  1409	
  1410		data->kind = sio_data->kind;
  1411		data->sioreg = sio_data->sioreg;
  1412		data->addr = res->start;
  1413		mutex_init(&data->update_lock);
  1414		platform_set_drvdata(pdev, data);
  1415	
  1416		data->customer_id = nct6683_read16(data, NCT6683_REG_CUSTOMER_ID);
  1417	
  1418		/* By default only instantiate driver if the customer ID is known */
  1419		switch (data->customer_id) {
  1420		case NCT6683_CUSTOMER_ID_LENOVO:
  1421			break;
  1422		case NCT6683_CUSTOMER_ID_INTEL:
  1423			break;
  1424		case NCT6683_CUSTOMER_ID_MITAC:
  1425			break;
  1426		case NCT6683_CUSTOMER_ID_MSI:
  1427			break;
  1428		case NCT6683_CUSTOMER_ID_ASROCK:
  1429			break;
  1430		case NCT6683_CUSTOMER_ID_ASROCK2:
  1431			break;
  1432		default:
  1433			if (!force)
  1434				return -ENODEV;
  1435		}
  1436	
  1437		nct6683_init_device(data);
  1438		nct6683_setup_fans(data);
  1439		nct6683_setup_sensors(data);
  1440	
  1441		/* Register sysfs hooks */
  1442	
  1443		if (data->have_pwm) {
  1444			group = nct6683_create_attr_group(dev,
  1445							  &nct6683_pwm_template_group,
  1446							  fls(data->have_pwm));
  1447			if (IS_ERR(group))
  1448				return PTR_ERR(group);
  1449			data->groups[groups++] = group;
  1450		}
  1451	
  1452		if (data->in_num) {
  1453			group = nct6683_create_attr_group(dev,
  1454							  &nct6683_in_template_group,
  1455							  data->in_num);
  1456			if (IS_ERR(group))
  1457				return PTR_ERR(group);
  1458			data->groups[groups++] = group;
  1459		}
  1460	
  1461		if (data->have_fan) {
  1462			group = nct6683_create_attr_group(dev,
  1463							  &nct6683_fan_template_group,
  1464							  fls(data->have_fan));
  1465			if (IS_ERR(group))
  1466				return PTR_ERR(group);
  1467			data->groups[groups++] = group;
  1468		}
  1469	
  1470		if (data->temp_num) {
  1471			group = nct6683_create_attr_group(dev,
  1472							  &nct6683_temp_template_group,
  1473							  data->temp_num);
  1474			if (IS_ERR(group))
  1475				return PTR_ERR(group);
  1476			data->groups[groups++] = group;
  1477		}
  1478		data->groups[groups++] = &nct6683_group_other;
  1479	
  1480		if (data->customer_id == NCT6683_CUSTOMER_ID_INTEL)
  1481			scnprintf(build, sizeof(build), "%02x/%02x/%02x",
  1482				  nct6683_read(data, NCT6683_REG_BUILD_MONTH),
  1483				  nct6683_read(data, NCT6683_REG_BUILD_DAY),
  1484				  nct6683_read(data, NCT6683_REG_BUILD_YEAR));
  1485		else
  1486			scnprintf(build, sizeof(build), "%02d/%02d/%02d",
  1487				  nct6683_read(data, NCT6683_REG_BUILD_MONTH),
  1488				  nct6683_read(data, NCT6683_REG_BUILD_DAY),
  1489				  nct6683_read(data, NCT6683_REG_BUILD_YEAR));
  1490	
  1491		dev_info(dev, "%s EC firmware version %d.%d build %s\n",
  1492			 nct6683_chip_names[data->kind],
  1493			 nct6683_read(data, NCT6683_REG_VERSION_HI),
  1494			 nct6683_read(data, NCT6683_REG_VERSION_LO),
  1495			 build);
  1496	
  1497		hwmon_dev = devm_hwmon_device_register_with_groups(dev,
  1498				nct6683_device_names[data->kind], data, data->groups);
  1499	
  1500		ret = PTR_ERR_OR_ZERO(hwmon_dev);
  1501		if (ret)
  1502			return ret;
  1503	
  1504		if (data->kind == nct6686 && data->customer_id == NCT6683_CUSTOMER_ID_LENOVO) {
  1505			/* Watchdog initialization */
  1506			data->wdt.ops = &nct6686_wdt_ops;
  1507			data->wdt.info = &nct6686_wdt_info;
  1508	
  1509			data->wdt.timeout = WATCHDOG_TIMEOUT; /* Set default timeout */
  1510			data->wdt.min_timeout = MIN_TIMEOUT;
  1511			data->wdt.max_timeout = MAX_TIMEOUT;
  1512			data->wdt.parent = &pdev->dev;
  1513	
> 1514			watchdog_init_timeout(&data->wdt, timeout, &pdev->dev);
  1515			watchdog_set_nowayout(&data->wdt, nowayout);
  1516			watchdog_set_drvdata(&data->wdt, data);
  1517	
  1518			/* reset trigger status */
  1519			reg = nct6686_read(data, WDT_STS);
  1520			nct6686_write(data, WDT_STS, reg & ~WDT_STS_EVT_MSK);
  1521	
  1522			watchdog_stop_on_unregister(&data->wdt);
  1523	
> 1524			return devm_watchdog_register_device(dev, &data->wdt);
  1525		}
  1526		return ret;
  1527	}
  1528
kernel test robot Aug. 15, 2023, 4:29 p.m. UTC | #4
Hi David,

kernel test robot noticed the following build errors:

[auto build test ERROR on groeck-staging/hwmon-next]
[also build test ERROR on linus/master v6.5-rc6 next-20230815]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url:    https://github.com/intel-lab-lkp/linux/commits/David-Ober/hwmon-add-in-watchdog-for-nct6686/20230815-195946
base:   https://git.kernel.org/pub/scm/linux/kernel/git/groeck/linux-staging.git hwmon-next
patch link:    https://lore.kernel.org/r/20230815115515.286142-1-dober6023%40gmail.com
patch subject: [PATCH] hwmon: add in watchdog for nct6686
config: i386-randconfig-i013-20230815 (https://download.01.org/0day-ci/archive/20230816/202308160001.hx3WQNSU-lkp@intel.com/config)
compiler: gcc-12 (Debian 12.2.0-14) 12.2.0
reproduce: (https://download.01.org/0day-ci/archive/20230816/202308160001.hx3WQNSU-lkp@intel.com/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202308160001.hx3WQNSU-lkp@intel.com/

All errors (new ones prefixed by >>, old ones prefixed by <<):

WARNING: modpost: missing MODULE_DESCRIPTION() in vmlinux.o
WARNING: modpost: missing MODULE_DESCRIPTION() in kernel/locking/locktorture.o
WARNING: modpost: missing MODULE_DESCRIPTION() in kernel/rcu/rcutorture.o
WARNING: modpost: missing MODULE_DESCRIPTION() in kernel/rcu/rcuscale.o
WARNING: modpost: missing MODULE_DESCRIPTION() in kernel/rcu/refscale.o
WARNING: modpost: missing MODULE_DESCRIPTION() in kernel/torture.o
WARNING: modpost: missing MODULE_DESCRIPTION() in fs/nfs/nfsv4.o
WARNING: modpost: missing MODULE_DESCRIPTION() in fs/nls/nls_cp437.o
WARNING: modpost: missing MODULE_DESCRIPTION() in fs/nls/nls_cp852.o
WARNING: modpost: missing MODULE_DESCRIPTION() in fs/nls/nls_cp863.o
WARNING: modpost: missing MODULE_DESCRIPTION() in fs/nls/nls_cp866.o
WARNING: modpost: missing MODULE_DESCRIPTION() in fs/nls/nls_cp869.o
WARNING: modpost: missing MODULE_DESCRIPTION() in fs/nls/nls_cp932.o
WARNING: modpost: missing MODULE_DESCRIPTION() in fs/nls/nls_euc-jp.o
WARNING: modpost: missing MODULE_DESCRIPTION() in fs/nls/nls_cp936.o
WARNING: modpost: missing MODULE_DESCRIPTION() in fs/nls/nls_cp949.o
WARNING: modpost: missing MODULE_DESCRIPTION() in fs/nls/nls_iso8859-6.o
WARNING: modpost: missing MODULE_DESCRIPTION() in fs/nls/nls_iso8859-7.o
WARNING: modpost: missing MODULE_DESCRIPTION() in fs/nls/nls_iso8859-9.o
WARNING: modpost: missing MODULE_DESCRIPTION() in fs/nls/nls_iso8859-14.o
WARNING: modpost: missing MODULE_DESCRIPTION() in fs/nls/nls_utf8.o
WARNING: modpost: missing MODULE_DESCRIPTION() in fs/nls/mac-greek.o
WARNING: modpost: missing MODULE_DESCRIPTION() in fs/nls/mac-iceland.o
WARNING: modpost: missing MODULE_DESCRIPTION() in fs/nls/mac-romanian.o
WARNING: modpost: missing MODULE_DESCRIPTION() in fs/smb/common/cifs_arc4.o
WARNING: modpost: missing MODULE_DESCRIPTION() in fs/smb/common/cifs_md4.o
WARNING: modpost: missing MODULE_DESCRIPTION() in security/keys/trusted-keys/trusted.o
WARNING: modpost: missing MODULE_DESCRIPTION() in crypto/ecc.o
WARNING: modpost: missing MODULE_DESCRIPTION() in crypto/curve25519-generic.o
WARNING: modpost: missing MODULE_DESCRIPTION() in block/t10-pi.o
WARNING: modpost: missing MODULE_DESCRIPTION() in lib/crypto/libdes.o
WARNING: modpost: missing MODULE_DESCRIPTION() in lib/asn1_decoder.o
WARNING: modpost: missing MODULE_DESCRIPTION() in lib/asn1_encoder.o
WARNING: modpost: missing MODULE_DESCRIPTION() in drivers/video/backlight/rt4831-backlight.o
WARNING: modpost: missing MODULE_DESCRIPTION() in drivers/virtio/virtio_dma_buf.o
WARNING: modpost: missing MODULE_DESCRIPTION() in drivers/regulator/rt4831-regulator.o
WARNING: modpost: missing MODULE_DESCRIPTION() in drivers/tty/serial/8250/serial_cs.o
WARNING: modpost: missing MODULE_DESCRIPTION() in drivers/tty/synclink_gt.o
WARNING: modpost: missing MODULE_DESCRIPTION() in drivers/char/agp/intel-gtt.o
WARNING: modpost: missing MODULE_DESCRIPTION() in drivers/gpu/drm/tiny/cirrus.o
WARNING: modpost: missing MODULE_DESCRIPTION() in drivers/gpu/drm/drm_panel_orientation_quirks.o
WARNING: modpost: missing MODULE_DESCRIPTION() in drivers/base/regmap/regmap-i2c.o
WARNING: modpost: missing MODULE_DESCRIPTION() in drivers/base/regmap/regmap-slimbus.o
WARNING: modpost: missing MODULE_DESCRIPTION() in drivers/block/loop.o
WARNING: modpost: missing MODULE_DESCRIPTION() in drivers/mfd/arizona.o
WARNING: modpost: missing MODULE_DESCRIPTION() in drivers/mfd/timberdale.o
WARNING: modpost: missing MODULE_DESCRIPTION() in drivers/mfd/rt4831.o
WARNING: modpost: missing MODULE_DESCRIPTION() in drivers/cxl/cxl_mem.o
WARNING: modpost: missing MODULE_DESCRIPTION() in drivers/cxl/cxl_pmem.o
WARNING: modpost: missing MODULE_DESCRIPTION() in drivers/scsi/scsi_common.o
WARNING: modpost: missing MODULE_DESCRIPTION() in drivers/scsi/aha1542.o
WARNING: modpost: missing MODULE_DESCRIPTION() in drivers/scsi/aha1740.o
WARNING: modpost: missing MODULE_DESCRIPTION() in drivers/scsi/isci/isci.o
WARNING: modpost: missing MODULE_DESCRIPTION() in drivers/scsi/g_NCR5380.o
WARNING: modpost: missing MODULE_DESCRIPTION() in drivers/scsi/atp870u.o
WARNING: modpost: missing MODULE_DESCRIPTION() in drivers/nvme/target/nvmet.o
WARNING: modpost: missing MODULE_DESCRIPTION() in drivers/cdrom/cdrom.o
WARNING: modpost: missing MODULE_DESCRIPTION() in drivers/input/touchscreen/cyttsp_i2c_common.o
WARNING: modpost: missing MODULE_DESCRIPTION() in drivers/i2c/busses/i2c-ccgx-ucsi.o
WARNING: modpost: missing MODULE_DESCRIPTION() in drivers/hid/hid-apple.o
WARNING: modpost: missing MODULE_DESCRIPTION() in drivers/hid/hid-dr.o
WARNING: modpost: missing MODULE_DESCRIPTION() in drivers/hid/hid-emsff.o
WARNING: modpost: missing MODULE_DESCRIPTION() in drivers/hid/hid-maltron.o
WARNING: modpost: missing MODULE_DESCRIPTION() in drivers/hid/hid-pl.o
WARNING: modpost: missing MODULE_DESCRIPTION() in drivers/hid/hid-petalynx.o
WARNING: modpost: missing MODULE_DESCRIPTION() in drivers/hid/hid-semitek.o
WARNING: modpost: missing MODULE_DESCRIPTION() in drivers/hid/hid-speedlink.o
WARNING: modpost: missing MODULE_DESCRIPTION() in drivers/hid/hid-sunplus.o
WARNING: modpost: missing MODULE_DESCRIPTION() in drivers/hid/hid-tivo.o
WARNING: modpost: missing MODULE_DESCRIPTION() in drivers/hid/hid-twinhan.o
WARNING: modpost: missing MODULE_DESCRIPTION() in drivers/hid/hid-xinmo.o
WARNING: modpost: missing MODULE_DESCRIPTION() in drivers/hid/hid-zydacron.o
WARNING: modpost: missing MODULE_DESCRIPTION() in drivers/hid/hid-viewsonic.o
WARNING: modpost: missing MODULE_DESCRIPTION() in drivers/hid/hid-waltop.o
WARNING: modpost: missing MODULE_DESCRIPTION() in drivers/devfreq/governor_simpleondemand.o
WARNING: modpost: missing MODULE_DESCRIPTION() in drivers/devfreq/governor_performance.o
WARNING: modpost: missing MODULE_DESCRIPTION() in drivers/parport/parport.o
WARNING: modpost: missing MODULE_DESCRIPTION() in drivers/nvdimm/libnvdimm.o
WARNING: modpost: missing MODULE_DESCRIPTION() in drivers/nvdimm/nd_pmem.o
WARNING: modpost: missing MODULE_DESCRIPTION() in drivers/nvdimm/nd_e820.o
WARNING: modpost: missing MODULE_DESCRIPTION() in drivers/mtd/chips/cfi_util.o
WARNING: modpost: missing MODULE_DESCRIPTION() in drivers/mtd/chips/cfi_cmdset_0020.o
WARNING: modpost: missing MODULE_DESCRIPTION() in drivers/mtd/maps/map_funcs.o
WARNING: modpost: missing MODULE_DESCRIPTION() in drivers/uio/uio.o
WARNING: modpost: missing MODULE_DESCRIPTION() in drivers/uio/uio_cif.o
WARNING: modpost: missing MODULE_DESCRIPTION() in drivers/uio/uio_aec.o
WARNING: modpost: missing MODULE_DESCRIPTION() in drivers/uio/uio_netx.o
WARNING: modpost: missing MODULE_DESCRIPTION() in drivers/hwmon/corsair-cpro.o
WARNING: modpost: missing MODULE_DESCRIPTION() in samples/vfio-mdev/mtty.o
WARNING: modpost: missing MODULE_DESCRIPTION() in samples/vfio-mdev/mdpy.o
WARNING: modpost: missing MODULE_DESCRIPTION() in samples/vfio-mdev/mbochs.o
WARNING: modpost: missing MODULE_DESCRIPTION() in samples/kfifo/bytestream-example.o
WARNING: modpost: missing MODULE_DESCRIPTION() in samples/kfifo/dma-example.o
WARNING: modpost: missing MODULE_DESCRIPTION() in samples/kfifo/inttype-example.o
WARNING: modpost: missing MODULE_DESCRIPTION() in samples/kfifo/record-example.o
>> ERROR: modpost: "watchdog_init_timeout" [drivers/hwmon/nct6683.ko] undefined!
>> ERROR: modpost: "devm_watchdog_register_device" [drivers/hwmon/nct6683.ko] undefined!
kernel test robot Sept. 22, 2023, 4:59 p.m. UTC | #5
Hi David,

kernel test robot noticed the following build errors:

[auto build test ERROR on groeck-staging/hwmon-next]
[also build test ERROR on linus/master v6.6-rc2 next-20230921]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url:    https://github.com/intel-lab-lkp/linux/commits/David-Ober/hwmon-add-in-watchdog-for-nct6686/20230815-195946
base:   https://git.kernel.org/pub/scm/linux/kernel/git/groeck/linux-staging.git hwmon-next
patch link:    https://lore.kernel.org/r/20230815115515.286142-1-dober6023%40gmail.com
patch subject: [PATCH] hwmon: add in watchdog for nct6686
config: csky-randconfig-r026-20230816 (https://download.01.org/0day-ci/archive/20230923/202309230032.aGiDd9Or-lkp@intel.com/config)
compiler: csky-linux-gcc (GCC) 12.3.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20230923/202309230032.aGiDd9Or-lkp@intel.com/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202309230032.aGiDd9Or-lkp@intel.com/

All errors (new ones prefixed by >>):

   csky-linux-ld: drivers/hwmon/nct6683.o: in function `nct6683_probe':
>> nct6683.c:(.text+0x1594): undefined reference to `watchdog_init_timeout'
>> csky-linux-ld: nct6683.c:(.text+0x15fc): undefined reference to `watchdog_init_timeout'
>> csky-linux-ld: nct6683.c:(.text+0x1684): undefined reference to `devm_watchdog_register_device'
   csky-linux-ld: nct6683.c:(.text+0x16a4): undefined reference to `devm_watchdog_register_device'
diff mbox series

Patch

diff --git a/Documentation/hwmon/nct6683.rst b/Documentation/hwmon/nct6683.rst
index 2e1408d174bd..7421bc444365 100644
--- a/Documentation/hwmon/nct6683.rst
+++ b/Documentation/hwmon/nct6683.rst
@@ -3,7 +3,7 @@  Kernel driver nct6683
 
 Supported chips:
 
-  * Nuvoton NCT6683D/NCT6687D
+  * Nuvoton NCT6683D/NCT6686D/NCT6687D
 
     Prefix: 'nct6683'
 
@@ -49,6 +49,8 @@  The driver has only been tested with the Intel firmware, and by default
 only instantiates on Intel boards. To enable it on non-Intel boards,
 set the 'force' module parameter to 1.
 
+Implement the watchdog functionality of the NCT6686D eSIO chip
+
 Tested Boards and Firmware Versions
 -----------------------------------
 
@@ -63,4 +65,5 @@  Intel DH87MC	NCT6683D EC firmware version 1.0 build 04/03/13
 Intel DB85FL	NCT6683D EC firmware version 1.0 build 04/03/13
 ASRock X570	NCT6683D EC firmware version 1.0 build 06/28/19
 MSI B550	NCT6687D EC firmware version 1.0 build 05/07/20
+LENOVO M90n-1	NCT6686D EC firmware version 9.0 build 04/21/21
 =============== ===============================================
diff --git a/drivers/hwmon/nct6683.c b/drivers/hwmon/nct6683.c
index f673f7d07941..eb95b91c4d39 100644
--- a/drivers/hwmon/nct6683.c
+++ b/drivers/hwmon/nct6683.c
@@ -24,15 +24,16 @@ 
 #include <linux/acpi.h>
 #include <linux/delay.h>
 #include <linux/err.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
 #include <linux/init.h>
 #include <linux/io.h>
 #include <linux/jiffies.h>
-#include <linux/hwmon.h>
-#include <linux/hwmon-sysfs.h>
 #include <linux/module.h>
 #include <linux/mutex.h>
 #include <linux/platform_device.h>
 #include <linux/slab.h>
+#include <linux/watchdog.h>
 
 enum kinds { nct6683, nct6686, nct6687 };
 
@@ -73,6 +74,34 @@  static const char * const nct6683_chip_names[] = {
 #define SIO_NCT6687_ID		0xd590
 #define SIO_ID_MASK		0xFFF0
 
+#define WDT_CFG			0x828    /* W/O Lock Watchdog Register */
+#define WDT_CNT			0x829    /* R/W Watchdog Timer Register */
+#define WDT_STS			0x82A    /* R/O Watchdog Status Register */
+#define WDT_STS_EVT_POS		(0)
+#define WDT_STS_EVT_MSK		(0x3 << WDT_STS_EVT_POS)
+#define WDT_SOFT_EN		0x87    /* Enable soft watchdog timer */
+#define WDT_SOFT_DIS		0xAA    /* Disable soft watchdog timer */
+
+#define WATCHDOG_TIMEOUT	60      /* 1 minute default timeout */
+
+/* The timeout range is 1-255 seconds */
+#define MIN_TIMEOUT		1
+#define MAX_TIMEOUT		255
+
+static int timeout;
+module_param(timeout, int, 0);
+MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds. 1 <= timeout <= 255, default="
+		 __MODULE_STRING(WATCHDOG_TIMEOUT) ".");
+
+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) ")");
+
+static int early_disable;
+module_param(early_disable, int, 0);
+MODULE_PARM_DESC(early_disable, "Disable watchdog at boot time (default=0)");
+
 static inline void
 superio_outb(int ioreg, int reg, int val)
 {
@@ -171,10 +200,10 @@  superio_exit(int ioreg)
 
 #define NCT6683_REG_CUSTOMER_ID		0x602
 #define NCT6683_CUSTOMER_ID_INTEL	0x805
+#define NCT6683_CUSTOMER_ID_LENOVO	0x1101
 #define NCT6683_CUSTOMER_ID_MITAC	0xa0e
 #define NCT6683_CUSTOMER_ID_MSI		0x201
-#define NCT6683_CUSTOMER_ID_MSI2	0x200
-#define NCT6683_CUSTOMER_ID_ASROCK		0xe2c
+#define NCT6683_CUSTOMER_ID_ASROCK	0xe2c
 #define NCT6683_CUSTOMER_ID_ASROCK2	0xe1b
 
 #define NCT6683_REG_BUILD_YEAR		0x604
@@ -183,6 +212,9 @@  superio_exit(int ioreg)
 #define NCT6683_REG_SERIAL		0x607
 #define NCT6683_REG_VERSION_HI		0x608
 #define NCT6683_REG_VERSION_LO		0x609
+#define NCT6686_PAGE_REG_OFFSET		0
+#define NCT6686_ADDR_REG_OFFSET		1
+#define NCT6686_DATA_REG_OFFSET		2
 
 #define NCT6683_REG_CR_CASEOPEN		0xe8
 #define NCT6683_CR_CASEOPEN_MASK	(1 << 7)
@@ -304,6 +336,7 @@  struct nct6683_data {
 
 	struct device *hwmon_dev;
 	const struct attribute_group *groups[6];
+	struct watchdog_device wdt;
 
 	int temp_num;			/* number of temperature attributes */
 	u8 temp_index[NCT6683_NUM_REG_MON];
@@ -518,6 +551,39 @@  static void nct6683_write(struct nct6683_data *data, u16 reg, u16 value)
 	outb_p(value & 0xff, data->addr + EC_DATA_REG);
 }
 
+static inline void nct6686_wdt_set_bank(int base_addr, u16 reg)
+{
+	outb_p(0xFF, base_addr + NCT6686_PAGE_REG_OFFSET);
+	outb_p(reg >> 8, base_addr + NCT6686_PAGE_REG_OFFSET);
+}
+
+/* Not strictly necessary, but play it safe for now */
+static inline void nct6686_wdt_reset_bank(int base_addr, u16 reg)
+{
+	if (reg & 0xff00)
+		outb_p(0xFF, base_addr + NCT6686_PAGE_REG_OFFSET);
+}
+
+static u8 nct6686_read(struct nct6683_data *data, u16 reg)
+{
+	u8 res;
+
+	nct6686_wdt_set_bank(data->addr, reg);
+	outb_p(reg & 0xff, data->addr + NCT6686_ADDR_REG_OFFSET);
+	res = inb_p(data->addr + NCT6686_DATA_REG_OFFSET);
+
+	nct6686_wdt_reset_bank(data->addr, reg);
+	return res;
+}
+
+static void nct6686_write(struct nct6683_data *data, u16 reg, u8 value)
+{
+	nct6686_wdt_set_bank(data->addr, reg);
+	outb_p(reg & 0xff, data->addr + NCT6686_ADDR_REG_OFFSET);
+	outb_p(value & 0xff, data->addr + NCT6686_DATA_REG_OFFSET);
+	nct6686_wdt_reset_bank(data->addr, reg);
+}
+
 static int get_in_reg(struct nct6683_data *data, int nr, int index)
 {
 	int ch = data->in_index[index];
@@ -680,11 +746,12 @@  static umode_t nct6683_in_is_visible(struct kobject *kobj,
 	int nr = index % 4;	/* attribute */
 
 	/*
-	 * Voltage limits exist for Intel boards,
+	 * Voltage limits exist for Intel and Lenovo boards,
 	 * but register location and encoding is unknown
 	 */
 	if ((nr == 2 || nr == 3) &&
-	    data->customer_id == NCT6683_CUSTOMER_ID_INTEL)
+	    (data->customer_id == NCT6683_CUSTOMER_ID_INTEL ||
+	     data->customer_id == NCT6683_CUSTOMER_ID_LENOVO))
 		return 0;
 
 	return attr->mode;
@@ -1186,6 +1253,139 @@  static void nct6683_setup_sensors(struct nct6683_data *data)
 	}
 }
 
+/*
+ * Watchdog Functions
+ */
+static int nct6686_wdt_enable(struct watchdog_device *wdog, bool enable)
+{
+	struct nct6683_data *data = watchdog_get_drvdata(wdog);
+
+	u_char reg;
+
+	mutex_lock(&data->update_lock);
+	reg = nct6686_read(data, WDT_CFG);
+
+	if (enable) {
+		nct6686_write(data, WDT_CFG, reg | 0x3);
+		mutex_unlock(&data->update_lock);
+		return 0;
+	}
+
+	nct6686_write(data, WDT_CFG, reg & ~BIT(0));
+	mutex_unlock(&data->update_lock);
+
+	return 0;
+}
+
+static int nct6686_wdt_set_time(struct watchdog_device *wdog)
+{
+	struct nct6683_data *data = watchdog_get_drvdata(wdog);
+
+	mutex_lock(&data->update_lock);
+	nct6686_write(data, WDT_CNT, wdog->timeout);
+	mutex_unlock(&data->update_lock);
+
+	if (wdog->timeout) {
+		nct6686_wdt_enable(wdog, true);
+		return 0;
+	}
+
+	nct6686_wdt_enable(wdog, false);
+	return 0;
+}
+
+static int nct6686_wdt_start(struct watchdog_device *wdt)
+{
+	struct nct6683_data *data = watchdog_get_drvdata(wdt);
+	u_char reg;
+
+	nct6686_wdt_set_time(wdt);
+
+	/* Enable soft watchdog timer */
+	mutex_lock(&data->update_lock);
+	/* reset trigger status */
+	reg = nct6686_read(data, WDT_STS);
+	nct6686_write(data, WDT_STS, reg & ~WDT_STS_EVT_MSK);
+	mutex_unlock(&data->update_lock);
+	return 0;
+}
+
+static int nct6686_wdt_stop(struct watchdog_device *wdt)
+{
+	struct nct6683_data *data = watchdog_get_drvdata(wdt);
+
+	mutex_lock(&data->update_lock);
+	nct6686_write(data, WDT_CFG, WDT_SOFT_DIS);
+	mutex_unlock(&data->update_lock);
+	return 0;
+}
+
+static int nct6686_wdt_set_timeout(struct watchdog_device *wdt,
+				   unsigned int timeout)
+{
+	struct nct6683_data *data = watchdog_get_drvdata(wdt);
+
+	wdt->timeout = timeout;
+	mutex_lock(&data->update_lock);
+	nct6686_write(data, WDT_CNT, timeout);
+	mutex_unlock(&data->update_lock);
+	return 0;
+}
+
+static int nct6686_wdt_ping(struct watchdog_device *wdt)
+{
+	struct nct6683_data *data = watchdog_get_drvdata(wdt);
+	int timeout;
+
+	/*
+	 * Note:
+	 * NCT6686 does not support refreshing WDT_TIMER_REG register when
+	 * the watchdog is active. Please disable watchdog before feeding
+	 * the watchdog and enable it again.
+	 */
+	/* Disable soft watchdog timer */
+	nct6686_wdt_enable(wdt, false);
+
+	/* feed watchdog */
+	timeout = wdt->timeout;
+	mutex_lock(&data->update_lock);
+	nct6686_write(data, WDT_CNT, timeout);
+	mutex_unlock(&data->update_lock);
+
+	/* Enable soft watchdog timer */
+	nct6686_wdt_enable(wdt, true);
+	return 0;
+}
+
+static unsigned int nct6686_wdt_get_timeleft(struct watchdog_device *wdt)
+{
+	struct nct6683_data *data = watchdog_get_drvdata(wdt);
+	int ret;
+
+	mutex_lock(&data->update_lock);
+	ret = nct6686_read(data, WDT_CNT);
+	mutex_unlock(&data->update_lock);
+	if (ret < 0)
+		return 0;
+
+	return ret;
+}
+
+static const struct watchdog_info nct6686_wdt_info = {
+	.options        = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING |
+			  WDIOF_MAGICCLOSE,
+	.identity       = "nct6686 watchdog",
+};
+
+static const struct watchdog_ops nct6686_wdt_ops = {
+	.owner          = THIS_MODULE,
+	.start          = nct6686_wdt_start,
+	.stop           = nct6686_wdt_stop,
+	.ping           = nct6686_wdt_ping,
+	.set_timeout    = nct6686_wdt_set_timeout,
+	.get_timeleft   = nct6686_wdt_get_timeleft,
+};
+
 static int nct6683_probe(struct platform_device *pdev)
 {
 	struct device *dev = &pdev->dev;
@@ -1195,7 +1395,9 @@  static int nct6683_probe(struct platform_device *pdev)
 	struct device *hwmon_dev;
 	struct resource *res;
 	int groups = 0;
+	int ret;
 	char build[16];
+	u_char reg;
 
 	res = platform_get_resource(pdev, IORESOURCE_IO, 0);
 	if (!devm_request_region(dev, res->start, IOREGION_LENGTH, DRVNAME))
@@ -1215,14 +1417,14 @@  static int nct6683_probe(struct platform_device *pdev)
 
 	/* By default only instantiate driver if the customer ID is known */
 	switch (data->customer_id) {
+	case NCT6683_CUSTOMER_ID_LENOVO:
+		break;
 	case NCT6683_CUSTOMER_ID_INTEL:
 		break;
 	case NCT6683_CUSTOMER_ID_MITAC:
 		break;
 	case NCT6683_CUSTOMER_ID_MSI:
 		break;
-	case NCT6683_CUSTOMER_ID_MSI2:
-		break;
 	case NCT6683_CUSTOMER_ID_ASROCK:
 		break;
 	case NCT6683_CUSTOMER_ID_ASROCK2:
@@ -1294,7 +1496,34 @@  static int nct6683_probe(struct platform_device *pdev)
 
 	hwmon_dev = devm_hwmon_device_register_with_groups(dev,
 			nct6683_device_names[data->kind], data, data->groups);
-	return PTR_ERR_OR_ZERO(hwmon_dev);
+
+	ret = PTR_ERR_OR_ZERO(hwmon_dev);
+	if (ret)
+		return ret;
+
+	if (data->kind == nct6686 && data->customer_id == NCT6683_CUSTOMER_ID_LENOVO) {
+		/* Watchdog initialization */
+		data->wdt.ops = &nct6686_wdt_ops;
+		data->wdt.info = &nct6686_wdt_info;
+
+		data->wdt.timeout = WATCHDOG_TIMEOUT; /* Set default timeout */
+		data->wdt.min_timeout = MIN_TIMEOUT;
+		data->wdt.max_timeout = MAX_TIMEOUT;
+		data->wdt.parent = &pdev->dev;
+
+		watchdog_init_timeout(&data->wdt, timeout, &pdev->dev);
+		watchdog_set_nowayout(&data->wdt, nowayout);
+		watchdog_set_drvdata(&data->wdt, data);
+
+		/* reset trigger status */
+		reg = nct6686_read(data, WDT_STS);
+		nct6686_write(data, WDT_STS, reg & ~WDT_STS_EVT_MSK);
+
+		watchdog_stop_on_unregister(&data->wdt);
+
+		return devm_watchdog_register_device(dev, &data->wdt);
+	}
+	return ret;
 }
 
 #ifdef CONFIG_PM