diff mbox

[RFC,24/37] watchdog: renesas_wdt_gen2: Add Gen2 specific driver

Message ID 1516903391-30467-25-git-send-email-fabrizio.castro@bp.renesas.com (mailing list archive)
State New, archived
Headers show

Commit Message

Fabrizio Castro Jan. 25, 2018, 6:02 p.m. UTC
R-Car Gen2 (and RZ/G1) platforms need some tweaking for SMP and watchdog
to coexist nicely.
This new driver is based on top of Wolfram Sang's driver
(drivers/watchdog/renesas_wdt.c), and it contains the quirks necessary
for R-Car Gen2 and RZ/G1 to work properly and in harmony with the rest of
the system. In particular, the driver:
* expects the device clock to be ON all the time,
* "pauses" the watchdog operation during suspend, and
* "reassures" the SMP bringup function about the availability of the
  watchdog registers.

Signed-off-by: Fabrizio Castro <fabrizio.castro@bp.renesas.com>
Signed-off-by: Ramesh Shanmugasundaram <ramesh.shanmugasundaram@bp.renesas.com>
---
 drivers/watchdog/Kconfig            |  15 +-
 drivers/watchdog/Makefile           |   1 +
 drivers/watchdog/renesas_wdt_gen2.c | 270 ++++++++++++++++++++++++++++++++++++
 drivers/watchdog/renesas_wdt_gen2.h |  22 +++
 4 files changed, 306 insertions(+), 2 deletions(-)
 create mode 100644 drivers/watchdog/renesas_wdt_gen2.c
 create mode 100644 drivers/watchdog/renesas_wdt_gen2.h

Comments

Geert Uytterhoeven Jan. 26, 2018, 10:22 a.m. UTC | #1
Hi Fabrizio,

CC Wolfram

On Thu, Jan 25, 2018 at 7:02 PM, Fabrizio Castro
<fabrizio.castro@bp.renesas.com> wrote:
> R-Car Gen2 (and RZ/G1) platforms need some tweaking for SMP and watchdog
> to coexist nicely.
> This new driver is based on top of Wolfram Sang's driver
> (drivers/watchdog/renesas_wdt.c), and it contains the quirks necessary
> for R-Car Gen2 and RZ/G1 to work properly and in harmony with the rest of
> the system. In particular, the driver:

Thanks for your patch!

IMHO, having a unified driver would be better from a maintenance point of
view.

> * expects the device clock to be ON all the time,

That can be handled in the driver, instead of through a critical clock.

> * "pauses" the watchdog operation during suspend, and

Isn't that needed in the original driver, too?

> * "reassures" the SMP bringup function about the availability of the
>   watchdog registers.

Through shmobile_set_wdt_clock_status(), which I don't like very much...
Can this be handled differently?

> Signed-off-by: Fabrizio Castro <fabrizio.castro@bp.renesas.com>
> Signed-off-by: Ramesh Shanmugasundaram <ramesh.shanmugasundaram@bp.renesas.com>
> ---
>  drivers/watchdog/Kconfig            |  15 +-
>  drivers/watchdog/Makefile           |   1 +
>  drivers/watchdog/renesas_wdt_gen2.c | 270 ++++++++++++++++++++++++++++++++++++
>  drivers/watchdog/renesas_wdt_gen2.h |  22 +++
>  4 files changed, 306 insertions(+), 2 deletions(-)
>  create mode 100644 drivers/watchdog/renesas_wdt_gen2.c
>  create mode 100644 drivers/watchdog/renesas_wdt_gen2.h
>
> diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig
> index ca200d1..e580c72 100644
> --- a/drivers/watchdog/Kconfig
> +++ b/drivers/watchdog/Kconfig
> @@ -725,12 +725,23 @@ config ATLAS7_WATCHDOG
>           module will be called atlas7_wdt.
>
>  config RENESAS_WDT
> -       tristate "Renesas WDT Watchdog"
> +       tristate "Renesas R-Car Gen3 WDT Watchdog"
>         depends on ARCH_RENESAS || COMPILE_TEST
>         select WATCHDOG_CORE
>         help
>           This driver adds watchdog support for the integrated watchdogs in the
> -         Renesas R-Car and other SH-Mobile SoCs (usually named RWDT or SWDT).
> +         Renesas R-Car Gen3 devices.
> +
> +config RENESAS_WDT_GEN2
> +       tristate "Renesas R-Car Gen2 and RZ/G1 WDT Watchdog"
> +       depends on ARCH_RENESAS || COMPILE_TEST

Note that compile-testing will fail if ARCH_RENESAS=n, due to the reference
to shmobile_set_wdt_clock_status().

> +       select WATCHDOG_CORE
> +       help
> +         This driver adds watchdog support for the integrated watchdogs in the
> +         Renesas R-Car Gen2 and RZ/G1 devices.
> +
> +         To compile this driver as a module, choose M here: the
> +         module will be called renesas_wdt_gen2.
>
>  config RENESAS_RZAWDT
>         tristate "Renesas RZ/A WDT Watchdog"
> diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile
> index 715a210..57ab810 100644
> --- a/drivers/watchdog/Makefile
> +++ b/drivers/watchdog/Makefile
> @@ -83,6 +83,7 @@ obj-$(CONFIG_LPC18XX_WATCHDOG) += lpc18xx_wdt.o
>  obj-$(CONFIG_BCM7038_WDT) += bcm7038_wdt.o
>  obj-$(CONFIG_ATLAS7_WATCHDOG) += atlas7_wdt.o
>  obj-$(CONFIG_RENESAS_WDT) += renesas_wdt.o
> +obj-$(CONFIG_RENESAS_WDT_GEN2) += renesas_wdt_gen2.o
>  obj-$(CONFIG_RENESAS_RZAWDT) += rza_wdt.o
>  obj-$(CONFIG_ASPEED_WATCHDOG) += aspeed_wdt.o
>  obj-$(CONFIG_ZX2967_WATCHDOG) += zx2967_wdt.o
> diff --git a/drivers/watchdog/renesas_wdt_gen2.c b/drivers/watchdog/renesas_wdt_gen2.c
> new file mode 100644
> index 0000000..c841636
> --- /dev/null
> +++ b/drivers/watchdog/renesas_wdt_gen2.c
> @@ -0,0 +1,270 @@
> +/*
> + * Watchdog driver for Renesas WDT watchdog
> + *
> + * Copyright (C) 2015-17 Wolfram Sang, Sang Engineering <wsa@sang-engineering.com>
> + * Copyright (C) 2018    Renesas Electronics Corporation
> + *
> + * 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/bitops.h>
> +#include <linux/clk.h>
> +#include <linux/io.h>
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/platform_device.h>
> +#include <linux/pm_runtime.h>
> +#include <linux/watchdog.h>
> +#include "renesas_wdt_gen2.h"
> +
> +#define RWTCNT                 0
> +#define RWTCSRA                        4
> +#define RWTCSRA_WOVF           BIT(4)
> +#define RWTCSRA_WRFLG          BIT(5)
> +#define RWTCSRA_TME            BIT(7)
> +#define RWTCSRB                        8
> +
> +#define RWDT_DEFAULT_TIMEOUT   60U
> +
> +/*
> + * In probe, clk_rate is checked to be not more than 16 bit * biggest clock
> + * divider (12 bits). d is only a factor to fully utilize the WDT counter and
> + * will not exceed its 16 bits. Thus, no overflow, we stay below 32 bits.
> + */
> +#define MUL_BY_CLKS_PER_SEC(p, d) \
> +       DIV_ROUND_UP((d) * (p)->clk_rate, clk_divs[(p)->cks])
> +
> +/* d is 16 bit, clk_divs 12 bit -> no 32 bit overflow */
> +#define DIV_BY_CLKS_PER_SEC(p, d) ((d) * clk_divs[(p)->cks] / (p)->clk_rate)
> +
> +static const unsigned int clk_divs[] = { 1, 4, 16, 32, 64, 128, 1024, 4096 };
> +
> +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) ")");
> +
> +struct rwdt_priv {
> +       void __iomem *base;
> +       struct watchdog_device wdev;
> +       unsigned long clk_rate;
> +       bool enabled;
> +       u8 cks;
> +};
> +
> +static void rwdt_write(struct rwdt_priv *priv, u32 val, unsigned int reg)
> +{
> +       if (reg == RWTCNT)
> +               val |= 0x5a5a0000;
> +       else
> +               val |= 0xa5a5a500;
> +
> +       writel_relaxed(val, priv->base + reg);
> +}
> +
> +static int rwdt_init_timeout(struct watchdog_device *wdev)
> +{
> +       struct rwdt_priv *priv = watchdog_get_drvdata(wdev);
> +
> +       rwdt_write(priv, 65536 - MUL_BY_CLKS_PER_SEC(priv, wdev->timeout),
> +                  RWTCNT);
> +
> +       return 0;
> +}
> +
> +static int rwdt_start(struct watchdog_device *wdev)
> +{
> +       struct rwdt_priv *priv = watchdog_get_drvdata(wdev);
> +
> +       rwdt_write(priv, 0, RWTCSRB);
> +       rwdt_write(priv, priv->cks, RWTCSRA);
> +       rwdt_init_timeout(wdev);
> +
> +       while (readb_relaxed(priv->base + RWTCSRA) & RWTCSRA_WRFLG)
> +               cpu_relax();
> +
> +       rwdt_write(priv, priv->cks | RWTCSRA_TME, RWTCSRA);
> +
> +       return 0;
> +}
> +
> +static int rwdt_stop(struct watchdog_device *wdev)
> +{
> +       struct rwdt_priv *priv = watchdog_get_drvdata(wdev);
> +
> +       rwdt_write(priv, priv->cks, RWTCSRA);
> +
> +       return 0;
> +}
> +
> +static unsigned int rwdt_get_timeleft(struct watchdog_device *wdev)
> +{
> +       struct rwdt_priv *priv = watchdog_get_drvdata(wdev);
> +       u16 val = readw_relaxed(priv->base + RWTCNT);
> +
> +       return DIV_BY_CLKS_PER_SEC(priv, 65536 - val);
> +}
> +
> +static int rwdt_restart(struct watchdog_device *wdev, unsigned long action,
> +                       void *data)
> +{
> +       struct rwdt_priv *priv = watchdog_get_drvdata(wdev);
> +
> +       rwdt_write(priv, 0x00, RWTCSRB);
> +       rwdt_write(priv, 0x00, RWTCSRA);
> +       rwdt_write(priv, 0xffff, RWTCNT);
> +
> +       while (readb_relaxed(priv->base + RWTCSRA) & RWTCSRA_WRFLG)
> +               cpu_relax();
> +
> +       rwdt_write(priv, 0x80, RWTCSRA);
> +       return 0;
> +}
> +
> +static const struct watchdog_info rwdt_ident = {
> +       .options = WDIOF_MAGICCLOSE | WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT,
> +       .identity = "Renesas WDT Watchdog",
> +};
> +
> +static const struct watchdog_ops rwdt_ops = {
> +       .owner = THIS_MODULE,
> +       .start = rwdt_start,
> +       .stop = rwdt_stop,
> +       .ping = rwdt_init_timeout,
> +       .get_timeleft = rwdt_get_timeleft,
> +       .restart = rwdt_restart,
> +};
> +
> +static int rwdt_probe(struct platform_device *pdev)
> +{
> +       struct rwdt_priv *priv;
> +       struct resource *res;
> +       struct clk *clk;
> +       unsigned long clks_per_sec;
> +       int ret, i;
> +
> +       priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
> +       if (!priv)
> +               return -ENOMEM;
> +       res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> +       priv->base = devm_ioremap_resource(&pdev->dev, res);
> +       if (IS_ERR(priv->base))
> +               return PTR_ERR(priv->base);
> +
> +       clk = devm_clk_get(&pdev->dev, NULL);
> +       if (IS_ERR(clk))
> +               return PTR_ERR(clk);
> +
> +
> +       priv->clk_rate = clk_get_rate(clk);
> +
> +       if (!priv->clk_rate)
> +               return -ENOENT;
> +
> +       for (i = ARRAY_SIZE(clk_divs) - 1; i >= 0; i--) {
> +               clks_per_sec = priv->clk_rate / clk_divs[i];
> +               if (clks_per_sec && clks_per_sec < 65536) {
> +                       priv->cks = i;
> +                       break;
> +               }
> +       }
> +
> +       if (i < 0) {
> +               dev_err(&pdev->dev, "Can't find suitable clock divider\n");
> +               return -ERANGE;
> +       }
> +
> +       priv->wdev.info = &rwdt_ident,
> +       priv->wdev.ops = &rwdt_ops,
> +       priv->wdev.parent = &pdev->dev;
> +       priv->wdev.min_timeout = 1;
> +       priv->wdev.max_timeout = DIV_BY_CLKS_PER_SEC(priv, 65536);
> +       priv->wdev.timeout = min(priv->wdev.max_timeout, RWDT_DEFAULT_TIMEOUT);
> +
> +       platform_set_drvdata(pdev, priv);
> +       watchdog_set_drvdata(&priv->wdev, priv);
> +       watchdog_set_nowayout(&priv->wdev, nowayout);
> +
> +       ret = watchdog_init_timeout(&priv->wdev, 0, &pdev->dev);
> +       if (ret)
> +               dev_warn(&pdev->dev,
> +                        "Specified timeout value invalid, using default\n");
> +       shmobile_set_wdt_clock_status(RWDT_CLOCK_ON);
> +
> +       ret = watchdog_register_device(&priv->wdev);
> +       return ret;
> +}
> +
> +static int rwdt_remove(struct platform_device *pdev)
> +{
> +       struct rwdt_priv *priv = platform_get_drvdata(pdev);
> +
> +       watchdog_unregister_device(&priv->wdev);
> +
> +       return 0;
> +}
> +
> +#ifdef CONFIG_PM
> +static int rwdt_suspend(struct device *dev)
> +{
> +       struct platform_device *pdev;
> +       struct rwdt_priv *priv;
> +       u8 val;
> +
> +       pdev = to_platform_device(dev);
> +       priv = platform_get_drvdata(pdev);
> +       val = readb_relaxed(priv->base + RWTCSRA);
> +       if (val & RWTCSRA_TME) {
> +               priv->enabled = true;
> +               rwdt_write(priv, val & ~RWTCSRA_TME, RWTCSRA);
> +       } else {
> +               priv->enabled = false;
> +       }
> +       return 0;
> +}
> +
> +static int rwdt_resume(struct device *dev)
> +{
> +       struct platform_device *pdev;
> +       struct rwdt_priv *priv;
> +       u8 val;
> +
> +       pdev = to_platform_device(dev);
> +       priv = platform_get_drvdata(pdev);
> +       if (priv->enabled) {
> +               val = readb_relaxed(priv->base + RWTCSRA);
> +               rwdt_write(priv, val | RWTCSRA_TME, RWTCSRA);
> +       }
> +       return 0;
> +}
> +
> +static const struct dev_pm_ops rwdt_pm = {
> +       .suspend = rwdt_suspend,
> +       .resume = rwdt_resume,
> +};
> +#endif
> +
> +static const struct of_device_id rwdt_ids[] = {
> +       { .compatible = "renesas,rcar-gen2-wdt", },
> +       { /* sentinel */ }
> +};
> +MODULE_DEVICE_TABLE(of, rwdt_ids);
> +
> +static struct platform_driver rwdt_driver = {
> +       .driver = {
> +               .name = "renesas_wdt_gen2",
> +               .of_match_table = rwdt_ids,
> +#ifdef CONFIG_PM
> +               .pm = &rwdt_pm,
> +#endif
> +       },
> +       .probe = rwdt_probe,
> +       .remove = rwdt_remove,
> +};
> +module_platform_driver(rwdt_driver);
> +
> +MODULE_DESCRIPTION("Renesas R-Car Gen2 Watchdog Driver");
> +MODULE_LICENSE("GPL v2");
> +MODULE_AUTHOR("Fabrizio Castro <fabrizio.castro@bp.renesas.com>");
> diff --git a/drivers/watchdog/renesas_wdt_gen2.h b/drivers/watchdog/renesas_wdt_gen2.h
> new file mode 100644
> index 0000000..41b9e9c
> --- /dev/null
> +++ b/drivers/watchdog/renesas_wdt_gen2.h
> @@ -0,0 +1,22 @@
> +/*
> + * drivers/watchdog/renesas_wdt_gen2.h
> + *
> + * Copyright (C) 2018 Renesas Electronics Corporation
> + *
> + * R-Car Gen2 and RZ/G specific symbols
> + *
> + * 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 RENESAS_WDT_GEN2_H
> +#define RENESAS_WDT_GEN2_H
> +
> +#define RWDT_CLOCK_ON          0xdeadbeef
> +#define RWDT_CLOCK_OFF         0x00000000
> +
> +extern void shmobile_set_wdt_clock_status(unsigned long value);
> +
> +#endif /* RENESAS_WDT_GEN2_H */
> --
> 2.7.4
Guenter Roeck Jan. 26, 2018, 5:28 p.m. UTC | #2
On Thu, Jan 25, 2018 at 06:02:58PM +0000, Fabrizio Castro wrote:
> R-Car Gen2 (and RZ/G1) platforms need some tweaking for SMP and watchdog
> to coexist nicely.
> This new driver is based on top of Wolfram Sang's driver
> (drivers/watchdog/renesas_wdt.c), and it contains the quirks necessary
> for R-Car Gen2 and RZ/G1 to work properly and in harmony with the rest of
> the system. In particular, the driver:
> * expects the device clock to be ON all the time,
> * "pauses" the watchdog operation during suspend, and
> * "reassures" the SMP bringup function about the availability of the
>   watchdog registers.
> 
> Signed-off-by: Fabrizio Castro <fabrizio.castro@bp.renesas.com>
> Signed-off-by: Ramesh Shanmugasundaram <ramesh.shanmugasundaram@bp.renesas.com>
> ---
>  drivers/watchdog/Kconfig            |  15 +-
>  drivers/watchdog/Makefile           |   1 +
>  drivers/watchdog/renesas_wdt_gen2.c | 270 ++++++++++++++++++++++++++++++++++++
>  drivers/watchdog/renesas_wdt_gen2.h |  22 +++
>  4 files changed, 306 insertions(+), 2 deletions(-)
>  create mode 100644 drivers/watchdog/renesas_wdt_gen2.c
>  create mode 100644 drivers/watchdog/renesas_wdt_gen2.h
> 
> diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig
> index ca200d1..e580c72 100644
> --- a/drivers/watchdog/Kconfig
> +++ b/drivers/watchdog/Kconfig
> @@ -725,12 +725,23 @@ config ATLAS7_WATCHDOG
>  	  module will be called atlas7_wdt.
>  
>  config RENESAS_WDT
> -	tristate "Renesas WDT Watchdog"
> +	tristate "Renesas R-Car Gen3 WDT Watchdog"
>  	depends on ARCH_RENESAS || COMPILE_TEST
>  	select WATCHDOG_CORE
>  	help
>  	  This driver adds watchdog support for the integrated watchdogs in the
> -	  Renesas R-Car and other SH-Mobile SoCs (usually named RWDT or SWDT).
> +	  Renesas R-Car Gen3 devices.
> +
> +config RENESAS_WDT_GEN2
> +	tristate "Renesas R-Car Gen2 and RZ/G1 WDT Watchdog"
> +	depends on ARCH_RENESAS || COMPILE_TEST
> +	select WATCHDOG_CORE
> +	help
> +	  This driver adds watchdog support for the integrated watchdogs in the
> +	  Renesas R-Car Gen2 and RZ/G1 devices.
> +
> +	  To compile this driver as a module, choose M here: the
> +	  module will be called renesas_wdt_gen2.
>  
>  config RENESAS_RZAWDT
>  	tristate "Renesas RZ/A WDT Watchdog"
> diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile
> index 715a210..57ab810 100644
> --- a/drivers/watchdog/Makefile
> +++ b/drivers/watchdog/Makefile
> @@ -83,6 +83,7 @@ obj-$(CONFIG_LPC18XX_WATCHDOG) += lpc18xx_wdt.o
>  obj-$(CONFIG_BCM7038_WDT) += bcm7038_wdt.o
>  obj-$(CONFIG_ATLAS7_WATCHDOG) += atlas7_wdt.o
>  obj-$(CONFIG_RENESAS_WDT) += renesas_wdt.o
> +obj-$(CONFIG_RENESAS_WDT_GEN2) += renesas_wdt_gen2.o
>  obj-$(CONFIG_RENESAS_RZAWDT) += rza_wdt.o
>  obj-$(CONFIG_ASPEED_WATCHDOG) += aspeed_wdt.o
>  obj-$(CONFIG_ZX2967_WATCHDOG) += zx2967_wdt.o
> diff --git a/drivers/watchdog/renesas_wdt_gen2.c b/drivers/watchdog/renesas_wdt_gen2.c
> new file mode 100644
> index 0000000..c841636
> --- /dev/null
> +++ b/drivers/watchdog/renesas_wdt_gen2.c
> @@ -0,0 +1,270 @@
> +/*
> + * Watchdog driver for Renesas WDT watchdog
> + *
> + * Copyright (C) 2015-17 Wolfram Sang, Sang Engineering <wsa@sang-engineering.com>
> + * Copyright (C) 2018    Renesas Electronics Corporation
> + *
> + * 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.

Please use the SPDX identifier.

> + */
> +#include <linux/bitops.h>
> +#include <linux/clk.h>
> +#include <linux/io.h>
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/platform_device.h>
> +#include <linux/pm_runtime.h>
> +#include <linux/watchdog.h>
> +#include "renesas_wdt_gen2.h"
> +
> +#define RWTCNT			0
> +#define RWTCSRA			4
> +#define RWTCSRA_WOVF		BIT(4)
> +#define RWTCSRA_WRFLG		BIT(5)
> +#define RWTCSRA_TME		BIT(7)
> +#define RWTCSRB			8
> +
> +#define RWDT_DEFAULT_TIMEOUT	60U
> +
> +/*
> + * In probe, clk_rate is checked to be not more than 16 bit * biggest clock
> + * divider (12 bits). d is only a factor to fully utilize the WDT counter and
> + * will not exceed its 16 bits. Thus, no overflow, we stay below 32 bits.
> + */
> +#define MUL_BY_CLKS_PER_SEC(p, d) \
> +	DIV_ROUND_UP((d) * (p)->clk_rate, clk_divs[(p)->cks])
> +
> +/* d is 16 bit, clk_divs 12 bit -> no 32 bit overflow */
> +#define DIV_BY_CLKS_PER_SEC(p, d) ((d) * clk_divs[(p)->cks] / (p)->clk_rate)
> +
> +static const unsigned int clk_divs[] = { 1, 4, 16, 32, 64, 128, 1024, 4096 };
> +
> +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) ")");
> +
> +struct rwdt_priv {
> +	void __iomem *base;
> +	struct watchdog_device wdev;
> +	unsigned long clk_rate;
> +	bool enabled;
> +	u8 cks;
> +};
> +
> +static void rwdt_write(struct rwdt_priv *priv, u32 val, unsigned int reg)
> +{
> +	if (reg == RWTCNT)
> +		val |= 0x5a5a0000;
> +	else
> +		val |= 0xa5a5a500;
> +
> +	writel_relaxed(val, priv->base + reg);
> +}
> +
> +static int rwdt_init_timeout(struct watchdog_device *wdev)
> +{
> +	struct rwdt_priv *priv = watchdog_get_drvdata(wdev);
> +
> +	rwdt_write(priv, 65536 - MUL_BY_CLKS_PER_SEC(priv, wdev->timeout),
> +		   RWTCNT);
> +
> +	return 0;
> +}
> +
> +static int rwdt_start(struct watchdog_device *wdev)
> +{
> +	struct rwdt_priv *priv = watchdog_get_drvdata(wdev);
> +
> +	rwdt_write(priv, 0, RWTCSRB);
> +	rwdt_write(priv, priv->cks, RWTCSRA);
> +	rwdt_init_timeout(wdev);
> +
> +	while (readb_relaxed(priv->base + RWTCSRA) & RWTCSRA_WRFLG)
> +		cpu_relax();
> +
Can this get stuck ?

> +	rwdt_write(priv, priv->cks | RWTCSRA_TME, RWTCSRA);
> +
> +	return 0;
> +}
> +
> +static int rwdt_stop(struct watchdog_device *wdev)
> +{
> +	struct rwdt_priv *priv = watchdog_get_drvdata(wdev);
> +
> +	rwdt_write(priv, priv->cks, RWTCSRA);
> +
> +	return 0;
> +}
> +
> +static unsigned int rwdt_get_timeleft(struct watchdog_device *wdev)
> +{
> +	struct rwdt_priv *priv = watchdog_get_drvdata(wdev);
> +	u16 val = readw_relaxed(priv->base + RWTCNT);
> +
> +	return DIV_BY_CLKS_PER_SEC(priv, 65536 - val);
> +}
> +
> +static int rwdt_restart(struct watchdog_device *wdev, unsigned long action,
> +			void *data)
> +{
> +	struct rwdt_priv *priv = watchdog_get_drvdata(wdev);
> +
> +	rwdt_write(priv, 0x00, RWTCSRB);
> +	rwdt_write(priv, 0x00, RWTCSRA);
> +	rwdt_write(priv, 0xffff, RWTCNT);
> +
> +	while (readb_relaxed(priv->base + RWTCSRA) & RWTCSRA_WRFLG)
> +		cpu_relax();
> +
> +	rwdt_write(priv, 0x80, RWTCSRA);
> +	return 0;
> +}
> +
> +static const struct watchdog_info rwdt_ident = {
> +	.options = WDIOF_MAGICCLOSE | WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT,
> +	.identity = "Renesas WDT Watchdog",
> +};
> +
> +static const struct watchdog_ops rwdt_ops = {
> +	.owner = THIS_MODULE,
> +	.start = rwdt_start,
> +	.stop = rwdt_stop,
> +	.ping = rwdt_init_timeout,
> +	.get_timeleft = rwdt_get_timeleft,
> +	.restart = rwdt_restart,
> +};
> +
> +static int rwdt_probe(struct platform_device *pdev)
> +{
> +	struct rwdt_priv *priv;
> +	struct resource *res;
> +	struct clk *clk;
> +	unsigned long clks_per_sec;
> +	int ret, i;
> +
> +	priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
> +	if (!priv)
> +		return -ENOMEM;
> +	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> +	priv->base = devm_ioremap_resource(&pdev->dev, res);
> +	if (IS_ERR(priv->base))
> +		return PTR_ERR(priv->base);
> +
> +	clk = devm_clk_get(&pdev->dev, NULL);
> +	if (IS_ERR(clk))
> +		return PTR_ERR(clk);
> +
> +
> +	priv->clk_rate = clk_get_rate(clk);
> +
> +	if (!priv->clk_rate)
> +		return -ENOENT;
> +
Odd line spacing. Please no double empty lines, and please no
empty line after a function and before checking the return value.

ENOENT is an odd return value. No such file or directory ?

> +	for (i = ARRAY_SIZE(clk_divs) - 1; i >= 0; i--) {
> +		clks_per_sec = priv->clk_rate / clk_divs[i];
> +		if (clks_per_sec && clks_per_sec < 65536) {
> +			priv->cks = i;
> +			break;
> +		}
> +	}
> +
> +	if (i < 0) {
> +		dev_err(&pdev->dev, "Can't find suitable clock divider\n");
> +		return -ERANGE;

Another odd return value. Math result not presentable ?

> +	}
> +
> +	priv->wdev.info = &rwdt_ident,
> +	priv->wdev.ops = &rwdt_ops,
> +	priv->wdev.parent = &pdev->dev;
> +	priv->wdev.min_timeout = 1;
> +	priv->wdev.max_timeout = DIV_BY_CLKS_PER_SEC(priv, 65536);
> +	priv->wdev.timeout = min(priv->wdev.max_timeout, RWDT_DEFAULT_TIMEOUT);
> +
> +	platform_set_drvdata(pdev, priv);
> +	watchdog_set_drvdata(&priv->wdev, priv);
> +	watchdog_set_nowayout(&priv->wdev, nowayout);
> +
> +	ret = watchdog_init_timeout(&priv->wdev, 0, &pdev->dev);
> +	if (ret)
> +		dev_warn(&pdev->dev,
> +			 "Specified timeout value invalid, using default\n");
> +	shmobile_set_wdt_clock_status(RWDT_CLOCK_ON);
> +
> +	ret = watchdog_register_device(&priv->wdev);

Please consider using devm_watchdog_register_device().

> +	return ret;
> +}
> +
> +static int rwdt_remove(struct platform_device *pdev)
> +{
> +	struct rwdt_priv *priv = platform_get_drvdata(pdev);
> +
> +	watchdog_unregister_device(&priv->wdev);
> +
> +	return 0;
> +}
> +
> +#ifdef CONFIG_PM
> +static int rwdt_suspend(struct device *dev)
> +{
> +	struct platform_device *pdev;
> +	struct rwdt_priv *priv;
> +	u8 val;
> +
> +	pdev = to_platform_device(dev);
> +	priv = platform_get_drvdata(pdev);
> +	val = readb_relaxed(priv->base + RWTCSRA);
> +	if (val & RWTCSRA_TME) {
> +		priv->enabled = true;
> +		rwdt_write(priv, val & ~RWTCSRA_TME, RWTCSRA);
> +	} else {
> +		priv->enabled = false;
> +	}

This will require an explanation why you can't use watchdog_active()
here and in the resume function.

> +	return 0;
> +}
> +
> +static int rwdt_resume(struct device *dev)
> +{
> +	struct platform_device *pdev;
> +	struct rwdt_priv *priv;
> +	u8 val;
> +
> +	pdev = to_platform_device(dev);
> +	priv = platform_get_drvdata(pdev);
> +	if (priv->enabled) {
> +		val = readb_relaxed(priv->base + RWTCSRA);
> +		rwdt_write(priv, val | RWTCSRA_TME, RWTCSRA);
> +	}
> +	return 0;
> +}
> +
> +static const struct dev_pm_ops rwdt_pm = {
> +	.suspend = rwdt_suspend,
> +	.resume = rwdt_resume,

Any reason for not using helper functions such as SET_SYSTEM_SLEEP_PM_OPS()
or SIMPLE_DEV_PM_OPS() ?

> +};
> +#endif
> +
> +static const struct of_device_id rwdt_ids[] = {
> +	{ .compatible = "renesas,rcar-gen2-wdt", },
> +	{ /* sentinel */ }
> +};
> +MODULE_DEVICE_TABLE(of, rwdt_ids);
> +
> +static struct platform_driver rwdt_driver = {
> +	.driver = {
> +		.name = "renesas_wdt_gen2",
> +		.of_match_table = rwdt_ids,
> +#ifdef CONFIG_PM
> +		.pm = &rwdt_pm,
> +#endif
> +	},
> +	.probe = rwdt_probe,
> +	.remove = rwdt_remove,
> +};
> +module_platform_driver(rwdt_driver);
> +
> +MODULE_DESCRIPTION("Renesas R-Car Gen2 Watchdog Driver");
> +MODULE_LICENSE("GPL v2");
> +MODULE_AUTHOR("Fabrizio Castro <fabrizio.castro@bp.renesas.com>");
> diff --git a/drivers/watchdog/renesas_wdt_gen2.h b/drivers/watchdog/renesas_wdt_gen2.h
> new file mode 100644
> index 0000000..41b9e9c
> --- /dev/null
> +++ b/drivers/watchdog/renesas_wdt_gen2.h
> @@ -0,0 +1,22 @@
> +/*
> + * drivers/watchdog/renesas_wdt_gen2.h
> + *
> + * Copyright (C) 2018 Renesas Electronics Corporation
> + *
> + * R-Car Gen2 and RZ/G specific symbols
> + *
> + * 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 RENESAS_WDT_GEN2_H
> +#define RENESAS_WDT_GEN2_H
> +
> +#define RWDT_CLOCK_ON		0xdeadbeef
> +#define RWDT_CLOCK_OFF		0x00000000
> +
> +extern void shmobile_set_wdt_clock_status(unsigned long value);

Wrong file to declare this function. It is not defined in the watchdog driver.
Same for the defines.

> +
> +#endif /* RENESAS_WDT_GEN2_H */
> -- 
> 2.7.4
> 
> --
> To unsubscribe from this list: send the line "unsubscribe linux-watchdog" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
Fabrizio Castro Jan. 26, 2018, 6 p.m. UTC | #3
Hello Guenter,

thank you so much for your feedback.
This driver is similar to the R-Car Gen3 watchdog driver (drivers/watchdog/renesas_wdt.c), at this point in time there is a discussion in progress about merging the functionality specific to R-Car Gen2 into the R-Car Gen3 driver, but this decision is stalled until we agree on something else that has in turn an impact on the driver implementation.
I'll keep your comments in mind whatever we decide to do, but I am going to get back to you only after the decision has been made.

Thanks,
Fabrizio

> Subject: Re: [RFC 24/37] watchdog: renesas_wdt_gen2: Add Gen2 specific driver
>
> On Thu, Jan 25, 2018 at 06:02:58PM +0000, Fabrizio Castro wrote:
> > R-Car Gen2 (and RZ/G1) platforms need some tweaking for SMP and watchdog
> > to coexist nicely.
> > This new driver is based on top of Wolfram Sang's driver
> > (drivers/watchdog/renesas_wdt.c), and it contains the quirks necessary
> > for R-Car Gen2 and RZ/G1 to work properly and in harmony with the rest of
> > the system. In particular, the driver:
> > * expects the device clock to be ON all the time,
> > * "pauses" the watchdog operation during suspend, and
> > * "reassures" the SMP bringup function about the availability of the
> >   watchdog registers.
> >
> > Signed-off-by: Fabrizio Castro <fabrizio.castro@bp.renesas.com>
> > Signed-off-by: Ramesh Shanmugasundaram <ramesh.shanmugasundaram@bp.renesas.com>
> > ---
> >  drivers/watchdog/Kconfig            |  15 +-
> >  drivers/watchdog/Makefile           |   1 +
> >  drivers/watchdog/renesas_wdt_gen2.c | 270 ++++++++++++++++++++++++++++++++++++
> >  drivers/watchdog/renesas_wdt_gen2.h |  22 +++
> >  4 files changed, 306 insertions(+), 2 deletions(-)
> >  create mode 100644 drivers/watchdog/renesas_wdt_gen2.c
> >  create mode 100644 drivers/watchdog/renesas_wdt_gen2.h
> >
> > diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig
> > index ca200d1..e580c72 100644
> > --- a/drivers/watchdog/Kconfig
> > +++ b/drivers/watchdog/Kconfig
> > @@ -725,12 +725,23 @@ config ATLAS7_WATCHDOG
> >    module will be called atlas7_wdt.
> >
> >  config RENESAS_WDT
> > -tristate "Renesas WDT Watchdog"
> > +tristate "Renesas R-Car Gen3 WDT Watchdog"
> >  depends on ARCH_RENESAS || COMPILE_TEST
> >  select WATCHDOG_CORE
> >  help
> >    This driver adds watchdog support for the integrated watchdogs in the
> > -  Renesas R-Car and other SH-Mobile SoCs (usually named RWDT or SWDT).
> > +  Renesas R-Car Gen3 devices.
> > +
> > +config RENESAS_WDT_GEN2
> > +tristate "Renesas R-Car Gen2 and RZ/G1 WDT Watchdog"
> > +depends on ARCH_RENESAS || COMPILE_TEST
> > +select WATCHDOG_CORE
> > +help
> > +  This driver adds watchdog support for the integrated watchdogs in the
> > +  Renesas R-Car Gen2 and RZ/G1 devices.
> > +
> > +  To compile this driver as a module, choose M here: the
> > +  module will be called renesas_wdt_gen2.
> >
> >  config RENESAS_RZAWDT
> >  tristate "Renesas RZ/A WDT Watchdog"
> > diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile
> > index 715a210..57ab810 100644
> > --- a/drivers/watchdog/Makefile
> > +++ b/drivers/watchdog/Makefile
> > @@ -83,6 +83,7 @@ obj-$(CONFIG_LPC18XX_WATCHDOG) += lpc18xx_wdt.o
> >  obj-$(CONFIG_BCM7038_WDT) += bcm7038_wdt.o
> >  obj-$(CONFIG_ATLAS7_WATCHDOG) += atlas7_wdt.o
> >  obj-$(CONFIG_RENESAS_WDT) += renesas_wdt.o
> > +obj-$(CONFIG_RENESAS_WDT_GEN2) += renesas_wdt_gen2.o
> >  obj-$(CONFIG_RENESAS_RZAWDT) += rza_wdt.o
> >  obj-$(CONFIG_ASPEED_WATCHDOG) += aspeed_wdt.o
> >  obj-$(CONFIG_ZX2967_WATCHDOG) += zx2967_wdt.o
> > diff --git a/drivers/watchdog/renesas_wdt_gen2.c b/drivers/watchdog/renesas_wdt_gen2.c
> > new file mode 100644
> > index 0000000..c841636
> > --- /dev/null
> > +++ b/drivers/watchdog/renesas_wdt_gen2.c
> > @@ -0,0 +1,270 @@
> > +/*
> > + * Watchdog driver for Renesas WDT watchdog
> > + *
> > + * Copyright (C) 2015-17 Wolfram Sang, Sang Engineering <wsa@sang-engineering.com>
> > + * Copyright (C) 2018    Renesas Electronics Corporation
> > + *
> > + * 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.
>
> Please use the SPDX identifier.
>
> > + */
> > +#include <linux/bitops.h>
> > +#include <linux/clk.h>
> > +#include <linux/io.h>
> > +#include <linux/kernel.h>
> > +#include <linux/module.h>
> > +#include <linux/of.h>
> > +#include <linux/platform_device.h>
> > +#include <linux/pm_runtime.h>
> > +#include <linux/watchdog.h>
> > +#include "renesas_wdt_gen2.h"
> > +
> > +#define RWTCNT0
> > +#define RWTCSRA4
> > +#define RWTCSRA_WOVFBIT(4)
> > +#define RWTCSRA_WRFLGBIT(5)
> > +#define RWTCSRA_TMEBIT(7)
> > +#define RWTCSRB8
> > +
> > +#define RWDT_DEFAULT_TIMEOUT60U
> > +
> > +/*
> > + * In probe, clk_rate is checked to be not more than 16 bit * biggest clock
> > + * divider (12 bits). d is only a factor to fully utilize the WDT counter and
> > + * will not exceed its 16 bits. Thus, no overflow, we stay below 32 bits.
> > + */
> > +#define MUL_BY_CLKS_PER_SEC(p, d) \
> > +DIV_ROUND_UP((d) * (p)->clk_rate, clk_divs[(p)->cks])
> > +
> > +/* d is 16 bit, clk_divs 12 bit -> no 32 bit overflow */
> > +#define DIV_BY_CLKS_PER_SEC(p, d) ((d) * clk_divs[(p)->cks] / (p)->clk_rate)
> > +
> > +static const unsigned int clk_divs[] = { 1, 4, 16, 32, 64, 128, 1024, 4096 };
> > +
> > +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) ")");
> > +
> > +struct rwdt_priv {
> > +void __iomem *base;
> > +struct watchdog_device wdev;
> > +unsigned long clk_rate;
> > +bool enabled;
> > +u8 cks;
> > +};
> > +
> > +static void rwdt_write(struct rwdt_priv *priv, u32 val, unsigned int reg)
> > +{
> > +if (reg == RWTCNT)
> > +val |= 0x5a5a0000;
> > +else
> > +val |= 0xa5a5a500;
> > +
> > +writel_relaxed(val, priv->base + reg);
> > +}
> > +
> > +static int rwdt_init_timeout(struct watchdog_device *wdev)
> > +{
> > +struct rwdt_priv *priv = watchdog_get_drvdata(wdev);
> > +
> > +rwdt_write(priv, 65536 - MUL_BY_CLKS_PER_SEC(priv, wdev->timeout),
> > +   RWTCNT);
> > +
> > +return 0;
> > +}
> > +
> > +static int rwdt_start(struct watchdog_device *wdev)
> > +{
> > +struct rwdt_priv *priv = watchdog_get_drvdata(wdev);
> > +
> > +rwdt_write(priv, 0, RWTCSRB);
> > +rwdt_write(priv, priv->cks, RWTCSRA);
> > +rwdt_init_timeout(wdev);
> > +
> > +while (readb_relaxed(priv->base + RWTCSRA) & RWTCSRA_WRFLG)
> > +cpu_relax();
> > +
> Can this get stuck ?
>
> > +rwdt_write(priv, priv->cks | RWTCSRA_TME, RWTCSRA);
> > +
> > +return 0;
> > +}
> > +
> > +static int rwdt_stop(struct watchdog_device *wdev)
> > +{
> > +struct rwdt_priv *priv = watchdog_get_drvdata(wdev);
> > +
> > +rwdt_write(priv, priv->cks, RWTCSRA);
> > +
> > +return 0;
> > +}
> > +
> > +static unsigned int rwdt_get_timeleft(struct watchdog_device *wdev)
> > +{
> > +struct rwdt_priv *priv = watchdog_get_drvdata(wdev);
> > +u16 val = readw_relaxed(priv->base + RWTCNT);
> > +
> > +return DIV_BY_CLKS_PER_SEC(priv, 65536 - val);
> > +}
> > +
> > +static int rwdt_restart(struct watchdog_device *wdev, unsigned long action,
> > +void *data)
> > +{
> > +struct rwdt_priv *priv = watchdog_get_drvdata(wdev);
> > +
> > +rwdt_write(priv, 0x00, RWTCSRB);
> > +rwdt_write(priv, 0x00, RWTCSRA);
> > +rwdt_write(priv, 0xffff, RWTCNT);
> > +
> > +while (readb_relaxed(priv->base + RWTCSRA) & RWTCSRA_WRFLG)
> > +cpu_relax();
> > +
> > +rwdt_write(priv, 0x80, RWTCSRA);
> > +return 0;
> > +}
> > +
> > +static const struct watchdog_info rwdt_ident = {
> > +.options = WDIOF_MAGICCLOSE | WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT,
> > +.identity = "Renesas WDT Watchdog",
> > +};
> > +
> > +static const struct watchdog_ops rwdt_ops = {
> > +.owner = THIS_MODULE,
> > +.start = rwdt_start,
> > +.stop = rwdt_stop,
> > +.ping = rwdt_init_timeout,
> > +.get_timeleft = rwdt_get_timeleft,
> > +.restart = rwdt_restart,
> > +};
> > +
> > +static int rwdt_probe(struct platform_device *pdev)
> > +{
> > +struct rwdt_priv *priv;
> > +struct resource *res;
> > +struct clk *clk;
> > +unsigned long clks_per_sec;
> > +int ret, i;
> > +
> > +priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
> > +if (!priv)
> > +return -ENOMEM;
> > +res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> > +priv->base = devm_ioremap_resource(&pdev->dev, res);
> > +if (IS_ERR(priv->base))
> > +return PTR_ERR(priv->base);
> > +
> > +clk = devm_clk_get(&pdev->dev, NULL);
> > +if (IS_ERR(clk))
> > +return PTR_ERR(clk);
> > +
> > +
> > +priv->clk_rate = clk_get_rate(clk);
> > +
> > +if (!priv->clk_rate)
> > +return -ENOENT;
> > +
> Odd line spacing. Please no double empty lines, and please no
> empty line after a function and before checking the return value.
>
> ENOENT is an odd return value. No such file or directory ?
>
> > +for (i = ARRAY_SIZE(clk_divs) - 1; i >= 0; i--) {
> > +clks_per_sec = priv->clk_rate / clk_divs[i];
> > +if (clks_per_sec && clks_per_sec < 65536) {
> > +priv->cks = i;
> > +break;
> > +}
> > +}
> > +
> > +if (i < 0) {
> > +dev_err(&pdev->dev, "Can't find suitable clock divider\n");
> > +return -ERANGE;
>
> Another odd return value. Math result not presentable ?
>
> > +}
> > +
> > +priv->wdev.info = &rwdt_ident,
> > +priv->wdev.ops = &rwdt_ops,
> > +priv->wdev.parent = &pdev->dev;
> > +priv->wdev.min_timeout = 1;
> > +priv->wdev.max_timeout = DIV_BY_CLKS_PER_SEC(priv, 65536);
> > +priv->wdev.timeout = min(priv->wdev.max_timeout, RWDT_DEFAULT_TIMEOUT);
> > +
> > +platform_set_drvdata(pdev, priv);
> > +watchdog_set_drvdata(&priv->wdev, priv);
> > +watchdog_set_nowayout(&priv->wdev, nowayout);
> > +
> > +ret = watchdog_init_timeout(&priv->wdev, 0, &pdev->dev);
> > +if (ret)
> > +dev_warn(&pdev->dev,
> > + "Specified timeout value invalid, using default\n");
> > +shmobile_set_wdt_clock_status(RWDT_CLOCK_ON);
> > +
> > +ret = watchdog_register_device(&priv->wdev);
>
> Please consider using devm_watchdog_register_device().
>
> > +return ret;
> > +}
> > +
> > +static int rwdt_remove(struct platform_device *pdev)
> > +{
> > +struct rwdt_priv *priv = platform_get_drvdata(pdev);
> > +
> > +watchdog_unregister_device(&priv->wdev);
> > +
> > +return 0;
> > +}
> > +
> > +#ifdef CONFIG_PM
> > +static int rwdt_suspend(struct device *dev)
> > +{
> > +struct platform_device *pdev;
> > +struct rwdt_priv *priv;
> > +u8 val;
> > +
> > +pdev = to_platform_device(dev);
> > +priv = platform_get_drvdata(pdev);
> > +val = readb_relaxed(priv->base + RWTCSRA);
> > +if (val & RWTCSRA_TME) {
> > +priv->enabled = true;
> > +rwdt_write(priv, val & ~RWTCSRA_TME, RWTCSRA);
> > +} else {
> > +priv->enabled = false;
> > +}
>
> This will require an explanation why you can't use watchdog_active()
> here and in the resume function.
>
> > +return 0;
> > +}
> > +
> > +static int rwdt_resume(struct device *dev)
> > +{
> > +struct platform_device *pdev;
> > +struct rwdt_priv *priv;
> > +u8 val;
> > +
> > +pdev = to_platform_device(dev);
> > +priv = platform_get_drvdata(pdev);
> > +if (priv->enabled) {
> > +val = readb_relaxed(priv->base + RWTCSRA);
> > +rwdt_write(priv, val | RWTCSRA_TME, RWTCSRA);
> > +}
> > +return 0;
> > +}
> > +
> > +static const struct dev_pm_ops rwdt_pm = {
> > +.suspend = rwdt_suspend,
> > +.resume = rwdt_resume,
>
> Any reason for not using helper functions such as SET_SYSTEM_SLEEP_PM_OPS()
> or SIMPLE_DEV_PM_OPS() ?
>
> > +};
> > +#endif
> > +
> > +static const struct of_device_id rwdt_ids[] = {
> > +{ .compatible = "renesas,rcar-gen2-wdt", },
> > +{ /* sentinel */ }
> > +};
> > +MODULE_DEVICE_TABLE(of, rwdt_ids);
> > +
> > +static struct platform_driver rwdt_driver = {
> > +.driver = {
> > +.name = "renesas_wdt_gen2",
> > +.of_match_table = rwdt_ids,
> > +#ifdef CONFIG_PM
> > +.pm = &rwdt_pm,
> > +#endif
> > +},
> > +.probe = rwdt_probe,
> > +.remove = rwdt_remove,
> > +};
> > +module_platform_driver(rwdt_driver);
> > +
> > +MODULE_DESCRIPTION("Renesas R-Car Gen2 Watchdog Driver");
> > +MODULE_LICENSE("GPL v2");
> > +MODULE_AUTHOR("Fabrizio Castro <fabrizio.castro@bp.renesas.com>");
> > diff --git a/drivers/watchdog/renesas_wdt_gen2.h b/drivers/watchdog/renesas_wdt_gen2.h
> > new file mode 100644
> > index 0000000..41b9e9c
> > --- /dev/null
> > +++ b/drivers/watchdog/renesas_wdt_gen2.h
> > @@ -0,0 +1,22 @@
> > +/*
> > + * drivers/watchdog/renesas_wdt_gen2.h
> > + *
> > + * Copyright (C) 2018 Renesas Electronics Corporation
> > + *
> > + * R-Car Gen2 and RZ/G specific symbols
> > + *
> > + * 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 RENESAS_WDT_GEN2_H
> > +#define RENESAS_WDT_GEN2_H
> > +
> > +#define RWDT_CLOCK_ON0xdeadbeef
> > +#define RWDT_CLOCK_OFF0x00000000
> > +
> > +extern void shmobile_set_wdt_clock_status(unsigned long value);
>
> Wrong file to declare this function. It is not defined in the watchdog driver.
> Same for the defines.
>
> > +
> > +#endif /* RENESAS_WDT_GEN2_H */
> > --
> > 2.7.4
> >
> > --
> > To unsubscribe from this list: send the line "unsubscribe linux-watchdog" in
> > the body of a message to majordomo@vger.kernel.org
> > More majordomo info at  http://vger.kernel.org/majordomo-info.html



Renesas Electronics Europe Ltd, Dukes Meadow, Millboard Road, Bourne End, Buckinghamshire, SL8 5FH, UK. Registered in England & Wales under Registered No. 04586709.
diff mbox

Patch

diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig
index ca200d1..e580c72 100644
--- a/drivers/watchdog/Kconfig
+++ b/drivers/watchdog/Kconfig
@@ -725,12 +725,23 @@  config ATLAS7_WATCHDOG
 	  module will be called atlas7_wdt.
 
 config RENESAS_WDT
-	tristate "Renesas WDT Watchdog"
+	tristate "Renesas R-Car Gen3 WDT Watchdog"
 	depends on ARCH_RENESAS || COMPILE_TEST
 	select WATCHDOG_CORE
 	help
 	  This driver adds watchdog support for the integrated watchdogs in the
-	  Renesas R-Car and other SH-Mobile SoCs (usually named RWDT or SWDT).
+	  Renesas R-Car Gen3 devices.
+
+config RENESAS_WDT_GEN2
+	tristate "Renesas R-Car Gen2 and RZ/G1 WDT Watchdog"
+	depends on ARCH_RENESAS || COMPILE_TEST
+	select WATCHDOG_CORE
+	help
+	  This driver adds watchdog support for the integrated watchdogs in the
+	  Renesas R-Car Gen2 and RZ/G1 devices.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called renesas_wdt_gen2.
 
 config RENESAS_RZAWDT
 	tristate "Renesas RZ/A WDT Watchdog"
diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile
index 715a210..57ab810 100644
--- a/drivers/watchdog/Makefile
+++ b/drivers/watchdog/Makefile
@@ -83,6 +83,7 @@  obj-$(CONFIG_LPC18XX_WATCHDOG) += lpc18xx_wdt.o
 obj-$(CONFIG_BCM7038_WDT) += bcm7038_wdt.o
 obj-$(CONFIG_ATLAS7_WATCHDOG) += atlas7_wdt.o
 obj-$(CONFIG_RENESAS_WDT) += renesas_wdt.o
+obj-$(CONFIG_RENESAS_WDT_GEN2) += renesas_wdt_gen2.o
 obj-$(CONFIG_RENESAS_RZAWDT) += rza_wdt.o
 obj-$(CONFIG_ASPEED_WATCHDOG) += aspeed_wdt.o
 obj-$(CONFIG_ZX2967_WATCHDOG) += zx2967_wdt.o
diff --git a/drivers/watchdog/renesas_wdt_gen2.c b/drivers/watchdog/renesas_wdt_gen2.c
new file mode 100644
index 0000000..c841636
--- /dev/null
+++ b/drivers/watchdog/renesas_wdt_gen2.c
@@ -0,0 +1,270 @@ 
+/*
+ * Watchdog driver for Renesas WDT watchdog
+ *
+ * Copyright (C) 2015-17 Wolfram Sang, Sang Engineering <wsa@sang-engineering.com>
+ * Copyright (C) 2018    Renesas Electronics Corporation
+ *
+ * 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/bitops.h>
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/watchdog.h>
+#include "renesas_wdt_gen2.h"
+
+#define RWTCNT			0
+#define RWTCSRA			4
+#define RWTCSRA_WOVF		BIT(4)
+#define RWTCSRA_WRFLG		BIT(5)
+#define RWTCSRA_TME		BIT(7)
+#define RWTCSRB			8
+
+#define RWDT_DEFAULT_TIMEOUT	60U
+
+/*
+ * In probe, clk_rate is checked to be not more than 16 bit * biggest clock
+ * divider (12 bits). d is only a factor to fully utilize the WDT counter and
+ * will not exceed its 16 bits. Thus, no overflow, we stay below 32 bits.
+ */
+#define MUL_BY_CLKS_PER_SEC(p, d) \
+	DIV_ROUND_UP((d) * (p)->clk_rate, clk_divs[(p)->cks])
+
+/* d is 16 bit, clk_divs 12 bit -> no 32 bit overflow */
+#define DIV_BY_CLKS_PER_SEC(p, d) ((d) * clk_divs[(p)->cks] / (p)->clk_rate)
+
+static const unsigned int clk_divs[] = { 1, 4, 16, 32, 64, 128, 1024, 4096 };
+
+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) ")");
+
+struct rwdt_priv {
+	void __iomem *base;
+	struct watchdog_device wdev;
+	unsigned long clk_rate;
+	bool enabled;
+	u8 cks;
+};
+
+static void rwdt_write(struct rwdt_priv *priv, u32 val, unsigned int reg)
+{
+	if (reg == RWTCNT)
+		val |= 0x5a5a0000;
+	else
+		val |= 0xa5a5a500;
+
+	writel_relaxed(val, priv->base + reg);
+}
+
+static int rwdt_init_timeout(struct watchdog_device *wdev)
+{
+	struct rwdt_priv *priv = watchdog_get_drvdata(wdev);
+
+	rwdt_write(priv, 65536 - MUL_BY_CLKS_PER_SEC(priv, wdev->timeout),
+		   RWTCNT);
+
+	return 0;
+}
+
+static int rwdt_start(struct watchdog_device *wdev)
+{
+	struct rwdt_priv *priv = watchdog_get_drvdata(wdev);
+
+	rwdt_write(priv, 0, RWTCSRB);
+	rwdt_write(priv, priv->cks, RWTCSRA);
+	rwdt_init_timeout(wdev);
+
+	while (readb_relaxed(priv->base + RWTCSRA) & RWTCSRA_WRFLG)
+		cpu_relax();
+
+	rwdt_write(priv, priv->cks | RWTCSRA_TME, RWTCSRA);
+
+	return 0;
+}
+
+static int rwdt_stop(struct watchdog_device *wdev)
+{
+	struct rwdt_priv *priv = watchdog_get_drvdata(wdev);
+
+	rwdt_write(priv, priv->cks, RWTCSRA);
+
+	return 0;
+}
+
+static unsigned int rwdt_get_timeleft(struct watchdog_device *wdev)
+{
+	struct rwdt_priv *priv = watchdog_get_drvdata(wdev);
+	u16 val = readw_relaxed(priv->base + RWTCNT);
+
+	return DIV_BY_CLKS_PER_SEC(priv, 65536 - val);
+}
+
+static int rwdt_restart(struct watchdog_device *wdev, unsigned long action,
+			void *data)
+{
+	struct rwdt_priv *priv = watchdog_get_drvdata(wdev);
+
+	rwdt_write(priv, 0x00, RWTCSRB);
+	rwdt_write(priv, 0x00, RWTCSRA);
+	rwdt_write(priv, 0xffff, RWTCNT);
+
+	while (readb_relaxed(priv->base + RWTCSRA) & RWTCSRA_WRFLG)
+		cpu_relax();
+
+	rwdt_write(priv, 0x80, RWTCSRA);
+	return 0;
+}
+
+static const struct watchdog_info rwdt_ident = {
+	.options = WDIOF_MAGICCLOSE | WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT,
+	.identity = "Renesas WDT Watchdog",
+};
+
+static const struct watchdog_ops rwdt_ops = {
+	.owner = THIS_MODULE,
+	.start = rwdt_start,
+	.stop = rwdt_stop,
+	.ping = rwdt_init_timeout,
+	.get_timeleft = rwdt_get_timeleft,
+	.restart = rwdt_restart,
+};
+
+static int rwdt_probe(struct platform_device *pdev)
+{
+	struct rwdt_priv *priv;
+	struct resource *res;
+	struct clk *clk;
+	unsigned long clks_per_sec;
+	int ret, i;
+
+	priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	priv->base = devm_ioremap_resource(&pdev->dev, res);
+	if (IS_ERR(priv->base))
+		return PTR_ERR(priv->base);
+
+	clk = devm_clk_get(&pdev->dev, NULL);
+	if (IS_ERR(clk))
+		return PTR_ERR(clk);
+
+
+	priv->clk_rate = clk_get_rate(clk);
+
+	if (!priv->clk_rate)
+		return -ENOENT;
+
+	for (i = ARRAY_SIZE(clk_divs) - 1; i >= 0; i--) {
+		clks_per_sec = priv->clk_rate / clk_divs[i];
+		if (clks_per_sec && clks_per_sec < 65536) {
+			priv->cks = i;
+			break;
+		}
+	}
+
+	if (i < 0) {
+		dev_err(&pdev->dev, "Can't find suitable clock divider\n");
+		return -ERANGE;
+	}
+
+	priv->wdev.info = &rwdt_ident,
+	priv->wdev.ops = &rwdt_ops,
+	priv->wdev.parent = &pdev->dev;
+	priv->wdev.min_timeout = 1;
+	priv->wdev.max_timeout = DIV_BY_CLKS_PER_SEC(priv, 65536);
+	priv->wdev.timeout = min(priv->wdev.max_timeout, RWDT_DEFAULT_TIMEOUT);
+
+	platform_set_drvdata(pdev, priv);
+	watchdog_set_drvdata(&priv->wdev, priv);
+	watchdog_set_nowayout(&priv->wdev, nowayout);
+
+	ret = watchdog_init_timeout(&priv->wdev, 0, &pdev->dev);
+	if (ret)
+		dev_warn(&pdev->dev,
+			 "Specified timeout value invalid, using default\n");
+	shmobile_set_wdt_clock_status(RWDT_CLOCK_ON);
+
+	ret = watchdog_register_device(&priv->wdev);
+	return ret;
+}
+
+static int rwdt_remove(struct platform_device *pdev)
+{
+	struct rwdt_priv *priv = platform_get_drvdata(pdev);
+
+	watchdog_unregister_device(&priv->wdev);
+
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int rwdt_suspend(struct device *dev)
+{
+	struct platform_device *pdev;
+	struct rwdt_priv *priv;
+	u8 val;
+
+	pdev = to_platform_device(dev);
+	priv = platform_get_drvdata(pdev);
+	val = readb_relaxed(priv->base + RWTCSRA);
+	if (val & RWTCSRA_TME) {
+		priv->enabled = true;
+		rwdt_write(priv, val & ~RWTCSRA_TME, RWTCSRA);
+	} else {
+		priv->enabled = false;
+	}
+	return 0;
+}
+
+static int rwdt_resume(struct device *dev)
+{
+	struct platform_device *pdev;
+	struct rwdt_priv *priv;
+	u8 val;
+
+	pdev = to_platform_device(dev);
+	priv = platform_get_drvdata(pdev);
+	if (priv->enabled) {
+		val = readb_relaxed(priv->base + RWTCSRA);
+		rwdt_write(priv, val | RWTCSRA_TME, RWTCSRA);
+	}
+	return 0;
+}
+
+static const struct dev_pm_ops rwdt_pm = {
+	.suspend = rwdt_suspend,
+	.resume = rwdt_resume,
+};
+#endif
+
+static const struct of_device_id rwdt_ids[] = {
+	{ .compatible = "renesas,rcar-gen2-wdt", },
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, rwdt_ids);
+
+static struct platform_driver rwdt_driver = {
+	.driver = {
+		.name = "renesas_wdt_gen2",
+		.of_match_table = rwdt_ids,
+#ifdef CONFIG_PM
+		.pm = &rwdt_pm,
+#endif
+	},
+	.probe = rwdt_probe,
+	.remove = rwdt_remove,
+};
+module_platform_driver(rwdt_driver);
+
+MODULE_DESCRIPTION("Renesas R-Car Gen2 Watchdog Driver");
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Fabrizio Castro <fabrizio.castro@bp.renesas.com>");
diff --git a/drivers/watchdog/renesas_wdt_gen2.h b/drivers/watchdog/renesas_wdt_gen2.h
new file mode 100644
index 0000000..41b9e9c
--- /dev/null
+++ b/drivers/watchdog/renesas_wdt_gen2.h
@@ -0,0 +1,22 @@ 
+/*
+ * drivers/watchdog/renesas_wdt_gen2.h
+ *
+ * Copyright (C) 2018 Renesas Electronics Corporation
+ *
+ * R-Car Gen2 and RZ/G specific symbols
+ *
+ * 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 RENESAS_WDT_GEN2_H
+#define RENESAS_WDT_GEN2_H
+
+#define RWDT_CLOCK_ON		0xdeadbeef
+#define RWDT_CLOCK_OFF		0x00000000
+
+extern void shmobile_set_wdt_clock_status(unsigned long value);
+
+#endif /* RENESAS_WDT_GEN2_H */