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,
};
|