13d3a6d18SAaro Koskinen /* 23d3a6d18SAaro Koskinen * Retu watchdog driver 33d3a6d18SAaro Koskinen * 43d3a6d18SAaro Koskinen * Copyright (C) 2004, 2005 Nokia Corporation 53d3a6d18SAaro Koskinen * 63d3a6d18SAaro Koskinen * Based on code written by Amit Kucheria and Michael Buesch. 73d3a6d18SAaro Koskinen * Rewritten by Aaro Koskinen. 83d3a6d18SAaro Koskinen * 93d3a6d18SAaro Koskinen * This file is subject to the terms and conditions of the GNU General 103d3a6d18SAaro Koskinen * Public License. See the file "COPYING" in the main directory of this 113d3a6d18SAaro Koskinen * archive for more details. 123d3a6d18SAaro Koskinen * 133d3a6d18SAaro Koskinen * This program is distributed in the hope that it will be useful, 143d3a6d18SAaro Koskinen * but WITHOUT ANY WARRANTY; without even the implied warranty of 153d3a6d18SAaro Koskinen * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 163d3a6d18SAaro Koskinen * GNU General Public License for more details. 173d3a6d18SAaro Koskinen */ 183d3a6d18SAaro Koskinen 193d3a6d18SAaro Koskinen #include <linux/slab.h> 203d3a6d18SAaro Koskinen #include <linux/errno.h> 213d3a6d18SAaro Koskinen #include <linux/device.h> 223d3a6d18SAaro Koskinen #include <linux/kernel.h> 233d3a6d18SAaro Koskinen #include <linux/module.h> 243d3a6d18SAaro Koskinen #include <linux/mfd/retu.h> 253d3a6d18SAaro Koskinen #include <linux/watchdog.h> 263d3a6d18SAaro Koskinen #include <linux/platform_device.h> 273d3a6d18SAaro Koskinen 283d3a6d18SAaro Koskinen /* Watchdog timer values in seconds */ 293d3a6d18SAaro Koskinen #define RETU_WDT_MAX_TIMER 63 303d3a6d18SAaro Koskinen 313d3a6d18SAaro Koskinen struct retu_wdt_dev { 323d3a6d18SAaro Koskinen struct retu_dev *rdev; 333d3a6d18SAaro Koskinen struct device *dev; 343d3a6d18SAaro Koskinen struct delayed_work ping_work; 353d3a6d18SAaro Koskinen }; 363d3a6d18SAaro Koskinen 373d3a6d18SAaro Koskinen /* 383d3a6d18SAaro Koskinen * Since Retu watchdog cannot be disabled in hardware, we must kick it 393d3a6d18SAaro Koskinen * with a timer until userspace watchdog software takes over. If 403d3a6d18SAaro Koskinen * CONFIG_WATCHDOG_NOWAYOUT is set, we never start the feeding. 413d3a6d18SAaro Koskinen */ 423d3a6d18SAaro Koskinen static void retu_wdt_ping_enable(struct retu_wdt_dev *wdev) 433d3a6d18SAaro Koskinen { 443d3a6d18SAaro Koskinen retu_write(wdev->rdev, RETU_REG_WATCHDOG, RETU_WDT_MAX_TIMER); 453d3a6d18SAaro Koskinen schedule_delayed_work(&wdev->ping_work, 463d3a6d18SAaro Koskinen round_jiffies_relative(RETU_WDT_MAX_TIMER * HZ / 2)); 473d3a6d18SAaro Koskinen } 483d3a6d18SAaro Koskinen 493d3a6d18SAaro Koskinen static void retu_wdt_ping_disable(struct retu_wdt_dev *wdev) 503d3a6d18SAaro Koskinen { 513d3a6d18SAaro Koskinen retu_write(wdev->rdev, RETU_REG_WATCHDOG, RETU_WDT_MAX_TIMER); 523d3a6d18SAaro Koskinen cancel_delayed_work_sync(&wdev->ping_work); 533d3a6d18SAaro Koskinen } 543d3a6d18SAaro Koskinen 553d3a6d18SAaro Koskinen static void retu_wdt_ping_work(struct work_struct *work) 563d3a6d18SAaro Koskinen { 573d3a6d18SAaro Koskinen struct retu_wdt_dev *wdev = container_of(to_delayed_work(work), 583d3a6d18SAaro Koskinen struct retu_wdt_dev, ping_work); 593d3a6d18SAaro Koskinen retu_wdt_ping_enable(wdev); 603d3a6d18SAaro Koskinen } 613d3a6d18SAaro Koskinen 623d3a6d18SAaro Koskinen static int retu_wdt_start(struct watchdog_device *wdog) 633d3a6d18SAaro Koskinen { 643d3a6d18SAaro Koskinen struct retu_wdt_dev *wdev = watchdog_get_drvdata(wdog); 653d3a6d18SAaro Koskinen 663d3a6d18SAaro Koskinen retu_wdt_ping_disable(wdev); 673d3a6d18SAaro Koskinen 683d3a6d18SAaro Koskinen return retu_write(wdev->rdev, RETU_REG_WATCHDOG, wdog->timeout); 693d3a6d18SAaro Koskinen } 703d3a6d18SAaro Koskinen 713d3a6d18SAaro Koskinen static int retu_wdt_stop(struct watchdog_device *wdog) 723d3a6d18SAaro Koskinen { 733d3a6d18SAaro Koskinen struct retu_wdt_dev *wdev = watchdog_get_drvdata(wdog); 743d3a6d18SAaro Koskinen 753d3a6d18SAaro Koskinen retu_wdt_ping_enable(wdev); 763d3a6d18SAaro Koskinen 773d3a6d18SAaro Koskinen return 0; 783d3a6d18SAaro Koskinen } 793d3a6d18SAaro Koskinen 803d3a6d18SAaro Koskinen static int retu_wdt_ping(struct watchdog_device *wdog) 813d3a6d18SAaro Koskinen { 823d3a6d18SAaro Koskinen struct retu_wdt_dev *wdev = watchdog_get_drvdata(wdog); 833d3a6d18SAaro Koskinen 843d3a6d18SAaro Koskinen return retu_write(wdev->rdev, RETU_REG_WATCHDOG, wdog->timeout); 853d3a6d18SAaro Koskinen } 863d3a6d18SAaro Koskinen 873d3a6d18SAaro Koskinen static int retu_wdt_set_timeout(struct watchdog_device *wdog, 883d3a6d18SAaro Koskinen unsigned int timeout) 893d3a6d18SAaro Koskinen { 903d3a6d18SAaro Koskinen struct retu_wdt_dev *wdev = watchdog_get_drvdata(wdog); 913d3a6d18SAaro Koskinen 923d3a6d18SAaro Koskinen wdog->timeout = timeout; 933d3a6d18SAaro Koskinen return retu_write(wdev->rdev, RETU_REG_WATCHDOG, wdog->timeout); 943d3a6d18SAaro Koskinen } 953d3a6d18SAaro Koskinen 963d3a6d18SAaro Koskinen static const struct watchdog_info retu_wdt_info = { 97fb1cbeaeSTony Lindgren .options = WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE | WDIOF_KEEPALIVEPING, 983d3a6d18SAaro Koskinen .identity = "Retu watchdog", 993d3a6d18SAaro Koskinen }; 1003d3a6d18SAaro Koskinen 1013d3a6d18SAaro Koskinen static const struct watchdog_ops retu_wdt_ops = { 1023d3a6d18SAaro Koskinen .owner = THIS_MODULE, 1033d3a6d18SAaro Koskinen .start = retu_wdt_start, 1043d3a6d18SAaro Koskinen .stop = retu_wdt_stop, 1053d3a6d18SAaro Koskinen .ping = retu_wdt_ping, 1063d3a6d18SAaro Koskinen .set_timeout = retu_wdt_set_timeout, 1073d3a6d18SAaro Koskinen }; 1083d3a6d18SAaro Koskinen 1093d3a6d18SAaro Koskinen static int retu_wdt_probe(struct platform_device *pdev) 1103d3a6d18SAaro Koskinen { 1113d3a6d18SAaro Koskinen struct retu_dev *rdev = dev_get_drvdata(pdev->dev.parent); 1123d3a6d18SAaro Koskinen bool nowayout = WATCHDOG_NOWAYOUT; 1133d3a6d18SAaro Koskinen struct watchdog_device *retu_wdt; 1143d3a6d18SAaro Koskinen struct retu_wdt_dev *wdev; 1153d3a6d18SAaro Koskinen int ret; 1163d3a6d18SAaro Koskinen 1173d3a6d18SAaro Koskinen retu_wdt = devm_kzalloc(&pdev->dev, sizeof(*retu_wdt), GFP_KERNEL); 1183d3a6d18SAaro Koskinen if (!retu_wdt) 1193d3a6d18SAaro Koskinen return -ENOMEM; 1203d3a6d18SAaro Koskinen 1213d3a6d18SAaro Koskinen wdev = devm_kzalloc(&pdev->dev, sizeof(*wdev), GFP_KERNEL); 1223d3a6d18SAaro Koskinen if (!wdev) 1233d3a6d18SAaro Koskinen return -ENOMEM; 1243d3a6d18SAaro Koskinen 1253d3a6d18SAaro Koskinen retu_wdt->info = &retu_wdt_info; 1263d3a6d18SAaro Koskinen retu_wdt->ops = &retu_wdt_ops; 1273d3a6d18SAaro Koskinen retu_wdt->timeout = RETU_WDT_MAX_TIMER; 1283d3a6d18SAaro Koskinen retu_wdt->min_timeout = 0; 1293d3a6d18SAaro Koskinen retu_wdt->max_timeout = RETU_WDT_MAX_TIMER; 130*6551881cSPratyush Anand retu_wdt->parent = &pdev->dev; 1313d3a6d18SAaro Koskinen 1323d3a6d18SAaro Koskinen watchdog_set_drvdata(retu_wdt, wdev); 1333d3a6d18SAaro Koskinen watchdog_set_nowayout(retu_wdt, nowayout); 1343d3a6d18SAaro Koskinen 1353d3a6d18SAaro Koskinen wdev->rdev = rdev; 1363d3a6d18SAaro Koskinen wdev->dev = &pdev->dev; 1373d3a6d18SAaro Koskinen 1383d3a6d18SAaro Koskinen INIT_DELAYED_WORK(&wdev->ping_work, retu_wdt_ping_work); 1393d3a6d18SAaro Koskinen 1403d3a6d18SAaro Koskinen ret = watchdog_register_device(retu_wdt); 1413d3a6d18SAaro Koskinen if (ret < 0) 1423d3a6d18SAaro Koskinen return ret; 1433d3a6d18SAaro Koskinen 1443d3a6d18SAaro Koskinen if (nowayout) 1453d3a6d18SAaro Koskinen retu_wdt_ping(retu_wdt); 1463d3a6d18SAaro Koskinen else 1473d3a6d18SAaro Koskinen retu_wdt_ping_enable(wdev); 1483d3a6d18SAaro Koskinen 1493d3a6d18SAaro Koskinen platform_set_drvdata(pdev, retu_wdt); 1503d3a6d18SAaro Koskinen 1513d3a6d18SAaro Koskinen return 0; 1523d3a6d18SAaro Koskinen } 1533d3a6d18SAaro Koskinen 1543d3a6d18SAaro Koskinen static int retu_wdt_remove(struct platform_device *pdev) 1553d3a6d18SAaro Koskinen { 1563d3a6d18SAaro Koskinen struct watchdog_device *wdog = platform_get_drvdata(pdev); 1573d3a6d18SAaro Koskinen struct retu_wdt_dev *wdev = watchdog_get_drvdata(wdog); 1583d3a6d18SAaro Koskinen 1593d3a6d18SAaro Koskinen watchdog_unregister_device(wdog); 1603d3a6d18SAaro Koskinen cancel_delayed_work_sync(&wdev->ping_work); 1613d3a6d18SAaro Koskinen 1623d3a6d18SAaro Koskinen return 0; 1633d3a6d18SAaro Koskinen } 1643d3a6d18SAaro Koskinen 1653d3a6d18SAaro Koskinen static struct platform_driver retu_wdt_driver = { 1663d3a6d18SAaro Koskinen .probe = retu_wdt_probe, 1673d3a6d18SAaro Koskinen .remove = retu_wdt_remove, 1683d3a6d18SAaro Koskinen .driver = { 1693d3a6d18SAaro Koskinen .name = "retu-wdt", 1703d3a6d18SAaro Koskinen }, 1713d3a6d18SAaro Koskinen }; 1723d3a6d18SAaro Koskinen module_platform_driver(retu_wdt_driver); 1733d3a6d18SAaro Koskinen 1743d3a6d18SAaro Koskinen MODULE_ALIAS("platform:retu-wdt"); 1753d3a6d18SAaro Koskinen MODULE_DESCRIPTION("Retu watchdog"); 1763d3a6d18SAaro Koskinen MODULE_AUTHOR("Amit Kucheria"); 1773d3a6d18SAaro Koskinen MODULE_AUTHOR("Aaro Koskinen <aaro.koskinen@iki.fi>"); 1783d3a6d18SAaro Koskinen MODULE_LICENSE("GPL"); 179