xref: /linux/drivers/watchdog/mt7621_wdt.c (revision 9a87ffc99ec8eb8d35eed7c4f816d75f5cc9662e)
1d2912cb1SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
2ab3f09feSJohn Crispin /*
3ab3f09feSJohn Crispin  * Ralink MT7621/MT7628 built-in hardware watchdog timer
4ab3f09feSJohn Crispin  *
5f3519a66SJohn Crispin  * Copyright (C) 2014 John Crispin <john@phrozen.org>
6ab3f09feSJohn Crispin  *
7ab3f09feSJohn Crispin  * This driver was based on: drivers/watchdog/rt2880_wdt.c
8ab3f09feSJohn Crispin  */
9ab3f09feSJohn Crispin 
10ab3f09feSJohn Crispin #include <linux/clk.h>
11ab3f09feSJohn Crispin #include <linux/reset.h>
12ab3f09feSJohn Crispin #include <linux/module.h>
13ab3f09feSJohn Crispin #include <linux/kernel.h>
14ab3f09feSJohn Crispin #include <linux/watchdog.h>
15ab3f09feSJohn Crispin #include <linux/moduleparam.h>
16ab3f09feSJohn Crispin #include <linux/platform_device.h>
173aa8b8bbSNeilBrown #include <linux/mod_devicetable.h>
18*ff8ec4acSSergio Paracuellos #include <linux/mfd/syscon.h>
19*ff8ec4acSSergio Paracuellos #include <linux/regmap.h>
20ab3f09feSJohn Crispin 
21ab3f09feSJohn Crispin #define SYSC_RSTSTAT			0x38
22ab3f09feSJohn Crispin #define WDT_RST_CAUSE			BIT(1)
23ab3f09feSJohn Crispin 
24ab3f09feSJohn Crispin #define RALINK_WDT_TIMEOUT		30
25ab3f09feSJohn Crispin 
26ab3f09feSJohn Crispin #define TIMER_REG_TMRSTAT		0x00
27ab3f09feSJohn Crispin #define TIMER_REG_TMR1LOAD		0x24
28ab3f09feSJohn Crispin #define TIMER_REG_TMR1CTL		0x20
29ab3f09feSJohn Crispin 
30ab3f09feSJohn Crispin #define TMR1CTL_ENABLE			BIT(7)
31ab3f09feSJohn Crispin #define TMR1CTL_RESTART			BIT(9)
32ab3f09feSJohn Crispin #define TMR1CTL_PRESCALE_SHIFT		16
33ab3f09feSJohn Crispin 
34783c7cb4SSergio Paracuellos struct mt7621_wdt_data {
35783c7cb4SSergio Paracuellos 	void __iomem *base;
36783c7cb4SSergio Paracuellos 	struct reset_control *rst;
37*ff8ec4acSSergio Paracuellos 	struct regmap *sysc;
38783c7cb4SSergio Paracuellos 	struct watchdog_device wdt;
39783c7cb4SSergio Paracuellos };
40ab3f09feSJohn Crispin 
41ab3f09feSJohn Crispin static bool nowayout = WATCHDOG_NOWAYOUT;
42ab3f09feSJohn Crispin module_param(nowayout, bool, 0);
43ab3f09feSJohn Crispin MODULE_PARM_DESC(nowayout,
44ab3f09feSJohn Crispin 		 "Watchdog cannot be stopped once started (default="
45ab3f09feSJohn Crispin 		 __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
46ab3f09feSJohn Crispin 
rt_wdt_w32(void __iomem * base,unsigned int reg,u32 val)47783c7cb4SSergio Paracuellos static inline void rt_wdt_w32(void __iomem *base, unsigned int reg, u32 val)
48ab3f09feSJohn Crispin {
49783c7cb4SSergio Paracuellos 	iowrite32(val, base + reg);
50ab3f09feSJohn Crispin }
51ab3f09feSJohn Crispin 
rt_wdt_r32(void __iomem * base,unsigned int reg)52783c7cb4SSergio Paracuellos static inline u32 rt_wdt_r32(void __iomem *base, unsigned int reg)
53ab3f09feSJohn Crispin {
54783c7cb4SSergio Paracuellos 	return ioread32(base + reg);
55ab3f09feSJohn Crispin }
56ab3f09feSJohn Crispin 
mt7621_wdt_ping(struct watchdog_device * w)57ab3f09feSJohn Crispin static int mt7621_wdt_ping(struct watchdog_device *w)
58ab3f09feSJohn Crispin {
59783c7cb4SSergio Paracuellos 	struct mt7621_wdt_data *drvdata = watchdog_get_drvdata(w);
60783c7cb4SSergio Paracuellos 
61783c7cb4SSergio Paracuellos 	rt_wdt_w32(drvdata->base, TIMER_REG_TMRSTAT, TMR1CTL_RESTART);
62ab3f09feSJohn Crispin 
63ab3f09feSJohn Crispin 	return 0;
64ab3f09feSJohn Crispin }
65ab3f09feSJohn Crispin 
mt7621_wdt_set_timeout(struct watchdog_device * w,unsigned int t)66ab3f09feSJohn Crispin static int mt7621_wdt_set_timeout(struct watchdog_device *w, unsigned int t)
67ab3f09feSJohn Crispin {
68783c7cb4SSergio Paracuellos 	struct mt7621_wdt_data *drvdata = watchdog_get_drvdata(w);
69783c7cb4SSergio Paracuellos 
70ab3f09feSJohn Crispin 	w->timeout = t;
71783c7cb4SSergio Paracuellos 	rt_wdt_w32(drvdata->base, TIMER_REG_TMR1LOAD, t * 1000);
72ab3f09feSJohn Crispin 	mt7621_wdt_ping(w);
73ab3f09feSJohn Crispin 
74ab3f09feSJohn Crispin 	return 0;
75ab3f09feSJohn Crispin }
76ab3f09feSJohn Crispin 
mt7621_wdt_start(struct watchdog_device * w)77ab3f09feSJohn Crispin static int mt7621_wdt_start(struct watchdog_device *w)
78ab3f09feSJohn Crispin {
79783c7cb4SSergio Paracuellos 	struct mt7621_wdt_data *drvdata = watchdog_get_drvdata(w);
80ab3f09feSJohn Crispin 	u32 t;
81ab3f09feSJohn Crispin 
82ab3f09feSJohn Crispin 	/* set the prescaler to 1ms == 1000us */
83783c7cb4SSergio Paracuellos 	rt_wdt_w32(drvdata->base, TIMER_REG_TMR1CTL, 1000 << TMR1CTL_PRESCALE_SHIFT);
84ab3f09feSJohn Crispin 
85ab3f09feSJohn Crispin 	mt7621_wdt_set_timeout(w, w->timeout);
86ab3f09feSJohn Crispin 
87783c7cb4SSergio Paracuellos 	t = rt_wdt_r32(drvdata->base, TIMER_REG_TMR1CTL);
88ab3f09feSJohn Crispin 	t |= TMR1CTL_ENABLE;
89783c7cb4SSergio Paracuellos 	rt_wdt_w32(drvdata->base, TIMER_REG_TMR1CTL, t);
90ab3f09feSJohn Crispin 
91ab3f09feSJohn Crispin 	return 0;
92ab3f09feSJohn Crispin }
93ab3f09feSJohn Crispin 
mt7621_wdt_stop(struct watchdog_device * w)94ab3f09feSJohn Crispin static int mt7621_wdt_stop(struct watchdog_device *w)
95ab3f09feSJohn Crispin {
96783c7cb4SSergio Paracuellos 	struct mt7621_wdt_data *drvdata = watchdog_get_drvdata(w);
97ab3f09feSJohn Crispin 	u32 t;
98ab3f09feSJohn Crispin 
99ab3f09feSJohn Crispin 	mt7621_wdt_ping(w);
100ab3f09feSJohn Crispin 
101783c7cb4SSergio Paracuellos 	t = rt_wdt_r32(drvdata->base, TIMER_REG_TMR1CTL);
102ab3f09feSJohn Crispin 	t &= ~TMR1CTL_ENABLE;
103783c7cb4SSergio Paracuellos 	rt_wdt_w32(drvdata->base, TIMER_REG_TMR1CTL, t);
104ab3f09feSJohn Crispin 
105ab3f09feSJohn Crispin 	return 0;
106ab3f09feSJohn Crispin }
107ab3f09feSJohn Crispin 
mt7621_wdt_bootcause(struct mt7621_wdt_data * d)108*ff8ec4acSSergio Paracuellos static int mt7621_wdt_bootcause(struct mt7621_wdt_data *d)
109ab3f09feSJohn Crispin {
110*ff8ec4acSSergio Paracuellos 	u32 val;
111*ff8ec4acSSergio Paracuellos 
112*ff8ec4acSSergio Paracuellos 	regmap_read(d->sysc, SYSC_RSTSTAT, &val);
113*ff8ec4acSSergio Paracuellos 	if (val & WDT_RST_CAUSE)
114ab3f09feSJohn Crispin 		return WDIOF_CARDRESET;
115ab3f09feSJohn Crispin 
116ab3f09feSJohn Crispin 	return 0;
117ab3f09feSJohn Crispin }
118ab3f09feSJohn Crispin 
mt7621_wdt_is_running(struct watchdog_device * w)119392d39a8SAndré Draszik static int mt7621_wdt_is_running(struct watchdog_device *w)
120392d39a8SAndré Draszik {
121783c7cb4SSergio Paracuellos 	struct mt7621_wdt_data *drvdata = watchdog_get_drvdata(w);
122783c7cb4SSergio Paracuellos 
123783c7cb4SSergio Paracuellos 	return !!(rt_wdt_r32(drvdata->base, TIMER_REG_TMR1CTL) & TMR1CTL_ENABLE);
124392d39a8SAndré Draszik }
125392d39a8SAndré Draszik 
126323edb2eSJulia Lawall static const struct watchdog_info mt7621_wdt_info = {
127ab3f09feSJohn Crispin 	.identity = "Mediatek Watchdog",
128ab3f09feSJohn Crispin 	.options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
129ab3f09feSJohn Crispin };
130ab3f09feSJohn Crispin 
131b893e344SBhumika Goyal static const struct watchdog_ops mt7621_wdt_ops = {
132ab3f09feSJohn Crispin 	.owner = THIS_MODULE,
133ab3f09feSJohn Crispin 	.start = mt7621_wdt_start,
134ab3f09feSJohn Crispin 	.stop = mt7621_wdt_stop,
135ab3f09feSJohn Crispin 	.ping = mt7621_wdt_ping,
136ab3f09feSJohn Crispin 	.set_timeout = mt7621_wdt_set_timeout,
137ab3f09feSJohn Crispin };
138ab3f09feSJohn Crispin 
mt7621_wdt_probe(struct platform_device * pdev)139ab3f09feSJohn Crispin static int mt7621_wdt_probe(struct platform_device *pdev)
140ab3f09feSJohn Crispin {
141*ff8ec4acSSergio Paracuellos 	struct device_node *np = pdev->dev.of_node;
1426fef817eSGuenter Roeck 	struct device *dev = &pdev->dev;
143783c7cb4SSergio Paracuellos 	struct watchdog_device *mt7621_wdt;
144783c7cb4SSergio Paracuellos 	struct mt7621_wdt_data *drvdata;
145783c7cb4SSergio Paracuellos 	int err;
146ab3f09feSJohn Crispin 
147783c7cb4SSergio Paracuellos 	drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL);
148783c7cb4SSergio Paracuellos 	if (!drvdata)
149783c7cb4SSergio Paracuellos 		return -ENOMEM;
150ab3f09feSJohn Crispin 
151*ff8ec4acSSergio Paracuellos 	drvdata->sysc = syscon_regmap_lookup_by_phandle(np, "mediatek,sysctl");
152*ff8ec4acSSergio Paracuellos 	if (IS_ERR(drvdata->sysc)) {
153*ff8ec4acSSergio Paracuellos 		drvdata->sysc = syscon_regmap_lookup_by_compatible("mediatek,mt7621-sysc");
154*ff8ec4acSSergio Paracuellos 		if (IS_ERR(drvdata->sysc))
155*ff8ec4acSSergio Paracuellos 			return PTR_ERR(drvdata->sysc);
156*ff8ec4acSSergio Paracuellos 	}
157*ff8ec4acSSergio Paracuellos 
158783c7cb4SSergio Paracuellos 	drvdata->base = devm_platform_ioremap_resource(pdev, 0);
159783c7cb4SSergio Paracuellos 	if (IS_ERR(drvdata->base))
160783c7cb4SSergio Paracuellos 		return PTR_ERR(drvdata->base);
161ab3f09feSJohn Crispin 
162783c7cb4SSergio Paracuellos 	drvdata->rst = devm_reset_control_get_exclusive(dev, NULL);
163783c7cb4SSergio Paracuellos 	if (!IS_ERR(drvdata->rst))
164783c7cb4SSergio Paracuellos 		reset_control_deassert(drvdata->rst);
165783c7cb4SSergio Paracuellos 
166783c7cb4SSergio Paracuellos 	mt7621_wdt = &drvdata->wdt;
167783c7cb4SSergio Paracuellos 	mt7621_wdt->info = &mt7621_wdt_info;
168783c7cb4SSergio Paracuellos 	mt7621_wdt->ops = &mt7621_wdt_ops;
169783c7cb4SSergio Paracuellos 	mt7621_wdt->min_timeout = 1;
170783c7cb4SSergio Paracuellos 	mt7621_wdt->max_timeout = 0xfffful / 1000;
171783c7cb4SSergio Paracuellos 	mt7621_wdt->parent = dev;
172783c7cb4SSergio Paracuellos 
173*ff8ec4acSSergio Paracuellos 	mt7621_wdt->bootstatus = mt7621_wdt_bootcause(drvdata);
174783c7cb4SSergio Paracuellos 
175783c7cb4SSergio Paracuellos 	watchdog_init_timeout(mt7621_wdt, mt7621_wdt->max_timeout, dev);
176783c7cb4SSergio Paracuellos 	watchdog_set_nowayout(mt7621_wdt, nowayout);
177783c7cb4SSergio Paracuellos 	watchdog_set_drvdata(mt7621_wdt, drvdata);
178783c7cb4SSergio Paracuellos 
179783c7cb4SSergio Paracuellos 	if (mt7621_wdt_is_running(mt7621_wdt)) {
180392d39a8SAndré Draszik 		/*
181392d39a8SAndré Draszik 		 * Make sure to apply timeout from watchdog core, taking
182392d39a8SAndré Draszik 		 * the prescaler of this driver here into account (the
183392d39a8SAndré Draszik 		 * boot loader might be using a different prescaler).
184392d39a8SAndré Draszik 		 *
185392d39a8SAndré Draszik 		 * To avoid spurious resets because of different scaling,
186392d39a8SAndré Draszik 		 * we first disable the watchdog, set the new prescaler
187392d39a8SAndré Draszik 		 * and timeout, and then re-enable the watchdog.
188392d39a8SAndré Draszik 		 */
189783c7cb4SSergio Paracuellos 		mt7621_wdt_stop(mt7621_wdt);
190783c7cb4SSergio Paracuellos 		mt7621_wdt_start(mt7621_wdt);
191783c7cb4SSergio Paracuellos 		set_bit(WDOG_HW_RUNNING, &mt7621_wdt->status);
192392d39a8SAndré Draszik 	}
193ab3f09feSJohn Crispin 
194783c7cb4SSergio Paracuellos 	err = devm_watchdog_register_device(dev, &drvdata->wdt);
195783c7cb4SSergio Paracuellos 	if (err)
196783c7cb4SSergio Paracuellos 		return err;
197783c7cb4SSergio Paracuellos 
198783c7cb4SSergio Paracuellos 	platform_set_drvdata(pdev, drvdata);
199783c7cb4SSergio Paracuellos 
200783c7cb4SSergio Paracuellos 	return 0;
201ab3f09feSJohn Crispin }
202ab3f09feSJohn Crispin 
mt7621_wdt_shutdown(struct platform_device * pdev)203ab3f09feSJohn Crispin static void mt7621_wdt_shutdown(struct platform_device *pdev)
204ab3f09feSJohn Crispin {
205783c7cb4SSergio Paracuellos 	struct mt7621_wdt_data *drvdata = platform_get_drvdata(pdev);
206783c7cb4SSergio Paracuellos 
207783c7cb4SSergio Paracuellos 	mt7621_wdt_stop(&drvdata->wdt);
208ab3f09feSJohn Crispin }
209ab3f09feSJohn Crispin 
210ab3f09feSJohn Crispin static const struct of_device_id mt7621_wdt_match[] = {
211ab3f09feSJohn Crispin 	{ .compatible = "mediatek,mt7621-wdt" },
212ab3f09feSJohn Crispin 	{},
213ab3f09feSJohn Crispin };
214ab3f09feSJohn Crispin MODULE_DEVICE_TABLE(of, mt7621_wdt_match);
215ab3f09feSJohn Crispin 
216ab3f09feSJohn Crispin static struct platform_driver mt7621_wdt_driver = {
217ab3f09feSJohn Crispin 	.probe		= mt7621_wdt_probe,
218ab3f09feSJohn Crispin 	.shutdown	= mt7621_wdt_shutdown,
219ab3f09feSJohn Crispin 	.driver		= {
220ab3f09feSJohn Crispin 		.name		= KBUILD_MODNAME,
221ab3f09feSJohn Crispin 		.of_match_table	= mt7621_wdt_match,
222ab3f09feSJohn Crispin 	},
223ab3f09feSJohn Crispin };
224ab3f09feSJohn Crispin 
225ab3f09feSJohn Crispin module_platform_driver(mt7621_wdt_driver);
226ab3f09feSJohn Crispin 
227ab3f09feSJohn Crispin MODULE_DESCRIPTION("MediaTek MT762x hardware watchdog driver");
228f3519a66SJohn Crispin MODULE_AUTHOR("John Crispin <john@phrozen.org");
229ab3f09feSJohn Crispin MODULE_LICENSE("GPL v2");
230