xref: /linux/drivers/watchdog/pic32-wdt.c (revision 8f91fc56bc439a5baefae598040d263f48526596)
1*8f91fc56SJoshua Henderson /*
2*8f91fc56SJoshua Henderson  * PIC32 watchdog driver
3*8f91fc56SJoshua Henderson  *
4*8f91fc56SJoshua Henderson  * Joshua Henderson <joshua.henderson@microchip.com>
5*8f91fc56SJoshua Henderson  * Copyright (c) 2016, Microchip Technology Inc.
6*8f91fc56SJoshua Henderson  *
7*8f91fc56SJoshua Henderson  * This program is free software; you can redistribute it and/or
8*8f91fc56SJoshua Henderson  * modify it under the terms of the GNU General Public License
9*8f91fc56SJoshua Henderson  * as published by the Free Software Foundation; either version
10*8f91fc56SJoshua Henderson  * 2 of the License, or (at your option) any later version.
11*8f91fc56SJoshua Henderson  */
12*8f91fc56SJoshua Henderson #include <linux/clk.h>
13*8f91fc56SJoshua Henderson #include <linux/device.h>
14*8f91fc56SJoshua Henderson #include <linux/err.h>
15*8f91fc56SJoshua Henderson #include <linux/io.h>
16*8f91fc56SJoshua Henderson #include <linux/kernel.h>
17*8f91fc56SJoshua Henderson #include <linux/module.h>
18*8f91fc56SJoshua Henderson #include <linux/of.h>
19*8f91fc56SJoshua Henderson #include <linux/of_device.h>
20*8f91fc56SJoshua Henderson #include <linux/platform_device.h>
21*8f91fc56SJoshua Henderson #include <linux/pm.h>
22*8f91fc56SJoshua Henderson #include <linux/watchdog.h>
23*8f91fc56SJoshua Henderson 
24*8f91fc56SJoshua Henderson #include <asm/mach-pic32/pic32.h>
25*8f91fc56SJoshua Henderson 
26*8f91fc56SJoshua Henderson /* Watchdog Timer Registers */
27*8f91fc56SJoshua Henderson #define WDTCON_REG		0x00
28*8f91fc56SJoshua Henderson 
29*8f91fc56SJoshua Henderson /* Watchdog Timer Control Register fields */
30*8f91fc56SJoshua Henderson #define WDTCON_WIN_EN		BIT(0)
31*8f91fc56SJoshua Henderson #define WDTCON_RMCS_MASK	0x0003
32*8f91fc56SJoshua Henderson #define WDTCON_RMCS_SHIFT	0x0006
33*8f91fc56SJoshua Henderson #define WDTCON_RMPS_MASK	0x001F
34*8f91fc56SJoshua Henderson #define WDTCON_RMPS_SHIFT	0x0008
35*8f91fc56SJoshua Henderson #define WDTCON_ON		BIT(15)
36*8f91fc56SJoshua Henderson #define WDTCON_CLR_KEY		0x5743
37*8f91fc56SJoshua Henderson 
38*8f91fc56SJoshua Henderson /* Reset Control Register fields for watchdog */
39*8f91fc56SJoshua Henderson #define RESETCON_TIMEOUT_IDLE	BIT(2)
40*8f91fc56SJoshua Henderson #define RESETCON_TIMEOUT_SLEEP	BIT(3)
41*8f91fc56SJoshua Henderson #define RESETCON_WDT_TIMEOUT	BIT(4)
42*8f91fc56SJoshua Henderson 
43*8f91fc56SJoshua Henderson struct pic32_wdt {
44*8f91fc56SJoshua Henderson 	void __iomem	*regs;
45*8f91fc56SJoshua Henderson 	void __iomem	*rst_base;
46*8f91fc56SJoshua Henderson 	struct clk	*clk;
47*8f91fc56SJoshua Henderson };
48*8f91fc56SJoshua Henderson 
49*8f91fc56SJoshua Henderson static inline bool pic32_wdt_is_win_enabled(struct pic32_wdt *wdt)
50*8f91fc56SJoshua Henderson {
51*8f91fc56SJoshua Henderson 	return !!(readl(wdt->regs + WDTCON_REG) & WDTCON_WIN_EN);
52*8f91fc56SJoshua Henderson }
53*8f91fc56SJoshua Henderson 
54*8f91fc56SJoshua Henderson static inline u32 pic32_wdt_get_post_scaler(struct pic32_wdt *wdt)
55*8f91fc56SJoshua Henderson {
56*8f91fc56SJoshua Henderson 	u32 v = readl(wdt->regs + WDTCON_REG);
57*8f91fc56SJoshua Henderson 
58*8f91fc56SJoshua Henderson 	return (v >> WDTCON_RMPS_SHIFT) & WDTCON_RMPS_MASK;
59*8f91fc56SJoshua Henderson }
60*8f91fc56SJoshua Henderson 
61*8f91fc56SJoshua Henderson static inline u32 pic32_wdt_get_clk_id(struct pic32_wdt *wdt)
62*8f91fc56SJoshua Henderson {
63*8f91fc56SJoshua Henderson 	u32 v = readl(wdt->regs + WDTCON_REG);
64*8f91fc56SJoshua Henderson 
65*8f91fc56SJoshua Henderson 	return (v >> WDTCON_RMCS_SHIFT) & WDTCON_RMCS_MASK;
66*8f91fc56SJoshua Henderson }
67*8f91fc56SJoshua Henderson 
68*8f91fc56SJoshua Henderson static int pic32_wdt_bootstatus(struct pic32_wdt *wdt)
69*8f91fc56SJoshua Henderson {
70*8f91fc56SJoshua Henderson 	u32 v = readl(wdt->rst_base);
71*8f91fc56SJoshua Henderson 
72*8f91fc56SJoshua Henderson 	writel(RESETCON_WDT_TIMEOUT, PIC32_CLR(wdt->rst_base));
73*8f91fc56SJoshua Henderson 
74*8f91fc56SJoshua Henderson 	return v & RESETCON_WDT_TIMEOUT;
75*8f91fc56SJoshua Henderson }
76*8f91fc56SJoshua Henderson 
77*8f91fc56SJoshua Henderson static u32 pic32_wdt_get_timeout_secs(struct pic32_wdt *wdt, struct device *dev)
78*8f91fc56SJoshua Henderson {
79*8f91fc56SJoshua Henderson 	unsigned long rate;
80*8f91fc56SJoshua Henderson 	u32 period, ps, terminal;
81*8f91fc56SJoshua Henderson 
82*8f91fc56SJoshua Henderson 	rate = clk_get_rate(wdt->clk);
83*8f91fc56SJoshua Henderson 
84*8f91fc56SJoshua Henderson 	dev_dbg(dev, "wdt: clk_id %d, clk_rate %lu (prescale)\n",
85*8f91fc56SJoshua Henderson 		pic32_wdt_get_clk_id(wdt), rate);
86*8f91fc56SJoshua Henderson 
87*8f91fc56SJoshua Henderson 	/* default, prescaler of 32 (i.e. div-by-32) is implicit. */
88*8f91fc56SJoshua Henderson 	rate >>= 5;
89*8f91fc56SJoshua Henderson 	if (!rate)
90*8f91fc56SJoshua Henderson 		return 0;
91*8f91fc56SJoshua Henderson 
92*8f91fc56SJoshua Henderson 	/* calculate terminal count from postscaler. */
93*8f91fc56SJoshua Henderson 	ps = pic32_wdt_get_post_scaler(wdt);
94*8f91fc56SJoshua Henderson 	terminal = BIT(ps);
95*8f91fc56SJoshua Henderson 
96*8f91fc56SJoshua Henderson 	/* find time taken (in secs) to reach terminal count */
97*8f91fc56SJoshua Henderson 	period = terminal / rate;
98*8f91fc56SJoshua Henderson 	dev_dbg(dev,
99*8f91fc56SJoshua Henderson 		"wdt: clk_rate %lu (postscale) / terminal %d, timeout %dsec\n",
100*8f91fc56SJoshua Henderson 		rate, terminal, period);
101*8f91fc56SJoshua Henderson 
102*8f91fc56SJoshua Henderson 	return period;
103*8f91fc56SJoshua Henderson }
104*8f91fc56SJoshua Henderson 
105*8f91fc56SJoshua Henderson static void pic32_wdt_keepalive(struct pic32_wdt *wdt)
106*8f91fc56SJoshua Henderson {
107*8f91fc56SJoshua Henderson 	/* write key through single half-word */
108*8f91fc56SJoshua Henderson 	writew(WDTCON_CLR_KEY, wdt->regs + WDTCON_REG + 2);
109*8f91fc56SJoshua Henderson }
110*8f91fc56SJoshua Henderson 
111*8f91fc56SJoshua Henderson static int pic32_wdt_start(struct watchdog_device *wdd)
112*8f91fc56SJoshua Henderson {
113*8f91fc56SJoshua Henderson 	struct pic32_wdt *wdt = watchdog_get_drvdata(wdd);
114*8f91fc56SJoshua Henderson 
115*8f91fc56SJoshua Henderson 	writel(WDTCON_ON, PIC32_SET(wdt->regs + WDTCON_REG));
116*8f91fc56SJoshua Henderson 	pic32_wdt_keepalive(wdt);
117*8f91fc56SJoshua Henderson 
118*8f91fc56SJoshua Henderson 	return 0;
119*8f91fc56SJoshua Henderson }
120*8f91fc56SJoshua Henderson 
121*8f91fc56SJoshua Henderson static int pic32_wdt_stop(struct watchdog_device *wdd)
122*8f91fc56SJoshua Henderson {
123*8f91fc56SJoshua Henderson 	struct pic32_wdt *wdt = watchdog_get_drvdata(wdd);
124*8f91fc56SJoshua Henderson 
125*8f91fc56SJoshua Henderson 	writel(WDTCON_ON, PIC32_CLR(wdt->regs + WDTCON_REG));
126*8f91fc56SJoshua Henderson 
127*8f91fc56SJoshua Henderson 	/*
128*8f91fc56SJoshua Henderson 	 * Cannot touch registers in the CPU cycle following clearing the
129*8f91fc56SJoshua Henderson 	 * ON bit.
130*8f91fc56SJoshua Henderson 	 */
131*8f91fc56SJoshua Henderson 	nop();
132*8f91fc56SJoshua Henderson 
133*8f91fc56SJoshua Henderson 	return 0;
134*8f91fc56SJoshua Henderson }
135*8f91fc56SJoshua Henderson 
136*8f91fc56SJoshua Henderson static int pic32_wdt_ping(struct watchdog_device *wdd)
137*8f91fc56SJoshua Henderson {
138*8f91fc56SJoshua Henderson 	struct pic32_wdt *wdt = watchdog_get_drvdata(wdd);
139*8f91fc56SJoshua Henderson 
140*8f91fc56SJoshua Henderson 	pic32_wdt_keepalive(wdt);
141*8f91fc56SJoshua Henderson 
142*8f91fc56SJoshua Henderson 	return 0;
143*8f91fc56SJoshua Henderson }
144*8f91fc56SJoshua Henderson 
145*8f91fc56SJoshua Henderson static const struct watchdog_ops pic32_wdt_fops = {
146*8f91fc56SJoshua Henderson 	.owner		= THIS_MODULE,
147*8f91fc56SJoshua Henderson 	.start		= pic32_wdt_start,
148*8f91fc56SJoshua Henderson 	.stop		= pic32_wdt_stop,
149*8f91fc56SJoshua Henderson 	.ping		= pic32_wdt_ping,
150*8f91fc56SJoshua Henderson };
151*8f91fc56SJoshua Henderson 
152*8f91fc56SJoshua Henderson static const struct watchdog_info pic32_wdt_ident = {
153*8f91fc56SJoshua Henderson 	.options = WDIOF_KEEPALIVEPING |
154*8f91fc56SJoshua Henderson 			WDIOF_MAGICCLOSE | WDIOF_CARDRESET,
155*8f91fc56SJoshua Henderson 	.identity = "PIC32 Watchdog",
156*8f91fc56SJoshua Henderson };
157*8f91fc56SJoshua Henderson 
158*8f91fc56SJoshua Henderson static struct watchdog_device pic32_wdd = {
159*8f91fc56SJoshua Henderson 	.info		= &pic32_wdt_ident,
160*8f91fc56SJoshua Henderson 	.ops		= &pic32_wdt_fops,
161*8f91fc56SJoshua Henderson };
162*8f91fc56SJoshua Henderson 
163*8f91fc56SJoshua Henderson static const struct of_device_id pic32_wdt_dt_ids[] = {
164*8f91fc56SJoshua Henderson 	{ .compatible = "microchip,pic32mzda-wdt", },
165*8f91fc56SJoshua Henderson 	{ /* sentinel */ }
166*8f91fc56SJoshua Henderson };
167*8f91fc56SJoshua Henderson MODULE_DEVICE_TABLE(of, pic32_wdt_dt_ids);
168*8f91fc56SJoshua Henderson 
169*8f91fc56SJoshua Henderson static int pic32_wdt_drv_probe(struct platform_device *pdev)
170*8f91fc56SJoshua Henderson {
171*8f91fc56SJoshua Henderson 	int ret;
172*8f91fc56SJoshua Henderson 	struct watchdog_device *wdd = &pic32_wdd;
173*8f91fc56SJoshua Henderson 	struct pic32_wdt *wdt;
174*8f91fc56SJoshua Henderson 	struct resource *mem;
175*8f91fc56SJoshua Henderson 
176*8f91fc56SJoshua Henderson 	wdt = devm_kzalloc(&pdev->dev, sizeof(*wdt), GFP_KERNEL);
177*8f91fc56SJoshua Henderson 	if (IS_ERR(wdt))
178*8f91fc56SJoshua Henderson 		return PTR_ERR(wdt);
179*8f91fc56SJoshua Henderson 
180*8f91fc56SJoshua Henderson 	mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
181*8f91fc56SJoshua Henderson 	wdt->regs = devm_ioremap_resource(&pdev->dev, mem);
182*8f91fc56SJoshua Henderson 	if (IS_ERR(wdt->regs))
183*8f91fc56SJoshua Henderson 		return PTR_ERR(wdt->regs);
184*8f91fc56SJoshua Henderson 
185*8f91fc56SJoshua Henderson 	wdt->rst_base = devm_ioremap(&pdev->dev, PIC32_BASE_RESET, 0x10);
186*8f91fc56SJoshua Henderson 	if (IS_ERR(wdt->rst_base))
187*8f91fc56SJoshua Henderson 		return PTR_ERR(wdt->rst_base);
188*8f91fc56SJoshua Henderson 
189*8f91fc56SJoshua Henderson 	wdt->clk = devm_clk_get(&pdev->dev, NULL);
190*8f91fc56SJoshua Henderson 	if (IS_ERR(wdt->clk)) {
191*8f91fc56SJoshua Henderson 		dev_err(&pdev->dev, "clk not found\n");
192*8f91fc56SJoshua Henderson 		return PTR_ERR(wdt->clk);
193*8f91fc56SJoshua Henderson 	}
194*8f91fc56SJoshua Henderson 
195*8f91fc56SJoshua Henderson 	ret = clk_prepare_enable(wdt->clk);
196*8f91fc56SJoshua Henderson 	if (ret) {
197*8f91fc56SJoshua Henderson 		dev_err(&pdev->dev, "clk enable failed\n");
198*8f91fc56SJoshua Henderson 		return ret;
199*8f91fc56SJoshua Henderson 	}
200*8f91fc56SJoshua Henderson 
201*8f91fc56SJoshua Henderson 	if (pic32_wdt_is_win_enabled(wdt)) {
202*8f91fc56SJoshua Henderson 		dev_err(&pdev->dev, "windowed-clear mode is not supported.\n");
203*8f91fc56SJoshua Henderson 		ret = -ENODEV;
204*8f91fc56SJoshua Henderson 		goto out_disable_clk;
205*8f91fc56SJoshua Henderson 	}
206*8f91fc56SJoshua Henderson 
207*8f91fc56SJoshua Henderson 	wdd->timeout = pic32_wdt_get_timeout_secs(wdt, &pdev->dev);
208*8f91fc56SJoshua Henderson 	if (!wdd->timeout) {
209*8f91fc56SJoshua Henderson 		dev_err(&pdev->dev,
210*8f91fc56SJoshua Henderson 			"failed to read watchdog register timeout\n");
211*8f91fc56SJoshua Henderson 		ret = -EINVAL;
212*8f91fc56SJoshua Henderson 		goto out_disable_clk;
213*8f91fc56SJoshua Henderson 	}
214*8f91fc56SJoshua Henderson 
215*8f91fc56SJoshua Henderson 	dev_info(&pdev->dev, "timeout %d\n", wdd->timeout);
216*8f91fc56SJoshua Henderson 
217*8f91fc56SJoshua Henderson 	wdd->bootstatus = pic32_wdt_bootstatus(wdt) ? WDIOF_CARDRESET : 0;
218*8f91fc56SJoshua Henderson 
219*8f91fc56SJoshua Henderson 	watchdog_set_nowayout(wdd, WATCHDOG_NOWAYOUT);
220*8f91fc56SJoshua Henderson 	watchdog_set_drvdata(wdd, wdt);
221*8f91fc56SJoshua Henderson 
222*8f91fc56SJoshua Henderson 	ret = watchdog_register_device(wdd);
223*8f91fc56SJoshua Henderson 	if (ret) {
224*8f91fc56SJoshua Henderson 		dev_err(&pdev->dev, "watchdog register failed, err %d\n", ret);
225*8f91fc56SJoshua Henderson 		goto out_disable_clk;
226*8f91fc56SJoshua Henderson 	}
227*8f91fc56SJoshua Henderson 
228*8f91fc56SJoshua Henderson 	platform_set_drvdata(pdev, wdd);
229*8f91fc56SJoshua Henderson 
230*8f91fc56SJoshua Henderson 	return 0;
231*8f91fc56SJoshua Henderson 
232*8f91fc56SJoshua Henderson out_disable_clk:
233*8f91fc56SJoshua Henderson 	clk_disable_unprepare(wdt->clk);
234*8f91fc56SJoshua Henderson 
235*8f91fc56SJoshua Henderson 	return ret;
236*8f91fc56SJoshua Henderson }
237*8f91fc56SJoshua Henderson 
238*8f91fc56SJoshua Henderson static int pic32_wdt_drv_remove(struct platform_device *pdev)
239*8f91fc56SJoshua Henderson {
240*8f91fc56SJoshua Henderson 	struct watchdog_device *wdd = platform_get_drvdata(pdev);
241*8f91fc56SJoshua Henderson 	struct pic32_wdt *wdt = watchdog_get_drvdata(wdd);
242*8f91fc56SJoshua Henderson 
243*8f91fc56SJoshua Henderson 	watchdog_unregister_device(wdd);
244*8f91fc56SJoshua Henderson 	clk_disable_unprepare(wdt->clk);
245*8f91fc56SJoshua Henderson 
246*8f91fc56SJoshua Henderson 	return 0;
247*8f91fc56SJoshua Henderson }
248*8f91fc56SJoshua Henderson 
249*8f91fc56SJoshua Henderson static struct platform_driver pic32_wdt_driver = {
250*8f91fc56SJoshua Henderson 	.probe		= pic32_wdt_drv_probe,
251*8f91fc56SJoshua Henderson 	.remove		= pic32_wdt_drv_remove,
252*8f91fc56SJoshua Henderson 	.driver		= {
253*8f91fc56SJoshua Henderson 		.name		= "pic32-wdt",
254*8f91fc56SJoshua Henderson 		.owner		= THIS_MODULE,
255*8f91fc56SJoshua Henderson 		.of_match_table = of_match_ptr(pic32_wdt_dt_ids),
256*8f91fc56SJoshua Henderson 	}
257*8f91fc56SJoshua Henderson };
258*8f91fc56SJoshua Henderson 
259*8f91fc56SJoshua Henderson module_platform_driver(pic32_wdt_driver);
260*8f91fc56SJoshua Henderson 
261*8f91fc56SJoshua Henderson MODULE_AUTHOR("Joshua Henderson <joshua.henderson@microchip.com>");
262*8f91fc56SJoshua Henderson MODULE_DESCRIPTION("Microchip PIC32 Watchdog Timer");
263*8f91fc56SJoshua Henderson MODULE_LICENSE("GPL");
264