xref: /linux/drivers/watchdog/apple_wdt.c (revision 0ea5c948cb64bab5bc7a5516774eb8536f05aa0d)
14ed224aeSSven Peter // SPDX-License-Identifier: GPL-2.0-only OR MIT
24ed224aeSSven Peter /*
34ed224aeSSven Peter  * Apple SoC Watchdog driver
44ed224aeSSven Peter  *
54ed224aeSSven Peter  * Copyright (C) The Asahi Linux Contributors
64ed224aeSSven Peter  */
74ed224aeSSven Peter 
84ed224aeSSven Peter #include <linux/bits.h>
94ed224aeSSven Peter #include <linux/clk.h>
104ed224aeSSven Peter #include <linux/delay.h>
114ed224aeSSven Peter #include <linux/io.h>
124ed224aeSSven Peter #include <linux/kernel.h>
134ed224aeSSven Peter #include <linux/limits.h>
144ed224aeSSven Peter #include <linux/module.h>
154ed224aeSSven Peter #include <linux/of.h>
164ed224aeSSven Peter #include <linux/platform_device.h>
174ed224aeSSven Peter #include <linux/watchdog.h>
184ed224aeSSven Peter 
194ed224aeSSven Peter /*
204ed224aeSSven Peter  * Apple Watchdog MMIO registers
214ed224aeSSven Peter  *
224ed224aeSSven Peter  * This HW block has three separate watchdogs. WD0 resets the machine
234ed224aeSSven Peter  * to recovery mode and is not very useful for us. WD1 and WD2 trigger a normal
244ed224aeSSven Peter  * machine reset. WD0 additionally supports a configurable interrupt.
254ed224aeSSven Peter  * This information can be used to implement pretimeout support at a later time.
264ed224aeSSven Peter  *
274ed224aeSSven Peter  * APPLE_WDT_WDx_CUR_TIME is a simple counter incremented for each tick of the
284ed224aeSSven Peter  * reference clock. It can also be overwritten to any value.
294ed224aeSSven Peter  * Whenever APPLE_WDT_CTRL_RESET_EN is set in APPLE_WDT_WDx_CTRL and
304ed224aeSSven Peter  * APPLE_WDT_WDx_CUR_TIME >= APPLE_WDT_WDx_BITE_TIME the entire machine is
314ed224aeSSven Peter  * reset.
324ed224aeSSven Peter  * Whenever APPLE_WDT_CTRL_IRQ_EN is set and APPLE_WDTx_WD1_CUR_TIME >=
334ed224aeSSven Peter  * APPLE_WDTx_WD1_BARK_TIME an interrupt is triggered and
344ed224aeSSven Peter  * APPLE_WDT_CTRL_IRQ_STATUS is set. The interrupt can be cleared by writing
354ed224aeSSven Peter  * 1 to APPLE_WDT_CTRL_IRQ_STATUS.
364ed224aeSSven Peter  */
374ed224aeSSven Peter #define APPLE_WDT_WD0_CUR_TIME		0x00
384ed224aeSSven Peter #define APPLE_WDT_WD0_BITE_TIME		0x04
394ed224aeSSven Peter #define APPLE_WDT_WD0_BARK_TIME		0x08
404ed224aeSSven Peter #define APPLE_WDT_WD0_CTRL		0x0c
414ed224aeSSven Peter 
424ed224aeSSven Peter #define APPLE_WDT_WD1_CUR_TIME		0x10
434ed224aeSSven Peter #define APPLE_WDT_WD1_BITE_TIME		0x14
444ed224aeSSven Peter #define APPLE_WDT_WD1_CTRL		0x1c
454ed224aeSSven Peter 
464ed224aeSSven Peter #define APPLE_WDT_WD2_CUR_TIME		0x20
474ed224aeSSven Peter #define APPLE_WDT_WD2_BITE_TIME		0x24
484ed224aeSSven Peter #define APPLE_WDT_WD2_CTRL		0x2c
494ed224aeSSven Peter 
504ed224aeSSven Peter #define APPLE_WDT_CTRL_IRQ_EN		BIT(0)
514ed224aeSSven Peter #define APPLE_WDT_CTRL_IRQ_STATUS	BIT(1)
524ed224aeSSven Peter #define APPLE_WDT_CTRL_RESET_EN		BIT(2)
534ed224aeSSven Peter 
544ed224aeSSven Peter #define APPLE_WDT_TIMEOUT_DEFAULT	30
554ed224aeSSven Peter 
564ed224aeSSven Peter struct apple_wdt {
574ed224aeSSven Peter 	struct watchdog_device wdd;
584ed224aeSSven Peter 	void __iomem *regs;
594ed224aeSSven Peter 	unsigned long clk_rate;
604ed224aeSSven Peter };
614ed224aeSSven Peter 
to_apple_wdt(struct watchdog_device * wdd)624ed224aeSSven Peter static struct apple_wdt *to_apple_wdt(struct watchdog_device *wdd)
634ed224aeSSven Peter {
644ed224aeSSven Peter 	return container_of(wdd, struct apple_wdt, wdd);
654ed224aeSSven Peter }
664ed224aeSSven Peter 
apple_wdt_start(struct watchdog_device * wdd)674ed224aeSSven Peter static int apple_wdt_start(struct watchdog_device *wdd)
684ed224aeSSven Peter {
694ed224aeSSven Peter 	struct apple_wdt *wdt = to_apple_wdt(wdd);
704ed224aeSSven Peter 
714ed224aeSSven Peter 	writel_relaxed(0, wdt->regs + APPLE_WDT_WD1_CUR_TIME);
724ed224aeSSven Peter 	writel_relaxed(APPLE_WDT_CTRL_RESET_EN, wdt->regs + APPLE_WDT_WD1_CTRL);
734ed224aeSSven Peter 
744ed224aeSSven Peter 	return 0;
754ed224aeSSven Peter }
764ed224aeSSven Peter 
apple_wdt_stop(struct watchdog_device * wdd)774ed224aeSSven Peter static int apple_wdt_stop(struct watchdog_device *wdd)
784ed224aeSSven Peter {
794ed224aeSSven Peter 	struct apple_wdt *wdt = to_apple_wdt(wdd);
804ed224aeSSven Peter 
814ed224aeSSven Peter 	writel_relaxed(0, wdt->regs + APPLE_WDT_WD1_CTRL);
824ed224aeSSven Peter 
834ed224aeSSven Peter 	return 0;
844ed224aeSSven Peter }
854ed224aeSSven Peter 
apple_wdt_ping(struct watchdog_device * wdd)864ed224aeSSven Peter static int apple_wdt_ping(struct watchdog_device *wdd)
874ed224aeSSven Peter {
884ed224aeSSven Peter 	struct apple_wdt *wdt = to_apple_wdt(wdd);
894ed224aeSSven Peter 
904ed224aeSSven Peter 	writel_relaxed(0, wdt->regs + APPLE_WDT_WD1_CUR_TIME);
914ed224aeSSven Peter 
924ed224aeSSven Peter 	return 0;
934ed224aeSSven Peter }
944ed224aeSSven Peter 
apple_wdt_set_timeout(struct watchdog_device * wdd,unsigned int s)954ed224aeSSven Peter static int apple_wdt_set_timeout(struct watchdog_device *wdd, unsigned int s)
964ed224aeSSven Peter {
974ed224aeSSven Peter 	struct apple_wdt *wdt = to_apple_wdt(wdd);
984ed224aeSSven Peter 
994ed224aeSSven Peter 	writel_relaxed(0, wdt->regs + APPLE_WDT_WD1_CUR_TIME);
1004ed224aeSSven Peter 	writel_relaxed(wdt->clk_rate * s, wdt->regs + APPLE_WDT_WD1_BITE_TIME);
1014ed224aeSSven Peter 
1024ed224aeSSven Peter 	wdd->timeout = s;
1034ed224aeSSven Peter 
1044ed224aeSSven Peter 	return 0;
1054ed224aeSSven Peter }
1064ed224aeSSven Peter 
apple_wdt_get_timeleft(struct watchdog_device * wdd)1074ed224aeSSven Peter static unsigned int apple_wdt_get_timeleft(struct watchdog_device *wdd)
1084ed224aeSSven Peter {
1094ed224aeSSven Peter 	struct apple_wdt *wdt = to_apple_wdt(wdd);
1104ed224aeSSven Peter 	u32 cur_time, reset_time;
1114ed224aeSSven Peter 
1124ed224aeSSven Peter 	cur_time = readl_relaxed(wdt->regs + APPLE_WDT_WD1_CUR_TIME);
1134ed224aeSSven Peter 	reset_time = readl_relaxed(wdt->regs + APPLE_WDT_WD1_BITE_TIME);
1144ed224aeSSven Peter 
1154ed224aeSSven Peter 	return (reset_time - cur_time) / wdt->clk_rate;
1164ed224aeSSven Peter }
1174ed224aeSSven Peter 
apple_wdt_restart(struct watchdog_device * wdd,unsigned long mode,void * cmd)1184ed224aeSSven Peter static int apple_wdt_restart(struct watchdog_device *wdd, unsigned long mode,
1194ed224aeSSven Peter 			     void *cmd)
1204ed224aeSSven Peter {
1214ed224aeSSven Peter 	struct apple_wdt *wdt = to_apple_wdt(wdd);
1224ed224aeSSven Peter 
1234ed224aeSSven Peter 	writel_relaxed(APPLE_WDT_CTRL_RESET_EN, wdt->regs + APPLE_WDT_WD1_CTRL);
1244ed224aeSSven Peter 	writel_relaxed(0, wdt->regs + APPLE_WDT_WD1_BITE_TIME);
1254ed224aeSSven Peter 	writel_relaxed(0, wdt->regs + APPLE_WDT_WD1_CUR_TIME);
1264ed224aeSSven Peter 
1274ed224aeSSven Peter 	/*
1284ed224aeSSven Peter 	 * Flush writes and then wait for the SoC to reset. Even though the
1294ed224aeSSven Peter 	 * reset is queued almost immediately experiments have shown that it
1304ed224aeSSven Peter 	 * can take up to ~20-25ms until the SoC is actually reset. Just wait
1314ed224aeSSven Peter 	 * 50ms here to be safe.
1324ed224aeSSven Peter 	 */
1334ed224aeSSven Peter 	(void)readl_relaxed(wdt->regs + APPLE_WDT_WD1_CUR_TIME);
1344ed224aeSSven Peter 	mdelay(50);
1354ed224aeSSven Peter 
1364ed224aeSSven Peter 	return 0;
1374ed224aeSSven Peter }
1384ed224aeSSven Peter 
1394ed224aeSSven Peter static struct watchdog_ops apple_wdt_ops = {
1404ed224aeSSven Peter 	.owner = THIS_MODULE,
1414ed224aeSSven Peter 	.start = apple_wdt_start,
1424ed224aeSSven Peter 	.stop = apple_wdt_stop,
1434ed224aeSSven Peter 	.ping = apple_wdt_ping,
1444ed224aeSSven Peter 	.set_timeout = apple_wdt_set_timeout,
1454ed224aeSSven Peter 	.get_timeleft = apple_wdt_get_timeleft,
1464ed224aeSSven Peter 	.restart = apple_wdt_restart,
1474ed224aeSSven Peter };
1484ed224aeSSven Peter 
1494ed224aeSSven Peter static struct watchdog_info apple_wdt_info = {
1504ed224aeSSven Peter 	.identity = "Apple SoC Watchdog",
1514ed224aeSSven Peter 	.options = WDIOF_MAGICCLOSE | WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT,
1524ed224aeSSven Peter };
1534ed224aeSSven Peter 
apple_wdt_probe(struct platform_device * pdev)1544ed224aeSSven Peter static int apple_wdt_probe(struct platform_device *pdev)
1554ed224aeSSven Peter {
1564ed224aeSSven Peter 	struct device *dev = &pdev->dev;
1574ed224aeSSven Peter 	struct apple_wdt *wdt;
1584ed224aeSSven Peter 	struct clk *clk;
1594ed224aeSSven Peter 	u32 wdt_ctrl;
1604ed224aeSSven Peter 
1614ed224aeSSven Peter 	wdt = devm_kzalloc(dev, sizeof(*wdt), GFP_KERNEL);
1624ed224aeSSven Peter 	if (!wdt)
1634ed224aeSSven Peter 		return -ENOMEM;
1644ed224aeSSven Peter 
1654ed224aeSSven Peter 	wdt->regs = devm_platform_ioremap_resource(pdev, 0);
1664ed224aeSSven Peter 	if (IS_ERR(wdt->regs))
1674ed224aeSSven Peter 		return PTR_ERR(wdt->regs);
1684ed224aeSSven Peter 
16998b7a161SChristophe JAILLET 	clk = devm_clk_get_enabled(dev, NULL);
1704ed224aeSSven Peter 	if (IS_ERR(clk))
1714ed224aeSSven Peter 		return PTR_ERR(clk);
1724ed224aeSSven Peter 	wdt->clk_rate = clk_get_rate(clk);
1734ed224aeSSven Peter 	if (!wdt->clk_rate)
1744ed224aeSSven Peter 		return -EINVAL;
1754ed224aeSSven Peter 
176*78ca4d69SJanne Grunau 	platform_set_drvdata(pdev, wdt);
177*78ca4d69SJanne Grunau 
1784ed224aeSSven Peter 	wdt->wdd.ops = &apple_wdt_ops;
1794ed224aeSSven Peter 	wdt->wdd.info = &apple_wdt_info;
1804ed224aeSSven Peter 	wdt->wdd.max_timeout = U32_MAX / wdt->clk_rate;
1814ed224aeSSven Peter 	wdt->wdd.timeout = APPLE_WDT_TIMEOUT_DEFAULT;
1824ed224aeSSven Peter 
1834ed224aeSSven Peter 	wdt_ctrl = readl_relaxed(wdt->regs + APPLE_WDT_WD1_CTRL);
1844ed224aeSSven Peter 	if (wdt_ctrl & APPLE_WDT_CTRL_RESET_EN)
1854ed224aeSSven Peter 		set_bit(WDOG_HW_RUNNING, &wdt->wdd.status);
1864ed224aeSSven Peter 
1874ed224aeSSven Peter 	watchdog_init_timeout(&wdt->wdd, 0, dev);
1884ed224aeSSven Peter 	apple_wdt_set_timeout(&wdt->wdd, wdt->wdd.timeout);
1894ed224aeSSven Peter 	watchdog_stop_on_unregister(&wdt->wdd);
1904ed224aeSSven Peter 	watchdog_set_restart_priority(&wdt->wdd, 128);
1914ed224aeSSven Peter 
1924ed224aeSSven Peter 	return devm_watchdog_register_device(dev, &wdt->wdd);
1934ed224aeSSven Peter }
1944ed224aeSSven Peter 
apple_wdt_resume(struct device * dev)195*78ca4d69SJanne Grunau static int apple_wdt_resume(struct device *dev)
196*78ca4d69SJanne Grunau {
197*78ca4d69SJanne Grunau 	struct apple_wdt *wdt = dev_get_drvdata(dev);
198*78ca4d69SJanne Grunau 
199*78ca4d69SJanne Grunau 	if (watchdog_active(&wdt->wdd) || watchdog_hw_running(&wdt->wdd))
200*78ca4d69SJanne Grunau 		apple_wdt_start(&wdt->wdd);
201*78ca4d69SJanne Grunau 
202*78ca4d69SJanne Grunau 	return 0;
203*78ca4d69SJanne Grunau }
204*78ca4d69SJanne Grunau 
apple_wdt_suspend(struct device * dev)205*78ca4d69SJanne Grunau static int apple_wdt_suspend(struct device *dev)
206*78ca4d69SJanne Grunau {
207*78ca4d69SJanne Grunau 	struct apple_wdt *wdt = dev_get_drvdata(dev);
208*78ca4d69SJanne Grunau 
209*78ca4d69SJanne Grunau 	if (watchdog_active(&wdt->wdd) || watchdog_hw_running(&wdt->wdd))
210*78ca4d69SJanne Grunau 		apple_wdt_stop(&wdt->wdd);
211*78ca4d69SJanne Grunau 
212*78ca4d69SJanne Grunau 	return 0;
213*78ca4d69SJanne Grunau }
214*78ca4d69SJanne Grunau 
215*78ca4d69SJanne Grunau static DEFINE_SIMPLE_DEV_PM_OPS(apple_wdt_pm_ops, apple_wdt_suspend, apple_wdt_resume);
216*78ca4d69SJanne Grunau 
2174ed224aeSSven Peter static const struct of_device_id apple_wdt_of_match[] = {
2184ed224aeSSven Peter 	{ .compatible = "apple,wdt" },
2194ed224aeSSven Peter 	{},
2204ed224aeSSven Peter };
2214ed224aeSSven Peter MODULE_DEVICE_TABLE(of, apple_wdt_of_match);
2224ed224aeSSven Peter 
2234ed224aeSSven Peter static struct platform_driver apple_wdt_driver = {
2244ed224aeSSven Peter 	.driver = {
2254ed224aeSSven Peter 		.name = "apple-watchdog",
2264ed224aeSSven Peter 		.of_match_table = apple_wdt_of_match,
227*78ca4d69SJanne Grunau 		.pm = pm_sleep_ptr(&apple_wdt_pm_ops),
2284ed224aeSSven Peter 	},
2294ed224aeSSven Peter 	.probe = apple_wdt_probe,
2304ed224aeSSven Peter };
2314ed224aeSSven Peter module_platform_driver(apple_wdt_driver);
2324ed224aeSSven Peter 
2334ed224aeSSven Peter MODULE_DESCRIPTION("Apple SoC watchdog driver");
2344ed224aeSSven Peter MODULE_AUTHOR("Sven Peter <sven@svenpeter.dev>");
2354ed224aeSSven Peter MODULE_LICENSE("Dual MIT/GPL");
236