Skip to content

Commit 4ed224a

Browse files
svenpeter42Wim Van Sebroeck
authored andcommitted
watchdog: Add Apple SoC watchdog driver
Add support for the watchdog timer found in Apple SoCs. This driver is also required to reboot these machines. Signed-off-by: Sven Peter <[email protected]> Tested-by: Janne Grunau <[email protected]> Reviewed-by: Hector Martin <[email protected]> Reviewed-by: Guenter Roeck <[email protected]> Link: https://lore.kernel.org/r/[email protected] Signed-off-by: Guenter Roeck <[email protected]> Signed-off-by: Wim Van Sebroeck <[email protected]>
1 parent b05e69f commit 4ed224a

File tree

3 files changed

+239
-0
lines changed

3 files changed

+239
-0
lines changed

drivers/watchdog/Kconfig

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -999,6 +999,18 @@ config MSC313E_WATCHDOG
999999
To compile this driver as a module, choose M here: the
10001000
module will be called msc313e_wdt.
10011001

1002+
config APPLE_WATCHDOG
1003+
tristate "Apple SoC watchdog"
1004+
depends on ARCH_APPLE || COMPILE_TEST
1005+
select WATCHDOG_CORE
1006+
help
1007+
Say Y here to include support for the Watchdog found in Apple
1008+
SoCs such as the M1. Next to the common watchdog features this
1009+
driver is also required in order to reboot these SoCs.
1010+
1011+
To compile this driver as a module, choose M here: the
1012+
module will be called apple_wdt.
1013+
10021014
# X86 (i386 + ia64 + x86_64) Architecture
10031015

10041016
config ACQUIRE_WDT

drivers/watchdog/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ obj-$(CONFIG_PM8916_WATCHDOG) += pm8916_wdt.o
9494
obj-$(CONFIG_ARM_SMC_WATCHDOG) += arm_smc_wdt.o
9595
obj-$(CONFIG_VISCONTI_WATCHDOG) += visconti_wdt.o
9696
obj-$(CONFIG_MSC313E_WATCHDOG) += msc313e_wdt.o
97+
obj-$(CONFIG_APPLE_WATCHDOG) += apple_wdt.o
9798

9899
# X86 (i386 + ia64 + x86_64) Architecture
99100
obj-$(CONFIG_ACQUIRE_WDT) += acquirewdt.o

drivers/watchdog/apple_wdt.c

Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
// SPDX-License-Identifier: GPL-2.0-only OR MIT
2+
/*
3+
* Apple SoC Watchdog driver
4+
*
5+
* Copyright (C) The Asahi Linux Contributors
6+
*/
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 registers
21+
*
22+
* This HW block has three separate watchdogs. WD0 resets the machine
23+
* to recovery mode and is not very useful for us. WD1 and WD2 trigger a normal
24+
* 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 the
28+
* reference clock. It can also be overwritten to any value.
29+
* Whenever APPLE_WDT_CTRL_RESET_EN is set in APPLE_WDT_WDx_CTRL and
30+
* APPLE_WDT_WDx_CUR_TIME >= APPLE_WDT_WDx_BITE_TIME the entire machine is
31+
* 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 and
34+
* APPLE_WDT_CTRL_IRQ_STATUS is set. The interrupt can be cleared by writing
35+
* 1 to APPLE_WDT_CTRL_IRQ_STATUS.
36+
*/
37+
#define APPLE_WDT_WD0_CUR_TIME 0x00
38+
#define APPLE_WDT_WD0_BITE_TIME 0x04
39+
#define APPLE_WDT_WD0_BARK_TIME 0x08
40+
#define APPLE_WDT_WD0_CTRL 0x0c
41+
42+
#define APPLE_WDT_WD1_CUR_TIME 0x10
43+
#define APPLE_WDT_WD1_BITE_TIME 0x14
44+
#define APPLE_WDT_WD1_CTRL 0x1c
45+
46+
#define APPLE_WDT_WD2_CUR_TIME 0x20
47+
#define APPLE_WDT_WD2_BITE_TIME 0x24
48+
#define APPLE_WDT_WD2_CTRL 0x2c
49+
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 30
55+
56+
struct apple_wdt {
57+
struct watchdog_device wdd;
58+
void __iomem *regs;
59+
unsigned long clk_rate;
60+
};
61+
62+
static struct apple_wdt *to_apple_wdt(struct watchdog_device *wdd)
63+
{
64+
return container_of(wdd, struct apple_wdt, wdd);
65+
}
66+
67+
static 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+
77+
static 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+
86+
static 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+
95+
static 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+
107+
static 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+
118+
static 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 the
129+
* reset is queued almost immediately experiments have shown that it
130+
* can take up to ~20-25ms until the SoC is actually reset. Just wait
131+
* 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+
139+
static void apple_wdt_clk_disable_unprepare(void *data)
140+
{
141+
clk_disable_unprepare(data);
142+
}
143+
144+
static struct watchdog_ops apple_wdt_ops = {
145+
.owner = THIS_MODULE,
146+
.start = apple_wdt_start,
147+
.stop = apple_wdt_stop,
148+
.ping = apple_wdt_ping,
149+
.set_timeout = apple_wdt_set_timeout,
150+
.get_timeleft = apple_wdt_get_timeleft,
151+
.restart = apple_wdt_restart,
152+
};
153+
154+
static struct watchdog_info apple_wdt_info = {
155+
.identity = "Apple SoC Watchdog",
156+
.options = WDIOF_MAGICCLOSE | WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT,
157+
};
158+
159+
static int apple_wdt_probe(struct platform_device *pdev)
160+
{
161+
struct device *dev = &pdev->dev;
162+
struct apple_wdt *wdt;
163+
struct clk *clk;
164+
u32 wdt_ctrl;
165+
int ret;
166+
167+
wdt = devm_kzalloc(dev, sizeof(*wdt), GFP_KERNEL);
168+
if (!wdt)
169+
return -ENOMEM;
170+
171+
wdt->regs = devm_platform_ioremap_resource(pdev, 0);
172+
if (IS_ERR(wdt->regs))
173+
return PTR_ERR(wdt->regs);
174+
175+
clk = devm_clk_get(dev, NULL);
176+
if (IS_ERR(clk))
177+
return PTR_ERR(clk);
178+
179+
ret = clk_prepare_enable(clk);
180+
if (ret)
181+
return ret;
182+
183+
ret = devm_add_action_or_reset(dev, apple_wdt_clk_disable_unprepare,
184+
clk);
185+
if (ret)
186+
return ret;
187+
188+
wdt->clk_rate = clk_get_rate(clk);
189+
if (!wdt->clk_rate)
190+
return -EINVAL;
191+
192+
wdt->wdd.ops = &apple_wdt_ops;
193+
wdt->wdd.info = &apple_wdt_info;
194+
wdt->wdd.max_timeout = U32_MAX / wdt->clk_rate;
195+
wdt->wdd.timeout = APPLE_WDT_TIMEOUT_DEFAULT;
196+
197+
wdt_ctrl = readl_relaxed(wdt->regs + APPLE_WDT_WD1_CTRL);
198+
if (wdt_ctrl & APPLE_WDT_CTRL_RESET_EN)
199+
set_bit(WDOG_HW_RUNNING, &wdt->wdd.status);
200+
201+
watchdog_init_timeout(&wdt->wdd, 0, dev);
202+
apple_wdt_set_timeout(&wdt->wdd, wdt->wdd.timeout);
203+
watchdog_stop_on_unregister(&wdt->wdd);
204+
watchdog_set_restart_priority(&wdt->wdd, 128);
205+
206+
return devm_watchdog_register_device(dev, &wdt->wdd);
207+
}
208+
209+
static const struct of_device_id apple_wdt_of_match[] = {
210+
{ .compatible = "apple,wdt" },
211+
{},
212+
};
213+
MODULE_DEVICE_TABLE(of, apple_wdt_of_match);
214+
215+
static struct platform_driver apple_wdt_driver = {
216+
.driver = {
217+
.name = "apple-watchdog",
218+
.of_match_table = apple_wdt_of_match,
219+
},
220+
.probe = apple_wdt_probe,
221+
};
222+
module_platform_driver(apple_wdt_driver);
223+
224+
MODULE_DESCRIPTION("Apple SoC watchdog driver");
225+
MODULE_AUTHOR("Sven Peter <[email protected]>");
226+
MODULE_LICENSE("Dual MIT/GPL");

0 commit comments

Comments
 (0)