]>
Commit | Line | Data |
---|---|---|
83d290c5 | 1 | // SPDX-License-Identifier: GPL-2.0 |
42537ca4 SS |
2 | /* |
3 | * Cadence WDT driver - Used by Xilinx Zynq | |
4 | * Reference: Linux kernel Cadence watchdog driver. | |
5 | * | |
6 | * Author(s): Shreenidhi Shedi <[email protected]> | |
42537ca4 SS |
7 | */ |
8 | ||
9 | #include <common.h> | |
10 | #include <dm.h> | |
f7ae49fc | 11 | #include <log.h> |
42537ca4 SS |
12 | #include <wdt.h> |
13 | #include <clk.h> | |
782ef57e | 14 | #include <div64.h> |
336d4615 | 15 | #include <dm/device_compat.h> |
61b29b82 | 16 | #include <linux/err.h> |
42537ca4 SS |
17 | #include <linux/io.h> |
18 | ||
42537ca4 SS |
19 | struct cdns_regs { |
20 | u32 zmr; /* WD Zero mode register, offset - 0x0 */ | |
21 | u32 ccr; /* Counter Control Register offset - 0x4 */ | |
22 | u32 restart; /* Restart key register, offset - 0x8 */ | |
23 | u32 status; /* Status Register, offset - 0xC */ | |
24 | }; | |
25 | ||
26 | struct cdns_wdt_priv { | |
27 | bool rst; | |
42537ca4 SS |
28 | struct cdns_regs *regs; |
29 | }; | |
30 | ||
31 | #define CDNS_WDT_DEFAULT_TIMEOUT 10 | |
32 | ||
33 | /* Supports 1 - 516 sec */ | |
34 | #define CDNS_WDT_MIN_TIMEOUT 1 | |
35 | #define CDNS_WDT_MAX_TIMEOUT 516 | |
36 | ||
37 | /* Restart key */ | |
38 | #define CDNS_WDT_RESTART_KEY 0x00001999 | |
39 | ||
40 | /* Counter register access key */ | |
41 | #define CDNS_WDT_REGISTER_ACCESS_KEY 0x00920000 | |
42 | ||
43 | /* Counter value divisor */ | |
44 | #define CDNS_WDT_COUNTER_VALUE_DIVISOR 0x1000 | |
45 | ||
46 | /* Clock prescaler value and selection */ | |
47 | #define CDNS_WDT_PRESCALE_64 64 | |
48 | #define CDNS_WDT_PRESCALE_512 512 | |
49 | #define CDNS_WDT_PRESCALE_4096 4096 | |
50 | #define CDNS_WDT_PRESCALE_SELECT_64 1 | |
51 | #define CDNS_WDT_PRESCALE_SELECT_512 2 | |
52 | #define CDNS_WDT_PRESCALE_SELECT_4096 3 | |
53 | ||
54 | /* Input clock frequency */ | |
55 | #define CDNS_WDT_CLK_75MHZ 75000000 | |
56 | ||
57 | /* Counter maximum value */ | |
58 | #define CDNS_WDT_COUNTER_MAX 0xFFF | |
59 | ||
60 | /********************* Register Map **********************************/ | |
61 | ||
62 | /* | |
63 | * Zero Mode Register - This register controls how the time out is indicated | |
64 | * and also contains the access code to allow writes to the register (0xABC). | |
65 | */ | |
66 | #define CDNS_WDT_ZMR_WDEN_MASK 0x00000001 /* Enable the WDT */ | |
67 | #define CDNS_WDT_ZMR_RSTEN_MASK 0x00000002 /* Enable the reset output */ | |
68 | #define CDNS_WDT_ZMR_IRQEN_MASK 0x00000004 /* Enable IRQ output */ | |
69 | #define CDNS_WDT_ZMR_RSTLEN_16 0x00000030 /* Reset pulse of 16 pclk cycles */ | |
70 | #define CDNS_WDT_ZMR_ZKEY_VAL 0x00ABC000 /* Access key, 0xABC << 12 */ | |
71 | ||
72 | /* | |
73 | * Counter Control register - This register controls how fast the timer runs | |
74 | * and the reset value and also contains the access code to allow writes to | |
75 | * the register. | |
76 | */ | |
77 | #define CDNS_WDT_CCR_CRV_MASK 0x00003FFC /* Counter reset value */ | |
78 | ||
79 | /* Write access to Registers */ | |
80 | static inline void cdns_wdt_writereg(u32 *addr, u32 val) | |
81 | { | |
82 | writel(val, addr); | |
83 | } | |
84 | ||
85 | /** | |
86 | * cdns_wdt_reset - Reload the watchdog timer (i.e. pat the watchdog). | |
87 | * | |
88 | * @dev: Watchdog device | |
89 | * | |
90 | * Write the restart key value (0x00001999) to the restart register. | |
91 | * | |
92 | * Return: Always 0 | |
93 | */ | |
94 | static int cdns_wdt_reset(struct udevice *dev) | |
95 | { | |
96 | struct cdns_wdt_priv *priv = dev_get_priv(dev); | |
97 | ||
98 | debug("%s\n", __func__); | |
99 | ||
100 | cdns_wdt_writereg(&priv->regs->restart, CDNS_WDT_RESTART_KEY); | |
101 | ||
102 | return 0; | |
103 | } | |
104 | ||
105 | /** | |
106 | * cdns_wdt_start - Enable and start the watchdog. | |
107 | * | |
108 | * @dev: Watchdog device | |
109 | * @timeout: Timeout value | |
110 | * @flags: Driver flags | |
111 | * | |
112 | * The counter value is calculated according to the formula: | |
113 | * count = (timeout * clock) / prescaler + 1. | |
114 | * | |
115 | * The calculated count is divided by 0x1000 to obtain the field value | |
116 | * to write to counter control register. | |
117 | * | |
118 | * Clears the contents of prescaler and counter reset value. Sets the | |
119 | * prescaler to 4096 and the calculated count and access key | |
120 | * to write to CCR Register. | |
121 | * | |
122 | * Sets the WDT (WDEN bit) and either the Reset signal(RSTEN bit) | |
123 | * or Interrupt signal(IRQEN) with a specified cycles and the access | |
124 | * key to write to ZMR Register. | |
125 | * | |
126 | * Return: Upon success 0, failure -1. | |
127 | */ | |
128 | static int cdns_wdt_start(struct udevice *dev, u64 timeout, ulong flags) | |
129 | { | |
130 | ulong clk_f; | |
131 | u32 count, prescaler, ctrl_clksel, data = 0; | |
132 | struct clk clock; | |
133 | struct cdns_wdt_priv *priv = dev_get_priv(dev); | |
134 | ||
135 | if (clk_get_by_index(dev, 0, &clock) < 0) { | |
136 | dev_err(dev, "failed to get clock\n"); | |
137 | return -1; | |
138 | } | |
139 | ||
140 | clk_f = clk_get_rate(&clock); | |
141 | if (IS_ERR_VALUE(clk_f)) { | |
142 | dev_err(dev, "failed to get rate\n"); | |
143 | return -1; | |
144 | } | |
145 | ||
782ef57e SR |
146 | /* Calculate timeout in seconds and restrict to min and max value */ |
147 | do_div(timeout, 1000); | |
148 | timeout = max_t(u64, timeout, CDNS_WDT_MIN_TIMEOUT); | |
149 | timeout = min_t(u64, timeout, CDNS_WDT_MAX_TIMEOUT); | |
42537ca4 | 150 | |
5b410dea MS |
151 | debug("%s: CLK_FREQ %ld, timeout %lld\n", __func__, clk_f, timeout); |
152 | ||
42537ca4 SS |
153 | if (clk_f <= CDNS_WDT_CLK_75MHZ) { |
154 | prescaler = CDNS_WDT_PRESCALE_512; | |
155 | ctrl_clksel = CDNS_WDT_PRESCALE_SELECT_512; | |
156 | } else { | |
157 | prescaler = CDNS_WDT_PRESCALE_4096; | |
158 | ctrl_clksel = CDNS_WDT_PRESCALE_SELECT_4096; | |
159 | } | |
160 | ||
161 | /* | |
162 | * Counter value divisor to obtain the value of | |
163 | * counter reset to be written to control register. | |
164 | */ | |
165 | count = (timeout * (clk_f / prescaler)) / | |
166 | CDNS_WDT_COUNTER_VALUE_DIVISOR + 1; | |
167 | ||
168 | if (count > CDNS_WDT_COUNTER_MAX) | |
169 | count = CDNS_WDT_COUNTER_MAX; | |
170 | ||
171 | cdns_wdt_writereg(&priv->regs->zmr, CDNS_WDT_ZMR_ZKEY_VAL); | |
172 | ||
173 | count = (count << 2) & CDNS_WDT_CCR_CRV_MASK; | |
174 | ||
175 | /* Write counter access key first to be able write to register */ | |
176 | data = count | CDNS_WDT_REGISTER_ACCESS_KEY | ctrl_clksel; | |
177 | cdns_wdt_writereg(&priv->regs->ccr, data); | |
178 | ||
179 | data = CDNS_WDT_ZMR_WDEN_MASK | CDNS_WDT_ZMR_RSTLEN_16 | | |
180 | CDNS_WDT_ZMR_ZKEY_VAL; | |
181 | ||
182 | /* Reset on timeout if specified in device tree. */ | |
183 | if (priv->rst) { | |
184 | data |= CDNS_WDT_ZMR_RSTEN_MASK; | |
185 | data &= ~CDNS_WDT_ZMR_IRQEN_MASK; | |
186 | } else { | |
187 | data &= ~CDNS_WDT_ZMR_RSTEN_MASK; | |
188 | data |= CDNS_WDT_ZMR_IRQEN_MASK; | |
189 | } | |
190 | ||
191 | cdns_wdt_writereg(&priv->regs->zmr, data); | |
192 | cdns_wdt_writereg(&priv->regs->restart, CDNS_WDT_RESTART_KEY); | |
193 | ||
194 | return 0; | |
195 | } | |
196 | ||
197 | /** | |
198 | * cdns_wdt_stop - Stop the watchdog. | |
199 | * | |
200 | * @dev: Watchdog device | |
201 | * | |
202 | * Read the contents of the ZMR register, clear the WDEN bit in the register | |
203 | * and set the access key for successful write. | |
204 | * | |
205 | * Return: Always 0 | |
206 | */ | |
207 | static int cdns_wdt_stop(struct udevice *dev) | |
208 | { | |
209 | struct cdns_wdt_priv *priv = dev_get_priv(dev); | |
210 | ||
211 | cdns_wdt_writereg(&priv->regs->zmr, | |
212 | CDNS_WDT_ZMR_ZKEY_VAL & (~CDNS_WDT_ZMR_WDEN_MASK)); | |
213 | ||
214 | return 0; | |
215 | } | |
216 | ||
217 | /** | |
218 | * cdns_wdt_probe - Probe call for the device. | |
219 | * | |
220 | * @dev: Handle to the udevice structure. | |
221 | * | |
222 | * Return: Always 0. | |
223 | */ | |
224 | static int cdns_wdt_probe(struct udevice *dev) | |
225 | { | |
42537ca4 SS |
226 | debug("%s: Probing wdt%u\n", __func__, dev->seq); |
227 | ||
42537ca4 SS |
228 | return 0; |
229 | } | |
230 | ||
231 | static int cdns_wdt_ofdata_to_platdata(struct udevice *dev) | |
232 | { | |
42537ca4 SS |
233 | struct cdns_wdt_priv *priv = dev_get_priv(dev); |
234 | ||
f01ef0ae | 235 | priv->regs = (struct cdns_regs *)dev_read_addr(dev); |
42537ca4 SS |
236 | if (IS_ERR(priv->regs)) |
237 | return PTR_ERR(priv->regs); | |
238 | ||
f01ef0ae | 239 | priv->rst = dev_read_bool(dev, "reset-on-timeout"); |
42537ca4 | 240 | |
782ef57e | 241 | debug("%s: reset %d\n", __func__, priv->rst); |
42537ca4 SS |
242 | |
243 | return 0; | |
244 | } | |
245 | ||
246 | static const struct wdt_ops cdns_wdt_ops = { | |
247 | .start = cdns_wdt_start, | |
248 | .reset = cdns_wdt_reset, | |
249 | .stop = cdns_wdt_stop, | |
6c253be7 | 250 | /* There is no bit/reg/support in IP for expire_now functionality */ |
42537ca4 SS |
251 | }; |
252 | ||
253 | static const struct udevice_id cdns_wdt_ids[] = { | |
254 | { .compatible = "cdns,wdt-r1p2" }, | |
255 | {} | |
256 | }; | |
257 | ||
258 | U_BOOT_DRIVER(cdns_wdt) = { | |
259 | .name = "cdns_wdt", | |
260 | .id = UCLASS_WDT, | |
261 | .of_match = cdns_wdt_ids, | |
262 | .probe = cdns_wdt_probe, | |
263 | .priv_auto_alloc_size = sizeof(struct cdns_wdt_priv), | |
264 | .ofdata_to_platdata = cdns_wdt_ofdata_to_platdata, | |
265 | .ops = &cdns_wdt_ops, | |
266 | }; |