xref: /OK3568_Linux_fs/kernel/drivers/watchdog/tangox_wdt.c (revision 4882a59341e53eb6f0b4789bf948001014eff981)
1*4882a593Smuzhiyun // SPDX-License-Identifier: GPL-2.0+
2*4882a593Smuzhiyun /*
3*4882a593Smuzhiyun  *  Copyright (C) 2015 Mans Rullgard <mans@mansr.com>
4*4882a593Smuzhiyun  *  SMP86xx/SMP87xx Watchdog driver
5*4882a593Smuzhiyun  */
6*4882a593Smuzhiyun 
7*4882a593Smuzhiyun #include <linux/bitops.h>
8*4882a593Smuzhiyun #include <linux/clk.h>
9*4882a593Smuzhiyun #include <linux/delay.h>
10*4882a593Smuzhiyun #include <linux/io.h>
11*4882a593Smuzhiyun #include <linux/kernel.h>
12*4882a593Smuzhiyun #include <linux/module.h>
13*4882a593Smuzhiyun #include <linux/moduleparam.h>
14*4882a593Smuzhiyun #include <linux/mod_devicetable.h>
15*4882a593Smuzhiyun #include <linux/platform_device.h>
16*4882a593Smuzhiyun #include <linux/watchdog.h>
17*4882a593Smuzhiyun 
18*4882a593Smuzhiyun #define DEFAULT_TIMEOUT 30
19*4882a593Smuzhiyun 
20*4882a593Smuzhiyun static bool nowayout = WATCHDOG_NOWAYOUT;
21*4882a593Smuzhiyun module_param(nowayout, bool, 0);
22*4882a593Smuzhiyun MODULE_PARM_DESC(nowayout,
23*4882a593Smuzhiyun 		 "Watchdog cannot be stopped once started (default="
24*4882a593Smuzhiyun 		 __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
25*4882a593Smuzhiyun 
26*4882a593Smuzhiyun static unsigned int timeout;
27*4882a593Smuzhiyun module_param(timeout, int, 0);
28*4882a593Smuzhiyun MODULE_PARM_DESC(timeout, "Watchdog timeout");
29*4882a593Smuzhiyun 
30*4882a593Smuzhiyun /*
31*4882a593Smuzhiyun  * Counter counts down from programmed value.  Reset asserts when
32*4882a593Smuzhiyun  * the counter reaches 1.
33*4882a593Smuzhiyun  */
34*4882a593Smuzhiyun #define WD_COUNTER		0
35*4882a593Smuzhiyun 
36*4882a593Smuzhiyun #define WD_CONFIG		4
37*4882a593Smuzhiyun #define WD_CONFIG_XTAL_IN	BIT(0)
38*4882a593Smuzhiyun #define WD_CONFIG_DISABLE	BIT(31)
39*4882a593Smuzhiyun 
40*4882a593Smuzhiyun struct tangox_wdt_device {
41*4882a593Smuzhiyun 	struct watchdog_device wdt;
42*4882a593Smuzhiyun 	void __iomem *base;
43*4882a593Smuzhiyun 	unsigned long clk_rate;
44*4882a593Smuzhiyun 	struct clk *clk;
45*4882a593Smuzhiyun };
46*4882a593Smuzhiyun 
tangox_wdt_set_timeout(struct watchdog_device * wdt,unsigned int new_timeout)47*4882a593Smuzhiyun static int tangox_wdt_set_timeout(struct watchdog_device *wdt,
48*4882a593Smuzhiyun 				  unsigned int new_timeout)
49*4882a593Smuzhiyun {
50*4882a593Smuzhiyun 	wdt->timeout = new_timeout;
51*4882a593Smuzhiyun 
52*4882a593Smuzhiyun 	return 0;
53*4882a593Smuzhiyun }
54*4882a593Smuzhiyun 
tangox_wdt_start(struct watchdog_device * wdt)55*4882a593Smuzhiyun static int tangox_wdt_start(struct watchdog_device *wdt)
56*4882a593Smuzhiyun {
57*4882a593Smuzhiyun 	struct tangox_wdt_device *dev = watchdog_get_drvdata(wdt);
58*4882a593Smuzhiyun 	u32 ticks;
59*4882a593Smuzhiyun 
60*4882a593Smuzhiyun 	ticks = 1 + wdt->timeout * dev->clk_rate;
61*4882a593Smuzhiyun 	writel(ticks, dev->base + WD_COUNTER);
62*4882a593Smuzhiyun 
63*4882a593Smuzhiyun 	return 0;
64*4882a593Smuzhiyun }
65*4882a593Smuzhiyun 
tangox_wdt_stop(struct watchdog_device * wdt)66*4882a593Smuzhiyun static int tangox_wdt_stop(struct watchdog_device *wdt)
67*4882a593Smuzhiyun {
68*4882a593Smuzhiyun 	struct tangox_wdt_device *dev = watchdog_get_drvdata(wdt);
69*4882a593Smuzhiyun 
70*4882a593Smuzhiyun 	writel(0, dev->base + WD_COUNTER);
71*4882a593Smuzhiyun 
72*4882a593Smuzhiyun 	return 0;
73*4882a593Smuzhiyun }
74*4882a593Smuzhiyun 
tangox_wdt_get_timeleft(struct watchdog_device * wdt)75*4882a593Smuzhiyun static unsigned int tangox_wdt_get_timeleft(struct watchdog_device *wdt)
76*4882a593Smuzhiyun {
77*4882a593Smuzhiyun 	struct tangox_wdt_device *dev = watchdog_get_drvdata(wdt);
78*4882a593Smuzhiyun 	u32 count;
79*4882a593Smuzhiyun 
80*4882a593Smuzhiyun 	count = readl(dev->base + WD_COUNTER);
81*4882a593Smuzhiyun 
82*4882a593Smuzhiyun 	if (!count)
83*4882a593Smuzhiyun 		return 0;
84*4882a593Smuzhiyun 
85*4882a593Smuzhiyun 	return (count - 1) / dev->clk_rate;
86*4882a593Smuzhiyun }
87*4882a593Smuzhiyun 
88*4882a593Smuzhiyun static const struct watchdog_info tangox_wdt_info = {
89*4882a593Smuzhiyun 	.options  = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
90*4882a593Smuzhiyun 	.identity = "tangox watchdog",
91*4882a593Smuzhiyun };
92*4882a593Smuzhiyun 
tangox_wdt_restart(struct watchdog_device * wdt,unsigned long action,void * data)93*4882a593Smuzhiyun static int tangox_wdt_restart(struct watchdog_device *wdt,
94*4882a593Smuzhiyun 			      unsigned long action, void *data)
95*4882a593Smuzhiyun {
96*4882a593Smuzhiyun 	struct tangox_wdt_device *dev = watchdog_get_drvdata(wdt);
97*4882a593Smuzhiyun 
98*4882a593Smuzhiyun 	writel(1, dev->base + WD_COUNTER);
99*4882a593Smuzhiyun 
100*4882a593Smuzhiyun 	return 0;
101*4882a593Smuzhiyun }
102*4882a593Smuzhiyun 
103*4882a593Smuzhiyun static const struct watchdog_ops tangox_wdt_ops = {
104*4882a593Smuzhiyun 	.start		= tangox_wdt_start,
105*4882a593Smuzhiyun 	.stop		= tangox_wdt_stop,
106*4882a593Smuzhiyun 	.set_timeout	= tangox_wdt_set_timeout,
107*4882a593Smuzhiyun 	.get_timeleft	= tangox_wdt_get_timeleft,
108*4882a593Smuzhiyun 	.restart	= tangox_wdt_restart,
109*4882a593Smuzhiyun };
110*4882a593Smuzhiyun 
tangox_clk_disable_unprepare(void * data)111*4882a593Smuzhiyun static void tangox_clk_disable_unprepare(void *data)
112*4882a593Smuzhiyun {
113*4882a593Smuzhiyun 	clk_disable_unprepare(data);
114*4882a593Smuzhiyun }
115*4882a593Smuzhiyun 
tangox_wdt_probe(struct platform_device * pdev)116*4882a593Smuzhiyun static int tangox_wdt_probe(struct platform_device *pdev)
117*4882a593Smuzhiyun {
118*4882a593Smuzhiyun 	struct tangox_wdt_device *dev;
119*4882a593Smuzhiyun 	u32 config;
120*4882a593Smuzhiyun 	int err;
121*4882a593Smuzhiyun 
122*4882a593Smuzhiyun 	dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL);
123*4882a593Smuzhiyun 	if (!dev)
124*4882a593Smuzhiyun 		return -ENOMEM;
125*4882a593Smuzhiyun 
126*4882a593Smuzhiyun 	dev->base = devm_platform_ioremap_resource(pdev, 0);
127*4882a593Smuzhiyun 	if (IS_ERR(dev->base))
128*4882a593Smuzhiyun 		return PTR_ERR(dev->base);
129*4882a593Smuzhiyun 
130*4882a593Smuzhiyun 	dev->clk = devm_clk_get(&pdev->dev, NULL);
131*4882a593Smuzhiyun 	if (IS_ERR(dev->clk))
132*4882a593Smuzhiyun 		return PTR_ERR(dev->clk);
133*4882a593Smuzhiyun 
134*4882a593Smuzhiyun 	err = clk_prepare_enable(dev->clk);
135*4882a593Smuzhiyun 	if (err)
136*4882a593Smuzhiyun 		return err;
137*4882a593Smuzhiyun 	err = devm_add_action_or_reset(&pdev->dev,
138*4882a593Smuzhiyun 				       tangox_clk_disable_unprepare, dev->clk);
139*4882a593Smuzhiyun 	if (err)
140*4882a593Smuzhiyun 		return err;
141*4882a593Smuzhiyun 
142*4882a593Smuzhiyun 	dev->clk_rate = clk_get_rate(dev->clk);
143*4882a593Smuzhiyun 	if (!dev->clk_rate)
144*4882a593Smuzhiyun 		return -EINVAL;
145*4882a593Smuzhiyun 
146*4882a593Smuzhiyun 	dev->wdt.parent = &pdev->dev;
147*4882a593Smuzhiyun 	dev->wdt.info = &tangox_wdt_info;
148*4882a593Smuzhiyun 	dev->wdt.ops = &tangox_wdt_ops;
149*4882a593Smuzhiyun 	dev->wdt.timeout = DEFAULT_TIMEOUT;
150*4882a593Smuzhiyun 	dev->wdt.min_timeout = 1;
151*4882a593Smuzhiyun 	dev->wdt.max_hw_heartbeat_ms = (U32_MAX - 1) / dev->clk_rate;
152*4882a593Smuzhiyun 
153*4882a593Smuzhiyun 	watchdog_init_timeout(&dev->wdt, timeout, &pdev->dev);
154*4882a593Smuzhiyun 	watchdog_set_nowayout(&dev->wdt, nowayout);
155*4882a593Smuzhiyun 	watchdog_set_drvdata(&dev->wdt, dev);
156*4882a593Smuzhiyun 
157*4882a593Smuzhiyun 	/*
158*4882a593Smuzhiyun 	 * Deactivate counter if disable bit is set to avoid
159*4882a593Smuzhiyun 	 * accidental reset.
160*4882a593Smuzhiyun 	 */
161*4882a593Smuzhiyun 	config = readl(dev->base + WD_CONFIG);
162*4882a593Smuzhiyun 	if (config & WD_CONFIG_DISABLE)
163*4882a593Smuzhiyun 		writel(0, dev->base + WD_COUNTER);
164*4882a593Smuzhiyun 
165*4882a593Smuzhiyun 	writel(WD_CONFIG_XTAL_IN, dev->base + WD_CONFIG);
166*4882a593Smuzhiyun 
167*4882a593Smuzhiyun 	/*
168*4882a593Smuzhiyun 	 * Mark as active and restart with configured timeout if
169*4882a593Smuzhiyun 	 * already running.
170*4882a593Smuzhiyun 	 */
171*4882a593Smuzhiyun 	if (readl(dev->base + WD_COUNTER)) {
172*4882a593Smuzhiyun 		set_bit(WDOG_HW_RUNNING, &dev->wdt.status);
173*4882a593Smuzhiyun 		tangox_wdt_start(&dev->wdt);
174*4882a593Smuzhiyun 	}
175*4882a593Smuzhiyun 
176*4882a593Smuzhiyun 	watchdog_set_restart_priority(&dev->wdt, 128);
177*4882a593Smuzhiyun 
178*4882a593Smuzhiyun 	watchdog_stop_on_unregister(&dev->wdt);
179*4882a593Smuzhiyun 	err = devm_watchdog_register_device(&pdev->dev, &dev->wdt);
180*4882a593Smuzhiyun 	if (err)
181*4882a593Smuzhiyun 		return err;
182*4882a593Smuzhiyun 
183*4882a593Smuzhiyun 	platform_set_drvdata(pdev, dev);
184*4882a593Smuzhiyun 
185*4882a593Smuzhiyun 	dev_info(&pdev->dev, "SMP86xx/SMP87xx watchdog registered\n");
186*4882a593Smuzhiyun 
187*4882a593Smuzhiyun 	return 0;
188*4882a593Smuzhiyun }
189*4882a593Smuzhiyun 
190*4882a593Smuzhiyun static const struct of_device_id tangox_wdt_dt_ids[] = {
191*4882a593Smuzhiyun 	{ .compatible = "sigma,smp8642-wdt" },
192*4882a593Smuzhiyun 	{ .compatible = "sigma,smp8759-wdt" },
193*4882a593Smuzhiyun 	{ }
194*4882a593Smuzhiyun };
195*4882a593Smuzhiyun MODULE_DEVICE_TABLE(of, tangox_wdt_dt_ids);
196*4882a593Smuzhiyun 
197*4882a593Smuzhiyun static struct platform_driver tangox_wdt_driver = {
198*4882a593Smuzhiyun 	.probe	= tangox_wdt_probe,
199*4882a593Smuzhiyun 	.driver	= {
200*4882a593Smuzhiyun 		.name		= "tangox-wdt",
201*4882a593Smuzhiyun 		.of_match_table	= tangox_wdt_dt_ids,
202*4882a593Smuzhiyun 	},
203*4882a593Smuzhiyun };
204*4882a593Smuzhiyun 
205*4882a593Smuzhiyun module_platform_driver(tangox_wdt_driver);
206*4882a593Smuzhiyun 
207*4882a593Smuzhiyun MODULE_AUTHOR("Mans Rullgard <mans@mansr.com>");
208*4882a593Smuzhiyun MODULE_DESCRIPTION("SMP86xx/SMP87xx Watchdog driver");
209*4882a593Smuzhiyun MODULE_LICENSE("GPL");
210