blob: 0923201ce8743af366bff7462c37bba9dff645da [file] [log] [blame]
Thomas Gleixner2874c5f2019-05-27 08:55:01 +02001// SPDX-License-Identifier: GPL-2.0-or-later
Alexander Shiyan25134ea2013-11-30 11:54:32 +04002/*
3 * Driver for watchdog device controlled through GPIO-line
4 *
5 * Author: 2013, Alexander Shiyan <shc_work@mail.ru>
Alexander Shiyan25134ea2013-11-30 11:54:32 +04006 */
7
8#include <linux/err.h>
9#include <linux/delay.h>
10#include <linux/module.h>
Linus Walleija2363f92017-10-09 01:28:47 +020011#include <linux/gpio/consumer.h>
12#include <linux/of.h>
Alexander Shiyan25134ea2013-11-30 11:54:32 +040013#include <linux/platform_device.h>
Alexander Shiyan25134ea2013-11-30 11:54:32 +040014#include <linux/watchdog.h>
15
Mans Rullgard1a4aaf92019-05-28 10:09:47 +010016static bool nowayout = WATCHDOG_NOWAYOUT;
17module_param(nowayout, bool, 0);
18MODULE_PARM_DESC(nowayout,
19 "Watchdog cannot be stopped once started (default="
20 __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
21
Alexander Shiyan25134ea2013-11-30 11:54:32 +040022#define SOFT_TIMEOUT_MIN 1
23#define SOFT_TIMEOUT_DEF 60
Alexander Shiyan25134ea2013-11-30 11:54:32 +040024
25enum {
26 HW_ALGO_TOGGLE,
27 HW_ALGO_LEVEL,
28};
29
30struct gpio_wdt_priv {
Linus Walleija2363f92017-10-09 01:28:47 +020031 struct gpio_desc *gpiod;
Alexander Shiyan25134ea2013-11-30 11:54:32 +040032 bool state;
Mike Looijmansba804a92015-01-14 07:28:29 +010033 bool always_running;
Alexander Shiyan25134ea2013-11-30 11:54:32 +040034 unsigned int hw_algo;
Alexander Shiyan25134ea2013-11-30 11:54:32 +040035 struct watchdog_device wdd;
36};
37
38static void gpio_wdt_disable(struct gpio_wdt_priv *priv)
39{
Linus Walleija2363f92017-10-09 01:28:47 +020040 /* Eternal ping */
41 gpiod_set_value_cansleep(priv->gpiod, 1);
Alexander Shiyan25134ea2013-11-30 11:54:32 +040042
43 /* Put GPIO back to tristate */
44 if (priv->hw_algo == HW_ALGO_TOGGLE)
Linus Walleija2363f92017-10-09 01:28:47 +020045 gpiod_direction_input(priv->gpiod);
Alexander Shiyan25134ea2013-11-30 11:54:32 +040046}
47
Guenter Roeck03bca152016-02-28 13:12:23 -080048static int gpio_wdt_ping(struct watchdog_device *wdd)
Uwe Kleine-König4f2d0b22015-07-31 09:21:36 +020049{
Uwe Kleine-König4f2d0b22015-07-31 09:21:36 +020050 struct gpio_wdt_priv *priv = watchdog_get_drvdata(wdd);
51
Uwe Kleine-König4f2d0b22015-07-31 09:21:36 +020052 switch (priv->hw_algo) {
53 case HW_ALGO_TOGGLE:
54 /* Toggle output pin */
55 priv->state = !priv->state;
Linus Walleija2363f92017-10-09 01:28:47 +020056 gpiod_set_value_cansleep(priv->gpiod, priv->state);
Uwe Kleine-König4f2d0b22015-07-31 09:21:36 +020057 break;
58 case HW_ALGO_LEVEL:
59 /* Pulse */
Linus Walleija2363f92017-10-09 01:28:47 +020060 gpiod_set_value_cansleep(priv->gpiod, 1);
Uwe Kleine-König4f2d0b22015-07-31 09:21:36 +020061 udelay(1);
Linus Walleija2363f92017-10-09 01:28:47 +020062 gpiod_set_value_cansleep(priv->gpiod, 0);
Uwe Kleine-König4f2d0b22015-07-31 09:21:36 +020063 break;
64 }
Guenter Roeck03bca152016-02-28 13:12:23 -080065 return 0;
Mike Looijmansba804a92015-01-14 07:28:29 +010066}
67
68static int gpio_wdt_start(struct watchdog_device *wdd)
69{
70 struct gpio_wdt_priv *priv = watchdog_get_drvdata(wdd);
71
Linus Walleija2363f92017-10-09 01:28:47 +020072 priv->state = 0;
73 gpiod_direction_output(priv->gpiod, priv->state);
Alexander Shiyan25134ea2013-11-30 11:54:32 +040074
Guenter Roeck03bca152016-02-28 13:12:23 -080075 set_bit(WDOG_HW_RUNNING, &wdd->status);
76
77 return gpio_wdt_ping(wdd);
Alexander Shiyan25134ea2013-11-30 11:54:32 +040078}
79
80static int gpio_wdt_stop(struct watchdog_device *wdd)
81{
82 struct gpio_wdt_priv *priv = watchdog_get_drvdata(wdd);
83
Mike Looijmansba804a92015-01-14 07:28:29 +010084 if (!priv->always_running) {
Mike Looijmansba804a92015-01-14 07:28:29 +010085 gpio_wdt_disable(priv);
Rasmus Villemoesbc137df2017-11-09 14:39:55 +010086 } else {
87 set_bit(WDOG_HW_RUNNING, &wdd->status);
Mike Looijmansba804a92015-01-14 07:28:29 +010088 }
Alexander Shiyan25134ea2013-11-30 11:54:32 +040089
90 return 0;
91}
92
Alexander Shiyan25134ea2013-11-30 11:54:32 +040093static const struct watchdog_info gpio_wdt_ident = {
94 .options = WDIOF_MAGICCLOSE | WDIOF_KEEPALIVEPING |
95 WDIOF_SETTIMEOUT,
96 .identity = "GPIO Watchdog",
97};
98
99static const struct watchdog_ops gpio_wdt_ops = {
100 .owner = THIS_MODULE,
101 .start = gpio_wdt_start,
102 .stop = gpio_wdt_stop,
103 .ping = gpio_wdt_ping,
Alexander Shiyan25134ea2013-11-30 11:54:32 +0400104};
105
106static int gpio_wdt_probe(struct platform_device *pdev)
107{
Linus Walleijd0d06772017-10-09 01:28:46 +0200108 struct device *dev = &pdev->dev;
109 struct device_node *np = dev->of_node;
Alexander Shiyan25134ea2013-11-30 11:54:32 +0400110 struct gpio_wdt_priv *priv;
Linus Walleija2363f92017-10-09 01:28:47 +0200111 enum gpiod_flags gflags;
Alexander Shiyan25134ea2013-11-30 11:54:32 +0400112 unsigned int hw_margin;
Alexander Shiyan25134ea2013-11-30 11:54:32 +0400113 const char *algo;
114 int ret;
115
Linus Walleijd0d06772017-10-09 01:28:46 +0200116 priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
Alexander Shiyan25134ea2013-11-30 11:54:32 +0400117 if (!priv)
118 return -ENOMEM;
119
Wei Yongjun1ac06562016-07-26 14:50:14 +0000120 platform_set_drvdata(pdev, priv);
121
Linus Walleijd0d06772017-10-09 01:28:46 +0200122 ret = of_property_read_string(np, "hw_algo", &algo);
Alexander Shiyan25134ea2013-11-30 11:54:32 +0400123 if (ret)
124 return ret;
Uwe Kleine-König0a0a5422015-07-30 11:32:23 +0200125 if (!strcmp(algo, "toggle")) {
Alexander Shiyan25134ea2013-11-30 11:54:32 +0400126 priv->hw_algo = HW_ALGO_TOGGLE;
Linus Walleija2363f92017-10-09 01:28:47 +0200127 gflags = GPIOD_IN;
Uwe Kleine-König0a0a5422015-07-30 11:32:23 +0200128 } else if (!strcmp(algo, "level")) {
Alexander Shiyan25134ea2013-11-30 11:54:32 +0400129 priv->hw_algo = HW_ALGO_LEVEL;
Linus Walleija2363f92017-10-09 01:28:47 +0200130 gflags = GPIOD_OUT_LOW;
Alexander Shiyan25134ea2013-11-30 11:54:32 +0400131 } else {
132 return -EINVAL;
133 }
134
Linus Walleija2363f92017-10-09 01:28:47 +0200135 priv->gpiod = devm_gpiod_get(dev, NULL, gflags);
136 if (IS_ERR(priv->gpiod))
137 return PTR_ERR(priv->gpiod);
Alexander Shiyan25134ea2013-11-30 11:54:32 +0400138
Linus Walleijd0d06772017-10-09 01:28:46 +0200139 ret = of_property_read_u32(np,
Alexander Shiyan25134ea2013-11-30 11:54:32 +0400140 "hw_margin_ms", &hw_margin);
141 if (ret)
142 return ret;
143 /* Disallow values lower than 2 and higher than 65535 ms */
144 if (hw_margin < 2 || hw_margin > 65535)
145 return -EINVAL;
146
Linus Walleijd0d06772017-10-09 01:28:46 +0200147 priv->always_running = of_property_read_bool(np,
Mike Looijmansba804a92015-01-14 07:28:29 +0100148 "always-running");
149
Alexander Shiyan25134ea2013-11-30 11:54:32 +0400150 watchdog_set_drvdata(&priv->wdd, priv);
151
152 priv->wdd.info = &gpio_wdt_ident;
153 priv->wdd.ops = &gpio_wdt_ops;
154 priv->wdd.min_timeout = SOFT_TIMEOUT_MIN;
Guenter Roeck03bca152016-02-28 13:12:23 -0800155 priv->wdd.max_hw_heartbeat_ms = hw_margin;
Linus Walleijd0d06772017-10-09 01:28:46 +0200156 priv->wdd.parent = dev;
Marcus Folkesson65adfa22018-02-10 21:36:22 +0100157 priv->wdd.timeout = SOFT_TIMEOUT_DEF;
Alexander Shiyan25134ea2013-11-30 11:54:32 +0400158
Guenter Roeck3564fbc2019-04-08 12:38:40 -0700159 watchdog_init_timeout(&priv->wdd, 0, dev);
Mans Rullgard1a4aaf92019-05-28 10:09:47 +0100160 watchdog_set_nowayout(&priv->wdd, nowayout);
Alexander Shiyan25134ea2013-11-30 11:54:32 +0400161
Damien Riegel28e805b2015-11-20 16:54:54 -0500162 watchdog_stop_on_reboot(&priv->wdd);
163
Mike Looijmansba804a92015-01-14 07:28:29 +0100164 if (priv->always_running)
Guenter Roeck03bca152016-02-28 13:12:23 -0800165 gpio_wdt_start(&priv->wdd);
Mike Looijmansba804a92015-01-14 07:28:29 +0100166
Guenter Roeck3564fbc2019-04-08 12:38:40 -0700167 return devm_watchdog_register_device(dev, &priv->wdd);
Alexander Shiyan25134ea2013-11-30 11:54:32 +0400168}
169
170static const struct of_device_id gpio_wdt_dt_ids[] = {
171 { .compatible = "linux,wdt-gpio", },
172 { }
173};
174MODULE_DEVICE_TABLE(of, gpio_wdt_dt_ids);
175
176static struct platform_driver gpio_wdt_driver = {
177 .driver = {
178 .name = "gpio-wdt",
Alexander Shiyan25134ea2013-11-30 11:54:32 +0400179 .of_match_table = gpio_wdt_dt_ids,
180 },
181 .probe = gpio_wdt_probe,
Alexander Shiyan25134ea2013-11-30 11:54:32 +0400182};
Jean-Baptiste Theou5e53c8e2015-06-09 09:55:03 -0700183
184#ifdef CONFIG_GPIO_WATCHDOG_ARCH_INITCALL
185static int __init gpio_wdt_init(void)
186{
187 return platform_driver_register(&gpio_wdt_driver);
188}
189arch_initcall(gpio_wdt_init);
190#else
Alexander Shiyan25134ea2013-11-30 11:54:32 +0400191module_platform_driver(gpio_wdt_driver);
Jean-Baptiste Theou5e53c8e2015-06-09 09:55:03 -0700192#endif
Alexander Shiyan25134ea2013-11-30 11:54:32 +0400193
194MODULE_AUTHOR("Alexander Shiyan <shc_work@mail.ru>");
195MODULE_DESCRIPTION("GPIO Watchdog");
196MODULE_LICENSE("GPL");