1 // SPDX-License-Identifier: GPL-2.0-or-later
3 * Copyright (C) 2020 ROHM Semiconductors
5 * ROHM BD9576MUF and BD9573MUF Watchdog driver
9 #include <linux/gpio/consumer.h>
10 #include <linux/mfd/rohm-bd957x.h>
11 #include <linux/module.h>
13 #include <linux/platform_device.h>
14 #include <linux/regmap.h>
15 #include <linux/watchdog.h>
18 module_param(nowayout, bool, 0);
19 MODULE_PARM_DESC(nowayout,
20 "Watchdog cannot be stopped once started (default=\"false\")");
22 #define HW_MARGIN_MIN 2
23 #define HW_MARGIN_MAX 4416
24 #define BD957X_WDT_DEFAULT_MARGIN 4416
25 #define WATCHDOG_TIMEOUT 30
27 struct bd9576_wdt_priv {
28 struct gpio_desc *gpiod_ping;
29 struct gpio_desc *gpiod_en;
31 struct regmap *regmap;
33 struct watchdog_device wdd;
36 static void bd9576_wdt_disable(struct bd9576_wdt_priv *priv)
38 gpiod_set_value_cansleep(priv->gpiod_en, 0);
41 static int bd9576_wdt_ping(struct watchdog_device *wdd)
43 struct bd9576_wdt_priv *priv = watchdog_get_drvdata(wdd);
46 gpiod_set_value_cansleep(priv->gpiod_ping, 1);
47 gpiod_set_value_cansleep(priv->gpiod_ping, 0);
52 static int bd9576_wdt_start(struct watchdog_device *wdd)
54 struct bd9576_wdt_priv *priv = watchdog_get_drvdata(wdd);
56 gpiod_set_value_cansleep(priv->gpiod_en, 1);
58 return bd9576_wdt_ping(wdd);
61 static int bd9576_wdt_stop(struct watchdog_device *wdd)
63 struct bd9576_wdt_priv *priv = watchdog_get_drvdata(wdd);
65 if (!priv->always_running)
66 bd9576_wdt_disable(priv);
68 set_bit(WDOG_HW_RUNNING, &wdd->status);
73 static const struct watchdog_info bd957x_wdt_ident = {
74 .options = WDIOF_MAGICCLOSE | WDIOF_KEEPALIVEPING |
76 .identity = "BD957x Watchdog",
79 static const struct watchdog_ops bd957x_wdt_ops = {
81 .start = bd9576_wdt_start,
82 .stop = bd9576_wdt_stop,
83 .ping = bd9576_wdt_ping,
86 /* Unit is hundreds of uS */
89 static int find_closest_fast(int target, int *sel, int *val)
92 int window = FASTNG_MIN;
94 for (i = 0; i < 8 && window < target; i++)
107 static int find_closest_slow_by_fast(int fast_val, int target, int *slowsel)
110 static const int multipliers[] = {2, 3, 7, 15};
112 for (sel = 0; sel < ARRAY_SIZE(multipliers) &&
113 multipliers[sel] * fast_val < target; sel++)
116 if (sel == ARRAY_SIZE(multipliers))
124 static int find_closest_slow(int target, int *slow_sel, int *fast_sel)
126 static const int multipliers[] = {2, 3, 7, 15};
129 int window = FASTNG_MIN;
131 for (i = 0; i < 8; i++) {
132 for (j = 0; j < ARRAY_SIZE(multipliers); j++) {
135 slow = window * multipliers[j];
136 if (slow >= target && (!val || slow < val)) {
150 #define BD957X_WDG_TYPE_WINDOW BIT(5)
151 #define BD957X_WDG_TYPE_SLOW 0
152 #define BD957X_WDG_TYPE_MASK BIT(5)
153 #define BD957X_WDG_NG_RATIO_MASK 0x18
154 #define BD957X_WDG_FASTNG_MASK 0x7
156 static int bd957x_set_wdt_mode(struct bd9576_wdt_priv *priv, int hw_margin,
159 int ret, fastng, slowng, type, reg, mask;
160 struct device *dev = priv->dev;
162 /* convert to 100uS */
168 type = BD957X_WDG_TYPE_WINDOW;
169 dev_dbg(dev, "Setting type WINDOW 0x%x\n", type);
170 ret = find_closest_fast(hw_margin_min, &fastng, &min);
172 dev_err(dev, "bad WDT window for fast timeout\n");
176 ret = find_closest_slow_by_fast(min, hw_margin, &slowng);
178 dev_err(dev, "bad WDT window\n");
183 type = BD957X_WDG_TYPE_SLOW;
184 dev_dbg(dev, "Setting type SLOW 0x%x\n", type);
185 ret = find_closest_slow(hw_margin, &slowng, &fastng);
187 dev_err(dev, "bad WDT window\n");
192 slowng <<= ffs(BD957X_WDG_NG_RATIO_MASK) - 1;
193 reg = type | slowng | fastng;
194 mask = BD957X_WDG_TYPE_MASK | BD957X_WDG_NG_RATIO_MASK |
195 BD957X_WDG_FASTNG_MASK;
196 ret = regmap_update_bits(priv->regmap, BD957X_REG_WDT_CONF,
202 static int bd9576_wdt_probe(struct platform_device *pdev)
204 struct device *dev = &pdev->dev;
205 struct device_node *np = dev->parent->of_node;
206 struct bd9576_wdt_priv *priv;
208 u32 hw_margin_max = BD957X_WDT_DEFAULT_MARGIN, hw_margin_min = 0;
211 priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
215 platform_set_drvdata(pdev, priv);
218 priv->regmap = dev_get_regmap(dev->parent, NULL);
220 dev_err(dev, "No regmap found\n");
224 priv->gpiod_en = devm_gpiod_get_from_of_node(dev, dev->parent->of_node,
225 "rohm,watchdog-enable-gpios",
228 if (IS_ERR(priv->gpiod_en))
229 return dev_err_probe(dev, PTR_ERR(priv->gpiod_en),
230 "getting watchdog-enable GPIO failed\n");
232 priv->gpiod_ping = devm_gpiod_get_from_of_node(dev, dev->parent->of_node,
233 "rohm,watchdog-ping-gpios",
236 if (IS_ERR(priv->gpiod_ping))
237 return dev_err_probe(dev, PTR_ERR(priv->gpiod_ping),
238 "getting watchdog-ping GPIO failed\n");
240 ret = of_property_read_variable_u32_array(np, "rohm,hw-timeout-ms",
241 &hw_margin[0], 1, 2);
242 if (ret < 0 && ret != -EINVAL)
246 hw_margin_max = hw_margin[0];
249 hw_margin_max = hw_margin[1];
250 hw_margin_min = hw_margin[0];
253 ret = bd957x_set_wdt_mode(priv, hw_margin_max, hw_margin_min);
257 priv->always_running = of_property_read_bool(np, "always-running");
259 watchdog_set_drvdata(&priv->wdd, priv);
261 priv->wdd.info = &bd957x_wdt_ident;
262 priv->wdd.ops = &bd957x_wdt_ops;
263 priv->wdd.min_hw_heartbeat_ms = hw_margin_min;
264 priv->wdd.max_hw_heartbeat_ms = hw_margin_max;
265 priv->wdd.parent = dev;
266 priv->wdd.timeout = WATCHDOG_TIMEOUT;
268 watchdog_init_timeout(&priv->wdd, 0, dev);
269 watchdog_set_nowayout(&priv->wdd, nowayout);
271 watchdog_stop_on_reboot(&priv->wdd);
273 if (priv->always_running)
274 bd9576_wdt_start(&priv->wdd);
276 return devm_watchdog_register_device(dev, &priv->wdd);
279 static struct platform_driver bd9576_wdt_driver = {
281 .name = "bd9576-wdt",
283 .probe = bd9576_wdt_probe,
286 module_platform_driver(bd9576_wdt_driver);
288 MODULE_AUTHOR("Matti Vaittinen <matti.vaittinen@fi.rohmeurope.com>");
289 MODULE_DESCRIPTION("ROHM BD9576/BD9573 Watchdog driver");
290 MODULE_LICENSE("GPL");
291 MODULE_ALIAS("platform:bd9576-wdt");