236 lines · c
1// SPDX-License-Identifier: GPL-2.0-only OR MIT2/*3 * Apple SoC Watchdog driver4 *5 * Copyright (C) The Asahi Linux Contributors6 */7 8#include <linux/bits.h>9#include <linux/clk.h>10#include <linux/delay.h>11#include <linux/io.h>12#include <linux/kernel.h>13#include <linux/limits.h>14#include <linux/module.h>15#include <linux/of.h>16#include <linux/platform_device.h>17#include <linux/watchdog.h>18 19/*20 * Apple Watchdog MMIO registers21 *22 * This HW block has three separate watchdogs. WD0 resets the machine23 * to recovery mode and is not very useful for us. WD1 and WD2 trigger a normal24 * machine reset. WD0 additionally supports a configurable interrupt.25 * This information can be used to implement pretimeout support at a later time.26 *27 * APPLE_WDT_WDx_CUR_TIME is a simple counter incremented for each tick of the28 * reference clock. It can also be overwritten to any value.29 * Whenever APPLE_WDT_CTRL_RESET_EN is set in APPLE_WDT_WDx_CTRL and30 * APPLE_WDT_WDx_CUR_TIME >= APPLE_WDT_WDx_BITE_TIME the entire machine is31 * reset.32 * Whenever APPLE_WDT_CTRL_IRQ_EN is set and APPLE_WDTx_WD1_CUR_TIME >=33 * APPLE_WDTx_WD1_BARK_TIME an interrupt is triggered and34 * APPLE_WDT_CTRL_IRQ_STATUS is set. The interrupt can be cleared by writing35 * 1 to APPLE_WDT_CTRL_IRQ_STATUS.36 */37#define APPLE_WDT_WD0_CUR_TIME 0x0038#define APPLE_WDT_WD0_BITE_TIME 0x0439#define APPLE_WDT_WD0_BARK_TIME 0x0840#define APPLE_WDT_WD0_CTRL 0x0c41 42#define APPLE_WDT_WD1_CUR_TIME 0x1043#define APPLE_WDT_WD1_BITE_TIME 0x1444#define APPLE_WDT_WD1_CTRL 0x1c45 46#define APPLE_WDT_WD2_CUR_TIME 0x2047#define APPLE_WDT_WD2_BITE_TIME 0x2448#define APPLE_WDT_WD2_CTRL 0x2c49 50#define APPLE_WDT_CTRL_IRQ_EN BIT(0)51#define APPLE_WDT_CTRL_IRQ_STATUS BIT(1)52#define APPLE_WDT_CTRL_RESET_EN BIT(2)53 54#define APPLE_WDT_TIMEOUT_DEFAULT 3055 56struct apple_wdt {57 struct watchdog_device wdd;58 void __iomem *regs;59 unsigned long clk_rate;60};61 62static struct apple_wdt *to_apple_wdt(struct watchdog_device *wdd)63{64 return container_of(wdd, struct apple_wdt, wdd);65}66 67static int apple_wdt_start(struct watchdog_device *wdd)68{69 struct apple_wdt *wdt = to_apple_wdt(wdd);70 71 writel_relaxed(0, wdt->regs + APPLE_WDT_WD1_CUR_TIME);72 writel_relaxed(APPLE_WDT_CTRL_RESET_EN, wdt->regs + APPLE_WDT_WD1_CTRL);73 74 return 0;75}76 77static int apple_wdt_stop(struct watchdog_device *wdd)78{79 struct apple_wdt *wdt = to_apple_wdt(wdd);80 81 writel_relaxed(0, wdt->regs + APPLE_WDT_WD1_CTRL);82 83 return 0;84}85 86static int apple_wdt_ping(struct watchdog_device *wdd)87{88 struct apple_wdt *wdt = to_apple_wdt(wdd);89 90 writel_relaxed(0, wdt->regs + APPLE_WDT_WD1_CUR_TIME);91 92 return 0;93}94 95static int apple_wdt_set_timeout(struct watchdog_device *wdd, unsigned int s)96{97 struct apple_wdt *wdt = to_apple_wdt(wdd);98 99 writel_relaxed(0, wdt->regs + APPLE_WDT_WD1_CUR_TIME);100 writel_relaxed(wdt->clk_rate * s, wdt->regs + APPLE_WDT_WD1_BITE_TIME);101 102 wdd->timeout = s;103 104 return 0;105}106 107static unsigned int apple_wdt_get_timeleft(struct watchdog_device *wdd)108{109 struct apple_wdt *wdt = to_apple_wdt(wdd);110 u32 cur_time, reset_time;111 112 cur_time = readl_relaxed(wdt->regs + APPLE_WDT_WD1_CUR_TIME);113 reset_time = readl_relaxed(wdt->regs + APPLE_WDT_WD1_BITE_TIME);114 115 return (reset_time - cur_time) / wdt->clk_rate;116}117 118static int apple_wdt_restart(struct watchdog_device *wdd, unsigned long mode,119 void *cmd)120{121 struct apple_wdt *wdt = to_apple_wdt(wdd);122 123 writel_relaxed(APPLE_WDT_CTRL_RESET_EN, wdt->regs + APPLE_WDT_WD1_CTRL);124 writel_relaxed(0, wdt->regs + APPLE_WDT_WD1_BITE_TIME);125 writel_relaxed(0, wdt->regs + APPLE_WDT_WD1_CUR_TIME);126 127 /*128 * Flush writes and then wait for the SoC to reset. Even though the129 * reset is queued almost immediately experiments have shown that it130 * can take up to ~20-25ms until the SoC is actually reset. Just wait131 * 50ms here to be safe.132 */133 (void)readl_relaxed(wdt->regs + APPLE_WDT_WD1_CUR_TIME);134 mdelay(50);135 136 return 0;137}138 139static struct watchdog_ops apple_wdt_ops = {140 .owner = THIS_MODULE,141 .start = apple_wdt_start,142 .stop = apple_wdt_stop,143 .ping = apple_wdt_ping,144 .set_timeout = apple_wdt_set_timeout,145 .get_timeleft = apple_wdt_get_timeleft,146 .restart = apple_wdt_restart,147};148 149static struct watchdog_info apple_wdt_info = {150 .identity = "Apple SoC Watchdog",151 .options = WDIOF_MAGICCLOSE | WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT,152};153 154static int apple_wdt_probe(struct platform_device *pdev)155{156 struct device *dev = &pdev->dev;157 struct apple_wdt *wdt;158 struct clk *clk;159 u32 wdt_ctrl;160 161 wdt = devm_kzalloc(dev, sizeof(*wdt), GFP_KERNEL);162 if (!wdt)163 return -ENOMEM;164 165 wdt->regs = devm_platform_ioremap_resource(pdev, 0);166 if (IS_ERR(wdt->regs))167 return PTR_ERR(wdt->regs);168 169 clk = devm_clk_get_enabled(dev, NULL);170 if (IS_ERR(clk))171 return PTR_ERR(clk);172 wdt->clk_rate = clk_get_rate(clk);173 if (!wdt->clk_rate)174 return -EINVAL;175 176 platform_set_drvdata(pdev, wdt);177 178 wdt->wdd.ops = &apple_wdt_ops;179 wdt->wdd.info = &apple_wdt_info;180 wdt->wdd.max_timeout = U32_MAX / wdt->clk_rate;181 wdt->wdd.timeout = APPLE_WDT_TIMEOUT_DEFAULT;182 183 wdt_ctrl = readl_relaxed(wdt->regs + APPLE_WDT_WD1_CTRL);184 if (wdt_ctrl & APPLE_WDT_CTRL_RESET_EN)185 set_bit(WDOG_HW_RUNNING, &wdt->wdd.status);186 187 watchdog_init_timeout(&wdt->wdd, 0, dev);188 apple_wdt_set_timeout(&wdt->wdd, wdt->wdd.timeout);189 watchdog_stop_on_unregister(&wdt->wdd);190 watchdog_set_restart_priority(&wdt->wdd, 128);191 192 return devm_watchdog_register_device(dev, &wdt->wdd);193}194 195static int apple_wdt_resume(struct device *dev)196{197 struct apple_wdt *wdt = dev_get_drvdata(dev);198 199 if (watchdog_active(&wdt->wdd) || watchdog_hw_running(&wdt->wdd))200 apple_wdt_start(&wdt->wdd);201 202 return 0;203}204 205static int apple_wdt_suspend(struct device *dev)206{207 struct apple_wdt *wdt = dev_get_drvdata(dev);208 209 if (watchdog_active(&wdt->wdd) || watchdog_hw_running(&wdt->wdd))210 apple_wdt_stop(&wdt->wdd);211 212 return 0;213}214 215static DEFINE_SIMPLE_DEV_PM_OPS(apple_wdt_pm_ops, apple_wdt_suspend, apple_wdt_resume);216 217static const struct of_device_id apple_wdt_of_match[] = {218 { .compatible = "apple,wdt" },219 {},220};221MODULE_DEVICE_TABLE(of, apple_wdt_of_match);222 223static struct platform_driver apple_wdt_driver = {224 .driver = {225 .name = "apple-watchdog",226 .of_match_table = apple_wdt_of_match,227 .pm = pm_sleep_ptr(&apple_wdt_pm_ops),228 },229 .probe = apple_wdt_probe,230};231module_platform_driver(apple_wdt_driver);232 233MODULE_DESCRIPTION("Apple SoC watchdog driver");234MODULE_AUTHOR("Sven Peter <sven@svenpeter.dev>");235MODULE_LICENSE("Dual MIT/GPL");236