Commit | Line | Data |
---|---|---|
dca536c4 MR |
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 | ||
3d29f808 | 187 | dev_info(&pdev->dev, "SMP86xx/SMP87xx watchdog registered\n"); |
dca536c4 MR |
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"); |