Blame view

drivers/watchdog/sunxi_wdt.c 7.43 KB
2874c5fd2   Thomas Gleixner   treewide: Replace...
1
  // SPDX-License-Identifier: GPL-2.0-or-later
d00680ed0   Carlo Caione   watchdog: sunxi: ...
2
3
4
5
6
7
  /*
   *      sunxi Watchdog Driver
   *
   *      Copyright (c) 2013 Carlo Caione
   *                    2012 Henrik Nordstrom
   *
d00680ed0   Carlo Caione   watchdog: sunxi: ...
8
9
10
11
12
   *      Based on xen_wdt.c
   *      (c) Copyright 2010 Novell, Inc.
   */
  
  #include <linux/clk.h>
440e96bc7   Maxime Ripard   wdt: sunxi: Move ...
13
  #include <linux/delay.h>
d00680ed0   Carlo Caione   watchdog: sunxi: ...
14
15
16
17
18
19
20
  #include <linux/err.h>
  #include <linux/init.h>
  #include <linux/io.h>
  #include <linux/kernel.h>
  #include <linux/module.h>
  #include <linux/moduleparam.h>
  #include <linux/of.h>
f2147de33   Chen-Yu Tsai   watchdog: sunxi: ...
21
  #include <linux/of_device.h>
d00680ed0   Carlo Caione   watchdog: sunxi: ...
22
23
24
25
26
27
  #include <linux/platform_device.h>
  #include <linux/types.h>
  #include <linux/watchdog.h>
  
  #define WDT_MAX_TIMEOUT         16
  #define WDT_MIN_TIMEOUT         1
f2147de33   Chen-Yu Tsai   watchdog: sunxi: ...
28
  #define WDT_TIMEOUT_MASK        0x0F
d00680ed0   Carlo Caione   watchdog: sunxi: ...
29

d00680ed0   Carlo Caione   watchdog: sunxi: ...
30
  #define WDT_CTRL_RELOAD         ((1 << 0) | (0x0a57 << 1))
d00680ed0   Carlo Caione   watchdog: sunxi: ...
31
  #define WDT_MODE_EN             (1 << 0)
d00680ed0   Carlo Caione   watchdog: sunxi: ...
32
33
34
35
36
  
  #define DRV_NAME		"sunxi-wdt"
  #define DRV_VERSION		"1.0"
  
  static bool nowayout = WATCHDOG_NOWAYOUT;
1d1dedc21   Marcus Folkesson   watchdog: sunxi: ...
37
  static unsigned int timeout;
d00680ed0   Carlo Caione   watchdog: sunxi: ...
38

f2147de33   Chen-Yu Tsai   watchdog: sunxi: ...
39
40
41
42
43
44
45
46
47
48
49
50
  /*
   * This structure stores the register offsets for different variants
   * of Allwinner's watchdog hardware.
   */
  struct sunxi_wdt_reg {
  	u8 wdt_ctrl;
  	u8 wdt_cfg;
  	u8 wdt_mode;
  	u8 wdt_timeout_shift;
  	u8 wdt_reset_mask;
  	u8 wdt_reset_val;
  };
d00680ed0   Carlo Caione   watchdog: sunxi: ...
51
52
53
  struct sunxi_wdt_dev {
  	struct watchdog_device wdt_dev;
  	void __iomem *wdt_base;
f2147de33   Chen-Yu Tsai   watchdog: sunxi: ...
54
  	const struct sunxi_wdt_reg *wdt_regs;
d00680ed0   Carlo Caione   watchdog: sunxi: ...
55
56
57
58
  };
  
  /*
   * wdt_timeout_map maps the watchdog timer interval value in seconds to
f2147de33   Chen-Yu Tsai   watchdog: sunxi: ...
59
   * the value of the register WDT_MODE at bits .wdt_timeout_shift ~ +3
d00680ed0   Carlo Caione   watchdog: sunxi: ...
60
61
62
63
64
65
   *
   * [timeout seconds] = register value
   *
   */
  
  static const int wdt_timeout_map[] = {
51ee34ab5   Emilio López   watchdog: sunxi: ...
66
67
68
69
70
71
72
73
74
75
76
  	[1] = 0x1,  /* 1s  */
  	[2] = 0x2,  /* 2s  */
  	[3] = 0x3,  /* 3s  */
  	[4] = 0x4,  /* 4s  */
  	[5] = 0x5,  /* 5s  */
  	[6] = 0x6,  /* 6s  */
  	[8] = 0x7,  /* 8s  */
  	[10] = 0x8, /* 10s */
  	[12] = 0x9, /* 12s */
  	[14] = 0xA, /* 14s */
  	[16] = 0xB, /* 16s */
d00680ed0   Carlo Caione   watchdog: sunxi: ...
77
  };
440e96bc7   Maxime Ripard   wdt: sunxi: Move ...
78

4d8b229d5   Guenter Roeck   watchdog: Add 'ac...
79
80
  static int sunxi_wdt_restart(struct watchdog_device *wdt_dev,
  			     unsigned long action, void *data)
440e96bc7   Maxime Ripard   wdt: sunxi: Move ...
81
  {
0ebad1e5e   Damien Riegel   watchdog: sunxi_w...
82
  	struct sunxi_wdt_dev *sunxi_wdt = watchdog_get_drvdata(wdt_dev);
d20a1d90a   Guenter Roeck   watchdog: sunxi: ...
83
  	void __iomem *wdt_base = sunxi_wdt->wdt_base;
f2147de33   Chen-Yu Tsai   watchdog: sunxi: ...
84
85
86
87
88
89
90
91
  	const struct sunxi_wdt_reg *regs = sunxi_wdt->wdt_regs;
  	u32 val;
  
  	/* Set system reset function */
  	val = readl(wdt_base + regs->wdt_cfg);
  	val &= ~(regs->wdt_reset_mask);
  	val |= regs->wdt_reset_val;
  	writel(val, wdt_base + regs->wdt_cfg);
d20a1d90a   Guenter Roeck   watchdog: sunxi: ...
92

f2147de33   Chen-Yu Tsai   watchdog: sunxi: ...
93
94
95
96
97
  	/* Set lowest timeout and enable watchdog */
  	val = readl(wdt_base + regs->wdt_mode);
  	val &= ~(WDT_TIMEOUT_MASK << regs->wdt_timeout_shift);
  	val |= WDT_MODE_EN;
  	writel(val, wdt_base + regs->wdt_mode);
440e96bc7   Maxime Ripard   wdt: sunxi: Move ...
98
99
100
101
102
  
  	/*
  	 * Restart the watchdog. The default (and lowest) interval
  	 * value for the watchdog is 0.5s.
  	 */
f2147de33   Chen-Yu Tsai   watchdog: sunxi: ...
103
  	writel(WDT_CTRL_RELOAD, wdt_base + regs->wdt_ctrl);
440e96bc7   Maxime Ripard   wdt: sunxi: Move ...
104
105
106
  
  	while (1) {
  		mdelay(5);
f2147de33   Chen-Yu Tsai   watchdog: sunxi: ...
107
108
109
  		val = readl(wdt_base + regs->wdt_mode);
  		val |= WDT_MODE_EN;
  		writel(val, wdt_base + regs->wdt_mode);
440e96bc7   Maxime Ripard   wdt: sunxi: Move ...
110
  	}
0ebad1e5e   Damien Riegel   watchdog: sunxi_w...
111
  	return 0;
440e96bc7   Maxime Ripard   wdt: sunxi: Move ...
112
  }
d00680ed0   Carlo Caione   watchdog: sunxi: ...
113
114
115
116
  static int sunxi_wdt_ping(struct watchdog_device *wdt_dev)
  {
  	struct sunxi_wdt_dev *sunxi_wdt = watchdog_get_drvdata(wdt_dev);
  	void __iomem *wdt_base = sunxi_wdt->wdt_base;
f2147de33   Chen-Yu Tsai   watchdog: sunxi: ...
117
  	const struct sunxi_wdt_reg *regs = sunxi_wdt->wdt_regs;
d00680ed0   Carlo Caione   watchdog: sunxi: ...
118

f2147de33   Chen-Yu Tsai   watchdog: sunxi: ...
119
  	writel(WDT_CTRL_RELOAD, wdt_base + regs->wdt_ctrl);
d00680ed0   Carlo Caione   watchdog: sunxi: ...
120
121
122
123
124
125
126
127
128
  
  	return 0;
  }
  
  static int sunxi_wdt_set_timeout(struct watchdog_device *wdt_dev,
  		unsigned int timeout)
  {
  	struct sunxi_wdt_dev *sunxi_wdt = watchdog_get_drvdata(wdt_dev);
  	void __iomem *wdt_base = sunxi_wdt->wdt_base;
f2147de33   Chen-Yu Tsai   watchdog: sunxi: ...
129
  	const struct sunxi_wdt_reg *regs = sunxi_wdt->wdt_regs;
d00680ed0   Carlo Caione   watchdog: sunxi: ...
130
131
132
133
134
135
  	u32 reg;
  
  	if (wdt_timeout_map[timeout] == 0)
  		timeout++;
  
  	sunxi_wdt->wdt_dev.timeout = timeout;
f2147de33   Chen-Yu Tsai   watchdog: sunxi: ...
136
137
138
139
  	reg = readl(wdt_base + regs->wdt_mode);
  	reg &= ~(WDT_TIMEOUT_MASK << regs->wdt_timeout_shift);
  	reg |= wdt_timeout_map[timeout] << regs->wdt_timeout_shift;
  	writel(reg, wdt_base + regs->wdt_mode);
d00680ed0   Carlo Caione   watchdog: sunxi: ...
140
141
142
143
144
145
146
147
148
149
  
  	sunxi_wdt_ping(wdt_dev);
  
  	return 0;
  }
  
  static int sunxi_wdt_stop(struct watchdog_device *wdt_dev)
  {
  	struct sunxi_wdt_dev *sunxi_wdt = watchdog_get_drvdata(wdt_dev);
  	void __iomem *wdt_base = sunxi_wdt->wdt_base;
f2147de33   Chen-Yu Tsai   watchdog: sunxi: ...
150
  	const struct sunxi_wdt_reg *regs = sunxi_wdt->wdt_regs;
d00680ed0   Carlo Caione   watchdog: sunxi: ...
151

f2147de33   Chen-Yu Tsai   watchdog: sunxi: ...
152
  	writel(0, wdt_base + regs->wdt_mode);
d00680ed0   Carlo Caione   watchdog: sunxi: ...
153
154
155
156
157
158
159
160
161
  
  	return 0;
  }
  
  static int sunxi_wdt_start(struct watchdog_device *wdt_dev)
  {
  	u32 reg;
  	struct sunxi_wdt_dev *sunxi_wdt = watchdog_get_drvdata(wdt_dev);
  	void __iomem *wdt_base = sunxi_wdt->wdt_base;
f2147de33   Chen-Yu Tsai   watchdog: sunxi: ...
162
  	const struct sunxi_wdt_reg *regs = sunxi_wdt->wdt_regs;
d00680ed0   Carlo Caione   watchdog: sunxi: ...
163
164
165
166
167
168
  	int ret;
  
  	ret = sunxi_wdt_set_timeout(&sunxi_wdt->wdt_dev,
  			sunxi_wdt->wdt_dev.timeout);
  	if (ret < 0)
  		return ret;
f2147de33   Chen-Yu Tsai   watchdog: sunxi: ...
169
170
171
  	/* Set system reset function */
  	reg = readl(wdt_base + regs->wdt_cfg);
  	reg &= ~(regs->wdt_reset_mask);
0919e4445   Francesco Lavra   watchdog: sunxi: ...
172
  	reg |= regs->wdt_reset_val;
f2147de33   Chen-Yu Tsai   watchdog: sunxi: ...
173
174
175
176
177
178
  	writel(reg, wdt_base + regs->wdt_cfg);
  
  	/* Enable watchdog */
  	reg = readl(wdt_base + regs->wdt_mode);
  	reg |= WDT_MODE_EN;
  	writel(reg, wdt_base + regs->wdt_mode);
d00680ed0   Carlo Caione   watchdog: sunxi: ...
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
  
  	return 0;
  }
  
  static const struct watchdog_info sunxi_wdt_info = {
  	.identity	= DRV_NAME,
  	.options	= WDIOF_SETTIMEOUT |
  			  WDIOF_KEEPALIVEPING |
  			  WDIOF_MAGICCLOSE,
  };
  
  static const struct watchdog_ops sunxi_wdt_ops = {
  	.owner		= THIS_MODULE,
  	.start		= sunxi_wdt_start,
  	.stop		= sunxi_wdt_stop,
  	.ping		= sunxi_wdt_ping,
  	.set_timeout	= sunxi_wdt_set_timeout,
0ebad1e5e   Damien Riegel   watchdog: sunxi_w...
196
  	.restart	= sunxi_wdt_restart,
d00680ed0   Carlo Caione   watchdog: sunxi: ...
197
  };
f2147de33   Chen-Yu Tsai   watchdog: sunxi: ...
198
199
200
201
202
203
204
205
  static const struct sunxi_wdt_reg sun4i_wdt_reg = {
  	.wdt_ctrl = 0x00,
  	.wdt_cfg = 0x04,
  	.wdt_mode = 0x04,
  	.wdt_timeout_shift = 3,
  	.wdt_reset_mask = 0x02,
  	.wdt_reset_val = 0x02,
  };
c5ec618fb   Chen-Yu Tsai   watchdog: sunxi: ...
206
207
208
209
210
211
212
213
  static const struct sunxi_wdt_reg sun6i_wdt_reg = {
  	.wdt_ctrl = 0x10,
  	.wdt_cfg = 0x14,
  	.wdt_mode = 0x18,
  	.wdt_timeout_shift = 4,
  	.wdt_reset_mask = 0x03,
  	.wdt_reset_val = 0x01,
  };
f2147de33   Chen-Yu Tsai   watchdog: sunxi: ...
214
215
  static const struct of_device_id sunxi_wdt_dt_ids[] = {
  	{ .compatible = "allwinner,sun4i-a10-wdt", .data = &sun4i_wdt_reg },
c5ec618fb   Chen-Yu Tsai   watchdog: sunxi: ...
216
  	{ .compatible = "allwinner,sun6i-a31-wdt", .data = &sun6i_wdt_reg },
f2147de33   Chen-Yu Tsai   watchdog: sunxi: ...
217
218
219
  	{ /* sentinel */ }
  };
  MODULE_DEVICE_TABLE(of, sunxi_wdt_dt_ids);
1d5898b4f   Maxime Ripard   watchdog: sunxi: ...
220
  static int sunxi_wdt_probe(struct platform_device *pdev)
d00680ed0   Carlo Caione   watchdog: sunxi: ...
221
  {
8ba41f6c4   Guenter Roeck   watchdog: sunxi_w...
222
  	struct device *dev = &pdev->dev;
d00680ed0   Carlo Caione   watchdog: sunxi: ...
223
  	struct sunxi_wdt_dev *sunxi_wdt;
d00680ed0   Carlo Caione   watchdog: sunxi: ...
224
  	int err;
8ba41f6c4   Guenter Roeck   watchdog: sunxi_w...
225
  	sunxi_wdt = devm_kzalloc(dev, sizeof(*sunxi_wdt), GFP_KERNEL);
d00680ed0   Carlo Caione   watchdog: sunxi: ...
226
227
  	if (!sunxi_wdt)
  		return -EINVAL;
8ba41f6c4   Guenter Roeck   watchdog: sunxi_w...
228
  	sunxi_wdt->wdt_regs = of_device_get_match_data(dev);
e53103714   Corentin Labbe   watchdog: sunxi_w...
229
  	if (!sunxi_wdt->wdt_regs)
f2147de33   Chen-Yu Tsai   watchdog: sunxi: ...
230
  		return -ENODEV;
0f0a6a285   Guenter Roeck   watchdog: Convert...
231
  	sunxi_wdt->wdt_base = devm_platform_ioremap_resource(pdev, 0);
d00680ed0   Carlo Caione   watchdog: sunxi: ...
232
233
234
235
236
237
238
239
  	if (IS_ERR(sunxi_wdt->wdt_base))
  		return PTR_ERR(sunxi_wdt->wdt_base);
  
  	sunxi_wdt->wdt_dev.info = &sunxi_wdt_info;
  	sunxi_wdt->wdt_dev.ops = &sunxi_wdt_ops;
  	sunxi_wdt->wdt_dev.timeout = WDT_MAX_TIMEOUT;
  	sunxi_wdt->wdt_dev.max_timeout = WDT_MAX_TIMEOUT;
  	sunxi_wdt->wdt_dev.min_timeout = WDT_MIN_TIMEOUT;
8ba41f6c4   Guenter Roeck   watchdog: sunxi_w...
240
  	sunxi_wdt->wdt_dev.parent = dev;
d00680ed0   Carlo Caione   watchdog: sunxi: ...
241

8ba41f6c4   Guenter Roeck   watchdog: sunxi_w...
242
  	watchdog_init_timeout(&sunxi_wdt->wdt_dev, timeout, dev);
d00680ed0   Carlo Caione   watchdog: sunxi: ...
243
  	watchdog_set_nowayout(&sunxi_wdt->wdt_dev, nowayout);
0ebad1e5e   Damien Riegel   watchdog: sunxi_w...
244
  	watchdog_set_restart_priority(&sunxi_wdt->wdt_dev, 128);
d00680ed0   Carlo Caione   watchdog: sunxi: ...
245
246
247
248
  
  	watchdog_set_drvdata(&sunxi_wdt->wdt_dev, sunxi_wdt);
  
  	sunxi_wdt_stop(&sunxi_wdt->wdt_dev);
42f826937   Guenter Roeck   watchdog: sunxi_w...
249
  	watchdog_stop_on_reboot(&sunxi_wdt->wdt_dev);
8ba41f6c4   Guenter Roeck   watchdog: sunxi_w...
250
  	err = devm_watchdog_register_device(dev, &sunxi_wdt->wdt_dev);
d00680ed0   Carlo Caione   watchdog: sunxi: ...
251
252
  	if (unlikely(err))
  		return err;
8ba41f6c4   Guenter Roeck   watchdog: sunxi_w...
253
254
  	dev_info(dev, "Watchdog enabled (timeout=%d sec, nowayout=%d)",
  		 sunxi_wdt->wdt_dev.timeout, nowayout);
d00680ed0   Carlo Caione   watchdog: sunxi: ...
255
256
257
  
  	return 0;
  }
d00680ed0   Carlo Caione   watchdog: sunxi: ...
258
259
  static struct platform_driver sunxi_wdt_driver = {
  	.probe		= sunxi_wdt_probe,
d00680ed0   Carlo Caione   watchdog: sunxi: ...
260
  	.driver		= {
d00680ed0   Carlo Caione   watchdog: sunxi: ...
261
  		.name		= DRV_NAME,
85eee8192   Sachin Kamat   watchdog: Remove ...
262
  		.of_match_table	= sunxi_wdt_dt_ids,
d00680ed0   Carlo Caione   watchdog: sunxi: ...
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
  	},
  };
  
  module_platform_driver(sunxi_wdt_driver);
  
  module_param(timeout, uint, 0);
  MODULE_PARM_DESC(timeout, "Watchdog heartbeat in seconds");
  
  module_param(nowayout, bool, 0);
  MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started "
  		"(default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
  
  MODULE_LICENSE("GPL");
  MODULE_AUTHOR("Carlo Caione <carlo.caione@gmail.com>");
  MODULE_AUTHOR("Henrik Nordstrom <henrik@henriknordstrom.net>");
  MODULE_DESCRIPTION("sunxi WatchDog Timer Driver");
  MODULE_VERSION(DRV_VERSION);