1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * Watchdog device driver for DA9062 and DA9061 PMICs |
4 | * Copyright (C) 2015 Dialog Semiconductor Ltd. |
5 | * |
6 | */ |
7 | |
8 | #include <linux/kernel.h> |
9 | #include <linux/module.h> |
10 | #include <linux/watchdog.h> |
11 | #include <linux/platform_device.h> |
12 | #include <linux/uaccess.h> |
13 | #include <linux/slab.h> |
14 | #include <linux/i2c.h> |
15 | #include <linux/delay.h> |
16 | #include <linux/jiffies.h> |
17 | #include <linux/mfd/da9062/registers.h> |
18 | #include <linux/mfd/da9062/core.h> |
19 | #include <linux/property.h> |
20 | #include <linux/regmap.h> |
21 | #include <linux/of.h> |
22 | |
23 | static const unsigned int wdt_timeout[] = { 0, 2, 4, 8, 16, 32, 65, 131 }; |
24 | #define DA9062_TWDSCALE_DISABLE 0 |
25 | #define DA9062_TWDSCALE_MIN 1 |
26 | #define DA9062_TWDSCALE_MAX (ARRAY_SIZE(wdt_timeout) - 1) |
27 | #define DA9062_WDT_MIN_TIMEOUT wdt_timeout[DA9062_TWDSCALE_MIN] |
28 | #define DA9062_WDT_MAX_TIMEOUT wdt_timeout[DA9062_TWDSCALE_MAX] |
29 | #define DA9062_WDG_DEFAULT_TIMEOUT wdt_timeout[DA9062_TWDSCALE_MAX-1] |
30 | #define DA9062_RESET_PROTECTION_MS 300 |
31 | |
32 | struct da9062_watchdog { |
33 | struct da9062 *hw; |
34 | struct watchdog_device wdtdev; |
35 | bool use_sw_pm; |
36 | }; |
37 | |
38 | static unsigned int da9062_wdt_read_timeout(struct da9062_watchdog *wdt) |
39 | { |
40 | unsigned int val; |
41 | |
42 | regmap_read(map: wdt->hw->regmap, DA9062AA_CONTROL_D, val: &val); |
43 | |
44 | return wdt_timeout[val & DA9062AA_TWDSCALE_MASK]; |
45 | } |
46 | |
47 | static unsigned int da9062_wdt_timeout_to_sel(unsigned int secs) |
48 | { |
49 | unsigned int i; |
50 | |
51 | for (i = DA9062_TWDSCALE_MIN; i <= DA9062_TWDSCALE_MAX; i++) { |
52 | if (wdt_timeout[i] >= secs) |
53 | return i; |
54 | } |
55 | |
56 | return DA9062_TWDSCALE_MAX; |
57 | } |
58 | |
59 | static int da9062_reset_watchdog_timer(struct da9062_watchdog *wdt) |
60 | { |
61 | return regmap_update_bits(map: wdt->hw->regmap, DA9062AA_CONTROL_F, |
62 | DA9062AA_WATCHDOG_MASK, |
63 | DA9062AA_WATCHDOG_MASK); |
64 | } |
65 | |
66 | static int da9062_wdt_update_timeout_register(struct da9062_watchdog *wdt, |
67 | unsigned int regval) |
68 | { |
69 | struct da9062 *chip = wdt->hw; |
70 | |
71 | regmap_update_bits(map: chip->regmap, |
72 | DA9062AA_CONTROL_D, |
73 | DA9062AA_TWDSCALE_MASK, |
74 | DA9062_TWDSCALE_DISABLE); |
75 | |
76 | usleep_range(min: 150, max: 300); |
77 | |
78 | return regmap_update_bits(map: chip->regmap, |
79 | DA9062AA_CONTROL_D, |
80 | DA9062AA_TWDSCALE_MASK, |
81 | val: regval); |
82 | } |
83 | |
84 | static int da9062_wdt_start(struct watchdog_device *wdd) |
85 | { |
86 | struct da9062_watchdog *wdt = watchdog_get_drvdata(wdd); |
87 | unsigned int selector; |
88 | int ret; |
89 | |
90 | selector = da9062_wdt_timeout_to_sel(secs: wdt->wdtdev.timeout); |
91 | ret = da9062_wdt_update_timeout_register(wdt, regval: selector); |
92 | if (ret) |
93 | dev_err(wdt->hw->dev, "Watchdog failed to start (err = %d)\n" , |
94 | ret); |
95 | |
96 | return ret; |
97 | } |
98 | |
99 | static int da9062_wdt_stop(struct watchdog_device *wdd) |
100 | { |
101 | struct da9062_watchdog *wdt = watchdog_get_drvdata(wdd); |
102 | int ret; |
103 | |
104 | ret = regmap_update_bits(map: wdt->hw->regmap, |
105 | DA9062AA_CONTROL_D, |
106 | DA9062AA_TWDSCALE_MASK, |
107 | DA9062_TWDSCALE_DISABLE); |
108 | if (ret) |
109 | dev_err(wdt->hw->dev, "Watchdog failed to stop (err = %d)\n" , |
110 | ret); |
111 | |
112 | return ret; |
113 | } |
114 | |
115 | static int da9062_wdt_ping(struct watchdog_device *wdd) |
116 | { |
117 | struct da9062_watchdog *wdt = watchdog_get_drvdata(wdd); |
118 | int ret; |
119 | |
120 | /* |
121 | * Prevent pings from occurring late in system poweroff/reboot sequence |
122 | * and possibly locking out restart handler from accessing i2c bus. |
123 | */ |
124 | if (system_state > SYSTEM_RUNNING) |
125 | return 0; |
126 | |
127 | ret = da9062_reset_watchdog_timer(wdt); |
128 | if (ret) |
129 | dev_err(wdt->hw->dev, "Failed to ping the watchdog (err = %d)\n" , |
130 | ret); |
131 | |
132 | return ret; |
133 | } |
134 | |
135 | static int da9062_wdt_set_timeout(struct watchdog_device *wdd, |
136 | unsigned int timeout) |
137 | { |
138 | struct da9062_watchdog *wdt = watchdog_get_drvdata(wdd); |
139 | unsigned int selector; |
140 | int ret; |
141 | |
142 | selector = da9062_wdt_timeout_to_sel(secs: timeout); |
143 | ret = da9062_wdt_update_timeout_register(wdt, regval: selector); |
144 | if (ret) |
145 | dev_err(wdt->hw->dev, "Failed to set watchdog timeout (err = %d)\n" , |
146 | ret); |
147 | else |
148 | wdd->timeout = wdt_timeout[selector]; |
149 | |
150 | return ret; |
151 | } |
152 | |
153 | static int da9062_wdt_restart(struct watchdog_device *wdd, unsigned long action, |
154 | void *data) |
155 | { |
156 | struct da9062_watchdog *wdt = watchdog_get_drvdata(wdd); |
157 | struct i2c_client *client = to_i2c_client(wdt->hw->dev); |
158 | union i2c_smbus_data msg; |
159 | int ret; |
160 | |
161 | /* |
162 | * Don't use regmap because it is not atomic safe. Additionally, use |
163 | * unlocked flavor of i2c_smbus_xfer to avoid scenario where i2c bus |
164 | * might be previously locked by some process unable to release the |
165 | * lock due to interrupts already being disabled at this late stage. |
166 | */ |
167 | msg.byte = DA9062AA_SHUTDOWN_MASK; |
168 | ret = __i2c_smbus_xfer(adapter: client->adapter, addr: client->addr, flags: client->flags, |
169 | I2C_SMBUS_WRITE, DA9062AA_CONTROL_F, |
170 | I2C_SMBUS_BYTE_DATA, data: &msg); |
171 | |
172 | if (ret < 0) |
173 | dev_alert(wdt->hw->dev, "Failed to shutdown (err = %d)\n" , |
174 | ret); |
175 | |
176 | /* wait for reset to assert... */ |
177 | mdelay(500); |
178 | |
179 | return ret; |
180 | } |
181 | |
182 | static const struct watchdog_info da9062_watchdog_info = { |
183 | .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING, |
184 | .identity = "DA9062 WDT" , |
185 | }; |
186 | |
187 | static const struct watchdog_ops da9062_watchdog_ops = { |
188 | .owner = THIS_MODULE, |
189 | .start = da9062_wdt_start, |
190 | .stop = da9062_wdt_stop, |
191 | .ping = da9062_wdt_ping, |
192 | .set_timeout = da9062_wdt_set_timeout, |
193 | .restart = da9062_wdt_restart, |
194 | }; |
195 | |
196 | static const struct of_device_id da9062_compatible_id_table[] = { |
197 | { .compatible = "dlg,da9062-watchdog" , }, |
198 | { }, |
199 | }; |
200 | |
201 | MODULE_DEVICE_TABLE(of, da9062_compatible_id_table); |
202 | |
203 | static int da9062_wdt_probe(struct platform_device *pdev) |
204 | { |
205 | struct device *dev = &pdev->dev; |
206 | unsigned int timeout; |
207 | struct da9062 *chip; |
208 | struct da9062_watchdog *wdt; |
209 | |
210 | chip = dev_get_drvdata(dev: dev->parent); |
211 | if (!chip) |
212 | return -EINVAL; |
213 | |
214 | wdt = devm_kzalloc(dev, size: sizeof(*wdt), GFP_KERNEL); |
215 | if (!wdt) |
216 | return -ENOMEM; |
217 | |
218 | wdt->use_sw_pm = device_property_present(dev, propname: "dlg,use-sw-pm" ); |
219 | |
220 | wdt->hw = chip; |
221 | |
222 | wdt->wdtdev.info = &da9062_watchdog_info; |
223 | wdt->wdtdev.ops = &da9062_watchdog_ops; |
224 | wdt->wdtdev.min_timeout = DA9062_WDT_MIN_TIMEOUT; |
225 | wdt->wdtdev.max_timeout = DA9062_WDT_MAX_TIMEOUT; |
226 | wdt->wdtdev.min_hw_heartbeat_ms = DA9062_RESET_PROTECTION_MS; |
227 | wdt->wdtdev.timeout = DA9062_WDG_DEFAULT_TIMEOUT; |
228 | wdt->wdtdev.status = WATCHDOG_NOWAYOUT_INIT_STATUS; |
229 | wdt->wdtdev.parent = dev; |
230 | |
231 | watchdog_set_restart_priority(wdd: &wdt->wdtdev, priority: 128); |
232 | |
233 | watchdog_set_drvdata(wdd: &wdt->wdtdev, data: wdt); |
234 | dev_set_drvdata(dev, data: &wdt->wdtdev); |
235 | |
236 | timeout = da9062_wdt_read_timeout(wdt); |
237 | if (timeout) |
238 | wdt->wdtdev.timeout = timeout; |
239 | |
240 | /* Set timeout from DT value if available */ |
241 | watchdog_init_timeout(wdd: &wdt->wdtdev, timeout_parm: 0, dev); |
242 | |
243 | if (timeout) { |
244 | da9062_wdt_set_timeout(wdd: &wdt->wdtdev, timeout: wdt->wdtdev.timeout); |
245 | set_bit(WDOG_HW_RUNNING, addr: &wdt->wdtdev.status); |
246 | } |
247 | |
248 | return devm_watchdog_register_device(dev, &wdt->wdtdev); |
249 | } |
250 | |
251 | static int __maybe_unused da9062_wdt_suspend(struct device *dev) |
252 | { |
253 | struct watchdog_device *wdd = dev_get_drvdata(dev); |
254 | struct da9062_watchdog *wdt = watchdog_get_drvdata(wdd); |
255 | |
256 | if (!wdt->use_sw_pm) |
257 | return 0; |
258 | |
259 | if (watchdog_active(wdd)) |
260 | return da9062_wdt_stop(wdd); |
261 | |
262 | return 0; |
263 | } |
264 | |
265 | static int __maybe_unused da9062_wdt_resume(struct device *dev) |
266 | { |
267 | struct watchdog_device *wdd = dev_get_drvdata(dev); |
268 | struct da9062_watchdog *wdt = watchdog_get_drvdata(wdd); |
269 | |
270 | if (!wdt->use_sw_pm) |
271 | return 0; |
272 | |
273 | if (watchdog_active(wdd)) |
274 | return da9062_wdt_start(wdd); |
275 | |
276 | return 0; |
277 | } |
278 | |
279 | static SIMPLE_DEV_PM_OPS(da9062_wdt_pm_ops, |
280 | da9062_wdt_suspend, da9062_wdt_resume); |
281 | |
282 | static struct platform_driver da9062_wdt_driver = { |
283 | .probe = da9062_wdt_probe, |
284 | .driver = { |
285 | .name = "da9062-watchdog" , |
286 | .pm = &da9062_wdt_pm_ops, |
287 | .of_match_table = da9062_compatible_id_table, |
288 | }, |
289 | }; |
290 | module_platform_driver(da9062_wdt_driver); |
291 | |
292 | MODULE_AUTHOR("S Twiss <stwiss.opensource@diasemi.com>" ); |
293 | MODULE_DESCRIPTION("WDT device driver for Dialog DA9062 and DA9061" ); |
294 | MODULE_LICENSE("GPL" ); |
295 | MODULE_ALIAS("platform:da9062-watchdog" ); |
296 | |