brintos

brintos / linux-shallow public Read only

0
0
Text · 6.4 KiB · 1280b9b Raw
259 lines · c
1// SPDX-License-Identifier: GPL-2.02/*3 * Copyright 2018-2019 NXP.4 */5 6#include <linux/arm-smccc.h>7#include <linux/firmware/imx/sci.h>8#include <linux/io.h>9#include <linux/kernel.h>10#include <linux/module.h>11#include <linux/moduleparam.h>12#include <linux/of.h>13#include <linux/platform_device.h>14#include <linux/watchdog.h>15 16#define DEFAULT_TIMEOUT 6017/*18 * Software timer tick implemented in scfw side, support 10ms to 0xffffffff ms19 * in theory, but for normal case, 1s~128s is enough, you can change this max20 * value in case it's not enough.21 */22#define MAX_TIMEOUT 12823 24#define IMX_SIP_TIMER			0xC200000225#define IMX_SIP_TIMER_START_WDOG		0x0126#define IMX_SIP_TIMER_STOP_WDOG		0x0227#define IMX_SIP_TIMER_SET_WDOG_ACT	0x0328#define IMX_SIP_TIMER_PING_WDOG		0x0429#define IMX_SIP_TIMER_SET_TIMEOUT_WDOG	0x0530#define IMX_SIP_TIMER_GET_WDOG_STAT	0x0631#define IMX_SIP_TIMER_SET_PRETIME_WDOG	0x0732 33#define SC_TIMER_WDOG_ACTION_PARTITION	034 35#define SC_IRQ_WDOG			136#define SC_IRQ_GROUP_WDOG		137#define SC_TIMER_ERR_BUSY		1038 39static bool nowayout = WATCHDOG_NOWAYOUT;40module_param(nowayout, bool, 0000);41MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default="42		 __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");43 44struct imx_sc_wdt_device {45	struct watchdog_device wdd;46	struct notifier_block wdt_notifier;47};48 49static int imx_sc_wdt_ping(struct watchdog_device *wdog)50{51	struct arm_smccc_res res;52 53	arm_smccc_smc(IMX_SIP_TIMER, IMX_SIP_TIMER_PING_WDOG,54		      0, 0, 0, 0, 0, 0, &res);55 56	return 0;57}58 59static bool imx_sc_wdt_is_running(void)60{61	struct arm_smccc_res res;62 63	arm_smccc_smc(IMX_SIP_TIMER, IMX_SIP_TIMER_START_WDOG,64		      0, 0, 0, 0, 0, 0, &res);65 66	/* Already enabled (SC_TIMER_ERR_BUSY)? */67	if (res.a0 == SC_TIMER_ERR_BUSY)68		return true;69 70	/* Undo only if that was us who has (successfully) enabled the WDT */71	if (!res.a0)72		arm_smccc_smc(IMX_SIP_TIMER, IMX_SIP_TIMER_STOP_WDOG,73			      0, 0, 0, 0, 0, 0, &res);74 75	return false;76}77 78static int imx_sc_wdt_start(struct watchdog_device *wdog)79{80	struct arm_smccc_res res;81 82	arm_smccc_smc(IMX_SIP_TIMER, IMX_SIP_TIMER_START_WDOG,83		      0, 0, 0, 0, 0, 0, &res);84 85	/* Ignore if already enabled(SC_TIMER_ERR_BUSY) */86	if (res.a0 && res.a0 != SC_TIMER_ERR_BUSY)87		return -EACCES;88 89	arm_smccc_smc(IMX_SIP_TIMER, IMX_SIP_TIMER_SET_WDOG_ACT,90		      SC_TIMER_WDOG_ACTION_PARTITION,91		      0, 0, 0, 0, 0, &res);92	return res.a0 ? -EACCES : 0;93}94 95static int imx_sc_wdt_stop(struct watchdog_device *wdog)96{97	struct arm_smccc_res res;98 99	arm_smccc_smc(IMX_SIP_TIMER, IMX_SIP_TIMER_STOP_WDOG,100		      0, 0, 0, 0, 0, 0, &res);101 102	return res.a0 ? -EACCES : 0;103}104 105static int imx_sc_wdt_set_timeout(struct watchdog_device *wdog,106				unsigned int timeout)107{108	struct arm_smccc_res res;109 110	wdog->timeout = timeout;111	arm_smccc_smc(IMX_SIP_TIMER, IMX_SIP_TIMER_SET_TIMEOUT_WDOG,112		      timeout * 1000, 0, 0, 0, 0, 0, &res);113 114	return res.a0 ? -EACCES : 0;115}116 117static int imx_sc_wdt_set_pretimeout(struct watchdog_device *wdog,118				     unsigned int pretimeout)119{120	struct arm_smccc_res res;121 122	/*123	 * SCU firmware calculates pretimeout based on current time124	 * stamp instead of watchdog timeout stamp, need to convert125	 * the pretimeout to SCU firmware's timeout value.126	 */127	arm_smccc_smc(IMX_SIP_TIMER, IMX_SIP_TIMER_SET_PRETIME_WDOG,128		      (wdog->timeout - pretimeout) * 1000, 0, 0, 0,129		      0, 0, &res);130	if (res.a0)131		return -EACCES;132 133	wdog->pretimeout = pretimeout;134 135	return 0;136}137 138static int imx_sc_wdt_notify(struct notifier_block *nb,139			     unsigned long event, void *group)140{141	struct imx_sc_wdt_device *imx_sc_wdd =142				 container_of(nb,143					      struct imx_sc_wdt_device,144					      wdt_notifier);145 146	if (event & SC_IRQ_WDOG &&147	    *(u8 *)group == SC_IRQ_GROUP_WDOG)148		watchdog_notify_pretimeout(&imx_sc_wdd->wdd);149 150	return 0;151}152 153static void imx_sc_wdt_action(void *data)154{155	struct notifier_block *wdt_notifier = data;156 157	imx_scu_irq_unregister_notifier(wdt_notifier);158	imx_scu_irq_group_enable(SC_IRQ_GROUP_WDOG,159				 SC_IRQ_WDOG,160				 false);161}162 163static const struct watchdog_ops imx_sc_wdt_ops = {164	.owner = THIS_MODULE,165	.start = imx_sc_wdt_start,166	.stop  = imx_sc_wdt_stop,167	.ping  = imx_sc_wdt_ping,168	.set_timeout = imx_sc_wdt_set_timeout,169	.set_pretimeout = imx_sc_wdt_set_pretimeout,170};171 172static struct watchdog_info imx_sc_wdt_info = {173	.identity	= "i.MX SC watchdog timer",174	.options	= WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING |175			  WDIOF_MAGICCLOSE,176};177 178static int imx_sc_wdt_probe(struct platform_device *pdev)179{180	struct imx_sc_wdt_device *imx_sc_wdd;181	struct watchdog_device *wdog;182	struct device *dev = &pdev->dev;183	int ret;184 185	imx_sc_wdd = devm_kzalloc(dev, sizeof(*imx_sc_wdd), GFP_KERNEL);186	if (!imx_sc_wdd)187		return -ENOMEM;188 189	platform_set_drvdata(pdev, imx_sc_wdd);190 191	wdog = &imx_sc_wdd->wdd;192	wdog->info = &imx_sc_wdt_info;193	wdog->ops = &imx_sc_wdt_ops;194	wdog->min_timeout = 1;195	wdog->max_timeout = MAX_TIMEOUT;196	wdog->parent = dev;197	wdog->timeout = DEFAULT_TIMEOUT;198 199	watchdog_init_timeout(wdog, 0, dev);200 201	ret = imx_sc_wdt_set_timeout(wdog, wdog->timeout);202	if (ret)203		return ret;204 205	if (imx_sc_wdt_is_running())206		set_bit(WDOG_HW_RUNNING, &wdog->status);207 208	watchdog_stop_on_reboot(wdog);209	watchdog_stop_on_unregister(wdog);210 211	ret = imx_scu_irq_group_enable(SC_IRQ_GROUP_WDOG,212				       SC_IRQ_WDOG,213				       true);214	if (ret) {215		dev_warn(dev, "Enable irq failed, pretimeout NOT supported\n");216		goto register_device;217	}218 219	imx_sc_wdd->wdt_notifier.notifier_call = imx_sc_wdt_notify;220	ret = imx_scu_irq_register_notifier(&imx_sc_wdd->wdt_notifier);221	if (ret) {222		imx_scu_irq_group_enable(SC_IRQ_GROUP_WDOG,223					 SC_IRQ_WDOG,224					 false);225		dev_warn(dev,226			 "Register irq notifier failed, pretimeout NOT supported\n");227		goto register_device;228	}229 230	ret = devm_add_action_or_reset(dev, imx_sc_wdt_action,231				       &imx_sc_wdd->wdt_notifier);232	if (!ret)233		imx_sc_wdt_info.options |= WDIOF_PRETIMEOUT;234	else235		dev_warn(dev, "Add action failed, pretimeout NOT supported\n");236 237register_device:238	return devm_watchdog_register_device(dev, wdog);239}240 241static const struct of_device_id imx_sc_wdt_dt_ids[] = {242	{ .compatible = "fsl,imx-sc-wdt", },243	{ /* sentinel */ }244};245MODULE_DEVICE_TABLE(of, imx_sc_wdt_dt_ids);246 247static struct platform_driver imx_sc_wdt_driver = {248	.probe		= imx_sc_wdt_probe,249	.driver		= {250		.name	= "imx-sc-wdt",251		.of_match_table = imx_sc_wdt_dt_ids,252	},253};254module_platform_driver(imx_sc_wdt_driver);255 256MODULE_AUTHOR("Robin Gong <yibin.gong@nxp.com>");257MODULE_DESCRIPTION("NXP i.MX system controller watchdog driver");258MODULE_LICENSE("GPL v2");259