linux/drivers/watchdog/gpio_wdt.c
<<
>>
Prefs
   1/*
   2 * Driver for watchdog device controlled through GPIO-line
   3 *
   4 * Author: 2013, Alexander Shiyan <shc_work@mail.ru>
   5 *
   6 * This program is free software; you can redistribute it and/or modify
   7 * it under the terms of the GNU General Public License as published by
   8 * the Free Software Foundation; either version 2 of the License, or
   9 * (at your option) any later version.
  10 */
  11
  12#include <linux/err.h>
  13#include <linux/delay.h>
  14#include <linux/module.h>
  15#include <linux/of_gpio.h>
  16#include <linux/platform_device.h>
  17#include <linux/watchdog.h>
  18
  19#define SOFT_TIMEOUT_MIN        1
  20#define SOFT_TIMEOUT_DEF        60
  21#define SOFT_TIMEOUT_MAX        0xffff
  22
  23enum {
  24        HW_ALGO_TOGGLE,
  25        HW_ALGO_LEVEL,
  26};
  27
  28struct gpio_wdt_priv {
  29        int                     gpio;
  30        bool                    active_low;
  31        bool                    state;
  32        bool                    always_running;
  33        bool                    armed;
  34        unsigned int            hw_algo;
  35        unsigned int            hw_margin;
  36        unsigned long           last_jiffies;
  37        struct timer_list       timer;
  38        struct watchdog_device  wdd;
  39};
  40
  41static void gpio_wdt_disable(struct gpio_wdt_priv *priv)
  42{
  43        gpio_set_value_cansleep(priv->gpio, !priv->active_low);
  44
  45        /* Put GPIO back to tristate */
  46        if (priv->hw_algo == HW_ALGO_TOGGLE)
  47                gpio_direction_input(priv->gpio);
  48}
  49
  50static void gpio_wdt_hwping(unsigned long data)
  51{
  52        struct watchdog_device *wdd = (struct watchdog_device *)data;
  53        struct gpio_wdt_priv *priv = watchdog_get_drvdata(wdd);
  54
  55        if (priv->armed && time_after(jiffies, priv->last_jiffies +
  56                                      msecs_to_jiffies(wdd->timeout * 1000))) {
  57                dev_crit(wdd->parent,
  58                         "Timer expired. System will reboot soon!\n");
  59                return;
  60        }
  61
  62        /* Restart timer */
  63        mod_timer(&priv->timer, jiffies + priv->hw_margin);
  64
  65        switch (priv->hw_algo) {
  66        case HW_ALGO_TOGGLE:
  67                /* Toggle output pin */
  68                priv->state = !priv->state;
  69                gpio_set_value_cansleep(priv->gpio, priv->state);
  70                break;
  71        case HW_ALGO_LEVEL:
  72                /* Pulse */
  73                gpio_set_value_cansleep(priv->gpio, !priv->active_low);
  74                udelay(1);
  75                gpio_set_value_cansleep(priv->gpio, priv->active_low);
  76                break;
  77        }
  78}
  79
  80static void gpio_wdt_start_impl(struct gpio_wdt_priv *priv)
  81{
  82        priv->state = priv->active_low;
  83        gpio_direction_output(priv->gpio, priv->state);
  84        priv->last_jiffies = jiffies;
  85        gpio_wdt_hwping((unsigned long)&priv->wdd);
  86}
  87
  88static int gpio_wdt_start(struct watchdog_device *wdd)
  89{
  90        struct gpio_wdt_priv *priv = watchdog_get_drvdata(wdd);
  91
  92        gpio_wdt_start_impl(priv);
  93        priv->armed = true;
  94
  95        return 0;
  96}
  97
  98static int gpio_wdt_stop(struct watchdog_device *wdd)
  99{
 100        struct gpio_wdt_priv *priv = watchdog_get_drvdata(wdd);
 101
 102        priv->armed = false;
 103        if (!priv->always_running) {
 104                mod_timer(&priv->timer, 0);
 105                gpio_wdt_disable(priv);
 106        }
 107
 108        return 0;
 109}
 110
 111static int gpio_wdt_ping(struct watchdog_device *wdd)
 112{
 113        struct gpio_wdt_priv *priv = watchdog_get_drvdata(wdd);
 114
 115        priv->last_jiffies = jiffies;
 116
 117        return 0;
 118}
 119
 120static int gpio_wdt_set_timeout(struct watchdog_device *wdd, unsigned int t)
 121{
 122        wdd->timeout = t;
 123
 124        return gpio_wdt_ping(wdd);
 125}
 126
 127static const struct watchdog_info gpio_wdt_ident = {
 128        .options        = WDIOF_MAGICCLOSE | WDIOF_KEEPALIVEPING |
 129                          WDIOF_SETTIMEOUT,
 130        .identity       = "GPIO Watchdog",
 131};
 132
 133static const struct watchdog_ops gpio_wdt_ops = {
 134        .owner          = THIS_MODULE,
 135        .start          = gpio_wdt_start,
 136        .stop           = gpio_wdt_stop,
 137        .ping           = gpio_wdt_ping,
 138        .set_timeout    = gpio_wdt_set_timeout,
 139};
 140
 141static int gpio_wdt_probe(struct platform_device *pdev)
 142{
 143        struct gpio_wdt_priv *priv;
 144        enum of_gpio_flags flags;
 145        unsigned int hw_margin;
 146        unsigned long f = 0;
 147        const char *algo;
 148        int ret;
 149
 150        priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
 151        if (!priv)
 152                return -ENOMEM;
 153
 154        priv->gpio = of_get_gpio_flags(pdev->dev.of_node, 0, &flags);
 155        if (!gpio_is_valid(priv->gpio))
 156                return priv->gpio;
 157
 158        priv->active_low = flags & OF_GPIO_ACTIVE_LOW;
 159
 160        ret = of_property_read_string(pdev->dev.of_node, "hw_algo", &algo);
 161        if (ret)
 162                return ret;
 163        if (!strcmp(algo, "toggle")) {
 164                priv->hw_algo = HW_ALGO_TOGGLE;
 165                f = GPIOF_IN;
 166        } else if (!strcmp(algo, "level")) {
 167                priv->hw_algo = HW_ALGO_LEVEL;
 168                f = priv->active_low ? GPIOF_OUT_INIT_HIGH : GPIOF_OUT_INIT_LOW;
 169        } else {
 170                return -EINVAL;
 171        }
 172
 173        ret = devm_gpio_request_one(&pdev->dev, priv->gpio, f,
 174                                    dev_name(&pdev->dev));
 175        if (ret)
 176                return ret;
 177
 178        ret = of_property_read_u32(pdev->dev.of_node,
 179                                   "hw_margin_ms", &hw_margin);
 180        if (ret)
 181                return ret;
 182        /* Disallow values lower than 2 and higher than 65535 ms */
 183        if (hw_margin < 2 || hw_margin > 65535)
 184                return -EINVAL;
 185
 186        /* Use safe value (1/2 of real timeout) */
 187        priv->hw_margin = msecs_to_jiffies(hw_margin / 2);
 188
 189        priv->always_running = of_property_read_bool(pdev->dev.of_node,
 190                                                     "always-running");
 191
 192        watchdog_set_drvdata(&priv->wdd, priv);
 193
 194        priv->wdd.info          = &gpio_wdt_ident;
 195        priv->wdd.ops           = &gpio_wdt_ops;
 196        priv->wdd.min_timeout   = SOFT_TIMEOUT_MIN;
 197        priv->wdd.max_timeout   = SOFT_TIMEOUT_MAX;
 198        priv->wdd.parent        = &pdev->dev;
 199
 200        if (watchdog_init_timeout(&priv->wdd, 0, &pdev->dev) < 0)
 201                priv->wdd.timeout = SOFT_TIMEOUT_DEF;
 202
 203        setup_timer(&priv->timer, gpio_wdt_hwping, (unsigned long)&priv->wdd);
 204
 205        watchdog_stop_on_reboot(&priv->wdd);
 206
 207        ret = watchdog_register_device(&priv->wdd);
 208        if (ret)
 209                return ret;
 210
 211        if (priv->always_running)
 212                gpio_wdt_start_impl(priv);
 213
 214        return 0;
 215}
 216
 217static int gpio_wdt_remove(struct platform_device *pdev)
 218{
 219        struct gpio_wdt_priv *priv = platform_get_drvdata(pdev);
 220
 221        del_timer_sync(&priv->timer);
 222        watchdog_unregister_device(&priv->wdd);
 223
 224        return 0;
 225}
 226
 227static const struct of_device_id gpio_wdt_dt_ids[] = {
 228        { .compatible = "linux,wdt-gpio", },
 229        { }
 230};
 231MODULE_DEVICE_TABLE(of, gpio_wdt_dt_ids);
 232
 233static struct platform_driver gpio_wdt_driver = {
 234        .driver = {
 235                .name           = "gpio-wdt",
 236                .of_match_table = gpio_wdt_dt_ids,
 237        },
 238        .probe  = gpio_wdt_probe,
 239        .remove = gpio_wdt_remove,
 240};
 241
 242#ifdef CONFIG_GPIO_WATCHDOG_ARCH_INITCALL
 243static int __init gpio_wdt_init(void)
 244{
 245        return platform_driver_register(&gpio_wdt_driver);
 246}
 247arch_initcall(gpio_wdt_init);
 248#else
 249module_platform_driver(gpio_wdt_driver);
 250#endif
 251
 252MODULE_AUTHOR("Alexander Shiyan <shc_work@mail.ru>");
 253MODULE_DESCRIPTION("GPIO Watchdog");
 254MODULE_LICENSE("GPL");
 255
lxr.linux.no kindly hosted by Redpill Linpro AS, provider of Linux consulting and operations services since 1995.