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
  22enum {
  23        HW_ALGO_TOGGLE,
  24        HW_ALGO_LEVEL,
  25};
  26
  27struct gpio_wdt_priv {
  28        int                     gpio;
  29        bool                    active_low;
  30        bool                    state;
  31        bool                    always_running;
  32        unsigned int            hw_algo;
  33        struct watchdog_device  wdd;
  34};
  35
  36static void gpio_wdt_disable(struct gpio_wdt_priv *priv)
  37{
  38        gpio_set_value_cansleep(priv->gpio, !priv->active_low);
  39
  40        /* Put GPIO back to tristate */
  41        if (priv->hw_algo == HW_ALGO_TOGGLE)
  42                gpio_direction_input(priv->gpio);
  43}
  44
  45static int gpio_wdt_ping(struct watchdog_device *wdd)
  46{
  47        struct gpio_wdt_priv *priv = watchdog_get_drvdata(wdd);
  48
  49        switch (priv->hw_algo) {
  50        case HW_ALGO_TOGGLE:
  51                /* Toggle output pin */
  52                priv->state = !priv->state;
  53                gpio_set_value_cansleep(priv->gpio, priv->state);
  54                break;
  55        case HW_ALGO_LEVEL:
  56                /* Pulse */
  57                gpio_set_value_cansleep(priv->gpio, !priv->active_low);
  58                udelay(1);
  59                gpio_set_value_cansleep(priv->gpio, priv->active_low);
  60                break;
  61        }
  62        return 0;
  63}
  64
  65static int gpio_wdt_start(struct watchdog_device *wdd)
  66{
  67        struct gpio_wdt_priv *priv = watchdog_get_drvdata(wdd);
  68
  69        priv->state = priv->active_low;
  70        gpio_direction_output(priv->gpio, priv->state);
  71
  72        set_bit(WDOG_HW_RUNNING, &wdd->status);
  73
  74        return gpio_wdt_ping(wdd);
  75}
  76
  77static int gpio_wdt_stop(struct watchdog_device *wdd)
  78{
  79        struct gpio_wdt_priv *priv = watchdog_get_drvdata(wdd);
  80
  81        if (!priv->always_running) {
  82                gpio_wdt_disable(priv);
  83                clear_bit(WDOG_HW_RUNNING, &wdd->status);
  84        }
  85
  86        return 0;
  87}
  88
  89static const struct watchdog_info gpio_wdt_ident = {
  90        .options        = WDIOF_MAGICCLOSE | WDIOF_KEEPALIVEPING |
  91                          WDIOF_SETTIMEOUT,
  92        .identity       = "GPIO Watchdog",
  93};
  94
  95static const struct watchdog_ops gpio_wdt_ops = {
  96        .owner          = THIS_MODULE,
  97        .start          = gpio_wdt_start,
  98        .stop           = gpio_wdt_stop,
  99        .ping           = gpio_wdt_ping,
 100};
 101
 102static int gpio_wdt_probe(struct platform_device *pdev)
 103{
 104        struct gpio_wdt_priv *priv;
 105        enum of_gpio_flags flags;
 106        unsigned int hw_margin;
 107        unsigned long f = 0;
 108        const char *algo;
 109        int ret;
 110
 111        priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
 112        if (!priv)
 113                return -ENOMEM;
 114
 115        platform_set_drvdata(pdev, priv);
 116
 117        priv->gpio = of_get_gpio_flags(pdev->dev.of_node, 0, &flags);
 118        if (!gpio_is_valid(priv->gpio))
 119                return priv->gpio;
 120
 121        priv->active_low = flags & OF_GPIO_ACTIVE_LOW;
 122
 123        ret = of_property_read_string(pdev->dev.of_node, "hw_algo", &algo);
 124        if (ret)
 125                return ret;
 126        if (!strcmp(algo, "toggle")) {
 127                priv->hw_algo = HW_ALGO_TOGGLE;
 128                f = GPIOF_IN;
 129        } else if (!strcmp(algo, "level")) {
 130                priv->hw_algo = HW_ALGO_LEVEL;
 131                f = priv->active_low ? GPIOF_OUT_INIT_HIGH : GPIOF_OUT_INIT_LOW;
 132        } else {
 133                return -EINVAL;
 134        }
 135
 136        ret = devm_gpio_request_one(&pdev->dev, priv->gpio, f,
 137                                    dev_name(&pdev->dev));
 138        if (ret)
 139                return ret;
 140
 141        ret = of_property_read_u32(pdev->dev.of_node,
 142                                   "hw_margin_ms", &hw_margin);
 143        if (ret)
 144                return ret;
 145        /* Disallow values lower than 2 and higher than 65535 ms */
 146        if (hw_margin < 2 || hw_margin > 65535)
 147                return -EINVAL;
 148
 149        priv->always_running = of_property_read_bool(pdev->dev.of_node,
 150                                                     "always-running");
 151
 152        watchdog_set_drvdata(&priv->wdd, priv);
 153
 154        priv->wdd.info          = &gpio_wdt_ident;
 155        priv->wdd.ops           = &gpio_wdt_ops;
 156        priv->wdd.min_timeout   = SOFT_TIMEOUT_MIN;
 157        priv->wdd.max_hw_heartbeat_ms = hw_margin;
 158        priv->wdd.parent        = &pdev->dev;
 159
 160        if (watchdog_init_timeout(&priv->wdd, 0, &pdev->dev) < 0)
 161                priv->wdd.timeout = SOFT_TIMEOUT_DEF;
 162
 163        watchdog_stop_on_reboot(&priv->wdd);
 164
 165        if (priv->always_running)
 166                gpio_wdt_start(&priv->wdd);
 167
 168        ret = watchdog_register_device(&priv->wdd);
 169
 170        return ret;
 171}
 172
 173static int gpio_wdt_remove(struct platform_device *pdev)
 174{
 175        struct gpio_wdt_priv *priv = platform_get_drvdata(pdev);
 176
 177        watchdog_unregister_device(&priv->wdd);
 178
 179        return 0;
 180}
 181
 182static const struct of_device_id gpio_wdt_dt_ids[] = {
 183        { .compatible = "linux,wdt-gpio", },
 184        { }
 185};
 186MODULE_DEVICE_TABLE(of, gpio_wdt_dt_ids);
 187
 188static struct platform_driver gpio_wdt_driver = {
 189        .driver = {
 190                .name           = "gpio-wdt",
 191                .of_match_table = gpio_wdt_dt_ids,
 192        },
 193        .probe  = gpio_wdt_probe,
 194        .remove = gpio_wdt_remove,
 195};
 196
 197#ifdef CONFIG_GPIO_WATCHDOG_ARCH_INITCALL
 198static int __init gpio_wdt_init(void)
 199{
 200        return platform_driver_register(&gpio_wdt_driver);
 201}
 202arch_initcall(gpio_wdt_init);
 203#else
 204module_platform_driver(gpio_wdt_driver);
 205#endif
 206
 207MODULE_AUTHOR("Alexander Shiyan <shc_work@mail.ru>");
 208MODULE_DESCRIPTION("GPIO Watchdog");
 209MODULE_LICENSE("GPL");
 210