brintos

brintos / linux-shallow public Read only

0
0
Text · 13.6 KiB · e6c7a29 Raw
546 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 * Kontron PLD watchdog driver4 *5 * Copyright (c) 2010-2013 Kontron Europe GmbH6 * Author: Michael Brunner <michael.brunner@kontron.com>7 *8 * Note: From the PLD watchdog point of view timeout and pretimeout are9 *       defined differently than in the kernel.10 *       First the pretimeout stage runs out before the timeout stage gets11 *       active.12 *13 * Kernel/API:                     P-----| pretimeout14 *               |-----------------------T timeout15 * Watchdog:     |-----------------P       pretimeout_stage16 *                                 |-----T timeout_stage17 */18 19#include <linux/module.h>20#include <linux/moduleparam.h>21#include <linux/uaccess.h>22#include <linux/watchdog.h>23#include <linux/platform_device.h>24#include <linux/mfd/kempld.h>25 26#define KEMPLD_WDT_STAGE_TIMEOUT(x)	(0x1b + (x) * 4)27#define KEMPLD_WDT_STAGE_CFG(x)		(0x18 + (x))28#define STAGE_CFG_GET_PRESCALER(x)	(((x) & 0x30) >> 4)29#define STAGE_CFG_SET_PRESCALER(x)	(((x) & 0x3) << 4)30#define STAGE_CFG_PRESCALER_MASK	0x3031#define STAGE_CFG_ACTION_MASK		0x732#define STAGE_CFG_ASSERT		(1 << 3)33 34#define KEMPLD_WDT_MAX_STAGES		235#define KEMPLD_WDT_KICK			0x1636#define KEMPLD_WDT_CFG			0x1737#define KEMPLD_WDT_CFG_ENABLE		0x1038#define KEMPLD_WDT_CFG_ENABLE_LOCK	0x839#define KEMPLD_WDT_CFG_GLOBAL_LOCK	0x8040 41enum {42	ACTION_NONE = 0,43	ACTION_RESET,44	ACTION_NMI,45	ACTION_SMI,46	ACTION_SCI,47	ACTION_DELAY,48};49 50enum {51	STAGE_TIMEOUT = 0,52	STAGE_PRETIMEOUT,53};54 55enum {56	PRESCALER_21 = 0,57	PRESCALER_17,58	PRESCALER_12,59};60 61static const u32 kempld_prescaler[] = {62	[PRESCALER_21] = (1 << 21) - 1,63	[PRESCALER_17] = (1 << 17) - 1,64	[PRESCALER_12] = (1 << 12) - 1,65	0,66};67 68struct kempld_wdt_stage {69	unsigned int	id;70	u32		mask;71};72 73struct kempld_wdt_data {74	struct kempld_device_data	*pld;75	struct watchdog_device		wdd;76	unsigned int			pretimeout;77	struct kempld_wdt_stage		stage[KEMPLD_WDT_MAX_STAGES];78	u8				pm_status_store;79};80 81#define DEFAULT_TIMEOUT		30 /* seconds */82#define DEFAULT_PRETIMEOUT	083 84static unsigned int timeout = DEFAULT_TIMEOUT;85module_param(timeout, uint, 0);86MODULE_PARM_DESC(timeout,87	"Watchdog timeout in seconds. (>=0, default="88	__MODULE_STRING(DEFAULT_TIMEOUT) ")");89 90static unsigned int pretimeout = DEFAULT_PRETIMEOUT;91module_param(pretimeout, uint, 0);92MODULE_PARM_DESC(pretimeout,93	"Watchdog pretimeout in seconds. (>=0, default="94	__MODULE_STRING(DEFAULT_PRETIMEOUT) ")");95 96static bool nowayout = WATCHDOG_NOWAYOUT;97module_param(nowayout, bool, 0);98MODULE_PARM_DESC(nowayout,99	"Watchdog cannot be stopped once started (default="100	__MODULE_STRING(WATCHDOG_NOWAYOUT) ")");101 102static int kempld_wdt_set_stage_action(struct kempld_wdt_data *wdt_data,103					struct kempld_wdt_stage *stage,104					u8 action)105{106	struct kempld_device_data *pld = wdt_data->pld;107	u8 stage_cfg;108 109	if (!stage || !stage->mask)110		return -EINVAL;111 112	kempld_get_mutex(pld);113	stage_cfg = kempld_read8(pld, KEMPLD_WDT_STAGE_CFG(stage->id));114	stage_cfg &= ~STAGE_CFG_ACTION_MASK;115	stage_cfg |= (action & STAGE_CFG_ACTION_MASK);116 117	if (action == ACTION_RESET)118		stage_cfg |= STAGE_CFG_ASSERT;119	else120		stage_cfg &= ~STAGE_CFG_ASSERT;121 122	kempld_write8(pld, KEMPLD_WDT_STAGE_CFG(stage->id), stage_cfg);123	kempld_release_mutex(pld);124 125	return 0;126}127 128static int kempld_wdt_set_stage_timeout(struct kempld_wdt_data *wdt_data,129					struct kempld_wdt_stage *stage,130					unsigned int timeout)131{132	struct kempld_device_data *pld = wdt_data->pld;133	u32 prescaler;134	u64 stage_timeout64;135	u32 stage_timeout;136	u32 remainder;137	u8 stage_cfg;138 139	prescaler = kempld_prescaler[PRESCALER_21];140 141	if (!stage)142		return -EINVAL;143 144	stage_timeout64 = (u64)timeout * pld->pld_clock;145	remainder = do_div(stage_timeout64, prescaler);146	if (remainder)147		stage_timeout64++;148 149	if (stage_timeout64 > stage->mask)150		return -EINVAL;151 152	stage_timeout = stage_timeout64 & stage->mask;153 154	kempld_get_mutex(pld);155	stage_cfg = kempld_read8(pld, KEMPLD_WDT_STAGE_CFG(stage->id));156	stage_cfg &= ~STAGE_CFG_PRESCALER_MASK;157	stage_cfg |= STAGE_CFG_SET_PRESCALER(PRESCALER_21);158	kempld_write8(pld, KEMPLD_WDT_STAGE_CFG(stage->id), stage_cfg);159	kempld_write32(pld, KEMPLD_WDT_STAGE_TIMEOUT(stage->id),160			stage_timeout);161	kempld_release_mutex(pld);162 163	return 0;164}165 166/*167 * kempld_get_mutex must be called prior to calling this function.168 */169static unsigned int kempld_wdt_get_timeout(struct kempld_wdt_data *wdt_data,170						struct kempld_wdt_stage *stage)171{172	struct kempld_device_data *pld = wdt_data->pld;173	unsigned int timeout;174	u64 stage_timeout;175	u32 prescaler;176	u32 remainder;177	u8 stage_cfg;178 179	if (!stage->mask)180		return 0;181 182	stage_cfg = kempld_read8(pld, KEMPLD_WDT_STAGE_CFG(stage->id));183	stage_timeout = kempld_read32(pld, KEMPLD_WDT_STAGE_TIMEOUT(stage->id));184	prescaler = kempld_prescaler[STAGE_CFG_GET_PRESCALER(stage_cfg)];185 186	stage_timeout = (stage_timeout & stage->mask) * prescaler;187	remainder = do_div(stage_timeout, pld->pld_clock);188	if (remainder)189		stage_timeout++;190 191	timeout = stage_timeout;192	WARN_ON_ONCE(timeout != stage_timeout);193 194	return timeout;195}196 197static int kempld_wdt_set_timeout(struct watchdog_device *wdd,198					unsigned int timeout)199{200	struct kempld_wdt_data *wdt_data = watchdog_get_drvdata(wdd);201	struct kempld_wdt_stage *pretimeout_stage;202	struct kempld_wdt_stage *timeout_stage;203	int ret;204 205	timeout_stage = &wdt_data->stage[STAGE_TIMEOUT];206	pretimeout_stage = &wdt_data->stage[STAGE_PRETIMEOUT];207 208	if (pretimeout_stage->mask && wdt_data->pretimeout > 0)209		timeout = wdt_data->pretimeout;210 211	ret = kempld_wdt_set_stage_action(wdt_data, timeout_stage,212						ACTION_RESET);213	if (ret)214		return ret;215	ret = kempld_wdt_set_stage_timeout(wdt_data, timeout_stage,216						timeout);217	if (ret)218		return ret;219 220	wdd->timeout = timeout;221	return 0;222}223 224static int kempld_wdt_set_pretimeout(struct watchdog_device *wdd,225					unsigned int pretimeout)226{227	struct kempld_wdt_data *wdt_data = watchdog_get_drvdata(wdd);228	struct kempld_wdt_stage *pretimeout_stage;229	u8 action = ACTION_NONE;230	int ret;231 232	pretimeout_stage = &wdt_data->stage[STAGE_PRETIMEOUT];233 234	if (!pretimeout_stage->mask)235		return -ENXIO;236 237	if (pretimeout > wdd->timeout)238		return -EINVAL;239 240	if (pretimeout > 0)241		action = ACTION_NMI;242 243	ret = kempld_wdt_set_stage_action(wdt_data, pretimeout_stage,244						action);245	if (ret)246		return ret;247	ret = kempld_wdt_set_stage_timeout(wdt_data, pretimeout_stage,248						wdd->timeout - pretimeout);249	if (ret)250		return ret;251 252	wdt_data->pretimeout = pretimeout;253	return 0;254}255 256static void kempld_wdt_update_timeouts(struct kempld_wdt_data *wdt_data)257{258	struct kempld_device_data *pld = wdt_data->pld;259	struct kempld_wdt_stage *pretimeout_stage;260	struct kempld_wdt_stage *timeout_stage;261	unsigned int pretimeout, timeout;262 263	pretimeout_stage = &wdt_data->stage[STAGE_PRETIMEOUT];264	timeout_stage = &wdt_data->stage[STAGE_TIMEOUT];265 266	kempld_get_mutex(pld);267	pretimeout = kempld_wdt_get_timeout(wdt_data, pretimeout_stage);268	timeout = kempld_wdt_get_timeout(wdt_data, timeout_stage);269	kempld_release_mutex(pld);270 271	if (pretimeout)272		wdt_data->pretimeout = timeout;273	else274		wdt_data->pretimeout = 0;275 276	wdt_data->wdd.timeout = pretimeout + timeout;277}278 279static int kempld_wdt_start(struct watchdog_device *wdd)280{281	struct kempld_wdt_data *wdt_data = watchdog_get_drvdata(wdd);282	struct kempld_device_data *pld = wdt_data->pld;283	u8 status;284	int ret;285 286	ret = kempld_wdt_set_timeout(wdd, wdd->timeout);287	if (ret)288		return ret;289 290	kempld_get_mutex(pld);291	status = kempld_read8(pld, KEMPLD_WDT_CFG);292	status |= KEMPLD_WDT_CFG_ENABLE;293	kempld_write8(pld, KEMPLD_WDT_CFG, status);294	status = kempld_read8(pld, KEMPLD_WDT_CFG);295	kempld_release_mutex(pld);296 297	/* Check if the watchdog was enabled */298	if (!(status & KEMPLD_WDT_CFG_ENABLE))299		return -EACCES;300 301	return 0;302}303 304static int kempld_wdt_stop(struct watchdog_device *wdd)305{306	struct kempld_wdt_data *wdt_data = watchdog_get_drvdata(wdd);307	struct kempld_device_data *pld = wdt_data->pld;308	u8 status;309 310	kempld_get_mutex(pld);311	status = kempld_read8(pld, KEMPLD_WDT_CFG);312	status &= ~KEMPLD_WDT_CFG_ENABLE;313	kempld_write8(pld, KEMPLD_WDT_CFG, status);314	status = kempld_read8(pld, KEMPLD_WDT_CFG);315	kempld_release_mutex(pld);316 317	/* Check if the watchdog was disabled */318	if (status & KEMPLD_WDT_CFG_ENABLE)319		return -EACCES;320 321	return 0;322}323 324static int kempld_wdt_keepalive(struct watchdog_device *wdd)325{326	struct kempld_wdt_data *wdt_data = watchdog_get_drvdata(wdd);327	struct kempld_device_data *pld = wdt_data->pld;328 329	kempld_get_mutex(pld);330	kempld_write8(pld, KEMPLD_WDT_KICK, 'K');331	kempld_release_mutex(pld);332 333	return 0;334}335 336static long kempld_wdt_ioctl(struct watchdog_device *wdd, unsigned int cmd,337				unsigned long arg)338{339	struct kempld_wdt_data *wdt_data = watchdog_get_drvdata(wdd);340	void __user *argp = (void __user *)arg;341	int ret = -ENOIOCTLCMD;342	int __user *p = argp;343	int new_value;344 345	switch (cmd) {346	case WDIOC_SETPRETIMEOUT:347		if (get_user(new_value, p))348			return -EFAULT;349		ret = kempld_wdt_set_pretimeout(wdd, new_value);350		if (ret)351			return ret;352		ret = kempld_wdt_keepalive(wdd);353		break;354	case WDIOC_GETPRETIMEOUT:355		ret = put_user(wdt_data->pretimeout, (int __user *)arg);356		break;357	}358 359	return ret;360}361 362static int kempld_wdt_probe_stages(struct watchdog_device *wdd)363{364	struct kempld_wdt_data *wdt_data = watchdog_get_drvdata(wdd);365	struct kempld_device_data *pld = wdt_data->pld;366	struct kempld_wdt_stage *pretimeout_stage;367	struct kempld_wdt_stage *timeout_stage;368	u8 index, data, data_orig;369	u32 mask;370	int i, j;371 372	pretimeout_stage = &wdt_data->stage[STAGE_PRETIMEOUT];373	timeout_stage = &wdt_data->stage[STAGE_TIMEOUT];374 375	pretimeout_stage->mask = 0;376	timeout_stage->mask = 0;377 378	for (i = 0; i < 3; i++) {379		index = KEMPLD_WDT_STAGE_TIMEOUT(i);380		mask = 0;381 382		kempld_get_mutex(pld);383		/* Probe each byte individually. */384		for (j = 0; j < 4; j++) {385			data_orig = kempld_read8(pld, index + j);386			kempld_write8(pld, index + j, 0x00);387			data = kempld_read8(pld, index + j);388			/* A failed write means this byte is reserved */389			if (data != 0x00)390				break;391			kempld_write8(pld, index + j, data_orig);392			mask |= 0xff << (j * 8);393		}394		kempld_release_mutex(pld);395 396		/* Assign available stages to timeout and pretimeout */397		if (!timeout_stage->mask) {398			timeout_stage->mask = mask;399			timeout_stage->id = i;400		} else {401			if (pld->feature_mask & KEMPLD_FEATURE_BIT_NMI) {402				pretimeout_stage->mask = timeout_stage->mask;403				timeout_stage->mask = mask;404				pretimeout_stage->id = timeout_stage->id;405				timeout_stage->id = i;406			}407			break;408		}409	}410 411	if (!timeout_stage->mask)412		return -ENODEV;413 414	return 0;415}416 417static const struct watchdog_info kempld_wdt_info = {418	.identity	= "KEMPLD Watchdog",419	.options	= WDIOF_SETTIMEOUT |420			WDIOF_KEEPALIVEPING |421			WDIOF_MAGICCLOSE |422			WDIOF_PRETIMEOUT423};424 425static const struct watchdog_ops kempld_wdt_ops = {426	.owner		= THIS_MODULE,427	.start		= kempld_wdt_start,428	.stop		= kempld_wdt_stop,429	.ping		= kempld_wdt_keepalive,430	.set_timeout	= kempld_wdt_set_timeout,431	.ioctl		= kempld_wdt_ioctl,432};433 434static int kempld_wdt_probe(struct platform_device *pdev)435{436	struct kempld_device_data *pld = dev_get_drvdata(pdev->dev.parent);437	struct kempld_wdt_data *wdt_data;438	struct device *dev = &pdev->dev;439	struct watchdog_device *wdd;440	u8 status;441	int ret = 0;442 443	wdt_data = devm_kzalloc(dev, sizeof(*wdt_data), GFP_KERNEL);444	if (!wdt_data)445		return -ENOMEM;446 447	wdt_data->pld = pld;448	wdd = &wdt_data->wdd;449	wdd->parent = dev;450 451	kempld_get_mutex(pld);452	status = kempld_read8(pld, KEMPLD_WDT_CFG);453	kempld_release_mutex(pld);454 455	/* Enable nowayout if watchdog is already locked */456	if (status & (KEMPLD_WDT_CFG_ENABLE_LOCK |457			KEMPLD_WDT_CFG_GLOBAL_LOCK)) {458		if (!nowayout)459			dev_warn(dev,460				 "Forcing nowayout - watchdog lock enabled!\n");461		nowayout = true;462	}463 464	wdd->info = &kempld_wdt_info;465	wdd->ops = &kempld_wdt_ops;466 467	watchdog_set_drvdata(wdd, wdt_data);468	watchdog_set_nowayout(wdd, nowayout);469 470	ret = kempld_wdt_probe_stages(wdd);471	if (ret)472		return ret;473 474	kempld_wdt_set_timeout(wdd, timeout);475	kempld_wdt_set_pretimeout(wdd, pretimeout);476 477	/* Check if watchdog is already enabled */478	if (status & KEMPLD_WDT_CFG_ENABLE) {479		/* Get current watchdog settings */480		kempld_wdt_update_timeouts(wdt_data);481		dev_info(dev, "Watchdog was already enabled\n");482	}483 484	platform_set_drvdata(pdev, wdt_data);485	watchdog_stop_on_reboot(wdd);486	watchdog_stop_on_unregister(wdd);487	ret = devm_watchdog_register_device(dev, wdd);488	if (ret)489		return ret;490 491	dev_info(dev, "Watchdog registered with %ds timeout\n", wdd->timeout);492 493	return 0;494}495 496/* Disable watchdog if it is active during suspend */497static int kempld_wdt_suspend(struct platform_device *pdev,498				pm_message_t message)499{500	struct kempld_wdt_data *wdt_data = platform_get_drvdata(pdev);501	struct kempld_device_data *pld = wdt_data->pld;502	struct watchdog_device *wdd = &wdt_data->wdd;503 504	kempld_get_mutex(pld);505	wdt_data->pm_status_store = kempld_read8(pld, KEMPLD_WDT_CFG);506	kempld_release_mutex(pld);507 508	kempld_wdt_update_timeouts(wdt_data);509 510	if (wdt_data->pm_status_store & KEMPLD_WDT_CFG_ENABLE)511		return kempld_wdt_stop(wdd);512 513	return 0;514}515 516/* Enable watchdog and configure it if necessary */517static int kempld_wdt_resume(struct platform_device *pdev)518{519	struct kempld_wdt_data *wdt_data = platform_get_drvdata(pdev);520	struct watchdog_device *wdd = &wdt_data->wdd;521 522	/*523	 * If watchdog was stopped before suspend be sure it gets disabled524	 * again, for the case BIOS has enabled it during resume525	 */526	if (wdt_data->pm_status_store & KEMPLD_WDT_CFG_ENABLE)527		return kempld_wdt_start(wdd);528	else529		return kempld_wdt_stop(wdd);530}531 532static struct platform_driver kempld_wdt_driver = {533	.driver		= {534		.name	= "kempld-wdt",535	},536	.probe		= kempld_wdt_probe,537	.suspend	= pm_ptr(kempld_wdt_suspend),538	.resume		= pm_ptr(kempld_wdt_resume),539};540 541module_platform_driver(kempld_wdt_driver);542 543MODULE_DESCRIPTION("KEM PLD Watchdog Driver");544MODULE_AUTHOR("Michael Brunner <michael.brunner@kontron.com>");545MODULE_LICENSE("GPL");546