brintos

brintos / linux-shallow public Read only

0
0
Text · 10.0 KiB · 12b74fd Raw
418 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 * Copyright (C) 2024 ROHM Semiconductors4 *5 * ROHM BD96801 watchdog driver6 */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	0x7026#define BD96801_WD_RATIO_MASK		0x327#define BD96801_WD_TYPE_MASK		0x428#define BD96801_WD_TYPE_SLOW		0x429#define BD96801_WD_TYPE_WIN		0x030 31#define BD96801_WD_EN_MASK		0x332#define BD96801_WD_IF_EN		0x133#define BD96801_WD_QA_EN		0x234#define BD96801_WD_DISABLE		0x035 36#define BD96801_WD_ASSERT_MASK		0x837#define BD96801_WD_ASSERT_RST		0x838#define BD96801_WD_ASSERT_IRQ		0x039 40#define BD96801_WD_FEED_MASK		0x141#define BD96801_WD_FEED			0x142 43/* 1.1 mS */44#define FASTNG_MIN			1145#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	184349/* Unit is seconds */50#define DEFAULT_TIMEOUT 3051 52/*53 * BD96801 WDG supports window mode so the TMO consists of SHORT and LONG54 * timeout values. SHORT time is meaningful only in window mode where feeding55 * period shorter than SHORT would be an error. LONG time is used to detect if56 * feeding is not occurring within given time limit (SoC SW hangs). The LONG57 * 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 in189	 * 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 not245	 * support in this driver. If the QA-mode is enabled then we just246	 * 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 to362	 * the timeout / mode registers and leave the hardware configs363	 * 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");418