This is support for the watchdog found on the Data Modul embedded boards.

Signed-off-by: Zahari Doychev <zahari.doyc...@linux.com>
---
 drivers/staging/dmec/Kconfig    |  11 +-
 drivers/staging/dmec/Makefile   |   1 +-
 drivers/staging/dmec/wdt-dmec.c | 569 +++++++++++++++++++++++++++++++++-
 3 files changed, 581 insertions(+), 0 deletions(-)
 create mode 100644 drivers/staging/dmec/wdt-dmec.c

diff --git a/drivers/staging/dmec/Kconfig b/drivers/staging/dmec/Kconfig
index 9c4a8e5..eddf0bb 100644
--- a/drivers/staging/dmec/Kconfig
+++ b/drivers/staging/dmec/Kconfig
@@ -27,3 +27,14 @@ config GPIO_DMEC
 
          To compile this driver as a module, say M here: the module will be
           called gpio-dmec
+
+config WDT_DMEC
+       tristate "Data Modul Watchdog"
+       depends on MFD_DMEC
+       select WATCHDOG_CORE
+       help
+         Say Y to enable support for a watchdog on a Data Modul embedded
+         controllers.
+
+         To compile this driver as a module, say M here: the module will be
+          called wdt-dmec
diff --git a/drivers/staging/dmec/Makefile b/drivers/staging/dmec/Makefile
index b71b27b..8b363cc 100644
--- a/drivers/staging/dmec/Makefile
+++ b/drivers/staging/dmec/Makefile
@@ -1,3 +1,4 @@
 obj-$(CONFIG_MFD_DMEC)         += dmec-core.o
 obj-$(CONFIG_I2C_DMEC)         += i2c-dmec.o
 obj-$(CONFIG_GPIO_DMEC)        += gpio-dmec.o
+obj-$(CONFIG_WDT_DMEC)         += wdt-dmec.o
diff --git a/drivers/staging/dmec/wdt-dmec.c b/drivers/staging/dmec/wdt-dmec.c
new file mode 100644
index 0000000..714ed11
--- /dev/null
+++ b/drivers/staging/dmec/wdt-dmec.c
@@ -0,0 +1,569 @@
+/*
+ * Watchdog driver for Data Modul AG Embedded Controller
+ *
+ * Copyright (C) 2016 Zahari Doychev, Data Modul AG
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published by
+ * the Free Software Foundation.
+ */
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/types.h>
+#include <linux/miscdevice.h>
+#include <linux/watchdog.h>
+#include <linux/fs.h>
+#include <linux/ioport.h>
+#include <linux/init.h>
+#include <linux/uaccess.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/platform_device.h>
+#include <linux/reboot.h>
+#include <linux/regmap.h>
+#include <linux/bitops.h>
+
+#include "dmec.h"
+
+#define DMEC_WDT_VER           0x30
+#define DMEC_WDT_SRV           0x30
+#define DMEC_WDT_CFG           0x31
+#define DMEC_WDT_S0CFG         0x32
+#define DMEC_WDT_S0MOD0                0x33
+#define DMEC_WDT_S0MOD1                0x34
+#define DMEC_WDT_S1CFG         (DMEC_WDT_S0CFG + 3)
+#define DMEC_WDT_S1MOD0                (DMEC_WDT_S0MOD0 + 3)
+#define DMEC_WDT_S1MOD1                (DMEC_WDT_S0MOD1 + 3)
+#define DMEC_WDT_S2CFG         (DMEC_WDT_S1CFG + 3)
+#define DMEC_WDT_S2MOD0                (DMEC_WDT_S1MOD0 + 3)
+#define DMEC_WDT_S2MOD1                (DMEC_WDT_S1MOD1 + 3)
+
+#define DMEC_WDT_EN            BIT(0)
+#define DMEC_WDT_LOCK          BIT(1)
+#define DMEC_WDT_WIN_MODE      BIT(2)
+#define DMEC_WDT_AL            BIT(3)
+
+#define DMEC_WDT_PRESCALER     BIT(4)
+#define DMEC_WDT_WDTEN         BIT(3)
+#define DMEC_WDT_WDSTS         BIT(5)
+
+#define DMEC_WDT_TIMEOUT_MIN   1 /* s */
+#define DMEC_WDT_TIMEOUT_MAX   (2 * 3600) /* s */
+
+#define DMEC_WDT_TIME_MAX      (65 * 1000)
+
+/* S0 used only during boot */
+#define DEFAULT_S0_TIMEOUT     0
+#define DEFAULT_S1_TIMEOUT     3
+#define DEFAULT_S2_TIMEOUT     5
+
+enum wdt_actions {
+       WDT_DISABLE = 0,
+       WDT_DELAY,
+       WDT_RESET,
+       WDT_SYSIRQ0,
+       WDT_SYSIRQ1,
+       WDT_SYSIRQ2,
+       WDT_IRQ,
+       WDT_RESERVED
+};
+
+enum wdt_stages {
+       S0,
+       S1,
+       S2
+};
+
+static unsigned int s1_timeout = DEFAULT_S1_TIMEOUT;
+module_param(s1_timeout, uint, 0644);
+MODULE_PARM_DESC(s1_timeout,
+                "Watchdog stage 1 timeout in [s], default=3");
+
+static unsigned int s2_timeout = DEFAULT_S2_TIMEOUT;
+module_param(s2_timeout, uint, 0644);
+MODULE_PARM_DESC(s2_timeout,
+                "Watchdog stage 2 timeout in [s], default=5");
+
+static enum wdt_actions action = WDT_DELAY;
+module_param(action, uint, 0644);
+MODULE_PARM_DESC(action,
+                "Watchdog action for stage 1, default=1 (delay)");
+
+static bool nowayout = WATCHDOG_NOWAYOUT;
+module_param(nowayout, bool, 0644);
+MODULE_PARM_DESC(nowayout,
+                "Watchdog cannot be stopped once started (default="
+                               __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
+
+static bool win_mode;
+module_param(win_mode, bool, 0644);
+MODULE_PARM_DESC(win_mode, "Use watchdog window mode, default=0");
+
+static bool stop_on_reboot;
+module_param(stop_on_reboot, bool, 0644);
+MODULE_PARM_DESC(stop_on_reboot, "stop watchdog on system reboot, default=0");
+
+struct dmec_wdt_data {
+       struct watchdog_device  wdd;
+       struct regmap *regmap;
+       unsigned int s1_time;
+       unsigned int s2_time;
+       unsigned int status;
+       unsigned int boot_cfg;
+       bool boot_mode;
+};
+
+static int dmec_wdt_set_stage_action(struct dmec_wdt_data *wdat,
+                                    unsigned int stage,
+                                    enum wdt_actions action)
+{
+       unsigned int offset = DMEC_WDT_S0CFG + (3 * stage);
+       unsigned int val;
+
+       regmap_read(wdat->regmap, offset, &val);
+       val &= ~DMEC_WDT_WDSTS;
+       val |= action | DMEC_WDT_WDTEN;
+       regmap_write(wdat->regmap, offset, val);
+
+       return 0;
+}
+
+static int dmec_wdt_clear_action(struct dmec_wdt_data *wdat, int stage)
+{
+       unsigned int val;
+
+       regmap_read(wdat->regmap, DMEC_WDT_S0CFG + 3 * stage, &val);
+       val |= DMEC_WDT_WDSTS;
+       val &= ~0xf;
+       regmap_write(wdat->regmap, DMEC_WDT_S0CFG + 3 * stage, val);
+
+       return 0;
+}
+
+static unsigned int dmec_wdt_get_stage_timeout(struct dmec_wdt_data *wdat,
+                                              unsigned int stage)
+{
+       unsigned int val, timeout = 0, cfg;
+
+       regmap_read(wdat->regmap, DMEC_WDT_S0CFG + 3 * stage, &cfg);
+       regmap_read(wdat->regmap, DMEC_WDT_S0MOD0 + 3 * stage, &val);
+       timeout = val;
+       regmap_read(wdat->regmap, DMEC_WDT_S0MOD1 + 3 * stage, &val);
+       timeout |= (val << 8);
+
+       if (cfg & DMEC_WDT_PRESCALER)
+               timeout <<= 7;
+
+       return timeout / 1000;
+}
+
+static int dmec_wdt_set_stage_timeout(struct dmec_wdt_data *wdat,
+                                     unsigned int stage,
+                                     unsigned int timeout)
+{
+       unsigned int val;
+
+       timeout *= 1000;
+       if (timeout > DMEC_WDT_TIME_MAX) {
+               /* enable prescaler */
+               regmap_read(wdat->regmap, DMEC_WDT_S0CFG + (3 * stage), &val);
+               val |= DMEC_WDT_PRESCALER;
+               regmap_write(wdat->regmap, DMEC_WDT_S0CFG + (3 * stage), val);
+               timeout >>= 7;
+       } else {
+               regmap_read(wdat->regmap, DMEC_WDT_S0CFG + (3 * stage), &val);
+               val &= ~DMEC_WDT_PRESCALER;
+               regmap_write(wdat->regmap, DMEC_WDT_S0CFG + (3 * stage), val);
+       }
+
+       val = timeout & 0xff;
+       regmap_write(wdat->regmap, DMEC_WDT_S0MOD0 + (3 * stage), val);
+       val = (timeout >> 8) & 0xff;
+       regmap_write(wdat->regmap, DMEC_WDT_S0MOD1 + (3 * stage), val);
+
+       return 0;
+}
+
+static int dmec_wdt_set_timeouts(struct dmec_wdt_data *wdat)
+{
+       dmec_wdt_clear_action(wdat, S0);
+       dmec_wdt_clear_action(wdat, S1);
+       dmec_wdt_clear_action(wdat, S2);
+
+       wdat->s1_time = s1_timeout;
+       wdat->s2_time = s2_timeout;
+       dmec_wdt_set_stage_timeout(wdat, S1, s1_timeout);
+       dmec_wdt_set_stage_timeout(wdat, S2, s2_timeout);
+       dmec_wdt_set_stage_action(wdat, S2, WDT_RESET);
+
+       return 0;
+}
+
+static int dmec_wdt_stop(struct watchdog_device *wdd)
+{
+       struct dmec_wdt_data *wdat = watchdog_get_drvdata(wdd);
+
+       regmap_update_bits(wdat->regmap, DMEC_WDT_CFG, DMEC_WDT_EN, 0);
+
+       return 0;
+}
+
+static int dmec_wdt_start(struct watchdog_device *wdd)
+{
+       struct dmec_wdt_data *wdat = watchdog_get_drvdata(wdd);
+
+       regmap_update_bits(wdat->regmap, DMEC_WDT_CFG,
+                          DMEC_WDT_EN, DMEC_WDT_EN);
+
+       wdat->boot_mode = false;
+
+       return 0;
+}
+
+static int dmec_wdt_ping(struct watchdog_device *wdd)
+{
+       struct dmec_wdt_data *wdat = watchdog_get_drvdata(wdd);
+       unsigned int val;
+
+       /* We should try avoiding resets in window mode until the watchdog
+        * daemon takes control
+        */
+       if (win_mode && wdat->boot_mode) {
+               regmap_read(wdat->regmap, DMEC_WDT_S1CFG, &val);
+               if (!(val & DMEC_WDT_WDSTS))
+                       return 0;
+               val |= DMEC_WDT_WDSTS;
+               regmap_write(wdat->regmap, DMEC_WDT_S1CFG, val);
+       }
+
+       regmap_write(wdat->regmap, DMEC_WDT_SRV, 0xff);
+
+       return 0;
+}
+
+static int dmec_wdt_set_win_mode(struct watchdog_device *wdd)
+{
+       struct dmec_wdt_data *wdat = watchdog_get_drvdata(wdd);
+
+       regmap_update_bits(wdat->regmap, DMEC_WDT_CFG,
+                          DMEC_WDT_WIN_MODE, DMEC_WDT_WIN_MODE);
+
+       return 0;
+}
+
+static int dmec_wdt_set_std_mode(struct watchdog_device *wdd)
+{
+       struct dmec_wdt_data *wdat = watchdog_get_drvdata(wdd);
+
+       regmap_update_bits(wdat->regmap, DMEC_WDT_CFG, DMEC_WDT_WIN_MODE, 0);
+
+       return 0;
+}
+
+static int dmec_wdt_set_timeout(struct watchdog_device *wdd,
+                               unsigned int timeout)
+{
+       struct dmec_wdt_data *wdat = watchdog_get_drvdata(wdd);
+
+       wdd->timeout = timeout;
+       wdat->boot_mode = false;
+       dmec_wdt_stop(wdd);
+       dmec_wdt_clear_action(wdat, S0);
+
+       if (win_mode) {
+               dmec_wdt_clear_action(wdat, S1);
+               dmec_wdt_set_stage_timeout(wdat, S1, wdat->s1_time);
+               dmec_wdt_set_stage_action(wdat, S1, WDT_DELAY);
+               dmec_wdt_set_win_mode(wdd);
+       } else {
+               dmec_wdt_set_std_mode(wdd);
+       }
+
+       wdat->s2_time = timeout;
+       dmec_wdt_clear_action(wdat, S2);
+       dmec_wdt_set_stage_timeout(wdat, S2, timeout);
+       dmec_wdt_set_stage_action(wdat, S2, WDT_RESET);
+       clear_bit(WDOG_HW_RUNNING, &wdd->status);
+       dmec_wdt_start(wdd);
+
+       return 0;
+}
+
+static long dmec_wdt_ioctl(struct watchdog_device *wdd, unsigned int cmd,
+                          unsigned long arg)
+{
+       struct dmec_wdt_data *wdat = watchdog_get_drvdata(wdd);
+       void __user *argp = (void __user *)arg;
+       int ret = -ENOIOCTLCMD;
+       int __user *p = argp;
+       int val;
+
+       switch (cmd) {
+       case WDIOC_SETPRETIMEOUT:
+               if (get_user(val, p) && val < 0)
+                       return -EFAULT;
+               dmec_wdt_clear_action(wdat, S1);
+               if (val > 0) {
+                       dmec_wdt_set_stage_timeout(wdat, S1, val);
+                       dmec_wdt_set_stage_action(wdat, S1, action);
+               }
+               wdat->s1_time = val;
+               wdat->boot_mode = false;
+               ret = 0;
+               if (!win_mode)
+                       ret = dmec_wdt_ping(wdd);
+               break;
+       case WDIOC_GETPRETIMEOUT:
+               ret = put_user(wdat->s1_time, (int __user *)arg);
+               break;
+       }
+
+       return ret;
+}
+
+static unsigned int dmec_wdt_get_timeleft(struct watchdog_device *wdd)
+{
+       struct dmec_wdt_data *wdat = watchdog_get_drvdata(wdd);
+
+       return dmec_wdt_get_stage_timeout(wdat, 2);
+}
+
+static unsigned int dmec_wdt_status(struct watchdog_device *wdd)
+{
+       struct dmec_wdt_data *wdat = watchdog_get_drvdata(wdd);
+       unsigned int status;
+
+       regmap_read(wdat->regmap, DMEC_WDT_CFG, &status);
+
+       return status;
+}
+
+static struct watchdog_info dmec_wdt_info = {
+       .options                = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT |
+                                 WDIOF_MAGICCLOSE | WDIOF_PRETIMEOUT,
+       .identity               = "DMEC WDT",
+};
+
+static const struct watchdog_ops dmec_wdt_ops = {
+       .owner          = THIS_MODULE,
+       .start          = dmec_wdt_start,
+       .stop           = dmec_wdt_stop,
+       .ping           = dmec_wdt_ping,
+       .set_timeout    = dmec_wdt_set_timeout,
+       .status         = dmec_wdt_status,
+       .ioctl          = dmec_wdt_ioctl,
+       .get_timeleft   = dmec_wdt_get_timeleft,
+};
+
+static int dmec_wdt_get_hw_ping_time(struct dmec_wdt_data *wdat)
+{
+       struct watchdog_device *wdd = &wdat->wdd;
+       unsigned int val, t;
+
+       regmap_read(wdat->regmap, DMEC_WDT_S0CFG, &val);
+       t = dmec_wdt_get_stage_timeout(wdat, S0);
+       if (t && (val & 0x7) > 1)
+               dmec_wdt_clear_action(wdat, S0);
+
+       wdat->s1_time = dmec_wdt_get_stage_timeout(wdat, S1);
+       wdat->s2_time = dmec_wdt_get_stage_timeout(wdat, S2);
+
+       regmap_read(wdat->regmap, DMEC_WDT_S1CFG, &val);
+       if (wdat->s1_time && (val & 0x7) > 1 && !(val & DMEC_WDT_WDSTS)) {
+               wdd->timeout = wdat->s1_time;
+               return 1;
+       }
+
+       regmap_read(wdat->regmap, DMEC_WDT_S2CFG, &val);
+       if (wdat->s2_time && (val & 0x7) > 1 && !(val & DMEC_WDT_WDSTS)) {
+               wdd->timeout = wdat->s2_time;
+               return 2;
+       }
+
+       return -1;
+}
+
+static int dmec_wdt_config_win_mode(struct dmec_wdt_data *wdat)
+{
+       struct watchdog_device *wdd = &wdat->wdd;
+       unsigned int t;
+
+       /* The timeout should be ok until the watchdog daemon appears */
+       t = (min_t(unsigned int, wdat->s1_time, wdat->s2_time) / 2);
+       wdd->timeout = t;
+
+       return 0;
+}
+
+static int dmec_wdt_config_mode(struct dmec_wdt_data *wdat)
+{
+       struct watchdog_device *wdd = &wdat->wdd;
+
+       if (!(wdat->boot_cfg & DMEC_WDT_EN))
+               return -1;
+
+       if (dmec_wdt_get_hw_ping_time(wdat) < 0)
+               return -1;
+
+       wdd->max_hw_heartbeat_ms = DMEC_WDT_TIMEOUT_MAX * 1000;
+       set_bit(WDOG_HW_RUNNING, &wdd->status);
+
+       if (wdat->boot_cfg & DMEC_WDT_WIN_MODE)
+               dmec_wdt_config_win_mode(wdat);
+
+       wdat->boot_mode = true;
+
+       return 0;
+}
+
+static int dmec_wdt_setup(struct dmec_wdt_data *wdat)
+{
+       struct watchdog_device *wdd = &wdat->wdd;
+       int ret = 0;
+
+       regmap_read(wdat->regmap, DMEC_WDT_CFG, &wdat->boot_cfg);
+
+       ret = dmec_wdt_config_mode(wdat);
+
+       if (ret < 0)
+               dmec_wdt_set_timeouts(wdat);
+
+       if (wdat->boot_cfg & DMEC_WDT_LOCK && !nowayout) {
+               dev_info(wdd->parent, "watchdog lock is enabled.\n");
+               nowayout = true;
+               return 0;
+       }
+
+       return 0;
+}
+
+static int dmec_wdt_probe(struct platform_device *pdev)
+{
+       struct dmec_wdt_data *wdat;
+       struct device *dev = &pdev->dev;
+       struct watchdog_device *wdd;
+       int ret = 0;
+
+       wdat = devm_kzalloc(dev, sizeof(*wdat), GFP_KERNEL);
+       if (!wdat)
+               return -ENOMEM;
+
+       wdat->regmap = dmec_get_regmap(pdev->dev.parent);
+       wdd = &wdat->wdd;
+       wdd->parent = dev;
+
+       wdd->info = &dmec_wdt_info;
+       wdd->ops = &dmec_wdt_ops;
+       wdd->min_timeout = DMEC_WDT_TIMEOUT_MIN;
+       wdd->max_timeout = DMEC_WDT_TIMEOUT_MAX;
+
+       wdat->s1_time = s1_timeout;
+       wdat->s2_time = s2_timeout;
+
+       watchdog_set_drvdata(wdd, wdat);
+       platform_set_drvdata(pdev, wdat);
+
+       dmec_wdt_setup(wdat);
+
+       watchdog_set_nowayout(wdd, nowayout);
+       if (stop_on_reboot)
+               watchdog_stop_on_reboot(wdd);
+
+       ret = watchdog_register_device(wdd);
+       if (ret)
+               return ret;
+
+       regmap_read(wdat->regmap, DMEC_WDT_VER,
+                   &dmec_wdt_info.firmware_version);
+
+       dev_info(dev, "registered. v%u.%u sta: %#lx mode:%d\n",
+                (dmec_wdt_info.firmware_version >> 4) & 0xf,
+                dmec_wdt_info.firmware_version & 0xf,
+                wdd->status, win_mode);
+
+       return 0;
+}
+
+static int dmec_wdt_remove(struct platform_device *pdev)
+{
+       struct dmec_wdt_data *wdat = platform_get_drvdata(pdev);
+
+       dmec_wdt_stop(&wdat->wdd);
+       watchdog_unregister_device(&wdat->wdd);
+
+       return 0;
+}
+
+static void dmec_wdt_shutdown(struct platform_device *pdev)
+{
+       struct dmec_wdt_data *wdat = platform_get_drvdata(pdev);
+
+       dmec_wdt_stop(&wdat->wdd);
+}
+
+#ifdef CONFIG_PM
+/* Disable watchdog if it is active during suspend */
+static int dmec_wdt_suspend(struct platform_device *pdev,
+                           pm_message_t message)
+{
+       struct dmec_wdt_data *wdat = platform_get_drvdata(pdev);
+       struct watchdog_device *wdd = &wdat->wdd;
+
+       regmap_read(wdat->regmap, DMEC_WDT_CFG, &wdat->status);
+
+       if (wdat->status & DMEC_WDT_EN)
+               return dmec_wdt_stop(wdd);
+
+       return 0;
+}
+
+/* Enable watchdog and configure it if necessary */
+static int dmec_wdt_resume(struct platform_device *pdev)
+{
+       struct dmec_wdt_data *wdat = platform_get_drvdata(pdev);
+       struct watchdog_device *wdd = &wdat->wdd;
+
+       if (!win_mode && wdat->status & DMEC_WDT_EN) {
+               dmec_wdt_stop(wdd);
+               dmec_wdt_clear_action(wdat, S0);
+               dmec_wdt_clear_action(wdat, S1);
+               dmec_wdt_clear_action(wdat, S2);
+
+               if (wdat->s1_time) {
+                       dmec_wdt_set_stage_timeout(wdat, S1, wdat->s1_time);
+                       dmec_wdt_set_stage_action(wdat, S1, WDT_DELAY);
+               }
+               dmec_wdt_set_stage_timeout(wdat, S2, wdat->s2_time);
+               dmec_wdt_set_stage_action(wdat, S2, WDT_RESET);
+               dmec_wdt_get_timeleft(wdd);
+               return dmec_wdt_start(wdd);
+       }
+
+       return dmec_wdt_stop(wdd);
+}
+#else
+#define dmec_wdt_suspend NULL
+#define dmec_wdt_resume        NULL
+#endif
+
+static struct platform_driver dmec_wdt_driver = {
+       .driver = {
+               .name = "dmec-wdt",
+               .owner = THIS_MODULE,
+       },
+       .probe = dmec_wdt_probe,
+       .remove = dmec_wdt_remove,
+       .shutdown = dmec_wdt_shutdown,
+       .suspend = dmec_wdt_suspend,
+       .resume = dmec_wdt_resume,
+};
+
+module_platform_driver(dmec_wdt_driver);
+
+MODULE_DESCRIPTION("dmec watchdog driver");
+MODULE_AUTHOR("Zahari Doychev <zahari.doyc...@linux.com");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:dmec-wdt");
-- 
git-series 0.8.10

Reply via email to