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