1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * sl28cpld watchdog driver |
4 | * |
5 | * Copyright 2020 Kontron Europe GmbH |
6 | */ |
7 | |
8 | #include <linux/kernel.h> |
9 | #include <linux/mod_devicetable.h> |
10 | #include <linux/module.h> |
11 | #include <linux/platform_device.h> |
12 | #include <linux/property.h> |
13 | #include <linux/regmap.h> |
14 | #include <linux/watchdog.h> |
15 | |
16 | /* |
17 | * Watchdog timer block registers. |
18 | */ |
19 | #define WDT_CTRL 0x00 |
20 | #define WDT_CTRL_EN BIT(0) |
21 | #define WDT_CTRL_LOCK BIT(2) |
22 | #define WDT_CTRL_ASSERT_SYS_RESET BIT(6) |
23 | #define WDT_CTRL_ASSERT_WDT_TIMEOUT BIT(7) |
24 | #define WDT_TIMEOUT 0x01 |
25 | #define WDT_KICK 0x02 |
26 | #define WDT_KICK_VALUE 0x6b |
27 | #define WDT_COUNT 0x03 |
28 | |
29 | #define WDT_DEFAULT_TIMEOUT 10 |
30 | |
31 | static bool nowayout = WATCHDOG_NOWAYOUT; |
32 | module_param(nowayout, bool, 0); |
33 | MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" |
34 | __MODULE_STRING(WATCHDOG_NOWAYOUT) ")" ); |
35 | |
36 | static int timeout; |
37 | module_param(timeout, int, 0); |
38 | MODULE_PARM_DESC(timeout, "Initial watchdog timeout in seconds" ); |
39 | |
40 | struct sl28cpld_wdt { |
41 | struct watchdog_device wdd; |
42 | struct regmap *regmap; |
43 | u32 offset; |
44 | bool assert_wdt_timeout; |
45 | }; |
46 | |
47 | static int sl28cpld_wdt_ping(struct watchdog_device *wdd) |
48 | { |
49 | struct sl28cpld_wdt *wdt = watchdog_get_drvdata(wdd); |
50 | |
51 | return regmap_write(map: wdt->regmap, reg: wdt->offset + WDT_KICK, |
52 | WDT_KICK_VALUE); |
53 | } |
54 | |
55 | static int sl28cpld_wdt_start(struct watchdog_device *wdd) |
56 | { |
57 | struct sl28cpld_wdt *wdt = watchdog_get_drvdata(wdd); |
58 | unsigned int val; |
59 | |
60 | val = WDT_CTRL_EN | WDT_CTRL_ASSERT_SYS_RESET; |
61 | if (wdt->assert_wdt_timeout) |
62 | val |= WDT_CTRL_ASSERT_WDT_TIMEOUT; |
63 | if (nowayout) |
64 | val |= WDT_CTRL_LOCK; |
65 | |
66 | return regmap_update_bits(map: wdt->regmap, reg: wdt->offset + WDT_CTRL, |
67 | mask: val, val); |
68 | } |
69 | |
70 | static int sl28cpld_wdt_stop(struct watchdog_device *wdd) |
71 | { |
72 | struct sl28cpld_wdt *wdt = watchdog_get_drvdata(wdd); |
73 | |
74 | return regmap_update_bits(map: wdt->regmap, reg: wdt->offset + WDT_CTRL, |
75 | WDT_CTRL_EN, val: 0); |
76 | } |
77 | |
78 | static unsigned int sl28cpld_wdt_get_timeleft(struct watchdog_device *wdd) |
79 | { |
80 | struct sl28cpld_wdt *wdt = watchdog_get_drvdata(wdd); |
81 | unsigned int val; |
82 | int ret; |
83 | |
84 | ret = regmap_read(map: wdt->regmap, reg: wdt->offset + WDT_COUNT, val: &val); |
85 | if (ret) |
86 | return 0; |
87 | |
88 | return val; |
89 | } |
90 | |
91 | static int sl28cpld_wdt_set_timeout(struct watchdog_device *wdd, |
92 | unsigned int timeout) |
93 | { |
94 | struct sl28cpld_wdt *wdt = watchdog_get_drvdata(wdd); |
95 | int ret; |
96 | |
97 | ret = regmap_write(map: wdt->regmap, reg: wdt->offset + WDT_TIMEOUT, val: timeout); |
98 | if (ret) |
99 | return ret; |
100 | |
101 | wdd->timeout = timeout; |
102 | |
103 | return 0; |
104 | } |
105 | |
106 | static const struct watchdog_info sl28cpld_wdt_info = { |
107 | .options = WDIOF_MAGICCLOSE | WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING, |
108 | .identity = "sl28cpld watchdog" , |
109 | }; |
110 | |
111 | static const struct watchdog_ops sl28cpld_wdt_ops = { |
112 | .owner = THIS_MODULE, |
113 | .start = sl28cpld_wdt_start, |
114 | .stop = sl28cpld_wdt_stop, |
115 | .ping = sl28cpld_wdt_ping, |
116 | .set_timeout = sl28cpld_wdt_set_timeout, |
117 | .get_timeleft = sl28cpld_wdt_get_timeleft, |
118 | }; |
119 | |
120 | static int sl28cpld_wdt_probe(struct platform_device *pdev) |
121 | { |
122 | struct watchdog_device *wdd; |
123 | struct sl28cpld_wdt *wdt; |
124 | unsigned int status; |
125 | unsigned int val; |
126 | int ret; |
127 | |
128 | if (!pdev->dev.parent) |
129 | return -ENODEV; |
130 | |
131 | wdt = devm_kzalloc(dev: &pdev->dev, size: sizeof(*wdt), GFP_KERNEL); |
132 | if (!wdt) |
133 | return -ENOMEM; |
134 | |
135 | wdt->regmap = dev_get_regmap(dev: pdev->dev.parent, NULL); |
136 | if (!wdt->regmap) |
137 | return -ENODEV; |
138 | |
139 | ret = device_property_read_u32(dev: &pdev->dev, propname: "reg" , val: &wdt->offset); |
140 | if (ret) |
141 | return -EINVAL; |
142 | |
143 | wdt->assert_wdt_timeout = device_property_read_bool(dev: &pdev->dev, |
144 | propname: "kontron,assert-wdt-timeout-pin" ); |
145 | |
146 | /* initialize struct watchdog_device */ |
147 | wdd = &wdt->wdd; |
148 | wdd->parent = &pdev->dev; |
149 | wdd->info = &sl28cpld_wdt_info; |
150 | wdd->ops = &sl28cpld_wdt_ops; |
151 | wdd->min_timeout = 1; |
152 | wdd->max_timeout = 255; |
153 | |
154 | watchdog_set_drvdata(wdd, data: wdt); |
155 | watchdog_stop_on_reboot(wdd); |
156 | |
157 | /* |
158 | * Read the status early, in case of an error, we haven't modified the |
159 | * hardware. |
160 | */ |
161 | ret = regmap_read(map: wdt->regmap, reg: wdt->offset + WDT_CTRL, val: &status); |
162 | if (ret) |
163 | return ret; |
164 | |
165 | /* |
166 | * Initial timeout value, may be overwritten by device tree or module |
167 | * parameter in watchdog_init_timeout(). |
168 | * |
169 | * Reading a zero here means that either the hardware has a default |
170 | * value of zero (which is very unlikely and definitely a hardware |
171 | * bug) or the bootloader set it to zero. In any case, we handle |
172 | * this case gracefully and set out own timeout. |
173 | */ |
174 | ret = regmap_read(map: wdt->regmap, reg: wdt->offset + WDT_TIMEOUT, val: &val); |
175 | if (ret) |
176 | return ret; |
177 | |
178 | if (val) |
179 | wdd->timeout = val; |
180 | else |
181 | wdd->timeout = WDT_DEFAULT_TIMEOUT; |
182 | |
183 | watchdog_init_timeout(wdd, timeout_parm: timeout, dev: &pdev->dev); |
184 | sl28cpld_wdt_set_timeout(wdd, timeout: wdd->timeout); |
185 | |
186 | /* if the watchdog is locked, we set nowayout */ |
187 | if (status & WDT_CTRL_LOCK) |
188 | nowayout = true; |
189 | watchdog_set_nowayout(wdd, nowayout); |
190 | |
191 | /* |
192 | * If watchdog is already running, keep it enabled, but make |
193 | * sure its mode is set correctly. |
194 | */ |
195 | if (status & WDT_CTRL_EN) { |
196 | sl28cpld_wdt_start(wdd); |
197 | set_bit(WDOG_HW_RUNNING, addr: &wdd->status); |
198 | } |
199 | |
200 | ret = devm_watchdog_register_device(dev: &pdev->dev, wdd); |
201 | if (ret < 0) { |
202 | dev_err(&pdev->dev, "failed to register watchdog device\n" ); |
203 | return ret; |
204 | } |
205 | |
206 | dev_info(&pdev->dev, "initial timeout %d sec%s\n" , |
207 | wdd->timeout, nowayout ? ", nowayout" : "" ); |
208 | |
209 | return 0; |
210 | } |
211 | |
212 | static const struct of_device_id sl28cpld_wdt_of_match[] = { |
213 | { .compatible = "kontron,sl28cpld-wdt" }, |
214 | {} |
215 | }; |
216 | MODULE_DEVICE_TABLE(of, sl28cpld_wdt_of_match); |
217 | |
218 | static struct platform_driver sl28cpld_wdt_driver = { |
219 | .probe = sl28cpld_wdt_probe, |
220 | .driver = { |
221 | .name = "sl28cpld-wdt" , |
222 | .of_match_table = sl28cpld_wdt_of_match, |
223 | }, |
224 | }; |
225 | module_platform_driver(sl28cpld_wdt_driver); |
226 | |
227 | MODULE_DESCRIPTION("sl28cpld Watchdog Driver" ); |
228 | MODULE_AUTHOR("Michael Walle <michael@walle.cc>" ); |
229 | MODULE_LICENSE("GPL" ); |
230 | |