root/drivers/watchdog/asm9260_wdt.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. asm9260_wdt_feed
  2. asm9260_wdt_gettimeleft
  3. asm9260_wdt_updatetimeout
  4. asm9260_wdt_enable
  5. asm9260_wdt_disable
  6. asm9260_wdt_settimeout
  7. asm9260_wdt_sys_reset
  8. asm9260_wdt_irq
  9. asm9260_restart
  10. asm9260_clk_disable_unprepare
  11. asm9260_wdt_get_dt_clks
  12. asm9260_wdt_get_dt_mode
  13. asm9260_wdt_probe

   1 // SPDX-License-Identifier: GPL-2.0-or-later
   2 /*
   3  * Watchdog driver for Alphascale ASM9260.
   4  *
   5  * Copyright (c) 2014 Oleksij Rempel <linux@rempel-privat.de>
   6  */
   7 
   8 #include <linux/bitops.h>
   9 #include <linux/clk.h>
  10 #include <linux/delay.h>
  11 #include <linux/interrupt.h>
  12 #include <linux/io.h>
  13 #include <linux/module.h>
  14 #include <linux/of.h>
  15 #include <linux/platform_device.h>
  16 #include <linux/reset.h>
  17 #include <linux/watchdog.h>
  18 
  19 #define CLOCK_FREQ      1000000
  20 
  21 /* Watchdog Mode register */
  22 #define HW_WDMOD                        0x00
  23 /* Wake interrupt. Set by HW, can't be cleared. */
  24 #define BM_MOD_WDINT                    BIT(3)
  25 /* This bit set if timeout reached. Cleared by SW. */
  26 #define BM_MOD_WDTOF                    BIT(2)
  27 /* HW Reset on timeout */
  28 #define BM_MOD_WDRESET                  BIT(1)
  29 /* WD enable */
  30 #define BM_MOD_WDEN                     BIT(0)
  31 
  32 /*
  33  * Watchdog Timer Constant register
  34  * Minimal value is 0xff, the meaning of this value
  35  * depends on used clock: T = WDCLK * (0xff + 1) * 4
  36  */
  37 #define HW_WDTC                         0x04
  38 #define BM_WDTC_MAX(freq)               (0x7fffffff / (freq))
  39 
  40 /* Watchdog Feed register */
  41 #define HW_WDFEED                       0x08
  42 
  43 /* Watchdog Timer Value register */
  44 #define HW_WDTV                         0x0c
  45 
  46 #define ASM9260_WDT_DEFAULT_TIMEOUT     30
  47 
  48 enum asm9260_wdt_mode {
  49         HW_RESET,
  50         SW_RESET,
  51         DEBUG,
  52 };
  53 
  54 struct asm9260_wdt_priv {
  55         struct device           *dev;
  56         struct watchdog_device  wdd;
  57         struct clk              *clk;
  58         struct clk              *clk_ahb;
  59         struct reset_control    *rst;
  60 
  61         void __iomem            *iobase;
  62         int                     irq;
  63         unsigned long           wdt_freq;
  64         enum asm9260_wdt_mode   mode;
  65 };
  66 
  67 static int asm9260_wdt_feed(struct watchdog_device *wdd)
  68 {
  69         struct asm9260_wdt_priv *priv = watchdog_get_drvdata(wdd);
  70 
  71         iowrite32(0xaa, priv->iobase + HW_WDFEED);
  72         iowrite32(0x55, priv->iobase + HW_WDFEED);
  73 
  74         return 0;
  75 }
  76 
  77 static unsigned int asm9260_wdt_gettimeleft(struct watchdog_device *wdd)
  78 {
  79         struct asm9260_wdt_priv *priv = watchdog_get_drvdata(wdd);
  80         u32 counter;
  81 
  82         counter = ioread32(priv->iobase + HW_WDTV);
  83 
  84         return counter / priv->wdt_freq;
  85 }
  86 
  87 static int asm9260_wdt_updatetimeout(struct watchdog_device *wdd)
  88 {
  89         struct asm9260_wdt_priv *priv = watchdog_get_drvdata(wdd);
  90         u32 counter;
  91 
  92         counter = wdd->timeout * priv->wdt_freq;
  93 
  94         iowrite32(counter, priv->iobase + HW_WDTC);
  95 
  96         return 0;
  97 }
  98 
  99 static int asm9260_wdt_enable(struct watchdog_device *wdd)
 100 {
 101         struct asm9260_wdt_priv *priv = watchdog_get_drvdata(wdd);
 102         u32 mode = 0;
 103 
 104         if (priv->mode == HW_RESET)
 105                 mode = BM_MOD_WDRESET;
 106 
 107         iowrite32(BM_MOD_WDEN | mode, priv->iobase + HW_WDMOD);
 108 
 109         asm9260_wdt_updatetimeout(wdd);
 110 
 111         asm9260_wdt_feed(wdd);
 112 
 113         return 0;
 114 }
 115 
 116 static int asm9260_wdt_disable(struct watchdog_device *wdd)
 117 {
 118         struct asm9260_wdt_priv *priv = watchdog_get_drvdata(wdd);
 119 
 120         /* The only way to disable WD is to reset it. */
 121         reset_control_assert(priv->rst);
 122         reset_control_deassert(priv->rst);
 123 
 124         return 0;
 125 }
 126 
 127 static int asm9260_wdt_settimeout(struct watchdog_device *wdd, unsigned int to)
 128 {
 129         wdd->timeout = to;
 130         asm9260_wdt_updatetimeout(wdd);
 131 
 132         return 0;
 133 }
 134 
 135 static void asm9260_wdt_sys_reset(struct asm9260_wdt_priv *priv)
 136 {
 137         /* init WD if it was not started */
 138 
 139         iowrite32(BM_MOD_WDEN | BM_MOD_WDRESET, priv->iobase + HW_WDMOD);
 140 
 141         iowrite32(0xff, priv->iobase + HW_WDTC);
 142         /* first pass correct sequence */
 143         asm9260_wdt_feed(&priv->wdd);
 144         /*
 145          * Then write wrong pattern to the feed to trigger reset
 146          * ASAP.
 147          */
 148         iowrite32(0xff, priv->iobase + HW_WDFEED);
 149 
 150         mdelay(1000);
 151 }
 152 
 153 static irqreturn_t asm9260_wdt_irq(int irq, void *devid)
 154 {
 155         struct asm9260_wdt_priv *priv = devid;
 156         u32 stat;
 157 
 158         stat = ioread32(priv->iobase + HW_WDMOD);
 159         if (!(stat & BM_MOD_WDINT))
 160                 return IRQ_NONE;
 161 
 162         if (priv->mode == DEBUG) {
 163                 dev_info(priv->dev, "Watchdog Timeout. Do nothing.\n");
 164         } else {
 165                 dev_info(priv->dev, "Watchdog Timeout. Doing SW Reset.\n");
 166                 asm9260_wdt_sys_reset(priv);
 167         }
 168 
 169         return IRQ_HANDLED;
 170 }
 171 
 172 static int asm9260_restart(struct watchdog_device *wdd, unsigned long action,
 173                            void *data)
 174 {
 175         struct asm9260_wdt_priv *priv = watchdog_get_drvdata(wdd);
 176 
 177         asm9260_wdt_sys_reset(priv);
 178 
 179         return 0;
 180 }
 181 
 182 static const struct watchdog_info asm9260_wdt_ident = {
 183         .options          =     WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING
 184                                 | WDIOF_MAGICCLOSE,
 185         .identity         =     "Alphascale asm9260 Watchdog",
 186 };
 187 
 188 static const struct watchdog_ops asm9260_wdt_ops = {
 189         .owner          = THIS_MODULE,
 190         .start          = asm9260_wdt_enable,
 191         .stop           = asm9260_wdt_disable,
 192         .get_timeleft   = asm9260_wdt_gettimeleft,
 193         .ping           = asm9260_wdt_feed,
 194         .set_timeout    = asm9260_wdt_settimeout,
 195         .restart        = asm9260_restart,
 196 };
 197 
 198 static void asm9260_clk_disable_unprepare(void *data)
 199 {
 200         clk_disable_unprepare(data);
 201 }
 202 
 203 static int asm9260_wdt_get_dt_clks(struct asm9260_wdt_priv *priv)
 204 {
 205         int err;
 206         unsigned long clk;
 207 
 208         priv->clk = devm_clk_get(priv->dev, "mod");
 209         if (IS_ERR(priv->clk)) {
 210                 dev_err(priv->dev, "Failed to get \"mod\" clk\n");
 211                 return PTR_ERR(priv->clk);
 212         }
 213 
 214         /* configure AHB clock */
 215         priv->clk_ahb = devm_clk_get(priv->dev, "ahb");
 216         if (IS_ERR(priv->clk_ahb)) {
 217                 dev_err(priv->dev, "Failed to get \"ahb\" clk\n");
 218                 return PTR_ERR(priv->clk_ahb);
 219         }
 220 
 221         err = clk_prepare_enable(priv->clk_ahb);
 222         if (err) {
 223                 dev_err(priv->dev, "Failed to enable ahb_clk!\n");
 224                 return err;
 225         }
 226         err = devm_add_action_or_reset(priv->dev,
 227                                        asm9260_clk_disable_unprepare,
 228                                        priv->clk_ahb);
 229         if (err)
 230                 return err;
 231 
 232         err = clk_set_rate(priv->clk, CLOCK_FREQ);
 233         if (err) {
 234                 dev_err(priv->dev, "Failed to set rate!\n");
 235                 return err;
 236         }
 237 
 238         err = clk_prepare_enable(priv->clk);
 239         if (err) {
 240                 dev_err(priv->dev, "Failed to enable clk!\n");
 241                 return err;
 242         }
 243         err = devm_add_action_or_reset(priv->dev,
 244                                        asm9260_clk_disable_unprepare,
 245                                        priv->clk);
 246         if (err)
 247                 return err;
 248 
 249         /* wdt has internal divider */
 250         clk = clk_get_rate(priv->clk);
 251         if (!clk) {
 252                 dev_err(priv->dev, "Failed, clk is 0!\n");
 253                 return -EINVAL;
 254         }
 255 
 256         priv->wdt_freq = clk / 2;
 257 
 258         return 0;
 259 }
 260 
 261 static void asm9260_wdt_get_dt_mode(struct asm9260_wdt_priv *priv)
 262 {
 263         const char *tmp;
 264         int ret;
 265 
 266         /* default mode */
 267         priv->mode = HW_RESET;
 268 
 269         ret = of_property_read_string(priv->dev->of_node,
 270                                       "alphascale,mode", &tmp);
 271         if (ret < 0)
 272                 return;
 273 
 274         if (!strcmp(tmp, "hw"))
 275                 priv->mode = HW_RESET;
 276         else if (!strcmp(tmp, "sw"))
 277                 priv->mode = SW_RESET;
 278         else if (!strcmp(tmp, "debug"))
 279                 priv->mode = DEBUG;
 280         else
 281                 dev_warn(priv->dev, "unknown reset-type: %s. Using default \"hw\" mode.",
 282                          tmp);
 283 }
 284 
 285 static int asm9260_wdt_probe(struct platform_device *pdev)
 286 {
 287         struct device *dev = &pdev->dev;
 288         struct asm9260_wdt_priv *priv;
 289         struct watchdog_device *wdd;
 290         int ret;
 291         static const char * const mode_name[] = { "hw", "sw", "debug", };
 292 
 293         priv = devm_kzalloc(dev, sizeof(struct asm9260_wdt_priv), GFP_KERNEL);
 294         if (!priv)
 295                 return -ENOMEM;
 296 
 297         priv->dev = dev;
 298 
 299         priv->iobase = devm_platform_ioremap_resource(pdev, 0);
 300         if (IS_ERR(priv->iobase))
 301                 return PTR_ERR(priv->iobase);
 302 
 303         priv->rst = devm_reset_control_get_exclusive(dev, "wdt_rst");
 304         if (IS_ERR(priv->rst))
 305                 return PTR_ERR(priv->rst);
 306 
 307         ret = asm9260_wdt_get_dt_clks(priv);
 308         if (ret)
 309                 return ret;
 310 
 311         wdd = &priv->wdd;
 312         wdd->info = &asm9260_wdt_ident;
 313         wdd->ops = &asm9260_wdt_ops;
 314         wdd->min_timeout = 1;
 315         wdd->max_timeout = BM_WDTC_MAX(priv->wdt_freq);
 316         wdd->parent = dev;
 317 
 318         watchdog_set_drvdata(wdd, priv);
 319 
 320         /*
 321          * If 'timeout-sec' unspecified in devicetree, assume a 30 second
 322          * default, unless the max timeout is less than 30 seconds, then use
 323          * the max instead.
 324          */
 325         wdd->timeout = ASM9260_WDT_DEFAULT_TIMEOUT;
 326         watchdog_init_timeout(wdd, 0, dev);
 327 
 328         asm9260_wdt_get_dt_mode(priv);
 329 
 330         if (priv->mode != HW_RESET)
 331                 priv->irq = platform_get_irq(pdev, 0);
 332 
 333         if (priv->irq > 0) {
 334                 /*
 335                  * Not all supported platforms specify an interrupt for the
 336                  * watchdog, so let's make it optional.
 337                  */
 338                 ret = devm_request_irq(dev, priv->irq, asm9260_wdt_irq, 0,
 339                                        pdev->name, priv);
 340                 if (ret < 0)
 341                         dev_warn(dev, "failed to request IRQ\n");
 342         }
 343 
 344         watchdog_set_restart_priority(wdd, 128);
 345 
 346         watchdog_stop_on_reboot(wdd);
 347         watchdog_stop_on_unregister(wdd);
 348         ret = devm_watchdog_register_device(dev, wdd);
 349         if (ret)
 350                 return ret;
 351 
 352         platform_set_drvdata(pdev, priv);
 353 
 354         dev_info(dev, "Watchdog enabled (timeout: %d sec, mode: %s)\n",
 355                  wdd->timeout, mode_name[priv->mode]);
 356         return 0;
 357 }
 358 
 359 static const struct of_device_id asm9260_wdt_of_match[] = {
 360         { .compatible = "alphascale,asm9260-wdt"},
 361         {},
 362 };
 363 MODULE_DEVICE_TABLE(of, asm9260_wdt_of_match);
 364 
 365 static struct platform_driver asm9260_wdt_driver = {
 366         .driver = {
 367                 .name = "asm9260-wdt",
 368                 .of_match_table = asm9260_wdt_of_match,
 369         },
 370         .probe = asm9260_wdt_probe,
 371 };
 372 module_platform_driver(asm9260_wdt_driver);
 373 
 374 MODULE_DESCRIPTION("asm9260 WatchDog Timer Driver");
 375 MODULE_AUTHOR("Oleksij Rempel <linux@rempel-privat.de>");
 376 MODULE_LICENSE("GPL");

/* [<][>][^][v][top][bottom][index][help] */