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>
12 #include <linux/platform_device.h>
13 #include <linux/property.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
;
32 struct watchdog_device wdd
;
35 static void bd9576_wdt_disable(struct bd9576_wdt_priv
*priv
)
37 gpiod_set_value_cansleep(priv
->gpiod_en
, 0);
40 static int bd9576_wdt_ping(struct watchdog_device
*wdd
)
42 struct bd9576_wdt_priv
*priv
= watchdog_get_drvdata(wdd
);
45 gpiod_set_value_cansleep(priv
->gpiod_ping
, 1);
46 gpiod_set_value_cansleep(priv
->gpiod_ping
, 0);
51 static int bd9576_wdt_start(struct watchdog_device
*wdd
)
53 struct bd9576_wdt_priv
*priv
= watchdog_get_drvdata(wdd
);
55 gpiod_set_value_cansleep(priv
->gpiod_en
, 1);
57 return bd9576_wdt_ping(wdd
);
60 static int bd9576_wdt_stop(struct watchdog_device
*wdd
)
62 struct bd9576_wdt_priv
*priv
= watchdog_get_drvdata(wdd
);
64 bd9576_wdt_disable(priv
);
69 static const struct watchdog_info bd957x_wdt_ident
= {
70 .options
= WDIOF_MAGICCLOSE
| WDIOF_KEEPALIVEPING
|
72 .identity
= "BD957x Watchdog",
75 static const struct watchdog_ops bd957x_wdt_ops
= {
77 .start
= bd9576_wdt_start
,
78 .stop
= bd9576_wdt_stop
,
79 .ping
= bd9576_wdt_ping
,
82 /* Unit is hundreds of uS */
85 static int find_closest_fast(int target
, int *sel
, int *val
)
88 int window
= FASTNG_MIN
;
90 for (i
= 0; i
< 8 && window
< target
; i
++)
103 static int find_closest_slow_by_fast(int fast_val
, int target
, int *slowsel
)
106 static const int multipliers
[] = {2, 3, 7, 15};
108 for (sel
= 0; sel
< ARRAY_SIZE(multipliers
) &&
109 multipliers
[sel
] * fast_val
< target
; sel
++)
112 if (sel
== ARRAY_SIZE(multipliers
))
120 static int find_closest_slow(int target
, int *slow_sel
, int *fast_sel
)
122 static const int multipliers
[] = {2, 3, 7, 15};
125 int window
= FASTNG_MIN
;
127 for (i
= 0; i
< 8; i
++) {
128 for (j
= 0; j
< ARRAY_SIZE(multipliers
); j
++) {
131 slow
= window
* multipliers
[j
];
132 if (slow
>= target
&& (!val
|| slow
< val
)) {
146 #define BD957X_WDG_TYPE_WINDOW BIT(5)
147 #define BD957X_WDG_TYPE_SLOW 0
148 #define BD957X_WDG_TYPE_MASK BIT(5)
149 #define BD957X_WDG_NG_RATIO_MASK 0x18
150 #define BD957X_WDG_FASTNG_MASK 0x7
152 static int bd957x_set_wdt_mode(struct bd9576_wdt_priv
*priv
, int hw_margin
,
155 int ret
, fastng
, slowng
, type
, reg
, mask
;
156 struct device
*dev
= priv
->dev
;
158 /* convert to 100uS */
164 type
= BD957X_WDG_TYPE_WINDOW
;
165 dev_dbg(dev
, "Setting type WINDOW 0x%x\n", type
);
166 ret
= find_closest_fast(hw_margin_min
, &fastng
, &min
);
168 dev_err(dev
, "bad WDT window for fast timeout\n");
172 ret
= find_closest_slow_by_fast(min
, hw_margin
, &slowng
);
174 dev_err(dev
, "bad WDT window\n");
179 type
= BD957X_WDG_TYPE_SLOW
;
180 dev_dbg(dev
, "Setting type SLOW 0x%x\n", type
);
181 ret
= find_closest_slow(hw_margin
, &slowng
, &fastng
);
183 dev_err(dev
, "bad WDT window\n");
188 slowng
<<= ffs(BD957X_WDG_NG_RATIO_MASK
) - 1;
189 reg
= type
| slowng
| fastng
;
190 mask
= BD957X_WDG_TYPE_MASK
| BD957X_WDG_NG_RATIO_MASK
|
191 BD957X_WDG_FASTNG_MASK
;
192 ret
= regmap_update_bits(priv
->regmap
, BD957X_REG_WDT_CONF
,
198 static int bd9576_wdt_probe(struct platform_device
*pdev
)
200 struct device
*dev
= &pdev
->dev
;
201 struct bd9576_wdt_priv
*priv
;
203 u32 hw_margin_max
= BD957X_WDT_DEFAULT_MARGIN
, hw_margin_min
= 0;
207 priv
= devm_kzalloc(dev
, sizeof(*priv
), GFP_KERNEL
);
211 platform_set_drvdata(pdev
, priv
);
214 priv
->regmap
= dev_get_regmap(dev
->parent
, NULL
);
216 dev_err(dev
, "No regmap found\n");
220 priv
->gpiod_en
= devm_fwnode_gpiod_get(dev
, dev_fwnode(dev
->parent
),
221 "rohm,watchdog-enable",
224 if (IS_ERR(priv
->gpiod_en
))
225 return dev_err_probe(dev
, PTR_ERR(priv
->gpiod_en
),
226 "getting watchdog-enable GPIO failed\n");
228 priv
->gpiod_ping
= devm_fwnode_gpiod_get(dev
, dev_fwnode(dev
->parent
),
229 "rohm,watchdog-ping",
232 if (IS_ERR(priv
->gpiod_ping
))
233 return dev_err_probe(dev
, PTR_ERR(priv
->gpiod_ping
),
234 "getting watchdog-ping GPIO failed\n");
236 count
= device_property_count_u32(dev
->parent
, "rohm,hw-timeout-ms");
237 if (count
< 0 && count
!= -EINVAL
)
241 if (count
> ARRAY_SIZE(hw_margin
))
244 ret
= device_property_read_u32_array(dev
->parent
,
245 "rohm,hw-timeout-ms",
251 hw_margin_max
= hw_margin
[0];
254 hw_margin_max
= hw_margin
[1];
255 hw_margin_min
= hw_margin
[0];
259 ret
= bd957x_set_wdt_mode(priv
, hw_margin_max
, hw_margin_min
);
263 watchdog_set_drvdata(&priv
->wdd
, priv
);
265 priv
->wdd
.info
= &bd957x_wdt_ident
;
266 priv
->wdd
.ops
= &bd957x_wdt_ops
;
267 priv
->wdd
.min_hw_heartbeat_ms
= hw_margin_min
;
268 priv
->wdd
.max_hw_heartbeat_ms
= hw_margin_max
;
269 priv
->wdd
.parent
= dev
;
270 priv
->wdd
.timeout
= WATCHDOG_TIMEOUT
;
272 watchdog_init_timeout(&priv
->wdd
, 0, dev
);
273 watchdog_set_nowayout(&priv
->wdd
, nowayout
);
275 watchdog_stop_on_reboot(&priv
->wdd
);
277 return devm_watchdog_register_device(dev
, &priv
->wdd
);
280 static struct platform_driver bd9576_wdt_driver
= {
282 .name
= "bd9576-wdt",
284 .probe
= bd9576_wdt_probe
,
287 module_platform_driver(bd9576_wdt_driver
);
289 MODULE_AUTHOR("Matti Vaittinen <matti.vaittinen@fi.rohmeurope.com>");
290 MODULE_DESCRIPTION("ROHM BD9576/BD9573 Watchdog driver");
291 MODULE_LICENSE("GPL");
292 MODULE_ALIAS("platform:bd9576-wdt");