[v4,6/6] Add Advantech iManager Watchdog driver
diff mbox

Message ID 20161102083751.6335-7-richard.dorsch@gmail.com
State Not Applicable
Headers show

Commit Message

richard.dorsch@gmail.com Nov. 2, 2016, 8:37 a.m. UTC
Signed-off-by: Richard Vidal-Dorsch <richard.dorsch@gmail.com>
---
 drivers/watchdog/Kconfig        |  11 ++
 drivers/watchdog/Makefile       |   1 +
 drivers/watchdog/imanager_wdt.c | 303 ++++++++++++++++++++++++++++++++++++++++
 3 files changed, 315 insertions(+)
 create mode 100644 drivers/watchdog/imanager_wdt.c

Patch
diff mbox

diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig
index fdd3228..d6859da 100644
--- a/drivers/watchdog/Kconfig
+++ b/drivers/watchdog/Kconfig
@@ -912,6 +912,17 @@  config WAFER_WDT
 	  To compile this driver as a module, choose M here: the
 	  module will be called wafer5823wdt.
 
+config IMANAGER_WDT
+	tristate "Advantech iManager Watchdog"
+	depends on MFD_IMANAGER
+	select WATCHDOG_CORE
+	help
+	  Support for Advantech iManager watchdog on some Advantech
+	  SOM, MIO, AIMB, and PCM modules/boards.
+
+	  This driver can also be built as a module.  If so, the module
+	  will be called imanager_wdt.
+
 config I6300ESB_WDT
 	tristate "Intel 6300ESB Timer/Watchdog"
 	depends on PCI
diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile
index caa9f4a..eb7fccf 100644
--- a/drivers/watchdog/Makefile
+++ b/drivers/watchdog/Makefile
@@ -117,6 +117,7 @@  endif
 obj-$(CONFIG_IT8712F_WDT) += it8712f_wdt.o
 obj-$(CONFIG_IT87_WDT) += it87_wdt.o
 obj-$(CONFIG_HP_WATCHDOG) += hpwdt.o
+obj-$(CONFIG_IMANAGER_WDT) += imanager_wdt.o
 obj-$(CONFIG_KEMPLD_WDT) += kempld_wdt.o
 obj-$(CONFIG_SC1200_WDT) += sc1200wdt.o
 obj-$(CONFIG_SCx200_WDT) += scx200_wdt.o
diff --git a/drivers/watchdog/imanager_wdt.c b/drivers/watchdog/imanager_wdt.c
new file mode 100644
index 0000000..53b409e
--- /dev/null
+++ b/drivers/watchdog/imanager_wdt.c
@@ -0,0 +1,303 @@ 
+/*
+ * Advantech iManager Watchdog driver
+ *
+ * Copyright (C) 2016 Advantech Co., Ltd.
+ * Author: Richard Vidal-Dorsch <richard.dorsch@advantech.com>
+ *
+ * This program is free software; you can redistribute  it and/or modify it
+ * under  the terms of  the GNU General  Public License as published by the
+ * Free Software Foundation;  either version 2 of the  License, or (at your
+ * option) any later version.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/bitops.h>
+#include <linux/byteorder/generic.h>
+#include <linux/device.h>
+#include <linux/mfd/imanager.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/watchdog.h>
+
+#define WDT_DEFAULT_TIMEOUT	30 /* seconds */
+#define WDT_FREQ		10 /* Hz */
+
+struct imanager_wdt_data {
+	struct imanager_device_data *imgr;
+	struct watchdog_device wdt;
+	ulong last_updated;
+	uint timeout;
+};
+
+static uint timeout = WDT_DEFAULT_TIMEOUT;
+module_param(timeout, uint, 0444);
+MODULE_PARM_DESC(timeout,
+		 "Watchdog timeout in seconds. 1 <= timeout <= 65534, default="
+		 __MODULE_STRING(WDT_DEFAULT_TIMEOUT) ".");
+
+static bool nowayout = WATCHDOG_NOWAYOUT;
+module_param(nowayout, bool, 0444);
+MODULE_PARM_DESC(nowayout,
+		 "Watchdog cannot be stopped once started (default="
+		 __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
+
+enum wdt_ctrl {
+	START = 1, STOP, RESET, GET_TIMEOUT, SET_TIMEOUT, STOPBOOT = 8
+};
+
+enum imanager_wdt_event {
+	WDT_EVT_NONE,
+	WDT_EVT_DELAY,
+	WDT_EVT_PWRBTN,
+	WDT_EVT_NMI,
+	WDT_EVT_RESET,
+	WDT_EVT_WDPIN,
+	WDT_EVT_SCI,
+};
+
+struct event_delay {
+	u16	delay,
+		pwrbtn,
+		nmi,
+		reset,
+		wdpin,
+		sci,
+		dummy;
+} __attribute__((__packed__));
+
+static int imanager_wdt_ctrl(struct imanager_ec_data *ec, int ctrl,
+			     int event_type, uint timeout)
+{
+	struct imanager_ec_message msg = {
+		IMANAGER_MSG_SIMPLE(0, 0, ctrl, NULL)
+	};
+	u8 *fevent = &msg.u.data[0];
+	struct event_delay *event = (struct event_delay *)&msg.u.data[1];
+	int val;
+
+	if (ctrl == SET_TIMEOUT) {
+		memset(event, 0xff, sizeof(*event));
+		msg.wlen = sizeof(*event);
+		*fevent = 0;
+		val = (!timeout) ? 0xffff : cpu_to_be16(timeout * WDT_FREQ);
+
+		switch (event_type) {
+		case WDT_EVT_DELAY:
+			event->delay = val;
+			break;
+		case WDT_EVT_PWRBTN:
+			event->pwrbtn = val;
+			break;
+		case WDT_EVT_NMI:
+			event->nmi = val;
+			break;
+		case WDT_EVT_RESET:
+			event->reset = val;
+			break;
+		case WDT_EVT_WDPIN:
+			event->wdpin = val;
+			break;
+		case WDT_EVT_SCI:
+			event->sci = val;
+			break;
+		default:
+			return -EINVAL;
+		}
+	}
+
+	return imanager_write(ec, EC_CMD_WDT_CTRL, &msg);
+}
+
+static inline int imanager_wdt_disable_all(struct imanager_wdt_data *data)
+{
+	struct imanager_ec_data *ec = &data->imgr->ec;
+
+	return (imanager_wdt_ctrl(ec, STOP, WDT_EVT_NONE, 0) ||
+		imanager_wdt_ctrl(ec, STOPBOOT, WDT_EVT_NONE, 0));
+}
+
+static int imanager_wdt_set(struct imanager_wdt_data *data, uint timeout)
+{
+	struct imanager_ec_data *ec = &data->imgr->ec;
+	int ret;
+
+	if (time_before(jiffies, data->last_updated + HZ + HZ / 2))
+		return 0;
+
+	if (data->timeout == timeout)
+		return 0;
+
+	ret = imanager_wdt_ctrl(ec, SET_TIMEOUT, WDT_EVT_PWRBTN, timeout);
+	if (ret < 0)
+		return ret;
+
+	data->timeout = timeout;
+	data->last_updated = jiffies;
+
+	return 0;
+}
+
+static int imanager_wdt_set_timeout(struct watchdog_device *wdt, uint timeout)
+{
+	struct imanager_wdt_data *data = watchdog_get_drvdata(wdt);
+	struct imanager_device_data *imgr = data->imgr;
+	int ret;
+
+	mutex_lock(&imgr->lock);
+	ret = imanager_wdt_set(data, timeout);
+	mutex_unlock(&imgr->lock);
+
+	return ret;
+}
+
+static uint imanager_wdt_get_timeleft(struct watchdog_device *wdt)
+{
+	struct imanager_wdt_data *data = watchdog_get_drvdata(wdt);
+	uint timeleft = 0;
+	ulong time_diff = ((jiffies - data->last_updated) / HZ);
+
+	if (data->last_updated && (data->timeout > time_diff))
+		timeleft = data->timeout - time_diff;
+
+	return timeleft;
+}
+
+static int imanager_wdt_start(struct watchdog_device *wdt)
+{
+	struct imanager_wdt_data *data = watchdog_get_drvdata(wdt);
+	struct imanager_device_data *imgr = data->imgr;
+	int ret;
+
+	mutex_lock(&imgr->lock);
+	ret = imanager_wdt_ctrl(&imgr->ec, START, WDT_EVT_NONE, 0);
+	data->last_updated = jiffies;
+	mutex_unlock(&imgr->lock);
+
+	return ret;
+}
+
+static int imanager_wdt_stop(struct watchdog_device *wdt)
+{
+	struct imanager_wdt_data *data = watchdog_get_drvdata(wdt);
+	struct imanager_device_data *imgr = data->imgr;
+	int ret;
+
+	mutex_lock(&imgr->lock);
+	ret = imanager_wdt_ctrl(&imgr->ec, STOP, WDT_EVT_NONE, 0);
+	data->last_updated = 0;
+	mutex_unlock(&imgr->lock);
+
+	return ret;
+}
+
+static int imanager_wdt_ping(struct watchdog_device *wdt)
+{
+	struct imanager_wdt_data *data = watchdog_get_drvdata(wdt);
+	struct imanager_device_data *imgr = data->imgr;
+	int ret;
+
+	mutex_lock(&imgr->lock);
+	ret = imanager_wdt_ctrl(&imgr->ec, RESET, WDT_EVT_NONE, 0);
+	data->last_updated = jiffies;
+	mutex_unlock(&imgr->lock);
+
+	return ret;
+}
+
+static const struct watchdog_info imanager_wdt_info = {
+	.options		= WDIOF_SETTIMEOUT |
+				  WDIOF_KEEPALIVEPING |
+				  WDIOF_MAGICCLOSE,
+	.firmware_version	= 0,
+	.identity		= "imanager-wdt",
+};
+
+static const struct watchdog_ops imanager_wdt_ops = {
+	.owner		= THIS_MODULE,
+	.start		= imanager_wdt_start,
+	.stop		= imanager_wdt_stop,
+	.ping		= imanager_wdt_ping,
+	.set_timeout	= imanager_wdt_set_timeout,
+	.get_timeleft	= imanager_wdt_get_timeleft,
+};
+
+static int imanager_wdt_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct imanager_device_data *imgr = dev_get_drvdata(dev->parent);
+	struct imanager_wdt_data *data;
+	struct watchdog_device *wdt_dev;
+	int ret;
+
+	data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+
+	data->imgr = imgr;
+
+	wdt_dev = &data->wdt;
+	wdt_dev->info		= &imanager_wdt_info;
+	wdt_dev->ops		= &imanager_wdt_ops;
+	wdt_dev->timeout	= WDT_DEFAULT_TIMEOUT;
+	wdt_dev->min_timeout	= 1;
+	wdt_dev->max_timeout	= 0xfffe;
+
+	watchdog_set_nowayout(wdt_dev, nowayout);
+	watchdog_set_drvdata(wdt_dev, data);
+
+	ret = watchdog_register_device(wdt_dev);
+	if (ret) {
+		dev_err(dev, "Could not register watchdog device\n");
+		return ret;
+	}
+
+	platform_set_drvdata(pdev, data);
+
+	imanager_wdt_disable_all(data);
+	imanager_wdt_set_timeout(wdt_dev, timeout);
+
+	dev_info(dev, "Driver loaded (timeout=%d seconds)\n", timeout);
+
+	return 0;
+}
+
+static int imanager_wdt_remove(struct platform_device *pdev)
+{
+	struct imanager_wdt_data *data = platform_get_drvdata(pdev);
+
+	if (!nowayout)
+		imanager_wdt_disable_all(data);
+
+	watchdog_unregister_device(&data->wdt);
+	platform_set_drvdata(pdev, NULL);
+
+	return 0;
+}
+
+static void imanager_wdt_shutdown(struct platform_device *pdev)
+{
+	struct imanager_device_data *imgr = dev_get_drvdata(pdev->dev.parent);
+
+	mutex_lock(&imgr->lock);
+	imanager_wdt_ctrl(&imgr->ec, STOP, WDT_EVT_NONE, 0);
+	mutex_unlock(&imgr->lock);
+}
+
+static struct platform_driver imanager_wdt_driver = {
+	.driver = {
+		.name	= "imanager-wdt",
+	},
+	.probe		= imanager_wdt_probe,
+	.remove		= imanager_wdt_remove,
+	.shutdown	= imanager_wdt_shutdown,
+};
+
+module_platform_driver(imanager_wdt_driver);
+
+MODULE_DESCRIPTION("Advantech iManager Watchdog Driver");
+MODULE_AUTHOR("Richard Vidal-Dorsch <richard.dorsch at advantech.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:imanager-wdt");