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