brintos

brintos / linux-shallow public Read only

0
0
Text · 6.6 KiB · bb001c5 Raw
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