1*72a9e7feSJulius Werner // SPDX-License-Identifier: GPL-2.0-only 2*72a9e7feSJulius Werner /* 3*72a9e7feSJulius Werner * ARM Secure Monitor Call watchdog driver 4*72a9e7feSJulius Werner * 5*72a9e7feSJulius Werner * Copyright 2020 Google LLC. 6*72a9e7feSJulius Werner * Julius Werner <jwerner@chromium.org> 7*72a9e7feSJulius Werner * Based on mtk_wdt.c 8*72a9e7feSJulius Werner */ 9*72a9e7feSJulius Werner 10*72a9e7feSJulius Werner #include <linux/arm-smccc.h> 11*72a9e7feSJulius Werner #include <linux/err.h> 12*72a9e7feSJulius Werner #include <linux/module.h> 13*72a9e7feSJulius Werner #include <linux/moduleparam.h> 14*72a9e7feSJulius Werner #include <linux/of.h> 15*72a9e7feSJulius Werner #include <linux/platform_device.h> 16*72a9e7feSJulius Werner #include <linux/types.h> 17*72a9e7feSJulius Werner #include <linux/watchdog.h> 18*72a9e7feSJulius Werner #include <uapi/linux/psci.h> 19*72a9e7feSJulius Werner 20*72a9e7feSJulius Werner #define DRV_NAME "arm_smc_wdt" 21*72a9e7feSJulius Werner #define DRV_VERSION "1.0" 22*72a9e7feSJulius Werner 23*72a9e7feSJulius Werner enum smcwd_call { 24*72a9e7feSJulius Werner SMCWD_INIT = 0, 25*72a9e7feSJulius Werner SMCWD_SET_TIMEOUT = 1, 26*72a9e7feSJulius Werner SMCWD_ENABLE = 2, 27*72a9e7feSJulius Werner SMCWD_PET = 3, 28*72a9e7feSJulius Werner SMCWD_GET_TIMELEFT = 4, 29*72a9e7feSJulius Werner }; 30*72a9e7feSJulius Werner 31*72a9e7feSJulius Werner static bool nowayout = WATCHDOG_NOWAYOUT; 32*72a9e7feSJulius Werner static unsigned int timeout; 33*72a9e7feSJulius Werner 34*72a9e7feSJulius Werner static int smcwd_call(struct watchdog_device *wdd, enum smcwd_call call, 35*72a9e7feSJulius Werner unsigned long arg, struct arm_smccc_res *res) 36*72a9e7feSJulius Werner { 37*72a9e7feSJulius Werner struct arm_smccc_res local_res; 38*72a9e7feSJulius Werner 39*72a9e7feSJulius Werner if (!res) 40*72a9e7feSJulius Werner res = &local_res; 41*72a9e7feSJulius Werner 42*72a9e7feSJulius Werner arm_smccc_smc((u32)(uintptr_t)watchdog_get_drvdata(wdd), call, arg, 0, 43*72a9e7feSJulius Werner 0, 0, 0, 0, res); 44*72a9e7feSJulius Werner 45*72a9e7feSJulius Werner if (res->a0 == PSCI_RET_NOT_SUPPORTED) 46*72a9e7feSJulius Werner return -ENODEV; 47*72a9e7feSJulius Werner if (res->a0 == PSCI_RET_INVALID_PARAMS) 48*72a9e7feSJulius Werner return -EINVAL; 49*72a9e7feSJulius Werner if (res->a0 != PSCI_RET_SUCCESS) 50*72a9e7feSJulius Werner return -EIO; 51*72a9e7feSJulius Werner return 0; 52*72a9e7feSJulius Werner } 53*72a9e7feSJulius Werner 54*72a9e7feSJulius Werner static int smcwd_ping(struct watchdog_device *wdd) 55*72a9e7feSJulius Werner { 56*72a9e7feSJulius Werner return smcwd_call(wdd, SMCWD_PET, 0, NULL); 57*72a9e7feSJulius Werner } 58*72a9e7feSJulius Werner 59*72a9e7feSJulius Werner static unsigned int smcwd_get_timeleft(struct watchdog_device *wdd) 60*72a9e7feSJulius Werner { 61*72a9e7feSJulius Werner struct arm_smccc_res res; 62*72a9e7feSJulius Werner 63*72a9e7feSJulius Werner smcwd_call(wdd, SMCWD_GET_TIMELEFT, 0, &res); 64*72a9e7feSJulius Werner if (res.a0) 65*72a9e7feSJulius Werner return 0; 66*72a9e7feSJulius Werner return res.a1; 67*72a9e7feSJulius Werner } 68*72a9e7feSJulius Werner 69*72a9e7feSJulius Werner static int smcwd_set_timeout(struct watchdog_device *wdd, unsigned int timeout) 70*72a9e7feSJulius Werner { 71*72a9e7feSJulius Werner int res; 72*72a9e7feSJulius Werner 73*72a9e7feSJulius Werner res = smcwd_call(wdd, SMCWD_SET_TIMEOUT, timeout, NULL); 74*72a9e7feSJulius Werner if (!res) 75*72a9e7feSJulius Werner wdd->timeout = timeout; 76*72a9e7feSJulius Werner return res; 77*72a9e7feSJulius Werner } 78*72a9e7feSJulius Werner 79*72a9e7feSJulius Werner static int smcwd_stop(struct watchdog_device *wdd) 80*72a9e7feSJulius Werner { 81*72a9e7feSJulius Werner return smcwd_call(wdd, SMCWD_ENABLE, 0, NULL); 82*72a9e7feSJulius Werner } 83*72a9e7feSJulius Werner 84*72a9e7feSJulius Werner static int smcwd_start(struct watchdog_device *wdd) 85*72a9e7feSJulius Werner { 86*72a9e7feSJulius Werner return smcwd_call(wdd, SMCWD_ENABLE, 1, NULL); 87*72a9e7feSJulius Werner } 88*72a9e7feSJulius Werner 89*72a9e7feSJulius Werner static const struct watchdog_info smcwd_info = { 90*72a9e7feSJulius Werner .identity = DRV_NAME, 91*72a9e7feSJulius Werner .options = WDIOF_SETTIMEOUT | 92*72a9e7feSJulius Werner WDIOF_KEEPALIVEPING | 93*72a9e7feSJulius Werner WDIOF_MAGICCLOSE, 94*72a9e7feSJulius Werner }; 95*72a9e7feSJulius Werner 96*72a9e7feSJulius Werner static const struct watchdog_ops smcwd_ops = { 97*72a9e7feSJulius Werner .start = smcwd_start, 98*72a9e7feSJulius Werner .stop = smcwd_stop, 99*72a9e7feSJulius Werner .ping = smcwd_ping, 100*72a9e7feSJulius Werner .set_timeout = smcwd_set_timeout, 101*72a9e7feSJulius Werner }; 102*72a9e7feSJulius Werner 103*72a9e7feSJulius Werner static const struct watchdog_ops smcwd_timeleft_ops = { 104*72a9e7feSJulius Werner .start = smcwd_start, 105*72a9e7feSJulius Werner .stop = smcwd_stop, 106*72a9e7feSJulius Werner .ping = smcwd_ping, 107*72a9e7feSJulius Werner .set_timeout = smcwd_set_timeout, 108*72a9e7feSJulius Werner .get_timeleft = smcwd_get_timeleft, 109*72a9e7feSJulius Werner }; 110*72a9e7feSJulius Werner 111*72a9e7feSJulius Werner static int smcwd_probe(struct platform_device *pdev) 112*72a9e7feSJulius Werner { 113*72a9e7feSJulius Werner struct watchdog_device *wdd; 114*72a9e7feSJulius Werner int err; 115*72a9e7feSJulius Werner struct arm_smccc_res res; 116*72a9e7feSJulius Werner u32 smc_func_id; 117*72a9e7feSJulius Werner 118*72a9e7feSJulius Werner wdd = devm_kzalloc(&pdev->dev, sizeof(*wdd), GFP_KERNEL); 119*72a9e7feSJulius Werner if (!wdd) 120*72a9e7feSJulius Werner return -ENOMEM; 121*72a9e7feSJulius Werner platform_set_drvdata(pdev, wdd); 122*72a9e7feSJulius Werner 123*72a9e7feSJulius Werner if (of_property_read_u32(pdev->dev.of_node, "arm,smc-id", 124*72a9e7feSJulius Werner &smc_func_id)) 125*72a9e7feSJulius Werner smc_func_id = 0x82003D06; 126*72a9e7feSJulius Werner watchdog_set_drvdata(wdd, (void *)(uintptr_t)smc_func_id); 127*72a9e7feSJulius Werner 128*72a9e7feSJulius Werner err = smcwd_call(wdd, SMCWD_INIT, 0, &res); 129*72a9e7feSJulius Werner if (err < 0) 130*72a9e7feSJulius Werner return err; 131*72a9e7feSJulius Werner 132*72a9e7feSJulius Werner wdd->info = &smcwd_info; 133*72a9e7feSJulius Werner /* get_timeleft is optional */ 134*72a9e7feSJulius Werner if (smcwd_call(wdd, SMCWD_GET_TIMELEFT, 0, NULL)) 135*72a9e7feSJulius Werner wdd->ops = &smcwd_ops; 136*72a9e7feSJulius Werner else 137*72a9e7feSJulius Werner wdd->ops = &smcwd_timeleft_ops; 138*72a9e7feSJulius Werner wdd->timeout = res.a2; 139*72a9e7feSJulius Werner wdd->max_timeout = res.a2; 140*72a9e7feSJulius Werner wdd->min_timeout = res.a1; 141*72a9e7feSJulius Werner wdd->parent = &pdev->dev; 142*72a9e7feSJulius Werner 143*72a9e7feSJulius Werner watchdog_stop_on_reboot(wdd); 144*72a9e7feSJulius Werner watchdog_stop_on_unregister(wdd); 145*72a9e7feSJulius Werner watchdog_set_nowayout(wdd, nowayout); 146*72a9e7feSJulius Werner watchdog_init_timeout(wdd, timeout, &pdev->dev); 147*72a9e7feSJulius Werner err = smcwd_set_timeout(wdd, wdd->timeout); 148*72a9e7feSJulius Werner if (err) 149*72a9e7feSJulius Werner return err; 150*72a9e7feSJulius Werner 151*72a9e7feSJulius Werner err = devm_watchdog_register_device(&pdev->dev, wdd); 152*72a9e7feSJulius Werner if (err) 153*72a9e7feSJulius Werner return err; 154*72a9e7feSJulius Werner 155*72a9e7feSJulius Werner dev_info(&pdev->dev, 156*72a9e7feSJulius Werner "Watchdog registered (timeout=%d sec, nowayout=%d)\n", 157*72a9e7feSJulius Werner wdd->timeout, nowayout); 158*72a9e7feSJulius Werner 159*72a9e7feSJulius Werner return 0; 160*72a9e7feSJulius Werner } 161*72a9e7feSJulius Werner 162*72a9e7feSJulius Werner static const struct of_device_id smcwd_dt_ids[] = { 163*72a9e7feSJulius Werner { .compatible = "arm,smc-wdt" }, 164*72a9e7feSJulius Werner {} 165*72a9e7feSJulius Werner }; 166*72a9e7feSJulius Werner MODULE_DEVICE_TABLE(of, smcwd_dt_ids); 167*72a9e7feSJulius Werner 168*72a9e7feSJulius Werner static struct platform_driver smcwd_driver = { 169*72a9e7feSJulius Werner .probe = smcwd_probe, 170*72a9e7feSJulius Werner .driver = { 171*72a9e7feSJulius Werner .name = DRV_NAME, 172*72a9e7feSJulius Werner .of_match_table = smcwd_dt_ids, 173*72a9e7feSJulius Werner }, 174*72a9e7feSJulius Werner }; 175*72a9e7feSJulius Werner 176*72a9e7feSJulius Werner module_platform_driver(smcwd_driver); 177*72a9e7feSJulius Werner 178*72a9e7feSJulius Werner module_param(timeout, uint, 0); 179*72a9e7feSJulius Werner MODULE_PARM_DESC(timeout, "Watchdog heartbeat in seconds"); 180*72a9e7feSJulius Werner 181*72a9e7feSJulius Werner module_param(nowayout, bool, 0); 182*72a9e7feSJulius Werner MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" 183*72a9e7feSJulius Werner __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); 184*72a9e7feSJulius Werner 185*72a9e7feSJulius Werner MODULE_LICENSE("GPL"); 186*72a9e7feSJulius Werner MODULE_AUTHOR("Julius Werner <jwerner@chromium.org>"); 187*72a9e7feSJulius Werner MODULE_DESCRIPTION("ARM Secure Monitor Call Watchdog Driver"); 188*72a9e7feSJulius Werner MODULE_VERSION(DRV_VERSION); 189