Commit dca536c433a20f916451d8318f4aa7158c0d811c
Committed by
Wim Van Sebroeck
1 parent
5c6bb88a1b
watchdog: add support for Sigma Designs SMP86xx/SMP87xx
This adds support for the Sigma Designs SMP86xx/SMP87xx family built-in watchdog. Signed-off-by: Mans Rullgard <mans@mansr.com> Signed-off-by: Guenter Roeck <linux@roeck-us.net> Signed-off-by: Wim Van Sebroeck <wim@iguana.be>
Showing 3 changed files with 236 additions and 0 deletions Side-by-side Diff
drivers/watchdog/Kconfig
... | ... | @@ -142,6 +142,16 @@ |
142 | 142 | This driver can also be built as a module. If so the module |
143 | 143 | will be called menf21bmc_wdt. |
144 | 144 | |
145 | +config TANGOX_WATCHDOG | |
146 | + tristate "Sigma Designs SMP86xx/SMP87xx watchdog" | |
147 | + select WATCHDOG_CORE | |
148 | + depends on ARCH_TANGOX || COMPILE_TEST | |
149 | + help | |
150 | + Support for the watchdog in Sigma Designs SMP86xx (tango3) | |
151 | + and SMP87xx (tango4) family chips. | |
152 | + | |
153 | + This driver can be built as a module. The module name is tangox_wdt. | |
154 | + | |
145 | 155 | config WM831X_WATCHDOG |
146 | 156 | tristate "WM831x watchdog" |
147 | 157 | depends on MFD_WM831X |
drivers/watchdog/Makefile
... | ... | @@ -190,6 +190,7 @@ |
190 | 190 | obj-$(CONFIG_DA9062_WATCHDOG) += da9062_wdt.o |
191 | 191 | obj-$(CONFIG_DA9063_WATCHDOG) += da9063_wdt.o |
192 | 192 | obj-$(CONFIG_GPIO_WATCHDOG) += gpio_wdt.o |
193 | +obj-$(CONFIG_TANGOX_WATCHDOG) += tangox_wdt.o | |
193 | 194 | obj-$(CONFIG_WM831X_WATCHDOG) += wm831x_wdt.o |
194 | 195 | obj-$(CONFIG_WM8350_WATCHDOG) += wm8350_wdt.o |
195 | 196 | obj-$(CONFIG_MAX63XX_WATCHDOG) += max63xx_wdt.o |
drivers/watchdog/tangox_wdt.c
1 | +/* | |
2 | + * Copyright (C) 2015 Mans Rullgard <mans@mansr.com> | |
3 | + * SMP86xx/SMP87xx Watchdog driver | |
4 | + * | |
5 | + * This program is free software; you can redistribute it and/or modify it | |
6 | + * under the terms of the GNU General Public License as published by the | |
7 | + * Free Software Foundation; either version 2 of the License, or (at your | |
8 | + * option) any later version. | |
9 | + */ | |
10 | + | |
11 | +#include <linux/bitops.h> | |
12 | +#include <linux/clk.h> | |
13 | +#include <linux/delay.h> | |
14 | +#include <linux/io.h> | |
15 | +#include <linux/kernel.h> | |
16 | +#include <linux/module.h> | |
17 | +#include <linux/moduleparam.h> | |
18 | +#include <linux/notifier.h> | |
19 | +#include <linux/platform_device.h> | |
20 | +#include <linux/reboot.h> | |
21 | +#include <linux/watchdog.h> | |
22 | + | |
23 | +#define DEFAULT_TIMEOUT 30 | |
24 | + | |
25 | +static bool nowayout = WATCHDOG_NOWAYOUT; | |
26 | +module_param(nowayout, bool, 0); | |
27 | +MODULE_PARM_DESC(nowayout, | |
28 | + "Watchdog cannot be stopped once started (default=" | |
29 | + __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); | |
30 | + | |
31 | +static unsigned int timeout; | |
32 | +module_param(timeout, int, 0); | |
33 | +MODULE_PARM_DESC(timeout, "Watchdog timeout"); | |
34 | + | |
35 | +/* | |
36 | + * Counter counts down from programmed value. Reset asserts when | |
37 | + * the counter reaches 1. | |
38 | + */ | |
39 | +#define WD_COUNTER 0 | |
40 | + | |
41 | +#define WD_CONFIG 4 | |
42 | +#define WD_CONFIG_XTAL_IN BIT(0) | |
43 | +#define WD_CONFIG_DISABLE BIT(31) | |
44 | + | |
45 | +struct tangox_wdt_device { | |
46 | + struct watchdog_device wdt; | |
47 | + void __iomem *base; | |
48 | + unsigned long clk_rate; | |
49 | + struct clk *clk; | |
50 | + struct notifier_block restart; | |
51 | +}; | |
52 | + | |
53 | +static int tangox_wdt_set_timeout(struct watchdog_device *wdt, | |
54 | + unsigned int new_timeout) | |
55 | +{ | |
56 | + wdt->timeout = new_timeout; | |
57 | + | |
58 | + return 0; | |
59 | +} | |
60 | + | |
61 | +static int tangox_wdt_start(struct watchdog_device *wdt) | |
62 | +{ | |
63 | + struct tangox_wdt_device *dev = watchdog_get_drvdata(wdt); | |
64 | + u32 ticks; | |
65 | + | |
66 | + ticks = 1 + wdt->timeout * dev->clk_rate; | |
67 | + writel(ticks, dev->base + WD_COUNTER); | |
68 | + | |
69 | + return 0; | |
70 | +} | |
71 | + | |
72 | +static int tangox_wdt_stop(struct watchdog_device *wdt) | |
73 | +{ | |
74 | + struct tangox_wdt_device *dev = watchdog_get_drvdata(wdt); | |
75 | + | |
76 | + writel(0, dev->base + WD_COUNTER); | |
77 | + | |
78 | + return 0; | |
79 | +} | |
80 | + | |
81 | +static unsigned int tangox_wdt_get_timeleft(struct watchdog_device *wdt) | |
82 | +{ | |
83 | + struct tangox_wdt_device *dev = watchdog_get_drvdata(wdt); | |
84 | + u32 count; | |
85 | + | |
86 | + count = readl(dev->base + WD_COUNTER); | |
87 | + | |
88 | + if (!count) | |
89 | + return 0; | |
90 | + | |
91 | + return (count - 1) / dev->clk_rate; | |
92 | +} | |
93 | + | |
94 | +static const struct watchdog_info tangox_wdt_info = { | |
95 | + .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE, | |
96 | + .identity = "tangox watchdog", | |
97 | +}; | |
98 | + | |
99 | +static const struct watchdog_ops tangox_wdt_ops = { | |
100 | + .start = tangox_wdt_start, | |
101 | + .stop = tangox_wdt_stop, | |
102 | + .set_timeout = tangox_wdt_set_timeout, | |
103 | + .get_timeleft = tangox_wdt_get_timeleft, | |
104 | +}; | |
105 | + | |
106 | +static int tangox_wdt_restart(struct notifier_block *nb, unsigned long action, | |
107 | + void *data) | |
108 | +{ | |
109 | + struct tangox_wdt_device *dev = | |
110 | + container_of(nb, struct tangox_wdt_device, restart); | |
111 | + | |
112 | + writel(1, dev->base + WD_COUNTER); | |
113 | + | |
114 | + return NOTIFY_DONE; | |
115 | +} | |
116 | + | |
117 | +static int tangox_wdt_probe(struct platform_device *pdev) | |
118 | +{ | |
119 | + struct tangox_wdt_device *dev; | |
120 | + struct resource *res; | |
121 | + u32 config; | |
122 | + int err; | |
123 | + | |
124 | + dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL); | |
125 | + if (!dev) | |
126 | + return -ENOMEM; | |
127 | + | |
128 | + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | |
129 | + dev->base = devm_ioremap_resource(&pdev->dev, res); | |
130 | + if (IS_ERR(dev->base)) | |
131 | + return PTR_ERR(dev->base); | |
132 | + | |
133 | + dev->clk = devm_clk_get(&pdev->dev, NULL); | |
134 | + if (IS_ERR(dev->clk)) | |
135 | + return PTR_ERR(dev->clk); | |
136 | + | |
137 | + err = clk_prepare_enable(dev->clk); | |
138 | + if (err) | |
139 | + return err; | |
140 | + | |
141 | + dev->clk_rate = clk_get_rate(dev->clk); | |
142 | + | |
143 | + dev->wdt.parent = &pdev->dev; | |
144 | + dev->wdt.info = &tangox_wdt_info; | |
145 | + dev->wdt.ops = &tangox_wdt_ops; | |
146 | + dev->wdt.timeout = DEFAULT_TIMEOUT; | |
147 | + dev->wdt.min_timeout = 1; | |
148 | + dev->wdt.max_timeout = (U32_MAX - 1) / dev->clk_rate; | |
149 | + | |
150 | + watchdog_init_timeout(&dev->wdt, timeout, &pdev->dev); | |
151 | + watchdog_set_nowayout(&dev->wdt, nowayout); | |
152 | + watchdog_set_drvdata(&dev->wdt, dev); | |
153 | + | |
154 | + /* | |
155 | + * Deactivate counter if disable bit is set to avoid | |
156 | + * accidental reset. | |
157 | + */ | |
158 | + config = readl(dev->base + WD_CONFIG); | |
159 | + if (config & WD_CONFIG_DISABLE) | |
160 | + writel(0, dev->base + WD_COUNTER); | |
161 | + | |
162 | + writel(WD_CONFIG_XTAL_IN, dev->base + WD_CONFIG); | |
163 | + | |
164 | + /* | |
165 | + * Mark as active and restart with configured timeout if | |
166 | + * already running. | |
167 | + */ | |
168 | + if (readl(dev->base + WD_COUNTER)) { | |
169 | + set_bit(WDOG_ACTIVE, &dev->wdt.status); | |
170 | + tangox_wdt_start(&dev->wdt); | |
171 | + } | |
172 | + | |
173 | + err = watchdog_register_device(&dev->wdt); | |
174 | + if (err) { | |
175 | + clk_disable_unprepare(dev->clk); | |
176 | + return err; | |
177 | + } | |
178 | + | |
179 | + platform_set_drvdata(pdev, dev); | |
180 | + | |
181 | + dev->restart.notifier_call = tangox_wdt_restart; | |
182 | + dev->restart.priority = 128; | |
183 | + err = register_restart_handler(&dev->restart); | |
184 | + if (err) | |
185 | + dev_warn(&pdev->dev, "failed to register restart handler\n"); | |
186 | + | |
187 | + dev_info(dev->wdt.dev, "SMP86xx/SMP87xx watchdog registered\n"); | |
188 | + | |
189 | + return 0; | |
190 | +} | |
191 | + | |
192 | +static int tangox_wdt_remove(struct platform_device *pdev) | |
193 | +{ | |
194 | + struct tangox_wdt_device *dev = platform_get_drvdata(pdev); | |
195 | + | |
196 | + tangox_wdt_stop(&dev->wdt); | |
197 | + clk_disable_unprepare(dev->clk); | |
198 | + | |
199 | + unregister_restart_handler(&dev->restart); | |
200 | + watchdog_unregister_device(&dev->wdt); | |
201 | + | |
202 | + return 0; | |
203 | +} | |
204 | + | |
205 | +static const struct of_device_id tangox_wdt_dt_ids[] = { | |
206 | + { .compatible = "sigma,smp8642-wdt" }, | |
207 | + { .compatible = "sigma,smp8759-wdt" }, | |
208 | + { } | |
209 | +}; | |
210 | +MODULE_DEVICE_TABLE(of, tangox_wdt_dt_ids); | |
211 | + | |
212 | +static struct platform_driver tangox_wdt_driver = { | |
213 | + .probe = tangox_wdt_probe, | |
214 | + .remove = tangox_wdt_remove, | |
215 | + .driver = { | |
216 | + .name = "tangox-wdt", | |
217 | + .of_match_table = tangox_wdt_dt_ids, | |
218 | + }, | |
219 | +}; | |
220 | + | |
221 | +module_platform_driver(tangox_wdt_driver); | |
222 | + | |
223 | +MODULE_AUTHOR("Mans Rullgard <mans@mansr.com>"); | |
224 | +MODULE_DESCRIPTION("SMP86xx/SMP87xx Watchdog driver"); | |
225 | +MODULE_LICENSE("GPL"); |