From a648f2856518f8884a7798469faf755d0f5dcd50 Mon Sep 17 00:00:00 2001
From: Michael Shych <michaelsh@mellanox.com>
Date: Mon, 17 Dec 2018 12:32:45 +0000
Subject: [PATCH v1 mlx-wdt 1/1] watchdog: mlx-wdt: introduce watchdog driver
 for Mellanox systems

Watchdog driver for Mellanox watchdog devices, implemented in
programmable logic device.

Main and auxiliary watchdog devices can exist on the same system.
There are several actions that can be defined in the watchdog:
system reset, start fans on full speed and increase counter.
The last 2 actions are performed without system reset.
Actions without reset are provided for auxiliary watchdog devices,
which is optional.
Access to CPLD registers is performed through generic
regmap interface.

There are 2 types of HW CPLD watchdog implementations.
Type 1: actual HW timeout can be defined as power of 2 msec.
e.g. timeout 20 sec will be rounded up to 32768 msec.;
maximum timeout period is 32 sec (32768 msec.);
get time-left isn't supported
Type 2: actual HW timeout is defined in sec. and it's a same as
user defined timeout;
maximum timeout is 255 sec;
get time-left is supported;

Watchdog driver is probed from common mlx_platform driver.

Signed-off-by: Michael Shych <michaelsh@mellanox.com>
---
 drivers/platform/x86/mlx-platform.c  | 202 +++++++++++++++++-
 drivers/watchdog/Kconfig             |  15 ++
 drivers/watchdog/Makefile            |   1 +
 drivers/watchdog/mlx_wdt.c           | 391 +++++++++++++++++++++++++++++++++++
 include/linux/platform_data/mlxreg.h |   6 +
 5 files changed, 612 insertions(+), 3 deletions(-)
 create mode 100644 drivers/watchdog/mlx_wdt.c

diff --git a/drivers/platform/x86/mlx-platform.c b/drivers/platform/x86/mlx-platform.c
index 6e150b4..fc8d655 100644
--- a/drivers/platform/x86/mlx-platform.c
+++ b/drivers/platform/x86/mlx-platform.c
@@ -55,6 +55,14 @@
 #define MLXPLAT_CPLD_LPC_REG_FAN_OFFSET		0x88
 #define MLXPLAT_CPLD_LPC_REG_FAN_EVENT_OFFSET	0x89
 #define MLXPLAT_CPLD_LPC_REG_FAN_MASK_OFFSET	0x8a
+#define MLXPLAT_CPLD_LPC_REG_WD_CLEAR_OFFSET	0xc7
+#define MLXPLAT_CPLD_LPC_REG_WD_CLEAR_WP_OFFSET	0xc8
+#define MLXPLAT_CPLD_LPC_REG_WD1_TMR_OFFSET	0xc9
+#define MLXPLAT_CPLD_LPC_REG_WD1_ACT_OFFSET	0xcb
+#define MLXPLAT_CPLD_LPC_REG_WD2_TMR_OFFSET	0xcd
+#define MLXPLAT_CPLD_LPC_REG_WD2_ACT_OFFSET	0xcf
+#define MLXPLAT_CPLD_LPC_REG_WD3_TMR_OFFSET	0xd1
+#define MLXPLAT_CPLD_LPC_REG_WD3_ACT_OFFSET	0xd2
 #define MLXPLAT_CPLD_LPC_REG_PWM1_OFFSET	0xe3
 #define MLXPLAT_CPLD_LPC_REG_TACHO1_OFFSET	0xe4
 #define MLXPLAT_CPLD_LPC_REG_TACHO2_OFFSET	0xe5
@@ -128,6 +136,18 @@
 #define MLXPLAT_CPLD_FAN3_DEFAULT_NR		13
 #define MLXPLAT_CPLD_FAN4_DEFAULT_NR		14
 
+/* Masks and default values for watchdogs */
+#define MLXPLAT_CPLD_WD1_CLEAR_MASK	GENMASK(7, 1)
+#define MLXPLAT_CPLD_WD2_CLEAR_MASK	(GENMASK(7, 0) & ~BIT(1))
+
+#define MLXPLAT_CPLD_WD_TYPE1_TO_MASK	GENMASK(7, 4)
+#define MLXPLAT_CPLD_WD_TYPE2_TO_MASK	0
+#define MLXPLAT_CPLD_WD_RESET_ACT_MASK	GENMASK(7, 1)
+#define MLXPLAT_CPLD_WD_FAN_ACT_MASK	(GENMASK(7, 0) & ~BIT(4))
+#define MLXPLAT_CPLD_WD_COUNT_ACT_MASK	(GENMASK(7, 0) & ~BIT(7))
+#define MLXPLAT_CPLD_WD_DFLT_TIMEOUT	30
+#define MLXPLAT_CPLD_WD_MAX_DEVS	2
+
 /* mlxplat_priv - platform private data
  * @pdev_i2c - i2c controller platform device
  * @pdev_mux - array of mux platform devices
@@ -135,6 +155,7 @@
  * @pdev_led - led platform devices
  * @pdev_io_regs - register access platform devices
  * @pdev_fan - FAN platform devices
+ * @pdev_wd - array of watchdog platform devices
  */
 struct mlxplat_priv {
 	struct platform_device *pdev_i2c;
@@ -143,6 +164,7 @@ struct mlxplat_priv {
 	struct platform_device *pdev_led;
 	struct platform_device *pdev_io_regs;
 	struct platform_device *pdev_fan;
+	struct platform_device *pdev_wd[MLXPLAT_CPLD_WD_MAX_DEVS];
 };
 
 /* Regions for LPC I2C controller and LPC base register space */
@@ -1340,6 +1362,132 @@ static struct mlxreg_core_platform_data mlxplat_default_fan_data = {
 		.counter = ARRAY_SIZE(mlxplat_mlxcpld_default_fan_data),
 };
 
+/* Type1 watchdog implementation on MSN2700, MSN2100 and MSN2140 systems */
+static struct mlxreg_core_data mlxplat_mlxcpld_wd_main_regs_type1[] = {
+	{
+		.label = "action",
+		.reg = MLXPLAT_CPLD_LPC_REG_WD1_ACT_OFFSET,
+		.mask = MLXPLAT_CPLD_WD_RESET_ACT_MASK,
+		.bit = 0,
+	},
+	{
+		.label = "timeout",
+		.reg = MLXPLAT_CPLD_LPC_REG_WD1_TMR_OFFSET,
+		.mask = MLXPLAT_CPLD_WD_TYPE1_TO_MASK,
+		.health_cntr = MLXPLAT_CPLD_WD_DFLT_TIMEOUT,
+	},
+	{
+		.label = "ping",
+		.reg = MLXPLAT_CPLD_LPC_REG_WD_CLEAR_OFFSET,
+		.mask = MLXPLAT_CPLD_WD1_CLEAR_MASK,
+		.bit = 0,
+	},
+	{
+		.label = "reset",
+		.reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET,
+		.mask = GENMASK(7, 0) & ~BIT(6),
+		.bit = 6,
+	},
+};
+
+static struct mlxreg_core_data mlxplat_mlxcpld_wd_aux_regs_type1[] = {
+	{
+		.label = "action",
+		.reg = MLXPLAT_CPLD_LPC_REG_WD2_ACT_OFFSET,
+		.mask = MLXPLAT_CPLD_WD_FAN_ACT_MASK,
+		.bit = 4,
+	},
+	{
+		.label = "timeout",
+		.reg = MLXPLAT_CPLD_LPC_REG_WD2_TMR_OFFSET,
+		.mask = MLXPLAT_CPLD_WD_TYPE1_TO_MASK,
+		.health_cntr = MLXPLAT_CPLD_WD_DFLT_TIMEOUT,
+	},
+	{
+		.label = "ping",
+		.reg = MLXPLAT_CPLD_LPC_REG_WD_CLEAR_OFFSET,
+		.mask = MLXPLAT_CPLD_WD1_CLEAR_MASK,
+		.bit = 1,
+	},
+};
+
+static struct mlxreg_core_platform_data mlxplat_mlxcpld_wd_set_type1[] = {
+	{
+		.data = mlxplat_mlxcpld_wd_main_regs_type1,
+		.counter = ARRAY_SIZE(mlxplat_mlxcpld_wd_main_regs_type1),
+		.identity = "mlx-wdt-main",
+	},
+	{
+		.data = mlxplat_mlxcpld_wd_aux_regs_type1,
+		.counter = ARRAY_SIZE(mlxplat_mlxcpld_wd_aux_regs_type1),
+		.identity = "mlx-wdt-aux",
+	},
+};
+
+/* Type2 watchdog implementation on MSB8700 and up systems
+ * To differentiate: ping reg == action reg
+ */
+static struct mlxreg_core_data mlxplat_mlxcpld_wd_main_regs_type2[] = {
+	{
+		.label = "action",
+		.reg = MLXPLAT_CPLD_LPC_REG_WD2_ACT_OFFSET,
+		.mask = MLXPLAT_CPLD_WD_RESET_ACT_MASK,
+		.bit = 0,
+	},
+	{
+		.label = "timeout",
+		.reg = MLXPLAT_CPLD_LPC_REG_WD2_TMR_OFFSET,
+		.mask = MLXPLAT_CPLD_WD_TYPE2_TO_MASK,
+		.health_cntr = MLXPLAT_CPLD_WD_DFLT_TIMEOUT,
+	},
+	{
+		.label = "ping",
+		.reg = MLXPLAT_CPLD_LPC_REG_WD2_ACT_OFFSET,
+		.mask = MLXPLAT_CPLD_WD_RESET_ACT_MASK,
+		.bit = 0,
+	},
+	{
+		.label = "reset",
+		.reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET,
+		.mask = GENMASK(7, 0) & ~BIT(6),
+		.bit = 6,
+	},
+};
+
+static struct mlxreg_core_data mlxplat_mlxcpld_wd_aux_regs_type2[] = {
+	{
+		.label = "action",
+		.reg = MLXPLAT_CPLD_LPC_REG_WD3_ACT_OFFSET,
+		.mask = MLXPLAT_CPLD_WD_FAN_ACT_MASK,
+		.bit = 4,
+	},
+	{
+		.label = "timeout",
+		.reg = MLXPLAT_CPLD_LPC_REG_WD3_TMR_OFFSET,
+		.mask = MLXPLAT_CPLD_WD_TYPE2_TO_MASK,
+		.health_cntr = MLXPLAT_CPLD_WD_DFLT_TIMEOUT,
+	},
+	{
+		.label = "ping",
+		.reg = MLXPLAT_CPLD_LPC_REG_WD3_ACT_OFFSET,
+		.mask = MLXPLAT_CPLD_WD_FAN_ACT_MASK,
+		.bit = 4,
+	},
+};
+
+static struct mlxreg_core_platform_data mlxplat_mlxcpld_wd_set_type2[] = {
+	{
+		.data = mlxplat_mlxcpld_wd_main_regs_type2,
+		.counter = ARRAY_SIZE(mlxplat_mlxcpld_wd_main_regs_type2),
+		.identity = "mlx-wdt-main",
+	},
+	{
+		.data = mlxplat_mlxcpld_wd_aux_regs_type2,
+		.counter = ARRAY_SIZE(mlxplat_mlxcpld_wd_aux_regs_type2),
+		.identity = "mlx-wdt-aux",
+	},
+};
+
 static bool mlxplat_mlxcpld_writeable_reg(struct device *dev, unsigned int reg)
 {
 	switch (reg) {
@@ -1362,6 +1510,14 @@ static bool mlxplat_mlxcpld_writeable_reg(struct device *dev, unsigned int reg)
 	case MLXPLAT_CPLD_LPC_REG_PWR_MASK_OFFSET:
 	case MLXPLAT_CPLD_LPC_REG_FAN_EVENT_OFFSET:
 	case MLXPLAT_CPLD_LPC_REG_FAN_MASK_OFFSET:
+	case MLXPLAT_CPLD_LPC_REG_WD_CLEAR_OFFSET:
+	case MLXPLAT_CPLD_LPC_REG_WD_CLEAR_WP_OFFSET:
+	case MLXPLAT_CPLD_LPC_REG_WD1_TMR_OFFSET:
+	case MLXPLAT_CPLD_LPC_REG_WD1_ACT_OFFSET:
+	case MLXPLAT_CPLD_LPC_REG_WD2_TMR_OFFSET:
+	case MLXPLAT_CPLD_LPC_REG_WD2_ACT_OFFSET:
+	case MLXPLAT_CPLD_LPC_REG_WD3_TMR_OFFSET:
+	case MLXPLAT_CPLD_LPC_REG_WD3_ACT_OFFSET:
 	case MLXPLAT_CPLD_LPC_REG_PWM1_OFFSET:
 	case MLXPLAT_CPLD_LPC_REG_PWM_CONTROL_OFFSET:
 		return true;
@@ -1404,6 +1560,14 @@ static bool mlxplat_mlxcpld_readable_reg(struct device *dev, unsigned int reg)
 	case MLXPLAT_CPLD_LPC_REG_FAN_OFFSET:
 	case MLXPLAT_CPLD_LPC_REG_FAN_EVENT_OFFSET:
 	case MLXPLAT_CPLD_LPC_REG_FAN_MASK_OFFSET:
+	case MLXPLAT_CPLD_LPC_REG_WD_CLEAR_OFFSET:
+	case MLXPLAT_CPLD_LPC_REG_WD_CLEAR_WP_OFFSET:
+	case MLXPLAT_CPLD_LPC_REG_WD1_TMR_OFFSET:
+	case MLXPLAT_CPLD_LPC_REG_WD1_ACT_OFFSET:
+	case MLXPLAT_CPLD_LPC_REG_WD2_TMR_OFFSET:
+	case MLXPLAT_CPLD_LPC_REG_WD2_ACT_OFFSET:
+	case MLXPLAT_CPLD_LPC_REG_WD3_TMR_OFFSET:
+	case MLXPLAT_CPLD_LPC_REG_WD3_ACT_OFFSET:
 	case MLXPLAT_CPLD_LPC_REG_PWM1_OFFSET:
 	case MLXPLAT_CPLD_LPC_REG_TACHO1_OFFSET:
 	case MLXPLAT_CPLD_LPC_REG_TACHO2_OFFSET:
@@ -1460,6 +1624,8 @@ static bool mlxplat_mlxcpld_volatile_reg(struct device *dev, unsigned int reg)
 	case MLXPLAT_CPLD_LPC_REG_FAN_OFFSET:
 	case MLXPLAT_CPLD_LPC_REG_FAN_EVENT_OFFSET:
 	case MLXPLAT_CPLD_LPC_REG_FAN_MASK_OFFSET:
+	case MLXPLAT_CPLD_LPC_REG_WD2_TMR_OFFSET:
+	case MLXPLAT_CPLD_LPC_REG_WD3_TMR_OFFSET:
 	case MLXPLAT_CPLD_LPC_REG_PWM1_OFFSET:
 	case MLXPLAT_CPLD_LPC_REG_TACHO1_OFFSET:
 	case MLXPLAT_CPLD_LPC_REG_TACHO2_OFFSET:
@@ -1487,6 +1653,7 @@ static const struct reg_default mlxplat_mlxcpld_regmap_default[] = {
 	{ MLXPLAT_CPLD_LPC_REG_WP1_OFFSET, 0x00 },
 	{ MLXPLAT_CPLD_LPC_REG_WP2_OFFSET, 0x00 },
 	{ MLXPLAT_CPLD_LPC_REG_PWM_CONTROL_OFFSET, 0x00 },
+	{ MLXPLAT_CPLD_LPC_REG_WD_CLEAR_WP_OFFSET, 0x00 },
 };
 
 struct mlxplat_mlxcpld_regmap_context {
@@ -1536,6 +1703,8 @@ static struct mlxreg_core_hotplug_platform_data *mlxplat_hotplug;
 static struct mlxreg_core_platform_data *mlxplat_led;
 static struct mlxreg_core_platform_data *mlxplat_regs_io;
 static struct mlxreg_core_platform_data *mlxplat_fan;
+static struct mlxreg_core_platform_data
+	*mlxplat_wd_data[MLXPLAT_CPLD_WD_MAX_DEVS];
 
 static int __init mlxplat_dmi_default_matched(const struct dmi_system_id *dmi)
 {
@@ -1551,6 +1720,7 @@ static int __init mlxplat_dmi_default_matched(const struct dmi_system_id *dmi)
 		mlxplat_default_channels[i - 1][MLXPLAT_CPLD_GRP_CHNL_NUM - 1];
 	mlxplat_led = &mlxplat_default_led_data;
 	mlxplat_regs_io = &mlxplat_default_regs_io_data;
+	mlxplat_wd_data[0] = &mlxplat_mlxcpld_wd_set_type1[0];
 
 	return 1;
 };
@@ -1569,6 +1739,7 @@ static int __init mlxplat_dmi_msn21xx_matched(const struct dmi_system_id *dmi)
 		mlxplat_msn21xx_channels[MLXPLAT_CPLD_GRP_CHNL_NUM - 1];
 	mlxplat_led = &mlxplat_msn21xx_led_data;
 	mlxplat_regs_io = &mlxplat_msn21xx_regs_io_data;
+	mlxplat_wd_data[0] = &mlxplat_mlxcpld_wd_set_type1[0];
 
 	return 1;
 };
@@ -1587,6 +1758,7 @@ static int __init mlxplat_dmi_msn274x_matched(const struct dmi_system_id *dmi)
 		mlxplat_msn21xx_channels[MLXPLAT_CPLD_GRP_CHNL_NUM - 1];
 	mlxplat_led = &mlxplat_default_led_data;
 	mlxplat_regs_io = &mlxplat_msn21xx_regs_io_data;
+	mlxplat_wd_data[0] = &mlxplat_mlxcpld_wd_set_type1[0];
 
 	return 1;
 };
@@ -1605,6 +1777,7 @@ static int __init mlxplat_dmi_msn201x_matched(const struct dmi_system_id *dmi)
 		mlxplat_default_channels[i - 1][MLXPLAT_CPLD_GRP_CHNL_NUM - 1];
 	mlxplat_led = &mlxplat_msn21xx_led_data;
 	mlxplat_regs_io = &mlxplat_msn21xx_regs_io_data;
+	mlxplat_wd_data[0] = &mlxplat_mlxcpld_wd_set_type1[0];
 
 	return 1;
 };
@@ -1624,6 +1797,8 @@ static int __init mlxplat_dmi_qmb7xx_matched(const struct dmi_system_id *dmi)
 	mlxplat_led = &mlxplat_default_ng_led_data;
 	mlxplat_regs_io = &mlxplat_default_ng_regs_io_data;
 	mlxplat_fan = &mlxplat_default_fan_data;
+	for (i = 0; i < ARRAY_SIZE(mlxplat_mlxcpld_wd_set_type2); i++)
+		mlxplat_wd_data[i] = &mlxplat_mlxcpld_wd_set_type2[i];
 
 	return 1;
 };
@@ -1906,15 +2081,33 @@ static int __init mlxplat_init(void)
 		}
 	}
 
+	/* Add WD drivers. */
+	for (j = 0; j < MLXPLAT_CPLD_WD_MAX_DEVS; j++) {
+		if (mlxplat_wd_data[j]) {
+			mlxplat_wd_data[j]->regmap = mlxplat_hotplug->regmap;
+			priv->pdev_wd[j] = platform_device_register_resndata(
+						&mlxplat_dev->dev,
+						"mlx-wdt", j, NULL, 0,
+						mlxplat_wd_data[j],
+						sizeof(*mlxplat_wd_data[j]));
+			if (IS_ERR(priv->pdev_wd[j])) {
+				err = PTR_ERR(priv->pdev_wd[j]);
+				goto fail_platform_wd_register;
+			}
+		}
+	}
+
 	/* Sync registers with hardware. */
 	regcache_mark_dirty(mlxplat_hotplug->regmap);
 	err = regcache_sync(mlxplat_hotplug->regmap);
 	if (err)
-		goto fail_platform_fan_register;
+		goto fail_platform_wd_register;
 
 	return 0;
 
-fail_platform_fan_register:
+fail_platform_wd_register:
+	while (--j >= 0)
+		platform_device_unregister(priv->pdev_wd[j]);
 	if (mlxplat_fan)
 		platform_device_unregister(priv->pdev_fan);
 fail_platform_io_regs_register:
@@ -1946,7 +2139,10 @@ static void __exit mlxplat_exit(void)
 		platform_device_unregister(priv->pdev_io_regs);
 	platform_device_unregister(priv->pdev_led);
 	platform_device_unregister(priv->pdev_hotplug);
-
+	for (i = MLXPLAT_CPLD_WD_MAX_DEVS - 1; i >= 0 ; i--) {
+		if (mlxplat_wd_data[i])
+			platform_device_unregister(priv->pdev_wd[i]);
+	}
 	for (i = ARRAY_SIZE(mlxplat_mux_data) - 1; i >= 0 ; i--)
 		platform_device_unregister(priv->pdev_mux[i]);
 
diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig
index 3eb58cb..ada5a33 100644
--- a/drivers/watchdog/Kconfig
+++ b/drivers/watchdog/Kconfig
@@ -141,6 +141,21 @@ config MENF21BMC_WATCHDOG
 	  This driver can also be built as a module. If so the module
 	  will be called menf21bmc_wdt.
 
+config MLX_WDT
+	tristate "Mellanox Watchdog"
+	select WATCHDOG_CORE
+	select REGMAP
+	---help---
+	  This is the driver for the hardware watchdog on Mellanox systems.
+	  If you are going to use it, say Y here, otherwise N.
+	  This driver can be used together with the watchdog daemon.
+	  It can also watch your kernel to make sure it doesn't freeze,
+	  and if it does, it reboots your system after a certain amount of
+	  time.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called mlx-wdt.
+
 config TANGOX_WATCHDOG
 	tristate "Sigma Designs SMP86xx/SMP87xx watchdog"
 	select WATCHDOG_CORE
diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile
index caa9f4a..50494df 100644
--- a/drivers/watchdog/Makefile
+++ b/drivers/watchdog/Makefile
@@ -139,6 +139,7 @@ obj-$(CONFIG_INTEL_SCU_WATCHDOG) += intel_scu_watchdog.o
 obj-$(CONFIG_INTEL_MID_WATCHDOG) += intel-mid_wdt.o
 obj-$(CONFIG_INTEL_MEI_WDT) += mei_wdt.o
 obj-$(CONFIG_NI903X_WDT) += ni903x_wdt.o
+obj-$(CONFIG_MLX_WDT) += mlx_wdt.o
 
 # M32R Architecture
 
diff --git a/drivers/watchdog/mlx_wdt.c b/drivers/watchdog/mlx_wdt.c
new file mode 100644
index 0000000..7effe8c
--- /dev/null
+++ b/drivers/watchdog/mlx_wdt.c
@@ -0,0 +1,391 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Mellanox watchdog driver
+ *
+ * Copyright (C) 2018 Mellanox Technologies
+ * Copyright (C) 2018 Michael Shych <mshych@mellanox.com>
+ */
+
+#include <linux/bitops.h>
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <linux/log2.h>
+#include <linux/module.h>
+#include <linux/platform_data/mlxreg.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/spinlock.h>
+#include <linux/types.h>
+#include <linux/watchdog.h>
+
+#define MLXREG_WDT_CLOCK_SCALE		1000
+#define MLXREG_WDT_MAX_TIMEOUT_TYPE1	32
+#define MLXREG_WDT_MAX_TIMEOUT_TYPE2	255
+#define MLXREG_WDT_MIN_TIMEOUT	1
+#define MLXREG_WDT_HW_TIMEOUT_CONVERT(hw_timeout) ((1 << (hw_timeout)) \
+						   / MLXREG_WDT_CLOCK_SCALE)
+
+/**
+ * enum mlxreg_wdt_type - type of HW watchdog
+ *
+ * TYPE1 can be differentiated by different register/mask
+ *	 for WD action set and ping.
+ */
+enum mlxreg_wdt_type {
+	MLX_WDT_TYPE1,
+	MLX_WDT_TYPE2,
+};
+
+/**
+ * struct mlxreg_wdt - wd private data:
+ *
+ * @wdd:	watchdog device;
+ * @device:	basic device;
+ * @pdata:	data received from platform driver;
+ * @regmap:	register map of parent device;
+ * @timeout:	defined timeout in sec.;
+ * @hw_timeout:	real timeout set in hw;
+ *		It will be roundup base of 2 in WD type 1,
+ *		in WD type 2 it will be same number of sec as timeout;
+ * @action_idx:	index for direct access to action register;
+ * @timeout_idx:index for direct access to TO register;
+ * @ping_idx:	index for direct access to ping register;
+ * @reset_idx:	index for direct access to reset cause register;
+ * @wd_type:	watchdog HW type;
+ * @hw_timeout:	actual HW timeout;
+ * @io_lock:	spinlock for io access;
+ */
+struct mlxreg_wdt {
+	struct watchdog_device wdd;
+	struct mlxreg_core_platform_data *pdata;
+	void *regmap;
+	int action_idx;
+	int timeout_idx;
+	int ping_idx;
+	int reset_idx;
+	enum mlxreg_wdt_type wdt_type;
+	u8 hw_timeout;
+	spinlock_t io_lock;	/* the lock for io operations */
+};
+
+static int mlxreg_wdt_roundup_to_base_2(struct mlxreg_wdt *wdt, int timeout)
+{
+	timeout *= MLXREG_WDT_CLOCK_SCALE;
+
+	wdt->hw_timeout = order_base_2(timeout);
+	dev_info(wdt->wdd.parent,
+		 "watchdog %s timeout %d was rounded up to %lu (msec)\n",
+		 wdt->wdd.info->identity, timeout, roundup_pow_of_two(timeout));
+
+	return 0;
+}
+
+static enum mlxreg_wdt_type
+mlxreg_wdt_check_watchdog_type(struct mlxreg_wdt *wdt,
+			       struct mlxreg_core_platform_data *pdata)
+{
+	if ((pdata->data[wdt->action_idx].reg ==
+	     pdata->data[wdt->ping_idx].reg) &&
+	    (pdata->data[wdt->action_idx].mask ==
+	     pdata->data[wdt->ping_idx].mask))
+		return MLX_WDT_TYPE2;
+	else
+		return MLX_WDT_TYPE1;
+}
+
+static int mlxreg_wdt_check_card_reset(struct mlxreg_wdt *wdt)
+{
+	struct mlxreg_core_data *reg_data;
+	u32 regval;
+	int rc;
+
+	if (wdt->reset_idx == -EINVAL)
+		return -EINVAL;
+
+	if (!(wdt->wdd.info->options & WDIOF_CARDRESET))
+		return 0;
+
+	spin_lock(&wdt->io_lock);
+	reg_data = &wdt->pdata->data[wdt->reset_idx];
+	rc = regmap_read(wdt->regmap, reg_data->reg, &regval);
+	spin_unlock(&wdt->io_lock);
+	if (rc)
+		goto read_error;
+
+	if (regval & ~reg_data->mask) {
+		wdt->wdd.bootstatus = WDIOF_CARDRESET;
+		dev_info(wdt->wdd.parent,
+			 "watchdog previously reset the CPU\n");
+	}
+
+read_error:
+	return rc;
+}
+
+static int mlxreg_wdt_start(struct watchdog_device *wdd)
+{
+	struct mlxreg_wdt *wdt = watchdog_get_drvdata(wdd);
+	struct mlxreg_core_data *reg_data = &wdt->pdata->data[wdt->action_idx];
+	u32 regval;
+	int rc;
+
+	spin_lock(&wdt->io_lock);
+	rc = regmap_read(wdt->regmap, reg_data->reg, &regval);
+	if (rc) {
+		spin_unlock(&wdt->io_lock);
+		goto read_error;
+	}
+
+	regval = (regval & reg_data->mask) | BIT(reg_data->bit);
+	rc = regmap_write(wdt->regmap, reg_data->reg, regval);
+	spin_unlock(&wdt->io_lock);
+	if (!rc) {
+		set_bit(WDOG_HW_RUNNING, &wdt->wdd.status);
+		dev_info(wdt->wdd.parent, "watchdog %s started\n",
+			 wdd->info->identity);
+	}
+
+read_error:
+	return rc;
+}
+
+static int mlxreg_wdt_stop(struct watchdog_device *wdd)
+{
+	struct mlxreg_wdt *wdt = watchdog_get_drvdata(wdd);
+	struct mlxreg_core_data *reg_data = &wdt->pdata->data[wdt->action_idx];
+	u32 regval;
+	int rc;
+
+	spin_lock(&wdt->io_lock);
+	rc = regmap_read(wdt->regmap, reg_data->reg, &regval);
+	if (rc) {
+		spin_unlock(&wdt->io_lock);
+		goto read_error;
+	}
+
+	regval = (regval & reg_data->mask) & ~BIT(reg_data->bit);
+	rc = regmap_write(wdt->regmap, reg_data->reg, regval);
+	spin_unlock(&wdt->io_lock);
+	if (!rc)
+		dev_info(wdt->wdd.parent, "watchdog %s stopped\n",
+			 wdd->info->identity);
+
+read_error:
+	return rc;
+}
+
+static int mlxreg_wdt_ping(struct watchdog_device *wdd)
+{
+	struct mlxreg_wdt *wdt = watchdog_get_drvdata(wdd);
+	struct mlxreg_core_data *reg_data = &wdt->pdata->data[wdt->ping_idx];
+	u32 regval;
+	int rc;
+
+	spin_lock(&wdt->io_lock);
+	rc = regmap_read(wdt->regmap, reg_data->reg, &regval);
+	if (rc)
+		goto read_error;
+
+	regval = (regval & reg_data->mask) | BIT(reg_data->bit);
+	rc = regmap_write(wdt->regmap, reg_data->reg, regval);
+
+read_error:
+	spin_unlock(&wdt->io_lock);
+
+	return rc;
+}
+
+static int mlxreg_wdt_set_timeout(struct watchdog_device *wdd,
+				  unsigned int timeout)
+{
+	struct mlxreg_wdt *wdt = watchdog_get_drvdata(wdd);
+	struct mlxreg_core_data *reg_data = &wdt->pdata->data[wdt->timeout_idx];
+	u32 regval;
+	int rc;
+
+	spin_lock(&wdt->io_lock);
+
+	if (wdt->wdt_type == MLX_WDT_TYPE1) {
+		rc = regmap_read(wdt->regmap, reg_data->reg, &regval);
+		if (rc)
+			goto read_error;
+		regval = (regval & reg_data->mask) | wdt->hw_timeout;
+	} else {
+		wdt->hw_timeout = timeout;
+		regval = timeout;
+	}
+
+	rc = regmap_write(wdt->regmap, reg_data->reg, regval);
+
+read_error:
+	spin_unlock(&wdt->io_lock);
+
+	return rc;
+}
+
+static unsigned int mlxreg_wdt_get_timeleft(struct watchdog_device *wdd)
+{
+	struct mlxreg_wdt *wdt = watchdog_get_drvdata(wdd);
+	struct mlxreg_core_data *reg_data = &wdt->pdata->data[wdt->timeout_idx];
+	u32 regval;
+	int rc;
+
+	if (wdt->wdt_type == MLX_WDT_TYPE1)
+		return 0;
+
+	spin_lock(&wdt->io_lock);
+	rc = regmap_read(wdt->regmap, reg_data->reg, &regval);
+	if (rc)
+		rc = 0;
+	else
+		rc = regval;
+
+	spin_unlock(&wdt->io_lock);
+
+	return rc;
+}
+
+static const struct watchdog_ops mlxreg_wdt_ops_type1 = {
+	.start		= mlxreg_wdt_start,
+	.stop		= mlxreg_wdt_stop,
+	.ping		= mlxreg_wdt_ping,
+	.set_timeout	= mlxreg_wdt_set_timeout,
+	.owner		= THIS_MODULE,
+};
+
+static const struct watchdog_ops mlxreg_wdt_ops_type2 = {
+	.start		= mlxreg_wdt_start,
+	.stop		= mlxreg_wdt_stop,
+	.ping		= mlxreg_wdt_ping,
+	.set_timeout	= mlxreg_wdt_set_timeout,
+	.get_timeleft	= mlxreg_wdt_get_timeleft,
+	.owner		= THIS_MODULE,
+};
+
+static const struct watchdog_info mlxreg_wdt_main_info = {
+	.options	= WDIOF_KEEPALIVEPING
+			| WDIOF_MAGICCLOSE
+			| WDIOF_SETTIMEOUT
+			| WDIOF_CARDRESET,
+	.identity	= "mlx-wdt-main",
+};
+
+static const struct watchdog_info mlxreg_wdt_aux_info = {
+	.options	= WDIOF_KEEPALIVEPING
+			| WDIOF_MAGICCLOSE
+			| WDIOF_SETTIMEOUT
+			| WDIOF_ALARMONLY,
+	.identity	= "mlx-wdt-aux",
+};
+
+static int mlxreg_wdt_config(struct mlxreg_wdt *wdt,
+			     struct mlxreg_core_platform_data *pdata)
+{
+	struct mlxreg_core_data *data = pdata->data;
+	int i, timeout;
+
+	wdt->reset_idx = -EINVAL;
+	for (i = 0; i < pdata->counter; i++, data++) {
+		if (strnstr(data->label, "action", sizeof(data->label)))
+			wdt->action_idx = i;
+		else if (strnstr(data->label, "timeout", sizeof(data->label)))
+			wdt->timeout_idx = i;
+		else if (strnstr(data->label, "ping", sizeof(data->label)))
+			wdt->ping_idx = i;
+		else if (strnstr(data->label, "reset", sizeof(data->label)))
+			wdt->reset_idx = i;
+	}
+
+	wdt->pdata = pdata;
+	if (strnstr(pdata->identity, mlxreg_wdt_main_info.identity,
+		    sizeof(mlxreg_wdt_main_info.identity)))
+		wdt->wdd.info = &mlxreg_wdt_main_info;
+	else
+		wdt->wdd.info = &mlxreg_wdt_aux_info;
+
+	timeout = pdata->data[wdt->timeout_idx].health_cntr;
+	wdt->wdt_type = mlxreg_wdt_check_watchdog_type(wdt, pdata);
+	if (wdt->wdt_type == MLX_WDT_TYPE2) {
+		wdt->hw_timeout = timeout;
+		wdt->wdd.ops = &mlxreg_wdt_ops_type2;
+		wdt->wdd.timeout = wdt->hw_timeout;
+		wdt->wdd.max_timeout = MLXREG_WDT_MAX_TIMEOUT_TYPE2;
+	} else {
+		mlxreg_wdt_roundup_to_base_2(wdt, timeout);
+		wdt->wdd.ops = &mlxreg_wdt_ops_type1;
+		/* Rowndown to actual closest number of sec. */
+		wdt->wdd.timeout =
+			MLXREG_WDT_HW_TIMEOUT_CONVERT(wdt->hw_timeout);
+		wdt->wdd.max_timeout = MLXREG_WDT_MAX_TIMEOUT_TYPE1;
+	}
+	wdt->wdd.min_timeout = MLXREG_WDT_MIN_TIMEOUT;
+
+	return -EINVAL;
+}
+
+static int mlxreg_wdt_probe(struct platform_device *pdev)
+{
+	struct mlxreg_core_platform_data *pdata;
+	struct mlxreg_wdt *wdt;
+	int rc;
+
+	pdata = dev_get_platdata(&pdev->dev);
+	if (!pdata) {
+		dev_err(&pdev->dev, "Failed to get platform data.\n");
+		return -EINVAL;
+	}
+	wdt = devm_kzalloc(&pdev->dev, sizeof(*wdt), GFP_KERNEL);
+	if (!wdt)
+		return -ENOMEM;
+
+	spin_lock_init(&wdt->io_lock);
+
+	wdt->wdd.parent = &pdev->dev;
+	wdt->regmap = pdata->regmap;
+	mlxreg_wdt_config(wdt, pdata);
+
+	if ((pdata->features & MLXREG_CORE_WD_FEATURE_NOSTOP_AFTER_START))
+		watchdog_set_nowayout(&wdt->wdd, WATCHDOG_NOWAYOUT);
+	watchdog_stop_on_reboot(&wdt->wdd);
+	watchdog_init_timeout(&wdt->wdd, 0, &pdev->dev);
+	watchdog_set_drvdata(&wdt->wdd, wdt);
+
+	mlxreg_wdt_check_card_reset(wdt);
+	rc = devm_watchdog_register_device(&pdev->dev, &wdt->wdd);
+	if (rc) {
+		dev_err(&pdev->dev,
+			"Cannot register watchdog device (err=%d)\n", rc);
+		return rc;
+	}
+
+	mlxreg_wdt_set_timeout(&wdt->wdd, wdt->wdd.timeout);
+	if ((pdata->features & MLXREG_CORE_WD_FEATURE_START_AT_BOOT))
+		mlxreg_wdt_start(&wdt->wdd);
+
+	return rc;
+}
+
+static int mlxreg_wdt_remove(struct platform_device *pdev)
+{
+	struct mlxreg_wdt *wdt = dev_get_platdata(&pdev->dev);
+
+	mlxreg_wdt_stop(&wdt->wdd);
+	watchdog_unregister_device(&wdt->wdd);
+
+	return 0;
+}
+
+static struct platform_driver mlxreg_wdt_driver = {
+	.probe	= mlxreg_wdt_probe,
+	.remove	= mlxreg_wdt_remove,
+	.driver	= {
+		.name = "mlx-wdt",
+	},
+};
+
+module_platform_driver(mlxreg_wdt_driver);
+
+MODULE_AUTHOR("Michael Shych <michaelsh@mellanox.com>");
+MODULE_DESCRIPTION("Mellanox watchdog driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:mlx-wdt");
diff --git a/include/linux/platform_data/mlxreg.h b/include/linux/platform_data/mlxreg.h
index 1b2f86f..4d70c00 100644
--- a/include/linux/platform_data/mlxreg.h
+++ b/include/linux/platform_data/mlxreg.h
@@ -35,6 +35,8 @@
 #define __LINUX_PLATFORM_DATA_MLXREG_H
 
 #define MLXREG_CORE_LABEL_MAX_SIZE	32
+#define MLXREG_CORE_WD_FEATURE_NOSTOP_AFTER_START	BIT(0)
+#define MLXREG_CORE_WD_FEATURE_START_AT_BOOT		BIT(1)
 
 /**
  * struct mlxreg_hotplug_device - I2C device data:
@@ -112,11 +114,15 @@ struct mlxreg_core_item {
  * @data: instance private data;
  * @regmap: register map of parent device;
  * @counter: number of instances;
+ * @features: supported features of device;
+ * @identity: device identity name;
  */
 struct mlxreg_core_platform_data {
 	struct mlxreg_core_data *data;
 	void *regmap;
 	int counter;
+	u32 features;
+	char identity[MLXREG_CORE_LABEL_MAX_SIZE];
 };
 
 /**
-- 
2.1.4

