1d0173278SGuenter Roeck // SPDX-License-Identifier: GPL-2.0+ 23d3a6d18SAaro Koskinen /* 33d3a6d18SAaro Koskinen * Retu watchdog driver 43d3a6d18SAaro Koskinen * 53d3a6d18SAaro Koskinen * Copyright (C) 2004, 2005 Nokia Corporation 63d3a6d18SAaro Koskinen * 73d3a6d18SAaro Koskinen * Based on code written by Amit Kucheria and Michael Buesch. 83d3a6d18SAaro Koskinen * Rewritten by Aaro Koskinen. 93d3a6d18SAaro Koskinen */ 103d3a6d18SAaro Koskinen 11*a7d30f3fSMatti Vaittinen #include <linux/devm-helpers.h> 123d3a6d18SAaro Koskinen #include <linux/slab.h> 133d3a6d18SAaro Koskinen #include <linux/errno.h> 143d3a6d18SAaro Koskinen #include <linux/device.h> 153d3a6d18SAaro Koskinen #include <linux/kernel.h> 163d3a6d18SAaro Koskinen #include <linux/module.h> 173d3a6d18SAaro Koskinen #include <linux/mfd/retu.h> 183d3a6d18SAaro Koskinen #include <linux/watchdog.h> 193d3a6d18SAaro Koskinen #include <linux/platform_device.h> 203d3a6d18SAaro Koskinen 213d3a6d18SAaro Koskinen /* Watchdog timer values in seconds */ 223d3a6d18SAaro Koskinen #define RETU_WDT_MAX_TIMER 63 233d3a6d18SAaro Koskinen 243d3a6d18SAaro Koskinen struct retu_wdt_dev { 253d3a6d18SAaro Koskinen struct retu_dev *rdev; 263d3a6d18SAaro Koskinen struct device *dev; 273d3a6d18SAaro Koskinen struct delayed_work ping_work; 283d3a6d18SAaro Koskinen }; 293d3a6d18SAaro Koskinen 303d3a6d18SAaro Koskinen /* 313d3a6d18SAaro Koskinen * Since Retu watchdog cannot be disabled in hardware, we must kick it 323d3a6d18SAaro Koskinen * with a timer until userspace watchdog software takes over. If 333d3a6d18SAaro Koskinen * CONFIG_WATCHDOG_NOWAYOUT is set, we never start the feeding. 343d3a6d18SAaro Koskinen */ 353d3a6d18SAaro Koskinen static void retu_wdt_ping_enable(struct retu_wdt_dev *wdev) 363d3a6d18SAaro Koskinen { 373d3a6d18SAaro Koskinen retu_write(wdev->rdev, RETU_REG_WATCHDOG, RETU_WDT_MAX_TIMER); 383d3a6d18SAaro Koskinen schedule_delayed_work(&wdev->ping_work, 393d3a6d18SAaro Koskinen round_jiffies_relative(RETU_WDT_MAX_TIMER * HZ / 2)); 403d3a6d18SAaro Koskinen } 413d3a6d18SAaro Koskinen 423d3a6d18SAaro Koskinen static void retu_wdt_ping_disable(struct retu_wdt_dev *wdev) 433d3a6d18SAaro Koskinen { 443d3a6d18SAaro Koskinen retu_write(wdev->rdev, RETU_REG_WATCHDOG, RETU_WDT_MAX_TIMER); 453d3a6d18SAaro Koskinen cancel_delayed_work_sync(&wdev->ping_work); 463d3a6d18SAaro Koskinen } 473d3a6d18SAaro Koskinen 483d3a6d18SAaro Koskinen static void retu_wdt_ping_work(struct work_struct *work) 493d3a6d18SAaro Koskinen { 503d3a6d18SAaro Koskinen struct retu_wdt_dev *wdev = container_of(to_delayed_work(work), 513d3a6d18SAaro Koskinen struct retu_wdt_dev, ping_work); 523d3a6d18SAaro Koskinen retu_wdt_ping_enable(wdev); 533d3a6d18SAaro Koskinen } 543d3a6d18SAaro Koskinen 553d3a6d18SAaro Koskinen static int retu_wdt_start(struct watchdog_device *wdog) 563d3a6d18SAaro Koskinen { 573d3a6d18SAaro Koskinen struct retu_wdt_dev *wdev = watchdog_get_drvdata(wdog); 583d3a6d18SAaro Koskinen 593d3a6d18SAaro Koskinen retu_wdt_ping_disable(wdev); 603d3a6d18SAaro Koskinen 613d3a6d18SAaro Koskinen return retu_write(wdev->rdev, RETU_REG_WATCHDOG, wdog->timeout); 623d3a6d18SAaro Koskinen } 633d3a6d18SAaro Koskinen 643d3a6d18SAaro Koskinen static int retu_wdt_stop(struct watchdog_device *wdog) 653d3a6d18SAaro Koskinen { 663d3a6d18SAaro Koskinen struct retu_wdt_dev *wdev = watchdog_get_drvdata(wdog); 673d3a6d18SAaro Koskinen 683d3a6d18SAaro Koskinen retu_wdt_ping_enable(wdev); 693d3a6d18SAaro Koskinen 703d3a6d18SAaro Koskinen return 0; 713d3a6d18SAaro Koskinen } 723d3a6d18SAaro Koskinen 733d3a6d18SAaro Koskinen static int retu_wdt_ping(struct watchdog_device *wdog) 743d3a6d18SAaro Koskinen { 753d3a6d18SAaro Koskinen struct retu_wdt_dev *wdev = watchdog_get_drvdata(wdog); 763d3a6d18SAaro Koskinen 773d3a6d18SAaro Koskinen return retu_write(wdev->rdev, RETU_REG_WATCHDOG, wdog->timeout); 783d3a6d18SAaro Koskinen } 793d3a6d18SAaro Koskinen 803d3a6d18SAaro Koskinen static int retu_wdt_set_timeout(struct watchdog_device *wdog, 813d3a6d18SAaro Koskinen unsigned int timeout) 823d3a6d18SAaro Koskinen { 833d3a6d18SAaro Koskinen struct retu_wdt_dev *wdev = watchdog_get_drvdata(wdog); 843d3a6d18SAaro Koskinen 853d3a6d18SAaro Koskinen wdog->timeout = timeout; 863d3a6d18SAaro Koskinen return retu_write(wdev->rdev, RETU_REG_WATCHDOG, wdog->timeout); 873d3a6d18SAaro Koskinen } 883d3a6d18SAaro Koskinen 893d3a6d18SAaro Koskinen static const struct watchdog_info retu_wdt_info = { 90fb1cbeaeSTony Lindgren .options = WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE | WDIOF_KEEPALIVEPING, 913d3a6d18SAaro Koskinen .identity = "Retu watchdog", 923d3a6d18SAaro Koskinen }; 933d3a6d18SAaro Koskinen 943d3a6d18SAaro Koskinen static const struct watchdog_ops retu_wdt_ops = { 953d3a6d18SAaro Koskinen .owner = THIS_MODULE, 963d3a6d18SAaro Koskinen .start = retu_wdt_start, 973d3a6d18SAaro Koskinen .stop = retu_wdt_stop, 983d3a6d18SAaro Koskinen .ping = retu_wdt_ping, 993d3a6d18SAaro Koskinen .set_timeout = retu_wdt_set_timeout, 1003d3a6d18SAaro Koskinen }; 1013d3a6d18SAaro Koskinen 1023d3a6d18SAaro Koskinen static int retu_wdt_probe(struct platform_device *pdev) 1033d3a6d18SAaro Koskinen { 1043d3a6d18SAaro Koskinen struct retu_dev *rdev = dev_get_drvdata(pdev->dev.parent); 1053d3a6d18SAaro Koskinen bool nowayout = WATCHDOG_NOWAYOUT; 1063d3a6d18SAaro Koskinen struct watchdog_device *retu_wdt; 1073d3a6d18SAaro Koskinen struct retu_wdt_dev *wdev; 1083d3a6d18SAaro Koskinen int ret; 1093d3a6d18SAaro Koskinen 1103d3a6d18SAaro Koskinen retu_wdt = devm_kzalloc(&pdev->dev, sizeof(*retu_wdt), GFP_KERNEL); 1113d3a6d18SAaro Koskinen if (!retu_wdt) 1123d3a6d18SAaro Koskinen return -ENOMEM; 1133d3a6d18SAaro Koskinen 1143d3a6d18SAaro Koskinen wdev = devm_kzalloc(&pdev->dev, sizeof(*wdev), GFP_KERNEL); 1153d3a6d18SAaro Koskinen if (!wdev) 1163d3a6d18SAaro Koskinen return -ENOMEM; 1173d3a6d18SAaro Koskinen 1183d3a6d18SAaro Koskinen retu_wdt->info = &retu_wdt_info; 1193d3a6d18SAaro Koskinen retu_wdt->ops = &retu_wdt_ops; 1203d3a6d18SAaro Koskinen retu_wdt->timeout = RETU_WDT_MAX_TIMER; 1213d3a6d18SAaro Koskinen retu_wdt->min_timeout = 0; 1223d3a6d18SAaro Koskinen retu_wdt->max_timeout = RETU_WDT_MAX_TIMER; 1236551881cSPratyush Anand retu_wdt->parent = &pdev->dev; 1243d3a6d18SAaro Koskinen 1253d3a6d18SAaro Koskinen watchdog_set_drvdata(retu_wdt, wdev); 1263d3a6d18SAaro Koskinen watchdog_set_nowayout(retu_wdt, nowayout); 1273d3a6d18SAaro Koskinen 1283d3a6d18SAaro Koskinen wdev->rdev = rdev; 1293d3a6d18SAaro Koskinen wdev->dev = &pdev->dev; 1303d3a6d18SAaro Koskinen 131*a7d30f3fSMatti Vaittinen ret = devm_delayed_work_autocancel(&pdev->dev, &wdev->ping_work, 132*a7d30f3fSMatti Vaittinen retu_wdt_ping_work); 133*a7d30f3fSMatti Vaittinen if (ret) 134*a7d30f3fSMatti Vaittinen return ret; 1353d3a6d18SAaro Koskinen 136*a7d30f3fSMatti Vaittinen ret = devm_watchdog_register_device(&pdev->dev, retu_wdt); 1373d3a6d18SAaro Koskinen if (ret < 0) 1383d3a6d18SAaro Koskinen return ret; 1393d3a6d18SAaro Koskinen 1403d3a6d18SAaro Koskinen if (nowayout) 1413d3a6d18SAaro Koskinen retu_wdt_ping(retu_wdt); 1423d3a6d18SAaro Koskinen else 1433d3a6d18SAaro Koskinen retu_wdt_ping_enable(wdev); 1443d3a6d18SAaro Koskinen 1453d3a6d18SAaro Koskinen return 0; 1463d3a6d18SAaro Koskinen } 1473d3a6d18SAaro Koskinen 1483d3a6d18SAaro Koskinen static struct platform_driver retu_wdt_driver = { 1493d3a6d18SAaro Koskinen .probe = retu_wdt_probe, 1503d3a6d18SAaro Koskinen .driver = { 1513d3a6d18SAaro Koskinen .name = "retu-wdt", 1523d3a6d18SAaro Koskinen }, 1533d3a6d18SAaro Koskinen }; 1543d3a6d18SAaro Koskinen module_platform_driver(retu_wdt_driver); 1553d3a6d18SAaro Koskinen 1563d3a6d18SAaro Koskinen MODULE_ALIAS("platform:retu-wdt"); 1573d3a6d18SAaro Koskinen MODULE_DESCRIPTION("Retu watchdog"); 1583d3a6d18SAaro Koskinen MODULE_AUTHOR("Amit Kucheria"); 1593d3a6d18SAaro Koskinen MODULE_AUTHOR("Aaro Koskinen <aaro.koskinen@iki.fi>"); 1603d3a6d18SAaro Koskinen MODULE_LICENSE("GPL"); 161