1 // SPDX-License-Identifier: BSD-2-Clause
2 /*
3 * Copyright 2022 Microchip
4 */
5
6 #include <assert.h>
7 #include <drivers/clk.h>
8 #include <drivers/clk_dt.h>
9 #include <drivers/wdt.h>
10 #include <io.h>
11 #include <kernel/delay.h>
12 #include <kernel/dt.h>
13 #include <kernel/pm.h>
14 #include <matrix.h>
15 #include <sama5d2.h>
16 #include <tee_api_types.h>
17
18 #define WDT_CR 0x0
19 #define WDT_CR_KEY SHIFT_U32(0xA5, 24)
20 #define WDT_CR_WDRSTT BIT(0)
21
22 #define WDT_MR 0x4
23 #define WDT_MR_WDV GENMASK_32(11, 0)
24 #define WDT_MR_WDV_SET(val) ((val) & WDT_MR_WDV)
25 #define WDT_MR_WDFIEN BIT(12)
26 #define WDT_MR_WDRSTEN BIT(13)
27 #define WDT_MR_WDDIS BIT(15)
28 #define WDT_MR_WDD_SHIFT 16
29 #define WDT_MR_WDD_MASK GENMASK_32(11, 0)
30 #define WDT_MR_WDD SHIFT_U32(WDT_MR_WDD_MASK, WDT_MR_WDD_SHIFT)
31 #define WDT_MR_WDD_SET(val) \
32 SHIFT_U32(((val) & WDT_MR_WDD_MASK), WDT_MR_WDD_SHIFT)
33 #define WDT_MR_WDDBGHLT BIT(28)
34 #define WDT_MR_WDIDLEHLT BIT(29)
35
36 #define WDT_SR 0x8
37 #define WDT_SR_DUNF BIT(0)
38 #define WDT_SR_DERR BIT(1)
39
40 /*
41 * The watchdog is clocked by a 32768Hz clock/128 and the counter is on
42 * 12 bits.
43 */
44 #define SLOW_CLOCK_FREQ (32768)
45 #define WDT_CLOCK_FREQ (SLOW_CLOCK_FREQ / 128)
46 #define WDT_MIN_TIMEOUT 1
47 #define WDT_MAX_TIMEOUT (BIT(12) / WDT_CLOCK_FREQ)
48
49 #define WDT_DEFAULT_TIMEOUT WDT_MAX_TIMEOUT
50
51 /*
52 * We must wait at least 3 clocks period before accessing registers MR and CR.
53 * Ensure that we see at least 4 edges
54 */
55 #define WDT_REG_ACCESS_UDELAY (1000000ULL / SLOW_CLOCK_FREQ * 4)
56
57 #define SEC_TO_WDT(sec) (((sec) * WDT_CLOCK_FREQ) - 1)
58
59 #define WDT_ENABLED(mr) (!((mr) & WDT_MR_WDDIS))
60
61 struct atmel_wdt {
62 struct wdt_chip chip;
63 vaddr_t base;
64 unsigned long rate;
65 uint32_t mr;
66 bool enabled;
67 };
68
atmel_wdt_write_sleep(struct atmel_wdt * wdt,uint32_t reg,uint32_t val)69 static void atmel_wdt_write_sleep(struct atmel_wdt *wdt, uint32_t reg,
70 uint32_t val)
71 {
72 udelay(WDT_REG_ACCESS_UDELAY);
73
74 io_write32(wdt->base + reg, val);
75 }
76
atmel_wdt_settimeout(struct wdt_chip * chip,unsigned long timeout)77 static TEE_Result atmel_wdt_settimeout(struct wdt_chip *chip,
78 unsigned long timeout)
79 {
80 struct atmel_wdt *wdt = container_of(chip, struct atmel_wdt, chip);
81
82 wdt->mr &= ~WDT_MR_WDV;
83 wdt->mr |= WDT_MR_WDV_SET(SEC_TO_WDT(timeout));
84
85 /* WDV and WDD can only be updated when the watchdog is running */
86 if (WDT_ENABLED(wdt->mr))
87 atmel_wdt_write_sleep(wdt, WDT_MR, wdt->mr);
88
89 return TEE_SUCCESS;
90 }
91
atmel_wdt_ping(struct wdt_chip * chip)92 static void atmel_wdt_ping(struct wdt_chip *chip)
93 {
94 struct atmel_wdt *wdt = container_of(chip, struct atmel_wdt, chip);
95
96 atmel_wdt_write_sleep(wdt, WDT_CR, WDT_CR_KEY | WDT_CR_WDRSTT);
97 }
98
atmel_wdt_start(struct atmel_wdt * wdt)99 static void atmel_wdt_start(struct atmel_wdt *wdt)
100 {
101 wdt->mr &= ~WDT_MR_WDDIS;
102 atmel_wdt_write_sleep(wdt, WDT_MR, wdt->mr);
103 }
104
atmel_wdt_enable(struct wdt_chip * chip)105 static void atmel_wdt_enable(struct wdt_chip *chip)
106 {
107 struct atmel_wdt *wdt = container_of(chip, struct atmel_wdt, chip);
108
109 wdt->enabled = true;
110 atmel_wdt_start(wdt);
111 }
112
atmel_wdt_stop(struct atmel_wdt * wdt)113 static void atmel_wdt_stop(struct atmel_wdt *wdt)
114 {
115 wdt->mr |= WDT_MR_WDDIS;
116 atmel_wdt_write_sleep(wdt, WDT_MR, wdt->mr);
117 }
118
atmel_wdt_disable(struct wdt_chip * chip)119 static void atmel_wdt_disable(struct wdt_chip *chip)
120 {
121 struct atmel_wdt *wdt = container_of(chip, struct atmel_wdt, chip);
122
123 wdt->enabled = false;
124 atmel_wdt_stop(wdt);
125 }
126
atmel_wdt_itr_cb(struct itr_handler * h)127 static enum itr_return atmel_wdt_itr_cb(struct itr_handler *h)
128 {
129 struct atmel_wdt *wdt = h->data;
130 uint32_t sr = io_read32(wdt->base + WDT_SR);
131
132 if (sr & WDT_SR_DUNF)
133 DMSG("Watchdog Underflow !");
134 if (sr & WDT_SR_DERR)
135 DMSG("Watchdog Error !");
136
137 panic("Watchdog interrupt");
138
139 return ITRR_HANDLED;
140 }
141
atmel_wdt_init(struct wdt_chip * chip __unused,unsigned long * min_timeout,unsigned long * max_timeout)142 static TEE_Result atmel_wdt_init(struct wdt_chip *chip __unused,
143 unsigned long *min_timeout,
144 unsigned long *max_timeout)
145 {
146 *min_timeout = WDT_MIN_TIMEOUT;
147 *max_timeout = WDT_MAX_TIMEOUT;
148
149 return TEE_SUCCESS;
150 }
151
152 static const struct wdt_ops atmel_wdt_ops = {
153 .init = atmel_wdt_init,
154 .start = atmel_wdt_enable,
155 .stop = atmel_wdt_disable,
156 .ping = atmel_wdt_ping,
157 .set_timeout = atmel_wdt_settimeout,
158 };
159
atmel_wdt_init_hw(struct atmel_wdt * wdt)160 static void atmel_wdt_init_hw(struct atmel_wdt *wdt)
161 {
162 uint32_t mr = 0;
163
164 /*
165 * If we are resuming, we disabled the watchdog on suspend but the
166 * bootloader might have enabled the watchdog. If so, disable it
167 * properly.
168 */
169 if (!WDT_ENABLED(wdt->mr)) {
170 mr = io_read32(wdt->base + WDT_MR);
171 if (WDT_ENABLED(mr))
172 io_write32(wdt->base + WDT_MR, mr | WDT_MR_WDDIS);
173 }
174
175 /* Enable interrupt, and disable watchdog in debug and idle */
176 wdt->mr |= WDT_MR_WDFIEN | WDT_MR_WDDBGHLT | WDT_MR_WDIDLEHLT;
177 /* Enable watchdog reset */
178 wdt->mr |= WDT_MR_WDRSTEN;
179 wdt->mr |= WDT_MR_WDD_SET(SEC_TO_WDT(WDT_MAX_TIMEOUT));
180 wdt->mr |= WDT_MR_WDV_SET(SEC_TO_WDT(WDT_DEFAULT_TIMEOUT));
181
182 /*
183 * If the watchdog was enabled, write the configuration which will ping
184 * the watchdog.
185 */
186 if (WDT_ENABLED(wdt->mr))
187 io_write32(wdt->base + WDT_MR, wdt->mr);
188 }
189
190 #ifdef CFG_PM_ARM32
atmel_wdt_pm(enum pm_op op,uint32_t pm_hint __unused,const struct pm_callback_handle * hdl)191 static TEE_Result atmel_wdt_pm(enum pm_op op, uint32_t pm_hint __unused,
192 const struct pm_callback_handle *hdl)
193 {
194 struct atmel_wdt *wdt = hdl->handle;
195
196 switch (op) {
197 case PM_OP_RESUME:
198 atmel_wdt_init_hw(wdt);
199 if (wdt->enabled)
200 atmel_wdt_start(wdt);
201 break;
202 case PM_OP_SUSPEND:
203 if (wdt->enabled)
204 atmel_wdt_stop(wdt);
205 break;
206 default:
207 panic("Invalid PM operation");
208 }
209
210 return TEE_SUCCESS;
211 }
212
atmel_wdt_register_pm(struct atmel_wdt * wdt)213 static void atmel_wdt_register_pm(struct atmel_wdt *wdt)
214 {
215 register_pm_driver_cb(atmel_wdt_pm, wdt, "atmel_wdt");
216 }
217 #else
atmel_wdt_register_pm(struct atmel_wdt * wdt __unused)218 static void atmel_wdt_register_pm(struct atmel_wdt *wdt __unused)
219 {
220 }
221 #endif
222
wdt_node_probe(const void * fdt,int node,const void * compat_data __unused)223 static TEE_Result wdt_node_probe(const void *fdt, int node,
224 const void *compat_data __unused)
225 {
226 size_t size = 0;
227 struct atmel_wdt *wdt;
228 uint32_t irq_type = 0;
229 uint32_t irq_prio = 0;
230 int it = DT_INFO_INVALID_INTERRUPT;
231 struct itr_handler *it_hdlr;
232
233 if (_fdt_get_status(fdt, node) != DT_STATUS_OK_SEC)
234 return TEE_ERROR_BAD_PARAMETERS;
235
236 matrix_configure_periph_secure(AT91C_ID_WDT);
237
238 wdt = calloc(1, sizeof(*wdt));
239 if (!wdt)
240 return TEE_ERROR_OUT_OF_MEMORY;
241
242 wdt->chip.ops = &atmel_wdt_ops;
243
244 it = dt_get_irq_type_prio(fdt, node, &irq_type, &irq_prio);
245 if (it == DT_INFO_INVALID_INTERRUPT)
246 goto err_free_wdt;
247
248 it_hdlr = itr_alloc_add_type_prio(it, &atmel_wdt_itr_cb, 0, wdt,
249 irq_type, irq_prio);
250 if (!it_hdlr)
251 goto err_free_wdt;
252
253 if (dt_map_dev(fdt, node, &wdt->base, &size, DT_MAP_AUTO) < 0)
254 goto err_free_itr_handler;
255
256 /* Get current state of the watchdog */
257 wdt->mr = io_read32(wdt->base + WDT_MR) & WDT_MR_WDDIS;
258
259 atmel_wdt_init_hw(wdt);
260 itr_enable(it);
261 atmel_wdt_register_pm(wdt);
262
263 return watchdog_register(&wdt->chip);
264
265 err_free_itr_handler:
266 itr_free(it_hdlr);
267 err_free_wdt:
268 free(wdt);
269
270 return TEE_ERROR_GENERIC;
271 }
272
273 static const struct dt_device_match atmel_wdt_match_table[] = {
274 { .compatible = "atmel,sama5d4-wdt" },
275 { }
276 };
277
278 DEFINE_DT_DRIVER(atmel_wdt_dt_driver) = {
279 .name = "atmel_wdt",
280 .type = DT_DRIVER_NOTYPE,
281 .match_table = atmel_wdt_match_table,
282 .probe = wdt_node_probe,
283 };
284