diff options
Diffstat (limited to 'src/flash')
-rw-r--r-- | src/flash/nor/Makefile.am | 1 | ||||
-rw-r--r-- | src/flash/nor/drivers.c | 2 | ||||
-rw-r--r-- | src/flash/nor/esirisc_flash.c | 621 |
3 files changed, 624 insertions, 0 deletions
diff --git a/src/flash/nor/Makefile.am b/src/flash/nor/Makefile.am index 3839d0a8..864f7f29 100644 --- a/src/flash/nor/Makefile.am +++ b/src/flash/nor/Makefile.am @@ -25,6 +25,7 @@ NOR_DRIVERS = \ %D%/dsp5680xx_flash.c \ %D%/efm32.c \ %D%/em357.c \ + %D%/esirisc_flash.c \ %D%/faux.c \ %D%/fm3.c \ %D%/fm4.c \ diff --git a/src/flash/nor/drivers.c b/src/flash/nor/drivers.c index 2251b965..4ffd5aca 100644 --- a/src/flash/nor/drivers.c +++ b/src/flash/nor/drivers.c @@ -38,6 +38,7 @@ extern struct flash_driver cfi_flash; extern struct flash_driver dsp5680xx_flash; extern struct flash_driver efm32_flash; extern struct flash_driver em357_flash; +extern struct flash_driver esirisc_flash; extern struct flash_driver faux_flash; extern struct flash_driver fm3_flash; extern struct flash_driver fm4_flash; @@ -103,6 +104,7 @@ static struct flash_driver *flash_drivers[] = { &dsp5680xx_flash, &efm32_flash, &em357_flash, + &esirisc_flash, &faux_flash, &fm3_flash, &fm4_flash, diff --git a/src/flash/nor/esirisc_flash.c b/src/flash/nor/esirisc_flash.c new file mode 100644 index 00000000..f3833df1 --- /dev/null +++ b/src/flash/nor/esirisc_flash.c @@ -0,0 +1,621 @@ +/*************************************************************************** + * Copyright (C) 2018 by Square, Inc. * + * Steven Stallion <stallion@squareup.com> * + * James Zhao <hjz@squareup.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. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see <http://www.gnu.org/licenses/>. * + ***************************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <flash/common.h> +#include <flash/nor/imp.h> +#include <helper/command.h> +#include <helper/log.h> +#include <helper/time_support.h> +#include <helper/types.h> +#include <target/esirisc.h> +#include <target/target.h> + +/* eSi-TSMC Flash Registers */ +#define CONTROL 0x00 /* Control Register */ +#define TIMING0 0x04 /* Timing Register 0 */ +#define TIMING1 0x08 /* Timing Register 1 */ +#define TIMING2 0x0c /* Timing Register 2 */ +#define UNLOCK1 0x18 /* Unlock 1 */ +#define UNLOCK2 0x1c /* Unlock 2 */ +#define ADDRESS 0x20 /* Erase/Program Address */ +#define PB_DATA 0x24 /* Program Buffer Data */ +#define PB_INDEX 0x28 /* Program Buffer Index */ +#define STATUS 0x2c /* Status Register */ +#define REDUN_0 0x30 /* Redundant Address 0 */ +#define REDUN_1 0x34 /* Redundant Address 1 */ + +/* Control Fields */ +#define CONTROL_SLM (1<<0) /* Sleep Mode */ +#define CONTROL_WP (1<<1) /* Register Write Protect */ +#define CONTROL_E (1<<3) /* Erase */ +#define CONTROL_EP (1<<4) /* Erase Page */ +#define CONTROL_P (1<<5) /* Program Flash */ +#define CONTROL_ERC (1<<6) /* Erase Reference Cell */ +#define CONTROL_R (1<<7) /* Recall Trim Code */ +#define CONTROL_AP (1<<8) /* Auto-Program */ + +/* Timing Fields */ +#define TIMING0_R(x) (((x) << 0) & 0x3f) /* Read Wait States */ +#define TIMING0_F(x) (((x) << 16) & 0xffff0000) /* Tnvh Clock Cycles */ +#define TIMING1_E(x) (((x) << 0) & 0xffffff) /* Tme/Terase/Tre Clock Cycles */ +#define TIMING2_P(x) (((x) << 0) & 0xffff) /* Tprog Clock Cycles */ +#define TIMING2_H(x) (((x) << 16) & 0xff0000) /* Clock Cycles in 100ns */ +#define TIMING2_T(x) (((x) << 24) & 0xf000000) /* Clock Cycles in 10ns */ + +/* Status Fields */ +#define STATUS_BUSY (1<<0) /* Busy (Erase/Program) */ +#define STATUS_WER (1<<1) /* Write Protect Error */ +#define STATUS_DR (1<<2) /* Disable Redundancy */ +#define STATUS_DIS (1<<3) /* Discharged */ +#define STATUS_BO (1<<4) /* Brown Out */ + +/* Redundant Address Fields */ +#define REDUN_R (1<<0) /* Used */ +#define REDUN_P(x) (((x) << 12) & 0x7f000) /* Redundant Page Address */ + +/* + * The eSi-TSMC Flash manual provides two sets of timings based on the + * underlying flash process. By default, 90nm is assumed. + */ +#if 0 /* 55nm */ +#define TNVH 5000 /* 5us */ +#define TME 80000000 /* 80ms */ +#define TERASE 160000000 /* 160ms */ +#define TRE 100000000 /* 100ms */ +#define TPROG 8000 /* 8us */ +#else /* 90nm */ +#define TNVH 5000 /* 5us */ +#define TME 20000000 /* 20ms */ +#define TERASE 40000000 /* 40ms */ +#define TRE 40000000 /* 40ms */ +#define TPROG 40000 /* 40us */ +#endif + +#define CONTROL_TIMEOUT 5000 /* 5s */ +#define PAGE_SIZE 4096 +#define PB_MAX 32 + +#define NUM_NS_PER_S 1000000000ULL + +struct esirisc_flash_bank { + bool probed; + uint32_t cfg; + uint32_t clock; + uint32_t wait_states; +}; + +FLASH_BANK_COMMAND_HANDLER(esirisc_flash_bank_command) +{ + struct esirisc_flash_bank *esirisc_info; + + if (CMD_ARGC < 9) + return ERROR_COMMAND_SYNTAX_ERROR; + + esirisc_info = calloc(1, sizeof(struct esirisc_flash_bank)); + + COMMAND_PARSE_NUMBER(u32, CMD_ARGV[6], esirisc_info->cfg); + COMMAND_PARSE_NUMBER(u32, CMD_ARGV[7], esirisc_info->clock); + COMMAND_PARSE_NUMBER(u32, CMD_ARGV[8], esirisc_info->wait_states); + + bank->driver_priv = esirisc_info; + + return ERROR_OK; +} + +/* + * Register writes are ignored if the control.WP flag is set; the + * following sequence is required to modify this flag even when + * protection is disabled. + */ +static int esirisc_flash_unlock(struct flash_bank *bank) +{ + struct esirisc_flash_bank *esirisc_info = bank->driver_priv; + struct target *target = bank->target; + + target_write_u32(target, esirisc_info->cfg + UNLOCK1, 0x7123); + target_write_u32(target, esirisc_info->cfg + UNLOCK2, 0x812a); + target_write_u32(target, esirisc_info->cfg + UNLOCK1, 0xbee1); + + return ERROR_OK; +} + +static int esirisc_flash_disable_protect(struct flash_bank *bank) +{ + struct esirisc_flash_bank *esirisc_info = bank->driver_priv; + struct target *target = bank->target; + uint32_t control; + + target_read_u32(target, esirisc_info->cfg + CONTROL, &control); + if (!(control & CONTROL_WP)) + return ERROR_OK; + + esirisc_flash_unlock(bank); + + control &= ~CONTROL_WP; + + target_write_u32(target, esirisc_info->cfg + CONTROL, control); + + return ERROR_OK; +} + +static int esirisc_flash_enable_protect(struct flash_bank *bank) +{ + struct esirisc_flash_bank *esirisc_info = bank->driver_priv; + struct target *target = bank->target; + uint32_t control; + + target_read_u32(target, esirisc_info->cfg + CONTROL, &control); + if (control & CONTROL_WP) + return ERROR_OK; + + esirisc_flash_unlock(bank); + + control |= CONTROL_WP; + + target_write_u32(target, esirisc_info->cfg + CONTROL, control); + + return ERROR_OK; +} + +static int esirisc_flash_check_status(struct flash_bank *bank) +{ + struct esirisc_flash_bank *esirisc_info = bank->driver_priv; + struct target *target = bank->target; + uint32_t status; + + target_read_u32(target, esirisc_info->cfg + STATUS, &status); + if (status & STATUS_WER) { + LOG_ERROR("%s: bad status: 0x%" PRIx32, bank->name, status); + return ERROR_FLASH_OPERATION_FAILED; + } + + return ERROR_OK; +} + +static int esirisc_flash_clear_status(struct flash_bank *bank) +{ + struct esirisc_flash_bank *esirisc_info = bank->driver_priv; + struct target *target = bank->target; + + target_write_u32(target, esirisc_info->cfg + STATUS, STATUS_WER); + + return ERROR_OK; +} + +static int esirisc_flash_wait(struct flash_bank *bank, int ms) +{ + struct esirisc_flash_bank *esirisc_info = bank->driver_priv; + struct target *target = bank->target; + uint32_t status; + int64_t t; + + t = timeval_ms(); + for (;;) { + target_read_u32(target, esirisc_info->cfg + STATUS, &status); + if (!(status & STATUS_BUSY)) + return ERROR_OK; + + if ((timeval_ms() - t) > ms) + return ERROR_TARGET_TIMEOUT; + + keep_alive(); + } +} + +static int esirisc_flash_control(struct flash_bank *bank, uint32_t control) +{ + struct esirisc_flash_bank *esirisc_info = bank->driver_priv; + struct target *target = bank->target; + + esirisc_flash_clear_status(bank); + + target_write_u32(target, esirisc_info->cfg + CONTROL, control); + + int retval = esirisc_flash_wait(bank, CONTROL_TIMEOUT); + if (retval != ERROR_OK) { + LOG_ERROR("%s: control timed out: 0x%" PRIx32, bank->name, control); + return retval; + } + + return esirisc_flash_check_status(bank); +} + +static int esirisc_flash_recall(struct flash_bank *bank) +{ + return esirisc_flash_control(bank, CONTROL_R); +} + +static int esirisc_flash_erase(struct flash_bank *bank, int first, int last) +{ + struct esirisc_flash_bank *esirisc_info = bank->driver_priv; + struct target *target = bank->target; + int retval = ERROR_OK; + + if (target->state != TARGET_HALTED) + return ERROR_TARGET_NOT_HALTED; + + esirisc_flash_disable_protect(bank); + + for (int page = first; page < last; ++page) { + uint32_t address = page * PAGE_SIZE; + + target_write_u32(target, esirisc_info->cfg + ADDRESS, address); + + retval = esirisc_flash_control(bank, CONTROL_EP); + if (retval != ERROR_OK) { + LOG_ERROR("%s: failed to erase address: 0x%" PRIx32, bank->name, address); + break; + } + } + + esirisc_flash_enable_protect(bank); + + return retval; +} + +static int esirisc_flash_mass_erase(struct flash_bank *bank) +{ + struct esirisc_flash_bank *esirisc_info = bank->driver_priv; + struct target *target = bank->target; + int retval; + + if (target->state != TARGET_HALTED) + return ERROR_TARGET_NOT_HALTED; + + esirisc_flash_disable_protect(bank); + + target_write_u32(target, esirisc_info->cfg + ADDRESS, 0); + + retval = esirisc_flash_control(bank, CONTROL_E); + if (retval != ERROR_OK) + LOG_ERROR("%s: failed to mass erase", bank->name); + + esirisc_flash_enable_protect(bank); + + return retval; +} + +/* + * Per TSMC, the reference cell should be erased once per sample. This + * is typically done during wafer sort, however we include support for + * those that may need to calibrate flash at a later time. + */ +static int esirisc_flash_ref_erase(struct flash_bank *bank) +{ + struct target *target = bank->target; + int retval; + + if (target->state != TARGET_HALTED) + return ERROR_TARGET_NOT_HALTED; + + esirisc_flash_disable_protect(bank); + + retval = esirisc_flash_control(bank, CONTROL_ERC); + if (retval != ERROR_OK) + LOG_ERROR("%s: failed to erase reference cell", bank->name); + + esirisc_flash_enable_protect(bank); + + return retval; +} + +static int esirisc_flash_protect(struct flash_bank *bank, int set, int first, int last) +{ + struct target *target = bank->target; + + if (target->state != TARGET_HALTED) + return ERROR_TARGET_NOT_HALTED; + + if (set) + esirisc_flash_enable_protect(bank); + else + esirisc_flash_disable_protect(bank); + + return ERROR_OK; +} + +static int esirisc_flash_fill_pb(struct flash_bank *bank, + const uint8_t *buffer, uint32_t count) +{ + struct esirisc_flash_bank *esirisc_info = bank->driver_priv; + struct target *target = bank->target; + struct esirisc_common *esirisc = target_to_esirisc(target); + + /* + * The pb_index register is auto-incremented when pb_data is written + * and should be cleared before each operation. + */ + target_write_u32(target, esirisc_info->cfg + PB_INDEX, 0); + + /* + * The width of the pb_data register depends on the underlying + * target; writing one byte at a time incurs a significant + * performance penalty and should be avoided. + */ + while (count > 0) { + uint32_t max_bytes = DIV_ROUND_UP(esirisc->num_bits, 8); + uint32_t num_bytes = MIN(count, max_bytes); + + target_write_buffer(target, esirisc_info->cfg + PB_DATA, num_bytes, buffer); + + buffer += num_bytes; + count -= num_bytes; + } + + return ERROR_OK; +} + +static int esirisc_flash_write(struct flash_bank *bank, + const uint8_t *buffer, uint32_t offset, uint32_t count) +{ + struct esirisc_flash_bank *esirisc_info = bank->driver_priv; + struct target *target = bank->target; + int retval = ERROR_OK; + + if (target->state != TARGET_HALTED) + return ERROR_TARGET_NOT_HALTED; + + esirisc_flash_disable_protect(bank); + + /* + * The address register is auto-incremented based on the contents of + * the pb_index register after each operation completes. It can be + * set once provided pb_index is cleared before each operation. + */ + target_write_u32(target, esirisc_info->cfg + ADDRESS, offset); + + /* + * Care must be taken when filling the program buffer; a maximum of + * 32 bytes may be written at a time and may not cross a 32-byte + * boundary based on the current offset. + */ + while (count > 0) { + uint32_t max_bytes = PB_MAX - (offset & 0x1f); + uint32_t num_bytes = MIN(count, max_bytes); + + esirisc_flash_fill_pb(bank, buffer, num_bytes); + + retval = esirisc_flash_control(bank, CONTROL_P); + if (retval != ERROR_OK) { + LOG_ERROR("%s: failed to program address: 0x%" PRIx32, bank->name, offset); + break; + } + + buffer += num_bytes; + offset += num_bytes; + count -= num_bytes; + } + + esirisc_flash_enable_protect(bank); + + return retval; +} + +static uint32_t esirisc_flash_num_cycles(struct flash_bank *bank, uint64_t ns) +{ + struct esirisc_flash_bank *esirisc_info = bank->driver_priv; + + /* apply scaling factor to avoid truncation */ + uint64_t hz = (uint64_t)esirisc_info->clock * 1000; + uint64_t num_cycles = ((hz / NUM_NS_PER_S) * ns) / 1000; + + if (hz % NUM_NS_PER_S > 0) + num_cycles++; + + return num_cycles; +} + +static int esirisc_flash_init(struct flash_bank *bank) +{ + struct esirisc_flash_bank *esirisc_info = bank->driver_priv; + struct target *target = bank->target; + uint32_t value; + int retval; + + esirisc_flash_disable_protect(bank); + + /* initialize timing registers */ + value = TIMING0_F(esirisc_flash_num_cycles(bank, TNVH)) | + TIMING0_R(esirisc_info->wait_states); + + LOG_DEBUG("TIMING0: 0x%" PRIx32, value); + target_write_u32(target, esirisc_info->cfg + TIMING0, value); + + value = TIMING1_E(esirisc_flash_num_cycles(bank, TERASE)); + + LOG_DEBUG("TIMING1: 0x%" PRIx32, value); + target_write_u32(target, esirisc_info->cfg + TIMING1, value); + + value = TIMING2_T(esirisc_flash_num_cycles(bank, 10)) | + TIMING2_H(esirisc_flash_num_cycles(bank, 100)) | + TIMING2_P(esirisc_flash_num_cycles(bank, TPROG)); + + LOG_DEBUG("TIMING2: 0x%" PRIx32, value); + target_write_u32(target, esirisc_info->cfg + TIMING2, value); + + /* recall trim code */ + retval = esirisc_flash_recall(bank); + if (retval != ERROR_OK) + LOG_ERROR("%s: failed to recall trim code", bank->name); + + esirisc_flash_enable_protect(bank); + + return retval; +} + +static int esirisc_flash_probe(struct flash_bank *bank) +{ + struct esirisc_flash_bank *esirisc_info = bank->driver_priv; + struct target *target = bank->target; + int retval; + + if (target->state != TARGET_HALTED) + return ERROR_TARGET_NOT_HALTED; + + bank->num_sectors = bank->size / PAGE_SIZE; + bank->sectors = alloc_block_array(0, PAGE_SIZE, bank->num_sectors); + + /* + * Register write protection is enforced using a single protection + * block for the entire bank. This is as good as it gets. + */ + bank->num_prot_blocks = 1; + bank->prot_blocks = alloc_block_array(0, bank->size, bank->num_prot_blocks); + + retval = esirisc_flash_init(bank); + if (retval != ERROR_OK) { + LOG_ERROR("%s: failed to initialize bank", bank->name); + return retval; + } + + esirisc_info->probed = true; + + return ERROR_OK; +} + +static int esirisc_flash_auto_probe(struct flash_bank *bank) +{ + struct esirisc_flash_bank *esirisc_info = bank->driver_priv; + + if (esirisc_info->probed) + return ERROR_OK; + + return esirisc_flash_probe(bank); +} + +static int esirisc_flash_protect_check(struct flash_bank *bank) +{ + struct esirisc_flash_bank *esirisc_info = bank->driver_priv; + struct target *target = bank->target; + uint32_t control; + + if (target->state != TARGET_HALTED) + return ERROR_TARGET_NOT_HALTED; + + target_read_u32(target, esirisc_info->cfg + CONTROL, &control); + + /* single protection block (also see: esirisc_flash_probe()) */ + bank->prot_blocks[0].is_protected = !!(control & CONTROL_WP); + + return ERROR_OK; +} + +static int esirisc_flash_info(struct flash_bank *bank, char *buf, int buf_size) +{ + struct esirisc_flash_bank *esirisc_info = bank->driver_priv; + + snprintf(buf, buf_size, + "%4s cfg at 0x%" PRIx32 ", clock %" PRId32 ", wait_states %" PRId32, + "", /* align with first line */ + esirisc_info->cfg, + esirisc_info->clock, + esirisc_info->wait_states); + + return ERROR_OK; +} + +COMMAND_HANDLER(handle_esirisc_flash_mass_erase_command) +{ + struct flash_bank *bank; + int retval; + + if (CMD_ARGC < 1) + return ERROR_COMMAND_SYNTAX_ERROR; + + retval = CALL_COMMAND_HANDLER(flash_command_get_bank, 0, &bank); + if (retval != ERROR_OK) + return retval; + + retval = esirisc_flash_mass_erase(bank); + + command_print(CMD_CTX, "mass erase %s", + (retval == ERROR_OK) ? "successful" : "failed"); + + return retval; +} + +COMMAND_HANDLER(handle_esirisc_flash_ref_erase_command) +{ + struct flash_bank *bank; + int retval; + + if (CMD_ARGC < 1) + return ERROR_COMMAND_SYNTAX_ERROR; + + retval = CALL_COMMAND_HANDLER(flash_command_get_bank, 0, &bank); + if (retval != ERROR_OK) + return retval; + + retval = esirisc_flash_ref_erase(bank); + + command_print(CMD_CTX, "erase reference cell %s", + (retval == ERROR_OK) ? "successful" : "failed"); + + return retval; +} + +static const struct command_registration esirisc_flash_exec_command_handlers[] = { + { + .name = "mass_erase", + .handler = handle_esirisc_flash_mass_erase_command, + .mode = COMMAND_EXEC, + .help = "erases all pages in data memory", + .usage = "bank_id", + }, + { + .name = "ref_erase", + .handler = handle_esirisc_flash_ref_erase_command, + .mode = COMMAND_EXEC, + .help = "erases reference cell (uncommon)", + .usage = "bank_id", + }, + COMMAND_REGISTRATION_DONE +}; + +static const struct command_registration esirisc_flash_command_handlers[] = { + { + .name = "esirisc_flash", + .mode = COMMAND_ANY, + .help = "eSi-RISC flash command group", + .usage = "", + .chain = esirisc_flash_exec_command_handlers, + }, + COMMAND_REGISTRATION_DONE +}; + +struct flash_driver esirisc_flash = { + .name = "esirisc", + .commands = esirisc_flash_command_handlers, + .usage = "flash bank bank_id 'esirisc' base_address size_bytes 0 0 target " + "cfg_address clock_hz wait_states", + .flash_bank_command = esirisc_flash_bank_command, + .erase = esirisc_flash_erase, + .protect = esirisc_flash_protect, + .write = esirisc_flash_write, + .read = default_flash_read, + .probe = esirisc_flash_probe, + .auto_probe = esirisc_flash_auto_probe, + .erase_check = default_flash_blank_check, + .protect_check = esirisc_flash_protect_check, + .info = esirisc_flash_info, +}; |