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 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 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 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 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 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 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 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 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 169*98b7a161SChristophe 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 1764ed224aeSSven Peter wdt->wdd.ops = &apple_wdt_ops; 1774ed224aeSSven Peter wdt->wdd.info = &apple_wdt_info; 1784ed224aeSSven Peter wdt->wdd.max_timeout = U32_MAX / wdt->clk_rate; 1794ed224aeSSven Peter wdt->wdd.timeout = APPLE_WDT_TIMEOUT_DEFAULT; 1804ed224aeSSven Peter 1814ed224aeSSven Peter wdt_ctrl = readl_relaxed(wdt->regs + APPLE_WDT_WD1_CTRL); 1824ed224aeSSven Peter if (wdt_ctrl & APPLE_WDT_CTRL_RESET_EN) 1834ed224aeSSven Peter set_bit(WDOG_HW_RUNNING, &wdt->wdd.status); 1844ed224aeSSven Peter 1854ed224aeSSven Peter watchdog_init_timeout(&wdt->wdd, 0, dev); 1864ed224aeSSven Peter apple_wdt_set_timeout(&wdt->wdd, wdt->wdd.timeout); 1874ed224aeSSven Peter watchdog_stop_on_unregister(&wdt->wdd); 1884ed224aeSSven Peter watchdog_set_restart_priority(&wdt->wdd, 128); 1894ed224aeSSven Peter 1904ed224aeSSven Peter return devm_watchdog_register_device(dev, &wdt->wdd); 1914ed224aeSSven Peter } 1924ed224aeSSven Peter 1934ed224aeSSven Peter static const struct of_device_id apple_wdt_of_match[] = { 1944ed224aeSSven Peter { .compatible = "apple,wdt" }, 1954ed224aeSSven Peter {}, 1964ed224aeSSven Peter }; 1974ed224aeSSven Peter MODULE_DEVICE_TABLE(of, apple_wdt_of_match); 1984ed224aeSSven Peter 1994ed224aeSSven Peter static struct platform_driver apple_wdt_driver = { 2004ed224aeSSven Peter .driver = { 2014ed224aeSSven Peter .name = "apple-watchdog", 2024ed224aeSSven Peter .of_match_table = apple_wdt_of_match, 2034ed224aeSSven Peter }, 2044ed224aeSSven Peter .probe = apple_wdt_probe, 2054ed224aeSSven Peter }; 2064ed224aeSSven Peter module_platform_driver(apple_wdt_driver); 2074ed224aeSSven Peter 2084ed224aeSSven Peter MODULE_DESCRIPTION("Apple SoC watchdog driver"); 2094ed224aeSSven Peter MODULE_AUTHOR("Sven Peter <sven@svenpeter.dev>"); 2104ed224aeSSven Peter MODULE_LICENSE("Dual MIT/GPL"); 211