diff options
author | Andreas Färber <afaerber@suse.de> | 2016-04-30 15:10:05 +0200 |
---|---|---|
committer | Tomas Vanek <vanekt@fbl.cz> | 2018-06-06 15:48:33 +0100 |
commit | 2d5f2ede55150235352773a976166c3ab68297bc (patch) | |
tree | 0b0799438322d99771c5c889c110716ef0067d7f /src | |
parent | d02de3a8a92091b9761ebaf44dff1a71f5b2edcb (diff) | |
download | riscv-openocd-2d5f2ede55150235352773a976166c3ab68297bc.zip riscv-openocd-2d5f2ede55150235352773a976166c3ab68297bc.tar.gz riscv-openocd-2d5f2ede55150235352773a976166c3ab68297bc.tar.bz2 |
flash/nor: Add PSoC 5LP flash driver
Always probe for ECC mode and display ECC sectors if disabled.
Non-ECC write is implemented as zeroing the ECC/config bytes.
Erasing ECC sectors is ignored, erase-checking takes them into account.
Tested with CY8CKIT-059 (CY8C5888), except ECC mode.
Change-Id: If63b9ffca7ad8de038be3c086c49712b629ec554
Signed-off-by: Andreas Färber <afaerber@suse.de>
Signed-off-by: Tomas Vanek <vanekt@fbl.cz>
Signed-off-by: Forest Crossman <cyrozap@gmail.com>
Reviewed-on: http://openocd.zylin.com/3432
Tested-by: jenkins
Diffstat (limited to 'src')
-rw-r--r-- | src/flash/nor/Makefile.am | 1 | ||||
-rw-r--r-- | src/flash/nor/drivers.c | 2 | ||||
-rw-r--r-- | src/flash/nor/psoc5lp.c | 975 |
3 files changed, 978 insertions, 0 deletions
diff --git a/src/flash/nor/Makefile.am b/src/flash/nor/Makefile.am index 5335931..5e5cdcd 100644 --- a/src/flash/nor/Makefile.am +++ b/src/flash/nor/Makefile.am @@ -43,6 +43,7 @@ NOR_DRIVERS = \ %D%/ocl.c \ %D%/pic32mx.c \ %D%/psoc4.c \ + %D%/psoc5lp.c \ %D%/psoc6.c \ %D%/sim3x.c \ %D%/spi.c \ diff --git a/src/flash/nor/drivers.c b/src/flash/nor/drivers.c index b09e58f..f777df6 100644 --- a/src/flash/nor/drivers.c +++ b/src/flash/nor/drivers.c @@ -56,6 +56,7 @@ extern struct flash_driver numicro_flash; extern struct flash_driver ocl_flash; extern struct flash_driver pic32mx_flash; extern struct flash_driver psoc4_flash; +extern struct flash_driver psoc5lp_flash; extern struct flash_driver psoc6_flash; extern struct flash_driver sim3x_flash; extern struct flash_driver stellaris_flash; @@ -115,6 +116,7 @@ static struct flash_driver *flash_drivers[] = { &ocl_flash, &pic32mx_flash, &psoc4_flash, + &psoc5lp_flash, &psoc6_flash, &sim3x_flash, &stellaris_flash, diff --git a/src/flash/nor/psoc5lp.c b/src/flash/nor/psoc5lp.c new file mode 100644 index 0000000..44fd033 --- /dev/null +++ b/src/flash/nor/psoc5lp.c @@ -0,0 +1,975 @@ +/* + * PSoC 5LP flash driver + * + * Copyright (c) 2016 Andreas Färber + * + * 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/time_support.h> +#include <target/armv7m.h> + +#define PM_ACT_CFG0 0x400043A0 +#define SPC_CPU_DATA 0x40004720 +#define SPC_SR 0x40004722 +#define PHUB_CH0_BASIC_CFG 0x40007010 +#define PHUB_CH0_ACTION 0x40007014 +#define PHUB_CH0_BASIC_STATUS 0x40007018 +#define PHUB_CH1_BASIC_CFG 0x40007020 +#define PHUB_CH1_ACTION 0x40007024 +#define PHUB_CH1_BASIC_STATUS 0x40007028 +#define PHUB_CFGMEM0_CFG0 0x40007600 +#define PHUB_CFGMEM0_CFG1 0x40007604 +#define PHUB_CFGMEM1_CFG0 0x40007608 +#define PHUB_CFGMEM1_CFG1 0x4000760C +#define PHUB_TDMEM0_ORIG_TD0 0x40007800 +#define PHUB_TDMEM0_ORIG_TD1 0x40007804 +#define PHUB_TDMEM1_ORIG_TD0 0x40007808 +#define PHUB_TDMEM1_ORIG_TD1 0x4000780C +#define PANTHER_DEVICE_ID 0x4008001C + +#define SPC_KEY1 0xB6 +#define SPC_KEY2 0xD3 + +#define SPC_LOAD_BYTE 0x00 +#define SPC_LOAD_MULTI_BYTE 0x01 +#define SPC_LOAD_ROW 0x02 +#define SPC_READ_BYTE 0x03 +#define SPC_READ_MULTI_BYTE 0x04 +#define SPC_WRITE_ROW 0x05 +#define SPC_WRITE_USER_NVL 0x06 +#define SPC_PRG_ROW 0x07 +#define SPC_ERASE_SECTOR 0x08 +#define SPC_ERASE_ALL 0x09 +#define SPC_READ_HIDDEN_ROW 0x0A +#define SPC_PROGRAM_PROTECT_ROW 0x0B +#define SPC_GET_CHECKSUM 0x0C +#define SPC_GET_TEMP 0x0E +#define SPC_READ_VOLATILE_BYTE 0x10 + +#define SPC_ARRAY_ALL 0x3F +#define SPC_ARRAY_EEPROM 0x40 +#define SPC_ARRAY_NVL_USER 0x80 +#define SPC_ARRAY_NVL_WO 0xF8 + +#define SPC_ROW_PROTECTION 0 + +#define SPC_OPCODE_LEN 3 + +#define SPC_SR_DATA_READY (1 << 0) +#define SPC_SR_IDLE (1 << 1) + +#define PM_ACT_CFG0_EN_CLK_SPC (1 << 3) + +#define PHUB_CHx_BASIC_CFG_EN (1 << 0) +#define PHUB_CHx_BASIC_CFG_WORK_SEP (1 << 5) + +#define PHUB_CHx_ACTION_CPU_REQ (1 << 0) + +#define PHUB_CFGMEMx_CFG0 (1 << 7) + +#define PHUB_TDMEMx_ORIG_TD0_NEXT_TD_PTR_LAST (0xff << 16) +#define PHUB_TDMEMx_ORIG_TD0_INC_SRC_ADDR (1 << 24) + +#define NVL_3_ECCEN (1 << 3) + +#define ROW_SIZE 256 +#define ROW_ECC_SIZE 32 +#define ROWS_PER_SECTOR 64 +#define SECTOR_SIZE (ROWS_PER_SECTOR * ROW_SIZE) +#define ROWS_PER_BLOCK 256 +#define BLOCK_SIZE (ROWS_PER_BLOCK * ROW_SIZE) +#define SECTORS_PER_BLOCK (BLOCK_SIZE / SECTOR_SIZE) + +#define PART_NUMBER_LEN (17 + 1) + +struct psoc5lp_device { + uint32_t id; + unsigned fam; + unsigned speed_mhz; + unsigned flash_kb; + unsigned eeprom_kb; +}; + +/* + * Device information collected from datasheets. + * Different temperature ranges (C/I/Q/A) may share IDs, not differing otherwise. + */ +static const struct psoc5lp_device psoc5lp_devices[] = { + /* CY8C58LP Family Datasheet */ + { .id = 0x2E11F069, .fam = 8, .speed_mhz = 67, .flash_kb = 256, .eeprom_kb = 2 }, + { .id = 0x2E120069, .fam = 8, .speed_mhz = 67, .flash_kb = 256, .eeprom_kb = 2 }, + { .id = 0x2E123069, .fam = 8, .speed_mhz = 67, .flash_kb = 256, .eeprom_kb = 2 }, + { .id = 0x2E124069, .fam = 8, .speed_mhz = 67, .flash_kb = 256, .eeprom_kb = 2 }, + { .id = 0x2E126069, .fam = 8, .speed_mhz = 67, .flash_kb = 256, .eeprom_kb = 2 }, + { .id = 0x2E127069, .fam = 8, .speed_mhz = 67, .flash_kb = 256, .eeprom_kb = 2 }, + { .id = 0x2E117069, .fam = 8, .speed_mhz = 67, .flash_kb = 128, .eeprom_kb = 2 }, + { .id = 0x2E118069, .fam = 8, .speed_mhz = 67, .flash_kb = 128, .eeprom_kb = 2 }, + { .id = 0x2E119069, .fam = 8, .speed_mhz = 67, .flash_kb = 128, .eeprom_kb = 2 }, + { .id = 0x2E11C069, .fam = 8, .speed_mhz = 67, .flash_kb = 128, .eeprom_kb = 2 }, + { .id = 0x2E114069, .fam = 8, .speed_mhz = 67, .flash_kb = 64, .eeprom_kb = 2 }, + { .id = 0x2E115069, .fam = 8, .speed_mhz = 67, .flash_kb = 64, .eeprom_kb = 2 }, + { .id = 0x2E116069, .fam = 8, .speed_mhz = 67, .flash_kb = 64, .eeprom_kb = 2 }, + { .id = 0x2E160069, .fam = 8, .speed_mhz = 80, .flash_kb = 256, .eeprom_kb = 2 }, + /* '' */ + { .id = 0x2E161069, .fam = 8, .speed_mhz = 80, .flash_kb = 256, .eeprom_kb = 2 }, + /* '' */ + { .id = 0x2E1D2069, .fam = 8, .speed_mhz = 80, .flash_kb = 256, .eeprom_kb = 2 }, + { .id = 0x2E1D6069, .fam = 8, .speed_mhz = 80, .flash_kb = 256, .eeprom_kb = 2 }, + + /* CY8C56LP Family Datasheet */ + { .id = 0x2E10A069, .fam = 6, .speed_mhz = 67, .flash_kb = 256, .eeprom_kb = 2 }, + { .id = 0x2E10D069, .fam = 6, .speed_mhz = 67, .flash_kb = 256, .eeprom_kb = 2 }, + { .id = 0x2E10E069, .fam = 6, .speed_mhz = 67, .flash_kb = 256, .eeprom_kb = 2 }, + { .id = 0x2E106069, .fam = 6, .speed_mhz = 67, .flash_kb = 128, .eeprom_kb = 2 }, + { .id = 0x2E108069, .fam = 6, .speed_mhz = 67, .flash_kb = 128, .eeprom_kb = 2 }, + { .id = 0x2E109069, .fam = 6, .speed_mhz = 67, .flash_kb = 128, .eeprom_kb = 2 }, + { .id = 0x2E101069, .fam = 6, .speed_mhz = 67, .flash_kb = 64, .eeprom_kb = 2 }, + { .id = 0x2E104069, .fam = 6, .speed_mhz = 67, .flash_kb = 64, .eeprom_kb = 2 }, + /* '' */ + { .id = 0x2E105069, .fam = 6, .speed_mhz = 67, .flash_kb = 64, .eeprom_kb = 2 }, + { .id = 0x2E128069, .fam = 6, .speed_mhz = 67, .flash_kb = 128, .eeprom_kb = 2 }, + /* '' */ + { .id = 0x2E122069, .fam = 6, .speed_mhz = 67, .flash_kb = 256, .eeprom_kb = 2 }, + { .id = 0x2E129069, .fam = 6, .speed_mhz = 67, .flash_kb = 128, .eeprom_kb = 2 }, + { .id = 0x2E163069, .fam = 6, .speed_mhz = 80, .flash_kb = 256, .eeprom_kb = 2 }, + { .id = 0x2E156069, .fam = 6, .speed_mhz = 80, .flash_kb = 256, .eeprom_kb = 2 }, + { .id = 0x2E1D3069, .fam = 6, .speed_mhz = 80, .flash_kb = 256, .eeprom_kb = 2 }, + + /* CY8C54LP Family Datasheet */ + { .id = 0x2E11A069, .fam = 4, .speed_mhz = 67, .flash_kb = 256, .eeprom_kb = 2 }, + { .id = 0x2E16A069, .fam = 4, .speed_mhz = 67, .flash_kb = 256, .eeprom_kb = 2 }, + { .id = 0x2E12A069, .fam = 4, .speed_mhz = 67, .flash_kb = 256, .eeprom_kb = 2 }, + { .id = 0x2E103069, .fam = 4, .speed_mhz = 67, .flash_kb = 128, .eeprom_kb = 2 }, + { .id = 0x2E16C069, .fam = 4, .speed_mhz = 67, .flash_kb = 128, .eeprom_kb = 2 }, + { .id = 0x2E102069, .fam = 4, .speed_mhz = 67, .flash_kb = 64, .eeprom_kb = 2 }, + { .id = 0x2E148069, .fam = 4, .speed_mhz = 67, .flash_kb = 64, .eeprom_kb = 2 }, + { .id = 0x2E155069, .fam = 4, .speed_mhz = 67, .flash_kb = 64, .eeprom_kb = 2 }, + { .id = 0x2E16B069, .fam = 4, .speed_mhz = 67, .flash_kb = 64, .eeprom_kb = 2 }, + { .id = 0x2E12B069, .fam = 4, .speed_mhz = 67, .flash_kb = 32, .eeprom_kb = 2 }, + { .id = 0x2E168069, .fam = 4, .speed_mhz = 67, .flash_kb = 32, .eeprom_kb = 2 }, + { .id = 0x2E178069, .fam = 4, .speed_mhz = 80, .flash_kb = 256, .eeprom_kb = 2 }, + { .id = 0x2E15D069, .fam = 4, .speed_mhz = 80, .flash_kb = 256, .eeprom_kb = 2 }, + { .id = 0x2E1D4069, .fam = 4, .speed_mhz = 80, .flash_kb = 256, .eeprom_kb = 2 }, + + /* CY8C52LP Family Datasheet */ + { .id = 0x2E11E069, .fam = 2, .speed_mhz = 67, .flash_kb = 256, .eeprom_kb = 2 }, + { .id = 0x2E12F069, .fam = 2, .speed_mhz = 67, .flash_kb = 256, .eeprom_kb = 2 }, + { .id = 0x2E133069, .fam = 2, .speed_mhz = 67, .flash_kb = 128, .eeprom_kb = 2 }, + { .id = 0x2E159069, .fam = 2, .speed_mhz = 67, .flash_kb = 128, .eeprom_kb = 2 }, + { .id = 0x2E11D069, .fam = 2, .speed_mhz = 67, .flash_kb = 64, .eeprom_kb = 2 }, + { .id = 0x2E121069, .fam = 2, .speed_mhz = 67, .flash_kb = 64, .eeprom_kb = 2 }, + { .id = 0x2E184069, .fam = 2, .speed_mhz = 67, .flash_kb = 64, .eeprom_kb = 2 }, + { .id = 0x2E196069, .fam = 2, .speed_mhz = 67, .flash_kb = 64, .eeprom_kb = 2 }, + { .id = 0x2E132069, .fam = 2, .speed_mhz = 67, .flash_kb = 32, .eeprom_kb = 2 }, + { .id = 0x2E138069, .fam = 2, .speed_mhz = 67, .flash_kb = 32, .eeprom_kb = 2 }, + { .id = 0x2E13A069, .fam = 2, .speed_mhz = 67, .flash_kb = 32, .eeprom_kb = 2 }, + { .id = 0x2E152069, .fam = 2, .speed_mhz = 67, .flash_kb = 32, .eeprom_kb = 2 }, + { .id = 0x2E15F069, .fam = 2, .speed_mhz = 80, .flash_kb = 256, .eeprom_kb = 2 }, + { .id = 0x2E15A069, .fam = 2, .speed_mhz = 80, .flash_kb = 256, .eeprom_kb = 2 }, + { .id = 0x2E1D5069, .fam = 2, .speed_mhz = 80, .flash_kb = 256, .eeprom_kb = 2 }, +}; + +static void psoc5lp_get_part_number(const struct psoc5lp_device *dev, char *str) +{ + strcpy(str, "CY8Cabcdefg-LPxxx"); + + str[4] = '5'; + str[5] = '0' + dev->fam; + + switch (dev->speed_mhz) { + case 67: + str[6] = '6'; + break; + case 80: + str[6] = '8'; + break; + default: + str[6] = '?'; + } + + switch (dev->flash_kb) { + case 32: + str[7] = '5'; + break; + case 64: + str[7] = '6'; + break; + case 128: + str[7] = '7'; + break; + case 256: + str[7] = '8'; + break; + default: + str[7] = '?'; + } + + /* Package does not matter. */ + strncpy(str + 8, "xx", 2); + + /* Temperate range cannot uniquely be identified. */ + str[10] = 'x'; +} + +static int psoc5lp_get_device_id(struct target *target, uint32_t *id) +{ + int retval; + + retval = target_read_u32(target, PANTHER_DEVICE_ID, id); /* dummy read */ + if (retval != ERROR_OK) + return retval; + retval = target_read_u32(target, PANTHER_DEVICE_ID, id); + return retval; +} + +static int psoc5lp_find_device(struct target *target, + const struct psoc5lp_device **device) +{ + uint32_t device_id; + unsigned i; + int retval; + + *device = NULL; + + retval = psoc5lp_get_device_id(target, &device_id); + if (retval != ERROR_OK) + return retval; + LOG_DEBUG("PANTHER_DEVICE_ID = 0x%08" PRIX32, device_id); + + for (i = 0; i < ARRAY_SIZE(psoc5lp_devices); i++) { + if (psoc5lp_devices[i].id == device_id) { + *device = &psoc5lp_devices[i]; + return ERROR_OK; + } + } + + LOG_ERROR("Device 0x%08" PRIX32 " not supported", device_id); + return ERROR_FLASH_OPER_UNSUPPORTED; +} + +static int psoc5lp_spc_enable_clock(struct target *target) +{ + int retval; + uint8_t pm_act_cfg0; + + retval = target_read_u8(target, PM_ACT_CFG0, &pm_act_cfg0); + if (retval != ERROR_OK) { + LOG_ERROR("Cannot read PM_ACT_CFG0"); + return retval; + } + + if (pm_act_cfg0 & PM_ACT_CFG0_EN_CLK_SPC) + return ERROR_OK; /* clock already enabled */ + + retval = target_write_u8(target, PM_ACT_CFG0, pm_act_cfg0 | PM_ACT_CFG0_EN_CLK_SPC); + if (retval != ERROR_OK) + LOG_ERROR("Cannot enable SPC clock"); + + return retval; +} + +static int psoc5lp_spc_write_opcode(struct target *target, uint8_t opcode) +{ + int retval; + + retval = target_write_u8(target, SPC_CPU_DATA, SPC_KEY1); + if (retval != ERROR_OK) + return retval; + retval = target_write_u8(target, SPC_CPU_DATA, SPC_KEY2 + opcode); + if (retval != ERROR_OK) + return retval; + retval = target_write_u8(target, SPC_CPU_DATA, opcode); + return retval; +} + +static void psoc5lp_spc_write_opcode_buffer(struct target *target, + uint8_t *buf, uint8_t opcode) +{ + buf[0] = SPC_KEY1; + buf[1] = SPC_KEY2 + opcode; + buf[2] = opcode; +} + +static int psoc5lp_spc_busy_wait_data(struct target *target) +{ + int64_t endtime; + uint8_t sr; + int retval; + + retval = target_read_u8(target, SPC_SR, &sr); /* dummy read */ + if (retval != ERROR_OK) + return retval; + + endtime = timeval_ms() + 1000; /* 1 second timeout */ + do { + alive_sleep(1); + retval = target_read_u8(target, SPC_SR, &sr); + if (retval != ERROR_OK) + return retval; + if (sr == SPC_SR_DATA_READY) + return ERROR_OK; + } while (timeval_ms() < endtime); + + return ERROR_FLASH_OPERATION_FAILED; +} + +static int psoc5lp_spc_busy_wait_idle(struct target *target) +{ + int64_t endtime; + uint8_t sr; + int retval; + + retval = target_read_u8(target, SPC_SR, &sr); /* dummy read */ + if (retval != ERROR_OK) + return retval; + + endtime = timeval_ms() + 1000; /* 1 second timeout */ + do { + alive_sleep(1); + retval = target_read_u8(target, SPC_SR, &sr); + if (retval != ERROR_OK) + return retval; + if (sr == SPC_SR_IDLE) + return ERROR_OK; + } while (timeval_ms() < endtime); + + return ERROR_FLASH_OPERATION_FAILED; +} + +static int psoc5lp_spc_read_byte(struct target *target, + uint8_t array_id, uint8_t offset, uint8_t *data) +{ + int retval; + + retval = psoc5lp_spc_write_opcode(target, SPC_READ_BYTE); + if (retval != ERROR_OK) + return retval; + retval = target_write_u8(target, SPC_CPU_DATA, array_id); + if (retval != ERROR_OK) + return retval; + retval = target_write_u8(target, SPC_CPU_DATA, offset); + if (retval != ERROR_OK) + return retval; + + retval = psoc5lp_spc_busy_wait_data(target); + if (retval != ERROR_OK) + return retval; + + retval = target_read_u8(target, SPC_CPU_DATA, data); + if (retval != ERROR_OK) + return retval; + + retval = psoc5lp_spc_busy_wait_idle(target); + if (retval != ERROR_OK) + return retval; + + return ERROR_OK; +} + +static int psoc5lp_spc_erase_sector(struct target *target, + uint8_t array_id, uint8_t row_id) +{ + int retval; + + retval = psoc5lp_spc_write_opcode(target, SPC_ERASE_SECTOR); + if (retval != ERROR_OK) + return retval; + retval = target_write_u8(target, SPC_CPU_DATA, array_id); + if (retval != ERROR_OK) + return retval; + retval = target_write_u8(target, SPC_CPU_DATA, row_id); + if (retval != ERROR_OK) + return retval; + + retval = psoc5lp_spc_busy_wait_idle(target); + if (retval != ERROR_OK) + return retval; + + return ERROR_OK; +} + +static int psoc5lp_spc_erase_all(struct target *target) +{ + int retval; + + retval = psoc5lp_spc_write_opcode(target, SPC_ERASE_ALL); + if (retval != ERROR_OK) + return retval; + + retval = psoc5lp_spc_busy_wait_idle(target); + if (retval != ERROR_OK) + return retval; + + return ERROR_OK; +} + +static int psoc5lp_spc_read_hidden_row(struct target *target, + uint8_t array_id, uint8_t row_id, uint8_t *data) +{ + int i, retval; + + retval = psoc5lp_spc_write_opcode(target, SPC_READ_HIDDEN_ROW); + if (retval != ERROR_OK) + return retval; + retval = target_write_u8(target, SPC_CPU_DATA, array_id); + if (retval != ERROR_OK) + return retval; + retval = target_write_u8(target, SPC_CPU_DATA, row_id); + if (retval != ERROR_OK) + return retval; + + retval = psoc5lp_spc_busy_wait_data(target); + if (retval != ERROR_OK) + return retval; + + for (i = 0; i < ROW_SIZE; i++) { + retval = target_read_u8(target, SPC_CPU_DATA, &data[i]); + if (retval != ERROR_OK) + return retval; + } + + retval = psoc5lp_spc_busy_wait_idle(target); + if (retval != ERROR_OK) + return retval; + + return ERROR_OK; +} + +static int psoc5lp_spc_get_temp(struct target *target, uint8_t samples, + uint8_t *data) +{ + int retval; + + retval = psoc5lp_spc_write_opcode(target, SPC_GET_TEMP); + if (retval != ERROR_OK) + return retval; + retval = target_write_u8(target, SPC_CPU_DATA, samples); + if (retval != ERROR_OK) + return retval; + + retval = psoc5lp_spc_busy_wait_data(target); + if (retval != ERROR_OK) + return retval; + + retval = target_read_u8(target, SPC_CPU_DATA, &data[0]); + if (retval != ERROR_OK) + return retval; + retval = target_read_u8(target, SPC_CPU_DATA, &data[1]); + if (retval != ERROR_OK) + return retval; + + retval = psoc5lp_spc_busy_wait_idle(target); + if (retval != ERROR_OK) + return retval; + + return ERROR_OK; +} + +/* + * Program Flash + */ + +struct psoc5lp_flash_bank { + bool probed; + const struct psoc5lp_device *device; + bool ecc_enabled; +}; + +static int psoc5lp_erase(struct flash_bank *bank, int first, int last) +{ + struct psoc5lp_flash_bank *psoc_bank = bank->driver_priv; + int i, retval; + + if (!psoc_bank->ecc_enabled) { + /* Silently avoid erasing sectors twice */ + if (last >= first + bank->num_sectors / 2) { + LOG_DEBUG("Skipping duplicate erase of sectors %d to %d", + first + bank->num_sectors / 2, last); + last = first + (bank->num_sectors / 2) - 1; + } + /* Check for any remaining ECC sectors */ + if (last >= bank->num_sectors / 2) { + LOG_WARNING("Skipping erase of ECC region sectors %d to %d", + bank->num_sectors / 2, last); + last = (bank->num_sectors / 2) - 1; + } + } + + for (i = first; i <= last; i++) { + retval = psoc5lp_spc_erase_sector(bank->target, + i / SECTORS_PER_BLOCK, i % SECTORS_PER_BLOCK); + if (retval != ERROR_OK) + return retval; + } + + return ERROR_OK; +} + +/* Derived from core.c:default_flash_blank_check() */ +static int psoc5lp_erase_check(struct flash_bank *bank) +{ + struct psoc5lp_flash_bank *psoc_bank = bank->driver_priv; + struct target *target = bank->target; + uint32_t blank; + int i, num_sectors, retval; + + if (target->state != TARGET_HALTED) { + LOG_ERROR("Target not halted"); + return ERROR_TARGET_NOT_HALTED; + } + + num_sectors = bank->num_sectors; + if (!psoc_bank->ecc_enabled) + num_sectors /= 2; + + for (i = 0; i < num_sectors; i++) { + uint32_t address = bank->base + bank->sectors[i].offset; + uint32_t size = bank->sectors[i].size; + + retval = armv7m_blank_check_memory(target, address, size, + &blank, bank->erased_value); + if (retval != ERROR_OK) + return retval; + + if (blank == 0x00 && !psoc_bank->ecc_enabled) { + address = bank->base + bank->sectors[num_sectors + i].offset; + size = bank->sectors[num_sectors + i].size; + + retval = armv7m_blank_check_memory(target, address, size, + &blank, bank->erased_value); + if (retval != ERROR_OK) + return retval; + } + + if (blank == 0x00) { + bank->sectors[i].is_erased = 1; + bank->sectors[num_sectors + i].is_erased = 1; + } else { + bank->sectors[i].is_erased = 0; + bank->sectors[num_sectors + i].is_erased = 0; + } + } + + return ERROR_OK; +} + +static int psoc5lp_write(struct flash_bank *bank, const uint8_t *buffer, + uint32_t offset, uint32_t byte_count) +{ + struct psoc5lp_flash_bank *psoc_bank = bank->driver_priv; + struct target *target = bank->target; + struct working_area *code_area, *even_row_area, *odd_row_area; + uint32_t row_size; + uint8_t temp[2], buf[12], ecc_bytes[ROW_ECC_SIZE]; + unsigned array_id, row; + int i, retval; + + if (offset + byte_count > bank->size) { + LOG_ERROR("Writing to ECC not supported"); + return ERROR_FLASH_DST_OUT_OF_BANK; + } + + if (offset % ROW_SIZE != 0) { + LOG_ERROR("Writes must be row-aligned, got offset 0x%08" PRIx32, + offset); + return ERROR_FLASH_DST_BREAKS_ALIGNMENT; + } + + row_size = ROW_SIZE; + if (!psoc_bank->ecc_enabled) { + row_size += ROW_ECC_SIZE; + memset(ecc_bytes, bank->default_padded_value, ROW_ECC_SIZE); + } + + retval = psoc5lp_spc_get_temp(target, 3, temp); + if (retval != ERROR_OK) { + LOG_ERROR("Unable to read Die temperature"); + return retval; + } + LOG_DEBUG("Get_Temp: sign 0x%02" PRIx8 ", magnitude 0x%02" PRIx8, + temp[0], temp[1]); + + assert(target_get_working_area_avail(target) == target->working_area_size); + retval = target_alloc_working_area(target, + target_get_working_area_avail(target) / 2, &code_area); + if (retval != ERROR_OK) { + LOG_ERROR("Could not allocate working area for program SRAM"); + return retval; + } + assert(code_area->address < 0x20000000); + + retval = target_alloc_working_area(target, + SPC_OPCODE_LEN + 1 + row_size + 3 + SPC_OPCODE_LEN + 6, + &even_row_area); + if (retval != ERROR_OK) { + LOG_ERROR("Could not allocate working area for even row"); + goto err_alloc_even; + } + assert(even_row_area->address >= 0x20000000); + + retval = target_alloc_working_area(target, even_row_area->size, + &odd_row_area); + if (retval != ERROR_OK) { + LOG_ERROR("Could not allocate working area for odd row"); + goto err_alloc_odd; + } + assert(odd_row_area->address >= 0x20000000); + + for (array_id = offset / BLOCK_SIZE; byte_count > 0; array_id++) { + for (row = (offset / ROW_SIZE) % ROWS_PER_BLOCK; + row < ROWS_PER_BLOCK && byte_count > 0; row++) { + bool even_row = (row % 2 == 0); + struct working_area *data_area = even_row ? even_row_area : odd_row_area; + unsigned len = MIN(ROW_SIZE, byte_count); + + LOG_DEBUG("Writing load command for array %u row %u at 0x%08" TARGET_PRIxADDR, + array_id, row, data_area->address); + + psoc5lp_spc_write_opcode_buffer(target, buf, SPC_LOAD_ROW); + buf[SPC_OPCODE_LEN] = array_id; + retval = target_write_buffer(target, data_area->address, 4, buf); + if (retval != ERROR_OK) + goto err_write; + + retval = target_write_buffer(target, + data_area->address + SPC_OPCODE_LEN + 1, + len, buffer); + if (retval != ERROR_OK) + goto err_write; + buffer += len; + byte_count -= len; + offset += len; + + if (len < ROW_SIZE) { + uint8_t padding[ROW_SIZE]; + + memset(padding, bank->default_padded_value, ROW_SIZE); + + LOG_DEBUG("Padding %d bytes", ROW_SIZE - len); + retval = target_write_buffer(target, + data_area->address + SPC_OPCODE_LEN + 1 + len, + ROW_SIZE - len, padding); + if (retval != ERROR_OK) + goto err_write; + } + + if (!psoc_bank->ecc_enabled) { + retval = target_write_buffer(target, + data_area->address + SPC_OPCODE_LEN + 1 + ROW_SIZE, + sizeof(ecc_bytes), ecc_bytes); + if (retval != ERROR_OK) + goto err_write; + } + + for (i = 0; i < 3; i++) + buf[i] = 0x00; /* 3 NOPs for short delay */ + psoc5lp_spc_write_opcode_buffer(target, buf + 3, SPC_PRG_ROW); + buf[3 + SPC_OPCODE_LEN] = array_id; + buf[3 + SPC_OPCODE_LEN + 1] = row >> 8; + buf[3 + SPC_OPCODE_LEN + 2] = row & 0xff; + memcpy(buf + 3 + SPC_OPCODE_LEN + 3, temp, 2); + buf[3 + SPC_OPCODE_LEN + 5] = 0x00; /* padding */ + retval = target_write_buffer(target, + data_area->address + SPC_OPCODE_LEN + 1 + row_size, + 12, buf); + if (retval != ERROR_OK) + goto err_write; + + retval = target_write_u32(target, + even_row ? PHUB_CH0_BASIC_STATUS : PHUB_CH1_BASIC_STATUS, + (even_row ? 0 : 1) << 8); + if (retval != ERROR_OK) + goto err_dma; + + retval = target_write_u32(target, + even_row ? PHUB_CH0_BASIC_CFG : PHUB_CH1_BASIC_CFG, + PHUB_CHx_BASIC_CFG_WORK_SEP | PHUB_CHx_BASIC_CFG_EN); + if (retval != ERROR_OK) + goto err_dma; + + retval = target_write_u32(target, + even_row ? PHUB_CFGMEM0_CFG0 : PHUB_CFGMEM1_CFG0, + PHUB_CFGMEMx_CFG0); + if (retval != ERROR_OK) + goto err_dma; + + retval = target_write_u32(target, + even_row ? PHUB_CFGMEM0_CFG1 : PHUB_CFGMEM1_CFG1, + ((SPC_CPU_DATA >> 16) << 16) | (data_area->address >> 16)); + if (retval != ERROR_OK) + goto err_dma; + + retval = target_write_u32(target, + even_row ? PHUB_TDMEM0_ORIG_TD0 : PHUB_TDMEM1_ORIG_TD0, + PHUB_TDMEMx_ORIG_TD0_INC_SRC_ADDR | + PHUB_TDMEMx_ORIG_TD0_NEXT_TD_PTR_LAST | + ((SPC_OPCODE_LEN + 1 + row_size + 3 + SPC_OPCODE_LEN + 5) & 0xfff)); + if (retval != ERROR_OK) + goto err_dma; + + retval = target_write_u32(target, + even_row ? PHUB_TDMEM0_ORIG_TD1 : PHUB_TDMEM1_ORIG_TD1, + ((SPC_CPU_DATA & 0xffff) << 16) | (data_area->address & 0xffff)); + if (retval != ERROR_OK) + goto err_dma; + + retval = psoc5lp_spc_busy_wait_idle(target); + if (retval != ERROR_OK) + goto err_idle; + + retval = target_write_u32(target, + even_row ? PHUB_CH0_ACTION : PHUB_CH1_ACTION, + PHUB_CHx_ACTION_CPU_REQ); + if (retval != ERROR_OK) + goto err_dma_action; + } + } + + retval = psoc5lp_spc_busy_wait_idle(target); + +err_dma_action: +err_idle: +err_dma: +err_write: + target_free_working_area(target, odd_row_area); +err_alloc_odd: + target_free_working_area(target, even_row_area); +err_alloc_even: + target_free_working_area(target, code_area); + + return retval; +} + +static int psoc5lp_protect_check(struct flash_bank *bank) +{ + struct psoc5lp_flash_bank *psoc_bank = bank->driver_priv; + uint8_t row_data[ROW_SIZE]; + const unsigned protection_bytes_per_sector = ROWS_PER_SECTOR * 2 / 8; + unsigned i, j, k, num_sectors; + int retval; + + if (bank->target->state != TARGET_HALTED) { + LOG_ERROR("Target not halted"); + return ERROR_TARGET_NOT_HALTED; + } + + for (i = 0; i < DIV_ROUND_UP(bank->size, BLOCK_SIZE); i++) { + retval = psoc5lp_spc_read_hidden_row(bank->target, i, + SPC_ROW_PROTECTION, row_data); + if (retval != ERROR_OK) + return retval; + + /* Last flash array may have less rows, but in practice full sectors. */ + if (i == bank->size / BLOCK_SIZE) + num_sectors = (bank->size % BLOCK_SIZE) / SECTOR_SIZE; + else + num_sectors = SECTORS_PER_BLOCK; + + for (j = 0; j < num_sectors; j++) { + int sector_nr = i * SECTORS_PER_BLOCK + j; + struct flash_sector *sector = &bank->sectors[sector_nr]; + struct flash_sector *ecc_sector; + + if (psoc_bank->ecc_enabled) + ecc_sector = &bank->sectors[bank->num_sectors + sector_nr]; + else + ecc_sector = &bank->sectors[bank->num_sectors / 2 + sector_nr]; + + sector->is_protected = ecc_sector->is_protected = 0; + for (k = protection_bytes_per_sector * j; + k < protection_bytes_per_sector * (j + 1); k++) { + assert(k < protection_bytes_per_sector * SECTORS_PER_BLOCK); + LOG_DEBUG("row[%u][%02u] = 0x%02" PRIx8, i, k, row_data[k]); + if (row_data[k] != 0x00) { + sector->is_protected = ecc_sector->is_protected = 1; + break; + } + } + } + } + + return ERROR_OK; +} + +static int psoc5lp_get_info_command(struct flash_bank *bank, char *buf, int buf_size) +{ + struct psoc5lp_flash_bank *psoc_bank = bank->driver_priv; + char part_number[PART_NUMBER_LEN]; + const char *ecc; + + psoc5lp_get_part_number(psoc_bank->device, part_number); + ecc = psoc_bank->ecc_enabled ? "ECC enabled" : "ECC disabled"; + + snprintf(buf, buf_size, "%s %s", part_number, ecc); + + return ERROR_OK; +} + +static int psoc5lp_probe(struct flash_bank *bank) +{ + struct target *target = bank->target; + struct psoc5lp_flash_bank *psoc_bank = bank->driver_priv; + uint32_t flash_addr = bank->base; + uint8_t nvl[4], temp[2]; + int i, retval; + + if (target->state != TARGET_HALTED) { + LOG_ERROR("Target not halted"); + return ERROR_TARGET_NOT_HALTED; + } + + if (!psoc_bank->device) { + retval = psoc5lp_find_device(target, &psoc_bank->device); + if (retval != ERROR_OK) + return retval; + + bank->size = psoc_bank->device->flash_kb * 1024; + } + + bank->num_sectors = DIV_ROUND_UP(bank->size, SECTOR_SIZE); + + if (!psoc_bank->probed) { + retval = psoc5lp_spc_enable_clock(target); + if (retval != ERROR_OK) + return retval; + + /* First values read are inaccurate, so do it once now. */ + retval = psoc5lp_spc_get_temp(target, 3, temp); + if (retval != ERROR_OK) { + LOG_ERROR("Unable to read Die temperature"); + return retval; + } + + bank->sectors = calloc(bank->num_sectors * 2, + sizeof(struct flash_sector)); + for (i = 0; i < bank->num_sectors; i++) { + bank->sectors[i].size = SECTOR_SIZE; + bank->sectors[i].offset = flash_addr - bank->base; + bank->sectors[i].is_erased = -1; + bank->sectors[i].is_protected = -1; + + flash_addr += bank->sectors[i].size; + } + flash_addr = 0x48000000; + for (i = bank->num_sectors; i < bank->num_sectors * 2; i++) { + bank->sectors[i].size = ROWS_PER_SECTOR * ROW_ECC_SIZE; + bank->sectors[i].offset = flash_addr - bank->base; + bank->sectors[i].is_erased = -1; + bank->sectors[i].is_protected = -1; + + flash_addr += bank->sectors[i].size; + } + + bank->default_padded_value = bank->erased_value = 0x00; + + psoc_bank->probed = true; + } + + retval = psoc5lp_spc_read_byte(target, SPC_ARRAY_NVL_USER, 3, &nvl[3]); + if (retval != ERROR_OK) + return retval; + LOG_DEBUG("NVL[%d] = 0x%02" PRIx8, 3, nvl[3]); + psoc_bank->ecc_enabled = nvl[3] & NVL_3_ECCEN; + + if (!psoc_bank->ecc_enabled) + bank->num_sectors *= 2; + + return ERROR_OK; +} + +static int psoc5lp_auto_probe(struct flash_bank *bank) +{ + return psoc5lp_probe(bank); +} + +COMMAND_HANDLER(psoc5lp_handle_mass_erase_command) +{ + struct flash_bank *bank; + int retval; + + if (CMD_ARGC < 1) + return ERROR_COMMAND_SYNTAX_ERROR; + + retval = CALL_COMMAND_HANDLER(flash_command_get_bank, 0, &bank); + if (retval != ERROR_OK) + return retval; + + retval = psoc5lp_spc_erase_all(bank->target); + if (retval == ERROR_OK) + command_print(CMD_CTX, "PSoC 5LP erase succeeded"); + else + command_print(CMD_CTX, "PSoC 5LP erase failed"); + + return retval; +} + +FLASH_BANK_COMMAND_HANDLER(psoc5lp_flash_bank_command) +{ + struct psoc5lp_flash_bank *psoc_bank; + + psoc_bank = malloc(sizeof(struct psoc5lp_flash_bank)); + if (!psoc_bank) + return ERROR_FLASH_OPERATION_FAILED; + + psoc_bank->probed = false; + psoc_bank->device = NULL; + + bank->driver_priv = psoc_bank; + + return ERROR_OK; +} + +static const struct command_registration psoc5lp_exec_command_handlers[] = { + { + .name = "mass_erase", + .handler = psoc5lp_handle_mass_erase_command, + .mode = COMMAND_EXEC, + .usage = "bank_id", + .help = "Erase all flash data and ECC/configuration bytes, " + "all flash protection rows, " + "and all row latches in all flash arrays on the device.", + }, + COMMAND_REGISTRATION_DONE +}; + +static const struct command_registration psoc5lp_command_handlers[] = { + { + .name = "psoc5lp", + .mode = COMMAND_ANY, + .help = "PSoC 5LP flash command group", + .usage = "", + .chain = psoc5lp_exec_command_handlers, + }, + COMMAND_REGISTRATION_DONE +}; + +struct flash_driver psoc5lp_flash = { + .name = "psoc5lp", + .commands = psoc5lp_command_handlers, + .flash_bank_command = psoc5lp_flash_bank_command, + .info = psoc5lp_get_info_command, + .probe = psoc5lp_probe, + .auto_probe = psoc5lp_auto_probe, + .protect_check = psoc5lp_protect_check, + .read = default_flash_read, + .erase = psoc5lp_erase, + .erase_check = psoc5lp_erase_check, + .write = psoc5lp_write, +}; |