summaryrefslogtreecommitdiffstats
path: root/drivers/watchdog/gpio_wdt.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/watchdog/gpio_wdt.c')
-rw-r--r--drivers/watchdog/gpio_wdt.c205
1 files changed, 205 insertions, 0 deletions
diff --git a/drivers/watchdog/gpio_wdt.c b/drivers/watchdog/gpio_wdt.c
new file mode 100644
index 000000000..ea77cae03
--- /dev/null
+++ b/drivers/watchdog/gpio_wdt.c
@@ -0,0 +1,205 @@
+/*
+ * Driver for watchdog device controlled through GPIO-line
+ *
+ * Author: 2013, Alexander Shiyan <shc_work@mail.ru>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <linux/err.h>
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/gpio/consumer.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/watchdog.h>
+
+#define SOFT_TIMEOUT_MIN 1
+#define SOFT_TIMEOUT_DEF 60
+
+enum {
+ HW_ALGO_TOGGLE,
+ HW_ALGO_LEVEL,
+};
+
+struct gpio_wdt_priv {
+ struct gpio_desc *gpiod;
+ bool state;
+ bool always_running;
+ unsigned int hw_algo;
+ struct watchdog_device wdd;
+};
+
+static void gpio_wdt_disable(struct gpio_wdt_priv *priv)
+{
+ /* Eternal ping */
+ gpiod_set_value_cansleep(priv->gpiod, 1);
+
+ /* Put GPIO back to tristate */
+ if (priv->hw_algo == HW_ALGO_TOGGLE)
+ gpiod_direction_input(priv->gpiod);
+}
+
+static int gpio_wdt_ping(struct watchdog_device *wdd)
+{
+ struct gpio_wdt_priv *priv = watchdog_get_drvdata(wdd);
+
+ switch (priv->hw_algo) {
+ case HW_ALGO_TOGGLE:
+ /* Toggle output pin */
+ priv->state = !priv->state;
+ gpiod_set_value_cansleep(priv->gpiod, priv->state);
+ break;
+ case HW_ALGO_LEVEL:
+ /* Pulse */
+ gpiod_set_value_cansleep(priv->gpiod, 1);
+ udelay(1);
+ gpiod_set_value_cansleep(priv->gpiod, 0);
+ break;
+ }
+ return 0;
+}
+
+static int gpio_wdt_start(struct watchdog_device *wdd)
+{
+ struct gpio_wdt_priv *priv = watchdog_get_drvdata(wdd);
+
+ priv->state = 0;
+ gpiod_direction_output(priv->gpiod, priv->state);
+
+ set_bit(WDOG_HW_RUNNING, &wdd->status);
+
+ return gpio_wdt_ping(wdd);
+}
+
+static int gpio_wdt_stop(struct watchdog_device *wdd)
+{
+ struct gpio_wdt_priv *priv = watchdog_get_drvdata(wdd);
+
+ if (!priv->always_running) {
+ gpio_wdt_disable(priv);
+ } else {
+ set_bit(WDOG_HW_RUNNING, &wdd->status);
+ }
+
+ return 0;
+}
+
+static const struct watchdog_info gpio_wdt_ident = {
+ .options = WDIOF_MAGICCLOSE | WDIOF_KEEPALIVEPING |
+ WDIOF_SETTIMEOUT,
+ .identity = "GPIO Watchdog",
+};
+
+static const struct watchdog_ops gpio_wdt_ops = {
+ .owner = THIS_MODULE,
+ .start = gpio_wdt_start,
+ .stop = gpio_wdt_stop,
+ .ping = gpio_wdt_ping,
+};
+
+static int gpio_wdt_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node;
+ struct gpio_wdt_priv *priv;
+ enum gpiod_flags gflags;
+ unsigned int hw_margin;
+ const char *algo;
+ int ret;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, priv);
+
+ ret = of_property_read_string(np, "hw_algo", &algo);
+ if (ret)
+ return ret;
+ if (!strcmp(algo, "toggle")) {
+ priv->hw_algo = HW_ALGO_TOGGLE;
+ gflags = GPIOD_IN;
+ } else if (!strcmp(algo, "level")) {
+ priv->hw_algo = HW_ALGO_LEVEL;
+ gflags = GPIOD_OUT_LOW;
+ } else {
+ return -EINVAL;
+ }
+
+ priv->gpiod = devm_gpiod_get(dev, NULL, gflags);
+ if (IS_ERR(priv->gpiod))
+ return PTR_ERR(priv->gpiod);
+
+ ret = of_property_read_u32(np,
+ "hw_margin_ms", &hw_margin);
+ if (ret)
+ return ret;
+ /* Disallow values lower than 2 and higher than 65535 ms */
+ if (hw_margin < 2 || hw_margin > 65535)
+ return -EINVAL;
+
+ priv->always_running = of_property_read_bool(np,
+ "always-running");
+
+ watchdog_set_drvdata(&priv->wdd, priv);
+
+ priv->wdd.info = &gpio_wdt_ident;
+ priv->wdd.ops = &gpio_wdt_ops;
+ priv->wdd.min_timeout = SOFT_TIMEOUT_MIN;
+ priv->wdd.max_hw_heartbeat_ms = hw_margin;
+ priv->wdd.parent = dev;
+ priv->wdd.timeout = SOFT_TIMEOUT_DEF;
+
+ watchdog_init_timeout(&priv->wdd, 0, &pdev->dev);
+
+ watchdog_stop_on_reboot(&priv->wdd);
+
+ if (priv->always_running)
+ gpio_wdt_start(&priv->wdd);
+
+ ret = watchdog_register_device(&priv->wdd);
+
+ return ret;
+}
+
+static int gpio_wdt_remove(struct platform_device *pdev)
+{
+ struct gpio_wdt_priv *priv = platform_get_drvdata(pdev);
+
+ watchdog_unregister_device(&priv->wdd);
+
+ return 0;
+}
+
+static const struct of_device_id gpio_wdt_dt_ids[] = {
+ { .compatible = "linux,wdt-gpio", },
+ { }
+};
+MODULE_DEVICE_TABLE(of, gpio_wdt_dt_ids);
+
+static struct platform_driver gpio_wdt_driver = {
+ .driver = {
+ .name = "gpio-wdt",
+ .of_match_table = gpio_wdt_dt_ids,
+ },
+ .probe = gpio_wdt_probe,
+ .remove = gpio_wdt_remove,
+};
+
+#ifdef CONFIG_GPIO_WATCHDOG_ARCH_INITCALL
+static int __init gpio_wdt_init(void)
+{
+ return platform_driver_register(&gpio_wdt_driver);
+}
+arch_initcall(gpio_wdt_init);
+#else
+module_platform_driver(gpio_wdt_driver);
+#endif
+
+MODULE_AUTHOR("Alexander Shiyan <shc_work@mail.ru>");
+MODULE_DESCRIPTION("GPIO Watchdog");
+MODULE_LICENSE("GPL");