xref: /openbmc/linux/drivers/watchdog/bd9576_wdt.c (revision b181f7029bd71238ac2754ce7052dffd69432085)
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