1 /*
2 * Copyright (c) 2022 Intel Corporation
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6 #define DT_DRV_COMPAT intel_tco_wdt
7
8 #include <zephyr/kernel.h>
9 #include <zephyr/drivers/watchdog.h>
10 #include <zephyr/logging/log.h>
11
12 LOG_MODULE_REGISTER(wdt_tco, CONFIG_WDT_LOG_LEVEL);
13
14 #define BASE(d) ((struct tco_config *)(d)->config)->base
15
16 #define TCO_RLD(d) (BASE(d) + 0x00) /* TCO Timer Reload/Curr. Value */
17 #define TCO_DAT_IN(d) (BASE(d) + 0x02) /* TCO Data In Register */
18 #define TCO_DAT_OUT(d) (BASE(d) + 0x03) /* TCO Data Out Register */
19 #define TCO1_STS(d) (BASE(d) + 0x04) /* TCO1 Status Register */
20 #define TCO2_STS(d) (BASE(d) + 0x06) /* TCO2 Status Register */
21 #define TCO1_CNT(d) (BASE(d) + 0x08) /* TCO1 Control Register */
22 #define TCO2_CNT(d) (BASE(d) + 0x0a) /* TCO2 Control Register */
23 #define TCO_MSG(d) (BASE(d) + 0x0c) /* TCO Message Registers */
24 #define TCO_WDSTATUS(d) (BASE(d) + 0x0e) /* TCO Watchdog Status Register */
25 #define TCO_TMR(d) (BASE(d) + 0x12) /* TCO Timer Register */
26
27 /* TCO1_STS bits */
28 #define STS_NMI2SMI BIT(0)
29 #define STS_OS_TCO_SMI BIT(1)
30 #define STS_TCO_INT BIT(2)
31 #define STS_TIMEOUT BIT(3)
32 #define STS_NEWCENTURY BIT(7)
33 #define STS_BIOSWR BIT(8)
34 #define STS_CPUSCI BIT(9)
35 #define STS_CPUSMI BIT(10)
36 #define STS_CPUSERR BIT(12)
37 #define STS_SLVSEL BIT(13)
38
39 /* TCO2_STS bits */
40 #define STS_INTRD_DET BIT(0)
41 #define STS_SECOND_TO BIT(1)
42 #define STS_NRSTRAP BIT(2)
43 #define STS_SMLINK_SLAVE_SMI BIT(3)
44
45 /* TCO1_CNT bits */
46 #define CNT_NR_MSUS BIT(0)
47 #define CNT_NMI_NOW BIT(8)
48 #define CNT_NMI2SMI_EN BIT(9)
49 #define CNT_TCO_TMR_HALT BIT(11)
50 #define CNT_TCO_LOCK BIT(12)
51
52 /* TCO_TMR bits */
53 #define TMR_TCOTMR BIT_MASK(10)
54 #define TMR_MIN 0x04
55 #define TMR_MAX 0x3f
56
57 struct tco_data {
58 struct k_spinlock lock;
59 bool no_reboot;
60 };
61
62 struct tco_config {
63 io_port_t base;
64 };
65
set_no_reboot(const struct device * dev,bool set)66 static int set_no_reboot(const struct device *dev, bool set)
67 {
68 uint16_t val, newval;
69
70 val = sys_in16(TCO1_CNT(dev));
71
72 if (set) {
73 val |= CNT_NR_MSUS;
74 } else {
75 val &= ~CNT_NR_MSUS;
76 }
77
78 sys_out16(val, TCO1_CNT(dev));
79 newval = sys_in16(TCO1_CNT(dev));
80
81 if (val != newval) {
82 return -EIO;
83 }
84
85 return 0;
86 }
87
tco_setup(const struct device * dev,uint8_t options)88 static int tco_setup(const struct device *dev, uint8_t options)
89 {
90 struct tco_data *data = dev->data;
91 k_spinlock_key_t key;
92 uint16_t val;
93 int err;
94
95 key = k_spin_lock(&data->lock);
96
97 err = set_no_reboot(dev, data->no_reboot);
98 if (err) {
99 k_spin_unlock(&data->lock, key);
100 LOG_ERR("Failed to update no_reboot bit (err %d)", err);
101 return err;
102 }
103
104 /* Reload the timer */
105 sys_out16(0x01, TCO_RLD(dev));
106
107 /* Enable the timer to start counting by clearing the TCO_TMR_HALT field */
108 val = sys_in16(TCO1_CNT(dev));
109 val &= ~CNT_TCO_TMR_HALT;
110 sys_out16(val, TCO1_CNT(dev));
111 val = sys_in16(TCO1_CNT(dev));
112
113 k_spin_unlock(&data->lock, key);
114
115 if ((val & CNT_TCO_TMR_HALT) == CNT_TCO_TMR_HALT) {
116 return -EIO;
117 }
118
119 return 0;
120 }
121
tco_disable(const struct device * dev)122 static int tco_disable(const struct device *dev)
123 {
124 struct tco_data *data = dev->data;
125 k_spinlock_key_t key;
126 uint16_t val;
127
128 key = k_spin_lock(&data->lock);
129
130 /* Set the TCO_TMR_HALT field so that the timer gets halted */
131 val = sys_in16(TCO1_CNT(dev));
132 val |= CNT_TCO_TMR_HALT;
133 sys_out16(val, TCO1_CNT(dev));
134 val = sys_in16(TCO1_CNT(dev));
135
136 set_no_reboot(dev, true);
137
138 k_spin_unlock(&data->lock, key);
139
140 if ((val & CNT_TCO_TMR_HALT) == 0) {
141 return -EIO;
142 }
143
144 return 0;
145 }
146
msec_to_ticks(uint32_t msec)147 static uint16_t msec_to_ticks(uint32_t msec)
148 {
149 /* Convert from milliseconds to timer ticks. The timer is clocked at
150 * approximately 0.6 seconds.
151 */
152 return ((msec / MSEC_PER_SEC) * 10) / 6;
153 }
154
tco_install_timeout(const struct device * dev,const struct wdt_timeout_cfg * cfg)155 static int tco_install_timeout(const struct device *dev,
156 const struct wdt_timeout_cfg *cfg)
157 {
158 struct tco_data *data = dev->data;
159 k_spinlock_key_t key;
160 uint16_t val, ticks;
161
162 /* TCO watchdog doesn't support windowed timeouts */
163 if (cfg->window.min != 0) {
164 return -EINVAL;
165 }
166
167 /* No callback support */
168 if (cfg->callback != NULL) {
169 return -ENOTSUP;
170 }
171
172 ticks = msec_to_ticks(cfg->window.max);
173
174 LOG_DBG("window.max %u -> ticks %u", cfg->window.max, ticks);
175
176 if (ticks < TMR_MIN || ticks > TMR_MAX) {
177 return -EINVAL;
178 }
179
180 switch (cfg->flags) {
181 case WDT_FLAG_RESET_SOC:
182 data->no_reboot = false;
183 break;
184 case WDT_FLAG_RESET_NONE:
185 data->no_reboot = true;
186 break;
187 case WDT_FLAG_RESET_CPU_CORE:
188 LOG_ERR("CPU-only reset not supported");
189 return -ENOTSUP;
190 default:
191 LOG_ERR("Unknown watchdog configuration flags");
192 return -EINVAL;
193 }
194
195 key = k_spin_lock(&data->lock);
196
197 /* Set the TCO_TMR field. This value is loaded into the timer each time
198 * the TCO_RLD register is written.
199 */
200 val = sys_in16(TCO_TMR(dev));
201 val &= ~TMR_TCOTMR;
202 val |= ticks;
203 sys_out16(val, TCO_TMR(dev));
204 val = sys_in16(TCO_TMR(dev));
205
206 k_spin_unlock(&data->lock, key);
207
208 if ((val & TMR_TCOTMR) != ticks) {
209 LOG_ERR("val %u", val);
210 return -EIO;
211 }
212
213 return 0;
214 }
215
tco_feed(const struct device * dev,int channel_id)216 static int tco_feed(const struct device *dev, int channel_id)
217 {
218 struct tco_data *data = dev->data;
219 k_spinlock_key_t key;
220
221 key = k_spin_lock(&data->lock);
222
223 /* TCORLD: Writing any value to this register will reload the timer to
224 * prevent the timeout.
225 */
226 sys_out16(0x01, TCO_RLD(dev));
227
228 k_spin_unlock(&data->lock, key);
229
230 return 0;
231 }
232
233 static DEVICE_API(wdt, tco_driver_api) = {
234 .setup = tco_setup,
235 .disable = tco_disable,
236 .install_timeout = tco_install_timeout,
237 .feed = tco_feed,
238 };
239
wdt_init(const struct device * dev)240 static int wdt_init(const struct device *dev)
241 {
242 const struct tco_config *config = dev->config;
243 struct tco_data *data = dev->data;
244 k_spinlock_key_t key;
245
246 LOG_DBG("Using 0x%04x as TCOBA", config->base);
247
248 key = k_spin_lock(&data->lock);
249
250 sys_out16(STS_TIMEOUT, TCO1_STS(dev)); /* Clear the Time Out Status bit */
251 sys_out16(STS_SECOND_TO, TCO2_STS(dev)); /* Clear SECOND_TO_STS bit */
252
253 set_no_reboot(dev, data->no_reboot);
254
255 k_spin_unlock(&data->lock, key);
256
257 if (IS_ENABLED(CONFIG_WDT_DISABLE_AT_BOOT)) {
258 tco_disable(dev);
259 }
260
261 return 0;
262 }
263
264 static struct tco_data wdt_data = {
265 };
266
267 static const struct tco_config wdt_config = {
268 .base = DT_INST_REG_ADDR(0),
269 };
270
271 DEVICE_DT_INST_DEFINE(0, wdt_init, NULL, &wdt_data, &wdt_config,
272 POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE,
273 &tco_driver_api);
274