Branch data Line data Source code
1 : : // SPDX-License-Identifier: GPL-2.0+ 2 : : /* 3 : : * Watchdog driver for Broadcom BCM2835 4 : : * 5 : : * "bcm2708_wdog" driver written by Luke Diamand that was obtained from 6 : : * branch "rpi-3.6.y" of git://github.com/raspberrypi/linux.git was used 7 : : * as a hardware reference for the Broadcom BCM2835 watchdog timer. 8 : : * 9 : : * Copyright (C) 2013 Lubomir Rintel <lkundrak@v3.sk> 10 : : * 11 : : */ 12 : : 13 : : #include <linux/delay.h> 14 : : #include <linux/types.h> 15 : : #include <linux/mfd/bcm2835-pm.h> 16 : : #include <linux/module.h> 17 : : #include <linux/io.h> 18 : : #include <linux/watchdog.h> 19 : : #include <linux/platform_device.h> 20 : : #include <linux/of_address.h> 21 : : #include <linux/of_platform.h> 22 : : 23 : : #define PM_RSTC 0x1c 24 : : #define PM_RSTS 0x20 25 : : #define PM_WDOG 0x24 26 : : 27 : : #define PM_PASSWORD 0x5a000000 28 : : 29 : : #define PM_WDOG_TIME_SET 0x000fffff 30 : : #define PM_RSTC_WRCFG_CLR 0xffffffcf 31 : : #define PM_RSTS_HADWRH_SET 0x00000040 32 : : #define PM_RSTC_WRCFG_SET 0x00000030 33 : : #define PM_RSTC_WRCFG_FULL_RESET 0x00000020 34 : : #define PM_RSTC_RESET 0x00000102 35 : : #define PM_RSTS_PARTITION_CLR 0xfffffaaa 36 : : 37 : : #define SECS_TO_WDOG_TICKS(x) ((x) << 16) 38 : : #define WDOG_TICKS_TO_SECS(x) ((x) >> 16) 39 : : 40 : : struct bcm2835_wdt { 41 : : void __iomem *base; 42 : : spinlock_t lock; 43 : : }; 44 : : 45 : : static struct bcm2835_wdt *bcm2835_power_off_wdt; 46 : : 47 : : static unsigned int heartbeat; 48 : : static bool nowayout = WATCHDOG_NOWAYOUT; 49 : : 50 : : static bool bcm2835_wdt_is_running(struct bcm2835_wdt *wdt) 51 : : { 52 : : uint32_t cur; 53 : : 54 : 3 : cur = readl(wdt->base + PM_RSTC); 55 : : 56 : 3 : return !!(cur & PM_RSTC_WRCFG_FULL_RESET); 57 : : } 58 : : 59 : 0 : static int bcm2835_wdt_start(struct watchdog_device *wdog) 60 : : { 61 : : struct bcm2835_wdt *wdt = watchdog_get_drvdata(wdog); 62 : : uint32_t cur; 63 : : unsigned long flags; 64 : : 65 : 0 : spin_lock_irqsave(&wdt->lock, flags); 66 : : 67 : 0 : writel_relaxed(PM_PASSWORD | (SECS_TO_WDOG_TICKS(wdog->timeout) & 68 : : PM_WDOG_TIME_SET), wdt->base + PM_WDOG); 69 : 0 : cur = readl_relaxed(wdt->base + PM_RSTC); 70 : 0 : writel_relaxed(PM_PASSWORD | (cur & PM_RSTC_WRCFG_CLR) | 71 : : PM_RSTC_WRCFG_FULL_RESET, wdt->base + PM_RSTC); 72 : : 73 : : spin_unlock_irqrestore(&wdt->lock, flags); 74 : : 75 : 0 : return 0; 76 : : } 77 : : 78 : 0 : static int bcm2835_wdt_stop(struct watchdog_device *wdog) 79 : : { 80 : : struct bcm2835_wdt *wdt = watchdog_get_drvdata(wdog); 81 : : 82 : 0 : writel_relaxed(PM_PASSWORD | PM_RSTC_RESET, wdt->base + PM_RSTC); 83 : 0 : return 0; 84 : : } 85 : : 86 : 0 : static unsigned int bcm2835_wdt_get_timeleft(struct watchdog_device *wdog) 87 : : { 88 : : struct bcm2835_wdt *wdt = watchdog_get_drvdata(wdog); 89 : : 90 : 0 : uint32_t ret = readl_relaxed(wdt->base + PM_WDOG); 91 : 0 : return WDOG_TICKS_TO_SECS(ret & PM_WDOG_TIME_SET); 92 : : } 93 : : 94 : : /* 95 : : * The Raspberry Pi firmware uses the RSTS register to know which partiton 96 : : * to boot from. The partiton value is spread into bits 0, 2, 4, 6, 8, 10. 97 : : * Partiton 63 is a special partition used by the firmware to indicate halt. 98 : : */ 99 : : 100 : 0 : static void __bcm2835_restart(struct bcm2835_wdt *wdt, u8 partition) 101 : : { 102 : : u32 val, rsts; 103 : : 104 : 0 : rsts = (partition & BIT(0)) | ((partition & BIT(1)) << 1) | 105 : 0 : ((partition & BIT(2)) << 2) | ((partition & BIT(3)) << 3) | 106 : 0 : ((partition & BIT(4)) << 4) | ((partition & BIT(5)) << 5); 107 : : 108 : 0 : val = readl_relaxed(wdt->base + PM_RSTS); 109 : 0 : val &= PM_RSTS_PARTITION_CLR; 110 : 0 : val |= PM_PASSWORD | rsts; 111 : : writel_relaxed(val, wdt->base + PM_RSTS); 112 : : 113 : : /* use a timeout of 10 ticks (~150us) */ 114 : 0 : writel_relaxed(10 | PM_PASSWORD, wdt->base + PM_WDOG); 115 : 0 : val = readl_relaxed(wdt->base + PM_RSTC); 116 : 0 : val &= PM_RSTC_WRCFG_CLR; 117 : 0 : val |= PM_PASSWORD | PM_RSTC_WRCFG_FULL_RESET; 118 : : writel_relaxed(val, wdt->base + PM_RSTC); 119 : : 120 : : /* No sleeping, possibly atomic. */ 121 : 0 : mdelay(1); 122 : 0 : } 123 : : 124 : 0 : static int bcm2835_restart(struct watchdog_device *wdog, 125 : : unsigned long action, void *data) 126 : : { 127 : : struct bcm2835_wdt *wdt = watchdog_get_drvdata(wdog); 128 : : 129 : : unsigned long long val; 130 : : u8 partition = 0; 131 : : 132 : 0 : if (data && !kstrtoull(data, 0, &val) && val <= 63) 133 : 0 : partition = val; 134 : : 135 : 0 : __bcm2835_restart(wdt, partition); 136 : : 137 : 0 : return 0; 138 : : } 139 : : 140 : : static const struct watchdog_ops bcm2835_wdt_ops = { 141 : : .owner = THIS_MODULE, 142 : : .start = bcm2835_wdt_start, 143 : : .stop = bcm2835_wdt_stop, 144 : : .get_timeleft = bcm2835_wdt_get_timeleft, 145 : : .restart = bcm2835_restart, 146 : : }; 147 : : 148 : : static const struct watchdog_info bcm2835_wdt_info = { 149 : : .options = WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE | 150 : : WDIOF_KEEPALIVEPING, 151 : : .identity = "Broadcom BCM2835 Watchdog timer", 152 : : }; 153 : : 154 : : static struct watchdog_device bcm2835_wdt_wdd = { 155 : : .info = &bcm2835_wdt_info, 156 : : .ops = &bcm2835_wdt_ops, 157 : : .min_timeout = 1, 158 : : .max_timeout = WDOG_TICKS_TO_SECS(PM_WDOG_TIME_SET), 159 : : .timeout = WDOG_TICKS_TO_SECS(PM_WDOG_TIME_SET), 160 : : }; 161 : : 162 : : /* 163 : : * We can't really power off, but if we do the normal reset scheme, and 164 : : * indicate to bootcode.bin not to reboot, then most of the chip will be 165 : : * powered off. 166 : : */ 167 : 0 : static void bcm2835_power_off(void) 168 : : { 169 : 0 : struct bcm2835_wdt *wdt = bcm2835_power_off_wdt; 170 : : 171 : : /* Partition 63 tells the firmware that this is a halt */ 172 : 0 : __bcm2835_restart(wdt, 63); 173 : 0 : } 174 : : 175 : 3 : static int bcm2835_wdt_probe(struct platform_device *pdev) 176 : : { 177 : 3 : struct bcm2835_pm *pm = dev_get_drvdata(pdev->dev.parent); 178 : 3 : struct device *dev = &pdev->dev; 179 : : struct bcm2835_wdt *wdt; 180 : : int err; 181 : : 182 : : wdt = devm_kzalloc(dev, sizeof(struct bcm2835_wdt), GFP_KERNEL); 183 : 3 : if (!wdt) 184 : : return -ENOMEM; 185 : : 186 : 3 : spin_lock_init(&wdt->lock); 187 : : 188 : 3 : wdt->base = pm->base; 189 : : 190 : : watchdog_set_drvdata(&bcm2835_wdt_wdd, wdt); 191 : 3 : watchdog_init_timeout(&bcm2835_wdt_wdd, heartbeat, dev); 192 : 3 : watchdog_set_nowayout(&bcm2835_wdt_wdd, nowayout); 193 : 3 : bcm2835_wdt_wdd.parent = dev; 194 : 3 : if (bcm2835_wdt_is_running(wdt)) { 195 : : /* 196 : : * The currently active timeout value (set by the 197 : : * bootloader) may be different from the module 198 : : * heartbeat parameter or the value in device 199 : : * tree. But we just need to set WDOG_HW_RUNNING, 200 : : * because then the framework will "immediately" ping 201 : : * the device, updating the timeout. 202 : : */ 203 : 0 : set_bit(WDOG_HW_RUNNING, &bcm2835_wdt_wdd.status); 204 : : } 205 : : 206 : 3 : watchdog_set_restart_priority(&bcm2835_wdt_wdd, 128); 207 : : 208 : : watchdog_stop_on_reboot(&bcm2835_wdt_wdd); 209 : 3 : err = devm_watchdog_register_device(dev, &bcm2835_wdt_wdd); 210 : 3 : if (err) 211 : : return err; 212 : : 213 : 3 : if (pm_power_off == NULL) { 214 : 3 : pm_power_off = bcm2835_power_off; 215 : 3 : bcm2835_power_off_wdt = wdt; 216 : : } 217 : : 218 : 3 : dev_info(dev, "Broadcom BCM2835 watchdog timer"); 219 : 3 : return 0; 220 : : } 221 : : 222 : 0 : static int bcm2835_wdt_remove(struct platform_device *pdev) 223 : : { 224 : 0 : if (pm_power_off == bcm2835_power_off) 225 : 0 : pm_power_off = NULL; 226 : : 227 : 0 : return 0; 228 : : } 229 : : 230 : : static struct platform_driver bcm2835_wdt_driver = { 231 : : .probe = bcm2835_wdt_probe, 232 : : .remove = bcm2835_wdt_remove, 233 : : .driver = { 234 : : .name = "bcm2835-wdt", 235 : : }, 236 : : }; 237 : 3 : module_platform_driver(bcm2835_wdt_driver); 238 : : 239 : : module_param(heartbeat, uint, 0); 240 : : MODULE_PARM_DESC(heartbeat, "Initial watchdog heartbeat in seconds"); 241 : : 242 : : module_param(nowayout, bool, 0); 243 : : MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" 244 : : __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); 245 : : 246 : : MODULE_ALIAS("platform:bcm2835-wdt"); 247 : : MODULE_AUTHOR("Lubomir Rintel <lkundrak@v3.sk>"); 248 : : MODULE_DESCRIPTION("Driver for Broadcom BCM2835 watchdog timer"); 249 : : MODULE_LICENSE("GPL");