|
|
@@ -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, |
|
|
|
}; |
|
|
|
|