diff options
Diffstat (limited to 'src/flash/nor/mini51.c')
-rw-r--r-- | src/flash/nor/mini51.c | 463 |
1 files changed, 463 insertions, 0 deletions
diff --git a/src/flash/nor/mini51.c b/src/flash/nor/mini51.c new file mode 100644 index 00000000..eb74a314 --- /dev/null +++ b/src/flash/nor/mini51.c @@ -0,0 +1,463 @@ +/*************************************************************************** + * Copyright (C) 2013 Cosmin Gorgovan * + * cosmin [at] linux-geek [dot] org * + * * + * 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, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * + ***************************************************************************/ + +/* + Flash driver for the Nuvoton NuMicro Mini51 series microcontrollers + + Part |APROM Size |Part ID (at 0x5000_0000) + ---------------------------------------------- + MINI51LAN 4 KB 0x00205100 + MINI51ZAN 4 KB 0x00205103 + MINI51TAN 4 KB 0x00205104 + MINI52LAN 8 KB 0x00205200 + MINI52ZAN 8 KB 0x00205203 + MINI52TAN 8 KB 0x00205204 + MINI54LAN 16 KB 0x00205400 + MINI54ZAN 16 KB 0x00205403 + MINI54TAN 16 KB 0x00205404 + + Datasheet & TRM + --------------- + + The ISP flash programming procedure is described on pages 130 and 131 of the (not very verbose) TRM. + + http://www.keil.com/dd/docs/datashts/nuvoton/mini51/da00-mini51_52_54c1.pdf + + This driver + ----------- + + * Only erase and write operations have been implemented; + * Both operations only support the APROM, not the LDROM; + * The TRM suggests that after the boot source has been selected, a software reset should be performed by + setting bit SWRST in ISPCON. However, this doesn't seem to have any effect on the MCU I'm using. At the + moment, the ARM core is reset using the IPRSTC1 register, which seems to do the trick. + + Flash access limitations + ------------------------ + + APROM can only be modified when the MCU has booted off the LDROM. For write and erase operations, the + microcontroller will probably need to be rebooted. Pseudocode: + + * If operation is write or erase, check bit BS (1) in ISPCON (0x5000_C000); + * If BS is 0 (APROM): + * unlock protected registers by writing 0x59, 0x16, 0x88 to RegLockAddr(0x5000_0100); + * set BS to 1 (LDROM); + * reboot by setting bit CPU_RST(1) in IPRSTC1 (0x50000008); + * poll CPU_RST until it is reset (not sure it's necessary); + * <Perform flash operation> + * reboot from APROM using the same procedure but writing 0 to BS + + + For implementing the read operation, please note that the APROM isn't memory mapped when booted from LDROM. +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "imp.h" + +#define PART_ID_REG 0x50000000 +#define IPRSTC1 0x50000008 +#define REGLOCKADDR 0x50000100 +#define ISPCON 0x5000C000 +#define ISPADR 0x5000C004 +#define ISPDAT 0x5000C008 +#define ISPCMD 0x5000C00C +#define ISPTRG 0x5000C010 + +#define PART_ID_MAIN_MASK 0xFFFFFFF8 +#define IPRSTC_CPU_RST 0x02 +#define ISPCON_BS_LDROM 0x02 +#define ISPCON_ISPEN 0x01 +#define ISPCON_SWRST 0x80 +#define ISPCON_ISPFF 0x40 +#define ISPCMD_PROGRAM 0x21 +#define ISPCMD_ERASE 0x22 +#define ISPTRG_ISPGO 0x01 + +#define MINI51 0x00205100 +#define MINI52 0x00205200 +#define MINI54 0x00205400 + +#define MINI51_APROM_BASE 0x00000000 +#define KB 1024 +#define PAGE_SIZE 512 +#define TIMEOUT 1000 + + +struct mini51_flash_bank { + bool probed; +}; + +enum mini51_boot_source { + APROM = 0, + LDROM = 1 +}; + + +/* Private methods */ + +static int mini51_unlock_reg(struct flash_bank *bank) +{ + int status; + struct target *target = bank->target; + + status = target_write_u32(target, REGLOCKADDR, 0x59); + if (status != ERROR_OK) + return status; + status = target_write_u32(target, REGLOCKADDR, 0x16); + if (status != ERROR_OK) + return status; + status = target_write_u32(target, REGLOCKADDR, 0x88); + if (status != ERROR_OK) + return status; + + return ERROR_OK; +} + +static int mini51_reboot_with_source(struct flash_bank *bank, + enum mini51_boot_source new_source, + enum mini51_boot_source *prev_source) +{ + uint32_t ispcon; + uint32_t isprtc1; + bool reboot = false; + int status; + int timeout = TIMEOUT; + + /* Read current boot source */ + struct target *target = bank->target; + status = target_read_u32(target, ISPCON, &ispcon); + if (status != ERROR_OK) + return status; + + *prev_source = (ispcon >> 1) & 1; + + if ((new_source == APROM) && (*prev_source != APROM)) { + ispcon &= ~ISPCON_BS_LDROM; + reboot = true; + } else if ((new_source == LDROM) && (*prev_source != LDROM)) { + ispcon |= ISPCON_BS_LDROM; + reboot = true; + } + + if (reboot) { + mini51_unlock_reg(bank); + status = target_write_u32(target, ISPCON, ispcon); + if (status != ERROR_OK) + return status; + + status = target_write_u32(target, IPRSTC1, IPRSTC_CPU_RST); + if (status != ERROR_OK) + return status; + + do { + target_read_u32(target, IPRSTC1, &isprtc1); + timeout--; + } while ((isprtc1 & IPRSTC_CPU_RST) && timeout > 0); + + if (timeout == 0) { + LOG_WARNING("Mini51 flash driver: timeout attempting to reboot\n"); + return ERROR_FLASH_OPERATION_FAILED; + } + } + + return ERROR_OK; +} + +static int mini51_get_part_id(struct flash_bank *bank, uint32_t *part_id) +{ + return target_read_u32(bank->target, PART_ID_REG, part_id); +} + +static int mini51_get_flash_size(struct flash_bank *bank, uint32_t *flash_size) +{ + uint32_t part_id; + int status; + + status = mini51_get_part_id(bank, &part_id); + if (status != ERROR_OK) + return status; + + switch (part_id & PART_ID_MAIN_MASK) { + case MINI51: + *flash_size = 4 * KB; + break; + case MINI52: + *flash_size = 8 * KB; + break; + case MINI54: + *flash_size = 16 * KB; + break; + default: + *flash_size = 0; + break; + } + + return ERROR_OK; +} + + +/* Public (API) methods */ + +FLASH_BANK_COMMAND_HANDLER(mini51_flash_bank_command) +{ + struct mini51_flash_bank *mini51_info; + mini51_info = malloc(sizeof(struct mini51_flash_bank)); + mini51_info->probed = false; + bank->driver_priv = mini51_info; + + return ERROR_OK; +} + +static int mini51_protect_check(struct flash_bank *bank) +{ + LOG_WARNING("Mini51 flash driver: protect_check not implemented yet\n"); + + return ERROR_FLASH_OPERATION_FAILED; +} + +static int mini51_erase(struct flash_bank *bank, int first, int last) +{ + int status; + int timeout; + uint32_t ispcon; + uint32_t isptrg; + enum mini51_boot_source new_source; + enum mini51_boot_source prev_source; + struct target *target = bank->target; + + if (target->state != TARGET_HALTED) { + LOG_ERROR("Target not halted"); + return ERROR_TARGET_NOT_HALTED; + } + + /* TODO: add support for erasing the LDROM */ + new_source = LDROM; + status = mini51_reboot_with_source(bank, new_source, &prev_source); + if (status != ERROR_OK) + return status; + + /* Enable ISP */ + status = target_read_u32(target, ISPCON, &ispcon); + if (status != ERROR_OK) + return status; + ispcon |= ISPCON_ISPEN; + status = target_write_u32(target, ISPCON, ispcon); + + for (int page_start = first; page_start <= last; page_start++) { + /* Set up erase command */ + status = target_write_u32(target, ISPADR, page_start*PAGE_SIZE); + if (status != ERROR_OK) + return status; + status = target_write_u32(target, ISPCMD, ISPCMD_ERASE); + if (status != ERROR_OK) + return status; + + /* Erase the selected page */ + status = target_write_u32(target, ISPTRG, ISPTRG_ISPGO); + if (status != ERROR_OK) + return status; + + /* Wait for for command to finish executing */ + timeout = TIMEOUT; + do { + target_read_u32(target, ISPTRG, &isptrg); + timeout--; + } while ((isptrg & ISPTRG_ISPGO) && (timeout > 0)); + if (timeout == 0) { + LOG_WARNING("Mini51 flash driver: Timeout erasing flash\n"); + return ERROR_FLASH_OPERATION_FAILED; + } + + /* Check for errors */ + status = target_read_u32(target, ISPCON, &ispcon); + if (status != ERROR_OK) + return status; + if (ispcon & ISPCON_ISPFF) { + LOG_WARNING("Mini51 flash driver: Erase operation failed\n"); + return ERROR_FLASH_OPERATION_FAILED; + } + } + + /* Reboot from previous source */ + if (prev_source != new_source) { + status = mini51_reboot_with_source(bank, prev_source, &new_source); + if (status != ERROR_OK) + return status; + } + + return ERROR_OK; +} + +static int mini51_protect(struct flash_bank *bank, int set, int first, int last) +{ + LOG_WARNING("Mini51 flash driver: protect operation not implemented yet\n"); + + return ERROR_FLASH_OPERATION_FAILED; +} + +static int mini51_write(struct flash_bank *bank, uint8_t *buffer, uint32_t offset, uint32_t count) +{ + int status; + int timeout; + uint32_t ispcon; + uint32_t isptrg; + uint32_t ispdat; + enum mini51_boot_source new_source; + enum mini51_boot_source prev_source; + struct target *target = bank->target; + + if (target->state != TARGET_HALTED) { + LOG_ERROR("Target not halted"); + return ERROR_TARGET_NOT_HALTED; + } + + if ((offset & 0x3) || (count & 0x3)) { + LOG_WARNING("Mini51 flash driver: unaligned access not supported\n"); + return ERROR_FLASH_OPERATION_FAILED; + } + + /* TODO: add support for writing to LDROM */ + new_source = LDROM; + status = mini51_reboot_with_source(bank, new_source, &prev_source); + if (status != ERROR_OK) + return status; + + /* Enable ISP */ + status = target_read_u32(target, ISPCON, &ispcon); + if (status != ERROR_OK) + return status; + ispcon |= ISPCON_ISPEN; + status = target_write_u32(target, ISPCON, ispcon); + + for (uint32_t i = offset; i < offset + count; i += 4) { + /* Set up program command */ + status = target_write_u32(target, ISPADR, i); + if (status != ERROR_OK) + return status; + status = target_write_u32(target, ISPCMD, ISPCMD_PROGRAM); + if (status != ERROR_OK) + return status; + memcpy(&ispdat, buffer, sizeof(ispdat)); + buffer += sizeof(ispdat); + status = target_write_u32(target, ISPDAT, ispdat); + if (status != ERROR_OK) + return status; + + /* Write the selected word */ + status = target_write_u32(target, ISPTRG, ISPTRG_ISPGO); + if (status != ERROR_OK) + return status; + + /* Wait for for command to finish executing */ + timeout = TIMEOUT; + do { + target_read_u32(target, ISPTRG, &isptrg); + timeout--; + } while ((isptrg & ISPTRG_ISPGO) && (timeout > 0)); + if (timeout == 0) { + LOG_WARNING("Mini51 flash driver: Timeout programming flash\n"); + return ERROR_FLASH_OPERATION_FAILED; + } + + /* Check for errors */ + status = target_read_u32(target, ISPCON, &ispcon); + if (status != ERROR_OK) + return status; + if (ispcon & ISPCON_ISPFF) { + LOG_WARNING("Mini51 flash driver: Programming operation failed\n"); + return ERROR_FLASH_OPERATION_FAILED; + } + } + + if (prev_source != new_source) { + status = mini51_reboot_with_source(bank, prev_source, &new_source); + if (status != ERROR_OK) + return status; + } + + return ERROR_OK; +} + + +static int get_mini51_info(struct flash_bank *bank, char *buf, int buf_size) +{ + snprintf(buf, buf_size, "Mini51 flash driver"); + return ERROR_OK; +} + +static int mini51_probe(struct flash_bank *bank) +{ + uint32_t flash_size; + int retval; + int num_pages; + uint32_t offset = 0; + + retval = mini51_get_flash_size(bank, &flash_size); + if (retval != ERROR_OK || flash_size == 0) { + LOG_WARNING("Mini51 flash driver: Failed to detect a known part\n"); + return ERROR_FLASH_OPERATION_FAILED; + } + + num_pages = flash_size / PAGE_SIZE; + + bank->base = MINI51_APROM_BASE; + bank->num_sectors = num_pages; + bank->sectors = malloc(sizeof(struct flash_sector) * num_pages); + bank->size = flash_size; + + for (int i = 0; i < num_pages; i++) { + bank->sectors[i].offset = offset; + bank->sectors[i].size = PAGE_SIZE; + bank->sectors[i].is_erased = -1; + bank->sectors[i].is_protected = 0; + offset += PAGE_SIZE; + } + + struct mini51_flash_bank *mini51_info = bank->driver_priv; + mini51_info->probed = true; + + return ERROR_OK; +} + +static int mini51_auto_probe(struct flash_bank *bank) +{ + struct mini51_flash_bank *mini51_info = bank->driver_priv; + if (mini51_info->probed) + return ERROR_OK; + return mini51_probe(bank); +} + +struct flash_driver mini51_flash = { + .name = "mini51", + .flash_bank_command = mini51_flash_bank_command, + .erase = mini51_erase, + .protect = mini51_protect, + .write = mini51_write, + .read = default_flash_read, + .probe = mini51_probe, + .auto_probe = mini51_auto_probe, + .erase_check = default_flash_blank_check, + .protect_check = mini51_protect_check, + .info = get_mini51_info, +}; + |