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