diff options
author | Linus Walleij <linus.walleij@linaro.org> | 2017-01-28 17:59:37 -0500 |
---|---|---|
committer | Guenter Roeck <linux@roeck-us.net> | 2017-02-24 17:00:23 -0500 |
commit | eca10ae6000d45ee3fec65f0abf7e07abfc66abb (patch) | |
tree | 1cbc57b906f1fa61647b9b3504025f8b9616471b /drivers/watchdog | |
parent | 428a66554a1d475896e47a23f1d2c99b58fa7105 (diff) |
watchdog: add driver for Cortina Gemini watchdog
This add support for the Cortina systems Gemini (SL3516)
SoC watchdog.
I have tried to use all the right new kernel interfaces
and tested with busybox' "watchdog" command both to kick
and get timeouts and reboots.
Signed-off-by: Linus Walleij <linus.walleij@linaro.org>
Signed-off-by: Guenter Roeck <linux@roeck-us.net>
Diffstat (limited to 'drivers/watchdog')
-rw-r--r-- | drivers/watchdog/Kconfig | 11 | ||||
-rw-r--r-- | drivers/watchdog/Makefile | 1 | ||||
-rw-r--r-- | drivers/watchdog/gemini_wdt.c | 229 |
3 files changed, 241 insertions, 0 deletions
diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index f7cb150d43d6..ba116f8bf3ee 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig | |||
@@ -301,6 +301,17 @@ config 977_WATCHDOG | |||
301 | 301 | ||
302 | Not sure? It's safe to say N. | 302 | Not sure? It's safe to say N. |
303 | 303 | ||
304 | config GEMINI_WATCHDOG | ||
305 | tristate "Gemini watchdog" | ||
306 | depends on ARCH_GEMINI | ||
307 | select WATCHDOG_CORE | ||
308 | help | ||
309 | Say Y here if to include support for the watchdog timer | ||
310 | embedded in the Cortina Systems Gemini family of devices. | ||
311 | |||
312 | To compile this driver as a module, choose M here: the | ||
313 | module will be called gemini_wdt. | ||
314 | |||
304 | config IXP4XX_WATCHDOG | 315 | config IXP4XX_WATCHDOG |
305 | tristate "IXP4xx Watchdog" | 316 | tristate "IXP4xx Watchdog" |
306 | depends on ARCH_IXP4XX | 317 | depends on ARCH_IXP4XX |
diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile index 3af35889a609..21b6bf9bff68 100644 --- a/drivers/watchdog/Makefile +++ b/drivers/watchdog/Makefile | |||
@@ -45,6 +45,7 @@ obj-$(CONFIG_OMAP_WATCHDOG) += omap_wdt.o | |||
45 | obj-$(CONFIG_TWL4030_WATCHDOG) += twl4030_wdt.o | 45 | obj-$(CONFIG_TWL4030_WATCHDOG) += twl4030_wdt.o |
46 | obj-$(CONFIG_21285_WATCHDOG) += wdt285.o | 46 | obj-$(CONFIG_21285_WATCHDOG) += wdt285.o |
47 | obj-$(CONFIG_977_WATCHDOG) += wdt977.o | 47 | obj-$(CONFIG_977_WATCHDOG) += wdt977.o |
48 | obj-$(CONFIG_GEMINI_WATCHDOG) += gemini_wdt.o | ||
48 | obj-$(CONFIG_IXP4XX_WATCHDOG) += ixp4xx_wdt.o | 49 | obj-$(CONFIG_IXP4XX_WATCHDOG) += ixp4xx_wdt.o |
49 | obj-$(CONFIG_KS8695_WATCHDOG) += ks8695_wdt.o | 50 | obj-$(CONFIG_KS8695_WATCHDOG) += ks8695_wdt.o |
50 | obj-$(CONFIG_S3C2410_WATCHDOG) += s3c2410_wdt.o | 51 | obj-$(CONFIG_S3C2410_WATCHDOG) += s3c2410_wdt.o |
diff --git a/drivers/watchdog/gemini_wdt.c b/drivers/watchdog/gemini_wdt.c new file mode 100644 index 000000000000..8155aa619e4c --- /dev/null +++ b/drivers/watchdog/gemini_wdt.c | |||
@@ -0,0 +1,229 @@ | |||
1 | /* | ||
2 | * Watchdog driver for Cortina Systems Gemini SoC | ||
3 | * | ||
4 | * Copyright (C) 2017 Linus Walleij <linus.walleij@linaro.org> | ||
5 | * | ||
6 | * Inspired by the out-of-tree drivers from OpenWRT: | ||
7 | * Copyright (C) 2009 Paulius Zaleckas <paulius.zaleckas@teltonika.lt> | ||
8 | * | ||
9 | * This program is free software; you can redistribute it and/or modify | ||
10 | * it under the terms of the GNU General Public License version 2 as | ||
11 | * published by the Free Software Foundation. | ||
12 | */ | ||
13 | |||
14 | #include <linux/bitops.h> | ||
15 | #include <linux/init.h> | ||
16 | #include <linux/interrupt.h> | ||
17 | #include <linux/io.h> | ||
18 | #include <linux/kernel.h> | ||
19 | #include <linux/module.h> | ||
20 | #include <linux/of_device.h> | ||
21 | #include <linux/platform_device.h> | ||
22 | #include <linux/slab.h> | ||
23 | #include <linux/watchdog.h> | ||
24 | |||
25 | #define GEMINI_WDCOUNTER 0x0 | ||
26 | #define GEMINI_WDLOAD 0x4 | ||
27 | #define GEMINI_WDRESTART 0x8 | ||
28 | #define GEMINI_WDCR 0xC | ||
29 | |||
30 | #define WDRESTART_MAGIC 0x5AB9 | ||
31 | |||
32 | #define WDCR_CLOCK_5MHZ BIT(4) | ||
33 | #define WDCR_SYS_RST BIT(1) | ||
34 | #define WDCR_ENABLE BIT(0) | ||
35 | |||
36 | #define WDT_CLOCK 5000000 /* 5 MHz */ | ||
37 | |||
38 | struct gemini_wdt { | ||
39 | struct watchdog_device wdd; | ||
40 | struct device *dev; | ||
41 | void __iomem *base; | ||
42 | }; | ||
43 | |||
44 | static inline | ||
45 | struct gemini_wdt *to_gemini_wdt(struct watchdog_device *wdd) | ||
46 | { | ||
47 | return container_of(wdd, struct gemini_wdt, wdd); | ||
48 | } | ||
49 | |||
50 | static int gemini_wdt_start(struct watchdog_device *wdd) | ||
51 | { | ||
52 | struct gemini_wdt *gwdt = to_gemini_wdt(wdd); | ||
53 | |||
54 | writel(wdd->timeout * WDT_CLOCK, gwdt->base + GEMINI_WDLOAD); | ||
55 | writel(WDRESTART_MAGIC, gwdt->base + GEMINI_WDRESTART); | ||
56 | /* set clock before enabling */ | ||
57 | writel(WDCR_CLOCK_5MHZ | WDCR_SYS_RST, | ||
58 | gwdt->base + GEMINI_WDCR); | ||
59 | writel(WDCR_CLOCK_5MHZ | WDCR_SYS_RST | WDCR_ENABLE, | ||
60 | gwdt->base + GEMINI_WDCR); | ||
61 | |||
62 | return 0; | ||
63 | } | ||
64 | |||
65 | static int gemini_wdt_stop(struct watchdog_device *wdd) | ||
66 | { | ||
67 | struct gemini_wdt *gwdt = to_gemini_wdt(wdd); | ||
68 | |||
69 | writel(0, gwdt->base + GEMINI_WDCR); | ||
70 | |||
71 | return 0; | ||
72 | } | ||
73 | |||
74 | static int gemini_wdt_ping(struct watchdog_device *wdd) | ||
75 | { | ||
76 | struct gemini_wdt *gwdt = to_gemini_wdt(wdd); | ||
77 | |||
78 | writel(WDRESTART_MAGIC, gwdt->base + GEMINI_WDRESTART); | ||
79 | |||
80 | return 0; | ||
81 | } | ||
82 | |||
83 | static int gemini_wdt_set_timeout(struct watchdog_device *wdd, | ||
84 | unsigned int timeout) | ||
85 | { | ||
86 | wdd->timeout = timeout; | ||
87 | if (watchdog_active(wdd)) | ||
88 | gemini_wdt_start(wdd); | ||
89 | |||
90 | return 0; | ||
91 | } | ||
92 | |||
93 | static irqreturn_t gemini_wdt_interrupt(int irq, void *data) | ||
94 | { | ||
95 | struct gemini_wdt *gwdt = data; | ||
96 | |||
97 | watchdog_notify_pretimeout(&gwdt->wdd); | ||
98 | |||
99 | return IRQ_HANDLED; | ||
100 | } | ||
101 | |||
102 | static const struct watchdog_ops gemini_wdt_ops = { | ||
103 | .start = gemini_wdt_start, | ||
104 | .stop = gemini_wdt_stop, | ||
105 | .ping = gemini_wdt_ping, | ||
106 | .set_timeout = gemini_wdt_set_timeout, | ||
107 | .owner = THIS_MODULE, | ||
108 | }; | ||
109 | |||
110 | static const struct watchdog_info gemini_wdt_info = { | ||
111 | .options = WDIOF_KEEPALIVEPING | ||
112 | | WDIOF_MAGICCLOSE | ||
113 | | WDIOF_SETTIMEOUT, | ||
114 | .identity = KBUILD_MODNAME, | ||
115 | }; | ||
116 | |||
117 | |||
118 | static int gemini_wdt_probe(struct platform_device *pdev) | ||
119 | { | ||
120 | struct device *dev = &pdev->dev; | ||
121 | struct resource *res; | ||
122 | struct gemini_wdt *gwdt; | ||
123 | unsigned int reg; | ||
124 | int irq; | ||
125 | int ret; | ||
126 | |||
127 | gwdt = devm_kzalloc(dev, sizeof(*gwdt), GFP_KERNEL); | ||
128 | if (!gwdt) | ||
129 | return -ENOMEM; | ||
130 | |||
131 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
132 | gwdt->base = devm_ioremap_resource(dev, res); | ||
133 | if (IS_ERR(gwdt->base)) | ||
134 | return PTR_ERR(gwdt->base); | ||
135 | |||
136 | irq = platform_get_irq(pdev, 0); | ||
137 | if (!irq) | ||
138 | return -EINVAL; | ||
139 | |||
140 | gwdt->dev = dev; | ||
141 | gwdt->wdd.info = &gemini_wdt_info; | ||
142 | gwdt->wdd.ops = &gemini_wdt_ops; | ||
143 | gwdt->wdd.min_timeout = 1; | ||
144 | gwdt->wdd.max_timeout = 0xFFFFFFFF / WDT_CLOCK; | ||
145 | gwdt->wdd.parent = dev; | ||
146 | |||
147 | /* | ||
148 | * If 'timeout-sec' unspecified in devicetree, assume a 13 second | ||
149 | * default. | ||
150 | */ | ||
151 | gwdt->wdd.timeout = 13U; | ||
152 | watchdog_init_timeout(&gwdt->wdd, 0, dev); | ||
153 | |||
154 | reg = readw(gwdt->base + GEMINI_WDCR); | ||
155 | if (reg & WDCR_ENABLE) { | ||
156 | /* Watchdog was enabled by the bootloader, disable it. */ | ||
157 | reg &= ~WDCR_ENABLE; | ||
158 | writel(reg, gwdt->base + GEMINI_WDCR); | ||
159 | } | ||
160 | |||
161 | ret = devm_request_irq(dev, irq, gemini_wdt_interrupt, 0, | ||
162 | "watchdog bark", gwdt); | ||
163 | if (ret) | ||
164 | return ret; | ||
165 | |||
166 | ret = devm_watchdog_register_device(dev, &gwdt->wdd); | ||
167 | if (ret) { | ||
168 | dev_err(&pdev->dev, "failed to register watchdog\n"); | ||
169 | return ret; | ||
170 | } | ||
171 | |||
172 | /* Set up platform driver data */ | ||
173 | platform_set_drvdata(pdev, gwdt); | ||
174 | dev_info(dev, "Gemini watchdog driver enabled\n"); | ||
175 | |||
176 | return 0; | ||
177 | } | ||
178 | |||
179 | static int __maybe_unused gemini_wdt_suspend(struct device *dev) | ||
180 | { | ||
181 | struct gemini_wdt *gwdt = dev_get_drvdata(dev); | ||
182 | unsigned int reg; | ||
183 | |||
184 | reg = readw(gwdt->base + GEMINI_WDCR); | ||
185 | reg &= ~WDCR_ENABLE; | ||
186 | writel(reg, gwdt->base + GEMINI_WDCR); | ||
187 | |||
188 | return 0; | ||
189 | } | ||
190 | |||
191 | static int __maybe_unused gemini_wdt_resume(struct device *dev) | ||
192 | { | ||
193 | struct gemini_wdt *gwdt = dev_get_drvdata(dev); | ||
194 | unsigned int reg; | ||
195 | |||
196 | if (watchdog_active(&gwdt->wdd)) { | ||
197 | reg = readw(gwdt->base + GEMINI_WDCR); | ||
198 | reg |= WDCR_ENABLE; | ||
199 | writel(reg, gwdt->base + GEMINI_WDCR); | ||
200 | } | ||
201 | |||
202 | return 0; | ||
203 | } | ||
204 | |||
205 | static const struct dev_pm_ops gemini_wdt_dev_pm_ops = { | ||
206 | SET_SYSTEM_SLEEP_PM_OPS(gemini_wdt_suspend, | ||
207 | gemini_wdt_resume) | ||
208 | }; | ||
209 | |||
210 | #ifdef CONFIG_OF | ||
211 | static const struct of_device_id gemini_wdt_match[] = { | ||
212 | { .compatible = "cortina,gemini-watchdog" }, | ||
213 | {}, | ||
214 | }; | ||
215 | MODULE_DEVICE_TABLE(of, gemini_wdt_match); | ||
216 | #endif | ||
217 | |||
218 | static struct platform_driver gemini_wdt_driver = { | ||
219 | .probe = gemini_wdt_probe, | ||
220 | .driver = { | ||
221 | .name = "gemini-wdt", | ||
222 | .of_match_table = of_match_ptr(gemini_wdt_match), | ||
223 | .pm = &gemini_wdt_dev_pm_ops, | ||
224 | }, | ||
225 | }; | ||
226 | module_platform_driver(gemini_wdt_driver); | ||
227 | MODULE_AUTHOR("Linus Walleij"); | ||
228 | MODULE_DESCRIPTION("Watchdog driver for Gemini"); | ||
229 | MODULE_LICENSE("GPL"); | ||