1 /* 2 * Watchdog driver for Broadcom BCM2835 3 * 4 * "bcm2708_wdog" driver written by Luke Diamand that was obtained from 5 * branch "rpi-3.6.y" of git://github.com/raspberrypi/linux.git was used 6 * as a hardware reference for the Broadcom BCM2835 watchdog timer. 7 * 8 * Copyright (C) 2013 Lubomir Rintel <lkundrak@v3.sk> 9 * 10 * This program is free software; you can redistribute it and/or modify it 11 * under the terms of the GNU General Public License as published by the 12 * Free Software Foundation; either version 2 of the License, or (at your 13 * option) any later version. 14 */ 15 16 #include <linux/delay.h> 17 #include <linux/reboot.h> 18 #include <linux/types.h> 19 #include <linux/module.h> 20 #include <linux/io.h> 21 #include <linux/watchdog.h> 22 #include <linux/platform_device.h> 23 #include <linux/of_address.h> 24 #include <linux/of_platform.h> 25 26 #define PM_RSTC 0x1c 27 #define PM_RSTS 0x20 28 #define PM_WDOG 0x24 29 30 #define PM_PASSWORD 0x5a000000 31 32 #define PM_WDOG_TIME_SET 0x000fffff 33 #define PM_RSTC_WRCFG_CLR 0xffffffcf 34 #define PM_RSTS_HADWRH_SET 0x00000040 35 #define PM_RSTC_WRCFG_SET 0x00000030 36 #define PM_RSTC_WRCFG_FULL_RESET 0x00000020 37 #define PM_RSTC_RESET 0x00000102 38 39 #define SECS_TO_WDOG_TICKS(x) ((x) << 16) 40 #define WDOG_TICKS_TO_SECS(x) ((x) >> 16) 41 42 struct bcm2835_wdt { 43 void __iomem *base; 44 spinlock_t lock; 45 struct notifier_block restart_handler; 46 }; 47 48 static unsigned int heartbeat; 49 static bool nowayout = WATCHDOG_NOWAYOUT; 50 51 static int bcm2835_wdt_start(struct watchdog_device *wdog) 52 { 53 struct bcm2835_wdt *wdt = watchdog_get_drvdata(wdog); 54 uint32_t cur; 55 unsigned long flags; 56 57 spin_lock_irqsave(&wdt->lock, flags); 58 59 writel_relaxed(PM_PASSWORD | (SECS_TO_WDOG_TICKS(wdog->timeout) & 60 PM_WDOG_TIME_SET), wdt->base + PM_WDOG); 61 cur = readl_relaxed(wdt->base + PM_RSTC); 62 writel_relaxed(PM_PASSWORD | (cur & PM_RSTC_WRCFG_CLR) | 63 PM_RSTC_WRCFG_FULL_RESET, wdt->base + PM_RSTC); 64 65 spin_unlock_irqrestore(&wdt->lock, flags); 66 67 return 0; 68 } 69 70 static int bcm2835_wdt_stop(struct watchdog_device *wdog) 71 { 72 struct bcm2835_wdt *wdt = watchdog_get_drvdata(wdog); 73 74 writel_relaxed(PM_PASSWORD | PM_RSTC_RESET, wdt->base + PM_RSTC); 75 dev_info(wdog->dev, "Watchdog timer stopped"); 76 return 0; 77 } 78 79 static int bcm2835_wdt_set_timeout(struct watchdog_device *wdog, unsigned int t) 80 { 81 wdog->timeout = t; 82 return 0; 83 } 84 85 static unsigned int bcm2835_wdt_get_timeleft(struct watchdog_device *wdog) 86 { 87 struct bcm2835_wdt *wdt = watchdog_get_drvdata(wdog); 88 89 uint32_t ret = readl_relaxed(wdt->base + PM_WDOG); 90 return WDOG_TICKS_TO_SECS(ret & PM_WDOG_TIME_SET); 91 } 92 93 static struct watchdog_ops bcm2835_wdt_ops = { 94 .owner = THIS_MODULE, 95 .start = bcm2835_wdt_start, 96 .stop = bcm2835_wdt_stop, 97 .set_timeout = bcm2835_wdt_set_timeout, 98 .get_timeleft = bcm2835_wdt_get_timeleft, 99 }; 100 101 static struct watchdog_info bcm2835_wdt_info = { 102 .options = WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE | 103 WDIOF_KEEPALIVEPING, 104 .identity = "Broadcom BCM2835 Watchdog timer", 105 }; 106 107 static struct watchdog_device bcm2835_wdt_wdd = { 108 .info = &bcm2835_wdt_info, 109 .ops = &bcm2835_wdt_ops, 110 .min_timeout = 1, 111 .max_timeout = WDOG_TICKS_TO_SECS(PM_WDOG_TIME_SET), 112 .timeout = WDOG_TICKS_TO_SECS(PM_WDOG_TIME_SET), 113 }; 114 115 static int 116 bcm2835_restart(struct notifier_block *this, unsigned long mode, void *cmd) 117 { 118 struct bcm2835_wdt *wdt = container_of(this, struct bcm2835_wdt, 119 restart_handler); 120 u32 val; 121 122 /* use a timeout of 10 ticks (~150us) */ 123 writel_relaxed(10 | PM_PASSWORD, wdt->base + PM_WDOG); 124 val = readl_relaxed(wdt->base + PM_RSTC); 125 val &= PM_RSTC_WRCFG_CLR; 126 val |= PM_PASSWORD | PM_RSTC_WRCFG_FULL_RESET; 127 writel_relaxed(val, wdt->base + PM_RSTC); 128 129 /* No sleeping, possibly atomic. */ 130 mdelay(1); 131 132 return 0; 133 } 134 135 /* 136 * We can't really power off, but if we do the normal reset scheme, and 137 * indicate to bootcode.bin not to reboot, then most of the chip will be 138 * powered off. 139 */ 140 static void bcm2835_power_off(void) 141 { 142 struct device_node *np = 143 of_find_compatible_node(NULL, NULL, "brcm,bcm2835-pm-wdt"); 144 struct platform_device *pdev = of_find_device_by_node(np); 145 struct bcm2835_wdt *wdt = platform_get_drvdata(pdev); 146 u32 val; 147 148 /* 149 * We set the watchdog hard reset bit here to distinguish this reset 150 * from the normal (full) reset. bootcode.bin will not reboot after a 151 * hard reset. 152 */ 153 val = readl_relaxed(wdt->base + PM_RSTS); 154 val &= PM_RSTC_WRCFG_CLR; 155 val |= PM_PASSWORD | PM_RSTS_HADWRH_SET; 156 writel_relaxed(val, wdt->base + PM_RSTS); 157 158 /* Continue with normal reset mechanism */ 159 bcm2835_restart(&wdt->restart_handler, REBOOT_HARD, NULL); 160 } 161 162 static int bcm2835_wdt_probe(struct platform_device *pdev) 163 { 164 struct device *dev = &pdev->dev; 165 struct device_node *np = dev->of_node; 166 struct bcm2835_wdt *wdt; 167 int err; 168 169 wdt = devm_kzalloc(dev, sizeof(struct bcm2835_wdt), GFP_KERNEL); 170 if (!wdt) 171 return -ENOMEM; 172 platform_set_drvdata(pdev, wdt); 173 174 spin_lock_init(&wdt->lock); 175 176 wdt->base = of_iomap(np, 0); 177 if (!wdt->base) { 178 dev_err(dev, "Failed to remap watchdog regs"); 179 return -ENODEV; 180 } 181 182 watchdog_set_drvdata(&bcm2835_wdt_wdd, wdt); 183 watchdog_init_timeout(&bcm2835_wdt_wdd, heartbeat, dev); 184 watchdog_set_nowayout(&bcm2835_wdt_wdd, nowayout); 185 bcm2835_wdt_wdd.parent = &pdev->dev; 186 err = watchdog_register_device(&bcm2835_wdt_wdd); 187 if (err) { 188 dev_err(dev, "Failed to register watchdog device"); 189 iounmap(wdt->base); 190 return err; 191 } 192 193 wdt->restart_handler.notifier_call = bcm2835_restart; 194 wdt->restart_handler.priority = 128; 195 register_restart_handler(&wdt->restart_handler); 196 if (pm_power_off == NULL) 197 pm_power_off = bcm2835_power_off; 198 199 dev_info(dev, "Broadcom BCM2835 watchdog timer"); 200 return 0; 201 } 202 203 static int bcm2835_wdt_remove(struct platform_device *pdev) 204 { 205 struct bcm2835_wdt *wdt = platform_get_drvdata(pdev); 206 207 unregister_restart_handler(&wdt->restart_handler); 208 if (pm_power_off == bcm2835_power_off) 209 pm_power_off = NULL; 210 watchdog_unregister_device(&bcm2835_wdt_wdd); 211 iounmap(wdt->base); 212 213 return 0; 214 } 215 216 static void bcm2835_wdt_shutdown(struct platform_device *pdev) 217 { 218 bcm2835_wdt_stop(&bcm2835_wdt_wdd); 219 } 220 221 static const struct of_device_id bcm2835_wdt_of_match[] = { 222 { .compatible = "brcm,bcm2835-pm-wdt", }, 223 {}, 224 }; 225 MODULE_DEVICE_TABLE(of, bcm2835_wdt_of_match); 226 227 static struct platform_driver bcm2835_wdt_driver = { 228 .probe = bcm2835_wdt_probe, 229 .remove = bcm2835_wdt_remove, 230 .shutdown = bcm2835_wdt_shutdown, 231 .driver = { 232 .name = "bcm2835-wdt", 233 .of_match_table = bcm2835_wdt_of_match, 234 }, 235 }; 236 module_platform_driver(bcm2835_wdt_driver); 237 238 module_param(heartbeat, uint, 0); 239 MODULE_PARM_DESC(heartbeat, "Initial watchdog heartbeat in seconds"); 240 241 module_param(nowayout, bool, 0); 242 MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" 243 __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); 244 245 MODULE_AUTHOR("Lubomir Rintel <lkundrak@v3.sk>"); 246 MODULE_DESCRIPTION("Driver for Broadcom BCM2835 watchdog timer"); 247 MODULE_LICENSE("GPL"); 248