cachepc-linux

Fork of AMDESE/linux with modifications for CachePC side-channel attack
git clone https://git.sinitax.com/sinitax/cachepc-linux
Log | Files | Refs | README | LICENSE | sfeed.txt

imx_sc_wdt.c (6471B)


      1// SPDX-License-Identifier: GPL-2.0
      2/*
      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 60
     17/*
     18 * Software timer tick implemented in scfw side, support 10ms to 0xffffffff ms
     19 * in theory, but for normal case, 1s~128s is enough, you can change this max
     20 * value in case it's not enough.
     21 */
     22#define MAX_TIMEOUT 128
     23
     24#define IMX_SIP_TIMER			0xC2000002
     25#define IMX_SIP_TIMER_START_WDOG		0x01
     26#define IMX_SIP_TIMER_STOP_WDOG		0x02
     27#define IMX_SIP_TIMER_SET_WDOG_ACT	0x03
     28#define IMX_SIP_TIMER_PING_WDOG		0x04
     29#define IMX_SIP_TIMER_SET_TIMEOUT_WDOG	0x05
     30#define IMX_SIP_TIMER_GET_WDOG_STAT	0x06
     31#define IMX_SIP_TIMER_SET_PRETIME_WDOG	0x07
     32
     33#define SC_TIMER_WDOG_ACTION_PARTITION	0
     34
     35#define SC_IRQ_WDOG			1
     36#define SC_IRQ_GROUP_WDOG		1
     37
     38static bool nowayout = WATCHDOG_NOWAYOUT;
     39module_param(nowayout, bool, 0000);
     40MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default="
     41		 __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
     42
     43struct imx_sc_wdt_device {
     44	struct watchdog_device wdd;
     45	struct notifier_block wdt_notifier;
     46};
     47
     48static int imx_sc_wdt_ping(struct watchdog_device *wdog)
     49{
     50	struct arm_smccc_res res;
     51
     52	arm_smccc_smc(IMX_SIP_TIMER, IMX_SIP_TIMER_PING_WDOG,
     53		      0, 0, 0, 0, 0, 0, &res);
     54
     55	return 0;
     56}
     57
     58static int imx_sc_wdt_start(struct watchdog_device *wdog)
     59{
     60	struct arm_smccc_res res;
     61
     62	arm_smccc_smc(IMX_SIP_TIMER, IMX_SIP_TIMER_START_WDOG,
     63		      0, 0, 0, 0, 0, 0, &res);
     64	if (res.a0)
     65		return -EACCES;
     66
     67	arm_smccc_smc(IMX_SIP_TIMER, IMX_SIP_TIMER_SET_WDOG_ACT,
     68		      SC_TIMER_WDOG_ACTION_PARTITION,
     69		      0, 0, 0, 0, 0, &res);
     70	return res.a0 ? -EACCES : 0;
     71}
     72
     73static int imx_sc_wdt_stop(struct watchdog_device *wdog)
     74{
     75	struct arm_smccc_res res;
     76
     77	arm_smccc_smc(IMX_SIP_TIMER, IMX_SIP_TIMER_STOP_WDOG,
     78		      0, 0, 0, 0, 0, 0, &res);
     79
     80	return res.a0 ? -EACCES : 0;
     81}
     82
     83static int imx_sc_wdt_set_timeout(struct watchdog_device *wdog,
     84				unsigned int timeout)
     85{
     86	struct arm_smccc_res res;
     87
     88	wdog->timeout = timeout;
     89	arm_smccc_smc(IMX_SIP_TIMER, IMX_SIP_TIMER_SET_TIMEOUT_WDOG,
     90		      timeout * 1000, 0, 0, 0, 0, 0, &res);
     91
     92	return res.a0 ? -EACCES : 0;
     93}
     94
     95static int imx_sc_wdt_set_pretimeout(struct watchdog_device *wdog,
     96				     unsigned int pretimeout)
     97{
     98	struct arm_smccc_res res;
     99
    100	/*
    101	 * SCU firmware calculates pretimeout based on current time
    102	 * stamp instead of watchdog timeout stamp, need to convert
    103	 * the pretimeout to SCU firmware's timeout value.
    104	 */
    105	arm_smccc_smc(IMX_SIP_TIMER, IMX_SIP_TIMER_SET_PRETIME_WDOG,
    106		      (wdog->timeout - pretimeout) * 1000, 0, 0, 0,
    107		      0, 0, &res);
    108	if (res.a0)
    109		return -EACCES;
    110
    111	wdog->pretimeout = pretimeout;
    112
    113	return 0;
    114}
    115
    116static int imx_sc_wdt_notify(struct notifier_block *nb,
    117			     unsigned long event, void *group)
    118{
    119	struct imx_sc_wdt_device *imx_sc_wdd =
    120				 container_of(nb,
    121					      struct imx_sc_wdt_device,
    122					      wdt_notifier);
    123
    124	if (event & SC_IRQ_WDOG &&
    125	    *(u8 *)group == SC_IRQ_GROUP_WDOG)
    126		watchdog_notify_pretimeout(&imx_sc_wdd->wdd);
    127
    128	return 0;
    129}
    130
    131static void imx_sc_wdt_action(void *data)
    132{
    133	struct notifier_block *wdt_notifier = data;
    134
    135	imx_scu_irq_unregister_notifier(wdt_notifier);
    136	imx_scu_irq_group_enable(SC_IRQ_GROUP_WDOG,
    137				 SC_IRQ_WDOG,
    138				 false);
    139}
    140
    141static const struct watchdog_ops imx_sc_wdt_ops = {
    142	.owner = THIS_MODULE,
    143	.start = imx_sc_wdt_start,
    144	.stop  = imx_sc_wdt_stop,
    145	.ping  = imx_sc_wdt_ping,
    146	.set_timeout = imx_sc_wdt_set_timeout,
    147	.set_pretimeout = imx_sc_wdt_set_pretimeout,
    148};
    149
    150static struct watchdog_info imx_sc_wdt_info = {
    151	.identity	= "i.MX SC watchdog timer",
    152	.options	= WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING |
    153			  WDIOF_MAGICCLOSE,
    154};
    155
    156static int imx_sc_wdt_probe(struct platform_device *pdev)
    157{
    158	struct imx_sc_wdt_device *imx_sc_wdd;
    159	struct watchdog_device *wdog;
    160	struct device *dev = &pdev->dev;
    161	int ret;
    162
    163	imx_sc_wdd = devm_kzalloc(dev, sizeof(*imx_sc_wdd), GFP_KERNEL);
    164	if (!imx_sc_wdd)
    165		return -ENOMEM;
    166
    167	platform_set_drvdata(pdev, imx_sc_wdd);
    168
    169	wdog = &imx_sc_wdd->wdd;
    170	wdog->info = &imx_sc_wdt_info;
    171	wdog->ops = &imx_sc_wdt_ops;
    172	wdog->min_timeout = 1;
    173	wdog->max_timeout = MAX_TIMEOUT;
    174	wdog->parent = dev;
    175	wdog->timeout = DEFAULT_TIMEOUT;
    176
    177	watchdog_init_timeout(wdog, 0, dev);
    178
    179	ret = imx_sc_wdt_set_timeout(wdog, wdog->timeout);
    180	if (ret)
    181		return ret;
    182
    183	watchdog_stop_on_reboot(wdog);
    184	watchdog_stop_on_unregister(wdog);
    185
    186	ret = imx_scu_irq_group_enable(SC_IRQ_GROUP_WDOG,
    187				       SC_IRQ_WDOG,
    188				       true);
    189	if (ret) {
    190		dev_warn(dev, "Enable irq failed, pretimeout NOT supported\n");
    191		goto register_device;
    192	}
    193
    194	imx_sc_wdd->wdt_notifier.notifier_call = imx_sc_wdt_notify;
    195	ret = imx_scu_irq_register_notifier(&imx_sc_wdd->wdt_notifier);
    196	if (ret) {
    197		imx_scu_irq_group_enable(SC_IRQ_GROUP_WDOG,
    198					 SC_IRQ_WDOG,
    199					 false);
    200		dev_warn(dev,
    201			 "Register irq notifier failed, pretimeout NOT supported\n");
    202		goto register_device;
    203	}
    204
    205	ret = devm_add_action_or_reset(dev, imx_sc_wdt_action,
    206				       &imx_sc_wdd->wdt_notifier);
    207	if (!ret)
    208		imx_sc_wdt_info.options |= WDIOF_PRETIMEOUT;
    209	else
    210		dev_warn(dev, "Add action failed, pretimeout NOT supported\n");
    211
    212register_device:
    213	return devm_watchdog_register_device(dev, wdog);
    214}
    215
    216static int __maybe_unused imx_sc_wdt_suspend(struct device *dev)
    217{
    218	struct imx_sc_wdt_device *imx_sc_wdd = dev_get_drvdata(dev);
    219
    220	if (watchdog_active(&imx_sc_wdd->wdd))
    221		imx_sc_wdt_stop(&imx_sc_wdd->wdd);
    222
    223	return 0;
    224}
    225
    226static int __maybe_unused imx_sc_wdt_resume(struct device *dev)
    227{
    228	struct imx_sc_wdt_device *imx_sc_wdd = dev_get_drvdata(dev);
    229
    230	if (watchdog_active(&imx_sc_wdd->wdd))
    231		imx_sc_wdt_start(&imx_sc_wdd->wdd);
    232
    233	return 0;
    234}
    235
    236static SIMPLE_DEV_PM_OPS(imx_sc_wdt_pm_ops,
    237			 imx_sc_wdt_suspend, imx_sc_wdt_resume);
    238
    239static const struct of_device_id imx_sc_wdt_dt_ids[] = {
    240	{ .compatible = "fsl,imx-sc-wdt", },
    241	{ /* sentinel */ }
    242};
    243MODULE_DEVICE_TABLE(of, imx_sc_wdt_dt_ids);
    244
    245static struct platform_driver imx_sc_wdt_driver = {
    246	.probe		= imx_sc_wdt_probe,
    247	.driver		= {
    248		.name	= "imx-sc-wdt",
    249		.of_match_table = imx_sc_wdt_dt_ids,
    250		.pm	= &imx_sc_wdt_pm_ops,
    251	},
    252};
    253module_platform_driver(imx_sc_wdt_driver);
    254
    255MODULE_AUTHOR("Robin Gong <yibin.gong@nxp.com>");
    256MODULE_DESCRIPTION("NXP i.MX system controller watchdog driver");
    257MODULE_LICENSE("GPL v2");