This is an automated email from Gerrit. "Andy <andrewjohnshel...@gmail.com>" just uploaded a new patch set to Gerrit, which you can find at https://review.openocd.org/c/openocd/+/8686
-- gerrit commit 3cbe4f801ede8247ba8bb577c3e0c6199c9ac04c Author: Andrew Shelley <andrewjohnshel...@gmail.com> Date: Tue Dec 31 20:10:04 2024 +0000 flash/nor: flash driver for PIC32CZ family Add new flash driver for internal flash of Microchip PIC32CZ family. The CZ family is unrelated to the original PIC32's, it is an ARM Cortex 7 based MCU at the higher performance end of the Microchip MCUs. Whilst the flash controller is internally not dissimilar to the PIC32MX familiy it is sufficiently different to warrant a new flash driver implementation. Change-Id: I84fb1eb33240e1824f2f53771c854a286a01f11a Signed-off-by: Andrew Shelley <andrewjohnshel...@gmail.com> diff --git a/src/flash/nor/Makefile.am b/src/flash/nor/Makefile.am index afa11e7d40..bb74eacc53 100644 --- a/src/flash/nor/Makefile.am +++ b/src/flash/nor/Makefile.am @@ -51,6 +51,7 @@ NOR_DRIVERS = \ %D%/nrf5.c \ %D%/numicro.c \ %D%/ocl.c \ + %D%/pic32cz.c \ %D%/pic32mx.c \ %D%/psoc4.c \ %D%/psoc5lp.c \ diff --git a/src/flash/nor/driver.h b/src/flash/nor/driver.h index 211661e214..13e9a14b2b 100644 --- a/src/flash/nor/driver.h +++ b/src/flash/nor/driver.h @@ -279,6 +279,7 @@ extern const struct flash_driver nrf51_flash; extern const struct flash_driver nrf5_flash; extern const struct flash_driver numicro_flash; extern const struct flash_driver ocl_flash; +extern const struct flash_driver pic32cz_flash; extern const struct flash_driver pic32mx_flash; extern const struct flash_driver psoc4_flash; extern const struct flash_driver psoc5lp_eeprom_flash; diff --git a/src/flash/nor/drivers.c b/src/flash/nor/drivers.c index dd9995ecba..70bccb7d18 100644 --- a/src/flash/nor/drivers.c +++ b/src/flash/nor/drivers.c @@ -56,6 +56,7 @@ static const struct flash_driver * const flash_drivers[] = { &nrf51_flash, &numicro_flash, &ocl_flash, + &pic32cz_flash, &pic32mx_flash, &psoc4_flash, &psoc5lp_flash, diff --git a/src/flash/nor/pic32cz.c b/src/flash/nor/pic32cz.c new file mode 100644 index 0000000000..cd020ac9a8 --- /dev/null +++ b/src/flash/nor/pic32cz.c @@ -0,0 +1,346 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +/*************************************************************************** + * Copyright (C) 2025 by Andrew Shelley * + * andrewjohnshel...@gmail.com * + ***************************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <jtag/jtag.h> +#include "imp.h" + +/* PIC32CZ DSU Registers */ +#define PIC32CZ_DSU ((uint32_t)0x44000000) +#define PIC32CZ_DSU_DID (PIC32CZ_DSU + 0x0120) + +/* PIC32CZ NVM Registers */ +#define PIC32CZ_NVM ((uint32_t)0x44002000) +#define PIC32CZ_NVM_CTRLA (PIC32CZ_NVM + 0x0000) +#define PIC32CZ_NVM_MUTEX (PIC32CZ_NVM + 0x0008) +#define PIC32CZ_NVM_STATUS (PIC32CZ_NVM + 0x0018) +#define PIC32CZ_NVM_KEY (PIC32CZ_NVM + 0x001C) +#define PIC32CZ_NVM_ADDR (PIC32CZ_NVM + 0x0020) +#define PIC32CZ_NVM_DATA0 (PIC32CZ_NVM + 0x0028 + 0x00) +#define PIC32CZ_NVM_DATA1 (PIC32CZ_NVM + 0x0028 + 0x04) + +/** + * PIC32CZ NVMOP Codes + * Default enabling the pre-program bit as recommended by Microchip, it might be prudent + * to make this a user option in the future. + */ +#define PIC32CZ_NVMOP_ERASE_PFM (0x80 | 0x07) +#define PIC32CZ_NVMOP_ERASE_PAGE (0x80 | 0x04) +#define PIC32CZ_NVMOP_WRITE_ROW (0x80 | 0x03) +#define PIC32CZ_NVMOP_WRITE_QUAD (0x80 | 0x02) +#define PIC32CZ_NVMOP_WRITE_SINGLE (0x80 | 0x01) + +/** + * PIC32CZ Timeouts + * The datasheet is not the best on flash timings but from what I can deduce the erase + * operations are 20ms and the write operations in the order of 1ms max. So the timeouts + * are going to be as follows to allow some slop. + */ +#define PIC32CZ_TIMEOUT_ERASE 100 +#define PIC32CZ_TIMEOUT_WRITE 10 + +/* All products share the same base addresses for the boot-flash, not to be + confused with the boot-rom which is an immutable block of built in bootloader. It is + 128KBytes on all products split into two 64K banks, buts lets not run before we can + walk, one bank for now. */ +#define FLASH_BANK_BFM_BASE 0x08000000 +#define FLASH_BANK_BFM_SIZE (2 * 64 * 1024) +#define FLASH_BANK_BFM_PAGE (4 * 1024) + +/* All products share the same base address to the programme flash, but the sizes + vary between 1 and 8MBytes and will need to be deduced from the device ID. */ +#define FLASH_BANK_PFM_BASE 0x0C000000 +#define FLASH_BANK_PFM_PAGE (4 * 1024) + +/* Canned list of all the PIC32CZ chips with the bits of config we're going to need */ +struct pic32cz_chip_info { + uint8_t devsel; + uint8_t flashsize; + const char *name; +}; +static const struct pic32cz_chip_info pic32cz_known_chips[] = { + {0x00, 8, "PIC32CZ8110CA80208"}, + {0x03, 8, "PIC32CZ8110CA80176"}, + {0x06, 8, "PIC32CZ8110CA80144"}, + {0x09, 8, "PIC32CZ8110CA80100"}, + {0x0C, 8, "PIC32CZ8110CA90208"}, + {0x0F, 8, "PIC32CZ8110CA90176"}, + {0x12, 8, "PIC32CZ8110CA90144"}, + {0x15, 8, "PIC32CZ8110CA90100"}}; + +/** + * Private Fetch the type of chip this is from the device ID. + */ +static const struct pic32cz_chip_info *_pic32cz_get_chip_info(struct target *target) +{ + uint32_t id = 0; + int res = target_read_u32(target, PIC32CZ_DSU_DID, &id); + if (res == ERROR_OK) { + /* Product ID and Manufacturer ID are the same for all PIC32CZ parts*/ + uint8_t product = (id >> 20) & 0xFF; + uint16_t manufacturer = (id >> 1) & 0x7FF; + if (product == 0x92 && manufacturer == 0x29) { + uint8_t devsel = (id >> 12) & 0xFF; + for (unsigned long i = 0; i < ARRAY_SIZE(pic32cz_known_chips); i++) { + if (pic32cz_known_chips[i].devsel == devsel) + return &pic32cz_known_chips[i]; + } + } + } + LOG_ERROR("Not a Microchip PIC32CZ device"); + return NULL; +} + +/** + * Private PIC32CZ NVM Sequence + * + * This is the startup sequence defined for write and erase operations on the PIC32CZ + * internal flash memories. It is defined in the PIC32CZ Family Reference Manual: + * + * 31.2.13 FCW Sequencer User Model + * + */ +static int _pic32cz_nvm_sequence(struct flash_bank *bank, uint32_t addr, uint8_t *data, uint32_t operation) +{ + uint32_t tmp = 0; + uint32_t timeout = (operation == PIC32CZ_NVMOP_ERASE_PFM || operation == PIC32CZ_NVMOP_ERASE_PAGE) + ? PIC32CZ_TIMEOUT_ERASE : PIC32CZ_TIMEOUT_WRITE; + + // 1. Lock the hardware write mutex by setting the LOCK bit to ‘1’ and the OWNER field to ‘01’ + // simultaneously to the MUTEX register. Ensure that these bits are set correctly before proceeding, + // if the OWNER field is not ‘01’ and the LOCK bit is ‘1’ another system has ownership of the + // hardware write mutex and this operation must be attempted again when that system releases + // the mutex. + target_write_u32(bank->target, PIC32CZ_NVM_MUTEX, 0x3); + target_read_u32(bank->target, PIC32CZ_NVM_MUTEX, &tmp); + if (tmp != 0x3) { + LOG_ERROR("Cannot lock hardware write mutex"); + return ERROR_FAIL; + } + + // 2.Setup ADDR and if programming either DATAx (Single/Quad) or SRCADDR (Row Write). + target_write_u32(bank->target, PIC32CZ_NVM_ADDR, addr); + if (operation == PIC32CZ_NVMOP_WRITE_QUAD) { + for (int i = 0; i < 32; i += 4) + target_write_u32(bank->target, + PIC32CZ_NVM_DATA0 + i, + data[i] | data[i + 1] << 8 | data[i + 2] << 16 | data[i + 3] << 24); + } + + // 3. Write WRKEY to KEY.KEY. + uint32_t keycode = 0x91C32C00 | 0x01; + target_write_u32(bank->target, PIC32CZ_NVM_KEY, keycode); + + // 4. Write CTRLA.NVMOP to <Desired NVMOP>. + target_write_u32(bank->target, PIC32CZ_NVM_CTRLA, operation); + + // 5. The FCW generates an interrupt when it clears STATUS.BUSY and sets INTFLAG.DONE. + tmp = 0; + while (timeout--) { + target_read_u32(bank->target, PIC32CZ_NVM_STATUS, &tmp); + if ((tmp & 0x1) == 0) + return ERROR_OK; + alive_sleep(1); + } + + LOG_ERROR("Timed out waiting for NVM operation to complete"); + return ERROR_FAIL; +} + +/** + * Request Human Readable Information about the PIC32CZ flash banks. + * + * Not returning anything meaningful as I can't see what I can say here that + * would not be provided elsewhere, for examples from "flash banks" command. + */ +static int pic32cz_info(struct flash_bank *bank, struct command_invocation *cmd) +{ + (void)bank; + (void)cmd; + return ERROR_OK; +} + +/** + * Driver specific extensions to the flash bank command + * + * Currently do not have any specific extensions. + */ +FLASH_BANK_COMMAND_HANDLER(pic32cz_flash_bank_command) +{ + (void)bank; + (void)cmd; + return ERROR_OK; +} + +/** + * Probe the PIC32CZ device to determine the flash bank configuration. + * + * Invoked by the probe script this probes the actual device to determine + * additional properties of the indicated flash bank as these will vary for + * different PIC32CZ devices. + * + */ +static int pic32cz_probe(struct flash_bank *bank) +{ + // Since all banks have a non zero size we'll use this as a test for + // whether we have already probed the device for this specific bank. + if (bank->size != 0) + return ERROR_OK; + + // We need to know exactly which device this is so we can size the banks correctly. + const struct pic32cz_chip_info *details = _pic32cz_get_chip_info(bank->target); + if (!details) { + LOG_WARNING("Cannot identify target as a PIC32CZ device"); + return ERROR_FLASH_OPERATION_FAILED; + } + + // Now we know the device we can size the banks, there are two banks, the BFM and the PFM. + switch (bank->base) { + case FLASH_BANK_BFM_BASE: + bank->size = FLASH_BANK_BFM_SIZE; + bank->num_sectors = bank->size / FLASH_BANK_BFM_PAGE; + break; + + case FLASH_BANK_PFM_BASE: + bank->size = details->flashsize * 1024 * 1024; + bank->num_sectors = bank->size / FLASH_BANK_PFM_PAGE; + break; + + default: + LOG_ERROR("Bank not found at 0x%08x ", (unsigned int)bank->base); + return ERROR_FAIL; + } + + // We are expected to allocate tracking structures for the sectors in the bank + // and store these in the bank structure. + bank->sectors = malloc(sizeof(struct flash_sector) * bank->num_sectors); + if (bank->sectors) { + for (int i = 0; i < (int)bank->num_sectors; i++) { + bank->sectors[i].offset = i * (bank->size / bank->num_sectors); + bank->sectors[i].size = (bank->size / bank->num_sectors); + bank->sectors[i].is_erased = -1; + bank->sectors[i].is_protected = 1; + } + } + + return ERROR_OK; +} + +/** + * Erase the specified range of sectors in the flash bank. + * + * Optimisations catch the cases where the defined first to last range voers an + * entire bank but most of the time this is dealing in 4Kbyte flash pages. + */ +static int pic32cz_erase(struct flash_bank *bank, unsigned int first, unsigned int last) +{ + if (bank->target->state != TARGET_HALTED) { + LOG_ERROR("Target not halted"); + return ERROR_TARGET_NOT_HALTED; + } + + // Trap the special cases where the entire block is erased (not applicable to BFM) + if (first == 0 && (last == (bank->num_sectors - 1)) && bank->base == FLASH_BANK_PFM_BASE) { + LOG_DEBUG("Erasing bank at 0x%08x", (uint32_t)(bank->base)); + _pic32cz_nvm_sequence(bank, bank->base, 0, PIC32CZ_NVMOP_ERASE_PFM); + return ERROR_OK; + } + + // Erase pages from first to last exclusive + for (unsigned int i = first; i <= last; i++) { + LOG_DEBUG("Erasing page %d at 0x%08x", i, (uint32_t)(bank->base + bank->sectors[i].offset)); + _pic32cz_nvm_sequence(bank, bank->base + bank->sectors[i].offset, 0, PIC32CZ_NVMOP_ERASE_PAGE); + } + + return ERROR_OK; +} + +/** + * Write contigous block of data to the flash bank + * + * Supports any alignment down to individual bytes using read/modify/write + * where demanded by the alignment. Flash write granularities in PIC32CZ are: + * + * "Single" (1 x Double Word) = 64bits of data (Data0 + Data1) + * "Quad" (4 x Double Words) = 256bits of data (Data0 thru Data7) + * "Row" (16 x Double Words) = 1024bits of data (SRAM Transfer) + * + * Currently this implementation uses the "Quad" option only and works by + * processing the range of quads completely spanning the supplied range. Where + * the quad strays outside of the supplied data the quad is populated with + * the current content of the memory address read from the target. + * + * Performance might be improved by implementing the ROW mechanism but this + * will require an SRAM area on the target to be populated with the write + * data. Whilst this potentially quarters the number of flash operations + * it might still be held back by the SWD/JTAG transfers. + * + */ +static int pic32cz_write(struct flash_bank *bank, const uint8_t *buffer, uint32_t offset, uint32_t count) +{ + if (bank->target->state != TARGET_HALTED) { + LOG_ERROR("Target not halted"); + return ERROR_TARGET_NOT_HALTED; + } + + LOG_DEBUG("writing to flash at address " TARGET_ADDR_FMT " at offset 0x%8.8" PRIx32 + " count: 0x%8.8" PRIx32 "", + bank->base, offset, count); + + // The inclusive range of addresses for which the caller is providing data + uint32_t addr_first = bank->base + offset; + uint32_t addr_last = bank->base + offset + count - 1; + + // Iterating over the range of quads spanning the callers data + uint8_t quad[32]; + uint32_t addr_quad = addr_first & ~(ARRAY_SIZE(quad) - 1); + while (addr_quad <= addr_last) { + for (unsigned long i = 0; i < ARRAY_SIZE(quad); ++i) { + uint32_t addr = addr_quad + i; + if (addr >= addr_first && addr <= addr_last) + quad[i] = buffer[addr - addr_first]; + else + target_read_u8(bank->target, addr, &quad[i]); + } + _pic32cz_nvm_sequence(bank, addr_quad, quad, PIC32CZ_NVMOP_WRITE_QUAD); + addr_quad += ARRAY_SIZE(quad); + } + + return ERROR_OK; +} + +static const struct command_registration pic32cz_exec_command_handlers[] = { + COMMAND_REGISTRATION_DONE}; + +static const struct command_registration pic32cz_command_handlers[] = { + { + .name = "pic32cz", + .mode = COMMAND_ANY, + .help = "pic32cz flash command group", + .usage = "", + .chain = pic32cz_exec_command_handlers, + }, + COMMAND_REGISTRATION_DONE}; + +const struct flash_driver pic32cz_flash = { + .name = "pic32cz", + .commands = pic32cz_command_handlers, + .flash_bank_command = pic32cz_flash_bank_command, + .erase = pic32cz_erase, + .protect = NULL, + .write = pic32cz_write, + .read = default_flash_read, + .probe = pic32cz_probe, + .auto_probe = pic32cz_probe, + .erase_check = default_flash_blank_check, + .protect_check = NULL, + .info = pic32cz_info, + .free_driver_priv = default_flash_free_driver_priv, +}; diff --git a/tcl/target/pic32cz.cfg b/tcl/target/pic32cz.cfg new file mode 100644 index 0000000000..93802ce230 --- /dev/null +++ b/tcl/target/pic32cz.cfg @@ -0,0 +1,30 @@ +# SPDX-License-Identifier: GPL-2.0-or-later + +if { [info exists CHIPNAME] } { + set _CHIPNAME $CHIPNAME +} else { + set _CHIPNAME pic32cz +} + +if { [info exists ENDIAN] } { + set _ENDIAN $ENDIAN +} else { + set _ENDIAN little +} + +if { [info exists CPUTAPID] } { + set _CPUTAPID $CPUTAPID +} else { + set _CPUTAPID 0x2ba01477 +} + +transport select swd + +swd newdap $_CHIPNAME cpu -enable +dap create $_CHIPNAME.dap -chain-position $_CHIPNAME.cpu +target create $_CHIPNAME.cpu cortex_m -dap $_CHIPNAME.dap + +flash bank $_CHIPNAME.bfm pic32cz 0x08000000 0 0 0 $_CHIPNAME.cpu +flash bank $_CHIPNAME.pfm pic32cz 0x0C000000 0 0 0 $_CHIPNAME.cpu + +reset_config srst_only srst_nogate connect_assert_srst --