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