]>
Commit | Line | Data |
---|---|---|
41b630f4 AH |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* | |
3 | * Copyright 2019 NXP. | |
4 | */ | |
5 | ||
6 | #include <linux/clk.h> | |
7 | #include <linux/init.h> | |
8 | #include <linux/io.h> | |
9 | #include <linux/kernel.h> | |
10 | #include <linux/module.h> | |
11 | #include <linux/of.h> | |
12 | #include <linux/platform_device.h> | |
13 | #include <linux/reboot.h> | |
14 | #include <linux/watchdog.h> | |
15 | ||
16 | #define WDOG_CS 0x0 | |
17 | #define WDOG_CS_CMD32EN BIT(13) | |
18 | #define WDOG_CS_ULK BIT(11) | |
19 | #define WDOG_CS_RCS BIT(10) | |
eccb7fe5 FE |
20 | #define LPO_CLK 0x1 |
21 | #define LPO_CLK_SHIFT 8 | |
22 | #define WDOG_CS_CLK (LPO_CLK << LPO_CLK_SHIFT) | |
41b630f4 AH |
23 | #define WDOG_CS_EN BIT(7) |
24 | #define WDOG_CS_UPDATE BIT(5) | |
25 | ||
26 | #define WDOG_CNT 0x4 | |
27 | #define WDOG_TOVAL 0x8 | |
28 | ||
29 | #define REFRESH_SEQ0 0xA602 | |
30 | #define REFRESH_SEQ1 0xB480 | |
31 | #define REFRESH ((REFRESH_SEQ1 << 16) | REFRESH_SEQ0) | |
32 | ||
33 | #define UNLOCK_SEQ0 0xC520 | |
34 | #define UNLOCK_SEQ1 0xD928 | |
35 | #define UNLOCK ((UNLOCK_SEQ1 << 16) | UNLOCK_SEQ0) | |
36 | ||
37 | #define DEFAULT_TIMEOUT 60 | |
38 | #define MAX_TIMEOUT 128 | |
39 | #define WDOG_CLOCK_RATE 1000 | |
40 | ||
41 | static bool nowayout = WATCHDOG_NOWAYOUT; | |
42 | module_param(nowayout, bool, 0000); | |
43 | MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" | |
44 | __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); | |
45 | ||
46 | struct imx7ulp_wdt_device { | |
41b630f4 AH |
47 | struct watchdog_device wdd; |
48 | void __iomem *base; | |
49 | struct clk *clk; | |
50 | }; | |
51 | ||
c37e3581 | 52 | static void imx7ulp_wdt_enable(struct watchdog_device *wdog, bool enable) |
41b630f4 | 53 | { |
747d88a1 | 54 | struct imx7ulp_wdt_device *wdt = watchdog_get_drvdata(wdog); |
41b630f4 | 55 | |
747d88a1 FE |
56 | u32 val = readl(wdt->base + WDOG_CS); |
57 | ||
58 | writel(UNLOCK, wdt->base + WDOG_CNT); | |
41b630f4 | 59 | if (enable) |
747d88a1 | 60 | writel(val | WDOG_CS_EN, wdt->base + WDOG_CS); |
41b630f4 | 61 | else |
747d88a1 | 62 | writel(val & ~WDOG_CS_EN, wdt->base + WDOG_CS); |
41b630f4 AH |
63 | } |
64 | ||
c37e3581 | 65 | static bool imx7ulp_wdt_is_enabled(void __iomem *base) |
41b630f4 AH |
66 | { |
67 | u32 val = readl(base + WDOG_CS); | |
68 | ||
69 | return val & WDOG_CS_EN; | |
70 | } | |
71 | ||
72 | static int imx7ulp_wdt_ping(struct watchdog_device *wdog) | |
73 | { | |
74 | struct imx7ulp_wdt_device *wdt = watchdog_get_drvdata(wdog); | |
75 | ||
76 | writel(REFRESH, wdt->base + WDOG_CNT); | |
77 | ||
78 | return 0; | |
79 | } | |
80 | ||
81 | static int imx7ulp_wdt_start(struct watchdog_device *wdog) | |
82 | { | |
41b630f4 | 83 | |
747d88a1 | 84 | imx7ulp_wdt_enable(wdog, true); |
41b630f4 AH |
85 | |
86 | return 0; | |
87 | } | |
88 | ||
89 | static int imx7ulp_wdt_stop(struct watchdog_device *wdog) | |
90 | { | |
747d88a1 | 91 | imx7ulp_wdt_enable(wdog, false); |
41b630f4 AH |
92 | |
93 | return 0; | |
94 | } | |
95 | ||
96 | static int imx7ulp_wdt_set_timeout(struct watchdog_device *wdog, | |
97 | unsigned int timeout) | |
98 | { | |
99 | struct imx7ulp_wdt_device *wdt = watchdog_get_drvdata(wdog); | |
100 | u32 val = WDOG_CLOCK_RATE * timeout; | |
101 | ||
102 | writel(UNLOCK, wdt->base + WDOG_CNT); | |
103 | writel(val, wdt->base + WDOG_TOVAL); | |
104 | ||
105 | wdog->timeout = timeout; | |
106 | ||
107 | return 0; | |
108 | } | |
109 | ||
6083ab7b FE |
110 | static int imx7ulp_wdt_restart(struct watchdog_device *wdog, |
111 | unsigned long action, void *data) | |
112 | { | |
113 | struct imx7ulp_wdt_device *wdt = watchdog_get_drvdata(wdog); | |
114 | ||
115 | imx7ulp_wdt_enable(wdt->base, true); | |
116 | imx7ulp_wdt_set_timeout(&wdt->wdd, 1); | |
117 | ||
118 | /* wait for wdog to fire */ | |
119 | while (true) | |
120 | ; | |
121 | ||
122 | return NOTIFY_DONE; | |
123 | } | |
124 | ||
41b630f4 AH |
125 | static const struct watchdog_ops imx7ulp_wdt_ops = { |
126 | .owner = THIS_MODULE, | |
127 | .start = imx7ulp_wdt_start, | |
128 | .stop = imx7ulp_wdt_stop, | |
129 | .ping = imx7ulp_wdt_ping, | |
130 | .set_timeout = imx7ulp_wdt_set_timeout, | |
6083ab7b | 131 | .restart = imx7ulp_wdt_restart, |
41b630f4 AH |
132 | }; |
133 | ||
134 | static const struct watchdog_info imx7ulp_wdt_info = { | |
135 | .identity = "i.MX7ULP watchdog timer", | |
136 | .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | | |
137 | WDIOF_MAGICCLOSE, | |
138 | }; | |
139 | ||
c37e3581 | 140 | static void imx7ulp_wdt_init(void __iomem *base, unsigned int timeout) |
41b630f4 AH |
141 | { |
142 | u32 val; | |
143 | ||
144 | /* unlock the wdog for reconfiguration */ | |
145 | writel_relaxed(UNLOCK_SEQ0, base + WDOG_CNT); | |
146 | writel_relaxed(UNLOCK_SEQ1, base + WDOG_CNT); | |
147 | ||
148 | /* set an initial timeout value in TOVAL */ | |
149 | writel(timeout, base + WDOG_TOVAL); | |
150 | /* enable 32bit command sequence and reconfigure */ | |
eccb7fe5 | 151 | val = WDOG_CS_CMD32EN | WDOG_CS_CLK | WDOG_CS_UPDATE; |
41b630f4 AH |
152 | writel(val, base + WDOG_CS); |
153 | } | |
154 | ||
155 | static void imx7ulp_wdt_action(void *data) | |
156 | { | |
157 | clk_disable_unprepare(data); | |
158 | } | |
159 | ||
160 | static int imx7ulp_wdt_probe(struct platform_device *pdev) | |
161 | { | |
162 | struct imx7ulp_wdt_device *imx7ulp_wdt; | |
163 | struct device *dev = &pdev->dev; | |
164 | struct watchdog_device *wdog; | |
165 | int ret; | |
166 | ||
167 | imx7ulp_wdt = devm_kzalloc(dev, sizeof(*imx7ulp_wdt), GFP_KERNEL); | |
168 | if (!imx7ulp_wdt) | |
169 | return -ENOMEM; | |
170 | ||
171 | platform_set_drvdata(pdev, imx7ulp_wdt); | |
172 | ||
173 | imx7ulp_wdt->base = devm_platform_ioremap_resource(pdev, 0); | |
174 | if (IS_ERR(imx7ulp_wdt->base)) | |
175 | return PTR_ERR(imx7ulp_wdt->base); | |
176 | ||
177 | imx7ulp_wdt->clk = devm_clk_get(dev, NULL); | |
178 | if (IS_ERR(imx7ulp_wdt->clk)) { | |
179 | dev_err(dev, "Failed to get watchdog clock\n"); | |
180 | return PTR_ERR(imx7ulp_wdt->clk); | |
181 | } | |
182 | ||
183 | ret = clk_prepare_enable(imx7ulp_wdt->clk); | |
184 | if (ret) | |
185 | return ret; | |
186 | ||
187 | ret = devm_add_action_or_reset(dev, imx7ulp_wdt_action, imx7ulp_wdt->clk); | |
188 | if (ret) | |
189 | return ret; | |
190 | ||
191 | wdog = &imx7ulp_wdt->wdd; | |
192 | wdog->info = &imx7ulp_wdt_info; | |
193 | wdog->ops = &imx7ulp_wdt_ops; | |
194 | wdog->min_timeout = 1; | |
195 | wdog->max_timeout = MAX_TIMEOUT; | |
196 | wdog->parent = dev; | |
197 | wdog->timeout = DEFAULT_TIMEOUT; | |
198 | ||
199 | watchdog_init_timeout(wdog, 0, dev); | |
200 | watchdog_stop_on_reboot(wdog); | |
201 | watchdog_stop_on_unregister(wdog); | |
202 | watchdog_set_drvdata(wdog, imx7ulp_wdt); | |
203 | imx7ulp_wdt_init(imx7ulp_wdt->base, wdog->timeout * WDOG_CLOCK_RATE); | |
204 | ||
205 | return devm_watchdog_register_device(dev, wdog); | |
206 | } | |
207 | ||
208 | static int __maybe_unused imx7ulp_wdt_suspend(struct device *dev) | |
209 | { | |
210 | struct imx7ulp_wdt_device *imx7ulp_wdt = dev_get_drvdata(dev); | |
211 | ||
212 | if (watchdog_active(&imx7ulp_wdt->wdd)) | |
213 | imx7ulp_wdt_stop(&imx7ulp_wdt->wdd); | |
214 | ||
215 | clk_disable_unprepare(imx7ulp_wdt->clk); | |
216 | ||
217 | return 0; | |
218 | } | |
219 | ||
220 | static int __maybe_unused imx7ulp_wdt_resume(struct device *dev) | |
221 | { | |
222 | struct imx7ulp_wdt_device *imx7ulp_wdt = dev_get_drvdata(dev); | |
223 | u32 timeout = imx7ulp_wdt->wdd.timeout * WDOG_CLOCK_RATE; | |
224 | int ret; | |
225 | ||
226 | ret = clk_prepare_enable(imx7ulp_wdt->clk); | |
227 | if (ret) | |
228 | return ret; | |
229 | ||
230 | if (imx7ulp_wdt_is_enabled(imx7ulp_wdt->base)) | |
231 | imx7ulp_wdt_init(imx7ulp_wdt->base, timeout); | |
232 | ||
233 | if (watchdog_active(&imx7ulp_wdt->wdd)) | |
234 | imx7ulp_wdt_start(&imx7ulp_wdt->wdd); | |
235 | ||
236 | return 0; | |
237 | } | |
238 | ||
239 | static SIMPLE_DEV_PM_OPS(imx7ulp_wdt_pm_ops, imx7ulp_wdt_suspend, | |
240 | imx7ulp_wdt_resume); | |
241 | ||
242 | static const struct of_device_id imx7ulp_wdt_dt_ids[] = { | |
243 | { .compatible = "fsl,imx7ulp-wdt", }, | |
244 | { /* sentinel */ } | |
245 | }; | |
246 | MODULE_DEVICE_TABLE(of, imx7ulp_wdt_dt_ids); | |
247 | ||
248 | static struct platform_driver imx7ulp_wdt_driver = { | |
249 | .probe = imx7ulp_wdt_probe, | |
250 | .driver = { | |
251 | .name = "imx7ulp-wdt", | |
252 | .pm = &imx7ulp_wdt_pm_ops, | |
253 | .of_match_table = imx7ulp_wdt_dt_ids, | |
254 | }, | |
255 | }; | |
256 | module_platform_driver(imx7ulp_wdt_driver); | |
257 | ||
258 | MODULE_AUTHOR("Anson Huang <Anson.Huang@nxp.com>"); | |
259 | MODULE_DESCRIPTION("Freescale i.MX7ULP watchdog driver"); | |
260 | MODULE_LICENSE("GPL v2"); |