blob: a086005fbaacd7fc50c10f09a8c36ab11dfd5929 [file] [log] [blame]
John Crispin2f58b8d2011-05-05 23:00:23 +02001/*
2 * This program is free software; you can redistribute it and/or modify it
3 * under the terms of the GNU General Public License version 2 as published
4 * by the Free Software Foundation.
5 *
John Crispinf3519a62016-12-20 19:56:59 +01006 * Copyright (C) 2010 John Crispin <john@phrozen.org>
Hauke Mehrtens710322b2017-08-20 00:18:11 +02007 * Copyright (C) 2017 Hauke Mehrtens <hauke@hauke-m.de>
John Crispin2f58b8d2011-05-05 23:00:23 +02008 * Based on EP93xx wdt driver
9 */
10
Joe Perches27c766a2012-02-15 15:06:19 -080011#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
12
John Crispin2f58b8d2011-05-05 23:00:23 +020013#include <linux/module.h>
14#include <linux/fs.h>
15#include <linux/miscdevice.h>
Hauke Mehrtens1f59f8a2018-09-13 23:32:09 +020016#include <linux/bitops.h>
John Crispin2f58b8d2011-05-05 23:00:23 +020017#include <linux/watchdog.h>
John Crispincdb86122012-04-12 21:21:56 +020018#include <linux/of_platform.h>
John Crispin2f58b8d2011-05-05 23:00:23 +020019#include <linux/uaccess.h>
20#include <linux/clk.h>
21#include <linux/io.h>
Hauke Mehrtens710322b2017-08-20 00:18:11 +020022#include <linux/regmap.h>
23#include <linux/mfd/syscon.h>
John Crispin2f58b8d2011-05-05 23:00:23 +020024
John Crispincdb86122012-04-12 21:21:56 +020025#include <lantiq_soc.h>
John Crispin2f58b8d2011-05-05 23:00:23 +020026
Hauke Mehrtens710322b2017-08-20 00:18:11 +020027#define LTQ_XRX_RCU_RST_STAT 0x0014
28#define LTQ_XRX_RCU_RST_STAT_WDT BIT(31)
29
30/* CPU0 Reset Source Register */
31#define LTQ_FALCON_SYS1_CPU0RS 0x0060
32/* reset cause mask */
33#define LTQ_FALCON_SYS1_CPU0RS_MASK 0x0007
34#define LTQ_FALCON_SYS1_CPU0RS_WDT 0x02
35
John Crispincdb86122012-04-12 21:21:56 +020036/*
37 * Section 3.4 of the datasheet
John Crispin2f58b8d2011-05-05 23:00:23 +020038 * The password sequence protects the WDT control register from unintended
39 * write actions, which might cause malfunction of the WDT.
40 *
41 * essentially the following two magic passwords need to be written to allow
42 * IO access to the WDT core
43 */
Hauke Mehrtens1f59f8a2018-09-13 23:32:09 +020044#define LTQ_WDT_CR_PW1 0x00BE0000
45#define LTQ_WDT_CR_PW2 0x00DC0000
John Crispin2f58b8d2011-05-05 23:00:23 +020046
Hauke Mehrtens1f59f8a2018-09-13 23:32:09 +020047#define LTQ_WDT_CR 0x0 /* watchdog control register */
48#define LTQ_WDT_CR_GEN BIT(31) /* enable bit */
49/* Pre-warning limit set to 1/16 of max WDT period */
50#define LTQ_WDT_CR_PWL (0x3 << 26)
51/* set clock divider to 0x40000 */
52#define LTQ_WDT_CR_CLKDIV (0x3 << 24)
53#define LTQ_WDT_CR_PW_MASK GENMASK(23, 16) /* Password field */
54#define LTQ_WDT_CR_MAX_TIMEOUT ((1 << 16) - 1) /* The reload field is 16 bit */
John Crispin2f58b8d2011-05-05 23:00:23 +020055
John Crispin2f58b8d2011-05-05 23:00:23 +020056#define LTQ_WDT_DIVIDER 0x40000
John Crispin2f58b8d2011-05-05 23:00:23 +020057
Wim Van Sebroeck86a1e182012-03-05 16:51:11 +010058static bool nowayout = WATCHDOG_NOWAYOUT;
John Crispin2f58b8d2011-05-05 23:00:23 +020059
60static void __iomem *ltq_wdt_membase;
61static unsigned long ltq_io_region_clk_rate;
62
63static unsigned long ltq_wdt_bootstatus;
64static unsigned long ltq_wdt_in_use;
65static int ltq_wdt_timeout = 30;
66static int ltq_wdt_ok_to_close;
67
68static void
69ltq_wdt_enable(void)
70{
John Crispin9cfce472011-08-24 10:31:39 +020071 unsigned long int timeout = ltq_wdt_timeout *
John Crispin2f58b8d2011-05-05 23:00:23 +020072 (ltq_io_region_clk_rate / LTQ_WDT_DIVIDER) + 0x1000;
Hauke Mehrtens1f59f8a2018-09-13 23:32:09 +020073 if (timeout > LTQ_WDT_CR_MAX_TIMEOUT)
74 timeout = LTQ_WDT_CR_MAX_TIMEOUT;
John Crispin2f58b8d2011-05-05 23:00:23 +020075
76 /* write the first password magic */
Hauke Mehrtens1f59f8a2018-09-13 23:32:09 +020077 ltq_w32(LTQ_WDT_CR_PW1, ltq_wdt_membase + LTQ_WDT_CR);
John Crispin2f58b8d2011-05-05 23:00:23 +020078 /* write the second magic plus the configuration and new timeout */
Hauke Mehrtens1f59f8a2018-09-13 23:32:09 +020079 ltq_w32(LTQ_WDT_CR_GEN | LTQ_WDT_CR_PWL | LTQ_WDT_CR_CLKDIV |
80 LTQ_WDT_CR_PW2 | timeout, ltq_wdt_membase + LTQ_WDT_CR);
John Crispin2f58b8d2011-05-05 23:00:23 +020081}
82
83static void
84ltq_wdt_disable(void)
85{
86 /* write the first password magic */
Hauke Mehrtens1f59f8a2018-09-13 23:32:09 +020087 ltq_w32(LTQ_WDT_CR_PW1, ltq_wdt_membase + LTQ_WDT_CR);
John Crispincdb86122012-04-12 21:21:56 +020088 /*
89 * write the second password magic with no config
John Crispin2f58b8d2011-05-05 23:00:23 +020090 * this turns the watchdog off
91 */
Hauke Mehrtens1f59f8a2018-09-13 23:32:09 +020092 ltq_w32(LTQ_WDT_CR_PW2, ltq_wdt_membase + LTQ_WDT_CR);
John Crispin2f58b8d2011-05-05 23:00:23 +020093}
94
95static ssize_t
96ltq_wdt_write(struct file *file, const char __user *data,
97 size_t len, loff_t *ppos)
98{
99 if (len) {
100 if (!nowayout) {
101 size_t i;
102
103 ltq_wdt_ok_to_close = 0;
104 for (i = 0; i != len; i++) {
105 char c;
106
107 if (get_user(c, data + i))
108 return -EFAULT;
109 if (c == 'V')
110 ltq_wdt_ok_to_close = 1;
111 else
112 ltq_wdt_ok_to_close = 0;
113 }
114 }
115 ltq_wdt_enable();
116 }
117
118 return len;
119}
120
121static struct watchdog_info ident = {
122 .options = WDIOF_MAGICCLOSE | WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING |
123 WDIOF_CARDRESET,
124 .identity = "ltq_wdt",
125};
126
127static long
128ltq_wdt_ioctl(struct file *file,
129 unsigned int cmd, unsigned long arg)
130{
131 int ret = -ENOTTY;
132
133 switch (cmd) {
134 case WDIOC_GETSUPPORT:
135 ret = copy_to_user((struct watchdog_info __user *)arg, &ident,
136 sizeof(ident)) ? -EFAULT : 0;
137 break;
138
139 case WDIOC_GETBOOTSTATUS:
140 ret = put_user(ltq_wdt_bootstatus, (int __user *)arg);
141 break;
142
143 case WDIOC_GETSTATUS:
144 ret = put_user(0, (int __user *)arg);
145 break;
146
147 case WDIOC_SETTIMEOUT:
148 ret = get_user(ltq_wdt_timeout, (int __user *)arg);
149 if (!ret)
150 ltq_wdt_enable();
151 /* intentional drop through */
152 case WDIOC_GETTIMEOUT:
153 ret = put_user(ltq_wdt_timeout, (int __user *)arg);
154 break;
155
156 case WDIOC_KEEPALIVE:
157 ltq_wdt_enable();
158 ret = 0;
159 break;
160 }
161 return ret;
162}
163
164static int
165ltq_wdt_open(struct inode *inode, struct file *file)
166{
167 if (test_and_set_bit(0, &ltq_wdt_in_use))
168 return -EBUSY;
169 ltq_wdt_in_use = 1;
170 ltq_wdt_enable();
171
172 return nonseekable_open(inode, file);
173}
174
175static int
176ltq_wdt_release(struct inode *inode, struct file *file)
177{
178 if (ltq_wdt_ok_to_close)
179 ltq_wdt_disable();
180 else
Joe Perches27c766a2012-02-15 15:06:19 -0800181 pr_err("watchdog closed without warning\n");
John Crispin2f58b8d2011-05-05 23:00:23 +0200182 ltq_wdt_ok_to_close = 0;
183 clear_bit(0, &ltq_wdt_in_use);
184
185 return 0;
186}
187
188static const struct file_operations ltq_wdt_fops = {
189 .owner = THIS_MODULE,
190 .write = ltq_wdt_write,
191 .unlocked_ioctl = ltq_wdt_ioctl,
192 .open = ltq_wdt_open,
193 .release = ltq_wdt_release,
194 .llseek = no_llseek,
195};
196
197static struct miscdevice ltq_wdt_miscdev = {
198 .minor = WATCHDOG_MINOR,
199 .name = "watchdog",
200 .fops = &ltq_wdt_fops,
201};
202
Hauke Mehrtens710322b2017-08-20 00:18:11 +0200203typedef int (*ltq_wdt_bootstatus_set)(struct platform_device *pdev);
204
205static int ltq_wdt_bootstatus_xrx(struct platform_device *pdev)
206{
207 struct device *dev = &pdev->dev;
208 struct regmap *rcu_regmap;
209 u32 val;
210 int err;
211
212 rcu_regmap = syscon_regmap_lookup_by_phandle(dev->of_node, "regmap");
213 if (IS_ERR(rcu_regmap))
214 return PTR_ERR(rcu_regmap);
215
216 err = regmap_read(rcu_regmap, LTQ_XRX_RCU_RST_STAT, &val);
217 if (err)
218 return err;
219
220 if (val & LTQ_XRX_RCU_RST_STAT_WDT)
221 ltq_wdt_bootstatus = WDIOF_CARDRESET;
222
223 return 0;
224}
225
226static int ltq_wdt_bootstatus_falcon(struct platform_device *pdev)
227{
228 struct device *dev = &pdev->dev;
229 struct regmap *rcu_regmap;
230 u32 val;
231 int err;
232
233 rcu_regmap = syscon_regmap_lookup_by_phandle(dev->of_node,
234 "lantiq,rcu");
235 if (IS_ERR(rcu_regmap))
236 return PTR_ERR(rcu_regmap);
237
238 err = regmap_read(rcu_regmap, LTQ_FALCON_SYS1_CPU0RS, &val);
239 if (err)
240 return err;
241
242 if ((val & LTQ_FALCON_SYS1_CPU0RS_MASK) == LTQ_FALCON_SYS1_CPU0RS_WDT)
243 ltq_wdt_bootstatus = WDIOF_CARDRESET;
244
245 return 0;
246}
247
Bill Pemberton2d991a12012-11-19 13:21:41 -0500248static int
John Crispin2f58b8d2011-05-05 23:00:23 +0200249ltq_wdt_probe(struct platform_device *pdev)
250{
251 struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
252 struct clk *clk;
Hauke Mehrtens710322b2017-08-20 00:18:11 +0200253 ltq_wdt_bootstatus_set ltq_wdt_bootstatus_set;
254 int ret;
John Crispin2f58b8d2011-05-05 23:00:23 +0200255
Thierry Reding4c271bb2013-01-21 11:09:25 +0100256 ltq_wdt_membase = devm_ioremap_resource(&pdev->dev, res);
257 if (IS_ERR(ltq_wdt_membase))
258 return PTR_ERR(ltq_wdt_membase);
John Crispin2f58b8d2011-05-05 23:00:23 +0200259
Hauke Mehrtens710322b2017-08-20 00:18:11 +0200260 ltq_wdt_bootstatus_set = of_device_get_match_data(&pdev->dev);
261 if (ltq_wdt_bootstatus_set) {
262 ret = ltq_wdt_bootstatus_set(pdev);
263 if (ret)
264 return ret;
265 }
266
John Crispin2f58b8d2011-05-05 23:00:23 +0200267 /* we do not need to enable the clock as it is always running */
John Crispincdb86122012-04-12 21:21:56 +0200268 clk = clk_get_io();
269 if (IS_ERR(clk)) {
270 dev_err(&pdev->dev, "Failed to get clock\n");
271 return -ENOENT;
272 }
John Crispin2f58b8d2011-05-05 23:00:23 +0200273 ltq_io_region_clk_rate = clk_get_rate(clk);
274 clk_put(clk);
275
John Crispincdb86122012-04-12 21:21:56 +0200276 dev_info(&pdev->dev, "Init done\n");
John Crispin2f58b8d2011-05-05 23:00:23 +0200277 return misc_register(&ltq_wdt_miscdev);
278}
279
Bill Pemberton4b12b892012-11-19 13:26:24 -0500280static int
John Crispin2f58b8d2011-05-05 23:00:23 +0200281ltq_wdt_remove(struct platform_device *pdev)
282{
283 misc_deregister(&ltq_wdt_miscdev);
284
John Crispin2f58b8d2011-05-05 23:00:23 +0200285 return 0;
286}
287
John Crispincdb86122012-04-12 21:21:56 +0200288static const struct of_device_id ltq_wdt_match[] = {
Hauke Mehrtens710322b2017-08-20 00:18:11 +0200289 { .compatible = "lantiq,wdt", .data = NULL},
290 { .compatible = "lantiq,xrx100-wdt", .data = ltq_wdt_bootstatus_xrx },
291 { .compatible = "lantiq,falcon-wdt", .data = ltq_wdt_bootstatus_falcon },
John Crispincdb86122012-04-12 21:21:56 +0200292 {},
293};
294MODULE_DEVICE_TABLE(of, ltq_wdt_match);
John Crispin2f58b8d2011-05-05 23:00:23 +0200295
296static struct platform_driver ltq_wdt_driver = {
John Crispincdb86122012-04-12 21:21:56 +0200297 .probe = ltq_wdt_probe,
Bill Pemberton82268712012-11-19 13:21:12 -0500298 .remove = ltq_wdt_remove,
John Crispin2f58b8d2011-05-05 23:00:23 +0200299 .driver = {
John Crispincdb86122012-04-12 21:21:56 +0200300 .name = "wdt",
John Crispincdb86122012-04-12 21:21:56 +0200301 .of_match_table = ltq_wdt_match,
John Crispin2f58b8d2011-05-05 23:00:23 +0200302 },
303};
304
John Crispincdb86122012-04-12 21:21:56 +0200305module_platform_driver(ltq_wdt_driver);
John Crispin2f58b8d2011-05-05 23:00:23 +0200306
Wim Van Sebroeck86a1e182012-03-05 16:51:11 +0100307module_param(nowayout, bool, 0);
John Crispin2f58b8d2011-05-05 23:00:23 +0200308MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started");
John Crispinf3519a62016-12-20 19:56:59 +0100309MODULE_AUTHOR("John Crispin <john@phrozen.org>");
John Crispin2f58b8d2011-05-05 23:00:23 +0200310MODULE_DESCRIPTION("Lantiq SoC Watchdog");
311MODULE_LICENSE("GPL");