1 /***************************************************************************
2 * Copyright (C) 2005 by Dominic Rath *
3 * Dominic.Rath@gmx.de *
5 * This program is free software; you can redistribute it and/or modify *
6 * it under the terms of the GNU General Public License as published by *
7 * the Free Software Foundation; either version 2 of the License, or *
8 * (at your option) any later version. *
10 * This program is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
15 * You should have received a copy of the GNU General Public License *
16 * along with this program; if not, write to the *
17 * Free Software Foundation, Inc., *
18 * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
19 ***************************************************************************/
24 #include "replacements.h"
31 #include "algorithm.h"
32 #include "binarybuffer.h"
38 str7x_mem_layout_t mem_layout
[] = {
39 {0x00000000, 0x02000, 0x01},
40 {0x00002000, 0x02000, 0x02},
41 {0x00004000, 0x02000, 0x04},
42 {0x00006000, 0x02000, 0x08},
43 {0x00008000, 0x08000, 0x10},
44 {0x00010000, 0x10000, 0x20},
45 {0x00020000, 0x10000, 0x40},
46 {0x00030000, 0x10000, 0x80},
47 {0x000C0000, 0x02000, 0x10000},
48 {0x000C2000, 0x02000, 0x20000},
51 int str7x_register_commands(struct command_context_s
*cmd_ctx
);
52 int str7x_flash_bank_command(struct command_context_s
*cmd_ctx
, char *cmd
, char **args
, int argc
, struct flash_bank_s
*bank
);
53 int str7x_erase(struct flash_bank_s
*bank
, int first
, int last
);
54 int str7x_protect(struct flash_bank_s
*bank
, int set
, int first
, int last
);
55 int str7x_write(struct flash_bank_s
*bank
, u8
*buffer
, u32 offset
, u32 count
);
56 int str7x_probe(struct flash_bank_s
*bank
);
57 int str7x_handle_part_id_command(struct command_context_s
*cmd_ctx
, char *cmd
, char **args
, int argc
);
58 int str7x_protect_check(struct flash_bank_s
*bank
);
59 int str7x_erase_check(struct flash_bank_s
*bank
);
60 int str7x_info(struct flash_bank_s
*bank
, char *buf
, int buf_size
);
62 flash_driver_t str7x_flash
=
65 .register_commands
= str7x_register_commands
,
66 .flash_bank_command
= str7x_flash_bank_command
,
68 .protect
= str7x_protect
,
71 .erase_check
= str7x_erase_check
,
72 .protect_check
= str7x_protect_check
,
76 int str7x_register_commands(struct command_context_s
*cmd_ctx
)
82 int str7x_get_flash_adr(struct flash_bank_s
*bank
, u32 reg
)
84 return (bank
->base
| reg
);
87 int str7x_build_block_list(struct flash_bank_s
*bank
)
89 str7x_flash_bank_t
*str7x_info
= bank
->driver_priv
;
92 int num_sectors
= 0, b0_sectors
= 0, b1_sectors
= 0;
109 ERROR("BUG: unknown bank->size encountered");
113 if( str7x_info
->bank1
== 1 )
118 num_sectors
= b0_sectors
+ b1_sectors
;
120 bank
->num_sectors
= num_sectors
;
121 bank
->sectors
= malloc(sizeof(flash_sector_t
) * num_sectors
);
122 str7x_info
->sector_bits
= malloc(sizeof(u32
) * num_sectors
);
123 str7x_info
->sector_bank
= malloc(sizeof(u32
) * num_sectors
);
127 for (i
= 0; i
< b0_sectors
; i
++)
129 bank
->sectors
[num_sectors
].offset
= mem_layout
[i
].sector_start
;
130 bank
->sectors
[num_sectors
].size
= mem_layout
[i
].sector_size
;
131 bank
->sectors
[num_sectors
].is_erased
= -1;
132 bank
->sectors
[num_sectors
].is_protected
= 1;
133 str7x_info
->sector_bank
[num_sectors
] = 0;
134 str7x_info
->sector_bits
[num_sectors
++] = mem_layout
[i
].sector_bit
;
139 for (i
= 8; i
< 10; i
++)
141 bank
->sectors
[num_sectors
].offset
= mem_layout
[i
].sector_start
;
142 bank
->sectors
[num_sectors
].size
= mem_layout
[i
].sector_size
;
143 bank
->sectors
[num_sectors
].is_erased
= -1;
144 bank
->sectors
[num_sectors
].is_protected
= 1;
145 str7x_info
->sector_bank
[num_sectors
] = 1;
146 str7x_info
->sector_bits
[num_sectors
++] = mem_layout
[i
].sector_bit
;
153 /* flash bank str7x <base> <size> 0 0 <target#> <str71_variant>
155 int str7x_flash_bank_command(struct command_context_s
*cmd_ctx
, char *cmd
, char **args
, int argc
, struct flash_bank_s
*bank
)
157 str7x_flash_bank_t
*str7x_info
;
161 WARNING("incomplete flash_bank str7x configuration");
162 return ERROR_FLASH_BANK_INVALID
;
165 str7x_info
= malloc(sizeof(str7x_flash_bank_t
));
166 bank
->driver_priv
= str7x_info
;
168 if (strcmp(args
[6], "STR71x") == 0)
170 str7x_info
->bank1
= 1;
171 if (bank
->base
!= 0x40000000)
173 WARNING("overriding flash base address for STR71x device with 0x40000000");
174 bank
->base
= 0x40000000;
177 else if (strcmp(args
[6], "STR73x") == 0)
179 str7x_info
->bank1
= 0;
180 if (bank
->base
!= 0x80000000)
182 WARNING("overriding flash base address for STR73x device with 0x80000000");
183 bank
->base
= 0x80000000;
186 else if (strcmp(args
[6], "STR75x") == 0)
188 str7x_info
->bank1
= 1;
189 if (bank
->base
!= 0x20000000)
191 WARNING("overriding flash base address for STR75x device with 0x20000000");
192 bank
->base
= 0x20000000;
197 ERROR("unknown STR7x variant: '%s'", args
[6]);
199 return ERROR_FLASH_BANK_INVALID
;
202 str7x_build_block_list(bank
);
204 str7x_info
->write_algorithm
= NULL
;
209 u32
str7x_status(struct flash_bank_s
*bank
)
211 target_t
*target
= bank
->target
;
214 target_read_u32(target
, str7x_get_flash_adr(bank
, FLASH_CR0
), &retval
);
219 u32
str7x_result(struct flash_bank_s
*bank
)
221 target_t
*target
= bank
->target
;
224 target_read_u32(target
, str7x_get_flash_adr(bank
, FLASH_ER
), &retval
);
229 int str7x_blank_check(struct flash_bank_s
*bank
, int first
, int last
)
231 target_t
*target
= bank
->target
;
236 if ((first
< 0) || (last
> bank
->num_sectors
))
237 return ERROR_FLASH_SECTOR_INVALID
;
239 if (bank
->target
->state
!= TARGET_HALTED
)
241 return ERROR_TARGET_NOT_HALTED
;
244 buffer
= malloc(256);
246 for (i
= first
; i
<= last
; i
++)
248 bank
->sectors
[i
].is_erased
= 1;
250 target
->type
->read_memory(target
, bank
->base
+ bank
->sectors
[i
].offset
, 4, 256/4, buffer
);
252 for (nBytes
= 0; nBytes
< 256; nBytes
++)
254 if (buffer
[nBytes
] != 0xFF)
256 bank
->sectors
[i
].is_erased
= 0;
267 int str7x_protect_check(struct flash_bank_s
*bank
)
269 str7x_flash_bank_t
*str7x_info
= bank
->driver_priv
;
270 target_t
*target
= bank
->target
;
275 if (bank
->target
->state
!= TARGET_HALTED
)
277 return ERROR_TARGET_NOT_HALTED
;
280 target_read_u32(target
, str7x_get_flash_adr(bank
, FLASH_NVWPAR
), &retval
);
282 for (i
= 0; i
< bank
->num_sectors
; i
++)
284 if (retval
& str7x_info
->sector_bits
[i
])
285 bank
->sectors
[i
].is_protected
= 0;
287 bank
->sectors
[i
].is_protected
= 1;
293 int str7x_erase(struct flash_bank_s
*bank
, int first
, int last
)
295 str7x_flash_bank_t
*str7x_info
= bank
->driver_priv
;
296 target_t
*target
= bank
->target
;
301 u32 b0_sectors
= 0, b1_sectors
= 0;
303 if (bank
->target
->state
!= TARGET_HALTED
)
305 return ERROR_TARGET_NOT_HALTED
;
308 for (i
= first
; i
<= last
; i
++)
310 if (str7x_info
->sector_bank
[i
] == 0)
311 b0_sectors
|= str7x_info
->sector_bits
[i
];
312 else if (str7x_info
->sector_bank
[i
] == 1)
313 b1_sectors
|= str7x_info
->sector_bits
[i
];
315 ERROR("BUG: str7x_info->sector_bank[i] neither 0 nor 1 (%i)", str7x_info
->sector_bank
[i
]);
320 DEBUG("b0_sectors: 0x%x", b0_sectors
);
322 /* clear FLASH_ER register */
323 target_write_u32(target
, str7x_get_flash_adr(bank
, FLASH_ER
), 0x0);
326 target_write_u32(target
, str7x_get_flash_adr(bank
, FLASH_CR0
), cmd
);
329 target_write_u32(target
, str7x_get_flash_adr(bank
, FLASH_CR1
), cmd
);
331 cmd
= FLASH_SER
|FLASH_WMS
;
332 target_write_u32(target
, str7x_get_flash_adr(bank
, FLASH_CR0
), cmd
);
334 while (((retval
= str7x_status(bank
)) & (FLASH_BSYA1
|FLASH_BSYA2
))){
338 retval
= str7x_result(bank
);
342 ERROR("error erasing flash bank, FLASH_ER: 0x%x", retval
);
343 return ERROR_FLASH_OPERATION_FAILED
;
349 DEBUG("b1_sectors: 0x%x", b1_sectors
);
351 /* clear FLASH_ER register */
352 target_write_u32(target
, str7x_get_flash_adr(bank
, FLASH_ER
), 0x0);
355 target_write_u32(target
, str7x_get_flash_adr(bank
, FLASH_CR0
), cmd
);
358 target_write_u32(target
, str7x_get_flash_adr(bank
, FLASH_CR1
), cmd
);
360 cmd
= FLASH_SER
|FLASH_WMS
;
361 target_write_u32(target
, str7x_get_flash_adr(bank
, FLASH_CR0
), cmd
);
363 while (((retval
= str7x_status(bank
)) & (FLASH_BSYA1
|FLASH_BSYA2
))){
367 retval
= str7x_result(bank
);
371 ERROR("error erasing flash bank, FLASH_ER: 0x%x", retval
);
372 return ERROR_FLASH_OPERATION_FAILED
;
376 for (i
= first
; i
<= last
; i
++)
377 bank
->sectors
[i
].is_erased
= 1;
382 int str7x_protect(struct flash_bank_s
*bank
, int set
, int first
, int last
)
384 str7x_flash_bank_t
*str7x_info
= bank
->driver_priv
;
385 target_t
*target
= bank
->target
;
391 if (bank
->target
->state
!= TARGET_HALTED
)
393 return ERROR_TARGET_NOT_HALTED
;
396 protect_blocks
= 0xFFFFFFFF;
400 for (i
= first
; i
<= last
; i
++)
401 protect_blocks
&= ~(str7x_info
->sector_bits
[i
]);
404 /* clear FLASH_ER register */
405 target_write_u32(target
, str7x_get_flash_adr(bank
, FLASH_ER
), 0x0);
408 target_write_u32(target
, str7x_get_flash_adr(bank
, FLASH_CR0
), cmd
);
410 cmd
= str7x_get_flash_adr(bank
, FLASH_NVWPAR
);
411 target_write_u32(target
, str7x_get_flash_adr(bank
, FLASH_AR
), cmd
);
413 cmd
= protect_blocks
;
414 target_write_u32(target
, str7x_get_flash_adr(bank
, FLASH_DR0
), cmd
);
416 cmd
= FLASH_SPR
|FLASH_WMS
;
417 target_write_u32(target
, str7x_get_flash_adr(bank
, FLASH_CR0
), cmd
);
419 while (((retval
= str7x_status(bank
)) & (FLASH_BSYA1
|FLASH_BSYA2
))){
423 retval
= str7x_result(bank
);
425 DEBUG("retval: 0x%8.8x", retval
);
427 if (retval
& FLASH_ERER
)
428 return ERROR_FLASH_SECTOR_NOT_ERASED
;
429 else if (retval
& FLASH_WPF
)
430 return ERROR_FLASH_OPERATION_FAILED
;
435 int str7x_write_block(struct flash_bank_s
*bank
, u8
*buffer
, u32 offset
, u32 count
)
437 str7x_flash_bank_t
*str7x_info
= bank
->driver_priv
;
438 target_t
*target
= bank
->target
;
439 u32 buffer_size
= 8192;
440 working_area_t
*source
;
441 u32 address
= bank
->base
+ offset
;
442 reg_param_t reg_params
[5];
443 armv4_5_algorithm_t armv4_5_info
;
444 int retval
= ERROR_OK
;
446 u32 str7x_flash_write_code
[] = {
448 0xe3a04201, /* mov r4, #0x10000000 */
449 0xe5824000, /* str r4, [r2, #0x0] */
450 0xe5821010, /* str r1, [r2, #0x10] */
451 0xe4904004, /* ldr r4, [r0], #4 */
452 0xe5824008, /* str r4, [r2, #0x8] */
453 0xe4904004, /* ldr r4, [r0], #4 */
454 0xe582400c, /* str r4, [r2, #0xc] */
455 0xe3a04209, /* mov r4, #0x90000000 */
456 0xe5824000, /* str r4, [r2, #0x0] */
458 0xe5924000, /* ldr r4, [r2, #0x0] */
459 0xe3140016, /* tst r4, #0x16 */
460 0x1afffffc, /* bne busy */
461 0xe5924014, /* ldr r4, [r2, #0x14] */
462 0xe31400ff, /* tst r4, #0xff */
463 0x03140c01, /* tsteq r4, #0x100 */
464 0x1a000002, /* bne exit */
465 0xe2811008, /* add r1, r1, #0x8 */
466 0xe2533001, /* subs r3, r3, #1 */
467 0x1affffec, /* bne write */
469 0xeafffffe, /* b exit */
472 u8 str7x_flash_write_code_buf
[80];
475 /* flash write code */
476 if (!str7x_info
->write_algorithm
)
478 if (target_alloc_working_area(target
, 4 * 20, &str7x_info
->write_algorithm
) != ERROR_OK
)
480 WARNING("no working area available, can't do block memory writes");
481 return ERROR_TARGET_RESOURCE_NOT_AVAILABLE
;
484 /* convert flash writing code into a buffer in target endianness */
485 for (i
= 0; i
< 20; i
++)
486 target_buffer_set_u32(target
, str7x_flash_write_code_buf
+ i
*4, str7x_flash_write_code
[i
]);
488 target_write_buffer(target
, str7x_info
->write_algorithm
->address
, 20 * 4, str7x_flash_write_code_buf
);
492 while (target_alloc_working_area(target
, buffer_size
, &source
) != ERROR_OK
)
495 if (buffer_size
<= 256)
497 /* if we already allocated the writing code, but failed to get a buffer, free the algorithm */
498 if (str7x_info
->write_algorithm
)
499 target_free_working_area(target
, str7x_info
->write_algorithm
);
501 WARNING("no large enough working area available, can't do block memory writes");
502 return ERROR_TARGET_RESOURCE_NOT_AVAILABLE
;
506 armv4_5_info
.common_magic
= ARMV4_5_COMMON_MAGIC
;
507 armv4_5_info
.core_mode
= ARMV4_5_MODE_SVC
;
508 armv4_5_info
.core_state
= ARMV4_5_STATE_ARM
;
510 init_reg_param(®_params
[0], "r0", 32, PARAM_OUT
);
511 init_reg_param(®_params
[1], "r1", 32, PARAM_OUT
);
512 init_reg_param(®_params
[2], "r2", 32, PARAM_OUT
);
513 init_reg_param(®_params
[3], "r3", 32, PARAM_OUT
);
514 init_reg_param(®_params
[4], "r4", 32, PARAM_IN
);
518 u32 thisrun_count
= (count
> (buffer_size
/ 8)) ? (buffer_size
/ 8) : count
;
520 target_write_buffer(target
, source
->address
, thisrun_count
* 8, buffer
);
522 buf_set_u32(reg_params
[0].value
, 0, 32, source
->address
);
523 buf_set_u32(reg_params
[1].value
, 0, 32, address
);
524 buf_set_u32(reg_params
[2].value
, 0, 32, str7x_get_flash_adr(bank
, FLASH_CR0
));
525 buf_set_u32(reg_params
[3].value
, 0, 32, thisrun_count
);
527 if ((retval
= target
->type
->run_algorithm(target
, 0, NULL
, 5, reg_params
, str7x_info
->write_algorithm
->address
, str7x_info
->write_algorithm
->address
+ (19 * 4), 10000, &armv4_5_info
)) != ERROR_OK
)
529 ERROR("error executing str7x flash write algorithm");
533 if (buf_get_u32(reg_params
[4].value
, 0, 32) != 0x00)
535 retval
= ERROR_FLASH_OPERATION_FAILED
;
539 buffer
+= thisrun_count
* 8;
540 address
+= thisrun_count
* 8;
541 count
-= thisrun_count
;
544 target_free_working_area(target
, source
);
546 destroy_reg_param(®_params
[0]);
547 destroy_reg_param(®_params
[1]);
548 destroy_reg_param(®_params
[2]);
549 destroy_reg_param(®_params
[3]);
550 destroy_reg_param(®_params
[4]);
555 int str7x_write(struct flash_bank_s
*bank
, u8
*buffer
, u32 offset
, u32 count
)
557 target_t
*target
= bank
->target
;
558 u32 dwords_remaining
= (count
/ 8);
559 u32 bytes_remaining
= (count
& 0x00000007);
560 u32 address
= bank
->base
+ offset
;
561 u32 bytes_written
= 0;
564 u32 check_address
= offset
;
567 if (bank
->target
->state
!= TARGET_HALTED
)
569 return ERROR_TARGET_NOT_HALTED
;
574 WARNING("offset 0x%x breaks required 8-byte alignment", offset
);
575 return ERROR_FLASH_DST_BREAKS_ALIGNMENT
;
578 for (i
= 0; i
< bank
->num_sectors
; i
++)
580 u32 sec_start
= bank
->sectors
[i
].offset
;
581 u32 sec_end
= sec_start
+ bank
->sectors
[i
].size
;
583 /* check if destination falls within the current sector */
584 if ((check_address
>= sec_start
) && (check_address
< sec_end
))
586 /* check if destination ends in the current sector */
587 if (offset
+ count
< sec_end
)
588 check_address
= offset
+ count
;
590 check_address
= sec_end
;
594 if (check_address
!= offset
+ count
)
595 return ERROR_FLASH_DST_OUT_OF_BANK
;
597 /* clear FLASH_ER register */
598 target_write_u32(target
, str7x_get_flash_adr(bank
, FLASH_ER
), 0x0);
600 /* multiple dwords (8-byte) to be programmed? */
601 if (dwords_remaining
> 0)
603 /* try using a block write */
604 if ((retval
= str7x_write_block(bank
, buffer
, offset
, dwords_remaining
)) != ERROR_OK
)
606 if (retval
== ERROR_TARGET_RESOURCE_NOT_AVAILABLE
)
608 /* if block write failed (no sufficient working area),
609 * we use normal (slow) single dword accesses */
610 WARNING("couldn't use block writes, falling back to single memory accesses");
612 else if (retval
== ERROR_FLASH_OPERATION_FAILED
)
614 /* if an error occured, we examine the reason, and quit */
615 retval
= str7x_result(bank
);
617 ERROR("flash writing failed with error code: 0x%x", retval
);
618 return ERROR_FLASH_OPERATION_FAILED
;
623 buffer
+= dwords_remaining
* 8;
624 address
+= dwords_remaining
* 8;
625 dwords_remaining
= 0;
629 while (dwords_remaining
> 0)
633 target_write_u32(target
, str7x_get_flash_adr(bank
, FLASH_CR0
), cmd
);
636 target_write_u32(target
, str7x_get_flash_adr(bank
, FLASH_AR
), address
);
639 target
->type
->write_memory(target
, str7x_get_flash_adr(bank
, FLASH_DR0
), 4, 1, buffer
+ bytes_written
);
643 target
->type
->write_memory(target
, str7x_get_flash_adr(bank
, FLASH_DR1
), 4, 1, buffer
+ bytes_written
);
646 /* start programming cycle */
647 cmd
= FLASH_DWPG
| FLASH_WMS
;
648 target_write_u32(target
, str7x_get_flash_adr(bank
, FLASH_CR0
), cmd
);
650 while (((retval
= str7x_status(bank
)) & (FLASH_BSYA1
| FLASH_BSYA2
)))
655 retval
= str7x_result(bank
);
657 if (retval
& FLASH_PGER
)
658 return ERROR_FLASH_OPERATION_FAILED
;
659 else if (retval
& FLASH_WPF
)
660 return ERROR_FLASH_OPERATION_FAILED
;
668 u8 last_dword
[8] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
671 while(bytes_remaining
> 0)
673 last_dword
[i
++] = *(buffer
+ bytes_written
);
680 target_write_u32(target
, str7x_get_flash_adr(bank
, FLASH_CR0
), cmd
);
683 target_write_u32(target
, str7x_get_flash_adr(bank
, FLASH_AR
), address
);
686 target
->type
->write_memory(target
, str7x_get_flash_adr(bank
, FLASH_DR0
), 4, 1, last_dword
);
690 target
->type
->write_memory(target
, str7x_get_flash_adr(bank
, FLASH_DR1
), 4, 1, last_dword
+ 4);
693 /* start programming cycle */
694 cmd
= FLASH_DWPG
| FLASH_WMS
;
695 target_write_u32(target
, str7x_get_flash_adr(bank
, FLASH_CR0
), cmd
);
697 while (((retval
= str7x_status(bank
)) & (FLASH_BSYA1
| FLASH_BSYA2
)))
702 retval
= str7x_result(bank
);
704 if (retval
& FLASH_PGER
)
705 return ERROR_FLASH_OPERATION_FAILED
;
706 else if (retval
& FLASH_WPF
)
707 return ERROR_FLASH_OPERATION_FAILED
;
713 int str7x_probe(struct flash_bank_s
*bank
)
718 int str7x_handle_part_id_command(struct command_context_s
*cmd_ctx
, char *cmd
, char **args
, int argc
)
723 int str7x_erase_check(struct flash_bank_s
*bank
)
725 return str7x_blank_check(bank
, 0, bank
->num_sectors
- 1);
728 int str7x_info(struct flash_bank_s
*bank
, char *buf
, int buf_size
)
730 snprintf(buf
, buf_size
, "str7x flash driver info" );
Linking to existing account procedure
If you already have an account and want to add another login method
you
MUST first sign in with your existing account and
then change URL to read
https://review.openocd.org/login/?link
to get to this page again but this time it'll work for linking. Thank you.
SSH host keys fingerprints
1024 SHA256:YKx8b7u5ZWdcbp7/4AeXNaqElP49m6QrwfXaqQGJAOk gerrit-code-review@openocd.zylin.com (DSA)
384 SHA256:jHIbSQa4REvwCFG4cq5LBlBLxmxSqelQPem/EXIrxjk gerrit-code-review@openocd.org (ECDSA)
521 SHA256:UAOPYkU9Fjtcao0Ul/Rrlnj/OsQvt+pgdYSZ4jOYdgs gerrit-code-review@openocd.org (ECDSA)
256 SHA256:A13M5QlnozFOvTllybRZH6vm7iSt0XLxbA48yfc2yfY gerrit-code-review@openocd.org (ECDSA)
256 SHA256:spYMBqEYoAOtK7yZBrcwE8ZpYt6b68Cfh9yEVetvbXg gerrit-code-review@openocd.org (ED25519)
+--[ED25519 256]--+
|=.. |
|+o.. . |
|*.o . . |
|+B . . . |
|Bo. = o S |
|Oo.+ + = |
|oB=.* = . o |
| =+=.+ + E |
|. .=o . o |
+----[SHA256]-----+
2048 SHA256:0Onrb7/PHjpo6iVZ7xQX2riKN83FJ3KGU0TvI0TaFG4 gerrit-code-review@openocd.zylin.com (RSA)