248 lines · c
1// SPDX-License-Identifier: GPL-2.0+2/*3 * Watchdog driver for Broadcom BCM28354 *5 * "bcm2708_wdog" driver written by Luke Diamand that was obtained from6 * branch "rpi-3.6.y" of git://github.com/raspberrypi/linux.git was used7 * 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 0x1c24#define PM_RSTS 0x2025#define PM_WDOG 0x2426 27#define PM_PASSWORD 0x5a00000028 29#define PM_WDOG_TIME_SET 0x000fffff30#define PM_RSTC_WRCFG_CLR 0xffffffcf31#define PM_RSTS_HADWRH_SET 0x0000004032#define PM_RSTC_WRCFG_SET 0x0000003033#define PM_RSTC_WRCFG_FULL_RESET 0x0000002034#define PM_RSTC_RESET 0x0000010235 36/*37 * The Raspberry Pi firmware uses the RSTS register to know which partition38 * to boot from. The partition value is spread into bits 0, 2, 4, 6, 8, 10.39 * Partition 63 is a special partition used by the firmware to indicate halt.40 */41#define PM_RSTS_RASPBERRYPI_HALT 0x55542 43#define SECS_TO_WDOG_TICKS(x) ((x) << 16)44#define WDOG_TICKS_TO_SECS(x) ((x) >> 16)45#define WDOG_TICKS_TO_MSECS(x) ((x) * 1000 >> 16)46 47struct bcm2835_wdt {48 void __iomem *base;49 spinlock_t lock;50};51 52static struct bcm2835_wdt *bcm2835_power_off_wdt;53 54static unsigned int heartbeat;55static bool nowayout = WATCHDOG_NOWAYOUT;56 57static bool bcm2835_wdt_is_running(struct bcm2835_wdt *wdt)58{59 uint32_t cur;60 61 cur = readl(wdt->base + PM_RSTC);62 63 return !!(cur & PM_RSTC_WRCFG_FULL_RESET);64}65 66static int bcm2835_wdt_start(struct watchdog_device *wdog)67{68 struct bcm2835_wdt *wdt = watchdog_get_drvdata(wdog);69 uint32_t cur;70 unsigned long flags;71 72 spin_lock_irqsave(&wdt->lock, flags);73 74 writel_relaxed(PM_PASSWORD | (SECS_TO_WDOG_TICKS(wdog->timeout) &75 PM_WDOG_TIME_SET), wdt->base + PM_WDOG);76 cur = readl_relaxed(wdt->base + PM_RSTC);77 writel_relaxed(PM_PASSWORD | (cur & PM_RSTC_WRCFG_CLR) |78 PM_RSTC_WRCFG_FULL_RESET, wdt->base + PM_RSTC);79 80 spin_unlock_irqrestore(&wdt->lock, flags);81 82 return 0;83}84 85static int bcm2835_wdt_stop(struct watchdog_device *wdog)86{87 struct bcm2835_wdt *wdt = watchdog_get_drvdata(wdog);88 89 writel_relaxed(PM_PASSWORD | PM_RSTC_RESET, wdt->base + PM_RSTC);90 return 0;91}92 93static unsigned int bcm2835_wdt_get_timeleft(struct watchdog_device *wdog)94{95 struct bcm2835_wdt *wdt = watchdog_get_drvdata(wdog);96 97 uint32_t ret = readl_relaxed(wdt->base + PM_WDOG);98 return WDOG_TICKS_TO_SECS(ret & PM_WDOG_TIME_SET);99}100 101static void __bcm2835_restart(struct bcm2835_wdt *wdt)102{103 u32 val;104 105 /* use a timeout of 10 ticks (~150us) */106 writel_relaxed(10 | PM_PASSWORD, wdt->base + PM_WDOG);107 val = readl_relaxed(wdt->base + PM_RSTC);108 val &= PM_RSTC_WRCFG_CLR;109 val |= PM_PASSWORD | PM_RSTC_WRCFG_FULL_RESET;110 writel_relaxed(val, wdt->base + PM_RSTC);111 112 /* No sleeping, possibly atomic. */113 mdelay(1);114}115 116static int bcm2835_restart(struct watchdog_device *wdog,117 unsigned long action, void *data)118{119 struct bcm2835_wdt *wdt = watchdog_get_drvdata(wdog);120 121 __bcm2835_restart(wdt);122 123 return 0;124}125 126static const struct watchdog_ops bcm2835_wdt_ops = {127 .owner = THIS_MODULE,128 .start = bcm2835_wdt_start,129 .stop = bcm2835_wdt_stop,130 .get_timeleft = bcm2835_wdt_get_timeleft,131 .restart = bcm2835_restart,132};133 134static const struct watchdog_info bcm2835_wdt_info = {135 .options = WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE |136 WDIOF_KEEPALIVEPING,137 .identity = "Broadcom BCM2835 Watchdog timer",138};139 140static struct watchdog_device bcm2835_wdt_wdd = {141 .info = &bcm2835_wdt_info,142 .ops = &bcm2835_wdt_ops,143 .min_timeout = 1,144 .max_hw_heartbeat_ms = WDOG_TICKS_TO_MSECS(PM_WDOG_TIME_SET),145 .timeout = WDOG_TICKS_TO_SECS(PM_WDOG_TIME_SET),146};147 148/*149 * We can't really power off, but if we do the normal reset scheme, and150 * indicate to bootcode.bin not to reboot, then most of the chip will be151 * powered off.152 */153static void bcm2835_power_off(void)154{155 struct bcm2835_wdt *wdt = bcm2835_power_off_wdt;156 u32 val;157 158 /*159 * We set the watchdog hard reset bit here to distinguish this reset160 * from the normal (full) reset. bootcode.bin will not reboot after a161 * hard reset.162 */163 val = readl_relaxed(wdt->base + PM_RSTS);164 val |= PM_PASSWORD | PM_RSTS_RASPBERRYPI_HALT;165 writel_relaxed(val, wdt->base + PM_RSTS);166 167 /* Continue with normal reset mechanism */168 __bcm2835_restart(wdt);169}170 171static int bcm2835_wdt_probe(struct platform_device *pdev)172{173 struct bcm2835_pm *pm = dev_get_drvdata(pdev->dev.parent);174 struct device *dev = &pdev->dev;175 struct bcm2835_wdt *wdt;176 int err;177 178 wdt = devm_kzalloc(dev, sizeof(struct bcm2835_wdt), GFP_KERNEL);179 if (!wdt)180 return -ENOMEM;181 182 spin_lock_init(&wdt->lock);183 184 wdt->base = pm->base;185 186 watchdog_set_drvdata(&bcm2835_wdt_wdd, wdt);187 watchdog_init_timeout(&bcm2835_wdt_wdd, heartbeat, dev);188 watchdog_set_nowayout(&bcm2835_wdt_wdd, nowayout);189 bcm2835_wdt_wdd.parent = dev;190 if (bcm2835_wdt_is_running(wdt)) {191 /*192 * The currently active timeout value (set by the193 * bootloader) may be different from the module194 * heartbeat parameter or the value in device195 * tree. But we just need to set WDOG_HW_RUNNING,196 * because then the framework will "immediately" ping197 * the device, updating the timeout.198 */199 set_bit(WDOG_HW_RUNNING, &bcm2835_wdt_wdd.status);200 }201 202 watchdog_set_restart_priority(&bcm2835_wdt_wdd, 128);203 204 watchdog_stop_on_reboot(&bcm2835_wdt_wdd);205 err = devm_watchdog_register_device(dev, &bcm2835_wdt_wdd);206 if (err)207 return err;208 209 if (of_device_is_system_power_controller(pdev->dev.parent->of_node)) {210 if (!pm_power_off) {211 pm_power_off = bcm2835_power_off;212 bcm2835_power_off_wdt = wdt;213 } else {214 dev_info(dev, "Poweroff handler already present!\n");215 }216 }217 218 dev_info(dev, "Broadcom BCM2835 watchdog timer");219 return 0;220}221 222static void bcm2835_wdt_remove(struct platform_device *pdev)223{224 if (pm_power_off == bcm2835_power_off)225 pm_power_off = NULL;226}227 228static struct platform_driver bcm2835_wdt_driver = {229 .probe = bcm2835_wdt_probe,230 .remove_new = bcm2835_wdt_remove,231 .driver = {232 .name = "bcm2835-wdt",233 },234};235module_platform_driver(bcm2835_wdt_driver);236 237module_param(heartbeat, uint, 0);238MODULE_PARM_DESC(heartbeat, "Initial watchdog heartbeat in seconds");239 240module_param(nowayout, bool, 0);241MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default="242 __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");243 244MODULE_ALIAS("platform:bcm2835-wdt");245MODULE_AUTHOR("Lubomir Rintel <lkundrak@v3.sk>");246MODULE_DESCRIPTION("Driver for Broadcom BCM2835 watchdog timer");247MODULE_LICENSE("GPL");248