diff options
Diffstat (limited to 'lib/chibios-contrib/testhal/MSP430X/EXP430FR5969/SPI/main.c')
-rw-r--r-- | lib/chibios-contrib/testhal/MSP430X/EXP430FR5969/SPI/main.c | 415 |
1 files changed, 415 insertions, 0 deletions
diff --git a/lib/chibios-contrib/testhal/MSP430X/EXP430FR5969/SPI/main.c b/lib/chibios-contrib/testhal/MSP430X/EXP430FR5969/SPI/main.c new file mode 100644 index 000000000..ccb98d52f --- /dev/null +++ b/lib/chibios-contrib/testhal/MSP430X/EXP430FR5969/SPI/main.c | |||
@@ -0,0 +1,415 @@ | |||
1 | /* | ||
2 | ChibiOS - Copyright (C) 2006..2015 Giovanni Di Sirio | ||
3 | |||
4 | Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | you may not use this file except in compliance with the License. | ||
6 | You may obtain a copy of the License at | ||
7 | |||
8 | http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | |||
10 | Unless required by applicable law or agreed to in writing, software | ||
11 | distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | See the License for the specific language governing permissions and | ||
14 | limitations under the License. | ||
15 | */ | ||
16 | |||
17 | #include "ch.h" | ||
18 | #include "hal.h" | ||
19 | #include "hal_dma_lld.h" | ||
20 | #include "string.h" | ||
21 | |||
22 | /* Disable watchdog because of lousy startup code in newlib */ | ||
23 | static void __attribute__((naked, section(".crt_0042disable_watchdog"), used)) | ||
24 | disable_watchdog(void) { | ||
25 | WDTCTL = WDTPW | WDTHOLD; | ||
26 | } | ||
27 | |||
28 | const char * start_msg = "\r\n\r\nExecuting SPI test suite...\r\n"; | ||
29 | const char * test_1_msg = "TEST 1: spiStartIgnore, with callback\r\n"; | ||
30 | const char * test_2_msg = "TEST 2: spiStartExchange, with callback\r\n"; | ||
31 | const char * test_3_msg = "TEST 3: spiStartSend, with callback\r\n"; | ||
32 | const char * test_4_msg = "TEST 4: spiStartReceive, with callback\r\n"; | ||
33 | const char * test_5_msg = "TEST 5: spiIgnore\r\n"; | ||
34 | const char * test_6_msg = "TEST 6: spiExchange\r\n"; | ||
35 | const char * test_7_msg = "TEST 7: spiSend\r\n"; | ||
36 | const char * test_8_msg = "TEST 8: spiReceive\r\n"; | ||
37 | const char * test_9_msg = "TEST 9: spiPolledExchange\r\n"; | ||
38 | const char * test_10_msg = "TEST 10: spiStartExchange with exclusive DMA\r\n"; | ||
39 | const char * test_11_msg = | ||
40 | "TEST 11: spiStartExchange with exclusive DMA for TX\r\n"; | ||
41 | const char * test_12_msg = | ||
42 | "TEST 12: spiStartExchange with exclusive DMA for RX\r\n"; | ||
43 | |||
44 | const char * succeed_string = "SUCCESS\r\n\r\n"; | ||
45 | const char * fail_string = "FAILURE\r\n\r\n"; | ||
46 | |||
47 | char instring[256]; | ||
48 | char outstring[256]; | ||
49 | uint8_t cb_arg = 1; | ||
50 | |||
51 | void spi_callback(SPIDriver * spip) { | ||
52 | (void)spip; | ||
53 | cb_arg = 0; | ||
54 | } | ||
55 | |||
56 | SPIConfig SPIDA1_config = { | ||
57 | spi_callback, /* callback */ | ||
58 | PAL_NOLINE, /* hardware slave select line */ | ||
59 | 250000, /* data rate */ | ||
60 | MSP430X_SPI_BO_LSB, /* bit order */ | ||
61 | MSP430X_SPI_DS_EIGHT, /* data size */ | ||
62 | 0, /* SPI mode */ | ||
63 | 0xFFU, /* no exclusive TX DMA */ | ||
64 | 0xFFU /* no exclusive RX DMA */ | ||
65 | }; | ||
66 | |||
67 | SPIConfig SPIDB0_config = { | ||
68 | NULL, /* callback */ | ||
69 | LINE_LED_G, /* GPIO slave select line */ | ||
70 | 1000, /* data rate */ | ||
71 | MSP430X_SPI_BO_MSB, /* bit order */ | ||
72 | MSP430X_SPI_DS_SEVEN, /* data size */ | ||
73 | 3, /* SPI mode */ | ||
74 | 0xFF, /* no exclusive TX DMA */ | ||
75 | 0xFF /* no exclusive RX DMA */ | ||
76 | }; | ||
77 | |||
78 | /* | ||
79 | * Thread 2. | ||
80 | */ | ||
81 | THD_WORKING_AREA(waThread1, 4096); | ||
82 | THD_FUNCTION(Thread1, arg) { | ||
83 | |||
84 | (void)arg; | ||
85 | |||
86 | /* Set up loopback mode for testing */ | ||
87 | SPIDA1.regs->statw_a |= UCLISTEN; | ||
88 | SPIDB0.regs->statw_b |= UCLISTEN; | ||
89 | |||
90 | /* | ||
91 | * Activate the serial driver 0 using the driver default configuration. | ||
92 | */ | ||
93 | sdStart(&SD0, NULL); | ||
94 | |||
95 | /* Activate the SPI driver A1 using its config */ | ||
96 | spiStart(&SPIDA1, &SPIDA1_config); | ||
97 | /* Activate the SPI driver B0 using its config */ | ||
98 | spiStart(&SPIDB0, &SPIDB0_config); | ||
99 | |||
100 | while (chnGetTimeout(&SD0, TIME_INFINITE)) { | ||
101 | chnWrite(&SD0, (const uint8_t *)start_msg, strlen(start_msg)); | ||
102 | chThdSleepMilliseconds(2000); | ||
103 | |||
104 | /* Test 1 - spiStartIgnore with callback */ | ||
105 | chnWrite(&SD0, (const uint8_t *)test_1_msg, strlen(test_1_msg)); | ||
106 | strcpy(outstring, "After SPI test \r\n"); | ||
107 | strcpy(instring, "Before SPI test \r\n"); | ||
108 | cb_arg = 1; | ||
109 | if (strcmp("Before SPI test \r\n", instring) || | ||
110 | strcmp("After SPI test \r\n", outstring) || cb_arg != 1) { | ||
111 | chnWrite(&SD0, (const uint8_t *)fail_string, strlen(fail_string)); | ||
112 | } | ||
113 | spiSelect(&SPIDA1); | ||
114 | spiStartIgnore(&SPIDA1, strlen(outstring)); | ||
115 | while (SPIDA1.state != SPI_READY) | ||
116 | ; /* wait for transaction to finish */ | ||
117 | spiUnselect(&SPIDA1); | ||
118 | if (strcmp("Before SPI test \r\n", instring) || | ||
119 | strcmp("After SPI test \r\n", outstring) || cb_arg != 0) { | ||
120 | chnWrite(&SD0, (const uint8_t *)fail_string, strlen(fail_string)); | ||
121 | } | ||
122 | else { | ||
123 | chnWrite(&SD0, (const uint8_t *)succeed_string, strlen(succeed_string)); | ||
124 | } | ||
125 | |||
126 | /* Test 2 - spiStartExchange with callback */ | ||
127 | chnWrite(&SD0, (const uint8_t *)test_2_msg, strlen(test_2_msg)); | ||
128 | strcpy(outstring, "After SPI test \r\n"); | ||
129 | strcpy(instring, "Before SPI test \r\n"); | ||
130 | cb_arg = 1; | ||
131 | if (strcmp("Before SPI test \r\n", instring) || | ||
132 | strcmp("After SPI test \r\n", outstring) || cb_arg != 1) { | ||
133 | chnWrite(&SD0, (const uint8_t *)fail_string, strlen(fail_string)); | ||
134 | } | ||
135 | spiSelect(&SPIDA1); | ||
136 | spiStartExchange(&SPIDA1, strlen(instring), outstring, instring); | ||
137 | while (SPIDA1.state != SPI_READY) | ||
138 | ; /* wait for transaction to finish */ | ||
139 | spiUnselect(&SPIDA1); | ||
140 | if (strcmp("After SPI test \r\n", instring) || | ||
141 | strcmp("After SPI test \r\n", outstring) || cb_arg != 0) { | ||
142 | chnWrite(&SD0, (const uint8_t *)fail_string, strlen(fail_string)); | ||
143 | } | ||
144 | else { | ||
145 | chnWrite(&SD0, (const uint8_t *)succeed_string, strlen(succeed_string)); | ||
146 | } | ||
147 | |||
148 | /* Test 3 - spiStartSend with callback */ | ||
149 | chnWrite(&SD0, (const uint8_t *)test_3_msg, strlen(test_3_msg)); | ||
150 | strcpy(outstring, "After SPI test \r\n"); | ||
151 | strcpy(instring, "Before SPI test \r\n"); | ||
152 | cb_arg = 1; | ||
153 | if (strcmp("Before SPI test \r\n", instring) || | ||
154 | strcmp("After SPI test \r\n", outstring) || cb_arg != 1) { | ||
155 | chnWrite(&SD0, (const uint8_t *)fail_string, strlen(fail_string)); | ||
156 | } | ||
157 | spiSelect(&SPIDA1); | ||
158 | spiStartSend(&SPIDA1, strlen(outstring), outstring); | ||
159 | while (SPIDA1.state != SPI_READY) | ||
160 | ; /* wait for transaction to finish */ | ||
161 | spiUnselect(&SPIDA1); | ||
162 | if (strcmp("Before SPI test \r\n", instring) || | ||
163 | strcmp("After SPI test \r\n", outstring) || cb_arg != 0) { | ||
164 | chnWrite(&SD0, (const uint8_t *)fail_string, strlen(fail_string)); | ||
165 | } | ||
166 | else { | ||
167 | chnWrite(&SD0, (const uint8_t *)succeed_string, strlen(succeed_string)); | ||
168 | } | ||
169 | |||
170 | /* Test 4 - spiStartReceive with callback */ | ||
171 | chnWrite(&SD0, (const uint8_t *)test_4_msg, strlen(test_4_msg)); | ||
172 | strcpy(outstring, "After SPI test \r\n"); | ||
173 | strcpy(instring, "Before SPI test \r\n"); | ||
174 | cb_arg = 1; | ||
175 | if (strcmp("Before SPI test \r\n", instring) || | ||
176 | strcmp("After SPI test \r\n", outstring) || cb_arg != 1) { | ||
177 | chnWrite(&SD0, (const uint8_t *)fail_string, strlen(fail_string)); | ||
178 | } | ||
179 | spiSelect(&SPIDA1); | ||
180 | chThdSleepMilliseconds(2000); | ||
181 | spiStartReceive(&SPIDA1, strlen(instring), instring); | ||
182 | while (SPIDA1.state != SPI_READY) | ||
183 | ; /* wait for transaction to finish */ | ||
184 | spiUnselect(&SPIDA1); | ||
185 | if (strcmp("After SPI test \r\n", outstring) || | ||
186 | strcmp("\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" | ||
187 | "\xff\xff\xff", | ||
188 | instring) || | ||
189 | cb_arg != 0) { | ||
190 | chnWrite(&SD0, (const uint8_t *)fail_string, strlen(fail_string)); | ||
191 | } | ||
192 | else { | ||
193 | chnWrite(&SD0, (const uint8_t *)succeed_string, strlen(succeed_string)); | ||
194 | } | ||
195 | |||
196 | /* Test 5 - spiIgnore */ | ||
197 | chnWrite(&SD0, (const uint8_t *)test_5_msg, strlen(test_5_msg)); | ||
198 | strcpy(instring, "After SPI test \r\n"); | ||
199 | strcpy(outstring, "Before SPI test \r\n"); | ||
200 | if (strcmp("Before SPI test \r\n", outstring) || | ||
201 | strcmp("After SPI test \r\n", instring)) { | ||
202 | chnWrite(&SD0, (const uint8_t *)fail_string, strlen(fail_string)); | ||
203 | } | ||
204 | spiSelect(&SPIDB0); | ||
205 | chThdSleepMilliseconds(2000); | ||
206 | spiIgnore(&SPIDB0, strlen(outstring)); | ||
207 | spiUnselect(&SPIDB0); | ||
208 | if (strcmp("After SPI test \r\n", instring) || | ||
209 | strcmp("Before SPI test \r\n", outstring)) { | ||
210 | chnWrite(&SD0, (const uint8_t *)fail_string, strlen(fail_string)); | ||
211 | } | ||
212 | else { | ||
213 | chnWrite(&SD0, (const uint8_t *)succeed_string, strlen(succeed_string)); | ||
214 | } | ||
215 | |||
216 | /* Test 6 - spiExchange */ | ||
217 | chnWrite(&SD0, (const uint8_t *)test_6_msg, strlen(test_6_msg)); | ||
218 | strcpy(outstring, "After SPI test \r\n"); | ||
219 | strcpy(instring, "Before SPI test \r\n"); | ||
220 | if (strcmp("Before SPI test \r\n", instring) || | ||
221 | strcmp("After SPI test \r\n", outstring)) { | ||
222 | chnWrite(&SD0, (const uint8_t *)fail_string, strlen(fail_string)); | ||
223 | } | ||
224 | spiSelect(&SPIDB0); | ||
225 | spiExchange(&SPIDB0, strlen(outstring), outstring, instring); | ||
226 | spiUnselect(&SPIDB0); | ||
227 | if (strcmp("After SPI test \r\n", instring) || | ||
228 | strcmp("After SPI test \r\n", outstring)) { | ||
229 | chnWrite(&SD0, (const uint8_t *)fail_string, strlen(fail_string)); | ||
230 | } | ||
231 | else { | ||
232 | chnWrite(&SD0, (const uint8_t *)succeed_string, strlen(succeed_string)); | ||
233 | } | ||
234 | |||
235 | /* Test 7 - spiSend */ | ||
236 | chnWrite(&SD0, (const uint8_t *)test_7_msg, strlen(test_7_msg)); | ||
237 | strcpy(outstring, "After SPI test \r\n"); | ||
238 | strcpy(instring, "Before SPI test \r\n"); | ||
239 | if (strcmp("Before SPI test \r\n", instring) || | ||
240 | strcmp("After SPI test \r\n", outstring)) { | ||
241 | chnWrite(&SD0, (const uint8_t *)fail_string, strlen(fail_string)); | ||
242 | } | ||
243 | spiSelect(&SPIDB0); | ||
244 | spiSend(&SPIDB0, strlen(outstring), outstring); | ||
245 | spiUnselect(&SPIDB0); | ||
246 | if (strcmp("After SPI test \r\n", outstring) || | ||
247 | strcmp("Before SPI test \r\n", instring)) { | ||
248 | chnWrite(&SD0, (const uint8_t *)fail_string, strlen(fail_string)); | ||
249 | } | ||
250 | else { | ||
251 | chnWrite(&SD0, (const uint8_t *)succeed_string, strlen(succeed_string)); | ||
252 | } | ||
253 | |||
254 | /* Test 8 - spiReceive */ | ||
255 | chnWrite(&SD0, (const uint8_t *)test_8_msg, strlen(test_8_msg)); | ||
256 | strcpy(outstring, "After SPI test \r\n"); | ||
257 | strcpy(instring, "Before SPI test \r\n"); | ||
258 | if (strcmp("Before SPI test \r\n", instring) || | ||
259 | strcmp("After SPI test \r\n", outstring)) { | ||
260 | chnWrite(&SD0, (const uint8_t *)fail_string, strlen(fail_string)); | ||
261 | } | ||
262 | spiSelect(&SPIDB0); | ||
263 | spiReceive(&SPIDB0, strlen(instring), instring); | ||
264 | spiUnselect(&SPIDB0); | ||
265 | if (strcmp("After SPI test \r\n", outstring) || | ||
266 | strcmp("\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f" | ||
267 | "\x7f\x7f\x7f", | ||
268 | instring)) { | ||
269 | chnWrite(&SD0, (const uint8_t *)fail_string, strlen(fail_string)); | ||
270 | } | ||
271 | else { | ||
272 | chnWrite(&SD0, (const uint8_t *)succeed_string, strlen(succeed_string)); | ||
273 | } | ||
274 | |||
275 | /* Test 9 - spiPolledExchange */ | ||
276 | chnWrite(&SD0, (const uint8_t *)test_9_msg, strlen(test_9_msg)); | ||
277 | strcpy(outstring, "After SPI test \r\n"); | ||
278 | strcpy(instring, "Before SPI test \r\n"); | ||
279 | if (strcmp("Before SPI test \r\n", instring) || | ||
280 | strcmp("After SPI test \r\n", outstring)) { | ||
281 | chnWrite(&SD0, (const uint8_t *)fail_string, strlen(fail_string)); | ||
282 | } | ||
283 | spiSelect(&SPIDB0); | ||
284 | outstring[0] = spiPolledExchange(&SPIDB0, instring[0]); | ||
285 | spiUnselect(&SPIDB0); | ||
286 | if (strcmp("Bfter SPI test \r\n", outstring) || | ||
287 | strcmp("Before SPI test \r\n", instring)) { | ||
288 | chnWrite(&SD0, (const uint8_t *)fail_string, strlen(fail_string)); | ||
289 | } | ||
290 | else { | ||
291 | chnWrite(&SD0, (const uint8_t *)succeed_string, strlen(succeed_string)); | ||
292 | } | ||
293 | |||
294 | /* Reconfigure SPIDA1 to use exclusive DMA for both */ | ||
295 | spiStop(&SPIDA1); | ||
296 | SPIDA1_config.dmatx_index = 0; | ||
297 | SPIDA1_config.dmarx_index = 1; | ||
298 | SPIDA1_config.spi_mode = 1; /* because why not get coverage */ | ||
299 | spiStart(&SPIDA1, &SPIDA1_config); | ||
300 | |||
301 | /* Test 10 - spiStartExchange with exclusive DMA */ | ||
302 | chnWrite(&SD0, (const uint8_t *)test_10_msg, strlen(test_10_msg)); | ||
303 | strcpy(outstring, "After SPI test \r\n"); | ||
304 | strcpy(instring, "Before SPI test \r\n"); | ||
305 | cb_arg = 1; | ||
306 | if (strcmp("Before SPI test \r\n", instring) || | ||
307 | strcmp("After SPI test \r\n", outstring) || cb_arg != 1) { | ||
308 | chnWrite(&SD0, (const uint8_t *)fail_string, strlen(fail_string)); | ||
309 | } | ||
310 | spiSelect(&SPIDA1); | ||
311 | spiStartExchange(&SPIDA1, strlen(outstring), outstring, instring); | ||
312 | while (SPIDA1.state != SPI_READY) | ||
313 | ; /* wait for transaction to finish */ | ||
314 | spiUnselect(&SPIDA1); | ||
315 | if (strcmp("After SPI test \r\n", instring) || | ||
316 | strcmp("After SPI test \r\n", outstring) || cb_arg != 0) { | ||
317 | chnWrite(&SD0, (const uint8_t *)fail_string, strlen(fail_string)); | ||
318 | } | ||
319 | else { | ||
320 | chnWrite(&SD0, (const uint8_t *)succeed_string, strlen(succeed_string)); | ||
321 | } | ||
322 | |||
323 | /* Reconfigure SPIDA1 to use exclusive DMA for TX only */ | ||
324 | spiStop(&SPIDA1); | ||
325 | SPIDA1_config.dmatx_index = 0; | ||
326 | SPIDA1_config.dmarx_index = 0xFFU; | ||
327 | SPIDA1_config.spi_mode = 2; /* because why not get coverage */ | ||
328 | spiStart(&SPIDA1, &SPIDA1_config); | ||
329 | |||
330 | /* Test 11 - spiStartExchange with exclusive DMA for TX */ | ||
331 | chnWrite(&SD0, (const uint8_t *)test_11_msg, strlen(test_11_msg)); | ||
332 | strcpy(outstring, "After SPI test \r\n"); | ||
333 | strcpy(instring, "Before SPI test \r\n"); | ||
334 | cb_arg = 1; | ||
335 | if (strcmp("Before SPI test \r\n", instring) || | ||
336 | strcmp("After SPI test \r\n", outstring) || cb_arg != 1) { | ||
337 | chnWrite(&SD0, (const uint8_t *)fail_string, strlen(fail_string)); | ||
338 | } | ||
339 | spiSelect(&SPIDA1); | ||
340 | spiStartExchange(&SPIDA1, strlen(outstring), outstring, instring); | ||
341 | while (SPIDA1.state != SPI_READY) | ||
342 | ; /* wait for transaction to finish */ | ||
343 | spiUnselect(&SPIDA1); | ||
344 | if (strcmp("After SPI test \r\n", instring) || | ||
345 | strcmp("After SPI test \r\n", outstring) || cb_arg != 0) { | ||
346 | chnWrite(&SD0, (const uint8_t *)fail_string, strlen(fail_string)); | ||
347 | } | ||
348 | else { | ||
349 | chnWrite(&SD0, (const uint8_t *)succeed_string, strlen(succeed_string)); | ||
350 | } | ||
351 | |||
352 | /* Reconfigure SPIDA1 to use exclusive DMA for TX only */ | ||
353 | spiStop(&SPIDA1); | ||
354 | SPIDA1_config.dmatx_index = 0xFFU; | ||
355 | SPIDA1_config.dmarx_index = 1; | ||
356 | SPIDA1_config.spi_mode = 3; /* because why not get coverage */ | ||
357 | spiStart(&SPIDA1, &SPIDA1_config); | ||
358 | |||
359 | /* Test 12 - spiStartExchange with exclusive DMA for RX */ | ||
360 | chnWrite(&SD0, (const uint8_t *)test_12_msg, strlen(test_12_msg)); | ||
361 | strcpy(outstring, "After SPI test \r\n"); | ||
362 | strcpy(instring, "Before SPI test \r\n"); | ||
363 | cb_arg = 1; | ||
364 | if (strcmp("Before SPI test \r\n", instring) || | ||
365 | strcmp("After SPI test \r\n", outstring) || cb_arg != 1) { | ||
366 | chnWrite(&SD0, (const uint8_t *)fail_string, strlen(fail_string)); | ||
367 | } | ||
368 | spiSelect(&SPIDA1); | ||
369 | spiStartExchange(&SPIDA1, strlen(outstring), outstring, instring); | ||
370 | while (SPIDA1.state != SPI_READY) | ||
371 | ; /* wait for transaction to finish */ | ||
372 | spiUnselect(&SPIDA1); | ||
373 | if (strcmp("After SPI test \r\n", instring) || | ||
374 | strcmp("After SPI test \r\n", outstring) || cb_arg != 0) { | ||
375 | chnWrite(&SD0, (const uint8_t *)fail_string, strlen(fail_string)); | ||
376 | } | ||
377 | else { | ||
378 | chnWrite(&SD0, (const uint8_t *)succeed_string, strlen(succeed_string)); | ||
379 | } | ||
380 | } | ||
381 | } | ||
382 | |||
383 | /* | ||
384 | * Threads static table, one entry per thread. The number of entries must | ||
385 | * match NIL_CFG_NUM_THREADS. | ||
386 | */ | ||
387 | THD_TABLE_BEGIN | ||
388 | THD_TABLE_ENTRY(waThread1, "spi_test", Thread1, NULL) | ||
389 | THD_TABLE_END | ||
390 | |||
391 | /* | ||
392 | * Application entry point. | ||
393 | */ | ||
394 | int main(void) { | ||
395 | |||
396 | /* | ||
397 | * System initializations. | ||
398 | * - HAL initialization, this also initializes the configured device drivers | ||
399 | * and performs the board-specific initializations. | ||
400 | * - Kernel initialization, the main() function becomes a thread and the | ||
401 | * RTOS is active. | ||
402 | */ | ||
403 | WDTCTL = WDTPW | WDTHOLD; | ||
404 | |||
405 | halInit(); | ||
406 | chSysInit(); | ||
407 | dmaInit(); | ||
408 | |||
409 | /* This is now the idle thread loop, you may perform here a low priority | ||
410 | task but you must never try to sleep or wait in this loop. Note that | ||
411 | this tasks runs at the lowest priority level so any instruction added | ||
412 | here will be executed after all other tasks have been started.*/ | ||
413 | while (true) { | ||
414 | } | ||
415 | } | ||