Loading...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 | // SPDX-License-Identifier: GPL-2.0-or-later /* * ARM Secure Monitor Call watchdog driver * Copyright (C) 2022, STMicroelectronics - All Rights Reserved * This file is based on Linux driver drivers/watchdog/arm_smc_wdt.c */ #define LOG_CATEGORY UCLASS_WDT #include <dm.h> #include <dm/device_compat.h> #include <linux/arm-smccc.h> #include <linux/psci.h> #include <wdt.h> #define DRV_NAME "arm_smc_wdt" #define WDT_TIMEOUT_SECS(TIMEOUT) ((TIMEOUT) / 1000) enum smcwd_call { SMCWD_INIT = 0, SMCWD_SET_TIMEOUT = 1, SMCWD_ENABLE = 2, SMCWD_PET = 3, SMCWD_GET_TIMELEFT = 4, }; struct smcwd_priv_data { u32 smc_id; unsigned int min_timeout; unsigned int max_timeout; }; static int smcwd_call(struct udevice *dev, enum smcwd_call call, unsigned long arg, struct arm_smccc_res *res) { struct smcwd_priv_data *priv = dev_get_priv(dev); struct arm_smccc_res local_res; if (!res) res = &local_res; arm_smccc_smc(priv->smc_id, call, arg, 0, 0, 0, 0, 0, res); if (res->a0 == PSCI_RET_NOT_SUPPORTED) return -ENODEV; if (res->a0 == PSCI_RET_INVALID_PARAMS) return -EINVAL; if (res->a0 == PSCI_RET_DISABLED) return -ENODATA; if (res->a0 != PSCI_RET_SUCCESS) return -EIO; return 0; } static int smcwd_reset(struct udevice *dev) { return smcwd_call(dev, SMCWD_PET, 0, NULL); } static int smcwd_stop(struct udevice *dev) { return smcwd_call(dev, SMCWD_ENABLE, 0, NULL); } static int smcwd_start(struct udevice *dev, u64 timeout_ms, ulong flags) { struct smcwd_priv_data *priv = dev_get_priv(dev); u64 timeout_sec = WDT_TIMEOUT_SECS(timeout_ms); int err; if (timeout_sec < priv->min_timeout || timeout_sec > priv->max_timeout) { dev_err(dev, "Timeout value not supported\n"); return -EINVAL; } err = smcwd_call(dev, SMCWD_SET_TIMEOUT, timeout_sec, NULL); if (err) { dev_err(dev, "Timeout out configuration failed\n"); return err; } return smcwd_call(dev, SMCWD_ENABLE, 1, NULL); } static int smcwd_probe(struct udevice *dev) { struct smcwd_priv_data *priv = dev_get_priv(dev); struct arm_smccc_res res; int err; priv->smc_id = dev_read_u32_default(dev, "arm,smc-id", 0x82003D06); err = smcwd_call(dev, SMCWD_INIT, 0, &res); if (err < 0) { dev_err(dev, "Init failed %i\n", err); return err; } priv->min_timeout = res.a1; priv->max_timeout = res.a2; /* If already started, then force u-boot to use it */ err = smcwd_call(dev, SMCWD_GET_TIMELEFT, 0, NULL); switch (err) { case 0: dev_dbg(dev, "Already started\n"); wdt_set_force_autostart(dev); break; case -ENODATA: dev_dbg(dev, "Not already started\n"); break; default: /* Optional SMCWD_GET_TIMELEFT not implemented */ break; } return 0; } static const struct wdt_ops smcwd_ops = { .start = smcwd_start, .stop = smcwd_stop, .reset = smcwd_reset, }; static const struct udevice_id smcwd_dt_ids[] = { { .compatible = "arm,smc-wdt" }, {} }; U_BOOT_DRIVER(wdt_sandbox) = { .name = "smcwd", .id = UCLASS_WDT, .of_match = smcwd_dt_ids, .priv_auto = sizeof(struct smcwd_priv_data), .probe = smcwd_probe, .ops = &smcwd_ops, }; |