|
From: <ge...@op...> - 2017-03-15 10:07:32
|
This is an automated email from Gerrit. psocprogrammer (yur...@cy...) just uploaded a new patch set to Gerrit, which you can find at http://openocd.zylin.com/4065 -- gerrit commit b18762ba5d6bad248473d000ddb0b05035bd7581 Author: Yuriy Vynnychek <yur...@cy...> Date: Wed Mar 15 11:40:38 2017 +0200 Added support for new PSoC 6 device. Change-Id: Ib8d09789367840b7f9dfd7914df63d99e472abdc Signed-off-by: Yuriy Vynnychek <yur...@cy...> diff --git a/src/flash/nor/Makefile.am b/src/flash/nor/Makefile.am index 727e4f2..77c980a 100644 --- a/src/flash/nor/Makefile.am +++ b/src/flash/nor/Makefile.am @@ -40,6 +40,7 @@ NOR_DRIVERS = \ %D%/ocl.c \ %D%/pic32mx.c \ %D%/psoc4.c \ + %D%/psoc6.c \ %D%/sim3x.c \ %D%/spi.c \ %D%/stmsmi.c \ diff --git a/src/flash/nor/psoc6.c b/src/flash/nor/psoc6.c new file mode 100755 index 0000000..fa272a3 --- /dev/null +++ b/src/flash/nor/psoc6.c @@ -0,0 +1,996 @@ +/******************************************************************************* + * Copyright (C) 2017 by Yuriy Vynnychek (PSoC 6 support derived from PSoC 4)* + * Yuriy.Vynnychek.cypress.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see <http://www.gnu.org/licenses/>. * + ******************************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "imp.h" +#include <helper/binarybuffer.h> +#include <jtag/jtag.h> +#include <target/algorithm.h> +#include <target/armv7m.h> + +/* device documets: + PSoC(R) 6: PSoC CY8C6XXX Family Datasheet + Document Number: + + PSoC CY8C6XXX Family PSoC(R) 6 Architecture TRM + Document No. 002-15785 Rev. ** xx/xx/2016 + + CY8C6XXX PSOC(R) 6 BLE 2 REGISTERS TECHNICAL REFERENCE MANUAL (TRM) + Document No. 002-12544 Rev. ** 05/09/2016 + + CY8C6XXX Programming Specifications + Document No.*/ +/*-------------------------------------------------------------------------------------------- + *Base addresses + *-------------------------------------------------------------------------------------------- + */ +/* 256kB System RAM */ +#define MEM_BASE_SRAM0 0x08000000u +/* 1024kB FLASH Main Region */ +#define MEM_BASE_FLASH 0x10000000u +/* Peripheral Interconnect */ +#define MEM_BASE_MMIO 0x40000000u +/* 0x40200000: Core platform peripherals */ +#define MEM_BASE_MMIO2 (MEM_BASE_MMIO + 0x200000u) +/* 0x40230000: Base address for IPC structs */ +#define MEM_BASE_IPC (MEM_BASE_MMIO2 + 0x30000u) +/* 0x40231000: Base address for IPC_INTR struct */ +#define MEM_BASE_IPCINTR (MEM_BASE_MMIO2 + 0x31000u) + +#define PSOC6_CHIP_PROT_UNKNOWN 0x0u +#define PSOC6_CHIP_PROT_VIRGIN 0x1u +#define PSOC6_CHIP_PROT_NORMAL 0x2u +#define PSOC6_CHIP_PROT_SECURE 0x3u +#define PSOC6_CHIP_PROT_DEAD 0x4u + +/* Addresses for IPC_STRUCT and IPC_INTR_STRUCT */ +#define IPC_INTR_STRUCT_SIZE 0x20u +#define IPC_STRUCT_SIZE 0x20u +/* 0x40230000: CM0+ IPC_STRUCT absolute address */ +#define IPC_STRUCT0 MEM_BASE_IPC +/* 0x40230020: CM4 IPC_STRUCT absolute address */ +#define IPC_STRUCT1 (IPC_STRUCT0 + IPC_STRUCT_SIZE) +/* 0x40230040: DAP IPC_STRUCT absolute address */ +#define IPC_STRUCT2 (IPC_STRUCT1 + IPC_STRUCT_SIZE) +/* 0x40231000: IPC_INTR struct absolute address */ +#define IPC_INTR_STRUCT MEM_BASE_IPCINTR + +#define FLASH_SECTOR_LENGTH 256u +#define PSOC6_SPCIF_GEOMETRY (MEM_BASE_MMIO2+0x5f00cu) + +/* Registers offsets in IPC_STRUCT[x] + * This register is used to acquire a lock. This register is NOT SW writable.*/ +#define IPC_STRUCT_ACQUIRE_OFFSET 0x00u +/* This field allows for the generation of notification events to the IPC interrupt structures. */ +#define IPC_STRUCT_NOTIFY_OFFSET 0x08u +/* This field holds a 32-bit data element that is associated with the IPC structure. */ +#define IPC_STRUCT_DATA_OFFSET 0x0Cu +/* IPC lock status */ +#define IPC_STRUCT_LOCK_STATUS_OFFSET 0x10u + +/* Registers offsets in IPC_INTR_STRUCT + * IPC interrupt mask */ +#define IPC_INTR_STRUCT_INTR_IPC_MASK_OFFSET 0x08u +/* Specifies if the lock is successfully acquired or not: '0': Not successfully acquired, '1': Successfully acquired.*/ +#define IPC_STRUCT_ACQUIRE_SUCCESS_MSK 0x80000000u +/* Specifies if the lock is acquired. */ +#define IPC_STRUCT_LOCK_STATUS_ACQUIRED_MSK 0x80000000u + +/* Misc + * Timeout attempts of IPC_STRUCT acuire*/ +#define IPC_STRUCT_ACQUIRE_TIMEOUT_ATTEMPTS 250u +/* Timeout attempts of IPC_STRUCT data */ +#define IPC_STRUCT_DATA_TIMEOUT_ATTEMPTS 250u +/* 0x08001000: Address of SRAM where the APIs parameters are stored by SW. */ +#define SRAM_SCRATCH_ADDR (MEM_BASE_SRAM0 + 0x00001000u) +#define ROW_SIZE 512u +/* Timemout 10 ms */ +#define DELAY_10_MS 10000u + +/*-------------------------------------------------------------------------------------------- + *SROM APIs + *-------------------------------------------------------------------------------------------- + *SROM APIs masks + *[0]: 1 - arguments are passed in IPC.DATA. 0 - arguments are passed in SRAM*/ +#define MXS40_SROMAPI_DATA_LOCATION_MSK 0x00000001u +/* Status Code: 4 bits [31:28] of the data register */ +#define MXS40_SROMAPI_STATUS_MSK 0xF0000000u +/* Status Code = 0xA */ +#define MXS40_SROMAPI_STAT_SUCCESS 0xA0000000u + +/* Sys calls IDs (SROM API Op code) + * [31:24]: Opcode = 0x00; [0]: 1 - arguments are passed in IPC.DATA*/ +#define MXS40_SROMAPI_SILID_CODE 0x00000001u +/* [15:8]: ID type */ +#define MXS40_SROMAPI_SILID_TYPE_MSK 0x0000FF00u +#define MXS40_SROMAPI_SILID_TYPE_ROL 0x08u +/* [15:8]: Family Id Hi */ +#define MXS40_SROMAPI_SILID_FAMID_HI_MSK 0x0000FF00u +#define MXS40_SROMAPI_SILID_FAMID_HI_ROR 0x08u + /* [7:0]: Family Id Lo */ +#define MXS40_SROMAPI_SILID_FAMID_LO_MSK 0x000000FFu +#define MXS40_SROMAPI_SILID_FAMID_LO_ROR 0u +/* [19:16]: Protection state */ +#define MXS40_SROMAPI_SILID_PROT_MSK 0x000F0000u +#define MXS40_SROMAPI_SILID_PROT_ROR 0x10u +/* [15:8]: Silicon Id Hi */ +#define MXS40_SROMAPI_SILID_SILID_HI_MSK 0x0000FF00u +#define MXS40_SROMAPI_SILID_SILID_HI_ROR 0x08u +/* [15:8]: Silicon Id Lo */ +#define MXS40_SROMAPI_SILID_SILID_LO_MSK 0x000000FFu +#define MXS40_SROMAPI_SILID_SILID_LO_ROR 0x00u +/* [31:24]: Opcode = 0x06; [0]: 0 - arguments are passed in SRAM */ +#define MXS40_SROMAPI_PROGRAMROW_CODE 0x06000100u +/* [31:24]: Opcode = 0x14; [0]: 0 - arguments are passed in SRAM */ +#define MXS40_SROMAPI_ERASESECTOR_CODE 0x14000100u +/* [31:24]: Opcode = 0x1C; [0]: 0 - arguments are passed in SRAM */ +#define MXS40_SROMAPI_ERASEROW_CODE 0x1C000100u +#define IPC_ID 2u +#define LENGHT_SILICON_ID 16u +#define SIZE_OF_STRING 32u + +/*Offset for data location/size and Integrity check*/ +#define DATA_LOCATION_OFFSET 0x04 + +/*Property for data location/size and Integrity check*/ +#define DATA_LOCATION_PROPERTY 0x106 + +/*Offset for flash address which will be programed*/ +#define FLASH_ADDRESS_OFFSET 0x08 + +/*Offset for first data byte in SRAM*/ +#define DATA_OFFSET 0x10 + +/*Offset for set pointer to the first data byte location*/ +#define POINTER_ON_FIRST_BYTE_LOCATION_OFFSET 0x0C + +struct Psoc6ChipDetails { + uint32_t id; + const char *type; + uint32_t flashSizeInKb; +}; + +/* list of PSoC 6 chips + * flashSizeInKb is not necessary as it can be decoded from SPCIF_GEOMETRY*/ +const struct Psoc6ChipDetails psoc6Devices[] = { + /* PSoC 6 BLE II */ + { 0xE2071100, "CY8C616FMI-BL603", .flashSizeInKb = 512 }, + { 0xE2081100, "CY8C616FMI-BL673", .flashSizeInKb = 512 }, + { 0xE2091100, "CY8C616LQI-BL601", .flashSizeInKb = 512 }, + { 0xE20A1100, "CY8C616LQI-BL671", .flashSizeInKb = 512 }, + { 0xE20B1100, "CY8C617FMI-BL603", .flashSizeInKb = 1024 }, + { 0xE20C1100, "CY8C617FMI-BLD73", .flashSizeInKb = 1024 }, + { 0xE20D1100, "CY8C626FMI-BL603", .flashSizeInKb = 512 }, + { 0xE20E1100, "CY8C626BZI-BL604", .flashSizeInKb = 512 }, + { 0xE20F1100, "CY8C626BZI-BL674", .flashSizeInKb = 512 }, + { 0xE2111100, "CY8C627BZI-BL604", .flashSizeInKb = 1024 }, + { 0xE2121100, "CY8C627FMI-BLD73", .flashSizeInKb = 1024 }, + { 0xE2131100, "CY8C627BZI-BLD74", .flashSizeInKb = 1024 }, + { 0xE2141100, "CY8C636BZI-BL604", .flashSizeInKb = 512 }, + { 0xE2151100, "CY8C636BZI-BL674", .flashSizeInKb = 512 }, + { 0xE2161100, "CY8C636FMI-BL603", .flashSizeInKb = 512 }, + { 0xE2171100, "CY8C636FMI-BL673", .flashSizeInKb = 512 }, + { 0xE2181100, "CY8C636LQI-BL601", .flashSizeInKb = 512 }, + { 0xE2191100, "CY8C636LQI-BL671", .flashSizeInKb = 512 }, + { 0xE21A1100, "CY8C637BZI-BLD04", .flashSizeInKb = 1024 }, + { 0xE2011100, "CY8C637BZI-BLD74", .flashSizeInKb = 1024 }, + { 0xE21C1100, "CY8C637FMI-BLD03", .flashSizeInKb = 1024 }, + { 0xE2021100, "CY8C637FMI-BLD73", .flashSizeInKb = 1024 }, + { 0xE21E1100, "CY8C637LQI-BLD01", .flashSizeInKb = 1024 }, + { 0xE2031100, "CY8C637LQI-BLD71", .flashSizeInKb = 1024 }, + { 0xE2041100, "CY8C68237FM-BLE", .flashSizeInKb = 1024 }, + { 0xE2051100, "CY8C68237BZ-BLE", .flashSizeInKb = 1024 }, + + /* PSoC 6 M */ + { 0xE2001100, "CY8C637BZI-MD76", .flashSizeInKb = 1024 }, + { 0xE2201100, "CY8C616BZI-M606", .flashSizeInKb = 512 }, + { 0xE2211100, "CY8C616BZI-M676", .flashSizeInKb = 512 }, + { 0xE2221100, "CY8C617BZI-MD76", .flashSizeInKb = 1024 }, + { 0xE2231100, "CY8C626BZI-M606", .flashSizeInKb = 512 }, + { 0xE2241100, "CY8C627BZI-MD76", .flashSizeInKb = 1024 }, + { 0xE2251100, "CY8C636BZI-MD06", .flashSizeInKb = 512 }, + { 0xE2261100, "CY8C636BZI-MD76", .flashSizeInKb = 512 }, + { 0xE2271100, "CY8C637BZI-MD06", .flashSizeInKb = 1024 }, +}; + +struct psoc6FlashBank { + uint32_t rowSize; + uint32_t userBankSize; + int probed; + uint32_t siliconId; + uint8_t chipProtection; + uint32_t flashSizeInKb; +}; + +static const struct Psoc6ChipDetails *psoc6_details_by_id(uint32_t siliconId) +{ + const struct Psoc6ChipDetails *p = psoc6Devices; + const struct Psoc6ChipDetails *chipInfo; + uint16_t i; + uint16_t id = siliconId >> LENGHT_SILICON_ID; /* ignore die revision */ + for (i = 0; i < sizeof(psoc6Devices)/sizeof(psoc6Devices[0]); i++) { + if (p->id == id) + chipInfo = p; + p++; + } + LOG_INFO("Unknown PSoC 6 device silicon id 0x%08" PRIx32 ".", siliconId); + return chipInfo; +} + +static const char *psoc6_decode_chipProtection(uint8_t protection) +{ + char *protectType = calloc(SIZE_OF_STRING, sizeof(*protectType)); + switch (protection) { + case PSOC6_CHIP_PROT_UNKNOWN: + strcpy(protectType, "protection UNKNOWN"); + break; + case PSOC6_CHIP_PROT_VIRGIN: + strcpy(protectType, "protection VIRGIN"); + break; + case PSOC6_CHIP_PROT_NORMAL: + strcpy(protectType, "protection NORMAL"); + break; + case PSOC6_CHIP_PROT_SECURE: + strcpy(protectType, "protection SECURE"); + break; + case PSOC6_CHIP_PROT_DEAD: + strcpy(protectType, "protection DEAD"); + break; + default: + LOG_WARNING("Unknown protection state 0x%02" PRIx8 "", protection); + strcpy(protectType, "Not allowed"); + break; + } + + return protectType; +} + +FLASH_BANK_COMMAND_HANDLER(psoc6_flash_bank_command) +{ + struct psoc6FlashBank *psoc6_info; + int hr = ERROR_OK; + + if (CMD_ARGC < 6) { + hr = ERROR_COMMAND_SYNTAX_ERROR; + } else { + psoc6_info = calloc(1, sizeof(struct psoc6FlashBank)); + bank->driver_priv = psoc6_info; + psoc6_info->userBankSize = bank->size; + } + return hr; +} + + +/*------------------------------------------------------------------------------ + SROM APIs basics +-------------------------------------------------------------------------------- +****************************************************************************** +* Purpose: Polls lock status of IPC structure +* Parameter: +* target - current target device +* ipcId - Id of IPC structure +* - 0: IPC_STRUCT0 (CM0+) +* - 1: IPC_STRUCT1 (CM4) +* - 2: IPC_STRUCT2 (DAP) +* lockExpected - true if look state is expected, or false if look state is not expected +* timeOutAttempts - timeout +* Return: +* ERROR_OK: IPC structure locked successfully +* ERROR_FAIL: Cannot lock IPC structure +*******************************************************************************/ +int Ipc_PollLockStatus(struct target *target, uint32_t ipcId, bool lockExpected, int timeOutAttempts) +{ + /* Poll lock status*/ + int hr = ERROR_OK; + int attemptsElapsed = 0x00; + bool isExpectedStatus = false; + uint32_t readData; + uint32_t ipcAddr = IPC_STRUCT0 + IPC_STRUCT_SIZE * ipcId; + do { + /* Check lock status*/ + hr = target_read_u32(target, ipcAddr + IPC_STRUCT_LOCK_STATUS_OFFSET, &readData); + if (hr == ERROR_OK) { + bool isLocked = (readData & IPC_STRUCT_LOCK_STATUS_ACQUIRED_MSK) != 0; + isExpectedStatus = (lockExpected && isLocked) || (!lockExpected && !isLocked); + } + /* Check for timeout*/ + if (!isExpectedStatus) { + if (attemptsElapsed > timeOutAttempts) { + LOG_ERROR("Timeout polling lock status of IPC_STRUCT"); + hr = ERROR_FAIL; + break; + } + usleep(DELAY_10_MS); + attemptsElapsed++; + } + } while (!isExpectedStatus); + return hr; +} + + +/******************************************************************************* +* Purpose: Acquires MXS40 IPC structure +* Parameter: +* target - current target device +* ipcId - Id of IPC structure +* - 0: IPC_STRUCT0 (CM0+) +* - 1: IPC_STRUCT1 (CM4) +* - 2: IPC_STRUCT2 (DAP) +* timeOutAttempts - timeout +* Return: +* ERROR_OK: IPC structure acquired successfully +* ERROR_FAIL: Cannon acquire IPC structure +*******************************************************************************/ +int Ipc_Acquire(struct target *target, char ipcId, int timeOutAttempts) +{ + int hr = ERROR_OK; + int attemptsElapsed = 0x00; + bool isAcquired = false; + uint32_t readData; + uint32_t ipcAddr = IPC_STRUCT0 + IPC_STRUCT_SIZE * ipcId; + + do { + /* Acquire the lock in DAP IPC struct (IPC_STRUCT.ACQUIRE).*/ + hr = target_write_u32(target, ipcAddr + IPC_STRUCT_ACQUIRE_OFFSET, IPC_STRUCT_ACQUIRE_SUCCESS_MSK); + if (hr == ERROR_OK) { + /* Check if data is writed on first step */ + hr = target_read_u32(target, ipcAddr + IPC_STRUCT_ACQUIRE_OFFSET, &readData); + if (hr == ERROR_OK) + isAcquired = (readData & IPC_STRUCT_ACQUIRE_SUCCESS_MSK) != 0; + } + /* Check for timeout */ + if (!isAcquired) { + if (attemptsElapsed > timeOutAttempts) { + LOG_ERROR("Timeout acquiring IPC_STRUCT"); + hr = ERROR_FAIL; + break; + } + usleep(DELAY_10_MS); + attemptsElapsed++; + } + } while (!isAcquired); + + if (isAcquired) { + /* If IPC structure is acquired, the lock status should be set */ + hr = Ipc_PollLockStatus(target, ipcId, true, timeOutAttempts); + } + return hr; +} + + +/******************************************************************************* +* Purpose: Polls execution status of SROM API +* Parameter: +* target - current target device +* address - Memory address of SROM API status word +* timeOutAttempts - timeout +* dataOut - status word +* Return: +* ERROR_OK: SROM API returned successful execution status +* ERROR_FAIL: SROM API execution failed +*******************************************************************************/ +int PollSromApiStatus(struct target *target, int address, int timeOutAttempts, uint32_t *dataOut) +{ + /* Poll data from SRAM, returned after system call execution */ + int hr = ERROR_OK; + int attemptsElapsed = 0x00; + bool isAcquired = false; + + do { + /* Poll data */ + hr = target_read_u32(target, address, dataOut); + if (hr == ERROR_OK) + isAcquired = (*dataOut & MXS40_SROMAPI_STATUS_MSK) == MXS40_SROMAPI_STAT_SUCCESS; + + /* Check for timeout */ + if (!isAcquired) { + if (attemptsElapsed > timeOutAttempts) { + LOG_DEBUG("PollSromApiStatus - FAIL status - 0x%08x", (unsigned int)*dataOut); + LOG_ERROR("Timeout waiting for SROM API execution complete"); + hr = ERROR_FAIL; + break; + } + usleep(DELAY_10_MS); + attemptsElapsed ++; + } + } while (!isAcquired); + + LOG_DEBUG("PollSromApiStatus - OK status - 0x%08x", (unsigned int)*dataOut); + return hr; +} + + +/******************************************************************************* +* Purpose: +* Calls SROM API +* SROM APIs are executed by invoking a system call & providing +* the corresponding arguments. +* System calls can be performed by CM0+, CM4 or DAP. +* Each of them have a reserved IPC structure (used as a mailbox) through which +* they can request CM0+ to perform a system call. +* Each one acquires the specific mailbox, writes the opcode and +* argument to the data field of the mailbox and notifies a dedicated +* IPC interrupt structure. This results in an NMI interrupt in M0+. +* Parameter: +* target - current target device +* callIdAndParams - OpCode of SROM API and params (in case all params are in IPC structure) +* dataOut - status word +* Return: +* ERROR_OK: SROM API returned successful execution status +* ERROR_FAIL: SROM API execution failed +*******************************************************************************/ +int CallSromApi(struct target *target, uint32_t callIdAndParams, uint32_t *dataOut) +{ + int hr; + /* Check where the arguments for this API are located + [0]: 1 - arguments are passed in IPC.DATA. 0 - arguments are passed in SRAM */ + bool isDataInRam = (callIdAndParams & MXS40_SROMAPI_DATA_LOCATION_MSK) == 0; + unsigned long IPC_STRUC = IPC_STRUCT2; + /* Acquire IPC_STRUCT[0] for CM0+ */ + hr = Ipc_Acquire(target, IPC_ID, IPC_STRUCT_ACQUIRE_TIMEOUT_ATTEMPTS); + if (hr == ERROR_OK) { + /* Write to IPC_STRUCT0.DATA - Sys call ID and Parameters + OR address in SRAM, where they are located */ + if (isDataInRam) { + LOG_DEBUG("CallSromApi: isDataInRam = true: address -> 0x%x, data -> 0x%x", (unsigned int)(IPC_STRUC + IPC_STRUCT_DATA_OFFSET), SRAM_SCRATCH_ADDR); + hr = target_write_u32(target, (unsigned int)(IPC_STRUC + IPC_STRUCT_DATA_OFFSET), SRAM_SCRATCH_ADDR); + } else { + LOG_DEBUG("CallSromApi: isDataInRam = false: address -> 0x%x, data -> 0x%x", (unsigned int)(IPC_STRUC + IPC_STRUCT_DATA_OFFSET), callIdAndParams); + hr = target_write_u32(target, (unsigned int)(IPC_STRUC + IPC_STRUCT_DATA_OFFSET), callIdAndParams); + } + if (hr == ERROR_OK) { + /* Enable notification interrupt of IPC_INTR_STRUCT0(CM0+) for IPC_STRUCT2 */ + hr = target_write_u32(target, (IPC_INTR_STRUCT + IPC_INTR_STRUCT_INTR_IPC_MASK_OFFSET), 1 << (16 + IPC_ID)); + if (hr == ERROR_OK) { + /* Notify to IPC_INTR_STRUCT0. IPC_STRUCT2.MASK <- Notify */ + hr = target_write_u32(target, IPC_STRUC + IPC_STRUCT_NOTIFY_OFFSET, 1); + if (hr == ERROR_OK) { + /* Poll lock status */ + hr = Ipc_PollLockStatus(target, IPC_ID, false, IPC_STRUCT_ACQUIRE_TIMEOUT_ATTEMPTS); + if (hr == ERROR_OK) { + /* Poll Data byte */ + if (isDataInRam) + hr = PollSromApiStatus(target, SRAM_SCRATCH_ADDR, IPC_STRUCT_DATA_TIMEOUT_ATTEMPTS, dataOut); + else + hr = PollSromApiStatus(target, IPC_STRUC + IPC_STRUCT_DATA_OFFSET, IPC_STRUCT_DATA_TIMEOUT_ATTEMPTS, dataOut); + } + } + } + } + + } + return hr; +} + + +/******************************************************************************* +* Purpose: Get Silicon ID for connected target +* Parameter: +* target - current target device +* siliconId - value for Silicon ID +* protection - value for protected state +* Return: +* ERROR_OK: Resault of get silicon id operation is OK +* ERROR_FAIL: Resault of get silicon id operation is FAIL +*******************************************************************************/ +static int Psoc6GetSiliconId(struct target *target, uint32_t *siliconId, uint8_t *protection) +{ + int hr = 0; + uint32_t dataOut0, dataOut1; + uint32_t params; + uint32_t familyIdHi = 0x0; + uint32_t familyIdLo = 0x0; + uint32_t siliconIdHi = 0x0; + uint32_t siliconIdLo = 0x0; + + /* Type 0: Get Family ID & Revision ID + SRAM_SCRATCH: OpCode */ + params = MXS40_SROMAPI_SILID_CODE + (MXS40_SROMAPI_SILID_TYPE_MSK & (0 << MXS40_SROMAPI_SILID_TYPE_ROL)); + hr = CallSromApi(target, params, &dataOut0); + if (hr == ERROR_OK) { + /* Type 1: Get Silicon ID and protection state */ + params = (MXS40_SROMAPI_SILID_CODE + (MXS40_SROMAPI_SILID_TYPE_MSK & (1 << MXS40_SROMAPI_SILID_TYPE_ROL))); + hr = CallSromApi(target, params, &dataOut1); + if (hr == ERROR_OK) { + familyIdHi = (dataOut0 & MXS40_SROMAPI_SILID_FAMID_HI_MSK) >> MXS40_SROMAPI_SILID_FAMID_HI_ROR; /* Family ID High */ + familyIdLo = (dataOut0 & MXS40_SROMAPI_SILID_FAMID_LO_MSK) >> MXS40_SROMAPI_SILID_FAMID_LO_ROR; /* Family ID Low */ + siliconIdHi = (dataOut1 & MXS40_SROMAPI_SILID_SILID_HI_MSK) >> MXS40_SROMAPI_SILID_SILID_HI_ROR; /* Silicon ID High */ + siliconIdLo = (dataOut1 & MXS40_SROMAPI_SILID_SILID_LO_MSK) >> MXS40_SROMAPI_SILID_SILID_LO_ROR; /* Silicon ID Low */ + *protection = (dataOut1 & MXS40_SROMAPI_SILID_PROT_MSK) >> MXS40_SROMAPI_SILID_PROT_ROR; /* Protection state */ + } else { + LOG_ERROR("Get Silicon ID and protection state has failed results"); + } + } + *siliconId = ((siliconIdHi & 0xFF) << 24) | ((siliconIdLo & 0xFF) << 16) | ((familyIdHi & 0xFF) << 8) | (familyIdLo & 0xFF); + return hr; +} + + +/******************************************************************************* +* Purpose: Check if bank of flash in protected state +* Parameter: +* bank - flash bank +* Return: +* ERROR_OK: Resault of check operation is OK +* ERROR_FAIL: Resault of check operation is FAIL +*******************************************************************************/ +static int Psoc6ProtectCheck(struct flash_bank *bank) +{ + LOG_INFO("Get protection state not work in OpenOCD"); + return ERROR_OK; +} + + +/******************************************************************************* +* Purpose: Set protected state in bank of flash +* Parameter: +* bank - flash bank +* set - protected value +* first - first address with protected data +* last - last address with protected data +* Return: +* ERROR_OK: Resault of protect operation is OK +* ERROR_FAIL: Resault of protect operation is FAIL +*******************************************************************************/ +static int Psoc6Protect(struct flash_bank *bank, int set, int first, int last) +{ + LOG_INFO("Protect for PSoC6 is not support in OpenOCD"); + return ERROR_OK; +} + + +/******************************************************************************* +* Purpose: Detect device and get all main parameters +* Parameter: +* bank - flash bank +* Return: +* ERROR_OK: Resault of probe operation is OK +* ERROR_FAIL: Resault of probe operation is FAIL +*******************************************************************************/ +static int Psoc6Probe(struct flash_bank *bank) +{ + struct psoc6FlashBank *psoc6Info = bank->driver_priv; + struct target *target = bank->target; + uint32_t hr; + uint32_t flashSizeInKb = 0; + uint32_t maxFlashSizeInKb = 0; + uint32_t siliconId = 0; + uint32_t rowSize = 0; + uint8_t protection = 0; + uint32_t spcifGeometry = 0; + + uint32_t haltStatus; + + target_read_u32(target, 0xE000EDF0, &haltStatus); + LOG_DEBUG("Status HALT - 0x%x", haltStatus); + psoc6Info->probed = 0; + hr = target_read_u32(target, PSOC6_SPCIF_GEOMETRY, &spcifGeometry); + if (hr == ERROR_OK) { + rowSize = ROW_SIZE; + flashSizeInKb = (FLASH_SECTOR_LENGTH * (((spcifGeometry >> 24) & 0xFF) + 1)); + LOG_INFO("SPCIF geometry: %" PRIu32 " kb flash, row %" PRIu32 " bytes.", flashSizeInKb, rowSize); + + /* Get silicon ID from target. */ + hr = Psoc6GetSiliconId(target, &siliconId, &protection); + if (hr == ERROR_OK) { + + const struct Psoc6ChipDetails *details = psoc6_details_by_id(siliconId); + if (details) { + LOG_INFO("%s device detected.", details->type); + if (flashSizeInKb == 0) { + flashSizeInKb = details->flashSizeInKb; + } else { + if (flashSizeInKb != details->flashSizeInKb) + LOG_ERROR("Flash size mismatch"); + } + } + + psoc6Info->flashSizeInKb = flashSizeInKb; + psoc6Info->rowSize = rowSize; + psoc6Info->siliconId = siliconId; + psoc6Info->chipProtection = protection; + + /* failed reading flash size or flash size invalid (early silicon), + default to max target family */ + if (hr != ERROR_OK || flashSizeInKb == 0xffff || flashSizeInKb == 0) { + LOG_WARNING("PSoC 6 flash size failed, probe inaccurate - assuming %" PRIu32 " k flash", + maxFlashSizeInKb); + flashSizeInKb = maxFlashSizeInKb; + } + + /* if the user sets the size manually then ignore the probed value + this allows us to work around devices that have a invalid flash size register value */ + if (psoc6Info->userBankSize) { + LOG_INFO("ignoring flash probed value, using configured bank size"); + flashSizeInKb = psoc6Info->userBankSize / 1024; + } + + /* did we assign flash size? */ + assert(flashSizeInKb != 0xffff); + + /* calculate numbers of pages */ + uint32_t num_rows = flashSizeInKb * 1024 / rowSize; + + /* check that calculation result makes sense */ + assert(num_rows > 0); + + if (bank->sectors) { + free(bank->sectors); + bank->sectors = NULL; + } + + bank->base = MEM_BASE_FLASH; + bank->size = num_rows * rowSize; + bank->num_sectors = num_rows; + bank->sectors = malloc(sizeof(struct flash_sector) * num_rows); + /* This part doesn't follow the typical standard of 0xff + being the erased value.*/ + bank->default_padded_value = bank->erased_value = 0x00; + + uint32_t i; + for (i = 0; i < num_rows; i++) { + bank->sectors[i].offset = i * rowSize; + bank->sectors[i].size = rowSize; + bank->sectors[i].is_erased = -1; + bank->sectors[i].is_protected = 1; + } + + LOG_DEBUG("psoc6Info->flashSizeInKb-> 0x%x", psoc6Info->flashSizeInKb); + LOG_DEBUG("psoc6Info->rowSize-> 0x%x", psoc6Info->rowSize); + LOG_DEBUG("psoc6Info->siliconId -> 0x%x", psoc6Info->siliconId); + LOG_DEBUG("psoc6Info->chipProtection-> 0x%x", psoc6Info->chipProtection); + + LOG_DEBUG("bank->base -> 0x%x", bank->base); + LOG_DEBUG("bank->size -> 0x%x", bank->size); + LOG_DEBUG("bank->num_sectors-> 0x%x", bank->num_sectors); + + psoc6Info->probed = 1; + } + } + + return hr; +} + + +/******************************************************************************* +* Purpose: Auto detect device and get all main parameters +* Parameter: +* bank - flash bank +* Return: +* ERROR_OK: Resault of probe operation is OK +* ERROR_FAIL: Resault of probe operation is FAIL +*******************************************************************************/ +static int Psoc6AutoProbe(struct flash_bank *bank) +{ + struct psoc6FlashBank *psoc6Info = bank->driver_priv; + int hr; + + if (psoc6Info->probed) + hr = ERROR_OK; + else + hr = Psoc6Probe(bank); + + return hr; +} + + +/******************************************************************************* +* Purpose: Erase sector operation for connected target +* Parameter: +* target - current target device +* first - first address which will be erased +* last - last sector which will be erased +* Return: +* ERROR_OK: Resault of Erase sector operation is OK +* ERROR_FAIL: Resault of Erase sector operation is FAIL +*******************************************************************************/ +static int EraseSector(struct target *target, int first, int last) +{ + int hr; + LOG_DEBUG("first-> 0x%x, last-> 0x%x", first, last); + + for (int i = first; i < last; i++) { + int addr = MEM_BASE_FLASH + (i * FLASH_SECTOR_LENGTH * 1024); + + /* Prepare batch request. Skip immediate responses in batch mode. + SRAM_SCRATCH: OpCode */ + hr = target_write_u32(target, SRAM_SCRATCH_ADDR, MXS40_SROMAPI_ERASESECTOR_CODE); + if (hr != ERROR_OK) + break; + + /* SRAM_SCRATCH + 0x04: Flash address to be erased (in 32-bit system address format) */ + hr = target_write_u32(target, SRAM_SCRATCH_ADDR + DATA_LOCATION_OFFSET, addr); + if (hr != ERROR_OK) + break; + + /* Send batch request */ + uint32_t dataOut; + hr = CallSromApi(target, MXS40_SROMAPI_ERASESECTOR_CODE, &dataOut); + if (hr != ERROR_OK) { + LOG_ERROR("Sector \"%d\" from \"%d\" sectors are not erased. Failed result for Erase operation.", i, last); + break; + } + + LOG_DEBUG("Sector -> 0x%x is Erased", addr); + } + return hr; +} + + +/******************************************************************************* +* Purpose: Erase all sectors operation for connected target +* Parameter: +* bank - flash bank +* Return: +* ERROR_OK: Resault of Erase sector operation is OK +* ERROR_FAIL: Resault of Erase sector operation is FAIL +*******************************************************************************/ +static int psoc6_mass_erase(struct flash_bank *bank) +{ + int hr; + struct target *target = bank->target; + struct psoc6FlashBank *psoc6Info = bank->driver_priv; + int sectors = psoc6Info->flashSizeInKb / FLASH_SECTOR_LENGTH; + + LOG_INFO("sectors-> 0x%x", sectors); + + if (bank->target->state != TARGET_HALTED) { + LOG_ERROR("Target not halted"); + hr = ERROR_TARGET_NOT_HALTED; + } + + if (hr == ERROR_OK) + hr = EraseSector(target, 0, sectors); + + return hr; +} + + +/******************************************************************************* +* Purpose: Erase sector operation for connected target +* Parameter: +* bank - flash bank +* first - first address which will be erased +* last - last sector which will be erased +* Return: +* ERROR_OK: Resault of Erase sector operation is OK +* ERROR_FAIL: Resault of Erase sector operation is FAIL +*******************************************************************************/ +static int Psoc6Erase(struct flash_bank *bank, int first, int last) +{ + int hr = ERROR_OK; + struct target *target = bank->target; + struct psoc6FlashBank *psoc6Info = bank->driver_priv; + + LOG_DEBUG("bank->num_sectors-> 0x%x", bank->num_sectors); + LOG_DEBUG("Calc-> 0x%x", (psoc6Info->flashSizeInKb / FLASH_SECTOR_LENGTH)); + LOG_DEBUG("first-> 0x%x, last-> 0x%x", first, last - 1); + + if ((unsigned)(last - 1) != (psoc6Info->flashSizeInKb/FLASH_SECTOR_LENGTH)) + LOG_INFO("Count of sector is more then real present"); + else + hr = EraseSector(target, first, last - 1); + + return hr; +} + + +/******************************************************************************* +* Purpose: Write row operation for connected target +* Parameter: +* target - current target device +* address - start address for write data +* buffer - buffer with all data which need write +* count - lenght data +* Return: +* ERROR_OK: Resault of write row operation is OK +* ERROR_FAIL: Resault of write row operation is FAIL +*******************************************************************************/ +static int WriteRow(struct target *target, int address, const uint8_t * buffer, int count) +{ + int hr = ERROR_OK; + uint32_t dataOut; + /* 1. Prepare data for SROM API - write it to SRAM --- + SRAM_SCRATCH: OpCode*/ + hr = target_write_u32(target, SRAM_SCRATCH_ADDR, MXS40_SROMAPI_PROGRAMROW_CODE); + if (hr == ERROR_OK) { + + /* SRAM_SCRATCH + 0x04: Data location/size and Integrity check + --- + Bits[31:24] Bits[23:16] Bits[15:8] Bits[7:0] + xxxxxxxx Verify row Data location Data size + --- + Verify row: 0-Data integrity check is not performed 1-Data integrity check is performed + Data location : 0 page latch , 1- SRAM + Data size* 0 8b ,1-16b , 2 -32b ,3 64b , 4 128b , 5 256 b , 6 512b , 7) + Data size is ignored for S40 SONOS FLASH as the lowest granularity for program operation equals page size. */ + hr = target_write_u32(target, SRAM_SCRATCH_ADDR + DATA_LOCATION_OFFSET, DATA_LOCATION_OFFSET); + if (hr == ERROR_OK) { + /* SRAM_SCRATCH + 0x08: + Flash address to be programmed (in 32-bit system address format) */ + hr = target_write_u32(target, SRAM_SCRATCH_ADDR + FLASH_ADDRESS_OFFSET, address); + if (hr == ERROR_OK) { + /* SRAM_SCRATCH + 0x10...n: Data word 0..n (Data provided should be proportional to data size provided, data to be programmed into LSBs ) */ + uint32_t dataRamAddr = SRAM_SCRATCH_ADDR + DATA_OFFSET; + + /* SRAM_SCRATCH + 0x0C: Pointer to the first data byte location */ + hr = target_write_u32(target, SRAM_SCRATCH_ADDR + POINTER_ON_FIRST_BYTE_LOCATION_OFFSET, dataRamAddr); + if (hr == ERROR_OK) { + if (target_write_buffer(target, dataRamAddr, count, buffer) != ERROR_OK) { + LOG_ERROR("Write to flash buffer failed"); + hr = ERROR_FAIL; + } else { + LOG_DEBUG("PSOC6: WRITE_ROW: ADDRESS->0x%x, PARAMS->0x%x, DATARAMADDR->0x%x, COUNT->0x%x", address, DATA_LOCATION_OFFSET, dataRamAddr, count); + /* 2. Call SROM API --- */ + hr = CallSromApi(target, MXS40_SROMAPI_PROGRAMROW_CODE, &dataOut); + } + } + } + } + } + return hr; +} + + +/******************************************************************************* +* Purpose: Write operation for connected target +* Parameter: +* bank - flash bank +* buffer - buffer with all data which need write +* offset - offset of address where need to write data +* count - lenght data +* Return: +* ERROR_OK: Resault of write operation is OK +* ERROR_FAIL: Resault of write operation is FAIL +*******************************************************************************/ +static int Psoc6Write(struct flash_bank *bank, const uint8_t *buffer, uint32_t offset, uint32_t count) +{ + int hr = ERROR_OK; + struct psoc6FlashBank *psoc6Info = bank->driver_priv; + struct target *target = bank->target; + uint32_t bytesRemaining = count; + uint8_t pageBuffer[psoc6Info->rowSize]; + uint32_t address, size, sourceOffset, maxAdressSize; + + LOG_DEBUG("PSOC6_WRITE: OFFSET -> 0x%x, COUNT -> 0x%x", offset, count); + + sourceOffset = 0; + address = bank->base + offset; + maxAdressSize = (address + count); + while (address < maxAdressSize) { + LOG_DEBUG("PSOC6_WRITE: SOURCE_OFFSET->0x%x, ADDRESS->0x%x, ADDRESS+COUNT->0x%x, BYTES_REMAINING->0x%x", sourceOffset, address, maxAdressSize, bytesRemaining); + LOG_INFO("Write data in 0x%x address", address); + size = psoc6Info->rowSize; + if (bytesRemaining < psoc6Info->rowSize) { + memset(pageBuffer, 0x00, size); + memcpy(pageBuffer, &buffer[sourceOffset], bytesRemaining); + size = bytesRemaining; + } else { + memcpy(pageBuffer, &buffer[sourceOffset], size); + } + + hr = WriteRow(target, address, pageBuffer, size); + if (hr != ERROR_OK) + break; + + sourceOffset += size; + address = address + size; + bytesRemaining -= size; + } + + return hr; +} + +/******************************************************************************* +* Purpose: Get information about connected target +* Parameter: +* bank - flash bank +* buf - buffer all information +* buf_size - size for buffer +* Return: +* ERROR_OK: Resault of get info operation operation is OK +* ERROR_FAIL: Resault of get info operation operation is FAIL +*******************************************************************************/ +static int GetPsoc6Info(struct flash_bank *bank, char *buf, int buf_size) +{ + int hr; + struct psoc6FlashBank *psoc6Info = bank->driver_priv; + int printed = 0; + if (psoc6Info->probed == 0) { + hr = ERROR_FAIL; + } else { + const struct Psoc6ChipDetails *details = psoc6_details_by_id(psoc6Info->siliconId); + if (details) { + uint32_t chip_revision = psoc6Info->siliconId & 0xffffffff; + printed = snprintf(buf, buf_size, "PSoC 6 %s rev 0x%04" PRIx32 " ", details->type, chip_revision); + } else { + printed = snprintf(buf, buf_size, "PSoC 6 silicon id 0x%x", psoc6Info->siliconId); + } + + buf += printed; + buf_size -= printed; + + const char *prot_txt = psoc6_decode_chipProtection(psoc6Info->chipProtection); + uint32_t size_in_kb = bank->size / 1024; + snprintf(buf, buf_size, " flash %" PRIu32 " kb %s", size_in_kb, prot_txt); + + hr = ERROR_OK; + } + return hr; +} + +COMMAND_HANDLER(psoc6_handle_mass_erase_command) +{ + LOG_INFO("psoc6_handle_mass_erase_command function"); + int hr; + if (CMD_ARGC < 1) + hr = ERROR_COMMAND_SYNTAX_ERROR; + + if (hr == ERROR_OK) { + struct flash_bank *bank; + hr = CALL_COMMAND_HANDLER(flash_command_get_bank, 0, &bank); + if (hr == ERROR_OK) { + hr = psoc6_mass_erase(bank); + if (hr == ERROR_OK) + command_print(CMD_CTX, "psoc mass erase complete"); + else + command_print(CMD_CTX, "psoc mass erase failed"); + } + } + return hr; +} + +static const struct command_registration psoc6_exec_command_handlers[] = { + { + .name = "mass_erase", + .handler = psoc6_handle_mass_erase_command, + .mode = COMMAND_EXEC, + .usage = "bank_id", + .help = "Erase entire flash device.", + }, + COMMAND_REGISTRATION_DONE +}; + +static const struct command_registration psoc6_command_handlers[] = { + { + .name = "psoc6", + .mode = COMMAND_ANY, + .help = "PSoC 6 flash command group", + .usage = "", + .chain = psoc6_exec_command_handlers, + }, + COMMAND_REGISTRATION_DONE +}; + +struct flash_driver psoc6_flash = { + .name = "psoc6", + .commands = psoc6_command_handlers, + .flash_bank_command = psoc6_flash_bank_command, + .erase = Psoc6Erase, + .protect = Psoc6Protect, + .write = Psoc6Write, + .read = default_flash_read, + .probe = Psoc6Probe, + .auto_probe = Psoc6AutoProbe, + .erase_check = default_flash_blank_check, + .protect_check = Psoc6ProtectCheck, + .info = GetPsoc6Info, +}; diff --git a/tcl/target/psoc6.cfg b/tcl/target/psoc6.cfg new file mode 100755 index 0000000..c61f1ea --- /dev/null +++ b/tcl/target/psoc6.cfg @@ -0,0 +1,149 @@ +source [find target/swj-dp.tcl] + +global CORE_ID +if { [info exists CORE] } { + set CORE_ID $CORE +} else { + set CORE_ID M0 +} + +echo [format "CORE in %s" $CORE_ID] + +global _CHIPNAME +if { [info exists CHIPNAME] } { + set _CHIPNAME $CHIPNAME +} else { + set _CHIPNAME psoc6 +} + +# Work-area is a space in RAM used for flash programming +# By default use 16kB +global _WORKAREASIZE +if { [info exists WORKAREASIZE] } { + set _WORKAREASIZE $WORKAREASIZE +} else { + set _WORKAREASIZE 0x4000 +} + +global _TAPID +if { [info exists M0_TAPID] } { + set _TAPID $M0_TAPID +} else { + set _TAPID 0x6BA02477 +} + +swj_newdap $_CHIPNAME cpu -irlen 4 -ircapture 0x1 -irmask 0xf -expected-id $_TAPID + +global TARGET +set TARGET $_CHIPNAME.cpu + +if { 0 == [string compare $CORE_ID M4] } { +target create $TARGET cortex_m -chain-position $TARGET -ap-num 2 +echo [format "CORE_ID in %s" $CORE_ID] +} else { +target create $TARGET cortex_m -chain-position $TARGET -ap-num 1 +echo [format "CORE_ID in %s" $CORE_ID] +} + +$TARGET configure -work-area-phys 0x08002000 -work-area-size $_WORKAREASIZE -work-area-backup 0 + +$TARGET configure -event gdb-attach { +echo [format "GDB_ATTACH_BEFORE_CPU in %s" [$TARGET curstate]] +reset init +echo [format "GDB_ATTACH_AFTER_CPU in %s" [$TARGET curstate]] + +echo [format "APSEL in %s" $TARGET dap apsel] + +} + +set _FLASHNAME $_CHIPNAME.flash +flash bank $_FLASHNAME psoc6 0x10000000 0 0 0 $TARGET + +if {![using_hla]} { + cortex_m reset_config sysresetreq +} + +proc mread32 {addr} { + set value(0) 0 + mem2array value 32 $addr 1 + return $value(0) +} + +proc ocd_process_reset_inner { MODE } { + if { 0 != [string compare psoc6.cpu [target names]] } { + return -code error "PSoC 6 reset can handle only one psoc6.cpu target"; + } + set target psoc6.cpu + + echo [format "APSEL - 1 in %s" [$target dap apsel]] + + # If this target must be halted... + set halt -1 + if { 0 == [string compare $MODE halt] } { + set halt 1 + } + if { 0 == [string compare $MODE init] } { + set halt 1; + } + if { 0 == [string compare $MODE run ] } { + set halt 0; + } + if { $halt < 0 } { + return -code error "Invalid mode: $MODE, must be one of: halt, init, or run"; + } + + $target invoke-event reset-assert-pre + $target arp_reset assert 0 + $target invoke-event reset-assert-post + $target invoke-event reset-deassert-pre + if {![using_hla]} { # workaround ST-Link v2 fails and forcing reconnect + $target arp_reset deassert 0 + } + $target invoke-event reset-deassert-post + + #Pass 2 - if needed "init" + if { 0 == [string compare init $MODE] } { + set err [catch "$target arp_waitstate halted 5000"] + #Did it halt? + if { $err == 0 } { + $target invoke-event reset-init + } + + } + $target invoke-event reset-end + + # Pass 1 - Now wait for any halt (requested as part of reset + # assert/deassert) to happen. Ideally it takes effect without + # first executing any instructions. + if { $halt == 1 } { + + echo "Verify the debug enable, cpu halt bits are set" + set halted [mread32 0xE000EDF0] + echo [format "OCD_PROCESS_RESET_INNER FORMAT HALTED IN 0x%x" $halted] + if { 0x00000003 != [expr ($halted & 0x00000003)] } { + return + } + + echo "Load infinite for loop code in SRAM address" + mww 0x08001010 0xE7FEE7FE + + echo "Load PC with address of infinite for loop SRAM address" + reg pc 0x08001011 + + echo "Load LR with address of infinite for loop SRAM address" + reg lr 0x08001010 + + echo "Load SP with top of SRAM address" + reg sp 0x0801C000 + + echo "Read xPSR register, set the thumb bit, and restore modified value to xPSR register" + set xPSR [ocd_reg xPSR] + regsub {xPSR[^:]*: } $xPSR "" xPSR + set xPSR [expr ($xPSR | 0x01000000)] + echo [format "FORMAT xPSR in 0x%x" $xPSR] + reg xPSR $xPSR + + resume + } + echo [format "APSEL - 2 in %s" [$target dap apsel]] +} -- |