Skip to content

Commit 9393766

Browse files
tellapatiWim Van Sebroeck
authored andcommitted
watchdog: ImgTec PDC Watchdog Timer Driver
This commit adds support for ImgTec PowerDown Controller Watchdog Timer. Reviewed-by: Andrew Bresticker <abrestic@chromium.org> Signed-off-by: Naidu Tellapati <Naidu.Tellapati@imgtec.com> Signed-off-by: Jude Abraham <Jude.Abraham@imgtec.com> [ezequiel: Minor style fixes] Signed-off-by: Ezequiel Garcia <ezequiel.garcia@imgtec.com> Reviewed-by: Guenter Roeck <linux@roeck-us.net> Signed-off-by: Wim Van Sebroeck <wim@iguana.be>
1 parent cc4f9c2 commit 9393766

File tree

3 files changed

+301
-0
lines changed

3 files changed

+301
-0
lines changed

drivers/watchdog/Kconfig

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1235,6 +1235,17 @@ config BCM_KONA_WDT_DEBUG
12351235

12361236
If in doubt, say 'N'.
12371237

1238+
config IMGPDC_WDT
1239+
tristate "Imagination Technologies PDC Watchdog Timer"
1240+
depends on HAS_IOMEM
1241+
depends on METAG || MIPS || COMPILE_TEST
1242+
help
1243+
Driver for Imagination Technologies PowerDown Controller
1244+
Watchdog Timer.
1245+
1246+
To compile this driver as a loadable module, choose M here.
1247+
The module will be called imgpdc_wdt.
1248+
12381249
config LANTIQ_WDT
12391250
tristate "Lantiq SoC watchdog"
12401251
depends on LANTIQ

drivers/watchdog/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ obj-$(CONFIG_OCTEON_WDT) += octeon-wdt.o
142142
octeon-wdt-y := octeon-wdt-main.o octeon-wdt-nmi.o
143143
obj-$(CONFIG_LANTIQ_WDT) += lantiq_wdt.o
144144
obj-$(CONFIG_RALINK_WDT) += rt2880_wdt.o
145+
obj-$(CONFIG_IMGPDC_WDT) += imgpdc_wdt.o
145146

146147
# PARISC Architecture
147148

drivers/watchdog/imgpdc_wdt.c

Lines changed: 289 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,289 @@
1+
/*
2+
* Imagination Technologies PowerDown Controller Watchdog Timer.
3+
*
4+
* Copyright (c) 2014 Imagination Technologies Ltd.
5+
*
6+
* This program is free software; you can redistribute it and/or modify it
7+
* under the terms of the GNU General Public License version 2 as published by
8+
* the Free Software Foundation.
9+
*
10+
* Based on drivers/watchdog/sunxi_wdt.c Copyright (c) 2013 Carlo Caione
11+
* 2012 Henrik Nordstrom
12+
*/
13+
14+
#include <linux/clk.h>
15+
#include <linux/io.h>
16+
#include <linux/log2.h>
17+
#include <linux/module.h>
18+
#include <linux/platform_device.h>
19+
#include <linux/slab.h>
20+
#include <linux/watchdog.h>
21+
22+
/* registers */
23+
#define PDC_WDT_SOFT_RESET 0x00
24+
#define PDC_WDT_CONFIG 0x04
25+
#define PDC_WDT_CONFIG_ENABLE BIT(31)
26+
#define PDC_WDT_CONFIG_DELAY_MASK 0x1f
27+
28+
#define PDC_WDT_TICKLE1 0x08
29+
#define PDC_WDT_TICKLE1_MAGIC 0xabcd1234
30+
#define PDC_WDT_TICKLE2 0x0c
31+
#define PDC_WDT_TICKLE2_MAGIC 0x4321dcba
32+
33+
#define PDC_WDT_TICKLE_STATUS_MASK 0x7
34+
#define PDC_WDT_TICKLE_STATUS_SHIFT 0
35+
#define PDC_WDT_TICKLE_STATUS_HRESET 0x0 /* Hard reset */
36+
#define PDC_WDT_TICKLE_STATUS_TIMEOUT 0x1 /* Timeout */
37+
#define PDC_WDT_TICKLE_STATUS_TICKLE 0x2 /* Tickled incorrectly */
38+
#define PDC_WDT_TICKLE_STATUS_SRESET 0x3 /* Soft reset */
39+
#define PDC_WDT_TICKLE_STATUS_USER 0x4 /* User reset */
40+
41+
/* Timeout values are in seconds */
42+
#define PDC_WDT_MIN_TIMEOUT 1
43+
#define PDC_WDT_DEF_TIMEOUT 64
44+
45+
static int heartbeat;
46+
module_param(heartbeat, int, 0);
47+
MODULE_PARM_DESC(heartbeat, "Watchdog heartbeats in seconds. "
48+
"(default = " __MODULE_STRING(PDC_WDT_DEF_TIMEOUT) ")");
49+
50+
static bool nowayout = WATCHDOG_NOWAYOUT;
51+
module_param(nowayout, bool, 0);
52+
MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started "
53+
"(default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
54+
55+
struct pdc_wdt_dev {
56+
struct watchdog_device wdt_dev;
57+
struct clk *wdt_clk;
58+
struct clk *sys_clk;
59+
void __iomem *base;
60+
};
61+
62+
static int pdc_wdt_keepalive(struct watchdog_device *wdt_dev)
63+
{
64+
struct pdc_wdt_dev *wdt = watchdog_get_drvdata(wdt_dev);
65+
66+
writel(PDC_WDT_TICKLE1_MAGIC, wdt->base + PDC_WDT_TICKLE1);
67+
writel(PDC_WDT_TICKLE2_MAGIC, wdt->base + PDC_WDT_TICKLE2);
68+
69+
return 0;
70+
}
71+
72+
static int pdc_wdt_stop(struct watchdog_device *wdt_dev)
73+
{
74+
unsigned int val;
75+
struct pdc_wdt_dev *wdt = watchdog_get_drvdata(wdt_dev);
76+
77+
val = readl(wdt->base + PDC_WDT_CONFIG);
78+
val &= ~PDC_WDT_CONFIG_ENABLE;
79+
writel(val, wdt->base + PDC_WDT_CONFIG);
80+
81+
/* Must tickle to finish the stop */
82+
pdc_wdt_keepalive(wdt_dev);
83+
84+
return 0;
85+
}
86+
87+
static int pdc_wdt_set_timeout(struct watchdog_device *wdt_dev,
88+
unsigned int new_timeout)
89+
{
90+
unsigned int val;
91+
struct pdc_wdt_dev *wdt = watchdog_get_drvdata(wdt_dev);
92+
unsigned long clk_rate = clk_get_rate(wdt->wdt_clk);
93+
94+
wdt->wdt_dev.timeout = new_timeout;
95+
96+
val = readl(wdt->base + PDC_WDT_CONFIG) & ~PDC_WDT_CONFIG_DELAY_MASK;
97+
val |= order_base_2(new_timeout * clk_rate) - 1;
98+
writel(val, wdt->base + PDC_WDT_CONFIG);
99+
100+
return 0;
101+
}
102+
103+
/* Start the watchdog timer (delay should already be set) */
104+
static int pdc_wdt_start(struct watchdog_device *wdt_dev)
105+
{
106+
unsigned int val;
107+
struct pdc_wdt_dev *wdt = watchdog_get_drvdata(wdt_dev);
108+
109+
val = readl(wdt->base + PDC_WDT_CONFIG);
110+
val |= PDC_WDT_CONFIG_ENABLE;
111+
writel(val, wdt->base + PDC_WDT_CONFIG);
112+
113+
return 0;
114+
}
115+
116+
static struct watchdog_info pdc_wdt_info = {
117+
.identity = "IMG PDC Watchdog",
118+
.options = WDIOF_SETTIMEOUT |
119+
WDIOF_KEEPALIVEPING |
120+
WDIOF_MAGICCLOSE,
121+
};
122+
123+
static const struct watchdog_ops pdc_wdt_ops = {
124+
.owner = THIS_MODULE,
125+
.start = pdc_wdt_start,
126+
.stop = pdc_wdt_stop,
127+
.ping = pdc_wdt_keepalive,
128+
.set_timeout = pdc_wdt_set_timeout,
129+
};
130+
131+
static int pdc_wdt_probe(struct platform_device *pdev)
132+
{
133+
int ret, val;
134+
unsigned long clk_rate;
135+
struct resource *res;
136+
struct pdc_wdt_dev *pdc_wdt;
137+
138+
pdc_wdt = devm_kzalloc(&pdev->dev, sizeof(*pdc_wdt), GFP_KERNEL);
139+
if (!pdc_wdt)
140+
return -ENOMEM;
141+
142+
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
143+
pdc_wdt->base = devm_ioremap_resource(&pdev->dev, res);
144+
if (IS_ERR(pdc_wdt->base))
145+
return PTR_ERR(pdc_wdt->base);
146+
147+
pdc_wdt->sys_clk = devm_clk_get(&pdev->dev, "sys");
148+
if (IS_ERR(pdc_wdt->sys_clk)) {
149+
dev_err(&pdev->dev, "failed to get the sys clock\n");
150+
return PTR_ERR(pdc_wdt->sys_clk);
151+
}
152+
153+
pdc_wdt->wdt_clk = devm_clk_get(&pdev->dev, "wdt");
154+
if (IS_ERR(pdc_wdt->wdt_clk)) {
155+
dev_err(&pdev->dev, "failed to get the wdt clock\n");
156+
return PTR_ERR(pdc_wdt->wdt_clk);
157+
}
158+
159+
ret = clk_prepare_enable(pdc_wdt->sys_clk);
160+
if (ret) {
161+
dev_err(&pdev->dev, "could not prepare or enable sys clock\n");
162+
return ret;
163+
}
164+
165+
ret = clk_prepare_enable(pdc_wdt->wdt_clk);
166+
if (ret) {
167+
dev_err(&pdev->dev, "could not prepare or enable wdt clock\n");
168+
goto disable_sys_clk;
169+
}
170+
171+
/* We use the clock rate to calculate the max timeout */
172+
clk_rate = clk_get_rate(pdc_wdt->wdt_clk);
173+
if (clk_rate == 0) {
174+
dev_err(&pdev->dev, "failed to get clock rate\n");
175+
ret = -EINVAL;
176+
goto disable_wdt_clk;
177+
}
178+
179+
if (order_base_2(clk_rate) > PDC_WDT_CONFIG_DELAY_MASK + 1) {
180+
dev_err(&pdev->dev, "invalid clock rate\n");
181+
ret = -EINVAL;
182+
goto disable_wdt_clk;
183+
}
184+
185+
if (order_base_2(clk_rate) == 0)
186+
pdc_wdt->wdt_dev.min_timeout = PDC_WDT_MIN_TIMEOUT + 1;
187+
else
188+
pdc_wdt->wdt_dev.min_timeout = PDC_WDT_MIN_TIMEOUT;
189+
190+
pdc_wdt->wdt_dev.info = &pdc_wdt_info;
191+
pdc_wdt->wdt_dev.ops = &pdc_wdt_ops;
192+
pdc_wdt->wdt_dev.max_timeout = 1 << PDC_WDT_CONFIG_DELAY_MASK;
193+
pdc_wdt->wdt_dev.parent = &pdev->dev;
194+
195+
ret = watchdog_init_timeout(&pdc_wdt->wdt_dev, heartbeat, &pdev->dev);
196+
if (ret < 0) {
197+
pdc_wdt->wdt_dev.timeout = pdc_wdt->wdt_dev.max_timeout;
198+
dev_warn(&pdev->dev,
199+
"Initial timeout out of range! setting max timeout\n");
200+
}
201+
202+
pdc_wdt_stop(&pdc_wdt->wdt_dev);
203+
204+
/* Find what caused the last reset */
205+
val = readl(pdc_wdt->base + PDC_WDT_TICKLE1);
206+
val = (val & PDC_WDT_TICKLE_STATUS_MASK) >> PDC_WDT_TICKLE_STATUS_SHIFT;
207+
switch (val) {
208+
case PDC_WDT_TICKLE_STATUS_TICKLE:
209+
case PDC_WDT_TICKLE_STATUS_TIMEOUT:
210+
pdc_wdt->wdt_dev.bootstatus |= WDIOF_CARDRESET;
211+
dev_info(&pdev->dev,
212+
"watchdog module last reset due to timeout\n");
213+
break;
214+
case PDC_WDT_TICKLE_STATUS_HRESET:
215+
dev_info(&pdev->dev,
216+
"watchdog module last reset due to hard reset\n");
217+
break;
218+
case PDC_WDT_TICKLE_STATUS_SRESET:
219+
dev_info(&pdev->dev,
220+
"watchdog module last reset due to soft reset\n");
221+
break;
222+
case PDC_WDT_TICKLE_STATUS_USER:
223+
dev_info(&pdev->dev,
224+
"watchdog module last reset due to user reset\n");
225+
break;
226+
default:
227+
dev_info(&pdev->dev,
228+
"contains an illegal status code (%08x)\n", val);
229+
break;
230+
}
231+
232+
watchdog_set_nowayout(&pdc_wdt->wdt_dev, nowayout);
233+
234+
platform_set_drvdata(pdev, pdc_wdt);
235+
watchdog_set_drvdata(&pdc_wdt->wdt_dev, pdc_wdt);
236+
237+
ret = watchdog_register_device(&pdc_wdt->wdt_dev);
238+
if (ret)
239+
goto disable_wdt_clk;
240+
241+
return 0;
242+
243+
disable_wdt_clk:
244+
clk_disable_unprepare(pdc_wdt->wdt_clk);
245+
disable_sys_clk:
246+
clk_disable_unprepare(pdc_wdt->sys_clk);
247+
return ret;
248+
}
249+
250+
static void pdc_wdt_shutdown(struct platform_device *pdev)
251+
{
252+
struct pdc_wdt_dev *pdc_wdt = platform_get_drvdata(pdev);
253+
254+
pdc_wdt_stop(&pdc_wdt->wdt_dev);
255+
}
256+
257+
static int pdc_wdt_remove(struct platform_device *pdev)
258+
{
259+
struct pdc_wdt_dev *pdc_wdt = platform_get_drvdata(pdev);
260+
261+
pdc_wdt_stop(&pdc_wdt->wdt_dev);
262+
watchdog_unregister_device(&pdc_wdt->wdt_dev);
263+
clk_disable_unprepare(pdc_wdt->wdt_clk);
264+
clk_disable_unprepare(pdc_wdt->sys_clk);
265+
266+
return 0;
267+
}
268+
269+
static const struct of_device_id pdc_wdt_match[] = {
270+
{ .compatible = "img,pdc-wdt" },
271+
{}
272+
};
273+
MODULE_DEVICE_TABLE(of, pdc_wdt_match);
274+
275+
static struct platform_driver pdc_wdt_driver = {
276+
.driver = {
277+
.name = "imgpdc-wdt",
278+
.of_match_table = pdc_wdt_match,
279+
},
280+
.probe = pdc_wdt_probe,
281+
.remove = pdc_wdt_remove,
282+
.shutdown = pdc_wdt_shutdown,
283+
};
284+
module_platform_driver(pdc_wdt_driver);
285+
286+
MODULE_AUTHOR("Jude Abraham <Jude.Abraham@imgtec.com>");
287+
MODULE_AUTHOR("Naidu Tellapati <Naidu.Tellapati@imgtec.com>");
288+
MODULE_DESCRIPTION("Imagination Technologies PDC Watchdog Timer Driver");
289+
MODULE_LICENSE("GPL v2");

0 commit comments

Comments
 (0)