psoc5lp: Add NV Latch flash driver 34/3434/22
authorAndreas Färber <afaerber@suse.de>
Tue, 3 May 2016 21:47:54 +0000 (23:47 +0200)
committerTomas Vanek <vanekt@fbl.cz>
Wed, 6 Jun 2018 17:12:21 +0000 (18:12 +0100)
Erasing is not supported by the hardware, it can be written directly.

Tested on CY8CKIT-059, except modifying-write.

Change-Id: I6e920ed930dcd5c7f0b10c5b1b4791a828d9080a
Signed-off-by: Andreas Färber <afaerber@suse.de>
Signed-off-by: Tomas Vanek <vanekt@fbl.cz>
Reviewed-on: http://openocd.zylin.com/3434
Tested-by: jenkins
doc/openocd.texi
src/flash/nor/drivers.c
src/flash/nor/psoc5lp.c
tcl/target/psoc5lp.cfg

index d8a7bb9..0122224 100644 (file)
@@ -6179,6 +6179,31 @@ flash bank $_CHIPNAME.eeprom psoc5lp_eeprom 0x40008000 0 0 0 $_TARGETNAME
 @end example
 @end deffn
 
+@deffn {Flash Driver} psoc5lp_nvl
+All members of the PSoC 5LP microcontroller family from Cypress
+include internal Nonvolatile Latches and use ARM Cortex-M3 cores.
+The driver probes for a number of these chips and autoconfigures itself.
+
+@example
+flash bank $_CHIPNAME.nvl psoc5lp_nvl 0 0 0 0 $_TARGETNAME
+@end example
+
+PSoC 5LP chips have multiple NV Latches:
+
+@itemize
+@item Device Configuration NV Latch - 4 bytes
+@item Write Once (WO) NV Latch - 4 bytes
+@end itemize
+
+@b{Note:} This driver only implements the Device Configuration NVL.
+
+The @var{psoc5lp} driver reads the ECC mode from Device Configuration NVL.
+@quotation Attention
+Switching ECC mode via write to Device Configuration NVL will require a reset
+after successful write.
+@end quotation
+@end deffn
+
 @deffn {Flash Driver} psoc6
 Supports PSoC6 (CY8C6xxx) family of Cypress microcontrollers.
 PSoC6 is a dual-core device with CM0+ and CM4 cores. Both cores share
index 2d391d2..6e1703b 100644 (file)
@@ -58,6 +58,7 @@ extern struct flash_driver pic32mx_flash;
 extern struct flash_driver psoc4_flash;
 extern struct flash_driver psoc5lp_flash;
 extern struct flash_driver psoc5lp_eeprom_flash;
+extern struct flash_driver psoc5lp_nvl_flash;
 extern struct flash_driver psoc6_flash;
 extern struct flash_driver sim3x_flash;
 extern struct flash_driver stellaris_flash;
@@ -119,6 +120,7 @@ static struct flash_driver *flash_drivers[] = {
        &psoc4_flash,
        &psoc5lp_flash,
        &psoc5lp_eeprom_flash,
+       &psoc5lp_nvl_flash,
        &psoc6_flash,
        &sim3x_flash,
        &stellaris_flash,
index 1b46020..ae8e3d3 100644 (file)
@@ -29,6 +29,7 @@
 #define PM_ACT_CFG12            0x400043AC
 #define SPC_CPU_DATA            0x40004720
 #define SPC_SR                  0x40004722
+#define PRT1_PC2                0x4000500A
 #define PHUB_CH0_BASIC_CFG      0x40007010
 #define PHUB_CH0_ACTION         0x40007014
 #define PHUB_CH0_BASIC_STATUS   0x40007018
 #define PHUB_TDMEM1_ORIG_TD1    0x4000780C
 #define PANTHER_DEVICE_ID       0x4008001C
 
+/* NVL is not actually mapped to the Cortex-M address space
+ * As we need a base addess different from other banks in the device
+ * we use the address of NVL programming data in Cypress images */
+#define NVL_META_BASE                  0x90000000
+
 #define PM_ACT_CFG12_EN_EE (1 << 4)
 
 #define SPC_KEY1 0xB6
@@ -359,6 +365,31 @@ static int psoc5lp_spc_busy_wait_idle(struct target *target)
        return ERROR_FLASH_OPERATION_FAILED;
 }
 
+static int psoc5lp_spc_load_byte(struct target *target,
+       uint8_t array_id, uint8_t offset, uint8_t value)
+{
+       int retval;
+
+       retval = psoc5lp_spc_write_opcode(target, SPC_LOAD_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 = target_write_u8(target, SPC_CPU_DATA, value);
+       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_load_row(struct target *target,
        uint8_t array_id, const uint8_t *data, unsigned row_size)
 {
@@ -446,6 +477,25 @@ static int psoc5lp_spc_write_row(struct target *target,
        return ERROR_OK;
 }
 
+static int psoc5lp_spc_write_user_nvl(struct target *target,
+       uint8_t array_id)
+{
+       int retval;
+
+       retval = psoc5lp_spc_write_opcode(target, SPC_WRITE_USER_NVL);
+       if (retval != ERROR_OK)
+               return retval;
+       retval = target_write_u8(target, SPC_CPU_DATA, array_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_sector(struct target *target,
        uint8_t array_id, uint8_t row_id)
 {
@@ -545,6 +595,272 @@ static int psoc5lp_spc_get_temp(struct target *target, uint8_t samples,
        return ERROR_OK;
 }
 
+static int psoc5lp_spc_read_volatile_byte(struct target *target,
+       uint8_t array_id, uint8_t offset, uint8_t *data)
+{
+       int retval;
+
+       retval = psoc5lp_spc_write_opcode(target, SPC_READ_VOLATILE_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;
+}
+
+/*
+ * NV Latch
+ */
+
+struct psoc5lp_nvl_flash_bank {
+       bool probed;
+       const struct psoc5lp_device *device;
+};
+
+static int psoc5lp_nvl_read(struct flash_bank *bank,
+       uint8_t *buffer, uint32_t offset, uint32_t count)
+{
+       int retval;
+
+       retval = psoc5lp_spc_enable_clock(bank->target);
+       if (retval != ERROR_OK)
+               return retval;
+
+       while (count > 0) {
+               retval = psoc5lp_spc_read_byte(bank->target,
+                               SPC_ARRAY_NVL_USER, offset, buffer);
+               if (retval != ERROR_OK)
+                       return retval;
+               buffer++;
+               offset++;
+               count--;
+       }
+
+       return ERROR_OK;
+}
+
+static int psoc5lp_nvl_erase(struct flash_bank *bank, int first, int last)
+{
+       LOG_WARNING("There is no erase operation for NV Latches");
+       return ERROR_FLASH_OPER_UNSUPPORTED;
+}
+
+static int psoc5lp_nvl_erase_check(struct flash_bank *bank)
+{
+       int i;
+
+       for (i = 0; i < bank->num_sectors; i++)
+               bank->sectors[i].is_erased = 0;
+
+       return ERROR_OK;
+}
+
+static int psoc5lp_nvl_write(struct flash_bank *bank,
+       const uint8_t *buffer, uint32_t offset, uint32_t byte_count)
+{
+       struct target *target = bank->target;
+       uint8_t *current_data, val;
+       bool write_required = false, pullup_needed = false, ecc_changed = false;
+       uint32_t i;
+       int retval;
+
+       if (offset != 0 || byte_count != bank->size) {
+               LOG_ERROR("NVL can only be written in whole");
+               return ERROR_FLASH_OPER_UNSUPPORTED;
+       }
+
+       current_data = calloc(1, bank->size);
+       if (!current_data)
+               return ERROR_FAIL;
+       retval = psoc5lp_nvl_read(bank, current_data, offset, byte_count);
+       if (retval != ERROR_OK) {
+               free(current_data);
+               return retval;
+       }
+       for (i = offset; i < byte_count; i++) {
+               if (current_data[i] != buffer[i]) {
+                       write_required = true;
+                       break;
+               }
+       }
+       if (((buffer[2] & 0x80) == 0x80) && ((current_data[0] & 0x0C) != 0x08))
+               pullup_needed = true;
+       if (((buffer[3] ^ current_data[3]) & 0x08) == 0x08)
+               ecc_changed = true;
+       free(current_data);
+
+       if (!write_required) {
+               LOG_INFO("Unchanged, skipping NVL write");
+               return ERROR_OK;
+       }
+       if (pullup_needed) {
+               retval = target_read_u8(target, PRT1_PC2, &val);
+               if (retval != ERROR_OK)
+                       return retval;
+               val &= 0xF0;
+               val |= 0x05;
+               retval = target_write_u8(target, PRT1_PC2, val);
+               if (retval != ERROR_OK)
+                       return retval;
+       }
+
+       for (i = offset; i < byte_count; i++) {
+               retval = psoc5lp_spc_load_byte(target,
+                               SPC_ARRAY_NVL_USER, i, buffer[i]);
+               if (retval != ERROR_OK)
+                       return retval;
+
+               retval = psoc5lp_spc_read_volatile_byte(target,
+                               SPC_ARRAY_NVL_USER, i, &val);
+               if (retval != ERROR_OK)
+                       return retval;
+               if (val != buffer[i]) {
+                       LOG_ERROR("Failed to load NVL byte %" PRIu32 ": "
+                               "expected 0x%02" PRIx8 ", read 0x%02" PRIx8,
+                               i, buffer[i], val);
+                       return ERROR_FLASH_OPERATION_FAILED;
+               }
+       }
+
+       retval = psoc5lp_spc_write_user_nvl(target, SPC_ARRAY_NVL_USER);
+       if (retval != ERROR_OK)
+               return retval;
+
+       if (ecc_changed) {
+               retval = target_call_reset_callbacks(target, RESET_INIT);
+               if (retval != ERROR_OK)
+                       LOG_WARNING("Reset failed after enabling or disabling ECC");
+       }
+
+       return ERROR_OK;
+}
+
+static int psoc5lp_nvl_protect_check(struct flash_bank *bank)
+{
+       int i;
+
+       for (i = 0; i < bank->num_sectors; i++)
+               bank->sectors[i].is_protected = -1;
+
+       return ERROR_OK;
+}
+
+static int psoc5lp_nvl_get_info_command(struct flash_bank *bank,
+       char *buf, int buf_size)
+{
+       struct psoc5lp_nvl_flash_bank *psoc_nvl_bank = bank->driver_priv;
+       char part_number[PART_NUMBER_LEN];
+
+       psoc5lp_get_part_number(psoc_nvl_bank->device, part_number);
+
+       snprintf(buf, buf_size, "%s", part_number);
+
+       return ERROR_OK;
+}
+
+static int psoc5lp_nvl_probe(struct flash_bank *bank)
+{
+       struct psoc5lp_nvl_flash_bank *psoc_nvl_bank = bank->driver_priv;
+       int retval;
+
+       if (psoc_nvl_bank->probed)
+               return ERROR_OK;
+
+       if (bank->target->state != TARGET_HALTED) {
+               LOG_ERROR("Target not halted");
+               return ERROR_TARGET_NOT_HALTED;
+       }
+
+       retval = psoc5lp_find_device(bank->target, &psoc_nvl_bank->device);
+       if (retval != ERROR_OK)
+               return retval;
+
+       bank->base = NVL_META_BASE;
+       bank->size = 4;
+       bank->num_sectors = 1;
+       bank->sectors = calloc(bank->num_sectors,
+                              sizeof(struct flash_sector));
+       bank->sectors[0].offset = 0;
+       bank->sectors[0].size = 4;
+       bank->sectors[0].is_erased = -1;
+       bank->sectors[0].is_protected = -1;
+
+       psoc_nvl_bank->probed = true;
+
+       return ERROR_OK;
+}
+
+static int psoc5lp_nvl_auto_probe(struct flash_bank *bank)
+{
+       struct psoc5lp_nvl_flash_bank *psoc_nvl_bank = bank->driver_priv;
+
+       if (psoc_nvl_bank->probed)
+               return ERROR_OK;
+
+       return psoc5lp_nvl_probe(bank);
+}
+
+FLASH_BANK_COMMAND_HANDLER(psoc5lp_nvl_flash_bank_command)
+{
+       struct psoc5lp_nvl_flash_bank *psoc_nvl_bank;
+
+       psoc_nvl_bank = malloc(sizeof(struct psoc5lp_nvl_flash_bank));
+       if (!psoc_nvl_bank)
+               return ERROR_FLASH_OPERATION_FAILED;
+
+       psoc_nvl_bank->probed = false;
+
+       bank->driver_priv = psoc_nvl_bank;
+
+       return ERROR_OK;
+}
+
+static const struct command_registration psoc5lp_nvl_exec_command_handlers[] = {
+       COMMAND_REGISTRATION_DONE
+};
+
+static const struct command_registration psoc5lp_nvl_command_handlers[] = {
+       {
+               .name = "psoc5lp_nvl",
+               .mode = COMMAND_ANY,
+               .help = "PSoC 5LP NV Latch command group",
+               .usage = "",
+               .chain = psoc5lp_nvl_exec_command_handlers,
+       },
+       COMMAND_REGISTRATION_DONE
+};
+
+struct flash_driver psoc5lp_nvl_flash = {
+       .name = "psoc5lp_nvl",
+       .commands = psoc5lp_nvl_command_handlers,
+       .flash_bank_command = psoc5lp_nvl_flash_bank_command,
+       .info = psoc5lp_nvl_get_info_command,
+       .probe = psoc5lp_nvl_probe,
+       .auto_probe = psoc5lp_nvl_auto_probe,
+       .protect_check = psoc5lp_nvl_protect_check,
+       .read = psoc5lp_nvl_read,
+       .erase = psoc5lp_nvl_erase,
+       .erase_check = psoc5lp_nvl_erase_check,
+       .write = psoc5lp_nvl_write,
+};
+
 /*
  * EEPROM
  */
index 2089158..b4e8d05 100644 (file)
@@ -58,6 +58,7 @@ $_TARGETNAME configure -event reset-init {
 set _FLASHNAME $_CHIPNAME.flash
 flash bank $_FLASHNAME psoc5lp 0x00000000 0 0 0 $_TARGETNAME
 flash bank $_CHIPNAME.eeprom psoc5lp_eeprom 0x40008000 0 0 0 $_TARGETNAME
+flash bank $_CHIPNAME.nvl psoc5lp_nvl 0 0 0 0 $_TARGETNAME
 
 if {![using_hla]} {
        cortex_m reset_config sysresetreq