1 /*
2  * Copyright (c) 2016 Linaro Limited
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #define DT_DRV_COMPAT arm_cmsdk_watchdog
8 
9 /**
10  * @brief Driver for CMSDK APB Watchdog.
11  */
12 
13 #include <errno.h>
14 #include <soc.h>
15 #include <zephyr/arch/arm/nmi.h>
16 #include <zephyr/drivers/watchdog.h>
17 #include <zephyr/sys/printk.h>
18 #include <zephyr/sys/reboot.h>
19 
20 struct wdog_cmsdk_apb {
21 	/* offset: 0x000 (r/w) watchdog load register */
22 	volatile uint32_t  load;
23 	/* offset: 0x004 (r/ ) watchdog value register */
24 	volatile uint32_t  value;
25 	/* offset: 0x008 (r/w) watchdog control register */
26 	volatile uint32_t  ctrl;
27 	/* offset: 0x00c ( /w) watchdog clear interrupt register */
28 	volatile uint32_t  intclr;
29 	/* offset: 0x010 (r/ ) watchdog raw interrupt status register */
30 	volatile uint32_t  rawintstat;
31 	/* offset: 0x014 (r/ ) watchdog interrupt status register */
32 	volatile uint32_t  maskintstat;
33 	volatile uint32_t  reserved0[762];
34 	/* offset: 0xc00 (r/w) watchdog lock register */
35 	volatile uint32_t  lock;
36 	volatile uint32_t  reserved1[191];
37 	/* offset: 0xf00 (r/w) watchdog integration test control register */
38 	volatile uint32_t  itcr;
39 	/* offset: 0xf04 ( /w) watchdog integration test output set register */
40 	volatile uint32_t  itop;
41 };
42 
43 #define CMSDK_APB_WDOG_LOAD		(0xFFFFFFFF << 0)
44 #define CMSDK_APB_WDOG_RELOAD		(0xE4E1C00 << 0)
45 #define CMSDK_APB_WDOG_VALUE		(0xFFFFFFFF << 0)
46 #define CMSDK_APB_WDOG_CTRL_RESEN	(0x1 << 1)
47 #define CMSDK_APB_WDOG_CTRL_INTEN	(0x1 << 0)
48 #define CMSDK_APB_WDOG_INTCLR		(0x1 << 0)
49 #define CMSDK_APB_WDOG_RAWINTSTAT	(0x1 << 0)
50 #define CMSDK_APB_WDOG_MASKINTSTAT	(0x1 << 0)
51 #define CMSDK_APB_WDOG_LOCK		(0x1 << 0)
52 #define CMSDK_APB_WDOG_INTEGTESTEN	(0x1 << 0)
53 #define CMSDK_APB_WDOG_INTEGTESTOUTSET	(0x1 << 1)
54 
55 /*
56  * Value written to the LOCK register to lock or unlock the write access
57  * to all of the registers of the watchdog (except the LOCK register)
58  */
59 #define CMSDK_APB_WDOG_UNLOCK_VALUE (0x1ACCE551)
60 #define CMSDK_APB_WDOG_LOCK_VALUE (0x2BDDF662)
61 
62 #define WDOG_STRUCT \
63 	((volatile struct wdog_cmsdk_apb *)(DT_INST_REG_ADDR(0)))
64 
65 /* Keep reference of the device to pass it to the callback */
66 const struct device *wdog_r;
67 
68 /* watchdog reload value in clock cycles */
69 static unsigned int reload_cycles = CMSDK_APB_WDOG_RELOAD;
70 static uint8_t assigned_channels;
71 static uint8_t flags;
72 static bool enabled;
73 
74 static void (*user_cb)(const struct device *dev, int channel_id);
75 
wdog_cmsdk_apb_unlock(const struct device * dev)76 static void wdog_cmsdk_apb_unlock(const struct device *dev)
77 {
78 	volatile struct wdog_cmsdk_apb *wdog = WDOG_STRUCT;
79 
80 	ARG_UNUSED(dev);
81 
82 	wdog->lock = CMSDK_APB_WDOG_UNLOCK_VALUE;
83 }
84 
wdog_cmsdk_apb_setup(const struct device * dev,uint8_t options)85 static int wdog_cmsdk_apb_setup(const struct device *dev, uint8_t options)
86 {
87 	volatile struct wdog_cmsdk_apb *wdog = WDOG_STRUCT;
88 
89 	ARG_UNUSED(dev);
90 	ARG_UNUSED(options);
91 
92 	/* Check if watchdog is already running */
93 	if (enabled) {
94 		return -EBUSY;
95 	}
96 
97 	/* Reset pending interrupts before starting */
98 	wdog->intclr = CMSDK_APB_WDOG_INTCLR;
99 	wdog->load = reload_cycles;
100 
101 	/* Start the watchdog counter with INTEN bit */
102 	wdog->ctrl = (CMSDK_APB_WDOG_CTRL_RESEN | CMSDK_APB_WDOG_CTRL_INTEN);
103 
104 	enabled = true;
105 	return 0;
106 }
107 
wdog_cmsdk_apb_disable(const struct device * dev)108 static int wdog_cmsdk_apb_disable(const struct device *dev)
109 {
110 	volatile struct wdog_cmsdk_apb *wdog = WDOG_STRUCT;
111 
112 	ARG_UNUSED(dev);
113 
114 	/* Stop the watchdog counter with INTEN bit */
115 	wdog->ctrl = ~(CMSDK_APB_WDOG_CTRL_RESEN | CMSDK_APB_WDOG_CTRL_INTEN);
116 
117 	enabled = false;
118 	assigned_channels = 0;
119 
120 	return 0;
121 }
122 
wdog_cmsdk_apb_install_timeout(const struct device * dev,const struct wdt_timeout_cfg * config)123 static int wdog_cmsdk_apb_install_timeout(const struct device *dev,
124 					  const struct wdt_timeout_cfg *config)
125 {
126 	volatile struct wdog_cmsdk_apb *wdog = WDOG_STRUCT;
127 	uint32_t clk_freq_khz = DT_INST_PROP_BY_PHANDLE(0, clocks, clock_frequency) / 1000;
128 
129 	ARG_UNUSED(dev);
130 
131 	if (config->window.max == 0) {
132 		return -EINVAL;
133 	}
134 	if (enabled == true) {
135 		return -EBUSY;
136 	}
137 	if (assigned_channels == 1) {
138 		return -ENOMEM;
139 	}
140 
141 	/* Reload value */
142 	reload_cycles = config->window.max * clk_freq_khz;
143 	flags = config->flags;
144 
145 	wdog->load = reload_cycles;
146 
147 	/* Configure only the callback */
148 	user_cb = config->callback;
149 
150 	assigned_channels++;
151 	return 0;
152 }
153 
wdog_cmsdk_apb_feed(const struct device * dev,int channel_id)154 static int wdog_cmsdk_apb_feed(const struct device *dev, int channel_id)
155 {
156 	volatile struct wdog_cmsdk_apb *wdog = WDOG_STRUCT;
157 
158 	ARG_UNUSED(dev);
159 	ARG_UNUSED(channel_id);
160 
161 	/* Clear the interrupt */
162 	wdog->intclr = CMSDK_APB_WDOG_INTCLR;
163 
164 	/* Reload */
165 	wdog->load = reload_cycles;
166 
167 	return 0;
168 }
169 
170 static DEVICE_API(wdt, wdog_cmsdk_apb_api) = {
171 	.setup = wdog_cmsdk_apb_setup,
172 	.disable = wdog_cmsdk_apb_disable,
173 	.install_timeout = wdog_cmsdk_apb_install_timeout,
174 	.feed = wdog_cmsdk_apb_feed,
175 };
176 
177 #ifdef CONFIG_RUNTIME_NMI
178 
wdog_cmsdk_apb_has_fired(void)179 static int wdog_cmsdk_apb_has_fired(void)
180 {
181 	volatile struct wdog_cmsdk_apb *wdog = WDOG_STRUCT;
182 
183 	return (wdog->maskintstat & CMSDK_APB_WDOG_MASKINTSTAT) != 0U;
184 }
185 
wdog_cmsdk_apb_isr(void)186 static void wdog_cmsdk_apb_isr(void)
187 {
188 	/*
189 	 * Check if the watchdog was the reason of the NMI interrupt
190 	 * and if not, exit immediately
191 	 */
192 	if (!wdog_cmsdk_apb_has_fired()) {
193 		printk("NMI received! Rebooting...\n");
194 		/* In ARM implementation sys_reboot ignores the parameter */
195 		sys_reboot(0);
196 	} else {
197 		if (user_cb != NULL) {
198 			user_cb(wdog_r, 0);
199 		}
200 	}
201 }
202 #endif /* CONFIG_RUNTIME_NMI */
203 
wdog_cmsdk_apb_init(const struct device * dev)204 static int wdog_cmsdk_apb_init(const struct device *dev)
205 {
206 	volatile struct wdog_cmsdk_apb *wdog = WDOG_STRUCT;
207 
208 	wdog_r = dev;
209 
210 	/* unlock access to configuration registers */
211 	wdog_cmsdk_apb_unlock(dev);
212 
213 	/* set default reload value */
214 	wdog->load = reload_cycles;
215 
216 #ifdef CONFIG_RUNTIME_NMI
217 	/* Configure the interrupts */
218 	z_arm_nmi_set_handler(wdog_cmsdk_apb_isr);
219 #endif
220 
221 #ifdef CONFIG_WDOG_CMSDK_APB_START_AT_BOOT
222 	wdog_cmsdk_apb_setup(dev, 0);
223 #endif
224 
225 	return 0;
226 }
227 
228 DEVICE_DT_INST_DEFINE(0,
229 		    wdog_cmsdk_apb_init,
230 		    NULL,
231 		    NULL, NULL,
232 		    PRE_KERNEL_1, CONFIG_KERNEL_INIT_PRIORITY_DEVICE,
233 		    &wdog_cmsdk_apb_api);
234