13b190545SBing Fan // SPDX-License-Identifier: GPL-2.0+
24a370278SViresh KUMAR /*
34a370278SViresh KUMAR * drivers/char/watchdog/sp805-wdt.c
44a370278SViresh KUMAR *
54a370278SViresh KUMAR * Watchdog driver for ARM SP805 watchdog module
64a370278SViresh KUMAR *
74a370278SViresh KUMAR * Copyright (C) 2010 ST Microelectronics
8da89947bSViresh Kumar * Viresh Kumar <vireshk@kernel.org>
94a370278SViresh KUMAR *
104a370278SViresh KUMAR * This file is licensed under the terms of the GNU General Public
114a370278SViresh KUMAR * License version 2 or later. This program is licensed "as is" without any
124a370278SViresh KUMAR * warranty of any kind, whether express or implied.
134a370278SViresh KUMAR */
144a370278SViresh KUMAR
154a370278SViresh KUMAR #include <linux/device.h>
164a370278SViresh KUMAR #include <linux/resource.h>
174a370278SViresh KUMAR #include <linux/amba/bus.h>
184a370278SViresh KUMAR #include <linux/bitops.h>
194a370278SViresh KUMAR #include <linux/clk.h>
204a370278SViresh KUMAR #include <linux/io.h>
214a370278SViresh KUMAR #include <linux/ioport.h>
224a370278SViresh KUMAR #include <linux/kernel.h>
234a370278SViresh KUMAR #include <linux/math64.h>
244a370278SViresh KUMAR #include <linux/module.h>
254a370278SViresh KUMAR #include <linux/moduleparam.h>
2616ac4abeSViresh Kumar #include <linux/pm.h>
2705f0a994SAndy Shevchenko #include <linux/property.h>
284a370278SViresh KUMAR #include <linux/slab.h>
294a370278SViresh KUMAR #include <linux/spinlock.h>
304a370278SViresh KUMAR #include <linux/types.h>
314a370278SViresh KUMAR #include <linux/watchdog.h>
324a370278SViresh KUMAR
334a370278SViresh KUMAR /* default timeout in seconds */
344a370278SViresh KUMAR #define DEFAULT_TIMEOUT 60
354a370278SViresh KUMAR
364a370278SViresh KUMAR #define MODULE_NAME "sp805-wdt"
374a370278SViresh KUMAR
384a370278SViresh KUMAR /* watchdog register offsets and masks */
394a370278SViresh KUMAR #define WDTLOAD 0x000
404a370278SViresh KUMAR #define LOAD_MIN 0x00000001
414a370278SViresh KUMAR #define LOAD_MAX 0xFFFFFFFF
424a370278SViresh KUMAR #define WDTVALUE 0x004
434a370278SViresh KUMAR #define WDTCONTROL 0x008
444a370278SViresh KUMAR /* control register masks */
454a370278SViresh KUMAR #define INT_ENABLE (1 << 0)
464a370278SViresh KUMAR #define RESET_ENABLE (1 << 1)
47fa5072edSRay Jui #define ENABLE_MASK (INT_ENABLE | RESET_ENABLE)
484a370278SViresh KUMAR #define WDTINTCLR 0x00C
494a370278SViresh KUMAR #define WDTRIS 0x010
504a370278SViresh KUMAR #define WDTMIS 0x014
514a370278SViresh KUMAR #define INT_MASK (1 << 0)
524a370278SViresh KUMAR #define WDTLOCK 0xC00
534a370278SViresh KUMAR #define UNLOCK 0x1ACCE551
544a370278SViresh KUMAR #define LOCK 0x00000001
554a370278SViresh KUMAR
564a370278SViresh KUMAR /**
574a370278SViresh KUMAR * struct sp805_wdt: sp805 wdt device structure
584a516539SViresh Kumar * @wdd: instance of struct watchdog_device
59bfae14b6SViresh Kumar * @lock: spin lock protecting dev structure and io access
60bfae14b6SViresh Kumar * @base: base address of wdt
613452239eSAndy Shevchenko * @clk: (optional) clock structure of wdt
623452239eSAndy Shevchenko * @rate: (optional) clock rate when provided via properties
63bfae14b6SViresh Kumar * @adev: amba device structure of wdt
64bfae14b6SViresh Kumar * @status: current status of wdt
65bfae14b6SViresh Kumar * @load_val: load value to be set for current timeout
664a370278SViresh KUMAR */
674a370278SViresh KUMAR struct sp805_wdt {
684a516539SViresh Kumar struct watchdog_device wdd;
694a370278SViresh KUMAR spinlock_t lock;
704a370278SViresh KUMAR void __iomem *base;
714a370278SViresh KUMAR struct clk *clk;
72dc0e4a3bSSrinath Mannam u64 rate;
734a370278SViresh KUMAR struct amba_device *adev;
744a370278SViresh KUMAR unsigned int load_val;
754a370278SViresh KUMAR };
764a370278SViresh KUMAR
7786a1e189SWim Van Sebroeck static bool nowayout = WATCHDOG_NOWAYOUT;
784a516539SViresh Kumar module_param(nowayout, bool, 0);
794a516539SViresh Kumar MODULE_PARM_DESC(nowayout,
804a516539SViresh Kumar "Set to 1 to keep watchdog running after device release");
814a370278SViresh KUMAR
82fa5072edSRay Jui /* returns true if wdt is running; otherwise returns false */
wdt_is_running(struct watchdog_device * wdd)83fa5072edSRay Jui static bool wdt_is_running(struct watchdog_device *wdd)
84fa5072edSRay Jui {
85fa5072edSRay Jui struct sp805_wdt *wdt = watchdog_get_drvdata(wdd);
86fa5072edSRay Jui u32 wdtcontrol = readl_relaxed(wdt->base + WDTCONTROL);
87fa5072edSRay Jui
88fa5072edSRay Jui return (wdtcontrol & ENABLE_MASK) == ENABLE_MASK;
89fa5072edSRay Jui }
90fa5072edSRay Jui
91*097a4a16SJiangshan Yi /* This routine finds load value that will reset system in required timeout */
wdt_setload(struct watchdog_device * wdd,unsigned int timeout)924a516539SViresh Kumar static int wdt_setload(struct watchdog_device *wdd, unsigned int timeout)
934a370278SViresh KUMAR {
944a516539SViresh Kumar struct sp805_wdt *wdt = watchdog_get_drvdata(wdd);
954a370278SViresh KUMAR u64 load, rate;
964a370278SViresh KUMAR
97dc0e4a3bSSrinath Mannam rate = wdt->rate;
984a370278SViresh KUMAR
994a370278SViresh KUMAR /*
1004a370278SViresh KUMAR * sp805 runs counter with given value twice, after the end of first
1014a370278SViresh KUMAR * counter it gives an interrupt and then starts counter again. If
10225985edcSLucas De Marchi * interrupt already occurred then it resets the system. This is why
1034a370278SViresh KUMAR * load is half of what should be required.
1044a370278SViresh KUMAR */
1054a370278SViresh KUMAR load = div_u64(rate, 2) * timeout - 1;
1064a370278SViresh KUMAR
1074a370278SViresh KUMAR load = (load > LOAD_MAX) ? LOAD_MAX : load;
1084a370278SViresh KUMAR load = (load < LOAD_MIN) ? LOAD_MIN : load;
1094a370278SViresh KUMAR
1104a370278SViresh KUMAR spin_lock(&wdt->lock);
1114a370278SViresh KUMAR wdt->load_val = load;
1124a370278SViresh KUMAR /* roundup timeout to closest positive integer value */
113938626d9SViresh Kumar wdd->timeout = div_u64((load + 1) * 2 + (rate / 2), rate);
1144a370278SViresh KUMAR spin_unlock(&wdt->lock);
1154a516539SViresh Kumar
1164a516539SViresh Kumar return 0;
1174a370278SViresh KUMAR }
1184a370278SViresh KUMAR
1194a370278SViresh KUMAR /* returns number of seconds left for reset to occur */
wdt_timeleft(struct watchdog_device * wdd)1204a516539SViresh Kumar static unsigned int wdt_timeleft(struct watchdog_device *wdd)
1214a370278SViresh KUMAR {
1224a516539SViresh Kumar struct sp805_wdt *wdt = watchdog_get_drvdata(wdd);
123dc0e4a3bSSrinath Mannam u64 load;
1244a370278SViresh KUMAR
1254a370278SViresh KUMAR spin_lock(&wdt->lock);
126d2e8919bSViresh Kumar load = readl_relaxed(wdt->base + WDTVALUE);
1274a370278SViresh KUMAR
1284a370278SViresh KUMAR /*If the interrupt is inactive then time left is WDTValue + WDTLoad. */
129d2e8919bSViresh Kumar if (!(readl_relaxed(wdt->base + WDTRIS) & INT_MASK))
1304a370278SViresh KUMAR load += wdt->load_val + 1;
1314a370278SViresh KUMAR spin_unlock(&wdt->lock);
1324a370278SViresh KUMAR
133dc0e4a3bSSrinath Mannam return div_u64(load, wdt->rate);
1344a370278SViresh KUMAR }
1354a370278SViresh KUMAR
1366c5c0d48SJongsung Kim static int
wdt_restart(struct watchdog_device * wdd,unsigned long mode,void * cmd)1376c5c0d48SJongsung Kim wdt_restart(struct watchdog_device *wdd, unsigned long mode, void *cmd)
1386c5c0d48SJongsung Kim {
1396c5c0d48SJongsung Kim struct sp805_wdt *wdt = watchdog_get_drvdata(wdd);
1406c5c0d48SJongsung Kim
141ea104a9eSMichael Walle writel_relaxed(UNLOCK, wdt->base + WDTLOCK);
1426c5c0d48SJongsung Kim writel_relaxed(0, wdt->base + WDTCONTROL);
1436c5c0d48SJongsung Kim writel_relaxed(0, wdt->base + WDTLOAD);
1446c5c0d48SJongsung Kim writel_relaxed(INT_ENABLE | RESET_ENABLE, wdt->base + WDTCONTROL);
1456c5c0d48SJongsung Kim
146ea104a9eSMichael Walle /* Flush posted writes. */
147ea104a9eSMichael Walle readl_relaxed(wdt->base + WDTLOCK);
148ea104a9eSMichael Walle
1496c5c0d48SJongsung Kim return 0;
1506c5c0d48SJongsung Kim }
1516c5c0d48SJongsung Kim
wdt_config(struct watchdog_device * wdd,bool ping)1524a516539SViresh Kumar static int wdt_config(struct watchdog_device *wdd, bool ping)
1534a370278SViresh KUMAR {
1544a516539SViresh Kumar struct sp805_wdt *wdt = watchdog_get_drvdata(wdd);
1554a516539SViresh Kumar int ret;
1564a516539SViresh Kumar
1574a516539SViresh Kumar if (!ping) {
158d9df0ef1SViresh Kumar
15963fbbc16SJulia Lawall ret = clk_prepare_enable(wdt->clk);
1604a516539SViresh Kumar if (ret) {
1614a516539SViresh Kumar dev_err(&wdt->adev->dev, "clock enable fail");
1624a516539SViresh Kumar return ret;
1634a516539SViresh Kumar }
1644a516539SViresh Kumar }
1654a516539SViresh Kumar
1664a370278SViresh KUMAR spin_lock(&wdt->lock);
1674a370278SViresh KUMAR
168d2e8919bSViresh Kumar writel_relaxed(UNLOCK, wdt->base + WDTLOCK);
169d2e8919bSViresh Kumar writel_relaxed(wdt->load_val, wdt->base + WDTLOAD);
170d2e8919bSViresh Kumar writel_relaxed(INT_MASK, wdt->base + WDTINTCLR);
17155e07177SSandeep Tripathy
17255e07177SSandeep Tripathy if (!ping)
1734a516539SViresh Kumar writel_relaxed(INT_ENABLE | RESET_ENABLE, wdt->base +
1744a516539SViresh Kumar WDTCONTROL);
1754a516539SViresh Kumar
176d2e8919bSViresh Kumar writel_relaxed(LOCK, wdt->base + WDTLOCK);
1774a370278SViresh KUMAR
178081d83a3SNick Bowler /* Flush posted writes. */
179d2e8919bSViresh Kumar readl_relaxed(wdt->base + WDTLOCK);
1804a370278SViresh KUMAR spin_unlock(&wdt->lock);
1814a516539SViresh Kumar
1824a516539SViresh Kumar return 0;
1834a516539SViresh Kumar }
1844a516539SViresh Kumar
wdt_ping(struct watchdog_device * wdd)1854a516539SViresh Kumar static int wdt_ping(struct watchdog_device *wdd)
1864a516539SViresh Kumar {
1874a516539SViresh Kumar return wdt_config(wdd, true);
1884a516539SViresh Kumar }
1894a516539SViresh Kumar
1904a516539SViresh Kumar /* enables watchdog timers reset */
wdt_enable(struct watchdog_device * wdd)1914a516539SViresh Kumar static int wdt_enable(struct watchdog_device *wdd)
1924a516539SViresh Kumar {
1934a516539SViresh Kumar return wdt_config(wdd, false);
1944a370278SViresh KUMAR }
1954a370278SViresh KUMAR
1964a370278SViresh KUMAR /* disables watchdog timers reset */
wdt_disable(struct watchdog_device * wdd)1974a516539SViresh Kumar static int wdt_disable(struct watchdog_device *wdd)
1984a370278SViresh KUMAR {
1994a516539SViresh Kumar struct sp805_wdt *wdt = watchdog_get_drvdata(wdd);
2004a516539SViresh Kumar
2014a370278SViresh KUMAR spin_lock(&wdt->lock);
2024a370278SViresh KUMAR
203d2e8919bSViresh Kumar writel_relaxed(UNLOCK, wdt->base + WDTLOCK);
204d2e8919bSViresh Kumar writel_relaxed(0, wdt->base + WDTCONTROL);
205d2e8919bSViresh Kumar writel_relaxed(LOCK, wdt->base + WDTLOCK);
2064a370278SViresh KUMAR
207081d83a3SNick Bowler /* Flush posted writes. */
208d2e8919bSViresh Kumar readl_relaxed(wdt->base + WDTLOCK);
2094a370278SViresh KUMAR spin_unlock(&wdt->lock);
2104a516539SViresh Kumar
21163fbbc16SJulia Lawall clk_disable_unprepare(wdt->clk);
2124a516539SViresh Kumar
2134a516539SViresh Kumar return 0;
2144a370278SViresh KUMAR }
2154a370278SViresh KUMAR
2164a516539SViresh Kumar static const struct watchdog_info wdt_info = {
2174a370278SViresh KUMAR .options = WDIOF_MAGICCLOSE | WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING,
2184a370278SViresh KUMAR .identity = MODULE_NAME,
2194a370278SViresh KUMAR };
2204a370278SViresh KUMAR
2214a516539SViresh Kumar static const struct watchdog_ops wdt_ops = {
2224a370278SViresh KUMAR .owner = THIS_MODULE,
2234a516539SViresh Kumar .start = wdt_enable,
2244a516539SViresh Kumar .stop = wdt_disable,
2254a516539SViresh Kumar .ping = wdt_ping,
2264a516539SViresh Kumar .set_timeout = wdt_setload,
2274a516539SViresh Kumar .get_timeleft = wdt_timeleft,
2286c5c0d48SJongsung Kim .restart = wdt_restart,
2294a370278SViresh KUMAR };
2304a370278SViresh KUMAR
2312d991a16SBill Pemberton static int
sp805_wdt_probe(struct amba_device * adev,const struct amba_id * id)232aa25afadSRussell King sp805_wdt_probe(struct amba_device *adev, const struct amba_id *id)
2334a370278SViresh KUMAR {
2344a516539SViresh Kumar struct sp805_wdt *wdt;
23505f0a994SAndy Shevchenko u64 rate = 0;
2364a370278SViresh KUMAR int ret = 0;
2374a370278SViresh KUMAR
238fb35a5adSViresh Kumar wdt = devm_kzalloc(&adev->dev, sizeof(*wdt), GFP_KERNEL);
2394a370278SViresh KUMAR if (!wdt) {
2404a370278SViresh KUMAR ret = -ENOMEM;
241fb35a5adSViresh Kumar goto err;
242fb35a5adSViresh Kumar }
243fb35a5adSViresh Kumar
2449d11e4f8SJingoo Han wdt->base = devm_ioremap_resource(&adev->dev, &adev->res);
2459d11e4f8SJingoo Han if (IS_ERR(wdt->base))
2469d11e4f8SJingoo Han return PTR_ERR(wdt->base);
2474a370278SViresh KUMAR
248dc0e4a3bSSrinath Mannam /*
24905f0a994SAndy Shevchenko * When driver probe with ACPI device, clock devices
250dc0e4a3bSSrinath Mannam * are not available, so watchdog rate get from
251dc0e4a3bSSrinath Mannam * clock-frequency property given in _DSD object.
252dc0e4a3bSSrinath Mannam */
25305f0a994SAndy Shevchenko device_property_read_u64(&adev->dev, "clock-frequency", &rate);
25405f0a994SAndy Shevchenko
25505f0a994SAndy Shevchenko wdt->clk = devm_clk_get_optional(&adev->dev, NULL);
25605f0a994SAndy Shevchenko if (IS_ERR(wdt->clk))
25705f0a994SAndy Shevchenko return dev_err_probe(&adev->dev, PTR_ERR(wdt->clk), "Clock not found\n");
25805f0a994SAndy Shevchenko
25905f0a994SAndy Shevchenko wdt->rate = clk_get_rate(wdt->clk);
26005f0a994SAndy Shevchenko if (!wdt->rate)
26105f0a994SAndy Shevchenko wdt->rate = rate;
262dc0e4a3bSSrinath Mannam if (!wdt->rate) {
263dc0e4a3bSSrinath Mannam dev_err(&adev->dev, "no clock-frequency property\n");
264dc0e4a3bSSrinath Mannam return -ENODEV;
265dc0e4a3bSSrinath Mannam }
2664a370278SViresh KUMAR
2674a370278SViresh KUMAR wdt->adev = adev;
2684a516539SViresh Kumar wdt->wdd.info = &wdt_info;
2694a516539SViresh Kumar wdt->wdd.ops = &wdt_ops;
2706551881cSPratyush Anand wdt->wdd.parent = &adev->dev;
2714a370278SViresh KUMAR
2724a516539SViresh Kumar spin_lock_init(&wdt->lock);
2734a516539SViresh Kumar watchdog_set_nowayout(&wdt->wdd, nowayout);
2744a516539SViresh Kumar watchdog_set_drvdata(&wdt->wdd, wdt);
2756c5c0d48SJongsung Kim watchdog_set_restart_priority(&wdt->wdd, 128);
276ac97c937SEliav Farber watchdog_stop_on_unregister(&wdt->wdd);
277b8008858SRay Jui
278b8008858SRay Jui /*
279b8008858SRay Jui * If 'timeout-sec' devicetree property is specified, use that.
280b8008858SRay Jui * Otherwise, use DEFAULT_TIMEOUT
281b8008858SRay Jui */
282b8008858SRay Jui wdt->wdd.timeout = DEFAULT_TIMEOUT;
283b8008858SRay Jui watchdog_init_timeout(&wdt->wdd, 0, &adev->dev);
284b8008858SRay Jui wdt_setload(&wdt->wdd, wdt->wdd.timeout);
2854a516539SViresh Kumar
286fa5072edSRay Jui /*
287fa5072edSRay Jui * If HW is already running, enable/reset the wdt and set the running
288fa5072edSRay Jui * bit to tell the wdt subsystem
289fa5072edSRay Jui */
290fa5072edSRay Jui if (wdt_is_running(&wdt->wdd)) {
291fa5072edSRay Jui wdt_enable(&wdt->wdd);
292fa5072edSRay Jui set_bit(WDOG_HW_RUNNING, &wdt->wdd.status);
293fa5072edSRay Jui }
294fa5072edSRay Jui
29542e967f3SZhao Qiang watchdog_stop_on_reboot(&wdt->wdd);
2964a516539SViresh Kumar ret = watchdog_register_device(&wdt->wdd);
297199801cdSWolfram Sang if (ret)
29807bf971aSJingoo Han goto err;
2994a516539SViresh Kumar amba_set_drvdata(adev, wdt);
3004a370278SViresh KUMAR
3014a370278SViresh KUMAR dev_info(&adev->dev, "registration successful\n");
3024a370278SViresh KUMAR return 0;
3034a370278SViresh KUMAR
3044a370278SViresh KUMAR err:
3054a370278SViresh KUMAR dev_err(&adev->dev, "Probe Failed!!!\n");
3064a370278SViresh KUMAR return ret;
3074a370278SViresh KUMAR }
3084a370278SViresh KUMAR
sp805_wdt_remove(struct amba_device * adev)3093fd269e7SUwe Kleine-König static void sp805_wdt_remove(struct amba_device *adev)
3104a370278SViresh KUMAR {
3114a516539SViresh Kumar struct sp805_wdt *wdt = amba_get_drvdata(adev);
3124a516539SViresh Kumar
3134a516539SViresh Kumar watchdog_unregister_device(&wdt->wdd);
3144a516539SViresh Kumar watchdog_set_drvdata(&wdt->wdd, NULL);
3154a370278SViresh KUMAR }
3164a370278SViresh KUMAR
sp805_wdt_suspend(struct device * dev)31760d6dd53SRussell King static int __maybe_unused sp805_wdt_suspend(struct device *dev)
31816ac4abeSViresh Kumar {
3194a516539SViresh Kumar struct sp805_wdt *wdt = dev_get_drvdata(dev);
3204a516539SViresh Kumar
3214a516539SViresh Kumar if (watchdog_active(&wdt->wdd))
3224a516539SViresh Kumar return wdt_disable(&wdt->wdd);
32316ac4abeSViresh Kumar
32416ac4abeSViresh Kumar return 0;
32516ac4abeSViresh Kumar }
32616ac4abeSViresh Kumar
sp805_wdt_resume(struct device * dev)32760d6dd53SRussell King static int __maybe_unused sp805_wdt_resume(struct device *dev)
32816ac4abeSViresh Kumar {
3294a516539SViresh Kumar struct sp805_wdt *wdt = dev_get_drvdata(dev);
33016ac4abeSViresh Kumar
3314a516539SViresh Kumar if (watchdog_active(&wdt->wdd))
3324a516539SViresh Kumar return wdt_enable(&wdt->wdd);
33316ac4abeSViresh Kumar
3344a516539SViresh Kumar return 0;
33516ac4abeSViresh Kumar }
33616ac4abeSViresh Kumar
33716ac4abeSViresh Kumar static SIMPLE_DEV_PM_OPS(sp805_wdt_dev_pm_ops, sp805_wdt_suspend,
33816ac4abeSViresh Kumar sp805_wdt_resume);
33916ac4abeSViresh Kumar
34005ce42ffSArvind Yadav static const struct amba_id sp805_wdt_ids[] = {
3414a370278SViresh KUMAR {
3424a370278SViresh KUMAR .id = 0x00141805,
3434a370278SViresh KUMAR .mask = 0x00ffffff,
3444a370278SViresh KUMAR },
3453b190545SBing Fan {
3463b190545SBing Fan .id = 0x001bb824,
3473b190545SBing Fan .mask = 0x00ffffff,
3483b190545SBing Fan },
3494a370278SViresh KUMAR { 0, 0 },
3504a370278SViresh KUMAR };
3514a370278SViresh KUMAR
35217885b05SDave Martin MODULE_DEVICE_TABLE(amba, sp805_wdt_ids);
35317885b05SDave Martin
3544a370278SViresh KUMAR static struct amba_driver sp805_wdt_driver = {
3554a370278SViresh KUMAR .drv = {
3564a370278SViresh KUMAR .name = MODULE_NAME,
35716ac4abeSViresh Kumar .pm = &sp805_wdt_dev_pm_ops,
3584a370278SViresh KUMAR },
3594a370278SViresh KUMAR .id_table = sp805_wdt_ids,
3604a370278SViresh KUMAR .probe = sp805_wdt_probe,
36182268714SBill Pemberton .remove = sp805_wdt_remove,
3624a370278SViresh KUMAR };
3634a370278SViresh KUMAR
3649e5ed094Sviresh kumar module_amba_driver(sp805_wdt_driver);
3654a370278SViresh KUMAR
366da89947bSViresh Kumar MODULE_AUTHOR("Viresh Kumar <vireshk@kernel.org>");
3674a370278SViresh KUMAR MODULE_DESCRIPTION("ARM SP805 Watchdog Driver");
3684a370278SViresh KUMAR MODULE_LICENSE("GPL");
369