]> git.gag.com Git - fw/altos/blob - src/stm/ao_spi_stm.c
altos: Create ao_data_fill shared function
[fw/altos] / src / stm / ao_spi_stm.c
1 /*
2  * Copyright © 2012 Keith Packard <keithp@keithp.com>
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License along
15  * with this program; if not, write to the Free Software Foundation, Inc.,
16  * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
17  */
18
19 #include <ao.h>
20
21 struct ao_spi_stm_info {
22         uint8_t miso_dma_index;
23         uint8_t mosi_dma_index;
24         struct stm_spi *stm_spi;
25 };
26
27 static uint8_t          ao_spi_mutex[STM_NUM_SPI];
28 static uint8_t          ao_spi_index[STM_NUM_SPI];
29
30 static const struct ao_spi_stm_info ao_spi_stm_info[STM_NUM_SPI] = {
31         {
32                 .miso_dma_index = STM_DMA_INDEX(STM_DMA_CHANNEL_SPI1_RX),
33                 .mosi_dma_index = STM_DMA_INDEX(STM_DMA_CHANNEL_SPI1_TX),
34                 &stm_spi1
35         },
36         {
37                 .miso_dma_index = STM_DMA_INDEX(STM_DMA_CHANNEL_SPI2_RX),
38                 .mosi_dma_index = STM_DMA_INDEX(STM_DMA_CHANNEL_SPI2_TX),
39                 &stm_spi2
40         }
41 };
42
43 static uint8_t  spi_dev_null;
44
45 #if DEBUG
46 static struct {
47         uint8_t task;
48         uint8_t which;
49         AO_TICK_TYPE tick;
50         uint16_t len;
51 } spi_tasks[64];
52 static uint8_t  spi_task_index;
53
54 static void
55 validate_spi(struct stm_spi *stm_spi, int which, uint16_t len)
56 {
57         uint32_t        sr = stm_spi->sr;
58
59         if (stm_spi != &stm_spi2)
60                 return;
61         spi_tasks[spi_task_index].task = ao_cur_task ? ao_cur_task->task_id : 0;
62         spi_tasks[spi_task_index].which = which;
63         spi_tasks[spi_task_index].tick = ao_time();
64         spi_tasks[spi_task_index].len = len;
65         spi_task_index = (spi_task_index + 1) & (63);
66         if (sr & (1 << STM_SPI_SR_FRE))
67                 ao_panic(0x40 | 1);
68         if (sr & (1 << STM_SPI_SR_BSY))
69                 ao_panic(0x40 | 2);
70         if (sr & (1 << STM_SPI_SR_OVR))
71                 ao_panic(0x40 | 3);
72         if (sr & (1 << STM_SPI_SR_MODF))
73                 ao_panic(0x40 | 4);
74         if (sr & (1 << STM_SPI_SR_UDR))
75                 ao_panic(0x40 | 5);
76         if ((sr & (1 << STM_SPI_SR_TXE)) == 0)
77                 ao_panic(0x40 | 6);
78         if (sr & (1 << STM_SPI_SR_RXNE))
79                 ao_panic(0x40 | 7);
80         if (which != 5 && which != 6 && which != 13)
81                 if (ao_cur_task->task_id != ao_spi_mutex[1])
82                         ao_panic(0x40 | 8);
83 }
84 #else
85 #define validate_spi(stm_spi, which, len) do { (void) (which); (void) (len); } while (0)
86 #endif
87
88 static void
89 ao_spi_set_dma_mosi(uint8_t id, const void *data, uint16_t len, uint32_t minc)
90 {
91         struct stm_spi *stm_spi = ao_spi_stm_info[id].stm_spi;
92         uint8_t mosi_dma_index = ao_spi_stm_info[id].mosi_dma_index;
93
94         ao_dma_set_transfer(mosi_dma_index,
95                             &stm_spi->dr,
96                             (void *) data,
97                             len,
98                             (0 << STM_DMA_CCR_MEM2MEM) |
99                             (STM_DMA_CCR_PL_MEDIUM << STM_DMA_CCR_PL) |
100                             (STM_DMA_CCR_MSIZE_8 << STM_DMA_CCR_MSIZE) |
101                             (STM_DMA_CCR_PSIZE_8 << STM_DMA_CCR_PSIZE) |
102                             (minc << STM_DMA_CCR_MINC) |
103                             (0 << STM_DMA_CCR_PINC) |
104                             (0 << STM_DMA_CCR_CIRC) |
105                             (STM_DMA_CCR_DIR_MEM_TO_PER << STM_DMA_CCR_DIR));
106 }
107
108 static void
109 ao_spi_set_dma_miso(uint8_t id, void *data, uint16_t len, uint32_t minc)
110 {
111         struct stm_spi *stm_spi = ao_spi_stm_info[id].stm_spi;
112         uint8_t miso_dma_index = ao_spi_stm_info[id].miso_dma_index;
113
114         ao_dma_set_transfer(miso_dma_index,
115                             &stm_spi->dr,
116                             data,
117                             len,
118                             (0 << STM_DMA_CCR_MEM2MEM) |
119                             (STM_DMA_CCR_PL_HIGH << STM_DMA_CCR_PL) |
120                             (STM_DMA_CCR_MSIZE_8 << STM_DMA_CCR_MSIZE) |
121                             (STM_DMA_CCR_PSIZE_8 << STM_DMA_CCR_PSIZE) |
122                             (minc << STM_DMA_CCR_MINC) |
123                             (0 << STM_DMA_CCR_PINC) |
124                             (0 << STM_DMA_CCR_CIRC) |
125                             (STM_DMA_CCR_DIR_PER_TO_MEM << STM_DMA_CCR_DIR));
126 }
127
128 static void
129 ao_spi_run(uint8_t id, uint8_t which, uint16_t len)
130 {
131         struct stm_spi *stm_spi = ao_spi_stm_info[id].stm_spi;
132         uint8_t mosi_dma_index = ao_spi_stm_info[id].mosi_dma_index;
133         uint8_t miso_dma_index = ao_spi_stm_info[id].miso_dma_index;
134
135         validate_spi(stm_spi, which, len);
136
137         stm_spi->cr2 = ((0 << STM_SPI_CR2_TXEIE) |
138                         (0 << STM_SPI_CR2_RXNEIE) |
139                         (0 << STM_SPI_CR2_ERRIE) |
140                         (0 << STM_SPI_CR2_SSOE) |
141                         (1 << STM_SPI_CR2_TXDMAEN) |
142                         (1 << STM_SPI_CR2_RXDMAEN));
143
144         ao_dma_start(miso_dma_index);
145         ao_dma_start(mosi_dma_index);
146
147         ao_arch_critical(
148                 while (!ao_dma_done[miso_dma_index])
149                         ao_sleep(&ao_dma_done[miso_dma_index]);
150                 );
151
152         while ((stm_spi->sr & (1 << STM_SPI_SR_TXE)) == 0);
153         while (stm_spi->sr & (1 << STM_SPI_SR_BSY));
154
155         validate_spi(stm_spi, which+1, len);
156
157         stm_spi->cr2 = 0;
158
159         ao_dma_done_transfer(mosi_dma_index);
160         ao_dma_done_transfer(miso_dma_index);
161 }
162
163 void
164 ao_spi_send(const void *block, uint16_t len, uint8_t spi_index)
165 {
166         uint8_t id = AO_SPI_INDEX(spi_index);
167
168         /* Set up the transmit DMA to deliver data */
169         ao_spi_set_dma_mosi(id, block, len, 1);
170
171         /* Set up the receive DMA -- when this is done, we know the SPI unit
172          * is idle. Without this, we'd have to poll waiting for the BSY bit to
173          * be cleared
174          */
175         ao_spi_set_dma_miso(id, &spi_dev_null, len, 0);
176
177         ao_spi_run(id, 1, len);
178 }
179
180 void
181 ao_spi_send_fixed(uint8_t value, uint16_t len, uint8_t spi_index)
182 {
183         uint8_t id = AO_SPI_INDEX(spi_index);
184
185         /* Set up the transmit DMA to deliver data */
186         ao_spi_set_dma_mosi(id, &value, len, 0);
187
188         /* Set up the receive DMA -- when this is done, we know the SPI unit
189          * is idle. Without this, we'd have to poll waiting for the BSY bit to
190          * be cleared
191          */
192         ao_spi_set_dma_miso(id, &spi_dev_null, len, 0);
193
194         ao_spi_run(id, 3, len);
195 }
196
197 void
198 ao_spi_start_bytes(uint8_t spi_index)
199 {
200         uint8_t         id = AO_SPI_INDEX(spi_index);
201         struct stm_spi  *stm_spi = ao_spi_stm_info[id].stm_spi;
202
203         stm_spi->cr2 = ((0 << STM_SPI_CR2_TXEIE) |
204                         (0 << STM_SPI_CR2_RXNEIE) |
205                         (0 << STM_SPI_CR2_ERRIE) |
206                         (0 << STM_SPI_CR2_SSOE) |
207                         (0 << STM_SPI_CR2_TXDMAEN) |
208                         (0 << STM_SPI_CR2_RXDMAEN));
209         validate_spi(stm_spi, 5, 0xffff);
210 }
211
212 void
213 ao_spi_stop_bytes(uint8_t spi_index)
214 {
215         uint8_t         id = AO_SPI_INDEX(spi_index);
216         struct stm_spi  *stm_spi = ao_spi_stm_info[id].stm_spi;
217
218         while ((stm_spi->sr & (1 << STM_SPI_SR_TXE)) == 0)
219                 ;
220         while (stm_spi->sr & (1 << STM_SPI_SR_BSY))
221                 ;
222         /* Clear the OVR flag */
223         (void) stm_spi->dr;
224         (void) stm_spi->sr;
225         validate_spi(stm_spi, 6, 0xffff);
226         stm_spi->cr2 = 0;
227 }
228
229 void
230 ao_spi_send_sync(const void *block, uint16_t len, uint8_t spi_index)
231 {
232         uint8_t         id = AO_SPI_INDEX(spi_index);
233         const uint8_t   *b = block;
234         struct stm_spi  *stm_spi = ao_spi_stm_info[id].stm_spi;
235
236         stm_spi->cr2 = ((0 << STM_SPI_CR2_TXEIE) |
237                         (0 << STM_SPI_CR2_RXNEIE) |
238                         (0 << STM_SPI_CR2_ERRIE) |
239                         (0 << STM_SPI_CR2_SSOE) |
240                         (0 << STM_SPI_CR2_TXDMAEN) |
241                         (0 << STM_SPI_CR2_RXDMAEN));
242         validate_spi(stm_spi, 7, len);
243         while (len--) {
244                 while (!(stm_spi->sr & (1 << STM_SPI_SR_TXE)));
245                 stm_spi->dr = *b++;
246         }
247         while ((stm_spi->sr & (1 << STM_SPI_SR_TXE)) == 0)
248                 ;
249         while (stm_spi->sr & (1 << STM_SPI_SR_BSY))
250                 ;
251         /* Clear the OVR flag */
252         (void) stm_spi->dr;
253         (void) stm_spi->sr;
254         validate_spi(stm_spi, 8, len);
255 }
256
257 void
258 ao_spi_recv(void *block, uint16_t len, uint8_t spi_index)
259 {
260         uint8_t         id = AO_SPI_INDEX(spi_index);
261
262         spi_dev_null = 0xff;
263
264         /* Set up transmit DMA to make the SPI hardware actually run */
265         ao_spi_set_dma_mosi(id, &spi_dev_null, len, 0);
266
267         /* Set up the receive DMA to capture data */
268         ao_spi_set_dma_miso(id, block, len, 1);
269
270         ao_spi_run(id, 9, len);
271 }
272
273 void
274 ao_spi_duplex(const void *out, void *in, uint16_t len, uint8_t spi_index)
275 {
276         uint8_t         id = AO_SPI_INDEX(spi_index);
277
278         /* Set up transmit DMA to send data */
279         ao_spi_set_dma_mosi(id, out, len, 1);
280
281         /* Set up the receive DMA to capture data */
282         ao_spi_set_dma_miso(id, in, len, 1);
283
284         ao_spi_run(id, 11, len);
285 }
286
287 static void
288 ao_spi_disable_index(uint8_t spi_index)
289 {
290         /* Disable current config
291          */
292         switch (spi_index) {
293         case AO_SPI_1_PA5_PA6_PA7:
294                 stm_gpio_set(&stm_gpioa, 5, 1);
295                 stm_moder_set(&stm_gpioa, 5, STM_MODER_OUTPUT);
296                 stm_moder_set(&stm_gpioa, 6, STM_MODER_INPUT);
297                 stm_moder_set(&stm_gpioa, 7, STM_MODER_OUTPUT);
298                 break;
299         case AO_SPI_1_PB3_PB4_PB5:
300                 stm_gpio_set(&stm_gpiob, 3, 1);
301                 stm_moder_set(&stm_gpiob, 3, STM_MODER_OUTPUT);
302                 stm_moder_set(&stm_gpiob, 4, STM_MODER_INPUT);
303                 stm_moder_set(&stm_gpiob, 5, STM_MODER_OUTPUT);
304                 break;
305         case AO_SPI_1_PE13_PE14_PE15:
306                 stm_gpio_set(&stm_gpioe, 13, 1);
307                 stm_moder_set(&stm_gpioe, 13, STM_MODER_OUTPUT);
308                 stm_moder_set(&stm_gpioe, 14, STM_MODER_INPUT);
309                 stm_moder_set(&stm_gpioe, 15, STM_MODER_OUTPUT);
310                 break;
311         case AO_SPI_2_PB13_PB14_PB15:
312                 stm_gpio_set(&stm_gpiob, 13, 1);
313                 stm_moder_set(&stm_gpiob, 13, STM_MODER_OUTPUT);
314                 stm_moder_set(&stm_gpiob, 14, STM_MODER_INPUT);
315                 stm_moder_set(&stm_gpiob, 15, STM_MODER_OUTPUT);
316                 break;
317         case AO_SPI_2_PD1_PD3_PD4:
318                 stm_gpio_set(&stm_gpiod, 1, 1);
319                 stm_moder_set(&stm_gpiod, 1, STM_MODER_OUTPUT);
320                 stm_moder_set(&stm_gpiod, 3, STM_MODER_INPUT);
321                 stm_moder_set(&stm_gpiod, 4, STM_MODER_OUTPUT);
322                 break;
323         }
324 }
325
326 static void
327 ao_spi_enable_index(uint8_t spi_index)
328 {
329         /* Enable new config
330          */
331         switch (spi_index) {
332         case AO_SPI_1_PA5_PA6_PA7:
333                 stm_afr_set(&stm_gpioa, 5, STM_AFR_AF5);
334                 stm_afr_set(&stm_gpioa, 6, STM_AFR_AF5);
335                 stm_afr_set(&stm_gpioa, 7, STM_AFR_AF5);
336                 break;
337         case AO_SPI_1_PB3_PB4_PB5:
338                 stm_afr_set(&stm_gpiob, 3, STM_AFR_AF5);
339                 stm_afr_set(&stm_gpiob, 4, STM_AFR_AF5);
340                 stm_afr_set(&stm_gpiob, 5, STM_AFR_AF5);
341                 break;
342         case AO_SPI_1_PE13_PE14_PE15:
343                 stm_afr_set(&stm_gpioe, 13, STM_AFR_AF5);
344                 stm_afr_set(&stm_gpioe, 14, STM_AFR_AF5);
345                 stm_afr_set(&stm_gpioe, 15, STM_AFR_AF5);
346                 break;
347         case AO_SPI_2_PB13_PB14_PB15:
348                 stm_afr_set(&stm_gpiob, 13, STM_AFR_AF5);
349                 stm_afr_set(&stm_gpiob, 14, STM_AFR_AF5);
350                 stm_afr_set(&stm_gpiob, 15, STM_AFR_AF5);
351                 break;
352         case AO_SPI_2_PD1_PD3_PD4:
353                 stm_afr_set(&stm_gpiod, 1, STM_AFR_AF5);
354                 stm_afr_set(&stm_gpiod, 3, STM_AFR_AF5);
355                 stm_afr_set(&stm_gpiod, 4, STM_AFR_AF5);
356                 break;
357         }
358 }
359
360 static void
361 ao_spi_config(uint8_t spi_index, uint32_t speed)
362 {
363         uint8_t         id = AO_SPI_INDEX(spi_index);
364         struct stm_spi  *stm_spi = ao_spi_stm_info[id].stm_spi;
365
366         if (spi_index != ao_spi_index[id]) {
367
368                 /* Disable old config
369                  */
370                 ao_spi_disable_index(ao_spi_index[id]);
371
372                 /* Enable new config
373                  */
374                 ao_spi_enable_index(spi_index);
375
376                 /* Remember current config
377                  */
378                 ao_spi_index[id] = spi_index;
379         }
380         stm_spi->cr1 = ((0 << STM_SPI_CR1_BIDIMODE) |           /* Three wire mode */
381                         (0 << STM_SPI_CR1_BIDIOE) |
382                         (0 << STM_SPI_CR1_CRCEN) |              /* CRC disabled */
383                         (0 << STM_SPI_CR1_CRCNEXT) |
384                         (0 << STM_SPI_CR1_DFF) |
385                         (0 << STM_SPI_CR1_RXONLY) |
386                         (1 << STM_SPI_CR1_SSM) |                /* Software SS handling */
387                         (1 << STM_SPI_CR1_SSI) |                /*  ... */
388                         (0 << STM_SPI_CR1_LSBFIRST) |           /* Big endian */
389                         (1 << STM_SPI_CR1_SPE) |                /* Enable SPI unit */
390                         (speed << STM_SPI_CR1_BR) |             /* baud rate to pclk/4 */
391                         (1 << STM_SPI_CR1_MSTR) |
392                         (0 << STM_SPI_CR1_CPOL) |               /* Format 0 */
393                         (0 << STM_SPI_CR1_CPHA));
394         validate_spi(stm_spi, 13, 0);
395 }
396
397 uint8_t
398 ao_spi_try_get(uint8_t spi_index, uint32_t speed, uint8_t task_id)
399 {
400         uint8_t         id = AO_SPI_INDEX(spi_index);
401
402         if (!ao_mutex_try(&ao_spi_mutex[id], task_id))
403                 return 0;
404         ao_spi_config(spi_index, speed);
405         return 1;
406 }
407
408 void
409 ao_spi_get(uint8_t spi_index, uint32_t speed)
410 {
411         uint8_t         id = AO_SPI_INDEX(spi_index);
412
413         ao_mutex_get(&ao_spi_mutex[id]);
414         ao_spi_config(spi_index, speed);
415 }
416
417 void
418 ao_spi_put(uint8_t spi_index)
419 {
420         uint8_t         id = AO_SPI_INDEX(spi_index);
421         struct stm_spi  *stm_spi = ao_spi_stm_info[id].stm_spi;
422
423         stm_spi->cr1 = 0;
424         ao_mutex_put(&ao_spi_mutex[id]);
425 }
426
427 static void
428 ao_spi_channel_init(uint8_t spi_index)
429 {
430         uint8_t         id = AO_SPI_INDEX(spi_index);
431         struct stm_spi  *stm_spi = ao_spi_stm_info[id].stm_spi;
432
433         ao_spi_disable_index(spi_index);
434
435         stm_spi->cr1 = 0;
436         stm_spi->cr2 = ((0 << STM_SPI_CR2_TXEIE) |
437                         (0 << STM_SPI_CR2_RXNEIE) |
438                         (0 << STM_SPI_CR2_ERRIE) |
439                         (0 << STM_SPI_CR2_SSOE) |
440                         (0 << STM_SPI_CR2_TXDMAEN) |
441                         (0 << STM_SPI_CR2_RXDMAEN));
442
443         /* Clear any pending data and error flags */
444         (void) stm_spi->dr;
445         (void) stm_spi->sr;
446 }
447
448 #if DEBUG
449 void
450 ao_spi_dump_cmd(void)
451 {
452         int s;
453
454         for (s = 0; s < 64; s++) {
455                 int i = (spi_task_index + s) & 63;
456                 if (spi_tasks[i].which) {
457                         int t;
458                         const char *name = "(none)";
459                         for (t = 0; t < ao_num_tasks; t++)
460                                 if (ao_tasks[t]->task_id == spi_tasks[i].task) {
461                                         name = ao_tasks[t]->name;
462                                         break;
463                                 }
464                         printf("%2d: %5d task %2d which %2d len %5d %s\n",
465                                s,
466                                spi_tasks[i].tick,
467                                spi_tasks[i].task,
468                                spi_tasks[i].which,
469                                spi_tasks[i].len,
470                                name);
471                 }
472         }
473         for (s = 0; s < STM_NUM_SPI; s++) {
474                 struct stm_spi *spi = ao_spi_stm_info[s].stm_spi;
475
476                 printf("%1d: mutex %2d index %3d miso dma %3d mosi dma %3d",
477                        s, ao_spi_mutex[s], ao_spi_index[s],
478                        ao_spi_stm_info[s].miso_dma_index,
479                        ao_spi_stm_info[s].mosi_dma_index);
480                 printf(" cr1 %04x cr2 %02x sr %03x\n",
481                        spi->cr1, spi->cr2, spi->sr);
482         }
483
484 }
485
486 static const struct ao_cmds ao_spi_cmds[] = {
487         { ao_spi_dump_cmd,      "S\0Dump SPI status" },
488         { 0, NULL }
489 };
490 #endif
491
492 void
493 ao_spi_init(void)
494 {
495 #if HAS_SPI_1
496 # if SPI_1_PA5_PA6_PA7
497         stm_rcc.ahbenr |= (1 << STM_RCC_AHBENR_GPIOAEN);
498         stm_ospeedr_set(&stm_gpioa, 5, SPI_1_OSPEEDR);
499         stm_ospeedr_set(&stm_gpioa, 6, SPI_1_OSPEEDR);
500         stm_ospeedr_set(&stm_gpioa, 7, SPI_1_OSPEEDR);
501 # endif
502 # if SPI_1_PB3_PB4_PB5
503         stm_rcc.ahbenr |= (1 << STM_RCC_AHBENR_GPIOBEN);
504         stm_ospeedr_set(&stm_gpiob, 3, SPI_1_OSPEEDR);
505         stm_ospeedr_set(&stm_gpiob, 4, SPI_1_OSPEEDR);
506         stm_ospeedr_set(&stm_gpiob, 5, SPI_1_OSPEEDR);
507 # endif
508 # if SPI_1_PE13_PE14_PE15
509         stm_rcc.ahbenr |= (1 << STM_RCC_AHBENR_GPIOEEN);
510         stm_ospeedr_set(&stm_gpioe, 13, SPI_1_OSPEEDR);
511         stm_ospeedr_set(&stm_gpioe, 14, SPI_1_OSPEEDR);
512         stm_ospeedr_set(&stm_gpioe, 15, SPI_1_OSPEEDR);
513 # endif
514         stm_rcc.apb2enr |= (1 << STM_RCC_APB2ENR_SPI1EN);
515         ao_spi_index[0] = AO_SPI_CONFIG_NONE;
516         ao_spi_channel_init(0);
517 #endif
518
519 #if HAS_SPI_2
520 # if SPI_2_PB13_PB14_PB15
521         stm_rcc.ahbenr |= (1 << STM_RCC_AHBENR_GPIOBEN);
522         stm_ospeedr_set(&stm_gpiob, 13, SPI_2_OSPEEDR);
523         stm_ospeedr_set(&stm_gpiob, 14, SPI_2_OSPEEDR);
524         stm_ospeedr_set(&stm_gpiob, 15, SPI_2_OSPEEDR);
525 # endif
526 # if SPI_2_PD1_PD3_PD4
527         stm_rcc.ahbenr |= (1 << STM_RCC_AHBENR_GPIODEN);
528         stm_ospeedr_set(&stm_gpiod, 1, SPI_2_OSPEEDR);
529         stm_ospeedr_set(&stm_gpiod, 3, SPI_2_OSPEEDR);
530         stm_ospeedr_set(&stm_gpiod, 4, SPI_2_OSPEEDR);
531 # endif
532         stm_rcc.apb1enr |= (1 << STM_RCC_APB1ENR_SPI2EN);
533         ao_spi_index[1] = AO_SPI_CONFIG_NONE;
534         ao_spi_channel_init(1);
535 #endif
536 #if DEBUG
537         ao_cmd_register(&ao_spi_cmds[0]);
538 #endif
539 }