Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0+
0002 /*
0003  * Watchdog driver for Broadcom BCM2835
0004  *
0005  * "bcm2708_wdog" driver written by Luke Diamand that was obtained from
0006  * branch "rpi-3.6.y" of git://github.com/raspberrypi/linux.git was used
0007  * as a hardware reference for the Broadcom BCM2835 watchdog timer.
0008  *
0009  * Copyright (C) 2013 Lubomir Rintel <lkundrak@v3.sk>
0010  *
0011  */
0012 
0013 #include <linux/delay.h>
0014 #include <linux/types.h>
0015 #include <linux/mfd/bcm2835-pm.h>
0016 #include <linux/module.h>
0017 #include <linux/io.h>
0018 #include <linux/watchdog.h>
0019 #include <linux/platform_device.h>
0020 #include <linux/of_address.h>
0021 #include <linux/of_platform.h>
0022 
0023 #define PM_RSTC             0x1c
0024 #define PM_RSTS             0x20
0025 #define PM_WDOG             0x24
0026 
0027 #define PM_PASSWORD         0x5a000000
0028 
0029 #define PM_WDOG_TIME_SET        0x000fffff
0030 #define PM_RSTC_WRCFG_CLR       0xffffffcf
0031 #define PM_RSTS_HADWRH_SET      0x00000040
0032 #define PM_RSTC_WRCFG_SET       0x00000030
0033 #define PM_RSTC_WRCFG_FULL_RESET    0x00000020
0034 #define PM_RSTC_RESET           0x00000102
0035 
0036 /*
0037  * The Raspberry Pi firmware uses the RSTS register to know which partition
0038  * to boot from. The partition value is spread into bits 0, 2, 4, 6, 8, 10.
0039  * Partition 63 is a special partition used by the firmware to indicate halt.
0040  */
0041 #define PM_RSTS_RASPBERRYPI_HALT    0x555
0042 
0043 #define SECS_TO_WDOG_TICKS(x) ((x) << 16)
0044 #define WDOG_TICKS_TO_SECS(x) ((x) >> 16)
0045 
0046 struct bcm2835_wdt {
0047     void __iomem        *base;
0048     spinlock_t      lock;
0049 };
0050 
0051 static struct bcm2835_wdt *bcm2835_power_off_wdt;
0052 
0053 static unsigned int heartbeat;
0054 static bool nowayout = WATCHDOG_NOWAYOUT;
0055 
0056 static bool bcm2835_wdt_is_running(struct bcm2835_wdt *wdt)
0057 {
0058     uint32_t cur;
0059 
0060     cur = readl(wdt->base + PM_RSTC);
0061 
0062     return !!(cur & PM_RSTC_WRCFG_FULL_RESET);
0063 }
0064 
0065 static int bcm2835_wdt_start(struct watchdog_device *wdog)
0066 {
0067     struct bcm2835_wdt *wdt = watchdog_get_drvdata(wdog);
0068     uint32_t cur;
0069     unsigned long flags;
0070 
0071     spin_lock_irqsave(&wdt->lock, flags);
0072 
0073     writel_relaxed(PM_PASSWORD | (SECS_TO_WDOG_TICKS(wdog->timeout) &
0074                 PM_WDOG_TIME_SET), wdt->base + PM_WDOG);
0075     cur = readl_relaxed(wdt->base + PM_RSTC);
0076     writel_relaxed(PM_PASSWORD | (cur & PM_RSTC_WRCFG_CLR) |
0077           PM_RSTC_WRCFG_FULL_RESET, wdt->base + PM_RSTC);
0078 
0079     spin_unlock_irqrestore(&wdt->lock, flags);
0080 
0081     return 0;
0082 }
0083 
0084 static int bcm2835_wdt_stop(struct watchdog_device *wdog)
0085 {
0086     struct bcm2835_wdt *wdt = watchdog_get_drvdata(wdog);
0087 
0088     writel_relaxed(PM_PASSWORD | PM_RSTC_RESET, wdt->base + PM_RSTC);
0089     return 0;
0090 }
0091 
0092 static unsigned int bcm2835_wdt_get_timeleft(struct watchdog_device *wdog)
0093 {
0094     struct bcm2835_wdt *wdt = watchdog_get_drvdata(wdog);
0095 
0096     uint32_t ret = readl_relaxed(wdt->base + PM_WDOG);
0097     return WDOG_TICKS_TO_SECS(ret & PM_WDOG_TIME_SET);
0098 }
0099 
0100 static void __bcm2835_restart(struct bcm2835_wdt *wdt)
0101 {
0102     u32 val;
0103 
0104     /* use a timeout of 10 ticks (~150us) */
0105     writel_relaxed(10 | PM_PASSWORD, wdt->base + PM_WDOG);
0106     val = readl_relaxed(wdt->base + PM_RSTC);
0107     val &= PM_RSTC_WRCFG_CLR;
0108     val |= PM_PASSWORD | PM_RSTC_WRCFG_FULL_RESET;
0109     writel_relaxed(val, wdt->base + PM_RSTC);
0110 
0111     /* No sleeping, possibly atomic. */
0112     mdelay(1);
0113 }
0114 
0115 static int bcm2835_restart(struct watchdog_device *wdog,
0116                unsigned long action, void *data)
0117 {
0118     struct bcm2835_wdt *wdt = watchdog_get_drvdata(wdog);
0119 
0120     __bcm2835_restart(wdt);
0121 
0122     return 0;
0123 }
0124 
0125 static const struct watchdog_ops bcm2835_wdt_ops = {
0126     .owner =    THIS_MODULE,
0127     .start =    bcm2835_wdt_start,
0128     .stop =     bcm2835_wdt_stop,
0129     .get_timeleft = bcm2835_wdt_get_timeleft,
0130     .restart =  bcm2835_restart,
0131 };
0132 
0133 static const struct watchdog_info bcm2835_wdt_info = {
0134     .options =  WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE |
0135             WDIOF_KEEPALIVEPING,
0136     .identity = "Broadcom BCM2835 Watchdog timer",
0137 };
0138 
0139 static struct watchdog_device bcm2835_wdt_wdd = {
0140     .info =     &bcm2835_wdt_info,
0141     .ops =      &bcm2835_wdt_ops,
0142     .min_timeout =  1,
0143     .max_timeout =  WDOG_TICKS_TO_SECS(PM_WDOG_TIME_SET),
0144     .timeout =  WDOG_TICKS_TO_SECS(PM_WDOG_TIME_SET),
0145 };
0146 
0147 /*
0148  * We can't really power off, but if we do the normal reset scheme, and
0149  * indicate to bootcode.bin not to reboot, then most of the chip will be
0150  * powered off.
0151  */
0152 static void bcm2835_power_off(void)
0153 {
0154     struct bcm2835_wdt *wdt = bcm2835_power_off_wdt;
0155     u32 val;
0156 
0157     /*
0158      * We set the watchdog hard reset bit here to distinguish this reset
0159      * from the normal (full) reset. bootcode.bin will not reboot after a
0160      * hard reset.
0161      */
0162     val = readl_relaxed(wdt->base + PM_RSTS);
0163     val |= PM_PASSWORD | PM_RSTS_RASPBERRYPI_HALT;
0164     writel_relaxed(val, wdt->base + PM_RSTS);
0165 
0166     /* Continue with normal reset mechanism */
0167     __bcm2835_restart(wdt);
0168 }
0169 
0170 static int bcm2835_wdt_probe(struct platform_device *pdev)
0171 {
0172     struct bcm2835_pm *pm = dev_get_drvdata(pdev->dev.parent);
0173     struct device *dev = &pdev->dev;
0174     struct bcm2835_wdt *wdt;
0175     int err;
0176 
0177     wdt = devm_kzalloc(dev, sizeof(struct bcm2835_wdt), GFP_KERNEL);
0178     if (!wdt)
0179         return -ENOMEM;
0180 
0181     spin_lock_init(&wdt->lock);
0182 
0183     wdt->base = pm->base;
0184 
0185     watchdog_set_drvdata(&bcm2835_wdt_wdd, wdt);
0186     watchdog_init_timeout(&bcm2835_wdt_wdd, heartbeat, dev);
0187     watchdog_set_nowayout(&bcm2835_wdt_wdd, nowayout);
0188     bcm2835_wdt_wdd.parent = dev;
0189     if (bcm2835_wdt_is_running(wdt)) {
0190         /*
0191          * The currently active timeout value (set by the
0192          * bootloader) may be different from the module
0193          * heartbeat parameter or the value in device
0194          * tree. But we just need to set WDOG_HW_RUNNING,
0195          * because then the framework will "immediately" ping
0196          * the device, updating the timeout.
0197          */
0198         set_bit(WDOG_HW_RUNNING, &bcm2835_wdt_wdd.status);
0199     }
0200 
0201     watchdog_set_restart_priority(&bcm2835_wdt_wdd, 128);
0202 
0203     watchdog_stop_on_reboot(&bcm2835_wdt_wdd);
0204     err = devm_watchdog_register_device(dev, &bcm2835_wdt_wdd);
0205     if (err)
0206         return err;
0207 
0208     if (of_device_is_system_power_controller(pdev->dev.parent->of_node)) {
0209         if (!pm_power_off) {
0210             pm_power_off = bcm2835_power_off;
0211             bcm2835_power_off_wdt = wdt;
0212         } else {
0213             dev_info(dev, "Poweroff handler already present!\n");
0214         }
0215     }
0216 
0217     dev_info(dev, "Broadcom BCM2835 watchdog timer");
0218     return 0;
0219 }
0220 
0221 static int bcm2835_wdt_remove(struct platform_device *pdev)
0222 {
0223     if (pm_power_off == bcm2835_power_off)
0224         pm_power_off = NULL;
0225 
0226     return 0;
0227 }
0228 
0229 static struct platform_driver bcm2835_wdt_driver = {
0230     .probe      = bcm2835_wdt_probe,
0231     .remove     = bcm2835_wdt_remove,
0232     .driver = {
0233         .name =     "bcm2835-wdt",
0234     },
0235 };
0236 module_platform_driver(bcm2835_wdt_driver);
0237 
0238 module_param(heartbeat, uint, 0);
0239 MODULE_PARM_DESC(heartbeat, "Initial watchdog heartbeat in seconds");
0240 
0241 module_param(nowayout, bool, 0);
0242 MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default="
0243                 __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
0244 
0245 MODULE_ALIAS("platform:bcm2835-wdt");
0246 MODULE_AUTHOR("Lubomir Rintel <lkundrak@v3.sk>");
0247 MODULE_DESCRIPTION("Driver for Broadcom BCM2835 watchdog timer");
0248 MODULE_LICENSE("GPL");