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 
73 static void (*user_cb)(const struct device *dev, int channel_id);
74 
wdog_cmsdk_apb_unlock(const struct device * dev)75 static void wdog_cmsdk_apb_unlock(const struct device *dev)
76 {
77 	volatile struct wdog_cmsdk_apb *wdog = WDOG_STRUCT;
78 
79 	ARG_UNUSED(dev);
80 
81 	wdog->lock = CMSDK_APB_WDOG_UNLOCK_VALUE;
82 }
83 
wdog_cmsdk_apb_setup(const struct device * dev,uint8_t options)84 static int wdog_cmsdk_apb_setup(const struct device *dev, uint8_t options)
85 {
86 	volatile struct wdog_cmsdk_apb *wdog = WDOG_STRUCT;
87 
88 	ARG_UNUSED(dev);
89 	ARG_UNUSED(options);
90 
91 	/* Start the watchdog counter with INTEN bit */
92 	wdog->ctrl = (CMSDK_APB_WDOG_CTRL_RESEN | CMSDK_APB_WDOG_CTRL_INTEN);
93 
94 	return 0;
95 }
96 
wdog_cmsdk_apb_disable(const struct device * dev)97 static int wdog_cmsdk_apb_disable(const struct device *dev)
98 {
99 	volatile struct wdog_cmsdk_apb *wdog = WDOG_STRUCT;
100 
101 	ARG_UNUSED(dev);
102 
103 	/* Stop the watchdog counter with INTEN bit */
104 	wdog->ctrl = ~(CMSDK_APB_WDOG_CTRL_RESEN | CMSDK_APB_WDOG_CTRL_INTEN);
105 
106 	assigned_channels = 0;
107 
108 	return 0;
109 }
110 
wdog_cmsdk_apb_install_timeout(const struct device * dev,const struct wdt_timeout_cfg * config)111 static int wdog_cmsdk_apb_install_timeout(const struct device *dev,
112 					  const struct wdt_timeout_cfg *config)
113 {
114 	volatile struct wdog_cmsdk_apb *wdog = WDOG_STRUCT;
115 	uint32_t clk_freq_khz = DT_INST_PROP_BY_PHANDLE(0, clocks, clock_frequency) / 1000;
116 
117 	ARG_UNUSED(dev);
118 
119 	if (config->window.max == 0) {
120 		return -EINVAL;
121 	}
122 	if (assigned_channels == 1) {
123 		return -ENOMEM;
124 	}
125 
126 	/* Reload value */
127 	reload_cycles = config->window.max * clk_freq_khz;
128 	flags = config->flags;
129 
130 	wdog->load = reload_cycles;
131 
132 	/* Configure only the callback */
133 	user_cb = config->callback;
134 
135 	assigned_channels++;
136 	return 0;
137 }
138 
wdog_cmsdk_apb_feed(const struct device * dev,int channel_id)139 static int wdog_cmsdk_apb_feed(const struct device *dev, int channel_id)
140 {
141 	volatile struct wdog_cmsdk_apb *wdog = WDOG_STRUCT;
142 
143 	ARG_UNUSED(dev);
144 	ARG_UNUSED(channel_id);
145 
146 	/* Clear the interrupt */
147 	wdog->intclr = CMSDK_APB_WDOG_INTCLR;
148 
149 	/* Reload */
150 	wdog->load = reload_cycles;
151 
152 	return 0;
153 }
154 
155 static DEVICE_API(wdt, wdog_cmsdk_apb_api) = {
156 	.setup = wdog_cmsdk_apb_setup,
157 	.disable = wdog_cmsdk_apb_disable,
158 	.install_timeout = wdog_cmsdk_apb_install_timeout,
159 	.feed = wdog_cmsdk_apb_feed,
160 };
161 
162 #ifdef CONFIG_RUNTIME_NMI
163 
wdog_cmsdk_apb_has_fired(void)164 static int wdog_cmsdk_apb_has_fired(void)
165 {
166 	volatile struct wdog_cmsdk_apb *wdog = WDOG_STRUCT;
167 
168 	return (wdog->maskintstat & CMSDK_APB_WDOG_MASKINTSTAT) != 0U;
169 }
170 
wdog_cmsdk_apb_isr(void)171 static void wdog_cmsdk_apb_isr(void)
172 {
173 	/*
174 	 * Check if the watchdog was the reason of the NMI interrupt
175 	 * and if not, exit immediately
176 	 */
177 	if (!wdog_cmsdk_apb_has_fired()) {
178 		printk("NMI received! Rebooting...\n");
179 		/* In ARM implementation sys_reboot ignores the parameter */
180 		sys_reboot(0);
181 	} else {
182 		if (user_cb != NULL) {
183 			user_cb(wdog_r, 0);
184 		}
185 	}
186 }
187 #endif /* CONFIG_RUNTIME_NMI */
188 
wdog_cmsdk_apb_init(const struct device * dev)189 static int wdog_cmsdk_apb_init(const struct device *dev)
190 {
191 	volatile struct wdog_cmsdk_apb *wdog = WDOG_STRUCT;
192 
193 	wdog_r = dev;
194 
195 	/* unlock access to configuration registers */
196 	wdog_cmsdk_apb_unlock(dev);
197 
198 	/* set default reload value */
199 	wdog->load = reload_cycles;
200 
201 #ifdef CONFIG_RUNTIME_NMI
202 	/* Configure the interrupts */
203 	z_arm_nmi_set_handler(wdog_cmsdk_apb_isr);
204 #endif
205 
206 #ifdef CONFIG_WDOG_CMSDK_APB_START_AT_BOOT
207 	wdog_cmsdk_apb_setup(dev, 0);
208 #endif
209 
210 	return 0;
211 }
212 
213 DEVICE_DT_INST_DEFINE(0,
214 		    wdog_cmsdk_apb_init,
215 		    NULL,
216 		    NULL, NULL,
217 		    PRE_KERNEL_1, CONFIG_KERNEL_INIT_PRIORITY_DEVICE,
218 		    &wdog_cmsdk_apb_api);
219