[RFC PATCH 4/5] wdt-dmec: watchdog support for dmec

From: Zahari Doychev
Date: Thu Oct 27 2016 - 09:59:10 EST


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

Signed-off-by: Zahari Doychev <zahari.doychev@xxxxxxxxx>
---
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.doychev@xxxxxxxxx");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:dmec-wdt");
--
git-series 0.8.10