Linux Audio

Check our new training course

Linux debugging, profiling, tracing and performance analysis training

Mar 24-27, 2025, special US time zones
Register
Loading...
Note: File does not exist in v4.6.
  1// SPDX-License-Identifier: GPL-2.0-only
  2/*
  3 * Copyright (C) 2024 ROHM Semiconductors
  4 *
  5 * ROHM BD96801 watchdog driver
  6 */
  7
  8#include <linux/bitfield.h>
  9#include <linux/interrupt.h>
 10#include <linux/kernel.h>
 11#include <linux/mfd/rohm-bd96801.h>
 12#include <linux/mfd/rohm-generic.h>
 13#include <linux/module.h>
 14#include <linux/of.h>
 15#include <linux/platform_device.h>
 16#include <linux/reboot.h>
 17#include <linux/regmap.h>
 18#include <linux/watchdog.h>
 19
 20static bool nowayout;
 21module_param(nowayout, bool, 0);
 22MODULE_PARM_DESC(nowayout,
 23		"Watchdog cannot be stopped once started (default=\"false\")");
 24
 25#define BD96801_WD_TMO_SHORT_MASK	0x70
 26#define BD96801_WD_RATIO_MASK		0x3
 27#define BD96801_WD_TYPE_MASK		0x4
 28#define BD96801_WD_TYPE_SLOW		0x4
 29#define BD96801_WD_TYPE_WIN		0x0
 30
 31#define BD96801_WD_EN_MASK		0x3
 32#define BD96801_WD_IF_EN		0x1
 33#define BD96801_WD_QA_EN		0x2
 34#define BD96801_WD_DISABLE		0x0
 35
 36#define BD96801_WD_ASSERT_MASK		0x8
 37#define BD96801_WD_ASSERT_RST		0x8
 38#define BD96801_WD_ASSERT_IRQ		0x0
 39
 40#define BD96801_WD_FEED_MASK		0x1
 41#define BD96801_WD_FEED			0x1
 42
 43/* 1.1 mS */
 44#define FASTNG_MIN			11
 45#define FASTNG_MAX_US			(100 * FASTNG_MIN << 7)
 46#define SLOWNG_MAX_US			(16 * FASTNG_MAX_US)
 47
 48#define BD96801_WDT_DEFAULT_MARGIN_MS	1843
 49/* Unit is seconds */
 50#define DEFAULT_TIMEOUT 30
 51
 52/*
 53 * BD96801 WDG supports window mode so the TMO consists of SHORT and LONG
 54 * timeout values. SHORT time is meaningful only in window mode where feeding
 55 * period shorter than SHORT would be an error. LONG time is used to detect if
 56 * feeding is not occurring within given time limit (SoC SW hangs). The LONG
 57 * timeout time is a multiple of (2, 4, 8 or 16 times) the SHORT timeout.
 58 */
 59
 60struct wdtbd96801 {
 61	struct device		*dev;
 62	struct regmap		*regmap;
 63	struct watchdog_device	wdt;
 64};
 65
 66static int bd96801_wdt_ping(struct watchdog_device *wdt)
 67{
 68	struct wdtbd96801 *w = watchdog_get_drvdata(wdt);
 69
 70	return regmap_update_bits(w->regmap, BD96801_REG_WD_FEED,
 71				  BD96801_WD_FEED_MASK, BD96801_WD_FEED);
 72}
 73
 74static int bd96801_wdt_start(struct watchdog_device *wdt)
 75{
 76	struct wdtbd96801 *w = watchdog_get_drvdata(wdt);
 77
 78	return regmap_update_bits(w->regmap, BD96801_REG_WD_CONF,
 79				  BD96801_WD_EN_MASK, BD96801_WD_IF_EN);
 80}
 81
 82static int bd96801_wdt_stop(struct watchdog_device *wdt)
 83{
 84	struct wdtbd96801 *w = watchdog_get_drvdata(wdt);
 85
 86	return regmap_update_bits(w->regmap, BD96801_REG_WD_CONF,
 87				  BD96801_WD_EN_MASK, BD96801_WD_DISABLE);
 88}
 89
 90static const struct watchdog_info bd96801_wdt_info = {
 91	.options	= WDIOF_MAGICCLOSE | WDIOF_KEEPALIVEPING |
 92			  WDIOF_SETTIMEOUT,
 93	.identity	= "BD96801 Watchdog",
 94};
 95
 96static const struct watchdog_ops bd96801_wdt_ops = {
 97	.start		= bd96801_wdt_start,
 98	.stop		= bd96801_wdt_stop,
 99	.ping		= bd96801_wdt_ping,
100};
101
102static int find_closest_fast(unsigned int target, int *sel, unsigned int *val)
103{
104	unsigned int window = FASTNG_MIN;
105	int i;
106
107	for (i = 0; i < 8 && window < target; i++)
108		window <<= 1;
109
110	if (i == 8)
111		return -EINVAL;
112
113	*val = window;
114	*sel = i;
115
116	return 0;
117}
118
119static int find_closest_slow_by_fast(unsigned int fast_val, unsigned int *target,
120				     int *slowsel)
121{
122	static const int multipliers[] = {2, 4, 8, 16};
123	int sel;
124
125	for (sel = 0; sel < ARRAY_SIZE(multipliers) &&
126	     multipliers[sel] * fast_val < *target; sel++)
127		;
128
129	if (sel == ARRAY_SIZE(multipliers))
130		return -EINVAL;
131
132	*slowsel = sel;
133	*target = multipliers[sel] * fast_val;
134
135	return 0;
136}
137
138static int find_closest_slow(unsigned int *target, int *slow_sel, int *fast_sel)
139{
140	static const int multipliers[] = {2, 4, 8, 16};
141	unsigned int window = FASTNG_MIN;
142	unsigned int val = 0;
143	int i, j;
144
145	for (i = 0; i < 8; i++) {
146		for (j = 0; j < ARRAY_SIZE(multipliers); j++) {
147			unsigned int slow;
148
149			slow = window * multipliers[j];
150			if (slow >= *target && (!val || slow < val)) {
151				val = slow;
152				*fast_sel = i;
153				*slow_sel = j;
154			}
155		}
156		window <<= 1;
157	}
158	if (!val)
159		return -EINVAL;
160
161	*target = val;
162
163	return 0;
164}
165
166static int bd96801_set_wdt_mode(struct wdtbd96801 *w, unsigned int hw_margin,
167			       unsigned int hw_margin_min)
168{
169	int fastng, slowng, type, ret, reg, mask;
170	struct device *dev = w->dev;
171
172
173	if (hw_margin_min * 1000 > FASTNG_MAX_US) {
174		dev_err(dev, "Unsupported fast timeout %u uS [max %u]\n",
175			hw_margin_min * 1000, FASTNG_MAX_US);
176
177		return -EINVAL;
178	}
179
180	if (hw_margin * 1000 > SLOWNG_MAX_US) {
181		dev_err(dev, "Unsupported slow timeout %u uS [max %u]\n",
182			hw_margin * 1000, SLOWNG_MAX_US);
183
184		return -EINVAL;
185	}
186
187	/*
188	 * Convert to 100uS to guarantee reasonable timeouts fit in
189	 * 32bit maintaining also a decent accuracy.
190	 */
191	hw_margin *= 10;
192	hw_margin_min *= 10;
193
194	if (hw_margin_min) {
195		unsigned int min;
196
197		type = BD96801_WD_TYPE_WIN;
198		dev_dbg(dev, "Setting type WINDOW 0x%x\n", type);
199		ret = find_closest_fast(hw_margin_min, &fastng, &min);
200		if (ret)
201			return ret;
202
203		ret = find_closest_slow_by_fast(min, &hw_margin, &slowng);
204		if (ret) {
205			dev_err(dev,
206				"can't support slow timeout %u uS using fast %u uS. [max slow %u uS]\n",
207				hw_margin * 100, min * 100, min * 100 * 16);
208
209			return ret;
210		}
211		w->wdt.min_hw_heartbeat_ms = min / 10;
212	} else {
213		type = BD96801_WD_TYPE_SLOW;
214		dev_dbg(dev, "Setting type SLOW 0x%x\n", type);
215		ret = find_closest_slow(&hw_margin, &slowng, &fastng);
216		if (ret)
217			return ret;
218	}
219
220	w->wdt.max_hw_heartbeat_ms = hw_margin / 10;
221
222	fastng = FIELD_PREP(BD96801_WD_TMO_SHORT_MASK, fastng);
223
224	reg = slowng | fastng;
225	mask = BD96801_WD_RATIO_MASK | BD96801_WD_TMO_SHORT_MASK;
226	ret = regmap_update_bits(w->regmap, BD96801_REG_WD_TMO,
227				 mask, reg);
228	if (ret)
229		return ret;
230
231	ret = regmap_update_bits(w->regmap, BD96801_REG_WD_CONF,
232				 BD96801_WD_TYPE_MASK, type);
233
234	return ret;
235}
236
237static int bd96801_set_heartbeat_from_hw(struct wdtbd96801 *w,
238					 unsigned int conf_reg)
239{
240	int ret;
241	unsigned int val, sel, fast;
242
243	/*
244	 * The BD96801 supports a somewhat peculiar QA-mode, which we do not
245	 * support in this driver. If the QA-mode is enabled then we just
246	 * warn and bail-out.
247	 */
248	if ((conf_reg & BD96801_WD_EN_MASK) != BD96801_WD_IF_EN) {
249		dev_err(w->dev, "watchdog set to Q&A mode - exiting\n");
250		return -EINVAL;
251	}
252
253	ret = regmap_read(w->regmap, BD96801_REG_WD_TMO, &val);
254	if (ret)
255		return ret;
256
257	sel = FIELD_GET(BD96801_WD_TMO_SHORT_MASK, val);
258	fast = FASTNG_MIN << sel;
259
260	sel = (val & BD96801_WD_RATIO_MASK) + 1;
261	w->wdt.max_hw_heartbeat_ms = (fast << sel) / USEC_PER_MSEC;
262
263	if ((conf_reg & BD96801_WD_TYPE_MASK) == BD96801_WD_TYPE_WIN)
264		w->wdt.min_hw_heartbeat_ms = fast / USEC_PER_MSEC;
265
266	return 0;
267}
268
269static int init_wdg_hw(struct wdtbd96801 *w)
270{
271	u32 hw_margin[2];
272	int count, ret;
273	u32 hw_margin_max = BD96801_WDT_DEFAULT_MARGIN_MS, hw_margin_min = 0;
274
275	count = device_property_count_u32(w->dev->parent, "rohm,hw-timeout-ms");
276	if (count < 0 && count != -EINVAL)
277		return count;
278
279	if (count > 0) {
280		if (count > ARRAY_SIZE(hw_margin))
281			return -EINVAL;
282
283		ret = device_property_read_u32_array(w->dev->parent,
284						     "rohm,hw-timeout-ms",
285						     &hw_margin[0], count);
286		if (ret < 0)
287			return ret;
288
289		if (count == 1)
290			hw_margin_max = hw_margin[0];
291
292		if (count == 2) {
293			if (hw_margin[1] > hw_margin[0]) {
294				hw_margin_max = hw_margin[1];
295				hw_margin_min = hw_margin[0];
296			} else {
297				hw_margin_max = hw_margin[0];
298				hw_margin_min = hw_margin[1];
299			}
300		}
301	}
302
303	ret = bd96801_set_wdt_mode(w, hw_margin_max, hw_margin_min);
304	if (ret)
305		return ret;
306
307	ret = device_property_match_string(w->dev->parent, "rohm,wdg-action",
308					   "prstb");
309	if (ret >= 0) {
310		ret = regmap_update_bits(w->regmap, BD96801_REG_WD_CONF,
311				 BD96801_WD_ASSERT_MASK,
312				 BD96801_WD_ASSERT_RST);
313		return ret;
314	}
315
316	ret = device_property_match_string(w->dev->parent, "rohm,wdg-action",
317					   "intb-only");
318	if (ret >= 0) {
319		ret = regmap_update_bits(w->regmap, BD96801_REG_WD_CONF,
320				 BD96801_WD_ASSERT_MASK,
321				 BD96801_WD_ASSERT_IRQ);
322		return ret;
323	}
324
325	return 0;
326}
327
328static irqreturn_t bd96801_irq_hnd(int irq, void *data)
329{
330	emergency_restart();
331
332	return IRQ_NONE;
333}
334
335static int bd96801_wdt_probe(struct platform_device *pdev)
336{
337	struct wdtbd96801 *w;
338	int ret, irq;
339	unsigned int val;
340
341	w = devm_kzalloc(&pdev->dev, sizeof(*w), GFP_KERNEL);
342	if (!w)
343		return -ENOMEM;
344
345	w->regmap = dev_get_regmap(pdev->dev.parent, NULL);
346	w->dev = &pdev->dev;
347
348	w->wdt.info = &bd96801_wdt_info;
349	w->wdt.ops =  &bd96801_wdt_ops;
350	w->wdt.parent = pdev->dev.parent;
351	w->wdt.timeout = DEFAULT_TIMEOUT;
352	watchdog_set_drvdata(&w->wdt, w);
353
354	ret = regmap_read(w->regmap, BD96801_REG_WD_CONF, &val);
355	if (ret)
356		return dev_err_probe(&pdev->dev, ret,
357				     "Failed to get the watchdog state\n");
358
359	/*
360	 * If the WDG is already enabled we assume it is configured by boot.
361	 * In this case we just update the hw-timeout based on values set to
362	 * the timeout / mode registers and leave the hardware configs
363	 * untouched.
364	 */
365	if ((val & BD96801_WD_EN_MASK) != BD96801_WD_DISABLE) {
366		dev_dbg(&pdev->dev, "watchdog was running during probe\n");
367		ret = bd96801_set_heartbeat_from_hw(w, val);
368		if (ret)
369			return ret;
370
371		set_bit(WDOG_HW_RUNNING, &w->wdt.status);
372	} else {
373		/* If WDG is not running so we will initializate it */
374		ret = init_wdg_hw(w);
375		if (ret)
376			return ret;
377	}
378
379	dev_dbg(w->dev, "heartbeat set to %u - %u\n",
380		w->wdt.min_hw_heartbeat_ms, w->wdt.max_hw_heartbeat_ms);
381
382	watchdog_init_timeout(&w->wdt, 0, pdev->dev.parent);
383	watchdog_set_nowayout(&w->wdt, nowayout);
384	watchdog_stop_on_reboot(&w->wdt);
385
386	irq = platform_get_irq_byname(pdev, "bd96801-wdg");
387	if (irq > 0) {
388		ret = devm_request_threaded_irq(&pdev->dev, irq, NULL,
389						bd96801_irq_hnd,
390						IRQF_ONESHOT,  "bd96801-wdg",
391						NULL);
392		if (ret)
393			return dev_err_probe(&pdev->dev, ret,
394					     "Failed to register IRQ\n");
395	}
396
397	return devm_watchdog_register_device(&pdev->dev, &w->wdt);
398}
399
400static const struct platform_device_id bd96801_wdt_id[] = {
401	{ "bd96801-wdt", },
402	{ }
403};
404MODULE_DEVICE_TABLE(platform, bd96801_wdt_id);
405
406static struct platform_driver bd96801_wdt = {
407	.driver = {
408		.name = "bd96801-wdt"
409	},
410	.probe = bd96801_wdt_probe,
411	.id_table = bd96801_wdt_id,
412};
413module_platform_driver(bd96801_wdt);
414
415MODULE_AUTHOR("Matti Vaittinen <matti.vaittinen@fi.rohmeurope.com>");
416MODULE_DESCRIPTION("BD96801 watchdog driver");
417MODULE_LICENSE("GPL");