xref: /openbmc/linux/drivers/watchdog/bcm2835_wdt.c (revision 1188f7f111c61394ec56beb8e30322305a8220b6)
12e62c498SMarcus Folkesson // SPDX-License-Identifier: GPL-2.0+
2938d0a84SLubomir Rintel /*
3938d0a84SLubomir Rintel  * Watchdog driver for Broadcom BCM2835
4938d0a84SLubomir Rintel  *
5938d0a84SLubomir Rintel  * "bcm2708_wdog" driver written by Luke Diamand that was obtained from
6938d0a84SLubomir Rintel  * branch "rpi-3.6.y" of git://github.com/raspberrypi/linux.git was used
7938d0a84SLubomir Rintel  * as a hardware reference for the Broadcom BCM2835 watchdog timer.
8938d0a84SLubomir Rintel  *
9938d0a84SLubomir Rintel  * Copyright (C) 2013 Lubomir Rintel <lkundrak@v3.sk>
10938d0a84SLubomir Rintel  *
11938d0a84SLubomir Rintel  */
12938d0a84SLubomir Rintel 
1333a9f5bcSEric Anholt #include <linux/delay.h>
14938d0a84SLubomir Rintel #include <linux/types.h>
155e6acc3eSEric Anholt #include <linux/mfd/bcm2835-pm.h>
16938d0a84SLubomir Rintel #include <linux/module.h>
17938d0a84SLubomir Rintel #include <linux/io.h>
18938d0a84SLubomir Rintel #include <linux/watchdog.h>
19938d0a84SLubomir Rintel #include <linux/platform_device.h>
20938d0a84SLubomir Rintel #include <linux/of_address.h>
2133a9f5bcSEric Anholt #include <linux/of_platform.h>
22938d0a84SLubomir Rintel 
23938d0a84SLubomir Rintel #define PM_RSTC				0x1c
2433a9f5bcSEric Anholt #define PM_RSTS				0x20
25938d0a84SLubomir Rintel #define PM_WDOG				0x24
26938d0a84SLubomir Rintel 
27938d0a84SLubomir Rintel #define PM_PASSWORD			0x5a000000
28938d0a84SLubomir Rintel 
29938d0a84SLubomir Rintel #define PM_WDOG_TIME_SET		0x000fffff
30938d0a84SLubomir Rintel #define PM_RSTC_WRCFG_CLR		0xffffffcf
3133a9f5bcSEric Anholt #define PM_RSTS_HADWRH_SET		0x00000040
32938d0a84SLubomir Rintel #define PM_RSTC_WRCFG_SET		0x00000030
33938d0a84SLubomir Rintel #define PM_RSTC_WRCFG_FULL_RESET	0x00000020
34938d0a84SLubomir Rintel #define PM_RSTC_RESET			0x00000102
35938d0a84SLubomir Rintel 
36898e6861SNoralf Trønnes /*
378ab102d6SMasahiro Yamada  * The Raspberry Pi firmware uses the RSTS register to know which partition
388ab102d6SMasahiro Yamada  * to boot from. The partition value is spread into bits 0, 2, 4, 6, 8, 10.
398ab102d6SMasahiro Yamada  * Partition 63 is a special partition used by the firmware to indicate halt.
40898e6861SNoralf Trønnes  */
41898e6861SNoralf Trønnes #define PM_RSTS_RASPBERRYPI_HALT	0x555
42898e6861SNoralf Trønnes 
43938d0a84SLubomir Rintel #define SECS_TO_WDOG_TICKS(x) ((x) << 16)
44938d0a84SLubomir Rintel #define WDOG_TICKS_TO_SECS(x) ((x) >> 16)
45*57b39f0bSStefan Wahren #define WDOG_TICKS_TO_MSECS(x) ((x) * 1000 >> 16)
46938d0a84SLubomir Rintel 
47938d0a84SLubomir Rintel struct bcm2835_wdt {
48938d0a84SLubomir Rintel 	void __iomem		*base;
49938d0a84SLubomir Rintel 	spinlock_t		lock;
50938d0a84SLubomir Rintel };
51938d0a84SLubomir Rintel 
525e6acc3eSEric Anholt static struct bcm2835_wdt *bcm2835_power_off_wdt;
535e6acc3eSEric Anholt 
54938d0a84SLubomir Rintel static unsigned int heartbeat;
55938d0a84SLubomir Rintel static bool nowayout = WATCHDOG_NOWAYOUT;
56938d0a84SLubomir Rintel 
bcm2835_wdt_is_running(struct bcm2835_wdt * wdt)57054ae194SRasmus Villemoes static bool bcm2835_wdt_is_running(struct bcm2835_wdt *wdt)
58054ae194SRasmus Villemoes {
59054ae194SRasmus Villemoes 	uint32_t cur;
60054ae194SRasmus Villemoes 
61054ae194SRasmus Villemoes 	cur = readl(wdt->base + PM_RSTC);
62054ae194SRasmus Villemoes 
63054ae194SRasmus Villemoes 	return !!(cur & PM_RSTC_WRCFG_FULL_RESET);
64054ae194SRasmus Villemoes }
65054ae194SRasmus Villemoes 
bcm2835_wdt_start(struct watchdog_device * wdog)66938d0a84SLubomir Rintel static int bcm2835_wdt_start(struct watchdog_device *wdog)
67938d0a84SLubomir Rintel {
68938d0a84SLubomir Rintel 	struct bcm2835_wdt *wdt = watchdog_get_drvdata(wdog);
69938d0a84SLubomir Rintel 	uint32_t cur;
70938d0a84SLubomir Rintel 	unsigned long flags;
71938d0a84SLubomir Rintel 
72938d0a84SLubomir Rintel 	spin_lock_irqsave(&wdt->lock, flags);
73938d0a84SLubomir Rintel 
74938d0a84SLubomir Rintel 	writel_relaxed(PM_PASSWORD | (SECS_TO_WDOG_TICKS(wdog->timeout) &
75938d0a84SLubomir Rintel 				PM_WDOG_TIME_SET), wdt->base + PM_WDOG);
76938d0a84SLubomir Rintel 	cur = readl_relaxed(wdt->base + PM_RSTC);
77938d0a84SLubomir Rintel 	writel_relaxed(PM_PASSWORD | (cur & PM_RSTC_WRCFG_CLR) |
78938d0a84SLubomir Rintel 		  PM_RSTC_WRCFG_FULL_RESET, wdt->base + PM_RSTC);
79938d0a84SLubomir Rintel 
80938d0a84SLubomir Rintel 	spin_unlock_irqrestore(&wdt->lock, flags);
81938d0a84SLubomir Rintel 
82938d0a84SLubomir Rintel 	return 0;
83938d0a84SLubomir Rintel }
84938d0a84SLubomir Rintel 
bcm2835_wdt_stop(struct watchdog_device * wdog)85938d0a84SLubomir Rintel static int bcm2835_wdt_stop(struct watchdog_device *wdog)
86938d0a84SLubomir Rintel {
87938d0a84SLubomir Rintel 	struct bcm2835_wdt *wdt = watchdog_get_drvdata(wdog);
88938d0a84SLubomir Rintel 
89938d0a84SLubomir Rintel 	writel_relaxed(PM_PASSWORD | PM_RSTC_RESET, wdt->base + PM_RSTC);
90938d0a84SLubomir Rintel 	return 0;
91938d0a84SLubomir Rintel }
92938d0a84SLubomir Rintel 
bcm2835_wdt_get_timeleft(struct watchdog_device * wdog)93938d0a84SLubomir Rintel static unsigned int bcm2835_wdt_get_timeleft(struct watchdog_device *wdog)
94938d0a84SLubomir Rintel {
95938d0a84SLubomir Rintel 	struct bcm2835_wdt *wdt = watchdog_get_drvdata(wdog);
96938d0a84SLubomir Rintel 
97938d0a84SLubomir Rintel 	uint32_t ret = readl_relaxed(wdt->base + PM_WDOG);
98938d0a84SLubomir Rintel 	return WDOG_TICKS_TO_SECS(ret & PM_WDOG_TIME_SET);
99938d0a84SLubomir Rintel }
100938d0a84SLubomir Rintel 
__bcm2835_restart(struct bcm2835_wdt * wdt)10171e9b2f0SGuenter Roeck static void __bcm2835_restart(struct bcm2835_wdt *wdt)
10271e9b2f0SGuenter Roeck {
10371e9b2f0SGuenter Roeck 	u32 val;
10471e9b2f0SGuenter Roeck 
10571e9b2f0SGuenter Roeck 	/* use a timeout of 10 ticks (~150us) */
10671e9b2f0SGuenter Roeck 	writel_relaxed(10 | PM_PASSWORD, wdt->base + PM_WDOG);
10771e9b2f0SGuenter Roeck 	val = readl_relaxed(wdt->base + PM_RSTC);
10871e9b2f0SGuenter Roeck 	val &= PM_RSTC_WRCFG_CLR;
10971e9b2f0SGuenter Roeck 	val |= PM_PASSWORD | PM_RSTC_WRCFG_FULL_RESET;
11071e9b2f0SGuenter Roeck 	writel_relaxed(val, wdt->base + PM_RSTC);
11171e9b2f0SGuenter Roeck 
11271e9b2f0SGuenter Roeck 	/* No sleeping, possibly atomic. */
11371e9b2f0SGuenter Roeck 	mdelay(1);
11471e9b2f0SGuenter Roeck }
11571e9b2f0SGuenter Roeck 
bcm2835_restart(struct watchdog_device * wdog,unsigned long action,void * data)11671e9b2f0SGuenter Roeck static int bcm2835_restart(struct watchdog_device *wdog,
11771e9b2f0SGuenter Roeck 			   unsigned long action, void *data)
11871e9b2f0SGuenter Roeck {
11971e9b2f0SGuenter Roeck 	struct bcm2835_wdt *wdt = watchdog_get_drvdata(wdog);
12071e9b2f0SGuenter Roeck 
12171e9b2f0SGuenter Roeck 	__bcm2835_restart(wdt);
12271e9b2f0SGuenter Roeck 
12371e9b2f0SGuenter Roeck 	return 0;
12471e9b2f0SGuenter Roeck }
12571e9b2f0SGuenter Roeck 
126aea4b477SRasmus Villemoes static const struct watchdog_ops bcm2835_wdt_ops = {
127938d0a84SLubomir Rintel 	.owner =	THIS_MODULE,
128938d0a84SLubomir Rintel 	.start =	bcm2835_wdt_start,
129938d0a84SLubomir Rintel 	.stop =		bcm2835_wdt_stop,
130938d0a84SLubomir Rintel 	.get_timeleft =	bcm2835_wdt_get_timeleft,
13171e9b2f0SGuenter Roeck 	.restart =	bcm2835_restart,
132938d0a84SLubomir Rintel };
133938d0a84SLubomir Rintel 
134aea4b477SRasmus Villemoes static const struct watchdog_info bcm2835_wdt_info = {
135938d0a84SLubomir Rintel 	.options =	WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE |
136938d0a84SLubomir Rintel 			WDIOF_KEEPALIVEPING,
137938d0a84SLubomir Rintel 	.identity =	"Broadcom BCM2835 Watchdog timer",
138938d0a84SLubomir Rintel };
139938d0a84SLubomir Rintel 
140938d0a84SLubomir Rintel static struct watchdog_device bcm2835_wdt_wdd = {
141938d0a84SLubomir Rintel 	.info =		&bcm2835_wdt_info,
142938d0a84SLubomir Rintel 	.ops =		&bcm2835_wdt_ops,
143938d0a84SLubomir Rintel 	.min_timeout =	1,
144*57b39f0bSStefan Wahren 	.max_hw_heartbeat_ms =	WDOG_TICKS_TO_MSECS(PM_WDOG_TIME_SET),
145938d0a84SLubomir Rintel 	.timeout =	WDOG_TICKS_TO_SECS(PM_WDOG_TIME_SET),
146938d0a84SLubomir Rintel };
147938d0a84SLubomir Rintel 
14833a9f5bcSEric Anholt /*
14933a9f5bcSEric Anholt  * We can't really power off, but if we do the normal reset scheme, and
15033a9f5bcSEric Anholt  * indicate to bootcode.bin not to reboot, then most of the chip will be
15133a9f5bcSEric Anholt  * powered off.
15233a9f5bcSEric Anholt  */
bcm2835_power_off(void)15333a9f5bcSEric Anholt static void bcm2835_power_off(void)
15433a9f5bcSEric Anholt {
1555e6acc3eSEric Anholt 	struct bcm2835_wdt *wdt = bcm2835_power_off_wdt;
15633a9f5bcSEric Anholt 	u32 val;
15733a9f5bcSEric Anholt 
15833a9f5bcSEric Anholt 	/*
15933a9f5bcSEric Anholt 	 * We set the watchdog hard reset bit here to distinguish this reset
16033a9f5bcSEric Anholt 	 * from the normal (full) reset. bootcode.bin will not reboot after a
16133a9f5bcSEric Anholt 	 * hard reset.
16233a9f5bcSEric Anholt 	 */
16333a9f5bcSEric Anholt 	val = readl_relaxed(wdt->base + PM_RSTS);
164898e6861SNoralf Trønnes 	val |= PM_PASSWORD | PM_RSTS_RASPBERRYPI_HALT;
16533a9f5bcSEric Anholt 	writel_relaxed(val, wdt->base + PM_RSTS);
16633a9f5bcSEric Anholt 
16733a9f5bcSEric Anholt 	/* Continue with normal reset mechanism */
16871e9b2f0SGuenter Roeck 	__bcm2835_restart(wdt);
16933a9f5bcSEric Anholt }
17033a9f5bcSEric Anholt 
bcm2835_wdt_probe(struct platform_device * pdev)171938d0a84SLubomir Rintel static int bcm2835_wdt_probe(struct platform_device *pdev)
172938d0a84SLubomir Rintel {
1735e6acc3eSEric Anholt 	struct bcm2835_pm *pm = dev_get_drvdata(pdev->dev.parent);
174938d0a84SLubomir Rintel 	struct device *dev = &pdev->dev;
175938d0a84SLubomir Rintel 	struct bcm2835_wdt *wdt;
176938d0a84SLubomir Rintel 	int err;
177938d0a84SLubomir Rintel 
178938d0a84SLubomir Rintel 	wdt = devm_kzalloc(dev, sizeof(struct bcm2835_wdt), GFP_KERNEL);
1798deea830SJingoo Han 	if (!wdt)
180938d0a84SLubomir Rintel 		return -ENOMEM;
181938d0a84SLubomir Rintel 
182938d0a84SLubomir Rintel 	spin_lock_init(&wdt->lock);
183938d0a84SLubomir Rintel 
1845e6acc3eSEric Anholt 	wdt->base = pm->base;
185938d0a84SLubomir Rintel 
186938d0a84SLubomir Rintel 	watchdog_set_drvdata(&bcm2835_wdt_wdd, wdt);
187938d0a84SLubomir Rintel 	watchdog_init_timeout(&bcm2835_wdt_wdd, heartbeat, dev);
188938d0a84SLubomir Rintel 	watchdog_set_nowayout(&bcm2835_wdt_wdd, nowayout);
1895f5aa6f1SGuenter Roeck 	bcm2835_wdt_wdd.parent = dev;
190054ae194SRasmus Villemoes 	if (bcm2835_wdt_is_running(wdt)) {
191054ae194SRasmus Villemoes 		/*
192054ae194SRasmus Villemoes 		 * The currently active timeout value (set by the
193054ae194SRasmus Villemoes 		 * bootloader) may be different from the module
194054ae194SRasmus Villemoes 		 * heartbeat parameter or the value in device
195054ae194SRasmus Villemoes 		 * tree. But we just need to set WDOG_HW_RUNNING,
196054ae194SRasmus Villemoes 		 * because then the framework will "immediately" ping
197054ae194SRasmus Villemoes 		 * the device, updating the timeout.
198054ae194SRasmus Villemoes 		 */
199054ae194SRasmus Villemoes 		set_bit(WDOG_HW_RUNNING, &bcm2835_wdt_wdd.status);
200054ae194SRasmus Villemoes 	}
20171e9b2f0SGuenter Roeck 
20271e9b2f0SGuenter Roeck 	watchdog_set_restart_priority(&bcm2835_wdt_wdd, 128);
20371e9b2f0SGuenter Roeck 
2045f5aa6f1SGuenter Roeck 	watchdog_stop_on_reboot(&bcm2835_wdt_wdd);
2055f5aa6f1SGuenter Roeck 	err = devm_watchdog_register_device(dev, &bcm2835_wdt_wdd);
206d5f3e24fSWolfram Sang 	if (err)
207938d0a84SLubomir Rintel 		return err;
208938d0a84SLubomir Rintel 
209a4f95810SStefan Wahren 	if (of_device_is_system_power_controller(pdev->dev.parent->of_node)) {
210a4f95810SStefan Wahren 		if (!pm_power_off) {
21133a9f5bcSEric Anholt 			pm_power_off = bcm2835_power_off;
2125e6acc3eSEric Anholt 			bcm2835_power_off_wdt = wdt;
213a4f95810SStefan Wahren 		} else {
214a4f95810SStefan Wahren 			dev_info(dev, "Poweroff handler already present!\n");
215a4f95810SStefan Wahren 		}
2165e6acc3eSEric Anholt 	}
21733a9f5bcSEric Anholt 
218938d0a84SLubomir Rintel 	dev_info(dev, "Broadcom BCM2835 watchdog timer");
219938d0a84SLubomir Rintel 	return 0;
220938d0a84SLubomir Rintel }
221938d0a84SLubomir Rintel 
bcm2835_wdt_remove(struct platform_device * pdev)2227ca823d5SUwe Kleine-König static void bcm2835_wdt_remove(struct platform_device *pdev)
223938d0a84SLubomir Rintel {
22433a9f5bcSEric Anholt 	if (pm_power_off == bcm2835_power_off)
22533a9f5bcSEric Anholt 		pm_power_off = NULL;
226938d0a84SLubomir Rintel }
227938d0a84SLubomir Rintel 
228938d0a84SLubomir Rintel static struct platform_driver bcm2835_wdt_driver = {
229938d0a84SLubomir Rintel 	.probe		= bcm2835_wdt_probe,
2307ca823d5SUwe Kleine-König 	.remove_new	= bcm2835_wdt_remove,
231938d0a84SLubomir Rintel 	.driver = {
232938d0a84SLubomir Rintel 		.name =		"bcm2835-wdt",
233938d0a84SLubomir Rintel 	},
234938d0a84SLubomir Rintel };
235938d0a84SLubomir Rintel module_platform_driver(bcm2835_wdt_driver);
236938d0a84SLubomir Rintel 
237938d0a84SLubomir Rintel module_param(heartbeat, uint, 0);
238938d0a84SLubomir Rintel MODULE_PARM_DESC(heartbeat, "Initial watchdog heartbeat in seconds");
239938d0a84SLubomir Rintel 
240938d0a84SLubomir Rintel module_param(nowayout, bool, 0);
241938d0a84SLubomir Rintel MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default="
242938d0a84SLubomir Rintel 				__MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
243938d0a84SLubomir Rintel 
244215e06f0SStefan Wahren MODULE_ALIAS("platform:bcm2835-wdt");
245938d0a84SLubomir Rintel MODULE_AUTHOR("Lubomir Rintel <lkundrak@v3.sk>");
246938d0a84SLubomir Rintel MODULE_DESCRIPTION("Driver for Broadcom BCM2835 watchdog timer");
247938d0a84SLubomir Rintel MODULE_LICENSE("GPL");
248