diff options
author | Jeff Ciesielski <jeffciesielski@gmail.com> | 2015-01-21 18:57:59 -0800 |
---|---|---|
committer | Freddie Chopin <freddie.chopin@gmail.com> | 2015-11-11 12:08:35 +0000 |
commit | 33b048d456e24b9944ff2261cf26b05d630bdd41 (patch) | |
tree | 595d052232f9a7dc32d8fa0473b06961684931cc /src | |
parent | 42c24acebd23d758c543c9bf2c2b97bdbd083c35 (diff) | |
download | riscv-openocd-33b048d456e24b9944ff2261cf26b05d630bdd41.zip riscv-openocd-33b048d456e24b9944ff2261cf26b05d630bdd41.tar.gz riscv-openocd-33b048d456e24b9944ff2261cf26b05d630bdd41.tar.bz2 |
flash: New driver for XMC4xxx microcontroller family
This is a complete flash driver for the Infineon XMC4xxx family of
microcontrollers, based on the TMS570 driver by Andrey Yurovsky.
The driver attempts to discover the particular variant of MCU via a
combination of the SCU register (to determine if this is indeed an
XMC4xxx part) and the FLASH0_ID register (to determine the variant).
If this fails, the driver will not load.
The driver has been added to the README and documentation.
Tests:
* Hardware: XMC4500 (XMC4500_relax), XMC4200 (XMC4200 enterprise)
* SWD + JTAG
* Binary: 144k, 1M
Note:
* Flash protect only partly tested. These parts only allow the flash
protection registers (UCB) to be written 4 times total, and my devkits
have run out of uses (more on the way)
Future Work:
* User 1/2(permalock) locking support via custom command
* In-memory flash loader bootstrap (flashing is rather slow...)
Change-Id: I1d3345d5255d8de8dc4175cf987eb4a037a8cf7f
Signed-off-by: Jeff Ciesielski <jeffciesielski@gmail.com>
Signed-off-by: Andreas Färber <afaerber@suse.de>
Reviewed-on: http://openocd.zylin.com/2488
Tested-by: jenkins
Reviewed-by: Paul Fertser <fercerpav@gmail.com>
Diffstat (limited to 'src')
-rw-r--r-- | src/flash/nor/Makefile.am | 3 | ||||
-rw-r--r-- | src/flash/nor/drivers.c | 2 | ||||
-rw-r--r-- | src/flash/nor/xmc4xxx.c | 1414 |
3 files changed, 1418 insertions, 1 deletions
diff --git a/src/flash/nor/Makefile.am b/src/flash/nor/Makefile.am index b065bb0..eabf6f9 100644 --- a/src/flash/nor/Makefile.am +++ b/src/flash/nor/Makefile.am @@ -49,7 +49,8 @@ NOR_DRIVERS = \ nrf51.c \ mrvlqspi.c \ psoc4.c \ - sim3x.c + sim3x.c \ + xmc4xxx.c noinst_HEADERS = \ core.h \ diff --git a/src/flash/nor/drivers.c b/src/flash/nor/drivers.c index 4723cff..24bd306 100644 --- a/src/flash/nor/drivers.c +++ b/src/flash/nor/drivers.c @@ -62,6 +62,7 @@ extern struct flash_driver mrvlqspi_flash; extern struct flash_driver psoc4_flash; extern struct flash_driver sim3x_flash; extern struct flash_driver jtagspi_flash; +extern struct flash_driver xmc4xxx_flash; /** * The list of built-in flash drivers. @@ -108,6 +109,7 @@ static struct flash_driver *flash_drivers[] = { &psoc4_flash, &sim3x_flash, &jtagspi_flash, + &xmc4xxx_flash, NULL, }; diff --git a/src/flash/nor/xmc4xxx.c b/src/flash/nor/xmc4xxx.c new file mode 100644 index 0000000..df288ff --- /dev/null +++ b/src/flash/nor/xmc4xxx.c @@ -0,0 +1,1414 @@ +/************************************************************************** +* Copyright (C) 2015 Jeff Ciesielski <jeffciesielski@gmail.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. * +* * +***************************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "imp.h" +#include <helper/binarybuffer.h> +#include <target/algorithm.h> +#include <target/armv7m.h> + +/* Maximum number of sectors */ +#define MAX_XMC_SECTORS 12 + +/* System control unit registers */ +#define SCU_REG_BASE 0x50004000 + +#define SCU_ID_CHIP 0x04 + +/* Base of the non-cached flash memory */ +#define PFLASH_BASE 0x0C000000 + +/* User configuration block offsets */ +#define UCB0_BASE 0x00000000 +#define UCB1_BASE 0x00000400 +#define UCB2_BASE 0x00000800 + +/* Flash register base */ +#define FLASH_REG_BASE 0x58000000 + +/* PMU ID Registers */ +#define FLASH_REG_PMU_ID (FLASH_REG_BASE | 0x0508) + +/* PMU Fields */ +#define PMU_MOD_REV_MASK 0xFF +#define PMU_MOD_TYPE_MASK 0xFF00 +#define PMU_MOD_NO_MASK 0xFFFF0000 + +/* Prefetch Config */ +#define FLASH_REG_PREF_PCON (FLASH_REG_BASE | 0x4000) + +/* Prefetch Fields */ +#define PCON_IBYP (1 << 0) +#define PCON_IINV (1 << 1) + +/* Flash ID Register */ +#define FLASH_REG_FLASH0_ID (FLASH_REG_BASE | 0x2008) + +/* Flash Status Register */ +#define FLASH_REG_FLASH0_FSR (FLASH_REG_BASE | 0x2010) + +#define FSR_PBUSY (0) +#define FSR_FABUSY (1) +#define FSR_PROG (4) +#define FSR_ERASE (5) +#define FSR_PFPAGE (6) +#define FSR_PFOPER (8) +#define FSR_SQER (10) +#define FSR_PROER (11) +#define FSR_PFSBER (12) +#define FSR_PFDBER (14) +#define FSR_PROIN (16) +#define FSR_RPROIN (18) +#define FSR_RPRODIS (19) +#define FSR_WPROIN0 (21) +#define FSR_WPROIN1 (22) +#define FSR_WPROIN2 (23) +#define FSR_WPRODIS0 (25) +#define FSR_WPRODIS1 (26) +#define FSR_SLM (28) +#define FSR_VER (31) + +#define FSR_PBUSY_MASK (0x01 << FSR_PBUSY) +#define FSR_FABUSY_MASK (0x01 << FSR_FABUSY) +#define FSR_PROG_MASK (0x01 << FSR_PROG) +#define FSR_ERASE_MASK (0x01 << FSR_ERASE) +#define FSR_PFPAGE_MASK (0x01 << FSR_PFPAGE) +#define FSR_PFOPER_MASK (0x01 << FSR_PFOPER) +#define FSR_SQER_MASK (0x01 << FSR_SQER) +#define FSR_PROER_MASK (0x01 << FSR_PROER) +#define FSR_PFSBER_MASK (0x01 << FSR_PFSBER) +#define FSR_PFDBER_MASK (0x01 << FSR_PFDBER) +#define FSR_PROIN_MASK (0x01 << FSR_PROIN) +#define FSR_RPROIN_MASK (0x01 << FSR_RPROIN) +#define FSR_RPRODIS_MASK (0x01 << FSR_RPRODIS) +#define FSR_WPROIN0_MASK (0x01 << FSR_WPROIN0) +#define FSR_WPROIN1_MASK (0x01 << FSR_WPROIN1) +#define FSR_WPROIN2_MASK (0x01 << FSR_WPROIN2) +#define FSR_WPRODIS0_MASK (0x01 << FSR_WPRODIS0) +#define FSR_WPRODIS1_MASK (0x01 << FSR_WPRODIS1) +#define FSR_SLM_MASK (0x01 << FSR_SLM) +#define FSR_VER_MASK (0x01 << FSR_VER) + +/* Flash Config Register */ +#define FLASH_REG_FLASH0_FCON (FLASH_REG_BASE | 0x2014) + +#define FCON_WSPFLASH (0) +#define FCON_WSECPF (4) +#define FCON_IDLE (13) +#define FCON_ESLDIS (14) +#define FCON_SLEEP (15) +#define FCON_RPA (16) +#define FCON_DCF (17) +#define FCON_DDF (18) +#define FCON_VOPERM (24) +#define FCON_SQERM (25) +#define FCON_PROERM (26) +#define FCON_PFSBERM (27) +#define FCON_PFDBERM (29) +#define FCON_EOBM (31) + +#define FCON_WSPFLASH_MASK (0x0f << FCON_WSPFLASH) +#define FCON_WSECPF_MASK (0x01 << FCON_WSECPF) +#define FCON_IDLE_MASK (0x01 << FCON_IDLE) +#define FCON_ESLDIS_MASK (0x01 << FCON_ESLDIS) +#define FCON_SLEEP_MASK (0x01 << FCON_SLEEP) +#define FCON_RPA_MASK (0x01 << FCON_RPA) +#define FCON_DCF_MASK (0x01 << FCON_DCF) +#define FCON_DDF_MASK (0x01 << FCON_DDF) +#define FCON_VOPERM_MASK (0x01 << FCON_VOPERM) +#define FCON_SQERM_MASK (0x01 << FCON_SQERM) +#define FCON_PROERM_MASK (0x01 << FCON_PROERM) +#define FCON_PFSBERM_MASK (0x01 << FCON_PFSBERM) +#define FCON_PFDBERM_MASK (0x01 << FCON_PFDBERM) +#define FCON_EOBM_MASK (0x01 << FCON_EOBM) + +/* Flash Margin Control Register */ +#define FLASH_REG_FLASH0_MARP (FLASH_REG_BASE | 0x2018) + +#define MARP_MARGIN (0) +#define MARP_TRAPDIS (15) + +#define MARP_MARGIN_MASK (0x0f << MARP_MARGIN) +#define MARP_TRAPDIS_MASK (0x01 << MARP_TRAPDIS) + +/* Flash Protection Registers */ +#define FLASH_REG_FLASH0_PROCON0 (FLASH_REG_BASE | 0x2020) +#define FLASH_REG_FLASH0_PROCON1 (FLASH_REG_BASE | 0x2024) +#define FLASH_REG_FLASH0_PROCON2 (FLASH_REG_BASE | 0x2028) + +#define PROCON_S0L (0) +#define PROCON_S1L (1) +#define PROCON_S2L (2) +#define PROCON_S3L (3) +#define PROCON_S4L (4) +#define PROCON_S5L (5) +#define PROCON_S6L (6) +#define PROCON_S7L (7) +#define PROCON_S8L (8) +#define PROCON_S9L (9) +#define PROCON_S10_S11L (10) +#define PROCON_RPRO (15) + +#define PROCON_S0L_MASK (0x01 << PROCON_S0L) +#define PROCON_S1L_MASK (0x01 << PROCON_S1L) +#define PROCON_S2L_MASK (0x01 << PROCON_S2L) +#define PROCON_S3L_MASK (0x01 << PROCON_S3L) +#define PROCON_S4L_MASK (0x01 << PROCON_S4L) +#define PROCON_S5L_MASK (0x01 << PROCON_S5L) +#define PROCON_S6L_MASK (0x01 << PROCON_S6L) +#define PROCON_S7L_MASK (0x01 << PROCON_S7L) +#define PROCON_S8L_MASK (0x01 << PROCON_S8L) +#define PROCON_S9L_MASK (0x01 << PROCON_S9L) +#define PROCON_S10_S11L_MASK (0x01 << PROCON_S10_S11L) +#define PROCON_RPRO_MASK (0x01 << PROCON_RPRO) + +#define FLASH_PROTECT_CONFIRMATION_CODE 0x8AFE15C3 + +/* Flash controller configuration values */ +#define FLASH_ID_XMC4500 0xA2 +#define FLASH_ID_XMC4100_4200 0x9C +#define FLASH_ID_XMC4400 0x9F + +/* Timeouts */ +#define FLASH_OP_TIMEOUT 5000 + +/* Flash commands (write/erase/protect) are performed using special + * command sequences that are written to magic addresses in the flash controller */ +/* Command sequence addresses. See reference manual, section 8: Flash Command Sequences */ +#define FLASH_CMD_ERASE_1 0x0C005554 +#define FLASH_CMD_ERASE_2 0x0C00AAA8 +#define FLASH_CMD_ERASE_3 FLASH_CMD_ERASE_1 +#define FLASH_CMD_ERASE_4 FLASH_CMD_ERASE_1 +#define FLASH_CMD_ERASE_5 FLASH_CMD_ERASE_2 +/* ERASE_6 is the sector base address */ + +#define FLASH_CMD_CLEAR_STATUS FLASH_CMD_ERASE_1 + +#define FLASH_CMD_ENTER_PAGEMODE FLASH_CMD_ERASE_1 + +#define FLASH_CMD_LOAD_PAGE_1 0x0C0055F0 +#define FLASH_CMD_LOAD_PAGE_2 0x0C0055F4 + +#define FLASH_CMD_WRITE_PAGE_1 FLASH_CMD_ERASE_1 +#define FLASH_CMD_WRITE_PAGE_2 FLASH_CMD_ERASE_2 +#define FLASH_CMD_WRITE_PAGE_3 FLASH_CMD_ERASE_1 +/* WRITE_PAGE_4 is the page base address */ + +#define FLASH_CMD_TEMP_UNPROT_1 FLASH_CMD_ERASE_1 +#define FLASH_CMD_TEMP_UNPROT_2 FLASH_CMD_ERASE_2 +#define FLASH_CMD_TEMP_UNPROT_3 0x0C00553C +#define FLASH_CMD_TEMP_UNPROT_4 FLASH_CMD_ERASE_2 +#define FLASH_CMD_TEMP_UNPROT_5 FLASH_CMD_ERASE_2 +#define FLASH_CMD_TEMP_UNPROT_6 0x0C005558 + +struct xmc4xxx_flash_bank { + bool probed; + + /* We need the flash controller ID to choose the sector layout */ + uint32_t fcon_id; + + /* Passwords used for protection operations */ + uint32_t pw1; + uint32_t pw2; + bool pw_set; + + /* Protection flags */ + bool read_protected; + + bool write_prot_otp[MAX_XMC_SECTORS]; +}; + +struct xmc4xxx_command_seq { + uint32_t address; + uint32_t magic; +}; + +/* Sector capacities. See section 8 of xmc4x00_rm */ +static unsigned int sector_capacity_8[] = { + 16, 16, 16, 16, 16, 16, 16, 128 +}; + +static unsigned int sector_capacity_9[] = { + 16, 16, 16, 16, 16, 16, 16, 128, 256 +}; + +static unsigned int sector_capacity_12[] = { + 16, 16, 16, 16, 16, 16, 16, 16, 128, 256, 256, 256 +}; + +static int xmc4xxx_write_command_sequence(struct flash_bank *bank, + struct xmc4xxx_command_seq *seq, + int seq_len) +{ + int res = ERROR_OK; + + for (int i = 0; i < seq_len; i++) { + res = target_write_u32(bank->target, seq[i].address, + seq[i].magic); + if (res != ERROR_OK) + return res; + } + + return ERROR_OK; +} + +static int xmc4xxx_load_bank_layout(struct flash_bank *bank) +{ + unsigned int *capacity = NULL; + + /* At this point, we know which flash controller ID we're + * talking to and simply need to fill out the bank structure accordingly */ + LOG_DEBUG("%d sectors", bank->num_sectors); + + switch (bank->num_sectors) { + case 8: + capacity = sector_capacity_8; + break; + case 9: + capacity = sector_capacity_9; + break; + case 12: + capacity = sector_capacity_12; + break; + default: + LOG_ERROR("Unexpected number of sectors, %d\n", + bank->num_sectors); + return ERROR_FAIL; + } + + /* This looks like a bank that we understand, now we know the + * corresponding sector capacities and we can add those up into the + * bank size. */ + uint32_t total_offset = 0; + bank->sectors = calloc(bank->num_sectors, + sizeof(struct flash_sector)); + for (int i = 0; i < bank->num_sectors; i++) { + bank->sectors[i].size = capacity[i] * 1024; + bank->sectors[i].offset = total_offset; + bank->sectors[i].is_erased = -1; + bank->sectors[i].is_protected = -1; + + bank->size += bank->sectors[i].size; + LOG_DEBUG("\t%d: %uk", i, capacity[i]); + total_offset += bank->sectors[i].size; + } + + /* This part doesn't follow the typical standard of 0xff + * being the default padding value.*/ + bank->default_padded_value = 0x00; + + return ERROR_OK; +} + +static int xmc4xxx_probe(struct flash_bank *bank) +{ + int res; + uint32_t devid, config; + struct xmc4xxx_flash_bank *fb = bank->driver_priv; + uint8_t flash_id; + + if (fb->probed) + return ERROR_OK; + + /* It's not possible for the DAP to access the OTP locations needed for + * probing the part info and Flash geometry so we require that the target + * be halted before proceeding. */ + if (bank->target->state != TARGET_HALTED) { + LOG_WARNING("Cannot communicate... target not halted."); + return ERROR_TARGET_NOT_HALTED; + } + + /* The SCU registers contain the ID of the chip */ + res = target_read_u32(bank->target, SCU_REG_BASE + SCU_ID_CHIP, &devid); + if (res != ERROR_OK) { + LOG_ERROR("Cannot read device identification register."); + return res; + } + + /* Make sure this is a XMC4000 family device */ + if ((devid & 0xF0000) != 0x40000) { + LOG_ERROR("Platform ID doesn't match XMC4xxx: 0x%08" PRIx32, devid); + return ERROR_FAIL; + } + + LOG_DEBUG("Found XMC4xxx with devid: 0x%08" PRIx32, devid); + + /* Now sanity-check the Flash controller itself. */ + res = target_read_u32(bank->target, FLASH_REG_FLASH0_ID, + &config); + if (res != ERROR_OK) { + LOG_ERROR("Cannot read Flash bank configuration."); + return res; + } + flash_id = (config & 0xff0000) >> 16; + + /* The Flash configuration register is our only means of + * determining the sector layout. We need to make sure that + * we understand the type of controller we're dealing with */ + switch (flash_id) { + case FLASH_ID_XMC4100_4200: + bank->num_sectors = 8; + LOG_DEBUG("XMC4xxx: XMC4100/4200 detected."); + break; + case FLASH_ID_XMC4400: + bank->num_sectors = 9; + LOG_DEBUG("XMC4xxx: XMC4400 detected."); + break; + case FLASH_ID_XMC4500: + bank->num_sectors = 12; + LOG_DEBUG("XMC4xxx: XMC4500 detected."); + break; + default: + LOG_ERROR("XMC4xxx: Unexpected flash ID. got %02" PRIx8, + flash_id); + return ERROR_FAIL; + } + + /* Retrieve information about the particular bank we're probing and fill in + * the bank structure accordingly. */ + res = xmc4xxx_load_bank_layout(bank); + if (res == ERROR_OK) { + /* We're done */ + fb->probed = true; + } else { + LOG_ERROR("Unable to load bank information."); + return ERROR_FAIL; + } + + return ERROR_OK; +} + +static int xmc4xxx_get_sector_start_addr(struct flash_bank *bank, + int sector, uint32_t *ret_addr) +{ + /* Make sure we understand this sector */ + if (sector > bank->num_sectors) + return ERROR_FAIL; + + *ret_addr = bank->base + bank->sectors[sector].offset; + + return ERROR_OK; + +} + +static int xmc4xxx_clear_flash_status(struct flash_bank *bank) +{ + int res; + /* TODO: Do we need to check for sequence error? */ + LOG_INFO("Clearing flash status"); + res = target_write_u32(bank->target, FLASH_CMD_CLEAR_STATUS, + 0xF5); + if (res != ERROR_OK) { + LOG_ERROR("Unable to write erase command sequence"); + return res; + } + + return ERROR_OK; +} + +static int xmc4xxx_get_flash_status(struct flash_bank *bank, uint32_t *status) +{ + int res; + + res = target_read_u32(bank->target, FLASH_REG_FLASH0_FSR, status); + + if (res != ERROR_OK) + LOG_ERROR("Cannot read flash status register."); + + return res; +} + +static int xmc4xxx_wait_status_busy(struct flash_bank *bank, int timeout) +{ + int res; + uint32_t status; + + res = xmc4xxx_get_flash_status(bank, &status); + if (res != ERROR_OK) + return res; + + /* While the flash controller is busy, wait */ + while (status & FSR_PBUSY_MASK) { + res = xmc4xxx_get_flash_status(bank, &status); + if (res != ERROR_OK) + return res; + + if (timeout-- <= 0) { + LOG_ERROR("Timed out waiting for flash"); + return ERROR_FAIL; + } + alive_sleep(1); + keep_alive(); + } + + if (status & FSR_PROER_MASK) { + LOG_ERROR("XMC4xxx flash protected"); + res = ERROR_FAIL; + } + + return res; +} + +static int xmc4xxx_erase_sector(struct flash_bank *bank, uint32_t address, + bool user_config) +{ + int res; + uint32_t status; + + /* See reference manual table 8.4: Command Sequences for Flash Control */ + struct xmc4xxx_command_seq erase_cmd_seq[6] = { + {FLASH_CMD_ERASE_1, 0xAA}, + {FLASH_CMD_ERASE_2, 0x55}, + {FLASH_CMD_ERASE_3, 0x80}, + {FLASH_CMD_ERASE_4, 0xAA}, + {FLASH_CMD_ERASE_5, 0x55}, + {0xFF, 0xFF} /* Needs filled in */ + }; + + /* We need to fill in the base address of the sector we'll be + * erasing, as well as the magic code that determines whether + * this is a standard flash sector or a user configuration block */ + + erase_cmd_seq[5].address = address; + if (user_config) { + /* Removing flash protection requires the addition of + * the base address */ + erase_cmd_seq[5].address += bank->base; + erase_cmd_seq[5].magic = 0xC0; + } else { + erase_cmd_seq[5].magic = 0x30; + } + + res = xmc4xxx_write_command_sequence(bank, erase_cmd_seq, + ARRAY_SIZE(erase_cmd_seq)); + if (res != ERROR_OK) + return res; + + /* Read the flash status register */ + res = target_read_u32(bank->target, FLASH_REG_FLASH0_FSR, &status); + if (res != ERROR_OK) { + LOG_ERROR("Cannot read flash status register."); + return res; + } + + /* Check for a sequence error */ + if (status & FSR_SQER_MASK) { + LOG_ERROR("Error with flash erase sequence"); + return ERROR_FAIL; + } + + /* Make sure a flash erase was triggered */ + if (!(status & FSR_ERASE_MASK)) { + LOG_ERROR("Flash failed to erase"); + return ERROR_FAIL; + } + + /* Now we must wait for the erase operation to end */ + res = xmc4xxx_wait_status_busy(bank, FLASH_OP_TIMEOUT); + + return res; +} + +static int xmc4xxx_erase(struct flash_bank *bank, int first, int last) +{ + struct xmc4xxx_flash_bank *fb = bank->driver_priv; + int res; + + if (bank->target->state != TARGET_HALTED) { + LOG_ERROR("Unable to erase, target is not halted"); + return ERROR_TARGET_NOT_HALTED; + } + + if (!fb->probed) { + res = xmc4xxx_probe(bank); + if (res != ERROR_OK) + return res; + } + + uint32_t tmp_addr; + /* Loop through the sectors and erase each one */ + for (int i = first; i <= last; i++) { + res = xmc4xxx_get_sector_start_addr(bank, i, &tmp_addr); + if (res != ERROR_OK) { + LOG_ERROR("Invalid sector %d", i); + return res; + } + + LOG_DEBUG("Erasing sector %d @ 0x%08"PRIx32, i, tmp_addr); + + res = xmc4xxx_erase_sector(bank, tmp_addr, false); + if (res != ERROR_OK) { + LOG_ERROR("Unable to write erase command sequence"); + goto clear_status_and_exit; + } + + /* Now we must wait for the erase operation to end */ + res = xmc4xxx_wait_status_busy(bank, FLASH_OP_TIMEOUT); + + if (res != ERROR_OK) + goto clear_status_and_exit; + + bank->sectors[i].is_erased = 1; + } + +clear_status_and_exit: + res = xmc4xxx_clear_flash_status(bank); + return res; + +} + +static int xmc4xxx_enter_page_mode(struct flash_bank *bank) +{ + int res; + uint32_t status; + + res = target_write_u32(bank->target, FLASH_CMD_ENTER_PAGEMODE, 0x50); + if (res != ERROR_OK) { + LOG_ERROR("Unable to write enter page mode command"); + return ERROR_FAIL; + } + + res = xmc4xxx_get_flash_status(bank, &status); + + if (res != ERROR_OK) + return res; + + /* Make sure we're in page mode */ + if (!(status & FSR_PFPAGE_MASK)) { + LOG_ERROR("Unable to enter page mode"); + return ERROR_FAIL; + } + + /* Make sure we didn't encounter a sequence error */ + if (status & FSR_SQER_MASK) { + LOG_ERROR("Sequence error while entering page mode"); + return ERROR_FAIL; + } + + return res; +} + +/* The logical erase value of an xmc4xxx memory cell is 0x00, + * therefore, we cannot use the built in flash blank check and must + * implement our own */ + +/** Checks whether a memory region is zeroed. */ +int xmc4xxx_blank_check_memory(struct target *target, + uint32_t address, uint32_t count, uint32_t *blank) +{ + struct working_area *erase_check_algorithm; + struct reg_param reg_params[3]; + struct armv7m_algorithm armv7m_info; + int retval; + + /* see contrib/loaders/erase_check/armv7m_0_erase_check.s for src */ + + static const uint8_t erase_check_code[] = { + /* loop: */ + 0x03, 0x78, /* ldrb r3, [r0] */ + 0x01, 0x30, /* adds r0, #1 */ + 0x1A, 0x43, /* orrs r2, r2, r3 */ + 0x01, 0x39, /* subs r1, r1, #1 */ + 0xFA, 0xD1, /* bne loop */ + 0x00, 0xBE /* bkpt #0 */ + }; + + /* make sure we have a working area */ + if (target_alloc_working_area(target, sizeof(erase_check_code), + &erase_check_algorithm) != ERROR_OK) + return ERROR_TARGET_RESOURCE_NOT_AVAILABLE; + + retval = target_write_buffer(target, erase_check_algorithm->address, + sizeof(erase_check_code), (uint8_t *)erase_check_code); + if (retval != ERROR_OK) + return retval; + + armv7m_info.common_magic = ARMV7M_COMMON_MAGIC; + armv7m_info.core_mode = ARM_MODE_THREAD; + + init_reg_param(®_params[0], "r0", 32, PARAM_OUT); + buf_set_u32(reg_params[0].value, 0, 32, address); + + init_reg_param(®_params[1], "r1", 32, PARAM_OUT); + buf_set_u32(reg_params[1].value, 0, 32, count); + + init_reg_param(®_params[2], "r2", 32, PARAM_IN_OUT); + buf_set_u32(reg_params[2].value, 0, 32, 0x00); + + retval = target_run_algorithm(target, + 0, + NULL, + 3, + reg_params, + erase_check_algorithm->address, + erase_check_algorithm->address + (sizeof(erase_check_code) - 2), + 10000, + &armv7m_info); + + if (retval == ERROR_OK) + *blank = buf_get_u32(reg_params[2].value, 0, 32); + + destroy_reg_param(®_params[0]); + destroy_reg_param(®_params[1]); + destroy_reg_param(®_params[2]); + + target_free_working_area(target, erase_check_algorithm); + + return retval; +} + +static int xmc4xxx_flash_blank_check(struct flash_bank *bank) +{ + struct target *target = bank->target; + int i; + int retval; + uint32_t blank; + + if (bank->target->state != TARGET_HALTED) { + LOG_ERROR("Target not halted"); + return ERROR_TARGET_NOT_HALTED; + } + + for (i = 0; i < bank->num_sectors; i++) { + uint32_t address = bank->base + bank->sectors[i].offset; + uint32_t size = bank->sectors[i].size; + + LOG_DEBUG("Erase checking 0x%08"PRIx32, address); + retval = xmc4xxx_blank_check_memory(target, address, size, &blank); + + if (retval != ERROR_OK) + break; + + if (blank == 0x00) + bank->sectors[i].is_erased = 1; + else + bank->sectors[i].is_erased = 0; + } + + return ERROR_OK; +} + +static int xmc4xxx_write_page(struct flash_bank *bank, const uint8_t *pg_buf, + uint32_t offset, bool user_config) +{ + int res; + uint32_t status; + + /* Base of the flash write command */ + struct xmc4xxx_command_seq write_cmd_seq[4] = { + {FLASH_CMD_WRITE_PAGE_1, 0xAA}, + {FLASH_CMD_WRITE_PAGE_2, 0x55}, + {FLASH_CMD_WRITE_PAGE_3, 0xFF}, /* Needs filled in */ + {0xFF, 0xFF} /* Needs filled in */ + }; + + /* The command sequence differs depending on whether this is + * being written to standard flash or the user configuration + * area */ + if (user_config) + write_cmd_seq[2].magic = 0xC0; + else + write_cmd_seq[2].magic = 0xA0; + + /* Finally, we need to add the address that this page will be + * written to */ + write_cmd_seq[3].address = bank->base + offset; + write_cmd_seq[3].magic = 0xAA; + + + /* Flash pages are written 256 bytes at a time. For each 256 + * byte chunk, we need to: + * 1. Enter page mode. This activates the flash write buffer + * 2. Load the page buffer with data (2x 32 bit words at a time) + * 3. Burn the page buffer into its intended location + * If the starting offset is not on a 256 byte boundary, we + * will need to pad the beginning of the write buffer + * accordingly. Likewise, if the last page does not fill the + * buffer, we should pad it to avoid leftover data from being + * written to flash + */ + res = xmc4xxx_enter_page_mode(bank); + if (res != ERROR_OK) + return res; + + /* Copy the data into the page buffer*/ + for (int i = 0; i < 256; i += 8) { + uint32_t w_lo = target_buffer_get_u32(bank->target, &pg_buf[i]); + uint32_t w_hi = target_buffer_get_u32(bank->target, &pg_buf[i + 4]); + LOG_DEBUG("WLO: %08"PRIx32, w_lo); + LOG_DEBUG("WHI: %08"PRIx32, w_hi); + + /* Data is loaded 2x 32 bit words at a time */ + res = target_write_u32(bank->target, FLASH_CMD_LOAD_PAGE_1, w_lo); + if (res != ERROR_OK) + return res; + + res = target_write_u32(bank->target, FLASH_CMD_LOAD_PAGE_2, w_hi); + if (res != ERROR_OK) + return res; + + /* Check for an error */ + res = xmc4xxx_get_flash_status(bank, &status); + if (res != ERROR_OK) + return res; + + if (status & FSR_SQER_MASK) { + LOG_ERROR("Error loading page buffer"); + return ERROR_FAIL; + } + } + + /* The page buffer is now full, time to commit it to flash */ + + res = xmc4xxx_write_command_sequence(bank, write_cmd_seq, ARRAY_SIZE(write_cmd_seq)); + if (res != ERROR_OK) { + LOG_ERROR("Unable to enter write command sequence"); + return res; + } + + /* Read the flash status register */ + res = xmc4xxx_get_flash_status(bank, &status); + if (res != ERROR_OK) + return res; + + /* Check for a sequence error */ + if (status & FSR_SQER_MASK) { + LOG_ERROR("Error with flash write sequence"); + return ERROR_FAIL; + } + + /* Make sure a flash write was triggered */ + if (!(status & FSR_PROG_MASK)) { + LOG_ERROR("Failed to write flash page"); + return ERROR_FAIL; + } + + /* Wait for the write operation to end */ + res = xmc4xxx_wait_status_busy(bank, FLASH_OP_TIMEOUT); + if (res != ERROR_OK) + return res; + + /* TODO: Verify that page was written without error */ + return res; +} + +static int xmc4xxx_write(struct flash_bank *bank, const uint8_t *buffer, + uint32_t offset, uint32_t count) +{ + struct xmc4xxx_flash_bank *fb = bank->driver_priv; + int res = ERROR_OK; + + if (bank->target->state != TARGET_HALTED) { + LOG_ERROR("Unable to erase, target is not halted"); + return ERROR_TARGET_NOT_HALTED; + } + + if (!fb->probed) { + res = xmc4xxx_probe(bank); + if (res != ERROR_OK) + return res; + } + + /* Make sure we won't run off the end of the flash bank */ + if ((offset + count) > (bank->size)) { + LOG_ERROR("Attempting to write past the end of flash"); + return ERROR_FAIL; + } + + + /* Attempt to write the passed in buffer to flash */ + /* Pages are written 256 bytes at a time, we need to handle + * scenarios where padding is required at the beginning and + * end of a page */ + while (count) { + /* page working area */ + uint8_t tmp_buf[256] = {0}; + + /* Amount of data we'll be writing to this page */ + int remaining; + int end_pad; + + remaining = MIN(count, sizeof(tmp_buf)); + end_pad = sizeof(tmp_buf) - remaining; + + /* Make sure we're starting on a page boundary */ + int start_pad = offset % 256; + if (start_pad) { + LOG_INFO("Write does not start on a 256 byte boundary. " + "Padding by %d bytes", start_pad); + memset(tmp_buf, 0xff, start_pad); + /* Subtract the amount of start offset from + * the amount of data we'll need to write */ + remaining -= start_pad; + } + + /* Remove the amount we'll be writing from the total count */ + count -= remaining; + + /* Now copy in the remaining data */ + memcpy(&tmp_buf[start_pad], buffer, remaining); + + if (end_pad) { + LOG_INFO("Padding end of page @%08"PRIx32" by %d bytes", + bank->base + offset, end_pad); + memset(&tmp_buf[256 - end_pad], 0xff, end_pad); + } + + /* Now commit this page to flash, if there was start + * padding, we should subtract that from the target offset */ + res = xmc4xxx_write_page(bank, tmp_buf, (offset - start_pad), false); + if (res != ERROR_OK) { + LOG_ERROR("Unable to write flash page"); + goto abort_write_and_exit; + } + + /* Advance the buffer pointer */ + buffer += remaining; + + /* Advance the offset */ + offset += remaining; + } + +abort_write_and_exit: + xmc4xxx_clear_flash_status(bank); + return res; + +} + +static int xmc4xxx_get_info_command(struct flash_bank *bank, char *buf, int buf_size) +{ + struct xmc4xxx_flash_bank *fb = bank->driver_priv; + uint32_t scu_idcode; + + if (bank->target->state != TARGET_HALTED) { + LOG_WARNING("Cannot communicate... target not halted."); + return ERROR_TARGET_NOT_HALTED; + } + + /* The SCU registers contain the ID of the chip */ + int res = target_read_u32(bank->target, SCU_REG_BASE + SCU_ID_CHIP, &scu_idcode); + if (res != ERROR_OK) { + LOG_ERROR("Cannot read device identification register."); + return res; + } + + uint16_t dev_id = (scu_idcode & 0xfff0) >> 4; + uint16_t rev_id = scu_idcode & 0xf; + const char *dev_str; + const char *rev_str = NULL; + + switch (dev_id) { + case 0x100: + dev_str = "XMC4100"; + + switch (rev_id) { + case 0x1: + rev_str = "AA"; + break; + case 0x2: + rev_str = "AB"; + break; + } + break; + case 0x200: + dev_str = "XMC4200"; + + switch (rev_id) { + case 0x1: + rev_str = "AA"; + break; + case 0x2: + rev_str = "AB"; + break; + } + break; + case 0x400: + dev_str = "XMC4400"; + + switch (rev_id) { + case 0x1: + rev_str = "AA"; + break; + case 0x2: + rev_str = "AB"; + break; + } + break; + case 0x500: + dev_str = "XMC4500"; + + switch (rev_id) { + case 0x2: + rev_str = "AA"; + break; + case 0x3: + rev_str = "AB"; + break; + case 0x4: + rev_str = "AC"; + break; + } + break; + + default: + snprintf(buf, buf_size, + "Cannot identify target as an XMC4xxx. SCU_ID: %"PRIx32"\n", + scu_idcode); + return ERROR_OK; + } + + /* String to declare protection data held in the private driver */ + char prot_str[512] = {0}; + if (fb->read_protected) + snprintf(prot_str, sizeof(prot_str), "\nFlash is read protected"); + + bool otp_enabled = false; + for (int i = 0; i < bank->num_sectors; i++) + if (fb->write_prot_otp[i]) + otp_enabled = true; + + /* If OTP Write protection is enabled (User 2), list each + * sector that has it enabled */ + char otp_str[8]; + if (otp_enabled) { + strcat(prot_str, "\nOTP Protection is enabled for sectors:\n"); + for (int i = 0; i < bank->num_sectors; i++) { + if (fb->write_prot_otp[i]) { + snprintf(otp_str, sizeof(otp_str), "- %d\n", i); + strncat(prot_str, otp_str, ARRAY_SIZE(otp_str)); + } + } + } + + if (rev_str != NULL) + snprintf(buf, buf_size, "%s - Rev: %s%s", + dev_str, rev_str, prot_str); + else + snprintf(buf, buf_size, "%s - Rev: unknown (0x%01x)%s", + dev_str, rev_id, prot_str); + + return ERROR_OK; +} + +static int xmc4xxx_temp_unprotect(struct flash_bank *bank, int user_level) +{ + struct xmc4xxx_flash_bank *fb; + int res = ERROR_OK; + uint32_t status = 0; + + struct xmc4xxx_command_seq temp_unprot_seq[6] = { + {FLASH_CMD_TEMP_UNPROT_1, 0xAA}, + {FLASH_CMD_TEMP_UNPROT_2, 0x55}, + {FLASH_CMD_TEMP_UNPROT_3, 0xFF}, /* Needs filled in */ + {FLASH_CMD_TEMP_UNPROT_4, 0xFF}, /* Needs filled in */ + {FLASH_CMD_TEMP_UNPROT_5, 0xFF}, /* Needs filled in */ + {FLASH_CMD_TEMP_UNPROT_6, 0x05} + }; + + if (user_level < 0 || user_level > 2) { + LOG_ERROR("Invalid user level, must be 0-2"); + return ERROR_FAIL; + } + + fb = bank->driver_priv; + + /* Fill in the user level and passwords */ + temp_unprot_seq[2].magic = user_level; + temp_unprot_seq[3].magic = fb->pw1; + temp_unprot_seq[4].magic = fb->pw2; + + res = xmc4xxx_write_command_sequence(bank, temp_unprot_seq, + ARRAY_SIZE(temp_unprot_seq)); + if (res != ERROR_OK) { + LOG_ERROR("Unable to write temp unprotect sequence"); + return res; + } + + res = xmc4xxx_get_flash_status(bank, &status); + if (res != ERROR_OK) + return res; + + if (status & FSR_WPRODIS0) { + LOG_INFO("Flash is temporarily unprotected"); + } else { + LOG_INFO("Unable to disable flash protection"); + res = ERROR_FAIL; + } + + + return res; +} + +static int xmc4xxx_flash_unprotect(struct flash_bank *bank, int32_t level) +{ + uint32_t addr; + int res; + + if ((level < 0) || (level > 1)) { + LOG_ERROR("Invalid user level. Must be 0-1"); + return ERROR_FAIL; + } + + switch (level) { + case 0: + addr = UCB0_BASE; + break; + case 1: + addr = UCB1_BASE; + break; + } + + res = xmc4xxx_erase_sector(bank, addr, true); + + if (res != ERROR_OK) + LOG_ERROR("Error erasing user configuration block"); + + return res; +} + +/* Reference: "XMC4500 Flash Protection.pptx" app note */ +static int xmc4xxx_flash_protect(struct flash_bank *bank, int level, bool read_protect, + int first, int last) +{ + /* User configuration block buffers */ + uint8_t ucp0_buf[8 * sizeof(uint32_t)] = {0}; + uint32_t ucb_base = 0; + uint32_t procon = 0; + int res = ERROR_OK; + uint32_t status = 0; + bool proin = false; + + struct xmc4xxx_flash_bank *fb = bank->driver_priv; + + /* Read protect only works for user 0, make sure we don't try + * to do something silly */ + if (level != 0 && read_protect) { + LOG_ERROR("Read protection is for user level 0 only!"); + return ERROR_FAIL; + } + + /* Check to see if protection is already installed for the + * specified user level. If it is, the user configuration + * block will need to be erased before we can continue */ + + /* Grab the flash status register*/ + res = xmc4xxx_get_flash_status(bank, &status); + if (res != ERROR_OK) + return res; + + switch (level) { + case 0: + if ((status & FSR_RPROIN_MASK) || (status & FSR_WPROIN0_MASK)) + proin = true; + break; + case 1: + if (status & FSR_WPROIN1_MASK) + proin = true; + break; + case 2: + if (status & FSR_WPROIN2_MASK) + proin = true; + break; + } + + if (proin) { + LOG_ERROR("Flash protection is installed for user %d" + " and must be removed before continuing", level); + return ERROR_FAIL; + } + + /* If this device has 12 flash sectors, protection for + * sectors 10 & 11 are handled jointly. If we are trying to + * write all sectors, we should decrement + * last to ensure we don't write to a register bit that + * doesn't exist*/ + if ((bank->num_sectors == 12) && (last == 12)) + last--; + + /* We need to fill out the procon register representation + * that we will be writing to the device */ + for (int i = first; i <= last; i++) + procon |= 1 << i; + + /* If read protection is requested, set the appropriate bit + * (we checked that this is allowed above) */ + if (read_protect) + procon |= PROCON_RPRO_MASK; + + LOG_DEBUG("Setting flash protection with procon:"); + LOG_DEBUG("PROCON: %"PRIx32, procon); + + /* First we need to copy in the procon register to the buffer + * we're going to attempt to write. This is written twice */ + target_buffer_set_u32(bank->target, &ucp0_buf[0 * 4], procon); + target_buffer_set_u32(bank->target, &ucp0_buf[2 * 4], procon); + + /* Now we must copy in both flash passwords. As with the + * procon data, this must be written twice (4 total words + * worth of data) */ + target_buffer_set_u32(bank->target, &ucp0_buf[4 * 4], fb->pw1); + target_buffer_set_u32(bank->target, &ucp0_buf[5 * 4], fb->pw2); + target_buffer_set_u32(bank->target, &ucp0_buf[6 * 4], fb->pw1); + target_buffer_set_u32(bank->target, &ucp0_buf[7 * 4], fb->pw2); + + /* Finally, (if requested) we copy in the confirmation + * code so that the protection is permanent and will + * require a password to undo. */ + target_buffer_set_u32(bank->target, &ucp0_buf[0 * 4], FLASH_PROTECT_CONFIRMATION_CODE); + target_buffer_set_u32(bank->target, &ucp0_buf[2 * 4], FLASH_PROTECT_CONFIRMATION_CODE); + + /* Now that the data is copied into place, we must write + * these pages into flash */ + + /* The user configuration block base depends on what level of + * protection we're trying to install, select the proper one */ + switch (level) { + case 0: + ucb_base = UCB0_BASE; + break; + case 1: + ucb_base = UCB1_BASE; + break; + case 2: + ucb_base = UCB2_BASE; + break; + } + + /* Write the user config pages */ + res = xmc4xxx_write_page(bank, ucp0_buf, ucb_base, true); + if (res != ERROR_OK) { + LOG_ERROR("Error writing user configuration block 0"); + return res; + } + + return ERROR_OK; +} + +static int xmc4xxx_protect(struct flash_bank *bank, int set, int first, int last) +{ + int ret; + struct xmc4xxx_flash_bank *fb = bank->driver_priv; + + /* Check for flash passwords */ + if (!fb->pw_set) { + LOG_ERROR("Flash passwords not set, use xmc4xxx flash_password to set them"); + return ERROR_FAIL; + } + + /* We want to clear flash protection temporarily*/ + if (set == 0) { + LOG_WARNING("Flash protection will be temporarily disabled" + " for all pages (User 0 only)!"); + ret = xmc4xxx_temp_unprotect(bank, 0); + return ret; + } + + /* Install write protection for user 0 on the specified pages */ + ret = xmc4xxx_flash_protect(bank, 0, false, first, last); + + return ret; +} + +static int xmc4xxx_protect_check(struct flash_bank *bank) +{ + int ret; + uint32_t protection[3] = {0}; + struct xmc4xxx_flash_bank *fb = bank->driver_priv; + + ret = target_read_u32(bank->target, FLASH_REG_FLASH0_PROCON0, &protection[0]); + if (ret != ERROR_OK) { + LOG_ERROR("Unable to read flash User0 protection register"); + return ret; + } + + ret = target_read_u32(bank->target, FLASH_REG_FLASH0_PROCON1, &protection[1]); + if (ret != ERROR_OK) { + LOG_ERROR("Unable to read flash User1 protection register"); + return ret; + } + + ret = target_read_u32(bank->target, FLASH_REG_FLASH0_PROCON2, &protection[2]); + if (ret != ERROR_OK) { + LOG_ERROR("Unable to read flash User2 protection register"); + return ret; + } + + int sectors = bank->num_sectors; + + /* On devices with 12 sectors, sectors 10 & 11 are ptected + * together instead of individually */ + if (sectors == 12) + sectors--; + + /* Clear the protection status */ + for (int i = 0; i < bank->num_sectors; i++) { + bank->sectors[i].is_protected = 0; + fb->write_prot_otp[i] = false; + } + fb->read_protected = false; + + /* The xmc4xxx series supports 3 levels of user protection + * (User0, User1 (low priority), and User 2(OTP), we need to + * check all 3 */ + for (unsigned int i = 0; i < ARRAY_SIZE(protection); i++) { + + /* Check for write protection on every available + * sector */ + for (int j = 0; j < sectors; j++) { + int set = (protection[i] & (1 << j)) ? 1 : 0; + bank->sectors[j].is_protected |= set; + + /* Handle sector 11 */ + if (j == 10) + bank->sectors[j + 1].is_protected |= set; + + /* User 2 indicates this protection is + * permanent, make note in the private driver structure */ + if (i == 2 && set) { + fb->write_prot_otp[j] = true; + + /* Handle sector 11 */ + if (j == 10) + fb->write_prot_otp[j + 1] = true; + } + + } + } + + /* XMC4xxx also supports read proptection, make a note + * in the private driver structure */ + if (protection[0] & PROCON_RPRO_MASK) + fb->read_protected = true; + + return ERROR_OK; +} + +FLASH_BANK_COMMAND_HANDLER(xmc4xxx_flash_bank_command) +{ + bank->driver_priv = malloc(sizeof(struct xmc4xxx_flash_bank)); + + if (!bank->driver_priv) + return ERROR_FLASH_OPERATION_FAILED; + + (void)memset(bank->driver_priv, 0, sizeof(struct xmc4xxx_flash_bank)); + + return ERROR_OK; +} + +COMMAND_HANDLER(xmc4xxx_handle_flash_password_command) +{ + int res; + struct flash_bank *bank; + + if (CMD_ARGC < 3) + return ERROR_COMMAND_SYNTAX_ERROR; + + res = CALL_COMMAND_HANDLER(flash_command_get_bank, 0, &bank); + if (res != ERROR_OK) + return res; + + struct xmc4xxx_flash_bank *fb = bank->driver_priv; + + errno = 0; + + /* We skip over the flash bank */ + fb->pw1 = strtol(CMD_ARGV[1], NULL, 16); + + if (errno) + return ERROR_COMMAND_SYNTAX_ERROR; + + fb->pw2 = strtol(CMD_ARGV[2], NULL, 16); + + if (errno) + return ERROR_COMMAND_SYNTAX_ERROR; + + fb->pw_set = true; + + command_print(CMD_CTX, "XMC4xxx flash passwords set to:\n"); + command_print(CMD_CTX, "-0x%08"PRIx32"\n", fb->pw1); + command_print(CMD_CTX, "-0x%08"PRIx32"\n", fb->pw2); + return ERROR_OK; +} + +COMMAND_HANDLER(xmc4xxx_handle_flash_unprotect_command) +{ + struct flash_bank *bank; + int res; + int32_t level; + + if (CMD_ARGC < 2) + return ERROR_COMMAND_SYNTAX_ERROR; + + res = CALL_COMMAND_HANDLER(flash_command_get_bank, 0, &bank); + if (res != ERROR_OK) + return res; + + COMMAND_PARSE_NUMBER(s32, CMD_ARGV[1], level); + + res = xmc4xxx_flash_unprotect(bank, level); + + return res; +} + +static const struct command_registration xmc4xxx_exec_command_handlers[] = { + { + .name = "flash_password", + .handler = xmc4xxx_handle_flash_password_command, + .mode = COMMAND_EXEC, + .usage = "bank_id password1 password2", + .help = "Set the flash passwords used for protect operations. " + "Passwords should be in standard hex form (0x00000000). " + "(You must call this before any other protect commands) " + "NOTE: The xmc4xxx's UCB area only allows for FOUR cycles. " + "Please use protection carefully!", + }, + { + .name = "flash_unprotect", + .handler = xmc4xxx_handle_flash_unprotect_command, + .mode = COMMAND_EXEC, + .usage = "bank_id user_level[0-1]", + .help = "Permanently Removes flash protection (read and write) " + "for the specified user level", + }, COMMAND_REGISTRATION_DONE +}; + +static const struct command_registration xmc4xxx_command_handlers[] = { + { + .name = "xmc4xxx", + .mode = COMMAND_ANY, + .help = "xmc4xxx flash command group", + .usage = "", + .chain = xmc4xxx_exec_command_handlers, + }, + COMMAND_REGISTRATION_DONE +}; + +struct flash_driver xmc4xxx_flash = { + .name = "xmc4xxx", + .commands = xmc4xxx_command_handlers, + .flash_bank_command = xmc4xxx_flash_bank_command, + .erase = xmc4xxx_erase, + .write = xmc4xxx_write, + .read = default_flash_read, + .probe = xmc4xxx_probe, + .auto_probe = xmc4xxx_probe, + .erase_check = xmc4xxx_flash_blank_check, + .info = xmc4xxx_get_info_command, + .protect_check = xmc4xxx_protect_check, + .protect = xmc4xxx_protect, +}; |