]> git.sur5r.net Git - openocd/blobdiff - src/flash/nor/esirisc_flash.c
esirisc: support eSi-RISC targets
[openocd] / src / flash / nor / esirisc_flash.c
diff --git a/src/flash/nor/esirisc_flash.c b/src/flash/nor/esirisc_flash.c
new file mode 100644 (file)
index 0000000..f3833df
--- /dev/null
@@ -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,
+};