]>
Commit | Line | Data |
---|---|---|
2e62c498 | 1 | // SPDX-License-Identifier: GPL-2.0+ |
dca536c4 MR |
2 | /* |
3 | * Copyright (C) 2015 Mans Rullgard <mans@mansr.com> | |
4 | * SMP86xx/SMP87xx Watchdog driver | |
dca536c4 MR |
5 | */ |
6 | ||
7 | #include <linux/bitops.h> | |
8 | #include <linux/clk.h> | |
9 | #include <linux/delay.h> | |
10 | #include <linux/io.h> | |
11 | #include <linux/kernel.h> | |
12 | #include <linux/module.h> | |
13 | #include <linux/moduleparam.h> | |
ac316725 | 14 | #include <linux/mod_devicetable.h> |
dca536c4 | 15 | #include <linux/platform_device.h> |
dca536c4 MR |
16 | #include <linux/watchdog.h> |
17 | ||
18 | #define DEFAULT_TIMEOUT 30 | |
19 | ||
20 | static bool nowayout = WATCHDOG_NOWAYOUT; | |
21 | module_param(nowayout, bool, 0); | |
22 | MODULE_PARM_DESC(nowayout, | |
23 | "Watchdog cannot be stopped once started (default=" | |
24 | __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); | |
25 | ||
26 | static unsigned int timeout; | |
27 | module_param(timeout, int, 0); | |
28 | MODULE_PARM_DESC(timeout, "Watchdog timeout"); | |
29 | ||
30 | /* | |
31 | * Counter counts down from programmed value. Reset asserts when | |
32 | * the counter reaches 1. | |
33 | */ | |
34 | #define WD_COUNTER 0 | |
35 | ||
36 | #define WD_CONFIG 4 | |
37 | #define WD_CONFIG_XTAL_IN BIT(0) | |
38 | #define WD_CONFIG_DISABLE BIT(31) | |
39 | ||
40 | struct tangox_wdt_device { | |
41 | struct watchdog_device wdt; | |
42 | void __iomem *base; | |
43 | unsigned long clk_rate; | |
44 | struct clk *clk; | |
dca536c4 MR |
45 | }; |
46 | ||
47 | static int tangox_wdt_set_timeout(struct watchdog_device *wdt, | |
48 | unsigned int new_timeout) | |
49 | { | |
50 | wdt->timeout = new_timeout; | |
51 | ||
52 | return 0; | |
53 | } | |
54 | ||
55 | static int tangox_wdt_start(struct watchdog_device *wdt) | |
56 | { | |
57 | struct tangox_wdt_device *dev = watchdog_get_drvdata(wdt); | |
58 | u32 ticks; | |
59 | ||
60 | ticks = 1 + wdt->timeout * dev->clk_rate; | |
61 | writel(ticks, dev->base + WD_COUNTER); | |
62 | ||
63 | return 0; | |
64 | } | |
65 | ||
66 | static int tangox_wdt_stop(struct watchdog_device *wdt) | |
67 | { | |
68 | struct tangox_wdt_device *dev = watchdog_get_drvdata(wdt); | |
69 | ||
70 | writel(0, dev->base + WD_COUNTER); | |
71 | ||
72 | return 0; | |
73 | } | |
74 | ||
75 | static unsigned int tangox_wdt_get_timeleft(struct watchdog_device *wdt) | |
76 | { | |
77 | struct tangox_wdt_device *dev = watchdog_get_drvdata(wdt); | |
78 | u32 count; | |
79 | ||
80 | count = readl(dev->base + WD_COUNTER); | |
81 | ||
82 | if (!count) | |
83 | return 0; | |
84 | ||
85 | return (count - 1) / dev->clk_rate; | |
86 | } | |
87 | ||
88 | static const struct watchdog_info tangox_wdt_info = { | |
89 | .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE, | |
90 | .identity = "tangox watchdog", | |
91 | }; | |
92 | ||
0397c5db GR |
93 | static int tangox_wdt_restart(struct watchdog_device *wdt, |
94 | unsigned long action, void *data) | |
95 | { | |
96 | struct tangox_wdt_device *dev = watchdog_get_drvdata(wdt); | |
97 | ||
98 | writel(1, dev->base + WD_COUNTER); | |
99 | ||
100 | return 0; | |
101 | } | |
102 | ||
dca536c4 MR |
103 | static const struct watchdog_ops tangox_wdt_ops = { |
104 | .start = tangox_wdt_start, | |
105 | .stop = tangox_wdt_stop, | |
106 | .set_timeout = tangox_wdt_set_timeout, | |
107 | .get_timeleft = tangox_wdt_get_timeleft, | |
0397c5db | 108 | .restart = tangox_wdt_restart, |
dca536c4 MR |
109 | }; |
110 | ||
c838a3ae GR |
111 | static void tangox_clk_disable_unprepare(void *data) |
112 | { | |
113 | clk_disable_unprepare(data); | |
114 | } | |
115 | ||
dca536c4 MR |
116 | static int tangox_wdt_probe(struct platform_device *pdev) |
117 | { | |
118 | struct tangox_wdt_device *dev; | |
dca536c4 MR |
119 | u32 config; |
120 | int err; | |
121 | ||
122 | dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL); | |
123 | if (!dev) | |
124 | return -ENOMEM; | |
125 | ||
0f0a6a28 | 126 | dev->base = devm_platform_ioremap_resource(pdev, 0); |
dca536c4 MR |
127 | if (IS_ERR(dev->base)) |
128 | return PTR_ERR(dev->base); | |
129 | ||
130 | dev->clk = devm_clk_get(&pdev->dev, NULL); | |
131 | if (IS_ERR(dev->clk)) | |
132 | return PTR_ERR(dev->clk); | |
133 | ||
134 | err = clk_prepare_enable(dev->clk); | |
135 | if (err) | |
136 | return err; | |
c838a3ae GR |
137 | err = devm_add_action_or_reset(&pdev->dev, |
138 | tangox_clk_disable_unprepare, dev->clk); | |
139 | if (err) | |
140 | return err; | |
dca536c4 MR |
141 | |
142 | dev->clk_rate = clk_get_rate(dev->clk); | |
c838a3ae GR |
143 | if (!dev->clk_rate) |
144 | return -EINVAL; | |
dca536c4 MR |
145 | |
146 | dev->wdt.parent = &pdev->dev; | |
147 | dev->wdt.info = &tangox_wdt_info; | |
148 | dev->wdt.ops = &tangox_wdt_ops; | |
149 | dev->wdt.timeout = DEFAULT_TIMEOUT; | |
150 | dev->wdt.min_timeout = 1; | |
c7ef68c3 | 151 | dev->wdt.max_hw_heartbeat_ms = (U32_MAX - 1) / dev->clk_rate; |
dca536c4 MR |
152 | |
153 | watchdog_init_timeout(&dev->wdt, timeout, &pdev->dev); | |
154 | watchdog_set_nowayout(&dev->wdt, nowayout); | |
155 | watchdog_set_drvdata(&dev->wdt, dev); | |
156 | ||
157 | /* | |
158 | * Deactivate counter if disable bit is set to avoid | |
159 | * accidental reset. | |
160 | */ | |
161 | config = readl(dev->base + WD_CONFIG); | |
162 | if (config & WD_CONFIG_DISABLE) | |
163 | writel(0, dev->base + WD_COUNTER); | |
164 | ||
165 | writel(WD_CONFIG_XTAL_IN, dev->base + WD_CONFIG); | |
166 | ||
167 | /* | |
168 | * Mark as active and restart with configured timeout if | |
169 | * already running. | |
170 | */ | |
171 | if (readl(dev->base + WD_COUNTER)) { | |
a3e376d2 | 172 | set_bit(WDOG_HW_RUNNING, &dev->wdt.status); |
dca536c4 MR |
173 | tangox_wdt_start(&dev->wdt); |
174 | } | |
175 | ||
0397c5db GR |
176 | watchdog_set_restart_priority(&dev->wdt, 128); |
177 | ||
c838a3ae GR |
178 | watchdog_stop_on_unregister(&dev->wdt); |
179 | err = devm_watchdog_register_device(&pdev->dev, &dev->wdt); | |
84b84bcf | 180 | if (err) |
c838a3ae | 181 | return err; |
dca536c4 MR |
182 | |
183 | platform_set_drvdata(pdev, dev); | |
184 | ||
3d29f808 | 185 | dev_info(&pdev->dev, "SMP86xx/SMP87xx watchdog registered\n"); |
dca536c4 | 186 | |
dca536c4 MR |
187 | return 0; |
188 | } | |
189 | ||
190 | static const struct of_device_id tangox_wdt_dt_ids[] = { | |
191 | { .compatible = "sigma,smp8642-wdt" }, | |
192 | { .compatible = "sigma,smp8759-wdt" }, | |
193 | { } | |
194 | }; | |
195 | MODULE_DEVICE_TABLE(of, tangox_wdt_dt_ids); | |
196 | ||
197 | static struct platform_driver tangox_wdt_driver = { | |
198 | .probe = tangox_wdt_probe, | |
dca536c4 MR |
199 | .driver = { |
200 | .name = "tangox-wdt", | |
201 | .of_match_table = tangox_wdt_dt_ids, | |
202 | }, | |
203 | }; | |
204 | ||
205 | module_platform_driver(tangox_wdt_driver); | |
206 | ||
207 | MODULE_AUTHOR("Mans Rullgard <mans@mansr.com>"); | |
208 | MODULE_DESCRIPTION("SMP86xx/SMP87xx Watchdog driver"); | |
209 | MODULE_LICENSE("GPL"); |