12874c5fdSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later
2d00680edSCarlo Caione /*
3d00680edSCarlo Caione * sunxi Watchdog Driver
4d00680edSCarlo Caione *
5d00680edSCarlo Caione * Copyright (c) 2013 Carlo Caione
6d00680edSCarlo Caione * 2012 Henrik Nordstrom
7d00680edSCarlo Caione *
8d00680edSCarlo Caione * Based on xen_wdt.c
9d00680edSCarlo Caione * (c) Copyright 2010 Novell, Inc.
10d00680edSCarlo Caione */
11d00680edSCarlo Caione
12d00680edSCarlo Caione #include <linux/clk.h>
13440e96bcSMaxime Ripard #include <linux/delay.h>
14d00680edSCarlo Caione #include <linux/err.h>
15d00680edSCarlo Caione #include <linux/init.h>
16d00680edSCarlo Caione #include <linux/io.h>
17d00680edSCarlo Caione #include <linux/kernel.h>
18d00680edSCarlo Caione #include <linux/module.h>
19d00680edSCarlo Caione #include <linux/moduleparam.h>
20d00680edSCarlo Caione #include <linux/of.h>
21d00680edSCarlo Caione #include <linux/platform_device.h>
22d00680edSCarlo Caione #include <linux/types.h>
23d00680edSCarlo Caione #include <linux/watchdog.h>
24d00680edSCarlo Caione
25d00680edSCarlo Caione #define WDT_MAX_TIMEOUT 16
26d00680edSCarlo Caione #define WDT_MIN_TIMEOUT 1
27f2147de3SChen-Yu Tsai #define WDT_TIMEOUT_MASK 0x0F
28d00680edSCarlo Caione
29d00680edSCarlo Caione #define WDT_CTRL_RELOAD ((1 << 0) | (0x0a57 << 1))
30d00680edSCarlo Caione
31d00680edSCarlo Caione #define WDT_MODE_EN (1 << 0)
32d00680edSCarlo Caione
33d00680edSCarlo Caione #define DRV_NAME "sunxi-wdt"
34d00680edSCarlo Caione #define DRV_VERSION "1.0"
35d00680edSCarlo Caione
36d00680edSCarlo Caione static bool nowayout = WATCHDOG_NOWAYOUT;
371d1dedc2SMarcus Folkesson static unsigned int timeout;
38d00680edSCarlo Caione
39f2147de3SChen-Yu Tsai /*
40f2147de3SChen-Yu Tsai * This structure stores the register offsets for different variants
41f2147de3SChen-Yu Tsai * of Allwinner's watchdog hardware.
42f2147de3SChen-Yu Tsai */
43f2147de3SChen-Yu Tsai struct sunxi_wdt_reg {
44f2147de3SChen-Yu Tsai u8 wdt_ctrl;
45f2147de3SChen-Yu Tsai u8 wdt_cfg;
46f2147de3SChen-Yu Tsai u8 wdt_mode;
47f2147de3SChen-Yu Tsai u8 wdt_timeout_shift;
48f2147de3SChen-Yu Tsai u8 wdt_reset_mask;
49f2147de3SChen-Yu Tsai u8 wdt_reset_val;
50*94213a39SSamuel Holland u32 wdt_key_val;
51f2147de3SChen-Yu Tsai };
52f2147de3SChen-Yu Tsai
53d00680edSCarlo Caione struct sunxi_wdt_dev {
54d00680edSCarlo Caione struct watchdog_device wdt_dev;
55d00680edSCarlo Caione void __iomem *wdt_base;
56f2147de3SChen-Yu Tsai const struct sunxi_wdt_reg *wdt_regs;
57d00680edSCarlo Caione };
58d00680edSCarlo Caione
59d00680edSCarlo Caione /*
60d00680edSCarlo Caione * wdt_timeout_map maps the watchdog timer interval value in seconds to
61f2147de3SChen-Yu Tsai * the value of the register WDT_MODE at bits .wdt_timeout_shift ~ +3
62d00680edSCarlo Caione *
63d00680edSCarlo Caione * [timeout seconds] = register value
64d00680edSCarlo Caione *
65d00680edSCarlo Caione */
66d00680edSCarlo Caione
67d00680edSCarlo Caione static const int wdt_timeout_map[] = {
6851ee34abSEmilio López [1] = 0x1, /* 1s */
6951ee34abSEmilio López [2] = 0x2, /* 2s */
7051ee34abSEmilio López [3] = 0x3, /* 3s */
7151ee34abSEmilio López [4] = 0x4, /* 4s */
7251ee34abSEmilio López [5] = 0x5, /* 5s */
7351ee34abSEmilio López [6] = 0x6, /* 6s */
7451ee34abSEmilio López [8] = 0x7, /* 8s */
7551ee34abSEmilio López [10] = 0x8, /* 10s */
7651ee34abSEmilio López [12] = 0x9, /* 12s */
7751ee34abSEmilio López [14] = 0xA, /* 14s */
7851ee34abSEmilio López [16] = 0xB, /* 16s */
79d00680edSCarlo Caione };
80d00680edSCarlo Caione
81440e96bcSMaxime Ripard
sunxi_wdt_restart(struct watchdog_device * wdt_dev,unsigned long action,void * data)824d8b229dSGuenter Roeck static int sunxi_wdt_restart(struct watchdog_device *wdt_dev,
834d8b229dSGuenter Roeck unsigned long action, void *data)
84440e96bcSMaxime Ripard {
850ebad1e5SDamien Riegel struct sunxi_wdt_dev *sunxi_wdt = watchdog_get_drvdata(wdt_dev);
86d20a1d90SGuenter Roeck void __iomem *wdt_base = sunxi_wdt->wdt_base;
87f2147de3SChen-Yu Tsai const struct sunxi_wdt_reg *regs = sunxi_wdt->wdt_regs;
88f2147de3SChen-Yu Tsai u32 val;
89d20a1d90SGuenter Roeck
90f2147de3SChen-Yu Tsai /* Set system reset function */
91f2147de3SChen-Yu Tsai val = readl(wdt_base + regs->wdt_cfg);
92f2147de3SChen-Yu Tsai val &= ~(regs->wdt_reset_mask);
93f2147de3SChen-Yu Tsai val |= regs->wdt_reset_val;
94*94213a39SSamuel Holland val |= regs->wdt_key_val;
95f2147de3SChen-Yu Tsai writel(val, wdt_base + regs->wdt_cfg);
96f2147de3SChen-Yu Tsai
97f2147de3SChen-Yu Tsai /* Set lowest timeout and enable watchdog */
98f2147de3SChen-Yu Tsai val = readl(wdt_base + regs->wdt_mode);
99f2147de3SChen-Yu Tsai val &= ~(WDT_TIMEOUT_MASK << regs->wdt_timeout_shift);
100f2147de3SChen-Yu Tsai val |= WDT_MODE_EN;
101*94213a39SSamuel Holland val |= regs->wdt_key_val;
102f2147de3SChen-Yu Tsai writel(val, wdt_base + regs->wdt_mode);
103440e96bcSMaxime Ripard
104440e96bcSMaxime Ripard /*
105440e96bcSMaxime Ripard * Restart the watchdog. The default (and lowest) interval
106440e96bcSMaxime Ripard * value for the watchdog is 0.5s.
107440e96bcSMaxime Ripard */
108f2147de3SChen-Yu Tsai writel(WDT_CTRL_RELOAD, wdt_base + regs->wdt_ctrl);
109440e96bcSMaxime Ripard
110440e96bcSMaxime Ripard while (1) {
111440e96bcSMaxime Ripard mdelay(5);
112f2147de3SChen-Yu Tsai val = readl(wdt_base + regs->wdt_mode);
113f2147de3SChen-Yu Tsai val |= WDT_MODE_EN;
114*94213a39SSamuel Holland val |= regs->wdt_key_val;
115f2147de3SChen-Yu Tsai writel(val, wdt_base + regs->wdt_mode);
116440e96bcSMaxime Ripard }
1170ebad1e5SDamien Riegel return 0;
118440e96bcSMaxime Ripard }
119440e96bcSMaxime Ripard
sunxi_wdt_ping(struct watchdog_device * wdt_dev)120d00680edSCarlo Caione static int sunxi_wdt_ping(struct watchdog_device *wdt_dev)
121d00680edSCarlo Caione {
122d00680edSCarlo Caione struct sunxi_wdt_dev *sunxi_wdt = watchdog_get_drvdata(wdt_dev);
123d00680edSCarlo Caione void __iomem *wdt_base = sunxi_wdt->wdt_base;
124f2147de3SChen-Yu Tsai const struct sunxi_wdt_reg *regs = sunxi_wdt->wdt_regs;
125d00680edSCarlo Caione
126f2147de3SChen-Yu Tsai writel(WDT_CTRL_RELOAD, wdt_base + regs->wdt_ctrl);
127d00680edSCarlo Caione
128d00680edSCarlo Caione return 0;
129d00680edSCarlo Caione }
130d00680edSCarlo Caione
sunxi_wdt_set_timeout(struct watchdog_device * wdt_dev,unsigned int timeout)131d00680edSCarlo Caione static int sunxi_wdt_set_timeout(struct watchdog_device *wdt_dev,
132d00680edSCarlo Caione unsigned int timeout)
133d00680edSCarlo Caione {
134d00680edSCarlo Caione struct sunxi_wdt_dev *sunxi_wdt = watchdog_get_drvdata(wdt_dev);
135d00680edSCarlo Caione void __iomem *wdt_base = sunxi_wdt->wdt_base;
136f2147de3SChen-Yu Tsai const struct sunxi_wdt_reg *regs = sunxi_wdt->wdt_regs;
137d00680edSCarlo Caione u32 reg;
138d00680edSCarlo Caione
139d00680edSCarlo Caione if (wdt_timeout_map[timeout] == 0)
140d00680edSCarlo Caione timeout++;
141d00680edSCarlo Caione
142d00680edSCarlo Caione sunxi_wdt->wdt_dev.timeout = timeout;
143d00680edSCarlo Caione
144f2147de3SChen-Yu Tsai reg = readl(wdt_base + regs->wdt_mode);
145f2147de3SChen-Yu Tsai reg &= ~(WDT_TIMEOUT_MASK << regs->wdt_timeout_shift);
146f2147de3SChen-Yu Tsai reg |= wdt_timeout_map[timeout] << regs->wdt_timeout_shift;
147*94213a39SSamuel Holland reg |= regs->wdt_key_val;
148f2147de3SChen-Yu Tsai writel(reg, wdt_base + regs->wdt_mode);
149d00680edSCarlo Caione
150d00680edSCarlo Caione sunxi_wdt_ping(wdt_dev);
151d00680edSCarlo Caione
152d00680edSCarlo Caione return 0;
153d00680edSCarlo Caione }
154d00680edSCarlo Caione
sunxi_wdt_stop(struct watchdog_device * wdt_dev)155d00680edSCarlo Caione static int sunxi_wdt_stop(struct watchdog_device *wdt_dev)
156d00680edSCarlo Caione {
157d00680edSCarlo Caione struct sunxi_wdt_dev *sunxi_wdt = watchdog_get_drvdata(wdt_dev);
158d00680edSCarlo Caione void __iomem *wdt_base = sunxi_wdt->wdt_base;
159f2147de3SChen-Yu Tsai const struct sunxi_wdt_reg *regs = sunxi_wdt->wdt_regs;
160d00680edSCarlo Caione
161*94213a39SSamuel Holland writel(regs->wdt_key_val, wdt_base + regs->wdt_mode);
162d00680edSCarlo Caione
163d00680edSCarlo Caione return 0;
164d00680edSCarlo Caione }
165d00680edSCarlo Caione
sunxi_wdt_start(struct watchdog_device * wdt_dev)166d00680edSCarlo Caione static int sunxi_wdt_start(struct watchdog_device *wdt_dev)
167d00680edSCarlo Caione {
168d00680edSCarlo Caione u32 reg;
169d00680edSCarlo Caione struct sunxi_wdt_dev *sunxi_wdt = watchdog_get_drvdata(wdt_dev);
170d00680edSCarlo Caione void __iomem *wdt_base = sunxi_wdt->wdt_base;
171f2147de3SChen-Yu Tsai const struct sunxi_wdt_reg *regs = sunxi_wdt->wdt_regs;
172d00680edSCarlo Caione int ret;
173d00680edSCarlo Caione
174d00680edSCarlo Caione ret = sunxi_wdt_set_timeout(&sunxi_wdt->wdt_dev,
175d00680edSCarlo Caione sunxi_wdt->wdt_dev.timeout);
176d00680edSCarlo Caione if (ret < 0)
177d00680edSCarlo Caione return ret;
178d00680edSCarlo Caione
179f2147de3SChen-Yu Tsai /* Set system reset function */
180f2147de3SChen-Yu Tsai reg = readl(wdt_base + regs->wdt_cfg);
181f2147de3SChen-Yu Tsai reg &= ~(regs->wdt_reset_mask);
1820919e444SFrancesco Lavra reg |= regs->wdt_reset_val;
183*94213a39SSamuel Holland reg |= regs->wdt_key_val;
184f2147de3SChen-Yu Tsai writel(reg, wdt_base + regs->wdt_cfg);
185f2147de3SChen-Yu Tsai
186f2147de3SChen-Yu Tsai /* Enable watchdog */
187f2147de3SChen-Yu Tsai reg = readl(wdt_base + regs->wdt_mode);
188f2147de3SChen-Yu Tsai reg |= WDT_MODE_EN;
189*94213a39SSamuel Holland reg |= regs->wdt_key_val;
190f2147de3SChen-Yu Tsai writel(reg, wdt_base + regs->wdt_mode);
191d00680edSCarlo Caione
192d00680edSCarlo Caione return 0;
193d00680edSCarlo Caione }
194d00680edSCarlo Caione
195d00680edSCarlo Caione static const struct watchdog_info sunxi_wdt_info = {
196d00680edSCarlo Caione .identity = DRV_NAME,
197d00680edSCarlo Caione .options = WDIOF_SETTIMEOUT |
198d00680edSCarlo Caione WDIOF_KEEPALIVEPING |
199d00680edSCarlo Caione WDIOF_MAGICCLOSE,
200d00680edSCarlo Caione };
201d00680edSCarlo Caione
202d00680edSCarlo Caione static const struct watchdog_ops sunxi_wdt_ops = {
203d00680edSCarlo Caione .owner = THIS_MODULE,
204d00680edSCarlo Caione .start = sunxi_wdt_start,
205d00680edSCarlo Caione .stop = sunxi_wdt_stop,
206d00680edSCarlo Caione .ping = sunxi_wdt_ping,
207d00680edSCarlo Caione .set_timeout = sunxi_wdt_set_timeout,
2080ebad1e5SDamien Riegel .restart = sunxi_wdt_restart,
209d00680edSCarlo Caione };
210d00680edSCarlo Caione
211f2147de3SChen-Yu Tsai static const struct sunxi_wdt_reg sun4i_wdt_reg = {
212f2147de3SChen-Yu Tsai .wdt_ctrl = 0x00,
213f2147de3SChen-Yu Tsai .wdt_cfg = 0x04,
214f2147de3SChen-Yu Tsai .wdt_mode = 0x04,
215f2147de3SChen-Yu Tsai .wdt_timeout_shift = 3,
216f2147de3SChen-Yu Tsai .wdt_reset_mask = 0x02,
217f2147de3SChen-Yu Tsai .wdt_reset_val = 0x02,
218f2147de3SChen-Yu Tsai };
219f2147de3SChen-Yu Tsai
220c5ec618fSChen-Yu Tsai static const struct sunxi_wdt_reg sun6i_wdt_reg = {
221c5ec618fSChen-Yu Tsai .wdt_ctrl = 0x10,
222c5ec618fSChen-Yu Tsai .wdt_cfg = 0x14,
223c5ec618fSChen-Yu Tsai .wdt_mode = 0x18,
224c5ec618fSChen-Yu Tsai .wdt_timeout_shift = 4,
225c5ec618fSChen-Yu Tsai .wdt_reset_mask = 0x03,
226c5ec618fSChen-Yu Tsai .wdt_reset_val = 0x01,
227c5ec618fSChen-Yu Tsai };
228c5ec618fSChen-Yu Tsai
229*94213a39SSamuel Holland static const struct sunxi_wdt_reg sun20i_wdt_reg = {
230*94213a39SSamuel Holland .wdt_ctrl = 0x10,
231*94213a39SSamuel Holland .wdt_cfg = 0x14,
232*94213a39SSamuel Holland .wdt_mode = 0x18,
233*94213a39SSamuel Holland .wdt_timeout_shift = 4,
234*94213a39SSamuel Holland .wdt_reset_mask = 0x03,
235*94213a39SSamuel Holland .wdt_reset_val = 0x01,
236*94213a39SSamuel Holland .wdt_key_val = 0x16aa0000,
237*94213a39SSamuel Holland };
238*94213a39SSamuel Holland
239f2147de3SChen-Yu Tsai static const struct of_device_id sunxi_wdt_dt_ids[] = {
240f2147de3SChen-Yu Tsai { .compatible = "allwinner,sun4i-a10-wdt", .data = &sun4i_wdt_reg },
241c5ec618fSChen-Yu Tsai { .compatible = "allwinner,sun6i-a31-wdt", .data = &sun6i_wdt_reg },
242*94213a39SSamuel Holland { .compatible = "allwinner,sun20i-d1-wdt", .data = &sun20i_wdt_reg },
243f2147de3SChen-Yu Tsai { /* sentinel */ }
244f2147de3SChen-Yu Tsai };
245f2147de3SChen-Yu Tsai MODULE_DEVICE_TABLE(of, sunxi_wdt_dt_ids);
246f2147de3SChen-Yu Tsai
sunxi_wdt_probe(struct platform_device * pdev)2471d5898b4SMaxime Ripard static int sunxi_wdt_probe(struct platform_device *pdev)
248d00680edSCarlo Caione {
2498ba41f6cSGuenter Roeck struct device *dev = &pdev->dev;
250d00680edSCarlo Caione struct sunxi_wdt_dev *sunxi_wdt;
251d00680edSCarlo Caione int err;
252d00680edSCarlo Caione
2538ba41f6cSGuenter Roeck sunxi_wdt = devm_kzalloc(dev, sizeof(*sunxi_wdt), GFP_KERNEL);
254d00680edSCarlo Caione if (!sunxi_wdt)
255ff01cb1cSMartin Wu return -ENOMEM;
256d00680edSCarlo Caione
2578ba41f6cSGuenter Roeck sunxi_wdt->wdt_regs = of_device_get_match_data(dev);
258e5310371SCorentin Labbe if (!sunxi_wdt->wdt_regs)
259f2147de3SChen-Yu Tsai return -ENODEV;
260f2147de3SChen-Yu Tsai
2610f0a6a28SGuenter Roeck sunxi_wdt->wdt_base = devm_platform_ioremap_resource(pdev, 0);
262d00680edSCarlo Caione if (IS_ERR(sunxi_wdt->wdt_base))
263d00680edSCarlo Caione return PTR_ERR(sunxi_wdt->wdt_base);
264d00680edSCarlo Caione
265d00680edSCarlo Caione sunxi_wdt->wdt_dev.info = &sunxi_wdt_info;
266d00680edSCarlo Caione sunxi_wdt->wdt_dev.ops = &sunxi_wdt_ops;
267d00680edSCarlo Caione sunxi_wdt->wdt_dev.timeout = WDT_MAX_TIMEOUT;
268d00680edSCarlo Caione sunxi_wdt->wdt_dev.max_timeout = WDT_MAX_TIMEOUT;
269d00680edSCarlo Caione sunxi_wdt->wdt_dev.min_timeout = WDT_MIN_TIMEOUT;
2708ba41f6cSGuenter Roeck sunxi_wdt->wdt_dev.parent = dev;
271d00680edSCarlo Caione
2728ba41f6cSGuenter Roeck watchdog_init_timeout(&sunxi_wdt->wdt_dev, timeout, dev);
273d00680edSCarlo Caione watchdog_set_nowayout(&sunxi_wdt->wdt_dev, nowayout);
2740ebad1e5SDamien Riegel watchdog_set_restart_priority(&sunxi_wdt->wdt_dev, 128);
275d00680edSCarlo Caione
276d00680edSCarlo Caione watchdog_set_drvdata(&sunxi_wdt->wdt_dev, sunxi_wdt);
277d00680edSCarlo Caione
278d00680edSCarlo Caione sunxi_wdt_stop(&sunxi_wdt->wdt_dev);
279d00680edSCarlo Caione
28042f82693SGuenter Roeck watchdog_stop_on_reboot(&sunxi_wdt->wdt_dev);
2818ba41f6cSGuenter Roeck err = devm_watchdog_register_device(dev, &sunxi_wdt->wdt_dev);
282d00680edSCarlo Caione if (unlikely(err))
283d00680edSCarlo Caione return err;
284d00680edSCarlo Caione
2858ba41f6cSGuenter Roeck dev_info(dev, "Watchdog enabled (timeout=%d sec, nowayout=%d)",
286d00680edSCarlo Caione sunxi_wdt->wdt_dev.timeout, nowayout);
287d00680edSCarlo Caione
288d00680edSCarlo Caione return 0;
289d00680edSCarlo Caione }
290d00680edSCarlo Caione
291d00680edSCarlo Caione static struct platform_driver sunxi_wdt_driver = {
292d00680edSCarlo Caione .probe = sunxi_wdt_probe,
293d00680edSCarlo Caione .driver = {
294d00680edSCarlo Caione .name = DRV_NAME,
29585eee819SSachin Kamat .of_match_table = sunxi_wdt_dt_ids,
296d00680edSCarlo Caione },
297d00680edSCarlo Caione };
298d00680edSCarlo Caione
299d00680edSCarlo Caione module_platform_driver(sunxi_wdt_driver);
300d00680edSCarlo Caione
301d00680edSCarlo Caione module_param(timeout, uint, 0);
302d00680edSCarlo Caione MODULE_PARM_DESC(timeout, "Watchdog heartbeat in seconds");
303d00680edSCarlo Caione
304d00680edSCarlo Caione module_param(nowayout, bool, 0);
305d00680edSCarlo Caione MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started "
306d00680edSCarlo Caione "(default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
307d00680edSCarlo Caione
308d00680edSCarlo Caione MODULE_LICENSE("GPL");
309d00680edSCarlo Caione MODULE_AUTHOR("Carlo Caione <carlo.caione@gmail.com>");
310d00680edSCarlo Caione MODULE_AUTHOR("Henrik Nordstrom <henrik@henriknordstrom.net>");
311d00680edSCarlo Caione MODULE_DESCRIPTION("sunxi WatchDog Timer Driver");
312d00680edSCarlo Caione MODULE_VERSION(DRV_VERSION);
313