1 // SPDX-License-Identifier: GPL-2.0
2 /*
3 * Support for Intel Camera Imaging ISP subsystem.
4 * Copyright (c) 2015, Intel Corporation.
5 *
6 * This program is free software; you can redistribute it and/or modify it
7 * under the terms and conditions of the GNU General Public License,
8 * version 2, as published by the Free Software Foundation.
9 *
10 * This program is distributed in the hope it will be useful, but WITHOUT
11 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
13 * more details.
14 */
15
16 #include "ia_css_mipi.h"
17 #include "sh_css_mipi.h"
18 #include <type_support.h>
19 #include "system_global.h"
20 #include "ia_css_err.h"
21 #include "ia_css_pipe.h"
22 #include "ia_css_stream_format.h"
23 #include "sh_css_stream_format.h"
24 #include "ia_css_stream_public.h"
25 #include "ia_css_frame_public.h"
26 #include "ia_css_input_port.h"
27 #include "ia_css_debug.h"
28 #include "sh_css_struct.h"
29 #include "sh_css_defs.h"
30 #include "sh_css_sp.h" /* sh_css_update_host2sp_mipi_frame sh_css_update_host2sp_num_mipi_frames ... */
31 #include "sw_event_global.h" /* IA_CSS_PSYS_SW_EVENT_MIPI_BUFFERS_READY */
32
33 static u32
34 ref_count_mipi_allocation[N_CSI_PORTS]; /* Initialized in mipi_init */
35
36 /* Assumptions:
37 * - A line is multiple of 4 bytes = 1 word.
38 * - Each frame has SOF and EOF (each 1 word).
39 * - Each line has format header and optionally SOL and EOL (each 1 word).
40 * - Odd and even lines of YUV420 format are different in bites per pixel size.
41 * - Custom size of embedded data.
42 * -- Interleaved frames are not taken into account.
43 * -- Lines are multiples of 8B, and not necessary of (custom 3B, or 7B
44 * etc.).
45 * Result is given in DDR mem words, 32B or 256 bits
46 */
47 int
ia_css_mipi_frame_calculate_size(const unsigned int width,const unsigned int height,const enum atomisp_input_format format,const bool hasSOLandEOL,const unsigned int embedded_data_size_words,unsigned int * size_mem_words)48 ia_css_mipi_frame_calculate_size(const unsigned int width,
49 const unsigned int height,
50 const enum atomisp_input_format format,
51 const bool hasSOLandEOL,
52 const unsigned int embedded_data_size_words,
53 unsigned int *size_mem_words)
54 {
55 int err = 0;
56
57 unsigned int bits_per_pixel = 0;
58 unsigned int even_line_bytes = 0;
59 unsigned int odd_line_bytes = 0;
60 unsigned int words_per_odd_line = 0;
61 unsigned int words_for_first_line = 0;
62 unsigned int words_per_even_line = 0;
63 unsigned int mem_words_per_even_line = 0;
64 unsigned int mem_words_per_odd_line = 0;
65 unsigned int mem_words_for_first_line = 0;
66 unsigned int mem_words_for_EOF = 0;
67 unsigned int mem_words = 0;
68 unsigned int width_padded = width;
69
70 /* The changes will be reverted as soon as RAW
71 * Buffers are deployed by the 2401 Input System
72 * in the non-continuous use scenario.
73 */
74 if (IS_ISP2401)
75 width_padded += (2 * ISP_VEC_NELEMS);
76
77 IA_CSS_ENTER("padded_width=%d, height=%d, format=%d, hasSOLandEOL=%d, embedded_data_size_words=%d\n",
78 width_padded, height, format, hasSOLandEOL, embedded_data_size_words);
79
80 switch (format) {
81 case ATOMISP_INPUT_FORMAT_RAW_6: /* 4p, 3B, 24bits */
82 bits_per_pixel = 6;
83 break;
84 case ATOMISP_INPUT_FORMAT_RAW_7: /* 8p, 7B, 56bits */
85 bits_per_pixel = 7;
86 break;
87 case ATOMISP_INPUT_FORMAT_RAW_8: /* 1p, 1B, 8bits */
88 case ATOMISP_INPUT_FORMAT_BINARY_8: /* 8bits, TODO: check. */
89 case ATOMISP_INPUT_FORMAT_YUV420_8: /* odd 2p, 2B, 16bits, even 2p, 4B, 32bits */
90 bits_per_pixel = 8;
91 break;
92 case ATOMISP_INPUT_FORMAT_YUV420_10: /* odd 4p, 5B, 40bits, even 4p, 10B, 80bits */
93 case ATOMISP_INPUT_FORMAT_RAW_10: /* 4p, 5B, 40bits */
94 /* The changes will be reverted as soon as RAW
95 * Buffers are deployed by the 2401 Input System
96 * in the non-continuous use scenario.
97 */
98 bits_per_pixel = 10;
99 break;
100 case ATOMISP_INPUT_FORMAT_YUV420_8_LEGACY: /* 2p, 3B, 24bits */
101 case ATOMISP_INPUT_FORMAT_RAW_12: /* 2p, 3B, 24bits */
102 bits_per_pixel = 12;
103 break;
104 case ATOMISP_INPUT_FORMAT_RAW_14: /* 4p, 7B, 56bits */
105 bits_per_pixel = 14;
106 break;
107 case ATOMISP_INPUT_FORMAT_RGB_444: /* 1p, 2B, 16bits */
108 case ATOMISP_INPUT_FORMAT_RGB_555: /* 1p, 2B, 16bits */
109 case ATOMISP_INPUT_FORMAT_RGB_565: /* 1p, 2B, 16bits */
110 case ATOMISP_INPUT_FORMAT_YUV422_8: /* 2p, 4B, 32bits */
111 bits_per_pixel = 16;
112 break;
113 case ATOMISP_INPUT_FORMAT_RGB_666: /* 4p, 9B, 72bits */
114 bits_per_pixel = 18;
115 break;
116 case ATOMISP_INPUT_FORMAT_YUV422_10: /* 2p, 5B, 40bits */
117 bits_per_pixel = 20;
118 break;
119 case ATOMISP_INPUT_FORMAT_RGB_888: /* 1p, 3B, 24bits */
120 bits_per_pixel = 24;
121 break;
122
123 case ATOMISP_INPUT_FORMAT_YUV420_16: /* Not supported */
124 case ATOMISP_INPUT_FORMAT_YUV422_16: /* Not supported */
125 case ATOMISP_INPUT_FORMAT_RAW_16: /* TODO: not specified in MIPI SPEC, check */
126 default:
127 return -EINVAL;
128 }
129
130 odd_line_bytes = (width_padded * bits_per_pixel + 7) >> 3; /* ceil ( bits per line / 8) */
131
132 /* Even lines for YUV420 formats are double in bits_per_pixel. */
133 if (format == ATOMISP_INPUT_FORMAT_YUV420_8
134 || format == ATOMISP_INPUT_FORMAT_YUV420_10
135 || format == ATOMISP_INPUT_FORMAT_YUV420_16) {
136 even_line_bytes = (width_padded * 2 * bits_per_pixel + 7) >>
137 3; /* ceil ( bits per line / 8) */
138 } else {
139 even_line_bytes = odd_line_bytes;
140 }
141
142 /* a frame represented in memory: ()- optional; data - payload words.
143 * addr 0 1 2 3 4 5 6 7:
144 * first SOF (SOL) PACK_H data data data data data
145 * data data data data data data data data
146 * ...
147 * data data 0 0 0 0 0 0
148 * second (EOL) (SOL) PACK_H data data data data data
149 * data data data data data data data data
150 * ...
151 * data data 0 0 0 0 0 0
152 * ...
153 * last (EOL) EOF 0 0 0 0 0 0
154 *
155 * Embedded lines are regular lines stored before the first and after
156 * payload lines.
157 */
158
159 words_per_odd_line = (odd_line_bytes + 3) >> 2;
160 /* ceil(odd_line_bytes/4); word = 4 bytes */
161 words_per_even_line = (even_line_bytes + 3) >> 2;
162 words_for_first_line = words_per_odd_line + 2 + (hasSOLandEOL ? 1 : 0);
163 /* + SOF +packet header + optionally (SOL), but (EOL) is not in the first line */
164 words_per_odd_line += (1 + (hasSOLandEOL ? 2 : 0));
165 /* each non-first line has format header, and optionally (SOL) and (EOL). */
166 words_per_even_line += (1 + (hasSOLandEOL ? 2 : 0));
167
168 mem_words_per_odd_line = (words_per_odd_line + 7) >> 3;
169 /* ceil(words_per_odd_line/8); mem_word = 32 bytes, 8 words */
170 mem_words_for_first_line = (words_for_first_line + 7) >> 3;
171 mem_words_per_even_line = (words_per_even_line + 7) >> 3;
172 mem_words_for_EOF = 1; /* last line consisit of the optional (EOL) and EOF */
173
174 mem_words = ((embedded_data_size_words + 7) >> 3) +
175 mem_words_for_first_line +
176 (((height + 1) >> 1) - 1) * mem_words_per_odd_line +
177 /* ceil (height/2) - 1 (first line is calculated separately) */
178 (height >> 1) * mem_words_per_even_line + /* floor(height/2) */
179 mem_words_for_EOF;
180
181 *size_mem_words = mem_words; /* ceil(words/8); mem word is 32B = 8words. */
182 /* Check if the above is still needed. */
183
184 IA_CSS_LEAVE_ERR(err);
185 return err;
186 }
187
188 void
mipi_init(void)189 mipi_init(void)
190 {
191 unsigned int i;
192
193 for (i = 0; i < N_CSI_PORTS; i++)
194 ref_count_mipi_allocation[i] = 0;
195 }
196
mipi_is_free(void)197 bool mipi_is_free(void)
198 {
199 unsigned int i;
200
201 for (i = 0; i < N_CSI_PORTS; i++)
202 if (ref_count_mipi_allocation[i])
203 return false;
204
205 return true;
206 }
207
208 /*
209 * @brief Calculate the required MIPI buffer sizes.
210 * Based on the stream configuration, calculate the
211 * required MIPI buffer sizes (in DDR words).
212 *
213 * @param[in] stream_cfg Point to the target stream configuration
214 * @param[out] size_mem_words MIPI buffer size in DDR words.
215 *
216 * @return
217 */
calculate_mipi_buff_size(struct ia_css_stream_config * stream_cfg,unsigned int * size_mem_words)218 static int calculate_mipi_buff_size(struct ia_css_stream_config *stream_cfg,
219 unsigned int *size_mem_words)
220 {
221 unsigned int width;
222 unsigned int height;
223 enum atomisp_input_format format;
224 bool pack_raw_pixels;
225
226 unsigned int width_padded;
227 unsigned int bits_per_pixel = 0;
228
229 unsigned int even_line_bytes = 0;
230 unsigned int odd_line_bytes = 0;
231
232 unsigned int words_per_odd_line = 0;
233 unsigned int words_per_even_line = 0;
234
235 unsigned int mem_words_per_even_line = 0;
236 unsigned int mem_words_per_odd_line = 0;
237
238 unsigned int mem_words_per_buff_line = 0;
239 unsigned int mem_words_per_buff = 0;
240 int err = 0;
241
242 /**
243 * zhengjie.lu@intel.com
244 *
245 * NOTE
246 * - In the struct "ia_css_stream_config", there
247 * are two members: "input_config" and "isys_config".
248 * Both of them provide the same information, e.g.
249 * input_res and format.
250 *
251 * Question here is that: which one shall be used?
252 */
253 width = stream_cfg->input_config.input_res.width;
254 height = stream_cfg->input_config.input_res.height;
255 format = stream_cfg->input_config.format;
256 pack_raw_pixels = stream_cfg->pack_raw_pixels;
257 /* end of NOTE */
258
259 /**
260 * zhengjie.lu@intel.com
261 *
262 * NOTE
263 * - The following code is derived from the
264 * existing code "ia_css_mipi_frame_calculate_size()".
265 *
266 * Question here is: why adding "2 * ISP_VEC_NELEMS"
267 * to "width_padded", but not making "width_padded"
268 * aligned with "2 * ISP_VEC_NELEMS"?
269 */
270 /* The changes will be reverted as soon as RAW
271 * Buffers are deployed by the 2401 Input System
272 * in the non-continuous use scenario.
273 */
274 width_padded = width + (2 * ISP_VEC_NELEMS);
275 /* end of NOTE */
276
277 IA_CSS_ENTER("padded_width=%d, height=%d, format=%d\n",
278 width_padded, height, format);
279
280 bits_per_pixel = sh_css_stream_format_2_bits_per_subpixel(format);
281 bits_per_pixel =
282 (format == ATOMISP_INPUT_FORMAT_RAW_10 && pack_raw_pixels) ? bits_per_pixel : 16;
283 if (bits_per_pixel == 0)
284 return -EINVAL;
285
286 odd_line_bytes = (width_padded * bits_per_pixel + 7) >> 3; /* ceil ( bits per line / 8) */
287
288 /* Even lines for YUV420 formats are double in bits_per_pixel. */
289 if (format == ATOMISP_INPUT_FORMAT_YUV420_8
290 || format == ATOMISP_INPUT_FORMAT_YUV420_10) {
291 even_line_bytes = (width_padded * 2 * bits_per_pixel + 7) >>
292 3; /* ceil ( bits per line / 8) */
293 } else {
294 even_line_bytes = odd_line_bytes;
295 }
296
297 words_per_odd_line = (odd_line_bytes + 3) >> 2;
298 /* ceil(odd_line_bytes/4); word = 4 bytes */
299 words_per_even_line = (even_line_bytes + 3) >> 2;
300
301 mem_words_per_odd_line = (words_per_odd_line + 7) >> 3;
302 /* ceil(words_per_odd_line/8); mem_word = 32 bytes, 8 words */
303 mem_words_per_even_line = (words_per_even_line + 7) >> 3;
304
305 mem_words_per_buff_line =
306 (mem_words_per_odd_line > mem_words_per_even_line) ? mem_words_per_odd_line : mem_words_per_even_line;
307 mem_words_per_buff = mem_words_per_buff_line * height;
308
309 *size_mem_words = mem_words_per_buff;
310
311 IA_CSS_LEAVE_ERR(err);
312 return err;
313 }
314
315 int
allocate_mipi_frames(struct ia_css_pipe * pipe,struct ia_css_stream_info * info)316 allocate_mipi_frames(struct ia_css_pipe *pipe,
317 struct ia_css_stream_info *info)
318 {
319 int err = -EINVAL;
320 unsigned int port;
321
322 ia_css_debug_dtrace(IA_CSS_DEBUG_TRACE_PRIVATE,
323 "allocate_mipi_frames(%p) enter:\n", pipe);
324
325 if (IS_ISP2401 && pipe->stream->config.online) {
326 ia_css_debug_dtrace(IA_CSS_DEBUG_TRACE_PRIVATE,
327 "allocate_mipi_frames(%p) exit: no buffers needed for 2401 pipe mode.\n",
328 pipe);
329 return 0;
330 }
331
332 if (pipe->stream->config.mode != IA_CSS_INPUT_MODE_BUFFERED_SENSOR) {
333 ia_css_debug_dtrace(IA_CSS_DEBUG_TRACE_PRIVATE,
334 "allocate_mipi_frames(%p) exit: no buffers needed for pipe mode.\n",
335 pipe);
336 return 0; /* AM TODO: Check */
337 }
338
339 port = (unsigned int)pipe->stream->config.source.port.port;
340 if (port >= N_CSI_PORTS) {
341 IA_CSS_ERROR("allocate_mipi_frames(%p) exit: port is not correct (port=%d).",
342 pipe, port);
343 return -EINVAL;
344 }
345
346 if (IS_ISP2401)
347 err = calculate_mipi_buff_size(&pipe->stream->config,
348 &my_css.mipi_frame_size[port]);
349
350 /*
351 * 2401 system allows multiple streams to use same physical port. This is not
352 * true for 2400 system. Currently 2401 uses MIPI buffers as a temporary solution.
353 * TODO AM: Once that is changed (removed) this code should be removed as well.
354 * In that case only 2400 related code should remain.
355 */
356 if (ref_count_mipi_allocation[port] != 0) {
357 if (IS_ISP2401)
358 ref_count_mipi_allocation[port]++;
359
360 ia_css_debug_dtrace(IA_CSS_DEBUG_TRACE_PRIVATE,
361 "allocate_mipi_frames(%p) leave: nothing to do, already allocated for this port (port=%d).\n",
362 pipe, port);
363 return 0;
364 }
365
366 ref_count_mipi_allocation[port]++;
367
368 /* AM TODO: mipi frames number should come from stream struct. */
369 my_css.num_mipi_frames[port] = NUM_MIPI_FRAMES_PER_STREAM;
370
371 /* Incremental allocation (per stream), not for all streams at once. */
372 { /* limit the scope of i,j */
373 unsigned int i, j;
374
375 for (i = 0; i < my_css.num_mipi_frames[port]; i++) {
376 /* free previous frame */
377 if (my_css.mipi_frames[port][i]) {
378 ia_css_frame_free(my_css.mipi_frames[port][i]);
379 my_css.mipi_frames[port][i] = NULL;
380 }
381 /* check if new frame is needed */
382 if (i < my_css.num_mipi_frames[port]) {
383 /* allocate new frame */
384 err = ia_css_frame_allocate_with_buffer_size(
385 &my_css.mipi_frames[port][i],
386 my_css.mipi_frame_size[port] * HIVE_ISP_DDR_WORD_BYTES);
387 if (err) {
388 for (j = 0; j < i; j++) {
389 if (my_css.mipi_frames[port][j]) {
390 ia_css_frame_free(my_css.mipi_frames[port][j]);
391 my_css.mipi_frames[port][j] = NULL;
392 }
393 }
394 IA_CSS_ERROR("allocate_mipi_frames(%p, %d) exit: allocation failed.",
395 pipe, port);
396 return err;
397 }
398 }
399 if (info->metadata_info.size > 0) {
400 /* free previous metadata buffer */
401 if (my_css.mipi_metadata[port][i]) {
402 ia_css_metadata_free(my_css.mipi_metadata[port][i]);
403 my_css.mipi_metadata[port][i] = NULL;
404 }
405 /* check if need to allocate a new metadata buffer */
406 if (i < my_css.num_mipi_frames[port]) {
407 /* allocate new metadata buffer */
408 my_css.mipi_metadata[port][i] = ia_css_metadata_allocate(&info->metadata_info);
409 if (!my_css.mipi_metadata[port][i]) {
410 ia_css_debug_dtrace(IA_CSS_DEBUG_TRACE_PRIVATE,
411 "allocate_mipi_metadata(%p, %d) failed.\n",
412 pipe, port);
413 return err;
414 }
415 }
416 }
417 }
418 }
419 ia_css_debug_dtrace(IA_CSS_DEBUG_TRACE_PRIVATE,
420 "allocate_mipi_frames(%p) exit:\n", pipe);
421
422 return err;
423 }
424
425 int
free_mipi_frames(struct ia_css_pipe * pipe)426 free_mipi_frames(struct ia_css_pipe *pipe)
427 {
428 int err = -EINVAL;
429 unsigned int port;
430
431 ia_css_debug_dtrace(IA_CSS_DEBUG_TRACE_PRIVATE,
432 "free_mipi_frames(%p) enter:\n", pipe);
433
434 /* assert(pipe != NULL); TEMP: TODO: Should be assert only. */
435 if (pipe) {
436 assert(pipe->stream);
437 if ((!pipe) || (!pipe->stream)) {
438 IA_CSS_ERROR("free_mipi_frames(%p) exit: pipe or stream is null.",
439 pipe);
440 return -EINVAL;
441 }
442
443 if (pipe->stream->config.mode != IA_CSS_INPUT_MODE_BUFFERED_SENSOR) {
444 IA_CSS_ERROR("free_mipi_frames(%p) exit: wrong mode.",
445 pipe);
446 return err;
447 }
448
449 port = (unsigned int)pipe->stream->config.source.port.port;
450
451 if (port >= N_CSI_PORTS) {
452 IA_CSS_ERROR("free_mipi_frames(%p, %d) exit: pipe port is not correct.",
453 pipe, port);
454 return err;
455 }
456
457 if (ref_count_mipi_allocation[port] > 0) {
458 if (!IS_ISP2401) {
459 assert(ref_count_mipi_allocation[port] == 1);
460 if (ref_count_mipi_allocation[port] != 1) {
461 IA_CSS_ERROR("free_mipi_frames(%p) exit: wrong ref_count (ref_count=%d).",
462 pipe, ref_count_mipi_allocation[port]);
463 return err;
464 }
465 }
466
467 ref_count_mipi_allocation[port]--;
468
469 if (ref_count_mipi_allocation[port] == 0) {
470 /* no streams are using this buffer, so free it */
471 unsigned int i;
472
473 for (i = 0; i < my_css.num_mipi_frames[port]; i++) {
474 if (my_css.mipi_frames[port][i]) {
475 ia_css_debug_dtrace(IA_CSS_DEBUG_TRACE_PRIVATE,
476 "free_mipi_frames(port=%d, num=%d).\n", port, i);
477 ia_css_frame_free(my_css.mipi_frames[port][i]);
478 my_css.mipi_frames[port][i] = NULL;
479 }
480 if (my_css.mipi_metadata[port][i]) {
481 ia_css_metadata_free(my_css.mipi_metadata[port][i]);
482 my_css.mipi_metadata[port][i] = NULL;
483 }
484 }
485
486 ia_css_debug_dtrace(IA_CSS_DEBUG_TRACE_PRIVATE,
487 "free_mipi_frames(%p) exit (deallocated).\n", pipe);
488 }
489 }
490 } else { /* pipe ==NULL */
491 /* AM TEMP: free-ing all mipi buffers just like a legacy code. */
492 for (port = 0; port < N_CSI_PORTS; port++) {
493 unsigned int i;
494
495 for (i = 0; i < my_css.num_mipi_frames[port]; i++) {
496 if (my_css.mipi_frames[port][i]) {
497 ia_css_debug_dtrace(IA_CSS_DEBUG_TRACE_PRIVATE,
498 "free_mipi_frames(port=%d, num=%d).\n", port, i);
499 ia_css_frame_free(my_css.mipi_frames[port][i]);
500 my_css.mipi_frames[port][i] = NULL;
501 }
502 if (my_css.mipi_metadata[port][i]) {
503 ia_css_metadata_free(my_css.mipi_metadata[port][i]);
504 my_css.mipi_metadata[port][i] = NULL;
505 }
506 }
507 ref_count_mipi_allocation[port] = 0;
508 }
509 }
510 return 0;
511 }
512
513 int
send_mipi_frames(struct ia_css_pipe * pipe)514 send_mipi_frames(struct ia_css_pipe *pipe)
515 {
516 int err = -EINVAL;
517 unsigned int i;
518 unsigned int port;
519
520 IA_CSS_ENTER_PRIVATE("pipe=%p", pipe);
521
522 /* multi stream video needs mipi buffers */
523 /* nothing to be done in other cases. */
524 if (pipe->stream->config.mode != IA_CSS_INPUT_MODE_BUFFERED_SENSOR) {
525 IA_CSS_LOG("nothing to be done for this mode");
526 return 0;
527 /* TODO: AM: maybe this should be returning an error. */
528 }
529
530 port = (unsigned int)pipe->stream->config.source.port.port;
531
532 if (port >= N_CSI_PORTS) {
533 IA_CSS_ERROR("send_mipi_frames(%p) exit: invalid port specified (port=%d).",
534 pipe, port);
535 return err;
536 }
537
538 /* Hand-over the SP-internal mipi buffers */
539 for (i = 0; i < my_css.num_mipi_frames[port]; i++) {
540 /* Need to include the offset for port. */
541 sh_css_update_host2sp_mipi_frame(port * NUM_MIPI_FRAMES_PER_STREAM + i,
542 my_css.mipi_frames[port][i]);
543 sh_css_update_host2sp_mipi_metadata(port * NUM_MIPI_FRAMES_PER_STREAM + i,
544 my_css.mipi_metadata[port][i]);
545 }
546 sh_css_update_host2sp_num_mipi_frames(my_css.num_mipi_frames[port]);
547
548 /**********************************
549 * Send an event to inform the SP
550 * that all MIPI frames are passed.
551 **********************************/
552 if (!sh_css_sp_is_running()) {
553 /* SP is not running. The queues are not valid */
554 IA_CSS_ERROR("sp is not running");
555 return err;
556 }
557
558 ia_css_bufq_enqueue_psys_event(
559 IA_CSS_PSYS_SW_EVENT_MIPI_BUFFERS_READY,
560 (uint8_t)port,
561 (uint8_t)my_css.num_mipi_frames[port],
562 0 /* not used */);
563 IA_CSS_LEAVE_ERR_PRIVATE(0);
564 return 0;
565 }
566