Blame view

drivers/watchdog/mtk_wdt.c 8.34 KB
2e62c4988   Marcus Folkesson   watchdog: add SPD...
1
  // SPDX-License-Identifier: GPL-2.0+
a44a45536   Matthias Brugger   watchdog: Add dri...
2
3
4
5
6
7
8
  /*
   * Mediatek Watchdog Driver
   *
   * Copyright (C) 2014 Matthias Brugger
   *
   * Matthias Brugger <matthias.bgg@gmail.com>
   *
a44a45536   Matthias Brugger   watchdog: Add dri...
9
10
   * Based on sunxi_wdt.c
   */
9e5236e7c   yong.liang   watchdog: mtk_wdt...
11
  #include <dt-bindings/reset-controller/mt2712-resets.h>
c254e1030   yong.liang   watchdog: mtk_wdt...
12
13
  #include <dt-bindings/reset-controller/mt8183-resets.h>
  #include <linux/delay.h>
a44a45536   Matthias Brugger   watchdog: Add dri...
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>
c254e1030   yong.liang   watchdog: mtk_wdt...
21
  #include <linux/of_device.h>
a44a45536   Matthias Brugger   watchdog: Add dri...
22
  #include <linux/platform_device.h>
c254e1030   yong.liang   watchdog: mtk_wdt...
23
  #include <linux/reset-controller.h>
a44a45536   Matthias Brugger   watchdog: Add dri...
24
25
  #include <linux/types.h>
  #include <linux/watchdog.h>
a44a45536   Matthias Brugger   watchdog: Add dri...
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
  
  #define WDT_MAX_TIMEOUT		31
  #define WDT_MIN_TIMEOUT		1
  #define WDT_LENGTH_TIMEOUT(n)	((n) << 5)
  
  #define WDT_LENGTH		0x04
  #define WDT_LENGTH_KEY		0x8
  
  #define WDT_RST			0x08
  #define WDT_RST_RELOAD		0x1971
  
  #define WDT_MODE		0x00
  #define WDT_MODE_EN		(1 << 0)
  #define WDT_MODE_EXT_POL_LOW	(0 << 1)
  #define WDT_MODE_EXT_POL_HIGH	(1 << 1)
  #define WDT_MODE_EXRST_EN	(1 << 2)
  #define WDT_MODE_IRQ_EN		(1 << 3)
  #define WDT_MODE_AUTO_START	(1 << 4)
  #define WDT_MODE_DUAL_EN	(1 << 6)
  #define WDT_MODE_KEY		0x22000000
  
  #define WDT_SWRST		0x14
  #define WDT_SWRST_KEY		0x1209
c254e1030   yong.liang   watchdog: mtk_wdt...
49
50
  #define WDT_SWSYSRST		0x18U
  #define WDT_SWSYS_RST_KEY	0x88000000
a44a45536   Matthias Brugger   watchdog: Add dri...
51
52
53
54
  #define DRV_NAME		"mtk-wdt"
  #define DRV_VERSION		"1.0"
  
  static bool nowayout = WATCHDOG_NOWAYOUT;
b82e6953a   Marcus Folkesson   watchdog: mtk: al...
55
  static unsigned int timeout;
a44a45536   Matthias Brugger   watchdog: Add dri...
56
57
58
59
  
  struct mtk_wdt_dev {
  	struct watchdog_device wdt_dev;
  	void __iomem *wdt_base;
c254e1030   yong.liang   watchdog: mtk_wdt...
60
61
62
63
64
65
  	spinlock_t lock; /* protects WDT_SWSYSRST reg */
  	struct reset_controller_dev rcdev;
  };
  
  struct mtk_wdt_data {
  	int toprgu_sw_rst_num;
a44a45536   Matthias Brugger   watchdog: Add dri...
66
  };
9e5236e7c   yong.liang   watchdog: mtk_wdt...
67
68
69
  static const struct mtk_wdt_data mt2712_data = {
  	.toprgu_sw_rst_num = MT2712_TOPRGU_SW_RST_NUM,
  };
c254e1030   yong.liang   watchdog: mtk_wdt...
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
  static const struct mtk_wdt_data mt8183_data = {
  	.toprgu_sw_rst_num = MT8183_TOPRGU_SW_RST_NUM,
  };
  
  static int toprgu_reset_update(struct reset_controller_dev *rcdev,
  			       unsigned long id, bool assert)
  {
  	unsigned int tmp;
  	unsigned long flags;
  	struct mtk_wdt_dev *data =
  		 container_of(rcdev, struct mtk_wdt_dev, rcdev);
  
  	spin_lock_irqsave(&data->lock, flags);
  
  	tmp = readl(data->wdt_base + WDT_SWSYSRST);
  	if (assert)
  		tmp |= BIT(id);
  	else
  		tmp &= ~BIT(id);
  	tmp |= WDT_SWSYS_RST_KEY;
  	writel(tmp, data->wdt_base + WDT_SWSYSRST);
  
  	spin_unlock_irqrestore(&data->lock, flags);
  
  	return 0;
  }
  
  static int toprgu_reset_assert(struct reset_controller_dev *rcdev,
  			       unsigned long id)
  {
  	return toprgu_reset_update(rcdev, id, true);
  }
  
  static int toprgu_reset_deassert(struct reset_controller_dev *rcdev,
  				 unsigned long id)
  {
  	return toprgu_reset_update(rcdev, id, false);
  }
  
  static int toprgu_reset(struct reset_controller_dev *rcdev,
  			unsigned long id)
  {
  	int ret;
  
  	ret = toprgu_reset_assert(rcdev, id);
  	if (ret)
  		return ret;
  
  	return toprgu_reset_deassert(rcdev, id);
  }
  
  static const struct reset_control_ops toprgu_reset_ops = {
  	.assert = toprgu_reset_assert,
  	.deassert = toprgu_reset_deassert,
  	.reset = toprgu_reset,
  };
  
  static int toprgu_register_reset_controller(struct platform_device *pdev,
  					    int rst_num)
  {
  	int ret;
  	struct mtk_wdt_dev *mtk_wdt = platform_get_drvdata(pdev);
  
  	spin_lock_init(&mtk_wdt->lock);
  
  	mtk_wdt->rcdev.owner = THIS_MODULE;
  	mtk_wdt->rcdev.nr_resets = rst_num;
  	mtk_wdt->rcdev.ops = &toprgu_reset_ops;
  	mtk_wdt->rcdev.of_node = pdev->dev.of_node;
  	ret = devm_reset_controller_register(&pdev->dev, &mtk_wdt->rcdev);
  	if (ret != 0)
  		dev_err(&pdev->dev,
  			"couldn't register wdt reset controller: %d
  ", ret);
  	return ret;
  }
4d8b229d5   Guenter Roeck   watchdog: Add 'ac...
146
147
  static int mtk_wdt_restart(struct watchdog_device *wdt_dev,
  			   unsigned long action, void *data)
a44a45536   Matthias Brugger   watchdog: Add dri...
148
  {
e86adc3f6   Damien Riegel   watchdog: mtk_wdt...
149
  	struct mtk_wdt_dev *mtk_wdt = watchdog_get_drvdata(wdt_dev);
a44a45536   Matthias Brugger   watchdog: Add dri...
150
  	void __iomem *wdt_base;
a44a45536   Matthias Brugger   watchdog: Add dri...
151
152
153
154
155
156
  	wdt_base = mtk_wdt->wdt_base;
  
  	while (1) {
  		writel(WDT_SWRST_KEY, wdt_base + WDT_SWRST);
  		mdelay(5);
  	}
e86adc3f6   Damien Riegel   watchdog: mtk_wdt...
157
  	return 0;
a44a45536   Matthias Brugger   watchdog: Add dri...
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
  }
  
  static int mtk_wdt_ping(struct watchdog_device *wdt_dev)
  {
  	struct mtk_wdt_dev *mtk_wdt = watchdog_get_drvdata(wdt_dev);
  	void __iomem *wdt_base = mtk_wdt->wdt_base;
  
  	iowrite32(WDT_RST_RELOAD, wdt_base + WDT_RST);
  
  	return 0;
  }
  
  static int mtk_wdt_set_timeout(struct watchdog_device *wdt_dev,
  				unsigned int timeout)
  {
  	struct mtk_wdt_dev *mtk_wdt = watchdog_get_drvdata(wdt_dev);
  	void __iomem *wdt_base = mtk_wdt->wdt_base;
  	u32 reg;
  
  	wdt_dev->timeout = timeout;
  
  	/*
  	 * One bit is the value of 512 ticks
  	 * The clock has 32 KHz
  	 */
  	reg = WDT_LENGTH_TIMEOUT(timeout << 6) | WDT_LENGTH_KEY;
  	iowrite32(reg, wdt_base + WDT_LENGTH);
  
  	mtk_wdt_ping(wdt_dev);
  
  	return 0;
  }
  
  static int mtk_wdt_stop(struct watchdog_device *wdt_dev)
  {
  	struct mtk_wdt_dev *mtk_wdt = watchdog_get_drvdata(wdt_dev);
  	void __iomem *wdt_base = mtk_wdt->wdt_base;
  	u32 reg;
  
  	reg = readl(wdt_base + WDT_MODE);
  	reg &= ~WDT_MODE_EN;
5da2bf1ac   Nicolas Boichat   watchdog: mtk_wdt...
199
  	reg |= WDT_MODE_KEY;
a44a45536   Matthias Brugger   watchdog: Add dri...
200
201
202
203
204
205
206
207
208
209
  	iowrite32(reg, wdt_base + WDT_MODE);
  
  	return 0;
  }
  
  static int mtk_wdt_start(struct watchdog_device *wdt_dev)
  {
  	u32 reg;
  	struct mtk_wdt_dev *mtk_wdt = watchdog_get_drvdata(wdt_dev);
  	void __iomem *wdt_base = mtk_wdt->wdt_base;
9ffd906d9   Dan Carpenter   watchdog: mtk_wdt...
210
  	int ret;
a44a45536   Matthias Brugger   watchdog: Add dri...
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
  
  	ret = mtk_wdt_set_timeout(wdt_dev, wdt_dev->timeout);
  	if (ret < 0)
  		return ret;
  
  	reg = ioread32(wdt_base + WDT_MODE);
  	reg &= ~(WDT_MODE_IRQ_EN | WDT_MODE_DUAL_EN);
  	reg |= (WDT_MODE_EN | WDT_MODE_KEY);
  	iowrite32(reg, wdt_base + WDT_MODE);
  
  	return 0;
  }
  
  static const struct watchdog_info mtk_wdt_info = {
  	.identity	= DRV_NAME,
  	.options	= WDIOF_SETTIMEOUT |
  			  WDIOF_KEEPALIVEPING |
  			  WDIOF_MAGICCLOSE,
  };
  
  static const struct watchdog_ops mtk_wdt_ops = {
  	.owner		= THIS_MODULE,
  	.start		= mtk_wdt_start,
  	.stop		= mtk_wdt_stop,
  	.ping		= mtk_wdt_ping,
  	.set_timeout	= mtk_wdt_set_timeout,
e86adc3f6   Damien Riegel   watchdog: mtk_wdt...
237
  	.restart	= mtk_wdt_restart,
a44a45536   Matthias Brugger   watchdog: Add dri...
238
239
240
241
  };
  
  static int mtk_wdt_probe(struct platform_device *pdev)
  {
a15f6e646   Guenter Roeck   watchdog: mtk_wdt...
242
  	struct device *dev = &pdev->dev;
a44a45536   Matthias Brugger   watchdog: Add dri...
243
  	struct mtk_wdt_dev *mtk_wdt;
c254e1030   yong.liang   watchdog: mtk_wdt...
244
  	const struct mtk_wdt_data *wdt_data;
a44a45536   Matthias Brugger   watchdog: Add dri...
245
  	int err;
a15f6e646   Guenter Roeck   watchdog: mtk_wdt...
246
  	mtk_wdt = devm_kzalloc(dev, sizeof(*mtk_wdt), GFP_KERNEL);
a44a45536   Matthias Brugger   watchdog: Add dri...
247
248
249
250
  	if (!mtk_wdt)
  		return -ENOMEM;
  
  	platform_set_drvdata(pdev, mtk_wdt);
0f0a6a285   Guenter Roeck   watchdog: Convert...
251
  	mtk_wdt->wdt_base = devm_platform_ioremap_resource(pdev, 0);
a44a45536   Matthias Brugger   watchdog: Add dri...
252
253
254
255
256
257
258
259
  	if (IS_ERR(mtk_wdt->wdt_base))
  		return PTR_ERR(mtk_wdt->wdt_base);
  
  	mtk_wdt->wdt_dev.info = &mtk_wdt_info;
  	mtk_wdt->wdt_dev.ops = &mtk_wdt_ops;
  	mtk_wdt->wdt_dev.timeout = WDT_MAX_TIMEOUT;
  	mtk_wdt->wdt_dev.max_timeout = WDT_MAX_TIMEOUT;
  	mtk_wdt->wdt_dev.min_timeout = WDT_MIN_TIMEOUT;
a15f6e646   Guenter Roeck   watchdog: mtk_wdt...
260
  	mtk_wdt->wdt_dev.parent = dev;
a44a45536   Matthias Brugger   watchdog: Add dri...
261

a15f6e646   Guenter Roeck   watchdog: mtk_wdt...
262
  	watchdog_init_timeout(&mtk_wdt->wdt_dev, timeout, dev);
a44a45536   Matthias Brugger   watchdog: Add dri...
263
  	watchdog_set_nowayout(&mtk_wdt->wdt_dev, nowayout);
e86adc3f6   Damien Riegel   watchdog: mtk_wdt...
264
  	watchdog_set_restart_priority(&mtk_wdt->wdt_dev, 128);
a44a45536   Matthias Brugger   watchdog: Add dri...
265
266
267
268
  
  	watchdog_set_drvdata(&mtk_wdt->wdt_dev, mtk_wdt);
  
  	mtk_wdt_stop(&mtk_wdt->wdt_dev);
a15f6e646   Guenter Roeck   watchdog: mtk_wdt...
269
270
  	watchdog_stop_on_reboot(&mtk_wdt->wdt_dev);
  	err = devm_watchdog_register_device(dev, &mtk_wdt->wdt_dev);
a44a45536   Matthias Brugger   watchdog: Add dri...
271
272
  	if (unlikely(err))
  		return err;
a15f6e646   Guenter Roeck   watchdog: mtk_wdt...
273
274
275
  	dev_info(dev, "Watchdog enabled (timeout=%d sec, nowayout=%d)
  ",
  		 mtk_wdt->wdt_dev.timeout, nowayout);
a44a45536   Matthias Brugger   watchdog: Add dri...
276

c254e1030   yong.liang   watchdog: mtk_wdt...
277
278
279
280
281
282
283
  	wdt_data = of_device_get_match_data(dev);
  	if (wdt_data) {
  		err = toprgu_register_reset_controller(pdev,
  						       wdt_data->toprgu_sw_rst_num);
  		if (err)
  			return err;
  	}
a44a45536   Matthias Brugger   watchdog: Add dri...
284
285
  	return 0;
  }
9fab06920   Greta Zhang   watchdog: mtk_wdt...
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
  #ifdef CONFIG_PM_SLEEP
  static int mtk_wdt_suspend(struct device *dev)
  {
  	struct mtk_wdt_dev *mtk_wdt = dev_get_drvdata(dev);
  
  	if (watchdog_active(&mtk_wdt->wdt_dev))
  		mtk_wdt_stop(&mtk_wdt->wdt_dev);
  
  	return 0;
  }
  
  static int mtk_wdt_resume(struct device *dev)
  {
  	struct mtk_wdt_dev *mtk_wdt = dev_get_drvdata(dev);
  
  	if (watchdog_active(&mtk_wdt->wdt_dev)) {
  		mtk_wdt_start(&mtk_wdt->wdt_dev);
  		mtk_wdt_ping(&mtk_wdt->wdt_dev);
  	}
  
  	return 0;
  }
  #endif
a44a45536   Matthias Brugger   watchdog: Add dri...
309
  static const struct of_device_id mtk_wdt_dt_ids[] = {
9e5236e7c   yong.liang   watchdog: mtk_wdt...
310
  	{ .compatible = "mediatek,mt2712-wdt", .data = &mt2712_data },
a44a45536   Matthias Brugger   watchdog: Add dri...
311
  	{ .compatible = "mediatek,mt6589-wdt" },
c254e1030   yong.liang   watchdog: mtk_wdt...
312
  	{ .compatible = "mediatek,mt8183-wdt", .data = &mt8183_data },
a44a45536   Matthias Brugger   watchdog: Add dri...
313
314
315
  	{ /* sentinel */ }
  };
  MODULE_DEVICE_TABLE(of, mtk_wdt_dt_ids);
9fab06920   Greta Zhang   watchdog: mtk_wdt...
316
317
318
319
  static const struct dev_pm_ops mtk_wdt_pm_ops = {
  	SET_SYSTEM_SLEEP_PM_OPS(mtk_wdt_suspend,
  				mtk_wdt_resume)
  };
a44a45536   Matthias Brugger   watchdog: Add dri...
320
321
  static struct platform_driver mtk_wdt_driver = {
  	.probe		= mtk_wdt_probe,
a44a45536   Matthias Brugger   watchdog: Add dri...
322
323
  	.driver		= {
  		.name		= DRV_NAME,
9fab06920   Greta Zhang   watchdog: mtk_wdt...
324
  		.pm		= &mtk_wdt_pm_ops,
a44a45536   Matthias Brugger   watchdog: Add dri...
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
  		.of_match_table	= mtk_wdt_dt_ids,
  	},
  };
  
  module_platform_driver(mtk_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("Matthias Brugger <matthias.bgg@gmail.com>");
  MODULE_DESCRIPTION("Mediatek WatchDog Timer Driver");
  MODULE_VERSION(DRV_VERSION);