1 /*
2  * Copyright (c) 2022, Jamie McCrae
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #define DT_DRV_COMPAT raspberrypi_pico_watchdog
8 
9 #include <hardware/watchdog.h>
10 #include <hardware/structs/psm.h>
11 #include <zephyr/drivers/watchdog.h>
12 #include <zephyr/sys_clock.h>
13 
14 #include <zephyr/logging/log.h>
15 LOG_MODULE_REGISTER(wdt_rpi_pico, CONFIG_WDT_LOG_LEVEL);
16 
17 /* Maximum watchdog time is halved due to errata RP2040-E1 */
18 #define RPI_PICO_MAX_WDT_TIME (0xffffff / 2)
19 #define RPI_PICO_WDT_TIME_MULTIPLICATION_FACTOR 2
20 
21 /* Watchdog requires a 1MHz clock source, divided from the crystal oscillator */
22 #define RPI_PICO_XTAL_FREQ_WDT_TICK_DIVISOR 1000000
23 
24 struct wdt_rpi_pico_data {
25 	uint8_t reset_type;
26 	uint32_t load;
27 	bool enabled;
28 };
29 
30 struct wdt_rpi_pico_config {
31 	uint32_t xtal_frequency;
32 };
33 
wdt_rpi_pico_setup(const struct device * dev,uint8_t options)34 static int wdt_rpi_pico_setup(const struct device *dev, uint8_t options)
35 {
36 	const struct wdt_rpi_pico_config *config = dev->config;
37 	struct wdt_rpi_pico_data *data = dev->data;
38 
39 	if ((options & WDT_OPT_PAUSE_IN_SLEEP) == 1) {
40 		return -ENOTSUP;
41 	}
42 
43 	hw_clear_bits(&watchdog_hw->ctrl, WATCHDOG_CTRL_ENABLE_BITS);
44 
45 	psm_hw->wdsel = 0;
46 
47 	/* TODO: Handle individual core reset when SMP support for RP2040 is added */
48 	if (data->reset_type == WDT_FLAG_RESET_SOC) {
49 		hw_set_bits(&psm_hw->wdsel, PSM_WDSEL_BITS);
50 	} else if (data->reset_type == WDT_FLAG_RESET_CPU_CORE) {
51 		hw_set_bits(&psm_hw->wdsel, PSM_WDSEL_PROC0_BITS);
52 	}
53 
54 	if ((options & WDT_OPT_PAUSE_HALTED_BY_DBG) == 0) {
55 		hw_clear_bits(&watchdog_hw->ctrl,
56 			      (WATCHDOG_CTRL_PAUSE_JTAG_BITS | WATCHDOG_CTRL_PAUSE_DBG0_BITS |
57 			       WATCHDOG_CTRL_PAUSE_DBG1_BITS));
58 	} else {
59 		hw_set_bits(&watchdog_hw->ctrl,
60 			    (WATCHDOG_CTRL_PAUSE_JTAG_BITS | WATCHDOG_CTRL_PAUSE_DBG0_BITS |
61 			     WATCHDOG_CTRL_PAUSE_DBG1_BITS));
62 	}
63 
64 	watchdog_hw->load = data->load;
65 
66 	/* Zero out the scratch registers so that the module reboots at the
67 	 * default program counter
68 	 */
69 	watchdog_hw->scratch[4] = 0;
70 	watchdog_hw->scratch[5] = 0;
71 	watchdog_hw->scratch[6] = 0;
72 	watchdog_hw->scratch[7] = 0;
73 
74 	hw_set_bits(&watchdog_hw->ctrl, WATCHDOG_CTRL_ENABLE_BITS);
75 
76 	data->enabled = true;
77 
78 	watchdog_hw->tick = (config->xtal_frequency / RPI_PICO_XTAL_FREQ_WDT_TICK_DIVISOR) |
79 			    WATCHDOG_TICK_ENABLE_BITS;
80 
81 	return 0;
82 }
83 
wdt_rpi_pico_disable(const struct device * dev)84 static int wdt_rpi_pico_disable(const struct device *dev)
85 {
86 	struct wdt_rpi_pico_data *data = dev->data;
87 
88 	if (data->enabled == false) {
89 		return -EFAULT;
90 	}
91 
92 	hw_clear_bits(&watchdog_hw->ctrl, WATCHDOG_CTRL_ENABLE_BITS);
93 
94 	data->enabled = false;
95 
96 	return 0;
97 }
98 
wdt_rpi_pico_install_timeout(const struct device * dev,const struct wdt_timeout_cfg * cfg)99 static int wdt_rpi_pico_install_timeout(const struct device *dev, const struct wdt_timeout_cfg *cfg)
100 {
101 	struct wdt_rpi_pico_data *data = dev->data;
102 
103 	if (cfg->window.min != 0U || cfg->window.max == 0U) {
104 		return -EINVAL;
105 	} else if (cfg->window.max * USEC_PER_MSEC > RPI_PICO_MAX_WDT_TIME) {
106 		return -EINVAL;
107 	} else if (cfg->callback != NULL) {
108 		return -ENOTSUP;
109 	} else if ((cfg->flags & WDT_FLAG_RESET_MASK) == WDT_FLAG_RESET_NONE) {
110 		/* The RP2040 does technically support this mode, but requires
111 		 * a program counter and stack pointer value to be set,
112 		 * therefore do not allow configuring in this mode
113 		 */
114 		return -EINVAL;
115 	}
116 
117 	data->load = (cfg->window.max * USEC_PER_MSEC * RPI_PICO_WDT_TIME_MULTIPLICATION_FACTOR);
118 	data->reset_type = (cfg->flags & WDT_FLAG_RESET_MASK);
119 
120 	return 0;
121 }
122 
wdt_rpi_pico_feed(const struct device * dev,int channel_id)123 static int wdt_rpi_pico_feed(const struct device *dev, int channel_id)
124 {
125 	struct wdt_rpi_pico_data *data = dev->data;
126 
127 	if (channel_id != 0) {
128 		/* There is only one input to the watchdog */
129 		return -EINVAL;
130 	}
131 
132 	if (data->enabled == false) {
133 		/* Watchdog is not running so does not need to be fed */
134 		return -EINVAL;
135 	}
136 
137 	watchdog_hw->load = data->load;
138 
139 	return 0;
140 }
141 
wdt_rpi_pico_init(const struct device * dev)142 static int wdt_rpi_pico_init(const struct device *dev)
143 {
144 #ifndef CONFIG_WDT_DISABLE_AT_BOOT
145 	return wdt_rpi_pico_setup(dev, WDT_OPT_PAUSE_HALTED_BY_DBG);
146 #endif
147 
148 	return 0;
149 }
150 
151 static const struct wdt_driver_api wdt_rpi_pico_driver_api = {
152 	.setup = wdt_rpi_pico_setup,
153 	.disable = wdt_rpi_pico_disable,
154 	.install_timeout = wdt_rpi_pico_install_timeout,
155 	.feed = wdt_rpi_pico_feed,
156 };
157 
158 #define WDT_RPI_PICO_WDT_DEVICE(idx)                                                               \
159 	static const struct wdt_rpi_pico_config wdt_##idx##_config = {                             \
160 		.xtal_frequency = DT_INST_PROP_BY_PHANDLE(idx, clocks, clock_frequency)            \
161 	};                                                                                         \
162 	static struct wdt_rpi_pico_data wdt_##idx##_data = {                                       \
163 		.reset_type = WDT_FLAG_RESET_SOC,                                                  \
164 		.load = (CONFIG_WDT_RPI_PICO_INITIAL_TIMEOUT *                                     \
165 			 RPI_PICO_WDT_TIME_MULTIPLICATION_FACTOR),                                 \
166 		.enabled = false                                                                   \
167 	};                                                                                         \
168 	DEVICE_DT_DEFINE(DT_NODELABEL(wdt##idx), wdt_rpi_pico_init, NULL, &wdt_##idx##_data,       \
169 			 &wdt_##idx##_config, PRE_KERNEL_1, CONFIG_KERNEL_INIT_PRIORITY_DEVICE,    \
170 			 &wdt_rpi_pico_driver_api)
171 
172 DT_INST_FOREACH_STATUS_OKAY(WDT_RPI_PICO_WDT_DEVICE);
173