1b237bcacSMatti Vaittinen // SPDX-License-Identifier: GPL-2.0-or-later
2b237bcacSMatti Vaittinen /*
3b237bcacSMatti Vaittinen * Copyright (C) 2020 ROHM Semiconductors
4b237bcacSMatti Vaittinen *
5b237bcacSMatti Vaittinen * ROHM BD9576MUF and BD9573MUF Watchdog driver
6b237bcacSMatti Vaittinen */
7b237bcacSMatti Vaittinen
8b237bcacSMatti Vaittinen #include <linux/err.h>
9b237bcacSMatti Vaittinen #include <linux/gpio/consumer.h>
10b237bcacSMatti Vaittinen #include <linux/mfd/rohm-bd957x.h>
11b237bcacSMatti Vaittinen #include <linux/module.h>
12b237bcacSMatti Vaittinen #include <linux/platform_device.h>
13*4f719022SDmitry Torokhov #include <linux/property.h>
14b237bcacSMatti Vaittinen #include <linux/regmap.h>
15b237bcacSMatti Vaittinen #include <linux/watchdog.h>
16b237bcacSMatti Vaittinen
17b237bcacSMatti Vaittinen static bool nowayout;
18b237bcacSMatti Vaittinen module_param(nowayout, bool, 0);
19b237bcacSMatti Vaittinen MODULE_PARM_DESC(nowayout,
20b237bcacSMatti Vaittinen "Watchdog cannot be stopped once started (default=\"false\")");
21b237bcacSMatti Vaittinen
22b237bcacSMatti Vaittinen #define HW_MARGIN_MIN 2
23b237bcacSMatti Vaittinen #define HW_MARGIN_MAX 4416
24b237bcacSMatti Vaittinen #define BD957X_WDT_DEFAULT_MARGIN 4416
25b237bcacSMatti Vaittinen #define WATCHDOG_TIMEOUT 30
26b237bcacSMatti Vaittinen
27b237bcacSMatti Vaittinen struct bd9576_wdt_priv {
28b237bcacSMatti Vaittinen struct gpio_desc *gpiod_ping;
29b237bcacSMatti Vaittinen struct gpio_desc *gpiod_en;
30b237bcacSMatti Vaittinen struct device *dev;
31b237bcacSMatti Vaittinen struct regmap *regmap;
32b237bcacSMatti Vaittinen struct watchdog_device wdd;
33b237bcacSMatti Vaittinen };
34b237bcacSMatti Vaittinen
bd9576_wdt_disable(struct bd9576_wdt_priv * priv)35b237bcacSMatti Vaittinen static void bd9576_wdt_disable(struct bd9576_wdt_priv *priv)
36b237bcacSMatti Vaittinen {
37b237bcacSMatti Vaittinen gpiod_set_value_cansleep(priv->gpiod_en, 0);
38b237bcacSMatti Vaittinen }
39b237bcacSMatti Vaittinen
bd9576_wdt_ping(struct watchdog_device * wdd)40b237bcacSMatti Vaittinen static int bd9576_wdt_ping(struct watchdog_device *wdd)
41b237bcacSMatti Vaittinen {
42b237bcacSMatti Vaittinen struct bd9576_wdt_priv *priv = watchdog_get_drvdata(wdd);
43b237bcacSMatti Vaittinen
44b237bcacSMatti Vaittinen /* Pulse */
45b237bcacSMatti Vaittinen gpiod_set_value_cansleep(priv->gpiod_ping, 1);
46b237bcacSMatti Vaittinen gpiod_set_value_cansleep(priv->gpiod_ping, 0);
47b237bcacSMatti Vaittinen
48b237bcacSMatti Vaittinen return 0;
49b237bcacSMatti Vaittinen }
50b237bcacSMatti Vaittinen
bd9576_wdt_start(struct watchdog_device * wdd)51b237bcacSMatti Vaittinen static int bd9576_wdt_start(struct watchdog_device *wdd)
52b237bcacSMatti Vaittinen {
53b237bcacSMatti Vaittinen struct bd9576_wdt_priv *priv = watchdog_get_drvdata(wdd);
54b237bcacSMatti Vaittinen
55b237bcacSMatti Vaittinen gpiod_set_value_cansleep(priv->gpiod_en, 1);
56b237bcacSMatti Vaittinen
57b237bcacSMatti Vaittinen return bd9576_wdt_ping(wdd);
58b237bcacSMatti Vaittinen }
59b237bcacSMatti Vaittinen
bd9576_wdt_stop(struct watchdog_device * wdd)60b237bcacSMatti Vaittinen static int bd9576_wdt_stop(struct watchdog_device *wdd)
61b237bcacSMatti Vaittinen {
62b237bcacSMatti Vaittinen struct bd9576_wdt_priv *priv = watchdog_get_drvdata(wdd);
63b237bcacSMatti Vaittinen
64b237bcacSMatti Vaittinen bd9576_wdt_disable(priv);
65b237bcacSMatti Vaittinen
66b237bcacSMatti Vaittinen return 0;
67b237bcacSMatti Vaittinen }
68b237bcacSMatti Vaittinen
69b237bcacSMatti Vaittinen static const struct watchdog_info bd957x_wdt_ident = {
70b237bcacSMatti Vaittinen .options = WDIOF_MAGICCLOSE | WDIOF_KEEPALIVEPING |
71b237bcacSMatti Vaittinen WDIOF_SETTIMEOUT,
72b237bcacSMatti Vaittinen .identity = "BD957x Watchdog",
73b237bcacSMatti Vaittinen };
74b237bcacSMatti Vaittinen
75b237bcacSMatti Vaittinen static const struct watchdog_ops bd957x_wdt_ops = {
76b237bcacSMatti Vaittinen .owner = THIS_MODULE,
77b237bcacSMatti Vaittinen .start = bd9576_wdt_start,
78b237bcacSMatti Vaittinen .stop = bd9576_wdt_stop,
79b237bcacSMatti Vaittinen .ping = bd9576_wdt_ping,
80b237bcacSMatti Vaittinen };
81b237bcacSMatti Vaittinen
82b237bcacSMatti Vaittinen /* Unit is hundreds of uS */
83b237bcacSMatti Vaittinen #define FASTNG_MIN 23
84b237bcacSMatti Vaittinen
find_closest_fast(int target,int * sel,int * val)85b237bcacSMatti Vaittinen static int find_closest_fast(int target, int *sel, int *val)
86b237bcacSMatti Vaittinen {
87b237bcacSMatti Vaittinen int i;
88b237bcacSMatti Vaittinen int window = FASTNG_MIN;
89b237bcacSMatti Vaittinen
90b237bcacSMatti Vaittinen for (i = 0; i < 8 && window < target; i++)
91b237bcacSMatti Vaittinen window <<= 1;
92b237bcacSMatti Vaittinen
93b237bcacSMatti Vaittinen *val = window;
94b237bcacSMatti Vaittinen *sel = i;
95b237bcacSMatti Vaittinen
96b237bcacSMatti Vaittinen if (i == 8)
97b237bcacSMatti Vaittinen return -EINVAL;
98b237bcacSMatti Vaittinen
99b237bcacSMatti Vaittinen return 0;
100b237bcacSMatti Vaittinen
101b237bcacSMatti Vaittinen }
102b237bcacSMatti Vaittinen
find_closest_slow_by_fast(int fast_val,int target,int * slowsel)103b237bcacSMatti Vaittinen static int find_closest_slow_by_fast(int fast_val, int target, int *slowsel)
104b237bcacSMatti Vaittinen {
105b237bcacSMatti Vaittinen int sel;
106b237bcacSMatti Vaittinen static const int multipliers[] = {2, 3, 7, 15};
107b237bcacSMatti Vaittinen
108b237bcacSMatti Vaittinen for (sel = 0; sel < ARRAY_SIZE(multipliers) &&
109b237bcacSMatti Vaittinen multipliers[sel] * fast_val < target; sel++)
110b237bcacSMatti Vaittinen ;
111b237bcacSMatti Vaittinen
112b237bcacSMatti Vaittinen if (sel == ARRAY_SIZE(multipliers))
113b237bcacSMatti Vaittinen return -EINVAL;
114b237bcacSMatti Vaittinen
115b237bcacSMatti Vaittinen *slowsel = sel;
116b237bcacSMatti Vaittinen
117b237bcacSMatti Vaittinen return 0;
118b237bcacSMatti Vaittinen }
119b237bcacSMatti Vaittinen
find_closest_slow(int target,int * slow_sel,int * fast_sel)120b237bcacSMatti Vaittinen static int find_closest_slow(int target, int *slow_sel, int *fast_sel)
121b237bcacSMatti Vaittinen {
122b237bcacSMatti Vaittinen static const int multipliers[] = {2, 3, 7, 15};
123b237bcacSMatti Vaittinen int i, j;
124b237bcacSMatti Vaittinen int val = 0;
125b237bcacSMatti Vaittinen int window = FASTNG_MIN;
126b237bcacSMatti Vaittinen
127b237bcacSMatti Vaittinen for (i = 0; i < 8; i++) {
128b237bcacSMatti Vaittinen for (j = 0; j < ARRAY_SIZE(multipliers); j++) {
129b237bcacSMatti Vaittinen int slow;
130b237bcacSMatti Vaittinen
131b237bcacSMatti Vaittinen slow = window * multipliers[j];
132b237bcacSMatti Vaittinen if (slow >= target && (!val || slow < val)) {
133b237bcacSMatti Vaittinen val = slow;
134b237bcacSMatti Vaittinen *fast_sel = i;
135b237bcacSMatti Vaittinen *slow_sel = j;
136b237bcacSMatti Vaittinen }
137b237bcacSMatti Vaittinen }
138b237bcacSMatti Vaittinen window <<= 1;
139b237bcacSMatti Vaittinen }
140b237bcacSMatti Vaittinen if (!val)
141b237bcacSMatti Vaittinen return -EINVAL;
142b237bcacSMatti Vaittinen
143b237bcacSMatti Vaittinen return 0;
144b237bcacSMatti Vaittinen }
145b237bcacSMatti Vaittinen
146b237bcacSMatti Vaittinen #define BD957X_WDG_TYPE_WINDOW BIT(5)
147b237bcacSMatti Vaittinen #define BD957X_WDG_TYPE_SLOW 0
148b237bcacSMatti Vaittinen #define BD957X_WDG_TYPE_MASK BIT(5)
149b237bcacSMatti Vaittinen #define BD957X_WDG_NG_RATIO_MASK 0x18
150b237bcacSMatti Vaittinen #define BD957X_WDG_FASTNG_MASK 0x7
151b237bcacSMatti Vaittinen
bd957x_set_wdt_mode(struct bd9576_wdt_priv * priv,int hw_margin,int hw_margin_min)152b237bcacSMatti Vaittinen static int bd957x_set_wdt_mode(struct bd9576_wdt_priv *priv, int hw_margin,
153b237bcacSMatti Vaittinen int hw_margin_min)
154b237bcacSMatti Vaittinen {
155b237bcacSMatti Vaittinen int ret, fastng, slowng, type, reg, mask;
156b237bcacSMatti Vaittinen struct device *dev = priv->dev;
157b237bcacSMatti Vaittinen
158b237bcacSMatti Vaittinen /* convert to 100uS */
159b237bcacSMatti Vaittinen hw_margin *= 10;
160b237bcacSMatti Vaittinen hw_margin_min *= 10;
161b237bcacSMatti Vaittinen if (hw_margin_min) {
162b237bcacSMatti Vaittinen int min;
163b237bcacSMatti Vaittinen
164b237bcacSMatti Vaittinen type = BD957X_WDG_TYPE_WINDOW;
165b237bcacSMatti Vaittinen dev_dbg(dev, "Setting type WINDOW 0x%x\n", type);
166b237bcacSMatti Vaittinen ret = find_closest_fast(hw_margin_min, &fastng, &min);
167b237bcacSMatti Vaittinen if (ret) {
168b237bcacSMatti Vaittinen dev_err(dev, "bad WDT window for fast timeout\n");
169b237bcacSMatti Vaittinen return ret;
170b237bcacSMatti Vaittinen }
171b237bcacSMatti Vaittinen
172b237bcacSMatti Vaittinen ret = find_closest_slow_by_fast(min, hw_margin, &slowng);
173b237bcacSMatti Vaittinen if (ret) {
174b237bcacSMatti Vaittinen dev_err(dev, "bad WDT window\n");
175b237bcacSMatti Vaittinen return ret;
176b237bcacSMatti Vaittinen }
177b237bcacSMatti Vaittinen
178b237bcacSMatti Vaittinen } else {
179b237bcacSMatti Vaittinen type = BD957X_WDG_TYPE_SLOW;
180b237bcacSMatti Vaittinen dev_dbg(dev, "Setting type SLOW 0x%x\n", type);
181b237bcacSMatti Vaittinen ret = find_closest_slow(hw_margin, &slowng, &fastng);
182b237bcacSMatti Vaittinen if (ret) {
183b237bcacSMatti Vaittinen dev_err(dev, "bad WDT window\n");
184b237bcacSMatti Vaittinen return ret;
185b237bcacSMatti Vaittinen }
186b237bcacSMatti Vaittinen }
187b237bcacSMatti Vaittinen
188b237bcacSMatti Vaittinen slowng <<= ffs(BD957X_WDG_NG_RATIO_MASK) - 1;
189b237bcacSMatti Vaittinen reg = type | slowng | fastng;
190b237bcacSMatti Vaittinen mask = BD957X_WDG_TYPE_MASK | BD957X_WDG_NG_RATIO_MASK |
191b237bcacSMatti Vaittinen BD957X_WDG_FASTNG_MASK;
192b237bcacSMatti Vaittinen ret = regmap_update_bits(priv->regmap, BD957X_REG_WDT_CONF,
193b237bcacSMatti Vaittinen mask, reg);
194b237bcacSMatti Vaittinen
195b237bcacSMatti Vaittinen return ret;
196b237bcacSMatti Vaittinen }
197b237bcacSMatti Vaittinen
bd9576_wdt_probe(struct platform_device * pdev)198b237bcacSMatti Vaittinen static int bd9576_wdt_probe(struct platform_device *pdev)
199b237bcacSMatti Vaittinen {
200b237bcacSMatti Vaittinen struct device *dev = &pdev->dev;
201b237bcacSMatti Vaittinen struct bd9576_wdt_priv *priv;
202b237bcacSMatti Vaittinen u32 hw_margin[2];
203b237bcacSMatti Vaittinen u32 hw_margin_max = BD957X_WDT_DEFAULT_MARGIN, hw_margin_min = 0;
204*4f719022SDmitry Torokhov int count;
205b237bcacSMatti Vaittinen int ret;
206b237bcacSMatti Vaittinen
207b237bcacSMatti Vaittinen priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
208b237bcacSMatti Vaittinen if (!priv)
209b237bcacSMatti Vaittinen return -ENOMEM;
210b237bcacSMatti Vaittinen
211b237bcacSMatti Vaittinen platform_set_drvdata(pdev, priv);
212b237bcacSMatti Vaittinen
213b237bcacSMatti Vaittinen priv->dev = dev;
214b237bcacSMatti Vaittinen priv->regmap = dev_get_regmap(dev->parent, NULL);
215b237bcacSMatti Vaittinen if (!priv->regmap) {
216b237bcacSMatti Vaittinen dev_err(dev, "No regmap found\n");
217b237bcacSMatti Vaittinen return -ENODEV;
218b237bcacSMatti Vaittinen }
219b237bcacSMatti Vaittinen
220*4f719022SDmitry Torokhov priv->gpiod_en = devm_fwnode_gpiod_get(dev, dev_fwnode(dev->parent),
221*4f719022SDmitry Torokhov "rohm,watchdog-enable",
222*4f719022SDmitry Torokhov GPIOD_OUT_LOW,
223b237bcacSMatti Vaittinen "watchdog-enable");
224b237bcacSMatti Vaittinen if (IS_ERR(priv->gpiod_en))
225b237bcacSMatti Vaittinen return dev_err_probe(dev, PTR_ERR(priv->gpiod_en),
226b237bcacSMatti Vaittinen "getting watchdog-enable GPIO failed\n");
227b237bcacSMatti Vaittinen
228*4f719022SDmitry Torokhov priv->gpiod_ping = devm_fwnode_gpiod_get(dev, dev_fwnode(dev->parent),
229*4f719022SDmitry Torokhov "rohm,watchdog-ping",
230*4f719022SDmitry Torokhov GPIOD_OUT_LOW,
231b237bcacSMatti Vaittinen "watchdog-ping");
232b237bcacSMatti Vaittinen if (IS_ERR(priv->gpiod_ping))
233b237bcacSMatti Vaittinen return dev_err_probe(dev, PTR_ERR(priv->gpiod_ping),
234b237bcacSMatti Vaittinen "getting watchdog-ping GPIO failed\n");
235b237bcacSMatti Vaittinen
236*4f719022SDmitry Torokhov count = device_property_count_u32(dev->parent, "rohm,hw-timeout-ms");
237*4f719022SDmitry Torokhov if (count < 0 && count != -EINVAL)
238*4f719022SDmitry Torokhov return count;
239*4f719022SDmitry Torokhov
240*4f719022SDmitry Torokhov if (count > 0) {
241*4f719022SDmitry Torokhov if (count > ARRAY_SIZE(hw_margin))
242*4f719022SDmitry Torokhov return -EINVAL;
243*4f719022SDmitry Torokhov
244*4f719022SDmitry Torokhov ret = device_property_read_u32_array(dev->parent,
245*4f719022SDmitry Torokhov "rohm,hw-timeout-ms",
246*4f719022SDmitry Torokhov hw_margin, count);
247*4f719022SDmitry Torokhov if (ret < 0)
248b237bcacSMatti Vaittinen return ret;
249b237bcacSMatti Vaittinen
250*4f719022SDmitry Torokhov if (count == 1)
251b237bcacSMatti Vaittinen hw_margin_max = hw_margin[0];
252b237bcacSMatti Vaittinen
253*4f719022SDmitry Torokhov if (count == 2) {
254b237bcacSMatti Vaittinen hw_margin_max = hw_margin[1];
255b237bcacSMatti Vaittinen hw_margin_min = hw_margin[0];
256b237bcacSMatti Vaittinen }
257*4f719022SDmitry Torokhov }
258b237bcacSMatti Vaittinen
259b237bcacSMatti Vaittinen ret = bd957x_set_wdt_mode(priv, hw_margin_max, hw_margin_min);
260b237bcacSMatti Vaittinen if (ret)
261b237bcacSMatti Vaittinen return ret;
262b237bcacSMatti Vaittinen
263b237bcacSMatti Vaittinen watchdog_set_drvdata(&priv->wdd, priv);
264b237bcacSMatti Vaittinen
265b237bcacSMatti Vaittinen priv->wdd.info = &bd957x_wdt_ident;
266b237bcacSMatti Vaittinen priv->wdd.ops = &bd957x_wdt_ops;
267b237bcacSMatti Vaittinen priv->wdd.min_hw_heartbeat_ms = hw_margin_min;
268b237bcacSMatti Vaittinen priv->wdd.max_hw_heartbeat_ms = hw_margin_max;
269b237bcacSMatti Vaittinen priv->wdd.parent = dev;
270b237bcacSMatti Vaittinen priv->wdd.timeout = WATCHDOG_TIMEOUT;
271b237bcacSMatti Vaittinen
272b237bcacSMatti Vaittinen watchdog_init_timeout(&priv->wdd, 0, dev);
273b237bcacSMatti Vaittinen watchdog_set_nowayout(&priv->wdd, nowayout);
274b237bcacSMatti Vaittinen
275b237bcacSMatti Vaittinen watchdog_stop_on_reboot(&priv->wdd);
276b237bcacSMatti Vaittinen
277b237bcacSMatti Vaittinen return devm_watchdog_register_device(dev, &priv->wdd);
278b237bcacSMatti Vaittinen }
279b237bcacSMatti Vaittinen
280b237bcacSMatti Vaittinen static struct platform_driver bd9576_wdt_driver = {
281b237bcacSMatti Vaittinen .driver = {
282b237bcacSMatti Vaittinen .name = "bd9576-wdt",
283b237bcacSMatti Vaittinen },
284b237bcacSMatti Vaittinen .probe = bd9576_wdt_probe,
285b237bcacSMatti Vaittinen };
286b237bcacSMatti Vaittinen
287b237bcacSMatti Vaittinen module_platform_driver(bd9576_wdt_driver);
288b237bcacSMatti Vaittinen
289b237bcacSMatti Vaittinen MODULE_AUTHOR("Matti Vaittinen <matti.vaittinen@fi.rohmeurope.com>");
290b237bcacSMatti Vaittinen MODULE_DESCRIPTION("ROHM BD9576/BD9573 Watchdog driver");
291b237bcacSMatti Vaittinen MODULE_LICENSE("GPL");
292b237bcacSMatti Vaittinen MODULE_ALIAS("platform:bd9576-wdt");
293