diff mbox

[v6] watchdog: Add DA9063 PMIC watchdog driver.

Message ID 1410184713-6436-1-git-send-email-mpa@pengutronix.de (mailing list archive)
State New, archived
Headers show

Commit Message

Markus Pargmann Sept. 8, 2014, 1:58 p.m. UTC
From: Krystian Garbaciak <krystian.garbaciak@diasemi.com>

This driver supports the watchdog device inside the DA9063 PMIC.

Signed-off-by: Krystian Garbaciak <krystian.garbaciak@diasemi.com>
Signed-off-by: Philipp Zabel <p.zabel@pengutronix.de>
Signed-off-by: Markus Pargmann <mpa@pengutronix.de>
---

Notes:
    Changes in v6:
     - Fix compile error
    
    Changes in v5:
     - Kconfig: Add note about the module name.
     - Change selector variables to int
     - Style fixes
     - use unsigned int to remove gcc verbose warning
     - Integrate _kick() and _disable() into the calling functions
    
    Changes in v4:
     - Fixed indentation
     - Fixed lock without unlock
     - Check for parent device and device driver data in probe(). If not present,
       this is an invalid prober initialization, return -EINVAL.
    
    Changes in v3:
     - Cleanup error handling for timeout selection and setting
     - Fix race condition due to late wdt drvdata setup
     - Remove static struct watchdog_device. It is not initialized in the probe
       function
     - Use WATCHDOG_NOWAYOUT_INIT_STATUS for the status
     - Remove 0 shift using DA9063_TWDSCALE_SHIFT

 drivers/watchdog/Kconfig      |   9 ++
 drivers/watchdog/Makefile     |   1 +
 drivers/watchdog/da9063_wdt.c | 222 ++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 232 insertions(+)
 create mode 100644 drivers/watchdog/da9063_wdt.c

Comments

Guenter Roeck Sept. 11, 2014, 4:30 p.m. UTC | #1
On Mon, Sep 08, 2014 at 03:58:33PM +0200, Markus Pargmann wrote:
> From: Krystian Garbaciak <krystian.garbaciak@diasemi.com>
> 
> This driver supports the watchdog device inside the DA9063 PMIC.
> 
> Signed-off-by: Krystian Garbaciak <krystian.garbaciak@diasemi.com>
> Signed-off-by: Philipp Zabel <p.zabel@pengutronix.de>
> Signed-off-by: Markus Pargmann <mpa@pengutronix.de>
> ---
> 
> Notes:
>     Changes in v6:
>      - Fix compile error
>     
>     Changes in v5:
>      - Kconfig: Add note about the module name.
>      - Change selector variables to int
>      - Style fixes
>      - use unsigned int to remove gcc verbose warning
>      - Integrate _kick() and _disable() into the calling functions
>     
>     Changes in v4:
>      - Fixed indentation
>      - Fixed lock without unlock
>      - Check for parent device and device driver data in probe(). If not present,
>        this is an invalid prober initialization, return -EINVAL.
>     
>     Changes in v3:
>      - Cleanup error handling for timeout selection and setting
>      - Fix race condition due to late wdt drvdata setup
>      - Remove static struct watchdog_device. It is not initialized in the probe
>        function
>      - Use WATCHDOG_NOWAYOUT_INIT_STATUS for the status
>      - Remove 0 shift using DA9063_TWDSCALE_SHIFT
> 
>  drivers/watchdog/Kconfig      |   9 ++
>  drivers/watchdog/Makefile     |   1 +
>  drivers/watchdog/da9063_wdt.c | 222 ++++++++++++++++++++++++++++++++++++++++++
>  3 files changed, 232 insertions(+)
>  create mode 100644 drivers/watchdog/da9063_wdt.c
> 
> diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig
> index f57312fced80..669de63a7a48 100644
> --- a/drivers/watchdog/Kconfig
> +++ b/drivers/watchdog/Kconfig
> @@ -87,6 +87,15 @@ config DA9055_WATCHDOG
>  	  This driver can also be built as a module.  If so, the module
>  	  will be called da9055_wdt.
>  
> +config DA9063_WATCHDOG
> +	tristate "Dialog DA9063 Watchdog"
> +	depends on MFD_DA9063
> +	select WATCHDOG_CORE
> +	help
> +	  Support for the watchdog in the DA9063 PMIC.
> +
> +	  This driver can be built as a module. The module name is da9063_wdt.
> +
>  config GPIO_WATCHDOG
>  	tristate "Watchdog device controlled through GPIO-line"
>  	depends on OF_GPIO
> diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile
> index 468c3204c3b1..6c467a9821fe 100644
> --- a/drivers/watchdog/Makefile
> +++ b/drivers/watchdog/Makefile
> @@ -173,6 +173,7 @@ obj-$(CONFIG_XEN_WDT) += xen_wdt.o
>  # Architecture Independent
>  obj-$(CONFIG_DA9052_WATCHDOG) += da9052_wdt.o
>  obj-$(CONFIG_DA9055_WATCHDOG) += da9055_wdt.o
> +obj-$(CONFIG_DA9063_WATCHDOG) += da9063_wdt.o
>  obj-$(CONFIG_GPIO_WATCHDOG)	+= gpio_wdt.o
>  obj-$(CONFIG_WM831X_WATCHDOG) += wm831x_wdt.o
>  obj-$(CONFIG_WM8350_WATCHDOG) += wm8350_wdt.o
> diff --git a/drivers/watchdog/da9063_wdt.c b/drivers/watchdog/da9063_wdt.c
> new file mode 100644
> index 000000000000..88441bdd2d1b
> --- /dev/null
> +++ b/drivers/watchdog/da9063_wdt.c
> @@ -0,0 +1,222 @@
> +/*
> + * Watchdog driver for DA9063 PMICs.
> + *
> + * Copyright(c) 2012 Dialog Semiconductor Ltd.
> + *
> + * Author: Mariusz Wojtasik <mariusz.wojtasik@diasemi.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.
> + */
> +
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/watchdog.h>
> +#include <linux/platform_device.h>
> +#include <linux/uaccess.h>
> +#include <linux/slab.h>
> +#include <linux/delay.h>
> +#include <linux/mfd/da9063/registers.h>
> +#include <linux/mfd/da9063/core.h>
> +#include <linux/regmap.h>
> +
> +/*
> + * Watchdog selector to timeout in seconds.
> + *   0: WDT disabled;
> + *   others: timeout = 2048 ms * 2^(TWDSCALE-1).
> + */
> +static const int wdt_timeout[] = { 0, 2, 4, 8, 16, 32, 65, 131 };
> +#define DA9063_TWDSCALE_DISABLE		0
> +#define DA9063_TWDSCALE_MIN		1
> +#define DA9063_TWDSCALE_MAX		(ARRAY_SIZE(wdt_timeout) - 1)
> +#define DA9063_WDT_MIN_TIMEOUT		wdt_timeout[DA9063_TWDSCALE_MIN]
> +#define DA9063_WDT_MAX_TIMEOUT		wdt_timeout[DA9063_TWDSCALE_MAX]
> +#define DA9063_WDG_TIMEOUT		wdt_timeout[3]
> +
> +struct da9063_watchdog {
> +	struct da9063 *da9063;
> +	struct watchdog_device wdtdev;
> +	struct mutex lock;

The watchdog subsystem already provides locking. I don't find a single case
where this additional lock is needed. Can you give me one ?

> +};
> +
> +static int da9063_wdt_timeout_to_sel(int secs)
> +{
> +	unsigned int i;
> +
> +	for (i = DA9063_TWDSCALE_MIN; i <= DA9063_TWDSCALE_MAX; i++) {
> +		if (wdt_timeout[i] >= secs)
> +			return i;
> +	}
> +
> +	return -EINVAL;
> +}
> +
> +static int _da9063_wdt_set_timeout(struct da9063 *da9063, unsigned int regval)
> +{
> +	return regmap_update_bits(da9063->regmap, DA9063_REG_CONTROL_D,
> +			DA9063_TWDSCALE_MASK, regval);

For hwmon I decided to start enforcing indentation rules, for the simple
reason that everyone otherwise uses different second-line indentations.
Lucky for you I am not the watchdog maintainer ;-)

> +}
> +
> +static int da9063_wdt_start(struct watchdog_device *wdd)
> +{
> +	struct da9063_watchdog *wdt = watchdog_get_drvdata(wdd);
> +	int selector;
> +	int ret;
> +
> +	selector = da9063_wdt_timeout_to_sel(wdt->wdtdev.timeout);
> +	if (selector < 0) {
> +		dev_err(wdt->da9063->dev, "Timeout out of range (timeout=%d)\n",
> +				wdt->wdtdev.timeout);
> +		return selector;
> +	}
> +
> +	mutex_lock(&wdt->lock);
> +	ret = _da9063_wdt_set_timeout(wdt->da9063, selector);
> +	if (ret) {
> +		dev_err(wdt->da9063->dev, "Watchdog failed to start (err = %d)\n",
> +				ret);
> +	}
> +	mutex_unlock(&wdt->lock);
> +
> +	return ret;
> +}
> +
> +static int da9063_wdt_stop(struct watchdog_device *wdd)
> +{
> +	struct da9063_watchdog *wdt = watchdog_get_drvdata(wdd);
> +	int ret;
> +
> +	mutex_lock(&wdt->lock);
> +	ret = regmap_update_bits(wdt->da9063->regmap, DA9063_REG_CONTROL_D,
> +			DA9063_TWDSCALE_MASK,
> +			DA9063_TWDSCALE_DISABLE);
> +	if (ret)
> +		dev_alert(wdt->da9063->dev, "Watchdog failed to stop (err = %d)\n",
> +				ret);

I am personally not in favor of dumping error mesasges onto the console if the
error is also reported to user space. My logic is that if everyone would be
doing that logging, the log would be all noise and be completely useless.

> +	mutex_unlock(&wdt->lock);
> +
> +	return ret;
> +}
> +
> +static int da9063_wdt_ping(struct watchdog_device *wdd)
> +{
> +	struct da9063_watchdog *wdt = watchdog_get_drvdata(wdd);
> +	int ret;
> +
> +	mutex_lock(&wdt->lock);
> +	ret = regmap_write(wdt->da9063->regmap, DA9063_REG_CONTROL_F,
> +			DA9063_WATCHDOG);
> +	if (ret)
> +		dev_alert(wdt->da9063->dev, "Failed to ping the watchdog (err = %d)\n",
> +				ret);
> +	mutex_unlock(&wdt->lock);
> +
> +	return ret;
> +}
> +
> +static int da9063_wdt_set_timeout(struct watchdog_device *wdd,
> +		unsigned int timeout)
> +{
> +	struct da9063_watchdog *wdt = watchdog_get_drvdata(wdd);
> +	int selector;
> +	int ret;
> +
> +	selector = da9063_wdt_timeout_to_sel(timeout);
> +	if (selector < 0) {
> +		dev_err(wdt->da9063->dev,
> +				"Unsupported watchdog timeout, should be between 2 and 131\n");

Since the caller already performs a range check, I wonder if this repeated
error check really provides additional value. Seems to me it would be much
simpler to have da9063_wdt_timeout_to_sel always return a valid value and
to drop all those additional range checks.

> +		return -EINVAL;
> +	}
> +
> +	mutex_lock(&wdt->lock);
> +
> +	ret = _da9063_wdt_set_timeout(wdt->da9063, selector);
> +	if (ret)
> +		dev_err(wdt->da9063->dev, "Failed to set watchdog timeout (err = %d)\n",
> +				ret);
> +	else
> +		wdd->timeout = wdt_timeout[selector];
> +
> +	mutex_unlock(&wdt->lock);
> +
> +	return ret;
> +}
> +
> +static const struct watchdog_info da9063_watchdog_info = {
> +	.options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING,
> +	.identity = "DA9063 Watchdog",
> +};
> +
> +static const struct watchdog_ops da9063_watchdog_ops = {
> +	.owner = THIS_MODULE,
> +	.start = da9063_wdt_start,
> +	.stop = da9063_wdt_stop,
> +	.ping = da9063_wdt_ping,
> +	.set_timeout = da9063_wdt_set_timeout,
> +};
> +
> +static int da9063_wdt_probe(struct platform_device *pdev)
> +{
> +	int ret;
> +	struct da9063 *da9063;
> +	struct da9063_watchdog *wdt;
> +
> +	if (!pdev->dev.parent)
> +		return -EINVAL;
> +
> +	da9063 = dev_get_drvdata(pdev->dev.parent);
> +	if (!da9063)
> +		return -EINVAL;
> +

This is not really an invalid argument. -ENODEV seems to be more appropriate,
given the context (presumably it means that there is no parent mfd device).

> +	wdt = devm_kzalloc(&pdev->dev, sizeof(*wdt), GFP_KERNEL);
> +	if (!wdt)
> +		return -ENOMEM;
> +
> +	wdt->da9063 = da9063;
> +	mutex_init(&wdt->lock);
> +
> +	wdt->wdtdev.info = &da9063_watchdog_info;
> +	wdt->wdtdev.ops = &da9063_watchdog_ops;
> +	wdt->wdtdev.min_timeout = DA9063_WDT_MIN_TIMEOUT;
> +	wdt->wdtdev.max_timeout = DA9063_WDT_MAX_TIMEOUT;
> +	wdt->wdtdev.timeout = DA9063_WDG_TIMEOUT;
> +
> +	wdt->wdtdev.status = WATCHDOG_NOWAYOUT_INIT_STATUS;
> +
> +	watchdog_set_drvdata(&wdt->wdtdev, wdt);
> +	dev_set_drvdata(&pdev->dev, wdt);
> +
> +	ret = watchdog_register_device(&wdt->wdtdev);
> +	if (ret) {
> +		dev_err(da9063->dev, "da9063-watchdog registration failed (err = %d)",
> +				ret);

"da9063-watchdog" is really redundant since dev_err already displays
the device name.

> +		return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static int da9063_wdt_remove(struct platform_device *pdev)
> +{
> +	struct da9063_watchdog *wdt = dev_get_drvdata(&pdev->dev);
> +
> +	watchdog_unregister_device(&wdt->wdtdev);
> +
> +	return 0;
> +}
> +
> +static struct platform_driver da9063_wdt_driver = {
> +	.probe = da9063_wdt_probe,
> +	.remove = da9063_wdt_remove,
> +	.driver = {
> +		.name = DA9063_DRVNAME_WATCHDOG,
> +	},
> +};
> +module_platform_driver(da9063_wdt_driver);
> +
> +MODULE_AUTHOR("Mariusz Wojtasik <mariusz.wojtasik@diasemi.com>");
> +MODULE_DESCRIPTION("Watchdog driver for Dialog DA9063");
> +MODULE_LICENSE("GPL");
> +MODULE_ALIAS("platform:" DA9063_DRVNAME_WATCHDOG);
> -- 
> 2.1.0
>
Guenter Roeck Sept. 19, 2014, 4:33 a.m. UTC | #2
On 09/08/2014 06:58 AM, Markus Pargmann wrote:
> From: Krystian Garbaciak <krystian.garbaciak@diasemi.com>
>
> This driver supports the watchdog device inside the DA9063 PMIC.
>
> Signed-off-by: Krystian Garbaciak <krystian.garbaciak@diasemi.com>
> Signed-off-by: Philipp Zabel <p.zabel@pengutronix.de>
> Signed-off-by: Markus Pargmann <mpa@pengutronix.de>
> ---
>
> Notes:
>      Changes in v6:
>       - Fix compile error
>
>      Changes in v5:
>       - Kconfig: Add note about the module name.
>       - Change selector variables to int
>       - Style fixes
>       - use unsigned int to remove gcc verbose warning
>       - Integrate _kick() and _disable() into the calling functions
>
>      Changes in v4:
>       - Fixed indentation
>       - Fixed lock without unlock
>       - Check for parent device and device driver data in probe(). If not present,
>         this is an invalid prober initialization, return -EINVAL.
>
>      Changes in v3:
>       - Cleanup error handling for timeout selection and setting
>       - Fix race condition due to late wdt drvdata setup
>       - Remove static struct watchdog_device. It is not initialized in the probe
>         function
>       - Use WATCHDOG_NOWAYOUT_INIT_STATUS for the status
>       - Remove 0 shift using DA9063_TWDSCALE_SHIFT
>
>   drivers/watchdog/Kconfig      |   9 ++
>   drivers/watchdog/Makefile     |   1 +
>   drivers/watchdog/da9063_wdt.c | 222 ++++++++++++++++++++++++++++++++++++++++++
>   3 files changed, 232 insertions(+)
>   create mode 100644 drivers/watchdog/da9063_wdt.c
>
> diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig
> index f57312fced80..669de63a7a48 100644
> --- a/drivers/watchdog/Kconfig
> +++ b/drivers/watchdog/Kconfig
> @@ -87,6 +87,15 @@ config DA9055_WATCHDOG
>   	  This driver can also be built as a module.  If so, the module
>   	  will be called da9055_wdt.
>
> +config DA9063_WATCHDOG
> +	tristate "Dialog DA9063 Watchdog"
> +	depends on MFD_DA9063
> +	select WATCHDOG_CORE
> +	help
> +	  Support for the watchdog in the DA9063 PMIC.
> +
> +	  This driver can be built as a module. The module name is da9063_wdt.
> +
>   config GPIO_WATCHDOG
>   	tristate "Watchdog device controlled through GPIO-line"
>   	depends on OF_GPIO
> diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile
> index 468c3204c3b1..6c467a9821fe 100644
> --- a/drivers/watchdog/Makefile
> +++ b/drivers/watchdog/Makefile
> @@ -173,6 +173,7 @@ obj-$(CONFIG_XEN_WDT) += xen_wdt.o
>   # Architecture Independent
>   obj-$(CONFIG_DA9052_WATCHDOG) += da9052_wdt.o
>   obj-$(CONFIG_DA9055_WATCHDOG) += da9055_wdt.o
> +obj-$(CONFIG_DA9063_WATCHDOG) += da9063_wdt.o
>   obj-$(CONFIG_GPIO_WATCHDOG)	+= gpio_wdt.o
>   obj-$(CONFIG_WM831X_WATCHDOG) += wm831x_wdt.o
>   obj-$(CONFIG_WM8350_WATCHDOG) += wm8350_wdt.o
> diff --git a/drivers/watchdog/da9063_wdt.c b/drivers/watchdog/da9063_wdt.c
> new file mode 100644
> index 000000000000..88441bdd2d1b
> --- /dev/null
> +++ b/drivers/watchdog/da9063_wdt.c
> @@ -0,0 +1,222 @@
> +/*
> + * Watchdog driver for DA9063 PMICs.
> + *
> + * Copyright(c) 2012 Dialog Semiconductor Ltd.
> + *
> + * Author: Mariusz Wojtasik <mariusz.wojtasik@diasemi.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.
> + */
> +
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/watchdog.h>
> +#include <linux/platform_device.h>
> +#include <linux/uaccess.h>
> +#include <linux/slab.h>
> +#include <linux/delay.h>
> +#include <linux/mfd/da9063/registers.h>
> +#include <linux/mfd/da9063/core.h>
> +#include <linux/regmap.h>
> +
> +/*
> + * Watchdog selector to timeout in seconds.
> + *   0: WDT disabled;
> + *   others: timeout = 2048 ms * 2^(TWDSCALE-1).
> + */
> +static const int wdt_timeout[] = { 0, 2, 4, 8, 16, 32, 65, 131 };
> +#define DA9063_TWDSCALE_DISABLE		0
> +#define DA9063_TWDSCALE_MIN		1
> +#define DA9063_TWDSCALE_MAX		(ARRAY_SIZE(wdt_timeout) - 1)
> +#define DA9063_WDT_MIN_TIMEOUT		wdt_timeout[DA9063_TWDSCALE_MIN]
> +#define DA9063_WDT_MAX_TIMEOUT		wdt_timeout[DA9063_TWDSCALE_MAX]
> +#define DA9063_WDG_TIMEOUT		wdt_timeout[3]
> +
> +struct da9063_watchdog {
> +	struct da9063 *da9063;
> +	struct watchdog_device wdtdev;
> +	struct mutex lock;
> +};
> +
> +static int da9063_wdt_timeout_to_sel(int secs)
> +{
> +	unsigned int i;
> +
> +	for (i = DA9063_TWDSCALE_MIN; i <= DA9063_TWDSCALE_MAX; i++) {
> +		if (wdt_timeout[i] >= secs)
> +			return i;
> +	}
> +
> +	return -EINVAL;
> +}
> +
> +static int _da9063_wdt_set_timeout(struct da9063 *da9063, unsigned int regval)
> +{
> +	return regmap_update_bits(da9063->regmap, DA9063_REG_CONTROL_D,
> +			DA9063_TWDSCALE_MASK, regval);

Lucky you I am not the watchdog maintainer. For the hwmon drivers
I review I decided to enforce the "align continuation lines with '('"
rule after seeing the all-over-the-place indentation in this driver.

I know you explained it, but for me it is just confusing.

> +}
> +
> +static int da9063_wdt_start(struct watchdog_device *wdd)
> +{
> +	struct da9063_watchdog *wdt = watchdog_get_drvdata(wdd);
> +	int selector;
> +	int ret;
> +
> +	selector = da9063_wdt_timeout_to_sel(wdt->wdtdev.timeout);
> +	if (selector < 0) {
> +		dev_err(wdt->da9063->dev, "Timeout out of range (timeout=%d)\n",
> +				wdt->wdtdev.timeout);

We should have a rule that error messages which can only happen if there
is a bug in the code should never be permitted ;-).

Seriously, the watchdog subsystem enforces the range, so da9063_wdt_timeout_to_sel
can never return an error. Which really means that, in case you hit that unlikely
case that da9063_wdt_timeout_to_sel gets into the error condition anyway,
a WARN_ONCE and returning DA9063_TWDSCALE_MAX there would make much more sense
than all those repeated error checks.

I come to understand that you like error checks, but please keep in mind that all
you accomplish is to increase the size of the Linux kernel with no other benefit.

> +		return selector;
> +	}
> +
> +	mutex_lock(&wdt->lock);

Wonder if this came up with this driver, or with another one.
I don't see a value in all those locks. The watchdog subsystem
already provides locking.

> +	ret = _da9063_wdt_set_timeout(wdt->da9063, selector);
> +	if (ret) {
> +		dev_err(wdt->da9063->dev, "Watchdog failed to start (err = %d)\n",
> +				ret);
> +	}

Single statement does not need { }

> +	mutex_unlock(&wdt->lock);
> +
> +	return ret;
> +}
> +
> +static int da9063_wdt_stop(struct watchdog_device *wdd)
> +{
> +	struct da9063_watchdog *wdt = watchdog_get_drvdata(wdd);
> +	int ret;
> +
> +	mutex_lock(&wdt->lock);
> +	ret = regmap_update_bits(wdt->da9063->regmap, DA9063_REG_CONTROL_D,
> +			DA9063_TWDSCALE_MASK,
> +			DA9063_TWDSCALE_DISABLE);
> +	if (ret)
> +		dev_alert(wdt->da9063->dev, "Watchdog failed to stop (err = %d)\n",
> +				ret);
> +	mutex_unlock(&wdt->lock);
> +
> +	return ret;
> +}
> +
> +static int da9063_wdt_ping(struct watchdog_device *wdd)
> +{
> +	struct da9063_watchdog *wdt = watchdog_get_drvdata(wdd);
> +	int ret;
> +
> +	mutex_lock(&wdt->lock);
> +	ret = regmap_write(wdt->da9063->regmap, DA9063_REG_CONTROL_F,
> +			DA9063_WATCHDOG);
> +	if (ret)
> +		dev_alert(wdt->da9063->dev, "Failed to ping the watchdog (err = %d)\n",
> +				ret);
> +	mutex_unlock(&wdt->lock);
> +
> +	return ret;
> +}
> +
> +static int da9063_wdt_set_timeout(struct watchdog_device *wdd,
> +		unsigned int timeout)
> +{
> +	struct da9063_watchdog *wdt = watchdog_get_drvdata(wdd);
> +	int selector;
> +	int ret;
> +
> +	selector = da9063_wdt_timeout_to_sel(timeout);
> +	if (selector < 0) {
> +		dev_err(wdt->da9063->dev,
> +				"Unsupported watchdog timeout, should be between 2 and 131\n");
> +		return -EINVAL;
> +	}
> +
> +	mutex_lock(&wdt->lock);
> +
> +	ret = _da9063_wdt_set_timeout(wdt->da9063, selector);
> +	if (ret)
> +		dev_err(wdt->da9063->dev, "Failed to set watchdog timeout (err = %d)\n",
> +				ret);
> +	else
> +		wdd->timeout = wdt_timeout[selector];
> +
> +	mutex_unlock(&wdt->lock);

The code here and in wdt_start is almost identical. Can you move it into
a single helper function, to be called with the timeout in seconds as
parameter ?

> +
> +	return ret;
> +}
> +
> +static const struct watchdog_info da9063_watchdog_info = {
> +	.options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING,
> +	.identity = "DA9063 Watchdog",
> +};
> +
> +static const struct watchdog_ops da9063_watchdog_ops = {
> +	.owner = THIS_MODULE,
> +	.start = da9063_wdt_start,
> +	.stop = da9063_wdt_stop,
> +	.ping = da9063_wdt_ping,
> +	.set_timeout = da9063_wdt_set_timeout,
> +};
> +
> +static int da9063_wdt_probe(struct platform_device *pdev)
> +{
> +	int ret;
> +	struct da9063 *da9063;
> +	struct da9063_watchdog *wdt;
> +
> +	if (!pdev->dev.parent)
> +		return -EINVAL;
> +
> +	da9063 = dev_get_drvdata(pdev->dev.parent);
> +	if (!da9063)
> +		return -EINVAL;
> +
	-ENODEV in both cases, please.

> +	wdt = devm_kzalloc(&pdev->dev, sizeof(*wdt), GFP_KERNEL);
> +	if (!wdt)
> +		return -ENOMEM;
> +
> +	wdt->da9063 = da9063;
> +	mutex_init(&wdt->lock);
> +
> +	wdt->wdtdev.info = &da9063_watchdog_info;
> +	wdt->wdtdev.ops = &da9063_watchdog_ops;
> +	wdt->wdtdev.min_timeout = DA9063_WDT_MIN_TIMEOUT;
> +	wdt->wdtdev.max_timeout = DA9063_WDT_MAX_TIMEOUT;
> +	wdt->wdtdev.timeout = DA9063_WDG_TIMEOUT;
> +
> +	wdt->wdtdev.status = WATCHDOG_NOWAYOUT_INIT_STATUS;
> +
> +	watchdog_set_drvdata(&wdt->wdtdev, wdt);
> +	dev_set_drvdata(&pdev->dev, wdt);
> +
> +	ret = watchdog_register_device(&wdt->wdtdev);
> +	if (ret) {
> +		dev_err(da9063->dev, "da9063-watchdog registration failed (err = %d)",

da9063-watchdog is redundant with dev_err.

> +				ret);
> +		return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static int da9063_wdt_remove(struct platform_device *pdev)
> +{
> +	struct da9063_watchdog *wdt = dev_get_drvdata(&pdev->dev);
> +
> +	watchdog_unregister_device(&wdt->wdtdev);
> +
> +	return 0;
> +}
> +
> +static struct platform_driver da9063_wdt_driver = {
> +	.probe = da9063_wdt_probe,
> +	.remove = da9063_wdt_remove,
> +	.driver = {
> +		.name = DA9063_DRVNAME_WATCHDOG,
> +	},
> +};
> +module_platform_driver(da9063_wdt_driver);
> +
> +MODULE_AUTHOR("Mariusz Wojtasik <mariusz.wojtasik@diasemi.com>");
> +MODULE_DESCRIPTION("Watchdog driver for Dialog DA9063");
> +MODULE_LICENSE("GPL");
> +MODULE_ALIAS("platform:" DA9063_DRVNAME_WATCHDOG);
>
Markus Pargmann Sept. 24, 2014, 9:10 a.m. UTC | #3
Hi,

sorry for the late reply, I was on vacation.

On Thu, Sep 11, 2014 at 09:30:35AM -0700, Guenter Roeck wrote:
> On Mon, Sep 08, 2014 at 03:58:33PM +0200, Markus Pargmann wrote:
> > From: Krystian Garbaciak <krystian.garbaciak@diasemi.com>
> > 
> > This driver supports the watchdog device inside the DA9063 PMIC.
> > 
> > Signed-off-by: Krystian Garbaciak <krystian.garbaciak@diasemi.com>
> > Signed-off-by: Philipp Zabel <p.zabel@pengutronix.de>
> > Signed-off-by: Markus Pargmann <mpa@pengutronix.de>
> > ---
> > 
> > Notes:
> >     Changes in v6:
> >      - Fix compile error
> >     
> >     Changes in v5:
> >      - Kconfig: Add note about the module name.
> >      - Change selector variables to int
> >      - Style fixes
> >      - use unsigned int to remove gcc verbose warning
> >      - Integrate _kick() and _disable() into the calling functions
> >     
> >     Changes in v4:
> >      - Fixed indentation
> >      - Fixed lock without unlock
> >      - Check for parent device and device driver data in probe(). If not present,
> >        this is an invalid prober initialization, return -EINVAL.
> >     
> >     Changes in v3:
> >      - Cleanup error handling for timeout selection and setting
> >      - Fix race condition due to late wdt drvdata setup
> >      - Remove static struct watchdog_device. It is not initialized in the probe
> >        function
> >      - Use WATCHDOG_NOWAYOUT_INIT_STATUS for the status
> >      - Remove 0 shift using DA9063_TWDSCALE_SHIFT
> > 
> >  drivers/watchdog/Kconfig      |   9 ++
> >  drivers/watchdog/Makefile     |   1 +
> >  drivers/watchdog/da9063_wdt.c | 222 ++++++++++++++++++++++++++++++++++++++++++
> >  3 files changed, 232 insertions(+)
> >  create mode 100644 drivers/watchdog/da9063_wdt.c
> > 
> > diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig
> > index f57312fced80..669de63a7a48 100644
> > --- a/drivers/watchdog/Kconfig
> > +++ b/drivers/watchdog/Kconfig
> > @@ -87,6 +87,15 @@ config DA9055_WATCHDOG
> >  	  This driver can also be built as a module.  If so, the module
> >  	  will be called da9055_wdt.
> >  
> > +config DA9063_WATCHDOG
> > +	tristate "Dialog DA9063 Watchdog"
> > +	depends on MFD_DA9063
> > +	select WATCHDOG_CORE
> > +	help
> > +	  Support for the watchdog in the DA9063 PMIC.
> > +
> > +	  This driver can be built as a module. The module name is da9063_wdt.
> > +
> >  config GPIO_WATCHDOG
> >  	tristate "Watchdog device controlled through GPIO-line"
> >  	depends on OF_GPIO
> > diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile
> > index 468c3204c3b1..6c467a9821fe 100644
> > --- a/drivers/watchdog/Makefile
> > +++ b/drivers/watchdog/Makefile
> > @@ -173,6 +173,7 @@ obj-$(CONFIG_XEN_WDT) += xen_wdt.o
> >  # Architecture Independent
> >  obj-$(CONFIG_DA9052_WATCHDOG) += da9052_wdt.o
> >  obj-$(CONFIG_DA9055_WATCHDOG) += da9055_wdt.o
> > +obj-$(CONFIG_DA9063_WATCHDOG) += da9063_wdt.o
> >  obj-$(CONFIG_GPIO_WATCHDOG)	+= gpio_wdt.o
> >  obj-$(CONFIG_WM831X_WATCHDOG) += wm831x_wdt.o
> >  obj-$(CONFIG_WM8350_WATCHDOG) += wm8350_wdt.o
> > diff --git a/drivers/watchdog/da9063_wdt.c b/drivers/watchdog/da9063_wdt.c
> > new file mode 100644
> > index 000000000000..88441bdd2d1b
> > --- /dev/null
> > +++ b/drivers/watchdog/da9063_wdt.c
> > @@ -0,0 +1,222 @@
> > +/*
> > + * Watchdog driver for DA9063 PMICs.
> > + *
> > + * Copyright(c) 2012 Dialog Semiconductor Ltd.
> > + *
> > + * Author: Mariusz Wojtasik <mariusz.wojtasik@diasemi.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.
> > + */
> > +
> > +#include <linux/kernel.h>
> > +#include <linux/module.h>
> > +#include <linux/watchdog.h>
> > +#include <linux/platform_device.h>
> > +#include <linux/uaccess.h>
> > +#include <linux/slab.h>
> > +#include <linux/delay.h>
> > +#include <linux/mfd/da9063/registers.h>
> > +#include <linux/mfd/da9063/core.h>
> > +#include <linux/regmap.h>
> > +
> > +/*
> > + * Watchdog selector to timeout in seconds.
> > + *   0: WDT disabled;
> > + *   others: timeout = 2048 ms * 2^(TWDSCALE-1).
> > + */
> > +static const int wdt_timeout[] = { 0, 2, 4, 8, 16, 32, 65, 131 };
> > +#define DA9063_TWDSCALE_DISABLE		0
> > +#define DA9063_TWDSCALE_MIN		1
> > +#define DA9063_TWDSCALE_MAX		(ARRAY_SIZE(wdt_timeout) - 1)
> > +#define DA9063_WDT_MIN_TIMEOUT		wdt_timeout[DA9063_TWDSCALE_MIN]
> > +#define DA9063_WDT_MAX_TIMEOUT		wdt_timeout[DA9063_TWDSCALE_MAX]
> > +#define DA9063_WDG_TIMEOUT		wdt_timeout[3]
> > +
> > +struct da9063_watchdog {
> > +	struct da9063 *da9063;
> > +	struct watchdog_device wdtdev;
> > +	struct mutex lock;
> 
> The watchdog subsystem already provides locking. I don't find a single case
> where this additional lock is needed. Can you give me one ?

Okay, I removed the lock.

> 
> > +};
> > +
> > +static int da9063_wdt_timeout_to_sel(int secs)
> > +{
> > +	unsigned int i;
> > +
> > +	for (i = DA9063_TWDSCALE_MIN; i <= DA9063_TWDSCALE_MAX; i++) {
> > +		if (wdt_timeout[i] >= secs)
> > +			return i;
> > +	}
> > +
> > +	return -EINVAL;
> > +}
> > +
> > +static int _da9063_wdt_set_timeout(struct da9063 *da9063, unsigned int regval)
> > +{
> > +	return regmap_update_bits(da9063->regmap, DA9063_REG_CONTROL_D,
> > +			DA9063_TWDSCALE_MASK, regval);
> 
> For hwmon I decided to start enforcing indentation rules, for the simple
> reason that everyone otherwise uses different second-line indentations.
> Lucky for you I am not the watchdog maintainer ;-)

As a lot of people seem to share your opinion about indentation I
configured my vim to align to the opening bracket now. So next version
will have aligned indentation ;-).
> 
> > +}
> > +
> > +static int da9063_wdt_start(struct watchdog_device *wdd)
> > +{
> > +	struct da9063_watchdog *wdt = watchdog_get_drvdata(wdd);
> > +	int selector;
> > +	int ret;
> > +
> > +	selector = da9063_wdt_timeout_to_sel(wdt->wdtdev.timeout);
> > +	if (selector < 0) {
> > +		dev_err(wdt->da9063->dev, "Timeout out of range (timeout=%d)\n",
> > +				wdt->wdtdev.timeout);
> > +		return selector;
> > +	}
> > +
> > +	mutex_lock(&wdt->lock);
> > +	ret = _da9063_wdt_set_timeout(wdt->da9063, selector);
> > +	if (ret) {
> > +		dev_err(wdt->da9063->dev, "Watchdog failed to start (err = %d)\n",
> > +				ret);
> > +	}
> > +	mutex_unlock(&wdt->lock);
> > +
> > +	return ret;
> > +}
> > +
> > +static int da9063_wdt_stop(struct watchdog_device *wdd)
> > +{
> > +	struct da9063_watchdog *wdt = watchdog_get_drvdata(wdd);
> > +	int ret;
> > +
> > +	mutex_lock(&wdt->lock);
> > +	ret = regmap_update_bits(wdt->da9063->regmap, DA9063_REG_CONTROL_D,
> > +			DA9063_TWDSCALE_MASK,
> > +			DA9063_TWDSCALE_DISABLE);
> > +	if (ret)
> > +		dev_alert(wdt->da9063->dev, "Watchdog failed to stop (err = %d)\n",
> > +				ret);
> 
> I am personally not in favor of dumping error mesasges onto the console if the
> error is also reported to user space. My logic is that if everyone would be
> doing that logging, the log would be all noise and be completely useless.

Yes, the userspace should handle error codes and report errors
appropriately. However as this is a quite serious error message, I would
prefer to have it as a kernel error message so that the reason for a
reset by watchdog is obvious and listed in the kernel log.

> 
> > +	mutex_unlock(&wdt->lock);
> > +
> > +	return ret;
> > +}
> > +
> > +static int da9063_wdt_ping(struct watchdog_device *wdd)
> > +{
> > +	struct da9063_watchdog *wdt = watchdog_get_drvdata(wdd);
> > +	int ret;
> > +
> > +	mutex_lock(&wdt->lock);
> > +	ret = regmap_write(wdt->da9063->regmap, DA9063_REG_CONTROL_F,
> > +			DA9063_WATCHDOG);
> > +	if (ret)
> > +		dev_alert(wdt->da9063->dev, "Failed to ping the watchdog (err = %d)\n",
> > +				ret);
> > +	mutex_unlock(&wdt->lock);
> > +
> > +	return ret;
> > +}
> > +
> > +static int da9063_wdt_set_timeout(struct watchdog_device *wdd,
> > +		unsigned int timeout)
> > +{
> > +	struct da9063_watchdog *wdt = watchdog_get_drvdata(wdd);
> > +	int selector;
> > +	int ret;
> > +
> > +	selector = da9063_wdt_timeout_to_sel(timeout);
> > +	if (selector < 0) {
> > +		dev_err(wdt->da9063->dev,
> > +				"Unsupported watchdog timeout, should be between 2 and 131\n");
> 
> Since the caller already performs a range check, I wonder if this repeated
> error check really provides additional value. Seems to me it would be much
> simpler to have da9063_wdt_timeout_to_sel always return a valid value and
> to drop all those additional range checks.

Okay, finally removed all these rangechecks.

> 
> > +		return -EINVAL;
> > +	}
> > +
> > +	mutex_lock(&wdt->lock);
> > +
> > +	ret = _da9063_wdt_set_timeout(wdt->da9063, selector);
> > +	if (ret)
> > +		dev_err(wdt->da9063->dev, "Failed to set watchdog timeout (err = %d)\n",
> > +				ret);
> > +	else
> > +		wdd->timeout = wdt_timeout[selector];
> > +
> > +	mutex_unlock(&wdt->lock);
> > +
> > +	return ret;
> > +}
> > +
> > +static const struct watchdog_info da9063_watchdog_info = {
> > +	.options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING,
> > +	.identity = "DA9063 Watchdog",
> > +};
> > +
> > +static const struct watchdog_ops da9063_watchdog_ops = {
> > +	.owner = THIS_MODULE,
> > +	.start = da9063_wdt_start,
> > +	.stop = da9063_wdt_stop,
> > +	.ping = da9063_wdt_ping,
> > +	.set_timeout = da9063_wdt_set_timeout,
> > +};
> > +
> > +static int da9063_wdt_probe(struct platform_device *pdev)
> > +{
> > +	int ret;
> > +	struct da9063 *da9063;
> > +	struct da9063_watchdog *wdt;
> > +
> > +	if (!pdev->dev.parent)
> > +		return -EINVAL;
> > +
> > +	da9063 = dev_get_drvdata(pdev->dev.parent);
> > +	if (!da9063)
> > +		return -EINVAL;
> > +
> 
> This is not really an invalid argument. -ENODEV seems to be more appropriate,
> given the context (presumably it means that there is no parent mfd device).

Yes, but ENODEV will not result in a probe error message because the
driver core assumes that the driver is not for this device. I would like
to have a probe error message so that developers immediately see that
something went wrong. This driver without a parent is a invalid setup,
so I chose EINVAL.
I prefer returning -EINVAL but as an alternative I could add an error
message here and return ENODEV.

> 
> > +	wdt = devm_kzalloc(&pdev->dev, sizeof(*wdt), GFP_KERNEL);
> > +	if (!wdt)
> > +		return -ENOMEM;
> > +
> > +	wdt->da9063 = da9063;
> > +	mutex_init(&wdt->lock);
> > +
> > +	wdt->wdtdev.info = &da9063_watchdog_info;
> > +	wdt->wdtdev.ops = &da9063_watchdog_ops;
> > +	wdt->wdtdev.min_timeout = DA9063_WDT_MIN_TIMEOUT;
> > +	wdt->wdtdev.max_timeout = DA9063_WDT_MAX_TIMEOUT;
> > +	wdt->wdtdev.timeout = DA9063_WDG_TIMEOUT;
> > +
> > +	wdt->wdtdev.status = WATCHDOG_NOWAYOUT_INIT_STATUS;
> > +
> > +	watchdog_set_drvdata(&wdt->wdtdev, wdt);
> > +	dev_set_drvdata(&pdev->dev, wdt);
> > +
> > +	ret = watchdog_register_device(&wdt->wdtdev);
> > +	if (ret) {
> > +		dev_err(da9063->dev, "da9063-watchdog registration failed (err = %d)",
> > +				ret);
> 
> "da9063-watchdog" is really redundant since dev_err already displays
> the device name.

Also this error message is redundant, as the driver probing code will
print an error message when it failed. So I removed the message
completely.

Thanks for your feedback.

Best regards,

Markus

> 
> > +		return ret;
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +static int da9063_wdt_remove(struct platform_device *pdev)
> > +{
> > +	struct da9063_watchdog *wdt = dev_get_drvdata(&pdev->dev);
> > +
> > +	watchdog_unregister_device(&wdt->wdtdev);
> > +
> > +	return 0;
> > +}
> > +
> > +static struct platform_driver da9063_wdt_driver = {
> > +	.probe = da9063_wdt_probe,
> > +	.remove = da9063_wdt_remove,
> > +	.driver = {
> > +		.name = DA9063_DRVNAME_WATCHDOG,
> > +	},
> > +};
> > +module_platform_driver(da9063_wdt_driver);
> > +
> > +MODULE_AUTHOR("Mariusz Wojtasik <mariusz.wojtasik@diasemi.com>");
> > +MODULE_DESCRIPTION("Watchdog driver for Dialog DA9063");
> > +MODULE_LICENSE("GPL");
> > +MODULE_ALIAS("platform:" DA9063_DRVNAME_WATCHDOG);
> > -- 
> > 2.1.0
> > 
>
Guenter Roeck Sept. 24, 2014, 1:30 p.m. UTC | #4
On 09/24/2014 02:10 AM, Markus Pargmann wrote:
> Hi,
>
> sorry for the late reply, I was on vacation.
>

No problem. Hope you had fun :-)

>>> +
>>> +static int da9063_wdt_probe(struct platform_device *pdev)
>>> +{
>>> +	int ret;
>>> +	struct da9063 *da9063;
>>> +	struct da9063_watchdog *wdt;
>>> +
>>> +	if (!pdev->dev.parent)
>>> +		return -EINVAL;
>>> +
>>> +	da9063 = dev_get_drvdata(pdev->dev.parent);
>>> +	if (!da9063)
>>> +		return -EINVAL;
>>> +
>>
>> This is not really an invalid argument. -ENODEV seems to be more appropriate,
>> given the context (presumably it means that there is no parent mfd device).
>
> Yes, but ENODEV will not result in a probe error message because the
> driver core assumes that the driver is not for this device. I would like
> to have a probe error message so that developers immediately see that
> something went wrong. This driver without a parent is a invalid setup,
> so I chose EINVAL.
> I prefer returning -EINVAL but as an alternative I could add an error
> message here and return ENODEV.
>

Good point. Given the context, one can argue that having no driver data _is_
an invalid argument (to the function), so I am fine with that.

Guenter
diff mbox

Patch

diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig
index f57312fced80..669de63a7a48 100644
--- a/drivers/watchdog/Kconfig
+++ b/drivers/watchdog/Kconfig
@@ -87,6 +87,15 @@  config DA9055_WATCHDOG
 	  This driver can also be built as a module.  If so, the module
 	  will be called da9055_wdt.
 
+config DA9063_WATCHDOG
+	tristate "Dialog DA9063 Watchdog"
+	depends on MFD_DA9063
+	select WATCHDOG_CORE
+	help
+	  Support for the watchdog in the DA9063 PMIC.
+
+	  This driver can be built as a module. The module name is da9063_wdt.
+
 config GPIO_WATCHDOG
 	tristate "Watchdog device controlled through GPIO-line"
 	depends on OF_GPIO
diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile
index 468c3204c3b1..6c467a9821fe 100644
--- a/drivers/watchdog/Makefile
+++ b/drivers/watchdog/Makefile
@@ -173,6 +173,7 @@  obj-$(CONFIG_XEN_WDT) += xen_wdt.o
 # Architecture Independent
 obj-$(CONFIG_DA9052_WATCHDOG) += da9052_wdt.o
 obj-$(CONFIG_DA9055_WATCHDOG) += da9055_wdt.o
+obj-$(CONFIG_DA9063_WATCHDOG) += da9063_wdt.o
 obj-$(CONFIG_GPIO_WATCHDOG)	+= gpio_wdt.o
 obj-$(CONFIG_WM831X_WATCHDOG) += wm831x_wdt.o
 obj-$(CONFIG_WM8350_WATCHDOG) += wm8350_wdt.o
diff --git a/drivers/watchdog/da9063_wdt.c b/drivers/watchdog/da9063_wdt.c
new file mode 100644
index 000000000000..88441bdd2d1b
--- /dev/null
+++ b/drivers/watchdog/da9063_wdt.c
@@ -0,0 +1,222 @@ 
+/*
+ * Watchdog driver for DA9063 PMICs.
+ *
+ * Copyright(c) 2012 Dialog Semiconductor Ltd.
+ *
+ * Author: Mariusz Wojtasik <mariusz.wojtasik@diasemi.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.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/watchdog.h>
+#include <linux/platform_device.h>
+#include <linux/uaccess.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/mfd/da9063/registers.h>
+#include <linux/mfd/da9063/core.h>
+#include <linux/regmap.h>
+
+/*
+ * Watchdog selector to timeout in seconds.
+ *   0: WDT disabled;
+ *   others: timeout = 2048 ms * 2^(TWDSCALE-1).
+ */
+static const int wdt_timeout[] = { 0, 2, 4, 8, 16, 32, 65, 131 };
+#define DA9063_TWDSCALE_DISABLE		0
+#define DA9063_TWDSCALE_MIN		1
+#define DA9063_TWDSCALE_MAX		(ARRAY_SIZE(wdt_timeout) - 1)
+#define DA9063_WDT_MIN_TIMEOUT		wdt_timeout[DA9063_TWDSCALE_MIN]
+#define DA9063_WDT_MAX_TIMEOUT		wdt_timeout[DA9063_TWDSCALE_MAX]
+#define DA9063_WDG_TIMEOUT		wdt_timeout[3]
+
+struct da9063_watchdog {
+	struct da9063 *da9063;
+	struct watchdog_device wdtdev;
+	struct mutex lock;
+};
+
+static int da9063_wdt_timeout_to_sel(int secs)
+{
+	unsigned int i;
+
+	for (i = DA9063_TWDSCALE_MIN; i <= DA9063_TWDSCALE_MAX; i++) {
+		if (wdt_timeout[i] >= secs)
+			return i;
+	}
+
+	return -EINVAL;
+}
+
+static int _da9063_wdt_set_timeout(struct da9063 *da9063, unsigned int regval)
+{
+	return regmap_update_bits(da9063->regmap, DA9063_REG_CONTROL_D,
+			DA9063_TWDSCALE_MASK, regval);
+}
+
+static int da9063_wdt_start(struct watchdog_device *wdd)
+{
+	struct da9063_watchdog *wdt = watchdog_get_drvdata(wdd);
+	int selector;
+	int ret;
+
+	selector = da9063_wdt_timeout_to_sel(wdt->wdtdev.timeout);
+	if (selector < 0) {
+		dev_err(wdt->da9063->dev, "Timeout out of range (timeout=%d)\n",
+				wdt->wdtdev.timeout);
+		return selector;
+	}
+
+	mutex_lock(&wdt->lock);
+	ret = _da9063_wdt_set_timeout(wdt->da9063, selector);
+	if (ret) {
+		dev_err(wdt->da9063->dev, "Watchdog failed to start (err = %d)\n",
+				ret);
+	}
+	mutex_unlock(&wdt->lock);
+
+	return ret;
+}
+
+static int da9063_wdt_stop(struct watchdog_device *wdd)
+{
+	struct da9063_watchdog *wdt = watchdog_get_drvdata(wdd);
+	int ret;
+
+	mutex_lock(&wdt->lock);
+	ret = regmap_update_bits(wdt->da9063->regmap, DA9063_REG_CONTROL_D,
+			DA9063_TWDSCALE_MASK,
+			DA9063_TWDSCALE_DISABLE);
+	if (ret)
+		dev_alert(wdt->da9063->dev, "Watchdog failed to stop (err = %d)\n",
+				ret);
+	mutex_unlock(&wdt->lock);
+
+	return ret;
+}
+
+static int da9063_wdt_ping(struct watchdog_device *wdd)
+{
+	struct da9063_watchdog *wdt = watchdog_get_drvdata(wdd);
+	int ret;
+
+	mutex_lock(&wdt->lock);
+	ret = regmap_write(wdt->da9063->regmap, DA9063_REG_CONTROL_F,
+			DA9063_WATCHDOG);
+	if (ret)
+		dev_alert(wdt->da9063->dev, "Failed to ping the watchdog (err = %d)\n",
+				ret);
+	mutex_unlock(&wdt->lock);
+
+	return ret;
+}
+
+static int da9063_wdt_set_timeout(struct watchdog_device *wdd,
+		unsigned int timeout)
+{
+	struct da9063_watchdog *wdt = watchdog_get_drvdata(wdd);
+	int selector;
+	int ret;
+
+	selector = da9063_wdt_timeout_to_sel(timeout);
+	if (selector < 0) {
+		dev_err(wdt->da9063->dev,
+				"Unsupported watchdog timeout, should be between 2 and 131\n");
+		return -EINVAL;
+	}
+
+	mutex_lock(&wdt->lock);
+
+	ret = _da9063_wdt_set_timeout(wdt->da9063, selector);
+	if (ret)
+		dev_err(wdt->da9063->dev, "Failed to set watchdog timeout (err = %d)\n",
+				ret);
+	else
+		wdd->timeout = wdt_timeout[selector];
+
+	mutex_unlock(&wdt->lock);
+
+	return ret;
+}
+
+static const struct watchdog_info da9063_watchdog_info = {
+	.options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING,
+	.identity = "DA9063 Watchdog",
+};
+
+static const struct watchdog_ops da9063_watchdog_ops = {
+	.owner = THIS_MODULE,
+	.start = da9063_wdt_start,
+	.stop = da9063_wdt_stop,
+	.ping = da9063_wdt_ping,
+	.set_timeout = da9063_wdt_set_timeout,
+};
+
+static int da9063_wdt_probe(struct platform_device *pdev)
+{
+	int ret;
+	struct da9063 *da9063;
+	struct da9063_watchdog *wdt;
+
+	if (!pdev->dev.parent)
+		return -EINVAL;
+
+	da9063 = dev_get_drvdata(pdev->dev.parent);
+	if (!da9063)
+		return -EINVAL;
+
+	wdt = devm_kzalloc(&pdev->dev, sizeof(*wdt), GFP_KERNEL);
+	if (!wdt)
+		return -ENOMEM;
+
+	wdt->da9063 = da9063;
+	mutex_init(&wdt->lock);
+
+	wdt->wdtdev.info = &da9063_watchdog_info;
+	wdt->wdtdev.ops = &da9063_watchdog_ops;
+	wdt->wdtdev.min_timeout = DA9063_WDT_MIN_TIMEOUT;
+	wdt->wdtdev.max_timeout = DA9063_WDT_MAX_TIMEOUT;
+	wdt->wdtdev.timeout = DA9063_WDG_TIMEOUT;
+
+	wdt->wdtdev.status = WATCHDOG_NOWAYOUT_INIT_STATUS;
+
+	watchdog_set_drvdata(&wdt->wdtdev, wdt);
+	dev_set_drvdata(&pdev->dev, wdt);
+
+	ret = watchdog_register_device(&wdt->wdtdev);
+	if (ret) {
+		dev_err(da9063->dev, "da9063-watchdog registration failed (err = %d)",
+				ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int da9063_wdt_remove(struct platform_device *pdev)
+{
+	struct da9063_watchdog *wdt = dev_get_drvdata(&pdev->dev);
+
+	watchdog_unregister_device(&wdt->wdtdev);
+
+	return 0;
+}
+
+static struct platform_driver da9063_wdt_driver = {
+	.probe = da9063_wdt_probe,
+	.remove = da9063_wdt_remove,
+	.driver = {
+		.name = DA9063_DRVNAME_WATCHDOG,
+	},
+};
+module_platform_driver(da9063_wdt_driver);
+
+MODULE_AUTHOR("Mariusz Wojtasik <mariusz.wojtasik@diasemi.com>");
+MODULE_DESCRIPTION("Watchdog driver for Dialog DA9063");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:" DA9063_DRVNAME_WATCHDOG);