12e62c498SMarcus Folkesson // SPDX-License-Identifier: GPL-2.0+
226c57ef1SJohannes Thumshirn /*
326c57ef1SJohannes Thumshirn * Watchdog driver for the A21 VME CPU Boards
426c57ef1SJohannes Thumshirn *
526c57ef1SJohannes Thumshirn * Copyright (C) 2013 MEN Mikro Elektronik Nuernberg GmbH
626c57ef1SJohannes Thumshirn *
726c57ef1SJohannes Thumshirn */
826c57ef1SJohannes Thumshirn #include <linux/module.h>
926c57ef1SJohannes Thumshirn #include <linux/moduleparam.h>
1026c57ef1SJohannes Thumshirn #include <linux/types.h>
1126c57ef1SJohannes Thumshirn #include <linux/kernel.h>
1226c57ef1SJohannes Thumshirn #include <linux/slab.h>
1326c57ef1SJohannes Thumshirn #include <linux/platform_device.h>
1426c57ef1SJohannes Thumshirn #include <linux/watchdog.h>
1526c57ef1SJohannes Thumshirn #include <linux/uaccess.h>
1622ec9bb1SLinus Walleij #include <linux/gpio/consumer.h>
1726c57ef1SJohannes Thumshirn #include <linux/delay.h>
1826c57ef1SJohannes Thumshirn #include <linux/bitops.h>
1922ec9bb1SLinus Walleij #include <linux/of.h>
2026c57ef1SJohannes Thumshirn
2126c57ef1SJohannes Thumshirn #define NUM_GPIOS 6
2226c57ef1SJohannes Thumshirn
2326c57ef1SJohannes Thumshirn enum a21_wdt_gpios {
2426c57ef1SJohannes Thumshirn GPIO_WD_ENAB,
2526c57ef1SJohannes Thumshirn GPIO_WD_FAST,
2626c57ef1SJohannes Thumshirn GPIO_WD_TRIG,
2726c57ef1SJohannes Thumshirn GPIO_WD_RST0,
2826c57ef1SJohannes Thumshirn GPIO_WD_RST1,
2926c57ef1SJohannes Thumshirn GPIO_WD_RST2,
3026c57ef1SJohannes Thumshirn };
3126c57ef1SJohannes Thumshirn
3226c57ef1SJohannes Thumshirn struct a21_wdt_drv {
3326c57ef1SJohannes Thumshirn struct watchdog_device wdt;
3422ec9bb1SLinus Walleij struct gpio_desc *gpios[NUM_GPIOS];
3526c57ef1SJohannes Thumshirn };
3626c57ef1SJohannes Thumshirn
3726c57ef1SJohannes Thumshirn static bool nowayout = WATCHDOG_NOWAYOUT;
3826c57ef1SJohannes Thumshirn module_param(nowayout, bool, 0);
3926c57ef1SJohannes Thumshirn MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default="
4026c57ef1SJohannes Thumshirn __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
4126c57ef1SJohannes Thumshirn
a21_wdt_get_bootstatus(struct a21_wdt_drv * drv)4226c57ef1SJohannes Thumshirn static unsigned int a21_wdt_get_bootstatus(struct a21_wdt_drv *drv)
4326c57ef1SJohannes Thumshirn {
4426c57ef1SJohannes Thumshirn int reset = 0;
4526c57ef1SJohannes Thumshirn
4622ec9bb1SLinus Walleij reset |= gpiod_get_value(drv->gpios[GPIO_WD_RST0]) ? (1 << 0) : 0;
4722ec9bb1SLinus Walleij reset |= gpiod_get_value(drv->gpios[GPIO_WD_RST1]) ? (1 << 1) : 0;
4822ec9bb1SLinus Walleij reset |= gpiod_get_value(drv->gpios[GPIO_WD_RST2]) ? (1 << 2) : 0;
4926c57ef1SJohannes Thumshirn
5026c57ef1SJohannes Thumshirn return reset;
5126c57ef1SJohannes Thumshirn }
5226c57ef1SJohannes Thumshirn
a21_wdt_start(struct watchdog_device * wdt)5326c57ef1SJohannes Thumshirn static int a21_wdt_start(struct watchdog_device *wdt)
5426c57ef1SJohannes Thumshirn {
5526c57ef1SJohannes Thumshirn struct a21_wdt_drv *drv = watchdog_get_drvdata(wdt);
5626c57ef1SJohannes Thumshirn
5722ec9bb1SLinus Walleij gpiod_set_value(drv->gpios[GPIO_WD_ENAB], 1);
5826c57ef1SJohannes Thumshirn
5926c57ef1SJohannes Thumshirn return 0;
6026c57ef1SJohannes Thumshirn }
6126c57ef1SJohannes Thumshirn
a21_wdt_stop(struct watchdog_device * wdt)6226c57ef1SJohannes Thumshirn static int a21_wdt_stop(struct watchdog_device *wdt)
6326c57ef1SJohannes Thumshirn {
6426c57ef1SJohannes Thumshirn struct a21_wdt_drv *drv = watchdog_get_drvdata(wdt);
6526c57ef1SJohannes Thumshirn
6622ec9bb1SLinus Walleij gpiod_set_value(drv->gpios[GPIO_WD_ENAB], 0);
6726c57ef1SJohannes Thumshirn
6826c57ef1SJohannes Thumshirn return 0;
6926c57ef1SJohannes Thumshirn }
7026c57ef1SJohannes Thumshirn
a21_wdt_ping(struct watchdog_device * wdt)7126c57ef1SJohannes Thumshirn static int a21_wdt_ping(struct watchdog_device *wdt)
7226c57ef1SJohannes Thumshirn {
7326c57ef1SJohannes Thumshirn struct a21_wdt_drv *drv = watchdog_get_drvdata(wdt);
7426c57ef1SJohannes Thumshirn
7522ec9bb1SLinus Walleij gpiod_set_value(drv->gpios[GPIO_WD_TRIG], 0);
7626c57ef1SJohannes Thumshirn ndelay(10);
7722ec9bb1SLinus Walleij gpiod_set_value(drv->gpios[GPIO_WD_TRIG], 1);
7826c57ef1SJohannes Thumshirn
7926c57ef1SJohannes Thumshirn return 0;
8026c57ef1SJohannes Thumshirn }
8126c57ef1SJohannes Thumshirn
a21_wdt_set_timeout(struct watchdog_device * wdt,unsigned int timeout)8226c57ef1SJohannes Thumshirn static int a21_wdt_set_timeout(struct watchdog_device *wdt,
8326c57ef1SJohannes Thumshirn unsigned int timeout)
8426c57ef1SJohannes Thumshirn {
8526c57ef1SJohannes Thumshirn struct a21_wdt_drv *drv = watchdog_get_drvdata(wdt);
8626c57ef1SJohannes Thumshirn
8726c57ef1SJohannes Thumshirn if (timeout != 1 && timeout != 30) {
8807352366SGuenter Roeck dev_err(wdt->parent, "Only 1 and 30 allowed as timeout\n");
8926c57ef1SJohannes Thumshirn return -EINVAL;
9026c57ef1SJohannes Thumshirn }
9126c57ef1SJohannes Thumshirn
9226c57ef1SJohannes Thumshirn if (timeout == 30 && wdt->timeout == 1) {
9307352366SGuenter Roeck dev_err(wdt->parent,
9426c57ef1SJohannes Thumshirn "Transition from fast to slow mode not allowed\n");
9526c57ef1SJohannes Thumshirn return -EINVAL;
9626c57ef1SJohannes Thumshirn }
9726c57ef1SJohannes Thumshirn
9826c57ef1SJohannes Thumshirn if (timeout == 1)
9922ec9bb1SLinus Walleij gpiod_set_value(drv->gpios[GPIO_WD_FAST], 1);
10026c57ef1SJohannes Thumshirn else
10122ec9bb1SLinus Walleij gpiod_set_value(drv->gpios[GPIO_WD_FAST], 0);
10226c57ef1SJohannes Thumshirn
10326c57ef1SJohannes Thumshirn wdt->timeout = timeout;
10426c57ef1SJohannes Thumshirn
10526c57ef1SJohannes Thumshirn return 0;
10626c57ef1SJohannes Thumshirn }
10726c57ef1SJohannes Thumshirn
10826c57ef1SJohannes Thumshirn static const struct watchdog_info a21_wdt_info = {
10926c57ef1SJohannes Thumshirn .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
11026c57ef1SJohannes Thumshirn .identity = "MEN A21 Watchdog",
11126c57ef1SJohannes Thumshirn };
11226c57ef1SJohannes Thumshirn
11326c57ef1SJohannes Thumshirn static const struct watchdog_ops a21_wdt_ops = {
11426c57ef1SJohannes Thumshirn .owner = THIS_MODULE,
11526c57ef1SJohannes Thumshirn .start = a21_wdt_start,
11626c57ef1SJohannes Thumshirn .stop = a21_wdt_stop,
11726c57ef1SJohannes Thumshirn .ping = a21_wdt_ping,
11826c57ef1SJohannes Thumshirn .set_timeout = a21_wdt_set_timeout,
11926c57ef1SJohannes Thumshirn };
12026c57ef1SJohannes Thumshirn
12126c57ef1SJohannes Thumshirn static struct watchdog_device a21_wdt = {
12226c57ef1SJohannes Thumshirn .info = &a21_wdt_info,
12326c57ef1SJohannes Thumshirn .ops = &a21_wdt_ops,
12426c57ef1SJohannes Thumshirn .min_timeout = 1,
12526c57ef1SJohannes Thumshirn .max_timeout = 30,
12626c57ef1SJohannes Thumshirn };
12726c57ef1SJohannes Thumshirn
a21_wdt_probe(struct platform_device * pdev)12826c57ef1SJohannes Thumshirn static int a21_wdt_probe(struct platform_device *pdev)
12926c57ef1SJohannes Thumshirn {
13094ac20d8SGuenter Roeck struct device *dev = &pdev->dev;
13126c57ef1SJohannes Thumshirn struct a21_wdt_drv *drv;
13226c57ef1SJohannes Thumshirn unsigned int reset = 0;
13326c57ef1SJohannes Thumshirn int num_gpios;
13426c57ef1SJohannes Thumshirn int ret;
13526c57ef1SJohannes Thumshirn int i;
13626c57ef1SJohannes Thumshirn
13794ac20d8SGuenter Roeck drv = devm_kzalloc(dev, sizeof(struct a21_wdt_drv), GFP_KERNEL);
13826c57ef1SJohannes Thumshirn if (!drv)
13926c57ef1SJohannes Thumshirn return -ENOMEM;
14026c57ef1SJohannes Thumshirn
14194ac20d8SGuenter Roeck num_gpios = gpiod_count(dev, NULL);
14226c57ef1SJohannes Thumshirn if (num_gpios != NUM_GPIOS) {
14394ac20d8SGuenter Roeck dev_err(dev, "gpios DT property wrong, got %d want %d",
14426c57ef1SJohannes Thumshirn num_gpios, NUM_GPIOS);
14526c57ef1SJohannes Thumshirn return -ENODEV;
14626c57ef1SJohannes Thumshirn }
14726c57ef1SJohannes Thumshirn
14826c57ef1SJohannes Thumshirn /* Request the used GPIOs */
14926c57ef1SJohannes Thumshirn for (i = 0; i < num_gpios; i++) {
15022ec9bb1SLinus Walleij enum gpiod_flags gflags;
15126c57ef1SJohannes Thumshirn
15226c57ef1SJohannes Thumshirn if (i < GPIO_WD_RST0)
15322ec9bb1SLinus Walleij gflags = GPIOD_ASIS;
15422ec9bb1SLinus Walleij else
15522ec9bb1SLinus Walleij gflags = GPIOD_IN;
15694ac20d8SGuenter Roeck drv->gpios[i] = devm_gpiod_get_index(dev, NULL, i, gflags);
15794ac20d8SGuenter Roeck if (IS_ERR(drv->gpios[i]))
15894ac20d8SGuenter Roeck return PTR_ERR(drv->gpios[i]);
15926c57ef1SJohannes Thumshirn
16022ec9bb1SLinus Walleij gpiod_set_consumer_name(drv->gpios[i], "MEN A21 Watchdog");
16122ec9bb1SLinus Walleij
16222ec9bb1SLinus Walleij /*
16322ec9bb1SLinus Walleij * Retrieve the initial value from the GPIOs that should be
16422ec9bb1SLinus Walleij * output, then set up the line as output with that value.
16522ec9bb1SLinus Walleij */
16622ec9bb1SLinus Walleij if (i < GPIO_WD_RST0) {
16722ec9bb1SLinus Walleij int val;
16822ec9bb1SLinus Walleij
16922ec9bb1SLinus Walleij val = gpiod_get_value(drv->gpios[i]);
17022ec9bb1SLinus Walleij gpiod_direction_output(drv->gpios[i], val);
17122ec9bb1SLinus Walleij }
17222ec9bb1SLinus Walleij }
17322ec9bb1SLinus Walleij
17494ac20d8SGuenter Roeck watchdog_init_timeout(&a21_wdt, 30, dev);
17526c57ef1SJohannes Thumshirn watchdog_set_nowayout(&a21_wdt, nowayout);
17626c57ef1SJohannes Thumshirn watchdog_set_drvdata(&a21_wdt, drv);
17794ac20d8SGuenter Roeck a21_wdt.parent = dev;
17826c57ef1SJohannes Thumshirn
17926c57ef1SJohannes Thumshirn reset = a21_wdt_get_bootstatus(drv);
18026c57ef1SJohannes Thumshirn if (reset == 2)
18126c57ef1SJohannes Thumshirn a21_wdt.bootstatus |= WDIOF_EXTERN1;
18226c57ef1SJohannes Thumshirn else if (reset == 4)
18326c57ef1SJohannes Thumshirn a21_wdt.bootstatus |= WDIOF_CARDRESET;
18426c57ef1SJohannes Thumshirn else if (reset == 5)
18526c57ef1SJohannes Thumshirn a21_wdt.bootstatus |= WDIOF_POWERUNDER;
18626c57ef1SJohannes Thumshirn else if (reset == 7)
18726c57ef1SJohannes Thumshirn a21_wdt.bootstatus |= WDIOF_EXTERN2;
18826c57ef1SJohannes Thumshirn
18957337db1SJohannes Thumshirn drv->wdt = a21_wdt;
19094ac20d8SGuenter Roeck dev_set_drvdata(dev, drv);
19157337db1SJohannes Thumshirn
19294ac20d8SGuenter Roeck ret = devm_watchdog_register_device(dev, &a21_wdt);
193*eddeb07bSWolfram Sang if (ret)
19426c57ef1SJohannes Thumshirn return ret;
19526c57ef1SJohannes Thumshirn
19694ac20d8SGuenter Roeck dev_info(dev, "MEN A21 watchdog timer driver enabled\n");
19726c57ef1SJohannes Thumshirn
19826c57ef1SJohannes Thumshirn return 0;
19926c57ef1SJohannes Thumshirn }
20026c57ef1SJohannes Thumshirn
a21_wdt_shutdown(struct platform_device * pdev)20126c57ef1SJohannes Thumshirn static void a21_wdt_shutdown(struct platform_device *pdev)
20226c57ef1SJohannes Thumshirn {
20326c57ef1SJohannes Thumshirn struct a21_wdt_drv *drv = dev_get_drvdata(&pdev->dev);
20426c57ef1SJohannes Thumshirn
20522ec9bb1SLinus Walleij gpiod_set_value(drv->gpios[GPIO_WD_ENAB], 0);
20626c57ef1SJohannes Thumshirn }
20726c57ef1SJohannes Thumshirn
20826c57ef1SJohannes Thumshirn static const struct of_device_id a21_wdt_ids[] = {
20926c57ef1SJohannes Thumshirn { .compatible = "men,a021-wdt" },
21026c57ef1SJohannes Thumshirn { },
21126c57ef1SJohannes Thumshirn };
212c73318f4SLuis de Bethencourt MODULE_DEVICE_TABLE(of, a21_wdt_ids);
21326c57ef1SJohannes Thumshirn
21426c57ef1SJohannes Thumshirn static struct platform_driver a21_wdt_driver = {
21526c57ef1SJohannes Thumshirn .probe = a21_wdt_probe,
21626c57ef1SJohannes Thumshirn .shutdown = a21_wdt_shutdown,
21726c57ef1SJohannes Thumshirn .driver = {
21826c57ef1SJohannes Thumshirn .name = "a21-watchdog",
21926c57ef1SJohannes Thumshirn .of_match_table = a21_wdt_ids,
22026c57ef1SJohannes Thumshirn },
22126c57ef1SJohannes Thumshirn };
22226c57ef1SJohannes Thumshirn
22326c57ef1SJohannes Thumshirn module_platform_driver(a21_wdt_driver);
22426c57ef1SJohannes Thumshirn
22526c57ef1SJohannes Thumshirn MODULE_AUTHOR("MEN Mikro Elektronik");
22626c57ef1SJohannes Thumshirn MODULE_DESCRIPTION("MEN A21 Watchdog");
22726c57ef1SJohannes Thumshirn MODULE_LICENSE("GPL");
22826c57ef1SJohannes Thumshirn MODULE_ALIAS("platform:a21-watchdog");
229