brintos

brintos / linux-shallow public Read only

0
0
Text · 6.3 KiB · d4f7399 Raw
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