diff mbox

[v3,6/6] Add Advantech iManager Watchdog driver

Message ID 1452468675-5827-7-git-send-email-richard.dorsch@gmail.com
State New
Headers show

Commit Message

richard.dorsch@gmail.com Jan. 10, 2016, 11:31 p.m. UTC
From: Richard Vidal-Dorsch <richard.dorsch@gmail.com>

Signed-off-by: Richard Vidal-Dorsch <richard.dorsch@gmail.com>
---
 drivers/watchdog/Kconfig           |  12 ++
 drivers/watchdog/Makefile          |   2 +
 drivers/watchdog/imanager-ec-wdt.c | 170 +++++++++++++++++++
 drivers/watchdog/imanager-wdt.c    | 333 +++++++++++++++++++++++++++++++++++++
 include/linux/mfd/imanager/wdt.h   |  37 +++++
 5 files changed, 554 insertions(+)
 create mode 100644 drivers/watchdog/imanager-ec-wdt.c
 create mode 100644 drivers/watchdog/imanager-wdt.c
 create mode 100644 include/linux/mfd/imanager/wdt.h

Comments

Guenter Roeck Jan. 17, 2016, 7:15 p.m. UTC | #1
On 01/10/2016 03:31 PM, richard.dorsch@gmail.com wrote:
> From: Richard Vidal-Dorsch <richard.dorsch@gmail.com>
>
> Signed-off-by: Richard Vidal-Dorsch <richard.dorsch@gmail.com>
> ---
>   drivers/watchdog/Kconfig           |  12 ++
>   drivers/watchdog/Makefile          |   2 +
>   drivers/watchdog/imanager-ec-wdt.c | 170 +++++++++++++++++++
>   drivers/watchdog/imanager-wdt.c    | 333 +++++++++++++++++++++++++++++++++++++
>   include/linux/mfd/imanager/wdt.h   |  37 +++++
>   5 files changed, 554 insertions(+)
>   create mode 100644 drivers/watchdog/imanager-ec-wdt.c
>   create mode 100644 drivers/watchdog/imanager-wdt.c
>   create mode 100644 include/linux/mfd/imanager/wdt.h
>
> diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig
> index 1c427be..e555127 100644
> --- a/drivers/watchdog/Kconfig
> +++ b/drivers/watchdog/Kconfig
> @@ -846,6 +846,18 @@ config ITCO_VENDOR_SUPPORT
>   	  devices. At this moment we only have additional support for some
>   	  SuperMicro Inc. motherboards.
>
> +config IMANAGER_WDT
> +	tristate "Advantech iManager Watchdog"
> +	depends on MFD_IMANAGER
> +	select WATCHDOG_CORE
> +	help
> +	  This driver provides support for Advantech iManager watchdog
> +	  of Advantech SOM, MIO, AIMB, and PCM modules/boards.
> +	  Requires mfd-core and imanager-core to function properly.
> +
> +	  This driver can also be built as a module.  If so, the module
> +	  will be called imanager_wdt.
> +
>   config IT8712F_WDT
>   	tristate "IT8712F (Smart Guardian) Watchdog Timer"
>   	depends on X86
> diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile
> index 53d4827..647eca87 100644
> --- a/drivers/watchdog/Makefile
> +++ b/drivers/watchdog/Makefile
> @@ -100,6 +100,8 @@ obj-$(CONFIG_ITCO_WDT) += iTCO_wdt.o
>   ifeq ($(CONFIG_ITCO_VENDOR_SUPPORT),y)
>   obj-$(CONFIG_ITCO_WDT) += iTCO_vendor_support.o
>   endif
> +imanager_wdt-objs := imanager-wdt.o imanager-ec-wdt.o
> +obj-$(CONFIG_IMANAGER_WDT) += imanager_wdt.o
>   obj-$(CONFIG_IT8712F_WDT) += it8712f_wdt.o
>   obj-$(CONFIG_IT87_WDT) += it87_wdt.o
>   obj-$(CONFIG_HP_WATCHDOG) += hpwdt.o
> diff --git a/drivers/watchdog/imanager-ec-wdt.c b/drivers/watchdog/imanager-ec-wdt.c
> new file mode 100644
> index 0000000..9c94b21
> --- /dev/null
> +++ b/drivers/watchdog/imanager-ec-wdt.c
> @@ -0,0 +1,170 @@
> +/*
> + * Advantech iManager Watchdog core
> + *
> + * Copyright (C) 2016 Advantech Co., Ltd., Irvine, CA, USA
> + * 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.
> + *
> + * Note: Current release only implements RESET of the EC WDT.
> + *       In other words, no PWR button, NMI, SCI, IRQ, or WDPin support yet!
> + */
> +
> +#include <linux/types.h>
> +#include <linux/errno.h>
> +#include <linux/bug.h>
> +#include <linux/io.h>
> +#include <linux/delay.h>
> +#include <linux/string.h>
> +#include <linux/byteorder/generic.h>
> +#include <linux/swab.h>
> +#include <linux/mfd/imanager/ec.h>
> +#include <linux/mfd/imanager/wdt.h>
> +
> +/* Timer resolution */
> +#define WDT_FREQ	10 /* Hz */
> +
> +enum wdt_ctrl {
> +	START = 1,
> +	STOP,
> +	RST,
> +	GET_TIMEOUT,
> +	SET_TIMEOUT,
> +	STOPBOOT = 8,
> +};
> +
> +struct wdt_event_delay {
> +	u16	delay,
> +		pwrbtn,
> +		nmi,
> +		reset,
> +		wdpin,
> +		sci,
> +		dummy;
> +};
> +
> +static const struct imanager_watchdog_device *wd;
> +

I am not entirely happy with those static variables. Any chance
to just read it in the wdt file and pass it around ?

> +static inline int set_timer(enum wdt_ctrl ctrl)
> +{
> +	if (WARN_ON(ctrl == SET_TIMEOUT))
> +		return -EINVAL;
> +

Unnecessary error message and warning.

> +	return imanager_msg_write(EC_CMD_WDT_CTRL, ctrl, NULL);
> +}
> +
> +static int
> +wdt_ctrl(enum wdt_ctrl ctrl, enum wdt_event type, unsigned int timeout)
> +{
> +	u16 val;
> +	int ret;
> +	struct ec_message msg = {
> +		.rlen = 0,
> +		.wlen = 0,
> +	};
> +	u8 *fevent = &msg.u.data[0];
> +	struct wdt_event_delay *event =
> +				(struct wdt_event_delay *)&msg.u.data[1];
> +
> +	switch (ctrl) {
> +	case SET_TIMEOUT:
> +		memset(event, 0xff, sizeof(*event));
> +		msg.wlen = sizeof(*event);
> +		*fevent = 0;
> +		val = (!timeout) ? 0xffff : swab16(timeout * WDT_FREQ);

cpu_to_be16()

> +
> +		switch (type) {
> +		case DELAY:
> +			event->delay = val;
> +			break;
> +		case PWRBTN:
> +			event->pwrbtn = val;
> +			break;
> +		case NMI:
> +			event->nmi = val;
> +			break;
> +		case RESET:
> +			event->reset = val;
> +			break;
> +		case WDPIN:
> +			event->wdpin = val;
> +			break;
> +		case SCI:
> +			event->sci = val;
> +			break;
> +		default:
> +			return -EINVAL;
> +		}
> +
> +		ret = imanager_msg_write(EC_CMD_WDT_CTRL, SET_TIMEOUT, &msg);
> +		if (ret < 0) {
> +			pr_err("Failed to set timeout\n");

Please consider dropping the error messages.

> +			return ret;
> +		}
> +		break;
> +	case START:
> +	case STOP:
> +	case RST:
> +	case STOPBOOT:
> +		/* simple command, no data */
> +		return imanager_msg_write(EC_CMD_WDT_CTRL, ctrl, NULL);

Why not use simple(r) wrappers for those or just call imanager_msg_write()
directly, as you do it for set_timer() ?

Actually, you never call wdt_ctrl() with anything but SET_TIMEOUT as parameter,
so everything else in this function seems to be unused.

> +	default:
> +		return -EINVAL;
> +	}
> +
> +	return timeout;
> +}
> +
> +int wdt_core_start_timer(void)
> +{
> +	return set_timer(START);
> +}
> +
> +int wdt_core_stop_timer(void)
> +{
> +	return set_timer(STOP);
> +}
> +
> +int wdt_core_reset_timer(void)
> +{
> +	return set_timer(RST);
> +}
> +
> +int wdt_core_stop_boot_timer(void)
> +{
> +	return set_timer(STOPBOOT);
> +}
> +
> +inline int wdt_core_set_timeout(enum wdt_event type, u32 timeout)
> +{
> +	return wdt_ctrl(SET_TIMEOUT, type, timeout);
> +}
> +
> +int wdt_core_disable_all(void)

What is the point of a return value which is never checked ?

> +{
> +	struct ec_message msg = {
> +		.rlen = 0,
> +		.wlen = sizeof(struct wdt_event_delay),
> +	};
> +	struct wdt_event_delay *event =
> +				(struct wdt_event_delay *)&msg.u.data[1];
> +
> +	memset(event, 0xff, sizeof(*event));
> +
> +	return  (wdt_core_stop_timer() ||
> +		 wdt_core_stop_boot_timer() ||
> +		 imanager_msg_write(EC_CMD_WDT_CTRL, SET_TIMEOUT, &msg));

Please use individual error checks here.

> +}
> +
> +int wdt_core_init(void)
> +{
> +	wd = imanager_get_watchdog_device();
> +	if (!wd)
> +		return -ENODEV;
> +
> +	return 0;
> +}
> +
> diff --git a/drivers/watchdog/imanager-wdt.c b/drivers/watchdog/imanager-wdt.c
> new file mode 100644
> index 0000000..10f8e22
> --- /dev/null
> +++ b/drivers/watchdog/imanager-wdt.c
> @@ -0,0 +1,333 @@
> +/*
> + * Advantech iManager Watchdog driver
> + *
> + * Copyright (C) 2016 Advantech Co., Ltd., Irvine, CA, USA
> + * 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/module.h>
> +#include <linux/moduleparam.h>
> +#include <linux/types.h>
> +#include <linux/watchdog.h>
> +#include <linux/notifier.h>
> +#include <linux/reboot.h>
> +#include <linux/uaccess.h>
> +#include <linux/platform_device.h>
> +#include <linux/mfd/imanager/core.h>
> +#include <linux/mfd/imanager/wdt.h>
> +
> +#define WATCHDOG_TIMEOUT 30 /* in seconds */
> +
> +static unsigned long last_updated = -1;
> +
> +static uint timeout = WATCHDOG_TIMEOUT;
> +module_param(timeout, uint, 0);
> +MODULE_PARM_DESC(timeout,
> +	"Watchdog timeout in seconds. 1 <= timeout <= 65534, 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 wdt_start_timer(void)
> +{
> +	int ret;
> +
> +	ret = wdt_core_start_timer();
> +	if (ret < 0)
> +		return ret;
> +
> +	last_updated = jiffies;
> +
> +	return 0;
> +}
> +
> +static int wdt_stop_timer(void)
> +{
> +	int ret;
> +
> +	ret = wdt_core_stop_timer();
> +	if (ret < 0)
> +		return ret;
> +
> +	last_updated = 0;
> +
> +	return 0;
> +}
> +
> +static int wdt_ping(void)
> +{
> +	int ret;
> +
> +	ret = wdt_core_reset_timer();
> +	if (ret < 0)
> +		return ret;
> +
> +	last_updated = jiffies;
> +
> +	return 0;
> +}
> +
> +static int imanager_wdt_set(unsigned int _timeout)
> +{
> +	int ret;
> +
> +	if (timeout != _timeout)
> +		timeout = _timeout;
> +
> +	ret = wdt_core_set_timeout(PWRBTN, timeout);
> +	if (ret < 0)
> +		return ret;
> +
> +	if (last_updated != 0)
> +		last_updated = jiffies;
> +
> +	return 0;
> +}
> +
> +static int imanager_wdt_set_timeout(struct watchdog_device *wdt_dev,
> +				    unsigned int timeout)
> +{
> +	struct imanager_device_data *idev = watchdog_get_drvdata(wdt_dev);
> +	int ret = 0;
> +
> +	mutex_lock(&idev->lock);
> +
> +	ret = imanager_wdt_set(timeout);
> +	if (ret < 0)
> +		dev_err(wdt_dev->dev, "Failed to set timeout\n");
> +	mutex_unlock(&idev->lock);
> +
> +	return ret;
> +}
> +
> +static unsigned int imanager_wdt_get_timeleft(struct watchdog_device *wdt_dev)
> +{
> +	unsigned int timeleft = 0;
> +	unsigned long time_diff = ((jiffies - last_updated) / HZ);
> +
> +	if (last_updated && (timeout > time_diff))
> +		timeleft = timeout - time_diff;
> +

This is supposed to be provided by the hardware. Please do not implement
get_timeleft in software (if we wanted to do that we could do it in the
watchdog core).

> +	return timeleft;
> +}
> +
> +static int imanager_wdt_start(struct watchdog_device *wdt_dev)
> +{
> +	struct imanager_device_data *idev = watchdog_get_drvdata(wdt_dev);
> +	int ret = 0;

Unnecessary variable initialization.

> +
> +	mutex_lock(&idev->lock);
> +

The watchdog core handles locking for watchdog devices.

> +	ret = wdt_start_timer();
> +	if (ret < 0)
> +		dev_err(wdt_dev->dev, "Failed to start timer\n");
> +
> +	mutex_unlock(&idev->lock);
> +
> +	return ret;
> +}
> +
> +static int imanager_wdt_stop(struct watchdog_device *wdt_dev)
> +{
> +	struct imanager_device_data *idev = watchdog_get_drvdata(wdt_dev);
> +	int ret = 0;

Unnecessary variable initialization.

> +
> +	mutex_lock(&idev->lock);
> +
> +	ret = wdt_stop_timer();
> +	if (ret < 0)
> +		dev_err(wdt_dev->dev, "Failed to stop timer\n");
> +
> +	mutex_unlock(&idev->lock);
> +
> +	return ret;
> +}
> +
> +static int imanager_wdt_ping(struct watchdog_device *wdt_dev)
> +{
> +	struct imanager_device_data *idev = watchdog_get_drvdata(wdt_dev);
> +	int ret = 0;
> +
> +	mutex_lock(&idev->lock);
> +
> +	ret = wdt_ping();
> +	if (ret < 0)
> +		dev_err(wdt_dev->dev, "Failed to reset timer\n");

Please consider dropping the error messages.

> +
> +	mutex_unlock(&idev->lock);
> +
> +	return ret;
> +}
> +
> +static long imanager_wdt_ioctl(struct watchdog_device *wdt_dev,
> +			       unsigned int cmd, unsigned long arg)
> +{

Why do you re-implement this function ?

> +	struct imanager_device_data *idev = watchdog_get_drvdata(wdt_dev);
> +	void __user *argp = (void __user *)arg;
> +	int __user *p = argp;
> +	int ret = 0;
> +	int timeval, options;
> +	static const struct watchdog_info ident = {
> +		.options = WDIOF_KEEPALIVEPING |
> +			   WDIOF_SETTIMEOUT |
> +			   WDIOF_MAGICCLOSE,
> +		.firmware_version = 0,
> +		.identity = "imanager_wdt",
> +	};
> +
> +	mutex_lock(&idev->lock);
> +
> +	switch (cmd) {
> +	case WDIOC_GETSUPPORT:
> +		if (copy_to_user(argp, &ident, sizeof(ident)))
> +			ret = -EFAULT;
> +		break;
> +	case WDIOC_GETSTATUS:
> +	case WDIOC_GETBOOTSTATUS:
> +		ret = put_user(0, p);
> +		break;
> +	case WDIOC_SETOPTIONS:
> +		if (get_user(options, p)) {
> +			ret = -EFAULT;
> +			goto out;
> +		}
> +		if (options & WDIOS_DISABLECARD)
> +			wdt_stop_timer();
> +		if (options & WDIOS_ENABLECARD) {
> +			wdt_ping();
> +			wdt_start_timer();
> +		}
> +		break;
> +	case WDIOC_KEEPALIVE:
> +		wdt_ping();
> +		break;
> +	case WDIOC_SETTIMEOUT:
> +		if (get_user(timeval, p)) {
> +			ret = -EFAULT;
> +			goto out;
> +		}
> +		if (imanager_wdt_set(timeval)) {
> +			ret = -EINVAL;
> +			goto out;
> +		}
> +		wdt_ping();
> +		/* Fall through */
> +	case WDIOC_GETTIMEOUT:
> +		ret = put_user(timeout, p);
> +		break;
> +	case WDIOC_GETTIMELEFT:
> +		timeval = imanager_wdt_get_timeleft(wdt_dev);
> +		ret = put_user(timeval, p);
> +		break;
> +	default:
> +		ret = -ENOTTY;
> +	}
> +
> +out:
> +	mutex_unlock(&idev->lock);
> +
> +	return ret;
> +}
> +
> +static const struct watchdog_info imanager_wdt_info = {
> +	.options		= WDIOF_SETTIMEOUT |
> +				  WDIOF_KEEPALIVEPING |
> +				  WDIOF_MAGICCLOSE,
> +	.identity		= "imanager_wdt",
> +	.firmware_version	= 0,
> +};
> +
> +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,
> +	.ioctl		= imanager_wdt_ioctl,
> +};
> +
> +static struct watchdog_device imanager_wdt_dev = {
> +	.info		= &imanager_wdt_info,
> +	.ops		= &imanager_wdt_ops,
> +	.timeout	= WATCHDOG_TIMEOUT,
> +	.min_timeout	= 1,
> +	.max_timeout	= 0xfffe,
> +};
> +
> +static int imanager_wdt_probe(struct platform_device *pdev)
> +{
> +	struct device *dev = &pdev->dev;
> +	struct imanager_device_data *idev = dev_get_drvdata(dev->parent);
> +	int ret;
> +
> +	if (!idev) {
> +		dev_err(dev, "Invalid platform data\n");

-ENODEV, and please no error message here.

> +		return -EINVAL;
> +	}
> +
> +	watchdog_set_nowayout(&imanager_wdt_dev, nowayout);
> +	watchdog_set_drvdata(&imanager_wdt_dev, idev);
> +
> +	ret = watchdog_register_device(&imanager_wdt_dev);
> +	if (ret) {
> +		dev_err(dev, "Failed to register watchdog device\n");
> +		return ret;
> +	}
> +
> +	ret = wdt_core_init();

Shouldn't that come first, before you register the watchdog device ?

> +	if (ret) {
> +		dev_err(dev, "Failed to initialize watchdog core\n");

Please no error message here (the error indicates that there is no watchdog device,
which is controlled by the mfd driver and would be on purpose if it happens).

> +		goto unregister_driver;
> +	}
> +
> +	wdt_core_disable_all();

After registration there is a distinct possibility that the watchdog has already
been enabled by user space by the time this code executes. Disabling it here
might be racy.

> +
> +	imanager_wdt_set_timeout(&imanager_wdt_dev, timeout);
> +
Same here - in theory, user space could already have opened the watchdog device
and changed the timeout. I would suggest to use watchdog_init_timeout() prior
to registering the watchdog.

> +	dev_info(dev, "Driver loaded (timeout=%d seconds)\n", timeout);
> +
> +	return 0;
> +
> +unregister_driver:
> +	watchdog_unregister_device(&imanager_wdt_dev);
> +	platform_set_drvdata(pdev, NULL);

Unnecessary.

> +
> +	return ret;
> +}
> +
> +static int imanager_wdt_remove(struct platform_device *pdev)
> +{
> +	wdt_core_disable_all();
> +
> +	watchdog_unregister_device(&imanager_wdt_dev);
> +	platform_set_drvdata(pdev, NULL);

Unnecessary.

> +
> +	return 0;
> +}
> +
> +static struct platform_driver imanager_wdt_driver = {
> +	.driver = {
> +		.name	= "imanager_wdt",
> +	},
> +	.probe	= imanager_wdt_probe,
> +	.remove	= imanager_wdt_remove,
> +};
> +
> +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");
> diff --git a/include/linux/mfd/imanager/wdt.h b/include/linux/mfd/imanager/wdt.h
> new file mode 100644
> index 0000000..eb709b7
> --- /dev/null
> +++ b/include/linux/mfd/imanager/wdt.h
> @@ -0,0 +1,37 @@
> +/*
> + * Advantech iManager Watchdog core
> + *
> + * Copyright (C) 2016 Advantech Co., Ltd., Irvine, CA, USA
> + * 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.
> + */
> +
> +#ifndef __WDT_H__
> +#define __WDT_H__
> +
> +#include <linux/types.h>
> +
> +enum wdt_event {
> +	DELAY,
> +	PWRBTN,
> +	NMI,
> +	RESET,
> +	WDPIN,
> +	SCI,
> +};
> +
> +int wdt_core_init(void);
> +
> +int wdt_core_set_timeout(enum wdt_event type, u32 timeout);
> +
> +int wdt_core_disable_all(void);
> +int wdt_core_start_timer(void);
> +int wdt_core_stop_timer(void);
> +int wdt_core_reset_timer(void);
> +int wdt_core_stop_boot_timer(void);
> +
> +#endif
>

--
To unsubscribe from this list: send the line "unsubscribe linux-gpio" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Lee Jones Jan. 18, 2016, 7:38 a.m. UTC | #2
On Sun, 10 Jan 2016, richard.dorsch@gmail.com wrote:

> From: Richard Vidal-Dorsch <richard.dorsch@gmail.com>

Some more information required about the device here.

> Signed-off-by: Richard Vidal-Dorsch <richard.dorsch@gmail.com>
> ---
>  drivers/watchdog/Kconfig           |  12 ++
>  drivers/watchdog/Makefile          |   2 +
>  drivers/watchdog/imanager-ec-wdt.c | 170 +++++++++++++++++++
>  drivers/watchdog/imanager-wdt.c    | 333 +++++++++++++++++++++++++++++++++++++
>  include/linux/mfd/imanager/wdt.h   |  37 +++++

Please relocate this to include/linux/watchdog.

>  5 files changed, 554 insertions(+)
>  create mode 100644 drivers/watchdog/imanager-ec-wdt.c
>  create mode 100644 drivers/watchdog/imanager-wdt.c
>  create mode 100644 include/linux/mfd/imanager/wdt.h

[...]

> diff --git a/include/linux/mfd/imanager/wdt.h b/include/linux/mfd/imanager/wdt.h
> new file mode 100644
> index 0000000..eb709b7
> --- /dev/null
> +++ b/include/linux/mfd/imanager/wdt.h
> @@ -0,0 +1,37 @@
> +/*
> + * Advantech iManager Watchdog core
> + *
> + * Copyright (C) 2016 Advantech Co., Ltd., Irvine, CA, USA
> + * 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.
> + */
> +
> +#ifndef __WDT_H__
> +#define __WDT_H__

These need to be less generic.

> +#include <linux/types.h>
> +
> +enum wdt_event {
> +	DELAY,
> +	PWRBTN,
> +	NMI,
> +	RESET,
> +	WDPIN,
> +	SCI,
> +};

Unless shared between other source files, this should probably move
into the driver.

> +int wdt_core_init(void);
> +
> +int wdt_core_set_timeout(enum wdt_event type, u32 timeout);
> +
> +int wdt_core_disable_all(void);
> +int wdt_core_start_timer(void);
> +int wdt_core_stop_timer(void);
> +int wdt_core_reset_timer(void);
> +int wdt_core_stop_boot_timer(void);

What's the point of these?

> +#endif
> -- 
> 2.7.0
>
diff mbox

Patch

diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig
index 1c427be..e555127 100644
--- a/drivers/watchdog/Kconfig
+++ b/drivers/watchdog/Kconfig
@@ -846,6 +846,18 @@  config ITCO_VENDOR_SUPPORT
 	  devices. At this moment we only have additional support for some
 	  SuperMicro Inc. motherboards.
 
+config IMANAGER_WDT
+	tristate "Advantech iManager Watchdog"
+	depends on MFD_IMANAGER
+	select WATCHDOG_CORE
+	help
+	  This driver provides support for Advantech iManager watchdog
+	  of Advantech SOM, MIO, AIMB, and PCM modules/boards.
+	  Requires mfd-core and imanager-core to function properly.
+
+	  This driver can also be built as a module.  If so, the module
+	  will be called imanager_wdt.
+
 config IT8712F_WDT
 	tristate "IT8712F (Smart Guardian) Watchdog Timer"
 	depends on X86
diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile
index 53d4827..647eca87 100644
--- a/drivers/watchdog/Makefile
+++ b/drivers/watchdog/Makefile
@@ -100,6 +100,8 @@  obj-$(CONFIG_ITCO_WDT) += iTCO_wdt.o
 ifeq ($(CONFIG_ITCO_VENDOR_SUPPORT),y)
 obj-$(CONFIG_ITCO_WDT) += iTCO_vendor_support.o
 endif
+imanager_wdt-objs := imanager-wdt.o imanager-ec-wdt.o
+obj-$(CONFIG_IMANAGER_WDT) += imanager_wdt.o
 obj-$(CONFIG_IT8712F_WDT) += it8712f_wdt.o
 obj-$(CONFIG_IT87_WDT) += it87_wdt.o
 obj-$(CONFIG_HP_WATCHDOG) += hpwdt.o
diff --git a/drivers/watchdog/imanager-ec-wdt.c b/drivers/watchdog/imanager-ec-wdt.c
new file mode 100644
index 0000000..9c94b21
--- /dev/null
+++ b/drivers/watchdog/imanager-ec-wdt.c
@@ -0,0 +1,170 @@ 
+/*
+ * Advantech iManager Watchdog core
+ *
+ * Copyright (C) 2016 Advantech Co., Ltd., Irvine, CA, USA
+ * 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.
+ *
+ * Note: Current release only implements RESET of the EC WDT.
+ *       In other words, no PWR button, NMI, SCI, IRQ, or WDPin support yet!
+ */
+
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/bug.h>
+#include <linux/io.h>
+#include <linux/delay.h>
+#include <linux/string.h>
+#include <linux/byteorder/generic.h>
+#include <linux/swab.h>
+#include <linux/mfd/imanager/ec.h>
+#include <linux/mfd/imanager/wdt.h>
+
+/* Timer resolution */
+#define WDT_FREQ	10 /* Hz */
+
+enum wdt_ctrl {
+	START = 1,
+	STOP,
+	RST,
+	GET_TIMEOUT,
+	SET_TIMEOUT,
+	STOPBOOT = 8,
+};
+
+struct wdt_event_delay {
+	u16	delay,
+		pwrbtn,
+		nmi,
+		reset,
+		wdpin,
+		sci,
+		dummy;
+};
+
+static const struct imanager_watchdog_device *wd;
+
+static inline int set_timer(enum wdt_ctrl ctrl)
+{
+	if (WARN_ON(ctrl == SET_TIMEOUT))
+		return -EINVAL;
+
+	return imanager_msg_write(EC_CMD_WDT_CTRL, ctrl, NULL);
+}
+
+static int
+wdt_ctrl(enum wdt_ctrl ctrl, enum wdt_event type, unsigned int timeout)
+{
+	u16 val;
+	int ret;
+	struct ec_message msg = {
+		.rlen = 0,
+		.wlen = 0,
+	};
+	u8 *fevent = &msg.u.data[0];
+	struct wdt_event_delay *event =
+				(struct wdt_event_delay *)&msg.u.data[1];
+
+	switch (ctrl) {
+	case SET_TIMEOUT:
+		memset(event, 0xff, sizeof(*event));
+		msg.wlen = sizeof(*event);
+		*fevent = 0;
+		val = (!timeout) ? 0xffff : swab16(timeout * WDT_FREQ);
+
+		switch (type) {
+		case DELAY:
+			event->delay = val;
+			break;
+		case PWRBTN:
+			event->pwrbtn = val;
+			break;
+		case NMI:
+			event->nmi = val;
+			break;
+		case RESET:
+			event->reset = val;
+			break;
+		case WDPIN:
+			event->wdpin = val;
+			break;
+		case SCI:
+			event->sci = val;
+			break;
+		default:
+			return -EINVAL;
+		}
+
+		ret = imanager_msg_write(EC_CMD_WDT_CTRL, SET_TIMEOUT, &msg);
+		if (ret < 0) {
+			pr_err("Failed to set timeout\n");
+			return ret;
+		}
+		break;
+	case START:
+	case STOP:
+	case RST:
+	case STOPBOOT:
+		/* simple command, no data */
+		return imanager_msg_write(EC_CMD_WDT_CTRL, ctrl, NULL);
+	default:
+		return -EINVAL;
+	}
+
+	return timeout;
+}
+
+int wdt_core_start_timer(void)
+{
+	return set_timer(START);
+}
+
+int wdt_core_stop_timer(void)
+{
+	return set_timer(STOP);
+}
+
+int wdt_core_reset_timer(void)
+{
+	return set_timer(RST);
+}
+
+int wdt_core_stop_boot_timer(void)
+{
+	return set_timer(STOPBOOT);
+}
+
+inline int wdt_core_set_timeout(enum wdt_event type, u32 timeout)
+{
+	return wdt_ctrl(SET_TIMEOUT, type, timeout);
+}
+
+int wdt_core_disable_all(void)
+{
+	struct ec_message msg = {
+		.rlen = 0,
+		.wlen = sizeof(struct wdt_event_delay),
+	};
+	struct wdt_event_delay *event =
+				(struct wdt_event_delay *)&msg.u.data[1];
+
+	memset(event, 0xff, sizeof(*event));
+
+	return  (wdt_core_stop_timer() ||
+		 wdt_core_stop_boot_timer() ||
+		 imanager_msg_write(EC_CMD_WDT_CTRL, SET_TIMEOUT, &msg));
+}
+
+int wdt_core_init(void)
+{
+	wd = imanager_get_watchdog_device();
+	if (!wd)
+		return -ENODEV;
+
+	return 0;
+}
+
diff --git a/drivers/watchdog/imanager-wdt.c b/drivers/watchdog/imanager-wdt.c
new file mode 100644
index 0000000..10f8e22
--- /dev/null
+++ b/drivers/watchdog/imanager-wdt.c
@@ -0,0 +1,333 @@ 
+/*
+ * Advantech iManager Watchdog driver
+ *
+ * Copyright (C) 2016 Advantech Co., Ltd., Irvine, CA, USA
+ * 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/module.h>
+#include <linux/moduleparam.h>
+#include <linux/types.h>
+#include <linux/watchdog.h>
+#include <linux/notifier.h>
+#include <linux/reboot.h>
+#include <linux/uaccess.h>
+#include <linux/platform_device.h>
+#include <linux/mfd/imanager/core.h>
+#include <linux/mfd/imanager/wdt.h>
+
+#define WATCHDOG_TIMEOUT 30 /* in seconds */
+
+static unsigned long last_updated = -1;
+
+static uint timeout = WATCHDOG_TIMEOUT;
+module_param(timeout, uint, 0);
+MODULE_PARM_DESC(timeout,
+	"Watchdog timeout in seconds. 1 <= timeout <= 65534, 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 wdt_start_timer(void)
+{
+	int ret;
+
+	ret = wdt_core_start_timer();
+	if (ret < 0)
+		return ret;
+
+	last_updated = jiffies;
+
+	return 0;
+}
+
+static int wdt_stop_timer(void)
+{
+	int ret;
+
+	ret = wdt_core_stop_timer();
+	if (ret < 0)
+		return ret;
+
+	last_updated = 0;
+
+	return 0;
+}
+
+static int wdt_ping(void)
+{
+	int ret;
+
+	ret = wdt_core_reset_timer();
+	if (ret < 0)
+		return ret;
+
+	last_updated = jiffies;
+
+	return 0;
+}
+
+static int imanager_wdt_set(unsigned int _timeout)
+{
+	int ret;
+
+	if (timeout != _timeout)
+		timeout = _timeout;
+
+	ret = wdt_core_set_timeout(PWRBTN, timeout);
+	if (ret < 0)
+		return ret;
+
+	if (last_updated != 0)
+		last_updated = jiffies;
+
+	return 0;
+}
+
+static int imanager_wdt_set_timeout(struct watchdog_device *wdt_dev,
+				    unsigned int timeout)
+{
+	struct imanager_device_data *idev = watchdog_get_drvdata(wdt_dev);
+	int ret = 0;
+
+	mutex_lock(&idev->lock);
+
+	ret = imanager_wdt_set(timeout);
+	if (ret < 0)
+		dev_err(wdt_dev->dev, "Failed to set timeout\n");
+	mutex_unlock(&idev->lock);
+
+	return ret;
+}
+
+static unsigned int imanager_wdt_get_timeleft(struct watchdog_device *wdt_dev)
+{
+	unsigned int timeleft = 0;
+	unsigned long time_diff = ((jiffies - last_updated) / HZ);
+
+	if (last_updated && (timeout > time_diff))
+		timeleft = timeout - time_diff;
+
+	return timeleft;
+}
+
+static int imanager_wdt_start(struct watchdog_device *wdt_dev)
+{
+	struct imanager_device_data *idev = watchdog_get_drvdata(wdt_dev);
+	int ret = 0;
+
+	mutex_lock(&idev->lock);
+
+	ret = wdt_start_timer();
+	if (ret < 0)
+		dev_err(wdt_dev->dev, "Failed to start timer\n");
+
+	mutex_unlock(&idev->lock);
+
+	return ret;
+}
+
+static int imanager_wdt_stop(struct watchdog_device *wdt_dev)
+{
+	struct imanager_device_data *idev = watchdog_get_drvdata(wdt_dev);
+	int ret = 0;
+
+	mutex_lock(&idev->lock);
+
+	ret = wdt_stop_timer();
+	if (ret < 0)
+		dev_err(wdt_dev->dev, "Failed to stop timer\n");
+
+	mutex_unlock(&idev->lock);
+
+	return ret;
+}
+
+static int imanager_wdt_ping(struct watchdog_device *wdt_dev)
+{
+	struct imanager_device_data *idev = watchdog_get_drvdata(wdt_dev);
+	int ret = 0;
+
+	mutex_lock(&idev->lock);
+
+	ret = wdt_ping();
+	if (ret < 0)
+		dev_err(wdt_dev->dev, "Failed to reset timer\n");
+
+	mutex_unlock(&idev->lock);
+
+	return ret;
+}
+
+static long imanager_wdt_ioctl(struct watchdog_device *wdt_dev,
+			       unsigned int cmd, unsigned long arg)
+{
+	struct imanager_device_data *idev = watchdog_get_drvdata(wdt_dev);
+	void __user *argp = (void __user *)arg;
+	int __user *p = argp;
+	int ret = 0;
+	int timeval, options;
+	static const struct watchdog_info ident = {
+		.options = WDIOF_KEEPALIVEPING |
+			   WDIOF_SETTIMEOUT |
+			   WDIOF_MAGICCLOSE,
+		.firmware_version = 0,
+		.identity = "imanager_wdt",
+	};
+
+	mutex_lock(&idev->lock);
+
+	switch (cmd) {
+	case WDIOC_GETSUPPORT:
+		if (copy_to_user(argp, &ident, sizeof(ident)))
+			ret = -EFAULT;
+		break;
+	case WDIOC_GETSTATUS:
+	case WDIOC_GETBOOTSTATUS:
+		ret = put_user(0, p);
+		break;
+	case WDIOC_SETOPTIONS:
+		if (get_user(options, p)) {
+			ret = -EFAULT;
+			goto out;
+		}
+		if (options & WDIOS_DISABLECARD)
+			wdt_stop_timer();
+		if (options & WDIOS_ENABLECARD) {
+			wdt_ping();
+			wdt_start_timer();
+		}
+		break;
+	case WDIOC_KEEPALIVE:
+		wdt_ping();
+		break;
+	case WDIOC_SETTIMEOUT:
+		if (get_user(timeval, p)) {
+			ret = -EFAULT;
+			goto out;
+		}
+		if (imanager_wdt_set(timeval)) {
+			ret = -EINVAL;
+			goto out;
+		}
+		wdt_ping();
+		/* Fall through */
+	case WDIOC_GETTIMEOUT:
+		ret = put_user(timeout, p);
+		break;
+	case WDIOC_GETTIMELEFT:
+		timeval = imanager_wdt_get_timeleft(wdt_dev);
+		ret = put_user(timeval, p);
+		break;
+	default:
+		ret = -ENOTTY;
+	}
+
+out:
+	mutex_unlock(&idev->lock);
+
+	return ret;
+}
+
+static const struct watchdog_info imanager_wdt_info = {
+	.options		= WDIOF_SETTIMEOUT |
+				  WDIOF_KEEPALIVEPING |
+				  WDIOF_MAGICCLOSE,
+	.identity		= "imanager_wdt",
+	.firmware_version	= 0,
+};
+
+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,
+	.ioctl		= imanager_wdt_ioctl,
+};
+
+static struct watchdog_device imanager_wdt_dev = {
+	.info		= &imanager_wdt_info,
+	.ops		= &imanager_wdt_ops,
+	.timeout	= WATCHDOG_TIMEOUT,
+	.min_timeout	= 1,
+	.max_timeout	= 0xfffe,
+};
+
+static int imanager_wdt_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct imanager_device_data *idev = dev_get_drvdata(dev->parent);
+	int ret;
+
+	if (!idev) {
+		dev_err(dev, "Invalid platform data\n");
+		return -EINVAL;
+	}
+
+	watchdog_set_nowayout(&imanager_wdt_dev, nowayout);
+	watchdog_set_drvdata(&imanager_wdt_dev, idev);
+
+	ret = watchdog_register_device(&imanager_wdt_dev);
+	if (ret) {
+		dev_err(dev, "Failed to register watchdog device\n");
+		return ret;
+	}
+
+	ret = wdt_core_init();
+	if (ret) {
+		dev_err(dev, "Failed to initialize watchdog core\n");
+		goto unregister_driver;
+	}
+
+	wdt_core_disable_all();
+
+	imanager_wdt_set_timeout(&imanager_wdt_dev, timeout);
+
+	dev_info(dev, "Driver loaded (timeout=%d seconds)\n", timeout);
+
+	return 0;
+
+unregister_driver:
+	watchdog_unregister_device(&imanager_wdt_dev);
+	platform_set_drvdata(pdev, NULL);
+
+	return ret;
+}
+
+static int imanager_wdt_remove(struct platform_device *pdev)
+{
+	wdt_core_disable_all();
+
+	watchdog_unregister_device(&imanager_wdt_dev);
+	platform_set_drvdata(pdev, NULL);
+
+	return 0;
+}
+
+static struct platform_driver imanager_wdt_driver = {
+	.driver = {
+		.name	= "imanager_wdt",
+	},
+	.probe	= imanager_wdt_probe,
+	.remove	= imanager_wdt_remove,
+};
+
+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");
diff --git a/include/linux/mfd/imanager/wdt.h b/include/linux/mfd/imanager/wdt.h
new file mode 100644
index 0000000..eb709b7
--- /dev/null
+++ b/include/linux/mfd/imanager/wdt.h
@@ -0,0 +1,37 @@ 
+/*
+ * Advantech iManager Watchdog core
+ *
+ * Copyright (C) 2016 Advantech Co., Ltd., Irvine, CA, USA
+ * 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.
+ */
+
+#ifndef __WDT_H__
+#define __WDT_H__
+
+#include <linux/types.h>
+
+enum wdt_event {
+	DELAY,
+	PWRBTN,
+	NMI,
+	RESET,
+	WDPIN,
+	SCI,
+};
+
+int wdt_core_init(void);
+
+int wdt_core_set_timeout(enum wdt_event type, u32 timeout);
+
+int wdt_core_disable_all(void);
+int wdt_core_start_timer(void);
+int wdt_core_stop_timer(void);
+int wdt_core_reset_timer(void);
+int wdt_core_stop_boot_timer(void);
+
+#endif