Re: [Linux-stm32] [PATCH 2/3] watchdog: stm32_iwdg: Add pretimeout support

From: Antonio Borneo
Date: Tue May 16 2023 - 11:40:55 EST


On Thu, 2023-05-11 at 02:19 +0200, Marek Vasut wrote:
> The STM32MP15xx IWDG adds registers which permit this IP to generate
> pretimeout interrupt. This interrupt can also be used to wake the CPU
> from suspend. Implement support for generating this interrupt and let
> userspace configure the pretimeout. In case the pretimeout is not
> configured by user, set pretimeout to half of the WDT timeout cycle.
>
> Signed-off-by: Marek Vasut <marex@xxxxxxx>
> ---
> Cc: Alexandre Torgue <alexandre.torgue@xxxxxxxxxxx>
> Cc: Guenter Roeck <linux@xxxxxxxxxxxx>
> Cc: Krzysztof Kozlowski <krzysztof.kozlowski+dt@xxxxxxxxxx>
> Cc: Marc Zyngier <maz@xxxxxxxxxx>
> Cc: Maxime Coquelin <mcoquelin.stm32@xxxxxxxxx>
> Cc: Richard Cochran <richardcochran@xxxxxxxxx>
> Cc: Rob Herring <robh+dt@xxxxxxxxxx>
> Cc: Thomas Gleixner <tglx@xxxxxxxxxxxxx>
> Cc: Wim Van Sebroeck <wim@xxxxxxxxxxxxxxxxxx>
> Cc: devicetree@xxxxxxxxxxxxxxx
> Cc: linux-arm-kernel@xxxxxxxxxxxxxxxxxxx
> Cc: linux-kernel@xxxxxxxxxxxxxxx
> Cc: linux-stm32@xxxxxxxxxxxxxxxxxxxxxxxxxxxx
> Cc: linux-watchdog@xxxxxxxxxxxxxxx
> ---
>  drivers/watchdog/stm32_iwdg.c | 93 ++++++++++++++++++++++++++++++++++-
>  1 file changed, 92 insertions(+), 1 deletion(-)
>
> diff --git a/drivers/watchdog/stm32_iwdg.c b/drivers/watchdog/stm32_iwdg.c
> index 570a71509d2a9..098ce83a3eb88 100644
> --- a/drivers/watchdog/stm32_iwdg.c
> +++ b/drivers/watchdog/stm32_iwdg.c
> @@ -19,6 +19,7 @@
>  #include <linux/of.h>
>  #include <linux/of_device.h>
>  #include <linux/platform_device.h>
> +#include <linux/pm_wakeirq.h>
>  #include <linux/watchdog.h>
>  
>  /* IWDG registers */
> @@ -27,6 +28,7 @@
>  #define IWDG_RLR       0x08 /* ReLoad Register */
>  #define IWDG_SR                0x0C /* Status Register */
>  #define IWDG_WINR      0x10 /* Windows Register */
> +#define IWDG_EWCR      0x14 /* Early Wake-up Register */
>  
>  /* IWDG_KR register bit mask */
>  #define KR_KEY_RELOAD  0xAAAA /* reload counter enable */
> @@ -46,22 +48,29 @@
>  #define SR_PVU BIT(0) /* Watchdog prescaler value update */
>  #define SR_RVU BIT(1) /* Watchdog counter reload value update */
>  
> +#define EWCR_EWIT      GENMASK(11, 0) /* Watchdog counter window value */
> +#define EWCR_EWIC      BIT(14) /* Watchdog early interrupt acknowledge */
> +#define EWCR_EWIE      BIT(15) /* Watchdog early interrupt enable */
> +
>  /* set timeout to 100000 us */
>  #define TIMEOUT_US     100000
>  #define SLEEP_US       1000
>  
>  struct stm32_iwdg_data {
>         bool has_pclk;
> +       bool has_early_wakeup;
>         u32 max_prescaler;
>  };
>  
>  static const struct stm32_iwdg_data stm32_iwdg_data = {
>         .has_pclk = false,
> +       .has_early_wakeup = false,
>         .max_prescaler = 256,
>  };
>  
>  static const struct stm32_iwdg_data stm32mp1_iwdg_data = {
>         .has_pclk = true,
> +       .has_early_wakeup = true,
>         .max_prescaler = 1024,
>  };
>  
> @@ -87,13 +96,17 @@ static inline void reg_write(void __iomem *base, u32 reg, u32 val)
>  static int stm32_iwdg_start(struct watchdog_device *wdd)
>  {
>         struct stm32_iwdg *wdt = watchdog_get_drvdata(wdd);
> -       u32 tout, presc, iwdg_rlr, iwdg_pr, iwdg_sr;
> +       u32 tout, ptot, presc, iwdg_rlr, iwdg_ewcr, iwdg_pr, iwdg_sr;
>         int ret;
>  
>         dev_dbg(wdd->parent, "%s\n", __func__);
>  
> +       if (!wdd->pretimeout)
> +               wdd->pretimeout = wdd->timeout / 2;
> +
>         tout = clamp_t(unsigned int, wdd->timeout,
>                        wdd->min_timeout, wdd->max_hw_heartbeat_ms / 1000);
> +       ptot = clamp_t(unsigned int, wdd->pretimeout, wdd->min_timeout, tout);
>  
>         presc = DIV_ROUND_UP(tout * wdt->rate, RLR_MAX + 1);
>  
> @@ -101,6 +114,7 @@ static int stm32_iwdg_start(struct watchdog_device *wdd)
>         presc = roundup_pow_of_two(presc);
>         iwdg_pr = presc <= 1 << PR_SHIFT ? 0 : ilog2(presc) - PR_SHIFT;
>         iwdg_rlr = ((tout * wdt->rate) / presc) - 1;
> +       iwdg_ewcr = ((ptot * wdt->rate) / presc) - 1;
>  
>         /* enable write access */
>         reg_write(wdt->regs, IWDG_KR, KR_KEY_EWA);
> @@ -108,6 +122,8 @@ static int stm32_iwdg_start(struct watchdog_device *wdd)
>         /* set prescaler & reload registers */
>         reg_write(wdt->regs, IWDG_PR, iwdg_pr);
>         reg_write(wdt->regs, IWDG_RLR, iwdg_rlr);
> +       if (wdt->data->has_early_wakeup)
> +               reg_write(wdt->regs, IWDG_EWCR, iwdg_ewcr | EWCR_EWIE);
>         reg_write(wdt->regs, IWDG_KR, KR_KEY_ENABLE);
>  
>         /* wait for the registers to be updated (max 100ms) */
> @@ -150,6 +166,34 @@ static int stm32_iwdg_set_timeout(struct watchdog_device *wdd,
>         return 0;
>  }
>  
> +static int stm32_iwdg_set_pretimeout(struct watchdog_device *wdd,
> +                                    unsigned int pretimeout)
> +{
> +       dev_dbg(wdd->parent, "%s pretimeout: %d sec\n", __func__, pretimeout);
> +
> +       wdd->pretimeout = pretimeout;
> +
> +       if (watchdog_active(wdd))
> +               return stm32_iwdg_start(wdd);
> +
> +       return 0;
> +}
> +
> +static irqreturn_t stm32_iwdg_isr(int irq, void *wdog_arg)
> +{
> +       struct watchdog_device *wdd = wdog_arg;
> +       struct stm32_iwdg *wdt = watchdog_get_drvdata(wdd);
> +       u32 reg;
> +
> +       reg = reg_read(wdt->regs, IWDG_EWCR);
> +       reg |= EWCR_EWIC;
> +       reg_write(wdt->regs, IWDG_EWCR, reg);
> +
> +       watchdog_notify_pretimeout(wdd);
> +
> +       return IRQ_HANDLED;
> +}
> +
>  static void stm32_clk_disable_unprepare(void *data)
>  {
>         clk_disable_unprepare(data);
> @@ -206,11 +250,20 @@ static const struct watchdog_info stm32_iwdg_info = {
>         .identity       = "STM32 Independent Watchdog",
>  };
>  
> +static const struct watchdog_info stm32_iwdg_preinfo = {
> +       .options        = WDIOF_SETTIMEOUT |
> +                         WDIOF_MAGICCLOSE |
> +                         WDIOF_KEEPALIVEPING |
> +                         WDIOF_PRETIMEOUT,
> +       .identity       = "STM32 Independent Watchdog",
> +};
> +
>  static const struct watchdog_ops stm32_iwdg_ops = {
>         .owner          = THIS_MODULE,
>         .start          = stm32_iwdg_start,
>         .ping           = stm32_iwdg_ping,
>         .set_timeout    = stm32_iwdg_set_timeout,
> +       .set_pretimeout = stm32_iwdg_set_pretimeout,
>  };
>  
>  static const struct of_device_id stm32_iwdg_of_match[] = {
> @@ -220,6 +273,39 @@ static const struct of_device_id stm32_iwdg_of_match[] = {
>  };
>  MODULE_DEVICE_TABLE(of, stm32_iwdg_of_match);
>  
> +static int stm32_iwdg_irq_init(struct platform_device *pdev,
> +                              struct stm32_iwdg *wdt)
> +{
> +       struct device_node *np = pdev->dev.of_node;
> +       struct watchdog_device *wdd = &wdt->wdd;
> +       struct device *dev = &pdev->dev;
> +       int irq, ret;
> +
> +       if (!wdt->data->has_early_wakeup)
> +               return 0;
> +
> +       irq = platform_get_irq(pdev, 0);
> +       if (irq <= 0)
> +               return 0;
> +
> +       if (of_property_read_bool(np, "wakeup-source")) {
> +               ret = device_init_wakeup(&pdev->dev, true);
> +               if (ret)
> +                       return ret;
> +
> +               ret = dev_pm_set_wake_irq(&pdev->dev, irq);
> +               if (ret)
> +                       return ret;
> +       }
> +
> +       ret = devm_request_irq(dev, irq, stm32_iwdg_isr, 0,
> +                              dev_name(dev), wdd);
> +       if (!ret)
> +               wdd->info = &stm32_iwdg_preinfo;
> +
> +       return ret;
> +}
> +
>  static int stm32_iwdg_probe(struct platform_device *pdev)
>  {
>         struct device *dev = &pdev->dev;
> @@ -253,6 +339,11 @@ static int stm32_iwdg_probe(struct platform_device *pdev)
>         wdd->max_hw_heartbeat_ms = ((RLR_MAX + 1) * wdt->data->max_prescaler *
>                                     1000) / wdt->rate;
>  
> +       /* Initialize IRQ, this might override wdd->info, hence it is here. */
> +       ret = stm32_iwdg_irq_init(pdev, wdt);
> +       if (ret)
> +               return ret;
> +
>         watchdog_set_drvdata(wdd, wdt);
>         watchdog_set_nowayout(wdd, WATCHDOG_NOWAYOUT);
>         watchdog_init_timeout(wdd, 0, dev);

Thanks!

Reviewed-by: Antonio Borneo <antonio.borneo@xxxxxxxxxxx>