1 /*
2 * Copyright (c) 2006-2022, RT-Thread Development Team
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 *
6 * Change Logs:
7 * Date Author Notes
8 * 2022-11-26 GuEe-GUI first version
9 */
10
11 #include <rtthread.h>
12 #include <rtdevice.h>
13
14 #define DBG_TAG "wdt.i6300esb"
15 #define DBG_LVL DBG_INFO
16 #include <rtdbg.h>
17
18 #define I6300ESB_REG_BAR 0
19
20 /* PCI configuration registers */
21 #define ESB_CONFIG_PCI_REG 0x60 /* Config register */
22 #define ESB_LOCK_PCI_REG 0x68 /* WDT lock register */
23
24 /* Memory mapped registers */
25 #define ESB_TIMER1_REG 0x00 /* Timer1 value after each reset */
26 #define ESB_TIMER2_REG 0x04 /* Timer2 value after each reset */
27 #define ESB_GINTSR_REG 0x08 /* General Interrupt Status Reg */
28 #define ESB_RELOAD_REG 0x0c /* Reload register */
29
30 /* Lock register bits */
31 #define ESB_WDT_FUNC (0x01 << 2) /* Watchdog functionality */
32 #define ESB_WDT_ENABLE (0x01 << 1) /* Enable WDT */
33 #define ESB_WDT_LOCK (0x01 << 0) /* Lock (nowayout) */
34
35 /* Config register bits */
36 #define ESB_WDT_REBOOT (0x01 << 5) /* Enable reboot on timeout */
37 #define ESB_WDT_FREQ (0x01 << 2) /* Decrement frequency */
38 #define ESB_WDT_INTTYPE (0x03 << 0) /* Interrupt type on timer1 timeout */
39
40 /* Reload register bits */
41 #define ESB_WDT_TIMEOUT (0x01 << 9) /* Watchdog timed out */
42 #define ESB_WDT_RELOAD (0x01 << 8) /* prevent timeout */
43
44 /* Magic constants */
45 #define ESB_UNLOCK1 0x80 /* Step 1 to unlock reset registers */
46 #define ESB_UNLOCK2 0x86 /* Step 2 to unlock reset registers */
47
48 /* 30 sec default heartbeat (1 < heartbeat < 2*1023) */
49 #define ESB_HEARTBEAT_MIN 1
50 #define ESB_HEARTBEAT_MAX 2046
51 #define ESB_HEARTBEAT_DEFAULT 30
52
53 struct i6300esb_wdt
54 {
55 rt_watchdog_t parent;
56
57 void *regs;
58 rt_uint32_t timeout;
59 struct rt_pci_device *pdev;
60 };
61
62 #define raw_to_i6300esb_wdt(raw) rt_container_of(raw, struct i6300esb_wdt, parent)
63
64 /*
65 * Prepare for reloading the timer by unlocking the proper registers.
66 * This is performed by first writing 0x80 followed by 0x86 to the
67 * reload register. After this the appropriate registers can be written
68 * to once before they need to be unlocked again.
69 */
i6300esb_wdt_unlock_registers(struct i6300esb_wdt * esb)70 rt_inline void i6300esb_wdt_unlock_registers(struct i6300esb_wdt *esb)
71 {
72 HWREG16(esb->regs + ESB_RELOAD_REG) = ESB_UNLOCK1;
73 HWREG16(esb->regs + ESB_RELOAD_REG) = ESB_UNLOCK2;
74 }
75
i6300esb_timer_start(struct i6300esb_wdt * esb)76 static rt_uint32_t i6300esb_timer_start(struct i6300esb_wdt *esb)
77 {
78 i6300esb_wdt_unlock_registers(esb);
79 HWREG16(esb->regs + ESB_RELOAD_REG) = ESB_WDT_RELOAD;
80
81 rt_pci_write_config_u8(esb->pdev, ESB_LOCK_PCI_REG, ESB_WDT_ENABLE);
82
83 return RT_EOK;
84 }
85
i6300esb_timer_stop(struct i6300esb_wdt * esb)86 static rt_uint32_t i6300esb_timer_stop(struct i6300esb_wdt *esb)
87 {
88 rt_uint8_t val;
89
90 /* First, reset timers as suggested by the docs */
91 i6300esb_wdt_unlock_registers(esb);
92 HWREG16(esb->regs + ESB_RELOAD_REG) = ESB_WDT_RELOAD;
93
94 /* Then disable the WDT */
95 rt_pci_write_config_u8(esb->pdev, ESB_LOCK_PCI_REG, 0x0);
96 rt_pci_read_config_u8(esb->pdev, ESB_LOCK_PCI_REG, &val);
97
98 /* Returns 0 if the timer was disabled, non-zero otherwise */
99 return val & ESB_WDT_ENABLE;
100 }
101
esb_timer_keepalive(struct i6300esb_wdt * esb)102 static rt_err_t esb_timer_keepalive(struct i6300esb_wdt *esb)
103 {
104 i6300esb_wdt_unlock_registers(esb);
105 HWREG16(esb->regs + ESB_RELOAD_REG) = ESB_WDT_RELOAD;
106
107 return RT_EOK;
108 }
109
i6300esb_timer_set_heartbeat(struct i6300esb_wdt * esb,rt_uint32_t time)110 static rt_err_t i6300esb_timer_set_heartbeat(struct i6300esb_wdt *esb, rt_uint32_t time)
111 {
112 rt_uint32_t val;
113
114 /*
115 * We shift by 9, so if we are passed a value of 1 sec,
116 * val will be 1 << 9 = 512, then write that to two
117 * timers => 2 * 512 = 1024 (which is decremented at 1KHz)
118 */
119 val = time << 9;
120
121 /* Write timer 1 */
122 i6300esb_wdt_unlock_registers(esb);
123 HWREG32(esb->regs + ESB_TIMER1_REG) = val;
124
125 /* Write timer 2 */
126 i6300esb_wdt_unlock_registers(esb);
127 HWREG32(esb->regs + ESB_TIMER2_REG) = val;
128
129 /* Reload */
130 i6300esb_wdt_unlock_registers(esb);
131 HWREG16(esb->regs + ESB_RELOAD_REG) = ESB_WDT_RELOAD;
132
133 esb->timeout = time;
134
135 return RT_EOK;
136 }
137
i6300esb_wdt_init(rt_watchdog_t * wdt)138 static rt_err_t i6300esb_wdt_init(rt_watchdog_t *wdt)
139 {
140 return RT_EOK;
141 }
142
i6300esb_wdt_control(rt_watchdog_t * wdt,int cmd,void * args)143 static rt_err_t i6300esb_wdt_control(rt_watchdog_t *wdt, int cmd, void *args)
144 {
145 rt_err_t err = RT_EOK;
146 struct i6300esb_wdt *esb = raw_to_i6300esb_wdt(wdt);
147
148 switch (cmd)
149 {
150 case RT_DEVICE_CTRL_WDT_GET_TIMEOUT:
151 *(rt_uint32_t *)args = esb->timeout;
152 break;
153
154 case RT_DEVICE_CTRL_WDT_SET_TIMEOUT:
155 err = i6300esb_timer_set_heartbeat(esb, *(rt_uint32_t *)args);
156 break;
157
158 case RT_DEVICE_CTRL_WDT_KEEPALIVE:
159 err = esb_timer_keepalive(esb);
160 break;
161
162 case RT_DEVICE_CTRL_WDT_START:
163 err = i6300esb_timer_start(esb);
164 break;
165
166 case RT_DEVICE_CTRL_WDT_STOP:
167 err = i6300esb_timer_stop(esb);
168 break;
169
170 default:
171 err = -RT_EINVAL;
172 }
173
174 return err;
175 }
176
177 static const struct rt_watchdog_ops i6300esb_wdt_ops =
178 {
179 .init = i6300esb_wdt_init,
180 .control = i6300esb_wdt_control,
181 };
182
i6300esb_wdt_probe(struct rt_pci_device * pdev)183 static rt_err_t i6300esb_wdt_probe(struct rt_pci_device *pdev)
184 {
185 rt_err_t err;
186 rt_uint8_t val1;
187 rt_uint16_t val2;
188 const char *dev_name;
189 struct i6300esb_wdt *esb = rt_calloc(1, sizeof(*esb));
190
191 if (!esb)
192 {
193 return -RT_ENOMEM;
194 }
195
196 esb->regs = rt_pci_iomap(pdev, I6300ESB_REG_BAR);
197
198 if (!esb->regs)
199 {
200 err = -RT_EIO;
201
202 goto _fail;
203 }
204
205 /*
206 * Config register:
207 * Bit 5 : 0 = Enable WDT_OUTPUT
208 * Bit 2 : 0 = set the timer frequency to the PCI clock
209 * divided by 2^15 (approx 1KHz).
210 * Bits 1:0 : 11 = WDT_INT_TYPE Disabled.
211 * The watchdog has two timers, it can be setup so that the expiry of timer1
212 * results in an interrupt and the expiry of timer2 results in a reboot.
213 * We set it to not generate any interrupts as there is not much
214 * we can do with it right now.
215 */
216 rt_pci_write_config_u16(pdev, ESB_CONFIG_PCI_REG, 0x0003);
217
218 /* Check that the WDT isn't already locked */
219 rt_pci_read_config_u8(pdev, ESB_LOCK_PCI_REG, &val1);
220 if (val1 & ESB_WDT_LOCK)
221 {
222 LOG_W("Nowayout already set");
223 }
224
225 /* Set the timer to watchdog mode and disable it for now */
226 rt_pci_write_config_u8(pdev, ESB_LOCK_PCI_REG, 0x00);
227
228 /* Check if the watchdog was previously triggered */
229 i6300esb_wdt_unlock_registers(esb);
230 val2 = HWREG16(esb->regs + ESB_RELOAD_REG);
231 if (val2 & ESB_WDT_TIMEOUT)
232 {
233 LOG_D("Card previously reset the CPU");
234 }
235
236 /* Reset WDT_TIMEOUT flag and timers */
237 i6300esb_wdt_unlock_registers(esb);
238 HWREG16(esb->regs + ESB_RELOAD_REG) = ESB_WDT_TIMEOUT | ESB_WDT_RELOAD;
239
240 /* And set the correct timeout value */
241 i6300esb_timer_set_heartbeat(esb, ESB_HEARTBEAT_DEFAULT);
242
243 pdev->parent.user_data = esb;
244
245 esb->pdev = pdev;
246 esb->parent.ops = &i6300esb_wdt_ops;
247
248 rt_dm_dev_set_name_auto(&esb->parent.parent, "wdt");
249 dev_name = rt_dm_dev_get_name(&esb->parent.parent);
250 rt_hw_watchdog_register(&esb->parent, dev_name, 0, esb);
251
252 return RT_EOK;
253
254 _fail:
255 if (esb->regs)
256 {
257 rt_iounmap(esb->regs);
258 }
259
260 rt_free(esb);
261
262 return err;
263 }
264
i6300esb_wdt_remove(struct rt_pci_device * pdev)265 static rt_err_t i6300esb_wdt_remove(struct rt_pci_device *pdev)
266 {
267 struct i6300esb_wdt *esb = pdev->parent.user_data;
268
269 i6300esb_timer_stop(esb);
270
271 rt_device_unregister(&esb->parent.parent);
272
273 rt_iounmap(esb->regs);
274 rt_free(esb);
275
276 return RT_EOK;
277 }
278
279 static const struct rt_pci_device_id i6300esb_wdt_pci_ids[] =
280 {
281 { RT_PCI_DEVICE_ID(PCI_VENDOR_ID_INTEL, 0x25ab), },
282 { /* sentinel */ }
283 };
284
285 static struct rt_pci_driver i6300esb_wdt_driver =
286 {
287 .name = "i6300esb-wdt",
288
289 .ids = i6300esb_wdt_pci_ids,
290 .probe = i6300esb_wdt_probe,
291 .remove = i6300esb_wdt_remove,
292 };
293 RT_PCI_DRIVER_EXPORT(i6300esb_wdt_driver);
294