234 lines · c
1// SPDX-License-Identifier: GPL-2.0+2/*3 * Watchdog driver for Broadcom BCM47XX4 *5 * Copyright (C) 2008 Aleksandar Radovanovic <biblbroks@sezampro.rs>6 * Copyright (C) 2009 Matthieu CASTET <castet.matthieu@free.fr>7 * Copyright (C) 2012-2013 Hauke Mehrtens <hauke@hauke-m.de>8 *9 */10 11#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt12 13#include <linux/bcm47xx_wdt.h>14#include <linux/bitops.h>15#include <linux/errno.h>16#include <linux/kernel.h>17#include <linux/module.h>18#include <linux/moduleparam.h>19#include <linux/platform_device.h>20#include <linux/types.h>21#include <linux/watchdog.h>22#include <linux/timer.h>23#include <linux/jiffies.h>24 25#define DRV_NAME "bcm47xx_wdt"26 27#define WDT_DEFAULT_TIME 30 /* seconds */28#define WDT_SOFTTIMER_MAX 255 /* seconds */29#define WDT_SOFTTIMER_THRESHOLD 60 /* seconds */30 31static int timeout = WDT_DEFAULT_TIME;32static bool nowayout = WATCHDOG_NOWAYOUT;33 34module_param(timeout, int, 0);35MODULE_PARM_DESC(timeout, "Watchdog time in seconds. (default="36 __MODULE_STRING(WDT_DEFAULT_TIME) ")");37 38module_param(nowayout, bool, 0);39MODULE_PARM_DESC(nowayout,40 "Watchdog cannot be stopped once started (default="41 __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");42 43static inline struct bcm47xx_wdt *bcm47xx_wdt_get(struct watchdog_device *wdd)44{45 return container_of(wdd, struct bcm47xx_wdt, wdd);46}47 48static int bcm47xx_wdt_hard_keepalive(struct watchdog_device *wdd)49{50 struct bcm47xx_wdt *wdt = bcm47xx_wdt_get(wdd);51 52 wdt->timer_set_ms(wdt, wdd->timeout * 1000);53 54 return 0;55}56 57static int bcm47xx_wdt_hard_start(struct watchdog_device *wdd)58{59 return 0;60}61 62static int bcm47xx_wdt_hard_stop(struct watchdog_device *wdd)63{64 struct bcm47xx_wdt *wdt = bcm47xx_wdt_get(wdd);65 66 wdt->timer_set(wdt, 0);67 68 return 0;69}70 71static int bcm47xx_wdt_hard_set_timeout(struct watchdog_device *wdd,72 unsigned int new_time)73{74 struct bcm47xx_wdt *wdt = bcm47xx_wdt_get(wdd);75 u32 max_timer = wdt->max_timer_ms;76 77 if (new_time < 1 || new_time > max_timer / 1000) {78 pr_warn("timeout value must be 1<=x<=%d, using %d\n",79 max_timer / 1000, new_time);80 return -EINVAL;81 }82 83 wdd->timeout = new_time;84 return 0;85}86 87static int bcm47xx_wdt_restart(struct watchdog_device *wdd,88 unsigned long action, void *data)89{90 struct bcm47xx_wdt *wdt = bcm47xx_wdt_get(wdd);91 92 wdt->timer_set(wdt, 1);93 94 return 0;95}96 97static const struct watchdog_ops bcm47xx_wdt_hard_ops = {98 .owner = THIS_MODULE,99 .start = bcm47xx_wdt_hard_start,100 .stop = bcm47xx_wdt_hard_stop,101 .ping = bcm47xx_wdt_hard_keepalive,102 .set_timeout = bcm47xx_wdt_hard_set_timeout,103 .restart = bcm47xx_wdt_restart,104};105 106static void bcm47xx_wdt_soft_timer_tick(struct timer_list *t)107{108 struct bcm47xx_wdt *wdt = from_timer(wdt, t, soft_timer);109 u32 next_tick = min(wdt->wdd.timeout * 1000, wdt->max_timer_ms);110 111 if (!atomic_dec_and_test(&wdt->soft_ticks)) {112 wdt->timer_set_ms(wdt, next_tick);113 mod_timer(&wdt->soft_timer, jiffies + HZ);114 } else {115 pr_crit("Watchdog will fire soon!!!\n");116 }117}118 119static int bcm47xx_wdt_soft_keepalive(struct watchdog_device *wdd)120{121 struct bcm47xx_wdt *wdt = bcm47xx_wdt_get(wdd);122 123 atomic_set(&wdt->soft_ticks, wdd->timeout);124 125 return 0;126}127 128static int bcm47xx_wdt_soft_start(struct watchdog_device *wdd)129{130 struct bcm47xx_wdt *wdt = bcm47xx_wdt_get(wdd);131 132 bcm47xx_wdt_soft_keepalive(wdd);133 bcm47xx_wdt_soft_timer_tick(&wdt->soft_timer);134 135 return 0;136}137 138static int bcm47xx_wdt_soft_stop(struct watchdog_device *wdd)139{140 struct bcm47xx_wdt *wdt = bcm47xx_wdt_get(wdd);141 142 del_timer_sync(&wdt->soft_timer);143 wdt->timer_set(wdt, 0);144 145 return 0;146}147 148static int bcm47xx_wdt_soft_set_timeout(struct watchdog_device *wdd,149 unsigned int new_time)150{151 if (new_time < 1 || new_time > WDT_SOFTTIMER_MAX) {152 pr_warn("timeout value must be 1<=x<=%d, using %d\n",153 WDT_SOFTTIMER_MAX, new_time);154 return -EINVAL;155 }156 157 wdd->timeout = new_time;158 return 0;159}160 161static const struct watchdog_info bcm47xx_wdt_info = {162 .identity = DRV_NAME,163 .options = WDIOF_SETTIMEOUT |164 WDIOF_KEEPALIVEPING |165 WDIOF_MAGICCLOSE,166};167 168static const struct watchdog_ops bcm47xx_wdt_soft_ops = {169 .owner = THIS_MODULE,170 .start = bcm47xx_wdt_soft_start,171 .stop = bcm47xx_wdt_soft_stop,172 .ping = bcm47xx_wdt_soft_keepalive,173 .set_timeout = bcm47xx_wdt_soft_set_timeout,174 .restart = bcm47xx_wdt_restart,175};176 177static int bcm47xx_wdt_probe(struct platform_device *pdev)178{179 int ret;180 bool soft;181 struct bcm47xx_wdt *wdt = dev_get_platdata(&pdev->dev);182 183 if (!wdt)184 return -ENXIO;185 186 soft = wdt->max_timer_ms < WDT_SOFTTIMER_THRESHOLD * 1000;187 188 if (soft) {189 wdt->wdd.ops = &bcm47xx_wdt_soft_ops;190 timer_setup(&wdt->soft_timer, bcm47xx_wdt_soft_timer_tick, 0);191 } else {192 wdt->wdd.ops = &bcm47xx_wdt_hard_ops;193 }194 195 wdt->wdd.info = &bcm47xx_wdt_info;196 wdt->wdd.timeout = WDT_DEFAULT_TIME;197 wdt->wdd.parent = &pdev->dev;198 ret = wdt->wdd.ops->set_timeout(&wdt->wdd, timeout);199 if (ret)200 goto err_timer;201 watchdog_set_nowayout(&wdt->wdd, nowayout);202 watchdog_set_restart_priority(&wdt->wdd, 64);203 watchdog_stop_on_reboot(&wdt->wdd);204 205 ret = devm_watchdog_register_device(&pdev->dev, &wdt->wdd);206 if (ret)207 goto err_timer;208 209 dev_info(&pdev->dev, "BCM47xx Watchdog Timer enabled (%d seconds%s%s)\n",210 timeout, nowayout ? ", nowayout" : "",211 soft ? ", Software Timer" : "");212 return 0;213 214err_timer:215 if (soft)216 del_timer_sync(&wdt->soft_timer);217 218 return ret;219}220 221static struct platform_driver bcm47xx_wdt_driver = {222 .driver = {223 .name = "bcm47xx-wdt",224 },225 .probe = bcm47xx_wdt_probe,226};227 228module_platform_driver(bcm47xx_wdt_driver);229 230MODULE_AUTHOR("Aleksandar Radovanovic");231MODULE_AUTHOR("Hauke Mehrtens <hauke@hauke-m.de>");232MODULE_DESCRIPTION("Watchdog driver for Broadcom BCM47xx");233MODULE_LICENSE("GPL");234