Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0-only
0002 /*
0003  * ARM Secure Monitor Call watchdog driver
0004  *
0005  * Copyright 2020 Google LLC.
0006  * Julius Werner <jwerner@chromium.org>
0007  * Based on mtk_wdt.c
0008  */
0009 
0010 #include <linux/arm-smccc.h>
0011 #include <linux/err.h>
0012 #include <linux/module.h>
0013 #include <linux/moduleparam.h>
0014 #include <linux/of.h>
0015 #include <linux/platform_device.h>
0016 #include <linux/types.h>
0017 #include <linux/watchdog.h>
0018 #include <uapi/linux/psci.h>
0019 
0020 #define DRV_NAME        "arm_smc_wdt"
0021 #define DRV_VERSION     "1.0"
0022 
0023 enum smcwd_call {
0024     SMCWD_INIT      = 0,
0025     SMCWD_SET_TIMEOUT   = 1,
0026     SMCWD_ENABLE        = 2,
0027     SMCWD_PET       = 3,
0028     SMCWD_GET_TIMELEFT  = 4,
0029 };
0030 
0031 static bool nowayout = WATCHDOG_NOWAYOUT;
0032 static unsigned int timeout;
0033 
0034 static int smcwd_call(struct watchdog_device *wdd, enum smcwd_call call,
0035               unsigned long arg, struct arm_smccc_res *res)
0036 {
0037     struct arm_smccc_res local_res;
0038 
0039     if (!res)
0040         res = &local_res;
0041 
0042     arm_smccc_smc((u32)(uintptr_t)watchdog_get_drvdata(wdd), call, arg, 0,
0043               0, 0, 0, 0, res);
0044 
0045     if (res->a0 == PSCI_RET_NOT_SUPPORTED)
0046         return -ENODEV;
0047     if (res->a0 == PSCI_RET_INVALID_PARAMS)
0048         return -EINVAL;
0049     if (res->a0 != PSCI_RET_SUCCESS)
0050         return -EIO;
0051     return 0;
0052 }
0053 
0054 static int smcwd_ping(struct watchdog_device *wdd)
0055 {
0056     return smcwd_call(wdd, SMCWD_PET, 0, NULL);
0057 }
0058 
0059 static unsigned int smcwd_get_timeleft(struct watchdog_device *wdd)
0060 {
0061     struct arm_smccc_res res;
0062 
0063     smcwd_call(wdd, SMCWD_GET_TIMELEFT, 0, &res);
0064     if (res.a0)
0065         return 0;
0066     return res.a1;
0067 }
0068 
0069 static int smcwd_set_timeout(struct watchdog_device *wdd, unsigned int timeout)
0070 {
0071     int res;
0072 
0073     res = smcwd_call(wdd, SMCWD_SET_TIMEOUT, timeout, NULL);
0074     if (!res)
0075         wdd->timeout = timeout;
0076     return res;
0077 }
0078 
0079 static int smcwd_stop(struct watchdog_device *wdd)
0080 {
0081     return smcwd_call(wdd, SMCWD_ENABLE, 0, NULL);
0082 }
0083 
0084 static int smcwd_start(struct watchdog_device *wdd)
0085 {
0086     return smcwd_call(wdd, SMCWD_ENABLE, 1, NULL);
0087 }
0088 
0089 static const struct watchdog_info smcwd_info = {
0090     .identity   = DRV_NAME,
0091     .options    = WDIOF_SETTIMEOUT |
0092               WDIOF_KEEPALIVEPING |
0093               WDIOF_MAGICCLOSE,
0094 };
0095 
0096 static const struct watchdog_ops smcwd_ops = {
0097     .start      = smcwd_start,
0098     .stop       = smcwd_stop,
0099     .ping       = smcwd_ping,
0100     .set_timeout    = smcwd_set_timeout,
0101 };
0102 
0103 static const struct watchdog_ops smcwd_timeleft_ops = {
0104     .start      = smcwd_start,
0105     .stop       = smcwd_stop,
0106     .ping       = smcwd_ping,
0107     .set_timeout    = smcwd_set_timeout,
0108     .get_timeleft   = smcwd_get_timeleft,
0109 };
0110 
0111 static int smcwd_probe(struct platform_device *pdev)
0112 {
0113     struct watchdog_device *wdd;
0114     int err;
0115     struct arm_smccc_res res;
0116     u32 smc_func_id;
0117 
0118     wdd = devm_kzalloc(&pdev->dev, sizeof(*wdd), GFP_KERNEL);
0119     if (!wdd)
0120         return -ENOMEM;
0121     platform_set_drvdata(pdev, wdd);
0122 
0123     if (of_property_read_u32(pdev->dev.of_node, "arm,smc-id",
0124                  &smc_func_id))
0125         smc_func_id = 0x82003D06;
0126     watchdog_set_drvdata(wdd, (void *)(uintptr_t)smc_func_id);
0127 
0128     err = smcwd_call(wdd, SMCWD_INIT, 0, &res);
0129     if (err < 0)
0130         return err;
0131 
0132     wdd->info = &smcwd_info;
0133     /* get_timeleft is optional */
0134     if (smcwd_call(wdd, SMCWD_GET_TIMELEFT, 0, NULL))
0135         wdd->ops = &smcwd_ops;
0136     else
0137         wdd->ops = &smcwd_timeleft_ops;
0138     wdd->timeout = res.a2;
0139     wdd->max_timeout = res.a2;
0140     wdd->min_timeout = res.a1;
0141     wdd->parent = &pdev->dev;
0142 
0143     watchdog_stop_on_reboot(wdd);
0144     watchdog_stop_on_unregister(wdd);
0145     watchdog_set_nowayout(wdd, nowayout);
0146     watchdog_init_timeout(wdd, timeout, &pdev->dev);
0147     err = smcwd_set_timeout(wdd, wdd->timeout);
0148     if (err)
0149         return err;
0150 
0151     err = devm_watchdog_register_device(&pdev->dev, wdd);
0152     if (err)
0153         return err;
0154 
0155     dev_info(&pdev->dev,
0156          "Watchdog registered (timeout=%d sec, nowayout=%d)\n",
0157          wdd->timeout, nowayout);
0158 
0159     return 0;
0160 }
0161 
0162 static const struct of_device_id smcwd_dt_ids[] = {
0163     { .compatible = "arm,smc-wdt" },
0164     {}
0165 };
0166 MODULE_DEVICE_TABLE(of, smcwd_dt_ids);
0167 
0168 static struct platform_driver smcwd_driver = {
0169     .probe      = smcwd_probe,
0170     .driver     = {
0171         .name       = DRV_NAME,
0172         .of_match_table = smcwd_dt_ids,
0173     },
0174 };
0175 
0176 module_platform_driver(smcwd_driver);
0177 
0178 module_param(timeout, uint, 0);
0179 MODULE_PARM_DESC(timeout, "Watchdog heartbeat in seconds");
0180 
0181 module_param(nowayout, bool, 0);
0182 MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default="
0183             __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
0184 
0185 MODULE_LICENSE("GPL");
0186 MODULE_AUTHOR("Julius Werner <jwerner@chromium.org>");
0187 MODULE_DESCRIPTION("ARM Secure Monitor Call Watchdog Driver");
0188 MODULE_VERSION(DRV_VERSION);