linux/drivers/watchdog/mena21_wdt.c
<<
>>
Prefs
   1/*
   2 * Watchdog driver for the A21 VME CPU Boards
   3 *
   4 * Copyright (C) 2013 MEN Mikro Elektronik Nuernberg GmbH
   5 *
   6 * This program is free software; you can redistribute it and/or
   7 * modify it under the terms of the GNU General Public License
   8 * as published by the Free Software Foundation
   9 */
  10#include <linux/module.h>
  11#include <linux/moduleparam.h>
  12#include <linux/types.h>
  13#include <linux/kernel.h>
  14#include <linux/slab.h>
  15#include <linux/platform_device.h>
  16#include <linux/watchdog.h>
  17#include <linux/uaccess.h>
  18#include <linux/gpio.h>
  19#include <linux/of_gpio.h>
  20#include <linux/delay.h>
  21#include <linux/bitops.h>
  22
  23#define NUM_GPIOS 6
  24
  25enum a21_wdt_gpios {
  26        GPIO_WD_ENAB,
  27        GPIO_WD_FAST,
  28        GPIO_WD_TRIG,
  29        GPIO_WD_RST0,
  30        GPIO_WD_RST1,
  31        GPIO_WD_RST2,
  32};
  33
  34struct a21_wdt_drv {
  35        struct watchdog_device wdt;
  36        struct mutex lock;
  37        unsigned gpios[NUM_GPIOS];
  38};
  39
  40static bool nowayout = WATCHDOG_NOWAYOUT;
  41module_param(nowayout, bool, 0);
  42MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default="
  43                            __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
  44
  45static unsigned int a21_wdt_get_bootstatus(struct a21_wdt_drv *drv)
  46{
  47        int reset = 0;
  48
  49        reset |= gpio_get_value(drv->gpios[GPIO_WD_RST0]) ? (1 << 0) : 0;
  50        reset |= gpio_get_value(drv->gpios[GPIO_WD_RST1]) ? (1 << 1) : 0;
  51        reset |= gpio_get_value(drv->gpios[GPIO_WD_RST2]) ? (1 << 2) : 0;
  52
  53        return reset;
  54}
  55
  56static int a21_wdt_start(struct watchdog_device *wdt)
  57{
  58        struct a21_wdt_drv *drv = watchdog_get_drvdata(wdt);
  59
  60        mutex_lock(&drv->lock);
  61
  62        gpio_set_value(drv->gpios[GPIO_WD_ENAB], 1);
  63
  64        mutex_unlock(&drv->lock);
  65
  66        return 0;
  67}
  68
  69static int a21_wdt_stop(struct watchdog_device *wdt)
  70{
  71        struct a21_wdt_drv *drv = watchdog_get_drvdata(wdt);
  72
  73        mutex_lock(&drv->lock);
  74
  75        gpio_set_value(drv->gpios[GPIO_WD_ENAB], 0);
  76
  77        mutex_unlock(&drv->lock);
  78
  79        return 0;
  80}
  81
  82static int a21_wdt_ping(struct watchdog_device *wdt)
  83{
  84        struct a21_wdt_drv *drv = watchdog_get_drvdata(wdt);
  85
  86        mutex_lock(&drv->lock);
  87
  88        gpio_set_value(drv->gpios[GPIO_WD_TRIG], 0);
  89        ndelay(10);
  90        gpio_set_value(drv->gpios[GPIO_WD_TRIG], 1);
  91
  92        mutex_unlock(&drv->lock);
  93
  94        return 0;
  95}
  96
  97static int a21_wdt_set_timeout(struct watchdog_device *wdt,
  98                               unsigned int timeout)
  99{
 100        struct a21_wdt_drv *drv = watchdog_get_drvdata(wdt);
 101
 102        if (timeout != 1 && timeout != 30) {
 103                dev_err(wdt->dev, "Only 1 and 30 allowed as timeout\n");
 104                return -EINVAL;
 105        }
 106
 107        if (timeout == 30 && wdt->timeout == 1) {
 108                dev_err(wdt->dev,
 109                        "Transition from fast to slow mode not allowed\n");
 110                return -EINVAL;
 111        }
 112
 113        mutex_lock(&drv->lock);
 114
 115        if (timeout == 1)
 116                gpio_set_value(drv->gpios[GPIO_WD_FAST], 1);
 117        else
 118                gpio_set_value(drv->gpios[GPIO_WD_FAST], 0);
 119
 120        wdt->timeout = timeout;
 121
 122        mutex_unlock(&drv->lock);
 123
 124        return 0;
 125}
 126
 127static const struct watchdog_info a21_wdt_info = {
 128        .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
 129        .identity = "MEN A21 Watchdog",
 130};
 131
 132static const struct watchdog_ops a21_wdt_ops = {
 133        .owner = THIS_MODULE,
 134        .start = a21_wdt_start,
 135        .stop = a21_wdt_stop,
 136        .ping = a21_wdt_ping,
 137        .set_timeout = a21_wdt_set_timeout,
 138};
 139
 140static struct watchdog_device a21_wdt = {
 141        .info = &a21_wdt_info,
 142        .ops = &a21_wdt_ops,
 143        .min_timeout = 1,
 144        .max_timeout = 30,
 145};
 146
 147static int a21_wdt_probe(struct platform_device *pdev)
 148{
 149        struct device_node *node;
 150        struct a21_wdt_drv *drv;
 151        unsigned int reset = 0;
 152        int num_gpios;
 153        int ret;
 154        int i;
 155
 156        drv = devm_kzalloc(&pdev->dev, sizeof(struct a21_wdt_drv), GFP_KERNEL);
 157        if (!drv)
 158                return -ENOMEM;
 159
 160        /* Fill GPIO pin array */
 161        node = pdev->dev.of_node;
 162
 163        num_gpios = of_gpio_count(node);
 164        if (num_gpios != NUM_GPIOS) {
 165                dev_err(&pdev->dev, "gpios DT property wrong, got %d want %d",
 166                        num_gpios, NUM_GPIOS);
 167                return -ENODEV;
 168        }
 169
 170        for (i = 0; i < num_gpios; i++) {
 171                int val;
 172
 173                val = of_get_gpio(node, i);
 174                if (val < 0)
 175                        return val;
 176
 177                drv->gpios[i] = val;
 178        }
 179
 180        /* Request the used GPIOs */
 181        for (i = 0; i < num_gpios; i++) {
 182                ret = devm_gpio_request(&pdev->dev, drv->gpios[i],
 183                                        "MEN A21 Watchdog");
 184                if (ret)
 185                        return ret;
 186
 187                if (i < GPIO_WD_RST0)
 188                        ret = gpio_direction_output(drv->gpios[i],
 189                                                gpio_get_value(drv->gpios[i]));
 190                else            /* GPIO_WD_RST[0..2] are inputs */
 191                        ret = gpio_direction_input(drv->gpios[i]);
 192                if (ret)
 193                        return ret;
 194        }
 195
 196        mutex_init(&drv->lock);
 197        watchdog_init_timeout(&a21_wdt, 30, &pdev->dev);
 198        watchdog_set_nowayout(&a21_wdt, nowayout);
 199        watchdog_set_drvdata(&a21_wdt, drv);
 200
 201        reset = a21_wdt_get_bootstatus(drv);
 202        if (reset == 2)
 203                a21_wdt.bootstatus |= WDIOF_EXTERN1;
 204        else if (reset == 4)
 205                a21_wdt.bootstatus |= WDIOF_CARDRESET;
 206        else if (reset == 5)
 207                a21_wdt.bootstatus |= WDIOF_POWERUNDER;
 208        else if (reset == 7)
 209                a21_wdt.bootstatus |= WDIOF_EXTERN2;
 210
 211        ret = watchdog_register_device(&a21_wdt);
 212        if (ret) {
 213                dev_err(&pdev->dev, "Cannot register watchdog device\n");
 214                goto err_register_wd;
 215        }
 216
 217        dev_set_drvdata(&pdev->dev, drv);
 218
 219        dev_info(&pdev->dev, "MEN A21 watchdog timer driver enabled\n");
 220
 221        return 0;
 222
 223err_register_wd:
 224        mutex_destroy(&drv->lock);
 225
 226        return ret;
 227}
 228
 229static int a21_wdt_remove(struct platform_device *pdev)
 230{
 231        struct a21_wdt_drv *drv = dev_get_drvdata(&pdev->dev);
 232
 233        dev_warn(&pdev->dev,
 234                "Unregistering A21 watchdog driver, board may reboot\n");
 235
 236        watchdog_unregister_device(&drv->wdt);
 237
 238        mutex_destroy(&drv->lock);
 239
 240        return 0;
 241}
 242
 243static void a21_wdt_shutdown(struct platform_device *pdev)
 244{
 245        struct a21_wdt_drv *drv = dev_get_drvdata(&pdev->dev);
 246
 247        gpio_set_value(drv->gpios[GPIO_WD_ENAB], 0);
 248}
 249
 250static const struct of_device_id a21_wdt_ids[] = {
 251        { .compatible = "men,a021-wdt" },
 252        { },
 253};
 254
 255static struct platform_driver a21_wdt_driver = {
 256        .probe = a21_wdt_probe,
 257        .remove = a21_wdt_remove,
 258        .shutdown = a21_wdt_shutdown,
 259        .driver = {
 260                .name = "a21-watchdog",
 261                .of_match_table = a21_wdt_ids,
 262        },
 263};
 264
 265module_platform_driver(a21_wdt_driver);
 266
 267MODULE_AUTHOR("MEN Mikro Elektronik");
 268MODULE_DESCRIPTION("MEN A21 Watchdog");
 269MODULE_LICENSE("GPL");
 270MODULE_ALIAS("platform:a21-watchdog");
 271